From 3f1a61e34f04ff1f03477bef8075a77a379daa02 Mon Sep 17 00:00:00 2001 From: perzeuss <11357019+perzeuss@users.noreply.github.com> Date: Thu, 11 Apr 2024 20:42:02 +0200 Subject: [PATCH 1/6] feat: add workflow editor shortcuts (#3382) --- .../workflow/hooks/use-nodes-interactions.ts | 111 ++++++++++++++++++ web/app/components/workflow/index.tsx | 12 +- web/app/components/workflow/store.ts | 4 + 3 files changed, 126 insertions(+), 1 deletion(-) diff --git a/web/app/components/workflow/hooks/use-nodes-interactions.ts b/web/app/components/workflow/hooks/use-nodes-interactions.ts index 191bd7af0afce..b8baf24843d58 100644 --- a/web/app/components/workflow/hooks/use-nodes-interactions.ts +++ b/web/app/components/workflow/hooks/use-nodes-interactions.ts @@ -715,6 +715,112 @@ export const useNodesInteractions = () => { handleSyncWorkflowDraft() }, [store, handleSyncWorkflowDraft, getNodesReadOnly, t]) + const handleNodeCopySelected = useCallback((): undefined | Node[] => { + if (getNodesReadOnly()) + return + + const { + setClipboardElements, + } = workflowStore.getState() + + const { + getNodes, + } = store.getState() + + const nodes = getNodes() + const nodesToCopy = nodes.filter(node => node.selected) + + setClipboardElements(nodesToCopy) + + return nodesToCopy + }, [getNodesReadOnly, store, workflowStore]) + + const handleNodePaste = useCallback((): undefined | Node[] => { + if (getNodesReadOnly()) + return + + const { + clipboardElements, + } = workflowStore.getState() + + const { + getNodes, + setNodes, + } = store.getState() + + const nodesToPaste: Node[] = [] + const nodes = getNodes() + + for (const nodeToPaste of clipboardElements) { + const nodeType = nodeToPaste.data.type + const nodesWithSameType = nodes.filter(node => node.data.type === nodeType) + + const newNode = generateNewNode({ + data: { + ...NODES_INITIAL_DATA[nodeType], + ...nodeToPaste.data, + _connectedSourceHandleIds: [], + _connectedTargetHandleIds: [], + title: nodesWithSameType.length > 0 ? `${t(`workflow.blocks.${nodeType}`)} ${nodesWithSameType.length + 1}` : t(`workflow.blocks.${nodeType}`), + selected: true, + }, + position: { + x: nodeToPaste.position.x + 10, + y: nodeToPaste.position.y + 10, + }, + }) + nodesToPaste.push(newNode) + } + + setNodes([...nodes.map((n: Node) => ({ ...n, selected: false, data: { ...n.data, selected: false } })), ...nodesToPaste]) + + handleSyncWorkflowDraft() + + return nodesToPaste + }, [getNodesReadOnly, store, t, workflowStore]) + + const handleNodeCloneSelected = useCallback(() => { + if (getNodesReadOnly()) + return + + handleNodeCopySelected() + handleNodePaste() + + handleSyncWorkflowDraft() + }, [handleNodeCopySelected, handleNodePaste, handleSyncWorkflowDraft]) + + const handleNodeCut = useCallback(() => { + if (getNodesReadOnly()) + return + + const nodesToCut = handleNodeCopySelected() + if (!nodesToCut) + return + + for (const node of nodesToCut) { + handleNodeDelete(node.id) + } + }, [store, handleNodeCopySelected]) + + const handleNodeDeleteSelected = useCallback(() => { + if (getNodesReadOnly()) + return + + const { + getNodes, + } = store.getState() + + const nodes = getNodes() + const nodesToDelete = nodes.filter(node => node.selected) + + if (!nodesToDelete) + return + + for (const node of nodesToDelete) { + handleNodeDelete(node.id) + } + }, [store]) + return { handleNodeDragStart, handleNodeDrag, @@ -729,5 +835,10 @@ export const useNodesInteractions = () => { handleNodeDelete, handleNodeChange, handleNodeAdd, + handleNodeCut, + handleNodeCopySelected, + handleNodeCloneSelected, + handleNodePaste, + handleNodeDeleteSelected, } } diff --git a/web/app/components/workflow/index.tsx b/web/app/components/workflow/index.tsx index 3bcd1cccd5433..be05a516f7206 100644 --- a/web/app/components/workflow/index.tsx +++ b/web/app/components/workflow/index.tsx @@ -113,6 +113,11 @@ const Workflow: FC = memo(({ handleNodeConnect, handleNodeConnectStart, handleNodeConnectEnd, + handleNodeCut, + handleNodeDeleteSelected, + handleNodeCloneSelected, + handleNodeCopySelected, + handleNodePaste, } = useNodesInteractions() const { handleEdgeEnter, @@ -128,7 +133,12 @@ const Workflow: FC = memo(({ }, }) - useKeyPress('Backspace', handleEdgeDelete) + useKeyPress('delete', handleEdgeDelete) + useKeyPress('delete', handleNodeDeleteSelected) + useKeyPress('ctrl.c', handleNodeCopySelected) + useKeyPress('ctrl.x', handleNodeCut) + useKeyPress('ctrl.v', handleNodePaste) + useKeyPress('ctrl.alt.d', handleNodeCloneSelected) return (
void customTools: ToolWithProvider[] setCustomTools: (tools: ToolWithProvider[]) => void + clipboardElements: Node[] + setClipboardElements: (clipboardElements: Node[]) => void } export const createWorkflowStore = () => { @@ -107,6 +109,8 @@ export const createWorkflowStore = () => { setBuildInTools: buildInTools => set(() => ({ buildInTools })), customTools: [], setCustomTools: customTools => set(() => ({ customTools })), + clipboardElements: [], + setClipboardElements: clipboardElements => set(() => ({ clipboardElements })), })) } From 92b59f85b8106fffb41da4fa7e98f323769623bf Mon Sep 17 00:00:00 2001 From: perzeuss <11357019+perzeuss@users.noreply.github.com> Date: Thu, 11 Apr 2024 21:29:46 +0200 Subject: [PATCH 2/6] refactor: resolve lint issues --- .../workflow/hooks/use-nodes-interactions.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/web/app/components/workflow/hooks/use-nodes-interactions.ts b/web/app/components/workflow/hooks/use-nodes-interactions.ts index b8baf24843d58..64e1b61e7f125 100644 --- a/web/app/components/workflow/hooks/use-nodes-interactions.ts +++ b/web/app/components/workflow/hooks/use-nodes-interactions.ts @@ -797,10 +797,9 @@ export const useNodesInteractions = () => { if (!nodesToCut) return - for (const node of nodesToCut) { + for (const node of nodesToCut) handleNodeDelete(node.id) - } - }, [store, handleNodeCopySelected]) + }, [getNodesReadOnly, handleNodeCopySelected, handleNodeDelete]) const handleNodeDeleteSelected = useCallback(() => { if (getNodesReadOnly()) @@ -816,10 +815,9 @@ export const useNodesInteractions = () => { if (!nodesToDelete) return - for (const node of nodesToDelete) { + for (const node of nodesToDelete) handleNodeDelete(node.id) - } - }, [store]) + }, [getNodesReadOnly, handleNodeDelete, store]) return { handleNodeDragStart, From d0551da7a60ccfbe11cb24e660c0214650d1c908 Mon Sep 17 00:00:00 2001 From: perzeuss <11357019+perzeuss@users.noreply.github.com> Date: Thu, 11 Apr 2024 21:36:54 +0200 Subject: [PATCH 3/6] refactor: changes after self review --- .../workflow/hooks/use-nodes-interactions.ts | 12 +++++------- web/app/components/workflow/index.tsx | 4 ++-- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/web/app/components/workflow/hooks/use-nodes-interactions.ts b/web/app/components/workflow/hooks/use-nodes-interactions.ts index 64e1b61e7f125..d4ce480a6dda1 100644 --- a/web/app/components/workflow/hooks/use-nodes-interactions.ts +++ b/web/app/components/workflow/hooks/use-nodes-interactions.ts @@ -777,7 +777,7 @@ export const useNodesInteractions = () => { handleSyncWorkflowDraft() return nodesToPaste - }, [getNodesReadOnly, store, t, workflowStore]) + }, [getNodesReadOnly, handleSyncWorkflowDraft, store, t, workflowStore]) const handleNodeCloneSelected = useCallback(() => { if (getNodesReadOnly()) @@ -785,9 +785,7 @@ export const useNodesInteractions = () => { handleNodeCopySelected() handleNodePaste() - - handleSyncWorkflowDraft() - }, [handleNodeCopySelected, handleNodePaste, handleSyncWorkflowDraft]) + }, [getNodesReadOnly, handleNodeCopySelected, handleNodePaste]) const handleNodeCut = useCallback(() => { if (getNodesReadOnly()) @@ -833,10 +831,10 @@ export const useNodesInteractions = () => { handleNodeDelete, handleNodeChange, handleNodeAdd, - handleNodeCut, - handleNodeCopySelected, handleNodeCloneSelected, - handleNodePaste, + handleNodeCopySelected, + handleNodeCut, handleNodeDeleteSelected, + handleNodePaste, } } diff --git a/web/app/components/workflow/index.tsx b/web/app/components/workflow/index.tsx index be05a516f7206..bb7668b3c01df 100644 --- a/web/app/components/workflow/index.tsx +++ b/web/app/components/workflow/index.tsx @@ -113,10 +113,10 @@ const Workflow: FC = memo(({ handleNodeConnect, handleNodeConnectStart, handleNodeConnectEnd, - handleNodeCut, - handleNodeDeleteSelected, handleNodeCloneSelected, handleNodeCopySelected, + handleNodeCut, + handleNodeDeleteSelected, handleNodePaste, } = useNodesInteractions() const { From b77372f74b67127347aab77a152db30f6d7c092c Mon Sep 17 00:00:00 2001 From: perzeuss <11357019+perzeuss@users.noreply.github.com> Date: Fri, 12 Apr 2024 13:50:18 +0200 Subject: [PATCH 4/6] refactor: rename clone to duplicate --- web/app/components/workflow/hooks/use-nodes-interactions.ts | 4 ++-- web/app/components/workflow/index.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/web/app/components/workflow/hooks/use-nodes-interactions.ts b/web/app/components/workflow/hooks/use-nodes-interactions.ts index d4ce480a6dda1..8be91d4344bad 100644 --- a/web/app/components/workflow/hooks/use-nodes-interactions.ts +++ b/web/app/components/workflow/hooks/use-nodes-interactions.ts @@ -779,7 +779,7 @@ export const useNodesInteractions = () => { return nodesToPaste }, [getNodesReadOnly, handleSyncWorkflowDraft, store, t, workflowStore]) - const handleNodeCloneSelected = useCallback(() => { + const handleNodeDuplicateSelected = useCallback(() => { if (getNodesReadOnly()) return @@ -831,7 +831,7 @@ export const useNodesInteractions = () => { handleNodeDelete, handleNodeChange, handleNodeAdd, - handleNodeCloneSelected, + handleNodeDuplicateSelected, handleNodeCopySelected, handleNodeCut, handleNodeDeleteSelected, diff --git a/web/app/components/workflow/index.tsx b/web/app/components/workflow/index.tsx index bb7668b3c01df..6e50672993d60 100644 --- a/web/app/components/workflow/index.tsx +++ b/web/app/components/workflow/index.tsx @@ -113,7 +113,7 @@ const Workflow: FC = memo(({ handleNodeConnect, handleNodeConnectStart, handleNodeConnectEnd, - handleNodeCloneSelected, + handleNodeDuplicateSelected, handleNodeCopySelected, handleNodeCut, handleNodeDeleteSelected, @@ -138,7 +138,7 @@ const Workflow: FC = memo(({ useKeyPress('ctrl.c', handleNodeCopySelected) useKeyPress('ctrl.x', handleNodeCut) useKeyPress('ctrl.v', handleNodePaste) - useKeyPress('ctrl.alt.d', handleNodeCloneSelected) + useKeyPress('ctrl.alt.d', handleNodeDuplicateSelected) return (
Date: Fri, 12 Apr 2024 13:53:53 +0200 Subject: [PATCH 5/6] feat: add mac shortcuts --- web/app/components/workflow/index.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/web/app/components/workflow/index.tsx b/web/app/components/workflow/index.tsx index 6e50672993d60..1bcc05fd9322f 100644 --- a/web/app/components/workflow/index.tsx +++ b/web/app/components/workflow/index.tsx @@ -133,12 +133,12 @@ const Workflow: FC = memo(({ }, }) - useKeyPress('delete', handleEdgeDelete) - useKeyPress('delete', handleNodeDeleteSelected) - useKeyPress('ctrl.c', handleNodeCopySelected) - useKeyPress('ctrl.x', handleNodeCut) - useKeyPress('ctrl.v', handleNodePaste) - useKeyPress('ctrl.alt.d', handleNodeDuplicateSelected) + useKeyPress(['delete'], handleEdgeDelete) + useKeyPress(['delete'], handleNodeDeleteSelected) + useKeyPress(['ctrl.c', 'meta.c'], handleNodeCopySelected) + useKeyPress(['ctrl.x', 'meta.x'], handleNodeCut) + useKeyPress(['ctrl.v', 'meta.v'], handleNodePaste) + useKeyPress(['ctrl.alt.d', 'meta.shift.d'], handleNodeDuplicateSelected) return (
Date: Fri, 12 Apr 2024 13:57:34 +0200 Subject: [PATCH 6/6] fix: wrong predicate used to filter selected nodes --- web/app/components/workflow/hooks/use-nodes-interactions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/app/components/workflow/hooks/use-nodes-interactions.ts b/web/app/components/workflow/hooks/use-nodes-interactions.ts index 8be91d4344bad..803799ac13703 100644 --- a/web/app/components/workflow/hooks/use-nodes-interactions.ts +++ b/web/app/components/workflow/hooks/use-nodes-interactions.ts @@ -728,7 +728,7 @@ export const useNodesInteractions = () => { } = store.getState() const nodes = getNodes() - const nodesToCopy = nodes.filter(node => node.selected) + const nodesToCopy = nodes.filter(node => node.data.selected) setClipboardElements(nodesToCopy) @@ -808,7 +808,7 @@ export const useNodesInteractions = () => { } = store.getState() const nodes = getNodes() - const nodesToDelete = nodes.filter(node => node.selected) + const nodesToDelete = nodes.filter(node => node.data.selected) if (!nodesToDelete) return