@stocksharp/diagram 1.4.0 → 1.5.1
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 +84 -1
- package/dist/esm/canvas-renderer.js +169 -26
- package/dist/esm/canvas-renderer.js.map +1 -1
- package/dist/esm/color.js +42 -0
- package/dist/esm/color.js.map +1 -1
- package/dist/esm/core/document.js +11 -0
- package/dist/esm/core/document.js.map +1 -1
- package/dist/esm/core/model.js.map +1 -1
- package/dist/esm/diagram/stocksharp-diagram.js +31 -3
- package/dist/esm/diagram/stocksharp-diagram.js.map +1 -1
- package/dist/esm/embed.js +104 -20
- 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-headless.js +197 -22
- package/dist/ssdiagram-headless.js.map +2 -2
- package/dist/ssdiagram.js +310 -44
- package/dist/ssdiagram.js.map +3 -3
- package/dist/types/canvas-renderer.d.ts +25 -3
- package/dist/types/canvas-renderer.d.ts.map +1 -1
- package/dist/types/color.d.ts +7 -0
- package/dist/types/color.d.ts.map +1 -1
- package/dist/types/core/model.d.ts +7 -0
- package/dist/types/core/model.d.ts.map +1 -1
- package/dist/types/diagram/api.d.ts +7 -0
- package/dist/types/diagram/api.d.ts.map +1 -1
- package/dist/types/diagram/stocksharp-diagram.d.ts +14 -0
- package/dist/types/diagram/stocksharp-diagram.d.ts.map +1 -1
- package/dist/types/embed.d.ts +21 -0
- package/dist/types/embed.d.ts.map +1 -1
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/canvas-renderer.ts +172 -26
- package/src/color.ts +48 -0
- package/src/core/document.ts +12 -0
- package/src/core/model.ts +7 -0
- package/src/diagram/api.ts +7 -0
- package/src/diagram/stocksharp-diagram.ts +38 -3
- package/src/embed.ts +147 -29
- package/src/index.ts +2 -0
package/dist/ssdiagram.js
CHANGED
|
@@ -53,6 +53,7 @@ var SSDiagram = (() => {
|
|
|
53
53
|
parseDiagramViewState: () => parseDiagramViewState,
|
|
54
54
|
renderAll: () => renderAll,
|
|
55
55
|
renderDiagramSvg: () => renderDiagramSvg,
|
|
56
|
+
renderDocument: () => renderDocument,
|
|
56
57
|
renderFromInline: () => renderFromInline,
|
|
57
58
|
renderFromSource: () => renderFromSource,
|
|
58
59
|
renderScheme: () => renderScheme,
|
|
@@ -162,10 +163,12 @@ var SSDiagram = (() => {
|
|
|
162
163
|
function normalizeNode(value, path) {
|
|
163
164
|
const input = requireStructure(value, path);
|
|
164
165
|
const id = requireIdentifier(input.id, `${path}.id`);
|
|
166
|
+
const subtitle = requireOptionalText(input.subtitle, `${path}.subtitle`);
|
|
165
167
|
return {
|
|
166
168
|
id,
|
|
167
169
|
typeId: requireIdentifier(input.typeId ?? id, `${path}.typeId`),
|
|
168
170
|
name: requireString(input.name, `${path}.name`),
|
|
171
|
+
...subtitle === "" ? {} : { subtitle },
|
|
169
172
|
description: requireString(input.description ?? "", `${path}.description`),
|
|
170
173
|
groupName: requireString(input.groupName ?? "Common", `${path}.groupName`),
|
|
171
174
|
x: requireFiniteNumber(input.x ?? 0, `${path}.x`),
|
|
@@ -216,14 +219,19 @@ var SSDiagram = (() => {
|
|
|
216
219
|
}
|
|
217
220
|
function normalizeLink(value, path) {
|
|
218
221
|
const input = requireStructure(value, path);
|
|
222
|
+
const label2 = requireOptionalText(input.label, `${path}.label`);
|
|
219
223
|
return {
|
|
220
224
|
id: requireIdentifier(input.id, `${path}.id`),
|
|
221
225
|
from: normalizeEndpoint(input.from, `${path}.from`),
|
|
222
226
|
to: normalizeEndpoint(input.to, `${path}.to`),
|
|
223
227
|
style: requireLinkStyle(input.style, `${path}.style`),
|
|
228
|
+
...label2 === "" ? {} : { label: label2 },
|
|
224
229
|
metadata: cloneJsonObject(input.metadata ?? {}, `${path}.metadata`)
|
|
225
230
|
};
|
|
226
231
|
}
|
|
232
|
+
function requireOptionalText(value, path) {
|
|
233
|
+
return value === void 0 || value === null ? "" : requireString(value, path);
|
|
234
|
+
}
|
|
227
235
|
function requireLinkStyle(value, path) {
|
|
228
236
|
if (value === void 0 || value === null) return "solid";
|
|
229
237
|
if (value !== "solid" && value !== "dashed") {
|
|
@@ -263,6 +271,7 @@ var SSDiagram = (() => {
|
|
|
263
271
|
id: requireString(node.id, `${path}.id`),
|
|
264
272
|
typeId: requireString(node.typeId, `${path}.typeId`),
|
|
265
273
|
name: requireString(node.name, `${path}.name`),
|
|
274
|
+
subtitle: node.subtitle,
|
|
266
275
|
description: requireString(node.description, `${path}.description`),
|
|
267
276
|
groupName: requireString(node.groupName, `${path}.groupName`),
|
|
268
277
|
x: requireNumber(node.x, `${path}.x`),
|
|
@@ -318,6 +327,7 @@ var SSDiagram = (() => {
|
|
|
318
327
|
from: parseEndpoint(link.from, `${path}.from`),
|
|
319
328
|
to: parseEndpoint(link.to, `${path}.to`),
|
|
320
329
|
style: link.style === void 0 ? void 0 : requireLinkStyle(link.style, `${path}.style`),
|
|
330
|
+
label: link.label,
|
|
321
331
|
metadata: cloneJsonObject(link.metadata, `${path}.metadata`)
|
|
322
332
|
}, path);
|
|
323
333
|
}
|
|
@@ -913,6 +923,42 @@ var SSDiagram = (() => {
|
|
|
913
923
|
return color;
|
|
914
924
|
return `hsl(${Math.round(hue * 10) / 10}, ${Math.round(saturation * 1e3) / 10}%, ${Math.round(maxLightness * 1e3) / 10}%)`;
|
|
915
925
|
}
|
|
926
|
+
function resolveCssVariables(value, lookup) {
|
|
927
|
+
return resolveAt(value, lookup, 0);
|
|
928
|
+
}
|
|
929
|
+
function resolveAt(value, lookup, depth) {
|
|
930
|
+
const start = value.indexOf("var(");
|
|
931
|
+
if (start < 0)
|
|
932
|
+
return value;
|
|
933
|
+
if (depth > 8)
|
|
934
|
+
return null;
|
|
935
|
+
let level = 0;
|
|
936
|
+
let end = -1;
|
|
937
|
+
for (let i = start + 3; i < value.length; i += 1) {
|
|
938
|
+
if (value[i] === "(")
|
|
939
|
+
level += 1;
|
|
940
|
+
else if (value[i] === ")" && --level === 0) {
|
|
941
|
+
end = i;
|
|
942
|
+
break;
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
if (end < 0)
|
|
946
|
+
return null;
|
|
947
|
+
const inner = value.slice(start + 4, end);
|
|
948
|
+
const comma = inner.indexOf(",");
|
|
949
|
+
const name = (comma < 0 ? inner : inner.slice(0, comma)).trim();
|
|
950
|
+
const fallback = comma < 0 ? "" : inner.slice(comma + 1).trim();
|
|
951
|
+
let replacement = name.startsWith("--") ? lookup(name).trim() : "";
|
|
952
|
+
if (replacement === "")
|
|
953
|
+
replacement = fallback;
|
|
954
|
+
if (replacement === "")
|
|
955
|
+
return null;
|
|
956
|
+
const resolved = resolveAt(replacement, lookup, depth + 1);
|
|
957
|
+
const rest = resolveAt(value.slice(end + 1), lookup, depth);
|
|
958
|
+
if (resolved === null || rest === null)
|
|
959
|
+
return null;
|
|
960
|
+
return value.slice(0, start) + resolved + rest;
|
|
961
|
+
}
|
|
916
962
|
|
|
917
963
|
// src/core/history.ts
|
|
918
964
|
var DiagramCommandHistory = class {
|
|
@@ -1202,6 +1248,7 @@ var SSDiagram = (() => {
|
|
|
1202
1248
|
this.id = id;
|
|
1203
1249
|
this.typeId = init.typeId ?? id;
|
|
1204
1250
|
this.name = init.name;
|
|
1251
|
+
this.subtitle = init.subtitle ?? "";
|
|
1205
1252
|
this.description = init.description ?? "";
|
|
1206
1253
|
this.groupName = init.groupName ?? "Common";
|
|
1207
1254
|
this.color = init.color ?? "#d7d7d7";
|
|
@@ -1227,6 +1274,7 @@ var SSDiagram = (() => {
|
|
|
1227
1274
|
id: this.id,
|
|
1228
1275
|
typeId: this.typeId,
|
|
1229
1276
|
name: this.name,
|
|
1277
|
+
...this.subtitle === "" ? {} : { subtitle: this.subtitle },
|
|
1230
1278
|
description: this.description,
|
|
1231
1279
|
groupName: this.groupName,
|
|
1232
1280
|
color: this.color,
|
|
@@ -1248,12 +1296,13 @@ var SSDiagram = (() => {
|
|
|
1248
1296
|
}
|
|
1249
1297
|
};
|
|
1250
1298
|
var LinkModel = class {
|
|
1251
|
-
constructor(from, fromPort, to, toPort, id = "", metadata, style = "solid") {
|
|
1299
|
+
constructor(from, fromPort, to, toPort, id = "", metadata, style = "solid", label2 = "") {
|
|
1252
1300
|
this.from = from;
|
|
1253
1301
|
this.fromPort = fromPort;
|
|
1254
1302
|
this.to = to;
|
|
1255
1303
|
this.toPort = toPort;
|
|
1256
1304
|
this.style = style;
|
|
1305
|
+
this.label = label2;
|
|
1257
1306
|
this.id = id;
|
|
1258
1307
|
this.metadata = copyJsonObject(metadata);
|
|
1259
1308
|
}
|
|
@@ -1268,6 +1317,7 @@ var SSDiagram = (() => {
|
|
|
1268
1317
|
to: this.to,
|
|
1269
1318
|
toPort: this.toPort,
|
|
1270
1319
|
style: this.style,
|
|
1320
|
+
...this.label === "" ? {} : { label: this.label },
|
|
1271
1321
|
metadata: copyJsonObject(this.metadata)
|
|
1272
1322
|
};
|
|
1273
1323
|
}
|
|
@@ -1288,6 +1338,15 @@ var SSDiagram = (() => {
|
|
|
1288
1338
|
var INTRO_RISE = 70;
|
|
1289
1339
|
var ERROR_FLASH_MS = 1100;
|
|
1290
1340
|
var ERROR_RED = "#f6465d";
|
|
1341
|
+
var DEFAULT_NODE_COLOR = "#d7d7d7";
|
|
1342
|
+
var DEFAULT_NODE_BORDER = "#8c8c8c";
|
|
1343
|
+
var DEFAULT_ZONE_TINT = "#8a8f98";
|
|
1344
|
+
var TITLE_FONT = "600 12px Segoe UI, Tahoma, sans-serif";
|
|
1345
|
+
var SUBTITLE_FONT = "11px Segoe UI, Tahoma, sans-serif";
|
|
1346
|
+
var SUBTITLE_MIN_H = 62;
|
|
1347
|
+
var LINK_LABEL_FONT = "600 10px Segoe UI, Tahoma, sans-serif";
|
|
1348
|
+
var ZONE_CAPTION_TOP = 9;
|
|
1349
|
+
var ZONE_CAPTION_H = 14;
|
|
1291
1350
|
var ERROR_BACKGROUND = "#7d2632";
|
|
1292
1351
|
var clamp = (v, lo, hi) => Math.min(hi, Math.max(lo, v));
|
|
1293
1352
|
function hashHue(s) {
|
|
@@ -1385,10 +1444,14 @@ var SSDiagram = (() => {
|
|
|
1385
1444
|
this.ovGeo = null;
|
|
1386
1445
|
this.validator = null;
|
|
1387
1446
|
this.handlers = /* @__PURE__ */ new Map();
|
|
1447
|
+
// Computed style of the host, read at most once per frame: colours written as var(--token)
|
|
1448
|
+
// resolve against it, so a frame drawn after a theme switch picks up the new values.
|
|
1449
|
+
this.hostStyle = null;
|
|
1388
1450
|
this.opts = opts;
|
|
1389
1451
|
if (opts.typeColors !== void 0) this.setTypeColorsInternal(opts.typeColors);
|
|
1390
1452
|
this.host = opts.host;
|
|
1391
1453
|
this.gridSnapEnabled = opts.gridSnap ?? false;
|
|
1454
|
+
this.pageScroll = opts.pageScroll ?? false;
|
|
1392
1455
|
this.gridSize = this.normalizeGridSize(opts.gridSize ?? DEFAULT_GRID_SIZE);
|
|
1393
1456
|
this.history = new DiagramCommandHistory(() => {
|
|
1394
1457
|
this.emit("undoStackChanged", { canUndo: this.canUndo(), canRedo: this.canRedo() });
|
|
@@ -1397,7 +1460,7 @@ var SSDiagram = (() => {
|
|
|
1397
1460
|
this.canvas.style.display = "block";
|
|
1398
1461
|
this.canvas.tabIndex = 0;
|
|
1399
1462
|
this.canvas.style.outline = "none";
|
|
1400
|
-
this.
|
|
1463
|
+
this.applyTouchAction();
|
|
1401
1464
|
this.canvas.style.userSelect = "none";
|
|
1402
1465
|
this.canvas.style.webkitUserSelect = "none";
|
|
1403
1466
|
this.canvas.style.webkitTouchCallout = "none";
|
|
@@ -1451,6 +1514,19 @@ var SSDiagram = (() => {
|
|
|
1451
1514
|
this.gridSize = nextSize;
|
|
1452
1515
|
this.scheduleDraw();
|
|
1453
1516
|
}
|
|
1517
|
+
/** See DiagramOptions.pageScroll. */
|
|
1518
|
+
setPageScroll(enabled) {
|
|
1519
|
+
this.pageScroll = enabled;
|
|
1520
|
+
this.applyTouchAction();
|
|
1521
|
+
}
|
|
1522
|
+
isPageScrollEnabled() {
|
|
1523
|
+
return this.pageScroll;
|
|
1524
|
+
}
|
|
1525
|
+
// pan-y hands a vertical swipe to the browser, which scrolls the page; a horizontal one and a
|
|
1526
|
+
// two-finger pinch still reach the canvas.
|
|
1527
|
+
applyTouchAction() {
|
|
1528
|
+
this.canvas.style.touchAction = this.pageScroll ? "pan-y" : "none";
|
|
1529
|
+
}
|
|
1454
1530
|
getGridSnap() {
|
|
1455
1531
|
return { enabled: this.gridSnapEnabled, size: this.gridSize };
|
|
1456
1532
|
}
|
|
@@ -1666,7 +1742,7 @@ var SSDiagram = (() => {
|
|
|
1666
1742
|
this.emit("linkValidation", { fromNode: fn, from: fp, toNode: tn, to: tp, ...validation });
|
|
1667
1743
|
if (!validation.allowed) return false;
|
|
1668
1744
|
}
|
|
1669
|
-
const link = new LinkModel(init.from, init.fromPort, init.to, init.toPort, init.id ?? this.nextLinkId(), init.metadata, init.style ?? "solid");
|
|
1745
|
+
const link = new LinkModel(init.from, init.fromPort, init.to, init.toPort, init.id ?? this.nextLinkId(), init.metadata, init.style ?? "solid", init.label ?? "");
|
|
1670
1746
|
if (this.links.some((l) => l.id === link.id)) return false;
|
|
1671
1747
|
this.links.splice(clamp(Math.trunc(index), 0, this.links.length), 0, link);
|
|
1672
1748
|
this.emit("linkAdded", { link });
|
|
@@ -1938,6 +2014,7 @@ var SSDiagram = (() => {
|
|
|
1938
2014
|
updateNode(nodeId, patch) {
|
|
1939
2015
|
return this.updateNodeState(nodeId, "update node", (node) => {
|
|
1940
2016
|
if (patch.name !== void 0) node.name = patch.name;
|
|
2017
|
+
if (patch.subtitle !== void 0) node.subtitle = patch.subtitle;
|
|
1941
2018
|
if (patch.description !== void 0) node.description = patch.description;
|
|
1942
2019
|
if (patch.color !== void 0) node.color = patch.color;
|
|
1943
2020
|
if (patch.border !== void 0) node.border = patch.border;
|
|
@@ -1983,6 +2060,7 @@ var SSDiagram = (() => {
|
|
|
1983
2060
|
const restored = new NodeModel(snapshot, nodeId);
|
|
1984
2061
|
node.typeId = restored.typeId;
|
|
1985
2062
|
node.name = restored.name;
|
|
2063
|
+
node.subtitle = restored.subtitle;
|
|
1986
2064
|
node.description = restored.description;
|
|
1987
2065
|
node.groupName = restored.groupName;
|
|
1988
2066
|
node.color = restored.color;
|
|
@@ -2013,6 +2091,10 @@ var SSDiagram = (() => {
|
|
|
2013
2091
|
return true;
|
|
2014
2092
|
}
|
|
2015
2093
|
load(nodes, links) {
|
|
2094
|
+
this.loadModel(nodes, links, []);
|
|
2095
|
+
}
|
|
2096
|
+
// Zones are placed before the view is fitted, so the first frame frames them too.
|
|
2097
|
+
loadModel(nodes, links, zones) {
|
|
2016
2098
|
this.clear();
|
|
2017
2099
|
this.idSeq = 1;
|
|
2018
2100
|
this.linkSeq = 1;
|
|
@@ -2039,9 +2121,10 @@ var SSDiagram = (() => {
|
|
|
2039
2121
|
if (this.links.some((existing) => existing.id === id)) {
|
|
2040
2122
|
throw new Error(`ssdiagram: duplicate link id "${id}"`);
|
|
2041
2123
|
}
|
|
2042
|
-
const model = new LinkModel(link.from, link.fromPort, link.to, link.toPort, id, link.metadata, link.style ?? "solid");
|
|
2124
|
+
const model = new LinkModel(link.from, link.fromPort, link.to, link.toPort, id, link.metadata, link.style ?? "solid", link.label ?? "");
|
|
2043
2125
|
if (!this.links.some((existing) => existing.key() === model.key())) this.links.push(model);
|
|
2044
2126
|
}
|
|
2127
|
+
this.zones = zones.map((zone) => ({ ...zone, metadata: copyJsonObject(zone.metadata) }));
|
|
2045
2128
|
for (const node of this.nodes) {
|
|
2046
2129
|
if (node.loadError.length === 0) continue;
|
|
2047
2130
|
const state = createDiagramNodeRuntimeState();
|
|
@@ -2064,13 +2147,14 @@ var SSDiagram = (() => {
|
|
|
2064
2147
|
to: l.to,
|
|
2065
2148
|
toPort: l.toPort,
|
|
2066
2149
|
style: l.style,
|
|
2150
|
+
...l.label === "" ? {} : { label: l.label },
|
|
2067
2151
|
metadata: copyJsonObject(l.metadata)
|
|
2068
2152
|
}))
|
|
2069
2153
|
};
|
|
2070
2154
|
}
|
|
2071
2155
|
loadDocument(source) {
|
|
2072
2156
|
const document2 = parseDiagramDocument(source);
|
|
2073
|
-
this.
|
|
2157
|
+
this.loadModel(
|
|
2074
2158
|
document2.nodes,
|
|
2075
2159
|
document2.links.map((link) => ({
|
|
2076
2160
|
id: link.id,
|
|
@@ -2079,10 +2163,11 @@ var SSDiagram = (() => {
|
|
|
2079
2163
|
to: link.to.nodeId,
|
|
2080
2164
|
toPort: link.to.portId,
|
|
2081
2165
|
style: link.style,
|
|
2166
|
+
label: link.label,
|
|
2082
2167
|
metadata: link.metadata
|
|
2083
|
-
}))
|
|
2168
|
+
})),
|
|
2169
|
+
document2.zones
|
|
2084
2170
|
);
|
|
2085
|
-
this.zones = document2.zones.map((zone) => ({ ...zone, metadata: copyJsonObject(zone.metadata) }));
|
|
2086
2171
|
this.documentMetadata = copyJsonObject(document2.metadata);
|
|
2087
2172
|
}
|
|
2088
2173
|
saveDocument() {
|
|
@@ -2093,6 +2178,7 @@ var SSDiagram = (() => {
|
|
|
2093
2178
|
from: { nodeId: link.from, portId: link.fromPort },
|
|
2094
2179
|
to: { nodeId: link.to, portId: link.toPort },
|
|
2095
2180
|
style: link.style,
|
|
2181
|
+
label: link.label,
|
|
2096
2182
|
metadata: link.metadata
|
|
2097
2183
|
})),
|
|
2098
2184
|
zones: this.zones.map((zone) => ({ ...zone, metadata: copyJsonObject(zone.metadata) })),
|
|
@@ -2519,8 +2605,10 @@ var SSDiagram = (() => {
|
|
|
2519
2605
|
if (t2.overviewViewportFill !== void 0) this.opts.overviewViewportFill = t2.overviewViewportFill;
|
|
2520
2606
|
this.scheduleDraw();
|
|
2521
2607
|
}
|
|
2608
|
+
// Zones count: fitting or exporting only the nodes cut off the places they sit in, captions
|
|
2609
|
+
// included. A caption that is taller than its zone still has to fit.
|
|
2522
2610
|
graphBounds() {
|
|
2523
|
-
if (this.nodes.length === 0) return null;
|
|
2611
|
+
if (this.nodes.length === 0 && this.zones.length === 0) return null;
|
|
2524
2612
|
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
2525
2613
|
for (const n2 of this.nodes) {
|
|
2526
2614
|
minX = Math.min(minX, n2.x);
|
|
@@ -2528,6 +2616,13 @@ var SSDiagram = (() => {
|
|
|
2528
2616
|
maxX = Math.max(maxX, n2.x + n2.w);
|
|
2529
2617
|
maxY = Math.max(maxY, n2.y + n2.h);
|
|
2530
2618
|
}
|
|
2619
|
+
for (const zone of this.zones) {
|
|
2620
|
+
const bottom = zone.name.length > 0 ? Math.max(zone.height, ZONE_CAPTION_TOP + ZONE_CAPTION_H) : zone.height;
|
|
2621
|
+
minX = Math.min(minX, zone.x - 1);
|
|
2622
|
+
minY = Math.min(minY, zone.y - 1);
|
|
2623
|
+
maxX = Math.max(maxX, zone.x + zone.width + 1);
|
|
2624
|
+
maxY = Math.max(maxY, zone.y + bottom + 1);
|
|
2625
|
+
}
|
|
2531
2626
|
return { minX, minY, maxX, maxY };
|
|
2532
2627
|
}
|
|
2533
2628
|
resize(w, h) {
|
|
@@ -2579,11 +2674,16 @@ var SSDiagram = (() => {
|
|
|
2579
2674
|
// ---- geometry ---------------------------------------------------
|
|
2580
2675
|
layoutNode(n2) {
|
|
2581
2676
|
const ctx = this.ctx;
|
|
2582
|
-
ctx.font =
|
|
2677
|
+
ctx.font = TITLE_FONT;
|
|
2583
2678
|
const titleW = ctx.measureText(n2.name).width;
|
|
2679
|
+
let subtitleW = 0;
|
|
2680
|
+
if (n2.subtitle.length > 0) {
|
|
2681
|
+
ctx.font = SUBTITLE_FONT;
|
|
2682
|
+
subtitleW = ctx.measureText(n2.subtitle).width;
|
|
2683
|
+
}
|
|
2584
2684
|
const rows = Math.max(n2.inPorts.length, n2.outPorts.length, 1);
|
|
2585
|
-
n2.w = Math.max(170, Math.ceil(titleW) + 28);
|
|
2586
|
-
n2.h = Math.max(48, rows * PORT_ROW_H + 16);
|
|
2685
|
+
n2.w = Math.max(170, Math.ceil(titleW) + 28, Math.ceil(subtitleW) + 28);
|
|
2686
|
+
n2.h = Math.max(n2.subtitle.length > 0 ? SUBTITLE_MIN_H : 48, rows * PORT_ROW_H + 16);
|
|
2587
2687
|
const off = PORT_SQ / 2;
|
|
2588
2688
|
const place = (ports, x) => {
|
|
2589
2689
|
const stack = ports.some(isGrowAnchor) ? [...ports.filter((p) => !isGrowAnchor(p)), ...ports.filter(isGrowAnchor)] : ports;
|
|
@@ -2942,6 +3042,7 @@ var SSDiagram = (() => {
|
|
|
2942
3042
|
// The clipboard is built from saveDocument(), so it carries the style;
|
|
2943
3043
|
// rebuilding without it silently turned every pasted dashed link solid.
|
|
2944
3044
|
style: l.style,
|
|
3045
|
+
label: l.label,
|
|
2945
3046
|
metadata: l.metadata
|
|
2946
3047
|
});
|
|
2947
3048
|
}
|
|
@@ -3327,6 +3428,7 @@ var SSDiagram = (() => {
|
|
|
3327
3428
|
this.listen(window, "pointerup", finish);
|
|
3328
3429
|
this.listen(window, "pointercancel", finish);
|
|
3329
3430
|
this.listen(this.canvas, "wheel", (e) => {
|
|
3431
|
+
if (this.pageScroll && !e.ctrlKey && !e.metaKey) return;
|
|
3330
3432
|
e.preventDefault();
|
|
3331
3433
|
const [sx, sy] = localXY(e);
|
|
3332
3434
|
const [wx, wy] = this.toWorld(sx, sy);
|
|
@@ -3472,6 +3574,7 @@ var SSDiagram = (() => {
|
|
|
3472
3574
|
}
|
|
3473
3575
|
draw(options = SCREEN_RENDER_OPTIONS) {
|
|
3474
3576
|
const ctx = this.ctx;
|
|
3577
|
+
this.hostStyle = null;
|
|
3475
3578
|
ctx.clearRect(0, 0, this.width, this.height);
|
|
3476
3579
|
const background = this.opts.background ?? "#1b1b1f";
|
|
3477
3580
|
ctx.fillStyle = background;
|
|
@@ -3499,6 +3602,7 @@ var SSDiagram = (() => {
|
|
|
3499
3602
|
);
|
|
3500
3603
|
this.drawZones();
|
|
3501
3604
|
const prior = [];
|
|
3605
|
+
const labels = [];
|
|
3502
3606
|
const hoveredNode = this.hoverPort?.node ?? this.hoverNode;
|
|
3503
3607
|
for (const l of this.links) {
|
|
3504
3608
|
const a = this.endpoint(l, "from");
|
|
@@ -3512,6 +3616,7 @@ var SSDiagram = (() => {
|
|
|
3512
3616
|
const pts = this.routeLink(a, b, /* @__PURE__ */ new Set([l.from, l.to]));
|
|
3513
3617
|
this.strokeRoute(pts, color, width, prior, l.style);
|
|
3514
3618
|
this.drawArrow(pts, color);
|
|
3619
|
+
if (l.label.length > 0) labels.push({ pts, text: l.label, color });
|
|
3515
3620
|
for (let k = 1; k < pts.length; k += 1) {
|
|
3516
3621
|
const [x1, y1] = pts[k - 1];
|
|
3517
3622
|
const [x2, y2] = pts[k];
|
|
@@ -3519,6 +3624,7 @@ var SSDiagram = (() => {
|
|
|
3519
3624
|
else if (y1 === y2 && x1 !== x2) prior.push({ h: true, c: y1, a: Math.min(x1, x2), b: Math.max(x1, x2) });
|
|
3520
3625
|
}
|
|
3521
3626
|
}
|
|
3627
|
+
for (const label2 of labels) this.drawLinkLabel(label2.pts, label2.text, label2.color, background);
|
|
3522
3628
|
if (options.transient && (this.linking !== null || this.relinking !== null)) this.drawPendingLink();
|
|
3523
3629
|
this.portFeed = this.feedTypes();
|
|
3524
3630
|
for (const n2 of this.nodes) this.drawNode(n2, options.selection && this.selectedNodes.has(n2), options);
|
|
@@ -3619,7 +3725,8 @@ ${runtime.error}`;
|
|
|
3619
3725
|
} else if (n2.description.length > 0) {
|
|
3620
3726
|
text = n2.description;
|
|
3621
3727
|
} else {
|
|
3622
|
-
|
|
3728
|
+
const typed = n2.typeId.length > 0 && n2.typeId !== n2.name && n2.typeId !== n2.id;
|
|
3729
|
+
text = typed ? `${n2.name} [${n2.typeId}]` : n2.name;
|
|
3623
3730
|
}
|
|
3624
3731
|
} else {
|
|
3625
3732
|
return;
|
|
@@ -3673,8 +3780,9 @@ ${runtime.error}`;
|
|
|
3673
3780
|
drawZones() {
|
|
3674
3781
|
if (this.zones.length === 0) return;
|
|
3675
3782
|
const ctx = this.ctx;
|
|
3783
|
+
const defaultTint = this.resolveColor(this.opts.zoneColor ?? DEFAULT_ZONE_TINT, DEFAULT_ZONE_TINT);
|
|
3676
3784
|
for (const zone of this.zones) {
|
|
3677
|
-
const tint = zone.color.length > 0 ? zone.color :
|
|
3785
|
+
const tint = zone.color.length > 0 ? this.resolveColor(zone.color, defaultTint) : defaultTint;
|
|
3678
3786
|
ctx.save();
|
|
3679
3787
|
roundRect(ctx, zone.x, zone.y, zone.width, zone.height, 10);
|
|
3680
3788
|
ctx.globalAlpha = 0.12;
|
|
@@ -3690,10 +3798,10 @@ ${runtime.error}`;
|
|
|
3690
3798
|
ctx.save();
|
|
3691
3799
|
ctx.globalAlpha = 0.75;
|
|
3692
3800
|
ctx.fillStyle = tint;
|
|
3693
|
-
ctx.font =
|
|
3801
|
+
ctx.font = TITLE_FONT;
|
|
3694
3802
|
ctx.textAlign = "left";
|
|
3695
3803
|
ctx.textBaseline = "top";
|
|
3696
|
-
ctx.fillText(zone.name, zone.x + 12, zone.y +
|
|
3804
|
+
ctx.fillText(zone.name, zone.x + 12, zone.y + ZONE_CAPTION_TOP, Math.max(0, zone.width - 24));
|
|
3697
3805
|
ctx.restore();
|
|
3698
3806
|
}
|
|
3699
3807
|
}
|
|
@@ -3738,11 +3846,12 @@ ${runtime.error}`;
|
|
|
3738
3846
|
const hasRuntimeError = options.runtime && n2.runtimeError.length > 0;
|
|
3739
3847
|
const runtime = options.runtime ? getJsonKey(this.runtimeState.nodes, n2.id) : void 0;
|
|
3740
3848
|
const active = options.runtime && (this.runtimeState.activeNodeId === n2.id || runtime?.active === true);
|
|
3849
|
+
const fill = this.resolveColor(n2.color, DEFAULT_NODE_COLOR);
|
|
3741
3850
|
roundRect(ctx, n2.x, n2.y, n2.w, n2.h, 6);
|
|
3742
|
-
ctx.fillStyle = hasLoadError ? ERROR_BACKGROUND : active ? "#ffd1dc" :
|
|
3851
|
+
ctx.fillStyle = hasLoadError ? ERROR_BACKGROUND : active ? "#ffd1dc" : fill;
|
|
3743
3852
|
ctx.fill();
|
|
3744
3853
|
ctx.lineWidth = selected ? 2 : 1.5;
|
|
3745
|
-
ctx.strokeStyle = selected ? this.selectionColor() : n2.border;
|
|
3854
|
+
ctx.strokeStyle = selected ? this.selectionColor() : this.resolveColor(n2.border, DEFAULT_NODE_BORDER);
|
|
3746
3855
|
ctx.stroke();
|
|
3747
3856
|
if (hasLoadError || hasRuntimeError) {
|
|
3748
3857
|
let alpha = 1;
|
|
@@ -3775,11 +3884,22 @@ ${runtime.error}`;
|
|
|
3775
3884
|
}
|
|
3776
3885
|
}
|
|
3777
3886
|
const titleShift = n2.icon ? iconW + 4 : 0;
|
|
3778
|
-
|
|
3779
|
-
|
|
3887
|
+
const textX = n2.x + titleShift + (n2.w - titleShift) / 2;
|
|
3888
|
+
const textWidth = n2.w - titleShift - 14;
|
|
3889
|
+
ctx.fillStyle = hasLoadError ? "#ffffff" : readableTextOn(active ? "#ffd1dc" : fill);
|
|
3890
|
+
ctx.font = TITLE_FONT;
|
|
3780
3891
|
ctx.textAlign = "center";
|
|
3781
3892
|
ctx.textBaseline = "middle";
|
|
3782
|
-
|
|
3893
|
+
if (n2.subtitle.length === 0) {
|
|
3894
|
+
ctx.fillText(n2.name, textX, n2.y + n2.h / 2, textWidth);
|
|
3895
|
+
} else {
|
|
3896
|
+
ctx.fillText(n2.name, textX, n2.y + n2.h / 2 - 8, textWidth);
|
|
3897
|
+
ctx.save();
|
|
3898
|
+
ctx.globalAlpha *= 0.72;
|
|
3899
|
+
ctx.font = SUBTITLE_FONT;
|
|
3900
|
+
ctx.fillText(n2.subtitle, textX, n2.y + n2.h / 2 + 9, textWidth);
|
|
3901
|
+
ctx.restore();
|
|
3902
|
+
}
|
|
3783
3903
|
for (const p of n2.inPorts) this.drawPort(n2, p, options);
|
|
3784
3904
|
for (const p of n2.outPorts) this.drawPort(n2, p, options);
|
|
3785
3905
|
}
|
|
@@ -3896,6 +4016,45 @@ ${runtime.error}`;
|
|
|
3896
4016
|
ctx.fill();
|
|
3897
4017
|
ctx.restore();
|
|
3898
4018
|
}
|
|
4019
|
+
// A plate of the canvas colour under the text, edged in the wire's colour: the label reads the same
|
|
4020
|
+
// whatever the wire crosses, and still says which wire it belongs to.
|
|
4021
|
+
drawLinkLabel(pts, text, color, background) {
|
|
4022
|
+
const [x, y] = pathMidpoint(pts);
|
|
4023
|
+
const ctx = this.ctx;
|
|
4024
|
+
ctx.save();
|
|
4025
|
+
ctx.font = LINK_LABEL_FONT;
|
|
4026
|
+
const w = Math.ceil(ctx.measureText(text).width) + 12;
|
|
4027
|
+
const h = 16;
|
|
4028
|
+
roundRect(ctx, x - w / 2, y - h / 2, w, h, h / 2);
|
|
4029
|
+
ctx.fillStyle = background;
|
|
4030
|
+
ctx.fill();
|
|
4031
|
+
ctx.lineWidth = 1;
|
|
4032
|
+
ctx.strokeStyle = color;
|
|
4033
|
+
ctx.stroke();
|
|
4034
|
+
ctx.fillStyle = readableTextOn(background);
|
|
4035
|
+
ctx.textAlign = "center";
|
|
4036
|
+
ctx.textBaseline = "middle";
|
|
4037
|
+
ctx.fillText(text, x, y);
|
|
4038
|
+
ctx.restore();
|
|
4039
|
+
}
|
|
4040
|
+
// Scheme colours may name a custom property of the page, var(--token), which a canvas cannot
|
|
4041
|
+
// paint. They resolve against the host element; one that resolves to nothing and has no
|
|
4042
|
+
// fallback of its own gets the renderer's default for that slot.
|
|
4043
|
+
resolveColor(value, fallback) {
|
|
4044
|
+
if (!value.includes("var(")) return value;
|
|
4045
|
+
return resolveCssVariables(value, (name) => this.hostProperty(name)) ?? fallback;
|
|
4046
|
+
}
|
|
4047
|
+
hostProperty(name) {
|
|
4048
|
+
if (this.hostStyle === null) {
|
|
4049
|
+
if (typeof getComputedStyle !== "function") return "";
|
|
4050
|
+
try {
|
|
4051
|
+
this.hostStyle = getComputedStyle(this.host);
|
|
4052
|
+
} catch {
|
|
4053
|
+
return "";
|
|
4054
|
+
}
|
|
4055
|
+
}
|
|
4056
|
+
return this.hostStyle.getPropertyValue(name);
|
|
4057
|
+
}
|
|
3899
4058
|
drawSelectedLinkEndpoints() {
|
|
3900
4059
|
if (this.selectedLink === null) return;
|
|
3901
4060
|
const ctx = this.ctx;
|
|
@@ -4011,9 +4170,9 @@ ${runtime.error}`;
|
|
|
4011
4170
|
ctx.stroke();
|
|
4012
4171
|
ctx.clip();
|
|
4013
4172
|
for (const n2 of this.nodes) {
|
|
4014
|
-
ctx.fillStyle = n2.color;
|
|
4173
|
+
ctx.fillStyle = this.resolveColor(n2.color, DEFAULT_NODE_COLOR);
|
|
4015
4174
|
ctx.fillRect(mx(n2.x), my(n2.y), Math.max(2, n2.w * s), Math.max(2, n2.h * s));
|
|
4016
|
-
ctx.strokeStyle = n2.border;
|
|
4175
|
+
ctx.strokeStyle = this.resolveColor(n2.border, DEFAULT_NODE_BORDER);
|
|
4017
4176
|
ctx.lineWidth = 1;
|
|
4018
4177
|
ctx.strokeRect(mx(n2.x), my(n2.y), Math.max(2, n2.w * s), Math.max(2, n2.h * s));
|
|
4019
4178
|
}
|
|
@@ -4054,6 +4213,23 @@ ${runtime.error}`;
|
|
|
4054
4213
|
ctx.arcTo(x, y, x + w, y, rr);
|
|
4055
4214
|
ctx.closePath();
|
|
4056
4215
|
}
|
|
4216
|
+
function pathMidpoint(pts) {
|
|
4217
|
+
let total = 0;
|
|
4218
|
+
for (let i = 1; i < pts.length; i += 1) total += Math.hypot(pts[i][0] - pts[i - 1][0], pts[i][1] - pts[i - 1][1]);
|
|
4219
|
+
let remaining = total / 2;
|
|
4220
|
+
for (let i = 1; i < pts.length; i += 1) {
|
|
4221
|
+
const [x1, y1] = pts[i - 1];
|
|
4222
|
+
const [x2, y2] = pts[i];
|
|
4223
|
+
const length = Math.hypot(x2 - x1, y2 - y1);
|
|
4224
|
+
if (length > 0 && remaining <= length) {
|
|
4225
|
+
const t2 = remaining / length;
|
|
4226
|
+
return [x1 + (x2 - x1) * t2, y1 + (y2 - y1) * t2];
|
|
4227
|
+
}
|
|
4228
|
+
remaining -= length;
|
|
4229
|
+
}
|
|
4230
|
+
const last = pts[pts.length - 1];
|
|
4231
|
+
return [last[0], last[1]];
|
|
4232
|
+
}
|
|
4057
4233
|
function route(a, b) {
|
|
4058
4234
|
const [ax, ay] = a;
|
|
4059
4235
|
const [bx, by] = b;
|
|
@@ -5038,6 +5214,7 @@ ${runtime.error}`;
|
|
|
5038
5214
|
this.fullscreen = false;
|
|
5039
5215
|
this.fullscreenButtonVisible = true;
|
|
5040
5216
|
this.downloadButtonVisible = false;
|
|
5217
|
+
this.exportEnabled = true;
|
|
5041
5218
|
this.downloadLabel = null;
|
|
5042
5219
|
// null means the host never named the button, so its wording comes from the shared bundle.
|
|
5043
5220
|
// Keeping the two apart is what lets an explicit label win while an untouched control still
|
|
@@ -5050,11 +5227,13 @@ ${runtime.error}`;
|
|
|
5050
5227
|
this.overviewContainer = options.overviewContainer ?? null;
|
|
5051
5228
|
this.zoomLabel = options.zoomLabel ?? null;
|
|
5052
5229
|
this.clipboard = this.resolveClipboard(options.clipboard);
|
|
5230
|
+
this.pageScroll = options.pageScroll ?? false;
|
|
5053
5231
|
this.canvas = new Diagram({
|
|
5054
5232
|
host: this.div,
|
|
5055
5233
|
typeColors: this.portTypeColors(),
|
|
5056
5234
|
gridSnap: options.gridSnap ?? true,
|
|
5057
|
-
gridSize: options.gridSize
|
|
5235
|
+
gridSize: options.gridSize,
|
|
5236
|
+
pageScroll: this.pageScroll
|
|
5058
5237
|
});
|
|
5059
5238
|
this.fullscreenButtonVisible = options.showFullscreenButton ?? true;
|
|
5060
5239
|
this.fullscreenLabels = options.fullscreenLabels ?? null;
|
|
@@ -5132,6 +5311,19 @@ ${runtime.error}`;
|
|
|
5132
5311
|
getGridSnap() {
|
|
5133
5312
|
return this.canvas.getGridSnap();
|
|
5134
5313
|
}
|
|
5314
|
+
/** See DiagramOptions.pageScroll. */
|
|
5315
|
+
setPageScroll(enabled) {
|
|
5316
|
+
this.pageScroll = enabled;
|
|
5317
|
+
this.applyPageScroll();
|
|
5318
|
+
}
|
|
5319
|
+
/** The requested setting, which stays true while fullscreen suspends it. */
|
|
5320
|
+
isPageScrollEnabled() {
|
|
5321
|
+
return this.pageScroll;
|
|
5322
|
+
}
|
|
5323
|
+
// Fullscreen covers the page, so there is no page left to scroll and every gesture goes to the diagram.
|
|
5324
|
+
applyPageScroll() {
|
|
5325
|
+
this.canvas.setPageScroll(this.pageScroll && !this.fullscreen);
|
|
5326
|
+
}
|
|
5135
5327
|
nudgeSelection(dx, dy) {
|
|
5136
5328
|
return this.canvas.nudgeSelection(dx, dy);
|
|
5137
5329
|
}
|
|
@@ -5313,6 +5505,17 @@ ${runtime.error}`;
|
|
|
5313
5505
|
this.downloadButton.hidden = false;
|
|
5314
5506
|
this.downloadButton.style.display = "inline-flex";
|
|
5315
5507
|
}
|
|
5508
|
+
/**
|
|
5509
|
+
* Turns the export commands of the context menu on or off. Off, the menu offers them greyed out and
|
|
5510
|
+
* `executeContextCommand` refuses them, for a page that does not want its diagram saved. The
|
|
5511
|
+
* download button is separate: hide it with `setDownloadButtonVisible(false)`.
|
|
5512
|
+
*/
|
|
5513
|
+
setExportEnabled(enabled) {
|
|
5514
|
+
this.exportEnabled = enabled;
|
|
5515
|
+
}
|
|
5516
|
+
isExportEnabled() {
|
|
5517
|
+
return this.exportEnabled;
|
|
5518
|
+
}
|
|
5316
5519
|
isDownloadButtonVisible() {
|
|
5317
5520
|
return this.downloadButtonVisible;
|
|
5318
5521
|
}
|
|
@@ -5355,6 +5558,7 @@ ${runtime.error}`;
|
|
|
5355
5558
|
setFullscreenState(fullscreen) {
|
|
5356
5559
|
if (this.fullscreen === fullscreen) return;
|
|
5357
5560
|
this.fullscreen = fullscreen;
|
|
5561
|
+
this.applyPageScroll();
|
|
5358
5562
|
this.updateFullscreenButton();
|
|
5359
5563
|
this.emit("fullscreenChanged", { fullscreen });
|
|
5360
5564
|
}
|
|
@@ -5814,15 +6018,15 @@ ${runtime.error}`;
|
|
|
5814
6018
|
// picture, and an empty diagram has none to give. They stay on in read-only mode,
|
|
5815
6019
|
// which forbids changing the diagram, not looking at it.
|
|
5816
6020
|
exportDocument: {
|
|
5817
|
-
canExecute: ({ document: document2 }) => document2.nodes.length > 0,
|
|
6021
|
+
canExecute: ({ document: document2 }) => this.exportEnabled && document2.nodes.length > 0,
|
|
5818
6022
|
execute: () => this.emit("exportRequested", { format: "document" })
|
|
5819
6023
|
},
|
|
5820
6024
|
exportPng: {
|
|
5821
|
-
canExecute: ({ document: document2 }) => document2.nodes.length > 0,
|
|
6025
|
+
canExecute: ({ document: document2 }) => this.exportEnabled && document2.nodes.length > 0,
|
|
5822
6026
|
execute: () => this.emit("exportRequested", { format: "png" })
|
|
5823
6027
|
},
|
|
5824
6028
|
exportSvg: {
|
|
5825
|
-
canExecute: ({ document: document2 }) => document2.nodes.length > 0,
|
|
6029
|
+
canExecute: ({ document: document2 }) => this.exportEnabled && document2.nodes.length > 0,
|
|
5826
6030
|
execute: () => this.emit("exportRequested", { format: "svg" })
|
|
5827
6031
|
},
|
|
5828
6032
|
// Always available: it shows the overview as readily as it hides it, and an empty
|
|
@@ -6458,17 +6662,62 @@ ${runtime.error}`;
|
|
|
6458
6662
|
if (renderRevisions.get(div) !== revision)
|
|
6459
6663
|
return null;
|
|
6460
6664
|
const catalog = buildCatalog(palette);
|
|
6665
|
+
return mountDiagram(div, catalog, options, false, (diagram) => {
|
|
6666
|
+
const missingTypeTemplate = div.dataset.diagramMissingElement ?? t("embedMissingElement", 'Element type "{typeId}" is missing from the palette.');
|
|
6667
|
+
const converted = toDiagramNodes(
|
|
6668
|
+
scheme,
|
|
6669
|
+
catalog,
|
|
6670
|
+
(typeId) => missingTypeTemplate.replace("{typeId}", typeId || "(empty)")
|
|
6671
|
+
);
|
|
6672
|
+
diagram.load(
|
|
6673
|
+
converted.nodes,
|
|
6674
|
+
scheme.links.map((link) => new Link({
|
|
6675
|
+
outNode: link.from,
|
|
6676
|
+
outPort: link.fromPort,
|
|
6677
|
+
inNode: link.to,
|
|
6678
|
+
inPort: link.toPort
|
|
6679
|
+
})),
|
|
6680
|
+
{ nodeErrors: converted.nodeErrors }
|
|
6681
|
+
);
|
|
6682
|
+
});
|
|
6683
|
+
}
|
|
6684
|
+
function renderDocument(div, source, options = {}) {
|
|
6685
|
+
const parsed = readDocument(source);
|
|
6686
|
+
beginRender(div);
|
|
6687
|
+
return mountDocument(div, parsed, options);
|
|
6688
|
+
}
|
|
6689
|
+
function readDocument(source) {
|
|
6690
|
+
let value = source;
|
|
6691
|
+
if (typeof source === "string") {
|
|
6692
|
+
try {
|
|
6693
|
+
value = JSON.parse(source);
|
|
6694
|
+
} catch (error) {
|
|
6695
|
+
throw new DiagramDocumentError(error instanceof Error ? error.message : "invalid JSON");
|
|
6696
|
+
}
|
|
6697
|
+
}
|
|
6698
|
+
if (!isRecord(value))
|
|
6699
|
+
throw new DiagramDocumentError("expected an object");
|
|
6700
|
+
if (value.version !== void 0 && value.version !== DIAGRAM_DOCUMENT_VERSION)
|
|
6701
|
+
throw new DiagramDocumentError(`unsupported document version ${String(value.version)}`, "$.version");
|
|
6702
|
+
return createDiagramDocument(value);
|
|
6703
|
+
}
|
|
6704
|
+
function mountDocument(div, source, options) {
|
|
6705
|
+
return mountDiagram(div, new StockSharpCatalog(), options, true, (diagram) => diagram.loadDocument(source));
|
|
6706
|
+
}
|
|
6707
|
+
function mountDiagram(div, catalog, options, pageScrollByDefault, populate) {
|
|
6461
6708
|
disposeActiveRender(div);
|
|
6462
6709
|
div.classList.remove("ss-diagram-error");
|
|
6463
6710
|
div.replaceChildren();
|
|
6464
6711
|
const [enterLabel, exitLabel] = (div.dataset.diagramFullscreen ?? "").split("|");
|
|
6465
6712
|
const downloadLabel = div.dataset.diagramDownload;
|
|
6466
|
-
const
|
|
6713
|
+
const exportAllowed = options.allowExport !== false && div.dataset.diagramExport !== "off";
|
|
6714
|
+
const showDownloadButton = exportAllowed && options.onExportRequested !== void 0;
|
|
6467
6715
|
let diagram;
|
|
6468
6716
|
try {
|
|
6469
6717
|
diagram = new StockSharpDiagram({
|
|
6470
6718
|
div,
|
|
6471
6719
|
catalog,
|
|
6720
|
+
pageScroll: options.pageScroll ?? pageScrollByDefault,
|
|
6472
6721
|
...enterLabel && exitLabel ? { fullscreenLabels: { enter: enterLabel, exit: exitLabel } } : {},
|
|
6473
6722
|
...showDownloadButton ? { showDownloadButton: true } : {},
|
|
6474
6723
|
...downloadLabel ? { downloadLabel } : {}
|
|
@@ -6518,7 +6767,9 @@ ${runtime.error}`;
|
|
|
6518
6767
|
activeRenders.set(div, handle);
|
|
6519
6768
|
connectedRenders.add(handle);
|
|
6520
6769
|
ensureDisconnectedHostObserver();
|
|
6521
|
-
if (
|
|
6770
|
+
if (!exportAllowed) {
|
|
6771
|
+
diagram.setExportEnabled(false);
|
|
6772
|
+
} else if (options.onExportRequested !== void 0) {
|
|
6522
6773
|
const notifyHost = options.onExportRequested;
|
|
6523
6774
|
cleanups.push(diagram.on("exportRequested", (request) => notifyHost(request, handle)));
|
|
6524
6775
|
}
|
|
@@ -6545,22 +6796,7 @@ ${runtime.error}`;
|
|
|
6545
6796
|
diagram.setReadOnly(true);
|
|
6546
6797
|
diagram.setLinkValidator(() => true);
|
|
6547
6798
|
diagram.setOverviewVisible(false);
|
|
6548
|
-
|
|
6549
|
-
const converted = toDiagramNodes(
|
|
6550
|
-
scheme,
|
|
6551
|
-
catalog,
|
|
6552
|
-
(typeId) => missingTypeTemplate.replace("{typeId}", typeId || "(empty)")
|
|
6553
|
-
);
|
|
6554
|
-
diagram.load(
|
|
6555
|
-
converted.nodes,
|
|
6556
|
-
scheme.links.map((link) => new Link({
|
|
6557
|
-
outNode: link.from,
|
|
6558
|
-
outPort: link.fromPort,
|
|
6559
|
-
inNode: link.to,
|
|
6560
|
-
inPort: link.toPort
|
|
6561
|
-
})),
|
|
6562
|
-
{ nodeErrors: converted.nodeErrors }
|
|
6563
|
-
);
|
|
6799
|
+
populate(diagram);
|
|
6564
6800
|
const fit = () => {
|
|
6565
6801
|
if (!div.isConnected) {
|
|
6566
6802
|
handle.destroy();
|
|
@@ -6657,6 +6893,8 @@ ${runtime.error}`;
|
|
|
6657
6893
|
note(div, errors.load, revision);
|
|
6658
6894
|
return null;
|
|
6659
6895
|
}
|
|
6896
|
+
if (holdsDocument(div, raw))
|
|
6897
|
+
return drawDocumentAtRevision(div, raw, revision, options, errors);
|
|
6660
6898
|
const scheme = parseRawScheme(raw);
|
|
6661
6899
|
if (scheme.nodes.length === 0) {
|
|
6662
6900
|
note(div, errors.empty, revision);
|
|
@@ -6679,6 +6917,8 @@ ${runtime.error}`;
|
|
|
6679
6917
|
note(div, errors.empty, revision);
|
|
6680
6918
|
return null;
|
|
6681
6919
|
}
|
|
6920
|
+
if (holdsDocument(div, raw))
|
|
6921
|
+
return drawDocumentAtRevision(div, raw, revision, options, errors);
|
|
6682
6922
|
const scheme = parseRawScheme(raw);
|
|
6683
6923
|
if (scheme.nodes.length === 0) {
|
|
6684
6924
|
note(div, errors.empty, revision);
|
|
@@ -6691,6 +6931,32 @@ ${runtime.error}`;
|
|
|
6691
6931
|
return null;
|
|
6692
6932
|
}
|
|
6693
6933
|
}
|
|
6934
|
+
function holdsDocument(div, raw) {
|
|
6935
|
+
if (div.dataset.diagramKind === "document")
|
|
6936
|
+
return true;
|
|
6937
|
+
return isRecord(raw) && typeof raw.version === "number" && Array.isArray(raw.nodes);
|
|
6938
|
+
}
|
|
6939
|
+
function drawDocumentAtRevision(div, raw, revision, options, errors) {
|
|
6940
|
+
if (renderRevisions.get(div) !== revision)
|
|
6941
|
+
return null;
|
|
6942
|
+
let parsed;
|
|
6943
|
+
try {
|
|
6944
|
+
parsed = readDocument(raw);
|
|
6945
|
+
} catch {
|
|
6946
|
+
note(div, errors.empty, revision);
|
|
6947
|
+
return null;
|
|
6948
|
+
}
|
|
6949
|
+
if (parsed.nodes.length === 0 && parsed.zones.length === 0) {
|
|
6950
|
+
note(div, errors.empty, revision);
|
|
6951
|
+
return null;
|
|
6952
|
+
}
|
|
6953
|
+
try {
|
|
6954
|
+
return mountDocument(div, parsed, options);
|
|
6955
|
+
} catch {
|
|
6956
|
+
note(div, errors.draw, revision);
|
|
6957
|
+
return null;
|
|
6958
|
+
}
|
|
6959
|
+
}
|
|
6694
6960
|
function renderAll(root = document, options = {}) {
|
|
6695
6961
|
root.querySelectorAll(".ss-diagram-host").forEach((host) => {
|
|
6696
6962
|
if (host.dataset.rendered === "1")
|