@phin-tech/dottie 0.7.0 → 0.9.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 +10 -7
- package/README.md +10 -2
- package/dist/compile.js +103 -11
- package/dist/dottie-compile.mjs +104 -12
- package/dist/dottie-player.js +1316 -100
- package/dist/dottie-player.min.js +55 -52
- package/dist/dottie-sim.min.js +4 -0
- package/dist/dottie.js +1300 -100
- package/dist/types/clicks.d.ts +1 -0
- package/dist/types/compile.d.ts +1 -1
- package/dist/types/connector.d.ts +4 -0
- package/dist/types/flows.d.ts +28 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/measure.d.ts +73 -0
- package/dist/types/model.d.ts +66 -0
- package/dist/types/planner.d.ts +12 -0
- package/dist/types/player.d.ts +21 -1
- package/dist/types/sim-entry.d.ts +1 -0
- package/dist/types/state.d.ts +28 -8
- package/dist/types/svg.d.ts +9 -2
- package/dist/types/symbols.d.ts +19 -3
- package/dist/types/timeline.d.ts +14 -4
- package/package.json +1 -1
package/dist/dottie.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! dottie.js 0.
|
|
1
|
+
/*! dottie.js 0.9.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);
|
|
@@ -8,10 +8,53 @@ var MAX_NAME = 64;
|
|
|
8
8
|
function isName(name) {
|
|
9
9
|
return name.length <= MAX_NAME && /^[A-Za-z_][A-Za-z0-9_]*$/.test(name);
|
|
10
10
|
}
|
|
11
|
+
var METRIC_NAMES = ["throughput", "load", "delay", "backlog", "depth", "inFlight", "transit", "backup"];
|
|
12
|
+
var OPS = [">=", "<=", "==", "!=", ">", "<"];
|
|
13
|
+
function metricTerm(term) {
|
|
14
|
+
const t = trim(term);
|
|
15
|
+
if (!t.includes("(")) {
|
|
16
|
+
const op2 = t.search(/[>=<!]/);
|
|
17
|
+
if (op2 < 0) return null;
|
|
18
|
+
const name2 = trim(t.slice(0, op2));
|
|
19
|
+
if (!METRIC_NAMES.includes(name2)) return null;
|
|
20
|
+
const m = metricTerm(name2 + "() " + t.slice(op2));
|
|
21
|
+
return m ? { ...m, target: "" } : null;
|
|
22
|
+
}
|
|
23
|
+
const open = t.indexOf("("), close = t.indexOf(")");
|
|
24
|
+
if (open < 0 || close < 0 || open >= close) return null;
|
|
25
|
+
const name = trim(t.slice(0, open)), target = trim(t.slice(open + 1, close));
|
|
26
|
+
let rest = trim(t.slice(close + 1));
|
|
27
|
+
if (!METRIC_NAMES.includes(name) || target.includes("(")) return null;
|
|
28
|
+
const op = OPS.find((o) => rest.startsWith(o));
|
|
29
|
+
if (op === void 0) return null;
|
|
30
|
+
rest = trim(rest.slice(op.length));
|
|
31
|
+
if (!/^-?[0-9]+(\.[0-9]+)?$/.test(rest)) return null;
|
|
32
|
+
return { metric: name, target, op, value: Number(rest) };
|
|
33
|
+
}
|
|
34
|
+
function passes(m, v) {
|
|
35
|
+
switch (m.op) {
|
|
36
|
+
case ">":
|
|
37
|
+
return v > m.value;
|
|
38
|
+
case ">=":
|
|
39
|
+
return v >= m.value;
|
|
40
|
+
case "<":
|
|
41
|
+
return v < m.value;
|
|
42
|
+
case "<=":
|
|
43
|
+
return v <= m.value;
|
|
44
|
+
case "==":
|
|
45
|
+
return v === m.value;
|
|
46
|
+
default:
|
|
47
|
+
return v !== m.value;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function mentionsMetric(condition) {
|
|
51
|
+
return condition.split("||").some((any) => any.split("&&").some((all) => metricTerm(all) !== null));
|
|
52
|
+
}
|
|
11
53
|
function conditionNames(condition) {
|
|
12
54
|
const out = [];
|
|
13
55
|
for (const any of condition.split("||")) {
|
|
14
56
|
for (const all of any.split("&&")) {
|
|
57
|
+
if (metricTerm(all) !== null) continue;
|
|
15
58
|
let term = trim(all);
|
|
16
59
|
if (term.startsWith("!")) term = trim(term.slice(1));
|
|
17
60
|
if (!isName(term)) return null;
|
|
@@ -20,9 +63,14 @@ function conditionNames(condition) {
|
|
|
20
63
|
}
|
|
21
64
|
return out;
|
|
22
65
|
}
|
|
23
|
-
function holds(condition, state) {
|
|
66
|
+
function holds(condition, state, metric = null) {
|
|
24
67
|
if (conditionNames(condition) === null) return false;
|
|
25
68
|
return condition.split("||").some((any) => any.split("&&").every((all) => {
|
|
69
|
+
const m = metricTerm(all);
|
|
70
|
+
if (m !== null) {
|
|
71
|
+
const v = metric ? metric(m) : null;
|
|
72
|
+
return v !== null && passes(m, v);
|
|
73
|
+
}
|
|
26
74
|
let term = trim(all);
|
|
27
75
|
let negated = false;
|
|
28
76
|
if (term.startsWith("!")) {
|
|
@@ -44,11 +92,40 @@ function stateHidden(page, choice) {
|
|
|
44
92
|
for (const e of page.elements) if (e.when !== null && !holds(e.when, values)) out.add(e.id);
|
|
45
93
|
return out;
|
|
46
94
|
}
|
|
95
|
+
function activeOverrides(e, values) {
|
|
96
|
+
if (!values || !e.overrides) return [];
|
|
97
|
+
return e.overrides.filter((o) => !mentionsMetric(o.when) && holds(o.when, values));
|
|
98
|
+
}
|
|
99
|
+
function looksOnly(o) {
|
|
100
|
+
return { ...o, reroute: null, offline: null, cost: null, capacity: null, delay: null };
|
|
101
|
+
}
|
|
102
|
+
function applyOverride(o, e) {
|
|
103
|
+
const out = cloneElement(e);
|
|
104
|
+
if (o.stroke) out.style.stroke = { ...o.stroke };
|
|
105
|
+
if (o.fill) out.style.fill = { ...o.fill };
|
|
106
|
+
if (o.textColor) out.style.textColor = { ...o.textColor };
|
|
107
|
+
if (o.strokeWidth !== null) out.style.strokeWidth = Math.max(0, o.strokeWidth);
|
|
108
|
+
if (o.dash !== null) out.style.dash = o.dash;
|
|
109
|
+
if (o.text !== null) out.text = o.text;
|
|
110
|
+
if (o.cost !== null && out.connector) out.connector.cost = Math.min(Math.max(o.cost, 0), 1e6);
|
|
111
|
+
if (o.capacity !== null) out.capacity = Math.max(o.capacity, 0);
|
|
112
|
+
if (o.delay !== null && out.kind !== "connector") out.delay = Math.max(o.delay, 0);
|
|
113
|
+
if (o.opacity !== null) {
|
|
114
|
+
const v = Math.min(Math.max(o.opacity, 0), 1) * (out.style.opacity ?? 1);
|
|
115
|
+
out.style.opacity = v >= 1 ? null : v;
|
|
116
|
+
}
|
|
117
|
+
return out;
|
|
118
|
+
}
|
|
47
119
|
function changesState(c) {
|
|
48
|
-
return !!c && (Object.keys(c.set ?? {}).length > 0 || (c.toggle ?? []).length > 0);
|
|
120
|
+
return !!c && (c.reset === true || Object.keys(c.set ?? {}).length > 0 || (c.toggle ?? []).length > 0);
|
|
49
121
|
}
|
|
50
|
-
function applyClick(c, state) {
|
|
51
|
-
|
|
122
|
+
function applyClick(c, state, initial = {}) {
|
|
123
|
+
let out = { ...state };
|
|
124
|
+
if (c.reset === true) {
|
|
125
|
+
out = {};
|
|
126
|
+
for (const k of Object.keys(state)) out[k] = false;
|
|
127
|
+
Object.assign(out, initial);
|
|
128
|
+
}
|
|
52
129
|
for (const [k, v] of Object.entries(c.set ?? {})) out[k] = v;
|
|
53
130
|
for (const k of c.toggle ?? []) out[k] = !(out[k] ?? false);
|
|
54
131
|
return out;
|
|
@@ -63,7 +140,9 @@ var KINDS = ["rectangle", "ellipse", "diamond", "text", "connector", "freehand",
|
|
|
63
140
|
var EASES = ["linear", "easeIn", "easeOut", "easeInOut", "hold", "spring", "bounce", "elastic"];
|
|
64
141
|
var KNOWN_FIELDS = {
|
|
65
142
|
file: ["version", "pages", "images"],
|
|
66
|
-
page: ["id", "name", "layers", "elements", "theme", "guidesX", "guidesY", "guidesLocked", "timeline", "symbols", "variables"],
|
|
143
|
+
page: ["id", "name", "layers", "elements", "theme", "guidesX", "guidesY", "guidesLocked", "timeline", "symbols", "variables", "flows"],
|
|
144
|
+
flow: ["id", "from", "to", "every", "speed", "color", "size", "when", "spread", "volume", "overrides"],
|
|
145
|
+
flowOverride: ["when", "volume"],
|
|
67
146
|
symbol: ["id", "name", "width", "height", "layers", "elements", "timeline", "button", "params"],
|
|
68
147
|
layer: ["id", "name", "visible", "locked"],
|
|
69
148
|
rgba: ["r", "g", "b", "a"],
|
|
@@ -92,6 +171,7 @@ var KNOWN_FIELDS = {
|
|
|
92
171
|
"click",
|
|
93
172
|
"hover",
|
|
94
173
|
"when",
|
|
174
|
+
"overrides",
|
|
95
175
|
"symbol",
|
|
96
176
|
"playback",
|
|
97
177
|
"offset",
|
|
@@ -100,8 +180,13 @@ var KNOWN_FIELDS = {
|
|
|
100
180
|
"reveal",
|
|
101
181
|
"ports",
|
|
102
182
|
"labelRect",
|
|
103
|
-
"extension"
|
|
183
|
+
"extension",
|
|
184
|
+
"capacity",
|
|
185
|
+
"delay",
|
|
186
|
+
"queue",
|
|
187
|
+
"gauge"
|
|
104
188
|
],
|
|
189
|
+
gauge: ["metric", "of", "max", "warn", "alarm", "style", "label"],
|
|
105
190
|
strokePoint: ["x", "y"],
|
|
106
191
|
subpath: ["closed", "coords"],
|
|
107
192
|
inlineImage: ["data", "format"],
|
|
@@ -109,7 +194,7 @@ var KNOWN_FIELDS = {
|
|
|
109
194
|
extensionRef: ["id", "shape", "version", "params"],
|
|
110
195
|
normalizedPoint: ["x", "y"],
|
|
111
196
|
normalizedRect: ["x", "y", "width", "height"],
|
|
112
|
-
connector: ["start", "end", "routing", "startHead", "endHead", "waypoints", "hops"],
|
|
197
|
+
connector: ["start", "end", "routing", "startHead", "endHead", "waypoints", "hops", "cost"],
|
|
113
198
|
waypoint: ["x", "y"],
|
|
114
199
|
timeline: ["fps", "duration", "boil", "settle", "poster", "tracks", "camera", "markers"],
|
|
115
200
|
track: ["element", "keys", "boil", "follow", "repeats", "march"],
|
|
@@ -118,8 +203,9 @@ var KNOWN_FIELDS = {
|
|
|
118
203
|
repeat: ["start", "end", "count"],
|
|
119
204
|
cameraKey: ["t", "x", "y", "width", "height", "ease"],
|
|
120
205
|
marker: ["t", "name", "loop"],
|
|
121
|
-
click: ["scene", "url", "set", "toggle"],
|
|
122
|
-
hover: ["stroke", "fill", "opacity"]
|
|
206
|
+
click: ["scene", "url", "set", "toggle", "reset"],
|
|
207
|
+
hover: ["stroke", "fill", "opacity"],
|
|
208
|
+
override: ["when", "stroke", "fill", "textColor", "opacity", "strokeWidth", "dash", "text", "reroute", "offline", "cost", "capacity", "delay"]
|
|
123
209
|
};
|
|
124
210
|
var MAX_DURATION = 3600;
|
|
125
211
|
var MAX_MARCH = 1e4;
|
|
@@ -135,6 +221,8 @@ var num = (v) => typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
|
135
221
|
var str = (v) => typeof v === "string" ? v : void 0;
|
|
136
222
|
var strings = (v) => isObj(v) && Object.values(v).every((x) => typeof x === "string") ? { ...v } : null;
|
|
137
223
|
var bool = (v) => typeof v === "boolean" ? v : void 0;
|
|
224
|
+
var NO_ID = "00000000-0000-0000-0000-000000000000";
|
|
225
|
+
var UUID = /^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$/;
|
|
138
226
|
var uuid = (v) => typeof v === "string" ? v.toUpperCase() : void 0;
|
|
139
227
|
function clampTime(t) {
|
|
140
228
|
if (!Number.isFinite(t)) return t === Infinity ? MAX_DURATION : 0;
|
|
@@ -226,7 +314,8 @@ var Reader = class {
|
|
|
226
314
|
this.fields(w, "waypoint");
|
|
227
315
|
return { x: num(w.x) ?? 0, y: num(w.y) ?? 0 };
|
|
228
316
|
}) : null,
|
|
229
|
-
hops: typeof v.hops === "boolean" ? v.hops : null
|
|
317
|
+
hops: typeof v.hops === "boolean" ? v.hops : null,
|
|
318
|
+
cost: typeof v.cost === "number" && Number.isFinite(v.cost) ? v.cost : null
|
|
230
319
|
};
|
|
231
320
|
}
|
|
232
321
|
image(v, table) {
|
|
@@ -278,6 +367,7 @@ var Reader = class {
|
|
|
278
367
|
click: this.click(v.click),
|
|
279
368
|
hover: this.hover(v.hover),
|
|
280
369
|
when: str(v.when) ?? null,
|
|
370
|
+
overrides: this.overrides(v.overrides),
|
|
281
371
|
symbol: uuid(v.symbol) ?? null,
|
|
282
372
|
playback: this.playback(v.playback),
|
|
283
373
|
offset: num(v.offset) ?? null,
|
|
@@ -289,7 +379,26 @@ var Reader = class {
|
|
|
289
379
|
return { x: num(o.x) ?? 0, y: num(o.y) ?? 0 };
|
|
290
380
|
}) : null,
|
|
291
381
|
labelRect: this.labelRect(v.labelRect),
|
|
292
|
-
extension: this.extension(v.extension)
|
|
382
|
+
extension: this.extension(v.extension),
|
|
383
|
+
capacity: num(v.capacity) ?? null,
|
|
384
|
+
delay: num(v.delay) ?? null,
|
|
385
|
+
queue: v.queue === true ? true : null,
|
|
386
|
+
gauge: this.gauge(v.gauge)
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
gauge(v) {
|
|
390
|
+
if (!isObj(v)) return null;
|
|
391
|
+
this.fields(v, "gauge");
|
|
392
|
+
const finite = (x) => typeof x === "number" && Number.isFinite(x) ? x : null;
|
|
393
|
+
const style2 = str(v.style);
|
|
394
|
+
return {
|
|
395
|
+
metric: str(v.metric) ?? "",
|
|
396
|
+
of: typeof v.of === "string" && UUID.test(v.of) ? v.of.toUpperCase() : NO_ID,
|
|
397
|
+
max: finite(v.max),
|
|
398
|
+
warn: finite(v.warn),
|
|
399
|
+
alarm: finite(v.alarm),
|
|
400
|
+
style: style2 === "number" || style2 === "bar" || style2 === "dial" ? style2 : null,
|
|
401
|
+
label: str(v.label) ?? null
|
|
293
402
|
};
|
|
294
403
|
}
|
|
295
404
|
labelRect(v) {
|
|
@@ -333,7 +442,7 @@ var Reader = class {
|
|
|
333
442
|
}
|
|
334
443
|
const raw = typeof v.toggle === "string" ? [v.toggle] : Array.isArray(v.toggle) && v.toggle.every((x) => typeof x === "string") ? v.toggle : null;
|
|
335
444
|
const toggle = raw ? raw.filter(isName) : null;
|
|
336
|
-
return { scene: str(v.scene) ?? null, url: safe, set, toggle: toggle && toggle.length > 0 ? toggle : null };
|
|
445
|
+
return { scene: str(v.scene) ?? null, url: safe, set, toggle: toggle && toggle.length > 0 ? toggle : null, reset: v.reset === true ? true : null };
|
|
337
446
|
}
|
|
338
447
|
playback(v) {
|
|
339
448
|
const s = str(v);
|
|
@@ -369,6 +478,35 @@ var Reader = class {
|
|
|
369
478
|
params: strings(v.params)
|
|
370
479
|
};
|
|
371
480
|
}
|
|
481
|
+
/** As DottieKit reads them: a reroute whose targets aren't all ids is dropped, as are keys that aren't ids. */
|
|
482
|
+
overrides(v) {
|
|
483
|
+
if (!Array.isArray(v)) return null;
|
|
484
|
+
return v.filter(isObj).map((o) => {
|
|
485
|
+
this.fields(o, "override");
|
|
486
|
+
const dash = str(o.dash);
|
|
487
|
+
let reroute = null;
|
|
488
|
+
if (isObj(o.reroute) && Object.values(o.reroute).every((x) => typeof x === "string" && UUID.test(x))) {
|
|
489
|
+
reroute = {};
|
|
490
|
+
for (const [k, x] of Object.entries(o.reroute)) if (UUID.test(k)) reroute[k.toUpperCase()] = x.toUpperCase();
|
|
491
|
+
if (Object.keys(reroute).length === 0) reroute = null;
|
|
492
|
+
}
|
|
493
|
+
return {
|
|
494
|
+
when: str(o.when) ?? "",
|
|
495
|
+
stroke: rgba(o.stroke) ?? null,
|
|
496
|
+
fill: rgba(o.fill) ?? null,
|
|
497
|
+
textColor: rgba(o.textColor) ?? null,
|
|
498
|
+
opacity: num(o.opacity) ?? null,
|
|
499
|
+
strokeWidth: num(o.strokeWidth) ?? null,
|
|
500
|
+
dash: dash !== void 0 && ["solid", "dashed", "dotted"].includes(dash) ? dash : null,
|
|
501
|
+
text: str(o.text) ?? null,
|
|
502
|
+
reroute,
|
|
503
|
+
offline: o.offline === true ? true : null,
|
|
504
|
+
cost: typeof o.cost === "number" && Number.isFinite(o.cost) ? o.cost : null,
|
|
505
|
+
capacity: typeof o.capacity === "number" && Number.isFinite(o.capacity) ? o.capacity : null,
|
|
506
|
+
delay: typeof o.delay === "number" && Number.isFinite(o.delay) ? o.delay : null
|
|
507
|
+
};
|
|
508
|
+
});
|
|
509
|
+
}
|
|
372
510
|
hover(v) {
|
|
373
511
|
if (!isObj(v)) return null;
|
|
374
512
|
this.fields(v, "hover");
|
|
@@ -408,15 +546,15 @@ var Reader = class {
|
|
|
408
546
|
const keys = (Array.isArray(v.keys) ? v.keys : []).map((k) => this.keyframe(k)).filter((k) => k !== null && k.t >= 0 && k.t <= MAX_DURATION);
|
|
409
547
|
stableSortBy(keys, (k) => k.t);
|
|
410
548
|
const follow = Array.isArray(v.follow) ? v.follow.filter(isObj).flatMap((f) => {
|
|
411
|
-
const path = uuid(f.path),
|
|
412
|
-
if (!path ||
|
|
413
|
-
return [{ path, start, end, ease: this.ease(f.ease), reverse: bool(f.reverse) ?? null, orient: bool(f.orient) ?? null, laps: num(f.laps) ?? null }];
|
|
549
|
+
const path = uuid(f.path), start2 = num(f.start), end = num(f.end);
|
|
550
|
+
if (!path || start2 === void 0 || end === void 0 || start2 < 0 || end > MAX_DURATION) return [];
|
|
551
|
+
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 }];
|
|
414
552
|
}) : null;
|
|
415
553
|
const repeats = Array.isArray(v.repeats) ? v.repeats.filter(isObj).flatMap((r) => {
|
|
416
|
-
const
|
|
417
|
-
if (
|
|
554
|
+
const start2 = num(r.start), end = num(r.end);
|
|
555
|
+
if (start2 === void 0 || end === void 0) return [];
|
|
418
556
|
const count = num(r.count);
|
|
419
|
-
const rep = { start, end, count: count === void 0 ? null : Math.trunc(count) };
|
|
557
|
+
const rep = { start: start2, end, count: count === void 0 ? null : Math.trunc(count) };
|
|
420
558
|
return repeatIsValid(rep) ? [rep] : [];
|
|
421
559
|
}) : null;
|
|
422
560
|
return { element, keys, boil: bool(v.boil) ?? null, follow, repeats, march: clampMarch(num(v.march)) };
|
|
@@ -468,7 +606,33 @@ var Reader = class {
|
|
|
468
606
|
theme: str(v.theme) ?? null,
|
|
469
607
|
timeline: this.timeline(v.timeline),
|
|
470
608
|
symbols: Array.isArray(v.symbols) ? v.symbols.map((x) => this.symbol(x, images)).filter((x) => x !== null) : null,
|
|
471
|
-
variables: this.variables(v.variables)
|
|
609
|
+
variables: this.variables(v.variables),
|
|
610
|
+
flows: Array.isArray(v.flows) ? v.flows.filter(isObj).map((f) => this.flow(f)) : null
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
/** As DottieKit reads one: a missing or bad id, source or destination sends nothing; bad destinations are dropped. */
|
|
614
|
+
flow(f) {
|
|
615
|
+
this.fields(f, "flow");
|
|
616
|
+
const id = (x) => typeof x === "string" && UUID.test(x) ? x.toUpperCase() : NO_ID;
|
|
617
|
+
const finite = (x) => {
|
|
618
|
+
const n = num(x);
|
|
619
|
+
return n !== void 0 && Number.isFinite(n) ? n : null;
|
|
620
|
+
};
|
|
621
|
+
return {
|
|
622
|
+
id: id(f.id),
|
|
623
|
+
from: id(f.from),
|
|
624
|
+
to: Array.isArray(f.to) && f.to.every((x) => typeof x === "string") ? f.to.filter((x) => UUID.test(x)).map((x) => x.toUpperCase()) : [],
|
|
625
|
+
every: finite(f.every),
|
|
626
|
+
speed: finite(f.speed),
|
|
627
|
+
size: finite(f.size),
|
|
628
|
+
color: isObj(f.color) && ["r", "g", "b", "a"].every((k) => typeof f.color[k] === "number") ? rgba(f.color) ?? null : null,
|
|
629
|
+
when: str(f.when) ?? null,
|
|
630
|
+
spread: f.spread === true ? true : null,
|
|
631
|
+
volume: finite(f.volume),
|
|
632
|
+
overrides: Array.isArray(f.overrides) && f.overrides.length > 0 ? f.overrides.filter(isObj).map((o) => {
|
|
633
|
+
this.fields(o, "flowOverride");
|
|
634
|
+
return { when: str(o.when) ?? "", volume: finite(o.volume) };
|
|
635
|
+
}) : null
|
|
472
636
|
};
|
|
473
637
|
}
|
|
474
638
|
};
|
|
@@ -530,7 +694,8 @@ function cloneElement(e) {
|
|
|
530
694
|
start: { ...e.connector.start },
|
|
531
695
|
end: { ...e.connector.end },
|
|
532
696
|
waypoints: e.connector.waypoints?.map((w) => ({ ...w })) ?? null
|
|
533
|
-
} : null
|
|
697
|
+
} : null,
|
|
698
|
+
gauge: e.gauge ? { ...e.gauge } : e.gauge
|
|
534
699
|
};
|
|
535
700
|
}
|
|
536
701
|
function orderedElements(page) {
|
|
@@ -614,6 +779,21 @@ function pieces(para) {
|
|
|
614
779
|
if (cur || out.length === 0) out.push(cur);
|
|
615
780
|
return out;
|
|
616
781
|
}
|
|
782
|
+
function prefixWidths(chars, fontSize, face, font) {
|
|
783
|
+
const fam = familyMetrics(font);
|
|
784
|
+
const f = fam.faces[face] ?? fam.faces.regular;
|
|
785
|
+
const fallback = f.w["n"];
|
|
786
|
+
const out = [0];
|
|
787
|
+
let w = 0;
|
|
788
|
+
let prev = "";
|
|
789
|
+
for (const ch of chars) {
|
|
790
|
+
w += f.w[ch] ?? fallback;
|
|
791
|
+
if (prev) w += f.k[prev + ch] ?? 0;
|
|
792
|
+
prev = ch;
|
|
793
|
+
out.push(w * fontSize / 1e3);
|
|
794
|
+
}
|
|
795
|
+
return out;
|
|
796
|
+
}
|
|
617
797
|
function wrap(s, fontSize, maxWidth, face = "regular", font) {
|
|
618
798
|
const width = (t) => measure(t, fontSize, face, font);
|
|
619
799
|
const out = [];
|
|
@@ -629,8 +809,9 @@ function wrap(s, fontSize, maxWidth, face = "regular", font) {
|
|
|
629
809
|
}
|
|
630
810
|
while (width(line2.trimEnd()) > maxWidth && [...line2.trimEnd()].length > 1) {
|
|
631
811
|
const chars = [...line2];
|
|
812
|
+
const prefix = prefixWidths(chars, fontSize, face, font);
|
|
632
813
|
let n = chars.length - 1;
|
|
633
|
-
while (n > 1 &&
|
|
814
|
+
while (n > 1 && prefix[n] > maxWidth) n--;
|
|
634
815
|
out.push(chars.slice(0, n).join(""));
|
|
635
816
|
line2 = chars.slice(n).join("");
|
|
636
817
|
}
|
|
@@ -785,13 +966,13 @@ var Path = class _Path {
|
|
|
785
966
|
contours() {
|
|
786
967
|
const out = [];
|
|
787
968
|
let current = [];
|
|
788
|
-
let
|
|
969
|
+
let start2 = { x: 0, y: 0 };
|
|
789
970
|
for (const s of this.segments) {
|
|
790
|
-
const last = current.length ? current[current.length - 1] :
|
|
971
|
+
const last = current.length ? current[current.length - 1] : start2;
|
|
791
972
|
switch (s.op) {
|
|
792
973
|
case "M":
|
|
793
974
|
if (current.length > 1) out.push(current);
|
|
794
|
-
|
|
975
|
+
start2 = s.p;
|
|
795
976
|
current = [s.p];
|
|
796
977
|
break;
|
|
797
978
|
case "L":
|
|
@@ -813,9 +994,9 @@ var Path = class _Path {
|
|
|
813
994
|
}
|
|
814
995
|
break;
|
|
815
996
|
case "Z":
|
|
816
|
-
current.push(
|
|
997
|
+
current.push(start2);
|
|
817
998
|
if (current.length > 1) out.push(current);
|
|
818
|
-
current = [
|
|
999
|
+
current = [start2];
|
|
819
1000
|
break;
|
|
820
1001
|
}
|
|
821
1002
|
}
|
|
@@ -1207,9 +1388,17 @@ function midpoint(e, index) {
|
|
|
1207
1388
|
}
|
|
1208
1389
|
return pts[0];
|
|
1209
1390
|
}
|
|
1391
|
+
var indexes = /* @__PURE__ */ new WeakMap();
|
|
1392
|
+
function isIndexOf(page, index) {
|
|
1393
|
+
return indexes.get(page.elements)?.index === index;
|
|
1394
|
+
}
|
|
1210
1395
|
function indexOf(page) {
|
|
1396
|
+
const els = page.elements;
|
|
1397
|
+
const hit = indexes.get(els);
|
|
1398
|
+
if (hit && hit.members.length === els.length && hit.members.every((e, i) => e === els[i])) return hit.index;
|
|
1211
1399
|
const m = /* @__PURE__ */ new Map();
|
|
1212
|
-
for (const e of
|
|
1400
|
+
for (const e of els) if (!m.has(e.id)) m.set(e.id, e);
|
|
1401
|
+
indexes.set(els, { members: [...els], index: m });
|
|
1213
1402
|
return m;
|
|
1214
1403
|
}
|
|
1215
1404
|
|
|
@@ -1281,8 +1470,712 @@ function mulConst(step) {
|
|
|
1281
1470
|
return [p[0], p[1]];
|
|
1282
1471
|
}
|
|
1283
1472
|
|
|
1473
|
+
// src/flows.ts
|
|
1474
|
+
var DEFAULT_EVERY = 0.5;
|
|
1475
|
+
var DEFAULT_SPEED = 160;
|
|
1476
|
+
var DEFAULT_SIZE = 12;
|
|
1477
|
+
var DEFAULT_COLOR = { r: 0.95, g: 0.36, b: 0.33, a: 1 };
|
|
1478
|
+
var clamp = (v, fallback, lo, hi) => v === null || !Number.isFinite(v) ? fallback : Math.min(Math.max(v, lo), hi);
|
|
1479
|
+
function packetID(flow, k) {
|
|
1480
|
+
const n = (k + 2 ** 40) % 2 ** 48;
|
|
1481
|
+
return flow.slice(0, 24) + (n < 0 ? n + 2 ** 48 : n).toString(16).toUpperCase().padStart(12, "0");
|
|
1482
|
+
}
|
|
1483
|
+
var routeCost = (cost) => cost === null || !Number.isFinite(cost) ? 1 : Math.min(Math.max(cost, 0), 1e6);
|
|
1484
|
+
var graphs = /* @__PURE__ */ new WeakMap();
|
|
1485
|
+
var networks = /* @__PURE__ */ new Map();
|
|
1486
|
+
function graphOf(frame2) {
|
|
1487
|
+
const els = frame2.elements;
|
|
1488
|
+
const hit = graphs.get(els);
|
|
1489
|
+
if (hit && hit.members.length === els.length && hit.members.every((e, i) => e === els[i])) return hit.graph;
|
|
1490
|
+
const shapes2 = new Set(els.filter((e) => e.kind !== "connector").map((e) => e.id));
|
|
1491
|
+
const lines = [];
|
|
1492
|
+
for (const e of els) {
|
|
1493
|
+
const c = e.connector;
|
|
1494
|
+
if (c) lines.push([e.id, c.start.elementID, c.end.elementID, String(routeCost(c.cost)), c.startHead !== "none" ? "both" : "one"]);
|
|
1495
|
+
}
|
|
1496
|
+
const key = JSON.stringify([[...shapes2], lines]);
|
|
1497
|
+
let graph = networks.get(key);
|
|
1498
|
+
if (!graph) {
|
|
1499
|
+
const next = /* @__PURE__ */ new Map();
|
|
1500
|
+
const add2 = (a, edge) => {
|
|
1501
|
+
const list = next.get(a);
|
|
1502
|
+
if (list) list.push(edge);
|
|
1503
|
+
else next.set(a, [edge]);
|
|
1504
|
+
};
|
|
1505
|
+
for (const e of els) {
|
|
1506
|
+
const c = e.connector;
|
|
1507
|
+
const a = c?.start.elementID ?? null, b = c?.end.elementID ?? null;
|
|
1508
|
+
if (!c || a === null || b === null || a === b || !shapes2.has(a) || !shapes2.has(b)) continue;
|
|
1509
|
+
const cost = routeCost(c.cost);
|
|
1510
|
+
add2(a, { to: b, via: e.id, back: false, cost });
|
|
1511
|
+
if (c.startHead !== "none") add2(b, { to: a, via: e.id, back: true, cost });
|
|
1512
|
+
}
|
|
1513
|
+
graph = { shapes: shapes2, next, trees: /* @__PURE__ */ new Map() };
|
|
1514
|
+
if (networks.size >= 16) networks.clear();
|
|
1515
|
+
networks.set(key, graph);
|
|
1516
|
+
}
|
|
1517
|
+
graphs.set(els, { members: [...els], graph });
|
|
1518
|
+
return graph;
|
|
1519
|
+
}
|
|
1520
|
+
var Frontier = class {
|
|
1521
|
+
constructor() {
|
|
1522
|
+
__publicField(this, "heap", []);
|
|
1523
|
+
}
|
|
1524
|
+
get size() {
|
|
1525
|
+
return this.heap.length;
|
|
1526
|
+
}
|
|
1527
|
+
less(i, j) {
|
|
1528
|
+
const a = this.heap[i], b = this.heap[j];
|
|
1529
|
+
return a.dist < b.dist || a.dist === b.dist && a.order < b.order;
|
|
1530
|
+
}
|
|
1531
|
+
swap(i, j) {
|
|
1532
|
+
[this.heap[i], this.heap[j]] = [this.heap[j], this.heap[i]];
|
|
1533
|
+
}
|
|
1534
|
+
push(id, dist, order) {
|
|
1535
|
+
this.heap.push({ id, dist, order });
|
|
1536
|
+
for (let i = this.heap.length - 1; i > 0; ) {
|
|
1537
|
+
const up = i - 1 >> 1;
|
|
1538
|
+
if (!this.less(i, up)) break;
|
|
1539
|
+
this.swap(i, up);
|
|
1540
|
+
i = up;
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
pop() {
|
|
1544
|
+
const top = this.heap[0], last = this.heap.pop();
|
|
1545
|
+
if (this.heap.length > 0) {
|
|
1546
|
+
this.heap[0] = last;
|
|
1547
|
+
for (let i = 0; ; ) {
|
|
1548
|
+
const l = 2 * i + 1, r = l + 1;
|
|
1549
|
+
let m = i;
|
|
1550
|
+
if (l < this.heap.length && this.less(l, m)) m = l;
|
|
1551
|
+
if (r < this.heap.length && this.less(r, m)) m = r;
|
|
1552
|
+
if (m === i) break;
|
|
1553
|
+
this.swap(i, m);
|
|
1554
|
+
i = m;
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
return top;
|
|
1558
|
+
}
|
|
1559
|
+
};
|
|
1560
|
+
function flowTree(from, frame2, offline = /* @__PURE__ */ new Set(), until) {
|
|
1561
|
+
const graph = graphOf(frame2);
|
|
1562
|
+
const key = JSON.stringify([from, until ?? null, [...offline].sort()]);
|
|
1563
|
+
if (graph.trees.has(key)) return graph.trees.get(key);
|
|
1564
|
+
const tree = grow(graph, from, offline, until);
|
|
1565
|
+
if (graph.trees.size >= 4096) graph.trees.clear();
|
|
1566
|
+
graph.trees.set(key, tree);
|
|
1567
|
+
return tree;
|
|
1568
|
+
}
|
|
1569
|
+
function grow({ shapes: shapes2, next }, from, offline, until) {
|
|
1570
|
+
if (!shapes2.has(from) || offline.has(from)) return null;
|
|
1571
|
+
const dist = /* @__PURE__ */ new Map([[from, 0]]), order = /* @__PURE__ */ new Map([[from, 0]]);
|
|
1572
|
+
let counter = 1;
|
|
1573
|
+
const tree = new FlowTree(from);
|
|
1574
|
+
const open = new Frontier();
|
|
1575
|
+
open.push(from, 0, 0);
|
|
1576
|
+
while (open.size > 0) {
|
|
1577
|
+
const { id: n, order: o } = open.pop();
|
|
1578
|
+
if (tree.settled.has(n) || order.get(n) !== o) continue;
|
|
1579
|
+
tree.settled.add(n);
|
|
1580
|
+
if (n === until && n !== from) break;
|
|
1581
|
+
for (const edge of next.get(n) ?? []) {
|
|
1582
|
+
if (offline.has(edge.via) || offline.has(edge.to)) continue;
|
|
1583
|
+
if (tree.settled.has(edge.to)) continue;
|
|
1584
|
+
const d = dist.get(n) + edge.cost;
|
|
1585
|
+
const old = dist.get(edge.to);
|
|
1586
|
+
if (old !== void 0 && old <= d) continue;
|
|
1587
|
+
dist.set(edge.to, d);
|
|
1588
|
+
order.set(edge.to, counter);
|
|
1589
|
+
open.push(edge.to, d, counter++);
|
|
1590
|
+
tree.came.set(edge.to, { from: n, via: edge.via, back: edge.back });
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
return tree;
|
|
1594
|
+
}
|
|
1595
|
+
var FlowTree = class {
|
|
1596
|
+
constructor(from) {
|
|
1597
|
+
__publicField(this, "settled", /* @__PURE__ */ new Set());
|
|
1598
|
+
__publicField(this, "came", /* @__PURE__ */ new Map());
|
|
1599
|
+
__publicField(this, "from");
|
|
1600
|
+
this.from = from;
|
|
1601
|
+
}
|
|
1602
|
+
/** The first of `to` that can be reached; with `spread`, when the first can't be, every other one that can. */
|
|
1603
|
+
goals(to, spread) {
|
|
1604
|
+
const reachable = to.filter((x) => x !== this.from && this.settled.has(x));
|
|
1605
|
+
if (reachable.length === 0) return [];
|
|
1606
|
+
return spread && reachable[0] !== to[0] ? reachable : [reachable[0]];
|
|
1607
|
+
}
|
|
1608
|
+
/** The connectors to `goal`, as [connector, backwards] hops. */
|
|
1609
|
+
hops(goal) {
|
|
1610
|
+
const hops = [];
|
|
1611
|
+
for (let at = goal, step = this.came.get(at); step; at = step.from, step = this.came.get(at)) hops.push([step.via, step.back]);
|
|
1612
|
+
return hops.reverse();
|
|
1613
|
+
}
|
|
1614
|
+
};
|
|
1615
|
+
|
|
1616
|
+
// src/measure.ts
|
|
1617
|
+
var emptyMetrics = () => ({ elements: /* @__PURE__ */ new Map(), flows: /* @__PURE__ */ new Map(), carry: {} });
|
|
1618
|
+
function metricValue(m, metric, id) {
|
|
1619
|
+
const f = m.flows.get(id);
|
|
1620
|
+
if (f) {
|
|
1621
|
+
switch (metric) {
|
|
1622
|
+
case "throughput":
|
|
1623
|
+
return f.placed;
|
|
1624
|
+
case "backlog":
|
|
1625
|
+
return f.backlog;
|
|
1626
|
+
case "backup":
|
|
1627
|
+
return f.backup;
|
|
1628
|
+
case "transit":
|
|
1629
|
+
return f.transit;
|
|
1630
|
+
default:
|
|
1631
|
+
return null;
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
const e = m.elements.get(id);
|
|
1635
|
+
if (!e) return null;
|
|
1636
|
+
switch (metric) {
|
|
1637
|
+
case "throughput":
|
|
1638
|
+
return e.throughput;
|
|
1639
|
+
case "load":
|
|
1640
|
+
return e.load;
|
|
1641
|
+
case "delay":
|
|
1642
|
+
return e.delay;
|
|
1643
|
+
case "backlog":
|
|
1644
|
+
return e.backlog;
|
|
1645
|
+
case "inFlight":
|
|
1646
|
+
return e.inFlight;
|
|
1647
|
+
case "depth":
|
|
1648
|
+
return e.depth;
|
|
1649
|
+
default:
|
|
1650
|
+
return null;
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
var TINY = 1e-9;
|
|
1654
|
+
var amount = (f) => clamp(f.volume, 1, 0, 1e12);
|
|
1655
|
+
function amountIn(f, values) {
|
|
1656
|
+
if (values === null || !f.overrides) return amount(f);
|
|
1657
|
+
let v = null;
|
|
1658
|
+
for (const o of f.overrides) if (o.volume !== null && !mentionsMetric(o.when) && holds(o.when, values)) v = o.volume;
|
|
1659
|
+
return v === null ? amount(f) : Math.min(Math.max(Number.isFinite(v) ? v : 1, 0), 1e12);
|
|
1660
|
+
}
|
|
1661
|
+
function delayOf(e) {
|
|
1662
|
+
const d = e.delay ?? null;
|
|
1663
|
+
if (e.kind === "connector" || d === null || !Number.isFinite(d)) return 0;
|
|
1664
|
+
return Math.min(Math.max(d, 0), 3600);
|
|
1665
|
+
}
|
|
1666
|
+
var queueKey = (shape, flow) => shape + "|" + flow;
|
|
1667
|
+
function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set(), sinceChange = null, carried = {}) {
|
|
1668
|
+
const since = Math.max(0, Math.min(sinceChange ?? t, 1e6));
|
|
1669
|
+
const byID = indexOf(frame2);
|
|
1670
|
+
const traced = /* @__PURE__ */ new Map();
|
|
1671
|
+
const traceOf = (c) => {
|
|
1672
|
+
let pts = traced.get(c.id);
|
|
1673
|
+
if (!pts) {
|
|
1674
|
+
pts = tracing(c, frame2, byID).points;
|
|
1675
|
+
traced.set(c.id, pts);
|
|
1676
|
+
}
|
|
1677
|
+
return pts;
|
|
1678
|
+
};
|
|
1679
|
+
const metrics2 = emptyMetrics();
|
|
1680
|
+
for (const e of frame2.elements) {
|
|
1681
|
+
if (e.kind === "connector" && !e.connector) continue;
|
|
1682
|
+
metrics2.elements.set(e.id, { throughput: 0, load: null, delay: e.kind !== "connector" ? delayOf(e) : 0, backlog: 0, inFlight: 0, depth: 0 });
|
|
1683
|
+
}
|
|
1684
|
+
const list = flows ?? [];
|
|
1685
|
+
const room = /* @__PURE__ */ new Map();
|
|
1686
|
+
for (const e of frame2.elements) {
|
|
1687
|
+
const c = e.capacity ?? null;
|
|
1688
|
+
if (c !== null && Number.isFinite(c)) room.set(e.id, Math.max(c, 0));
|
|
1689
|
+
}
|
|
1690
|
+
const full = () => new Set([...room].filter(([, v]) => v <= TINY).map(([id]) => id));
|
|
1691
|
+
const route2 = (tree, goal) => {
|
|
1692
|
+
const hops = [], shapes2 = [];
|
|
1693
|
+
let at = goal;
|
|
1694
|
+
for (let step = tree.came.get(at); step; step = tree.came.get(at)) {
|
|
1695
|
+
hops.push([step.via, step.back]);
|
|
1696
|
+
shapes2.push(at);
|
|
1697
|
+
at = step.from;
|
|
1698
|
+
}
|
|
1699
|
+
shapes2.push(at);
|
|
1700
|
+
return { hops: hops.reverse(), shapes: shapes2.reverse() };
|
|
1701
|
+
};
|
|
1702
|
+
const way = (hops, shapes2) => {
|
|
1703
|
+
const points = [], inside = [], stretches = [];
|
|
1704
|
+
let length = 0;
|
|
1705
|
+
hops.forEach(([id, back], k) => {
|
|
1706
|
+
const c = byID.get(id);
|
|
1707
|
+
if (!c) return;
|
|
1708
|
+
const line2 = back ? [...traceOf(c)].reverse() : traceOf(c);
|
|
1709
|
+
if (line2.length === 0) return;
|
|
1710
|
+
const last = points[points.length - 1];
|
|
1711
|
+
if (last) {
|
|
1712
|
+
const gap = Math.hypot(line2[0].x - last.x, line2[0].y - last.y);
|
|
1713
|
+
const shape = byID.get(shapes2[k]);
|
|
1714
|
+
inside.push([length, length + gap, shape ? delayOf(shape) : 0]);
|
|
1715
|
+
length += gap;
|
|
1716
|
+
}
|
|
1717
|
+
const start2 = length;
|
|
1718
|
+
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);
|
|
1719
|
+
stretches.push([id, start2, length]);
|
|
1720
|
+
points.push(...line2);
|
|
1721
|
+
});
|
|
1722
|
+
return length > 0 ? { line: new Polyline(points), length, inside, stretches } : null;
|
|
1723
|
+
};
|
|
1724
|
+
const along = (w, elapsedAt, speed) => {
|
|
1725
|
+
let elapsed = elapsedAt, d = 0;
|
|
1726
|
+
for (const [a, b, delay] of w.inside) {
|
|
1727
|
+
const reach = (a - d) / speed;
|
|
1728
|
+
if (elapsed <= reach) break;
|
|
1729
|
+
elapsed -= reach;
|
|
1730
|
+
d = a;
|
|
1731
|
+
const stay = (b - a) / speed + delay;
|
|
1732
|
+
if (elapsed < stay) return null;
|
|
1733
|
+
elapsed -= stay;
|
|
1734
|
+
d = b;
|
|
1735
|
+
}
|
|
1736
|
+
d += elapsed * speed;
|
|
1737
|
+
return d >= 0 && d <= w.length ? d : null;
|
|
1738
|
+
};
|
|
1739
|
+
const union2 = (a, b) => /* @__PURE__ */ new Set([...a, ...b]);
|
|
1740
|
+
const bump = (id, f) => {
|
|
1741
|
+
const m = metrics2.elements.get(id);
|
|
1742
|
+
if (m) f(m);
|
|
1743
|
+
};
|
|
1744
|
+
const leaving = /* @__PURE__ */ new Map(), turn = [];
|
|
1745
|
+
for (const flow of list) {
|
|
1746
|
+
const n = leaving.get(flow.from) ?? 0;
|
|
1747
|
+
turn.push(n);
|
|
1748
|
+
leaving.set(flow.from, n + 1);
|
|
1749
|
+
}
|
|
1750
|
+
const biggest = list.length > 0 ? Math.max(...list.map(amount)) : 1;
|
|
1751
|
+
const packets = [];
|
|
1752
|
+
const waitingBy = /* @__PURE__ */ new Map();
|
|
1753
|
+
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);
|
|
1754
|
+
const bases = /* @__PURE__ */ new Map();
|
|
1755
|
+
const baseTree = (from) => {
|
|
1756
|
+
if (!bases.has(from)) bases.set(from, flowTree(from, frame2, offline));
|
|
1757
|
+
return bases.get(from);
|
|
1758
|
+
};
|
|
1759
|
+
for (const [i, flow] of list.entries()) {
|
|
1760
|
+
if (flow.when !== null && values !== null && !holds(flow.when, values)) continue;
|
|
1761
|
+
const source = byID.get(flow.from);
|
|
1762
|
+
const base2 = source ? baseTree(flow.from) : null;
|
|
1763
|
+
if (!source || !base2) continue;
|
|
1764
|
+
const volume = amountIn(flow, values);
|
|
1765
|
+
const fm = { volume, placed: 0, backlog: 0, backup: 0, transit: null };
|
|
1766
|
+
const placements = [];
|
|
1767
|
+
const waiting2 = [];
|
|
1768
|
+
const goals = base2.goals(flow.to, flow.spread === true);
|
|
1769
|
+
const shares = goals.length > 1 ? goals.map((g) => [[g], volume / goals.length]) : [[flow.to, volume]];
|
|
1770
|
+
for (const [candidates, share] of shares) {
|
|
1771
|
+
if (!(share > TINY)) continue;
|
|
1772
|
+
let left = share;
|
|
1773
|
+
for (let n = 0; n < 32 && left > TINY; n++) {
|
|
1774
|
+
const tree = flowTree(flow.from, frame2, union2(offline, full()), candidates[0]);
|
|
1775
|
+
const goal2 = tree?.goals(candidates, false)[0];
|
|
1776
|
+
if (!tree || goal2 === void 0) break;
|
|
1777
|
+
const r = route2(tree, goal2);
|
|
1778
|
+
const ids = [...r.shapes, ...r.hops.map(([id]) => id)];
|
|
1779
|
+
const rooms = ids.filter((id) => room.has(id)).map((id) => room.get(id));
|
|
1780
|
+
const fits = Math.min(left, rooms.length > 0 ? Math.min(...rooms) : Infinity);
|
|
1781
|
+
if (!(fits > TINY)) break;
|
|
1782
|
+
for (const id of ids) if (room.has(id)) room.set(id, room.get(id) - fits);
|
|
1783
|
+
placements.push({ hops: r.hops, shapes: r.shapes, goal: goal2, amount: fits });
|
|
1784
|
+
left -= fits;
|
|
1785
|
+
}
|
|
1786
|
+
if (!(left > TINY)) continue;
|
|
1787
|
+
let at = flow.from;
|
|
1788
|
+
const goal = base2.goals(candidates, false)[0];
|
|
1789
|
+
if (goal !== void 0) {
|
|
1790
|
+
const r = route2(base2, goal), done = full();
|
|
1791
|
+
const sequence = [[r.shapes[0], r.shapes[0]]];
|
|
1792
|
+
r.hops.forEach(([id], k) => {
|
|
1793
|
+
sequence.push([id, r.shapes[k]], [r.shapes[k + 1], r.shapes[k]]);
|
|
1794
|
+
});
|
|
1795
|
+
const first = sequence.find(([id]) => done.has(id));
|
|
1796
|
+
if (first) {
|
|
1797
|
+
at = first[1];
|
|
1798
|
+
const upTo = r.shapes.indexOf(at);
|
|
1799
|
+
for (let q = upTo; q >= 0; q--) {
|
|
1800
|
+
const s = byID.get(r.shapes[q]);
|
|
1801
|
+
if (s && s.queue === true && s.kind !== "connector") {
|
|
1802
|
+
at = r.shapes[q];
|
|
1803
|
+
break;
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
const n = r.shapes.indexOf(at);
|
|
1808
|
+
if (at !== flow.from && n >= 0) {
|
|
1809
|
+
const shapes2 = r.shapes.slice(0, n + 1), hops = r.hops.slice(0, n);
|
|
1810
|
+
for (const id of [...shapes2, ...hops.map(([h]) => h)]) if (room.has(id)) room.set(id, Math.max(0, room.get(id) - left));
|
|
1811
|
+
placements.push({ hops, shapes: shapes2, goal: at, amount: left, waits: true });
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
const existing = waiting2.find(([s]) => s === at);
|
|
1815
|
+
if (existing) existing[1] += left;
|
|
1816
|
+
else waiting2.push([at, left]);
|
|
1817
|
+
}
|
|
1818
|
+
waitingBy.set(flow.id, waiting2);
|
|
1819
|
+
for (const p of placements) {
|
|
1820
|
+
for (const id of [...p.shapes, ...p.hops.map(([h]) => h)]) bump(id, (m) => {
|
|
1821
|
+
m.throughput += p.amount;
|
|
1822
|
+
});
|
|
1823
|
+
if (!p.waits) fm.placed += p.amount;
|
|
1824
|
+
}
|
|
1825
|
+
for (const [at, a] of waiting2) {
|
|
1826
|
+
bump(at, (m) => {
|
|
1827
|
+
m.backlog += a;
|
|
1828
|
+
});
|
|
1829
|
+
fm.backlog += a;
|
|
1830
|
+
}
|
|
1831
|
+
const reachedFirst = placements.filter((p) => !p.waits && p.goal === flow.to[0]).reduce((s, p) => s + p.amount, 0);
|
|
1832
|
+
fm.backup = volume > TINY ? Math.max(0, (volume - reachedFirst) / volume) : 0;
|
|
1833
|
+
const every = everyOf(flow);
|
|
1834
|
+
const speed = clamp(flow.speed, DEFAULT_SPEED, 1, 1e4), size = clamp(flow.size, DEFAULT_SIZE, 2, 200);
|
|
1835
|
+
const phase = turn[i] / leaving.get(flow.from) * every;
|
|
1836
|
+
const color2 = flow.color ?? DEFAULT_COLOR;
|
|
1837
|
+
const cumulative = [];
|
|
1838
|
+
let sum = 0;
|
|
1839
|
+
for (const p of placements) {
|
|
1840
|
+
sum += p.amount / Math.max(volume, TINY);
|
|
1841
|
+
cumulative.push(sum);
|
|
1842
|
+
}
|
|
1843
|
+
if (cumulative.length > 0 && Math.abs(cumulative[cumulative.length - 1] - 1) < 1e-9) cumulative[cumulative.length - 1] = 1;
|
|
1844
|
+
let transitSum = 0;
|
|
1845
|
+
placements.forEach((p, r) => {
|
|
1846
|
+
const w = way(p.hops, p.shapes);
|
|
1847
|
+
if (!w) return;
|
|
1848
|
+
const travel = w.length / speed + w.inside.reduce((s, x) => s + x[2], 0);
|
|
1849
|
+
if (!p.waits) transitSum += travel * p.amount;
|
|
1850
|
+
const lo = Math.ceil((t - phase - travel) / every);
|
|
1851
|
+
const hi = Math.floor((t - phase) / every);
|
|
1852
|
+
const below = r === 0 ? 0 : cumulative[r - 1], upTo = cumulative[r];
|
|
1853
|
+
const dealt = (c, k) => Math.floor((k + 1) * c) - Math.floor(k * c);
|
|
1854
|
+
for (let k = Math.max(lo, hi - 3999); k <= hi; k++) {
|
|
1855
|
+
if (dealt(upTo, k) - dealt(below, k) !== 1) continue;
|
|
1856
|
+
const d = along(w, t - k * every - phase, speed);
|
|
1857
|
+
if (d === null) continue;
|
|
1858
|
+
const s = w.stretches.find(([, a, b]) => d >= a && d <= b);
|
|
1859
|
+
if (s) bump(s[0], (m) => {
|
|
1860
|
+
m.inFlight += 1;
|
|
1861
|
+
});
|
|
1862
|
+
const pt2 = w.line.point(d / w.length);
|
|
1863
|
+
packets.push(packet(packetID(flow.id, k), pt2.x - size / 2, pt2.y - size / 2, size, color2, source.layerID));
|
|
1864
|
+
}
|
|
1865
|
+
});
|
|
1866
|
+
fm.transit = fm.placed > TINY ? transitSum / fm.placed : null;
|
|
1867
|
+
metrics2.flows.set(flow.id, fm);
|
|
1868
|
+
}
|
|
1869
|
+
const piles = /* @__PURE__ */ new Map();
|
|
1870
|
+
const carriedKeys = Object.keys(carried).filter((k) => carried[k] > TINY);
|
|
1871
|
+
for (const flow of list) {
|
|
1872
|
+
const waiting2 = waitingBy.get(flow.id) ?? [];
|
|
1873
|
+
const shapes2 = waiting2.map(([s]) => s);
|
|
1874
|
+
const suffix = "|" + flow.id;
|
|
1875
|
+
if (carriedKeys.some((k) => k.endsWith(suffix))) for (const e of frame2.elements) {
|
|
1876
|
+
const c = carried[queueKey(e.id, flow.id)];
|
|
1877
|
+
if (c !== void 0 && c > TINY && !shapes2.includes(e.id)) shapes2.push(e.id);
|
|
1878
|
+
}
|
|
1879
|
+
if (shapes2.length === 0) continue;
|
|
1880
|
+
const volume = amountIn(flow, values);
|
|
1881
|
+
const every = everyOf(flow);
|
|
1882
|
+
const unit = every * Math.max(volume, TINY);
|
|
1883
|
+
const speed = clamp(flow.speed, DEFAULT_SPEED, 1, 1e4), size = clamp(flow.size, DEFAULT_SIZE, 2, 200);
|
|
1884
|
+
const color2 = flow.color ?? DEFAULT_COLOR;
|
|
1885
|
+
shapes2.forEach((at, j) => {
|
|
1886
|
+
const shape = byID.get(at);
|
|
1887
|
+
if (!shape) return;
|
|
1888
|
+
const layer = byID.get(flow.from)?.layerID ?? shape.layerID;
|
|
1889
|
+
const backlog = waiting2.find(([s]) => s === at)?.[1] ?? 0;
|
|
1890
|
+
const before = Math.max(0, carried[queueKey(at, flow.id)] ?? 0);
|
|
1891
|
+
let depth = before + backlog * since;
|
|
1892
|
+
if (backlog <= TINY && before > TINY) {
|
|
1893
|
+
const trial = new Map(room);
|
|
1894
|
+
const outlets = [];
|
|
1895
|
+
let total = 0;
|
|
1896
|
+
for (let n2 = 0; n2 < 32; n2++) {
|
|
1897
|
+
const blocked = new Set([...trial].filter(([, v]) => v <= TINY).map(([id]) => id));
|
|
1898
|
+
const tree = flowTree(at, frame2, union2(offline, blocked), flow.to[0]);
|
|
1899
|
+
const goal = tree?.goals(flow.to, false)[0];
|
|
1900
|
+
if (!tree || goal === void 0) break;
|
|
1901
|
+
const r = route2(tree, goal);
|
|
1902
|
+
const ids = [...r.shapes.slice(1), ...r.hops.map(([id]) => id)];
|
|
1903
|
+
const rooms = ids.filter((id) => trial.has(id)).map((id) => trial.get(id));
|
|
1904
|
+
const spare = rooms.length > 0 ? Math.min(...rooms) : Infinity;
|
|
1905
|
+
const rate = Number.isFinite(spare) ? spare : Math.max(before / 0.5 - total, TINY * 2);
|
|
1906
|
+
if (!(rate > TINY)) break;
|
|
1907
|
+
for (const id of ids) if (trial.has(id)) trial.set(id, trial.get(id) - rate);
|
|
1908
|
+
outlets.push({ hops: r.hops, shapes: r.shapes, rate });
|
|
1909
|
+
total += rate;
|
|
1910
|
+
if (!Number.isFinite(spare)) break;
|
|
1911
|
+
}
|
|
1912
|
+
if (total > TINY) {
|
|
1913
|
+
depth = Math.max(0, before - total * since);
|
|
1914
|
+
if (depth > TINY) {
|
|
1915
|
+
for (const [id, v] of trial) room.set(id, v);
|
|
1916
|
+
for (const o of outlets) for (const id of [...o.shapes, ...o.hops.map(([h]) => h)]) bump(id, (m) => {
|
|
1917
|
+
m.throughput += o.rate;
|
|
1918
|
+
});
|
|
1919
|
+
}
|
|
1920
|
+
const lasts = before / total;
|
|
1921
|
+
outlets.forEach((o, i) => {
|
|
1922
|
+
const w = way(o.hops, o.shapes);
|
|
1923
|
+
if (!w) return;
|
|
1924
|
+
const spacing = Math.max(unit / o.rate, 0.05);
|
|
1925
|
+
for (let k = 0; k * spacing < lasts && k * spacing <= since && k < 2048; k++) {
|
|
1926
|
+
const d = along(w, since - k * spacing, speed);
|
|
1927
|
+
if (d === null) continue;
|
|
1928
|
+
const pt2 = w.line.point(d / w.length);
|
|
1929
|
+
packets.push(packet(packetID(flow.id, 2 ** 39 + 2 ** 20 + j * 2 ** 14 + i * 2 ** 11 + k), pt2.x - size / 2, pt2.y - size / 2, size, color2, layer));
|
|
1930
|
+
}
|
|
1931
|
+
});
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1934
|
+
if (!(depth > TINY || backlog > TINY)) return;
|
|
1935
|
+
bump(at, (m) => {
|
|
1936
|
+
m.depth += depth;
|
|
1937
|
+
});
|
|
1938
|
+
if (depth > TINY) metrics2.carry[queueKey(at, flow.id)] = depth;
|
|
1939
|
+
const n = Math.min(Math.max(Math.ceil(depth / unit - 1e-9), 1), 30);
|
|
1940
|
+
const rows = Math.trunc((n + 9) / 10);
|
|
1941
|
+
const base2 = piles.get(at) ?? 0;
|
|
1942
|
+
piles.set(at, base2 + rows);
|
|
1943
|
+
for (let q = 0; q < n; q++) {
|
|
1944
|
+
const cx = shape.x + 2 + size / 2 + q % 10 * (size + 3);
|
|
1945
|
+
const cy = shape.y - 4 - size / 2 - (base2 + Math.trunc(q / 10)) * (size + 3);
|
|
1946
|
+
packets.push(packet(packetID(flow.id, 2 ** 39 + j * 64 + q), cx - size / 2, cy - size / 2, size, color2, layer));
|
|
1947
|
+
}
|
|
1948
|
+
});
|
|
1949
|
+
}
|
|
1950
|
+
for (const e of frame2.elements) {
|
|
1951
|
+
const c = e.capacity ?? null;
|
|
1952
|
+
const m = metrics2.elements.get(e.id);
|
|
1953
|
+
if (c === null || !Number.isFinite(c) || !m) continue;
|
|
1954
|
+
m.load = c > 0 ? m.throughput / c : 1;
|
|
1955
|
+
}
|
|
1956
|
+
return { packets, metrics: metrics2 };
|
|
1957
|
+
}
|
|
1958
|
+
var BLANK = {
|
|
1959
|
+
seed: 1,
|
|
1960
|
+
text: null,
|
|
1961
|
+
connector: null,
|
|
1962
|
+
groupID: null,
|
|
1963
|
+
points: null,
|
|
1964
|
+
path: null,
|
|
1965
|
+
subpaths: null,
|
|
1966
|
+
evenOdd: null,
|
|
1967
|
+
rotation: null,
|
|
1968
|
+
link: null,
|
|
1969
|
+
url: null,
|
|
1970
|
+
anchor: null,
|
|
1971
|
+
click: null,
|
|
1972
|
+
hover: null,
|
|
1973
|
+
when: null,
|
|
1974
|
+
overrides: null,
|
|
1975
|
+
symbol: null,
|
|
1976
|
+
playback: null,
|
|
1977
|
+
offset: null,
|
|
1978
|
+
args: null,
|
|
1979
|
+
image: null,
|
|
1980
|
+
reveal: null
|
|
1981
|
+
};
|
|
1982
|
+
function style(stroke, fill, strokeWidth, fillStyle = "solid", opacity2 = null) {
|
|
1983
|
+
return {
|
|
1984
|
+
stroke: { ...stroke },
|
|
1985
|
+
fill: { ...fill },
|
|
1986
|
+
fillStyle,
|
|
1987
|
+
strokeWidth,
|
|
1988
|
+
dash: "solid",
|
|
1989
|
+
roughness: 0,
|
|
1990
|
+
bowing: 1,
|
|
1991
|
+
fontSize: 18,
|
|
1992
|
+
font: null,
|
|
1993
|
+
gridSize: null,
|
|
1994
|
+
opacity: opacity2,
|
|
1995
|
+
dashOffset: null,
|
|
1996
|
+
textColor: null
|
|
1997
|
+
};
|
|
1998
|
+
}
|
|
1999
|
+
function packet(id, x, y, size, color2, layerID) {
|
|
2000
|
+
return { ...BLANK, id, kind: "ellipse", layerID, x, y, width: size, height: size, style: style(color2, color2, 1.5) };
|
|
2001
|
+
}
|
|
2002
|
+
function metricLookup(m, frame2) {
|
|
2003
|
+
const anchors = /* @__PURE__ */ new Map();
|
|
2004
|
+
for (const e of frame2.elements) if (e.anchor !== null && !anchors.has(e.anchor)) anchors.set(e.anchor, e.id);
|
|
2005
|
+
const UUID2 = /^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$/;
|
|
2006
|
+
return (term, own) => {
|
|
2007
|
+
const id = term.target === "" ? own : anchors.get(term.target) ?? (UUID2.test(term.target) ? term.target.toUpperCase() : null);
|
|
2008
|
+
return id === null ? null : metricValue(m, term.metric, id);
|
|
2009
|
+
};
|
|
2010
|
+
}
|
|
2011
|
+
function finishPlan(elements, metrics2, values, frame2, since = Infinity, carried = {}) {
|
|
2012
|
+
const lookup = metricLookup(metrics2, frame2);
|
|
2013
|
+
const plan2 = [], readings = {};
|
|
2014
|
+
for (const e of elements) {
|
|
2015
|
+
const applied = [];
|
|
2016
|
+
if (values !== null && e.overrides) {
|
|
2017
|
+
e.overrides.forEach((o, k) => {
|
|
2018
|
+
if (mentionsMetric(o.when) && holds(o.when, values, (m) => lookup(m, e.id))) applied.push(k);
|
|
2019
|
+
});
|
|
2020
|
+
}
|
|
2021
|
+
const g = values === null ? null : e.gauge;
|
|
2022
|
+
if (!g) {
|
|
2023
|
+
plan2.push({ applied, gauge: null });
|
|
2024
|
+
continue;
|
|
2025
|
+
}
|
|
2026
|
+
let shown = metricValue(metrics2, g.metric, g.of);
|
|
2027
|
+
const then = carried[gaugeKey(e.id)];
|
|
2028
|
+
if (shown !== null && then !== void 0 && g.metric !== "depth" && g.metric !== "inFlight" && since < GAUGE_EASE) {
|
|
2029
|
+
const f = Math.max(0, since / GAUGE_EASE), smooth = f * f * (3 - 2 * f);
|
|
2030
|
+
shown = then + (shown - then) * smooth;
|
|
2031
|
+
}
|
|
2032
|
+
if (shown !== null) readings[gaugeKey(e.id)] = shown;
|
|
2033
|
+
plan2.push({ applied, gauge: { value: shown } });
|
|
2034
|
+
}
|
|
2035
|
+
return { plan: plan2, readings };
|
|
2036
|
+
}
|
|
2037
|
+
function finishing(elements, metrics2, values, frame2, flows, since = Infinity, carried = {}) {
|
|
2038
|
+
const { plan: plan2, readings } = finishPlan(elements, metrics2, values, frame2, since, carried);
|
|
2039
|
+
const out = [];
|
|
2040
|
+
elements.forEach((e, i) => {
|
|
2041
|
+
for (const k of plan2[i].applied) e = applyOverride(looksOnly(e.overrides[k]), e);
|
|
2042
|
+
const g = plan2[i].gauge;
|
|
2043
|
+
if (g && e.gauge) out.push(...gauge(e, e.gauge, g.value, frame2, flows));
|
|
2044
|
+
else out.push(e);
|
|
2045
|
+
});
|
|
2046
|
+
return { elements: out, readings };
|
|
2047
|
+
}
|
|
2048
|
+
var gaugeKey = (id) => "gauge|" + id;
|
|
2049
|
+
var GAUGE_EASE = 1;
|
|
2050
|
+
var hex = (h) => {
|
|
2051
|
+
const v = parseInt(h.slice(1), 16);
|
|
2052
|
+
return { r: (v >> 16 & 255) / 255, g: (v >> 8 & 255) / 255, b: (v & 255) / 255, a: 1 };
|
|
2053
|
+
};
|
|
2054
|
+
var GAUGE_GREEN = hex("#16a34a");
|
|
2055
|
+
var GAUGE_AMBER = hex("#d97706");
|
|
2056
|
+
var GAUGE_RED = hex("#dc2626");
|
|
2057
|
+
var GAUGE_BLUE = hex("#2f6fdf");
|
|
2058
|
+
var GAUGE_TRACK = hex("#e5e7eb");
|
|
2059
|
+
var GAUGE_GRAY = hex("#9ca3af");
|
|
2060
|
+
function formatted(metric, v) {
|
|
2061
|
+
if (v === null || !Number.isFinite(v)) return "\u2013";
|
|
2062
|
+
switch (metric) {
|
|
2063
|
+
case "load":
|
|
2064
|
+
case "backup":
|
|
2065
|
+
return formatNumber(v * 100, 0) + "%";
|
|
2066
|
+
case "transit":
|
|
2067
|
+
case "delay":
|
|
2068
|
+
return formatNumber(v, Math.abs(v) >= 10 ? 1 : 2) + "s";
|
|
2069
|
+
default:
|
|
2070
|
+
return formatNumber(v, Math.abs(v) >= 100 ? 0 : Math.abs(v) >= 10 ? 1 : 2);
|
|
2071
|
+
}
|
|
2072
|
+
}
|
|
2073
|
+
function formatNumber(v, places) {
|
|
2074
|
+
const scale = places === 0 ? 1 : places === 1 ? 10 : 100;
|
|
2075
|
+
const n = Math.floor(Math.abs(v) * scale + 0.5);
|
|
2076
|
+
if (!(n < 1e15)) return v < 0 ? "-\u221E" : "\u221E";
|
|
2077
|
+
const whole = Math.trunc(n / scale), part = n - whole * scale;
|
|
2078
|
+
let s = String(whole);
|
|
2079
|
+
if (places > 0 && part > 0) {
|
|
2080
|
+
let digits = String(part);
|
|
2081
|
+
while (digits.length < places) digits = "0" + digits;
|
|
2082
|
+
while (digits.endsWith("0")) digits = digits.slice(0, -1);
|
|
2083
|
+
s += "." + digits;
|
|
2084
|
+
}
|
|
2085
|
+
return (v < 0 && n > 0 ? "-" : "") + s;
|
|
2086
|
+
}
|
|
2087
|
+
function gauge(e, g, value2, frame2, flows) {
|
|
2088
|
+
const target = frame2.elements.find((x) => x.id === g.of);
|
|
2089
|
+
const full = g.max ?? (g.metric === "load" ? 1 : target?.capacity ?? value2 ?? 1);
|
|
2090
|
+
const fraction = value2 === null ? 0 : full > 0 ? Math.min(Math.max(value2 / full, 0), 1) : 0;
|
|
2091
|
+
const warn = g.warn ?? (g.metric === "load" ? 0.7 : null), alarm = g.alarm ?? (g.metric === "load" ? 0.9 : null);
|
|
2092
|
+
let color2 = warn === null && alarm === null ? GAUGE_BLUE : GAUGE_GREEN;
|
|
2093
|
+
if (value2 !== null && warn !== null && value2 >= warn) color2 = GAUGE_AMBER;
|
|
2094
|
+
if (value2 !== null && alarm !== null && value2 >= alarm) color2 = GAUGE_RED;
|
|
2095
|
+
if (value2 === null) color2 = GAUGE_GRAY;
|
|
2096
|
+
let name = g.label;
|
|
2097
|
+
if (name === null) {
|
|
2098
|
+
const what = target ? target.text !== null ? target.text.split("\n")[0] : target.anchor : null;
|
|
2099
|
+
name = `${g.metric} ${what ?? (flows.some((f) => f.id === g.of) ? "flow" : "?")}`;
|
|
2100
|
+
}
|
|
2101
|
+
const kind = g.style ?? "number";
|
|
2102
|
+
const w = Math.max(e.width, 1), h = Math.max(e.height, 1);
|
|
2103
|
+
const line2 = (a, b) => [
|
|
2104
|
+
a.x / w,
|
|
2105
|
+
a.y / h,
|
|
2106
|
+
(a.x + (b.x - a.x) / 3) / w,
|
|
2107
|
+
(a.y + (b.y - a.y) / 3) / h,
|
|
2108
|
+
(a.x + 2 * (b.x - a.x) / 3) / w,
|
|
2109
|
+
(a.y + 2 * (b.y - a.y) / 3) / h,
|
|
2110
|
+
b.x / w,
|
|
2111
|
+
b.y / h
|
|
2112
|
+
];
|
|
2113
|
+
const box = (x, y, bw, bh) => {
|
|
2114
|
+
const p = [{ x, y }, { x: x + bw, y }, { x: x + bw, y: y + bh }, { x, y: y + bh }];
|
|
2115
|
+
let coords = line2(p[0], p[1]);
|
|
2116
|
+
for (let k = 1; k < 4; k++) coords = coords.concat(line2(p[k], p[(k + 1) % 4]).slice(2));
|
|
2117
|
+
return { closed: true, coords };
|
|
2118
|
+
};
|
|
2119
|
+
const arc = (c, radius, a0, a1) => {
|
|
2120
|
+
const pieces2 = Math.max(1, Math.ceil(Math.abs(a1 - a0) / (Math.PI / 2) - 1e-9));
|
|
2121
|
+
const step = (a1 - a0) / pieces2, kappa = 4 / 3 * Math.tan(step / 4);
|
|
2122
|
+
const at = (a) => ({ x: c.x + radius * Math.cos(a), y: c.y + radius * Math.sin(a) });
|
|
2123
|
+
const s = at(a0);
|
|
2124
|
+
const coords = [s.x / w, s.y / h];
|
|
2125
|
+
for (let k = 0; k < pieces2; k++) {
|
|
2126
|
+
const a = a0 + k * step, b = a + step;
|
|
2127
|
+
const p0 = at(a), p3 = at(b);
|
|
2128
|
+
const c1 = { x: p0.x - kappa * radius * Math.sin(a), y: p0.y + kappa * radius * Math.cos(a) };
|
|
2129
|
+
const c2 = { x: p3.x + kappa * radius * Math.sin(b), y: p3.y - kappa * radius * Math.cos(b) };
|
|
2130
|
+
coords.push(c1.x / w, c1.y / h, c2.x / w, c2.y / h, p3.x / w, p3.y / h);
|
|
2131
|
+
}
|
|
2132
|
+
return { closed: false, coords };
|
|
2133
|
+
};
|
|
2134
|
+
const part = (k, subpaths, stroke, fill, width) => ({
|
|
2135
|
+
...BLANK,
|
|
2136
|
+
id: packetID(e.id, -(2 ** 38) - k),
|
|
2137
|
+
kind: "path",
|
|
2138
|
+
layerID: e.layerID,
|
|
2139
|
+
x: e.x,
|
|
2140
|
+
y: e.y,
|
|
2141
|
+
width: e.width,
|
|
2142
|
+
height: e.height,
|
|
2143
|
+
style: style(stroke, fill ?? { r: 0, g: 0, b: 0, a: 0 }, width, "solid", e.style.opacity),
|
|
2144
|
+
subpaths
|
|
2145
|
+
});
|
|
2146
|
+
const back = {
|
|
2147
|
+
...e,
|
|
2148
|
+
gauge: null,
|
|
2149
|
+
kind: "path",
|
|
2150
|
+
subpaths: [box(0, 0, w, h)],
|
|
2151
|
+
text: name + "\n" + formatted(g.metric, value2),
|
|
2152
|
+
style: { ...e.style }
|
|
2153
|
+
};
|
|
2154
|
+
const parts = [];
|
|
2155
|
+
if (kind === "number") {
|
|
2156
|
+
back.labelRect = { x: 0, y: 0, width: 1, height: 1 };
|
|
2157
|
+
back.style.textColor = { ...color2 };
|
|
2158
|
+
} else if (kind === "bar") {
|
|
2159
|
+
back.labelRect = { x: 0, y: 0, width: 1, height: 0.66 };
|
|
2160
|
+
const inset2 = Math.min(10, w * 0.08), barH = Math.min(12, h * 0.18), top = h * 0.72;
|
|
2161
|
+
const trackW = w - 2 * inset2;
|
|
2162
|
+
parts.push(part(1, [box(inset2, top, trackW, barH)], GAUGE_TRACK, GAUGE_TRACK, 0.5));
|
|
2163
|
+
if (fraction > 0) parts.push(part(2, [box(inset2, top, trackW * fraction, barH)], color2, color2, 0.5));
|
|
2164
|
+
} else {
|
|
2165
|
+
back.labelRect = { x: 0, y: 0.54, width: 1, height: 0.46 };
|
|
2166
|
+
const radius = Math.min(w * 0.38, h * 0.4), center2 = { x: w / 2, y: h * 0.5 };
|
|
2167
|
+
const thick = Math.max(2, radius * 0.16);
|
|
2168
|
+
parts.push(part(1, [arc(center2, radius, Math.PI, 2 * Math.PI)], GAUGE_TRACK, null, thick));
|
|
2169
|
+
if (fraction > 0) parts.push(part(2, [arc(center2, radius, Math.PI, Math.PI + Math.PI * fraction)], color2, null, thick));
|
|
2170
|
+
const a = Math.PI + Math.PI * fraction;
|
|
2171
|
+
const tip = { x: center2.x + radius * 0.85 * Math.cos(a), y: center2.y + radius * 0.85 * Math.sin(a) };
|
|
2172
|
+
parts.push(part(3, [{ closed: false, coords: line2(center2, tip) }], { r: 0.2, g: 0.2, b: 0.2, a: 1 }, null, 2));
|
|
2173
|
+
}
|
|
2174
|
+
return [back, ...parts];
|
|
2175
|
+
}
|
|
2176
|
+
|
|
1284
2177
|
// src/rough.ts
|
|
1285
|
-
var offset = (rng,
|
|
2178
|
+
var offset = (rng, amount2, roughness) => rng.signed() * amount2 * roughness;
|
|
1286
2179
|
function line(a, b, rng, roughness, bowing, preserveEnds, into) {
|
|
1287
2180
|
const length = Math.hypot(b.x - a.x, b.y - a.y);
|
|
1288
2181
|
if (!(length > 0.5)) return;
|
|
@@ -1295,7 +2188,7 @@ function line(a, b, rng, roughness, bowing, preserveEnds, into) {
|
|
|
1295
2188
|
const bow = offset(rng, length / 60, roughness) * bowing * gain;
|
|
1296
2189
|
const dx = b.x - a.x, dy = b.y - a.y;
|
|
1297
2190
|
const nx = -dy / length, ny = dx / length;
|
|
1298
|
-
const
|
|
2191
|
+
const start2 = preserveEnds ? a : (() => {
|
|
1299
2192
|
const x = a.x + j();
|
|
1300
2193
|
return { x, y: a.y + j() };
|
|
1301
2194
|
})();
|
|
@@ -1307,7 +2200,7 @@ function line(a, b, rng, roughness, bowing, preserveEnds, into) {
|
|
|
1307
2200
|
const c1y = a.y + dy * mid * 0.66 + ny * bow + j() * 0.5;
|
|
1308
2201
|
const c2x = a.x + dx * (0.5 + mid * 0.5) + nx * bow + j() * 0.5;
|
|
1309
2202
|
const c2y = a.y + dy * (0.5 + mid * 0.5) + ny * bow + j() * 0.5;
|
|
1310
|
-
into.moveTo(
|
|
2203
|
+
into.moveTo(start2);
|
|
1311
2204
|
into.curveTo(end, { x: c1x, y: c1y }, { x: c2x, y: c2y });
|
|
1312
2205
|
}
|
|
1313
2206
|
}
|
|
@@ -1568,10 +2461,10 @@ var Cache = class {
|
|
|
1568
2461
|
__publicField(this, "limit");
|
|
1569
2462
|
this.limit = limit;
|
|
1570
2463
|
}
|
|
1571
|
-
get(key,
|
|
2464
|
+
get(key, make2) {
|
|
1572
2465
|
const hit = this.map.get(key);
|
|
1573
2466
|
if (hit !== void 0) return hit;
|
|
1574
|
-
const v =
|
|
2467
|
+
const v = make2();
|
|
1575
2468
|
if (this.map.size >= this.limit) this.map.clear();
|
|
1576
2469
|
this.map.set(key, v);
|
|
1577
2470
|
return v;
|
|
@@ -1795,26 +2688,59 @@ function subCubic(p, t0, t1) {
|
|
|
1795
2688
|
const left = split(p, t1)[0];
|
|
1796
2689
|
return t1 > 0 ? split(left, t0 / t1)[1] : [p[0], p[0], p[0], p[0]];
|
|
1797
2690
|
}
|
|
2691
|
+
function boxOf(pts) {
|
|
2692
|
+
let x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity;
|
|
2693
|
+
for (const p of pts) {
|
|
2694
|
+
x0 = Math.min(x0, p.x);
|
|
2695
|
+
y0 = Math.min(y0, p.y);
|
|
2696
|
+
x1 = Math.max(x1, p.x);
|
|
2697
|
+
y1 = Math.max(y1, p.y);
|
|
2698
|
+
}
|
|
2699
|
+
return { x0, y0, x1, y1 };
|
|
2700
|
+
}
|
|
2701
|
+
function touching(a, b) {
|
|
2702
|
+
const eps = 1e-6;
|
|
2703
|
+
return a.x0 <= b.x1 + eps && b.x0 <= a.x1 + eps && a.y0 <= b.y1 + eps && b.y0 <= a.y1 + eps;
|
|
2704
|
+
}
|
|
2705
|
+
var hopFrames = /* @__PURE__ */ new WeakMap();
|
|
2706
|
+
function linesBeneath(e, page, index) {
|
|
2707
|
+
let f = hopFrames.get(index);
|
|
2708
|
+
if (!f || f.layers !== page.layers || !isIndexOf(page, index)) {
|
|
2709
|
+
const visible = new Set(page.layers.filter((l) => l.visible).map((l) => l.id));
|
|
2710
|
+
const first = /* @__PURE__ */ new Map(), under = [];
|
|
2711
|
+
for (const o of orderedElements(page)) {
|
|
2712
|
+
if (!first.has(o.id)) first.set(o.id, under.length);
|
|
2713
|
+
if (o.kind === "connector" && visible.has(o.layerID)) under.push(o);
|
|
2714
|
+
}
|
|
2715
|
+
f = { layers: page.layers, first, under, lines: [] };
|
|
2716
|
+
if (isIndexOf(page, index)) hopFrames.set(index, f);
|
|
2717
|
+
}
|
|
2718
|
+
const count = f.first.get(e.id) ?? f.under.length;
|
|
2719
|
+
while (f.lines.length < count) {
|
|
2720
|
+
const points = routePoints(route(f.under[f.lines.length], index));
|
|
2721
|
+
f.lines.push({ points, box: boxOf(points) });
|
|
2722
|
+
}
|
|
2723
|
+
return { lines: f.lines, count };
|
|
2724
|
+
}
|
|
1798
2725
|
function hopCrossings(e, r, page, index) {
|
|
1799
2726
|
const segments = hopSamples(r);
|
|
1800
2727
|
if (segments.length === 0) return [];
|
|
1801
|
-
const
|
|
1802
|
-
const beneath = [];
|
|
1803
|
-
for (const o of orderedElements(page)) {
|
|
1804
|
-
if (o.id === e.id) break;
|
|
1805
|
-
if (o.kind !== "connector" || !visible.has(o.layerID)) continue;
|
|
1806
|
-
beneath.push(routePoints(route(o, index)));
|
|
1807
|
-
}
|
|
2728
|
+
const { lines, count } = linesBeneath(e, page, index);
|
|
1808
2729
|
const rad = hopRadius(e.style.strokeWidth);
|
|
1809
2730
|
const out = [];
|
|
1810
2731
|
for (const samples of segments) {
|
|
1811
2732
|
const along = runningLengths(samples);
|
|
1812
2733
|
const length = along[along.length - 1];
|
|
1813
2734
|
const found = [];
|
|
2735
|
+
const near = [];
|
|
2736
|
+
const whole = boxOf(samples);
|
|
2737
|
+
for (let n = 0; n < count; n++) if (touching(whole, lines[n].box)) near.push(lines[n]);
|
|
1814
2738
|
for (let i = 0; i < samples.length - 1; i++) {
|
|
1815
2739
|
const p = samples[i], q = samples[i + 1];
|
|
1816
2740
|
const dx = q.x - p.x, dy = q.y - p.y;
|
|
1817
|
-
|
|
2741
|
+
const box = boxOf([p, q]);
|
|
2742
|
+
for (const { points: line2, box: lineBox } of near) {
|
|
2743
|
+
if (!touching(box, lineBox)) continue;
|
|
1818
2744
|
for (let k = 0; k < line2.length - 1; k++) {
|
|
1819
2745
|
const a = line2[k], b = line2[k + 1];
|
|
1820
2746
|
const ex = b.x - a.x, ey = b.y - a.y;
|
|
@@ -2094,8 +3020,8 @@ function followSpan(track, t) {
|
|
|
2094
3020
|
if (track.keys.some((k) => k.t >= span.end && k.t <= t && (k.x !== void 0 || k.y !== void 0))) return null;
|
|
2095
3021
|
return f;
|
|
2096
3022
|
}
|
|
2097
|
-
function tracing(e, page) {
|
|
2098
|
-
if (e.kind === "connector") return new Polyline(routePoints(route(e,
|
|
3023
|
+
function tracing(e, page, index = indexOf(page)) {
|
|
3024
|
+
if (e.kind === "connector") return new Polyline(routePoints(route(e, index)));
|
|
2099
3025
|
const clean = cloneElement(e);
|
|
2100
3026
|
clean.style.roughness = 0;
|
|
2101
3027
|
const pts = outline(clean).contours()[0] ?? [];
|
|
@@ -2105,9 +3031,10 @@ function following(track, e, t, page) {
|
|
|
2105
3031
|
const f = followSpan(track, t);
|
|
2106
3032
|
if (!f) return e;
|
|
2107
3033
|
t = localTime(track, t);
|
|
2108
|
-
const
|
|
3034
|
+
const index = indexOf(page);
|
|
3035
|
+
const pathElement = index.get(f.path);
|
|
2109
3036
|
if (!pathElement || pathElement.id === e.id) return e;
|
|
2110
|
-
const line2 = tracing(pathElement, page);
|
|
3037
|
+
const line2 = tracing(pathElement, page, index);
|
|
2111
3038
|
const span = f.end - f.start;
|
|
2112
3039
|
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);
|
|
2113
3040
|
if (u > 1) u = rem(u, 1) === 0 ? 1 : rem(u, 1);
|
|
@@ -2138,16 +3065,39 @@ function trackMap(tl) {
|
|
|
2138
3065
|
for (const t of tl.tracks) if (!m.has(t.element)) m.set(t.element, t);
|
|
2139
3066
|
return m;
|
|
2140
3067
|
}
|
|
2141
|
-
function resolved(page, t, state = "initial") {
|
|
2142
|
-
return expandingSymbols(posed(page, t, state), clampTime(t));
|
|
3068
|
+
function resolved(page, t, state = "initial", since = null, carried = {}) {
|
|
3069
|
+
return expandingSymbols(posed(page, t, state, since, carried), clampTime(t));
|
|
2143
3070
|
}
|
|
2144
3071
|
var EMPTY_TIMELINE = { fps: 12, duration: 0, boil: null, settle: null, poster: null, tracks: [], camera: null, markers: null };
|
|
2145
|
-
function posed(page, t, state = "initial") {
|
|
3072
|
+
function posed(page, t, state = "initial", since = null, carried = {}) {
|
|
3073
|
+
return posedWithMetrics(page, t, state, since, carried).frame;
|
|
3074
|
+
}
|
|
3075
|
+
function posedWithMetrics(page, t, state = "initial", since = null, carried = {}) {
|
|
2146
3076
|
const off = stateHidden(page, state);
|
|
2147
|
-
const
|
|
2148
|
-
|
|
3077
|
+
const values = stateValues(page, state);
|
|
3078
|
+
const active = /* @__PURE__ */ new Map();
|
|
3079
|
+
for (const e of page.elements) {
|
|
3080
|
+
const o = activeOverrides(e, values);
|
|
3081
|
+
if (o.length > 0) active.set(e.id, o);
|
|
3082
|
+
}
|
|
3083
|
+
const flowing = (page.flows ?? []).length > 0;
|
|
3084
|
+
const measuring = flowing || page.elements.some((e) => e.gauge || (e.overrides ?? []).some((o) => mentionsMetric(o.when)));
|
|
3085
|
+
const timeline = page.timeline ?? (off.size === 0 && active.size === 0 && !measuring ? null : EMPTY_TIMELINE);
|
|
3086
|
+
if (!timeline) return { frame: page, metrics: emptyMetrics() };
|
|
2149
3087
|
t = clampTime(t);
|
|
2150
3088
|
const tracks = trackMap(timeline);
|
|
3089
|
+
for (const [id, overrides] of active) {
|
|
3090
|
+
const track = tracks.get(id);
|
|
3091
|
+
if (!track?.follow || !overrides.some((o) => o.reroute)) continue;
|
|
3092
|
+
tracks.set(id, {
|
|
3093
|
+
...track,
|
|
3094
|
+
follow: track.follow.map((f) => {
|
|
3095
|
+
let path = f.path;
|
|
3096
|
+
for (const o of overrides) path = o.reroute?.[path] ?? path;
|
|
3097
|
+
return { ...f, path };
|
|
3098
|
+
})
|
|
3099
|
+
});
|
|
3100
|
+
}
|
|
2151
3101
|
const reseed = timeline.boil === null ? null : Math.trunc(Math.floor(t * clampFPS(timeline.fps) + 1e-9) / Math.max(1, timeline.boil)) % BOIL_DRAWINGS >>> 0;
|
|
2152
3102
|
const hidden = /* @__PURE__ */ new Set();
|
|
2153
3103
|
for (const tr of tracks.values()) if (!isVisible(tr, t)) hidden.add(tr.element);
|
|
@@ -2167,6 +3117,14 @@ function posed(page, t, state = "initial") {
|
|
|
2167
3117
|
let elements = page.elements.map((e) => {
|
|
2168
3118
|
const track = tracks.get(e.id);
|
|
2169
3119
|
let out = track ? applyTrack(track, e, t) : e;
|
|
3120
|
+
for (const o of active.get(e.id) ?? []) out = applyOverride(o, out);
|
|
3121
|
+
const list = active.get(e.id);
|
|
3122
|
+
if (list) {
|
|
3123
|
+
const caps = list.map((o) => o.capacity).filter((c) => c !== null && Number.isFinite(c));
|
|
3124
|
+
const delays = list.map((o) => o.delay).filter((d) => d !== null && Number.isFinite(d));
|
|
3125
|
+
if (caps.length > 0) out.capacity = Math.max(Math.min(...caps), 0);
|
|
3126
|
+
if (delays.length > 0 && out.kind !== "connector") out.delay = Math.max(Math.max(...delays), 0);
|
|
3127
|
+
}
|
|
2170
3128
|
if (reseed !== null && reseed > 0 && boils(e)) {
|
|
2171
3129
|
if (out === e) out = cloneElement(e);
|
|
2172
3130
|
out.seed = boiled(e.seed, reseed);
|
|
@@ -2200,7 +3158,18 @@ function posed(page, t, state = "initial") {
|
|
|
2200
3158
|
pinned.connector.end = { elementID: null, port: c.end.port, x: b.x, y: b.y };
|
|
2201
3159
|
return pinned;
|
|
2202
3160
|
});
|
|
2203
|
-
|
|
3161
|
+
elements = elements.filter((e) => !gone.has(e.id));
|
|
3162
|
+
let metrics2 = emptyMetrics();
|
|
3163
|
+
if (measuring) {
|
|
3164
|
+
const offline = new Set([...active].filter(([, list]) => list.some((o) => o.offline === true)).map(([id]) => id));
|
|
3165
|
+
const frame2 = { ...page, elements, timeline: null };
|
|
3166
|
+
const out = flowFrame(page.flows, frame2, t, values, offline, since, carried);
|
|
3167
|
+
metrics2 = out.metrics;
|
|
3168
|
+
const finished = finishing(elements, out.metrics, values, frame2, page.flows ?? [], Math.max(0, Math.min(since ?? t, 1e6)), carried);
|
|
3169
|
+
Object.assign(metrics2.carry, finished.readings);
|
|
3170
|
+
elements = [...finished.elements, ...out.packets];
|
|
3171
|
+
}
|
|
3172
|
+
return { frame: { ...page, elements, timeline: null, flows: measuring ? null : page.flows }, metrics: metrics2 };
|
|
2204
3173
|
}
|
|
2205
3174
|
function posterTime(page) {
|
|
2206
3175
|
return page.timeline ? page.timeline.poster ?? page.timeline.duration : 0;
|
|
@@ -2315,22 +3284,35 @@ function filled(s, args) {
|
|
|
2315
3284
|
}) };
|
|
2316
3285
|
}
|
|
2317
3286
|
function symbolPage(s) {
|
|
2318
|
-
return { id: s.id, name: s.name, layers: s.layers, elements: s.elements, theme: null, timeline: s.timeline, symbols: null, variables: null };
|
|
3287
|
+
return { id: s.id, name: s.name, layers: s.layers, elements: s.elements, theme: null, timeline: s.timeline, symbols: null, variables: null, flows: null };
|
|
2319
3288
|
}
|
|
2320
3289
|
function hasInstances(page) {
|
|
2321
3290
|
return page.elements.some((e) => e.kind === "symbol");
|
|
2322
3291
|
}
|
|
2323
|
-
|
|
3292
|
+
var Expansions = class {
|
|
3293
|
+
constructor(page, t, posing) {
|
|
3294
|
+
__publicField(this, "page");
|
|
3295
|
+
__publicField(this, "t");
|
|
3296
|
+
__publicField(this, "posing");
|
|
3297
|
+
/** For each instance: its time override, the budget it found and left, and what it made. */
|
|
3298
|
+
__publicField(this, "made", /* @__PURE__ */ new Map());
|
|
3299
|
+
this.page = page;
|
|
3300
|
+
this.t = t;
|
|
3301
|
+
this.posing = posing;
|
|
3302
|
+
}
|
|
3303
|
+
};
|
|
3304
|
+
function expandingSymbols(page, t, posing = true, times = /* @__PURE__ */ new Map(), memo) {
|
|
2324
3305
|
if (!hasInstances(page)) return page;
|
|
2325
3306
|
const budget = { left: MAX_ELEMENTS };
|
|
2326
|
-
|
|
3307
|
+
const m = memo && memo.page === page && memo.t === t && memo.posing === posing ? memo : void 0;
|
|
3308
|
+
return { ...page, elements: expand(page.elements, page, t, symbolTable(page), [], posing, budget, times, m) };
|
|
2327
3309
|
}
|
|
2328
3310
|
function symbolTable(page) {
|
|
2329
3311
|
const table = /* @__PURE__ */ new Map();
|
|
2330
3312
|
for (const s of page.symbols ?? []) if (!table.has(s.id)) table.set(s.id, s);
|
|
2331
3313
|
return table;
|
|
2332
3314
|
}
|
|
2333
|
-
function expand(elements, page, t, table, stack, posing, budget, times = /* @__PURE__ */ new Map()) {
|
|
3315
|
+
function expand(elements, page, t, table, stack, posing, budget, times = /* @__PURE__ */ new Map(), memo) {
|
|
2334
3316
|
const instances = new Set(elements.filter((e) => e.kind === "symbol").map((e) => e.id));
|
|
2335
3317
|
if (instances.size === 0) {
|
|
2336
3318
|
budget.left -= elements.length;
|
|
@@ -2355,6 +3337,14 @@ function expand(elements, page, t, table, stack, posing, budget, times = /* @__P
|
|
|
2355
3337
|
budget.left -= 1;
|
|
2356
3338
|
continue;
|
|
2357
3339
|
}
|
|
3340
|
+
const override0 = stack.length === 0 ? times.get(e.id) : void 0;
|
|
3341
|
+
const was = memo?.made.get(e);
|
|
3342
|
+
if (was && was.override === override0 && was.after > 0 && budget.left - (was.before - was.after) > 0) {
|
|
3343
|
+
budget.left -= was.before - was.after;
|
|
3344
|
+
for (const x of was.out) out.push(x);
|
|
3345
|
+
continue;
|
|
3346
|
+
}
|
|
3347
|
+
const before = budget.left, from = out.length;
|
|
2358
3348
|
budget.left -= 1;
|
|
2359
3349
|
const id = e.symbol;
|
|
2360
3350
|
const symbol = id === null ? void 0 : table.get(id);
|
|
@@ -2370,6 +3360,7 @@ function expand(elements, page, t, table, stack, posing, budget, times = /* @__P
|
|
|
2370
3360
|
layers: [{ id: e.layerID, name: "", visible: true, locked: false }]
|
|
2371
3361
|
};
|
|
2372
3362
|
for (const x of expand(drawn.elements, drawn, u, table, [...stack, id], posing, budget)) out.push(placed(x, e, symbol));
|
|
3363
|
+
if (memo && stack.length === 0 && !was) memo.made.set(e, { override: override0, before, after: budget.left, out: out.slice(from) });
|
|
2373
3364
|
}
|
|
2374
3365
|
return out;
|
|
2375
3366
|
}
|
|
@@ -2422,8 +3413,8 @@ function mix2(x) {
|
|
|
2422
3413
|
return z ^ z >> 31n;
|
|
2423
3414
|
}
|
|
2424
3415
|
function halves(id) {
|
|
2425
|
-
const
|
|
2426
|
-
return [BigInt(`0x${
|
|
3416
|
+
const hex2 = id.replace(/-/g, "").padStart(32, "0").slice(-32);
|
|
3417
|
+
return [BigInt(`0x${hex2.slice(0, 16)}`), BigInt(`0x${hex2.slice(16)}`)];
|
|
2427
3418
|
}
|
|
2428
3419
|
function derivedID(instance, inner) {
|
|
2429
3420
|
const [a, b] = halves(instance), [c, d] = halves(inner);
|
|
@@ -2444,7 +3435,7 @@ function clickTargets(page, file) {
|
|
|
2444
3435
|
let pageOf = null;
|
|
2445
3436
|
for (const e of page.elements) {
|
|
2446
3437
|
const c = e.click;
|
|
2447
|
-
const state = c && changesState(c) ? { ...c.set ? { set: c.set } : {}, ...c.toggle ? { toggle: c.toggle } : {} } : null;
|
|
3438
|
+
const state = c && changesState(c) ? { ...c.set ? { set: c.set } : {}, ...c.toggle ? { toggle: c.toggle } : {}, ...c.reset ? { reset: true } : {} } : null;
|
|
2448
3439
|
if (c?.scene) {
|
|
2449
3440
|
const i = sceneNamed(c.scene, markers);
|
|
2450
3441
|
if (i >= 0) {
|
|
@@ -2477,14 +3468,14 @@ function clickTargets(page, file) {
|
|
|
2477
3468
|
function hoveredElement(e) {
|
|
2478
3469
|
const h = e.hover;
|
|
2479
3470
|
if (!h) return e;
|
|
2480
|
-
const
|
|
2481
|
-
if (h.stroke)
|
|
2482
|
-
if (h.fill)
|
|
3471
|
+
const style2 = { ...e.style };
|
|
3472
|
+
if (h.stroke) style2.stroke = h.stroke;
|
|
3473
|
+
if (h.fill) style2.fill = h.fill;
|
|
2483
3474
|
if (h.opacity !== null && Number.isFinite(h.opacity)) {
|
|
2484
3475
|
const o = Math.min(Math.max(h.opacity, 0), 1);
|
|
2485
|
-
|
|
3476
|
+
style2.opacity = o >= 1 ? null : o;
|
|
2486
3477
|
}
|
|
2487
|
-
return { ...e, style };
|
|
3478
|
+
return { ...e, style: style2 };
|
|
2488
3479
|
}
|
|
2489
3480
|
function hovered(page, ids) {
|
|
2490
3481
|
return { ...page, elements: page.elements.map((e) => ids.has(e.id) ? hoveredElement(e) : e) };
|
|
@@ -2637,13 +3628,13 @@ function blocks(lines) {
|
|
|
2637
3628
|
flush();
|
|
2638
3629
|
return out;
|
|
2639
3630
|
}
|
|
2640
|
-
function parseList(lines,
|
|
2641
|
-
const first = reItem.exec(lines[
|
|
3631
|
+
function parseList(lines, start2) {
|
|
3632
|
+
const first = reItem.exec(lines[start2]);
|
|
2642
3633
|
const indent = first[1].length;
|
|
2643
3634
|
const bullet = /^\d/.test(first[2]) ? null : first[2];
|
|
2644
3635
|
const ordered = bullet === null ? parseInt(first[2], 10) : null;
|
|
2645
3636
|
const items = [];
|
|
2646
|
-
let i =
|
|
3637
|
+
let i = start2;
|
|
2647
3638
|
while (i < lines.length) {
|
|
2648
3639
|
const m = reItem.exec(lines[i]);
|
|
2649
3640
|
if (!m || m[1].length !== indent || bullet === null !== /^\d/.test(m[2]) || bullet !== null && m[2] !== bullet) break;
|
|
@@ -2688,14 +3679,14 @@ function parseList(lines, start) {
|
|
|
2688
3679
|
}
|
|
2689
3680
|
return { block: { kind: "list", ordered, items }, next: i };
|
|
2690
3681
|
}
|
|
2691
|
-
function inline(src,
|
|
3682
|
+
function inline(src, style2 = {}) {
|
|
2692
3683
|
const out = [];
|
|
2693
3684
|
const push = (text, s) => {
|
|
2694
3685
|
if (text) out.push({ ...s, text });
|
|
2695
3686
|
};
|
|
2696
3687
|
let i = 0, buf = "";
|
|
2697
3688
|
const emit = () => {
|
|
2698
|
-
push(buf,
|
|
3689
|
+
push(buf, style2);
|
|
2699
3690
|
buf = "";
|
|
2700
3691
|
};
|
|
2701
3692
|
while (i < src.length) {
|
|
@@ -2723,43 +3714,43 @@ function inline(src, style = {}) {
|
|
|
2723
3714
|
emit();
|
|
2724
3715
|
let code = m[2].replace(/\n/g, " ");
|
|
2725
3716
|
if (/^ .* $/.test(code)) code = code.slice(1, -1);
|
|
2726
|
-
push(code, { ...
|
|
3717
|
+
push(code, { ...style2, mono: true });
|
|
2727
3718
|
i += m[0].length;
|
|
2728
3719
|
continue;
|
|
2729
3720
|
}
|
|
2730
3721
|
if (m = /^!\[([^\]]*)\]\([^)]*\)/.exec(rest)) {
|
|
2731
3722
|
emit();
|
|
2732
|
-
out.push(...inline(m[1],
|
|
3723
|
+
out.push(...inline(m[1], style2));
|
|
2733
3724
|
i += m[0].length;
|
|
2734
3725
|
continue;
|
|
2735
3726
|
}
|
|
2736
3727
|
if (m = /^\[([^\]]+)\]\([^)]*\)/.exec(rest)) {
|
|
2737
3728
|
emit();
|
|
2738
|
-
out.push(...inline(m[1], { ...
|
|
3729
|
+
out.push(...inline(m[1], { ...style2, link: true }));
|
|
2739
3730
|
i += m[0].length;
|
|
2740
3731
|
continue;
|
|
2741
3732
|
}
|
|
2742
3733
|
if (m = /^<((?:https?|ftp|mailto):[^>\s]+)>/.exec(rest)) {
|
|
2743
3734
|
emit();
|
|
2744
|
-
push(m[1], { ...
|
|
3735
|
+
push(m[1], { ...style2, link: true });
|
|
2745
3736
|
i += m[0].length;
|
|
2746
3737
|
continue;
|
|
2747
3738
|
}
|
|
2748
3739
|
if (m = /^~~(?=\S)([\s\S]*?\S)~~/.exec(rest)) {
|
|
2749
3740
|
emit();
|
|
2750
|
-
out.push(...inline(m[1], { ...
|
|
3741
|
+
out.push(...inline(m[1], { ...style2, strike: true }));
|
|
2751
3742
|
i += m[0].length;
|
|
2752
3743
|
continue;
|
|
2753
3744
|
}
|
|
2754
3745
|
if (m = /^(\*\*|__)(?=\S)([\s\S]*?\S)\1/.exec(rest)) {
|
|
2755
3746
|
emit();
|
|
2756
|
-
out.push(...inline(m[2], { ...
|
|
3747
|
+
out.push(...inline(m[2], { ...style2, bold: true }));
|
|
2757
3748
|
i += m[0].length;
|
|
2758
3749
|
continue;
|
|
2759
3750
|
}
|
|
2760
3751
|
if ((m = /^(\*|_)(?=\S)([\s\S]*?\S)\1(?!\1)/.exec(rest)) && (m[1] === "*" || !/\w/.test(src[i - 1] ?? ""))) {
|
|
2761
3752
|
emit();
|
|
2762
|
-
out.push(...inline(m[2], { ...
|
|
3753
|
+
out.push(...inline(m[2], { ...style2, italic: true }));
|
|
2763
3754
|
i += m[0].length;
|
|
2764
3755
|
continue;
|
|
2765
3756
|
}
|
|
@@ -2972,10 +3963,10 @@ function layoutLines(paras, width, height, fontSize, fam) {
|
|
|
2972
3963
|
let k = 0;
|
|
2973
3964
|
let firstLine = true;
|
|
2974
3965
|
while (k < chars.length || firstLine && chars.length === 0) {
|
|
2975
|
-
const
|
|
3966
|
+
const start2 = firstLine ? p.firstIndent : p.indent;
|
|
2976
3967
|
const maxX2 = width;
|
|
2977
3968
|
const glyphs = [];
|
|
2978
|
-
let x =
|
|
3969
|
+
let x = start2, lastSpace = -1, end = k;
|
|
2979
3970
|
for (; end < chars.length; end++) {
|
|
2980
3971
|
const { ch, run } = chars[end];
|
|
2981
3972
|
const r = runs[run];
|
|
@@ -3024,7 +4015,7 @@ function layoutLines(paras, width, height, fontSize, fam) {
|
|
|
3024
4015
|
}
|
|
3025
4016
|
if (glyphs.length === 0 && end === k && k < chars.length) {
|
|
3026
4017
|
const { ch, run } = chars[k];
|
|
3027
|
-
glyphs.push({ ch, run, x:
|
|
4018
|
+
glyphs.push({ ch, run, x: start2, w: charWidth(ch, runs[run], fam) });
|
|
3028
4019
|
end = k + 1;
|
|
3029
4020
|
}
|
|
3030
4021
|
let asc = 0, desc = 0, lead = 0;
|
|
@@ -3050,13 +4041,38 @@ function layoutLines(paras, width, height, fontSize, fam) {
|
|
|
3050
4041
|
});
|
|
3051
4042
|
return lines;
|
|
3052
4043
|
}
|
|
4044
|
+
var Memo = class {
|
|
4045
|
+
constructor(limit) {
|
|
4046
|
+
__publicField(this, "map", /* @__PURE__ */ new Map());
|
|
4047
|
+
__publicField(this, "limit");
|
|
4048
|
+
this.limit = limit;
|
|
4049
|
+
}
|
|
4050
|
+
get(key, make2) {
|
|
4051
|
+
const hit = this.map.get(key);
|
|
4052
|
+
if (hit !== void 0) return hit;
|
|
4053
|
+
const v = make2();
|
|
4054
|
+
if (this.map.size >= this.limit) this.map.clear();
|
|
4055
|
+
this.map.set(key, v);
|
|
4056
|
+
return v;
|
|
4057
|
+
}
|
|
4058
|
+
};
|
|
4059
|
+
var layouts = new Memo(500);
|
|
4060
|
+
var markups = new Memo(500);
|
|
3053
4061
|
function noteMarkup(source, e) {
|
|
4062
|
+
const st = e.style, ink = st.stroke;
|
|
4063
|
+
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]);
|
|
4064
|
+
return markups.get(key, () => computeNoteMarkup(source, e));
|
|
4065
|
+
}
|
|
4066
|
+
function computeNoteMarkup(source, e) {
|
|
3054
4067
|
const rect2 = textRect({ x: e.x, y: e.y, width: e.width, height: e.height });
|
|
3055
4068
|
if (!(rect2.width > 1 && rect2.height > 1)) return "";
|
|
3056
4069
|
const fam = familyMetrics(e.style.font);
|
|
3057
|
-
const
|
|
3058
|
-
|
|
3059
|
-
|
|
4070
|
+
const layoutKey = JSON.stringify([...[rect2.width, rect2.height, e.style.fontSize].map(String), e.style.font ?? "", source]);
|
|
4071
|
+
const lines = layouts.get(layoutKey, () => {
|
|
4072
|
+
const b = new Builder(e.style.fontSize, rect2.width, fam);
|
|
4073
|
+
for (const block of parseBlocks(source)) b.block(block, { indent: 0, marker: null, quote: null, listDepth: 0 });
|
|
4074
|
+
return layoutLines(b.paras, rect2.width, rect2.height, e.style.fontSize, fam);
|
|
4075
|
+
});
|
|
3060
4076
|
const ink = color(e.style.stroke);
|
|
3061
4077
|
let s = "";
|
|
3062
4078
|
const marks = [];
|
|
@@ -3145,6 +4161,26 @@ function bounds(page, padding = 24) {
|
|
|
3145
4161
|
return inset(r, -padding, -padding);
|
|
3146
4162
|
}
|
|
3147
4163
|
function plan(page) {
|
|
4164
|
+
const hit = plans.get(page);
|
|
4165
|
+
if (hit && unchanged(page, hit.fields)) return hit.plan;
|
|
4166
|
+
const made = makePlan(page);
|
|
4167
|
+
plans.set(page, { fields: { ...page }, plan: made });
|
|
4168
|
+
return made;
|
|
4169
|
+
}
|
|
4170
|
+
var plans = /* @__PURE__ */ new WeakMap();
|
|
4171
|
+
function hasPlan(page) {
|
|
4172
|
+
const hit = plans.get(page);
|
|
4173
|
+
return !!hit && unchanged(page, hit.fields);
|
|
4174
|
+
}
|
|
4175
|
+
function rememberPlan(page, made) {
|
|
4176
|
+
plans.set(page, { fields: { ...page }, plan: made });
|
|
4177
|
+
}
|
|
4178
|
+
function unchanged(page, fields) {
|
|
4179
|
+
const a = page, b = fields;
|
|
4180
|
+
const keys = Object.keys(a);
|
|
4181
|
+
return keys.length === Object.keys(b).length && keys.every((k) => a[k] === b[k]);
|
|
4182
|
+
}
|
|
4183
|
+
function makePlan(page) {
|
|
3148
4184
|
const tl = page.timeline ?? { fps: 12, duration: 0, boil: null, settle: null, poster: null, tracks: [], camera: null, markers: null };
|
|
3149
4185
|
const times = frameTimes(tl);
|
|
3150
4186
|
const first = tl.camera?.find((k) => k.width > 1 && k.height > 1);
|
|
@@ -3172,8 +4208,8 @@ function svgOpen(b, background, paint) {
|
|
|
3172
4208
|
}
|
|
3173
4209
|
return out;
|
|
3174
4210
|
}
|
|
3175
|
-
function svgParts(page, background, fixed, links = false) {
|
|
3176
|
-
if (hasInstances(page)) return svgParts(expandingSymbols(page, 0), background, fixed, links);
|
|
4211
|
+
function svgParts(page, background, fixed, links = false, only) {
|
|
4212
|
+
if (hasInstances(page)) return svgParts(expandingSymbols(page, 0), background, fixed, links, only);
|
|
3177
4213
|
const theme = themeNamed(page.theme);
|
|
3178
4214
|
const bg = background ? theme?.background ?? background : null;
|
|
3179
4215
|
const ink = theme?.ink ?? black;
|
|
@@ -3184,11 +4220,11 @@ function svgParts(page, background, fixed, links = false) {
|
|
|
3184
4220
|
const index = indexOf(d);
|
|
3185
4221
|
const ordered = orderedElements(d);
|
|
3186
4222
|
const floors = ordered.filter((e) => visible.has(e.layerID) && isFloor(e));
|
|
3187
|
-
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"/>
|
|
4223
|
+
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"/>
|
|
3188
4224
|
`).join("");
|
|
3189
4225
|
if (rockMarkup) parts.push({ key: "rock", markup: rockMarkup });
|
|
3190
4226
|
for (const e of ordered) {
|
|
3191
|
-
if (!visible.has(e.layerID)) continue;
|
|
4227
|
+
if (!visible.has(e.layerID) || only && !only(e.id)) continue;
|
|
3192
4228
|
const alpha = Math.min(Math.max(e.style.opacity ?? 1, 0), 1);
|
|
3193
4229
|
if (alpha <= 0) continue;
|
|
3194
4230
|
let body = svgElement(e, index, bg, d);
|
|
@@ -3336,6 +4372,95 @@ function textElement(text, f, e, centered) {
|
|
|
3336
4372
|
return s + "</text>\n";
|
|
3337
4373
|
}
|
|
3338
4374
|
|
|
4375
|
+
// src/planner.ts
|
|
4376
|
+
var WORKER_WORK = 3e4;
|
|
4377
|
+
function planWork(page) {
|
|
4378
|
+
const tl = page.timeline;
|
|
4379
|
+
if (!tl || tl.camera?.some((k) => k.width > 1 && k.height > 1)) return 0;
|
|
4380
|
+
const elements = page.elements.length + (page.symbols ?? []).reduce((n, s) => n + s.elements.length, 0);
|
|
4381
|
+
return frameTimes(tl).length * elements;
|
|
4382
|
+
}
|
|
4383
|
+
function workerMaker() {
|
|
4384
|
+
if (typeof Worker === "undefined" || typeof __DOTTIE_WORKER__ === "undefined") return null;
|
|
4385
|
+
if (__DOTTIE_WORKER__ === "self" && typeof dottiePlayerScript === "function") {
|
|
4386
|
+
const source = `(${dottiePlayerScript.toString()})();
|
|
4387
|
+
`;
|
|
4388
|
+
return () => {
|
|
4389
|
+
const url = URL.createObjectURL(new Blob([source], { type: "text/javascript" }));
|
|
4390
|
+
try {
|
|
4391
|
+
return new Worker(url);
|
|
4392
|
+
} finally {
|
|
4393
|
+
setTimeout(() => URL.revokeObjectURL(url), 6e4);
|
|
4394
|
+
}
|
|
4395
|
+
};
|
|
4396
|
+
}
|
|
4397
|
+
if (__DOTTIE_WORKER__ === "sibling") {
|
|
4398
|
+
const here = import.meta.url;
|
|
4399
|
+
if (!here) return null;
|
|
4400
|
+
return () => new Worker(new URL("./dottie-player.min.js", here));
|
|
4401
|
+
}
|
|
4402
|
+
return null;
|
|
4403
|
+
}
|
|
4404
|
+
var make;
|
|
4405
|
+
var worker = null;
|
|
4406
|
+
var failed = false;
|
|
4407
|
+
var nextID = 1;
|
|
4408
|
+
var waiting = /* @__PURE__ */ new Map();
|
|
4409
|
+
function plansInWorker(page) {
|
|
4410
|
+
if (failed || hasPlan(page) || planWork(page) < WORKER_WORK) return false;
|
|
4411
|
+
if (make === void 0) make = workerMaker();
|
|
4412
|
+
return make !== null;
|
|
4413
|
+
}
|
|
4414
|
+
function planInWorker(page) {
|
|
4415
|
+
return new Promise((resolve2) => {
|
|
4416
|
+
const done = (p) => {
|
|
4417
|
+
if (p) rememberPlan(page, p);
|
|
4418
|
+
resolve2();
|
|
4419
|
+
};
|
|
4420
|
+
let w;
|
|
4421
|
+
try {
|
|
4422
|
+
w = worker ?? start();
|
|
4423
|
+
} catch {
|
|
4424
|
+
failed = true;
|
|
4425
|
+
done(null);
|
|
4426
|
+
return;
|
|
4427
|
+
}
|
|
4428
|
+
const id = nextID++;
|
|
4429
|
+
waiting.set(id, { page, done });
|
|
4430
|
+
try {
|
|
4431
|
+
w.postMessage({ dottiePlan: id, page });
|
|
4432
|
+
} catch {
|
|
4433
|
+
waiting.delete(id);
|
|
4434
|
+
done(null);
|
|
4435
|
+
}
|
|
4436
|
+
});
|
|
4437
|
+
}
|
|
4438
|
+
function start() {
|
|
4439
|
+
const w = make();
|
|
4440
|
+
w.onmessage = (ev) => {
|
|
4441
|
+
const { dottiePlan: id, plan: p } = ev.data ?? {};
|
|
4442
|
+
const job = id === void 0 ? void 0 : waiting.get(id);
|
|
4443
|
+
if (!job) return;
|
|
4444
|
+
waiting.delete(id);
|
|
4445
|
+
job.done(p ?? null);
|
|
4446
|
+
if (waiting.size === 0 && worker === w) {
|
|
4447
|
+
w.terminate();
|
|
4448
|
+
worker = null;
|
|
4449
|
+
}
|
|
4450
|
+
};
|
|
4451
|
+
w.onerror = (ev) => {
|
|
4452
|
+
ev.preventDefault?.();
|
|
4453
|
+
failed = true;
|
|
4454
|
+
if (worker === w) worker = null;
|
|
4455
|
+
w.terminate();
|
|
4456
|
+
const jobs = [...waiting.values()];
|
|
4457
|
+
waiting.clear();
|
|
4458
|
+
for (const job of jobs) job.done(null);
|
|
4459
|
+
};
|
|
4460
|
+
worker = w;
|
|
4461
|
+
return w;
|
|
4462
|
+
}
|
|
4463
|
+
|
|
3339
4464
|
// src/player.ts
|
|
3340
4465
|
var WHITE = { r: 1, g: 1, b: 1, a: 1 };
|
|
3341
4466
|
function pageIndex(file, spec) {
|
|
@@ -3376,6 +4501,12 @@ var Playback = class {
|
|
|
3376
4501
|
__publicField(this, "sceneLoops");
|
|
3377
4502
|
/** The page's variables now: their starting values until clicks change them. */
|
|
3378
4503
|
__publicField(this, "state");
|
|
4504
|
+
/** When the variables last changed (performance.now() milliseconds): backlog piles up from then (ADR 0003). */
|
|
4505
|
+
__publicField(this, "stateSince", typeof performance === "undefined" ? 0 : performance.now());
|
|
4506
|
+
/** A still (the `at` attribute): backlog is drawn for the page time, not the wall clock. */
|
|
4507
|
+
__publicField(this, "still", false);
|
|
4508
|
+
/** What each queue held when the variables last changed, which it drains from (FrameMetrics.carry). */
|
|
4509
|
+
__publicField(this, "carried", {});
|
|
3379
4510
|
__publicField(this, "background");
|
|
3380
4511
|
__publicField(this, "ink");
|
|
3381
4512
|
/** What clicking each clickable element does, by element id. */
|
|
@@ -3399,7 +4530,7 @@ var Playback = class {
|
|
|
3399
4530
|
__publicField(this, "order", []);
|
|
3400
4531
|
__publicField(this, "frame", -1);
|
|
3401
4532
|
__publicField(this, "onFrame", null);
|
|
3402
|
-
this.page = options.clean ?
|
|
4533
|
+
this.page = options.clean ? cleanCopy(page) : page;
|
|
3403
4534
|
addFonts(usedFonts(this.page));
|
|
3404
4535
|
this.plan = plan(this.page);
|
|
3405
4536
|
this.clicks = clickTargets(page, options.file);
|
|
@@ -3447,6 +4578,10 @@ var Playback = class {
|
|
|
3447
4578
|
this.svg.style.maxWidth = `${Math.ceil(this.plan.size.width)}px`;
|
|
3448
4579
|
into.replaceChildren(this.svg);
|
|
3449
4580
|
}
|
|
4581
|
+
/** Seconds since the variables last changed, or null for a still. */
|
|
4582
|
+
sinceChange(now = typeof performance === "undefined" ? 0 : performance.now()) {
|
|
4583
|
+
return this.still || typeof performance === "undefined" ? null : (now - this.stateSince) / 1e3;
|
|
4584
|
+
}
|
|
3450
4585
|
get frameCount() {
|
|
3451
4586
|
return this.plan.times.length;
|
|
3452
4587
|
}
|
|
@@ -3472,7 +4607,7 @@ var Playback = class {
|
|
|
3472
4607
|
}
|
|
3473
4608
|
/** Whether a copy is still playing through after a click. */
|
|
3474
4609
|
replaying(now = performance.now()) {
|
|
3475
|
-
for (const [id,
|
|
4610
|
+
for (const [id, start2] of this.replayStarts) if (now - start2 < (this.replays.get(id) ?? 0) * 1e3) return true;
|
|
3476
4611
|
return false;
|
|
3477
4612
|
}
|
|
3478
4613
|
/** Draws the last moment drawn again, for copies replaying while the page stands still. */
|
|
@@ -3485,17 +4620,24 @@ var Playback = class {
|
|
|
3485
4620
|
const r = this.plan.rects[Math.min(i, this.plan.rects.length - 1)];
|
|
3486
4621
|
const now = typeof performance === "undefined" ? 0 : performance.now();
|
|
3487
4622
|
const times = /* @__PURE__ */ new Map();
|
|
3488
|
-
for (const [id,
|
|
4623
|
+
for (const [id, start2] of this.replayStarts) times.set(id, Math.min((now - start2) / 1e3, this.replays.get(id) ?? 0));
|
|
3489
4624
|
const tc = clampTime(t);
|
|
3490
|
-
const pose = posed(this.page, tc, this.state);
|
|
3491
|
-
const
|
|
4625
|
+
const pose = posed(this.page, tc, this.state, this.sinceChange(now), this.carried);
|
|
4626
|
+
const made = new Expansions(pose, tc, true);
|
|
4627
|
+
const frame2 = expandingSymbols(pose, tc, true, times, made);
|
|
3492
4628
|
const { parts } = svgParts(frame2, this.background, r);
|
|
3493
4629
|
const owned = (m, parts2) => {
|
|
3494
4630
|
for (const p of parts2) if (this.owner.has(p.key)) m.set(p.key, p.markup);
|
|
3495
4631
|
return m;
|
|
3496
4632
|
};
|
|
3497
|
-
const state = (st) => st.size === 0 ? /* @__PURE__ */ new Map() : owned(/* @__PURE__ */ new Map(), svgParts(
|
|
3498
|
-
|
|
4633
|
+
const state = (st) => st.size === 0 ? /* @__PURE__ */ new Map() : owned(/* @__PURE__ */ new Map(), svgParts(
|
|
4634
|
+
expandingSymbols(pose, tc, true, new Map([...times, ...st]), made),
|
|
4635
|
+
this.background,
|
|
4636
|
+
r,
|
|
4637
|
+
false,
|
|
4638
|
+
(key) => st.has(this.owner.get(key) ?? "")
|
|
4639
|
+
).parts);
|
|
4640
|
+
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);
|
|
3499
4641
|
const over = state(this.overTimes), down = state(this.downTimes), hit = state(this.hitTimes);
|
|
3500
4642
|
const normal = owned(/* @__PURE__ */ new Map(), parts);
|
|
3501
4643
|
const merged = [];
|
|
@@ -3624,6 +4766,14 @@ ${h}</g>
|
|
|
3624
4766
|
return { frame: next, stop: entered >= 0 && !this.sceneLoops[entered] };
|
|
3625
4767
|
}
|
|
3626
4768
|
};
|
|
4769
|
+
var cleanCopies = /* @__PURE__ */ new WeakMap();
|
|
4770
|
+
function cleanCopy(page) {
|
|
4771
|
+
const hit = cleanCopies.get(page);
|
|
4772
|
+
if (hit && unchanged(page, hit.fields)) return hit.page;
|
|
4773
|
+
const clean = withRoughness(page, 0);
|
|
4774
|
+
cleanCopies.set(page, { fields: { ...page }, page: clean });
|
|
4775
|
+
return clean;
|
|
4776
|
+
}
|
|
3627
4777
|
var ElementBase = typeof HTMLElement === "undefined" ? class {
|
|
3628
4778
|
} : HTMLElement;
|
|
3629
4779
|
var DottiePlayer = class extends ElementBase {
|
|
@@ -3646,6 +4796,10 @@ var DottiePlayer = class extends ElementBase {
|
|
|
3646
4796
|
__publicField(this, "raf", 0);
|
|
3647
4797
|
__publicField(this, "replayRaf", 0);
|
|
3648
4798
|
__publicField(this, "loadToken", 0);
|
|
4799
|
+
/** Counts mounts, so a plan that comes back from a worker after a newer one is dropped. */
|
|
4800
|
+
__publicField(this, "mountToken", 0);
|
|
4801
|
+
/** The time a still shows; null while the drawing is animated. */
|
|
4802
|
+
__publicField(this, "stillTime", null);
|
|
3649
4803
|
__publicField(this, "visible", true);
|
|
3650
4804
|
__publicField(this, "observer", null);
|
|
3651
4805
|
this.root = this.attachShadow({ mode: "open" });
|
|
@@ -3722,9 +4876,19 @@ var DottiePlayer = class extends ElementBase {
|
|
|
3722
4876
|
text = inline2.textContent ?? "";
|
|
3723
4877
|
}
|
|
3724
4878
|
if (token !== this.loadToken) return;
|
|
3725
|
-
this.
|
|
4879
|
+
this.showFile(parseDottie(text));
|
|
3726
4880
|
} catch (err) {
|
|
3727
4881
|
if (token !== this.loadToken) return;
|
|
4882
|
+
this.setPlaying(false);
|
|
4883
|
+
cancelAnimationFrame(this.raf);
|
|
4884
|
+
cancelAnimationFrame(this.replayRaf);
|
|
4885
|
+
this.raf = 0;
|
|
4886
|
+
this.replayRaf = 0;
|
|
4887
|
+
this.playback = null;
|
|
4888
|
+
this.file = null;
|
|
4889
|
+
this.mountToken++;
|
|
4890
|
+
this.stillTime = null;
|
|
4891
|
+
this.bar.hidden = true;
|
|
3728
4892
|
this.stage.innerHTML = "";
|
|
3729
4893
|
const box = document.createElement("div");
|
|
3730
4894
|
box.className = "error";
|
|
@@ -3735,15 +4899,36 @@ var DottiePlayer = class extends ElementBase {
|
|
|
3735
4899
|
}
|
|
3736
4900
|
/** Shows an already-parsed file. */
|
|
3737
4901
|
setFile(file) {
|
|
4902
|
+
this.loadToken++;
|
|
4903
|
+
this.showFile(file);
|
|
4904
|
+
}
|
|
4905
|
+
showFile(file) {
|
|
3738
4906
|
this.file = file;
|
|
3739
4907
|
for (const w of file.warnings) console.warn(`dottie-player: ${w}`);
|
|
3740
|
-
this.mount();
|
|
3741
|
-
this.dispatchEvent(new CustomEvent("load", { detail: { file } }));
|
|
4908
|
+
this.mount(() => this.dispatchEvent(new CustomEvent("load", { detail: { file } })));
|
|
3742
4909
|
}
|
|
3743
|
-
|
|
4910
|
+
/** Shows the file's page; then `mounted`. A big animation is planned in a worker first (planner.ts), and what was
|
|
4911
|
+
* showing stays until it is ready; a newer mount, file or failed load in the meantime wins. */
|
|
4912
|
+
mount(mounted) {
|
|
3744
4913
|
const file = this.file;
|
|
3745
4914
|
const page = file.pages[pageIndex(file, this.getAttribute("page"))];
|
|
3746
|
-
const
|
|
4915
|
+
const clean = this.hasAttribute("clean");
|
|
4916
|
+
const token = ++this.mountToken;
|
|
4917
|
+
const shown = clean ? cleanCopy(page) : page;
|
|
4918
|
+
if (!plansInWorker(shown)) {
|
|
4919
|
+
this.mountPage(file, page, clean);
|
|
4920
|
+
mounted?.();
|
|
4921
|
+
return;
|
|
4922
|
+
}
|
|
4923
|
+
void planInWorker(shown).then(() => {
|
|
4924
|
+
if (token !== this.mountToken || this.file !== file) return;
|
|
4925
|
+
this.mountPage(file, page, clean);
|
|
4926
|
+
mounted?.();
|
|
4927
|
+
});
|
|
4928
|
+
}
|
|
4929
|
+
mountPage(file, page, clean) {
|
|
4930
|
+
const p = new Playback(page, this.stage, { clean, file });
|
|
4931
|
+
p.still = this.hasAttribute("at");
|
|
3747
4932
|
this.playback = p;
|
|
3748
4933
|
this.style.setProperty("--dottie-page", color(p.background));
|
|
3749
4934
|
this.style.setProperty("--dottie-ink", color(p.ink));
|
|
@@ -3765,10 +4950,14 @@ var DottiePlayer = class extends ElementBase {
|
|
|
3765
4950
|
this.dispatchEvent(new CustomEvent("frame", { detail: { frame: i, time: p.plan.times[i] } }));
|
|
3766
4951
|
};
|
|
3767
4952
|
if (!animated) {
|
|
3768
|
-
|
|
4953
|
+
const i = at !== null ? nearest(p, Number(at) || 0) : p.posterFrame;
|
|
4954
|
+
this.stillTime = at !== null ? Number(at) || 0 : posterTime(page);
|
|
4955
|
+
p.drawAt(this.stillTime, i);
|
|
4956
|
+
p.frame = i;
|
|
3769
4957
|
this.setPlaying(false);
|
|
3770
4958
|
return;
|
|
3771
4959
|
}
|
|
4960
|
+
this.stillTime = null;
|
|
3772
4961
|
p.show(0);
|
|
3773
4962
|
const reduced = typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
3774
4963
|
if (reduced) {
|
|
@@ -3782,8 +4971,10 @@ var DottiePlayer = class extends ElementBase {
|
|
|
3782
4971
|
get paused() {
|
|
3783
4972
|
return !this.playing;
|
|
3784
4973
|
}
|
|
4974
|
+
/** The moment shown: a still's own time (its `at`, or the poster), else the current frame's. */
|
|
3785
4975
|
get currentTime() {
|
|
3786
4976
|
const p = this.playback;
|
|
4977
|
+
if (p && this.stillTime !== null) return this.stillTime;
|
|
3787
4978
|
return p && p.frame >= 0 ? p.plan.times[p.frame] : 0;
|
|
3788
4979
|
}
|
|
3789
4980
|
set currentTime(t) {
|
|
@@ -3837,7 +5028,13 @@ var DottiePlayer = class extends ElementBase {
|
|
|
3837
5028
|
this.scheduleReplay();
|
|
3838
5029
|
}
|
|
3839
5030
|
if (!click) return;
|
|
3840
|
-
if (click.set || click.toggle
|
|
5031
|
+
if (click.set || click.toggle || click.reset) {
|
|
5032
|
+
this.setState(applyClick(
|
|
5033
|
+
{ scene: null, url: null, set: click.set ?? null, toggle: click.toggle ?? null, reset: click.reset ?? null },
|
|
5034
|
+
p.state,
|
|
5035
|
+
p.page.variables ?? {}
|
|
5036
|
+
));
|
|
5037
|
+
}
|
|
3841
5038
|
if ("state" in click) return;
|
|
3842
5039
|
if ("url" in click) {
|
|
3843
5040
|
if (/^(https?:|mailto:)/i.test(click.url)) window.open(click.url, "_blank", "noopener");
|
|
@@ -3863,7 +5060,10 @@ var DottiePlayer = class extends ElementBase {
|
|
|
3863
5060
|
if (!p) return;
|
|
3864
5061
|
const next = { ...p.state, ...values };
|
|
3865
5062
|
if (Object.keys(next).every((k) => next[k] === p.state[k])) return;
|
|
5063
|
+
const t = clampTime(p.last.t);
|
|
5064
|
+
p.carried = posedWithMetrics(p.page, t, p.state, p.sinceChange(), p.carried).metrics.carry;
|
|
3866
5065
|
p.state = next;
|
|
5066
|
+
p.stateSince = performance.now();
|
|
3867
5067
|
p.redraw();
|
|
3868
5068
|
this.dispatchEvent(new CustomEvent("state", { detail: { variables: { ...next } } }));
|
|
3869
5069
|
}
|
|
@@ -4061,7 +5261,7 @@ function render(source, options = {}) {
|
|
|
4061
5261
|
function isFile(v) {
|
|
4062
5262
|
return typeof v === "object" && v !== null && Array.isArray(v.pages) && Array.isArray(v.warnings);
|
|
4063
5263
|
}
|
|
4064
|
-
var VERSION = "0.
|
|
5264
|
+
var VERSION = "0.9.0";
|
|
4065
5265
|
export {
|
|
4066
5266
|
BOIL_DRAWINGS,
|
|
4067
5267
|
BUNDLED_FONTS,
|