From 1eab033023fe1142fb300dc6e4c24b1ba839ec66 Mon Sep 17 00:00:00 2001 From: Michael Charfadi Date: Thu, 20 Jun 2024 09:29:43 +0200 Subject: [PATCH] [3651] Improve isDescendantOf used in diagram MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug: https://github.com/eclipse-sirius/sirius-web/issues/3651 Signed-off-by: Michaƫl Charfadi --- CHANGELOG.adoc | 1 + .../src/converter/convertDiagram.ts | 8 ++- .../src/renderer/connector/useConnector.tsx | 4 +- .../src/renderer/dropNode/useDropNode.ts | 22 +++++--- .../src/renderer/edge/EdgeLayout.ts | 47 ++++++++-------- .../src/renderer/edge/EdgeLayout.types.ts | 10 ++-- .../src/renderer/handles/useHandleChange.tsx | 7 +-- .../renderer/helper-lines/useHelperLines.tsx | 54 +++++++++++-------- .../src/renderer/layout/layoutHandles.ts | 24 ++++++--- .../src/renderer/layout/layoutNode.ts | 32 ++++++----- 10 files changed, 121 insertions(+), 88 deletions(-) diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index af84c9640e..b0b97a19d7 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -111,6 +111,7 @@ image:doc/screenshots/insideLabelPositions.png[Inside label positions, 70%] - https://github.com/eclipse-sirius/sirius-web/issues/3634[#3634] [sirius-web] Simplifying the contribution to the GraphQL subscription of the diagram for custom nodes - https://github.com/eclipse-sirius/sirius-web/issues/3656[#3656] [core] Add the ability to customize the GraphQL type resolver - https://github.com/eclipse-sirius/sirius-web/issues/3645[#3645] [core] Revert earlier change made in #3595 which caused regressions +- https://github.com/eclipse-sirius/sirius-web/issues/3651[#3651] [diagram] Improve performances when interacting with the diagram, especially when dragging a node == v2024.5.0 diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/converter/convertDiagram.ts b/packages/diagrams/frontend/sirius-components-diagrams/src/converter/convertDiagram.ts index 272e0e23bf..28c707b191 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/converter/convertDiagram.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/converter/convertDiagram.ts @@ -208,9 +208,15 @@ export const convertDiagram = ( nodes, edges, }; + + const nodeInternals = new Map(); + nodes.forEach((node) => { + nodeInternals.set(node.id, node); + }); + computeBorderNodeExtents(rawDiagram.nodes); computeBorderNodePositions(rawDiagram.nodes); - layoutHandles(rawDiagram, diagramDescription); + layoutHandles(rawDiagram, diagramDescription, nodeInternals); return { metadata: { diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/connector/useConnector.tsx b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/connector/useConnector.tsx index 1add52a18d..aa69e1828a 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/connector/useConnector.tsx +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/connector/useConnector.tsx @@ -52,7 +52,7 @@ export const useConnector = (): UseConnectorValue => { } = useContext(ConnectorContext); const reactFlowInstance = useReactFlow(); - const { getNodes, getNode, setEdges } = reactFlowInstance; + const { getNode, setEdges } = reactFlowInstance; const theme = useTheme(); const { hideDiagramElementPalette } = useDiagramElementPalette(); @@ -110,7 +110,7 @@ export const useConnector = (): UseConnectorValue => { const { targetPosition, sourcePosition } = getEdgeParameters( sourceNode, targetNode, - getNodes(), + store.getState().nodeInternals, diagramDescription.arrangeLayoutDirection ); diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/dropNode/useDropNode.ts b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/dropNode/useDropNode.ts index 695595b07b..d38fd45b2b 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/dropNode/useDropNode.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/dropNode/useDropNode.ts @@ -13,7 +13,7 @@ import { gql, useMutation } from '@apollo/client'; import { useMultiToast } from '@eclipse-sirius/sirius-components-core'; import { useCallback, useContext, useEffect } from 'react'; -import { Node, NodeDragHandler, XYPosition, useReactFlow } from 'reactflow'; +import { Node, NodeDragHandler, XYPosition, useReactFlow, useStoreApi } from 'reactflow'; import { DiagramContext } from '../../contexts/DiagramContext'; import { DiagramContextValue } from '../../contexts/DiagramContext.types'; import { useDiagramDescription } from '../../contexts/useDiagramDescription'; @@ -133,13 +133,16 @@ export const useDropNode = (): UseDropNodeValue => { const onDropNode = useDropNodeMutation(); const { getNodes, getIntersectingNodes, screenToFlowPosition } = useReactFlow(); const { setNodes } = useStore(); + const storeApi = useStoreApi(); - const getNodeById: (string) => Node | undefined = (id: string) => getNodes().find((n) => n.id === id); + const getNodeById = (id: string) => storeApi.getState().nodeInternals.get(id); const getDraggableNode = (node: Node): Node => { - const parentNode = getNodeById(node.parentNode); - if (parentNode && isListData(parentNode) && !parentNode.data.areChildNodesDraggable) { - return getDraggableNode(parentNode); + if (node.parentNode) { + const parentNode = getNodeById(node.parentNode); + if (parentNode && isListData(parentNode) && !parentNode.data.areChildNodesDraggable) { + return getDraggableNode(parentNode); + } } return node; }; @@ -156,7 +159,10 @@ export const useDropNode = (): UseDropNodeValue => { (entry) => entry.droppedNodeDescriptionId === (computedNode as Node).data.descriptionId ); const compatibleNodes = getNodes() - .filter((candidate) => !candidate.hidden && !isDescendantOf(computedNode, candidate, getNodeById)) + .filter( + (candidate) => + !candidate.hidden && !isDescendantOf(computedNode, candidate, storeApi.getState().nodeInternals) + ) .filter((candidate) => dropDataEntry?.droppableOnNodeTypes.includes((candidate as Node).data.descriptionId) ) @@ -197,7 +203,9 @@ export const useDropNode = (): UseDropNodeValue => { const intersections = getIntersectingNodes(draggedNode).filter((intersectingNode) => !intersectingNode.hidden); const newParentId = [...intersections] - .filter((intersectingNode) => !isDescendantOf(draggedNode, intersectingNode, getNodeById)) + .filter( + (intersectingNode) => !isDescendantOf(draggedNode, intersectingNode, storeApi.getState().nodeInternals) + ) .sort((n1, n2) => getNodeDepth(n2, intersections) - getNodeDepth(n1, intersections))[0]?.id || null; const targetNode = getNodes().find((node) => node.data.isDropNodeTarget) || null; diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/edge/EdgeLayout.ts b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/edge/EdgeLayout.ts index 67654dc9d5..d7ce61b757 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/edge/EdgeLayout.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/edge/EdgeLayout.ts @@ -15,6 +15,7 @@ import { HandleElement, Position, XYPosition, internalsSymbol } from 'reactflow' import { BorderNodePosition } from '../DiagramRenderer.types'; import { ConnectionHandle } from '../handles/ConnectionHandles.types'; import { isDescendantOf, isSiblingOrDescendantOf } from '../layout/layoutNode'; +import { horizontalLayoutDirectionGap, verticalLayoutDirectionGap } from '../layout/layoutParams'; import { GetEdgeParameters, GetEdgeParametersWhileMoving, @@ -24,7 +25,6 @@ import { GetUpdatedConnectionHandlesParameters, NodeCenter, } from './EdgeLayout.types'; -import { verticalLayoutDirectionGap, horizontalLayoutDirectionGap } from '../layout/layoutParams'; export const getUpdatedConnectionHandles: GetUpdatedConnectionHandlesParameters = ( sourceNode, @@ -62,11 +62,11 @@ export const getEdgeParametersWhileMoving: GetEdgeParametersWhileMoving = ( movingNode, source, target, - visiblesNodes, + nodeInternals, layoutDirection ) => { - const { position: sourcePosition } = getParameters(movingNode, source, target, visiblesNodes, layoutDirection); - const { position: targetPosition } = getParameters(movingNode, target, source, visiblesNodes, layoutDirection); + const { position: sourcePosition } = getParameters(movingNode, source, target, nodeInternals, layoutDirection); + const { position: targetPosition } = getParameters(movingNode, target, source, nodeInternals, layoutDirection); return { sourcePosition, @@ -74,9 +74,9 @@ export const getEdgeParametersWhileMoving: GetEdgeParametersWhileMoving = ( }; }; -export const getEdgeParameters: GetEdgeParameters = (source, target, visiblesNodes, layoutDirection) => { - const { position: sourcePosition } = getParameters(null, source, target, visiblesNodes, layoutDirection); - const { position: targetPosition } = getParameters(null, target, source, visiblesNodes, layoutDirection); +export const getEdgeParameters: GetEdgeParameters = (source, target, nodeInternals, layoutDirection) => { + const { position: sourcePosition } = getParameters(null, source, target, nodeInternals, layoutDirection); + const { position: targetPosition } = getParameters(null, target, source, nodeInternals, layoutDirection); return { sourcePosition, @@ -107,11 +107,9 @@ const computeBorderNodeHandlePosition = ( } }; -const getParameters: GetParameters = (movingNode, nodeA, nodeB, visiblesNodes, layoutDirection) => { +const getParameters: GetParameters = (movingNode, nodeA, nodeB, nodeInternals, layoutDirection) => { if (nodeA.data.isBorderNode) { - const isInside = isSiblingOrDescendantOf(nodeA, nodeB, (nodeId) => - visiblesNodes.find((node) => node.id === nodeId) - ); + const isInside = isSiblingOrDescendantOf(nodeA, nodeB, nodeInternals); return { position: computeBorderNodeHandlePosition(nodeA.data.borderNodePosition, isInside), }; @@ -123,7 +121,7 @@ const getParameters: GetParameters = (movingNode, nodeA, nodeB, visiblesNodes, l y: (movingNode.positionAbsolute?.y ?? 0) + (nodeA.height ?? 0) / 2, }; } else { - centerA = getNodeCenter(nodeA, visiblesNodes); + centerA = getNodeCenter(nodeA, nodeInternals); } let centerB: NodeCenter; @@ -133,11 +131,11 @@ const getParameters: GetParameters = (movingNode, nodeA, nodeB, visiblesNodes, l y: (movingNode.positionAbsolute?.y ?? 0) + (nodeB.height ?? 0) / 2, }; } else { - centerB = getNodeCenter(nodeB, visiblesNodes); + centerB = getNodeCenter(nodeB, nodeInternals); } const horizontalDifference = Math.abs(centerA.x - centerB.x); const verticalDifference = Math.abs(centerA.y - centerB.y); - const isDescendant = isDescendantOf(nodeB, nodeA, (nodeId) => visiblesNodes.find((node) => node.id === nodeId)); + const isDescendant = isDescendantOf(nodeB, nodeA, nodeInternals); let position: Position; if (isVerticalLayoutDirection(layoutDirection)) { if (Math.abs(centerA.y - centerB.y) < verticalLayoutDirectionGap) { @@ -175,25 +173,28 @@ const getParameters: GetParameters = (movingNode, nodeA, nodeB, visiblesNodes, l }; }; -export const getNodeCenter: GetNodeCenter = (node, visiblesNodes) => { +export const getNodeCenter: GetNodeCenter = (node, nodeInternals) => { if (node.positionAbsolute?.x && node.positionAbsolute?.y) { return { x: (node.positionAbsolute?.x ?? 0) + (node.width ?? 0) / 2, y: (node.positionAbsolute?.y ?? 0) + (node.height ?? 0) / 2, }; } else { - let parentNode = visiblesNodes.find((nodeParent) => nodeParent.id === node.parentNode); let position = { x: (node.position?.x ?? 0) + (node.width ?? 0) / 2, y: (node.position?.y ?? 0) + (node.height ?? 0) / 2, }; - while (parentNode) { - position = { - x: position.x + (parentNode.position?.x ?? 0), - y: position.y + (parentNode.position?.y ?? 0), - }; - let parentNodeId = parentNode.parentNode ?? ''; - parentNode = visiblesNodes.find((nodeParent) => nodeParent.id === parentNodeId); + + if (node.parentNode) { + let parentNode = nodeInternals.get(node.parentNode); + while (parentNode) { + position = { + x: position.x + (parentNode.position?.x ?? 0), + y: position.y + (parentNode.position?.y ?? 0), + }; + let parentNodeId = parentNode.parentNode ?? ''; + parentNode = nodeInternals.get(parentNodeId); + } } return position; } diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/edge/EdgeLayout.types.ts b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/edge/EdgeLayout.types.ts index 48c8b85c57..e383e74993 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/edge/EdgeLayout.types.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/edge/EdgeLayout.types.ts @@ -11,7 +11,7 @@ * Obeo - initial API and implementation *******************************************************************************/ -import { HandleElement, Node, NodePositionChange, Position, XYPosition } from 'reactflow'; +import { HandleElement, Node, NodeInternals, NodePositionChange, Position, XYPosition } from 'reactflow'; import { NodeData } from '../DiagramRenderer.types'; import { ConnectionHandle } from '../handles/ConnectionHandles.types'; @@ -38,14 +38,14 @@ export type GetEdgeParametersWhileMoving = ( movingNode: NodePositionChange, source: Node, target: Node, - visiblesNodes: Node[], + nodeInternals: NodeInternals, layoutDirection: string ) => EdgeParameters; export type GetEdgeParameters = ( source: Node, target: Node, - visiblesNodes: Node[], + nodeInternals: NodeInternals, layoutDirection: string ) => EdgeParameters; @@ -58,7 +58,7 @@ export type GetParameters = ( movingNode: NodePositionChange | null, nodeA: Node, nodeB: Node, - visiblesNodes: Node[], + nodeInternals: NodeInternals, layoutDirection: string ) => Parameters; @@ -66,7 +66,7 @@ export interface Parameters { position: Position; } -export type GetNodeCenter = (node: Node, visiblesNodes: Node[]) => NodeCenter; +export type GetNodeCenter = (node: Node, nodeInternals: NodeInternals) => NodeCenter; export interface NodeCenter { x: number; diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/handles/useHandleChange.tsx b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/handles/useHandleChange.tsx index b41e9024fb..9f21de1b85 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/handles/useHandleChange.tsx +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/handles/useHandleChange.tsx @@ -11,14 +11,14 @@ * Obeo - initial API and implementation *******************************************************************************/ import { useCallback } from 'react'; -import { Node, NodeChange, NodePositionChange, getConnectedEdges } from 'reactflow'; +import { Node, NodeChange, NodePositionChange, getConnectedEdges, useStoreApi } from 'reactflow'; +import { useDiagramDescription } from '../../contexts/useDiagramDescription'; import { useStore } from '../../representation/useStore'; import { NodeData } from '../DiagramRenderer.types'; import { getEdgeParametersWhileMoving, getUpdatedConnectionHandles } from '../edge/EdgeLayout'; import { DiagramNodeType } from '../node/NodeTypes.types'; import { ConnectionHandle } from './ConnectionHandles.types'; import { UseHandleChangeValue } from './useHandleChange.types'; -import { useDiagramDescription } from '../../contexts/useDiagramDescription'; const isNodePositionChange = (change: NodeChange): change is NodePositionChange => change.type === 'position' && typeof change.dragging === 'boolean' && change.dragging; @@ -26,6 +26,7 @@ const isNodePositionChange = (change: NodeChange): change is NodePositionChange export const useHandleChange = (): UseHandleChangeValue => { const { getEdges } = useStore(); const { diagramDescription } = useDiagramDescription(); + const storeApi = useStoreApi(); const applyHandleChange = useCallback( (changes: NodeChange[], nodes: Node[]): Node[] => { @@ -44,7 +45,7 @@ export const useHandleChange = (): UseHandleChangeValue => { nodeDraggingChange, sourceNode, targetNode, - nodes, + storeApi.getState().nodeInternals, diagramDescription.arrangeLayoutDirection ); const nodeSourceConnectionHandle: ConnectionHandle | undefined = sourceNode.data.connectionHandles.find( diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/helper-lines/useHelperLines.tsx b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/helper-lines/useHelperLines.tsx index 133fe233bc..9dfa9851f8 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/helper-lines/useHelperLines.tsx +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/helper-lines/useHelperLines.tsx @@ -11,11 +11,19 @@ * Obeo - initial API and implementation *******************************************************************************/ import { useCallback, useState } from 'react'; -import { Node, NodeChange, NodeDimensionChange, NodePositionChange, useReactFlow } from 'reactflow'; +import { + Node, + NodeChange, + NodeDimensionChange, + NodeInternals, + NodePositionChange, + useReactFlow, + useStoreApi, +} from 'reactflow'; import { EdgeData, NodeData } from '../DiagramRenderer.types'; import { isDescendantOf } from '../layout/layoutNode'; -import { HelperLines, UseHelperLinesState, UseHelperLinesValue } from './useHelperLines.types'; import { horizontalHelperLinesSnapGap, verticalHelperLinesSnapGap } from '../layout/layoutParams'; +import { HelperLines, UseHelperLinesState, UseHelperLinesValue } from './useHelperLines.types'; const isMove = (change: NodeChange, dragging: boolean): change is NodePositionChange => change.type === 'position' && (!dragging || (typeof change.dragging === 'boolean' && change.dragging)); @@ -26,11 +34,11 @@ const isResize = (change: NodeChange): change is NodeDimensionChange => const getHelperLinesForMove = ( change: NodePositionChange, movingNode: Node, - nodes: Node[] + nodes: Node[], + nodeInternal: NodeInternals ): HelperLines => { const noHelperLines: HelperLines = { horizontal: null, vertical: null, snapX: 0, snapY: 0 }; if (change.positionAbsolute) { - const getNodeById = (id: string | undefined) => nodes.find((n) => n.id === id); let verticalSnapGap: number = verticalHelperLinesSnapGap; let horizontalSnapGap: number = horizontalHelperLinesSnapGap; const movingNodeBounds: { x1: number; x2: number; y1: number; y2: number } = { @@ -41,7 +49,7 @@ const getHelperLinesForMove = ( }; return nodes .filter((node) => node.id != movingNode.id) - .filter((node) => !isDescendantOf(movingNode, node, getNodeById)) + .filter((node) => !isDescendantOf(movingNode, node, nodeInternal)) .reduce((helperLines, otherNode) => { if (otherNode.positionAbsolute) { const otherNodeBounds = { @@ -132,11 +140,11 @@ const getHelperLinesForMove = ( const getHelperLinesForResize = ( change: NodeDimensionChange, resizingNode: Node, - nodes: Node[] + nodes: Node[], + nodeInternal: NodeInternals ): HelperLines => { const noHelperLines: HelperLines = { horizontal: null, vertical: null, snapX: 0, snapY: 0 }; if (resizingNode.positionAbsolute && change.dimensions) { - const getNodeById = (id: string | undefined) => nodes.find((n) => n.id === id); let verticalSnapGap: number = verticalHelperLinesSnapGap; let horizontalSnapGap: number = horizontalHelperLinesSnapGap; const resizingNodeBounds: { x1: number; x2: number; y1: number; y2: number } = { @@ -147,7 +155,7 @@ const getHelperLinesForResize = ( }; return nodes .filter((node) => node.id != resizingNode.id) - .filter((node) => !isDescendantOf(resizingNode, node, getNodeById)) + .filter((node) => !isDescendantOf(resizingNode, node, nodeInternal)) .reduce((helperLines, otherNode) => { if (otherNode.positionAbsolute) { const otherNodeBounds = { @@ -195,11 +203,11 @@ const getHelperLinesForResizeAndMove = ( resizingChange: NodeDimensionChange, movingChange: NodePositionChange, resizingNode: Node, - nodes: Node[] + nodes: Node[], + nodeInternal: NodeInternals ): HelperLines => { const noHelperLines: HelperLines = { horizontal: null, vertical: null, snapX: 0, snapY: 0 }; if (resizingNode.positionAbsolute && resizingChange.dimensions && movingChange.position) { - const getNodeById = (id: string | undefined) => nodes.find((n) => n.id === id); let verticalSnapGap: number = verticalHelperLinesSnapGap; let horizontalSnapGap: number = horizontalHelperLinesSnapGap; const nodeBounds: { x1: number; x2: number; y1: number; y2: number } = { @@ -218,7 +226,7 @@ const getHelperLinesForResizeAndMove = ( }; return nodes .filter((node) => node.id != resizingNode.id) - .filter((node) => !isDescendantOf(resizingNode, node, getNodeById)) + .filter((node) => !isDescendantOf(resizingNode, node, nodeInternal)) .reduce((helperLines, otherNode) => { if (otherNode.positionAbsolute) { const otherNodeBounds = { @@ -266,24 +274,25 @@ export const useHelperLines = (): UseHelperLinesValue => { const [enabled, setEnabled] = useState(false); const [state, setState] = useState({ vertical: null, horizontal: null }); //Here we need the nodes in the ReactFlow store to get positionAbsolute + const storeApi = useStoreApi(); const { getNodes } = useReactFlow(); - const applyHelperLines = useCallback( (changes: NodeChange[]): NodeChange[] => { + const nodeInternal: NodeInternals = storeApi.getState().nodeInternals; if (enabled && changes.length === 1 && changes[0]) { const change = changes[0]; if (isMove(change, true)) { - const movingNode = getNodes().find((node) => node.id === change.id); + const movingNode = nodeInternal.get(change.id); if (movingNode && !movingNode.data.pinned) { - const helperLines: HelperLines = getHelperLinesForMove(change, movingNode, getNodes()); + const helperLines: HelperLines = getHelperLinesForMove(change, movingNode, getNodes(), nodeInternal); setState({ vertical: helperLines.vertical, horizontal: helperLines.horizontal }); let snapOffsetX: number = 0; let snapOffsetY: number = 0; - let parentNode = getNodes().find((node) => node.id === movingNode.parentNode); + let parentNode = nodeInternal.get(movingNode.parentNode || ''); while (parentNode) { snapOffsetX -= parentNode.position.x; snapOffsetY -= parentNode.position.y; - parentNode = getNodes().find((node) => node.id === (parentNode?.parentNode ?? '')); + parentNode = nodeInternal.get(parentNode?.parentNode ?? ''); } if (helperLines.snapX && change.position) { change.position.x = helperLines.snapX + snapOffsetX; @@ -293,9 +302,9 @@ export const useHelperLines = (): UseHelperLinesValue => { } } } else if (isResize(change)) { - const resizingNode = getNodes().find((node) => node.id === change.id); + const resizingNode = nodeInternal.get(change.id); if (resizingNode) { - const helperLines: HelperLines = getHelperLinesForResize(change, resizingNode, getNodes()); + const helperLines: HelperLines = getHelperLinesForResize(change, resizingNode, getNodes(), nodeInternal); setState({ vertical: helperLines.vertical, horizontal: helperLines.horizontal }); if (helperLines.snapX && change.dimensions && resizingNode.positionAbsolute) { change.dimensions.width = Math.abs(resizingNode.positionAbsolute.x - helperLines.snapX); @@ -309,22 +318,23 @@ export const useHelperLines = (): UseHelperLinesValue => { const movingChange = changes[0]; const resizingChange = changes[1]; if (isMove(movingChange, false) && isResize(resizingChange)) { - const resizingNode = getNodes().find((node) => node.id === movingChange.id); + const resizingNode = nodeInternal.get(movingChange.id); if (resizingNode) { const helperLines: HelperLines = getHelperLinesForResizeAndMove( resizingChange, movingChange, resizingNode, - getNodes() + getNodes(), + nodeInternal ); setState({ vertical: helperLines.vertical, horizontal: helperLines.horizontal }); let snapOffsetX: number = 0; let snapOffsetY: number = 0; - let parentNode = getNodes().find((node) => node.id === resizingNode.parentNode); + let parentNode = nodeInternal.get(resizingNode.parentNode || ''); while (parentNode) { snapOffsetX -= parentNode.position.x; snapOffsetY -= parentNode.position.y; - parentNode = getNodes().find((node) => node.id === (parentNode?.parentNode ?? '')); + parentNode = nodeInternal.get(parentNode?.parentNode ?? ''); } if ( helperLines.snapX && diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/layout/layoutHandles.ts b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/layout/layoutHandles.ts index 0f4b1fd1a5..2116703cd6 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/layout/layoutHandles.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/layout/layoutHandles.ts @@ -10,7 +10,7 @@ * Contributors: * Obeo - initial API and implementation *******************************************************************************/ -import { Node, Position, XYPosition } from 'reactflow'; +import { Node, NodeInternals, Position, XYPosition } from 'reactflow'; import { GQLDiagramDescription } from '../../representation/DiagramRepresentation.types'; import { NodeData } from '../DiagramRenderer.types'; import { getEdgeParameters, getNodeCenter, getUpdatedConnectionHandles } from '../edge/EdgeLayout'; @@ -91,13 +91,13 @@ const populateHandleIdToOtherEndNode: PopulateHandleIdToOtherHandNode = ( }); }; -const layoutHandleIndex = (diagram: RawDiagram) => { +const layoutHandleIndex = (diagram: RawDiagram, nodeInternals: NodeInternals) => { const handleIdToOtherEndNode: Map> = new Map>(); const nodeIdToNodeCenter: Map = new Map(); diagram.nodes.forEach((node) => { const handlesId = getHandlesIdsFromNode(node); populateHandleIdToOtherEndNode(diagram.edges, diagram.nodes, handlesId, handleIdToOtherEndNode); - nodeIdToNodeCenter.set(node.id, getNodeCenter(node, diagram.nodes)); + nodeIdToNodeCenter.set(node.id, getNodeCenter(node, nodeInternals)); }); const nodeIdToConnectionHandle: Map = new Map(); @@ -128,7 +128,11 @@ const layoutHandleIndex = (diagram: RawDiagram) => { }); }; -const layoutHandlePosition = (diagram: RawDiagram, diagramDescription: GQLDiagramDescription) => { +const layoutHandlePosition = ( + diagram: RawDiagram, + diagramDescription: GQLDiagramDescription, + nodeInternals: NodeInternals +) => { diagram.edges.forEach((edge) => { const { sourceNode: sourceEdgeNode, targetNode: targetEdgeNode, sourceHandle, targetHandle } = edge; const sourceNode = diagram.nodes.find((node) => node.id === sourceEdgeNode?.id); @@ -137,7 +141,7 @@ const layoutHandlePosition = (diagram: RawDiagram, diagramDescription: GQLDiagra const { sourcePosition, targetPosition } = getEdgeParameters( sourceNode, targetNode, - diagram.nodes, + nodeInternals, diagramDescription.arrangeLayoutDirection ); @@ -177,7 +181,11 @@ const layoutHandlePosition = (diagram: RawDiagram, diagramDescription: GQLDiagra }); }; -export const layoutHandles = (diagram: RawDiagram, diagramDescription: GQLDiagramDescription) => { - layoutHandlePosition(diagram, diagramDescription); - layoutHandleIndex(diagram); +export const layoutHandles = ( + diagram: RawDiagram, + diagramDescription: GQLDiagramDescription, + nodeInternals: NodeInternals +) => { + layoutHandlePosition(diagram, diagramDescription, nodeInternals); + layoutHandleIndex(diagram, nodeInternals); }; diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/layout/layoutNode.ts b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/layout/layoutNode.ts index 8926acb0b2..a1a159dac6 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/layout/layoutNode.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/layout/layoutNode.ts @@ -10,8 +10,9 @@ * Contributors: * Obeo - initial API and implementation *******************************************************************************/ -import { Box, Dimensions, Node, Rect, XYPosition, boxToRect, rectToBox } from 'reactflow'; -import { NodeData, InsideLabel } from '../DiagramRenderer.types'; +import { Box, Dimensions, Node, NodeInternals, Rect, XYPosition, boxToRect, rectToBox } from 'reactflow'; +import { InsideLabel, NodeData } from '../DiagramRenderer.types'; +import { computePreviousPosition } from './bounds'; import { RawDiagram } from './layout.types'; import { getBorderNodeExtent, @@ -28,7 +29,6 @@ import { gap, rectangularNodePadding, } from './layoutParams'; -import { computePreviousPosition } from './bounds'; /** * It requires that nodes are already positioned @@ -512,28 +512,26 @@ export const applyRatioOnNewNodeSizeValue = (node: Node) => { } }; -export const isDescendantOf = ( - parent: Node, - candidate: Node, - nodeById: (nodeId: string | undefined) => Node | undefined -): boolean => { +export const isDescendantOf = (parent: Node, candidate: Node, nodeInternals: NodeInternals): boolean => { if (parent.id === candidate.id) { return true; } else { - const candidateParent: Node | undefined = nodeById(candidate.parentNode); - return candidateParent !== undefined && isDescendantOf(parent, candidateParent, nodeById); + if (candidate.parentNode) { + const candidateParent: Node | undefined = nodeInternals.get(candidate.parentNode); + return !!candidateParent && isDescendantOf(parent, candidateParent, nodeInternals); + } + return false; } }; -export const isSiblingOrDescendantOf = ( - sibling: Node, - candidate: Node, - nodeById: (nodeId: string | undefined) => Node | undefined -): boolean => { +export const isSiblingOrDescendantOf = (sibling: Node, candidate: Node, nodeInternals: NodeInternals): boolean => { if (sibling.parentNode === candidate.id) { return true; } else { - const candidateParent: Node | undefined = nodeById(candidate.parentNode); - return candidateParent !== undefined && isSiblingOrDescendantOf(sibling, candidateParent, nodeById); + if (candidate.parentNode) { + const candidateParent: Node | undefined = nodeInternals.get(candidate.parentNode); + return !!candidateParent && isSiblingOrDescendantOf(sibling, candidateParent, nodeInternals); + } + return false; } };