Skip to content

Commit fca8d61

Browse files
committed
Merge branch 'master' into dev
2 parents 2b4af19 + 3982e20 commit fca8d61

File tree

2 files changed

+127
-61
lines changed

2 files changed

+127
-61
lines changed

autogpt_platform/frontend/src/components/Flow.tsx

+5-61
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import RunnerUIWrapper, {
4545
import PrimaryActionBar from "@/components/PrimaryActionButton";
4646
import { useToast } from "@/components/ui/use-toast";
4747
import { forceLoad } from "@sentry/nextjs";
48+
import { useCopyPaste } from "../hooks/useCopyPaste";
4849

4950
// This is for the history, this is the minimum distance a block must move before it is logged
5051
// It helps to prevent spamming the history with small movements especially when pressing on a input in a block
@@ -459,6 +460,8 @@ const FlowEditor: React.FC<{
459460
history.redo();
460461
};
461462

463+
const handleCopyPaste = useCopyPaste(getNextNodeId);
464+
462465
const handleKeyDown = useCallback(
463466
(event: KeyboardEvent) => {
464467
// Prevent copy/paste if any modal is open or if the focus is on an input element
@@ -470,68 +473,9 @@ const FlowEditor: React.FC<{
470473

471474
if (isAnyModalOpen || isInputField) return;
472475

473-
if (event.ctrlKey || event.metaKey) {
474-
if (event.key === "c" || event.key === "C") {
475-
// Copy selected nodes
476-
const selectedNodes = nodes.filter((node) => node.selected);
477-
const selectedEdges = edges.filter((edge) => edge.selected);
478-
setCopiedNodes(selectedNodes);
479-
setCopiedEdges(selectedEdges);
480-
}
481-
if (event.key === "v" || event.key === "V") {
482-
// Paste copied nodes
483-
if (copiedNodes.length > 0) {
484-
const oldToNewNodeIDMap: Record<string, string> = {};
485-
const pastedNodes = copiedNodes.map((node, index) => {
486-
const newNodeId = (nodeId + index).toString();
487-
oldToNewNodeIDMap[node.id] = newNodeId;
488-
return {
489-
...node,
490-
id: newNodeId,
491-
position: {
492-
x: node.position.x + 20, // Offset pasted nodes
493-
y: node.position.y + 20,
494-
},
495-
data: {
496-
...node.data,
497-
status: undefined, // Reset status
498-
executionResults: undefined, // Clear output data
499-
},
500-
};
501-
});
502-
setNodes((existingNodes) =>
503-
// Deselect copied nodes
504-
existingNodes.map((node) => ({ ...node, selected: false })),
505-
);
506-
addNodes(pastedNodes);
507-
setNodeId((prevId) => prevId + copiedNodes.length);
508-
509-
const pastedEdges = copiedEdges.map((edge) => {
510-
const newSourceId = oldToNewNodeIDMap[edge.source] ?? edge.source;
511-
const newTargetId = oldToNewNodeIDMap[edge.target] ?? edge.target;
512-
return {
513-
...edge,
514-
id: `${newSourceId}_${edge.sourceHandle}_${newTargetId}_${edge.targetHandle}_${Date.now()}`,
515-
source: newSourceId,
516-
target: newTargetId,
517-
};
518-
});
519-
addEdges(pastedEdges);
520-
}
521-
}
522-
}
476+
handleCopyPaste(event);
523477
},
524-
[
525-
isAnyModalOpen,
526-
nodes,
527-
edges,
528-
copiedNodes,
529-
setNodes,
530-
addNodes,
531-
copiedEdges,
532-
addEdges,
533-
nodeId,
534-
],
478+
[isAnyModalOpen, handleCopyPaste],
535479
);
536480

537481
useEffect(() => {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import { useCallback } from "react";
2+
import { Node, Edge, useReactFlow, useViewport } from "@xyflow/react";
3+
4+
export function useCopyPaste(getNextNodeId: () => string) {
5+
const { setNodes, addEdges, getNodes, getEdges } = useReactFlow();
6+
const { x, y, zoom } = useViewport();
7+
8+
const handleCopyPaste = useCallback(
9+
(event: KeyboardEvent) => {
10+
if (event.ctrlKey || event.metaKey) {
11+
if (event.key === "c" || event.key === "C") {
12+
const selectedNodes = getNodes().filter((node) => node.selected);
13+
const selectedEdges = getEdges().filter((edge) => edge.selected);
14+
15+
const copiedData = {
16+
nodes: selectedNodes.map((node) => ({
17+
...node,
18+
data: {
19+
...node.data,
20+
connections: [],
21+
},
22+
})),
23+
edges: selectedEdges,
24+
};
25+
26+
localStorage.setItem("copiedFlowData", JSON.stringify(copiedData));
27+
}
28+
if (event.key === "v" || event.key === "V") {
29+
const copiedDataString = localStorage.getItem("copiedFlowData");
30+
if (copiedDataString) {
31+
const copiedData = JSON.parse(copiedDataString);
32+
const oldToNewIdMap: Record<string, string> = {};
33+
34+
const viewportCenter = {
35+
x: (window.innerWidth / 2 - x) / zoom,
36+
y: (window.innerHeight / 2 - y) / zoom,
37+
};
38+
39+
let minX = Infinity,
40+
minY = Infinity,
41+
maxX = -Infinity,
42+
maxY = -Infinity;
43+
copiedData.nodes.forEach((node: Node) => {
44+
minX = Math.min(minX, node.position.x);
45+
minY = Math.min(minY, node.position.y);
46+
maxX = Math.max(maxX, node.position.x);
47+
maxY = Math.max(maxY, node.position.y);
48+
});
49+
50+
const offsetX = viewportCenter.x - (minX + maxX) / 2;
51+
const offsetY = viewportCenter.y - (minY + maxY) / 2;
52+
53+
const pastedNodes = copiedData.nodes.map((node: Node) => {
54+
const newNodeId = getNextNodeId();
55+
oldToNewIdMap[node.id] = newNodeId;
56+
return {
57+
...node,
58+
id: newNodeId,
59+
position: {
60+
x: node.position.x + offsetX,
61+
y: node.position.y + offsetY,
62+
},
63+
data: {
64+
...node.data,
65+
status: undefined,
66+
executionResults: undefined,
67+
},
68+
};
69+
});
70+
71+
const pastedEdges = copiedData.edges.map((edge: Edge) => {
72+
const newSourceId = oldToNewIdMap[edge.source] ?? edge.source;
73+
const newTargetId = oldToNewIdMap[edge.target] ?? edge.target;
74+
return {
75+
...edge,
76+
id: `${newSourceId}_${edge.sourceHandle}_${newTargetId}_${edge.targetHandle}_${Date.now()}`,
77+
source: newSourceId,
78+
target: newTargetId,
79+
};
80+
});
81+
82+
setNodes((existingNodes) => [
83+
...existingNodes.map((node) => ({ ...node, selected: false })),
84+
...pastedNodes,
85+
]);
86+
addEdges(pastedEdges);
87+
88+
setNodes((nodes) => {
89+
return nodes.map((node) => {
90+
if (oldToNewIdMap[node.id]) {
91+
const nodeConnections = pastedEdges
92+
.filter(
93+
(edge) =>
94+
edge.source === node.id || edge.target === node.id,
95+
)
96+
.map((edge) => ({
97+
edge_id: edge.id,
98+
source: edge.source,
99+
target: edge.target,
100+
sourceHandle: edge.sourceHandle,
101+
targetHandle: edge.targetHandle,
102+
}));
103+
return {
104+
...node,
105+
data: {
106+
...node.data,
107+
connections: nodeConnections,
108+
},
109+
};
110+
}
111+
return node;
112+
});
113+
});
114+
}
115+
}
116+
}
117+
},
118+
[setNodes, addEdges, getNodes, getEdges, getNextNodeId, x, y, zoom],
119+
);
120+
121+
return handleCopyPaste;
122+
}

0 commit comments

Comments
 (0)