From a7aaefa572dd081e6d0fae3eb34f98047a81612a Mon Sep 17 00:00:00 2001 From: Florent Latombe Date: Thu, 23 Jan 2025 15:54:38 +0100 Subject: [PATCH 1/4] [4376] Add Workbench prop to apply an effect based on a Selection Bug: https://github.com/eclipse-sirius/sirius-web/issues/4376 Signed-off-by: Florent Latombe --- .../sirius-components-core/src/workbench/Workbench.tsx | 7 ++++++- .../src/workbench/Workbench.types.ts | 4 +++- .../src/views/edit-project/EditProjectView.tsx | 5 ++++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/core/frontend/sirius-components-core/src/workbench/Workbench.tsx b/packages/core/frontend/sirius-components-core/src/workbench/Workbench.tsx index 79ea721cab..41c6b6c1f1 100644 --- a/packages/core/frontend/sirius-components-core/src/workbench/Workbench.tsx +++ b/packages/core/frontend/sirius-components-core/src/workbench/Workbench.tsx @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2021, 2024 Obeo. + * Copyright (c) 2021, 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 @@ -79,6 +79,7 @@ export const Workbench = ({ editingContextId, initialRepresentationSelected, onRepresentationSelected, + onSelectionChanged, readOnly, }: WorkbenchProps) => { const { classes } = useWorkbenchStyles(); @@ -156,6 +157,10 @@ export const Workbench = ({ } }, [onRepresentationSelected, initialRepresentationSelected, displayedRepresentation]); + useEffect(() => { + onSelectionChanged(selection); + }, [selection]); + const workbenchViewLeftSideContributions: WorkbenchViewContribution[] = []; const workbenchViewRightSideContributions: WorkbenchViewContribution[] = []; diff --git a/packages/core/frontend/sirius-components-core/src/workbench/Workbench.types.ts b/packages/core/frontend/sirius-components-core/src/workbench/Workbench.types.ts index 68c19cbcf2..daa5ac34bf 100644 --- a/packages/core/frontend/sirius-components-core/src/workbench/Workbench.types.ts +++ b/packages/core/frontend/sirius-components-core/src/workbench/Workbench.types.ts @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2021, 2024 Obeo and others. + * Copyright (c) 2021, 2025 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 @@ -11,6 +11,7 @@ * Obeo - initial API and implementation *******************************************************************************/ import React from 'react'; +import { Selection } from '../selection/SelectionContext.types'; export interface GQLEditingContextEventPayload { __typename: string; @@ -56,6 +57,7 @@ export type WorkbenchProps = { editingContextId: string; initialRepresentationSelected: RepresentationMetadata | null; onRepresentationSelected: (representation: RepresentationMetadata | null) => void; + onSelectionChanged: (selection: Selection | null) => void; readOnly: boolean; }; diff --git a/packages/sirius-web/frontend/sirius-web-application/src/views/edit-project/EditProjectView.tsx b/packages/sirius-web/frontend/sirius-web-application/src/views/edit-project/EditProjectView.tsx index afc1679b2e..bfb8bcdeef 100644 --- a/packages/sirius-web/frontend/sirius-web-application/src/views/edit-project/EditProjectView.tsx +++ b/packages/sirius-web/frontend/sirius-web-application/src/views/edit-project/EditProjectView.tsx @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2019, 2024 Obeo. + * Copyright (c) 2019, 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 @@ -83,6 +83,8 @@ export const EditProjectView = () => { dispatch(selectRepresentationEvent); }; + const workbenchOnSelectionChanged = () => {}; + useEffect(() => { if (context.representation && context.representation.id !== representationId) { const pathname = generatePath('/projects/:projectId/edit/:representationId', { @@ -135,6 +137,7 @@ export const EditProjectView = () => { editingContextId={context.project.currentEditingContext.id} initialRepresentationSelected={context.representation} onRepresentationSelected={onRepresentationSelected} + onSelectionChanged={workbenchOnSelectionChanged} readOnly={readOnly} /> From 0503568ead032f19a1065ea49186f91422f04053 Mon Sep 17 00:00:00 2001 From: Florent Latombe Date: Thu, 23 Jan 2025 16:01:35 +0100 Subject: [PATCH 2/4] [4376] Make navigation in EditProjectView preserve URL search params Bug: https://github.com/eclipse-sirius/sirius-web/issues/4376 Signed-off-by: Florent Latombe --- .../views/edit-project/EditProjectView.tsx | 31 +++++++++++++++---- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/packages/sirius-web/frontend/sirius-web-application/src/views/edit-project/EditProjectView.tsx b/packages/sirius-web/frontend/sirius-web-application/src/views/edit-project/EditProjectView.tsx index bfb8bcdeef..8978f9783f 100644 --- a/packages/sirius-web/frontend/sirius-web-application/src/views/edit-project/EditProjectView.tsx +++ b/packages/sirius-web/frontend/sirius-web-application/src/views/edit-project/EditProjectView.tsx @@ -25,7 +25,15 @@ import { } from '@eclipse-sirius/sirius-components-trees'; import { useMachine } from '@xstate/react'; import { useEffect } from 'react'; -import { generatePath, Navigate, useNavigate, useParams, useResolvedPath } from 'react-router-dom'; +import { + generatePath, + Navigate, + SetURLSearchParams, + useNavigate, + useParams, + useResolvedPath, + useSearchParams, +} from 'react-router-dom'; import { makeStyles } from 'tss-react/mui'; import { StateMachine } from 'xstate'; import { NavigationBar } from '../../navigationBar/NavigationBar'; @@ -61,6 +69,7 @@ export const EditProjectView = () => { const routeMatch = useResolvedPath('.'); const { projectId, representationId } = useParams(); const { classes } = useEditProjectViewStyles(); + const [urlSearchParams]: [URLSearchParams, SetURLSearchParams] = useSearchParams(); const [{ value, context }, dispatch] = useMachine>( @@ -86,17 +95,27 @@ export const EditProjectView = () => { const workbenchOnSelectionChanged = () => {}; useEffect(() => { + let pathname: string = null; if (context.representation && context.representation.id !== representationId) { - const pathname = generatePath('/projects/:projectId/edit/:representationId', { + pathname = generatePath('/projects/:projectId/edit/:representationId', { projectId, representationId: context.representation.id, }); - navigate(pathname); } else if (value === 'loaded' && context.representation === null && representationId) { - const pathname = generatePath('/projects/:projectId/edit/', { projectId }); - navigate(pathname); + pathname = generatePath('/projects/:projectId/edit/', { projectId }); + } + + if (pathname !== null) { + if (urlSearchParams !== null && urlSearchParams.size > 0) { + navigate({ + pathname: pathname, + search: `?${urlSearchParams}`, + }); + } else { + navigate(pathname); + } } - }, [value, projectId, routeMatch, history, context.representation, representationId]); + }, [value, projectId, routeMatch, history, context.representation, representationId, urlSearchParams]); let content: React.ReactNode = null; From 805f8bb85d6de2f691e85a8f6b8cbd7f701ce7f3 Mon Sep 17 00:00:00 2001 From: Florent Latombe Date: Thu, 23 Jan 2025 16:01:57 +0100 Subject: [PATCH 3/4] [4376] Add current selection as a URL search parameter * When the workbench selection changes, the URL search parameters are now automatically updated to encode the contents of the selection (id and kind). * Reversely, when resolving a URL with such search parameters, the workbench selection is set to those specified elements. Bug: https://github.com/eclipse-sirius/sirius-web/issues/4376 Signed-off-by: Florent Latombe --- CHANGELOG.adoc | 3 +- .../sirius-components-core/src/index.ts | 3 +- .../selection/useSelectionUrlSearchParams.ts | 75 +++++++++++++++++++ .../views/edit-project/EditProjectView.tsx | 23 +++--- 4 files changed, 90 insertions(+), 14 deletions(-) create mode 100644 packages/core/frontend/sirius-components-core/src/selection/useSelectionUrlSearchParams.ts diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index b924b4f174..dc4e3ecb7f 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -121,7 +121,8 @@ See `PapayaExtensionRegistry.tsx` for how the same result as before can be achie - https://github.com/eclipse-sirius/sirius-web/issues/4326[#4326] [diagram] Add quickAccessTools to diagram's palette in view dsl - https://github.com/eclipse-sirius/sirius-web/issues/4458[#4458] [sirius-web] Export direct edit functionalities. - https://github.com/eclipse-sirius/sirius-web/issues/4481[#4481] [table] Add support for cell custom target object in View DSL - +- https://github.com/eclipse-sirius/sirius-web/issues/4376[#4376] [sirius-web] Add current workbench selection to the URL. +Reversely, when resolving a URL, the specified elements automatically get selected. === Improvements diff --git a/packages/core/frontend/sirius-components-core/src/index.ts b/packages/core/frontend/sirius-components-core/src/index.ts index 4ce0a2508e..e12d7d078d 100644 --- a/packages/core/frontend/sirius-components-core/src/index.ts +++ b/packages/core/frontend/sirius-components-core/src/index.ts @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2022, 2024 Obeo and others. + * Copyright (c) 2022, 2025 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 @@ -52,6 +52,7 @@ export * from './selection/SelectionContext'; export type * from './selection/SelectionContext.types'; export * from './selection/useSelection'; export type * from './selection/useSelection.types'; +export * from './selection/useSelectionUrlSearchParams'; export * from './theme'; export * from './toast/MultiToast'; export * from './toast/Toast'; diff --git a/packages/core/frontend/sirius-components-core/src/selection/useSelectionUrlSearchParams.ts b/packages/core/frontend/sirius-components-core/src/selection/useSelectionUrlSearchParams.ts new file mode 100644 index 0000000000..4b6f0d58d2 --- /dev/null +++ b/packages/core/frontend/sirius-components-core/src/selection/useSelectionUrlSearchParams.ts @@ -0,0 +1,75 @@ +/******************************************************************************* + * 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 { SetURLSearchParams } from 'react-router-dom'; +import { Selection, SelectionEntry } from './SelectionContext.types'; +import { useSelection } from './useSelection'; +import { UseSelectionValue } from './useSelection.types'; + +export const updateUrlSearchParamsWithSelection = (selection: Selection, setUrlSearchParams: SetURLSearchParams) => { + setUrlSearchParams((previousSearchParams: URLSearchParams) => { + if (selection.entries.length > 0) { + const selectionValue: string = selection.entries + .map((selectionEntry) => `${selectionEntry.id}${urlSelectionEntryContentSeparator}${selectionEntry.kind}`) + .join(urlSelectionEntriesSeparator); + previousSearchParams.set(urlSelectionSearchParameterName, selectionValue); + } else { + if (previousSearchParams.has(urlSelectionSearchParameterName)) { + previousSearchParams.delete(urlSelectionSearchParameterName); + } + } + return previousSearchParams; + }); +}; + +export const updateSelectionBasedOnUrlSearchParamsIfNeeded = (urlSearchParams: URLSearchParams) => { + const { selection, setSelection }: UseSelectionValue = useSelection(); + if (urlSearchParams.has(urlSelectionSearchParameterName)) { + const urlSelection: Selection | null = createSelectionFromUrlSearchParams(urlSearchParams); + if (urlSelection !== null && !areSelectionContentsEqual(urlSelection, selection)) { + setSelection(urlSelection); + } + } +}; + +export const createSelectionFromUrlSearchParams = (urlSearchParams: URLSearchParams): Selection | null => { + if (urlSearchParams !== null && urlSearchParams.has(urlSelectionSearchParameterName)) { + const urlSelectionSearchParam: string = urlSearchParams.get(urlSelectionSearchParameterName) ?? ''; + const urlSelectionContents: string[] = urlSelectionSearchParam.split(urlSelectionEntriesSeparator); + const urlSelectionEntries: SelectionEntry[] = urlSelectionContents + .map((urlSelectionContent) => { + const [projectElementId, projectElementKind]: string[] = urlSelectionContent.split( + urlSelectionEntryContentSeparator + ); + if (projectElementId && projectElementKind) { + return { id: projectElementId, kind: projectElementKind }; + } else { + return null; + } + }) + .filter((selectionEntry) => selectionEntry !== null) as Array; + return { entries: urlSelectionEntries }; + } + return null; +}; + +const areSelectionContentsEqual = (left: Selection, right: Selection): boolean => { + return ( + left.entries.length == right.entries.length && + left.entries.every( + (element, index) => element.id === right.entries[index]?.id && element.kind === right.entries[index]?.kind + ) + ); +}; +const urlSelectionSearchParameterName: string = 'selection'; +const urlSelectionEntriesSeparator: string = ','; +const urlSelectionEntryContentSeparator: string = ';'; diff --git a/packages/sirius-web/frontend/sirius-web-application/src/views/edit-project/EditProjectView.tsx b/packages/sirius-web/frontend/sirius-web-application/src/views/edit-project/EditProjectView.tsx index 8978f9783f..d50b680567 100644 --- a/packages/sirius-web/frontend/sirius-web-application/src/views/edit-project/EditProjectView.tsx +++ b/packages/sirius-web/frontend/sirius-web-application/src/views/edit-project/EditProjectView.tsx @@ -10,10 +10,14 @@ * Contributors: * Obeo - initial API and implementation *******************************************************************************/ + import { + createSelectionFromUrlSearchParams, RepresentationMetadata, Selection, SelectionContextProvider, + updateSelectionBasedOnUrlSearchParamsIfNeeded, + updateUrlSearchParamsWithSelection, useData, Workbench, } from '@eclipse-sirius/sirius-components-core'; @@ -69,7 +73,7 @@ export const EditProjectView = () => { const routeMatch = useResolvedPath('.'); const { projectId, representationId } = useParams(); const { classes } = useEditProjectViewStyles(); - const [urlSearchParams]: [URLSearchParams, SetURLSearchParams] = useSearchParams(); + const [urlSearchParams, setUrlSearchParams]: [URLSearchParams, SetURLSearchParams] = useSearchParams(); const [{ value, context }, dispatch] = useMachine>( @@ -92,7 +96,9 @@ export const EditProjectView = () => { dispatch(selectRepresentationEvent); }; - const workbenchOnSelectionChanged = () => {}; + const workbenchOnSelectionChanged = (selection: Selection) => { + updateUrlSearchParamsWithSelection(selection, setUrlSearchParams); + }; useEffect(() => { let pathname: string = null; @@ -117,6 +123,8 @@ export const EditProjectView = () => { } }, [value, projectId, routeMatch, history, context.representation, representationId, urlSearchParams]); + updateSelectionBasedOnUrlSearchParamsIfNeeded(urlSearchParams); + let content: React.ReactNode = null; if (value === 'loading') { @@ -130,16 +138,7 @@ export const EditProjectView = () => { const { data: readOnlyPredicate } = useData(editProjectViewReadOnlyPredicateExtensionPoint); if (value === 'loaded' && context.project) { - const initialSelection: Selection = { - entries: context.representation - ? [ - { - id: context.representation.id, - kind: context.representation.kind, - }, - ] - : [], - }; + const initialSelection: Selection = createSelectionFromUrlSearchParams(urlSearchParams); const readOnly = readOnlyPredicate(context.project); const initialContextEntries: OmniboxContextEntry[] = [ From ba0a1d0df7a00ee787a217867b0268c68372467b Mon Sep 17 00:00:00 2001 From: Florent Latombe Date: Tue, 4 Feb 2025 11:07:10 +0100 Subject: [PATCH 4/4] [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>();