@phin-tech/dottie 0.6.2 → 0.7.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 +5 -0
- package/README.md +8 -3
- package/dist/compile.js +41 -11
- package/dist/dottie-compile.mjs +42 -12
- package/dist/dottie-player.js +413 -67
- package/dist/dottie-player.min.js +53 -53
- package/dist/dottie.js +413 -67
- package/dist/types/clicks.d.ts +8 -2
- package/dist/types/connector.d.ts +6 -6
- package/dist/types/element.d.ts +3 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/model.d.ts +16 -1
- package/dist/types/player.d.ts +19 -2
- package/dist/types/shapes.d.ts +17 -4
- package/dist/types/state.d.ts +20 -0
- package/dist/types/timeline.d.ts +5 -3
- package/package.json +1 -1
package/dist/dottie.js
CHANGED
|
@@ -1,19 +1,73 @@
|
|
|
1
|
-
/*! dottie.js 0.
|
|
1
|
+
/*! dottie.js 0.7.0 | Apache-2.0 | https://github.com/phin-tech/dottie */
|
|
2
2
|
var __defProp = Object.defineProperty;
|
|
3
3
|
var __defNormalProp = (obj, key, value2) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value: value2 }) : obj[key] = value2;
|
|
4
4
|
var __publicField = (obj, key, value2) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value2);
|
|
5
5
|
|
|
6
|
+
// src/state.ts
|
|
7
|
+
var MAX_NAME = 64;
|
|
8
|
+
function isName(name) {
|
|
9
|
+
return name.length <= MAX_NAME && /^[A-Za-z_][A-Za-z0-9_]*$/.test(name);
|
|
10
|
+
}
|
|
11
|
+
function conditionNames(condition) {
|
|
12
|
+
const out = [];
|
|
13
|
+
for (const any of condition.split("||")) {
|
|
14
|
+
for (const all of any.split("&&")) {
|
|
15
|
+
let term = trim(all);
|
|
16
|
+
if (term.startsWith("!")) term = trim(term.slice(1));
|
|
17
|
+
if (!isName(term)) return null;
|
|
18
|
+
out.push(term);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return out;
|
|
22
|
+
}
|
|
23
|
+
function holds(condition, state) {
|
|
24
|
+
if (conditionNames(condition) === null) return false;
|
|
25
|
+
return condition.split("||").some((any) => any.split("&&").every((all) => {
|
|
26
|
+
let term = trim(all);
|
|
27
|
+
let negated = false;
|
|
28
|
+
if (term.startsWith("!")) {
|
|
29
|
+
negated = true;
|
|
30
|
+
term = trim(term.slice(1));
|
|
31
|
+
}
|
|
32
|
+
return (state[term] ?? false) !== negated;
|
|
33
|
+
}));
|
|
34
|
+
}
|
|
35
|
+
function stateValues(page, choice) {
|
|
36
|
+
if (choice === "all") return null;
|
|
37
|
+
if (choice === "initial") return { ...page.variables ?? {} };
|
|
38
|
+
return { ...page.variables ?? {}, ...choice };
|
|
39
|
+
}
|
|
40
|
+
function stateHidden(page, choice) {
|
|
41
|
+
const values = stateValues(page, choice);
|
|
42
|
+
const out = /* @__PURE__ */ new Set();
|
|
43
|
+
if (!values) return out;
|
|
44
|
+
for (const e of page.elements) if (e.when !== null && !holds(e.when, values)) out.add(e.id);
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
function changesState(c) {
|
|
48
|
+
return !!c && (Object.keys(c.set ?? {}).length > 0 || (c.toggle ?? []).length > 0);
|
|
49
|
+
}
|
|
50
|
+
function applyClick(c, state) {
|
|
51
|
+
const out = { ...state };
|
|
52
|
+
for (const [k, v] of Object.entries(c.set ?? {})) out[k] = v;
|
|
53
|
+
for (const k of c.toggle ?? []) out[k] = !(out[k] ?? false);
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
function trim(s) {
|
|
57
|
+
return s.replace(/^[ \t ]+|[ \t ]+$/g, "");
|
|
58
|
+
}
|
|
59
|
+
|
|
6
60
|
// src/model.ts
|
|
7
61
|
var SUPPORTED_VERSION = 2;
|
|
8
62
|
var KINDS = ["rectangle", "ellipse", "diamond", "text", "connector", "freehand", "path", "note", "image", "symbol"];
|
|
9
63
|
var EASES = ["linear", "easeIn", "easeOut", "easeInOut", "hold", "spring", "bounce", "elastic"];
|
|
10
64
|
var KNOWN_FIELDS = {
|
|
11
65
|
file: ["version", "pages", "images"],
|
|
12
|
-
page: ["id", "name", "layers", "elements", "theme", "guidesX", "guidesY", "guidesLocked", "timeline", "symbols"],
|
|
66
|
+
page: ["id", "name", "layers", "elements", "theme", "guidesX", "guidesY", "guidesLocked", "timeline", "symbols", "variables"],
|
|
13
67
|
symbol: ["id", "name", "width", "height", "layers", "elements", "timeline", "button", "params"],
|
|
14
68
|
layer: ["id", "name", "visible", "locked"],
|
|
15
69
|
rgba: ["r", "g", "b", "a"],
|
|
16
|
-
style: ["stroke", "fill", "fillStyle", "strokeWidth", "dash", "roughness", "bowing", "fontSize", "font", "gridSize", "opacity", "dashOffset"],
|
|
70
|
+
style: ["stroke", "fill", "fillStyle", "strokeWidth", "dash", "roughness", "bowing", "fontSize", "font", "gridSize", "opacity", "dashOffset", "textColor"],
|
|
17
71
|
element: [
|
|
18
72
|
"id",
|
|
19
73
|
"kind",
|
|
@@ -37,6 +91,7 @@ var KNOWN_FIELDS = {
|
|
|
37
91
|
"anchor",
|
|
38
92
|
"click",
|
|
39
93
|
"hover",
|
|
94
|
+
"when",
|
|
40
95
|
"symbol",
|
|
41
96
|
"playback",
|
|
42
97
|
"offset",
|
|
@@ -54,15 +109,16 @@ var KNOWN_FIELDS = {
|
|
|
54
109
|
extensionRef: ["id", "shape", "version", "params"],
|
|
55
110
|
normalizedPoint: ["x", "y"],
|
|
56
111
|
normalizedRect: ["x", "y", "width", "height"],
|
|
57
|
-
connector: ["start", "end", "routing", "startHead", "endHead"],
|
|
112
|
+
connector: ["start", "end", "routing", "startHead", "endHead", "waypoints", "hops"],
|
|
113
|
+
waypoint: ["x", "y"],
|
|
58
114
|
timeline: ["fps", "duration", "boil", "settle", "poster", "tracks", "camera", "markers"],
|
|
59
115
|
track: ["element", "keys", "boil", "follow", "repeats", "march"],
|
|
60
116
|
keyframe: ["t", "x", "y", "width", "height", "rotation", "strokeWidth", "stroke", "fill", "visible", "reveal", "opacity", "ease"],
|
|
61
117
|
follow: ["path", "start", "end", "ease", "reverse", "orient", "laps"],
|
|
62
118
|
repeat: ["start", "end", "count"],
|
|
63
119
|
cameraKey: ["t", "x", "y", "width", "height", "ease"],
|
|
64
|
-
marker: ["t", "name"],
|
|
65
|
-
click: ["scene", "url"],
|
|
120
|
+
marker: ["t", "name", "loop"],
|
|
121
|
+
click: ["scene", "url", "set", "toggle"],
|
|
66
122
|
hover: ["stroke", "fill", "opacity"]
|
|
67
123
|
};
|
|
68
124
|
var MAX_DURATION = 3600;
|
|
@@ -136,7 +192,8 @@ var Reader = class {
|
|
|
136
192
|
font: str(s.font) || null,
|
|
137
193
|
gridSize: num(s.gridSize) ?? null,
|
|
138
194
|
opacity: num(s.opacity) ?? null,
|
|
139
|
-
dashOffset: num(s.dashOffset) ?? null
|
|
195
|
+
dashOffset: num(s.dashOffset) ?? null,
|
|
196
|
+
textColor: rgba(s.textColor) ?? null
|
|
140
197
|
};
|
|
141
198
|
}
|
|
142
199
|
endpoint(v) {
|
|
@@ -164,7 +221,12 @@ var Reader = class {
|
|
|
164
221
|
end: this.endpoint(v.end),
|
|
165
222
|
routing: routing && ["straight", "curved", "elbow"].includes(routing) ? routing : "straight",
|
|
166
223
|
startHead: head(v.startHead, "none"),
|
|
167
|
-
endHead: head(v.endHead, "arrow")
|
|
224
|
+
endHead: head(v.endHead, "arrow"),
|
|
225
|
+
waypoints: Array.isArray(v.waypoints) ? v.waypoints.filter(isObj).map((w) => {
|
|
226
|
+
this.fields(w, "waypoint");
|
|
227
|
+
return { x: num(w.x) ?? 0, y: num(w.y) ?? 0 };
|
|
228
|
+
}) : null,
|
|
229
|
+
hops: typeof v.hops === "boolean" ? v.hops : null
|
|
168
230
|
};
|
|
169
231
|
}
|
|
170
232
|
image(v, table) {
|
|
@@ -215,6 +277,7 @@ var Reader = class {
|
|
|
215
277
|
anchor: str(v.anchor) ?? null,
|
|
216
278
|
click: this.click(v.click),
|
|
217
279
|
hover: this.hover(v.hover),
|
|
280
|
+
when: str(v.when) ?? null,
|
|
218
281
|
symbol: uuid(v.symbol) ?? null,
|
|
219
282
|
playback: this.playback(v.playback),
|
|
220
283
|
offset: num(v.offset) ?? null,
|
|
@@ -250,13 +313,27 @@ var Reader = class {
|
|
|
250
313
|
}
|
|
251
314
|
return { id, shape, version, params };
|
|
252
315
|
}
|
|
316
|
+
variables(v) {
|
|
317
|
+
if (!isObj(v)) return null;
|
|
318
|
+
const out = {};
|
|
319
|
+
for (const [k, b] of Object.entries(v)) if (typeof b === "boolean") out[k] = b;
|
|
320
|
+
return Object.keys(out).length > 0 ? out : null;
|
|
321
|
+
}
|
|
253
322
|
click(v) {
|
|
254
323
|
if (!isObj(v)) return null;
|
|
255
324
|
this.fields(v, "click");
|
|
256
325
|
const url = str(v.url);
|
|
257
326
|
const safe = url === void 0 ? null : allowedURL(url);
|
|
258
327
|
if (url !== void 0 && safe === null) this.warn("Skipped a click link that isn't http, https or mailto");
|
|
259
|
-
|
|
328
|
+
let set = null;
|
|
329
|
+
if (isObj(v.set)) {
|
|
330
|
+
set = {};
|
|
331
|
+
for (const [k, b] of Object.entries(v.set)) if (typeof b === "boolean" && isName(k)) set[k] = b;
|
|
332
|
+
if (Object.keys(set).length === 0) set = null;
|
|
333
|
+
}
|
|
334
|
+
const raw = typeof v.toggle === "string" ? [v.toggle] : Array.isArray(v.toggle) && v.toggle.every((x) => typeof x === "string") ? v.toggle : null;
|
|
335
|
+
const toggle = raw ? raw.filter(isName) : null;
|
|
336
|
+
return { scene: str(v.scene) ?? null, url: safe, set, toggle: toggle && toggle.length > 0 ? toggle : null };
|
|
260
337
|
}
|
|
261
338
|
playback(v) {
|
|
262
339
|
const s = str(v);
|
|
@@ -357,7 +434,7 @@ var Reader = class {
|
|
|
357
434
|
if (camera) stableSortBy(camera, (c) => c.t);
|
|
358
435
|
const markers = Array.isArray(v.markers) ? v.markers.filter(isObj).flatMap((m) => {
|
|
359
436
|
const t = num(m.t);
|
|
360
|
-
return t !== void 0 && t >= 0 ? [{ t, name: str(m.name) ?? "" }] : [];
|
|
437
|
+
return t !== void 0 && t >= 0 ? [{ t, name: str(m.name) ?? "", loop: bool(m.loop) ?? null }] : [];
|
|
361
438
|
}) : null;
|
|
362
439
|
if (markers) stableSortBy(markers, (m) => m.t);
|
|
363
440
|
const poster = num(v.poster);
|
|
@@ -390,7 +467,8 @@ var Reader = class {
|
|
|
390
467
|
elements: (Array.isArray(v.elements) ? v.elements : []).map((e) => this.element(e, images)).filter((e) => e !== null),
|
|
391
468
|
theme: str(v.theme) ?? null,
|
|
392
469
|
timeline: this.timeline(v.timeline),
|
|
393
|
-
symbols: Array.isArray(v.symbols) ? v.symbols.map((x) => this.symbol(x, images)).filter((x) => x !== null) : null
|
|
470
|
+
symbols: Array.isArray(v.symbols) ? v.symbols.map((x) => this.symbol(x, images)).filter((x) => x !== null) : null,
|
|
471
|
+
variables: this.variables(v.variables)
|
|
394
472
|
};
|
|
395
473
|
}
|
|
396
474
|
};
|
|
@@ -441,8 +519,18 @@ function isAutomaticInk(c) {
|
|
|
441
519
|
function cloneElement(e) {
|
|
442
520
|
return {
|
|
443
521
|
...e,
|
|
444
|
-
style: {
|
|
445
|
-
|
|
522
|
+
style: {
|
|
523
|
+
...e.style,
|
|
524
|
+
stroke: { ...e.style.stroke },
|
|
525
|
+
fill: { ...e.style.fill },
|
|
526
|
+
textColor: e.style.textColor ? { ...e.style.textColor } : null
|
|
527
|
+
},
|
|
528
|
+
connector: e.connector ? {
|
|
529
|
+
...e.connector,
|
|
530
|
+
start: { ...e.connector.start },
|
|
531
|
+
end: { ...e.connector.end },
|
|
532
|
+
waypoints: e.connector.waypoints?.map((w) => ({ ...w })) ?? null
|
|
533
|
+
} : null
|
|
446
534
|
};
|
|
447
535
|
}
|
|
448
536
|
function orderedElements(page) {
|
|
@@ -872,13 +960,19 @@ function rotatedBounds(e) {
|
|
|
872
960
|
return { x, y, width: Math.max(...xs) - x, height: Math.max(...ys) - y };
|
|
873
961
|
}
|
|
874
962
|
function resolvingInk(e, ink) {
|
|
875
|
-
|
|
963
|
+
const text = e.style.textColor !== null && isAutomaticInk(e.style.textColor);
|
|
964
|
+
if (!isAutomaticInk(e.style.stroke) && !text) return e;
|
|
876
965
|
const copy = cloneElement(e);
|
|
877
966
|
const covers = e.style.fillStyle === "solid" || e.style.fillStyle === "grid";
|
|
878
967
|
const onLightFill = covers && e.style.fill.a >= 0.5 && luminance(e.style.fill) > 0.5;
|
|
879
|
-
|
|
968
|
+
const resolved2 = luminance(ink) > 0.5 && onLightFill ? black : ink;
|
|
969
|
+
if (isAutomaticInk(e.style.stroke)) copy.style.stroke = { ...resolved2 };
|
|
970
|
+
if (text) copy.style.textColor = { ...resolved2 };
|
|
880
971
|
return copy;
|
|
881
972
|
}
|
|
973
|
+
function labelColor(e) {
|
|
974
|
+
return e.kind === "text" || e.kind === "note" ? e.style.stroke : e.style.textColor ?? e.style.stroke;
|
|
975
|
+
}
|
|
882
976
|
function shapeContours(e) {
|
|
883
977
|
if (e.kind !== "path" || !e.subpaths) return [];
|
|
884
978
|
const out = [];
|
|
@@ -972,34 +1066,93 @@ function resolve(ep, other, index) {
|
|
|
972
1066
|
function endpoints(e, index) {
|
|
973
1067
|
const c = e.connector;
|
|
974
1068
|
if (!c) return [{ x: 0, y: 0 }, { x: 0, y: 0 }];
|
|
975
|
-
|
|
1069
|
+
const w = c.waypoints ?? [];
|
|
1070
|
+
return [
|
|
1071
|
+
resolve(c.start, w.length > 0 ? w[0] : reference(c.end, index), index),
|
|
1072
|
+
resolve(c.end, w.length > 0 ? w[w.length - 1] : reference(c.start, index), index)
|
|
1073
|
+
];
|
|
976
1074
|
}
|
|
977
1075
|
function routePoints(r) {
|
|
978
1076
|
if (r.kind === "polyline") return r.points;
|
|
979
|
-
const out = [];
|
|
980
|
-
for (let
|
|
981
|
-
const
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
1077
|
+
const out = [], p = r.points;
|
|
1078
|
+
for (let s = 0; s < Math.floor((p.length - 1) / 3); s++) {
|
|
1079
|
+
const a = p[3 * s], b = p[3 * s + 1], c = p[3 * s + 2], d = p[3 * s + 3];
|
|
1080
|
+
for (let i = s === 0 ? 0 : 1; i <= 24; i++) {
|
|
1081
|
+
const t = i / 24, u = 1 - t;
|
|
1082
|
+
out.push({
|
|
1083
|
+
x: u * u * u * a.x + 3 * u * u * t * b.x + 3 * u * t * t * c.x + t * t * t * d.x,
|
|
1084
|
+
y: u * u * u * a.y + 3 * u * u * t * b.y + 3 * u * t * t * c.y + t * t * t * d.y
|
|
1085
|
+
});
|
|
1086
|
+
}
|
|
986
1087
|
}
|
|
987
1088
|
return out;
|
|
988
1089
|
}
|
|
1090
|
+
var vertical = (p) => p === null ? null : p === "top" || p === "bottom";
|
|
1091
|
+
function simplified(pts) {
|
|
1092
|
+
const out = [];
|
|
1093
|
+
for (const q of pts) {
|
|
1094
|
+
const l = out[out.length - 1];
|
|
1095
|
+
if (l && Math.abs(l.x - q.x) < 0.5 && Math.abs(l.y - q.y) < 0.5) continue;
|
|
1096
|
+
if (out.length >= 2) {
|
|
1097
|
+
const p = out[out.length - 2], m = out[out.length - 1];
|
|
1098
|
+
if (Math.abs(p.x - m.x) < 0.5 && Math.abs(m.x - q.x) < 0.5 || Math.abs(p.y - m.y) < 0.5 && Math.abs(m.y - q.y) < 0.5) {
|
|
1099
|
+
out[out.length - 1] = q;
|
|
1100
|
+
continue;
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
out.push(q);
|
|
1104
|
+
}
|
|
1105
|
+
return out.length >= 2 ? out : pts;
|
|
1106
|
+
}
|
|
1107
|
+
function routeThrough(p, c) {
|
|
1108
|
+
switch (c.routing) {
|
|
1109
|
+
case "straight":
|
|
1110
|
+
return { kind: "polyline", points: p };
|
|
1111
|
+
case "curved": {
|
|
1112
|
+
const out = [p[0]];
|
|
1113
|
+
for (let i = 0; i < p.length - 1; i++) {
|
|
1114
|
+
const p0 = p[Math.max(i - 1, 0)], p1 = p[i], p2 = p[i + 1], p3 = p[Math.min(i + 2, p.length - 1)];
|
|
1115
|
+
out.push({ x: p1.x + (p2.x - p0.x) / 6, y: p1.y + (p2.y - p0.y) / 6 });
|
|
1116
|
+
out.push({ x: p2.x - (p3.x - p1.x) / 6, y: p2.y - (p3.y - p1.y) / 6 });
|
|
1117
|
+
out.push(p2);
|
|
1118
|
+
}
|
|
1119
|
+
return { kind: "curve", points: out };
|
|
1120
|
+
}
|
|
1121
|
+
case "elbow": {
|
|
1122
|
+
const a = p[0], first = p[1];
|
|
1123
|
+
let goVertical = vertical(c.start.port) ?? Math.abs(first.y - a.y) > Math.abs(first.x - a.x);
|
|
1124
|
+
const endV = vertical(c.end.port);
|
|
1125
|
+
const out = [a];
|
|
1126
|
+
for (let i = 1; i < p.length; i++) {
|
|
1127
|
+
const q = p[i], cur = out[out.length - 1];
|
|
1128
|
+
const alignedX = Math.abs(cur.x - q.x) < 0.5, alignedY = Math.abs(cur.y - q.y) < 0.5;
|
|
1129
|
+
if (alignedX || alignedY) {
|
|
1130
|
+
out.push(q);
|
|
1131
|
+
if (alignedX !== alignedY) goVertical = !alignedX;
|
|
1132
|
+
continue;
|
|
1133
|
+
}
|
|
1134
|
+
const up = i === p.length - 1 && endV !== null ? !endV : goVertical;
|
|
1135
|
+
out.push(up ? { x: cur.x, y: q.y } : { x: q.x, y: cur.y }, q);
|
|
1136
|
+
goVertical = up;
|
|
1137
|
+
}
|
|
1138
|
+
return { kind: "polyline", points: simplified(out) };
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
989
1142
|
function route(e, index) {
|
|
990
1143
|
const c = e.connector;
|
|
991
1144
|
if (!c) return { kind: "polyline", points: [] };
|
|
992
1145
|
const [a, b] = endpoints(e, index);
|
|
1146
|
+
if (c.waypoints && c.waypoints.length > 0) return routeThrough([a, ...c.waypoints.map((w) => ({ x: w.x, y: w.y })), b], c);
|
|
993
1147
|
const dx = b.x - a.x, dy = b.y - a.y;
|
|
994
1148
|
const horizontal = Math.abs(dx) >= Math.abs(dy);
|
|
995
1149
|
switch (c.routing) {
|
|
996
1150
|
case "straight":
|
|
997
1151
|
return { kind: "polyline", points: [a, b] };
|
|
998
1152
|
case "curved":
|
|
999
|
-
if (horizontal) return { kind: "
|
|
1000
|
-
return { kind: "
|
|
1153
|
+
if (horizontal) return { kind: "curve", points: [a, { x: a.x + dx / 2, y: a.y }, { x: b.x - dx / 2, y: b.y }, b] };
|
|
1154
|
+
return { kind: "curve", points: [a, { x: a.x, y: a.y + dy / 2 }, { x: b.x, y: b.y - dy / 2 }, b] };
|
|
1001
1155
|
case "elbow": {
|
|
1002
|
-
const vertical = (p) => p === null ? null : p === "top" || p === "bottom";
|
|
1003
1156
|
const startV = vertical(c.start.port) ?? !horizontal;
|
|
1004
1157
|
const endV = vertical(c.end.port) ?? !horizontal;
|
|
1005
1158
|
if (startV && endV) {
|
|
@@ -1579,10 +1732,12 @@ function dashPattern(dash, width) {
|
|
|
1579
1732
|
return [0.01, Math.max(5, width * 2.6)];
|
|
1580
1733
|
}
|
|
1581
1734
|
}
|
|
1582
|
-
function connectorParts(e, index) {
|
|
1735
|
+
function connectorParts(e, index, page) {
|
|
1583
1736
|
const c = e.connector;
|
|
1584
1737
|
if (!c) return [new Path(), new Path()];
|
|
1585
1738
|
const [a, b] = endpoints(e, index);
|
|
1739
|
+
const r = route(e, index);
|
|
1740
|
+
const hops = c.hops === true && page ? hopCrossings(e, r, page, index) : [];
|
|
1586
1741
|
const key = [
|
|
1587
1742
|
e.seed,
|
|
1588
1743
|
e.style.roughness,
|
|
@@ -1596,28 +1751,166 @@ function connectorParts(e, index) {
|
|
|
1596
1751
|
a.x,
|
|
1597
1752
|
a.y,
|
|
1598
1753
|
b.x,
|
|
1599
|
-
b.y
|
|
1754
|
+
b.y,
|
|
1755
|
+
JSON.stringify(c.waypoints),
|
|
1756
|
+
JSON.stringify(hops)
|
|
1600
1757
|
].join("|");
|
|
1601
|
-
return connectors.get(key, () => computeConnector(e,
|
|
1758
|
+
return connectors.get(key, () => computeConnector(e, r, hops));
|
|
1759
|
+
}
|
|
1760
|
+
function hopRadius(strokeWidth) {
|
|
1761
|
+
return Math.max(8, strokeWidth * 4);
|
|
1602
1762
|
}
|
|
1603
|
-
function
|
|
1763
|
+
function hopSamples(r) {
|
|
1764
|
+
const pts = r.points, out = [];
|
|
1765
|
+
if (r.kind === "polyline") {
|
|
1766
|
+
for (let i = 0; i < pts.length - 1; i++) out.push([pts[i], pts[i + 1]]);
|
|
1767
|
+
return out;
|
|
1768
|
+
}
|
|
1769
|
+
for (let s = 0; s < Math.floor((pts.length - 1) / 3); s++) {
|
|
1770
|
+
const seg = pts.slice(3 * s, 3 * s + 4), samples = [];
|
|
1771
|
+
for (let i = 0; i <= 24; i++) samples.push(cubicPoint(seg, i / 24));
|
|
1772
|
+
out.push(samples);
|
|
1773
|
+
}
|
|
1774
|
+
return out;
|
|
1775
|
+
}
|
|
1776
|
+
function runningLengths(samples) {
|
|
1777
|
+
const out = [0];
|
|
1778
|
+
for (let i = 1; i < samples.length; i++) out.push(out[i - 1] + Math.hypot(samples[i].x - samples[i - 1].x, samples[i].y - samples[i - 1].y));
|
|
1779
|
+
return out;
|
|
1780
|
+
}
|
|
1781
|
+
function cubicPoint(p, t) {
|
|
1782
|
+
const u = 1 - t;
|
|
1783
|
+
return {
|
|
1784
|
+
x: u * u * u * p[0].x + 3 * u * u * t * p[1].x + 3 * u * t * t * p[2].x + t * t * t * p[3].x,
|
|
1785
|
+
y: u * u * u * p[0].y + 3 * u * u * t * p[1].y + 3 * u * t * t * p[2].y + t * t * t * p[3].y
|
|
1786
|
+
};
|
|
1787
|
+
}
|
|
1788
|
+
function subCubic(p, t0, t1) {
|
|
1789
|
+
const split = (q, t) => {
|
|
1790
|
+
const lerp = (a, b) => ({ x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t });
|
|
1791
|
+
const ab = lerp(q[0], q[1]), bc = lerp(q[1], q[2]), cd = lerp(q[2], q[3]);
|
|
1792
|
+
const abc = lerp(ab, bc), bcd = lerp(bc, cd), m = lerp(abc, bcd);
|
|
1793
|
+
return [[q[0], ab, abc, m], [m, bcd, cd, q[3]]];
|
|
1794
|
+
};
|
|
1795
|
+
const left = split(p, t1)[0];
|
|
1796
|
+
return t1 > 0 ? split(left, t0 / t1)[1] : [p[0], p[0], p[0], p[0]];
|
|
1797
|
+
}
|
|
1798
|
+
function hopCrossings(e, r, page, index) {
|
|
1799
|
+
const segments = hopSamples(r);
|
|
1800
|
+
if (segments.length === 0) return [];
|
|
1801
|
+
const visible = new Set(page.layers.filter((l) => l.visible).map((l) => l.id));
|
|
1802
|
+
const beneath = [];
|
|
1803
|
+
for (const o of orderedElements(page)) {
|
|
1804
|
+
if (o.id === e.id) break;
|
|
1805
|
+
if (o.kind !== "connector" || !visible.has(o.layerID)) continue;
|
|
1806
|
+
beneath.push(routePoints(route(o, index)));
|
|
1807
|
+
}
|
|
1808
|
+
const rad = hopRadius(e.style.strokeWidth);
|
|
1809
|
+
const out = [];
|
|
1810
|
+
for (const samples of segments) {
|
|
1811
|
+
const along = runningLengths(samples);
|
|
1812
|
+
const length = along[along.length - 1];
|
|
1813
|
+
const found = [];
|
|
1814
|
+
for (let i = 0; i < samples.length - 1; i++) {
|
|
1815
|
+
const p = samples[i], q = samples[i + 1];
|
|
1816
|
+
const dx = q.x - p.x, dy = q.y - p.y;
|
|
1817
|
+
for (const line2 of beneath) {
|
|
1818
|
+
for (let k = 0; k < line2.length - 1; k++) {
|
|
1819
|
+
const a = line2[k], b = line2[k + 1];
|
|
1820
|
+
const ex = b.x - a.x, ey = b.y - a.y;
|
|
1821
|
+
const det = dx * ey - dy * ex;
|
|
1822
|
+
if (Math.abs(det) < 1e-9) continue;
|
|
1823
|
+
const wx = a.x - p.x, wy = a.y - p.y;
|
|
1824
|
+
const t = (wx * ey - wy * ex) / det, s = (wx * dy - wy * dx) / det;
|
|
1825
|
+
if (t > 0 && t < 1 && s >= 0 && s <= 1) found.push(along[i] + t * (along[i + 1] - along[i]));
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
const kept = [];
|
|
1830
|
+
for (const at of found.sort((x, y) => x - y)) {
|
|
1831
|
+
if (at < rad + 2 || at > length - rad - 2) continue;
|
|
1832
|
+
if (kept.length > 0 && at - kept[kept.length - 1] < 2 * rad + 2) continue;
|
|
1833
|
+
kept.push(at);
|
|
1834
|
+
}
|
|
1835
|
+
out.push(kept);
|
|
1836
|
+
}
|
|
1837
|
+
return out;
|
|
1838
|
+
}
|
|
1839
|
+
function computeConnector(e, r, hops) {
|
|
1604
1840
|
const c = e.connector;
|
|
1605
1841
|
const rng = new SeededRNG(e.seed);
|
|
1606
1842
|
const rough = e.style.roughness, bow = e.style.bowing;
|
|
1607
1843
|
const path = new Path(), solid = new Path();
|
|
1608
|
-
const
|
|
1844
|
+
const w = rough * 3;
|
|
1845
|
+
const j = () => rng.signed() * w;
|
|
1846
|
+
const hop = (s, t, u, rad2) => {
|
|
1847
|
+
let n = { x: u.y, y: -u.x };
|
|
1848
|
+
if (n.y > 1e-9 || Math.abs(n.y) <= 1e-9 && n.x > 0) n = { x: -n.x, y: -n.y };
|
|
1849
|
+
const k = rad2 * 0.5523;
|
|
1850
|
+
const m = { x: (s.x + t.x) / 2, y: (s.y + t.y) / 2 };
|
|
1851
|
+
const top = { x: m.x + n.x * rad2, y: m.y + n.y * rad2 };
|
|
1852
|
+
path.moveTo(s);
|
|
1853
|
+
const a1x = s.x + n.x * k + j(), a1y = s.y + n.y * k + j(), a2x = top.x - u.x * k + j(), a2y = top.y - u.y * k + j();
|
|
1854
|
+
path.curveTo(top, { x: a1x, y: a1y }, { x: a2x, y: a2y });
|
|
1855
|
+
const b1x = top.x + u.x * k + j(), b1y = top.y + u.y * k + j(), b2x = t.x + n.x * k + j(), b2y = t.y + n.y * k + j();
|
|
1856
|
+
path.curveTo(t, { x: b1x, y: b1y }, { x: b2x, y: b2y });
|
|
1857
|
+
};
|
|
1858
|
+
const rad = hopRadius(e.style.strokeWidth);
|
|
1609
1859
|
if (r.kind === "polyline") {
|
|
1610
1860
|
const pts2 = r.points;
|
|
1861
|
+
const straight = (p, q) => {
|
|
1862
|
+
if (rough <= 0) path.moveTo(p).lineTo(q);
|
|
1863
|
+
else line(p, q, rng, rough, bow, true, path);
|
|
1864
|
+
};
|
|
1611
1865
|
for (let i = 0; i < Math.max(pts2.length - 1, 0); i++) {
|
|
1612
|
-
|
|
1613
|
-
|
|
1866
|
+
const p = pts2[i], q = pts2[i + 1];
|
|
1867
|
+
const length = Math.hypot(q.x - p.x, q.y - p.y);
|
|
1868
|
+
const u = length > 0 ? { x: (q.x - p.x) / length, y: (q.y - p.y) / length } : { x: 0, y: 0 };
|
|
1869
|
+
const at = (d) => ({ x: p.x + u.x * d, y: p.y + u.y * d });
|
|
1870
|
+
let from = p;
|
|
1871
|
+
for (const d of i < hops.length ? hops[i] : []) {
|
|
1872
|
+
const s = at(d - rad), t = at(d + rad);
|
|
1873
|
+
straight(from, s);
|
|
1874
|
+
hop(s, t, u, rad);
|
|
1875
|
+
from = t;
|
|
1876
|
+
}
|
|
1877
|
+
straight(from, q);
|
|
1614
1878
|
}
|
|
1615
1879
|
} else {
|
|
1616
|
-
|
|
1617
|
-
const
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1880
|
+
const pts2 = r.points;
|
|
1881
|
+
const samples = hopSamples(r);
|
|
1882
|
+
path.moveTo(pts2[0]);
|
|
1883
|
+
for (let s = 0; s < Math.floor((pts2.length - 1) / 3); s++) {
|
|
1884
|
+
const b = pts2[3 * s + 1], c2 = pts2[3 * s + 2], d = pts2[3 * s + 3];
|
|
1885
|
+
const c1x = b.x + j(), c1y = b.y + j(), c2x = c2.x + j(), c2y = c2.y + j();
|
|
1886
|
+
const here = s < hops.length ? hops[s] : [];
|
|
1887
|
+
if (here.length === 0) {
|
|
1888
|
+
path.curveTo(d, { x: c1x, y: c1y }, { x: c2x, y: c2y });
|
|
1889
|
+
continue;
|
|
1890
|
+
}
|
|
1891
|
+
const cubic = [pts2[3 * s], { x: c1x, y: c1y }, { x: c2x, y: c2y }, d];
|
|
1892
|
+
const along = runningLengths(samples[s]);
|
|
1893
|
+
const param = (at) => {
|
|
1894
|
+
let i = 0;
|
|
1895
|
+
while (i < along.length - 2 && along[i + 1] < at) i++;
|
|
1896
|
+
const span = along[i + 1] - along[i];
|
|
1897
|
+
return (i + (span > 0 ? (at - along[i]) / span : 0)) / 24;
|
|
1898
|
+
};
|
|
1899
|
+
let from = 0;
|
|
1900
|
+
for (const at of here) {
|
|
1901
|
+
const t0 = param(at - rad), t1 = param(at + rad);
|
|
1902
|
+
const piece = subCubic(cubic, from, t0);
|
|
1903
|
+
if (s > 0 || from > 0) path.moveTo(piece[0]);
|
|
1904
|
+
path.curveTo(piece[3], piece[1], piece[2]);
|
|
1905
|
+
const a = cubicPoint(cubic, t0), z = cubicPoint(cubic, t1);
|
|
1906
|
+
const width = Math.hypot(z.x - a.x, z.y - a.y);
|
|
1907
|
+
const u = width > 0 ? { x: (z.x - a.x) / width, y: (z.y - a.y) / width } : { x: 0, y: 0 };
|
|
1908
|
+
hop(a, z, u, width / 2);
|
|
1909
|
+
from = t1;
|
|
1910
|
+
}
|
|
1911
|
+
const rest = subCubic(cubic, from, 1);
|
|
1912
|
+
path.moveTo(rest[0]).curveTo(rest[3], rest[1], rest[2]);
|
|
1913
|
+
}
|
|
1621
1914
|
}
|
|
1622
1915
|
const pts = routePoints(r);
|
|
1623
1916
|
const size = Math.max(10, e.style.strokeWidth * 5);
|
|
@@ -1639,10 +1932,10 @@ function computeConnector(e, index) {
|
|
|
1639
1932
|
solid.moveTo(tip).lineTo(w1).lineTo(w2).close();
|
|
1640
1933
|
break;
|
|
1641
1934
|
case "dot": {
|
|
1642
|
-
const
|
|
1935
|
+
const rad2 = size * 0.32;
|
|
1643
1936
|
const ang = Math.atan2(tip.y - from.y, tip.x - from.x);
|
|
1644
|
-
const cx = tip.x -
|
|
1645
|
-
solid.addEllipse({ x: cx -
|
|
1937
|
+
const cx = tip.x - rad2 * Math.cos(ang), cy = tip.y - rad2 * Math.sin(ang);
|
|
1938
|
+
solid.addEllipse({ x: cx - rad2, y: cy - rad2, width: 2 * rad2, height: 2 * rad2 });
|
|
1646
1939
|
break;
|
|
1647
1940
|
}
|
|
1648
1941
|
case "bar": {
|
|
@@ -1845,17 +2138,20 @@ function trackMap(tl) {
|
|
|
1845
2138
|
for (const t of tl.tracks) if (!m.has(t.element)) m.set(t.element, t);
|
|
1846
2139
|
return m;
|
|
1847
2140
|
}
|
|
1848
|
-
function resolved(page, t) {
|
|
1849
|
-
return expandingSymbols(posed(page, t), clampTime(t));
|
|
2141
|
+
function resolved(page, t, state = "initial") {
|
|
2142
|
+
return expandingSymbols(posed(page, t, state), clampTime(t));
|
|
1850
2143
|
}
|
|
1851
|
-
|
|
1852
|
-
|
|
2144
|
+
var EMPTY_TIMELINE = { fps: 12, duration: 0, boil: null, settle: null, poster: null, tracks: [], camera: null, markers: null };
|
|
2145
|
+
function posed(page, t, state = "initial") {
|
|
2146
|
+
const off = stateHidden(page, state);
|
|
2147
|
+
const timeline = page.timeline ?? (off.size === 0 ? null : EMPTY_TIMELINE);
|
|
1853
2148
|
if (!timeline) return page;
|
|
1854
2149
|
t = clampTime(t);
|
|
1855
2150
|
const tracks = trackMap(timeline);
|
|
1856
2151
|
const reseed = timeline.boil === null ? null : Math.trunc(Math.floor(t * clampFPS(timeline.fps) + 1e-9) / Math.max(1, timeline.boil)) % BOIL_DRAWINGS >>> 0;
|
|
1857
2152
|
const hidden = /* @__PURE__ */ new Set();
|
|
1858
2153
|
for (const tr of tracks.values()) if (!isVisible(tr, t)) hidden.add(tr.element);
|
|
2154
|
+
for (const id of off) hidden.add(id);
|
|
1859
2155
|
let moving = null;
|
|
1860
2156
|
if (timeline.settle === true) {
|
|
1861
2157
|
moving = /* @__PURE__ */ new Set();
|
|
@@ -1894,7 +2190,7 @@ function posed(page, t) {
|
|
|
1894
2190
|
elements = elements.map((e) => {
|
|
1895
2191
|
const c = e.connector;
|
|
1896
2192
|
if (!c || ![c.start.elementID, c.end.elementID].some((id) => id !== null && hidden.has(id))) return e;
|
|
1897
|
-
if (!(tracks.get(e.id)?.keys.some((k) => k.visible !== void 0) ?? false)) {
|
|
2193
|
+
if (!(tracks.get(e.id)?.keys.some((k) => k.visible !== void 0) ?? false) || [c.start.elementID, c.end.elementID].some((id) => id !== null && off.has(id))) {
|
|
1898
2194
|
gone.add(e.id);
|
|
1899
2195
|
return e;
|
|
1900
2196
|
}
|
|
@@ -1910,7 +2206,7 @@ function posterTime(page) {
|
|
|
1910
2206
|
return page.timeline ? page.timeline.poster ?? page.timeline.duration : 0;
|
|
1911
2207
|
}
|
|
1912
2208
|
function still(page) {
|
|
1913
|
-
return page.timeline ? resolved(page, posterTime(page)) : expandingSymbols(page, 0);
|
|
2209
|
+
return page.timeline ? resolved(page, posterTime(page)) : expandingSymbols(posed(page, 0), 0);
|
|
1914
2210
|
}
|
|
1915
2211
|
function cameraRect(tl, t) {
|
|
1916
2212
|
const keys = tl.camera ?? [];
|
|
@@ -2019,7 +2315,7 @@ function filled(s, args) {
|
|
|
2019
2315
|
}) };
|
|
2020
2316
|
}
|
|
2021
2317
|
function symbolPage(s) {
|
|
2022
|
-
return { id: s.id, name: s.name, layers: s.layers, elements: s.elements, theme: null, timeline: s.timeline, symbols: null };
|
|
2318
|
+
return { id: s.id, name: s.name, layers: s.layers, elements: s.elements, theme: null, timeline: s.timeline, symbols: null, variables: null };
|
|
2023
2319
|
}
|
|
2024
2320
|
function hasInstances(page) {
|
|
2025
2321
|
return page.elements.some((e) => e.kind === "symbol");
|
|
@@ -2109,7 +2405,12 @@ function placed(inner, instance, symbol) {
|
|
|
2109
2405
|
const q = point(p);
|
|
2110
2406
|
return { elementID: null, port: p.port, x: q.x, y: q.y };
|
|
2111
2407
|
};
|
|
2112
|
-
e.connector = {
|
|
2408
|
+
e.connector = {
|
|
2409
|
+
...inner.connector,
|
|
2410
|
+
start: end(inner.connector.start),
|
|
2411
|
+
end: end(inner.connector.end),
|
|
2412
|
+
waypoints: inner.connector.waypoints?.map(point) ?? null
|
|
2413
|
+
};
|
|
2113
2414
|
}
|
|
2114
2415
|
return e;
|
|
2115
2416
|
}
|
|
@@ -2143,16 +2444,21 @@ function clickTargets(page, file) {
|
|
|
2143
2444
|
let pageOf = null;
|
|
2144
2445
|
for (const e of page.elements) {
|
|
2145
2446
|
const c = e.click;
|
|
2447
|
+
const state = c && changesState(c) ? { ...c.set ? { set: c.set } : {}, ...c.toggle ? { toggle: c.toggle } : {} } : null;
|
|
2146
2448
|
if (c?.scene) {
|
|
2147
2449
|
const i = sceneNamed(c.scene, markers);
|
|
2148
2450
|
if (i >= 0) {
|
|
2149
|
-
out.set(e.id, { scene: i });
|
|
2451
|
+
out.set(e.id, { scene: i, ...state });
|
|
2150
2452
|
continue;
|
|
2151
2453
|
}
|
|
2152
2454
|
}
|
|
2153
2455
|
const url = (c?.url ? allowedURL(c.url) : null) ?? webLink(e.url);
|
|
2154
2456
|
if (url) {
|
|
2155
|
-
out.set(e.id, { url });
|
|
2457
|
+
out.set(e.id, { url, ...state });
|
|
2458
|
+
continue;
|
|
2459
|
+
}
|
|
2460
|
+
if (state) {
|
|
2461
|
+
out.set(e.id, { state: true, ...state });
|
|
2156
2462
|
continue;
|
|
2157
2463
|
}
|
|
2158
2464
|
if (e.link && file && file.pages.length > 1) {
|
|
@@ -2885,7 +3191,7 @@ function svgParts(page, background, fixed, links = false) {
|
|
|
2885
3191
|
if (!visible.has(e.layerID)) continue;
|
|
2886
3192
|
const alpha = Math.min(Math.max(e.style.opacity ?? 1, 0), 1);
|
|
2887
3193
|
if (alpha <= 0) continue;
|
|
2888
|
-
let body = svgElement(e, index, bg);
|
|
3194
|
+
let body = svgElement(e, index, bg, d);
|
|
2889
3195
|
if (angle(e) !== 0 && e.kind !== "connector") {
|
|
2890
3196
|
body = `<g transform="rotate(${fmt(angle(e) * 180 / Math.PI)} ${fmt(midX(frame(e)))} ${fmt(midY(frame(e)))})">
|
|
2891
3197
|
${body}</g>
|
|
@@ -2920,7 +3226,7 @@ function typed(s, progress) {
|
|
|
2920
3226
|
function revealProgress(e) {
|
|
2921
3227
|
return e.reveal !== null && e.reveal < 1 ? Math.max(0, e.reveal) : null;
|
|
2922
3228
|
}
|
|
2923
|
-
function svgElement(e, index, labelBackground) {
|
|
3229
|
+
function svgElement(e, index, labelBackground, page) {
|
|
2924
3230
|
const stroke = color(e.style.stroke);
|
|
2925
3231
|
let strokeAttrs = `stroke="${stroke}" stroke-width="${fmt(e.style.strokeWidth)}" stroke-linecap="round" stroke-linejoin="round"`;
|
|
2926
3232
|
const dash = dashPattern(e.style.dash, e.style.strokeWidth);
|
|
@@ -2931,7 +3237,7 @@ function svgElement(e, index, labelBackground) {
|
|
|
2931
3237
|
if (r !== null) {
|
|
2932
3238
|
if (e.kind === "text") text = typed(text ?? "", r);
|
|
2933
3239
|
else if (e.kind === "connector") {
|
|
2934
|
-
return `<path d="${connectorParts(e, index)[0].trimmed(r).data()}" fill="none" ${strokeAttrs}/>
|
|
3240
|
+
return `<path d="${connectorParts(e, index, page)[0].trimmed(r).data()}" fill="none" ${strokeAttrs}/>
|
|
2935
3241
|
`;
|
|
2936
3242
|
} else {
|
|
2937
3243
|
return `<path d="${outline(e).trimmed(r).data()}" fill="none" ${strokeAttrs}/>
|
|
@@ -2940,7 +3246,7 @@ function svgElement(e, index, labelBackground) {
|
|
|
2940
3246
|
}
|
|
2941
3247
|
switch (e.kind) {
|
|
2942
3248
|
case "connector": {
|
|
2943
|
-
const [line2, filled2] = connectorParts(e, index);
|
|
3249
|
+
const [line2, filled2] = connectorParts(e, index, page);
|
|
2944
3250
|
let s = `<path d="${line2.data()}" fill="none" ${strokeAttrs}/>
|
|
2945
3251
|
`;
|
|
2946
3252
|
if (!filled2.isEmpty) s += `<path d="${filled2.data()}" fill="${stroke}" stroke="none"/>
|
|
@@ -3023,7 +3329,7 @@ function textElement(text, f, e, centered) {
|
|
|
3023
3329
|
const total = lineHeight * lines.length;
|
|
3024
3330
|
const x = centered ? midX(f) : f.x;
|
|
3025
3331
|
const top = centered ? midY(f) - total / 2 : f.y;
|
|
3026
|
-
let s = `<text x="${fmt(x)}" font-family="${cssFamily(e.style.font)}" font-size="${fmt(size)}" fill="${color(e
|
|
3332
|
+
let s = `<text x="${fmt(x)}" font-family="${cssFamily(e.style.font)}" font-size="${fmt(size)}" fill="${color(labelColor(e))}"${centered ? ` text-anchor="middle"` : ""}>`;
|
|
3027
3333
|
lines.forEach((line2, i) => {
|
|
3028
3334
|
s += `<tspan x="${fmt(x)}" y="${fmt(top + lineHeight * (i + 0.8))}">${escapeXML(line2)}</tspan>`;
|
|
3029
3335
|
});
|
|
@@ -3066,6 +3372,10 @@ var Playback = class {
|
|
|
3066
3372
|
__publicField(this, "plan");
|
|
3067
3373
|
__publicField(this, "scenes");
|
|
3068
3374
|
__publicField(this, "sceneNames");
|
|
3375
|
+
/** Whether each scene loops (Marker.loop). */
|
|
3376
|
+
__publicField(this, "sceneLoops");
|
|
3377
|
+
/** The page's variables now: their starting values until clicks change them. */
|
|
3378
|
+
__publicField(this, "state");
|
|
3069
3379
|
__publicField(this, "background");
|
|
3070
3380
|
__publicField(this, "ink");
|
|
3071
3381
|
/** What clicking each clickable element does, by element id. */
|
|
@@ -3125,6 +3435,8 @@ var Playback = class {
|
|
|
3125
3435
|
};
|
|
3126
3436
|
this.scenes = (page.timeline?.markers ?? []).map((m) => near(m.t));
|
|
3127
3437
|
this.sceneNames = (page.timeline?.markers ?? []).map((m) => m.name);
|
|
3438
|
+
this.sceneLoops = (page.timeline?.markers ?? []).map((m) => m.loop === true);
|
|
3439
|
+
this.state = { ...page.variables ?? {} };
|
|
3128
3440
|
const theme = themeNamed(page.theme);
|
|
3129
3441
|
this.background = theme?.background ?? WHITE;
|
|
3130
3442
|
this.ink = theme?.ink ?? { r: 0.12, g: 0.12, b: 0.13, a: 1 };
|
|
@@ -3175,7 +3487,7 @@ var Playback = class {
|
|
|
3175
3487
|
const times = /* @__PURE__ */ new Map();
|
|
3176
3488
|
for (const [id, start] of this.replayStarts) times.set(id, Math.min((now - start) / 1e3, this.replays.get(id) ?? 0));
|
|
3177
3489
|
const tc = clampTime(t);
|
|
3178
|
-
const pose = posed(this.page, tc);
|
|
3490
|
+
const pose = posed(this.page, tc, this.state);
|
|
3179
3491
|
const frame2 = expandingSymbols(pose, tc, true, times);
|
|
3180
3492
|
const { parts } = svgParts(frame2, this.background, r);
|
|
3181
3493
|
const owned = (m, parts2) => {
|
|
@@ -3297,6 +3609,20 @@ ${h}</g>
|
|
|
3297
3609
|
});
|
|
3298
3610
|
return s;
|
|
3299
3611
|
}
|
|
3612
|
+
/** The frame after scene `s`'s last: the next scene's, or the frame count after the last scene. */
|
|
3613
|
+
sceneEnd(s) {
|
|
3614
|
+
return this.scenes.find((f, j) => j > s && f > this.scenes[s]) ?? this.frameCount;
|
|
3615
|
+
}
|
|
3616
|
+
/** Where playing goes from frame `i`, and whether it stops there: on into a looping scene, round to the start of
|
|
3617
|
+
* the looping scene `i` ends, or a stop at a scene or the end (`loops`: whether the timeline loops at its end). */
|
|
3618
|
+
step(i, loops) {
|
|
3619
|
+
const next = i + 1;
|
|
3620
|
+
const s = this.sceneAt(i);
|
|
3621
|
+
if (s >= 0 && this.sceneLoops[s] && next >= this.sceneEnd(s)) return { frame: this.scenes[s], stop: false };
|
|
3622
|
+
if (next >= this.frameCount) return { frame: 0, stop: !loops || this.scenes.length > 0 };
|
|
3623
|
+
const entered = this.scenes.indexOf(next);
|
|
3624
|
+
return { frame: next, stop: entered >= 0 && !this.sceneLoops[entered] };
|
|
3625
|
+
}
|
|
3300
3626
|
};
|
|
3301
3627
|
var ElementBase = typeof HTMLElement === "undefined" ? class {
|
|
3302
3628
|
} : HTMLElement;
|
|
@@ -3450,7 +3776,7 @@ var DottiePlayer = class extends ElementBase {
|
|
|
3450
3776
|
this.setPlaying(false);
|
|
3451
3777
|
return;
|
|
3452
3778
|
}
|
|
3453
|
-
this.setPlaying(!this.hasAttribute("no-autoplay") && !p.scenes.includes(0));
|
|
3779
|
+
this.setPlaying(!this.hasAttribute("no-autoplay") && (!p.scenes.includes(0) || p.sceneLoops[p.scenes.indexOf(0)]));
|
|
3454
3780
|
}
|
|
3455
3781
|
// MARK: Playback
|
|
3456
3782
|
get paused() {
|
|
@@ -3476,11 +3802,13 @@ var DottiePlayer = class extends ElementBase {
|
|
|
3476
3802
|
pause() {
|
|
3477
3803
|
this.setPlaying(false);
|
|
3478
3804
|
}
|
|
3479
|
-
/** Plays on to the next scene (from the start after the last). */
|
|
3805
|
+
/** Plays on to the next scene (from the start after the last). Out of a looping scene it jumps to the next. */
|
|
3480
3806
|
nextScene() {
|
|
3481
3807
|
const p = this.playback;
|
|
3482
3808
|
if (!p) return;
|
|
3483
|
-
|
|
3809
|
+
const s = p.sceneAt(p.frame);
|
|
3810
|
+
if (s >= 0 && p.sceneLoops[s]) p.show(s + 1 < p.scenes.length ? p.scenes[s + 1] : 0);
|
|
3811
|
+
else if (p.frame >= p.frameCount - 1) p.show(0);
|
|
3484
3812
|
this.setPlaying(true);
|
|
3485
3813
|
}
|
|
3486
3814
|
/** Back to the start of this scene, or the one before if already there. */
|
|
@@ -3509,6 +3837,8 @@ var DottiePlayer = class extends ElementBase {
|
|
|
3509
3837
|
this.scheduleReplay();
|
|
3510
3838
|
}
|
|
3511
3839
|
if (!click) return;
|
|
3840
|
+
if (click.set || click.toggle) this.setState(applyClick({ scene: null, url: null, set: click.set ?? null, toggle: click.toggle ?? null }, p.state));
|
|
3841
|
+
if ("state" in click) return;
|
|
3512
3842
|
if ("url" in click) {
|
|
3513
3843
|
if (/^(https?:|mailto:)/i.test(click.url)) window.open(click.url, "_blank", "noopener");
|
|
3514
3844
|
} else if ("page" in click) {
|
|
@@ -3516,13 +3846,27 @@ var DottiePlayer = class extends ElementBase {
|
|
|
3516
3846
|
} else if (click.scene >= 0 && click.scene < p.scenes.length) {
|
|
3517
3847
|
if (reduced || this.hasAttribute("at") || p.frameCount < 2) {
|
|
3518
3848
|
this.setPlaying(false);
|
|
3519
|
-
p.show(click.scene + 1 < p.scenes.length ? p.scenes[click.scene + 1] : p.frameCount - 1);
|
|
3849
|
+
p.show(p.sceneLoops[click.scene] ? p.scenes[click.scene] : click.scene + 1 < p.scenes.length ? p.scenes[click.scene + 1] : p.frameCount - 1);
|
|
3520
3850
|
} else {
|
|
3521
3851
|
p.show(p.scenes[click.scene]);
|
|
3522
3852
|
this.setPlaying(true);
|
|
3523
3853
|
}
|
|
3524
3854
|
}
|
|
3525
3855
|
}
|
|
3856
|
+
/** The page's variables now. */
|
|
3857
|
+
get variables() {
|
|
3858
|
+
return { ...this.playback?.state ?? {} };
|
|
3859
|
+
}
|
|
3860
|
+
/** Gives the page's variables these values (the others keep theirs), redraws, and fires `state`. */
|
|
3861
|
+
setState(values) {
|
|
3862
|
+
const p = this.playback;
|
|
3863
|
+
if (!p) return;
|
|
3864
|
+
const next = { ...p.state, ...values };
|
|
3865
|
+
if (Object.keys(next).every((k) => next[k] === p.state[k])) return;
|
|
3866
|
+
p.state = next;
|
|
3867
|
+
p.redraw();
|
|
3868
|
+
this.dispatchEvent(new CustomEvent("state", { detail: { variables: { ...next } } }));
|
|
3869
|
+
}
|
|
3526
3870
|
/** Redraws, while the page itself stands still, as long as a clicked copy is playing through. */
|
|
3527
3871
|
scheduleReplay() {
|
|
3528
3872
|
if (this.replayRaf) return;
|
|
@@ -3548,7 +3892,8 @@ var DottiePlayer = class extends ElementBase {
|
|
|
3548
3892
|
schedule() {
|
|
3549
3893
|
if (this.playing && this.visible && !this.raf && this.isConnected) this.raf = requestAnimationFrame((now) => this.tick(now));
|
|
3550
3894
|
}
|
|
3551
|
-
/** Steps whole frames at the timeline's rate; with scenes, playing stops at each one and at the end
|
|
3895
|
+
/** Steps whole frames at the timeline's rate; with scenes, playing stops at each one and at the end, except that a
|
|
3896
|
+
* looping scene plays round (Playback.step). */
|
|
3552
3897
|
tick(now) {
|
|
3553
3898
|
this.raf = 0;
|
|
3554
3899
|
const p = this.playback;
|
|
@@ -3559,14 +3904,14 @@ var DottiePlayer = class extends ElementBase {
|
|
|
3559
3904
|
let advanced = false;
|
|
3560
3905
|
while (this.playing && this.acc >= step) {
|
|
3561
3906
|
this.acc -= step;
|
|
3562
|
-
const
|
|
3563
|
-
if (
|
|
3907
|
+
const { frame: frame2, stop } = p.step(p.frame, !this.hasAttribute("no-loop"));
|
|
3908
|
+
if (stop && frame2 === 0 && p.frame + 1 >= p.frameCount) {
|
|
3564
3909
|
this.setPlaying(false);
|
|
3565
3910
|
break;
|
|
3566
3911
|
}
|
|
3567
|
-
p.frame =
|
|
3912
|
+
p.frame = frame2;
|
|
3568
3913
|
advanced = true;
|
|
3569
|
-
if (
|
|
3914
|
+
if (stop) {
|
|
3570
3915
|
this.setPlaying(false);
|
|
3571
3916
|
break;
|
|
3572
3917
|
}
|
|
@@ -3672,6 +4017,7 @@ function inspectFrame(page, t) {
|
|
|
3672
4017
|
d.strokeWidth = r4(e.style.strokeWidth);
|
|
3673
4018
|
d.stroke = rgba2(e.style.stroke);
|
|
3674
4019
|
d.fill = rgba2(e.style.fill);
|
|
4020
|
+
if (e.style.textColor !== null) d.textColor = rgba2(e.style.textColor);
|
|
3675
4021
|
if (e.kind === "connector") {
|
|
3676
4022
|
const [a, b] = endpoints(e, index);
|
|
3677
4023
|
d.start = pt(a);
|
|
@@ -3715,7 +4061,7 @@ function render(source, options = {}) {
|
|
|
3715
4061
|
function isFile(v) {
|
|
3716
4062
|
return typeof v === "object" && v !== null && Array.isArray(v.pages) && Array.isArray(v.warnings);
|
|
3717
4063
|
}
|
|
3718
|
-
var VERSION = "0.
|
|
4064
|
+
var VERSION = "0.7.0";
|
|
3719
4065
|
export {
|
|
3720
4066
|
BOIL_DRAWINGS,
|
|
3721
4067
|
BUNDLED_FONTS,
|