1. Copy, paste, cut and delete functions added to the web page
This commit is contained in:
@@ -637,6 +637,104 @@
|
||||
|
||||
const [gridSnap, setGridSnap] = useState(false);
|
||||
|
||||
// --- CLIPBOARD & ACTIONS ---
|
||||
const [clipboard, setClipboard] = useState({ nodes: [] });
|
||||
|
||||
const handleCopy = useCallback(() => {
|
||||
const currentNodes = reactFlowInstance.getNodes();
|
||||
const selectedNodes = currentNodes.filter(n => n.selected);
|
||||
if (selectedNodes.length > 0) {
|
||||
// Deep clone the selected nodes to prevent reference issues
|
||||
setClipboard({ nodes: JSON.parse(JSON.stringify(selectedNodes)) });
|
||||
}
|
||||
}, [reactFlowInstance]);
|
||||
|
||||
const handleCut = useCallback(() => {
|
||||
const currentNodes = reactFlowInstance.getNodes();
|
||||
const selectedNodes = currentNodes.filter(n => n.selected);
|
||||
|
||||
if (selectedNodes.length > 0) {
|
||||
setClipboard({ nodes: JSON.parse(JSON.stringify(selectedNodes)) });
|
||||
|
||||
const selectedNodeIds = new Set(selectedNodes.map(n => n.id));
|
||||
|
||||
// Remove nodes and any edges connected to them
|
||||
setNodes(nds => nds.filter(n => !selectedNodeIds.has(n.id)));
|
||||
setEdges(eds => eds.filter(e => !selectedNodeIds.has(e.source) && !selectedNodeIds.has(e.target)));
|
||||
}
|
||||
}, [reactFlowInstance, setNodes, setEdges]);
|
||||
|
||||
const handlePaste = useCallback(() => {
|
||||
if (clipboard.nodes.length > 0) {
|
||||
const newNodes = clipboard.nodes.map(node => {
|
||||
// Give the new node a unique ID and a slight positional offset
|
||||
// so it doesn't overlap perfectly with the original
|
||||
return {
|
||||
...node,
|
||||
id: Date.now().toString() + Math.random().toString(36).substr(2, 5),
|
||||
position: {
|
||||
x: node.position.x + 20,
|
||||
y: node.position.y + 20
|
||||
},
|
||||
selected: true, // Automatically select the newly pasted node
|
||||
data: {
|
||||
...node.data,
|
||||
// Ensure the pasted node gets a fresh display name (e.g., component_5)
|
||||
componentDisplayName: generateComponentDisplayName()
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
// Deselect existing nodes and add the new ones
|
||||
setNodes(nds => nds.map(n => ({...n, selected: false})).concat(newNodes));
|
||||
|
||||
// Update the clipboard with the new offset nodes so
|
||||
// rapid pasting cascades them nicely
|
||||
setClipboard({ nodes: newNodes });
|
||||
}
|
||||
}, [clipboard, setNodes, generateComponentDisplayName]);
|
||||
|
||||
const handleDelete = useCallback(() => {
|
||||
const currentNodes = reactFlowInstance.getNodes();
|
||||
const selectedNodes = currentNodes.filter(n => n.selected);
|
||||
const selectedNodeIds = new Set(selectedNodes.map(n => n.id));
|
||||
|
||||
if (selectedNodeIds.size > 0) {
|
||||
setNodes(nds => nds.filter(n => !selectedNodeIds.has(n.id)));
|
||||
setEdges(eds => eds.filter(e => !selectedNodeIds.has(e.source) && !selectedNodeIds.has(e.target)));
|
||||
}
|
||||
}, [reactFlowInstance, setNodes, setEdges]);
|
||||
|
||||
// --- KEYBOARD SHORTCUTS ---
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e) => {
|
||||
// Prevent actions if the user is typing in an input or textarea
|
||||
if (document.activeElement.tagName === 'INPUT' || document.activeElement.tagName === 'TEXTAREA') {
|
||||
return;
|
||||
}
|
||||
|
||||
const cmdOrCtrl = e.ctrlKey || e.metaKey; // Supports Windows/Linux (Ctrl) and Mac (Cmd)
|
||||
|
||||
if (cmdOrCtrl && e.key.toLowerCase() === 'c') {
|
||||
e.preventDefault();
|
||||
handleCopy();
|
||||
} else if (cmdOrCtrl && e.key.toLowerCase() === 'x') {
|
||||
e.preventDefault();
|
||||
handleCut();
|
||||
} else if (cmdOrCtrl && e.key.toLowerCase() === 'v') {
|
||||
e.preventDefault();
|
||||
handlePaste();
|
||||
} else if (e.key === 'Delete' || e.key === 'Backspace') {
|
||||
// ReactFlow handles this natively if the canvas is focused,
|
||||
// but this ensures it fires even if focus is outside the canvas wrapper.
|
||||
handleDelete();
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||
}, [handleCopy, handleCut, handlePaste, handleDelete]);
|
||||
|
||||
const componentCounterRef = useRef(1);
|
||||
|
||||
const generateComponentDisplayName = useCallback(() => {
|
||||
|
||||
Reference in New Issue
Block a user