@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-player.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
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 __export = (target, all) => {
|
|
@@ -66,17 +66,71 @@ __export(index_exports, {
|
|
|
66
66
|
withRoughness: () => withRoughness
|
|
67
67
|
});
|
|
68
68
|
|
|
69
|
+
// src/state.ts
|
|
70
|
+
var MAX_NAME = 64;
|
|
71
|
+
function isName(name) {
|
|
72
|
+
return name.length <= MAX_NAME && /^[A-Za-z_][A-Za-z0-9_]*$/.test(name);
|
|
73
|
+
}
|
|
74
|
+
function conditionNames(condition) {
|
|
75
|
+
const out = [];
|
|
76
|
+
for (const any of condition.split("||")) {
|
|
77
|
+
for (const all of any.split("&&")) {
|
|
78
|
+
let term = trim(all);
|
|
79
|
+
if (term.startsWith("!")) term = trim(term.slice(1));
|
|
80
|
+
if (!isName(term)) return null;
|
|
81
|
+
out.push(term);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return out;
|
|
85
|
+
}
|
|
86
|
+
function holds(condition, state) {
|
|
87
|
+
if (conditionNames(condition) === null) return false;
|
|
88
|
+
return condition.split("||").some((any) => any.split("&&").every((all) => {
|
|
89
|
+
let term = trim(all);
|
|
90
|
+
let negated = false;
|
|
91
|
+
if (term.startsWith("!")) {
|
|
92
|
+
negated = true;
|
|
93
|
+
term = trim(term.slice(1));
|
|
94
|
+
}
|
|
95
|
+
return (state[term] ?? false) !== negated;
|
|
96
|
+
}));
|
|
97
|
+
}
|
|
98
|
+
function stateValues(page, choice) {
|
|
99
|
+
if (choice === "all") return null;
|
|
100
|
+
if (choice === "initial") return { ...page.variables ?? {} };
|
|
101
|
+
return { ...page.variables ?? {}, ...choice };
|
|
102
|
+
}
|
|
103
|
+
function stateHidden(page, choice) {
|
|
104
|
+
const values = stateValues(page, choice);
|
|
105
|
+
const out = /* @__PURE__ */ new Set();
|
|
106
|
+
if (!values) return out;
|
|
107
|
+
for (const e of page.elements) if (e.when !== null && !holds(e.when, values)) out.add(e.id);
|
|
108
|
+
return out;
|
|
109
|
+
}
|
|
110
|
+
function changesState(c) {
|
|
111
|
+
return !!c && (Object.keys(c.set ?? {}).length > 0 || (c.toggle ?? []).length > 0);
|
|
112
|
+
}
|
|
113
|
+
function applyClick(c, state) {
|
|
114
|
+
const out = { ...state };
|
|
115
|
+
for (const [k, v] of Object.entries(c.set ?? {})) out[k] = v;
|
|
116
|
+
for (const k of c.toggle ?? []) out[k] = !(out[k] ?? false);
|
|
117
|
+
return out;
|
|
118
|
+
}
|
|
119
|
+
function trim(s) {
|
|
120
|
+
return s.replace(/^[ \t ]+|[ \t ]+$/g, "");
|
|
121
|
+
}
|
|
122
|
+
|
|
69
123
|
// src/model.ts
|
|
70
124
|
var SUPPORTED_VERSION = 2;
|
|
71
125
|
var KINDS = ["rectangle", "ellipse", "diamond", "text", "connector", "freehand", "path", "note", "image", "symbol"];
|
|
72
126
|
var EASES = ["linear", "easeIn", "easeOut", "easeInOut", "hold", "spring", "bounce", "elastic"];
|
|
73
127
|
var KNOWN_FIELDS = {
|
|
74
128
|
file: ["version", "pages", "images"],
|
|
75
|
-
page: ["id", "name", "layers", "elements", "theme", "guidesX", "guidesY", "guidesLocked", "timeline", "symbols"],
|
|
129
|
+
page: ["id", "name", "layers", "elements", "theme", "guidesX", "guidesY", "guidesLocked", "timeline", "symbols", "variables"],
|
|
76
130
|
symbol: ["id", "name", "width", "height", "layers", "elements", "timeline", "button", "params"],
|
|
77
131
|
layer: ["id", "name", "visible", "locked"],
|
|
78
132
|
rgba: ["r", "g", "b", "a"],
|
|
79
|
-
style: ["stroke", "fill", "fillStyle", "strokeWidth", "dash", "roughness", "bowing", "fontSize", "font", "gridSize", "opacity", "dashOffset"],
|
|
133
|
+
style: ["stroke", "fill", "fillStyle", "strokeWidth", "dash", "roughness", "bowing", "fontSize", "font", "gridSize", "opacity", "dashOffset", "textColor"],
|
|
80
134
|
element: [
|
|
81
135
|
"id",
|
|
82
136
|
"kind",
|
|
@@ -100,6 +154,7 @@ var KNOWN_FIELDS = {
|
|
|
100
154
|
"anchor",
|
|
101
155
|
"click",
|
|
102
156
|
"hover",
|
|
157
|
+
"when",
|
|
103
158
|
"symbol",
|
|
104
159
|
"playback",
|
|
105
160
|
"offset",
|
|
@@ -117,15 +172,16 @@ var KNOWN_FIELDS = {
|
|
|
117
172
|
extensionRef: ["id", "shape", "version", "params"],
|
|
118
173
|
normalizedPoint: ["x", "y"],
|
|
119
174
|
normalizedRect: ["x", "y", "width", "height"],
|
|
120
|
-
connector: ["start", "end", "routing", "startHead", "endHead"],
|
|
175
|
+
connector: ["start", "end", "routing", "startHead", "endHead", "waypoints", "hops"],
|
|
176
|
+
waypoint: ["x", "y"],
|
|
121
177
|
timeline: ["fps", "duration", "boil", "settle", "poster", "tracks", "camera", "markers"],
|
|
122
178
|
track: ["element", "keys", "boil", "follow", "repeats", "march"],
|
|
123
179
|
keyframe: ["t", "x", "y", "width", "height", "rotation", "strokeWidth", "stroke", "fill", "visible", "reveal", "opacity", "ease"],
|
|
124
180
|
follow: ["path", "start", "end", "ease", "reverse", "orient", "laps"],
|
|
125
181
|
repeat: ["start", "end", "count"],
|
|
126
182
|
cameraKey: ["t", "x", "y", "width", "height", "ease"],
|
|
127
|
-
marker: ["t", "name"],
|
|
128
|
-
click: ["scene", "url"],
|
|
183
|
+
marker: ["t", "name", "loop"],
|
|
184
|
+
click: ["scene", "url", "set", "toggle"],
|
|
129
185
|
hover: ["stroke", "fill", "opacity"]
|
|
130
186
|
};
|
|
131
187
|
var MAX_DURATION = 3600;
|
|
@@ -199,7 +255,8 @@ var Reader = class {
|
|
|
199
255
|
font: str(s.font) || null,
|
|
200
256
|
gridSize: num(s.gridSize) ?? null,
|
|
201
257
|
opacity: num(s.opacity) ?? null,
|
|
202
|
-
dashOffset: num(s.dashOffset) ?? null
|
|
258
|
+
dashOffset: num(s.dashOffset) ?? null,
|
|
259
|
+
textColor: rgba(s.textColor) ?? null
|
|
203
260
|
};
|
|
204
261
|
}
|
|
205
262
|
endpoint(v) {
|
|
@@ -227,7 +284,12 @@ var Reader = class {
|
|
|
227
284
|
end: this.endpoint(v.end),
|
|
228
285
|
routing: routing && ["straight", "curved", "elbow"].includes(routing) ? routing : "straight",
|
|
229
286
|
startHead: head(v.startHead, "none"),
|
|
230
|
-
endHead: head(v.endHead, "arrow")
|
|
287
|
+
endHead: head(v.endHead, "arrow"),
|
|
288
|
+
waypoints: Array.isArray(v.waypoints) ? v.waypoints.filter(isObj).map((w) => {
|
|
289
|
+
this.fields(w, "waypoint");
|
|
290
|
+
return { x: num(w.x) ?? 0, y: num(w.y) ?? 0 };
|
|
291
|
+
}) : null,
|
|
292
|
+
hops: typeof v.hops === "boolean" ? v.hops : null
|
|
231
293
|
};
|
|
232
294
|
}
|
|
233
295
|
image(v, table) {
|
|
@@ -278,6 +340,7 @@ var Reader = class {
|
|
|
278
340
|
anchor: str(v.anchor) ?? null,
|
|
279
341
|
click: this.click(v.click),
|
|
280
342
|
hover: this.hover(v.hover),
|
|
343
|
+
when: str(v.when) ?? null,
|
|
281
344
|
symbol: uuid(v.symbol) ?? null,
|
|
282
345
|
playback: this.playback(v.playback),
|
|
283
346
|
offset: num(v.offset) ?? null,
|
|
@@ -313,13 +376,27 @@ var Reader = class {
|
|
|
313
376
|
}
|
|
314
377
|
return { id, shape, version, params };
|
|
315
378
|
}
|
|
379
|
+
variables(v) {
|
|
380
|
+
if (!isObj(v)) return null;
|
|
381
|
+
const out = {};
|
|
382
|
+
for (const [k, b] of Object.entries(v)) if (typeof b === "boolean") out[k] = b;
|
|
383
|
+
return Object.keys(out).length > 0 ? out : null;
|
|
384
|
+
}
|
|
316
385
|
click(v) {
|
|
317
386
|
if (!isObj(v)) return null;
|
|
318
387
|
this.fields(v, "click");
|
|
319
388
|
const url = str(v.url);
|
|
320
389
|
const safe = url === void 0 ? null : allowedURL(url);
|
|
321
390
|
if (url !== void 0 && safe === null) this.warn("Skipped a click link that isn't http, https or mailto");
|
|
322
|
-
|
|
391
|
+
let set = null;
|
|
392
|
+
if (isObj(v.set)) {
|
|
393
|
+
set = {};
|
|
394
|
+
for (const [k, b] of Object.entries(v.set)) if (typeof b === "boolean" && isName(k)) set[k] = b;
|
|
395
|
+
if (Object.keys(set).length === 0) set = null;
|
|
396
|
+
}
|
|
397
|
+
const raw = typeof v.toggle === "string" ? [v.toggle] : Array.isArray(v.toggle) && v.toggle.every((x) => typeof x === "string") ? v.toggle : null;
|
|
398
|
+
const toggle = raw ? raw.filter(isName) : null;
|
|
399
|
+
return { scene: str(v.scene) ?? null, url: safe, set, toggle: toggle && toggle.length > 0 ? toggle : null };
|
|
323
400
|
}
|
|
324
401
|
playback(v) {
|
|
325
402
|
const s = str(v);
|
|
@@ -420,7 +497,7 @@ var Reader = class {
|
|
|
420
497
|
if (camera) stableSortBy(camera, (c) => c.t);
|
|
421
498
|
const markers = Array.isArray(v.markers) ? v.markers.filter(isObj).flatMap((m) => {
|
|
422
499
|
const t = num(m.t);
|
|
423
|
-
return t !== void 0 && t >= 0 ? [{ t, name: str(m.name) ?? "" }] : [];
|
|
500
|
+
return t !== void 0 && t >= 0 ? [{ t, name: str(m.name) ?? "", loop: bool(m.loop) ?? null }] : [];
|
|
424
501
|
}) : null;
|
|
425
502
|
if (markers) stableSortBy(markers, (m) => m.t);
|
|
426
503
|
const poster = num(v.poster);
|
|
@@ -453,7 +530,8 @@ var Reader = class {
|
|
|
453
530
|
elements: (Array.isArray(v.elements) ? v.elements : []).map((e) => this.element(e, images)).filter((e) => e !== null),
|
|
454
531
|
theme: str(v.theme) ?? null,
|
|
455
532
|
timeline: this.timeline(v.timeline),
|
|
456
|
-
symbols: Array.isArray(v.symbols) ? v.symbols.map((x) => this.symbol(x, images)).filter((x) => x !== null) : null
|
|
533
|
+
symbols: Array.isArray(v.symbols) ? v.symbols.map((x) => this.symbol(x, images)).filter((x) => x !== null) : null,
|
|
534
|
+
variables: this.variables(v.variables)
|
|
457
535
|
};
|
|
458
536
|
}
|
|
459
537
|
};
|
|
@@ -504,8 +582,18 @@ function isAutomaticInk(c) {
|
|
|
504
582
|
function cloneElement(e) {
|
|
505
583
|
return {
|
|
506
584
|
...e,
|
|
507
|
-
style: {
|
|
508
|
-
|
|
585
|
+
style: {
|
|
586
|
+
...e.style,
|
|
587
|
+
stroke: { ...e.style.stroke },
|
|
588
|
+
fill: { ...e.style.fill },
|
|
589
|
+
textColor: e.style.textColor ? { ...e.style.textColor } : null
|
|
590
|
+
},
|
|
591
|
+
connector: e.connector ? {
|
|
592
|
+
...e.connector,
|
|
593
|
+
start: { ...e.connector.start },
|
|
594
|
+
end: { ...e.connector.end },
|
|
595
|
+
waypoints: e.connector.waypoints?.map((w) => ({ ...w })) ?? null
|
|
596
|
+
} : null
|
|
509
597
|
};
|
|
510
598
|
}
|
|
511
599
|
function orderedElements(page) {
|
|
@@ -935,13 +1023,19 @@ function rotatedBounds(e) {
|
|
|
935
1023
|
return { x, y, width: Math.max(...xs) - x, height: Math.max(...ys) - y };
|
|
936
1024
|
}
|
|
937
1025
|
function resolvingInk(e, ink) {
|
|
938
|
-
|
|
1026
|
+
const text = e.style.textColor !== null && isAutomaticInk(e.style.textColor);
|
|
1027
|
+
if (!isAutomaticInk(e.style.stroke) && !text) return e;
|
|
939
1028
|
const copy = cloneElement(e);
|
|
940
1029
|
const covers = e.style.fillStyle === "solid" || e.style.fillStyle === "grid";
|
|
941
1030
|
const onLightFill = covers && e.style.fill.a >= 0.5 && luminance(e.style.fill) > 0.5;
|
|
942
|
-
|
|
1031
|
+
const resolved2 = luminance(ink) > 0.5 && onLightFill ? black : ink;
|
|
1032
|
+
if (isAutomaticInk(e.style.stroke)) copy.style.stroke = { ...resolved2 };
|
|
1033
|
+
if (text) copy.style.textColor = { ...resolved2 };
|
|
943
1034
|
return copy;
|
|
944
1035
|
}
|
|
1036
|
+
function labelColor(e) {
|
|
1037
|
+
return e.kind === "text" || e.kind === "note" ? e.style.stroke : e.style.textColor ?? e.style.stroke;
|
|
1038
|
+
}
|
|
945
1039
|
function shapeContours(e) {
|
|
946
1040
|
if (e.kind !== "path" || !e.subpaths) return [];
|
|
947
1041
|
const out = [];
|
|
@@ -1035,34 +1129,93 @@ function resolve(ep, other, index) {
|
|
|
1035
1129
|
function endpoints(e, index) {
|
|
1036
1130
|
const c = e.connector;
|
|
1037
1131
|
if (!c) return [{ x: 0, y: 0 }, { x: 0, y: 0 }];
|
|
1038
|
-
|
|
1132
|
+
const w = c.waypoints ?? [];
|
|
1133
|
+
return [
|
|
1134
|
+
resolve(c.start, w.length > 0 ? w[0] : reference(c.end, index), index),
|
|
1135
|
+
resolve(c.end, w.length > 0 ? w[w.length - 1] : reference(c.start, index), index)
|
|
1136
|
+
];
|
|
1039
1137
|
}
|
|
1040
1138
|
function routePoints(r) {
|
|
1041
1139
|
if (r.kind === "polyline") return r.points;
|
|
1042
|
-
const out = [];
|
|
1043
|
-
for (let
|
|
1044
|
-
const
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1140
|
+
const out = [], p = r.points;
|
|
1141
|
+
for (let s = 0; s < Math.floor((p.length - 1) / 3); s++) {
|
|
1142
|
+
const a = p[3 * s], b = p[3 * s + 1], c = p[3 * s + 2], d = p[3 * s + 3];
|
|
1143
|
+
for (let i = s === 0 ? 0 : 1; i <= 24; i++) {
|
|
1144
|
+
const t = i / 24, u = 1 - t;
|
|
1145
|
+
out.push({
|
|
1146
|
+
x: u * u * u * a.x + 3 * u * u * t * b.x + 3 * u * t * t * c.x + t * t * t * d.x,
|
|
1147
|
+
y: u * u * u * a.y + 3 * u * u * t * b.y + 3 * u * t * t * c.y + t * t * t * d.y
|
|
1148
|
+
});
|
|
1149
|
+
}
|
|
1049
1150
|
}
|
|
1050
1151
|
return out;
|
|
1051
1152
|
}
|
|
1153
|
+
var vertical = (p) => p === null ? null : p === "top" || p === "bottom";
|
|
1154
|
+
function simplified(pts) {
|
|
1155
|
+
const out = [];
|
|
1156
|
+
for (const q of pts) {
|
|
1157
|
+
const l = out[out.length - 1];
|
|
1158
|
+
if (l && Math.abs(l.x - q.x) < 0.5 && Math.abs(l.y - q.y) < 0.5) continue;
|
|
1159
|
+
if (out.length >= 2) {
|
|
1160
|
+
const p = out[out.length - 2], m = out[out.length - 1];
|
|
1161
|
+
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) {
|
|
1162
|
+
out[out.length - 1] = q;
|
|
1163
|
+
continue;
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
out.push(q);
|
|
1167
|
+
}
|
|
1168
|
+
return out.length >= 2 ? out : pts;
|
|
1169
|
+
}
|
|
1170
|
+
function routeThrough(p, c) {
|
|
1171
|
+
switch (c.routing) {
|
|
1172
|
+
case "straight":
|
|
1173
|
+
return { kind: "polyline", points: p };
|
|
1174
|
+
case "curved": {
|
|
1175
|
+
const out = [p[0]];
|
|
1176
|
+
for (let i = 0; i < p.length - 1; i++) {
|
|
1177
|
+
const p0 = p[Math.max(i - 1, 0)], p1 = p[i], p2 = p[i + 1], p3 = p[Math.min(i + 2, p.length - 1)];
|
|
1178
|
+
out.push({ x: p1.x + (p2.x - p0.x) / 6, y: p1.y + (p2.y - p0.y) / 6 });
|
|
1179
|
+
out.push({ x: p2.x - (p3.x - p1.x) / 6, y: p2.y - (p3.y - p1.y) / 6 });
|
|
1180
|
+
out.push(p2);
|
|
1181
|
+
}
|
|
1182
|
+
return { kind: "curve", points: out };
|
|
1183
|
+
}
|
|
1184
|
+
case "elbow": {
|
|
1185
|
+
const a = p[0], first = p[1];
|
|
1186
|
+
let goVertical = vertical(c.start.port) ?? Math.abs(first.y - a.y) > Math.abs(first.x - a.x);
|
|
1187
|
+
const endV = vertical(c.end.port);
|
|
1188
|
+
const out = [a];
|
|
1189
|
+
for (let i = 1; i < p.length; i++) {
|
|
1190
|
+
const q = p[i], cur = out[out.length - 1];
|
|
1191
|
+
const alignedX = Math.abs(cur.x - q.x) < 0.5, alignedY = Math.abs(cur.y - q.y) < 0.5;
|
|
1192
|
+
if (alignedX || alignedY) {
|
|
1193
|
+
out.push(q);
|
|
1194
|
+
if (alignedX !== alignedY) goVertical = !alignedX;
|
|
1195
|
+
continue;
|
|
1196
|
+
}
|
|
1197
|
+
const up = i === p.length - 1 && endV !== null ? !endV : goVertical;
|
|
1198
|
+
out.push(up ? { x: cur.x, y: q.y } : { x: q.x, y: cur.y }, q);
|
|
1199
|
+
goVertical = up;
|
|
1200
|
+
}
|
|
1201
|
+
return { kind: "polyline", points: simplified(out) };
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1052
1205
|
function route(e, index) {
|
|
1053
1206
|
const c = e.connector;
|
|
1054
1207
|
if (!c) return { kind: "polyline", points: [] };
|
|
1055
1208
|
const [a, b] = endpoints(e, index);
|
|
1209
|
+
if (c.waypoints && c.waypoints.length > 0) return routeThrough([a, ...c.waypoints.map((w) => ({ x: w.x, y: w.y })), b], c);
|
|
1056
1210
|
const dx = b.x - a.x, dy = b.y - a.y;
|
|
1057
1211
|
const horizontal = Math.abs(dx) >= Math.abs(dy);
|
|
1058
1212
|
switch (c.routing) {
|
|
1059
1213
|
case "straight":
|
|
1060
1214
|
return { kind: "polyline", points: [a, b] };
|
|
1061
1215
|
case "curved":
|
|
1062
|
-
if (horizontal) return { kind: "
|
|
1063
|
-
return { kind: "
|
|
1216
|
+
if (horizontal) return { kind: "curve", points: [a, { x: a.x + dx / 2, y: a.y }, { x: b.x - dx / 2, y: b.y }, b] };
|
|
1217
|
+
return { kind: "curve", points: [a, { x: a.x, y: a.y + dy / 2 }, { x: b.x, y: b.y - dy / 2 }, b] };
|
|
1064
1218
|
case "elbow": {
|
|
1065
|
-
const vertical = (p) => p === null ? null : p === "top" || p === "bottom";
|
|
1066
1219
|
const startV = vertical(c.start.port) ?? !horizontal;
|
|
1067
1220
|
const endV = vertical(c.end.port) ?? !horizontal;
|
|
1068
1221
|
if (startV && endV) {
|
|
@@ -1642,10 +1795,12 @@ function dashPattern(dash, width) {
|
|
|
1642
1795
|
return [0.01, Math.max(5, width * 2.6)];
|
|
1643
1796
|
}
|
|
1644
1797
|
}
|
|
1645
|
-
function connectorParts(e, index) {
|
|
1798
|
+
function connectorParts(e, index, page) {
|
|
1646
1799
|
const c = e.connector;
|
|
1647
1800
|
if (!c) return [new Path(), new Path()];
|
|
1648
1801
|
const [a, b] = endpoints(e, index);
|
|
1802
|
+
const r = route(e, index);
|
|
1803
|
+
const hops = c.hops === true && page ? hopCrossings(e, r, page, index) : [];
|
|
1649
1804
|
const key = [
|
|
1650
1805
|
e.seed,
|
|
1651
1806
|
e.style.roughness,
|
|
@@ -1659,28 +1814,166 @@ function connectorParts(e, index) {
|
|
|
1659
1814
|
a.x,
|
|
1660
1815
|
a.y,
|
|
1661
1816
|
b.x,
|
|
1662
|
-
b.y
|
|
1817
|
+
b.y,
|
|
1818
|
+
JSON.stringify(c.waypoints),
|
|
1819
|
+
JSON.stringify(hops)
|
|
1663
1820
|
].join("|");
|
|
1664
|
-
return connectors.get(key, () => computeConnector(e,
|
|
1821
|
+
return connectors.get(key, () => computeConnector(e, r, hops));
|
|
1822
|
+
}
|
|
1823
|
+
function hopRadius(strokeWidth) {
|
|
1824
|
+
return Math.max(8, strokeWidth * 4);
|
|
1665
1825
|
}
|
|
1666
|
-
function
|
|
1826
|
+
function hopSamples(r) {
|
|
1827
|
+
const pts = r.points, out = [];
|
|
1828
|
+
if (r.kind === "polyline") {
|
|
1829
|
+
for (let i = 0; i < pts.length - 1; i++) out.push([pts[i], pts[i + 1]]);
|
|
1830
|
+
return out;
|
|
1831
|
+
}
|
|
1832
|
+
for (let s = 0; s < Math.floor((pts.length - 1) / 3); s++) {
|
|
1833
|
+
const seg = pts.slice(3 * s, 3 * s + 4), samples = [];
|
|
1834
|
+
for (let i = 0; i <= 24; i++) samples.push(cubicPoint(seg, i / 24));
|
|
1835
|
+
out.push(samples);
|
|
1836
|
+
}
|
|
1837
|
+
return out;
|
|
1838
|
+
}
|
|
1839
|
+
function runningLengths(samples) {
|
|
1840
|
+
const out = [0];
|
|
1841
|
+
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));
|
|
1842
|
+
return out;
|
|
1843
|
+
}
|
|
1844
|
+
function cubicPoint(p, t) {
|
|
1845
|
+
const u = 1 - t;
|
|
1846
|
+
return {
|
|
1847
|
+
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,
|
|
1848
|
+
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
|
|
1849
|
+
};
|
|
1850
|
+
}
|
|
1851
|
+
function subCubic(p, t0, t1) {
|
|
1852
|
+
const split = (q, t) => {
|
|
1853
|
+
const lerp = (a, b) => ({ x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t });
|
|
1854
|
+
const ab = lerp(q[0], q[1]), bc = lerp(q[1], q[2]), cd = lerp(q[2], q[3]);
|
|
1855
|
+
const abc = lerp(ab, bc), bcd = lerp(bc, cd), m = lerp(abc, bcd);
|
|
1856
|
+
return [[q[0], ab, abc, m], [m, bcd, cd, q[3]]];
|
|
1857
|
+
};
|
|
1858
|
+
const left = split(p, t1)[0];
|
|
1859
|
+
return t1 > 0 ? split(left, t0 / t1)[1] : [p[0], p[0], p[0], p[0]];
|
|
1860
|
+
}
|
|
1861
|
+
function hopCrossings(e, r, page, index) {
|
|
1862
|
+
const segments = hopSamples(r);
|
|
1863
|
+
if (segments.length === 0) return [];
|
|
1864
|
+
const visible = new Set(page.layers.filter((l) => l.visible).map((l) => l.id));
|
|
1865
|
+
const beneath = [];
|
|
1866
|
+
for (const o of orderedElements(page)) {
|
|
1867
|
+
if (o.id === e.id) break;
|
|
1868
|
+
if (o.kind !== "connector" || !visible.has(o.layerID)) continue;
|
|
1869
|
+
beneath.push(routePoints(route(o, index)));
|
|
1870
|
+
}
|
|
1871
|
+
const rad = hopRadius(e.style.strokeWidth);
|
|
1872
|
+
const out = [];
|
|
1873
|
+
for (const samples of segments) {
|
|
1874
|
+
const along = runningLengths(samples);
|
|
1875
|
+
const length = along[along.length - 1];
|
|
1876
|
+
const found = [];
|
|
1877
|
+
for (let i = 0; i < samples.length - 1; i++) {
|
|
1878
|
+
const p = samples[i], q = samples[i + 1];
|
|
1879
|
+
const dx = q.x - p.x, dy = q.y - p.y;
|
|
1880
|
+
for (const line2 of beneath) {
|
|
1881
|
+
for (let k = 0; k < line2.length - 1; k++) {
|
|
1882
|
+
const a = line2[k], b = line2[k + 1];
|
|
1883
|
+
const ex = b.x - a.x, ey = b.y - a.y;
|
|
1884
|
+
const det = dx * ey - dy * ex;
|
|
1885
|
+
if (Math.abs(det) < 1e-9) continue;
|
|
1886
|
+
const wx = a.x - p.x, wy = a.y - p.y;
|
|
1887
|
+
const t = (wx * ey - wy * ex) / det, s = (wx * dy - wy * dx) / det;
|
|
1888
|
+
if (t > 0 && t < 1 && s >= 0 && s <= 1) found.push(along[i] + t * (along[i + 1] - along[i]));
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
const kept = [];
|
|
1893
|
+
for (const at of found.sort((x, y) => x - y)) {
|
|
1894
|
+
if (at < rad + 2 || at > length - rad - 2) continue;
|
|
1895
|
+
if (kept.length > 0 && at - kept[kept.length - 1] < 2 * rad + 2) continue;
|
|
1896
|
+
kept.push(at);
|
|
1897
|
+
}
|
|
1898
|
+
out.push(kept);
|
|
1899
|
+
}
|
|
1900
|
+
return out;
|
|
1901
|
+
}
|
|
1902
|
+
function computeConnector(e, r, hops) {
|
|
1667
1903
|
const c = e.connector;
|
|
1668
1904
|
const rng = new SeededRNG(e.seed);
|
|
1669
1905
|
const rough = e.style.roughness, bow = e.style.bowing;
|
|
1670
1906
|
const path = new Path(), solid = new Path();
|
|
1671
|
-
const
|
|
1907
|
+
const w = rough * 3;
|
|
1908
|
+
const j = () => rng.signed() * w;
|
|
1909
|
+
const hop = (s, t, u, rad2) => {
|
|
1910
|
+
let n = { x: u.y, y: -u.x };
|
|
1911
|
+
if (n.y > 1e-9 || Math.abs(n.y) <= 1e-9 && n.x > 0) n = { x: -n.x, y: -n.y };
|
|
1912
|
+
const k = rad2 * 0.5523;
|
|
1913
|
+
const m = { x: (s.x + t.x) / 2, y: (s.y + t.y) / 2 };
|
|
1914
|
+
const top = { x: m.x + n.x * rad2, y: m.y + n.y * rad2 };
|
|
1915
|
+
path.moveTo(s);
|
|
1916
|
+
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();
|
|
1917
|
+
path.curveTo(top, { x: a1x, y: a1y }, { x: a2x, y: a2y });
|
|
1918
|
+
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();
|
|
1919
|
+
path.curveTo(t, { x: b1x, y: b1y }, { x: b2x, y: b2y });
|
|
1920
|
+
};
|
|
1921
|
+
const rad = hopRadius(e.style.strokeWidth);
|
|
1672
1922
|
if (r.kind === "polyline") {
|
|
1673
1923
|
const pts2 = r.points;
|
|
1924
|
+
const straight = (p, q) => {
|
|
1925
|
+
if (rough <= 0) path.moveTo(p).lineTo(q);
|
|
1926
|
+
else line(p, q, rng, rough, bow, true, path);
|
|
1927
|
+
};
|
|
1674
1928
|
for (let i = 0; i < Math.max(pts2.length - 1, 0); i++) {
|
|
1675
|
-
|
|
1676
|
-
|
|
1929
|
+
const p = pts2[i], q = pts2[i + 1];
|
|
1930
|
+
const length = Math.hypot(q.x - p.x, q.y - p.y);
|
|
1931
|
+
const u = length > 0 ? { x: (q.x - p.x) / length, y: (q.y - p.y) / length } : { x: 0, y: 0 };
|
|
1932
|
+
const at = (d) => ({ x: p.x + u.x * d, y: p.y + u.y * d });
|
|
1933
|
+
let from = p;
|
|
1934
|
+
for (const d of i < hops.length ? hops[i] : []) {
|
|
1935
|
+
const s = at(d - rad), t = at(d + rad);
|
|
1936
|
+
straight(from, s);
|
|
1937
|
+
hop(s, t, u, rad);
|
|
1938
|
+
from = t;
|
|
1939
|
+
}
|
|
1940
|
+
straight(from, q);
|
|
1677
1941
|
}
|
|
1678
1942
|
} else {
|
|
1679
|
-
|
|
1680
|
-
const
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1943
|
+
const pts2 = r.points;
|
|
1944
|
+
const samples = hopSamples(r);
|
|
1945
|
+
path.moveTo(pts2[0]);
|
|
1946
|
+
for (let s = 0; s < Math.floor((pts2.length - 1) / 3); s++) {
|
|
1947
|
+
const b = pts2[3 * s + 1], c2 = pts2[3 * s + 2], d = pts2[3 * s + 3];
|
|
1948
|
+
const c1x = b.x + j(), c1y = b.y + j(), c2x = c2.x + j(), c2y = c2.y + j();
|
|
1949
|
+
const here = s < hops.length ? hops[s] : [];
|
|
1950
|
+
if (here.length === 0) {
|
|
1951
|
+
path.curveTo(d, { x: c1x, y: c1y }, { x: c2x, y: c2y });
|
|
1952
|
+
continue;
|
|
1953
|
+
}
|
|
1954
|
+
const cubic = [pts2[3 * s], { x: c1x, y: c1y }, { x: c2x, y: c2y }, d];
|
|
1955
|
+
const along = runningLengths(samples[s]);
|
|
1956
|
+
const param = (at) => {
|
|
1957
|
+
let i = 0;
|
|
1958
|
+
while (i < along.length - 2 && along[i + 1] < at) i++;
|
|
1959
|
+
const span = along[i + 1] - along[i];
|
|
1960
|
+
return (i + (span > 0 ? (at - along[i]) / span : 0)) / 24;
|
|
1961
|
+
};
|
|
1962
|
+
let from = 0;
|
|
1963
|
+
for (const at of here) {
|
|
1964
|
+
const t0 = param(at - rad), t1 = param(at + rad);
|
|
1965
|
+
const piece = subCubic(cubic, from, t0);
|
|
1966
|
+
if (s > 0 || from > 0) path.moveTo(piece[0]);
|
|
1967
|
+
path.curveTo(piece[3], piece[1], piece[2]);
|
|
1968
|
+
const a = cubicPoint(cubic, t0), z = cubicPoint(cubic, t1);
|
|
1969
|
+
const width = Math.hypot(z.x - a.x, z.y - a.y);
|
|
1970
|
+
const u = width > 0 ? { x: (z.x - a.x) / width, y: (z.y - a.y) / width } : { x: 0, y: 0 };
|
|
1971
|
+
hop(a, z, u, width / 2);
|
|
1972
|
+
from = t1;
|
|
1973
|
+
}
|
|
1974
|
+
const rest = subCubic(cubic, from, 1);
|
|
1975
|
+
path.moveTo(rest[0]).curveTo(rest[3], rest[1], rest[2]);
|
|
1976
|
+
}
|
|
1684
1977
|
}
|
|
1685
1978
|
const pts = routePoints(r);
|
|
1686
1979
|
const size = Math.max(10, e.style.strokeWidth * 5);
|
|
@@ -1702,10 +1995,10 @@ function computeConnector(e, index) {
|
|
|
1702
1995
|
solid.moveTo(tip).lineTo(w1).lineTo(w2).close();
|
|
1703
1996
|
break;
|
|
1704
1997
|
case "dot": {
|
|
1705
|
-
const
|
|
1998
|
+
const rad2 = size * 0.32;
|
|
1706
1999
|
const ang = Math.atan2(tip.y - from.y, tip.x - from.x);
|
|
1707
|
-
const cx = tip.x -
|
|
1708
|
-
solid.addEllipse({ x: cx -
|
|
2000
|
+
const cx = tip.x - rad2 * Math.cos(ang), cy = tip.y - rad2 * Math.sin(ang);
|
|
2001
|
+
solid.addEllipse({ x: cx - rad2, y: cy - rad2, width: 2 * rad2, height: 2 * rad2 });
|
|
1709
2002
|
break;
|
|
1710
2003
|
}
|
|
1711
2004
|
case "bar": {
|
|
@@ -1908,17 +2201,20 @@ function trackMap(tl) {
|
|
|
1908
2201
|
for (const t of tl.tracks) if (!m.has(t.element)) m.set(t.element, t);
|
|
1909
2202
|
return m;
|
|
1910
2203
|
}
|
|
1911
|
-
function resolved(page, t) {
|
|
1912
|
-
return expandingSymbols(posed(page, t), clampTime(t));
|
|
2204
|
+
function resolved(page, t, state = "initial") {
|
|
2205
|
+
return expandingSymbols(posed(page, t, state), clampTime(t));
|
|
1913
2206
|
}
|
|
1914
|
-
|
|
1915
|
-
|
|
2207
|
+
var EMPTY_TIMELINE = { fps: 12, duration: 0, boil: null, settle: null, poster: null, tracks: [], camera: null, markers: null };
|
|
2208
|
+
function posed(page, t, state = "initial") {
|
|
2209
|
+
const off = stateHidden(page, state);
|
|
2210
|
+
const timeline = page.timeline ?? (off.size === 0 ? null : EMPTY_TIMELINE);
|
|
1916
2211
|
if (!timeline) return page;
|
|
1917
2212
|
t = clampTime(t);
|
|
1918
2213
|
const tracks = trackMap(timeline);
|
|
1919
2214
|
const reseed = timeline.boil === null ? null : Math.trunc(Math.floor(t * clampFPS(timeline.fps) + 1e-9) / Math.max(1, timeline.boil)) % BOIL_DRAWINGS >>> 0;
|
|
1920
2215
|
const hidden = /* @__PURE__ */ new Set();
|
|
1921
2216
|
for (const tr of tracks.values()) if (!isVisible(tr, t)) hidden.add(tr.element);
|
|
2217
|
+
for (const id of off) hidden.add(id);
|
|
1922
2218
|
let moving = null;
|
|
1923
2219
|
if (timeline.settle === true) {
|
|
1924
2220
|
moving = /* @__PURE__ */ new Set();
|
|
@@ -1957,7 +2253,7 @@ function posed(page, t) {
|
|
|
1957
2253
|
elements = elements.map((e) => {
|
|
1958
2254
|
const c = e.connector;
|
|
1959
2255
|
if (!c || ![c.start.elementID, c.end.elementID].some((id) => id !== null && hidden.has(id))) return e;
|
|
1960
|
-
if (!(tracks.get(e.id)?.keys.some((k) => k.visible !== void 0) ?? false)) {
|
|
2256
|
+
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))) {
|
|
1961
2257
|
gone.add(e.id);
|
|
1962
2258
|
return e;
|
|
1963
2259
|
}
|
|
@@ -1973,7 +2269,7 @@ function posterTime(page) {
|
|
|
1973
2269
|
return page.timeline ? page.timeline.poster ?? page.timeline.duration : 0;
|
|
1974
2270
|
}
|
|
1975
2271
|
function still(page) {
|
|
1976
|
-
return page.timeline ? resolved(page, posterTime(page)) : expandingSymbols(page, 0);
|
|
2272
|
+
return page.timeline ? resolved(page, posterTime(page)) : expandingSymbols(posed(page, 0), 0);
|
|
1977
2273
|
}
|
|
1978
2274
|
function cameraRect(tl, t) {
|
|
1979
2275
|
const keys = tl.camera ?? [];
|
|
@@ -2082,7 +2378,7 @@ function filled(s, args) {
|
|
|
2082
2378
|
}) };
|
|
2083
2379
|
}
|
|
2084
2380
|
function symbolPage(s) {
|
|
2085
|
-
return { id: s.id, name: s.name, layers: s.layers, elements: s.elements, theme: null, timeline: s.timeline, symbols: null };
|
|
2381
|
+
return { id: s.id, name: s.name, layers: s.layers, elements: s.elements, theme: null, timeline: s.timeline, symbols: null, variables: null };
|
|
2086
2382
|
}
|
|
2087
2383
|
function hasInstances(page) {
|
|
2088
2384
|
return page.elements.some((e) => e.kind === "symbol");
|
|
@@ -2172,7 +2468,12 @@ function placed(inner, instance, symbol) {
|
|
|
2172
2468
|
const q = point(p);
|
|
2173
2469
|
return { elementID: null, port: p.port, x: q.x, y: q.y };
|
|
2174
2470
|
};
|
|
2175
|
-
e.connector = {
|
|
2471
|
+
e.connector = {
|
|
2472
|
+
...inner.connector,
|
|
2473
|
+
start: end(inner.connector.start),
|
|
2474
|
+
end: end(inner.connector.end),
|
|
2475
|
+
waypoints: inner.connector.waypoints?.map(point) ?? null
|
|
2476
|
+
};
|
|
2176
2477
|
}
|
|
2177
2478
|
return e;
|
|
2178
2479
|
}
|
|
@@ -2206,16 +2507,21 @@ function clickTargets(page, file) {
|
|
|
2206
2507
|
let pageOf = null;
|
|
2207
2508
|
for (const e of page.elements) {
|
|
2208
2509
|
const c = e.click;
|
|
2510
|
+
const state = c && changesState(c) ? { ...c.set ? { set: c.set } : {}, ...c.toggle ? { toggle: c.toggle } : {} } : null;
|
|
2209
2511
|
if (c?.scene) {
|
|
2210
2512
|
const i = sceneNamed(c.scene, markers);
|
|
2211
2513
|
if (i >= 0) {
|
|
2212
|
-
out.set(e.id, { scene: i });
|
|
2514
|
+
out.set(e.id, { scene: i, ...state });
|
|
2213
2515
|
continue;
|
|
2214
2516
|
}
|
|
2215
2517
|
}
|
|
2216
2518
|
const url = (c?.url ? allowedURL(c.url) : null) ?? webLink(e.url);
|
|
2217
2519
|
if (url) {
|
|
2218
|
-
out.set(e.id, { url });
|
|
2520
|
+
out.set(e.id, { url, ...state });
|
|
2521
|
+
continue;
|
|
2522
|
+
}
|
|
2523
|
+
if (state) {
|
|
2524
|
+
out.set(e.id, { state: true, ...state });
|
|
2219
2525
|
continue;
|
|
2220
2526
|
}
|
|
2221
2527
|
if (e.link && file && file.pages.length > 1) {
|
|
@@ -2948,7 +3254,7 @@ function svgParts(page, background, fixed, links = false) {
|
|
|
2948
3254
|
if (!visible.has(e.layerID)) continue;
|
|
2949
3255
|
const alpha = Math.min(Math.max(e.style.opacity ?? 1, 0), 1);
|
|
2950
3256
|
if (alpha <= 0) continue;
|
|
2951
|
-
let body = svgElement(e, index, bg);
|
|
3257
|
+
let body = svgElement(e, index, bg, d);
|
|
2952
3258
|
if (angle(e) !== 0 && e.kind !== "connector") {
|
|
2953
3259
|
body = `<g transform="rotate(${fmt(angle(e) * 180 / Math.PI)} ${fmt(midX(frame(e)))} ${fmt(midY(frame(e)))})">
|
|
2954
3260
|
${body}</g>
|
|
@@ -2983,7 +3289,7 @@ function typed(s, progress) {
|
|
|
2983
3289
|
function revealProgress(e) {
|
|
2984
3290
|
return e.reveal !== null && e.reveal < 1 ? Math.max(0, e.reveal) : null;
|
|
2985
3291
|
}
|
|
2986
|
-
function svgElement(e, index, labelBackground) {
|
|
3292
|
+
function svgElement(e, index, labelBackground, page) {
|
|
2987
3293
|
const stroke = color(e.style.stroke);
|
|
2988
3294
|
let strokeAttrs = `stroke="${stroke}" stroke-width="${fmt(e.style.strokeWidth)}" stroke-linecap="round" stroke-linejoin="round"`;
|
|
2989
3295
|
const dash = dashPattern(e.style.dash, e.style.strokeWidth);
|
|
@@ -2994,7 +3300,7 @@ function svgElement(e, index, labelBackground) {
|
|
|
2994
3300
|
if (r !== null) {
|
|
2995
3301
|
if (e.kind === "text") text = typed(text ?? "", r);
|
|
2996
3302
|
else if (e.kind === "connector") {
|
|
2997
|
-
return `<path d="${connectorParts(e, index)[0].trimmed(r).data()}" fill="none" ${strokeAttrs}/>
|
|
3303
|
+
return `<path d="${connectorParts(e, index, page)[0].trimmed(r).data()}" fill="none" ${strokeAttrs}/>
|
|
2998
3304
|
`;
|
|
2999
3305
|
} else {
|
|
3000
3306
|
return `<path d="${outline(e).trimmed(r).data()}" fill="none" ${strokeAttrs}/>
|
|
@@ -3003,7 +3309,7 @@ function svgElement(e, index, labelBackground) {
|
|
|
3003
3309
|
}
|
|
3004
3310
|
switch (e.kind) {
|
|
3005
3311
|
case "connector": {
|
|
3006
|
-
const [line2, filled2] = connectorParts(e, index);
|
|
3312
|
+
const [line2, filled2] = connectorParts(e, index, page);
|
|
3007
3313
|
let s = `<path d="${line2.data()}" fill="none" ${strokeAttrs}/>
|
|
3008
3314
|
`;
|
|
3009
3315
|
if (!filled2.isEmpty) s += `<path d="${filled2.data()}" fill="${stroke}" stroke="none"/>
|
|
@@ -3086,7 +3392,7 @@ function textElement(text, f, e, centered) {
|
|
|
3086
3392
|
const total = lineHeight * lines.length;
|
|
3087
3393
|
const x = centered ? midX(f) : f.x;
|
|
3088
3394
|
const top = centered ? midY(f) - total / 2 : f.y;
|
|
3089
|
-
let s = `<text x="${fmt(x)}" font-family="${cssFamily(e.style.font)}" font-size="${fmt(size)}" fill="${color(e
|
|
3395
|
+
let s = `<text x="${fmt(x)}" font-family="${cssFamily(e.style.font)}" font-size="${fmt(size)}" fill="${color(labelColor(e))}"${centered ? ` text-anchor="middle"` : ""}>`;
|
|
3090
3396
|
lines.forEach((line2, i) => {
|
|
3091
3397
|
s += `<tspan x="${fmt(x)}" y="${fmt(top + lineHeight * (i + 0.8))}">${escapeXML(line2)}</tspan>`;
|
|
3092
3398
|
});
|
|
@@ -3129,6 +3435,10 @@ var Playback = class {
|
|
|
3129
3435
|
__publicField(this, "plan");
|
|
3130
3436
|
__publicField(this, "scenes");
|
|
3131
3437
|
__publicField(this, "sceneNames");
|
|
3438
|
+
/** Whether each scene loops (Marker.loop). */
|
|
3439
|
+
__publicField(this, "sceneLoops");
|
|
3440
|
+
/** The page's variables now: their starting values until clicks change them. */
|
|
3441
|
+
__publicField(this, "state");
|
|
3132
3442
|
__publicField(this, "background");
|
|
3133
3443
|
__publicField(this, "ink");
|
|
3134
3444
|
/** What clicking each clickable element does, by element id. */
|
|
@@ -3188,6 +3498,8 @@ var Playback = class {
|
|
|
3188
3498
|
};
|
|
3189
3499
|
this.scenes = (page.timeline?.markers ?? []).map((m) => near(m.t));
|
|
3190
3500
|
this.sceneNames = (page.timeline?.markers ?? []).map((m) => m.name);
|
|
3501
|
+
this.sceneLoops = (page.timeline?.markers ?? []).map((m) => m.loop === true);
|
|
3502
|
+
this.state = { ...page.variables ?? {} };
|
|
3191
3503
|
const theme = themeNamed(page.theme);
|
|
3192
3504
|
this.background = theme?.background ?? WHITE;
|
|
3193
3505
|
this.ink = theme?.ink ?? { r: 0.12, g: 0.12, b: 0.13, a: 1 };
|
|
@@ -3238,7 +3550,7 @@ var Playback = class {
|
|
|
3238
3550
|
const times = /* @__PURE__ */ new Map();
|
|
3239
3551
|
for (const [id, start] of this.replayStarts) times.set(id, Math.min((now - start) / 1e3, this.replays.get(id) ?? 0));
|
|
3240
3552
|
const tc = clampTime(t);
|
|
3241
|
-
const pose = posed(this.page, tc);
|
|
3553
|
+
const pose = posed(this.page, tc, this.state);
|
|
3242
3554
|
const frame2 = expandingSymbols(pose, tc, true, times);
|
|
3243
3555
|
const { parts } = svgParts(frame2, this.background, r);
|
|
3244
3556
|
const owned = (m, parts2) => {
|
|
@@ -3360,6 +3672,20 @@ ${h}</g>
|
|
|
3360
3672
|
});
|
|
3361
3673
|
return s;
|
|
3362
3674
|
}
|
|
3675
|
+
/** The frame after scene `s`'s last: the next scene's, or the frame count after the last scene. */
|
|
3676
|
+
sceneEnd(s) {
|
|
3677
|
+
return this.scenes.find((f, j) => j > s && f > this.scenes[s]) ?? this.frameCount;
|
|
3678
|
+
}
|
|
3679
|
+
/** Where playing goes from frame `i`, and whether it stops there: on into a looping scene, round to the start of
|
|
3680
|
+
* the looping scene `i` ends, or a stop at a scene or the end (`loops`: whether the timeline loops at its end). */
|
|
3681
|
+
step(i, loops) {
|
|
3682
|
+
const next = i + 1;
|
|
3683
|
+
const s = this.sceneAt(i);
|
|
3684
|
+
if (s >= 0 && this.sceneLoops[s] && next >= this.sceneEnd(s)) return { frame: this.scenes[s], stop: false };
|
|
3685
|
+
if (next >= this.frameCount) return { frame: 0, stop: !loops || this.scenes.length > 0 };
|
|
3686
|
+
const entered = this.scenes.indexOf(next);
|
|
3687
|
+
return { frame: next, stop: entered >= 0 && !this.sceneLoops[entered] };
|
|
3688
|
+
}
|
|
3363
3689
|
};
|
|
3364
3690
|
var ElementBase = typeof HTMLElement === "undefined" ? class {
|
|
3365
3691
|
} : HTMLElement;
|
|
@@ -3513,7 +3839,7 @@ var DottiePlayer = class extends ElementBase {
|
|
|
3513
3839
|
this.setPlaying(false);
|
|
3514
3840
|
return;
|
|
3515
3841
|
}
|
|
3516
|
-
this.setPlaying(!this.hasAttribute("no-autoplay") && !p.scenes.includes(0));
|
|
3842
|
+
this.setPlaying(!this.hasAttribute("no-autoplay") && (!p.scenes.includes(0) || p.sceneLoops[p.scenes.indexOf(0)]));
|
|
3517
3843
|
}
|
|
3518
3844
|
// MARK: Playback
|
|
3519
3845
|
get paused() {
|
|
@@ -3539,11 +3865,13 @@ var DottiePlayer = class extends ElementBase {
|
|
|
3539
3865
|
pause() {
|
|
3540
3866
|
this.setPlaying(false);
|
|
3541
3867
|
}
|
|
3542
|
-
/** Plays on to the next scene (from the start after the last). */
|
|
3868
|
+
/** Plays on to the next scene (from the start after the last). Out of a looping scene it jumps to the next. */
|
|
3543
3869
|
nextScene() {
|
|
3544
3870
|
const p = this.playback;
|
|
3545
3871
|
if (!p) return;
|
|
3546
|
-
|
|
3872
|
+
const s = p.sceneAt(p.frame);
|
|
3873
|
+
if (s >= 0 && p.sceneLoops[s]) p.show(s + 1 < p.scenes.length ? p.scenes[s + 1] : 0);
|
|
3874
|
+
else if (p.frame >= p.frameCount - 1) p.show(0);
|
|
3547
3875
|
this.setPlaying(true);
|
|
3548
3876
|
}
|
|
3549
3877
|
/** Back to the start of this scene, or the one before if already there. */
|
|
@@ -3572,6 +3900,8 @@ var DottiePlayer = class extends ElementBase {
|
|
|
3572
3900
|
this.scheduleReplay();
|
|
3573
3901
|
}
|
|
3574
3902
|
if (!click) return;
|
|
3903
|
+
if (click.set || click.toggle) this.setState(applyClick({ scene: null, url: null, set: click.set ?? null, toggle: click.toggle ?? null }, p.state));
|
|
3904
|
+
if ("state" in click) return;
|
|
3575
3905
|
if ("url" in click) {
|
|
3576
3906
|
if (/^(https?:|mailto:)/i.test(click.url)) window.open(click.url, "_blank", "noopener");
|
|
3577
3907
|
} else if ("page" in click) {
|
|
@@ -3579,13 +3909,27 @@ var DottiePlayer = class extends ElementBase {
|
|
|
3579
3909
|
} else if (click.scene >= 0 && click.scene < p.scenes.length) {
|
|
3580
3910
|
if (reduced || this.hasAttribute("at") || p.frameCount < 2) {
|
|
3581
3911
|
this.setPlaying(false);
|
|
3582
|
-
p.show(click.scene + 1 < p.scenes.length ? p.scenes[click.scene + 1] : p.frameCount - 1);
|
|
3912
|
+
p.show(p.sceneLoops[click.scene] ? p.scenes[click.scene] : click.scene + 1 < p.scenes.length ? p.scenes[click.scene + 1] : p.frameCount - 1);
|
|
3583
3913
|
} else {
|
|
3584
3914
|
p.show(p.scenes[click.scene]);
|
|
3585
3915
|
this.setPlaying(true);
|
|
3586
3916
|
}
|
|
3587
3917
|
}
|
|
3588
3918
|
}
|
|
3919
|
+
/** The page's variables now. */
|
|
3920
|
+
get variables() {
|
|
3921
|
+
return { ...this.playback?.state ?? {} };
|
|
3922
|
+
}
|
|
3923
|
+
/** Gives the page's variables these values (the others keep theirs), redraws, and fires `state`. */
|
|
3924
|
+
setState(values) {
|
|
3925
|
+
const p = this.playback;
|
|
3926
|
+
if (!p) return;
|
|
3927
|
+
const next = { ...p.state, ...values };
|
|
3928
|
+
if (Object.keys(next).every((k) => next[k] === p.state[k])) return;
|
|
3929
|
+
p.state = next;
|
|
3930
|
+
p.redraw();
|
|
3931
|
+
this.dispatchEvent(new CustomEvent("state", { detail: { variables: { ...next } } }));
|
|
3932
|
+
}
|
|
3589
3933
|
/** Redraws, while the page itself stands still, as long as a clicked copy is playing through. */
|
|
3590
3934
|
scheduleReplay() {
|
|
3591
3935
|
if (this.replayRaf) return;
|
|
@@ -3611,7 +3955,8 @@ var DottiePlayer = class extends ElementBase {
|
|
|
3611
3955
|
schedule() {
|
|
3612
3956
|
if (this.playing && this.visible && !this.raf && this.isConnected) this.raf = requestAnimationFrame((now) => this.tick(now));
|
|
3613
3957
|
}
|
|
3614
|
-
/** Steps whole frames at the timeline's rate; with scenes, playing stops at each one and at the end
|
|
3958
|
+
/** Steps whole frames at the timeline's rate; with scenes, playing stops at each one and at the end, except that a
|
|
3959
|
+
* looping scene plays round (Playback.step). */
|
|
3615
3960
|
tick(now) {
|
|
3616
3961
|
this.raf = 0;
|
|
3617
3962
|
const p = this.playback;
|
|
@@ -3622,14 +3967,14 @@ var DottiePlayer = class extends ElementBase {
|
|
|
3622
3967
|
let advanced = false;
|
|
3623
3968
|
while (this.playing && this.acc >= step) {
|
|
3624
3969
|
this.acc -= step;
|
|
3625
|
-
const
|
|
3626
|
-
if (
|
|
3970
|
+
const { frame: frame2, stop } = p.step(p.frame, !this.hasAttribute("no-loop"));
|
|
3971
|
+
if (stop && frame2 === 0 && p.frame + 1 >= p.frameCount) {
|
|
3627
3972
|
this.setPlaying(false);
|
|
3628
3973
|
break;
|
|
3629
3974
|
}
|
|
3630
|
-
p.frame =
|
|
3975
|
+
p.frame = frame2;
|
|
3631
3976
|
advanced = true;
|
|
3632
|
-
if (
|
|
3977
|
+
if (stop) {
|
|
3633
3978
|
this.setPlaying(false);
|
|
3634
3979
|
break;
|
|
3635
3980
|
}
|
|
@@ -3735,6 +4080,7 @@ function inspectFrame(page, t) {
|
|
|
3735
4080
|
d.strokeWidth = r4(e.style.strokeWidth);
|
|
3736
4081
|
d.stroke = rgba2(e.style.stroke);
|
|
3737
4082
|
d.fill = rgba2(e.style.fill);
|
|
4083
|
+
if (e.style.textColor !== null) d.textColor = rgba2(e.style.textColor);
|
|
3738
4084
|
if (e.kind === "connector") {
|
|
3739
4085
|
const [a, b] = endpoints(e, index);
|
|
3740
4086
|
d.start = pt(a);
|
|
@@ -3778,7 +4124,7 @@ function render(source, options = {}) {
|
|
|
3778
4124
|
function isFile(v) {
|
|
3779
4125
|
return typeof v === "object" && v !== null && Array.isArray(v.pages) && Array.isArray(v.warnings);
|
|
3780
4126
|
}
|
|
3781
|
-
var VERSION = "0.
|
|
4127
|
+
var VERSION = "0.7.0";
|
|
3782
4128
|
|
|
3783
4129
|
// src/player-entry.ts
|
|
3784
4130
|
definePlayer();
|