diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index e6e3ab7475..21de6b6895 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -179,7 +179,9 @@ The new implementation of `IEditService`, named `ComposedEditService`, tries fir - https://github.com/eclipse-sirius/sirius-web/issues/2608[#2608] [diagram] Improve the placement of handles on the left and right side of nodes so that they are properly centered - https://github.com/eclipse-sirius/sirius-web/issues/2262[#2262] [diagram] Share diagram layout data between the subscribers of the representation - https://github.com/eclipse-sirius/sirius-web/issues/2621[#2621] [diagram] Set the same ConnectionLine path as the one used to render edges - +- https://github.com/eclipse-sirius/sirius-web/issues/2595[#2595] [diagram] Call the layout on node move and resize. ++ +Node will not be outside of their container, nor on a node header because of a move or resize. == v2023.10.0 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 35f2890b42..450887da97 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 @@ -25,6 +25,7 @@ import { OnEdgesChange, OnNodesChange, ReactFlow, + applyNodeChanges, useEdgesState, useNodesState, } from 'reactflow'; @@ -44,6 +45,7 @@ import { edgeTypes } from './edge/EdgeTypes'; import { MultiLabelEdgeData } from './edge/MultiLabelEdge.types'; import { useInitialFitToScreen } from './fit-to-screen/useInitialFitToScreen'; import { useHandleChange } from './handles/useHandleChange'; +import { useLayoutOnBoundsChange } from './layout-events/useLayoutOnBoundsChange'; import { RawDiagram } from './layout/layout.types'; import { useLayout } from './layout/useLayout'; import { useSynchronizeLayoutData } from './layout/useSynchronizeLayoutData'; @@ -77,8 +79,6 @@ export const DiagramRenderer = ({ diagramRefreshedEventPayload, selection, setSe const { onConnect, onConnectStart, onConnectEnd } = useConnector(); const { reconnectEdge } = useReconnectEdge(); const { onDrop, onDragOver } = useDrop(); - const { onBorderChange } = useBorderChange(); - const { onHandleChange } = useHandleChange(); const { getNodeTypes } = useNodeType(); const [nodes, setNodes, onNodesChange] = useNodesState([]); @@ -121,9 +121,19 @@ export const DiagramRenderer = ({ diagramRefreshedEventPayload, selection, setSe }, [diagramRefreshedEventPayload, diagramDescription]); const { updateSelectionOnNodesChange, updateSelectionOnEdgesChange } = useDiagramSelection(selection, setSelection); + const { transformBorderNodeChanges } = useBorderChange(); + const { applyHandleChange } = useHandleChange(); + const { layoutOnBoundsChange } = useLayoutOnBoundsChange(diagramRefreshedEventPayload.id); const handleNodesChange: OnNodesChange = (changes: NodeChange[]) => { - onNodesChange(onBorderChange(onHandleChange(changes))); + const transformedNodeChanges = transformBorderNodeChanges(changes); + + let newNodes = applyNodeChanges(transformedNodeChanges, nodes); + + newNodes = applyHandleChange(transformedNodeChanges, newNodes as Node[]); + setNodes(newNodes); + layoutOnBoundsChange(transformedNodeChanges, newNodes as Node[]); + updateSelectionOnNodesChange(changes); }; diff --git a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/border/useBorderChange.tsx b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/border/useBorderChange.tsx index 9a8608a6c6..8c4d1bd02d 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/border/useBorderChange.tsx +++ b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/border/useBorderChange.tsx @@ -65,7 +65,7 @@ const computeNewBorderPosition = ( export const useBorderChange = (): UseBorderChangeValue => { const { getNodes } = useReactFlow(); - const onBorderChange = useCallback((changes: NodeChange[]): NodeChange[] => { + const transformBorderNodeChanges = useCallback((changes: NodeChange[]): NodeChange[] => { return changes.map((change) => { if (change.type === 'position' && change.positionAbsolute) { const movedNode = getNodes().find((node) => change.id === node.id); @@ -88,5 +88,5 @@ export const useBorderChange = (): UseBorderChangeValue => { }); }, []); - return { onBorderChange }; + return { transformBorderNodeChanges }; }; diff --git a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/border/useBorderChange.types.ts b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/border/useBorderChange.types.ts index 2c7c684986..3cf2e3d5cc 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/border/useBorderChange.types.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/border/useBorderChange.types.ts @@ -13,5 +13,5 @@ import { NodeChange } from 'reactflow'; export interface UseBorderChangeValue { - onBorderChange: (changes: NodeChange[]) => NodeChange[]; + transformBorderNodeChanges: (changes: NodeChange[]) => NodeChange[]; } diff --git a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/dropNode/useDropNode.ts b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/dropNode/useDropNode.ts index d66ebd506b..c111e4631a 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/dropNode/useDropNode.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/dropNode/useDropNode.ts @@ -212,6 +212,10 @@ export const useDropNode = (): UseDropNodeValue => { [element?.top, element?.left, viewport, draggedNode, targetNodeId, droppableOnDiagram, compatibleNodeIds] ); + const hasDroppedNodeParentChanged = (): boolean => { + return draggedNode?.parentNode !== targetNodeId; + }; + const theme = useTheme(); const diagramTargeted = targetNodeId === null && initialParentId !== null; const diagramForbidden = diagramTargeted && draggedNode?.id !== null && !droppableOnDiagram; @@ -226,6 +230,7 @@ export const useDropNode = (): UseDropNodeValue => { onNodeDragStart, onNodeDrag, onNodeDragStop, + hasDroppedNodeParentChanged, compatibleNodeIds, draggedNode, targetNodeId, diff --git a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/dropNode/useDropNode.types.ts b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/dropNode/useDropNode.types.ts index 2f9c127fa5..e10b6c7e15 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/dropNode/useDropNode.types.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/dropNode/useDropNode.types.ts @@ -19,6 +19,7 @@ export interface UseDropNodeValue { onNodeDragStart: NodeDragHandler; onNodeDrag: NodeDragHandler; onNodeDragStop: (onDragCancelled: (node: Node) => void) => NodeDragHandler; + hasDroppedNodeParentChanged: () => boolean; draggedNode: Node | null; targetNodeId: string | null; compatibleNodeIds: string[]; 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 1ca0918dd0..5d7484ff7e 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 @@ -84,8 +84,8 @@ const getParameters: GetParameters = (movingNode, nodeA, nodeB, visiblesNodes) = let centerA: NodeCenter; if (movingNode && movingNode.id === nodeA.id) { centerA = { - x: movingNode.positionAbsolute?.x ?? 0 + (nodeA.width ?? 0) / 2, - y: movingNode.positionAbsolute?.y ?? 0 + (nodeA.height ?? 0) / 2, + x: (movingNode.positionAbsolute?.x ?? 0) + (nodeA.width ?? 0) / 2, + y: (movingNode.positionAbsolute?.y ?? 0) + (nodeA.height ?? 0) / 2, }; } else { centerA = getNodeCenter(nodeA, visiblesNodes); @@ -94,17 +94,17 @@ const getParameters: GetParameters = (movingNode, nodeA, nodeB, visiblesNodes) = let centerB: NodeCenter; if (movingNode && movingNode.id === nodeB.id) { centerB = { - x: movingNode.positionAbsolute?.x ?? 0 + (nodeB.width ?? 0) / 2, - y: movingNode.positionAbsolute?.y ?? 0 + (nodeB.height ?? 0) / 2, + 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 horizontalDifference = Math.abs(centerA.x - centerB.x); const verticalDifference = Math.abs(centerA.y - centerB.y); let position: Position; - if (horizontallDifference > verticalDifference) { + if (horizontalDifference > verticalDifference) { position = centerA.x > centerB.x ? Position.Left : Position.Right; } else { position = centerA.y > centerB.y ? Position.Top : Position.Bottom; @@ -118,14 +118,14 @@ const getParameters: GetParameters = (movingNode, nodeA, nodeB, visiblesNodes) = 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, + 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, + x: (node.position?.x ?? 0) + (node.width ?? 0) / 2, + y: (node.position?.y ?? 0) + (node.height ?? 0) / 2, }; while (parentNode) { position = { 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 index a6b5ffc410..a5840b3f64 100644 --- 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 @@ -10,72 +10,74 @@ * Contributors: * Obeo - initial API and implementation *******************************************************************************/ -import { Node, NodeChange, getConnectedEdges, useReactFlow } from 'reactflow'; +import { Node, NodeChange, NodePositionChange, getConnectedEdges, useReactFlow } from 'reactflow'; import { EdgeData, 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'; + +const isNodePositionChange = (change: NodeChange): change is NodePositionChange => + change.type === 'position' && typeof change.dragging === 'boolean' && change.dragging; export const useHandleChange = (): UseHandleChangeValue => { - const { getEdges, getNodes, setNodes } = useReactFlow(); + const { getEdges } = useReactFlow(); + + const applyHandleChange = ( + changes: NodeChange[], + nodes: Node[] + ): Node[] => { + return nodes.map((node) => { + const nodeDraggingChange: NodePositionChange | undefined = changes + .filter(isNodePositionChange) + .find((change) => change.id === node.id); - const onHandleChange = (changes: NodeChange[]): NodeChange[] => { - return changes.map((change) => { - if (change.type === 'position' && change.positionAbsolute) { - const movedNode = getNodes().find((node) => change.id === node.id); + if (nodeDraggingChange) { + const connectedEdges = getConnectedEdges([node], getEdges()); + connectedEdges.forEach((edge) => { + const { sourceHandle, targetHandle } = edge; + const sourceNode = nodes.find((node) => node.id === edge.sourceNode?.id); + const targetNode = nodes.find((node) => node.id === edge.targetNode?.id); - if (movedNode) { - const connectedEdges = getConnectedEdges([movedNode], getEdges()); - connectedEdges.forEach((edge) => { - const { sourceHandle, targetHandle } = edge; - const sourceNode = getNodes().find((node) => node.id === edge.sourceNode?.id); - const targetNode = getNodes().find((node) => node.id === edge.targetNode?.id); + if (sourceNode && targetNode && sourceHandle && targetHandle) { + const { sourcePosition, targetPosition } = getEdgeParametersWhileMoving( + nodeDraggingChange, + sourceNode, + targetNode, + nodes + ); + 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 (sourceNode && targetNode && sourceHandle && targetHandle) { - const { sourcePosition, targetPosition } = getEdgeParametersWhileMoving( - change, + if ( + nodeSourceConnectionHandle?.position !== sourcePosition && + nodeTargetConnectionHandle?.position !== targetPosition + ) { + const { sourceConnectionHandles, targetConnectionHandles } = getUpdatedConnectionHandles( sourceNode, targetNode, - getNodes() - ); - const nodeSourceConnectionHandle: ConnectionHandle | undefined = sourceNode.data.connectionHandles.find( - (connectionHandle: ConnectionHandle) => connectionHandle.id === sourceHandle + sourcePosition, + targetPosition, + sourceHandle, + targetHandle ); - const nodeTargetConnectionHandle: ConnectionHandle | undefined = targetNode.data.connectionHandles.find( - (connectionHandle: ConnectionHandle) => connectionHandle.id === targetHandle - ); - - if ( - nodeSourceConnectionHandle?.position !== sourcePosition && - nodeTargetConnectionHandle?.position !== targetPosition - ) { - const { sourceConnectionHandles, targetConnectionHandles } = getUpdatedConnectionHandles( - sourceNode, - targetNode, - sourcePosition, - targetPosition, - sourceHandle, - targetHandle - ); - setNodes((nodes: Node[]) => - nodes.map((node) => { - if (sourceNode.id === node.id) { - node.data = { ...node.data, connectionHandles: sourceConnectionHandles }; - } - if (targetNode.id === node.id) { - node.data = { ...node.data, connectionHandles: targetConnectionHandles }; - } - return node; - }) - ); + if (node.id === sourceNode.id) { + node.data = { ...node.data, connectionHandles: sourceConnectionHandles }; + } + if (node.id === targetNode.id) { + node.data = { ...node.data, connectionHandles: targetConnectionHandles }; } } - }); - } + } + }); } - return change; + return node; }); }; - return { onHandleChange }; + return { applyHandleChange }; }; 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 index 1dc5d360ef..6e2100ccbf 100644 --- 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 @@ -10,8 +10,13 @@ * Contributors: * Obeo - initial API and implementation *******************************************************************************/ -import { NodeChange } from 'reactflow'; +import { Node, NodeChange } from 'reactflow'; +import { NodeData } from '../DiagramRenderer.types'; +import { DiagramNodeType } from '../node/NodeTypes.types'; export interface UseHandleChangeValue { - onHandleChange: (changes: NodeChange[]) => NodeChange[]; + applyHandleChange: ( + changes: NodeChange[], + nodes: Node[] + ) => Node[]; } diff --git a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/layout-events/useLayoutOnBoundsChange.ts b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/layout-events/useLayoutOnBoundsChange.ts new file mode 100644 index 0000000000..3941d23d9a --- /dev/null +++ b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/layout-events/useLayoutOnBoundsChange.ts @@ -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 { Node, NodeChange, useReactFlow } from 'reactflow'; +import { EdgeData, NodeData } from '../DiagramRenderer.types'; +import { useDropNode } from '../dropNode/useDropNode'; +import { RawDiagram } from '../layout/layout.types'; +import { useLayout } from '../layout/useLayout'; +import { useSynchronizeLayoutData } from '../layout/useSynchronizeLayoutData'; +import { DiagramNodeType } from '../node/NodeTypes.types'; +import { UseLayoutOnBoundsChangeValue } from './useLayoutOnBoundsChange.types'; + +export const useLayoutOnBoundsChange = (refreshEventPayloadId: string): UseLayoutOnBoundsChangeValue => { + const { getEdges, setNodes, setEdges } = useReactFlow(); + const { layout } = useLayout(); + const { hasDroppedNodeParentChanged } = useDropNode(); + const { synchronizeLayoutData } = useSynchronizeLayoutData(); + + const isMoveFinished = (change: NodeChange): boolean => { + return ( + change.type === 'position' && + typeof change.dragging === 'boolean' && + !change.dragging && + !hasDroppedNodeParentChanged() + ); + }; + const isResizeFinished = (change: NodeChange): boolean => + change.type === 'dimensions' && typeof change.resizing === 'boolean' && !change.resizing; + + const isBoundsChangeFinished = (changes: NodeChange[]): NodeChange | undefined => { + return changes.find((change) => isMoveFinished(change) || isResizeFinished(change)); + }; + + const layoutOnBoundsChange = (changes: NodeChange[], nodes: Node[]): void => { + const change = isBoundsChangeFinished(changes); + if (change) { + const diagramToLayout: RawDiagram = { + nodes, + edges: getEdges(), + }; + + layout(diagramToLayout, diagramToLayout, (laidOutDiagram) => { + nodes.map((node) => { + const existingNode = laidOutDiagram.nodes.find((laidoutNode) => laidoutNode.id === node.id); + if (existingNode) { + return { + ...node, + position: existingNode.position, + width: existingNode.width, + height: existingNode.height, + style: { + ...node.style, + width: `${existingNode.width}px`, + height: `${existingNode.height}px`, + }, + }; + } + return node; + }); + setNodes(nodes); + setEdges(laidOutDiagram.edges); + const finalDiagram: RawDiagram = { + nodes: nodes, + edges: laidOutDiagram.edges, + }; + + synchronizeLayoutData(refreshEventPayloadId, finalDiagram); + }); + } + }; + + return { layoutOnBoundsChange }; +}; diff --git a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/layout-events/useLayoutOnBoundsChange.types.ts b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/layout-events/useLayoutOnBoundsChange.types.ts new file mode 100644 index 0000000000..9542180815 --- /dev/null +++ b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/layout-events/useLayoutOnBoundsChange.types.ts @@ -0,0 +1,19 @@ +/******************************************************************************* + * 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 { Node, NodeChange } from 'reactflow'; +import { NodeData } from '../DiagramRenderer.types'; +import { DiagramNodeType } from '../node/NodeTypes.types'; + +export interface UseLayoutOnBoundsChangeValue { + layoutOnBoundsChange: (changes: NodeChange[], nodes: Node[]) => void; +} diff --git a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/layout/ListNodeLayoutHandler.ts b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/layout/ListNodeLayoutHandler.ts index c681281b54..addc19cd2f 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/layout/ListNodeLayoutHandler.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/layout/ListNodeLayoutHandler.ts @@ -101,14 +101,23 @@ export class ListNodeLayoutHandler implements INodeLayoutHandler { const northBorderNodeFootprintWidth = getNorthBorderNodeFootprintWidth(visibleNodes, borderNodes, previousDiagram); const southBorderNodeFootprintWidth = getSouthBorderNodeFootprintWidth(visibleNodes, borderNodes, previousDiagram); + const previousNode: Node | undefined = (previousDiagram?.nodes ?? []).find( + (previouseNode) => previouseNode.id === node.id + ); + if (!forceWidth) { + let previousChildrenContentBoxWidthToConsider: number = 0; + if (node.data.nodeDescription?.userResizable) { + previousChildrenContentBoxWidthToConsider = (previousNode?.width ?? 0) - borderWidth * 2; + } const widerWidth = Math.max( directNodesChildren.reduce( (widerWidth, child) => Math.max(child.width ?? 0, widerWidth), labelElement?.getBoundingClientRect().width ?? 0 ), northBorderNodeFootprintWidth, - southBorderNodeFootprintWidth + southBorderNodeFootprintWidth, + previousChildrenContentBoxWidthToConsider ); layoutEngine.layoutNodes(previousDiagram, visibleNodes, directNodesChildren, newlyAddedNode, widerWidth); @@ -157,7 +166,18 @@ export class ListNodeLayoutHandler implements INodeLayoutHandler { ); node.width = forceWidth ?? getNodeOrMinWidth(nodeWidth, node); - node.height = getNodeOrMinHeight(nodeHeight, node); + + const minNodeheight = getNodeOrMinHeight(nodeHeight, node); + // TODO: rework this. + if (node.data.nodeDescription?.userResizable && previousNode) { + if (minNodeheight > (previousNode.height ?? 0)) { + node.height = minNodeheight; + } else { + node.height = previousNode.height; + } + } else { + node.height = minNodeheight; + } if (node.data.nodeDescription?.keepAspectRatio) { applyRatioOnNewNodeSizeValue(node); } @@ -168,3 +188,5 @@ export class ListNodeLayoutHandler implements INodeLayoutHandler { setBorderNodesPosition(borderNodes, node, previousDiagram); } } + +// TODO: Tester avec un compartiment free form entre attribut et operation. diff --git a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/layout/RectangleNodeLayoutHandler.ts b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/layout/RectangleNodeLayoutHandler.ts index 85b8eead72..9c31d7e725 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/layout/RectangleNodeLayoutHandler.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/layout/RectangleNodeLayoutHandler.ts @@ -103,7 +103,17 @@ export class RectangleNodeLayoutHandler implements INodeLayoutHandler previouseNode.id === node.id); + if (previousNode && node.data.nodeDescription?.userResizable) { + if (minNodeWith > (previousNode.width ?? 0)) { + node.width = minNodeWith; + } else { + node.width = previousNode.width; + } + if (minNodeheight > (previousNode.height ?? 0)) { + node.height = minNodeheight; + } else { + node.height = previousNode.height; + } + } else { + node.width = minNodeWith; + node.height = minNodeheight; + } + if (node.data.nodeDescription?.keepAspectRatio) { applyRatioOnNewNodeSizeValue(node); } @@ -168,7 +197,7 @@ export class RectangleNodeLayoutHandler implements INodeLayoutHandler, visibleNodes: Node[], borderWidth: number, @@ -184,8 +213,26 @@ export class RectangleNodeLayoutHandler implements INodeLayoutHandler previouseNode.id === node.id); + if (previousNode && node.data.nodeDescription?.userResizable) { + if (minNodeWith > (previousNode.width ?? 0)) { + node.width = minNodeWith; + } else { + node.width = previousNode.width; + } + if (minNodeheight > (previousNode.height ?? 0)) { + node.height = minNodeheight; + } else { + node.height = previousNode.height; + } + } else { + node.width = minNodeWith; + node.height = minNodeheight; + } if (node.data.nodeDescription?.keepAspectRatio) { applyRatioOnNewNodeSizeValue(node); diff --git a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/layout/layoutHandles.ts b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/layout/layoutHandles.ts index 5569c7dfb2..43f795c4fb 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/layout/layoutHandles.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/layout/layoutHandles.ts @@ -16,7 +16,9 @@ import { RawDiagram } from './layout.types'; export const layoutHandles = (diagram: RawDiagram) => { diagram.edges.forEach((edge) => { - const { sourceNode, targetNode, sourceHandle, targetHandle } = edge; + const { sourceNode: sourceEdgeNode, targetNode: targetEdgeNode, sourceHandle, targetHandle } = edge; + const sourceNode = diagram.nodes.find((node) => node.id === sourceEdgeNode?.id); + const targetNode = diagram.nodes.find((node) => node.id === targetEdgeNode?.id); if (sourceNode && targetNode && sourceHandle && targetHandle) { const { sourcePosition, targetPosition } = getEdgeParameters(sourceNode, targetNode, diagram.nodes); diff --git a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/layout/layoutNode.ts b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/layout/layoutNode.ts index 13e57931a8..5b76b3285c 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/layout/layoutNode.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams-reactflow/src/renderer/layout/layoutNode.ts @@ -131,6 +131,8 @@ export const getHeaderFootprint = ( if (displayHeaderSeparator) { headerFootprint += rectangularNodePadding; } + } else { + headerFootprint = rectangularNodePadding; } return headerFootprint; diff --git a/packages/sirius-web/frontend/sirius-web-application/src/nodes/EllipseNodeLayoutHandler.ts b/packages/sirius-web/frontend/sirius-web-application/src/nodes/EllipseNodeLayoutHandler.ts index 89c22eb112..caa159ab92 100644 --- a/packages/sirius-web/frontend/sirius-web-application/src/nodes/EllipseNodeLayoutHandler.ts +++ b/packages/sirius-web/frontend/sirius-web-application/src/nodes/EllipseNodeLayoutHandler.ts @@ -19,6 +19,7 @@ import { getBorderNodeExtent, getChildNodePosition, getEastBorderNodeFootprintHeight, + getHeaderFootprint, getNodeOrMinHeight, getNodeOrMinWidth, getNorthBorderNodeFootprintWidth, @@ -62,9 +63,22 @@ export class EllipseNodeLayoutHandler implements INodeLayoutHandler { const createdNode = newlyAddedNode?.id === child.id ? newlyAddedNode : undefined; if (!!createdNode) { + // WARN: this prevent the created to overlep the TOP header. It is a quick fix but a proper solution should be implemented. + const headerHeightFootprint = labelElement ? getHeaderFootprint(labelElement, false, false) : 0; child.position = createdNode.position; + if (child.position.y < borderWidth + headerHeightFootprint) { + child.position = { ...child.position, y: borderWidth + headerHeightFootprint }; + } } else if (previousNode) { + // WARN: this prevent the moved node to overlep the TOP header or appear outside of its container. It is a quick fix but a proper solution should be implemented. + const headerHeightFootprint = labelElement ? getHeaderFootprint(labelElement, false, false) : 0; child.position = previousNode.position; + if (child.position.y < borderWidth + headerHeightFootprint) { + child.position = { ...child.position, y: borderWidth + headerHeightFootprint }; + } + if (child.position.x < borderWidth) { + child.position = { ...child.position, x: borderWidth }; + } } else { child.position = child.position = getChildNodePosition( visibleNodes, @@ -114,8 +128,27 @@ export class EllipseNodeLayoutHandler implements INodeLayoutHandler { const nodeHeight = Math.max(directChildrenAwareNodeHeight, eastBorderNodeFootprintHeight, westBorderNodeFootprintHeight) + borderWidth * 2; - node.width = forceWidth ?? getNodeOrMinWidth(nodeWidth, node); - node.height = getNodeOrMinHeight(nodeHeight, node); + + const minNodeWith = forceWidth ?? getNodeOrMinWidth(nodeWidth, node); // WARN: not sure yet for the forceWidth to be here. + const minNodeheight = getNodeOrMinHeight(nodeHeight, node); + + const previousNode = (previousDiagram?.nodes ?? []).find((previouseNode) => previouseNode.id === node.id); + if (previousNode && node.data.nodeDescription?.userResizable) { + if (minNodeWith > (previousNode.width ?? 0)) { + node.width = minNodeWith; + } else { + node.width = previousNode.width; + } + if (minNodeheight > (previousNode.height ?? 0)) { + node.height = minNodeheight; + } else { + node.height = previousNode.height; + } + } else { + node.width = minNodeWith; + node.height = minNodeheight; + } + if (node.data.nodeDescription?.keepAspectRatio) { applyRatioOnNewNodeSizeValue(node); }