From b602184d7f804d61222777d2156f2e05fe56ed76 Mon Sep 17 00:00:00 2001 From: sybiote Date: Fri, 22 Dec 2023 01:49:26 +0530 Subject: [PATCH 01/10] multi select nodes with shift --- .../editor/src/components/graph/ee-flow/components/Flow.tsx | 4 ++-- .../src/components/graph/ee-flow/hooks/useSelectionHandler.ts | 0 2 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 packages/editor/src/components/graph/ee-flow/hooks/useSelectionHandler.ts diff --git a/packages/editor/src/components/graph/ee-flow/components/Flow.tsx b/packages/editor/src/components/graph/ee-flow/components/Flow.tsx index 64d3434142..fccabe58da 100644 --- a/packages/editor/src/components/graph/ee-flow/components/Flow.tsx +++ b/packages/editor/src/components/graph/ee-flow/components/Flow.tsx @@ -96,9 +96,7 @@ export const Flow: React.FC = ({ initialGraph: graph, examples, regis onNodesChange={onNodesChange} onEdgesChange={onEdgesChange} onConnect={onConnect} - // @ts-ignore onConnectStart={handleStartConnect} - // @ts-ignore onConnectEnd={handleStopConnect} onPaneMouseEnter={() => mouseOver.set(true)} onPaneMouseLeave={() => mouseOver.set(false)} @@ -106,6 +104,8 @@ export const Flow: React.FC = ({ initialGraph: graph, examples, regis fitViewOptions={{ maxZoom: 1 }} onPaneClick={handlePaneClick} onPaneContextMenu={handlePaneContextMenu} + multiSelectionKeyCode={'Shift'} + deleteKeyCode={'Backspace'} > Date: Fri, 22 Dec 2023 03:11:11 +0530 Subject: [PATCH 02/10] multi copy - paste --- .../graph/ee-flow/components/Flow.tsx | 9 +++ .../ee-flow/hooks/useSelectionHandler.ts | 80 +++++++++++++++++++ 2 files changed, 89 insertions(+) diff --git a/packages/editor/src/components/graph/ee-flow/components/Flow.tsx b/packages/editor/src/components/graph/ee-flow/components/Flow.tsx index fccabe58da..8b15eb7973 100644 --- a/packages/editor/src/components/graph/ee-flow/components/Flow.tsx +++ b/packages/editor/src/components/graph/ee-flow/components/Flow.tsx @@ -33,6 +33,7 @@ import { useHookstate } from '@hookstate/core' import { useBehaveGraphFlow } from '../hooks/useBehaveGraphFlow.js' import { useFlowHandlers } from '../hooks/useFlowHandlers.js' import { useNodeSpecGenerator } from '../hooks/useNodeSpecGenerator.js' +import { useSelectionHandler } from '../hooks/useSelectionHandler.js' import CustomControls from './Controls.js' import { NodePicker } from './NodePicker.js' import { Examples } from './modals/LoadModal.js' @@ -80,6 +81,11 @@ export const Flow: React.FC = ({ initialGraph: graph, examples, regis registry }) + const { onSelectionChange, handleKeyDown, handleKeyUp } = useSelectionHandler({ + nodes, + onNodesChange + }) + useEffect(() => { if (dragging.value || !mouseOver.value) return onChangeGraph(graphJson ?? graph) @@ -104,8 +110,11 @@ export const Flow: React.FC = ({ initialGraph: graph, examples, regis fitViewOptions={{ maxZoom: 1 }} onPaneClick={handlePaneClick} onPaneContextMenu={handlePaneContextMenu} + onSelectionChange={onSelectionChange} multiSelectionKeyCode={'Shift'} deleteKeyCode={'Backspace'} + onKeyDown={handleKeyDown} + onKeyUp={handleKeyUp} > + +export const useSelectionHandler = ({ + nodes, + onNodesChange +}: Pick & { + nodes: Node[] +}) => { + const [selectedNodes, setSelectedNodes] = useState([] as Node[]) + const [copiedNodes, setCopiedNodes] = useState([] as Node[]) + + const onSelectionChange = (elements) => { + if (elements.nodes.length === 0) return + if (isEqual(elements.nodes.length, selectedNodes)) return + setSelectedNodes(elements.nodes) + } + + const handleKeyDown = (event) => { + if (event.key === 'c' && event.ctrlKey) { + // Copy selected elements + setCopiedNodes(selectedNodes) + } + if (event.key === 'v' && event.ctrlKey) { + // Paste copied elements + const newNodes = copiedNodes.map((node) => ({ + ...node, + id: uuidv4(), // Generate unique IDs for the new elements + position: { + x: node.position.x + node.width! + 10, // Adjust position to avoid overlap + y: node.position.y + } + })) + + const newNodeChange: NodeChange[] = newNodes.map((node) => ({ + type: 'add', + item: node + })) + + onNodesChange(newNodeChange) + setCopiedNodes(newNodes) + } + } + + const handleKeyUp = (event) => { + // empty for now + } + + return { onSelectionChange, handleKeyDown, handleKeyUp } +} From 3c2c9b2e7246cbdb0e388e3d3e39a1e926965d9a Mon Sep 17 00:00:00 2001 From: sybiote Date: Fri, 22 Dec 2023 03:33:27 +0530 Subject: [PATCH 03/10] memoize and optimization --- .../ee-flow/hooks/useSelectionHandler.ts | 78 +++++++++++-------- 1 file changed, 45 insertions(+), 33 deletions(-) diff --git a/packages/editor/src/components/graph/ee-flow/hooks/useSelectionHandler.ts b/packages/editor/src/components/graph/ee-flow/hooks/useSelectionHandler.ts index b82024b6c1..7b96850e4e 100644 --- a/packages/editor/src/components/graph/ee-flow/hooks/useSelectionHandler.ts +++ b/packages/editor/src/components/graph/ee-flow/hooks/useSelectionHandler.ts @@ -22,11 +22,10 @@ Original Code is the Ethereal Engine team. All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 Ethereal Engine. All Rights Reserved. */ -import { useState } from 'react' +import { useMemo, useState } from 'react' import { Node, NodeChange } from 'reactflow' import { v4 as uuidv4 } from 'uuid' -import { isEqual } from 'lodash' import { useBehaveGraphFlow } from './useBehaveGraphFlow' type BehaveGraphFlow = ReturnType @@ -40,41 +39,54 @@ export const useSelectionHandler = ({ const [selectedNodes, setSelectedNodes] = useState([] as Node[]) const [copiedNodes, setCopiedNodes] = useState([] as Node[]) - const onSelectionChange = (elements) => { - if (elements.nodes.length === 0) return - if (isEqual(elements.nodes.length, selectedNodes)) return - setSelectedNodes(elements.nodes) + const copyNodes = () => { + setCopiedNodes(selectedNodes) } - const handleKeyDown = (event) => { - if (event.key === 'c' && event.ctrlKey) { - // Copy selected elements - setCopiedNodes(selectedNodes) - } - if (event.key === 'v' && event.ctrlKey) { - // Paste copied elements - const newNodes = copiedNodes.map((node) => ({ - ...node, - id: uuidv4(), // Generate unique IDs for the new elements - position: { - x: node.position.x + node.width! + 10, // Adjust position to avoid overlap - y: node.position.y - } - })) - - const newNodeChange: NodeChange[] = newNodes.map((node) => ({ - type: 'add', - item: node - })) - - onNodesChange(newNodeChange) - setCopiedNodes(newNodes) - } - } + const pasteNodes = () => { + const newNodes = copiedNodes.map((node) => ({ + ...node, + id: uuidv4(), + position: { + x: node.position.x + node.width! + 10, + y: node.position.y + } + })) + + const newNodeChange: NodeChange[] = newNodes.map((node) => ({ + type: 'add', + item: node + })) - const handleKeyUp = (event) => { - // empty for now + onNodesChange(newNodeChange) + setCopiedNodes(newNodes) } + const onSelectionChange = useMemo( + () => (elements) => { + setSelectedNodes(elements.nodes) + }, + [selectedNodes] + ) + + const handleKeyDown = useMemo( + () => (event) => { + if (event.key === 'c' && event.ctrlKey) { + copyNodes() + } + if (event.key === 'v' && event.ctrlKey) { + pasteNodes() + } + }, + [copiedNodes, onNodesChange, selectedNodes] + ) + + const handleKeyUp = useMemo( + () => (event) => { + // empty for now + }, + [] + ) + return { onSelectionChange, handleKeyDown, handleKeyUp } } From 5db1ec16323ed1aff9043db2ec3583e879f26936 Mon Sep 17 00:00:00 2001 From: sybiote Date: Fri, 22 Dec 2023 03:38:43 +0530 Subject: [PATCH 04/10] make if to siwtch --- .../graph/ee-flow/hooks/useSelectionHandler.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/packages/editor/src/components/graph/ee-flow/hooks/useSelectionHandler.ts b/packages/editor/src/components/graph/ee-flow/hooks/useSelectionHandler.ts index 7b96850e4e..272e75c051 100644 --- a/packages/editor/src/components/graph/ee-flow/hooks/useSelectionHandler.ts +++ b/packages/editor/src/components/graph/ee-flow/hooks/useSelectionHandler.ts @@ -71,11 +71,17 @@ export const useSelectionHandler = ({ const handleKeyDown = useMemo( () => (event) => { - if (event.key === 'c' && event.ctrlKey) { - copyNodes() - } - if (event.key === 'v' && event.ctrlKey) { - pasteNodes() + if (event.ctrlKey) { + switch (event.key) { + case 'c': + copyNodes() + break + case 'v': + pasteNodes() + break + default: + break + } } }, [copiedNodes, onNodesChange, selectedNodes] From 5671df99064216a01ba7170f87f8f8c3103c020b Mon Sep 17 00:00:00 2001 From: sybiote Date: Fri, 22 Dec 2023 03:40:24 +0530 Subject: [PATCH 05/10] slight improvement --- .../ee-flow/hooks/useSelectionHandler.ts | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/editor/src/components/graph/ee-flow/hooks/useSelectionHandler.ts b/packages/editor/src/components/graph/ee-flow/hooks/useSelectionHandler.ts index 272e75c051..4e9ce68242 100644 --- a/packages/editor/src/components/graph/ee-flow/hooks/useSelectionHandler.ts +++ b/packages/editor/src/components/graph/ee-flow/hooks/useSelectionHandler.ts @@ -71,17 +71,17 @@ export const useSelectionHandler = ({ const handleKeyDown = useMemo( () => (event) => { - if (event.ctrlKey) { - switch (event.key) { - case 'c': - copyNodes() - break - case 'v': - pasteNodes() - break - default: - break - } + if (!event.ctrlKey) return + + switch (event.key) { + case 'c': + copyNodes() + break + case 'v': + pasteNodes() + break + default: + break } }, [copiedNodes, onNodesChange, selectedNodes] From 773e88784750c5ef414b0e81573f7fb554d2959e Mon Sep 17 00:00:00 2001 From: sybiote Date: Fri, 22 Dec 2023 03:44:23 +0530 Subject: [PATCH 06/10] fix hook state error --- packages/editor/src/components/graph/BehaveFlow.tsx | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/editor/src/components/graph/BehaveFlow.tsx b/packages/editor/src/components/graph/BehaveFlow.tsx index b11ffe95a6..578bd8ac59 100644 --- a/packages/editor/src/components/graph/BehaveFlow.tsx +++ b/packages/editor/src/components/graph/BehaveFlow.tsx @@ -25,12 +25,7 @@ Ethereal Engine. All Rights Reserved. import { BehaveGraphComponent } from '@etherealengine/engine/src/behave-graph/components/BehaveGraphComponent' import { BehaveGraphState } from '@etherealengine/engine/src/behave-graph/state/BehaveGraphState' -import { - getComponent, - hasComponent, - useComponent, - useQuery -} from '@etherealengine/engine/src/ecs/functions/ComponentFunctions' +import { getComponent, hasComponent, useQuery } from '@etherealengine/engine/src/ecs/functions/ComponentFunctions' import { getMutableState, getState, useHookstate } from '@etherealengine/hyperflux' import { isEqual } from 'lodash' import React from 'react' @@ -47,7 +42,6 @@ export const ActiveBehaveGraph = (props: { entity }) => { const { entity } = props // reactivity - useComponent(entity, BehaveGraphComponent).graph.value const behaveGraphState = getState(BehaveGraphState) // get underlying data, avoid hookstate error 202 From 1956b39d9f122785db92fd9cc7f384521720d40a Mon Sep 17 00:00:00 2001 From: sybiote Date: Fri, 22 Dec 2023 03:52:46 +0530 Subject: [PATCH 07/10] add editor json --- packages/client-core/i18n/en/editor.json | 3 +++ packages/editor/src/components/graph/BehaveFlow.tsx | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/client-core/i18n/en/editor.json b/packages/client-core/i18n/en/editor.json index d6a85e55ff..a682f84847 100755 --- a/packages/client-core/i18n/en/editor.json +++ b/packages/client-core/i18n/en/editor.json @@ -895,6 +895,9 @@ "lbl-newProject": "New Project" } }, + "graphPanel":{ + "addGraph" : "Add Behave Graph" + }, "layout": { "assetGrid": { "loading": "Loading...", diff --git a/packages/editor/src/components/graph/BehaveFlow.tsx b/packages/editor/src/components/graph/BehaveFlow.tsx index 578bd8ac59..3a7e7e19b4 100644 --- a/packages/editor/src/components/graph/BehaveFlow.tsx +++ b/packages/editor/src/components/graph/BehaveFlow.tsx @@ -29,6 +29,7 @@ import { getComponent, hasComponent, useQuery } from '@etherealengine/engine/src import { getMutableState, getState, useHookstate } from '@etherealengine/hyperflux' import { isEqual } from 'lodash' import React from 'react' +import { useTranslation } from 'react-i18next' import AutoSizer from 'react-virtualized-auto-sizer' import 'reactflow/dist/style.css' import { EditorControlFunctions } from '../../functions/EditorControlFunctions' @@ -69,6 +70,7 @@ const BehaveFlow = () => { const entities = selectionState.selectedEntities.value const entity = entities[entities.length - 1] const validEntity = typeof entity === 'number' && hasComponent(entity, BehaveGraphComponent) + const { t } = useTranslation() const addGraph = () => EditorControlFunctions.addOrRemoveComponent([entity], BehaveGraphComponent, true) @@ -91,8 +93,7 @@ const BehaveFlow = () => { addGraph() }} > - {' '} - Add Graph + {t('editor:graphPanel.addGraph')} ) : ( <> From f8600158a2a53816f2d9ff73808bfa2b4738c27a Mon Sep 17 00:00:00 2001 From: sybiote Date: Fri, 22 Dec 2023 03:54:17 +0530 Subject: [PATCH 08/10] remove old style file --- .../graph/ee-behave-flow/styles.module.scss | 216 ------------------ 1 file changed, 216 deletions(-) delete mode 100644 packages/editor/src/components/graph/ee-behave-flow/styles.module.scss diff --git a/packages/editor/src/components/graph/ee-behave-flow/styles.module.scss b/packages/editor/src/components/graph/ee-behave-flow/styles.module.scss deleted file mode 100644 index b65b3f861d..0000000000 --- a/packages/editor/src/components/graph/ee-behave-flow/styles.module.scss +++ /dev/null @@ -1,216 +0,0 @@ -.inputSocket { - display: flex; - flex-grow: 1; - align-items: center; - justify-content: flex-start; - height: 1.75rem; - position: relative; - - .socketName { - text-transform: capitalize; - margin-left: 0.5rem; - margin-right: 1rem; - } - - .handle { - background-color: #333; - } - - .inputField { - background-color: #555; - padding-block: 0.5rem 1rem; - margin-right: 0.5rem; - - :disabled { - background-color: #aaa; - } - } -} - -.outputSocket { - display: flex; - flex-grow: 1; - align-items: center; - justify-content: flex-end; - height: 1.75rem; - position: relative; - - .socketName { - text-transform: capitalize; - margin-left: 1rem; - margin-right: 0.5rem; - } - - .handle { - background-color: #333; - } - - .flowIcon { - margin-left: 0.25rem; - } -} - -.node { - display: inline-block; -} - -.addNode { - background-color: #555; - padding-left: 1rem; - padding-right: 1rem; -} - -.search { - padding-left: 0.25rem; - padding-right: 0.25rem; - - input { - background-color: #666; - - :disabled { - background-color: #888; - } - - width: 100%; - padding-block: 0.5rem 0.125rem; - } -} - -.options { - max-height: 24rem; - overflow-y: scroll; - - .option { - padding: 0.25rem; - cursor: pointer; - border-bottom-width: 1px; - } -} - -.nodeContainer { - border-radius: 0.25rem; - color: #fff; - font-size: small; - background-color: #aaa; - min-width: 120px; - - ::selection { - outline: 1px white solid; - } - - .title { - background-color: #aac; - color: #fff; - padding-block: 0.5rem 0.25rem; - border-top-left-radius: 0.25rem; - border-top-right-radius: 0.25rem; - } - - .content { - display: flex; - flex-grow: 1; - flex-direction: column; - gap: 0.5rem; - padding-top: 0.5rem; - padding-bottom: 0.5rem; - } -} - -.modalExit { - z-index: 19; - position: fixed; - inset: 0; - background-color: #555; - fill-opacity: 0.5; - overflow-y: auto; - height: 100%; - width: 100%; -} - -.Modal { - z-index: 20; - font-size: small; - border-radius: 2rem; - position: relative; - top: 20%; - - .title { - padding: 0.75rem; - border-bottom-width: 1px; - - h2 { - font-size: large; - text-align: center; - font-style: bold; - font-weight: normal; - } - } - - .children { - padding: 0.75rem; - } - - .actions { - flex: content; - gap: 0.75rem; - padding: 0.75rem; - border-top-width: 1px; - - button { - color: #fff; - padding: 0.5rem; - width: 100%; - - &.primary { - background-color: #088; - - :hover { - background-color: #0aa; - } - } - - &.secondary { - background-color: #888; - - :hover { - background-color: #bbb; - } - } - } - } -} - -.saveTextArea { - border-width: 1px; - border-color: #333; - width: 100%; - padding: 0.5rem; - height: 8rem; -} - -.GraphEditor { - /* fixes issue with tailwind resetting flow control buttons */ - button, - [type="button"] { - background-color: #fff; - } - - select { - appearance: none; - background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='%236B7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3E%3C/svg%3E"); - background-position: right 0.25rem center; - background-repeat: no-repeat; - background-size: 1.5rem 1.5rem; - } - - input[type="number"] { - appearance: textfield; - -moz-appearance: textfield; - } - - input::-webkit-outer-spin-button, - input::-webkit-inner-spin-button { - appearance: none; - -webkit-appearance: none; - } -} From 8309b1f73755f8907a71d496783bb5bc4ed673c0 Mon Sep 17 00:00:00 2001 From: sybiote Date: Fri, 22 Dec 2023 05:12:51 +0530 Subject: [PATCH 09/10] fix node panel visibility on drag into panel --- .../src/components/graph/ee-flow/hooks/useFlowHandlers.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/editor/src/components/graph/ee-flow/hooks/useFlowHandlers.ts b/packages/editor/src/components/graph/ee-flow/hooks/useFlowHandlers.ts index e1898bc161..46c66078bc 100644 --- a/packages/editor/src/components/graph/ee-flow/hooks/useFlowHandlers.ts +++ b/packages/editor/src/components/graph/ee-flow/hooks/useFlowHandlers.ts @@ -63,6 +63,7 @@ export const useFlowHandlers = ({ }) => { const [lastConnectStart, setLastConnectStart] = useState() const [nodePickerVisibility, setNodePickerVisibility] = useState() + const [nodePickerLastOpened, setNodePickerLastOpened] = useState() const onConnect = useCallback( (connection: Connection) => { @@ -87,9 +88,10 @@ export const useFlowHandlers = ({ ) const closeNodePicker = useCallback(() => { + if (nodePickerLastOpened && Date.now() - nodePickerLastOpened < 100) return setLastConnectStart(undefined) setNodePickerVisibility(undefined) - }, []) + }, [nodePickerLastOpened]) const handleAddNode = useCallback( (nodeType: string, position: XYPosition) => { @@ -131,7 +133,8 @@ export const useFlowHandlers = ({ const element = e.target as HTMLElement if (element.classList.contains('react-flow__pane')) { const targetBounds = element.getBoundingClientRect() - setNodePickerVisibility({ x: e.clientX - targetBounds.left, y: e.clientY - targetBounds.left }) + setNodePickerVisibility({ x: e.clientX - targetBounds.left, y: e.clientY - targetBounds.top }) + setNodePickerLastOpened(Date.now()) } else { setLastConnectStart(undefined) } @@ -144,6 +147,7 @@ export const useFlowHandlers = ({ const targetElement = e.target as HTMLElement const targetBounds = targetElement.getBoundingClientRect() setNodePickerVisibility({ x: e.clientX - targetBounds.left, y: e.clientY - targetBounds.top }) + setNodePickerLastOpened(Date.now()) }, []) const nodePickFilters = useNodePickFilters({ From 593938499515ece05c4ca39cf41844c00c5bbb0b Mon Sep 17 00:00:00 2001 From: sybiote Date: Fri, 22 Dec 2023 08:09:15 +0530 Subject: [PATCH 10/10] box slect and copy paste across entities --- .../graph/ee-flow/components/Flow.tsx | 93 ++++++++++--------- .../ee-flow/hooks/useSelectionHandler.ts | 87 ++++++++++------- 2 files changed, 99 insertions(+), 81 deletions(-) diff --git a/packages/editor/src/components/graph/ee-flow/components/Flow.tsx b/packages/editor/src/components/graph/ee-flow/components/Flow.tsx index 8b15eb7973..1e7ebc9d9d 100644 --- a/packages/editor/src/components/graph/ee-flow/components/Flow.tsx +++ b/packages/editor/src/components/graph/ee-flow/components/Flow.tsx @@ -24,7 +24,7 @@ Ethereal Engine. All Rights Reserved. */ import React, { useEffect, useRef } from 'react' -import { Background, BackgroundVariant, ReactFlow } from 'reactflow' +import { Background, BackgroundVariant, ReactFlow, ReactFlowProvider } from 'reactflow' import { GraphJSON, IRegistry } from '@behave-graph/core' @@ -81,9 +81,10 @@ export const Flow: React.FC = ({ initialGraph: graph, examples, regis registry }) - const { onSelectionChange, handleKeyDown, handleKeyUp } = useSelectionHandler({ + const { onSelectionChange } = useSelectionHandler({ nodes, - onNodesChange + onNodesChange, + onEdgesChange }) useEffect(() => { @@ -92,49 +93,49 @@ export const Flow: React.FC = ({ initialGraph: graph, examples, regis }, [graphJson]) // change in node position triggers reactor return ( - dragging.set(true)} - onNodeDragStop={() => dragging.set(false)} - onNodesChange={onNodesChange} - onEdgesChange={onEdgesChange} - onConnect={onConnect} - onConnectStart={handleStartConnect} - onConnectEnd={handleStopConnect} - onPaneMouseEnter={() => mouseOver.set(true)} - onPaneMouseLeave={() => mouseOver.set(false)} - fitView - fitViewOptions={{ maxZoom: 1 }} - onPaneClick={handlePaneClick} - onPaneContextMenu={handlePaneContextMenu} - onSelectionChange={onSelectionChange} - multiSelectionKeyCode={'Shift'} - deleteKeyCode={'Backspace'} - onKeyDown={handleKeyDown} - onKeyUp={handleKeyUp} - > - - - {nodePickerVisibility && ( - + dragging.set(true)} + onNodeDragStop={() => dragging.set(false)} + onNodesChange={onNodesChange} + onEdgesChange={onEdgesChange} + onConnect={onConnect} + onConnectStart={handleStartConnect} + onConnectEnd={handleStopConnect} + onPaneMouseEnter={() => mouseOver.set(true)} + onPaneMouseLeave={() => mouseOver.set(false)} + fitView + fitViewOptions={{ maxZoom: 1 }} + onPaneClick={handlePaneClick} + onPaneContextMenu={handlePaneContextMenu} + onSelectionChange={onSelectionChange} + multiSelectionKeyCode={'Shift'} + deleteKeyCode={'Backspace'} + > + - )} - + + {nodePickerVisibility && ( + + )} + + ) } diff --git a/packages/editor/src/components/graph/ee-flow/hooks/useSelectionHandler.ts b/packages/editor/src/components/graph/ee-flow/hooks/useSelectionHandler.ts index 4e9ce68242..38b1fe9e65 100644 --- a/packages/editor/src/components/graph/ee-flow/hooks/useSelectionHandler.ts +++ b/packages/editor/src/components/graph/ee-flow/hooks/useSelectionHandler.ts @@ -22,8 +22,8 @@ Original Code is the Ethereal Engine team. All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 Ethereal Engine. All Rights Reserved. */ -import { useMemo, useState } from 'react' -import { Node, NodeChange } from 'reactflow' +import { useEffect, useMemo, useState } from 'react' +import { Edge, EdgeChange, Node, NodeChange, useKeyPress } from 'reactflow' import { v4 as uuidv4 } from 'uuid' import { useBehaveGraphFlow } from './useBehaveGraphFlow' @@ -32,26 +32,56 @@ type BehaveGraphFlow = ReturnType export const useSelectionHandler = ({ nodes, - onNodesChange -}: Pick & { + onNodesChange, + onEdgesChange +}: Pick & { nodes: Node[] }) => { + const ctrlCPressed = useKeyPress(['Control+c', 'Meta+c']) + const ctrlVPressed = useKeyPress(['Control+v', 'Meta+v']) const [selectedNodes, setSelectedNodes] = useState([] as Node[]) + const [selectedEdges, setSelectedEdges] = useState([] as Edge[]) + const [copiedNodes, setCopiedNodes] = useState([] as Node[]) + const [copiedEdges, setCopiedEdges] = useState([] as Edge[]) const copyNodes = () => { setCopiedNodes(selectedNodes) + setCopiedEdges(selectedEdges) } const pasteNodes = () => { - const newNodes = copiedNodes.map((node) => ({ - ...node, - id: uuidv4(), - position: { - x: node.position.x + node.width! + 10, - y: node.position.y + const minPosLeft = Math.min(...copiedNodes.map((node) => node.position.x)) + const maxPosLeft = Math.max(...copiedNodes.map((node) => node.position.x)) + const nodeMaxPosX = copiedNodes.reduce( + (maxNode, currentNode) => (currentNode.position.x > maxNode.position.x ? currentNode : maxNode), + copiedNodes[0] + ) + + const nodeIdMap = new Map() + const newNodes = copiedNodes.map((node) => { + nodeIdMap[node.id] = uuidv4() + return { + ...node, + id: nodeIdMap[node.id], + position: { + x: maxPosLeft + (node.position.x - minPosLeft) + nodeMaxPosX.width! + 20, + y: node.position.y + } } - })) + }) + + const newEdgeChange: EdgeChange[] = copiedEdges.map((edge) => { + return { + type: 'add', + item: { + ...edge, + id: uuidv4(), + source: nodeIdMap[edge.source], + target: nodeIdMap[edge.target] + } + } + }) const newNodeChange: NodeChange[] = newNodes.map((node) => ({ type: 'add', @@ -59,40 +89,27 @@ export const useSelectionHandler = ({ })) onNodesChange(newNodeChange) + onEdgesChange(newEdgeChange) setCopiedNodes(newNodes) } const onSelectionChange = useMemo( () => (elements) => { setSelectedNodes(elements.nodes) + setSelectedEdges(elements.edges) }, [selectedNodes] ) - const handleKeyDown = useMemo( - () => (event) => { - if (!event.ctrlKey) return - - switch (event.key) { - case 'c': - copyNodes() - break - case 'v': - pasteNodes() - break - default: - break - } - }, - [copiedNodes, onNodesChange, selectedNodes] - ) + useEffect(() => { + if (!ctrlCPressed || selectedNodes.length === 0) return + copyNodes() + }, [ctrlCPressed]) - const handleKeyUp = useMemo( - () => (event) => { - // empty for now - }, - [] - ) + useEffect(() => { + if (!ctrlVPressed || copiedNodes.length === 0) return + pasteNodes() + }, [ctrlVPressed]) - return { onSelectionChange, handleKeyDown, handleKeyUp } + return { onSelectionChange } }