From 65fc69896859c887fa97920d9013286e21f2cc6e Mon Sep 17 00:00:00 2001 From: Avinash Kumar Deepak Date: Thu, 26 Feb 2026 22:07:55 +0530 Subject: [PATCH] feat: add copy/paste support for nodes --- src/graph-builder/graph-core/3-component.js | 25 +++++++++++++++++++++ src/reducer/actionType.js | 1 + src/reducer/initialState.js | 1 + src/reducer/reducer.js | 4 ++++ src/toolbarActions/toolbarFunctions.js | 12 ++++++++++ src/toolbarActions/toolbarList.js | 22 +++++++++++++++++- 6 files changed, 64 insertions(+), 1 deletion(-) diff --git a/src/graph-builder/graph-core/3-component.js b/src/graph-builder/graph-core/3-component.js index 576875b..4cf79ac 100644 --- a/src/graph-builder/graph-core/3-component.js +++ b/src/graph-builder/graph-core/3-component.js @@ -267,6 +267,31 @@ class GraphComponent extends GraphCanvas { } } + copySelected() { + const selected = this.cy.$('node[type="ordin"]:selected'); + return selected.map((node) => ({ + label: node.data('label'), + style: this.getStyle(node.id()), + position: { ...node.position() }, + })); + } + + pasteClipboard(nodes) { + if (!nodes.length) return; + const tid = this.getTid(); + nodes.forEach((node) => { + this.addNode( + node.label, + node.style, + 'ordin', + { x: node.position.x + 20, y: node.position.y + 20 }, + {}, + undefined, + tid, + ); + }); + } + validiateNode(label, style, id, type) { if (id) { const node = this.getById(id); diff --git a/src/reducer/actionType.js b/src/reducer/actionType.js index ea540e1..38ddae4 100644 --- a/src/reducer/actionType.js +++ b/src/reducer/actionType.js @@ -46,6 +46,7 @@ const actionType = { SET_LOGS_MESSAGE: 'SET_LOGS_MESSAGE', SET_GRAPH_INSTANCE: 'SET_GRAPH_INSTANCE', TOGGLE_DARK_MODE: 'TOGGLE_DARK_MODE', + SET_CLIPBOARD: 'SET_CLIPBOARD', }; export default zealit(actionType); diff --git a/src/reducer/initialState.js b/src/reducer/initialState.js index 4fb1993..42d753a 100644 --- a/src/reducer/initialState.js +++ b/src/reducer/initialState.js @@ -39,6 +39,7 @@ const initialState = { logs: false, logsmessage: '', darkMode: false, + clipboard: [], }; const initialGraphState = { diff --git a/src/reducer/reducer.js b/src/reducer/reducer.js index e7419ff..2c540b7 100644 --- a/src/reducer/reducer.js +++ b/src/reducer/reducer.js @@ -260,6 +260,10 @@ const reducer = (state, action) => { return { ...state, darkMode: !state.darkMode }; } + case T.SET_CLIPBOARD: { + return { ...state, clipboard: action.payload }; + } + default: return state; } diff --git a/src/toolbarActions/toolbarFunctions.js b/src/toolbarActions/toolbarFunctions.js index 97a57a6..9def06b 100644 --- a/src/toolbarActions/toolbarFunctions.js +++ b/src/toolbarActions/toolbarFunctions.js @@ -178,6 +178,17 @@ const redo = (state) => { if (getGraphFun(state)) getGraphFun(state).redo(); }; +const copySelected = (state, dispatcher) => { + if (!getGraphFun(state)) return; + const nodes = getGraphFun(state).copySelected(); + if (nodes.length) dispatcher({ type: T.SET_CLIPBOARD, payload: nodes }); +}; + +const pasteClipboard = (state) => { + if (!getGraphFun(state) || !state.clipboard.length) return; + getGraphFun(state).pasteClipboard(state.clipboard); +}; + const openShareModal = (state, setState) => { setState({ type: T.SET_SHARE_MODAL, payload: true }); }; @@ -202,5 +213,6 @@ export { createNode, editElement, deleteElem, downloadImg, saveAction, saveGraphMLFile, createFile, readFile, readTextFile, newProject, clearAll, editDetails, undo, redo, openShareModal, openSettingModal, viewHistory, resetAfterClear, toggleLogs, + copySelected, pasteClipboard, toggleServer, optionModalToggle, contribute, }; diff --git a/src/toolbarActions/toolbarList.js b/src/toolbarActions/toolbarList.js index d814554..3ca9bc9 100644 --- a/src/toolbarActions/toolbarList.js +++ b/src/toolbarActions/toolbarList.js @@ -2,6 +2,7 @@ import { FaSave, FaUndo, FaRedo, FaTrash, FaFileImport, FaPlus, FaDownload, FaEdit, FaRegTimesCircle, FaHistory, FaHammer, FaBug, FaBomb, FaToggleOn, FaThermometerEmpty, FaTrashRestore, FaCogs, FaPencilAlt, FaTerminal, + FaCopy, FaPaste, } from 'react-icons/fa'; import { @@ -12,7 +13,7 @@ import { import { createNode, editElement, deleteElem, downloadImg, saveAction, saveGraphMLFile, createFile, readFile, clearAll, undo, redo, viewHistory, resetAfterClear, - toggleServer, optionModalToggle, toggleLogs, contribute, + toggleServer, optionModalToggle, toggleLogs, contribute, copySelected, pasteClipboard, // openSettingModal, } from './toolbarFunctions'; @@ -117,6 +118,25 @@ const toolbarList = (state, dispatcher) => [ hotkey: 'Delete,Backspace,Del,Clear', }, { type: 'vsep' }, + { + type: 'action', + text: 'Copy', + icon: FaCopy, + action: copySelected, + active: state.curGraphInstance && state.eleSelected, + visibility: true, + hotkey: 'Ctrl+C', + }, + { + type: 'action', + text: 'Paste', + icon: FaPaste, + action: pasteClipboard, + active: state.curGraphInstance && state.clipboard.length > 0, + visibility: true, + hotkey: 'Ctrl+V', + }, + { type: 'vsep' }, { type: 'action', text: 'History',