@phin-tech/dottie 0.7.0 → 0.8.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 +3 -0
- package/README.md +3 -2
- package/dist/compile.js +97 -10
- package/dist/dottie-compile.mjs +98 -11
- package/dist/dottie-player.js +879 -43
- package/dist/dottie-player.min.js +55 -53
- package/dist/dottie.js +879 -43
- package/dist/types/clicks.d.ts +1 -0
- package/dist/types/flows.d.ts +26 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/measure.d.ts +58 -0
- package/dist/types/model.d.ts +66 -0
- package/dist/types/player.d.ts +12 -1
- package/dist/types/state.d.ts +28 -8
- package/dist/types/timeline.d.ts +10 -2
- package/package.json +1 -1
package/dist/dottie.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! dottie.js 0.
|
|
1
|
+
/*! dottie.js 0.8.0 | Apache-2.0 | https://github.com/phin-tech/dottie */
|
|
2
2
|
var __defProp = Object.defineProperty;
|
|
3
3
|
var __defNormalProp = (obj, key, value2) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value: value2 }) : obj[key] = value2;
|
|
4
4
|
var __publicField = (obj, key, value2) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value2);
|
|
@@ -8,10 +8,53 @@ var MAX_NAME = 64;
|
|
|
8
8
|
function isName(name) {
|
|
9
9
|
return name.length <= MAX_NAME && /^[A-Za-z_][A-Za-z0-9_]*$/.test(name);
|
|
10
10
|
}
|
|
11
|
+
var METRIC_NAMES = ["throughput", "load", "delay", "backlog", "depth", "inFlight", "transit", "backup"];
|
|
12
|
+
var OPS = [">=", "<=", "==", "!=", ">", "<"];
|
|
13
|
+
function metricTerm(term) {
|
|
14
|
+
const t = trim(term);
|
|
15
|
+
if (!t.includes("(")) {
|
|
16
|
+
const op2 = t.search(/[>=<!]/);
|
|
17
|
+
if (op2 < 0) return null;
|
|
18
|
+
const name2 = trim(t.slice(0, op2));
|
|
19
|
+
if (!METRIC_NAMES.includes(name2)) return null;
|
|
20
|
+
const m = metricTerm(name2 + "() " + t.slice(op2));
|
|
21
|
+
return m ? { ...m, target: "" } : null;
|
|
22
|
+
}
|
|
23
|
+
const open = t.indexOf("("), close = t.indexOf(")");
|
|
24
|
+
if (open < 0 || close < 0 || open >= close) return null;
|
|
25
|
+
const name = trim(t.slice(0, open)), target = trim(t.slice(open + 1, close));
|
|
26
|
+
let rest = trim(t.slice(close + 1));
|
|
27
|
+
if (!METRIC_NAMES.includes(name) || target.includes("(")) return null;
|
|
28
|
+
const op = OPS.find((o) => rest.startsWith(o));
|
|
29
|
+
if (op === void 0) return null;
|
|
30
|
+
rest = trim(rest.slice(op.length));
|
|
31
|
+
if (!/^-?[0-9]+(\.[0-9]+)?$/.test(rest)) return null;
|
|
32
|
+
return { metric: name, target, op, value: Number(rest) };
|
|
33
|
+
}
|
|
34
|
+
function passes(m, v) {
|
|
35
|
+
switch (m.op) {
|
|
36
|
+
case ">":
|
|
37
|
+
return v > m.value;
|
|
38
|
+
case ">=":
|
|
39
|
+
return v >= m.value;
|
|
40
|
+
case "<":
|
|
41
|
+
return v < m.value;
|
|
42
|
+
case "<=":
|
|
43
|
+
return v <= m.value;
|
|
44
|
+
case "==":
|
|
45
|
+
return v === m.value;
|
|
46
|
+
default:
|
|
47
|
+
return v !== m.value;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function mentionsMetric(condition) {
|
|
51
|
+
return condition.split("||").some((any) => any.split("&&").some((all) => metricTerm(all) !== null));
|
|
52
|
+
}
|
|
11
53
|
function conditionNames(condition) {
|
|
12
54
|
const out = [];
|
|
13
55
|
for (const any of condition.split("||")) {
|
|
14
56
|
for (const all of any.split("&&")) {
|
|
57
|
+
if (metricTerm(all) !== null) continue;
|
|
15
58
|
let term = trim(all);
|
|
16
59
|
if (term.startsWith("!")) term = trim(term.slice(1));
|
|
17
60
|
if (!isName(term)) return null;
|
|
@@ -20,9 +63,14 @@ function conditionNames(condition) {
|
|
|
20
63
|
}
|
|
21
64
|
return out;
|
|
22
65
|
}
|
|
23
|
-
function holds(condition, state) {
|
|
66
|
+
function holds(condition, state, metric = null) {
|
|
24
67
|
if (conditionNames(condition) === null) return false;
|
|
25
68
|
return condition.split("||").some((any) => any.split("&&").every((all) => {
|
|
69
|
+
const m = metricTerm(all);
|
|
70
|
+
if (m !== null) {
|
|
71
|
+
const v = metric ? metric(m) : null;
|
|
72
|
+
return v !== null && passes(m, v);
|
|
73
|
+
}
|
|
26
74
|
let term = trim(all);
|
|
27
75
|
let negated = false;
|
|
28
76
|
if (term.startsWith("!")) {
|
|
@@ -44,11 +92,40 @@ function stateHidden(page, choice) {
|
|
|
44
92
|
for (const e of page.elements) if (e.when !== null && !holds(e.when, values)) out.add(e.id);
|
|
45
93
|
return out;
|
|
46
94
|
}
|
|
95
|
+
function activeOverrides(e, values) {
|
|
96
|
+
if (!values || !e.overrides) return [];
|
|
97
|
+
return e.overrides.filter((o) => !mentionsMetric(o.when) && holds(o.when, values));
|
|
98
|
+
}
|
|
99
|
+
function looksOnly(o) {
|
|
100
|
+
return { ...o, reroute: null, offline: null, cost: null, capacity: null, delay: null };
|
|
101
|
+
}
|
|
102
|
+
function applyOverride(o, e) {
|
|
103
|
+
const out = cloneElement(e);
|
|
104
|
+
if (o.stroke) out.style.stroke = { ...o.stroke };
|
|
105
|
+
if (o.fill) out.style.fill = { ...o.fill };
|
|
106
|
+
if (o.textColor) out.style.textColor = { ...o.textColor };
|
|
107
|
+
if (o.strokeWidth !== null) out.style.strokeWidth = Math.max(0, o.strokeWidth);
|
|
108
|
+
if (o.dash !== null) out.style.dash = o.dash;
|
|
109
|
+
if (o.text !== null) out.text = o.text;
|
|
110
|
+
if (o.cost !== null && out.connector) out.connector.cost = Math.min(Math.max(o.cost, 0), 1e6);
|
|
111
|
+
if (o.capacity !== null) out.capacity = Math.max(o.capacity, 0);
|
|
112
|
+
if (o.delay !== null && out.kind !== "connector") out.delay = Math.max(o.delay, 0);
|
|
113
|
+
if (o.opacity !== null) {
|
|
114
|
+
const v = Math.min(Math.max(o.opacity, 0), 1) * (out.style.opacity ?? 1);
|
|
115
|
+
out.style.opacity = v >= 1 ? null : v;
|
|
116
|
+
}
|
|
117
|
+
return out;
|
|
118
|
+
}
|
|
47
119
|
function changesState(c) {
|
|
48
|
-
return !!c && (Object.keys(c.set ?? {}).length > 0 || (c.toggle ?? []).length > 0);
|
|
120
|
+
return !!c && (c.reset === true || Object.keys(c.set ?? {}).length > 0 || (c.toggle ?? []).length > 0);
|
|
49
121
|
}
|
|
50
|
-
function applyClick(c, state) {
|
|
51
|
-
|
|
122
|
+
function applyClick(c, state, initial = {}) {
|
|
123
|
+
let out = { ...state };
|
|
124
|
+
if (c.reset === true) {
|
|
125
|
+
out = {};
|
|
126
|
+
for (const k of Object.keys(state)) out[k] = false;
|
|
127
|
+
Object.assign(out, initial);
|
|
128
|
+
}
|
|
52
129
|
for (const [k, v] of Object.entries(c.set ?? {})) out[k] = v;
|
|
53
130
|
for (const k of c.toggle ?? []) out[k] = !(out[k] ?? false);
|
|
54
131
|
return out;
|
|
@@ -63,7 +140,9 @@ var KINDS = ["rectangle", "ellipse", "diamond", "text", "connector", "freehand",
|
|
|
63
140
|
var EASES = ["linear", "easeIn", "easeOut", "easeInOut", "hold", "spring", "bounce", "elastic"];
|
|
64
141
|
var KNOWN_FIELDS = {
|
|
65
142
|
file: ["version", "pages", "images"],
|
|
66
|
-
page: ["id", "name", "layers", "elements", "theme", "guidesX", "guidesY", "guidesLocked", "timeline", "symbols", "variables"],
|
|
143
|
+
page: ["id", "name", "layers", "elements", "theme", "guidesX", "guidesY", "guidesLocked", "timeline", "symbols", "variables", "flows"],
|
|
144
|
+
flow: ["id", "from", "to", "every", "speed", "color", "size", "when", "spread", "volume", "overrides"],
|
|
145
|
+
flowOverride: ["when", "volume"],
|
|
67
146
|
symbol: ["id", "name", "width", "height", "layers", "elements", "timeline", "button", "params"],
|
|
68
147
|
layer: ["id", "name", "visible", "locked"],
|
|
69
148
|
rgba: ["r", "g", "b", "a"],
|
|
@@ -92,6 +171,7 @@ var KNOWN_FIELDS = {
|
|
|
92
171
|
"click",
|
|
93
172
|
"hover",
|
|
94
173
|
"when",
|
|
174
|
+
"overrides",
|
|
95
175
|
"symbol",
|
|
96
176
|
"playback",
|
|
97
177
|
"offset",
|
|
@@ -100,8 +180,13 @@ var KNOWN_FIELDS = {
|
|
|
100
180
|
"reveal",
|
|
101
181
|
"ports",
|
|
102
182
|
"labelRect",
|
|
103
|
-
"extension"
|
|
183
|
+
"extension",
|
|
184
|
+
"capacity",
|
|
185
|
+
"delay",
|
|
186
|
+
"queue",
|
|
187
|
+
"gauge"
|
|
104
188
|
],
|
|
189
|
+
gauge: ["metric", "of", "max", "warn", "alarm", "style", "label"],
|
|
105
190
|
strokePoint: ["x", "y"],
|
|
106
191
|
subpath: ["closed", "coords"],
|
|
107
192
|
inlineImage: ["data", "format"],
|
|
@@ -109,7 +194,7 @@ var KNOWN_FIELDS = {
|
|
|
109
194
|
extensionRef: ["id", "shape", "version", "params"],
|
|
110
195
|
normalizedPoint: ["x", "y"],
|
|
111
196
|
normalizedRect: ["x", "y", "width", "height"],
|
|
112
|
-
connector: ["start", "end", "routing", "startHead", "endHead", "waypoints", "hops"],
|
|
197
|
+
connector: ["start", "end", "routing", "startHead", "endHead", "waypoints", "hops", "cost"],
|
|
113
198
|
waypoint: ["x", "y"],
|
|
114
199
|
timeline: ["fps", "duration", "boil", "settle", "poster", "tracks", "camera", "markers"],
|
|
115
200
|
track: ["element", "keys", "boil", "follow", "repeats", "march"],
|
|
@@ -118,8 +203,9 @@ var KNOWN_FIELDS = {
|
|
|
118
203
|
repeat: ["start", "end", "count"],
|
|
119
204
|
cameraKey: ["t", "x", "y", "width", "height", "ease"],
|
|
120
205
|
marker: ["t", "name", "loop"],
|
|
121
|
-
click: ["scene", "url", "set", "toggle"],
|
|
122
|
-
hover: ["stroke", "fill", "opacity"]
|
|
206
|
+
click: ["scene", "url", "set", "toggle", "reset"],
|
|
207
|
+
hover: ["stroke", "fill", "opacity"],
|
|
208
|
+
override: ["when", "stroke", "fill", "textColor", "opacity", "strokeWidth", "dash", "text", "reroute", "offline", "cost", "capacity", "delay"]
|
|
123
209
|
};
|
|
124
210
|
var MAX_DURATION = 3600;
|
|
125
211
|
var MAX_MARCH = 1e4;
|
|
@@ -135,6 +221,8 @@ var num = (v) => typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
|
135
221
|
var str = (v) => typeof v === "string" ? v : void 0;
|
|
136
222
|
var strings = (v) => isObj(v) && Object.values(v).every((x) => typeof x === "string") ? { ...v } : null;
|
|
137
223
|
var bool = (v) => typeof v === "boolean" ? v : void 0;
|
|
224
|
+
var NO_ID = "00000000-0000-0000-0000-000000000000";
|
|
225
|
+
var UUID = /^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$/;
|
|
138
226
|
var uuid = (v) => typeof v === "string" ? v.toUpperCase() : void 0;
|
|
139
227
|
function clampTime(t) {
|
|
140
228
|
if (!Number.isFinite(t)) return t === Infinity ? MAX_DURATION : 0;
|
|
@@ -226,7 +314,8 @@ var Reader = class {
|
|
|
226
314
|
this.fields(w, "waypoint");
|
|
227
315
|
return { x: num(w.x) ?? 0, y: num(w.y) ?? 0 };
|
|
228
316
|
}) : null,
|
|
229
|
-
hops: typeof v.hops === "boolean" ? v.hops : null
|
|
317
|
+
hops: typeof v.hops === "boolean" ? v.hops : null,
|
|
318
|
+
cost: typeof v.cost === "number" && Number.isFinite(v.cost) ? v.cost : null
|
|
230
319
|
};
|
|
231
320
|
}
|
|
232
321
|
image(v, table) {
|
|
@@ -278,6 +367,7 @@ var Reader = class {
|
|
|
278
367
|
click: this.click(v.click),
|
|
279
368
|
hover: this.hover(v.hover),
|
|
280
369
|
when: str(v.when) ?? null,
|
|
370
|
+
overrides: this.overrides(v.overrides),
|
|
281
371
|
symbol: uuid(v.symbol) ?? null,
|
|
282
372
|
playback: this.playback(v.playback),
|
|
283
373
|
offset: num(v.offset) ?? null,
|
|
@@ -289,7 +379,26 @@ var Reader = class {
|
|
|
289
379
|
return { x: num(o.x) ?? 0, y: num(o.y) ?? 0 };
|
|
290
380
|
}) : null,
|
|
291
381
|
labelRect: this.labelRect(v.labelRect),
|
|
292
|
-
extension: this.extension(v.extension)
|
|
382
|
+
extension: this.extension(v.extension),
|
|
383
|
+
capacity: num(v.capacity) ?? null,
|
|
384
|
+
delay: num(v.delay) ?? null,
|
|
385
|
+
queue: v.queue === true ? true : null,
|
|
386
|
+
gauge: this.gauge(v.gauge)
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
gauge(v) {
|
|
390
|
+
if (!isObj(v)) return null;
|
|
391
|
+
this.fields(v, "gauge");
|
|
392
|
+
const finite = (x) => typeof x === "number" && Number.isFinite(x) ? x : null;
|
|
393
|
+
const style2 = str(v.style);
|
|
394
|
+
return {
|
|
395
|
+
metric: str(v.metric) ?? "",
|
|
396
|
+
of: typeof v.of === "string" && UUID.test(v.of) ? v.of.toUpperCase() : NO_ID,
|
|
397
|
+
max: finite(v.max),
|
|
398
|
+
warn: finite(v.warn),
|
|
399
|
+
alarm: finite(v.alarm),
|
|
400
|
+
style: style2 === "number" || style2 === "bar" || style2 === "dial" ? style2 : null,
|
|
401
|
+
label: str(v.label) ?? null
|
|
293
402
|
};
|
|
294
403
|
}
|
|
295
404
|
labelRect(v) {
|
|
@@ -333,7 +442,7 @@ var Reader = class {
|
|
|
333
442
|
}
|
|
334
443
|
const raw = typeof v.toggle === "string" ? [v.toggle] : Array.isArray(v.toggle) && v.toggle.every((x) => typeof x === "string") ? v.toggle : null;
|
|
335
444
|
const toggle = raw ? raw.filter(isName) : null;
|
|
336
|
-
return { scene: str(v.scene) ?? null, url: safe, set, toggle: toggle && toggle.length > 0 ? toggle : null };
|
|
445
|
+
return { scene: str(v.scene) ?? null, url: safe, set, toggle: toggle && toggle.length > 0 ? toggle : null, reset: v.reset === true ? true : null };
|
|
337
446
|
}
|
|
338
447
|
playback(v) {
|
|
339
448
|
const s = str(v);
|
|
@@ -369,6 +478,35 @@ var Reader = class {
|
|
|
369
478
|
params: strings(v.params)
|
|
370
479
|
};
|
|
371
480
|
}
|
|
481
|
+
/** As DottieKit reads them: a reroute whose targets aren't all ids is dropped, as are keys that aren't ids. */
|
|
482
|
+
overrides(v) {
|
|
483
|
+
if (!Array.isArray(v)) return null;
|
|
484
|
+
return v.filter(isObj).map((o) => {
|
|
485
|
+
this.fields(o, "override");
|
|
486
|
+
const dash = str(o.dash);
|
|
487
|
+
let reroute = null;
|
|
488
|
+
if (isObj(o.reroute) && Object.values(o.reroute).every((x) => typeof x === "string" && UUID.test(x))) {
|
|
489
|
+
reroute = {};
|
|
490
|
+
for (const [k, x] of Object.entries(o.reroute)) if (UUID.test(k)) reroute[k.toUpperCase()] = x.toUpperCase();
|
|
491
|
+
if (Object.keys(reroute).length === 0) reroute = null;
|
|
492
|
+
}
|
|
493
|
+
return {
|
|
494
|
+
when: str(o.when) ?? "",
|
|
495
|
+
stroke: rgba(o.stroke) ?? null,
|
|
496
|
+
fill: rgba(o.fill) ?? null,
|
|
497
|
+
textColor: rgba(o.textColor) ?? null,
|
|
498
|
+
opacity: num(o.opacity) ?? null,
|
|
499
|
+
strokeWidth: num(o.strokeWidth) ?? null,
|
|
500
|
+
dash: dash !== void 0 && ["solid", "dashed", "dotted"].includes(dash) ? dash : null,
|
|
501
|
+
text: str(o.text) ?? null,
|
|
502
|
+
reroute,
|
|
503
|
+
offline: o.offline === true ? true : null,
|
|
504
|
+
cost: typeof o.cost === "number" && Number.isFinite(o.cost) ? o.cost : null,
|
|
505
|
+
capacity: typeof o.capacity === "number" && Number.isFinite(o.capacity) ? o.capacity : null,
|
|
506
|
+
delay: typeof o.delay === "number" && Number.isFinite(o.delay) ? o.delay : null
|
|
507
|
+
};
|
|
508
|
+
});
|
|
509
|
+
}
|
|
372
510
|
hover(v) {
|
|
373
511
|
if (!isObj(v)) return null;
|
|
374
512
|
this.fields(v, "hover");
|
|
@@ -468,7 +606,33 @@ var Reader = class {
|
|
|
468
606
|
theme: str(v.theme) ?? null,
|
|
469
607
|
timeline: this.timeline(v.timeline),
|
|
470
608
|
symbols: Array.isArray(v.symbols) ? v.symbols.map((x) => this.symbol(x, images)).filter((x) => x !== null) : null,
|
|
471
|
-
variables: this.variables(v.variables)
|
|
609
|
+
variables: this.variables(v.variables),
|
|
610
|
+
flows: Array.isArray(v.flows) ? v.flows.filter(isObj).map((f) => this.flow(f)) : null
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
/** As DottieKit reads one: a missing or bad id, source or destination sends nothing; bad destinations are dropped. */
|
|
614
|
+
flow(f) {
|
|
615
|
+
this.fields(f, "flow");
|
|
616
|
+
const id = (x) => typeof x === "string" && UUID.test(x) ? x.toUpperCase() : NO_ID;
|
|
617
|
+
const finite = (x) => {
|
|
618
|
+
const n = num(x);
|
|
619
|
+
return n !== void 0 && Number.isFinite(n) ? n : null;
|
|
620
|
+
};
|
|
621
|
+
return {
|
|
622
|
+
id: id(f.id),
|
|
623
|
+
from: id(f.from),
|
|
624
|
+
to: Array.isArray(f.to) && f.to.every((x) => typeof x === "string") ? f.to.filter((x) => UUID.test(x)).map((x) => x.toUpperCase()) : [],
|
|
625
|
+
every: finite(f.every),
|
|
626
|
+
speed: finite(f.speed),
|
|
627
|
+
size: finite(f.size),
|
|
628
|
+
color: isObj(f.color) && ["r", "g", "b", "a"].every((k) => typeof f.color[k] === "number") ? rgba(f.color) ?? null : null,
|
|
629
|
+
when: str(f.when) ?? null,
|
|
630
|
+
spread: f.spread === true ? true : null,
|
|
631
|
+
volume: finite(f.volume),
|
|
632
|
+
overrides: Array.isArray(f.overrides) && f.overrides.length > 0 ? f.overrides.filter(isObj).map((o) => {
|
|
633
|
+
this.fields(o, "flowOverride");
|
|
634
|
+
return { when: str(o.when) ?? "", volume: finite(o.volume) };
|
|
635
|
+
}) : null
|
|
472
636
|
};
|
|
473
637
|
}
|
|
474
638
|
};
|
|
@@ -530,7 +694,8 @@ function cloneElement(e) {
|
|
|
530
694
|
start: { ...e.connector.start },
|
|
531
695
|
end: { ...e.connector.end },
|
|
532
696
|
waypoints: e.connector.waypoints?.map((w) => ({ ...w })) ?? null
|
|
533
|
-
} : null
|
|
697
|
+
} : null,
|
|
698
|
+
gauge: e.gauge ? { ...e.gauge } : e.gauge
|
|
534
699
|
};
|
|
535
700
|
}
|
|
536
701
|
function orderedElements(page) {
|
|
@@ -1281,8 +1446,617 @@ function mulConst(step) {
|
|
|
1281
1446
|
return [p[0], p[1]];
|
|
1282
1447
|
}
|
|
1283
1448
|
|
|
1449
|
+
// src/flows.ts
|
|
1450
|
+
var DEFAULT_EVERY = 0.5;
|
|
1451
|
+
var DEFAULT_SPEED = 160;
|
|
1452
|
+
var DEFAULT_SIZE = 12;
|
|
1453
|
+
var DEFAULT_COLOR = { r: 0.95, g: 0.36, b: 0.33, a: 1 };
|
|
1454
|
+
var clamp = (v, fallback, lo, hi) => v === null || !Number.isFinite(v) ? fallback : Math.min(Math.max(v, lo), hi);
|
|
1455
|
+
function packetID(flow, k) {
|
|
1456
|
+
const n = (k + 2 ** 40) % 2 ** 48;
|
|
1457
|
+
return flow.slice(0, 24) + (n < 0 ? n + 2 ** 48 : n).toString(16).toUpperCase().padStart(12, "0");
|
|
1458
|
+
}
|
|
1459
|
+
var routeCost = (cost) => cost === null || !Number.isFinite(cost) ? 1 : Math.min(Math.max(cost, 0), 1e6);
|
|
1460
|
+
function flowTree(from, frame2, offline = /* @__PURE__ */ new Set()) {
|
|
1461
|
+
const shapes2 = new Set(frame2.elements.filter((e) => e.kind !== "connector" && !offline.has(e.id)).map((e) => e.id));
|
|
1462
|
+
if (!shapes2.has(from)) return null;
|
|
1463
|
+
const next = /* @__PURE__ */ new Map();
|
|
1464
|
+
const add2 = (a, edge) => {
|
|
1465
|
+
const list = next.get(a);
|
|
1466
|
+
if (list) list.push(edge);
|
|
1467
|
+
else next.set(a, [edge]);
|
|
1468
|
+
};
|
|
1469
|
+
for (const e of frame2.elements) {
|
|
1470
|
+
const c = e.connector;
|
|
1471
|
+
const a = c?.start.elementID ?? null, b = c?.end.elementID ?? null;
|
|
1472
|
+
if (!c || offline.has(e.id) || a === null || b === null || a === b || !shapes2.has(a) || !shapes2.has(b)) continue;
|
|
1473
|
+
const cost = routeCost(c.cost);
|
|
1474
|
+
add2(a, { to: b, via: e.id, back: false, cost });
|
|
1475
|
+
if (c.startHead !== "none") add2(b, { to: a, via: e.id, back: true, cost });
|
|
1476
|
+
}
|
|
1477
|
+
const dist = /* @__PURE__ */ new Map([[from, 0]]), order = /* @__PURE__ */ new Map([[from, 0]]);
|
|
1478
|
+
let counter = 1;
|
|
1479
|
+
const tree = new FlowTree(from);
|
|
1480
|
+
const open = [from];
|
|
1481
|
+
while (open.length > 0) {
|
|
1482
|
+
let best = 0;
|
|
1483
|
+
for (let i = 1; i < open.length; i++) {
|
|
1484
|
+
const da = dist.get(open[i]), db = dist.get(open[best]);
|
|
1485
|
+
if (da < db || da === db && order.get(open[i]) < order.get(open[best])) best = i;
|
|
1486
|
+
}
|
|
1487
|
+
const n = open.splice(best, 1)[0];
|
|
1488
|
+
tree.settled.add(n);
|
|
1489
|
+
for (const edge of next.get(n) ?? []) {
|
|
1490
|
+
if (tree.settled.has(edge.to)) continue;
|
|
1491
|
+
const d = dist.get(n) + edge.cost;
|
|
1492
|
+
const old = dist.get(edge.to);
|
|
1493
|
+
if (old !== void 0 && old <= d) continue;
|
|
1494
|
+
if (old === void 0) open.push(edge.to);
|
|
1495
|
+
dist.set(edge.to, d);
|
|
1496
|
+
order.set(edge.to, counter++);
|
|
1497
|
+
tree.came.set(edge.to, { from: n, via: edge.via, back: edge.back });
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
return tree;
|
|
1501
|
+
}
|
|
1502
|
+
var FlowTree = class {
|
|
1503
|
+
constructor(from) {
|
|
1504
|
+
__publicField(this, "settled", /* @__PURE__ */ new Set());
|
|
1505
|
+
__publicField(this, "came", /* @__PURE__ */ new Map());
|
|
1506
|
+
__publicField(this, "from");
|
|
1507
|
+
this.from = from;
|
|
1508
|
+
}
|
|
1509
|
+
/** The first of `to` that can be reached; with `spread`, when the first can't be, every other one that can. */
|
|
1510
|
+
goals(to, spread) {
|
|
1511
|
+
const reachable = to.filter((x) => x !== this.from && this.settled.has(x));
|
|
1512
|
+
if (reachable.length === 0) return [];
|
|
1513
|
+
return spread && reachable[0] !== to[0] ? reachable : [reachable[0]];
|
|
1514
|
+
}
|
|
1515
|
+
/** The connectors to `goal`, as [connector, backwards] hops. */
|
|
1516
|
+
hops(goal) {
|
|
1517
|
+
const hops = [];
|
|
1518
|
+
for (let at = goal, step = this.came.get(at); step; at = step.from, step = this.came.get(at)) hops.push([step.via, step.back]);
|
|
1519
|
+
return hops.reverse();
|
|
1520
|
+
}
|
|
1521
|
+
};
|
|
1522
|
+
|
|
1523
|
+
// src/measure.ts
|
|
1524
|
+
var emptyMetrics = () => ({ elements: /* @__PURE__ */ new Map(), flows: /* @__PURE__ */ new Map(), carry: {} });
|
|
1525
|
+
function metricValue(m, metric, id) {
|
|
1526
|
+
const f = m.flows.get(id);
|
|
1527
|
+
if (f) {
|
|
1528
|
+
switch (metric) {
|
|
1529
|
+
case "throughput":
|
|
1530
|
+
return f.placed;
|
|
1531
|
+
case "backlog":
|
|
1532
|
+
return f.backlog;
|
|
1533
|
+
case "backup":
|
|
1534
|
+
return f.backup;
|
|
1535
|
+
case "transit":
|
|
1536
|
+
return f.transit;
|
|
1537
|
+
default:
|
|
1538
|
+
return null;
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
const e = m.elements.get(id);
|
|
1542
|
+
if (!e) return null;
|
|
1543
|
+
switch (metric) {
|
|
1544
|
+
case "throughput":
|
|
1545
|
+
return e.throughput;
|
|
1546
|
+
case "load":
|
|
1547
|
+
return e.load;
|
|
1548
|
+
case "delay":
|
|
1549
|
+
return e.delay;
|
|
1550
|
+
case "backlog":
|
|
1551
|
+
return e.backlog;
|
|
1552
|
+
case "inFlight":
|
|
1553
|
+
return e.inFlight;
|
|
1554
|
+
case "depth":
|
|
1555
|
+
return e.depth;
|
|
1556
|
+
default:
|
|
1557
|
+
return null;
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
var TINY = 1e-9;
|
|
1561
|
+
var amount = (f) => clamp(f.volume, 1, 0, 1e12);
|
|
1562
|
+
function amountIn(f, values) {
|
|
1563
|
+
if (values === null || !f.overrides) return amount(f);
|
|
1564
|
+
let v = null;
|
|
1565
|
+
for (const o of f.overrides) if (o.volume !== null && !mentionsMetric(o.when) && holds(o.when, values)) v = o.volume;
|
|
1566
|
+
return v === null ? amount(f) : Math.min(Math.max(Number.isFinite(v) ? v : 1, 0), 1e12);
|
|
1567
|
+
}
|
|
1568
|
+
function delayOf(e) {
|
|
1569
|
+
const d = e.delay ?? null;
|
|
1570
|
+
if (e.kind === "connector" || d === null || !Number.isFinite(d)) return 0;
|
|
1571
|
+
return Math.min(Math.max(d, 0), 3600);
|
|
1572
|
+
}
|
|
1573
|
+
var queueKey = (shape, flow) => shape + "|" + flow;
|
|
1574
|
+
function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set(), sinceChange = null, carried = {}) {
|
|
1575
|
+
const since = Math.max(0, Math.min(sinceChange ?? t, 1e6));
|
|
1576
|
+
const byID = /* @__PURE__ */ new Map();
|
|
1577
|
+
for (const e of frame2.elements) if (!byID.has(e.id)) byID.set(e.id, e);
|
|
1578
|
+
const metrics2 = emptyMetrics();
|
|
1579
|
+
for (const e of frame2.elements) {
|
|
1580
|
+
if (e.kind === "connector" && !e.connector) continue;
|
|
1581
|
+
metrics2.elements.set(e.id, { throughput: 0, load: null, delay: e.kind !== "connector" ? delayOf(e) : 0, backlog: 0, inFlight: 0, depth: 0 });
|
|
1582
|
+
}
|
|
1583
|
+
const list = flows ?? [];
|
|
1584
|
+
const room = /* @__PURE__ */ new Map();
|
|
1585
|
+
for (const e of frame2.elements) {
|
|
1586
|
+
const c = e.capacity ?? null;
|
|
1587
|
+
if (c !== null && Number.isFinite(c)) room.set(e.id, Math.max(c, 0));
|
|
1588
|
+
}
|
|
1589
|
+
const full = () => new Set([...room].filter(([, v]) => v <= TINY).map(([id]) => id));
|
|
1590
|
+
const route2 = (tree, goal) => {
|
|
1591
|
+
const hops = [], shapes2 = [];
|
|
1592
|
+
let at = goal;
|
|
1593
|
+
for (let step = tree.came.get(at); step; step = tree.came.get(at)) {
|
|
1594
|
+
hops.push([step.via, step.back]);
|
|
1595
|
+
shapes2.push(at);
|
|
1596
|
+
at = step.from;
|
|
1597
|
+
}
|
|
1598
|
+
shapes2.push(at);
|
|
1599
|
+
return { hops: hops.reverse(), shapes: shapes2.reverse() };
|
|
1600
|
+
};
|
|
1601
|
+
const way = (hops, shapes2) => {
|
|
1602
|
+
const points = [], inside = [], stretches = [];
|
|
1603
|
+
let length = 0;
|
|
1604
|
+
hops.forEach(([id, back], k) => {
|
|
1605
|
+
const c = byID.get(id);
|
|
1606
|
+
if (!c) return;
|
|
1607
|
+
const line2 = back ? [...tracing(c, frame2).points].reverse() : tracing(c, frame2).points;
|
|
1608
|
+
if (line2.length === 0) return;
|
|
1609
|
+
const last = points[points.length - 1];
|
|
1610
|
+
if (last) {
|
|
1611
|
+
const gap = Math.hypot(line2[0].x - last.x, line2[0].y - last.y);
|
|
1612
|
+
const shape = byID.get(shapes2[k]);
|
|
1613
|
+
inside.push([length, length + gap, shape ? delayOf(shape) : 0]);
|
|
1614
|
+
length += gap;
|
|
1615
|
+
}
|
|
1616
|
+
const start = length;
|
|
1617
|
+
for (let q = 1; q < line2.length; q++) length += Math.hypot(line2[q].x - line2[q - 1].x, line2[q].y - line2[q - 1].y);
|
|
1618
|
+
stretches.push([id, start, length]);
|
|
1619
|
+
points.push(...line2);
|
|
1620
|
+
});
|
|
1621
|
+
return length > 0 ? { line: new Polyline(points), length, inside, stretches } : null;
|
|
1622
|
+
};
|
|
1623
|
+
const along = (w, elapsedAt, speed) => {
|
|
1624
|
+
let elapsed = elapsedAt, d = 0;
|
|
1625
|
+
for (const [a, b, delay] of w.inside) {
|
|
1626
|
+
const reach = (a - d) / speed;
|
|
1627
|
+
if (elapsed <= reach) break;
|
|
1628
|
+
elapsed -= reach;
|
|
1629
|
+
d = a;
|
|
1630
|
+
const stay = (b - a) / speed + delay;
|
|
1631
|
+
if (elapsed < stay) return null;
|
|
1632
|
+
elapsed -= stay;
|
|
1633
|
+
d = b;
|
|
1634
|
+
}
|
|
1635
|
+
d += elapsed * speed;
|
|
1636
|
+
return d >= 0 && d <= w.length ? d : null;
|
|
1637
|
+
};
|
|
1638
|
+
const union2 = (a, b) => /* @__PURE__ */ new Set([...a, ...b]);
|
|
1639
|
+
const bump = (id, f) => {
|
|
1640
|
+
const m = metrics2.elements.get(id);
|
|
1641
|
+
if (m) f(m);
|
|
1642
|
+
};
|
|
1643
|
+
const leaving = /* @__PURE__ */ new Map(), turn = [];
|
|
1644
|
+
for (const flow of list) {
|
|
1645
|
+
const n = leaving.get(flow.from) ?? 0;
|
|
1646
|
+
turn.push(n);
|
|
1647
|
+
leaving.set(flow.from, n + 1);
|
|
1648
|
+
}
|
|
1649
|
+
const biggest = list.length > 0 ? Math.max(...list.map(amount)) : 1;
|
|
1650
|
+
const packets = [];
|
|
1651
|
+
const waitingBy = /* @__PURE__ */ new Map();
|
|
1652
|
+
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);
|
|
1653
|
+
for (const [i, flow] of list.entries()) {
|
|
1654
|
+
if (flow.when !== null && values !== null && !holds(flow.when, values)) continue;
|
|
1655
|
+
const source = byID.get(flow.from);
|
|
1656
|
+
const base2 = source ? flowTree(flow.from, frame2, offline) : null;
|
|
1657
|
+
if (!source || !base2) continue;
|
|
1658
|
+
const volume = amountIn(flow, values);
|
|
1659
|
+
const fm = { volume, placed: 0, backlog: 0, backup: 0, transit: null };
|
|
1660
|
+
const placements = [];
|
|
1661
|
+
const waiting = [];
|
|
1662
|
+
const goals = base2.goals(flow.to, flow.spread === true);
|
|
1663
|
+
const shares = goals.length > 1 ? goals.map((g) => [[g], volume / goals.length]) : [[flow.to, volume]];
|
|
1664
|
+
for (const [candidates, share] of shares) {
|
|
1665
|
+
if (!(share > TINY)) continue;
|
|
1666
|
+
let left = share;
|
|
1667
|
+
for (let n = 0; n < 32 && left > TINY; n++) {
|
|
1668
|
+
const tree = flowTree(flow.from, frame2, union2(offline, full()));
|
|
1669
|
+
const goal2 = tree?.goals(candidates, false)[0];
|
|
1670
|
+
if (!tree || goal2 === void 0) break;
|
|
1671
|
+
const r = route2(tree, goal2);
|
|
1672
|
+
const ids = [...r.shapes, ...r.hops.map(([id]) => id)];
|
|
1673
|
+
const rooms = ids.filter((id) => room.has(id)).map((id) => room.get(id));
|
|
1674
|
+
const fits = Math.min(left, rooms.length > 0 ? Math.min(...rooms) : Infinity);
|
|
1675
|
+
if (!(fits > TINY)) break;
|
|
1676
|
+
for (const id of ids) if (room.has(id)) room.set(id, room.get(id) - fits);
|
|
1677
|
+
placements.push({ hops: r.hops, shapes: r.shapes, goal: goal2, amount: fits });
|
|
1678
|
+
left -= fits;
|
|
1679
|
+
}
|
|
1680
|
+
if (!(left > TINY)) continue;
|
|
1681
|
+
let at = flow.from;
|
|
1682
|
+
const goal = base2.goals(candidates, false)[0];
|
|
1683
|
+
if (goal !== void 0) {
|
|
1684
|
+
const r = route2(base2, goal), done = full();
|
|
1685
|
+
const sequence = [[r.shapes[0], r.shapes[0]]];
|
|
1686
|
+
r.hops.forEach(([id], k) => {
|
|
1687
|
+
sequence.push([id, r.shapes[k]], [r.shapes[k + 1], r.shapes[k]]);
|
|
1688
|
+
});
|
|
1689
|
+
const first = sequence.find(([id]) => done.has(id));
|
|
1690
|
+
if (first) {
|
|
1691
|
+
at = first[1];
|
|
1692
|
+
const upTo = r.shapes.indexOf(at);
|
|
1693
|
+
for (let q = upTo; q >= 0; q--) {
|
|
1694
|
+
const s = byID.get(r.shapes[q]);
|
|
1695
|
+
if (s && s.queue === true && s.kind !== "connector") {
|
|
1696
|
+
at = r.shapes[q];
|
|
1697
|
+
break;
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
const n = r.shapes.indexOf(at);
|
|
1702
|
+
if (at !== flow.from && n >= 0) {
|
|
1703
|
+
const shapes2 = r.shapes.slice(0, n + 1), hops = r.hops.slice(0, n);
|
|
1704
|
+
for (const id of [...shapes2, ...hops.map(([h]) => h)]) if (room.has(id)) room.set(id, Math.max(0, room.get(id) - left));
|
|
1705
|
+
placements.push({ hops, shapes: shapes2, goal: at, amount: left, waits: true });
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
const existing = waiting.find(([s]) => s === at);
|
|
1709
|
+
if (existing) existing[1] += left;
|
|
1710
|
+
else waiting.push([at, left]);
|
|
1711
|
+
}
|
|
1712
|
+
waitingBy.set(flow.id, waiting);
|
|
1713
|
+
for (const p of placements) {
|
|
1714
|
+
for (const id of [...p.shapes, ...p.hops.map(([h]) => h)]) bump(id, (m) => {
|
|
1715
|
+
m.throughput += p.amount;
|
|
1716
|
+
});
|
|
1717
|
+
if (!p.waits) fm.placed += p.amount;
|
|
1718
|
+
}
|
|
1719
|
+
for (const [at, a] of waiting) {
|
|
1720
|
+
bump(at, (m) => {
|
|
1721
|
+
m.backlog += a;
|
|
1722
|
+
});
|
|
1723
|
+
fm.backlog += a;
|
|
1724
|
+
}
|
|
1725
|
+
const reachedFirst = placements.filter((p) => !p.waits && p.goal === flow.to[0]).reduce((s, p) => s + p.amount, 0);
|
|
1726
|
+
fm.backup = volume > TINY ? Math.max(0, (volume - reachedFirst) / volume) : 0;
|
|
1727
|
+
const every = everyOf(flow);
|
|
1728
|
+
const speed = clamp(flow.speed, DEFAULT_SPEED, 1, 1e4), size = clamp(flow.size, DEFAULT_SIZE, 2, 200);
|
|
1729
|
+
const phase = turn[i] / leaving.get(flow.from) * every;
|
|
1730
|
+
const color2 = flow.color ?? DEFAULT_COLOR;
|
|
1731
|
+
const cumulative = [];
|
|
1732
|
+
let sum = 0;
|
|
1733
|
+
for (const p of placements) {
|
|
1734
|
+
sum += p.amount / Math.max(volume, TINY);
|
|
1735
|
+
cumulative.push(sum);
|
|
1736
|
+
}
|
|
1737
|
+
if (cumulative.length > 0 && Math.abs(cumulative[cumulative.length - 1] - 1) < 1e-9) cumulative[cumulative.length - 1] = 1;
|
|
1738
|
+
let transitSum = 0;
|
|
1739
|
+
placements.forEach((p, r) => {
|
|
1740
|
+
const w = way(p.hops, p.shapes);
|
|
1741
|
+
if (!w) return;
|
|
1742
|
+
const travel = w.length / speed + w.inside.reduce((s, x) => s + x[2], 0);
|
|
1743
|
+
if (!p.waits) transitSum += travel * p.amount;
|
|
1744
|
+
const lo = Math.ceil((t - phase - travel) / every);
|
|
1745
|
+
const hi = Math.floor((t - phase) / every);
|
|
1746
|
+
const below = r === 0 ? 0 : cumulative[r - 1], upTo = cumulative[r];
|
|
1747
|
+
const dealt = (c, k) => Math.floor((k + 1) * c) - Math.floor(k * c);
|
|
1748
|
+
for (let k = Math.max(lo, hi - 3999); k <= hi; k++) {
|
|
1749
|
+
if (dealt(upTo, k) - dealt(below, k) !== 1) continue;
|
|
1750
|
+
const d = along(w, t - k * every - phase, speed);
|
|
1751
|
+
if (d === null) continue;
|
|
1752
|
+
const s = w.stretches.find(([, a, b]) => d >= a && d <= b);
|
|
1753
|
+
if (s) bump(s[0], (m) => {
|
|
1754
|
+
m.inFlight += 1;
|
|
1755
|
+
});
|
|
1756
|
+
const pt2 = w.line.point(d / w.length);
|
|
1757
|
+
packets.push(packet(packetID(flow.id, k), pt2.x - size / 2, pt2.y - size / 2, size, color2, source.layerID));
|
|
1758
|
+
}
|
|
1759
|
+
});
|
|
1760
|
+
fm.transit = fm.placed > TINY ? transitSum / fm.placed : null;
|
|
1761
|
+
metrics2.flows.set(flow.id, fm);
|
|
1762
|
+
}
|
|
1763
|
+
const piles = /* @__PURE__ */ new Map();
|
|
1764
|
+
for (const flow of list) {
|
|
1765
|
+
const waiting = waitingBy.get(flow.id) ?? [];
|
|
1766
|
+
const shapes2 = waiting.map(([s]) => s);
|
|
1767
|
+
for (const e of frame2.elements) {
|
|
1768
|
+
const c = carried[queueKey(e.id, flow.id)];
|
|
1769
|
+
if (c !== void 0 && c > TINY && !shapes2.includes(e.id)) shapes2.push(e.id);
|
|
1770
|
+
}
|
|
1771
|
+
if (shapes2.length === 0) continue;
|
|
1772
|
+
const volume = amountIn(flow, values);
|
|
1773
|
+
const every = everyOf(flow);
|
|
1774
|
+
const unit = every * Math.max(volume, TINY);
|
|
1775
|
+
const speed = clamp(flow.speed, DEFAULT_SPEED, 1, 1e4), size = clamp(flow.size, DEFAULT_SIZE, 2, 200);
|
|
1776
|
+
const color2 = flow.color ?? DEFAULT_COLOR;
|
|
1777
|
+
shapes2.forEach((at, j) => {
|
|
1778
|
+
const shape = byID.get(at);
|
|
1779
|
+
if (!shape) return;
|
|
1780
|
+
const layer = byID.get(flow.from)?.layerID ?? shape.layerID;
|
|
1781
|
+
const backlog = waiting.find(([s]) => s === at)?.[1] ?? 0;
|
|
1782
|
+
const before = Math.max(0, carried[queueKey(at, flow.id)] ?? 0);
|
|
1783
|
+
let depth = before + backlog * since;
|
|
1784
|
+
if (backlog <= TINY && before > TINY) {
|
|
1785
|
+
const trial = new Map(room);
|
|
1786
|
+
const outlets = [];
|
|
1787
|
+
let total = 0;
|
|
1788
|
+
for (let n2 = 0; n2 < 32; n2++) {
|
|
1789
|
+
const blocked = new Set([...trial].filter(([, v]) => v <= TINY).map(([id]) => id));
|
|
1790
|
+
const tree = flowTree(at, frame2, union2(offline, blocked));
|
|
1791
|
+
const goal = tree?.goals(flow.to, false)[0];
|
|
1792
|
+
if (!tree || goal === void 0) break;
|
|
1793
|
+
const r = route2(tree, goal);
|
|
1794
|
+
const ids = [...r.shapes.slice(1), ...r.hops.map(([id]) => id)];
|
|
1795
|
+
const rooms = ids.filter((id) => trial.has(id)).map((id) => trial.get(id));
|
|
1796
|
+
const spare = rooms.length > 0 ? Math.min(...rooms) : Infinity;
|
|
1797
|
+
const rate = Number.isFinite(spare) ? spare : Math.max(before / 0.5 - total, TINY * 2);
|
|
1798
|
+
if (!(rate > TINY)) break;
|
|
1799
|
+
for (const id of ids) if (trial.has(id)) trial.set(id, trial.get(id) - rate);
|
|
1800
|
+
outlets.push({ hops: r.hops, shapes: r.shapes, rate });
|
|
1801
|
+
total += rate;
|
|
1802
|
+
if (!Number.isFinite(spare)) break;
|
|
1803
|
+
}
|
|
1804
|
+
if (total > TINY) {
|
|
1805
|
+
depth = Math.max(0, before - total * since);
|
|
1806
|
+
if (depth > TINY) {
|
|
1807
|
+
for (const [id, v] of trial) room.set(id, v);
|
|
1808
|
+
for (const o of outlets) for (const id of [...o.shapes, ...o.hops.map(([h]) => h)]) bump(id, (m) => {
|
|
1809
|
+
m.throughput += o.rate;
|
|
1810
|
+
});
|
|
1811
|
+
}
|
|
1812
|
+
const lasts = before / total;
|
|
1813
|
+
outlets.forEach((o, i) => {
|
|
1814
|
+
const w = way(o.hops, o.shapes);
|
|
1815
|
+
if (!w) return;
|
|
1816
|
+
const spacing = Math.max(unit / o.rate, 0.05);
|
|
1817
|
+
for (let k = 0; k * spacing < lasts && k * spacing <= since && k < 2048; k++) {
|
|
1818
|
+
const d = along(w, since - k * spacing, speed);
|
|
1819
|
+
if (d === null) continue;
|
|
1820
|
+
const pt2 = w.line.point(d / w.length);
|
|
1821
|
+
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));
|
|
1822
|
+
}
|
|
1823
|
+
});
|
|
1824
|
+
}
|
|
1825
|
+
}
|
|
1826
|
+
if (!(depth > TINY || backlog > TINY)) return;
|
|
1827
|
+
bump(at, (m) => {
|
|
1828
|
+
m.depth += depth;
|
|
1829
|
+
});
|
|
1830
|
+
if (depth > TINY) metrics2.carry[queueKey(at, flow.id)] = depth;
|
|
1831
|
+
const n = Math.min(Math.max(Math.ceil(depth / unit - 1e-9), 1), 30);
|
|
1832
|
+
const rows = Math.trunc((n + 9) / 10);
|
|
1833
|
+
const base2 = piles.get(at) ?? 0;
|
|
1834
|
+
piles.set(at, base2 + rows);
|
|
1835
|
+
for (let q = 0; q < n; q++) {
|
|
1836
|
+
const cx = shape.x + 2 + size / 2 + q % 10 * (size + 3);
|
|
1837
|
+
const cy = shape.y - 4 - size / 2 - (base2 + Math.trunc(q / 10)) * (size + 3);
|
|
1838
|
+
packets.push(packet(packetID(flow.id, 2 ** 39 + j * 64 + q), cx - size / 2, cy - size / 2, size, color2, layer));
|
|
1839
|
+
}
|
|
1840
|
+
});
|
|
1841
|
+
}
|
|
1842
|
+
for (const e of frame2.elements) {
|
|
1843
|
+
const c = e.capacity ?? null;
|
|
1844
|
+
const m = metrics2.elements.get(e.id);
|
|
1845
|
+
if (c === null || !Number.isFinite(c) || !m) continue;
|
|
1846
|
+
m.load = c > 0 ? m.throughput / c : 1;
|
|
1847
|
+
}
|
|
1848
|
+
return { packets, metrics: metrics2 };
|
|
1849
|
+
}
|
|
1850
|
+
var BLANK = {
|
|
1851
|
+
seed: 1,
|
|
1852
|
+
text: null,
|
|
1853
|
+
connector: null,
|
|
1854
|
+
groupID: null,
|
|
1855
|
+
points: null,
|
|
1856
|
+
path: null,
|
|
1857
|
+
subpaths: null,
|
|
1858
|
+
evenOdd: null,
|
|
1859
|
+
rotation: null,
|
|
1860
|
+
link: null,
|
|
1861
|
+
url: null,
|
|
1862
|
+
anchor: null,
|
|
1863
|
+
click: null,
|
|
1864
|
+
hover: null,
|
|
1865
|
+
when: null,
|
|
1866
|
+
overrides: null,
|
|
1867
|
+
symbol: null,
|
|
1868
|
+
playback: null,
|
|
1869
|
+
offset: null,
|
|
1870
|
+
args: null,
|
|
1871
|
+
image: null,
|
|
1872
|
+
reveal: null
|
|
1873
|
+
};
|
|
1874
|
+
function style(stroke, fill, strokeWidth, fillStyle = "solid", opacity2 = null) {
|
|
1875
|
+
return {
|
|
1876
|
+
stroke: { ...stroke },
|
|
1877
|
+
fill: { ...fill },
|
|
1878
|
+
fillStyle,
|
|
1879
|
+
strokeWidth,
|
|
1880
|
+
dash: "solid",
|
|
1881
|
+
roughness: 0,
|
|
1882
|
+
bowing: 1,
|
|
1883
|
+
fontSize: 18,
|
|
1884
|
+
font: null,
|
|
1885
|
+
gridSize: null,
|
|
1886
|
+
opacity: opacity2,
|
|
1887
|
+
dashOffset: null,
|
|
1888
|
+
textColor: null
|
|
1889
|
+
};
|
|
1890
|
+
}
|
|
1891
|
+
function packet(id, x, y, size, color2, layerID) {
|
|
1892
|
+
return { ...BLANK, id, kind: "ellipse", layerID, x, y, width: size, height: size, style: style(color2, color2, 1.5) };
|
|
1893
|
+
}
|
|
1894
|
+
function metricLookup(m, frame2) {
|
|
1895
|
+
const anchors = /* @__PURE__ */ new Map();
|
|
1896
|
+
for (const e of frame2.elements) if (e.anchor !== null && !anchors.has(e.anchor)) anchors.set(e.anchor, e.id);
|
|
1897
|
+
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}$/;
|
|
1898
|
+
return (term, own) => {
|
|
1899
|
+
const id = term.target === "" ? own : anchors.get(term.target) ?? (UUID2.test(term.target) ? term.target.toUpperCase() : null);
|
|
1900
|
+
return id === null ? null : metricValue(m, term.metric, id);
|
|
1901
|
+
};
|
|
1902
|
+
}
|
|
1903
|
+
function finishing(elements, metrics2, values, frame2, flows, since = Infinity, carried = {}) {
|
|
1904
|
+
const lookup = metricLookup(metrics2, frame2);
|
|
1905
|
+
const out = [], readings = {};
|
|
1906
|
+
for (let e of elements) {
|
|
1907
|
+
if (values !== null && e.overrides) {
|
|
1908
|
+
for (const o of e.overrides) {
|
|
1909
|
+
const own = e.id;
|
|
1910
|
+
if (mentionsMetric(o.when) && holds(o.when, values, (m) => lookup(m, own))) e = applyOverride(looksOnly(o), e);
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
if (!e.gauge || values === null) {
|
|
1914
|
+
out.push(e);
|
|
1915
|
+
continue;
|
|
1916
|
+
}
|
|
1917
|
+
const g = e.gauge;
|
|
1918
|
+
let shown = metricValue(metrics2, g.metric, g.of);
|
|
1919
|
+
const then = carried[gaugeKey(e.id)];
|
|
1920
|
+
if (shown !== null && then !== void 0 && g.metric !== "depth" && g.metric !== "inFlight" && since < GAUGE_EASE) {
|
|
1921
|
+
const f = Math.max(0, since / GAUGE_EASE), smooth = f * f * (3 - 2 * f);
|
|
1922
|
+
shown = then + (shown - then) * smooth;
|
|
1923
|
+
}
|
|
1924
|
+
if (shown !== null) readings[gaugeKey(e.id)] = shown;
|
|
1925
|
+
out.push(...gauge(e, g, shown, frame2, flows));
|
|
1926
|
+
}
|
|
1927
|
+
return { elements: out, readings };
|
|
1928
|
+
}
|
|
1929
|
+
var gaugeKey = (id) => "gauge|" + id;
|
|
1930
|
+
var GAUGE_EASE = 1;
|
|
1931
|
+
var hex = (h) => {
|
|
1932
|
+
const v = parseInt(h.slice(1), 16);
|
|
1933
|
+
return { r: (v >> 16 & 255) / 255, g: (v >> 8 & 255) / 255, b: (v & 255) / 255, a: 1 };
|
|
1934
|
+
};
|
|
1935
|
+
var GAUGE_GREEN = hex("#16a34a");
|
|
1936
|
+
var GAUGE_AMBER = hex("#d97706");
|
|
1937
|
+
var GAUGE_RED = hex("#dc2626");
|
|
1938
|
+
var GAUGE_BLUE = hex("#2f6fdf");
|
|
1939
|
+
var GAUGE_TRACK = hex("#e5e7eb");
|
|
1940
|
+
var GAUGE_GRAY = hex("#9ca3af");
|
|
1941
|
+
function formatted(metric, v) {
|
|
1942
|
+
if (v === null || !Number.isFinite(v)) return "\u2013";
|
|
1943
|
+
switch (metric) {
|
|
1944
|
+
case "load":
|
|
1945
|
+
case "backup":
|
|
1946
|
+
return formatNumber(v * 100, 0) + "%";
|
|
1947
|
+
case "transit":
|
|
1948
|
+
case "delay":
|
|
1949
|
+
return formatNumber(v, Math.abs(v) >= 10 ? 1 : 2) + "s";
|
|
1950
|
+
default:
|
|
1951
|
+
return formatNumber(v, Math.abs(v) >= 100 ? 0 : Math.abs(v) >= 10 ? 1 : 2);
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
function formatNumber(v, places) {
|
|
1955
|
+
const scale = places === 0 ? 1 : places === 1 ? 10 : 100;
|
|
1956
|
+
const n = Math.floor(Math.abs(v) * scale + 0.5);
|
|
1957
|
+
if (!(n < 1e15)) return v < 0 ? "-\u221E" : "\u221E";
|
|
1958
|
+
const whole = Math.trunc(n / scale), part = n - whole * scale;
|
|
1959
|
+
let s = String(whole);
|
|
1960
|
+
if (places > 0 && part > 0) {
|
|
1961
|
+
let digits = String(part);
|
|
1962
|
+
while (digits.length < places) digits = "0" + digits;
|
|
1963
|
+
while (digits.endsWith("0")) digits = digits.slice(0, -1);
|
|
1964
|
+
s += "." + digits;
|
|
1965
|
+
}
|
|
1966
|
+
return (v < 0 && n > 0 ? "-" : "") + s;
|
|
1967
|
+
}
|
|
1968
|
+
function gauge(e, g, value2, frame2, flows) {
|
|
1969
|
+
const target = frame2.elements.find((x) => x.id === g.of);
|
|
1970
|
+
const full = g.max ?? (g.metric === "load" ? 1 : target?.capacity ?? value2 ?? 1);
|
|
1971
|
+
const fraction = value2 === null ? 0 : full > 0 ? Math.min(Math.max(value2 / full, 0), 1) : 0;
|
|
1972
|
+
const warn = g.warn ?? (g.metric === "load" ? 0.7 : null), alarm = g.alarm ?? (g.metric === "load" ? 0.9 : null);
|
|
1973
|
+
let color2 = warn === null && alarm === null ? GAUGE_BLUE : GAUGE_GREEN;
|
|
1974
|
+
if (value2 !== null && warn !== null && value2 >= warn) color2 = GAUGE_AMBER;
|
|
1975
|
+
if (value2 !== null && alarm !== null && value2 >= alarm) color2 = GAUGE_RED;
|
|
1976
|
+
if (value2 === null) color2 = GAUGE_GRAY;
|
|
1977
|
+
let name = g.label;
|
|
1978
|
+
if (name === null) {
|
|
1979
|
+
const what = target ? target.text !== null ? target.text.split("\n")[0] : target.anchor : null;
|
|
1980
|
+
name = `${g.metric} ${what ?? (flows.some((f) => f.id === g.of) ? "flow" : "?")}`;
|
|
1981
|
+
}
|
|
1982
|
+
const kind = g.style ?? "number";
|
|
1983
|
+
const w = Math.max(e.width, 1), h = Math.max(e.height, 1);
|
|
1984
|
+
const line2 = (a, b) => [
|
|
1985
|
+
a.x / w,
|
|
1986
|
+
a.y / h,
|
|
1987
|
+
(a.x + (b.x - a.x) / 3) / w,
|
|
1988
|
+
(a.y + (b.y - a.y) / 3) / h,
|
|
1989
|
+
(a.x + 2 * (b.x - a.x) / 3) / w,
|
|
1990
|
+
(a.y + 2 * (b.y - a.y) / 3) / h,
|
|
1991
|
+
b.x / w,
|
|
1992
|
+
b.y / h
|
|
1993
|
+
];
|
|
1994
|
+
const box = (x, y, bw, bh) => {
|
|
1995
|
+
const p = [{ x, y }, { x: x + bw, y }, { x: x + bw, y: y + bh }, { x, y: y + bh }];
|
|
1996
|
+
let coords = line2(p[0], p[1]);
|
|
1997
|
+
for (let k = 1; k < 4; k++) coords = coords.concat(line2(p[k], p[(k + 1) % 4]).slice(2));
|
|
1998
|
+
return { closed: true, coords };
|
|
1999
|
+
};
|
|
2000
|
+
const arc = (c, radius, a0, a1) => {
|
|
2001
|
+
const pieces2 = Math.max(1, Math.ceil(Math.abs(a1 - a0) / (Math.PI / 2) - 1e-9));
|
|
2002
|
+
const step = (a1 - a0) / pieces2, kappa = 4 / 3 * Math.tan(step / 4);
|
|
2003
|
+
const at = (a) => ({ x: c.x + radius * Math.cos(a), y: c.y + radius * Math.sin(a) });
|
|
2004
|
+
const s = at(a0);
|
|
2005
|
+
const coords = [s.x / w, s.y / h];
|
|
2006
|
+
for (let k = 0; k < pieces2; k++) {
|
|
2007
|
+
const a = a0 + k * step, b = a + step;
|
|
2008
|
+
const p0 = at(a), p3 = at(b);
|
|
2009
|
+
const c1 = { x: p0.x - kappa * radius * Math.sin(a), y: p0.y + kappa * radius * Math.cos(a) };
|
|
2010
|
+
const c2 = { x: p3.x + kappa * radius * Math.sin(b), y: p3.y - kappa * radius * Math.cos(b) };
|
|
2011
|
+
coords.push(c1.x / w, c1.y / h, c2.x / w, c2.y / h, p3.x / w, p3.y / h);
|
|
2012
|
+
}
|
|
2013
|
+
return { closed: false, coords };
|
|
2014
|
+
};
|
|
2015
|
+
const part = (k, subpaths, stroke, fill, width) => ({
|
|
2016
|
+
...BLANK,
|
|
2017
|
+
id: packetID(e.id, -(2 ** 38) - k),
|
|
2018
|
+
kind: "path",
|
|
2019
|
+
layerID: e.layerID,
|
|
2020
|
+
x: e.x,
|
|
2021
|
+
y: e.y,
|
|
2022
|
+
width: e.width,
|
|
2023
|
+
height: e.height,
|
|
2024
|
+
style: style(stroke, fill ?? { r: 0, g: 0, b: 0, a: 0 }, width, "solid", e.style.opacity),
|
|
2025
|
+
subpaths
|
|
2026
|
+
});
|
|
2027
|
+
const back = {
|
|
2028
|
+
...e,
|
|
2029
|
+
gauge: null,
|
|
2030
|
+
kind: "path",
|
|
2031
|
+
subpaths: [box(0, 0, w, h)],
|
|
2032
|
+
text: name + "\n" + formatted(g.metric, value2),
|
|
2033
|
+
style: { ...e.style }
|
|
2034
|
+
};
|
|
2035
|
+
const parts = [];
|
|
2036
|
+
if (kind === "number") {
|
|
2037
|
+
back.labelRect = { x: 0, y: 0, width: 1, height: 1 };
|
|
2038
|
+
back.style.textColor = { ...color2 };
|
|
2039
|
+
} else if (kind === "bar") {
|
|
2040
|
+
back.labelRect = { x: 0, y: 0, width: 1, height: 0.66 };
|
|
2041
|
+
const inset2 = Math.min(10, w * 0.08), barH = Math.min(12, h * 0.18), top = h * 0.72;
|
|
2042
|
+
const trackW = w - 2 * inset2;
|
|
2043
|
+
parts.push(part(1, [box(inset2, top, trackW, barH)], GAUGE_TRACK, GAUGE_TRACK, 0.5));
|
|
2044
|
+
if (fraction > 0) parts.push(part(2, [box(inset2, top, trackW * fraction, barH)], color2, color2, 0.5));
|
|
2045
|
+
} else {
|
|
2046
|
+
back.labelRect = { x: 0, y: 0.54, width: 1, height: 0.46 };
|
|
2047
|
+
const radius = Math.min(w * 0.38, h * 0.4), center2 = { x: w / 2, y: h * 0.5 };
|
|
2048
|
+
const thick = Math.max(2, radius * 0.16);
|
|
2049
|
+
parts.push(part(1, [arc(center2, radius, Math.PI, 2 * Math.PI)], GAUGE_TRACK, null, thick));
|
|
2050
|
+
if (fraction > 0) parts.push(part(2, [arc(center2, radius, Math.PI, Math.PI + Math.PI * fraction)], color2, null, thick));
|
|
2051
|
+
const a = Math.PI + Math.PI * fraction;
|
|
2052
|
+
const tip = { x: center2.x + radius * 0.85 * Math.cos(a), y: center2.y + radius * 0.85 * Math.sin(a) };
|
|
2053
|
+
parts.push(part(3, [{ closed: false, coords: line2(center2, tip) }], { r: 0.2, g: 0.2, b: 0.2, a: 1 }, null, 2));
|
|
2054
|
+
}
|
|
2055
|
+
return [back, ...parts];
|
|
2056
|
+
}
|
|
2057
|
+
|
|
1284
2058
|
// src/rough.ts
|
|
1285
|
-
var offset = (rng,
|
|
2059
|
+
var offset = (rng, amount2, roughness) => rng.signed() * amount2 * roughness;
|
|
1286
2060
|
function line(a, b, rng, roughness, bowing, preserveEnds, into) {
|
|
1287
2061
|
const length = Math.hypot(b.x - a.x, b.y - a.y);
|
|
1288
2062
|
if (!(length > 0.5)) return;
|
|
@@ -2138,16 +2912,39 @@ function trackMap(tl) {
|
|
|
2138
2912
|
for (const t of tl.tracks) if (!m.has(t.element)) m.set(t.element, t);
|
|
2139
2913
|
return m;
|
|
2140
2914
|
}
|
|
2141
|
-
function resolved(page, t, state = "initial") {
|
|
2142
|
-
return expandingSymbols(posed(page, t, state), clampTime(t));
|
|
2915
|
+
function resolved(page, t, state = "initial", since = null, carried = {}) {
|
|
2916
|
+
return expandingSymbols(posed(page, t, state, since, carried), clampTime(t));
|
|
2143
2917
|
}
|
|
2144
2918
|
var EMPTY_TIMELINE = { fps: 12, duration: 0, boil: null, settle: null, poster: null, tracks: [], camera: null, markers: null };
|
|
2145
|
-
function posed(page, t, state = "initial") {
|
|
2919
|
+
function posed(page, t, state = "initial", since = null, carried = {}) {
|
|
2920
|
+
return posedWithMetrics(page, t, state, since, carried).frame;
|
|
2921
|
+
}
|
|
2922
|
+
function posedWithMetrics(page, t, state = "initial", since = null, carried = {}) {
|
|
2146
2923
|
const off = stateHidden(page, state);
|
|
2147
|
-
const
|
|
2148
|
-
|
|
2924
|
+
const values = stateValues(page, state);
|
|
2925
|
+
const active = /* @__PURE__ */ new Map();
|
|
2926
|
+
for (const e of page.elements) {
|
|
2927
|
+
const o = activeOverrides(e, values);
|
|
2928
|
+
if (o.length > 0) active.set(e.id, o);
|
|
2929
|
+
}
|
|
2930
|
+
const flowing = (page.flows ?? []).length > 0;
|
|
2931
|
+
const measuring = flowing || page.elements.some((e) => e.gauge || (e.overrides ?? []).some((o) => mentionsMetric(o.when)));
|
|
2932
|
+
const timeline = page.timeline ?? (off.size === 0 && active.size === 0 && !measuring ? null : EMPTY_TIMELINE);
|
|
2933
|
+
if (!timeline) return { frame: page, metrics: emptyMetrics() };
|
|
2149
2934
|
t = clampTime(t);
|
|
2150
2935
|
const tracks = trackMap(timeline);
|
|
2936
|
+
for (const [id, overrides] of active) {
|
|
2937
|
+
const track = tracks.get(id);
|
|
2938
|
+
if (!track?.follow || !overrides.some((o) => o.reroute)) continue;
|
|
2939
|
+
tracks.set(id, {
|
|
2940
|
+
...track,
|
|
2941
|
+
follow: track.follow.map((f) => {
|
|
2942
|
+
let path = f.path;
|
|
2943
|
+
for (const o of overrides) path = o.reroute?.[path] ?? path;
|
|
2944
|
+
return { ...f, path };
|
|
2945
|
+
})
|
|
2946
|
+
});
|
|
2947
|
+
}
|
|
2151
2948
|
const reseed = timeline.boil === null ? null : Math.trunc(Math.floor(t * clampFPS(timeline.fps) + 1e-9) / Math.max(1, timeline.boil)) % BOIL_DRAWINGS >>> 0;
|
|
2152
2949
|
const hidden = /* @__PURE__ */ new Set();
|
|
2153
2950
|
for (const tr of tracks.values()) if (!isVisible(tr, t)) hidden.add(tr.element);
|
|
@@ -2167,6 +2964,14 @@ function posed(page, t, state = "initial") {
|
|
|
2167
2964
|
let elements = page.elements.map((e) => {
|
|
2168
2965
|
const track = tracks.get(e.id);
|
|
2169
2966
|
let out = track ? applyTrack(track, e, t) : e;
|
|
2967
|
+
for (const o of active.get(e.id) ?? []) out = applyOverride(o, out);
|
|
2968
|
+
const list = active.get(e.id);
|
|
2969
|
+
if (list) {
|
|
2970
|
+
const caps = list.map((o) => o.capacity).filter((c) => c !== null && Number.isFinite(c));
|
|
2971
|
+
const delays = list.map((o) => o.delay).filter((d) => d !== null && Number.isFinite(d));
|
|
2972
|
+
if (caps.length > 0) out.capacity = Math.max(Math.min(...caps), 0);
|
|
2973
|
+
if (delays.length > 0 && out.kind !== "connector") out.delay = Math.max(Math.max(...delays), 0);
|
|
2974
|
+
}
|
|
2170
2975
|
if (reseed !== null && reseed > 0 && boils(e)) {
|
|
2171
2976
|
if (out === e) out = cloneElement(e);
|
|
2172
2977
|
out.seed = boiled(e.seed, reseed);
|
|
@@ -2200,7 +3005,18 @@ function posed(page, t, state = "initial") {
|
|
|
2200
3005
|
pinned.connector.end = { elementID: null, port: c.end.port, x: b.x, y: b.y };
|
|
2201
3006
|
return pinned;
|
|
2202
3007
|
});
|
|
2203
|
-
|
|
3008
|
+
elements = elements.filter((e) => !gone.has(e.id));
|
|
3009
|
+
let metrics2 = emptyMetrics();
|
|
3010
|
+
if (measuring) {
|
|
3011
|
+
const offline = new Set([...active].filter(([, list]) => list.some((o) => o.offline === true)).map(([id]) => id));
|
|
3012
|
+
const frame2 = { ...page, elements, timeline: null };
|
|
3013
|
+
const out = flowFrame(page.flows, frame2, t, values, offline, since, carried);
|
|
3014
|
+
metrics2 = out.metrics;
|
|
3015
|
+
const finished = finishing(elements, out.metrics, values, frame2, page.flows ?? [], Math.max(0, Math.min(since ?? t, 1e6)), carried);
|
|
3016
|
+
Object.assign(metrics2.carry, finished.readings);
|
|
3017
|
+
elements = [...finished.elements, ...out.packets];
|
|
3018
|
+
}
|
|
3019
|
+
return { frame: { ...page, elements, timeline: null, flows: measuring ? null : page.flows }, metrics: metrics2 };
|
|
2204
3020
|
}
|
|
2205
3021
|
function posterTime(page) {
|
|
2206
3022
|
return page.timeline ? page.timeline.poster ?? page.timeline.duration : 0;
|
|
@@ -2315,7 +3131,7 @@ function filled(s, args) {
|
|
|
2315
3131
|
}) };
|
|
2316
3132
|
}
|
|
2317
3133
|
function symbolPage(s) {
|
|
2318
|
-
return { id: s.id, name: s.name, layers: s.layers, elements: s.elements, theme: null, timeline: s.timeline, symbols: null, variables: null };
|
|
3134
|
+
return { id: s.id, name: s.name, layers: s.layers, elements: s.elements, theme: null, timeline: s.timeline, symbols: null, variables: null, flows: null };
|
|
2319
3135
|
}
|
|
2320
3136
|
function hasInstances(page) {
|
|
2321
3137
|
return page.elements.some((e) => e.kind === "symbol");
|
|
@@ -2422,8 +3238,8 @@ function mix2(x) {
|
|
|
2422
3238
|
return z ^ z >> 31n;
|
|
2423
3239
|
}
|
|
2424
3240
|
function halves(id) {
|
|
2425
|
-
const
|
|
2426
|
-
return [BigInt(`0x${
|
|
3241
|
+
const hex2 = id.replace(/-/g, "").padStart(32, "0").slice(-32);
|
|
3242
|
+
return [BigInt(`0x${hex2.slice(0, 16)}`), BigInt(`0x${hex2.slice(16)}`)];
|
|
2427
3243
|
}
|
|
2428
3244
|
function derivedID(instance, inner) {
|
|
2429
3245
|
const [a, b] = halves(instance), [c, d] = halves(inner);
|
|
@@ -2444,7 +3260,7 @@ function clickTargets(page, file) {
|
|
|
2444
3260
|
let pageOf = null;
|
|
2445
3261
|
for (const e of page.elements) {
|
|
2446
3262
|
const c = e.click;
|
|
2447
|
-
const state = c && changesState(c) ? { ...c.set ? { set: c.set } : {}, ...c.toggle ? { toggle: c.toggle } : {} } : null;
|
|
3263
|
+
const state = c && changesState(c) ? { ...c.set ? { set: c.set } : {}, ...c.toggle ? { toggle: c.toggle } : {}, ...c.reset ? { reset: true } : {} } : null;
|
|
2448
3264
|
if (c?.scene) {
|
|
2449
3265
|
const i = sceneNamed(c.scene, markers);
|
|
2450
3266
|
if (i >= 0) {
|
|
@@ -2477,14 +3293,14 @@ function clickTargets(page, file) {
|
|
|
2477
3293
|
function hoveredElement(e) {
|
|
2478
3294
|
const h = e.hover;
|
|
2479
3295
|
if (!h) return e;
|
|
2480
|
-
const
|
|
2481
|
-
if (h.stroke)
|
|
2482
|
-
if (h.fill)
|
|
3296
|
+
const style2 = { ...e.style };
|
|
3297
|
+
if (h.stroke) style2.stroke = h.stroke;
|
|
3298
|
+
if (h.fill) style2.fill = h.fill;
|
|
2483
3299
|
if (h.opacity !== null && Number.isFinite(h.opacity)) {
|
|
2484
3300
|
const o = Math.min(Math.max(h.opacity, 0), 1);
|
|
2485
|
-
|
|
3301
|
+
style2.opacity = o >= 1 ? null : o;
|
|
2486
3302
|
}
|
|
2487
|
-
return { ...e, style };
|
|
3303
|
+
return { ...e, style: style2 };
|
|
2488
3304
|
}
|
|
2489
3305
|
function hovered(page, ids) {
|
|
2490
3306
|
return { ...page, elements: page.elements.map((e) => ids.has(e.id) ? hoveredElement(e) : e) };
|
|
@@ -2688,14 +3504,14 @@ function parseList(lines, start) {
|
|
|
2688
3504
|
}
|
|
2689
3505
|
return { block: { kind: "list", ordered, items }, next: i };
|
|
2690
3506
|
}
|
|
2691
|
-
function inline(src,
|
|
3507
|
+
function inline(src, style2 = {}) {
|
|
2692
3508
|
const out = [];
|
|
2693
3509
|
const push = (text, s) => {
|
|
2694
3510
|
if (text) out.push({ ...s, text });
|
|
2695
3511
|
};
|
|
2696
3512
|
let i = 0, buf = "";
|
|
2697
3513
|
const emit = () => {
|
|
2698
|
-
push(buf,
|
|
3514
|
+
push(buf, style2);
|
|
2699
3515
|
buf = "";
|
|
2700
3516
|
};
|
|
2701
3517
|
while (i < src.length) {
|
|
@@ -2723,43 +3539,43 @@ function inline(src, style = {}) {
|
|
|
2723
3539
|
emit();
|
|
2724
3540
|
let code = m[2].replace(/\n/g, " ");
|
|
2725
3541
|
if (/^ .* $/.test(code)) code = code.slice(1, -1);
|
|
2726
|
-
push(code, { ...
|
|
3542
|
+
push(code, { ...style2, mono: true });
|
|
2727
3543
|
i += m[0].length;
|
|
2728
3544
|
continue;
|
|
2729
3545
|
}
|
|
2730
3546
|
if (m = /^!\[([^\]]*)\]\([^)]*\)/.exec(rest)) {
|
|
2731
3547
|
emit();
|
|
2732
|
-
out.push(...inline(m[1],
|
|
3548
|
+
out.push(...inline(m[1], style2));
|
|
2733
3549
|
i += m[0].length;
|
|
2734
3550
|
continue;
|
|
2735
3551
|
}
|
|
2736
3552
|
if (m = /^\[([^\]]+)\]\([^)]*\)/.exec(rest)) {
|
|
2737
3553
|
emit();
|
|
2738
|
-
out.push(...inline(m[1], { ...
|
|
3554
|
+
out.push(...inline(m[1], { ...style2, link: true }));
|
|
2739
3555
|
i += m[0].length;
|
|
2740
3556
|
continue;
|
|
2741
3557
|
}
|
|
2742
3558
|
if (m = /^<((?:https?|ftp|mailto):[^>\s]+)>/.exec(rest)) {
|
|
2743
3559
|
emit();
|
|
2744
|
-
push(m[1], { ...
|
|
3560
|
+
push(m[1], { ...style2, link: true });
|
|
2745
3561
|
i += m[0].length;
|
|
2746
3562
|
continue;
|
|
2747
3563
|
}
|
|
2748
3564
|
if (m = /^~~(?=\S)([\s\S]*?\S)~~/.exec(rest)) {
|
|
2749
3565
|
emit();
|
|
2750
|
-
out.push(...inline(m[1], { ...
|
|
3566
|
+
out.push(...inline(m[1], { ...style2, strike: true }));
|
|
2751
3567
|
i += m[0].length;
|
|
2752
3568
|
continue;
|
|
2753
3569
|
}
|
|
2754
3570
|
if (m = /^(\*\*|__)(?=\S)([\s\S]*?\S)\1/.exec(rest)) {
|
|
2755
3571
|
emit();
|
|
2756
|
-
out.push(...inline(m[2], { ...
|
|
3572
|
+
out.push(...inline(m[2], { ...style2, bold: true }));
|
|
2757
3573
|
i += m[0].length;
|
|
2758
3574
|
continue;
|
|
2759
3575
|
}
|
|
2760
3576
|
if ((m = /^(\*|_)(?=\S)([\s\S]*?\S)\1(?!\1)/.exec(rest)) && (m[1] === "*" || !/\w/.test(src[i - 1] ?? ""))) {
|
|
2761
3577
|
emit();
|
|
2762
|
-
out.push(...inline(m[2], { ...
|
|
3578
|
+
out.push(...inline(m[2], { ...style2, italic: true }));
|
|
2763
3579
|
i += m[0].length;
|
|
2764
3580
|
continue;
|
|
2765
3581
|
}
|
|
@@ -3376,6 +4192,12 @@ var Playback = class {
|
|
|
3376
4192
|
__publicField(this, "sceneLoops");
|
|
3377
4193
|
/** The page's variables now: their starting values until clicks change them. */
|
|
3378
4194
|
__publicField(this, "state");
|
|
4195
|
+
/** When the variables last changed (performance.now() milliseconds): backlog piles up from then (ADR 0003). */
|
|
4196
|
+
__publicField(this, "stateSince", typeof performance === "undefined" ? 0 : performance.now());
|
|
4197
|
+
/** A still (the `at` attribute): backlog is drawn for the page time, not the wall clock. */
|
|
4198
|
+
__publicField(this, "still", false);
|
|
4199
|
+
/** What each queue held when the variables last changed, which it drains from (FrameMetrics.carry). */
|
|
4200
|
+
__publicField(this, "carried", {});
|
|
3379
4201
|
__publicField(this, "background");
|
|
3380
4202
|
__publicField(this, "ink");
|
|
3381
4203
|
/** What clicking each clickable element does, by element id. */
|
|
@@ -3447,6 +4269,10 @@ var Playback = class {
|
|
|
3447
4269
|
this.svg.style.maxWidth = `${Math.ceil(this.plan.size.width)}px`;
|
|
3448
4270
|
into.replaceChildren(this.svg);
|
|
3449
4271
|
}
|
|
4272
|
+
/** Seconds since the variables last changed, or null for a still. */
|
|
4273
|
+
sinceChange(now = typeof performance === "undefined" ? 0 : performance.now()) {
|
|
4274
|
+
return this.still || typeof performance === "undefined" ? null : (now - this.stateSince) / 1e3;
|
|
4275
|
+
}
|
|
3450
4276
|
get frameCount() {
|
|
3451
4277
|
return this.plan.times.length;
|
|
3452
4278
|
}
|
|
@@ -3487,7 +4313,7 @@ var Playback = class {
|
|
|
3487
4313
|
const times = /* @__PURE__ */ new Map();
|
|
3488
4314
|
for (const [id, start] of this.replayStarts) times.set(id, Math.min((now - start) / 1e3, this.replays.get(id) ?? 0));
|
|
3489
4315
|
const tc = clampTime(t);
|
|
3490
|
-
const pose = posed(this.page, tc, this.state);
|
|
4316
|
+
const pose = posed(this.page, tc, this.state, this.sinceChange(now), this.carried);
|
|
3491
4317
|
const frame2 = expandingSymbols(pose, tc, true, times);
|
|
3492
4318
|
const { parts } = svgParts(frame2, this.background, r);
|
|
3493
4319
|
const owned = (m, parts2) => {
|
|
@@ -3744,6 +4570,7 @@ var DottiePlayer = class extends ElementBase {
|
|
|
3744
4570
|
const file = this.file;
|
|
3745
4571
|
const page = file.pages[pageIndex(file, this.getAttribute("page"))];
|
|
3746
4572
|
const p = new Playback(page, this.stage, { clean: this.hasAttribute("clean"), file });
|
|
4573
|
+
p.still = this.hasAttribute("at");
|
|
3747
4574
|
this.playback = p;
|
|
3748
4575
|
this.style.setProperty("--dottie-page", color(p.background));
|
|
3749
4576
|
this.style.setProperty("--dottie-ink", color(p.ink));
|
|
@@ -3837,7 +4664,13 @@ var DottiePlayer = class extends ElementBase {
|
|
|
3837
4664
|
this.scheduleReplay();
|
|
3838
4665
|
}
|
|
3839
4666
|
if (!click) return;
|
|
3840
|
-
if (click.set || click.toggle
|
|
4667
|
+
if (click.set || click.toggle || click.reset) {
|
|
4668
|
+
this.setState(applyClick(
|
|
4669
|
+
{ scene: null, url: null, set: click.set ?? null, toggle: click.toggle ?? null, reset: click.reset ?? null },
|
|
4670
|
+
p.state,
|
|
4671
|
+
p.page.variables ?? {}
|
|
4672
|
+
));
|
|
4673
|
+
}
|
|
3841
4674
|
if ("state" in click) return;
|
|
3842
4675
|
if ("url" in click) {
|
|
3843
4676
|
if (/^(https?:|mailto:)/i.test(click.url)) window.open(click.url, "_blank", "noopener");
|
|
@@ -3863,7 +4696,10 @@ var DottiePlayer = class extends ElementBase {
|
|
|
3863
4696
|
if (!p) return;
|
|
3864
4697
|
const next = { ...p.state, ...values };
|
|
3865
4698
|
if (Object.keys(next).every((k) => next[k] === p.state[k])) return;
|
|
4699
|
+
const t = clampTime(p.last.t);
|
|
4700
|
+
p.carried = posedWithMetrics(p.page, t, p.state, p.sinceChange(), p.carried).metrics.carry;
|
|
3866
4701
|
p.state = next;
|
|
4702
|
+
p.stateSince = performance.now();
|
|
3867
4703
|
p.redraw();
|
|
3868
4704
|
this.dispatchEvent(new CustomEvent("state", { detail: { variables: { ...next } } }));
|
|
3869
4705
|
}
|
|
@@ -4061,7 +4897,7 @@ function render(source, options = {}) {
|
|
|
4061
4897
|
function isFile(v) {
|
|
4062
4898
|
return typeof v === "object" && v !== null && Array.isArray(v.pages) && Array.isArray(v.warnings);
|
|
4063
4899
|
}
|
|
4064
|
-
var VERSION = "0.
|
|
4900
|
+
var VERSION = "0.8.0";
|
|
4065
4901
|
export {
|
|
4066
4902
|
BOIL_DRAWINGS,
|
|
4067
4903
|
BUNDLED_FONTS,
|