@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 +209 -205
- package/dist/vite.d.ts +2 -1
- package/dist/vite.js +48 -1
- package/manifest.schema.json +112 -112
- package/package.json +1 -1
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
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
</
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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');
|
package/manifest.schema.json
CHANGED
|
@@ -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.
|
|
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",
|