Browse Source

线修改

feature/260608
TianYun 1 month ago
parent
commit
9562c66108
  1. 44
      src/components/__tests__/EditableEdge.test.tsx
  2. 17
      src/components/edges/EditableEdge.tsx
  3. 2
      src/components/nodes/GroupNode.tsx

44
src/components/__tests__/EditableEdge.test.tsx

@ -58,6 +58,8 @@ const createDefaultProps = (overrides = {}) => ({
const createDefaultState = (overrides = {}) => ({ const createDefaultState = (overrides = {}) => ({
edgeStyle: "angular" as const, edgeStyle: "angular" as const,
nodes: [], nodes: [],
runningNodeIds: new Set<string>(),
currentNodeIds: [] as string[],
...overrides, ...overrides,
}); });
@ -287,6 +289,23 @@ describe("EditableEdge", () => {
expect(stroke).toContain("-active"); expect(stroke).toContain("-active");
}); });
it("should show pulse animation when connected to selected node", () => {
mockUseWorkflowStore.mockImplementation((selector) => {
return selector(createDefaultState({
nodes: [{ id: "node-1", selected: true }],
}));
});
const { container } = render(
<TestWrapper>
<EditableEdge {...createDefaultProps()} />
</TestWrapper>
);
const paths = container.querySelectorAll("path");
expect(paths.length).toBeGreaterThan(2);
});
it("should have dimmed opacity when not connected to selected node", () => { it("should have dimmed opacity when not connected to selected node", () => {
mockUseWorkflowStore.mockImplementation((selector) => { mockUseWorkflowStore.mockImplementation((selector) => {
return selector(createDefaultState({ return selector(createDefaultState({
@ -308,12 +327,33 @@ describe("EditableEdge", () => {
}); });
describe("Loading Animation", () => { describe("Loading Animation", () => {
it("should show pulse animation when target node is loading", () => { it("should show pulse animation when target node is running", () => {
mockUseWorkflowStore.mockImplementation((selector) => {
return selector(createDefaultState({
nodes: [
{ id: "node-1", type: "prompt", selected: false },
{ id: "node-2", type: "generateVideo", selected: false, data: { status: "idle" } },
],
runningNodeIds: new Set(["node-2"]),
}));
});
const { container } = render(
<TestWrapper>
<EditableEdge {...createDefaultProps()} />
</TestWrapper>
);
const paths = container.querySelectorAll("path");
expect(paths.length).toBeGreaterThan(2);
});
it("should show pulse animation when target node data is loading", () => {
mockUseWorkflowStore.mockImplementation((selector) => { mockUseWorkflowStore.mockImplementation((selector) => {
return selector(createDefaultState({ return selector(createDefaultState({
nodes: [ nodes: [
{ id: "node-1", type: "prompt", selected: false }, { id: "node-1", type: "prompt", selected: false },
{ id: "node-2", type: "nanoBanana", selected: false, data: { status: "loading" } }, { id: "node-2", type: "generateAudio", selected: false, data: { status: "loading" } },
], ],
})); }));
}); });

17
src/components/edges/EditableEdge.tsx

@ -9,7 +9,7 @@ import {
useReactFlow, useReactFlow,
} from "@xyflow/react"; } from "@xyflow/react";
import { useWorkflowStore } from "@/store/workflowStore"; import { useWorkflowStore } from "@/store/workflowStore";
import { NanoBananaNodeData, WorkflowEdgeData } from "@/types"; import { WorkflowEdgeData } from "@/types";
import { getSharedGradientId } from "./SharedEdgeGradients"; import { getSharedGradientId } from "./SharedEdgeGradients";
interface EdgeData extends WorkflowEdgeData { interface EdgeData extends WorkflowEdgeData {
@ -65,12 +65,15 @@ export function EditableEdge({
const offsetY = edgeData?.offsetY ?? 0; const offsetY = edgeData?.offsetY ?? 0;
const hasPause = edgeData?.hasPause ?? false; const hasPause = edgeData?.hasPause ?? false;
// Narrow selector: only re-renders when target loading status changes // Animate incoming edges for the node-level task state, with node data status as a fallback.
const isTargetLoading = useWorkflowStore((state) => { const isTargetRunning = useWorkflowStore((state) => {
if (state.runningNodeIds.has(target) || state.currentNodeIds.includes(target)) {
return true;
}
const targetNode = state.nodes.find((n) => n.id === target); const targetNode = state.nodes.find((n) => n.id === target);
if (targetNode?.type !== "nanoBanana") return false; return (targetNode?.data as { status?: unknown } | undefined)?.status === "loading";
return (targetNode.data as NanoBananaNodeData).status === "loading";
}); });
const shouldAnimateEdge = isConnectedToSelection || isTargetRunning;
// Determine edge color based on handle type (magenta for loop edges, orange if paused) // Determine edge color based on handle type (magenta for loop edges, orange if paused)
const edgeColor = useMemo(() => { const edgeColor = useMemo(() => {
@ -233,8 +236,8 @@ export function EditableEdge({
}} }}
/> />
{/* Animated pulse overlay when target is loading */} {/* Animated pulse overlay for selected-node context and running targets */}
{isTargetLoading && ( {shouldAnimateEdge && (
<> <>
{/* Outer glow — replaces blur(6px) filter for better perf on Windows */} {/* Outer glow — replaces blur(6px) filter for better perf on Windows */}
<path <path

2
src/components/nodes/GroupNode.tsx

@ -364,7 +364,7 @@ export function GroupNode({ id, data, selected }: NodeProps<GroupNodeType>) {
type="button" type="button"
className={`nodrag nopan flex h-8 w-[96px] shrink-0 items-center justify-center gap-1 rounded px-2 text-[11px] font-medium transition-colors focus:outline-none focus:ring-1 ${isGroupRunning className={`nodrag nopan flex h-8 w-[96px] shrink-0 items-center justify-center gap-1 rounded px-2 text-[11px] font-medium transition-colors focus:outline-none focus:ring-1 ${isGroupRunning
? "text-red-300 hover:bg-red-500/10 hover:text-red-200 focus:ring-red-500/60" ? "text-red-300 hover:bg-red-500/10 hover:text-red-200 focus:ring-red-500/60"
: "text-emerald-300 hover:bg-emerald-500/10 hover:text-emerald-200 focus:ring-emerald-500/60" : "text-neutral-100 hover:bg-neutral-700 hover:text-white focus:ring-neutral-500"
}`} }`}
onClick={handleRunGroup} onClick={handleRunGroup}
title={t("group.runWholeGroup")} title={t("group.runWholeGroup")}

Loading…
Cancel
Save