@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.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 __publicField = (obj, key, value2) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value2);
|
|
@@ -134,19 +134,80 @@ function trim(s) {
|
|
|
134
134
|
return s.replace(/^[ \t ]+|[ \t ]+$/g, "");
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
+
// src/theme.ts
|
|
138
|
+
var rgb = (r, g, b) => ({ r, g, b, a: 1 });
|
|
139
|
+
function hex(h) {
|
|
140
|
+
const n = parseInt(h.slice(1), 16);
|
|
141
|
+
return { r: (n >> 16 & 255) / 255, g: (n >> 8 & 255) / 255, b: (n & 255) / 255, a: 1 };
|
|
142
|
+
}
|
|
143
|
+
var PALETTE_NAMES = ["accent", "muted", "danger", "warning", "success", "info"];
|
|
144
|
+
var palette = (colors) => PALETTE_NAMES.map((name, i) => ({ name, color: hex(colors[i]) }));
|
|
145
|
+
var LIGHT_PALETTE = palette(["#2563eb", "#6b7280", "#dc2626", "#d97706", "#16a34a", "#0891b2"]);
|
|
146
|
+
var DARK_PALETTE = palette(["#93c5fd", "#cbd5e1", "#fca5a5", "#fcd34d", "#86efac", "#67e8f9"]);
|
|
147
|
+
var themes = [
|
|
148
|
+
{ id: "paper", name: "Paper", background: rgb(0.984, 0.98, 0.965), ink: rgb(0.12, 0.12, 0.13), palette: LIGHT_PALETTE },
|
|
149
|
+
{ id: "white", name: "White", background: rgb(1, 1, 1), ink: rgb(0.12, 0.12, 0.13), palette: LIGHT_PALETTE },
|
|
150
|
+
{
|
|
151
|
+
id: "sepia",
|
|
152
|
+
name: "Sepia",
|
|
153
|
+
background: rgb(0.953, 0.914, 0.824),
|
|
154
|
+
ink: rgb(0.23, 0.18, 0.13),
|
|
155
|
+
palette: palette(["#9a3412", "#78716c", "#b91c1c", "#b45309", "#4d7c0f", "#0e7490"])
|
|
156
|
+
},
|
|
157
|
+
{ id: "blueprint", name: "Blueprint", background: rgb(0.106, 0.227, 0.361), ink: rgb(0.91, 0.945, 0.984), palette: DARK_PALETTE },
|
|
158
|
+
{ id: "chalkboard", name: "Chalkboard", background: rgb(0.169, 0.227, 0.2), ink: rgb(0.945, 0.945, 0.918), palette: DARK_PALETTE },
|
|
159
|
+
{ id: "midnight", name: "Midnight", background: rgb(0.082, 0.086, 0.102), ink: rgb(0.925, 0.925, 0.94), palette: DARK_PALETTE }
|
|
160
|
+
];
|
|
161
|
+
function themeNamed(id) {
|
|
162
|
+
return themes.find((t) => t.id === id) ?? null;
|
|
163
|
+
}
|
|
164
|
+
function pageTheme(page) {
|
|
165
|
+
const c = page.customTheme;
|
|
166
|
+
if (c) return { id: "custom", name: c.name, background: c.background, ink: c.ink, palette: c.palette };
|
|
167
|
+
return themeNamed(page.theme);
|
|
168
|
+
}
|
|
169
|
+
function paletteColor(page, name) {
|
|
170
|
+
const n = name.trim().toLowerCase();
|
|
171
|
+
return (pageTheme(page)?.palette ?? LIGHT_PALETTE).find((p) => p.name.toLowerCase() === n)?.color ?? null;
|
|
172
|
+
}
|
|
173
|
+
function resolvingPalette(page) {
|
|
174
|
+
const named = (e) => !!e.style.named;
|
|
175
|
+
if (!page.elements.some(named) && !(page.symbols ?? []).some((s) => s.elements.some(named))) return page;
|
|
176
|
+
const resolve2 = (e) => {
|
|
177
|
+
const n = e.style.named;
|
|
178
|
+
if (!n) return e;
|
|
179
|
+
const style2 = { ...e.style };
|
|
180
|
+
const f = n.fill !== null ? paletteColor(page, n.fill) : null;
|
|
181
|
+
const s = n.stroke !== null ? paletteColor(page, n.stroke) : null;
|
|
182
|
+
const t = n.textColor !== null ? paletteColor(page, n.textColor) : null;
|
|
183
|
+
if (f) style2.fill = { ...f };
|
|
184
|
+
if (s) style2.stroke = { ...s };
|
|
185
|
+
if (t) style2.textColor = { ...t };
|
|
186
|
+
return { ...e, style: style2 };
|
|
187
|
+
};
|
|
188
|
+
return {
|
|
189
|
+
...page,
|
|
190
|
+
elements: page.elements.map(resolve2),
|
|
191
|
+
symbols: page.symbols ? page.symbols.map((s) => ({ ...s, elements: s.elements.map(resolve2) })) : page.symbols
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
137
195
|
// src/model.ts
|
|
138
196
|
var SUPPORTED_VERSION = 2;
|
|
139
197
|
var KINDS = ["rectangle", "ellipse", "diamond", "text", "connector", "freehand", "path", "note", "image", "symbol"];
|
|
140
198
|
var EASES = ["linear", "easeIn", "easeOut", "easeInOut", "hold", "spring", "bounce", "elastic"];
|
|
141
199
|
var KNOWN_FIELDS = {
|
|
142
200
|
file: ["version", "pages", "images"],
|
|
143
|
-
page: ["id", "name", "layers", "elements", "theme", "guidesX", "guidesY", "guidesLocked", "timeline", "symbols", "variables", "flows"],
|
|
201
|
+
page: ["id", "name", "layers", "elements", "theme", "customTheme", "guidesX", "guidesY", "guidesLocked", "timeline", "symbols", "variables", "flows"],
|
|
144
202
|
flow: ["id", "from", "to", "every", "speed", "color", "size", "when", "spread", "volume", "overrides"],
|
|
145
203
|
flowOverride: ["when", "volume"],
|
|
146
204
|
symbol: ["id", "name", "width", "height", "layers", "elements", "timeline", "button", "params"],
|
|
147
205
|
layer: ["id", "name", "visible", "locked"],
|
|
148
206
|
rgba: ["r", "g", "b", "a"],
|
|
149
|
-
style: ["stroke", "fill", "fillStyle", "strokeWidth", "dash", "roughness", "bowing", "fontSize", "font", "gridSize", "opacity", "dashOffset", "textColor"],
|
|
207
|
+
style: ["stroke", "fill", "fillStyle", "strokeWidth", "dash", "roughness", "bowing", "fontSize", "font", "gridSize", "opacity", "dashOffset", "textColor", "named"],
|
|
208
|
+
colorNames: ["fill", "stroke", "textColor"],
|
|
209
|
+
customTheme: ["name", "background", "ink", "palette", "base"],
|
|
210
|
+
paletteColor: ["name", "color"],
|
|
150
211
|
element: [
|
|
151
212
|
"id",
|
|
152
213
|
"kind",
|
|
@@ -184,8 +245,11 @@ var KNOWN_FIELDS = {
|
|
|
184
245
|
"capacity",
|
|
185
246
|
"delay",
|
|
186
247
|
"queue",
|
|
187
|
-
"gauge"
|
|
248
|
+
"gauge",
|
|
249
|
+
"containerID",
|
|
250
|
+
"container"
|
|
188
251
|
],
|
|
252
|
+
container: ["fixed", "lanes"],
|
|
189
253
|
gauge: ["metric", "of", "max", "warn", "alarm", "style", "label"],
|
|
190
254
|
strokePoint: ["x", "y"],
|
|
191
255
|
subpath: ["closed", "coords"],
|
|
@@ -194,7 +258,7 @@ var KNOWN_FIELDS = {
|
|
|
194
258
|
extensionRef: ["id", "shape", "version", "params"],
|
|
195
259
|
normalizedPoint: ["x", "y"],
|
|
196
260
|
normalizedRect: ["x", "y", "width", "height"],
|
|
197
|
-
connector: ["start", "end", "routing", "startHead", "endHead", "waypoints", "hops", "cost"],
|
|
261
|
+
connector: ["start", "end", "routing", "startHead", "endHead", "waypoints", "hops", "avoid", "cost"],
|
|
198
262
|
waypoint: ["x", "y"],
|
|
199
263
|
timeline: ["fps", "duration", "boil", "settle", "poster", "tracks", "camera", "markers"],
|
|
200
264
|
track: ["element", "keys", "boil", "follow", "repeats", "march"],
|
|
@@ -281,9 +345,36 @@ var Reader = class {
|
|
|
281
345
|
gridSize: num(s.gridSize) ?? null,
|
|
282
346
|
opacity: num(s.opacity) ?? null,
|
|
283
347
|
dashOffset: num(s.dashOffset) ?? null,
|
|
284
|
-
textColor: rgba(s.textColor) ?? null
|
|
348
|
+
textColor: rgba(s.textColor) ?? null,
|
|
349
|
+
named: this.colorNames(s.named)
|
|
285
350
|
};
|
|
286
351
|
}
|
|
352
|
+
container(v) {
|
|
353
|
+
if (!isObj(v)) return null;
|
|
354
|
+
this.fields(v, "container");
|
|
355
|
+
return { fixed: v.fixed === true ? true : null, lanes: v.lanes === "rows" || v.lanes === "columns" ? v.lanes : null };
|
|
356
|
+
}
|
|
357
|
+
colorNames(v) {
|
|
358
|
+
if (!isObj(v)) return null;
|
|
359
|
+
this.fields(v, "colorNames");
|
|
360
|
+
const n = { fill: str(v.fill) ?? null, stroke: str(v.stroke) ?? null, textColor: str(v.textColor) ?? null };
|
|
361
|
+
return n.fill === null && n.stroke === null && n.textColor === null ? null : n;
|
|
362
|
+
}
|
|
363
|
+
customTheme(v) {
|
|
364
|
+
if (!isObj(v)) return null;
|
|
365
|
+
this.fields(v, "customTheme");
|
|
366
|
+
const background = rgba(v.background), ink = rgba(v.ink);
|
|
367
|
+
if (!background || !ink) {
|
|
368
|
+
this.warn("A custom theme needs a background and an ink; drawn without it");
|
|
369
|
+
return null;
|
|
370
|
+
}
|
|
371
|
+
const palette2 = (Array.isArray(v.palette) ? v.palette : []).filter(isObj).flatMap((p) => {
|
|
372
|
+
this.fields(p, "paletteColor");
|
|
373
|
+
const name = str(p.name), color2 = rgba(p.color);
|
|
374
|
+
return name && color2 ? [{ name, color: color2 }] : [];
|
|
375
|
+
});
|
|
376
|
+
return { name: str(v.name) ?? "Custom", background, ink, palette: palette2, base: str(v.base) ?? null };
|
|
377
|
+
}
|
|
287
378
|
endpoint(v) {
|
|
288
379
|
const e = isObj(v) ? v : {};
|
|
289
380
|
const port = str(e.port);
|
|
@@ -315,6 +406,7 @@ var Reader = class {
|
|
|
315
406
|
return { x: num(w.x) ?? 0, y: num(w.y) ?? 0 };
|
|
316
407
|
}) : null,
|
|
317
408
|
hops: typeof v.hops === "boolean" ? v.hops : null,
|
|
409
|
+
avoid: typeof v.avoid === "boolean" ? v.avoid : null,
|
|
318
410
|
cost: typeof v.cost === "number" && Number.isFinite(v.cost) ? v.cost : null
|
|
319
411
|
};
|
|
320
412
|
}
|
|
@@ -383,7 +475,9 @@ var Reader = class {
|
|
|
383
475
|
capacity: num(v.capacity) ?? null,
|
|
384
476
|
delay: num(v.delay) ?? null,
|
|
385
477
|
queue: v.queue === true ? true : null,
|
|
386
|
-
gauge: this.gauge(v.gauge)
|
|
478
|
+
gauge: this.gauge(v.gauge),
|
|
479
|
+
containerID: uuid(v.containerID) ?? null,
|
|
480
|
+
container: this.container(v.container)
|
|
387
481
|
};
|
|
388
482
|
}
|
|
389
483
|
gauge(v) {
|
|
@@ -546,15 +640,15 @@ var Reader = class {
|
|
|
546
640
|
const keys = (Array.isArray(v.keys) ? v.keys : []).map((k) => this.keyframe(k)).filter((k) => k !== null && k.t >= 0 && k.t <= MAX_DURATION);
|
|
547
641
|
stableSortBy(keys, (k) => k.t);
|
|
548
642
|
const follow = Array.isArray(v.follow) ? v.follow.filter(isObj).flatMap((f) => {
|
|
549
|
-
const path = uuid(f.path),
|
|
550
|
-
if (!path ||
|
|
551
|
-
return [{ path, start, end, ease: this.ease(f.ease), reverse: bool(f.reverse) ?? null, orient: bool(f.orient) ?? null, laps: num(f.laps) ?? null }];
|
|
643
|
+
const path = uuid(f.path), start2 = num(f.start), end = num(f.end);
|
|
644
|
+
if (!path || start2 === void 0 || end === void 0 || start2 < 0 || end > MAX_DURATION) return [];
|
|
645
|
+
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 }];
|
|
552
646
|
}) : null;
|
|
553
647
|
const repeats = Array.isArray(v.repeats) ? v.repeats.filter(isObj).flatMap((r) => {
|
|
554
|
-
const
|
|
555
|
-
if (
|
|
648
|
+
const start2 = num(r.start), end = num(r.end);
|
|
649
|
+
if (start2 === void 0 || end === void 0) return [];
|
|
556
650
|
const count = num(r.count);
|
|
557
|
-
const rep = { start, end, count: count === void 0 ? null : Math.trunc(count) };
|
|
651
|
+
const rep = { start: start2, end, count: count === void 0 ? null : Math.trunc(count) };
|
|
558
652
|
return repeatIsValid(rep) ? [rep] : [];
|
|
559
653
|
}) : null;
|
|
560
654
|
return { element, keys, boil: bool(v.boil) ?? null, follow, repeats, march: clampMarch(num(v.march)) };
|
|
@@ -604,6 +698,7 @@ var Reader = class {
|
|
|
604
698
|
layers,
|
|
605
699
|
elements: (Array.isArray(v.elements) ? v.elements : []).map((e) => this.element(e, images)).filter((e) => e !== null),
|
|
606
700
|
theme: str(v.theme) ?? null,
|
|
701
|
+
customTheme: this.customTheme(v.customTheme),
|
|
607
702
|
timeline: this.timeline(v.timeline),
|
|
608
703
|
symbols: Array.isArray(v.symbols) ? v.symbols.map((x) => this.symbol(x, images)).filter((x) => x !== null) : null,
|
|
609
704
|
variables: this.variables(v.variables),
|
|
@@ -667,7 +762,7 @@ function parseDottie(source) {
|
|
|
667
762
|
if (isObj(v) && typeof v.data === "string") images[k] = reader.picture(v.data, str(v.format));
|
|
668
763
|
}
|
|
669
764
|
}
|
|
670
|
-
const pages = json.pages.map((p, i) => reader.page(p, images, i + 1));
|
|
765
|
+
const pages = json.pages.map((p, i) => resolvingPalette(reader.page(p, images, i + 1)));
|
|
671
766
|
if (new Set(pages.map((p) => p.id)).size !== pages.length) throw new DottieFormatError("Two pages share an id");
|
|
672
767
|
return { version, pages, warnings: reader.warnings };
|
|
673
768
|
}
|
|
@@ -687,7 +782,8 @@ function cloneElement(e) {
|
|
|
687
782
|
...e.style,
|
|
688
783
|
stroke: { ...e.style.stroke },
|
|
689
784
|
fill: { ...e.style.fill },
|
|
690
|
-
textColor: e.style.textColor ? { ...e.style.textColor } : null
|
|
785
|
+
textColor: e.style.textColor ? { ...e.style.textColor } : null,
|
|
786
|
+
named: e.style.named ? { ...e.style.named } : e.style.named
|
|
691
787
|
},
|
|
692
788
|
connector: e.connector ? {
|
|
693
789
|
...e.connector,
|
|
@@ -779,6 +875,21 @@ function pieces(para) {
|
|
|
779
875
|
if (cur || out.length === 0) out.push(cur);
|
|
780
876
|
return out;
|
|
781
877
|
}
|
|
878
|
+
function prefixWidths(chars, fontSize, face, font) {
|
|
879
|
+
const fam = familyMetrics(font);
|
|
880
|
+
const f = fam.faces[face] ?? fam.faces.regular;
|
|
881
|
+
const fallback = f.w["n"];
|
|
882
|
+
const out = [0];
|
|
883
|
+
let w = 0;
|
|
884
|
+
let prev = "";
|
|
885
|
+
for (const ch of chars) {
|
|
886
|
+
w += f.w[ch] ?? fallback;
|
|
887
|
+
if (prev) w += f.k[prev + ch] ?? 0;
|
|
888
|
+
prev = ch;
|
|
889
|
+
out.push(w * fontSize / 1e3);
|
|
890
|
+
}
|
|
891
|
+
return out;
|
|
892
|
+
}
|
|
782
893
|
function wrap(s, fontSize, maxWidth, face = "regular", font) {
|
|
783
894
|
const width = (t) => measure(t, fontSize, face, font);
|
|
784
895
|
const out = [];
|
|
@@ -794,8 +905,9 @@ function wrap(s, fontSize, maxWidth, face = "regular", font) {
|
|
|
794
905
|
}
|
|
795
906
|
while (width(line2.trimEnd()) > maxWidth && [...line2.trimEnd()].length > 1) {
|
|
796
907
|
const chars = [...line2];
|
|
908
|
+
const prefix = prefixWidths(chars, fontSize, face, font);
|
|
797
909
|
let n = chars.length - 1;
|
|
798
|
-
while (n > 1 &&
|
|
910
|
+
while (n > 1 && prefix[n] > maxWidth) n--;
|
|
799
911
|
out.push(chars.slice(0, n).join(""));
|
|
800
912
|
line2 = chars.slice(n).join("");
|
|
801
913
|
}
|
|
@@ -950,13 +1062,13 @@ var Path = class _Path {
|
|
|
950
1062
|
contours() {
|
|
951
1063
|
const out = [];
|
|
952
1064
|
let current = [];
|
|
953
|
-
let
|
|
1065
|
+
let start2 = { x: 0, y: 0 };
|
|
954
1066
|
for (const s of this.segments) {
|
|
955
|
-
const last = current.length ? current[current.length - 1] :
|
|
1067
|
+
const last = current.length ? current[current.length - 1] : start2;
|
|
956
1068
|
switch (s.op) {
|
|
957
1069
|
case "M":
|
|
958
1070
|
if (current.length > 1) out.push(current);
|
|
959
|
-
|
|
1071
|
+
start2 = s.p;
|
|
960
1072
|
current = [s.p];
|
|
961
1073
|
break;
|
|
962
1074
|
case "L":
|
|
@@ -978,9 +1090,9 @@ var Path = class _Path {
|
|
|
978
1090
|
}
|
|
979
1091
|
break;
|
|
980
1092
|
case "Z":
|
|
981
|
-
current.push(
|
|
1093
|
+
current.push(start2);
|
|
982
1094
|
if (current.length > 1) out.push(current);
|
|
983
|
-
current = [
|
|
1095
|
+
current = [start2];
|
|
984
1096
|
break;
|
|
985
1097
|
}
|
|
986
1098
|
}
|
|
@@ -1161,6 +1273,165 @@ function shapePath(e) {
|
|
|
1161
1273
|
return path;
|
|
1162
1274
|
}
|
|
1163
1275
|
|
|
1276
|
+
// src/elbow.ts
|
|
1277
|
+
var MARGIN = 16;
|
|
1278
|
+
var BEND = 24;
|
|
1279
|
+
var SMALLEST = 24;
|
|
1280
|
+
var MOST = 48;
|
|
1281
|
+
function obstacle(e) {
|
|
1282
|
+
if (e.kind === "connector" || e.kind === "text" || e.kind === "freehand") return null;
|
|
1283
|
+
if (e.kind === "path" && !e.extension) return null;
|
|
1284
|
+
if (e.style.opacity !== null && e.style.opacity <= 0) return null;
|
|
1285
|
+
const r = rotatedBounds(e);
|
|
1286
|
+
return r.width < SMALLEST || r.height < SMALLEST ? null : r;
|
|
1287
|
+
}
|
|
1288
|
+
function crosses(points, r) {
|
|
1289
|
+
const x1 = r.x + r.width, y1 = r.y + r.height;
|
|
1290
|
+
for (let k = 1; k < points.length; k++) {
|
|
1291
|
+
const p = points[k - 1], q = points[k];
|
|
1292
|
+
if (p.y === q.y) {
|
|
1293
|
+
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;
|
|
1294
|
+
} else if (p.x === q.x) {
|
|
1295
|
+
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;
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
return false;
|
|
1299
|
+
}
|
|
1300
|
+
var padded = (r) => ({ x: r.x - MARGIN, y: r.y - MARGIN, width: r.width + 2 * MARGIN, height: r.height + 2 * MARGIN });
|
|
1301
|
+
var inside = (p, r) => r.x < p.x && p.x < r.x + r.width && r.y < p.y && p.y < r.y + r.height;
|
|
1302
|
+
var memo = /* @__PURE__ */ new Map();
|
|
1303
|
+
function avoiding(plain, a, b, startVertical, endVertical, elements, except) {
|
|
1304
|
+
const usable = (r) => !inside(a, padded(r)) && !inside(b, padded(r));
|
|
1305
|
+
const all = [];
|
|
1306
|
+
let crossed = false;
|
|
1307
|
+
for (const e of elements) {
|
|
1308
|
+
if (except.has(e.id)) continue;
|
|
1309
|
+
const r = obstacle(e);
|
|
1310
|
+
if (!r || !usable(r)) continue;
|
|
1311
|
+
all.push(r);
|
|
1312
|
+
if (!crossed && crosses(plain, r)) crossed = true;
|
|
1313
|
+
}
|
|
1314
|
+
if (!crossed) return plain;
|
|
1315
|
+
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])]);
|
|
1316
|
+
const hit = memo.get(key);
|
|
1317
|
+
if (hit) return hit;
|
|
1318
|
+
const out = route(plain, a, b, startVertical, endVertical, all);
|
|
1319
|
+
if (memo.size >= 512) memo.clear();
|
|
1320
|
+
memo.set(key, out);
|
|
1321
|
+
return out;
|
|
1322
|
+
}
|
|
1323
|
+
function route(plain, a, b, startVertical, endVertical, usable) {
|
|
1324
|
+
let chosen = usable.map((_, i) => i).filter((i) => crosses(plain, usable[i]));
|
|
1325
|
+
if (chosen.length === 0) return plain;
|
|
1326
|
+
for (let round = 0; round < 4; round++) {
|
|
1327
|
+
if (chosen.length > MOST) return plain;
|
|
1328
|
+
const path = grid(a, b, startVertical, endVertical, chosen.map((i) => padded(usable[i])));
|
|
1329
|
+
if (!path) return plain;
|
|
1330
|
+
const more = usable.map((_, i) => i).filter((i) => !chosen.includes(i) && crosses(path, usable[i]));
|
|
1331
|
+
if (more.length === 0) return path;
|
|
1332
|
+
chosen = [...chosen, ...more].sort((p, q) => p - q);
|
|
1333
|
+
}
|
|
1334
|
+
return plain;
|
|
1335
|
+
}
|
|
1336
|
+
function grid(a, b, startVertical, endVertical, boxes) {
|
|
1337
|
+
let xs = [a.x, b.x, (a.x + b.x) / 2], ys = [a.y, b.y, (a.y + b.y) / 2];
|
|
1338
|
+
for (const r of boxes) {
|
|
1339
|
+
xs.push(r.x, r.x + r.width);
|
|
1340
|
+
ys.push(r.y, r.y + r.height);
|
|
1341
|
+
}
|
|
1342
|
+
xs = [...new Set(xs)].sort((p, q) => p - q);
|
|
1343
|
+
ys = [...new Set(ys)].sort((p, q) => p - q);
|
|
1344
|
+
const nx = xs.length, ny = ys.length;
|
|
1345
|
+
const ia = xs.indexOf(a.x), ja = ys.indexOf(a.y), ib = xs.indexOf(b.x), jb = ys.indexOf(b.y);
|
|
1346
|
+
if (ia < 0 || ja < 0 || ib < 0 || jb < 0) return null;
|
|
1347
|
+
const at = new Map(xs.map((x, k) => [x, k])), bt = new Map(ys.map((y, k) => [y, k]));
|
|
1348
|
+
const inner = new Uint8Array(nx * ny), acrossBlocked = new Uint8Array(nx * ny), downBlocked = new Uint8Array(nx * ny);
|
|
1349
|
+
for (const r of boxes) {
|
|
1350
|
+
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);
|
|
1351
|
+
if (i0 === void 0 || i1 === void 0 || j0 === void 0 || j1 === void 0) continue;
|
|
1352
|
+
for (let i = i0; i < i1; i++) for (let j = j0; j < j1; j++) {
|
|
1353
|
+
if (j > j0) acrossBlocked[i * ny + j] = 1;
|
|
1354
|
+
if (i > i0) downBlocked[i * ny + j] = 1;
|
|
1355
|
+
if (i > i0 && j > j0) inner[i * ny + j] = 1;
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
const blocked = (i, j) => inner[i * ny + j] === 1;
|
|
1359
|
+
const across = (i0, i1, j) => acrossBlocked[Math.min(i0, i1) * ny + j] === 1;
|
|
1360
|
+
const down = (j0, j1, i) => downBlocked[i * ny + Math.min(j0, j1)] === 1;
|
|
1361
|
+
const count = nx * ny * 2;
|
|
1362
|
+
const dist = new Array(count).fill(Infinity), prev = new Array(count).fill(-1);
|
|
1363
|
+
const done = new Array(count).fill(false);
|
|
1364
|
+
const heap = new Frontier();
|
|
1365
|
+
const first = (ia * ny + ja) * 2 + (startVertical ? 1 : 0);
|
|
1366
|
+
dist[first] = 0;
|
|
1367
|
+
heap.push(0, first);
|
|
1368
|
+
const want = (ib * ny + jb) * 2 + (endVertical ? 1 : 0);
|
|
1369
|
+
for (let top = heap.pop(); top; top = heap.pop()) {
|
|
1370
|
+
const [cost, s] = top;
|
|
1371
|
+
if (done[s]) continue;
|
|
1372
|
+
done[s] = true;
|
|
1373
|
+
if (s === want) break;
|
|
1374
|
+
const node = Math.floor(s / 2), axis = s % 2, i = Math.floor(node / ny), j = node % ny;
|
|
1375
|
+
for (const [di, dj] of [[1, 0], [-1, 0], [0, 1], [0, -1]]) {
|
|
1376
|
+
const ni = i + di, nj = j + dj;
|
|
1377
|
+
if (ni < 0 || ni >= nx || nj < 0 || nj >= ny || blocked(ni, nj)) continue;
|
|
1378
|
+
const turn = di !== 0 ? 0 : 1;
|
|
1379
|
+
if (di !== 0 ? across(i, ni, j) : down(j, nj, i)) continue;
|
|
1380
|
+
const length = Math.abs(xs[ni] - xs[i]) + Math.abs(ys[nj] - ys[j]);
|
|
1381
|
+
const c = cost + length + (turn !== axis ? BEND : 0);
|
|
1382
|
+
const t = (ni * ny + nj) * 2 + turn;
|
|
1383
|
+
if (c < dist[t]) {
|
|
1384
|
+
dist[t] = c;
|
|
1385
|
+
prev[t] = s;
|
|
1386
|
+
heap.push(c, t);
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
if (!done[want]) return null;
|
|
1391
|
+
const points = [];
|
|
1392
|
+
for (let s = want; s >= 0; s = prev[s]) {
|
|
1393
|
+
const node = Math.floor(s / 2);
|
|
1394
|
+
points.push({ x: xs[Math.floor(node / ny)], y: ys[node % ny] });
|
|
1395
|
+
}
|
|
1396
|
+
return points.reverse();
|
|
1397
|
+
}
|
|
1398
|
+
var Frontier = class {
|
|
1399
|
+
constructor() {
|
|
1400
|
+
__publicField(this, "heap", []);
|
|
1401
|
+
__publicField(this, "next", 0);
|
|
1402
|
+
}
|
|
1403
|
+
less(i, j) {
|
|
1404
|
+
const a = this.heap[i], b = this.heap[j];
|
|
1405
|
+
return a[0] < b[0] || a[0] === b[0] && a[1] < b[1];
|
|
1406
|
+
}
|
|
1407
|
+
push(cost, state) {
|
|
1408
|
+
this.heap.push([cost, this.next++, state]);
|
|
1409
|
+
for (let i = this.heap.length - 1; i > 0; ) {
|
|
1410
|
+
const up = i - 1 >> 1;
|
|
1411
|
+
if (!this.less(i, up)) break;
|
|
1412
|
+
[this.heap[i], this.heap[up]] = [this.heap[up], this.heap[i]];
|
|
1413
|
+
i = up;
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
pop() {
|
|
1417
|
+
if (this.heap.length === 0) return null;
|
|
1418
|
+
const top = this.heap[0], last = this.heap.pop();
|
|
1419
|
+
if (this.heap.length > 0) {
|
|
1420
|
+
this.heap[0] = last;
|
|
1421
|
+
for (let i = 0; ; ) {
|
|
1422
|
+
const l = 2 * i + 1, r = l + 1;
|
|
1423
|
+
let m = i;
|
|
1424
|
+
if (l < this.heap.length && this.less(l, m)) m = l;
|
|
1425
|
+
if (r < this.heap.length && this.less(r, m)) m = r;
|
|
1426
|
+
if (m === i) break;
|
|
1427
|
+
[this.heap[i], this.heap[m]] = [this.heap[m], this.heap[i]];
|
|
1428
|
+
i = m;
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
return [top[0], top[2]];
|
|
1432
|
+
}
|
|
1433
|
+
};
|
|
1434
|
+
|
|
1164
1435
|
// src/connector.ts
|
|
1165
1436
|
function portPoint(shape, port) {
|
|
1166
1437
|
const r = frame(shape);
|
|
@@ -1304,7 +1575,7 @@ function routeThrough(p, c) {
|
|
|
1304
1575
|
}
|
|
1305
1576
|
}
|
|
1306
1577
|
}
|
|
1307
|
-
function
|
|
1578
|
+
function route2(e, index) {
|
|
1308
1579
|
const c = e.connector;
|
|
1309
1580
|
if (!c) return { kind: "polyline", points: [] };
|
|
1310
1581
|
const [a, b] = endpoints(e, index);
|
|
@@ -1320,16 +1591,19 @@ function route(e, index) {
|
|
|
1320
1591
|
case "elbow": {
|
|
1321
1592
|
const startV = vertical(c.start.port) ?? !horizontal;
|
|
1322
1593
|
const endV = vertical(c.end.port) ?? !horizontal;
|
|
1594
|
+
let plain;
|
|
1323
1595
|
if (startV && endV) {
|
|
1324
1596
|
const my = a.y + dy / 2;
|
|
1325
|
-
|
|
1326
|
-
}
|
|
1327
|
-
if (!startV && !endV) {
|
|
1597
|
+
plain = [a, { x: a.x, y: my }, { x: b.x, y: my }, b];
|
|
1598
|
+
} else if (!startV && !endV) {
|
|
1328
1599
|
const mx = a.x + dx / 2;
|
|
1329
|
-
|
|
1330
|
-
}
|
|
1331
|
-
|
|
1332
|
-
return { kind: "polyline", points:
|
|
1600
|
+
plain = [a, { x: mx, y: a.y }, { x: mx, y: b.y }, b];
|
|
1601
|
+
} else if (startV) plain = [a, { x: a.x, y: b.y }, b];
|
|
1602
|
+
else plain = [a, { x: b.x, y: a.y }, b];
|
|
1603
|
+
if (c.avoid !== true) return { kind: "polyline", points: plain };
|
|
1604
|
+
const ends = new Set([c.start.elementID, c.end.elementID].filter((x) => x !== null));
|
|
1605
|
+
const routed = avoiding(plain, a, b, startV, endV, index.values(), ends);
|
|
1606
|
+
return { kind: "polyline", points: routed === plain ? plain : simplified(routed) };
|
|
1333
1607
|
}
|
|
1334
1608
|
}
|
|
1335
1609
|
}
|
|
@@ -1340,7 +1614,7 @@ function arrowhead(tip, from, size) {
|
|
|
1340
1614
|
return [wing(spread), wing(-spread)];
|
|
1341
1615
|
}
|
|
1342
1616
|
function connectorBounds(e, index) {
|
|
1343
|
-
const pts = routePoints(
|
|
1617
|
+
const pts = routePoints(route2(e, index));
|
|
1344
1618
|
if (pts.length === 0) return { x: 0, y: 0, width: 0, height: 0 };
|
|
1345
1619
|
let x0 = pts[0].x, y0 = pts[0].y, x1 = x0, y1 = y0;
|
|
1346
1620
|
for (const p of pts) {
|
|
@@ -1352,7 +1626,7 @@ function connectorBounds(e, index) {
|
|
|
1352
1626
|
return { x: x0, y: y0, width: x1 - x0, height: y1 - y0 };
|
|
1353
1627
|
}
|
|
1354
1628
|
function midpoint(e, index) {
|
|
1355
|
-
const pts = routePoints(
|
|
1629
|
+
const pts = routePoints(route2(e, index));
|
|
1356
1630
|
if (pts.length <= 1) return pts[0] ?? { x: 0, y: 0 };
|
|
1357
1631
|
const lengths = [];
|
|
1358
1632
|
let total = 0;
|
|
@@ -1372,9 +1646,17 @@ function midpoint(e, index) {
|
|
|
1372
1646
|
}
|
|
1373
1647
|
return pts[0];
|
|
1374
1648
|
}
|
|
1649
|
+
var indexes = /* @__PURE__ */ new WeakMap();
|
|
1650
|
+
function isIndexOf(page, index) {
|
|
1651
|
+
return indexes.get(page.elements)?.index === index;
|
|
1652
|
+
}
|
|
1375
1653
|
function indexOf(page) {
|
|
1654
|
+
const els = page.elements;
|
|
1655
|
+
const hit = indexes.get(els);
|
|
1656
|
+
if (hit && hit.members.length === els.length && hit.members.every((e, i) => e === els[i])) return hit.index;
|
|
1376
1657
|
const m = /* @__PURE__ */ new Map();
|
|
1377
|
-
for (const e of
|
|
1658
|
+
for (const e of els) if (!m.has(e.id)) m.set(e.id, e);
|
|
1659
|
+
indexes.set(els, { members: [...els], index: m });
|
|
1378
1660
|
return m;
|
|
1379
1661
|
}
|
|
1380
1662
|
|
|
@@ -1457,43 +1739,112 @@ function packetID(flow, k) {
|
|
|
1457
1739
|
return flow.slice(0, 24) + (n < 0 ? n + 2 ** 48 : n).toString(16).toUpperCase().padStart(12, "0");
|
|
1458
1740
|
}
|
|
1459
1741
|
var routeCost = (cost) => cost === null || !Number.isFinite(cost) ? 1 : Math.min(Math.max(cost, 0), 1e6);
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
const
|
|
1464
|
-
const
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
for (const e of frame2.elements) {
|
|
1742
|
+
var graphs = /* @__PURE__ */ new WeakMap();
|
|
1743
|
+
var networks = /* @__PURE__ */ new Map();
|
|
1744
|
+
function graphOf(frame2) {
|
|
1745
|
+
const els = frame2.elements;
|
|
1746
|
+
const hit = graphs.get(els);
|
|
1747
|
+
if (hit && hit.members.length === els.length && hit.members.every((e, i) => e === els[i])) return hit.graph;
|
|
1748
|
+
const shapes2 = new Set(els.filter((e) => e.kind !== "connector").map((e) => e.id));
|
|
1749
|
+
const lines = [];
|
|
1750
|
+
for (const e of els) {
|
|
1470
1751
|
const c = e.connector;
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1752
|
+
if (c) lines.push([e.id, c.start.elementID, c.end.elementID, String(routeCost(c.cost)), c.startHead !== "none" ? "both" : "one"]);
|
|
1753
|
+
}
|
|
1754
|
+
const key = JSON.stringify([[...shapes2], lines]);
|
|
1755
|
+
let graph = networks.get(key);
|
|
1756
|
+
if (!graph) {
|
|
1757
|
+
const next = /* @__PURE__ */ new Map();
|
|
1758
|
+
const add2 = (a, edge) => {
|
|
1759
|
+
const list = next.get(a);
|
|
1760
|
+
if (list) list.push(edge);
|
|
1761
|
+
else next.set(a, [edge]);
|
|
1762
|
+
};
|
|
1763
|
+
for (const e of els) {
|
|
1764
|
+
const c = e.connector;
|
|
1765
|
+
const a = c?.start.elementID ?? null, b = c?.end.elementID ?? null;
|
|
1766
|
+
if (!c || a === null || b === null || a === b || !shapes2.has(a) || !shapes2.has(b)) continue;
|
|
1767
|
+
const cost = routeCost(c.cost);
|
|
1768
|
+
add2(a, { to: b, via: e.id, back: false, cost });
|
|
1769
|
+
if (c.startHead !== "none") add2(b, { to: a, via: e.id, back: true, cost });
|
|
1770
|
+
}
|
|
1771
|
+
graph = { shapes: shapes2, next, trees: /* @__PURE__ */ new Map() };
|
|
1772
|
+
if (networks.size >= 16) networks.clear();
|
|
1773
|
+
networks.set(key, graph);
|
|
1774
|
+
}
|
|
1775
|
+
graphs.set(els, { members: [...els], graph });
|
|
1776
|
+
return graph;
|
|
1777
|
+
}
|
|
1778
|
+
var Frontier2 = class {
|
|
1779
|
+
constructor() {
|
|
1780
|
+
__publicField(this, "heap", []);
|
|
1781
|
+
}
|
|
1782
|
+
get size() {
|
|
1783
|
+
return this.heap.length;
|
|
1784
|
+
}
|
|
1785
|
+
less(i, j) {
|
|
1786
|
+
const a = this.heap[i], b = this.heap[j];
|
|
1787
|
+
return a.dist < b.dist || a.dist === b.dist && a.order < b.order;
|
|
1788
|
+
}
|
|
1789
|
+
swap(i, j) {
|
|
1790
|
+
[this.heap[i], this.heap[j]] = [this.heap[j], this.heap[i]];
|
|
1791
|
+
}
|
|
1792
|
+
push(id, dist, order) {
|
|
1793
|
+
this.heap.push({ id, dist, order });
|
|
1794
|
+
for (let i = this.heap.length - 1; i > 0; ) {
|
|
1795
|
+
const up = i - 1 >> 1;
|
|
1796
|
+
if (!this.less(i, up)) break;
|
|
1797
|
+
this.swap(i, up);
|
|
1798
|
+
i = up;
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
pop() {
|
|
1802
|
+
const top = this.heap[0], last = this.heap.pop();
|
|
1803
|
+
if (this.heap.length > 0) {
|
|
1804
|
+
this.heap[0] = last;
|
|
1805
|
+
for (let i = 0; ; ) {
|
|
1806
|
+
const l = 2 * i + 1, r = l + 1;
|
|
1807
|
+
let m = i;
|
|
1808
|
+
if (l < this.heap.length && this.less(l, m)) m = l;
|
|
1809
|
+
if (r < this.heap.length && this.less(r, m)) m = r;
|
|
1810
|
+
if (m === i) break;
|
|
1811
|
+
this.swap(i, m);
|
|
1812
|
+
i = m;
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
return top;
|
|
1476
1816
|
}
|
|
1817
|
+
};
|
|
1818
|
+
function flowTree(from, frame2, offline = /* @__PURE__ */ new Set(), until) {
|
|
1819
|
+
const graph = graphOf(frame2);
|
|
1820
|
+
const key = JSON.stringify([from, until ?? null, [...offline].sort()]);
|
|
1821
|
+
if (graph.trees.has(key)) return graph.trees.get(key);
|
|
1822
|
+
const tree = grow(graph, from, offline, until);
|
|
1823
|
+
if (graph.trees.size >= 4096) graph.trees.clear();
|
|
1824
|
+
graph.trees.set(key, tree);
|
|
1825
|
+
return tree;
|
|
1826
|
+
}
|
|
1827
|
+
function grow({ shapes: shapes2, next }, from, offline, until) {
|
|
1828
|
+
if (!shapes2.has(from) || offline.has(from)) return null;
|
|
1477
1829
|
const dist = /* @__PURE__ */ new Map([[from, 0]]), order = /* @__PURE__ */ new Map([[from, 0]]);
|
|
1478
1830
|
let counter = 1;
|
|
1479
1831
|
const tree = new FlowTree(from);
|
|
1480
|
-
const open =
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
if (da < db || da === db && order.get(open[i]) < order.get(open[best])) best = i;
|
|
1486
|
-
}
|
|
1487
|
-
const n = open.splice(best, 1)[0];
|
|
1832
|
+
const open = new Frontier2();
|
|
1833
|
+
open.push(from, 0, 0);
|
|
1834
|
+
while (open.size > 0) {
|
|
1835
|
+
const { id: n, order: o } = open.pop();
|
|
1836
|
+
if (tree.settled.has(n) || order.get(n) !== o) continue;
|
|
1488
1837
|
tree.settled.add(n);
|
|
1838
|
+
if (n === until && n !== from) break;
|
|
1489
1839
|
for (const edge of next.get(n) ?? []) {
|
|
1840
|
+
if (offline.has(edge.via) || offline.has(edge.to)) continue;
|
|
1490
1841
|
if (tree.settled.has(edge.to)) continue;
|
|
1491
1842
|
const d = dist.get(n) + edge.cost;
|
|
1492
1843
|
const old = dist.get(edge.to);
|
|
1493
1844
|
if (old !== void 0 && old <= d) continue;
|
|
1494
|
-
if (old === void 0) open.push(edge.to);
|
|
1495
1845
|
dist.set(edge.to, d);
|
|
1496
|
-
order.set(edge.to, counter
|
|
1846
|
+
order.set(edge.to, counter);
|
|
1847
|
+
open.push(edge.to, d, counter++);
|
|
1497
1848
|
tree.came.set(edge.to, { from: n, via: edge.via, back: edge.back });
|
|
1498
1849
|
}
|
|
1499
1850
|
}
|
|
@@ -1573,9 +1924,18 @@ function delayOf(e) {
|
|
|
1573
1924
|
var queueKey = (shape, flow) => shape + "|" + flow;
|
|
1574
1925
|
function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set(), sinceChange = null, carried = {}) {
|
|
1575
1926
|
const since = Math.max(0, Math.min(sinceChange ?? t, 1e6));
|
|
1576
|
-
const byID =
|
|
1577
|
-
|
|
1927
|
+
const byID = indexOf(frame2);
|
|
1928
|
+
const traced = /* @__PURE__ */ new Map();
|
|
1929
|
+
const traceOf = (c) => {
|
|
1930
|
+
let pts = traced.get(c.id);
|
|
1931
|
+
if (!pts) {
|
|
1932
|
+
pts = tracing(c, frame2, byID).points;
|
|
1933
|
+
traced.set(c.id, pts);
|
|
1934
|
+
}
|
|
1935
|
+
return pts;
|
|
1936
|
+
};
|
|
1578
1937
|
const metrics2 = emptyMetrics();
|
|
1938
|
+
const routes = /* @__PURE__ */ new Map();
|
|
1579
1939
|
for (const e of frame2.elements) {
|
|
1580
1940
|
if (e.kind === "connector" && !e.connector) continue;
|
|
1581
1941
|
metrics2.elements.set(e.id, { throughput: 0, load: null, delay: e.kind !== "connector" ? delayOf(e) : 0, backlog: 0, inFlight: 0, depth: 0 });
|
|
@@ -1587,7 +1947,7 @@ function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set()
|
|
|
1587
1947
|
if (c !== null && Number.isFinite(c)) room.set(e.id, Math.max(c, 0));
|
|
1588
1948
|
}
|
|
1589
1949
|
const full = () => new Set([...room].filter(([, v]) => v <= TINY).map(([id]) => id));
|
|
1590
|
-
const
|
|
1950
|
+
const route3 = (tree, goal) => {
|
|
1591
1951
|
const hops = [], shapes2 = [];
|
|
1592
1952
|
let at = goal;
|
|
1593
1953
|
for (let step = tree.came.get(at); step; step = tree.came.get(at)) {
|
|
@@ -1599,26 +1959,26 @@ function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set()
|
|
|
1599
1959
|
return { hops: hops.reverse(), shapes: shapes2.reverse() };
|
|
1600
1960
|
};
|
|
1601
1961
|
const way = (hops, shapes2) => {
|
|
1602
|
-
const points = [],
|
|
1962
|
+
const points = [], inside2 = [], stretches = [];
|
|
1603
1963
|
let length = 0;
|
|
1604
1964
|
hops.forEach(([id, back], k) => {
|
|
1605
1965
|
const c = byID.get(id);
|
|
1606
1966
|
if (!c) return;
|
|
1607
|
-
const line2 = back ? [...
|
|
1967
|
+
const line2 = back ? [...traceOf(c)].reverse() : traceOf(c);
|
|
1608
1968
|
if (line2.length === 0) return;
|
|
1609
1969
|
const last = points[points.length - 1];
|
|
1610
1970
|
if (last) {
|
|
1611
1971
|
const gap = Math.hypot(line2[0].x - last.x, line2[0].y - last.y);
|
|
1612
1972
|
const shape = byID.get(shapes2[k]);
|
|
1613
|
-
|
|
1973
|
+
inside2.push([length, length + gap, shape ? delayOf(shape) : 0]);
|
|
1614
1974
|
length += gap;
|
|
1615
1975
|
}
|
|
1616
|
-
const
|
|
1976
|
+
const start2 = length;
|
|
1617
1977
|
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);
|
|
1618
|
-
stretches.push([id,
|
|
1978
|
+
stretches.push([id, start2, length]);
|
|
1619
1979
|
points.push(...line2);
|
|
1620
1980
|
});
|
|
1621
|
-
return length > 0 ? { line: new Polyline(points), length, inside, stretches } : null;
|
|
1981
|
+
return length > 0 ? { line: new Polyline(points), length, inside: inside2, stretches } : null;
|
|
1622
1982
|
};
|
|
1623
1983
|
const along = (w, elapsedAt, speed) => {
|
|
1624
1984
|
let elapsed = elapsedAt, d = 0;
|
|
@@ -1650,25 +2010,31 @@ function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set()
|
|
|
1650
2010
|
const packets = [];
|
|
1651
2011
|
const waitingBy = /* @__PURE__ */ new Map();
|
|
1652
2012
|
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);
|
|
2013
|
+
const bases = /* @__PURE__ */ new Map();
|
|
2014
|
+
const baseTree = (from) => {
|
|
2015
|
+
if (!bases.has(from)) bases.set(from, flowTree(from, frame2, offline));
|
|
2016
|
+
return bases.get(from);
|
|
2017
|
+
};
|
|
1653
2018
|
for (const [i, flow] of list.entries()) {
|
|
1654
2019
|
if (flow.when !== null && values !== null && !holds(flow.when, values)) continue;
|
|
1655
2020
|
const source = byID.get(flow.from);
|
|
1656
|
-
const base2 = source ?
|
|
2021
|
+
const base2 = source ? baseTree(flow.from) : null;
|
|
2022
|
+
routes.set(flow.id, []);
|
|
1657
2023
|
if (!source || !base2) continue;
|
|
1658
2024
|
const volume = amountIn(flow, values);
|
|
1659
2025
|
const fm = { volume, placed: 0, backlog: 0, backup: 0, transit: null };
|
|
1660
2026
|
const placements = [];
|
|
1661
|
-
const
|
|
2027
|
+
const waiting2 = [];
|
|
1662
2028
|
const goals = base2.goals(flow.to, flow.spread === true);
|
|
1663
2029
|
const shares = goals.length > 1 ? goals.map((g) => [[g], volume / goals.length]) : [[flow.to, volume]];
|
|
1664
2030
|
for (const [candidates, share] of shares) {
|
|
1665
2031
|
if (!(share > TINY)) continue;
|
|
1666
2032
|
let left = share;
|
|
1667
2033
|
for (let n = 0; n < 32 && left > TINY; n++) {
|
|
1668
|
-
const tree = flowTree(flow.from, frame2, union2(offline, full()));
|
|
2034
|
+
const tree = flowTree(flow.from, frame2, union2(offline, full()), candidates[0]);
|
|
1669
2035
|
const goal2 = tree?.goals(candidates, false)[0];
|
|
1670
2036
|
if (!tree || goal2 === void 0) break;
|
|
1671
|
-
const r =
|
|
2037
|
+
const r = route3(tree, goal2);
|
|
1672
2038
|
const ids = [...r.shapes, ...r.hops.map(([id]) => id)];
|
|
1673
2039
|
const rooms = ids.filter((id) => room.has(id)).map((id) => room.get(id));
|
|
1674
2040
|
const fits = Math.min(left, rooms.length > 0 ? Math.min(...rooms) : Infinity);
|
|
@@ -1681,7 +2047,7 @@ function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set()
|
|
|
1681
2047
|
let at = flow.from;
|
|
1682
2048
|
const goal = base2.goals(candidates, false)[0];
|
|
1683
2049
|
if (goal !== void 0) {
|
|
1684
|
-
const r =
|
|
2050
|
+
const r = route3(base2, goal), done = full();
|
|
1685
2051
|
const sequence = [[r.shapes[0], r.shapes[0]]];
|
|
1686
2052
|
r.hops.forEach(([id], k) => {
|
|
1687
2053
|
sequence.push([id, r.shapes[k]], [r.shapes[k + 1], r.shapes[k]]);
|
|
@@ -1705,18 +2071,25 @@ function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set()
|
|
|
1705
2071
|
placements.push({ hops, shapes: shapes2, goal: at, amount: left, waits: true });
|
|
1706
2072
|
}
|
|
1707
2073
|
}
|
|
1708
|
-
const existing =
|
|
2074
|
+
const existing = waiting2.find(([s]) => s === at);
|
|
1709
2075
|
if (existing) existing[1] += left;
|
|
1710
|
-
else
|
|
1711
|
-
}
|
|
1712
|
-
waitingBy.set(flow.id,
|
|
2076
|
+
else waiting2.push([at, left]);
|
|
2077
|
+
}
|
|
2078
|
+
waitingBy.set(flow.id, waiting2);
|
|
2079
|
+
routes.set(flow.id, placements.map((p) => ({
|
|
2080
|
+
goal: p.goal,
|
|
2081
|
+
hops: p.hops,
|
|
2082
|
+
amount: p.amount,
|
|
2083
|
+
waits: p.waits === true,
|
|
2084
|
+
cost: p.hops.reduce((s, [id]) => s + routeCost(byID.get(id)?.connector?.cost ?? null), 0)
|
|
2085
|
+
})));
|
|
1713
2086
|
for (const p of placements) {
|
|
1714
2087
|
for (const id of [...p.shapes, ...p.hops.map(([h]) => h)]) bump(id, (m) => {
|
|
1715
2088
|
m.throughput += p.amount;
|
|
1716
2089
|
});
|
|
1717
2090
|
if (!p.waits) fm.placed += p.amount;
|
|
1718
2091
|
}
|
|
1719
|
-
for (const [at, a] of
|
|
2092
|
+
for (const [at, a] of waiting2) {
|
|
1720
2093
|
bump(at, (m) => {
|
|
1721
2094
|
m.backlog += a;
|
|
1722
2095
|
});
|
|
@@ -1761,10 +2134,12 @@ function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set()
|
|
|
1761
2134
|
metrics2.flows.set(flow.id, fm);
|
|
1762
2135
|
}
|
|
1763
2136
|
const piles = /* @__PURE__ */ new Map();
|
|
2137
|
+
const carriedKeys = Object.keys(carried).filter((k) => carried[k] > TINY);
|
|
1764
2138
|
for (const flow of list) {
|
|
1765
|
-
const
|
|
1766
|
-
const shapes2 =
|
|
1767
|
-
|
|
2139
|
+
const waiting2 = waitingBy.get(flow.id) ?? [];
|
|
2140
|
+
const shapes2 = waiting2.map(([s]) => s);
|
|
2141
|
+
const suffix = "|" + flow.id;
|
|
2142
|
+
if (carriedKeys.some((k) => k.endsWith(suffix))) for (const e of frame2.elements) {
|
|
1768
2143
|
const c = carried[queueKey(e.id, flow.id)];
|
|
1769
2144
|
if (c !== void 0 && c > TINY && !shapes2.includes(e.id)) shapes2.push(e.id);
|
|
1770
2145
|
}
|
|
@@ -1778,7 +2153,7 @@ function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set()
|
|
|
1778
2153
|
const shape = byID.get(at);
|
|
1779
2154
|
if (!shape) return;
|
|
1780
2155
|
const layer = byID.get(flow.from)?.layerID ?? shape.layerID;
|
|
1781
|
-
const backlog =
|
|
2156
|
+
const backlog = waiting2.find(([s]) => s === at)?.[1] ?? 0;
|
|
1782
2157
|
const before = Math.max(0, carried[queueKey(at, flow.id)] ?? 0);
|
|
1783
2158
|
let depth = before + backlog * since;
|
|
1784
2159
|
if (backlog <= TINY && before > TINY) {
|
|
@@ -1787,10 +2162,10 @@ function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set()
|
|
|
1787
2162
|
let total = 0;
|
|
1788
2163
|
for (let n2 = 0; n2 < 32; n2++) {
|
|
1789
2164
|
const blocked = new Set([...trial].filter(([, v]) => v <= TINY).map(([id]) => id));
|
|
1790
|
-
const tree = flowTree(at, frame2, union2(offline, blocked));
|
|
2165
|
+
const tree = flowTree(at, frame2, union2(offline, blocked), flow.to[0]);
|
|
1791
2166
|
const goal = tree?.goals(flow.to, false)[0];
|
|
1792
2167
|
if (!tree || goal === void 0) break;
|
|
1793
|
-
const r =
|
|
2168
|
+
const r = route3(tree, goal);
|
|
1794
2169
|
const ids = [...r.shapes.slice(1), ...r.hops.map(([id]) => id)];
|
|
1795
2170
|
const rooms = ids.filter((id) => trial.has(id)).map((id) => trial.get(id));
|
|
1796
2171
|
const spare = rooms.length > 0 ? Math.min(...rooms) : Infinity;
|
|
@@ -1845,7 +2220,7 @@ function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set()
|
|
|
1845
2220
|
if (c === null || !Number.isFinite(c) || !m) continue;
|
|
1846
2221
|
m.load = c > 0 ? m.throughput / c : 1;
|
|
1847
2222
|
}
|
|
1848
|
-
return { packets, metrics: metrics2 };
|
|
2223
|
+
return { packets, metrics: metrics2, routes };
|
|
1849
2224
|
}
|
|
1850
2225
|
var BLANK = {
|
|
1851
2226
|
seed: 1,
|
|
@@ -1900,21 +2275,21 @@ function metricLookup(m, frame2) {
|
|
|
1900
2275
|
return id === null ? null : metricValue(m, term.metric, id);
|
|
1901
2276
|
};
|
|
1902
2277
|
}
|
|
1903
|
-
function
|
|
2278
|
+
function finishPlan(elements, metrics2, values, frame2, since = Infinity, carried = {}) {
|
|
1904
2279
|
const lookup = metricLookup(metrics2, frame2);
|
|
1905
|
-
const
|
|
1906
|
-
for (
|
|
2280
|
+
const plan2 = [], readings = {};
|
|
2281
|
+
for (const e of elements) {
|
|
2282
|
+
const applied = [];
|
|
1907
2283
|
if (values !== null && e.overrides) {
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
}
|
|
2284
|
+
e.overrides.forEach((o, k) => {
|
|
2285
|
+
if (mentionsMetric(o.when) && holds(o.when, values, (m) => lookup(m, e.id))) applied.push(k);
|
|
2286
|
+
});
|
|
1912
2287
|
}
|
|
1913
|
-
|
|
1914
|
-
|
|
2288
|
+
const g = values === null ? null : e.gauge;
|
|
2289
|
+
if (!g) {
|
|
2290
|
+
plan2.push({ applied, gauge: null });
|
|
1915
2291
|
continue;
|
|
1916
2292
|
}
|
|
1917
|
-
const g = e.gauge;
|
|
1918
2293
|
let shown = metricValue(metrics2, g.metric, g.of);
|
|
1919
2294
|
const then = carried[gaugeKey(e.id)];
|
|
1920
2295
|
if (shown !== null && then !== void 0 && g.metric !== "depth" && g.metric !== "inFlight" && since < GAUGE_EASE) {
|
|
@@ -1922,22 +2297,33 @@ function finishing(elements, metrics2, values, frame2, flows, since = Infinity,
|
|
|
1922
2297
|
shown = then + (shown - then) * smooth;
|
|
1923
2298
|
}
|
|
1924
2299
|
if (shown !== null) readings[gaugeKey(e.id)] = shown;
|
|
1925
|
-
|
|
2300
|
+
plan2.push({ applied, gauge: { value: shown } });
|
|
1926
2301
|
}
|
|
2302
|
+
return { plan: plan2, readings };
|
|
2303
|
+
}
|
|
2304
|
+
function finishing(elements, metrics2, values, frame2, flows, since = Infinity, carried = {}) {
|
|
2305
|
+
const { plan: plan2, readings } = finishPlan(elements, metrics2, values, frame2, since, carried);
|
|
2306
|
+
const out = [];
|
|
2307
|
+
elements.forEach((e, i) => {
|
|
2308
|
+
for (const k of plan2[i].applied) e = applyOverride(looksOnly(e.overrides[k]), e);
|
|
2309
|
+
const g = plan2[i].gauge;
|
|
2310
|
+
if (g && e.gauge) out.push(...gauge(e, e.gauge, g.value, frame2, flows));
|
|
2311
|
+
else out.push(e);
|
|
2312
|
+
});
|
|
1927
2313
|
return { elements: out, readings };
|
|
1928
2314
|
}
|
|
1929
2315
|
var gaugeKey = (id) => "gauge|" + id;
|
|
1930
2316
|
var GAUGE_EASE = 1;
|
|
1931
|
-
var
|
|
2317
|
+
var hex2 = (h) => {
|
|
1932
2318
|
const v = parseInt(h.slice(1), 16);
|
|
1933
2319
|
return { r: (v >> 16 & 255) / 255, g: (v >> 8 & 255) / 255, b: (v & 255) / 255, a: 1 };
|
|
1934
2320
|
};
|
|
1935
|
-
var GAUGE_GREEN =
|
|
1936
|
-
var GAUGE_AMBER =
|
|
1937
|
-
var GAUGE_RED =
|
|
1938
|
-
var GAUGE_BLUE =
|
|
1939
|
-
var GAUGE_TRACK =
|
|
1940
|
-
var GAUGE_GRAY =
|
|
2321
|
+
var GAUGE_GREEN = hex2("#16a34a");
|
|
2322
|
+
var GAUGE_AMBER = hex2("#d97706");
|
|
2323
|
+
var GAUGE_RED = hex2("#dc2626");
|
|
2324
|
+
var GAUGE_BLUE = hex2("#2f6fdf");
|
|
2325
|
+
var GAUGE_TRACK = hex2("#e5e7eb");
|
|
2326
|
+
var GAUGE_GRAY = hex2("#9ca3af");
|
|
1941
2327
|
function formatted(metric, v) {
|
|
1942
2328
|
if (v === null || !Number.isFinite(v)) return "\u2013";
|
|
1943
2329
|
switch (metric) {
|
|
@@ -2069,7 +2455,7 @@ function line(a, b, rng, roughness, bowing, preserveEnds, into) {
|
|
|
2069
2455
|
const bow = offset(rng, length / 60, roughness) * bowing * gain;
|
|
2070
2456
|
const dx = b.x - a.x, dy = b.y - a.y;
|
|
2071
2457
|
const nx = -dy / length, ny = dx / length;
|
|
2072
|
-
const
|
|
2458
|
+
const start2 = preserveEnds ? a : (() => {
|
|
2073
2459
|
const x = a.x + j();
|
|
2074
2460
|
return { x, y: a.y + j() };
|
|
2075
2461
|
})();
|
|
@@ -2081,7 +2467,7 @@ function line(a, b, rng, roughness, bowing, preserveEnds, into) {
|
|
|
2081
2467
|
const c1y = a.y + dy * mid * 0.66 + ny * bow + j() * 0.5;
|
|
2082
2468
|
const c2x = a.x + dx * (0.5 + mid * 0.5) + nx * bow + j() * 0.5;
|
|
2083
2469
|
const c2y = a.y + dy * (0.5 + mid * 0.5) + ny * bow + j() * 0.5;
|
|
2084
|
-
into.moveTo(
|
|
2470
|
+
into.moveTo(start2);
|
|
2085
2471
|
into.curveTo(end, { x: c1x, y: c1y }, { x: c2x, y: c2y });
|
|
2086
2472
|
}
|
|
2087
2473
|
}
|
|
@@ -2223,17 +2609,17 @@ function clusterStrokes(c, angle2, size) {
|
|
|
2223
2609
|
});
|
|
2224
2610
|
}
|
|
2225
2611
|
function contains(contours, p) {
|
|
2226
|
-
let
|
|
2612
|
+
let inside2 = false;
|
|
2227
2613
|
for (const poly of contours) {
|
|
2228
2614
|
if (poly.length < 3) continue;
|
|
2229
2615
|
let k = poly.length - 1;
|
|
2230
2616
|
for (let i = 0; i < poly.length; i++) {
|
|
2231
2617
|
const a = poly[i], b = poly[k];
|
|
2232
|
-
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)
|
|
2618
|
+
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;
|
|
2233
2619
|
k = i;
|
|
2234
2620
|
}
|
|
2235
2621
|
}
|
|
2236
|
-
return
|
|
2622
|
+
return inside2;
|
|
2237
2623
|
}
|
|
2238
2624
|
function distance(p, contours) {
|
|
2239
2625
|
let best = Infinity;
|
|
@@ -2342,10 +2728,10 @@ var Cache = class {
|
|
|
2342
2728
|
__publicField(this, "limit");
|
|
2343
2729
|
this.limit = limit;
|
|
2344
2730
|
}
|
|
2345
|
-
get(key,
|
|
2731
|
+
get(key, make2) {
|
|
2346
2732
|
const hit = this.map.get(key);
|
|
2347
2733
|
if (hit !== void 0) return hit;
|
|
2348
|
-
const v =
|
|
2734
|
+
const v = make2();
|
|
2349
2735
|
if (this.map.size >= this.limit) this.map.clear();
|
|
2350
2736
|
this.map.set(key, v);
|
|
2351
2737
|
return v;
|
|
@@ -2510,7 +2896,7 @@ function connectorParts(e, index, page) {
|
|
|
2510
2896
|
const c = e.connector;
|
|
2511
2897
|
if (!c) return [new Path(), new Path()];
|
|
2512
2898
|
const [a, b] = endpoints(e, index);
|
|
2513
|
-
const r =
|
|
2899
|
+
const r = route2(e, index);
|
|
2514
2900
|
const hops = c.hops === true && page ? hopCrossings(e, r, page, index) : [];
|
|
2515
2901
|
const key = [
|
|
2516
2902
|
e.seed,
|
|
@@ -2569,26 +2955,59 @@ function subCubic(p, t0, t1) {
|
|
|
2569
2955
|
const left = split(p, t1)[0];
|
|
2570
2956
|
return t1 > 0 ? split(left, t0 / t1)[1] : [p[0], p[0], p[0], p[0]];
|
|
2571
2957
|
}
|
|
2958
|
+
function boxOf(pts) {
|
|
2959
|
+
let x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity;
|
|
2960
|
+
for (const p of pts) {
|
|
2961
|
+
x0 = Math.min(x0, p.x);
|
|
2962
|
+
y0 = Math.min(y0, p.y);
|
|
2963
|
+
x1 = Math.max(x1, p.x);
|
|
2964
|
+
y1 = Math.max(y1, p.y);
|
|
2965
|
+
}
|
|
2966
|
+
return { x0, y0, x1, y1 };
|
|
2967
|
+
}
|
|
2968
|
+
function touching(a, b) {
|
|
2969
|
+
const eps = 1e-6;
|
|
2970
|
+
return a.x0 <= b.x1 + eps && b.x0 <= a.x1 + eps && a.y0 <= b.y1 + eps && b.y0 <= a.y1 + eps;
|
|
2971
|
+
}
|
|
2972
|
+
var hopFrames = /* @__PURE__ */ new WeakMap();
|
|
2973
|
+
function linesBeneath(e, page, index) {
|
|
2974
|
+
let f = hopFrames.get(index);
|
|
2975
|
+
if (!f || f.layers !== page.layers || !isIndexOf(page, index)) {
|
|
2976
|
+
const visible = new Set(page.layers.filter((l) => l.visible).map((l) => l.id));
|
|
2977
|
+
const first = /* @__PURE__ */ new Map(), under = [];
|
|
2978
|
+
for (const o of orderedElements(page)) {
|
|
2979
|
+
if (!first.has(o.id)) first.set(o.id, under.length);
|
|
2980
|
+
if (o.kind === "connector" && visible.has(o.layerID)) under.push(o);
|
|
2981
|
+
}
|
|
2982
|
+
f = { layers: page.layers, first, under, lines: [] };
|
|
2983
|
+
if (isIndexOf(page, index)) hopFrames.set(index, f);
|
|
2984
|
+
}
|
|
2985
|
+
const count = f.first.get(e.id) ?? f.under.length;
|
|
2986
|
+
while (f.lines.length < count) {
|
|
2987
|
+
const points = routePoints(route2(f.under[f.lines.length], index));
|
|
2988
|
+
f.lines.push({ points, box: boxOf(points) });
|
|
2989
|
+
}
|
|
2990
|
+
return { lines: f.lines, count };
|
|
2991
|
+
}
|
|
2572
2992
|
function hopCrossings(e, r, page, index) {
|
|
2573
2993
|
const segments = hopSamples(r);
|
|
2574
2994
|
if (segments.length === 0) return [];
|
|
2575
|
-
const
|
|
2576
|
-
const beneath = [];
|
|
2577
|
-
for (const o of orderedElements(page)) {
|
|
2578
|
-
if (o.id === e.id) break;
|
|
2579
|
-
if (o.kind !== "connector" || !visible.has(o.layerID)) continue;
|
|
2580
|
-
beneath.push(routePoints(route(o, index)));
|
|
2581
|
-
}
|
|
2995
|
+
const { lines, count } = linesBeneath(e, page, index);
|
|
2582
2996
|
const rad = hopRadius(e.style.strokeWidth);
|
|
2583
2997
|
const out = [];
|
|
2584
2998
|
for (const samples of segments) {
|
|
2585
2999
|
const along = runningLengths(samples);
|
|
2586
3000
|
const length = along[along.length - 1];
|
|
2587
3001
|
const found = [];
|
|
3002
|
+
const near = [];
|
|
3003
|
+
const whole = boxOf(samples);
|
|
3004
|
+
for (let n = 0; n < count; n++) if (touching(whole, lines[n].box)) near.push(lines[n]);
|
|
2588
3005
|
for (let i = 0; i < samples.length - 1; i++) {
|
|
2589
3006
|
const p = samples[i], q = samples[i + 1];
|
|
2590
3007
|
const dx = q.x - p.x, dy = q.y - p.y;
|
|
2591
|
-
|
|
3008
|
+
const box = boxOf([p, q]);
|
|
3009
|
+
for (const { points: line2, box: lineBox } of near) {
|
|
3010
|
+
if (!touching(box, lineBox)) continue;
|
|
2592
3011
|
for (let k = 0; k < line2.length - 1; k++) {
|
|
2593
3012
|
const a = line2[k], b = line2[k + 1];
|
|
2594
3013
|
const ex = b.x - a.x, ey = b.y - a.y;
|
|
@@ -2868,8 +3287,8 @@ function followSpan(track, t) {
|
|
|
2868
3287
|
if (track.keys.some((k) => k.t >= span.end && k.t <= t && (k.x !== void 0 || k.y !== void 0))) return null;
|
|
2869
3288
|
return f;
|
|
2870
3289
|
}
|
|
2871
|
-
function tracing(e, page) {
|
|
2872
|
-
if (e.kind === "connector") return new Polyline(routePoints(
|
|
3290
|
+
function tracing(e, page, index = indexOf(page)) {
|
|
3291
|
+
if (e.kind === "connector") return new Polyline(routePoints(route2(e, index)));
|
|
2873
3292
|
const clean = cloneElement(e);
|
|
2874
3293
|
clean.style.roughness = 0;
|
|
2875
3294
|
const pts = outline(clean).contours()[0] ?? [];
|
|
@@ -2879,9 +3298,10 @@ function following(track, e, t, page) {
|
|
|
2879
3298
|
const f = followSpan(track, t);
|
|
2880
3299
|
if (!f) return e;
|
|
2881
3300
|
t = localTime(track, t);
|
|
2882
|
-
const
|
|
3301
|
+
const index = indexOf(page);
|
|
3302
|
+
const pathElement = index.get(f.path);
|
|
2883
3303
|
if (!pathElement || pathElement.id === e.id) return e;
|
|
2884
|
-
const line2 = tracing(pathElement, page);
|
|
3304
|
+
const line2 = tracing(pathElement, page, index);
|
|
2885
3305
|
const span = f.end - f.start;
|
|
2886
3306
|
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);
|
|
2887
3307
|
if (u > 1) u = rem(u, 1) === 0 ? 1 : rem(u, 1);
|
|
@@ -3136,17 +3556,30 @@ function symbolPage(s) {
|
|
|
3136
3556
|
function hasInstances(page) {
|
|
3137
3557
|
return page.elements.some((e) => e.kind === "symbol");
|
|
3138
3558
|
}
|
|
3139
|
-
|
|
3559
|
+
var Expansions = class {
|
|
3560
|
+
constructor(page, t, posing) {
|
|
3561
|
+
__publicField(this, "page");
|
|
3562
|
+
__publicField(this, "t");
|
|
3563
|
+
__publicField(this, "posing");
|
|
3564
|
+
/** For each instance: its time override, the budget it found and left, and what it made. */
|
|
3565
|
+
__publicField(this, "made", /* @__PURE__ */ new Map());
|
|
3566
|
+
this.page = page;
|
|
3567
|
+
this.t = t;
|
|
3568
|
+
this.posing = posing;
|
|
3569
|
+
}
|
|
3570
|
+
};
|
|
3571
|
+
function expandingSymbols(page, t, posing = true, times = /* @__PURE__ */ new Map(), memo2) {
|
|
3140
3572
|
if (!hasInstances(page)) return page;
|
|
3141
3573
|
const budget = { left: MAX_ELEMENTS };
|
|
3142
|
-
|
|
3574
|
+
const m = memo2 && memo2.page === page && memo2.t === t && memo2.posing === posing ? memo2 : void 0;
|
|
3575
|
+
return { ...page, elements: expand(page.elements, page, t, symbolTable(page), [], posing, budget, times, m) };
|
|
3143
3576
|
}
|
|
3144
3577
|
function symbolTable(page) {
|
|
3145
3578
|
const table = /* @__PURE__ */ new Map();
|
|
3146
3579
|
for (const s of page.symbols ?? []) if (!table.has(s.id)) table.set(s.id, s);
|
|
3147
3580
|
return table;
|
|
3148
3581
|
}
|
|
3149
|
-
function expand(elements, page, t, table, stack, posing, budget, times = /* @__PURE__ */ new Map()) {
|
|
3582
|
+
function expand(elements, page, t, table, stack, posing, budget, times = /* @__PURE__ */ new Map(), memo2) {
|
|
3150
3583
|
const instances = new Set(elements.filter((e) => e.kind === "symbol").map((e) => e.id));
|
|
3151
3584
|
if (instances.size === 0) {
|
|
3152
3585
|
budget.left -= elements.length;
|
|
@@ -3171,6 +3604,14 @@ function expand(elements, page, t, table, stack, posing, budget, times = /* @__P
|
|
|
3171
3604
|
budget.left -= 1;
|
|
3172
3605
|
continue;
|
|
3173
3606
|
}
|
|
3607
|
+
const override0 = stack.length === 0 ? times.get(e.id) : void 0;
|
|
3608
|
+
const was = memo2?.made.get(e);
|
|
3609
|
+
if (was && was.override === override0 && was.after > 0 && budget.left - (was.before - was.after) > 0) {
|
|
3610
|
+
budget.left -= was.before - was.after;
|
|
3611
|
+
for (const x of was.out) out.push(x);
|
|
3612
|
+
continue;
|
|
3613
|
+
}
|
|
3614
|
+
const before = budget.left, from = out.length;
|
|
3174
3615
|
budget.left -= 1;
|
|
3175
3616
|
const id = e.symbol;
|
|
3176
3617
|
const symbol = id === null ? void 0 : table.get(id);
|
|
@@ -3186,6 +3627,7 @@ function expand(elements, page, t, table, stack, posing, budget, times = /* @__P
|
|
|
3186
3627
|
layers: [{ id: e.layerID, name: "", visible: true, locked: false }]
|
|
3187
3628
|
};
|
|
3188
3629
|
for (const x of expand(drawn.elements, drawn, u, table, [...stack, id], posing, budget)) out.push(placed(x, e, symbol));
|
|
3630
|
+
if (memo2 && stack.length === 0 && !was) memo2.made.set(e, { override: override0, before, after: budget.left, out: out.slice(from) });
|
|
3189
3631
|
}
|
|
3190
3632
|
return out;
|
|
3191
3633
|
}
|
|
@@ -3238,8 +3680,8 @@ function mix2(x) {
|
|
|
3238
3680
|
return z ^ z >> 31n;
|
|
3239
3681
|
}
|
|
3240
3682
|
function halves(id) {
|
|
3241
|
-
const
|
|
3242
|
-
return [BigInt(`0x${
|
|
3683
|
+
const hex3 = id.replace(/-/g, "").padStart(32, "0").slice(-32);
|
|
3684
|
+
return [BigInt(`0x${hex3.slice(0, 16)}`), BigInt(`0x${hex3.slice(16)}`)];
|
|
3243
3685
|
}
|
|
3244
3686
|
function derivedID(instance, inner) {
|
|
3245
3687
|
const [a, b] = halves(instance), [c, d] = halves(inner);
|
|
@@ -3453,13 +3895,13 @@ function blocks(lines) {
|
|
|
3453
3895
|
flush();
|
|
3454
3896
|
return out;
|
|
3455
3897
|
}
|
|
3456
|
-
function parseList(lines,
|
|
3457
|
-
const first = reItem.exec(lines[
|
|
3898
|
+
function parseList(lines, start2) {
|
|
3899
|
+
const first = reItem.exec(lines[start2]);
|
|
3458
3900
|
const indent = first[1].length;
|
|
3459
3901
|
const bullet = /^\d/.test(first[2]) ? null : first[2];
|
|
3460
3902
|
const ordered = bullet === null ? parseInt(first[2], 10) : null;
|
|
3461
3903
|
const items = [];
|
|
3462
|
-
let i =
|
|
3904
|
+
let i = start2;
|
|
3463
3905
|
while (i < lines.length) {
|
|
3464
3906
|
const m = reItem.exec(lines[i]);
|
|
3465
3907
|
if (!m || m[1].length !== indent || bullet === null !== /^\d/.test(m[2]) || bullet !== null && m[2] !== bullet) break;
|
|
@@ -3567,12 +4009,23 @@ function inline(src, style2 = {}) {
|
|
|
3567
4009
|
i += m[0].length;
|
|
3568
4010
|
continue;
|
|
3569
4011
|
}
|
|
3570
|
-
if (m = /^(
|
|
4012
|
+
if (m = /^(\*\*\*|___)(?=\S)([\s\S]*?\S)\1(?!\*|_)/.exec(rest)) {
|
|
3571
4013
|
emit();
|
|
3572
|
-
out.push(...inline(m[2], { ...style2, bold: true }));
|
|
4014
|
+
out.push(...inline(m[2], { ...style2, bold: true, italic: true }));
|
|
3573
4015
|
i += m[0].length;
|
|
3574
4016
|
continue;
|
|
3575
4017
|
}
|
|
4018
|
+
if (m = /^(\*\*|__)(?=\S)([\s\S]*?\S)\1/.exec(rest)) {
|
|
4019
|
+
let body = m[2], length = m[0].length;
|
|
4020
|
+
if (m[1] === "**" && rest[length] === "*" && (body.replace(/\*\*/g, "").match(/\*/g) ?? []).length % 2 === 1) {
|
|
4021
|
+
body += "*";
|
|
4022
|
+
length++;
|
|
4023
|
+
}
|
|
4024
|
+
emit();
|
|
4025
|
+
out.push(...inline(body, { ...style2, bold: true }));
|
|
4026
|
+
i += length;
|
|
4027
|
+
continue;
|
|
4028
|
+
}
|
|
3576
4029
|
if ((m = /^(\*|_)(?=\S)([\s\S]*?\S)\1(?!\1)/.exec(rest)) && (m[1] === "*" || !/\w/.test(src[i - 1] ?? ""))) {
|
|
3577
4030
|
emit();
|
|
3578
4031
|
out.push(...inline(m[2], { ...style2, italic: true }));
|
|
@@ -3788,10 +4241,10 @@ function layoutLines(paras, width, height, fontSize, fam) {
|
|
|
3788
4241
|
let k = 0;
|
|
3789
4242
|
let firstLine = true;
|
|
3790
4243
|
while (k < chars.length || firstLine && chars.length === 0) {
|
|
3791
|
-
const
|
|
4244
|
+
const start2 = firstLine ? p.firstIndent : p.indent;
|
|
3792
4245
|
const maxX2 = width;
|
|
3793
4246
|
const glyphs = [];
|
|
3794
|
-
let x =
|
|
4247
|
+
let x = start2, lastSpace = -1, end = k;
|
|
3795
4248
|
for (; end < chars.length; end++) {
|
|
3796
4249
|
const { ch, run } = chars[end];
|
|
3797
4250
|
const r = runs[run];
|
|
@@ -3840,7 +4293,7 @@ function layoutLines(paras, width, height, fontSize, fam) {
|
|
|
3840
4293
|
}
|
|
3841
4294
|
if (glyphs.length === 0 && end === k && k < chars.length) {
|
|
3842
4295
|
const { ch, run } = chars[k];
|
|
3843
|
-
glyphs.push({ ch, run, x:
|
|
4296
|
+
glyphs.push({ ch, run, x: start2, w: charWidth(ch, runs[run], fam) });
|
|
3844
4297
|
end = k + 1;
|
|
3845
4298
|
}
|
|
3846
4299
|
let asc = 0, desc = 0, lead = 0;
|
|
@@ -3866,13 +4319,38 @@ function layoutLines(paras, width, height, fontSize, fam) {
|
|
|
3866
4319
|
});
|
|
3867
4320
|
return lines;
|
|
3868
4321
|
}
|
|
4322
|
+
var Memo = class {
|
|
4323
|
+
constructor(limit) {
|
|
4324
|
+
__publicField(this, "map", /* @__PURE__ */ new Map());
|
|
4325
|
+
__publicField(this, "limit");
|
|
4326
|
+
this.limit = limit;
|
|
4327
|
+
}
|
|
4328
|
+
get(key, make2) {
|
|
4329
|
+
const hit = this.map.get(key);
|
|
4330
|
+
if (hit !== void 0) return hit;
|
|
4331
|
+
const v = make2();
|
|
4332
|
+
if (this.map.size >= this.limit) this.map.clear();
|
|
4333
|
+
this.map.set(key, v);
|
|
4334
|
+
return v;
|
|
4335
|
+
}
|
|
4336
|
+
};
|
|
4337
|
+
var layouts = new Memo(500);
|
|
4338
|
+
var markups = new Memo(500);
|
|
3869
4339
|
function noteMarkup(source, e) {
|
|
4340
|
+
const st = e.style, ink = st.stroke;
|
|
4341
|
+
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]);
|
|
4342
|
+
return markups.get(key, () => computeNoteMarkup(source, e));
|
|
4343
|
+
}
|
|
4344
|
+
function computeNoteMarkup(source, e) {
|
|
3870
4345
|
const rect2 = textRect({ x: e.x, y: e.y, width: e.width, height: e.height });
|
|
3871
4346
|
if (!(rect2.width > 1 && rect2.height > 1)) return "";
|
|
3872
4347
|
const fam = familyMetrics(e.style.font);
|
|
3873
|
-
const
|
|
3874
|
-
|
|
3875
|
-
|
|
4348
|
+
const layoutKey = JSON.stringify([...[rect2.width, rect2.height, e.style.fontSize].map(String), e.style.font ?? "", source]);
|
|
4349
|
+
const lines = layouts.get(layoutKey, () => {
|
|
4350
|
+
const b = new Builder(e.style.fontSize, rect2.width, fam);
|
|
4351
|
+
for (const block of parseBlocks(source)) b.block(block, { indent: 0, marker: null, quote: null, listDepth: 0 });
|
|
4352
|
+
return layoutLines(b.paras, rect2.width, rect2.height, e.style.fontSize, fam);
|
|
4353
|
+
});
|
|
3876
4354
|
const ink = color(e.style.stroke);
|
|
3877
4355
|
let s = "";
|
|
3878
4356
|
const marks = [];
|
|
@@ -3931,20 +4409,6 @@ function sameStyle(a, b) {
|
|
|
3931
4409
|
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;
|
|
3932
4410
|
}
|
|
3933
4411
|
|
|
3934
|
-
// src/theme.ts
|
|
3935
|
-
var rgb = (r, g, b) => ({ r, g, b, a: 1 });
|
|
3936
|
-
var themes = [
|
|
3937
|
-
{ id: "paper", name: "Paper", background: rgb(0.984, 0.98, 0.965), ink: rgb(0.12, 0.12, 0.13) },
|
|
3938
|
-
{ id: "white", name: "White", background: rgb(1, 1, 1), ink: rgb(0.12, 0.12, 0.13) },
|
|
3939
|
-
{ id: "sepia", name: "Sepia", background: rgb(0.953, 0.914, 0.824), ink: rgb(0.23, 0.18, 0.13) },
|
|
3940
|
-
{ id: "blueprint", name: "Blueprint", background: rgb(0.106, 0.227, 0.361), ink: rgb(0.91, 0.945, 0.984) },
|
|
3941
|
-
{ id: "chalkboard", name: "Chalkboard", background: rgb(0.169, 0.227, 0.2), ink: rgb(0.945, 0.945, 0.918) },
|
|
3942
|
-
{ id: "midnight", name: "Midnight", background: rgb(0.082, 0.086, 0.102), ink: rgb(0.925, 0.925, 0.94) }
|
|
3943
|
-
];
|
|
3944
|
-
function themeNamed(id) {
|
|
3945
|
-
return themes.find((t) => t.id === id) ?? null;
|
|
3946
|
-
}
|
|
3947
|
-
|
|
3948
4412
|
// src/svg.ts
|
|
3949
4413
|
function bounds(page, padding = 24) {
|
|
3950
4414
|
if (hasInstances(page)) return bounds(expandingSymbols(page, 0), padding);
|
|
@@ -3961,6 +4425,26 @@ function bounds(page, padding = 24) {
|
|
|
3961
4425
|
return inset(r, -padding, -padding);
|
|
3962
4426
|
}
|
|
3963
4427
|
function plan(page) {
|
|
4428
|
+
const hit = plans.get(page);
|
|
4429
|
+
if (hit && unchanged(page, hit.fields)) return hit.plan;
|
|
4430
|
+
const made = makePlan(page);
|
|
4431
|
+
plans.set(page, { fields: { ...page }, plan: made });
|
|
4432
|
+
return made;
|
|
4433
|
+
}
|
|
4434
|
+
var plans = /* @__PURE__ */ new WeakMap();
|
|
4435
|
+
function hasPlan(page) {
|
|
4436
|
+
const hit = plans.get(page);
|
|
4437
|
+
return !!hit && unchanged(page, hit.fields);
|
|
4438
|
+
}
|
|
4439
|
+
function rememberPlan(page, made) {
|
|
4440
|
+
plans.set(page, { fields: { ...page }, plan: made });
|
|
4441
|
+
}
|
|
4442
|
+
function unchanged(page, fields) {
|
|
4443
|
+
const a = page, b = fields;
|
|
4444
|
+
const keys = Object.keys(a);
|
|
4445
|
+
return keys.length === Object.keys(b).length && keys.every((k) => a[k] === b[k]);
|
|
4446
|
+
}
|
|
4447
|
+
function makePlan(page) {
|
|
3964
4448
|
const tl = page.timeline ?? { fps: 12, duration: 0, boil: null, settle: null, poster: null, tracks: [], camera: null, markers: null };
|
|
3965
4449
|
const times = frameTimes(tl);
|
|
3966
4450
|
const first = tl.camera?.find((k) => k.width > 1 && k.height > 1);
|
|
@@ -3988,9 +4472,9 @@ function svgOpen(b, background, paint) {
|
|
|
3988
4472
|
}
|
|
3989
4473
|
return out;
|
|
3990
4474
|
}
|
|
3991
|
-
function svgParts(page, background, fixed, links = false) {
|
|
3992
|
-
if (hasInstances(page)) return svgParts(expandingSymbols(page, 0), background, fixed, links);
|
|
3993
|
-
const theme =
|
|
4475
|
+
function svgParts(page, background, fixed, links = false, only) {
|
|
4476
|
+
if (hasInstances(page)) return svgParts(expandingSymbols(page, 0), background, fixed, links, only);
|
|
4477
|
+
const theme = pageTheme(page);
|
|
3994
4478
|
const bg = background ? theme?.background ?? background : null;
|
|
3995
4479
|
const ink = theme?.ink ?? black;
|
|
3996
4480
|
const d = { ...page, elements: page.elements.map((e) => resolvingInk(e, ink)) };
|
|
@@ -4000,11 +4484,11 @@ function svgParts(page, background, fixed, links = false) {
|
|
|
4000
4484
|
const index = indexOf(d);
|
|
4001
4485
|
const ordered = orderedElements(d);
|
|
4002
4486
|
const floors = ordered.filter((e) => visible.has(e.layerID) && isFloor(e));
|
|
4003
|
-
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"/>
|
|
4487
|
+
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"/>
|
|
4004
4488
|
`).join("");
|
|
4005
4489
|
if (rockMarkup) parts.push({ key: "rock", markup: rockMarkup });
|
|
4006
4490
|
for (const e of ordered) {
|
|
4007
|
-
if (!visible.has(e.layerID)) continue;
|
|
4491
|
+
if (!visible.has(e.layerID) || only && !only(e.id)) continue;
|
|
4008
4492
|
const alpha = Math.min(Math.max(e.style.opacity ?? 1, 0), 1);
|
|
4009
4493
|
if (alpha <= 0) continue;
|
|
4010
4494
|
let body = svgElement(e, index, bg, d);
|
|
@@ -4026,7 +4510,7 @@ ${body}</a>
|
|
|
4026
4510
|
}
|
|
4027
4511
|
function svg(page, background = { r: 1, g: 1, b: 1, a: 1 }, fixed) {
|
|
4028
4512
|
const { bounds: b, parts } = svgParts(page, background, fixed, true);
|
|
4029
|
-
return svgOpen(b,
|
|
4513
|
+
return svgOpen(b, pageTheme(page)?.background ?? background, background !== null) + parts.map((p) => p.markup).join("") + "</svg>\n";
|
|
4030
4514
|
}
|
|
4031
4515
|
function labelRect(e, index) {
|
|
4032
4516
|
const m = midpoint(e, index);
|
|
@@ -4120,7 +4604,7 @@ function svgElement(e, index, labelBackground, page) {
|
|
|
4120
4604
|
}
|
|
4121
4605
|
s += `<path d="${outline(e).data()}" fill="none" ${strokeAttrs}/>
|
|
4122
4606
|
`;
|
|
4123
|
-
if (text) s += e.kind === "note" ? noteMarkup(text, e) : textElement(text, e.kind === "path" ? pathLabelBox(e) : frame(e), e, true);
|
|
4607
|
+
if (text) s += e.kind === "note" ? noteMarkup(text, e) : textElement(text, e.kind === "path" || e.kind === "rectangle" ? pathLabelBox(e) : frame(e), e, true);
|
|
4124
4608
|
return s;
|
|
4125
4609
|
}
|
|
4126
4610
|
default:
|
|
@@ -4152,6 +4636,95 @@ function textElement(text, f, e, centered) {
|
|
|
4152
4636
|
return s + "</text>\n";
|
|
4153
4637
|
}
|
|
4154
4638
|
|
|
4639
|
+
// src/planner.ts
|
|
4640
|
+
var WORKER_WORK = 3e4;
|
|
4641
|
+
function planWork(page) {
|
|
4642
|
+
const tl = page.timeline;
|
|
4643
|
+
if (!tl || tl.camera?.some((k) => k.width > 1 && k.height > 1)) return 0;
|
|
4644
|
+
const elements = page.elements.length + (page.symbols ?? []).reduce((n, s) => n + s.elements.length, 0);
|
|
4645
|
+
return frameTimes(tl).length * elements;
|
|
4646
|
+
}
|
|
4647
|
+
function workerMaker() {
|
|
4648
|
+
if (typeof Worker === "undefined" || typeof __DOTTIE_WORKER__ === "undefined") return null;
|
|
4649
|
+
if (__DOTTIE_WORKER__ === "self" && typeof dottiePlayerScript === "function") {
|
|
4650
|
+
const source = `(${dottiePlayerScript.toString()})();
|
|
4651
|
+
`;
|
|
4652
|
+
return () => {
|
|
4653
|
+
const url = URL.createObjectURL(new Blob([source], { type: "text/javascript" }));
|
|
4654
|
+
try {
|
|
4655
|
+
return new Worker(url);
|
|
4656
|
+
} finally {
|
|
4657
|
+
setTimeout(() => URL.revokeObjectURL(url), 6e4);
|
|
4658
|
+
}
|
|
4659
|
+
};
|
|
4660
|
+
}
|
|
4661
|
+
if (__DOTTIE_WORKER__ === "sibling") {
|
|
4662
|
+
const here = import.meta.url;
|
|
4663
|
+
if (!here) return null;
|
|
4664
|
+
return () => new Worker(new URL("./dottie-player.min.js", here));
|
|
4665
|
+
}
|
|
4666
|
+
return null;
|
|
4667
|
+
}
|
|
4668
|
+
var make;
|
|
4669
|
+
var worker = null;
|
|
4670
|
+
var failed = false;
|
|
4671
|
+
var nextID = 1;
|
|
4672
|
+
var waiting = /* @__PURE__ */ new Map();
|
|
4673
|
+
function plansInWorker(page) {
|
|
4674
|
+
if (failed || hasPlan(page) || planWork(page) < WORKER_WORK) return false;
|
|
4675
|
+
if (make === void 0) make = workerMaker();
|
|
4676
|
+
return make !== null;
|
|
4677
|
+
}
|
|
4678
|
+
function planInWorker(page) {
|
|
4679
|
+
return new Promise((resolve2) => {
|
|
4680
|
+
const done = (p) => {
|
|
4681
|
+
if (p) rememberPlan(page, p);
|
|
4682
|
+
resolve2();
|
|
4683
|
+
};
|
|
4684
|
+
let w;
|
|
4685
|
+
try {
|
|
4686
|
+
w = worker ?? start();
|
|
4687
|
+
} catch {
|
|
4688
|
+
failed = true;
|
|
4689
|
+
done(null);
|
|
4690
|
+
return;
|
|
4691
|
+
}
|
|
4692
|
+
const id = nextID++;
|
|
4693
|
+
waiting.set(id, { page, done });
|
|
4694
|
+
try {
|
|
4695
|
+
w.postMessage({ dottiePlan: id, page });
|
|
4696
|
+
} catch {
|
|
4697
|
+
waiting.delete(id);
|
|
4698
|
+
done(null);
|
|
4699
|
+
}
|
|
4700
|
+
});
|
|
4701
|
+
}
|
|
4702
|
+
function start() {
|
|
4703
|
+
const w = make();
|
|
4704
|
+
w.onmessage = (ev) => {
|
|
4705
|
+
const { dottiePlan: id, plan: p } = ev.data ?? {};
|
|
4706
|
+
const job = id === void 0 ? void 0 : waiting.get(id);
|
|
4707
|
+
if (!job) return;
|
|
4708
|
+
waiting.delete(id);
|
|
4709
|
+
job.done(p ?? null);
|
|
4710
|
+
if (waiting.size === 0 && worker === w) {
|
|
4711
|
+
w.terminate();
|
|
4712
|
+
worker = null;
|
|
4713
|
+
}
|
|
4714
|
+
};
|
|
4715
|
+
w.onerror = (ev) => {
|
|
4716
|
+
ev.preventDefault?.();
|
|
4717
|
+
failed = true;
|
|
4718
|
+
if (worker === w) worker = null;
|
|
4719
|
+
w.terminate();
|
|
4720
|
+
const jobs = [...waiting.values()];
|
|
4721
|
+
waiting.clear();
|
|
4722
|
+
for (const job of jobs) job.done(null);
|
|
4723
|
+
};
|
|
4724
|
+
worker = w;
|
|
4725
|
+
return w;
|
|
4726
|
+
}
|
|
4727
|
+
|
|
4155
4728
|
// src/player.ts
|
|
4156
4729
|
var WHITE = { r: 1, g: 1, b: 1, a: 1 };
|
|
4157
4730
|
function pageIndex(file, spec) {
|
|
@@ -4221,7 +4794,7 @@ var Playback = class {
|
|
|
4221
4794
|
__publicField(this, "order", []);
|
|
4222
4795
|
__publicField(this, "frame", -1);
|
|
4223
4796
|
__publicField(this, "onFrame", null);
|
|
4224
|
-
this.page = options.clean ?
|
|
4797
|
+
this.page = options.clean ? cleanCopy(page) : page;
|
|
4225
4798
|
addFonts(usedFonts(this.page));
|
|
4226
4799
|
this.plan = plan(this.page);
|
|
4227
4800
|
this.clicks = clickTargets(page, options.file);
|
|
@@ -4259,7 +4832,7 @@ var Playback = class {
|
|
|
4259
4832
|
this.sceneNames = (page.timeline?.markers ?? []).map((m) => m.name);
|
|
4260
4833
|
this.sceneLoops = (page.timeline?.markers ?? []).map((m) => m.loop === true);
|
|
4261
4834
|
this.state = { ...page.variables ?? {} };
|
|
4262
|
-
const theme =
|
|
4835
|
+
const theme = pageTheme(page);
|
|
4263
4836
|
this.background = theme?.background ?? WHITE;
|
|
4264
4837
|
this.ink = theme?.ink ?? { r: 0.12, g: 0.12, b: 0.13, a: 1 };
|
|
4265
4838
|
const ns = "http://www.w3.org/2000/svg";
|
|
@@ -4298,7 +4871,7 @@ var Playback = class {
|
|
|
4298
4871
|
}
|
|
4299
4872
|
/** Whether a copy is still playing through after a click. */
|
|
4300
4873
|
replaying(now = performance.now()) {
|
|
4301
|
-
for (const [id,
|
|
4874
|
+
for (const [id, start2] of this.replayStarts) if (now - start2 < (this.replays.get(id) ?? 0) * 1e3) return true;
|
|
4302
4875
|
return false;
|
|
4303
4876
|
}
|
|
4304
4877
|
/** Draws the last moment drawn again, for copies replaying while the page stands still. */
|
|
@@ -4311,17 +4884,24 @@ var Playback = class {
|
|
|
4311
4884
|
const r = this.plan.rects[Math.min(i, this.plan.rects.length - 1)];
|
|
4312
4885
|
const now = typeof performance === "undefined" ? 0 : performance.now();
|
|
4313
4886
|
const times = /* @__PURE__ */ new Map();
|
|
4314
|
-
for (const [id,
|
|
4887
|
+
for (const [id, start2] of this.replayStarts) times.set(id, Math.min((now - start2) / 1e3, this.replays.get(id) ?? 0));
|
|
4315
4888
|
const tc = clampTime(t);
|
|
4316
4889
|
const pose = posed(this.page, tc, this.state, this.sinceChange(now), this.carried);
|
|
4317
|
-
const
|
|
4890
|
+
const made = new Expansions(pose, tc, true);
|
|
4891
|
+
const frame2 = expandingSymbols(pose, tc, true, times, made);
|
|
4318
4892
|
const { parts } = svgParts(frame2, this.background, r);
|
|
4319
4893
|
const owned = (m, parts2) => {
|
|
4320
4894
|
for (const p of parts2) if (this.owner.has(p.key)) m.set(p.key, p.markup);
|
|
4321
4895
|
return m;
|
|
4322
4896
|
};
|
|
4323
|
-
const state = (st) => st.size === 0 ? /* @__PURE__ */ new Map() : owned(/* @__PURE__ */ new Map(), svgParts(
|
|
4324
|
-
|
|
4897
|
+
const state = (st) => st.size === 0 ? /* @__PURE__ */ new Map() : owned(/* @__PURE__ */ new Map(), svgParts(
|
|
4898
|
+
expandingSymbols(pose, tc, true, new Map([...times, ...st]), made),
|
|
4899
|
+
this.background,
|
|
4900
|
+
r,
|
|
4901
|
+
false,
|
|
4902
|
+
(key) => st.has(this.owner.get(key) ?? "")
|
|
4903
|
+
).parts);
|
|
4904
|
+
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);
|
|
4325
4905
|
const over = state(this.overTimes), down = state(this.downTimes), hit = state(this.hitTimes);
|
|
4326
4906
|
const normal = owned(/* @__PURE__ */ new Map(), parts);
|
|
4327
4907
|
const merged = [];
|
|
@@ -4450,6 +5030,14 @@ ${h}</g>
|
|
|
4450
5030
|
return { frame: next, stop: entered >= 0 && !this.sceneLoops[entered] };
|
|
4451
5031
|
}
|
|
4452
5032
|
};
|
|
5033
|
+
var cleanCopies = /* @__PURE__ */ new WeakMap();
|
|
5034
|
+
function cleanCopy(page) {
|
|
5035
|
+
const hit = cleanCopies.get(page);
|
|
5036
|
+
if (hit && unchanged(page, hit.fields)) return hit.page;
|
|
5037
|
+
const clean = withRoughness(page, 0);
|
|
5038
|
+
cleanCopies.set(page, { fields: { ...page }, page: clean });
|
|
5039
|
+
return clean;
|
|
5040
|
+
}
|
|
4453
5041
|
var ElementBase = typeof HTMLElement === "undefined" ? class {
|
|
4454
5042
|
} : HTMLElement;
|
|
4455
5043
|
var DottiePlayer = class extends ElementBase {
|
|
@@ -4472,6 +5060,10 @@ var DottiePlayer = class extends ElementBase {
|
|
|
4472
5060
|
__publicField(this, "raf", 0);
|
|
4473
5061
|
__publicField(this, "replayRaf", 0);
|
|
4474
5062
|
__publicField(this, "loadToken", 0);
|
|
5063
|
+
/** Counts mounts, so a plan that comes back from a worker after a newer one is dropped. */
|
|
5064
|
+
__publicField(this, "mountToken", 0);
|
|
5065
|
+
/** The time a still shows; null while the drawing is animated. */
|
|
5066
|
+
__publicField(this, "stillTime", null);
|
|
4475
5067
|
__publicField(this, "visible", true);
|
|
4476
5068
|
__publicField(this, "observer", null);
|
|
4477
5069
|
this.root = this.attachShadow({ mode: "open" });
|
|
@@ -4548,9 +5140,19 @@ var DottiePlayer = class extends ElementBase {
|
|
|
4548
5140
|
text = inline2.textContent ?? "";
|
|
4549
5141
|
}
|
|
4550
5142
|
if (token !== this.loadToken) return;
|
|
4551
|
-
this.
|
|
5143
|
+
this.showFile(parseDottie(text));
|
|
4552
5144
|
} catch (err) {
|
|
4553
5145
|
if (token !== this.loadToken) return;
|
|
5146
|
+
this.setPlaying(false);
|
|
5147
|
+
cancelAnimationFrame(this.raf);
|
|
5148
|
+
cancelAnimationFrame(this.replayRaf);
|
|
5149
|
+
this.raf = 0;
|
|
5150
|
+
this.replayRaf = 0;
|
|
5151
|
+
this.playback = null;
|
|
5152
|
+
this.file = null;
|
|
5153
|
+
this.mountToken++;
|
|
5154
|
+
this.stillTime = null;
|
|
5155
|
+
this.bar.hidden = true;
|
|
4554
5156
|
this.stage.innerHTML = "";
|
|
4555
5157
|
const box = document.createElement("div");
|
|
4556
5158
|
box.className = "error";
|
|
@@ -4561,15 +5163,35 @@ var DottiePlayer = class extends ElementBase {
|
|
|
4561
5163
|
}
|
|
4562
5164
|
/** Shows an already-parsed file. */
|
|
4563
5165
|
setFile(file) {
|
|
5166
|
+
this.loadToken++;
|
|
5167
|
+
this.showFile(file);
|
|
5168
|
+
}
|
|
5169
|
+
showFile(file) {
|
|
4564
5170
|
this.file = file;
|
|
4565
5171
|
for (const w of file.warnings) console.warn(`dottie-player: ${w}`);
|
|
4566
|
-
this.mount();
|
|
4567
|
-
this.dispatchEvent(new CustomEvent("load", { detail: { file } }));
|
|
5172
|
+
this.mount(() => this.dispatchEvent(new CustomEvent("load", { detail: { file } })));
|
|
4568
5173
|
}
|
|
4569
|
-
|
|
5174
|
+
/** Shows the file's page; then `mounted`. A big animation is planned in a worker first (planner.ts), and what was
|
|
5175
|
+
* showing stays until it is ready; a newer mount, file or failed load in the meantime wins. */
|
|
5176
|
+
mount(mounted) {
|
|
4570
5177
|
const file = this.file;
|
|
4571
5178
|
const page = file.pages[pageIndex(file, this.getAttribute("page"))];
|
|
4572
|
-
const
|
|
5179
|
+
const clean = this.hasAttribute("clean");
|
|
5180
|
+
const token = ++this.mountToken;
|
|
5181
|
+
const shown = clean ? cleanCopy(page) : page;
|
|
5182
|
+
if (!plansInWorker(shown)) {
|
|
5183
|
+
this.mountPage(file, page, clean);
|
|
5184
|
+
mounted?.();
|
|
5185
|
+
return;
|
|
5186
|
+
}
|
|
5187
|
+
void planInWorker(shown).then(() => {
|
|
5188
|
+
if (token !== this.mountToken || this.file !== file) return;
|
|
5189
|
+
this.mountPage(file, page, clean);
|
|
5190
|
+
mounted?.();
|
|
5191
|
+
});
|
|
5192
|
+
}
|
|
5193
|
+
mountPage(file, page, clean) {
|
|
5194
|
+
const p = new Playback(page, this.stage, { clean, file });
|
|
4573
5195
|
p.still = this.hasAttribute("at");
|
|
4574
5196
|
this.playback = p;
|
|
4575
5197
|
this.style.setProperty("--dottie-page", color(p.background));
|
|
@@ -4592,10 +5214,14 @@ var DottiePlayer = class extends ElementBase {
|
|
|
4592
5214
|
this.dispatchEvent(new CustomEvent("frame", { detail: { frame: i, time: p.plan.times[i] } }));
|
|
4593
5215
|
};
|
|
4594
5216
|
if (!animated) {
|
|
4595
|
-
|
|
5217
|
+
const i = at !== null ? nearest(p, Number(at) || 0) : p.posterFrame;
|
|
5218
|
+
this.stillTime = at !== null ? Number(at) || 0 : posterTime(page);
|
|
5219
|
+
p.drawAt(this.stillTime, i);
|
|
5220
|
+
p.frame = i;
|
|
4596
5221
|
this.setPlaying(false);
|
|
4597
5222
|
return;
|
|
4598
5223
|
}
|
|
5224
|
+
this.stillTime = null;
|
|
4599
5225
|
p.show(0);
|
|
4600
5226
|
const reduced = typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
4601
5227
|
if (reduced) {
|
|
@@ -4609,8 +5235,10 @@ var DottiePlayer = class extends ElementBase {
|
|
|
4609
5235
|
get paused() {
|
|
4610
5236
|
return !this.playing;
|
|
4611
5237
|
}
|
|
5238
|
+
/** The moment shown: a still's own time (its `at`, or the poster), else the current frame's. */
|
|
4612
5239
|
get currentTime() {
|
|
4613
5240
|
const p = this.playback;
|
|
5241
|
+
if (p && this.stillTime !== null) return this.stillTime;
|
|
4614
5242
|
return p && p.frame >= 0 ? p.plan.times[p.frame] : 0;
|
|
4615
5243
|
}
|
|
4616
5244
|
set currentTime(t) {
|
|
@@ -4858,7 +5486,7 @@ function inspectFrame(page, t) {
|
|
|
4858
5486
|
const [a, b] = endpoints(e, index);
|
|
4859
5487
|
d.start = pt(a);
|
|
4860
5488
|
d.end = pt(b);
|
|
4861
|
-
d.route = routePoints(
|
|
5489
|
+
d.route = routePoints(route2(e, index)).map(pt);
|
|
4862
5490
|
}
|
|
4863
5491
|
return d;
|
|
4864
5492
|
});
|
|
@@ -4897,7 +5525,7 @@ function render(source, options = {}) {
|
|
|
4897
5525
|
function isFile(v) {
|
|
4898
5526
|
return typeof v === "object" && v !== null && Array.isArray(v.pages) && Array.isArray(v.warnings);
|
|
4899
5527
|
}
|
|
4900
|
-
var VERSION = "0.
|
|
5528
|
+
var VERSION = "0.10.0";
|
|
4901
5529
|
export {
|
|
4902
5530
|
BOIL_DRAWINGS,
|
|
4903
5531
|
BUNDLED_FONTS,
|
|
@@ -4933,6 +5561,8 @@ export {
|
|
|
4933
5561
|
luminance,
|
|
4934
5562
|
orderedElements,
|
|
4935
5563
|
pageIndex,
|
|
5564
|
+
pageTheme,
|
|
5565
|
+
paletteColor,
|
|
4936
5566
|
parseDottie,
|
|
4937
5567
|
plan,
|
|
4938
5568
|
posterTime,
|