Skip to content

Commit

Permalink
[3980] Re-enable selection after the execution of a tool on diagrams
Browse files Browse the repository at this point in the history
This reverts commits 5fc00f3 and
14f4133 which themselves reverted the
initial frontend side of the feature, and provides an updated version
of the DiagramRenderer.tsx hook which should fix the issue.

Bug: #3980
Signed-off-by: Pierre-Charles David <[email protected]>
  • Loading branch information
pcdavid authored and AxelRICHARD committed Dec 11, 2024
1 parent 17ed587 commit 7f19700
Show file tree
Hide file tree
Showing 7 changed files with 197 additions and 79 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ Specifiers are also encouraged to implement their own `IRestDataVersionPayloadSe
- https://github.com/eclipse-sirius/sirius-web/issues/4210[#4210] [table] Add the ability to fork the studio used by a table representation
- https://github.com/eclipse-sirius/sirius-web/issues/4273[#4273] [table] Add support to column filtering in table

- https://github.com/eclipse-sirius/sirius-web/issues/3980[#3980] Add the ability to select newly created nodes.
The backend part (the ability to define an _Elements to Select Expression_ on diagram tools) was added in Sirius Web 2024.11.0 but the frontend did not apply the requested selection.
This is now fixed.

=== Improvements

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
ReactFlow,
ReactFlowProps,
applyNodeChanges,
useStoreApi,
} from '@xyflow/react';
import React, { MouseEvent as ReactMouseEvent, memo, useCallback, useContext, useEffect, useMemo, useRef } from 'react';
import { DiagramContext } from '../contexts/DiagramContext';
Expand Down Expand Up @@ -121,24 +122,76 @@ export const DiagramRenderer = memo(({ diagramRefreshedEventPayload }: DiagramRe

const { nodeConverters } = useContext<NodeTypeContextValue>(NodeTypeContext);

const { setSelection } = useSelection();
const { selection, setSelection } = useSelection();
const { edgeType, setEdgeType } = useEdgeType();

useInitialFitToScreen();

const store = useStoreApi<Node<NodeData>, Edge<EdgeData>>();
useEffect(() => {
const { diagram, cause } = diagramRefreshedEventPayload;
const convertedDiagram: Diagram = convertDiagram(diagram, nodeConverters, diagramDescription, edgeType);

const selectedNodeIds = nodes.filter((node) => node.selected).map((node) => node.id);
const selectedEdgeIds = edges.filter((edge) => edge.selected).map((edge) => edge.id);
if (cause === 'layout') {
convertedDiagram.nodes
.filter((node) => selectedNodeIds.includes(node.id))
.forEach((node) => (node.selected = true));
convertedDiagram.edges
.filter((edge) => selectedEdgeIds.includes(edge.id))
.forEach((edge) => (edge.selected = true));
const diagramElementIds: string[] = [
...getNodes().map((node) => node.data.targetObjectId),
...getEdges().map((edge) => edge.data?.targetObjectId ?? ''),
];

const selectionDiagramEntryIds = selection.entries
.map((entry) => entry.id)
.filter((id) => diagramElementIds.includes(id))
.sort((id1: string, id2: string) => id1.localeCompare(id2));
const selectedDiagramElementIds = [
...new Set(
[...getNodes(), ...getEdges()]
.filter((element) => element.selected)
.map((element) => element.data?.targetObjectId ?? '')
),
];
selectedDiagramElementIds.sort((id1: string, id2: string) => id1.localeCompare(id2));

const semanticElementsViews: Map<string, string[]> = new Map();
[...getNodes(), ...getEdges()].forEach((element) => {
const viewId = element.id;
const semanticElementId = element.data?.targetObjectId ?? '';
if (!semanticElementsViews.has(semanticElementId)) {
semanticElementsViews.set(semanticElementId, [viewId]);
} else {
semanticElementsViews.get(semanticElementId)?.push(viewId);
}
});

// For each selected semantic element which appears on the diagram,
// determine which of its views should be selected.
const viewsToSelect: Map<string, string[]> = new Map();
const previouslySelectedViews = [...getNodes(), ...getEdges()].filter((element) => element.selected);
for (var semanticElementId of selectionDiagramEntryIds) {
const allRelatedViews = semanticElementsViews.get(semanticElementId) || [];
const alreadySelectedViews = allRelatedViews.filter(
(viewId) => !!previouslySelectedViews.find((view: Node<NodeData> | Edge<EdgeData>) => view.id === viewId)
);
if (alreadySelectedViews.length > 0) {
// Keep the previous graphical selection if there was one that is still valid
viewsToSelect.set(semanticElementId, alreadySelectedViews);
} else if (allRelatedViews.length > 0 && allRelatedViews[0]) {
// Otherwise select a single view among the candidates.
// Given the order we receive the views from the backend, if there
// are multiple candidates in the same view hierarchy, the parent
// will appear first, and it's the "main" view we want to select.
viewsToSelect.set(semanticElementId, [allRelatedViews[0]]);
}
}

// 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)),
}));

setEdges(convertedDiagram.edges);
setNodes(convertedDiagram.nodes);
Expand All @@ -148,12 +201,27 @@ export const DiagramRenderer = memo(({ diagramRefreshedEventPayload }: DiagramRe
edges,
};
layout(previousDiagram, convertedDiagram, diagramRefreshedEventPayload.referencePosition, (laidOutDiagram) => {
laidOutDiagram.nodes
.filter((node) => selectedNodeIds.includes(node.id))
.forEach((node) => (node.selected = true));
laidOutDiagram.edges
.filter((edge) => selectedEdgeIds.includes(edge.id))
.forEach((edge) => (edge.selected = true));
const { nodeLookup, edgeLookup } = store.getState();

laidOutDiagram.nodes = laidOutDiagram.nodes.map((node) => {
if (nodeLookup.get(node.id)) {
return {
...node,
selected: !!nodeLookup.get(node.id)?.selected,
};
}
return node;
});

laidOutDiagram.edges = laidOutDiagram.edges.map((edge) => {
if (edgeLookup.get(edge.id)) {
return {
...edge,
selected: !!edgeLookup.get(edge.id)?.selected,
};
}
return edge;
});

setEdges(laidOutDiagram.edges);
setNodes(laidOutDiagram.nodes);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
*******************************************************************************/

import { gql, useMutation, useQuery } from '@apollo/client';
import { IconOverlay, useMultiToast } from '@eclipse-sirius/sirius-components-core';
import { IconOverlay, useMultiToast, useSelection } from '@eclipse-sirius/sirius-components-core';
import ListItemIcon from '@mui/material/ListItemIcon';
import Menu from '@mui/material/Menu';
import MenuItem from '@mui/material/MenuItem';
Expand Down Expand Up @@ -118,6 +118,7 @@ const ConnectorContextualMenuComponent = memo(({}: ConnectorContextualMenuProps)
const { connection, position, onConnectorContextualMenuClose, addTempConnectionLine, removeTempConnectionLine } =
useConnector();
const { addMessages, addErrorMessage } = useMultiToast();
const { setSelection } = useSelection();

const { showDialog, isOpened } = useDialog();

Expand Down Expand Up @@ -226,6 +227,10 @@ const ConnectorContextualMenuComponent = memo(({}: ConnectorContextualMenuProps)
addMessages(payload.messages);
}
if (isSuccessPayload(payload)) {
const { newSelection } = payload;
if (newSelection?.entries.length ?? 0 > 0) {
setSelection(newSelection);
}
addMessages(payload.messages);
onShouldConnectorContextualMenuClose();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,6 @@ export interface GQLWorkbenchSelection {

export interface GQLWorkbenchSelectionEntry {
id: string;
label: string;
kind: string;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
*******************************************************************************/

import { gql, useMutation, useQuery } from '@apollo/client';
import { useDeletionConfirmationDialog, useMultiToast } from '@eclipse-sirius/sirius-components-core';
import { useDeletionConfirmationDialog, useMultiToast, useSelection } from '@eclipse-sirius/sirius-components-core';
import { Edge, Node, useStoreApi } from '@xyflow/react';
import { useCallback, useContext, useEffect } from 'react';
import { DiagramContext } from '../../contexts/DiagramContext';
Expand Down Expand Up @@ -98,6 +98,12 @@ const invokeSingleClickOnDiagramElementToolMutation = gql`
invokeSingleClickOnDiagramElementTool(input: $input) {
__typename
... on InvokeSingleClickOnDiagramElementToolSuccessPayload {
newSelection {
entries {
id
kind
}
}
messages {
body
level
Expand Down Expand Up @@ -177,6 +183,7 @@ export const usePalette = ({
const { addErrorMessage, addMessages } = useMultiToast();
const { showDeletionConfirmation } = useDeletionConfirmationDialog();
const { showDialog } = useDialog();
const { setSelection } = useSelection();

const { data: paletteData, error: paletteError } = useQuery<GQLGetToolSectionsData, GQLGetToolSectionsVariables>(
getPaletteQuery,
Expand Down Expand Up @@ -228,6 +235,10 @@ export const usePalette = ({
if (data) {
const { invokeSingleClickOnDiagramElementTool } = data;
if (isInvokeSingleClickSuccessPayload(invokeSingleClickOnDiagramElementTool)) {
const { newSelection } = invokeSingleClickOnDiagramElementTool;
if (newSelection?.entries.length ?? 0 > 0) {
setSelection(newSelection);
}
addMessages(invokeSingleClickOnDiagramElementTool.messages);
}
if (isErrorPayload(invokeSingleClickOnDiagramElementTool)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,6 @@ export interface GQLWorkbenchSelection {

export interface GQLWorkbenchSelectionEntry {
id: string;
label: string;
kind: string;
}

Expand Down
Loading

0 comments on commit 7f19700

Please sign in to comment.