@stocksharp/diagram 0.2.3 → 0.4.0
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/dist/esm/canvas-renderer.js +79 -28
- package/dist/esm/canvas-renderer.js.map +1 -1
- package/dist/esm/core/action-registry.js +24 -0
- package/dist/esm/core/action-registry.js.map +1 -1
- package/dist/esm/core/document.js +134 -28
- package/dist/esm/core/document.js.map +1 -1
- package/dist/esm/core/json.js +17 -0
- package/dist/esm/core/json.js.map +1 -0
- package/dist/esm/core/model.js +8 -0
- package/dist/esm/core/model.js.map +1 -1
- package/dist/esm/core/state.js +16 -2
- package/dist/esm/core/state.js.map +1 -1
- package/dist/esm/diagram/catalog.js +17 -5
- package/dist/esm/diagram/catalog.js.map +1 -1
- package/dist/esm/diagram/event-emitter.js +10 -0
- package/dist/esm/diagram/event-emitter.js.map +1 -1
- package/dist/esm/diagram/stocksharp-diagram.js +58 -56
- package/dist/esm/diagram/stocksharp-diagram.js.map +1 -1
- package/dist/esm/diagram/types.js +2 -1
- package/dist/esm/diagram/types.js.map +1 -1
- package/dist/esm/embed.js +121 -38
- package/dist/esm/embed.js.map +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/ssdiagram.js +380 -150
- package/dist/ssdiagram.js.map +4 -4
- package/dist/types/canvas-renderer.d.ts +11 -6
- package/dist/types/canvas-renderer.d.ts.map +1 -1
- package/dist/types/core/action-registry.d.ts +7 -0
- package/dist/types/core/action-registry.d.ts.map +1 -1
- package/dist/types/core/document.d.ts +1 -1
- package/dist/types/core/document.d.ts.map +1 -1
- package/dist/types/core/json.d.ts +11 -0
- package/dist/types/core/json.d.ts.map +1 -0
- package/dist/types/core/model.d.ts +45 -2
- package/dist/types/core/model.d.ts.map +1 -1
- package/dist/types/core/state.d.ts +24 -1
- package/dist/types/core/state.d.ts.map +1 -1
- package/dist/types/diagram/api.d.ts +6 -6
- package/dist/types/diagram/api.d.ts.map +1 -1
- package/dist/types/diagram/catalog.d.ts +2 -1
- package/dist/types/diagram/catalog.d.ts.map +1 -1
- package/dist/types/diagram/event-emitter.d.ts +6 -1
- package/dist/types/diagram/event-emitter.d.ts.map +1 -1
- package/dist/types/diagram/palette.d.ts +1 -1
- package/dist/types/diagram/palette.d.ts.map +1 -1
- package/dist/types/diagram/stocksharp-diagram.d.ts +29 -22
- package/dist/types/diagram/stocksharp-diagram.d.ts.map +1 -1
- package/dist/types/diagram/types.d.ts +4 -4
- package/dist/types/diagram/types.d.ts.map +1 -1
- package/dist/types/embed.d.ts.map +1 -1
- package/dist/types/index.d.ts +3 -3
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/canvas-renderer.ts +84 -29
- package/src/core/action-registry.ts +25 -0
- package/src/core/document.ts +147 -31
- package/src/core/json.ts +16 -0
- package/src/core/model.ts +55 -2
- package/src/core/state.ts +41 -3
- package/src/diagram/api.ts +6 -5
- package/src/diagram/catalog.ts +18 -6
- package/src/diagram/event-emitter.ts +12 -1
- package/src/diagram/palette.ts +1 -1
- package/src/diagram/stocksharp-diagram.ts +79 -76
- package/src/diagram/types.ts +6 -5
- package/src/embed.ts +140 -39
- package/src/index.ts +4 -0
package/dist/ssdiagram.js
CHANGED
|
@@ -37,6 +37,7 @@ var SSDiagram = (() => {
|
|
|
37
37
|
StockSharpDiagram: () => StockSharpDiagram,
|
|
38
38
|
StockSharpPalette: () => StockSharpPalette,
|
|
39
39
|
cloneDiagramDocument: () => cloneDiagramDocument,
|
|
40
|
+
cloneDiagramNodeErrors: () => cloneDiagramNodeErrors,
|
|
40
41
|
cloneDiagramRuntimeState: () => cloneDiagramRuntimeState,
|
|
41
42
|
createDiagramDocument: () => createDiagramDocument,
|
|
42
43
|
createDiagramNodeRuntimeState: () => createDiagramNodeRuntimeState,
|
|
@@ -58,7 +59,19 @@ var SSDiagram = (() => {
|
|
|
58
59
|
serializeDiagramViewState: () => serializeDiagramViewState
|
|
59
60
|
});
|
|
60
61
|
|
|
62
|
+
// src/core/json.ts
|
|
63
|
+
function setJsonKey(target, key, value) {
|
|
64
|
+
if (key === "__proto__") {
|
|
65
|
+
Object.defineProperty(target, key, { value, enumerable: true, writable: true, configurable: true });
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
target[key] = value;
|
|
69
|
+
}
|
|
70
|
+
|
|
61
71
|
// src/core/model.ts
|
|
72
|
+
function toPortDynamicMode(value) {
|
|
73
|
+
return value === "manual" || value === "onConnect" ? value : "";
|
|
74
|
+
}
|
|
62
75
|
var DIAGRAM_DOCUMENT_VERSION = 1;
|
|
63
76
|
|
|
64
77
|
// src/core/document.ts
|
|
@@ -70,32 +83,47 @@ var SSDiagram = (() => {
|
|
|
70
83
|
}
|
|
71
84
|
};
|
|
72
85
|
function createDiagramDocument(input = {}) {
|
|
73
|
-
const
|
|
86
|
+
const rawNodes = mapElements(input.nodes, "$.nodes");
|
|
87
|
+
const rawLinks = mapElements(input.links, "$.links");
|
|
88
|
+
const nodes = rawNodes.map((node, index) => normalizeNode(node, `$.nodes[${index}]`));
|
|
74
89
|
const usedLinkIds = /* @__PURE__ */ new Set();
|
|
75
|
-
for (let index = 0; index <
|
|
76
|
-
const
|
|
77
|
-
if (
|
|
78
|
-
requireIdentifier(
|
|
90
|
+
for (let index = 0; index < rawLinks.length; index++) {
|
|
91
|
+
const raw = rawLinks[index].id;
|
|
92
|
+
if (raw === void 0) continue;
|
|
93
|
+
const id = requireIdentifier(raw, `$.links[${index}].id`);
|
|
79
94
|
if (usedLinkIds.has(id)) {
|
|
80
95
|
throw new DiagramDocumentError(`duplicate link id "${id}"`, `$.links[${index}].id`);
|
|
81
96
|
}
|
|
82
97
|
usedLinkIds.add(id);
|
|
83
98
|
}
|
|
84
99
|
let sequence = 1;
|
|
85
|
-
const links =
|
|
100
|
+
const links = rawLinks.map((link, index) => {
|
|
86
101
|
let id = link.id;
|
|
87
102
|
if (id === void 0) {
|
|
103
|
+
let generated;
|
|
88
104
|
do
|
|
89
|
-
|
|
90
|
-
while (usedLinkIds.has(
|
|
91
|
-
usedLinkIds.add(
|
|
105
|
+
generated = `link_${sequence++}`;
|
|
106
|
+
while (usedLinkIds.has(generated));
|
|
107
|
+
usedLinkIds.add(generated);
|
|
108
|
+
id = generated;
|
|
92
109
|
}
|
|
93
110
|
return normalizeLink({ ...link, id }, `$.links[${index}]`);
|
|
94
111
|
});
|
|
112
|
+
const rawZones = mapElements(input.zones, "$.zones");
|
|
113
|
+
const usedZoneIds = /* @__PURE__ */ new Set();
|
|
114
|
+
const zones = rawZones.map((zone, index) => {
|
|
115
|
+
const normalized = normalizeZone(zone, `$.zones[${index}]`);
|
|
116
|
+
if (usedZoneIds.has(normalized.id)) {
|
|
117
|
+
throw new DiagramDocumentError(`duplicate zone id "${normalized.id}"`, `$.zones[${index}].id`);
|
|
118
|
+
}
|
|
119
|
+
usedZoneIds.add(normalized.id);
|
|
120
|
+
return normalized;
|
|
121
|
+
});
|
|
95
122
|
const document2 = {
|
|
96
123
|
version: DIAGRAM_DOCUMENT_VERSION,
|
|
97
124
|
nodes,
|
|
98
125
|
links,
|
|
126
|
+
zones,
|
|
99
127
|
metadata: cloneJsonObject(input.metadata ?? {}, "$.metadata")
|
|
100
128
|
};
|
|
101
129
|
validateDocument(document2);
|
|
@@ -117,7 +145,7 @@ var SSDiagram = (() => {
|
|
|
117
145
|
throw new DiagramDocumentError(reason);
|
|
118
146
|
}
|
|
119
147
|
}
|
|
120
|
-
const root =
|
|
148
|
+
const root = requireStructure(value, "$");
|
|
121
149
|
const version = requireNumber(root.version, "$.version");
|
|
122
150
|
if (version !== DIAGRAM_DOCUMENT_VERSION) {
|
|
123
151
|
throw new DiagramDocumentError(`unsupported document version ${version}`, "$.version");
|
|
@@ -126,12 +154,16 @@ var SSDiagram = (() => {
|
|
|
126
154
|
version: DIAGRAM_DOCUMENT_VERSION,
|
|
127
155
|
nodes: requireArray(root.nodes, "$.nodes").map((node, index) => parseNode(node, `$.nodes[${index}]`)),
|
|
128
156
|
links: requireArray(root.links, "$.links").map((link, index) => parseLink(link, `$.links[${index}]`)),
|
|
157
|
+
// Zones arrived after the first documents were written, so their absence is not a fault:
|
|
158
|
+
// an older file simply has none, and reading it must not need a migration step.
|
|
159
|
+
zones: root.zones === void 0 || root.zones === null ? [] : requireArray(root.zones, "$.zones").map((zone, index) => normalizeZone(zone, `$.zones[${index}]`)),
|
|
129
160
|
metadata: cloneJsonObject(root.metadata, "$.metadata")
|
|
130
161
|
};
|
|
131
162
|
validateDocument(document2);
|
|
132
163
|
return document2;
|
|
133
164
|
}
|
|
134
|
-
function normalizeNode(
|
|
165
|
+
function normalizeNode(value, path) {
|
|
166
|
+
const input = requireStructure(value, path);
|
|
135
167
|
const id = requireIdentifier(input.id, `${path}.id`);
|
|
136
168
|
return {
|
|
137
169
|
id,
|
|
@@ -146,28 +178,30 @@ var SSDiagram = (() => {
|
|
|
146
178
|
icon: requireString(input.icon ?? "", `${path}.icon`),
|
|
147
179
|
message: requireString(input.message ?? "", `${path}.message`),
|
|
148
180
|
openAction: requireString(input.openAction ?? "", `${path}.openAction`),
|
|
149
|
-
inPorts: (input.inPorts
|
|
150
|
-
outPorts: (input.outPorts
|
|
151
|
-
parameters: (input.parameters
|
|
181
|
+
inPorts: mapElements(input.inPorts, `${path}.inPorts`).map((port, index) => normalizePort(port, `${path}.inPorts[${index}]`)),
|
|
182
|
+
outPorts: mapElements(input.outPorts, `${path}.outPorts`).map((port, index) => normalizePort(port, `${path}.outPorts[${index}]`)),
|
|
183
|
+
parameters: mapElements(input.parameters, `${path}.parameters`).map((parameter, index) => normalizeParameter(parameter, `${path}.parameters[${index}]`)),
|
|
152
184
|
paramValues: cloneStringRecord(input.paramValues ?? {}, `${path}.paramValues`),
|
|
153
185
|
metadata: cloneJsonObject(input.metadata ?? {}, `${path}.metadata`)
|
|
154
186
|
};
|
|
155
187
|
}
|
|
156
|
-
function normalizePort(
|
|
188
|
+
function normalizePort(value, path) {
|
|
189
|
+
const input = requireStructure(value, path);
|
|
157
190
|
return {
|
|
158
191
|
id: requireIdentifier(input.id, `${path}.id`),
|
|
159
192
|
name: requireString(input.name, `${path}.name`),
|
|
160
193
|
description: requireString(input.description ?? "", `${path}.description`),
|
|
161
194
|
type: requireString(input.type ?? "", `${path}.type`),
|
|
162
195
|
maxLinks: requireNonNegativeInteger(input.maxLinks ?? 0, `${path}.maxLinks`),
|
|
163
|
-
availableTypes: (input.availableTypes ?? []).map((type, index) => requireString(type, `${path}.availableTypes[${index}]`)),
|
|
196
|
+
availableTypes: requireArray(input.availableTypes ?? [], `${path}.availableTypes`).map((type, index) => requireString(type, `${path}.availableTypes[${index}]`)),
|
|
164
197
|
isDynamic: requireBoolean(input.isDynamic ?? false, `${path}.isDynamic`),
|
|
165
|
-
dynamicMode:
|
|
198
|
+
dynamicMode: requirePortDynamicMode(input.dynamicMode ?? "", `${path}.dynamicMode`),
|
|
166
199
|
isSibling: requireBoolean(input.isSibling ?? false, `${path}.isSibling`),
|
|
167
200
|
metadata: cloneJsonObject(input.metadata ?? {}, `${path}.metadata`)
|
|
168
201
|
};
|
|
169
202
|
}
|
|
170
|
-
function normalizeParameter(
|
|
203
|
+
function normalizeParameter(value, path) {
|
|
204
|
+
const input = requireStructure(value, path);
|
|
171
205
|
return {
|
|
172
206
|
name: requireIdentifier(input.name, `${path}.name`),
|
|
173
207
|
displayName: requireString(input.displayName, `${path}.displayName`),
|
|
@@ -183,22 +217,50 @@ var SSDiagram = (() => {
|
|
|
183
217
|
editorType: requireString(input.editorType, `${path}.editorType`)
|
|
184
218
|
};
|
|
185
219
|
}
|
|
186
|
-
function normalizeLink(
|
|
220
|
+
function normalizeLink(value, path) {
|
|
221
|
+
const input = requireStructure(value, path);
|
|
187
222
|
return {
|
|
188
223
|
id: requireIdentifier(input.id, `${path}.id`),
|
|
189
224
|
from: normalizeEndpoint(input.from, `${path}.from`),
|
|
190
225
|
to: normalizeEndpoint(input.to, `${path}.to`),
|
|
226
|
+
style: requireLinkStyle(input.style, `${path}.style`),
|
|
191
227
|
metadata: cloneJsonObject(input.metadata ?? {}, `${path}.metadata`)
|
|
192
228
|
};
|
|
193
229
|
}
|
|
194
|
-
function
|
|
230
|
+
function requireLinkStyle(value, path) {
|
|
231
|
+
if (value === void 0 || value === null) return "solid";
|
|
232
|
+
if (value !== "solid" && value !== "dashed") {
|
|
233
|
+
throw new DiagramDocumentError(`unknown link style "${String(value)}"`, path);
|
|
234
|
+
}
|
|
235
|
+
return value;
|
|
236
|
+
}
|
|
237
|
+
function normalizeZone(value, path) {
|
|
238
|
+
const input = requireStructure(value, path);
|
|
239
|
+
return {
|
|
240
|
+
id: requireIdentifier(input.id, `${path}.id`),
|
|
241
|
+
name: requireString(input.name, `${path}.name`),
|
|
242
|
+
x: requireNumber(input.x, `${path}.x`),
|
|
243
|
+
y: requireNumber(input.y, `${path}.y`),
|
|
244
|
+
width: requirePositiveSize(input.width, `${path}.width`),
|
|
245
|
+
height: requirePositiveSize(input.height, `${path}.height`),
|
|
246
|
+
color: input.color === void 0 || input.color === null ? "" : requireString(input.color, `${path}.color`),
|
|
247
|
+
metadata: cloneJsonObject(input.metadata ?? {}, `${path}.metadata`)
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
function requirePositiveSize(value, path) {
|
|
251
|
+
const size = requireNumber(value, path);
|
|
252
|
+
if (!(size > 0)) throw new DiagramDocumentError("size must be greater than zero", path);
|
|
253
|
+
return size;
|
|
254
|
+
}
|
|
255
|
+
function normalizeEndpoint(value, path) {
|
|
256
|
+
const input = requireStructure(value, path);
|
|
195
257
|
return {
|
|
196
258
|
nodeId: requireIdentifier(input.nodeId, `${path}.nodeId`),
|
|
197
259
|
portId: requireIdentifier(input.portId, `${path}.portId`)
|
|
198
260
|
};
|
|
199
261
|
}
|
|
200
262
|
function parseNode(value, path) {
|
|
201
|
-
const node =
|
|
263
|
+
const node = requireStructure(value, path);
|
|
202
264
|
return normalizeNode({
|
|
203
265
|
id: requireString(node.id, `${path}.id`),
|
|
204
266
|
typeId: requireString(node.typeId, `${path}.typeId`),
|
|
@@ -220,7 +282,7 @@ var SSDiagram = (() => {
|
|
|
220
282
|
}, path);
|
|
221
283
|
}
|
|
222
284
|
function parsePort(value, path) {
|
|
223
|
-
const port =
|
|
285
|
+
const port = requireStructure(value, path);
|
|
224
286
|
return normalizePort({
|
|
225
287
|
id: requireString(port.id, `${path}.id`),
|
|
226
288
|
name: requireString(port.name, `${path}.name`),
|
|
@@ -229,13 +291,13 @@ var SSDiagram = (() => {
|
|
|
229
291
|
maxLinks: requireNumber(port.maxLinks, `${path}.maxLinks`),
|
|
230
292
|
availableTypes: requireArray(port.availableTypes, `${path}.availableTypes`).map((type, index) => requireString(type, `${path}.availableTypes[${index}]`)),
|
|
231
293
|
isDynamic: requireBoolean(port.isDynamic, `${path}.isDynamic`),
|
|
232
|
-
dynamicMode:
|
|
294
|
+
dynamicMode: requirePortDynamicMode(port.dynamicMode, `${path}.dynamicMode`),
|
|
233
295
|
isSibling: requireBoolean(port.isSibling, `${path}.isSibling`),
|
|
234
296
|
metadata: cloneJsonObject(port.metadata, `${path}.metadata`)
|
|
235
297
|
}, path);
|
|
236
298
|
}
|
|
237
299
|
function parseParameter(value, path) {
|
|
238
|
-
const parameter =
|
|
300
|
+
const parameter = requireStructure(value, path);
|
|
239
301
|
return normalizeParameter({
|
|
240
302
|
name: requireString(parameter.name, `${path}.name`),
|
|
241
303
|
displayName: requireString(parameter.displayName, `${path}.displayName`),
|
|
@@ -252,16 +314,17 @@ var SSDiagram = (() => {
|
|
|
252
314
|
}, path);
|
|
253
315
|
}
|
|
254
316
|
function parseLink(value, path) {
|
|
255
|
-
const link =
|
|
317
|
+
const link = requireStructure(value, path);
|
|
256
318
|
return normalizeLink({
|
|
257
319
|
id: requireString(link.id, `${path}.id`),
|
|
258
320
|
from: parseEndpoint(link.from, `${path}.from`),
|
|
259
321
|
to: parseEndpoint(link.to, `${path}.to`),
|
|
322
|
+
style: link.style === void 0 ? void 0 : requireLinkStyle(link.style, `${path}.style`),
|
|
260
323
|
metadata: cloneJsonObject(link.metadata, `${path}.metadata`)
|
|
261
324
|
}, path);
|
|
262
325
|
}
|
|
263
326
|
function parseEndpoint(value, path) {
|
|
264
|
-
const endpoint =
|
|
327
|
+
const endpoint = requireStructure(value, path);
|
|
265
328
|
return {
|
|
266
329
|
nodeId: requireIdentifier(endpoint.nodeId, `${path}.nodeId`),
|
|
267
330
|
portId: requireIdentifier(endpoint.portId, `${path}.portId`)
|
|
@@ -309,7 +372,7 @@ var SSDiagram = (() => {
|
|
|
309
372
|
function cloneStringRecord(value, path) {
|
|
310
373
|
const object = requireObject(value, path);
|
|
311
374
|
const result = {};
|
|
312
|
-
for (const [key, item] of Object.entries(object)) result
|
|
375
|
+
for (const [key, item] of Object.entries(object)) setJsonKey(result, key, requireString(item, `${path}.${key}`));
|
|
313
376
|
return result;
|
|
314
377
|
}
|
|
315
378
|
function cloneJsonObject(value, path, ancestors = /* @__PURE__ */ new WeakSet()) {
|
|
@@ -319,7 +382,7 @@ var SSDiagram = (() => {
|
|
|
319
382
|
const result = {};
|
|
320
383
|
try {
|
|
321
384
|
for (const [key, item] of Object.entries(object)) {
|
|
322
|
-
result
|
|
385
|
+
setJsonKey(result, key, cloneJsonValue(item, `${path}.${key}`, ancestors));
|
|
323
386
|
}
|
|
324
387
|
} finally {
|
|
325
388
|
ancestors.delete(object);
|
|
@@ -341,6 +404,12 @@ var SSDiagram = (() => {
|
|
|
341
404
|
if (isObject(value)) return cloneJsonObject(value, path, ancestors);
|
|
342
405
|
throw new DiagramDocumentError("expected a JSON value", path);
|
|
343
406
|
}
|
|
407
|
+
function requireStructure(value, path) {
|
|
408
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
409
|
+
throw new DiagramDocumentError("expected an object", path);
|
|
410
|
+
}
|
|
411
|
+
return value;
|
|
412
|
+
}
|
|
344
413
|
function requireObject(value, path) {
|
|
345
414
|
if (!isObject(value)) throw new DiagramDocumentError("expected an object", path);
|
|
346
415
|
return value;
|
|
@@ -350,6 +419,13 @@ var SSDiagram = (() => {
|
|
|
350
419
|
const prototype = Object.getPrototypeOf(value);
|
|
351
420
|
return prototype === Object.prototype || prototype === null;
|
|
352
421
|
}
|
|
422
|
+
function mapElements(value, path) {
|
|
423
|
+
if (value === void 0 || value === null) return [];
|
|
424
|
+
return Array.from(
|
|
425
|
+
requireArray(value, path),
|
|
426
|
+
(element, index) => requireStructure(element, `${path}[${index}]`)
|
|
427
|
+
);
|
|
428
|
+
}
|
|
353
429
|
function requireArray(value, path) {
|
|
354
430
|
if (!Array.isArray(value)) throw new DiagramDocumentError("expected an array", path);
|
|
355
431
|
return value;
|
|
@@ -358,6 +434,10 @@ var SSDiagram = (() => {
|
|
|
358
434
|
if (typeof value !== "string") throw new DiagramDocumentError("expected a string", path);
|
|
359
435
|
return value;
|
|
360
436
|
}
|
|
437
|
+
function requirePortDynamicMode(value, path) {
|
|
438
|
+
const mode = requireString(value, path);
|
|
439
|
+
return toPortDynamicMode(mode);
|
|
440
|
+
}
|
|
361
441
|
function requireIdentifier(value, path) {
|
|
362
442
|
const id = requireString(value, path);
|
|
363
443
|
if (id.trim().length === 0) throw new DiagramDocumentError("identifier cannot be empty", path);
|
|
@@ -399,6 +479,24 @@ var SSDiagram = (() => {
|
|
|
399
479
|
if (this.actions.get(action.id) === action) this.actions.delete(action.id);
|
|
400
480
|
};
|
|
401
481
|
}
|
|
482
|
+
/**
|
|
483
|
+
* Registers one action per id from a table keyed by TId, in key order.
|
|
484
|
+
* Because the table is a total Record, adding a member to TId without adding
|
|
485
|
+
* its action stops compiling instead of producing a command that silently
|
|
486
|
+
* does nothing.
|
|
487
|
+
*/
|
|
488
|
+
registerAll(actions) {
|
|
489
|
+
const ids = Object.keys(actions);
|
|
490
|
+
const disposers = ids.map((id) => {
|
|
491
|
+
const action = actions[id];
|
|
492
|
+
return this.register({
|
|
493
|
+
id,
|
|
494
|
+
canExecute: (context) => action.canExecute(context),
|
|
495
|
+
execute: (context) => action.execute(context)
|
|
496
|
+
});
|
|
497
|
+
});
|
|
498
|
+
return () => disposers.forEach((dispose) => dispose());
|
|
499
|
+
}
|
|
402
500
|
get(id) {
|
|
403
501
|
return this.actions.get(id) ?? null;
|
|
404
502
|
}
|
|
@@ -554,7 +652,7 @@ var SSDiagram = (() => {
|
|
|
554
652
|
globalError: state.globalError === null ? null : { ...state.globalError },
|
|
555
653
|
nodes: Object.fromEntries(Object.entries(state.nodes).map(([nodeId, node]) => [nodeId, {
|
|
556
654
|
active: node.active,
|
|
557
|
-
|
|
655
|
+
errors: cloneDiagramNodeErrors(node.errors),
|
|
558
656
|
ports: {
|
|
559
657
|
in: Object.fromEntries(Object.entries(node.ports.in).map(([portId, port]) => [portId, { ...port }])),
|
|
560
658
|
out: Object.fromEntries(Object.entries(node.ports.out).map(([portId, port]) => [portId, { ...port }]))
|
|
@@ -611,10 +709,17 @@ var SSDiagram = (() => {
|
|
|
611
709
|
function createDiagramNodeRuntimeState() {
|
|
612
710
|
return {
|
|
613
711
|
active: false,
|
|
614
|
-
|
|
712
|
+
errors: {},
|
|
615
713
|
ports: { in: {}, out: {} }
|
|
616
714
|
};
|
|
617
715
|
}
|
|
716
|
+
function cloneDiagramNodeErrors(errors) {
|
|
717
|
+
const result = {};
|
|
718
|
+
if (errors === void 0 || errors === null) return result;
|
|
719
|
+
if (errors.runtime !== void 0) result.runtime = { ...errors.runtime };
|
|
720
|
+
if (errors.load !== void 0) result.load = { ...errors.load };
|
|
721
|
+
return result;
|
|
722
|
+
}
|
|
618
723
|
|
|
619
724
|
// src/core/view-state.ts
|
|
620
725
|
var DIAGRAM_VIEW_STATE_VERSION = 1;
|
|
@@ -699,7 +804,7 @@ var SSDiagram = (() => {
|
|
|
699
804
|
function copyJsonObject(value) {
|
|
700
805
|
if (value === void 0) return {};
|
|
701
806
|
const result = {};
|
|
702
|
-
for (const [key, item] of Object.entries(value)) result
|
|
807
|
+
for (const [key, item] of Object.entries(value)) setJsonKey(result, key, copyJsonValue(item));
|
|
703
808
|
return result;
|
|
704
809
|
}
|
|
705
810
|
function copyParameters(value) {
|
|
@@ -730,7 +835,7 @@ var SSDiagram = (() => {
|
|
|
730
835
|
this.maxLinks = typeof init.maxLinks === "number" ? init.maxLinks : 0;
|
|
731
836
|
this.availableTypes = [...init.availableTypes ?? []];
|
|
732
837
|
this.isDynamic = init.isDynamic ?? false;
|
|
733
|
-
this.dynamicMode = init.dynamicMode
|
|
838
|
+
this.dynamicMode = toPortDynamicMode(init.dynamicMode);
|
|
734
839
|
this.isSibling = init.isSibling ?? false;
|
|
735
840
|
this.metadata = copyJsonObject(init.metadata);
|
|
736
841
|
}
|
|
@@ -805,11 +910,12 @@ var SSDiagram = (() => {
|
|
|
805
910
|
}
|
|
806
911
|
};
|
|
807
912
|
var LinkModel = class {
|
|
808
|
-
constructor(from, fromPort, to, toPort, id = "", metadata) {
|
|
913
|
+
constructor(from, fromPort, to, toPort, id = "", metadata, style = "solid") {
|
|
809
914
|
this.from = from;
|
|
810
915
|
this.fromPort = fromPort;
|
|
811
916
|
this.to = to;
|
|
812
917
|
this.toPort = toPort;
|
|
918
|
+
this.style = style;
|
|
813
919
|
this.id = id;
|
|
814
920
|
this.metadata = copyJsonObject(metadata);
|
|
815
921
|
}
|
|
@@ -823,6 +929,7 @@ var SSDiagram = (() => {
|
|
|
823
929
|
fromPort: this.fromPort,
|
|
824
930
|
to: this.to,
|
|
825
931
|
toPort: this.toPort,
|
|
932
|
+
style: this.style,
|
|
826
933
|
metadata: copyJsonObject(this.metadata)
|
|
827
934
|
};
|
|
828
935
|
}
|
|
@@ -872,6 +979,7 @@ var SSDiagram = (() => {
|
|
|
872
979
|
constructor(opts) {
|
|
873
980
|
this.nodes = [];
|
|
874
981
|
this.links = [];
|
|
982
|
+
this.zones = [];
|
|
875
983
|
this.idSeq = 1;
|
|
876
984
|
this.linkSeq = 1;
|
|
877
985
|
this.documentMetadata = {};
|
|
@@ -897,8 +1005,6 @@ var SSDiagram = (() => {
|
|
|
897
1005
|
this.dragStart = [];
|
|
898
1006
|
// group-drag origin
|
|
899
1007
|
this.dragAnchor = { wx: 0, wy: 0 };
|
|
900
|
-
this.dragDX = 0;
|
|
901
|
-
this.dragDY = 0;
|
|
902
1008
|
this.panning = false;
|
|
903
1009
|
this.panX = 0;
|
|
904
1010
|
this.panY = 0;
|
|
@@ -1203,7 +1309,7 @@ var SSDiagram = (() => {
|
|
|
1203
1309
|
this.emit("linkValidation", { fromNode: fn, from: fp, toNode: tn, to: tp, ...validation });
|
|
1204
1310
|
if (!validation.allowed) return false;
|
|
1205
1311
|
}
|
|
1206
|
-
const link = new LinkModel(init.from, init.fromPort, init.to, init.toPort, init.id ?? this.nextLinkId(), init.metadata);
|
|
1312
|
+
const link = new LinkModel(init.from, init.fromPort, init.to, init.toPort, init.id ?? this.nextLinkId(), init.metadata, init.style ?? "solid");
|
|
1207
1313
|
if (this.links.some((l) => l.id === link.id)) return false;
|
|
1208
1314
|
this.links.splice(clamp(Math.trunc(index), 0, this.links.length), 0, link);
|
|
1209
1315
|
this.emit("linkAdded", { link });
|
|
@@ -1482,7 +1588,7 @@ var SSDiagram = (() => {
|
|
|
1482
1588
|
setNodeParamValue(nodeId, name, value) {
|
|
1483
1589
|
return this.updateNodeState(nodeId, "set node parameter", (node) => {
|
|
1484
1590
|
if (value === void 0) delete node.paramValues[name];
|
|
1485
|
-
else node.paramValues
|
|
1591
|
+
else setJsonKey(node.paramValues, name, value);
|
|
1486
1592
|
});
|
|
1487
1593
|
}
|
|
1488
1594
|
setShowNodeMessages(show) {
|
|
@@ -1559,13 +1665,13 @@ var SSDiagram = (() => {
|
|
|
1559
1665
|
if (this.links.some((existing) => existing.id === id)) {
|
|
1560
1666
|
throw new Error(`ssdiagram: duplicate link id "${id}"`);
|
|
1561
1667
|
}
|
|
1562
|
-
const model = new LinkModel(link.from, link.fromPort, link.to, link.toPort, id, link.metadata);
|
|
1668
|
+
const model = new LinkModel(link.from, link.fromPort, link.to, link.toPort, id, link.metadata, link.style ?? "solid");
|
|
1563
1669
|
if (!this.links.some((existing) => existing.key() === model.key())) this.links.push(model);
|
|
1564
1670
|
}
|
|
1565
1671
|
for (const node of this.nodes) {
|
|
1566
1672
|
if (node.loadError.length === 0) continue;
|
|
1567
1673
|
const state = createDiagramNodeRuntimeState();
|
|
1568
|
-
state.
|
|
1674
|
+
state.errors.load = { kind: "load", message: node.loadError, pulse: ++this.runtimePulse };
|
|
1569
1675
|
this.runtimeState.nodes[node.id] = state;
|
|
1570
1676
|
}
|
|
1571
1677
|
if (Object.keys(this.runtimeState.nodes).length > 0) this.emitRuntimeStateChanged();
|
|
@@ -1590,9 +1696,11 @@ var SSDiagram = (() => {
|
|
|
1590
1696
|
fromPort: link.from.portId,
|
|
1591
1697
|
to: link.to.nodeId,
|
|
1592
1698
|
toPort: link.to.portId,
|
|
1699
|
+
style: link.style,
|
|
1593
1700
|
metadata: link.metadata
|
|
1594
1701
|
}))
|
|
1595
1702
|
);
|
|
1703
|
+
this.zones = document2.zones.map((zone) => ({ ...zone, metadata: copyJsonObject(zone.metadata) }));
|
|
1596
1704
|
this.documentMetadata = copyJsonObject(document2.metadata);
|
|
1597
1705
|
}
|
|
1598
1706
|
saveDocument() {
|
|
@@ -1602,8 +1710,10 @@ var SSDiagram = (() => {
|
|
|
1602
1710
|
id: link.id,
|
|
1603
1711
|
from: { nodeId: link.from, portId: link.fromPort },
|
|
1604
1712
|
to: { nodeId: link.to, portId: link.toPort },
|
|
1713
|
+
style: link.style,
|
|
1605
1714
|
metadata: link.metadata
|
|
1606
1715
|
})),
|
|
1716
|
+
zones: this.zones.map((zone) => ({ ...zone, metadata: copyJsonObject(zone.metadata) })),
|
|
1607
1717
|
metadata: this.documentMetadata
|
|
1608
1718
|
});
|
|
1609
1719
|
}
|
|
@@ -1765,15 +1875,16 @@ var SSDiagram = (() => {
|
|
|
1765
1875
|
this.runtimePulse = Math.max(
|
|
1766
1876
|
this.runtimePulse,
|
|
1767
1877
|
this.runtimeState.globalError?.pulse ?? 0,
|
|
1768
|
-
...Object.values(this.runtimeState.nodes).
|
|
1878
|
+
...Object.values(this.runtimeState.nodes).flatMap((node) => [node.errors.runtime?.pulse ?? 0, node.errors.load?.pulse ?? 0])
|
|
1769
1879
|
);
|
|
1770
1880
|
for (const node of this.nodes) {
|
|
1771
|
-
const
|
|
1772
|
-
const
|
|
1773
|
-
|
|
1774
|
-
node.
|
|
1775
|
-
|
|
1776
|
-
|
|
1881
|
+
const errors = this.runtimeState.nodes[node.id]?.errors;
|
|
1882
|
+
const previousRuntime = previous.nodes[node.id]?.errors.runtime;
|
|
1883
|
+
const runtimeError = errors?.runtime;
|
|
1884
|
+
node.runtimeError = runtimeError?.message ?? "";
|
|
1885
|
+
node.loadError = errors?.load?.message ?? "";
|
|
1886
|
+
if (runtimeError !== void 0) {
|
|
1887
|
+
if (previousRuntime === void 0 || previousRuntime.pulse !== runtimeError.pulse) {
|
|
1777
1888
|
node.errorFlashStart = performance.now();
|
|
1778
1889
|
}
|
|
1779
1890
|
} else {
|
|
@@ -1822,7 +1933,9 @@ var SSDiagram = (() => {
|
|
|
1822
1933
|
const state = this.getRuntimeState();
|
|
1823
1934
|
const nodeState = state.nodes[id] ?? createDiagramNodeRuntimeState();
|
|
1824
1935
|
state.nodes[id] = nodeState;
|
|
1825
|
-
|
|
1936
|
+
if (message.length === 0) delete nodeState.errors[kind];
|
|
1937
|
+
else if (kind === "load") nodeState.errors.load = { kind, message, pulse: ++this.runtimePulse };
|
|
1938
|
+
else nodeState.errors.runtime = { kind, message, pulse: ++this.runtimePulse };
|
|
1826
1939
|
this.runtimeState = state;
|
|
1827
1940
|
this.emitRuntimeStateChanged();
|
|
1828
1941
|
return true;
|
|
@@ -1837,8 +1950,9 @@ var SSDiagram = (() => {
|
|
|
1837
1950
|
if (kind === void 0 || kind === "load") node.loadError = "";
|
|
1838
1951
|
const state = this.getRuntimeState();
|
|
1839
1952
|
const nodeState = state.nodes[id];
|
|
1840
|
-
if (nodeState !== void 0
|
|
1841
|
-
|
|
1953
|
+
if (nodeState !== void 0) {
|
|
1954
|
+
if (kind === void 0 || kind === "runtime") delete nodeState.errors.runtime;
|
|
1955
|
+
if (kind === void 0 || kind === "load") delete nodeState.errors.load;
|
|
1842
1956
|
}
|
|
1843
1957
|
this.runtimeState = state;
|
|
1844
1958
|
this.emitRuntimeStateChanged();
|
|
@@ -2830,6 +2944,7 @@ var SSDiagram = (() => {
|
|
|
2830
2944
|
this.dpr * this.offX,
|
|
2831
2945
|
this.dpr * (this.offY + introDy)
|
|
2832
2946
|
);
|
|
2947
|
+
this.drawZones();
|
|
2833
2948
|
const prior = [];
|
|
2834
2949
|
const hoveredNode = this.hoverPort?.node ?? this.hoverNode;
|
|
2835
2950
|
for (const l of this.links) {
|
|
@@ -2842,7 +2957,7 @@ var SSDiagram = (() => {
|
|
|
2842
2957
|
const color = state === "sel" ? this.selectionColor() : state === "hov" ? this.linkHoverColor() : baseColor;
|
|
2843
2958
|
const width = state === "sel" ? 3 : state === "hov" ? 2.6 : 2;
|
|
2844
2959
|
const pts = this.routeLink(a, b, /* @__PURE__ */ new Set([l.from, l.to]));
|
|
2845
|
-
this.strokeRoute(pts, color, width, prior);
|
|
2960
|
+
this.strokeRoute(pts, color, width, prior, l.style);
|
|
2846
2961
|
this.drawArrow(pts, color);
|
|
2847
2962
|
for (let k = 1; k < pts.length; k += 1) {
|
|
2848
2963
|
const [x1, y1] = pts[k - 1];
|
|
@@ -2995,6 +3110,33 @@ ${runtime.error}`;
|
|
|
2995
3110
|
}
|
|
2996
3111
|
return lines.length > 0 ? lines : [""];
|
|
2997
3112
|
}
|
|
3113
|
+
drawZones() {
|
|
3114
|
+
if (this.zones.length === 0) return;
|
|
3115
|
+
const ctx = this.ctx;
|
|
3116
|
+
for (const zone of this.zones) {
|
|
3117
|
+
const tint = zone.color.length > 0 ? zone.color : this.opts.zoneColor ?? "#8a8f98";
|
|
3118
|
+
ctx.save();
|
|
3119
|
+
roundRect(ctx, zone.x, zone.y, zone.width, zone.height, 10);
|
|
3120
|
+
ctx.globalAlpha = 0.12;
|
|
3121
|
+
ctx.fillStyle = tint;
|
|
3122
|
+
ctx.fill();
|
|
3123
|
+
ctx.globalAlpha = 0.5;
|
|
3124
|
+
ctx.setLineDash([10, 6]);
|
|
3125
|
+
ctx.lineWidth = 1.5;
|
|
3126
|
+
ctx.strokeStyle = tint;
|
|
3127
|
+
ctx.stroke();
|
|
3128
|
+
ctx.restore();
|
|
3129
|
+
if (zone.name.length === 0) continue;
|
|
3130
|
+
ctx.save();
|
|
3131
|
+
ctx.globalAlpha = 0.75;
|
|
3132
|
+
ctx.fillStyle = tint;
|
|
3133
|
+
ctx.font = "600 12px Segoe UI, Tahoma, sans-serif";
|
|
3134
|
+
ctx.textAlign = "left";
|
|
3135
|
+
ctx.textBaseline = "top";
|
|
3136
|
+
ctx.fillText(zone.name, zone.x + 12, zone.y + 9, Math.max(0, zone.width - 24));
|
|
3137
|
+
ctx.restore();
|
|
3138
|
+
}
|
|
3139
|
+
}
|
|
2998
3140
|
drawGrid() {
|
|
2999
3141
|
const ctx = this.ctx;
|
|
3000
3142
|
const step = this.gridSize * this.scale;
|
|
@@ -3127,10 +3269,11 @@ ${runtime.error}`;
|
|
|
3127
3269
|
// Symmetric jump-over: any segment of THIS link hops the perpendicular
|
|
3128
3270
|
// segments of links drawn before it (H over earlier V, V over earlier
|
|
3129
3271
|
// H). Exactly one bridge per real crossing, consistent everywhere.
|
|
3130
|
-
strokeRoute(pts, color, width, prior) {
|
|
3272
|
+
strokeRoute(pts, color, width, prior, style = "solid") {
|
|
3131
3273
|
const ctx = this.ctx;
|
|
3132
3274
|
ctx.strokeStyle = color;
|
|
3133
3275
|
ctx.lineWidth = width;
|
|
3276
|
+
if (style === "dashed") ctx.setLineDash([9, 6]);
|
|
3134
3277
|
ctx.lineJoin = "miter";
|
|
3135
3278
|
ctx.lineCap = "butt";
|
|
3136
3279
|
ctx.beginPath();
|
|
@@ -3173,6 +3316,7 @@ ${runtime.error}`;
|
|
|
3173
3316
|
}
|
|
3174
3317
|
}
|
|
3175
3318
|
ctx.stroke();
|
|
3319
|
+
ctx.setLineDash([]);
|
|
3176
3320
|
}
|
|
3177
3321
|
drawArrow(pts, color) {
|
|
3178
3322
|
const n = pts.length;
|
|
@@ -3634,6 +3778,13 @@ ${runtime.error}`;
|
|
|
3634
3778
|
}
|
|
3635
3779
|
}
|
|
3636
3780
|
}
|
|
3781
|
+
/**
|
|
3782
|
+
* Whether anyone is listening. Lets a subject skip building a payload that
|
|
3783
|
+
* is expensive to produce and that nothing would read.
|
|
3784
|
+
*/
|
|
3785
|
+
hasHandlers(event) {
|
|
3786
|
+
return (this.handlers.get(event)?.size ?? 0) > 0;
|
|
3787
|
+
}
|
|
3637
3788
|
clearEventHandlers() {
|
|
3638
3789
|
this.handlers.clear();
|
|
3639
3790
|
}
|
|
@@ -3655,7 +3806,7 @@ ${runtime.error}`;
|
|
|
3655
3806
|
this.maxLinks = typeof init.maxLinks === "number" ? init.maxLinks : 0;
|
|
3656
3807
|
this.availableTypes = init.availableTypes ?? [];
|
|
3657
3808
|
this.isDynamic = init.isDynamic ?? false;
|
|
3658
|
-
this.dynamicMode = init.dynamicMode
|
|
3809
|
+
this.dynamicMode = toPortDynamicMode(init.dynamicMode);
|
|
3659
3810
|
this.isSibling = init.isSibling ?? false;
|
|
3660
3811
|
}
|
|
3661
3812
|
clone() {
|
|
@@ -3757,7 +3908,6 @@ ${runtime.error}`;
|
|
|
3757
3908
|
this.fullscreen = false;
|
|
3758
3909
|
this.fullscreenButtonVisible = true;
|
|
3759
3910
|
this.fullscreenLabels = { enter: "Enter fullscreen", exit: "Exit fullscreen" };
|
|
3760
|
-
this.linkValidator = null;
|
|
3761
3911
|
this.contextActions = new DiagramActionRegistry();
|
|
3762
3912
|
this.div = options.div;
|
|
3763
3913
|
this.catalog = options.catalog;
|
|
@@ -3781,7 +3931,6 @@ ${runtime.error}`;
|
|
|
3781
3931
|
this.updateZoomLabel();
|
|
3782
3932
|
}
|
|
3783
3933
|
setLinkValidator(validator) {
|
|
3784
|
-
this.linkValidator = validator;
|
|
3785
3934
|
this.canvas.setLinkValidator(validator === null ? null : ({ fromNode, fromPort, toNode, toPort }) => validator({
|
|
3786
3935
|
fromNode: this.fromCanvasNode(fromNode),
|
|
3787
3936
|
fromPort: this.fromCanvasPort(fromPort),
|
|
@@ -3855,21 +4004,25 @@ ${runtime.error}`;
|
|
|
3855
4004
|
removeLink(link) {
|
|
3856
4005
|
this.canvas.removeLink(this.toCanvasLink(link));
|
|
3857
4006
|
}
|
|
4007
|
+
/** False when nothing changed: no such node or port, or the edit was a no-op. */
|
|
3858
4008
|
addPort(nodeId, direction, port) {
|
|
3859
|
-
this.canvas.addPort(nodeId, direction, this.toCanvasPort(port));
|
|
4009
|
+
return this.canvas.addPort(nodeId, direction, this.toCanvasPort(port));
|
|
3860
4010
|
}
|
|
4011
|
+
/** False when nothing changed: no such node or port, or the edit was a no-op. */
|
|
3861
4012
|
removePort(nodeId, direction, portId) {
|
|
3862
|
-
this.canvas.removePort(nodeId, direction, portId);
|
|
4013
|
+
return this.canvas.removePort(nodeId, direction, portId);
|
|
3863
4014
|
}
|
|
4015
|
+
/** False when nothing changed: no such node or port, or the edit was a no-op. */
|
|
3864
4016
|
updatePortType(nodeId, direction, portId, type) {
|
|
3865
|
-
this.canvas.updatePortType(nodeId, direction, portId, type);
|
|
4017
|
+
return this.canvas.updatePortType(nodeId, direction, portId, type);
|
|
3866
4018
|
}
|
|
3867
4019
|
updatePort(nodeId, direction, portId, patch) {
|
|
3868
4020
|
return this.canvas.updatePort(nodeId, direction, portId, patch);
|
|
3869
4021
|
}
|
|
4022
|
+
/** False when nothing changed: no such node or port, or the edit was a no-op. */
|
|
3870
4023
|
setNodePorts(nodeId, inPorts, outPorts) {
|
|
3871
4024
|
const current = this.canvas.findNode(nodeId);
|
|
3872
|
-
if (current === void 0) return;
|
|
4025
|
+
if (current === void 0) return false;
|
|
3873
4026
|
const convert = (port) => ({
|
|
3874
4027
|
id: port.key,
|
|
3875
4028
|
name: port.name,
|
|
@@ -3888,13 +4041,15 @@ ${runtime.error}`;
|
|
|
3888
4041
|
for (const sibling of current.outPorts.filter((port) => port.isSibling)) {
|
|
3889
4042
|
if (!nextOut.some((port) => port.id === sibling.id)) nextOut.push(sibling.toInit());
|
|
3890
4043
|
}
|
|
3891
|
-
this.canvas.setNodePorts(nodeId, nextIn, nextOut);
|
|
4044
|
+
return this.canvas.setNodePorts(nodeId, nextIn, nextOut);
|
|
3892
4045
|
}
|
|
4046
|
+
/** False when nothing changed: no such node or port, or the edit was a no-op. */
|
|
3893
4047
|
updateNode(nodeId, patch) {
|
|
3894
|
-
this.canvas.updateNode(nodeId, patch);
|
|
4048
|
+
return this.canvas.updateNode(nodeId, patch);
|
|
3895
4049
|
}
|
|
4050
|
+
/** False when nothing changed: no such node or port, or the edit was a no-op. */
|
|
3896
4051
|
setNodeMessage(nodeId, message) {
|
|
3897
|
-
this.canvas.updateNode(nodeId, { message });
|
|
4052
|
+
return this.canvas.updateNode(nodeId, { message });
|
|
3898
4053
|
}
|
|
3899
4054
|
setNodeError(nodeId, message, options = {}) {
|
|
3900
4055
|
return this.canvas.setNodeError(nodeId, message, options);
|
|
@@ -3920,11 +4075,13 @@ ${runtime.error}`;
|
|
|
3920
4075
|
setGlobalError(message, kind = "invalid") {
|
|
3921
4076
|
this.canvas.setGlobalError(message, kind);
|
|
3922
4077
|
}
|
|
4078
|
+
/** False when nothing changed: no such node or port, or the edit was a no-op. */
|
|
3923
4079
|
setNodeParamValue(nodeId, paramName, value) {
|
|
3924
|
-
this.canvas.setNodeParamValue(nodeId, paramName, value);
|
|
4080
|
+
return this.canvas.setNodeParamValue(nodeId, paramName, value);
|
|
3925
4081
|
}
|
|
4082
|
+
/** False when nothing changed: no such node or port, or the edit was a no-op. */
|
|
3926
4083
|
setNodeName(nodeId, value) {
|
|
3927
|
-
this.canvas.updateNode(nodeId, { name: value });
|
|
4084
|
+
return this.canvas.updateNode(nodeId, { name: value });
|
|
3928
4085
|
}
|
|
3929
4086
|
/** Groups host-driven document edits into one undo/redo operation. */
|
|
3930
4087
|
transaction(label, action) {
|
|
@@ -4347,50 +4504,43 @@ ${runtime.error}`;
|
|
|
4347
4504
|
}
|
|
4348
4505
|
registerContextActions() {
|
|
4349
4506
|
const permissions = () => this.canvas.getInteractionPermissions();
|
|
4350
|
-
this.contextActions.
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
canExecute: ({ nodes }) => nodes.length > 0,
|
|
4388
|
-
execute: ({ nodes }) => this.emit("nodeProperties", { nodes })
|
|
4389
|
-
});
|
|
4390
|
-
this.contextActions.register({
|
|
4391
|
-
id: "help",
|
|
4392
|
-
canExecute: ({ nodes }) => this.helpEnabled && nodes.length > 0,
|
|
4393
|
-
execute: ({ nodes }) => this.emit("nodeHelp", { nodes })
|
|
4507
|
+
this.contextActions.registerAll({
|
|
4508
|
+
undo: {
|
|
4509
|
+
canExecute: () => this.canUndo(),
|
|
4510
|
+
execute: () => this.undo()
|
|
4511
|
+
},
|
|
4512
|
+
redo: {
|
|
4513
|
+
canExecute: () => this.canRedo(),
|
|
4514
|
+
execute: () => this.redo()
|
|
4515
|
+
},
|
|
4516
|
+
cut: {
|
|
4517
|
+
canExecute: ({ nodes }) => nodes.length > 0 && permissions().copy && permissions().deleteSelection,
|
|
4518
|
+
execute: () => this.cutSelection()
|
|
4519
|
+
},
|
|
4520
|
+
copy: {
|
|
4521
|
+
canExecute: ({ nodes }) => nodes.length > 0 && permissions().copy,
|
|
4522
|
+
execute: () => this.copySelection()
|
|
4523
|
+
},
|
|
4524
|
+
paste: {
|
|
4525
|
+
canExecute: () => this.canvas.hasClipboard() && permissions().paste,
|
|
4526
|
+
execute: () => this.pasteSelection()
|
|
4527
|
+
},
|
|
4528
|
+
open: {
|
|
4529
|
+
canExecute: ({ nodes }) => nodes.length === 1 && nodes[0].openAction.length > 0,
|
|
4530
|
+
execute: ({ nodes }) => this.emit("nodeOpen", { nodes })
|
|
4531
|
+
},
|
|
4532
|
+
delete: {
|
|
4533
|
+
canExecute: ({ selection }) => permissions().deleteSelection && (selection.nodeIds.length > 0 || selection.linkIds.length > 0),
|
|
4534
|
+
execute: () => this.canvas.deleteSelection()
|
|
4535
|
+
},
|
|
4536
|
+
properties: {
|
|
4537
|
+
canExecute: ({ nodes }) => nodes.length > 0,
|
|
4538
|
+
execute: ({ nodes }) => this.emit("nodeProperties", { nodes })
|
|
4539
|
+
},
|
|
4540
|
+
help: {
|
|
4541
|
+
canExecute: ({ nodes }) => this.helpEnabled && nodes.length > 0,
|
|
4542
|
+
execute: ({ nodes }) => this.emit("nodeHelp", { nodes })
|
|
4543
|
+
}
|
|
4394
4544
|
});
|
|
4395
4545
|
}
|
|
4396
4546
|
contextActionContext() {
|
|
@@ -4558,19 +4708,26 @@ ${runtime.error}`;
|
|
|
4558
4708
|
// imported node ("Element type is missing from the palette"). Node.id
|
|
4559
4709
|
// keeps its original casing so save/load round-trips unchanged.
|
|
4560
4710
|
addNodeType(node) {
|
|
4561
|
-
const n = node instanceof Node ? node : new Node(node);
|
|
4711
|
+
const n = (node instanceof Node ? node : new Node(node)).clone();
|
|
4562
4712
|
this.nodeTypes.set(n.id.toLowerCase(), n);
|
|
4563
|
-
this.
|
|
4713
|
+
this.emitNodeTypesChanged();
|
|
4564
4714
|
}
|
|
4565
4715
|
removeNodeType(id) {
|
|
4566
4716
|
this.nodeTypes.delete(id.toLowerCase());
|
|
4567
|
-
this.
|
|
4717
|
+
this.emitNodeTypesChanged();
|
|
4568
4718
|
}
|
|
4569
4719
|
getNodeType(id) {
|
|
4570
|
-
return this.nodeTypes.get(id.toLowerCase()) ?? null;
|
|
4720
|
+
return this.nodeTypes.get(id.toLowerCase())?.clone() ?? null;
|
|
4571
4721
|
}
|
|
4572
4722
|
getNodeTypes() {
|
|
4573
|
-
return Array.from(this.nodeTypes.values());
|
|
4723
|
+
return Array.from(this.nodeTypes.values(), (node) => node.clone());
|
|
4724
|
+
}
|
|
4725
|
+
// Node definitions are mutable, so the payload has to be a copy of each one.
|
|
4726
|
+
// Building a catalog is a loop of addNodeType calls, which would make that
|
|
4727
|
+
// quadratic; skipping it while nobody listens keeps catalog construction --
|
|
4728
|
+
// where no subscriber exists yet -- as cheap as it was.
|
|
4729
|
+
emitNodeTypesChanged() {
|
|
4730
|
+
if (this.hasHandlers("nodeTypesChanged")) this.emit("nodeTypesChanged", this.getNodeTypes());
|
|
4574
4731
|
}
|
|
4575
4732
|
};
|
|
4576
4733
|
|
|
@@ -4883,60 +5040,127 @@ ${runtime.error}`;
|
|
|
4883
5040
|
});
|
|
4884
5041
|
return { nodes, nodeErrors };
|
|
4885
5042
|
}
|
|
5043
|
+
function isRecord(value) {
|
|
5044
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5045
|
+
}
|
|
5046
|
+
function asRecord(value) {
|
|
5047
|
+
return isRecord(value) ? value : void 0;
|
|
5048
|
+
}
|
|
5049
|
+
function asArray(value) {
|
|
5050
|
+
return Array.isArray(value) ? value : [];
|
|
5051
|
+
}
|
|
5052
|
+
function asString(value) {
|
|
5053
|
+
return typeof value === "string" ? value : "";
|
|
5054
|
+
}
|
|
5055
|
+
function asFiniteNumber(value) {
|
|
5056
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
5057
|
+
}
|
|
4886
5058
|
function nodeName(node) {
|
|
4887
|
-
const
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
const
|
|
4891
|
-
|
|
4892
|
-
return nameVal;
|
|
4893
|
-
if (typeof node?.Figure === "string" && node.Figure.length > 0)
|
|
4894
|
-
return node.Figure;
|
|
4895
|
-
return typeof node?.Key === "string" ? node.Key : "";
|
|
5059
|
+
const named = asRecord(asRecord(asRecord(node.Settings)?.Parameters)?.Name)?.Value;
|
|
5060
|
+
if (typeof named === "string" && named.length > 0)
|
|
5061
|
+
return named;
|
|
5062
|
+
const figure = asString(node.Figure);
|
|
5063
|
+
return figure.length > 0 ? figure : asString(node.Key);
|
|
4896
5064
|
}
|
|
4897
5065
|
function parseRawScheme(raw) {
|
|
4898
5066
|
const nodes = [];
|
|
4899
5067
|
const links = [];
|
|
4900
|
-
const
|
|
4901
|
-
const
|
|
4902
|
-
|
|
4903
|
-
const scheme = value?.Scheme;
|
|
4904
|
-
const model = scheme?.Model;
|
|
4905
|
-
if (!model)
|
|
5068
|
+
const model = asRecord(asRecord(asRecord(asRecord(raw)?.Content)?.Value)?.Scheme)?.Model;
|
|
5069
|
+
const fields = asRecord(model);
|
|
5070
|
+
if (fields === void 0)
|
|
4906
5071
|
return { nodes, links };
|
|
4907
|
-
for (const
|
|
4908
|
-
const
|
|
5072
|
+
for (const entry of asArray(fields.Nodes)) {
|
|
5073
|
+
const node = asRecord(entry);
|
|
5074
|
+
if (node === void 0)
|
|
5075
|
+
continue;
|
|
5076
|
+
const key = node.Key;
|
|
4909
5077
|
if (typeof key !== "string" || key.length === 0)
|
|
4910
5078
|
continue;
|
|
4911
5079
|
nodes.push({
|
|
4912
5080
|
id: key,
|
|
4913
|
-
typeId:
|
|
4914
|
-
name: nodeName(
|
|
4915
|
-
x:
|
|
4916
|
-
y:
|
|
5081
|
+
typeId: asString(node.TypeId),
|
|
5082
|
+
name: nodeName(node),
|
|
5083
|
+
x: asFiniteNumber(node.X),
|
|
5084
|
+
y: asFiniteNumber(node.Y)
|
|
4917
5085
|
});
|
|
4918
5086
|
}
|
|
4919
|
-
for (const
|
|
4920
|
-
const
|
|
4921
|
-
|
|
5087
|
+
for (const entry of asArray(fields.Links)) {
|
|
5088
|
+
const link = asRecord(entry);
|
|
5089
|
+
if (link === void 0)
|
|
5090
|
+
continue;
|
|
5091
|
+
const from = link.From;
|
|
5092
|
+
const to = link.To;
|
|
4922
5093
|
if (typeof from !== "string" || typeof to !== "string")
|
|
4923
5094
|
continue;
|
|
4924
5095
|
links.push({
|
|
4925
5096
|
from,
|
|
4926
|
-
fromPort:
|
|
5097
|
+
fromPort: asString(link.FromPort),
|
|
4927
5098
|
to,
|
|
4928
|
-
toPort:
|
|
5099
|
+
toPort: asString(link.ToPort)
|
|
4929
5100
|
});
|
|
4930
5101
|
}
|
|
4931
5102
|
return { nodes, links };
|
|
4932
5103
|
}
|
|
5104
|
+
function parsePalettePorts(value) {
|
|
5105
|
+
const ports = [];
|
|
5106
|
+
for (const entry of asArray(value)) {
|
|
5107
|
+
const port = asRecord(entry);
|
|
5108
|
+
if (port === void 0)
|
|
5109
|
+
continue;
|
|
5110
|
+
const key = asString(port.key);
|
|
5111
|
+
if (key.length === 0)
|
|
5112
|
+
continue;
|
|
5113
|
+
ports.push({
|
|
5114
|
+
key,
|
|
5115
|
+
name: asString(port.name),
|
|
5116
|
+
type: asString(port.type),
|
|
5117
|
+
maxLinks: asFiniteNumber(port.maxLinks),
|
|
5118
|
+
availableTypes: asArray(port.availableTypes).filter((item) => typeof item === "string"),
|
|
5119
|
+
isDynamic: port.isDynamic === true,
|
|
5120
|
+
dynamicMode: toPortDynamicMode(port.dynamicMode)
|
|
5121
|
+
});
|
|
5122
|
+
}
|
|
5123
|
+
return ports;
|
|
5124
|
+
}
|
|
5125
|
+
function parsePalette(value) {
|
|
5126
|
+
const socketTypes = [];
|
|
5127
|
+
const elements = [];
|
|
5128
|
+
const root = asRecord(value);
|
|
5129
|
+
if (root === void 0)
|
|
5130
|
+
return { socketTypes, elements };
|
|
5131
|
+
for (const entry of asArray(root.socketTypes)) {
|
|
5132
|
+
const socket = asRecord(entry);
|
|
5133
|
+
const name = asString(socket?.name);
|
|
5134
|
+
if (name.length === 0)
|
|
5135
|
+
continue;
|
|
5136
|
+
socketTypes.push({ name, color: asString(socket?.color) });
|
|
5137
|
+
}
|
|
5138
|
+
for (const entry of asArray(root.elements)) {
|
|
5139
|
+
const element = asRecord(entry);
|
|
5140
|
+
const typeId = asString(element?.typeId);
|
|
5141
|
+
if (element === void 0 || typeId.length === 0)
|
|
5142
|
+
continue;
|
|
5143
|
+
elements.push({
|
|
5144
|
+
typeId,
|
|
5145
|
+
name: asString(element.name),
|
|
5146
|
+
groupName: asString(element.groupName),
|
|
5147
|
+
icon: asString(element.icon),
|
|
5148
|
+
inPorts: parsePalettePorts(element.inPorts),
|
|
5149
|
+
outPorts: parsePalettePorts(element.outPorts)
|
|
5150
|
+
});
|
|
5151
|
+
}
|
|
5152
|
+
return { socketTypes, elements };
|
|
5153
|
+
}
|
|
4933
5154
|
async function renderScheme(div, paletteUrl, scheme, options = {}) {
|
|
4934
5155
|
return renderSchemeAtRevision(div, paletteUrl, scheme, beginRender(div), options);
|
|
4935
5156
|
}
|
|
4936
5157
|
async function renderSchemeAtRevision(div, paletteUrl, scheme, revision, options) {
|
|
4937
5158
|
if (renderRevisions.get(div) !== revision)
|
|
4938
5159
|
return null;
|
|
4939
|
-
const
|
|
5160
|
+
const response = await fetch(paletteUrl);
|
|
5161
|
+
if (!response.ok)
|
|
5162
|
+
throw new Error(`Palette request to ${paletteUrl} failed with status ${response.status}.`);
|
|
5163
|
+
const palette = parsePalette(await response.json());
|
|
4940
5164
|
if (renderRevisions.get(div) !== revision)
|
|
4941
5165
|
return null;
|
|
4942
5166
|
const catalog = buildCatalog(palette);
|
|
@@ -5100,6 +5324,14 @@ ${runtime.error}`;
|
|
|
5100
5324
|
disconnectedHostObserver.disconnect();
|
|
5101
5325
|
disconnectedHostObserver = null;
|
|
5102
5326
|
}
|
|
5327
|
+
function errorTexts(div) {
|
|
5328
|
+
const parts = (div.dataset.diagramErrors ?? "").split("|");
|
|
5329
|
+
return {
|
|
5330
|
+
load: parts[0] || "Diagram source could not be loaded.",
|
|
5331
|
+
empty: parts[1] || "Diagram is empty or malformed.",
|
|
5332
|
+
draw: parts[2] || "Diagram could not be rendered."
|
|
5333
|
+
};
|
|
5334
|
+
}
|
|
5103
5335
|
function note(div, message, revision) {
|
|
5104
5336
|
if (renderRevisions.get(div) !== revision) return;
|
|
5105
5337
|
disposeActiveRender(div);
|
|
@@ -5108,53 +5340,51 @@ ${runtime.error}`;
|
|
|
5108
5340
|
}
|
|
5109
5341
|
async function renderFromSource(div, paletteUrl, srcUrl, options = {}) {
|
|
5110
5342
|
const revision = beginRender(div);
|
|
5111
|
-
const errors = (div
|
|
5112
|
-
const [errLoad = "Diagram source could not be loaded.", errEmpty = "Diagram is empty or malformed.", errDraw = "Diagram could not be rendered."] = errors;
|
|
5343
|
+
const errors = errorTexts(div);
|
|
5113
5344
|
let raw;
|
|
5114
5345
|
try {
|
|
5115
5346
|
const resp = await fetch(srcUrl);
|
|
5116
5347
|
if (!resp.ok) {
|
|
5117
|
-
note(div,
|
|
5348
|
+
note(div, errors.load, revision);
|
|
5118
5349
|
return null;
|
|
5119
5350
|
}
|
|
5120
5351
|
const text = (await resp.text()).replace(/^/, "");
|
|
5121
5352
|
raw = JSON.parse(text);
|
|
5122
5353
|
} catch {
|
|
5123
|
-
note(div,
|
|
5354
|
+
note(div, errors.load, revision);
|
|
5124
5355
|
return null;
|
|
5125
5356
|
}
|
|
5126
5357
|
const scheme = parseRawScheme(raw);
|
|
5127
5358
|
if (scheme.nodes.length === 0) {
|
|
5128
|
-
note(div,
|
|
5359
|
+
note(div, errors.empty, revision);
|
|
5129
5360
|
return null;
|
|
5130
5361
|
}
|
|
5131
5362
|
try {
|
|
5132
5363
|
return await renderSchemeAtRevision(div, paletteUrl, scheme, revision, options);
|
|
5133
5364
|
} catch {
|
|
5134
|
-
note(div,
|
|
5365
|
+
note(div, errors.draw, revision);
|
|
5135
5366
|
return null;
|
|
5136
5367
|
}
|
|
5137
5368
|
}
|
|
5138
5369
|
async function renderFromInline(div, paletteUrl, json, options = {}) {
|
|
5139
5370
|
const revision = beginRender(div);
|
|
5140
|
-
const errors = (div
|
|
5141
|
-
const [, errEmpty = "Diagram is empty or malformed.", errDraw = "Diagram could not be rendered."] = errors;
|
|
5371
|
+
const errors = errorTexts(div);
|
|
5142
5372
|
let raw;
|
|
5143
5373
|
try {
|
|
5144
5374
|
raw = JSON.parse(json);
|
|
5145
5375
|
} catch {
|
|
5146
|
-
note(div,
|
|
5376
|
+
note(div, errors.empty, revision);
|
|
5147
5377
|
return null;
|
|
5148
5378
|
}
|
|
5149
5379
|
const scheme = parseRawScheme(raw);
|
|
5150
5380
|
if (scheme.nodes.length === 0) {
|
|
5151
|
-
note(div,
|
|
5381
|
+
note(div, errors.empty, revision);
|
|
5152
5382
|
return null;
|
|
5153
5383
|
}
|
|
5154
5384
|
try {
|
|
5155
5385
|
return await renderSchemeAtRevision(div, paletteUrl, scheme, revision, options);
|
|
5156
5386
|
} catch {
|
|
5157
|
-
note(div,
|
|
5387
|
+
note(div, errors.draw, revision);
|
|
5158
5388
|
return null;
|
|
5159
5389
|
}
|
|
5160
5390
|
}
|