Skip to content

Commit 80c698c

Browse files
committed
code generator example.
1 parent b67ca45 commit 80c698c

File tree

4 files changed

+272
-0
lines changed

4 files changed

+272
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Features:
2121
* [⛅ Light Dark](https://b4rtaz.github.io/sequential-workflow-designer/examples/light-dark.html)
2222
* [⏩ Live Testing](https://b4rtaz.github.io/sequential-workflow-designer/examples/live-testing.html)
2323
* [🔴 Particles](https://b4rtaz.github.io/sequential-workflow-designer/examples/particles.html)
24+
* [🤖 Code Generator](https://b4rtaz.github.io/sequential-workflow-designer/examples/code-generator.html)
2425

2526
## 🚀 Installation
2627

examples/assets/code-generator.js

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
/* global window, document, sequentialWorkflowDesigner */
2+
3+
let designer;
4+
5+
const nextId = () => sequentialWorkflowDesigner.nextId();
6+
7+
function createTaskStep(type, name, properties) {
8+
return {
9+
id: nextId(),
10+
componentType: 'task',
11+
type,
12+
name,
13+
properties
14+
};
15+
}
16+
17+
class Steps {
18+
static setNumber(name, targetVarName, value) {
19+
return createTaskStep('setNumber', name, {
20+
targetVarName,
21+
value
22+
});
23+
}
24+
25+
static assignVar(name, targetVarName, sourceVarName) {
26+
return createTaskStep('assignVar', name, {
27+
targetVarName,
28+
sourceVarName
29+
});
30+
}
31+
32+
static addVar(name, targetVarName, sourceVarName) {
33+
return createTaskStep('addVar', name, {
34+
targetVarName,
35+
sourceVarName
36+
});
37+
}
38+
39+
static loop(name, varName, from, to, sequence) {
40+
return {
41+
id: nextId(),
42+
componentType: 'container',
43+
type: 'loop',
44+
name,
45+
properties: {
46+
varName,
47+
from,
48+
to
49+
},
50+
sequence: sequence || []
51+
}
52+
}
53+
}
54+
55+
class CodeGenerator {
56+
static generateTask(step, variables) {
57+
const props = step.properties;
58+
switch (step.type) {
59+
case 'setNumber':
60+
variables.add(props.targetVarName);
61+
return `${props.targetVarName} = ${props.value};\r\n`
62+
case 'assignVar':
63+
variables.add(props.targetVarName);
64+
variables.add(props.sourceVarName);
65+
return `${props.targetVarName} = ${props.sourceVarName};\r\n`;
66+
case 'addNumber':
67+
variables.add(props.targetVarName);
68+
return `${props.targetVarName} += ${props.const};\r\n`;
69+
case 'addVar':
70+
variables.add(props.targetVarName);
71+
variables.add(props.sourceVarName);
72+
return `${props.targetVarName} += ${props.sourceVarName};\r\n`;
73+
case 'loop':
74+
variables.add(props.varName);
75+
return `for (${props.varName} = ${props.from}; ${props.varName} < ${props.to}; ${props.varName}++) {\r\n` +
76+
this.generateSequence(step.sequence, variables).replace(/(.*)\r\n/g, ' $1\r\n') +
77+
'}\r\n';
78+
}
79+
throw new Error(`Not supported step: ${step.type}`);
80+
}
81+
82+
static generateSequence(sequence, variables) {
83+
let code = '';
84+
for (let step of sequence) {
85+
code += this.generateTask(step, variables);
86+
}
87+
return code;
88+
}
89+
90+
static generateHeader(variables) {
91+
if (variables.size === 0) {
92+
return '';
93+
}
94+
return 'let ' + Array.from(variables).join(', ') + ';\r\n\r\n';
95+
}
96+
97+
static generateFooter(variables) {
98+
if (variables.size === 0) {
99+
return 'return null;';
100+
}
101+
return '\r\nreturn { ' + Array.from(variables).join(', ') + ' };';
102+
}
103+
}
104+
105+
class Editors {
106+
static createGlobalEditor() {
107+
const root = document.createElement('div');
108+
root.innerText = 'Select step.';
109+
return root;
110+
}
111+
112+
static createStepEditor(step) {
113+
const root = document.createElement('div');
114+
const title = document.createElement('h3');
115+
title.innerText = `Edit ${step.type} step`;
116+
root.appendChild(title);
117+
118+
const nameItem = document.createElement('p');
119+
nameItem.innerHTML = '<label>Name</label> <input type="text" />';
120+
const nameInput = nameItem.querySelector('input');
121+
nameInput.value = step.name;
122+
nameInput.addEventListener('input', () => {
123+
step.name = nameInput.value;
124+
designer.notifiyDefinitionChanged();
125+
});
126+
root.appendChild(nameItem);
127+
128+
const numberPropNames = ['value'];
129+
for (let propName of Object.keys(step.properties)) {
130+
const isNumberProp = numberPropNames.includes(propName);
131+
const item = document.createElement('p');
132+
item.innerHTML = `<label></label> <input type="${isNumberProp ? 'number' : 'text'}" />`;
133+
item.querySelector('label').innerText = propName;
134+
const input = item.querySelector('input');
135+
input.value = step.properties[propName];
136+
input.addEventListener('input', () => {
137+
const value = isNumberProp
138+
? parseInt(input.value)
139+
: input.value;
140+
step.properties[propName] = value;
141+
designer.notifiyDefinitionChanged();
142+
});
143+
root.appendChild(item);
144+
}
145+
return root;
146+
}
147+
}
148+
149+
function reload(definition) {
150+
const variables = new Set();
151+
const code = CodeGenerator.generateSequence(definition.sequence, variables);
152+
const header = CodeGenerator.generateHeader(variables);
153+
const footer = CodeGenerator.generateFooter(variables);
154+
const finalCode = header + code + footer;
155+
156+
const codeElement = document.getElementById('code');
157+
const resultElement = document.getElementById('result');
158+
159+
codeElement.innerHTML = finalCode;
160+
try {
161+
const func = new Function(finalCode);
162+
const result = func();
163+
resultElement.innerText = JSON.stringify(result, null, 2);
164+
} catch (e) {
165+
resultElement.innerText = e;
166+
}
167+
}
168+
169+
const configuration = {
170+
toolbox: {
171+
isHidden: false,
172+
groups: [
173+
{
174+
name: 'Expressions',
175+
steps: [
176+
Steps.setNumber('set number', 'X', 0),
177+
Steps.assignVar('assign var', 'X', 'Y'),
178+
Steps.addVar('add var', 'X', 'Y'),
179+
Steps.loop('loop', 'i', 0, 5)
180+
]
181+
}
182+
]
183+
},
184+
185+
steps: {
186+
iconUrlProvider: (_, type) => {
187+
const supportedIcons = ['loop'];
188+
const fileName = supportedIcons.includes(type) ? type : 'task';
189+
return `./assets/icon-${fileName}.svg`;
190+
},
191+
validator: () => {
192+
return true;
193+
}
194+
},
195+
196+
editors: {
197+
globalEditorProvider: () => {
198+
return Editors.createGlobalEditor();
199+
},
200+
stepEditorProvider: (step) => {
201+
return Editors.createStepEditor(step);
202+
}
203+
}
204+
};
205+
206+
const startDefinition = {
207+
properties: {},
208+
sequence: [
209+
Steps.setNumber('a = 1', 'a', 1),
210+
Steps.setNumber('b = 1', 'b', 1),
211+
Steps.loop('loop', 'i', 2, 7, [
212+
Steps.setNumber('F = 0', 'fibonacci', 0),
213+
Steps.addVar('F += a', 'fibonacci', 'a'),
214+
Steps.addVar('F += b', 'fibonacci', 'b'),
215+
Steps.assignVar('a = b', 'a', 'b'),
216+
Steps.assignVar('b = F', 'b', 'fibonacci')
217+
])
218+
]
219+
};
220+
221+
window.addEventListener('load', () => {
222+
const placeholder = document.getElementById('designer');
223+
designer = sequentialWorkflowDesigner.create(placeholder, startDefinition, configuration);
224+
designer.onDefinitionChanged.subscribe(reload);
225+
reload(startDefinition);
226+
});

examples/code-generator.html

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="UTF-8">
5+
<title>🤖 Code Generator - Sequential Workflow Designer</title>
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
7+
8+
<style>
9+
body {font: 14px/1.3em 'Open Sans', Arial, Verdana, Serif;}
10+
html, body {width: 100vw; height: 100vh; margin: 0; padding: 0; overflow: hidden;}
11+
#designer {position: absolute; top: 0; left: 0; bottom: 0; right: 30%;}
12+
#output {position: absolute; top: 0; right: 0; bottom: 0; left: 70%; padding: 10px; box-sizing: border-box; color: #FFF; background: #151515;}
13+
#output #code {color: silver;}
14+
#output #result {color: yellow;}
15+
16+
.sqd-global-editor {padding: 10px;}
17+
18+
.sqd-smart-editor h3 {margin: 0; padding: 10px;}
19+
.sqd-smart-editor p {margin: 0; padding: 10px;}
20+
.sqd-smart-editor p label {display: block; padding: 0 0 5px;}
21+
.sqd-smart-editor input {width: 100%; box-sizing: border-box; border: 1px solid silver; padding: 6px; border-radius: 5px;}
22+
</style>
23+
</head>
24+
<body>
25+
<div id="designer"></div>
26+
27+
<div id="output">
28+
<h4>Generated Code</h4>
29+
30+
<pre id="code"></pre>
31+
32+
<h4>Result</h4>
33+
34+
<pre id="result"></pre>
35+
</div>
36+
37+
<script src="./assets/lib.js"></script>
38+
<script src="./assets/code-generator.js"></script>
39+
</body>
40+
</html>

src/designer.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { BehaviorController } from './behaviors/behavior-controller';
22
import { ObjectCloner } from './core/object-cloner';
33
import { SimpleEvent } from './core/simple-event';
4+
import { Uid } from './core/uid';
45
import { Definition } from './definition';
56
import { DesignerConfiguration } from './designer-configuration';
67
import { DesignerContext } from './designer-context';
@@ -23,6 +24,10 @@ export default class Designer {
2324
return designer;
2425
}
2526

27+
public static nextId(): string {
28+
return Uid.next();
29+
}
30+
2631
private constructor(private readonly view: DesignerView, private readonly context: DesignerContext) {}
2732

2833
public readonly onDefinitionChanged = new SimpleEvent<Definition>();

0 commit comments

Comments
 (0)