@osmo.inc/cli-linux-arm64 0.1.19 → 0.1.20
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/dist/studio.js +920 -329
- package/package.json +1 -1
- package/release.json +32 -32
- package/runtime/apps/native/tools/compile.mjs +10 -2
- package/runtime/packages/scene/src/assets.ts +27 -0
- package/runtime/packages/scene/src/elements.ts +29 -13
- package/runtime/packages/scene/src/evaluate.ts +58 -21
- package/runtime/packages/scene/src/index.ts +4 -0
- package/runtime/packages/scene/src/jsx-runtime.ts +9 -3
- package/runtime/packages/scene/src/knob-schema.ts +5 -2
- package/runtime/packages/scene/src/knobs.ts +50 -10
- package/runtime/packages/scene/src/motion-sampling.d.mts +1 -0
- package/runtime/packages/scene/src/motion-sampling.mjs +33 -0
- package/runtime/packages/scene/src/motion.ts +5 -5
- package/runtime/packages/scene/src/text-animation.ts +239 -0
- package/studio/src/app.css +1 -1
- package/studio/src/app.js +207 -198
- package/studio/src/engine_bg.wasm +0 -0
- package/third-party/inventory.json +1061 -78
- package/third-party/npm/@phosphor-icons__react@2.1.10/LICENSE +21 -0
- package/third-party/npm/@phosphor-icons__react@2.1.10/package.json +113 -0
- package/third-party/npm/@radix-ui__number@1.1.1/package.json +62 -0
- package/third-party/npm/@radix-ui__number@1.1.1/radix-ui-primitives-ee4e3197bffe-LICENSE +21 -0
- package/third-party/npm/@radix-ui__primitive@1.1.3/LICENSE +21 -0
- package/third-party/npm/@radix-ui__primitive@1.1.3/package.json +47 -0
- package/third-party/npm/@radix-ui__react-accessible-icon@1.1.7/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-accessible-icon@1.1.7/package.json +68 -0
- package/third-party/npm/@radix-ui__react-accordion@1.2.12/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-accordion@1.2.12/package.json +76 -0
- package/third-party/npm/@radix-ui__react-alert-dialog@1.1.15/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-alert-dialog@1.1.15/package.json +73 -0
- package/third-party/npm/@radix-ui__react-arrow@1.1.7/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-arrow@1.1.7/package.json +68 -0
- package/third-party/npm/@radix-ui__react-aspect-ratio@1.1.7/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-aspect-ratio@1.1.7/package.json +68 -0
- package/third-party/npm/@radix-ui__react-avatar@1.1.10/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-avatar@1.1.10/package.json +72 -0
- package/third-party/npm/@radix-ui__react-checkbox@1.3.3/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-checkbox@1.3.3/package.json +75 -0
- package/third-party/npm/@radix-ui__react-collapsible@1.1.12/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-collapsible@1.1.12/package.json +75 -0
- package/third-party/npm/@radix-ui__react-collection@1.1.7/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-collection@1.1.7/package.json +71 -0
- package/third-party/npm/@radix-ui__react-compose-refs@1.1.2/package.json +75 -0
- package/third-party/npm/@radix-ui__react-compose-refs@1.1.2/radix-ui-primitives-ee4e3197bffe-LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-context-menu@2.2.16/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-context-menu@2.2.16/package.json +74 -0
- package/third-party/npm/@radix-ui__react-context@1.1.2/package.json +75 -0
- package/third-party/npm/@radix-ui__react-context@1.1.2/radix-ui-primitives-ee4e3197bffe-LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-dialog@1.1.15/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-dialog@1.1.15/package.json +81 -0
- package/third-party/npm/@radix-ui__react-direction@1.1.1/package.json +75 -0
- package/third-party/npm/@radix-ui__react-direction@1.1.1/radix-ui-primitives-ee4e3197bffe-LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-dismissable-layer@1.1.11/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-dismissable-layer@1.1.11/package.json +73 -0
- package/third-party/npm/@radix-ui__react-dropdown-menu@2.1.16/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-dropdown-menu@2.1.16/package.json +75 -0
- package/third-party/npm/@radix-ui__react-focus-guards@1.1.3/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-focus-guards@1.1.3/package.json +60 -0
- package/third-party/npm/@radix-ui__react-focus-scope@1.1.7/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-focus-scope@1.1.7/package.json +70 -0
- package/third-party/npm/@radix-ui__react-form@0.1.8/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-form@0.1.8/package.json +73 -0
- package/third-party/npm/@radix-ui__react-hover-card@1.1.15/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-hover-card@1.1.15/package.json +76 -0
- package/third-party/npm/@radix-ui__react-id@1.1.1/package.json +78 -0
- package/third-party/npm/@radix-ui__react-id@1.1.1/radix-ui-primitives-ee4e3197bffe-LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-label@2.1.7/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-label@2.1.7/package.json +68 -0
- package/third-party/npm/@radix-ui__react-menu@2.1.16/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-menu@2.1.16/package.json +86 -0
- package/third-party/npm/@radix-ui__react-menubar@1.1.16/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-menubar@1.1.16/package.json +78 -0
- package/third-party/npm/@radix-ui__react-navigation-menu@1.2.14/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-navigation-menu@1.2.14/package.json +81 -0
- package/third-party/npm/@radix-ui__react-one-time-password-field@0.1.8/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-one-time-password-field@0.1.8/package.json +80 -0
- package/third-party/npm/@radix-ui__react-password-toggle-field@0.1.3/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-password-toggle-field@0.1.3/package.json +76 -0
- package/third-party/npm/@radix-ui__react-popover@1.1.15/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-popover@1.1.15/package.json +82 -0
- package/third-party/npm/@radix-ui__react-popper@1.2.8/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-popper@1.2.8/package.json +77 -0
- package/third-party/npm/@radix-ui__react-portal@1.1.9/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-portal@1.1.9/package.json +69 -0
- package/third-party/npm/@radix-ui__react-presence@1.1.5/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-presence@1.1.5/package.json +69 -0
- package/third-party/npm/@radix-ui__react-primitive@2.1.3/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-primitive@2.1.3/package.json +68 -0
- package/third-party/npm/@radix-ui__react-progress@1.1.7/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-progress@1.1.7/package.json +69 -0
- package/third-party/npm/@radix-ui__react-radio-group@1.3.8/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-radio-group@1.3.8/package.json +77 -0
- package/third-party/npm/@radix-ui__react-roving-focus@1.1.11/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-roving-focus@1.1.11/package.json +76 -0
- package/third-party/npm/@radix-ui__react-scroll-area@1.2.10/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-scroll-area@1.2.10/package.json +76 -0
- package/third-party/npm/@radix-ui__react-select@2.2.6/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-select@2.2.6/package.json +89 -0
- package/third-party/npm/@radix-ui__react-separator@1.1.7/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-separator@1.1.7/package.json +68 -0
- package/third-party/npm/@radix-ui__react-slider@1.3.6/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-slider@1.3.6/package.json +78 -0
- package/third-party/npm/@radix-ui__react-slot@1.2.3/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-slot@1.2.3/package.json +63 -0
- package/third-party/npm/@radix-ui__react-switch@1.2.6/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-switch@1.2.6/package.json +74 -0
- package/third-party/npm/@radix-ui__react-tabs@1.1.13/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-tabs@1.1.13/package.json +75 -0
- package/third-party/npm/@radix-ui__react-toast@1.2.15/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-toast@1.2.15/package.json +79 -0
- package/third-party/npm/@radix-ui__react-toggle-group@1.1.11/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-toggle-group@1.1.11/package.json +74 -0
- package/third-party/npm/@radix-ui__react-toggle@1.1.10/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-toggle@1.1.10/package.json +70 -0
- package/third-party/npm/@radix-ui__react-toolbar@1.1.11/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-toolbar@1.1.11/package.json +74 -0
- package/third-party/npm/@radix-ui__react-tooltip@1.2.8/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-tooltip@1.2.8/package.json +79 -0
- package/third-party/npm/@radix-ui__react-use-callback-ref@1.1.1/package.json +75 -0
- package/third-party/npm/@radix-ui__react-use-callback-ref@1.1.1/radix-ui-primitives-ee4e3197bffe-LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-use-controllable-state@1.2.2/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-use-controllable-state@1.2.2/package.json +64 -0
- package/third-party/npm/@radix-ui__react-use-effect-event@0.0.2/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-use-effect-event@0.0.2/package.json +65 -0
- package/third-party/npm/@radix-ui__react-use-escape-keydown@1.1.1/package.json +78 -0
- package/third-party/npm/@radix-ui__react-use-escape-keydown@1.1.1/radix-ui-primitives-ee4e3197bffe-LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-use-is-hydrated@0.1.0/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-use-is-hydrated@0.1.0/package.json +65 -0
- package/third-party/npm/@radix-ui__react-use-layout-effect@1.1.1/package.json +75 -0
- package/third-party/npm/@radix-ui__react-use-layout-effect@1.1.1/radix-ui-primitives-ee4e3197bffe-LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-use-previous@1.1.1/package.json +75 -0
- package/third-party/npm/@radix-ui__react-use-previous@1.1.1/radix-ui-primitives-ee4e3197bffe-LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-use-size@1.1.1/package.json +79 -0
- package/third-party/npm/@radix-ui__react-use-size@1.1.1/radix-ui-primitives-ee4e3197bffe-LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-visually-hidden@1.2.3/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-visually-hidden@1.2.3/package.json +68 -0
- package/third-party/npm/color-convert@3.1.3/LICENSE +22 -0
- package/third-party/npm/color-convert@3.1.3/package.json +58 -0
- package/third-party/npm/color-name@2.1.0/LICENSE +8 -0
- package/third-party/npm/color-name@2.1.0/package.json +32 -0
- package/third-party/npm/color-string@2.1.4/LICENSE +21 -0
- package/third-party/npm/color-string@2.1.4/package.json +47 -0
- package/third-party/npm/color@5.0.3/LICENSE +21 -0
- package/third-party/npm/color@5.0.3/package.json +52 -0
- package/third-party/npm/radix-ui@1.4.3/LICENSE +21 -0
- package/third-party/npm/radix-ui@1.4.3/package.json +136 -0
- package/third-party/npm/use-sync-external-store@1.6.0/LICENSE +21 -0
- package/third-party/npm/use-sync-external-store@1.6.0/package.json +43 -0
- package/vendor/osmo-lite +0 -0
- package/vendor/osmo-scene-0.1.0.tgz +0 -0
- package/vendor/platform.json +1 -1
package/package.json
CHANGED
package/release.json
CHANGED
|
@@ -1,36 +1,36 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.1.
|
|
2
|
+
"version": "0.1.20",
|
|
3
3
|
"package": "@osmo.inc/cli-linux-arm64",
|
|
4
4
|
"platform": "linux-arm64",
|
|
5
5
|
"channel": "latest",
|
|
6
|
-
"sourceCommit": "
|
|
6
|
+
"sourceCommit": "d884ae69ee0ce8f9171efbfcef5c5f9feb43c7e6",
|
|
7
7
|
"sourceDirty": false,
|
|
8
8
|
"signing": "unsigned",
|
|
9
9
|
"licenseFileIncluded": false,
|
|
10
|
-
"dependencyCount":
|
|
10
|
+
"dependencyCount": 297,
|
|
11
11
|
"nativeDefaultFeatures": false,
|
|
12
12
|
"contents": {
|
|
13
|
-
"totalBytes":
|
|
14
|
-
"fileCount":
|
|
13
|
+
"totalBytes": 53814153,
|
|
14
|
+
"fileCount": 1079,
|
|
15
15
|
"groups": {
|
|
16
|
-
"vendor":
|
|
17
|
-
"studio":
|
|
16
|
+
"vendor": 18416740,
|
|
17
|
+
"studio": 17474729,
|
|
18
18
|
"node_modules/typescript": 9913238,
|
|
19
|
-
"third-party":
|
|
19
|
+
"third-party": 4936088,
|
|
20
20
|
"src": 2622428,
|
|
21
|
-
"dist":
|
|
22
|
-
"runtime":
|
|
21
|
+
"dist": 293259,
|
|
22
|
+
"runtime": 155767,
|
|
23
23
|
"THIRD_PARTY_NOTICES.md": 1090,
|
|
24
24
|
"package.json": 814
|
|
25
25
|
},
|
|
26
26
|
"largestFiles": [
|
|
27
27
|
{
|
|
28
28
|
"path": "vendor/osmo-lite",
|
|
29
|
-
"bytes":
|
|
29
|
+
"bytes": 18376392
|
|
30
30
|
},
|
|
31
31
|
{
|
|
32
32
|
"path": "studio/src/engine_bg.wasm",
|
|
33
|
-
"bytes":
|
|
33
|
+
"bytes": 12038924
|
|
34
34
|
},
|
|
35
35
|
{
|
|
36
36
|
"path": "node_modules/typescript/lib/typescript.js",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
},
|
|
39
39
|
{
|
|
40
40
|
"path": "studio/src/app.js",
|
|
41
|
-
"bytes":
|
|
41
|
+
"bytes": 5055327
|
|
42
42
|
},
|
|
43
43
|
{
|
|
44
44
|
"path": "node_modules/typescript/lib/lib.dom.d.ts",
|
|
@@ -65,12 +65,12 @@
|
|
|
65
65
|
"bytes": 370430
|
|
66
66
|
},
|
|
67
67
|
{
|
|
68
|
-
"path": "
|
|
69
|
-
"bytes":
|
|
68
|
+
"path": "dist/studio.js",
|
|
69
|
+
"bytes": 293259
|
|
70
70
|
},
|
|
71
71
|
{
|
|
72
|
-
"path": "
|
|
73
|
-
"bytes":
|
|
72
|
+
"path": "third-party/rust-standard-library/COPYRIGHT-library.html",
|
|
73
|
+
"bytes": 279302
|
|
74
74
|
},
|
|
75
75
|
{
|
|
76
76
|
"path": "node_modules/typescript/lib/lib.es5.d.ts",
|
|
@@ -80,10 +80,18 @@
|
|
|
80
80
|
"path": "src/starter/animations/osmo/shape34-0.tsx",
|
|
81
81
|
"bytes": 139902
|
|
82
82
|
},
|
|
83
|
+
{
|
|
84
|
+
"path": "studio/src/app.css",
|
|
85
|
+
"bytes": 131513
|
|
86
|
+
},
|
|
83
87
|
{
|
|
84
88
|
"path": "third-party/npm/mediabunny@1.52.2/source/src/isobmff/isobmff-demuxer.ts",
|
|
85
89
|
"bytes": 118682
|
|
86
90
|
},
|
|
91
|
+
{
|
|
92
|
+
"path": "third-party/inventory.json",
|
|
93
|
+
"bytes": 115089
|
|
94
|
+
},
|
|
87
95
|
{
|
|
88
96
|
"path": "src/starter/animations/osmo/shape8-83.tsx",
|
|
89
97
|
"bytes": 112489
|
|
@@ -95,27 +103,19 @@
|
|
|
95
103
|
{
|
|
96
104
|
"path": "third-party/npm/mediabunny@1.52.2/source/src/media-source.ts",
|
|
97
105
|
"bytes": 97606
|
|
98
|
-
},
|
|
99
|
-
{
|
|
100
|
-
"path": "studio/src/app.css",
|
|
101
|
-
"bytes": 95067
|
|
102
|
-
},
|
|
103
|
-
{
|
|
104
|
-
"path": "third-party/npm/mediabunny@1.52.2/source/src/codec-data.ts",
|
|
105
|
-
"bytes": 92683
|
|
106
106
|
}
|
|
107
107
|
]
|
|
108
108
|
},
|
|
109
109
|
"hashes": {
|
|
110
|
-
"dist/studio.js": "
|
|
111
|
-
"vendor/osmo-lite": "
|
|
112
|
-
"studio/src/engine_bg.wasm": "
|
|
113
|
-
"studio/src/app.js": "
|
|
114
|
-
"studio/src/app.css": "
|
|
115
|
-
"third-party/inventory.json": "
|
|
110
|
+
"dist/studio.js": "ae0b993f2af7d91678570c9b64b99b62f6c581982b56f2d7305e56516424ed2a",
|
|
111
|
+
"vendor/osmo-lite": "e2cfb28ea87859434bb19a6c381b7dba0767d2961c38182c21174f0b5011a35d",
|
|
112
|
+
"studio/src/engine_bg.wasm": "1a2f493e6ec26f178658c7d3f251b4f6e6ba62104da7735a346058754687eb2a",
|
|
113
|
+
"studio/src/app.js": "44a2284edc365d197450e286aaeda4dfcfa7e6f97385d7f0c98d7c32da9816df",
|
|
114
|
+
"studio/src/app.css": "1f57360a850bbbf2b51ebb0920c5b54c649b8e5d3bea7a6537b67befaa233906",
|
|
115
|
+
"third-party/inventory.json": "d0813172f555bb9547e7a23b1b23197b8264f1cd22e1fd75a73fa0ad73e847e0"
|
|
116
116
|
},
|
|
117
117
|
"lockfiles": {
|
|
118
|
-
"package-lock.json": "
|
|
118
|
+
"package-lock.json": "e762e69bcb0bc3d88b7dc7b8ff81736790e2e05e992f42230378e5ef8373dc68",
|
|
119
119
|
"apps/native/Cargo.lock": "cf31d67cf167c5f32438abfeb42159eb50da61d36e4e482bae50ed3d71241373",
|
|
120
120
|
"packages/render-engine/Cargo.lock": "a1a68051e1eea30ef0fa46bf17cd0685bb2c6a327defed0405e0d63dd61017a7"
|
|
121
121
|
}
|
|
@@ -33,14 +33,15 @@ const result = await build({
|
|
|
33
33
|
const overridden = Object.hasOwn(sources, file);
|
|
34
34
|
const text = overridden ? sources[file] : await readFile(file, "utf8");
|
|
35
35
|
const loader = /tsx$/.test(file) ? "tsx" : /jsx$/.test(file) ? "jsx" : /[cm]?ts$/.test(file) ? "ts" : "js";
|
|
36
|
-
if (!text.includes("defineEffect")) return overridden ? {contents:text,loader} : undefined;
|
|
36
|
+
if (!text.includes("defineEffect") && !text.includes("defineKnobs")) return overridden ? {contents:text,loader} : undefined;
|
|
37
37
|
const ast = ts.createSourceFile(file, text, ts.ScriptTarget.Latest, true);
|
|
38
|
-
const names = new Set(), namespaces = new Set();
|
|
38
|
+
const names = new Set(), knobNames = new Set(), namespaces = new Set();
|
|
39
39
|
for (const statement of ast.statements) {
|
|
40
40
|
if (!ts.isImportDeclaration(statement) || statement.moduleSpecifier.text !== "@osmo/scene") continue;
|
|
41
41
|
const imports = statement.importClause?.namedBindings;
|
|
42
42
|
if (imports && ts.isNamedImports(imports)) for (const item of imports.elements) {
|
|
43
43
|
if ((item.propertyName ?? item.name).text === "defineEffect") names.add(item.name.text);
|
|
44
|
+
if ((item.propertyName ?? item.name).text === "defineKnobs") knobNames.add(item.name.text);
|
|
44
45
|
}
|
|
45
46
|
if (imports && ts.isNamespaceImport(imports)) namespaces.add(imports.name.text);
|
|
46
47
|
}
|
|
@@ -51,6 +52,12 @@ const result = await build({
|
|
|
51
52
|
ts.isPropertyAccessExpression(node.expression) && ts.isIdentifier(node.expression.expression) && namespaces.has(node.expression.expression.text) && node.expression.name.text === "defineEffect")) {
|
|
52
53
|
return ts.factory.updateCallExpression(node, node.expression, node.typeArguments, [ts.visitNode(node.arguments[0], visit), ts.factory.createStringLiteral(file)]);
|
|
53
54
|
}
|
|
55
|
+
if (ts.isCallExpression(node) && node.arguments.length <= 2 &&
|
|
56
|
+
(ts.isIdentifier(node.expression) && knobNames.has(node.expression.text) ||
|
|
57
|
+
ts.isPropertyAccessExpression(node.expression) && ts.isIdentifier(node.expression.expression) && namespaces.has(node.expression.expression.text) && node.expression.name.text === "defineKnobs")) {
|
|
58
|
+
return ts.factory.updateCallExpression(node, node.expression, node.typeArguments,
|
|
59
|
+
[node.arguments[0], node.arguments[1] ?? ts.factory.createObjectLiteralExpression(), ts.factory.createStringLiteral(file)]);
|
|
60
|
+
}
|
|
54
61
|
return ts.visitEachChild(node, visit, context);
|
|
55
62
|
};
|
|
56
63
|
return ts.visitNode(root, visit);
|
|
@@ -128,6 +135,7 @@ async function collectEffects(target) {
|
|
|
128
135
|
async function collectFonts(nodes) {
|
|
129
136
|
for (const node of nodes) {
|
|
130
137
|
await collectEffects(node);
|
|
138
|
+
for (const state of node.animate?.states ?? []) await collectEffects(state);
|
|
131
139
|
if(node.type === "composition") { await collectEffects(node.scene); await collectFonts(node.scene.children); }
|
|
132
140
|
if (["group","model","surface"].includes(node.type)) await collectFonts(node.children);
|
|
133
141
|
if (["image", "video", "audio", "model"].includes(node.type)) {
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** Local asset contracts shared by authoring, evaluation and the editor. */
|
|
2
|
+
export type AssetKind = "image" | "video" | "audio" | "font" | "model";
|
|
3
|
+
/** Plain paths remain valid; typed knob references retain their asset kind. */
|
|
4
|
+
export type AssetPath<K extends AssetKind> = string & {
|
|
5
|
+
readonly __assetKind?: K;
|
|
6
|
+
};
|
|
7
|
+
export const assetExtensions: Record<AssetKind, readonly string[]> = {
|
|
8
|
+
image: ["png", "jpg", "jpeg", "webp", "gif", "svg"],
|
|
9
|
+
video: ["mp4", "webm", "mov", "m4v"],
|
|
10
|
+
audio: ["wav", "mp3", "m4a", "aac", "ogg", "flac"],
|
|
11
|
+
font: ["ttf", "otf"],
|
|
12
|
+
model: ["glb"],
|
|
13
|
+
};
|
|
14
|
+
export function validAssetPath(
|
|
15
|
+
kind: AssetKind,
|
|
16
|
+
value: unknown,
|
|
17
|
+
): value is string {
|
|
18
|
+
return (
|
|
19
|
+
Object.hasOwn(assetExtensions, kind) &&
|
|
20
|
+
typeof value === "string" &&
|
|
21
|
+
value.length > 0 &&
|
|
22
|
+
value.length <= 4096 &&
|
|
23
|
+
value === value.trim() &&
|
|
24
|
+
!/^[a-z][a-z0-9+.-]*:|^[/\\]|[\x00-\x1f?#\\]/i.test(value) &&
|
|
25
|
+
assetExtensions[kind].includes(value.split(".").at(-1)!.toLowerCase())
|
|
26
|
+
);
|
|
27
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { AssetPath } from "./assets.js";
|
|
2
|
+
import type { TextAnimation } from "./text-animation.js";
|
|
1
3
|
import type { EffectInstance } from "./effects.js";
|
|
2
4
|
import type { KnobRef, KnobSet, KnobBindings } from "./knobs.js";
|
|
3
5
|
import type { Knobs } from "./knob-schema.js";
|
|
@@ -13,7 +15,7 @@ export type Input<T> = KnobRef<T> | (T extends number
|
|
|
13
15
|
? T | Motion<T>
|
|
14
16
|
: T extends readonly number[]
|
|
15
17
|
? T | Motion<T> | { readonly [K in keyof T]: Input<T[K]> }
|
|
16
|
-
: T extends object
|
|
18
|
+
: T extends string ? T : T extends object
|
|
17
19
|
? { readonly [K in keyof T]: Input<T[K]> }
|
|
18
20
|
: T);
|
|
19
21
|
export type InputProps<P> = {
|
|
@@ -69,14 +71,14 @@ export interface TransformProps extends ClipProps {
|
|
|
69
71
|
z?: number;
|
|
70
72
|
/** Clockwise degrees around origin. Default: 0. */
|
|
71
73
|
rotation?: number;
|
|
72
|
-
/** Uniform factor
|
|
73
|
-
scale?: number
|
|
74
|
+
/** Uniform factor applied to both axes. Default: 1. */
|
|
75
|
+
scale?: number;
|
|
74
76
|
/** Local pivot in pixels, not a fraction. Default: [0, 0]. */
|
|
75
77
|
origin?: Vec2;
|
|
76
78
|
/** 0–1. Group opacity composites its children together. Default: 1. */
|
|
77
79
|
opacity?: number;
|
|
78
80
|
}
|
|
79
|
-
export interface
|
|
81
|
+
export interface SceneStyleProps {
|
|
80
82
|
/** Optional lane metadata keyed by track number (0–99). Labels are static, one word maximum. Does not change paint order. */
|
|
81
83
|
tracks?: Readonly<Record<number, TrackMetadata>>;
|
|
82
84
|
/** Filter the complete viewport, including 3D surfaces; array order matters. */
|
|
@@ -90,9 +92,14 @@ export interface SceneProps {
|
|
|
90
92
|
/** Positive frames per second; static. Default: 30. */
|
|
91
93
|
fps?: number;
|
|
92
94
|
/** Fixed viewport backdrop, independent of camera and layout. Default: transparent. */
|
|
93
|
-
background?: Color
|
|
95
|
+
background?: Input<Color>;
|
|
94
96
|
children?: Child;
|
|
95
97
|
}
|
|
98
|
+
/** Scene controls follow the same ownership and instance rules as Groups. */
|
|
99
|
+
export type SceneProps<K extends Knobs = Knobs> = SceneStyleProps & (
|
|
100
|
+
| { knobs: KnobSet<K>; values?: KnobBindings<NoInfer<K>> }
|
|
101
|
+
| { knobs?: never; values?: never }
|
|
102
|
+
);
|
|
96
103
|
/** Timeline lane metadata; independent of the clips occupying the lane. */
|
|
97
104
|
export interface TrackMetadata {
|
|
98
105
|
/** Optional static display name, one word maximum, e.g. "Titles". */
|
|
@@ -179,12 +186,16 @@ export type SceneInstanceProps = InputProps<TransformProps & {
|
|
|
179
186
|
width?: number;
|
|
180
187
|
/** Viewport height in pixels; defaults to the source scene height. Does not scale content. */
|
|
181
188
|
height?: number;
|
|
182
|
-
}> & {
|
|
189
|
+
}> & {
|
|
190
|
+
label?: string; hidden?: boolean; solo?: boolean; trimIn?: number;
|
|
191
|
+
/** Overrides for the controls declared by the source Scene. */
|
|
192
|
+
values?: Readonly<Record<string, MotionValue | Motion | KnobRef>>;
|
|
193
|
+
};
|
|
183
194
|
/** JSX attribute contract for imported static Group documents (legacy) and Scenes. */
|
|
184
|
-
export type GroupInstance = (props: GroupInstanceProps) => Element;
|
|
195
|
+
export type GroupInstance = (props: GroupInstanceProps | SceneInstanceProps) => Element;
|
|
185
196
|
export interface ImageProps extends TransformProps {
|
|
186
197
|
/** Local PNG/JPEG/WebP/SVG path, relative to this TSX file; no remote URLs. */
|
|
187
|
-
src:
|
|
198
|
+
src: AssetPath<"image">;
|
|
188
199
|
/** Positive box width in pixels. */
|
|
189
200
|
width: number;
|
|
190
201
|
/** Positive box height in pixels. */
|
|
@@ -214,7 +225,7 @@ export interface ShaderProps extends TransformProps {
|
|
|
214
225
|
export type ModelSelector = string | number;
|
|
215
226
|
export interface ModelProps extends ClipProps {
|
|
216
227
|
/** Local .glb path. Uses the default GLB scene; inspect node/material/clip names before targeting them. */
|
|
217
|
-
src:
|
|
228
|
+
src: AssetPath<"model">;
|
|
218
229
|
/** Composition [x, y] in pixels. Default: scene center. */
|
|
219
230
|
position?: Vec2;
|
|
220
231
|
/** Depth in pixels; positive toward the viewer. Default: 0. */
|
|
@@ -269,10 +280,10 @@ export interface MediaTiming extends ClipProps {
|
|
|
269
280
|
/** Gain from 0–1; animatable in JSX. Use 0 to mute video audio. Default: 1. */
|
|
270
281
|
volume?: number;
|
|
271
282
|
}
|
|
272
|
-
export interface VideoProps extends ImageProps, MediaTiming {}
|
|
283
|
+
export interface VideoProps extends Omit<ImageProps, "src">, MediaTiming { src: AssetPath<"video"> }
|
|
273
284
|
export interface AudioProps extends MediaTiming {
|
|
274
285
|
/** Local audio file path, relative to this TSX file. */
|
|
275
|
-
src:
|
|
286
|
+
src: AssetPath<"audio">;
|
|
276
287
|
}
|
|
277
288
|
|
|
278
289
|
/** SVG-style stroke painted along the contour, centered on its geometry. */
|
|
@@ -338,7 +349,7 @@ export interface TextStyleProps extends TransformProps {
|
|
|
338
349
|
/** Whole string or number; the renderer handles shaping, kerning and glyph placement. */
|
|
339
350
|
children: string | number;
|
|
340
351
|
/** Bundled Inter or a local .ttf/.otf path relative to this TSX file. Default: Inter. */
|
|
341
|
-
font?:
|
|
352
|
+
font?: AssetPath<"font">;
|
|
342
353
|
/** Positive size in pixels. Default: 48. */
|
|
343
354
|
fontSize?: number;
|
|
344
355
|
/** Requested font weight; omitted uses the selected face’s default. */
|
|
@@ -351,6 +362,8 @@ export interface TextStyleProps extends TransformProps {
|
|
|
351
362
|
export type TextProps = TextStyleProps & (
|
|
352
363
|
| {
|
|
353
364
|
path?: never;
|
|
365
|
+
/** Staggered post-layout character, word or visual-line animation. */
|
|
366
|
+
animate?: TextAnimation;
|
|
354
367
|
/** Positive wrapping width in pixels; omitted uses natural text width. */
|
|
355
368
|
width?: number;
|
|
356
369
|
/** Positive text-box height. Overflow remains visible; use a clipping Group to crop. */
|
|
@@ -367,6 +380,7 @@ export type TextProps = TextStyleProps & (
|
|
|
367
380
|
| {
|
|
368
381
|
/** Move a whole string along a curve by animating progress. No width, align, lineHeight or line breaks. */
|
|
369
382
|
path: TextPath;
|
|
383
|
+
animate?: never;
|
|
370
384
|
width?: never;
|
|
371
385
|
height?: never;
|
|
372
386
|
verticalAlign?: never;
|
|
@@ -402,6 +416,8 @@ export interface Element {
|
|
|
402
416
|
readonly props: Readonly<Record<string, unknown>>;
|
|
403
417
|
readonly key?: string | number;
|
|
404
418
|
readonly sourceFile?: string;
|
|
419
|
+
/** File supplying each literal instance override. */
|
|
420
|
+
readonly valueSources?: Readonly<Record<string, string>>;
|
|
405
421
|
}
|
|
406
422
|
export function element(
|
|
407
423
|
type: Element["type"],
|
|
@@ -418,7 +434,7 @@ export function element(
|
|
|
418
434
|
/** Root composition with fixed dimensions, duration and FPS.
|
|
419
435
|
* @example <Scene width={1080} height={1080} duration={4} fps={30} background="#141414"><Text track={0}>Hello</Text></Scene> */
|
|
420
436
|
export const Scene = Object.assign(
|
|
421
|
-
(props: SceneProps): Element => element("scene", props),
|
|
437
|
+
<const K extends Knobs = Knobs>(props: SceneProps<K>): Element => element("scene", props),
|
|
422
438
|
{ sceneRoot: true as const },
|
|
423
439
|
);
|
|
424
440
|
/** Layout, transform and style children together; also draws boxes, ellipses and thin lines.
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { evaluateTextAnimation, type EvaluatedTextAnimation } from "./text-animation.js";
|
|
1
2
|
import { readMask, maskSourceIndex } from "./mask.mjs";
|
|
2
3
|
import { clipFields, clipTiming, isClipPrimitive } from "./clip.mjs";
|
|
3
4
|
import { readGroupLabel, readGroupPlayback, readSceneTracks, trackPlayback } from "./metadata.mjs";
|
|
4
|
-
import { isKnobRef, isKnobSet, resolveKnob, evaluateKnobs, type KnobScope, type KnobDescription } from "./knobs.js";
|
|
5
|
+
import { isKnobRef, isKnobSet, resolveKnob, resolveKnobAsset, evaluateKnobs, type KnobScope, type KnobDescription } from "./knobs.js";
|
|
5
6
|
import { evaluateEffects, type EvaluatedEffect } from "./effects.js";
|
|
6
7
|
import {
|
|
7
8
|
Scene, Model, Surface,
|
|
@@ -40,6 +41,7 @@ interface BaseNode {
|
|
|
40
41
|
key?: string | number;
|
|
41
42
|
}
|
|
42
43
|
export interface CompositionNode extends BaseNode {
|
|
44
|
+
controls?: KnobDescription;
|
|
43
45
|
type: "composition";
|
|
44
46
|
width: number;
|
|
45
47
|
height: number;
|
|
@@ -86,6 +88,7 @@ export interface PathNode extends BaseNode {
|
|
|
86
88
|
stroke?: { color: Color; width: number; cap: "butt" | "round" | "square"; join: "miter" | "round" | "bevel"; miterLimit: number; trimStart: number; trimEnd: number };
|
|
87
89
|
}
|
|
88
90
|
export interface TextNode extends BaseNode {
|
|
91
|
+
animate?: EvaluatedTextAnimation;
|
|
89
92
|
height?: number;
|
|
90
93
|
verticalAlign?: "top" | "center" | "bottom";
|
|
91
94
|
alignBounds?: "lineBox" | "glyphs";
|
|
@@ -145,6 +148,7 @@ export interface EvaluatedCamera {
|
|
|
145
148
|
fov: number;
|
|
146
149
|
}
|
|
147
150
|
export interface EvaluatedScene extends FrameContext {
|
|
151
|
+
controls?: KnobDescription;
|
|
148
152
|
effects?: EvaluatedEffect[];
|
|
149
153
|
camera?: EvaluatedCamera;
|
|
150
154
|
type: "scene";
|
|
@@ -179,17 +183,14 @@ function color(value: unknown, label: string): Color {
|
|
|
179
183
|
return value as Color;
|
|
180
184
|
}
|
|
181
185
|
function transform(props: Readonly<Record<string, unknown>>): Transform {
|
|
182
|
-
const scale = props.scale ?? 1;
|
|
186
|
+
const scale = number(props.scale ?? 1, "scale (one uniform number)");
|
|
183
187
|
const opacity = number(props.opacity ?? 1, "opacity", 0);
|
|
184
188
|
if (opacity > 1) throw new Error("opacity: must be <= 1");
|
|
185
189
|
return {
|
|
186
190
|
position: pair(props.position ?? [0, 0], "position"),
|
|
187
191
|
z: number(props.z ?? 0, "z"),
|
|
188
192
|
rotation: number(props.rotation ?? 0, "rotation"),
|
|
189
|
-
scale:
|
|
190
|
-
typeof scale === "number"
|
|
191
|
-
? [number(scale, "scale"), scale]
|
|
192
|
-
: pair(scale, "scale"),
|
|
193
|
+
scale: [scale, scale],
|
|
193
194
|
origin: pair(props.origin ?? [0, 0], "origin"),
|
|
194
195
|
opacity,
|
|
195
196
|
};
|
|
@@ -223,7 +224,13 @@ function isElement(value: unknown): value is Element {
|
|
|
223
224
|
export function evaluateFrame(root: Element, frame: number): EvaluatedScene {
|
|
224
225
|
return evaluateScene(root, frame);
|
|
225
226
|
}
|
|
226
|
-
|
|
227
|
+
interface SceneBindings {
|
|
228
|
+
values?: Record<string, unknown>;
|
|
229
|
+
scope: KnobScope;
|
|
230
|
+
time: number;
|
|
231
|
+
sourceFile?: string;
|
|
232
|
+
}
|
|
233
|
+
function evaluateScene(root: Element, frame: number, clock?: Partial<ClockContext>, depth = 0, instance?: SceneBindings): EvaluatedScene {
|
|
227
234
|
if (depth > 32) throw new Error("Scene nesting exceeds 32 levels");
|
|
228
235
|
if (!isElement(root) || (root.type !== Scene && root.type !== "scene")) {
|
|
229
236
|
throw new Error(
|
|
@@ -234,7 +241,7 @@ function evaluateScene(root: Element, frame: number, clock?: Partial<ClockContex
|
|
|
234
241
|
checkProps(
|
|
235
242
|
p,
|
|
236
243
|
"Scene",
|
|
237
|
-
["width", "height", "duration", "fps", "background", "children", "effects", "tracks"],
|
|
244
|
+
["width", "height", "duration", "fps", "background", "children", "effects", "tracks", "knobs", "values"],
|
|
238
245
|
false,
|
|
239
246
|
);
|
|
240
247
|
const tracks = readSceneTracks(p.tracks);
|
|
@@ -258,6 +265,24 @@ function evaluateScene(root: Element, frame: number, clock?: Partial<ClockContex
|
|
|
258
265
|
duration,
|
|
259
266
|
...clock,
|
|
260
267
|
});
|
|
268
|
+
for (const values of [p.values, instance?.values])
|
|
269
|
+
if (values !== undefined && (!values || typeof values !== "object" || Array.isArray(values)))
|
|
270
|
+
throw new Error("Scene.values must be named knob bindings");
|
|
271
|
+
let scope: KnobScope = [];
|
|
272
|
+
let controls: KnobDescription | undefined;
|
|
273
|
+
if (p.knobs !== undefined) {
|
|
274
|
+
const overrides = instance?.values ?? {};
|
|
275
|
+
controls = evaluateKnobs(p.knobs as import("./knobs.js").KnobSet,
|
|
276
|
+
{...(p.values as object ?? {}), ...overrides}, context.time, instance?.scope ?? [], {
|
|
277
|
+
sourceFile: root.sourceFile,
|
|
278
|
+
valueSources: {...root.valueSources, ...Object.fromEntries(Object.keys(overrides)
|
|
279
|
+
.filter(() => instance?.sourceFile).map(k => [k, instance!.sourceFile!]))},
|
|
280
|
+
bindingTimes: Object.fromEntries(Object.keys(overrides).map(k => [k, instance!.time])),
|
|
281
|
+
});
|
|
282
|
+
scope = [{set: p.knobs as import("./knobs.js").KnobSet, description: controls}];
|
|
283
|
+
} else if (p.values !== undefined || instance?.values !== undefined) {
|
|
284
|
+
throw new Error("Scene.values requires Scene.knobs");
|
|
285
|
+
}
|
|
261
286
|
let camera: EvaluatedCamera | undefined;
|
|
262
287
|
const children: unknown[] = [];
|
|
263
288
|
function collect(child: unknown, depth = 0): void {
|
|
@@ -269,14 +294,14 @@ function evaluateScene(root: Element, frame: number, clock?: Partial<ClockContex
|
|
|
269
294
|
) {
|
|
270
295
|
if (camera) throw new Error("Scene accepts only one Camera");
|
|
271
296
|
camera = evaluateCamera(
|
|
272
|
-
resolve(child.props, context.time) as Record<string, unknown>, context,
|
|
297
|
+
resolve(child.props, context.time, scope) as Record<string, unknown>, context,
|
|
273
298
|
);
|
|
274
299
|
} else {
|
|
275
300
|
children.push(child);
|
|
276
301
|
}
|
|
277
302
|
}
|
|
278
303
|
collect(p.children);
|
|
279
|
-
const evaluatedChildren = isolateGroups(visit(children, context, depth));
|
|
304
|
+
const evaluatedChildren = isolateGroups(visit(children, context, depth, scope));
|
|
280
305
|
function checkPlacement(nodes: SceneNode[], parent: string) {
|
|
281
306
|
for (const n of nodes) {
|
|
282
307
|
if (n.track !== undefined && parent !== "scene") throw new Error(`${n.type}.track is only supported on root timeline clips`);
|
|
@@ -297,8 +322,9 @@ function evaluateScene(root: Element, frame: number, clock?: Partial<ClockContex
|
|
|
297
322
|
...(camera ? { camera } : {}),
|
|
298
323
|
...context,
|
|
299
324
|
totalFrames,
|
|
300
|
-
background: color(p.background ?? "transparent", "Scene.background"),
|
|
301
|
-
|
|
325
|
+
background: color(resolve(p.background ?? "transparent", context.time, scope), "Scene.background"),
|
|
326
|
+
...(controls ? {controls} : {}),
|
|
327
|
+
...(p.effects === undefined ? {} : { effects: evaluateEffects(p.effects, context.time, root.sourceFile, v => isKnobRef(v) ? resolveKnob(v, scope) : v) }),
|
|
302
328
|
children: evaluatedChildren,
|
|
303
329
|
};
|
|
304
330
|
}
|
|
@@ -404,7 +430,7 @@ function visit(
|
|
|
404
430
|
const source = child.props.scene as Element;
|
|
405
431
|
const timing = clipTiming(child.props, context.duration, context.fps, 'Scene instance');
|
|
406
432
|
const localTime = Math.max(0,Math.min(timing.duration-1e-9,context.time-timing.start));
|
|
407
|
-
const p = Object.fromEntries(Object.entries(child.props).filter(([k])=>k!=='scene').map(([k,v])=>[k,k==='effects'?v:resolve(v,localTime,scope)]));
|
|
433
|
+
const p = Object.fromEntries(Object.entries(child.props).filter(([k])=>k!=='scene').map(([k,v])=>[k,k==='effects'||k==='values'?v:resolve(v,localTime,scope)]));
|
|
408
434
|
const trim = number(p.trimIn ?? 0, 'Scene instance.trimIn', 0);
|
|
409
435
|
const sourceDuration = positive(source.props.duration, 'Scene.duration');
|
|
410
436
|
if (trim >= sourceDuration) throw new Error('Scene instance.trimIn must be before the source end');
|
|
@@ -422,13 +448,14 @@ function visit(
|
|
|
422
448
|
return Math.round(value);
|
|
423
449
|
};
|
|
424
450
|
const viewport = {...source,props:{...source.props,width:dimension('width'),height:dimension('height')}};
|
|
425
|
-
const scene = evaluateScene(viewport, 0, {time, frame:time*context.fps, fps:context.fps, offset, windowStart:start, windowEnd:end}, depth+1);
|
|
451
|
+
const scene = evaluateScene(viewport, 0, {time, frame:time*context.fps, fps:context.fps, offset, windowStart:start, windowEnd:end}, depth+1, {values:p.values as Record<string,unknown> | undefined, scope, time:localTime, sourceFile:child.sourceFile});
|
|
426
452
|
const active = context.time >= timing.start && context.time < timing.start + Math.min(timing.duration, sourceDuration-trim);
|
|
427
453
|
readGroupLabel(p.label); readGroupPlayback(p);
|
|
428
454
|
const node: CompositionNode = {type:'composition',width:scene.width,height:scene.height,scene,
|
|
455
|
+
...(scene.controls ? {controls:scene.controls} : {}),
|
|
429
456
|
...(timing.track === undefined ? {} : {track:timing.track}),
|
|
430
457
|
transform:transform({...p,opacity:active && !p.hidden ? p.opacity : 0}),
|
|
431
|
-
...(p.effects === undefined ? {} : {effects:evaluateEffects(p.effects,localTime,child.sourceFile)})};
|
|
458
|
+
...(p.effects === undefined ? {} : {effects:evaluateEffects(p.effects,localTime,child.sourceFile,v => isKnobRef(v) ? resolveKnob(v,scope) : v)})};
|
|
432
459
|
if (p.solo) soloGroups.add(node);
|
|
433
460
|
return [node];
|
|
434
461
|
}
|
|
@@ -452,10 +479,18 @@ function visit(
|
|
|
452
479
|
readGroupPlayback(child.props);
|
|
453
480
|
if (child.props.knobs !== undefined) {
|
|
454
481
|
if (!isKnobSet(child.props.knobs)) throw new Error("Group.knobs must be a defineKnobs declaration");
|
|
455
|
-
controls = evaluateKnobs(child.props.knobs, child.props.values, context.time, scope);
|
|
482
|
+
controls = evaluateKnobs(child.props.knobs, child.props.values, context.time, scope, {sourceFile:child.sourceFile, valueSources:child.valueSources});
|
|
456
483
|
scope = [...scope, { set: child.props.knobs, description: controls }];
|
|
457
484
|
} else if (child.props.values !== undefined) throw new Error("Group.values requires Group.knobs");
|
|
458
485
|
}
|
|
486
|
+
// Asset controls carry their authoring file through all forwarding scopes.
|
|
487
|
+
const assetSource = (prop: "src" | "font") => {
|
|
488
|
+
const ref = child.props[prop];
|
|
489
|
+
const asset = isKnobRef(ref) ? resolveKnobAsset(ref, scope) : undefined;
|
|
490
|
+
if (asset && asset.kind !== (prop === "font" ? "font" : type))
|
|
491
|
+
throw new Error(`${type}.${prop}: asset knob ${asset.name} has incompatible kind ${asset.kind}`);
|
|
492
|
+
return asset?.sourceFile ?? child.sourceFile;
|
|
493
|
+
};
|
|
459
494
|
const props = Object.fromEntries(Object.entries(child.props).map(([name, value]) => [name,
|
|
460
495
|
(name === "children" && !isKnobRef(value)) || name === "effects" || (type === "group" && (name === "knobs" || name === "values"))
|
|
461
496
|
? value : resolve(value, context.time, scope),
|
|
@@ -500,7 +535,7 @@ function visit(
|
|
|
500
535
|
}
|
|
501
536
|
const children=visit(props.children,context,depth+1,scope);
|
|
502
537
|
if(children.some(n=>n.type!=="surface"))throw new Error("Model accepts Surface children only");
|
|
503
|
-
return [{type:"model",src:props.src,...(
|
|
538
|
+
return [{type:"model",src:props.src,...(assetSource("src")?{sourceFile:assetSource("src")}:{}),
|
|
504
539
|
...(child.key===undefined?{}:{key:child.key}),
|
|
505
540
|
...(timing?.track === undefined ? {} : {track: timing.track}),
|
|
506
541
|
transform:transform({...props,opacity:active ? props.opacity : 0,position:props.position??[context.width/2,context.height/2],rotation:0,scale:positive(props.scale??1,"Model.scale")}),
|
|
@@ -683,7 +718,7 @@ function visit(
|
|
|
683
718
|
Object.defineProperty(uniforms, name, { value, enumerable: true });
|
|
684
719
|
}
|
|
685
720
|
return [{ type: "shader", ...base, src: props.src, backdrop: props.backdrop ?? false,
|
|
686
|
-
...(
|
|
721
|
+
...(assetSource("src") ? { sourceFile: assetSource("src") } : {}),
|
|
687
722
|
width: positive(props.width, "Shader.width"), height: positive(props.height, "Shader.height"),
|
|
688
723
|
rounding: number(props.rounding ?? 0, "Shader.rounding", 0), uniforms }];
|
|
689
724
|
}
|
|
@@ -712,7 +747,7 @@ function visit(
|
|
|
712
747
|
type,
|
|
713
748
|
...base,
|
|
714
749
|
src: props.src,
|
|
715
|
-
...(
|
|
750
|
+
...(assetSource("src") ? { sourceFile: assetSource("src") } : {}),
|
|
716
751
|
};
|
|
717
752
|
if (visual)
|
|
718
753
|
Object.assign(node, {
|
|
@@ -805,7 +840,7 @@ function visit(
|
|
|
805
840
|
];
|
|
806
841
|
}
|
|
807
842
|
if (type === "text") {
|
|
808
|
-
checkProps(props, "Text", ["height", "verticalAlign", "alignBounds",
|
|
843
|
+
checkProps(props, "Text", ["animate", "height", "verticalAlign", "alignBounds",
|
|
809
844
|
"children",
|
|
810
845
|
"font",
|
|
811
846
|
"fontSize",
|
|
@@ -817,6 +852,7 @@ function visit(
|
|
|
817
852
|
"letterSpacing",
|
|
818
853
|
"path",
|
|
819
854
|
]);
|
|
855
|
+
if (props.animate !== undefined && props.path !== undefined) throw new Error("Text.animate cannot be combined with Text.path");
|
|
820
856
|
let path: TextPath | undefined;
|
|
821
857
|
if (props.path !== undefined) {
|
|
822
858
|
if (!props.path || typeof props.path !== "object" || Array.isArray(props.path))
|
|
@@ -863,8 +899,9 @@ function visit(
|
|
|
863
899
|
...base, ...box,
|
|
864
900
|
...(path ? { path } : {}),
|
|
865
901
|
text: String(props.children),
|
|
902
|
+
...(props.animate === undefined ? {} : {animate: evaluateTextAnimation(props.animate, String(props.children), context.time, color(props.color ?? "#ffffff", "Text.color"))}),
|
|
866
903
|
font,
|
|
867
|
-
...(
|
|
904
|
+
...(assetSource("font") ? { fontSource: assetSource("font") } : {}),
|
|
868
905
|
...(props.fontWeight === undefined
|
|
869
906
|
? {}
|
|
870
907
|
: {
|
|
@@ -67,3 +67,7 @@ export type { EffectPadding, Knob, KnobGroup, Knobs, KnobValues, EffectDefinitio
|
|
|
67
67
|
|
|
68
68
|
export { defineKnobs, describeKnobs } from "./knobs.js";
|
|
69
69
|
export type { KnobRef, KnobSet, KnobBindings, KnobDescription } from "./knobs.js";
|
|
70
|
+
|
|
71
|
+
export type { TextAnimation, TextAnimationStyle } from "./text-animation.js";
|
|
72
|
+
|
|
73
|
+
export type { AssetKind, AssetPath } from "./assets.js";
|
|
@@ -2,7 +2,6 @@ import {
|
|
|
2
2
|
element,
|
|
3
3
|
type Element as SceneElement,
|
|
4
4
|
type InputProps,
|
|
5
|
-
type SceneProps,
|
|
6
5
|
type Component,
|
|
7
6
|
type GroupInstance,
|
|
8
7
|
Scene,
|
|
@@ -18,9 +17,11 @@ export function jsx(
|
|
|
18
17
|
if (typeof type === "object" && type !== null) {
|
|
19
18
|
if (type.type === Scene || type.type === "scene") {
|
|
20
19
|
const overrides = (props ?? {}) as Record<string, unknown>;
|
|
21
|
-
const allowed = new Set(['id','label','track','start','duration','trimIn','width','height','position','z','rotation','scale','origin','opacity','effects','hidden','solo']);
|
|
20
|
+
const allowed = new Set(['id','label','track','start','duration','trimIn','width','height','position','z','rotation','scale','origin','opacity','effects','hidden','solo','values']);
|
|
22
21
|
for (const name of Object.keys(overrides)) if (!allowed.has(name))
|
|
23
22
|
throw new Error(`Scene instance.${name}: edit scene settings in the source file`);
|
|
23
|
+
if (overrides.values !== undefined && (!overrides.values || typeof overrides.values !== "object" || Array.isArray(overrides.values)))
|
|
24
|
+
throw new Error("Scene instance.values must be named knob bindings");
|
|
24
25
|
return element("composition", {duration:type.props.duration, ...overrides, scene:type}, key);
|
|
25
26
|
}
|
|
26
27
|
if (type.type !== Group && type.type !== "group")
|
|
@@ -36,6 +37,7 @@ export function jsx(
|
|
|
36
37
|
...(values === undefined ? {} : { values: { ...(type.props.values as object ?? {}), ...values } }),
|
|
37
38
|
}, key),
|
|
38
39
|
...(type.sourceFile ? { sourceFile: type.sourceFile } : {}),
|
|
40
|
+
...(type.valueSources ? { valueSources: type.valueSources } : {}),
|
|
39
41
|
});
|
|
40
42
|
}
|
|
41
43
|
return element(type, props ?? {}, key);
|
|
@@ -50,6 +52,10 @@ export function jsxDEV(
|
|
|
50
52
|
): SceneElement {
|
|
51
53
|
return Object.freeze({
|
|
52
54
|
...jsx(type, props, key),
|
|
55
|
+
...(source?.fileName && props && "values" in props && props.values && typeof props.values === "object" ? {
|
|
56
|
+
valueSources: {...(typeof type === "object" ? type.valueSources : {}),
|
|
57
|
+
...Object.fromEntries(Object.keys(props.values).map(k => [k, source.fileName!]))},
|
|
58
|
+
} : {}),
|
|
53
59
|
...(typeof type === "object" && type?.sourceFile && type.type!==Scene && type.type!=="scene" ? { sourceFile: type.sourceFile }
|
|
54
60
|
: source?.fileName ? { sourceFile: source.fileName } : {}),
|
|
55
61
|
});
|
|
@@ -68,6 +74,6 @@ export namespace JSX {
|
|
|
68
74
|
}
|
|
69
75
|
export interface IntrinsicElements {}
|
|
70
76
|
export type LibraryManagedAttributes<C, P> = C extends typeof Scene
|
|
71
|
-
?
|
|
77
|
+
? P
|
|
72
78
|
: InputProps<P>;
|
|
73
79
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { validAssetPath, type AssetKind } from "./assets.js";
|
|
1
2
|
import { parseColor } from "./color.js";
|
|
2
3
|
type When = Readonly<Record<string, string | number | boolean>>;
|
|
3
4
|
interface Control {
|
|
@@ -23,6 +24,7 @@ export type Knob = Control & (
|
|
|
23
24
|
max?: number;
|
|
24
25
|
}
|
|
25
26
|
| { type: "color" | "text"; default: string }
|
|
27
|
+
| { type: "asset"; kind: AssetKind; default: string }
|
|
26
28
|
| { type: "boolean"; default: boolean }
|
|
27
29
|
| { type: "select"; default: string; options: Readonly<Record<string, string>> }
|
|
28
30
|
| { type: "multiselect"; default: readonly string[]; options: Readonly<Record<string, string>> }
|
|
@@ -59,6 +61,7 @@ export function validateKnobValue(knob: Knob, value: unknown, name: string) {
|
|
|
59
61
|
switch (knob.type) {
|
|
60
62
|
case "number": if (!finite(value)) fail(`${name}: expected a finite number`); break;
|
|
61
63
|
case "boolean": if (typeof value !== "boolean") fail(`${name}: expected a boolean`); break;
|
|
64
|
+
case "asset": if (!validAssetPath(knob.kind, value)) fail(`${name}: expected a local ${knob.kind} asset path`); break;
|
|
62
65
|
case "text": if (typeof value !== "string") fail(`${name}: expected text`); break;
|
|
63
66
|
case "color": if (!parseColor(value)) fail(`${name}: expected a hex color or transparent`); break;
|
|
64
67
|
case "select": if (typeof value !== "string" || !Object.hasOwn(knob.options, value)) fail(`${name}: unknown choice`); break;
|
|
@@ -97,9 +100,9 @@ export function validateKnobSchema(knobs: Knobs, inputGroups: Readonly<Record<st
|
|
|
97
100
|
}
|
|
98
101
|
for (const [name, knob] of Object.entries(knobs)) {
|
|
99
102
|
if (!/^[a-zA-Z][a-zA-Z0-9_]*$/.test(name) || !record(knob)) fail(`invalid knob ${name}`);
|
|
100
|
-
const extras = knob.type === "number" ? ["min", "max", "step"] : ["select", "multiselect"].includes(knob.type) ? ["options"] : [];
|
|
103
|
+
const extras = knob.type === "number" ? ["min", "max", "step"] : knob.type === "asset" ? ["kind"] : ["select", "multiselect"].includes(knob.type) ? ["options"] : [];
|
|
101
104
|
keys(knob, ["type", "default", "label", "group", "visibleWhen", ...extras], name);
|
|
102
|
-
if (!["number", "color", "text", "boolean", "select", "multiselect"].includes(knob.type)) fail(`${name}: unknown knob type`);
|
|
105
|
+
if (!["number", "color", "text", "boolean", "select", "multiselect", "asset"].includes(knob.type)) fail(`${name}: unknown knob type`);
|
|
103
106
|
if (knob.label !== undefined && typeof knob.label !== "string") fail(`${name}.label: expected text`);
|
|
104
107
|
if (knob.group !== undefined && !Object.hasOwn(groups, knob.group)) fail(`${name}: unknown group ${knob.group}`);
|
|
105
108
|
if (knob.type === "number") {
|