Skip to content

Commit 5fdc12f

Browse files
committed
added support for editing section edges
Signed-off-by: Teo Koon Peng <[email protected]>
1 parent fc47a7f commit 5fdc12f

15 files changed

+410
-109
lines changed

diagram-editor/frontend/add-operation.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ function AddOperation({ parentId, newNodePosition, onAdd }: AddOperationProps) {
334334
)
335335
}
336336
>
337-
Scope
337+
Section
338338
</StyledOperationButton>
339339
</ButtonGroup>
340340
);

diagram-editor/frontend/diagram-editor.tsx

Lines changed: 66 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
applyEdgeChanges,
1616
applyNodeChanges,
1717
Background,
18+
type Connection,
1819
type EdgeChange,
1920
type EdgeRemoveChange,
2021
type NodeChange,
@@ -30,7 +31,12 @@ import React from 'react';
3031
import AddOperation from './add-operation';
3132
import CommandPanel from './command-panel';
3233
import type { DiagramEditorEdge } from './edges';
33-
import { createBaseEdge, EDGE_TYPES } from './edges';
34+
import {
35+
createBaseEdge,
36+
EDGE_CATEGORIES,
37+
EDGE_TYPES,
38+
EdgeCategory,
39+
} from './edges';
3440
import {
3541
EditorMode,
3642
type EditorModeContext,
@@ -497,6 +503,54 @@ function DiagramEditor() {
497503
mouseDownTime.current = Date.now();
498504
}, []);
499505

506+
const tryCreateEdge = React.useCallback(
507+
(conn: Connection, id?: string): DiagramEditorEdge | null => {
508+
if (!reactFlowInstance.current) {
509+
return null;
510+
}
511+
512+
const sourceNode = reactFlowInstance.current.getNode(conn.source);
513+
const targetNode = reactFlowInstance.current.getNode(conn.target);
514+
if (!sourceNode || !targetNode) {
515+
throw new Error('cannot find source or target node');
516+
}
517+
518+
const validEdges = getValidEdgeTypes(sourceNode, targetNode);
519+
if (validEdges.length === 0) {
520+
showErrorToast(
521+
`cannot connect "${sourceNode.type}" to "${targetNode.type}"`,
522+
);
523+
return null;
524+
}
525+
526+
const newEdge = {
527+
...createBaseEdge(conn.source, conn.target, id),
528+
type: validEdges[0],
529+
data: defaultEdgeData(validEdges[0]),
530+
} as DiagramEditorEdge;
531+
532+
if (targetNode.type === 'section') {
533+
if (EDGE_CATEGORIES[newEdge.type] === EdgeCategory.Buffer) {
534+
newEdge.data.input = { type: 'sectionBuffer', inputId: '' };
535+
} else if (EDGE_CATEGORIES[newEdge.type] === EdgeCategory.Data) {
536+
newEdge.data.input = { type: 'sectionInput', inputId: '' };
537+
}
538+
}
539+
540+
const validationResult = validateEdgeSimple(
541+
newEdge,
542+
reactFlowInstance.current,
543+
);
544+
if (!validationResult.valid) {
545+
showErrorToast(validationResult.error);
546+
return null;
547+
}
548+
549+
return newEdge;
550+
},
551+
[showErrorToast],
552+
);
553+
500554
return (
501555
<EditorModeProvider value={[editorMode, updateEditorModeAction]}>
502556
<ReactFlow
@@ -541,40 +595,10 @@ function DiagramEditor() {
541595
closeAllPopovers();
542596
}}
543597
onConnect={(conn) => {
544-
if (!reactFlowInstance.current) {
545-
return;
546-
}
547-
548-
const sourceNode = reactFlowInstance.current?.getNode(conn.source);
549-
const targetNode = reactFlowInstance.current?.getNode(conn.target);
550-
if (!sourceNode || !targetNode) {
551-
throw new Error('cannot find source or target node');
598+
const newEdge = tryCreateEdge(conn);
599+
if (newEdge) {
600+
setEdges((prev) => addEdge(newEdge, prev));
552601
}
553-
554-
const validEdges = getValidEdgeTypes(sourceNode, targetNode);
555-
if (validEdges.length === 0) {
556-
showErrorToast(
557-
`cannot connect "${sourceNode.type}" to "${targetNode.type}"`,
558-
);
559-
return;
560-
}
561-
562-
const newEdge = {
563-
...createBaseEdge(conn.source, conn.target),
564-
type: validEdges[0],
565-
data: defaultEdgeData(validEdges[0]),
566-
} as DiagramEditorEdge;
567-
568-
const validationResult = validateEdgeSimple(
569-
newEdge,
570-
reactFlowInstance.current,
571-
);
572-
if (!validationResult.valid) {
573-
showErrorToast(validationResult.error);
574-
return;
575-
}
576-
577-
setEdges((prev) => addEdge(newEdge, prev));
578602
}}
579603
isValidConnection={(conn) => {
580604
const sourceNode = reactFlowInstance.current?.getNode(conn.source);
@@ -586,9 +610,14 @@ function DiagramEditor() {
586610
const allowedEdges = getValidEdgeTypes(sourceNode, targetNode);
587611
return allowedEdges.length > 0;
588612
}}
589-
onReconnect={(oldEdge, newConnection) =>
590-
setEdges((prev) => reconnectEdge(oldEdge, newConnection, prev))
591-
}
613+
onReconnect={(oldEdge, newConnection) => {
614+
const newEdge = tryCreateEdge(newConnection, oldEdge.id);
615+
if (newEdge) {
616+
oldEdge.type = newEdge.type;
617+
oldEdge.data = newEdge.data;
618+
setEdges((prev) => reconnectEdge(oldEdge, newConnection, prev));
619+
}
620+
}}
592621
onNodeClick={(ev, node) => {
593622
ev.stopPropagation();
594623
closeAllPopovers();

diagram-editor/frontend/edges/buffer-edge.tsx

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { type EdgeProps, StepEdge } from '@xyflow/react';
22
import type { Edge } from '../types/react-flow';
33
import type { SectionBufferSlotData } from './section-edge';
4+
import { memo } from 'react';
45

56
export type BufferKeySlotData = {
67
type: 'bufferKey';
@@ -20,8 +21,17 @@ export type BufferEdge = Edge<
2021
'buffer'
2122
>;
2223

23-
export type BufferEdgeProps = Exclude<EdgeProps<BufferEdge>, 'label'>;
24+
export type BufferEdgeCompProps = Exclude<EdgeProps<BufferEdge>, 'label'>;
2425

25-
const BufferEdgeComp = StepEdge;
26-
27-
export default BufferEdgeComp;
26+
export const BufferEdgeComp = memo((props: BufferEdgeCompProps) => {
27+
return (
28+
<StepEdge
29+
{...props}
30+
label={
31+
props.data.input.type === 'sectionBuffer'
32+
? props.data.input.inputId
33+
: undefined
34+
}
35+
/>
36+
);
37+
});

diagram-editor/frontend/edges/create-edge.ts

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ import type { BufferKeySlotData, BufferSeqSlotData } from './buffer-edge';
66
export function createBaseEdge(
77
source: string,
88
target: string,
9+
id?: string,
910
): Pick<DiagramEditorEdge, 'id' | 'source' | 'target' | 'markerEnd'> {
1011
return {
11-
id: uuidv4(),
12+
id: id || uuidv4(),
1213
source,
1314
target,
1415
markerEnd: {
@@ -126,14 +127,14 @@ export function createStreamOutEdge(
126127
};
127128
}
128129

129-
// export function createSectionEdge(
130-
// source: string,
131-
// target: string,
132-
// data: EdgeOutputData<'section'>,
133-
// ): DiagramEditorEdge<'section'> {
134-
// return {
135-
// ...createBaseEdge(source, target),
136-
// type: 'section',
137-
// data: { output: data },
138-
// };
139-
// }
130+
export function createSectionEdge(
131+
source: string,
132+
target: string,
133+
data: EdgeOutputData<'section'>,
134+
): DiagramEditorEdge<'section'> {
135+
return {
136+
...createBaseEdge(source, target),
137+
type: 'section',
138+
data: { output: data, input: { type: 'default' } },
139+
};
140+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { type EdgeProps, StepEdge } from '@xyflow/react';
2+
import { memo } from 'react';
3+
import type { DataEdge } from '.';
4+
5+
export type DefaultOutputData = Record<string, never>;
6+
7+
export type DefaultEdge = DataEdge<DefaultOutputData, 'default'>;
8+
9+
export type DefaultEdgeCompProps = Omit<EdgeProps<DefaultEdge>, 'label'>;
10+
11+
export const DefaultEdgeComp = memo((props: DefaultEdgeCompProps) => {
12+
return (
13+
<StepEdge
14+
{...props}
15+
label={
16+
props.data.input.type === 'sectionInput'
17+
? props.data.input.inputId
18+
: undefined
19+
}
20+
/>
21+
);
22+
});

diagram-editor/frontend/edges/index.ts

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { StepEdge } from '@xyflow/react';
21
import type { Edge } from '../types/react-flow';
3-
import BufferEdgeComp, { type BufferEdge } from './buffer-edge';
2+
import { type BufferEdge, BufferEdgeComp } from './buffer-edge';
3+
import { type DefaultEdge, DefaultEdgeComp } from './default-edge';
44
import ForkResultErrEdgeComp, {
55
type ForkResultErrEdge,
66
} from './fork-result-err-edge';
@@ -22,22 +22,20 @@ import UnzipEdgeComp, { type UnzipEdge } from './unzip-edge';
2222

2323
export type { BufferEdge } from './buffer-edge';
2424
export * from './create-edge';
25+
export type { SectionEdge } from './section-edge';
2526
export type { SplitKeyEdge } from './split-key-edge';
2627
export type { SplitRemainingEdge } from './split-remaining-edge';
2728
export type { SplitSeqEdge } from './split-seq-edge';
2829
export type { StreamOutEdge } from './stream-out-edge';
2930
export type { UnzipEdge } from './unzip-edge';
3031

31-
export type DefaultOutputData = Record<string, never>;
3232
export type DefaultInputData = { type: 'default' };
3333

3434
export type DataEdge<
3535
O extends Record<string, unknown>,
3636
S extends string,
3737
> = Edge<O, DefaultInputData | SectionInputSlotData, S>;
3838

39-
export type DefaultEdge = DataEdge<DefaultOutputData, 'default'>;
40-
4139
export type EdgeMapping = {
4240
default: DefaultEdge;
4341
unzip: UnzipEdge;
@@ -59,7 +57,7 @@ export type EdgeOutputData<K extends EdgeTypes = EdgeTypes> =
5957
EdgeData<K>['output'];
6058

6159
export const EDGE_TYPES = {
62-
default: StepEdge,
60+
default: DefaultEdgeComp,
6361
unzip: UnzipEdgeComp,
6462
forkResultOk: ForkResultOkEdgeComp,
6563
forkResultErr: ForkResultErrEdgeComp,
@@ -72,3 +70,28 @@ export const EDGE_TYPES = {
7270
} satisfies Record<EdgeTypes, unknown>;
7371

7472
export type DiagramEditorEdge<T extends EdgeTypes = EdgeTypes> = EdgeMapping[T];
73+
74+
export enum EdgeCategory {
75+
Data,
76+
Buffer,
77+
Stream,
78+
}
79+
80+
export const EDGE_CATEGORIES: Record<EdgeTypes, EdgeCategory> = {
81+
buffer: EdgeCategory.Buffer,
82+
forkResultOk: EdgeCategory.Data,
83+
forkResultErr: EdgeCategory.Data,
84+
splitKey: EdgeCategory.Data,
85+
splitSeq: EdgeCategory.Data,
86+
splitRemaining: EdgeCategory.Data,
87+
default: EdgeCategory.Data,
88+
streamOut: EdgeCategory.Stream,
89+
unzip: EdgeCategory.Data,
90+
section: EdgeCategory.Data,
91+
};
92+
93+
export function isDataEdge<T extends EdgeTypes>(
94+
edge: DiagramEditorEdge<T>,
95+
): edge is DiagramEditorEdge<T> & DataEdge<Record<string, unknown>, T> {
96+
return EDGE_CATEGORIES[edge.type] === EdgeCategory.Data;
97+
}

diagram-editor/frontend/edges/section-edge.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export type SectionEdge = DataEdge<SectionOutputData, 'section'>;
1111
export type SectionOutputEdgeProps = Exclude<EdgeProps<SectionEdge>, 'label'>;
1212

1313
export const SectionOutputEdgeComp = memo((props: SectionOutputEdgeProps) => {
14-
return <StepEdge {...props} label={props.data.output.toString()} />;
14+
return <StepEdge {...props} label={props.data.output.output} />;
1515
});
1616

1717
export type SectionInputSlotData = {

0 commit comments

Comments
 (0)