@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
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
// selection, delete, zoom/pan, load/save round-trip.
|
|
6
6
|
import { createDiagramDocument, parseDiagramDocument } from './core/document.js';
|
|
7
7
|
import { DiagramCommandHistory } from './core/history.js';
|
|
8
|
+
import { setJsonKey } from './core/json.js';
|
|
9
|
+
import { toPortDynamicMode } from './core/model.js';
|
|
8
10
|
import { cloneDiagramRuntimeState, createDiagramNodeRuntimeState, createDiagramPortRuntimeState, createDiagramRuntimeState, createEditableDiagramPermissions, createReadOnlyDiagramPermissions, } from './core/state.js';
|
|
9
11
|
const SCREEN_RENDER_OPTIONS = {
|
|
10
12
|
grid: true,
|
|
@@ -26,7 +28,7 @@ function copyJsonObject(value) {
|
|
|
26
28
|
return {};
|
|
27
29
|
const result = {};
|
|
28
30
|
for (const [key, item] of Object.entries(value))
|
|
29
|
-
result
|
|
31
|
+
setJsonKey(result, key, copyJsonValue(item));
|
|
30
32
|
return result;
|
|
31
33
|
}
|
|
32
34
|
function copyParameters(value) {
|
|
@@ -65,7 +67,7 @@ export class PortModel {
|
|
|
65
67
|
this.maxLinks = typeof init.maxLinks === 'number' ? init.maxLinks : 0;
|
|
66
68
|
this.availableTypes = [...(init.availableTypes ?? [])];
|
|
67
69
|
this.isDynamic = init.isDynamic ?? false;
|
|
68
|
-
this.dynamicMode = init.dynamicMode
|
|
70
|
+
this.dynamicMode = toPortDynamicMode(init.dynamicMode);
|
|
69
71
|
this.isSibling = init.isSibling ?? false;
|
|
70
72
|
this.metadata = copyJsonObject(init.metadata);
|
|
71
73
|
}
|
|
@@ -141,11 +143,12 @@ export class NodeModel {
|
|
|
141
143
|
}
|
|
142
144
|
}
|
|
143
145
|
export class LinkModel {
|
|
144
|
-
constructor(from, fromPort, to, toPort, id = '', metadata) {
|
|
146
|
+
constructor(from, fromPort, to, toPort, id = '', metadata, style = 'solid') {
|
|
145
147
|
this.from = from;
|
|
146
148
|
this.fromPort = fromPort;
|
|
147
149
|
this.to = to;
|
|
148
150
|
this.toPort = toPort;
|
|
151
|
+
this.style = style;
|
|
149
152
|
this.id = id;
|
|
150
153
|
this.metadata = copyJsonObject(metadata);
|
|
151
154
|
}
|
|
@@ -157,14 +160,13 @@ export class LinkModel {
|
|
|
157
160
|
fromPort: this.fromPort,
|
|
158
161
|
to: this.to,
|
|
159
162
|
toPort: this.toPort,
|
|
163
|
+
style: this.style,
|
|
160
164
|
metadata: copyJsonObject(this.metadata),
|
|
161
165
|
};
|
|
162
166
|
}
|
|
163
167
|
}
|
|
164
|
-
const HEADER_H = 22;
|
|
165
168
|
const PORT_R = 6;
|
|
166
169
|
const PORT_ROW_H = 20;
|
|
167
|
-
const NODE_PAD = 10;
|
|
168
170
|
const RELINK_HANDLE_PX = 8; // WPF relink adornment is an 8x8 diamond
|
|
169
171
|
const RELINK_DRAG_THRESHOLD_PX = 4;
|
|
170
172
|
const PORT_SQ = 9; // socket square size — sits outside the node
|
|
@@ -219,6 +221,7 @@ export class Diagram {
|
|
|
219
221
|
constructor(opts) {
|
|
220
222
|
this.nodes = [];
|
|
221
223
|
this.links = [];
|
|
224
|
+
this.zones = [];
|
|
222
225
|
this.idSeq = 1;
|
|
223
226
|
this.linkSeq = 1;
|
|
224
227
|
this.documentMetadata = {};
|
|
@@ -241,8 +244,6 @@ export class Diagram {
|
|
|
241
244
|
this.dragNode = null;
|
|
242
245
|
this.dragStart = []; // group-drag origin
|
|
243
246
|
this.dragAnchor = { wx: 0, wy: 0 };
|
|
244
|
-
this.dragDX = 0;
|
|
245
|
-
this.dragDY = 0;
|
|
246
247
|
this.panning = false;
|
|
247
248
|
this.panX = 0;
|
|
248
249
|
this.panY = 0;
|
|
@@ -567,7 +568,7 @@ export class Diagram {
|
|
|
567
568
|
if (!validation.allowed)
|
|
568
569
|
return false;
|
|
569
570
|
}
|
|
570
|
-
const link = new LinkModel(init.from, init.fromPort, init.to, init.toPort, init.id ?? this.nextLinkId(), init.metadata);
|
|
571
|
+
const link = new LinkModel(init.from, init.fromPort, init.to, init.toPort, init.id ?? this.nextLinkId(), init.metadata, init.style ?? 'solid');
|
|
571
572
|
if (this.links.some((l) => l.id === link.id))
|
|
572
573
|
return false;
|
|
573
574
|
this.links.splice(clamp(Math.trunc(index), 0, this.links.length), 0, link);
|
|
@@ -894,7 +895,7 @@ export class Diagram {
|
|
|
894
895
|
if (value === undefined)
|
|
895
896
|
delete node.paramValues[name];
|
|
896
897
|
else
|
|
897
|
-
node.paramValues
|
|
898
|
+
setJsonKey(node.paramValues, name, value);
|
|
898
899
|
});
|
|
899
900
|
}
|
|
900
901
|
setShowNodeMessages(show) {
|
|
@@ -977,7 +978,7 @@ export class Diagram {
|
|
|
977
978
|
if (this.links.some((existing) => existing.id === id)) {
|
|
978
979
|
throw new Error(`ssdiagram: duplicate link id "${id}"`);
|
|
979
980
|
}
|
|
980
|
-
const model = new LinkModel(link.from, link.fromPort, link.to, link.toPort, id, link.metadata);
|
|
981
|
+
const model = new LinkModel(link.from, link.fromPort, link.to, link.toPort, id, link.metadata, link.style ?? 'solid');
|
|
981
982
|
if (!this.links.some((existing) => existing.key() === model.key()))
|
|
982
983
|
this.links.push(model);
|
|
983
984
|
}
|
|
@@ -985,7 +986,7 @@ export class Diagram {
|
|
|
985
986
|
if (node.loadError.length === 0)
|
|
986
987
|
continue;
|
|
987
988
|
const state = createDiagramNodeRuntimeState();
|
|
988
|
-
state.
|
|
989
|
+
state.errors.load = { kind: 'load', message: node.loadError, pulse: ++this.runtimePulse };
|
|
989
990
|
this.runtimeState.nodes[node.id] = state;
|
|
990
991
|
}
|
|
991
992
|
if (Object.keys(this.runtimeState.nodes).length > 0)
|
|
@@ -1009,8 +1010,10 @@ export class Diagram {
|
|
|
1009
1010
|
fromPort: link.from.portId,
|
|
1010
1011
|
to: link.to.nodeId,
|
|
1011
1012
|
toPort: link.to.portId,
|
|
1013
|
+
style: link.style,
|
|
1012
1014
|
metadata: link.metadata,
|
|
1013
1015
|
})));
|
|
1016
|
+
this.zones = document.zones.map((zone) => ({ ...zone, metadata: copyJsonObject(zone.metadata) }));
|
|
1014
1017
|
this.documentMetadata = copyJsonObject(document.metadata);
|
|
1015
1018
|
}
|
|
1016
1019
|
saveDocument() {
|
|
@@ -1020,8 +1023,10 @@ export class Diagram {
|
|
|
1020
1023
|
id: link.id,
|
|
1021
1024
|
from: { nodeId: link.from, portId: link.fromPort },
|
|
1022
1025
|
to: { nodeId: link.to, portId: link.toPort },
|
|
1026
|
+
style: link.style,
|
|
1023
1027
|
metadata: link.metadata,
|
|
1024
1028
|
})),
|
|
1029
|
+
zones: this.zones.map((zone) => ({ ...zone, metadata: copyJsonObject(zone.metadata) })),
|
|
1025
1030
|
metadata: this.documentMetadata,
|
|
1026
1031
|
});
|
|
1027
1032
|
}
|
|
@@ -1190,14 +1195,16 @@ export class Diagram {
|
|
|
1190
1195
|
else if (previous.globalError?.pulse !== this.runtimeState.globalError.pulse) {
|
|
1191
1196
|
this.globalErrorFlashStart = performance.now();
|
|
1192
1197
|
}
|
|
1193
|
-
this.runtimePulse = Math.max(this.runtimePulse, this.runtimeState.globalError?.pulse ?? 0, ...Object.values(this.runtimeState.nodes)
|
|
1198
|
+
this.runtimePulse = Math.max(this.runtimePulse, this.runtimeState.globalError?.pulse ?? 0, ...Object.values(this.runtimeState.nodes)
|
|
1199
|
+
.flatMap((node) => [node.errors.runtime?.pulse ?? 0, node.errors.load?.pulse ?? 0]));
|
|
1194
1200
|
for (const node of this.nodes) {
|
|
1195
|
-
const
|
|
1196
|
-
const
|
|
1197
|
-
|
|
1198
|
-
node.
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
+
const errors = this.runtimeState.nodes[node.id]?.errors;
|
|
1202
|
+
const previousRuntime = previous.nodes[node.id]?.errors.runtime;
|
|
1203
|
+
const runtimeError = errors?.runtime;
|
|
1204
|
+
node.runtimeError = runtimeError?.message ?? '';
|
|
1205
|
+
node.loadError = errors?.load?.message ?? '';
|
|
1206
|
+
if (runtimeError !== undefined) {
|
|
1207
|
+
if (previousRuntime === undefined || previousRuntime.pulse !== runtimeError.pulse) {
|
|
1201
1208
|
node.errorFlashStart = performance.now();
|
|
1202
1209
|
}
|
|
1203
1210
|
}
|
|
@@ -1257,7 +1264,14 @@ export class Diagram {
|
|
|
1257
1264
|
const state = this.getRuntimeState();
|
|
1258
1265
|
const nodeState = state.nodes[id] ?? createDiagramNodeRuntimeState();
|
|
1259
1266
|
state.nodes[id] = nodeState;
|
|
1260
|
-
|
|
1267
|
+
// Only this kind's slot is written, so an error of the other kind that is
|
|
1268
|
+
// already on the node survives.
|
|
1269
|
+
if (message.length === 0)
|
|
1270
|
+
delete nodeState.errors[kind];
|
|
1271
|
+
else if (kind === 'load')
|
|
1272
|
+
nodeState.errors.load = { kind, message, pulse: ++this.runtimePulse };
|
|
1273
|
+
else
|
|
1274
|
+
nodeState.errors.runtime = { kind, message, pulse: ++this.runtimePulse };
|
|
1261
1275
|
this.runtimeState = state;
|
|
1262
1276
|
this.emitRuntimeStateChanged();
|
|
1263
1277
|
return true;
|
|
@@ -1274,12 +1288,11 @@ export class Diagram {
|
|
|
1274
1288
|
node.loadError = '';
|
|
1275
1289
|
const state = this.getRuntimeState();
|
|
1276
1290
|
const nodeState = state.nodes[id];
|
|
1277
|
-
if (nodeState !== undefined
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
: null;
|
|
1291
|
+
if (nodeState !== undefined) {
|
|
1292
|
+
if (kind === undefined || kind === 'runtime')
|
|
1293
|
+
delete nodeState.errors.runtime;
|
|
1294
|
+
if (kind === undefined || kind === 'load')
|
|
1295
|
+
delete nodeState.errors.load;
|
|
1283
1296
|
}
|
|
1284
1297
|
this.runtimeState = state;
|
|
1285
1298
|
this.emitRuntimeStateChanged();
|
|
@@ -2382,7 +2395,10 @@ export class Diagram {
|
|
|
2382
2395
|
if (options.grid)
|
|
2383
2396
|
this.drawGrid();
|
|
2384
2397
|
ctx.setTransform(this.dpr * this.scale, 0, 0, this.dpr * this.scale, this.dpr * this.offX, this.dpr * (this.offY + introDy));
|
|
2385
|
-
//
|
|
2398
|
+
// Zones sit under everything: they say where things are, and a place that painted over
|
|
2399
|
+
// its occupants would be worse than no place at all.
|
|
2400
|
+
this.drawZones();
|
|
2401
|
+
// Links next. Each link jumps over the verticals of the links
|
|
2386
2402
|
// drawn before it (deterministic bridge — Link.JumpOver parity).
|
|
2387
2403
|
const prior = []; // segments of links already drawn
|
|
2388
2404
|
const hoveredNode = this.hoverPort?.node ?? this.hoverNode;
|
|
@@ -2404,7 +2420,7 @@ export class Diagram {
|
|
|
2404
2420
|
const color = state === 'sel' ? this.selectionColor() : state === 'hov' ? this.linkHoverColor() : baseColor;
|
|
2405
2421
|
const width = state === 'sel' ? 3 : state === 'hov' ? 2.6 : 2;
|
|
2406
2422
|
const pts = this.routeLink(a, b, new Set([l.from, l.to]));
|
|
2407
|
-
this.strokeRoute(pts, color, width, prior);
|
|
2423
|
+
this.strokeRoute(pts, color, width, prior, l.style);
|
|
2408
2424
|
this.drawArrow(pts, color);
|
|
2409
2425
|
for (let k = 1; k < pts.length; k += 1) {
|
|
2410
2426
|
const [x1, y1] = pts[k - 1];
|
|
@@ -2571,6 +2587,37 @@ export class Diagram {
|
|
|
2571
2587
|
}
|
|
2572
2588
|
return lines.length > 0 ? lines : [''];
|
|
2573
2589
|
}
|
|
2590
|
+
drawZones() {
|
|
2591
|
+
if (this.zones.length === 0)
|
|
2592
|
+
return;
|
|
2593
|
+
const ctx = this.ctx;
|
|
2594
|
+
for (const zone of this.zones) {
|
|
2595
|
+
const tint = zone.color.length > 0 ? zone.color : (this.opts.zoneColor ?? '#8a8f98');
|
|
2596
|
+
ctx.save();
|
|
2597
|
+
roundRect(ctx, zone.x, zone.y, zone.width, zone.height, 10);
|
|
2598
|
+
ctx.globalAlpha = 0.12;
|
|
2599
|
+
ctx.fillStyle = tint;
|
|
2600
|
+
ctx.fill();
|
|
2601
|
+
ctx.globalAlpha = 0.5;
|
|
2602
|
+
ctx.setLineDash([10, 6]);
|
|
2603
|
+
ctx.lineWidth = 1.5;
|
|
2604
|
+
ctx.strokeStyle = tint;
|
|
2605
|
+
ctx.stroke();
|
|
2606
|
+
ctx.restore();
|
|
2607
|
+
if (zone.name.length === 0)
|
|
2608
|
+
continue;
|
|
2609
|
+
ctx.save();
|
|
2610
|
+
ctx.globalAlpha = 0.75;
|
|
2611
|
+
ctx.fillStyle = tint;
|
|
2612
|
+
ctx.font = '600 12px Segoe UI, Tahoma, sans-serif';
|
|
2613
|
+
ctx.textAlign = 'left';
|
|
2614
|
+
ctx.textBaseline = 'top';
|
|
2615
|
+
// Inside the top-left corner: a caption outside the box would collide with whatever
|
|
2616
|
+
// sits above it, and the corner is the one spot a rectangle always has to spare.
|
|
2617
|
+
ctx.fillText(zone.name, zone.x + 12, zone.y + 9, Math.max(0, zone.width - 24));
|
|
2618
|
+
ctx.restore();
|
|
2619
|
+
}
|
|
2620
|
+
}
|
|
2574
2621
|
drawGrid() {
|
|
2575
2622
|
const ctx = this.ctx;
|
|
2576
2623
|
const step = this.gridSize * this.scale;
|
|
@@ -2712,10 +2759,13 @@ export class Diagram {
|
|
|
2712
2759
|
// Symmetric jump-over: any segment of THIS link hops the perpendicular
|
|
2713
2760
|
// segments of links drawn before it (H over earlier V, V over earlier
|
|
2714
2761
|
// H). Exactly one bridge per real crossing, consistent everywhere.
|
|
2715
|
-
strokeRoute(pts, color, width, prior) {
|
|
2762
|
+
strokeRoute(pts, color, width, prior, style = 'solid') {
|
|
2716
2763
|
const ctx = this.ctx;
|
|
2717
2764
|
ctx.strokeStyle = color;
|
|
2718
2765
|
ctx.lineWidth = width;
|
|
2766
|
+
// The dash is measured in world units, so it keeps its rhythm as the canvas is zoomed.
|
|
2767
|
+
if (style === 'dashed')
|
|
2768
|
+
ctx.setLineDash([9, 6]);
|
|
2719
2769
|
ctx.lineJoin = 'miter';
|
|
2720
2770
|
ctx.lineCap = 'butt';
|
|
2721
2771
|
ctx.beginPath();
|
|
@@ -2775,6 +2825,7 @@ export class Diagram {
|
|
|
2775
2825
|
}
|
|
2776
2826
|
}
|
|
2777
2827
|
ctx.stroke();
|
|
2828
|
+
ctx.setLineDash([]);
|
|
2778
2829
|
}
|
|
2779
2830
|
drawArrow(pts, color) {
|
|
2780
2831
|
const n = pts.length;
|