@fcannizzaro/streamdeck-react 0.1.10 → 0.1.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +190 -21
- package/README.md +2 -0
- package/dist/action.d.ts +2 -2
- package/dist/action.js +1 -2
- package/dist/adapter/index.d.ts +2 -0
- package/dist/adapter/physical-device.d.ts +2 -0
- package/dist/adapter/physical-device.js +153 -0
- package/dist/adapter/types.d.ts +127 -0
- package/dist/bundler-shared.d.ts +11 -0
- package/dist/bundler-shared.js +11 -0
- package/dist/context/event-bus.d.ts +1 -1
- package/dist/context/event-bus.js +1 -1
- package/dist/context/touchstrip-context.d.ts +2 -0
- package/dist/context/touchstrip-context.js +5 -0
- package/dist/devtools/bridge.d.ts +35 -7
- package/dist/devtools/bridge.js +152 -46
- package/dist/devtools/highlight.d.ts +5 -0
- package/dist/devtools/highlight.js +107 -57
- package/dist/devtools/index.js +6 -0
- package/dist/devtools/observers/lifecycle.d.ts +4 -4
- package/dist/devtools/server.d.ts +6 -1
- package/dist/devtools/server.js +6 -1
- package/dist/devtools/types.d.ts +50 -6
- package/dist/font-inline.d.ts +5 -1
- package/dist/font-inline.js +8 -3
- package/dist/hooks/animation.d.ts +154 -0
- package/dist/hooks/animation.js +381 -0
- package/dist/hooks/events.js +2 -6
- package/dist/hooks/sdk.js +11 -11
- package/dist/hooks/touchstrip.d.ts +6 -0
- package/dist/hooks/touchstrip.js +37 -0
- package/dist/hooks/utility.js +3 -2
- package/dist/index.d.ts +9 -2
- package/dist/index.js +4 -2
- package/dist/manifest-codegen.d.ts +38 -0
- package/dist/manifest-codegen.js +110 -0
- package/dist/plugin.js +86 -106
- package/dist/reconciler/host-config.js +19 -1
- package/dist/reconciler/vnode.d.ts +26 -2
- package/dist/reconciler/vnode.js +40 -10
- package/dist/render/buffer-pool.d.ts +19 -0
- package/dist/render/buffer-pool.js +51 -0
- package/dist/render/cache.d.ts +29 -0
- package/dist/render/cache.js +137 -5
- package/dist/render/image-cache.d.ts +54 -0
- package/dist/render/image-cache.js +144 -0
- package/dist/render/metrics.d.ts +57 -0
- package/dist/render/metrics.js +98 -0
- package/dist/render/pipeline.d.ts +36 -1
- package/dist/render/pipeline.js +304 -34
- package/dist/render/png.d.ts +1 -1
- package/dist/render/png.js +26 -11
- package/dist/render/render-pool.d.ts +24 -0
- package/dist/render/render-pool.js +130 -0
- package/dist/render/svg.d.ts +7 -0
- package/dist/render/svg.js +139 -0
- package/dist/render/worker.d.ts +1 -0
- package/dist/rollup.d.ts +23 -9
- package/dist/rollup.js +24 -9
- package/dist/roots/registry.d.ts +9 -11
- package/dist/roots/registry.js +39 -42
- package/dist/roots/root.d.ts +9 -6
- package/dist/roots/root.js +52 -29
- package/dist/roots/settings-equality.d.ts +5 -0
- package/dist/roots/settings-equality.js +24 -0
- package/dist/roots/{touchbar-root.d.ts → touchstrip-root.d.ts} +30 -8
- package/dist/roots/touchstrip-root.js +263 -0
- package/dist/types.d.ts +73 -23
- package/dist/vite.d.ts +22 -8
- package/dist/vite.js +24 -8
- package/package.json +7 -4
- package/dist/context/touchbar-context.d.ts +0 -2
- package/dist/context/touchbar-context.js +0 -5
- package/dist/hooks/touchbar.d.ts +0 -6
- package/dist/hooks/touchbar.js +0 -37
- package/dist/roots/touchbar-root.js +0 -175
package/dist/types.d.ts
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
import { ComponentType, ReactNode } from 'react';
|
|
2
|
-
import { Action, Controller, Coordinates, DeviceType, DialAction, KeyAction, Size } from '@elgato/streamdeck';
|
|
3
2
|
import { JsonObject, JsonValue } from '@elgato/utils';
|
|
3
|
+
import { AdapterActionHandle, StreamDeckAdapter } from './adapter/types';
|
|
4
|
+
/** Controller surface type. */
|
|
5
|
+
export type Controller = "Keypad" | "Encoder";
|
|
6
|
+
/** Grid coordinates for a key or encoder on a device. */
|
|
7
|
+
export interface Coordinates {
|
|
8
|
+
column: number;
|
|
9
|
+
row: number;
|
|
10
|
+
}
|
|
11
|
+
/** Device grid size (number of key columns and rows). */
|
|
12
|
+
export interface Size {
|
|
13
|
+
columns: number;
|
|
14
|
+
rows: number;
|
|
15
|
+
}
|
|
4
16
|
export interface FontConfig {
|
|
5
17
|
name: string;
|
|
6
18
|
data: ArrayBuffer | Buffer;
|
|
@@ -62,41 +74,80 @@ export interface TouchStripLayout {
|
|
|
62
74
|
}
|
|
63
75
|
export type EncoderLayout = string | TouchStripLayout;
|
|
64
76
|
export interface PluginConfig {
|
|
77
|
+
/** Stream Deck adapter. Defaults to physicalDevice() (Elgato SDK). */
|
|
78
|
+
adapter?: StreamDeckAdapter;
|
|
65
79
|
fonts: FontConfig[];
|
|
66
80
|
actions: ActionDefinition[];
|
|
67
81
|
wrapper?: WrapperComponent;
|
|
68
|
-
renderDebounceMs?: number;
|
|
69
82
|
imageFormat?: "png" | "webp";
|
|
70
83
|
caching?: boolean;
|
|
71
84
|
devicePixelRatio?: number;
|
|
72
85
|
onActionError?: (uuid: string, actionId: string, error: Error) => void;
|
|
73
86
|
/** Enable the devtools WebSocket server. Browser devtools UI discovers it via port scanning. @default false */
|
|
74
87
|
devtools?: boolean;
|
|
88
|
+
/** Enable performance diagnostics (render counters, duplicate detection, depth warnings). Defaults to `process.env.NODE_ENV !== 'production'`. */
|
|
89
|
+
debug?: boolean;
|
|
90
|
+
/** Maximum image cache size in bytes for key/dial renders. Set to 0 to disable. @default 16777216 (16 MB) */
|
|
91
|
+
imageCacheMaxBytes?: number;
|
|
92
|
+
/** Maximum TouchStrip raw buffer cache size in bytes. Set to 0 to disable. @default 8388608 (8 MB) */
|
|
93
|
+
touchStripCacheMaxBytes?: number;
|
|
94
|
+
/** Offload Takumi rendering to a worker thread. Set to false to disable. @default true */
|
|
95
|
+
useWorker?: boolean;
|
|
75
96
|
}
|
|
76
97
|
export interface Plugin {
|
|
77
98
|
connect(): Promise<void>;
|
|
78
99
|
}
|
|
100
|
+
export interface ManifestActions {
|
|
101
|
+
}
|
|
102
|
+
/** Action UUID — a union of manifest UUIDs when available, plain `string` otherwise. */
|
|
103
|
+
export type ActionUUID = [keyof ManifestActions] extends [never] ? string : Extract<keyof ManifestActions, string>;
|
|
104
|
+
type HasController<UUID extends string, C extends string> = UUID extends keyof ManifestActions ? ManifestActions[UUID] extends {
|
|
105
|
+
controllers: readonly (infer Item)[];
|
|
106
|
+
} ? C extends Item ? true : false : false : false;
|
|
107
|
+
type KeySurface<UUID extends string> = HasController<UUID, "Keypad"> extends true ? {
|
|
108
|
+
key: ComponentType;
|
|
109
|
+
} : {
|
|
110
|
+
key?: ComponentType;
|
|
111
|
+
};
|
|
112
|
+
type EncoderSurface<UUID extends string> = HasController<UUID, "Encoder"> extends true ? {
|
|
113
|
+
dial: ComponentType;
|
|
114
|
+
touchStrip?: ComponentType;
|
|
115
|
+
} | {
|
|
116
|
+
dial?: ComponentType;
|
|
117
|
+
touchStrip: ComponentType;
|
|
118
|
+
} : {
|
|
119
|
+
dial?: ComponentType;
|
|
120
|
+
touchStrip?: ComponentType;
|
|
121
|
+
};
|
|
79
122
|
export interface ActionConfig<S extends JsonObject = JsonObject> {
|
|
80
123
|
uuid: string;
|
|
81
124
|
key?: ComponentType;
|
|
82
125
|
dial?: ComponentType;
|
|
83
|
-
/** Full-strip
|
|
84
|
-
|
|
85
|
-
/** Target frame rate for the touchbar animation loop and render pipeline. Controls both `useTick` cadence (via `useTouchBar().fps`) and the render debounce. @default 60 */
|
|
86
|
-
touchBarFPS?: number;
|
|
126
|
+
/** Full-strip TouchStrip component. When set, replaces per-encoder `dial` display with a single shared React tree that spans the entire touch strip. */
|
|
127
|
+
touchStrip?: ComponentType;
|
|
87
128
|
/** Encoder feedback layout. Defaults to a full-width `pixmap` canvas layout. Custom layouts should include a `pixmap` item keyed as `canvas`. */
|
|
88
129
|
dialLayout?: EncoderLayout;
|
|
89
130
|
wrapper?: WrapperComponent;
|
|
90
131
|
defaultSettings?: Partial<S>;
|
|
91
132
|
}
|
|
133
|
+
/** Resolved action config shape. When `ManifestActions` is populated (via `streamdeck-env.d.ts`), this becomes a mapped type that iterates over every UUID in the manifest and produces a discriminated union. Each member intersects `KeySurface<UUID>` and `EncoderSurface<UUID>` to enforce controller-specific requirements. When `ManifestActions` is empty, it falls back to the permissive `ActionConfig<S>`. */
|
|
134
|
+
export type ActionConfigInput<S extends JsonObject = JsonObject> = [keyof ManifestActions] extends [
|
|
135
|
+
never
|
|
136
|
+
] ? ActionConfig<S> : {
|
|
137
|
+
[UUID in ActionUUID]: {
|
|
138
|
+
uuid: UUID;
|
|
139
|
+
/** Encoder feedback layout. Defaults to a full-width `pixmap` canvas layout. Custom layouts should include a `pixmap` item keyed as `canvas`. */
|
|
140
|
+
dialLayout?: EncoderLayout;
|
|
141
|
+
wrapper?: WrapperComponent;
|
|
142
|
+
defaultSettings?: Partial<S>;
|
|
143
|
+
} & KeySurface<UUID> & EncoderSurface<UUID>;
|
|
144
|
+
}[ActionUUID];
|
|
92
145
|
export interface ActionDefinition<S extends JsonObject = JsonObject> {
|
|
93
146
|
uuid: string;
|
|
94
147
|
key?: ComponentType;
|
|
95
148
|
dial?: ComponentType;
|
|
96
|
-
/** Full-strip
|
|
97
|
-
|
|
98
|
-
/** Target frame rate for the touchbar animation loop and render pipeline. @default 60 */
|
|
99
|
-
touchBarFPS?: number;
|
|
149
|
+
/** Full-strip TouchStrip component. When set, replaces per-encoder `dial` display with a single shared React tree that spans the entire touch strip. */
|
|
150
|
+
touchStrip?: ComponentType;
|
|
100
151
|
/** Encoder feedback layout. Defaults to a full-width `pixmap` canvas layout. Custom layouts should include a `pixmap` item keyed as `canvas`. */
|
|
101
152
|
dialLayout?: EncoderLayout;
|
|
102
153
|
wrapper?: WrapperComponent;
|
|
@@ -104,7 +155,8 @@ export interface ActionDefinition<S extends JsonObject = JsonObject> {
|
|
|
104
155
|
}
|
|
105
156
|
export interface DeviceInfo {
|
|
106
157
|
id: string;
|
|
107
|
-
type
|
|
158
|
+
/** Numeric device type matching Elgato DeviceType enum values (e.g. 7 = StreamDeckPlus). */
|
|
159
|
+
type: number;
|
|
108
160
|
size: Size;
|
|
109
161
|
name: string;
|
|
110
162
|
}
|
|
@@ -121,8 +173,8 @@ export interface CanvasInfo {
|
|
|
121
173
|
type: "key" | "dial" | "touch";
|
|
122
174
|
}
|
|
123
175
|
export interface StreamDeckAccess {
|
|
124
|
-
action:
|
|
125
|
-
|
|
176
|
+
action: AdapterActionHandle;
|
|
177
|
+
adapter: StreamDeckAdapter;
|
|
126
178
|
}
|
|
127
179
|
export interface KeyDownPayload {
|
|
128
180
|
settings: JsonObject;
|
|
@@ -161,7 +213,7 @@ export interface DialHints {
|
|
|
161
213
|
touch?: string;
|
|
162
214
|
longTouch?: string;
|
|
163
215
|
}
|
|
164
|
-
export interface
|
|
216
|
+
export interface TouchStripInfo {
|
|
165
217
|
/** Full render width in pixels (e.g., 800 for 4 encoders). */
|
|
166
218
|
width: number;
|
|
167
219
|
/** Strip height in pixels (always 100). */
|
|
@@ -170,22 +222,20 @@ export interface TouchBarInfo {
|
|
|
170
222
|
columns: number[];
|
|
171
223
|
/** Width of each encoder segment in pixels (always 200). */
|
|
172
224
|
segmentWidth: number;
|
|
173
|
-
/** Target frame rate for the animation loop. Pass to `useTick` for matched cadence. */
|
|
174
|
-
fps: number;
|
|
175
225
|
}
|
|
176
|
-
export interface
|
|
226
|
+
export interface TouchStripTapPayload {
|
|
177
227
|
/** Absolute tap position across the full strip width. */
|
|
178
228
|
tapPos: [x: number, y: number];
|
|
179
229
|
hold: boolean;
|
|
180
230
|
/** The encoder column that was touched. */
|
|
181
231
|
column: number;
|
|
182
232
|
}
|
|
183
|
-
export interface
|
|
233
|
+
export interface TouchStripDialRotatePayload {
|
|
184
234
|
column: number;
|
|
185
235
|
ticks: number;
|
|
186
236
|
pressed: boolean;
|
|
187
237
|
}
|
|
188
|
-
export interface
|
|
238
|
+
export interface TouchStripDialPressPayload {
|
|
189
239
|
column: number;
|
|
190
240
|
}
|
|
191
241
|
export interface EventMap {
|
|
@@ -205,9 +255,9 @@ export interface EventMap {
|
|
|
205
255
|
title: string;
|
|
206
256
|
settings: JsonObject;
|
|
207
257
|
};
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
258
|
+
touchStripTap: TouchStripTapPayload;
|
|
259
|
+
touchStripDialRotate: TouchStripDialRotatePayload;
|
|
260
|
+
touchStripDialDown: TouchStripDialPressPayload;
|
|
261
|
+
touchStripDialUp: TouchStripDialPressPayload;
|
|
212
262
|
}
|
|
213
263
|
export {};
|
package/dist/vite.d.ts
CHANGED
|
@@ -10,17 +10,31 @@ export interface StreamDeckReactOptions extends StreamDeckTargetOptions {
|
|
|
10
10
|
* each successful build.
|
|
11
11
|
*/
|
|
12
12
|
uuid?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Path to the plugin `manifest.json`. When omitted, the plugin
|
|
15
|
+
* auto-detects by scanning the project root for a `*.sdPlugin/manifest.json`.
|
|
16
|
+
*
|
|
17
|
+
* Set to `false` to disable manifest type generation entirely.
|
|
18
|
+
*/
|
|
19
|
+
manifest?: string | false;
|
|
13
20
|
}
|
|
14
21
|
/**
|
|
15
22
|
* Vite plugin for Stream Deck React projects.
|
|
16
23
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
24
|
+
* Responsibilities mapped to Vite lifecycle hooks:
|
|
25
|
+
*
|
|
26
|
+
* configResolved → detect dev/production mode, set strip flags
|
|
27
|
+
* buildStart → generate streamdeck-env.d.ts from manifest.json
|
|
28
|
+
* resolveId → redirect devtools imports (production) + font imports
|
|
29
|
+
* load → return noop devtools stub + inline font as base64 Buffer
|
|
30
|
+
* writeBundle → copy native .node bindings to output directory
|
|
31
|
+
* closeBundle → restart Stream Deck plugin (optional, via CLI)
|
|
32
|
+
*
|
|
33
|
+
* Font inlining:
|
|
34
|
+
* `.ttf`, `.otf`, `.woff`, `.woff2` imports are resolved to absolute
|
|
35
|
+
* paths and loaded as synthetic ES modules:
|
|
36
|
+
* `export default Buffer.from("<base64>", "base64");`
|
|
37
|
+
* This eliminates runtime filesystem access in the sandboxed
|
|
38
|
+
* Stream Deck Node.js environment.
|
|
25
39
|
*/
|
|
26
40
|
export declare function streamDeckReact(options?: StreamDeckReactOptions): Plugin;
|
package/dist/vite.js
CHANGED
|
@@ -1,19 +1,27 @@
|
|
|
1
1
|
import { NOOP_DEVTOOLS_CODE, NOOP_DEVTOOLS_ID, copyNativeBindings, isLibraryDevtoolsImport, shouldStripDevtools } from "./bundler-shared.js";
|
|
2
2
|
import { loadFont, resolveFontId } from "./font-inline.js";
|
|
3
|
+
import { generateManifestTypes } from "./manifest-codegen.js";
|
|
3
4
|
import { resolve } from "node:path";
|
|
4
5
|
import { exec } from "node:child_process";
|
|
5
6
|
//#region src/vite.ts
|
|
6
7
|
/**
|
|
7
8
|
* Vite plugin for Stream Deck React projects.
|
|
8
9
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
10
|
+
* Responsibilities mapped to Vite lifecycle hooks:
|
|
11
|
+
*
|
|
12
|
+
* configResolved → detect dev/production mode, set strip flags
|
|
13
|
+
* buildStart → generate streamdeck-env.d.ts from manifest.json
|
|
14
|
+
* resolveId → redirect devtools imports (production) + font imports
|
|
15
|
+
* load → return noop devtools stub + inline font as base64 Buffer
|
|
16
|
+
* writeBundle → copy native .node bindings to output directory
|
|
17
|
+
* closeBundle → restart Stream Deck plugin (optional, via CLI)
|
|
18
|
+
*
|
|
19
|
+
* Font inlining:
|
|
20
|
+
* `.ttf`, `.otf`, `.woff`, `.woff2` imports are resolved to absolute
|
|
21
|
+
* paths and loaded as synthetic ES modules:
|
|
22
|
+
* `export default Buffer.from("<base64>", "base64");`
|
|
23
|
+
* This eliminates runtime filesystem access in the sandboxed
|
|
24
|
+
* Stream Deck Node.js environment.
|
|
17
25
|
*/
|
|
18
26
|
function streamDeckReact(options = {}) {
|
|
19
27
|
let resolvedConfig;
|
|
@@ -29,6 +37,14 @@ function streamDeckReact(options = {}) {
|
|
|
29
37
|
isDevelopment = isWatch || process.env.NODE_ENV === "development";
|
|
30
38
|
stripDevtools = shouldStripDevtools(isWatch);
|
|
31
39
|
},
|
|
40
|
+
buildStart() {
|
|
41
|
+
const warn = (msg) => resolvedConfig.logger.warn(msg);
|
|
42
|
+
const result = generateManifestTypes(resolvedConfig.root, options.manifest, warn);
|
|
43
|
+
if (result) {
|
|
44
|
+
this.addWatchFile(result.manifestPath);
|
|
45
|
+
if (result.written) resolvedConfig.logger.info("[@fcannizzaro/streamdeck-react] Generated src/streamdeck-env.d.ts");
|
|
46
|
+
}
|
|
47
|
+
},
|
|
32
48
|
resolveId(source, importer) {
|
|
33
49
|
if (stripDevtools && isLibraryDevtoolsImport(source, importer)) return NOOP_DEVTOOLS_ID;
|
|
34
50
|
return resolveFontId(source, importer);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fcannizzaro/streamdeck-react",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.12",
|
|
4
4
|
"description": "Build Stream Deck plugins with React — render components directly to keys, dials, and touch screens",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"elgato",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"bugs": {
|
|
14
14
|
"url": "https://github.com/fcannizzaro/streamdeck-react/issues"
|
|
15
15
|
},
|
|
16
|
-
"license": "
|
|
16
|
+
"license": "Apache-2.0",
|
|
17
17
|
"author": {
|
|
18
18
|
"name": "Francesco Saverio Cannizzaro",
|
|
19
19
|
"url": "https://github.com/fcannizzaro"
|
|
@@ -56,11 +56,10 @@
|
|
|
56
56
|
},
|
|
57
57
|
"scripts": {
|
|
58
58
|
"build": "vite build",
|
|
59
|
-
"
|
|
59
|
+
"typecheck": "tsc --noEmit",
|
|
60
60
|
"test": "bun test ./src"
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
|
-
"@elgato/streamdeck": "^2.0.2",
|
|
64
63
|
"@takumi-rs/core": "^0.71.7",
|
|
65
64
|
"@takumi-rs/helpers": "^0.71.7",
|
|
66
65
|
"react-reconciler": "^0.33.0"
|
|
@@ -76,12 +75,16 @@
|
|
|
76
75
|
"vite-plugin-dts": "^4.5.4"
|
|
77
76
|
},
|
|
78
77
|
"peerDependencies": {
|
|
78
|
+
"@elgato/streamdeck": "^2.0.2",
|
|
79
79
|
"react": "^18.0.0 || ^19.0.0",
|
|
80
80
|
"rollup": "^4.0.0",
|
|
81
81
|
"typescript": "^5",
|
|
82
82
|
"vite": "^7.0.0 || ^8.0.0"
|
|
83
83
|
},
|
|
84
84
|
"peerDependenciesMeta": {
|
|
85
|
+
"@elgato/streamdeck": {
|
|
86
|
+
"optional": true
|
|
87
|
+
},
|
|
85
88
|
"rollup": {
|
|
86
89
|
"optional": true
|
|
87
90
|
},
|
package/dist/hooks/touchbar.d.ts
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import { TouchBarInfo, TouchBarTapPayload, TouchBarDialRotatePayload, TouchBarDialPressPayload } from '../types';
|
|
2
|
-
export declare function useTouchBar(): TouchBarInfo;
|
|
3
|
-
export declare function useTouchBarTap(callback: (payload: TouchBarTapPayload) => void): void;
|
|
4
|
-
export declare function useTouchBarDialRotate(callback: (payload: TouchBarDialRotatePayload) => void): void;
|
|
5
|
-
export declare function useTouchBarDialDown(callback: (payload: TouchBarDialPressPayload) => void): void;
|
|
6
|
-
export declare function useTouchBarDialUp(callback: (payload: TouchBarDialPressPayload) => void): void;
|
package/dist/hooks/touchbar.js
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { EventBusContext } from "../context/providers.js";
|
|
2
|
-
import { TouchBarContext } from "../context/touchbar-context.js";
|
|
3
|
-
import { useCallbackRef } from "./internal/useCallbackRef.js";
|
|
4
|
-
import { useContext, useEffect } from "react";
|
|
5
|
-
//#region src/hooks/touchbar.ts
|
|
6
|
-
function useTouchBarEvent(event, callback) {
|
|
7
|
-
const bus = useContext(EventBusContext);
|
|
8
|
-
const callbackRef = useCallbackRef(callback);
|
|
9
|
-
useEffect(() => {
|
|
10
|
-
const handler = (payload) => {
|
|
11
|
-
callbackRef.current(payload);
|
|
12
|
-
};
|
|
13
|
-
bus.on(event, handler);
|
|
14
|
-
return () => bus.off(event, handler);
|
|
15
|
-
}, [
|
|
16
|
-
bus,
|
|
17
|
-
callbackRef,
|
|
18
|
-
event
|
|
19
|
-
]);
|
|
20
|
-
}
|
|
21
|
-
function useTouchBar() {
|
|
22
|
-
return useContext(TouchBarContext);
|
|
23
|
-
}
|
|
24
|
-
function useTouchBarTap(callback) {
|
|
25
|
-
useTouchBarEvent("touchBarTap", callback);
|
|
26
|
-
}
|
|
27
|
-
function useTouchBarDialRotate(callback) {
|
|
28
|
-
useTouchBarEvent("touchBarDialRotate", callback);
|
|
29
|
-
}
|
|
30
|
-
function useTouchBarDialDown(callback) {
|
|
31
|
-
useTouchBarEvent("touchBarDialDown", callback);
|
|
32
|
-
}
|
|
33
|
-
function useTouchBarDialUp(callback) {
|
|
34
|
-
useTouchBarEvent("touchBarDialUp", callback);
|
|
35
|
-
}
|
|
36
|
-
//#endregion
|
|
37
|
-
export { useTouchBar, useTouchBarDialDown, useTouchBarDialRotate, useTouchBarDialUp, useTouchBarTap };
|
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
import { createVContainer } from "../reconciler/vnode.js";
|
|
2
|
-
import { reconciler } from "../reconciler/renderer.js";
|
|
3
|
-
import { renderToRaw, sliceToDataUri } from "../render/pipeline.js";
|
|
4
|
-
import { EventBus } from "../context/event-bus.js";
|
|
5
|
-
import { DeviceContext, EventBusContext, GlobalSettingsContext } from "../context/providers.js";
|
|
6
|
-
import { TouchBarContext } from "../context/touchbar-context.js";
|
|
7
|
-
import { createElement } from "react";
|
|
8
|
-
//#region src/roots/touchbar-root.ts
|
|
9
|
-
var SEGMENT_WIDTH = 200;
|
|
10
|
-
var SEGMENT_HEIGHT = 100;
|
|
11
|
-
var DEFAULT_TOUCHBAR_FPS = 60;
|
|
12
|
-
var TouchBarRoot = class {
|
|
13
|
-
eventBus = new EventBus();
|
|
14
|
-
container;
|
|
15
|
-
fiberRoot;
|
|
16
|
-
columns = /* @__PURE__ */ new Map();
|
|
17
|
-
globalSettings;
|
|
18
|
-
setGlobalSettingsFn;
|
|
19
|
-
renderDebounceMs;
|
|
20
|
-
renderConfig;
|
|
21
|
-
deviceInfo;
|
|
22
|
-
disposed = false;
|
|
23
|
-
fps;
|
|
24
|
-
pluginWrapper;
|
|
25
|
-
/** Last rendered per-column data URIs. Used by devtools snapshots. */
|
|
26
|
-
lastSegmentUris = /* @__PURE__ */ new Map();
|
|
27
|
-
/** Exposes the VContainer for devtools inspection. */
|
|
28
|
-
get vcontainer() {
|
|
29
|
-
return this.container;
|
|
30
|
-
}
|
|
31
|
-
/** Sorted column numbers for devtools observer. */
|
|
32
|
-
get columnNumbers() {
|
|
33
|
-
return [...this.columns.keys()].sort((a, b) => a - b);
|
|
34
|
-
}
|
|
35
|
-
/** Column → actionId map for devtools observer. */
|
|
36
|
-
get columnActionMap() {
|
|
37
|
-
const map = /* @__PURE__ */ new Map();
|
|
38
|
-
for (const [col, entry] of this.columns) map.set(col, entry.actionId);
|
|
39
|
-
return map;
|
|
40
|
-
}
|
|
41
|
-
globalSettingsValue;
|
|
42
|
-
touchBarValue;
|
|
43
|
-
constructor(component, deviceInfo, initialGlobalSettings, renderConfig, renderDebounceMs, onGlobalSettingsChange, pluginWrapper, touchBarFPS) {
|
|
44
|
-
this.component = component;
|
|
45
|
-
this.deviceInfo = deviceInfo;
|
|
46
|
-
this.globalSettings = { ...initialGlobalSettings };
|
|
47
|
-
this.renderConfig = renderConfig;
|
|
48
|
-
this.fps = touchBarFPS ?? DEFAULT_TOUCHBAR_FPS;
|
|
49
|
-
this.renderDebounceMs = touchBarFPS != null ? Math.max(1, Math.round(1e3 / touchBarFPS)) : renderDebounceMs;
|
|
50
|
-
this.pluginWrapper = pluginWrapper;
|
|
51
|
-
this.setGlobalSettingsFn = (partial) => {
|
|
52
|
-
this.globalSettings = {
|
|
53
|
-
...this.globalSettings,
|
|
54
|
-
...partial
|
|
55
|
-
};
|
|
56
|
-
this.globalSettingsValue = {
|
|
57
|
-
settings: this.globalSettings,
|
|
58
|
-
setSettings: this.setGlobalSettingsFn
|
|
59
|
-
};
|
|
60
|
-
onGlobalSettingsChange(this.globalSettings);
|
|
61
|
-
this.scheduleRerender();
|
|
62
|
-
};
|
|
63
|
-
this.globalSettingsValue = {
|
|
64
|
-
settings: this.globalSettings,
|
|
65
|
-
setSettings: this.setGlobalSettingsFn
|
|
66
|
-
};
|
|
67
|
-
this.touchBarValue = {
|
|
68
|
-
width: 0,
|
|
69
|
-
height: SEGMENT_HEIGHT,
|
|
70
|
-
columns: [],
|
|
71
|
-
segmentWidth: SEGMENT_WIDTH,
|
|
72
|
-
fps: this.fps
|
|
73
|
-
};
|
|
74
|
-
this.container = createVContainer(() => {
|
|
75
|
-
this.flush();
|
|
76
|
-
});
|
|
77
|
-
this.fiberRoot = reconciler.createContainer(this.container, 0, null, false, null, "", (err) => {
|
|
78
|
-
console.error("[@fcannizzaro/streamdeck-react] TouchBar uncaught error:", err);
|
|
79
|
-
}, (err) => {
|
|
80
|
-
console.error("[@fcannizzaro/streamdeck-react] TouchBar caught error:", err);
|
|
81
|
-
}, (err) => {
|
|
82
|
-
console.error("[@fcannizzaro/streamdeck-react] TouchBar recoverable error:", err);
|
|
83
|
-
}, () => {});
|
|
84
|
-
this.eventBus.ownerId = `touchbar:${deviceInfo.id}`;
|
|
85
|
-
}
|
|
86
|
-
addColumn(column, actionId, sdkAction) {
|
|
87
|
-
this.columns.set(column, {
|
|
88
|
-
actionId,
|
|
89
|
-
sdkAction
|
|
90
|
-
});
|
|
91
|
-
this.updateTouchBarInfo();
|
|
92
|
-
this.scheduleRerender();
|
|
93
|
-
}
|
|
94
|
-
removeColumn(column) {
|
|
95
|
-
this.columns.delete(column);
|
|
96
|
-
if (this.columns.size === 0) return;
|
|
97
|
-
this.updateTouchBarInfo();
|
|
98
|
-
this.scheduleRerender();
|
|
99
|
-
}
|
|
100
|
-
get isEmpty() {
|
|
101
|
-
return this.columns.size === 0;
|
|
102
|
-
}
|
|
103
|
-
findColumnByActionId(actionId) {
|
|
104
|
-
for (const [column, entry] of this.columns) if (entry.actionId === actionId) return column;
|
|
105
|
-
}
|
|
106
|
-
updateTouchBarInfo() {
|
|
107
|
-
const sortedColumns = [...this.columns.keys()].sort((a, b) => a - b);
|
|
108
|
-
this.touchBarValue = {
|
|
109
|
-
width: (sortedColumns.length > 0 ? sortedColumns[sortedColumns.length - 1] + 1 : 0) * SEGMENT_WIDTH,
|
|
110
|
-
height: SEGMENT_HEIGHT,
|
|
111
|
-
columns: sortedColumns,
|
|
112
|
-
segmentWidth: SEGMENT_WIDTH,
|
|
113
|
-
fps: this.fps
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
render() {
|
|
117
|
-
if (this.disposed) return;
|
|
118
|
-
const element = this.buildTree();
|
|
119
|
-
reconciler.updateContainer(element, this.fiberRoot, null, () => {});
|
|
120
|
-
}
|
|
121
|
-
buildTree() {
|
|
122
|
-
let child = createElement(this.component);
|
|
123
|
-
if (this.pluginWrapper) child = createElement(this.pluginWrapper, null, child);
|
|
124
|
-
return createElement(TouchBarContext.Provider, { value: this.touchBarValue }, createElement(DeviceContext.Provider, { value: this.deviceInfo }, createElement(EventBusContext.Provider, { value: this.eventBus }, createElement(GlobalSettingsContext.Provider, { value: this.globalSettingsValue }, child))));
|
|
125
|
-
}
|
|
126
|
-
scheduleRerender() {
|
|
127
|
-
if (this.disposed) return;
|
|
128
|
-
this.render();
|
|
129
|
-
}
|
|
130
|
-
async flush() {
|
|
131
|
-
if (this.disposed) return;
|
|
132
|
-
if (this.renderDebounceMs > 0 && this.container.renderTimer !== null) clearTimeout(this.container.renderTimer);
|
|
133
|
-
if (this.renderDebounceMs > 0) this.container.renderTimer = setTimeout(async () => {
|
|
134
|
-
this.container.renderTimer = null;
|
|
135
|
-
await this.doFlush();
|
|
136
|
-
}, this.renderDebounceMs);
|
|
137
|
-
else await this.doFlush();
|
|
138
|
-
}
|
|
139
|
-
async doFlush() {
|
|
140
|
-
if (this.disposed || this.columns.size === 0) return;
|
|
141
|
-
try {
|
|
142
|
-
const width = this.touchBarValue.width;
|
|
143
|
-
if (width === 0) return;
|
|
144
|
-
const result = await renderToRaw(this.container, width, SEGMENT_HEIGHT, this.renderConfig);
|
|
145
|
-
if (result === null || this.disposed) return;
|
|
146
|
-
const feedbackPromises = [];
|
|
147
|
-
for (const [column, entry] of this.columns) {
|
|
148
|
-
const sliceUri = sliceToDataUri(result.buffer, result.width, result.height, column, SEGMENT_WIDTH, SEGMENT_HEIGHT);
|
|
149
|
-
this.lastSegmentUris.set(column, sliceUri);
|
|
150
|
-
feedbackPromises.push(entry.sdkAction.setFeedback({ canvas: sliceUri }));
|
|
151
|
-
}
|
|
152
|
-
await Promise.all(feedbackPromises);
|
|
153
|
-
} catch (err) {
|
|
154
|
-
console.error("[@fcannizzaro/streamdeck-react] TouchBar render error:", err);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
updateGlobalSettings(settings) {
|
|
158
|
-
this.globalSettings = { ...settings };
|
|
159
|
-
this.globalSettingsValue = {
|
|
160
|
-
settings: this.globalSettings,
|
|
161
|
-
setSettings: this.setGlobalSettingsFn
|
|
162
|
-
};
|
|
163
|
-
this.scheduleRerender();
|
|
164
|
-
}
|
|
165
|
-
unmount() {
|
|
166
|
-
this.disposed = true;
|
|
167
|
-
if (this.container.renderTimer !== null) clearTimeout(this.container.renderTimer);
|
|
168
|
-
this.eventBus.emit("willDisappear", void 0);
|
|
169
|
-
reconciler.updateContainer(null, this.fiberRoot, null, () => {});
|
|
170
|
-
this.eventBus.removeAllListeners();
|
|
171
|
-
this.columns.clear();
|
|
172
|
-
}
|
|
173
|
-
};
|
|
174
|
-
//#endregion
|
|
175
|
-
export { TouchBarRoot };
|