@phin-tech/dottie 0.9.0 → 0.11.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 +15 -0
- package/dist/compile.js +123 -15
- package/dist/dottie-compile.mjs +131 -17
- package/dist/dottie-logic.min.js +2171 -0
- package/dist/dottie-player.js +819 -209
- package/dist/dottie-player.min.js +55 -53
- package/dist/dottie-sim.min.js +5 -4
- package/dist/dottie.js +817 -209
- package/dist/types/clicks.d.ts +3 -2
- package/dist/types/compile.d.ts +3 -0
- package/dist/types/elbow.d.ts +18 -0
- package/dist/types/expression.d.ts +51 -0
- package/dist/types/flows.d.ts +2 -0
- package/dist/types/index.d.ts +2 -2
- 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 +15 -3
- package/dist/types/model.d.ts +42 -3
- package/dist/types/player.d.ts +8 -3
- package/dist/types/state.d.ts +14 -13
- package/dist/types/theme.d.ts +13 -1
- package/package.json +3 -1
package/PARITY.md
CHANGED
|
@@ -24,6 +24,11 @@ fixture, so the gap is visible rather than silent.
|
|
|
24
24
|
| Ports and edge-following, rotated shapes, free ends | Supported | [edge/connectors](../fixtures/edge/connectors.dottie) | |
|
|
25
25
|
| Connectors and labels on outlines (ADR 0001) | Supported | [edge/extensions](../fixtures/edge/extensions.dottie) | Edge-following meets a `.path`'s actual outline, not its box (ray against 16-step flattened curves). Endpoints pinned with `portIndex` attach to the element's `ports`. A path's label goes in its `labelRect`. |
|
|
26
26
|
| Extension shapes as stored (ADR 0001) | Supported | [edge/extensions](../fixtures/edge/extensions.dottie) | Drawn from the stored path with no extension installed. The `extension` field is kept as metadata and nothing runs. Parameters, commands and handles are Dottie-only. |
|
|
27
|
+
| Click logic (`logic`, a click's `run`) | Supported | [edge/logic](../fixtures/edge/logic.dottie) | One engine for both: `logic.ts` (QuickJS in WebAssembly), run by `LogicEngine.swift` in JavaScriptCore. Runs only with `allow-logic` (ADR 0007). |
|
|
28
|
+
| Number variables, expressions in conditions and clicks, `{value}` in labels | Supported | [edge/counters](../fixtures/edge/counters.dottie), [expressions.json](../fixtures/expressions.json) | One grammar in `Expression.swift` and `expression.ts` (ADR 0007). |
|
|
29
|
+
| Containers and swimlanes | Supported | [edge/containers](../fixtures/edge/containers.dottie) | Drawn from the stored frame and title band (`labelRect`); the app keeps them fitted (ADR 0006). |
|
|
30
|
+
| Themes of a page's own, and palette colors by name | Supported | [edge/palette](../fixtures/edge/palette.dottie) | Names are resolved when a file is read, by the same rules in `Theme.swift` and `theme.ts` (ADR 0005). |
|
|
31
|
+
| Elbows that go round shapes (`avoid`) | Supported | [edge/elbow-around](../fixtures/edge/elbow-around.dottie) | The same grid router, point for point (`ElbowRouter.swift`, `elbow.ts`). |
|
|
27
32
|
| Waypoints (bends) on every routing, and hops | Supported | [edge/waypoints](../fixtures/edge/waypoints.dottie), [edge/waypoints-symbol](../fixtures/edge/waypoints-symbol.dottie) | Hops need the page, so `connectorParts` takes it; without one a line draws none. |
|
|
28
33
|
| Arrowheads: arrow, triangle, dot, bar | Supported | [edge/connectors](../fixtures/edge/connectors.dottie) | |
|
|
29
34
|
| Label colors (`textColor`, apart from the stroke) | Supported | [edge/text-colors](../fixtures/edge/text-colors.dottie) | Text boxes and notes ignore it, as in Dottie |
|
package/README.md
CHANGED
|
@@ -56,6 +56,21 @@ when it is showing. The classic script starts the worker from its own source (a
|
|
|
56
56
|
loads `dottie-player.min.js` from beside itself, so host the two together. Where a worker can't start, as under a
|
|
57
57
|
`worker-src` policy that refuses it, the player works it out in place as before. Small drawings always do.
|
|
58
58
|
|
|
59
|
+
**Click logic.** Variables can be numbers, and conditions and clicks are small expressions (`count >= 3`,
|
|
60
|
+
`"count + 1"`, `{count}` in a label). For more, a page can carry `logic`: JavaScript handlers a click `run`s, each
|
|
61
|
+
getting the variables and the click and returning new values. The player runs them only with `allow-logic`:
|
|
62
|
+
|
|
63
|
+
```html
|
|
64
|
+
<dottie-player src="game.dottie" allow-logic></dottie-player>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Each click's handler runs in a fresh QuickJS sandbox compiled to WebAssembly, never in your page's JavaScript: it has
|
|
68
|
+
no `window`, `document`, network, timers, `Date` or `Math.random`, and a time and memory limit, so the same click on
|
|
69
|
+
the same state gives the same result everywhere. A handler that fails leaves the state as it was and fires
|
|
70
|
+
`logicerror` (with `{element, handler, error}`). Without `allow-logic`, a click does its own `set` and `toggle`
|
|
71
|
+
instead. The engine (`dottie-logic.min.js`, about 850 KB) is loaded from beside the player only when a page with
|
|
72
|
+
logic is clicked, so host it with the player; `dottie-compile --allow-logic` puts it in the compiled script instead.
|
|
73
|
+
|
|
59
74
|
**Fonts.** Text in Dottie's handwriting fonts (Caveat, Patrick Hand, Architects Daughter; SIL Open Font License)
|
|
60
75
|
is drawn in them: the player loads the ones a drawing uses from `fonts/` beside its own script, as `dist/fonts` on
|
|
61
76
|
npm and the CDNs. If you host the player yourself, copy that folder with it, or call `Dottie.setFontBase(url)`
|
package/dist/compile.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! dottie.js 0.
|
|
1
|
+
/*! dottie.js 0.11.0 | Apache-2.0 | https://github.com/phin-tech/dottie */
|
|
2
2
|
var __defProp = Object.defineProperty;
|
|
3
3
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
4
4
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
@@ -50,19 +50,80 @@ function isName(name) {
|
|
|
50
50
|
return name.length <= MAX_NAME && /^[A-Za-z_][A-Za-z0-9_]*$/.test(name);
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
// src/theme.ts
|
|
54
|
+
var rgb = (r, g, b) => ({ r, g, b, a: 1 });
|
|
55
|
+
function hex(h) {
|
|
56
|
+
const n = parseInt(h.slice(1), 16);
|
|
57
|
+
return { r: (n >> 16 & 255) / 255, g: (n >> 8 & 255) / 255, b: (n & 255) / 255, a: 1 };
|
|
58
|
+
}
|
|
59
|
+
var PALETTE_NAMES = ["accent", "muted", "danger", "warning", "success", "info"];
|
|
60
|
+
var palette = (colors) => PALETTE_NAMES.map((name, i) => ({ name, color: hex(colors[i]) }));
|
|
61
|
+
var LIGHT_PALETTE = palette(["#2563eb", "#6b7280", "#dc2626", "#d97706", "#16a34a", "#0891b2"]);
|
|
62
|
+
var DARK_PALETTE = palette(["#93c5fd", "#cbd5e1", "#fca5a5", "#fcd34d", "#86efac", "#67e8f9"]);
|
|
63
|
+
var themes = [
|
|
64
|
+
{ id: "paper", name: "Paper", background: rgb(0.984, 0.98, 0.965), ink: rgb(0.12, 0.12, 0.13), palette: LIGHT_PALETTE },
|
|
65
|
+
{ id: "white", name: "White", background: rgb(1, 1, 1), ink: rgb(0.12, 0.12, 0.13), palette: LIGHT_PALETTE },
|
|
66
|
+
{
|
|
67
|
+
id: "sepia",
|
|
68
|
+
name: "Sepia",
|
|
69
|
+
background: rgb(0.953, 0.914, 0.824),
|
|
70
|
+
ink: rgb(0.23, 0.18, 0.13),
|
|
71
|
+
palette: palette(["#9a3412", "#78716c", "#b91c1c", "#b45309", "#4d7c0f", "#0e7490"])
|
|
72
|
+
},
|
|
73
|
+
{ id: "blueprint", name: "Blueprint", background: rgb(0.106, 0.227, 0.361), ink: rgb(0.91, 0.945, 0.984), palette: DARK_PALETTE },
|
|
74
|
+
{ id: "chalkboard", name: "Chalkboard", background: rgb(0.169, 0.227, 0.2), ink: rgb(0.945, 0.945, 0.918), palette: DARK_PALETTE },
|
|
75
|
+
{ id: "midnight", name: "Midnight", background: rgb(0.082, 0.086, 0.102), ink: rgb(0.925, 0.925, 0.94), palette: DARK_PALETTE }
|
|
76
|
+
];
|
|
77
|
+
function themeNamed(id) {
|
|
78
|
+
return themes.find((t) => t.id === id) ?? null;
|
|
79
|
+
}
|
|
80
|
+
function pageTheme(page) {
|
|
81
|
+
const c = page.customTheme;
|
|
82
|
+
if (c) return { id: "custom", name: c.name, background: c.background, ink: c.ink, palette: c.palette };
|
|
83
|
+
return themeNamed(page.theme);
|
|
84
|
+
}
|
|
85
|
+
function paletteColor(page, name) {
|
|
86
|
+
const n = name.trim().toLowerCase();
|
|
87
|
+
return (pageTheme(page)?.palette ?? LIGHT_PALETTE).find((p) => p.name.toLowerCase() === n)?.color ?? null;
|
|
88
|
+
}
|
|
89
|
+
function resolvingPalette(page) {
|
|
90
|
+
const named = (e) => !!e.style.named;
|
|
91
|
+
if (!page.elements.some(named) && !(page.symbols ?? []).some((s) => s.elements.some(named))) return page;
|
|
92
|
+
const resolve = (e) => {
|
|
93
|
+
const n = e.style.named;
|
|
94
|
+
if (!n) return e;
|
|
95
|
+
const style = { ...e.style };
|
|
96
|
+
const f = n.fill !== null ? paletteColor(page, n.fill) : null;
|
|
97
|
+
const s = n.stroke !== null ? paletteColor(page, n.stroke) : null;
|
|
98
|
+
const t = n.textColor !== null ? paletteColor(page, n.textColor) : null;
|
|
99
|
+
if (f) style.fill = { ...f };
|
|
100
|
+
if (s) style.stroke = { ...s };
|
|
101
|
+
if (t) style.textColor = { ...t };
|
|
102
|
+
return { ...e, style };
|
|
103
|
+
};
|
|
104
|
+
return {
|
|
105
|
+
...page,
|
|
106
|
+
elements: page.elements.map(resolve),
|
|
107
|
+
symbols: page.symbols ? page.symbols.map((s) => ({ ...s, elements: s.elements.map(resolve) })) : page.symbols
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
53
111
|
// src/model.ts
|
|
54
|
-
var SUPPORTED_VERSION =
|
|
112
|
+
var SUPPORTED_VERSION = 3;
|
|
55
113
|
var KINDS = ["rectangle", "ellipse", "diamond", "text", "connector", "freehand", "path", "note", "image", "symbol"];
|
|
56
114
|
var EASES = ["linear", "easeIn", "easeOut", "easeInOut", "hold", "spring", "bounce", "elastic"];
|
|
57
115
|
var KNOWN_FIELDS = {
|
|
58
116
|
file: ["version", "pages", "images"],
|
|
59
|
-
page: ["id", "name", "layers", "elements", "theme", "guidesX", "guidesY", "guidesLocked", "timeline", "symbols", "variables", "flows"],
|
|
117
|
+
page: ["id", "name", "layers", "elements", "theme", "customTheme", "logic", "guidesX", "guidesY", "guidesLocked", "timeline", "symbols", "variables", "flows"],
|
|
60
118
|
flow: ["id", "from", "to", "every", "speed", "color", "size", "when", "spread", "volume", "overrides"],
|
|
61
119
|
flowOverride: ["when", "volume"],
|
|
62
120
|
symbol: ["id", "name", "width", "height", "layers", "elements", "timeline", "button", "params"],
|
|
63
121
|
layer: ["id", "name", "visible", "locked"],
|
|
64
122
|
rgba: ["r", "g", "b", "a"],
|
|
65
|
-
style: ["stroke", "fill", "fillStyle", "strokeWidth", "dash", "roughness", "bowing", "fontSize", "font", "gridSize", "opacity", "dashOffset", "textColor"],
|
|
123
|
+
style: ["stroke", "fill", "fillStyle", "strokeWidth", "dash", "roughness", "bowing", "fontSize", "font", "gridSize", "opacity", "dashOffset", "textColor", "named"],
|
|
124
|
+
colorNames: ["fill", "stroke", "textColor"],
|
|
125
|
+
customTheme: ["name", "background", "ink", "palette", "base"],
|
|
126
|
+
paletteColor: ["name", "color"],
|
|
66
127
|
element: [
|
|
67
128
|
"id",
|
|
68
129
|
"kind",
|
|
@@ -100,8 +161,11 @@ var KNOWN_FIELDS = {
|
|
|
100
161
|
"capacity",
|
|
101
162
|
"delay",
|
|
102
163
|
"queue",
|
|
103
|
-
"gauge"
|
|
164
|
+
"gauge",
|
|
165
|
+
"containerID",
|
|
166
|
+
"container"
|
|
104
167
|
],
|
|
168
|
+
container: ["fixed", "lanes"],
|
|
105
169
|
gauge: ["metric", "of", "max", "warn", "alarm", "style", "label"],
|
|
106
170
|
strokePoint: ["x", "y"],
|
|
107
171
|
subpath: ["closed", "coords"],
|
|
@@ -110,7 +174,7 @@ var KNOWN_FIELDS = {
|
|
|
110
174
|
extensionRef: ["id", "shape", "version", "params"],
|
|
111
175
|
normalizedPoint: ["x", "y"],
|
|
112
176
|
normalizedRect: ["x", "y", "width", "height"],
|
|
113
|
-
connector: ["start", "end", "routing", "startHead", "endHead", "waypoints", "hops", "cost"],
|
|
177
|
+
connector: ["start", "end", "routing", "startHead", "endHead", "waypoints", "hops", "avoid", "cost"],
|
|
114
178
|
waypoint: ["x", "y"],
|
|
115
179
|
timeline: ["fps", "duration", "boil", "settle", "poster", "tracks", "camera", "markers"],
|
|
116
180
|
track: ["element", "keys", "boil", "follow", "repeats", "march"],
|
|
@@ -119,7 +183,7 @@ var KNOWN_FIELDS = {
|
|
|
119
183
|
repeat: ["start", "end", "count"],
|
|
120
184
|
cameraKey: ["t", "x", "y", "width", "height", "ease"],
|
|
121
185
|
marker: ["t", "name", "loop"],
|
|
122
|
-
click: ["scene", "url", "set", "toggle", "reset"],
|
|
186
|
+
click: ["scene", "url", "set", "toggle", "reset", "run"],
|
|
123
187
|
hover: ["stroke", "fill", "opacity"],
|
|
124
188
|
override: ["when", "stroke", "fill", "textColor", "opacity", "strokeWidth", "dash", "text", "reroute", "offline", "cost", "capacity", "delay"]
|
|
125
189
|
};
|
|
@@ -192,9 +256,36 @@ var Reader = class {
|
|
|
192
256
|
gridSize: num(s.gridSize) ?? null,
|
|
193
257
|
opacity: num(s.opacity) ?? null,
|
|
194
258
|
dashOffset: num(s.dashOffset) ?? null,
|
|
195
|
-
textColor: rgba(s.textColor) ?? null
|
|
259
|
+
textColor: rgba(s.textColor) ?? null,
|
|
260
|
+
named: this.colorNames(s.named)
|
|
196
261
|
};
|
|
197
262
|
}
|
|
263
|
+
container(v) {
|
|
264
|
+
if (!isObj(v)) return null;
|
|
265
|
+
this.fields(v, "container");
|
|
266
|
+
return { fixed: v.fixed === true ? true : null, lanes: v.lanes === "rows" || v.lanes === "columns" ? v.lanes : null };
|
|
267
|
+
}
|
|
268
|
+
colorNames(v) {
|
|
269
|
+
if (!isObj(v)) return null;
|
|
270
|
+
this.fields(v, "colorNames");
|
|
271
|
+
const n = { fill: str(v.fill) ?? null, stroke: str(v.stroke) ?? null, textColor: str(v.textColor) ?? null };
|
|
272
|
+
return n.fill === null && n.stroke === null && n.textColor === null ? null : n;
|
|
273
|
+
}
|
|
274
|
+
customTheme(v) {
|
|
275
|
+
if (!isObj(v)) return null;
|
|
276
|
+
this.fields(v, "customTheme");
|
|
277
|
+
const background = rgba(v.background), ink = rgba(v.ink);
|
|
278
|
+
if (!background || !ink) {
|
|
279
|
+
this.warn("A custom theme needs a background and an ink; drawn without it");
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
const palette2 = (Array.isArray(v.palette) ? v.palette : []).filter(isObj).flatMap((p) => {
|
|
283
|
+
this.fields(p, "paletteColor");
|
|
284
|
+
const name = str(p.name), color = rgba(p.color);
|
|
285
|
+
return name && color ? [{ name, color }] : [];
|
|
286
|
+
});
|
|
287
|
+
return { name: str(v.name) ?? "Custom", background, ink, palette: palette2, base: str(v.base) ?? null };
|
|
288
|
+
}
|
|
198
289
|
endpoint(v) {
|
|
199
290
|
const e = isObj(v) ? v : {};
|
|
200
291
|
const port = str(e.port);
|
|
@@ -226,6 +317,7 @@ var Reader = class {
|
|
|
226
317
|
return { x: num(w.x) ?? 0, y: num(w.y) ?? 0 };
|
|
227
318
|
}) : null,
|
|
228
319
|
hops: typeof v.hops === "boolean" ? v.hops : null,
|
|
320
|
+
avoid: typeof v.avoid === "boolean" ? v.avoid : null,
|
|
229
321
|
cost: typeof v.cost === "number" && Number.isFinite(v.cost) ? v.cost : null
|
|
230
322
|
};
|
|
231
323
|
}
|
|
@@ -294,7 +386,9 @@ var Reader = class {
|
|
|
294
386
|
capacity: num(v.capacity) ?? null,
|
|
295
387
|
delay: num(v.delay) ?? null,
|
|
296
388
|
queue: v.queue === true ? true : null,
|
|
297
|
-
gauge: this.gauge(v.gauge)
|
|
389
|
+
gauge: this.gauge(v.gauge),
|
|
390
|
+
containerID: uuid(v.containerID) ?? null,
|
|
391
|
+
container: this.container(v.container)
|
|
298
392
|
};
|
|
299
393
|
}
|
|
300
394
|
gauge(v) {
|
|
@@ -336,7 +430,7 @@ var Reader = class {
|
|
|
336
430
|
variables(v) {
|
|
337
431
|
if (!isObj(v)) return null;
|
|
338
432
|
const out = {};
|
|
339
|
-
for (const [k, b] of Object.entries(v)) if (typeof b === "boolean") out[k] = b;
|
|
433
|
+
for (const [k, b] of Object.entries(v)) if (typeof b === "boolean" || typeof b === "number" && Number.isFinite(b)) out[k] = b;
|
|
340
434
|
return Object.keys(out).length > 0 ? out : null;
|
|
341
435
|
}
|
|
342
436
|
click(v) {
|
|
@@ -348,12 +442,22 @@ var Reader = class {
|
|
|
348
442
|
let set = null;
|
|
349
443
|
if (isObj(v.set)) {
|
|
350
444
|
set = {};
|
|
351
|
-
for (const [k, b] of Object.entries(v.set))
|
|
445
|
+
for (const [k, b] of Object.entries(v.set)) {
|
|
446
|
+
if (isName(k) && (typeof b === "boolean" || typeof b === "string" || typeof b === "number" && Number.isFinite(b))) set[k] = b;
|
|
447
|
+
}
|
|
352
448
|
if (Object.keys(set).length === 0) set = null;
|
|
353
449
|
}
|
|
354
450
|
const raw = typeof v.toggle === "string" ? [v.toggle] : Array.isArray(v.toggle) && v.toggle.every((x) => typeof x === "string") ? v.toggle : null;
|
|
355
451
|
const toggle = raw ? raw.filter(isName) : null;
|
|
356
|
-
|
|
452
|
+
const run = str(v.run);
|
|
453
|
+
return {
|
|
454
|
+
scene: str(v.scene) ?? null,
|
|
455
|
+
url: safe,
|
|
456
|
+
set,
|
|
457
|
+
toggle: toggle && toggle.length > 0 ? toggle : null,
|
|
458
|
+
reset: v.reset === true ? true : null,
|
|
459
|
+
run: run !== void 0 && isName(run) ? run : null
|
|
460
|
+
};
|
|
357
461
|
}
|
|
358
462
|
playback(v) {
|
|
359
463
|
const s = str(v);
|
|
@@ -515,6 +619,8 @@ var Reader = class {
|
|
|
515
619
|
layers,
|
|
516
620
|
elements: (Array.isArray(v.elements) ? v.elements : []).map((e) => this.element(e, images)).filter((e) => e !== null),
|
|
517
621
|
theme: str(v.theme) ?? null,
|
|
622
|
+
customTheme: this.customTheme(v.customTheme),
|
|
623
|
+
logic: typeof v.logic === "string" && v.logic.length > 0 ? v.logic : null,
|
|
518
624
|
timeline: this.timeline(v.timeline),
|
|
519
625
|
symbols: Array.isArray(v.symbols) ? v.symbols.map((x) => this.symbol(x, images)).filter((x) => x !== null) : null,
|
|
520
626
|
variables: this.variables(v.variables),
|
|
@@ -578,7 +684,7 @@ function parseDottie(source) {
|
|
|
578
684
|
if (isObj(v) && typeof v.data === "string") images[k] = reader.picture(v.data, str(v.format));
|
|
579
685
|
}
|
|
580
686
|
}
|
|
581
|
-
const pages = json.pages.map((p, i) => reader.page(p, images, i + 1));
|
|
687
|
+
const pages = json.pages.map((p, i) => resolvingPalette(reader.page(p, images, i + 1)));
|
|
582
688
|
if (new Set(pages.map((p) => p.id)).size !== pages.length) throw new DottieFormatError("Two pages share an id");
|
|
583
689
|
return { version, pages, warnings: reader.warnings };
|
|
584
690
|
}
|
|
@@ -656,6 +762,7 @@ function playerAttributes(options) {
|
|
|
656
762
|
if (options.noAutoplay) out["no-autoplay"] = "";
|
|
657
763
|
if (options.noLoop) out["no-loop"] = "";
|
|
658
764
|
if (options.label !== void 0) out.label = options.label;
|
|
765
|
+
if (options.logicEngine) out["allow-logic"] = "";
|
|
659
766
|
return out;
|
|
660
767
|
}
|
|
661
768
|
function compile(source, runtime, options = {}) {
|
|
@@ -688,10 +795,11 @@ function compile(source, runtime, options = {}) {
|
|
|
688
795
|
}
|
|
689
796
|
const fill = {
|
|
690
797
|
DOTTIE_DATA: () => scriptJSON(data),
|
|
691
|
-
DOTTIE_OPTIONS: () => scriptJSON(playerAttributes(options)),
|
|
798
|
+
DOTTIE_OPTIONS: () => scriptJSON(playerAttributes(file.pages[i].logic ? options : { ...options, logicEngine: void 0 })),
|
|
692
799
|
DOTTIE_FONTS: () => scriptJSON(fonts)
|
|
693
800
|
};
|
|
694
|
-
|
|
801
|
+
const engine = options.logicEngine && file.pages[i].logic ? options.logicEngine.trimEnd() + "\n" : "";
|
|
802
|
+
return engine + runtime.trimEnd() + "\n" + LOADER.replace(/\bDOTTIE_(?:DATA|OPTIONS|FONTS)\b/g, (m) => fill[m]());
|
|
695
803
|
}
|
|
696
804
|
export {
|
|
697
805
|
LOADER,
|
package/dist/dottie-compile.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
/*! dottie.js 0.
|
|
2
|
+
/*! dottie.js 0.11.0 | Apache-2.0 | https://github.com/phin-tech/dottie */
|
|
3
3
|
var __defProp = Object.defineProperty;
|
|
4
4
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
5
5
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
@@ -58,19 +58,80 @@ function isName(name) {
|
|
|
58
58
|
return name.length <= MAX_NAME && /^[A-Za-z_][A-Za-z0-9_]*$/.test(name);
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
// src/theme.ts
|
|
62
|
+
var rgb = (r, g, b) => ({ r, g, b, a: 1 });
|
|
63
|
+
function hex(h) {
|
|
64
|
+
const n = parseInt(h.slice(1), 16);
|
|
65
|
+
return { r: (n >> 16 & 255) / 255, g: (n >> 8 & 255) / 255, b: (n & 255) / 255, a: 1 };
|
|
66
|
+
}
|
|
67
|
+
var PALETTE_NAMES = ["accent", "muted", "danger", "warning", "success", "info"];
|
|
68
|
+
var palette = (colors) => PALETTE_NAMES.map((name, i) => ({ name, color: hex(colors[i]) }));
|
|
69
|
+
var LIGHT_PALETTE = palette(["#2563eb", "#6b7280", "#dc2626", "#d97706", "#16a34a", "#0891b2"]);
|
|
70
|
+
var DARK_PALETTE = palette(["#93c5fd", "#cbd5e1", "#fca5a5", "#fcd34d", "#86efac", "#67e8f9"]);
|
|
71
|
+
var themes = [
|
|
72
|
+
{ id: "paper", name: "Paper", background: rgb(0.984, 0.98, 0.965), ink: rgb(0.12, 0.12, 0.13), palette: LIGHT_PALETTE },
|
|
73
|
+
{ id: "white", name: "White", background: rgb(1, 1, 1), ink: rgb(0.12, 0.12, 0.13), palette: LIGHT_PALETTE },
|
|
74
|
+
{
|
|
75
|
+
id: "sepia",
|
|
76
|
+
name: "Sepia",
|
|
77
|
+
background: rgb(0.953, 0.914, 0.824),
|
|
78
|
+
ink: rgb(0.23, 0.18, 0.13),
|
|
79
|
+
palette: palette(["#9a3412", "#78716c", "#b91c1c", "#b45309", "#4d7c0f", "#0e7490"])
|
|
80
|
+
},
|
|
81
|
+
{ id: "blueprint", name: "Blueprint", background: rgb(0.106, 0.227, 0.361), ink: rgb(0.91, 0.945, 0.984), palette: DARK_PALETTE },
|
|
82
|
+
{ id: "chalkboard", name: "Chalkboard", background: rgb(0.169, 0.227, 0.2), ink: rgb(0.945, 0.945, 0.918), palette: DARK_PALETTE },
|
|
83
|
+
{ id: "midnight", name: "Midnight", background: rgb(0.082, 0.086, 0.102), ink: rgb(0.925, 0.925, 0.94), palette: DARK_PALETTE }
|
|
84
|
+
];
|
|
85
|
+
function themeNamed(id) {
|
|
86
|
+
return themes.find((t) => t.id === id) ?? null;
|
|
87
|
+
}
|
|
88
|
+
function pageTheme(page) {
|
|
89
|
+
const c = page.customTheme;
|
|
90
|
+
if (c) return { id: "custom", name: c.name, background: c.background, ink: c.ink, palette: c.palette };
|
|
91
|
+
return themeNamed(page.theme);
|
|
92
|
+
}
|
|
93
|
+
function paletteColor(page, name) {
|
|
94
|
+
const n = name.trim().toLowerCase();
|
|
95
|
+
return (pageTheme(page)?.palette ?? LIGHT_PALETTE).find((p) => p.name.toLowerCase() === n)?.color ?? null;
|
|
96
|
+
}
|
|
97
|
+
function resolvingPalette(page) {
|
|
98
|
+
const named = (e) => !!e.style.named;
|
|
99
|
+
if (!page.elements.some(named) && !(page.symbols ?? []).some((s) => s.elements.some(named))) return page;
|
|
100
|
+
const resolve = (e) => {
|
|
101
|
+
const n = e.style.named;
|
|
102
|
+
if (!n) return e;
|
|
103
|
+
const style = { ...e.style };
|
|
104
|
+
const f = n.fill !== null ? paletteColor(page, n.fill) : null;
|
|
105
|
+
const s = n.stroke !== null ? paletteColor(page, n.stroke) : null;
|
|
106
|
+
const t = n.textColor !== null ? paletteColor(page, n.textColor) : null;
|
|
107
|
+
if (f) style.fill = { ...f };
|
|
108
|
+
if (s) style.stroke = { ...s };
|
|
109
|
+
if (t) style.textColor = { ...t };
|
|
110
|
+
return { ...e, style };
|
|
111
|
+
};
|
|
112
|
+
return {
|
|
113
|
+
...page,
|
|
114
|
+
elements: page.elements.map(resolve),
|
|
115
|
+
symbols: page.symbols ? page.symbols.map((s) => ({ ...s, elements: s.elements.map(resolve) })) : page.symbols
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
61
119
|
// src/model.ts
|
|
62
|
-
var SUPPORTED_VERSION =
|
|
120
|
+
var SUPPORTED_VERSION = 3;
|
|
63
121
|
var KINDS = ["rectangle", "ellipse", "diamond", "text", "connector", "freehand", "path", "note", "image", "symbol"];
|
|
64
122
|
var EASES = ["linear", "easeIn", "easeOut", "easeInOut", "hold", "spring", "bounce", "elastic"];
|
|
65
123
|
var KNOWN_FIELDS = {
|
|
66
124
|
file: ["version", "pages", "images"],
|
|
67
|
-
page: ["id", "name", "layers", "elements", "theme", "guidesX", "guidesY", "guidesLocked", "timeline", "symbols", "variables", "flows"],
|
|
125
|
+
page: ["id", "name", "layers", "elements", "theme", "customTheme", "logic", "guidesX", "guidesY", "guidesLocked", "timeline", "symbols", "variables", "flows"],
|
|
68
126
|
flow: ["id", "from", "to", "every", "speed", "color", "size", "when", "spread", "volume", "overrides"],
|
|
69
127
|
flowOverride: ["when", "volume"],
|
|
70
128
|
symbol: ["id", "name", "width", "height", "layers", "elements", "timeline", "button", "params"],
|
|
71
129
|
layer: ["id", "name", "visible", "locked"],
|
|
72
130
|
rgba: ["r", "g", "b", "a"],
|
|
73
|
-
style: ["stroke", "fill", "fillStyle", "strokeWidth", "dash", "roughness", "bowing", "fontSize", "font", "gridSize", "opacity", "dashOffset", "textColor"],
|
|
131
|
+
style: ["stroke", "fill", "fillStyle", "strokeWidth", "dash", "roughness", "bowing", "fontSize", "font", "gridSize", "opacity", "dashOffset", "textColor", "named"],
|
|
132
|
+
colorNames: ["fill", "stroke", "textColor"],
|
|
133
|
+
customTheme: ["name", "background", "ink", "palette", "base"],
|
|
134
|
+
paletteColor: ["name", "color"],
|
|
74
135
|
element: [
|
|
75
136
|
"id",
|
|
76
137
|
"kind",
|
|
@@ -108,8 +169,11 @@ var KNOWN_FIELDS = {
|
|
|
108
169
|
"capacity",
|
|
109
170
|
"delay",
|
|
110
171
|
"queue",
|
|
111
|
-
"gauge"
|
|
172
|
+
"gauge",
|
|
173
|
+
"containerID",
|
|
174
|
+
"container"
|
|
112
175
|
],
|
|
176
|
+
container: ["fixed", "lanes"],
|
|
113
177
|
gauge: ["metric", "of", "max", "warn", "alarm", "style", "label"],
|
|
114
178
|
strokePoint: ["x", "y"],
|
|
115
179
|
subpath: ["closed", "coords"],
|
|
@@ -118,7 +182,7 @@ var KNOWN_FIELDS = {
|
|
|
118
182
|
extensionRef: ["id", "shape", "version", "params"],
|
|
119
183
|
normalizedPoint: ["x", "y"],
|
|
120
184
|
normalizedRect: ["x", "y", "width", "height"],
|
|
121
|
-
connector: ["start", "end", "routing", "startHead", "endHead", "waypoints", "hops", "cost"],
|
|
185
|
+
connector: ["start", "end", "routing", "startHead", "endHead", "waypoints", "hops", "avoid", "cost"],
|
|
122
186
|
waypoint: ["x", "y"],
|
|
123
187
|
timeline: ["fps", "duration", "boil", "settle", "poster", "tracks", "camera", "markers"],
|
|
124
188
|
track: ["element", "keys", "boil", "follow", "repeats", "march"],
|
|
@@ -127,7 +191,7 @@ var KNOWN_FIELDS = {
|
|
|
127
191
|
repeat: ["start", "end", "count"],
|
|
128
192
|
cameraKey: ["t", "x", "y", "width", "height", "ease"],
|
|
129
193
|
marker: ["t", "name", "loop"],
|
|
130
|
-
click: ["scene", "url", "set", "toggle", "reset"],
|
|
194
|
+
click: ["scene", "url", "set", "toggle", "reset", "run"],
|
|
131
195
|
hover: ["stroke", "fill", "opacity"],
|
|
132
196
|
override: ["when", "stroke", "fill", "textColor", "opacity", "strokeWidth", "dash", "text", "reroute", "offline", "cost", "capacity", "delay"]
|
|
133
197
|
};
|
|
@@ -200,9 +264,36 @@ var Reader = class {
|
|
|
200
264
|
gridSize: num(s.gridSize) ?? null,
|
|
201
265
|
opacity: num(s.opacity) ?? null,
|
|
202
266
|
dashOffset: num(s.dashOffset) ?? null,
|
|
203
|
-
textColor: rgba(s.textColor) ?? null
|
|
267
|
+
textColor: rgba(s.textColor) ?? null,
|
|
268
|
+
named: this.colorNames(s.named)
|
|
204
269
|
};
|
|
205
270
|
}
|
|
271
|
+
container(v) {
|
|
272
|
+
if (!isObj(v)) return null;
|
|
273
|
+
this.fields(v, "container");
|
|
274
|
+
return { fixed: v.fixed === true ? true : null, lanes: v.lanes === "rows" || v.lanes === "columns" ? v.lanes : null };
|
|
275
|
+
}
|
|
276
|
+
colorNames(v) {
|
|
277
|
+
if (!isObj(v)) return null;
|
|
278
|
+
this.fields(v, "colorNames");
|
|
279
|
+
const n = { fill: str(v.fill) ?? null, stroke: str(v.stroke) ?? null, textColor: str(v.textColor) ?? null };
|
|
280
|
+
return n.fill === null && n.stroke === null && n.textColor === null ? null : n;
|
|
281
|
+
}
|
|
282
|
+
customTheme(v) {
|
|
283
|
+
if (!isObj(v)) return null;
|
|
284
|
+
this.fields(v, "customTheme");
|
|
285
|
+
const background = rgba(v.background), ink = rgba(v.ink);
|
|
286
|
+
if (!background || !ink) {
|
|
287
|
+
this.warn("A custom theme needs a background and an ink; drawn without it");
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
const palette2 = (Array.isArray(v.palette) ? v.palette : []).filter(isObj).flatMap((p) => {
|
|
291
|
+
this.fields(p, "paletteColor");
|
|
292
|
+
const name = str(p.name), color = rgba(p.color);
|
|
293
|
+
return name && color ? [{ name, color }] : [];
|
|
294
|
+
});
|
|
295
|
+
return { name: str(v.name) ?? "Custom", background, ink, palette: palette2, base: str(v.base) ?? null };
|
|
296
|
+
}
|
|
206
297
|
endpoint(v) {
|
|
207
298
|
const e = isObj(v) ? v : {};
|
|
208
299
|
const port = str(e.port);
|
|
@@ -234,6 +325,7 @@ var Reader = class {
|
|
|
234
325
|
return { x: num(w.x) ?? 0, y: num(w.y) ?? 0 };
|
|
235
326
|
}) : null,
|
|
236
327
|
hops: typeof v.hops === "boolean" ? v.hops : null,
|
|
328
|
+
avoid: typeof v.avoid === "boolean" ? v.avoid : null,
|
|
237
329
|
cost: typeof v.cost === "number" && Number.isFinite(v.cost) ? v.cost : null
|
|
238
330
|
};
|
|
239
331
|
}
|
|
@@ -302,7 +394,9 @@ var Reader = class {
|
|
|
302
394
|
capacity: num(v.capacity) ?? null,
|
|
303
395
|
delay: num(v.delay) ?? null,
|
|
304
396
|
queue: v.queue === true ? true : null,
|
|
305
|
-
gauge: this.gauge(v.gauge)
|
|
397
|
+
gauge: this.gauge(v.gauge),
|
|
398
|
+
containerID: uuid(v.containerID) ?? null,
|
|
399
|
+
container: this.container(v.container)
|
|
306
400
|
};
|
|
307
401
|
}
|
|
308
402
|
gauge(v) {
|
|
@@ -344,7 +438,7 @@ var Reader = class {
|
|
|
344
438
|
variables(v) {
|
|
345
439
|
if (!isObj(v)) return null;
|
|
346
440
|
const out = {};
|
|
347
|
-
for (const [k, b] of Object.entries(v)) if (typeof b === "boolean") out[k] = b;
|
|
441
|
+
for (const [k, b] of Object.entries(v)) if (typeof b === "boolean" || typeof b === "number" && Number.isFinite(b)) out[k] = b;
|
|
348
442
|
return Object.keys(out).length > 0 ? out : null;
|
|
349
443
|
}
|
|
350
444
|
click(v) {
|
|
@@ -356,12 +450,22 @@ var Reader = class {
|
|
|
356
450
|
let set = null;
|
|
357
451
|
if (isObj(v.set)) {
|
|
358
452
|
set = {};
|
|
359
|
-
for (const [k, b] of Object.entries(v.set))
|
|
453
|
+
for (const [k, b] of Object.entries(v.set)) {
|
|
454
|
+
if (isName(k) && (typeof b === "boolean" || typeof b === "string" || typeof b === "number" && Number.isFinite(b))) set[k] = b;
|
|
455
|
+
}
|
|
360
456
|
if (Object.keys(set).length === 0) set = null;
|
|
361
457
|
}
|
|
362
458
|
const raw = typeof v.toggle === "string" ? [v.toggle] : Array.isArray(v.toggle) && v.toggle.every((x) => typeof x === "string") ? v.toggle : null;
|
|
363
459
|
const toggle = raw ? raw.filter(isName) : null;
|
|
364
|
-
|
|
460
|
+
const run = str(v.run);
|
|
461
|
+
return {
|
|
462
|
+
scene: str(v.scene) ?? null,
|
|
463
|
+
url: safe,
|
|
464
|
+
set,
|
|
465
|
+
toggle: toggle && toggle.length > 0 ? toggle : null,
|
|
466
|
+
reset: v.reset === true ? true : null,
|
|
467
|
+
run: run !== void 0 && isName(run) ? run : null
|
|
468
|
+
};
|
|
365
469
|
}
|
|
366
470
|
playback(v) {
|
|
367
471
|
const s = str(v);
|
|
@@ -523,6 +627,8 @@ var Reader = class {
|
|
|
523
627
|
layers,
|
|
524
628
|
elements: (Array.isArray(v.elements) ? v.elements : []).map((e) => this.element(e, images)).filter((e) => e !== null),
|
|
525
629
|
theme: str(v.theme) ?? null,
|
|
630
|
+
customTheme: this.customTheme(v.customTheme),
|
|
631
|
+
logic: typeof v.logic === "string" && v.logic.length > 0 ? v.logic : null,
|
|
526
632
|
timeline: this.timeline(v.timeline),
|
|
527
633
|
symbols: Array.isArray(v.symbols) ? v.symbols.map((x) => this.symbol(x, images)).filter((x) => x !== null) : null,
|
|
528
634
|
variables: this.variables(v.variables),
|
|
@@ -586,7 +692,7 @@ function parseDottie(source2) {
|
|
|
586
692
|
if (isObj(v) && typeof v.data === "string") images[k] = reader.picture(v.data, str(v.format));
|
|
587
693
|
}
|
|
588
694
|
}
|
|
589
|
-
const pages = json.pages.map((p, i) => reader.page(p, images, i + 1));
|
|
695
|
+
const pages = json.pages.map((p, i) => resolvingPalette(reader.page(p, images, i + 1)));
|
|
590
696
|
if (new Set(pages.map((p) => p.id)).size !== pages.length) throw new DottieFormatError("Two pages share an id");
|
|
591
697
|
return { version, pages, warnings: reader.warnings };
|
|
592
698
|
}
|
|
@@ -664,6 +770,7 @@ function playerAttributes(options2) {
|
|
|
664
770
|
if (options2.noAutoplay) out["no-autoplay"] = "";
|
|
665
771
|
if (options2.noLoop) out["no-loop"] = "";
|
|
666
772
|
if (options2.label !== void 0) out.label = options2.label;
|
|
773
|
+
if (options2.logicEngine) out["allow-logic"] = "";
|
|
667
774
|
return out;
|
|
668
775
|
}
|
|
669
776
|
function compile(source2, runtime, options2 = {}) {
|
|
@@ -696,14 +803,15 @@ function compile(source2, runtime, options2 = {}) {
|
|
|
696
803
|
}
|
|
697
804
|
const fill = {
|
|
698
805
|
DOTTIE_DATA: () => scriptJSON(data),
|
|
699
|
-
DOTTIE_OPTIONS: () => scriptJSON(playerAttributes(options2)),
|
|
806
|
+
DOTTIE_OPTIONS: () => scriptJSON(playerAttributes(file.pages[i].logic ? options2 : { ...options2, logicEngine: void 0 })),
|
|
700
807
|
DOTTIE_FONTS: () => scriptJSON(fonts)
|
|
701
808
|
};
|
|
702
|
-
|
|
809
|
+
const engine = options2.logicEngine && file.pages[i].logic ? options2.logicEngine.trimEnd() + "\n" : "";
|
|
810
|
+
return engine + runtime.trimEnd() + "\n" + LOADER.replace(/\bDOTTIE_(?:DATA|OPTIONS|FONTS)\b/g, (m) => fill[m]());
|
|
703
811
|
}
|
|
704
812
|
|
|
705
813
|
// src/index.ts
|
|
706
|
-
var VERSION = "0.
|
|
814
|
+
var VERSION = "0.11.0";
|
|
707
815
|
|
|
708
816
|
// src/cli.ts
|
|
709
817
|
var usage = `dottie-compile ${VERSION}: a Dottie drawing as one script that plays it on any page.
|
|
@@ -720,6 +828,7 @@ OPTIONS
|
|
|
720
828
|
--at <seconds> Show that moment as a still
|
|
721
829
|
--clean Draw precisely instead of sketchy
|
|
722
830
|
--no-controls, --no-autoplay, --no-loop
|
|
831
|
+
--allow-logic let the page's click logic run (ADR 0007); it carries the engine (about 850 KB)
|
|
723
832
|
--label <text> The player's accessible name
|
|
724
833
|
`;
|
|
725
834
|
function fail(message, code = 2) {
|
|
@@ -729,6 +838,7 @@ function fail(message, code = 2) {
|
|
|
729
838
|
}
|
|
730
839
|
var args = process.argv.slice(2);
|
|
731
840
|
var options = {};
|
|
841
|
+
var allowLogic = false;
|
|
732
842
|
var input;
|
|
733
843
|
var output;
|
|
734
844
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -770,6 +880,9 @@ for (let i = 0; i < args.length; i++) {
|
|
|
770
880
|
case "--no-loop":
|
|
771
881
|
options.noLoop = true;
|
|
772
882
|
break;
|
|
883
|
+
case "--allow-logic":
|
|
884
|
+
allowLogic = true;
|
|
885
|
+
break;
|
|
773
886
|
case "--label":
|
|
774
887
|
options.label = value();
|
|
775
888
|
break;
|
|
@@ -800,7 +913,8 @@ try {
|
|
|
800
913
|
return void 0;
|
|
801
914
|
}
|
|
802
915
|
};
|
|
803
|
-
const
|
|
916
|
+
const logicEngine = allowLogic ? readFileSync(new URL("./dottie-logic.min.js", import.meta.url), "utf8") : void 0;
|
|
917
|
+
const js = compile(source, runtime, { ...options, fontData, logicEngine });
|
|
804
918
|
if (output === "-") process.stdout.write(js);
|
|
805
919
|
else {
|
|
806
920
|
const target = output ?? input.replace(/\.dottie$/i, "") + ".js";
|