From ad239562d71633f29eb9d362ec7f05fbc0d31951 Mon Sep 17 00:00:00 2001 From: Michael Charfadi Date: Tue, 14 Nov 2023 15:32:44 +0100 Subject: [PATCH] [2572] Use dynamic handles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug: https://github.com/eclipse-sirius/sirius-web/issues/2572 Signed-off-by: Michaƫl Charfadi --- .../src/converter/ConvertEngine.types.ts | 4 +- .../IconLabelNodeConverterHandler.ts | 6 +- .../converter/ImageNodeConverterHandler.ts | 16 ++- .../src/converter/ListNodeConverterHandler.ts | 14 ++- .../RectangleNodeConverterHandler.ts | 14 ++- .../src/converter/convertDiagram.ts | 21 +++- .../src/converter/convertHandles.ts | 62 ++++++++++ .../src/renderer/DiagramRenderer.tsx | 4 +- .../src/renderer/DiagramRenderer.types.ts | 2 + .../src/renderer/edge/EdgeLayout.ts | 91 ++++++++++----- .../src/renderer/edge/EdgeLayout.types.ts | 40 +++++-- .../src/renderer/edge/MultiLabelEdge.tsx | 35 +++--- .../renderer/handles/ConnectionHandles.tsx | 108 ++++++++++++++++++ .../handles/ConnectionHandles.types.ts | 23 ++++ .../src/renderer/handles/useHandleChange.tsx | 83 ++++++++++++++ .../renderer/handles/useHandleChange.types.ts | 17 +++ .../handles/useRefreshConnectionHandles.tsx | 41 +++++++ .../useRefreshConnectionHandles.types.ts | 18 +++ .../src/renderer/node/ImageNode.tsx | 43 ++----- .../src/renderer/node/ListNode.tsx | 42 ++----- .../src/renderer/node/RectangularNode.tsx | 42 ++----- 21 files changed, 550 insertions(+), 176 deletions(-) create mode 100644 packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/converter/convertHandles.ts create mode 100644 packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/handles/ConnectionHandles.tsx create mode 100644 packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/handles/ConnectionHandles.types.ts create mode 100644 packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/handles/useHandleChange.tsx create mode 100644 packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/handles/useHandleChange.types.ts create mode 100644 packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/handles/useRefreshConnectionHandles.tsx create mode 100644 packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/handles/useRefreshConnectionHandles.types.ts diff --git a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/converter/ConvertEngine.types.ts b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/converter/ConvertEngine.types.ts index da89c17c27..464008cb49 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/converter/ConvertEngine.types.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/converter/ConvertEngine.types.ts @@ -11,6 +11,7 @@ * Obeo - initial API and implementation *******************************************************************************/ import { Node } from 'reactflow'; +import { GQLEdge } from '../graphql/subscription/edgeFragment.types'; import { GQLNode, GQLNodeStyle } from '../graphql/subscription/nodeFragment.types'; export interface IConvertEngine { @@ -25,6 +26,7 @@ export interface INodeConverterHandler { gqlNode: GQLNode, parentNode: GQLNode | null, isBorderNode: boolean, - nodes: Node[] + nodes: Node[], + gqlEdges: GQLEdge[] ): void; } diff --git a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/converter/IconLabelNodeConverterHandler.ts b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/converter/IconLabelNodeConverterHandler.ts index 4b3a58f4fe..61a8f459db 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/converter/IconLabelNodeConverterHandler.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/converter/IconLabelNodeConverterHandler.ts @@ -18,9 +18,10 @@ import { GQLViewModifier, } from '../graphql/subscription/nodeFragment.types'; import { BorderNodePositon } from '../renderer/DiagramRenderer.types'; +import { ConnectionHandle } from '../renderer/handles/ConnectionHandles.types'; import { IconLabelNodeData } from '../renderer/node/IconsLabelNode.types'; -import { convertLabelStyle } from './convertDiagram'; import { IConvertEngine, INodeConverterHandler } from './ConvertEngine.types'; +import { convertLabelStyle } from './convertDiagram'; const defaultPosition: XYPosition = { x: 0, y: 0 }; @@ -41,6 +42,8 @@ const toIconLabelNode = ( labelEditable, } = gqlNode; + const connectionHandles: ConnectionHandle[] = []; + const data: IconLabelNodeData = { targetObjectId, targetObjectLabel, @@ -55,6 +58,7 @@ const toIconLabelNode = ( borderNodePosition: isBorderNode ? BorderNodePositon.WEST : null, faded: state === GQLViewModifier.Faded, labelEditable: labelEditable, + connectionHandles, }; if (insideLabel) { diff --git a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/converter/ImageNodeConverterHandler.ts b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/converter/ImageNodeConverterHandler.ts index d4ae7f8957..228e79418c 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/converter/ImageNodeConverterHandler.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/converter/ImageNodeConverterHandler.ts @@ -11,19 +11,23 @@ * Obeo - initial API and implementation *******************************************************************************/ import { Node, XYPosition } from 'reactflow'; +import { GQLEdge } from '../graphql/subscription/edgeFragment.types'; import { GQLImageNodeStyle, GQLNode, GQLNodeStyle, GQLViewModifier } from '../graphql/subscription/nodeFragment.types'; import { BorderNodePositon } from '../renderer/DiagramRenderer.types'; +import { ConnectionHandle } from '../renderer/handles/ConnectionHandles.types'; import { ImageNodeData } from '../renderer/node/ImageNode.types'; +import { IConvertEngine, INodeConverterHandler } from './ConvertEngine.types'; import { convertLabelStyle } from './convertDiagram'; import { AlignmentMap } from './convertDiagram.types'; -import { IConvertEngine, INodeConverterHandler } from './ConvertEngine.types'; +import { convertHandles } from './convertHandles'; const defaultPosition: XYPosition = { x: 0, y: 0 }; const toImageNode = ( gqlNode: GQLNode, gqlParentNode: GQLNode | null, - isBorderNode: boolean + isBorderNode: boolean, + gqlEdges: GQLEdge[] ): Node => { const { targetObjectId, @@ -37,6 +41,8 @@ const toImageNode = ( labelEditable, } = gqlNode; + const connectionHandles: ConnectionHandle[] = convertHandles(gqlNode, gqlEdges); + const data: ImageNodeData = { targetObjectId, targetObjectLabel, @@ -50,6 +56,7 @@ const toImageNode = ( borderNodePosition: isBorderNode ? BorderNodePositon.WEST : null, labelEditable, positionDependentRotation: style.positionDependentRotation, + connectionHandles, }; if (insideLabel) { @@ -106,9 +113,10 @@ export class ImageNodeConverterHandler implements INodeConverterHandler { gqlNode: GQLNode, parentNode: GQLNode | null, isBorderNode: boolean, - nodes: Node[] + nodes: Node[], + gqlEdges: GQLEdge[] ) { - nodes.push(toImageNode(gqlNode, parentNode, isBorderNode)); + nodes.push(toImageNode(gqlNode, parentNode, isBorderNode, gqlEdges)); convertEngine.convertNodes(gqlNode.borderNodes ?? [], gqlNode, nodes); convertEngine.convertNodes(gqlNode.childNodes ?? [], gqlNode, nodes); diff --git a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/converter/ListNodeConverterHandler.ts b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/converter/ListNodeConverterHandler.ts index 2dab1b3e94..151afcee6c 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/converter/ListNodeConverterHandler.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/converter/ListNodeConverterHandler.ts @@ -11,6 +11,7 @@ * Obeo - initial API and implementation *******************************************************************************/ import { Node, XYPosition } from 'reactflow'; +import { GQLEdge } from '../graphql/subscription/edgeFragment.types'; import { GQLNode, GQLNodeStyle, @@ -18,17 +19,20 @@ import { GQLViewModifier, } from '../graphql/subscription/nodeFragment.types'; import { BorderNodePositon } from '../renderer/DiagramRenderer.types'; +import { ConnectionHandle } from '../renderer/handles/ConnectionHandles.types'; import { ListNodeData } from '../renderer/node/ListNode.types'; import { IConvertEngine, INodeConverterHandler } from './ConvertEngine.types'; import { convertLabelStyle } from './convertDiagram'; import { AlignmentMap } from './convertDiagram.types'; +import { convertHandles } from './convertHandles'; const defaultPosition: XYPosition = { x: 0, y: 0 }; const toListNode = ( gqlNode: GQLNode, gqlParentNode: GQLNode | null, - isBorderNode: boolean + isBorderNode: boolean, + gqlEdges: GQLEdge[] ): Node => { const { targetObjectId, @@ -42,6 +46,8 @@ const toListNode = ( labelEditable, } = gqlNode; + const connectionHandles: ConnectionHandle[] = convertHandles(gqlNode, gqlEdges); + const data: ListNodeData = { targetObjectId, targetObjectLabel, @@ -59,6 +65,7 @@ const toListNode = ( borderNodePosition: isBorderNode ? BorderNodePositon.WEST : null, faded: state === GQLViewModifier.Faded, labelEditable, + connectionHandles, }; if (insideLabel) { @@ -120,9 +127,10 @@ export class ListNodeConverterHandler implements INodeConverterHandler { gqlNode: GQLNode, parentNode: GQLNode | null, isBorderNode: boolean, - nodes: Node[] + nodes: Node[], + gqlEdges: GQLEdge[] ) { - nodes.push(toListNode(gqlNode, parentNode, isBorderNode)); + nodes.push(toListNode(gqlNode, parentNode, isBorderNode, gqlEdges)); convertEngine.convertNodes(gqlNode.borderNodes ?? [], gqlNode, nodes); convertEngine.convertNodes(gqlNode.childNodes ?? [], gqlNode, nodes); } diff --git a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/converter/RectangleNodeConverterHandler.ts b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/converter/RectangleNodeConverterHandler.ts index c4b0c0de28..d5e127af6c 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/converter/RectangleNodeConverterHandler.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/converter/RectangleNodeConverterHandler.ts @@ -11,6 +11,7 @@ * Obeo - initial API and implementation *******************************************************************************/ import { Node, XYPosition } from 'reactflow'; +import { GQLEdge } from '../graphql/subscription/edgeFragment.types'; import { GQLNode, GQLNodeStyle, @@ -18,17 +19,20 @@ import { GQLViewModifier, } from '../graphql/subscription/nodeFragment.types'; import { BorderNodePositon } from '../renderer/DiagramRenderer.types'; +import { ConnectionHandle } from '../renderer/handles/ConnectionHandles.types'; import { RectangularNodeData } from '../renderer/node/RectangularNode.types'; import { IConvertEngine, INodeConverterHandler } from './ConvertEngine.types'; import { convertLabelStyle } from './convertDiagram'; import { AlignmentMap } from './convertDiagram.types'; +import { convertHandles } from './convertHandles'; const defaultPosition: XYPosition = { x: 0, y: 0 }; const toRectangularNode = ( gqlNode: GQLNode, gqlParentNode: GQLNode | null, - isBorderNode: boolean + isBorderNode: boolean, + gqlEdges: GQLEdge[] ): Node => { const { targetObjectId, @@ -42,6 +46,8 @@ const toRectangularNode = ( labelEditable, } = gqlNode; + const connectionHandles: ConnectionHandle[] = convertHandles(gqlNode, gqlEdges); + const data: RectangularNodeData = { targetObjectId, targetObjectLabel, @@ -60,6 +66,7 @@ const toRectangularNode = ( isBorderNode: isBorderNode, borderNodePosition: isBorderNode ? BorderNodePositon.EAST : null, labelEditable, + connectionHandles, }; if (insideLabel) { @@ -121,9 +128,10 @@ export class RectangleNodeConverterHandler implements INodeConverterHandler { gqlNode: GQLNode, parentNode: GQLNode | null, isBorderNode: boolean, - nodes: Node[] + nodes: Node[], + gqlEdges: GQLEdge[] ) { - nodes.push(toRectangularNode(gqlNode, parentNode, isBorderNode)); + nodes.push(toRectangularNode(gqlNode, parentNode, isBorderNode, gqlEdges)); convertEngine.convertNodes(gqlNode.borderNodes ?? [], gqlNode, nodes); convertEngine.convertNodes(gqlNode.childNodes ?? [], gqlNode, nodes); } diff --git a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/converter/convertDiagram.ts b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/converter/convertDiagram.ts index 30c6de1349..a0d9de4814 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/converter/convertDiagram.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/converter/convertDiagram.ts @@ -105,7 +105,7 @@ export const convertDiagram = ( if (nodeConverterHandler) { const isBorderNode: boolean = !!parentNode?.borderNodes?.map((borderNode) => borderNode.id).includes(node.id); - nodeConverterHandler.handle(this, node, parentNode, isBorderNode, nodes); + nodeConverterHandler.handle(this, node, parentNode, isBorderNode, nodes, gqlDiagram.edges); } }); }, @@ -118,8 +118,10 @@ export const convertDiagram = ( const nodeId2Depth = new Map(); nodes.forEach((node) => nodeId2Depth.set(node.id, nodeDepth(nodeId2node, node.id))); - + let usedHandles: string[] = []; const edges: Edge[] = gqlDiagram.edges.map((gqlEdge) => { + const sourceNode: Node | undefined = nodeId2node.get(gqlEdge.sourceId); + const targetNode: Node | undefined = nodeId2node.get(gqlEdge.targetId); const data: MultiLabelEdgeData = { targetObjectId: gqlEdge.targetObjectId, targetObjectKind: gqlEdge.targetObjectKind, @@ -138,6 +140,17 @@ export const convertDiagram = ( data.endLabel = convertEdgeLabel(gqlEdge.endLabel); } + const sourceHandle = sourceNode?.data.connectionHandles + .filter((handle) => handle.type === 'source') + .find((handle) => !usedHandles.find((usedHandles) => usedHandles === handle.id)); + + const targetHandle = targetNode?.data.connectionHandles + .filter((handle) => handle.type === 'target') + .find((handle) => !usedHandles.find((usedHandles) => usedHandles === handle.id)); + if (sourceHandle?.id && targetHandle?.id) { + usedHandles.push(sourceHandle?.id, targetHandle.id); + } + return { id: gqlEdge.id, type: 'multiLabelEdge', @@ -152,6 +165,10 @@ export const convertDiagram = ( }, data, hidden: gqlEdge.state === GQLViewModifier.Hidden, + sourceHandle: sourceHandle?.id, + targetHandle: targetHandle?.id, + sourceNode: sourceNode, + targetNode: targetNode, }; }); diff --git a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/converter/convertHandles.ts b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/converter/convertHandles.ts new file mode 100644 index 0000000000..f9616f6cb0 --- /dev/null +++ b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/converter/convertHandles.ts @@ -0,0 +1,62 @@ +/******************************************************************************* + * Copyright (c) 2023 Obeo. + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Obeo - initial API and implementation + *******************************************************************************/ +import { Position } from 'reactflow'; +import { ConnectionHandle } from '../renderer/handles/ConnectionHandles.types'; + +const numberSourceHandles = (handles: ConnectionHandle[]): string => { + return handles.filter((handle) => handle.type === 'source').length.toString(); +}; +const numberTargetHandles = (handles: ConnectionHandle[]): string => { + return handles.filter((handle) => handle.type === 'target').length.toString(); +}; +export const convertHandles = (gqlNode, gqlEdges) => { + const connectionHandles: ConnectionHandle[] = []; + gqlEdges.forEach((edge) => { + if (edge.sourceId === gqlNode.id) { + connectionHandles.push({ + id: `handle--source--${gqlNode.id}--${numberSourceHandles(connectionHandles)}`, + edgeId: edge.id, + nodeId: gqlNode.id, + position: Position.Right, + type: 'source', + }); + } + + if (edge.targetId === gqlNode.id) { + connectionHandles.push({ + id: `handle--target--${gqlNode.id}--${numberTargetHandles(connectionHandles)}`, + edgeId: edge.id, + nodeId: gqlNode.id, + position: Position.Left, + type: 'target', + }); + } + }); + connectionHandles.push({ + id: `handle--source--${gqlNode.id}--${numberSourceHandles(connectionHandles)}`, + edgeId: '', + nodeId: gqlNode.id, + position: Position.Right, + type: 'source', + }); + + connectionHandles.push({ + id: `handle--target--${gqlNode.id}--${numberTargetHandles(connectionHandles)}`, + edgeId: '', + nodeId: gqlNode.id, + position: Position.Left, + type: 'target', + }); + + return connectionHandles; +}; diff --git a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/DiagramRenderer.tsx b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/DiagramRenderer.tsx index f4ec65c3e2..df6b8ca688 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/DiagramRenderer.tsx +++ b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/DiagramRenderer.tsx @@ -45,6 +45,7 @@ import { useDrop } from './drop/useDrop'; import { useDropNode } from './dropNode/useDropNode'; import { edgeTypes } from './edge/EdgeTypes'; import { MultiLabelEdgeData } from './edge/MultiLabelEdge.types'; +import { useHandleChange } from './handles/useHandleChange'; import { useLayout } from './layout/useLayout'; import { DiagramNodeType } from './node/NodeTypes.types'; import { useNodeType } from './node/useNodeType'; @@ -81,6 +82,7 @@ export const DiagramRenderer = ({ diagramRefreshedEventPayload, selection, setSe const { reconnectEdge } = useReconnectEdge(); const { onDrop, onDragOver } = useDrop(); const { onBorderChange } = useBorderChange(); + const { onHandleChange } = useHandleChange(); const { getNodeTypes } = useNodeType(); const [nodes, setNodes, onNodesChange] = useNodesState([]); @@ -187,7 +189,7 @@ export const DiagramRenderer = ({ diagramRefreshedEventPayload, selection, setSe const handleNodesChange: OnNodesChange = (changes: NodeChange[]) => { onNodesChange(onBorderChange(changes)); - + onNodesChange(onHandleChange(changes)); const selectionEntries: SelectionEntry[] = changes .filter(isNodeSelectChange) .filter((change) => change.selected) diff --git a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/DiagramRenderer.types.ts b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/DiagramRenderer.types.ts index 66cea07eb8..7fc99b3b9d 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/DiagramRenderer.types.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/DiagramRenderer.types.ts @@ -15,6 +15,7 @@ import { Selection } from '@eclipse-sirius/sirius-components-core'; import { Edge, Node } from 'reactflow'; import { GQLDiagramRefreshedEventPayload } from '../graphql/subscription/diagramEventSubscription.types'; import { MultiLabelEdgeData } from './edge/MultiLabelEdge.types'; +import { ConnectionHandle } from './handles/ConnectionHandles.types'; import { DiagramNodeType } from './node/NodeTypes.types'; export type FitViewLifecycle = 'neverRendered' | 'shouldFitview' | 'viewfit'; @@ -60,6 +61,7 @@ export interface NodeData { borderNodePosition: BorderNodePositon | null; labelEditable: boolean; style: React.CSSProperties; + connectionHandles: ConnectionHandle[]; } export enum BorderNodePositon { diff --git a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/edge/EdgeLayout.ts b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/edge/EdgeLayout.ts index b5c7eeb3df..ce00ff11db 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/edge/EdgeLayout.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/edge/EdgeLayout.ts @@ -12,25 +12,44 @@ *******************************************************************************/ import { HandleElement, Position, internalsSymbol } from 'reactflow'; -import { GetEdgeParameters, GetHandleCoordinatesByPosition, GetNodeCenter, GetParameters } from './EdgeLayout.types'; +import { + GetEdgeParameters, + GetHandleCoordinatesByPosition, + GetNodeCenter, + GetParameters, + NodeCenter, +} from './EdgeLayout.types'; -export const getEdgeParameters: GetEdgeParameters = (source, target) => { - const { x: sourceX, y: sourceY, position: sourcePosition } = getParameters(source, target); - const { x: targetX, y: targetY, position: targetPosition } = getParameters(target, source); +export const getEdgeParameters: GetEdgeParameters = (movingNode, source, target, visiblesNodes) => { + const { position: sourcePosition } = getParameters(movingNode, source, target, visiblesNodes); + const { position: targetPosition } = getParameters(movingNode, target, source, visiblesNodes); return { - sourceX, - sourceY, sourcePosition, - targetX, - targetY, targetPosition, }; }; -const getParameters: GetParameters = (nodeA, nodeB) => { - const centerA = getNodeCenter(nodeA); - const centerB = getNodeCenter(nodeB); +const getParameters: GetParameters = (movingNode, nodeA, nodeB, visiblesNodes) => { + let centerA: NodeCenter; + if (movingNode.id === nodeA.id) { + centerA = { + x: movingNode.positionAbsolute?.x ?? 0 + (nodeA.width ?? 0) / 2, + y: movingNode.positionAbsolute?.y ?? 0 + (nodeA.height ?? 0) / 2, + }; + } else { + centerA = getNodeCenter(nodeA, visiblesNodes); + } + + let centerB: NodeCenter; + if (movingNode.id === nodeB.id) { + centerB = { + x: movingNode.positionAbsolute?.x ?? 0 + (nodeB.width ?? 0) / 2, + y: movingNode.positionAbsolute?.y ?? 0 + (nodeB.height ?? 0) / 2, + }; + } else { + centerB = getNodeCenter(nodeB, visiblesNodes); + } const horizontallDifference = Math.abs(centerA.x - centerB.x); const verticalDifference = Math.abs(centerA.y - centerB.y); @@ -42,43 +61,59 @@ const getParameters: GetParameters = (nodeA, nodeB) => { position = centerA.y > centerB.y ? Position.Top : Position.Bottom; } - const { x, y } = getHandleCoordinatesByPosition(nodeA, position); - return { - x, - y, position, }; }; -const getNodeCenter: GetNodeCenter = (node) => { - return { - x: node.positionAbsolute?.x ?? 0 + (node.width ?? 0) / 2, - y: node.positionAbsolute?.y ?? 0 + (node.height ?? 0) / 2, - }; +const getNodeCenter: GetNodeCenter = (node, visiblesNodes) => { + 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); + } + return position; + } }; -const getHandleCoordinatesByPosition: GetHandleCoordinatesByPosition = (node, handlePosition) => { - const handle: HandleElement | undefined = (node[internalsSymbol]?.handleBounds?.source ?? []).find( - (handle) => handle.position === handlePosition +export const getHandleCoordinatesByPosition: GetHandleCoordinatesByPosition = (node, handlePosition, handleId) => { + let handle: HandleElement | undefined = (node[internalsSymbol]?.handleBounds?.source ?? []).find( + (handle) => handle.id === handleId ); + if (!handle) { + handle = (node[internalsSymbol]?.handleBounds?.target ?? []).find((handle) => handle.id === handleId); + } - if (handle) { + if (handle && handlePosition) { let offsetX = handle.width / 2; let offsetY = handle.height / 2; switch (handlePosition) { case Position.Left: - offsetX = 0; + offsetX = handle.width; break; case Position.Right: - offsetX = handle.width; + offsetX = 0; break; case Position.Top: - offsetY = 0; + offsetY = handle.height; break; case Position.Bottom: - offsetY = handle.height; + offsetY = 0; break; } diff --git a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/edge/EdgeLayout.types.ts b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/edge/EdgeLayout.types.ts index 8df0c35304..411933345f 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/edge/EdgeLayout.types.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/edge/EdgeLayout.types.ts @@ -11,36 +11,54 @@ * Obeo - initial API and implementation *******************************************************************************/ -import { Node, Position } from 'reactflow'; +import { Node, NodePositionChange, Position, XYPosition } from 'reactflow'; import { NodeData } from '../DiagramRenderer.types'; +export interface EdgeParameters { + sourcePosition: Position; + targetPosition: Position; +} -export type GetEdgeParameters = (source: Node, target: Node) => EdgeParameters; +export type GetEdgeParameters = ( + movingNode: NodePositionChange, + source: Node, + target: Node, + visiblesNodes: Node[] +) => EdgeParameters; export interface EdgeParameters { - sourceX: number; - sourceY: number; sourcePosition: Position; - targetX: number; - targetY: number; targetPosition: Position; } -export type GetParameters = (nodeA: Node, nodeB: Node) => Parameters; +export type GetParameters = ( + movingNode: NodePositionChange, + nodeA: Node, + nodeB: Node, + visiblesNodes: Node[] +) => Parameters; export interface Parameters { - x: number; - y: number; position: Position; } -export type GetNodeCenter = (node: Node) => NodeCenter; +export type GetNodeCenter = (node: Node, visiblesNodes: Node[]) => NodeCenter; export interface NodeCenter { x: number; y: number; } -export type GetHandleCoordinatesByPosition = (node: Node, handlePosition: Position) => HandleCoordinates; +export type GetHandleCoordinatesByPosition = ( + node: Node, + handlePosition: Position, + handleId: string +) => XYPosition; + +export type GetHandleCoordinatesByPosition2 = ( + node: Node, + handlePosition: Position, + edgeId: string +) => HandleCoordinates; export interface HandleCoordinates { x: number; diff --git a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/edge/MultiLabelEdge.tsx b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/edge/MultiLabelEdge.tsx index d97e19b74b..dd0ed7d967 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/edge/MultiLabelEdge.tsx +++ b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/edge/MultiLabelEdge.tsx @@ -27,7 +27,7 @@ import { import { EdgeData, NodeData } from '../DiagramRenderer.types'; import { Label } from '../Label'; import { DiagramElementPalette } from '../palette/DiagramElementPalette'; -import { getEdgeParameters } from './EdgeLayout'; +import { getHandleCoordinatesByPosition } from './EdgeLayout'; import { MultiLabelEdgeData } from './MultiLabelEdge.types'; const multiLabelEdgeStyle = ( @@ -59,10 +59,13 @@ export const MultiLabelEdge = memo( markerEnd, markerStart, selected, + sourcePosition, + targetPosition, sourceHandleId, targetHandleId, }: EdgeProps) => { const theme = useTheme(); + const reactFlowInstance = useReactFlow(); const sourceNode = useStore | undefined>( useCallback((store: ReactFlowState) => store.nodeInternals.get(source), [source]) @@ -75,10 +78,8 @@ export const MultiLabelEdge = memo( return null; } - const { sourceX, sourceY, sourcePosition, targetX, targetY, targetPosition } = getEdgeParameters( - sourceNode, - targetNode - ); + const { x: sourceX, y: sourceY } = getHandleCoordinatesByPosition(sourceNode, sourcePosition, sourceHandleId ?? ''); + const { x: targetX, y: targetY } = getHandleCoordinatesByPosition(targetNode, targetPosition, targetHandleId ?? ''); const [edgePath, labelX, labelY] = getSmoothStepPath({ sourceX, @@ -91,20 +92,20 @@ export const MultiLabelEdge = memo( const { beginLabel, endLabel, label, faded } = data || {}; - const reactFlowInstance = useReactFlow(); useEffect(() => { - if (sourceHandleId?.split('--')[2] !== sourcePosition || targetHandleId?.split('--')[2] !== targetPosition) { - reactFlowInstance.setEdges((edges: Edge[]) => - edges.map((edge) => { - if (edge.id === id) { - edge.sourceHandle = `handle--${edge.source}--${sourcePosition}`; - edge.targetHandle = `handle--${edge.target}--${targetPosition}`; + reactFlowInstance.setEdges((edges: Edge[]) => + edges.map((edge) => { + if (edge.id === id) { + if (selected) { + edge.updatable = true; + } else { + edge.updatable = false; } - return edge; - }) - ); - } - }, [sourcePosition, targetPosition]); + } + return edge; + }) + ); + }, [selected]); return ( <> diff --git a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/handles/ConnectionHandles.tsx b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/handles/ConnectionHandles.tsx new file mode 100644 index 0000000000..d8840f42d7 --- /dev/null +++ b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/handles/ConnectionHandles.tsx @@ -0,0 +1,108 @@ +/******************************************************************************* + * Copyright (c) 2023 Obeo and others. + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Obeo - initial API and implementation + *******************************************************************************/ +import { Theme, useTheme } from '@material-ui/core/styles'; +import React from 'react'; +import { Handle, Position, useReactFlow } from 'reactflow'; +import { EdgeData, NodeData } from '../DiagramRenderer.types'; +import { ConnectionHandlesProps } from './ConnectionHandles.types'; + +const borderHandlesStyle = (position: Position): React.CSSProperties => { + const style: React.CSSProperties = { + display: 'flex', + position: 'absolute', + justifyContent: 'space-evenly', + }; + switch (position) { + case Position.Left: + style.height = '100%'; + style.left = '0'; + style.top = '0'; + style.flexDirection = 'column'; + break; + case Position.Right: + style.height = '100%'; + style.right = '0'; + style.top = '0'; + style.flexDirection = 'column'; + break; + case Position.Top: + style.width = '100%'; + style.left = '0'; + style.top = '0'; + style.flexDirection = 'row'; + break; + case Position.Bottom: + style.width = '100%'; + style.left = '0'; + style.bottom = '0'; + style.flexDirection = 'row'; + break; + } + return style; +}; + +const handleStyle = (theme: Theme, position: Position, isEdgeSelected: boolean): React.CSSProperties => { + const style: React.CSSProperties = { + position: 'relative', + transform: 'none', + opacity: '0', + pointerEvents: 'none', + }; + switch (position) { + case Position.Left: + case Position.Right: + style.top = 'auto'; + break; + case Position.Top: + case Position.Bottom: + style.left = 'auto'; + break; + } + if (isEdgeSelected) { + style.opacity = 1; + style.outline = `${theme.palette.primary.main} solid 1px`; + } + return style; +}; + +export const ConnectionHandles = ({ connectionHandles }: ConnectionHandlesProps) => { + const theme = useTheme(); + const reactFlowInstance = useReactFlow(); + const isEdgeSelected = (edgeId: string) => { + return !!reactFlowInstance.getEdge(edgeId)?.selected; + }; + return ( + <> + {Object.values(Position).map((key) => { + return ( +
+ {connectionHandles + .filter((customHandle) => customHandle.position === key) + .map((customHandle) => { + return ( + + ); + })} +
+ ); + })} + + ); +}; diff --git a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/handles/ConnectionHandles.types.ts b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/handles/ConnectionHandles.types.ts new file mode 100644 index 0000000000..dba9a890ba --- /dev/null +++ b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/handles/ConnectionHandles.types.ts @@ -0,0 +1,23 @@ +/******************************************************************************* + * Copyright (c) 2023 Obeo. + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Obeo - initial API and implementation + *******************************************************************************/ + +import { HandleProps } from 'reactflow'; + +export interface ConnectionHandlesProps { + connectionHandles: ConnectionHandle[]; +} + +export interface ConnectionHandle extends HandleProps { + edgeId: string; + nodeId: string; +} diff --git a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/handles/useHandleChange.tsx b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/handles/useHandleChange.tsx new file mode 100644 index 0000000000..29ef2b5e22 --- /dev/null +++ b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/handles/useHandleChange.tsx @@ -0,0 +1,83 @@ +/******************************************************************************* + * Copyright (c) 2023 Obeo. + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Obeo - initial API and implementation + *******************************************************************************/ +import { useCallback } from 'react'; +import { Node, NodeChange, getConnectedEdges, useReactFlow } from 'reactflow'; +import { EdgeData, NodeData } from '../DiagramRenderer.types'; +import { getEdgeParameters } from '../edge/EdgeLayout'; +import { ConnectionHandle } from './ConnectionHandles.types'; +import { UseHandleChangeValue } from './useHandleChange.types'; +export const useHandleChange = (): UseHandleChangeValue => { + const { getEdges, getNodes, setNodes } = useReactFlow(); + + const onHandleChange = useCallback((changes: NodeChange[]): NodeChange[] => { + return changes.map((change) => { + if (change.type === 'position' && change.positionAbsolute) { + const movedNode = getNodes().find((node) => change.id === node.id); + + if (movedNode) { + const connectedEdges = getConnectedEdges([movedNode], getEdges()); + connectedEdges.forEach((edge) => { + const { sourceNode, targetNode, sourceHandle, targetHandle, id } = edge; + if (sourceNode && targetNode) { + const { sourcePosition, targetPosition } = getEdgeParameters(change, sourceNode, targetNode, getNodes()); + + const nodeSourceConnectionHandle: ConnectionHandle | undefined = sourceNode.data.connectionHandles.find( + (connectionHandle: ConnectionHandle) => connectionHandle.id === sourceHandle + ); + const nodeTargetConnectionHandle: ConnectionHandle | undefined = targetNode.data.connectionHandles.find( + (connectionHandle: ConnectionHandle) => connectionHandle.id === targetHandle + ); + if ( + nodeSourceConnectionHandle?.position !== sourcePosition && + nodeTargetConnectionHandle?.position !== targetPosition + ) { + const nodeSourceConnectionHandles: ConnectionHandle[] = sourceNode.data.connectionHandles.map( + (nodeConnectionHandle: ConnectionHandle) => { + if (nodeConnectionHandle.edgeId === id && nodeConnectionHandle.type === 'source') { + nodeConnectionHandle.position = sourcePosition; + } + return nodeConnectionHandle; + } + ); + + const nodeTargetConnectionHandles: ConnectionHandle[] = targetNode.data.connectionHandles.map( + (nodeConnectionHandle: ConnectionHandle) => { + if (nodeConnectionHandle.edgeId === id && nodeConnectionHandle.type === 'target') { + nodeConnectionHandle.position = targetPosition; + } + return nodeConnectionHandle; + } + ); + + setNodes((nodes: Node[]) => + nodes.map((node) => { + if (sourceNode.id === node.id) { + node.data = { ...node.data, connectionHandles: nodeSourceConnectionHandles }; + } + if (targetNode.id === node.id) { + node.data = { ...node.data, connectionHandles: nodeTargetConnectionHandles }; + } + return node; + }) + ); + } + } + }); + } + } + return change; + }); + }, []); + + return { onHandleChange }; +}; diff --git a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/handles/useHandleChange.types.ts b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/handles/useHandleChange.types.ts new file mode 100644 index 0000000000..1dc5d360ef --- /dev/null +++ b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/handles/useHandleChange.types.ts @@ -0,0 +1,17 @@ +/******************************************************************************* + * Copyright (c) 2023 Obeo. + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Obeo - initial API and implementation + *******************************************************************************/ +import { NodeChange } from 'reactflow'; + +export interface UseHandleChangeValue { + onHandleChange: (changes: NodeChange[]) => NodeChange[]; +} diff --git a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/handles/useRefreshConnectionHandles.tsx b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/handles/useRefreshConnectionHandles.tsx new file mode 100644 index 0000000000..c96f372f7d --- /dev/null +++ b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/handles/useRefreshConnectionHandles.tsx @@ -0,0 +1,41 @@ +/******************************************************************************* + * Copyright (c) 2023 Obeo. + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Obeo - initial API and implementation + *******************************************************************************/ + +import { useEffect, useRef } from 'react'; +import { useUpdateNodeInternals } from 'reactflow'; +import { ConnectionHandle } from './ConnectionHandles.types'; +import { UseRefreshConnectionHandlesValue } from './useRefreshConnectionHandles.types'; + +export const useRefreshConnectionHandles = ( + id: string, + connectionHandles: ConnectionHandle[] +): UseRefreshConnectionHandlesValue => { + const updateNodeInternals = useUpdateNodeInternals(); + const firstUpdate = useRef(true); + + const connectionHandlesIdentity = connectionHandles + .map((handle) => `${handle.edgeId}#${handle.position}#${handle.nodeId}`) + .join(', '); + + useEffect(() => { + if (firstUpdate.current && firstUpdate) { + firstUpdate.current = false; + } else { + updateNodeInternals(id); + } + }, [connectionHandlesIdentity]); + + return { + useRefreshConnectionHandles, + }; +}; diff --git a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/handles/useRefreshConnectionHandles.types.ts b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/handles/useRefreshConnectionHandles.types.ts new file mode 100644 index 0000000000..8b2f03e546 --- /dev/null +++ b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/handles/useRefreshConnectionHandles.types.ts @@ -0,0 +1,18 @@ +/******************************************************************************* + * Copyright (c) 2023 Obeo. + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Obeo - initial API and implementation + *******************************************************************************/ + +import { ConnectionHandle } from './ConnectionHandles.types'; + +export interface UseRefreshConnectionHandlesValue { + useRefreshConnectionHandles: (id: string, connectionHandles: ConnectionHandle[]) => void; +} diff --git a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/node/ImageNode.tsx b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/node/ImageNode.tsx index 0068dec476..c0d2bc5ab9 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/node/ImageNode.tsx +++ b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/node/ImageNode.tsx @@ -14,13 +14,15 @@ import { ServerContext, ServerContextValue } from '@eclipse-sirius/sirius-components-core'; import { Theme, useTheme } from '@material-ui/core/styles'; import { memo, useContext } from 'react'; -import { Handle, NodeProps, NodeResizer, Position } from 'reactflow'; +import { NodeProps, NodeResizer } from 'reactflow'; import { BorderNodePositon } from '../DiagramRenderer.types'; import { Label } from '../Label'; import { useConnector } from '../connector/useConnector'; import { useDropNode } from '../dropNode/useDropNode'; import { ConnectionCreationHandles } from '../handles/ConnectionCreationHandles'; +import { ConnectionHandles } from '../handles/ConnectionHandles'; import { ConnectionTargetHandle } from '../handles/ConnectionTargetHandle'; +import { useRefreshConnectionHandles } from '../handles/useRefreshConnectionHandles'; import { DiagramElementPalette } from '../palette/DiagramElementPalette'; import { ImageNodeData } from './ImageNode.types'; @@ -61,12 +63,14 @@ const computeBorderRotation = (data: ImageNodeData): string | undefined => { return undefined; }; -export const ImageNode = memo(({ data, isConnectable, id, selected }: NodeProps) => { +export const ImageNode = memo(({ data, id, selected }: NodeProps) => { const theme = useTheme(); const { dropFeedbackStyleProvider } = useDropNode(); const { httpOrigin } = useContext(ServerContext); - const { onConnectionStartElementClick, newConnectionStyleProvider } = useConnector(); + const { newConnectionStyleProvider } = useConnector(); const rotation = computeBorderRotation(data); + + useRefreshConnectionHandles(id, data.connectionHandles); return ( <> : null} {selected ? : null} - - - - + ); }); diff --git a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/node/ListNode.tsx b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/node/ListNode.tsx index d025ad96e3..7261485416 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/node/ListNode.tsx +++ b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/node/ListNode.tsx @@ -14,13 +14,15 @@ import { getCSSColor } from '@eclipse-sirius/sirius-components-core'; import { Theme, useTheme } from '@material-ui/core/styles'; import { memo } from 'react'; -import { Handle, NodeProps, NodeResizer, Position } from 'reactflow'; +import { NodeProps, NodeResizer } from 'reactflow'; import { Label } from '../Label'; import { useConnector } from '../connector/useConnector'; import { useDrop } from '../drop/useDrop'; import { useDropNode } from '../dropNode/useDropNode'; import { ConnectionCreationHandles } from '../handles/ConnectionCreationHandles'; +import { ConnectionHandles } from '../handles/ConnectionHandles'; import { ConnectionTargetHandle } from '../handles/ConnectionTargetHandle'; +import { useRefreshConnectionHandles } from '../handles/useRefreshConnectionHandles'; import { DiagramElementPalette } from '../palette/DiagramElementPalette'; import { ListNodeData } from './ListNode.types'; @@ -45,16 +47,17 @@ const listNodeStyle = ( return listNodeStyle; }; -export const ListNode = memo(({ data, isConnectable, id, selected }: NodeProps) => { +export const ListNode = memo(({ data, id, selected }: NodeProps) => { const theme = useTheme(); const { onDrop, onDragOver } = useDrop(); - const { onConnectionStartElementClick, newConnectionStyleProvider } = useConnector(); + const { newConnectionStyleProvider } = useConnector(); const { dropFeedbackStyleProvider } = useDropNode(); const handleOnDrop = (event: React.DragEvent) => { onDrop(event, id); }; + useRefreshConnectionHandles(id, data.connectionHandles); return ( <> !data.isBorderNode} /> @@ -71,38 +74,7 @@ export const ListNode = memo(({ data, isConnectable, id, selected }: NodeProps : null} {selected ? : null} - - - - + ); diff --git a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/node/RectangularNode.tsx b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/node/RectangularNode.tsx index 7058500399..c854c685e8 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/node/RectangularNode.tsx +++ b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/node/RectangularNode.tsx @@ -14,13 +14,15 @@ import { getCSSColor } from '@eclipse-sirius/sirius-components-core'; import { Theme, useTheme } from '@material-ui/core/styles'; import React, { memo } from 'react'; -import { Handle, NodeProps, NodeResizer, Position } from 'reactflow'; +import { NodeProps, NodeResizer } from 'reactflow'; import { Label } from '../Label'; import { useConnector } from '../connector/useConnector'; import { useDrop } from '../drop/useDrop'; import { useDropNode } from '../dropNode/useDropNode'; import { ConnectionCreationHandles } from '../handles/ConnectionCreationHandles'; +import { ConnectionHandles } from '../handles/ConnectionHandles'; import { ConnectionTargetHandle } from '../handles/ConnectionTargetHandle'; +import { useRefreshConnectionHandles } from '../handles/useRefreshConnectionHandles'; import { DiagramElementPalette } from '../palette/DiagramElementPalette'; import { RectangularNodeData } from './RectangularNode.types'; @@ -43,16 +45,17 @@ const rectangularNodeStyle = ( return rectangularNodeStyle; }; -export const RectangularNode = memo(({ data, isConnectable, id, selected }: NodeProps) => { +export const RectangularNode = memo(({ data, id, selected }: NodeProps) => { const theme = useTheme(); const { onDrop, onDragOver } = useDrop(); - const { onConnectionStartElementClick, newConnectionStyleProvider } = useConnector(); + const { newConnectionStyleProvider } = useConnector(); const { dropFeedbackStyleProvider } = useDropNode(); const handleOnDrop = (event: React.DragEvent) => { onDrop(event, id); }; + useRefreshConnectionHandles(id, data.connectionHandles); return ( <> !data.isBorderNode} /> @@ -69,38 +72,7 @@ export const RectangularNode = memo(({ data, isConnectable, id, selected }: Node {selected ? : null} {selected ? : null} - - - - + );