diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/contexts/DiagramContext.ts b/packages/diagrams/frontend/sirius-components-diagrams/src/contexts/DiagramContext.ts index 58cb393ef7..82a68b7522 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/contexts/DiagramContext.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/contexts/DiagramContext.ts @@ -16,8 +16,6 @@ import { DiagramContextValue } from './DiagramContext.types'; const value: DiagramContextValue = { editingContextId: '', diagramId: '', - refreshEventPayloadId: '', - payload: null, readOnly: false, }; diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/contexts/DiagramContext.types.ts b/packages/diagrams/frontend/sirius-components-diagrams/src/contexts/DiagramContext.types.ts index 2fa29085e0..ecae447210 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/contexts/DiagramContext.types.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/contexts/DiagramContext.types.ts @@ -11,12 +11,8 @@ * Obeo - initial API and implementation *******************************************************************************/ -import { GQLDiagramEventPayload } from '../graphql/subscription/diagramEventSubscription.types'; - export interface DiagramContextValue { editingContextId: string; diagramId: string; - refreshEventPayloadId: string; - payload: GQLDiagramEventPayload | null; readOnly: boolean; } diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/contexts/DiagramPayloadContext.ts b/packages/diagrams/frontend/sirius-components-diagrams/src/contexts/DiagramPayloadContext.ts new file mode 100644 index 0000000000..a5f14fd8ca --- /dev/null +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/contexts/DiagramPayloadContext.ts @@ -0,0 +1,21 @@ +/******************************************************************************* + * Copyright (c) 2025 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 React from 'react'; +import { DiagramPayloadContextValue } from './DiagramPayloadContext.types'; + +const value: DiagramPayloadContextValue = { + refreshEventPayloadId: '', + payload: null, +}; + +export const DiagramPayloadContext = React.createContext(value); diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/contexts/DiagramPayloadContext.types.ts b/packages/diagrams/frontend/sirius-components-diagrams/src/contexts/DiagramPayloadContext.types.ts new file mode 100644 index 0000000000..96ff2568a6 --- /dev/null +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/contexts/DiagramPayloadContext.types.ts @@ -0,0 +1,19 @@ +/******************************************************************************* + * Copyright (c) 2025 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 { GQLDiagramEventPayload } from '../graphql/subscription/diagramEventSubscription.types'; + +export interface DiagramPayloadContextValue { + refreshEventPayloadId: string; + payload: GQLDiagramEventPayload | null; +} diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/converter/ConvertEngine.types.ts b/packages/diagrams/frontend/sirius-components-diagrams/src/converter/ConvertEngine.types.ts index f8002261a4..8b3014e8d6 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/converter/ConvertEngine.types.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/converter/ConvertEngine.types.ts @@ -10,15 +10,17 @@ * Contributors: * Obeo - initial API and implementation *******************************************************************************/ -import { Node } from '@xyflow/react'; +import { InternalNode, Node } from '@xyflow/react'; +import { NodeLookup } from '@xyflow/system'; import { GQLNodeDescription } from '../graphql/query/nodeDescriptionFragment.types'; import { GQLDiagram } from '../graphql/subscription/diagramFragment.types'; import { GQLEdge } from '../graphql/subscription/edgeFragment.types'; import { GQLNode, GQLNodeStyle } from '../graphql/subscription/nodeFragment.types'; +import { NodeData } from '../renderer/DiagramRenderer.types'; import { GQLDiagramDescription } from '../representation/DiagramRepresentation.types'; - export interface IConvertEngine { convertNodes( + nodeLookUp: NodeLookup>>, gqlDiagram: GQLDiagram, gqlNodesToConvert: GQLNode[], parentNode: GQLNode | null, @@ -33,6 +35,7 @@ export interface INodeConverter { handle( convertEngine: IConvertEngine, + nodeLookUp: NodeLookup>>, gqlDiagram: GQLDiagram, gqlNode: GQLNode, gqlEdges: GQLEdge[], diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/converter/IconLabelNodeConverter.ts b/packages/diagrams/frontend/sirius-components-diagrams/src/converter/IconLabelNodeConverter.ts index a0a2c7f15f..ec576dd633 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/converter/IconLabelNodeConverter.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/converter/IconLabelNodeConverter.ts @@ -10,7 +10,8 @@ * Contributors: * Obeo - initial API and implementation *******************************************************************************/ -import { Node, XYPosition } from '@xyflow/react'; +import { InternalNode, Node, XYPosition } from '@xyflow/react'; +import { NodeLookup } from '@xyflow/system'; import { GQLNodeDescription } from '../graphql/query/nodeDescriptionFragment.types'; import { GQLDiagram, GQLNodeLayoutData } from '../graphql/subscription/diagramFragment.types'; import { GQLEdge } from '../graphql/subscription/edgeFragment.types'; @@ -20,7 +21,7 @@ import { GQLNodeStyle, GQLViewModifier, } from '../graphql/subscription/nodeFragment.types'; -import { BorderNodePosition } from '../renderer/DiagramRenderer.types'; +import { BorderNodePosition, NodeData } from '../renderer/DiagramRenderer.types'; import { ConnectionHandle } from '../renderer/handles/ConnectionHandles.types'; import { defaultHeight, defaultWidth } from '../renderer/layout/layoutParams'; import { IconLabelNodeData } from '../renderer/node/IconsLabelNode.types'; @@ -32,6 +33,7 @@ import { convertInsideLabel, convertOutsideLabels } from './convertLabel'; const defaultPosition: XYPosition = { x: 0, y: 0 }; const toIconLabelNode = ( + nodeLookUp: NodeLookup>>, gqlDiagram: GQLDiagram, gqlNode: GQLNode, gqlParentNode: GQLNode | null, @@ -95,6 +97,7 @@ const toIconLabelNode = ( data, position: defaultPosition, hidden: state === GQLViewModifier.Hidden, + selected: !!nodeLookUp.get(id)?.selected, }; if (gqlParentNode) { @@ -130,6 +133,7 @@ export class IconLabelNodeConverter implements INodeConverter { handle( _convertEngine: IConvertEngine, + nodeLookUp: NodeLookup>>, gqlDiagram: GQLDiagram, gqlNode: GQLNode, _gqlEdges: GQLEdge[], @@ -141,7 +145,7 @@ export class IconLabelNodeConverter implements INodeConverter { ) { const nodeDescription = nodeDescriptions.find((description) => description.id === gqlNode.descriptionId); if (nodeDescription) { - nodes.push(toIconLabelNode(gqlDiagram, gqlNode, parentNode, nodeDescription, isBorderNode)); + nodes.push(toIconLabelNode(nodeLookUp, gqlDiagram, gqlNode, parentNode, nodeDescription, isBorderNode)); } } } diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/converter/ImageNodeConverter.ts b/packages/diagrams/frontend/sirius-components-diagrams/src/converter/ImageNodeConverter.ts index f7bc93f4f2..3fae06a310 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/converter/ImageNodeConverter.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/converter/ImageNodeConverter.ts @@ -10,12 +10,13 @@ * Contributors: * Obeo - initial API and implementation *******************************************************************************/ -import { Node, XYPosition } from '@xyflow/react'; +import { InternalNode, Node, XYPosition } from '@xyflow/react'; +import { NodeLookup } from '@xyflow/system'; import { GQLNodeDescription } from '../graphql/query/nodeDescriptionFragment.types'; import { GQLDiagram, GQLNodeLayoutData } from '../graphql/subscription/diagramFragment.types'; import { GQLEdge } from '../graphql/subscription/edgeFragment.types'; import { GQLImageNodeStyle, GQLNode, GQLNodeStyle, GQLViewModifier } from '../graphql/subscription/nodeFragment.types'; -import { BorderNodePosition } from '../renderer/DiagramRenderer.types'; +import { BorderNodePosition, NodeData } from '../renderer/DiagramRenderer.types'; import { ConnectionHandle } from '../renderer/handles/ConnectionHandles.types'; import { defaultHeight, defaultWidth } from '../renderer/layout/layoutParams'; import { FreeFormNodeData } from '../renderer/node/FreeFormNode.types'; @@ -28,6 +29,7 @@ import { convertInsideLabel, convertOutsideLabels } from './convertLabel'; const defaultPosition: XYPosition = { x: 0, y: 0 }; const toImageNode = ( + nodeLookUp: NodeLookup>>, gqlDiagram: GQLDiagram, gqlNode: GQLNode, gqlParentNode: GQLNode | null, @@ -101,6 +103,7 @@ const toImageNode = ( data, position: defaultPosition, hidden: state === GQLViewModifier.Hidden, + selected: !!nodeLookUp.get(id)?.selected, }; if (gqlParentNode) { @@ -136,6 +139,7 @@ export class ImageNodeConverter implements INodeConverter { handle( convertEngine: IConvertEngine, + nodeLookUp: NodeLookup>>, gqlDiagram: GQLDiagram, gqlNode: GQLNode, gqlEdges: GQLEdge[], @@ -147,7 +151,7 @@ export class ImageNodeConverter implements INodeConverter { ) { const nodeDescription = nodeDescriptions.find((description) => description.id === gqlNode.descriptionId); if (nodeDescription) { - nodes.push(toImageNode(gqlDiagram, gqlNode, parentNode, nodeDescription, isBorderNode, gqlEdges)); + nodes.push(toImageNode(nodeLookUp, gqlDiagram, gqlNode, parentNode, nodeDescription, isBorderNode, gqlEdges)); } const borderNodeDescriptions: GQLNodeDescription[] = (nodeDescription?.borderNodeDescriptionIds ?? []).flatMap( @@ -160,6 +164,7 @@ export class ImageNodeConverter implements INodeConverter { ); convertEngine.convertNodes( + nodeLookUp, gqlDiagram, gqlNode.borderNodes ?? [], gqlNode, @@ -168,6 +173,7 @@ export class ImageNodeConverter implements INodeConverter { borderNodeDescriptions ); convertEngine.convertNodes( + nodeLookUp, gqlDiagram, gqlNode.childNodes ?? [], gqlNode, diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/converter/ListNodeConverter.ts b/packages/diagrams/frontend/sirius-components-diagrams/src/converter/ListNodeConverter.ts index f3b7cfa577..cdcba5fc03 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/converter/ListNodeConverter.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/converter/ListNodeConverter.ts @@ -10,7 +10,8 @@ * Contributors: * Obeo - initial API and implementation *******************************************************************************/ -import { Node, XYPosition } from '@xyflow/react'; +import { InternalNode, Node, XYPosition } from '@xyflow/react'; +import { NodeLookup } from '@xyflow/system'; import { GQLNodeDescription } from '../graphql/query/nodeDescriptionFragment.types'; import { GQLDiagram, GQLNodeLayoutData } from '../graphql/subscription/diagramFragment.types'; import { GQLEdge } from '../graphql/subscription/edgeFragment.types'; @@ -33,6 +34,7 @@ import { convertInsideLabel, convertOutsideLabels } from './convertLabel'; const defaultPosition: XYPosition = { x: 0, y: 0 }; const toListNode = ( + nodeLookUp: NodeLookup>>, gqlDiagram: GQLDiagram, gqlNode: GQLNode, gqlParentNode: GQLNode | null, @@ -116,6 +118,7 @@ const toListNode = ( data, position: defaultPosition, hidden: state === GQLViewModifier.Hidden, + selected: !!nodeLookUp.get(id)?.selected, }; if (gqlParentNode) { @@ -177,6 +180,7 @@ export class ListNodeConverter implements INodeConverter { handle( convertEngine: IConvertEngine, + nodeLookUp: NodeLookup>>, gqlDiagram: GQLDiagram, gqlNode: GQLNode, gqlEdges: GQLEdge[], @@ -188,7 +192,7 @@ export class ListNodeConverter implements INodeConverter { ) { const nodeDescription = nodeDescriptions.find((description) => description.id === gqlNode.descriptionId); if (nodeDescription) { - nodes.push(toListNode(gqlDiagram, gqlNode, parentNode, nodeDescription, isBorderNode, gqlEdges)); + nodes.push(toListNode(nodeLookUp, gqlDiagram, gqlNode, parentNode, nodeDescription, isBorderNode, gqlEdges)); } const borderNodeDescriptions: GQLNodeDescription[] = (nodeDescription?.borderNodeDescriptionIds ?? []).flatMap( @@ -201,6 +205,7 @@ export class ListNodeConverter implements INodeConverter { ); convertEngine.convertNodes( + nodeLookUp, gqlDiagram, gqlNode.borderNodes ?? [], gqlNode, @@ -209,6 +214,7 @@ export class ListNodeConverter implements INodeConverter { borderNodeDescriptions ); convertEngine.convertNodes( + nodeLookUp, gqlDiagram, gqlNode.childNodes ?? [], gqlNode, diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/converter/RectangleNodeConverter.ts b/packages/diagrams/frontend/sirius-components-diagrams/src/converter/RectangleNodeConverter.ts index dd2e8f3a8d..1a0fbf522b 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/converter/RectangleNodeConverter.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/converter/RectangleNodeConverter.ts @@ -10,7 +10,8 @@ * Contributors: * Obeo - initial API and implementation *******************************************************************************/ -import { Node, XYPosition } from '@xyflow/react'; +import { InternalNode, Node, XYPosition } from '@xyflow/react'; +import { NodeLookup } from '@xyflow/system'; import { GQLNodeDescription } from '../graphql/query/nodeDescriptionFragment.types'; import { GQLDiagram, GQLNodeLayoutData } from '../graphql/subscription/diagramFragment.types'; import { GQLEdge } from '../graphql/subscription/edgeFragment.types'; @@ -20,7 +21,7 @@ import { GQLRectangularNodeStyle, GQLViewModifier, } from '../graphql/subscription/nodeFragment.types'; -import { BorderNodePosition } from '../renderer/DiagramRenderer.types'; +import { BorderNodePosition, NodeData } from '../renderer/DiagramRenderer.types'; import { ConnectionHandle } from '../renderer/handles/ConnectionHandles.types'; import { defaultHeight, defaultWidth } from '../renderer/layout/layoutParams'; import { FreeFormNodeData } from '../renderer/node/FreeFormNode.types'; @@ -33,6 +34,7 @@ import { convertInsideLabel, convertOutsideLabels } from './convertLabel'; const defaultPosition: XYPosition = { x: 0, y: 0 }; const toRectangularNode = ( + nodeLookUp: NodeLookup>>, gqlDiagram: GQLDiagram, gqlNode: GQLNode, gqlParentNode: GQLNode | null, @@ -108,6 +110,7 @@ const toRectangularNode = ( data, position: defaultPosition, hidden: state === GQLViewModifier.Hidden, + selected: !!nodeLookUp.get(id)?.selected, }; if (gqlParentNode) { @@ -143,6 +146,7 @@ export class RectangleNodeConverter implements INodeConverter { handle( convertEngine: IConvertEngine, + nodeLookUp: NodeLookup>>, gqlDiagram: GQLDiagram, gqlNode: GQLNode, gqlEdges: GQLEdge[], @@ -154,7 +158,9 @@ export class RectangleNodeConverter implements INodeConverter { ) { const nodeDescription = nodeDescriptions.find((description) => description.id === gqlNode.descriptionId); if (nodeDescription) { - nodes.push(toRectangularNode(gqlDiagram, gqlNode, parentNode, nodeDescription, isBorderNode, gqlEdges)); + nodes.push( + toRectangularNode(nodeLookUp, gqlDiagram, gqlNode, parentNode, nodeDescription, isBorderNode, gqlEdges) + ); } const borderNodeDescriptions: GQLNodeDescription[] = (nodeDescription?.borderNodeDescriptionIds ?? []).flatMap( @@ -167,6 +173,7 @@ export class RectangleNodeConverter implements INodeConverter { ); convertEngine.convertNodes( + nodeLookUp, gqlDiagram, gqlNode.borderNodes ?? [], gqlNode, @@ -175,6 +182,7 @@ export class RectangleNodeConverter implements INodeConverter { borderNodeDescriptions ); convertEngine.convertNodes( + nodeLookUp, gqlDiagram, gqlNode.childNodes ?? [], gqlNode, 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 472bab6183..0f6cd1c4fa 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/converter/convertDiagram.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/converter/convertDiagram.ts @@ -11,7 +11,8 @@ * Obeo - initial API and implementation *******************************************************************************/ -import { Edge, Node } from '@xyflow/react'; +import { Edge, InternalNode, Node } from '@xyflow/react'; +import { EdgeLookup, NodeLookup } from '@xyflow/system'; import { GQLNodeDescription } from '../graphql/query/nodeDescriptionFragment.types'; import { GQLDiagram } from '../graphql/subscription/diagramFragment.types'; import { GQLLabel } from '../graphql/subscription/labelFragment.types'; @@ -28,6 +29,7 @@ import { MultiLabelEdgeData } from '../renderer/edge/MultiLabelEdge.types'; import { RawDiagram } from '../renderer/layout/layout.types'; import { computeBorderNodeExtents, computeBorderNodePositions } from '../renderer/layout/layoutBorderNodes'; import { layoutHandles } from '../renderer/layout/layoutHandles'; +import { GQLEdgeLayoutData } from '../renderer/layout/useSynchronizeLayoutData.types'; import { DiagramNodeType } from '../renderer/node/NodeTypes.types'; import { GQLDiagramDescription } from '../representation/DiagramRepresentation.types'; import { IConvertEngine, INodeConverter } from './ConvertEngine.types'; @@ -36,7 +38,6 @@ import { ImageNodeConverter } from './ImageNodeConverter'; import { ListNodeConverter } from './ListNodeConverter'; import { RectangleNodeConverter } from './RectangleNodeConverter'; import { convertContentStyle, convertLabelStyle } from './convertLabel'; -import { GQLEdgeLayoutData } from '../renderer/layout/useSynchronizeLayoutData.types'; const nodeDepth = (nodeId2node: Map, nodeId: string): number => { const node = nodeId2node.get(nodeId); @@ -87,6 +88,8 @@ const defaultNodeConverters: INodeConverter[] = [ ]; export const convertDiagram = ( + nodeLookUp: NodeLookup>>, + edgeLookUp: EdgeLookup>, gqlDiagram: GQLDiagram, nodeConverterContributions: INodeConverter[], diagramDescription: GQLDiagramDescription, @@ -95,6 +98,7 @@ export const convertDiagram = ( const nodes: Node[] = []; const convertEngine: IConvertEngine = { convertNodes( + nodeLookUp: NodeLookup>>, gqlDiagram: GQLDiagram, gqlNodesToConvert: GQLNode[], parentNode: GQLNode | null, @@ -112,6 +116,7 @@ export const convertDiagram = ( const isBorderNode: boolean = !!parentNode?.borderNodes?.map((borderNode) => borderNode.id).includes(node.id); nodeConverter.handle( this, + nodeLookUp, gqlDiagram, node, gqlDiagram.edges, @@ -127,6 +132,7 @@ export const convertDiagram = ( }; convertEngine.convertNodes( + nodeLookUp, gqlDiagram, gqlDiagram.nodes, null, @@ -208,6 +214,7 @@ export const convertDiagram = ( sourceNode: sourceNode, targetNode: targetNode, reconnectable: false, + selected: !!edgeLookUp.get(gqlEdge.id)?.selected, }; }); @@ -216,14 +223,14 @@ export const convertDiagram = ( edges, }; - const nodeLookUp = new Map(); + const futurNodeLookUp = new Map(); nodes.forEach((node) => { - nodeLookUp.set(node.id, node); + futurNodeLookUp.set(node.id, node); }); computeBorderNodeExtents(rawDiagram.nodes); computeBorderNodePositions(rawDiagram.nodes); - layoutHandles(rawDiagram, diagramDescription, nodeLookUp); + layoutHandles(rawDiagram, diagramDescription, futurNodeLookUp); return { nodes: rawDiagram.nodes, diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/index.ts b/packages/diagrams/frontend/sirius-components-diagrams/src/index.ts index 42f6233dce..aea325c051 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/index.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/index.ts @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2023, 2024 Obeo. + * Copyright (c) 2023, 2025 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 @@ -13,6 +13,8 @@ export { DiagramContext } from './contexts/DiagramContext'; export type { DiagramContextValue } from './contexts/DiagramContext.types'; +export { DiagramPayloadContext } from './contexts/DiagramPayloadContext'; +export type { DiagramPayloadContextValue } from './contexts/DiagramPayloadContext.types'; export { NodeTypeContext } from './contexts/NodeContext'; export type { NodeTypeContextValue, NodeTypeContributionElement } from './contexts/NodeContext.types'; export { convertLineStyle, isListLayoutStrategy } from './converter/convertDiagram'; diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/DiagramRenderer.tsx b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/DiagramRenderer.tsx index cccf18cddf..01d0cadfd7 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/DiagramRenderer.tsx +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/DiagramRenderer.tsx @@ -28,7 +28,6 @@ import { ReactFlow, ReactFlowProps, applyNodeChanges, - useReactFlow, useStoreApi, } from '@xyflow/react'; import React, { MouseEvent as ReactMouseEvent, memo, useCallback, useContext, useEffect, useMemo, useRef } from 'react'; @@ -128,14 +127,28 @@ export const DiagramRenderer = memo(({ diagramRefreshedEventPayload }: DiagramRe const { edgeType, setEdgeType } = useEdgeType(); useInitialFitToScreen(); - const { getNode } = useReactFlow, Edge>(); const store = useStoreApi, Edge>(); + const { nodeLookup, edgeLookup } = store.getState(); useEffect(() => { const { diagram, cause } = diagramRefreshedEventPayload; - const convertedDiagram: Diagram = convertDiagram(diagram, nodeConverters, diagramDescription, edgeType); + const convertedDiagram: Diagram = convertDiagram( + nodeLookup, + edgeLookup, + diagram, + nodeConverters, + diagramDescription, + edgeType + ); convertedDiagram.nodes = convertedDiagram.nodes.map((convertedNode) => { - const currentNode = getNode(convertedNode.id); + const currentNode = nodeLookup.get(convertedNode.id); + if (currentNode?.selected !== convertedNode.selected) { + console.log('CHANGED SELECTION'); + console.log(currentNode); + console.log(currentNode?.selected); + console.log(convertedNode.selected); + console.log('-------------------'); + } if ( !!currentNode && (convertedNode.position.x !== currentNode.position.x || @@ -144,12 +157,15 @@ export const DiagramRenderer = memo(({ diagramRefreshedEventPayload }: DiagramRe convertedNode.height !== currentNode.height || (!!currentNode && JSON.stringify(convertedNode.data) !== JSON.stringify(currentNode.data))) ) { + console.log('not equal'); return { ...convertedNode, }; } else if (!!currentNode) { + console.log('EQUAL'); return currentNode; } else { + console.log('added'); return { ...convertedNode, }; @@ -208,14 +224,31 @@ export const DiagramRenderer = memo(({ diagramRefreshedEventPayload }: DiagramRe } // Apply the new graphical selection - convertedDiagram.nodes = convertedDiagram.nodes.map((node) => ({ - ...node, - selected: viewsToSelect.get(node.data?.targetObjectId)?.includes(node.id), - })); - convertedDiagram.edges = convertedDiagram.edges.map((edge) => ({ - ...edge, - selected: !!(edge.data?.targetObjectId && viewsToSelect.get(edge.data?.targetObjectId)?.includes(edge.id)), - })); + convertedDiagram.nodes = convertedDiagram.nodes.map((node) => { + const shouldBeSelected = !!viewsToSelect.get(node.data?.targetObjectId)?.includes(node.id); + if (shouldBeSelected === node.selected) { + return node; + } else { + return { + ...node, + selected: shouldBeSelected, + }; + } + }); + + convertedDiagram.edges = convertedDiagram.edges.map((edge) => { + const shouldBeSelected = !!( + edge.data?.targetObjectId && viewsToSelect.get(edge.data?.targetObjectId)?.includes(edge.id) + ); + if (shouldBeSelected === edge.selected) { + return edge; + } else { + return { + ...edge, + selected: shouldBeSelected, + }; + } + }); setEdges(convertedDiagram.edges); setNodes(convertedDiagram.nodes); @@ -229,20 +262,30 @@ export const DiagramRenderer = memo(({ diagramRefreshedEventPayload }: DiagramRe laidOutDiagram.nodes = laidOutDiagram.nodes.map((node) => { if (nodeLookup.get(node.id)) { - return { - ...node, - selected: !!nodeLookup.get(node.id)?.selected, - }; + const shouldBeSelected = !!nodeLookup.get(node.id)?.selected; + if (shouldBeSelected === node.selected) { + return node; + } else { + return { + ...node, + selected: shouldBeSelected, + }; + } } return node; }); laidOutDiagram.edges = laidOutDiagram.edges.map((edge) => { if (edgeLookup.get(edge.id)) { - return { - ...edge, - selected: !!edgeLookup.get(edge.id)?.selected, - }; + const shouldBeSelected = !!edgeLookup.get(edge.id)?.selected; + if (shouldBeSelected === edge.selected) { + return edge; + } else { + return { + ...edge, + selected: shouldBeSelected, + }; + } } return edge; }); diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/adjust-size/useAdjustSize.tsx b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/adjust-size/useAdjustSize.tsx index bcc5e154e3..335261d8da 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/adjust-size/useAdjustSize.tsx +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/adjust-size/useAdjustSize.tsx @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2024 Obeo. + * Copyright (c) 2024, 2025 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 @@ -12,8 +12,8 @@ *******************************************************************************/ import { Edge, Node, useReactFlow } from '@xyflow/react'; import { useContext } from 'react'; -import { DiagramContext } from '../../contexts/DiagramContext'; -import { DiagramContextValue } from '../../contexts/DiagramContext.types'; +import { DiagramPayloadContext } from '../../contexts/DiagramPayloadContext'; +import { DiagramPayloadContextValue } from '../../contexts/DiagramPayloadContext.types'; import { EdgeData, NodeData } from '../DiagramRenderer.types'; import { RawDiagram } from '../layout/layout.types'; import { useLayout } from '../layout/useLayout'; @@ -24,7 +24,7 @@ import { UseAdjustSizeValue } from './useAdjustSize.types'; export const useAdjustSize = (): UseAdjustSizeValue => { const { layout } = useLayout(); - const { refreshEventPayloadId } = useContext(DiagramContext); + const { refreshEventPayloadId } = useContext(DiagramPayloadContext); const { synchronizeLayoutData } = useSynchronizeLayoutData(); const { hideDiagramElementPalette } = useDiagramElementPalette(); const { getNodes, getEdges, setNodes, setEdges } = useReactFlow, Edge>(); diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/connector/useConnectorNodeStyle.tsx b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/connector/useConnectorNodeStyle.tsx index bc710739ee..c39f43eb24 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/connector/useConnectorNodeStyle.tsx +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/connector/useConnectorNodeStyle.tsx @@ -21,7 +21,7 @@ import { UseConnectorNodeStyleValue } from './useConnectorStyle.types'; export const useConnectorNodeStyle = (nodeId: string, descriptionId: string): UseConnectorNodeStyleValue => { const theme = useTheme(); - + //TODO uses data.hoveredNode ? rework connection ... not important const { candidates, isNewConnection } = useContext(ConnectorContext); const { hoveredNode } = useContext(NodeContext); diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/edge/useEditableEdgePath.ts b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/edge/useEditableEdgePath.ts index 00bba92d77..87fd5f31e1 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/edge/useEditableEdgePath.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/edge/useEditableEdgePath.ts @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2024 Obeo. + * Copyright (c) 2024, 2025 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 @@ -12,18 +12,18 @@ *******************************************************************************/ import { Edge, Node } from '@xyflow/react'; import { useCallback, useContext } from 'react'; -import { UseEditableEdgePathValue } from './useEditableEdgePath.types'; -import { NodeData, EdgeData } from '../DiagramRenderer.types'; -import { RawDiagram } from '../layout/layout.types'; -import { DiagramNodeType } from '../node/NodeTypes.types'; +import { DiagramPayloadContext } from '../../contexts/DiagramPayloadContext'; +import { DiagramPayloadContextValue } from '../../contexts/DiagramPayloadContext.types'; import { useStore } from '../../representation/useStore'; +import { EdgeData, NodeData } from '../DiagramRenderer.types'; +import { RawDiagram } from '../layout/layout.types'; import { useSynchronizeLayoutData } from '../layout/useSynchronizeLayoutData'; -import { DiagramContextValue } from '../../contexts/DiagramContext.types'; -import { DiagramContext } from '../../contexts/DiagramContext'; +import { DiagramNodeType } from '../node/NodeTypes.types'; +import { UseEditableEdgePathValue } from './useEditableEdgePath.types'; export const useEditableEdgePath = (): UseEditableEdgePathValue => { const { getEdges, getNodes, setEdges } = useStore(); - const { refreshEventPayloadId } = useContext(DiagramContext); + const { refreshEventPayloadId } = useContext(DiagramPayloadContext); const { synchronizeLayoutData } = useSynchronizeLayoutData(); const synchronizeEdgeLayoutData = useCallback( diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/hover/useNodeHover.ts b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/hover/useNodeHover.ts index fe3e0e4ac8..1dc4a7be22 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/hover/useNodeHover.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/hover/useNodeHover.ts @@ -11,60 +11,52 @@ * Obeo - initial API and implementation *******************************************************************************/ -import { Node, NodeMouseHandler } from '@xyflow/react'; -import { useCallback, useContext } from 'react'; -import { useStore } from '../../representation/useStore'; +import { Node, NodeMouseHandler, useReactFlow } from '@xyflow/react'; +import { useCallback } from 'react'; import { NodeData } from '../DiagramRenderer.types'; -import { DropNodeContext } from '../dropNode/DropNodeContext'; -import { DropNodeContextValue } from '../dropNode/DropNodeContext.types'; import { UseNodeHoverValue } from './useNodeHover.types'; export const useNodeHover = (): UseNodeHoverValue => { - const { setNodes } = useStore(); - const { draggedNodeId } = useContext(DropNodeContext); + const { setNodes } = useReactFlow(); const onNodeMouseEnter: NodeMouseHandler> = useCallback( (_: React.MouseEvent, node: Node) => { - if (!draggedNodeId) { - setNodes((nds) => - nds.map((n) => { - if (n.id === node.id) { - if (!n.data.isHovered) { - return { - ...n, - data: { - ...n.data, - isHovered: true, - }, - }; - } - } - return n; - }) - ); - } - }, - [setNodes, draggedNodeId] - ); - - const onNodeMouseLeave: NodeMouseHandler = useCallback(() => { - if (!draggedNodeId) { setNodes((nds) => nds.map((n) => { - if (n.data.isHovered) { - return { - ...n, - data: { - ...n.data, - isHovered: false, - }, - }; + if (n.id === node.id) { + if (!n.data.isHovered) { + return { + ...n, + data: { + ...n.data, + isHovered: true, + }, + }; + } } return n; }) ); - } - }, [setNodes, draggedNodeId]); + }, + [] + ); + + const onNodeMouseLeave: NodeMouseHandler = useCallback(() => { + setNodes((nds) => + nds.map((n) => { + if (n.data.isHovered) { + return { + ...n, + data: { + ...n.data, + isHovered: false, + }, + }; + } + return n; + }) + ); + }, []); return { onNodeMouseEnter, diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/layout/useArrangeAll.ts b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/layout/useArrangeAll.ts index ab944c4eb5..7294db314b 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/layout/useArrangeAll.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/layout/useArrangeAll.ts @@ -15,8 +15,8 @@ import { Edge, Node, useReactFlow, useViewport } from '@xyflow/react'; import { LayoutOptions } from 'elkjs/lib/elk-api'; import ELK, { ElkLabel, ElkNode } from 'elkjs/lib/elk.bundled'; import { useContext } from 'react'; -import { DiagramContext } from '../../contexts/DiagramContext'; -import { DiagramContextValue } from '../../contexts/DiagramContext.types'; +import { DiagramPayloadContext } from '../../contexts/DiagramPayloadContext'; +import { DiagramPayloadContextValue } from '../../contexts/DiagramPayloadContext.types'; import { useDiagramDescription } from '../../contexts/useDiagramDescription'; import { EdgeData, NodeData } from '../DiagramRenderer.types'; import { ListNodeData } from '../node/ListNode.types'; @@ -123,7 +123,7 @@ export const useArrangeAll = (reactFlowWrapper: React.MutableRefObject(DiagramContext); + const { refreshEventPayloadId } = useContext(DiagramPayloadContext); const { resolveNodeOverlap } = useOverlap(); const { addErrorMessage } = useMultiToast(); diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/layout/useDistributeElements.ts b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/layout/useDistributeElements.ts index 7f441bd127..f071eb5d47 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/layout/useDistributeElements.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/layout/useDistributeElements.ts @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2024 Obeo. + * Copyright (c) 2024, 2025 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 @@ -13,8 +13,8 @@ import { useMultiToast } from '@eclipse-sirius/sirius-components-core'; import { Edge, Node, XYPosition, useReactFlow } from '@xyflow/react'; import { useCallback, useContext } from 'react'; -import { DiagramContext } from '../../contexts/DiagramContext'; -import { DiagramContextValue } from '../../contexts/DiagramContext.types'; +import { DiagramPayloadContext } from '../../contexts/DiagramPayloadContext'; +import { DiagramPayloadContextValue } from '../../contexts/DiagramPayloadContext.types'; import { EdgeData, NodeData } from '../DiagramRenderer.types'; import { DiagramNodeType } from '../node/NodeTypes.types'; import { useOverlap } from '../overlap/useOverlap'; @@ -40,7 +40,7 @@ export const useDistributeElements = (): UseDistributeElementsValue => { const { layout } = useLayout(); const { synchronizeLayoutData } = useSynchronizeLayoutData(); const { addMessages } = useMultiToast(); - const { refreshEventPayloadId } = useContext(DiagramContext); + const { refreshEventPayloadId } = useContext(DiagramPayloadContext); const { resolveNodeOverlap } = useOverlap(); const processLayoutTool = ( diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/representation/DiagramRepresentation.tsx b/packages/diagrams/frontend/sirius-components-diagrams/src/representation/DiagramRepresentation.tsx index 2874369af1..fc2b527eb7 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/representation/DiagramRepresentation.tsx +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/representation/DiagramRepresentation.tsx @@ -15,6 +15,7 @@ import { gql, useQuery } from '@apollo/client'; import { RepresentationComponentProps, useMultiToast } from '@eclipse-sirius/sirius-components-core'; import { ReactFlowProvider } from '@xyflow/react'; import { memo, useEffect, useState } from 'react'; +import { DiagramContext } from '../contexts/DiagramContext'; import { DiagramDescriptionContext } from '../contexts/DiagramDescriptionContext'; import { ConnectorContextProvider } from '../renderer/connector/ConnectorContext'; import { DiagramDirectEditContextProvider } from '../renderer/direct-edit/DiagramDirectEditContext'; @@ -31,6 +32,7 @@ import { GQLDiagramDescriptionVariables, } from './DiagramRepresentation.types'; import { DiagramSubscriptionProvider } from './DiagramSubscriptionProvider'; +import { StoreContextProvider } from './StoreContext'; export const getDiagramDescription = gql` query getDiagramDescription($editingContextId: ID!, $representationId: ID!) { @@ -108,27 +110,36 @@ export const DiagramRepresentation = memo( return ( - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + ); } diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/representation/DiagramSubscriptionProvider.tsx b/packages/diagrams/frontend/sirius-components-diagrams/src/representation/DiagramSubscriptionProvider.tsx index 8976233113..f40919eee7 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/representation/DiagramSubscriptionProvider.tsx +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/representation/DiagramSubscriptionProvider.tsx @@ -14,7 +14,7 @@ import LinearProgress from '@mui/material/LinearProgress'; import Typography from '@mui/material/Typography'; import { memo, useEffect, useState } from 'react'; -import { DiagramContext } from '../contexts/DiagramContext'; +import { DiagramPayloadContext } from '../contexts/DiagramPayloadContext'; import { DialogContextProvider } from '../dialog/DialogContext'; import { GQLDiagramEventPayload, @@ -22,65 +22,57 @@ import { } from '../graphql/subscription/diagramEventSubscription.types'; import { DiagramRenderer } from '../renderer/DiagramRenderer'; import { DiagramSubscriptionProviderProps, DiagramSubscriptionState } from './DiagramSubscriptionProvider.types'; -import { StoreContextProvider } from './StoreContext'; import { useDiagramSubscription } from './useDiagramSubscription'; const isDiagramRefreshedEventPayload = ( payload: GQLDiagramEventPayload | null ): payload is GQLDiagramRefreshedEventPayload => !!payload && payload.__typename === 'DiagramRefreshedEventPayload'; -export const DiagramSubscriptionProvider = memo( - ({ diagramId, editingContextId, readOnly }: DiagramSubscriptionProviderProps) => { - const [state, setState] = useState({ - id: crypto.randomUUID(), - diagramRefreshedEventPayload: null, - complete: false, - message: '', - }); +export const DiagramSubscriptionProvider = memo(({ diagramId, editingContextId }: DiagramSubscriptionProviderProps) => { + const [state, setState] = useState({ + id: crypto.randomUUID(), + diagramRefreshedEventPayload: null, + complete: false, + message: '', + }); - const { complete, payload } = useDiagramSubscription(editingContextId, diagramId); + const { complete, payload } = useDiagramSubscription(editingContextId, diagramId); - useEffect(() => { - if (isDiagramRefreshedEventPayload(payload)) { - setState((prevState) => ({ ...prevState, diagramRefreshedEventPayload: payload })); - } - }, [payload]); - - if (complete) { - return ( -
- The representation is not available anymore -
- ); - } - - if (!state.diagramRefreshedEventPayload) { - return ; + useEffect(() => { + if (isDiagramRefreshedEventPayload(payload)) { + setState((prevState) => ({ ...prevState, diagramRefreshedEventPayload: payload })); } + }, [payload]); + if (complete) { return ( - - - -
- -
-
-
-
+
+ The representation is not available anymore +
); } -); + + if (!state.diagramRefreshedEventPayload) { + return ; + } + + return ( + + +
+ +
+
+
+ ); +}); diff --git a/packages/sirius-web/frontend/sirius-web/src/nodes/EllipseNodeConverter.ts b/packages/sirius-web/frontend/sirius-web/src/nodes/EllipseNodeConverter.ts index 43193aa9f1..ff6f74af6d 100644 --- a/packages/sirius-web/frontend/sirius-web/src/nodes/EllipseNodeConverter.ts +++ b/packages/sirius-web/frontend/sirius-web/src/nodes/EllipseNodeConverter.ts @@ -13,6 +13,12 @@ import { BorderNodePosition, ConnectionHandle, + convertHandles, + convertInsideLabel, + convertLineStyle, + convertOutsideLabels, + defaultHeight, + defaultWidth, GQLDiagram, GQLDiagramDescription, GQLEdge, @@ -23,20 +29,17 @@ import { GQLViewModifier, IConvertEngine, INodeConverter, - convertHandles, - convertInsideLabel, - convertLineStyle, - convertOutsideLabels, isListLayoutStrategy, - defaultHeight, - defaultWidth, + NodeData, } from '@eclipse-sirius/sirius-components-diagrams'; -import { Node, XYPosition } from '@xyflow/react'; +import { InternalNode, Node, XYPosition } from '@xyflow/react'; +import { NodeLookup } from '@xyflow/system'; import { EllipseNodeData, GQLEllipseNodeStyle } from './EllipseNode.types'; const defaultPosition: XYPosition = { x: 0, y: 0 }; const toEllipseNode = ( + nodeLookUp: NodeLookup>>, gqlDiagram: GQLDiagram, gqlNode: GQLNode, gqlParentNode: GQLNode | null, @@ -108,6 +111,7 @@ const toEllipseNode = ( data, position: defaultPosition, hidden: gqlNode.state === GQLViewModifier.Hidden, + selected: !!nodeLookUp.get(id)?.selected, }; if (gqlParentNode) { @@ -143,6 +147,7 @@ export class EllipseNodeConverter implements INodeConverter { handle( convertEngine: IConvertEngine, + nodeLookUp: NodeLookup>>, gqlDiagram: GQLDiagram, gqlNode: GQLNode, gqlEdges: GQLEdge[], @@ -153,7 +158,7 @@ export class EllipseNodeConverter implements INodeConverter { nodeDescriptions: GQLNodeDescription[] ) { const nodeDescription = nodeDescriptions.find((description) => description.id === gqlNode.descriptionId); - nodes.push(toEllipseNode(gqlDiagram, gqlNode, parentNode, nodeDescription, isBorderNode, gqlEdges)); + nodes.push(toEllipseNode(nodeLookUp, gqlDiagram, gqlNode, parentNode, nodeDescription, isBorderNode, gqlEdges)); const borderNodeDescriptions: GQLNodeDescription[] = (nodeDescription?.borderNodeDescriptionIds ?? []).flatMap( (nodeDescriptionId) => @@ -165,6 +170,7 @@ export class EllipseNodeConverter implements INodeConverter { ); convertEngine.convertNodes( + nodeLookUp, gqlDiagram, gqlNode.borderNodes ?? [], gqlNode, @@ -173,6 +179,7 @@ export class EllipseNodeConverter implements INodeConverter { borderNodeDescriptions ); convertEngine.convertNodes( + nodeLookUp, gqlDiagram, gqlNode.childNodes ?? [], gqlNode,