Skip to content

Commit

Permalink
[2034] Integrate workbench selection with react-flow on node
Browse files Browse the repository at this point in the history
Bug: #2034
Signed-off-by: Guillaume Coutable <[email protected]>
  • Loading branch information
gcoutable authored and sbegaudeau committed Jun 9, 2023
1 parent 8f285cc commit 734dfc5
Show file tree
Hide file tree
Showing 11 changed files with 143 additions and 25 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

=== Improvements

- https://github.com/eclipse-sirius/sirius-components/issues/2034[#2034] [diagram] Integrate the workbench selection in the react-flow prototype


== v2023.6.0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,13 @@ import { RectangularNodeData } from '../renderer/RectangularNode.types';

const toRectangularNode = (gqlNode: GQLNode, gqlParentNode: GQLNode | null): Node<RectangularNodeData> => {
const style = gqlNode.style as GQLRectangularNodeStyle;
const { position, size } = gqlNode;
const { targetObjectId, targetObjectLabel, targetObjectKind, position, size } = gqlNode;
const labelStyle = gqlNode.label.style;

const data: RectangularNodeData = {
targetObjectId,
targetObjectLabel,
targetObjectKind,
style: {
backgroundColor: style.color,
borderColor: style.borderColor,
Expand Down Expand Up @@ -92,12 +95,12 @@ const toRectangularNode = (gqlNode: GQLNode, gqlParentNode: GQLNode | null): Nod

const toListNode = (gqlNode: GQLNode, gqlParentNode: GQLNode | null): Node<ListNodeData> => {
const style = gqlNode.style as GQLRectangularNodeStyle;
const { position } = gqlNode;
const labelStyle = gqlNode.label.style;

const listItems: ListItemData[] = (gqlNode.childNodes ?? []).map((gqlChildNode) => {
const { id } = gqlChildNode;
return {
id: gqlChildNode.id,
id,
label: {
text: gqlChildNode.label.text,
style: {},
Expand All @@ -111,7 +114,11 @@ const toListNode = (gqlNode: GQLNode, gqlParentNode: GQLNode | null): Node<ListN
};
});

const { targetObjectId, targetObjectLabel, targetObjectKind, position } = gqlNode;
const data: ListNodeData = {
targetObjectId,
targetObjectLabel,
targetObjectKind,
style: {
backgroundColor: style.color,
borderColor: style.borderColor,
Expand Down Expand Up @@ -182,14 +189,17 @@ const toListNode = (gqlNode: GQLNode, gqlParentNode: GQLNode | null): Node<ListN

const toImageNode = (gqlNode: GQLNode, gqlParentNode: GQLNode | null): Node<ImageNodeData> => {
const style = gqlNode.style as GQLImageNodeStyle;
const { position, size } = gqlNode;
const { targetObjectId, targetObjectLabel, targetObjectKind, position, size } = gqlNode;

const data: ImageNodeData = {
targetObjectId,
targetObjectKind,
targetObjectLabel,
imageURL: style.imageURL,
style: {
width: `${size.width}px`,
height: `${size.height}px`,
},
imageURL: style.imageURL,
};

const node: Node<ImageNodeData> = {
Expand Down Expand Up @@ -266,6 +276,11 @@ export const convertDiagram = (gqlDiagram: GQLDiagram): Diagram => {
});

return {
metadata: {
id: gqlDiagram.id,
label: gqlDiagram.metadata.label,
kind: gqlDiagram.metadata.kind,
},
nodes,
edges,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,21 @@
* Obeo - initial API and implementation
*******************************************************************************/

import { Selection, SelectionEntry } from '@eclipse-sirius/sirius-components-core';
import { useEffect } from 'react';
import {
EdgeChange,
NodeChange,
NodeSelectionChange,
NodeTypes,
OnEdgesChange,
OnNodesChange,
ReactFlow,
useEdgesState,
useNodesState,
useStoreApi,
} from 'reactflow';
import { DiagramRendererProps } from './DiagramRenderer.types';
import { DiagramRendererProps, NodeData } from './DiagramRenderer.types';
import { ImageNode } from './ImageNode';
import { ListNode } from './ListNode';
import { RectangularNode } from './RectangularNode';
Expand All @@ -35,7 +38,10 @@ const nodeTypes: NodeTypes = {
listNode: ListNode,
};

export const DiagramRenderer = ({ diagram }: DiagramRendererProps) => {
const isSelectChange = (change: NodeChange): change is NodeSelectionChange => change.type === 'select';

export const DiagramRenderer = ({ diagram, selection, setSelection }: DiagramRendererProps) => {
const store = useStoreApi();
const [nodes, setNodes, onNodesChange] = useNodesState(diagram.nodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(diagram.edges);

Expand All @@ -44,21 +50,69 @@ export const DiagramRenderer = ({ diagram }: DiagramRendererProps) => {
setEdges(diagram.edges);
}, [diagram]);

useEffect(() => {
const selectionEntryIds = selection.entries.map((entry) => entry.id);
const nodesIds = diagram.nodes
.filter((node) => selectionEntryIds.includes((node.data as NodeData).targetObjectId))
.map((node) => node.id);
const reactFlowState = store.getState();
reactFlowState.unselectNodesAndEdges();
reactFlowState.addSelectedNodes(nodesIds);
}, [selection]);

const handleNodesChange: OnNodesChange = (changes: NodeChange[]) => {
onNodesChange(changes);

const selectionEntries: SelectionEntry[] = changes
.filter(isSelectChange)
.filter((change) => change.selected)
.flatMap((change) => diagram.nodes.filter((node) => node.id === change.id))
.map((node) => {
const nodeData = node.data as NodeData;
const { targetObjectId, targetObjectKind, targetObjectLabel } = nodeData;
return {
id: targetObjectId,
kind: targetObjectKind,
label: targetObjectLabel,
};
});

const currentSelectionEntryIds = selection.entries.map((selectionEntry) => selectionEntry.id);
const shouldUpdateSelection =
selectionEntries.map((entry) => entry.id).filter((entryId) => currentSelectionEntryIds.includes(entryId))
.length !== selectionEntries.length;

if (selectionEntries.length > 0 && shouldUpdateSelection) {
console.log('Should update Workbench selection');
setSelection({ entries: selectionEntries });
}
};

const handleEdgesChange: OnEdgesChange = (changes: EdgeChange[]) => {
onEdgesChange(changes);
};

const handlePaneClick = () => {
const selection: Selection = {
entries: [
{
id: diagram.metadata.id,
kind: diagram.metadata.kind,
label: diagram.metadata.label,
},
],
};
setSelection(selection);
};

return (
<ReactFlow
nodes={nodes}
nodeTypes={nodeTypes}
onNodesChange={handleNodesChange}
edges={edges}
onEdgesChange={handleEdgesChange}
onPaneClick={handlePaneClick}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,29 @@
* Obeo - initial API and implementation
*******************************************************************************/

import { Selection } from '@eclipse-sirius/sirius-components-core';
import { Edge, Node } from 'reactflow';

export interface DiagramRendererProps {
diagram: Diagram;
selection: Selection;
setSelection: (selection: Selection) => void;
}

export interface Diagram {
metadata: DiagramMetadata;
nodes: Node[];
edges: Edge[];
}

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

export interface NodeData {
targetObjectId: string;
targetObjectKind: string;
targetObjectLabel: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,21 @@ import { memo, useContext } from 'react';
import { Handle, NodeProps, Position } from 'reactflow';
import { ImageNodeData } from './ImageNode.types';

const imageNodeStyle = (style: React.CSSProperties): React.CSSProperties => {
return { ...style };
const imageNodeStyle = (style: React.CSSProperties, selected: boolean): React.CSSProperties => {
const imageNodeStyle: React.CSSProperties = { ...style };

if (selected) {
imageNodeStyle.outline = `var(--blue-lagoon) solid 1px`;
}

return imageNodeStyle;
};

export const ImageNode = memo(({ data, isConnectable }: NodeProps<ImageNodeData>) => {
export const ImageNode = memo(({ data, isConnectable, selected }: NodeProps<ImageNodeData>) => {
const { httpOrigin } = useContext(ServerContext);
return (
<>
<img src={httpOrigin + data.imageURL} style={imageNodeStyle(data.style)} />
<img src={httpOrigin + data.imageURL} style={imageNodeStyle(data.style, selected)} />
<Handle type="source" position={Position.Left} isConnectable={isConnectable} />
<Handle type="target" position={Position.Right} isConnectable={isConnectable} />
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
* Obeo - initial API and implementation
*******************************************************************************/

export interface ImageNodeData {
import { NodeData } from './DiagramRenderer.types';

export interface ImageNodeData extends NodeData {
imageURL: string;
style: React.CSSProperties;
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,18 @@ import { memo } from 'react';
import { Handle, NodeProps, Position } from 'reactflow';
import { ListNodeData } from './ListNode.types';

const listNodeStyle = (style: React.CSSProperties): React.CSSProperties => {
return {
const listNodeStyle = (style: React.CSSProperties, selected: boolean): React.CSSProperties => {
const listNodeStyle: React.CSSProperties = {
display: 'flex',
flexDirection: 'column',
alignItems: 'stretch',
...style,
};
if (selected) {
listNodeStyle.outline = `var(--blue-lagoon) solid 1px`;
}

return listNodeStyle;
};

const listNodeHeaderStyle = (style: React.CSSProperties): React.CSSProperties => {
Expand All @@ -48,9 +53,9 @@ const listItemStyle = (style: React.CSSProperties): React.CSSProperties => {
};
};

export const ListNode = memo(({ data, isConnectable }: NodeProps<ListNodeData>) => {
export const ListNode = memo(({ data, isConnectable, selected }: NodeProps<ListNodeData>) => {
return (
<div style={listNodeStyle(data.style)}>
<div style={listNodeStyle(data.style, selected)}>
<div style={listNodeHeaderStyle(data.label.style)}>{data.label.text}</div>
<div>
{data.listItems.map((listItem) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@
* Obeo - initial API and implementation
*******************************************************************************/

import { NodeData } from './DiagramRenderer.types';
import { Label } from './Label.types';

export interface ListNodeData {
export interface ListNodeData extends NodeData {
label: Label;
style: React.CSSProperties;
listItems: ListItemData[];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,18 @@ import { memo } from 'react';
import { Handle, NodeProps, Position } from 'reactflow';
import { RectangularNodeData } from './RectangularNode.types';

const rectangularNodeStyle = (style: React.CSSProperties): React.CSSProperties => {
return {
const rectangularNodeStyle = (style: React.CSSProperties, selected: boolean): React.CSSProperties => {
const rectangularNodeStyle: React.CSSProperties = {
display: 'flex',
padding: '8px',
...style,
};

if (selected) {
rectangularNodeStyle.outline = `var(--blue-lagoon) solid 1px`;
}

return rectangularNodeStyle;
};

const labelStyle = (style: React.CSSProperties): React.CSSProperties => {
Expand All @@ -29,9 +35,9 @@ const labelStyle = (style: React.CSSProperties): React.CSSProperties => {
};
};

export const RectangularNode = memo(({ data, isConnectable }: NodeProps<RectangularNodeData>) => {
export const RectangularNode = memo(({ data, isConnectable, selected }: NodeProps<RectangularNodeData>) => {
return (
<div style={rectangularNodeStyle(data.style)}>
<div style={rectangularNodeStyle(data.style, selected)}>
<div style={labelStyle(data.label.style)}>{data.label.text}</div>
<Handle type="source" position={Position.Left} isConnectable={isConnectable} />
<Handle type="target" position={Position.Right} isConnectable={isConnectable} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@
* Obeo - initial API and implementation
*******************************************************************************/

import { NodeData } from './DiagramRenderer.types';
import { Label } from './Label.types';

export interface RectangularNodeData {
export interface RectangularNodeData extends NodeData {
label: Label;
style: React.CSSProperties;
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import { OnSubscriptionDataOptions, gql, useSubscription } from '@apollo/client';
import { RepresentationComponentProps } from '@eclipse-sirius/sirius-components-core';
import { useState } from 'react';
import { ReactFlowProvider } from 'reactflow';
import { convertDiagram } from '../converter/convertDiagram';
import { diagramEventSubscription } from '../graphql/subscription/diagramEventSubscription';
import {
Expand All @@ -33,7 +34,12 @@ const subscription = gql(diagramEventSubscription);
const isDiagramRefreshedEventPayload = (payload: GQLDiagramEventPayload): payload is GQLDiagramRefreshedEventPayload =>
payload.__typename === 'DiagramRefreshedEventPayload';

export const DiagramRepresentation = ({ editingContextId, representationId }: RepresentationComponentProps) => {
export const DiagramRepresentation = ({
editingContextId,
representationId,
selection,
setSelection,
}: RepresentationComponentProps) => {
const [state, setState] = useState<DiagramRepresentationState>({
id: crypto.randomUUID(),
diagram: null,
Expand Down Expand Up @@ -61,7 +67,7 @@ export const DiagramRepresentation = ({ editingContextId, representationId }: Re
};

const onSubscriptionComplete = () => {
setState((prevState) => ({ ...prevState, diagram: null, complete: true }));
setState((prevState) => ({ ...prevState, diagram: null, convertedDiagram: null, complete: true }));
};

const { error } = useSubscription<GQLDiagramEventData>(subscription, {
Expand All @@ -84,5 +90,9 @@ export const DiagramRepresentation = ({ editingContextId, representationId }: Re
return <div></div>;
}

return <DiagramRenderer diagram={state.diagram} />;
return (
<ReactFlowProvider>
<DiagramRenderer diagram={state.diagram} selection={selection} setSelection={setSelection} />
</ReactFlowProvider>
);
};

0 comments on commit 734dfc5

Please sign in to comment.