@wyndlass/app-sdk 1.3.0 → 1.5.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/README.md +20 -2
- package/dist/commands.d.ts +10 -0
- package/dist/commands.js +16 -0
- package/dist/contract/player-api-event.d.ts +60 -1
- package/dist/contract/player-api-status.d.ts +48 -0
- package/dist/following.d.ts +39 -0
- package/dist/following.js +61 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/preview/frame/frame.js +1 -1
- package/dist/preview/page.js +40 -5
- package/dist/react.d.ts +20 -0
- package/dist/react.js +33 -1
- package/dist/signals.d.ts +28 -4
- package/dist/signals.js +45 -4
- package/package.json +1 -1
- package/skill/wyndlass-app/references/contract.md +17 -3
- package/templates/react/AGENTS.md +2 -0
- package/templates/vanilla/AGENTS.md +2 -0
package/README.md
CHANGED
|
@@ -63,7 +63,7 @@ Its controls:
|
|
|
63
63
|
- **Data:** each data source's answer, from samples you can edit in the page. `--data <dir>` reads `<source>.json` there instead (the answer's `data`, or a whole answer). The preview reaches no data service. A source the definition doesn't declare gets no answer, as on a screen.
|
|
64
64
|
- **Tables:** each file in `<dir>/tables/`, or one you add in the page. A file may be CSV, a JSON array of rows, or a snapshot. A CSV's header gives its columns' labels, and their keys are made as Wyndlass makes a table's (`Now serving` is `now_serving`), so a `column` param holds the key it will on screens. A `datatable` param chooses among the tables, a `column` param among the chosen table's columns, and an app with `slots` is bound to the first table. Edit a table's rows in the page, or change its file, and the app gets them as it plays, as a screen gives a table's new version: frozen, and without making the app again. The report says how many times the app was made.
|
|
65
65
|
- **The people at the screen,** for an app that declares the `impressions` sensor: someone arrives, looks, or leaves.
|
|
66
|
-
- **The player's controller,** for what `capabilities.player` allows. The page sends the app the player's events, each one editable. A command the app may give is answered "not applied", and one it may not is refused, as on a screen.
|
|
66
|
+
- **The player's controller,** for what `capabilities.player` allows. The page sends the app the player's events, each one editable, and the controller's status follows them, as a screen's does. It sends a command by name, with an argument, as `apps.sendCommand` does. A command the app may give is answered "not applied", and one it may not is refused, as on a screen.
|
|
67
67
|
|
|
68
68
|
**What a screen would report** is shown beside the app: how long it took to say it was ready, or that it was late, and each failure with its reason.
|
|
69
69
|
|
|
@@ -148,7 +148,9 @@ export default class QueueBoard extends WyndlassApp {
|
|
|
148
148
|
- `data(this, source, params)`, the data service's answer;
|
|
149
149
|
- `kept(this, source, params)`, the answer the screen last kept, at once;
|
|
150
150
|
- `image(this, path)`, `snapshot(this, table)`, `controllerOf(this)` and `impressionsOf(this)`;
|
|
151
|
+
- `onCommand(this, name, handler)`, each command of that name sent to the screen's apps (`apps.sendCommand`), with its argument. A command is an event: the same one sent twice runs twice;
|
|
151
152
|
- `contextOf(this)`, for the rest.
|
|
153
|
+
- **Follow the player with the signals or hooks** (`playbackSignal`, `usePlayback` and the rest, below), never by reading `controllerOf(this).status()` at each frame.
|
|
152
154
|
- **A table's rows change while the app plays.** A screen gives the app a table's new rows within about a second of the save, without making it again. Read them where you draw: `snapshot()` in `draw()`, `useSnapshot`, or `tableSignal`. Never read them once in `mount()` and keep them. On a screen they are the same object until they change, and frozen: copy rows before you sort or change them.
|
|
153
155
|
- **Helpers:** `fit`, `typographyCss`, `color` and `escape`, and the presets' curves `easingOf`, `cubicBezier` and `staggerTimes`.
|
|
154
156
|
- **Types:**
|
|
@@ -184,12 +186,15 @@ export default reactApp(Board, { css });
|
|
|
184
186
|
- **`usePaging(count, ms)`:** the page showing now, rendering when it turns. It also tells the host the pages.
|
|
185
187
|
- **`useData(source, params)`:** at once the answer the screen kept, then the service's, asked again every 10 minutes. `error` is set once an ask fails with nothing to show.
|
|
186
188
|
- **`useSnapshot(table)`:** a data table's snapshot, as `snapshot` gives it. The component renders when the rows change: when the screen gets a table's new version, or when a binding's rows, chosen on the layout's clock, change. On players before Wyndlass's live rows (WP-0149), which make the app again when a table changes, it renders at every frame, as before.
|
|
189
|
+
- **`usePlayback()`, `useConnection()`, `useAudio()`, `usePlaying()`:** the player's state (starting, playing, paused, stopped, idle or error), online or offline, the volume and whether it is muted, and the items playing. Each renders only when its value changes. Each is `null` where the app has no controller: in the designer and the CMS's previews.
|
|
190
|
+
- **`useCommand(name, handler)`:** each command of that name sent to the screen's apps, with its argument, while the component is mounted.
|
|
191
|
+
- **`useEmergency()`:** emergency messages at the screen (see below). The component renders when they change.
|
|
187
192
|
|
|
188
193
|
The hooks reach the app's context through the SDK's own module, never through React's tree, so nothing another app could use hangs off the shadow root.
|
|
189
194
|
|
|
190
195
|
## Signals (`@wyndlass/app-sdk/signals`)
|
|
191
196
|
|
|
192
|
-
For an app that updates its DOM when its rows change, rather than at every frame:
|
|
197
|
+
For an app that updates its DOM when its rows or the player's state change, rather than at every frame:
|
|
193
198
|
|
|
194
199
|
```ts
|
|
195
200
|
import { WyndlassApp } from "@wyndlass/app-sdk";
|
|
@@ -208,8 +213,21 @@ export default class Queue extends WyndlassApp {
|
|
|
208
213
|
```
|
|
209
214
|
|
|
210
215
|
- **`tableSignal(this, table)`:** a read-only [Preact signal](https://github.com/preactjs/signals) of the table's snapshot. Its value changes when the rows do, and is the same object until then. Make it in `mount()`.
|
|
216
|
+
- **`playbackSignal(this)`, `connectionSignal(this)`, `audioSignal(this)`, `nowSignal(this)`:** the player's state, online or offline, the volume and whether it is muted, and the items playing, as read-only signals. Each is set only when its value changes, and is `null` where the app has no controller. Make them in `mount()`.
|
|
217
|
+
- **`emergencySignal(this)`:** emergency messages at the screen (below), as a read-only signal.
|
|
211
218
|
- **`effect`, `computed`, `batch`, `signal`, `untracked`:** Preact's, from the same copy. Import them from here, not from `@preact/signals-core`: an effect from another copy does not see these signals.
|
|
212
219
|
- **Its own entry,** so an app that doesn't import it doesn't bundle it (about 2 KB gzipped when it does).
|
|
220
|
+
|
|
221
|
+
## Emergency messages
|
|
222
|
+
|
|
223
|
+
A screen shows emergency messages itself, over every app: a takeover covers the screen, and a banner covers its bottom 14% while the content above it plays on. `emergencySignal(this)` and `useEmergency()` say what shows, so an app can behave:
|
|
224
|
+
|
|
225
|
+
- **`{ showing, messages }`:** `showing` is `none`, `banner` or `takeover`. Each message in force gives its `source`, `severity`, `presentation`, `event` and `category` where it has them, and `test` for a test or a drill.
|
|
226
|
+
- **`null`** where the app has no controller, or before the screen has said what it holds. That is not the same as `none`.
|
|
227
|
+
- **Under a banner,** the app plays on below it. It may lower its sound, stop calling numbers or pause what calls for attention, or show the way out.
|
|
228
|
+
- **Never an alert's words.** The screen shows them in one design that apps can't change, and an app that drew them again could show one out of date or mistranslated.
|
|
229
|
+
- **An app can't move, cover or hide the emergency layer.**
|
|
230
|
+
- **In the preview,** the controller's panel shows a Test banner or takeover over the app, drawn as a screen draws it, and the app gets its `emergency.changed`.
|
|
213
231
|
- On players before WP-0149, the signal keeps the rows the app was made with: such a player makes the app again when a table changes.
|
|
214
232
|
|
|
215
233
|
## Limits
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Commands sent to the screen's apps (`apps.sendCommand`, the `app.command` event; WP-0150 decision 3). The main entry
|
|
3
|
+
* gives it; a module of its own, so the runtime that every app bundles is not changed by it.
|
|
4
|
+
*/
|
|
5
|
+
import { type WyndlassApp } from "./index.js";
|
|
6
|
+
/**
|
|
7
|
+
* Calls `handler` with the argument of each command of this name sent to the screen's apps. A command is an event: the
|
|
8
|
+
* same one sent twice runs twice. Returns how to stop. Never called where the app has no controller (the designer).
|
|
9
|
+
*/
|
|
10
|
+
export declare function onCommand(app: WyndlassApp, name: string, handler: (argument: Record<string, unknown> | undefined) => void): () => void;
|
package/dist/commands.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Commands sent to the screen's apps (`apps.sendCommand`, the `app.command` event; WP-0150 decision 3). The main entry
|
|
3
|
+
* gives it; a module of its own, so the runtime that every app bundles is not changed by it.
|
|
4
|
+
*/
|
|
5
|
+
import { controllerOf } from "./index.js";
|
|
6
|
+
/**
|
|
7
|
+
* Calls `handler` with the argument of each command of this name sent to the screen's apps. A command is an event: the
|
|
8
|
+
* same one sent twice runs twice. Returns how to stop. Never called where the app has no controller (the designer).
|
|
9
|
+
*/
|
|
10
|
+
export function onCommand(app, name, handler) {
|
|
11
|
+
const controller = controllerOf(app);
|
|
12
|
+
if (!controller)
|
|
13
|
+
return () => { };
|
|
14
|
+
return controller.on("app.command", e => { if (e.type === "app.command" && e.name === name)
|
|
15
|
+
handler(e.argument); });
|
|
16
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* An event the controller emits (protocol/player-api.json, ADR 0050): to apps that subscribed on the screen, and in the ring player.events reads. Told apart by type.
|
|
3
3
|
*/
|
|
4
|
-
export type PlayerAPIEvent = StateChanged | ContentUpdated | ItemStart | ItemEnd | ItemError | ConnectionChanged | AudioChanged | DisplayChanged | TimeChanged | AppCommand | AppFailed | AppHeld;
|
|
4
|
+
export type PlayerAPIEvent = StateChanged | ContentUpdated | ItemStart | ItemEnd | ItemError | ConnectionChanged | AudioChanged | DisplayChanged | TimeChanged | EmergencyChanged | AppCommand | AppFailed | AppHeld;
|
|
5
5
|
/**
|
|
6
6
|
* RFC 3339 timestamp with offset.
|
|
7
7
|
*/
|
|
@@ -189,6 +189,65 @@ export interface TimeChanged {
|
|
|
189
189
|
at: CommonTimestamp;
|
|
190
190
|
reason: "clock-adjusted" | "time-zone-changed";
|
|
191
191
|
}
|
|
192
|
+
/**
|
|
193
|
+
* What the screen shows of emergency messages, or the messages in force, changed (WP-0150): the status's emergency as it is now.
|
|
194
|
+
*/
|
|
195
|
+
export interface EmergencyChanged {
|
|
196
|
+
type: "emergency.changed";
|
|
197
|
+
/**
|
|
198
|
+
* The event's place in this player's sequence since it started; player.events pages by it.
|
|
199
|
+
*/
|
|
200
|
+
seq: number;
|
|
201
|
+
at: CommonTimestamp;
|
|
202
|
+
emergency: PlayerApiCommonEmergency;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Emergency messages at the screen as apps and agents may read them (WP-0150 decision 4, ADR 0135): what shows, and of each message in force what conditions read (ADR 0127 decision 9) and whether it is a test. Never its words: no headline, description, instruction, area or sender. Absent until the shell has said what it holds.
|
|
206
|
+
*/
|
|
207
|
+
export interface PlayerApiCommonEmergency {
|
|
208
|
+
/**
|
|
209
|
+
* What the screen shows of emergency messages now: a takeover, over everything; a banner across the bottom, over content that plays on; or none. A record shows nothing.
|
|
210
|
+
*/
|
|
211
|
+
showing: "none" | "banner" | "takeover";
|
|
212
|
+
/**
|
|
213
|
+
* Each message in force at the screen, records and Test messages included.
|
|
214
|
+
*/
|
|
215
|
+
messages: PlayerApiCommonEmergencyFact[];
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* One emergency message in force, as apps and agents may read it: never its words.
|
|
219
|
+
*/
|
|
220
|
+
export interface PlayerApiCommonEmergencyFact {
|
|
221
|
+
/**
|
|
222
|
+
* Where it came from: a person (manual), an organization's own feed, the National Weather Service or IPAWS.
|
|
223
|
+
*/
|
|
224
|
+
source: "manual" | "cap-feed" | "nws" | "ipaws";
|
|
225
|
+
/**
|
|
226
|
+
* CAP's severity, from highest.
|
|
227
|
+
*/
|
|
228
|
+
severity: "Extreme" | "Severe" | "Moderate" | "Minor" | "Unknown";
|
|
229
|
+
/**
|
|
230
|
+
* takeover: above everything; banner: a strip over content that keeps playing; record: held and read by conditions, but not shown.
|
|
231
|
+
*/
|
|
232
|
+
presentation: "takeover" | "banner" | "record";
|
|
233
|
+
/**
|
|
234
|
+
* CAP's event, such as Tornado Warning or Lockdown, where the message has one.
|
|
235
|
+
*/
|
|
236
|
+
event?: string;
|
|
237
|
+
/**
|
|
238
|
+
* CAP's categories, where the message has them.
|
|
239
|
+
*
|
|
240
|
+
* @minItems 1
|
|
241
|
+
*/
|
|
242
|
+
category?: [
|
|
243
|
+
("Geo" | "Met" | "Safety" | "Security" | "Rescue" | "Fire" | "Health" | "Env" | "Transport" | "Infra" | "CBRNE" | "Other"),
|
|
244
|
+
...("Geo" | "Met" | "Safety" | "Security" | "Rescue" | "Fire" | "Health" | "Env" | "Transport" | "Infra" | "CBRNE" | "Other")[]
|
|
245
|
+
];
|
|
246
|
+
/**
|
|
247
|
+
* Status Test or Exercise: the screen marks it TEST.
|
|
248
|
+
*/
|
|
249
|
+
test: boolean;
|
|
250
|
+
}
|
|
192
251
|
export interface AppCommand {
|
|
193
252
|
type: "app.command";
|
|
194
253
|
/**
|
|
@@ -83,6 +83,7 @@ export interface PlayerAPIStatus {
|
|
|
83
83
|
now: NowItem[];
|
|
84
84
|
audio?: PlayerApiCommonAudio;
|
|
85
85
|
display?: PlayerApiCommonDisplay;
|
|
86
|
+
emergency?: PlayerApiCommonEmergency;
|
|
86
87
|
capabilities: PlayerApiCommonHostCapabilities;
|
|
87
88
|
}
|
|
88
89
|
/**
|
|
@@ -155,6 +156,53 @@ export interface PlayerApiCommonDisplay {
|
|
|
155
156
|
power: "on" | "standby" | "unknown";
|
|
156
157
|
brightness?: number;
|
|
157
158
|
}
|
|
159
|
+
/**
|
|
160
|
+
* Emergency messages at the screen as apps and agents may read them (WP-0150 decision 4, ADR 0135): what shows, and of each message in force what conditions read (ADR 0127 decision 9) and whether it is a test. Never its words: no headline, description, instruction, area or sender. Absent until the shell has said what it holds.
|
|
161
|
+
*/
|
|
162
|
+
export interface PlayerApiCommonEmergency {
|
|
163
|
+
/**
|
|
164
|
+
* What the screen shows of emergency messages now: a takeover, over everything; a banner across the bottom, over content that plays on; or none. A record shows nothing.
|
|
165
|
+
*/
|
|
166
|
+
showing: "none" | "banner" | "takeover";
|
|
167
|
+
/**
|
|
168
|
+
* Each message in force at the screen, records and Test messages included.
|
|
169
|
+
*/
|
|
170
|
+
messages: PlayerApiCommonEmergencyFact[];
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* One emergency message in force, as apps and agents may read it: never its words.
|
|
174
|
+
*/
|
|
175
|
+
export interface PlayerApiCommonEmergencyFact {
|
|
176
|
+
/**
|
|
177
|
+
* Where it came from: a person (manual), an organization's own feed, the National Weather Service or IPAWS.
|
|
178
|
+
*/
|
|
179
|
+
source: "manual" | "cap-feed" | "nws" | "ipaws";
|
|
180
|
+
/**
|
|
181
|
+
* CAP's severity, from highest.
|
|
182
|
+
*/
|
|
183
|
+
severity: "Extreme" | "Severe" | "Moderate" | "Minor" | "Unknown";
|
|
184
|
+
/**
|
|
185
|
+
* takeover: above everything; banner: a strip over content that keeps playing; record: held and read by conditions, but not shown.
|
|
186
|
+
*/
|
|
187
|
+
presentation: "takeover" | "banner" | "record";
|
|
188
|
+
/**
|
|
189
|
+
* CAP's event, such as Tornado Warning or Lockdown, where the message has one.
|
|
190
|
+
*/
|
|
191
|
+
event?: string;
|
|
192
|
+
/**
|
|
193
|
+
* CAP's categories, where the message has them.
|
|
194
|
+
*
|
|
195
|
+
* @minItems 1
|
|
196
|
+
*/
|
|
197
|
+
category?: [
|
|
198
|
+
("Geo" | "Met" | "Safety" | "Security" | "Rescue" | "Fire" | "Health" | "Env" | "Transport" | "Infra" | "CBRNE" | "Other"),
|
|
199
|
+
...("Geo" | "Met" | "Safety" | "Security" | "Rescue" | "Fire" | "Health" | "Env" | "Transport" | "Infra" | "CBRNE" | "Other")[]
|
|
200
|
+
];
|
|
201
|
+
/**
|
|
202
|
+
* Status Test or Exercise: the screen marks it TEST.
|
|
203
|
+
*/
|
|
204
|
+
test: boolean;
|
|
205
|
+
}
|
|
158
206
|
/**
|
|
159
207
|
* What the host can do beyond the controller's own; tools that need what it lacks are not listed.
|
|
160
208
|
*/
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What an app follows of the player (WP-0150 decisions 1 and 2): a field of its controller's status, read again on the
|
|
3
|
+
* events that change it, and the same object until its value changes (compared as JSON). Null without a controller: in
|
|
4
|
+
* the designer, or on a player that gives none. Not one of the package's exports: signals.ts and react.ts make it public.
|
|
5
|
+
*/
|
|
6
|
+
import type { PlayerAPIStatus } from "./contract/player-api-status.js";
|
|
7
|
+
import { type WyndlassApp } from "./index.js";
|
|
8
|
+
/** Each field an app can follow, and the events after which it is read again. */
|
|
9
|
+
declare const FIELDS: {
|
|
10
|
+
readonly playback: {
|
|
11
|
+
readonly events: readonly ["state.changed"];
|
|
12
|
+
readonly read: (s: PlayerAPIStatus) => import("./contract/player-api-status.js").State;
|
|
13
|
+
};
|
|
14
|
+
readonly connection: {
|
|
15
|
+
readonly events: readonly ["connection.changed"];
|
|
16
|
+
readonly read: (s: PlayerAPIStatus) => import("./contract/player-api-status.js").PlayerApiCommonConnection;
|
|
17
|
+
};
|
|
18
|
+
readonly audio: {
|
|
19
|
+
readonly events: readonly ["audio.changed"];
|
|
20
|
+
readonly read: (s: PlayerAPIStatus) => import("./contract/player-api-status.js").PlayerApiCommonAudio | null;
|
|
21
|
+
};
|
|
22
|
+
readonly now: {
|
|
23
|
+
readonly events: readonly ["item.start", "item.end"];
|
|
24
|
+
readonly read: (s: PlayerAPIStatus) => import("./contract/player-api-status.js").NowItem[];
|
|
25
|
+
};
|
|
26
|
+
readonly emergency: {
|
|
27
|
+
readonly events: readonly ["emergency.changed"];
|
|
28
|
+
readonly read: (s: PlayerAPIStatus) => import("./contract/player-api-status.js").PlayerApiCommonEmergency | null;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
export type Field = keyof typeof FIELDS;
|
|
32
|
+
export type Followed<K extends Field> = ReturnType<(typeof FIELDS)[K]["read"]> | null;
|
|
33
|
+
/** A field's value now, and who to tell when it changes, as useSyncExternalStore and a signal take them. */
|
|
34
|
+
export interface Following<T> {
|
|
35
|
+
get(): T;
|
|
36
|
+
subscribe(listener: () => void): () => void;
|
|
37
|
+
}
|
|
38
|
+
export declare function follow<K extends Field>(app: WyndlassApp, field: K): Following<Followed<K>>;
|
|
39
|
+
export {};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { controllerOf } from "./index.js";
|
|
2
|
+
/** Each field an app can follow, and the events after which it is read again. */
|
|
3
|
+
const FIELDS = {
|
|
4
|
+
playback: { events: ["state.changed"], read: (s) => s.state },
|
|
5
|
+
connection: { events: ["connection.changed"], read: (s) => s.connection },
|
|
6
|
+
audio: { events: ["audio.changed"], read: (s) => s.audio ?? null },
|
|
7
|
+
now: { events: ["item.start", "item.end"], read: (s) => s.now },
|
|
8
|
+
// Absent until the shell has said what it holds: null then, as for an older player (WP-0150 decision 4).
|
|
9
|
+
emergency: { events: ["emergency.changed"], read: (s) => s.emergency ?? null },
|
|
10
|
+
};
|
|
11
|
+
export function follow(app, field) {
|
|
12
|
+
const controller = controllerOf(app);
|
|
13
|
+
const { events, read } = FIELDS[field];
|
|
14
|
+
const now = () => {
|
|
15
|
+
// A controller not yet started throws; the field is unknown until it has.
|
|
16
|
+
try {
|
|
17
|
+
return controller ? (read(controller.status()) ?? null) : null;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
let value = now(), text = JSON.stringify(value);
|
|
24
|
+
const listeners = new Set();
|
|
25
|
+
let unheard = [];
|
|
26
|
+
/** Reads the field again, keeping the same object unless its value changed; says whether it did. */
|
|
27
|
+
const reread = () => {
|
|
28
|
+
const next = now(), json = JSON.stringify(next);
|
|
29
|
+
if (json === text)
|
|
30
|
+
return false;
|
|
31
|
+
value = next;
|
|
32
|
+
text = json;
|
|
33
|
+
return true;
|
|
34
|
+
};
|
|
35
|
+
const heard = () => { if (reread())
|
|
36
|
+
for (const l of [...listeners])
|
|
37
|
+
l(); };
|
|
38
|
+
return {
|
|
39
|
+
// With nobody listening, nothing says when it changes: it is read at each get.
|
|
40
|
+
get() { if (!listeners.size)
|
|
41
|
+
reread(); return value; },
|
|
42
|
+
subscribe(listener) {
|
|
43
|
+
if (!controller)
|
|
44
|
+
return () => { };
|
|
45
|
+
// The first listener: what changed while nobody listened is read now (useSyncExternalStore reads after subscribing).
|
|
46
|
+
if (!listeners.size) {
|
|
47
|
+
reread();
|
|
48
|
+
unheard = events.map(e => controller.on(e, heard));
|
|
49
|
+
}
|
|
50
|
+
listeners.add(listener);
|
|
51
|
+
return () => {
|
|
52
|
+
listeners.delete(listener);
|
|
53
|
+
if (listeners.size)
|
|
54
|
+
return;
|
|
55
|
+
for (const u of unheard)
|
|
56
|
+
u();
|
|
57
|
+
unheard = [];
|
|
58
|
+
};
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ export type * as series from "./contract/data-series.js";
|
|
|
22
22
|
export type * as weather from "./contract/data-weather.js";
|
|
23
23
|
export type * as tables from "./contract/data-snapshot.js";
|
|
24
24
|
export { cubicBezier, easingOf, staggerTimes } from "./easing.js";
|
|
25
|
+
export { onCommand } from "./commands.js";
|
|
25
26
|
/** A default's type, widened: a prop that defaults to "digital" may be any string. */
|
|
26
27
|
type Widen<T> = T extends string ? string : T extends number ? number : T extends boolean ? boolean : T;
|
|
27
28
|
type Params = Record<string, string | number | boolean>;
|
package/dist/index.js
CHANGED
|
@@ -27,6 +27,7 @@ import { cubicBezier, easingOf, staggerTimes } from "./easing.js";
|
|
|
27
27
|
import { atFrame } from "./frames.js";
|
|
28
28
|
export { ATTACH };
|
|
29
29
|
export { cubicBezier, easingOf, staggerTimes } from "./easing.js";
|
|
30
|
+
export { onCommand } from "./commands.js";
|
|
30
31
|
/** The contexts handed over through ATTACH, where no other script on the page can reach them. */
|
|
31
32
|
const given = new WeakMap();
|
|
32
33
|
// What acts or reads for an app is a function of this module, never a method or getter of the element: another app on
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/* @wyndlass/app-sdk's preview: player-core's app frame, compiled. MIT, (c) Northwynd Labs LLC. */
|
|
2
|
-
"use strict";(()=>{var vt=Object.defineProperty;var kt=(e,t,n)=>t in e?vt(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n;var m=(e,t,n)=>kt(e,typeof t!="symbol"?t+"":t,n);var Te={apiVersion:2,properties:[{name:"device",description:"What the device is: id, type, shell and versions, model, resolution and orientation. Fixed while the player runs."},{name:"mode",description:"Where player-core runs: player, preview (in the CMS) or designer. In preview and the designer, commands change nothing."},{name:"timeZone",description:"The player's time zone (IANA), from the host.",event:"time.changed"},{name:"locale",description:"The player's locale (BCP 47), from the host."},{name:"connection",description:"Whether the player is connected to Wyndlass.",event:"connection.changed"},{name:"state",description:"starting, playing, paused, stopped (the standby screen), idle (nothing to play) or error.",event:"state.changed"},{name:"held",description:"A remote pause or stop in effect, by whom and since when; absent when none.",event:"state.changed"},{name:"content",description:"The manifest playing, when it was issued and switched to; absent when there is none.",event:"content.updated"},{name:"now",description:"What each zone shows: the item, its blob and content type, when it started, and its duration and position when known.",event:"item.start"},{name:"audio",description:"The player's volume (0 to 100) and whether it is muted; only when the host can set them.",event:"audio.changed"},{name:"display",description:"The display's power and brightness; only when the host can report them.",event:"display.changed"},{name:"capabilities",description:"What the host can do: audio, display power, display brightness."}],events:[{name:"state.changed",description:"The player's state changed (playing, paused, stopped, idle, error), and who changed it."},{name:"content.updated",description:"A new manifest was switched to."},{name:"item.start",description:"An item started in a zone (source start)."},{name:"item.end",description:"An item ended in a zone (source end): completed, skipped, stopped, error, replaced by new content, or finished by its app."},{name:"item.error",description:"An item could not be shown."},{name:"connection.changed",description:"The player connected to Wyndlass or lost its connection."},{name:"audio.changed",description:"The volume or mute changed, and who changed it."},{name:"display.changed",description:"The display's power or brightness changed, and who changed it."},{name:"time.changed",description:"The clock was adjusted or the time zone changed."},{name:"app.command",description:"A command for apps on the screen (apps.sendCommand); an app reacts if it knows the name."},{name:"app.failed",description:"An app on the screen could not run, or misbehaved (WP-0100): its bundle did not load, it was not an app, it needs a newer player, it kept throwing and was stopped, or it was late or overran a clip."},{name:"app.held",description:"An app version was held back on this screen until the next manifest, after the page stopped answering twice in an hour while it was on screen (WP-0100)."}],commands:[{name:"player.status",access:"read",apps:"read",remote:!0,params:"player-api-params.schema.json#/$defs/empty",result:"player-api-results.schema.json#/$defs/status",description:"Everything about the player now: device, mode, time zone, locale, connection, state (and any hold), the content playing, what each zone shows, audio, display and what the host can do. Changes nothing.",idempotent:!0},{name:"player.events",access:"read",apps:"read",remote:!0,params:"player-api-params.schema.json#/$defs/events",result:"player-api-results.schema.json#/$defs/events",description:"The player's recent events (the last 200), oldest first, after a sequence number: state changes, content updates, item starts and ends, errors, connection, audio, display and time. Pass next as after to poll for more. Changes nothing.",idempotent:!0},{name:"time.now",access:"read",apps:"read",remote:!0,params:"player-api-params.schema.json#/$defs/empty",result:"player-api-results.schema.json#/$defs/timeNow",description:"The player's current time, its time zone and UTC offset, and whether its clock is synchronized. Changes nothing.",idempotent:!0},{name:"player.play",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/empty",result:"player-api-results.schema.json#/$defs/state",description:"Resumes or starts playback on the screen. From a remote caller it also ends a remote pause or stop.",idempotent:!0},{name:"player.pause",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/empty",result:"player-api-results.schema.json#/$defs/state",description:"Freezes the screen on what it shows now: the loop's timer and any media stop where they are. From a remote caller it holds until a remote player.play or a restart; apps cannot resume it, and new content arrives paused.",idempotent:!0},{name:"player.stop",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/empty",result:"player-api-results.schema.json#/$defs/state",description:"Stops playback and shows the standby screen (black, display on). From a remote caller it holds until a remote player.play or a restart; apps cannot restart it, and new content arrives stopped.",idempotent:!0},{name:"player.next",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/zone",result:"player-api-results.schema.json#/$defs/now",description:"Skips to the next item in a zone (main by default); the current item ends as skipped."},{name:"player.previous",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/zone",result:"player-api-results.schema.json#/$defs/now",description:"Goes back to the previous item in a zone (main by default); the current item ends as skipped."},{name:"player.playItem",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/playItem",result:"player-api-results.schema.json#/$defs/now",description:"Shows a given item of the content playing now in a zone (main by default), then continues the loop from it. Refused for an item the playing manifest does not have."},{name:"audio.setVolume",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/setVolume",result:"player-api-results.schema.json#/$defs/audio",description:"Sets the player's volume, 0 to 100. Only on players whose host can set it to a level.",needs:"audio.volume",idempotent:!0},{name:"audio.setMuted",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/setMuted",result:"player-api-results.schema.json#/$defs/audio",description:"Mutes or unmutes the player. Only on players whose host can.",needs:"audio",idempotent:!0},{name:"display.setPower",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/setPower",result:"player-api-results.schema.json#/$defs/display",description:"Turns the display on or to standby. Only on players whose host can. Standby blanks the screen until it is turned on.",needs:"display.power",idempotent:!0},{name:"display.setBrightness",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/setBrightness",result:"player-api-results.schema.json#/$defs/display",description:"Sets the display's brightness, 0 to 100. Only on players whose host can.",needs:"display.brightness",idempotent:!0},{name:"apps.sendCommand",access:"control",apps:"none",remote:!0,params:"player-api-params.schema.json#/$defs/sendCommand",result:"player-api-results.schema.json#/$defs/delivered",description:"Sends a named command, with an optional argument, to the apps on the screen; an app that knows the name reacts. Returns how many apps listen."},{name:"app.finish",access:"control",apps:"read",remote:!1,params:"player-api-params.schema.json#/$defs/empty",result:"player-api-results.schema.json#/$defs/none",description:"An app ends its own slot now: its item ends as finished and the loop moves on. Apps only; not a tool."}],host:[{name:"host.describe",kind:"request",description:"What the device is, its time zone, locale, connection and capabilities.",result:"player-api-host.schema.json#/$defs/describeResult"},{name:"host.time",kind:"request",description:"The host's time and time zone.",result:"player-api-common.schema.json#/$defs/timeNow"},{name:"host.getAudio",kind:"request",description:"The player's volume and mute.",result:"player-api-common.schema.json#/$defs/audio"},{name:"host.setAudio",kind:"request",description:"Sets the volume, the mute or both; answers the audio after.",result:"player-api-common.schema.json#/$defs/audio"},{name:"host.getDisplay",kind:"request",description:"The display's power and brightness.",result:"player-api-common.schema.json#/$defs/display"},{name:"host.setDisplay",kind:"request",description:"Sets the display's power, brightness or both; answers the display after.",result:"player-api-common.schema.json#/$defs/display"},{name:"host.adRequest",kind:"request",description:"Asks Wyndlass's ad broker for one slot of a break over the host's NATS connection (WP-0075); the result is the broker's ad-decision (ad-decision.schema.json). Only a host whose describe says ads is asked."},{name:"host.mediaPrepare",kind:"request",description:"Lays a video out behind the page and prepares it to its first frame; host.mediaState says when (ADR 0055)."},{name:"host.mediaShow",kind:"request",description:"Shows and hides prepared videos; replies once its layer has changed."},{name:"host.mediaControl",kind:"request",description:"Plays, pauses or seeks a video, or asks for its position."},{name:"host.mediaRelease",kind:"request",description:"Removes a video and frees its decoder."},{name:"host.reply",kind:"reply",description:"The host's answer to a request: result or error (unsupported, failed)."},{name:"host.connection",kind:"event",description:"The connection to Wyndlass changed."},{name:"host.timeChanged",kind:"event",description:"The clock was adjusted or the time zone changed."},{name:"host.audioChanged",kind:"event",description:"The volume or mute changed outside the player."},{name:"host.displayChanged",kind:"event",description:"The display changed outside the player."},{name:"host.mediaState",kind:"event",description:"A video was prepared, started, ended or failed, or its position was asked for."},{name:"host.presence",kind:"event",description:"What one of the host's sensors sees now: the presences in view, with random ids that never leave the page (WP-0105)."},{name:"host.sensing",kind:"event",description:"The site's sensing mode as the host applies it, and its sensors (WP-0105)."},{name:"host.serialPorts",kind:"event",description:"The host's sensor controller ports, and which are open (WP-0043)."},{name:"host.serialLine",kind:"event",description:"One line from a sensor controller, as it arrived (WP-0043)."},{name:"host.serialWrite",kind:"request",description:"Writes lines to a sensor controller, a gap apart; replies once written (WP-0043)."},{name:"host.bleWatch",kind:"request",description:"What the page listens for: beacons' iBeacon UUIDs, Eddystone namespaces and addresses; replies once the scan follows it. Sent only to a host that has sent host.bleState (WP-0044)."},{name:"host.bleAdverts",kind:"event",description:"Beacon frames that match the watch, at most four a second of each kind from each beacon (WP-0044)."},{name:"host.bleState",kind:"event",description:"The host's Bluetooth scanner: scanning, idle, off, refused, missing or failed; sent only by a host that has one (WP-0044)."},{name:"host.gpioPins",kind:"event",description:"The GPIO pins the host reads, with their values (WP-0044)."},{name:"host.gpioEdge",kind:"event",description:"A GPIO pin changed (WP-0044)."},{name:"host.mcp",kind:"relay",description:"An MCP message from ctl.dev.{deviceId}, for player-core to answer."},{name:"core.ready",kind:"core",description:"player-core is ready to answer host.mcp; until then the shell answers -32002 itself."},{name:"core.mcpReply",kind:"core",description:"player-core's answer to a host.mcp, for the shell to send back."},{name:"core.sensors",kind:"core",description:"The page's sensor controllers, channels and inputs, and the beacons it hears, whenever they change, for the heartbeat and sensors.recent (WP-0043, WP-0044)."}]};var Me=Symbol.for("wyndlass.app.attach");var De=Reflect.apply,de=Object.getOwnPropertyDescriptor,Q=Object.getPrototypeOf,b=globalThis,a=(e,t,n)=>De(e,t,n),k=(e,t)=>e?de(e,t)?.get:void 0,pe=(e,t)=>e?de(e,t)?.set:void 0,g=e=>b[e]?.prototype,He=(e,t,n)=>De(e,t,n);var qn=Date.now,zn=new TextEncoder,_n=new TextDecoder,Zn=TextEncoder.prototype.encode,Kn=TextDecoder.prototype.decode;var Vn=k(Q(Uint8Array.prototype),"length");var xt=Uint8Array,St=e=>new xt(e),Et=[];for(let e=0;e<256;e++)Et[e]=(e<16?"0":"")+e.toString(16);var Tt=Object.create(null);for(let e=0;e<64;e++)Tt["ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[e]]=e;var Fe=b.crypto?.subtle,le=Fe?Q(Fe):void 0,ae=b.crypto,Mt=ae?Q(ae).getRandomValues:void 0,Gn=le?.digest,Jn=le?.importKey,Yn=le?.sign;var Be=e=>a(Mt,ae,[St(e)]),Qn={setTimeout:b.setTimeout,clearTimeout:b.clearTimeout,setInterval:b.setInterval,clearInterval:b.clearInterval};var Xn=b.fetch;var q=g("MessagePort"),er=q&&q.postMessage,tr=pe(q,"onmessage"),nr=q&&q.start,ce=g("MessageEvent"),rr=k(ce,"data"),or=k(ce,"ports"),sr=k(ce,"source");var We=g("EventTarget"),ir=We?.addEventListener,ar=We?.removeEventListener;var dr=b.Worker,G=g("Worker"),pr=G&&G.postMessage,lr=pe(G,"onmessage"),cr=pe(G,"onerror");var Pe=b.Blob,J=b.URL,Re=J?.createObjectURL,Ft=J?.revokeObjectURL,ue=Pe&&Re?{make:(e,t)=>a(Re,J,[new Pe(e,{type:t})]),revoke:e=>a(Ft,J,[e])}:void 0,U=b.customElements,me=U?Q(U):void 0,Pt=me?.define,Rt=me?.get,fe=U&&me?{define:(e,t)=>a(Pt,U,[e,t]),get:e=>a(Rt,U,[e])}:void 0,$t=b.document,Y=g("Document"),Nt=Y?.createElement,ur=Y?.createElementNS,Ot=WeakSet.prototype.add,mr=WeakSet.prototype.has,Ct=new WeakSet;var Lt=(e,t)=>(a(Ot,e,[t]),t);function Ue(e){return Lt(Ct,a(Nt,$t,[e]))}var z=g("Node"),he=g("Element"),X=g("HTMLElement"),D=e=>k(z,e),$e=D("parentNode"),jt=D("firstChild"),It=D("lastChild"),Dt=D("nextSibling"),Ht=D("nodeType"),Bt=D("childNodes"),Wt=k(he,"localName"),Ut=z?.appendChild,qt=z?.insertBefore,zt=z?.removeChild,L=he,_t=L?.getAttribute,Zt=L?.getAttributeNames,Kt=L?.setAttribute,Vt=L?.removeAttribute,Gt=L?.matches,Jt=L?.attachShadow,Yt=L?.querySelectorAll,Qt=g("HTMLDialogElement")?.close,Xt=k(g("NodeList"),"length"),en=g("NodeList")?.item,ee=g("MutationRecord"),tn=k(ee,"type"),nn=k(ee,"target"),rn=k(ee,"addedNodes"),on=k(ee,"removedNodes"),Ne=b.MutationObserver,sn=g("MutationObserver")?.observe,qe=Y?de(Y,"adoptedStyleSheets"):void 0,Oe=qe?.get,Ce=qe?.set,an=b.requestAnimationFrame,Le=k(X,"style"),te=g("CSSStyleDeclaration"),dn=te?.getPropertyValue,pn=te?.getPropertyPriority,ln=te?.setProperty,je=X?.showPopover,Ie=X?.hidePopover,cn=k(g("Event"),"target"),un=g("Event")?.stopImmediatePropagation,V=e=>{let t=[];for(let n=0,r=a(Xt,e,[]);n<r;n++)t[n]=a(en,e,[n]);return t},ze=Ne&&z&&he&&X&&te?{parent:e=>a($e,e,[]),first:e=>a(jt,e,[]),last:e=>a(It,e,[]),next:e=>a(Dt,e,[]),isElement:e=>a(Ht,e,[])===1,name:e=>a(Wt,e,[]),children:e=>V(a(Bt,e,[])),append:(e,t)=>a(Ut,e,[t]),insert:(e,t,n)=>a(qt,e,[t,n]),remove:e=>{let t=a($e,e,[]);t&&a(zt,t,[e])},attribute:(e,t)=>a(_t,e,[t]),attributes:e=>a(Zt,e,[]),setAttribute:(e,t,n)=>a(Kt,e,[t,n]),removeAttribute:(e,t)=>a(Vt,e,[t]),matches:(e,t)=>a(Gt,e,[t]),all:(e,t)=>V(a(Yt,e,[t])),closeDialog:e=>a(Qt,e,[]),shadow:e=>a(Jt,e,[{mode:"closed"}]),style:(e,t)=>{let n=a(Le,e,[]);return[a(dn,n,[t]),a(pn,n,[t])]},setStyle:(e,t,n,r="")=>a(ln,a(Le,e,[]),[t,n,r]),observer:e=>new Ne(e),observe:(e,t,n)=>a(sn,e,[t,n]),record:e=>({type:a(tn,e,[]),target:a(nn,e,[]),added:V(a(rn,e,[])),removed:V(a(on,e,[]))}),adopted:Oe&&Ce?{count:e=>a(Oe,e,[]).length,clear:e=>a(Ce,e,[[]])}:void 0,frame:e=>a(an,globalThis,[e]),popover:je&&Ie?{show:e=>a(je,e,[]),hide:e=>a(Ie,e,[])}:void 0,target:e=>a(cn,e,[]),stop:e=>a(un,e,[])}:void 0;var P=class extends Error{constructor(n,r){super(`the app could not run (${n})`);m(this,"reason",n);m(this,"error",r)}};function j(e){try{let t=e instanceof Error?e.name:void 0;return typeof t=="string"&&/^[A-Za-z][A-Za-z0-9_]{0,63}$/.test(t)?t:void 0}catch{return}}function _(e){let t=e?.id;if(typeof t!="string"||t==="")return!0;for(let n=0;n<_e.length;n++)if(t[n]!==_e[n])return!1;return!0}var _e="com.wyndlass.";function mn(e=typeof navigator>"u"?"":navigator.userAgent){let t=/Chrom(?:e|ium)\/(\d+)/.exec(e);return t?Number(t[1]):void 0}function Ze(e,t=Te.apiVersion,n=mn()){return!(e?.playerApi!==void 0&&e.playerApi>t||e?.webView!==void 0&&n!==void 0&&e.webView>n)}function fn(e){let t;try{t=e.paging?.()}catch{return null}let n=t;return n&&Number.isInteger(n.count)&&n.count>1&&typeof n.ms=="number"&&n.ms>0?{count:n.count,ms:n.ms}:null}var ge=new Map;function hn(){let e=Be(8),t="";for(let n=0;n<8;n++)t+=gn[e[n]%26];return t}var gn="abcdefghijklmnopqrstuvwxyz",yn=0;function Ke(e,t,n){let r=ge.get(e);if(!r){let o=ue.make([t],"text/javascript");r=import(o).then(s=>bn(s,n),s=>{throw s instanceof P?s:new P("load",j(s))}).finally(()=>ue.revoke(o)),ge.set(e,r),r.catch(()=>ge.delete(e))}return r}function bn(e,t){let n=e.default;if(typeof n=="function"&&n.prototype instanceof HTMLElement){let r=n.prototype[Me];if(typeof r!="function")throw new P("shape");let o=class extends n{},s=`${t?.element??"wl-app"}-${hn()}${++yn}`;try{fe.define(s,o)}catch(c){throw new P("shape",j(c))}return{tag:s,shape:"class",attach:(c,A)=>{He(r,c,[A])}}}if(_(t)&&typeof e.element=="string"&&fe.get(e.element))return{tag:e.element,shape:"legacy"};throw new P("shape")}var wn=3,vn=6e4,ne=class{constructor(t,n){m(this,"element",t);m(this,"o",n);m(this,"faults",[]);m(this,"halted",!1)}get stopped(){return this.halted}has(t){try{return!this.halted&&typeof this.element[t]=="function"}catch{return!1}}update(t){this.halted||this.run(()=>this.element.update?.(t))}reset(){this.halted||this.run(()=>this.element.reset?.())}paging(){return this.halted?null:fn(this.element)}hook(t,n){if(this.halted)return Promise.resolve();let r;try{r=this.element[t]?.(n)}catch(o){return this.fault(o),Promise.resolve()}return Promise.resolve(r).then(()=>{},o=>this.fault(o))}stop(t){if(!this.halted){this.halted=!0;try{this.element.remove()}catch{}this.o.dispose?.(),this.o.onStop(t)}}dispose(){this.o.dispose?.()}run(t){try{t()}catch(n){this.fault(n)}}fault(t){let n=(this.o.now??Date.now)();this.faults=this.faults.filter(r=>n-r<vn),this.faults.push(n),this.faults.length>=wn&&this.stop(j(t))}},br=24*3600*1e3;var re="wyndlass.frame";function Ye(e,t){return!!e&&(e.apps?e.apps(t):!_(t))}var kn=new Set(["load","shape","unsupported","error","late","overran"]),Ve=50,Ge=250,ye=class{constructor(t,n,r,o){m(this,"frame",t);m(this,"port",n);m(this,"o",o);m(this,"halted",!1);m(this,"pages");m(this,"hooks");m(this,"pending",new Map);m(this,"next",0);m(this,"told");m(this,"last");m(this,"quiet");this.hooks=new Set(r.hooks),this.pages=r.paging}get stopped(){return this.halted}has(t){return!this.halted&&this.hooks.has(t)}paging(){return this.halted?null:this.pages}update(t){if(this.halted)return;let n=performance.now(),r=this.last;this.last={t,at:n};let o=this.told;if(o&&(o.running?Math.abs(t-(o.t+(n-o.at)))<=Ve:t===o.t)){o.running&&this.watch();return}let s=!!r&&n-r.at<Ge&&t>r.t&&Math.abs(t-r.t-(n-r.at))<=Ve;this.told={t,at:n,running:s},this.post({type:"clock",t,running:s}),s&&this.watch()}reset(){this.halted||this.post({type:"reset"})}snapshots(t){this.halted||this.post({type:"snapshots",snapshots:t})}hook(t,n){if(this.halted)return Promise.resolve();let r=++this.next;return new Promise(o=>{this.pending.set(r,o),this.post({type:"hook",id:r,kind:t,options:n})})}stop(t){this.halted||(this.halted=!0,this.frame.remove(),this.dispose(),this.o.onStop(t))}dispose(){this.o.dispose?.(),clearTimeout(this.quiet),this.port.close();for(let t of this.pending.values())t();this.pending.clear()}heard(t){t.type==="paging"?this.pages=Xe(t.paging):t.type==="hooked"&&typeof t.id=="number"?(this.pending.get(t.id)?.(),this.pending.delete(t.id)):t.type==="stopped"&&this.stop(et(t.error))}post(t){try{this.port.postMessage(t)}catch{}}watch(){clearTimeout(this.quiet),this.quiet=setTimeout(()=>{!this.told?.running||!this.last||this.halted||(this.told={...this.last,running:!1},this.post({type:"clock",t:this.last.t,running:!1}))},Ge)}};async function Qe(e,t,n,r,o,s,c,A,y,i){let l=e.appInfo??n.info,u;try{u=new URL(y.frames).origin}catch{return i("unsupported")}let d=document.createElement("iframe");d.setAttribute("sandbox","allow-scripts"),d.setAttribute("referrerpolicy","no-referrer"),d.title=l?.id??"app",d.tabIndex=-1,d.dataset.frame=l?.id??"",d.style.cssText="display:block;width:100%;height:100%;border:0;pointer-events:none;background:transparent;color-scheme:normal";let w=new Promise(T=>{d.addEventListener("load",()=>T(!0),{once:!0}),setTimeout(()=>T(!1),s)});d.src=`${u}/`,t.append(d);let E=(T,v)=>(d.remove(),i(T,v));if(!await w||!d.contentWindow)return E("load");let{port1:x,port2:Z}=new MessageChannel,O={type:"init",element:{id:e.id,type:"app",app:e.app,props:e.props??{},...e.binding?{binding:e.binding}:{},...e.appInfo?{appInfo:e.appInfo}:{}},bundle:{hash:n.hash,bytes:n.bytes.slice(),...n.info?{info:n.info}:{}},...r.place?{place:r.place}:{},screen:r.screen??null,timeZone:r.timeZone,locale:r.locale,preview:!!r.preview,clock:{t:o.now(),running:!1,startedAt:o.startedAt()},fonts:Sn(e.props??{},y),snapshots:Je(e,r),deadlineMs:s};return new Promise(T=>{let v,$=!1,M=B=>{$||($=!0,clearTimeout(C),T(B))},C=setTimeout(()=>{v?(M({app:v,late:!0}),v.update(o.now())):(x.close(),M(E("load")))},s+1e3),S=!1;x.onmessage=B=>{let h=B.data;if(!(!h||typeof h!="object")){if(h.type==="ask"){S&&An(h,r,x);return}if(v){h.type==="ready"?(M({app:v,late:h.late===!0}),v.update(o.now())):v.heard(h);return}if(h.type==="hello"&&!S)S=!0,x.postMessage(O,[O.bundle.bytes.buffer]);else if(h.type==="made"&&Array.isArray(h.hooks)){let W=tt(e),K,p=new ye(d,x,{type:"made",hooks:h.hooks.filter(f=>f==="enter"||f==="exit"),paging:Xe(h.paging)},{onStop:A,dispose:()=>K?.()});K=r.onSnapshots?.(f=>{f.some(F=>W.has(F))&&p.snapshots(Je(e,r))}),v=p,c(v)}else h.type==="failed"&&(x.close(),M(E(kn.has(h.reason)?h.reason:"error",et(h.error))))}},d.contentWindow.postMessage({type:re},"*",[Z])})}async function An(e,t,n){if(typeof e.id!="number")return;let r=(o,s=[])=>{try{n.postMessage(o,s)}catch{}};try{if(!t.data)throw new Error("no data service");if(e.what==="data"&&typeof e.source=="string")r({type:"answer",id:e.id,value:await t.data.get(e.source,xn(e.params))});else if(e.what==="image"&&typeof e.path=="string"){let o=await t.data.imageBlob(e.path),s=new Uint8Array(await o.arrayBuffer());r({type:"answer",id:e.id,value:{bytes:s,type:o.type}},[s.buffer])}else throw new TypeError("not a question")}catch(o){r({type:"answer",id:e.id,error:j(o)??"Error"})}}function xn(e){let t={};if(e&&typeof e=="object")for(let[n,r]of Object.entries(e))(typeof r=="string"||typeof r=="number"||typeof r=="boolean")&&(t[n]=r);return t}function Xe(e){let t=e;return t&&Number.isInteger(t.count)&&t.count>1&&typeof t.ms=="number"&&t.ms>0?{count:t.count,ms:t.ms}:null}function et(e){return typeof e=="string"&&/^[A-Za-z][A-Za-z0-9_]{0,63}$/.test(e)?e:void 0}function Sn(e,t){let n=new Set;for(let o of Object.values(e)){let s=o?.family;o&&typeof o=="object"&&typeof s=="string"&&n.add(s)}let r=[];for(let o of t.fonts??[]){let s=n.has(o.family)?t.font?.(o.blob):void 0;s&&r.push({family:o.family,weight:o.weight,style:o.style,blob:o.blob,bytes:s.slice()})}return r}function tt(e){let t=new Set;e.binding?.table&&t.add(e.binding.table);for(let n of Object.values(e.props??{}))typeof n=="string"&&/^tbl_[0-9a-z]{26}$/.test(n)&&t.add(n);return t}function Je(e,t){let n={};for(let r of tt(e)){let o=t.snapshot?.(r);o&&(n[r]=o)}return n}function nt(e){let t=ze?.setStyle??((n,r,o,s)=>n.style.setProperty(r,o,s));t(e,"contain","strict","important"),t(e,"overflow","clip","important")}var En=["name","type","tags","location","site","district","region","organization","path","timeZone"],Tn=["text","date","currency","truncate"],rt={site:"SITE",district:"DISTRICT",region:"REGION",organization:"ORGANIZATION"};function oe(e,t,n,r){if(!e)return n==="timeZone"&&t?t:void 0;if(n==="fields")return r&&e.fields?.[r]||void 0;let o=e.path??[];switch(n){case"name":return e.name||void 0;case"type":return e.type||void 0;case"tags":return e.tags?.length?e.tags.join(", "):void 0;case"location":return o.at(-1)?.name;case"path":return o.length?o.map(s=>s.name).join(" \u203A "):void 0;case"timeZone":return t||void 0;default:return rt[n]?[...o].reverse().find(s=>s.kind===rt[n])?.name:void 0}}function Mn(e,t){if(e==null||e==="")return;let n=String(e);if(t==="currency"){let r=Number(e);return Number.isFinite(r)?r.toFixed(2):n}if(t==="date"){let r=Date.parse(n);return Number.isNaN(r)?n:new Date(r).toISOString().slice(0,10)}return t==="truncate"&&n.length>40?n.slice(0,39)+"\u2026":n}function ot(e,t){return(e.source==="screen"?oe(t.screen,t.timeZone,e.field,e.key):Mn(t.tables?.[e.table]?.rows?.[e.row??0]?.[e.column],e.format))??e.fallback??""}function st(e){let t=/^\s*(?:screen\.(fields\.[a-z][A-Za-z0-9]{0,31}|[A-Za-z]+)|table\.([A-Za-z0-9_-]+)\[(\d+)\](?:\.([A-Za-z0-9_]+)|\["((?:[^"\\]|\\.)*)"\]))\s*((?:\|\s*(?:[a-z]+|"(?:[^"\\]|\\.)*")\s*)*)$/.exec(e);if(!t)return null;let n=[...t[6].matchAll(/\|\s*([a-z]+|"((?:[^"\\]|\\.)*)")/g)],r=n.find(s=>s[1].startsWith('"'))?.[2]?.replace(/\\(.)/g,"$1"),o=n.find(s=>!s[1].startsWith('"'))?.[1];return t[1]!==void 0?o?null:t[1].startsWith("fields.")?{source:"screen",field:"fields",key:t[1].slice(7),...r!==void 0?{fallback:r}:{}}:En.includes(t[1])?{source:"screen",field:t[1],...r!==void 0?{fallback:r}:{}}:null:o&&!Tn.includes(o)?null:{source:"table",table:t[2],row:Number(t[3]),column:t[4]??t[5].replace(/\\(.)/g,"$1"),...o?{format:o}:{},...r!==void 0?{fallback:r}:{}}}function it(e,t){return e.replace(/\{{2}([^{}]*)\}{2}/g,(n,r)=>{let o=st(r);return o?ot(o,t):n})}function at(e){return[...e.matchAll(/\{{2}([^{}]*)\}{2}/g)].some(t=>st(t[1])!==null)}function be(e,t){return Object.fromEntries(Object.entries(e).map(([n,r])=>[n,typeof r=="string"?it(r,t):r]))}var we=e=>e==null||e==="";function pt(e,t,n,r,o,s){return Fn(e,t,n,r,{now:o,timeZone:s})}function Fn(e,t,n,r,o){if(n===null&&(t==="eq"||t==="ne"))return t==="eq"?we(e):!we(e);if(we(e)||n===null||n===void 0)return!1;switch(r){case"number":case"currency":{let s=Number(e),c=Number(n);return typeof n!="boolean"&&n!==""&&Number.isFinite(s)&&Number.isFinite(c)&&ve(t,s-c)}case"boolean":{let s=n===!0||n==="true"?!0:n===!1||n==="false"?!1:void 0;return s!==void 0&&(t==="eq"?e===s:t==="ne"?e!==s:!1)}case"date":{let s=n==="today"?Pn(o.now,o.timeZone).date:String(n),c=String(e).slice(0,10);return/^\d{4}-\d{2}-\d{2}$/.test(s)&&ve(t,c<s?-1:c>s?1:0)}case"datetime":{let s=Date.parse(String(e)),c=n==="now"?o.now:Date.parse(String(n));return!Number.isNaN(s)&&!Number.isNaN(c)&&ve(t,s-c)}default:{let s=String(e).toLowerCase(),c=String(n).toLowerCase();return t==="eq"?s===c:t==="ne"?s!==c:t==="contains"?s.includes(c):!1}}}function ve(e,t){switch(e){case"eq":return t===0;case"ne":return t!==0;case"lt":return t<0;case"lte":return t<=0;case"gt":return t>0;case"gte":return t>=0;default:return!1}}var dt=new Map;function Pn(e,t){let n=dt.get(t);n||(n=new Intl.DateTimeFormat("en-US",{timeZone:t,hourCycle:"h23",year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",weekday:"short"}),dt.set(t,n));let r=Object.fromEntries(n.formatToParts(new Date(e)).map(o=>[o.type,o.value]));return{date:`${r.year}-${r.month}-${r.day}`,minute:Number(r.hour)%24*60+Number(r.minute),day:String(r.weekday).slice(0,3).toLowerCase()}}var lt=e=>!!(e?.filter?.length||e?.sort||e?.limit),Rn=e=>e==null||e==="";function $n(e,t){if(!Rn(e))switch(t){case"number":case"currency":{let n=Number(e);return Number.isFinite(n)?n:void 0}case"datetime":{let n=Date.parse(String(e));return Number.isNaN(n)?void 0:n}case"boolean":return e===!0||String(e).toLowerCase()==="true"?1:0;case"date":return String(e).slice(0,10);default:return String(e).toLowerCase()}}function Nn(e,t){let n=Array.from(e),r=Array.from(t);for(let o=0;o<n.length&&o<r.length;o++){let s=n[o].codePointAt(0)-r[o].codePointAt(0);if(s!==0)return s}return n.length-r.length}function On(e,t,n,r,o){let s=e,c=Array.isArray(s?.rows)?s.rows:[],A=Array.isArray(s?.columns)?s.columns:void 0,y=i=>A?.find(l=>l.key===i)?.type??"text";for(let i of t.filter??[]){if(A&&!A.some(d=>d.key===i.column))return[];let l=i.value;if(l!==null&&typeof l=="object"){let d=l;if(l=oe(n,r,d.screen,d.key),l===void 0)return[]}let u=y(i.column);c=c.filter(d=>pt(d[i.column],i.op,l,u,o,r))}if(t.sort){let{column:i}=t.sort,l=y(i),u=t.sort.order==="desc"?-1:1;c=c.map((d,w)=>({row:d,i:w,key:$n(d[i],l)})).sort((d,w)=>{if(d.key===void 0||w.key===void 0)return d.key===void 0&&w.key===void 0?d.i-w.i:d.key===void 0?1:-1;let E=typeof d.key=="number"&&typeof w.key=="number"?d.key-w.key:Nn(String(d.key),String(w.key));return E===0?d.i-w.i:E<0?-u:u}).map(d=>d.row)}return t.limit?c.slice(0,t.limit):c}function ct(e,t,n,r,o){return!e||typeof e!="object"?e:{...e,rows:On(e,t,n,r,o)}}function ut(e){return{screen:e?.screen??null,timeZone:e?.timeZone,tables:new Proxy({},{get:(t,n)=>typeof n=="string"?e?.snapshot?.(n):void 0})}}async function ft(e,t,n,r,o,s,c,A=()=>{},y){let i=e.appInfo??n.info;nt(t),i?.id&&(t.dataset.app=i.id);let l=(p,f)=>(t.dataset.appFailed=p,{failed:p,...f?{error:f}:{}});if(!Ze(i?.requires))return l("unsupported");let u=r.appHost;if(u&&!u.custom&&!_(i))return l("unsupported");if(Ye(y,i))return Qe(e,t,n,r,o,s,c,A,y,l);let d=e.app;if(u?.held?.(d))return t.dataset.appHeld="",{held:!0};await u?.mounting?.(d,i?.id);let w=!1,E=()=>{w||(w=!0,u?.unmounted?.(d))},x;try{x=await Ke(n.hash,n.bytes,i)}catch(p){return E(),p instanceof P?l(p.reason,p.error):l("load")}let Z=x.tag,O=i?.id?r.controllerFor?.({id:i.id,instance:e.id,capability:i.player??"read"}):void 0,T,v=new Promise(p=>{T=p}),$,M=new Set,C={app:i?.id??e.app.id,element:Z,props:be(e.props??{},ut(r)),place:r.place,screen:r.screen??null,timeZone:r.timeZone??Intl.DateTimeFormat().resolvedOptions().timeZone,locale:r.locale??navigator.language,preview:!!r.preview,now:o.now,startedAt:o.startedAt,ready:()=>T(),data:(p,f)=>r.data?r.data.get(p,f):Promise.reject(new Error("no data service")),kept:(p,f)=>r.data?.kept?.(p,f),image:p=>r.data?r.data.image(p):Promise.reject(new Error("no data service")),snapshot:p=>{let f=r.snapshot?.(p)??null;if(!f||e.binding?.table!==p||!lt(e.binding))return f;let F=ct(f,e.binding,r.screen,C.timeZone,o.now());return $?.from===f&&$.rows.length===F.rows.length&&$.rows.every((bt,wt)=>bt===F.rows[wt])?$.snapshot:($={from:f,rows:F.rows,snapshot:F},F)},...e.binding?{binding:e.binding}:{},...r.onSnapshots?{onSnapshot:p=>(M.add(p),()=>{M.delete(p)})}:{},...e.sensors?.includes("impressions")&&r.impressions?.measuring?{impressions:{now:()=>r.impressions.now(),on:p=>r.impressions.on(p)}}:{},...O?{controller:O}:{}},S;try{S=Ue(Z),x.shape==="class"?x.attach(S,C):S.wyndlass=C}catch(p){return O?.dispose(),E(),l("error",j(p))}S.style.cssText="display:block;width:100%;height:100%",u?.contained?.(t,S);let B=Object.values(e.props??{}).some(p=>typeof p=="string"&&at(p)),h=r.onSnapshots?.(p=>{B&&(C.props=be(e.props??{},ut(r)));for(let f of p)for(let F of[...M])try{F(f)}catch{}}),W=new ne(S,{onStop:A,dispose:()=>{h?.(),M.clear(),O?.dispose(),E()}});t.append(S),c(W);let K=await Promise.race([v.then(()=>!1),new Promise(p=>setTimeout(()=>p(!0),s))]);return W.update(C.now()),{app:W,late:K}}var mt=new Set;async function ht(e,t){for(let n of e){let r=`${n.family}/${n.weight??400}/${n.style??"normal"}/${n.blob}`,o=t(n.blob);if(!(mt.has(r)||!o||typeof FontFace>"u"))try{let s=new FontFace(n.family,o,{weight:String(n.weight??400),style:n.style??"normal"});document.fonts.add(await s.load()),mt.add(r)}catch{}}}var Cn=Object.freeze,Ln=Object.values,gt=Reflect.apply;function se(e){if(e===null||typeof e!="object")return e;for(let t of gt(Ln,Object,[e]))se(t);return gt(Cn,Object,[e])}var ie,R,I={t:0,at:0,running:!1},H={},ke=new Set,Ae="null",xe=new Map,jn=0,N=e=>ie.postMessage(e),Se=()=>I.running?I.t+(performance.now()-I.at):I.t;addEventListener("message",e=>{ie||e.source!==parent||e.data?.type!==re||!e.ports[0]||(ie=e.ports[0],ie.onmessage=t=>In(t.data),N({type:"hello"}))});function In(e){switch(e.type){case"init":Dn(e);break;case"clock":I={t:e.t,at:performance.now(),running:e.running},R?.update(Se()),Ee();break;case"hook":{let t=e.id;(R?R.hook(e.kind,e.options):Promise.resolve()).then(()=>N({type:"hooked",id:t}));break}case"reset":R?.reset();break;case"snapshots":{let t=H;H=se(e.snapshots);let n=[...new Set([...Object.keys(t),...Object.keys(H)])].filter(r=>JSON.stringify(t[r])!==JSON.stringify(H[r]));if(n.length)for(let r of[...ke])r(n);break}case"answer":{let t=xe.get(e.id);xe.delete(e.id),e.error!==void 0?t?.reject(Object.assign(new Error("the host could not answer"),{name:e.error})):t?.resolve(e.value);break}}}function yt(e){let t=++jn;return new Promise((n,r)=>{xe.set(t,{resolve:n,reject:r}),N({type:"ask",id:t,...e})})}function Ee(){let e=R?.paging()??null,t=JSON.stringify(e);t!==Ae&&(Ae=t,N({type:"paging",paging:e}))}async function Dn(e){if(R)return;I={t:e.clock.t,at:performance.now(),running:e.clock.running},H=se(e.snapshots);let t=new Map,n=(i,l)=>`${i}?${JSON.stringify(Object.keys(l).sort().map(u=>[u,l[u]]))}`,o={data:{get:async(i,l)=>{let u=await yt({what:"data",source:i,params:l});return t.set(n(i,l),u),u},kept:(i,l)=>t.get(n(i,l)),image:async i=>{let{bytes:l,type:u}=await yt({what:"image",path:i});return URL.createObjectURL(new Blob([l],{type:u}))}},place:e.place,screen:e.screen,timeZone:e.timeZone,locale:e.locale,preview:e.preview,snapshot:i=>H[i]??null,onSnapshots:i=>(ke.add(i),()=>{ke.delete(i)})},s={now:Se,startedAt:()=>e.clock.startedAt},c=new Map(e.fonts.map(i=>[i.blob,i.bytes]));await ht(e.fonts,i=>c.get(i));let A=document.getElementById("app"),y=await ft(e.element,A,e.bundle,o,s,e.deadlineMs,i=>{R=i,Ae=JSON.stringify(i.paging()),N({type:"made",hooks:["enter","exit"].filter(l=>i.has(l)),paging:i.paging()})},i=>N({type:"stopped",...i?{error:i}:{}}));"failed"in y?N({type:"failed",reason:y.failed,...y.error?{error:y.error}:{}}):"app"in y&&(N({type:"ready",late:y.late}),Ee(),Hn())}function Hn(){let e=0,t=n=>{R?.stopped||(I.running&&R?.update(Se()),n-e>1e3&&(e=n,Ee()),requestAnimationFrame(t))};requestAnimationFrame(t)}})();
|
|
2
|
+
"use strict";(()=>{var vt=Object.defineProperty;var kt=(e,t,n)=>t in e?vt(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n;var m=(e,t,n)=>kt(e,typeof t!="symbol"?t+"":t,n);var Te={apiVersion:2,properties:[{name:"device",description:"What the device is: id, type, shell and versions, model, resolution and orientation. Fixed while the player runs."},{name:"mode",description:"Where player-core runs: player, preview (in the CMS) or designer. In preview and the designer, commands change nothing."},{name:"timeZone",description:"The player's time zone (IANA), from the host.",event:"time.changed"},{name:"locale",description:"The player's locale (BCP 47), from the host."},{name:"connection",description:"Whether the player is connected to Wyndlass.",event:"connection.changed"},{name:"state",description:"starting, playing, paused, stopped (the standby screen), idle (nothing to play) or error.",event:"state.changed"},{name:"held",description:"A remote pause or stop in effect, by whom and since when; absent when none.",event:"state.changed"},{name:"content",description:"The manifest playing, when it was issued and switched to; absent when there is none.",event:"content.updated"},{name:"now",description:"What each zone shows: the item, its blob and content type, when it started, and its duration and position when known.",event:"item.start"},{name:"audio",description:"The player's volume (0 to 100) and whether it is muted; only when the host can set them.",event:"audio.changed"},{name:"display",description:"The display's power and brightness; only when the host can report them.",event:"display.changed"},{name:"emergency",description:"Emergency messages at the screen (WP-0150): what shows (none, a banner or a takeover), and of each message in force its source, severity, presentation, event, categories and whether it is a test. Never its words. Absent until the shell has said what it holds."},{name:"capabilities",description:"What the host can do: audio, display power, display brightness."}],events:[{name:"state.changed",description:"The player's state changed (playing, paused, stopped, idle, error), and who changed it."},{name:"content.updated",description:"A new manifest was switched to."},{name:"item.start",description:"An item started in a zone (source start)."},{name:"item.end",description:"An item ended in a zone (source end): completed, skipped, stopped, error, replaced by new content, or finished by its app."},{name:"item.error",description:"An item could not be shown."},{name:"connection.changed",description:"The player connected to Wyndlass or lost its connection."},{name:"audio.changed",description:"The volume or mute changed, and who changed it."},{name:"display.changed",description:"The display's power or brightness changed, and who changed it."},{name:"time.changed",description:"The clock was adjusted or the time zone changed."},{name:"emergency.changed",description:"What the screen shows of emergency messages, or the messages in force, changed."},{name:"app.command",description:"A command for apps on the screen (apps.sendCommand); an app reacts if it knows the name."},{name:"app.failed",description:"An app on the screen could not run, or misbehaved (WP-0100): its bundle did not load, it was not an app, it needs a newer player, it kept throwing and was stopped, or it was late or overran a clip."},{name:"app.held",description:"An app version was held back on this screen until the next manifest, after the page stopped answering twice in an hour while it was on screen (WP-0100)."}],commands:[{name:"player.status",access:"read",apps:"read",remote:!0,params:"player-api-params.schema.json#/$defs/empty",result:"player-api-results.schema.json#/$defs/status",description:"Everything about the player now: device, mode, time zone, locale, connection, state (and any hold), the content playing, what each zone shows, audio, display and what the host can do. Changes nothing.",idempotent:!0},{name:"player.events",access:"read",apps:"read",remote:!0,params:"player-api-params.schema.json#/$defs/events",result:"player-api-results.schema.json#/$defs/events",description:"The player's recent events (the last 200), oldest first, after a sequence number: state changes, content updates, item starts and ends, errors, connection, audio, display and time. Pass next as after to poll for more. Changes nothing.",idempotent:!0},{name:"time.now",access:"read",apps:"read",remote:!0,params:"player-api-params.schema.json#/$defs/empty",result:"player-api-results.schema.json#/$defs/timeNow",description:"The player's current time, its time zone and UTC offset, and whether its clock is synchronized. Changes nothing.",idempotent:!0},{name:"player.play",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/empty",result:"player-api-results.schema.json#/$defs/state",description:"Resumes or starts playback on the screen. From a remote caller it also ends a remote pause or stop.",idempotent:!0},{name:"player.pause",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/empty",result:"player-api-results.schema.json#/$defs/state",description:"Freezes the screen on what it shows now: the loop's timer and any media stop where they are. From a remote caller it holds until a remote player.play or a restart; apps cannot resume it, and new content arrives paused.",idempotent:!0},{name:"player.stop",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/empty",result:"player-api-results.schema.json#/$defs/state",description:"Stops playback and shows the standby screen (black, display on). From a remote caller it holds until a remote player.play or a restart; apps cannot restart it, and new content arrives stopped.",idempotent:!0},{name:"player.next",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/zone",result:"player-api-results.schema.json#/$defs/now",description:"Skips to the next item in a zone (main by default); the current item ends as skipped."},{name:"player.previous",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/zone",result:"player-api-results.schema.json#/$defs/now",description:"Goes back to the previous item in a zone (main by default); the current item ends as skipped."},{name:"player.playItem",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/playItem",result:"player-api-results.schema.json#/$defs/now",description:"Shows a given item of the content playing now in a zone (main by default), then continues the loop from it. Refused for an item the playing manifest does not have."},{name:"audio.setVolume",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/setVolume",result:"player-api-results.schema.json#/$defs/audio",description:"Sets the player's volume, 0 to 100. Only on players whose host can set it to a level.",needs:"audio.volume",idempotent:!0},{name:"audio.setMuted",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/setMuted",result:"player-api-results.schema.json#/$defs/audio",description:"Mutes or unmutes the player. Only on players whose host can.",needs:"audio",idempotent:!0},{name:"display.setPower",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/setPower",result:"player-api-results.schema.json#/$defs/display",description:"Turns the display on or to standby. Only on players whose host can. Standby blanks the screen until it is turned on.",needs:"display.power",idempotent:!0},{name:"display.setBrightness",access:"control",apps:"control",remote:!0,params:"player-api-params.schema.json#/$defs/setBrightness",result:"player-api-results.schema.json#/$defs/display",description:"Sets the display's brightness, 0 to 100. Only on players whose host can.",needs:"display.brightness",idempotent:!0},{name:"apps.sendCommand",access:"control",apps:"none",remote:!0,params:"player-api-params.schema.json#/$defs/sendCommand",result:"player-api-results.schema.json#/$defs/delivered",description:"Sends a named command, with an optional argument, to the apps on the screen; an app that knows the name reacts. Returns how many apps listen."},{name:"app.finish",access:"control",apps:"read",remote:!1,params:"player-api-params.schema.json#/$defs/empty",result:"player-api-results.schema.json#/$defs/none",description:"An app ends its own slot now: its item ends as finished and the loop moves on. Apps only; not a tool."}],host:[{name:"host.describe",kind:"request",description:"What the device is, its time zone, locale, connection and capabilities.",result:"player-api-host.schema.json#/$defs/describeResult"},{name:"host.time",kind:"request",description:"The host's time and time zone.",result:"player-api-common.schema.json#/$defs/timeNow"},{name:"host.getAudio",kind:"request",description:"The player's volume and mute.",result:"player-api-common.schema.json#/$defs/audio"},{name:"host.setAudio",kind:"request",description:"Sets the volume, the mute or both; answers the audio after.",result:"player-api-common.schema.json#/$defs/audio"},{name:"host.getDisplay",kind:"request",description:"The display's power and brightness.",result:"player-api-common.schema.json#/$defs/display"},{name:"host.setDisplay",kind:"request",description:"Sets the display's power, brightness or both; answers the display after.",result:"player-api-common.schema.json#/$defs/display"},{name:"host.adRequest",kind:"request",description:"Asks Wyndlass's ad broker for one slot of a break over the host's NATS connection (WP-0075); the result is the broker's ad-decision (ad-decision.schema.json). Only a host whose describe says ads is asked."},{name:"host.mediaPrepare",kind:"request",description:"Lays a video out behind the page and prepares it to its first frame; host.mediaState says when (ADR 0055)."},{name:"host.mediaShow",kind:"request",description:"Shows and hides prepared videos; replies once its layer has changed."},{name:"host.mediaControl",kind:"request",description:"Plays, pauses or seeks a video, or asks for its position."},{name:"host.mediaRelease",kind:"request",description:"Removes a video and frees its decoder."},{name:"host.reply",kind:"reply",description:"The host's answer to a request: result or error (unsupported, failed)."},{name:"host.connection",kind:"event",description:"The connection to Wyndlass changed."},{name:"host.timeChanged",kind:"event",description:"The clock was adjusted or the time zone changed."},{name:"host.audioChanged",kind:"event",description:"The volume or mute changed outside the player."},{name:"host.displayChanged",kind:"event",description:"The display changed outside the player."},{name:"host.mediaState",kind:"event",description:"A video was prepared, started, ended or failed, or its position was asked for."},{name:"host.presence",kind:"event",description:"What one of the host's sensors sees now: the presences in view, with random ids that never leave the page (WP-0105)."},{name:"host.sensing",kind:"event",description:"The site's sensing mode as the host applies it, and its sensors (WP-0105)."},{name:"host.serialPorts",kind:"event",description:"The host's sensor controller ports, and which are open (WP-0043)."},{name:"host.serialLine",kind:"event",description:"One line from a sensor controller, as it arrived (WP-0043)."},{name:"host.serialWrite",kind:"request",description:"Writes lines to a sensor controller, a gap apart; replies once written (WP-0043)."},{name:"host.bleWatch",kind:"request",description:"What the page listens for: beacons' iBeacon UUIDs, Eddystone namespaces and addresses; replies once the scan follows it. Sent only to a host that has sent host.bleState (WP-0044)."},{name:"host.bleAdverts",kind:"event",description:"Beacon frames that match the watch, at most four a second of each kind from each beacon (WP-0044)."},{name:"host.bleState",kind:"event",description:"The host's Bluetooth scanner: scanning, idle, off, refused, missing or failed; sent only by a host that has one (WP-0044)."},{name:"host.gpioPins",kind:"event",description:"The GPIO pins the host reads, with their values (WP-0044)."},{name:"host.gpioEdge",kind:"event",description:"A GPIO pin changed (WP-0044)."},{name:"host.mcp",kind:"relay",description:"An MCP message from ctl.dev.{deviceId}, for player-core to answer."},{name:"core.ready",kind:"core",description:"player-core is ready to answer host.mcp; until then the shell answers -32002 itself."},{name:"core.mcpReply",kind:"core",description:"player-core's answer to a host.mcp, for the shell to send back."},{name:"core.sensors",kind:"core",description:"The page's sensor controllers, channels and inputs, and the beacons it hears, whenever they change, for the heartbeat and sensors.recent (WP-0043, WP-0044)."}]};var Me=Symbol.for("wyndlass.app.attach");var De=Reflect.apply,de=Object.getOwnPropertyDescriptor,Q=Object.getPrototypeOf,b=globalThis,a=(e,t,n)=>De(e,t,n),k=(e,t)=>e?de(e,t)?.get:void 0,pe=(e,t)=>e?de(e,t)?.set:void 0,g=e=>b[e]?.prototype,He=(e,t,n)=>De(e,t,n);var qn=Date.now,zn=new TextEncoder,_n=new TextDecoder,Zn=TextEncoder.prototype.encode,Kn=TextDecoder.prototype.decode;var Vn=k(Q(Uint8Array.prototype),"length");var xt=Uint8Array,St=e=>new xt(e),Et=[];for(let e=0;e<256;e++)Et[e]=(e<16?"0":"")+e.toString(16);var Tt=Object.create(null);for(let e=0;e<64;e++)Tt["ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[e]]=e;var Pe=b.crypto?.subtle,le=Pe?Q(Pe):void 0,ae=b.crypto,Mt=ae?Q(ae).getRandomValues:void 0,Gn=le?.digest,Jn=le?.importKey,Yn=le?.sign;var Be=e=>a(Mt,ae,[St(e)]),Qn={setTimeout:b.setTimeout,clearTimeout:b.clearTimeout,setInterval:b.setInterval,clearInterval:b.clearInterval};var Xn=b.fetch;var q=g("MessagePort"),er=q&&q.postMessage,tr=pe(q,"onmessage"),nr=q&&q.start,ce=g("MessageEvent"),rr=k(ce,"data"),sr=k(ce,"ports"),or=k(ce,"source");var We=g("EventTarget"),ir=We?.addEventListener,ar=We?.removeEventListener;var dr=b.Worker,G=g("Worker"),pr=G&&G.postMessage,lr=pe(G,"onmessage"),cr=pe(G,"onerror");var Fe=b.Blob,J=b.URL,Re=J?.createObjectURL,Pt=J?.revokeObjectURL,ue=Fe&&Re?{make:(e,t)=>a(Re,J,[new Fe(e,{type:t})]),revoke:e=>a(Pt,J,[e])}:void 0,U=b.customElements,me=U?Q(U):void 0,Ft=me?.define,Rt=me?.get,fe=U&&me?{define:(e,t)=>a(Ft,U,[e,t]),get:e=>a(Rt,U,[e])}:void 0,$t=b.document,Y=g("Document"),Nt=Y?.createElement,ur=Y?.createElementNS,Ot=WeakSet.prototype.add,mr=WeakSet.prototype.has,Ct=new WeakSet;var Lt=(e,t)=>(a(Ot,e,[t]),t);function Ue(e){return Lt(Ct,a(Nt,$t,[e]))}var z=g("Node"),he=g("Element"),X=g("HTMLElement"),D=e=>k(z,e),$e=D("parentNode"),jt=D("firstChild"),It=D("lastChild"),Dt=D("nextSibling"),Ht=D("nodeType"),Bt=D("childNodes"),Wt=k(he,"localName"),Ut=z?.appendChild,qt=z?.insertBefore,zt=z?.removeChild,L=he,_t=L?.getAttribute,Zt=L?.getAttributeNames,Kt=L?.setAttribute,Vt=L?.removeAttribute,Gt=L?.matches,Jt=L?.attachShadow,Yt=L?.querySelectorAll,Qt=g("HTMLDialogElement")?.close,Xt=k(g("NodeList"),"length"),en=g("NodeList")?.item,ee=g("MutationRecord"),tn=k(ee,"type"),nn=k(ee,"target"),rn=k(ee,"addedNodes"),sn=k(ee,"removedNodes"),Ne=b.MutationObserver,on=g("MutationObserver")?.observe,qe=Y?de(Y,"adoptedStyleSheets"):void 0,Oe=qe?.get,Ce=qe?.set,an=b.requestAnimationFrame,Le=k(X,"style"),te=g("CSSStyleDeclaration"),dn=te?.getPropertyValue,pn=te?.getPropertyPriority,ln=te?.setProperty,je=X?.showPopover,Ie=X?.hidePopover,cn=k(g("Event"),"target"),un=g("Event")?.stopImmediatePropagation,V=e=>{let t=[];for(let n=0,r=a(Xt,e,[]);n<r;n++)t[n]=a(en,e,[n]);return t},ze=Ne&&z&&he&&X&&te?{parent:e=>a($e,e,[]),first:e=>a(jt,e,[]),last:e=>a(It,e,[]),next:e=>a(Dt,e,[]),isElement:e=>a(Ht,e,[])===1,name:e=>a(Wt,e,[]),children:e=>V(a(Bt,e,[])),append:(e,t)=>a(Ut,e,[t]),insert:(e,t,n)=>a(qt,e,[t,n]),remove:e=>{let t=a($e,e,[]);t&&a(zt,t,[e])},attribute:(e,t)=>a(_t,e,[t]),attributes:e=>a(Zt,e,[]),setAttribute:(e,t,n)=>a(Kt,e,[t,n]),removeAttribute:(e,t)=>a(Vt,e,[t]),matches:(e,t)=>a(Gt,e,[t]),all:(e,t)=>V(a(Yt,e,[t])),closeDialog:e=>a(Qt,e,[]),shadow:e=>a(Jt,e,[{mode:"closed"}]),style:(e,t)=>{let n=a(Le,e,[]);return[a(dn,n,[t]),a(pn,n,[t])]},setStyle:(e,t,n,r="")=>a(ln,a(Le,e,[]),[t,n,r]),observer:e=>new Ne(e),observe:(e,t,n)=>a(on,e,[t,n]),record:e=>({type:a(tn,e,[]),target:a(nn,e,[]),added:V(a(rn,e,[])),removed:V(a(sn,e,[]))}),adopted:Oe&&Ce?{count:e=>a(Oe,e,[]).length,clear:e=>a(Ce,e,[[]])}:void 0,frame:e=>a(an,globalThis,[e]),popover:je&&Ie?{show:e=>a(je,e,[]),hide:e=>a(Ie,e,[])}:void 0,target:e=>a(cn,e,[]),stop:e=>a(un,e,[])}:void 0;var F=class extends Error{constructor(n,r){super(`the app could not run (${n})`);m(this,"reason",n);m(this,"error",r)}};function j(e){try{let t=e instanceof Error?e.name:void 0;return typeof t=="string"&&/^[A-Za-z][A-Za-z0-9_]{0,63}$/.test(t)?t:void 0}catch{return}}function _(e){let t=e?.id;if(typeof t!="string"||t==="")return!0;for(let n=0;n<_e.length;n++)if(t[n]!==_e[n])return!1;return!0}var _e="com.wyndlass.";function mn(e=typeof navigator>"u"?"":navigator.userAgent){let t=/Chrom(?:e|ium)\/(\d+)/.exec(e);return t?Number(t[1]):void 0}function Ze(e,t=Te.apiVersion,n=mn()){return!(e?.playerApi!==void 0&&e.playerApi>t||e?.webView!==void 0&&n!==void 0&&e.webView>n)}function fn(e){let t;try{t=e.paging?.()}catch{return null}let n=t;return n&&Number.isInteger(n.count)&&n.count>1&&typeof n.ms=="number"&&n.ms>0?{count:n.count,ms:n.ms}:null}var ge=new Map;function hn(){let e=Be(8),t="";for(let n=0;n<8;n++)t+=gn[e[n]%26];return t}var gn="abcdefghijklmnopqrstuvwxyz",yn=0;function Ke(e,t,n){let r=ge.get(e);if(!r){let s=ue.make([t],"text/javascript");r=import(s).then(o=>bn(o,n),o=>{throw o instanceof F?o:new F("load",j(o))}).finally(()=>ue.revoke(s)),ge.set(e,r),r.catch(()=>ge.delete(e))}return r}function bn(e,t){let n=e.default;if(typeof n=="function"&&n.prototype instanceof HTMLElement){let r=n.prototype[Me];if(typeof r!="function")throw new F("shape");let s=class extends n{},o=`${t?.element??"wl-app"}-${hn()}${++yn}`;try{fe.define(o,s)}catch(c){throw new F("shape",j(c))}return{tag:o,shape:"class",attach:(c,A)=>{He(r,c,[A])}}}if(_(t)&&typeof e.element=="string"&&fe.get(e.element))return{tag:e.element,shape:"legacy"};throw new F("shape")}var wn=3,vn=6e4,ne=class{constructor(t,n){m(this,"element",t);m(this,"o",n);m(this,"faults",[]);m(this,"halted",!1)}get stopped(){return this.halted}has(t){try{return!this.halted&&typeof this.element[t]=="function"}catch{return!1}}update(t){this.halted||this.run(()=>this.element.update?.(t))}reset(){this.halted||this.run(()=>this.element.reset?.())}paging(){return this.halted?null:fn(this.element)}hook(t,n){if(this.halted)return Promise.resolve();let r;try{r=this.element[t]?.(n)}catch(s){return this.fault(s),Promise.resolve()}return Promise.resolve(r).then(()=>{},s=>this.fault(s))}stop(t){if(!this.halted){this.halted=!0;try{this.element.remove()}catch{}this.o.dispose?.(),this.o.onStop(t)}}dispose(){this.o.dispose?.()}run(t){try{t()}catch(n){this.fault(n)}}fault(t){let n=(this.o.now??Date.now)();this.faults=this.faults.filter(r=>n-r<vn),this.faults.push(n),this.faults.length>=wn&&this.stop(j(t))}},br=24*3600*1e3;var re="wyndlass.frame";function Ye(e,t){return!!e&&(e.apps?e.apps(t):!_(t))}var kn=new Set(["load","shape","unsupported","error","late","overran"]),Ve=50,Ge=250,ye=class{constructor(t,n,r,s){m(this,"frame",t);m(this,"port",n);m(this,"o",s);m(this,"halted",!1);m(this,"pages");m(this,"hooks");m(this,"pending",new Map);m(this,"next",0);m(this,"told");m(this,"last");m(this,"quiet");this.hooks=new Set(r.hooks),this.pages=r.paging}get stopped(){return this.halted}has(t){return!this.halted&&this.hooks.has(t)}paging(){return this.halted?null:this.pages}update(t){if(this.halted)return;let n=performance.now(),r=this.last;this.last={t,at:n};let s=this.told;if(s&&(s.running?Math.abs(t-(s.t+(n-s.at)))<=Ve:t===s.t)){s.running&&this.watch();return}let o=!!r&&n-r.at<Ge&&t>r.t&&Math.abs(t-r.t-(n-r.at))<=Ve;this.told={t,at:n,running:o},this.post({type:"clock",t,running:o}),o&&this.watch()}reset(){this.halted||this.post({type:"reset"})}snapshots(t){this.halted||this.post({type:"snapshots",snapshots:t})}hook(t,n){if(this.halted)return Promise.resolve();let r=++this.next;return new Promise(s=>{this.pending.set(r,s),this.post({type:"hook",id:r,kind:t,options:n})})}stop(t){this.halted||(this.halted=!0,this.frame.remove(),this.dispose(),this.o.onStop(t))}dispose(){this.o.dispose?.(),clearTimeout(this.quiet),this.port.close();for(let t of this.pending.values())t();this.pending.clear()}heard(t){t.type==="paging"?this.pages=Xe(t.paging):t.type==="hooked"&&typeof t.id=="number"?(this.pending.get(t.id)?.(),this.pending.delete(t.id)):t.type==="stopped"&&this.stop(et(t.error))}post(t){try{this.port.postMessage(t)}catch{}}watch(){clearTimeout(this.quiet),this.quiet=setTimeout(()=>{!this.told?.running||!this.last||this.halted||(this.told={...this.last,running:!1},this.post({type:"clock",t:this.last.t,running:!1}))},Ge)}};async function Qe(e,t,n,r,s,o,c,A,y,i){let l=e.appInfo??n.info,u;try{u=new URL(y.frames).origin}catch{return i("unsupported")}let d=document.createElement("iframe");d.setAttribute("sandbox","allow-scripts"),d.setAttribute("referrerpolicy","no-referrer"),d.title=l?.id??"app",d.tabIndex=-1,d.dataset.frame=l?.id??"",d.style.cssText="display:block;width:100%;height:100%;border:0;pointer-events:none;background:transparent;color-scheme:normal";let w=new Promise(T=>{d.addEventListener("load",()=>T(!0),{once:!0}),setTimeout(()=>T(!1),o)});d.src=`${u}/`,t.append(d);let E=(T,v)=>(d.remove(),i(T,v));if(!await w||!d.contentWindow)return E("load");let{port1:x,port2:Z}=new MessageChannel,O={type:"init",element:{id:e.id,type:"app",app:e.app,props:e.props??{},...e.binding?{binding:e.binding}:{},...e.appInfo?{appInfo:e.appInfo}:{}},bundle:{hash:n.hash,bytes:n.bytes.slice(),...n.info?{info:n.info}:{}},...r.place?{place:r.place}:{},screen:r.screen??null,timeZone:r.timeZone,locale:r.locale,preview:!!r.preview,clock:{t:s.now(),running:!1,startedAt:s.startedAt()},fonts:Sn(e.props??{},y),snapshots:Je(e,r),deadlineMs:o};return new Promise(T=>{let v,$=!1,M=B=>{$||($=!0,clearTimeout(C),T(B))},C=setTimeout(()=>{v?(M({app:v,late:!0}),v.update(s.now())):(x.close(),M(E("load")))},o+1e3),S=!1;x.onmessage=B=>{let h=B.data;if(!(!h||typeof h!="object")){if(h.type==="ask"){S&&An(h,r,x);return}if(v){h.type==="ready"?(M({app:v,late:h.late===!0}),v.update(s.now())):v.heard(h);return}if(h.type==="hello"&&!S)S=!0,x.postMessage(O,[O.bundle.bytes.buffer]);else if(h.type==="made"&&Array.isArray(h.hooks)){let W=tt(e),K,p=new ye(d,x,{type:"made",hooks:h.hooks.filter(f=>f==="enter"||f==="exit"),paging:Xe(h.paging)},{onStop:A,dispose:()=>K?.()});K=r.onSnapshots?.(f=>{f.some(P=>W.has(P))&&p.snapshots(Je(e,r))}),v=p,c(v)}else h.type==="failed"&&(x.close(),M(E(kn.has(h.reason)?h.reason:"error",et(h.error))))}},d.contentWindow.postMessage({type:re},"*",[Z])})}async function An(e,t,n){if(typeof e.id!="number")return;let r=(s,o=[])=>{try{n.postMessage(s,o)}catch{}};try{if(!t.data)throw new Error("no data service");if(e.what==="data"&&typeof e.source=="string")r({type:"answer",id:e.id,value:await t.data.get(e.source,xn(e.params))});else if(e.what==="image"&&typeof e.path=="string"){let s=await t.data.imageBlob(e.path),o=new Uint8Array(await s.arrayBuffer());r({type:"answer",id:e.id,value:{bytes:o,type:s.type}},[o.buffer])}else throw new TypeError("not a question")}catch(s){r({type:"answer",id:e.id,error:j(s)??"Error"})}}function xn(e){let t={};if(e&&typeof e=="object")for(let[n,r]of Object.entries(e))(typeof r=="string"||typeof r=="number"||typeof r=="boolean")&&(t[n]=r);return t}function Xe(e){let t=e;return t&&Number.isInteger(t.count)&&t.count>1&&typeof t.ms=="number"&&t.ms>0?{count:t.count,ms:t.ms}:null}function et(e){return typeof e=="string"&&/^[A-Za-z][A-Za-z0-9_]{0,63}$/.test(e)?e:void 0}function Sn(e,t){let n=new Set;for(let s of Object.values(e)){let o=s?.family;s&&typeof s=="object"&&typeof o=="string"&&n.add(o)}let r=[];for(let s of t.fonts??[]){let o=n.has(s.family)?t.font?.(s.blob):void 0;o&&r.push({family:s.family,weight:s.weight,style:s.style,blob:s.blob,bytes:o.slice()})}return r}function tt(e){let t=new Set;e.binding?.table&&t.add(e.binding.table);for(let n of Object.values(e.props??{}))typeof n=="string"&&/^tbl_[0-9a-z]{26}$/.test(n)&&t.add(n);return t}function Je(e,t){let n={};for(let r of tt(e)){let s=t.snapshot?.(r);s&&(n[r]=s)}return n}function nt(e){let t=ze?.setStyle??((n,r,s,o)=>n.style.setProperty(r,s,o));t(e,"contain","strict","important"),t(e,"overflow","clip","important")}var En=["name","type","tags","location","site","district","region","organization","path","timeZone"],Tn=["text","date","currency","truncate"],rt={site:"SITE",district:"DISTRICT",region:"REGION",organization:"ORGANIZATION"};function se(e,t,n,r){if(!e)return n==="timeZone"&&t?t:void 0;if(n==="fields")return r&&e.fields?.[r]||void 0;let s=e.path??[];switch(n){case"name":return e.name||void 0;case"type":return e.type||void 0;case"tags":return e.tags?.length?e.tags.join(", "):void 0;case"location":return s.at(-1)?.name;case"path":return s.length?s.map(o=>o.name).join(" \u203A "):void 0;case"timeZone":return t||void 0;default:return rt[n]?[...s].reverse().find(o=>o.kind===rt[n])?.name:void 0}}function Mn(e,t){if(e==null||e==="")return;let n=String(e);if(t==="currency"){let r=Number(e);return Number.isFinite(r)?r.toFixed(2):n}if(t==="date"){let r=Date.parse(n);return Number.isNaN(r)?n:new Date(r).toISOString().slice(0,10)}return t==="truncate"&&n.length>40?n.slice(0,39)+"\u2026":n}function st(e,t){return(e.source==="screen"?se(t.screen,t.timeZone,e.field,e.key):Mn(t.tables?.[e.table]?.rows?.[e.row??0]?.[e.column],e.format))??e.fallback??""}function ot(e){let t=/^\s*(?:screen\.(fields\.[a-z][A-Za-z0-9]{0,31}|[A-Za-z]+)|table\.([A-Za-z0-9_-]+)\[(\d+)\](?:\.([A-Za-z0-9_]+)|\["((?:[^"\\]|\\.)*)"\]))\s*((?:\|\s*(?:[a-z]+|"(?:[^"\\]|\\.)*")\s*)*)$/.exec(e);if(!t)return null;let n=[...t[6].matchAll(/\|\s*([a-z]+|"((?:[^"\\]|\\.)*)")/g)],r=n.find(o=>o[1].startsWith('"'))?.[2]?.replace(/\\(.)/g,"$1"),s=n.find(o=>!o[1].startsWith('"'))?.[1];return t[1]!==void 0?s?null:t[1].startsWith("fields.")?{source:"screen",field:"fields",key:t[1].slice(7),...r!==void 0?{fallback:r}:{}}:En.includes(t[1])?{source:"screen",field:t[1],...r!==void 0?{fallback:r}:{}}:null:s&&!Tn.includes(s)?null:{source:"table",table:t[2],row:Number(t[3]),column:t[4]??t[5].replace(/\\(.)/g,"$1"),...s?{format:s}:{},...r!==void 0?{fallback:r}:{}}}function it(e,t){return e.replace(/\{{2}([^{}]*)\}{2}/g,(n,r)=>{let s=ot(r);return s?st(s,t):n})}function at(e){return[...e.matchAll(/\{{2}([^{}]*)\}{2}/g)].some(t=>ot(t[1])!==null)}function be(e,t){return Object.fromEntries(Object.entries(e).map(([n,r])=>[n,typeof r=="string"?it(r,t):r]))}var we=e=>e==null||e==="";function pt(e,t,n,r,s,o){return Pn(e,t,n,r,{now:s,timeZone:o})}function Pn(e,t,n,r,s){if(n===null&&(t==="eq"||t==="ne"))return t==="eq"?we(e):!we(e);if(we(e)||n===null||n===void 0)return!1;switch(r){case"number":case"currency":{let o=Number(e),c=Number(n);return typeof n!="boolean"&&n!==""&&Number.isFinite(o)&&Number.isFinite(c)&&ve(t,o-c)}case"boolean":{let o=n===!0||n==="true"?!0:n===!1||n==="false"?!1:void 0;return o!==void 0&&(t==="eq"?e===o:t==="ne"?e!==o:!1)}case"date":{let o=n==="today"?Fn(s.now,s.timeZone).date:String(n),c=String(e).slice(0,10);return/^\d{4}-\d{2}-\d{2}$/.test(o)&&ve(t,c<o?-1:c>o?1:0)}case"datetime":{let o=Date.parse(String(e)),c=n==="now"?s.now:Date.parse(String(n));return!Number.isNaN(o)&&!Number.isNaN(c)&&ve(t,o-c)}default:{let o=String(e).toLowerCase(),c=String(n).toLowerCase();return t==="eq"?o===c:t==="ne"?o!==c:t==="contains"?o.includes(c):!1}}}function ve(e,t){switch(e){case"eq":return t===0;case"ne":return t!==0;case"lt":return t<0;case"lte":return t<=0;case"gt":return t>0;case"gte":return t>=0;default:return!1}}var dt=new Map;function Fn(e,t){let n=dt.get(t);n||(n=new Intl.DateTimeFormat("en-US",{timeZone:t,hourCycle:"h23",year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",weekday:"short"}),dt.set(t,n));let r=Object.fromEntries(n.formatToParts(new Date(e)).map(s=>[s.type,s.value]));return{date:`${r.year}-${r.month}-${r.day}`,minute:Number(r.hour)%24*60+Number(r.minute),day:String(r.weekday).slice(0,3).toLowerCase()}}var lt=e=>!!(e?.filter?.length||e?.sort||e?.limit),Rn=e=>e==null||e==="";function $n(e,t){if(!Rn(e))switch(t){case"number":case"currency":{let n=Number(e);return Number.isFinite(n)?n:void 0}case"datetime":{let n=Date.parse(String(e));return Number.isNaN(n)?void 0:n}case"boolean":return e===!0||String(e).toLowerCase()==="true"?1:0;case"date":return String(e).slice(0,10);default:return String(e).toLowerCase()}}function Nn(e,t){let n=Array.from(e),r=Array.from(t);for(let s=0;s<n.length&&s<r.length;s++){let o=n[s].codePointAt(0)-r[s].codePointAt(0);if(o!==0)return o}return n.length-r.length}function On(e,t,n,r,s){let o=e,c=Array.isArray(o?.rows)?o.rows:[],A=Array.isArray(o?.columns)?o.columns:void 0,y=i=>A?.find(l=>l.key===i)?.type??"text";for(let i of t.filter??[]){if(A&&!A.some(d=>d.key===i.column))return[];let l=i.value;if(l!==null&&typeof l=="object"){let d=l;if(l=se(n,r,d.screen,d.key),l===void 0)return[]}let u=y(i.column);c=c.filter(d=>pt(d[i.column],i.op,l,u,s,r))}if(t.sort){let{column:i}=t.sort,l=y(i),u=t.sort.order==="desc"?-1:1;c=c.map((d,w)=>({row:d,i:w,key:$n(d[i],l)})).sort((d,w)=>{if(d.key===void 0||w.key===void 0)return d.key===void 0&&w.key===void 0?d.i-w.i:d.key===void 0?1:-1;let E=typeof d.key=="number"&&typeof w.key=="number"?d.key-w.key:Nn(String(d.key),String(w.key));return E===0?d.i-w.i:E<0?-u:u}).map(d=>d.row)}return t.limit?c.slice(0,t.limit):c}function ct(e,t,n,r,s){return!e||typeof e!="object"?e:{...e,rows:On(e,t,n,r,s)}}function ut(e){return{screen:e?.screen??null,timeZone:e?.timeZone,tables:new Proxy({},{get:(t,n)=>typeof n=="string"?e?.snapshot?.(n):void 0})}}async function ft(e,t,n,r,s,o,c,A=()=>{},y){let i=e.appInfo??n.info;nt(t),i?.id&&(t.dataset.app=i.id);let l=(p,f)=>(t.dataset.appFailed=p,{failed:p,...f?{error:f}:{}});if(!Ze(i?.requires))return l("unsupported");let u=r.appHost;if(u&&!u.custom&&!_(i))return l("unsupported");if(Ye(y,i))return Qe(e,t,n,r,s,o,c,A,y,l);let d=e.app;if(u?.held?.(d))return t.dataset.appHeld="",{held:!0};await u?.mounting?.(d,i?.id);let w=!1,E=()=>{w||(w=!0,u?.unmounted?.(d))},x;try{x=await Ke(n.hash,n.bytes,i)}catch(p){return E(),p instanceof F?l(p.reason,p.error):l("load")}let Z=x.tag,O=i?.id?r.controllerFor?.({id:i.id,instance:e.id,capability:i.player??"read"}):void 0,T,v=new Promise(p=>{T=p}),$,M=new Set,C={app:i?.id??e.app.id,element:Z,props:be(e.props??{},ut(r)),place:r.place,screen:r.screen??null,timeZone:r.timeZone??Intl.DateTimeFormat().resolvedOptions().timeZone,locale:r.locale??navigator.language,preview:!!r.preview,now:s.now,startedAt:s.startedAt,ready:()=>T(),data:(p,f)=>r.data?r.data.get(p,f):Promise.reject(new Error("no data service")),kept:(p,f)=>r.data?.kept?.(p,f),image:p=>r.data?r.data.image(p):Promise.reject(new Error("no data service")),snapshot:p=>{let f=r.snapshot?.(p)??null;if(!f||e.binding?.table!==p||!lt(e.binding))return f;let P=ct(f,e.binding,r.screen,C.timeZone,s.now());return $?.from===f&&$.rows.length===P.rows.length&&$.rows.every((bt,wt)=>bt===P.rows[wt])?$.snapshot:($={from:f,rows:P.rows,snapshot:P},P)},...e.binding?{binding:e.binding}:{},...r.onSnapshots?{onSnapshot:p=>(M.add(p),()=>{M.delete(p)})}:{},...e.sensors?.includes("impressions")&&r.impressions?.measuring?{impressions:{now:()=>r.impressions.now(),on:p=>r.impressions.on(p)}}:{},...O?{controller:O}:{}},S;try{S=Ue(Z),x.shape==="class"?x.attach(S,C):S.wyndlass=C}catch(p){return O?.dispose(),E(),l("error",j(p))}S.style.cssText="display:block;width:100%;height:100%",u?.contained?.(t,S);let B=Object.values(e.props??{}).some(p=>typeof p=="string"&&at(p)),h=r.onSnapshots?.(p=>{B&&(C.props=be(e.props??{},ut(r)));for(let f of p)for(let P of[...M])try{P(f)}catch{}}),W=new ne(S,{onStop:A,dispose:()=>{h?.(),M.clear(),O?.dispose(),E()}});t.append(S),c(W);let K=await Promise.race([v.then(()=>!1),new Promise(p=>setTimeout(()=>p(!0),o))]);return W.update(C.now()),{app:W,late:K}}var mt=new Set;async function ht(e,t){for(let n of e){let r=`${n.family}/${n.weight??400}/${n.style??"normal"}/${n.blob}`,s=t(n.blob);if(!(mt.has(r)||!s||typeof FontFace>"u"))try{let o=new FontFace(n.family,s,{weight:String(n.weight??400),style:n.style??"normal"});document.fonts.add(await o.load()),mt.add(r)}catch{}}}var Cn=Object.freeze,Ln=Object.values,gt=Reflect.apply;function oe(e){if(e===null||typeof e!="object")return e;for(let t of gt(Ln,Object,[e]))oe(t);return gt(Cn,Object,[e])}var ie,R,I={t:0,at:0,running:!1},H={},ke=new Set,Ae="null",xe=new Map,jn=0,N=e=>ie.postMessage(e),Se=()=>I.running?I.t+(performance.now()-I.at):I.t;addEventListener("message",e=>{ie||e.source!==parent||e.data?.type!==re||!e.ports[0]||(ie=e.ports[0],ie.onmessage=t=>In(t.data),N({type:"hello"}))});function In(e){switch(e.type){case"init":Dn(e);break;case"clock":I={t:e.t,at:performance.now(),running:e.running},R?.update(Se()),Ee();break;case"hook":{let t=e.id;(R?R.hook(e.kind,e.options):Promise.resolve()).then(()=>N({type:"hooked",id:t}));break}case"reset":R?.reset();break;case"snapshots":{let t=H;H=oe(e.snapshots);let n=[...new Set([...Object.keys(t),...Object.keys(H)])].filter(r=>JSON.stringify(t[r])!==JSON.stringify(H[r]));if(n.length)for(let r of[...ke])r(n);break}case"answer":{let t=xe.get(e.id);xe.delete(e.id),e.error!==void 0?t?.reject(Object.assign(new Error("the host could not answer"),{name:e.error})):t?.resolve(e.value);break}}}function yt(e){let t=++jn;return new Promise((n,r)=>{xe.set(t,{resolve:n,reject:r}),N({type:"ask",id:t,...e})})}function Ee(){let e=R?.paging()??null,t=JSON.stringify(e);t!==Ae&&(Ae=t,N({type:"paging",paging:e}))}async function Dn(e){if(R)return;I={t:e.clock.t,at:performance.now(),running:e.clock.running},H=oe(e.snapshots);let t=new Map,n=(i,l)=>`${i}?${JSON.stringify(Object.keys(l).sort().map(u=>[u,l[u]]))}`,s={data:{get:async(i,l)=>{let u=await yt({what:"data",source:i,params:l});return t.set(n(i,l),u),u},kept:(i,l)=>t.get(n(i,l)),image:async i=>{let{bytes:l,type:u}=await yt({what:"image",path:i});return URL.createObjectURL(new Blob([l],{type:u}))}},place:e.place,screen:e.screen,timeZone:e.timeZone,locale:e.locale,preview:e.preview,snapshot:i=>H[i]??null,onSnapshots:i=>(ke.add(i),()=>{ke.delete(i)})},o={now:Se,startedAt:()=>e.clock.startedAt},c=new Map(e.fonts.map(i=>[i.blob,i.bytes]));await ht(e.fonts,i=>c.get(i));let A=document.getElementById("app"),y=await ft(e.element,A,e.bundle,s,o,e.deadlineMs,i=>{R=i,Ae=JSON.stringify(i.paging()),N({type:"made",hooks:["enter","exit"].filter(l=>i.has(l)),paging:i.paging()})},i=>N({type:"stopped",...i?{error:i}:{}}));"failed"in y?N({type:"failed",reason:y.failed,...y.error?{error:y.error}:{}}):"app"in y&&(N({type:"ready",late:y.late}),Ee(),Hn())}function Hn(){let e=0,t=n=>{R?.stopped||(I.running&&R?.update(Se()),n-e>1e3&&(e=n,Ee()),requestAnimationFrame(t))};requestAnimationFrame(t)}})();
|