Skip to content

Commit 4ebe9af

Browse files
committed
Implement Node Manager
1 parent 846582d commit 4ebe9af

27 files changed

+754
-661
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 & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,13 @@ import {
1212
TooltipTrigger,
1313
} from "@/components/ui/tooltip";
1414
import { Paragraph } from "@/components/ui/typography";
15+
import { useNodeManager } from "@/hooks/useNodeManager";
1516
import { cn } from "@/lib/utils";
1617
import { useComponentSpec } from "@/providers/ComponentSpecProvider";
1718
import { useContextPanel } from "@/providers/ContextPanelProvider";
1819
import type { IONodeData } from "@/types/nodes";
1920
import { isInputSpec, typeSpecToString } from "@/utils/componentSpec";
2021
import { ENABLE_DEBUG_MODE } from "@/utils/constants";
21-
import {
22-
inputNameToNodeId,
23-
outputNameToNodeId,
24-
} from "@/utils/nodes/nodeIdUtils";
2522

2623
interface IONodeProps {
2724
type: "input" | "output";
@@ -31,6 +28,7 @@ interface IONodeProps {
3128
}
3229

3330
const IONode = ({ type, data, selected = false }: IONodeProps) => {
31+
const { getNodeId, getHandleNodeId } = useNodeManager();
3432
const { graphSpec, componentSpec } = useComponentSpec();
3533
const { setContent, clearContent } = useContextPanel();
3634

@@ -58,11 +56,9 @@ const IONode = ({ type, data, selected = false }: IONodeProps) => {
5856
[componentSpec.outputs, spec.name],
5957
);
6058

61-
const nodeId = isInput
62-
? inputNameToNodeId(spec.name)
63-
: outputNameToNodeId(spec.name);
64-
65-
const nodeHandleId = `${nodeId}_handle`;
59+
const nodeId = getNodeId(spec.name, type);
60+
const handleNodeType = isInput ? "outputHandle" : "inputHandle";
61+
const nodeHandleId = getHandleNodeId(spec.name, spec.name, handleNodeType);
6662

6763
useEffect(() => {
6864
if (selected) {
@@ -163,6 +159,7 @@ const IONode = ({ type, data, selected = false }: IONodeProps) => {
163159
<Tooltip>
164160
<TooltipTrigger asChild>
165161
<Handle
162+
id={nodeHandleId}
166163
type={handleType}
167164
position={handlePosition}
168165
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";
@@ -33,7 +34,8 @@ export const InputHandle = ({
3334
onLabelClick,
3435
onHandleSelectionChange,
3536
}: InputHandleProps) => {
36-
const { nodeId, state, name } = useTaskNode();
37+
const { getInputHandleNodeId } = useNodeManager();
38+
const { taskId, nodeId, state, name } = useTaskNode();
3739

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

48-
const handleId = getInputHandleId(input.name);
50+
const handleId = getInputHandleNodeId(taskId, input.name);
4951

5052
const missing = invalid ? "bg-red-700!" : "bg-gray-500!";
5153
const hasValue = value !== undefined && value !== null;
@@ -229,7 +231,8 @@ export const OutputHandle = ({
229231
onLabelClick,
230232
onHandleSelectionChange,
231233
}: OutputHandleProps) => {
232-
const { nodeId, state, name } = useTaskNode();
234+
const { getOutputHandleNodeId } = useNodeManager();
235+
const { taskId, nodeId, state, name } = useTaskNode();
233236

234237
const fromHandle = useConnection((connection) => connection.fromHandle?.id);
235238
const toHandle = useConnection((connection) => connection.toHandle?.id);
@@ -241,7 +244,7 @@ export const OutputHandle = ({
241244
const [selected, setSelected] = useState(false);
242245
const [active, setActive] = useState(false);
243246

244-
const handleId = getOutputHandleId(output.name);
247+
const handleId = getOutputHandleNodeId(taskId, output.name);
245248
const hasValue = value !== undefined && value !== "" && value !== null;
246249

247250
const handleHandleClick = useCallback(
@@ -376,14 +379,6 @@ export const OutputHandle = ({
376379
);
377380
};
378381

379-
const getOutputHandleId = (outputName: string) => {
380-
return `output_${outputName}`;
381-
};
382-
383-
const getInputHandleId = (inputName: string) => {
384-
return `input_${inputName}`;
385-
};
386-
387382
const skipHandleDeselect = (e: MouseEvent) => {
388383
let el = e.target as HTMLElement | null;
389384
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)