@phin-tech/dottie 0.10.0 → 0.11.1
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 -1
- package/README.md +22 -2
- package/dist/compile.js +33 -13
- package/dist/dottie-compile.mjs +46 -15
- package/dist/dottie-logic.min.js +2171 -0
- package/dist/dottie-player.js +550 -148
- package/dist/dottie-player.min.js +58 -56
- package/dist/dottie-sim.min.js +5 -4
- package/dist/dottie.js +550 -148
- package/dist/types/clicks.d.ts +13 -5
- package/dist/types/compile.d.ts +8 -1
- package/dist/types/expression.d.ts +51 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/logic-entry.d.ts +1 -0
- package/dist/types/logic-loader.d.ts +9 -0
- package/dist/types/logic-polyfill.d.ts +2 -0
- package/dist/types/logic.d.ts +32 -0
- package/dist/types/measure.d.ts +4 -3
- package/dist/types/model.d.ts +10 -3
- package/dist/types/player.d.ts +18 -4
- package/dist/types/state.d.ts +14 -13
- package/package.json +3 -1
package/dist/dottie.js
CHANGED
|
@@ -1,84 +1,334 @@
|
|
|
1
|
-
/*! dottie.js 0.
|
|
1
|
+
/*! dottie.js 0.11.1 | 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);
|
|
5
5
|
|
|
6
|
-
// src/
|
|
6
|
+
// src/expression.ts
|
|
7
|
+
var METRIC_NAMES = ["throughput", "load", "delay", "backlog", "depth", "inFlight", "transit", "backup"];
|
|
8
|
+
var FUNCTIONS = { min: null, max: null, abs: 1, floor: 1, ceil: 1, round: 1, clamp: 3 };
|
|
7
9
|
var MAX_NAME = 64;
|
|
8
|
-
|
|
9
|
-
|
|
10
|
+
var truthy = (v) => typeof v === "boolean" ? v : v !== 0 && !Number.isNaN(v);
|
|
11
|
+
var num = (v) => typeof v === "boolean" ? v ? 1 : 0 : v;
|
|
12
|
+
var parsed = /* @__PURE__ */ new Map();
|
|
13
|
+
function parseExpr(text) {
|
|
14
|
+
const hit = parsed.get(text);
|
|
15
|
+
if (hit !== void 0) return hit;
|
|
16
|
+
const p = new Parser(text);
|
|
17
|
+
let e = p.or();
|
|
18
|
+
if (e && !p.atEnd()) e = null;
|
|
19
|
+
if (parsed.size > 4096) parsed.clear();
|
|
20
|
+
parsed.set(text, e);
|
|
21
|
+
return e;
|
|
10
22
|
}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
23
|
+
function evaluate(e, state, metric = null) {
|
|
24
|
+
switch (e.k) {
|
|
25
|
+
case "lit":
|
|
26
|
+
return e.v;
|
|
27
|
+
case "name":
|
|
28
|
+
return state[e.name] ?? false;
|
|
29
|
+
case "metric":
|
|
30
|
+
return metric?.(e.metric, e.target) ?? NaN;
|
|
31
|
+
case "not":
|
|
32
|
+
return !truthy(evaluate(e.e, state, metric));
|
|
33
|
+
case "neg":
|
|
34
|
+
return -num(evaluate(e.e, state, metric));
|
|
35
|
+
case "call": {
|
|
36
|
+
const xs = e.args.map((a) => num(evaluate(a, state, metric)));
|
|
37
|
+
const fold = (better) => {
|
|
38
|
+
if (xs.some(Number.isNaN)) return NaN;
|
|
39
|
+
let out = xs[0];
|
|
40
|
+
for (const x of xs.slice(1)) if (better(x, out)) out = x;
|
|
41
|
+
return out;
|
|
42
|
+
};
|
|
43
|
+
switch (e.fn) {
|
|
44
|
+
case "min":
|
|
45
|
+
return fold((a, b) => a < b);
|
|
46
|
+
case "max":
|
|
47
|
+
return fold((a, b) => a > b);
|
|
48
|
+
case "abs":
|
|
49
|
+
return Math.abs(xs[0]);
|
|
50
|
+
case "floor":
|
|
51
|
+
return Math.floor(xs[0]);
|
|
52
|
+
case "ceil":
|
|
53
|
+
return Math.ceil(xs[0]);
|
|
54
|
+
case "round":
|
|
55
|
+
return Math.floor(xs[0] + 0.5);
|
|
56
|
+
default:
|
|
57
|
+
if (xs.some(Number.isNaN)) return NaN;
|
|
58
|
+
return xs[0] < xs[1] ? xs[1] : xs[0] > xs[2] ? xs[2] : xs[0];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
case "bin": {
|
|
62
|
+
if (e.op === "&&") return truthy(evaluate(e.l, state, metric)) && truthy(evaluate(e.r, state, metric));
|
|
63
|
+
if (e.op === "||") return truthy(evaluate(e.l, state, metric)) || truthy(evaluate(e.r, state, metric));
|
|
64
|
+
const a = evaluate(e.l, state, metric), b = evaluate(e.r, state, metric);
|
|
65
|
+
const x = num(a), y = num(b);
|
|
66
|
+
switch (e.op) {
|
|
67
|
+
case "+":
|
|
68
|
+
return x + y;
|
|
69
|
+
case "-":
|
|
70
|
+
return x - y;
|
|
71
|
+
case "*":
|
|
72
|
+
return x * y;
|
|
73
|
+
case "/":
|
|
74
|
+
return x / y;
|
|
75
|
+
case "%":
|
|
76
|
+
return x % y;
|
|
77
|
+
}
|
|
78
|
+
if (Number.isNaN(x) || Number.isNaN(y)) return false;
|
|
79
|
+
switch (e.op) {
|
|
80
|
+
case "<":
|
|
81
|
+
return x < y;
|
|
82
|
+
case "<=":
|
|
83
|
+
return x <= y;
|
|
84
|
+
case ">":
|
|
85
|
+
return x > y;
|
|
86
|
+
case ">=":
|
|
87
|
+
return x >= y;
|
|
88
|
+
case "==":
|
|
89
|
+
return typeof a === "boolean" && typeof b === "boolean" ? a === b : x === y;
|
|
90
|
+
default:
|
|
91
|
+
return typeof a === "boolean" && typeof b === "boolean" ? a !== b : x !== y;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
48
94
|
}
|
|
49
95
|
}
|
|
50
|
-
function
|
|
51
|
-
|
|
96
|
+
function exprNames(e) {
|
|
97
|
+
const out = [];
|
|
98
|
+
const walk = (x) => {
|
|
99
|
+
switch (x.k) {
|
|
100
|
+
case "name":
|
|
101
|
+
if (!out.includes(x.name)) out.push(x.name);
|
|
102
|
+
break;
|
|
103
|
+
case "not":
|
|
104
|
+
case "neg":
|
|
105
|
+
walk(x.e);
|
|
106
|
+
break;
|
|
107
|
+
case "bin":
|
|
108
|
+
walk(x.l);
|
|
109
|
+
walk(x.r);
|
|
110
|
+
break;
|
|
111
|
+
case "call":
|
|
112
|
+
x.args.forEach(walk);
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
walk(e);
|
|
117
|
+
return out;
|
|
52
118
|
}
|
|
53
|
-
function
|
|
119
|
+
function exprMetrics(e) {
|
|
54
120
|
const out = [];
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
121
|
+
const walk = (x) => {
|
|
122
|
+
switch (x.k) {
|
|
123
|
+
case "metric":
|
|
124
|
+
out.push({ metric: x.metric, target: x.target });
|
|
125
|
+
break;
|
|
126
|
+
case "not":
|
|
127
|
+
case "neg":
|
|
128
|
+
walk(x.e);
|
|
129
|
+
break;
|
|
130
|
+
case "bin":
|
|
131
|
+
walk(x.l);
|
|
132
|
+
walk(x.r);
|
|
133
|
+
break;
|
|
134
|
+
case "call":
|
|
135
|
+
x.args.forEach(walk);
|
|
136
|
+
break;
|
|
62
137
|
}
|
|
63
|
-
}
|
|
138
|
+
};
|
|
139
|
+
walk(e);
|
|
64
140
|
return out;
|
|
65
141
|
}
|
|
142
|
+
var isDigit = (c) => c !== void 0 && c >= "0" && c <= "9";
|
|
143
|
+
var isLetter = (c) => c !== void 0 && (c === "_" || c >= "a" && c <= "z" || c >= "A" && c <= "Z");
|
|
144
|
+
var Parser = class {
|
|
145
|
+
constructor(text) {
|
|
146
|
+
__publicField(this, "s");
|
|
147
|
+
__publicField(this, "i", 0);
|
|
148
|
+
this.s = [...text];
|
|
149
|
+
}
|
|
150
|
+
atEnd() {
|
|
151
|
+
this.skip();
|
|
152
|
+
return this.i === this.s.length;
|
|
153
|
+
}
|
|
154
|
+
skip() {
|
|
155
|
+
while (this.i < this.s.length && " \n\r".includes(this.s[this.i])) this.i++;
|
|
156
|
+
}
|
|
157
|
+
eat(token) {
|
|
158
|
+
this.skip();
|
|
159
|
+
const t = [...token];
|
|
160
|
+
if (this.i + t.length > this.s.length) return false;
|
|
161
|
+
for (let k = 0; k < t.length; k++) if (this.s[this.i + k] !== t[k]) return false;
|
|
162
|
+
if (t.length === 1 && this.s[this.i + 1] === "=" && "<>!=".includes(t[0])) return false;
|
|
163
|
+
this.i += t.length;
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
or() {
|
|
167
|
+
let l = this.and();
|
|
168
|
+
if (!l) return null;
|
|
169
|
+
while (this.eat("||")) {
|
|
170
|
+
const r = this.and();
|
|
171
|
+
if (!r) return null;
|
|
172
|
+
l = { k: "bin", op: "||", l, r };
|
|
173
|
+
}
|
|
174
|
+
return l;
|
|
175
|
+
}
|
|
176
|
+
and() {
|
|
177
|
+
let l = this.compare();
|
|
178
|
+
if (!l) return null;
|
|
179
|
+
while (this.eat("&&")) {
|
|
180
|
+
const r = this.compare();
|
|
181
|
+
if (!r) return null;
|
|
182
|
+
l = { k: "bin", op: "&&", l, r };
|
|
183
|
+
}
|
|
184
|
+
return l;
|
|
185
|
+
}
|
|
186
|
+
compare() {
|
|
187
|
+
const l = this.sum();
|
|
188
|
+
if (!l) return null;
|
|
189
|
+
for (const op of ["<=", ">=", "==", "!=", "<", ">"]) {
|
|
190
|
+
if (this.eat(op)) {
|
|
191
|
+
const r = this.sum();
|
|
192
|
+
return r ? { k: "bin", op, l, r } : null;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return l;
|
|
196
|
+
}
|
|
197
|
+
sum() {
|
|
198
|
+
let l = this.product();
|
|
199
|
+
if (!l) return null;
|
|
200
|
+
for (; ; ) {
|
|
201
|
+
const op = this.eat("+") ? "+" : this.eat("-") ? "-" : null;
|
|
202
|
+
if (!op) return l;
|
|
203
|
+
const r = this.product();
|
|
204
|
+
if (!r) return null;
|
|
205
|
+
l = { k: "bin", op, l, r };
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
product() {
|
|
209
|
+
let l = this.unary();
|
|
210
|
+
if (!l) return null;
|
|
211
|
+
for (; ; ) {
|
|
212
|
+
const op = ["*", "/", "%"].find((o) => this.eat(o));
|
|
213
|
+
if (!op) return l;
|
|
214
|
+
const r = this.unary();
|
|
215
|
+
if (!r) return null;
|
|
216
|
+
l = { k: "bin", op, l, r };
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
unary() {
|
|
220
|
+
if (this.eat("!")) {
|
|
221
|
+
const e = this.unary();
|
|
222
|
+
return e ? { k: "not", e } : null;
|
|
223
|
+
}
|
|
224
|
+
if (this.eat("-")) {
|
|
225
|
+
const e = this.unary();
|
|
226
|
+
return e ? { k: "neg", e } : null;
|
|
227
|
+
}
|
|
228
|
+
return this.primary();
|
|
229
|
+
}
|
|
230
|
+
primary() {
|
|
231
|
+
this.skip();
|
|
232
|
+
if (this.i >= this.s.length) return null;
|
|
233
|
+
if (this.eat("(")) {
|
|
234
|
+
const e = this.or();
|
|
235
|
+
return e && this.eat(")") ? e : null;
|
|
236
|
+
}
|
|
237
|
+
const s = this.s;
|
|
238
|
+
const c = s[this.i];
|
|
239
|
+
if (isDigit(c)) {
|
|
240
|
+
const start3 = this.i;
|
|
241
|
+
while (isDigit(s[this.i])) this.i++;
|
|
242
|
+
if (s[this.i] === "." && isDigit(s[this.i + 1])) {
|
|
243
|
+
this.i++;
|
|
244
|
+
while (isDigit(s[this.i])) this.i++;
|
|
245
|
+
}
|
|
246
|
+
return { k: "lit", v: Number(s.slice(start3, this.i).join("")) };
|
|
247
|
+
}
|
|
248
|
+
if (!isLetter(c)) return null;
|
|
249
|
+
const start2 = this.i;
|
|
250
|
+
while (isLetter(s[this.i]) || isDigit(s[this.i])) this.i++;
|
|
251
|
+
const name = s.slice(start2, this.i).join("");
|
|
252
|
+
if (name.length > MAX_NAME) return null;
|
|
253
|
+
let j = this.i;
|
|
254
|
+
while (s[j] === " " || s[j] === " ") j++;
|
|
255
|
+
const called = s[j] === "(";
|
|
256
|
+
if (METRIC_NAMES.includes(name)) {
|
|
257
|
+
if (!called) return { k: "metric", metric: name, target: "" };
|
|
258
|
+
this.eat("(");
|
|
259
|
+
const from = this.i;
|
|
260
|
+
while (this.i < s.length && s[this.i] !== ")" && s[this.i] !== "(") this.i++;
|
|
261
|
+
if (s[this.i] !== ")") return null;
|
|
262
|
+
const target = s.slice(from, this.i).join("").replace(/^[ \t]+|[ \t]+$/g, "");
|
|
263
|
+
this.i++;
|
|
264
|
+
return { k: "metric", metric: name, target };
|
|
265
|
+
}
|
|
266
|
+
if (name in FUNCTIONS) {
|
|
267
|
+
if (!called || !this.eat("(")) return null;
|
|
268
|
+
const args = [];
|
|
269
|
+
if (!this.eat(")")) {
|
|
270
|
+
do {
|
|
271
|
+
const a = this.or();
|
|
272
|
+
if (!a) return null;
|
|
273
|
+
args.push(a);
|
|
274
|
+
} while (this.eat(","));
|
|
275
|
+
if (!this.eat(")")) return null;
|
|
276
|
+
}
|
|
277
|
+
const arity = FUNCTIONS[name];
|
|
278
|
+
if (arity !== null ? args.length !== arity : args.length === 0) return null;
|
|
279
|
+
return { k: "call", fn: name, args };
|
|
280
|
+
}
|
|
281
|
+
if (called) return null;
|
|
282
|
+
if (name === "true") return { k: "lit", v: true };
|
|
283
|
+
if (name === "false") return { k: "lit", v: false };
|
|
284
|
+
return { k: "name", name };
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
function describeVar(v) {
|
|
288
|
+
return typeof v === "boolean" ? v ? "true" : "false" : formatTwo(v);
|
|
289
|
+
}
|
|
290
|
+
function formatTwo(v) {
|
|
291
|
+
const n = Math.floor(Math.abs(v) * 100 + 0.5);
|
|
292
|
+
if (!(n < 1e15)) return v < 0 ? "-\u221E" : "\u221E";
|
|
293
|
+
const whole = Math.floor(n / 100), part = n - whole * 100;
|
|
294
|
+
let s = String(whole);
|
|
295
|
+
if (part > 0) s += "." + String(part).padStart(2, "0").replace(/0+$/, "");
|
|
296
|
+
return (v < 0 && n > 0 ? "-" : "") + s;
|
|
297
|
+
}
|
|
298
|
+
function filling(text, values) {
|
|
299
|
+
if (!text.includes("{")) return text;
|
|
300
|
+
let out = "", rest = text;
|
|
301
|
+
for (let open = rest.indexOf("{"); open >= 0; open = rest.indexOf("{")) {
|
|
302
|
+
out += rest.slice(0, open);
|
|
303
|
+
const close = rest.indexOf("}", open);
|
|
304
|
+
if (close < 0) {
|
|
305
|
+
out += rest.slice(open);
|
|
306
|
+
rest = "";
|
|
307
|
+
break;
|
|
308
|
+
}
|
|
309
|
+
const inner = rest.slice(open + 1, close);
|
|
310
|
+
const e = inner.includes("{") ? null : parseExpr(inner);
|
|
311
|
+
const names = e ? exprNames(e) : [];
|
|
312
|
+
if (e && exprMetrics(e).length === 0 && names.length > 0 && names.every((n) => values[n] !== void 0)) out += describeVar(evaluate(e, values));
|
|
313
|
+
else out += rest.slice(open, close + 1);
|
|
314
|
+
rest = rest.slice(close + 1);
|
|
315
|
+
}
|
|
316
|
+
return out + rest;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// src/state.ts
|
|
320
|
+
var MAX_NAME2 = 64;
|
|
321
|
+
function isName(name) {
|
|
322
|
+
return name.length <= MAX_NAME2 && /^[A-Za-z_][A-Za-z0-9_]*$/.test(name);
|
|
323
|
+
}
|
|
324
|
+
function mentionsMetric(condition) {
|
|
325
|
+
const e = parseExpr(condition);
|
|
326
|
+
return e !== null && exprMetrics(e).length > 0;
|
|
327
|
+
}
|
|
66
328
|
function holds(condition, state, metric = null) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
if (m !== null) {
|
|
71
|
-
const v = metric ? metric(m) : null;
|
|
72
|
-
return v !== null && passes(m, v);
|
|
73
|
-
}
|
|
74
|
-
let term = trim(all);
|
|
75
|
-
let negated = false;
|
|
76
|
-
if (term.startsWith("!")) {
|
|
77
|
-
negated = true;
|
|
78
|
-
term = trim(term.slice(1));
|
|
79
|
-
}
|
|
80
|
-
return (state[term] ?? false) !== negated;
|
|
81
|
-
}));
|
|
329
|
+
const e = parseExpr(condition);
|
|
330
|
+
if (!e) return false;
|
|
331
|
+
return truthy(evaluate(e, state, metric ? (m, target) => metric({ metric: m, target, op: "", value: 0 }) : null));
|
|
82
332
|
}
|
|
83
333
|
function stateValues(page, choice) {
|
|
84
334
|
if (choice === "all") return null;
|
|
@@ -117,7 +367,7 @@ function applyOverride(o, e) {
|
|
|
117
367
|
return out;
|
|
118
368
|
}
|
|
119
369
|
function changesState(c) {
|
|
120
|
-
return !!c && (c.reset === true || Object.keys(c.set ?? {}).length > 0 || (c.toggle ?? []).length > 0);
|
|
370
|
+
return !!c && (c.reset === true || Object.keys(c.set ?? {}).length > 0 || (c.toggle ?? []).length > 0 || !!c.run);
|
|
121
371
|
}
|
|
122
372
|
function applyClick(c, state, initial = {}) {
|
|
123
373
|
let out = { ...state };
|
|
@@ -126,12 +376,15 @@ function applyClick(c, state, initial = {}) {
|
|
|
126
376
|
for (const k of Object.keys(state)) out[k] = false;
|
|
127
377
|
Object.assign(out, initial);
|
|
128
378
|
}
|
|
129
|
-
|
|
130
|
-
for (const k of c.
|
|
379
|
+
const before = { ...out };
|
|
380
|
+
for (const [k, v] of Object.entries(c.set ?? {})) out[k] = clickValue(v, before, before[k]);
|
|
381
|
+
for (const k of c.toggle ?? []) out[k] = !truthy(out[k] ?? false);
|
|
131
382
|
return out;
|
|
132
383
|
}
|
|
133
|
-
function
|
|
134
|
-
|
|
384
|
+
function clickValue(v, state, current) {
|
|
385
|
+
if (typeof v !== "string") return v;
|
|
386
|
+
const e = parseExpr(v);
|
|
387
|
+
return e ? evaluate(e, state) : current ?? false;
|
|
135
388
|
}
|
|
136
389
|
|
|
137
390
|
// src/theme.ts
|
|
@@ -193,12 +446,12 @@ function resolvingPalette(page) {
|
|
|
193
446
|
}
|
|
194
447
|
|
|
195
448
|
// src/model.ts
|
|
196
|
-
var SUPPORTED_VERSION =
|
|
449
|
+
var SUPPORTED_VERSION = 3;
|
|
197
450
|
var KINDS = ["rectangle", "ellipse", "diamond", "text", "connector", "freehand", "path", "note", "image", "symbol"];
|
|
198
451
|
var EASES = ["linear", "easeIn", "easeOut", "easeInOut", "hold", "spring", "bounce", "elastic"];
|
|
199
452
|
var KNOWN_FIELDS = {
|
|
200
453
|
file: ["version", "pages", "images"],
|
|
201
|
-
page: ["id", "name", "layers", "elements", "theme", "customTheme", "guidesX", "guidesY", "guidesLocked", "timeline", "symbols", "variables", "flows"],
|
|
454
|
+
page: ["id", "name", "layers", "elements", "theme", "customTheme", "logic", "guidesX", "guidesY", "guidesLocked", "timeline", "symbols", "variables", "flows"],
|
|
202
455
|
flow: ["id", "from", "to", "every", "speed", "color", "size", "when", "spread", "volume", "overrides"],
|
|
203
456
|
flowOverride: ["when", "volume"],
|
|
204
457
|
symbol: ["id", "name", "width", "height", "layers", "elements", "timeline", "button", "params"],
|
|
@@ -267,7 +520,7 @@ var KNOWN_FIELDS = {
|
|
|
267
520
|
repeat: ["start", "end", "count"],
|
|
268
521
|
cameraKey: ["t", "x", "y", "width", "height", "ease"],
|
|
269
522
|
marker: ["t", "name", "loop"],
|
|
270
|
-
click: ["scene", "url", "set", "toggle", "reset"],
|
|
523
|
+
click: ["scene", "url", "set", "toggle", "reset", "run"],
|
|
271
524
|
hover: ["stroke", "fill", "opacity"],
|
|
272
525
|
override: ["when", "stroke", "fill", "textColor", "opacity", "strokeWidth", "dash", "text", "reroute", "offline", "cost", "capacity", "delay"]
|
|
273
526
|
};
|
|
@@ -281,7 +534,7 @@ var inkOnDark = { r: 0.93, g: 0.93, b: 0.94, a: 1 };
|
|
|
281
534
|
var DottieFormatError = class extends Error {
|
|
282
535
|
};
|
|
283
536
|
var isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
284
|
-
var
|
|
537
|
+
var num2 = (v) => typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
285
538
|
var str = (v) => typeof v === "string" ? v : void 0;
|
|
286
539
|
var strings = (v) => isObj(v) && Object.values(v).every((x) => typeof x === "string") ? { ...v } : null;
|
|
287
540
|
var bool = (v) => typeof v === "boolean" ? v : void 0;
|
|
@@ -305,9 +558,9 @@ function webLink(url) {
|
|
|
305
558
|
}
|
|
306
559
|
function rgba(v) {
|
|
307
560
|
if (!isObj(v)) return void 0;
|
|
308
|
-
const r =
|
|
561
|
+
const r = num2(v.r), g = num2(v.g), b = num2(v.b);
|
|
309
562
|
if (r === void 0 || g === void 0 || b === void 0) return void 0;
|
|
310
|
-
return { r, g, b, a:
|
|
563
|
+
return { r, g, b, a: num2(v.a) ?? 1 };
|
|
311
564
|
}
|
|
312
565
|
var Reader = class {
|
|
313
566
|
constructor() {
|
|
@@ -336,15 +589,15 @@ var Reader = class {
|
|
|
336
589
|
stroke: rgba(s.stroke) ?? { ...black },
|
|
337
590
|
fill: rgba(s.fill) ?? { ...clear },
|
|
338
591
|
fillStyle: ["solid", "hachure", "crossHatch", "grid"].includes(fillStyle) ? fillStyle : "solid",
|
|
339
|
-
strokeWidth:
|
|
592
|
+
strokeWidth: num2(s.strokeWidth) ?? 2,
|
|
340
593
|
dash: ["solid", "dashed", "dotted"].includes(dash) ? dash : "solid",
|
|
341
|
-
roughness:
|
|
342
|
-
bowing:
|
|
343
|
-
fontSize:
|
|
594
|
+
roughness: num2(s.roughness) ?? 1,
|
|
595
|
+
bowing: num2(s.bowing) ?? 1,
|
|
596
|
+
fontSize: num2(s.fontSize) ?? 18,
|
|
344
597
|
font: str(s.font) || null,
|
|
345
|
-
gridSize:
|
|
346
|
-
opacity:
|
|
347
|
-
dashOffset:
|
|
598
|
+
gridSize: num2(s.gridSize) ?? null,
|
|
599
|
+
opacity: num2(s.opacity) ?? null,
|
|
600
|
+
dashOffset: num2(s.dashOffset) ?? null,
|
|
348
601
|
textColor: rgba(s.textColor) ?? null,
|
|
349
602
|
named: this.colorNames(s.named)
|
|
350
603
|
};
|
|
@@ -378,13 +631,13 @@ var Reader = class {
|
|
|
378
631
|
endpoint(v) {
|
|
379
632
|
const e = isObj(v) ? v : {};
|
|
380
633
|
const port = str(e.port);
|
|
381
|
-
const portIndex =
|
|
634
|
+
const portIndex = num2(e.portIndex);
|
|
382
635
|
return {
|
|
383
636
|
elementID: uuid(e.elementID) ?? null,
|
|
384
637
|
port: port && ["top", "right", "bottom", "left"].includes(port) ? port : null,
|
|
385
638
|
portIndex: portIndex !== void 0 && Number.isInteger(portIndex) && portIndex >= 0 ? portIndex : null,
|
|
386
|
-
x:
|
|
387
|
-
y:
|
|
639
|
+
x: num2(e.x) ?? 0,
|
|
640
|
+
y: num2(e.y) ?? 0
|
|
388
641
|
};
|
|
389
642
|
}
|
|
390
643
|
connector(v) {
|
|
@@ -403,7 +656,7 @@ var Reader = class {
|
|
|
403
656
|
endHead: head(v.endHead, "arrow"),
|
|
404
657
|
waypoints: Array.isArray(v.waypoints) ? v.waypoints.filter(isObj).map((w) => {
|
|
405
658
|
this.fields(w, "waypoint");
|
|
406
|
-
return { x:
|
|
659
|
+
return { x: num2(w.x) ?? 0, y: num2(w.y) ?? 0 };
|
|
407
660
|
}) : null,
|
|
408
661
|
hops: typeof v.hops === "boolean" ? v.hops : null,
|
|
409
662
|
avoid: typeof v.avoid === "boolean" ? v.avoid : null,
|
|
@@ -439,20 +692,20 @@ var Reader = class {
|
|
|
439
692
|
id,
|
|
440
693
|
kind,
|
|
441
694
|
layerID,
|
|
442
|
-
x:
|
|
443
|
-
y:
|
|
444
|
-
width:
|
|
445
|
-
height:
|
|
695
|
+
x: num2(v.x) ?? 0,
|
|
696
|
+
y: num2(v.y) ?? 0,
|
|
697
|
+
width: num2(v.width) ?? 0,
|
|
698
|
+
height: num2(v.height) ?? 0,
|
|
446
699
|
style: this.style(v.style),
|
|
447
|
-
seed: (
|
|
700
|
+
seed: (num2(v.seed) ?? 1) >>> 0,
|
|
448
701
|
text: str(v.text) ?? null,
|
|
449
702
|
connector: this.connector(v.connector),
|
|
450
703
|
groupID: uuid(v.groupID) ?? null,
|
|
451
|
-
points: Array.isArray(v.points) ? v.points.filter(isObj).map((p) => ({ x:
|
|
704
|
+
points: Array.isArray(v.points) ? v.points.filter(isObj).map((p) => ({ x: num2(p.x) ?? 0, y: num2(p.y) ?? 0 })) : null,
|
|
452
705
|
path: Array.isArray(v.path) ? v.path.filter((c) => typeof c === "number") : null,
|
|
453
706
|
subpaths,
|
|
454
707
|
evenOdd: bool(v.evenOdd) ?? null,
|
|
455
|
-
rotation:
|
|
708
|
+
rotation: num2(v.rotation) ?? null,
|
|
456
709
|
link: uuid(v.link) ?? null,
|
|
457
710
|
url: str(v.url) ?? null,
|
|
458
711
|
anchor: str(v.anchor) ?? null,
|
|
@@ -462,18 +715,18 @@ var Reader = class {
|
|
|
462
715
|
overrides: this.overrides(v.overrides),
|
|
463
716
|
symbol: uuid(v.symbol) ?? null,
|
|
464
717
|
playback: this.playback(v.playback),
|
|
465
|
-
offset:
|
|
718
|
+
offset: num2(v.offset) ?? null,
|
|
466
719
|
args: strings(v.args),
|
|
467
720
|
image: this.image(v.image, images),
|
|
468
|
-
reveal:
|
|
721
|
+
reveal: num2(v.reveal) ?? null,
|
|
469
722
|
ports: Array.isArray(v.ports) ? v.ports.map((p) => {
|
|
470
723
|
const o = isObj(p) ? p : {};
|
|
471
|
-
return { x:
|
|
724
|
+
return { x: num2(o.x) ?? 0, y: num2(o.y) ?? 0 };
|
|
472
725
|
}) : null,
|
|
473
726
|
labelRect: this.labelRect(v.labelRect),
|
|
474
727
|
extension: this.extension(v.extension),
|
|
475
|
-
capacity:
|
|
476
|
-
delay:
|
|
728
|
+
capacity: num2(v.capacity) ?? null,
|
|
729
|
+
delay: num2(v.delay) ?? null,
|
|
477
730
|
queue: v.queue === true ? true : null,
|
|
478
731
|
gauge: this.gauge(v.gauge),
|
|
479
732
|
containerID: uuid(v.containerID) ?? null,
|
|
@@ -498,7 +751,7 @@ var Reader = class {
|
|
|
498
751
|
labelRect(v) {
|
|
499
752
|
if (!isObj(v)) return null;
|
|
500
753
|
this.fields(v, "normalizedRect");
|
|
501
|
-
return { x:
|
|
754
|
+
return { x: num2(v.x) ?? 0, y: num2(v.y) ?? 0, width: num2(v.width) ?? 0, height: num2(v.height) ?? 0 };
|
|
502
755
|
}
|
|
503
756
|
extension(v) {
|
|
504
757
|
if (!isObj(v)) return null;
|
|
@@ -519,7 +772,7 @@ var Reader = class {
|
|
|
519
772
|
variables(v) {
|
|
520
773
|
if (!isObj(v)) return null;
|
|
521
774
|
const out = {};
|
|
522
|
-
for (const [k, b] of Object.entries(v)) if (typeof b === "boolean") out[k] = b;
|
|
775
|
+
for (const [k, b] of Object.entries(v)) if (typeof b === "boolean" || typeof b === "number" && Number.isFinite(b)) out[k] = b;
|
|
523
776
|
return Object.keys(out).length > 0 ? out : null;
|
|
524
777
|
}
|
|
525
778
|
click(v) {
|
|
@@ -531,12 +784,22 @@ var Reader = class {
|
|
|
531
784
|
let set = null;
|
|
532
785
|
if (isObj(v.set)) {
|
|
533
786
|
set = {};
|
|
534
|
-
for (const [k, b] of Object.entries(v.set))
|
|
787
|
+
for (const [k, b] of Object.entries(v.set)) {
|
|
788
|
+
if (isName(k) && (typeof b === "boolean" || typeof b === "string" || typeof b === "number" && Number.isFinite(b))) set[k] = b;
|
|
789
|
+
}
|
|
535
790
|
if (Object.keys(set).length === 0) set = null;
|
|
536
791
|
}
|
|
537
792
|
const raw = typeof v.toggle === "string" ? [v.toggle] : Array.isArray(v.toggle) && v.toggle.every((x) => typeof x === "string") ? v.toggle : null;
|
|
538
793
|
const toggle = raw ? raw.filter(isName) : null;
|
|
539
|
-
|
|
794
|
+
const run = str(v.run);
|
|
795
|
+
return {
|
|
796
|
+
scene: str(v.scene) ?? null,
|
|
797
|
+
url: safe,
|
|
798
|
+
set,
|
|
799
|
+
toggle: toggle && toggle.length > 0 ? toggle : null,
|
|
800
|
+
reset: v.reset === true ? true : null,
|
|
801
|
+
run: run !== void 0 && isName(run) ? run : null
|
|
802
|
+
};
|
|
540
803
|
}
|
|
541
804
|
playback(v) {
|
|
542
805
|
const s = str(v);
|
|
@@ -552,7 +815,7 @@ var Reader = class {
|
|
|
552
815
|
return null;
|
|
553
816
|
}
|
|
554
817
|
const size = (x) => {
|
|
555
|
-
const n =
|
|
818
|
+
const n = num2(x);
|
|
556
819
|
return Math.max(1, n ?? 100);
|
|
557
820
|
};
|
|
558
821
|
const layers = (Array.isArray(v.layers) ? v.layers : []).filter(isObj).flatMap((l) => {
|
|
@@ -589,8 +852,8 @@ var Reader = class {
|
|
|
589
852
|
stroke: rgba(o.stroke) ?? null,
|
|
590
853
|
fill: rgba(o.fill) ?? null,
|
|
591
854
|
textColor: rgba(o.textColor) ?? null,
|
|
592
|
-
opacity:
|
|
593
|
-
strokeWidth:
|
|
855
|
+
opacity: num2(o.opacity) ?? null,
|
|
856
|
+
strokeWidth: num2(o.strokeWidth) ?? null,
|
|
594
857
|
dash: dash !== void 0 && ["solid", "dashed", "dotted"].includes(dash) ? dash : null,
|
|
595
858
|
text: str(o.text) ?? null,
|
|
596
859
|
reroute,
|
|
@@ -604,7 +867,7 @@ var Reader = class {
|
|
|
604
867
|
hover(v) {
|
|
605
868
|
if (!isObj(v)) return null;
|
|
606
869
|
this.fields(v, "hover");
|
|
607
|
-
return { stroke: rgba(v.stroke) ?? null, fill: rgba(v.fill) ?? null, opacity:
|
|
870
|
+
return { stroke: rgba(v.stroke) ?? null, fill: rgba(v.fill) ?? null, opacity: num2(v.opacity) ?? null };
|
|
608
871
|
}
|
|
609
872
|
ease(v) {
|
|
610
873
|
const s = str(v);
|
|
@@ -618,11 +881,11 @@ var Reader = class {
|
|
|
618
881
|
keyframe(v) {
|
|
619
882
|
if (!isObj(v)) return null;
|
|
620
883
|
this.fields(v, "keyframe");
|
|
621
|
-
const t =
|
|
884
|
+
const t = num2(v.t);
|
|
622
885
|
if (t === void 0) return null;
|
|
623
886
|
const k = { t };
|
|
624
887
|
for (const p of ["x", "y", "width", "height", "rotation", "strokeWidth", "reveal", "opacity"]) {
|
|
625
|
-
const n =
|
|
888
|
+
const n = num2(v[p]);
|
|
626
889
|
if (n !== void 0) k[p] = n;
|
|
627
890
|
}
|
|
628
891
|
const stroke = rgba(v.stroke), fill = rgba(v.fill), visible = bool(v.visible), ease = this.ease(v.ease);
|
|
@@ -640,39 +903,39 @@ var Reader = class {
|
|
|
640
903
|
const keys = (Array.isArray(v.keys) ? v.keys : []).map((k) => this.keyframe(k)).filter((k) => k !== null && k.t >= 0 && k.t <= MAX_DURATION);
|
|
641
904
|
stableSortBy(keys, (k) => k.t);
|
|
642
905
|
const follow = Array.isArray(v.follow) ? v.follow.filter(isObj).flatMap((f) => {
|
|
643
|
-
const path = uuid(f.path), start2 =
|
|
906
|
+
const path = uuid(f.path), start2 = num2(f.start), end = num2(f.end);
|
|
644
907
|
if (!path || start2 === void 0 || end === void 0 || start2 < 0 || end > MAX_DURATION) return [];
|
|
645
|
-
return [{ path, start: start2, end, ease: this.ease(f.ease), reverse: bool(f.reverse) ?? null, orient: bool(f.orient) ?? null, laps:
|
|
908
|
+
return [{ path, start: start2, end, ease: this.ease(f.ease), reverse: bool(f.reverse) ?? null, orient: bool(f.orient) ?? null, laps: num2(f.laps) ?? null }];
|
|
646
909
|
}) : null;
|
|
647
910
|
const repeats = Array.isArray(v.repeats) ? v.repeats.filter(isObj).flatMap((r) => {
|
|
648
|
-
const start2 =
|
|
911
|
+
const start2 = num2(r.start), end = num2(r.end);
|
|
649
912
|
if (start2 === void 0 || end === void 0) return [];
|
|
650
|
-
const count =
|
|
913
|
+
const count = num2(r.count);
|
|
651
914
|
const rep = { start: start2, end, count: count === void 0 ? null : Math.trunc(count) };
|
|
652
915
|
return repeatIsValid(rep) ? [rep] : [];
|
|
653
916
|
}) : null;
|
|
654
|
-
return { element, keys, boil: bool(v.boil) ?? null, follow, repeats, march: clampMarch(
|
|
917
|
+
return { element, keys, boil: bool(v.boil) ?? null, follow, repeats, march: clampMarch(num2(v.march)) };
|
|
655
918
|
}
|
|
656
919
|
timeline(v) {
|
|
657
920
|
if (!isObj(v)) return null;
|
|
658
921
|
this.fields(v, "timeline");
|
|
659
|
-
const boil =
|
|
922
|
+
const boil = num2(v.boil);
|
|
660
923
|
const camera = Array.isArray(v.camera) ? v.camera.filter(isObj).flatMap((c) => {
|
|
661
|
-
const t =
|
|
924
|
+
const t = num2(c.t), x = num2(c.x), y = num2(c.y), width = num2(c.width), height = num2(c.height);
|
|
662
925
|
if (t === void 0 || x === void 0 || y === void 0 || width === void 0 || height === void 0) return [];
|
|
663
926
|
if (!(width > 1 && height > 1 && t >= 0)) return [];
|
|
664
927
|
return [{ t, x, y, width, height, ease: this.ease(c.ease) }];
|
|
665
928
|
}) : null;
|
|
666
929
|
if (camera) stableSortBy(camera, (c) => c.t);
|
|
667
930
|
const markers = Array.isArray(v.markers) ? v.markers.filter(isObj).flatMap((m) => {
|
|
668
|
-
const t =
|
|
931
|
+
const t = num2(m.t);
|
|
669
932
|
return t !== void 0 && t >= 0 ? [{ t, name: str(m.name) ?? "", loop: bool(m.loop) ?? null }] : [];
|
|
670
933
|
}) : null;
|
|
671
934
|
if (markers) stableSortBy(markers, (m) => m.t);
|
|
672
|
-
const poster =
|
|
935
|
+
const poster = num2(v.poster);
|
|
673
936
|
return {
|
|
674
|
-
fps: clampFPS(
|
|
675
|
-
duration: clampTime(
|
|
937
|
+
fps: clampFPS(num2(v.fps) ?? 12),
|
|
938
|
+
duration: clampTime(num2(v.duration) ?? 0),
|
|
676
939
|
boil: boil !== void 0 && Number.isInteger(boil) && boil > 0 ? Math.min(boil, 1e4) : null,
|
|
677
940
|
settle: bool(v.settle) ?? null,
|
|
678
941
|
poster: poster === void 0 ? null : clampTime(poster),
|
|
@@ -699,6 +962,7 @@ var Reader = class {
|
|
|
699
962
|
elements: (Array.isArray(v.elements) ? v.elements : []).map((e) => this.element(e, images)).filter((e) => e !== null),
|
|
700
963
|
theme: str(v.theme) ?? null,
|
|
701
964
|
customTheme: this.customTheme(v.customTheme),
|
|
965
|
+
logic: typeof v.logic === "string" && v.logic.length > 0 ? v.logic : null,
|
|
702
966
|
timeline: this.timeline(v.timeline),
|
|
703
967
|
symbols: Array.isArray(v.symbols) ? v.symbols.map((x) => this.symbol(x, images)).filter((x) => x !== null) : null,
|
|
704
968
|
variables: this.variables(v.variables),
|
|
@@ -710,7 +974,7 @@ var Reader = class {
|
|
|
710
974
|
this.fields(f, "flow");
|
|
711
975
|
const id = (x) => typeof x === "string" && UUID.test(x) ? x.toUpperCase() : NO_ID;
|
|
712
976
|
const finite = (x) => {
|
|
713
|
-
const n =
|
|
977
|
+
const n = num2(x);
|
|
714
978
|
return n !== void 0 && Number.isFinite(n) ? n : null;
|
|
715
979
|
};
|
|
716
980
|
return {
|
|
@@ -750,7 +1014,7 @@ function parseDottie(source) {
|
|
|
750
1014
|
}
|
|
751
1015
|
}
|
|
752
1016
|
if (!isObj(json)) throw new DottieFormatError("Not a Dottie drawing");
|
|
753
|
-
const version =
|
|
1017
|
+
const version = num2(json.version);
|
|
754
1018
|
if (version === void 0) throw new DottieFormatError("Not a Dottie drawing (no version)");
|
|
755
1019
|
if (!Array.isArray(json.pages) || json.pages.length === 0) throw new DottieFormatError("A drawing needs at least one page");
|
|
756
1020
|
const reader = new Reader();
|
|
@@ -2998,7 +3262,7 @@ function hopCrossings(e, r, page, index) {
|
|
|
2998
3262
|
for (const samples of segments) {
|
|
2999
3263
|
const along = runningLengths(samples);
|
|
3000
3264
|
const length = along[along.length - 1];
|
|
3001
|
-
const
|
|
3265
|
+
const found2 = [];
|
|
3002
3266
|
const near = [];
|
|
3003
3267
|
const whole = boxOf(samples);
|
|
3004
3268
|
for (let n = 0; n < count; n++) if (touching(whole, lines[n].box)) near.push(lines[n]);
|
|
@@ -3015,12 +3279,12 @@ function hopCrossings(e, r, page, index) {
|
|
|
3015
3279
|
if (Math.abs(det) < 1e-9) continue;
|
|
3016
3280
|
const wx = a.x - p.x, wy = a.y - p.y;
|
|
3017
3281
|
const t = (wx * ey - wy * ex) / det, s = (wx * dy - wy * dx) / det;
|
|
3018
|
-
if (t > 0 && t < 1 && s >= 0 && s <= 1)
|
|
3282
|
+
if (t > 0 && t < 1 && s >= 0 && s <= 1) found2.push(along[i] + t * (along[i + 1] - along[i]));
|
|
3019
3283
|
}
|
|
3020
3284
|
}
|
|
3021
3285
|
}
|
|
3022
3286
|
const kept = [];
|
|
3023
|
-
for (const at of
|
|
3287
|
+
for (const at of found2.sort((x, y) => x - y)) {
|
|
3024
3288
|
if (at < rad + 2 || at > length - rad - 2) continue;
|
|
3025
3289
|
if (kept.length > 0 && at - kept[kept.length - 1] < 2 * rad + 2) continue;
|
|
3026
3290
|
kept.push(at);
|
|
@@ -3234,7 +3498,7 @@ var lerpColor = (a, b, u) => ({
|
|
|
3234
3498
|
b: mix01(a.b, b.b, u),
|
|
3235
3499
|
a: mix01(a.a, b.a, u)
|
|
3236
3500
|
});
|
|
3237
|
-
function
|
|
3501
|
+
function num3(track, t, p, base2) {
|
|
3238
3502
|
return value(track, t, (k) => k[p], base2, lerpNum);
|
|
3239
3503
|
}
|
|
3240
3504
|
function isChanging(track, t) {
|
|
@@ -3260,20 +3524,20 @@ function isVisible(track, t) {
|
|
|
3260
3524
|
function applyTrack(track, e, t) {
|
|
3261
3525
|
e = cloneElement(e);
|
|
3262
3526
|
if (track.keys.some((k) => k.reveal !== void 0)) {
|
|
3263
|
-
const r =
|
|
3527
|
+
const r = num3(track, t, "reveal", e.reveal ?? 1);
|
|
3264
3528
|
e.reveal = r >= 1 ? null : Math.max(0, r);
|
|
3265
3529
|
}
|
|
3266
3530
|
if (track.keys.some((k) => k.opacity !== void 0)) {
|
|
3267
|
-
const o = Math.min(Math.max(
|
|
3531
|
+
const o = Math.min(Math.max(num3(track, t, "opacity", e.style.opacity ?? 1), 0), 1);
|
|
3268
3532
|
e.style.opacity = o >= 1 ? null : o;
|
|
3269
3533
|
}
|
|
3270
3534
|
if (e.kind === "connector") return e;
|
|
3271
|
-
e.x =
|
|
3272
|
-
e.y =
|
|
3273
|
-
e.width = Math.max(0,
|
|
3274
|
-
e.height = Math.max(0,
|
|
3275
|
-
if (track.keys.some((k) => k.rotation !== void 0)) e.rotation =
|
|
3276
|
-
e.style.strokeWidth = Math.max(0,
|
|
3535
|
+
e.x = num3(track, t, "x", e.x);
|
|
3536
|
+
e.y = num3(track, t, "y", e.y);
|
|
3537
|
+
e.width = Math.max(0, num3(track, t, "width", e.width));
|
|
3538
|
+
e.height = Math.max(0, num3(track, t, "height", e.height));
|
|
3539
|
+
if (track.keys.some((k) => k.rotation !== void 0)) e.rotation = num3(track, t, "rotation", angle(e));
|
|
3540
|
+
e.style.strokeWidth = Math.max(0, num3(track, t, "strokeWidth", e.style.strokeWidth));
|
|
3277
3541
|
e.style.stroke = value(track, t, (k) => k.stroke, e.style.stroke, lerpColor);
|
|
3278
3542
|
e.style.fill = value(track, t, (k) => k.fill, e.style.fill, lerpColor);
|
|
3279
3543
|
return e;
|
|
@@ -3349,7 +3613,8 @@ function posedWithMetrics(page, t, state = "initial", since = null, carried = {}
|
|
|
3349
3613
|
}
|
|
3350
3614
|
const flowing = (page.flows ?? []).length > 0;
|
|
3351
3615
|
const measuring = flowing || page.elements.some((e) => e.gauge || (e.overrides ?? []).some((o) => mentionsMetric(o.when)));
|
|
3352
|
-
const
|
|
3616
|
+
const fills = values !== null && Object.keys(values).length > 0 && page.elements.some((e) => e.text?.includes("{") || (e.overrides ?? []).some((o) => o.text?.includes("{")));
|
|
3617
|
+
const timeline = page.timeline ?? (off.size === 0 && active.size === 0 && !measuring && !fills ? null : EMPTY_TIMELINE);
|
|
3353
3618
|
if (!timeline) return { frame: page, metrics: emptyMetrics() };
|
|
3354
3619
|
t = clampTime(t);
|
|
3355
3620
|
const tracks = trackMap(timeline);
|
|
@@ -3385,6 +3650,10 @@ function posedWithMetrics(page, t, state = "initial", since = null, carried = {}
|
|
|
3385
3650
|
const track = tracks.get(e.id);
|
|
3386
3651
|
let out = track ? applyTrack(track, e, t) : e;
|
|
3387
3652
|
for (const o of active.get(e.id) ?? []) out = applyOverride(o, out);
|
|
3653
|
+
if (fills && values && out.text) {
|
|
3654
|
+
const text = filling(out.text, values);
|
|
3655
|
+
if (text !== out.text) out = { ...out, text };
|
|
3656
|
+
}
|
|
3388
3657
|
const list = active.get(e.id);
|
|
3389
3658
|
if (list) {
|
|
3390
3659
|
const caps = list.map((o) => o.capacity).filter((c) => c !== null && Number.isFinite(c));
|
|
@@ -3696,13 +3965,18 @@ function sceneNamed(name, markers) {
|
|
|
3696
3965
|
const exact = markers.findIndex((m) => m.name === name);
|
|
3697
3966
|
return exact >= 0 ? exact : markers.findIndex((m) => m.name.toLowerCase() === name.toLowerCase());
|
|
3698
3967
|
}
|
|
3699
|
-
function clickTargets(page, file) {
|
|
3968
|
+
function clickTargets(page, file, hrefs) {
|
|
3700
3969
|
const markers = page.timeline?.markers ?? [];
|
|
3701
3970
|
const out = /* @__PURE__ */ new Map();
|
|
3702
3971
|
let pageOf = null;
|
|
3703
3972
|
for (const e of page.elements) {
|
|
3704
3973
|
const c = e.click;
|
|
3705
|
-
const state = c && changesState(c) ? {
|
|
3974
|
+
const state = c && changesState(c) ? {
|
|
3975
|
+
...c.set ? { set: c.set } : {},
|
|
3976
|
+
...c.toggle ? { toggle: c.toggle } : {},
|
|
3977
|
+
...c.reset ? { reset: true } : {},
|
|
3978
|
+
...c.run ? { run: c.run } : {}
|
|
3979
|
+
} : null;
|
|
3706
3980
|
if (c?.scene) {
|
|
3707
3981
|
const i = sceneNamed(c.scene, markers);
|
|
3708
3982
|
if (i >= 0) {
|
|
@@ -3727,11 +4001,19 @@ function clickTargets(page, file) {
|
|
|
3727
4001
|
});
|
|
3728
4002
|
}
|
|
3729
4003
|
const target = pageOf.get(e.link);
|
|
3730
|
-
if (target !== void 0 && file.pages[target] !== page)
|
|
4004
|
+
if (target !== void 0 && file.pages[target] !== page) {
|
|
4005
|
+
out.set(e.id, { page: target });
|
|
4006
|
+
continue;
|
|
4007
|
+
}
|
|
3731
4008
|
}
|
|
4009
|
+
const href = e.link && hrefs && Object.prototype.hasOwnProperty.call(hrefs, e.link) ? pageHref(hrefs[e.link]) : null;
|
|
4010
|
+
if (href) out.set(e.id, { href });
|
|
3732
4011
|
}
|
|
3733
4012
|
return out;
|
|
3734
4013
|
}
|
|
4014
|
+
function pageHref(href) {
|
|
4015
|
+
return typeof href === "string" && /^[A-Za-z0-9._~%/-]+\.html(#[^\s:]*)?$/.test(href) && !href.startsWith("//") ? href : null;
|
|
4016
|
+
}
|
|
3735
4017
|
function hoveredElement(e) {
|
|
3736
4018
|
const h = e.hover;
|
|
3737
4019
|
if (!h) return e;
|
|
@@ -4725,6 +5007,40 @@ function start() {
|
|
|
4725
5007
|
return w;
|
|
4726
5008
|
}
|
|
4727
5009
|
|
|
5010
|
+
// src/logic-loader.ts
|
|
5011
|
+
var scriptSrc = typeof document !== "undefined" ? document.currentScript?.src ?? null : null;
|
|
5012
|
+
var found = null;
|
|
5013
|
+
var present = () => globalThis.DottieLogic;
|
|
5014
|
+
function logicEngine() {
|
|
5015
|
+
found ?? (found = (async () => {
|
|
5016
|
+
if (!present()) {
|
|
5017
|
+
const here = import.meta.url;
|
|
5018
|
+
const base2 = here ?? scriptSrc;
|
|
5019
|
+
if (!base2) throw new Error("No dottie-logic.min.js on the page, and no address to find it from");
|
|
5020
|
+
const url = new URL("./dottie-logic.min.js", base2).href;
|
|
5021
|
+
if (here) await import(
|
|
5022
|
+
/* @vite-ignore */
|
|
5023
|
+
url
|
|
5024
|
+
);
|
|
5025
|
+
else await new Promise((resolve2, reject) => {
|
|
5026
|
+
const s = document.createElement("script");
|
|
5027
|
+
s.src = url;
|
|
5028
|
+
s.onload = () => resolve2();
|
|
5029
|
+
s.onerror = () => reject(new Error(`Couldn't load ${url}`));
|
|
5030
|
+
document.head.append(s);
|
|
5031
|
+
});
|
|
5032
|
+
}
|
|
5033
|
+
const engine = present();
|
|
5034
|
+
if (!engine) throw new Error("dottie-logic.min.js didn't load");
|
|
5035
|
+
await engine.loadLogic();
|
|
5036
|
+
return engine;
|
|
5037
|
+
})());
|
|
5038
|
+
found.catch(() => {
|
|
5039
|
+
found = null;
|
|
5040
|
+
});
|
|
5041
|
+
return found;
|
|
5042
|
+
}
|
|
5043
|
+
|
|
4728
5044
|
// src/player.ts
|
|
4729
5045
|
var WHITE = { r: 1, g: 1, b: 1, a: 1 };
|
|
4730
5046
|
function pageIndex(file, spec) {
|
|
@@ -4755,7 +5071,8 @@ output { font-variant-numeric: tabular-nums; min-width: 4.5em; text-align: right
|
|
|
4755
5071
|
.error { padding: 16px; font-family: ui-monospace, Menlo, monospace; white-space: pre-wrap; }
|
|
4756
5072
|
${CLICK_STYLE}`;
|
|
4757
5073
|
var Playback = class {
|
|
4758
|
-
/** `file` is the drawing `page` belongs to, so links to its other pages are clickable
|
|
5074
|
+
/** `file` is the drawing `page` belongs to, so links to its other pages are clickable; `hrefs`, the web pages that
|
|
5075
|
+
* show the elements links point at, for pages that aren't in it (clickTargets). */
|
|
4759
5076
|
constructor(page, into, options = {}) {
|
|
4760
5077
|
__publicField(this, "page");
|
|
4761
5078
|
__publicField(this, "plan");
|
|
@@ -4797,7 +5114,7 @@ var Playback = class {
|
|
|
4797
5114
|
this.page = options.clean ? cleanCopy(page) : page;
|
|
4798
5115
|
addFonts(usedFonts(this.page));
|
|
4799
5116
|
this.plan = plan(this.page);
|
|
4800
|
-
this.clicks = clickTargets(page, options.file);
|
|
5117
|
+
this.clicks = clickTargets(page, options.file, options.hrefs);
|
|
4801
5118
|
const table = symbolTable(this.page);
|
|
4802
5119
|
for (const e of this.page.elements) {
|
|
4803
5120
|
const s = e.kind === "symbol" && e.symbol !== null ? table.get(e.symbol) : void 0;
|
|
@@ -5066,6 +5383,14 @@ var DottiePlayer = class extends ElementBase {
|
|
|
5066
5383
|
__publicField(this, "stillTime", null);
|
|
5067
5384
|
__publicField(this, "visible", true);
|
|
5068
5385
|
__publicField(this, "observer", null);
|
|
5386
|
+
/** The page last shown, so `page` fires only when it changes. */
|
|
5387
|
+
__publicField(this, "shownPage", null);
|
|
5388
|
+
__publicField(this, "onPopState", () => {
|
|
5389
|
+
if (this.file) this.mount();
|
|
5390
|
+
});
|
|
5391
|
+
/** Runs handler `handler` from the page's logic for a click on `id` (DottieDocument.clicked). Clicks take turns, so
|
|
5392
|
+
* each handler sees the state the one before left. */
|
|
5393
|
+
__publicField(this, "logicQueue", Promise.resolve());
|
|
5069
5394
|
this.root = this.attachShadow({ mode: "open" });
|
|
5070
5395
|
this.root.innerHTML = `<style>${STYLE}</style><div class="stage" part="stage" role="img"></div>
|
|
5071
5396
|
<div class="bar" part="controls" hidden>
|
|
@@ -5111,15 +5436,21 @@ var DottiePlayer = class extends ElementBase {
|
|
|
5111
5436
|
});
|
|
5112
5437
|
this.observer.observe(this);
|
|
5113
5438
|
}
|
|
5439
|
+
if (this.hasAttribute("history")) window.addEventListener("popstate", this.onPopState);
|
|
5114
5440
|
void this.load();
|
|
5115
5441
|
}
|
|
5116
5442
|
disconnectedCallback() {
|
|
5117
5443
|
cancelAnimationFrame(this.raf);
|
|
5118
5444
|
this.raf = 0;
|
|
5119
5445
|
this.observer?.disconnect();
|
|
5446
|
+
window.removeEventListener("popstate", this.onPopState);
|
|
5120
5447
|
}
|
|
5121
5448
|
attributeChangedCallback(name, old, value2) {
|
|
5122
5449
|
if (old === value2 || !this.isConnected) return;
|
|
5450
|
+
if (name === "history") {
|
|
5451
|
+
if (value2 === null) window.removeEventListener("popstate", this.onPopState);
|
|
5452
|
+
else window.addEventListener("popstate", this.onPopState);
|
|
5453
|
+
}
|
|
5123
5454
|
if (name === "src") void this.load();
|
|
5124
5455
|
else if (name === "label") this.stage.setAttribute("aria-label", value2 ?? "");
|
|
5125
5456
|
else if (this.file) this.mount();
|
|
@@ -5168,6 +5499,7 @@ var DottiePlayer = class extends ElementBase {
|
|
|
5168
5499
|
}
|
|
5169
5500
|
showFile(file) {
|
|
5170
5501
|
this.file = file;
|
|
5502
|
+
this.shownPage = null;
|
|
5171
5503
|
for (const w of file.warnings) console.warn(`dottie-player: ${w}`);
|
|
5172
5504
|
this.mount(() => this.dispatchEvent(new CustomEvent("load", { detail: { file } })));
|
|
5173
5505
|
}
|
|
@@ -5175,7 +5507,7 @@ var DottiePlayer = class extends ElementBase {
|
|
|
5175
5507
|
* showing stays until it is ready; a newer mount, file or failed load in the meantime wins. */
|
|
5176
5508
|
mount(mounted) {
|
|
5177
5509
|
const file = this.file;
|
|
5178
|
-
const page = file.pages[pageIndex(file, this.getAttribute("page"))];
|
|
5510
|
+
const page = file.pages[this.addressedPage(file) ?? pageIndex(file, this.getAttribute("page"))];
|
|
5179
5511
|
const clean = this.hasAttribute("clean");
|
|
5180
5512
|
const token = ++this.mountToken;
|
|
5181
5513
|
const shown = clean ? cleanCopy(page) : page;
|
|
@@ -5190,10 +5522,41 @@ var DottiePlayer = class extends ElementBase {
|
|
|
5190
5522
|
mounted?.();
|
|
5191
5523
|
});
|
|
5192
5524
|
}
|
|
5525
|
+
/** With `history`, the page the address names after its `#` (by name, any case), if it names one. */
|
|
5526
|
+
addressedPage(file) {
|
|
5527
|
+
if (!this.hasAttribute("history") || typeof location === "undefined" || location.hash.length < 2) return null;
|
|
5528
|
+
let name;
|
|
5529
|
+
try {
|
|
5530
|
+
name = decodeURIComponent(location.hash.slice(1));
|
|
5531
|
+
} catch {
|
|
5532
|
+
return null;
|
|
5533
|
+
}
|
|
5534
|
+
const i = file.pages.findIndex((p) => p.name.toLowerCase() === name.toLowerCase());
|
|
5535
|
+
return i >= 0 ? i : null;
|
|
5536
|
+
}
|
|
5537
|
+
/** The `links` attribute read: element id to web page, the pages that can't be named left out. */
|
|
5538
|
+
hrefs() {
|
|
5539
|
+
const raw = this.getAttribute("links");
|
|
5540
|
+
if (!raw) return void 0;
|
|
5541
|
+
let v;
|
|
5542
|
+
try {
|
|
5543
|
+
v = JSON.parse(raw);
|
|
5544
|
+
} catch {
|
|
5545
|
+
console.warn("dottie-player: links isn't JSON");
|
|
5546
|
+
return void 0;
|
|
5547
|
+
}
|
|
5548
|
+
if (v === null || typeof v !== "object" || Array.isArray(v)) return void 0;
|
|
5549
|
+
return v;
|
|
5550
|
+
}
|
|
5193
5551
|
mountPage(file, page, clean) {
|
|
5194
|
-
const p = new Playback(page, this.stage, { clean, file });
|
|
5552
|
+
const p = new Playback(page, this.stage, { clean, file, hrefs: this.hrefs() });
|
|
5195
5553
|
p.still = this.hasAttribute("at");
|
|
5196
5554
|
this.playback = p;
|
|
5555
|
+
if (this.shownPage !== page) {
|
|
5556
|
+
const first = this.shownPage === null;
|
|
5557
|
+
this.shownPage = page;
|
|
5558
|
+
queueMicrotask(() => this.dispatchEvent(new CustomEvent("page", { detail: { index: file.pages.indexOf(page), name: page.name, first } })));
|
|
5559
|
+
}
|
|
5197
5560
|
this.style.setProperty("--dottie-page", color(p.background));
|
|
5198
5561
|
this.style.setProperty("--dottie-ink", color(p.ink));
|
|
5199
5562
|
this.style.colorScheme = luminance(p.background) < 0.5 ? "dark" : "light";
|
|
@@ -5292,7 +5655,9 @@ var DottiePlayer = class extends ElementBase {
|
|
|
5292
5655
|
this.scheduleReplay();
|
|
5293
5656
|
}
|
|
5294
5657
|
if (!click) return;
|
|
5295
|
-
if (click.
|
|
5658
|
+
if (click.run && p.page.logic && this.hasAttribute("allow-logic")) {
|
|
5659
|
+
this.runLogic(click.run, id, click.reset === true);
|
|
5660
|
+
} else if (click.set || click.toggle || click.reset) {
|
|
5296
5661
|
this.setState(applyClick(
|
|
5297
5662
|
{ scene: null, url: null, set: click.set ?? null, toggle: click.toggle ?? null, reset: click.reset ?? null },
|
|
5298
5663
|
p.state,
|
|
@@ -5303,7 +5668,15 @@ var DottiePlayer = class extends ElementBase {
|
|
|
5303
5668
|
if ("url" in click) {
|
|
5304
5669
|
if (/^(https?:|mailto:)/i.test(click.url)) window.open(click.url, "_blank", "noopener");
|
|
5305
5670
|
} else if ("page" in click) {
|
|
5306
|
-
this.
|
|
5671
|
+
const target = this.file?.pages[click.page];
|
|
5672
|
+
if (this.hasAttribute("history") && target) {
|
|
5673
|
+
history.pushState(null, "", "#" + encodeURIComponent(target.name));
|
|
5674
|
+
this.mount();
|
|
5675
|
+
} else {
|
|
5676
|
+
this.setAttribute("page", String(click.page + 1));
|
|
5677
|
+
}
|
|
5678
|
+
} else if ("href" in click) {
|
|
5679
|
+
location.assign(click.href);
|
|
5307
5680
|
} else if (click.scene >= 0 && click.scene < p.scenes.length) {
|
|
5308
5681
|
if (reduced || this.hasAttribute("at") || p.frameCount < 2) {
|
|
5309
5682
|
this.setPlaying(false);
|
|
@@ -5314,6 +5687,35 @@ var DottiePlayer = class extends ElementBase {
|
|
|
5314
5687
|
}
|
|
5315
5688
|
}
|
|
5316
5689
|
}
|
|
5690
|
+
runLogic(handler, id, reset) {
|
|
5691
|
+
const p = this.playback;
|
|
5692
|
+
if (!p) return;
|
|
5693
|
+
const e = p.page.elements.find((x) => x.id === id);
|
|
5694
|
+
const s = p.sceneAt(p.frame);
|
|
5695
|
+
const event = {
|
|
5696
|
+
id,
|
|
5697
|
+
anchor: e?.anchor ?? null,
|
|
5698
|
+
text: e?.text?.split("\n")[0] ?? null,
|
|
5699
|
+
time: this.currentTime,
|
|
5700
|
+
scene: s < 0 ? null : p.sceneNames[s]
|
|
5701
|
+
};
|
|
5702
|
+
this.logicQueue = this.logicQueue.then(async () => {
|
|
5703
|
+
const engine = await logicEngine();
|
|
5704
|
+
const now = this.playback;
|
|
5705
|
+
if (!now || now !== p || !now.page.logic) return;
|
|
5706
|
+
let start2 = now.state;
|
|
5707
|
+
if (reset) {
|
|
5708
|
+
start2 = {};
|
|
5709
|
+
for (const k of Object.keys(now.state)) start2[k] = false;
|
|
5710
|
+
Object.assign(start2, now.page.variables ?? {});
|
|
5711
|
+
}
|
|
5712
|
+
const r = engine.runLogic(now.page.logic, handler, start2, event);
|
|
5713
|
+
if (r.ok) this.setState({ ...start2, ...r.set });
|
|
5714
|
+
else this.dispatchEvent(new CustomEvent("logicerror", { detail: { element: id, handler, error: r.error } }));
|
|
5715
|
+
}).catch((err) => {
|
|
5716
|
+
this.dispatchEvent(new CustomEvent("logicerror", { detail: { element: id, handler, error: err.message } }));
|
|
5717
|
+
});
|
|
5718
|
+
}
|
|
5317
5719
|
/** The page's variables now. */
|
|
5318
5720
|
get variables() {
|
|
5319
5721
|
return { ...this.playback?.state ?? {} };
|
|
@@ -5420,7 +5822,7 @@ var DottiePlayer = class extends ElementBase {
|
|
|
5420
5822
|
if (handled) e.preventDefault();
|
|
5421
5823
|
}
|
|
5422
5824
|
};
|
|
5423
|
-
__publicField(DottiePlayer, "observedAttributes", ["src", "page", "at", "clean", "no-controls", "label"]);
|
|
5825
|
+
__publicField(DottiePlayer, "observedAttributes", ["src", "page", "at", "clean", "no-controls", "label", "allow-logic", "history", "links"]);
|
|
5424
5826
|
function clickable(target) {
|
|
5425
5827
|
const el = target;
|
|
5426
5828
|
return el && typeof el.closest === "function" ? el.closest(".dottie-a") : null;
|
|
@@ -5525,7 +5927,7 @@ function render(source, options = {}) {
|
|
|
5525
5927
|
function isFile(v) {
|
|
5526
5928
|
return typeof v === "object" && v !== null && Array.isArray(v.pages) && Array.isArray(v.warnings);
|
|
5527
5929
|
}
|
|
5528
|
-
var VERSION = "0.
|
|
5930
|
+
var VERSION = "0.11.1";
|
|
5529
5931
|
export {
|
|
5530
5932
|
BOIL_DRAWINGS,
|
|
5531
5933
|
BUNDLED_FONTS,
|