@phin-tech/dottie 0.10.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 +2 -0
- package/README.md +15 -0
- package/dist/compile.js +22 -9
- package/dist/dottie-compile.mjs +30 -11
- package/dist/dottie-logic.min.js +2171 -0
- package/dist/dottie-player.js +483 -141
- package/dist/dottie-player.min.js +58 -56
- package/dist/dottie-sim.min.js +5 -4
- package/dist/dottie.js +483 -141
- package/dist/types/clicks.d.ts +3 -2
- package/dist/types/compile.d.ts +3 -0
- package/dist/types/expression.d.ts +51 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/logic-entry.d.ts +1 -0
- package/dist/types/logic-loader.d.ts +9 -0
- package/dist/types/logic-polyfill.d.ts +2 -0
- package/dist/types/logic.d.ts +32 -0
- package/dist/types/measure.d.ts +4 -3
- package/dist/types/model.d.ts +10 -3
- package/dist/types/player.d.ts +8 -3
- package/dist/types/state.d.ts +14 -13
- package/package.json +3 -1
package/PARITY.md
CHANGED
|
@@ -24,6 +24,8 @@ 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). |
|
|
27
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). |
|
|
28
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). |
|
|
29
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`). |
|
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);
|
|
@@ -109,12 +109,12 @@ function resolvingPalette(page) {
|
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
// src/model.ts
|
|
112
|
-
var SUPPORTED_VERSION =
|
|
112
|
+
var SUPPORTED_VERSION = 3;
|
|
113
113
|
var KINDS = ["rectangle", "ellipse", "diamond", "text", "connector", "freehand", "path", "note", "image", "symbol"];
|
|
114
114
|
var EASES = ["linear", "easeIn", "easeOut", "easeInOut", "hold", "spring", "bounce", "elastic"];
|
|
115
115
|
var KNOWN_FIELDS = {
|
|
116
116
|
file: ["version", "pages", "images"],
|
|
117
|
-
page: ["id", "name", "layers", "elements", "theme", "customTheme", "guidesX", "guidesY", "guidesLocked", "timeline", "symbols", "variables", "flows"],
|
|
117
|
+
page: ["id", "name", "layers", "elements", "theme", "customTheme", "logic", "guidesX", "guidesY", "guidesLocked", "timeline", "symbols", "variables", "flows"],
|
|
118
118
|
flow: ["id", "from", "to", "every", "speed", "color", "size", "when", "spread", "volume", "overrides"],
|
|
119
119
|
flowOverride: ["when", "volume"],
|
|
120
120
|
symbol: ["id", "name", "width", "height", "layers", "elements", "timeline", "button", "params"],
|
|
@@ -183,7 +183,7 @@ var KNOWN_FIELDS = {
|
|
|
183
183
|
repeat: ["start", "end", "count"],
|
|
184
184
|
cameraKey: ["t", "x", "y", "width", "height", "ease"],
|
|
185
185
|
marker: ["t", "name", "loop"],
|
|
186
|
-
click: ["scene", "url", "set", "toggle", "reset"],
|
|
186
|
+
click: ["scene", "url", "set", "toggle", "reset", "run"],
|
|
187
187
|
hover: ["stroke", "fill", "opacity"],
|
|
188
188
|
override: ["when", "stroke", "fill", "textColor", "opacity", "strokeWidth", "dash", "text", "reroute", "offline", "cost", "capacity", "delay"]
|
|
189
189
|
};
|
|
@@ -430,7 +430,7 @@ var Reader = class {
|
|
|
430
430
|
variables(v) {
|
|
431
431
|
if (!isObj(v)) return null;
|
|
432
432
|
const out = {};
|
|
433
|
-
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;
|
|
434
434
|
return Object.keys(out).length > 0 ? out : null;
|
|
435
435
|
}
|
|
436
436
|
click(v) {
|
|
@@ -442,12 +442,22 @@ var Reader = class {
|
|
|
442
442
|
let set = null;
|
|
443
443
|
if (isObj(v.set)) {
|
|
444
444
|
set = {};
|
|
445
|
-
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
|
+
}
|
|
446
448
|
if (Object.keys(set).length === 0) set = null;
|
|
447
449
|
}
|
|
448
450
|
const raw = typeof v.toggle === "string" ? [v.toggle] : Array.isArray(v.toggle) && v.toggle.every((x) => typeof x === "string") ? v.toggle : null;
|
|
449
451
|
const toggle = raw ? raw.filter(isName) : null;
|
|
450
|
-
|
|
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
|
+
};
|
|
451
461
|
}
|
|
452
462
|
playback(v) {
|
|
453
463
|
const s = str(v);
|
|
@@ -610,6 +620,7 @@ var Reader = class {
|
|
|
610
620
|
elements: (Array.isArray(v.elements) ? v.elements : []).map((e) => this.element(e, images)).filter((e) => e !== null),
|
|
611
621
|
theme: str(v.theme) ?? null,
|
|
612
622
|
customTheme: this.customTheme(v.customTheme),
|
|
623
|
+
logic: typeof v.logic === "string" && v.logic.length > 0 ? v.logic : null,
|
|
613
624
|
timeline: this.timeline(v.timeline),
|
|
614
625
|
symbols: Array.isArray(v.symbols) ? v.symbols.map((x) => this.symbol(x, images)).filter((x) => x !== null) : null,
|
|
615
626
|
variables: this.variables(v.variables),
|
|
@@ -751,6 +762,7 @@ function playerAttributes(options) {
|
|
|
751
762
|
if (options.noAutoplay) out["no-autoplay"] = "";
|
|
752
763
|
if (options.noLoop) out["no-loop"] = "";
|
|
753
764
|
if (options.label !== void 0) out.label = options.label;
|
|
765
|
+
if (options.logicEngine) out["allow-logic"] = "";
|
|
754
766
|
return out;
|
|
755
767
|
}
|
|
756
768
|
function compile(source, runtime, options = {}) {
|
|
@@ -783,10 +795,11 @@ function compile(source, runtime, options = {}) {
|
|
|
783
795
|
}
|
|
784
796
|
const fill = {
|
|
785
797
|
DOTTIE_DATA: () => scriptJSON(data),
|
|
786
|
-
DOTTIE_OPTIONS: () => scriptJSON(playerAttributes(options)),
|
|
798
|
+
DOTTIE_OPTIONS: () => scriptJSON(playerAttributes(file.pages[i].logic ? options : { ...options, logicEngine: void 0 })),
|
|
787
799
|
DOTTIE_FONTS: () => scriptJSON(fonts)
|
|
788
800
|
};
|
|
789
|
-
|
|
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]());
|
|
790
803
|
}
|
|
791
804
|
export {
|
|
792
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);
|
|
@@ -117,12 +117,12 @@ function resolvingPalette(page) {
|
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
// src/model.ts
|
|
120
|
-
var SUPPORTED_VERSION =
|
|
120
|
+
var SUPPORTED_VERSION = 3;
|
|
121
121
|
var KINDS = ["rectangle", "ellipse", "diamond", "text", "connector", "freehand", "path", "note", "image", "symbol"];
|
|
122
122
|
var EASES = ["linear", "easeIn", "easeOut", "easeInOut", "hold", "spring", "bounce", "elastic"];
|
|
123
123
|
var KNOWN_FIELDS = {
|
|
124
124
|
file: ["version", "pages", "images"],
|
|
125
|
-
page: ["id", "name", "layers", "elements", "theme", "customTheme", "guidesX", "guidesY", "guidesLocked", "timeline", "symbols", "variables", "flows"],
|
|
125
|
+
page: ["id", "name", "layers", "elements", "theme", "customTheme", "logic", "guidesX", "guidesY", "guidesLocked", "timeline", "symbols", "variables", "flows"],
|
|
126
126
|
flow: ["id", "from", "to", "every", "speed", "color", "size", "when", "spread", "volume", "overrides"],
|
|
127
127
|
flowOverride: ["when", "volume"],
|
|
128
128
|
symbol: ["id", "name", "width", "height", "layers", "elements", "timeline", "button", "params"],
|
|
@@ -191,7 +191,7 @@ var KNOWN_FIELDS = {
|
|
|
191
191
|
repeat: ["start", "end", "count"],
|
|
192
192
|
cameraKey: ["t", "x", "y", "width", "height", "ease"],
|
|
193
193
|
marker: ["t", "name", "loop"],
|
|
194
|
-
click: ["scene", "url", "set", "toggle", "reset"],
|
|
194
|
+
click: ["scene", "url", "set", "toggle", "reset", "run"],
|
|
195
195
|
hover: ["stroke", "fill", "opacity"],
|
|
196
196
|
override: ["when", "stroke", "fill", "textColor", "opacity", "strokeWidth", "dash", "text", "reroute", "offline", "cost", "capacity", "delay"]
|
|
197
197
|
};
|
|
@@ -438,7 +438,7 @@ var Reader = class {
|
|
|
438
438
|
variables(v) {
|
|
439
439
|
if (!isObj(v)) return null;
|
|
440
440
|
const out = {};
|
|
441
|
-
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;
|
|
442
442
|
return Object.keys(out).length > 0 ? out : null;
|
|
443
443
|
}
|
|
444
444
|
click(v) {
|
|
@@ -450,12 +450,22 @@ var Reader = class {
|
|
|
450
450
|
let set = null;
|
|
451
451
|
if (isObj(v.set)) {
|
|
452
452
|
set = {};
|
|
453
|
-
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
|
+
}
|
|
454
456
|
if (Object.keys(set).length === 0) set = null;
|
|
455
457
|
}
|
|
456
458
|
const raw = typeof v.toggle === "string" ? [v.toggle] : Array.isArray(v.toggle) && v.toggle.every((x) => typeof x === "string") ? v.toggle : null;
|
|
457
459
|
const toggle = raw ? raw.filter(isName) : null;
|
|
458
|
-
|
|
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
|
+
};
|
|
459
469
|
}
|
|
460
470
|
playback(v) {
|
|
461
471
|
const s = str(v);
|
|
@@ -618,6 +628,7 @@ var Reader = class {
|
|
|
618
628
|
elements: (Array.isArray(v.elements) ? v.elements : []).map((e) => this.element(e, images)).filter((e) => e !== null),
|
|
619
629
|
theme: str(v.theme) ?? null,
|
|
620
630
|
customTheme: this.customTheme(v.customTheme),
|
|
631
|
+
logic: typeof v.logic === "string" && v.logic.length > 0 ? v.logic : null,
|
|
621
632
|
timeline: this.timeline(v.timeline),
|
|
622
633
|
symbols: Array.isArray(v.symbols) ? v.symbols.map((x) => this.symbol(x, images)).filter((x) => x !== null) : null,
|
|
623
634
|
variables: this.variables(v.variables),
|
|
@@ -759,6 +770,7 @@ function playerAttributes(options2) {
|
|
|
759
770
|
if (options2.noAutoplay) out["no-autoplay"] = "";
|
|
760
771
|
if (options2.noLoop) out["no-loop"] = "";
|
|
761
772
|
if (options2.label !== void 0) out.label = options2.label;
|
|
773
|
+
if (options2.logicEngine) out["allow-logic"] = "";
|
|
762
774
|
return out;
|
|
763
775
|
}
|
|
764
776
|
function compile(source2, runtime, options2 = {}) {
|
|
@@ -791,14 +803,15 @@ function compile(source2, runtime, options2 = {}) {
|
|
|
791
803
|
}
|
|
792
804
|
const fill = {
|
|
793
805
|
DOTTIE_DATA: () => scriptJSON(data),
|
|
794
|
-
DOTTIE_OPTIONS: () => scriptJSON(playerAttributes(options2)),
|
|
806
|
+
DOTTIE_OPTIONS: () => scriptJSON(playerAttributes(file.pages[i].logic ? options2 : { ...options2, logicEngine: void 0 })),
|
|
795
807
|
DOTTIE_FONTS: () => scriptJSON(fonts)
|
|
796
808
|
};
|
|
797
|
-
|
|
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]());
|
|
798
811
|
}
|
|
799
812
|
|
|
800
813
|
// src/index.ts
|
|
801
|
-
var VERSION = "0.
|
|
814
|
+
var VERSION = "0.11.0";
|
|
802
815
|
|
|
803
816
|
// src/cli.ts
|
|
804
817
|
var usage = `dottie-compile ${VERSION}: a Dottie drawing as one script that plays it on any page.
|
|
@@ -815,6 +828,7 @@ OPTIONS
|
|
|
815
828
|
--at <seconds> Show that moment as a still
|
|
816
829
|
--clean Draw precisely instead of sketchy
|
|
817
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)
|
|
818
832
|
--label <text> The player's accessible name
|
|
819
833
|
`;
|
|
820
834
|
function fail(message, code = 2) {
|
|
@@ -824,6 +838,7 @@ function fail(message, code = 2) {
|
|
|
824
838
|
}
|
|
825
839
|
var args = process.argv.slice(2);
|
|
826
840
|
var options = {};
|
|
841
|
+
var allowLogic = false;
|
|
827
842
|
var input;
|
|
828
843
|
var output;
|
|
829
844
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -865,6 +880,9 @@ for (let i = 0; i < args.length; i++) {
|
|
|
865
880
|
case "--no-loop":
|
|
866
881
|
options.noLoop = true;
|
|
867
882
|
break;
|
|
883
|
+
case "--allow-logic":
|
|
884
|
+
allowLogic = true;
|
|
885
|
+
break;
|
|
868
886
|
case "--label":
|
|
869
887
|
options.label = value();
|
|
870
888
|
break;
|
|
@@ -895,7 +913,8 @@ try {
|
|
|
895
913
|
return void 0;
|
|
896
914
|
}
|
|
897
915
|
};
|
|
898
|
-
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 });
|
|
899
918
|
if (output === "-") process.stdout.write(js);
|
|
900
919
|
else {
|
|
901
920
|
const target = output ?? input.replace(/\.dottie$/i, "") + ".js";
|