@wyndlass/app-sdk 0.0.0-stage → 1.0.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/LICENSE +21 -0
- package/README.md +196 -2
- package/cli/app-package.mjs +111 -0
- package/cli/commands.mjs +114 -0
- package/cli/main.mjs +66 -0
- package/cli/package.mjs +119 -0
- package/cli/preview.mjs +142 -0
- package/cli/zip.mjs +63 -0
- package/dist/contract/app-contract.d.ts +221 -0
- package/dist/contract/app-contract.js +6 -0
- package/dist/contract/app-definition.d.ts +155 -0
- package/dist/contract/app-definition.js +3 -0
- package/dist/contract/data-answer.d.ts +40 -0
- package/dist/contract/data-answer.js +3 -0
- package/dist/contract/data-calendar.d.ts +57 -0
- package/dist/contract/data-calendar.js +3 -0
- package/dist/contract/data-news.d.ts +29 -0
- package/dist/contract/data-news.js +3 -0
- package/dist/contract/data-quotes.d.ts +75 -0
- package/dist/contract/data-quotes.js +3 -0
- package/dist/contract/data-series.d.ts +51 -0
- package/dist/contract/data-series.js +3 -0
- package/dist/contract/data-snapshot.d.ts +30 -0
- package/dist/contract/data-snapshot.js +3 -0
- package/dist/contract/data-weather.d.ts +421 -0
- package/dist/contract/data-weather.js +3 -0
- package/dist/contract/player-api-event.d.ts +253 -0
- package/dist/contract/player-api-event.js +3 -0
- package/dist/contract/player-api-status.d.ts +178 -0
- package/dist/contract/player-api-status.js +3 -0
- package/dist/easing.d.ts +16 -0
- package/dist/easing.js +71 -0
- package/dist/index.d.ts +94 -0
- package/dist/index.js +218 -0
- package/dist/preview/frame/frame.js +2 -0
- package/dist/preview/frame/index.html +12 -0
- package/dist/preview/index.html +40 -0
- package/dist/preview/page.js +7 -0
- package/dist/qr.d.ts +5 -0
- package/dist/qr.js +14 -0
- package/dist/react.d.ts +52 -0
- package/dist/react.js +149 -0
- package/package.json +83 -3
- package/protocol/app-definition.schema.json +435 -0
- package/protocol/app-packages.json +172 -0
- package/protocol/common.schema.json +197 -0
- package/protocol/samples/calendar.json +63 -0
- package/protocol/samples/events/app.command.json +14 -0
- package/protocol/samples/events/item.end.json +13 -0
- package/protocol/samples/events/item.start.json +18 -0
- package/protocol/samples/events/state.changed.json +11 -0
- package/protocol/samples/news.json +37 -0
- package/protocol/samples/player-status.json +58 -0
- package/protocol/samples/weather.json +59 -0
- package/templates/react/AGENTS.md +13 -0
- package/templates/react/README.md +22 -0
- package/templates/react/app.json +14 -0
- package/templates/react/gitignore +2 -0
- package/templates/react/package.json +22 -0
- package/templates/react/src/app.css +7 -0
- package/templates/react/src/app.tsx +39 -0
- package/templates/react/src/env.d.ts +5 -0
- package/templates/react/tsconfig.json +13 -0
- package/templates/vanilla/AGENTS.md +13 -0
- package/templates/vanilla/README.md +22 -0
- package/templates/vanilla/app.json +14 -0
- package/templates/vanilla/gitignore +2 -0
- package/templates/vanilla/package.json +18 -0
- package/templates/vanilla/src/app.css +7 -0
- package/templates/vanilla/src/app.ts +59 -0
- package/templates/vanilla/src/env.d.ts +5 -0
- package/templates/vanilla/tsconfig.json +12 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# __APP_NAME__
|
|
2
|
+
|
|
3
|
+
An app for Wyndlass's screens (`__APP_ID__`), built on [`@wyndlass/app-sdk`](https://www.npmjs.com/package/@wyndlass/app-sdk).
|
|
4
|
+
|
|
5
|
+
- `app.json` is its definition: its id, version, name, element, params and the data it reads.
|
|
6
|
+
- `src/app.ts` is its module: the element's class, the default export. `src/app.css` is its styles.
|
|
7
|
+
|
|
8
|
+
## Commands
|
|
9
|
+
|
|
10
|
+
- `npm run preview`: the app as screens run it, at http://127.0.0.1:5199/, drawn again as you change it. Its controls set the screen's size, the params, the clock, enter and exit, the time zone, the screen's facts and each data source's answer, and give the app tables from CSV or JSON, the people at the screen and the player's events where it uses them. `npm run preview -- --frame` runs it in the frame the CMS gives apps, where it has no storage.
|
|
11
|
+
- `npm run check`: the checks Wyndlass makes when you import it.
|
|
12
|
+
- `npm run pack`: `dist/__APP_ID__-<version>.zip`, the package to import, after the checks.
|
|
13
|
+
- `npm run typecheck`: TypeScript's check.
|
|
14
|
+
|
|
15
|
+
Import the package through the Wyndlass MCP (`app.prepareImport`, `npx wyndlass-app upload`, then `app.import`) or in the CMS (Content, Library, Import an app). A person who may approve media there reviews the app's first version, and any that asks for more than the one before it, before screens play it. Raise `version` in `app.json` for each new one.
|
|
16
|
+
|
|
17
|
+
## What screens enforce
|
|
18
|
+
|
|
19
|
+
- The time comes from `now()` (the `epochMs` `draw` is given), never `Date.now()`.
|
|
20
|
+
- `ready()` within 5 s of the app's start.
|
|
21
|
+
- No `localStorage` for anything that matters, and no secrets in params or the module: both are readable.
|
|
22
|
+
- Data only from the sources `app.json` declares (`weather`, `news`, `image` or `calendar`), and the network hosts declared truthfully in `capabilities.network`: a person approves them.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "__APP_ID__",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"name": "__APP_NAME__",
|
|
5
|
+
"element": "__ELEMENT__",
|
|
6
|
+
"description": "A heading and the time on one page, the weather on the next.",
|
|
7
|
+
"params": {
|
|
8
|
+
"heading": { "type": "string", "default": "Good morning", "label": "Heading" },
|
|
9
|
+
"accent": { "type": "color", "default": "#4f8cff", "label": "Accent" },
|
|
10
|
+
"units": { "type": "enum", "values": ["metric", "imperial"], "default": "metric", "label": "Units" },
|
|
11
|
+
"seconds": { "type": "boolean", "default": false, "label": "Show seconds" }
|
|
12
|
+
},
|
|
13
|
+
"data": ["weather"]
|
|
14
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "__PACKAGE__",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"preview": "wyndlass-app preview",
|
|
8
|
+
"check": "wyndlass-app check",
|
|
9
|
+
"pack": "wyndlass-app pack",
|
|
10
|
+
"typecheck": "tsc --noEmit"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@wyndlass/app-sdk": "__SDK__"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"typescript": "^5.9.3"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
:host { display: block; width: 100%; height: 100%; color: #ffffff; font-family: system-ui, sans-serif; }
|
|
2
|
+
.page { width: 100%; height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: calc(var(--h) * .04); }
|
|
3
|
+
.page[hidden] { display: none; }
|
|
4
|
+
h1 { margin: 0; font-size: min(calc(var(--h) * .14), calc(var(--w) * .08)); font-weight: 600; color: var(--accent); }
|
|
5
|
+
time { font-size: min(calc(var(--h) * .3), calc(var(--w) * .16)); font-variant-numeric: tabular-nums; }
|
|
6
|
+
.temperature { font-size: min(calc(var(--h) * .3), calc(var(--w) * .16)); }
|
|
7
|
+
.condition, .none { font-size: min(calc(var(--h) * .1), calc(var(--w) * .05)); opacity: .85; }
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// __APP_NAME__: a heading and the time on one page, the weather on the next, built on Wyndlass's app SDK.
|
|
2
|
+
// Everything it draws comes from the layout's clock (now()), so the designer's seek and pause show the right moment.
|
|
3
|
+
import { WyndlassApp, color, contextOf, data, kept, type DataAnswer, type weather } from "@wyndlass/app-sdk";
|
|
4
|
+
import css from "./app.css";
|
|
5
|
+
|
|
6
|
+
/** How long each of the two pages shows. */
|
|
7
|
+
const PAGE_MS = 8_000;
|
|
8
|
+
/** How often the weather is asked for again. */
|
|
9
|
+
const EVERY_MS = 10 * 60_000;
|
|
10
|
+
|
|
11
|
+
export default class App extends WyndlassApp {
|
|
12
|
+
#pages: HTMLElement[] = [];
|
|
13
|
+
#heading!: HTMLElement;
|
|
14
|
+
#time!: HTMLElement;
|
|
15
|
+
#weather!: HTMLElement;
|
|
16
|
+
#answer?: DataAnswer<weather.Weather>;
|
|
17
|
+
#askedAt = -Infinity;
|
|
18
|
+
#format?: Intl.DateTimeFormat;
|
|
19
|
+
|
|
20
|
+
protected mount() {
|
|
21
|
+
this.css(css);
|
|
22
|
+
this.style.setProperty("--accent", color(this.prop("accent", "#4f8cff"), "#4f8cff"));
|
|
23
|
+
this.root.innerHTML = `<section class="page"><h1></h1><time></time></section><section class="page" hidden><div data-weather></div></section>`;
|
|
24
|
+
this.#pages = [...this.root.querySelectorAll<HTMLElement>(".page")];
|
|
25
|
+
this.#heading = this.root.querySelector("h1")!;
|
|
26
|
+
this.#time = this.root.querySelector("time")!;
|
|
27
|
+
this.#weather = this.root.querySelector("[data-weather]")!;
|
|
28
|
+
this.#heading.textContent = this.prop("heading", "Good morning");
|
|
29
|
+
this.#format = new Intl.DateTimeFormat(this.locale, { timeZone: this.timeZone, hour: "numeric", minute: "2-digit", ...(this.prop("seconds", false) ? { second: "2-digit" } : {}) });
|
|
30
|
+
// The answer the screen kept, at once: the app is ready without waiting for the network.
|
|
31
|
+
this.#answer = kept<weather.Weather>(this, "weather", this.#params());
|
|
32
|
+
if (this.#answer) this.ready();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** The weather for the screen's place, or Winnipeg's where there is none (the designer's sample). */
|
|
36
|
+
#params() {
|
|
37
|
+
const place = contextOf(this)?.place ?? { lat: 49.9, lon: -97.14 };
|
|
38
|
+
return { lat: place.lat, lon: place.lon, units: this.prop("units", "metric") };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
protected draw(epochMs: number) {
|
|
42
|
+
if (epochMs - this.#askedAt >= EVERY_MS) {
|
|
43
|
+
this.#askedAt = epochMs;
|
|
44
|
+
data<weather.Weather>(this, "weather", this.#params())
|
|
45
|
+
.then(answer => { this.#answer = answer; this.update(this.now()); }, () => undefined)
|
|
46
|
+
.finally(() => this.ready());
|
|
47
|
+
}
|
|
48
|
+
const page = Math.floor(Math.max(0, epochMs - this.startedAt()) / PAGE_MS) % 2;
|
|
49
|
+
this.#pages.forEach((p, i) => { p.hidden = i !== page; });
|
|
50
|
+
this.#time.textContent = this.#format!.format(epochMs);
|
|
51
|
+
const w = this.#answer?.data;
|
|
52
|
+
this.#weather.innerHTML = w
|
|
53
|
+
? `<div class="temperature">${Math.round(w.current.temperature)}°${w.units === "imperial" ? "F" : "C"}</div><div class="condition">${w.current.condition.replace(/-/g, " ")}</div>`
|
|
54
|
+
: `<div class="none">The weather is on its way.</div>`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Two pages of PAGE_MS each, for a layout that turns its enter and exit with them. */
|
|
58
|
+
paging() { return { count: 2, ms: PAGE_MS }; }
|
|
59
|
+
}
|