Skip to content

Commit

Permalink
[4376] Work around selection not being set at initial diagram rendering
Browse files Browse the repository at this point in the history
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: #4376
Signed-off-by: Florent Latombe <[email protected]>
  • Loading branch information
flatombe committed Feb 4, 2025
1 parent 1984b23 commit 9fba3e4
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -26,13 +29,21 @@ export const useInitialFitToScreen = () => {
const [state, setState] = useState<UseInitialFitToScreenState>({
initialFitToScreenPerformed: false,
});
const { selection } = useSelection();
const { fitView } = useReactFlow<Node<NodeData>, Edge<EdgeData>>();
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]);
};
Original file line number Diff line number Diff line change
@@ -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<NodeData>[],
setNodes: Dispatch<SetStateAction<Node<NodeData>[]>>,
getEdges: () => Edge<EdgeData>[],
setEdges: Dispatch<SetStateAction<Edge<EdgeData>[]>>,
fitView: FitView
) => {
const allDiagramElements = [...getNodes(), ...getEdges()];
const displayedSemanticElements: Set<string> = 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<string> = 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 });
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -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[]) => {
Expand All @@ -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<string> = 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<string> = 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<Node<NodeData>, Edge<EdgeData>>();
Expand Down

0 comments on commit 9fba3e4

Please sign in to comment.