Skip to content

Commit 705d37e

Browse files
committed
Implement Node Manager
1 parent 07c7df4 commit 705d37e

27 files changed

+754
-652
lines changed

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

Lines changed: 5 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

2020
import { NameField, TextField, TypeField } from "./FormFields/FormFields";
2121
import { checkNameCollision } from "./FormFields/utils";
@@ -30,8 +30,11 @@ export const InputValueEditor = ({
3030
input,
3131
disabled = false,
3232
}: InputValueEditorProps) => {
33+
const { getInputNodeId } = useNodeManager();
34+
3335
const notify = useToastNotification();
34-
const { transferSelection } = useNodeSelectionTransfer(inputNameToNodeId);
36+
37+
const { transferSelection } = useNodeSelectionTransfer(getInputNodeId);
3538
const { componentSpec, setComponentSpec } = useComponentSpec();
3639
const { clearContent } = useContextPanel();
3740
const {

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

Lines changed: 4 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

1616
import { type OutputConnectedDetails } from "../../utils/getOutputConnectedDetails";
1717
import { updateOutputNameOnComponentSpec } from "../../utils/updateOutputNameOnComponentSpec";
@@ -29,7 +29,9 @@ export const OutputNameEditor = ({
2929
disabled,
3030
connectedDetails,
3131
}: OutputNameEditorProps) => {
32-
const { transferSelection } = useNodeSelectionTransfer(outputNameToNodeId);
32+
const { getOutputNodeId } = useNodeManager();
33+
34+
const { transferSelection } = useNodeSelectionTransfer(getOutputNodeId);
3335
const { setComponentSpec, componentSpec } = useComponentSpec();
3436
const { clearContent } = useContextPanel();
3537
const {

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

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ const FlowCanvas = ({
115115
graphSpec,
116116
updateGraphSpec,
117117
currentSubgraphPath,
118+
nodeManager,
118119
} = useComponentSpec();
119120
const { preserveIOSelectionOnSpecChange, resetPrevSpec } =
120121
useIOSelectionPersistence();
@@ -286,15 +287,23 @@ const FlowCanvas = ({
286287
let updatedComponentSpec = { ...componentSpec };
287288

288289
for (const edge of params.edges) {
289-
updatedComponentSpec = removeEdge(edge, updatedComponentSpec);
290+
updatedComponentSpec = removeEdge(
291+
edge,
292+
updatedComponentSpec,
293+
nodeManager,
294+
);
290295
}
291296
for (const node of params.nodes) {
292-
updatedComponentSpec = removeNode(node, updatedComponentSpec);
297+
updatedComponentSpec = removeNode(
298+
node,
299+
updatedComponentSpec,
300+
nodeManager,
301+
);
293302
}
294303

295304
setComponentSpec(updatedComponentSpec);
296305
},
297-
[componentSpec, setComponentSpec],
306+
[componentSpec, nodeManager, setComponentSpec],
298307
);
299308

300309
const nodeCallbacks = useNodeCallbacks({
@@ -308,15 +317,20 @@ const FlowCanvas = ({
308317
connectable: !readOnly && !!nodesConnectable,
309318
readOnly,
310319
callbacks: nodeCallbacks,
320+
nodeManager,
311321
}),
312-
[readOnly, nodesConnectable, nodeCallbacks],
322+
[readOnly, nodesConnectable, nodeCallbacks, nodeManager],
313323
);
314324

315325
const onConnect = useCallback(
316326
(connection: Connection) => {
317327
if (connection.source === connection.target) return;
318328

319-
const updatedGraphSpec = handleConnection(graphSpec, connection);
329+
const updatedGraphSpec = handleConnection(
330+
graphSpec,
331+
connection,
332+
nodeManager,
333+
);
320334
updateGraphSpec(updatedGraphSpec);
321335
},
322336
[graphSpec, handleConnection, updateGraphSpec],
@@ -356,19 +370,30 @@ const FlowCanvas = ({
356370
);
357371

358372
if (existingInputEdge) {
359-
newComponentSpec = removeEdge(existingInputEdge, newComponentSpec);
373+
newComponentSpec = removeEdge(
374+
existingInputEdge,
375+
newComponentSpec,
376+
nodeManager,
377+
);
360378
}
361379

362380
const updatedComponentSpec = addAndConnectNode({
363381
componentRef,
364382
fromHandle,
365383
position,
366384
componentSpec: newComponentSpec,
385+
nodeManager,
367386
});
368387

369388
setComponentSpec(updatedComponentSpec);
370389
},
371-
[reactFlowInstance, componentSpec, setComponentSpec, updateOrAddNodes],
390+
[
391+
reactFlowInstance,
392+
componentSpec,
393+
nodeManager,
394+
setComponentSpec,
395+
updateOrAddNodes,
396+
],
372397
);
373398

374399
useEffect(() => {
@@ -615,14 +640,15 @@ const FlowCanvas = ({
615640
const updatedComponentSpec = updateNodePositions(
616641
updatedNodes,
617642
componentSpec,
643+
nodeManager,
618644
);
619645
setComponentSpec(updatedComponentSpec);
620646
}
621647
}
622648

623649
onNodesChange(changes);
624650
},
625-
[nodes, componentSpec, setComponentSpec, onNodesChange],
651+
[nodes, componentSpec, nodeManager, setComponentSpec, onNodesChange],
626652
);
627653

628654
const handleBeforeDelete = async (params: NodesAndEdges) => {
@@ -650,6 +676,7 @@ const FlowCanvas = ({
650676
const { updatedComponentSpec, newNodes, updatedNodes } = duplicateNodes(
651677
componentSpec,
652678
selectedNodes,
679+
nodeManager,
653680
{ selected: true },
654681
);
655682

@@ -659,7 +686,7 @@ const FlowCanvas = ({
659686
updatedNodes,
660687
newNodes,
661688
});
662-
}, [componentSpec, selectedNodes, setComponentSpec, setNodes]);
689+
}, [componentSpec, selectedNodes, nodeManager, setComponentSpec, setNodes]);
663690

664691
const onUpgradeNodes = useCallback(async () => {
665692
let newGraphSpec = graphSpec;
@@ -828,6 +855,7 @@ const FlowCanvas = ({
828855
const { newNodes, updatedComponentSpec } = duplicateNodes(
829856
componentSpec,
830857
nodesToPaste,
858+
nodeManager,
831859
{ position: reactFlowCenter, connection: "internal" },
832860
);
833861

@@ -853,6 +881,7 @@ const FlowCanvas = ({
853881
nodes,
854882
reactFlowInstance,
855883
store,
884+
nodeManager,
856885
updateOrAddNodes,
857886
setComponentSpec,
858887
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";
@@ -21,6 +22,7 @@ interface IONodeProps {
2122
}
2223

2324
const IONode = ({ type, data, selected = false }: IONodeProps) => {
25+
const { getHandleNodeId } = useNodeManager();
2426
const { graphSpec, componentSpec } = useComponentSpec();
2527
const { setContent, clearContent } = useContextPanel();
2628

@@ -48,6 +50,9 @@ const IONode = ({ type, data, selected = false }: IONodeProps) => {
4850
[componentSpec.outputs, spec.name],
4951
);
5052

53+
const handleNodeType = isInput ? "handle-out" : "handle-in";
54+
const nodeHandleId = getHandleNodeId(spec.name, spec.name, handleNodeType);
55+
5156
useEffect(() => {
5257
if (selected) {
5358
if (input && isInput) {
@@ -140,6 +145,7 @@ const IONode = ({ type, data, selected = false }: IONodeProps) => {
140145
</InlineStack>
141146
</BlockStack>
142147
<Handle
148+
id={nodeHandleId}
143149
type={handleType}
144150
position={handlePosition}
145151
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,
@@ -145,7 +146,7 @@ export function TaskNodeInputs({
145146
}
146147

147148
const input = inputs.find(
148-
(i) => inputNameToNodeId(i.name) === fromHandle?.id,
149+
(i) => getInputHandleNodeId(taskId, i.name) === fromHandle?.id,
149150
);
150151

151152
if (!input) return;

0 commit comments

Comments
 (0)