Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[2572] Use dynamic handles #2573

Merged
merged 1 commit into from
Nov 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ The new implementation of `IEditService`, named `ComposedEditService`, tries fir
- https://github.com/eclipse-sirius/sirius-web/issues/2555[#2555] [diagram] Add distinct edge creation handles (only visible when a node is selected).
- https://github.com/eclipse-sirius/sirius-web/issues/2559[#2559] [diagram] Allow to target the whole node when creating or reconnecting an edge.
- https://github.com/eclipse-sirius/sirius-web/issues/2235[#2235] [diagram] Reduce the amount of code in DiagramRenderer

- https://github.com/eclipse-sirius/sirius-web/issues/2572[#2572] [diagram] Each edges now have a dedicated handle.

== v2023.10.0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
*******************************************************************************/
import { Node } from 'reactflow';
import { GQLNodeDescription } from '../graphql/query/nodeDescriptionFragment.types';
import { GQLEdge } from '../graphql/subscription/edgeFragment.types';
import { GQLNode, GQLNodeStyle } from '../graphql/subscription/nodeFragment.types';

export interface IConvertEngine {
Expand All @@ -29,6 +30,7 @@ export interface INodeConverterHandler {
handle(
convertEngine: IConvertEngine,
gqlNode: GQLNode<GQLNodeStyle>,
gqlEdges: GQLEdge[],
parentNode: GQLNode<GQLNodeStyle> | null,
isBorderNode: boolean,
nodes: Node[],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,18 @@
*******************************************************************************/
import { Node, XYPosition } from 'reactflow';
import { GQLNodeDescription } from '../graphql/query/nodeDescriptionFragment.types';
import { GQLEdge } from '../graphql/subscription/edgeFragment.types';
import {
GQLIconLabelNodeStyle,
GQLNode,
GQLNodeStyle,
GQLViewModifier,
} from '../graphql/subscription/nodeFragment.types';
import { BorderNodePositon } from '../renderer/DiagramRenderer.types';
import { ConnectionHandle } from '../renderer/handles/ConnectionHandles.types';
import { IconLabelNodeData } from '../renderer/node/IconsLabelNode.types';
import { convertLabelStyle } from './convertDiagram';
import { IConvertEngine, INodeConverterHandler } from './ConvertEngine.types';
import { convertLabelStyle } from './convertDiagram';

const defaultPosition: XYPosition = { x: 0, y: 0 };

Expand All @@ -43,6 +45,8 @@ const toIconLabelNode = (
labelEditable,
} = gqlNode;

const connectionHandles: ConnectionHandle[] = [];

const data: IconLabelNodeData = {
targetObjectId,
targetObjectLabel,
Expand All @@ -60,6 +64,7 @@ const toIconLabelNode = (
defaultWidth: gqlNode.defaultWidth,
defaultHeight: gqlNode.defaultHeight,
labelEditable: labelEditable,
connectionHandles,
};

if (insideLabel) {
Expand Down Expand Up @@ -98,6 +103,7 @@ export class IconLabelNodeConverterHandler implements INodeConverterHandler {
handle(
_convertEngine: IConvertEngine,
gqlNode: GQLNode<GQLIconLabelNodeStyle>,
_gqlEdges: GQLEdge[],
parentNode: GQLNode<GQLNodeStyle> | null,
isBorderNode: boolean,
nodes: Node[],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,24 @@
*******************************************************************************/
import { Node, XYPosition } from 'reactflow';
import { GQLNodeDescription } from '../graphql/query/nodeDescriptionFragment.types';
import { GQLEdge } from '../graphql/subscription/edgeFragment.types';
import { GQLImageNodeStyle, GQLNode, GQLNodeStyle, GQLViewModifier } from '../graphql/subscription/nodeFragment.types';
import { BorderNodePositon } from '../renderer/DiagramRenderer.types';
import { ConnectionHandle } from '../renderer/handles/ConnectionHandles.types';
import { ImageNodeData } from '../renderer/node/ImageNode.types';
import { IConvertEngine, INodeConverterHandler } from './ConvertEngine.types';
import { convertLabelStyle } from './convertDiagram';
import { AlignmentMap } from './convertDiagram.types';
import { IConvertEngine, INodeConverterHandler } from './ConvertEngine.types';
import { convertHandles } from './convertHandles';

const defaultPosition: XYPosition = { x: 0, y: 0 };

const toImageNode = (
gqlNode: GQLNode<GQLImageNodeStyle>,
gqlParentNode: GQLNode<GQLNodeStyle> | null,
nodeDescription: GQLNodeDescription | undefined,
isBorderNode: boolean
isBorderNode: boolean,
gqlEdges: GQLEdge[]
): Node<ImageNodeData> => {
const {
targetObjectId,
Expand All @@ -39,6 +43,8 @@ const toImageNode = (
labelEditable,
} = gqlNode;

const connectionHandles: ConnectionHandle[] = convertHandles(gqlNode, gqlEdges);

const data: ImageNodeData = {
targetObjectId,
targetObjectLabel,
Expand All @@ -55,6 +61,7 @@ const toImageNode = (
borderNodePosition: isBorderNode ? BorderNodePositon.WEST : null,
labelEditable,
positionDependentRotation: style.positionDependentRotation,
connectionHandles,
};

if (insideLabel) {
Expand Down Expand Up @@ -109,13 +116,14 @@ export class ImageNodeConverterHandler implements INodeConverterHandler {
handle(
convertEngine: IConvertEngine,
gqlNode: GQLNode<GQLImageNodeStyle>,
gqlEdges: GQLEdge[],
parentNode: GQLNode<GQLNodeStyle> | null,
isBorderNode: boolean,
nodes: Node[],
nodeDescriptions: GQLNodeDescription[]
) {
const nodeDescription = nodeDescriptions.find((description) => description.id === gqlNode.descriptionId);
nodes.push(toImageNode(gqlNode, parentNode, nodeDescription, isBorderNode));
nodes.push(toImageNode(gqlNode, parentNode, nodeDescription, isBorderNode, gqlEdges));
convertEngine.convertNodes(gqlNode.borderNodes ?? [], gqlNode, nodes, nodeDescriptions);
convertEngine.convertNodes(gqlNode.childNodes ?? [], gqlNode, nodes, nodeDescriptions);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,29 @@
*******************************************************************************/
import { Node, XYPosition } from 'reactflow';
import { GQLNodeDescription } from '../graphql/query/nodeDescriptionFragment.types';
import { GQLEdge } from '../graphql/subscription/edgeFragment.types';
import {
GQLNode,
GQLNodeStyle,
GQLRectangularNodeStyle,
GQLViewModifier,
} from '../graphql/subscription/nodeFragment.types';
import { BorderNodePositon } from '../renderer/DiagramRenderer.types';
import { ConnectionHandle } from '../renderer/handles/ConnectionHandles.types';
import { ListNodeData } from '../renderer/node/ListNode.types';
import { IConvertEngine, INodeConverterHandler } from './ConvertEngine.types';
import { convertLabelStyle } from './convertDiagram';
import { AlignmentMap } from './convertDiagram.types';
import { IConvertEngine, INodeConverterHandler } from './ConvertEngine.types';
import { convertHandles } from './convertHandles';

const defaultPosition: XYPosition = { x: 0, y: 0 };

const toListNode = (
gqlNode: GQLNode<GQLRectangularNodeStyle>,
gqlParentNode: GQLNode<GQLNodeStyle> | null,
nodeDescription: GQLNodeDescription | undefined,
isBorderNode: boolean
isBorderNode: boolean,
gqlEdges: GQLEdge[]
): Node<ListNodeData> => {
const {
targetObjectId,
Expand All @@ -44,6 +48,8 @@ const toListNode = (
labelEditable,
} = gqlNode;

const connectionHandles: ConnectionHandle[] = convertHandles(gqlNode, gqlEdges);

const data: ListNodeData = {
targetObjectId,
targetObjectLabel,
Expand All @@ -62,6 +68,7 @@ const toListNode = (
faded: state === GQLViewModifier.Faded,
labelEditable,
nodeDescription,
connectionHandles,
defaultWidth: gqlNode.defaultWidth,
defaultHeight: gqlNode.defaultHeight,
};
Expand Down Expand Up @@ -123,13 +130,14 @@ export class ListNodeConverterHandler implements INodeConverterHandler {
handle(
convertEngine: IConvertEngine,
gqlNode: GQLNode<GQLRectangularNodeStyle>,
gqlEdges: GQLEdge[],
parentNode: GQLNode<GQLNodeStyle> | null,
isBorderNode: boolean,
nodes: Node[],
nodeDescriptions: GQLNodeDescription[]
) {
const nodeDescription = nodeDescriptions.find((description) => description.id === gqlNode.descriptionId);
nodes.push(toListNode(gqlNode, parentNode, nodeDescription, isBorderNode));
nodes.push(toListNode(gqlNode, parentNode, nodeDescription, isBorderNode, gqlEdges));
convertEngine.convertNodes(gqlNode.borderNodes ?? [], gqlNode, nodes, nodeDescriptions);
convertEngine.convertNodes(gqlNode.childNodes ?? [], gqlNode, nodes, nodeDescriptions);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,29 @@
*******************************************************************************/
import { Node, XYPosition } from 'reactflow';
import { GQLNodeDescription } from '../graphql/query/nodeDescriptionFragment.types';
import { GQLEdge } from '../graphql/subscription/edgeFragment.types';
import {
GQLNode,
GQLNodeStyle,
GQLRectangularNodeStyle,
GQLViewModifier,
} from '../graphql/subscription/nodeFragment.types';
import { BorderNodePositon } from '../renderer/DiagramRenderer.types';
import { ConnectionHandle } from '../renderer/handles/ConnectionHandles.types';
import { RectangularNodeData } from '../renderer/node/RectangularNode.types';
import { IConvertEngine, INodeConverterHandler } from './ConvertEngine.types';
import { convertLabelStyle } from './convertDiagram';
import { AlignmentMap } from './convertDiagram.types';
import { IConvertEngine, INodeConverterHandler } from './ConvertEngine.types';
import { convertHandles } from './convertHandles';

const defaultPosition: XYPosition = { x: 0, y: 0 };

const toRectangularNode = (
gqlNode: GQLNode<GQLRectangularNodeStyle>,
gqlParentNode: GQLNode<GQLNodeStyle> | null,
nodeDescription: GQLNodeDescription | undefined,
isBorderNode: boolean
isBorderNode: boolean,
gqlEdges: GQLEdge[]
): Node<RectangularNodeData> => {
const {
targetObjectId,
Expand All @@ -44,6 +48,8 @@ const toRectangularNode = (
labelEditable,
} = gqlNode;

const connectionHandles: ConnectionHandle[] = convertHandles(gqlNode, gqlEdges);

const data: RectangularNodeData = {
targetObjectId,
targetObjectLabel,
Expand All @@ -65,6 +71,7 @@ const toRectangularNode = (
isBorderNode: isBorderNode,
borderNodePosition: isBorderNode ? BorderNodePositon.EAST : null,
labelEditable,
connectionHandles,
};

if (insideLabel) {
Expand Down Expand Up @@ -124,13 +131,14 @@ export class RectangleNodeConverterHandler implements INodeConverterHandler {
handle(
convertEngine: IConvertEngine,
gqlNode: GQLNode<GQLRectangularNodeStyle>,
gqlEdges: GQLEdge[],
parentNode: GQLNode<GQLNodeStyle> | null,
isBorderNode: boolean,
nodes: Node[],
nodeDescriptions: GQLNodeDescription[]
) {
const nodeDescription = nodeDescriptions.find((description) => description.id === gqlNode.descriptionId);
nodes.push(toRectangularNode(gqlNode, parentNode, nodeDescription, isBorderNode));
nodes.push(toRectangularNode(gqlNode, parentNode, nodeDescription, isBorderNode, gqlEdges));
convertEngine.convertNodes(gqlNode.borderNodes ?? [], gqlNode, nodes, nodeDescriptions);
convertEngine.convertNodes(gqlNode.childNodes ?? [], gqlNode, nodes, nodeDescriptions);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ export const convertDiagram = (
if (nodeConverterHandler) {
const isBorderNode: boolean = !!parentNode?.borderNodes?.map((borderNode) => borderNode.id).includes(node.id);

nodeConverterHandler.handle(this, node, parentNode, isBorderNode, nodes, nodeDescriptions);
nodeConverterHandler.handle(this, node, gqlDiagram.edges, parentNode, isBorderNode, nodes, nodeDescriptions);
}
});
},
Expand All @@ -125,8 +125,10 @@ export const convertDiagram = (

const nodeId2Depth = new Map<string, number>();
nodes.forEach((node) => nodeId2Depth.set(node.id, nodeDepth(nodeId2node, node.id)));

let usedHandles: string[] = [];
const edges: Edge[] = gqlDiagram.edges.map((gqlEdge) => {
const sourceNode: Node<NodeData> | undefined = nodeId2node.get(gqlEdge.sourceId);
const targetNode: Node<NodeData> | undefined = nodeId2node.get(gqlEdge.targetId);
const data: MultiLabelEdgeData = {
targetObjectId: gqlEdge.targetObjectId,
targetObjectKind: gqlEdge.targetObjectKind,
Expand All @@ -145,6 +147,18 @@ export const convertDiagram = (
data.endLabel = convertEdgeLabel(gqlEdge.endLabel);
}

const sourceHandle = sourceNode?.data.connectionHandles
.filter((connectionHandle) => connectionHandle.type === 'source')
.find((connectionHandle) => !usedHandles.find((usedHandle) => usedHandle === connectionHandle.id));

const targetHandle = targetNode?.data.connectionHandles
.filter((connectionHandle) => connectionHandle.type === 'target')
.find((connectionHandle) => !usedHandles.find((usedHandle) => usedHandle === connectionHandle.id));

if (sourceHandle?.id && targetHandle?.id) {
usedHandles.push(sourceHandle?.id, targetHandle.id);
}

return {
id: gqlEdge.id,
type: 'multiLabelEdge',
Expand All @@ -159,6 +173,10 @@ export const convertDiagram = (
},
data,
hidden: gqlEdge.state === GQLViewModifier.Hidden,
sourceHandle: sourceHandle?.id,
targetHandle: targetHandle?.id,
sourceNode: sourceNode,
targetNode: targetNode,
};
});

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*******************************************************************************
* Copyright (c) 2023 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 { Position } from 'reactflow';
import { GQLEdge } from '../graphql/subscription/edgeFragment.types';
import { GQLNode, GQLNodeStyle } from '../graphql/subscription/nodeFragment.types';
import { ConnectionHandle } from '../renderer/handles/ConnectionHandles.types';

export const convertHandles = (gqlNode: GQLNode<GQLNodeStyle>, gqlEdges: GQLEdge[]): ConnectionHandle[] => {
const connectionHandles: ConnectionHandle[] = [];
let sourceHandlesCounter = 0;
let targetHandlesCounter = 0;
gqlEdges.forEach((edge) => {
if (edge.sourceId === gqlNode.id) {
connectionHandles.push({
id: `handle--source--${gqlNode.id}--${sourceHandlesCounter}`,
nodeId: gqlNode.id,
position: Position.Right,
type: 'source',
});
sourceHandlesCounter += 1;
}

if (edge.targetId === gqlNode.id) {
connectionHandles.push({
id: `handle--target--${gqlNode.id}--${targetHandlesCounter}`,
nodeId: gqlNode.id,
position: Position.Left,
type: 'target',
});
targetHandlesCounter += 1;
}
});
connectionHandles.push({
id: `handle--source--${gqlNode.id}--${sourceHandlesCounter}`,
nodeId: gqlNode.id,
position: Position.Right,
type: 'source',
});

connectionHandles.push({
id: `handle--target--${gqlNode.id}--${targetHandlesCounter}`,
nodeId: gqlNode.id,
position: Position.Left,
type: 'target',
});

return connectionHandles;
};
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,22 @@ export type { NodeTypeContextValue } from './contexts/NodeContext.types';
export type { IConvertEngine, INodeConverterHandler } from './converter/ConvertEngine.types';
export { convertLabelStyle } from './converter/convertDiagram';
export { AlignmentMap } from './converter/convertDiagram.types';
export { convertHandles } from './converter/convertHandles';
export type { GQLNodeDescription } from './graphql/query/nodeDescriptionFragment.types';
export type { GQLEdge } from './graphql/subscription/edgeFragment.types';
export { GQLViewModifier } from './graphql/subscription/nodeFragment.types';
export type { GQLNode, GQLNodeStyle, GraphQLNodeStyleFragment } from './graphql/subscription/nodeFragment.types';
export type { GQLNodeDescription } from './graphql/query/nodeDescriptionFragment.types';
export { BorderNodePositon } from './renderer/DiagramRenderer.types';
export type { Diagram, NodeData } from './renderer/DiagramRenderer.types';
export { Label } from './renderer/Label';
export { useConnector } from './renderer/connector/useConnector';
export { useDrop } from './renderer/drop/useDrop';
export { useDropNode } from './renderer/dropNode/useDropNode';
export { ConnectionCreationHandles } from './renderer/handles/ConnectionCreationHandles';
export { ConnectionHandles } from './renderer/handles/ConnectionHandles';
export type { ConnectionHandle } from './renderer/handles/ConnectionHandles.types';
export { ConnectionTargetHandle } from './renderer/handles/ConnectionTargetHandle';
export { useRefreshConnectionHandles } from './renderer/handles/useRefreshConnectionHandles';
export type { ILayoutEngine, INodeLayoutHandler } from './renderer/layout/LayoutEngine.types';
export * from './renderer/layout/layoutBorderNodes';
export * from './renderer/layout/layoutNode';
Expand Down