frameos-wasm 2026.7.3
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/LICENSE +661 -0
- package/README.md +72 -0
- package/dist/assets/frameos.js +4208 -0
- package/dist/assets/frameos.wasm +0 -0
- package/dist/assets/preview-worker.js +198 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +10 -0
- package/dist/manager.d.ts +16 -0
- package/dist/manager.js +258 -0
- package/dist/preview.d.ts +60 -0
- package/dist/preview.js +116 -0
- package/dist/showIf.d.ts +18 -0
- package/dist/showIf.js +99 -0
- package/dist/types.d.ts +71 -0
- package/dist/types.js +30 -0
- package/index.html +66 -0
- package/package.json +43 -0
package/dist/showIf.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ShowIfCondition, StateField } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* True when the field should be visible: no conditions, or at least one
|
|
4
|
+
* top-level condition matches (top level is OR-ed, `and` blocks are AND-ed).
|
|
5
|
+
*/
|
|
6
|
+
export declare function evaluateShowIf(conditions: ShowIfCondition[] | undefined, values: Record<string, unknown>, currentFieldName?: string | null): boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Coerce a raw form value (usually a string) to the field's type so showIf
|
|
9
|
+
* comparisons behave the same as on the frame itself.
|
|
10
|
+
*/
|
|
11
|
+
export declare function coerceStateFieldValue(field: Pick<StateField, 'type'>, value: unknown): unknown;
|
|
12
|
+
/** The values map for showIf checks: field defaults overlaid with the sources. */
|
|
13
|
+
export declare function stateFieldShowIfValues(fields: StateField[], ...valueSources: (Record<string, unknown> | null | undefined)[]): Record<string, unknown>;
|
|
14
|
+
/**
|
|
15
|
+
* The public state fields visible in a control form given the current values
|
|
16
|
+
* (later sources win, defaults fill the gaps).
|
|
17
|
+
*/
|
|
18
|
+
export declare function visiblePublicStateFields(fields: StateField[], ...valueSources: (Record<string, unknown> | null | undefined)[]): StateField[];
|
package/dist/showIf.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
function matchCondition(condition, values, currentFieldName) {
|
|
2
|
+
if ('and' in condition) {
|
|
3
|
+
return condition.and.every((cond) => matchCondition(cond, values, currentFieldName));
|
|
4
|
+
}
|
|
5
|
+
const { value, operator, field: fieldName } = condition;
|
|
6
|
+
const field = fieldName || currentFieldName || '';
|
|
7
|
+
const actualValue = values[field];
|
|
8
|
+
if (operator === 'eq') {
|
|
9
|
+
return actualValue === value;
|
|
10
|
+
}
|
|
11
|
+
else if (operator === 'ne') {
|
|
12
|
+
return actualValue !== value;
|
|
13
|
+
}
|
|
14
|
+
else if (operator === 'gt') {
|
|
15
|
+
return actualValue > value;
|
|
16
|
+
}
|
|
17
|
+
else if (operator === 'lt') {
|
|
18
|
+
return actualValue < value;
|
|
19
|
+
}
|
|
20
|
+
else if (operator === 'gte') {
|
|
21
|
+
return actualValue >= value;
|
|
22
|
+
}
|
|
23
|
+
else if (operator === 'lte') {
|
|
24
|
+
return actualValue <= value;
|
|
25
|
+
}
|
|
26
|
+
else if (operator === 'in') {
|
|
27
|
+
return !!value?.includes?.(actualValue);
|
|
28
|
+
}
|
|
29
|
+
else if (operator === 'notIn') {
|
|
30
|
+
return !value?.includes?.(actualValue);
|
|
31
|
+
}
|
|
32
|
+
else if (operator === 'empty') {
|
|
33
|
+
return !actualValue;
|
|
34
|
+
}
|
|
35
|
+
else if (operator === 'notEmpty') {
|
|
36
|
+
return !!actualValue;
|
|
37
|
+
}
|
|
38
|
+
return value !== undefined ? value === actualValue : !!actualValue;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* True when the field should be visible: no conditions, or at least one
|
|
42
|
+
* top-level condition matches (top level is OR-ed, `and` blocks are AND-ed).
|
|
43
|
+
*/
|
|
44
|
+
export function evaluateShowIf(conditions, values, currentFieldName) {
|
|
45
|
+
if (!conditions || conditions.length === 0) {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
return conditions.some((condition) => matchCondition(condition, values, currentFieldName ?? null));
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Coerce a raw form value (usually a string) to the field's type so showIf
|
|
52
|
+
* comparisons behave the same as on the frame itself.
|
|
53
|
+
*/
|
|
54
|
+
export function coerceStateFieldValue(field, value) {
|
|
55
|
+
if (value === undefined || value === null) {
|
|
56
|
+
return value;
|
|
57
|
+
}
|
|
58
|
+
if (field.type === 'boolean') {
|
|
59
|
+
return value === true || value === 'true';
|
|
60
|
+
}
|
|
61
|
+
if (field.type === 'integer') {
|
|
62
|
+
const parsed = typeof value === 'number' ? Math.trunc(value) : parseInt(String(value));
|
|
63
|
+
return isNaN(parsed) ? undefined : parsed;
|
|
64
|
+
}
|
|
65
|
+
if (field.type === 'float') {
|
|
66
|
+
const parsed = typeof value === 'number' ? value : parseFloat(String(value));
|
|
67
|
+
return isNaN(parsed) ? undefined : parsed;
|
|
68
|
+
}
|
|
69
|
+
return value;
|
|
70
|
+
}
|
|
71
|
+
/** The values map for showIf checks: field defaults overlaid with the sources. */
|
|
72
|
+
export function stateFieldShowIfValues(fields, ...valueSources) {
|
|
73
|
+
const values = {};
|
|
74
|
+
for (const field of fields) {
|
|
75
|
+
if (!field.name) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
let value = field.value;
|
|
79
|
+
for (const source of valueSources) {
|
|
80
|
+
if (source && field.name in source && source[field.name] !== undefined) {
|
|
81
|
+
value = source[field.name];
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const coerced = coerceStateFieldValue(field, value);
|
|
85
|
+
if (coerced !== undefined) {
|
|
86
|
+
values[field.name] = coerced;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return values;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* The public state fields visible in a control form given the current values
|
|
93
|
+
* (later sources win, defaults fill the gaps).
|
|
94
|
+
*/
|
|
95
|
+
export function visiblePublicStateFields(fields, ...valueSources) {
|
|
96
|
+
const publicFields = fields.filter((field) => field.access === 'public');
|
|
97
|
+
const values = stateFieldShowIfValues(publicFields, ...valueSources);
|
|
98
|
+
return publicFields.filter((field) => evaluateShowIf(field.showIf, values, field.name ?? null));
|
|
99
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/** One showIf condition: compare a (state) field's value against `value`. */
|
|
2
|
+
export interface ConfigFieldCondition {
|
|
3
|
+
field?: string;
|
|
4
|
+
operator?: 'eq' | 'ne' | 'gt' | 'lt' | 'gte' | 'lte' | 'empty' | 'notEmpty' | 'in' | 'notIn' | null;
|
|
5
|
+
value?: unknown;
|
|
6
|
+
}
|
|
7
|
+
/** All conditions inside `and` must match; top-level conditions are OR-ed. */
|
|
8
|
+
export interface ConfigFieldConditionAnd {
|
|
9
|
+
and: ConfigFieldCondition[];
|
|
10
|
+
}
|
|
11
|
+
export type ShowIfCondition = ConfigFieldCondition | ConfigFieldConditionAnd;
|
|
12
|
+
/** A scene state field, as found in a scene JSON's `fields` array. */
|
|
13
|
+
export interface StateField {
|
|
14
|
+
name: string;
|
|
15
|
+
label?: string;
|
|
16
|
+
type?: string;
|
|
17
|
+
value?: unknown;
|
|
18
|
+
options?: string[];
|
|
19
|
+
placeholder?: string;
|
|
20
|
+
required?: boolean;
|
|
21
|
+
secret?: boolean;
|
|
22
|
+
persist?: 'memory' | 'disk';
|
|
23
|
+
access?: 'private' | 'public';
|
|
24
|
+
showIf?: ShowIfCondition[];
|
|
25
|
+
}
|
|
26
|
+
export interface SceneNode {
|
|
27
|
+
id?: string;
|
|
28
|
+
type?: string;
|
|
29
|
+
data?: Record<string, unknown> & {
|
|
30
|
+
keyword?: string;
|
|
31
|
+
config?: Record<string, unknown>;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/** A FrameOS scene as exported in scenes.json / template zips. */
|
|
35
|
+
export interface FrameOSScene {
|
|
36
|
+
id: string;
|
|
37
|
+
name?: string;
|
|
38
|
+
nodes?: SceneNode[];
|
|
39
|
+
edges?: unknown[];
|
|
40
|
+
fields?: StateField[];
|
|
41
|
+
settings?: Record<string, unknown>;
|
|
42
|
+
default?: boolean;
|
|
43
|
+
[key: string]: unknown;
|
|
44
|
+
}
|
|
45
|
+
/** frameos_wasm_scene_info() payload, sent with the worker's `ready` message. */
|
|
46
|
+
export interface SceneInfo {
|
|
47
|
+
loaded: number;
|
|
48
|
+
currentSceneId: string;
|
|
49
|
+
currentSceneName: string;
|
|
50
|
+
defaultSceneId: string;
|
|
51
|
+
renderRequested: boolean;
|
|
52
|
+
scenes: {
|
|
53
|
+
id: string;
|
|
54
|
+
name: string;
|
|
55
|
+
refreshInterval: number;
|
|
56
|
+
}[];
|
|
57
|
+
}
|
|
58
|
+
export interface PreviewFrame {
|
|
59
|
+
width: number;
|
|
60
|
+
height: number;
|
|
61
|
+
renderMs: number;
|
|
62
|
+
}
|
|
63
|
+
/** An interactive scene event (custom `event` node) usable as a button. */
|
|
64
|
+
export interface SceneEventButton {
|
|
65
|
+
keyword: string;
|
|
66
|
+
label: string | null;
|
|
67
|
+
}
|
|
68
|
+
/** Events every scene handles on its own; not useful as interactive buttons. */
|
|
69
|
+
export declare const LIFECYCLE_EVENTS: Set<string>;
|
|
70
|
+
/** The custom event nodes of a scene, deduplicated — render these as buttons. */
|
|
71
|
+
export declare function sceneEventButtons(scene: FrameOSScene | undefined | null): SceneEventButton[];
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Types for the FrameOS scenes JSON and the preview worker protocol. These
|
|
2
|
+
// mirror the FrameOS backend/frontend definitions (frontend/src/types.tsx and
|
|
3
|
+
// frameos/src/frameos/types.nim) for the parts a browser preview needs.
|
|
4
|
+
/** Events every scene handles on its own; not useful as interactive buttons. */
|
|
5
|
+
export const LIFECYCLE_EVENTS = new Set(['render', 'init', 'open', 'close', 'setSceneState', 'setCurrentScene']);
|
|
6
|
+
/** The custom event nodes of a scene, deduplicated — render these as buttons. */
|
|
7
|
+
export function sceneEventButtons(scene) {
|
|
8
|
+
if (!scene) {
|
|
9
|
+
return [];
|
|
10
|
+
}
|
|
11
|
+
const events = [];
|
|
12
|
+
const seen = new Set();
|
|
13
|
+
for (const node of scene.nodes ?? []) {
|
|
14
|
+
if (node.type !== 'event') {
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
const data = (node.data ?? {});
|
|
18
|
+
const keyword = String(data.keyword ?? '');
|
|
19
|
+
if (!keyword || LIFECYCLE_EVENTS.has(keyword)) {
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
const label = (data.config?.label ?? data.label ?? null);
|
|
23
|
+
const dedupeKey = `${keyword}:${label ?? ''}`;
|
|
24
|
+
if (!seen.has(dedupeKey)) {
|
|
25
|
+
seen.add(dedupeKey);
|
|
26
|
+
events.push({ keyword, label: label ? String(label) : null });
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return events;
|
|
30
|
+
}
|
package/index.html
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>frameos-wasm — scene preview</title>
|
|
7
|
+
<style>
|
|
8
|
+
body { margin: 0 auto; max-width: 860px; padding: 24px; font: 15px/1.5 system-ui, sans-serif; color: #0f172a; }
|
|
9
|
+
h1 { font-size: 22px; }
|
|
10
|
+
.setup { display: flex; flex-direction: column; gap: 8px; margin-bottom: 20px; }
|
|
11
|
+
.setup label { font-weight: 600; font-size: 13px; }
|
|
12
|
+
.setup input, .setup textarea { padding: 8px 10px; border: 1px solid #cbd5e1; border-radius: 8px; font: inherit; }
|
|
13
|
+
.setup textarea { font-family: ui-monospace, monospace; font-size: 12px; }
|
|
14
|
+
.setup .dims { display: flex; gap: 8px; }
|
|
15
|
+
.setup .dims input { width: 110px; }
|
|
16
|
+
button.start { align-self: flex-start; padding: 8px 16px; border: 0; border-radius: 8px; background: #2563eb; color: #fff; font: inherit; cursor: pointer; }
|
|
17
|
+
button.start:hover { background: #1d4ed8; }
|
|
18
|
+
.hint { font-size: 13px; color: #475569; }
|
|
19
|
+
</style>
|
|
20
|
+
</head>
|
|
21
|
+
<body>
|
|
22
|
+
<h1>frameos-wasm scene preview</h1>
|
|
23
|
+
<p class="hint">
|
|
24
|
+
Runs a FrameOS scene in your browser through the WebAssembly runtime, with the same state fields, event
|
|
25
|
+
buttons, and logs the frame itself exposes. Paste the contents of a <code>scenes.json</code> (from a template
|
|
26
|
+
zip, a backup, or an export) and press start. Serve this file over HTTP with the runtime assets next to it —
|
|
27
|
+
for example: <code>npx serve node_modules/frameos-wasm</code> and open <code>/index.html</code>.
|
|
28
|
+
</p>
|
|
29
|
+
<div class="setup">
|
|
30
|
+
<label for="scenes">scenes.json</label>
|
|
31
|
+
<textarea id="scenes" rows="8" placeholder='[{"id": "...", "nodes": [...], "fields": [...]}]'></textarea>
|
|
32
|
+
<label>Dimensions</label>
|
|
33
|
+
<div class="dims">
|
|
34
|
+
<input id="width" type="number" value="800" /> ×
|
|
35
|
+
<input id="height" type="number" value="480" />
|
|
36
|
+
</div>
|
|
37
|
+
<button class="start" id="start">Start preview</button>
|
|
38
|
+
</div>
|
|
39
|
+
<div id="manager"></div>
|
|
40
|
+
<script type="module">
|
|
41
|
+
import { mountFrameOSManager } from './dist/index.js'
|
|
42
|
+
|
|
43
|
+
let handle = null
|
|
44
|
+
document.getElementById('start').onclick = () => {
|
|
45
|
+
let scenes
|
|
46
|
+
try {
|
|
47
|
+
scenes = JSON.parse(document.getElementById('scenes').value)
|
|
48
|
+
} catch (error) {
|
|
49
|
+
alert('scenes.json did not parse: ' + error)
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
if (!Array.isArray(scenes) || scenes.length === 0) {
|
|
53
|
+
alert('Expected a non-empty JSON array of scenes.')
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
handle?.destroy()
|
|
57
|
+
handle = mountFrameOSManager(document.getElementById('manager'), {
|
|
58
|
+
workerUrl: new URL('./dist/assets/preview-worker.js', import.meta.url),
|
|
59
|
+
width: parseInt(document.getElementById('width').value) || 800,
|
|
60
|
+
height: parseInt(document.getElementById('height').value) || 480,
|
|
61
|
+
scenes,
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
</script>
|
|
65
|
+
</body>
|
|
66
|
+
</html>
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "frameos-wasm",
|
|
3
|
+
"version": "2026.7.3",
|
|
4
|
+
"description": "Run FrameOS scenes in the browser via WebAssembly: a typed live-preview runtime plus a minimal showIf-aware management interface.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"frameos",
|
|
7
|
+
"wasm",
|
|
8
|
+
"webassembly",
|
|
9
|
+
"e-ink",
|
|
10
|
+
"smart-frame"
|
|
11
|
+
],
|
|
12
|
+
"homepage": "https://frameos.net",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "https://github.com/FrameOS/frameos.git",
|
|
16
|
+
"directory": "frameos/wasm"
|
|
17
|
+
},
|
|
18
|
+
"license": "AGPL-3.0-only",
|
|
19
|
+
"type": "module",
|
|
20
|
+
"main": "./dist/index.js",
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"import": "./dist/index.js"
|
|
26
|
+
},
|
|
27
|
+
"./assets/*": "./dist/assets/*",
|
|
28
|
+
"./package.json": "./package.json"
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"dist",
|
|
32
|
+
"index.html",
|
|
33
|
+
"README.md"
|
|
34
|
+
],
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"typescript": "~5.9.3"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsc -p tsconfig.json && node scripts/build.mjs",
|
|
40
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
41
|
+
"sync-version": "node scripts/sync-version.mjs"
|
|
42
|
+
}
|
|
43
|
+
}
|