sequential-workflow-designer 0.10.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +9 -0
- package/README.md +205 -0
- package/css/designer-dark.css +148 -0
- package/css/designer-light.css +148 -0
- package/css/designer.css +259 -0
- package/dist/index.umd.js +3842 -0
- package/lib/cjs/index.cjs +3844 -0
- package/lib/esm/index.js +3799 -0
- package/lib/index.d.ts +858 -0
- package/package.json +88 -0
package/lib/esm/index.js
ADDED
|
@@ -0,0 +1,3799 @@
|
|
|
1
|
+
export * from 'sequential-workflow-model';
|
|
2
|
+
|
|
3
|
+
class ControlBarApi {
|
|
4
|
+
constructor(state, historyController, definitionModifier, viewportApi) {
|
|
5
|
+
this.state = state;
|
|
6
|
+
this.historyController = historyController;
|
|
7
|
+
this.definitionModifier = definitionModifier;
|
|
8
|
+
this.viewportApi = viewportApi;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* @deprecated Don't use this method
|
|
12
|
+
*/
|
|
13
|
+
subscribe(handler) {
|
|
14
|
+
// TODO: this should be refactored
|
|
15
|
+
this.state.onIsReadonlyChanged.subscribe(handler);
|
|
16
|
+
this.state.onSelectedStepIdChanged.subscribe(handler);
|
|
17
|
+
this.state.onIsDragDisabledChanged.subscribe(handler);
|
|
18
|
+
if (this.isUndoRedoSupported()) {
|
|
19
|
+
this.state.onDefinitionChanged.subscribe(handler);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
resetViewport() {
|
|
23
|
+
this.viewportApi.resetViewport();
|
|
24
|
+
}
|
|
25
|
+
zoomIn() {
|
|
26
|
+
this.viewportApi.zoom(true);
|
|
27
|
+
}
|
|
28
|
+
zoomOut() {
|
|
29
|
+
this.viewportApi.zoom(false);
|
|
30
|
+
}
|
|
31
|
+
isDragDisabled() {
|
|
32
|
+
return this.state.isDragDisabled;
|
|
33
|
+
}
|
|
34
|
+
toggleIsDragDisabled() {
|
|
35
|
+
this.state.toggleIsDragDisabled();
|
|
36
|
+
}
|
|
37
|
+
isUndoRedoSupported() {
|
|
38
|
+
return !!this.historyController;
|
|
39
|
+
}
|
|
40
|
+
tryUndo() {
|
|
41
|
+
if (this.canUndo() && this.historyController) {
|
|
42
|
+
this.historyController.undo();
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
canUndo() {
|
|
48
|
+
return !!this.historyController && this.historyController.canUndo() && !this.state.isReadonly && !this.state.isDragging;
|
|
49
|
+
}
|
|
50
|
+
tryRedo() {
|
|
51
|
+
if (this.canRedo() && this.historyController) {
|
|
52
|
+
this.historyController.redo();
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
canRedo() {
|
|
58
|
+
return !!this.historyController && this.historyController.canRedo() && !this.state.isReadonly && !this.state.isDragging;
|
|
59
|
+
}
|
|
60
|
+
tryDelete() {
|
|
61
|
+
if (this.canDelete() && this.state.selectedStepId) {
|
|
62
|
+
this.definitionModifier.tryDelete(this.state.selectedStepId);
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
canDelete() {
|
|
68
|
+
return (!!this.state.selectedStepId &&
|
|
69
|
+
!this.state.isReadonly &&
|
|
70
|
+
!this.state.isDragging &&
|
|
71
|
+
this.definitionModifier.isDeletable(this.state.selectedStepId));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
class SimpleEvent {
|
|
76
|
+
constructor() {
|
|
77
|
+
this.listeners = [];
|
|
78
|
+
}
|
|
79
|
+
subscribe(listener) {
|
|
80
|
+
this.listeners.push(listener);
|
|
81
|
+
}
|
|
82
|
+
unsubscribe(listener) {
|
|
83
|
+
const index = this.listeners.indexOf(listener);
|
|
84
|
+
if (index >= 0) {
|
|
85
|
+
this.listeners.splice(index, 1);
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
throw new Error('Unknown listener');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
forward(value) {
|
|
92
|
+
if (this.listeners.length > 0) {
|
|
93
|
+
this.listeners.forEach(listener => listener(value));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
count() {
|
|
97
|
+
return this.listeners.length;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
class Vector {
|
|
102
|
+
constructor(x, y) {
|
|
103
|
+
this.x = x;
|
|
104
|
+
this.y = y;
|
|
105
|
+
}
|
|
106
|
+
add(v) {
|
|
107
|
+
return new Vector(this.x + v.x, this.y + v.y);
|
|
108
|
+
}
|
|
109
|
+
subtract(v) {
|
|
110
|
+
return new Vector(this.x - v.x, this.y - v.y);
|
|
111
|
+
}
|
|
112
|
+
multiplyByScalar(s) {
|
|
113
|
+
return new Vector(this.x * s, this.y * s);
|
|
114
|
+
}
|
|
115
|
+
divideByScalar(s) {
|
|
116
|
+
return new Vector(this.x / s, this.y / s);
|
|
117
|
+
}
|
|
118
|
+
round() {
|
|
119
|
+
return new Vector(Math.round(this.x), Math.round(this.y));
|
|
120
|
+
}
|
|
121
|
+
distance() {
|
|
122
|
+
return Math.sqrt(Math.pow(this.x, 2) + Math.pow(this.y, 2));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
var DefinitionChangeType;
|
|
127
|
+
(function (DefinitionChangeType) {
|
|
128
|
+
DefinitionChangeType[DefinitionChangeType["stepNameChanged"] = 1] = "stepNameChanged";
|
|
129
|
+
DefinitionChangeType[DefinitionChangeType["stepPropertyChanged"] = 2] = "stepPropertyChanged";
|
|
130
|
+
DefinitionChangeType[DefinitionChangeType["stepChildrenChanged"] = 3] = "stepChildrenChanged";
|
|
131
|
+
DefinitionChangeType[DefinitionChangeType["stepDeleted"] = 4] = "stepDeleted";
|
|
132
|
+
DefinitionChangeType[DefinitionChangeType["stepMoved"] = 5] = "stepMoved";
|
|
133
|
+
DefinitionChangeType[DefinitionChangeType["stepInserted"] = 6] = "stepInserted";
|
|
134
|
+
DefinitionChangeType[DefinitionChangeType["globalPropertyChanged"] = 7] = "globalPropertyChanged";
|
|
135
|
+
DefinitionChangeType[DefinitionChangeType["rootReplaced"] = 8] = "rootReplaced";
|
|
136
|
+
})(DefinitionChangeType || (DefinitionChangeType = {}));
|
|
137
|
+
class DesignerState {
|
|
138
|
+
constructor(definition, isReadonly) {
|
|
139
|
+
this.definition = definition;
|
|
140
|
+
this.isReadonly = isReadonly;
|
|
141
|
+
this.onViewportChanged = new SimpleEvent();
|
|
142
|
+
this.onSelectedStepIdChanged = new SimpleEvent();
|
|
143
|
+
this.onFolderPathChanged = new SimpleEvent();
|
|
144
|
+
this.onIsReadonlyChanged = new SimpleEvent();
|
|
145
|
+
this.onIsDraggingChanged = new SimpleEvent();
|
|
146
|
+
this.onIsDragDisabledChanged = new SimpleEvent();
|
|
147
|
+
this.onDefinitionChanged = new SimpleEvent();
|
|
148
|
+
this.viewport = {
|
|
149
|
+
position: new Vector(0, 0),
|
|
150
|
+
scale: 1
|
|
151
|
+
};
|
|
152
|
+
this.selectedStepId = null;
|
|
153
|
+
this.folderPath = [];
|
|
154
|
+
this.isDragging = false;
|
|
155
|
+
this.isDragDisabled = false;
|
|
156
|
+
}
|
|
157
|
+
setSelectedStepId(stepId) {
|
|
158
|
+
if (this.selectedStepId !== stepId) {
|
|
159
|
+
this.selectedStepId = stepId;
|
|
160
|
+
this.onSelectedStepIdChanged.forward(stepId);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
pushStepIdToFolderPath(stepId) {
|
|
164
|
+
this.folderPath.push(stepId);
|
|
165
|
+
this.onFolderPathChanged.forward(this.folderPath);
|
|
166
|
+
}
|
|
167
|
+
setFolderPath(path) {
|
|
168
|
+
this.folderPath = path;
|
|
169
|
+
this.onFolderPathChanged.forward(path);
|
|
170
|
+
}
|
|
171
|
+
tryGetLastStepIdFromFolderPath() {
|
|
172
|
+
return this.folderPath.length > 0 ? this.folderPath[this.folderPath.length - 1] : null;
|
|
173
|
+
}
|
|
174
|
+
setDefinition(definition) {
|
|
175
|
+
this.definition = definition;
|
|
176
|
+
this.notifyDefinitionChanged(DefinitionChangeType.rootReplaced, null);
|
|
177
|
+
}
|
|
178
|
+
notifyDefinitionChanged(changeType, stepId) {
|
|
179
|
+
this.onDefinitionChanged.forward({ changeType, stepId });
|
|
180
|
+
}
|
|
181
|
+
setViewport(viewport) {
|
|
182
|
+
this.viewport = viewport;
|
|
183
|
+
this.onViewportChanged.forward(viewport);
|
|
184
|
+
}
|
|
185
|
+
setIsReadonly(isReadonly) {
|
|
186
|
+
if (this.isReadonly !== isReadonly) {
|
|
187
|
+
this.isReadonly = isReadonly;
|
|
188
|
+
this.onIsReadonlyChanged.forward(isReadonly);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
setIsDragging(isDragging) {
|
|
192
|
+
if (this.isDragging !== isDragging) {
|
|
193
|
+
this.isDragging = isDragging;
|
|
194
|
+
this.onIsDraggingChanged.forward(isDragging);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
toggleIsDragDisabled() {
|
|
198
|
+
this.isDragDisabled = !this.isDragDisabled;
|
|
199
|
+
this.onIsDragDisabledChanged.forward(this.isDragDisabled);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
class Dom {
|
|
204
|
+
static svg(name, attributes) {
|
|
205
|
+
const element = document.createElementNS('http://www.w3.org/2000/svg', name);
|
|
206
|
+
if (attributes) {
|
|
207
|
+
Dom.attrs(element, attributes);
|
|
208
|
+
}
|
|
209
|
+
return element;
|
|
210
|
+
}
|
|
211
|
+
static translate(element, x, y) {
|
|
212
|
+
element.setAttribute('transform', `translate(${x}, ${y})`);
|
|
213
|
+
}
|
|
214
|
+
static attrs(element, attributes) {
|
|
215
|
+
Object.keys(attributes).forEach(name => {
|
|
216
|
+
const value = attributes[name];
|
|
217
|
+
element.setAttribute(name, typeof value === 'string' ? value : value.toString());
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
static element(name, attributes) {
|
|
221
|
+
const element = document.createElement(name);
|
|
222
|
+
if (attributes) {
|
|
223
|
+
Dom.attrs(element, attributes);
|
|
224
|
+
}
|
|
225
|
+
return element;
|
|
226
|
+
}
|
|
227
|
+
static toggleClass(element, isEnabled, className) {
|
|
228
|
+
if (isEnabled) {
|
|
229
|
+
element.classList.add(className);
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
element.classList.remove(className);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Source: https://fonts.google.com/icons or https://github.com/google/material-design-icons
|
|
238
|
+
class Icons {
|
|
239
|
+
static appendPath(parent, pathClassName, d, size) {
|
|
240
|
+
const g = Dom.svg('g');
|
|
241
|
+
const scale = size / 48;
|
|
242
|
+
const path = Dom.svg('path', {
|
|
243
|
+
d,
|
|
244
|
+
class: pathClassName,
|
|
245
|
+
transform: `scale(${scale})`
|
|
246
|
+
});
|
|
247
|
+
g.appendChild(path);
|
|
248
|
+
parent.appendChild(g);
|
|
249
|
+
return g;
|
|
250
|
+
}
|
|
251
|
+
static createSvg(className, d) {
|
|
252
|
+
const icon = Dom.svg('svg', {
|
|
253
|
+
class: className,
|
|
254
|
+
viewBox: '0 0 48 48'
|
|
255
|
+
});
|
|
256
|
+
const path = Dom.svg('path', {
|
|
257
|
+
d,
|
|
258
|
+
class: 'sqd-icon-path'
|
|
259
|
+
});
|
|
260
|
+
icon.appendChild(path);
|
|
261
|
+
return icon;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
Icons.folderIn = 'M42.05 42.25H11.996v-7.12h17.388L6 11.746 11.546 6.2 34.93 29.584V12.196h7.12V42.25z';
|
|
265
|
+
Icons.folderOut = 'M6 6.2h30.054v7.12H18.666L42.05 36.704l-5.546 5.546L13.12 18.866v17.388H6V6.2z';
|
|
266
|
+
Icons.center = 'M9 42q-1.2 0-2.1-.9Q6 40.2 6 39v-8.6h3V39h8.6v3Zm21.4 0v-3H39v-8.6h3V39q0 1.2-.9 2.1-.9.9-2.1.9ZM24 31.15q-3.15 0-5.15-2-2-2-2-5.15 0-3.15 2-5.15 2-2 5.15-2 3.15 0 5.15 2 2 2 2 5.15 0 3.15-2 5.15-2 2-5.15 2ZM6 17.6V9q0-1.2.9-2.1Q7.8 6 9 6h8.6v3H9v8.6Zm33 0V9h-8.6V6H39q1.2 0 2.1.9.9.9.9 2.1v8.6Z';
|
|
267
|
+
Icons.zoomIn = 'M39.8 41.95 26.65 28.8q-1.5 1.3-3.5 2.025-2 .725-4.25.725-5.4 0-9.15-3.75T6 18.75q0-5.3 3.75-9.05 3.75-3.75 9.1-3.75 5.3 0 9.025 3.75 3.725 3.75 3.725 9.05 0 2.15-.7 4.15-.7 2-2.1 3.75L42 39.75Zm-20.95-13.4q4.05 0 6.9-2.875Q28.6 22.8 28.6 18.75t-2.85-6.925Q22.9 8.95 18.85 8.95q-4.1 0-6.975 2.875T9 18.75q0 4.05 2.875 6.925t6.975 2.875ZM17.3 24.3v-4.1h-4.1v-3h4.1v-4.05h3v4.05h4.05v3H20.3v4.1Z';
|
|
268
|
+
Icons.zoomOut = 'M39.8 41.95 26.65 28.8q-1.5 1.3-3.5 2.025-2 .725-4.25.725-5.4 0-9.15-3.75T6 18.75q0-5.3 3.75-9.05 3.75-3.75 9.1-3.75 5.3 0 9.025 3.75 3.725 3.75 3.725 9.05 0 2.15-.7 4.15-.7 2-2.1 3.75L42 39.75Zm-20.95-13.4q4.05 0 6.9-2.875Q28.6 22.8 28.6 18.75t-2.85-6.925Q22.9 8.95 18.85 8.95q-4.1 0-6.975 2.875T9 18.75q0 4.05 2.875 6.925t6.975 2.875Zm-5.1-8.35v-3H23.8v3Z';
|
|
269
|
+
Icons.undo = 'M14 38v-3h14.45q3.5 0 6.025-2.325Q37 30.35 37 26.9t-2.525-5.775Q31.95 18.8 28.45 18.8H13.7l5.7 5.7-2.1 2.1L8 17.3 17.3 8l2.1 2.1-5.7 5.7h14.7q4.75 0 8.175 3.2Q40 22.2 40 26.9t-3.425 7.9Q33.15 38 28.4 38Z';
|
|
270
|
+
Icons.redo = 'M19.6 38q-4.75 0-8.175-3.2Q8 31.6 8 26.9t3.425-7.9q3.425-3.2 8.175-3.2h14.7l-5.7-5.7L30.7 8l9.3 9.3-9.3 9.3-2.1-2.1 5.7-5.7H19.55q-3.5 0-6.025 2.325Q11 23.45 11 26.9t2.525 5.775Q16.05 35 19.55 35H34v3Z';
|
|
271
|
+
Icons.move = 'm24 44-8.15-8.15 2.2-2.2 4.45 4.45v-9.45h3v9.45l4.45-4.45 2.2 2.2ZM11.9 31.9 4 24l7.95-7.95 2.2 2.2L9.9 22.5h9.45v3H9.9l4.2 4.2Zm24.2 0-2.2-2.2 4.2-4.2h-9.4v-3h9.4l-4.2-4.2 2.2-2.2L44 24ZM22.5 19.3V9.9l-4.2 4.2-2.2-2.2L24 4l7.9 7.9-2.2 2.2-4.2-4.2v9.4Z';
|
|
272
|
+
Icons.delete = 'm16.5 33.6 7.5-7.5 7.5 7.5 2.1-2.1-7.5-7.5 7.5-7.5-2.1-2.1-7.5 7.5-7.5-7.5-2.1 2.1 7.5 7.5-7.5 7.5ZM24 44q-4.1 0-7.75-1.575-3.65-1.575-6.375-4.3-2.725-2.725-4.3-6.375Q4 28.1 4 24q0-4.15 1.575-7.8 1.575-3.65 4.3-6.35 2.725-2.7 6.375-4.275Q19.9 4 24 4q4.15 0 7.8 1.575 3.65 1.575 6.35 4.275 2.7 2.7 4.275 6.35Q44 19.85 44 24q0 4.1-1.575 7.75-1.575 3.65-4.275 6.375t-6.35 4.3Q28.15 44 24 44Z';
|
|
273
|
+
Icons.folderUp = 'M22.5 34h3V23.75l3.7 3.7 2.1-2.1-7.3-7.3-7.3 7.3 2.1 2.1 3.7-3.7ZM7.05 40q-1.2 0-2.1-.925-.9-.925-.9-2.075V11q0-1.15.9-2.075Q5.85 8 7.05 8h14l3 3h17q1.15 0 2.075.925.925.925.925 2.075v23q0 1.15-.925 2.075Q42.2 40 41.05 40Zm0-29v26h34V14H22.8l-3-3H7.05Zm0 0v26Z';
|
|
274
|
+
Icons.close = 'm12.45 37.65-2.1-2.1L21.9 24 10.35 12.45l2.1-2.1L24 21.9l11.55-11.55 2.1 2.1L26.1 24l11.55 11.55-2.1 2.1L24 26.1Z';
|
|
275
|
+
Icons.options = 'm19.4 44-1-6.3q-.95-.35-2-.95t-1.85-1.25l-5.9 2.7L4 30l5.4-3.95q-.1-.45-.125-1.025Q9.25 24.45 9.25 24q0-.45.025-1.025T9.4 21.95L4 18l4.65-8.2 5.9 2.7q.8-.65 1.85-1.25t2-.9l1-6.35h9.2l1 6.3q.95.35 2.025.925Q32.7 11.8 33.45 12.5l5.9-2.7L44 18l-5.4 3.85q.1.5.125 1.075.025.575.025 1.075t-.025 1.05q-.025.55-.125 1.05L44 30l-4.65 8.2-5.9-2.7q-.8.65-1.825 1.275-1.025.625-2.025.925l-1 6.3ZM24 30.5q2.7 0 4.6-1.9 1.9-1.9 1.9-4.6 0-2.7-1.9-4.6-1.9-1.9-4.6-1.9-2.7 0-4.6 1.9-1.9 1.9-1.9 4.6 0 2.7 1.9 4.6 1.9 1.9 4.6 1.9Z';
|
|
276
|
+
Icons.expand = 'm24 30.75-12-12 2.15-2.15L24 26.5l9.85-9.85L36 18.8Z';
|
|
277
|
+
Icons.alert = 'M24 42q-1.45 0-2.475-1.025Q20.5 39.95 20.5 38.5q0-1.45 1.025-2.475Q22.55 35 24 35q1.45 0 2.475 1.025Q27.5 37.05 27.5 38.5q0 1.45-1.025 2.475Q25.45 42 24 42Zm-3.5-12V6h7v24Z';
|
|
278
|
+
Icons.play = 'M14.75 40.15V7.55l25.6 16.3Z';
|
|
279
|
+
Icons.stop = 'M10.75 37.25V10.7H37.3v26.55Z';
|
|
280
|
+
Icons.folder = 'M7.05 40q-1.2 0-2.1-.925-.9-.925-.9-2.075V11q0-1.15.9-2.075Q5.85 8 7.05 8h14l3 3h17q1.15 0 2.075.925.925.925.925 2.075v23q0 1.15-.925 2.075Q42.2 40 41.05 40Z';
|
|
281
|
+
|
|
282
|
+
class ObjectCloner {
|
|
283
|
+
static deepClone(instance) {
|
|
284
|
+
if (typeof window.structuredClone !== 'undefined') {
|
|
285
|
+
return window.structuredClone(instance);
|
|
286
|
+
}
|
|
287
|
+
return JSON.parse(JSON.stringify(instance));
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
class Uid {
|
|
292
|
+
static next() {
|
|
293
|
+
const bytes = new Uint8Array(16);
|
|
294
|
+
window.crypto.getRandomValues(bytes);
|
|
295
|
+
return Array.from(bytes, v => v.toString(16).padStart(2, '0')).join('');
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
var StepChildrenType;
|
|
300
|
+
(function (StepChildrenType) {
|
|
301
|
+
StepChildrenType[StepChildrenType["singleSequence"] = 1] = "singleSequence";
|
|
302
|
+
StepChildrenType[StepChildrenType["branches"] = 2] = "branches";
|
|
303
|
+
})(StepChildrenType || (StepChildrenType = {}));
|
|
304
|
+
|
|
305
|
+
class StepsTraverser {
|
|
306
|
+
constructor(stepExtensionResolver) {
|
|
307
|
+
this.stepExtensionResolver = stepExtensionResolver;
|
|
308
|
+
}
|
|
309
|
+
getChildren(step) {
|
|
310
|
+
const resolver = this.stepExtensionResolver.resolve(step.componentType);
|
|
311
|
+
return resolver.getChildren(step);
|
|
312
|
+
}
|
|
313
|
+
find(sequence, needSequence, needStepId, result) {
|
|
314
|
+
if (needSequence && sequence === needSequence) {
|
|
315
|
+
return true;
|
|
316
|
+
}
|
|
317
|
+
const count = sequence.length;
|
|
318
|
+
for (let index = 0; index < count; index++) {
|
|
319
|
+
const step = sequence[index];
|
|
320
|
+
if (needStepId && step.id === needStepId) {
|
|
321
|
+
result.push({ step, index, parentSequence: sequence });
|
|
322
|
+
return true;
|
|
323
|
+
}
|
|
324
|
+
const children = this.getChildren(step);
|
|
325
|
+
if (children) {
|
|
326
|
+
switch (children.type) {
|
|
327
|
+
case StepChildrenType.singleSequence:
|
|
328
|
+
{
|
|
329
|
+
const parentSequence = children.sequences;
|
|
330
|
+
if (this.find(parentSequence, needSequence, needStepId, result)) {
|
|
331
|
+
result.push({ step, index, parentSequence });
|
|
332
|
+
return true;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
break;
|
|
336
|
+
case StepChildrenType.branches:
|
|
337
|
+
{
|
|
338
|
+
const branches = children.sequences;
|
|
339
|
+
const branchNames = Object.keys(branches);
|
|
340
|
+
for (const branchName of branchNames) {
|
|
341
|
+
const parentSequence = branches[branchName];
|
|
342
|
+
if (this.find(parentSequence, needSequence, needStepId, result)) {
|
|
343
|
+
result.push(branchName);
|
|
344
|
+
result.push({ step, index, parentSequence });
|
|
345
|
+
return true;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
break;
|
|
350
|
+
default:
|
|
351
|
+
throw new Error(`Step children type ${children.type} is not supported`);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
return false;
|
|
356
|
+
}
|
|
357
|
+
getParents(definition, needle) {
|
|
358
|
+
const result = [];
|
|
359
|
+
const searchSequence = Array.isArray(needle) ? needle : null;
|
|
360
|
+
const searchStepId = !searchSequence ? needle.id : null;
|
|
361
|
+
if (this.find(definition.sequence, searchSequence, searchStepId, result)) {
|
|
362
|
+
result.reverse();
|
|
363
|
+
return result.map(item => {
|
|
364
|
+
return typeof item === 'string' ? item : item.step;
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
throw new Error(searchStepId ? `Cannot get parents of step: ${searchStepId}` : 'Cannot get parents of sequence');
|
|
368
|
+
}
|
|
369
|
+
findParentSequence(definition, stepId) {
|
|
370
|
+
const result = [];
|
|
371
|
+
if (this.find(definition.sequence, null, stepId, result)) {
|
|
372
|
+
return result[0];
|
|
373
|
+
}
|
|
374
|
+
return null;
|
|
375
|
+
}
|
|
376
|
+
getParentSequence(definition, stepId) {
|
|
377
|
+
const result = this.findParentSequence(definition, stepId);
|
|
378
|
+
if (!result) {
|
|
379
|
+
throw new Error(`Cannot find step by id: ${stepId}`);
|
|
380
|
+
}
|
|
381
|
+
return result;
|
|
382
|
+
}
|
|
383
|
+
findById(definition, stepId) {
|
|
384
|
+
const result = this.findParentSequence(definition, stepId);
|
|
385
|
+
return result ? result.step : null;
|
|
386
|
+
}
|
|
387
|
+
getById(definition, stepId) {
|
|
388
|
+
return this.getParentSequence(definition, stepId).step;
|
|
389
|
+
}
|
|
390
|
+
getChildAndParentSequences(definition, stepId) {
|
|
391
|
+
const result = this.getParentSequence(definition, stepId);
|
|
392
|
+
const lastStepChildren = this.getChildren(result.step);
|
|
393
|
+
if (!lastStepChildren || lastStepChildren.type !== StepChildrenType.singleSequence) {
|
|
394
|
+
throw new Error(`Cannot find single sequence in step: ${stepId}`);
|
|
395
|
+
}
|
|
396
|
+
const childSequence = lastStepChildren.sequences;
|
|
397
|
+
return { index: result.index, parentSequence: result.parentSequence, childSequence };
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function race(timeout, a, b, c) {
|
|
402
|
+
const value = [undefined, undefined, undefined];
|
|
403
|
+
const result = new SimpleEvent();
|
|
404
|
+
let scheduled = false;
|
|
405
|
+
function forward() {
|
|
406
|
+
if (scheduled) {
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
scheduled = true;
|
|
410
|
+
setTimeout(() => {
|
|
411
|
+
try {
|
|
412
|
+
result.forward(value);
|
|
413
|
+
}
|
|
414
|
+
finally {
|
|
415
|
+
scheduled = false;
|
|
416
|
+
value.fill(undefined);
|
|
417
|
+
}
|
|
418
|
+
}, timeout);
|
|
419
|
+
}
|
|
420
|
+
[a, b, c]
|
|
421
|
+
.filter(e => e)
|
|
422
|
+
.forEach((e, index) => {
|
|
423
|
+
e.subscribe(v => {
|
|
424
|
+
value[index] = v;
|
|
425
|
+
forward();
|
|
426
|
+
});
|
|
427
|
+
});
|
|
428
|
+
return result;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
class EditorRenderer {
|
|
432
|
+
static create(state, stepsTraverser, handler) {
|
|
433
|
+
const listener = new EditorRenderer(state, stepsTraverser, handler);
|
|
434
|
+
race(0, state.onDefinitionChanged, state.onSelectedStepIdChanged).subscribe(r => {
|
|
435
|
+
const [definitionChanged, selectedStepId] = r;
|
|
436
|
+
if (definitionChanged) {
|
|
437
|
+
listener.onDefinitionChanged(definitionChanged);
|
|
438
|
+
}
|
|
439
|
+
else if (selectedStepId !== undefined) {
|
|
440
|
+
listener.onSelectedStepIdChanged(selectedStepId);
|
|
441
|
+
}
|
|
442
|
+
});
|
|
443
|
+
listener.tryRender(state.selectedStepId);
|
|
444
|
+
return listener;
|
|
445
|
+
}
|
|
446
|
+
constructor(state, stepsTraverser, handler) {
|
|
447
|
+
this.state = state;
|
|
448
|
+
this.stepsTraverser = stepsTraverser;
|
|
449
|
+
this.handler = handler;
|
|
450
|
+
this.currentStepId = undefined;
|
|
451
|
+
}
|
|
452
|
+
destroy() {
|
|
453
|
+
// TODO: unsubscribe from events
|
|
454
|
+
}
|
|
455
|
+
render(stepId) {
|
|
456
|
+
const step = stepId ? this.stepsTraverser.getById(this.state.definition, stepId) : null;
|
|
457
|
+
this.currentStepId = stepId;
|
|
458
|
+
this.handler(step);
|
|
459
|
+
}
|
|
460
|
+
tryRender(stepId) {
|
|
461
|
+
if (this.currentStepId !== stepId) {
|
|
462
|
+
this.render(stepId);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
onDefinitionChanged(event) {
|
|
466
|
+
if (event.changeType === DefinitionChangeType.rootReplaced) {
|
|
467
|
+
this.render(this.state.selectedStepId);
|
|
468
|
+
}
|
|
469
|
+
else {
|
|
470
|
+
this.tryRender(this.state.selectedStepId);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
onSelectedStepIdChanged(stepId) {
|
|
474
|
+
this.tryRender(stepId);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
class EditorApi {
|
|
479
|
+
constructor(state, stepsTraverser, layoutController, definitionModifier) {
|
|
480
|
+
this.state = state;
|
|
481
|
+
this.stepsTraverser = stepsTraverser;
|
|
482
|
+
this.layoutController = layoutController;
|
|
483
|
+
this.definitionModifier = definitionModifier;
|
|
484
|
+
}
|
|
485
|
+
isVisibleAtStart() {
|
|
486
|
+
return this.layoutController.isMobile();
|
|
487
|
+
}
|
|
488
|
+
getDefinition() {
|
|
489
|
+
return this.state.definition;
|
|
490
|
+
}
|
|
491
|
+
runRenderer(rendererHandler) {
|
|
492
|
+
return EditorRenderer.create(this.state, this.stepsTraverser, rendererHandler);
|
|
493
|
+
}
|
|
494
|
+
createStepEditorContext(stepId) {
|
|
495
|
+
return {
|
|
496
|
+
notifyPropertiesChanged: () => {
|
|
497
|
+
this.state.notifyDefinitionChanged(DefinitionChangeType.stepPropertyChanged, stepId);
|
|
498
|
+
},
|
|
499
|
+
notifyNameChanged: () => {
|
|
500
|
+
this.state.notifyDefinitionChanged(DefinitionChangeType.stepNameChanged, stepId);
|
|
501
|
+
},
|
|
502
|
+
notifyChildrenChanged: () => {
|
|
503
|
+
this.state.notifyDefinitionChanged(DefinitionChangeType.stepChildrenChanged, stepId);
|
|
504
|
+
this.definitionModifier.updateDependantFields();
|
|
505
|
+
}
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
createGlobalEditorContext() {
|
|
509
|
+
return {
|
|
510
|
+
notifyPropertiesChanged: () => {
|
|
511
|
+
this.state.notifyDefinitionChanged(DefinitionChangeType.globalPropertyChanged, null);
|
|
512
|
+
}
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
class PathBarApi {
|
|
518
|
+
constructor(state, stepsTraverser) {
|
|
519
|
+
this.state = state;
|
|
520
|
+
this.stepsTraverser = stepsTraverser;
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* @deprecated Don't use this method
|
|
524
|
+
*/
|
|
525
|
+
subscribe(handler) {
|
|
526
|
+
// TODO: this should be refactored
|
|
527
|
+
race(0, this.state.onFolderPathChanged, this.state.onDefinitionChanged).subscribe(handler);
|
|
528
|
+
}
|
|
529
|
+
setFolderPath(path) {
|
|
530
|
+
this.state.setFolderPath(path);
|
|
531
|
+
}
|
|
532
|
+
getFolderPath() {
|
|
533
|
+
return this.state.folderPath;
|
|
534
|
+
}
|
|
535
|
+
getFolderPathStepNames() {
|
|
536
|
+
return this.state.folderPath.map(stepId => {
|
|
537
|
+
return this.stepsTraverser.getById(this.state.definition, stepId).name;
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
class DragStepView {
|
|
543
|
+
static create(step, configuration, componentContext) {
|
|
544
|
+
const theme = configuration.theme || 'light';
|
|
545
|
+
const layer = Dom.element('div', {
|
|
546
|
+
class: `sqd-drag sqd-theme-${theme}`
|
|
547
|
+
});
|
|
548
|
+
document.body.appendChild(layer);
|
|
549
|
+
const component = componentContext.services.draggedComponent.create(layer, step, componentContext);
|
|
550
|
+
return new DragStepView(component, layer);
|
|
551
|
+
}
|
|
552
|
+
constructor(component, layer) {
|
|
553
|
+
this.component = component;
|
|
554
|
+
this.layer = layer;
|
|
555
|
+
}
|
|
556
|
+
setPosition(position) {
|
|
557
|
+
this.layer.style.top = position.y + 'px';
|
|
558
|
+
this.layer.style.left = position.x + 'px';
|
|
559
|
+
}
|
|
560
|
+
remove() {
|
|
561
|
+
this.component.destroy();
|
|
562
|
+
document.body.removeChild(this.layer);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
class PlaceholderFinder {
|
|
567
|
+
static create(placeholders, state) {
|
|
568
|
+
const checker = new PlaceholderFinder(placeholders, state);
|
|
569
|
+
state.onViewportChanged.subscribe(checker.clearCacheHandler);
|
|
570
|
+
window.addEventListener('scroll', checker.clearCacheHandler, false);
|
|
571
|
+
return checker;
|
|
572
|
+
}
|
|
573
|
+
constructor(placeholders, state) {
|
|
574
|
+
this.placeholders = placeholders;
|
|
575
|
+
this.state = state;
|
|
576
|
+
this.clearCacheHandler = () => this.clearCache();
|
|
577
|
+
}
|
|
578
|
+
find(vLt, vWidth, vHeight) {
|
|
579
|
+
var _a;
|
|
580
|
+
if (!this.cache) {
|
|
581
|
+
const scroll = new Vector(window.scrollX, window.scrollY);
|
|
582
|
+
this.cache = this.placeholders.map(placeholder => {
|
|
583
|
+
const rect = placeholder.getClientRect();
|
|
584
|
+
return {
|
|
585
|
+
placeholder,
|
|
586
|
+
lt: new Vector(rect.x, rect.y).add(scroll),
|
|
587
|
+
br: new Vector(rect.x + rect.width, rect.y + rect.height).add(scroll)
|
|
588
|
+
};
|
|
589
|
+
});
|
|
590
|
+
this.cache.sort((a, b) => a.lt.y - b.lt.y);
|
|
591
|
+
}
|
|
592
|
+
const vR = vLt.x + vWidth;
|
|
593
|
+
const vB = vLt.y + vHeight;
|
|
594
|
+
return (_a = this.cache.find(p => {
|
|
595
|
+
return Math.max(vLt.x, p.lt.x) < Math.min(vR, p.br.x) && Math.max(vLt.y, p.lt.y) < Math.min(vB, p.br.y);
|
|
596
|
+
})) === null || _a === void 0 ? void 0 : _a.placeholder;
|
|
597
|
+
}
|
|
598
|
+
destroy() {
|
|
599
|
+
this.state.onViewportChanged.unsubscribe(this.clearCacheHandler);
|
|
600
|
+
window.removeEventListener('scroll', this.clearCacheHandler, false);
|
|
601
|
+
}
|
|
602
|
+
clearCache() {
|
|
603
|
+
this.cache = undefined;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
class DragStepBehavior {
|
|
608
|
+
static create(designerContext, step, draggedStepComponent) {
|
|
609
|
+
const view = DragStepView.create(step, designerContext.configuration, designerContext.componentContext);
|
|
610
|
+
return new DragStepBehavior(view, designerContext.workspaceController, designerContext.state, step, designerContext.definitionModifier, draggedStepComponent);
|
|
611
|
+
}
|
|
612
|
+
constructor(view, workspaceController, designerState, step, definitionModifier, draggedStepComponent) {
|
|
613
|
+
this.view = view;
|
|
614
|
+
this.workspaceController = workspaceController;
|
|
615
|
+
this.designerState = designerState;
|
|
616
|
+
this.step = step;
|
|
617
|
+
this.definitionModifier = definitionModifier;
|
|
618
|
+
this.draggedStepComponent = draggedStepComponent;
|
|
619
|
+
}
|
|
620
|
+
onStart(position) {
|
|
621
|
+
let offset = null;
|
|
622
|
+
if (this.draggedStepComponent) {
|
|
623
|
+
this.draggedStepComponent.setIsDisabled(true);
|
|
624
|
+
const hasSameSize = this.draggedStepComponent.view.width === this.view.component.width &&
|
|
625
|
+
this.draggedStepComponent.view.height === this.view.component.height;
|
|
626
|
+
if (hasSameSize) {
|
|
627
|
+
const scroll = new Vector(window.scrollX, window.scrollY);
|
|
628
|
+
// Mouse cursor will be positioned on the same place as the source component.
|
|
629
|
+
const pagePosition = this.draggedStepComponent.view.getClientPosition().add(scroll);
|
|
630
|
+
offset = position.subtract(pagePosition);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
if (!offset) {
|
|
634
|
+
// Mouse cursor will be positioned in the center of the component.
|
|
635
|
+
offset = new Vector(this.view.component.width, this.view.component.height).divideByScalar(2);
|
|
636
|
+
}
|
|
637
|
+
this.view.setPosition(position.subtract(offset));
|
|
638
|
+
this.designerState.setIsDragging(true);
|
|
639
|
+
const placeholders = this.workspaceController.getPlaceholders();
|
|
640
|
+
this.state = {
|
|
641
|
+
startPosition: position,
|
|
642
|
+
finder: PlaceholderFinder.create(placeholders, this.designerState),
|
|
643
|
+
offset
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
onMove(delta) {
|
|
647
|
+
if (this.state) {
|
|
648
|
+
const newPosition = this.state.startPosition.subtract(delta).subtract(this.state.offset);
|
|
649
|
+
this.view.setPosition(newPosition);
|
|
650
|
+
const placeholder = this.state.finder.find(newPosition, this.view.component.width, this.view.component.height);
|
|
651
|
+
if (this.currentPlaceholder !== placeholder) {
|
|
652
|
+
if (this.currentPlaceholder) {
|
|
653
|
+
this.currentPlaceholder.setIsHover(false);
|
|
654
|
+
}
|
|
655
|
+
if (placeholder) {
|
|
656
|
+
placeholder.setIsHover(true);
|
|
657
|
+
}
|
|
658
|
+
this.currentPlaceholder = placeholder;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
onEnd(interrupt) {
|
|
663
|
+
if (!this.state) {
|
|
664
|
+
throw new Error('Invalid state');
|
|
665
|
+
}
|
|
666
|
+
this.state.finder.destroy();
|
|
667
|
+
this.state = undefined;
|
|
668
|
+
this.view.remove();
|
|
669
|
+
this.designerState.setIsDragging(false);
|
|
670
|
+
let modified = false;
|
|
671
|
+
if (!interrupt && this.currentPlaceholder) {
|
|
672
|
+
if (this.draggedStepComponent) {
|
|
673
|
+
modified = this.definitionModifier.tryMove(this.draggedStepComponent.parentSequence, this.draggedStepComponent.step, this.currentPlaceholder.parentSequence, this.currentPlaceholder.index);
|
|
674
|
+
}
|
|
675
|
+
else {
|
|
676
|
+
modified = this.definitionModifier.tryInsert(this.step, this.currentPlaceholder.parentSequence, this.currentPlaceholder.index);
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
if (!modified) {
|
|
680
|
+
if (this.draggedStepComponent) {
|
|
681
|
+
this.draggedStepComponent.setIsDisabled(false);
|
|
682
|
+
}
|
|
683
|
+
if (this.currentPlaceholder) {
|
|
684
|
+
this.currentPlaceholder.setIsHover(false);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
this.currentPlaceholder = undefined;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
class ToolboxApi {
|
|
692
|
+
constructor(state, designerContext, behaviorController, layoutController, configuration) {
|
|
693
|
+
this.state = state;
|
|
694
|
+
this.designerContext = designerContext;
|
|
695
|
+
this.behaviorController = behaviorController;
|
|
696
|
+
this.layoutController = layoutController;
|
|
697
|
+
this.configuration = configuration;
|
|
698
|
+
}
|
|
699
|
+
isVisibleAtStart() {
|
|
700
|
+
return this.layoutController.isMobile();
|
|
701
|
+
}
|
|
702
|
+
tryGetIconUrl(step) {
|
|
703
|
+
return this.configuration.iconUrlProvider ? this.configuration.iconUrlProvider(step.componentType, step.type) : null;
|
|
704
|
+
}
|
|
705
|
+
/**
|
|
706
|
+
* @param position Mouse or touch position.
|
|
707
|
+
* @param step Step definition.
|
|
708
|
+
* @returns If started dragging returns true, otherwise returns false.
|
|
709
|
+
*/
|
|
710
|
+
tryDrag(position, step) {
|
|
711
|
+
if (!this.state.isReadonly) {
|
|
712
|
+
const newStep = createStep(step);
|
|
713
|
+
this.behaviorController.start(position, DragStepBehavior.create(this.designerContext, newStep));
|
|
714
|
+
return true;
|
|
715
|
+
}
|
|
716
|
+
return false;
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
function createStep(step) {
|
|
720
|
+
const newStep = ObjectCloner.deepClone(step);
|
|
721
|
+
newStep.id = Uid.next();
|
|
722
|
+
return newStep;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
class ViewportApi {
|
|
726
|
+
constructor(workspaceController, viewportController) {
|
|
727
|
+
this.workspaceController = workspaceController;
|
|
728
|
+
this.viewportController = viewportController;
|
|
729
|
+
}
|
|
730
|
+
resetViewport() {
|
|
731
|
+
this.viewportController.setDefault();
|
|
732
|
+
}
|
|
733
|
+
zoom(direction) {
|
|
734
|
+
this.viewportController.zoom(direction);
|
|
735
|
+
}
|
|
736
|
+
moveViewportToStep(stepId) {
|
|
737
|
+
const component = this.workspaceController.getComponentByStepId(stepId);
|
|
738
|
+
const componentPosition = component.view.getClientPosition();
|
|
739
|
+
const componentSize = new Vector(component.view.width, component.view.height);
|
|
740
|
+
this.viewportController.focusOnComponent(componentPosition, componentSize);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
class WorkspaceApi {
|
|
745
|
+
constructor(state, workspaceController) {
|
|
746
|
+
this.state = state;
|
|
747
|
+
this.workspaceController = workspaceController;
|
|
748
|
+
}
|
|
749
|
+
getCanvasPosition() {
|
|
750
|
+
return this.workspaceController.getCanvasPosition();
|
|
751
|
+
}
|
|
752
|
+
getCanvasSize() {
|
|
753
|
+
return this.workspaceController.getCanvasSize();
|
|
754
|
+
}
|
|
755
|
+
getRootComponentSize() {
|
|
756
|
+
return this.workspaceController.getRootComponentSize();
|
|
757
|
+
}
|
|
758
|
+
getViewport() {
|
|
759
|
+
return this.state.viewport;
|
|
760
|
+
}
|
|
761
|
+
setViewport(viewport) {
|
|
762
|
+
this.state.setViewport(viewport);
|
|
763
|
+
}
|
|
764
|
+
updateBadges() {
|
|
765
|
+
this.workspaceController.updateBadges();
|
|
766
|
+
}
|
|
767
|
+
updateSize() {
|
|
768
|
+
this.workspaceController.updateSize();
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
class DesignerApi {
|
|
773
|
+
static create(context) {
|
|
774
|
+
const workspace = new WorkspaceApi(context.state, context.workspaceController);
|
|
775
|
+
const viewportController = context.services.viewportController.create(workspace);
|
|
776
|
+
const viewport = new ViewportApi(context.workspaceController, viewportController);
|
|
777
|
+
return new DesignerApi(new ControlBarApi(context.state, context.historyController, context.definitionModifier, viewport), new ToolboxApi(context.state, context, context.behaviorController, context.layoutController, context.configuration.steps), new EditorApi(context.state, context.stepsTraverser, context.layoutController, context.definitionModifier), workspace, viewport, new PathBarApi(context.state, context.stepsTraverser));
|
|
778
|
+
}
|
|
779
|
+
constructor(controlBar, toolbox, editor, workspace, viewport, pathBar) {
|
|
780
|
+
this.controlBar = controlBar;
|
|
781
|
+
this.toolbox = toolbox;
|
|
782
|
+
this.editor = editor;
|
|
783
|
+
this.workspace = workspace;
|
|
784
|
+
this.viewport = viewport;
|
|
785
|
+
this.pathBar = pathBar;
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
const BADGE_GAP = 4;
|
|
790
|
+
class Badges {
|
|
791
|
+
static create(stepContext, view, componentContext) {
|
|
792
|
+
const g = Dom.svg('g', {
|
|
793
|
+
class: 'sqd-badges'
|
|
794
|
+
});
|
|
795
|
+
view.g.appendChild(g);
|
|
796
|
+
const badges = componentContext.services.badges.map(ext => ext.createBadge(g, stepContext, componentContext));
|
|
797
|
+
return new Badges(g, view, badges);
|
|
798
|
+
}
|
|
799
|
+
constructor(g, view, badges) {
|
|
800
|
+
this.g = g;
|
|
801
|
+
this.view = view;
|
|
802
|
+
this.badges = badges;
|
|
803
|
+
}
|
|
804
|
+
update(result) {
|
|
805
|
+
const count = this.badges.length;
|
|
806
|
+
for (let i = 0; i < count; i++) {
|
|
807
|
+
result[i] = this.badges[i].update(result[i]);
|
|
808
|
+
}
|
|
809
|
+
let offsetX = 0;
|
|
810
|
+
let maxHeight = 0;
|
|
811
|
+
let j = 0;
|
|
812
|
+
for (let i = 0; i < count; i++) {
|
|
813
|
+
const badge = this.badges[i];
|
|
814
|
+
if (badge.view) {
|
|
815
|
+
offsetX += j === 0 ? badge.view.width / 2 : badge.view.width;
|
|
816
|
+
maxHeight = Math.max(maxHeight, badge.view.height);
|
|
817
|
+
Dom.translate(badge.view.g, -offsetX, 0);
|
|
818
|
+
offsetX += BADGE_GAP;
|
|
819
|
+
j++;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
Dom.translate(this.g, this.view.width, -maxHeight / 2);
|
|
823
|
+
}
|
|
824
|
+
resolveClick(click) {
|
|
825
|
+
for (const badge of this.badges) {
|
|
826
|
+
const command = badge.resolveClick(click);
|
|
827
|
+
if (command) {
|
|
828
|
+
return command;
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
return null;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
class StepComponent {
|
|
836
|
+
static create(view, stepContext, componentContext) {
|
|
837
|
+
const badges = Badges.create(stepContext, view, componentContext);
|
|
838
|
+
return new StepComponent(view, stepContext.step, stepContext.parentSequence, view.hasOutput(), badges);
|
|
839
|
+
}
|
|
840
|
+
constructor(view, step, parentSequence, hasOutput, badges) {
|
|
841
|
+
this.view = view;
|
|
842
|
+
this.step = step;
|
|
843
|
+
this.parentSequence = parentSequence;
|
|
844
|
+
this.hasOutput = hasOutput;
|
|
845
|
+
this.badges = badges;
|
|
846
|
+
this.isDisabled = false;
|
|
847
|
+
}
|
|
848
|
+
findById(stepId) {
|
|
849
|
+
if (this.step.id === stepId) {
|
|
850
|
+
return this;
|
|
851
|
+
}
|
|
852
|
+
if (this.view.sequenceComponents) {
|
|
853
|
+
for (const component of this.view.sequenceComponents) {
|
|
854
|
+
const result = component.findById(stepId);
|
|
855
|
+
if (result) {
|
|
856
|
+
return result;
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
return null;
|
|
861
|
+
}
|
|
862
|
+
resolveClick(click) {
|
|
863
|
+
if (this.view.sequenceComponents) {
|
|
864
|
+
for (const component of this.view.sequenceComponents) {
|
|
865
|
+
const result = component.resolveClick(click);
|
|
866
|
+
if (result) {
|
|
867
|
+
return result;
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
const command = this.badges.resolveClick(click) || this.view.resolveClick(click);
|
|
872
|
+
if (command) {
|
|
873
|
+
return {
|
|
874
|
+
component: this,
|
|
875
|
+
command
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
return null;
|
|
879
|
+
}
|
|
880
|
+
getPlaceholders(result) {
|
|
881
|
+
if (!this.isDisabled) {
|
|
882
|
+
if (this.view.sequenceComponents) {
|
|
883
|
+
this.view.sequenceComponents.forEach(component => component.getPlaceholders(result));
|
|
884
|
+
}
|
|
885
|
+
if (this.view.placeholders) {
|
|
886
|
+
this.view.placeholders.forEach(ph => result.push(ph));
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
setIsDragging(isDragging) {
|
|
891
|
+
if (!this.isDisabled && this.view.sequenceComponents) {
|
|
892
|
+
this.view.sequenceComponents.forEach(component => component.setIsDragging(isDragging));
|
|
893
|
+
}
|
|
894
|
+
this.view.setIsDragging(isDragging);
|
|
895
|
+
}
|
|
896
|
+
setIsSelected(isSelected) {
|
|
897
|
+
this.view.setIsSelected(isSelected);
|
|
898
|
+
}
|
|
899
|
+
setIsDisabled(isDisabled) {
|
|
900
|
+
this.isDisabled = isDisabled;
|
|
901
|
+
this.view.setIsDisabled(isDisabled);
|
|
902
|
+
}
|
|
903
|
+
updateBadges(result) {
|
|
904
|
+
if (this.view.sequenceComponents) {
|
|
905
|
+
this.view.sequenceComponents.forEach(component => component.updateBadges(result));
|
|
906
|
+
}
|
|
907
|
+
this.badges.update(result);
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
class StepComponentFactory {
|
|
912
|
+
constructor(stepExtensionResolver) {
|
|
913
|
+
this.stepExtensionResolver = stepExtensionResolver;
|
|
914
|
+
}
|
|
915
|
+
create(parentElement, stepContext, componentContext) {
|
|
916
|
+
const extension = this.stepExtensionResolver.resolve(stepContext.step.componentType);
|
|
917
|
+
const view = extension.createComponentView(parentElement, stepContext, componentContext);
|
|
918
|
+
return StepComponent.create(view, stepContext, componentContext);
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
class ComponentContext {
|
|
923
|
+
static create(configuration, stepExtensionResolver, services) {
|
|
924
|
+
const placeholderController = services.placeholderController.create();
|
|
925
|
+
const stepComponentFactory = new StepComponentFactory(stepExtensionResolver);
|
|
926
|
+
return new ComponentContext(configuration, placeholderController, stepComponentFactory, services);
|
|
927
|
+
}
|
|
928
|
+
constructor(configuration, placeholderController, stepComponentFactory, services) {
|
|
929
|
+
this.configuration = configuration;
|
|
930
|
+
this.placeholderController = placeholderController;
|
|
931
|
+
this.stepComponentFactory = stepComponentFactory;
|
|
932
|
+
this.services = services;
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
class EditorView {
|
|
937
|
+
static create(parent) {
|
|
938
|
+
return new EditorView(parent);
|
|
939
|
+
}
|
|
940
|
+
constructor(parent) {
|
|
941
|
+
this.parent = parent;
|
|
942
|
+
this.currentContainer = null;
|
|
943
|
+
}
|
|
944
|
+
setContent(content, className) {
|
|
945
|
+
const container = Dom.element('div', {
|
|
946
|
+
class: className
|
|
947
|
+
});
|
|
948
|
+
container.appendChild(content);
|
|
949
|
+
if (this.currentContainer) {
|
|
950
|
+
this.parent.removeChild(this.currentContainer);
|
|
951
|
+
}
|
|
952
|
+
this.parent.appendChild(container);
|
|
953
|
+
this.currentContainer = container;
|
|
954
|
+
}
|
|
955
|
+
destroy() {
|
|
956
|
+
if (this.currentContainer) {
|
|
957
|
+
this.parent.removeChild(this.currentContainer);
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
class Editor {
|
|
963
|
+
static create(parent, api, stepEditorClassName, stepEditorProvider, globalEditorClassName, globalEditorProvider) {
|
|
964
|
+
const view = EditorView.create(parent);
|
|
965
|
+
function render(step) {
|
|
966
|
+
let content;
|
|
967
|
+
let className;
|
|
968
|
+
if (step) {
|
|
969
|
+
const stepContext = api.editor.createStepEditorContext(step.id);
|
|
970
|
+
content = stepEditorProvider(step, stepContext);
|
|
971
|
+
className = stepEditorClassName;
|
|
972
|
+
}
|
|
973
|
+
else {
|
|
974
|
+
const globalContext = api.editor.createGlobalEditorContext();
|
|
975
|
+
content = globalEditorProvider(api.editor.getDefinition(), globalContext);
|
|
976
|
+
className = globalEditorClassName;
|
|
977
|
+
}
|
|
978
|
+
view.setContent(content, className);
|
|
979
|
+
}
|
|
980
|
+
const renderer = api.editor.runRenderer(step => render(step));
|
|
981
|
+
return new Editor(view, renderer);
|
|
982
|
+
}
|
|
983
|
+
constructor(view, renderer) {
|
|
984
|
+
this.view = view;
|
|
985
|
+
this.renderer = renderer;
|
|
986
|
+
}
|
|
987
|
+
destroy() {
|
|
988
|
+
this.view.destroy();
|
|
989
|
+
this.renderer.destroy();
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
const RECT_INPUT_SIZE = 18;
|
|
994
|
+
const RECT_INPUT_ICON_SIZE = 14;
|
|
995
|
+
const ROUND_INPUT_SIZE = 7;
|
|
996
|
+
class InputView {
|
|
997
|
+
static createRectInput(parent, x, y, iconUrl) {
|
|
998
|
+
const g = Dom.svg('g');
|
|
999
|
+
parent.appendChild(g);
|
|
1000
|
+
const rect = Dom.svg('rect', {
|
|
1001
|
+
class: 'sqd-input',
|
|
1002
|
+
width: RECT_INPUT_SIZE,
|
|
1003
|
+
height: RECT_INPUT_SIZE,
|
|
1004
|
+
x: x - RECT_INPUT_SIZE / 2,
|
|
1005
|
+
y: y + RECT_INPUT_SIZE / -2 + 0.5,
|
|
1006
|
+
rx: 4,
|
|
1007
|
+
ry: 4
|
|
1008
|
+
});
|
|
1009
|
+
g.appendChild(rect);
|
|
1010
|
+
if (iconUrl) {
|
|
1011
|
+
const icon = Dom.svg('image', {
|
|
1012
|
+
href: iconUrl,
|
|
1013
|
+
width: RECT_INPUT_ICON_SIZE,
|
|
1014
|
+
height: RECT_INPUT_ICON_SIZE,
|
|
1015
|
+
x: x - RECT_INPUT_ICON_SIZE / 2,
|
|
1016
|
+
y: y + RECT_INPUT_ICON_SIZE / -2
|
|
1017
|
+
});
|
|
1018
|
+
g.appendChild(icon);
|
|
1019
|
+
}
|
|
1020
|
+
return new InputView(g);
|
|
1021
|
+
}
|
|
1022
|
+
static createRoundInput(parent, x, y) {
|
|
1023
|
+
const circle = Dom.svg('circle', {
|
|
1024
|
+
class: 'sqd-input',
|
|
1025
|
+
cx: x,
|
|
1026
|
+
xy: y,
|
|
1027
|
+
r: ROUND_INPUT_SIZE
|
|
1028
|
+
});
|
|
1029
|
+
parent.appendChild(circle);
|
|
1030
|
+
return new InputView(circle);
|
|
1031
|
+
}
|
|
1032
|
+
constructor(root) {
|
|
1033
|
+
this.root = root;
|
|
1034
|
+
}
|
|
1035
|
+
setIsHidden(isHidden) {
|
|
1036
|
+
Dom.attrs(this.root, {
|
|
1037
|
+
visibility: isHidden ? 'hidden' : 'visible'
|
|
1038
|
+
});
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
class JoinView {
|
|
1043
|
+
static createStraightJoin(parent, start, height) {
|
|
1044
|
+
const join = Dom.svg('line', {
|
|
1045
|
+
class: 'sqd-join',
|
|
1046
|
+
x1: start.x,
|
|
1047
|
+
y1: start.y,
|
|
1048
|
+
x2: start.x,
|
|
1049
|
+
y2: start.y + height
|
|
1050
|
+
});
|
|
1051
|
+
parent.insertBefore(join, parent.firstChild);
|
|
1052
|
+
}
|
|
1053
|
+
static createJoins(parent, start, targets) {
|
|
1054
|
+
const firstTarget = targets[0];
|
|
1055
|
+
const h = Math.abs(firstTarget.y - start.y) / 2; // half height
|
|
1056
|
+
const y = Math.sign(firstTarget.y - start.y); // y direction
|
|
1057
|
+
switch (targets.length) {
|
|
1058
|
+
case 1:
|
|
1059
|
+
if (start.x === targets[0].x) {
|
|
1060
|
+
JoinView.createStraightJoin(parent, start, firstTarget.y * y);
|
|
1061
|
+
}
|
|
1062
|
+
else {
|
|
1063
|
+
appendCurvedJoins(parent, start, targets, h, y);
|
|
1064
|
+
}
|
|
1065
|
+
break;
|
|
1066
|
+
case 2:
|
|
1067
|
+
appendCurvedJoins(parent, start, targets, h, y);
|
|
1068
|
+
break;
|
|
1069
|
+
default:
|
|
1070
|
+
{
|
|
1071
|
+
const f = targets[0]; // first
|
|
1072
|
+
const l = targets[targets.length - 1]; // last
|
|
1073
|
+
appendJoin(parent, `M ${f.x} ${f.y} q ${h * 0.3} ${h * -y * 0.8} ${h} ${h * -y} ` +
|
|
1074
|
+
`l ${l.x - f.x - h * 2} 0 q ${h * 0.8} ${-h * -y * 0.3} ${h} ${-h * -y}`);
|
|
1075
|
+
for (let i = 1; i < targets.length - 1; i++) {
|
|
1076
|
+
JoinView.createStraightJoin(parent, targets[i], h * -y);
|
|
1077
|
+
}
|
|
1078
|
+
JoinView.createStraightJoin(parent, start, h * y);
|
|
1079
|
+
}
|
|
1080
|
+
break;
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
function appendCurvedJoins(parent, start, targets, h, y) {
|
|
1085
|
+
for (const target of targets) {
|
|
1086
|
+
const l = Math.abs(target.x - start.x) - h * 2; // line size
|
|
1087
|
+
const x = Math.sign(target.x - start.x); // x direction
|
|
1088
|
+
appendJoin(parent, `M ${start.x} ${start.y} q ${x * h * 0.3} ${y * h * 0.8} ${x * h} ${y * h} ` +
|
|
1089
|
+
`l ${x * l} 0 q ${x * h * 0.7} ${y * h * 0.2} ${x * h} ${y * h}`);
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
function appendJoin(parent, d) {
|
|
1093
|
+
const join = Dom.svg('path', {
|
|
1094
|
+
class: 'sqd-join',
|
|
1095
|
+
fill: 'none',
|
|
1096
|
+
d
|
|
1097
|
+
});
|
|
1098
|
+
parent.insertBefore(join, parent.firstChild);
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
const LABEL_HEIGHT = 22;
|
|
1102
|
+
const LABEL_PADDING_X = 10;
|
|
1103
|
+
const MIN_LABEL_WIDTH = 50;
|
|
1104
|
+
class LabelView {
|
|
1105
|
+
static create(parent, y, text, theme) {
|
|
1106
|
+
const g = Dom.svg('g', {
|
|
1107
|
+
class: 'sqd-label'
|
|
1108
|
+
});
|
|
1109
|
+
parent.appendChild(g);
|
|
1110
|
+
const nameText = Dom.svg('text', {
|
|
1111
|
+
class: 'sqd-label-text',
|
|
1112
|
+
y: y + LABEL_HEIGHT / 2
|
|
1113
|
+
});
|
|
1114
|
+
nameText.textContent = text;
|
|
1115
|
+
g.appendChild(nameText);
|
|
1116
|
+
const width = Math.max(nameText.getBBox().width + LABEL_PADDING_X * 2, MIN_LABEL_WIDTH);
|
|
1117
|
+
const nameRect = Dom.svg('rect', {
|
|
1118
|
+
class: `sqd-label-rect sqd-label-${theme}`,
|
|
1119
|
+
width: width,
|
|
1120
|
+
height: LABEL_HEIGHT,
|
|
1121
|
+
x: -width / 2,
|
|
1122
|
+
y,
|
|
1123
|
+
rx: 10,
|
|
1124
|
+
ry: 10
|
|
1125
|
+
});
|
|
1126
|
+
g.insertBefore(nameRect, nameText);
|
|
1127
|
+
return new LabelView(g, width, LABEL_HEIGHT);
|
|
1128
|
+
}
|
|
1129
|
+
constructor(g, width, height) {
|
|
1130
|
+
this.g = g;
|
|
1131
|
+
this.width = width;
|
|
1132
|
+
this.height = height;
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
const OUTPUT_SIZE = 5;
|
|
1137
|
+
class OutputView {
|
|
1138
|
+
static create(parent, x, y) {
|
|
1139
|
+
const circle = Dom.svg('circle', {
|
|
1140
|
+
class: 'sqd-output',
|
|
1141
|
+
cx: x,
|
|
1142
|
+
cy: y,
|
|
1143
|
+
r: OUTPUT_SIZE
|
|
1144
|
+
});
|
|
1145
|
+
parent.appendChild(circle);
|
|
1146
|
+
return new OutputView(circle);
|
|
1147
|
+
}
|
|
1148
|
+
constructor(root) {
|
|
1149
|
+
this.root = root;
|
|
1150
|
+
}
|
|
1151
|
+
setIsHidden(isHidden) {
|
|
1152
|
+
Dom.attrs(this.root, {
|
|
1153
|
+
visibility: isHidden ? 'hidden' : 'visible'
|
|
1154
|
+
});
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
class RegionView {
|
|
1159
|
+
static create(parent, widths, height) {
|
|
1160
|
+
const totalWidth = widths.reduce((result, width) => result + width, 0);
|
|
1161
|
+
const lines = [
|
|
1162
|
+
drawLine(parent, 0, 0, totalWidth, 0),
|
|
1163
|
+
drawLine(parent, 0, 0, 0, height),
|
|
1164
|
+
drawLine(parent, 0, height, totalWidth, height),
|
|
1165
|
+
drawLine(parent, totalWidth, 0, totalWidth, height)
|
|
1166
|
+
];
|
|
1167
|
+
let offsetX = widths[0];
|
|
1168
|
+
for (let i = 1; i < widths.length; i++) {
|
|
1169
|
+
lines.push(drawLine(parent, offsetX, 0, offsetX, height));
|
|
1170
|
+
offsetX += widths[i];
|
|
1171
|
+
}
|
|
1172
|
+
return new RegionView(lines, totalWidth, height);
|
|
1173
|
+
}
|
|
1174
|
+
constructor(lines, width, height) {
|
|
1175
|
+
this.lines = lines;
|
|
1176
|
+
this.width = width;
|
|
1177
|
+
this.height = height;
|
|
1178
|
+
}
|
|
1179
|
+
getClientPosition() {
|
|
1180
|
+
const rect = this.lines[0].getBoundingClientRect();
|
|
1181
|
+
return new Vector(rect.x, rect.y);
|
|
1182
|
+
}
|
|
1183
|
+
resolveClick(click) {
|
|
1184
|
+
const regionPosition = this.getClientPosition();
|
|
1185
|
+
const d = click.position.subtract(regionPosition);
|
|
1186
|
+
return d.x >= 0 && d.y >= 0 && d.x < this.width * click.scale && d.y < this.height * click.scale;
|
|
1187
|
+
}
|
|
1188
|
+
setIsSelected(isSelected) {
|
|
1189
|
+
this.lines.forEach(region => {
|
|
1190
|
+
Dom.toggleClass(region, isSelected, 'sqd-selected');
|
|
1191
|
+
});
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
function drawLine(parent, x1, y1, x2, y2) {
|
|
1195
|
+
const line = Dom.svg('line', {
|
|
1196
|
+
class: 'sqd-region',
|
|
1197
|
+
x1,
|
|
1198
|
+
y1,
|
|
1199
|
+
x2,
|
|
1200
|
+
y2
|
|
1201
|
+
});
|
|
1202
|
+
parent.insertBefore(line, parent.firstChild);
|
|
1203
|
+
return line;
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
const ICON_SIZE$2 = 16;
|
|
1207
|
+
var RectPlaceholderDirection;
|
|
1208
|
+
(function (RectPlaceholderDirection) {
|
|
1209
|
+
RectPlaceholderDirection[RectPlaceholderDirection["none"] = 0] = "none";
|
|
1210
|
+
RectPlaceholderDirection[RectPlaceholderDirection["in"] = 1] = "in";
|
|
1211
|
+
RectPlaceholderDirection[RectPlaceholderDirection["out"] = 2] = "out";
|
|
1212
|
+
})(RectPlaceholderDirection || (RectPlaceholderDirection = {}));
|
|
1213
|
+
class RectPlaceholderView {
|
|
1214
|
+
static create(parent, x, y, width, height, direction) {
|
|
1215
|
+
const g = Dom.svg('g', {
|
|
1216
|
+
visibility: 'hidden',
|
|
1217
|
+
class: 'sqd-placeholder'
|
|
1218
|
+
});
|
|
1219
|
+
Dom.translate(g, x, y);
|
|
1220
|
+
parent.appendChild(g);
|
|
1221
|
+
const rect = Dom.svg('rect', {
|
|
1222
|
+
class: 'sqd-placeholder-rect',
|
|
1223
|
+
width,
|
|
1224
|
+
height,
|
|
1225
|
+
rx: 6,
|
|
1226
|
+
ry: 6
|
|
1227
|
+
});
|
|
1228
|
+
g.appendChild(rect);
|
|
1229
|
+
if (direction) {
|
|
1230
|
+
const iconD = direction === RectPlaceholderDirection.in ? Icons.folderIn : Icons.folderOut;
|
|
1231
|
+
const icon = Icons.appendPath(g, 'sqd-placeholder-icon-path', iconD, ICON_SIZE$2);
|
|
1232
|
+
Dom.translate(icon, (width - ICON_SIZE$2) / 2, (height - ICON_SIZE$2) / 2);
|
|
1233
|
+
}
|
|
1234
|
+
parent.appendChild(g);
|
|
1235
|
+
return new RectPlaceholderView(rect, g);
|
|
1236
|
+
}
|
|
1237
|
+
constructor(rect, g) {
|
|
1238
|
+
this.rect = rect;
|
|
1239
|
+
this.g = g;
|
|
1240
|
+
}
|
|
1241
|
+
setIsHover(isHover) {
|
|
1242
|
+
Dom.toggleClass(this.g, isHover, 'sqd-hover');
|
|
1243
|
+
}
|
|
1244
|
+
setIsVisible(isVisible) {
|
|
1245
|
+
Dom.attrs(this.g, {
|
|
1246
|
+
visibility: isVisible ? 'visible' : 'hidden'
|
|
1247
|
+
});
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
const PH_WIDTH = 100;
|
|
1252
|
+
const PH_HEIGHT = 24;
|
|
1253
|
+
class SequenceComponentView {
|
|
1254
|
+
static create(parent, sequenceContext, componentContext) {
|
|
1255
|
+
const { sequence } = sequenceContext;
|
|
1256
|
+
const g = Dom.svg('g');
|
|
1257
|
+
parent.appendChild(g);
|
|
1258
|
+
const components = [];
|
|
1259
|
+
for (let index = 0; index < sequence.length; index++) {
|
|
1260
|
+
const stepContext = {
|
|
1261
|
+
parentSequence: sequenceContext.sequence,
|
|
1262
|
+
step: sequence[index],
|
|
1263
|
+
depth: sequenceContext.depth,
|
|
1264
|
+
position: index,
|
|
1265
|
+
isInputConnected: index === 0 ? sequenceContext.isInputConnected : components[index - 1].hasOutput,
|
|
1266
|
+
isOutputConnected: index === sequence.length - 1 ? sequenceContext.isOutputConnected : true
|
|
1267
|
+
};
|
|
1268
|
+
components[index] = componentContext.stepComponentFactory.create(g, stepContext, componentContext);
|
|
1269
|
+
}
|
|
1270
|
+
let joinX;
|
|
1271
|
+
let totalWidth;
|
|
1272
|
+
if (components.length > 0) {
|
|
1273
|
+
const restWidth = Math.max(...components.map(c => c.view.width - c.view.joinX));
|
|
1274
|
+
joinX = Math.max(...components.map(c => c.view.joinX));
|
|
1275
|
+
totalWidth = joinX + restWidth;
|
|
1276
|
+
}
|
|
1277
|
+
else {
|
|
1278
|
+
joinX = PH_WIDTH / 2;
|
|
1279
|
+
totalWidth = PH_WIDTH;
|
|
1280
|
+
}
|
|
1281
|
+
let offsetY = PH_HEIGHT;
|
|
1282
|
+
const placeholders = [];
|
|
1283
|
+
for (let i = 0; i < components.length; i++) {
|
|
1284
|
+
const component = components[i];
|
|
1285
|
+
const offsetX = joinX - component.view.joinX;
|
|
1286
|
+
if ((i === 0 && sequenceContext.isInputConnected) || (i > 0 && components[i - 1].hasOutput)) {
|
|
1287
|
+
JoinView.createStraightJoin(g, new Vector(joinX, offsetY - PH_HEIGHT), PH_HEIGHT);
|
|
1288
|
+
}
|
|
1289
|
+
if (componentContext.placeholderController.canCreate(sequence, i)) {
|
|
1290
|
+
const view = RectPlaceholderView.create(g, joinX - PH_WIDTH / 2, offsetY - PH_HEIGHT, PH_WIDTH, PH_HEIGHT, RectPlaceholderDirection.none);
|
|
1291
|
+
placeholders.push({
|
|
1292
|
+
view,
|
|
1293
|
+
index: i
|
|
1294
|
+
});
|
|
1295
|
+
}
|
|
1296
|
+
Dom.translate(component.view.g, offsetX, offsetY);
|
|
1297
|
+
offsetY += component.view.height + PH_HEIGHT;
|
|
1298
|
+
}
|
|
1299
|
+
if (sequenceContext.isOutputConnected && (components.length === 0 || components[components.length - 1].hasOutput)) {
|
|
1300
|
+
JoinView.createStraightJoin(g, new Vector(joinX, offsetY - PH_HEIGHT), PH_HEIGHT);
|
|
1301
|
+
}
|
|
1302
|
+
const newIndex = components.length;
|
|
1303
|
+
if (componentContext.placeholderController.canCreate(sequence, newIndex)) {
|
|
1304
|
+
const view = RectPlaceholderView.create(g, joinX - PH_WIDTH / 2, offsetY - PH_HEIGHT, PH_WIDTH, PH_HEIGHT, RectPlaceholderDirection.none);
|
|
1305
|
+
placeholders.push({
|
|
1306
|
+
view,
|
|
1307
|
+
index: newIndex
|
|
1308
|
+
});
|
|
1309
|
+
}
|
|
1310
|
+
return new SequenceComponentView(g, totalWidth, offsetY, joinX, placeholders, components);
|
|
1311
|
+
}
|
|
1312
|
+
constructor(g, width, height, joinX, placeholders, components) {
|
|
1313
|
+
this.g = g;
|
|
1314
|
+
this.width = width;
|
|
1315
|
+
this.height = height;
|
|
1316
|
+
this.joinX = joinX;
|
|
1317
|
+
this.placeholders = placeholders;
|
|
1318
|
+
this.components = components;
|
|
1319
|
+
}
|
|
1320
|
+
setIsDragging(isDragging) {
|
|
1321
|
+
this.placeholders.forEach(placeholder => {
|
|
1322
|
+
placeholder.view.setIsVisible(isDragging);
|
|
1323
|
+
});
|
|
1324
|
+
}
|
|
1325
|
+
hasOutput() {
|
|
1326
|
+
if (this.components.length > 0) {
|
|
1327
|
+
return this.components[this.components.length - 1].hasOutput;
|
|
1328
|
+
}
|
|
1329
|
+
return true;
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
class RectPlaceholder {
|
|
1334
|
+
constructor(view, parentSequence, index) {
|
|
1335
|
+
this.view = view;
|
|
1336
|
+
this.parentSequence = parentSequence;
|
|
1337
|
+
this.index = index;
|
|
1338
|
+
}
|
|
1339
|
+
getClientRect() {
|
|
1340
|
+
return this.view.rect.getBoundingClientRect();
|
|
1341
|
+
}
|
|
1342
|
+
setIsHover(isHover) {
|
|
1343
|
+
this.view.setIsHover(isHover);
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
class SequenceComponent {
|
|
1348
|
+
static create(parentElement, sequenceContext, context) {
|
|
1349
|
+
const view = SequenceComponentView.create(parentElement, sequenceContext, context);
|
|
1350
|
+
return new SequenceComponent(view, view.hasOutput(), sequenceContext.sequence);
|
|
1351
|
+
}
|
|
1352
|
+
constructor(view, hasOutput, sequence) {
|
|
1353
|
+
this.view = view;
|
|
1354
|
+
this.hasOutput = hasOutput;
|
|
1355
|
+
this.sequence = sequence;
|
|
1356
|
+
}
|
|
1357
|
+
resolveClick(click) {
|
|
1358
|
+
for (const component of this.view.components) {
|
|
1359
|
+
const result = component.resolveClick(click);
|
|
1360
|
+
if (result) {
|
|
1361
|
+
return result;
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
return null;
|
|
1365
|
+
}
|
|
1366
|
+
findById(stepId) {
|
|
1367
|
+
for (const component of this.view.components) {
|
|
1368
|
+
const sc = component.findById(stepId);
|
|
1369
|
+
if (sc) {
|
|
1370
|
+
return sc;
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
return null;
|
|
1374
|
+
}
|
|
1375
|
+
getPlaceholders(result) {
|
|
1376
|
+
this.view.placeholders.forEach(placeholder => {
|
|
1377
|
+
result.push(new RectPlaceholder(placeholder.view, this.sequence, placeholder.index));
|
|
1378
|
+
});
|
|
1379
|
+
this.view.components.forEach(c => c.getPlaceholders(result));
|
|
1380
|
+
}
|
|
1381
|
+
setIsDragging(isDragging) {
|
|
1382
|
+
this.view.setIsDragging(isDragging);
|
|
1383
|
+
this.view.components.forEach(c => c.setIsDragging(isDragging));
|
|
1384
|
+
}
|
|
1385
|
+
updateBadges(result) {
|
|
1386
|
+
for (const component of this.view.components) {
|
|
1387
|
+
component.updateBadges(result);
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
var ClickCommandType;
|
|
1393
|
+
(function (ClickCommandType) {
|
|
1394
|
+
ClickCommandType[ClickCommandType["selectStep"] = 1] = "selectStep";
|
|
1395
|
+
ClickCommandType[ClickCommandType["openFolder"] = 2] = "openFolder";
|
|
1396
|
+
ClickCommandType[ClickCommandType["triggerCustomAction"] = 3] = "triggerCustomAction";
|
|
1397
|
+
})(ClickCommandType || (ClickCommandType = {}));
|
|
1398
|
+
|
|
1399
|
+
const PADDING_X$2 = 12;
|
|
1400
|
+
const PADDING_Y = 10;
|
|
1401
|
+
const MIN_TEXT_WIDTH = 70;
|
|
1402
|
+
const ICON_SIZE$1 = 22;
|
|
1403
|
+
const RECT_RADIUS = 5;
|
|
1404
|
+
class TaskStepComponentView {
|
|
1405
|
+
static create(parentElement, stepContext, configuration, isInterrupted) {
|
|
1406
|
+
const { step } = stepContext;
|
|
1407
|
+
const g = Dom.svg('g', {
|
|
1408
|
+
class: `sqd-step-task sqd-type-${step.type}`
|
|
1409
|
+
});
|
|
1410
|
+
parentElement.appendChild(g);
|
|
1411
|
+
const boxHeight = ICON_SIZE$1 + PADDING_Y * 2;
|
|
1412
|
+
const text = Dom.svg('text', {
|
|
1413
|
+
x: ICON_SIZE$1 + PADDING_X$2 * 2,
|
|
1414
|
+
y: boxHeight / 2,
|
|
1415
|
+
class: 'sqd-step-task-text'
|
|
1416
|
+
});
|
|
1417
|
+
text.textContent = step.name;
|
|
1418
|
+
g.appendChild(text);
|
|
1419
|
+
const textWidth = Math.max(text.getBBox().width, MIN_TEXT_WIDTH);
|
|
1420
|
+
const boxWidth = ICON_SIZE$1 + PADDING_X$2 * 3 + textWidth;
|
|
1421
|
+
const rect = Dom.svg('rect', {
|
|
1422
|
+
x: 0.5,
|
|
1423
|
+
y: 0.5,
|
|
1424
|
+
class: 'sqd-step-task-rect',
|
|
1425
|
+
width: boxWidth,
|
|
1426
|
+
height: boxHeight,
|
|
1427
|
+
rx: RECT_RADIUS,
|
|
1428
|
+
ry: RECT_RADIUS
|
|
1429
|
+
});
|
|
1430
|
+
g.insertBefore(rect, text);
|
|
1431
|
+
const iconUrl = configuration.iconUrlProvider ? configuration.iconUrlProvider(step.componentType, step.type) : null;
|
|
1432
|
+
const icon = iconUrl
|
|
1433
|
+
? Dom.svg('image', {
|
|
1434
|
+
href: iconUrl
|
|
1435
|
+
})
|
|
1436
|
+
: Dom.svg('rect', {
|
|
1437
|
+
class: 'sqd-step-task-empty-icon',
|
|
1438
|
+
rx: 4,
|
|
1439
|
+
ry: 4
|
|
1440
|
+
});
|
|
1441
|
+
Dom.attrs(icon, {
|
|
1442
|
+
x: PADDING_X$2,
|
|
1443
|
+
y: PADDING_Y,
|
|
1444
|
+
width: ICON_SIZE$1,
|
|
1445
|
+
height: ICON_SIZE$1
|
|
1446
|
+
});
|
|
1447
|
+
g.appendChild(icon);
|
|
1448
|
+
const isInputViewHidden = stepContext.depth === 0 && stepContext.position === 0 && !stepContext.isInputConnected;
|
|
1449
|
+
const isOutputViewHidden = isInterrupted;
|
|
1450
|
+
const inputView = isInputViewHidden ? null : InputView.createRoundInput(g, boxWidth / 2, 0);
|
|
1451
|
+
const outputView = isOutputViewHidden ? null : OutputView.create(g, boxWidth / 2, boxHeight);
|
|
1452
|
+
return new TaskStepComponentView(g, boxWidth, boxHeight, boxWidth / 2, rect, inputView, outputView);
|
|
1453
|
+
}
|
|
1454
|
+
constructor(g, width, height, joinX, rect, inputView, outputView) {
|
|
1455
|
+
this.g = g;
|
|
1456
|
+
this.width = width;
|
|
1457
|
+
this.height = height;
|
|
1458
|
+
this.joinX = joinX;
|
|
1459
|
+
this.rect = rect;
|
|
1460
|
+
this.inputView = inputView;
|
|
1461
|
+
this.outputView = outputView;
|
|
1462
|
+
this.sequenceComponents = null;
|
|
1463
|
+
this.placeholders = null;
|
|
1464
|
+
}
|
|
1465
|
+
hasOutput() {
|
|
1466
|
+
return !!this.outputView;
|
|
1467
|
+
}
|
|
1468
|
+
getClientPosition() {
|
|
1469
|
+
const rect = this.rect.getBoundingClientRect();
|
|
1470
|
+
return new Vector(rect.x, rect.y);
|
|
1471
|
+
}
|
|
1472
|
+
resolveClick(click) {
|
|
1473
|
+
if (this.g.contains(click.element)) {
|
|
1474
|
+
return {
|
|
1475
|
+
type: ClickCommandType.selectStep
|
|
1476
|
+
};
|
|
1477
|
+
}
|
|
1478
|
+
return null;
|
|
1479
|
+
}
|
|
1480
|
+
setIsDragging(isDragging) {
|
|
1481
|
+
var _a, _b;
|
|
1482
|
+
(_a = this.inputView) === null || _a === void 0 ? void 0 : _a.setIsHidden(isDragging);
|
|
1483
|
+
(_b = this.outputView) === null || _b === void 0 ? void 0 : _b.setIsHidden(isDragging);
|
|
1484
|
+
}
|
|
1485
|
+
setIsDisabled(isDisabled) {
|
|
1486
|
+
Dom.toggleClass(this.g, isDisabled, 'sqd-disabled');
|
|
1487
|
+
}
|
|
1488
|
+
setIsSelected(isSelected) {
|
|
1489
|
+
Dom.toggleClass(this.rect, isSelected, 'sqd-selected');
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
class CenteredViewportCalculator {
|
|
1494
|
+
static center(margin, canvasSize, rootComponentSize) {
|
|
1495
|
+
const canvasSafeWidth = Math.max(canvasSize.x - margin * 2, 0);
|
|
1496
|
+
const canvasSafeHeight = Math.max(canvasSize.y - margin * 2, 0);
|
|
1497
|
+
const scale = Math.min(Math.min(canvasSafeWidth / rootComponentSize.x, canvasSafeHeight / rootComponentSize.y), 1);
|
|
1498
|
+
const width = rootComponentSize.x * scale;
|
|
1499
|
+
const height = rootComponentSize.y * scale;
|
|
1500
|
+
const x = Math.max(0, (canvasSize.x - width) / 2);
|
|
1501
|
+
const y = Math.max(0, (canvasSize.y - height) / 2);
|
|
1502
|
+
return {
|
|
1503
|
+
position: new Vector(x, y),
|
|
1504
|
+
scale
|
|
1505
|
+
};
|
|
1506
|
+
}
|
|
1507
|
+
static focusOnComponent(canvasSize, viewport, componentPosition, componentSize) {
|
|
1508
|
+
const realPosition = viewport.position.divideByScalar(viewport.scale).subtract(componentPosition.divideByScalar(viewport.scale));
|
|
1509
|
+
const componentOffset = componentSize.divideByScalar(2);
|
|
1510
|
+
const position = realPosition.add(canvasSize.divideByScalar(2)).subtract(componentOffset);
|
|
1511
|
+
return { position, scale: 1 };
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
class NextQuantifiedNumber {
|
|
1516
|
+
constructor(values) {
|
|
1517
|
+
this.values = values;
|
|
1518
|
+
}
|
|
1519
|
+
next(value, direction) {
|
|
1520
|
+
let bestIndex = 0;
|
|
1521
|
+
let bestDistance = Number.MAX_VALUE;
|
|
1522
|
+
for (let i = 0; i < this.values.length; i++) {
|
|
1523
|
+
const distance = Math.abs(this.values[i] - value);
|
|
1524
|
+
if (bestDistance > distance) {
|
|
1525
|
+
bestIndex = i;
|
|
1526
|
+
bestDistance = distance;
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
let index;
|
|
1530
|
+
if (direction) {
|
|
1531
|
+
index = Math.min(bestIndex + 1, this.values.length - 1);
|
|
1532
|
+
}
|
|
1533
|
+
else {
|
|
1534
|
+
index = Math.max(bestIndex - 1, 0);
|
|
1535
|
+
}
|
|
1536
|
+
return {
|
|
1537
|
+
current: this.values[bestIndex],
|
|
1538
|
+
next: this.values[index]
|
|
1539
|
+
};
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
const SCALES = [0.06, 0.08, 0.1, 0.12, 0.16, 0.2, 0.26, 0.32, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1];
|
|
1544
|
+
const MAX_DELTA_Y = 16;
|
|
1545
|
+
const quantifiedScale = new NextQuantifiedNumber(SCALES);
|
|
1546
|
+
class QuantifiedScaleViewportCalculator {
|
|
1547
|
+
static zoom(current, direction) {
|
|
1548
|
+
const nextScale = quantifiedScale.next(current.scale, direction);
|
|
1549
|
+
return {
|
|
1550
|
+
position: current.position,
|
|
1551
|
+
scale: nextScale.next
|
|
1552
|
+
};
|
|
1553
|
+
}
|
|
1554
|
+
static zoomByWheel(current, e, canvasPosition) {
|
|
1555
|
+
if (e.deltaY === 0) {
|
|
1556
|
+
return null;
|
|
1557
|
+
}
|
|
1558
|
+
const nextScale = quantifiedScale.next(current.scale, e.deltaY < 0);
|
|
1559
|
+
let scale;
|
|
1560
|
+
const absDeltaY = Math.abs(e.deltaY);
|
|
1561
|
+
if (absDeltaY < MAX_DELTA_Y) {
|
|
1562
|
+
const fraction = absDeltaY / MAX_DELTA_Y;
|
|
1563
|
+
const step = nextScale.next - nextScale.current;
|
|
1564
|
+
scale = current.scale + step * fraction;
|
|
1565
|
+
}
|
|
1566
|
+
else {
|
|
1567
|
+
scale = nextScale.next;
|
|
1568
|
+
}
|
|
1569
|
+
const mousePoint = new Vector(e.pageX, e.pageY).subtract(canvasPosition);
|
|
1570
|
+
// The real point is point on canvas with no scale.
|
|
1571
|
+
const mouseRealPoint = mousePoint.divideByScalar(current.scale).subtract(current.position.divideByScalar(current.scale));
|
|
1572
|
+
const position = mouseRealPoint.multiplyByScalar(-scale).add(mousePoint);
|
|
1573
|
+
return { position, scale };
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
class ClassicWheelController {
|
|
1578
|
+
static create(api) {
|
|
1579
|
+
return new ClassicWheelController(api);
|
|
1580
|
+
}
|
|
1581
|
+
constructor(api) {
|
|
1582
|
+
this.api = api;
|
|
1583
|
+
}
|
|
1584
|
+
onWheel(e) {
|
|
1585
|
+
const viewport = this.api.getViewport();
|
|
1586
|
+
const canvasPosition = this.api.getCanvasPosition();
|
|
1587
|
+
const newViewport = QuantifiedScaleViewportCalculator.zoomByWheel(viewport, e, canvasPosition);
|
|
1588
|
+
if (newViewport) {
|
|
1589
|
+
this.api.setViewport(newViewport);
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
class ClassicWheelControllerExtension {
|
|
1595
|
+
constructor() {
|
|
1596
|
+
this.create = ClassicWheelController.create;
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
function animate(interval, handler) {
|
|
1601
|
+
const iv = setInterval(tick, 15);
|
|
1602
|
+
const startTime = Date.now();
|
|
1603
|
+
const anim = {
|
|
1604
|
+
isAlive: true,
|
|
1605
|
+
stop: () => {
|
|
1606
|
+
anim.isAlive = false;
|
|
1607
|
+
clearInterval(iv);
|
|
1608
|
+
}
|
|
1609
|
+
};
|
|
1610
|
+
function tick() {
|
|
1611
|
+
const progress = Math.min((Date.now() - startTime) / interval, 1);
|
|
1612
|
+
handler(progress);
|
|
1613
|
+
if (progress === 1) {
|
|
1614
|
+
anim.stop();
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
return anim;
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
class ViewportAnimator {
|
|
1621
|
+
constructor(api) {
|
|
1622
|
+
this.api = api;
|
|
1623
|
+
}
|
|
1624
|
+
execute(target) {
|
|
1625
|
+
if (this.animation && this.animation.isAlive) {
|
|
1626
|
+
this.animation.stop();
|
|
1627
|
+
}
|
|
1628
|
+
const viewport = this.api.getViewport();
|
|
1629
|
+
const startPosition = viewport.position;
|
|
1630
|
+
const startScale = viewport.scale;
|
|
1631
|
+
const deltaPosition = startPosition.subtract(target.position);
|
|
1632
|
+
const deltaScale = startScale - target.scale;
|
|
1633
|
+
this.animation = animate(150, progress => {
|
|
1634
|
+
const newScale = startScale - deltaScale * progress;
|
|
1635
|
+
this.api.setViewport({
|
|
1636
|
+
position: startPosition.subtract(deltaPosition.multiplyByScalar(progress)),
|
|
1637
|
+
scale: newScale
|
|
1638
|
+
});
|
|
1639
|
+
});
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
const CENTER_MARGIN = 10;
|
|
1644
|
+
class DefaultViewportController {
|
|
1645
|
+
static create(api) {
|
|
1646
|
+
return new DefaultViewportController(api);
|
|
1647
|
+
}
|
|
1648
|
+
constructor(api) {
|
|
1649
|
+
this.api = api;
|
|
1650
|
+
this.animator = new ViewportAnimator(this.api);
|
|
1651
|
+
}
|
|
1652
|
+
setDefault() {
|
|
1653
|
+
const rootComponentSize = this.api.getRootComponentSize();
|
|
1654
|
+
const canvasSize = this.api.getCanvasSize();
|
|
1655
|
+
const target = CenteredViewportCalculator.center(CENTER_MARGIN, canvasSize, rootComponentSize);
|
|
1656
|
+
this.api.setViewport(target);
|
|
1657
|
+
}
|
|
1658
|
+
zoom(direction) {
|
|
1659
|
+
const viewport = this.api.getViewport();
|
|
1660
|
+
const target = QuantifiedScaleViewportCalculator.zoom(viewport, direction);
|
|
1661
|
+
this.api.setViewport(target);
|
|
1662
|
+
}
|
|
1663
|
+
focusOnComponent(componentPosition, componentSize) {
|
|
1664
|
+
const viewport = this.api.getViewport();
|
|
1665
|
+
const canvasSize = this.api.getCanvasSize();
|
|
1666
|
+
const target = CenteredViewportCalculator.focusOnComponent(canvasSize, viewport, componentPosition, componentSize);
|
|
1667
|
+
this.animateTo(target);
|
|
1668
|
+
}
|
|
1669
|
+
animateTo(viewport) {
|
|
1670
|
+
this.animator.execute(viewport);
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
class DefaultViewportControllerExtension {
|
|
1675
|
+
constructor() {
|
|
1676
|
+
this.create = DefaultViewportController.create;
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
class StepExtensionResolver {
|
|
1681
|
+
static create(services) {
|
|
1682
|
+
const dict = {};
|
|
1683
|
+
for (let i = services.steps.length - 1; i >= 0; i--) {
|
|
1684
|
+
const extension = services.steps[i];
|
|
1685
|
+
dict[extension.componentType] = extension;
|
|
1686
|
+
}
|
|
1687
|
+
return new StepExtensionResolver(dict);
|
|
1688
|
+
}
|
|
1689
|
+
constructor(dict) {
|
|
1690
|
+
this.dict = dict;
|
|
1691
|
+
}
|
|
1692
|
+
resolve(componentType) {
|
|
1693
|
+
const extension = this.dict[componentType];
|
|
1694
|
+
if (!extension) {
|
|
1695
|
+
throw new Error(`Not supported component type: ${componentType}`);
|
|
1696
|
+
}
|
|
1697
|
+
return extension;
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
function readMousePosition(e) {
|
|
1702
|
+
return new Vector(e.pageX, e.pageY);
|
|
1703
|
+
}
|
|
1704
|
+
function readTouchClientPosition(e) {
|
|
1705
|
+
if (e.touches.length > 0) {
|
|
1706
|
+
const touch = e.touches[0];
|
|
1707
|
+
return new Vector(touch.clientX, touch.clientY);
|
|
1708
|
+
}
|
|
1709
|
+
throw new Error('Unknown touch position');
|
|
1710
|
+
}
|
|
1711
|
+
function readTouchPosition(e) {
|
|
1712
|
+
if (e.touches.length > 0) {
|
|
1713
|
+
const touch = e.touches[0];
|
|
1714
|
+
return new Vector(touch.pageX, touch.pageY);
|
|
1715
|
+
}
|
|
1716
|
+
throw new Error('Unknown touch position');
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
const notInitializedError = 'State is not initialized';
|
|
1720
|
+
const nonPassiveOptions = {
|
|
1721
|
+
passive: false
|
|
1722
|
+
};
|
|
1723
|
+
class BehaviorController {
|
|
1724
|
+
constructor() {
|
|
1725
|
+
this.onMouseMove = (e) => {
|
|
1726
|
+
e.preventDefault();
|
|
1727
|
+
this.move(readMousePosition(e));
|
|
1728
|
+
};
|
|
1729
|
+
this.onTouchMove = (e) => {
|
|
1730
|
+
e.preventDefault();
|
|
1731
|
+
this.move(readTouchPosition(e));
|
|
1732
|
+
};
|
|
1733
|
+
this.onMouseUp = (e) => {
|
|
1734
|
+
e.preventDefault();
|
|
1735
|
+
this.stop(false, e.target);
|
|
1736
|
+
};
|
|
1737
|
+
this.onTouchEnd = (e) => {
|
|
1738
|
+
var _a;
|
|
1739
|
+
e.preventDefault();
|
|
1740
|
+
if (!this.state) {
|
|
1741
|
+
throw new Error(notInitializedError);
|
|
1742
|
+
}
|
|
1743
|
+
const position = (_a = this.state.lastPosition) !== null && _a !== void 0 ? _a : this.state.startPosition;
|
|
1744
|
+
const element = document.elementFromPoint(position.x, position.y);
|
|
1745
|
+
this.stop(false, element);
|
|
1746
|
+
};
|
|
1747
|
+
this.onTouchStart = (e) => {
|
|
1748
|
+
e.preventDefault();
|
|
1749
|
+
if (e.touches.length !== 1) {
|
|
1750
|
+
this.stop(true, null);
|
|
1751
|
+
}
|
|
1752
|
+
};
|
|
1753
|
+
}
|
|
1754
|
+
start(startPosition, behavior) {
|
|
1755
|
+
if (this.state) {
|
|
1756
|
+
this.stop(true, null);
|
|
1757
|
+
return;
|
|
1758
|
+
}
|
|
1759
|
+
this.state = {
|
|
1760
|
+
startPosition,
|
|
1761
|
+
behavior
|
|
1762
|
+
};
|
|
1763
|
+
behavior.onStart(this.state.startPosition);
|
|
1764
|
+
window.addEventListener('mousemove', this.onMouseMove, false);
|
|
1765
|
+
window.addEventListener('touchmove', this.onTouchMove, nonPassiveOptions);
|
|
1766
|
+
window.addEventListener('mouseup', this.onMouseUp, false);
|
|
1767
|
+
window.addEventListener('touchend', this.onTouchEnd, nonPassiveOptions);
|
|
1768
|
+
window.addEventListener('touchstart', this.onTouchStart, nonPassiveOptions);
|
|
1769
|
+
}
|
|
1770
|
+
move(position) {
|
|
1771
|
+
if (!this.state) {
|
|
1772
|
+
throw new Error(notInitializedError);
|
|
1773
|
+
}
|
|
1774
|
+
this.state.lastPosition = position;
|
|
1775
|
+
const delta = this.state.startPosition.subtract(position);
|
|
1776
|
+
const newBehavior = this.state.behavior.onMove(delta);
|
|
1777
|
+
if (newBehavior) {
|
|
1778
|
+
this.state.behavior.onEnd(true, null);
|
|
1779
|
+
this.state.behavior = newBehavior;
|
|
1780
|
+
this.state.startPosition = position;
|
|
1781
|
+
this.state.behavior.onStart(this.state.startPosition);
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
stop(interrupt, element) {
|
|
1785
|
+
if (!this.state) {
|
|
1786
|
+
throw new Error(notInitializedError);
|
|
1787
|
+
}
|
|
1788
|
+
window.removeEventListener('mousemove', this.onMouseMove, false);
|
|
1789
|
+
window.removeEventListener('touchmove', this.onTouchMove, nonPassiveOptions);
|
|
1790
|
+
window.removeEventListener('mouseup', this.onMouseUp, false);
|
|
1791
|
+
window.removeEventListener('touchend', this.onTouchEnd, nonPassiveOptions);
|
|
1792
|
+
window.removeEventListener('touchstart', this.onTouchStart, nonPassiveOptions);
|
|
1793
|
+
this.state.behavior.onEnd(interrupt, element);
|
|
1794
|
+
this.state = undefined;
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
|
|
1798
|
+
class SequenceModifier {
|
|
1799
|
+
static moveStep(sourceSequence, step, targetSequence, targetIndex) {
|
|
1800
|
+
const sourceIndex = sourceSequence.indexOf(step);
|
|
1801
|
+
if (sourceIndex < 0) {
|
|
1802
|
+
throw new Error('Unknown step');
|
|
1803
|
+
}
|
|
1804
|
+
const isSameSequence = sourceSequence === targetSequence;
|
|
1805
|
+
if (isSameSequence && sourceIndex === targetIndex) {
|
|
1806
|
+
return; // Nothing to do.
|
|
1807
|
+
}
|
|
1808
|
+
sourceSequence.splice(sourceIndex, 1);
|
|
1809
|
+
if (isSameSequence && sourceIndex < targetIndex) {
|
|
1810
|
+
targetIndex--;
|
|
1811
|
+
}
|
|
1812
|
+
targetSequence.splice(targetIndex, 0, step);
|
|
1813
|
+
}
|
|
1814
|
+
static insertStep(step, targetSequence, targetIndex) {
|
|
1815
|
+
targetSequence.splice(targetIndex, 0, step);
|
|
1816
|
+
}
|
|
1817
|
+
static deleteStep(step, parentSequence) {
|
|
1818
|
+
const index = parentSequence.indexOf(step);
|
|
1819
|
+
if (index < 0) {
|
|
1820
|
+
throw new Error('Unknown step');
|
|
1821
|
+
}
|
|
1822
|
+
parentSequence.splice(index, 1);
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
class DefinitionModifier {
|
|
1827
|
+
constructor(stepsTraverser, state, configuration) {
|
|
1828
|
+
this.stepsTraverser = stepsTraverser;
|
|
1829
|
+
this.state = state;
|
|
1830
|
+
this.configuration = configuration;
|
|
1831
|
+
}
|
|
1832
|
+
isDeletable(stepId) {
|
|
1833
|
+
if (this.configuration.steps.isDeletable) {
|
|
1834
|
+
const result = this.stepsTraverser.getParentSequence(this.state.definition, stepId);
|
|
1835
|
+
return this.configuration.steps.isDeletable(result.step, result.parentSequence);
|
|
1836
|
+
}
|
|
1837
|
+
return true;
|
|
1838
|
+
}
|
|
1839
|
+
tryDelete(stepId) {
|
|
1840
|
+
const result = this.stepsTraverser.getParentSequence(this.state.definition, stepId);
|
|
1841
|
+
const canDeleteStep = this.configuration.steps.canDeleteStep
|
|
1842
|
+
? this.configuration.steps.canDeleteStep(result.step, result.parentSequence)
|
|
1843
|
+
: true;
|
|
1844
|
+
if (!canDeleteStep) {
|
|
1845
|
+
return false;
|
|
1846
|
+
}
|
|
1847
|
+
SequenceModifier.deleteStep(result.step, result.parentSequence);
|
|
1848
|
+
this.state.notifyDefinitionChanged(DefinitionChangeType.stepDeleted, result.step.id);
|
|
1849
|
+
this.updateDependantFields();
|
|
1850
|
+
return true;
|
|
1851
|
+
}
|
|
1852
|
+
tryInsert(step, targetSequence, targetIndex) {
|
|
1853
|
+
const canInsertStep = this.configuration.steps.canInsertStep
|
|
1854
|
+
? this.configuration.steps.canInsertStep(step, targetSequence, targetIndex)
|
|
1855
|
+
: true;
|
|
1856
|
+
if (!canInsertStep) {
|
|
1857
|
+
return false;
|
|
1858
|
+
}
|
|
1859
|
+
SequenceModifier.insertStep(step, targetSequence, targetIndex);
|
|
1860
|
+
this.state.notifyDefinitionChanged(DefinitionChangeType.stepInserted, step.id);
|
|
1861
|
+
this.state.setSelectedStepId(step.id);
|
|
1862
|
+
return true;
|
|
1863
|
+
}
|
|
1864
|
+
isDraggable(step, parentSequence) {
|
|
1865
|
+
return this.configuration.steps.isDraggable ? this.configuration.steps.isDraggable(step, parentSequence) : true;
|
|
1866
|
+
}
|
|
1867
|
+
tryMove(sourceSequence, step, targetSequence, targetIndex) {
|
|
1868
|
+
const canMoveStep = this.configuration.steps.canMoveStep
|
|
1869
|
+
? this.configuration.steps.canMoveStep(sourceSequence, step, targetSequence, targetIndex)
|
|
1870
|
+
: true;
|
|
1871
|
+
if (!canMoveStep) {
|
|
1872
|
+
return false;
|
|
1873
|
+
}
|
|
1874
|
+
SequenceModifier.moveStep(sourceSequence, step, targetSequence, targetIndex);
|
|
1875
|
+
this.state.notifyDefinitionChanged(DefinitionChangeType.stepMoved, step.id);
|
|
1876
|
+
this.state.setSelectedStepId(step.id);
|
|
1877
|
+
return true;
|
|
1878
|
+
}
|
|
1879
|
+
replaceDefinition(definition) {
|
|
1880
|
+
if (!definition) {
|
|
1881
|
+
throw new Error('Definition is empty');
|
|
1882
|
+
}
|
|
1883
|
+
this.state.setDefinition(definition);
|
|
1884
|
+
this.updateDependantFields();
|
|
1885
|
+
}
|
|
1886
|
+
updateDependantFields() {
|
|
1887
|
+
if (this.state.selectedStepId) {
|
|
1888
|
+
const found = this.stepsTraverser.findById(this.state.definition, this.state.selectedStepId);
|
|
1889
|
+
if (!found) {
|
|
1890
|
+
// We need to unselect step when it's deleted.
|
|
1891
|
+
this.state.setSelectedStepId(null);
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
for (let index = 0; index < this.state.folderPath.length; index++) {
|
|
1895
|
+
const stepId = this.state.folderPath[index];
|
|
1896
|
+
const found = this.stepsTraverser.findById(this.state.definition, stepId);
|
|
1897
|
+
if (!found) {
|
|
1898
|
+
// We need to update path if any folder is deleted.
|
|
1899
|
+
const newPath = this.state.folderPath.slice(0, index);
|
|
1900
|
+
this.state.setFolderPath(newPath);
|
|
1901
|
+
break;
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1907
|
+
class HistoryController {
|
|
1908
|
+
static create(state, definitionModifier, configuration) {
|
|
1909
|
+
if (!configuration.undoStackSize || configuration.undoStackSize < 1) {
|
|
1910
|
+
throw new Error('Invalid undo stack size');
|
|
1911
|
+
}
|
|
1912
|
+
const controller = new HistoryController(state, definitionModifier, configuration.undoStackSize);
|
|
1913
|
+
controller.remember(DefinitionChangeType.rootReplaced, null);
|
|
1914
|
+
state.onDefinitionChanged.subscribe(event => {
|
|
1915
|
+
if (event.changeType !== DefinitionChangeType.rootReplaced) {
|
|
1916
|
+
controller.remember(event.changeType, event.stepId);
|
|
1917
|
+
}
|
|
1918
|
+
});
|
|
1919
|
+
return controller;
|
|
1920
|
+
}
|
|
1921
|
+
constructor(state, definitionModifier, stackSize) {
|
|
1922
|
+
this.state = state;
|
|
1923
|
+
this.definitionModifier = definitionModifier;
|
|
1924
|
+
this.stackSize = stackSize;
|
|
1925
|
+
this.stack = [];
|
|
1926
|
+
this.currentIndex = 0;
|
|
1927
|
+
}
|
|
1928
|
+
canUndo() {
|
|
1929
|
+
return this.currentIndex > 1;
|
|
1930
|
+
}
|
|
1931
|
+
undo() {
|
|
1932
|
+
this.currentIndex--;
|
|
1933
|
+
this.commit();
|
|
1934
|
+
}
|
|
1935
|
+
canRedo() {
|
|
1936
|
+
return this.currentIndex < this.stack.length;
|
|
1937
|
+
}
|
|
1938
|
+
redo() {
|
|
1939
|
+
this.currentIndex++;
|
|
1940
|
+
this.commit();
|
|
1941
|
+
}
|
|
1942
|
+
remember(changeType, stepId) {
|
|
1943
|
+
const definition = ObjectCloner.deepClone(this.state.definition);
|
|
1944
|
+
if (this.stack.length > 0 && this.currentIndex === this.stack.length) {
|
|
1945
|
+
const lastItem = this.stack[this.stack.length - 1];
|
|
1946
|
+
if (areItemsEqual(lastItem, changeType, stepId)) {
|
|
1947
|
+
lastItem.definition = definition;
|
|
1948
|
+
return;
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1951
|
+
this.stack.splice(this.currentIndex);
|
|
1952
|
+
this.stack.push({
|
|
1953
|
+
definition,
|
|
1954
|
+
changeType,
|
|
1955
|
+
stepId
|
|
1956
|
+
});
|
|
1957
|
+
if (this.stack.length > this.stackSize) {
|
|
1958
|
+
this.stack.splice(0, this.stack.length - this.stackSize - 1);
|
|
1959
|
+
}
|
|
1960
|
+
this.currentIndex = this.stack.length;
|
|
1961
|
+
}
|
|
1962
|
+
commit() {
|
|
1963
|
+
const definition = ObjectCloner.deepClone(this.stack[this.currentIndex - 1].definition);
|
|
1964
|
+
this.definitionModifier.replaceDefinition(definition);
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1967
|
+
function areItemsEqual(item, changeType, stepId) {
|
|
1968
|
+
return item.changeType === changeType && item.stepId === stepId;
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1971
|
+
class LayoutController {
|
|
1972
|
+
constructor(parent) {
|
|
1973
|
+
this.parent = parent;
|
|
1974
|
+
}
|
|
1975
|
+
isMobile() {
|
|
1976
|
+
return this.parent.clientWidth < 400; // TODO
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
class WorkspaceControllerWrapper {
|
|
1981
|
+
set(controller) {
|
|
1982
|
+
if (this.controller) {
|
|
1983
|
+
throw new Error('Controller is already set');
|
|
1984
|
+
}
|
|
1985
|
+
this.controller = controller;
|
|
1986
|
+
}
|
|
1987
|
+
get() {
|
|
1988
|
+
if (!this.controller) {
|
|
1989
|
+
throw new Error('Controller is not set');
|
|
1990
|
+
}
|
|
1991
|
+
return this.controller;
|
|
1992
|
+
}
|
|
1993
|
+
getPlaceholders() {
|
|
1994
|
+
return this.get().getPlaceholders();
|
|
1995
|
+
}
|
|
1996
|
+
getComponentByStepId(stepId) {
|
|
1997
|
+
return this.get().getComponentByStepId(stepId);
|
|
1998
|
+
}
|
|
1999
|
+
getCanvasPosition() {
|
|
2000
|
+
return this.get().getCanvasPosition();
|
|
2001
|
+
}
|
|
2002
|
+
getCanvasSize() {
|
|
2003
|
+
return this.get().getCanvasSize();
|
|
2004
|
+
}
|
|
2005
|
+
getRootComponentSize() {
|
|
2006
|
+
return this.get().getRootComponentSize();
|
|
2007
|
+
}
|
|
2008
|
+
updateBadges() {
|
|
2009
|
+
this.get().updateBadges();
|
|
2010
|
+
}
|
|
2011
|
+
updateSize() {
|
|
2012
|
+
this.get().updateSize();
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
|
|
2016
|
+
class DesignerContext {
|
|
2017
|
+
static create(parent, startDefinition, configuration, services) {
|
|
2018
|
+
const definition = ObjectCloner.deepClone(startDefinition);
|
|
2019
|
+
const layoutController = new LayoutController(parent);
|
|
2020
|
+
const isReadonly = !!configuration.isReadonly;
|
|
2021
|
+
const state = new DesignerState(definition, isReadonly);
|
|
2022
|
+
const workspaceController = new WorkspaceControllerWrapper();
|
|
2023
|
+
const behaviorController = new BehaviorController();
|
|
2024
|
+
const stepExtensionResolver = StepExtensionResolver.create(services);
|
|
2025
|
+
const stepsTraverser = new StepsTraverser(stepExtensionResolver);
|
|
2026
|
+
const definitionModifier = new DefinitionModifier(stepsTraverser, state, configuration);
|
|
2027
|
+
let historyController = undefined;
|
|
2028
|
+
if (configuration.undoStackSize) {
|
|
2029
|
+
historyController = HistoryController.create(state, definitionModifier, configuration);
|
|
2030
|
+
}
|
|
2031
|
+
const componentContext = ComponentContext.create(configuration.steps, stepExtensionResolver, services);
|
|
2032
|
+
return new DesignerContext(state, configuration, services, componentContext, stepsTraverser, definitionModifier, layoutController, workspaceController, behaviorController, historyController);
|
|
2033
|
+
}
|
|
2034
|
+
constructor(state, configuration, services, componentContext, stepsTraverser, definitionModifier, layoutController, workspaceController, behaviorController, historyController) {
|
|
2035
|
+
this.state = state;
|
|
2036
|
+
this.configuration = configuration;
|
|
2037
|
+
this.services = services;
|
|
2038
|
+
this.componentContext = componentContext;
|
|
2039
|
+
this.stepsTraverser = stepsTraverser;
|
|
2040
|
+
this.definitionModifier = definitionModifier;
|
|
2041
|
+
this.layoutController = layoutController;
|
|
2042
|
+
this.workspaceController = workspaceController;
|
|
2043
|
+
this.behaviorController = behaviorController;
|
|
2044
|
+
this.historyController = historyController;
|
|
2045
|
+
}
|
|
2046
|
+
setWorkspaceController(controller) {
|
|
2047
|
+
this.workspaceController.set(controller);
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
|
|
2051
|
+
function isElementAttached(element) {
|
|
2052
|
+
return !(document.compareDocumentPosition(element) & Node.DOCUMENT_POSITION_DISCONNECTED);
|
|
2053
|
+
}
|
|
2054
|
+
|
|
2055
|
+
const GRID_SIZE = 48;
|
|
2056
|
+
let lastGridPatternId = 0;
|
|
2057
|
+
class WorkspaceView {
|
|
2058
|
+
static create(parent, componentContext) {
|
|
2059
|
+
const defs = Dom.svg('defs');
|
|
2060
|
+
const gridPatternId = 'sqd-grid-pattern-' + lastGridPatternId++;
|
|
2061
|
+
const gridPattern = Dom.svg('pattern', {
|
|
2062
|
+
id: gridPatternId,
|
|
2063
|
+
patternUnits: 'userSpaceOnUse'
|
|
2064
|
+
});
|
|
2065
|
+
const gridPatternPath = Dom.svg('path', {
|
|
2066
|
+
class: 'sqd-grid-path',
|
|
2067
|
+
fill: 'none'
|
|
2068
|
+
});
|
|
2069
|
+
defs.appendChild(gridPattern);
|
|
2070
|
+
gridPattern.appendChild(gridPatternPath);
|
|
2071
|
+
const foreground = Dom.svg('g');
|
|
2072
|
+
const workspace = Dom.element('div', {
|
|
2073
|
+
class: 'sqd-workspace'
|
|
2074
|
+
});
|
|
2075
|
+
const canvas = Dom.svg('svg', {
|
|
2076
|
+
class: 'sqd-workspace-canvas'
|
|
2077
|
+
});
|
|
2078
|
+
canvas.appendChild(defs);
|
|
2079
|
+
canvas.appendChild(Dom.svg('rect', {
|
|
2080
|
+
width: '100%',
|
|
2081
|
+
height: '100%',
|
|
2082
|
+
fill: `url(#${gridPatternId})`
|
|
2083
|
+
}));
|
|
2084
|
+
canvas.appendChild(foreground);
|
|
2085
|
+
workspace.appendChild(canvas);
|
|
2086
|
+
parent.appendChild(workspace);
|
|
2087
|
+
const view = new WorkspaceView(workspace, canvas, gridPattern, gridPatternPath, foreground, componentContext);
|
|
2088
|
+
window.addEventListener('resize', view.onResizeHandler, false);
|
|
2089
|
+
return view;
|
|
2090
|
+
}
|
|
2091
|
+
constructor(workspace, canvas, gridPattern, gridPatternPath, foreground, context) {
|
|
2092
|
+
this.workspace = workspace;
|
|
2093
|
+
this.canvas = canvas;
|
|
2094
|
+
this.gridPattern = gridPattern;
|
|
2095
|
+
this.gridPatternPath = gridPatternPath;
|
|
2096
|
+
this.foreground = foreground;
|
|
2097
|
+
this.context = context;
|
|
2098
|
+
this.onResizeHandler = () => this.onResize();
|
|
2099
|
+
}
|
|
2100
|
+
render(sequence, parentSequencePlaceIndicator) {
|
|
2101
|
+
if (this.rootComponent) {
|
|
2102
|
+
this.foreground.removeChild(this.rootComponent.view.g);
|
|
2103
|
+
}
|
|
2104
|
+
this.rootComponent = this.context.services.rootComponent.create(this.foreground, sequence, parentSequencePlaceIndicator, this.context);
|
|
2105
|
+
this.refreshSize();
|
|
2106
|
+
}
|
|
2107
|
+
setPositionAndScale(position, scale) {
|
|
2108
|
+
const gridSize = GRID_SIZE * scale;
|
|
2109
|
+
Dom.attrs(this.gridPattern, {
|
|
2110
|
+
x: position.x,
|
|
2111
|
+
y: position.y,
|
|
2112
|
+
width: gridSize,
|
|
2113
|
+
height: gridSize
|
|
2114
|
+
});
|
|
2115
|
+
Dom.attrs(this.gridPatternPath, {
|
|
2116
|
+
d: `M ${gridSize} 0 L 0 0 0 ${gridSize}`
|
|
2117
|
+
});
|
|
2118
|
+
Dom.attrs(this.foreground, {
|
|
2119
|
+
transform: `translate(${position.x}, ${position.y}) scale(${scale})`
|
|
2120
|
+
});
|
|
2121
|
+
}
|
|
2122
|
+
getCanvasPosition() {
|
|
2123
|
+
const rect = this.canvas.getBoundingClientRect();
|
|
2124
|
+
return new Vector(rect.x + window.scrollX, rect.y + window.scrollY);
|
|
2125
|
+
}
|
|
2126
|
+
getCanvasSize() {
|
|
2127
|
+
return new Vector(this.canvas.clientWidth, this.canvas.clientHeight);
|
|
2128
|
+
}
|
|
2129
|
+
bindClick(handler) {
|
|
2130
|
+
this.canvas.addEventListener('mousedown', e => {
|
|
2131
|
+
e.preventDefault();
|
|
2132
|
+
handler(readMousePosition(e), e.target, e.button);
|
|
2133
|
+
}, false);
|
|
2134
|
+
this.canvas.addEventListener('touchstart', e => {
|
|
2135
|
+
e.preventDefault();
|
|
2136
|
+
const clientPosition = readTouchClientPosition(e);
|
|
2137
|
+
const element = document.elementFromPoint(clientPosition.x, clientPosition.y);
|
|
2138
|
+
if (element) {
|
|
2139
|
+
const position = readTouchPosition(e);
|
|
2140
|
+
handler(position, element, 0);
|
|
2141
|
+
}
|
|
2142
|
+
}, { passive: false });
|
|
2143
|
+
}
|
|
2144
|
+
bindContextMenu(handler) {
|
|
2145
|
+
this.canvas.addEventListener('contextmenu', handler, false);
|
|
2146
|
+
}
|
|
2147
|
+
bindWheel(handler) {
|
|
2148
|
+
this.canvas.addEventListener('wheel', handler, false);
|
|
2149
|
+
}
|
|
2150
|
+
destroy() {
|
|
2151
|
+
window.removeEventListener('resize', this.onResizeHandler, false);
|
|
2152
|
+
}
|
|
2153
|
+
refreshSize() {
|
|
2154
|
+
Dom.attrs(this.canvas, {
|
|
2155
|
+
width: this.workspace.offsetWidth,
|
|
2156
|
+
height: this.workspace.offsetHeight
|
|
2157
|
+
});
|
|
2158
|
+
}
|
|
2159
|
+
onResize() {
|
|
2160
|
+
this.refreshSize();
|
|
2161
|
+
}
|
|
2162
|
+
}
|
|
2163
|
+
|
|
2164
|
+
class MoveViewportBehavior {
|
|
2165
|
+
static create(state, resetSelectedStep) {
|
|
2166
|
+
return new MoveViewportBehavior(state.viewport.position, resetSelectedStep, state);
|
|
2167
|
+
}
|
|
2168
|
+
constructor(startPosition, resetSelectedStep, state) {
|
|
2169
|
+
this.startPosition = startPosition;
|
|
2170
|
+
this.resetSelectedStep = resetSelectedStep;
|
|
2171
|
+
this.state = state;
|
|
2172
|
+
}
|
|
2173
|
+
onStart() {
|
|
2174
|
+
if (this.resetSelectedStep) {
|
|
2175
|
+
const stepId = this.state.tryGetLastStepIdFromFolderPath();
|
|
2176
|
+
this.state.setSelectedStepId(stepId);
|
|
2177
|
+
}
|
|
2178
|
+
}
|
|
2179
|
+
onMove(delta) {
|
|
2180
|
+
this.state.setViewport({
|
|
2181
|
+
position: this.startPosition.subtract(delta),
|
|
2182
|
+
scale: this.state.viewport.scale
|
|
2183
|
+
});
|
|
2184
|
+
}
|
|
2185
|
+
onEnd() {
|
|
2186
|
+
// Nothing to do.
|
|
2187
|
+
}
|
|
2188
|
+
}
|
|
2189
|
+
|
|
2190
|
+
class TriggerCustomActionBehavior {
|
|
2191
|
+
static create(designerContext, clickedElement, resolvedClick) {
|
|
2192
|
+
return new TriggerCustomActionBehavior(clickedElement, resolvedClick, designerContext.configuration.customActionHandler);
|
|
2193
|
+
}
|
|
2194
|
+
constructor(clickedElement, resolvedClick, customActionHandler) {
|
|
2195
|
+
this.clickedElement = clickedElement;
|
|
2196
|
+
this.resolvedClick = resolvedClick;
|
|
2197
|
+
this.customActionHandler = customActionHandler;
|
|
2198
|
+
}
|
|
2199
|
+
onStart() {
|
|
2200
|
+
// Nothing...
|
|
2201
|
+
}
|
|
2202
|
+
onMove() {
|
|
2203
|
+
// Nothing...
|
|
2204
|
+
}
|
|
2205
|
+
onEnd(_, element) {
|
|
2206
|
+
if (this.clickedElement !== element) {
|
|
2207
|
+
return;
|
|
2208
|
+
}
|
|
2209
|
+
if (!this.customActionHandler) {
|
|
2210
|
+
console.warn('Custom action handler is not defined');
|
|
2211
|
+
return;
|
|
2212
|
+
}
|
|
2213
|
+
const action = this.resolvedClick.command.action;
|
|
2214
|
+
this.customActionHandler(action, this.resolvedClick.component.step);
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
|
|
2218
|
+
class SelectStepBehavior {
|
|
2219
|
+
static create(pressedStepComponent, isDragDisabled, designerContext) {
|
|
2220
|
+
return new SelectStepBehavior(pressedStepComponent, isDragDisabled, designerContext, designerContext.state);
|
|
2221
|
+
}
|
|
2222
|
+
constructor(pressedStepComponent, isDragDisabled, designerContext, state) {
|
|
2223
|
+
this.pressedStepComponent = pressedStepComponent;
|
|
2224
|
+
this.isDragDisabled = isDragDisabled;
|
|
2225
|
+
this.designerContext = designerContext;
|
|
2226
|
+
this.state = state;
|
|
2227
|
+
}
|
|
2228
|
+
onStart() {
|
|
2229
|
+
// Nothing to do.
|
|
2230
|
+
}
|
|
2231
|
+
onMove(delta) {
|
|
2232
|
+
if (delta.distance() > 2) {
|
|
2233
|
+
const canDrag = !this.state.isReadonly && !this.isDragDisabled;
|
|
2234
|
+
if (canDrag) {
|
|
2235
|
+
this.state.setSelectedStepId(null);
|
|
2236
|
+
return DragStepBehavior.create(this.designerContext, this.pressedStepComponent.step, this.pressedStepComponent);
|
|
2237
|
+
}
|
|
2238
|
+
else {
|
|
2239
|
+
return MoveViewportBehavior.create(this.state, false);
|
|
2240
|
+
}
|
|
2241
|
+
}
|
|
2242
|
+
}
|
|
2243
|
+
onEnd(interrupt) {
|
|
2244
|
+
if (!interrupt) {
|
|
2245
|
+
this.state.setSelectedStepId(this.pressedStepComponent.step.id);
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2249
|
+
|
|
2250
|
+
class OpenFolderBehavior {
|
|
2251
|
+
static create(context, clickedElement, resolvedClick) {
|
|
2252
|
+
return new OpenFolderBehavior(context, clickedElement, resolvedClick);
|
|
2253
|
+
}
|
|
2254
|
+
constructor(context, clickedElement, resolvedClick) {
|
|
2255
|
+
this.context = context;
|
|
2256
|
+
this.clickedElement = clickedElement;
|
|
2257
|
+
this.resolvedClick = resolvedClick;
|
|
2258
|
+
}
|
|
2259
|
+
onStart() {
|
|
2260
|
+
// Nothing...
|
|
2261
|
+
}
|
|
2262
|
+
onMove() {
|
|
2263
|
+
// Nothing...
|
|
2264
|
+
}
|
|
2265
|
+
onEnd(_, element) {
|
|
2266
|
+
if (this.clickedElement === element) {
|
|
2267
|
+
const stepId = this.resolvedClick.component.step.id;
|
|
2268
|
+
this.context.state.pushStepIdToFolderPath(stepId);
|
|
2269
|
+
}
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2272
|
+
|
|
2273
|
+
class ClickBehaviorResolver {
|
|
2274
|
+
constructor(designerContext, state) {
|
|
2275
|
+
this.designerContext = designerContext;
|
|
2276
|
+
this.state = state;
|
|
2277
|
+
}
|
|
2278
|
+
resolve(rootComponent, element, position, forceDisableDrag) {
|
|
2279
|
+
const click = {
|
|
2280
|
+
element,
|
|
2281
|
+
position,
|
|
2282
|
+
scale: this.state.viewport.scale
|
|
2283
|
+
};
|
|
2284
|
+
const result = rootComponent.resolveClick(click);
|
|
2285
|
+
if (!result) {
|
|
2286
|
+
return MoveViewportBehavior.create(this.state, true);
|
|
2287
|
+
}
|
|
2288
|
+
switch (result.command.type) {
|
|
2289
|
+
case ClickCommandType.selectStep: {
|
|
2290
|
+
const isDragDisabled = forceDisableDrag ||
|
|
2291
|
+
this.state.isDragDisabled ||
|
|
2292
|
+
!this.designerContext.definitionModifier.isDraggable(result.component.step, result.component.parentSequence);
|
|
2293
|
+
return SelectStepBehavior.create(result.component, isDragDisabled, this.designerContext);
|
|
2294
|
+
}
|
|
2295
|
+
case ClickCommandType.openFolder:
|
|
2296
|
+
return OpenFolderBehavior.create(this.designerContext, element, result);
|
|
2297
|
+
case ClickCommandType.triggerCustomAction:
|
|
2298
|
+
return TriggerCustomActionBehavior.create(this.designerContext, element, result);
|
|
2299
|
+
default:
|
|
2300
|
+
throw new Error('Not supported behavior type');
|
|
2301
|
+
}
|
|
2302
|
+
}
|
|
2303
|
+
}
|
|
2304
|
+
|
|
2305
|
+
class BadgesResultFactory {
|
|
2306
|
+
static create(services) {
|
|
2307
|
+
return services.badges.map(ext => ext.createStartValue());
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
|
|
2311
|
+
class Workspace {
|
|
2312
|
+
static create(parent, designerContext, api) {
|
|
2313
|
+
const view = WorkspaceView.create(parent, designerContext.componentContext);
|
|
2314
|
+
const clickBehaviorResolver = new ClickBehaviorResolver(designerContext, designerContext.state);
|
|
2315
|
+
const wheelController = designerContext.services.wheelController.create(api.workspace);
|
|
2316
|
+
const workspace = new Workspace(view, designerContext.stepsTraverser, designerContext.state, designerContext.behaviorController, wheelController, clickBehaviorResolver, api.viewport, designerContext.services);
|
|
2317
|
+
setTimeout(() => {
|
|
2318
|
+
workspace.render();
|
|
2319
|
+
api.viewport.resetViewport();
|
|
2320
|
+
workspace.onReady.forward();
|
|
2321
|
+
});
|
|
2322
|
+
designerContext.setWorkspaceController(workspace);
|
|
2323
|
+
designerContext.state.onViewportChanged.subscribe(vp => workspace.onViewportChanged(vp));
|
|
2324
|
+
designerContext.state.onIsDraggingChanged.subscribe(is => workspace.onIsDraggingChanged(is));
|
|
2325
|
+
race(0, designerContext.state.onDefinitionChanged, designerContext.state.onSelectedStepIdChanged, designerContext.state.onFolderPathChanged).subscribe(r => {
|
|
2326
|
+
workspace.onStateChanged(r[0], r[1], r[2]);
|
|
2327
|
+
});
|
|
2328
|
+
view.bindClick((p, t, b) => workspace.onClick(p, t, b));
|
|
2329
|
+
view.bindWheel(e => workspace.onWheel(e));
|
|
2330
|
+
view.bindContextMenu(e => workspace.onContextMenu(e));
|
|
2331
|
+
return workspace;
|
|
2332
|
+
}
|
|
2333
|
+
constructor(view, stepsTraverser, state, behaviorController, wheelController, clickBehaviorResolver, viewportApi, services) {
|
|
2334
|
+
this.view = view;
|
|
2335
|
+
this.stepsTraverser = stepsTraverser;
|
|
2336
|
+
this.state = state;
|
|
2337
|
+
this.behaviorController = behaviorController;
|
|
2338
|
+
this.wheelController = wheelController;
|
|
2339
|
+
this.clickBehaviorResolver = clickBehaviorResolver;
|
|
2340
|
+
this.viewportApi = viewportApi;
|
|
2341
|
+
this.services = services;
|
|
2342
|
+
this.onReady = new SimpleEvent();
|
|
2343
|
+
this.isValid = false;
|
|
2344
|
+
this.selectedStepComponent = null;
|
|
2345
|
+
}
|
|
2346
|
+
render() {
|
|
2347
|
+
this.selectedStepComponent = null;
|
|
2348
|
+
let parentSequencePlaceIndicator;
|
|
2349
|
+
let sequence;
|
|
2350
|
+
const stepId = this.state.tryGetLastStepIdFromFolderPath();
|
|
2351
|
+
if (stepId) {
|
|
2352
|
+
const result = this.stepsTraverser.getChildAndParentSequences(this.state.definition, stepId);
|
|
2353
|
+
sequence = result.childSequence;
|
|
2354
|
+
parentSequencePlaceIndicator = {
|
|
2355
|
+
sequence: result.parentSequence,
|
|
2356
|
+
index: result.index
|
|
2357
|
+
};
|
|
2358
|
+
}
|
|
2359
|
+
else {
|
|
2360
|
+
sequence = this.state.definition.sequence;
|
|
2361
|
+
parentSequencePlaceIndicator = null;
|
|
2362
|
+
}
|
|
2363
|
+
this.view.render(sequence, parentSequencePlaceIndicator);
|
|
2364
|
+
this.trySelectStepComponent(this.state.selectedStepId);
|
|
2365
|
+
this.updateBadges();
|
|
2366
|
+
}
|
|
2367
|
+
getPlaceholders() {
|
|
2368
|
+
const result = [];
|
|
2369
|
+
this.getRootComponent().getPlaceholders(result);
|
|
2370
|
+
return result;
|
|
2371
|
+
}
|
|
2372
|
+
getComponentByStepId(stepId) {
|
|
2373
|
+
const component = this.getRootComponent().findById(stepId);
|
|
2374
|
+
if (!component) {
|
|
2375
|
+
throw new Error(`Cannot find component for step id: ${stepId}`);
|
|
2376
|
+
}
|
|
2377
|
+
return component;
|
|
2378
|
+
}
|
|
2379
|
+
getCanvasPosition() {
|
|
2380
|
+
return this.view.getCanvasPosition();
|
|
2381
|
+
}
|
|
2382
|
+
getCanvasSize() {
|
|
2383
|
+
return this.view.getCanvasSize();
|
|
2384
|
+
}
|
|
2385
|
+
getRootComponentSize() {
|
|
2386
|
+
const view = this.getRootComponent().view;
|
|
2387
|
+
return new Vector(view.width, view.height);
|
|
2388
|
+
}
|
|
2389
|
+
updateSize() {
|
|
2390
|
+
setTimeout(() => this.view.refreshSize());
|
|
2391
|
+
}
|
|
2392
|
+
updateBadges() {
|
|
2393
|
+
const result = BadgesResultFactory.create(this.services);
|
|
2394
|
+
this.getRootComponent().updateBadges(result);
|
|
2395
|
+
// TODO: this is a weak assumption
|
|
2396
|
+
this.isValid = Boolean(result[0]);
|
|
2397
|
+
}
|
|
2398
|
+
destroy() {
|
|
2399
|
+
this.view.destroy();
|
|
2400
|
+
}
|
|
2401
|
+
onClick(position, target, buttonIndex) {
|
|
2402
|
+
const isPrimaryButton = buttonIndex === 0;
|
|
2403
|
+
const isMiddleButton = buttonIndex === 1;
|
|
2404
|
+
if (isPrimaryButton || isMiddleButton) {
|
|
2405
|
+
const rootComponent = this.getRootComponent();
|
|
2406
|
+
const forceDisableDrag = isMiddleButton;
|
|
2407
|
+
const behavior = this.clickBehaviorResolver.resolve(rootComponent, target, position, forceDisableDrag);
|
|
2408
|
+
this.behaviorController.start(position, behavior);
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
onWheel(e) {
|
|
2412
|
+
e.preventDefault();
|
|
2413
|
+
e.stopPropagation();
|
|
2414
|
+
this.wheelController.onWheel(e);
|
|
2415
|
+
}
|
|
2416
|
+
onContextMenu(e) {
|
|
2417
|
+
e.preventDefault();
|
|
2418
|
+
}
|
|
2419
|
+
onIsDraggingChanged(isDragging) {
|
|
2420
|
+
this.getRootComponent().setIsDragging(isDragging);
|
|
2421
|
+
}
|
|
2422
|
+
onViewportChanged(viewport) {
|
|
2423
|
+
this.view.setPositionAndScale(viewport.position, viewport.scale);
|
|
2424
|
+
}
|
|
2425
|
+
onStateChanged(definitionChanged, selectedStepIdChanged, folderPathChanged) {
|
|
2426
|
+
if (folderPathChanged) {
|
|
2427
|
+
this.render();
|
|
2428
|
+
this.viewportApi.resetViewport();
|
|
2429
|
+
}
|
|
2430
|
+
else if (definitionChanged) {
|
|
2431
|
+
if (definitionChanged.changeType === DefinitionChangeType.stepPropertyChanged) {
|
|
2432
|
+
this.updateBadges();
|
|
2433
|
+
}
|
|
2434
|
+
else {
|
|
2435
|
+
this.render();
|
|
2436
|
+
}
|
|
2437
|
+
}
|
|
2438
|
+
else if (selectedStepIdChanged !== undefined) {
|
|
2439
|
+
this.trySelectStepComponent(selectedStepIdChanged);
|
|
2440
|
+
}
|
|
2441
|
+
}
|
|
2442
|
+
trySelectStepComponent(stepId) {
|
|
2443
|
+
if (this.selectedStepComponent) {
|
|
2444
|
+
this.selectedStepComponent.setIsSelected(false);
|
|
2445
|
+
this.selectedStepComponent = null;
|
|
2446
|
+
}
|
|
2447
|
+
if (stepId) {
|
|
2448
|
+
this.selectedStepComponent = this.getRootComponent().findById(stepId);
|
|
2449
|
+
if (this.selectedStepComponent) {
|
|
2450
|
+
this.selectedStepComponent.setIsSelected(true);
|
|
2451
|
+
}
|
|
2452
|
+
}
|
|
2453
|
+
}
|
|
2454
|
+
getRootComponent() {
|
|
2455
|
+
if (this.view.rootComponent) {
|
|
2456
|
+
return this.view.rootComponent;
|
|
2457
|
+
}
|
|
2458
|
+
throw new Error('Root component not found');
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
|
|
2462
|
+
class DesignerView {
|
|
2463
|
+
static create(parent, designerContext, configuration, api) {
|
|
2464
|
+
const theme = configuration.theme || 'light';
|
|
2465
|
+
const root = Dom.element('div', {
|
|
2466
|
+
class: `sqd-designer sqd-theme-${theme}`
|
|
2467
|
+
});
|
|
2468
|
+
parent.appendChild(root);
|
|
2469
|
+
const workspace = Workspace.create(root, designerContext, api);
|
|
2470
|
+
const uiComponents = designerContext.services.uiComponents.map(factory => factory.create(root, api));
|
|
2471
|
+
const daemons = designerContext.services.daemons.map(factory => factory.create(api));
|
|
2472
|
+
const view = new DesignerView(root, designerContext.layoutController, workspace, uiComponents, daemons);
|
|
2473
|
+
view.reloadLayout();
|
|
2474
|
+
window.addEventListener('resize', view.onResizeHandler, false);
|
|
2475
|
+
return view;
|
|
2476
|
+
}
|
|
2477
|
+
constructor(root, layoutController, workspace, uiComponents, daemons) {
|
|
2478
|
+
this.root = root;
|
|
2479
|
+
this.layoutController = layoutController;
|
|
2480
|
+
this.workspace = workspace;
|
|
2481
|
+
this.uiComponents = uiComponents;
|
|
2482
|
+
this.daemons = daemons;
|
|
2483
|
+
this.onResizeHandler = () => this.onResize();
|
|
2484
|
+
}
|
|
2485
|
+
destroy() {
|
|
2486
|
+
var _a;
|
|
2487
|
+
window.removeEventListener('resize', this.onResizeHandler, false);
|
|
2488
|
+
this.workspace.destroy();
|
|
2489
|
+
this.uiComponents.forEach(component => component.destroy());
|
|
2490
|
+
this.daemons.forEach(daemon => daemon.destroy());
|
|
2491
|
+
(_a = this.root.parentElement) === null || _a === void 0 ? void 0 : _a.removeChild(this.root);
|
|
2492
|
+
}
|
|
2493
|
+
onResize() {
|
|
2494
|
+
this.reloadLayout();
|
|
2495
|
+
}
|
|
2496
|
+
reloadLayout() {
|
|
2497
|
+
const isMobile = this.layoutController.isMobile();
|
|
2498
|
+
Dom.toggleClass(this.root, !isMobile, 'sqd-layout-desktop');
|
|
2499
|
+
Dom.toggleClass(this.root, isMobile, 'sqd-layout-mobile');
|
|
2500
|
+
}
|
|
2501
|
+
}
|
|
2502
|
+
|
|
2503
|
+
const SAFE_OFFSET = 10;
|
|
2504
|
+
class DefaultDraggedComponent {
|
|
2505
|
+
static create(parent, step, componentContext) {
|
|
2506
|
+
const canvas = Dom.svg('svg');
|
|
2507
|
+
canvas.style.marginLeft = -SAFE_OFFSET + 'px';
|
|
2508
|
+
canvas.style.marginTop = -SAFE_OFFSET + 'px';
|
|
2509
|
+
parent.appendChild(canvas);
|
|
2510
|
+
const fakeStepContext = {
|
|
2511
|
+
parentSequence: [],
|
|
2512
|
+
step,
|
|
2513
|
+
depth: 0,
|
|
2514
|
+
position: 0,
|
|
2515
|
+
isInputConnected: true,
|
|
2516
|
+
isOutputConnected: true
|
|
2517
|
+
};
|
|
2518
|
+
const stepComponent = componentContext.stepComponentFactory.create(canvas, fakeStepContext, componentContext);
|
|
2519
|
+
Dom.attrs(canvas, {
|
|
2520
|
+
width: stepComponent.view.width + SAFE_OFFSET * 2,
|
|
2521
|
+
height: stepComponent.view.height + SAFE_OFFSET * 2
|
|
2522
|
+
});
|
|
2523
|
+
Dom.translate(stepComponent.view.g, SAFE_OFFSET, SAFE_OFFSET);
|
|
2524
|
+
return new DefaultDraggedComponent(stepComponent.view.width, stepComponent.view.height);
|
|
2525
|
+
}
|
|
2526
|
+
constructor(width, height) {
|
|
2527
|
+
this.width = width;
|
|
2528
|
+
this.height = height;
|
|
2529
|
+
}
|
|
2530
|
+
destroy() {
|
|
2531
|
+
// Nothing to destroy...
|
|
2532
|
+
}
|
|
2533
|
+
}
|
|
2534
|
+
|
|
2535
|
+
class DefaultDraggedComponentExtension {
|
|
2536
|
+
constructor() {
|
|
2537
|
+
this.create = DefaultDraggedComponent.create;
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2540
|
+
|
|
2541
|
+
class ControlBarView {
|
|
2542
|
+
static create(parent, isUndoRedoSupported) {
|
|
2543
|
+
const root = Dom.element('div', {
|
|
2544
|
+
class: 'sqd-control-bar'
|
|
2545
|
+
});
|
|
2546
|
+
const resetButton = createButton(Icons.center, 'Reset view');
|
|
2547
|
+
root.appendChild(resetButton);
|
|
2548
|
+
const zoomInButton = createButton(Icons.zoomIn, 'Zoom in');
|
|
2549
|
+
root.appendChild(zoomInButton);
|
|
2550
|
+
const zoomOutButton = createButton(Icons.zoomOut, 'Zoom out');
|
|
2551
|
+
root.appendChild(zoomOutButton);
|
|
2552
|
+
let undoButton = null;
|
|
2553
|
+
let redoButton = null;
|
|
2554
|
+
if (isUndoRedoSupported) {
|
|
2555
|
+
undoButton = createButton(Icons.undo, 'Undo');
|
|
2556
|
+
root.appendChild(undoButton);
|
|
2557
|
+
redoButton = createButton(Icons.redo, 'Redo');
|
|
2558
|
+
root.appendChild(redoButton);
|
|
2559
|
+
}
|
|
2560
|
+
const disableDragButton = createButton(Icons.move, 'Turn on/off drag and drop');
|
|
2561
|
+
disableDragButton.classList.add('sqd-disabled');
|
|
2562
|
+
root.appendChild(disableDragButton);
|
|
2563
|
+
const deleteButton = createButton(Icons.delete, 'Delete selected step');
|
|
2564
|
+
deleteButton.classList.add('sqd-delete');
|
|
2565
|
+
deleteButton.classList.add('sqd-hidden');
|
|
2566
|
+
root.appendChild(deleteButton);
|
|
2567
|
+
parent.appendChild(root);
|
|
2568
|
+
return new ControlBarView(resetButton, zoomInButton, zoomOutButton, undoButton, redoButton, disableDragButton, deleteButton);
|
|
2569
|
+
}
|
|
2570
|
+
constructor(resetButton, zoomInButton, zoomOutButton, undoButton, redoButton, disableDragButton, deleteButton) {
|
|
2571
|
+
this.resetButton = resetButton;
|
|
2572
|
+
this.zoomInButton = zoomInButton;
|
|
2573
|
+
this.zoomOutButton = zoomOutButton;
|
|
2574
|
+
this.undoButton = undoButton;
|
|
2575
|
+
this.redoButton = redoButton;
|
|
2576
|
+
this.disableDragButton = disableDragButton;
|
|
2577
|
+
this.deleteButton = deleteButton;
|
|
2578
|
+
}
|
|
2579
|
+
bindResetButtonClick(handler) {
|
|
2580
|
+
bindClick(this.resetButton, handler);
|
|
2581
|
+
}
|
|
2582
|
+
bindZoomInButtonClick(handler) {
|
|
2583
|
+
bindClick(this.zoomInButton, handler);
|
|
2584
|
+
}
|
|
2585
|
+
bindZoomOutButtonClick(handler) {
|
|
2586
|
+
bindClick(this.zoomOutButton, handler);
|
|
2587
|
+
}
|
|
2588
|
+
bindUndoButtonClick(handler) {
|
|
2589
|
+
if (!this.undoButton) {
|
|
2590
|
+
throw new Error('Undo button is disabled');
|
|
2591
|
+
}
|
|
2592
|
+
bindClick(this.undoButton, handler);
|
|
2593
|
+
}
|
|
2594
|
+
bindRedoButtonClick(handler) {
|
|
2595
|
+
if (!this.redoButton) {
|
|
2596
|
+
throw new Error('Redo button is disabled');
|
|
2597
|
+
}
|
|
2598
|
+
bindClick(this.redoButton, handler);
|
|
2599
|
+
}
|
|
2600
|
+
bindDisableDragButtonClick(handler) {
|
|
2601
|
+
bindClick(this.disableDragButton, handler);
|
|
2602
|
+
}
|
|
2603
|
+
bindDeleteButtonClick(handler) {
|
|
2604
|
+
bindClick(this.deleteButton, handler);
|
|
2605
|
+
}
|
|
2606
|
+
setIsDeleteButtonHidden(isHidden) {
|
|
2607
|
+
Dom.toggleClass(this.deleteButton, isHidden, 'sqd-hidden');
|
|
2608
|
+
}
|
|
2609
|
+
setDisableDragButtonDisabled(isDisabled) {
|
|
2610
|
+
Dom.toggleClass(this.disableDragButton, isDisabled, 'sqd-disabled');
|
|
2611
|
+
}
|
|
2612
|
+
setUndoButtonDisabled(isDisabled) {
|
|
2613
|
+
if (!this.undoButton) {
|
|
2614
|
+
throw new Error('Undo button is disabled');
|
|
2615
|
+
}
|
|
2616
|
+
Dom.toggleClass(this.undoButton, isDisabled, 'sqd-disabled');
|
|
2617
|
+
}
|
|
2618
|
+
setRedoButtonDisabled(isDisabled) {
|
|
2619
|
+
if (!this.redoButton) {
|
|
2620
|
+
throw new Error('Redo button is disabled');
|
|
2621
|
+
}
|
|
2622
|
+
Dom.toggleClass(this.redoButton, isDisabled, 'sqd-disabled');
|
|
2623
|
+
}
|
|
2624
|
+
}
|
|
2625
|
+
function bindClick(element, handler) {
|
|
2626
|
+
element.addEventListener('click', e => {
|
|
2627
|
+
e.preventDefault();
|
|
2628
|
+
handler();
|
|
2629
|
+
}, false);
|
|
2630
|
+
}
|
|
2631
|
+
function createButton(d, title) {
|
|
2632
|
+
const button = Dom.element('div', {
|
|
2633
|
+
class: 'sqd-control-bar-button',
|
|
2634
|
+
title
|
|
2635
|
+
});
|
|
2636
|
+
const icon = Icons.createSvg('sqd-control-bar-button-icon', d);
|
|
2637
|
+
button.appendChild(icon);
|
|
2638
|
+
return button;
|
|
2639
|
+
}
|
|
2640
|
+
|
|
2641
|
+
class ControlBar {
|
|
2642
|
+
static create(parent, api) {
|
|
2643
|
+
const isUndoRedoSupported = api.controlBar.isUndoRedoSupported();
|
|
2644
|
+
const view = ControlBarView.create(parent, isUndoRedoSupported);
|
|
2645
|
+
const bar = new ControlBar(view, api.controlBar, isUndoRedoSupported);
|
|
2646
|
+
view.bindResetButtonClick(() => bar.onResetButtonClicked());
|
|
2647
|
+
view.bindZoomInButtonClick(() => bar.onZoomInButtonClicked());
|
|
2648
|
+
view.bindZoomOutButtonClick(() => bar.onZoomOutButtonClicked());
|
|
2649
|
+
view.bindDisableDragButtonClick(() => bar.onMoveButtonClicked());
|
|
2650
|
+
view.bindDeleteButtonClick(() => bar.onDeleteButtonClicked());
|
|
2651
|
+
api.controlBar.subscribe(() => bar.refreshButtons());
|
|
2652
|
+
if (isUndoRedoSupported) {
|
|
2653
|
+
view.bindUndoButtonClick(() => bar.onUndoButtonClicked());
|
|
2654
|
+
view.bindRedoButtonClick(() => bar.onRedoButtonClicked());
|
|
2655
|
+
}
|
|
2656
|
+
bar.refreshButtons();
|
|
2657
|
+
return bar;
|
|
2658
|
+
}
|
|
2659
|
+
constructor(view, controlBarApi, isUndoRedoSupported) {
|
|
2660
|
+
this.view = view;
|
|
2661
|
+
this.controlBarApi = controlBarApi;
|
|
2662
|
+
this.isUndoRedoSupported = isUndoRedoSupported;
|
|
2663
|
+
}
|
|
2664
|
+
destroy() {
|
|
2665
|
+
//
|
|
2666
|
+
}
|
|
2667
|
+
onResetButtonClicked() {
|
|
2668
|
+
this.controlBarApi.resetViewport();
|
|
2669
|
+
}
|
|
2670
|
+
onZoomInButtonClicked() {
|
|
2671
|
+
this.controlBarApi.zoomIn();
|
|
2672
|
+
}
|
|
2673
|
+
onZoomOutButtonClicked() {
|
|
2674
|
+
this.controlBarApi.zoomOut();
|
|
2675
|
+
}
|
|
2676
|
+
onMoveButtonClicked() {
|
|
2677
|
+
this.controlBarApi.toggleIsDragDisabled();
|
|
2678
|
+
}
|
|
2679
|
+
onUndoButtonClicked() {
|
|
2680
|
+
this.controlBarApi.tryUndo();
|
|
2681
|
+
}
|
|
2682
|
+
onRedoButtonClicked() {
|
|
2683
|
+
this.controlBarApi.tryRedo();
|
|
2684
|
+
}
|
|
2685
|
+
onDeleteButtonClicked() {
|
|
2686
|
+
this.controlBarApi.tryDelete();
|
|
2687
|
+
}
|
|
2688
|
+
refreshButtons() {
|
|
2689
|
+
this.refreshDeleteButtonVisibility();
|
|
2690
|
+
this.refreshIsDragDisabled();
|
|
2691
|
+
if (this.isUndoRedoSupported) {
|
|
2692
|
+
this.refreshUndoRedoAvailability();
|
|
2693
|
+
}
|
|
2694
|
+
}
|
|
2695
|
+
//
|
|
2696
|
+
refreshIsDragDisabled() {
|
|
2697
|
+
const isDragDisabled = this.controlBarApi.isDragDisabled();
|
|
2698
|
+
this.view.setDisableDragButtonDisabled(!isDragDisabled);
|
|
2699
|
+
}
|
|
2700
|
+
refreshUndoRedoAvailability() {
|
|
2701
|
+
const canUndo = this.controlBarApi.canUndo();
|
|
2702
|
+
const canRedo = this.controlBarApi.canRedo();
|
|
2703
|
+
this.view.setUndoButtonDisabled(!canUndo);
|
|
2704
|
+
this.view.setRedoButtonDisabled(!canRedo);
|
|
2705
|
+
}
|
|
2706
|
+
refreshDeleteButtonVisibility() {
|
|
2707
|
+
const canDelete = this.controlBarApi.canDelete();
|
|
2708
|
+
this.view.setIsDeleteButtonHidden(!canDelete);
|
|
2709
|
+
}
|
|
2710
|
+
}
|
|
2711
|
+
|
|
2712
|
+
class ControlBarExtension {
|
|
2713
|
+
constructor() {
|
|
2714
|
+
this.create = ControlBar.create;
|
|
2715
|
+
}
|
|
2716
|
+
}
|
|
2717
|
+
|
|
2718
|
+
const supportedKeys = ['Backspace', 'Delete'];
|
|
2719
|
+
const ignoreTagNames = ['INPUT', 'TEXTAREA'];
|
|
2720
|
+
class KeyboardDaemon {
|
|
2721
|
+
static create(api) {
|
|
2722
|
+
const controller = new KeyboardDaemon(api.controlBar);
|
|
2723
|
+
document.addEventListener('keyup', controller.onKeyUp, false);
|
|
2724
|
+
return controller;
|
|
2725
|
+
}
|
|
2726
|
+
constructor(controlBarApi) {
|
|
2727
|
+
this.controlBarApi = controlBarApi;
|
|
2728
|
+
this.onKeyUp = (e) => {
|
|
2729
|
+
if (!supportedKeys.includes(e.key)) {
|
|
2730
|
+
return;
|
|
2731
|
+
}
|
|
2732
|
+
if (document.activeElement && ignoreTagNames.includes(document.activeElement.tagName)) {
|
|
2733
|
+
return;
|
|
2734
|
+
}
|
|
2735
|
+
const isDeletable = this.controlBarApi.canDelete();
|
|
2736
|
+
if (isDeletable) {
|
|
2737
|
+
e.preventDefault();
|
|
2738
|
+
e.stopPropagation();
|
|
2739
|
+
this.controlBarApi.tryDelete();
|
|
2740
|
+
}
|
|
2741
|
+
};
|
|
2742
|
+
}
|
|
2743
|
+
destroy() {
|
|
2744
|
+
document.removeEventListener('keyup', this.onKeyUp, false);
|
|
2745
|
+
}
|
|
2746
|
+
}
|
|
2747
|
+
|
|
2748
|
+
class KeyboardDaemonExtension {
|
|
2749
|
+
constructor() {
|
|
2750
|
+
this.create = KeyboardDaemon.create;
|
|
2751
|
+
}
|
|
2752
|
+
}
|
|
2753
|
+
|
|
2754
|
+
class SmartEditorView {
|
|
2755
|
+
static create(parent, api, configuration) {
|
|
2756
|
+
const root = Dom.element('div', {
|
|
2757
|
+
class: 'sqd-smart-editor'
|
|
2758
|
+
});
|
|
2759
|
+
const toggle = Dom.element('div', {
|
|
2760
|
+
class: 'sqd-smart-editor-toggle',
|
|
2761
|
+
title: 'Toggle editor'
|
|
2762
|
+
});
|
|
2763
|
+
parent.appendChild(toggle);
|
|
2764
|
+
parent.appendChild(root);
|
|
2765
|
+
const editor = Editor.create(root, api, 'sqd-editor sqd-step-editor', configuration.stepEditorProvider, 'sqd-editor sqd-global-editor', configuration.globalEditorProvider);
|
|
2766
|
+
return new SmartEditorView(root, toggle, editor);
|
|
2767
|
+
}
|
|
2768
|
+
constructor(root, toggle, editor) {
|
|
2769
|
+
this.root = root;
|
|
2770
|
+
this.toggle = toggle;
|
|
2771
|
+
this.editor = editor;
|
|
2772
|
+
}
|
|
2773
|
+
bindToggleIsCollapsedClick(handler) {
|
|
2774
|
+
this.toggle.addEventListener('click', e => {
|
|
2775
|
+
e.preventDefault();
|
|
2776
|
+
handler();
|
|
2777
|
+
}, false);
|
|
2778
|
+
}
|
|
2779
|
+
setIsCollapsed(isCollapsed) {
|
|
2780
|
+
Dom.toggleClass(this.root, isCollapsed, 'sqd-hidden');
|
|
2781
|
+
Dom.toggleClass(this.toggle, isCollapsed, 'sqd-collapsed');
|
|
2782
|
+
if (this.toggleIcon) {
|
|
2783
|
+
this.toggle.removeChild(this.toggleIcon);
|
|
2784
|
+
}
|
|
2785
|
+
this.toggleIcon = Icons.createSvg('sqd-smart-editor-toggle-icon', isCollapsed ? Icons.options : Icons.close);
|
|
2786
|
+
this.toggle.appendChild(this.toggleIcon);
|
|
2787
|
+
}
|
|
2788
|
+
destroy() {
|
|
2789
|
+
this.editor.destroy();
|
|
2790
|
+
}
|
|
2791
|
+
}
|
|
2792
|
+
|
|
2793
|
+
class SmartEditor {
|
|
2794
|
+
static create(parent, api, configuration) {
|
|
2795
|
+
const view = SmartEditorView.create(parent, api, configuration);
|
|
2796
|
+
const editor = new SmartEditor(view, api.workspace);
|
|
2797
|
+
view.bindToggleIsCollapsedClick(() => editor.toggleIsCollapsedClick());
|
|
2798
|
+
editor.setIsCollapsed(api.editor.isVisibleAtStart());
|
|
2799
|
+
return editor;
|
|
2800
|
+
}
|
|
2801
|
+
constructor(view, workspaceApi) {
|
|
2802
|
+
this.view = view;
|
|
2803
|
+
this.workspaceApi = workspaceApi;
|
|
2804
|
+
}
|
|
2805
|
+
setIsCollapsed(isCollapsed) {
|
|
2806
|
+
this.isCollapsed = isCollapsed;
|
|
2807
|
+
this.view.setIsCollapsed(isCollapsed);
|
|
2808
|
+
}
|
|
2809
|
+
toggleIsCollapsedClick() {
|
|
2810
|
+
this.setIsCollapsed(!this.isCollapsed);
|
|
2811
|
+
this.workspaceApi.updateSize();
|
|
2812
|
+
}
|
|
2813
|
+
destroy() {
|
|
2814
|
+
this.view.destroy();
|
|
2815
|
+
}
|
|
2816
|
+
}
|
|
2817
|
+
|
|
2818
|
+
class SmartEditorExtension {
|
|
2819
|
+
constructor(configuration) {
|
|
2820
|
+
this.configuration = configuration;
|
|
2821
|
+
}
|
|
2822
|
+
create(root, api) {
|
|
2823
|
+
return SmartEditor.create(root, api, this.configuration);
|
|
2824
|
+
}
|
|
2825
|
+
}
|
|
2826
|
+
|
|
2827
|
+
const listenerOptions = {
|
|
2828
|
+
passive: false
|
|
2829
|
+
};
|
|
2830
|
+
class ScrollBoxView {
|
|
2831
|
+
static create(parent, viewport) {
|
|
2832
|
+
const root = Dom.element('div', {
|
|
2833
|
+
class: 'sqd-scrollbox'
|
|
2834
|
+
});
|
|
2835
|
+
parent.appendChild(root);
|
|
2836
|
+
const view = new ScrollBoxView(root, viewport);
|
|
2837
|
+
window.addEventListener('resize', view.onResize, false);
|
|
2838
|
+
root.addEventListener('wheel', e => view.onWheel(e), false);
|
|
2839
|
+
root.addEventListener('touchstart', e => view.onTouchStart(e), listenerOptions);
|
|
2840
|
+
root.addEventListener('mousedown', e => view.onMouseDown(e), false);
|
|
2841
|
+
return view;
|
|
2842
|
+
}
|
|
2843
|
+
constructor(root, viewport) {
|
|
2844
|
+
this.root = root;
|
|
2845
|
+
this.viewport = viewport;
|
|
2846
|
+
this.onResize = () => {
|
|
2847
|
+
this.refresh();
|
|
2848
|
+
};
|
|
2849
|
+
this.onTouchStart = (e) => {
|
|
2850
|
+
e.preventDefault();
|
|
2851
|
+
this.startScroll(readTouchPosition(e));
|
|
2852
|
+
};
|
|
2853
|
+
this.onMouseDown = (e) => {
|
|
2854
|
+
this.startScroll(readMousePosition(e));
|
|
2855
|
+
};
|
|
2856
|
+
this.onTouchMove = (e) => {
|
|
2857
|
+
e.preventDefault();
|
|
2858
|
+
this.moveScroll(readTouchPosition(e));
|
|
2859
|
+
};
|
|
2860
|
+
this.onMouseMove = (e) => {
|
|
2861
|
+
e.preventDefault();
|
|
2862
|
+
this.moveScroll(readMousePosition(e));
|
|
2863
|
+
};
|
|
2864
|
+
this.onTouchEnd = (e) => {
|
|
2865
|
+
e.preventDefault();
|
|
2866
|
+
this.stopScroll();
|
|
2867
|
+
};
|
|
2868
|
+
this.onMouseUp = (e) => {
|
|
2869
|
+
e.preventDefault();
|
|
2870
|
+
this.stopScroll();
|
|
2871
|
+
};
|
|
2872
|
+
}
|
|
2873
|
+
setContent(element) {
|
|
2874
|
+
if (this.content) {
|
|
2875
|
+
this.root.removeChild(this.content.element);
|
|
2876
|
+
}
|
|
2877
|
+
element.classList.add('sqd-scrollbox-body');
|
|
2878
|
+
this.root.appendChild(element);
|
|
2879
|
+
this.reload(element);
|
|
2880
|
+
}
|
|
2881
|
+
refresh() {
|
|
2882
|
+
if (this.content) {
|
|
2883
|
+
this.reload(this.content.element);
|
|
2884
|
+
}
|
|
2885
|
+
}
|
|
2886
|
+
destroy() {
|
|
2887
|
+
window.removeEventListener('resize', this.onResize, false);
|
|
2888
|
+
}
|
|
2889
|
+
reload(element) {
|
|
2890
|
+
const maxHeightPercent = 0.7;
|
|
2891
|
+
const minDistance = 206;
|
|
2892
|
+
let height = Math.min(this.viewport.clientHeight * maxHeightPercent, element.clientHeight);
|
|
2893
|
+
height = Math.min(height, this.viewport.clientHeight - minDistance);
|
|
2894
|
+
this.root.style.height = height + 'px';
|
|
2895
|
+
element.style.top = '0px';
|
|
2896
|
+
this.content = {
|
|
2897
|
+
element,
|
|
2898
|
+
height
|
|
2899
|
+
};
|
|
2900
|
+
}
|
|
2901
|
+
onWheel(e) {
|
|
2902
|
+
e.stopPropagation();
|
|
2903
|
+
if (this.content) {
|
|
2904
|
+
const delta = e.deltaY > 0 ? -25 : 25;
|
|
2905
|
+
const scrollTop = this.getScrollTop();
|
|
2906
|
+
this.setScrollTop(scrollTop + delta);
|
|
2907
|
+
}
|
|
2908
|
+
}
|
|
2909
|
+
startScroll(startPosition) {
|
|
2910
|
+
if (!this.scroll) {
|
|
2911
|
+
window.addEventListener('touchmove', this.onTouchMove, listenerOptions);
|
|
2912
|
+
window.addEventListener('mousemove', this.onMouseMove, false);
|
|
2913
|
+
window.addEventListener('touchend', this.onTouchEnd, listenerOptions);
|
|
2914
|
+
window.addEventListener('mouseup', this.onMouseUp, false);
|
|
2915
|
+
}
|
|
2916
|
+
this.scroll = {
|
|
2917
|
+
startPositionY: startPosition.y,
|
|
2918
|
+
startScrollTop: this.getScrollTop()
|
|
2919
|
+
};
|
|
2920
|
+
}
|
|
2921
|
+
moveScroll(position) {
|
|
2922
|
+
if (this.scroll) {
|
|
2923
|
+
const delta = position.y - this.scroll.startPositionY;
|
|
2924
|
+
this.setScrollTop(this.scroll.startScrollTop + delta);
|
|
2925
|
+
}
|
|
2926
|
+
}
|
|
2927
|
+
stopScroll() {
|
|
2928
|
+
if (this.scroll) {
|
|
2929
|
+
window.removeEventListener('touchmove', this.onTouchMove, listenerOptions);
|
|
2930
|
+
window.removeEventListener('mousemove', this.onMouseMove, false);
|
|
2931
|
+
window.removeEventListener('touchend', this.onTouchEnd, listenerOptions);
|
|
2932
|
+
window.removeEventListener('mouseup', this.onMouseUp, false);
|
|
2933
|
+
this.scroll = undefined;
|
|
2934
|
+
}
|
|
2935
|
+
}
|
|
2936
|
+
getScrollTop() {
|
|
2937
|
+
if (this.content && this.content.element.style.top) {
|
|
2938
|
+
return parseInt(this.content.element.style.top);
|
|
2939
|
+
}
|
|
2940
|
+
return 0;
|
|
2941
|
+
}
|
|
2942
|
+
setScrollTop(scrollTop) {
|
|
2943
|
+
if (this.content) {
|
|
2944
|
+
const max = this.content.element.clientHeight - this.content.height;
|
|
2945
|
+
const limited = Math.max(Math.min(scrollTop, 0), -max);
|
|
2946
|
+
this.content.element.style.top = limited + 'px';
|
|
2947
|
+
}
|
|
2948
|
+
}
|
|
2949
|
+
}
|
|
2950
|
+
|
|
2951
|
+
const regexp = /^[a-zA-Z][a-zA-Z0-9_-]+$/;
|
|
2952
|
+
class StepTypeValidator {
|
|
2953
|
+
static validate(type) {
|
|
2954
|
+
if (!regexp.test(type)) {
|
|
2955
|
+
throw new Error(`Step type "${type}" contains not allowed characters`);
|
|
2956
|
+
}
|
|
2957
|
+
}
|
|
2958
|
+
}
|
|
2959
|
+
|
|
2960
|
+
class ToolboxItemView {
|
|
2961
|
+
static create(parent, step, api) {
|
|
2962
|
+
const root = Dom.element('div', {
|
|
2963
|
+
class: `sqd-toolbox-item sqd-type-${step.type}`,
|
|
2964
|
+
title: step.name
|
|
2965
|
+
});
|
|
2966
|
+
const iconUrl = api.tryGetIconUrl(step);
|
|
2967
|
+
const icon = Dom.element('div', {
|
|
2968
|
+
class: 'sqd-toolbox-item-icon'
|
|
2969
|
+
});
|
|
2970
|
+
if (iconUrl) {
|
|
2971
|
+
const iconImage = Dom.element('img', {
|
|
2972
|
+
class: 'sqd-toolbox-item-icon-image',
|
|
2973
|
+
src: iconUrl
|
|
2974
|
+
});
|
|
2975
|
+
icon.appendChild(iconImage);
|
|
2976
|
+
}
|
|
2977
|
+
else {
|
|
2978
|
+
icon.classList.add('sqd-no-icon');
|
|
2979
|
+
}
|
|
2980
|
+
const text = Dom.element('div', {
|
|
2981
|
+
class: 'sqd-toolbox-item-text'
|
|
2982
|
+
});
|
|
2983
|
+
text.textContent = step.name;
|
|
2984
|
+
root.appendChild(icon);
|
|
2985
|
+
root.appendChild(text);
|
|
2986
|
+
parent.appendChild(root);
|
|
2987
|
+
return new ToolboxItemView(root);
|
|
2988
|
+
}
|
|
2989
|
+
constructor(root) {
|
|
2990
|
+
this.root = root;
|
|
2991
|
+
}
|
|
2992
|
+
bindMousedown(handler) {
|
|
2993
|
+
this.root.addEventListener('mousedown', handler, false);
|
|
2994
|
+
}
|
|
2995
|
+
bindTouchstart(handler) {
|
|
2996
|
+
this.root.addEventListener('touchstart', handler, false);
|
|
2997
|
+
}
|
|
2998
|
+
bindContextMenu(handler) {
|
|
2999
|
+
this.root.addEventListener('contextmenu', handler, false);
|
|
3000
|
+
}
|
|
3001
|
+
}
|
|
3002
|
+
|
|
3003
|
+
class ToolboxItem {
|
|
3004
|
+
static create(parent, step, api) {
|
|
3005
|
+
StepTypeValidator.validate(step.type);
|
|
3006
|
+
const view = ToolboxItemView.create(parent, step, api);
|
|
3007
|
+
const item = new ToolboxItem(step, api);
|
|
3008
|
+
view.bindMousedown(e => item.onMousedown(e));
|
|
3009
|
+
view.bindTouchstart(e => item.onTouchstart(e));
|
|
3010
|
+
view.bindContextMenu(e => item.onContextMenu(e));
|
|
3011
|
+
return item;
|
|
3012
|
+
}
|
|
3013
|
+
constructor(step, api) {
|
|
3014
|
+
this.step = step;
|
|
3015
|
+
this.api = api;
|
|
3016
|
+
}
|
|
3017
|
+
onTouchstart(e) {
|
|
3018
|
+
e.preventDefault();
|
|
3019
|
+
if (e.touches.length === 1) {
|
|
3020
|
+
e.stopPropagation();
|
|
3021
|
+
this.tryDrag(readTouchPosition(e));
|
|
3022
|
+
}
|
|
3023
|
+
}
|
|
3024
|
+
onMousedown(e) {
|
|
3025
|
+
e.stopPropagation();
|
|
3026
|
+
const isPrimaryButton = e.button === 0;
|
|
3027
|
+
if (isPrimaryButton) {
|
|
3028
|
+
this.tryDrag(readMousePosition(e));
|
|
3029
|
+
}
|
|
3030
|
+
}
|
|
3031
|
+
onContextMenu(e) {
|
|
3032
|
+
e.preventDefault();
|
|
3033
|
+
}
|
|
3034
|
+
tryDrag(position) {
|
|
3035
|
+
this.api.tryDrag(position, this.step);
|
|
3036
|
+
}
|
|
3037
|
+
}
|
|
3038
|
+
|
|
3039
|
+
class ToolboxView {
|
|
3040
|
+
static create(parent, api) {
|
|
3041
|
+
const root = Dom.element('div', {
|
|
3042
|
+
class: 'sqd-toolbox'
|
|
3043
|
+
});
|
|
3044
|
+
const header = Dom.element('div', {
|
|
3045
|
+
class: 'sqd-toolbox-header'
|
|
3046
|
+
});
|
|
3047
|
+
const headerTitle = Dom.element('div', {
|
|
3048
|
+
class: 'sqd-toolbox-header-title'
|
|
3049
|
+
});
|
|
3050
|
+
headerTitle.innerText = 'Toolbox';
|
|
3051
|
+
const body = Dom.element('div', {
|
|
3052
|
+
class: 'sqd-toolbox-body'
|
|
3053
|
+
});
|
|
3054
|
+
const filterInput = Dom.element('input', {
|
|
3055
|
+
class: 'sqd-toolbox-filter',
|
|
3056
|
+
type: 'text',
|
|
3057
|
+
placeholder: 'Search...'
|
|
3058
|
+
});
|
|
3059
|
+
root.appendChild(header);
|
|
3060
|
+
root.appendChild(body);
|
|
3061
|
+
header.appendChild(headerTitle);
|
|
3062
|
+
body.appendChild(filterInput);
|
|
3063
|
+
parent.appendChild(root);
|
|
3064
|
+
const scrollBoxView = ScrollBoxView.create(body, parent);
|
|
3065
|
+
return new ToolboxView(header, body, filterInput, scrollBoxView, api);
|
|
3066
|
+
}
|
|
3067
|
+
constructor(header, body, filterInput, scrollBoxView, api) {
|
|
3068
|
+
this.header = header;
|
|
3069
|
+
this.body = body;
|
|
3070
|
+
this.filterInput = filterInput;
|
|
3071
|
+
this.scrollBoxView = scrollBoxView;
|
|
3072
|
+
this.api = api;
|
|
3073
|
+
}
|
|
3074
|
+
bindToggleIsCollapsedClick(handler) {
|
|
3075
|
+
function forward(e) {
|
|
3076
|
+
e.preventDefault();
|
|
3077
|
+
handler();
|
|
3078
|
+
}
|
|
3079
|
+
this.header.addEventListener('click', forward, false);
|
|
3080
|
+
}
|
|
3081
|
+
bindFilterInputChange(handler) {
|
|
3082
|
+
function forward(e) {
|
|
3083
|
+
handler(e.target.value);
|
|
3084
|
+
}
|
|
3085
|
+
this.filterInput.addEventListener('keyup', forward, false);
|
|
3086
|
+
this.filterInput.addEventListener('blur', forward, false);
|
|
3087
|
+
}
|
|
3088
|
+
setIsCollapsed(isCollapsed) {
|
|
3089
|
+
Dom.toggleClass(this.body, isCollapsed, 'sqd-hidden');
|
|
3090
|
+
if (this.headerToggleIcon) {
|
|
3091
|
+
this.header.removeChild(this.headerToggleIcon);
|
|
3092
|
+
}
|
|
3093
|
+
this.headerToggleIcon = Icons.createSvg('sqd-toolbox-toggle-icon', isCollapsed ? Icons.expand : Icons.close);
|
|
3094
|
+
this.header.appendChild(this.headerToggleIcon);
|
|
3095
|
+
if (!isCollapsed) {
|
|
3096
|
+
this.scrollBoxView.refresh();
|
|
3097
|
+
}
|
|
3098
|
+
}
|
|
3099
|
+
setGroups(groups) {
|
|
3100
|
+
const list = Dom.element('div');
|
|
3101
|
+
groups.forEach(group => {
|
|
3102
|
+
const groupTitle = Dom.element('div', {
|
|
3103
|
+
class: 'sqd-toolbox-group-title'
|
|
3104
|
+
});
|
|
3105
|
+
groupTitle.innerText = group.name;
|
|
3106
|
+
list.appendChild(groupTitle);
|
|
3107
|
+
group.steps.forEach(s => ToolboxItem.create(list, s, this.api));
|
|
3108
|
+
});
|
|
3109
|
+
this.scrollBoxView.setContent(list);
|
|
3110
|
+
}
|
|
3111
|
+
destroy() {
|
|
3112
|
+
this.scrollBoxView.destroy();
|
|
3113
|
+
}
|
|
3114
|
+
}
|
|
3115
|
+
|
|
3116
|
+
class Toolbox {
|
|
3117
|
+
static create(root, api, configuration) {
|
|
3118
|
+
const view = ToolboxView.create(root, api.toolbox);
|
|
3119
|
+
const toolbox = new Toolbox(view, configuration);
|
|
3120
|
+
toolbox.render();
|
|
3121
|
+
toolbox.setIsCollapsed(api.toolbox.isVisibleAtStart());
|
|
3122
|
+
view.bindToggleIsCollapsedClick(() => toolbox.toggleIsCollapsedClick());
|
|
3123
|
+
view.bindFilterInputChange(v => toolbox.onFilterInputChanged(v));
|
|
3124
|
+
return toolbox;
|
|
3125
|
+
}
|
|
3126
|
+
constructor(view, configuration) {
|
|
3127
|
+
this.view = view;
|
|
3128
|
+
this.configuration = configuration;
|
|
3129
|
+
}
|
|
3130
|
+
destroy() {
|
|
3131
|
+
this.view.destroy();
|
|
3132
|
+
}
|
|
3133
|
+
render() {
|
|
3134
|
+
const groups = this.configuration.groups
|
|
3135
|
+
.map(group => {
|
|
3136
|
+
return {
|
|
3137
|
+
name: group.name,
|
|
3138
|
+
steps: group.steps.filter(s => {
|
|
3139
|
+
return this.filter ? s.name.toLowerCase().includes(this.filter) : true;
|
|
3140
|
+
})
|
|
3141
|
+
};
|
|
3142
|
+
})
|
|
3143
|
+
.filter(group => group.steps.length > 0);
|
|
3144
|
+
this.view.setGroups(groups);
|
|
3145
|
+
}
|
|
3146
|
+
setIsCollapsed(isCollapsed) {
|
|
3147
|
+
this.isCollapsed = isCollapsed;
|
|
3148
|
+
this.view.setIsCollapsed(isCollapsed);
|
|
3149
|
+
}
|
|
3150
|
+
toggleIsCollapsedClick() {
|
|
3151
|
+
this.setIsCollapsed(!this.isCollapsed);
|
|
3152
|
+
}
|
|
3153
|
+
onFilterInputChanged(value) {
|
|
3154
|
+
this.filter = value.toLowerCase();
|
|
3155
|
+
this.render();
|
|
3156
|
+
}
|
|
3157
|
+
}
|
|
3158
|
+
|
|
3159
|
+
class ToolboxExtension {
|
|
3160
|
+
constructor(configuration) {
|
|
3161
|
+
this.configuration = configuration;
|
|
3162
|
+
}
|
|
3163
|
+
create(root, api) {
|
|
3164
|
+
return Toolbox.create(root, api, this.configuration);
|
|
3165
|
+
}
|
|
3166
|
+
}
|
|
3167
|
+
|
|
3168
|
+
const SIZE$1 = 22;
|
|
3169
|
+
const ICON_SIZE = 12;
|
|
3170
|
+
class ValidationErrorBadgeView {
|
|
3171
|
+
static create(parent) {
|
|
3172
|
+
const g = Dom.svg('g');
|
|
3173
|
+
const halfOfSize = SIZE$1 / 2;
|
|
3174
|
+
const circle = Dom.svg('path', {
|
|
3175
|
+
class: 'sqd-validation-error',
|
|
3176
|
+
d: `M 0 ${-halfOfSize} l ${halfOfSize} ${SIZE$1} l ${-SIZE$1} 0 Z`
|
|
3177
|
+
});
|
|
3178
|
+
Dom.translate(circle, halfOfSize, halfOfSize);
|
|
3179
|
+
g.appendChild(circle);
|
|
3180
|
+
const icon = Icons.appendPath(g, 'sqd-validation-error-icon-path', Icons.alert, ICON_SIZE);
|
|
3181
|
+
const offsetX = (SIZE$1 - ICON_SIZE) * 0.5;
|
|
3182
|
+
const offsetY = offsetX * 1.5; // 0.5 * 1.5 = 0.75
|
|
3183
|
+
Dom.translate(icon, offsetX, offsetY);
|
|
3184
|
+
parent.appendChild(g);
|
|
3185
|
+
return new ValidationErrorBadgeView(parent, g, SIZE$1, SIZE$1);
|
|
3186
|
+
}
|
|
3187
|
+
constructor(parent, g, width, height) {
|
|
3188
|
+
this.parent = parent;
|
|
3189
|
+
this.g = g;
|
|
3190
|
+
this.width = width;
|
|
3191
|
+
this.height = height;
|
|
3192
|
+
}
|
|
3193
|
+
destroy() {
|
|
3194
|
+
this.parent.removeChild(this.g);
|
|
3195
|
+
}
|
|
3196
|
+
}
|
|
3197
|
+
|
|
3198
|
+
class ValidationErrorBadge {
|
|
3199
|
+
static create(parentElement, stepContext, componentContext) {
|
|
3200
|
+
return new ValidationErrorBadge(parentElement, stepContext, componentContext.configuration.validator);
|
|
3201
|
+
}
|
|
3202
|
+
constructor(parentElement, stepContext, validator) {
|
|
3203
|
+
this.parentElement = parentElement;
|
|
3204
|
+
this.stepContext = stepContext;
|
|
3205
|
+
this.validator = validator;
|
|
3206
|
+
this.view = null;
|
|
3207
|
+
}
|
|
3208
|
+
update(result) {
|
|
3209
|
+
const isValid = this.validator ? this.validator(this.stepContext.step, this.stepContext.parentSequence) : true;
|
|
3210
|
+
if (isValid) {
|
|
3211
|
+
if (this.view) {
|
|
3212
|
+
this.view.destroy();
|
|
3213
|
+
this.view = null;
|
|
3214
|
+
}
|
|
3215
|
+
}
|
|
3216
|
+
else if (!this.view) {
|
|
3217
|
+
this.view = ValidationErrorBadgeView.create(this.parentElement);
|
|
3218
|
+
}
|
|
3219
|
+
return isValid && result;
|
|
3220
|
+
}
|
|
3221
|
+
resolveClick() {
|
|
3222
|
+
return null;
|
|
3223
|
+
}
|
|
3224
|
+
}
|
|
3225
|
+
|
|
3226
|
+
class ValidationErrorBadgeExtension {
|
|
3227
|
+
constructor() {
|
|
3228
|
+
this.createBadge = ValidationErrorBadge.create;
|
|
3229
|
+
this.createStartValue = () => true;
|
|
3230
|
+
}
|
|
3231
|
+
}
|
|
3232
|
+
|
|
3233
|
+
const PADDING_TOP$1 = 20;
|
|
3234
|
+
const PADDING_X$1 = 20;
|
|
3235
|
+
class ContainerStepComponentView {
|
|
3236
|
+
static create(parentElement, stepContext, componentContext) {
|
|
3237
|
+
const { step } = stepContext;
|
|
3238
|
+
const g = Dom.svg('g', {
|
|
3239
|
+
class: `sqd-step-container sqd-type-${step.type}`
|
|
3240
|
+
});
|
|
3241
|
+
parentElement.appendChild(g);
|
|
3242
|
+
const labelView = LabelView.create(g, PADDING_TOP$1, step.name, 'primary');
|
|
3243
|
+
const sequenceContext = {
|
|
3244
|
+
sequence: step.sequence,
|
|
3245
|
+
depth: stepContext.depth + 1,
|
|
3246
|
+
isInputConnected: true,
|
|
3247
|
+
isOutputConnected: stepContext.isOutputConnected
|
|
3248
|
+
};
|
|
3249
|
+
const component = SequenceComponent.create(g, sequenceContext, componentContext);
|
|
3250
|
+
const halfOfWidestElement = labelView.width / 2;
|
|
3251
|
+
const offsetLeft = Math.max(halfOfWidestElement - component.view.joinX, 0) + PADDING_X$1;
|
|
3252
|
+
const offsetRight = Math.max(halfOfWidestElement - (component.view.width - component.view.joinX), 0) + PADDING_X$1;
|
|
3253
|
+
const viewWidth = offsetLeft + component.view.width + offsetRight;
|
|
3254
|
+
const viewHeight = PADDING_TOP$1 + LABEL_HEIGHT + component.view.height;
|
|
3255
|
+
const joinX = component.view.joinX + offsetLeft;
|
|
3256
|
+
Dom.translate(labelView.g, joinX, 0);
|
|
3257
|
+
Dom.translate(component.view.g, offsetLeft, PADDING_TOP$1 + LABEL_HEIGHT);
|
|
3258
|
+
const iconUrl = componentContext.configuration.iconUrlProvider
|
|
3259
|
+
? componentContext.configuration.iconUrlProvider(step.componentType, step.type)
|
|
3260
|
+
: null;
|
|
3261
|
+
const inputView = InputView.createRectInput(g, joinX, 0, iconUrl);
|
|
3262
|
+
JoinView.createStraightJoin(g, new Vector(joinX, 0), PADDING_TOP$1);
|
|
3263
|
+
const regionView = RegionView.create(g, [viewWidth], viewHeight);
|
|
3264
|
+
return new ContainerStepComponentView(g, viewWidth, viewHeight, joinX, component, inputView, regionView);
|
|
3265
|
+
}
|
|
3266
|
+
constructor(g, width, height, joinX, sequenceComponent, inputView, regionView) {
|
|
3267
|
+
this.g = g;
|
|
3268
|
+
this.width = width;
|
|
3269
|
+
this.height = height;
|
|
3270
|
+
this.joinX = joinX;
|
|
3271
|
+
this.sequenceComponent = sequenceComponent;
|
|
3272
|
+
this.inputView = inputView;
|
|
3273
|
+
this.regionView = regionView;
|
|
3274
|
+
this.sequenceComponents = [this.sequenceComponent];
|
|
3275
|
+
this.placeholders = null;
|
|
3276
|
+
}
|
|
3277
|
+
getClientPosition() {
|
|
3278
|
+
return this.regionView.getClientPosition();
|
|
3279
|
+
}
|
|
3280
|
+
resolveClick(click) {
|
|
3281
|
+
if (this.regionView.resolveClick(click) || this.g.contains(click.element)) {
|
|
3282
|
+
return {
|
|
3283
|
+
type: ClickCommandType.selectStep
|
|
3284
|
+
};
|
|
3285
|
+
}
|
|
3286
|
+
return null;
|
|
3287
|
+
}
|
|
3288
|
+
setIsDragging(isDragging) {
|
|
3289
|
+
this.inputView.setIsHidden(isDragging);
|
|
3290
|
+
this.sequenceComponent.setIsDragging(isDragging);
|
|
3291
|
+
}
|
|
3292
|
+
setIsSelected(isSelected) {
|
|
3293
|
+
this.regionView.setIsSelected(isSelected);
|
|
3294
|
+
}
|
|
3295
|
+
setIsDisabled(isDisabled) {
|
|
3296
|
+
Dom.toggleClass(this.g, isDisabled, 'sqd-disabled');
|
|
3297
|
+
}
|
|
3298
|
+
hasOutput() {
|
|
3299
|
+
return this.sequenceComponent.hasOutput;
|
|
3300
|
+
}
|
|
3301
|
+
}
|
|
3302
|
+
|
|
3303
|
+
class ContainerStepExtension {
|
|
3304
|
+
constructor() {
|
|
3305
|
+
this.componentType = 'container';
|
|
3306
|
+
this.createComponentView = ContainerStepComponentView.create;
|
|
3307
|
+
}
|
|
3308
|
+
getChildren(step) {
|
|
3309
|
+
return {
|
|
3310
|
+
type: StepChildrenType.singleSequence,
|
|
3311
|
+
sequences: step.sequence
|
|
3312
|
+
};
|
|
3313
|
+
}
|
|
3314
|
+
}
|
|
3315
|
+
|
|
3316
|
+
class DefaultPlaceholderControllerExtension {
|
|
3317
|
+
create() {
|
|
3318
|
+
return {
|
|
3319
|
+
canCreate: () => true
|
|
3320
|
+
};
|
|
3321
|
+
}
|
|
3322
|
+
}
|
|
3323
|
+
|
|
3324
|
+
const SIZE = 30;
|
|
3325
|
+
const DEFAULT_ICON_SIZE = 22;
|
|
3326
|
+
const FOLDER_ICON_SIZE = 18;
|
|
3327
|
+
class StartStopRootComponentView {
|
|
3328
|
+
static create(parent, sequence, isInsideFolder, context) {
|
|
3329
|
+
const g = Dom.svg('g', {
|
|
3330
|
+
class: 'sqd-root-start-stop'
|
|
3331
|
+
});
|
|
3332
|
+
parent.appendChild(g);
|
|
3333
|
+
const sequenceComponent = SequenceComponent.create(g, {
|
|
3334
|
+
sequence,
|
|
3335
|
+
depth: 0,
|
|
3336
|
+
isInputConnected: true,
|
|
3337
|
+
isOutputConnected: true
|
|
3338
|
+
}, context);
|
|
3339
|
+
const view = sequenceComponent.view;
|
|
3340
|
+
const x = view.joinX - SIZE / 2;
|
|
3341
|
+
const endY = SIZE + view.height;
|
|
3342
|
+
const iconSize = isInsideFolder ? FOLDER_ICON_SIZE : DEFAULT_ICON_SIZE;
|
|
3343
|
+
const startCircle = createCircle(isInsideFolder ? Icons.folder : Icons.play, iconSize);
|
|
3344
|
+
Dom.translate(startCircle, x, 0);
|
|
3345
|
+
g.appendChild(startCircle);
|
|
3346
|
+
Dom.translate(view.g, 0, SIZE);
|
|
3347
|
+
const endCircle = createCircle(isInsideFolder ? Icons.folder : Icons.stop, iconSize);
|
|
3348
|
+
Dom.translate(endCircle, x, endY);
|
|
3349
|
+
g.appendChild(endCircle);
|
|
3350
|
+
let startPlaceholderView = null;
|
|
3351
|
+
let endPlaceholderView = null;
|
|
3352
|
+
if (isInsideFolder) {
|
|
3353
|
+
startPlaceholderView = RectPlaceholderView.create(g, x, 0, SIZE, SIZE, RectPlaceholderDirection.out);
|
|
3354
|
+
endPlaceholderView = RectPlaceholderView.create(g, x, endY, SIZE, SIZE, RectPlaceholderDirection.out);
|
|
3355
|
+
}
|
|
3356
|
+
return new StartStopRootComponentView(g, view.width, view.height + SIZE * 2, view.joinX, sequenceComponent, startPlaceholderView, endPlaceholderView);
|
|
3357
|
+
}
|
|
3358
|
+
constructor(g, width, height, joinX, component, startPlaceholderView, endPlaceholderView) {
|
|
3359
|
+
this.g = g;
|
|
3360
|
+
this.width = width;
|
|
3361
|
+
this.height = height;
|
|
3362
|
+
this.joinX = joinX;
|
|
3363
|
+
this.component = component;
|
|
3364
|
+
this.startPlaceholderView = startPlaceholderView;
|
|
3365
|
+
this.endPlaceholderView = endPlaceholderView;
|
|
3366
|
+
}
|
|
3367
|
+
destroy() {
|
|
3368
|
+
var _a;
|
|
3369
|
+
(_a = this.g.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(this.g);
|
|
3370
|
+
}
|
|
3371
|
+
}
|
|
3372
|
+
function createCircle(d, iconSize) {
|
|
3373
|
+
const r = SIZE / 2;
|
|
3374
|
+
const circle = Dom.svg('circle', {
|
|
3375
|
+
class: 'sqd-root-start-stop-circle',
|
|
3376
|
+
cx: r,
|
|
3377
|
+
cy: r,
|
|
3378
|
+
r: r
|
|
3379
|
+
});
|
|
3380
|
+
const g = Dom.svg('g');
|
|
3381
|
+
g.appendChild(circle);
|
|
3382
|
+
const offset = (SIZE - iconSize) / 2;
|
|
3383
|
+
const icon = Icons.appendPath(g, 'sqd-root-start-stop-icon', d, iconSize);
|
|
3384
|
+
Dom.translate(icon, offset, offset);
|
|
3385
|
+
return g;
|
|
3386
|
+
}
|
|
3387
|
+
|
|
3388
|
+
class StartStopRootComponent {
|
|
3389
|
+
static create(parentElement, sequence, parentSequencePlaceIndicator, context) {
|
|
3390
|
+
const view = StartStopRootComponentView.create(parentElement, sequence, !!parentSequencePlaceIndicator, context);
|
|
3391
|
+
return new StartStopRootComponent(view, parentSequencePlaceIndicator);
|
|
3392
|
+
}
|
|
3393
|
+
constructor(view, parentSequencePlaceIndicator) {
|
|
3394
|
+
this.view = view;
|
|
3395
|
+
this.parentSequencePlaceIndicator = parentSequencePlaceIndicator;
|
|
3396
|
+
}
|
|
3397
|
+
resolveClick(click) {
|
|
3398
|
+
return this.view.component.resolveClick(click);
|
|
3399
|
+
}
|
|
3400
|
+
findById(stepId) {
|
|
3401
|
+
return this.view.component.findById(stepId);
|
|
3402
|
+
}
|
|
3403
|
+
getPlaceholders(result) {
|
|
3404
|
+
this.view.component.getPlaceholders(result);
|
|
3405
|
+
if (this.parentSequencePlaceIndicator && this.view.startPlaceholderView && this.view.endPlaceholderView) {
|
|
3406
|
+
const { index, sequence } = this.parentSequencePlaceIndicator;
|
|
3407
|
+
result.push(new RectPlaceholder(this.view.startPlaceholderView, sequence, index));
|
|
3408
|
+
result.push(new RectPlaceholder(this.view.endPlaceholderView, sequence, index + 1));
|
|
3409
|
+
}
|
|
3410
|
+
}
|
|
3411
|
+
setIsDragging(isDragging) {
|
|
3412
|
+
this.view.component.setIsDragging(isDragging);
|
|
3413
|
+
if (this.view.startPlaceholderView && this.view.endPlaceholderView) {
|
|
3414
|
+
this.view.startPlaceholderView.setIsVisible(isDragging);
|
|
3415
|
+
this.view.endPlaceholderView.setIsVisible(isDragging);
|
|
3416
|
+
}
|
|
3417
|
+
}
|
|
3418
|
+
updateBadges(result) {
|
|
3419
|
+
this.view.component.updateBadges(result);
|
|
3420
|
+
}
|
|
3421
|
+
}
|
|
3422
|
+
|
|
3423
|
+
class StartStopRootComponentExtension {
|
|
3424
|
+
constructor() {
|
|
3425
|
+
this.create = StartStopRootComponent.create;
|
|
3426
|
+
}
|
|
3427
|
+
}
|
|
3428
|
+
|
|
3429
|
+
const MIN_CONTAINER_WIDTH = 40;
|
|
3430
|
+
const PADDING_X = 20;
|
|
3431
|
+
const PADDING_TOP = 20;
|
|
3432
|
+
const CONNECTION_HEIGHT = 16;
|
|
3433
|
+
class SwitchStepComponentView {
|
|
3434
|
+
static create(parent, stepContext, context) {
|
|
3435
|
+
const { step } = stepContext;
|
|
3436
|
+
const g = Dom.svg('g', {
|
|
3437
|
+
class: `sqd-step-switch sqd-type-${step.type}`
|
|
3438
|
+
});
|
|
3439
|
+
parent.appendChild(g);
|
|
3440
|
+
const branchNames = Object.keys(step.branches);
|
|
3441
|
+
const branchComponents = branchNames.map(branchName => {
|
|
3442
|
+
const sequenceContext = {
|
|
3443
|
+
sequence: step.branches[branchName],
|
|
3444
|
+
depth: stepContext.depth + 1,
|
|
3445
|
+
isInputConnected: true,
|
|
3446
|
+
isOutputConnected: stepContext.isOutputConnected
|
|
3447
|
+
};
|
|
3448
|
+
return SequenceComponent.create(g, sequenceContext, context);
|
|
3449
|
+
});
|
|
3450
|
+
const branchLabelViews = branchNames.map(branchName => {
|
|
3451
|
+
return LabelView.create(g, PADDING_TOP + LABEL_HEIGHT + CONNECTION_HEIGHT, branchName, 'secondary');
|
|
3452
|
+
});
|
|
3453
|
+
const nameLabelView = LabelView.create(g, PADDING_TOP, step.name, 'primary');
|
|
3454
|
+
let prevOffsetX = 0;
|
|
3455
|
+
const branchSizes = branchComponents.map((component, i) => {
|
|
3456
|
+
const halfOfWidestBranchElement = Math.max(branchLabelViews[i].width, MIN_CONTAINER_WIDTH) / 2;
|
|
3457
|
+
const branchOffsetLeft = Math.max(halfOfWidestBranchElement - component.view.joinX, 0) + PADDING_X;
|
|
3458
|
+
const branchOffsetRight = Math.max(halfOfWidestBranchElement - (component.view.width - component.view.joinX), 0) + PADDING_X;
|
|
3459
|
+
const width = component.view.width + branchOffsetLeft + branchOffsetRight;
|
|
3460
|
+
const joinX = component.view.joinX + branchOffsetLeft;
|
|
3461
|
+
const offsetX = prevOffsetX;
|
|
3462
|
+
prevOffsetX += width;
|
|
3463
|
+
return { width, branchOffsetLeft, offsetX, joinX };
|
|
3464
|
+
});
|
|
3465
|
+
const centerBranchIndex = Math.floor(branchNames.length / 2);
|
|
3466
|
+
const centerBranchSize = branchSizes[centerBranchIndex];
|
|
3467
|
+
let joinX = centerBranchSize.offsetX;
|
|
3468
|
+
if (branchNames.length % 2 !== 0) {
|
|
3469
|
+
joinX += centerBranchSize.joinX;
|
|
3470
|
+
}
|
|
3471
|
+
const totalBranchesWidth = branchSizes.reduce((result, s) => result + s.width, 0);
|
|
3472
|
+
const maxBranchesHeight = Math.max(...branchComponents.map(s => s.view.height));
|
|
3473
|
+
const halfOfWidestSwitchElement = nameLabelView.width / 2 + PADDING_X;
|
|
3474
|
+
const switchOffsetLeft = Math.max(halfOfWidestSwitchElement - joinX, 0);
|
|
3475
|
+
const switchOffsetRight = Math.max(halfOfWidestSwitchElement - (totalBranchesWidth - joinX), 0);
|
|
3476
|
+
const viewWidth = switchOffsetLeft + totalBranchesWidth + switchOffsetRight;
|
|
3477
|
+
const viewHeight = maxBranchesHeight + PADDING_TOP + LABEL_HEIGHT * 2 + CONNECTION_HEIGHT * 2;
|
|
3478
|
+
const shiftedJoinX = switchOffsetLeft + joinX;
|
|
3479
|
+
Dom.translate(nameLabelView.g, shiftedJoinX, 0);
|
|
3480
|
+
const branchOffsetTop = PADDING_TOP + LABEL_HEIGHT * 2 + CONNECTION_HEIGHT;
|
|
3481
|
+
branchComponents.forEach((component, i) => {
|
|
3482
|
+
const branchSize = branchSizes[i];
|
|
3483
|
+
const branchOffsetLeft = switchOffsetLeft + branchSize.offsetX + branchSize.branchOffsetLeft;
|
|
3484
|
+
Dom.translate(branchLabelViews[i].g, switchOffsetLeft + branchSize.offsetX + branchSize.joinX, 0);
|
|
3485
|
+
Dom.translate(component.view.g, branchOffsetLeft, branchOffsetTop);
|
|
3486
|
+
if (component.hasOutput && stepContext.isOutputConnected) {
|
|
3487
|
+
const endOffsetTopOfComponent = PADDING_TOP + LABEL_HEIGHT * 2 + CONNECTION_HEIGHT + component.view.height;
|
|
3488
|
+
const missingHeight = viewHeight - endOffsetTopOfComponent - CONNECTION_HEIGHT;
|
|
3489
|
+
if (missingHeight > 0) {
|
|
3490
|
+
JoinView.createStraightJoin(g, new Vector(switchOffsetLeft + branchSize.offsetX + branchSize.joinX, endOffsetTopOfComponent), missingHeight);
|
|
3491
|
+
}
|
|
3492
|
+
}
|
|
3493
|
+
});
|
|
3494
|
+
const iconUrl = context.configuration.iconUrlProvider ? context.configuration.iconUrlProvider(step.componentType, step.type) : null;
|
|
3495
|
+
const inputView = InputView.createRectInput(g, shiftedJoinX, 0, iconUrl);
|
|
3496
|
+
JoinView.createStraightJoin(g, new Vector(shiftedJoinX, 0), PADDING_TOP);
|
|
3497
|
+
JoinView.createJoins(g, new Vector(shiftedJoinX, PADDING_TOP + LABEL_HEIGHT), branchSizes.map(o => new Vector(switchOffsetLeft + o.offsetX + o.joinX, PADDING_TOP + LABEL_HEIGHT + CONNECTION_HEIGHT)));
|
|
3498
|
+
if (stepContext.isOutputConnected) {
|
|
3499
|
+
const ongoingSequenceIndexes = branchComponents
|
|
3500
|
+
.map((component, index) => (component.hasOutput ? index : null))
|
|
3501
|
+
.filter(index => index !== null);
|
|
3502
|
+
const ongoingJoinTargets = ongoingSequenceIndexes.map((i) => new Vector(switchOffsetLeft + branchSizes[i].offsetX + branchSizes[i].joinX, PADDING_TOP + CONNECTION_HEIGHT + LABEL_HEIGHT * 2 + maxBranchesHeight));
|
|
3503
|
+
if (ongoingJoinTargets.length > 0) {
|
|
3504
|
+
JoinView.createJoins(g, new Vector(shiftedJoinX, viewHeight), ongoingJoinTargets);
|
|
3505
|
+
}
|
|
3506
|
+
}
|
|
3507
|
+
const regions = branchSizes.map(s => s.width);
|
|
3508
|
+
regions[0] += switchOffsetLeft;
|
|
3509
|
+
regions[regions.length - 1] += switchOffsetRight;
|
|
3510
|
+
const regionView = RegionView.create(g, regions, viewHeight);
|
|
3511
|
+
return new SwitchStepComponentView(g, viewWidth, viewHeight, shiftedJoinX, branchComponents, regionView, inputView);
|
|
3512
|
+
}
|
|
3513
|
+
constructor(g, width, height, joinX, sequenceComponents, regionView, inputView) {
|
|
3514
|
+
this.g = g;
|
|
3515
|
+
this.width = width;
|
|
3516
|
+
this.height = height;
|
|
3517
|
+
this.joinX = joinX;
|
|
3518
|
+
this.sequenceComponents = sequenceComponents;
|
|
3519
|
+
this.regionView = regionView;
|
|
3520
|
+
this.inputView = inputView;
|
|
3521
|
+
this.placeholders = null;
|
|
3522
|
+
}
|
|
3523
|
+
getClientPosition() {
|
|
3524
|
+
return this.regionView.getClientPosition();
|
|
3525
|
+
}
|
|
3526
|
+
resolveClick(click) {
|
|
3527
|
+
if (this.regionView.resolveClick(click) || this.g.contains(click.element)) {
|
|
3528
|
+
return {
|
|
3529
|
+
type: ClickCommandType.selectStep
|
|
3530
|
+
};
|
|
3531
|
+
}
|
|
3532
|
+
return null;
|
|
3533
|
+
}
|
|
3534
|
+
setIsDragging(isDragging) {
|
|
3535
|
+
var _a;
|
|
3536
|
+
(_a = this.inputView) === null || _a === void 0 ? void 0 : _a.setIsHidden(isDragging);
|
|
3537
|
+
}
|
|
3538
|
+
setIsSelected(isSelected) {
|
|
3539
|
+
this.regionView.setIsSelected(isSelected);
|
|
3540
|
+
}
|
|
3541
|
+
setIsDisabled(isDisabled) {
|
|
3542
|
+
Dom.toggleClass(this.g, isDisabled, 'sqd-disabled');
|
|
3543
|
+
}
|
|
3544
|
+
hasOutput() {
|
|
3545
|
+
return this.sequenceComponents.some(c => c.hasOutput);
|
|
3546
|
+
}
|
|
3547
|
+
}
|
|
3548
|
+
|
|
3549
|
+
class SwitchStepExtension {
|
|
3550
|
+
constructor() {
|
|
3551
|
+
this.componentType = 'switch';
|
|
3552
|
+
this.createComponentView = SwitchStepComponentView.create;
|
|
3553
|
+
}
|
|
3554
|
+
getChildren(step) {
|
|
3555
|
+
return {
|
|
3556
|
+
type: StepChildrenType.branches,
|
|
3557
|
+
sequences: step.branches
|
|
3558
|
+
};
|
|
3559
|
+
}
|
|
3560
|
+
}
|
|
3561
|
+
|
|
3562
|
+
class TaskStepExtension {
|
|
3563
|
+
constructor() {
|
|
3564
|
+
this.componentType = 'task';
|
|
3565
|
+
}
|
|
3566
|
+
createComponentView(parentElement, stepContext, componentContext) {
|
|
3567
|
+
return TaskStepComponentView.create(parentElement, stepContext, componentContext.configuration, false);
|
|
3568
|
+
}
|
|
3569
|
+
getChildren() {
|
|
3570
|
+
return null;
|
|
3571
|
+
}
|
|
3572
|
+
}
|
|
3573
|
+
|
|
3574
|
+
class ServicesResolver {
|
|
3575
|
+
static resolve(extensions, configuration) {
|
|
3576
|
+
const services = {};
|
|
3577
|
+
merge(services, extensions || []);
|
|
3578
|
+
setDefault(services, configuration);
|
|
3579
|
+
return services;
|
|
3580
|
+
}
|
|
3581
|
+
}
|
|
3582
|
+
function merge(services, extensions) {
|
|
3583
|
+
for (const ext of extensions) {
|
|
3584
|
+
if (ext.steps) {
|
|
3585
|
+
services.steps = (services.steps || []).concat(ext.steps);
|
|
3586
|
+
}
|
|
3587
|
+
if (ext.badges) {
|
|
3588
|
+
services.badges = (services.badges || []).concat(ext.badges);
|
|
3589
|
+
}
|
|
3590
|
+
if (ext.uiComponents) {
|
|
3591
|
+
services.uiComponents = (services.uiComponents || []).concat(ext.uiComponents);
|
|
3592
|
+
}
|
|
3593
|
+
if (ext.draggedComponent) {
|
|
3594
|
+
services.draggedComponent = ext.draggedComponent;
|
|
3595
|
+
}
|
|
3596
|
+
if (ext.wheelController) {
|
|
3597
|
+
services.wheelController = ext.wheelController;
|
|
3598
|
+
}
|
|
3599
|
+
if (ext.placeholderController) {
|
|
3600
|
+
services.placeholderController = ext.placeholderController;
|
|
3601
|
+
}
|
|
3602
|
+
if (ext.viewportController) {
|
|
3603
|
+
services.viewportController = ext.viewportController;
|
|
3604
|
+
}
|
|
3605
|
+
if (ext.rootComponent) {
|
|
3606
|
+
services.rootComponent = ext.rootComponent;
|
|
3607
|
+
}
|
|
3608
|
+
if (ext.daemons) {
|
|
3609
|
+
services.daemons = (services.daemons || []).concat(ext.daemons);
|
|
3610
|
+
}
|
|
3611
|
+
}
|
|
3612
|
+
}
|
|
3613
|
+
function setDefault(services, configuration) {
|
|
3614
|
+
if (!services.steps) {
|
|
3615
|
+
services.steps = [];
|
|
3616
|
+
}
|
|
3617
|
+
services.steps.push(new ContainerStepExtension());
|
|
3618
|
+
services.steps.push(new SwitchStepExtension());
|
|
3619
|
+
services.steps.push(new TaskStepExtension());
|
|
3620
|
+
if (!services.badges) {
|
|
3621
|
+
services.badges = [];
|
|
3622
|
+
}
|
|
3623
|
+
// TODO: this is a weak assumption
|
|
3624
|
+
services.badges.unshift(new ValidationErrorBadgeExtension());
|
|
3625
|
+
if (!services.draggedComponent) {
|
|
3626
|
+
services.draggedComponent = new DefaultDraggedComponentExtension();
|
|
3627
|
+
}
|
|
3628
|
+
if (!services.uiComponents) {
|
|
3629
|
+
services.uiComponents = [];
|
|
3630
|
+
}
|
|
3631
|
+
if (configuration.controlBar) {
|
|
3632
|
+
services.uiComponents.push(new ControlBarExtension());
|
|
3633
|
+
}
|
|
3634
|
+
if (configuration.editors) {
|
|
3635
|
+
services.uiComponents.push(new SmartEditorExtension(configuration.editors));
|
|
3636
|
+
}
|
|
3637
|
+
if (configuration.toolbox) {
|
|
3638
|
+
services.uiComponents.push(new ToolboxExtension(configuration.toolbox));
|
|
3639
|
+
}
|
|
3640
|
+
if (!services.wheelController) {
|
|
3641
|
+
services.wheelController = new ClassicWheelControllerExtension();
|
|
3642
|
+
}
|
|
3643
|
+
if (!services.placeholderController) {
|
|
3644
|
+
services.placeholderController = new DefaultPlaceholderControllerExtension();
|
|
3645
|
+
}
|
|
3646
|
+
if (!services.viewportController) {
|
|
3647
|
+
services.viewportController = new DefaultViewportControllerExtension();
|
|
3648
|
+
}
|
|
3649
|
+
if (!services.rootComponent) {
|
|
3650
|
+
services.rootComponent = new StartStopRootComponentExtension();
|
|
3651
|
+
}
|
|
3652
|
+
if (!services.daemons) {
|
|
3653
|
+
services.daemons = [];
|
|
3654
|
+
}
|
|
3655
|
+
services.daemons.push(new KeyboardDaemonExtension());
|
|
3656
|
+
}
|
|
3657
|
+
|
|
3658
|
+
function validateConfiguration(configuration) {
|
|
3659
|
+
if (configuration.controlBar === undefined) {
|
|
3660
|
+
throw new Error('The "controlBar" property is not defined in the configuration');
|
|
3661
|
+
}
|
|
3662
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3663
|
+
if (configuration.toolbox && configuration.toolbox.isHidden !== undefined) {
|
|
3664
|
+
throw new Error('The "isHidden" property in the toolbox configuration is depreciated');
|
|
3665
|
+
}
|
|
3666
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3667
|
+
if (configuration.editors && configuration.editors.isHidden !== undefined) {
|
|
3668
|
+
throw new Error('The "isHidden" property in the editors configuration is depreciated');
|
|
3669
|
+
}
|
|
3670
|
+
}
|
|
3671
|
+
|
|
3672
|
+
class Designer {
|
|
3673
|
+
/**
|
|
3674
|
+
* Creates a designer.
|
|
3675
|
+
* @param placeholder Placeholder where the designer will be attached.
|
|
3676
|
+
* @param startDefinition Start definition of a flow.
|
|
3677
|
+
* @param configuration Designer's configuration.
|
|
3678
|
+
* @returns An instance of the designer.
|
|
3679
|
+
*/
|
|
3680
|
+
static create(placeholder, startDefinition, configuration) {
|
|
3681
|
+
if (!placeholder) {
|
|
3682
|
+
throw new Error('Placeholder is not set');
|
|
3683
|
+
}
|
|
3684
|
+
if (!isElementAttached(placeholder)) {
|
|
3685
|
+
throw new Error('Placeholder is not attached to the DOM');
|
|
3686
|
+
}
|
|
3687
|
+
if (!startDefinition) {
|
|
3688
|
+
throw new Error('Start definition is not set');
|
|
3689
|
+
}
|
|
3690
|
+
if (!configuration) {
|
|
3691
|
+
throw new Error('Configuration is not set');
|
|
3692
|
+
}
|
|
3693
|
+
const config = configuration;
|
|
3694
|
+
validateConfiguration(config);
|
|
3695
|
+
const services = ServicesResolver.resolve(configuration.extensions, config);
|
|
3696
|
+
const designerContext = DesignerContext.create(placeholder, startDefinition, config, services);
|
|
3697
|
+
const designerApi = DesignerApi.create(designerContext);
|
|
3698
|
+
const view = DesignerView.create(placeholder, designerContext, config, designerApi);
|
|
3699
|
+
const designer = new Designer(view, designerContext.state, designerContext.stepsTraverser, designerApi);
|
|
3700
|
+
view.workspace.onReady.subscribe(() => designer.onReady.forward());
|
|
3701
|
+
designerContext.state.onDefinitionChanged.subscribe(() => {
|
|
3702
|
+
setTimeout(() => designer.onDefinitionChanged.forward(designerContext.state.definition));
|
|
3703
|
+
});
|
|
3704
|
+
designerContext.state.onSelectedStepIdChanged.subscribe(() => designer.onSelectedStepIdChanged.forward(designerContext.state.selectedStepId));
|
|
3705
|
+
return designer;
|
|
3706
|
+
}
|
|
3707
|
+
constructor(view, state, stepsTraverser, api) {
|
|
3708
|
+
this.view = view;
|
|
3709
|
+
this.state = state;
|
|
3710
|
+
this.stepsTraverser = stepsTraverser;
|
|
3711
|
+
this.api = api;
|
|
3712
|
+
/**
|
|
3713
|
+
* @description Fires when the designer is initialized and ready to use.
|
|
3714
|
+
*/
|
|
3715
|
+
this.onReady = new SimpleEvent();
|
|
3716
|
+
/**
|
|
3717
|
+
* @description Fires when the definition has changed.
|
|
3718
|
+
*/
|
|
3719
|
+
this.onDefinitionChanged = new SimpleEvent();
|
|
3720
|
+
/**
|
|
3721
|
+
* @description Fires when the selected step has changed.
|
|
3722
|
+
*/
|
|
3723
|
+
this.onSelectedStepIdChanged = new SimpleEvent();
|
|
3724
|
+
}
|
|
3725
|
+
/**
|
|
3726
|
+
* @returns the current definition of the workflow.
|
|
3727
|
+
*/
|
|
3728
|
+
getDefinition() {
|
|
3729
|
+
return this.state.definition;
|
|
3730
|
+
}
|
|
3731
|
+
/**
|
|
3732
|
+
* @returns the validation result of the current definition.
|
|
3733
|
+
*/
|
|
3734
|
+
isValid() {
|
|
3735
|
+
return this.view.workspace.isValid;
|
|
3736
|
+
}
|
|
3737
|
+
/**
|
|
3738
|
+
* @returns the readonly flag.
|
|
3739
|
+
*/
|
|
3740
|
+
isReadonly() {
|
|
3741
|
+
return this.state.isReadonly;
|
|
3742
|
+
}
|
|
3743
|
+
/**
|
|
3744
|
+
* @description Changes the readonly flag.
|
|
3745
|
+
*/
|
|
3746
|
+
setIsReadonly(isReadonly) {
|
|
3747
|
+
this.state.setIsReadonly(isReadonly);
|
|
3748
|
+
}
|
|
3749
|
+
/**
|
|
3750
|
+
* @returns current selected step id or `null` if nothing is selected.
|
|
3751
|
+
*/
|
|
3752
|
+
getSelectedStepId() {
|
|
3753
|
+
return this.state.selectedStepId;
|
|
3754
|
+
}
|
|
3755
|
+
/**
|
|
3756
|
+
* @description Selects a step by the id.
|
|
3757
|
+
*/
|
|
3758
|
+
selectStepById(stepId) {
|
|
3759
|
+
this.state.setSelectedStepId(stepId);
|
|
3760
|
+
}
|
|
3761
|
+
/**
|
|
3762
|
+
* @description Unselects the selected step.
|
|
3763
|
+
*/
|
|
3764
|
+
clearSelectedStep() {
|
|
3765
|
+
this.state.setSelectedStepId(null);
|
|
3766
|
+
}
|
|
3767
|
+
/**
|
|
3768
|
+
* @description Moves the viewport to the step with the animation.
|
|
3769
|
+
*/
|
|
3770
|
+
moveViewportToStep(stepId) {
|
|
3771
|
+
this.api.viewport.moveViewportToStep(stepId);
|
|
3772
|
+
}
|
|
3773
|
+
/**
|
|
3774
|
+
* @deprecated Use `moveViewportToStep` instead.
|
|
3775
|
+
*/
|
|
3776
|
+
moveViewPortToStep(stepId) {
|
|
3777
|
+
this.moveViewportToStep(stepId);
|
|
3778
|
+
}
|
|
3779
|
+
/**
|
|
3780
|
+
* @description Updates all badges.
|
|
3781
|
+
*/
|
|
3782
|
+
updateBadges() {
|
|
3783
|
+
this.api.workspace.updateBadges();
|
|
3784
|
+
}
|
|
3785
|
+
/**
|
|
3786
|
+
* @returns parent steps and branch names of the passed step or the passed sequence.
|
|
3787
|
+
*/
|
|
3788
|
+
getStepParents(needle) {
|
|
3789
|
+
return this.stepsTraverser.getParents(this.state.definition, needle);
|
|
3790
|
+
}
|
|
3791
|
+
/**
|
|
3792
|
+
* @description Destroys the designer and deletes all nodes from the placeholder.
|
|
3793
|
+
*/
|
|
3794
|
+
destroy() {
|
|
3795
|
+
this.view.destroy();
|
|
3796
|
+
}
|
|
3797
|
+
}
|
|
3798
|
+
|
|
3799
|
+
export { CenteredViewportCalculator, ClassicWheelControllerExtension, ClickCommandType, ComponentContext, ControlBarApi, DefaultViewportController, DefaultViewportControllerExtension, DefinitionChangeType, Designer, DesignerApi, DesignerContext, DesignerState, Dom, Editor, EditorApi, Icons, InputView, JoinView, LABEL_HEIGHT, LabelView, ObjectCloner, OutputView, PathBarApi, QuantifiedScaleViewportCalculator, RectPlaceholder, RectPlaceholderDirection, RectPlaceholderView, RegionView, SequenceComponent, SequenceComponentView, ServicesResolver, SimpleEvent, StepChildrenType, StepComponent, StepExtensionResolver, StepsTraverser, TaskStepComponentView, ToolboxApi, Uid, Vector, WorkspaceApi, race };
|