@514sid/signagekit 0.0.0-stage → 0.0.2
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/CHANGELOG.md +39 -0
- package/LICENSE +44 -0
- package/README.md +106 -2
- package/bin/signagekit.js +4 -0
- package/dist/chunks/chart-WERjiRho.js +2 -0
- package/dist/chunks/empty--6iNLsra.js +2 -0
- package/dist/chunks/icon-paths-Dl0Rqja7.js +2 -0
- package/dist/chunks/lists-qOUpM5us.js +2 -0
- package/dist/chunks/measures-D0av_G5P.js +2 -0
- package/dist/chunks/media-DI35kQ5D.js +3 -0
- package/dist/chunks/qr-size-Dq23do6_.js +2 -0
- package/dist/chunks/ready-Boq20M-G.js +2 -0
- package/dist/chunks/share-D64VSp1B.js +2 -0
- package/dist/chunks/stat-DEE3meII.js +2 -0
- package/dist/chunks/time-KYYBrpD9.js +2 -0
- package/dist/cli.js +10 -0
- package/dist/index.js +3 -0
- package/dist/style.css +2 -0
- package/dist/types/alert.d.ts +20 -0
- package/dist/types/binds.d.ts +6 -0
- package/dist/types/chart.d.ts +16 -0
- package/dist/types/check.d.ts +31 -0
- package/dist/types/checks.d.ts +36 -0
- package/dist/types/colors.d.ts +18 -0
- package/dist/types/components.d.ts +13 -0
- package/dist/types/cycle-fit.d.ts +6 -0
- package/dist/types/data.d.ts +19 -0
- package/dist/types/empty.d.ts +6 -0
- package/dist/types/fit.d.ts +3 -0
- package/dist/types/flow.d.ts +7 -0
- package/dist/types/icon-paths.d.ts +2 -0
- package/dist/types/icon.d.ts +7 -0
- package/dist/types/icons.d.ts +3 -0
- package/dist/types/index.d.ts +20 -0
- package/dist/types/lists.d.ts +29 -0
- package/dist/types/measure.d.ts +4 -0
- package/dist/types/measures.d.ts +22 -0
- package/dist/types/media.d.ts +15 -0
- package/dist/types/mount.d.ts +45 -0
- package/dist/types/parse.d.ts +11 -0
- package/dist/types/qr-size.d.ts +2 -0
- package/dist/types/ready.d.ts +3 -0
- package/dist/types/share.d.ts +33 -0
- package/dist/types/sizes.d.ts +15 -0
- package/dist/types/specs.d.ts +7 -0
- package/dist/types/stat.d.ts +38 -0
- package/dist/types/table-fit.d.ts +8 -0
- package/dist/types/table.d.ts +11 -0
- package/dist/types/time.d.ts +28 -0
- package/dist/types/trend-fit.d.ts +14 -0
- package/dist/types/type-scale.d.ts +10 -0
- package/dist/types/types.d.ts +271 -0
- package/dist/types/validate-layout.d.ts +2 -0
- package/dist/types/validate.d.ts +7 -0
- package/dist/types/version.d.ts +24 -0
- package/dist/types/views.d.ts +29 -0
- package/docs/api.md +106 -0
- package/docs/assistants.md +55 -0
- package/docs/format.md +183 -0
- package/docs/getting-started.md +77 -0
- package/docs/preview.md +84 -0
- package/examples/clinic-queue/data.json +17 -0
- package/examples/clinic-queue/layout.yml +43 -0
- package/examples/fire-alarm/data.yml +23 -0
- package/examples/fire-alarm/layout.yml +30 -0
- package/examples/gym-classes/data.yml +17 -0
- package/examples/gym-classes/layout.yml +47 -0
- package/examples/hotel-lobby/data.yml +2 -0
- package/examples/hotel-lobby/layout.yml +29 -0
- package/examples/lunch/data.yml +16 -0
- package/examples/lunch/layout.yml +43 -0
- package/examples/meeting-room/data.json +19 -0
- package/examples/meeting-room/layout.json +107 -0
- package/examples/noc-wall/data.yml +18 -0
- package/examples/noc-wall/layout.yml +63 -0
- package/examples/oee-line/data.yml +25 -0
- package/examples/oee-line/layout.yml +55 -0
- package/examples/office-air/data.yml +25 -0
- package/examples/office-air/layout.yml +71 -0
- package/examples/sales-today/data.json +28 -0
- package/examples/sales-today/layout.json +62 -0
- package/examples/snow-closure/data.yml +28 -0
- package/examples/snow-closure/layout.json +35 -0
- package/examples/station-departures/data.json +24 -0
- package/examples/station-departures/layout.json +34 -0
- package/examples/store-promo/data.yml +18 -0
- package/examples/store-promo/layout.yml +54 -0
- package/examples/storm-banner/data.json +20 -0
- package/examples/storm-banner/layout.json +33 -0
- package/examples/trading-floor/data.json +23 -0
- package/examples/trading-floor/layout.json +65 -0
- package/examples/warehouse/data.json +20 -0
- package/examples/warehouse/layout.json +50 -0
- package/package.json +100 -4
- package/schema.json +962 -0
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import type { Colors } from './colors';
|
|
2
|
+
/** A literal, or a pointer into the data, like { "bind": "/menu/0/price" }. */
|
|
3
|
+
export type Bind = {
|
|
4
|
+
bind: string;
|
|
5
|
+
/** Shown when the path is missing or empty in the data. */
|
|
6
|
+
fallback?: string | number;
|
|
7
|
+
};
|
|
8
|
+
export type Value = string | number | Bind;
|
|
9
|
+
export type Surface = 'base' | 'raised' | 'accent';
|
|
10
|
+
export type Align = 'start' | 'center' | 'end';
|
|
11
|
+
export type TextSize = 's' | 'm' | 'l' | 'xl';
|
|
12
|
+
interface Base {
|
|
13
|
+
surface?: Surface;
|
|
14
|
+
/** Short label shown above the component, like "Menu" or "Wait time". */
|
|
15
|
+
title?: Value;
|
|
16
|
+
/** Pins the text size in px on the reference canvas, skipping fitting. */
|
|
17
|
+
size?: number | TextSize;
|
|
18
|
+
/** Share of a stack's space, relative to its siblings. Default 1. */
|
|
19
|
+
weight?: number;
|
|
20
|
+
/** A named icon shown before the title, in place of the square marker. */
|
|
21
|
+
icon?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface Heading extends Base {
|
|
24
|
+
type: 'heading';
|
|
25
|
+
text: Value;
|
|
26
|
+
align?: Align;
|
|
27
|
+
}
|
|
28
|
+
export interface Text extends Base {
|
|
29
|
+
type: 'text';
|
|
30
|
+
text: Value;
|
|
31
|
+
size?: TextSize;
|
|
32
|
+
align?: Align;
|
|
33
|
+
fit?: boolean;
|
|
34
|
+
}
|
|
35
|
+
export type State = 'neutral' | 'good' | 'warning' | 'danger';
|
|
36
|
+
/**
|
|
37
|
+
* Sets the state from the value, so data only has to carry numbers. With
|
|
38
|
+
* danger above warning, higher is worse; with danger below, lower is worse.
|
|
39
|
+
*/
|
|
40
|
+
export interface Thresholds {
|
|
41
|
+
warning?: number;
|
|
42
|
+
danger?: number;
|
|
43
|
+
}
|
|
44
|
+
export interface Stat extends Base {
|
|
45
|
+
type: 'stat';
|
|
46
|
+
value: Value;
|
|
47
|
+
label?: Value;
|
|
48
|
+
unit?: Value;
|
|
49
|
+
/** Bound or literal; anything but good, warning or danger is neutral. */
|
|
50
|
+
state?: Value;
|
|
51
|
+
/** up, down or flat; bound or literal. Anything else shows no arrow. */
|
|
52
|
+
trend?: Value;
|
|
53
|
+
/** Change shown next to the arrow, like "+4.2%". */
|
|
54
|
+
delta?: Value;
|
|
55
|
+
/** Which direction is good news; colors the arrow green or red. */
|
|
56
|
+
good_when?: 'up' | 'down';
|
|
57
|
+
/** compact (default) shortens numbers from 10,000 up; full never does. */
|
|
58
|
+
format?: 'compact' | 'full';
|
|
59
|
+
/** false keeps this stat's own size instead of matching its row. */
|
|
60
|
+
match_row?: boolean;
|
|
61
|
+
/** Recent values drawn as a sparkline under the number. */
|
|
62
|
+
spark?: Value[] | Bind;
|
|
63
|
+
thresholds?: Thresholds;
|
|
64
|
+
}
|
|
65
|
+
export interface Table extends Base {
|
|
66
|
+
type: 'table';
|
|
67
|
+
columns: string[];
|
|
68
|
+
rows: Value[][] | Bind;
|
|
69
|
+
align?: Align[];
|
|
70
|
+
/** Rows per page; pages flip instantly every `seconds`. */
|
|
71
|
+
page_rows?: number;
|
|
72
|
+
/** Relative column widths, like [3, 1, 1]. Long cells end in an ellipsis. */
|
|
73
|
+
widths?: number[];
|
|
74
|
+
seconds?: number;
|
|
75
|
+
}
|
|
76
|
+
export interface List extends Base {
|
|
77
|
+
type: 'list';
|
|
78
|
+
items: Value[] | Bind;
|
|
79
|
+
}
|
|
80
|
+
export interface Image extends Base {
|
|
81
|
+
type: 'image';
|
|
82
|
+
src: Value;
|
|
83
|
+
fit?: 'cover' | 'contain';
|
|
84
|
+
alt?: string;
|
|
85
|
+
}
|
|
86
|
+
export interface Clock extends Base {
|
|
87
|
+
type: 'clock';
|
|
88
|
+
hour12?: boolean;
|
|
89
|
+
show_date?: boolean;
|
|
90
|
+
}
|
|
91
|
+
export interface Countdown extends Base {
|
|
92
|
+
type: 'countdown';
|
|
93
|
+
to: Value;
|
|
94
|
+
label?: Value;
|
|
95
|
+
/** Shown once the time has passed. Default "Now". */
|
|
96
|
+
done?: Value;
|
|
97
|
+
}
|
|
98
|
+
export interface ChartSeries {
|
|
99
|
+
name: Value;
|
|
100
|
+
values: Value[] | Bind;
|
|
101
|
+
}
|
|
102
|
+
/** Line, area or column chart, drawn by uPlot. */
|
|
103
|
+
export interface Chart extends Base {
|
|
104
|
+
type: 'chart';
|
|
105
|
+
kind: 'line' | 'area' | 'column';
|
|
106
|
+
labels: Value[] | Bind;
|
|
107
|
+
series: ChartSeries[];
|
|
108
|
+
/** A horizontal target line, like an SLO or a production goal. */
|
|
109
|
+
target?: Value;
|
|
110
|
+
target_label?: Value;
|
|
111
|
+
/** Fixed y-axis range; either end can be left out. */
|
|
112
|
+
y_min?: number;
|
|
113
|
+
y_max?: number;
|
|
114
|
+
}
|
|
115
|
+
/** Shows one message at a time and swaps to the next instantly. */
|
|
116
|
+
export interface Cycle extends Base {
|
|
117
|
+
type: 'cycle';
|
|
118
|
+
items: Value[] | Bind;
|
|
119
|
+
seconds?: number;
|
|
120
|
+
align?: Align;
|
|
121
|
+
}
|
|
122
|
+
export interface Stack extends Base {
|
|
123
|
+
type: 'stack';
|
|
124
|
+
direction?: 'row' | 'column';
|
|
125
|
+
items: Component[];
|
|
126
|
+
}
|
|
127
|
+
export type Component = Heading | Text | Stat | Table | List | Image | Clock | Countdown | Cycle | Chart | Stack | Meter | Meters | Bars | Split | Status | Schedule | Queue | Clocks | Slideshow | Qr;
|
|
128
|
+
/** A labelled amount, used by meters, bars and split. */
|
|
129
|
+
export interface Part {
|
|
130
|
+
label: Value;
|
|
131
|
+
value: Value;
|
|
132
|
+
max?: Value;
|
|
133
|
+
state?: Value;
|
|
134
|
+
}
|
|
135
|
+
export type Parts = Part[] | Bind;
|
|
136
|
+
/** One value against a limit. */
|
|
137
|
+
export interface Meter extends Base {
|
|
138
|
+
type: 'meter';
|
|
139
|
+
value: Value;
|
|
140
|
+
max?: Value;
|
|
141
|
+
label?: Value;
|
|
142
|
+
unit?: Value;
|
|
143
|
+
state?: Value;
|
|
144
|
+
thresholds?: Thresholds;
|
|
145
|
+
}
|
|
146
|
+
/** Several meters, one row each, bars aligned. */
|
|
147
|
+
export interface Meters extends Base {
|
|
148
|
+
type: 'meters';
|
|
149
|
+
items: Parts;
|
|
150
|
+
max?: Value;
|
|
151
|
+
unit?: Value;
|
|
152
|
+
/** Applied to each row without its own state. */
|
|
153
|
+
thresholds?: Thresholds;
|
|
154
|
+
}
|
|
155
|
+
/** Ranked horizontal bars, largest first. */
|
|
156
|
+
export interface Bars extends Base {
|
|
157
|
+
type: 'bars';
|
|
158
|
+
items: Parts;
|
|
159
|
+
unit?: Value;
|
|
160
|
+
limit?: number;
|
|
161
|
+
}
|
|
162
|
+
/** One whole split into up to six parts. */
|
|
163
|
+
export interface Split extends Base {
|
|
164
|
+
type: 'split';
|
|
165
|
+
items: Parts;
|
|
166
|
+
}
|
|
167
|
+
/** Rows with a state icon each, like service health. */
|
|
168
|
+
export interface Status extends Base {
|
|
169
|
+
type: 'status';
|
|
170
|
+
items: {
|
|
171
|
+
label: Value;
|
|
172
|
+
state: Value;
|
|
173
|
+
note?: Value;
|
|
174
|
+
}[] | Bind;
|
|
175
|
+
}
|
|
176
|
+
/** Time-ordered entries; the current and next ones are marked. */
|
|
177
|
+
export interface Schedule extends Base {
|
|
178
|
+
type: 'schedule';
|
|
179
|
+
items: {
|
|
180
|
+
start: Value;
|
|
181
|
+
end?: Value;
|
|
182
|
+
title: Value;
|
|
183
|
+
detail?: Value;
|
|
184
|
+
}[] | Bind;
|
|
185
|
+
rows?: number;
|
|
186
|
+
}
|
|
187
|
+
/** "Now serving" number with the recently called ones. */
|
|
188
|
+
export interface Queue extends Base {
|
|
189
|
+
type: 'queue';
|
|
190
|
+
current: Value;
|
|
191
|
+
recent?: Value[] | Bind;
|
|
192
|
+
label?: Value;
|
|
193
|
+
}
|
|
194
|
+
/** Several clocks, one per time zone. */
|
|
195
|
+
export interface Clocks extends Base {
|
|
196
|
+
type: 'clocks';
|
|
197
|
+
/** `zone` is the older name for `timezone` and still works. */
|
|
198
|
+
zones: {
|
|
199
|
+
label: Value;
|
|
200
|
+
timezone?: string;
|
|
201
|
+
zone?: string;
|
|
202
|
+
}[];
|
|
203
|
+
hour12?: boolean;
|
|
204
|
+
}
|
|
205
|
+
/** Images that swap instantly on a timer. */
|
|
206
|
+
export interface Slideshow extends Base {
|
|
207
|
+
type: 'slideshow';
|
|
208
|
+
images: Value[] | Bind;
|
|
209
|
+
seconds?: number;
|
|
210
|
+
fit?: 'cover' | 'contain';
|
|
211
|
+
}
|
|
212
|
+
/** QR code, black on white for scanners, with an optional caption. */
|
|
213
|
+
export interface Qr extends Base {
|
|
214
|
+
type: 'qr';
|
|
215
|
+
value: Value;
|
|
216
|
+
label?: Value;
|
|
217
|
+
}
|
|
218
|
+
export type ComponentType = Component['type'];
|
|
219
|
+
/** A fixed grid: named areas, like CSS grid-template-areas. */
|
|
220
|
+
export interface GridLayout {
|
|
221
|
+
areas: string[];
|
|
222
|
+
columns?: string;
|
|
223
|
+
rows?: string;
|
|
224
|
+
gap?: number;
|
|
225
|
+
}
|
|
226
|
+
/** One zone in a flow layout. */
|
|
227
|
+
export interface FlowItem {
|
|
228
|
+
zone: string;
|
|
229
|
+
/** Columns out of 12 in the design orientation. Default 12. */
|
|
230
|
+
span?: number;
|
|
231
|
+
/** A number is a share of the free height (like fr); "150px" is fixed. */
|
|
232
|
+
height?: number | string;
|
|
233
|
+
}
|
|
234
|
+
/** Zones in reading order, packed into rows to suit the screen's shape. */
|
|
235
|
+
export interface FlowLayout {
|
|
236
|
+
flow: (string | FlowItem)[];
|
|
237
|
+
design?: 'landscape' | 'portrait';
|
|
238
|
+
gap?: number;
|
|
239
|
+
}
|
|
240
|
+
export type Layout = GridLayout | FlowLayout;
|
|
241
|
+
/**
|
|
242
|
+
* A screen-wide message: full screen (takeover) or a strip across the top
|
|
243
|
+
* (banner). It shows while its title or message is set and until `until`.
|
|
244
|
+
* The whole alert can also be a bind to an object in the data.
|
|
245
|
+
*/
|
|
246
|
+
export interface AlertFields {
|
|
247
|
+
level?: 'info' | 'good' | 'warning' | 'danger' | Bind;
|
|
248
|
+
icon?: string | Bind;
|
|
249
|
+
title?: Value;
|
|
250
|
+
message?: Value;
|
|
251
|
+
/** Older name for `message`. */
|
|
252
|
+
text?: Value;
|
|
253
|
+
steps?: Value[] | Bind;
|
|
254
|
+
footer?: Value;
|
|
255
|
+
mode?: 'takeover' | 'banner';
|
|
256
|
+
/** ISO date and time after which the alert no longer shows. */
|
|
257
|
+
until?: Value;
|
|
258
|
+
/** Older name for `level`. */
|
|
259
|
+
state?: 'warning' | 'danger';
|
|
260
|
+
}
|
|
261
|
+
export type Alert = AlertFields | Bind;
|
|
262
|
+
export interface ScreenDoc {
|
|
263
|
+
/** Format version, default 1. See src/version.ts. */
|
|
264
|
+
version?: number;
|
|
265
|
+
colors?: Colors;
|
|
266
|
+
alert?: Alert;
|
|
267
|
+
layout: Layout;
|
|
268
|
+
zones: Record<string, Component>;
|
|
269
|
+
}
|
|
270
|
+
export type Data = Record<string, unknown>;
|
|
271
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export declare const FORMAT = 1;
|
|
2
|
+
type Obj = Record<string, unknown>;
|
|
3
|
+
/** Upgrades a document from version N to N+1. */
|
|
4
|
+
export type Migration = (doc: Obj) => {
|
|
5
|
+
doc: Obj;
|
|
6
|
+
notes: string[];
|
|
7
|
+
};
|
|
8
|
+
export type Migrations = Record<number, () => Promise<{
|
|
9
|
+
default: Migration;
|
|
10
|
+
}>>;
|
|
11
|
+
export interface Upgraded {
|
|
12
|
+
/** The document in the current format, or as written when newer. */
|
|
13
|
+
doc: unknown;
|
|
14
|
+
/** What changed or was lost on the way, as warnings. */
|
|
15
|
+
notes: string[];
|
|
16
|
+
}
|
|
17
|
+
/** The version a document declares, or null when it isn't a whole number. */
|
|
18
|
+
export declare function versionOf(doc: Obj): number | null;
|
|
19
|
+
/**
|
|
20
|
+
* Brings a parsed document to the current format. Documents already current,
|
|
21
|
+
* newer, or not even objects pass through untouched for validate() to judge.
|
|
22
|
+
*/
|
|
23
|
+
export declare function upgrade(input: unknown, migrations?: Migrations, current?: number): Promise<Upgraded>;
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { JSX } from 'preact';
|
|
2
|
+
import type { Data, ScreenDoc } from './types';
|
|
3
|
+
export type AnyView = (p: {
|
|
4
|
+
c: never;
|
|
5
|
+
data: Data;
|
|
6
|
+
}) => JSX.Element | null;
|
|
7
|
+
declare const LOADERS: {
|
|
8
|
+
chart: () => Promise<AnyView | typeof import("./chart").Chart>;
|
|
9
|
+
meter: () => Promise<AnyView | typeof import("./measures").MeterView>;
|
|
10
|
+
meters: () => Promise<AnyView | typeof import("./measures").MetersView>;
|
|
11
|
+
bars: () => Promise<AnyView | typeof import("./measures").BarsView>;
|
|
12
|
+
split: () => Promise<AnyView | typeof import("./measures").SplitView>;
|
|
13
|
+
status: () => Promise<AnyView | typeof import("./lists").StatusView>;
|
|
14
|
+
schedule: () => Promise<AnyView | typeof import("./lists").ScheduleView>;
|
|
15
|
+
queue: () => Promise<AnyView | typeof import("./lists").QueueView>;
|
|
16
|
+
clocks: () => Promise<AnyView | typeof import("./lists").ClocksView>;
|
|
17
|
+
slideshow: () => Promise<AnyView | typeof import("./media").SlideshowView>;
|
|
18
|
+
qr: () => Promise<AnyView | typeof import("./media").QrView>;
|
|
19
|
+
};
|
|
20
|
+
export type LazyType = keyof typeof LOADERS;
|
|
21
|
+
export declare const isLazy: (t: string) => t is LazyType;
|
|
22
|
+
/** The view if its chunk is already here, so it renders without waiting. */
|
|
23
|
+
export declare const loadedView: (t: LazyType) => AnyView | undefined;
|
|
24
|
+
export declare function loadView(t: LazyType): Promise<AnyView>;
|
|
25
|
+
/** Component types a document uses, stacks included. */
|
|
26
|
+
export declare function usedTypes(doc: ScreenDoc): Set<string>;
|
|
27
|
+
/** Loads every chunk the document needs, so the first paint is complete. */
|
|
28
|
+
export declare function preloadViews(doc: ScreenDoc | null | undefined): Promise<void>;
|
|
29
|
+
export {};
|
package/docs/api.md
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# API
|
|
2
|
+
|
|
3
|
+
## Mounting a screen
|
|
4
|
+
|
|
5
|
+
Give `mount()` an element and a document. The screen fills the element, so size the element the way you want the screen.
|
|
6
|
+
|
|
7
|
+
```js
|
|
8
|
+
import { mount } from '@514sid/signagekit'
|
|
9
|
+
import '@514sid/signagekit/style.css'
|
|
10
|
+
|
|
11
|
+
const screen = await mount(document.getElementById('screen'), {
|
|
12
|
+
layout: layoutYamlOrObject,
|
|
13
|
+
data: { menu: [['Tomato soup', '4.50']], wait: 6 },
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
// Later, when the values change:
|
|
17
|
+
await screen.update({ data: { menu: [...], wait: 12 } })
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Without a bundler, load it as a module with an import map. The YAML parser, uPlot and the QR encoder only download when a screen needs them.
|
|
21
|
+
|
|
22
|
+
```html
|
|
23
|
+
<link
|
|
24
|
+
rel="stylesheet"
|
|
25
|
+
href="https://cdn.jsdelivr.net/npm/@514sid/signagekit@0.0.2/dist/style.css"
|
|
26
|
+
/>
|
|
27
|
+
<script type="importmap">
|
|
28
|
+
{
|
|
29
|
+
"imports": {
|
|
30
|
+
"@514sid/signagekit": "https://cdn.jsdelivr.net/npm/@514sid/signagekit@0.0.2/dist/index.js",
|
|
31
|
+
"preact": "https://cdn.jsdelivr.net/npm/preact@10.27.2/dist/preact.module.js",
|
|
32
|
+
"preact/hooks": "https://cdn.jsdelivr.net/npm/preact@10.27.2/hooks/dist/hooks.module.js",
|
|
33
|
+
"preact/jsx-runtime": "https://cdn.jsdelivr.net/npm/preact@10.27.2/jsx-runtime/dist/jsxRuntime.module.js",
|
|
34
|
+
"uplot": "https://cdn.jsdelivr.net/npm/uplot@1.6.32/dist/uPlot.esm.js",
|
|
35
|
+
"uqr": "https://cdn.jsdelivr.net/npm/uqr@0.1.3/dist/index.mjs",
|
|
36
|
+
"yaml": "https://cdn.jsdelivr.net/npm/yaml@2.9.1/browser/index.js"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
</script>
|
|
40
|
+
<div id="screen" style="position: fixed; inset: 0"></div>
|
|
41
|
+
<script type="module">
|
|
42
|
+
import { mount } from '@514sid/signagekit'
|
|
43
|
+
await mount(document.getElementById('screen'), { layout: '...', data: '...' })
|
|
44
|
+
</script>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## `mount(element, options)`
|
|
48
|
+
|
|
49
|
+
Renders a document into `element` and resolves with a handle once the screen is drawn, charts included. The canvas is 1080 px on the element's short side and follows its shape, then scales to fit with one CSS transform. When the element changes size, the layout reflows.
|
|
50
|
+
|
|
51
|
+
| Option | Default | What it does |
|
|
52
|
+
| -------------- | ---------------- | -------------------------------------------------------------------- |
|
|
53
|
+
| `layout` | required | The document: JSON or YAML text, or an object. |
|
|
54
|
+
| `data` | `{}` | Values that binds read: JSON or YAML text, or an object. |
|
|
55
|
+
| `scheme` | `'dark'` | `'dark'` or `'light'`, used when the document doesn't set one. |
|
|
56
|
+
| `accent` | `'#2563eb'` | Accent color, used when the document doesn't set one. |
|
|
57
|
+
| `colors` | none | Colors that win over the document's own, like `{ scheme: 'light' }`. |
|
|
58
|
+
| `timeZone` | device time zone | IANA time zone for clocks, dates and schedules. |
|
|
59
|
+
| `locale` | device locale | BCP 47 locale for dates and times. |
|
|
60
|
+
| `showProblems` | `false` | Shows errors and warnings over the screen. Useful while building. |
|
|
61
|
+
| `now` | live clock | A `Date` that freezes the clock, for screenshots and tests. |
|
|
62
|
+
|
|
63
|
+
`timeZone`, `locale` and `now` belong to the screen they are given to: several screens on one page can each use their own.
|
|
64
|
+
|
|
65
|
+
The handle has:
|
|
66
|
+
|
|
67
|
+
- `update({ layout?, data? })` replaces either or both and renders again.
|
|
68
|
+
- `errors` and `warnings` hold the messages from the last check.
|
|
69
|
+
- `report(message)` adds an error, which `showProblems` displays. Use it for your own failures, like a data request that didn't come back.
|
|
70
|
+
- `destroy()` removes the screen and stops watching the element's size.
|
|
71
|
+
|
|
72
|
+
`mount()` never throws on a bad document. Errors drop only what they affect: a broken layout renders nothing, a broken zone is left out, and warnings never block rendering.
|
|
73
|
+
|
|
74
|
+
## `check(layout, data?, options?)`
|
|
75
|
+
|
|
76
|
+
Parses and validates a document without rendering it. Resolves with `{ doc, data, errors, warnings }`. Run it on anything an assistant wrote before showing it. Options are `scheme`, `accent` and `colors`, which the contrast check uses, and `checkBinds: false`, which skips the warnings about binds that find nothing in the data, for data that arrives later.
|
|
77
|
+
|
|
78
|
+
## `validate(object)`
|
|
79
|
+
|
|
80
|
+
The synchronous core of `check()`, for an already parsed document. Returns `{ doc, errors, warnings }`. It doesn't upgrade older formats, since upgrades load on demand; use `check()` for documents that may be old.
|
|
81
|
+
|
|
82
|
+
## `previewUrl(layout, data?, options?)`
|
|
83
|
+
|
|
84
|
+
Resolves with a link to the [preview tool](preview.md) showing the screen. Options are the preview's parameters: `mode`, `size`, `scheme`, `accent`, `timeZone`, `locale`, `now` and `problems`, plus `base` for your own copy of the site. `pack()` and `unpack()` encode and decode the `#s=` part on their own.
|
|
85
|
+
|
|
86
|
+
## Also exported
|
|
87
|
+
|
|
88
|
+
`FORMAT` is the newest document format this version reads. `canvasSize(width, height)` returns the canvas size for a screen. `COLOR_KEYS`, `SCHEMES`, `ICON_NAMES` and `LEVELS` list the allowed colors, schemes, icons and alert levels. Every document type is exported too, from `ScreenDoc` down to each component.
|
|
89
|
+
|
|
90
|
+
## Command line
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
npx @514sid/signagekit check screen.layout.yml screen.data.yml
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Prints every error and warning with its exact path, low contrast and binds that find nothing in the data. It exits with 1 when there are errors, so it works as a gate in scripts and as a feedback loop for an assistant. `--scheme light` and `--accent '#hex'` match the contrast check to how you'll mount the screen.
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
npx @514sid/signagekit url screen.layout.yml screen.data.yml --mode sizes --now 2026-11-14T09:30
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Prints a [preview](preview.md) link with the screen packed into it. It takes every preview parameter as a flag: `--mode`, `--size`, `--scheme`, `--accent`, `--tz`, `--locale`, `--now`, `--problems` and `--base`.
|
|
103
|
+
|
|
104
|
+
## Browser support
|
|
105
|
+
|
|
106
|
+
Browsers from 2020 on: current Chrome, Edge, Firefox and Safari, and the Chromium builds in signage players, smart TVs and sticks. It needs ES2020 and CSS grid. `ResizeObserver` is used when present.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Writing screens with an AI assistant
|
|
2
|
+
|
|
3
|
+
This guide is written to be handed to an assistant along with [format.md](format.md) and, for editors and validation, [schema.json](../schema.json). It covers what makes a screen read well from across a room and the mistakes assistants make most often.
|
|
4
|
+
|
|
5
|
+
## How to work
|
|
6
|
+
|
|
7
|
+
Write the layout once and put everything that changes in the data. A menu, a score, a list of arrivals or an alert lives in the data; the layout only says where it goes. After the first version, an update rewrites the data and leaves the layout alone, so a screen can't break because a value changed.
|
|
8
|
+
|
|
9
|
+
Run `npx @514sid/signagekit check layout.yml data.yml` after every change and fix everything it prints, warnings included. A warning that a bind finds nothing in the data is almost always a typo in a path, and it shows as a dash on the screen. Then look at it. `npx @514sid/signagekit url layout.yml data.yml --mode sizes` prints a link to the [preview tool](preview.md); screenshot it once `html[data-status="ready"]` appears and check the screen at every resolution. Pass `--now` with a fixed time so clocks and schedules match between screenshots.
|
|
10
|
+
|
|
11
|
+
## Layout
|
|
12
|
+
|
|
13
|
+
- Start every document with `version: 1`, so it keeps working when the format changes.
|
|
14
|
+
|
|
15
|
+
- Use a `flow` layout. Design it once for the orientation the screen mostly has (`design: landscape` or `portrait`), and it adapts to other shapes. Use `areas` only when a screen must look exactly as drawn.
|
|
16
|
+
- Give headers and footers a fixed height in px, about 120 to 170 for a heading and 90 to 120 for a strip of messages. Let everything else share the remaining height with plain numbers.
|
|
17
|
+
- Spans are out of 12 columns. Two or three zones per row reads well on a landscape screen; one or two on portrait.
|
|
18
|
+
- Fewer, bigger zones read from further away. A screen seen from across a room works best with four to six zones and short lists. Each extra zone makes every text smaller.
|
|
19
|
+
|
|
20
|
+
## Content
|
|
21
|
+
|
|
22
|
+
- Don't set `size`. signagekit sizes all text together after every render, so tables, lists and stats on one screen share sizes. Pin a size only when the person asks for one.
|
|
23
|
+
- Keep text short. Headings are a few words, cycle messages are one line each, and table cells are a word or a number. Long text doesn't shrink to fit: lists drop rows with "+N more" and tables page.
|
|
24
|
+
- Use `stat` with `thresholds` instead of writing states into the data, so the data only carries numbers: `thresholds: { warning: 80, danger: 95 }`. With danger below warning, lower is worse.
|
|
25
|
+
- Show state with `state`, never with color alone. States add an icon and a label as well as a color.
|
|
26
|
+
- Give a bind a `fallback` when the value may be missing and a dash would confuse people: `{ bind: /wait, fallback: Ask at the desk }`.
|
|
27
|
+
- Pick the component that matches the data: `bars` for a ranking, `meters` for values against limits, `split` for parts of one whole, `chart` for change over time, `status` for things that are up or down, `schedule` for a timetable, `queue` for "now serving".
|
|
28
|
+
- Never ask for scrolling or moving text. `cycle` swaps whole messages instantly.
|
|
29
|
+
|
|
30
|
+
## Alerts
|
|
31
|
+
|
|
32
|
+
Bind the alert to the data, `alert: { bind: /alert }`, so raising one and clearing it are data changes:
|
|
33
|
+
|
|
34
|
+
```yaml
|
|
35
|
+
alert:
|
|
36
|
+
level: danger
|
|
37
|
+
icon: fire
|
|
38
|
+
title: Fire alarm
|
|
39
|
+
message: Leave the building now by the nearest exit.
|
|
40
|
+
steps: [Do not use the lifts, Gather at assembly point B]
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Set it to `null` to bring the screen back. Pick the `icon` that names the situation from the list in [format.md](format.md#icons), keep the title to a few words, and put instructions in `steps`, one action each. Use `mode: banner` for notices that shouldn't hide the screen, and `until` for ones that should end on their own.
|
|
44
|
+
|
|
45
|
+
## Colors
|
|
46
|
+
|
|
47
|
+
Set `colors.scheme` so the screen looks the same everywhere, and set `accent` only when the person names a brand color. Fix every contrast warning: change the color the warning names, or remove it and let the scheme supply one.
|
|
48
|
+
|
|
49
|
+
## YAML
|
|
50
|
+
|
|
51
|
+
YAML data is read as text, so `4.50` stays `4.50`. In the layout, quote values that must stay text, like `delta: '+0.04'`. Quote any value that starts with `%`, `#`, `@` or `*`, and any value containing `: ` or, inside `{ }` and `[ ]`, a comma. When in doubt, write JSON.
|
|
52
|
+
|
|
53
|
+
## A prompt to start from
|
|
54
|
+
|
|
55
|
+
> You write screens for signagekit. A screen is a layout document and a data object, both YAML. Follow schema.json and docs/assistants.md. Put every value that can change in the data and bind to it. Use a flow layout with at most six zones. Don't set text sizes. After writing, run `npx @514sid/signagekit check` and fix every error and warning, then screenshot the link from `npx @514sid/signagekit url --mode sizes` and fix anything that looks cramped or empty before you answer. For later changes, edit only the data unless the person asks for a different layout.
|