Skip to content

Commit d672fba

Browse files
committed
Implement Node Manager
1 parent 59a4d8f commit d672fba

27 files changed

+751
-650
lines changed

src/components/Editor/IOEditor/InputValueEditor/InputValueEditor.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@ import { Icon } from "@/components/ui/icon";
99
import { BlockStack } from "@/components/ui/layout";
1010
import { Heading, Paragraph } from "@/components/ui/typography";
1111
import useConfirmationDialog from "@/hooks/useConfirmationDialog";
12+
import { useNodeManager } from "@/hooks/useNodeManager";
1213
import { useNodeSelectionTransfer } from "@/hooks/useNodeSelectionTransfer";
1314
import useToastNotification from "@/hooks/useToastNotification";
1415
import { useComponentSpec } from "@/providers/ComponentSpecProvider";
1516
import { useContextPanel } from "@/providers/ContextPanelProvider";
1617
import { type InputSpec } from "@/utils/componentSpec";
1718
import { checkInputConnectionToRequiredFields } from "@/utils/inputConnectionUtils";
18-
import { inputNameToNodeId } from "@/utils/nodes/nodeIdUtils";
1919
import { updateSubgraphSpec } from "@/utils/subgraphUtils";
2020

2121
import { NameField, TextField, TypeField } from "./FormFields/FormFields";
@@ -31,8 +31,10 @@ export const InputValueEditor = ({
3131
input,
3232
disabled = false,
3333
}: InputValueEditorProps) => {
34+
const { getInputNodeId } = useNodeManager();
35+
3436
const notify = useToastNotification();
35-
const { transferSelection } = useNodeSelectionTransfer(inputNameToNodeId);
37+
const { transferSelection } = useNodeSelectionTransfer(getInputNodeId);
3638
const {
3739
componentSpec,
3840
setComponentSpec,

src/components/Editor/IOEditor/OutputNameEditor/OutputNameEditor.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ import { Icon } from "@/components/ui/icon";
77
import { BlockStack, InlineStack } from "@/components/ui/layout";
88
import { Heading, Paragraph } from "@/components/ui/typography";
99
import useConfirmationDialog from "@/hooks/useConfirmationDialog";
10+
import { useNodeManager } from "@/hooks/useNodeManager";
1011
import { useNodeSelectionTransfer } from "@/hooks/useNodeSelectionTransfer";
1112
import { useComponentSpec } from "@/providers/ComponentSpecProvider";
1213
import { useContextPanel } from "@/providers/ContextPanelProvider";
1314
import { type OutputSpec } from "@/utils/componentSpec";
14-
import { outputNameToNodeId } from "@/utils/nodes/nodeIdUtils";
1515
import { updateSubgraphSpec } from "@/utils/subgraphUtils";
1616

1717
import { type OutputConnectedDetails } from "../../utils/getOutputConnectedDetails";
@@ -30,7 +30,8 @@ export const OutputNameEditor = ({
3030
disabled,
3131
connectedDetails,
3232
}: OutputNameEditorProps) => {
33-
const { transferSelection } = useNodeSelectionTransfer(outputNameToNodeId);
33+
const { getOutputNodeId } = useNodeManager();
34+
const { transferSelection } = useNodeSelectionTransfer(getOutputNodeId);
3435
const {
3536
setComponentSpec,
3637
componentSpec,

src/components/shared/ReactFlow/FlowCanvas/FlowCanvas.tsx

Lines changed: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ const FlowCanvas = ({
119119
currentSubgraphSpec,
120120
updateGraphSpec,
121121
currentSubgraphPath,
122+
nodeManager,
122123
} = useComponentSpec();
123124
const { preserveIOSelectionOnSpecChange, resetPrevSpec } =
124125
useIOSelectionPersistence();
@@ -285,10 +286,18 @@ const FlowCanvas = ({
285286
let updatedSubgraphSpec = { ...currentSubgraphSpec };
286287

287288
for (const edge of params.edges) {
288-
updatedSubgraphSpec = removeEdge(edge, updatedSubgraphSpec);
289+
updatedSubgraphSpec = removeEdge(
290+
edge,
291+
updatedSubgraphSpec,
292+
nodeManager,
293+
);
289294
}
290295
for (const node of params.nodes) {
291-
updatedSubgraphSpec = removeNode(node, updatedSubgraphSpec);
296+
updatedSubgraphSpec = removeNode(
297+
node,
298+
updatedSubgraphSpec,
299+
nodeManager,
300+
);
292301
}
293302

294303
const updatedRootSpec = updateSubgraphSpec(
@@ -299,7 +308,13 @@ const FlowCanvas = ({
299308

300309
setComponentSpec(updatedRootSpec);
301310
},
302-
[componentSpec, currentSubgraphSpec, currentSubgraphPath, setComponentSpec],
311+
[
312+
componentSpec,
313+
currentSubgraphSpec,
314+
currentSubgraphPath,
315+
nodeManager,
316+
setComponentSpec,
317+
],
303318
);
304319

305320
const nodeCallbacks = useNodeCallbacks({
@@ -313,18 +328,23 @@ const FlowCanvas = ({
313328
connectable: !readOnly && !!nodesConnectable,
314329
readOnly,
315330
callbacks: nodeCallbacks,
331+
nodeManager,
316332
}),
317-
[readOnly, nodesConnectable, nodeCallbacks],
333+
[readOnly, nodesConnectable, nodeCallbacks, nodeManager],
318334
);
319335

320336
const onConnect = useCallback(
321337
(connection: Connection) => {
322338
if (connection.source === connection.target) return;
323339

324-
const updatedGraphSpec = handleConnection(currentGraphSpec, connection);
340+
const updatedGraphSpec = handleConnection(
341+
currentGraphSpec,
342+
connection,
343+
nodeManager,
344+
);
325345
updateGraphSpec(updatedGraphSpec);
326346
},
327-
[currentGraphSpec, handleConnection, updateGraphSpec],
347+
[currentGraphSpec, nodeManager, handleConnection, updateGraphSpec],
328348
);
329349

330350
const onConnectEnd = useCallback(
@@ -361,19 +381,30 @@ const FlowCanvas = ({
361381
);
362382

363383
if (existingInputEdge) {
364-
newComponentSpec = removeEdge(existingInputEdge, newComponentSpec);
384+
newComponentSpec = removeEdge(
385+
existingInputEdge,
386+
newComponentSpec,
387+
nodeManager,
388+
);
365389
}
366390

367391
const updatedComponentSpec = addAndConnectNode({
368392
componentRef,
369393
fromHandle,
370394
position,
371395
componentSpec: newComponentSpec,
396+
nodeManager,
372397
});
373398

374399
setComponentSpec(updatedComponentSpec);
375400
},
376-
[reactFlowInstance, componentSpec, setComponentSpec, updateOrAddNodes],
401+
[
402+
reactFlowInstance,
403+
componentSpec,
404+
nodeManager,
405+
setComponentSpec,
406+
updateOrAddNodes,
407+
],
377408
);
378409

379410
useEffect(() => {
@@ -627,6 +658,7 @@ const FlowCanvas = ({
627658
const updatedSubgraphSpec = updateNodePositions(
628659
updatedNodes,
629660
currentSubgraphSpec,
661+
nodeManager,
630662
);
631663

632664
const updatedRootSpec = updateSubgraphSpec(
@@ -646,6 +678,7 @@ const FlowCanvas = ({
646678
componentSpec,
647679
currentSubgraphSpec,
648680
currentSubgraphPath,
681+
nodeManager,
649682
setComponentSpec,
650683
onNodesChange,
651684
],
@@ -677,7 +710,9 @@ const FlowCanvas = ({
677710
updatedComponentSpec: updatedSubgraphSpec,
678711
newNodes,
679712
updatedNodes,
680-
} = duplicateNodes(currentSubgraphSpec, selectedNodes, { selected: true });
713+
} = duplicateNodes(currentSubgraphSpec, selectedNodes, nodeManager, {
714+
selected: true,
715+
});
681716

682717
const updatedRootSpec = updateSubgraphSpec(
683718
componentSpec,
@@ -696,6 +731,7 @@ const FlowCanvas = ({
696731
currentSubgraphSpec,
697732
currentSubgraphPath,
698733
selectedNodes,
734+
nodeManager,
699735
setComponentSpec,
700736
setNodes,
701737
]);
@@ -873,6 +909,7 @@ const FlowCanvas = ({
873909
const { newNodes, updatedComponentSpec } = duplicateNodes(
874910
componentSpec,
875911
nodesToPaste,
912+
nodeManager,
876913
{ position: reactFlowCenter, connection: "internal" },
877914
);
878915

@@ -898,6 +935,7 @@ const FlowCanvas = ({
898935
nodes,
899936
reactFlowInstance,
900937
store,
938+
nodeManager,
901939
updateOrAddNodes,
902940
setComponentSpec,
903941
readOnly,

src/components/shared/ReactFlow/FlowCanvas/IONode/IONode.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { getOutputConnectedDetails } from "@/components/Editor/utils/getOutputCo
77
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
88
import { BlockStack, InlineStack } from "@/components/ui/layout";
99
import { Paragraph } from "@/components/ui/typography";
10+
import { useNodeManager } from "@/hooks/useNodeManager";
1011
import { cn } from "@/lib/utils";
1112
import { useComponentSpec } from "@/providers/ComponentSpecProvider";
1213
import { useContextPanel } from "@/providers/ContextPanelProvider";
@@ -23,6 +24,7 @@ interface IONodeProps {
2324
const IONode = ({ type, data, selected = false }: IONodeProps) => {
2425
const { currentGraphSpec, currentSubgraphSpec } = useComponentSpec();
2526
const { setContent, clearContent } = useContextPanel();
27+
const { getHandleNodeId } = useNodeManager();
2628

2729
const { spec, readOnly } = data;
2830

@@ -49,6 +51,9 @@ const IONode = ({ type, data, selected = false }: IONodeProps) => {
4951
[currentSubgraphSpec.outputs, spec.name],
5052
);
5153

54+
const handleNodeType = isInput ? "handle-out" : "handle-in";
55+
const nodeHandleId = getHandleNodeId(spec.name, spec.name, handleNodeType);
56+
5257
useEffect(() => {
5358
if (selected) {
5459
if (input && isInput) {
@@ -144,6 +149,7 @@ const IONode = ({ type, data, selected = false }: IONodeProps) => {
144149
</InlineStack>
145150
</BlockStack>
146151
<Handle
152+
id={nodeHandleId}
147153
type={handleType}
148154
position={handlePosition}
149155
className={cn(handleDefaultClassName, handleClassName)}

src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskNodeCard/Handles.tsx

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
TooltipContent,
1212
TooltipTrigger,
1313
} from "@/components/ui/tooltip";
14+
import { useNodeManager } from "@/hooks/useNodeManager";
1415
import { cn } from "@/lib/utils";
1516
import { useTaskNode } from "@/providers/TaskNodeProvider";
1617
import type { InputSpec, OutputSpec } from "@/utils/componentSpec";
@@ -32,7 +33,8 @@ export const InputHandle = ({
3233
onLabelClick,
3334
onHandleSelectionChange,
3435
}: InputHandleProps) => {
35-
const { nodeId, state } = useTaskNode();
36+
const { getInputHandleNodeId } = useNodeManager();
37+
const { taskId, nodeId, state } = useTaskNode();
3638

3739
const fromHandle = useConnection((connection) => connection.fromHandle?.id);
3840
const toHandle = useConnection((connection) => connection.toHandle?.id);
@@ -44,7 +46,7 @@ export const InputHandle = ({
4446
const [selected, setSelected] = useState(false);
4547
const [active, setActive] = useState(false);
4648

47-
const handleId = getInputHandleId(input.name);
49+
const handleId = getInputHandleNodeId(taskId, input.name);
4850

4951
const missing = invalid ? "bg-red-700!" : "bg-gray-500!";
5052
const hasValue = value !== undefined && value !== null;
@@ -218,7 +220,8 @@ export const OutputHandle = ({
218220
onLabelClick,
219221
onHandleSelectionChange,
220222
}: OutputHandleProps) => {
221-
const { nodeId, state } = useTaskNode();
223+
const { getOutputHandleNodeId } = useNodeManager();
224+
const { taskId, nodeId, state } = useTaskNode();
222225

223226
const fromHandle = useConnection((connection) => connection.fromHandle?.id);
224227
const toHandle = useConnection((connection) => connection.toHandle?.id);
@@ -230,7 +233,7 @@ export const OutputHandle = ({
230233
const [selected, setSelected] = useState(false);
231234
const [active, setActive] = useState(false);
232235

233-
const handleId = getOutputHandleId(output.name);
236+
const handleId = getOutputHandleNodeId(taskId, output.name);
234237
const hasValue = value !== undefined && value !== "" && value !== null;
235238

236239
const handleHandleClick = useCallback(
@@ -355,14 +358,6 @@ export const OutputHandle = ({
355358
);
356359
};
357360

358-
const getOutputHandleId = (outputName: string) => {
359-
return `output_${outputName}`;
360-
};
361-
362-
const getInputHandleId = (inputName: string) => {
363-
return `input_${inputName}`;
364-
};
365-
366361
const skipHandleDeselect = (e: MouseEvent) => {
367362
let el = e.target as HTMLElement | null;
368363
while (el) {

src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskNodeCard/TaskNodeInputs.test.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,15 @@ vi.mock("@/providers/ComponentLibraryProvider", () => ({
1717
}),
1818
}));
1919

20+
vi.mock("@/providers/ComponentLibraryProvider/ForcedSearchProvider", () => ({
21+
useForcedSearchContext: () => ({
22+
highlightSearchFilter: vi.fn(),
23+
resetSearchFilter: vi.fn(),
24+
currentSearchFilter: { searchTerm: "", filters: [] },
25+
highlightSearchResults: false,
26+
}),
27+
}));
28+
2029
vi.mock("@/providers/ComponentSpecProvider", () => ({
2130
useComponentSpec: () => ({
2231
graphSpec: {
@@ -25,6 +34,18 @@ vi.mock("@/providers/ComponentSpecProvider", () => ({
2534
}),
2635
}));
2736

37+
vi.mock("@/hooks/useNodeManager", () => ({
38+
useNodeManager: () => ({
39+
getInputHandleNodeId: vi.fn(
40+
(_refId: string, inputName: string) => `input-handle-${inputName}`,
41+
),
42+
getOutputHandleNodeId: vi.fn(),
43+
getNodeId: vi.fn(),
44+
getHandleNodeId: vi.fn(),
45+
nodeManager: {},
46+
}),
47+
}));
48+
2849
vi.mock("@/providers/TaskNodeProvider");
2950

3051
const TestWrapper = ReactFlowProvider;
@@ -39,6 +60,7 @@ describe("<TaskNodeInputs />", () => {
3960
state: { readOnly: false },
4061
select: vi.fn(),
4162
nodeId: "test-node",
63+
taskId: "test-task",
4264
} as any);
4365
};
4466

src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskNodeCard/TaskNodeInputs.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { useConnection } from "@xyflow/react";
22
import { AlertCircle } from "lucide-react";
33
import { type MouseEvent, useCallback, useEffect, useState } from "react";
44

5+
import { useNodeManager } from "@/hooks/useNodeManager";
56
import { cn } from "@/lib/utils";
67
import { useForcedSearchContext } from "@/providers/ComponentLibraryProvider/ForcedSearchProvider";
78
import { isValidFilterRequest } from "@/providers/ComponentLibraryProvider/types";
@@ -10,7 +11,6 @@ import { useTaskNode } from "@/providers/TaskNodeProvider";
1011
import { inputsWithInvalidArguments } from "@/services/componentService";
1112
import type { InputSpec } from "@/utils/componentSpec";
1213
import { ComponentSearchFilter } from "@/utils/constants";
13-
import { inputNameToNodeId } from "@/utils/nodes/nodeIdUtils";
1414
import { checkArtifactMatchesSearchFilters } from "@/utils/searchUtils";
1515

1616
import { InputHandle } from "./Handles";
@@ -27,7 +27,8 @@ export function TaskNodeInputs({
2727
expanded,
2828
onBackgroundClick,
2929
}: TaskNodeInputsProps) {
30-
const { inputs, taskSpec, state, select } = useTaskNode();
30+
const { getInputHandleNodeId } = useNodeManager();
31+
const { taskId, inputs, taskSpec, state, select } = useTaskNode();
3132
const { graphSpec } = useComponentSpec();
3233
const {
3334
highlightSearchFilter,
@@ -147,7 +148,7 @@ export function TaskNodeInputs({
147148
}
148149

149150
const input = inputs.find(
150-
(i) => inputNameToNodeId(i.name) === fromHandle?.id,
151+
(i) => getInputHandleNodeId(taskId, i.name) === fromHandle?.id,
151152
);
152153

153154
if (!input) return;

0 commit comments

Comments
 (0)