@phin-tech/dottie 0.8.0 → 0.10.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/PARITY.md +11 -8
- package/README.md +7 -0
- package/dist/compile.js +109 -9
- package/dist/dottie-compile.mjs +110 -10
- package/dist/dottie-player.js +821 -173
- package/dist/dottie-player.min.js +54 -53
- package/dist/dottie-sim.min.js +4 -0
- package/dist/dottie.js +803 -173
- package/dist/types/compile.d.ts +1 -1
- package/dist/types/connector.d.ts +4 -0
- package/dist/types/elbow.d.ts +18 -0
- package/dist/types/flows.d.ts +7 -3
- package/dist/types/index.d.ts +2 -2
- package/dist/types/measure.d.ts +29 -3
- package/dist/types/model.d.ts +32 -0
- package/dist/types/planner.d.ts +12 -0
- package/dist/types/player.d.ts +9 -0
- package/dist/types/sim-entry.d.ts +1 -0
- package/dist/types/svg.d.ts +9 -2
- package/dist/types/symbols.d.ts +19 -3
- package/dist/types/theme.d.ts +13 -1
- package/dist/types/timeline.d.ts +4 -2
- package/package.json +1 -1
package/dist/dottie-player.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! dottie.js 0.
|
|
1
|
+
/*! dottie.js 0.10.0 | Apache-2.0 | https://github.com/phin-tech/dottie */
|
|
2
2
|
var __defProp = Object.defineProperty;
|
|
3
3
|
var __defNormalProp = (obj, key, value2) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value: value2 }) : obj[key] = value2;
|
|
4
4
|
var __export = (target, all) => {
|
|
@@ -44,6 +44,8 @@ __export(index_exports, {
|
|
|
44
44
|
luminance: () => luminance,
|
|
45
45
|
orderedElements: () => orderedElements,
|
|
46
46
|
pageIndex: () => pageIndex,
|
|
47
|
+
pageTheme: () => pageTheme,
|
|
48
|
+
paletteColor: () => paletteColor,
|
|
47
49
|
parseDottie: () => parseDottie,
|
|
48
50
|
plan: () => plan,
|
|
49
51
|
posterTime: () => posterTime,
|
|
@@ -197,19 +199,80 @@ function trim(s) {
|
|
|
197
199
|
return s.replace(/^[ \t ]+|[ \t ]+$/g, "");
|
|
198
200
|
}
|
|
199
201
|
|
|
202
|
+
// src/theme.ts
|
|
203
|
+
var rgb = (r, g, b) => ({ r, g, b, a: 1 });
|
|
204
|
+
function hex(h) {
|
|
205
|
+
const n = parseInt(h.slice(1), 16);
|
|
206
|
+
return { r: (n >> 16 & 255) / 255, g: (n >> 8 & 255) / 255, b: (n & 255) / 255, a: 1 };
|
|
207
|
+
}
|
|
208
|
+
var PALETTE_NAMES = ["accent", "muted", "danger", "warning", "success", "info"];
|
|
209
|
+
var palette = (colors) => PALETTE_NAMES.map((name, i) => ({ name, color: hex(colors[i]) }));
|
|
210
|
+
var LIGHT_PALETTE = palette(["#2563eb", "#6b7280", "#dc2626", "#d97706", "#16a34a", "#0891b2"]);
|
|
211
|
+
var DARK_PALETTE = palette(["#93c5fd", "#cbd5e1", "#fca5a5", "#fcd34d", "#86efac", "#67e8f9"]);
|
|
212
|
+
var themes = [
|
|
213
|
+
{ id: "paper", name: "Paper", background: rgb(0.984, 0.98, 0.965), ink: rgb(0.12, 0.12, 0.13), palette: LIGHT_PALETTE },
|
|
214
|
+
{ id: "white", name: "White", background: rgb(1, 1, 1), ink: rgb(0.12, 0.12, 0.13), palette: LIGHT_PALETTE },
|
|
215
|
+
{
|
|
216
|
+
id: "sepia",
|
|
217
|
+
name: "Sepia",
|
|
218
|
+
background: rgb(0.953, 0.914, 0.824),
|
|
219
|
+
ink: rgb(0.23, 0.18, 0.13),
|
|
220
|
+
palette: palette(["#9a3412", "#78716c", "#b91c1c", "#b45309", "#4d7c0f", "#0e7490"])
|
|
221
|
+
},
|
|
222
|
+
{ id: "blueprint", name: "Blueprint", background: rgb(0.106, 0.227, 0.361), ink: rgb(0.91, 0.945, 0.984), palette: DARK_PALETTE },
|
|
223
|
+
{ id: "chalkboard", name: "Chalkboard", background: rgb(0.169, 0.227, 0.2), ink: rgb(0.945, 0.945, 0.918), palette: DARK_PALETTE },
|
|
224
|
+
{ id: "midnight", name: "Midnight", background: rgb(0.082, 0.086, 0.102), ink: rgb(0.925, 0.925, 0.94), palette: DARK_PALETTE }
|
|
225
|
+
];
|
|
226
|
+
function themeNamed(id) {
|
|
227
|
+
return themes.find((t) => t.id === id) ?? null;
|
|
228
|
+
}
|
|
229
|
+
function pageTheme(page) {
|
|
230
|
+
const c = page.customTheme;
|
|
231
|
+
if (c) return { id: "custom", name: c.name, background: c.background, ink: c.ink, palette: c.palette };
|
|
232
|
+
return themeNamed(page.theme);
|
|
233
|
+
}
|
|
234
|
+
function paletteColor(page, name) {
|
|
235
|
+
const n = name.trim().toLowerCase();
|
|
236
|
+
return (pageTheme(page)?.palette ?? LIGHT_PALETTE).find((p) => p.name.toLowerCase() === n)?.color ?? null;
|
|
237
|
+
}
|
|
238
|
+
function resolvingPalette(page) {
|
|
239
|
+
const named = (e) => !!e.style.named;
|
|
240
|
+
if (!page.elements.some(named) && !(page.symbols ?? []).some((s) => s.elements.some(named))) return page;
|
|
241
|
+
const resolve2 = (e) => {
|
|
242
|
+
const n = e.style.named;
|
|
243
|
+
if (!n) return e;
|
|
244
|
+
const style2 = { ...e.style };
|
|
245
|
+
const f = n.fill !== null ? paletteColor(page, n.fill) : null;
|
|
246
|
+
const s = n.stroke !== null ? paletteColor(page, n.stroke) : null;
|
|
247
|
+
const t = n.textColor !== null ? paletteColor(page, n.textColor) : null;
|
|
248
|
+
if (f) style2.fill = { ...f };
|
|
249
|
+
if (s) style2.stroke = { ...s };
|
|
250
|
+
if (t) style2.textColor = { ...t };
|
|
251
|
+
return { ...e, style: style2 };
|
|
252
|
+
};
|
|
253
|
+
return {
|
|
254
|
+
...page,
|
|
255
|
+
elements: page.elements.map(resolve2),
|
|
256
|
+
symbols: page.symbols ? page.symbols.map((s) => ({ ...s, elements: s.elements.map(resolve2) })) : page.symbols
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
200
260
|
// src/model.ts
|
|
201
261
|
var SUPPORTED_VERSION = 2;
|
|
202
262
|
var KINDS = ["rectangle", "ellipse", "diamond", "text", "connector", "freehand", "path", "note", "image", "symbol"];
|
|
203
263
|
var EASES = ["linear", "easeIn", "easeOut", "easeInOut", "hold", "spring", "bounce", "elastic"];
|
|
204
264
|
var KNOWN_FIELDS = {
|
|
205
265
|
file: ["version", "pages", "images"],
|
|
206
|
-
page: ["id", "name", "layers", "elements", "theme", "guidesX", "guidesY", "guidesLocked", "timeline", "symbols", "variables", "flows"],
|
|
266
|
+
page: ["id", "name", "layers", "elements", "theme", "customTheme", "guidesX", "guidesY", "guidesLocked", "timeline", "symbols", "variables", "flows"],
|
|
207
267
|
flow: ["id", "from", "to", "every", "speed", "color", "size", "when", "spread", "volume", "overrides"],
|
|
208
268
|
flowOverride: ["when", "volume"],
|
|
209
269
|
symbol: ["id", "name", "width", "height", "layers", "elements", "timeline", "button", "params"],
|
|
210
270
|
layer: ["id", "name", "visible", "locked"],
|
|
211
271
|
rgba: ["r", "g", "b", "a"],
|
|
212
|
-
style: ["stroke", "fill", "fillStyle", "strokeWidth", "dash", "roughness", "bowing", "fontSize", "font", "gridSize", "opacity", "dashOffset", "textColor"],
|
|
272
|
+
style: ["stroke", "fill", "fillStyle", "strokeWidth", "dash", "roughness", "bowing", "fontSize", "font", "gridSize", "opacity", "dashOffset", "textColor", "named"],
|
|
273
|
+
colorNames: ["fill", "stroke", "textColor"],
|
|
274
|
+
customTheme: ["name", "background", "ink", "palette", "base"],
|
|
275
|
+
paletteColor: ["name", "color"],
|
|
213
276
|
element: [
|
|
214
277
|
"id",
|
|
215
278
|
"kind",
|
|
@@ -247,8 +310,11 @@ var KNOWN_FIELDS = {
|
|
|
247
310
|
"capacity",
|
|
248
311
|
"delay",
|
|
249
312
|
"queue",
|
|
250
|
-
"gauge"
|
|
313
|
+
"gauge",
|
|
314
|
+
"containerID",
|
|
315
|
+
"container"
|
|
251
316
|
],
|
|
317
|
+
container: ["fixed", "lanes"],
|
|
252
318
|
gauge: ["metric", "of", "max", "warn", "alarm", "style", "label"],
|
|
253
319
|
strokePoint: ["x", "y"],
|
|
254
320
|
subpath: ["closed", "coords"],
|
|
@@ -257,7 +323,7 @@ var KNOWN_FIELDS = {
|
|
|
257
323
|
extensionRef: ["id", "shape", "version", "params"],
|
|
258
324
|
normalizedPoint: ["x", "y"],
|
|
259
325
|
normalizedRect: ["x", "y", "width", "height"],
|
|
260
|
-
connector: ["start", "end", "routing", "startHead", "endHead", "waypoints", "hops", "cost"],
|
|
326
|
+
connector: ["start", "end", "routing", "startHead", "endHead", "waypoints", "hops", "avoid", "cost"],
|
|
261
327
|
waypoint: ["x", "y"],
|
|
262
328
|
timeline: ["fps", "duration", "boil", "settle", "poster", "tracks", "camera", "markers"],
|
|
263
329
|
track: ["element", "keys", "boil", "follow", "repeats", "march"],
|
|
@@ -344,9 +410,36 @@ var Reader = class {
|
|
|
344
410
|
gridSize: num(s.gridSize) ?? null,
|
|
345
411
|
opacity: num(s.opacity) ?? null,
|
|
346
412
|
dashOffset: num(s.dashOffset) ?? null,
|
|
347
|
-
textColor: rgba(s.textColor) ?? null
|
|
413
|
+
textColor: rgba(s.textColor) ?? null,
|
|
414
|
+
named: this.colorNames(s.named)
|
|
348
415
|
};
|
|
349
416
|
}
|
|
417
|
+
container(v) {
|
|
418
|
+
if (!isObj(v)) return null;
|
|
419
|
+
this.fields(v, "container");
|
|
420
|
+
return { fixed: v.fixed === true ? true : null, lanes: v.lanes === "rows" || v.lanes === "columns" ? v.lanes : null };
|
|
421
|
+
}
|
|
422
|
+
colorNames(v) {
|
|
423
|
+
if (!isObj(v)) return null;
|
|
424
|
+
this.fields(v, "colorNames");
|
|
425
|
+
const n = { fill: str(v.fill) ?? null, stroke: str(v.stroke) ?? null, textColor: str(v.textColor) ?? null };
|
|
426
|
+
return n.fill === null && n.stroke === null && n.textColor === null ? null : n;
|
|
427
|
+
}
|
|
428
|
+
customTheme(v) {
|
|
429
|
+
if (!isObj(v)) return null;
|
|
430
|
+
this.fields(v, "customTheme");
|
|
431
|
+
const background = rgba(v.background), ink = rgba(v.ink);
|
|
432
|
+
if (!background || !ink) {
|
|
433
|
+
this.warn("A custom theme needs a background and an ink; drawn without it");
|
|
434
|
+
return null;
|
|
435
|
+
}
|
|
436
|
+
const palette2 = (Array.isArray(v.palette) ? v.palette : []).filter(isObj).flatMap((p) => {
|
|
437
|
+
this.fields(p, "paletteColor");
|
|
438
|
+
const name = str(p.name), color2 = rgba(p.color);
|
|
439
|
+
return name && color2 ? [{ name, color: color2 }] : [];
|
|
440
|
+
});
|
|
441
|
+
return { name: str(v.name) ?? "Custom", background, ink, palette: palette2, base: str(v.base) ?? null };
|
|
442
|
+
}
|
|
350
443
|
endpoint(v) {
|
|
351
444
|
const e = isObj(v) ? v : {};
|
|
352
445
|
const port = str(e.port);
|
|
@@ -378,6 +471,7 @@ var Reader = class {
|
|
|
378
471
|
return { x: num(w.x) ?? 0, y: num(w.y) ?? 0 };
|
|
379
472
|
}) : null,
|
|
380
473
|
hops: typeof v.hops === "boolean" ? v.hops : null,
|
|
474
|
+
avoid: typeof v.avoid === "boolean" ? v.avoid : null,
|
|
381
475
|
cost: typeof v.cost === "number" && Number.isFinite(v.cost) ? v.cost : null
|
|
382
476
|
};
|
|
383
477
|
}
|
|
@@ -446,7 +540,9 @@ var Reader = class {
|
|
|
446
540
|
capacity: num(v.capacity) ?? null,
|
|
447
541
|
delay: num(v.delay) ?? null,
|
|
448
542
|
queue: v.queue === true ? true : null,
|
|
449
|
-
gauge: this.gauge(v.gauge)
|
|
543
|
+
gauge: this.gauge(v.gauge),
|
|
544
|
+
containerID: uuid(v.containerID) ?? null,
|
|
545
|
+
container: this.container(v.container)
|
|
450
546
|
};
|
|
451
547
|
}
|
|
452
548
|
gauge(v) {
|
|
@@ -609,15 +705,15 @@ var Reader = class {
|
|
|
609
705
|
const keys = (Array.isArray(v.keys) ? v.keys : []).map((k) => this.keyframe(k)).filter((k) => k !== null && k.t >= 0 && k.t <= MAX_DURATION);
|
|
610
706
|
stableSortBy(keys, (k) => k.t);
|
|
611
707
|
const follow = Array.isArray(v.follow) ? v.follow.filter(isObj).flatMap((f) => {
|
|
612
|
-
const path = uuid(f.path),
|
|
613
|
-
if (!path ||
|
|
614
|
-
return [{ path, start, end, ease: this.ease(f.ease), reverse: bool(f.reverse) ?? null, orient: bool(f.orient) ?? null, laps: num(f.laps) ?? null }];
|
|
708
|
+
const path = uuid(f.path), start2 = num(f.start), end = num(f.end);
|
|
709
|
+
if (!path || start2 === void 0 || end === void 0 || start2 < 0 || end > MAX_DURATION) return [];
|
|
710
|
+
return [{ path, start: start2, end, ease: this.ease(f.ease), reverse: bool(f.reverse) ?? null, orient: bool(f.orient) ?? null, laps: num(f.laps) ?? null }];
|
|
615
711
|
}) : null;
|
|
616
712
|
const repeats = Array.isArray(v.repeats) ? v.repeats.filter(isObj).flatMap((r) => {
|
|
617
|
-
const
|
|
618
|
-
if (
|
|
713
|
+
const start2 = num(r.start), end = num(r.end);
|
|
714
|
+
if (start2 === void 0 || end === void 0) return [];
|
|
619
715
|
const count = num(r.count);
|
|
620
|
-
const rep = { start, end, count: count === void 0 ? null : Math.trunc(count) };
|
|
716
|
+
const rep = { start: start2, end, count: count === void 0 ? null : Math.trunc(count) };
|
|
621
717
|
return repeatIsValid(rep) ? [rep] : [];
|
|
622
718
|
}) : null;
|
|
623
719
|
return { element, keys, boil: bool(v.boil) ?? null, follow, repeats, march: clampMarch(num(v.march)) };
|
|
@@ -667,6 +763,7 @@ var Reader = class {
|
|
|
667
763
|
layers,
|
|
668
764
|
elements: (Array.isArray(v.elements) ? v.elements : []).map((e) => this.element(e, images)).filter((e) => e !== null),
|
|
669
765
|
theme: str(v.theme) ?? null,
|
|
766
|
+
customTheme: this.customTheme(v.customTheme),
|
|
670
767
|
timeline: this.timeline(v.timeline),
|
|
671
768
|
symbols: Array.isArray(v.symbols) ? v.symbols.map((x) => this.symbol(x, images)).filter((x) => x !== null) : null,
|
|
672
769
|
variables: this.variables(v.variables),
|
|
@@ -730,7 +827,7 @@ function parseDottie(source) {
|
|
|
730
827
|
if (isObj(v) && typeof v.data === "string") images[k] = reader.picture(v.data, str(v.format));
|
|
731
828
|
}
|
|
732
829
|
}
|
|
733
|
-
const pages = json.pages.map((p, i) => reader.page(p, images, i + 1));
|
|
830
|
+
const pages = json.pages.map((p, i) => resolvingPalette(reader.page(p, images, i + 1)));
|
|
734
831
|
if (new Set(pages.map((p) => p.id)).size !== pages.length) throw new DottieFormatError("Two pages share an id");
|
|
735
832
|
return { version, pages, warnings: reader.warnings };
|
|
736
833
|
}
|
|
@@ -750,7 +847,8 @@ function cloneElement(e) {
|
|
|
750
847
|
...e.style,
|
|
751
848
|
stroke: { ...e.style.stroke },
|
|
752
849
|
fill: { ...e.style.fill },
|
|
753
|
-
textColor: e.style.textColor ? { ...e.style.textColor } : null
|
|
850
|
+
textColor: e.style.textColor ? { ...e.style.textColor } : null,
|
|
851
|
+
named: e.style.named ? { ...e.style.named } : e.style.named
|
|
754
852
|
},
|
|
755
853
|
connector: e.connector ? {
|
|
756
854
|
...e.connector,
|
|
@@ -842,6 +940,21 @@ function pieces(para) {
|
|
|
842
940
|
if (cur || out.length === 0) out.push(cur);
|
|
843
941
|
return out;
|
|
844
942
|
}
|
|
943
|
+
function prefixWidths(chars, fontSize, face, font) {
|
|
944
|
+
const fam = familyMetrics(font);
|
|
945
|
+
const f = fam.faces[face] ?? fam.faces.regular;
|
|
946
|
+
const fallback = f.w["n"];
|
|
947
|
+
const out = [0];
|
|
948
|
+
let w = 0;
|
|
949
|
+
let prev = "";
|
|
950
|
+
for (const ch of chars) {
|
|
951
|
+
w += f.w[ch] ?? fallback;
|
|
952
|
+
if (prev) w += f.k[prev + ch] ?? 0;
|
|
953
|
+
prev = ch;
|
|
954
|
+
out.push(w * fontSize / 1e3);
|
|
955
|
+
}
|
|
956
|
+
return out;
|
|
957
|
+
}
|
|
845
958
|
function wrap(s, fontSize, maxWidth, face = "regular", font) {
|
|
846
959
|
const width = (t) => measure(t, fontSize, face, font);
|
|
847
960
|
const out = [];
|
|
@@ -857,8 +970,9 @@ function wrap(s, fontSize, maxWidth, face = "regular", font) {
|
|
|
857
970
|
}
|
|
858
971
|
while (width(line2.trimEnd()) > maxWidth && [...line2.trimEnd()].length > 1) {
|
|
859
972
|
const chars = [...line2];
|
|
973
|
+
const prefix = prefixWidths(chars, fontSize, face, font);
|
|
860
974
|
let n = chars.length - 1;
|
|
861
|
-
while (n > 1 &&
|
|
975
|
+
while (n > 1 && prefix[n] > maxWidth) n--;
|
|
862
976
|
out.push(chars.slice(0, n).join(""));
|
|
863
977
|
line2 = chars.slice(n).join("");
|
|
864
978
|
}
|
|
@@ -1013,13 +1127,13 @@ var Path = class _Path {
|
|
|
1013
1127
|
contours() {
|
|
1014
1128
|
const out = [];
|
|
1015
1129
|
let current = [];
|
|
1016
|
-
let
|
|
1130
|
+
let start2 = { x: 0, y: 0 };
|
|
1017
1131
|
for (const s of this.segments) {
|
|
1018
|
-
const last = current.length ? current[current.length - 1] :
|
|
1132
|
+
const last = current.length ? current[current.length - 1] : start2;
|
|
1019
1133
|
switch (s.op) {
|
|
1020
1134
|
case "M":
|
|
1021
1135
|
if (current.length > 1) out.push(current);
|
|
1022
|
-
|
|
1136
|
+
start2 = s.p;
|
|
1023
1137
|
current = [s.p];
|
|
1024
1138
|
break;
|
|
1025
1139
|
case "L":
|
|
@@ -1041,9 +1155,9 @@ var Path = class _Path {
|
|
|
1041
1155
|
}
|
|
1042
1156
|
break;
|
|
1043
1157
|
case "Z":
|
|
1044
|
-
current.push(
|
|
1158
|
+
current.push(start2);
|
|
1045
1159
|
if (current.length > 1) out.push(current);
|
|
1046
|
-
current = [
|
|
1160
|
+
current = [start2];
|
|
1047
1161
|
break;
|
|
1048
1162
|
}
|
|
1049
1163
|
}
|
|
@@ -1224,6 +1338,165 @@ function shapePath(e) {
|
|
|
1224
1338
|
return path;
|
|
1225
1339
|
}
|
|
1226
1340
|
|
|
1341
|
+
// src/elbow.ts
|
|
1342
|
+
var MARGIN = 16;
|
|
1343
|
+
var BEND = 24;
|
|
1344
|
+
var SMALLEST = 24;
|
|
1345
|
+
var MOST = 48;
|
|
1346
|
+
function obstacle(e) {
|
|
1347
|
+
if (e.kind === "connector" || e.kind === "text" || e.kind === "freehand") return null;
|
|
1348
|
+
if (e.kind === "path" && !e.extension) return null;
|
|
1349
|
+
if (e.style.opacity !== null && e.style.opacity <= 0) return null;
|
|
1350
|
+
const r = rotatedBounds(e);
|
|
1351
|
+
return r.width < SMALLEST || r.height < SMALLEST ? null : r;
|
|
1352
|
+
}
|
|
1353
|
+
function crosses(points, r) {
|
|
1354
|
+
const x1 = r.x + r.width, y1 = r.y + r.height;
|
|
1355
|
+
for (let k = 1; k < points.length; k++) {
|
|
1356
|
+
const p = points[k - 1], q = points[k];
|
|
1357
|
+
if (p.y === q.y) {
|
|
1358
|
+
if (r.y < p.y && p.y < y1 && Math.min(p.x, q.x) < x1 && Math.max(p.x, q.x) > r.x) return true;
|
|
1359
|
+
} else if (p.x === q.x) {
|
|
1360
|
+
if (r.x < p.x && p.x < x1 && Math.min(p.y, q.y) < y1 && Math.max(p.y, q.y) > r.y) return true;
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
return false;
|
|
1364
|
+
}
|
|
1365
|
+
var padded = (r) => ({ x: r.x - MARGIN, y: r.y - MARGIN, width: r.width + 2 * MARGIN, height: r.height + 2 * MARGIN });
|
|
1366
|
+
var inside = (p, r) => r.x < p.x && p.x < r.x + r.width && r.y < p.y && p.y < r.y + r.height;
|
|
1367
|
+
var memo = /* @__PURE__ */ new Map();
|
|
1368
|
+
function avoiding(plain, a, b, startVertical, endVertical, elements, except) {
|
|
1369
|
+
const usable = (r) => !inside(a, padded(r)) && !inside(b, padded(r));
|
|
1370
|
+
const all = [];
|
|
1371
|
+
let crossed = false;
|
|
1372
|
+
for (const e of elements) {
|
|
1373
|
+
if (except.has(e.id)) continue;
|
|
1374
|
+
const r = obstacle(e);
|
|
1375
|
+
if (!r || !usable(r)) continue;
|
|
1376
|
+
all.push(r);
|
|
1377
|
+
if (!crossed && crosses(plain, r)) crossed = true;
|
|
1378
|
+
}
|
|
1379
|
+
if (!crossed) return plain;
|
|
1380
|
+
const key = JSON.stringify([a.x, a.y, b.x, b.y, startVertical, endVertical, all.map((r) => [r.x, r.y, r.width, r.height])]);
|
|
1381
|
+
const hit = memo.get(key);
|
|
1382
|
+
if (hit) return hit;
|
|
1383
|
+
const out = route(plain, a, b, startVertical, endVertical, all);
|
|
1384
|
+
if (memo.size >= 512) memo.clear();
|
|
1385
|
+
memo.set(key, out);
|
|
1386
|
+
return out;
|
|
1387
|
+
}
|
|
1388
|
+
function route(plain, a, b, startVertical, endVertical, usable) {
|
|
1389
|
+
let chosen = usable.map((_, i) => i).filter((i) => crosses(plain, usable[i]));
|
|
1390
|
+
if (chosen.length === 0) return plain;
|
|
1391
|
+
for (let round = 0; round < 4; round++) {
|
|
1392
|
+
if (chosen.length > MOST) return plain;
|
|
1393
|
+
const path = grid(a, b, startVertical, endVertical, chosen.map((i) => padded(usable[i])));
|
|
1394
|
+
if (!path) return plain;
|
|
1395
|
+
const more = usable.map((_, i) => i).filter((i) => !chosen.includes(i) && crosses(path, usable[i]));
|
|
1396
|
+
if (more.length === 0) return path;
|
|
1397
|
+
chosen = [...chosen, ...more].sort((p, q) => p - q);
|
|
1398
|
+
}
|
|
1399
|
+
return plain;
|
|
1400
|
+
}
|
|
1401
|
+
function grid(a, b, startVertical, endVertical, boxes) {
|
|
1402
|
+
let xs = [a.x, b.x, (a.x + b.x) / 2], ys = [a.y, b.y, (a.y + b.y) / 2];
|
|
1403
|
+
for (const r of boxes) {
|
|
1404
|
+
xs.push(r.x, r.x + r.width);
|
|
1405
|
+
ys.push(r.y, r.y + r.height);
|
|
1406
|
+
}
|
|
1407
|
+
xs = [...new Set(xs)].sort((p, q) => p - q);
|
|
1408
|
+
ys = [...new Set(ys)].sort((p, q) => p - q);
|
|
1409
|
+
const nx = xs.length, ny = ys.length;
|
|
1410
|
+
const ia = xs.indexOf(a.x), ja = ys.indexOf(a.y), ib = xs.indexOf(b.x), jb = ys.indexOf(b.y);
|
|
1411
|
+
if (ia < 0 || ja < 0 || ib < 0 || jb < 0) return null;
|
|
1412
|
+
const at = new Map(xs.map((x, k) => [x, k])), bt = new Map(ys.map((y, k) => [y, k]));
|
|
1413
|
+
const inner = new Uint8Array(nx * ny), acrossBlocked = new Uint8Array(nx * ny), downBlocked = new Uint8Array(nx * ny);
|
|
1414
|
+
for (const r of boxes) {
|
|
1415
|
+
const i0 = at.get(r.x), i1 = at.get(r.x + r.width), j0 = bt.get(r.y), j1 = bt.get(r.y + r.height);
|
|
1416
|
+
if (i0 === void 0 || i1 === void 0 || j0 === void 0 || j1 === void 0) continue;
|
|
1417
|
+
for (let i = i0; i < i1; i++) for (let j = j0; j < j1; j++) {
|
|
1418
|
+
if (j > j0) acrossBlocked[i * ny + j] = 1;
|
|
1419
|
+
if (i > i0) downBlocked[i * ny + j] = 1;
|
|
1420
|
+
if (i > i0 && j > j0) inner[i * ny + j] = 1;
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
const blocked = (i, j) => inner[i * ny + j] === 1;
|
|
1424
|
+
const across = (i0, i1, j) => acrossBlocked[Math.min(i0, i1) * ny + j] === 1;
|
|
1425
|
+
const down = (j0, j1, i) => downBlocked[i * ny + Math.min(j0, j1)] === 1;
|
|
1426
|
+
const count = nx * ny * 2;
|
|
1427
|
+
const dist = new Array(count).fill(Infinity), prev = new Array(count).fill(-1);
|
|
1428
|
+
const done = new Array(count).fill(false);
|
|
1429
|
+
const heap = new Frontier();
|
|
1430
|
+
const first = (ia * ny + ja) * 2 + (startVertical ? 1 : 0);
|
|
1431
|
+
dist[first] = 0;
|
|
1432
|
+
heap.push(0, first);
|
|
1433
|
+
const want = (ib * ny + jb) * 2 + (endVertical ? 1 : 0);
|
|
1434
|
+
for (let top = heap.pop(); top; top = heap.pop()) {
|
|
1435
|
+
const [cost, s] = top;
|
|
1436
|
+
if (done[s]) continue;
|
|
1437
|
+
done[s] = true;
|
|
1438
|
+
if (s === want) break;
|
|
1439
|
+
const node = Math.floor(s / 2), axis = s % 2, i = Math.floor(node / ny), j = node % ny;
|
|
1440
|
+
for (const [di, dj] of [[1, 0], [-1, 0], [0, 1], [0, -1]]) {
|
|
1441
|
+
const ni = i + di, nj = j + dj;
|
|
1442
|
+
if (ni < 0 || ni >= nx || nj < 0 || nj >= ny || blocked(ni, nj)) continue;
|
|
1443
|
+
const turn = di !== 0 ? 0 : 1;
|
|
1444
|
+
if (di !== 0 ? across(i, ni, j) : down(j, nj, i)) continue;
|
|
1445
|
+
const length = Math.abs(xs[ni] - xs[i]) + Math.abs(ys[nj] - ys[j]);
|
|
1446
|
+
const c = cost + length + (turn !== axis ? BEND : 0);
|
|
1447
|
+
const t = (ni * ny + nj) * 2 + turn;
|
|
1448
|
+
if (c < dist[t]) {
|
|
1449
|
+
dist[t] = c;
|
|
1450
|
+
prev[t] = s;
|
|
1451
|
+
heap.push(c, t);
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
if (!done[want]) return null;
|
|
1456
|
+
const points = [];
|
|
1457
|
+
for (let s = want; s >= 0; s = prev[s]) {
|
|
1458
|
+
const node = Math.floor(s / 2);
|
|
1459
|
+
points.push({ x: xs[Math.floor(node / ny)], y: ys[node % ny] });
|
|
1460
|
+
}
|
|
1461
|
+
return points.reverse();
|
|
1462
|
+
}
|
|
1463
|
+
var Frontier = class {
|
|
1464
|
+
constructor() {
|
|
1465
|
+
__publicField(this, "heap", []);
|
|
1466
|
+
__publicField(this, "next", 0);
|
|
1467
|
+
}
|
|
1468
|
+
less(i, j) {
|
|
1469
|
+
const a = this.heap[i], b = this.heap[j];
|
|
1470
|
+
return a[0] < b[0] || a[0] === b[0] && a[1] < b[1];
|
|
1471
|
+
}
|
|
1472
|
+
push(cost, state) {
|
|
1473
|
+
this.heap.push([cost, this.next++, state]);
|
|
1474
|
+
for (let i = this.heap.length - 1; i > 0; ) {
|
|
1475
|
+
const up = i - 1 >> 1;
|
|
1476
|
+
if (!this.less(i, up)) break;
|
|
1477
|
+
[this.heap[i], this.heap[up]] = [this.heap[up], this.heap[i]];
|
|
1478
|
+
i = up;
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
pop() {
|
|
1482
|
+
if (this.heap.length === 0) return null;
|
|
1483
|
+
const top = this.heap[0], last = this.heap.pop();
|
|
1484
|
+
if (this.heap.length > 0) {
|
|
1485
|
+
this.heap[0] = last;
|
|
1486
|
+
for (let i = 0; ; ) {
|
|
1487
|
+
const l = 2 * i + 1, r = l + 1;
|
|
1488
|
+
let m = i;
|
|
1489
|
+
if (l < this.heap.length && this.less(l, m)) m = l;
|
|
1490
|
+
if (r < this.heap.length && this.less(r, m)) m = r;
|
|
1491
|
+
if (m === i) break;
|
|
1492
|
+
[this.heap[i], this.heap[m]] = [this.heap[m], this.heap[i]];
|
|
1493
|
+
i = m;
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
return [top[0], top[2]];
|
|
1497
|
+
}
|
|
1498
|
+
};
|
|
1499
|
+
|
|
1227
1500
|
// src/connector.ts
|
|
1228
1501
|
function portPoint(shape, port) {
|
|
1229
1502
|
const r = frame(shape);
|
|
@@ -1367,7 +1640,7 @@ function routeThrough(p, c) {
|
|
|
1367
1640
|
}
|
|
1368
1641
|
}
|
|
1369
1642
|
}
|
|
1370
|
-
function
|
|
1643
|
+
function route2(e, index) {
|
|
1371
1644
|
const c = e.connector;
|
|
1372
1645
|
if (!c) return { kind: "polyline", points: [] };
|
|
1373
1646
|
const [a, b] = endpoints(e, index);
|
|
@@ -1383,16 +1656,19 @@ function route(e, index) {
|
|
|
1383
1656
|
case "elbow": {
|
|
1384
1657
|
const startV = vertical(c.start.port) ?? !horizontal;
|
|
1385
1658
|
const endV = vertical(c.end.port) ?? !horizontal;
|
|
1659
|
+
let plain;
|
|
1386
1660
|
if (startV && endV) {
|
|
1387
1661
|
const my = a.y + dy / 2;
|
|
1388
|
-
|
|
1389
|
-
}
|
|
1390
|
-
if (!startV && !endV) {
|
|
1662
|
+
plain = [a, { x: a.x, y: my }, { x: b.x, y: my }, b];
|
|
1663
|
+
} else if (!startV && !endV) {
|
|
1391
1664
|
const mx = a.x + dx / 2;
|
|
1392
|
-
|
|
1393
|
-
}
|
|
1394
|
-
|
|
1395
|
-
return { kind: "polyline", points:
|
|
1665
|
+
plain = [a, { x: mx, y: a.y }, { x: mx, y: b.y }, b];
|
|
1666
|
+
} else if (startV) plain = [a, { x: a.x, y: b.y }, b];
|
|
1667
|
+
else plain = [a, { x: b.x, y: a.y }, b];
|
|
1668
|
+
if (c.avoid !== true) return { kind: "polyline", points: plain };
|
|
1669
|
+
const ends = new Set([c.start.elementID, c.end.elementID].filter((x) => x !== null));
|
|
1670
|
+
const routed = avoiding(plain, a, b, startV, endV, index.values(), ends);
|
|
1671
|
+
return { kind: "polyline", points: routed === plain ? plain : simplified(routed) };
|
|
1396
1672
|
}
|
|
1397
1673
|
}
|
|
1398
1674
|
}
|
|
@@ -1403,7 +1679,7 @@ function arrowhead(tip, from, size) {
|
|
|
1403
1679
|
return [wing(spread), wing(-spread)];
|
|
1404
1680
|
}
|
|
1405
1681
|
function connectorBounds(e, index) {
|
|
1406
|
-
const pts = routePoints(
|
|
1682
|
+
const pts = routePoints(route2(e, index));
|
|
1407
1683
|
if (pts.length === 0) return { x: 0, y: 0, width: 0, height: 0 };
|
|
1408
1684
|
let x0 = pts[0].x, y0 = pts[0].y, x1 = x0, y1 = y0;
|
|
1409
1685
|
for (const p of pts) {
|
|
@@ -1415,7 +1691,7 @@ function connectorBounds(e, index) {
|
|
|
1415
1691
|
return { x: x0, y: y0, width: x1 - x0, height: y1 - y0 };
|
|
1416
1692
|
}
|
|
1417
1693
|
function midpoint(e, index) {
|
|
1418
|
-
const pts = routePoints(
|
|
1694
|
+
const pts = routePoints(route2(e, index));
|
|
1419
1695
|
if (pts.length <= 1) return pts[0] ?? { x: 0, y: 0 };
|
|
1420
1696
|
const lengths = [];
|
|
1421
1697
|
let total = 0;
|
|
@@ -1435,9 +1711,17 @@ function midpoint(e, index) {
|
|
|
1435
1711
|
}
|
|
1436
1712
|
return pts[0];
|
|
1437
1713
|
}
|
|
1714
|
+
var indexes = /* @__PURE__ */ new WeakMap();
|
|
1715
|
+
function isIndexOf(page, index) {
|
|
1716
|
+
return indexes.get(page.elements)?.index === index;
|
|
1717
|
+
}
|
|
1438
1718
|
function indexOf(page) {
|
|
1719
|
+
const els = page.elements;
|
|
1720
|
+
const hit = indexes.get(els);
|
|
1721
|
+
if (hit && hit.members.length === els.length && hit.members.every((e, i) => e === els[i])) return hit.index;
|
|
1439
1722
|
const m = /* @__PURE__ */ new Map();
|
|
1440
|
-
for (const e of
|
|
1723
|
+
for (const e of els) if (!m.has(e.id)) m.set(e.id, e);
|
|
1724
|
+
indexes.set(els, { members: [...els], index: m });
|
|
1441
1725
|
return m;
|
|
1442
1726
|
}
|
|
1443
1727
|
|
|
@@ -1520,43 +1804,112 @@ function packetID(flow, k) {
|
|
|
1520
1804
|
return flow.slice(0, 24) + (n < 0 ? n + 2 ** 48 : n).toString(16).toUpperCase().padStart(12, "0");
|
|
1521
1805
|
}
|
|
1522
1806
|
var routeCost = (cost) => cost === null || !Number.isFinite(cost) ? 1 : Math.min(Math.max(cost, 0), 1e6);
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
const
|
|
1527
|
-
const
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
for (const e of frame2.elements) {
|
|
1807
|
+
var graphs = /* @__PURE__ */ new WeakMap();
|
|
1808
|
+
var networks = /* @__PURE__ */ new Map();
|
|
1809
|
+
function graphOf(frame2) {
|
|
1810
|
+
const els = frame2.elements;
|
|
1811
|
+
const hit = graphs.get(els);
|
|
1812
|
+
if (hit && hit.members.length === els.length && hit.members.every((e, i) => e === els[i])) return hit.graph;
|
|
1813
|
+
const shapes2 = new Set(els.filter((e) => e.kind !== "connector").map((e) => e.id));
|
|
1814
|
+
const lines = [];
|
|
1815
|
+
for (const e of els) {
|
|
1533
1816
|
const c = e.connector;
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1817
|
+
if (c) lines.push([e.id, c.start.elementID, c.end.elementID, String(routeCost(c.cost)), c.startHead !== "none" ? "both" : "one"]);
|
|
1818
|
+
}
|
|
1819
|
+
const key = JSON.stringify([[...shapes2], lines]);
|
|
1820
|
+
let graph = networks.get(key);
|
|
1821
|
+
if (!graph) {
|
|
1822
|
+
const next = /* @__PURE__ */ new Map();
|
|
1823
|
+
const add2 = (a, edge) => {
|
|
1824
|
+
const list = next.get(a);
|
|
1825
|
+
if (list) list.push(edge);
|
|
1826
|
+
else next.set(a, [edge]);
|
|
1827
|
+
};
|
|
1828
|
+
for (const e of els) {
|
|
1829
|
+
const c = e.connector;
|
|
1830
|
+
const a = c?.start.elementID ?? null, b = c?.end.elementID ?? null;
|
|
1831
|
+
if (!c || a === null || b === null || a === b || !shapes2.has(a) || !shapes2.has(b)) continue;
|
|
1832
|
+
const cost = routeCost(c.cost);
|
|
1833
|
+
add2(a, { to: b, via: e.id, back: false, cost });
|
|
1834
|
+
if (c.startHead !== "none") add2(b, { to: a, via: e.id, back: true, cost });
|
|
1835
|
+
}
|
|
1836
|
+
graph = { shapes: shapes2, next, trees: /* @__PURE__ */ new Map() };
|
|
1837
|
+
if (networks.size >= 16) networks.clear();
|
|
1838
|
+
networks.set(key, graph);
|
|
1839
|
+
}
|
|
1840
|
+
graphs.set(els, { members: [...els], graph });
|
|
1841
|
+
return graph;
|
|
1842
|
+
}
|
|
1843
|
+
var Frontier2 = class {
|
|
1844
|
+
constructor() {
|
|
1845
|
+
__publicField(this, "heap", []);
|
|
1846
|
+
}
|
|
1847
|
+
get size() {
|
|
1848
|
+
return this.heap.length;
|
|
1849
|
+
}
|
|
1850
|
+
less(i, j) {
|
|
1851
|
+
const a = this.heap[i], b = this.heap[j];
|
|
1852
|
+
return a.dist < b.dist || a.dist === b.dist && a.order < b.order;
|
|
1853
|
+
}
|
|
1854
|
+
swap(i, j) {
|
|
1855
|
+
[this.heap[i], this.heap[j]] = [this.heap[j], this.heap[i]];
|
|
1856
|
+
}
|
|
1857
|
+
push(id, dist, order) {
|
|
1858
|
+
this.heap.push({ id, dist, order });
|
|
1859
|
+
for (let i = this.heap.length - 1; i > 0; ) {
|
|
1860
|
+
const up = i - 1 >> 1;
|
|
1861
|
+
if (!this.less(i, up)) break;
|
|
1862
|
+
this.swap(i, up);
|
|
1863
|
+
i = up;
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
pop() {
|
|
1867
|
+
const top = this.heap[0], last = this.heap.pop();
|
|
1868
|
+
if (this.heap.length > 0) {
|
|
1869
|
+
this.heap[0] = last;
|
|
1870
|
+
for (let i = 0; ; ) {
|
|
1871
|
+
const l = 2 * i + 1, r = l + 1;
|
|
1872
|
+
let m = i;
|
|
1873
|
+
if (l < this.heap.length && this.less(l, m)) m = l;
|
|
1874
|
+
if (r < this.heap.length && this.less(r, m)) m = r;
|
|
1875
|
+
if (m === i) break;
|
|
1876
|
+
this.swap(i, m);
|
|
1877
|
+
i = m;
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
return top;
|
|
1539
1881
|
}
|
|
1882
|
+
};
|
|
1883
|
+
function flowTree(from, frame2, offline = /* @__PURE__ */ new Set(), until) {
|
|
1884
|
+
const graph = graphOf(frame2);
|
|
1885
|
+
const key = JSON.stringify([from, until ?? null, [...offline].sort()]);
|
|
1886
|
+
if (graph.trees.has(key)) return graph.trees.get(key);
|
|
1887
|
+
const tree = grow(graph, from, offline, until);
|
|
1888
|
+
if (graph.trees.size >= 4096) graph.trees.clear();
|
|
1889
|
+
graph.trees.set(key, tree);
|
|
1890
|
+
return tree;
|
|
1891
|
+
}
|
|
1892
|
+
function grow({ shapes: shapes2, next }, from, offline, until) {
|
|
1893
|
+
if (!shapes2.has(from) || offline.has(from)) return null;
|
|
1540
1894
|
const dist = /* @__PURE__ */ new Map([[from, 0]]), order = /* @__PURE__ */ new Map([[from, 0]]);
|
|
1541
1895
|
let counter = 1;
|
|
1542
1896
|
const tree = new FlowTree(from);
|
|
1543
|
-
const open =
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
if (da < db || da === db && order.get(open[i]) < order.get(open[best])) best = i;
|
|
1549
|
-
}
|
|
1550
|
-
const n = open.splice(best, 1)[0];
|
|
1897
|
+
const open = new Frontier2();
|
|
1898
|
+
open.push(from, 0, 0);
|
|
1899
|
+
while (open.size > 0) {
|
|
1900
|
+
const { id: n, order: o } = open.pop();
|
|
1901
|
+
if (tree.settled.has(n) || order.get(n) !== o) continue;
|
|
1551
1902
|
tree.settled.add(n);
|
|
1903
|
+
if (n === until && n !== from) break;
|
|
1552
1904
|
for (const edge of next.get(n) ?? []) {
|
|
1905
|
+
if (offline.has(edge.via) || offline.has(edge.to)) continue;
|
|
1553
1906
|
if (tree.settled.has(edge.to)) continue;
|
|
1554
1907
|
const d = dist.get(n) + edge.cost;
|
|
1555
1908
|
const old = dist.get(edge.to);
|
|
1556
1909
|
if (old !== void 0 && old <= d) continue;
|
|
1557
|
-
if (old === void 0) open.push(edge.to);
|
|
1558
1910
|
dist.set(edge.to, d);
|
|
1559
|
-
order.set(edge.to, counter
|
|
1911
|
+
order.set(edge.to, counter);
|
|
1912
|
+
open.push(edge.to, d, counter++);
|
|
1560
1913
|
tree.came.set(edge.to, { from: n, via: edge.via, back: edge.back });
|
|
1561
1914
|
}
|
|
1562
1915
|
}
|
|
@@ -1636,9 +1989,18 @@ function delayOf(e) {
|
|
|
1636
1989
|
var queueKey = (shape, flow) => shape + "|" + flow;
|
|
1637
1990
|
function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set(), sinceChange = null, carried = {}) {
|
|
1638
1991
|
const since = Math.max(0, Math.min(sinceChange ?? t, 1e6));
|
|
1639
|
-
const byID =
|
|
1640
|
-
|
|
1992
|
+
const byID = indexOf(frame2);
|
|
1993
|
+
const traced = /* @__PURE__ */ new Map();
|
|
1994
|
+
const traceOf = (c) => {
|
|
1995
|
+
let pts = traced.get(c.id);
|
|
1996
|
+
if (!pts) {
|
|
1997
|
+
pts = tracing(c, frame2, byID).points;
|
|
1998
|
+
traced.set(c.id, pts);
|
|
1999
|
+
}
|
|
2000
|
+
return pts;
|
|
2001
|
+
};
|
|
1641
2002
|
const metrics2 = emptyMetrics();
|
|
2003
|
+
const routes = /* @__PURE__ */ new Map();
|
|
1642
2004
|
for (const e of frame2.elements) {
|
|
1643
2005
|
if (e.kind === "connector" && !e.connector) continue;
|
|
1644
2006
|
metrics2.elements.set(e.id, { throughput: 0, load: null, delay: e.kind !== "connector" ? delayOf(e) : 0, backlog: 0, inFlight: 0, depth: 0 });
|
|
@@ -1650,7 +2012,7 @@ function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set()
|
|
|
1650
2012
|
if (c !== null && Number.isFinite(c)) room.set(e.id, Math.max(c, 0));
|
|
1651
2013
|
}
|
|
1652
2014
|
const full = () => new Set([...room].filter(([, v]) => v <= TINY).map(([id]) => id));
|
|
1653
|
-
const
|
|
2015
|
+
const route3 = (tree, goal) => {
|
|
1654
2016
|
const hops = [], shapes2 = [];
|
|
1655
2017
|
let at = goal;
|
|
1656
2018
|
for (let step = tree.came.get(at); step; step = tree.came.get(at)) {
|
|
@@ -1662,26 +2024,26 @@ function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set()
|
|
|
1662
2024
|
return { hops: hops.reverse(), shapes: shapes2.reverse() };
|
|
1663
2025
|
};
|
|
1664
2026
|
const way = (hops, shapes2) => {
|
|
1665
|
-
const points = [],
|
|
2027
|
+
const points = [], inside2 = [], stretches = [];
|
|
1666
2028
|
let length = 0;
|
|
1667
2029
|
hops.forEach(([id, back], k) => {
|
|
1668
2030
|
const c = byID.get(id);
|
|
1669
2031
|
if (!c) return;
|
|
1670
|
-
const line2 = back ? [...
|
|
2032
|
+
const line2 = back ? [...traceOf(c)].reverse() : traceOf(c);
|
|
1671
2033
|
if (line2.length === 0) return;
|
|
1672
2034
|
const last = points[points.length - 1];
|
|
1673
2035
|
if (last) {
|
|
1674
2036
|
const gap = Math.hypot(line2[0].x - last.x, line2[0].y - last.y);
|
|
1675
2037
|
const shape = byID.get(shapes2[k]);
|
|
1676
|
-
|
|
2038
|
+
inside2.push([length, length + gap, shape ? delayOf(shape) : 0]);
|
|
1677
2039
|
length += gap;
|
|
1678
2040
|
}
|
|
1679
|
-
const
|
|
2041
|
+
const start2 = length;
|
|
1680
2042
|
for (let q = 1; q < line2.length; q++) length += Math.hypot(line2[q].x - line2[q - 1].x, line2[q].y - line2[q - 1].y);
|
|
1681
|
-
stretches.push([id,
|
|
2043
|
+
stretches.push([id, start2, length]);
|
|
1682
2044
|
points.push(...line2);
|
|
1683
2045
|
});
|
|
1684
|
-
return length > 0 ? { line: new Polyline(points), length, inside, stretches } : null;
|
|
2046
|
+
return length > 0 ? { line: new Polyline(points), length, inside: inside2, stretches } : null;
|
|
1685
2047
|
};
|
|
1686
2048
|
const along = (w, elapsedAt, speed) => {
|
|
1687
2049
|
let elapsed = elapsedAt, d = 0;
|
|
@@ -1713,25 +2075,31 @@ function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set()
|
|
|
1713
2075
|
const packets = [];
|
|
1714
2076
|
const waitingBy = /* @__PURE__ */ new Map();
|
|
1715
2077
|
const everyOf = (flow) => Math.min(Math.max(clamp(flow.every, DEFAULT_EVERY, 0.05, 60) * biggest / Math.max(amountIn(flow, values), TINY), 0.05), 60);
|
|
2078
|
+
const bases = /* @__PURE__ */ new Map();
|
|
2079
|
+
const baseTree = (from) => {
|
|
2080
|
+
if (!bases.has(from)) bases.set(from, flowTree(from, frame2, offline));
|
|
2081
|
+
return bases.get(from);
|
|
2082
|
+
};
|
|
1716
2083
|
for (const [i, flow] of list.entries()) {
|
|
1717
2084
|
if (flow.when !== null && values !== null && !holds(flow.when, values)) continue;
|
|
1718
2085
|
const source = byID.get(flow.from);
|
|
1719
|
-
const base2 = source ?
|
|
2086
|
+
const base2 = source ? baseTree(flow.from) : null;
|
|
2087
|
+
routes.set(flow.id, []);
|
|
1720
2088
|
if (!source || !base2) continue;
|
|
1721
2089
|
const volume = amountIn(flow, values);
|
|
1722
2090
|
const fm = { volume, placed: 0, backlog: 0, backup: 0, transit: null };
|
|
1723
2091
|
const placements = [];
|
|
1724
|
-
const
|
|
2092
|
+
const waiting2 = [];
|
|
1725
2093
|
const goals = base2.goals(flow.to, flow.spread === true);
|
|
1726
2094
|
const shares = goals.length > 1 ? goals.map((g) => [[g], volume / goals.length]) : [[flow.to, volume]];
|
|
1727
2095
|
for (const [candidates, share] of shares) {
|
|
1728
2096
|
if (!(share > TINY)) continue;
|
|
1729
2097
|
let left = share;
|
|
1730
2098
|
for (let n = 0; n < 32 && left > TINY; n++) {
|
|
1731
|
-
const tree = flowTree(flow.from, frame2, union2(offline, full()));
|
|
2099
|
+
const tree = flowTree(flow.from, frame2, union2(offline, full()), candidates[0]);
|
|
1732
2100
|
const goal2 = tree?.goals(candidates, false)[0];
|
|
1733
2101
|
if (!tree || goal2 === void 0) break;
|
|
1734
|
-
const r =
|
|
2102
|
+
const r = route3(tree, goal2);
|
|
1735
2103
|
const ids = [...r.shapes, ...r.hops.map(([id]) => id)];
|
|
1736
2104
|
const rooms = ids.filter((id) => room.has(id)).map((id) => room.get(id));
|
|
1737
2105
|
const fits = Math.min(left, rooms.length > 0 ? Math.min(...rooms) : Infinity);
|
|
@@ -1744,7 +2112,7 @@ function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set()
|
|
|
1744
2112
|
let at = flow.from;
|
|
1745
2113
|
const goal = base2.goals(candidates, false)[0];
|
|
1746
2114
|
if (goal !== void 0) {
|
|
1747
|
-
const r =
|
|
2115
|
+
const r = route3(base2, goal), done = full();
|
|
1748
2116
|
const sequence = [[r.shapes[0], r.shapes[0]]];
|
|
1749
2117
|
r.hops.forEach(([id], k) => {
|
|
1750
2118
|
sequence.push([id, r.shapes[k]], [r.shapes[k + 1], r.shapes[k]]);
|
|
@@ -1768,18 +2136,25 @@ function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set()
|
|
|
1768
2136
|
placements.push({ hops, shapes: shapes2, goal: at, amount: left, waits: true });
|
|
1769
2137
|
}
|
|
1770
2138
|
}
|
|
1771
|
-
const existing =
|
|
2139
|
+
const existing = waiting2.find(([s]) => s === at);
|
|
1772
2140
|
if (existing) existing[1] += left;
|
|
1773
|
-
else
|
|
1774
|
-
}
|
|
1775
|
-
waitingBy.set(flow.id,
|
|
2141
|
+
else waiting2.push([at, left]);
|
|
2142
|
+
}
|
|
2143
|
+
waitingBy.set(flow.id, waiting2);
|
|
2144
|
+
routes.set(flow.id, placements.map((p) => ({
|
|
2145
|
+
goal: p.goal,
|
|
2146
|
+
hops: p.hops,
|
|
2147
|
+
amount: p.amount,
|
|
2148
|
+
waits: p.waits === true,
|
|
2149
|
+
cost: p.hops.reduce((s, [id]) => s + routeCost(byID.get(id)?.connector?.cost ?? null), 0)
|
|
2150
|
+
})));
|
|
1776
2151
|
for (const p of placements) {
|
|
1777
2152
|
for (const id of [...p.shapes, ...p.hops.map(([h]) => h)]) bump(id, (m) => {
|
|
1778
2153
|
m.throughput += p.amount;
|
|
1779
2154
|
});
|
|
1780
2155
|
if (!p.waits) fm.placed += p.amount;
|
|
1781
2156
|
}
|
|
1782
|
-
for (const [at, a] of
|
|
2157
|
+
for (const [at, a] of waiting2) {
|
|
1783
2158
|
bump(at, (m) => {
|
|
1784
2159
|
m.backlog += a;
|
|
1785
2160
|
});
|
|
@@ -1824,10 +2199,12 @@ function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set()
|
|
|
1824
2199
|
metrics2.flows.set(flow.id, fm);
|
|
1825
2200
|
}
|
|
1826
2201
|
const piles = /* @__PURE__ */ new Map();
|
|
2202
|
+
const carriedKeys = Object.keys(carried).filter((k) => carried[k] > TINY);
|
|
1827
2203
|
for (const flow of list) {
|
|
1828
|
-
const
|
|
1829
|
-
const shapes2 =
|
|
1830
|
-
|
|
2204
|
+
const waiting2 = waitingBy.get(flow.id) ?? [];
|
|
2205
|
+
const shapes2 = waiting2.map(([s]) => s);
|
|
2206
|
+
const suffix = "|" + flow.id;
|
|
2207
|
+
if (carriedKeys.some((k) => k.endsWith(suffix))) for (const e of frame2.elements) {
|
|
1831
2208
|
const c = carried[queueKey(e.id, flow.id)];
|
|
1832
2209
|
if (c !== void 0 && c > TINY && !shapes2.includes(e.id)) shapes2.push(e.id);
|
|
1833
2210
|
}
|
|
@@ -1841,7 +2218,7 @@ function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set()
|
|
|
1841
2218
|
const shape = byID.get(at);
|
|
1842
2219
|
if (!shape) return;
|
|
1843
2220
|
const layer = byID.get(flow.from)?.layerID ?? shape.layerID;
|
|
1844
|
-
const backlog =
|
|
2221
|
+
const backlog = waiting2.find(([s]) => s === at)?.[1] ?? 0;
|
|
1845
2222
|
const before = Math.max(0, carried[queueKey(at, flow.id)] ?? 0);
|
|
1846
2223
|
let depth = before + backlog * since;
|
|
1847
2224
|
if (backlog <= TINY && before > TINY) {
|
|
@@ -1850,10 +2227,10 @@ function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set()
|
|
|
1850
2227
|
let total = 0;
|
|
1851
2228
|
for (let n2 = 0; n2 < 32; n2++) {
|
|
1852
2229
|
const blocked = new Set([...trial].filter(([, v]) => v <= TINY).map(([id]) => id));
|
|
1853
|
-
const tree = flowTree(at, frame2, union2(offline, blocked));
|
|
2230
|
+
const tree = flowTree(at, frame2, union2(offline, blocked), flow.to[0]);
|
|
1854
2231
|
const goal = tree?.goals(flow.to, false)[0];
|
|
1855
2232
|
if (!tree || goal === void 0) break;
|
|
1856
|
-
const r =
|
|
2233
|
+
const r = route3(tree, goal);
|
|
1857
2234
|
const ids = [...r.shapes.slice(1), ...r.hops.map(([id]) => id)];
|
|
1858
2235
|
const rooms = ids.filter((id) => trial.has(id)).map((id) => trial.get(id));
|
|
1859
2236
|
const spare = rooms.length > 0 ? Math.min(...rooms) : Infinity;
|
|
@@ -1908,7 +2285,7 @@ function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set()
|
|
|
1908
2285
|
if (c === null || !Number.isFinite(c) || !m) continue;
|
|
1909
2286
|
m.load = c > 0 ? m.throughput / c : 1;
|
|
1910
2287
|
}
|
|
1911
|
-
return { packets, metrics: metrics2 };
|
|
2288
|
+
return { packets, metrics: metrics2, routes };
|
|
1912
2289
|
}
|
|
1913
2290
|
var BLANK = {
|
|
1914
2291
|
seed: 1,
|
|
@@ -1963,21 +2340,21 @@ function metricLookup(m, frame2) {
|
|
|
1963
2340
|
return id === null ? null : metricValue(m, term.metric, id);
|
|
1964
2341
|
};
|
|
1965
2342
|
}
|
|
1966
|
-
function
|
|
2343
|
+
function finishPlan(elements, metrics2, values, frame2, since = Infinity, carried = {}) {
|
|
1967
2344
|
const lookup = metricLookup(metrics2, frame2);
|
|
1968
|
-
const
|
|
1969
|
-
for (
|
|
2345
|
+
const plan2 = [], readings = {};
|
|
2346
|
+
for (const e of elements) {
|
|
2347
|
+
const applied = [];
|
|
1970
2348
|
if (values !== null && e.overrides) {
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
}
|
|
2349
|
+
e.overrides.forEach((o, k) => {
|
|
2350
|
+
if (mentionsMetric(o.when) && holds(o.when, values, (m) => lookup(m, e.id))) applied.push(k);
|
|
2351
|
+
});
|
|
1975
2352
|
}
|
|
1976
|
-
|
|
1977
|
-
|
|
2353
|
+
const g = values === null ? null : e.gauge;
|
|
2354
|
+
if (!g) {
|
|
2355
|
+
plan2.push({ applied, gauge: null });
|
|
1978
2356
|
continue;
|
|
1979
2357
|
}
|
|
1980
|
-
const g = e.gauge;
|
|
1981
2358
|
let shown = metricValue(metrics2, g.metric, g.of);
|
|
1982
2359
|
const then = carried[gaugeKey(e.id)];
|
|
1983
2360
|
if (shown !== null && then !== void 0 && g.metric !== "depth" && g.metric !== "inFlight" && since < GAUGE_EASE) {
|
|
@@ -1985,22 +2362,33 @@ function finishing(elements, metrics2, values, frame2, flows, since = Infinity,
|
|
|
1985
2362
|
shown = then + (shown - then) * smooth;
|
|
1986
2363
|
}
|
|
1987
2364
|
if (shown !== null) readings[gaugeKey(e.id)] = shown;
|
|
1988
|
-
|
|
2365
|
+
plan2.push({ applied, gauge: { value: shown } });
|
|
1989
2366
|
}
|
|
2367
|
+
return { plan: plan2, readings };
|
|
2368
|
+
}
|
|
2369
|
+
function finishing(elements, metrics2, values, frame2, flows, since = Infinity, carried = {}) {
|
|
2370
|
+
const { plan: plan2, readings } = finishPlan(elements, metrics2, values, frame2, since, carried);
|
|
2371
|
+
const out = [];
|
|
2372
|
+
elements.forEach((e, i) => {
|
|
2373
|
+
for (const k of plan2[i].applied) e = applyOverride(looksOnly(e.overrides[k]), e);
|
|
2374
|
+
const g = plan2[i].gauge;
|
|
2375
|
+
if (g && e.gauge) out.push(...gauge(e, e.gauge, g.value, frame2, flows));
|
|
2376
|
+
else out.push(e);
|
|
2377
|
+
});
|
|
1990
2378
|
return { elements: out, readings };
|
|
1991
2379
|
}
|
|
1992
2380
|
var gaugeKey = (id) => "gauge|" + id;
|
|
1993
2381
|
var GAUGE_EASE = 1;
|
|
1994
|
-
var
|
|
2382
|
+
var hex2 = (h) => {
|
|
1995
2383
|
const v = parseInt(h.slice(1), 16);
|
|
1996
2384
|
return { r: (v >> 16 & 255) / 255, g: (v >> 8 & 255) / 255, b: (v & 255) / 255, a: 1 };
|
|
1997
2385
|
};
|
|
1998
|
-
var GAUGE_GREEN =
|
|
1999
|
-
var GAUGE_AMBER =
|
|
2000
|
-
var GAUGE_RED =
|
|
2001
|
-
var GAUGE_BLUE =
|
|
2002
|
-
var GAUGE_TRACK =
|
|
2003
|
-
var GAUGE_GRAY =
|
|
2386
|
+
var GAUGE_GREEN = hex2("#16a34a");
|
|
2387
|
+
var GAUGE_AMBER = hex2("#d97706");
|
|
2388
|
+
var GAUGE_RED = hex2("#dc2626");
|
|
2389
|
+
var GAUGE_BLUE = hex2("#2f6fdf");
|
|
2390
|
+
var GAUGE_TRACK = hex2("#e5e7eb");
|
|
2391
|
+
var GAUGE_GRAY = hex2("#9ca3af");
|
|
2004
2392
|
function formatted(metric, v) {
|
|
2005
2393
|
if (v === null || !Number.isFinite(v)) return "\u2013";
|
|
2006
2394
|
switch (metric) {
|
|
@@ -2132,7 +2520,7 @@ function line(a, b, rng, roughness, bowing, preserveEnds, into) {
|
|
|
2132
2520
|
const bow = offset(rng, length / 60, roughness) * bowing * gain;
|
|
2133
2521
|
const dx = b.x - a.x, dy = b.y - a.y;
|
|
2134
2522
|
const nx = -dy / length, ny = dx / length;
|
|
2135
|
-
const
|
|
2523
|
+
const start2 = preserveEnds ? a : (() => {
|
|
2136
2524
|
const x = a.x + j();
|
|
2137
2525
|
return { x, y: a.y + j() };
|
|
2138
2526
|
})();
|
|
@@ -2144,7 +2532,7 @@ function line(a, b, rng, roughness, bowing, preserveEnds, into) {
|
|
|
2144
2532
|
const c1y = a.y + dy * mid * 0.66 + ny * bow + j() * 0.5;
|
|
2145
2533
|
const c2x = a.x + dx * (0.5 + mid * 0.5) + nx * bow + j() * 0.5;
|
|
2146
2534
|
const c2y = a.y + dy * (0.5 + mid * 0.5) + ny * bow + j() * 0.5;
|
|
2147
|
-
into.moveTo(
|
|
2535
|
+
into.moveTo(start2);
|
|
2148
2536
|
into.curveTo(end, { x: c1x, y: c1y }, { x: c2x, y: c2y });
|
|
2149
2537
|
}
|
|
2150
2538
|
}
|
|
@@ -2286,17 +2674,17 @@ function clusterStrokes(c, angle2, size) {
|
|
|
2286
2674
|
});
|
|
2287
2675
|
}
|
|
2288
2676
|
function contains(contours, p) {
|
|
2289
|
-
let
|
|
2677
|
+
let inside2 = false;
|
|
2290
2678
|
for (const poly of contours) {
|
|
2291
2679
|
if (poly.length < 3) continue;
|
|
2292
2680
|
let k = poly.length - 1;
|
|
2293
2681
|
for (let i = 0; i < poly.length; i++) {
|
|
2294
2682
|
const a = poly[i], b = poly[k];
|
|
2295
|
-
if (a.y > p.y !== b.y > p.y && p.x < (b.x - a.x) * (p.y - a.y) / (b.y - a.y) + a.x)
|
|
2683
|
+
if (a.y > p.y !== b.y > p.y && p.x < (b.x - a.x) * (p.y - a.y) / (b.y - a.y) + a.x) inside2 = !inside2;
|
|
2296
2684
|
k = i;
|
|
2297
2685
|
}
|
|
2298
2686
|
}
|
|
2299
|
-
return
|
|
2687
|
+
return inside2;
|
|
2300
2688
|
}
|
|
2301
2689
|
function distance(p, contours) {
|
|
2302
2690
|
let best = Infinity;
|
|
@@ -2405,10 +2793,10 @@ var Cache = class {
|
|
|
2405
2793
|
__publicField(this, "limit");
|
|
2406
2794
|
this.limit = limit;
|
|
2407
2795
|
}
|
|
2408
|
-
get(key,
|
|
2796
|
+
get(key, make2) {
|
|
2409
2797
|
const hit = this.map.get(key);
|
|
2410
2798
|
if (hit !== void 0) return hit;
|
|
2411
|
-
const v =
|
|
2799
|
+
const v = make2();
|
|
2412
2800
|
if (this.map.size >= this.limit) this.map.clear();
|
|
2413
2801
|
this.map.set(key, v);
|
|
2414
2802
|
return v;
|
|
@@ -2573,7 +2961,7 @@ function connectorParts(e, index, page) {
|
|
|
2573
2961
|
const c = e.connector;
|
|
2574
2962
|
if (!c) return [new Path(), new Path()];
|
|
2575
2963
|
const [a, b] = endpoints(e, index);
|
|
2576
|
-
const r =
|
|
2964
|
+
const r = route2(e, index);
|
|
2577
2965
|
const hops = c.hops === true && page ? hopCrossings(e, r, page, index) : [];
|
|
2578
2966
|
const key = [
|
|
2579
2967
|
e.seed,
|
|
@@ -2632,26 +3020,59 @@ function subCubic(p, t0, t1) {
|
|
|
2632
3020
|
const left = split(p, t1)[0];
|
|
2633
3021
|
return t1 > 0 ? split(left, t0 / t1)[1] : [p[0], p[0], p[0], p[0]];
|
|
2634
3022
|
}
|
|
3023
|
+
function boxOf(pts) {
|
|
3024
|
+
let x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity;
|
|
3025
|
+
for (const p of pts) {
|
|
3026
|
+
x0 = Math.min(x0, p.x);
|
|
3027
|
+
y0 = Math.min(y0, p.y);
|
|
3028
|
+
x1 = Math.max(x1, p.x);
|
|
3029
|
+
y1 = Math.max(y1, p.y);
|
|
3030
|
+
}
|
|
3031
|
+
return { x0, y0, x1, y1 };
|
|
3032
|
+
}
|
|
3033
|
+
function touching(a, b) {
|
|
3034
|
+
const eps = 1e-6;
|
|
3035
|
+
return a.x0 <= b.x1 + eps && b.x0 <= a.x1 + eps && a.y0 <= b.y1 + eps && b.y0 <= a.y1 + eps;
|
|
3036
|
+
}
|
|
3037
|
+
var hopFrames = /* @__PURE__ */ new WeakMap();
|
|
3038
|
+
function linesBeneath(e, page, index) {
|
|
3039
|
+
let f = hopFrames.get(index);
|
|
3040
|
+
if (!f || f.layers !== page.layers || !isIndexOf(page, index)) {
|
|
3041
|
+
const visible = new Set(page.layers.filter((l) => l.visible).map((l) => l.id));
|
|
3042
|
+
const first = /* @__PURE__ */ new Map(), under = [];
|
|
3043
|
+
for (const o of orderedElements(page)) {
|
|
3044
|
+
if (!first.has(o.id)) first.set(o.id, under.length);
|
|
3045
|
+
if (o.kind === "connector" && visible.has(o.layerID)) under.push(o);
|
|
3046
|
+
}
|
|
3047
|
+
f = { layers: page.layers, first, under, lines: [] };
|
|
3048
|
+
if (isIndexOf(page, index)) hopFrames.set(index, f);
|
|
3049
|
+
}
|
|
3050
|
+
const count = f.first.get(e.id) ?? f.under.length;
|
|
3051
|
+
while (f.lines.length < count) {
|
|
3052
|
+
const points = routePoints(route2(f.under[f.lines.length], index));
|
|
3053
|
+
f.lines.push({ points, box: boxOf(points) });
|
|
3054
|
+
}
|
|
3055
|
+
return { lines: f.lines, count };
|
|
3056
|
+
}
|
|
2635
3057
|
function hopCrossings(e, r, page, index) {
|
|
2636
3058
|
const segments = hopSamples(r);
|
|
2637
3059
|
if (segments.length === 0) return [];
|
|
2638
|
-
const
|
|
2639
|
-
const beneath = [];
|
|
2640
|
-
for (const o of orderedElements(page)) {
|
|
2641
|
-
if (o.id === e.id) break;
|
|
2642
|
-
if (o.kind !== "connector" || !visible.has(o.layerID)) continue;
|
|
2643
|
-
beneath.push(routePoints(route(o, index)));
|
|
2644
|
-
}
|
|
3060
|
+
const { lines, count } = linesBeneath(e, page, index);
|
|
2645
3061
|
const rad = hopRadius(e.style.strokeWidth);
|
|
2646
3062
|
const out = [];
|
|
2647
3063
|
for (const samples of segments) {
|
|
2648
3064
|
const along = runningLengths(samples);
|
|
2649
3065
|
const length = along[along.length - 1];
|
|
2650
3066
|
const found = [];
|
|
3067
|
+
const near = [];
|
|
3068
|
+
const whole = boxOf(samples);
|
|
3069
|
+
for (let n = 0; n < count; n++) if (touching(whole, lines[n].box)) near.push(lines[n]);
|
|
2651
3070
|
for (let i = 0; i < samples.length - 1; i++) {
|
|
2652
3071
|
const p = samples[i], q = samples[i + 1];
|
|
2653
3072
|
const dx = q.x - p.x, dy = q.y - p.y;
|
|
2654
|
-
|
|
3073
|
+
const box = boxOf([p, q]);
|
|
3074
|
+
for (const { points: line2, box: lineBox } of near) {
|
|
3075
|
+
if (!touching(box, lineBox)) continue;
|
|
2655
3076
|
for (let k = 0; k < line2.length - 1; k++) {
|
|
2656
3077
|
const a = line2[k], b = line2[k + 1];
|
|
2657
3078
|
const ex = b.x - a.x, ey = b.y - a.y;
|
|
@@ -2931,8 +3352,8 @@ function followSpan(track, t) {
|
|
|
2931
3352
|
if (track.keys.some((k) => k.t >= span.end && k.t <= t && (k.x !== void 0 || k.y !== void 0))) return null;
|
|
2932
3353
|
return f;
|
|
2933
3354
|
}
|
|
2934
|
-
function tracing(e, page) {
|
|
2935
|
-
if (e.kind === "connector") return new Polyline(routePoints(
|
|
3355
|
+
function tracing(e, page, index = indexOf(page)) {
|
|
3356
|
+
if (e.kind === "connector") return new Polyline(routePoints(route2(e, index)));
|
|
2936
3357
|
const clean = cloneElement(e);
|
|
2937
3358
|
clean.style.roughness = 0;
|
|
2938
3359
|
const pts = outline(clean).contours()[0] ?? [];
|
|
@@ -2942,9 +3363,10 @@ function following(track, e, t, page) {
|
|
|
2942
3363
|
const f = followSpan(track, t);
|
|
2943
3364
|
if (!f) return e;
|
|
2944
3365
|
t = localTime(track, t);
|
|
2945
|
-
const
|
|
3366
|
+
const index = indexOf(page);
|
|
3367
|
+
const pathElement = index.get(f.path);
|
|
2946
3368
|
if (!pathElement || pathElement.id === e.id) return e;
|
|
2947
|
-
const line2 = tracing(pathElement, page);
|
|
3369
|
+
const line2 = tracing(pathElement, page, index);
|
|
2948
3370
|
const span = f.end - f.start;
|
|
2949
3371
|
let u = Math.min(Math.max(applyEase(f.ease ?? "easeInOut", span > 0 ? Math.min(1, (t - f.start) / span) : 1), 0), 1) * (f.laps ?? 1);
|
|
2950
3372
|
if (u > 1) u = rem(u, 1) === 0 ? 1 : rem(u, 1);
|
|
@@ -3199,17 +3621,30 @@ function symbolPage(s) {
|
|
|
3199
3621
|
function hasInstances(page) {
|
|
3200
3622
|
return page.elements.some((e) => e.kind === "symbol");
|
|
3201
3623
|
}
|
|
3202
|
-
|
|
3624
|
+
var Expansions = class {
|
|
3625
|
+
constructor(page, t, posing) {
|
|
3626
|
+
__publicField(this, "page");
|
|
3627
|
+
__publicField(this, "t");
|
|
3628
|
+
__publicField(this, "posing");
|
|
3629
|
+
/** For each instance: its time override, the budget it found and left, and what it made. */
|
|
3630
|
+
__publicField(this, "made", /* @__PURE__ */ new Map());
|
|
3631
|
+
this.page = page;
|
|
3632
|
+
this.t = t;
|
|
3633
|
+
this.posing = posing;
|
|
3634
|
+
}
|
|
3635
|
+
};
|
|
3636
|
+
function expandingSymbols(page, t, posing = true, times = /* @__PURE__ */ new Map(), memo2) {
|
|
3203
3637
|
if (!hasInstances(page)) return page;
|
|
3204
3638
|
const budget = { left: MAX_ELEMENTS };
|
|
3205
|
-
|
|
3639
|
+
const m = memo2 && memo2.page === page && memo2.t === t && memo2.posing === posing ? memo2 : void 0;
|
|
3640
|
+
return { ...page, elements: expand(page.elements, page, t, symbolTable(page), [], posing, budget, times, m) };
|
|
3206
3641
|
}
|
|
3207
3642
|
function symbolTable(page) {
|
|
3208
3643
|
const table = /* @__PURE__ */ new Map();
|
|
3209
3644
|
for (const s of page.symbols ?? []) if (!table.has(s.id)) table.set(s.id, s);
|
|
3210
3645
|
return table;
|
|
3211
3646
|
}
|
|
3212
|
-
function expand(elements, page, t, table, stack, posing, budget, times = /* @__PURE__ */ new Map()) {
|
|
3647
|
+
function expand(elements, page, t, table, stack, posing, budget, times = /* @__PURE__ */ new Map(), memo2) {
|
|
3213
3648
|
const instances = new Set(elements.filter((e) => e.kind === "symbol").map((e) => e.id));
|
|
3214
3649
|
if (instances.size === 0) {
|
|
3215
3650
|
budget.left -= elements.length;
|
|
@@ -3234,6 +3669,14 @@ function expand(elements, page, t, table, stack, posing, budget, times = /* @__P
|
|
|
3234
3669
|
budget.left -= 1;
|
|
3235
3670
|
continue;
|
|
3236
3671
|
}
|
|
3672
|
+
const override0 = stack.length === 0 ? times.get(e.id) : void 0;
|
|
3673
|
+
const was = memo2?.made.get(e);
|
|
3674
|
+
if (was && was.override === override0 && was.after > 0 && budget.left - (was.before - was.after) > 0) {
|
|
3675
|
+
budget.left -= was.before - was.after;
|
|
3676
|
+
for (const x of was.out) out.push(x);
|
|
3677
|
+
continue;
|
|
3678
|
+
}
|
|
3679
|
+
const before = budget.left, from = out.length;
|
|
3237
3680
|
budget.left -= 1;
|
|
3238
3681
|
const id = e.symbol;
|
|
3239
3682
|
const symbol = id === null ? void 0 : table.get(id);
|
|
@@ -3249,6 +3692,7 @@ function expand(elements, page, t, table, stack, posing, budget, times = /* @__P
|
|
|
3249
3692
|
layers: [{ id: e.layerID, name: "", visible: true, locked: false }]
|
|
3250
3693
|
};
|
|
3251
3694
|
for (const x of expand(drawn.elements, drawn, u, table, [...stack, id], posing, budget)) out.push(placed(x, e, symbol));
|
|
3695
|
+
if (memo2 && stack.length === 0 && !was) memo2.made.set(e, { override: override0, before, after: budget.left, out: out.slice(from) });
|
|
3252
3696
|
}
|
|
3253
3697
|
return out;
|
|
3254
3698
|
}
|
|
@@ -3301,8 +3745,8 @@ function mix2(x) {
|
|
|
3301
3745
|
return z ^ z >> 31n;
|
|
3302
3746
|
}
|
|
3303
3747
|
function halves(id) {
|
|
3304
|
-
const
|
|
3305
|
-
return [BigInt(`0x${
|
|
3748
|
+
const hex3 = id.replace(/-/g, "").padStart(32, "0").slice(-32);
|
|
3749
|
+
return [BigInt(`0x${hex3.slice(0, 16)}`), BigInt(`0x${hex3.slice(16)}`)];
|
|
3306
3750
|
}
|
|
3307
3751
|
function derivedID(instance, inner) {
|
|
3308
3752
|
const [a, b] = halves(instance), [c, d] = halves(inner);
|
|
@@ -3516,13 +3960,13 @@ function blocks(lines) {
|
|
|
3516
3960
|
flush();
|
|
3517
3961
|
return out;
|
|
3518
3962
|
}
|
|
3519
|
-
function parseList(lines,
|
|
3520
|
-
const first = reItem.exec(lines[
|
|
3963
|
+
function parseList(lines, start2) {
|
|
3964
|
+
const first = reItem.exec(lines[start2]);
|
|
3521
3965
|
const indent = first[1].length;
|
|
3522
3966
|
const bullet = /^\d/.test(first[2]) ? null : first[2];
|
|
3523
3967
|
const ordered = bullet === null ? parseInt(first[2], 10) : null;
|
|
3524
3968
|
const items = [];
|
|
3525
|
-
let i =
|
|
3969
|
+
let i = start2;
|
|
3526
3970
|
while (i < lines.length) {
|
|
3527
3971
|
const m = reItem.exec(lines[i]);
|
|
3528
3972
|
if (!m || m[1].length !== indent || bullet === null !== /^\d/.test(m[2]) || bullet !== null && m[2] !== bullet) break;
|
|
@@ -3630,12 +4074,23 @@ function inline(src, style2 = {}) {
|
|
|
3630
4074
|
i += m[0].length;
|
|
3631
4075
|
continue;
|
|
3632
4076
|
}
|
|
3633
|
-
if (m = /^(
|
|
4077
|
+
if (m = /^(\*\*\*|___)(?=\S)([\s\S]*?\S)\1(?!\*|_)/.exec(rest)) {
|
|
3634
4078
|
emit();
|
|
3635
|
-
out.push(...inline(m[2], { ...style2, bold: true }));
|
|
4079
|
+
out.push(...inline(m[2], { ...style2, bold: true, italic: true }));
|
|
3636
4080
|
i += m[0].length;
|
|
3637
4081
|
continue;
|
|
3638
4082
|
}
|
|
4083
|
+
if (m = /^(\*\*|__)(?=\S)([\s\S]*?\S)\1/.exec(rest)) {
|
|
4084
|
+
let body = m[2], length = m[0].length;
|
|
4085
|
+
if (m[1] === "**" && rest[length] === "*" && (body.replace(/\*\*/g, "").match(/\*/g) ?? []).length % 2 === 1) {
|
|
4086
|
+
body += "*";
|
|
4087
|
+
length++;
|
|
4088
|
+
}
|
|
4089
|
+
emit();
|
|
4090
|
+
out.push(...inline(body, { ...style2, bold: true }));
|
|
4091
|
+
i += length;
|
|
4092
|
+
continue;
|
|
4093
|
+
}
|
|
3639
4094
|
if ((m = /^(\*|_)(?=\S)([\s\S]*?\S)\1(?!\1)/.exec(rest)) && (m[1] === "*" || !/\w/.test(src[i - 1] ?? ""))) {
|
|
3640
4095
|
emit();
|
|
3641
4096
|
out.push(...inline(m[2], { ...style2, italic: true }));
|
|
@@ -3851,10 +4306,10 @@ function layoutLines(paras, width, height, fontSize, fam) {
|
|
|
3851
4306
|
let k = 0;
|
|
3852
4307
|
let firstLine = true;
|
|
3853
4308
|
while (k < chars.length || firstLine && chars.length === 0) {
|
|
3854
|
-
const
|
|
4309
|
+
const start2 = firstLine ? p.firstIndent : p.indent;
|
|
3855
4310
|
const maxX2 = width;
|
|
3856
4311
|
const glyphs = [];
|
|
3857
|
-
let x =
|
|
4312
|
+
let x = start2, lastSpace = -1, end = k;
|
|
3858
4313
|
for (; end < chars.length; end++) {
|
|
3859
4314
|
const { ch, run } = chars[end];
|
|
3860
4315
|
const r = runs[run];
|
|
@@ -3903,7 +4358,7 @@ function layoutLines(paras, width, height, fontSize, fam) {
|
|
|
3903
4358
|
}
|
|
3904
4359
|
if (glyphs.length === 0 && end === k && k < chars.length) {
|
|
3905
4360
|
const { ch, run } = chars[k];
|
|
3906
|
-
glyphs.push({ ch, run, x:
|
|
4361
|
+
glyphs.push({ ch, run, x: start2, w: charWidth(ch, runs[run], fam) });
|
|
3907
4362
|
end = k + 1;
|
|
3908
4363
|
}
|
|
3909
4364
|
let asc = 0, desc = 0, lead = 0;
|
|
@@ -3929,13 +4384,38 @@ function layoutLines(paras, width, height, fontSize, fam) {
|
|
|
3929
4384
|
});
|
|
3930
4385
|
return lines;
|
|
3931
4386
|
}
|
|
4387
|
+
var Memo = class {
|
|
4388
|
+
constructor(limit) {
|
|
4389
|
+
__publicField(this, "map", /* @__PURE__ */ new Map());
|
|
4390
|
+
__publicField(this, "limit");
|
|
4391
|
+
this.limit = limit;
|
|
4392
|
+
}
|
|
4393
|
+
get(key, make2) {
|
|
4394
|
+
const hit = this.map.get(key);
|
|
4395
|
+
if (hit !== void 0) return hit;
|
|
4396
|
+
const v = make2();
|
|
4397
|
+
if (this.map.size >= this.limit) this.map.clear();
|
|
4398
|
+
this.map.set(key, v);
|
|
4399
|
+
return v;
|
|
4400
|
+
}
|
|
4401
|
+
};
|
|
4402
|
+
var layouts = new Memo(500);
|
|
4403
|
+
var markups = new Memo(500);
|
|
3932
4404
|
function noteMarkup(source, e) {
|
|
4405
|
+
const st = e.style, ink = st.stroke;
|
|
4406
|
+
const key = JSON.stringify([...[e.x, e.y, e.width, e.height, st.fontSize, ink.r, ink.g, ink.b, ink.a].map(String), st.font ?? "", source]);
|
|
4407
|
+
return markups.get(key, () => computeNoteMarkup(source, e));
|
|
4408
|
+
}
|
|
4409
|
+
function computeNoteMarkup(source, e) {
|
|
3933
4410
|
const rect2 = textRect({ x: e.x, y: e.y, width: e.width, height: e.height });
|
|
3934
4411
|
if (!(rect2.width > 1 && rect2.height > 1)) return "";
|
|
3935
4412
|
const fam = familyMetrics(e.style.font);
|
|
3936
|
-
const
|
|
3937
|
-
|
|
3938
|
-
|
|
4413
|
+
const layoutKey = JSON.stringify([...[rect2.width, rect2.height, e.style.fontSize].map(String), e.style.font ?? "", source]);
|
|
4414
|
+
const lines = layouts.get(layoutKey, () => {
|
|
4415
|
+
const b = new Builder(e.style.fontSize, rect2.width, fam);
|
|
4416
|
+
for (const block of parseBlocks(source)) b.block(block, { indent: 0, marker: null, quote: null, listDepth: 0 });
|
|
4417
|
+
return layoutLines(b.paras, rect2.width, rect2.height, e.style.fontSize, fam);
|
|
4418
|
+
});
|
|
3939
4419
|
const ink = color(e.style.stroke);
|
|
3940
4420
|
let s = "";
|
|
3941
4421
|
const marks = [];
|
|
@@ -3994,20 +4474,6 @@ function sameStyle(a, b) {
|
|
|
3994
4474
|
return a === b || a.size === b.size && a.bold === b.bold && a.italic === b.italic && a.mono === b.mono && a.strike === b.strike && a.underline === b.underline && a.alpha === b.alpha;
|
|
3995
4475
|
}
|
|
3996
4476
|
|
|
3997
|
-
// src/theme.ts
|
|
3998
|
-
var rgb = (r, g, b) => ({ r, g, b, a: 1 });
|
|
3999
|
-
var themes = [
|
|
4000
|
-
{ id: "paper", name: "Paper", background: rgb(0.984, 0.98, 0.965), ink: rgb(0.12, 0.12, 0.13) },
|
|
4001
|
-
{ id: "white", name: "White", background: rgb(1, 1, 1), ink: rgb(0.12, 0.12, 0.13) },
|
|
4002
|
-
{ id: "sepia", name: "Sepia", background: rgb(0.953, 0.914, 0.824), ink: rgb(0.23, 0.18, 0.13) },
|
|
4003
|
-
{ id: "blueprint", name: "Blueprint", background: rgb(0.106, 0.227, 0.361), ink: rgb(0.91, 0.945, 0.984) },
|
|
4004
|
-
{ id: "chalkboard", name: "Chalkboard", background: rgb(0.169, 0.227, 0.2), ink: rgb(0.945, 0.945, 0.918) },
|
|
4005
|
-
{ id: "midnight", name: "Midnight", background: rgb(0.082, 0.086, 0.102), ink: rgb(0.925, 0.925, 0.94) }
|
|
4006
|
-
];
|
|
4007
|
-
function themeNamed(id) {
|
|
4008
|
-
return themes.find((t) => t.id === id) ?? null;
|
|
4009
|
-
}
|
|
4010
|
-
|
|
4011
4477
|
// src/svg.ts
|
|
4012
4478
|
function bounds(page, padding = 24) {
|
|
4013
4479
|
if (hasInstances(page)) return bounds(expandingSymbols(page, 0), padding);
|
|
@@ -4024,6 +4490,26 @@ function bounds(page, padding = 24) {
|
|
|
4024
4490
|
return inset(r, -padding, -padding);
|
|
4025
4491
|
}
|
|
4026
4492
|
function plan(page) {
|
|
4493
|
+
const hit = plans.get(page);
|
|
4494
|
+
if (hit && unchanged(page, hit.fields)) return hit.plan;
|
|
4495
|
+
const made = makePlan(page);
|
|
4496
|
+
plans.set(page, { fields: { ...page }, plan: made });
|
|
4497
|
+
return made;
|
|
4498
|
+
}
|
|
4499
|
+
var plans = /* @__PURE__ */ new WeakMap();
|
|
4500
|
+
function hasPlan(page) {
|
|
4501
|
+
const hit = plans.get(page);
|
|
4502
|
+
return !!hit && unchanged(page, hit.fields);
|
|
4503
|
+
}
|
|
4504
|
+
function rememberPlan(page, made) {
|
|
4505
|
+
plans.set(page, { fields: { ...page }, plan: made });
|
|
4506
|
+
}
|
|
4507
|
+
function unchanged(page, fields) {
|
|
4508
|
+
const a = page, b = fields;
|
|
4509
|
+
const keys = Object.keys(a);
|
|
4510
|
+
return keys.length === Object.keys(b).length && keys.every((k) => a[k] === b[k]);
|
|
4511
|
+
}
|
|
4512
|
+
function makePlan(page) {
|
|
4027
4513
|
const tl = page.timeline ?? { fps: 12, duration: 0, boil: null, settle: null, poster: null, tracks: [], camera: null, markers: null };
|
|
4028
4514
|
const times = frameTimes(tl);
|
|
4029
4515
|
const first = tl.camera?.find((k) => k.width > 1 && k.height > 1);
|
|
@@ -4051,9 +4537,9 @@ function svgOpen(b, background, paint) {
|
|
|
4051
4537
|
}
|
|
4052
4538
|
return out;
|
|
4053
4539
|
}
|
|
4054
|
-
function svgParts(page, background, fixed, links = false) {
|
|
4055
|
-
if (hasInstances(page)) return svgParts(expandingSymbols(page, 0), background, fixed, links);
|
|
4056
|
-
const theme =
|
|
4540
|
+
function svgParts(page, background, fixed, links = false, only) {
|
|
4541
|
+
if (hasInstances(page)) return svgParts(expandingSymbols(page, 0), background, fixed, links, only);
|
|
4542
|
+
const theme = pageTheme(page);
|
|
4057
4543
|
const bg = background ? theme?.background ?? background : null;
|
|
4058
4544
|
const ink = theme?.ink ?? black;
|
|
4059
4545
|
const d = { ...page, elements: page.elements.map((e) => resolvingInk(e, ink)) };
|
|
@@ -4063,11 +4549,11 @@ function svgParts(page, background, fixed, links = false) {
|
|
|
4063
4549
|
const index = indexOf(d);
|
|
4064
4550
|
const ordered = orderedElements(d);
|
|
4065
4551
|
const floors = ordered.filter((e) => visible.has(e.layerID) && isFloor(e));
|
|
4066
|
-
const rockMarkup = rock(floors).map((r) => `<path d="${r.path.data()}" fill="none" stroke="${color(r.ink)}"${opacity(r.ink, "stroke-opacity")} stroke-width="${fmt(r.width)}" stroke-linecap="round"/>
|
|
4552
|
+
const rockMarkup = only && !only("rock") ? "" : rock(floors).map((r) => `<path d="${r.path.data()}" fill="none" stroke="${color(r.ink)}"${opacity(r.ink, "stroke-opacity")} stroke-width="${fmt(r.width)}" stroke-linecap="round"/>
|
|
4067
4553
|
`).join("");
|
|
4068
4554
|
if (rockMarkup) parts.push({ key: "rock", markup: rockMarkup });
|
|
4069
4555
|
for (const e of ordered) {
|
|
4070
|
-
if (!visible.has(e.layerID)) continue;
|
|
4556
|
+
if (!visible.has(e.layerID) || only && !only(e.id)) continue;
|
|
4071
4557
|
const alpha = Math.min(Math.max(e.style.opacity ?? 1, 0), 1);
|
|
4072
4558
|
if (alpha <= 0) continue;
|
|
4073
4559
|
let body = svgElement(e, index, bg, d);
|
|
@@ -4089,7 +4575,7 @@ ${body}</a>
|
|
|
4089
4575
|
}
|
|
4090
4576
|
function svg(page, background = { r: 1, g: 1, b: 1, a: 1 }, fixed) {
|
|
4091
4577
|
const { bounds: b, parts } = svgParts(page, background, fixed, true);
|
|
4092
|
-
return svgOpen(b,
|
|
4578
|
+
return svgOpen(b, pageTheme(page)?.background ?? background, background !== null) + parts.map((p) => p.markup).join("") + "</svg>\n";
|
|
4093
4579
|
}
|
|
4094
4580
|
function labelRect(e, index) {
|
|
4095
4581
|
const m = midpoint(e, index);
|
|
@@ -4183,7 +4669,7 @@ function svgElement(e, index, labelBackground, page) {
|
|
|
4183
4669
|
}
|
|
4184
4670
|
s += `<path d="${outline(e).data()}" fill="none" ${strokeAttrs}/>
|
|
4185
4671
|
`;
|
|
4186
|
-
if (text) s += e.kind === "note" ? noteMarkup(text, e) : textElement(text, e.kind === "path" ? pathLabelBox(e) : frame(e), e, true);
|
|
4672
|
+
if (text) s += e.kind === "note" ? noteMarkup(text, e) : textElement(text, e.kind === "path" || e.kind === "rectangle" ? pathLabelBox(e) : frame(e), e, true);
|
|
4187
4673
|
return s;
|
|
4188
4674
|
}
|
|
4189
4675
|
default:
|
|
@@ -4215,6 +4701,110 @@ function textElement(text, f, e, centered) {
|
|
|
4215
4701
|
return s + "</text>\n";
|
|
4216
4702
|
}
|
|
4217
4703
|
|
|
4704
|
+
// src/planner.ts
|
|
4705
|
+
var WORKER_WORK = 3e4;
|
|
4706
|
+
function planWork(page) {
|
|
4707
|
+
const tl = page.timeline;
|
|
4708
|
+
if (!tl || tl.camera?.some((k) => k.width > 1 && k.height > 1)) return 0;
|
|
4709
|
+
const elements = page.elements.length + (page.symbols ?? []).reduce((n, s) => n + s.elements.length, 0);
|
|
4710
|
+
return frameTimes(tl).length * elements;
|
|
4711
|
+
}
|
|
4712
|
+
function workerMaker() {
|
|
4713
|
+
if (typeof Worker === "undefined" || false) return null;
|
|
4714
|
+
if (false) {
|
|
4715
|
+
const source = `(${dottiePlayerScript.toString()})();
|
|
4716
|
+
`;
|
|
4717
|
+
return () => {
|
|
4718
|
+
const url = URL.createObjectURL(new Blob([source], { type: "text/javascript" }));
|
|
4719
|
+
try {
|
|
4720
|
+
return new Worker(url);
|
|
4721
|
+
} finally {
|
|
4722
|
+
setTimeout(() => URL.revokeObjectURL(url), 6e4);
|
|
4723
|
+
}
|
|
4724
|
+
};
|
|
4725
|
+
}
|
|
4726
|
+
if (true) {
|
|
4727
|
+
const here = import.meta.url;
|
|
4728
|
+
if (!here) return null;
|
|
4729
|
+
return () => new Worker(new URL("./dottie-player.min.js", here));
|
|
4730
|
+
}
|
|
4731
|
+
return null;
|
|
4732
|
+
}
|
|
4733
|
+
var make;
|
|
4734
|
+
var worker = null;
|
|
4735
|
+
var failed = false;
|
|
4736
|
+
var nextID = 1;
|
|
4737
|
+
var waiting = /* @__PURE__ */ new Map();
|
|
4738
|
+
function plansInWorker(page) {
|
|
4739
|
+
if (failed || hasPlan(page) || planWork(page) < WORKER_WORK) return false;
|
|
4740
|
+
if (make === void 0) make = workerMaker();
|
|
4741
|
+
return make !== null;
|
|
4742
|
+
}
|
|
4743
|
+
function planInWorker(page) {
|
|
4744
|
+
return new Promise((resolve2) => {
|
|
4745
|
+
const done = (p) => {
|
|
4746
|
+
if (p) rememberPlan(page, p);
|
|
4747
|
+
resolve2();
|
|
4748
|
+
};
|
|
4749
|
+
let w;
|
|
4750
|
+
try {
|
|
4751
|
+
w = worker ?? start();
|
|
4752
|
+
} catch {
|
|
4753
|
+
failed = true;
|
|
4754
|
+
done(null);
|
|
4755
|
+
return;
|
|
4756
|
+
}
|
|
4757
|
+
const id = nextID++;
|
|
4758
|
+
waiting.set(id, { page, done });
|
|
4759
|
+
try {
|
|
4760
|
+
w.postMessage({ dottiePlan: id, page });
|
|
4761
|
+
} catch {
|
|
4762
|
+
waiting.delete(id);
|
|
4763
|
+
done(null);
|
|
4764
|
+
}
|
|
4765
|
+
});
|
|
4766
|
+
}
|
|
4767
|
+
function start() {
|
|
4768
|
+
const w = make();
|
|
4769
|
+
w.onmessage = (ev) => {
|
|
4770
|
+
const { dottiePlan: id, plan: p } = ev.data ?? {};
|
|
4771
|
+
const job = id === void 0 ? void 0 : waiting.get(id);
|
|
4772
|
+
if (!job) return;
|
|
4773
|
+
waiting.delete(id);
|
|
4774
|
+
job.done(p ?? null);
|
|
4775
|
+
if (waiting.size === 0 && worker === w) {
|
|
4776
|
+
w.terminate();
|
|
4777
|
+
worker = null;
|
|
4778
|
+
}
|
|
4779
|
+
};
|
|
4780
|
+
w.onerror = (ev) => {
|
|
4781
|
+
ev.preventDefault?.();
|
|
4782
|
+
failed = true;
|
|
4783
|
+
if (worker === w) worker = null;
|
|
4784
|
+
w.terminate();
|
|
4785
|
+
const jobs = [...waiting.values()];
|
|
4786
|
+
waiting.clear();
|
|
4787
|
+
for (const job of jobs) job.done(null);
|
|
4788
|
+
};
|
|
4789
|
+
worker = w;
|
|
4790
|
+
return w;
|
|
4791
|
+
}
|
|
4792
|
+
function servePlans() {
|
|
4793
|
+
const g = globalThis;
|
|
4794
|
+
if (typeof g.WorkerGlobalScope === "undefined" || !(g instanceof g.WorkerGlobalScope)) return;
|
|
4795
|
+
g.addEventListener("message", (ev) => {
|
|
4796
|
+
const id = ev.data?.dottiePlan;
|
|
4797
|
+
if (typeof id !== "number") return;
|
|
4798
|
+
let p = null;
|
|
4799
|
+
try {
|
|
4800
|
+
p = plan(ev.data.page);
|
|
4801
|
+
} catch {
|
|
4802
|
+
p = null;
|
|
4803
|
+
}
|
|
4804
|
+
g.postMessage({ dottiePlan: id, plan: p });
|
|
4805
|
+
});
|
|
4806
|
+
}
|
|
4807
|
+
|
|
4218
4808
|
// src/player.ts
|
|
4219
4809
|
var WHITE = { r: 1, g: 1, b: 1, a: 1 };
|
|
4220
4810
|
function pageIndex(file, spec) {
|
|
@@ -4284,7 +4874,7 @@ var Playback = class {
|
|
|
4284
4874
|
__publicField(this, "order", []);
|
|
4285
4875
|
__publicField(this, "frame", -1);
|
|
4286
4876
|
__publicField(this, "onFrame", null);
|
|
4287
|
-
this.page = options.clean ?
|
|
4877
|
+
this.page = options.clean ? cleanCopy(page) : page;
|
|
4288
4878
|
addFonts(usedFonts(this.page));
|
|
4289
4879
|
this.plan = plan(this.page);
|
|
4290
4880
|
this.clicks = clickTargets(page, options.file);
|
|
@@ -4322,7 +4912,7 @@ var Playback = class {
|
|
|
4322
4912
|
this.sceneNames = (page.timeline?.markers ?? []).map((m) => m.name);
|
|
4323
4913
|
this.sceneLoops = (page.timeline?.markers ?? []).map((m) => m.loop === true);
|
|
4324
4914
|
this.state = { ...page.variables ?? {} };
|
|
4325
|
-
const theme =
|
|
4915
|
+
const theme = pageTheme(page);
|
|
4326
4916
|
this.background = theme?.background ?? WHITE;
|
|
4327
4917
|
this.ink = theme?.ink ?? { r: 0.12, g: 0.12, b: 0.13, a: 1 };
|
|
4328
4918
|
const ns = "http://www.w3.org/2000/svg";
|
|
@@ -4361,7 +4951,7 @@ var Playback = class {
|
|
|
4361
4951
|
}
|
|
4362
4952
|
/** Whether a copy is still playing through after a click. */
|
|
4363
4953
|
replaying(now = performance.now()) {
|
|
4364
|
-
for (const [id,
|
|
4954
|
+
for (const [id, start2] of this.replayStarts) if (now - start2 < (this.replays.get(id) ?? 0) * 1e3) return true;
|
|
4365
4955
|
return false;
|
|
4366
4956
|
}
|
|
4367
4957
|
/** Draws the last moment drawn again, for copies replaying while the page stands still. */
|
|
@@ -4374,17 +4964,24 @@ var Playback = class {
|
|
|
4374
4964
|
const r = this.plan.rects[Math.min(i, this.plan.rects.length - 1)];
|
|
4375
4965
|
const now = typeof performance === "undefined" ? 0 : performance.now();
|
|
4376
4966
|
const times = /* @__PURE__ */ new Map();
|
|
4377
|
-
for (const [id,
|
|
4967
|
+
for (const [id, start2] of this.replayStarts) times.set(id, Math.min((now - start2) / 1e3, this.replays.get(id) ?? 0));
|
|
4378
4968
|
const tc = clampTime(t);
|
|
4379
4969
|
const pose = posed(this.page, tc, this.state, this.sinceChange(now), this.carried);
|
|
4380
|
-
const
|
|
4970
|
+
const made = new Expansions(pose, tc, true);
|
|
4971
|
+
const frame2 = expandingSymbols(pose, tc, true, times, made);
|
|
4381
4972
|
const { parts } = svgParts(frame2, this.background, r);
|
|
4382
4973
|
const owned = (m, parts2) => {
|
|
4383
4974
|
for (const p of parts2) if (this.owner.has(p.key)) m.set(p.key, p.markup);
|
|
4384
4975
|
return m;
|
|
4385
4976
|
};
|
|
4386
|
-
const state = (st) => st.size === 0 ? /* @__PURE__ */ new Map() : owned(/* @__PURE__ */ new Map(), svgParts(
|
|
4387
|
-
|
|
4977
|
+
const state = (st) => st.size === 0 ? /* @__PURE__ */ new Map() : owned(/* @__PURE__ */ new Map(), svgParts(
|
|
4978
|
+
expandingSymbols(pose, tc, true, new Map([...times, ...st]), made),
|
|
4979
|
+
this.background,
|
|
4980
|
+
r,
|
|
4981
|
+
false,
|
|
4982
|
+
(key) => st.has(this.owner.get(key) ?? "")
|
|
4983
|
+
).parts);
|
|
4984
|
+
const hover = this.hoverIDs.size === 0 ? /* @__PURE__ */ new Map() : owned(/* @__PURE__ */ new Map(), svgParts(hovered(frame2, this.hoverIDs), this.background, r, false, (key) => this.hoverIDs.has(key)).parts);
|
|
4388
4985
|
const over = state(this.overTimes), down = state(this.downTimes), hit = state(this.hitTimes);
|
|
4389
4986
|
const normal = owned(/* @__PURE__ */ new Map(), parts);
|
|
4390
4987
|
const merged = [];
|
|
@@ -4513,6 +5110,14 @@ ${h}</g>
|
|
|
4513
5110
|
return { frame: next, stop: entered >= 0 && !this.sceneLoops[entered] };
|
|
4514
5111
|
}
|
|
4515
5112
|
};
|
|
5113
|
+
var cleanCopies = /* @__PURE__ */ new WeakMap();
|
|
5114
|
+
function cleanCopy(page) {
|
|
5115
|
+
const hit = cleanCopies.get(page);
|
|
5116
|
+
if (hit && unchanged(page, hit.fields)) return hit.page;
|
|
5117
|
+
const clean = withRoughness(page, 0);
|
|
5118
|
+
cleanCopies.set(page, { fields: { ...page }, page: clean });
|
|
5119
|
+
return clean;
|
|
5120
|
+
}
|
|
4516
5121
|
var ElementBase = typeof HTMLElement === "undefined" ? class {
|
|
4517
5122
|
} : HTMLElement;
|
|
4518
5123
|
var DottiePlayer = class extends ElementBase {
|
|
@@ -4535,6 +5140,10 @@ var DottiePlayer = class extends ElementBase {
|
|
|
4535
5140
|
__publicField(this, "raf", 0);
|
|
4536
5141
|
__publicField(this, "replayRaf", 0);
|
|
4537
5142
|
__publicField(this, "loadToken", 0);
|
|
5143
|
+
/** Counts mounts, so a plan that comes back from a worker after a newer one is dropped. */
|
|
5144
|
+
__publicField(this, "mountToken", 0);
|
|
5145
|
+
/** The time a still shows; null while the drawing is animated. */
|
|
5146
|
+
__publicField(this, "stillTime", null);
|
|
4538
5147
|
__publicField(this, "visible", true);
|
|
4539
5148
|
__publicField(this, "observer", null);
|
|
4540
5149
|
this.root = this.attachShadow({ mode: "open" });
|
|
@@ -4611,9 +5220,19 @@ var DottiePlayer = class extends ElementBase {
|
|
|
4611
5220
|
text = inline2.textContent ?? "";
|
|
4612
5221
|
}
|
|
4613
5222
|
if (token !== this.loadToken) return;
|
|
4614
|
-
this.
|
|
5223
|
+
this.showFile(parseDottie(text));
|
|
4615
5224
|
} catch (err) {
|
|
4616
5225
|
if (token !== this.loadToken) return;
|
|
5226
|
+
this.setPlaying(false);
|
|
5227
|
+
cancelAnimationFrame(this.raf);
|
|
5228
|
+
cancelAnimationFrame(this.replayRaf);
|
|
5229
|
+
this.raf = 0;
|
|
5230
|
+
this.replayRaf = 0;
|
|
5231
|
+
this.playback = null;
|
|
5232
|
+
this.file = null;
|
|
5233
|
+
this.mountToken++;
|
|
5234
|
+
this.stillTime = null;
|
|
5235
|
+
this.bar.hidden = true;
|
|
4617
5236
|
this.stage.innerHTML = "";
|
|
4618
5237
|
const box = document.createElement("div");
|
|
4619
5238
|
box.className = "error";
|
|
@@ -4624,15 +5243,35 @@ var DottiePlayer = class extends ElementBase {
|
|
|
4624
5243
|
}
|
|
4625
5244
|
/** Shows an already-parsed file. */
|
|
4626
5245
|
setFile(file) {
|
|
5246
|
+
this.loadToken++;
|
|
5247
|
+
this.showFile(file);
|
|
5248
|
+
}
|
|
5249
|
+
showFile(file) {
|
|
4627
5250
|
this.file = file;
|
|
4628
5251
|
for (const w of file.warnings) console.warn(`dottie-player: ${w}`);
|
|
4629
|
-
this.mount();
|
|
4630
|
-
this.dispatchEvent(new CustomEvent("load", { detail: { file } }));
|
|
5252
|
+
this.mount(() => this.dispatchEvent(new CustomEvent("load", { detail: { file } })));
|
|
4631
5253
|
}
|
|
4632
|
-
|
|
5254
|
+
/** Shows the file's page; then `mounted`. A big animation is planned in a worker first (planner.ts), and what was
|
|
5255
|
+
* showing stays until it is ready; a newer mount, file or failed load in the meantime wins. */
|
|
5256
|
+
mount(mounted) {
|
|
4633
5257
|
const file = this.file;
|
|
4634
5258
|
const page = file.pages[pageIndex(file, this.getAttribute("page"))];
|
|
4635
|
-
const
|
|
5259
|
+
const clean = this.hasAttribute("clean");
|
|
5260
|
+
const token = ++this.mountToken;
|
|
5261
|
+
const shown = clean ? cleanCopy(page) : page;
|
|
5262
|
+
if (!plansInWorker(shown)) {
|
|
5263
|
+
this.mountPage(file, page, clean);
|
|
5264
|
+
mounted?.();
|
|
5265
|
+
return;
|
|
5266
|
+
}
|
|
5267
|
+
void planInWorker(shown).then(() => {
|
|
5268
|
+
if (token !== this.mountToken || this.file !== file) return;
|
|
5269
|
+
this.mountPage(file, page, clean);
|
|
5270
|
+
mounted?.();
|
|
5271
|
+
});
|
|
5272
|
+
}
|
|
5273
|
+
mountPage(file, page, clean) {
|
|
5274
|
+
const p = new Playback(page, this.stage, { clean, file });
|
|
4636
5275
|
p.still = this.hasAttribute("at");
|
|
4637
5276
|
this.playback = p;
|
|
4638
5277
|
this.style.setProperty("--dottie-page", color(p.background));
|
|
@@ -4655,10 +5294,14 @@ var DottiePlayer = class extends ElementBase {
|
|
|
4655
5294
|
this.dispatchEvent(new CustomEvent("frame", { detail: { frame: i, time: p.plan.times[i] } }));
|
|
4656
5295
|
};
|
|
4657
5296
|
if (!animated) {
|
|
4658
|
-
|
|
5297
|
+
const i = at !== null ? nearest(p, Number(at) || 0) : p.posterFrame;
|
|
5298
|
+
this.stillTime = at !== null ? Number(at) || 0 : posterTime(page);
|
|
5299
|
+
p.drawAt(this.stillTime, i);
|
|
5300
|
+
p.frame = i;
|
|
4659
5301
|
this.setPlaying(false);
|
|
4660
5302
|
return;
|
|
4661
5303
|
}
|
|
5304
|
+
this.stillTime = null;
|
|
4662
5305
|
p.show(0);
|
|
4663
5306
|
const reduced = typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
4664
5307
|
if (reduced) {
|
|
@@ -4672,8 +5315,10 @@ var DottiePlayer = class extends ElementBase {
|
|
|
4672
5315
|
get paused() {
|
|
4673
5316
|
return !this.playing;
|
|
4674
5317
|
}
|
|
5318
|
+
/** The moment shown: a still's own time (its `at`, or the poster), else the current frame's. */
|
|
4675
5319
|
get currentTime() {
|
|
4676
5320
|
const p = this.playback;
|
|
5321
|
+
if (p && this.stillTime !== null) return this.stillTime;
|
|
4677
5322
|
return p && p.frame >= 0 ? p.plan.times[p.frame] : 0;
|
|
4678
5323
|
}
|
|
4679
5324
|
set currentTime(t) {
|
|
@@ -4921,7 +5566,7 @@ function inspectFrame(page, t) {
|
|
|
4921
5566
|
const [a, b] = endpoints(e, index);
|
|
4922
5567
|
d.start = pt(a);
|
|
4923
5568
|
d.end = pt(b);
|
|
4924
|
-
d.route = routePoints(
|
|
5569
|
+
d.route = routePoints(route2(e, index)).map(pt);
|
|
4925
5570
|
}
|
|
4926
5571
|
return d;
|
|
4927
5572
|
});
|
|
@@ -4960,9 +5605,10 @@ function render(source, options = {}) {
|
|
|
4960
5605
|
function isFile(v) {
|
|
4961
5606
|
return typeof v === "object" && v !== null && Array.isArray(v.pages) && Array.isArray(v.warnings);
|
|
4962
5607
|
}
|
|
4963
|
-
var VERSION = "0.
|
|
5608
|
+
var VERSION = "0.10.0";
|
|
4964
5609
|
|
|
4965
5610
|
// src/player-entry.ts
|
|
5611
|
+
servePlans();
|
|
4966
5612
|
definePlayer();
|
|
4967
5613
|
globalThis.Dottie ?? (globalThis.Dottie = index_exports);
|
|
4968
5614
|
export {
|
|
@@ -5000,6 +5646,8 @@ export {
|
|
|
5000
5646
|
luminance,
|
|
5001
5647
|
orderedElements,
|
|
5002
5648
|
pageIndex,
|
|
5649
|
+
pageTheme,
|
|
5650
|
+
paletteColor,
|
|
5003
5651
|
parseDottie,
|
|
5004
5652
|
plan,
|
|
5005
5653
|
posterTime,
|