@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-player.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 __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");
|
|
@@ -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) {
|
|
@@ -1344,8 +1509,617 @@ function mulConst(step) {
|
|
|
1344
1509
|
return [p[0], p[1]];
|
|
1345
1510
|
}
|
|
1346
1511
|
|
|
1512
|
+
// src/flows.ts
|
|
1513
|
+
var DEFAULT_EVERY = 0.5;
|
|
1514
|
+
var DEFAULT_SPEED = 160;
|
|
1515
|
+
var DEFAULT_SIZE = 12;
|
|
1516
|
+
var DEFAULT_COLOR = { r: 0.95, g: 0.36, b: 0.33, a: 1 };
|
|
1517
|
+
var clamp = (v, fallback, lo, hi) => v === null || !Number.isFinite(v) ? fallback : Math.min(Math.max(v, lo), hi);
|
|
1518
|
+
function packetID(flow, k) {
|
|
1519
|
+
const n = (k + 2 ** 40) % 2 ** 48;
|
|
1520
|
+
return flow.slice(0, 24) + (n < 0 ? n + 2 ** 48 : n).toString(16).toUpperCase().padStart(12, "0");
|
|
1521
|
+
}
|
|
1522
|
+
var routeCost = (cost) => cost === null || !Number.isFinite(cost) ? 1 : Math.min(Math.max(cost, 0), 1e6);
|
|
1523
|
+
function flowTree(from, frame2, offline = /* @__PURE__ */ new Set()) {
|
|
1524
|
+
const shapes2 = new Set(frame2.elements.filter((e) => e.kind !== "connector" && !offline.has(e.id)).map((e) => e.id));
|
|
1525
|
+
if (!shapes2.has(from)) return null;
|
|
1526
|
+
const next = /* @__PURE__ */ new Map();
|
|
1527
|
+
const add2 = (a, edge) => {
|
|
1528
|
+
const list = next.get(a);
|
|
1529
|
+
if (list) list.push(edge);
|
|
1530
|
+
else next.set(a, [edge]);
|
|
1531
|
+
};
|
|
1532
|
+
for (const e of frame2.elements) {
|
|
1533
|
+
const c = e.connector;
|
|
1534
|
+
const a = c?.start.elementID ?? null, b = c?.end.elementID ?? null;
|
|
1535
|
+
if (!c || offline.has(e.id) || a === null || b === null || a === b || !shapes2.has(a) || !shapes2.has(b)) continue;
|
|
1536
|
+
const cost = routeCost(c.cost);
|
|
1537
|
+
add2(a, { to: b, via: e.id, back: false, cost });
|
|
1538
|
+
if (c.startHead !== "none") add2(b, { to: a, via: e.id, back: true, cost });
|
|
1539
|
+
}
|
|
1540
|
+
const dist = /* @__PURE__ */ new Map([[from, 0]]), order = /* @__PURE__ */ new Map([[from, 0]]);
|
|
1541
|
+
let counter = 1;
|
|
1542
|
+
const tree = new FlowTree(from);
|
|
1543
|
+
const open = [from];
|
|
1544
|
+
while (open.length > 0) {
|
|
1545
|
+
let best = 0;
|
|
1546
|
+
for (let i = 1; i < open.length; i++) {
|
|
1547
|
+
const da = dist.get(open[i]), db = dist.get(open[best]);
|
|
1548
|
+
if (da < db || da === db && order.get(open[i]) < order.get(open[best])) best = i;
|
|
1549
|
+
}
|
|
1550
|
+
const n = open.splice(best, 1)[0];
|
|
1551
|
+
tree.settled.add(n);
|
|
1552
|
+
for (const edge of next.get(n) ?? []) {
|
|
1553
|
+
if (tree.settled.has(edge.to)) continue;
|
|
1554
|
+
const d = dist.get(n) + edge.cost;
|
|
1555
|
+
const old = dist.get(edge.to);
|
|
1556
|
+
if (old !== void 0 && old <= d) continue;
|
|
1557
|
+
if (old === void 0) open.push(edge.to);
|
|
1558
|
+
dist.set(edge.to, d);
|
|
1559
|
+
order.set(edge.to, counter++);
|
|
1560
|
+
tree.came.set(edge.to, { from: n, via: edge.via, back: edge.back });
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
return tree;
|
|
1564
|
+
}
|
|
1565
|
+
var FlowTree = class {
|
|
1566
|
+
constructor(from) {
|
|
1567
|
+
__publicField(this, "settled", /* @__PURE__ */ new Set());
|
|
1568
|
+
__publicField(this, "came", /* @__PURE__ */ new Map());
|
|
1569
|
+
__publicField(this, "from");
|
|
1570
|
+
this.from = from;
|
|
1571
|
+
}
|
|
1572
|
+
/** The first of `to` that can be reached; with `spread`, when the first can't be, every other one that can. */
|
|
1573
|
+
goals(to, spread) {
|
|
1574
|
+
const reachable = to.filter((x) => x !== this.from && this.settled.has(x));
|
|
1575
|
+
if (reachable.length === 0) return [];
|
|
1576
|
+
return spread && reachable[0] !== to[0] ? reachable : [reachable[0]];
|
|
1577
|
+
}
|
|
1578
|
+
/** The connectors to `goal`, as [connector, backwards] hops. */
|
|
1579
|
+
hops(goal) {
|
|
1580
|
+
const hops = [];
|
|
1581
|
+
for (let at = goal, step = this.came.get(at); step; at = step.from, step = this.came.get(at)) hops.push([step.via, step.back]);
|
|
1582
|
+
return hops.reverse();
|
|
1583
|
+
}
|
|
1584
|
+
};
|
|
1585
|
+
|
|
1586
|
+
// src/measure.ts
|
|
1587
|
+
var emptyMetrics = () => ({ elements: /* @__PURE__ */ new Map(), flows: /* @__PURE__ */ new Map(), carry: {} });
|
|
1588
|
+
function metricValue(m, metric, id) {
|
|
1589
|
+
const f = m.flows.get(id);
|
|
1590
|
+
if (f) {
|
|
1591
|
+
switch (metric) {
|
|
1592
|
+
case "throughput":
|
|
1593
|
+
return f.placed;
|
|
1594
|
+
case "backlog":
|
|
1595
|
+
return f.backlog;
|
|
1596
|
+
case "backup":
|
|
1597
|
+
return f.backup;
|
|
1598
|
+
case "transit":
|
|
1599
|
+
return f.transit;
|
|
1600
|
+
default:
|
|
1601
|
+
return null;
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
const e = m.elements.get(id);
|
|
1605
|
+
if (!e) return null;
|
|
1606
|
+
switch (metric) {
|
|
1607
|
+
case "throughput":
|
|
1608
|
+
return e.throughput;
|
|
1609
|
+
case "load":
|
|
1610
|
+
return e.load;
|
|
1611
|
+
case "delay":
|
|
1612
|
+
return e.delay;
|
|
1613
|
+
case "backlog":
|
|
1614
|
+
return e.backlog;
|
|
1615
|
+
case "inFlight":
|
|
1616
|
+
return e.inFlight;
|
|
1617
|
+
case "depth":
|
|
1618
|
+
return e.depth;
|
|
1619
|
+
default:
|
|
1620
|
+
return null;
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
var TINY = 1e-9;
|
|
1624
|
+
var amount = (f) => clamp(f.volume, 1, 0, 1e12);
|
|
1625
|
+
function amountIn(f, values) {
|
|
1626
|
+
if (values === null || !f.overrides) return amount(f);
|
|
1627
|
+
let v = null;
|
|
1628
|
+
for (const o of f.overrides) if (o.volume !== null && !mentionsMetric(o.when) && holds(o.when, values)) v = o.volume;
|
|
1629
|
+
return v === null ? amount(f) : Math.min(Math.max(Number.isFinite(v) ? v : 1, 0), 1e12);
|
|
1630
|
+
}
|
|
1631
|
+
function delayOf(e) {
|
|
1632
|
+
const d = e.delay ?? null;
|
|
1633
|
+
if (e.kind === "connector" || d === null || !Number.isFinite(d)) return 0;
|
|
1634
|
+
return Math.min(Math.max(d, 0), 3600);
|
|
1635
|
+
}
|
|
1636
|
+
var queueKey = (shape, flow) => shape + "|" + flow;
|
|
1637
|
+
function flowFrame(flows, frame2, t, values, offline = /* @__PURE__ */ new Set(), sinceChange = null, carried = {}) {
|
|
1638
|
+
const since = Math.max(0, Math.min(sinceChange ?? t, 1e6));
|
|
1639
|
+
const byID = /* @__PURE__ */ new Map();
|
|
1640
|
+
for (const e of frame2.elements) if (!byID.has(e.id)) byID.set(e.id, e);
|
|
1641
|
+
const metrics2 = emptyMetrics();
|
|
1642
|
+
for (const e of frame2.elements) {
|
|
1643
|
+
if (e.kind === "connector" && !e.connector) continue;
|
|
1644
|
+
metrics2.elements.set(e.id, { throughput: 0, load: null, delay: e.kind !== "connector" ? delayOf(e) : 0, backlog: 0, inFlight: 0, depth: 0 });
|
|
1645
|
+
}
|
|
1646
|
+
const list = flows ?? [];
|
|
1647
|
+
const room = /* @__PURE__ */ new Map();
|
|
1648
|
+
for (const e of frame2.elements) {
|
|
1649
|
+
const c = e.capacity ?? null;
|
|
1650
|
+
if (c !== null && Number.isFinite(c)) room.set(e.id, Math.max(c, 0));
|
|
1651
|
+
}
|
|
1652
|
+
const full = () => new Set([...room].filter(([, v]) => v <= TINY).map(([id]) => id));
|
|
1653
|
+
const route2 = (tree, goal) => {
|
|
1654
|
+
const hops = [], shapes2 = [];
|
|
1655
|
+
let at = goal;
|
|
1656
|
+
for (let step = tree.came.get(at); step; step = tree.came.get(at)) {
|
|
1657
|
+
hops.push([step.via, step.back]);
|
|
1658
|
+
shapes2.push(at);
|
|
1659
|
+
at = step.from;
|
|
1660
|
+
}
|
|
1661
|
+
shapes2.push(at);
|
|
1662
|
+
return { hops: hops.reverse(), shapes: shapes2.reverse() };
|
|
1663
|
+
};
|
|
1664
|
+
const way = (hops, shapes2) => {
|
|
1665
|
+
const points = [], inside = [], stretches = [];
|
|
1666
|
+
let length = 0;
|
|
1667
|
+
hops.forEach(([id, back], k) => {
|
|
1668
|
+
const c = byID.get(id);
|
|
1669
|
+
if (!c) return;
|
|
1670
|
+
const line2 = back ? [...tracing(c, frame2).points].reverse() : tracing(c, frame2).points;
|
|
1671
|
+
if (line2.length === 0) return;
|
|
1672
|
+
const last = points[points.length - 1];
|
|
1673
|
+
if (last) {
|
|
1674
|
+
const gap = Math.hypot(line2[0].x - last.x, line2[0].y - last.y);
|
|
1675
|
+
const shape = byID.get(shapes2[k]);
|
|
1676
|
+
inside.push([length, length + gap, shape ? delayOf(shape) : 0]);
|
|
1677
|
+
length += gap;
|
|
1678
|
+
}
|
|
1679
|
+
const start = length;
|
|
1680
|
+
for (let q = 1; q < line2.length; q++) length += Math.hypot(line2[q].x - line2[q - 1].x, line2[q].y - line2[q - 1].y);
|
|
1681
|
+
stretches.push([id, start, length]);
|
|
1682
|
+
points.push(...line2);
|
|
1683
|
+
});
|
|
1684
|
+
return length > 0 ? { line: new Polyline(points), length, inside, stretches } : null;
|
|
1685
|
+
};
|
|
1686
|
+
const along = (w, elapsedAt, speed) => {
|
|
1687
|
+
let elapsed = elapsedAt, d = 0;
|
|
1688
|
+
for (const [a, b, delay] of w.inside) {
|
|
1689
|
+
const reach = (a - d) / speed;
|
|
1690
|
+
if (elapsed <= reach) break;
|
|
1691
|
+
elapsed -= reach;
|
|
1692
|
+
d = a;
|
|
1693
|
+
const stay = (b - a) / speed + delay;
|
|
1694
|
+
if (elapsed < stay) return null;
|
|
1695
|
+
elapsed -= stay;
|
|
1696
|
+
d = b;
|
|
1697
|
+
}
|
|
1698
|
+
d += elapsed * speed;
|
|
1699
|
+
return d >= 0 && d <= w.length ? d : null;
|
|
1700
|
+
};
|
|
1701
|
+
const union2 = (a, b) => /* @__PURE__ */ new Set([...a, ...b]);
|
|
1702
|
+
const bump = (id, f) => {
|
|
1703
|
+
const m = metrics2.elements.get(id);
|
|
1704
|
+
if (m) f(m);
|
|
1705
|
+
};
|
|
1706
|
+
const leaving = /* @__PURE__ */ new Map(), turn = [];
|
|
1707
|
+
for (const flow of list) {
|
|
1708
|
+
const n = leaving.get(flow.from) ?? 0;
|
|
1709
|
+
turn.push(n);
|
|
1710
|
+
leaving.set(flow.from, n + 1);
|
|
1711
|
+
}
|
|
1712
|
+
const biggest = list.length > 0 ? Math.max(...list.map(amount)) : 1;
|
|
1713
|
+
const packets = [];
|
|
1714
|
+
const waitingBy = /* @__PURE__ */ new Map();
|
|
1715
|
+
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);
|
|
1716
|
+
for (const [i, flow] of list.entries()) {
|
|
1717
|
+
if (flow.when !== null && values !== null && !holds(flow.when, values)) continue;
|
|
1718
|
+
const source = byID.get(flow.from);
|
|
1719
|
+
const base2 = source ? flowTree(flow.from, frame2, offline) : null;
|
|
1720
|
+
if (!source || !base2) continue;
|
|
1721
|
+
const volume = amountIn(flow, values);
|
|
1722
|
+
const fm = { volume, placed: 0, backlog: 0, backup: 0, transit: null };
|
|
1723
|
+
const placements = [];
|
|
1724
|
+
const waiting = [];
|
|
1725
|
+
const goals = base2.goals(flow.to, flow.spread === true);
|
|
1726
|
+
const shares = goals.length > 1 ? goals.map((g) => [[g], volume / goals.length]) : [[flow.to, volume]];
|
|
1727
|
+
for (const [candidates, share] of shares) {
|
|
1728
|
+
if (!(share > TINY)) continue;
|
|
1729
|
+
let left = share;
|
|
1730
|
+
for (let n = 0; n < 32 && left > TINY; n++) {
|
|
1731
|
+
const tree = flowTree(flow.from, frame2, union2(offline, full()));
|
|
1732
|
+
const goal2 = tree?.goals(candidates, false)[0];
|
|
1733
|
+
if (!tree || goal2 === void 0) break;
|
|
1734
|
+
const r = route2(tree, goal2);
|
|
1735
|
+
const ids = [...r.shapes, ...r.hops.map(([id]) => id)];
|
|
1736
|
+
const rooms = ids.filter((id) => room.has(id)).map((id) => room.get(id));
|
|
1737
|
+
const fits = Math.min(left, rooms.length > 0 ? Math.min(...rooms) : Infinity);
|
|
1738
|
+
if (!(fits > TINY)) break;
|
|
1739
|
+
for (const id of ids) if (room.has(id)) room.set(id, room.get(id) - fits);
|
|
1740
|
+
placements.push({ hops: r.hops, shapes: r.shapes, goal: goal2, amount: fits });
|
|
1741
|
+
left -= fits;
|
|
1742
|
+
}
|
|
1743
|
+
if (!(left > TINY)) continue;
|
|
1744
|
+
let at = flow.from;
|
|
1745
|
+
const goal = base2.goals(candidates, false)[0];
|
|
1746
|
+
if (goal !== void 0) {
|
|
1747
|
+
const r = route2(base2, goal), done = full();
|
|
1748
|
+
const sequence = [[r.shapes[0], r.shapes[0]]];
|
|
1749
|
+
r.hops.forEach(([id], k) => {
|
|
1750
|
+
sequence.push([id, r.shapes[k]], [r.shapes[k + 1], r.shapes[k]]);
|
|
1751
|
+
});
|
|
1752
|
+
const first = sequence.find(([id]) => done.has(id));
|
|
1753
|
+
if (first) {
|
|
1754
|
+
at = first[1];
|
|
1755
|
+
const upTo = r.shapes.indexOf(at);
|
|
1756
|
+
for (let q = upTo; q >= 0; q--) {
|
|
1757
|
+
const s = byID.get(r.shapes[q]);
|
|
1758
|
+
if (s && s.queue === true && s.kind !== "connector") {
|
|
1759
|
+
at = r.shapes[q];
|
|
1760
|
+
break;
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
const n = r.shapes.indexOf(at);
|
|
1765
|
+
if (at !== flow.from && n >= 0) {
|
|
1766
|
+
const shapes2 = r.shapes.slice(0, n + 1), hops = r.hops.slice(0, n);
|
|
1767
|
+
for (const id of [...shapes2, ...hops.map(([h]) => h)]) if (room.has(id)) room.set(id, Math.max(0, room.get(id) - left));
|
|
1768
|
+
placements.push({ hops, shapes: shapes2, goal: at, amount: left, waits: true });
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
const existing = waiting.find(([s]) => s === at);
|
|
1772
|
+
if (existing) existing[1] += left;
|
|
1773
|
+
else waiting.push([at, left]);
|
|
1774
|
+
}
|
|
1775
|
+
waitingBy.set(flow.id, waiting);
|
|
1776
|
+
for (const p of placements) {
|
|
1777
|
+
for (const id of [...p.shapes, ...p.hops.map(([h]) => h)]) bump(id, (m) => {
|
|
1778
|
+
m.throughput += p.amount;
|
|
1779
|
+
});
|
|
1780
|
+
if (!p.waits) fm.placed += p.amount;
|
|
1781
|
+
}
|
|
1782
|
+
for (const [at, a] of waiting) {
|
|
1783
|
+
bump(at, (m) => {
|
|
1784
|
+
m.backlog += a;
|
|
1785
|
+
});
|
|
1786
|
+
fm.backlog += a;
|
|
1787
|
+
}
|
|
1788
|
+
const reachedFirst = placements.filter((p) => !p.waits && p.goal === flow.to[0]).reduce((s, p) => s + p.amount, 0);
|
|
1789
|
+
fm.backup = volume > TINY ? Math.max(0, (volume - reachedFirst) / volume) : 0;
|
|
1790
|
+
const every = everyOf(flow);
|
|
1791
|
+
const speed = clamp(flow.speed, DEFAULT_SPEED, 1, 1e4), size = clamp(flow.size, DEFAULT_SIZE, 2, 200);
|
|
1792
|
+
const phase = turn[i] / leaving.get(flow.from) * every;
|
|
1793
|
+
const color2 = flow.color ?? DEFAULT_COLOR;
|
|
1794
|
+
const cumulative = [];
|
|
1795
|
+
let sum = 0;
|
|
1796
|
+
for (const p of placements) {
|
|
1797
|
+
sum += p.amount / Math.max(volume, TINY);
|
|
1798
|
+
cumulative.push(sum);
|
|
1799
|
+
}
|
|
1800
|
+
if (cumulative.length > 0 && Math.abs(cumulative[cumulative.length - 1] - 1) < 1e-9) cumulative[cumulative.length - 1] = 1;
|
|
1801
|
+
let transitSum = 0;
|
|
1802
|
+
placements.forEach((p, r) => {
|
|
1803
|
+
const w = way(p.hops, p.shapes);
|
|
1804
|
+
if (!w) return;
|
|
1805
|
+
const travel = w.length / speed + w.inside.reduce((s, x) => s + x[2], 0);
|
|
1806
|
+
if (!p.waits) transitSum += travel * p.amount;
|
|
1807
|
+
const lo = Math.ceil((t - phase - travel) / every);
|
|
1808
|
+
const hi = Math.floor((t - phase) / every);
|
|
1809
|
+
const below = r === 0 ? 0 : cumulative[r - 1], upTo = cumulative[r];
|
|
1810
|
+
const dealt = (c, k) => Math.floor((k + 1) * c) - Math.floor(k * c);
|
|
1811
|
+
for (let k = Math.max(lo, hi - 3999); k <= hi; k++) {
|
|
1812
|
+
if (dealt(upTo, k) - dealt(below, k) !== 1) continue;
|
|
1813
|
+
const d = along(w, t - k * every - phase, speed);
|
|
1814
|
+
if (d === null) continue;
|
|
1815
|
+
const s = w.stretches.find(([, a, b]) => d >= a && d <= b);
|
|
1816
|
+
if (s) bump(s[0], (m) => {
|
|
1817
|
+
m.inFlight += 1;
|
|
1818
|
+
});
|
|
1819
|
+
const pt2 = w.line.point(d / w.length);
|
|
1820
|
+
packets.push(packet(packetID(flow.id, k), pt2.x - size / 2, pt2.y - size / 2, size, color2, source.layerID));
|
|
1821
|
+
}
|
|
1822
|
+
});
|
|
1823
|
+
fm.transit = fm.placed > TINY ? transitSum / fm.placed : null;
|
|
1824
|
+
metrics2.flows.set(flow.id, fm);
|
|
1825
|
+
}
|
|
1826
|
+
const piles = /* @__PURE__ */ new Map();
|
|
1827
|
+
for (const flow of list) {
|
|
1828
|
+
const waiting = waitingBy.get(flow.id) ?? [];
|
|
1829
|
+
const shapes2 = waiting.map(([s]) => s);
|
|
1830
|
+
for (const e of frame2.elements) {
|
|
1831
|
+
const c = carried[queueKey(e.id, flow.id)];
|
|
1832
|
+
if (c !== void 0 && c > TINY && !shapes2.includes(e.id)) shapes2.push(e.id);
|
|
1833
|
+
}
|
|
1834
|
+
if (shapes2.length === 0) continue;
|
|
1835
|
+
const volume = amountIn(flow, values);
|
|
1836
|
+
const every = everyOf(flow);
|
|
1837
|
+
const unit = every * Math.max(volume, TINY);
|
|
1838
|
+
const speed = clamp(flow.speed, DEFAULT_SPEED, 1, 1e4), size = clamp(flow.size, DEFAULT_SIZE, 2, 200);
|
|
1839
|
+
const color2 = flow.color ?? DEFAULT_COLOR;
|
|
1840
|
+
shapes2.forEach((at, j) => {
|
|
1841
|
+
const shape = byID.get(at);
|
|
1842
|
+
if (!shape) return;
|
|
1843
|
+
const layer = byID.get(flow.from)?.layerID ?? shape.layerID;
|
|
1844
|
+
const backlog = waiting.find(([s]) => s === at)?.[1] ?? 0;
|
|
1845
|
+
const before = Math.max(0, carried[queueKey(at, flow.id)] ?? 0);
|
|
1846
|
+
let depth = before + backlog * since;
|
|
1847
|
+
if (backlog <= TINY && before > TINY) {
|
|
1848
|
+
const trial = new Map(room);
|
|
1849
|
+
const outlets = [];
|
|
1850
|
+
let total = 0;
|
|
1851
|
+
for (let n2 = 0; n2 < 32; n2++) {
|
|
1852
|
+
const blocked = new Set([...trial].filter(([, v]) => v <= TINY).map(([id]) => id));
|
|
1853
|
+
const tree = flowTree(at, frame2, union2(offline, blocked));
|
|
1854
|
+
const goal = tree?.goals(flow.to, false)[0];
|
|
1855
|
+
if (!tree || goal === void 0) break;
|
|
1856
|
+
const r = route2(tree, goal);
|
|
1857
|
+
const ids = [...r.shapes.slice(1), ...r.hops.map(([id]) => id)];
|
|
1858
|
+
const rooms = ids.filter((id) => trial.has(id)).map((id) => trial.get(id));
|
|
1859
|
+
const spare = rooms.length > 0 ? Math.min(...rooms) : Infinity;
|
|
1860
|
+
const rate = Number.isFinite(spare) ? spare : Math.max(before / 0.5 - total, TINY * 2);
|
|
1861
|
+
if (!(rate > TINY)) break;
|
|
1862
|
+
for (const id of ids) if (trial.has(id)) trial.set(id, trial.get(id) - rate);
|
|
1863
|
+
outlets.push({ hops: r.hops, shapes: r.shapes, rate });
|
|
1864
|
+
total += rate;
|
|
1865
|
+
if (!Number.isFinite(spare)) break;
|
|
1866
|
+
}
|
|
1867
|
+
if (total > TINY) {
|
|
1868
|
+
depth = Math.max(0, before - total * since);
|
|
1869
|
+
if (depth > TINY) {
|
|
1870
|
+
for (const [id, v] of trial) room.set(id, v);
|
|
1871
|
+
for (const o of outlets) for (const id of [...o.shapes, ...o.hops.map(([h]) => h)]) bump(id, (m) => {
|
|
1872
|
+
m.throughput += o.rate;
|
|
1873
|
+
});
|
|
1874
|
+
}
|
|
1875
|
+
const lasts = before / total;
|
|
1876
|
+
outlets.forEach((o, i) => {
|
|
1877
|
+
const w = way(o.hops, o.shapes);
|
|
1878
|
+
if (!w) return;
|
|
1879
|
+
const spacing = Math.max(unit / o.rate, 0.05);
|
|
1880
|
+
for (let k = 0; k * spacing < lasts && k * spacing <= since && k < 2048; k++) {
|
|
1881
|
+
const d = along(w, since - k * spacing, speed);
|
|
1882
|
+
if (d === null) continue;
|
|
1883
|
+
const pt2 = w.line.point(d / w.length);
|
|
1884
|
+
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));
|
|
1885
|
+
}
|
|
1886
|
+
});
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
if (!(depth > TINY || backlog > TINY)) return;
|
|
1890
|
+
bump(at, (m) => {
|
|
1891
|
+
m.depth += depth;
|
|
1892
|
+
});
|
|
1893
|
+
if (depth > TINY) metrics2.carry[queueKey(at, flow.id)] = depth;
|
|
1894
|
+
const n = Math.min(Math.max(Math.ceil(depth / unit - 1e-9), 1), 30);
|
|
1895
|
+
const rows = Math.trunc((n + 9) / 10);
|
|
1896
|
+
const base2 = piles.get(at) ?? 0;
|
|
1897
|
+
piles.set(at, base2 + rows);
|
|
1898
|
+
for (let q = 0; q < n; q++) {
|
|
1899
|
+
const cx = shape.x + 2 + size / 2 + q % 10 * (size + 3);
|
|
1900
|
+
const cy = shape.y - 4 - size / 2 - (base2 + Math.trunc(q / 10)) * (size + 3);
|
|
1901
|
+
packets.push(packet(packetID(flow.id, 2 ** 39 + j * 64 + q), cx - size / 2, cy - size / 2, size, color2, layer));
|
|
1902
|
+
}
|
|
1903
|
+
});
|
|
1904
|
+
}
|
|
1905
|
+
for (const e of frame2.elements) {
|
|
1906
|
+
const c = e.capacity ?? null;
|
|
1907
|
+
const m = metrics2.elements.get(e.id);
|
|
1908
|
+
if (c === null || !Number.isFinite(c) || !m) continue;
|
|
1909
|
+
m.load = c > 0 ? m.throughput / c : 1;
|
|
1910
|
+
}
|
|
1911
|
+
return { packets, metrics: metrics2 };
|
|
1912
|
+
}
|
|
1913
|
+
var BLANK = {
|
|
1914
|
+
seed: 1,
|
|
1915
|
+
text: null,
|
|
1916
|
+
connector: null,
|
|
1917
|
+
groupID: null,
|
|
1918
|
+
points: null,
|
|
1919
|
+
path: null,
|
|
1920
|
+
subpaths: null,
|
|
1921
|
+
evenOdd: null,
|
|
1922
|
+
rotation: null,
|
|
1923
|
+
link: null,
|
|
1924
|
+
url: null,
|
|
1925
|
+
anchor: null,
|
|
1926
|
+
click: null,
|
|
1927
|
+
hover: null,
|
|
1928
|
+
when: null,
|
|
1929
|
+
overrides: null,
|
|
1930
|
+
symbol: null,
|
|
1931
|
+
playback: null,
|
|
1932
|
+
offset: null,
|
|
1933
|
+
args: null,
|
|
1934
|
+
image: null,
|
|
1935
|
+
reveal: null
|
|
1936
|
+
};
|
|
1937
|
+
function style(stroke, fill, strokeWidth, fillStyle = "solid", opacity2 = null) {
|
|
1938
|
+
return {
|
|
1939
|
+
stroke: { ...stroke },
|
|
1940
|
+
fill: { ...fill },
|
|
1941
|
+
fillStyle,
|
|
1942
|
+
strokeWidth,
|
|
1943
|
+
dash: "solid",
|
|
1944
|
+
roughness: 0,
|
|
1945
|
+
bowing: 1,
|
|
1946
|
+
fontSize: 18,
|
|
1947
|
+
font: null,
|
|
1948
|
+
gridSize: null,
|
|
1949
|
+
opacity: opacity2,
|
|
1950
|
+
dashOffset: null,
|
|
1951
|
+
textColor: null
|
|
1952
|
+
};
|
|
1953
|
+
}
|
|
1954
|
+
function packet(id, x, y, size, color2, layerID) {
|
|
1955
|
+
return { ...BLANK, id, kind: "ellipse", layerID, x, y, width: size, height: size, style: style(color2, color2, 1.5) };
|
|
1956
|
+
}
|
|
1957
|
+
function metricLookup(m, frame2) {
|
|
1958
|
+
const anchors = /* @__PURE__ */ new Map();
|
|
1959
|
+
for (const e of frame2.elements) if (e.anchor !== null && !anchors.has(e.anchor)) anchors.set(e.anchor, e.id);
|
|
1960
|
+
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}$/;
|
|
1961
|
+
return (term, own) => {
|
|
1962
|
+
const id = term.target === "" ? own : anchors.get(term.target) ?? (UUID2.test(term.target) ? term.target.toUpperCase() : null);
|
|
1963
|
+
return id === null ? null : metricValue(m, term.metric, id);
|
|
1964
|
+
};
|
|
1965
|
+
}
|
|
1966
|
+
function finishing(elements, metrics2, values, frame2, flows, since = Infinity, carried = {}) {
|
|
1967
|
+
const lookup = metricLookup(metrics2, frame2);
|
|
1968
|
+
const out = [], readings = {};
|
|
1969
|
+
for (let e of elements) {
|
|
1970
|
+
if (values !== null && e.overrides) {
|
|
1971
|
+
for (const o of e.overrides) {
|
|
1972
|
+
const own = e.id;
|
|
1973
|
+
if (mentionsMetric(o.when) && holds(o.when, values, (m) => lookup(m, own))) e = applyOverride(looksOnly(o), e);
|
|
1974
|
+
}
|
|
1975
|
+
}
|
|
1976
|
+
if (!e.gauge || values === null) {
|
|
1977
|
+
out.push(e);
|
|
1978
|
+
continue;
|
|
1979
|
+
}
|
|
1980
|
+
const g = e.gauge;
|
|
1981
|
+
let shown = metricValue(metrics2, g.metric, g.of);
|
|
1982
|
+
const then = carried[gaugeKey(e.id)];
|
|
1983
|
+
if (shown !== null && then !== void 0 && g.metric !== "depth" && g.metric !== "inFlight" && since < GAUGE_EASE) {
|
|
1984
|
+
const f = Math.max(0, since / GAUGE_EASE), smooth = f * f * (3 - 2 * f);
|
|
1985
|
+
shown = then + (shown - then) * smooth;
|
|
1986
|
+
}
|
|
1987
|
+
if (shown !== null) readings[gaugeKey(e.id)] = shown;
|
|
1988
|
+
out.push(...gauge(e, g, shown, frame2, flows));
|
|
1989
|
+
}
|
|
1990
|
+
return { elements: out, readings };
|
|
1991
|
+
}
|
|
1992
|
+
var gaugeKey = (id) => "gauge|" + id;
|
|
1993
|
+
var GAUGE_EASE = 1;
|
|
1994
|
+
var hex = (h) => {
|
|
1995
|
+
const v = parseInt(h.slice(1), 16);
|
|
1996
|
+
return { r: (v >> 16 & 255) / 255, g: (v >> 8 & 255) / 255, b: (v & 255) / 255, a: 1 };
|
|
1997
|
+
};
|
|
1998
|
+
var GAUGE_GREEN = hex("#16a34a");
|
|
1999
|
+
var GAUGE_AMBER = hex("#d97706");
|
|
2000
|
+
var GAUGE_RED = hex("#dc2626");
|
|
2001
|
+
var GAUGE_BLUE = hex("#2f6fdf");
|
|
2002
|
+
var GAUGE_TRACK = hex("#e5e7eb");
|
|
2003
|
+
var GAUGE_GRAY = hex("#9ca3af");
|
|
2004
|
+
function formatted(metric, v) {
|
|
2005
|
+
if (v === null || !Number.isFinite(v)) return "\u2013";
|
|
2006
|
+
switch (metric) {
|
|
2007
|
+
case "load":
|
|
2008
|
+
case "backup":
|
|
2009
|
+
return formatNumber(v * 100, 0) + "%";
|
|
2010
|
+
case "transit":
|
|
2011
|
+
case "delay":
|
|
2012
|
+
return formatNumber(v, Math.abs(v) >= 10 ? 1 : 2) + "s";
|
|
2013
|
+
default:
|
|
2014
|
+
return formatNumber(v, Math.abs(v) >= 100 ? 0 : Math.abs(v) >= 10 ? 1 : 2);
|
|
2015
|
+
}
|
|
2016
|
+
}
|
|
2017
|
+
function formatNumber(v, places) {
|
|
2018
|
+
const scale = places === 0 ? 1 : places === 1 ? 10 : 100;
|
|
2019
|
+
const n = Math.floor(Math.abs(v) * scale + 0.5);
|
|
2020
|
+
if (!(n < 1e15)) return v < 0 ? "-\u221E" : "\u221E";
|
|
2021
|
+
const whole = Math.trunc(n / scale), part = n - whole * scale;
|
|
2022
|
+
let s = String(whole);
|
|
2023
|
+
if (places > 0 && part > 0) {
|
|
2024
|
+
let digits = String(part);
|
|
2025
|
+
while (digits.length < places) digits = "0" + digits;
|
|
2026
|
+
while (digits.endsWith("0")) digits = digits.slice(0, -1);
|
|
2027
|
+
s += "." + digits;
|
|
2028
|
+
}
|
|
2029
|
+
return (v < 0 && n > 0 ? "-" : "") + s;
|
|
2030
|
+
}
|
|
2031
|
+
function gauge(e, g, value2, frame2, flows) {
|
|
2032
|
+
const target = frame2.elements.find((x) => x.id === g.of);
|
|
2033
|
+
const full = g.max ?? (g.metric === "load" ? 1 : target?.capacity ?? value2 ?? 1);
|
|
2034
|
+
const fraction = value2 === null ? 0 : full > 0 ? Math.min(Math.max(value2 / full, 0), 1) : 0;
|
|
2035
|
+
const warn = g.warn ?? (g.metric === "load" ? 0.7 : null), alarm = g.alarm ?? (g.metric === "load" ? 0.9 : null);
|
|
2036
|
+
let color2 = warn === null && alarm === null ? GAUGE_BLUE : GAUGE_GREEN;
|
|
2037
|
+
if (value2 !== null && warn !== null && value2 >= warn) color2 = GAUGE_AMBER;
|
|
2038
|
+
if (value2 !== null && alarm !== null && value2 >= alarm) color2 = GAUGE_RED;
|
|
2039
|
+
if (value2 === null) color2 = GAUGE_GRAY;
|
|
2040
|
+
let name = g.label;
|
|
2041
|
+
if (name === null) {
|
|
2042
|
+
const what = target ? target.text !== null ? target.text.split("\n")[0] : target.anchor : null;
|
|
2043
|
+
name = `${g.metric} ${what ?? (flows.some((f) => f.id === g.of) ? "flow" : "?")}`;
|
|
2044
|
+
}
|
|
2045
|
+
const kind = g.style ?? "number";
|
|
2046
|
+
const w = Math.max(e.width, 1), h = Math.max(e.height, 1);
|
|
2047
|
+
const line2 = (a, b) => [
|
|
2048
|
+
a.x / w,
|
|
2049
|
+
a.y / h,
|
|
2050
|
+
(a.x + (b.x - a.x) / 3) / w,
|
|
2051
|
+
(a.y + (b.y - a.y) / 3) / h,
|
|
2052
|
+
(a.x + 2 * (b.x - a.x) / 3) / w,
|
|
2053
|
+
(a.y + 2 * (b.y - a.y) / 3) / h,
|
|
2054
|
+
b.x / w,
|
|
2055
|
+
b.y / h
|
|
2056
|
+
];
|
|
2057
|
+
const box = (x, y, bw, bh) => {
|
|
2058
|
+
const p = [{ x, y }, { x: x + bw, y }, { x: x + bw, y: y + bh }, { x, y: y + bh }];
|
|
2059
|
+
let coords = line2(p[0], p[1]);
|
|
2060
|
+
for (let k = 1; k < 4; k++) coords = coords.concat(line2(p[k], p[(k + 1) % 4]).slice(2));
|
|
2061
|
+
return { closed: true, coords };
|
|
2062
|
+
};
|
|
2063
|
+
const arc = (c, radius, a0, a1) => {
|
|
2064
|
+
const pieces2 = Math.max(1, Math.ceil(Math.abs(a1 - a0) / (Math.PI / 2) - 1e-9));
|
|
2065
|
+
const step = (a1 - a0) / pieces2, kappa = 4 / 3 * Math.tan(step / 4);
|
|
2066
|
+
const at = (a) => ({ x: c.x + radius * Math.cos(a), y: c.y + radius * Math.sin(a) });
|
|
2067
|
+
const s = at(a0);
|
|
2068
|
+
const coords = [s.x / w, s.y / h];
|
|
2069
|
+
for (let k = 0; k < pieces2; k++) {
|
|
2070
|
+
const a = a0 + k * step, b = a + step;
|
|
2071
|
+
const p0 = at(a), p3 = at(b);
|
|
2072
|
+
const c1 = { x: p0.x - kappa * radius * Math.sin(a), y: p0.y + kappa * radius * Math.cos(a) };
|
|
2073
|
+
const c2 = { x: p3.x + kappa * radius * Math.sin(b), y: p3.y - kappa * radius * Math.cos(b) };
|
|
2074
|
+
coords.push(c1.x / w, c1.y / h, c2.x / w, c2.y / h, p3.x / w, p3.y / h);
|
|
2075
|
+
}
|
|
2076
|
+
return { closed: false, coords };
|
|
2077
|
+
};
|
|
2078
|
+
const part = (k, subpaths, stroke, fill, width) => ({
|
|
2079
|
+
...BLANK,
|
|
2080
|
+
id: packetID(e.id, -(2 ** 38) - k),
|
|
2081
|
+
kind: "path",
|
|
2082
|
+
layerID: e.layerID,
|
|
2083
|
+
x: e.x,
|
|
2084
|
+
y: e.y,
|
|
2085
|
+
width: e.width,
|
|
2086
|
+
height: e.height,
|
|
2087
|
+
style: style(stroke, fill ?? { r: 0, g: 0, b: 0, a: 0 }, width, "solid", e.style.opacity),
|
|
2088
|
+
subpaths
|
|
2089
|
+
});
|
|
2090
|
+
const back = {
|
|
2091
|
+
...e,
|
|
2092
|
+
gauge: null,
|
|
2093
|
+
kind: "path",
|
|
2094
|
+
subpaths: [box(0, 0, w, h)],
|
|
2095
|
+
text: name + "\n" + formatted(g.metric, value2),
|
|
2096
|
+
style: { ...e.style }
|
|
2097
|
+
};
|
|
2098
|
+
const parts = [];
|
|
2099
|
+
if (kind === "number") {
|
|
2100
|
+
back.labelRect = { x: 0, y: 0, width: 1, height: 1 };
|
|
2101
|
+
back.style.textColor = { ...color2 };
|
|
2102
|
+
} else if (kind === "bar") {
|
|
2103
|
+
back.labelRect = { x: 0, y: 0, width: 1, height: 0.66 };
|
|
2104
|
+
const inset2 = Math.min(10, w * 0.08), barH = Math.min(12, h * 0.18), top = h * 0.72;
|
|
2105
|
+
const trackW = w - 2 * inset2;
|
|
2106
|
+
parts.push(part(1, [box(inset2, top, trackW, barH)], GAUGE_TRACK, GAUGE_TRACK, 0.5));
|
|
2107
|
+
if (fraction > 0) parts.push(part(2, [box(inset2, top, trackW * fraction, barH)], color2, color2, 0.5));
|
|
2108
|
+
} else {
|
|
2109
|
+
back.labelRect = { x: 0, y: 0.54, width: 1, height: 0.46 };
|
|
2110
|
+
const radius = Math.min(w * 0.38, h * 0.4), center2 = { x: w / 2, y: h * 0.5 };
|
|
2111
|
+
const thick = Math.max(2, radius * 0.16);
|
|
2112
|
+
parts.push(part(1, [arc(center2, radius, Math.PI, 2 * Math.PI)], GAUGE_TRACK, null, thick));
|
|
2113
|
+
if (fraction > 0) parts.push(part(2, [arc(center2, radius, Math.PI, Math.PI + Math.PI * fraction)], color2, null, thick));
|
|
2114
|
+
const a = Math.PI + Math.PI * fraction;
|
|
2115
|
+
const tip = { x: center2.x + radius * 0.85 * Math.cos(a), y: center2.y + radius * 0.85 * Math.sin(a) };
|
|
2116
|
+
parts.push(part(3, [{ closed: false, coords: line2(center2, tip) }], { r: 0.2, g: 0.2, b: 0.2, a: 1 }, null, 2));
|
|
2117
|
+
}
|
|
2118
|
+
return [back, ...parts];
|
|
2119
|
+
}
|
|
2120
|
+
|
|
1347
2121
|
// src/rough.ts
|
|
1348
|
-
var offset = (rng,
|
|
2122
|
+
var offset = (rng, amount2, roughness) => rng.signed() * amount2 * roughness;
|
|
1349
2123
|
function line(a, b, rng, roughness, bowing, preserveEnds, into) {
|
|
1350
2124
|
const length = Math.hypot(b.x - a.x, b.y - a.y);
|
|
1351
2125
|
if (!(length > 0.5)) return;
|
|
@@ -2201,16 +2975,39 @@ function trackMap(tl) {
|
|
|
2201
2975
|
for (const t of tl.tracks) if (!m.has(t.element)) m.set(t.element, t);
|
|
2202
2976
|
return m;
|
|
2203
2977
|
}
|
|
2204
|
-
function resolved(page, t, state = "initial") {
|
|
2205
|
-
return expandingSymbols(posed(page, t, state), clampTime(t));
|
|
2978
|
+
function resolved(page, t, state = "initial", since = null, carried = {}) {
|
|
2979
|
+
return expandingSymbols(posed(page, t, state, since, carried), clampTime(t));
|
|
2206
2980
|
}
|
|
2207
2981
|
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") {
|
|
2982
|
+
function posed(page, t, state = "initial", since = null, carried = {}) {
|
|
2983
|
+
return posedWithMetrics(page, t, state, since, carried).frame;
|
|
2984
|
+
}
|
|
2985
|
+
function posedWithMetrics(page, t, state = "initial", since = null, carried = {}) {
|
|
2209
2986
|
const off = stateHidden(page, state);
|
|
2210
|
-
const
|
|
2211
|
-
|
|
2987
|
+
const values = stateValues(page, state);
|
|
2988
|
+
const active = /* @__PURE__ */ new Map();
|
|
2989
|
+
for (const e of page.elements) {
|
|
2990
|
+
const o = activeOverrides(e, values);
|
|
2991
|
+
if (o.length > 0) active.set(e.id, o);
|
|
2992
|
+
}
|
|
2993
|
+
const flowing = (page.flows ?? []).length > 0;
|
|
2994
|
+
const measuring = flowing || page.elements.some((e) => e.gauge || (e.overrides ?? []).some((o) => mentionsMetric(o.when)));
|
|
2995
|
+
const timeline = page.timeline ?? (off.size === 0 && active.size === 0 && !measuring ? null : EMPTY_TIMELINE);
|
|
2996
|
+
if (!timeline) return { frame: page, metrics: emptyMetrics() };
|
|
2212
2997
|
t = clampTime(t);
|
|
2213
2998
|
const tracks = trackMap(timeline);
|
|
2999
|
+
for (const [id, overrides] of active) {
|
|
3000
|
+
const track = tracks.get(id);
|
|
3001
|
+
if (!track?.follow || !overrides.some((o) => o.reroute)) continue;
|
|
3002
|
+
tracks.set(id, {
|
|
3003
|
+
...track,
|
|
3004
|
+
follow: track.follow.map((f) => {
|
|
3005
|
+
let path = f.path;
|
|
3006
|
+
for (const o of overrides) path = o.reroute?.[path] ?? path;
|
|
3007
|
+
return { ...f, path };
|
|
3008
|
+
})
|
|
3009
|
+
});
|
|
3010
|
+
}
|
|
2214
3011
|
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
3012
|
const hidden = /* @__PURE__ */ new Set();
|
|
2216
3013
|
for (const tr of tracks.values()) if (!isVisible(tr, t)) hidden.add(tr.element);
|
|
@@ -2230,6 +3027,14 @@ function posed(page, t, state = "initial") {
|
|
|
2230
3027
|
let elements = page.elements.map((e) => {
|
|
2231
3028
|
const track = tracks.get(e.id);
|
|
2232
3029
|
let out = track ? applyTrack(track, e, t) : e;
|
|
3030
|
+
for (const o of active.get(e.id) ?? []) out = applyOverride(o, out);
|
|
3031
|
+
const list = active.get(e.id);
|
|
3032
|
+
if (list) {
|
|
3033
|
+
const caps = list.map((o) => o.capacity).filter((c) => c !== null && Number.isFinite(c));
|
|
3034
|
+
const delays = list.map((o) => o.delay).filter((d) => d !== null && Number.isFinite(d));
|
|
3035
|
+
if (caps.length > 0) out.capacity = Math.max(Math.min(...caps), 0);
|
|
3036
|
+
if (delays.length > 0 && out.kind !== "connector") out.delay = Math.max(Math.max(...delays), 0);
|
|
3037
|
+
}
|
|
2233
3038
|
if (reseed !== null && reseed > 0 && boils(e)) {
|
|
2234
3039
|
if (out === e) out = cloneElement(e);
|
|
2235
3040
|
out.seed = boiled(e.seed, reseed);
|
|
@@ -2263,7 +3068,18 @@ function posed(page, t, state = "initial") {
|
|
|
2263
3068
|
pinned.connector.end = { elementID: null, port: c.end.port, x: b.x, y: b.y };
|
|
2264
3069
|
return pinned;
|
|
2265
3070
|
});
|
|
2266
|
-
|
|
3071
|
+
elements = elements.filter((e) => !gone.has(e.id));
|
|
3072
|
+
let metrics2 = emptyMetrics();
|
|
3073
|
+
if (measuring) {
|
|
3074
|
+
const offline = new Set([...active].filter(([, list]) => list.some((o) => o.offline === true)).map(([id]) => id));
|
|
3075
|
+
const frame2 = { ...page, elements, timeline: null };
|
|
3076
|
+
const out = flowFrame(page.flows, frame2, t, values, offline, since, carried);
|
|
3077
|
+
metrics2 = out.metrics;
|
|
3078
|
+
const finished = finishing(elements, out.metrics, values, frame2, page.flows ?? [], Math.max(0, Math.min(since ?? t, 1e6)), carried);
|
|
3079
|
+
Object.assign(metrics2.carry, finished.readings);
|
|
3080
|
+
elements = [...finished.elements, ...out.packets];
|
|
3081
|
+
}
|
|
3082
|
+
return { frame: { ...page, elements, timeline: null, flows: measuring ? null : page.flows }, metrics: metrics2 };
|
|
2267
3083
|
}
|
|
2268
3084
|
function posterTime(page) {
|
|
2269
3085
|
return page.timeline ? page.timeline.poster ?? page.timeline.duration : 0;
|
|
@@ -2378,7 +3194,7 @@ function filled(s, args) {
|
|
|
2378
3194
|
}) };
|
|
2379
3195
|
}
|
|
2380
3196
|
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 };
|
|
3197
|
+
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
3198
|
}
|
|
2383
3199
|
function hasInstances(page) {
|
|
2384
3200
|
return page.elements.some((e) => e.kind === "symbol");
|
|
@@ -2485,8 +3301,8 @@ function mix2(x) {
|
|
|
2485
3301
|
return z ^ z >> 31n;
|
|
2486
3302
|
}
|
|
2487
3303
|
function halves(id) {
|
|
2488
|
-
const
|
|
2489
|
-
return [BigInt(`0x${
|
|
3304
|
+
const hex2 = id.replace(/-/g, "").padStart(32, "0").slice(-32);
|
|
3305
|
+
return [BigInt(`0x${hex2.slice(0, 16)}`), BigInt(`0x${hex2.slice(16)}`)];
|
|
2490
3306
|
}
|
|
2491
3307
|
function derivedID(instance, inner) {
|
|
2492
3308
|
const [a, b] = halves(instance), [c, d] = halves(inner);
|
|
@@ -2507,7 +3323,7 @@ function clickTargets(page, file) {
|
|
|
2507
3323
|
let pageOf = null;
|
|
2508
3324
|
for (const e of page.elements) {
|
|
2509
3325
|
const c = e.click;
|
|
2510
|
-
const state = c && changesState(c) ? { ...c.set ? { set: c.set } : {}, ...c.toggle ? { toggle: c.toggle } : {} } : null;
|
|
3326
|
+
const state = c && changesState(c) ? { ...c.set ? { set: c.set } : {}, ...c.toggle ? { toggle: c.toggle } : {}, ...c.reset ? { reset: true } : {} } : null;
|
|
2511
3327
|
if (c?.scene) {
|
|
2512
3328
|
const i = sceneNamed(c.scene, markers);
|
|
2513
3329
|
if (i >= 0) {
|
|
@@ -2540,14 +3356,14 @@ function clickTargets(page, file) {
|
|
|
2540
3356
|
function hoveredElement(e) {
|
|
2541
3357
|
const h = e.hover;
|
|
2542
3358
|
if (!h) return e;
|
|
2543
|
-
const
|
|
2544
|
-
if (h.stroke)
|
|
2545
|
-
if (h.fill)
|
|
3359
|
+
const style2 = { ...e.style };
|
|
3360
|
+
if (h.stroke) style2.stroke = h.stroke;
|
|
3361
|
+
if (h.fill) style2.fill = h.fill;
|
|
2546
3362
|
if (h.opacity !== null && Number.isFinite(h.opacity)) {
|
|
2547
3363
|
const o = Math.min(Math.max(h.opacity, 0), 1);
|
|
2548
|
-
|
|
3364
|
+
style2.opacity = o >= 1 ? null : o;
|
|
2549
3365
|
}
|
|
2550
|
-
return { ...e, style };
|
|
3366
|
+
return { ...e, style: style2 };
|
|
2551
3367
|
}
|
|
2552
3368
|
function hovered(page, ids) {
|
|
2553
3369
|
return { ...page, elements: page.elements.map((e) => ids.has(e.id) ? hoveredElement(e) : e) };
|
|
@@ -2751,14 +3567,14 @@ function parseList(lines, start) {
|
|
|
2751
3567
|
}
|
|
2752
3568
|
return { block: { kind: "list", ordered, items }, next: i };
|
|
2753
3569
|
}
|
|
2754
|
-
function inline(src,
|
|
3570
|
+
function inline(src, style2 = {}) {
|
|
2755
3571
|
const out = [];
|
|
2756
3572
|
const push = (text, s) => {
|
|
2757
3573
|
if (text) out.push({ ...s, text });
|
|
2758
3574
|
};
|
|
2759
3575
|
let i = 0, buf = "";
|
|
2760
3576
|
const emit = () => {
|
|
2761
|
-
push(buf,
|
|
3577
|
+
push(buf, style2);
|
|
2762
3578
|
buf = "";
|
|
2763
3579
|
};
|
|
2764
3580
|
while (i < src.length) {
|
|
@@ -2786,43 +3602,43 @@ function inline(src, style = {}) {
|
|
|
2786
3602
|
emit();
|
|
2787
3603
|
let code = m[2].replace(/\n/g, " ");
|
|
2788
3604
|
if (/^ .* $/.test(code)) code = code.slice(1, -1);
|
|
2789
|
-
push(code, { ...
|
|
3605
|
+
push(code, { ...style2, mono: true });
|
|
2790
3606
|
i += m[0].length;
|
|
2791
3607
|
continue;
|
|
2792
3608
|
}
|
|
2793
3609
|
if (m = /^!\[([^\]]*)\]\([^)]*\)/.exec(rest)) {
|
|
2794
3610
|
emit();
|
|
2795
|
-
out.push(...inline(m[1],
|
|
3611
|
+
out.push(...inline(m[1], style2));
|
|
2796
3612
|
i += m[0].length;
|
|
2797
3613
|
continue;
|
|
2798
3614
|
}
|
|
2799
3615
|
if (m = /^\[([^\]]+)\]\([^)]*\)/.exec(rest)) {
|
|
2800
3616
|
emit();
|
|
2801
|
-
out.push(...inline(m[1], { ...
|
|
3617
|
+
out.push(...inline(m[1], { ...style2, link: true }));
|
|
2802
3618
|
i += m[0].length;
|
|
2803
3619
|
continue;
|
|
2804
3620
|
}
|
|
2805
3621
|
if (m = /^<((?:https?|ftp|mailto):[^>\s]+)>/.exec(rest)) {
|
|
2806
3622
|
emit();
|
|
2807
|
-
push(m[1], { ...
|
|
3623
|
+
push(m[1], { ...style2, link: true });
|
|
2808
3624
|
i += m[0].length;
|
|
2809
3625
|
continue;
|
|
2810
3626
|
}
|
|
2811
3627
|
if (m = /^~~(?=\S)([\s\S]*?\S)~~/.exec(rest)) {
|
|
2812
3628
|
emit();
|
|
2813
|
-
out.push(...inline(m[1], { ...
|
|
3629
|
+
out.push(...inline(m[1], { ...style2, strike: true }));
|
|
2814
3630
|
i += m[0].length;
|
|
2815
3631
|
continue;
|
|
2816
3632
|
}
|
|
2817
3633
|
if (m = /^(\*\*|__)(?=\S)([\s\S]*?\S)\1/.exec(rest)) {
|
|
2818
3634
|
emit();
|
|
2819
|
-
out.push(...inline(m[2], { ...
|
|
3635
|
+
out.push(...inline(m[2], { ...style2, bold: true }));
|
|
2820
3636
|
i += m[0].length;
|
|
2821
3637
|
continue;
|
|
2822
3638
|
}
|
|
2823
3639
|
if ((m = /^(\*|_)(?=\S)([\s\S]*?\S)\1(?!\1)/.exec(rest)) && (m[1] === "*" || !/\w/.test(src[i - 1] ?? ""))) {
|
|
2824
3640
|
emit();
|
|
2825
|
-
out.push(...inline(m[2], { ...
|
|
3641
|
+
out.push(...inline(m[2], { ...style2, italic: true }));
|
|
2826
3642
|
i += m[0].length;
|
|
2827
3643
|
continue;
|
|
2828
3644
|
}
|
|
@@ -3439,6 +4255,12 @@ var Playback = class {
|
|
|
3439
4255
|
__publicField(this, "sceneLoops");
|
|
3440
4256
|
/** The page's variables now: their starting values until clicks change them. */
|
|
3441
4257
|
__publicField(this, "state");
|
|
4258
|
+
/** When the variables last changed (performance.now() milliseconds): backlog piles up from then (ADR 0003). */
|
|
4259
|
+
__publicField(this, "stateSince", typeof performance === "undefined" ? 0 : performance.now());
|
|
4260
|
+
/** A still (the `at` attribute): backlog is drawn for the page time, not the wall clock. */
|
|
4261
|
+
__publicField(this, "still", false);
|
|
4262
|
+
/** What each queue held when the variables last changed, which it drains from (FrameMetrics.carry). */
|
|
4263
|
+
__publicField(this, "carried", {});
|
|
3442
4264
|
__publicField(this, "background");
|
|
3443
4265
|
__publicField(this, "ink");
|
|
3444
4266
|
/** What clicking each clickable element does, by element id. */
|
|
@@ -3510,6 +4332,10 @@ var Playback = class {
|
|
|
3510
4332
|
this.svg.style.maxWidth = `${Math.ceil(this.plan.size.width)}px`;
|
|
3511
4333
|
into.replaceChildren(this.svg);
|
|
3512
4334
|
}
|
|
4335
|
+
/** Seconds since the variables last changed, or null for a still. */
|
|
4336
|
+
sinceChange(now = typeof performance === "undefined" ? 0 : performance.now()) {
|
|
4337
|
+
return this.still || typeof performance === "undefined" ? null : (now - this.stateSince) / 1e3;
|
|
4338
|
+
}
|
|
3513
4339
|
get frameCount() {
|
|
3514
4340
|
return this.plan.times.length;
|
|
3515
4341
|
}
|
|
@@ -3550,7 +4376,7 @@ var Playback = class {
|
|
|
3550
4376
|
const times = /* @__PURE__ */ new Map();
|
|
3551
4377
|
for (const [id, start] of this.replayStarts) times.set(id, Math.min((now - start) / 1e3, this.replays.get(id) ?? 0));
|
|
3552
4378
|
const tc = clampTime(t);
|
|
3553
|
-
const pose = posed(this.page, tc, this.state);
|
|
4379
|
+
const pose = posed(this.page, tc, this.state, this.sinceChange(now), this.carried);
|
|
3554
4380
|
const frame2 = expandingSymbols(pose, tc, true, times);
|
|
3555
4381
|
const { parts } = svgParts(frame2, this.background, r);
|
|
3556
4382
|
const owned = (m, parts2) => {
|
|
@@ -3807,6 +4633,7 @@ var DottiePlayer = class extends ElementBase {
|
|
|
3807
4633
|
const file = this.file;
|
|
3808
4634
|
const page = file.pages[pageIndex(file, this.getAttribute("page"))];
|
|
3809
4635
|
const p = new Playback(page, this.stage, { clean: this.hasAttribute("clean"), file });
|
|
4636
|
+
p.still = this.hasAttribute("at");
|
|
3810
4637
|
this.playback = p;
|
|
3811
4638
|
this.style.setProperty("--dottie-page", color(p.background));
|
|
3812
4639
|
this.style.setProperty("--dottie-ink", color(p.ink));
|
|
@@ -3900,7 +4727,13 @@ var DottiePlayer = class extends ElementBase {
|
|
|
3900
4727
|
this.scheduleReplay();
|
|
3901
4728
|
}
|
|
3902
4729
|
if (!click) return;
|
|
3903
|
-
if (click.set || click.toggle
|
|
4730
|
+
if (click.set || click.toggle || click.reset) {
|
|
4731
|
+
this.setState(applyClick(
|
|
4732
|
+
{ scene: null, url: null, set: click.set ?? null, toggle: click.toggle ?? null, reset: click.reset ?? null },
|
|
4733
|
+
p.state,
|
|
4734
|
+
p.page.variables ?? {}
|
|
4735
|
+
));
|
|
4736
|
+
}
|
|
3904
4737
|
if ("state" in click) return;
|
|
3905
4738
|
if ("url" in click) {
|
|
3906
4739
|
if (/^(https?:|mailto:)/i.test(click.url)) window.open(click.url, "_blank", "noopener");
|
|
@@ -3926,7 +4759,10 @@ var DottiePlayer = class extends ElementBase {
|
|
|
3926
4759
|
if (!p) return;
|
|
3927
4760
|
const next = { ...p.state, ...values };
|
|
3928
4761
|
if (Object.keys(next).every((k) => next[k] === p.state[k])) return;
|
|
4762
|
+
const t = clampTime(p.last.t);
|
|
4763
|
+
p.carried = posedWithMetrics(p.page, t, p.state, p.sinceChange(), p.carried).metrics.carry;
|
|
3929
4764
|
p.state = next;
|
|
4765
|
+
p.stateSince = performance.now();
|
|
3930
4766
|
p.redraw();
|
|
3931
4767
|
this.dispatchEvent(new CustomEvent("state", { detail: { variables: { ...next } } }));
|
|
3932
4768
|
}
|
|
@@ -4124,7 +4960,7 @@ function render(source, options = {}) {
|
|
|
4124
4960
|
function isFile(v) {
|
|
4125
4961
|
return typeof v === "object" && v !== null && Array.isArray(v.pages) && Array.isArray(v.warnings);
|
|
4126
4962
|
}
|
|
4127
|
-
var VERSION = "0.
|
|
4963
|
+
var VERSION = "0.8.0";
|
|
4128
4964
|
|
|
4129
4965
|
// src/player-entry.ts
|
|
4130
4966
|
definePlayer();
|