From 9fba3e4f2cc2e6fe6db5c7992920f1d07be5c27d Mon Sep 17 00:00:00 2001 From: Florent Latombe Date: Tue, 4 Feb 2025 11:07:10 +0100 Subject: [PATCH] [4376] Work around selection not being set at initial diagram rendering There was an issue where when resolving a URL with a selection and an open diagram, the selection was not being applied graphically in the diagram because at the time the selection is applied, the diagram exists but has no nodes or edges. This is a workaround to make sure the selection gets applied during the first render *after* the nodes and edges have been created. Bug: https://github.com/eclipse-sirius/sirius-web/issues/4376 Signed-off-by: Florent Latombe --- .../fit-to-screen/useInitialFitToScreen.tsx | 19 +++-- .../useApplyWorkbenchSelectionToDiagram.ts | 69 +++++++++++++++++++ .../renderer/selection/useDiagramSelection.ts | 46 +------------ 3 files changed, 86 insertions(+), 48 deletions(-) create mode 100644 packages/diagrams/frontend/sirius-components-diagrams/src/renderer/selection/useApplyWorkbenchSelectionToDiagram.ts diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/fit-to-screen/useInitialFitToScreen.tsx b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/fit-to-screen/useInitialFitToScreen.tsx index cb1fcb2cb2..020ae8090c 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/fit-to-screen/useInitialFitToScreen.tsx +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/fit-to-screen/useInitialFitToScreen.tsx @@ -11,9 +11,12 @@ * Obeo - initial API and implementation *******************************************************************************/ +import { useSelection } from '@eclipse-sirius/sirius-components-core'; import { Edge, Node, useNodesInitialized, useReactFlow } from '@xyflow/react'; import { useEffect, useState } from 'react'; +import { useStore } from '../../representation/useStore'; import { EdgeData, NodeData } from '../DiagramRenderer.types'; +import { useApplyWorkbenchSelectionToDiagram } from '../selection/useApplyWorkbenchSelectionToDiagram'; import { UseInitialFitToScreenState } from './useInitialFitToScreen.types'; const options = { @@ -26,13 +29,21 @@ export const useInitialFitToScreen = () => { const [state, setState] = useState({ initialFitToScreenPerformed: false, }); + const { selection } = useSelection(); + const { fitView } = useReactFlow, Edge>(); + const { getNodes, setNodes, getEdges, setEdges } = useStore(); + console.debug('fit-to-screen has been performed:' + state.initialFitToScreenPerformed); // We cannot perform the fit to screen directly but instead need to wait for the next render in order to retrieve the updated nodes and edges in the react flow instance useEffect(() => { if (nodesInitialized && !state.initialFitToScreenPerformed) { - reactFlowInstance.fitView({ duration: 200, nodes: reactFlowInstance.getNodes() }).then(() => { - setState({ initialFitToScreenPerformed: true }); - }); + if (selection.entries.length === 0) { + reactFlowInstance.fitView({ duration: 200, nodes: reactFlowInstance.getNodes() }).then(() => { + setState({ initialFitToScreenPerformed: true }); + }); + } else { + useApplyWorkbenchSelectionToDiagram(selection, getNodes, setNodes, getEdges, setEdges, fitView); + } } - }, [nodesInitialized, state.initialFitToScreenPerformed]); + }, [nodesInitialized, state.initialFitToScreenPerformed, selection]); }; diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/selection/useApplyWorkbenchSelectionToDiagram.ts b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/selection/useApplyWorkbenchSelectionToDiagram.ts new file mode 100644 index 0000000000..6bc763d010 --- /dev/null +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/selection/useApplyWorkbenchSelectionToDiagram.ts @@ -0,0 +1,69 @@ +/******************************************************************************* + * 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 { Selection } from '@eclipse-sirius/sirius-components-core'; +import { Edge, FitView, Node } from '@xyflow/react'; +import { Dispatch, SetStateAction } from 'react'; +import { EdgeData, NodeData } from '../DiagramRenderer.types'; + +export const useApplyWorkbenchSelectionToDiagram = ( + selection: Selection, + getNodes: () => Node[], + setNodes: Dispatch[]>>, + getEdges: () => Edge[], + setEdges: Dispatch[]>>, + fitView: FitView +) => { + const allDiagramElements = [...getNodes(), ...getEdges()]; + const displayedSemanticElements: Set = new Set([ + ...getNodes().map((node) => node.data.targetObjectId), + ...getEdges().map((edge) => edge.data?.targetObjectId ?? ''), + ]); + const displayedSemanticElementsToSelect = selection.entries + .map((entry) => entry.id) + .filter((id) => displayedSemanticElements.has(id)) + .sort((id1: string, id2: string) => id1.localeCompare(id2)); + + const semanticElementsAlreadySelectedOnDiagram = allDiagramElements + .filter((element) => element.selected) + .map((element) => element.data?.targetObjectId ?? '') + .sort((id1: string, id2: string) => id1.localeCompare(id2)); + + if (JSON.stringify(displayedSemanticElementsToSelect) !== JSON.stringify(semanticElementsAlreadySelectedOnDiagram)) { + const nodesToReveal: Set = new Set(); + const newNodes = getNodes().map((node) => { + const selected = displayedSemanticElementsToSelect.includes(node.data.targetObjectId); + const newNode = { ...node, selected }; + if (selected) { + nodesToReveal.add(newNode.id); + } + return newNode; + }); + const newEdges = getEdges().map((edge) => { + const selected = displayedSemanticElementsToSelect.includes(edge.data ? edge.data.targetObjectId : ''); + const newEdge = { ...edge, selected }; + if (selected) { + // React Flow does not support "fit on edge", so include its source & target nodes + // to ensure the edge is visible and in context + nodesToReveal.add(newEdge.source); + nodesToReveal.add(newEdge.target); + } + return newEdge; + }); + + setEdges(newEdges); + setNodes(newNodes); + + fitView({ nodes: getNodes().filter((node) => nodesToReveal.has(node.id)), maxZoom: 1.5, duration: 1000 }); + } +}; diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/selection/useDiagramSelection.ts b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/selection/useDiagramSelection.ts index 30879c0c83..9226236f80 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/selection/useDiagramSelection.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/selection/useDiagramSelection.ts @@ -16,6 +16,7 @@ import { Edge, Node, useOnSelectionChange, useReactFlow, useStoreApi } from '@xy import { useCallback, useEffect, useState } from 'react'; import { useStore } from '../../representation/useStore'; import { EdgeData, NodeData } from '../DiagramRenderer.types'; +import { useApplyWorkbenchSelectionToDiagram } from './useApplyWorkbenchSelectionToDiagram'; // Compute a deterministic key from a selection const selectionKey = (entries: SelectionEntry[]) => { @@ -35,50 +36,7 @@ export const useDiagramSelection = (onShiftSelection: boolean): void => { // Apply it on our diagram by selecting exactly the diagram elements // present which correspond to the workbench-selected semantic elements. useEffect(() => { - const allDiagramElements = [...getNodes(), ...getEdges()]; - const displayedSemanticElements: Set = new Set([ - ...getNodes().map((node) => node.data.targetObjectId), - ...getEdges().map((edge) => edge.data?.targetObjectId ?? ''), - ]); - const displayedSemanticElementsToSelect = selection.entries - .map((entry) => entry.id) - .filter((id) => displayedSemanticElements.has(id)) - .sort((id1: string, id2: string) => id1.localeCompare(id2)); - - const semanticElementsAlreadySelectedOnDiagram = allDiagramElements - .filter((element) => element.selected) - .map((element) => element.data?.targetObjectId ?? '') - .sort((id1: string, id2: string) => id1.localeCompare(id2)); - - if ( - JSON.stringify(displayedSemanticElementsToSelect) !== JSON.stringify(semanticElementsAlreadySelectedOnDiagram) - ) { - const nodesToReveal: Set = new Set(); - const newNodes = getNodes().map((node) => { - const selected = displayedSemanticElementsToSelect.includes(node.data.targetObjectId); - const newNode = { ...node, selected }; - if (selected) { - nodesToReveal.add(newNode.id); - } - return newNode; - }); - const newEdges = getEdges().map((edge) => { - const selected = displayedSemanticElementsToSelect.includes(edge.data ? edge.data.targetObjectId : ''); - const newEdge = { ...edge, selected }; - if (selected) { - // React Flow does not support "fit on edge", so include its source & target nodes - // to ensure the edge is visible and in context - nodesToReveal.add(newEdge.source); - nodesToReveal.add(newEdge.target); - } - return newEdge; - }); - - setEdges(newEdges); - setNodes(newNodes); - - fitView({ nodes: getNodes().filter((node) => nodesToReveal.has(node.id)), maxZoom: 1.5, duration: 1000 }); - } + useApplyWorkbenchSelectionToDiagram(selection, getNodes, setNodes, getEdges, setEdges, fitView); }, [selection]); const store = useStoreApi, Edge>();