@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-player.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 __export = (target, all) => {
|
|
@@ -71,10 +71,53 @@ var MAX_NAME = 64;
|
|
|
71
71
|
function isName(name) {
|
|
72
72
|
return name.length <= MAX_NAME && /^[A-Za-z_][A-Za-z0-9_]*$/.test(name);
|
|
73
73
|
}
|
|
74
|
+
var METRIC_NAMES = ["throughput", "load", "delay", "backlog", "depth", "inFlight", "transit", "backup"];
|
|
75
|
+
var OPS = [">=", "<=", "==", "!=", ">", "<"];
|
|
76
|
+
function metricTerm(term) {
|
|
77
|
+
const t = trim(term);
|
|
78
|
+
if (!t.includes("(")) {
|
|
79
|
+
const op2 = t.search(/[>=<!]/);
|
|
80
|
+
if (op2 < 0) return null;
|
|
81
|
+
const name2 = trim(t.slice(0, op2));
|
|
82
|
+
if (!METRIC_NAMES.includes(name2)) return null;
|
|
83
|
+
const m = metricTerm(name2 + "() " + t.slice(op2));
|
|
84
|
+
return m ? { ...m, target: "" } : null;
|
|
85
|
+
}
|
|
86
|
+
const open = t.indexOf("("), close = t.indexOf(")");
|
|
87
|
+
if (open < 0 || close < 0 || open >= close) return null;
|
|
88
|
+
const name = trim(t.slice(0, open)), target = trim(t.slice(open + 1, close));
|
|
89
|
+
let rest = trim(t.slice(close + 1));
|
|
90
|
+
if (!METRIC_NAMES.includes(name) || target.includes("(")) return null;
|
|
91
|
+
const op = OPS.find((o) => rest.startsWith(o));
|
|
92
|
+
if (op === void 0) return null;
|
|
93
|
+
rest = trim(rest.slice(op.length));
|
|
94
|
+
if (!/^-?[0-9]+(\.[0-9]+)?$/.test(rest)) return null;
|
|
95
|
+
return { metric: name, target, op, value: Number(rest) };
|
|
96
|
+
}
|
|
97
|
+
function passes(m, v) {
|
|
98
|
+
switch (m.op) {
|
|
99
|
+
case ">":
|
|
100
|
+
return v > m.value;
|
|
101
|
+
case ">=":
|
|
102
|
+
return v >= m.value;
|
|
103
|
+
case "<":
|
|
104
|
+
return v < m.value;
|
|
105
|
+
case "<=":
|
|
106
|
+
return v <= m.value;
|
|
107
|
+
case "==":
|
|
108
|
+
return v === m.value;
|
|
109
|
+
default:
|
|
110
|
+
return v !== m.value;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function mentionsMetric(condition) {
|
|
114
|
+
return condition.split("||").some((any) => any.split("&&").some((all) => metricTerm(all) !== null));
|
|
115
|
+
}
|
|
74
116
|
function conditionNames(condition) {
|
|
75
117
|
const out = [];
|
|
76
118
|
for (const any of condition.split("||")) {
|
|
77
119
|
for (const all of any.split("&&")) {
|
|
120
|
+
if (metricTerm(all) !== null) continue;
|
|
78
121
|
let term = trim(all);
|
|
79
122
|
if (term.startsWith("!")) term = trim(term.slice(1));
|
|
80
123
|
if (!isName(term)) return null;
|
|
@@ -83,9 +126,14 @@ function conditionNames(condition) {
|
|
|
83
126
|
}
|
|
84
127
|
return out;
|
|
85
128
|
}
|
|
86
|
-
function holds(condition, state) {
|
|
129
|
+
function holds(condition, state, metric = null) {
|
|
87
130
|
if (conditionNames(condition) === null) return false;
|
|
88
131
|
return condition.split("||").some((any) => any.split("&&").every((all) => {
|
|
132
|
+
const m = metricTerm(all);
|
|
133
|
+
if (m !== null) {
|
|
134
|
+
const v = metric ? metric(m) : null;
|
|
135
|
+
return v !== null && passes(m, v);
|
|
136
|
+
}
|
|
89
137
|
let term = trim(all);
|
|
90
138
|
let negated = false;
|
|
91
139
|
if (term.startsWith("!")) {
|
|
@@ -107,11 +155,40 @@ function stateHidden(page, choice) {
|
|
|
107
155
|
for (const e of page.elements) if (e.when !== null && !holds(e.when, values)) out.add(e.id);
|
|
108
156
|
return out;
|
|
109
157
|
}
|
|
158
|
+
function activeOverrides(e, values) {
|
|
159
|
+
if (!values || !e.overrides) return [];
|
|
160
|
+
return e.overrides.filter((o) => !mentionsMetric(o.when) && holds(o.when, values));
|
|
161
|
+
}
|
|
162
|
+
function looksOnly(o) {
|
|
163
|
+
return { ...o, reroute: null, offline: null, cost: null, capacity: null, delay: null };
|
|
164
|
+
}
|
|
165
|
+
function applyOverride(o, e) {
|
|
166
|
+
const out = cloneElement(e);
|
|
167
|
+
if (o.stroke) out.style.stroke = { ...o.stroke };
|
|
168
|
+
if (o.fill) out.style.fill = { ...o.fill };
|
|
169
|
+
if (o.textColor) out.style.textColor = { ...o.textColor };
|
|
170
|
+
if (o.strokeWidth !== null) out.style.strokeWidth = Math.max(0, o.strokeWidth);
|
|
171
|
+
if (o.dash !== null) out.style.dash = o.dash;
|
|
172
|
+
if (o.text !== null) out.text = o.text;
|
|
173
|
+
if (o.cost !== null && out.connector) out.connector.cost = Math.min(Math.max(o.cost, 0), 1e6);
|
|
174
|
+
if (o.capacity !== null) out.capacity = Math.max(o.capacity, 0);
|
|
175
|
+
if (o.delay !== null && out.kind !== "connector") out.delay = Math.max(o.delay, 0);
|
|
176
|
+
if (o.opacity !== null) {
|
|
177
|
+
const v = Math.min(Math.max(o.opacity, 0), 1) * (out.style.opacity ?? 1);
|
|
178
|
+
out.style.opacity = v >= 1 ? null : v;
|
|
179
|
+
}
|
|
180
|
+
return out;
|
|
181
|
+
}
|
|
110
182
|
function changesState(c) {
|
|
111
|
-
return !!c && (Object.keys(c.set ?? {}).length > 0 || (c.toggle ?? []).length > 0);
|
|
183
|
+
return !!c && (c.reset === true || Object.keys(c.set ?? {}).length > 0 || (c.toggle ?? []).length > 0);
|
|
112
184
|
}
|
|
113
|
-
function applyClick(c, state) {
|
|
114
|
-
|
|
185
|
+
function applyClick(c, state, initial = {}) {
|
|
186
|
+
let out = { ...state };
|
|
187
|
+
if (c.reset === true) {
|
|
188
|
+
out = {};
|
|
189
|
+
for (const k of Object.keys(state)) out[k] = false;
|
|
190
|
+
Object.assign(out, initial);
|
|
191
|
+
}
|
|
115
192
|
for (const [k, v] of Object.entries(c.set ?? {})) out[k] = v;
|
|
116
193
|
for (const k of c.toggle ?? []) out[k] = !(out[k] ?? false);
|
|
117
194
|
return out;
|
|
@@ -126,7 +203,9 @@ var KINDS = ["rectangle", "ellipse", "diamond", "text", "connector", "freehand",
|
|
|
126
203
|
var EASES = ["linear", "easeIn", "easeOut", "easeInOut", "hold", "spring", "bounce", "elastic"];
|
|
127
204
|
var KNOWN_FIELDS = {
|
|
128
205
|
file: ["version", "pages", "images"],
|
|
129
|
-
page: ["id", "name", "layers", "elements", "theme", "guidesX", "guidesY", "guidesLocked", "timeline", "symbols", "variables"],
|
|
206
|
+
page: ["id", "name", "layers", "elements", "theme", "guidesX", "guidesY", "guidesLocked", "timeline", "symbols", "variables", "flows"],
|
|
207
|
+
flow: ["id", "from", "to", "every", "speed", "color", "size", "when", "spread", "volume", "overrides"],
|
|
208
|
+
flowOverride: ["when", "volume"],
|
|
130
209
|
symbol: ["id", "name", "width", "height", "layers", "elements", "timeline", "button", "params"],
|
|
131
210
|
layer: ["id", "name", "visible", "locked"],
|
|
132
211
|
rgba: ["r", "g", "b", "a"],
|
|
@@ -155,6 +234,7 @@ var KNOWN_FIELDS = {
|
|
|
155
234
|
"click",
|
|
156
235
|
"hover",
|
|
157
236
|
"when",
|
|
237
|
+
"overrides",
|
|
158
238
|
"symbol",
|
|
159
239
|
"playback",
|
|
160
240
|
"offset",
|
|
@@ -163,8 +243,13 @@ var KNOWN_FIELDS = {
|
|
|
163
243
|
"reveal",
|
|
164
244
|
"ports",
|
|
165
245
|
"labelRect",
|
|
166
|
-
"extension"
|
|
246
|
+
"extension",
|
|
247
|
+
"capacity",
|
|
248
|
+
"delay",
|
|
249
|
+
"queue",
|
|
250
|
+
"gauge"
|
|
167
251
|
],
|
|
252
|
+
gauge: ["metric", "of", "max", "warn", "alarm", "style", "label"],
|
|
168
253
|
strokePoint: ["x", "y"],
|
|
169
254
|
subpath: ["closed", "coords"],
|
|
170
255
|
inlineImage: ["data", "format"],
|
|
@@ -172,7 +257,7 @@ var KNOWN_FIELDS = {
|
|
|
172
257
|
extensionRef: ["id", "shape", "version", "params"],
|
|
173
258
|
normalizedPoint: ["x", "y"],
|
|
174
259
|
normalizedRect: ["x", "y", "width", "height"],
|
|
175
|
-
connector: ["start", "end", "routing", "startHead", "endHead", "waypoints", "hops"],
|
|
260
|
+
connector: ["start", "end", "routing", "startHead", "endHead", "waypoints", "hops", "cost"],
|
|
176
261
|
waypoint: ["x", "y"],
|
|
177
262
|
timeline: ["fps", "duration", "boil", "settle", "poster", "tracks", "camera", "markers"],
|
|
178
263
|
track: ["element", "keys", "boil", "follow", "repeats", "march"],
|
|
@@ -181,8 +266,9 @@ var KNOWN_FIELDS = {
|
|
|
181
266
|
repeat: ["start", "end", "count"],
|
|
182
267
|
cameraKey: ["t", "x", "y", "width", "height", "ease"],
|
|
183
268
|
marker: ["t", "name", "loop"],
|
|
184
|
-
click: ["scene", "url", "set", "toggle"],
|
|
185
|
-
hover: ["stroke", "fill", "opacity"]
|
|
269
|
+
click: ["scene", "url", "set", "toggle", "reset"],
|
|
270
|
+
hover: ["stroke", "fill", "opacity"],
|
|
271
|
+
override: ["when", "stroke", "fill", "textColor", "opacity", "strokeWidth", "dash", "text", "reroute", "offline", "cost", "capacity", "delay"]
|
|
186
272
|
};
|
|
187
273
|
var MAX_DURATION = 3600;
|
|
188
274
|
var MAX_MARCH = 1e4;
|
|
@@ -198,6 +284,8 @@ var num = (v) => typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
|
198
284
|
var str = (v) => typeof v === "string" ? v : void 0;
|
|
199
285
|
var strings = (v) => isObj(v) && Object.values(v).every((x) => typeof x === "string") ? { ...v } : null;
|
|
200
286
|
var bool = (v) => typeof v === "boolean" ? v : void 0;
|
|
287
|
+
var NO_ID = "00000000-0000-0000-0000-000000000000";
|
|
288
|
+
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}$/;
|
|
201
289
|
var uuid = (v) => typeof v === "string" ? v.toUpperCase() : void 0;
|
|
202
290
|
function clampTime(t) {
|
|
203
291
|
if (!Number.isFinite(t)) return t === Infinity ? MAX_DURATION : 0;
|
|
@@ -289,7 +377,8 @@ var Reader = class {
|
|
|
289
377
|
this.fields(w, "waypoint");
|
|
290
378
|
return { x: num(w.x) ?? 0, y: num(w.y) ?? 0 };
|
|
291
379
|
}) : null,
|
|
292
|
-
hops: typeof v.hops === "boolean" ? v.hops : null
|
|
380
|
+
hops: typeof v.hops === "boolean" ? v.hops : null,
|
|
381
|
+
cost: typeof v.cost === "number" && Number.isFinite(v.cost) ? v.cost : null
|
|
293
382
|
};
|
|
294
383
|
}
|
|
295
384
|
image(v, table) {
|
|
@@ -341,6 +430,7 @@ var Reader = class {
|
|
|
341
430
|
click: this.click(v.click),
|
|
342
431
|
hover: this.hover(v.hover),
|
|
343
432
|
when: str(v.when) ?? null,
|
|
433
|
+
overrides: this.overrides(v.overrides),
|
|
344
434
|
symbol: uuid(v.symbol) ?? null,
|
|
345
435
|
playback: this.playback(v.playback),
|
|
346
436
|
offset: num(v.offset) ?? null,
|
|
@@ -352,7 +442,26 @@ var Reader = class {
|
|
|
352
442
|
return { x: num(o.x) ?? 0, y: num(o.y) ?? 0 };
|
|
353
443
|
}) : null,
|
|
354
444
|
labelRect: this.labelRect(v.labelRect),
|
|
355
|
-
extension: this.extension(v.extension)
|
|
445
|
+
extension: this.extension(v.extension),
|
|
446
|
+
capacity: num(v.capacity) ?? null,
|
|
447
|
+
delay: num(v.delay) ?? null,
|
|
448
|
+
queue: v.queue === true ? true : null,
|
|
449
|
+
gauge: this.gauge(v.gauge)
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
gauge(v) {
|
|
453
|
+
if (!isObj(v)) return null;
|
|
454
|
+
this.fields(v, "gauge");
|
|
455
|
+
const finite = (x) => typeof x === "number" && Number.isFinite(x) ? x : null;
|
|
456
|
+
const style2 = str(v.style);
|
|
457
|
+
return {
|
|
458
|
+
metric: str(v.metric) ?? "",
|
|
459
|
+
of: typeof v.of === "string" && UUID.test(v.of) ? v.of.toUpperCase() : NO_ID,
|
|
460
|
+
max: finite(v.max),
|
|
461
|
+
warn: finite(v.warn),
|
|
462
|
+
alarm: finite(v.alarm),
|
|
463
|
+
style: style2 === "number" || style2 === "bar" || style2 === "dial" ? style2 : null,
|
|
464
|
+
label: str(v.label) ?? null
|
|
356
465
|
};
|
|
357
466
|
}
|
|
358
467
|
labelRect(v) {
|
|
@@ -396,7 +505,7 @@ var Reader = class {
|
|
|
396
505
|
}
|
|
397
506
|
const raw = typeof v.toggle === "string" ? [v.toggle] : Array.isArray(v.toggle) && v.toggle.every((x) => typeof x === "string") ? v.toggle : null;
|
|
398
507
|
const toggle = raw ? raw.filter(isName) : null;
|
|
399
|
-
return { scene: str(v.scene) ?? null, url: safe, set, toggle: toggle && toggle.length > 0 ? toggle : null };
|
|
508
|
+
return { scene: str(v.scene) ?? null, url: safe, set, toggle: toggle && toggle.length > 0 ? toggle : null, reset: v.reset === true ? true : null };
|
|
400
509
|
}
|
|
401
510
|
playback(v) {
|
|
402
511
|
const s = str(v);
|
|
@@ -432,6 +541,35 @@ var Reader = class {
|
|
|
432
541
|
params: strings(v.params)
|
|
433
542
|
};
|
|
434
543
|
}
|
|
544
|
+
/** As DottieKit reads them: a reroute whose targets aren't all ids is dropped, as are keys that aren't ids. */
|
|
545
|
+
overrides(v) {
|
|
546
|
+
if (!Array.isArray(v)) return null;
|
|
547
|
+
return v.filter(isObj).map((o) => {
|
|
548
|
+
this.fields(o, "override");
|
|
549
|
+
const dash = str(o.dash);
|
|
550
|
+
let reroute = null;
|
|
551
|
+
if (isObj(o.reroute) && Object.values(o.reroute).every((x) => typeof x === "string" && UUID.test(x))) {
|
|
552
|
+
reroute = {};
|
|
553
|
+
for (const [k, x] of Object.entries(o.reroute)) if (UUID.test(k)) reroute[k.toUpperCase()] = x.toUpperCase();
|
|
554
|
+
if (Object.keys(reroute).length === 0) reroute = null;
|
|
555
|
+
}
|
|
556
|
+
return {
|
|
557
|
+
when: str(o.when) ?? "",
|
|
558
|
+
stroke: rgba(o.stroke) ?? null,
|
|
559
|
+
fill: rgba(o.fill) ?? null,
|
|
560
|
+
textColor: rgba(o.textColor) ?? null,
|
|
561
|
+
opacity: num(o.opacity) ?? null,
|
|
562
|
+
strokeWidth: num(o.strokeWidth) ?? null,
|
|
563
|
+
dash: dash !== void 0 && ["solid", "dashed", "dotted"].includes(dash) ? dash : null,
|
|
564
|
+
text: str(o.text) ?? null,
|
|
565
|
+
reroute,
|
|
566
|
+
offline: o.offline === true ? true : null,
|
|
567
|
+
cost: typeof o.cost === "number" && Number.isFinite(o.cost) ? o.cost : null,
|
|
568
|
+
capacity: typeof o.capacity === "number" && Number.isFinite(o.capacity) ? o.capacity : null,
|
|
569
|
+
delay: typeof o.delay === "number" && Number.isFinite(o.delay) ? o.delay : null
|
|
570
|
+
};
|
|
571
|
+
});
|
|
572
|
+
}
|
|
435
573
|
hover(v) {
|
|
436
574
|
if (!isObj(v)) return null;
|
|
437
575
|
this.fields(v, "hover");
|
|
@@ -471,15 +609,15 @@ var Reader = class {
|
|
|
471
609
|
const keys = (Array.isArray(v.keys) ? v.keys : []).map((k) => this.keyframe(k)).filter((k) => k !== null && k.t >= 0 && k.t <= MAX_DURATION);
|
|
472
610
|
stableSortBy(keys, (k) => k.t);
|
|
473
611
|
const follow = Array.isArray(v.follow) ? v.follow.filter(isObj).flatMap((f) => {
|
|
474
|
-
const path = uuid(f.path),
|
|
475
|
-
if (!path ||
|
|
476
|
-
return [{ path, start, end, ease: this.ease(f.ease), reverse: bool(f.reverse) ?? null, orient: bool(f.orient) ?? null, laps: num(f.laps) ?? null }];
|
|
612
|
+
const path = uuid(f.path), start2 = num(f.start), end = num(f.end);
|
|
613
|
+
if (!path || start2 === void 0 || end === void 0 || start2 < 0 || end > MAX_DURATION) return [];
|
|
614
|
+
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 }];
|
|
477
615
|
}) : null;
|
|
478
616
|
const repeats = Array.isArray(v.repeats) ? v.repeats.filter(isObj).flatMap((r) => {
|
|
479
|
-
const
|
|
480
|
-
if (
|
|
617
|
+
const start2 = num(r.start), end = num(r.end);
|
|
618
|
+
if (start2 === void 0 || end === void 0) return [];
|
|
481
619
|
const count = num(r.count);
|
|
482
|
-
const rep = { start, end, count: count === void 0 ? null : Math.trunc(count) };
|
|
620
|
+
const rep = { start: start2, end, count: count === void 0 ? null : Math.trunc(count) };
|
|
483
621
|
return repeatIsValid(rep) ? [rep] : [];
|
|
484
622
|
}) : null;
|
|
485
623
|
return { element, keys, boil: bool(v.boil) ?? null, follow, repeats, march: clampMarch(num(v.march)) };
|
|
@@ -531,7 +669,33 @@ var Reader = class {
|
|
|
531
669
|
theme: str(v.theme) ?? null,
|
|
532
670
|
timeline: this.timeline(v.timeline),
|
|
533
671
|
symbols: Array.isArray(v.symbols) ? v.symbols.map((x) => this.symbol(x, images)).filter((x) => x !== null) : null,
|
|
534
|
-
variables: this.variables(v.variables)
|
|
672
|
+
variables: this.variables(v.variables),
|
|
673
|
+
flows: Array.isArray(v.flows) ? v.flows.filter(isObj).map((f) => this.flow(f)) : null
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
/** As DottieKit reads one: a missing or bad id, source or destination sends nothing; bad destinations are dropped. */
|
|
677
|
+
flow(f) {
|
|
678
|
+
this.fields(f, "flow");
|
|
679
|
+
const id = (x) => typeof x === "string" && UUID.test(x) ? x.toUpperCase() : NO_ID;
|
|
680
|
+
const finite = (x) => {
|
|
681
|
+
const n = num(x);
|
|
682
|
+
return n !== void 0 && Number.isFinite(n) ? n : null;
|
|
683
|
+
};
|
|
684
|
+
return {
|
|
685
|
+
id: id(f.id),
|
|
686
|
+
from: id(f.from),
|
|
687
|
+
to: Array.isArray(f.to) && f.to.every((x) => typeof x === "string") ? f.to.filter((x) => UUID.test(x)).map((x) => x.toUpperCase()) : [],
|
|
688
|
+
every: finite(f.every),
|
|
689
|
+
speed: finite(f.speed),
|
|
690
|
+
size: finite(f.size),
|
|
691
|
+
color: isObj(f.color) && ["r", "g", "b", "a"].every((k) => typeof f.color[k] === "number") ? rgba(f.color) ?? null : null,
|
|
692
|
+
when: str(f.when) ?? null,
|
|
693
|
+
spread: f.spread === true ? true : null,
|
|
694
|
+
volume: finite(f.volume),
|
|
695
|
+
overrides: Array.isArray(f.overrides) && f.overrides.length > 0 ? f.overrides.filter(isObj).map((o) => {
|
|
696
|
+
this.fields(o, "flowOverride");
|
|
697
|
+
return { when: str(o.when) ?? "", volume: finite(o.volume) };
|
|
698
|
+
}) : null
|
|
535
699
|
};
|
|
536
700
|
}
|
|
537
701
|
};
|
|
@@ -593,7 +757,8 @@ function cloneElement(e) {
|
|
|
593
757
|
start: { ...e.connector.start },
|
|
594
758
|
end: { ...e.connector.end },
|
|
595
759
|
waypoints: e.connector.waypoints?.map((w) => ({ ...w })) ?? null
|
|
596
|
-
} : null
|
|
760
|
+
} : null,
|
|
761
|
+
gauge: e.gauge ? { ...e.gauge } : e.gauge
|
|
597
762
|
};
|
|
598
763
|
}
|
|
599
764
|
function orderedElements(page) {
|
|
@@ -677,6 +842,21 @@ function pieces(para) {
|
|
|
677
842
|
if (cur || out.length === 0) out.push(cur);
|
|
678
843
|
return out;
|
|
679
844
|
}
|
|
845
|
+
function prefixWidths(chars, fontSize, face, font) {
|
|
846
|
+
const fam = familyMetrics(font);
|
|
847
|
+
const f = fam.faces[face] ?? fam.faces.regular;
|
|
848
|
+
const fallback = f.w["n"];
|
|
849
|
+
const out = [0];
|
|
850
|
+
let w = 0;
|
|
851
|
+
let prev = "";
|
|
852
|
+
for (const ch of chars) {
|
|
853
|
+
w += f.w[ch] ?? fallback;
|
|
854
|
+
if (prev) w += f.k[prev + ch] ?? 0;
|
|
855
|
+
prev = ch;
|
|
856
|
+
out.push(w * fontSize / 1e3);
|
|
857
|
+
}
|
|
858
|
+
return out;
|
|
859
|
+
}
|
|
680
860
|
function wrap(s, fontSize, maxWidth, face = "regular", font) {
|
|
681
861
|
const width = (t) => measure(t, fontSize, face, font);
|
|
682
862
|
const out = [];
|
|
@@ -692,8 +872,9 @@ function wrap(s, fontSize, maxWidth, face = "regular", font) {
|
|
|
692
872
|
}
|
|
693
873
|
while (width(line2.trimEnd()) > maxWidth && [...line2.trimEnd()].length > 1) {
|
|
694
874
|
const chars = [...line2];
|
|
875
|
+
const prefix = prefixWidths(chars, fontSize, face, font);
|
|
695
876
|
let n = chars.length - 1;
|
|
696
|
-
while (n > 1 &&
|
|
877
|
+
while (n > 1 && prefix[n] > maxWidth) n--;
|
|
697
878
|
out.push(chars.slice(0, n).join(""));
|
|
698
879
|
line2 = chars.slice(n).join("");
|
|
699
880
|
}
|
|
@@ -848,13 +1029,13 @@ var Path = class _Path {
|
|
|
848
1029
|
contours() {
|
|
849
1030
|
const out = [];
|
|
850
1031
|
let current = [];
|
|
851
|
-
let
|
|
1032
|
+
let start2 = { x: 0, y: 0 };
|
|
852
1033
|
for (const s of this.segments) {
|
|
853
|
-
const last = current.length ? current[current.length - 1] :
|
|
1034
|
+
const last = current.length ? current[current.length - 1] : start2;
|
|
854
1035
|
switch (s.op) {
|
|
855
1036
|
case "M":
|
|
856
1037
|
if (current.length > 1) out.push(current);
|
|
857
|
-
|
|
1038
|
+
start2 = s.p;
|
|
858
1039
|
current = [s.p];
|
|
859
1040
|
break;
|
|
860
1041
|
case "L":
|
|
@@ -876,9 +1057,9 @@ var Path = class _Path {
|
|
|
876
1057
|
}
|
|
877
1058
|
break;
|
|
878
1059
|
case "Z":
|
|
879
|
-
current.push(
|
|
1060
|
+
current.push(start2);
|
|
880
1061
|
if (current.length > 1) out.push(current);
|
|
881
|
-
current = [
|
|
1062
|
+
current = [start2];
|
|
882
1063
|
break;
|
|
883
1064
|
}
|
|
884
1065
|
}
|
|
@@ -1270,9 +1451,17 @@ function midpoint(e, index) {
|
|
|
1270
1451
|
}
|
|
1271
1452
|
return pts[0];
|
|
1272
1453
|
}
|
|
1454
|
+
var indexes = /* @__PURE__ */ new WeakMap();
|
|
1455
|
+
function isIndexOf(page, index) {
|
|
1456
|
+
return indexes.get(page.elements)?.index === index;
|
|
1457
|
+
}
|
|
1273
1458
|
function indexOf(page) {
|
|
1459
|
+
const els = page.elements;
|
|
1460
|
+
const hit = indexes.get(els);
|
|
1461
|
+
if (hit && hit.members.length === els.length && hit.members.every((e, i) => e === els[i])) return hit.index;
|
|
1274
1462
|
const m = /* @__PURE__ */ new Map();
|
|
1275
|
-
for (const e of
|
|
1463
|
+
for (const e of els) if (!m.has(e.id)) m.set(e.id, e);
|
|
1464
|
+
indexes.set(els, { members: [...els], index: m });
|
|
1276
1465
|
return m;
|
|
1277
1466
|
}
|
|
1278
1467
|
|
|
@@ -1344,8 +1533,712 @@ function mulConst(step) {
|
|
|
1344
1533
|
return [p[0], p[1]];
|
|
1345
1534
|
}
|
|
1346
1535
|
|
|
1536
|
+
// src/flows.ts
|
|
1537
|
+
var DEFAULT_EVERY = 0.5;
|
|
1538
|
+
var DEFAULT_SPEED = 160;
|
|
1539
|
+
var DEFAULT_SIZE = 12;
|
|
1540
|
+
var DEFAULT_COLOR = { r: 0.95, g: 0.36, b: 0.33, a: 1 };
|
|
1541
|
+
var clamp = (v, fallback, lo, hi) => v === null || !Number.isFinite(v) ? fallback : Math.min(Math.max(v, lo), hi);
|
|
1542
|
+
function packetID(flow, k) {
|
|
1543
|
+
const n = (k + 2 ** 40) % 2 ** 48;
|
|
1544
|
+
return flow.slice(0, 24) + (n < 0 ? n + 2 ** 48 : n).toString(16).toUpperCase().padStart(12, "0");
|
|
1545
|
+
}
|
|
1546
|
+
var routeCost = (cost) => cost === null || !Number.isFinite(cost) ? 1 : Math.min(Math.max(cost, 0), 1e6);
|
|
1547
|
+
var graphs = /* @__PURE__ */ new WeakMap();
|
|
1548
|
+
var networks = /* @__PURE__ */ new Map();
|
|
1549
|
+
function graphOf(frame2) {
|
|
1550
|
+
const els = frame2.elements;
|
|
1551
|
+
const hit = graphs.get(els);
|
|
1552
|
+
if (hit && hit.members.length === els.length && hit.members.every((e, i) => e === els[i])) return hit.graph;
|
|
1553
|
+
const shapes2 = new Set(els.filter((e) => e.kind !== "connector").map((e) => e.id));
|
|
1554
|
+
const lines = [];
|
|
1555
|
+
for (const e of els) {
|
|
1556
|
+
const c = e.connector;
|
|
1557
|
+
if (c) lines.push([e.id, c.start.elementID, c.end.elementID, String(routeCost(c.cost)), c.startHead !== "none" ? "both" : "one"]);
|
|
1558
|
+
}
|
|
1559
|
+
const key = JSON.stringify([[...shapes2], lines]);
|
|
1560
|
+
let graph = networks.get(key);
|
|
1561
|
+
if (!graph) {
|
|
1562
|
+
const next = /* @__PURE__ */ new Map();
|
|
1563
|
+
const add2 = (a, edge) => {
|
|
1564
|
+
const list = next.get(a);
|
|
1565
|
+
if (list) list.push(edge);
|
|
1566
|
+
else next.set(a, [edge]);
|
|
1567
|
+
};
|
|
1568
|
+
for (const e of els) {
|
|
1569
|
+
const c = e.connector;
|
|
1570
|
+
const a = c?.start.elementID ?? null, b = c?.end.elementID ?? null;
|
|
1571
|
+
if (!c || a === null || b === null || a === b || !shapes2.has(a) || !shapes2.has(b)) continue;
|
|
1572
|
+
const cost = routeCost(c.cost);
|
|
1573
|
+
add2(a, { to: b, via: e.id, back: false, cost });
|
|
1574
|
+
if (c.startHead !== "none") add2(b, { to: a, via: e.id, back: true, cost });
|
|
1575
|
+
}
|
|
1576
|
+
graph = { shapes: shapes2, next, trees: /* @__PURE__ */ new Map() };
|
|
1577
|
+
if (networks.size >= 16) networks.clear();
|
|
1578
|
+
networks.set(key, graph);
|
|
1579
|
+
}
|
|
1580
|
+
graphs.set(els, { members: [...els], graph });
|
|
1581
|
+
return graph;
|
|
1582
|
+
}
|
|
1583
|
+
var Frontier = class {
|
|
1584
|
+
constructor() {
|
|
1585
|
+
__publicField(this, "heap", []);
|
|
1586
|
+
}
|
|
1587
|
+
get size() {
|
|
1588
|
+
return this.heap.length;
|
|
1589
|
+
}
|
|
1590
|
+
less(i, j) {
|
|
1591
|
+
const a = this.heap[i], b = this.heap[j];
|
|
1592
|
+
return a.dist < b.dist || a.dist === b.dist && a.order < b.order;
|
|
1593
|
+
}
|
|
1594
|
+
swap(i, j) {
|
|
1595
|
+
[this.heap[i], this.heap[j]] = [this.heap[j], this.heap[i]];
|
|
1596
|
+
}
|
|
1597
|
+
push(id, dist, order) {
|
|
1598
|
+
this.heap.push({ id, dist, order });
|
|
1599
|
+
for (let i = this.heap.length - 1; i > 0; ) {
|
|
1600
|
+
const up = i - 1 >> 1;
|
|
1601
|
+
if (!this.less(i, up)) break;
|
|
1602
|
+
this.swap(i, up);
|
|
1603
|
+
i = up;
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
pop() {
|
|
1607
|
+
const top = this.heap[0], last = this.heap.pop();
|
|
1608
|
+
if (this.heap.length > 0) {
|
|
1609
|
+
this.heap[0] = last;
|
|
1610
|
+
for (let i = 0; ; ) {
|
|
1611
|
+
const l = 2 * i + 1, r = l + 1;
|
|
1612
|
+
let m = i;
|
|
1613
|
+
if (l < this.heap.length && this.less(l, m)) m = l;
|
|
1614
|
+
if (r < this.heap.length && this.less(r, m)) m = r;
|
|
1615
|
+
if (m === i) break;
|
|
1616
|
+
this.swap(i, m);
|
|
1617
|
+
i = m;
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
return top;
|
|
1621
|
+
}
|
|
1622
|
+
};
|
|
1623
|
+
function flowTree(from, frame2, offline = /* @__PURE__ */ new Set(), until) {
|
|
1624
|
+
const graph = graphOf(frame2);
|
|
1625
|
+
const key = JSON.stringify([from, until ?? null, [...offline].sort()]);
|
|
1626
|
+
if (graph.trees.has(key)) return graph.trees.get(key);
|
|
1627
|
+
const tree = grow(graph, from, offline, until);
|
|
1628
|
+
if (graph.trees.size >= 4096) graph.trees.clear();
|
|
1629
|
+
graph.trees.set(key, tree);
|
|
1630
|
+
return tree;
|
|
1631
|
+
}
|
|
1632
|
+
function grow({ shapes: shapes2, next }, from, offline, until) {
|
|
1633
|
+
if (!shapes2.has(from) || offline.has(from)) return null;
|
|
1634
|
+
const dist = /* @__PURE__ */ new Map([[from, 0]]), order = /* @__PURE__ */ new Map([[from, 0]]);
|
|
1635
|
+
let counter = 1;
|
|
1636
|
+
const tree = new FlowTree(from);
|
|
1637
|
+
const open = new Frontier();
|
|
1638
|
+
open.push(from, 0, 0);
|
|
1639
|
+
while (open.size > 0) {
|
|
1640
|
+
const { id: n, order: o } = open.pop();
|
|
1641
|
+
if (tree.settled.has(n) || order.get(n) !== o) continue;
|
|
1642
|
+
tree.settled.add(n);
|
|
1643
|
+
if (n === until && n !== from) break;
|
|
1644
|
+
for (const edge of next.get(n) ?? []) {
|
|
1645
|
+
if (offline.has(edge.via) || offline.has(edge.to)) continue;
|
|
1646
|
+
if (tree.settled.has(edge.to)) continue;
|
|
1647
|
+
const d = dist.get(n) + edge.cost;
|
|
1648
|
+
const old = dist.get(edge.to);
|
|
1649
|
+
if (old !== void 0 && old <= d) continue;
|
|
1650
|
+
dist.set(edge.to, d);
|
|
1651
|
+
order.set(edge.to, counter);
|
|
1652
|
+
open.push(edge.to, d, counter++);
|
|
1653
|
+
tree.came.set(edge.to, { from: n, via: edge.via, back: edge.back });
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
return tree;
|
|
1657
|
+
}
|
|
1658
|
+
var FlowTree = class {
|
|
1659
|
+
constructor(from) {
|
|
1660
|
+
__publicField(this, "settled", /* @__PURE__ */ new Set());
|
|
1661
|
+
__publicField(this, "came", /* @__PURE__ */ new Map());
|
|
1662
|
+
__publicField(this, "from");
|
|
1663
|
+
this.from = from;
|
|
1664
|
+
}
|
|
1665
|
+
/** The first of `to` that can be reached; with `spread`, when the first can't be, every other one that can. */
|
|
1666
|
+
goals(to, spread) {
|
|
1667
|
+
const reachable = to.filter((x) => x !== this.from && this.settled.has(x));
|
|
1668
|
+
if (reachable.length === 0) return [];
|
|
1669
|
+
return spread && reachable[0] !== to[0] ? reachable : [reachable[0]];
|
|
1670
|
+
}
|
|
1671
|
+
/** The connectors to `goal`, as [connector, backwards] hops. */
|
|
1672
|
+
hops(goal) {
|
|
1673
|
+
const hops = [];
|
|
1674
|
+
for (let at = goal, step = this.came.get(at); step; at = step.from, step = this.came.get(at)) hops.push([step.via, step.back]);
|
|
1675
|
+
return hops.reverse();
|
|
1676
|
+
}
|
|
1677
|
+
};
|
|
1678
|
+
|
|
1679
|
+
// src/measure.ts
|
|
1680
|
+
var emptyMetrics = () => ({ elements: /* @__PURE__ */ new Map(), flows: /* @__PURE__ */ new Map(), carry: {} });
|
|
1681
|
+
function metricValue(m, metric, id) {
|
|
1682
|
+
const f = m.flows.get(id);
|
|
1683
|
+
if (f) {
|
|
1684
|
+
switch (metric) {
|
|
1685
|
+
case "throughput":
|
|
1686
|
+
return f.placed;
|
|
1687
|
+
case "backlog":
|
|
1688
|
+
return f.backlog;
|
|
1689
|
+
case "backup":
|
|
1690
|
+
return f.backup;
|
|
1691
|
+
case "transit":
|
|
1692
|
+
return f.transit;
|
|
1693
|
+
default:
|
|
1694
|
+
return null;
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
const e = m.elements.get(id);
|
|
1698
|
+
if (!e) return null;
|
|
1699
|
+
switch (metric) {
|
|
1700
|
+
case "throughput":
|
|
1701
|
+
return e.throughput;
|
|
1702
|
+
case "load":
|
|
1703
|
+
return e.load;
|
|
1704
|
+
case "delay":
|
|
1705
|
+
return e.delay;
|
|
1706
|
+
case "backlog":
|
|
1707
|
+
return e.backlog;
|
|
1708
|
+
case "inFlight":
|
|
1709
|
+
return e.inFlight;
|
|
1710
|
+
case "depth":
|
|
1711
|
+
return e.depth;
|
|
1712
|
+
default:
|
|
1713
|
+
return null;
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
var TINY = 1e-9;
|
|
1717
|
+
var amount = (f) => clamp(f.volume, 1, 0, 1e12);
|
|
1718
|
+
function amountIn(f, values) {
|
|
1719
|
+
if (values === null || !f.overrides) return amount(f);
|
|
1720
|
+
let v = null;
|
|
1721
|
+
for (const o of f.overrides) if (o.volume !== null && !mentionsMetric(o.when) && holds(o.when, values)) v = o.volume;
|
|
1722
|
+
return v === null ? amount(f) : Math.min(Math.max(Number.isFinite(v) ? v : 1, 0), 1e12);
|
|
1723
|
+
}
|
|
1724
|
+
function delayOf(e) {
|
|
1725
|
+
const d = e.delay ?? null;
|
|
1726
|
+
if (e.kind === "connector" || d === null || !Number.isFinite(d)) return 0;
|
|
1727
|
+
return Math.min(Math.max(d, 0), 3600);
|
|
1728
|
+
}
|
|
1729
|
+
var queueKey = (shape, flow) => shape + "|" + flow;
|
|
1730
|
+
function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set(), sinceChange = null, carried = {}) {
|
|
1731
|
+
const since = Math.max(0, Math.min(sinceChange ?? t, 1e6));
|
|
1732
|
+
const byID = indexOf(frame2);
|
|
1733
|
+
const traced = /* @__PURE__ */ new Map();
|
|
1734
|
+
const traceOf = (c) => {
|
|
1735
|
+
let pts = traced.get(c.id);
|
|
1736
|
+
if (!pts) {
|
|
1737
|
+
pts = tracing(c, frame2, byID).points;
|
|
1738
|
+
traced.set(c.id, pts);
|
|
1739
|
+
}
|
|
1740
|
+
return pts;
|
|
1741
|
+
};
|
|
1742
|
+
const metrics2 = emptyMetrics();
|
|
1743
|
+
for (const e of frame2.elements) {
|
|
1744
|
+
if (e.kind === "connector" && !e.connector) continue;
|
|
1745
|
+
metrics2.elements.set(e.id, { throughput: 0, load: null, delay: e.kind !== "connector" ? delayOf(e) : 0, backlog: 0, inFlight: 0, depth: 0 });
|
|
1746
|
+
}
|
|
1747
|
+
const list = flows ?? [];
|
|
1748
|
+
const room = /* @__PURE__ */ new Map();
|
|
1749
|
+
for (const e of frame2.elements) {
|
|
1750
|
+
const c = e.capacity ?? null;
|
|
1751
|
+
if (c !== null && Number.isFinite(c)) room.set(e.id, Math.max(c, 0));
|
|
1752
|
+
}
|
|
1753
|
+
const full = () => new Set([...room].filter(([, v]) => v <= TINY).map(([id]) => id));
|
|
1754
|
+
const route2 = (tree, goal) => {
|
|
1755
|
+
const hops = [], shapes2 = [];
|
|
1756
|
+
let at = goal;
|
|
1757
|
+
for (let step = tree.came.get(at); step; step = tree.came.get(at)) {
|
|
1758
|
+
hops.push([step.via, step.back]);
|
|
1759
|
+
shapes2.push(at);
|
|
1760
|
+
at = step.from;
|
|
1761
|
+
}
|
|
1762
|
+
shapes2.push(at);
|
|
1763
|
+
return { hops: hops.reverse(), shapes: shapes2.reverse() };
|
|
1764
|
+
};
|
|
1765
|
+
const way = (hops, shapes2) => {
|
|
1766
|
+
const points = [], inside = [], stretches = [];
|
|
1767
|
+
let length = 0;
|
|
1768
|
+
hops.forEach(([id, back], k) => {
|
|
1769
|
+
const c = byID.get(id);
|
|
1770
|
+
if (!c) return;
|
|
1771
|
+
const line2 = back ? [...traceOf(c)].reverse() : traceOf(c);
|
|
1772
|
+
if (line2.length === 0) return;
|
|
1773
|
+
const last = points[points.length - 1];
|
|
1774
|
+
if (last) {
|
|
1775
|
+
const gap = Math.hypot(line2[0].x - last.x, line2[0].y - last.y);
|
|
1776
|
+
const shape = byID.get(shapes2[k]);
|
|
1777
|
+
inside.push([length, length + gap, shape ? delayOf(shape) : 0]);
|
|
1778
|
+
length += gap;
|
|
1779
|
+
}
|
|
1780
|
+
const start2 = length;
|
|
1781
|
+
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);
|
|
1782
|
+
stretches.push([id, start2, length]);
|
|
1783
|
+
points.push(...line2);
|
|
1784
|
+
});
|
|
1785
|
+
return length > 0 ? { line: new Polyline(points), length, inside, stretches } : null;
|
|
1786
|
+
};
|
|
1787
|
+
const along = (w, elapsedAt, speed) => {
|
|
1788
|
+
let elapsed = elapsedAt, d = 0;
|
|
1789
|
+
for (const [a, b, delay] of w.inside) {
|
|
1790
|
+
const reach = (a - d) / speed;
|
|
1791
|
+
if (elapsed <= reach) break;
|
|
1792
|
+
elapsed -= reach;
|
|
1793
|
+
d = a;
|
|
1794
|
+
const stay = (b - a) / speed + delay;
|
|
1795
|
+
if (elapsed < stay) return null;
|
|
1796
|
+
elapsed -= stay;
|
|
1797
|
+
d = b;
|
|
1798
|
+
}
|
|
1799
|
+
d += elapsed * speed;
|
|
1800
|
+
return d >= 0 && d <= w.length ? d : null;
|
|
1801
|
+
};
|
|
1802
|
+
const union2 = (a, b) => /* @__PURE__ */ new Set([...a, ...b]);
|
|
1803
|
+
const bump = (id, f) => {
|
|
1804
|
+
const m = metrics2.elements.get(id);
|
|
1805
|
+
if (m) f(m);
|
|
1806
|
+
};
|
|
1807
|
+
const leaving = /* @__PURE__ */ new Map(), turn = [];
|
|
1808
|
+
for (const flow of list) {
|
|
1809
|
+
const n = leaving.get(flow.from) ?? 0;
|
|
1810
|
+
turn.push(n);
|
|
1811
|
+
leaving.set(flow.from, n + 1);
|
|
1812
|
+
}
|
|
1813
|
+
const biggest = list.length > 0 ? Math.max(...list.map(amount)) : 1;
|
|
1814
|
+
const packets = [];
|
|
1815
|
+
const waitingBy = /* @__PURE__ */ new Map();
|
|
1816
|
+
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);
|
|
1817
|
+
const bases = /* @__PURE__ */ new Map();
|
|
1818
|
+
const baseTree = (from) => {
|
|
1819
|
+
if (!bases.has(from)) bases.set(from, flowTree(from, frame2, offline));
|
|
1820
|
+
return bases.get(from);
|
|
1821
|
+
};
|
|
1822
|
+
for (const [i, flow] of list.entries()) {
|
|
1823
|
+
if (flow.when !== null && values !== null && !holds(flow.when, values)) continue;
|
|
1824
|
+
const source = byID.get(flow.from);
|
|
1825
|
+
const base2 = source ? baseTree(flow.from) : null;
|
|
1826
|
+
if (!source || !base2) continue;
|
|
1827
|
+
const volume = amountIn(flow, values);
|
|
1828
|
+
const fm = { volume, placed: 0, backlog: 0, backup: 0, transit: null };
|
|
1829
|
+
const placements = [];
|
|
1830
|
+
const waiting2 = [];
|
|
1831
|
+
const goals = base2.goals(flow.to, flow.spread === true);
|
|
1832
|
+
const shares = goals.length > 1 ? goals.map((g) => [[g], volume / goals.length]) : [[flow.to, volume]];
|
|
1833
|
+
for (const [candidates, share] of shares) {
|
|
1834
|
+
if (!(share > TINY)) continue;
|
|
1835
|
+
let left = share;
|
|
1836
|
+
for (let n = 0; n < 32 && left > TINY; n++) {
|
|
1837
|
+
const tree = flowTree(flow.from, frame2, union2(offline, full()), candidates[0]);
|
|
1838
|
+
const goal2 = tree?.goals(candidates, false)[0];
|
|
1839
|
+
if (!tree || goal2 === void 0) break;
|
|
1840
|
+
const r = route2(tree, goal2);
|
|
1841
|
+
const ids = [...r.shapes, ...r.hops.map(([id]) => id)];
|
|
1842
|
+
const rooms = ids.filter((id) => room.has(id)).map((id) => room.get(id));
|
|
1843
|
+
const fits = Math.min(left, rooms.length > 0 ? Math.min(...rooms) : Infinity);
|
|
1844
|
+
if (!(fits > TINY)) break;
|
|
1845
|
+
for (const id of ids) if (room.has(id)) room.set(id, room.get(id) - fits);
|
|
1846
|
+
placements.push({ hops: r.hops, shapes: r.shapes, goal: goal2, amount: fits });
|
|
1847
|
+
left -= fits;
|
|
1848
|
+
}
|
|
1849
|
+
if (!(left > TINY)) continue;
|
|
1850
|
+
let at = flow.from;
|
|
1851
|
+
const goal = base2.goals(candidates, false)[0];
|
|
1852
|
+
if (goal !== void 0) {
|
|
1853
|
+
const r = route2(base2, goal), done = full();
|
|
1854
|
+
const sequence = [[r.shapes[0], r.shapes[0]]];
|
|
1855
|
+
r.hops.forEach(([id], k) => {
|
|
1856
|
+
sequence.push([id, r.shapes[k]], [r.shapes[k + 1], r.shapes[k]]);
|
|
1857
|
+
});
|
|
1858
|
+
const first = sequence.find(([id]) => done.has(id));
|
|
1859
|
+
if (first) {
|
|
1860
|
+
at = first[1];
|
|
1861
|
+
const upTo = r.shapes.indexOf(at);
|
|
1862
|
+
for (let q = upTo; q >= 0; q--) {
|
|
1863
|
+
const s = byID.get(r.shapes[q]);
|
|
1864
|
+
if (s && s.queue === true && s.kind !== "connector") {
|
|
1865
|
+
at = r.shapes[q];
|
|
1866
|
+
break;
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
const n = r.shapes.indexOf(at);
|
|
1871
|
+
if (at !== flow.from && n >= 0) {
|
|
1872
|
+
const shapes2 = r.shapes.slice(0, n + 1), hops = r.hops.slice(0, n);
|
|
1873
|
+
for (const id of [...shapes2, ...hops.map(([h]) => h)]) if (room.has(id)) room.set(id, Math.max(0, room.get(id) - left));
|
|
1874
|
+
placements.push({ hops, shapes: shapes2, goal: at, amount: left, waits: true });
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
const existing = waiting2.find(([s]) => s === at);
|
|
1878
|
+
if (existing) existing[1] += left;
|
|
1879
|
+
else waiting2.push([at, left]);
|
|
1880
|
+
}
|
|
1881
|
+
waitingBy.set(flow.id, waiting2);
|
|
1882
|
+
for (const p of placements) {
|
|
1883
|
+
for (const id of [...p.shapes, ...p.hops.map(([h]) => h)]) bump(id, (m) => {
|
|
1884
|
+
m.throughput += p.amount;
|
|
1885
|
+
});
|
|
1886
|
+
if (!p.waits) fm.placed += p.amount;
|
|
1887
|
+
}
|
|
1888
|
+
for (const [at, a] of waiting2) {
|
|
1889
|
+
bump(at, (m) => {
|
|
1890
|
+
m.backlog += a;
|
|
1891
|
+
});
|
|
1892
|
+
fm.backlog += a;
|
|
1893
|
+
}
|
|
1894
|
+
const reachedFirst = placements.filter((p) => !p.waits && p.goal === flow.to[0]).reduce((s, p) => s + p.amount, 0);
|
|
1895
|
+
fm.backup = volume > TINY ? Math.max(0, (volume - reachedFirst) / volume) : 0;
|
|
1896
|
+
const every = everyOf(flow);
|
|
1897
|
+
const speed = clamp(flow.speed, DEFAULT_SPEED, 1, 1e4), size = clamp(flow.size, DEFAULT_SIZE, 2, 200);
|
|
1898
|
+
const phase = turn[i] / leaving.get(flow.from) * every;
|
|
1899
|
+
const color2 = flow.color ?? DEFAULT_COLOR;
|
|
1900
|
+
const cumulative = [];
|
|
1901
|
+
let sum = 0;
|
|
1902
|
+
for (const p of placements) {
|
|
1903
|
+
sum += p.amount / Math.max(volume, TINY);
|
|
1904
|
+
cumulative.push(sum);
|
|
1905
|
+
}
|
|
1906
|
+
if (cumulative.length > 0 && Math.abs(cumulative[cumulative.length - 1] - 1) < 1e-9) cumulative[cumulative.length - 1] = 1;
|
|
1907
|
+
let transitSum = 0;
|
|
1908
|
+
placements.forEach((p, r) => {
|
|
1909
|
+
const w = way(p.hops, p.shapes);
|
|
1910
|
+
if (!w) return;
|
|
1911
|
+
const travel = w.length / speed + w.inside.reduce((s, x) => s + x[2], 0);
|
|
1912
|
+
if (!p.waits) transitSum += travel * p.amount;
|
|
1913
|
+
const lo = Math.ceil((t - phase - travel) / every);
|
|
1914
|
+
const hi = Math.floor((t - phase) / every);
|
|
1915
|
+
const below = r === 0 ? 0 : cumulative[r - 1], upTo = cumulative[r];
|
|
1916
|
+
const dealt = (c, k) => Math.floor((k + 1) * c) - Math.floor(k * c);
|
|
1917
|
+
for (let k = Math.max(lo, hi - 3999); k <= hi; k++) {
|
|
1918
|
+
if (dealt(upTo, k) - dealt(below, k) !== 1) continue;
|
|
1919
|
+
const d = along(w, t - k * every - phase, speed);
|
|
1920
|
+
if (d === null) continue;
|
|
1921
|
+
const s = w.stretches.find(([, a, b]) => d >= a && d <= b);
|
|
1922
|
+
if (s) bump(s[0], (m) => {
|
|
1923
|
+
m.inFlight += 1;
|
|
1924
|
+
});
|
|
1925
|
+
const pt2 = w.line.point(d / w.length);
|
|
1926
|
+
packets.push(packet(packetID(flow.id, k), pt2.x - size / 2, pt2.y - size / 2, size, color2, source.layerID));
|
|
1927
|
+
}
|
|
1928
|
+
});
|
|
1929
|
+
fm.transit = fm.placed > TINY ? transitSum / fm.placed : null;
|
|
1930
|
+
metrics2.flows.set(flow.id, fm);
|
|
1931
|
+
}
|
|
1932
|
+
const piles = /* @__PURE__ */ new Map();
|
|
1933
|
+
const carriedKeys = Object.keys(carried).filter((k) => carried[k] > TINY);
|
|
1934
|
+
for (const flow of list) {
|
|
1935
|
+
const waiting2 = waitingBy.get(flow.id) ?? [];
|
|
1936
|
+
const shapes2 = waiting2.map(([s]) => s);
|
|
1937
|
+
const suffix = "|" + flow.id;
|
|
1938
|
+
if (carriedKeys.some((k) => k.endsWith(suffix))) for (const e of frame2.elements) {
|
|
1939
|
+
const c = carried[queueKey(e.id, flow.id)];
|
|
1940
|
+
if (c !== void 0 && c > TINY && !shapes2.includes(e.id)) shapes2.push(e.id);
|
|
1941
|
+
}
|
|
1942
|
+
if (shapes2.length === 0) continue;
|
|
1943
|
+
const volume = amountIn(flow, values);
|
|
1944
|
+
const every = everyOf(flow);
|
|
1945
|
+
const unit = every * Math.max(volume, TINY);
|
|
1946
|
+
const speed = clamp(flow.speed, DEFAULT_SPEED, 1, 1e4), size = clamp(flow.size, DEFAULT_SIZE, 2, 200);
|
|
1947
|
+
const color2 = flow.color ?? DEFAULT_COLOR;
|
|
1948
|
+
shapes2.forEach((at, j) => {
|
|
1949
|
+
const shape = byID.get(at);
|
|
1950
|
+
if (!shape) return;
|
|
1951
|
+
const layer = byID.get(flow.from)?.layerID ?? shape.layerID;
|
|
1952
|
+
const backlog = waiting2.find(([s]) => s === at)?.[1] ?? 0;
|
|
1953
|
+
const before = Math.max(0, carried[queueKey(at, flow.id)] ?? 0);
|
|
1954
|
+
let depth = before + backlog * since;
|
|
1955
|
+
if (backlog <= TINY && before > TINY) {
|
|
1956
|
+
const trial = new Map(room);
|
|
1957
|
+
const outlets = [];
|
|
1958
|
+
let total = 0;
|
|
1959
|
+
for (let n2 = 0; n2 < 32; n2++) {
|
|
1960
|
+
const blocked = new Set([...trial].filter(([, v]) => v <= TINY).map(([id]) => id));
|
|
1961
|
+
const tree = flowTree(at, frame2, union2(offline, blocked), flow.to[0]);
|
|
1962
|
+
const goal = tree?.goals(flow.to, false)[0];
|
|
1963
|
+
if (!tree || goal === void 0) break;
|
|
1964
|
+
const r = route2(tree, goal);
|
|
1965
|
+
const ids = [...r.shapes.slice(1), ...r.hops.map(([id]) => id)];
|
|
1966
|
+
const rooms = ids.filter((id) => trial.has(id)).map((id) => trial.get(id));
|
|
1967
|
+
const spare = rooms.length > 0 ? Math.min(...rooms) : Infinity;
|
|
1968
|
+
const rate = Number.isFinite(spare) ? spare : Math.max(before / 0.5 - total, TINY * 2);
|
|
1969
|
+
if (!(rate > TINY)) break;
|
|
1970
|
+
for (const id of ids) if (trial.has(id)) trial.set(id, trial.get(id) - rate);
|
|
1971
|
+
outlets.push({ hops: r.hops, shapes: r.shapes, rate });
|
|
1972
|
+
total += rate;
|
|
1973
|
+
if (!Number.isFinite(spare)) break;
|
|
1974
|
+
}
|
|
1975
|
+
if (total > TINY) {
|
|
1976
|
+
depth = Math.max(0, before - total * since);
|
|
1977
|
+
if (depth > TINY) {
|
|
1978
|
+
for (const [id, v] of trial) room.set(id, v);
|
|
1979
|
+
for (const o of outlets) for (const id of [...o.shapes, ...o.hops.map(([h]) => h)]) bump(id, (m) => {
|
|
1980
|
+
m.throughput += o.rate;
|
|
1981
|
+
});
|
|
1982
|
+
}
|
|
1983
|
+
const lasts = before / total;
|
|
1984
|
+
outlets.forEach((o, i) => {
|
|
1985
|
+
const w = way(o.hops, o.shapes);
|
|
1986
|
+
if (!w) return;
|
|
1987
|
+
const spacing = Math.max(unit / o.rate, 0.05);
|
|
1988
|
+
for (let k = 0; k * spacing < lasts && k * spacing <= since && k < 2048; k++) {
|
|
1989
|
+
const d = along(w, since - k * spacing, speed);
|
|
1990
|
+
if (d === null) continue;
|
|
1991
|
+
const pt2 = w.line.point(d / w.length);
|
|
1992
|
+
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));
|
|
1993
|
+
}
|
|
1994
|
+
});
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
if (!(depth > TINY || backlog > TINY)) return;
|
|
1998
|
+
bump(at, (m) => {
|
|
1999
|
+
m.depth += depth;
|
|
2000
|
+
});
|
|
2001
|
+
if (depth > TINY) metrics2.carry[queueKey(at, flow.id)] = depth;
|
|
2002
|
+
const n = Math.min(Math.max(Math.ceil(depth / unit - 1e-9), 1), 30);
|
|
2003
|
+
const rows = Math.trunc((n + 9) / 10);
|
|
2004
|
+
const base2 = piles.get(at) ?? 0;
|
|
2005
|
+
piles.set(at, base2 + rows);
|
|
2006
|
+
for (let q = 0; q < n; q++) {
|
|
2007
|
+
const cx = shape.x + 2 + size / 2 + q % 10 * (size + 3);
|
|
2008
|
+
const cy = shape.y - 4 - size / 2 - (base2 + Math.trunc(q / 10)) * (size + 3);
|
|
2009
|
+
packets.push(packet(packetID(flow.id, 2 ** 39 + j * 64 + q), cx - size / 2, cy - size / 2, size, color2, layer));
|
|
2010
|
+
}
|
|
2011
|
+
});
|
|
2012
|
+
}
|
|
2013
|
+
for (const e of frame2.elements) {
|
|
2014
|
+
const c = e.capacity ?? null;
|
|
2015
|
+
const m = metrics2.elements.get(e.id);
|
|
2016
|
+
if (c === null || !Number.isFinite(c) || !m) continue;
|
|
2017
|
+
m.load = c > 0 ? m.throughput / c : 1;
|
|
2018
|
+
}
|
|
2019
|
+
return { packets, metrics: metrics2 };
|
|
2020
|
+
}
|
|
2021
|
+
var BLANK = {
|
|
2022
|
+
seed: 1,
|
|
2023
|
+
text: null,
|
|
2024
|
+
connector: null,
|
|
2025
|
+
groupID: null,
|
|
2026
|
+
points: null,
|
|
2027
|
+
path: null,
|
|
2028
|
+
subpaths: null,
|
|
2029
|
+
evenOdd: null,
|
|
2030
|
+
rotation: null,
|
|
2031
|
+
link: null,
|
|
2032
|
+
url: null,
|
|
2033
|
+
anchor: null,
|
|
2034
|
+
click: null,
|
|
2035
|
+
hover: null,
|
|
2036
|
+
when: null,
|
|
2037
|
+
overrides: null,
|
|
2038
|
+
symbol: null,
|
|
2039
|
+
playback: null,
|
|
2040
|
+
offset: null,
|
|
2041
|
+
args: null,
|
|
2042
|
+
image: null,
|
|
2043
|
+
reveal: null
|
|
2044
|
+
};
|
|
2045
|
+
function style(stroke, fill, strokeWidth, fillStyle = "solid", opacity2 = null) {
|
|
2046
|
+
return {
|
|
2047
|
+
stroke: { ...stroke },
|
|
2048
|
+
fill: { ...fill },
|
|
2049
|
+
fillStyle,
|
|
2050
|
+
strokeWidth,
|
|
2051
|
+
dash: "solid",
|
|
2052
|
+
roughness: 0,
|
|
2053
|
+
bowing: 1,
|
|
2054
|
+
fontSize: 18,
|
|
2055
|
+
font: null,
|
|
2056
|
+
gridSize: null,
|
|
2057
|
+
opacity: opacity2,
|
|
2058
|
+
dashOffset: null,
|
|
2059
|
+
textColor: null
|
|
2060
|
+
};
|
|
2061
|
+
}
|
|
2062
|
+
function packet(id, x, y, size, color2, layerID) {
|
|
2063
|
+
return { ...BLANK, id, kind: "ellipse", layerID, x, y, width: size, height: size, style: style(color2, color2, 1.5) };
|
|
2064
|
+
}
|
|
2065
|
+
function metricLookup(m, frame2) {
|
|
2066
|
+
const anchors = /* @__PURE__ */ new Map();
|
|
2067
|
+
for (const e of frame2.elements) if (e.anchor !== null && !anchors.has(e.anchor)) anchors.set(e.anchor, e.id);
|
|
2068
|
+
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}$/;
|
|
2069
|
+
return (term, own) => {
|
|
2070
|
+
const id = term.target === "" ? own : anchors.get(term.target) ?? (UUID2.test(term.target) ? term.target.toUpperCase() : null);
|
|
2071
|
+
return id === null ? null : metricValue(m, term.metric, id);
|
|
2072
|
+
};
|
|
2073
|
+
}
|
|
2074
|
+
function finishPlan(elements, metrics2, values, frame2, since = Infinity, carried = {}) {
|
|
2075
|
+
const lookup = metricLookup(metrics2, frame2);
|
|
2076
|
+
const plan2 = [], readings = {};
|
|
2077
|
+
for (const e of elements) {
|
|
2078
|
+
const applied = [];
|
|
2079
|
+
if (values !== null && e.overrides) {
|
|
2080
|
+
e.overrides.forEach((o, k) => {
|
|
2081
|
+
if (mentionsMetric(o.when) && holds(o.when, values, (m) => lookup(m, e.id))) applied.push(k);
|
|
2082
|
+
});
|
|
2083
|
+
}
|
|
2084
|
+
const g = values === null ? null : e.gauge;
|
|
2085
|
+
if (!g) {
|
|
2086
|
+
plan2.push({ applied, gauge: null });
|
|
2087
|
+
continue;
|
|
2088
|
+
}
|
|
2089
|
+
let shown = metricValue(metrics2, g.metric, g.of);
|
|
2090
|
+
const then = carried[gaugeKey(e.id)];
|
|
2091
|
+
if (shown !== null && then !== void 0 && g.metric !== "depth" && g.metric !== "inFlight" && since < GAUGE_EASE) {
|
|
2092
|
+
const f = Math.max(0, since / GAUGE_EASE), smooth = f * f * (3 - 2 * f);
|
|
2093
|
+
shown = then + (shown - then) * smooth;
|
|
2094
|
+
}
|
|
2095
|
+
if (shown !== null) readings[gaugeKey(e.id)] = shown;
|
|
2096
|
+
plan2.push({ applied, gauge: { value: shown } });
|
|
2097
|
+
}
|
|
2098
|
+
return { plan: plan2, readings };
|
|
2099
|
+
}
|
|
2100
|
+
function finishing(elements, metrics2, values, frame2, flows, since = Infinity, carried = {}) {
|
|
2101
|
+
const { plan: plan2, readings } = finishPlan(elements, metrics2, values, frame2, since, carried);
|
|
2102
|
+
const out = [];
|
|
2103
|
+
elements.forEach((e, i) => {
|
|
2104
|
+
for (const k of plan2[i].applied) e = applyOverride(looksOnly(e.overrides[k]), e);
|
|
2105
|
+
const g = plan2[i].gauge;
|
|
2106
|
+
if (g && e.gauge) out.push(...gauge(e, e.gauge, g.value, frame2, flows));
|
|
2107
|
+
else out.push(e);
|
|
2108
|
+
});
|
|
2109
|
+
return { elements: out, readings };
|
|
2110
|
+
}
|
|
2111
|
+
var gaugeKey = (id) => "gauge|" + id;
|
|
2112
|
+
var GAUGE_EASE = 1;
|
|
2113
|
+
var hex = (h) => {
|
|
2114
|
+
const v = parseInt(h.slice(1), 16);
|
|
2115
|
+
return { r: (v >> 16 & 255) / 255, g: (v >> 8 & 255) / 255, b: (v & 255) / 255, a: 1 };
|
|
2116
|
+
};
|
|
2117
|
+
var GAUGE_GREEN = hex("#16a34a");
|
|
2118
|
+
var GAUGE_AMBER = hex("#d97706");
|
|
2119
|
+
var GAUGE_RED = hex("#dc2626");
|
|
2120
|
+
var GAUGE_BLUE = hex("#2f6fdf");
|
|
2121
|
+
var GAUGE_TRACK = hex("#e5e7eb");
|
|
2122
|
+
var GAUGE_GRAY = hex("#9ca3af");
|
|
2123
|
+
function formatted(metric, v) {
|
|
2124
|
+
if (v === null || !Number.isFinite(v)) return "\u2013";
|
|
2125
|
+
switch (metric) {
|
|
2126
|
+
case "load":
|
|
2127
|
+
case "backup":
|
|
2128
|
+
return formatNumber(v * 100, 0) + "%";
|
|
2129
|
+
case "transit":
|
|
2130
|
+
case "delay":
|
|
2131
|
+
return formatNumber(v, Math.abs(v) >= 10 ? 1 : 2) + "s";
|
|
2132
|
+
default:
|
|
2133
|
+
return formatNumber(v, Math.abs(v) >= 100 ? 0 : Math.abs(v) >= 10 ? 1 : 2);
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
function formatNumber(v, places) {
|
|
2137
|
+
const scale = places === 0 ? 1 : places === 1 ? 10 : 100;
|
|
2138
|
+
const n = Math.floor(Math.abs(v) * scale + 0.5);
|
|
2139
|
+
if (!(n < 1e15)) return v < 0 ? "-\u221E" : "\u221E";
|
|
2140
|
+
const whole = Math.trunc(n / scale), part = n - whole * scale;
|
|
2141
|
+
let s = String(whole);
|
|
2142
|
+
if (places > 0 && part > 0) {
|
|
2143
|
+
let digits = String(part);
|
|
2144
|
+
while (digits.length < places) digits = "0" + digits;
|
|
2145
|
+
while (digits.endsWith("0")) digits = digits.slice(0, -1);
|
|
2146
|
+
s += "." + digits;
|
|
2147
|
+
}
|
|
2148
|
+
return (v < 0 && n > 0 ? "-" : "") + s;
|
|
2149
|
+
}
|
|
2150
|
+
function gauge(e, g, value2, frame2, flows) {
|
|
2151
|
+
const target = frame2.elements.find((x) => x.id === g.of);
|
|
2152
|
+
const full = g.max ?? (g.metric === "load" ? 1 : target?.capacity ?? value2 ?? 1);
|
|
2153
|
+
const fraction = value2 === null ? 0 : full > 0 ? Math.min(Math.max(value2 / full, 0), 1) : 0;
|
|
2154
|
+
const warn = g.warn ?? (g.metric === "load" ? 0.7 : null), alarm = g.alarm ?? (g.metric === "load" ? 0.9 : null);
|
|
2155
|
+
let color2 = warn === null && alarm === null ? GAUGE_BLUE : GAUGE_GREEN;
|
|
2156
|
+
if (value2 !== null && warn !== null && value2 >= warn) color2 = GAUGE_AMBER;
|
|
2157
|
+
if (value2 !== null && alarm !== null && value2 >= alarm) color2 = GAUGE_RED;
|
|
2158
|
+
if (value2 === null) color2 = GAUGE_GRAY;
|
|
2159
|
+
let name = g.label;
|
|
2160
|
+
if (name === null) {
|
|
2161
|
+
const what = target ? target.text !== null ? target.text.split("\n")[0] : target.anchor : null;
|
|
2162
|
+
name = `${g.metric} ${what ?? (flows.some((f) => f.id === g.of) ? "flow" : "?")}`;
|
|
2163
|
+
}
|
|
2164
|
+
const kind = g.style ?? "number";
|
|
2165
|
+
const w = Math.max(e.width, 1), h = Math.max(e.height, 1);
|
|
2166
|
+
const line2 = (a, b) => [
|
|
2167
|
+
a.x / w,
|
|
2168
|
+
a.y / h,
|
|
2169
|
+
(a.x + (b.x - a.x) / 3) / w,
|
|
2170
|
+
(a.y + (b.y - a.y) / 3) / h,
|
|
2171
|
+
(a.x + 2 * (b.x - a.x) / 3) / w,
|
|
2172
|
+
(a.y + 2 * (b.y - a.y) / 3) / h,
|
|
2173
|
+
b.x / w,
|
|
2174
|
+
b.y / h
|
|
2175
|
+
];
|
|
2176
|
+
const box = (x, y, bw, bh) => {
|
|
2177
|
+
const p = [{ x, y }, { x: x + bw, y }, { x: x + bw, y: y + bh }, { x, y: y + bh }];
|
|
2178
|
+
let coords = line2(p[0], p[1]);
|
|
2179
|
+
for (let k = 1; k < 4; k++) coords = coords.concat(line2(p[k], p[(k + 1) % 4]).slice(2));
|
|
2180
|
+
return { closed: true, coords };
|
|
2181
|
+
};
|
|
2182
|
+
const arc = (c, radius, a0, a1) => {
|
|
2183
|
+
const pieces2 = Math.max(1, Math.ceil(Math.abs(a1 - a0) / (Math.PI / 2) - 1e-9));
|
|
2184
|
+
const step = (a1 - a0) / pieces2, kappa = 4 / 3 * Math.tan(step / 4);
|
|
2185
|
+
const at = (a) => ({ x: c.x + radius * Math.cos(a), y: c.y + radius * Math.sin(a) });
|
|
2186
|
+
const s = at(a0);
|
|
2187
|
+
const coords = [s.x / w, s.y / h];
|
|
2188
|
+
for (let k = 0; k < pieces2; k++) {
|
|
2189
|
+
const a = a0 + k * step, b = a + step;
|
|
2190
|
+
const p0 = at(a), p3 = at(b);
|
|
2191
|
+
const c1 = { x: p0.x - kappa * radius * Math.sin(a), y: p0.y + kappa * radius * Math.cos(a) };
|
|
2192
|
+
const c2 = { x: p3.x + kappa * radius * Math.sin(b), y: p3.y - kappa * radius * Math.cos(b) };
|
|
2193
|
+
coords.push(c1.x / w, c1.y / h, c2.x / w, c2.y / h, p3.x / w, p3.y / h);
|
|
2194
|
+
}
|
|
2195
|
+
return { closed: false, coords };
|
|
2196
|
+
};
|
|
2197
|
+
const part = (k, subpaths, stroke, fill, width) => ({
|
|
2198
|
+
...BLANK,
|
|
2199
|
+
id: packetID(e.id, -(2 ** 38) - k),
|
|
2200
|
+
kind: "path",
|
|
2201
|
+
layerID: e.layerID,
|
|
2202
|
+
x: e.x,
|
|
2203
|
+
y: e.y,
|
|
2204
|
+
width: e.width,
|
|
2205
|
+
height: e.height,
|
|
2206
|
+
style: style(stroke, fill ?? { r: 0, g: 0, b: 0, a: 0 }, width, "solid", e.style.opacity),
|
|
2207
|
+
subpaths
|
|
2208
|
+
});
|
|
2209
|
+
const back = {
|
|
2210
|
+
...e,
|
|
2211
|
+
gauge: null,
|
|
2212
|
+
kind: "path",
|
|
2213
|
+
subpaths: [box(0, 0, w, h)],
|
|
2214
|
+
text: name + "\n" + formatted(g.metric, value2),
|
|
2215
|
+
style: { ...e.style }
|
|
2216
|
+
};
|
|
2217
|
+
const parts = [];
|
|
2218
|
+
if (kind === "number") {
|
|
2219
|
+
back.labelRect = { x: 0, y: 0, width: 1, height: 1 };
|
|
2220
|
+
back.style.textColor = { ...color2 };
|
|
2221
|
+
} else if (kind === "bar") {
|
|
2222
|
+
back.labelRect = { x: 0, y: 0, width: 1, height: 0.66 };
|
|
2223
|
+
const inset2 = Math.min(10, w * 0.08), barH = Math.min(12, h * 0.18), top = h * 0.72;
|
|
2224
|
+
const trackW = w - 2 * inset2;
|
|
2225
|
+
parts.push(part(1, [box(inset2, top, trackW, barH)], GAUGE_TRACK, GAUGE_TRACK, 0.5));
|
|
2226
|
+
if (fraction > 0) parts.push(part(2, [box(inset2, top, trackW * fraction, barH)], color2, color2, 0.5));
|
|
2227
|
+
} else {
|
|
2228
|
+
back.labelRect = { x: 0, y: 0.54, width: 1, height: 0.46 };
|
|
2229
|
+
const radius = Math.min(w * 0.38, h * 0.4), center2 = { x: w / 2, y: h * 0.5 };
|
|
2230
|
+
const thick = Math.max(2, radius * 0.16);
|
|
2231
|
+
parts.push(part(1, [arc(center2, radius, Math.PI, 2 * Math.PI)], GAUGE_TRACK, null, thick));
|
|
2232
|
+
if (fraction > 0) parts.push(part(2, [arc(center2, radius, Math.PI, Math.PI + Math.PI * fraction)], color2, null, thick));
|
|
2233
|
+
const a = Math.PI + Math.PI * fraction;
|
|
2234
|
+
const tip = { x: center2.x + radius * 0.85 * Math.cos(a), y: center2.y + radius * 0.85 * Math.sin(a) };
|
|
2235
|
+
parts.push(part(3, [{ closed: false, coords: line2(center2, tip) }], { r: 0.2, g: 0.2, b: 0.2, a: 1 }, null, 2));
|
|
2236
|
+
}
|
|
2237
|
+
return [back, ...parts];
|
|
2238
|
+
}
|
|
2239
|
+
|
|
1347
2240
|
// src/rough.ts
|
|
1348
|
-
var offset = (rng,
|
|
2241
|
+
var offset = (rng, amount2, roughness) => rng.signed() * amount2 * roughness;
|
|
1349
2242
|
function line(a, b, rng, roughness, bowing, preserveEnds, into) {
|
|
1350
2243
|
const length = Math.hypot(b.x - a.x, b.y - a.y);
|
|
1351
2244
|
if (!(length > 0.5)) return;
|
|
@@ -1358,7 +2251,7 @@ function line(a, b, rng, roughness, bowing, preserveEnds, into) {
|
|
|
1358
2251
|
const bow = offset(rng, length / 60, roughness) * bowing * gain;
|
|
1359
2252
|
const dx = b.x - a.x, dy = b.y - a.y;
|
|
1360
2253
|
const nx = -dy / length, ny = dx / length;
|
|
1361
|
-
const
|
|
2254
|
+
const start2 = preserveEnds ? a : (() => {
|
|
1362
2255
|
const x = a.x + j();
|
|
1363
2256
|
return { x, y: a.y + j() };
|
|
1364
2257
|
})();
|
|
@@ -1370,7 +2263,7 @@ function line(a, b, rng, roughness, bowing, preserveEnds, into) {
|
|
|
1370
2263
|
const c1y = a.y + dy * mid * 0.66 + ny * bow + j() * 0.5;
|
|
1371
2264
|
const c2x = a.x + dx * (0.5 + mid * 0.5) + nx * bow + j() * 0.5;
|
|
1372
2265
|
const c2y = a.y + dy * (0.5 + mid * 0.5) + ny * bow + j() * 0.5;
|
|
1373
|
-
into.moveTo(
|
|
2266
|
+
into.moveTo(start2);
|
|
1374
2267
|
into.curveTo(end, { x: c1x, y: c1y }, { x: c2x, y: c2y });
|
|
1375
2268
|
}
|
|
1376
2269
|
}
|
|
@@ -1631,10 +2524,10 @@ var Cache = class {
|
|
|
1631
2524
|
__publicField(this, "limit");
|
|
1632
2525
|
this.limit = limit;
|
|
1633
2526
|
}
|
|
1634
|
-
get(key,
|
|
2527
|
+
get(key, make2) {
|
|
1635
2528
|
const hit = this.map.get(key);
|
|
1636
2529
|
if (hit !== void 0) return hit;
|
|
1637
|
-
const v =
|
|
2530
|
+
const v = make2();
|
|
1638
2531
|
if (this.map.size >= this.limit) this.map.clear();
|
|
1639
2532
|
this.map.set(key, v);
|
|
1640
2533
|
return v;
|
|
@@ -1858,26 +2751,59 @@ function subCubic(p, t0, t1) {
|
|
|
1858
2751
|
const left = split(p, t1)[0];
|
|
1859
2752
|
return t1 > 0 ? split(left, t0 / t1)[1] : [p[0], p[0], p[0], p[0]];
|
|
1860
2753
|
}
|
|
2754
|
+
function boxOf(pts) {
|
|
2755
|
+
let x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity;
|
|
2756
|
+
for (const p of pts) {
|
|
2757
|
+
x0 = Math.min(x0, p.x);
|
|
2758
|
+
y0 = Math.min(y0, p.y);
|
|
2759
|
+
x1 = Math.max(x1, p.x);
|
|
2760
|
+
y1 = Math.max(y1, p.y);
|
|
2761
|
+
}
|
|
2762
|
+
return { x0, y0, x1, y1 };
|
|
2763
|
+
}
|
|
2764
|
+
function touching(a, b) {
|
|
2765
|
+
const eps = 1e-6;
|
|
2766
|
+
return a.x0 <= b.x1 + eps && b.x0 <= a.x1 + eps && a.y0 <= b.y1 + eps && b.y0 <= a.y1 + eps;
|
|
2767
|
+
}
|
|
2768
|
+
var hopFrames = /* @__PURE__ */ new WeakMap();
|
|
2769
|
+
function linesBeneath(e, page, index) {
|
|
2770
|
+
let f = hopFrames.get(index);
|
|
2771
|
+
if (!f || f.layers !== page.layers || !isIndexOf(page, index)) {
|
|
2772
|
+
const visible = new Set(page.layers.filter((l) => l.visible).map((l) => l.id));
|
|
2773
|
+
const first = /* @__PURE__ */ new Map(), under = [];
|
|
2774
|
+
for (const o of orderedElements(page)) {
|
|
2775
|
+
if (!first.has(o.id)) first.set(o.id, under.length);
|
|
2776
|
+
if (o.kind === "connector" && visible.has(o.layerID)) under.push(o);
|
|
2777
|
+
}
|
|
2778
|
+
f = { layers: page.layers, first, under, lines: [] };
|
|
2779
|
+
if (isIndexOf(page, index)) hopFrames.set(index, f);
|
|
2780
|
+
}
|
|
2781
|
+
const count = f.first.get(e.id) ?? f.under.length;
|
|
2782
|
+
while (f.lines.length < count) {
|
|
2783
|
+
const points = routePoints(route(f.under[f.lines.length], index));
|
|
2784
|
+
f.lines.push({ points, box: boxOf(points) });
|
|
2785
|
+
}
|
|
2786
|
+
return { lines: f.lines, count };
|
|
2787
|
+
}
|
|
1861
2788
|
function hopCrossings(e, r, page, index) {
|
|
1862
2789
|
const segments = hopSamples(r);
|
|
1863
2790
|
if (segments.length === 0) return [];
|
|
1864
|
-
const
|
|
1865
|
-
const beneath = [];
|
|
1866
|
-
for (const o of orderedElements(page)) {
|
|
1867
|
-
if (o.id === e.id) break;
|
|
1868
|
-
if (o.kind !== "connector" || !visible.has(o.layerID)) continue;
|
|
1869
|
-
beneath.push(routePoints(route(o, index)));
|
|
1870
|
-
}
|
|
2791
|
+
const { lines, count } = linesBeneath(e, page, index);
|
|
1871
2792
|
const rad = hopRadius(e.style.strokeWidth);
|
|
1872
2793
|
const out = [];
|
|
1873
2794
|
for (const samples of segments) {
|
|
1874
2795
|
const along = runningLengths(samples);
|
|
1875
2796
|
const length = along[along.length - 1];
|
|
1876
2797
|
const found = [];
|
|
2798
|
+
const near = [];
|
|
2799
|
+
const whole = boxOf(samples);
|
|
2800
|
+
for (let n = 0; n < count; n++) if (touching(whole, lines[n].box)) near.push(lines[n]);
|
|
1877
2801
|
for (let i = 0; i < samples.length - 1; i++) {
|
|
1878
2802
|
const p = samples[i], q = samples[i + 1];
|
|
1879
2803
|
const dx = q.x - p.x, dy = q.y - p.y;
|
|
1880
|
-
|
|
2804
|
+
const box = boxOf([p, q]);
|
|
2805
|
+
for (const { points: line2, box: lineBox } of near) {
|
|
2806
|
+
if (!touching(box, lineBox)) continue;
|
|
1881
2807
|
for (let k = 0; k < line2.length - 1; k++) {
|
|
1882
2808
|
const a = line2[k], b = line2[k + 1];
|
|
1883
2809
|
const ex = b.x - a.x, ey = b.y - a.y;
|
|
@@ -2157,8 +3083,8 @@ function followSpan(track, t) {
|
|
|
2157
3083
|
if (track.keys.some((k) => k.t >= span.end && k.t <= t && (k.x !== void 0 || k.y !== void 0))) return null;
|
|
2158
3084
|
return f;
|
|
2159
3085
|
}
|
|
2160
|
-
function tracing(e, page) {
|
|
2161
|
-
if (e.kind === "connector") return new Polyline(routePoints(route(e,
|
|
3086
|
+
function tracing(e, page, index = indexOf(page)) {
|
|
3087
|
+
if (e.kind === "connector") return new Polyline(routePoints(route(e, index)));
|
|
2162
3088
|
const clean = cloneElement(e);
|
|
2163
3089
|
clean.style.roughness = 0;
|
|
2164
3090
|
const pts = outline(clean).contours()[0] ?? [];
|
|
@@ -2168,9 +3094,10 @@ function following(track, e, t, page) {
|
|
|
2168
3094
|
const f = followSpan(track, t);
|
|
2169
3095
|
if (!f) return e;
|
|
2170
3096
|
t = localTime(track, t);
|
|
2171
|
-
const
|
|
3097
|
+
const index = indexOf(page);
|
|
3098
|
+
const pathElement = index.get(f.path);
|
|
2172
3099
|
if (!pathElement || pathElement.id === e.id) return e;
|
|
2173
|
-
const line2 = tracing(pathElement, page);
|
|
3100
|
+
const line2 = tracing(pathElement, page, index);
|
|
2174
3101
|
const span = f.end - f.start;
|
|
2175
3102
|
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);
|
|
2176
3103
|
if (u > 1) u = rem(u, 1) === 0 ? 1 : rem(u, 1);
|
|
@@ -2201,16 +3128,39 @@ function trackMap(tl) {
|
|
|
2201
3128
|
for (const t of tl.tracks) if (!m.has(t.element)) m.set(t.element, t);
|
|
2202
3129
|
return m;
|
|
2203
3130
|
}
|
|
2204
|
-
function resolved(page, t, state = "initial") {
|
|
2205
|
-
return expandingSymbols(posed(page, t, state), clampTime(t));
|
|
3131
|
+
function resolved(page, t, state = "initial", since = null, carried = {}) {
|
|
3132
|
+
return expandingSymbols(posed(page, t, state, since, carried), clampTime(t));
|
|
2206
3133
|
}
|
|
2207
3134
|
var EMPTY_TIMELINE = { fps: 12, duration: 0, boil: null, settle: null, poster: null, tracks: [], camera: null, markers: null };
|
|
2208
|
-
function posed(page, t, state = "initial") {
|
|
3135
|
+
function posed(page, t, state = "initial", since = null, carried = {}) {
|
|
3136
|
+
return posedWithMetrics(page, t, state, since, carried).frame;
|
|
3137
|
+
}
|
|
3138
|
+
function posedWithMetrics(page, t, state = "initial", since = null, carried = {}) {
|
|
2209
3139
|
const off = stateHidden(page, state);
|
|
2210
|
-
const
|
|
2211
|
-
|
|
3140
|
+
const values = stateValues(page, state);
|
|
3141
|
+
const active = /* @__PURE__ */ new Map();
|
|
3142
|
+
for (const e of page.elements) {
|
|
3143
|
+
const o = activeOverrides(e, values);
|
|
3144
|
+
if (o.length > 0) active.set(e.id, o);
|
|
3145
|
+
}
|
|
3146
|
+
const flowing = (page.flows ?? []).length > 0;
|
|
3147
|
+
const measuring = flowing || page.elements.some((e) => e.gauge || (e.overrides ?? []).some((o) => mentionsMetric(o.when)));
|
|
3148
|
+
const timeline = page.timeline ?? (off.size === 0 && active.size === 0 && !measuring ? null : EMPTY_TIMELINE);
|
|
3149
|
+
if (!timeline) return { frame: page, metrics: emptyMetrics() };
|
|
2212
3150
|
t = clampTime(t);
|
|
2213
3151
|
const tracks = trackMap(timeline);
|
|
3152
|
+
for (const [id, overrides] of active) {
|
|
3153
|
+
const track = tracks.get(id);
|
|
3154
|
+
if (!track?.follow || !overrides.some((o) => o.reroute)) continue;
|
|
3155
|
+
tracks.set(id, {
|
|
3156
|
+
...track,
|
|
3157
|
+
follow: track.follow.map((f) => {
|
|
3158
|
+
let path = f.path;
|
|
3159
|
+
for (const o of overrides) path = o.reroute?.[path] ?? path;
|
|
3160
|
+
return { ...f, path };
|
|
3161
|
+
})
|
|
3162
|
+
});
|
|
3163
|
+
}
|
|
2214
3164
|
const reseed = timeline.boil === null ? null : Math.trunc(Math.floor(t * clampFPS(timeline.fps) + 1e-9) / Math.max(1, timeline.boil)) % BOIL_DRAWINGS >>> 0;
|
|
2215
3165
|
const hidden = /* @__PURE__ */ new Set();
|
|
2216
3166
|
for (const tr of tracks.values()) if (!isVisible(tr, t)) hidden.add(tr.element);
|
|
@@ -2230,6 +3180,14 @@ function posed(page, t, state = "initial") {
|
|
|
2230
3180
|
let elements = page.elements.map((e) => {
|
|
2231
3181
|
const track = tracks.get(e.id);
|
|
2232
3182
|
let out = track ? applyTrack(track, e, t) : e;
|
|
3183
|
+
for (const o of active.get(e.id) ?? []) out = applyOverride(o, out);
|
|
3184
|
+
const list = active.get(e.id);
|
|
3185
|
+
if (list) {
|
|
3186
|
+
const caps = list.map((o) => o.capacity).filter((c) => c !== null && Number.isFinite(c));
|
|
3187
|
+
const delays = list.map((o) => o.delay).filter((d) => d !== null && Number.isFinite(d));
|
|
3188
|
+
if (caps.length > 0) out.capacity = Math.max(Math.min(...caps), 0);
|
|
3189
|
+
if (delays.length > 0 && out.kind !== "connector") out.delay = Math.max(Math.max(...delays), 0);
|
|
3190
|
+
}
|
|
2233
3191
|
if (reseed !== null && reseed > 0 && boils(e)) {
|
|
2234
3192
|
if (out === e) out = cloneElement(e);
|
|
2235
3193
|
out.seed = boiled(e.seed, reseed);
|
|
@@ -2263,7 +3221,18 @@ function posed(page, t, state = "initial") {
|
|
|
2263
3221
|
pinned.connector.end = { elementID: null, port: c.end.port, x: b.x, y: b.y };
|
|
2264
3222
|
return pinned;
|
|
2265
3223
|
});
|
|
2266
|
-
|
|
3224
|
+
elements = elements.filter((e) => !gone.has(e.id));
|
|
3225
|
+
let metrics2 = emptyMetrics();
|
|
3226
|
+
if (measuring) {
|
|
3227
|
+
const offline = new Set([...active].filter(([, list]) => list.some((o) => o.offline === true)).map(([id]) => id));
|
|
3228
|
+
const frame2 = { ...page, elements, timeline: null };
|
|
3229
|
+
const out = flowFrame(page.flows, frame2, t, values, offline, since, carried);
|
|
3230
|
+
metrics2 = out.metrics;
|
|
3231
|
+
const finished = finishing(elements, out.metrics, values, frame2, page.flows ?? [], Math.max(0, Math.min(since ?? t, 1e6)), carried);
|
|
3232
|
+
Object.assign(metrics2.carry, finished.readings);
|
|
3233
|
+
elements = [...finished.elements, ...out.packets];
|
|
3234
|
+
}
|
|
3235
|
+
return { frame: { ...page, elements, timeline: null, flows: measuring ? null : page.flows }, metrics: metrics2 };
|
|
2267
3236
|
}
|
|
2268
3237
|
function posterTime(page) {
|
|
2269
3238
|
return page.timeline ? page.timeline.poster ?? page.timeline.duration : 0;
|
|
@@ -2378,22 +3347,35 @@ function filled(s, args) {
|
|
|
2378
3347
|
}) };
|
|
2379
3348
|
}
|
|
2380
3349
|
function symbolPage(s) {
|
|
2381
|
-
return { id: s.id, name: s.name, layers: s.layers, elements: s.elements, theme: null, timeline: s.timeline, symbols: null, variables: null };
|
|
3350
|
+
return { id: s.id, name: s.name, layers: s.layers, elements: s.elements, theme: null, timeline: s.timeline, symbols: null, variables: null, flows: null };
|
|
2382
3351
|
}
|
|
2383
3352
|
function hasInstances(page) {
|
|
2384
3353
|
return page.elements.some((e) => e.kind === "symbol");
|
|
2385
3354
|
}
|
|
2386
|
-
|
|
3355
|
+
var Expansions = class {
|
|
3356
|
+
constructor(page, t, posing) {
|
|
3357
|
+
__publicField(this, "page");
|
|
3358
|
+
__publicField(this, "t");
|
|
3359
|
+
__publicField(this, "posing");
|
|
3360
|
+
/** For each instance: its time override, the budget it found and left, and what it made. */
|
|
3361
|
+
__publicField(this, "made", /* @__PURE__ */ new Map());
|
|
3362
|
+
this.page = page;
|
|
3363
|
+
this.t = t;
|
|
3364
|
+
this.posing = posing;
|
|
3365
|
+
}
|
|
3366
|
+
};
|
|
3367
|
+
function expandingSymbols(page, t, posing = true, times = /* @__PURE__ */ new Map(), memo) {
|
|
2387
3368
|
if (!hasInstances(page)) return page;
|
|
2388
3369
|
const budget = { left: MAX_ELEMENTS };
|
|
2389
|
-
|
|
3370
|
+
const m = memo && memo.page === page && memo.t === t && memo.posing === posing ? memo : void 0;
|
|
3371
|
+
return { ...page, elements: expand(page.elements, page, t, symbolTable(page), [], posing, budget, times, m) };
|
|
2390
3372
|
}
|
|
2391
3373
|
function symbolTable(page) {
|
|
2392
3374
|
const table = /* @__PURE__ */ new Map();
|
|
2393
3375
|
for (const s of page.symbols ?? []) if (!table.has(s.id)) table.set(s.id, s);
|
|
2394
3376
|
return table;
|
|
2395
3377
|
}
|
|
2396
|
-
function expand(elements, page, t, table, stack, posing, budget, times = /* @__PURE__ */ new Map()) {
|
|
3378
|
+
function expand(elements, page, t, table, stack, posing, budget, times = /* @__PURE__ */ new Map(), memo) {
|
|
2397
3379
|
const instances = new Set(elements.filter((e) => e.kind === "symbol").map((e) => e.id));
|
|
2398
3380
|
if (instances.size === 0) {
|
|
2399
3381
|
budget.left -= elements.length;
|
|
@@ -2418,6 +3400,14 @@ function expand(elements, page, t, table, stack, posing, budget, times = /* @__P
|
|
|
2418
3400
|
budget.left -= 1;
|
|
2419
3401
|
continue;
|
|
2420
3402
|
}
|
|
3403
|
+
const override0 = stack.length === 0 ? times.get(e.id) : void 0;
|
|
3404
|
+
const was = memo?.made.get(e);
|
|
3405
|
+
if (was && was.override === override0 && was.after > 0 && budget.left - (was.before - was.after) > 0) {
|
|
3406
|
+
budget.left -= was.before - was.after;
|
|
3407
|
+
for (const x of was.out) out.push(x);
|
|
3408
|
+
continue;
|
|
3409
|
+
}
|
|
3410
|
+
const before = budget.left, from = out.length;
|
|
2421
3411
|
budget.left -= 1;
|
|
2422
3412
|
const id = e.symbol;
|
|
2423
3413
|
const symbol = id === null ? void 0 : table.get(id);
|
|
@@ -2433,6 +3423,7 @@ function expand(elements, page, t, table, stack, posing, budget, times = /* @__P
|
|
|
2433
3423
|
layers: [{ id: e.layerID, name: "", visible: true, locked: false }]
|
|
2434
3424
|
};
|
|
2435
3425
|
for (const x of expand(drawn.elements, drawn, u, table, [...stack, id], posing, budget)) out.push(placed(x, e, symbol));
|
|
3426
|
+
if (memo && stack.length === 0 && !was) memo.made.set(e, { override: override0, before, after: budget.left, out: out.slice(from) });
|
|
2436
3427
|
}
|
|
2437
3428
|
return out;
|
|
2438
3429
|
}
|
|
@@ -2485,8 +3476,8 @@ function mix2(x) {
|
|
|
2485
3476
|
return z ^ z >> 31n;
|
|
2486
3477
|
}
|
|
2487
3478
|
function halves(id) {
|
|
2488
|
-
const
|
|
2489
|
-
return [BigInt(`0x${
|
|
3479
|
+
const hex2 = id.replace(/-/g, "").padStart(32, "0").slice(-32);
|
|
3480
|
+
return [BigInt(`0x${hex2.slice(0, 16)}`), BigInt(`0x${hex2.slice(16)}`)];
|
|
2490
3481
|
}
|
|
2491
3482
|
function derivedID(instance, inner) {
|
|
2492
3483
|
const [a, b] = halves(instance), [c, d] = halves(inner);
|
|
@@ -2507,7 +3498,7 @@ function clickTargets(page, file) {
|
|
|
2507
3498
|
let pageOf = null;
|
|
2508
3499
|
for (const e of page.elements) {
|
|
2509
3500
|
const c = e.click;
|
|
2510
|
-
const state = c && changesState(c) ? { ...c.set ? { set: c.set } : {}, ...c.toggle ? { toggle: c.toggle } : {} } : null;
|
|
3501
|
+
const state = c && changesState(c) ? { ...c.set ? { set: c.set } : {}, ...c.toggle ? { toggle: c.toggle } : {}, ...c.reset ? { reset: true } : {} } : null;
|
|
2511
3502
|
if (c?.scene) {
|
|
2512
3503
|
const i = sceneNamed(c.scene, markers);
|
|
2513
3504
|
if (i >= 0) {
|
|
@@ -2540,14 +3531,14 @@ function clickTargets(page, file) {
|
|
|
2540
3531
|
function hoveredElement(e) {
|
|
2541
3532
|
const h = e.hover;
|
|
2542
3533
|
if (!h) return e;
|
|
2543
|
-
const
|
|
2544
|
-
if (h.stroke)
|
|
2545
|
-
if (h.fill)
|
|
3534
|
+
const style2 = { ...e.style };
|
|
3535
|
+
if (h.stroke) style2.stroke = h.stroke;
|
|
3536
|
+
if (h.fill) style2.fill = h.fill;
|
|
2546
3537
|
if (h.opacity !== null && Number.isFinite(h.opacity)) {
|
|
2547
3538
|
const o = Math.min(Math.max(h.opacity, 0), 1);
|
|
2548
|
-
|
|
3539
|
+
style2.opacity = o >= 1 ? null : o;
|
|
2549
3540
|
}
|
|
2550
|
-
return { ...e, style };
|
|
3541
|
+
return { ...e, style: style2 };
|
|
2551
3542
|
}
|
|
2552
3543
|
function hovered(page, ids) {
|
|
2553
3544
|
return { ...page, elements: page.elements.map((e) => ids.has(e.id) ? hoveredElement(e) : e) };
|
|
@@ -2700,13 +3691,13 @@ function blocks(lines) {
|
|
|
2700
3691
|
flush();
|
|
2701
3692
|
return out;
|
|
2702
3693
|
}
|
|
2703
|
-
function parseList(lines,
|
|
2704
|
-
const first = reItem.exec(lines[
|
|
3694
|
+
function parseList(lines, start2) {
|
|
3695
|
+
const first = reItem.exec(lines[start2]);
|
|
2705
3696
|
const indent = first[1].length;
|
|
2706
3697
|
const bullet = /^\d/.test(first[2]) ? null : first[2];
|
|
2707
3698
|
const ordered = bullet === null ? parseInt(first[2], 10) : null;
|
|
2708
3699
|
const items = [];
|
|
2709
|
-
let i =
|
|
3700
|
+
let i = start2;
|
|
2710
3701
|
while (i < lines.length) {
|
|
2711
3702
|
const m = reItem.exec(lines[i]);
|
|
2712
3703
|
if (!m || m[1].length !== indent || bullet === null !== /^\d/.test(m[2]) || bullet !== null && m[2] !== bullet) break;
|
|
@@ -2751,14 +3742,14 @@ function parseList(lines, start) {
|
|
|
2751
3742
|
}
|
|
2752
3743
|
return { block: { kind: "list", ordered, items }, next: i };
|
|
2753
3744
|
}
|
|
2754
|
-
function inline(src,
|
|
3745
|
+
function inline(src, style2 = {}) {
|
|
2755
3746
|
const out = [];
|
|
2756
3747
|
const push = (text, s) => {
|
|
2757
3748
|
if (text) out.push({ ...s, text });
|
|
2758
3749
|
};
|
|
2759
3750
|
let i = 0, buf = "";
|
|
2760
3751
|
const emit = () => {
|
|
2761
|
-
push(buf,
|
|
3752
|
+
push(buf, style2);
|
|
2762
3753
|
buf = "";
|
|
2763
3754
|
};
|
|
2764
3755
|
while (i < src.length) {
|
|
@@ -2786,43 +3777,43 @@ function inline(src, style = {}) {
|
|
|
2786
3777
|
emit();
|
|
2787
3778
|
let code = m[2].replace(/\n/g, " ");
|
|
2788
3779
|
if (/^ .* $/.test(code)) code = code.slice(1, -1);
|
|
2789
|
-
push(code, { ...
|
|
3780
|
+
push(code, { ...style2, mono: true });
|
|
2790
3781
|
i += m[0].length;
|
|
2791
3782
|
continue;
|
|
2792
3783
|
}
|
|
2793
3784
|
if (m = /^!\[([^\]]*)\]\([^)]*\)/.exec(rest)) {
|
|
2794
3785
|
emit();
|
|
2795
|
-
out.push(...inline(m[1],
|
|
3786
|
+
out.push(...inline(m[1], style2));
|
|
2796
3787
|
i += m[0].length;
|
|
2797
3788
|
continue;
|
|
2798
3789
|
}
|
|
2799
3790
|
if (m = /^\[([^\]]+)\]\([^)]*\)/.exec(rest)) {
|
|
2800
3791
|
emit();
|
|
2801
|
-
out.push(...inline(m[1], { ...
|
|
3792
|
+
out.push(...inline(m[1], { ...style2, link: true }));
|
|
2802
3793
|
i += m[0].length;
|
|
2803
3794
|
continue;
|
|
2804
3795
|
}
|
|
2805
3796
|
if (m = /^<((?:https?|ftp|mailto):[^>\s]+)>/.exec(rest)) {
|
|
2806
3797
|
emit();
|
|
2807
|
-
push(m[1], { ...
|
|
3798
|
+
push(m[1], { ...style2, link: true });
|
|
2808
3799
|
i += m[0].length;
|
|
2809
3800
|
continue;
|
|
2810
3801
|
}
|
|
2811
3802
|
if (m = /^~~(?=\S)([\s\S]*?\S)~~/.exec(rest)) {
|
|
2812
3803
|
emit();
|
|
2813
|
-
out.push(...inline(m[1], { ...
|
|
3804
|
+
out.push(...inline(m[1], { ...style2, strike: true }));
|
|
2814
3805
|
i += m[0].length;
|
|
2815
3806
|
continue;
|
|
2816
3807
|
}
|
|
2817
3808
|
if (m = /^(\*\*|__)(?=\S)([\s\S]*?\S)\1/.exec(rest)) {
|
|
2818
3809
|
emit();
|
|
2819
|
-
out.push(...inline(m[2], { ...
|
|
3810
|
+
out.push(...inline(m[2], { ...style2, bold: true }));
|
|
2820
3811
|
i += m[0].length;
|
|
2821
3812
|
continue;
|
|
2822
3813
|
}
|
|
2823
3814
|
if ((m = /^(\*|_)(?=\S)([\s\S]*?\S)\1(?!\1)/.exec(rest)) && (m[1] === "*" || !/\w/.test(src[i - 1] ?? ""))) {
|
|
2824
3815
|
emit();
|
|
2825
|
-
out.push(...inline(m[2], { ...
|
|
3816
|
+
out.push(...inline(m[2], { ...style2, italic: true }));
|
|
2826
3817
|
i += m[0].length;
|
|
2827
3818
|
continue;
|
|
2828
3819
|
}
|
|
@@ -3035,10 +4026,10 @@ function layoutLines(paras, width, height, fontSize, fam) {
|
|
|
3035
4026
|
let k = 0;
|
|
3036
4027
|
let firstLine = true;
|
|
3037
4028
|
while (k < chars.length || firstLine && chars.length === 0) {
|
|
3038
|
-
const
|
|
4029
|
+
const start2 = firstLine ? p.firstIndent : p.indent;
|
|
3039
4030
|
const maxX2 = width;
|
|
3040
4031
|
const glyphs = [];
|
|
3041
|
-
let x =
|
|
4032
|
+
let x = start2, lastSpace = -1, end = k;
|
|
3042
4033
|
for (; end < chars.length; end++) {
|
|
3043
4034
|
const { ch, run } = chars[end];
|
|
3044
4035
|
const r = runs[run];
|
|
@@ -3087,7 +4078,7 @@ function layoutLines(paras, width, height, fontSize, fam) {
|
|
|
3087
4078
|
}
|
|
3088
4079
|
if (glyphs.length === 0 && end === k && k < chars.length) {
|
|
3089
4080
|
const { ch, run } = chars[k];
|
|
3090
|
-
glyphs.push({ ch, run, x:
|
|
4081
|
+
glyphs.push({ ch, run, x: start2, w: charWidth(ch, runs[run], fam) });
|
|
3091
4082
|
end = k + 1;
|
|
3092
4083
|
}
|
|
3093
4084
|
let asc = 0, desc = 0, lead = 0;
|
|
@@ -3113,13 +4104,38 @@ function layoutLines(paras, width, height, fontSize, fam) {
|
|
|
3113
4104
|
});
|
|
3114
4105
|
return lines;
|
|
3115
4106
|
}
|
|
4107
|
+
var Memo = class {
|
|
4108
|
+
constructor(limit) {
|
|
4109
|
+
__publicField(this, "map", /* @__PURE__ */ new Map());
|
|
4110
|
+
__publicField(this, "limit");
|
|
4111
|
+
this.limit = limit;
|
|
4112
|
+
}
|
|
4113
|
+
get(key, make2) {
|
|
4114
|
+
const hit = this.map.get(key);
|
|
4115
|
+
if (hit !== void 0) return hit;
|
|
4116
|
+
const v = make2();
|
|
4117
|
+
if (this.map.size >= this.limit) this.map.clear();
|
|
4118
|
+
this.map.set(key, v);
|
|
4119
|
+
return v;
|
|
4120
|
+
}
|
|
4121
|
+
};
|
|
4122
|
+
var layouts = new Memo(500);
|
|
4123
|
+
var markups = new Memo(500);
|
|
3116
4124
|
function noteMarkup(source, e) {
|
|
4125
|
+
const st = e.style, ink = st.stroke;
|
|
4126
|
+
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]);
|
|
4127
|
+
return markups.get(key, () => computeNoteMarkup(source, e));
|
|
4128
|
+
}
|
|
4129
|
+
function computeNoteMarkup(source, e) {
|
|
3117
4130
|
const rect2 = textRect({ x: e.x, y: e.y, width: e.width, height: e.height });
|
|
3118
4131
|
if (!(rect2.width > 1 && rect2.height > 1)) return "";
|
|
3119
4132
|
const fam = familyMetrics(e.style.font);
|
|
3120
|
-
const
|
|
3121
|
-
|
|
3122
|
-
|
|
4133
|
+
const layoutKey = JSON.stringify([...[rect2.width, rect2.height, e.style.fontSize].map(String), e.style.font ?? "", source]);
|
|
4134
|
+
const lines = layouts.get(layoutKey, () => {
|
|
4135
|
+
const b = new Builder(e.style.fontSize, rect2.width, fam);
|
|
4136
|
+
for (const block of parseBlocks(source)) b.block(block, { indent: 0, marker: null, quote: null, listDepth: 0 });
|
|
4137
|
+
return layoutLines(b.paras, rect2.width, rect2.height, e.style.fontSize, fam);
|
|
4138
|
+
});
|
|
3123
4139
|
const ink = color(e.style.stroke);
|
|
3124
4140
|
let s = "";
|
|
3125
4141
|
const marks = [];
|
|
@@ -3208,6 +4224,26 @@ function bounds(page, padding = 24) {
|
|
|
3208
4224
|
return inset(r, -padding, -padding);
|
|
3209
4225
|
}
|
|
3210
4226
|
function plan(page) {
|
|
4227
|
+
const hit = plans.get(page);
|
|
4228
|
+
if (hit && unchanged(page, hit.fields)) return hit.plan;
|
|
4229
|
+
const made = makePlan(page);
|
|
4230
|
+
plans.set(page, { fields: { ...page }, plan: made });
|
|
4231
|
+
return made;
|
|
4232
|
+
}
|
|
4233
|
+
var plans = /* @__PURE__ */ new WeakMap();
|
|
4234
|
+
function hasPlan(page) {
|
|
4235
|
+
const hit = plans.get(page);
|
|
4236
|
+
return !!hit && unchanged(page, hit.fields);
|
|
4237
|
+
}
|
|
4238
|
+
function rememberPlan(page, made) {
|
|
4239
|
+
plans.set(page, { fields: { ...page }, plan: made });
|
|
4240
|
+
}
|
|
4241
|
+
function unchanged(page, fields) {
|
|
4242
|
+
const a = page, b = fields;
|
|
4243
|
+
const keys = Object.keys(a);
|
|
4244
|
+
return keys.length === Object.keys(b).length && keys.every((k) => a[k] === b[k]);
|
|
4245
|
+
}
|
|
4246
|
+
function makePlan(page) {
|
|
3211
4247
|
const tl = page.timeline ?? { fps: 12, duration: 0, boil: null, settle: null, poster: null, tracks: [], camera: null, markers: null };
|
|
3212
4248
|
const times = frameTimes(tl);
|
|
3213
4249
|
const first = tl.camera?.find((k) => k.width > 1 && k.height > 1);
|
|
@@ -3235,8 +4271,8 @@ function svgOpen(b, background, paint) {
|
|
|
3235
4271
|
}
|
|
3236
4272
|
return out;
|
|
3237
4273
|
}
|
|
3238
|
-
function svgParts(page, background, fixed, links = false) {
|
|
3239
|
-
if (hasInstances(page)) return svgParts(expandingSymbols(page, 0), background, fixed, links);
|
|
4274
|
+
function svgParts(page, background, fixed, links = false, only) {
|
|
4275
|
+
if (hasInstances(page)) return svgParts(expandingSymbols(page, 0), background, fixed, links, only);
|
|
3240
4276
|
const theme = themeNamed(page.theme);
|
|
3241
4277
|
const bg = background ? theme?.background ?? background : null;
|
|
3242
4278
|
const ink = theme?.ink ?? black;
|
|
@@ -3247,11 +4283,11 @@ function svgParts(page, background, fixed, links = false) {
|
|
|
3247
4283
|
const index = indexOf(d);
|
|
3248
4284
|
const ordered = orderedElements(d);
|
|
3249
4285
|
const floors = ordered.filter((e) => visible.has(e.layerID) && isFloor(e));
|
|
3250
|
-
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"/>
|
|
4286
|
+
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"/>
|
|
3251
4287
|
`).join("");
|
|
3252
4288
|
if (rockMarkup) parts.push({ key: "rock", markup: rockMarkup });
|
|
3253
4289
|
for (const e of ordered) {
|
|
3254
|
-
if (!visible.has(e.layerID)) continue;
|
|
4290
|
+
if (!visible.has(e.layerID) || only && !only(e.id)) continue;
|
|
3255
4291
|
const alpha = Math.min(Math.max(e.style.opacity ?? 1, 0), 1);
|
|
3256
4292
|
if (alpha <= 0) continue;
|
|
3257
4293
|
let body = svgElement(e, index, bg, d);
|
|
@@ -3399,6 +4435,110 @@ function textElement(text, f, e, centered) {
|
|
|
3399
4435
|
return s + "</text>\n";
|
|
3400
4436
|
}
|
|
3401
4437
|
|
|
4438
|
+
// src/planner.ts
|
|
4439
|
+
var WORKER_WORK = 3e4;
|
|
4440
|
+
function planWork(page) {
|
|
4441
|
+
const tl = page.timeline;
|
|
4442
|
+
if (!tl || tl.camera?.some((k) => k.width > 1 && k.height > 1)) return 0;
|
|
4443
|
+
const elements = page.elements.length + (page.symbols ?? []).reduce((n, s) => n + s.elements.length, 0);
|
|
4444
|
+
return frameTimes(tl).length * elements;
|
|
4445
|
+
}
|
|
4446
|
+
function workerMaker() {
|
|
4447
|
+
if (typeof Worker === "undefined" || false) return null;
|
|
4448
|
+
if (false) {
|
|
4449
|
+
const source = `(${dottiePlayerScript.toString()})();
|
|
4450
|
+
`;
|
|
4451
|
+
return () => {
|
|
4452
|
+
const url = URL.createObjectURL(new Blob([source], { type: "text/javascript" }));
|
|
4453
|
+
try {
|
|
4454
|
+
return new Worker(url);
|
|
4455
|
+
} finally {
|
|
4456
|
+
setTimeout(() => URL.revokeObjectURL(url), 6e4);
|
|
4457
|
+
}
|
|
4458
|
+
};
|
|
4459
|
+
}
|
|
4460
|
+
if (true) {
|
|
4461
|
+
const here = import.meta.url;
|
|
4462
|
+
if (!here) return null;
|
|
4463
|
+
return () => new Worker(new URL("./dottie-player.min.js", here));
|
|
4464
|
+
}
|
|
4465
|
+
return null;
|
|
4466
|
+
}
|
|
4467
|
+
var make;
|
|
4468
|
+
var worker = null;
|
|
4469
|
+
var failed = false;
|
|
4470
|
+
var nextID = 1;
|
|
4471
|
+
var waiting = /* @__PURE__ */ new Map();
|
|
4472
|
+
function plansInWorker(page) {
|
|
4473
|
+
if (failed || hasPlan(page) || planWork(page) < WORKER_WORK) return false;
|
|
4474
|
+
if (make === void 0) make = workerMaker();
|
|
4475
|
+
return make !== null;
|
|
4476
|
+
}
|
|
4477
|
+
function planInWorker(page) {
|
|
4478
|
+
return new Promise((resolve2) => {
|
|
4479
|
+
const done = (p) => {
|
|
4480
|
+
if (p) rememberPlan(page, p);
|
|
4481
|
+
resolve2();
|
|
4482
|
+
};
|
|
4483
|
+
let w;
|
|
4484
|
+
try {
|
|
4485
|
+
w = worker ?? start();
|
|
4486
|
+
} catch {
|
|
4487
|
+
failed = true;
|
|
4488
|
+
done(null);
|
|
4489
|
+
return;
|
|
4490
|
+
}
|
|
4491
|
+
const id = nextID++;
|
|
4492
|
+
waiting.set(id, { page, done });
|
|
4493
|
+
try {
|
|
4494
|
+
w.postMessage({ dottiePlan: id, page });
|
|
4495
|
+
} catch {
|
|
4496
|
+
waiting.delete(id);
|
|
4497
|
+
done(null);
|
|
4498
|
+
}
|
|
4499
|
+
});
|
|
4500
|
+
}
|
|
4501
|
+
function start() {
|
|
4502
|
+
const w = make();
|
|
4503
|
+
w.onmessage = (ev) => {
|
|
4504
|
+
const { dottiePlan: id, plan: p } = ev.data ?? {};
|
|
4505
|
+
const job = id === void 0 ? void 0 : waiting.get(id);
|
|
4506
|
+
if (!job) return;
|
|
4507
|
+
waiting.delete(id);
|
|
4508
|
+
job.done(p ?? null);
|
|
4509
|
+
if (waiting.size === 0 && worker === w) {
|
|
4510
|
+
w.terminate();
|
|
4511
|
+
worker = null;
|
|
4512
|
+
}
|
|
4513
|
+
};
|
|
4514
|
+
w.onerror = (ev) => {
|
|
4515
|
+
ev.preventDefault?.();
|
|
4516
|
+
failed = true;
|
|
4517
|
+
if (worker === w) worker = null;
|
|
4518
|
+
w.terminate();
|
|
4519
|
+
const jobs = [...waiting.values()];
|
|
4520
|
+
waiting.clear();
|
|
4521
|
+
for (const job of jobs) job.done(null);
|
|
4522
|
+
};
|
|
4523
|
+
worker = w;
|
|
4524
|
+
return w;
|
|
4525
|
+
}
|
|
4526
|
+
function servePlans() {
|
|
4527
|
+
const g = globalThis;
|
|
4528
|
+
if (typeof g.WorkerGlobalScope === "undefined" || !(g instanceof g.WorkerGlobalScope)) return;
|
|
4529
|
+
g.addEventListener("message", (ev) => {
|
|
4530
|
+
const id = ev.data?.dottiePlan;
|
|
4531
|
+
if (typeof id !== "number") return;
|
|
4532
|
+
let p = null;
|
|
4533
|
+
try {
|
|
4534
|
+
p = plan(ev.data.page);
|
|
4535
|
+
} catch {
|
|
4536
|
+
p = null;
|
|
4537
|
+
}
|
|
4538
|
+
g.postMessage({ dottiePlan: id, plan: p });
|
|
4539
|
+
});
|
|
4540
|
+
}
|
|
4541
|
+
|
|
3402
4542
|
// src/player.ts
|
|
3403
4543
|
var WHITE = { r: 1, g: 1, b: 1, a: 1 };
|
|
3404
4544
|
function pageIndex(file, spec) {
|
|
@@ -3439,6 +4579,12 @@ var Playback = class {
|
|
|
3439
4579
|
__publicField(this, "sceneLoops");
|
|
3440
4580
|
/** The page's variables now: their starting values until clicks change them. */
|
|
3441
4581
|
__publicField(this, "state");
|
|
4582
|
+
/** When the variables last changed (performance.now() milliseconds): backlog piles up from then (ADR 0003). */
|
|
4583
|
+
__publicField(this, "stateSince", typeof performance === "undefined" ? 0 : performance.now());
|
|
4584
|
+
/** A still (the `at` attribute): backlog is drawn for the page time, not the wall clock. */
|
|
4585
|
+
__publicField(this, "still", false);
|
|
4586
|
+
/** What each queue held when the variables last changed, which it drains from (FrameMetrics.carry). */
|
|
4587
|
+
__publicField(this, "carried", {});
|
|
3442
4588
|
__publicField(this, "background");
|
|
3443
4589
|
__publicField(this, "ink");
|
|
3444
4590
|
/** What clicking each clickable element does, by element id. */
|
|
@@ -3462,7 +4608,7 @@ var Playback = class {
|
|
|
3462
4608
|
__publicField(this, "order", []);
|
|
3463
4609
|
__publicField(this, "frame", -1);
|
|
3464
4610
|
__publicField(this, "onFrame", null);
|
|
3465
|
-
this.page = options.clean ?
|
|
4611
|
+
this.page = options.clean ? cleanCopy(page) : page;
|
|
3466
4612
|
addFonts(usedFonts(this.page));
|
|
3467
4613
|
this.plan = plan(this.page);
|
|
3468
4614
|
this.clicks = clickTargets(page, options.file);
|
|
@@ -3510,6 +4656,10 @@ var Playback = class {
|
|
|
3510
4656
|
this.svg.style.maxWidth = `${Math.ceil(this.plan.size.width)}px`;
|
|
3511
4657
|
into.replaceChildren(this.svg);
|
|
3512
4658
|
}
|
|
4659
|
+
/** Seconds since the variables last changed, or null for a still. */
|
|
4660
|
+
sinceChange(now = typeof performance === "undefined" ? 0 : performance.now()) {
|
|
4661
|
+
return this.still || typeof performance === "undefined" ? null : (now - this.stateSince) / 1e3;
|
|
4662
|
+
}
|
|
3513
4663
|
get frameCount() {
|
|
3514
4664
|
return this.plan.times.length;
|
|
3515
4665
|
}
|
|
@@ -3535,7 +4685,7 @@ var Playback = class {
|
|
|
3535
4685
|
}
|
|
3536
4686
|
/** Whether a copy is still playing through after a click. */
|
|
3537
4687
|
replaying(now = performance.now()) {
|
|
3538
|
-
for (const [id,
|
|
4688
|
+
for (const [id, start2] of this.replayStarts) if (now - start2 < (this.replays.get(id) ?? 0) * 1e3) return true;
|
|
3539
4689
|
return false;
|
|
3540
4690
|
}
|
|
3541
4691
|
/** Draws the last moment drawn again, for copies replaying while the page stands still. */
|
|
@@ -3548,17 +4698,24 @@ var Playback = class {
|
|
|
3548
4698
|
const r = this.plan.rects[Math.min(i, this.plan.rects.length - 1)];
|
|
3549
4699
|
const now = typeof performance === "undefined" ? 0 : performance.now();
|
|
3550
4700
|
const times = /* @__PURE__ */ new Map();
|
|
3551
|
-
for (const [id,
|
|
4701
|
+
for (const [id, start2] of this.replayStarts) times.set(id, Math.min((now - start2) / 1e3, this.replays.get(id) ?? 0));
|
|
3552
4702
|
const tc = clampTime(t);
|
|
3553
|
-
const pose = posed(this.page, tc, this.state);
|
|
3554
|
-
const
|
|
4703
|
+
const pose = posed(this.page, tc, this.state, this.sinceChange(now), this.carried);
|
|
4704
|
+
const made = new Expansions(pose, tc, true);
|
|
4705
|
+
const frame2 = expandingSymbols(pose, tc, true, times, made);
|
|
3555
4706
|
const { parts } = svgParts(frame2, this.background, r);
|
|
3556
4707
|
const owned = (m, parts2) => {
|
|
3557
4708
|
for (const p of parts2) if (this.owner.has(p.key)) m.set(p.key, p.markup);
|
|
3558
4709
|
return m;
|
|
3559
4710
|
};
|
|
3560
|
-
const state = (st) => st.size === 0 ? /* @__PURE__ */ new Map() : owned(/* @__PURE__ */ new Map(), svgParts(
|
|
3561
|
-
|
|
4711
|
+
const state = (st) => st.size === 0 ? /* @__PURE__ */ new Map() : owned(/* @__PURE__ */ new Map(), svgParts(
|
|
4712
|
+
expandingSymbols(pose, tc, true, new Map([...times, ...st]), made),
|
|
4713
|
+
this.background,
|
|
4714
|
+
r,
|
|
4715
|
+
false,
|
|
4716
|
+
(key) => st.has(this.owner.get(key) ?? "")
|
|
4717
|
+
).parts);
|
|
4718
|
+
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);
|
|
3562
4719
|
const over = state(this.overTimes), down = state(this.downTimes), hit = state(this.hitTimes);
|
|
3563
4720
|
const normal = owned(/* @__PURE__ */ new Map(), parts);
|
|
3564
4721
|
const merged = [];
|
|
@@ -3687,6 +4844,14 @@ ${h}</g>
|
|
|
3687
4844
|
return { frame: next, stop: entered >= 0 && !this.sceneLoops[entered] };
|
|
3688
4845
|
}
|
|
3689
4846
|
};
|
|
4847
|
+
var cleanCopies = /* @__PURE__ */ new WeakMap();
|
|
4848
|
+
function cleanCopy(page) {
|
|
4849
|
+
const hit = cleanCopies.get(page);
|
|
4850
|
+
if (hit && unchanged(page, hit.fields)) return hit.page;
|
|
4851
|
+
const clean = withRoughness(page, 0);
|
|
4852
|
+
cleanCopies.set(page, { fields: { ...page }, page: clean });
|
|
4853
|
+
return clean;
|
|
4854
|
+
}
|
|
3690
4855
|
var ElementBase = typeof HTMLElement === "undefined" ? class {
|
|
3691
4856
|
} : HTMLElement;
|
|
3692
4857
|
var DottiePlayer = class extends ElementBase {
|
|
@@ -3709,6 +4874,10 @@ var DottiePlayer = class extends ElementBase {
|
|
|
3709
4874
|
__publicField(this, "raf", 0);
|
|
3710
4875
|
__publicField(this, "replayRaf", 0);
|
|
3711
4876
|
__publicField(this, "loadToken", 0);
|
|
4877
|
+
/** Counts mounts, so a plan that comes back from a worker after a newer one is dropped. */
|
|
4878
|
+
__publicField(this, "mountToken", 0);
|
|
4879
|
+
/** The time a still shows; null while the drawing is animated. */
|
|
4880
|
+
__publicField(this, "stillTime", null);
|
|
3712
4881
|
__publicField(this, "visible", true);
|
|
3713
4882
|
__publicField(this, "observer", null);
|
|
3714
4883
|
this.root = this.attachShadow({ mode: "open" });
|
|
@@ -3785,9 +4954,19 @@ var DottiePlayer = class extends ElementBase {
|
|
|
3785
4954
|
text = inline2.textContent ?? "";
|
|
3786
4955
|
}
|
|
3787
4956
|
if (token !== this.loadToken) return;
|
|
3788
|
-
this.
|
|
4957
|
+
this.showFile(parseDottie(text));
|
|
3789
4958
|
} catch (err) {
|
|
3790
4959
|
if (token !== this.loadToken) return;
|
|
4960
|
+
this.setPlaying(false);
|
|
4961
|
+
cancelAnimationFrame(this.raf);
|
|
4962
|
+
cancelAnimationFrame(this.replayRaf);
|
|
4963
|
+
this.raf = 0;
|
|
4964
|
+
this.replayRaf = 0;
|
|
4965
|
+
this.playback = null;
|
|
4966
|
+
this.file = null;
|
|
4967
|
+
this.mountToken++;
|
|
4968
|
+
this.stillTime = null;
|
|
4969
|
+
this.bar.hidden = true;
|
|
3791
4970
|
this.stage.innerHTML = "";
|
|
3792
4971
|
const box = document.createElement("div");
|
|
3793
4972
|
box.className = "error";
|
|
@@ -3798,15 +4977,36 @@ var DottiePlayer = class extends ElementBase {
|
|
|
3798
4977
|
}
|
|
3799
4978
|
/** Shows an already-parsed file. */
|
|
3800
4979
|
setFile(file) {
|
|
4980
|
+
this.loadToken++;
|
|
4981
|
+
this.showFile(file);
|
|
4982
|
+
}
|
|
4983
|
+
showFile(file) {
|
|
3801
4984
|
this.file = file;
|
|
3802
4985
|
for (const w of file.warnings) console.warn(`dottie-player: ${w}`);
|
|
3803
|
-
this.mount();
|
|
3804
|
-
this.dispatchEvent(new CustomEvent("load", { detail: { file } }));
|
|
4986
|
+
this.mount(() => this.dispatchEvent(new CustomEvent("load", { detail: { file } })));
|
|
3805
4987
|
}
|
|
3806
|
-
|
|
4988
|
+
/** Shows the file's page; then `mounted`. A big animation is planned in a worker first (planner.ts), and what was
|
|
4989
|
+
* showing stays until it is ready; a newer mount, file or failed load in the meantime wins. */
|
|
4990
|
+
mount(mounted) {
|
|
3807
4991
|
const file = this.file;
|
|
3808
4992
|
const page = file.pages[pageIndex(file, this.getAttribute("page"))];
|
|
3809
|
-
const
|
|
4993
|
+
const clean = this.hasAttribute("clean");
|
|
4994
|
+
const token = ++this.mountToken;
|
|
4995
|
+
const shown = clean ? cleanCopy(page) : page;
|
|
4996
|
+
if (!plansInWorker(shown)) {
|
|
4997
|
+
this.mountPage(file, page, clean);
|
|
4998
|
+
mounted?.();
|
|
4999
|
+
return;
|
|
5000
|
+
}
|
|
5001
|
+
void planInWorker(shown).then(() => {
|
|
5002
|
+
if (token !== this.mountToken || this.file !== file) return;
|
|
5003
|
+
this.mountPage(file, page, clean);
|
|
5004
|
+
mounted?.();
|
|
5005
|
+
});
|
|
5006
|
+
}
|
|
5007
|
+
mountPage(file, page, clean) {
|
|
5008
|
+
const p = new Playback(page, this.stage, { clean, file });
|
|
5009
|
+
p.still = this.hasAttribute("at");
|
|
3810
5010
|
this.playback = p;
|
|
3811
5011
|
this.style.setProperty("--dottie-page", color(p.background));
|
|
3812
5012
|
this.style.setProperty("--dottie-ink", color(p.ink));
|
|
@@ -3828,10 +5028,14 @@ var DottiePlayer = class extends ElementBase {
|
|
|
3828
5028
|
this.dispatchEvent(new CustomEvent("frame", { detail: { frame: i, time: p.plan.times[i] } }));
|
|
3829
5029
|
};
|
|
3830
5030
|
if (!animated) {
|
|
3831
|
-
|
|
5031
|
+
const i = at !== null ? nearest(p, Number(at) || 0) : p.posterFrame;
|
|
5032
|
+
this.stillTime = at !== null ? Number(at) || 0 : posterTime(page);
|
|
5033
|
+
p.drawAt(this.stillTime, i);
|
|
5034
|
+
p.frame = i;
|
|
3832
5035
|
this.setPlaying(false);
|
|
3833
5036
|
return;
|
|
3834
5037
|
}
|
|
5038
|
+
this.stillTime = null;
|
|
3835
5039
|
p.show(0);
|
|
3836
5040
|
const reduced = typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
3837
5041
|
if (reduced) {
|
|
@@ -3845,8 +5049,10 @@ var DottiePlayer = class extends ElementBase {
|
|
|
3845
5049
|
get paused() {
|
|
3846
5050
|
return !this.playing;
|
|
3847
5051
|
}
|
|
5052
|
+
/** The moment shown: a still's own time (its `at`, or the poster), else the current frame's. */
|
|
3848
5053
|
get currentTime() {
|
|
3849
5054
|
const p = this.playback;
|
|
5055
|
+
if (p && this.stillTime !== null) return this.stillTime;
|
|
3850
5056
|
return p && p.frame >= 0 ? p.plan.times[p.frame] : 0;
|
|
3851
5057
|
}
|
|
3852
5058
|
set currentTime(t) {
|
|
@@ -3900,7 +5106,13 @@ var DottiePlayer = class extends ElementBase {
|
|
|
3900
5106
|
this.scheduleReplay();
|
|
3901
5107
|
}
|
|
3902
5108
|
if (!click) return;
|
|
3903
|
-
if (click.set || click.toggle
|
|
5109
|
+
if (click.set || click.toggle || click.reset) {
|
|
5110
|
+
this.setState(applyClick(
|
|
5111
|
+
{ scene: null, url: null, set: click.set ?? null, toggle: click.toggle ?? null, reset: click.reset ?? null },
|
|
5112
|
+
p.state,
|
|
5113
|
+
p.page.variables ?? {}
|
|
5114
|
+
));
|
|
5115
|
+
}
|
|
3904
5116
|
if ("state" in click) return;
|
|
3905
5117
|
if ("url" in click) {
|
|
3906
5118
|
if (/^(https?:|mailto:)/i.test(click.url)) window.open(click.url, "_blank", "noopener");
|
|
@@ -3926,7 +5138,10 @@ var DottiePlayer = class extends ElementBase {
|
|
|
3926
5138
|
if (!p) return;
|
|
3927
5139
|
const next = { ...p.state, ...values };
|
|
3928
5140
|
if (Object.keys(next).every((k) => next[k] === p.state[k])) return;
|
|
5141
|
+
const t = clampTime(p.last.t);
|
|
5142
|
+
p.carried = posedWithMetrics(p.page, t, p.state, p.sinceChange(), p.carried).metrics.carry;
|
|
3929
5143
|
p.state = next;
|
|
5144
|
+
p.stateSince = performance.now();
|
|
3930
5145
|
p.redraw();
|
|
3931
5146
|
this.dispatchEvent(new CustomEvent("state", { detail: { variables: { ...next } } }));
|
|
3932
5147
|
}
|
|
@@ -4124,9 +5339,10 @@ function render(source, options = {}) {
|
|
|
4124
5339
|
function isFile(v) {
|
|
4125
5340
|
return typeof v === "object" && v !== null && Array.isArray(v.pages) && Array.isArray(v.warnings);
|
|
4126
5341
|
}
|
|
4127
|
-
var VERSION = "0.
|
|
5342
|
+
var VERSION = "0.9.0";
|
|
4128
5343
|
|
|
4129
5344
|
// src/player-entry.ts
|
|
5345
|
+
servePlans();
|
|
4130
5346
|
definePlayer();
|
|
4131
5347
|
globalThis.Dottie ?? (globalThis.Dottie = index_exports);
|
|
4132
5348
|
export {
|