From bc6e03cf9e0b954b3ecf7a287e59adb77707cfd1 Mon Sep 17 00:00:00 2001 From: root Date: Sat, 16 May 2026 22:30:36 +0800 Subject: [PATCH] 1. Copy, paste, cut and delete functions added to the web page --- backend/__pycache__/database.cpython-39.pyc | Bin 0 -> 1368 bytes frontend/canvas.html | 98 +++ frontend/canvas_edit.html | 829 ++++++++++++++++++++ 3 files changed, 927 insertions(+) create mode 100644 backend/__pycache__/database.cpython-39.pyc create mode 100644 frontend/canvas_edit.html diff --git a/backend/__pycache__/database.cpython-39.pyc b/backend/__pycache__/database.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..45b654736f5a73fbe9354d1acefdb902962e1876 GIT binary patch literal 1368 zcmaJ>PjA~c6elHHlHH_efuX~0#4fD{!^75gz=Ex*n<$+zN9k<2O9cahEYgV*Tk=R6 zZ4Bp>eUSLLPqFX8%MLs3w)0MVr2nAVVKnsk$oK!fAD!;*))B0~|8Or}R1x}9A3k1i zhhD-b20$pHc#PuJj$;gGYiv#I*v5$Ai^^@)P=~ooOzou=d$dBUzoNKC9qPhfXZNUg zY4KB5yFp;HLu)q}gV!GN>wiH66#7^_I%5UnsbtAC72y*5^Z94Z=76R$9j1Z} zCLgAGmgr-X4l`}KSvD{_?F0D45Qs##SXxVbjTY$EUZQKfz_%5Omk`=;ATwNbi9{$J zYTw{%%Sa&og*CF47Dm#gj!qVQcuPng_L|-)g5=$B{(GIQ*FiUEMFELguQ~x~ACd4R zBEf097xlR<@i1fquPyN6|3HzPSUDZ1sT<=mv&2 z0lz1YQ##3u$FcL(*H524|7QMeJM0DBNSAQ3DQ}ZsgWL)4leeu-Kj@Js`wg=1$5rEM z{ybtr8dH*S24km9(qC7&M~n_xkT+{6?2%Pqzph;I%Q%nBRx{3~T9nRbbbj0q`?mwgA?^)d02@mIm+wjj)C$oB+DlHb@-07s{o%c{)1EC?B&VS=FV7unn)F n4gx*4j|$M)@>LbzO-ec&vlkjm;sFpn&K>)a<$daT9(M4*7CBrr literal 0 HcmV?d00001 diff --git a/frontend/canvas.html b/frontend/canvas.html index 7431059..540d9eb 100644 --- a/frontend/canvas.html +++ b/frontend/canvas.html @@ -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(() => { diff --git a/frontend/canvas_edit.html b/frontend/canvas_edit.html new file mode 100644 index 0000000..7431059 --- /dev/null +++ b/frontend/canvas_edit.html @@ -0,0 +1,829 @@ + + + +{% raw %} + + + + mxPIC Core - Canvas + + + + + + + + + + +
+ + + + + +{% endraw %} \ No newline at end of file