Skip to content

Commit df6acf4

Browse files
committed
feat: Duplicate Flex Nodes
1 parent 34106a3 commit df6acf4

3 files changed

Lines changed: 95 additions & 17 deletions

File tree

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ import { removeNode } from "./utils/removeNode";
8585
import { replaceTaskNode } from "./utils/replaceTaskNode";
8686
import { updateNodePositions } from "./utils/updateNodePosition";
8787

88-
const SELECTABLE_NODES = new Set(["task", "input", "output"]);
88+
const SELECTABLE_NODES = new Set(["task", "input", "output", "flex"]);
8989
const UPGRADEABLE_NODES = new Set(["task"]);
9090
const REPLACEABLE_NODES = new Set(["task"]);
9191
const FAST_PLACE_NODE_TYPES = new Set<Node["type"]>(["task"]);
@@ -737,7 +737,10 @@ const FlowCanvas = ({
737737
updatedComponentSpec: updatedSubgraphSpec,
738738
newNodes,
739739
updatedNodes,
740-
} = duplicateNodes(currentSubgraphSpec, selectedNodes, { selected: true });
740+
} = duplicateNodes(currentSubgraphSpec, selectedNodes, {
741+
selected: true,
742+
author: currentUserDetails?.id,
743+
});
741744

742745
const updatedRootSpec = updateSubgraphSpec(
743746
componentSpec,
@@ -932,6 +935,7 @@ const FlowCanvas = ({
932935
duplicateNodes(currentSubgraphSpec, nodesToPaste, {
933936
position: reactFlowCenter,
934937
connection: "internal",
938+
author: currentUserDetails?.id,
935939
});
936940

937941
// Deselect all existing nodes

src/components/shared/ReactFlow/FlowCanvas/utils/addFlexNode.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ const addFlexNode = (
3232
position: XYPosition,
3333
author: string,
3434
componentSpec: ComponentSpec,
35+
data?: Partial<FlexNodeData>,
3536
): AddFlexNodeResult => {
3637
const nodeId = nanoid();
3738

@@ -40,13 +41,15 @@ const addFlexNode = (
4041
createdBy: author,
4142
};
4243

44+
const { properties, size, zIndex } = data || {};
45+
4346
const flexNode: FlexNodeData = {
4447
id: nodeId,
45-
properties: DEFAULT_STICKY_NOTE,
48+
properties: properties ?? DEFAULT_STICKY_NOTE,
4649
metadata,
47-
size: DEFAULT_FLEX_NODE_SIZE,
50+
size: size ?? DEFAULT_FLEX_NODE_SIZE,
4851
position: position,
49-
zIndex: Z_INDEX_RANGES.flex.default,
52+
zIndex: zIndex ?? Z_INDEX_RANGES.flex.default,
5053
};
5154

5255
const newComponentSpec = updateFlexNodeInComponentSpec(

src/components/shared/ReactFlow/FlowCanvas/utils/duplicateNodes.ts

Lines changed: 83 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import {
77
type GraphInputArgument,
88
type InputSpec,
99
isGraphImplementation,
10+
isGraphInputArgument,
11+
isTaskOutputArgument,
1012
type OutputSpec,
1113
type TaskOutputArgument,
1214
type TaskSpec,
@@ -28,6 +30,14 @@ import {
2830
getUniqueTaskName,
2931
} from "@/utils/unique";
3032

33+
import {
34+
getFlexNode,
35+
updateFlexNodeInComponentSpec,
36+
} from "../FlexNode/interface";
37+
import type { FlexNodeData } from "../FlexNode/types";
38+
import { createFlexNode } from "../FlexNode/utils";
39+
import { isFlexNode } from "../types";
40+
import addFlexNode from "./addFlexNode";
3141
import { getNodesBounds } from "./geometry";
3242

3343
const OFFSET = 10;
@@ -49,6 +59,7 @@ export const duplicateNodes = (
4959
position?: XYPosition;
5060
connection?: ConnectionMode;
5161
status?: boolean;
62+
author?: string;
5263
},
5364
) => {
5465
if (!isGraphImplementation(componentSpec.implementation)) {
@@ -62,10 +73,12 @@ export const duplicateNodes = (
6273
const newTasks: Record<string, TaskSpec> = {};
6374
const newInputs: Record<string, InputSpec> = {};
6475
const newOutputs: Record<string, OutputSpec> = {};
76+
const newFlexNodes: Record<string, FlexNodeData> = {};
6577

6678
// Default Config
6779
const selected = config?.selected ?? true;
6880
const connection = config?.connection ?? "all";
81+
const author = config?.author ?? "system";
6982

7083
/* Create new Nodes and map old Task IDs to new Task IDs */
7184
nodesToDuplicate.forEach((node) => {
@@ -141,6 +154,27 @@ export const duplicateNodes = (
141154
};
142155

143156
newOutputs[newOutputId] = newOutputSpec;
157+
} else if (isFlexNode(node)) {
158+
const flexNode = getFlexNode(node.id, componentSpec);
159+
const { spec: updatedComponentSpec, nodeId: newNodeId } = addFlexNode(
160+
{
161+
x: node.position.x + OFFSET,
162+
y: node.position.y + OFFSET,
163+
},
164+
author,
165+
componentSpec,
166+
flexNode ? { ...flexNode, id: undefined } : undefined,
167+
);
168+
169+
const newNodeData = getFlexNode(newNodeId, updatedComponentSpec);
170+
if (!newNodeData) {
171+
throw new Error("Failed to retrieve newly created Flex Node data.");
172+
}
173+
174+
newFlexNodes[newNodeId] = newNodeData;
175+
nodeIdMap[oldNodeId] = newNodeId;
176+
} else {
177+
throw new Error(`Unsupported node type: ${node.type}`);
144178
}
145179
});
146180

@@ -153,11 +187,7 @@ export const duplicateNodes = (
153187
const newTaskSpec = newTasks[taskId];
154188

155189
// Check if the Argument is a connection to another Task or Input Node (i.e. TaskOutput or GraphInput) or a static value
156-
if (
157-
typeof argument === "object" &&
158-
argument !== null &&
159-
("taskOutput" in argument || "graphInput" in argument)
160-
) {
190+
if (isTaskOutputArgument(argument) || isGraphInputArgument(argument)) {
161191
newTasks[taskId] = reconfigureConnections(
162192
newTaskSpec,
163193
argKey,
@@ -217,7 +247,7 @@ export const duplicateNodes = (
217247
updatedOutputValue !== null &&
218248
connection !== "external"
219249
) {
220-
if ("taskOutput" in updatedOutputValue) {
250+
if (isTaskOutputArgument(updatedOutputValue)) {
221251
const oldTaskId = updatedOutputValue.taskOutput.taskId;
222252
const oldTaskNodeId = taskIdToNodeId(oldTaskId);
223253
if (oldTaskNodeId in nodeIdMap) {
@@ -348,6 +378,28 @@ export const duplicateNodes = (
348378

349379
updatedNodes.push(originalNode);
350380

381+
return newNode;
382+
} else if (isFlexNode(originalNode)) {
383+
const newNodeData = newFlexNodes[newNodeId];
384+
385+
if (!newNodeData) {
386+
return null;
387+
}
388+
389+
const newNode = createFlexNode(newNodeData);
390+
391+
newNode.id = newNodeId;
392+
393+
// Move selection to new node by default
394+
if (selected) {
395+
originalNode.selected = false;
396+
newNode.selected = true;
397+
}
398+
399+
newNode.measured = originalNode.measured;
400+
401+
updatedNodes.push(originalNode);
402+
351403
return newNode;
352404
}
353405
})
@@ -447,13 +499,26 @@ export const duplicateNodes = (
447499
if (updatedOutputIndex !== -1) {
448500
updatedOutputs[updatedOutputIndex] = newOutputSpec;
449501
}
502+
} else if (isFlexNode(node)) {
503+
const flexNode = getFlexNode(node.id, componentSpec);
504+
505+
if (!flexNode) {
506+
return;
507+
}
508+
509+
const newFlexNodeData = {
510+
...flexNode,
511+
position: newPosition,
512+
};
513+
514+
newFlexNodes[node.id] = newFlexNodeData;
450515
}
451516

452517
node.position = newPosition;
453518
});
454519
}
455520

456-
const updatedComponentSpec = {
521+
const updatedComponentSpec: ComponentSpec = {
457522
...componentSpec,
458523
inputs: updatedInputs,
459524
outputs: updatedOutputs,
@@ -463,7 +528,13 @@ export const duplicateNodes = (
463528
updatedComponentSpec.implementation.graph = updatedGraphSpec;
464529
}
465530

466-
return { updatedComponentSpec, nodeIdMap, newNodes, updatedNodes };
531+
/* Handle Flex Nodes */
532+
let spec = updatedComponentSpec;
533+
Object.values(newFlexNodes).forEach((newNodeData) => {
534+
spec = updateFlexNodeInComponentSpec(spec, newNodeData);
535+
});
536+
537+
return { updatedComponentSpec: spec, nodeIdMap, newNodes, updatedNodes };
467538
};
468539

469540
function reconfigureConnections(
@@ -479,7 +550,7 @@ function reconfigureConnections(
479550
let newArgId = undefined;
480551
let isExternal = false;
481552

482-
if ("taskOutput" in argument) {
553+
if (isTaskOutputArgument(argument)) {
483554
const oldTaskId = argument.taskOutput.taskId;
484555
oldNodeId = taskIdToNodeId(oldTaskId);
485556

@@ -499,7 +570,7 @@ function reconfigureConnections(
499570
const newTaskId = nodeIdToTaskId(newNodeId);
500571

501572
newArgId = newTaskId;
502-
} else if ("graphInput" in argument) {
573+
} else if (isGraphInputArgument(argument)) {
503574
const oldInputId = argument.graphInput.inputName;
504575
oldNodeId = inputNameToNodeId(oldInputId);
505576

@@ -596,7 +667,7 @@ function updateTaskArgumentConnection(
596667
argument: TaskOutputArgument | GraphInputArgument,
597668
newArgId: string,
598669
): TaskSpec {
599-
if ("taskOutput" in argument) {
670+
if (isTaskOutputArgument(argument)) {
600671
return {
601672
...taskSpec,
602673
arguments: {
@@ -610,7 +681,7 @@ function updateTaskArgumentConnection(
610681
},
611682
},
612683
};
613-
} else if ("graphInput" in argument) {
684+
} else if (isGraphInputArgument(argument)) {
614685
return {
615686
...taskSpec,
616687
arguments: {

0 commit comments

Comments
 (0)