@stocksharp/diagram 0.5.0 → 0.7.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/README.md +72 -11
- package/dist/esm/canvas-renderer.js +210 -54
- package/dist/esm/canvas-renderer.js.map +1 -1
- package/dist/esm/core/document.js +24 -12
- package/dist/esm/core/document.js.map +1 -1
- package/dist/esm/core/history.js +12 -3
- package/dist/esm/core/history.js.map +1 -1
- package/dist/esm/core/json.js +10 -0
- package/dist/esm/core/json.js.map +1 -1
- package/dist/esm/diagram/context-menu.js +93 -28
- package/dist/esm/diagram/context-menu.js.map +1 -1
- package/dist/esm/diagram/event-emitter.js +12 -1
- package/dist/esm/diagram/event-emitter.js.map +1 -1
- package/dist/esm/diagram/palette.js +9 -1
- package/dist/esm/diagram/palette.js.map +1 -1
- package/dist/esm/diagram/stocksharp-diagram.js +50 -8
- package/dist/esm/diagram/stocksharp-diagram.js.map +1 -1
- package/dist/esm/diagram/types.js +29 -2
- package/dist/esm/diagram/types.js.map +1 -1
- package/dist/esm/embed.js +70 -14
- package/dist/esm/embed.js.map +1 -1
- package/dist/esm/i18n.js +15 -4
- package/dist/esm/i18n.js.map +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/svg-surface.js +10 -2
- package/dist/esm/svg-surface.js.map +1 -1
- package/dist/ssdiagram.js +376 -122
- package/dist/ssdiagram.js.map +3 -3
- package/dist/types/canvas-renderer.d.ts +20 -1
- package/dist/types/canvas-renderer.d.ts.map +1 -1
- package/dist/types/core/document.d.ts.map +1 -1
- package/dist/types/core/history.d.ts +1 -0
- package/dist/types/core/history.d.ts.map +1 -1
- package/dist/types/core/json.d.ts +8 -0
- package/dist/types/core/json.d.ts.map +1 -1
- package/dist/types/diagram/api.d.ts +7 -1
- package/dist/types/diagram/api.d.ts.map +1 -1
- package/dist/types/diagram/context-menu.d.ts +8 -0
- package/dist/types/diagram/context-menu.d.ts.map +1 -1
- package/dist/types/diagram/event-emitter.d.ts.map +1 -1
- package/dist/types/diagram/palette.d.ts.map +1 -1
- package/dist/types/diagram/stocksharp-diagram.d.ts +7 -0
- package/dist/types/diagram/stocksharp-diagram.d.ts.map +1 -1
- package/dist/types/diagram/types.d.ts +5 -0
- package/dist/types/diagram/types.d.ts.map +1 -1
- package/dist/types/embed.d.ts.map +1 -1
- package/dist/types/i18n.d.ts +10 -0
- package/dist/types/i18n.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/svg-surface.d.ts +1 -1
- package/dist/types/svg-surface.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/canvas-renderer.ts +190 -45
- package/src/core/document.ts +25 -12
- package/src/core/history.ts +13 -3
- package/src/core/json.ts +11 -0
- package/src/diagram/api.ts +7 -0
- package/src/diagram/context-menu.ts +112 -27
- package/src/diagram/event-emitter.ts +10 -1
- package/src/diagram/palette.ts +9 -2
- package/src/diagram/stocksharp-diagram.ts +50 -8
- package/src/diagram/types.ts +29 -3
- package/src/embed.ts +78 -14
- package/src/i18n.ts +36 -6
- package/src/index.ts +5 -0
- package/src/svg-surface.ts +11 -2
package/dist/ssdiagram.js
CHANGED
|
@@ -67,6 +67,9 @@ var SSDiagram = (() => {
|
|
|
67
67
|
}
|
|
68
68
|
target[key] = value;
|
|
69
69
|
}
|
|
70
|
+
function getJsonKey(target, key) {
|
|
71
|
+
return Object.prototype.hasOwnProperty.call(target, key) ? target[key] : void 0;
|
|
72
|
+
}
|
|
70
73
|
|
|
71
74
|
// src/core/model.ts
|
|
72
75
|
function toPortDynamicMode(value) {
|
|
@@ -110,15 +113,8 @@ var SSDiagram = (() => {
|
|
|
110
113
|
return normalizeLink({ ...link, id }, `$.links[${index}]`);
|
|
111
114
|
});
|
|
112
115
|
const rawZones = mapElements(input.zones, "$.zones");
|
|
113
|
-
const
|
|
114
|
-
|
|
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
|
-
});
|
|
116
|
+
const zones = rawZones.map((zone, index) => normalizeZone(zone, `$.zones[${index}]`));
|
|
117
|
+
validateUniqueZones(zones);
|
|
122
118
|
const document2 = {
|
|
123
119
|
version: DIAGRAM_DOCUMENT_VERSION,
|
|
124
120
|
nodes,
|
|
@@ -239,8 +235,9 @@ var SSDiagram = (() => {
|
|
|
239
235
|
return {
|
|
240
236
|
id: requireIdentifier(input.id, `${path}.id`),
|
|
241
237
|
name: requireString(input.name, `${path}.name`),
|
|
242
|
-
|
|
243
|
-
|
|
238
|
+
// Finite, like node coordinates: NaN survives every arithmetic check and writes as null.
|
|
239
|
+
x: requireFiniteNumber(input.x, `${path}.x`),
|
|
240
|
+
y: requireFiniteNumber(input.y, `${path}.y`),
|
|
244
241
|
width: requirePositiveSize(input.width, `${path}.width`),
|
|
245
242
|
height: requirePositiveSize(input.height, `${path}.height`),
|
|
246
243
|
color: input.color === void 0 || input.color === null ? "" : requireString(input.color, `${path}.color`),
|
|
@@ -248,7 +245,7 @@ var SSDiagram = (() => {
|
|
|
248
245
|
};
|
|
249
246
|
}
|
|
250
247
|
function requirePositiveSize(value, path) {
|
|
251
|
-
const size =
|
|
248
|
+
const size = requireFiniteNumber(value, path);
|
|
252
249
|
if (!(size > 0)) throw new DiagramDocumentError("size must be greater than zero", path);
|
|
253
250
|
return size;
|
|
254
251
|
}
|
|
@@ -330,6 +327,14 @@ var SSDiagram = (() => {
|
|
|
330
327
|
portId: requireIdentifier(endpoint.portId, `${path}.portId`)
|
|
331
328
|
};
|
|
332
329
|
}
|
|
330
|
+
function validateUniqueZones(zones) {
|
|
331
|
+
const seen = /* @__PURE__ */ new Set();
|
|
332
|
+
for (let index = 0; index < zones.length; index++) {
|
|
333
|
+
const zone = zones[index];
|
|
334
|
+
if (seen.has(zone.id)) throw new DiagramDocumentError(`duplicate zone id "${zone.id}"`, `$.zones[${index}].id`);
|
|
335
|
+
seen.add(zone.id);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
333
338
|
function validateDocument(document2) {
|
|
334
339
|
const nodes = /* @__PURE__ */ new Map();
|
|
335
340
|
for (let index = 0; index < document2.nodes.length; index++) {
|
|
@@ -360,6 +365,7 @@ var SSDiagram = (() => {
|
|
|
360
365
|
if (endpoints.has(key)) throw new DiagramDocumentError("duplicate link endpoints", path);
|
|
361
366
|
endpoints.add(key);
|
|
362
367
|
}
|
|
368
|
+
validateUniqueZones(document2.zones);
|
|
363
369
|
}
|
|
364
370
|
function validateUniquePorts(ports, path) {
|
|
365
371
|
const ids = /* @__PURE__ */ new Set();
|
|
@@ -573,7 +579,13 @@ var SSDiagram = (() => {
|
|
|
573
579
|
throw error;
|
|
574
580
|
}
|
|
575
581
|
}
|
|
582
|
+
assertNoOpenTransaction(operation) {
|
|
583
|
+
if (this.transactions.length > 0) {
|
|
584
|
+
throw new Error(`Cannot ${operation} command history inside a transaction.`);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
576
587
|
undo() {
|
|
588
|
+
this.assertNoOpenTransaction("undo");
|
|
577
589
|
const command = this.undoCommands.pop();
|
|
578
590
|
if (command === void 0) return false;
|
|
579
591
|
try {
|
|
@@ -587,6 +599,7 @@ var SSDiagram = (() => {
|
|
|
587
599
|
return true;
|
|
588
600
|
}
|
|
589
601
|
redo() {
|
|
602
|
+
this.assertNoOpenTransaction("redo");
|
|
590
603
|
const command = this.redoCommands.pop();
|
|
591
604
|
if (command === void 0) return false;
|
|
592
605
|
try {
|
|
@@ -600,9 +613,7 @@ var SSDiagram = (() => {
|
|
|
600
613
|
return true;
|
|
601
614
|
}
|
|
602
615
|
clear() {
|
|
603
|
-
|
|
604
|
-
throw new Error("Cannot clear command history inside a transaction.");
|
|
605
|
-
}
|
|
616
|
+
this.assertNoOpenTransaction("clear");
|
|
606
617
|
this.undoCommands.length = 0;
|
|
607
618
|
this.redoCommands.length = 0;
|
|
608
619
|
this.notify();
|
|
@@ -1045,12 +1056,16 @@ var SSDiagram = (() => {
|
|
|
1045
1056
|
this.emit(`<rect x="${n(x)}" y="${n(y)}" width="${n(w)}" height="${n(h)}" fill="none" ${this.strokeAttrs()}/>`);
|
|
1046
1057
|
}
|
|
1047
1058
|
// ===== text ==========================================================
|
|
1048
|
-
fillText(text, x, y) {
|
|
1059
|
+
fillText(text, x, y, maxWidth) {
|
|
1049
1060
|
if (text === "") return;
|
|
1050
1061
|
const anchor = ANCHOR[this.state.textAlign] ?? "start";
|
|
1051
1062
|
const baseline = BASELINE[this.state.textBaseline] ?? "alphabetic";
|
|
1063
|
+
let constraint = "";
|
|
1064
|
+
if (maxWidth !== void 0 && maxWidth > 0 && this.measureText(text).width > maxWidth) {
|
|
1065
|
+
constraint = ` textLength="${n(maxWidth)}" lengthAdjust="spacingAndGlyphs"`;
|
|
1066
|
+
}
|
|
1052
1067
|
this.emit(
|
|
1053
|
-
`<text x="${n(x)}" y="${n(y)}" style="font:${escapeXml(this.state.font)}" text-anchor="${anchor}" dominant-baseline="${baseline}" ${this.fillAttrs()}>${escapeXml(text)}</text>`
|
|
1068
|
+
`<text x="${n(x)}" y="${n(y)}" style="font:${escapeXml(this.state.font)}" text-anchor="${anchor}" dominant-baseline="${baseline}"${constraint} ${this.fillAttrs()}>${escapeXml(text)}</text>`
|
|
1054
1069
|
);
|
|
1055
1070
|
}
|
|
1056
1071
|
measureText(text) {
|
|
@@ -1358,15 +1373,20 @@ var SSDiagram = (() => {
|
|
|
1358
1373
|
this.introStart = null;
|
|
1359
1374
|
this.overviewVisible = true;
|
|
1360
1375
|
this.ovDragging = false;
|
|
1376
|
+
this.historyShortcut = (direction) => {
|
|
1377
|
+
if (direction === "undo") this.undo();
|
|
1378
|
+
else this.redo();
|
|
1379
|
+
};
|
|
1361
1380
|
this.ovGeo = null;
|
|
1362
1381
|
this.validator = null;
|
|
1363
1382
|
this.handlers = /* @__PURE__ */ new Map();
|
|
1364
1383
|
this.opts = opts;
|
|
1384
|
+
if (opts.typeColors !== void 0) this.setTypeColorsInternal(opts.typeColors);
|
|
1365
1385
|
this.host = opts.host;
|
|
1366
1386
|
this.gridSnapEnabled = opts.gridSnap ?? false;
|
|
1367
1387
|
this.gridSize = this.normalizeGridSize(opts.gridSize ?? DEFAULT_GRID_SIZE);
|
|
1368
|
-
this.history = new DiagramCommandHistory((
|
|
1369
|
-
this.emit("undoStackChanged", { canUndo, canRedo });
|
|
1388
|
+
this.history = new DiagramCommandHistory(() => {
|
|
1389
|
+
this.emit("undoStackChanged", { canUndo: this.canUndo(), canRedo: this.canRedo() });
|
|
1370
1390
|
});
|
|
1371
1391
|
this.canvas = document.createElement("canvas");
|
|
1372
1392
|
this.canvas.style.display = "block";
|
|
@@ -1749,6 +1769,8 @@ var SSDiagram = (() => {
|
|
|
1749
1769
|
clear() {
|
|
1750
1770
|
this.nodes = [];
|
|
1751
1771
|
this.links = [];
|
|
1772
|
+
this.zones = [];
|
|
1773
|
+
const hadSelection = this.selectedNode !== null || this.selectedNodes.size > 0 || this.selectedLink !== null || this.selectedPort !== null;
|
|
1752
1774
|
this.selectedNode = null;
|
|
1753
1775
|
this.selectedNodes.clear();
|
|
1754
1776
|
this.selectedLink = null;
|
|
@@ -1758,6 +1780,7 @@ var SSDiagram = (() => {
|
|
|
1758
1780
|
this.runtimePulse = 0;
|
|
1759
1781
|
this.globalErrorFlashStart = null;
|
|
1760
1782
|
this.history.clear();
|
|
1783
|
+
if (hadSelection) this.emitSelectionChanged();
|
|
1761
1784
|
this.emitRuntimeStateChanged();
|
|
1762
1785
|
}
|
|
1763
1786
|
relink(linkId, next) {
|
|
@@ -1976,6 +1999,20 @@ var SSDiagram = (() => {
|
|
|
1976
1999
|
this.clear();
|
|
1977
2000
|
this.idSeq = 1;
|
|
1978
2001
|
this.linkSeq = 1;
|
|
2002
|
+
const seenNodeIds = /* @__PURE__ */ new Set();
|
|
2003
|
+
for (const node of nodes) {
|
|
2004
|
+
const id = node.id ?? "";
|
|
2005
|
+
if (id === "") continue;
|
|
2006
|
+
if (seenNodeIds.has(id)) throw new Error(`ssdiagram: duplicate node id "${id}"`);
|
|
2007
|
+
seenNodeIds.add(id);
|
|
2008
|
+
}
|
|
2009
|
+
const seenLinkIds = /* @__PURE__ */ new Set();
|
|
2010
|
+
for (const link of links) {
|
|
2011
|
+
const id = link.id ?? "";
|
|
2012
|
+
if (id === "") continue;
|
|
2013
|
+
if (seenLinkIds.has(id)) throw new Error(`ssdiagram: duplicate link id "${id}"`);
|
|
2014
|
+
seenLinkIds.add(id);
|
|
2015
|
+
}
|
|
1979
2016
|
for (const node of nodes) {
|
|
1980
2017
|
const id = node.id ?? this.nextNodeId();
|
|
1981
2018
|
this.doAddNode({ ...node, id });
|
|
@@ -1992,7 +2029,7 @@ var SSDiagram = (() => {
|
|
|
1992
2029
|
if (node.loadError.length === 0) continue;
|
|
1993
2030
|
const state = createDiagramNodeRuntimeState();
|
|
1994
2031
|
state.errors.load = { kind: "load", message: node.loadError, pulse: ++this.runtimePulse };
|
|
1995
|
-
this.runtimeState.nodes
|
|
2032
|
+
setJsonKey(this.runtimeState.nodes, node.id, state);
|
|
1996
2033
|
}
|
|
1997
2034
|
if (Object.keys(this.runtimeState.nodes).length > 0) this.emitRuntimeStateChanged();
|
|
1998
2035
|
this.history.clear();
|
|
@@ -2003,7 +2040,15 @@ var SSDiagram = (() => {
|
|
|
2003
2040
|
save() {
|
|
2004
2041
|
return {
|
|
2005
2042
|
nodes: this.nodes.map((node) => node.toInit(false)),
|
|
2006
|
-
links: this.links.map((l) => ({
|
|
2043
|
+
links: this.links.map((l) => ({
|
|
2044
|
+
id: l.id,
|
|
2045
|
+
from: l.from,
|
|
2046
|
+
fromPort: l.fromPort,
|
|
2047
|
+
to: l.to,
|
|
2048
|
+
toPort: l.toPort,
|
|
2049
|
+
style: l.style,
|
|
2050
|
+
metadata: copyJsonObject(l.metadata)
|
|
2051
|
+
}))
|
|
2007
2052
|
};
|
|
2008
2053
|
}
|
|
2009
2054
|
loadDocument(source) {
|
|
@@ -2073,7 +2118,20 @@ var SSDiagram = (() => {
|
|
|
2073
2118
|
overviewVisible: this.overviewVisible
|
|
2074
2119
|
};
|
|
2075
2120
|
}
|
|
2121
|
+
/**
|
|
2122
|
+
* Replaces what Ctrl+Z / Ctrl+Y do. Passing null restores the built-in behaviour, which is
|
|
2123
|
+
* plain undo()/redo() -- so a renderer used on its own keeps its keyboard history.
|
|
2124
|
+
*/
|
|
2125
|
+
setHistoryShortcutHandler(handler) {
|
|
2126
|
+
this.historyShortcut = handler ?? ((direction) => {
|
|
2127
|
+
if (direction === "undo") this.undo();
|
|
2128
|
+
else this.redo();
|
|
2129
|
+
});
|
|
2130
|
+
}
|
|
2076
2131
|
setViewState(state) {
|
|
2132
|
+
if (!Number.isFinite(state.zoom) || !Number.isFinite(state.panX) || !Number.isFinite(state.panY)) {
|
|
2133
|
+
throw new RangeError("ssdiagram: view state zoom and pan must be finite numbers");
|
|
2134
|
+
}
|
|
2077
2135
|
this.scale = clamp(state.zoom, ZOOM_MIN, ZOOM_MAX);
|
|
2078
2136
|
this.offX = state.panX;
|
|
2079
2137
|
this.offY = state.panY;
|
|
@@ -2101,6 +2159,7 @@ var SSDiagram = (() => {
|
|
|
2101
2159
|
throw new RangeError(`ssdiagram: unsupported screenshot scope "${String(scope)}"`);
|
|
2102
2160
|
}
|
|
2103
2161
|
if (scope === "viewport" && Object.keys(options).length === 0) {
|
|
2162
|
+
if (this.drawScheduled) this.draw();
|
|
2104
2163
|
const copy = document.createElement("canvas");
|
|
2105
2164
|
copy.width = this.canvas.width;
|
|
2106
2165
|
copy.height = this.canvas.height;
|
|
@@ -2127,7 +2186,10 @@ var SSDiagram = (() => {
|
|
|
2127
2186
|
offX: this.offX,
|
|
2128
2187
|
offY: this.offY,
|
|
2129
2188
|
overviewVisible: this.overviewVisible,
|
|
2130
|
-
background: this.opts.background
|
|
2189
|
+
background: this.opts.background,
|
|
2190
|
+
// Drawing the overview recomputes this from the export frame. Left behind, the next
|
|
2191
|
+
// click on the on-screen minimap panned to a point in the export's coordinates.
|
|
2192
|
+
ovGeo: this.ovGeo
|
|
2131
2193
|
};
|
|
2132
2194
|
try {
|
|
2133
2195
|
this.ctx = outputContext;
|
|
@@ -2157,6 +2219,7 @@ var SSDiagram = (() => {
|
|
|
2157
2219
|
this.offY = previous.offY;
|
|
2158
2220
|
this.overviewVisible = previous.overviewVisible;
|
|
2159
2221
|
this.opts.background = previous.background;
|
|
2222
|
+
this.ovGeo = previous.ovGeo;
|
|
2160
2223
|
}
|
|
2161
2224
|
return output;
|
|
2162
2225
|
}
|
|
@@ -2210,10 +2273,15 @@ var SSDiagram = (() => {
|
|
|
2210
2273
|
scale: this.scale,
|
|
2211
2274
|
offX: this.offX,
|
|
2212
2275
|
offY: this.offY,
|
|
2213
|
-
overviewVisible: this.overviewVisible
|
|
2276
|
+
overviewVisible: this.overviewVisible,
|
|
2277
|
+
background: this.opts.background,
|
|
2278
|
+
// Drawing the overview recomputes this from the export frame. Left behind, the next
|
|
2279
|
+
// click on the on-screen minimap panned to a point in the export's coordinates.
|
|
2280
|
+
ovGeo: this.ovGeo
|
|
2214
2281
|
};
|
|
2215
2282
|
try {
|
|
2216
2283
|
this.ctx = surface;
|
|
2284
|
+
if (options.background !== void 0) this.opts.background = options.background;
|
|
2217
2285
|
this.width = width;
|
|
2218
2286
|
this.height = height;
|
|
2219
2287
|
this.dpr = 1;
|
|
@@ -2237,6 +2305,8 @@ var SSDiagram = (() => {
|
|
|
2237
2305
|
this.offX = previous.offX;
|
|
2238
2306
|
this.offY = previous.offY;
|
|
2239
2307
|
this.overviewVisible = previous.overviewVisible;
|
|
2308
|
+
this.opts.background = previous.background;
|
|
2309
|
+
this.ovGeo = previous.ovGeo;
|
|
2240
2310
|
}
|
|
2241
2311
|
return surface.toSvg();
|
|
2242
2312
|
}
|
|
@@ -2269,8 +2339,8 @@ var SSDiagram = (() => {
|
|
|
2269
2339
|
...Object.values(this.runtimeState.nodes).flatMap((node) => [node.errors.runtime?.pulse ?? 0, node.errors.load?.pulse ?? 0])
|
|
2270
2340
|
);
|
|
2271
2341
|
for (const node of this.nodes) {
|
|
2272
|
-
const errors = this.runtimeState.nodes
|
|
2273
|
-
const previousRuntime = previous.nodes
|
|
2342
|
+
const errors = getJsonKey(this.runtimeState.nodes, node.id)?.errors;
|
|
2343
|
+
const previousRuntime = getJsonKey(previous.nodes, node.id)?.errors.runtime;
|
|
2274
2344
|
const runtimeError = errors?.runtime;
|
|
2275
2345
|
node.runtimeError = runtimeError?.message ?? "";
|
|
2276
2346
|
node.loadError = errors?.load?.message ?? "";
|
|
@@ -2299,8 +2369,8 @@ var SSDiagram = (() => {
|
|
|
2299
2369
|
const port = direction === "in" ? node?.inPorts.find((candidate) => candidate.id === portId) : node?.outPorts.find((candidate) => candidate.id === portId);
|
|
2300
2370
|
if (node === void 0 || port === void 0) return false;
|
|
2301
2371
|
const state = this.getRuntimeState();
|
|
2302
|
-
const nodeState = state.nodes
|
|
2303
|
-
state.nodes
|
|
2372
|
+
const nodeState = getJsonKey(state.nodes, nodeId) ?? createDiagramNodeRuntimeState();
|
|
2373
|
+
setJsonKey(state.nodes, nodeId, nodeState);
|
|
2304
2374
|
const current = nodeState.ports[direction][portId] ?? createDiagramPortRuntimeState();
|
|
2305
2375
|
nodeState.ports[direction][portId] = { ...current, ...patch };
|
|
2306
2376
|
this.setRuntimeState(state);
|
|
@@ -2322,8 +2392,8 @@ var SSDiagram = (() => {
|
|
|
2322
2392
|
node.errorFlashStart = message.length > 0 && options.animate !== false ? performance.now() : null;
|
|
2323
2393
|
}
|
|
2324
2394
|
const state = this.getRuntimeState();
|
|
2325
|
-
const nodeState = state.nodes
|
|
2326
|
-
state.nodes
|
|
2395
|
+
const nodeState = getJsonKey(state.nodes, id) ?? createDiagramNodeRuntimeState();
|
|
2396
|
+
setJsonKey(state.nodes, id, nodeState);
|
|
2327
2397
|
if (message.length === 0) delete nodeState.errors[kind];
|
|
2328
2398
|
else if (kind === "load") nodeState.errors.load = { kind, message, pulse: ++this.runtimePulse };
|
|
2329
2399
|
else nodeState.errors.runtime = { kind, message, pulse: ++this.runtimePulse };
|
|
@@ -2340,7 +2410,7 @@ var SSDiagram = (() => {
|
|
|
2340
2410
|
}
|
|
2341
2411
|
if (kind === void 0 || kind === "load") node.loadError = "";
|
|
2342
2412
|
const state = this.getRuntimeState();
|
|
2343
|
-
const nodeState = state.nodes
|
|
2413
|
+
const nodeState = getJsonKey(state.nodes, id);
|
|
2344
2414
|
if (nodeState !== void 0) {
|
|
2345
2415
|
if (kind === void 0 || kind === "runtime") delete nodeState.errors.runtime;
|
|
2346
2416
|
if (kind === void 0 || kind === "load") delete nodeState.errors.load;
|
|
@@ -2364,6 +2434,7 @@ var SSDiagram = (() => {
|
|
|
2364
2434
|
this.selectNode(node);
|
|
2365
2435
|
}
|
|
2366
2436
|
setZoom(scale) {
|
|
2437
|
+
if (!Number.isFinite(scale)) throw new RangeError("ssdiagram: zoom must be a finite number");
|
|
2367
2438
|
const cx = this.width / 2;
|
|
2368
2439
|
const cy = this.height / 2;
|
|
2369
2440
|
const wx = (cx - this.offX) / this.scale;
|
|
@@ -2405,9 +2476,21 @@ var SSDiagram = (() => {
|
|
|
2405
2476
|
this.scheduleDraw();
|
|
2406
2477
|
}
|
|
2407
2478
|
setTypeColors(colors) {
|
|
2408
|
-
this.
|
|
2479
|
+
this.setTypeColorsInternal(colors);
|
|
2409
2480
|
this.scheduleDraw();
|
|
2410
2481
|
}
|
|
2482
|
+
/**
|
|
2483
|
+
* Keyed the way port types are compared everywhere else. Connection compatibility is case- and
|
|
2484
|
+
* space-insensitive, so an imported scheme spelling a socket "candle " connects to "Candle" and
|
|
2485
|
+
* then fell through to a hash colour, showing one logical type in two colours in one diagram.
|
|
2486
|
+
*/
|
|
2487
|
+
setTypeColorsInternal(colors) {
|
|
2488
|
+
const normalized = {};
|
|
2489
|
+
for (const [type, color] of Object.entries(colors)) {
|
|
2490
|
+
setJsonKey(normalized, normalizePortType(type), color);
|
|
2491
|
+
}
|
|
2492
|
+
this.opts.typeColors = normalized;
|
|
2493
|
+
}
|
|
2411
2494
|
setTheme(t2) {
|
|
2412
2495
|
if (t2.background !== void 0) this.opts.background = t2.background;
|
|
2413
2496
|
if (t2.gridColor !== void 0) this.opts.gridColor = t2.gridColor;
|
|
@@ -2509,7 +2592,8 @@ var SSDiagram = (() => {
|
|
|
2509
2592
|
const light = maxL !== void 0 && maxL < 0.5;
|
|
2510
2593
|
if (type === "") return light ? "#585c62" : "#9aa0a6";
|
|
2511
2594
|
const map = this.opts.typeColors ?? {};
|
|
2512
|
-
const
|
|
2595
|
+
const normalized = normalizePortType(type);
|
|
2596
|
+
const base = getJsonKey(map, normalized) ?? `hsl(${hashHue(normalized)}, 62%, 58%)`;
|
|
2513
2597
|
return maxL !== void 0 ? clampLightness(base, maxL) : base;
|
|
2514
2598
|
}
|
|
2515
2599
|
// Selection/hover accent. The dark-canvas blue washes out on a light canvas, so a light theme
|
|
@@ -2529,6 +2613,7 @@ var SSDiagram = (() => {
|
|
|
2529
2613
|
for (const p of [...n2.inPorts, ...n2.outPorts]) {
|
|
2530
2614
|
if ((wx - p.cx) ** 2 + (wy - p.cy) ** 2 <= (PORT_R + 4) ** 2) return { node: n2, port: p };
|
|
2531
2615
|
}
|
|
2616
|
+
if (wx >= n2.x && wx <= n2.x + n2.w && wy >= n2.y && wy <= n2.y + n2.h) return null;
|
|
2532
2617
|
}
|
|
2533
2618
|
return null;
|
|
2534
2619
|
}
|
|
@@ -2807,6 +2892,9 @@ var SSDiagram = (() => {
|
|
|
2807
2892
|
fromPort: l.from.portId,
|
|
2808
2893
|
to: t2,
|
|
2809
2894
|
toPort: l.to.portId,
|
|
2895
|
+
// The clipboard is built from saveDocument(), so it carries the style;
|
|
2896
|
+
// rebuilding without it silently turned every pasted dashed link solid.
|
|
2897
|
+
style: l.style,
|
|
2810
2898
|
metadata: l.metadata
|
|
2811
2899
|
});
|
|
2812
2900
|
}
|
|
@@ -2815,6 +2903,23 @@ var SSDiagram = (() => {
|
|
|
2815
2903
|
return added.map((node) => node.id);
|
|
2816
2904
|
}
|
|
2817
2905
|
// ---- input ------------------------------------------------------
|
|
2906
|
+
// Ends hover and everything hanging off it: highlight, cursor and the delayed tooltip.
|
|
2907
|
+
clearHover() {
|
|
2908
|
+
if (this.hoverPort !== null) this.emit("portHover", { node: this.hoverPort.node, port: this.hoverPort.port, hovering: false });
|
|
2909
|
+
if (this.hoverNode !== null) this.emit("nodeHover", { node: this.hoverNode, hovering: false });
|
|
2910
|
+
if (this.hoveredLink !== null) this.emit("linkHover", { link: this.hoveredLink, hovering: false });
|
|
2911
|
+
this.hoverPort = null;
|
|
2912
|
+
this.hoverNode = null;
|
|
2913
|
+
this.hoveredLink = null;
|
|
2914
|
+
this.tipTarget = null;
|
|
2915
|
+
this.tipShow = false;
|
|
2916
|
+
if (this.tipTimer !== null) {
|
|
2917
|
+
clearTimeout(this.tipTimer);
|
|
2918
|
+
this.tipTimer = null;
|
|
2919
|
+
}
|
|
2920
|
+
this.canvas.style.cursor = "default";
|
|
2921
|
+
this.scheduleDraw();
|
|
2922
|
+
}
|
|
2818
2923
|
cancelLongPress() {
|
|
2819
2924
|
if (this.lpTimer !== null) {
|
|
2820
2925
|
clearTimeout(this.lpTimer);
|
|
@@ -2847,7 +2952,7 @@ var SSDiagram = (() => {
|
|
|
2847
2952
|
this.relinking = null;
|
|
2848
2953
|
this.relinkCandidate = null;
|
|
2849
2954
|
this.linkSnap = null;
|
|
2850
|
-
this.
|
|
2955
|
+
this.clearHover();
|
|
2851
2956
|
this.emit("contextMenu", { x: pageX, y: pageY, link, node, port });
|
|
2852
2957
|
}
|
|
2853
2958
|
listen(target, type, listener, options) {
|
|
@@ -2866,14 +2971,16 @@ var SSDiagram = (() => {
|
|
|
2866
2971
|
}
|
|
2867
2972
|
this.cancelLongPress();
|
|
2868
2973
|
this.relinkCandidate = null;
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
this.lpTimer =
|
|
2875
|
-
|
|
2876
|
-
|
|
2974
|
+
if (e.pointerType !== "mouse") {
|
|
2975
|
+
this.lpStart = { sx: e.clientX, sy: e.clientY, px: e.clientX, py: e.clientY };
|
|
2976
|
+
const downSx = e.clientX, downSy = e.clientY;
|
|
2977
|
+
const r0 = this.canvas.getBoundingClientRect();
|
|
2978
|
+
const localSx = downSx - r0.left, localSy = downSy - r0.top;
|
|
2979
|
+
this.lpTimer = setTimeout(() => {
|
|
2980
|
+
this.lpTimer = null;
|
|
2981
|
+
this.fireContextMenu(localSx, localSy, downSx, downSy);
|
|
2982
|
+
}, this.lpDelayMs);
|
|
2983
|
+
}
|
|
2877
2984
|
this.canvas.focus();
|
|
2878
2985
|
const [sx, sy] = localXY(e);
|
|
2879
2986
|
if (this.ovHit(sx, sy)) {
|
|
@@ -2943,13 +3050,13 @@ var SSDiagram = (() => {
|
|
|
2943
3050
|
if (node !== null) {
|
|
2944
3051
|
const add = e.shiftKey || e.ctrlKey || e.metaKey;
|
|
2945
3052
|
if (this.permissions.select) {
|
|
2946
|
-
if (add) {
|
|
3053
|
+
if (add && e.button === 0) {
|
|
2947
3054
|
this.toggleSelect(node);
|
|
2948
3055
|
return;
|
|
2949
3056
|
}
|
|
2950
3057
|
if (!this.selectedNodes.has(node)) this.selectNode(node);
|
|
2951
3058
|
}
|
|
2952
|
-
if (this.permissions.moveNodes) {
|
|
3059
|
+
if (this.permissions.moveNodes && e.button === 0) {
|
|
2953
3060
|
this.dragNode = node;
|
|
2954
3061
|
this.dragAnchor = { wx, wy };
|
|
2955
3062
|
const moving = this.selectedNodes.has(node) ? [...this.selectedNodes] : [node];
|
|
@@ -2964,6 +3071,7 @@ var SSDiagram = (() => {
|
|
|
2964
3071
|
if (this.permissions.select) this.selectLink(link);
|
|
2965
3072
|
return;
|
|
2966
3073
|
}
|
|
3074
|
+
if (e.button !== 0 && e.button !== 1) return;
|
|
2967
3075
|
const wantPan = !this.permissions.select || e.button === 1 || e.ctrlKey || e.altKey || e.pointerType === "touch";
|
|
2968
3076
|
if (wantPan) {
|
|
2969
3077
|
if (this.permissions.select && !e.shiftKey) this.selectNode(null);
|
|
@@ -3087,7 +3195,10 @@ var SSDiagram = (() => {
|
|
|
3087
3195
|
this.relinkCandidate = null;
|
|
3088
3196
|
if (this.dragNode !== null) {
|
|
3089
3197
|
const moves = this.dragStart.map((it) => ({ id: it.n.id, fromX: it.x, fromY: it.y, toX: it.n.x, toY: it.n.y })).filter((m) => m.fromX !== m.toX || m.fromY !== m.toY);
|
|
3090
|
-
|
|
3198
|
+
const moved = new Set(moves.map((m) => m.id));
|
|
3199
|
+
for (const it of this.dragStart) {
|
|
3200
|
+
if (moved.has(it.n.id)) this.emit("nodeMoved", { node: it.n });
|
|
3201
|
+
}
|
|
3091
3202
|
this.dragNode = null;
|
|
3092
3203
|
this.dragStart = [];
|
|
3093
3204
|
if (moves.length > 0) {
|
|
@@ -3169,26 +3280,12 @@ var SSDiagram = (() => {
|
|
|
3169
3280
|
this.emitViewChanged(true);
|
|
3170
3281
|
this.scheduleDraw();
|
|
3171
3282
|
}, { passive: false });
|
|
3172
|
-
this.listen(this.canvas, "pointerleave", () =>
|
|
3173
|
-
if (this.hoverPort !== null) this.emit("portHover", { node: this.hoverPort.node, port: this.hoverPort.port, hovering: false });
|
|
3174
|
-
if (this.hoverNode !== null) this.emit("nodeHover", { node: this.hoverNode, hovering: false });
|
|
3175
|
-
if (this.hoveredLink !== null) this.emit("linkHover", { link: this.hoveredLink, hovering: false });
|
|
3176
|
-
this.hoverPort = null;
|
|
3177
|
-
this.hoverNode = null;
|
|
3178
|
-
this.hoveredLink = null;
|
|
3179
|
-
this.tipTarget = null;
|
|
3180
|
-
this.tipShow = false;
|
|
3181
|
-
if (this.tipTimer !== null) {
|
|
3182
|
-
clearTimeout(this.tipTimer);
|
|
3183
|
-
this.tipTimer = null;
|
|
3184
|
-
}
|
|
3185
|
-
this.canvas.style.cursor = "default";
|
|
3186
|
-
this.scheduleDraw();
|
|
3187
|
-
});
|
|
3283
|
+
this.listen(this.canvas, "pointerleave", () => this.clearHover());
|
|
3188
3284
|
this.listen(this.canvas, "dblclick", (e) => {
|
|
3189
3285
|
const [sx, sy] = localXY(e);
|
|
3190
3286
|
const [wx, wy] = this.toWorld(sx, sy);
|
|
3191
3287
|
if (this.portAt(wx, wy) !== null) return;
|
|
3288
|
+
if (this.linkAt(wx, wy) !== null) return;
|
|
3192
3289
|
const node = this.nodeAt(wx, wy);
|
|
3193
3290
|
if (node !== null) {
|
|
3194
3291
|
if (this.permissions.inspect && node.openAction.length > 0) {
|
|
@@ -3231,6 +3328,8 @@ var SSDiagram = (() => {
|
|
|
3231
3328
|
this.linking = null;
|
|
3232
3329
|
this.relinking = null;
|
|
3233
3330
|
this.relinkCandidate = null;
|
|
3331
|
+
this.ovDragging = false;
|
|
3332
|
+
this.linkSnap = null;
|
|
3234
3333
|
}
|
|
3235
3334
|
}, { passive: false });
|
|
3236
3335
|
this.listen(this.canvas, "touchmove", (e) => {
|
|
@@ -3291,12 +3390,12 @@ var SSDiagram = (() => {
|
|
|
3291
3390
|
}
|
|
3292
3391
|
if (mod && this.permissions.history && e.code === "KeyZ" && !e.shiftKey) {
|
|
3293
3392
|
e.preventDefault();
|
|
3294
|
-
this.undo
|
|
3393
|
+
this.historyShortcut("undo");
|
|
3295
3394
|
return;
|
|
3296
3395
|
}
|
|
3297
3396
|
if (mod && this.permissions.history && (e.code === "KeyY" || e.code === "KeyZ" && e.shiftKey)) {
|
|
3298
3397
|
e.preventDefault();
|
|
3299
|
-
this.redo
|
|
3398
|
+
this.historyShortcut("redo");
|
|
3300
3399
|
return;
|
|
3301
3400
|
}
|
|
3302
3401
|
});
|
|
@@ -3374,17 +3473,23 @@ var SSDiagram = (() => {
|
|
|
3374
3473
|
}
|
|
3375
3474
|
ctx.restore();
|
|
3376
3475
|
if (options.overview) this.drawOverview();
|
|
3377
|
-
if (options.runtime) this.drawGlobalError();
|
|
3476
|
+
if (options.runtime) this.drawGlobalError(options.transient);
|
|
3378
3477
|
if (options.transient) this.drawTooltip();
|
|
3379
3478
|
if (options.transient && (this.introStart !== null || this.globalErrorFlashStart !== null || this.nodes.some((n2) => n2.errorFlashStart !== null)))
|
|
3380
3479
|
this.scheduleDraw();
|
|
3381
3480
|
}
|
|
3382
|
-
|
|
3481
|
+
/**
|
|
3482
|
+
* `transient` gates the flash exactly as it gates every other animation, which an export must
|
|
3483
|
+
* not see: it would bake in whatever point of the pulse it caught, so the same export taken
|
|
3484
|
+
* twice differed in opacity. The gate also keeps the export from consuming the animation --
|
|
3485
|
+
* clearing globalErrorFlashStart from an export cut the flash short on screen.
|
|
3486
|
+
*/
|
|
3487
|
+
drawGlobalError(transient) {
|
|
3383
3488
|
const error = this.runtimeState.globalError;
|
|
3384
3489
|
if (error === null) return;
|
|
3385
3490
|
const ctx = this.ctx;
|
|
3386
3491
|
let flashAlpha = 1;
|
|
3387
|
-
if (this.globalErrorFlashStart !== null) {
|
|
3492
|
+
if (transient && this.globalErrorFlashStart !== null) {
|
|
3388
3493
|
const elapsed = performance.now() - this.globalErrorFlashStart;
|
|
3389
3494
|
if (elapsed >= ERROR_FLASH_MS) {
|
|
3390
3495
|
this.globalErrorFlashStart = null;
|
|
@@ -3567,7 +3672,7 @@ ${runtime.error}`;
|
|
|
3567
3672
|
const ctx = this.ctx;
|
|
3568
3673
|
const hasLoadError = options.runtime && n2.loadError.length > 0;
|
|
3569
3674
|
const hasRuntimeError = options.runtime && n2.runtimeError.length > 0;
|
|
3570
|
-
const runtime = options.runtime ? this.runtimeState.nodes
|
|
3675
|
+
const runtime = options.runtime ? getJsonKey(this.runtimeState.nodes, n2.id) : void 0;
|
|
3571
3676
|
const active = options.runtime && (this.runtimeState.activeNodeId === n2.id || runtime?.active === true);
|
|
3572
3677
|
roundRect(ctx, n2.x, n2.y, n2.w, n2.h, 6);
|
|
3573
3678
|
ctx.fillStyle = hasLoadError ? ERROR_BACKGROUND : active ? "#ffd1dc" : n2.color;
|
|
@@ -3655,7 +3760,7 @@ ${runtime.error}`;
|
|
|
3655
3760
|
}
|
|
3656
3761
|
}
|
|
3657
3762
|
portRuntimeState(node, port) {
|
|
3658
|
-
return this.runtimeState.nodes
|
|
3763
|
+
return getJsonKey(this.runtimeState.nodes, node.id)?.ports[port.direction][port.id] ?? null;
|
|
3659
3764
|
}
|
|
3660
3765
|
// Symmetric jump-over: any segment of THIS link hops the perpendicular
|
|
3661
3766
|
// segments of links drawn before it (H over earlier V, V over earlier
|
|
@@ -4133,9 +4238,10 @@ ${runtime.error}`;
|
|
|
4133
4238
|
}
|
|
4134
4239
|
|
|
4135
4240
|
// src/i18n.ts
|
|
4136
|
-
var i18n = typeof window === "undefined" ? {} : window.__designerI18n ?? {};
|
|
4137
4241
|
function t(key, fallback) {
|
|
4138
|
-
|
|
4242
|
+
if (typeof window === "undefined") return fallback;
|
|
4243
|
+
const value = window.__designerI18n?.[key];
|
|
4244
|
+
return value === void 0 || value === "" ? fallback : value;
|
|
4139
4245
|
}
|
|
4140
4246
|
|
|
4141
4247
|
// src/diagram/context-menu.ts
|
|
@@ -4145,7 +4251,8 @@ ${runtime.error}`;
|
|
|
4145
4251
|
var SEPARATOR_CLASS = "ssdiagram-context-menu-separator";
|
|
4146
4252
|
var LABEL_CLASS = "ssdiagram-context-menu-label";
|
|
4147
4253
|
var ARROW_CLASS = "ssdiagram-context-menu-arrow";
|
|
4148
|
-
var
|
|
4254
|
+
var CHECK_CLASS = "ssdiagram-context-menu-check";
|
|
4255
|
+
var SEPARATE_AFTER = /* @__PURE__ */ new Set(["redo", "paste", "delete", "overview"]);
|
|
4149
4256
|
function label(id) {
|
|
4150
4257
|
switch (id) {
|
|
4151
4258
|
case "undo":
|
|
@@ -4161,7 +4268,7 @@ ${runtime.error}`;
|
|
|
4161
4268
|
case "open":
|
|
4162
4269
|
return t("ctxOpen", "Open");
|
|
4163
4270
|
case "delete":
|
|
4164
|
-
return t("
|
|
4271
|
+
return t("ctxDelete", "Delete");
|
|
4165
4272
|
case "export":
|
|
4166
4273
|
return t("ctxExportAs", "Export as");
|
|
4167
4274
|
case "exportDocument":
|
|
@@ -4170,6 +4277,8 @@ ${runtime.error}`;
|
|
|
4170
4277
|
return t("ctxExportPng", "PNG image");
|
|
4171
4278
|
case "exportSvg":
|
|
4172
4279
|
return t("ctxExportSvg", "SVG image");
|
|
4280
|
+
case "overview":
|
|
4281
|
+
return t("ctxOverview", "Overview");
|
|
4173
4282
|
case "properties":
|
|
4174
4283
|
return t("properties", "Properties");
|
|
4175
4284
|
case "help":
|
|
@@ -4180,6 +4289,10 @@ ${runtime.error}`;
|
|
|
4180
4289
|
position: "fixed",
|
|
4181
4290
|
zIndex: "2147483000",
|
|
4182
4291
|
minWidth: "176px",
|
|
4292
|
+
// Translations run longer than English -- German and Russian routinely half again. Without
|
|
4293
|
+
// a cap the panel just grows, and on a narrow viewport it leaves the screen entirely, which
|
|
4294
|
+
// no amount of repositioning can recover.
|
|
4295
|
+
maxWidth: "min(320px, calc(100vw - 16px))",
|
|
4183
4296
|
padding: "4px",
|
|
4184
4297
|
border: "1px solid var(--ssdiagram-menu-border, var(--ssdiagram-control-border, var(--t-border, #3a4250)))",
|
|
4185
4298
|
borderRadius: "7px",
|
|
@@ -4201,8 +4314,10 @@ ${runtime.error}`;
|
|
|
4201
4314
|
background: "transparent",
|
|
4202
4315
|
color: "inherit",
|
|
4203
4316
|
font: "inherit",
|
|
4204
|
-
|
|
4205
|
-
|
|
4317
|
+
// Logical, not physical: on an RTL page these follow the writing direction instead of
|
|
4318
|
+
// pinning the text left and truncating from the wrong end.
|
|
4319
|
+
textAlign: "start",
|
|
4320
|
+
overflowWrap: "anywhere",
|
|
4206
4321
|
cursor: "pointer",
|
|
4207
4322
|
boxSizing: "border-box"
|
|
4208
4323
|
};
|
|
@@ -4212,6 +4327,7 @@ ${runtime.error}`;
|
|
|
4212
4327
|
constructor(options) {
|
|
4213
4328
|
this.root = null;
|
|
4214
4329
|
this.submenu = null;
|
|
4330
|
+
this.submenuTriggers = /* @__PURE__ */ new Map();
|
|
4215
4331
|
this.detach = [];
|
|
4216
4332
|
this.container = options.container;
|
|
4217
4333
|
this.execute = options.execute;
|
|
@@ -4225,12 +4341,16 @@ ${runtime.error}`;
|
|
|
4225
4341
|
const root = this.panel(owner);
|
|
4226
4342
|
root.className = ROOT_CLASS;
|
|
4227
4343
|
root.setAttribute("role", "menu");
|
|
4344
|
+
const gutter = items.some((item) => !("group" in item) && item.checked !== void 0);
|
|
4228
4345
|
items.forEach((item, index) => {
|
|
4229
|
-
if ("group" in item) root.appendChild(this.groupEntry(owner, item));
|
|
4230
|
-
else root.appendChild(this.commandEntry(owner, item.command, item.enabled, true));
|
|
4346
|
+
if ("group" in item) root.appendChild(this.groupEntry(owner, item, gutter));
|
|
4347
|
+
else root.appendChild(this.commandEntry(owner, item.command, item.enabled, true, item.checked, gutter));
|
|
4231
4348
|
const id = "group" in item ? item.group : item.command;
|
|
4232
4349
|
if (SEPARATE_AFTER.has(id) && index < items.length - 1) root.appendChild(this.separator(owner));
|
|
4233
4350
|
});
|
|
4351
|
+
this.on(root, "contextmenu", (event) => {
|
|
4352
|
+
event.preventDefault();
|
|
4353
|
+
});
|
|
4234
4354
|
this.container.appendChild(root);
|
|
4235
4355
|
this.root = root;
|
|
4236
4356
|
this.place(root, x, y);
|
|
@@ -4239,6 +4359,7 @@ ${runtime.error}`;
|
|
|
4239
4359
|
hide() {
|
|
4240
4360
|
for (const dispose of this.detach.splice(0)) dispose();
|
|
4241
4361
|
this.submenu = null;
|
|
4362
|
+
this.submenuTriggers.clear();
|
|
4242
4363
|
this.root?.remove();
|
|
4243
4364
|
this.root = null;
|
|
4244
4365
|
}
|
|
@@ -4260,17 +4381,26 @@ ${runtime.error}`;
|
|
|
4260
4381
|
});
|
|
4261
4382
|
return rule;
|
|
4262
4383
|
}
|
|
4263
|
-
item(owner, text, enabled, arrow) {
|
|
4384
|
+
item(owner, text, enabled, arrow, checked, gutter) {
|
|
4264
4385
|
const button = owner.createElement("button");
|
|
4265
4386
|
button.type = "button";
|
|
4266
4387
|
button.className = ITEM_CLASS;
|
|
4267
|
-
button.setAttribute("role", "menuitem");
|
|
4388
|
+
button.setAttribute("role", checked === void 0 ? "menuitem" : "menuitemcheckbox");
|
|
4389
|
+
if (checked !== void 0) button.setAttribute("aria-checked", String(checked));
|
|
4268
4390
|
Object.assign(button.style, ITEM_STYLE);
|
|
4269
4391
|
if (!enabled) {
|
|
4270
4392
|
button.disabled = true;
|
|
4271
4393
|
button.style.color = DISABLED_COLOR;
|
|
4272
4394
|
button.style.cursor = "default";
|
|
4273
4395
|
}
|
|
4396
|
+
if (gutter) {
|
|
4397
|
+
const tick = owner.createElement("span");
|
|
4398
|
+
tick.className = CHECK_CLASS;
|
|
4399
|
+
tick.textContent = checked === true ? "\u2713" : "";
|
|
4400
|
+
tick.setAttribute("aria-hidden", "true");
|
|
4401
|
+
Object.assign(tick.style, { flex: "none", width: "12px", textAlign: "center" });
|
|
4402
|
+
button.appendChild(tick);
|
|
4403
|
+
}
|
|
4274
4404
|
const caption = owner.createElement("span");
|
|
4275
4405
|
caption.className = LABEL_CLASS;
|
|
4276
4406
|
caption.textContent = text;
|
|
@@ -4279,7 +4409,7 @@ ${runtime.error}`;
|
|
|
4279
4409
|
if (arrow) {
|
|
4280
4410
|
const chevron = owner.createElement("span");
|
|
4281
4411
|
chevron.className = ARROW_CLASS;
|
|
4282
|
-
chevron.textContent = "\u203A";
|
|
4412
|
+
chevron.textContent = this.rtl() ? "\u2039" : "\u203A";
|
|
4283
4413
|
chevron.setAttribute("aria-hidden", "true");
|
|
4284
4414
|
Object.assign(chevron.style, { flex: "none", opacity: "0.7", fontSize: "14px" });
|
|
4285
4415
|
button.appendChild(chevron);
|
|
@@ -4291,8 +4421,8 @@ ${runtime.error}`;
|
|
|
4291
4421
|
// the submenu must not: reaching it means the pointer has entered that very panel, and
|
|
4292
4422
|
// closing it there would pull it out from under the pointer and drop the click on the
|
|
4293
4423
|
// canvas behind.
|
|
4294
|
-
commandEntry(owner, command, enabled, closesSubmenu) {
|
|
4295
|
-
const button = this.item(owner, label(command), enabled, false);
|
|
4424
|
+
commandEntry(owner, command, enabled, closesSubmenu, checked, gutter) {
|
|
4425
|
+
const button = this.item(owner, label(command), enabled, false, checked, gutter);
|
|
4296
4426
|
this.on(button, "pointerenter", () => {
|
|
4297
4427
|
if (closesSubmenu) this.closeSubmenu();
|
|
4298
4428
|
if (enabled) button.style.background = HOVER_BACKGROUND;
|
|
@@ -4307,20 +4437,27 @@ ${runtime.error}`;
|
|
|
4307
4437
|
});
|
|
4308
4438
|
return button;
|
|
4309
4439
|
}
|
|
4310
|
-
groupEntry(owner, group) {
|
|
4440
|
+
groupEntry(owner, group, gutter) {
|
|
4311
4441
|
const host = owner.createElement("div");
|
|
4312
4442
|
host.style.position = "relative";
|
|
4313
|
-
const button = this.item(owner, label(group.group), group.enabled, true);
|
|
4443
|
+
const button = this.item(owner, label(group.group), group.enabled, true, void 0, gutter);
|
|
4444
|
+
button.setAttribute("aria-haspopup", "menu");
|
|
4445
|
+
button.setAttribute("aria-expanded", "false");
|
|
4314
4446
|
host.appendChild(button);
|
|
4315
4447
|
const panel = this.panel(owner);
|
|
4316
4448
|
panel.className = SUBMENU_CLASS;
|
|
4317
4449
|
panel.setAttribute("role", "menu");
|
|
4318
|
-
|
|
4319
|
-
|
|
4450
|
+
panel.setAttribute("aria-label", label(group.group));
|
|
4451
|
+
Object.assign(panel.style, { position: "absolute", insetInlineStart: "100%", top: "-5px", display: "none" });
|
|
4452
|
+
const childGutter = group.commands.some((child) => child.checked !== void 0);
|
|
4453
|
+
for (const child of group.commands) {
|
|
4454
|
+
panel.appendChild(this.commandEntry(owner, child.command, child.enabled, false, child.checked, childGutter));
|
|
4455
|
+
}
|
|
4320
4456
|
host.appendChild(panel);
|
|
4321
4457
|
const open = () => {
|
|
4322
4458
|
if (!group.enabled) return;
|
|
4323
4459
|
button.style.background = HOVER_BACKGROUND;
|
|
4460
|
+
button.setAttribute("aria-expanded", "true");
|
|
4324
4461
|
panel.style.display = "block";
|
|
4325
4462
|
this.submenu = panel;
|
|
4326
4463
|
this.keepOnScreen(panel);
|
|
@@ -4330,25 +4467,48 @@ ${runtime.error}`;
|
|
|
4330
4467
|
this.on(host, "pointerleave", () => {
|
|
4331
4468
|
if (this.submenu !== panel) return;
|
|
4332
4469
|
button.style.background = "transparent";
|
|
4470
|
+
button.setAttribute("aria-expanded", "false");
|
|
4333
4471
|
panel.style.display = "none";
|
|
4334
4472
|
this.submenu = null;
|
|
4335
4473
|
});
|
|
4474
|
+
this.submenuTriggers.set(panel, button);
|
|
4336
4475
|
return host;
|
|
4337
4476
|
}
|
|
4338
4477
|
closeSubmenu() {
|
|
4339
4478
|
if (this.submenu === null) return;
|
|
4340
4479
|
this.submenu.style.display = "none";
|
|
4480
|
+
this.submenuTriggers.get(this.submenu)?.setAttribute("aria-expanded", "false");
|
|
4341
4481
|
this.submenu = null;
|
|
4342
4482
|
}
|
|
4483
|
+
/**
|
|
4484
|
+
* The window the menu actually lives in. A component created from one document and mounted
|
|
4485
|
+
* into another -- a same-origin iframe, a popup -- must be measured and dismissed against
|
|
4486
|
+
* that document's window, not the one this module happened to be evaluated in.
|
|
4487
|
+
*/
|
|
4488
|
+
get view() {
|
|
4489
|
+
const owner = this.container.ownerDocument ?? (typeof document === "undefined" ? null : document);
|
|
4490
|
+
const view = owner?.defaultView ?? (typeof window === "undefined" ? null : window);
|
|
4491
|
+
return view ?? null;
|
|
4492
|
+
}
|
|
4493
|
+
// Reads the resolved writing direction so the submenu's side and its arrow agree with it.
|
|
4494
|
+
rtl() {
|
|
4495
|
+
if (typeof getComputedStyle !== "function") return false;
|
|
4496
|
+
try {
|
|
4497
|
+
return getComputedStyle(this.container).direction === "rtl";
|
|
4498
|
+
} catch {
|
|
4499
|
+
return false;
|
|
4500
|
+
}
|
|
4501
|
+
}
|
|
4343
4502
|
/** Keeps the panel inside the viewport, flipping it left when it would run off the edge. */
|
|
4344
4503
|
keepOnScreen(panel) {
|
|
4345
|
-
|
|
4504
|
+
const view = this.view;
|
|
4505
|
+
if (typeof panel.getBoundingClientRect !== "function" || view === null) return;
|
|
4346
4506
|
const box = panel.getBoundingClientRect();
|
|
4347
|
-
if (box.right >
|
|
4348
|
-
panel.style.
|
|
4349
|
-
panel.style.
|
|
4507
|
+
if (box.right > view.innerWidth - 4 || box.left < 4) {
|
|
4508
|
+
panel.style.insetInlineStart = "auto";
|
|
4509
|
+
panel.style.insetInlineEnd = "100%";
|
|
4350
4510
|
}
|
|
4351
|
-
if (box.bottom >
|
|
4511
|
+
if (box.bottom > view.innerHeight - 4) {
|
|
4352
4512
|
panel.style.top = "auto";
|
|
4353
4513
|
panel.style.bottom = "-5px";
|
|
4354
4514
|
}
|
|
@@ -4356,10 +4516,11 @@ ${runtime.error}`;
|
|
|
4356
4516
|
place(root, x, y) {
|
|
4357
4517
|
root.style.left = `${x}px`;
|
|
4358
4518
|
root.style.top = `${y}px`;
|
|
4359
|
-
|
|
4519
|
+
const view = this.view;
|
|
4520
|
+
if (typeof root.getBoundingClientRect !== "function" || view === null) return;
|
|
4360
4521
|
const box = root.getBoundingClientRect();
|
|
4361
|
-
if (box.right >
|
|
4362
|
-
if (box.bottom >
|
|
4522
|
+
if (box.right > view.innerWidth - 4) root.style.left = `${Math.max(4, x - box.width)}px`;
|
|
4523
|
+
if (box.bottom > view.innerHeight - 4) root.style.top = `${Math.max(4, y - box.height)}px`;
|
|
4363
4524
|
}
|
|
4364
4525
|
watchForDismissal(owner) {
|
|
4365
4526
|
const dismiss = () => this.hide();
|
|
@@ -4373,10 +4534,11 @@ ${runtime.error}`;
|
|
|
4373
4534
|
this.on(owner, "keydown", (event) => {
|
|
4374
4535
|
if (event.key === "Escape") this.hide();
|
|
4375
4536
|
});
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
this.on(
|
|
4379
|
-
this.on(
|
|
4537
|
+
const view = this.view;
|
|
4538
|
+
if (view !== null) {
|
|
4539
|
+
this.on(view, "blur", dismiss);
|
|
4540
|
+
this.on(view, "resize", dismiss);
|
|
4541
|
+
this.on(view, "scroll", dismiss, true);
|
|
4380
4542
|
}
|
|
4381
4543
|
}
|
|
4382
4544
|
on(target, type, listener, capture = false) {
|
|
@@ -4415,7 +4577,8 @@ ${runtime.error}`;
|
|
|
4415
4577
|
if (set === void 0) {
|
|
4416
4578
|
return;
|
|
4417
4579
|
}
|
|
4418
|
-
for (const handler of set) {
|
|
4580
|
+
for (const handler of [...set]) {
|
|
4581
|
+
if (!set.has(handler)) continue;
|
|
4419
4582
|
try {
|
|
4420
4583
|
handler(payload);
|
|
4421
4584
|
} catch (err) {
|
|
@@ -4431,11 +4594,23 @@ ${runtime.error}`;
|
|
|
4431
4594
|
return (this.handlers.get(event)?.size ?? 0) > 0;
|
|
4432
4595
|
}
|
|
4433
4596
|
clearEventHandlers() {
|
|
4597
|
+
for (const set of this.handlers.values()) set.clear();
|
|
4434
4598
|
this.handlers.clear();
|
|
4435
4599
|
}
|
|
4436
4600
|
};
|
|
4437
4601
|
|
|
4438
4602
|
// src/diagram/types.ts
|
|
4603
|
+
function copyJsonValue2(value) {
|
|
4604
|
+
if (Array.isArray(value)) return value.map(copyJsonValue2);
|
|
4605
|
+
if (value !== null && typeof value === "object") return copyJsonObject2(value);
|
|
4606
|
+
return value;
|
|
4607
|
+
}
|
|
4608
|
+
function copyJsonObject2(value) {
|
|
4609
|
+
if (value === void 0) return {};
|
|
4610
|
+
const result = {};
|
|
4611
|
+
for (const [key, item] of Object.entries(value)) setJsonKey(result, key, copyJsonValue2(item));
|
|
4612
|
+
return result;
|
|
4613
|
+
}
|
|
4439
4614
|
var PortType = class {
|
|
4440
4615
|
constructor(init) {
|
|
4441
4616
|
this.name = init.name;
|
|
@@ -4473,7 +4648,7 @@ ${runtime.error}`;
|
|
|
4473
4648
|
this.id = init.id;
|
|
4474
4649
|
this.name = init.name;
|
|
4475
4650
|
this.description = init.description ?? "";
|
|
4476
|
-
this.groupName = init.groupName ??
|
|
4651
|
+
this.groupName = init.groupName ?? DEFAULT_GROUP_NAME;
|
|
4477
4652
|
this.inPorts = (init.inPorts ?? []).map((p) => p instanceof Port ? p : new Port(p));
|
|
4478
4653
|
this.outPorts = (init.outPorts ?? []).map((p) => p instanceof Port ? p : new Port(p));
|
|
4479
4654
|
this.icon = init.icon ?? "";
|
|
@@ -4528,6 +4703,7 @@ ${runtime.error}`;
|
|
|
4528
4703
|
});
|
|
4529
4704
|
}
|
|
4530
4705
|
};
|
|
4706
|
+
var DEFAULT_GROUP_NAME = "Common";
|
|
4531
4707
|
var Link = class {
|
|
4532
4708
|
constructor(init) {
|
|
4533
4709
|
this.id = init.id ?? "";
|
|
@@ -4535,7 +4711,7 @@ ${runtime.error}`;
|
|
|
4535
4711
|
this.outPort = init.outPort;
|
|
4536
4712
|
this.inNode = init.inNode;
|
|
4537
4713
|
this.inPort = init.inPort;
|
|
4538
|
-
this.metadata = init.metadata
|
|
4714
|
+
this.metadata = copyJsonObject2(init.metadata);
|
|
4539
4715
|
}
|
|
4540
4716
|
};
|
|
4541
4717
|
|
|
@@ -4549,9 +4725,13 @@ ${runtime.error}`;
|
|
|
4549
4725
|
"open",
|
|
4550
4726
|
"delete",
|
|
4551
4727
|
{ group: "export", commands: ["exportDocument", "exportPng", "exportSvg"] },
|
|
4728
|
+
"overview",
|
|
4552
4729
|
"properties",
|
|
4553
4730
|
"help"
|
|
4554
4731
|
];
|
|
4732
|
+
var CONTEXT_CHECKED = {
|
|
4733
|
+
overview: (diagram) => diagram.getViewState().overviewVisible
|
|
4734
|
+
};
|
|
4555
4735
|
var StockSharpDiagram = class extends EventEmitter {
|
|
4556
4736
|
constructor(options) {
|
|
4557
4737
|
super();
|
|
@@ -4564,7 +4744,10 @@ ${runtime.error}`;
|
|
|
4564
4744
|
this.destroyed = false;
|
|
4565
4745
|
this.fullscreen = false;
|
|
4566
4746
|
this.fullscreenButtonVisible = true;
|
|
4567
|
-
|
|
4747
|
+
// null means the host never named the button, so its wording comes from the shared bundle.
|
|
4748
|
+
// Keeping the two apart is what lets an explicit label win while an untouched control still
|
|
4749
|
+
// follows the page's language instead of being stuck in English.
|
|
4750
|
+
this.fullscreenLabels = null;
|
|
4568
4751
|
this.contextActions = new DiagramActionRegistry();
|
|
4569
4752
|
this.contextMenu = null;
|
|
4570
4753
|
this.div = options.div;
|
|
@@ -4579,11 +4762,15 @@ ${runtime.error}`;
|
|
|
4579
4762
|
gridSize: options.gridSize
|
|
4580
4763
|
});
|
|
4581
4764
|
this.fullscreenButtonVisible = options.showFullscreenButton ?? true;
|
|
4582
|
-
this.fullscreenLabels = options.fullscreenLabels ??
|
|
4765
|
+
this.fullscreenLabels = options.fullscreenLabels ?? null;
|
|
4583
4766
|
this.prepareFullscreenButtonHost();
|
|
4584
4767
|
this.fullscreenButton = this.createFullscreenButton();
|
|
4585
4768
|
this.updateFullscreenButton();
|
|
4586
4769
|
this.setContextMenuEnabled(options.showContextMenu ?? true);
|
|
4770
|
+
this.canvas.setHistoryShortcutHandler((direction) => {
|
|
4771
|
+
if (direction === "undo") this.undo();
|
|
4772
|
+
else this.redo();
|
|
4773
|
+
});
|
|
4587
4774
|
this.registerContextActions();
|
|
4588
4775
|
this.bindCanvasEvents();
|
|
4589
4776
|
this.disposables.push(this.catalog.on("portTypesChanged", () => this.applySocketTheme()));
|
|
@@ -4828,8 +5015,22 @@ ${runtime.error}`;
|
|
|
4828
5015
|
this.fullscreenLabels = labels;
|
|
4829
5016
|
this.updateFullscreenButton();
|
|
4830
5017
|
}
|
|
5018
|
+
/**
|
|
5019
|
+
* Re-reads the shared bundle for text the control renders itself. Call it after changing
|
|
5020
|
+
* the page language: the context menu is rebuilt on every open and needs nothing, but the
|
|
5021
|
+
* fullscreen button is drawn once and would otherwise keep the old wording.
|
|
5022
|
+
*/
|
|
5023
|
+
refreshLabels() {
|
|
5024
|
+
this.updateFullscreenButton();
|
|
5025
|
+
}
|
|
4831
5026
|
getFullscreenLabels() {
|
|
4832
|
-
return { ...this.
|
|
5027
|
+
return { ...this.resolveFullscreenLabels() };
|
|
5028
|
+
}
|
|
5029
|
+
resolveFullscreenLabels() {
|
|
5030
|
+
return this.fullscreenLabels ?? {
|
|
5031
|
+
enter: t("fullscreenEnter", "Enter fullscreen"),
|
|
5032
|
+
exit: t("fullscreenExit", "Exit fullscreen")
|
|
5033
|
+
};
|
|
4833
5034
|
}
|
|
4834
5035
|
/** Updates only the control's state after the host changed its own layout. */
|
|
4835
5036
|
setFullscreenState(fullscreen) {
|
|
@@ -4954,10 +5155,14 @@ ${runtime.error}`;
|
|
|
4954
5155
|
}
|
|
4955
5156
|
getContextCommands() {
|
|
4956
5157
|
const context = this.contextActionContext();
|
|
4957
|
-
const state = (command) =>
|
|
4958
|
-
command
|
|
4959
|
-
|
|
4960
|
-
|
|
5158
|
+
const state = (command) => {
|
|
5159
|
+
const checked = CONTEXT_CHECKED[command];
|
|
5160
|
+
return {
|
|
5161
|
+
command,
|
|
5162
|
+
enabled: this.contextActions.canExecute(command, context),
|
|
5163
|
+
...checked === void 0 ? {} : { checked: checked(this) }
|
|
5164
|
+
};
|
|
5165
|
+
};
|
|
4961
5166
|
return CONTEXT_MENU.map((entry) => {
|
|
4962
5167
|
if (typeof entry === "string") return state(entry);
|
|
4963
5168
|
const commands = entry.commands.map(state);
|
|
@@ -5084,7 +5289,8 @@ ${runtime.error}`;
|
|
|
5084
5289
|
}
|
|
5085
5290
|
updateFullscreenButton() {
|
|
5086
5291
|
const fullscreen = this.fullscreen;
|
|
5087
|
-
const
|
|
5292
|
+
const labels = this.resolveFullscreenLabels();
|
|
5293
|
+
const label2 = fullscreen ? labels.exit : labels.enter;
|
|
5088
5294
|
const path = fullscreen ? "M4 9h5V4M20 9h-5V4M4 15h5v5M20 15h-5v5" : "M9 4H4v5M15 4h5v5M9 20H4v-5M15 20h5v-5";
|
|
5089
5295
|
this.fullscreenButton.className = fullscreen ? "ssdiagram-fullscreen-button is-active" : "ssdiagram-fullscreen-button";
|
|
5090
5296
|
this.fullscreenButton.title = label2;
|
|
@@ -5247,6 +5453,12 @@ ${runtime.error}`;
|
|
|
5247
5453
|
canExecute: ({ document: document2 }) => document2.nodes.length > 0,
|
|
5248
5454
|
execute: () => this.emit("exportRequested", { format: "svg" })
|
|
5249
5455
|
},
|
|
5456
|
+
// Always available: it shows the overview as readily as it hides it, and an empty
|
|
5457
|
+
// diagram is exactly when someone may want the panel out of the way.
|
|
5458
|
+
overview: {
|
|
5459
|
+
canExecute: () => true,
|
|
5460
|
+
execute: () => this.setOverviewVisible(!this.getViewState().overviewVisible)
|
|
5461
|
+
},
|
|
5250
5462
|
properties: {
|
|
5251
5463
|
canExecute: ({ nodes }) => nodes.length > 0,
|
|
5252
5464
|
execute: ({ nodes }) => this.emit("nodeProperties", { nodes })
|
|
@@ -5669,7 +5881,8 @@ ${runtime.error}`;
|
|
|
5669
5881
|
}
|
|
5670
5882
|
};
|
|
5671
5883
|
function groupNameOf(node) {
|
|
5672
|
-
|
|
5884
|
+
const group = node.groupName;
|
|
5885
|
+
return group.length > 0 && group !== DEFAULT_GROUP_NAME ? group : t("paletteCommonGroup", DEFAULT_GROUP_NAME);
|
|
5673
5886
|
}
|
|
5674
5887
|
function normalizeTypeId(typeId) {
|
|
5675
5888
|
return typeId.toLowerCase();
|
|
@@ -5687,12 +5900,43 @@ ${runtime.error}`;
|
|
|
5687
5900
|
function iconUrl(name) {
|
|
5688
5901
|
return name ? `/diagram-icons/${name}.svg` : "";
|
|
5689
5902
|
}
|
|
5690
|
-
|
|
5691
|
-
|
|
5692
|
-
|
|
5693
|
-
|
|
5694
|
-
|
|
5695
|
-
|
|
5903
|
+
var CSS_COLOR_KEYWORDS = {
|
|
5904
|
+
white: [255, 255, 255],
|
|
5905
|
+
black: [0, 0, 0],
|
|
5906
|
+
transparent: [255, 255, 255]
|
|
5907
|
+
};
|
|
5908
|
+
function luminance(color) {
|
|
5909
|
+
const value = color.trim().toLowerCase();
|
|
5910
|
+
if (value === "")
|
|
5911
|
+
return -1;
|
|
5912
|
+
const keyword = CSS_COLOR_KEYWORDS[value];
|
|
5913
|
+
if (keyword !== void 0)
|
|
5914
|
+
return 0.299 * keyword[0] + 0.587 * keyword[1] + 0.114 * keyword[2];
|
|
5915
|
+
const long = value.match(/^#?([0-9a-f]{6})$/);
|
|
5916
|
+
if (long !== null) {
|
|
5917
|
+
const n2 = parseInt(long[1], 16);
|
|
5918
|
+
return 0.299 * (n2 >> 16 & 255) + 0.587 * (n2 >> 8 & 255) + 0.114 * (n2 & 255);
|
|
5919
|
+
}
|
|
5920
|
+
const short = value.match(/^#?([0-9a-f]{3})([0-9a-f])?$/);
|
|
5921
|
+
if (short !== null) {
|
|
5922
|
+
const [r, g, b] = [...short[1]].map((digit) => parseInt(digit + digit, 16));
|
|
5923
|
+
return 0.299 * r + 0.587 * g + 0.114 * b;
|
|
5924
|
+
}
|
|
5925
|
+
const long8 = value.match(/^#?([0-9a-f]{6})[0-9a-f]{2}$/);
|
|
5926
|
+
if (long8 !== null) {
|
|
5927
|
+
const n2 = parseInt(long8[1], 16);
|
|
5928
|
+
return 0.299 * (n2 >> 16 & 255) + 0.587 * (n2 >> 8 & 255) + 0.114 * (n2 & 255);
|
|
5929
|
+
}
|
|
5930
|
+
const rgb = value.match(/^rgba?\(([^)]+)\)$/);
|
|
5931
|
+
if (rgb !== null) {
|
|
5932
|
+
const parts = rgb[1].split(/[\s,/]+/).filter((part) => part !== "");
|
|
5933
|
+
if (parts.length >= 3) {
|
|
5934
|
+
const [r, g, b] = parts.slice(0, 3).map((part) => part.endsWith("%") ? parseFloat(part) / 100 * 255 : parseFloat(part));
|
|
5935
|
+
if ([r, g, b].every((channel) => Number.isFinite(channel)))
|
|
5936
|
+
return 0.299 * r + 0.587 * g + 0.114 * b;
|
|
5937
|
+
}
|
|
5938
|
+
}
|
|
5939
|
+
return -1;
|
|
5696
5940
|
}
|
|
5697
5941
|
function makePort(p) {
|
|
5698
5942
|
return new Port({
|
|
@@ -5947,6 +6191,7 @@ ${runtime.error}`;
|
|
|
5947
6191
|
diagram.setTheme({
|
|
5948
6192
|
diagramBackground: bg,
|
|
5949
6193
|
gridColor,
|
|
6194
|
+
// Unreadable colours keep the dark default rather than being treated as black.
|
|
5950
6195
|
linkMaxLightness: luminance(bg) > 140 ? 0.42 : 1
|
|
5951
6196
|
});
|
|
5952
6197
|
};
|
|
@@ -5957,7 +6202,7 @@ ${runtime.error}`;
|
|
|
5957
6202
|
diagram.setReadOnly(true);
|
|
5958
6203
|
diagram.setLinkValidator(() => true);
|
|
5959
6204
|
diagram.setOverviewVisible(false);
|
|
5960
|
-
const missingTypeTemplate = div.dataset.diagramMissingElement ?? 'Element type "{typeId}" is missing from the palette.';
|
|
6205
|
+
const missingTypeTemplate = div.dataset.diagramMissingElement ?? t("embedMissingElement", 'Element type "{typeId}" is missing from the palette.');
|
|
5961
6206
|
const converted = toDiagramNodes(
|
|
5962
6207
|
scheme,
|
|
5963
6208
|
catalog,
|
|
@@ -6042,9 +6287,9 @@ ${runtime.error}`;
|
|
|
6042
6287
|
function errorTexts(div) {
|
|
6043
6288
|
const parts = (div.dataset.diagramErrors ?? "").split("|");
|
|
6044
6289
|
return {
|
|
6045
|
-
load: parts[0] || "Diagram source could not be loaded.",
|
|
6046
|
-
empty: parts[1] || "Diagram is empty or malformed.",
|
|
6047
|
-
draw: parts[2] || "Diagram could not be rendered."
|
|
6290
|
+
load: parts[0] || t("embedErrorLoad", "Diagram source could not be loaded."),
|
|
6291
|
+
empty: parts[1] || t("embedErrorEmpty", "Diagram is empty or malformed."),
|
|
6292
|
+
draw: parts[2] || t("embedErrorDraw", "Diagram could not be rendered.")
|
|
6048
6293
|
};
|
|
6049
6294
|
}
|
|
6050
6295
|
function note(div, message, revision) {
|
|
@@ -6107,16 +6352,25 @@ ${runtime.error}`;
|
|
|
6107
6352
|
root.querySelectorAll(".ss-diagram-host").forEach((host) => {
|
|
6108
6353
|
if (host.dataset.rendered === "1")
|
|
6109
6354
|
return;
|
|
6355
|
+
const started = (run) => {
|
|
6356
|
+
host.dataset.rendered = "1";
|
|
6357
|
+
void run.then(
|
|
6358
|
+
(handle) => {
|
|
6359
|
+
if (handle === null) delete host.dataset.rendered;
|
|
6360
|
+
},
|
|
6361
|
+
() => {
|
|
6362
|
+
delete host.dataset.rendered;
|
|
6363
|
+
}
|
|
6364
|
+
);
|
|
6365
|
+
};
|
|
6110
6366
|
const src = host.dataset.diagramSrc;
|
|
6111
6367
|
if (src) {
|
|
6112
|
-
host
|
|
6113
|
-
void renderFromSource(host, PALETTE_URL, src, options);
|
|
6368
|
+
started(renderFromSource(host, PALETTE_URL, src, options));
|
|
6114
6369
|
return;
|
|
6115
6370
|
}
|
|
6116
6371
|
const inline = host.querySelector('script[type="application/json"]');
|
|
6117
6372
|
if (inline) {
|
|
6118
|
-
host.
|
|
6119
|
-
void renderFromInline(host, PALETTE_URL, inline.textContent ?? "", options);
|
|
6373
|
+
started(renderFromInline(host, PALETTE_URL, inline.textContent ?? "", options));
|
|
6120
6374
|
}
|
|
6121
6375
|
});
|
|
6122
6376
|
}
|