Skip to content

Commit c7902fa

Browse files
committed
wip exporting template
Signed-off-by: Teo Koon Peng <[email protected]>
1 parent 430a61a commit c7902fa

File tree

3 files changed

+188
-105
lines changed

3 files changed

+188
-105
lines changed

diagram-editor/frontend/add-operation.tsx

Lines changed: 15 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import {
55
type XYPosition,
66
} from '@xyflow/react';
77
import React from 'react';
8-
import { v4 as uuidv4 } from 'uuid';
98
import { EditorMode, useEditorMode } from './editor-mode';
109
import type { DiagramEditorNode } from './nodes';
1110
import {
@@ -30,7 +29,13 @@ import {
3029
UnzipIcon,
3130
} from './nodes';
3231
import type { DiagramOperation } from './types/api';
33-
import { calculateScopeBounds, LAYOUT_OPTIONS } from './utils/layout';
32+
import {
33+
createOperationNode,
34+
createScopeNode,
35+
createSectionBufferNode,
36+
createSectionInputNode,
37+
createSectionOutputNode,
38+
} from './utils/create-node';
3439
import { joinNamespaces, ROOT_NAMESPACE } from './utils/namespace';
3540

3641
const StyledOperationButton = styled(Button)({
@@ -44,18 +49,12 @@ export interface AddOperationProps {
4449
}
4550

4651
function createSectionInputChange(
47-
remappedId: string,
4852
targetId: string,
4953
position: XYPosition,
5054
): NodeAddChange<SectionInputNode> {
5155
return {
5256
type: 'add',
53-
item: {
54-
id: uuidv4(),
55-
type: 'sectionInput',
56-
data: { remappedId, targetId },
57-
position,
58-
},
57+
item: createSectionInputNode(targetId, position),
5958
};
6059
}
6160

@@ -65,28 +64,17 @@ function createSectionOutputChange(
6564
): NodeAddChange<SectionOutputNode> {
6665
return {
6766
type: 'add',
68-
item: {
69-
id: uuidv4(),
70-
type: 'sectionOutput',
71-
data: { outputId },
72-
position,
73-
},
67+
item: createSectionOutputNode(outputId, position),
7468
};
7569
}
7670

7771
function createSectionBufferChange(
78-
remappedId: string,
7972
targetId: string,
8073
position: XYPosition,
8174
): NodeAddChange<SectionBufferNode> {
8275
return {
8376
type: 'add',
84-
item: {
85-
id: uuidv4(),
86-
type: 'sectionBuffer',
87-
data: { remappedId, targetId },
88-
position,
89-
},
77+
item: createSectionBufferNode(targetId, position),
9078
};
9179
}
9280

@@ -97,82 +85,15 @@ function createNodeChange(
9785
op: DiagramOperation,
9886
): NodeAddChange<DiagramEditorNode>[] {
9987
if (op.type === 'scope') {
100-
const scopeId = uuidv4();
101-
const children: NodeAddChange<DiagramEditorNode>[] = [
102-
{
103-
type: 'add',
104-
item: {
105-
id: uuidv4(),
106-
type: 'start',
107-
position: {
108-
x: LAYOUT_OPTIONS.scopePadding.leftRight,
109-
y: LAYOUT_OPTIONS.scopePadding.topBottom,
110-
},
111-
data: {
112-
namespace: joinNamespaces(namespace, scopeId),
113-
},
114-
parentId: scopeId,
115-
},
116-
},
117-
{
118-
type: 'add',
119-
item: {
120-
id: uuidv4(),
121-
type: 'terminate',
122-
position: {
123-
x: LAYOUT_OPTIONS.scopePadding.leftRight,
124-
y: LAYOUT_OPTIONS.scopePadding.topBottom * 5,
125-
},
126-
data: {
127-
namespace: joinNamespaces(namespace, scopeId),
128-
},
129-
parentId: scopeId,
130-
},
131-
},
132-
];
133-
const scopeBounds = calculateScopeBounds(
134-
children.map((c) => c.item.position),
88+
return createScopeNode(namespace, parentId, newNodePosition, op).map(
89+
(node) => ({ type: 'add', item: node }),
13590
);
136-
return [
137-
{
138-
type: 'add',
139-
item: {
140-
id: scopeId,
141-
type: 'scope',
142-
position: {
143-
x: newNodePosition.x + scopeBounds.x,
144-
y: newNodePosition.y + scopeBounds.y,
145-
},
146-
data: {
147-
namespace,
148-
opId: uuidv4(),
149-
op,
150-
},
151-
width: scopeBounds.width,
152-
height: scopeBounds.height,
153-
zIndex: -1,
154-
parentId,
155-
},
156-
},
157-
...children,
158-
];
15991
}
16092

16193
return [
16294
{
16395
type: 'add',
164-
item: {
165-
id: uuidv4(),
166-
type: op.type,
167-
position: newNodePosition,
168-
data: {
169-
namespace,
170-
opId: uuidv4(),
171-
op,
172-
},
173-
174-
parentId,
175-
},
96+
item: createOperationNode(namespace, parentId, newNodePosition, op),
17697
},
17798
];
17899
}
@@ -200,13 +121,7 @@ function AddOperation({ parentId, newNodePosition, onAdd }: AddOperationProps) {
200121
<StyledOperationButton
201122
startIcon={<SectionInputIcon />}
202123
onClick={() => {
203-
onAdd?.([
204-
createSectionInputChange(
205-
'new_input',
206-
'new_input',
207-
newNodePosition,
208-
),
209-
]);
124+
onAdd?.([createSectionInputChange('new_input', newNodePosition)]);
210125
}}
211126
>
212127
Section Input
@@ -231,11 +146,7 @@ function AddOperation({ parentId, newNodePosition, onAdd }: AddOperationProps) {
231146
startIcon={<SectionBufferIcon />}
232147
onClick={() => {
233148
onAdd?.([
234-
createSectionBufferChange(
235-
'new_buffer',
236-
'new_buffer',
237-
newNodePosition,
238-
),
149+
createSectionBufferChange('new_buffer', newNodePosition),
239150
]);
240151
}}
241152
>
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import type { XYPosition } from '@xyflow/react';
2+
import { v4 as uuidv4 } from 'uuid';
3+
import type {
4+
DiagramEditorNode,
5+
OperationNode,
6+
SectionBufferNode,
7+
SectionInputNode,
8+
SectionOutputNode,
9+
} from '../nodes';
10+
import type { DiagramOperation } from '../types/api';
11+
import { calculateScopeBounds, LAYOUT_OPTIONS } from './layout';
12+
import { joinNamespaces } from './namespace';
13+
14+
/**
15+
* Create a section input node. For simplicity, remapping the input is not supported, `targetId`
16+
* must point to the id of an operation in the template.
17+
*/
18+
export function createSectionInputNode(
19+
targetId: string,
20+
position: XYPosition,
21+
): SectionInputNode {
22+
return {
23+
id: uuidv4(),
24+
type: 'sectionInput',
25+
data: { remappedId: targetId, targetId },
26+
position,
27+
};
28+
}
29+
30+
export function createSectionOutputNode(
31+
outputId: string,
32+
position: XYPosition,
33+
): SectionOutputNode {
34+
return {
35+
id: uuidv4(),
36+
type: 'sectionOutput',
37+
data: { outputId },
38+
position,
39+
};
40+
}
41+
42+
/**
43+
* Create a section input node. For simplicity, remapping the input is not supported, `targetId`
44+
* must point to the id of a `buffer` operation in the template.
45+
*/
46+
export function createSectionBufferNode(
47+
targetId: string,
48+
position: XYPosition,
49+
): SectionBufferNode {
50+
return {
51+
id: uuidv4(),
52+
type: 'sectionBuffer',
53+
data: { remappedId: targetId, targetId },
54+
position,
55+
};
56+
}
57+
58+
export function createOperationNode(
59+
namespace: string,
60+
parentId: string | undefined,
61+
position: XYPosition,
62+
op: Exclude<DiagramOperation, { type: 'scope' }>,
63+
): OperationNode {
64+
return {
65+
id: uuidv4(),
66+
type: op.type,
67+
position,
68+
data: {
69+
namespace,
70+
opId: uuidv4(),
71+
op,
72+
},
73+
...(parentId && { parentId }),
74+
};
75+
}
76+
77+
export function createScopeNode(
78+
namespace: string,
79+
parentId: string | undefined,
80+
position: XYPosition,
81+
op: DiagramOperation & { type: 'scope' },
82+
): DiagramEditorNode[] {
83+
const scopeId = uuidv4();
84+
const children: DiagramEditorNode[] = [
85+
{
86+
id: uuidv4(),
87+
type: 'start',
88+
position: {
89+
x: LAYOUT_OPTIONS.scopePadding.leftRight,
90+
y: LAYOUT_OPTIONS.scopePadding.topBottom,
91+
},
92+
data: {
93+
namespace: joinNamespaces(namespace, scopeId),
94+
},
95+
parentId: scopeId,
96+
},
97+
{
98+
id: uuidv4(),
99+
type: 'terminate',
100+
position: {
101+
x: LAYOUT_OPTIONS.scopePadding.leftRight,
102+
y: LAYOUT_OPTIONS.scopePadding.topBottom * 5,
103+
},
104+
data: {
105+
namespace: joinNamespaces(namespace, scopeId),
106+
},
107+
parentId: scopeId,
108+
},
109+
];
110+
const scopeBounds = calculateScopeBounds(
111+
children.map((child) => child.position),
112+
);
113+
return [
114+
{
115+
id: scopeId,
116+
type: 'scope',
117+
position: {
118+
x: position.x + scopeBounds.x,
119+
y: position.y + scopeBounds.y,
120+
},
121+
data: {
122+
namespace,
123+
opId: uuidv4(),
124+
op,
125+
},
126+
width: scopeBounds.width,
127+
height: scopeBounds.height,
128+
zIndex: -1,
129+
...(parentId && { parentId }),
130+
},
131+
...children,
132+
];
133+
}

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

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import { NodeManager } from '../node-manager';
22
import { START_ID } from '../nodes';
3-
import { exportDiagram } from './export-diagram';
3+
import {
4+
createOperationNode,
5+
createSectionBufferNode,
6+
createSectionInputNode,
7+
createSectionOutputNode,
8+
} from './create-node';
9+
import { exportDiagram, exportTemplate } from './export-diagram';
410
import { loadDiagramJson } from './load-diagram';
511
import { joinNamespaces, ROOT_NAMESPACE } from './namespace';
612
import testDiagram from './test-data/test-diagram.json';
@@ -43,3 +49,36 @@ test('export diagram with scope', () => {
4349
diagram = exportDiagram(nodeManager, edges, {});
4450
expect(diagram.ops.scope.start).toBe('mul4');
4551
});
52+
53+
test('export diagram with templates', () => {
54+
const nodes = [
55+
createSectionInputNode('test_input', { x: 0, y: 0 }),
56+
createSectionOutputNode('test_output', { x: 0, y: 0 }),
57+
createSectionBufferNode('test_buffer', { x: 0, y: 0 }),
58+
createOperationNode(
59+
ROOT_NAMESPACE,
60+
undefined,
61+
{ x: 0, y: 0 },
62+
{ type: 'node', builder: 'test', next: 'test_output' },
63+
),
64+
createOperationNode(
65+
ROOT_NAMESPACE,
66+
undefined,
67+
{ x: 0, y: 0 },
68+
{ type: 'buffer' },
69+
),
70+
];
71+
const template = exportTemplate(new NodeManager(nodes), []);
72+
73+
if (typeof template.inputs !== 'object' || Array.isArray(template.inputs)) {
74+
throw new Error('expected template inputs to be a mapping');
75+
}
76+
expect(template.inputs.test_input).toBe('test_input');
77+
78+
expect(template.outputs?.[0]).toBe('test_output');
79+
80+
if (typeof template.buffers !== 'object' || Array.isArray(template.buffers)) {
81+
throw new Error('expected template buffers to be a mapping');
82+
}
83+
expect(template.buffers.test_buffer).toBe('test_buffer');
84+
});

0 commit comments

Comments
 (0)