Skip to content

Commit fa1f8e7

Browse files
committed
[6338] Move the execution of connector tools in a dedicated hook
Bug: #6338 Signed-off-by: Michaël Charfadi <michael.charfadi@obeosoft.com>
1 parent 523f186 commit fa1f8e7

File tree

8 files changed

+301
-174
lines changed

8 files changed

+301
-174
lines changed

CHANGELOG.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ The new template for functional specification is located in `doc/iterations/YYYY
4545
The technical specifications will now rely on a new architectural decision record template located in `doc/adrs/adr.adoc`.
4646
This new template for ADRs will have several additional requirements compared to the previous one.
4747
It will require contributors to specify the persons involved in the decision process, to details the qualities expected of the solution, to list the various options considered, to clarify why a specific option has been selected, its advantages and drawbacks, which steps will be taken for the implementation among other things.
48+
- https://github.com/eclipse-sirius/sirius-web/issues/6338[#6338] [diagram] Move the execution of connector tools in a dedicated hook
4849

4950

5051

packages/diagrams/frontend/sirius-components-diagrams/src/renderer/connector/ConnectorContext.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2023, 2024 Obeo.
2+
* Copyright (c) 2023, 2026 Obeo.
33
* This program and the accompanying materials
44
* are made available under the terms of the Eclipse Public License v2.0
55
* which accompanies this distribution, and is available at
@@ -22,7 +22,7 @@ import { GQLNodeDescription } from './useConnector.types';
2222

2323
const defaultValue: ConnectorContextValue = {
2424
connection: null,
25-
position: null,
25+
position: { x: 0, y: 0 },
2626
candidates: [],
2727
isNewConnection: false,
2828
setConnection: () => {},
@@ -37,7 +37,7 @@ export const ConnectorContext = React.createContext<ConnectorContextValue>(defau
3737
export const ConnectorContextProvider = ({ children }: ConnectorContextProviderProps) => {
3838
const [state, setState] = useState<ConnectorContextProviderState>({
3939
connection: null,
40-
position: null,
40+
position: { x: 0, y: 0 },
4141
candidates: [],
4242
isNewConnection: false,
4343
});

packages/diagrams/frontend/sirius-components-diagrams/src/renderer/connector/ConnectorContext.types.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2023, 2024 Obeo.
2+
* Copyright (c) 2023, 2026 Obeo.
33
* This program and the accompanying materials
44
* are made available under the terms of the Eclipse Public License v2.0
55
* which accompanies this distribution, and is available at
@@ -16,7 +16,7 @@ import { GQLNodeDescription } from './useConnector.types';
1616

1717
export interface ConnectorContextValue {
1818
connection: Connection | null;
19-
position: XYPosition | null;
19+
position: XYPosition;
2020
candidates: GQLNodeDescription[];
2121
isNewConnection: boolean;
2222
setConnection: (connection: Connection) => void;
@@ -32,7 +32,7 @@ export interface ConnectorContextProviderProps {
3232

3333
export interface ConnectorContextProviderState {
3434
connection: Connection | null;
35-
position: XYPosition | null;
35+
position: XYPosition;
3636
candidates: GQLNodeDescription[];
3737
isNewConnection: boolean;
3838
}

packages/diagrams/frontend/sirius-components-diagrams/src/renderer/connector/ConnectorContextualMenu.tsx

Lines changed: 16 additions & 162 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2023, 2025 Obeo.
2+
* Copyright (c) 2023, 2026 Obeo.
33
* This program and the accompanying materials
44
* are made available under the terms of the Eclipse Public License v2.0
55
* which accompanies this distribution, and is available at
@@ -11,37 +11,27 @@
1111
* Obeo - initial API and implementation
1212
*******************************************************************************/
1313

14-
import { gql, useMutation, useQuery } from '@apollo/client';
14+
import { gql, useQuery } from '@apollo/client';
1515
import { IconOverlay, useMultiToast } from '@eclipse-sirius/sirius-components-core';
1616
import ListItemIcon from '@mui/material/ListItemIcon';
1717
import Menu from '@mui/material/Menu';
1818
import MenuItem from '@mui/material/MenuItem';
1919
import Typography from '@mui/material/Typography';
20-
import { Edge, Node, useReactFlow, useStoreApi, XYPosition } from '@xyflow/react';
20+
import { Edge, Node, useReactFlow } from '@xyflow/react';
2121
import { memo, useContext, useEffect } from 'react';
2222
import { DiagramContext } from '../../contexts/DiagramContext';
2323
import { DiagramContextValue } from '../../contexts/DiagramContext.types';
24-
import { DiagramDialogVariable } from '../../dialog/DialogContextExtensionPoints.types';
25-
import { useDialog } from '../../dialog/useDialog';
2624
import { EdgeData, NodeData } from '../DiagramRenderer.types';
27-
import { isCursorNearCenterOfTheNode } from '../edge/EdgeLayout';
2825
import {
2926
ConnectorContextualMenuProps,
3027
GetConnectorToolsData,
3128
GetConnectorToolsVariables,
3229
GQLDiagramDescription,
33-
GQLErrorPayload,
34-
GQLInvokeSingleClickOnTwoDiagramElementsToolData,
35-
GQLInvokeSingleClickOnTwoDiagramElementsToolInput,
36-
GQLInvokeSingleClickOnTwoDiagramElementsToolPayload,
37-
GQLInvokeSingleClickOnTwoDiagramElementsToolSuccessPayload,
38-
GQLInvokeSingleClickOnTwoDiagramElementsToolVariables,
3930
GQLRepresentationDescription,
4031
GQLTool,
41-
GQLToolVariable,
4232
} from './ConnectorContextualMenu.types';
4333
import { useConnector } from './useConnector';
44-
import { GQLSingleClickOnTwoDiagramElementsTool } from './useConnector.types';
34+
import { useSingleClickOnTwoDiagramElementTool } from './useSingleClickOnTwoDiagramElementTool';
4535

4636
export const getConnectorToolsQuery = gql`
4737
query getConnectorTools(
@@ -74,56 +64,18 @@ export const getConnectorToolsQuery = gql`
7464
}
7565
`;
7666

77-
export const invokeSingleClickOnTwoDiagramElementsToolMutation = gql`
78-
mutation invokeSingleClickOnTwoDiagramElementsTool($input: InvokeSingleClickOnTwoDiagramElementsToolInput!) {
79-
invokeSingleClickOnTwoDiagramElementsTool(input: $input) {
80-
__typename
81-
... on InvokeSingleClickOnTwoDiagramElementsToolSuccessPayload {
82-
id
83-
newSelection {
84-
entries {
85-
id
86-
}
87-
}
88-
messages {
89-
body
90-
level
91-
}
92-
}
93-
... on ErrorPayload {
94-
messages {
95-
body
96-
level
97-
}
98-
}
99-
}
100-
}
101-
`;
102-
10367
const isDiagramDescription = (
10468
representationDescription: GQLRepresentationDescription
10569
): representationDescription is GQLDiagramDescription => representationDescription.__typename === 'DiagramDescription';
10670

107-
const isErrorPayload = (payload: GQLInvokeSingleClickOnTwoDiagramElementsToolPayload): payload is GQLErrorPayload =>
108-
payload.__typename === 'ErrorPayload';
109-
const isSuccessPayload = (
110-
payload: GQLInvokeSingleClickOnTwoDiagramElementsToolPayload
111-
): payload is GQLInvokeSingleClickOnTwoDiagramElementsToolSuccessPayload =>
112-
payload.__typename === 'InvokeSingleClickOnTwoDiagramElementsToolSuccessPayload';
113-
114-
const isSingleClickOnTwoDiagramElementsTool = (tool: GQLTool): tool is GQLSingleClickOnTwoDiagramElementsTool =>
115-
tool.__typename === 'SingleClickOnTwoDiagramElementsTool';
116-
11771
const ConnectorContextualMenuComponent = memo(({}: ConnectorContextualMenuProps) => {
118-
const { editingContextId, diagramId, registerPostToolSelection } = useContext<DiagramContextValue>(DiagramContext);
119-
const store = useStoreApi<Node<NodeData>, Edge<EdgeData>>();
72+
const { editingContextId, diagramId } = useContext<DiagramContextValue>(DiagramContext);
12073
const { connection, position, onConnectorContextualMenuClose, addTempConnectionLine, removeTempConnectionLine } =
12174
useConnector();
12275
const { addMessages, addErrorMessage } = useMultiToast();
123-
124-
const { showDialog, isOpened } = useDialog();
125-
126-
const { getNodes, screenToFlowPosition } = useReactFlow<Node<NodeData>, Edge<EdgeData>>();
76+
const { screenToFlowPosition } = useReactFlow<Node<NodeData>, Edge<EdgeData>>();
77+
const { invokeConnectorTool, data: invokeSingleClickOnTwoDiagramElementToolCalled } =
78+
useSingleClickOnTwoDiagramElementTool();
12779

12880
const connectionSource: HTMLElement | null = connection
12981
? document.querySelector(`[data-id="${connection.source}"]`)
@@ -147,116 +99,12 @@ const ConnectorContextualMenuComponent = memo(({}: ConnectorContextualMenuProps)
14799
skip: !connectionSource || !connectionTarget,
148100
});
149101

150-
const invokeOpenSelectionDialog = (tool: GQLSingleClickOnTwoDiagramElementsTool) => {
151-
const onConfirm = (variables: GQLToolVariable[]) => {
152-
invokeToolMutation(tool, variables);
153-
};
154-
155-
const onClose = () => {
156-
onShouldConnectorContextualMenuClose();
157-
};
158-
159-
const sourceNode = getNodes().find((node) => node.id === sourceDiagramElementId);
160-
const targetNode = getNodes().find((node) => node.id === targetDiagramElementId);
161-
if (sourceNode && targetNode) {
162-
const variables: DiagramDialogVariable[] = [
163-
{ name: 'targetObjectId', value: sourceNode.data.targetObjectId },
164-
{ name: 'sourceDiagramElementTargetObjectId', value: sourceNode.data.targetObjectId },
165-
{ name: 'targetDiagramElementTargetObjectId', value: targetNode.data.targetObjectId },
166-
];
167-
showDialog(tool.dialogDescriptionId, variables, onConfirm, onClose);
168-
}
169-
};
170-
171102
useEffect(() => {
172103
if (error) {
173104
addErrorMessage(error.message);
174105
}
175106
}, [error]);
176107

177-
const [
178-
invokeSingleClickOnTwoDiagramElementsTool,
179-
{
180-
data: invokeSingleClickOnTwoDiagramElementToolData,
181-
error: invokeSingleClickOnTwoDiagramElementToolError,
182-
called: invokeSingleClickOnTwoDiagramElementToolCalled,
183-
reset,
184-
},
185-
] = useMutation<
186-
GQLInvokeSingleClickOnTwoDiagramElementsToolData,
187-
GQLInvokeSingleClickOnTwoDiagramElementsToolVariables
188-
>(invokeSingleClickOnTwoDiagramElementsToolMutation);
189-
190-
const invokeTool = (tool: GQLTool) => {
191-
if (isSingleClickOnTwoDiagramElementsTool(tool)) {
192-
if (tool.dialogDescriptionId) {
193-
if (!isOpened) {
194-
invokeOpenSelectionDialog(tool);
195-
}
196-
} else {
197-
invokeToolMutation(tool, []);
198-
}
199-
}
200-
};
201-
202-
const invokeToolMutation = (tool: GQLTool, variables: GQLToolVariable[]) => {
203-
let targetHandlePosition: XYPosition = { x: 0, y: 0 };
204-
if (position) {
205-
targetHandlePosition = screenToFlowPosition({ x: position.x, y: position.y });
206-
}
207-
const target = store.getState().nodeLookup.get(targetDiagramElementId);
208-
if (target && position) {
209-
const isNearCenter = isCursorNearCenterOfTheNode(target, {
210-
x: targetHandlePosition.x,
211-
y: targetHandlePosition.y,
212-
});
213-
214-
if (isNearCenter) {
215-
targetHandlePosition = { x: 0, y: 0 };
216-
}
217-
}
218-
219-
const input: GQLInvokeSingleClickOnTwoDiagramElementsToolInput = {
220-
id: crypto.randomUUID(),
221-
editingContextId,
222-
representationId: diagramId,
223-
diagramSourceElementId: sourceDiagramElementId,
224-
diagramTargetElementId: targetDiagramElementId,
225-
toolId: tool.id,
226-
sourcePositionX: 0,
227-
sourcePositionY: 0,
228-
targetPositionX: targetHandlePosition.x,
229-
targetPositionY: targetHandlePosition.y,
230-
variables,
231-
};
232-
invokeSingleClickOnTwoDiagramElementsTool({ variables: { input } });
233-
};
234-
235-
const onShouldConnectorContextualMenuClose = () => {
236-
onConnectorContextualMenuClose();
237-
reset();
238-
};
239-
240-
useEffect(() => {
241-
if (invokeSingleClickOnTwoDiagramElementToolData) {
242-
const payload = invokeSingleClickOnTwoDiagramElementToolData.invokeSingleClickOnTwoDiagramElementsTool;
243-
if (isErrorPayload(payload)) {
244-
addMessages(payload.messages);
245-
}
246-
if (isSuccessPayload(payload)) {
247-
const { id, newSelection } = payload;
248-
if (newSelection?.entries.length ?? 0 > 0) {
249-
registerPostToolSelection(id, newSelection);
250-
}
251-
addMessages(payload.messages);
252-
onShouldConnectorContextualMenuClose();
253-
}
254-
}
255-
if (invokeSingleClickOnTwoDiagramElementToolError?.message) {
256-
addErrorMessage(invokeSingleClickOnTwoDiagramElementToolError.message);
257-
}
258-
}, [invokeSingleClickOnTwoDiagramElementToolData, invokeSingleClickOnTwoDiagramElementToolError]);
259-
260108
const connectorTools: GQLTool[] = [];
261109
const representationDescription: GQLRepresentationDescription | null | undefined =
262110
data?.viewer.editingContext?.representation?.description;
@@ -272,6 +120,7 @@ const ConnectorContextualMenuComponent = memo(({}: ConnectorContextualMenuProps)
272120

273121
useEffect(() => {
274122
if (!loading && connection && data && connectorTools.length === 0) {
123+
onConnectorContextualMenuClose();
275124
addMessages([{ body: 'No edge found between source and target selected', level: 'WARNING' }]);
276125
}
277126
}, [loading, data, connection, connectorTools.length]);
@@ -284,7 +133,12 @@ const ConnectorContextualMenuComponent = memo(({}: ConnectorContextualMenuProps)
284133
if (!invokeSingleClickOnTwoDiagramElementToolCalled && connectorTools.length === 1 && connectorTools[0]) {
285134
invokeTool(connectorTools[0]);
286135
}
287-
}, [connectorTools]);
136+
}, [connectorTools.length]);
137+
138+
const invokeTool = (tool: GQLTool) => {
139+
const { x: cursorPositionX, y: cursorPositionY } = screenToFlowPosition({ x: position.x, y: position.y });
140+
invokeConnectorTool(tool, sourceDiagramElementId, targetDiagramElementId, cursorPositionX, cursorPositionY);
141+
};
288142

289143
if (!data || connectorTools.length <= 1) {
290144
return null;
@@ -293,7 +147,7 @@ const ConnectorContextualMenuComponent = memo(({}: ConnectorContextualMenuProps)
293147
return (
294148
<Menu
295149
open={!!connection}
296-
onClose={onShouldConnectorContextualMenuClose}
150+
onClose={onConnectorContextualMenuClose}
297151
anchorEl={connectionTarget}
298152
anchorReference="anchorPosition"
299153
data-testid="connectorContextualMenu"

packages/diagrams/frontend/sirius-components-diagrams/src/renderer/connector/useConnector.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2023, 2025 Obeo.
2+
* Copyright (c) 2023, 2026 Obeo.
33
* This program and the accompanying materials
44
* are made available under the terms of the Eclipse Public License v2.0
55
* which accompanies this distribution, and is available at
@@ -113,8 +113,6 @@ export const useConnector = (): UseConnectorValue => {
113113
[]
114114
);
115115

116-
const onConnectorContextualMenuClose = () => resetConnection();
117-
118116
const onConnectionStartElementClick = useCallback((event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
119117
if (event.button === 0) {
120118
setIsNewConnection(true);
@@ -198,7 +196,7 @@ export const useConnector = (): UseConnectorValue => {
198196
onConnect,
199197
onConnectStart,
200198
onConnectEnd,
201-
onConnectorContextualMenuClose,
199+
onConnectorContextualMenuClose: resetConnection,
202200
onConnectionStartElementClick,
203201
addTempConnectionLine,
204202
removeTempConnectionLine,

packages/diagrams/frontend/sirius-components-diagrams/src/renderer/connector/useConnector.types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2023, 2025 Obeo.
2+
* Copyright (c) 2023, 2026 Obeo.
33
* This program and the accompanying materials
44
* are made available under the terms of the Eclipse Public License v2.0
55
* which accompanies this distribution, and is available at
@@ -23,7 +23,7 @@ export interface UseConnectorValue {
2323
addTempConnectionLine: () => void;
2424
removeTempConnectionLine: () => void;
2525
connection: Connection | null;
26-
position: XYPosition | null;
26+
position: XYPosition;
2727
isConnectionInProgress: () => boolean;
2828
isReconnectionInProgress: () => boolean;
2929
candidates: GQLNodeDescription[];

0 commit comments

Comments
 (0)