Skip to content

Commit b5ef3a7

Browse files
committed
check if edge is valid when connecting; add arrow marker to edges
Signed-off-by: Teo Koon Peng <[email protected]>
1 parent 710d12e commit b5ef3a7

File tree

10 files changed

+241
-203
lines changed

10 files changed

+241
-203
lines changed

diagram-editor/frontend/add-operation.tsx

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ import type { DiagramEditorNode } from './nodes';
1111
import {
1212
BufferAccessIcon,
1313
BufferIcon,
14+
createOperationNode,
15+
createScopeNode,
16+
createSectionBufferNode,
17+
createSectionInputNode,
18+
createSectionOutputNode,
1419
ForkCloneIcon,
1520
ForkResultIcon,
1621
isOperationNode,
@@ -30,13 +35,6 @@ import {
3035
UnzipIcon,
3136
} from './nodes';
3237
import type { DiagramOperation } from './types/api';
33-
import {
34-
createOperationNode,
35-
createScopeNode,
36-
createSectionBufferNode,
37-
createSectionInputNode,
38-
createSectionOutputNode,
39-
} from './utils/create-node';
4038
import { joinNamespaces, ROOT_NAMESPACE } from './utils/namespace';
4139

4240
const StyledOperationButton = styled(Button)({

diagram-editor/frontend/diagram-editor.tsx

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import {
3939
import ExportDiagramDialog from './export-diagram-dialog';
4040
import { defaultEdgeData, EditEdgeForm, EditNodeForm } from './forms';
4141
import EditScopeForm from './forms/edit-scope-form';
42+
import { NodeManager } from './node-manager';
4243
import {
4344
type DiagramEditorNode,
4445
isOperationNode,
@@ -48,12 +49,11 @@ import {
4849
} from './nodes';
4950
import { useTemplates } from './templates-provider';
5051
import { autoLayout } from './utils/auto-layout';
51-
import { allowEdges as getAllowEdges } from './utils/connection';
52+
import { getValidEdgeTypes } from './utils/connection';
5253
import { exhaustiveCheck } from './utils/exhaustive-check';
54+
import { exportTemplate } from './utils/export-diagram';
5355
import { calculateScopeBounds, LAYOUT_OPTIONS } from './utils/layout';
5456
import { loadDiagramJson, loadEmpty, loadTemplate } from './utils/load-diagram';
55-
import { exportTemplate } from './utils/export-diagram';
56-
import { NodeManager } from './node-manager';
5757

5858
const NonCapturingPopoverContainer = ({
5959
children,
@@ -527,14 +527,14 @@ function DiagramEditor() {
527527
closeAllPopovers();
528528
}}
529529
onConnect={(conn) => {
530-
const sourceNode = nodes.find((n) => n.id === conn.source);
531-
const targetNode = nodes.find((n) => n.id === conn.target);
530+
const sourceNode = reactFlowInstance.current?.getNode(conn.source);
531+
const targetNode = reactFlowInstance.current?.getNode(conn.target);
532532
if (!sourceNode || !targetNode) {
533533
throw new Error('cannot find source or target node');
534534
}
535535

536-
const allowedEdges = getAllowEdges(sourceNode, targetNode);
537-
if (allowedEdges.length === 0) {
536+
const validEdges = getValidEdgeTypes(sourceNode, targetNode);
537+
if (validEdges.length === 0) {
538538
showErrorToast(
539539
`cannot connect "${sourceNode.type}" to "${targetNode.type}"`,
540540
);
@@ -545,13 +545,23 @@ function DiagramEditor() {
545545
addEdge(
546546
{
547547
...conn,
548-
type: allowedEdges[0],
549-
data: defaultEdgeData(allowedEdges[0]),
548+
type: validEdges[0],
549+
data: defaultEdgeData(validEdges[0]),
550550
},
551551
prev,
552552
),
553553
);
554554
}}
555+
isValidConnection={(conn) => {
556+
const sourceNode = reactFlowInstance.current?.getNode(conn.source);
557+
const targetNode = reactFlowInstance.current?.getNode(conn.target);
558+
if (!sourceNode || !targetNode) {
559+
throw new Error('cannot find source or target node');
560+
}
561+
562+
const allowedEdges = getValidEdgeTypes(sourceNode, targetNode);
563+
return allowedEdges.length > 0;
564+
}}
555565
onReconnect={(oldEdge, newConnection) =>
556566
setEdges((prev) => reconnectEdge(oldEdge, newConnection, prev))
557567
}
@@ -678,7 +688,7 @@ function DiagramEditor() {
678688
{editingEdge && (
679689
<EditEdgeForm
680690
edge={editingEdge.edge}
681-
allowedEdgeTypes={getAllowEdges(
691+
allowedEdgeTypes={getValidEdgeTypes(
682692
editingEdge.sourceNode,
683693
editingEdge.targetNode,
684694
)}
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import { MarkerType } from '@xyflow/react';
2+
import { v4 as uuidv4 } from 'uuid';
3+
import type { DiagramEditorEdge, EdgeData } from '.';
4+
5+
function createBaseEdge(
6+
source: string,
7+
target: string,
8+
): Pick<DiagramEditorEdge, 'id' | 'source' | 'target' | 'markerEnd'> {
9+
return {
10+
id: uuidv4(),
11+
source,
12+
target,
13+
markerEnd: {
14+
type: MarkerType.ArrowClosed,
15+
width: 24,
16+
height: 24,
17+
},
18+
};
19+
}
20+
21+
export function createDefaultEdge(
22+
source: string,
23+
target: string,
24+
): DiagramEditorEdge<'default'> {
25+
return {
26+
...createBaseEdge(source, target),
27+
type: 'default',
28+
data: {},
29+
};
30+
}
31+
32+
export function createUnzipEdge(
33+
source: string,
34+
target: string,
35+
data: EdgeData<'unzip'>,
36+
): DiagramEditorEdge<'unzip'> {
37+
return {
38+
...createBaseEdge(source, target),
39+
type: 'unzip',
40+
data,
41+
};
42+
}
43+
44+
export function createForkResultOkEdge(
45+
source: string,
46+
target: string,
47+
): DiagramEditorEdge<'forkResultOk'> {
48+
return {
49+
...createBaseEdge(source, target),
50+
type: 'forkResultOk',
51+
data: {},
52+
};
53+
}
54+
55+
export function createForkResultErrEdge(
56+
source: string,
57+
target: string,
58+
): DiagramEditorEdge<'forkResultErr'> {
59+
return {
60+
...createBaseEdge(source, target),
61+
type: 'forkResultErr',
62+
data: {},
63+
};
64+
}
65+
66+
export function createSplitKeyEdge(
67+
source: string,
68+
target: string,
69+
data: EdgeData<'splitKey'>,
70+
): DiagramEditorEdge<'splitKey'> {
71+
return {
72+
...createBaseEdge(source, target),
73+
type: 'splitKey',
74+
data,
75+
};
76+
}
77+
78+
export function createSplitSeqEdge(
79+
source: string,
80+
target: string,
81+
data: EdgeData<'splitSeq'>,
82+
): DiagramEditorEdge<'splitSeq'> {
83+
return {
84+
...createBaseEdge(source, target),
85+
type: 'splitSeq',
86+
data,
87+
};
88+
}
89+
90+
export function createSplitRemainingEdge(
91+
source: string,
92+
target: string,
93+
): DiagramEditorEdge<'splitRemaining'> {
94+
return {
95+
...createBaseEdge(source, target),
96+
type: 'splitRemaining',
97+
data: {},
98+
};
99+
}
100+
101+
export function createBufferKeyEdge(
102+
source: string,
103+
target: string,
104+
data: EdgeData<'bufferKey'>,
105+
): DiagramEditorEdge<'bufferKey'> {
106+
return {
107+
...createBaseEdge(source, target),
108+
type: 'bufferKey',
109+
data,
110+
};
111+
}
112+
113+
export function createBufferSeqEdge(
114+
source: string,
115+
target: string,
116+
data: EdgeData<'bufferSeq'>,
117+
): DiagramEditorEdge<'bufferSeq'> {
118+
return {
119+
...createBaseEdge(source, target),
120+
type: 'bufferSeq',
121+
data,
122+
};
123+
}
124+
125+
export function createStreamOutEdge(
126+
source: string,
127+
target: string,
128+
data: EdgeData<'streamOut'>,
129+
): DiagramEditorEdge<'streamOut'> {
130+
return {
131+
...createBaseEdge(source, target),
132+
type: 'streamOut',
133+
data,
134+
};
135+
}

diagram-editor/frontend/edges/index.ts

Lines changed: 25 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,30 @@ export type { SplitSeqEdge } from './split-seq-edge';
4545
export type { StreamOutEdge } from './stream-out-edge';
4646
export type { UnzipEdge } from './unzip-edge';
4747

48+
export * from './create-edge';
49+
export type DefaultEdgeData = Record<string, never>;
50+
export type DefaultEdge = Edge<DefaultEdgeData, 'default'>;
51+
52+
type EdgeMapping = {
53+
default: { comp: DefaultEdge; data: DefaultEdgeData };
54+
unzip: { comp: UnzipEdge; data: UnzipEdgeData };
55+
forkResultOk: { comp: ForkResultOkEdge; data: ForkResultOkEdgeData };
56+
forkResultErr: { comp: ForkResultErrEdge; data: ForkResultErrEdgeData };
57+
splitKey: { comp: SplitKeyEdge; data: SplitKeyEdgeData };
58+
splitSeq: { comp: SplitSeqEdge; data: SplitSeqEdgeData };
59+
splitRemaining: { comp: SplitRemainingEdge; data: SplitRemainingEdgeData };
60+
bufferKey: { comp: BufferKeyEdge; data: BufferKeyEdgeData };
61+
bufferSeq: { comp: BufferSeqEdge; data: BufferSeqEdgeData };
62+
streamOut: { comp: StreamOutEdge; data: StreamOutEdgeData };
63+
};
64+
65+
export type EdgeTypes = keyof EdgeMapping;
66+
67+
export type DiagramEditorEdge<T extends EdgeTypes = EdgeTypes> =
68+
EdgeMapping[T]['comp'];
69+
70+
export type EdgeData<T extends EdgeTypes = EdgeTypes> = EdgeMapping[T]['data'];
71+
4872
export const EDGE_TYPES = {
4973
default: StepEdge,
5074
unzip: UnzipEdgeComp,
@@ -56,34 +80,4 @@ export const EDGE_TYPES = {
5680
bufferKey: BufferKeyEdgeComp,
5781
bufferSeq: BufferSeqEdgeComp,
5882
streamOut: StreamOutEdgeComp,
59-
};
60-
61-
export type EdgeTypes = keyof typeof EDGE_TYPES;
62-
63-
export type DefaultEdgeData = Record<string, never>;
64-
export type DefaultEdge = Edge<DefaultEdgeData, 'default'>;
65-
66-
export type EdgeData = {
67-
default: DefaultEdgeData;
68-
unzip: UnzipEdgeData;
69-
forkResultOk: ForkResultOkEdgeData;
70-
forkResultErr: ForkResultErrEdgeData;
71-
splitKey: SplitKeyEdgeData;
72-
splitSeq: SplitSeqEdgeData;
73-
splitRemaining: SplitRemainingEdgeData;
74-
bufferKey: BufferKeyEdgeData;
75-
bufferSeq: BufferSeqEdgeData;
76-
streamOut: StreamOutEdgeData;
77-
};
78-
79-
export type DiagramEditorEdge =
80-
| DefaultEdge
81-
| UnzipEdge
82-
| ForkResultOkEdge
83-
| ForkResultErrEdge
84-
| SplitKeyEdge
85-
| SplitSeqEdge
86-
| SplitRemainingEdge
87-
| BufferKeyEdge
88-
| BufferSeqEdge
89-
| StreamOutEdge;
83+
} satisfies Record<EdgeTypes, unknown>;

diagram-editor/frontend/utils/create-node.ts renamed to diagram-editor/frontend/nodes/create-node.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import type { XYPosition } from '@xyflow/react';
22
import { v4 as uuidv4 } from 'uuid';
3+
import type { DiagramOperation, NextOperation } from '../types/api';
4+
import { calculateScopeBounds, LAYOUT_OPTIONS } from '../utils/layout';
5+
import { joinNamespaces } from '../utils/namespace';
36
import {
47
type DiagramEditorNode,
58
type OperationNode,
@@ -8,10 +11,7 @@ import {
811
type SectionOutputNode,
912
START_ID,
1013
TERMINATE_ID,
11-
} from '../nodes';
12-
import type { DiagramOperation, NextOperation } from '../types/api';
13-
import { calculateScopeBounds, LAYOUT_OPTIONS } from './layout';
14-
import { joinNamespaces } from './namespace';
14+
} from '.';
1515

1616
export function createStartNode(
1717
namespace: string,

diagram-editor/frontend/nodes/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import TerminateNodeComp from './terminate-node';
2222
import TransformNodeComp from './transform-node';
2323
import UnzipNodeComp from './unzip-node';
2424

25+
export * from './create-node';
2526
export * from './icons';
2627
export type {
2728
SectionBufferData,

diagram-editor/frontend/utils/connection.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -55,15 +55,15 @@ function setIntersection<T>(a: Set<T>, b: Set<T>): Set<T> {
5555
return intersection;
5656
}
5757

58-
export function allowEdges(
59-
source: DiagramEditorNode,
60-
target: DiagramEditorNode,
58+
export function getValidEdgeTypes(
59+
sourceNode: DiagramEditorNode,
60+
targetNode: DiagramEditorNode,
6161
): EdgeTypes[] {
62-
const allowed_output_edges = source.type
63-
? ALLOWED_OUTPUT_EDGES[source.type]
62+
const allowedOutputEdges = sourceNode.type
63+
? ALLOWED_OUTPUT_EDGES[sourceNode.type]
6464
: new Set([]);
65-
const allowed_input_edges = target.type
66-
? ALLOWED_INPUT_EDGES[target.type]
65+
const allowedInputEdges = targetNode.type
66+
? ALLOWED_INPUT_EDGES[targetNode.type]
6767
: new Set([]);
68-
return Array.from(setIntersection(allowed_output_edges, allowed_input_edges));
68+
return Array.from(setIntersection(allowedOutputEdges, allowedInputEdges));
6969
}

diagram-editor/frontend/utils/export-diagram.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import { v4 as uuidv4 } from 'uuid';
22
import type { DiagramEditorEdge } from '../edges';
33
import { NodeManager } from '../node-manager';
4-
import { type DiagramEditorNode, START_ID } from '../nodes';
54
import {
65
createOperationNode,
76
createSectionBufferNode,
87
createSectionInputNode,
98
createSectionOutputNode,
10-
} from './create-node';
9+
type DiagramEditorNode,
10+
START_ID,
11+
} from '../nodes';
1112
import { exportDiagram, exportTemplate } from './export-diagram';
1213
import { loadDiagramJson } from './load-diagram';
1314
import { joinNamespaces, ROOT_NAMESPACE } from './namespace';

0 commit comments

Comments
 (0)