@edgestation/sdk 0.1.0 → 0.2.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 CHANGED
@@ -1,205 +1,209 @@
1
- # @edgestation/sdk
2
-
3
- Write widgets for [EdgeStation](https://github.com/chainlist/edgestation), in plain JavaScript, Svelte, React or Vue. EdgeStation is a widget dashboard for a secondary display: made first for the Corsair Xeneon Edge, it works on any other one. The docs call the display it runs on "the Edge", whichever it is.
4
-
5
- A widget is a folder with a `manifest.json` and an ES module that EdgeStation loads at runtime. The app hands each widget its `api` when it mounts, so nothing from this package runs inside the app: it gives you
6
-
7
- - the TypeScript types of the `api`, the manifest and the widget module,
8
- - `defineWidget()`, to get those types in plain JavaScript too,
9
- - adapters that mount a Svelte, React or Vue component as a widget,
10
- - a Vite plugin that builds the one self-contained `dist/main.js` EdgeStation loads,
11
- - a JSON Schema for `manifest.json`, for autocomplete and checks in your editor.
12
-
13
- What the `api` does, the manifest fields, settings, styling and theming are documented in [Writing a widget](https://github.com/chainlist/edgestation/blob/main/widgets/README.md).
14
-
15
- ## Start a widget
16
-
17
- ```bash
18
- npm install -D @edgestation/sdk vite
19
- ```
20
-
21
- ```
22
- my-widget/
23
- manifest.json
24
- package.json ("type": "module")
25
- vite.config.ts
26
- src/main.ts (or main.js, main.tsx, main.jsx)
27
- dist/main.js (built)
28
- ```
29
-
30
- ```json
31
- {
32
- "$schema": "./node_modules/@edgestation/sdk/manifest.schema.json",
33
- "name": "My widget",
34
- "minAppVersion": "0.1.0",
35
- "main": "dist/main.js",
36
- "size": { "w": 2, "h": 1 }
37
- }
38
- ```
39
-
40
- `minAppVersion` is the oldest EdgeStation that can run the widget: set it to the version of this package you build with (they are released together), and older apps show an error card instead of a broken widget.
41
-
42
- ### Plain JavaScript or TypeScript
43
-
44
- ```js
45
- // vite.config.js
46
- import { widget } from '@edgestation/sdk/vite';
47
- import { defineConfig } from 'vite';
48
-
49
- export default defineConfig({ plugins: [widget()] });
50
- ```
51
-
52
- ```js
53
- // src/main.js
54
- import { defineWidget } from '@edgestation/sdk';
55
-
56
- export default defineWidget({
57
- mount(el, api) {
58
- api.every(1000, async () => {
59
- const { cpu } = await api.system();
60
- el.textContent = `${Math.round(cpu.load)} %`;
61
- });
62
- }
63
- });
64
- ```
65
-
66
- ### Svelte 5
67
-
68
- ```bash
69
- npm install -D svelte @sveltejs/vite-plugin-svelte
70
- ```
71
-
72
- ```ts
73
- // vite.config.ts
74
- import { widget } from '@edgestation/sdk/vite';
75
- import { svelte } from '@sveltejs/vite-plugin-svelte';
76
- import { defineConfig } from 'vite';
77
-
78
- export default defineConfig({ plugins: [svelte({ emitCss: false }), widget()] });
79
- ```
80
-
81
- ```ts
82
- // src/main.ts
83
- import { svelteWidget } from '@edgestation/sdk/svelte';
84
- import Cpu from './Cpu.svelte';
85
-
86
- export default svelteWidget(Cpu);
87
- ```
88
-
89
- ```svelte
90
- <!-- src/Cpu.svelte -->
91
- <script lang="ts">
92
- import type { WidgetProps } from '@edgestation/sdk';
93
-
94
- let { api }: WidgetProps = $props();
95
- let load = $state(0);
96
- api.every(1000, async () => (load = (await api.system()).cpu.load));
97
- </script>
98
-
99
- <p>{Math.round(load)} %</p>
100
- ```
101
-
102
- `emitCss: false` keeps each component's styles in its JavaScript, and Svelte adds them to the widget's shadow root when it mounts.
103
-
104
- ### React 19
105
-
106
- ```bash
107
- npm install -D react react-dom @vitejs/plugin-react
108
- ```
109
-
110
- ```ts
111
- // vite.config.ts
112
- import { widget } from '@edgestation/sdk/vite';
113
- import react from '@vitejs/plugin-react';
114
- import { defineConfig } from 'vite';
115
-
116
- export default defineConfig({ plugins: [react(), widget()] });
117
- ```
118
-
119
- ```tsx
120
- // src/main.tsx
121
- import { reactWidget } from '@edgestation/sdk/react';
122
- import type { WidgetProps } from '@edgestation/sdk';
123
- import { useEffect, useState } from 'react';
124
-
125
- function Cpu({ api }: WidgetProps) {
126
- const [load, setLoad] = useState(0);
127
- useEffect(() => api.every(1000, async () => setLoad((await api.system()).cpu.load)), []);
128
- return <p>{Math.round(load)} %</p>;
129
- }
130
-
131
- export default reactWidget(Cpu);
132
- ```
133
-
134
- An error thrown while rendering, that no error boundary of yours catches, shows the widget's error card.
135
-
136
- ### Vue 3
137
-
138
- ```bash
139
- npm install -D vue @vitejs/plugin-vue
140
- ```
141
-
142
- ```ts
143
- // vite.config.ts
144
- import { widget } from '@edgestation/sdk/vite';
145
- import vue from '@vitejs/plugin-vue';
146
- import { defineConfig } from 'vite';
147
-
148
- export default defineConfig({ plugins: [vue(), widget()] });
149
- ```
150
-
151
- ```ts
152
- // src/main.ts
153
- import { vueWidget } from '@edgestation/sdk/vue';
154
- import Cpu from './Cpu.vue';
155
-
156
- export default vueWidget(Cpu);
157
- ```
158
-
159
- ```vue
160
- <!-- src/Cpu.vue -->
161
- <script setup lang="ts">
162
- import { ref } from 'vue';
163
- import type { WidgetProps } from '@edgestation/sdk';
164
-
165
- const { api } = defineProps<WidgetProps>();
166
- const load = ref(0);
167
- api.every(1000, async () => (load.value = (await api.system()).cpu.load));
168
- </script>
169
-
170
- <template>
171
- <p>{{ Math.round(load) }} %</p>
172
- </template>
173
- ```
174
-
175
- Errors Vue catches (rendering, lifecycle hooks, watchers, template event handlers) show the widget's error card.
176
-
177
- ## Build
178
-
179
- ```bash
180
- npx vite build
181
- ```
182
-
183
- `widget()` builds the entry into `dist/main.js`, one ES module with every dependency bundled in, your framework's runtime included. CSS your code imports (React, Vue `<style>` blocks) goes to `dist/main.css`: add `"styles": "dist/main.css"` to the manifest so EdgeStation loads it into the widget's shadow root. The build warns when the manifest doesn't point at what it wrote.
184
-
185
- Pass `widget({ entry: 'src/other.ts' })` for another entry. For TypeScript to accept CSS imports, add `"types": ["vite/client"]` to your `tsconfig.json`.
186
-
187
- ## Try it in EdgeStation
188
-
189
- EdgeStation loads every folder of `%APPDATA%\com.edgestation.dashboard\widgets\` (Settings → Widgets → Open folder) that has a `manifest.json`; the folder name is the widget's id. Rather than copying your project there, link it, from a Command Prompt:
190
-
191
- ```bat
192
- mklink /J "%APPDATA%\com.edgestation.dashboard\widgets\my-widget" C:\path\to\my-widget
193
- ```
194
-
195
- Then rebuild on each change, and use Settings → Widgets → Reload Edge to load the new build:
196
-
197
- ```bash
198
- npx vite build --watch
199
- ```
200
-
201
- To share the widget, ship the folder with `manifest.json`, `dist/` and any files it reads with `api.asset()`.
202
-
203
- ## Types
204
-
205
- Everything is exported from `@edgestation/sdk` as types: `WidgetApi`, `WidgetModule`, `WidgetManifest`, `WidgetProps`, the settings types and the results of `api.system()`, `api.media`, `api.apps` and `api.storage`.
1
+ # @edgestation/sdk
2
+
3
+ Write widgets for [EdgeStation](https://github.com/chainlist/edgestation), in plain JavaScript, Svelte, React or Vue. EdgeStation is a widget dashboard for a secondary display: made first for the Corsair Xeneon Edge, it works on any other one. The docs call the display it runs on "the Edge", whichever it is.
4
+
5
+ A widget is a folder with a `manifest.json` and an ES module that EdgeStation loads at runtime. The app hands each widget its `api` when it mounts, so nothing from this package runs inside the app: it gives you
6
+
7
+ - the TypeScript types of the `api`, the manifest and the widget module,
8
+ - `defineWidget()`, to get those types in plain JavaScript too,
9
+ - adapters that mount a Svelte, React or Vue component as a widget,
10
+ - a Vite plugin that builds the one self-contained `dist/main.js` EdgeStation loads,
11
+ - a JSON Schema for `manifest.json`, for autocomplete and checks in your editor.
12
+
13
+ What the `api` does, the manifest fields, settings, styling and theming are documented in [Writing a widget](https://github.com/chainlist/edgestation/blob/main/widgets/README.md).
14
+
15
+ ## Start a widget
16
+
17
+ ```bash
18
+ npm install -D @edgestation/sdk vite
19
+ ```
20
+
21
+ ```
22
+ my-widget/
23
+ manifest.json
24
+ package.json ("type": "module")
25
+ vite.config.ts
26
+ src/main.ts (or main.js, main.tsx, main.jsx)
27
+ dist/main.js (built)
28
+ ```
29
+
30
+ ```json
31
+ {
32
+ "$schema": "./node_modules/@edgestation/sdk/manifest.schema.json",
33
+ "name": "My widget",
34
+ "minAppVersion": "0.1.0",
35
+ "main": "dist/main.js",
36
+ "size": { "w": 2, "h": 1 }
37
+ }
38
+ ```
39
+
40
+ `minAppVersion` is the oldest EdgeStation that can run the widget: set it to the version of this package you build with (they are released together), and older apps show an error card instead of a broken widget.
41
+
42
+ ### Plain JavaScript or TypeScript
43
+
44
+ ```js
45
+ // vite.config.js
46
+ import { widget } from '@edgestation/sdk/vite';
47
+ import { defineConfig } from 'vite';
48
+
49
+ export default defineConfig({ plugins: [widget()] });
50
+ ```
51
+
52
+ ```js
53
+ // src/main.js
54
+ import { defineWidget } from '@edgestation/sdk';
55
+
56
+ export default defineWidget({
57
+ mount(el, api) {
58
+ api.every(1000, async () => {
59
+ const { cpu } = await api.system();
60
+ el.textContent = `${Math.round(cpu.load)} %`;
61
+ });
62
+ }
63
+ });
64
+ ```
65
+
66
+ ### Svelte 5
67
+
68
+ ```bash
69
+ npm install -D svelte @sveltejs/vite-plugin-svelte
70
+ ```
71
+
72
+ ```ts
73
+ // vite.config.ts
74
+ import { widget } from '@edgestation/sdk/vite';
75
+ import { svelte } from '@sveltejs/vite-plugin-svelte';
76
+ import { defineConfig } from 'vite';
77
+
78
+ export default defineConfig({ plugins: [svelte({ emitCss: false }), widget()] });
79
+ ```
80
+
81
+ ```ts
82
+ // src/main.ts
83
+ import { svelteWidget } from '@edgestation/sdk/svelte';
84
+ import Cpu from './Cpu.svelte';
85
+
86
+ export default svelteWidget(Cpu);
87
+ ```
88
+
89
+ ```svelte
90
+ <!-- src/Cpu.svelte -->
91
+ <script lang="ts">
92
+ import type { WidgetProps } from '@edgestation/sdk';
93
+
94
+ let props: WidgetProps = $props();
95
+ // svelte-ignore state_referenced_locally
96
+ const { api } = props;
97
+ let load = $state(0);
98
+ api.every(1000, async () => (load = (await api.system()).cpu.load));
99
+ </script>
100
+
101
+ <p>{Math.round(load)} %</p>
102
+ ```
103
+
104
+ `emitCss: false` keeps each component's styles in its JavaScript, and Svelte adds them to the widget's shadow root when it mounts.
105
+
106
+ Read `api` from the props once, at the top of the script. Svelte warns that this only captures the prop's first value (`state_referenced_locally`): that is what you want, since a setting change remounts the widget rather than changing the prop. Taking `api` out of `props` in one statement keeps the warning to that line, which the `svelte-ignore` comment silences. Destructured straight from `$props()`, `api` would raise it at every use.
107
+
108
+ ### React 19
109
+
110
+ ```bash
111
+ npm install -D react react-dom @vitejs/plugin-react
112
+ ```
113
+
114
+ ```ts
115
+ // vite.config.ts
116
+ import { widget } from '@edgestation/sdk/vite';
117
+ import react from '@vitejs/plugin-react';
118
+ import { defineConfig } from 'vite';
119
+
120
+ export default defineConfig({ plugins: [react(), widget()] });
121
+ ```
122
+
123
+ ```tsx
124
+ // src/main.tsx
125
+ import { reactWidget } from '@edgestation/sdk/react';
126
+ import type { WidgetProps } from '@edgestation/sdk';
127
+ import { useEffect, useState } from 'react';
128
+
129
+ function Cpu({ api }: WidgetProps) {
130
+ const [load, setLoad] = useState(0);
131
+ useEffect(() => api.every(1000, async () => setLoad((await api.system()).cpu.load)), []);
132
+ return <p>{Math.round(load)} %</p>;
133
+ }
134
+
135
+ export default reactWidget(Cpu);
136
+ ```
137
+
138
+ An error thrown while rendering, that no error boundary of yours catches, shows the widget's error card.
139
+
140
+ ### Vue 3
141
+
142
+ ```bash
143
+ npm install -D vue @vitejs/plugin-vue
144
+ ```
145
+
146
+ ```ts
147
+ // vite.config.ts
148
+ import { widget } from '@edgestation/sdk/vite';
149
+ import vue from '@vitejs/plugin-vue';
150
+ import { defineConfig } from 'vite';
151
+
152
+ export default defineConfig({ plugins: [vue(), widget()] });
153
+ ```
154
+
155
+ ```ts
156
+ // src/main.ts
157
+ import { vueWidget } from '@edgestation/sdk/vue';
158
+ import Cpu from './Cpu.vue';
159
+
160
+ export default vueWidget(Cpu);
161
+ ```
162
+
163
+ ```vue
164
+ <!-- src/Cpu.vue -->
165
+ <script setup lang="ts">
166
+ import { ref } from 'vue';
167
+ import type { WidgetProps } from '@edgestation/sdk';
168
+
169
+ const { api } = defineProps<WidgetProps>();
170
+ const load = ref(0);
171
+ api.every(1000, async () => (load.value = (await api.system()).cpu.load));
172
+ </script>
173
+
174
+ <template>
175
+ <p>{{ Math.round(load) }} %</p>
176
+ </template>
177
+ ```
178
+
179
+ Errors Vue catches (rendering, lifecycle hooks, watchers, template event handlers) show the widget's error card.
180
+
181
+ ## Build
182
+
183
+ ```bash
184
+ npx vite build
185
+ ```
186
+
187
+ `widget()` builds the entry into `dist/main.js`, one ES module with every dependency bundled in, your framework's runtime included. CSS your code imports (React, Vue `<style>` blocks) goes to `dist/main.css`: add `"styles": "dist/main.css"` to the manifest so EdgeStation loads it into the widget's shadow root. The build warns when the manifest doesn't point at what it wrote, and when your own code (not your dependencies) calls `setTimeout` or `setInterval`, which keep running while the widget's page is off screen: use `api.after` and `api.every` instead.
188
+
189
+ Pass `widget({ entry: 'src/other.ts' })` for another entry. For TypeScript to accept CSS imports, add `"types": ["vite/client"]` to your `tsconfig.json`.
190
+
191
+ ## Try it in EdgeStation
192
+
193
+ EdgeStation loads every folder of `%APPDATA%\com.edgestation.dashboard\widgets\` (Settings → Widgets → Open folder) that has a `manifest.json`; the folder name is the widget's id. Rather than copying your project there, link it, from a Command Prompt:
194
+
195
+ ```bat
196
+ mklink /J "%APPDATA%\com.edgestation.dashboard\widgets\my-widget" C:\path\to\my-widget
197
+ ```
198
+
199
+ Then rebuild on each change, and use Settings → Widgets → Reload Edge to load the new build:
200
+
201
+ ```bash
202
+ npx vite build --watch
203
+ ```
204
+
205
+ To share the widget, ship the folder with `manifest.json`, `dist/` and any files it reads with `api.asset()`.
206
+
207
+ ## Types
208
+
209
+ Everything is exported from `@edgestation/sdk` as types: `WidgetApi`, `WidgetModule`, `WidgetManifest`, `WidgetProps`, the settings types and the results of `api.system()`, `api.media`, `api.apps` and `api.storage`.
package/dist/vite.d.ts CHANGED
@@ -16,6 +16,7 @@ export interface WidgetBuildOptions {
16
16
  * ```
17
17
  *
18
18
  * It also replaces `process.env.NODE_ENV`, which React and Vue read and a browser lacks, and
19
- * warns when `manifest.json` doesn't point at the files it writes.
19
+ * warns when `manifest.json` doesn't point at the files it writes, or when the widget's own
20
+ * code (not its dependencies) calls `setTimeout` or `setInterval`, which don't pause with it.
20
21
  */
21
22
  export declare function widget(options?: WidgetBuildOptions): Plugin;
package/dist/vite.js CHANGED
@@ -4,6 +4,38 @@ import { join, relative, resolve } from 'node:path';
4
4
  const ENTRIES = ['src/main.ts', 'src/main.tsx', 'src/main.js', 'src/main.jsx'];
5
5
  /** Forward slashes and no leading `./`, as paths are written in `manifest.json`. */
6
6
  const normalize = (path) => path.replace(/\\/g, '/').replace(/^\.\//, '');
7
+ /** What each global timer does that the `api` one doesn't. */
8
+ const TIMERS = {
9
+ setInterval: "keeps running while the widget's page is off screen or the Edge is frozen, and after " +
10
+ 'the widget unmounts: use api.every instead',
11
+ setTimeout: "fires even while the widget's page is off screen, and after the widget unmounts: use " +
12
+ 'api.after instead'
13
+ };
14
+ const GLOBALS = new Set(['window', 'globalThis', 'self']);
15
+ /** A file of the widget's own, by its module id: not a dependency, a virtual module or a query. */
16
+ const OWN = /^[^\0?]+\.(?:[cm]?[jt]sx?|svelte|vue)$/;
17
+ /** The global timer that `callee` names (`setTimeout`, `window.setInterval`, ...), if any. */
18
+ function timerName(callee) {
19
+ const { object, property } = callee;
20
+ const global = callee.type === 'MemberExpression' &&
21
+ !callee.computed &&
22
+ object?.type === 'Identifier' &&
23
+ GLOBALS.has(object.name ?? '');
24
+ const name = global ? property?.name : callee.type === 'Identifier' ? callee.name : undefined;
25
+ return name !== undefined && Object.hasOwn(TIMERS, name) ? name : undefined;
26
+ }
27
+ /** The global timers called anywhere in `node`, an ESTree node or a list of them. */
28
+ function timersCalled(node, found = new Set()) {
29
+ if (node && typeof node === 'object') {
30
+ const { type, callee } = node;
31
+ const name = type === 'CallExpression' && callee ? timerName(callee) : undefined;
32
+ if (name)
33
+ found.add(name);
34
+ for (const child of Object.values(node))
35
+ timersCalled(child, found);
36
+ }
37
+ return found;
38
+ }
7
39
  /**
8
40
  * Builds the widget into what EdgeStation loads: one self-contained ES module,
9
41
  * `dist/main.js`, with every dependency bundled in (frameworks included), plus `dist/main.css`
@@ -14,7 +46,8 @@ const normalize = (path) => path.replace(/\\/g, '/').replace(/^\.\//, '');
14
46
  * ```
15
47
  *
16
48
  * It also replaces `process.env.NODE_ENV`, which React and Vue read and a browser lacks, and
17
- * warns when `manifest.json` doesn't point at the files it writes.
49
+ * warns when `manifest.json` doesn't point at the files it writes, or when the widget's own
50
+ * code (not its dependencies) calls `setTimeout` or `setInterval`, which don't pause with it.
18
51
  */
19
52
  export function widget(options = {}) {
20
53
  let root = process.cwd();
@@ -66,6 +99,20 @@ export function widget(options = {}) {
66
99
  `set "main": "${built('main.js')}".`);
67
100
  }
68
101
  },
102
+ // Last, so TypeScript, Svelte and Vue files are plain JavaScript by then, whatever the
103
+ // plugin order. A dependency's timers are its own business.
104
+ transform: {
105
+ order: 'post',
106
+ handler(code, id) {
107
+ if (!OWN.test(id) || id.includes('/node_modules/'))
108
+ return;
109
+ if (!/\bset(?:Timeout|Interval)\b/.test(code))
110
+ return;
111
+ for (const name of timersCalled(this.parse(code))) {
112
+ this.warn(`${normalize(relative(root, id))} calls ${name}, which ${TIMERS[name]}.`);
113
+ }
114
+ }
115
+ },
69
116
  // Not `generateBundle`: Vite's CSS plugin adds main.css to the bundle after it.
70
117
  writeBundle(_, bundle) {
71
118
  const css = built('main.css');
@@ -1,112 +1,112 @@
1
- {
2
- "$schema": "http://json-schema.org/draft-07/schema#",
3
- "title": "EdgeStation widget manifest",
4
- "description": "The manifest.json of an EdgeStation widget. The widget's id is its folder name.",
5
- "type": "object",
6
- "required": ["name"],
7
- "additionalProperties": false,
8
- "properties": {
9
- "$schema": { "type": "string" },
10
- "name": {
11
- "type": "string",
12
- "minLength": 1,
13
- "description": "Name shown in Settings."
14
- },
15
- "version": { "type": "string", "description": "The widget's own version." },
16
- "description": { "type": "string", "description": "What the widget shows, for Settings." },
17
- "author": { "type": "string" },
18
- "minAppVersion": {
19
- "type": "string",
20
- "pattern": "^\\d+\\.\\d+\\.\\d+$",
21
- "description": "Oldest EdgeStation version the widget runs on, such as \"0.1.0\": the one whose api it uses. Older versions show the error card instead."
22
- },
23
- "main": {
24
- "type": "string",
25
- "default": "main.js",
26
- "description": "Entry ES module, relative to the widget folder. It default-exports { mount(el, api) }."
27
- },
28
- "styles": {
29
- "type": "string",
30
- "description": "Stylesheet loaded into the widget's shadow root, relative to the widget folder."
31
- },
32
- "icon": {
33
- "type": "string",
34
- "description": "Square SVG or PNG on a transparent background, shown in Settings. Only its shape counts: it is drawn in the accent color."
35
- },
36
- "size": {
37
- "type": "object",
38
- "description": "Default size in grid cells when the widget is added to a page.",
39
- "required": ["w", "h"],
40
- "additionalProperties": false,
41
- "properties": {
42
- "w": { "type": "integer", "minimum": 1 },
43
- "h": { "type": "integer", "minimum": 1 }
44
- }
45
- },
46
- "webview": {
47
- "type": "boolean",
48
- "description": "Lets the widget call api.webview, and gives its settings a Profile choice."
49
- },
50
- "settings": {
51
- "type": "array",
52
- "description": "Options the user sets per placement, in display order. The widget reads them from api.settings.",
53
- "items": { "oneOf": [{ "$ref": "#/definitions/select" }, { "$ref": "#/definitions/text" }] }
54
- }
55
- },
56
- "definitions": {
57
- "key": {
58
- "type": "string",
59
- "minLength": 1,
60
- "description": "Name of the value in api.settings."
61
- },
62
- "label": { "type": "string", "description": "Shown next to the field." },
63
- "when": {
64
- "type": "object",
65
- "description": "Only show this setting while the named settings have these values.",
66
- "additionalProperties": { "type": "string" }
67
- },
68
- "select": {
69
- "type": "object",
70
- "description": "One value out of a list.",
71
- "required": ["key", "label", "type", "options"],
72
- "additionalProperties": false,
73
- "properties": {
74
- "key": { "$ref": "#/definitions/key" },
75
- "label": { "$ref": "#/definitions/label" },
76
- "type": { "const": "select" },
77
- "options": {
78
- "type": "array",
79
- "minItems": 1,
80
- "items": {
81
- "type": "object",
82
- "required": ["value", "label"],
83
- "additionalProperties": false,
84
- "properties": {
85
- "value": { "type": "string", "description": "Saved value." },
86
- "label": { "type": "string", "description": "Shown label." }
87
- }
88
- }
89
- },
90
- "default": {
91
- "type": "string",
92
- "description": "Value until the user picks one. Defaults to the first option."
93
- },
94
- "when": { "$ref": "#/definitions/when" }
95
- }
96
- },
97
- "text": {
98
- "type": "object",
99
- "description": "Free text, such as a URL.",
100
- "required": ["key", "label", "type"],
101
- "additionalProperties": false,
102
- "properties": {
103
- "key": { "$ref": "#/definitions/key" },
104
- "label": { "$ref": "#/definitions/label" },
105
- "type": { "const": "text" },
106
- "default": { "type": "string", "description": "Value until the user types one." },
107
- "placeholder": { "type": "string", "description": "Hint shown while the field is empty." },
108
- "when": { "$ref": "#/definitions/when" }
109
- }
110
- }
111
- }
112
- }
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "title": "EdgeStation widget manifest",
4
+ "description": "The manifest.json of an EdgeStation widget. The widget's id is its folder name.",
5
+ "type": "object",
6
+ "required": ["name"],
7
+ "additionalProperties": false,
8
+ "properties": {
9
+ "$schema": { "type": "string" },
10
+ "name": {
11
+ "type": "string",
12
+ "minLength": 1,
13
+ "description": "Name shown in Settings."
14
+ },
15
+ "version": { "type": "string", "description": "The widget's own version." },
16
+ "description": { "type": "string", "description": "What the widget shows, for Settings." },
17
+ "author": { "type": "string" },
18
+ "minAppVersion": {
19
+ "type": "string",
20
+ "pattern": "^\\d+\\.\\d+\\.\\d+$",
21
+ "description": "Oldest EdgeStation version the widget runs on, such as \"0.1.0\": the one whose api it uses. Older versions show the error card instead."
22
+ },
23
+ "main": {
24
+ "type": "string",
25
+ "default": "main.js",
26
+ "description": "Entry ES module, relative to the widget folder. It default-exports { mount(el, api) }."
27
+ },
28
+ "styles": {
29
+ "type": "string",
30
+ "description": "Stylesheet loaded into the widget's shadow root, relative to the widget folder."
31
+ },
32
+ "icon": {
33
+ "type": "string",
34
+ "description": "Square SVG or PNG on a transparent background, shown in Settings. Only its shape counts: it is drawn in the accent color."
35
+ },
36
+ "size": {
37
+ "type": "object",
38
+ "description": "Default size in grid cells when the widget is added to a page.",
39
+ "required": ["w", "h"],
40
+ "additionalProperties": false,
41
+ "properties": {
42
+ "w": { "type": "integer", "minimum": 1 },
43
+ "h": { "type": "integer", "minimum": 1 }
44
+ }
45
+ },
46
+ "webview": {
47
+ "type": "boolean",
48
+ "description": "Lets the widget call api.webview, and gives its settings a Profile choice."
49
+ },
50
+ "settings": {
51
+ "type": "array",
52
+ "description": "Options the user sets per placement, in display order. The widget reads them from api.settings.",
53
+ "items": { "oneOf": [{ "$ref": "#/definitions/select" }, { "$ref": "#/definitions/text" }] }
54
+ }
55
+ },
56
+ "definitions": {
57
+ "key": {
58
+ "type": "string",
59
+ "minLength": 1,
60
+ "description": "Name of the value in api.settings."
61
+ },
62
+ "label": { "type": "string", "description": "Shown next to the field." },
63
+ "when": {
64
+ "type": "object",
65
+ "description": "Only show this setting while the named settings have these values.",
66
+ "additionalProperties": { "type": "string" }
67
+ },
68
+ "select": {
69
+ "type": "object",
70
+ "description": "One value out of a list.",
71
+ "required": ["key", "label", "type", "options"],
72
+ "additionalProperties": false,
73
+ "properties": {
74
+ "key": { "$ref": "#/definitions/key" },
75
+ "label": { "$ref": "#/definitions/label" },
76
+ "type": { "const": "select" },
77
+ "options": {
78
+ "type": "array",
79
+ "minItems": 1,
80
+ "items": {
81
+ "type": "object",
82
+ "required": ["value", "label"],
83
+ "additionalProperties": false,
84
+ "properties": {
85
+ "value": { "type": "string", "description": "Saved value." },
86
+ "label": { "type": "string", "description": "Shown label." }
87
+ }
88
+ }
89
+ },
90
+ "default": {
91
+ "type": "string",
92
+ "description": "Value until the user picks one. Defaults to the first option."
93
+ },
94
+ "when": { "$ref": "#/definitions/when" }
95
+ }
96
+ },
97
+ "text": {
98
+ "type": "object",
99
+ "description": "Free text, such as a URL.",
100
+ "required": ["key", "label", "type"],
101
+ "additionalProperties": false,
102
+ "properties": {
103
+ "key": { "$ref": "#/definitions/key" },
104
+ "label": { "$ref": "#/definitions/label" },
105
+ "type": { "const": "text" },
106
+ "default": { "type": "string", "description": "Value until the user types one." },
107
+ "placeholder": { "type": "string", "description": "Hint shown while the field is empty." },
108
+ "when": { "$ref": "#/definitions/when" }
109
+ }
110
+ }
111
+ }
112
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@edgestation/sdk",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Types and build helpers to write widgets for EdgeStation, the secondary display dashboard made for the Corsair Xeneon Edge, in plain JavaScript, Svelte, React or Vue.",
5
5
  "keywords": [
6
6
  "edgestation",