@fcannizzaro/streamdeck-react 0.1.11 → 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/dist/action.js +0 -1
- 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/devtools/bridge.d.ts +2 -2
- package/dist/devtools/bridge.js +7 -8
- package/dist/devtools/highlight.d.ts +1 -2
- package/dist/devtools/highlight.js +4 -3
- package/dist/devtools/types.d.ts +5 -5
- package/dist/hooks/animation.d.ts +1 -1
- package/dist/hooks/animation.js +2 -2
- package/dist/hooks/events.js +1 -1
- package/dist/hooks/sdk.js +11 -11
- package/dist/hooks/utility.js +3 -2
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -1
- package/dist/plugin.js +69 -100
- package/dist/reconciler/vnode.d.ts +0 -2
- package/dist/reconciler/vnode.js +0 -1
- package/dist/render/cache.d.ts +5 -17
- package/dist/render/cache.js +7 -29
- package/dist/render/image-cache.d.ts +8 -7
- package/dist/render/image-cache.js +33 -17
- package/dist/render/metrics.d.ts +9 -10
- package/dist/render/metrics.js +36 -39
- package/dist/render/pipeline.d.ts +4 -14
- package/dist/render/pipeline.js +47 -111
- package/dist/render/png.d.ts +0 -9
- package/dist/render/png.js +5 -8
- package/dist/render/render-pool.d.ts +0 -2
- package/dist/render/render-pool.js +1 -12
- package/dist/roots/registry.d.ts +5 -9
- package/dist/roots/registry.js +10 -27
- package/dist/roots/root.d.ts +7 -34
- package/dist/roots/root.js +23 -90
- package/dist/roots/touchstrip-root.d.ts +6 -32
- package/dist/roots/touchstrip-root.js +61 -181
- package/dist/types.d.ts +23 -19
- package/package.json +6 -4
- package/dist/node_modules/.bun/xxhash-wasm@1.1.0/node_modules/xxhash-wasm/esm/xxhash-wasm.js +0 -3157
- package/dist/roots/flush-coordinator.d.ts +0 -18
- package/dist/roots/flush-coordinator.js +0 -38
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { clearDirtyFlags, createVContainer, isContainerDirty } from "../reconciler/vnode.js";
|
|
2
2
|
import { reconciler } from "../reconciler/renderer.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { buildTakumiChildren, measureTree, renderSegmentToDataUri, renderToRaw, sliceToDataUriAsync } from "../render/pipeline.js";
|
|
3
|
+
import { computeTouchStripSegmentCacheKey, computeTreeHash, fnv1a } from "../render/cache.js";
|
|
4
|
+
import { getTouchStripSegmentCache } from "../render/image-cache.js";
|
|
5
|
+
import { measureTree, renderToRaw, sliceToDataUri } from "../render/pipeline.js";
|
|
7
6
|
import { EventBus } from "../context/event-bus.js";
|
|
8
7
|
import { DeviceContext, EventBusContext, GlobalSettingsContext } from "../context/providers.js";
|
|
9
8
|
import { partialHasChanges, shallowEqualSettings } from "./settings-equality.js";
|
|
@@ -12,7 +11,7 @@ import { createElement } from "react";
|
|
|
12
11
|
//#region src/roots/touchstrip-root.ts
|
|
13
12
|
var SEGMENT_WIDTH = 200;
|
|
14
13
|
var SEGMENT_HEIGHT = 100;
|
|
15
|
-
var
|
|
14
|
+
var DEFAULT_TOUCH_STRIP_FPS = 30;
|
|
16
15
|
var TouchStripRoot = class TouchStripRoot {
|
|
17
16
|
eventBus = new EventBus();
|
|
18
17
|
container;
|
|
@@ -20,34 +19,15 @@ var TouchStripRoot = class TouchStripRoot {
|
|
|
20
19
|
columns = /* @__PURE__ */ new Map();
|
|
21
20
|
globalSettings;
|
|
22
21
|
setGlobalSettingsFn;
|
|
23
|
-
renderDebounceMs;
|
|
24
22
|
renderConfig;
|
|
25
23
|
deviceInfo;
|
|
26
24
|
disposed = false;
|
|
27
|
-
fps;
|
|
28
25
|
pluginWrapper;
|
|
26
|
+
renderDebounceMs = 17;
|
|
29
27
|
_renderCount = 0;
|
|
30
28
|
_lastRenderReport = 0;
|
|
31
|
-
static RENDER_WARN_THRESHOLD =
|
|
32
|
-
|
|
33
|
-
_pendingFlush = false;
|
|
34
|
-
_recentRenders = [];
|
|
35
|
-
_lastInteraction = 0;
|
|
36
|
-
static ANIMATION_WINDOW_MS = 100;
|
|
37
|
-
static ANIMATION_THRESHOLD = 2;
|
|
38
|
-
static INTERACTION_COOLDOWN_MS = 500;
|
|
39
|
-
static IDLE_THRESHOLD_MS = 2e3;
|
|
40
|
-
_lastFlushTime = 0;
|
|
41
|
-
/** Current render priority (lower = higher priority). Used by flush coordinator. */
|
|
42
|
-
get priority() {
|
|
43
|
-
const now = Date.now();
|
|
44
|
-
const cutoff = now - TouchStripRoot.ANIMATION_WINDOW_MS;
|
|
45
|
-
while (this._recentRenders.length > 0 && this._recentRenders[0] < cutoff) this._recentRenders.shift();
|
|
46
|
-
if (this._recentRenders.length > TouchStripRoot.ANIMATION_THRESHOLD) return 0;
|
|
47
|
-
if (now - this._lastInteraction < TouchStripRoot.INTERACTION_COOLDOWN_MS) return 1;
|
|
48
|
-
if (this._lastFlushTime > 0 && now - this._lastFlushTime > TouchStripRoot.IDLE_THRESHOLD_MS) return 3;
|
|
49
|
-
return 2;
|
|
50
|
-
}
|
|
29
|
+
static RENDER_WARN_THRESHOLD = 35;
|
|
30
|
+
_lastSegmentUriHash = 0;
|
|
51
31
|
/** Last rendered per-column data URIs. Used by devtools snapshots. */
|
|
52
32
|
lastSegmentUris = /* @__PURE__ */ new Map();
|
|
53
33
|
/**
|
|
@@ -93,14 +73,11 @@ var TouchStripRoot = class TouchStripRoot {
|
|
|
93
73
|
}
|
|
94
74
|
globalSettingsValue;
|
|
95
75
|
touchStripValue;
|
|
96
|
-
constructor(component, deviceInfo, initialGlobalSettings, renderConfig,
|
|
76
|
+
constructor(component, deviceInfo, initialGlobalSettings, renderConfig, onGlobalSettingsChange, pluginWrapper) {
|
|
97
77
|
this.component = component;
|
|
98
|
-
this.flushCoordinator = flushCoordinator;
|
|
99
78
|
this.deviceInfo = deviceInfo;
|
|
100
79
|
this.globalSettings = { ...initialGlobalSettings };
|
|
101
80
|
this.renderConfig = renderConfig;
|
|
102
|
-
this.fps = touchStripFPS ?? DEFAULT_TOUCHSTRIP_FPS;
|
|
103
|
-
this.renderDebounceMs = touchStripFPS != null ? Math.max(1, Math.round(1e3 / touchStripFPS)) : renderDebounceMs;
|
|
104
81
|
this.pluginWrapper = pluginWrapper;
|
|
105
82
|
this.setGlobalSettingsFn = (partial) => {
|
|
106
83
|
const hasChanges = partialHasChanges(this.globalSettings, partial);
|
|
@@ -125,8 +102,7 @@ var TouchStripRoot = class TouchStripRoot {
|
|
|
125
102
|
width: 0,
|
|
126
103
|
height: SEGMENT_HEIGHT,
|
|
127
104
|
columns: [],
|
|
128
|
-
segmentWidth: SEGMENT_WIDTH
|
|
129
|
-
fps: this.fps
|
|
105
|
+
segmentWidth: SEGMENT_WIDTH
|
|
130
106
|
};
|
|
131
107
|
this.container = createVContainer(() => {
|
|
132
108
|
this.flush();
|
|
@@ -138,7 +114,7 @@ var TouchStripRoot = class TouchStripRoot {
|
|
|
138
114
|
}, (err) => {
|
|
139
115
|
console.error("[@fcannizzaro/streamdeck-react] TouchStrip recoverable error:", err);
|
|
140
116
|
}, () => {});
|
|
141
|
-
this.eventBus.ownerId = `
|
|
117
|
+
this.eventBus.ownerId = `touchStrip:${deviceInfo.id}`;
|
|
142
118
|
}
|
|
143
119
|
addColumn(column, actionId, sdkAction) {
|
|
144
120
|
this.columns.set(column, {
|
|
@@ -166,8 +142,7 @@ var TouchStripRoot = class TouchStripRoot {
|
|
|
166
142
|
width: (sortedColumns.length > 0 ? sortedColumns[sortedColumns.length - 1] + 1 : 0) * SEGMENT_WIDTH,
|
|
167
143
|
height: SEGMENT_HEIGHT,
|
|
168
144
|
columns: sortedColumns,
|
|
169
|
-
segmentWidth: SEGMENT_WIDTH
|
|
170
|
-
fps: this.fps
|
|
145
|
+
segmentWidth: SEGMENT_WIDTH
|
|
171
146
|
};
|
|
172
147
|
}
|
|
173
148
|
render() {
|
|
@@ -184,59 +159,21 @@ var TouchStripRoot = class TouchStripRoot {
|
|
|
184
159
|
if (this.disposed) return;
|
|
185
160
|
this.render();
|
|
186
161
|
}
|
|
187
|
-
/** Record a user interaction for adaptive debounce. */
|
|
188
|
-
markInteraction() {
|
|
189
|
-
this._lastInteraction = Date.now();
|
|
190
|
-
}
|
|
191
|
-
get effectiveDebounceMs() {
|
|
192
|
-
const now = Date.now();
|
|
193
|
-
const cutoff = now - TouchStripRoot.ANIMATION_WINDOW_MS;
|
|
194
|
-
while (this._recentRenders.length > 0 && this._recentRenders[0] < cutoff) this._recentRenders.shift();
|
|
195
|
-
if (this._recentRenders.length > TouchStripRoot.ANIMATION_THRESHOLD) return 0;
|
|
196
|
-
if (now - this._lastInteraction < TouchStripRoot.INTERACTION_COOLDOWN_MS) return Math.min(this.renderDebounceMs, 16);
|
|
197
|
-
return this.renderDebounceMs;
|
|
198
|
-
}
|
|
199
162
|
async flush() {
|
|
200
163
|
if (this.disposed) return;
|
|
201
|
-
if (this.
|
|
202
|
-
this._pendingFlush = true;
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
this._recentRenders.push(Date.now());
|
|
206
|
-
const debounce = this.effectiveDebounceMs;
|
|
207
|
-
if (debounce > 0 && this.container.renderTimer !== null) clearTimeout(this.container.renderTimer);
|
|
208
|
-
if (debounce > 0) this.container.renderTimer = setTimeout(() => {
|
|
164
|
+
if (this.renderDebounceMs > 0) this.container.renderTimer = setTimeout(async () => {
|
|
209
165
|
this.container.renderTimer = null;
|
|
210
|
-
this.
|
|
211
|
-
},
|
|
212
|
-
else this.
|
|
213
|
-
}
|
|
214
|
-
/**
|
|
215
|
-
* Submit this root for flushing. Routes through the coordinator
|
|
216
|
-
* (priority-ordered) when available, or flushes directly.
|
|
217
|
-
*/
|
|
218
|
-
submitFlush() {
|
|
219
|
-
if (this.disposed) return;
|
|
220
|
-
if (this.flushCoordinator) this.flushCoordinator.requestFlush(this);
|
|
221
|
-
else this.doFlush();
|
|
222
|
-
}
|
|
223
|
-
/**
|
|
224
|
-
* Execute the flush. Called by FlushCoordinator in priority order,
|
|
225
|
-
* or directly when no coordinator is present.
|
|
226
|
-
*/
|
|
227
|
-
async executeFlush() {
|
|
228
|
-
await this.doFlush();
|
|
166
|
+
await this.doFlush();
|
|
167
|
+
}, this.renderDebounceMs);
|
|
168
|
+
else await this.doFlush();
|
|
229
169
|
}
|
|
230
170
|
async doFlush() {
|
|
231
171
|
if (this.disposed || this.columns.size === 0) return;
|
|
232
|
-
this._rendering = true;
|
|
233
|
-
this._pendingFlush = false;
|
|
234
|
-
this._lastFlushTime = Date.now();
|
|
235
172
|
if (this.renderConfig.debug) {
|
|
236
173
|
this._renderCount++;
|
|
237
174
|
const now = Date.now();
|
|
238
175
|
if (now - this._lastRenderReport > 1e3) {
|
|
239
|
-
if (this._renderCount > TouchStripRoot.RENDER_WARN_THRESHOLD) console.warn(`[@fcannizzaro/streamdeck-react] TouchStrip rendered ${this._renderCount}x in 1s (
|
|
176
|
+
if (this._renderCount > TouchStripRoot.RENDER_WARN_THRESHOLD) console.warn(`[@fcannizzaro/streamdeck-react] TouchStrip rendered ${this._renderCount}x in 1s (max ${DEFAULT_TOUCH_STRIP_FPS}fps)`);
|
|
240
177
|
this._renderCount = 0;
|
|
241
178
|
this._lastRenderReport = now;
|
|
242
179
|
}
|
|
@@ -244,121 +181,64 @@ var TouchStripRoot = class TouchStripRoot {
|
|
|
244
181
|
try {
|
|
245
182
|
const width = this.touchStripValue.width;
|
|
246
183
|
if (width === 0) return;
|
|
247
|
-
if (this.renderConfig.
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
const
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
if (this.renderConfig.caching && this.renderConfig.touchstripCacheMaxBytes > 0) {
|
|
260
|
-
treeHash = computeTreeHash(this.container);
|
|
261
|
-
cacheKey = computeNativeTouchstripCacheKey(treeHash, width, SEGMENT_HEIGHT, this.renderConfig.devicePixelRatio, this.renderConfig.touchstripImageFormat, sortedColumns);
|
|
262
|
-
const cache = getTouchstripNativeCache(this.renderConfig.touchstripCacheMaxBytes);
|
|
263
|
-
const cached = cache.get(cacheKey);
|
|
264
|
-
if (cached !== void 0) {
|
|
265
|
-
metrics.recordCacheHit();
|
|
266
|
-
cacheHit = true;
|
|
267
|
-
for (const [column, uri] of cached) {
|
|
268
|
-
this.lastSegmentUris.set(column, uri);
|
|
269
|
-
if (!this.suppressHardwarePush) this.columns.get(column)?.sdkAction.setFeedback({ canvas: uri }).catch(() => {});
|
|
270
|
-
}
|
|
271
|
-
if (profiling) {
|
|
272
|
-
const tNow = performance.now();
|
|
273
|
-
const stats = measureTree(this.container.children);
|
|
274
|
-
this.renderConfig.onProfile({
|
|
275
|
-
vnodeToElementMs: 0,
|
|
276
|
-
fromJsxMs: 0,
|
|
277
|
-
takumiRenderMs: tNow - t0,
|
|
278
|
-
hashMs: 0,
|
|
279
|
-
base64Ms: 0,
|
|
280
|
-
totalMs: tNow - t0,
|
|
281
|
-
skipped: false,
|
|
282
|
-
cacheHit: true,
|
|
283
|
-
treeDepth: stats.depth,
|
|
284
|
-
nodeCount: stats.count,
|
|
285
|
-
cacheStats: cache.stats
|
|
286
|
-
});
|
|
287
|
-
}
|
|
288
|
-
clearDirtyFlags(this.container);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
if (!cacheHit) {
|
|
292
|
-
const takumiChildren = buildTakumiChildren(this.container);
|
|
293
|
-
const segmentResults = [];
|
|
294
|
-
const renderPromises = [...this.columns.entries()].map(async ([column]) => {
|
|
295
|
-
const sliceUri = await renderSegmentToDataUri(this.container, width, SEGMENT_HEIGHT, column, SEGMENT_WIDTH, this.renderConfig.touchstripImageFormat, this.renderConfig, takumiChildren);
|
|
296
|
-
if (sliceUri != null) {
|
|
297
|
-
segmentResults.push([column, sliceUri]);
|
|
298
|
-
this.lastSegmentUris.set(column, sliceUri);
|
|
299
|
-
}
|
|
300
|
-
});
|
|
301
|
-
await Promise.all(renderPromises);
|
|
302
|
-
segmentResults.sort((a, b) => a[0] - b[0]);
|
|
303
|
-
let skipped = false;
|
|
304
|
-
if (this.renderConfig.caching) {
|
|
305
|
-
const uriHash = fnv1a(segmentResults.map(([col, uri]) => `${col}:${uri}`).join("\0"));
|
|
306
|
-
if (uriHash === this.container.lastSvgHash) {
|
|
307
|
-
metrics.recordHashDedup();
|
|
308
|
-
skipped = true;
|
|
309
|
-
} else this.container.lastSvgHash = uriHash;
|
|
310
|
-
}
|
|
311
|
-
if (!skipped && !this.suppressHardwarePush) for (const [column, uri] of segmentResults) this.columns.get(column)?.sdkAction.setFeedback({ canvas: uri }).catch(() => {});
|
|
312
|
-
const elapsedMs = (profiling ? performance.now() : 0) - t0;
|
|
313
|
-
metrics.recordRender(elapsedMs);
|
|
314
|
-
if (this.renderConfig.caching && this.renderConfig.touchstripCacheMaxBytes > 0) {
|
|
315
|
-
if (treeHash === void 0 || cacheKey === void 0) {
|
|
316
|
-
treeHash = computeTreeHash(this.container);
|
|
317
|
-
cacheKey = computeNativeTouchstripCacheKey(treeHash, width, SEGMENT_HEIGHT, this.renderConfig.devicePixelRatio, this.renderConfig.touchstripImageFormat, sortedColumns);
|
|
318
|
-
}
|
|
319
|
-
const cache = getTouchstripNativeCache(this.renderConfig.touchstripCacheMaxBytes);
|
|
320
|
-
let byteSize = 64;
|
|
321
|
-
for (const [, uri] of segmentResults) byteSize += uri.length * 2 + 16;
|
|
322
|
-
cache.set(cacheKey, segmentResults, byteSize);
|
|
323
|
-
}
|
|
184
|
+
if (this.renderConfig.caching && !isContainerDirty(this.container)) return;
|
|
185
|
+
const sortedColumns = [...this.columns.keys()].sort((a, b) => a - b);
|
|
186
|
+
let cacheKey;
|
|
187
|
+
const profiling = this.renderConfig.onProfile != null;
|
|
188
|
+
const tPhase2 = profiling ? performance.now() : 0;
|
|
189
|
+
if (this.renderConfig.caching && this.renderConfig.touchStripCacheMaxBytes > 0) {
|
|
190
|
+
cacheKey = computeTouchStripSegmentCacheKey(computeTreeHash(this.container), width, SEGMENT_HEIGHT, this.renderConfig.devicePixelRatio, sortedColumns);
|
|
191
|
+
const cached = getTouchStripSegmentCache(this.renderConfig.touchStripCacheMaxBytes).get(cacheKey);
|
|
192
|
+
if (cached !== void 0) {
|
|
193
|
+
for (const [column, uri] of cached) this.lastSegmentUris.set(column, uri);
|
|
194
|
+
if (!this.suppressHardwarePush) for (const [column, uri] of cached) this.columns.get(column)?.sdkAction.setFeedback({ canvas: uri }).catch(() => {});
|
|
195
|
+
clearDirtyFlags(this.container);
|
|
324
196
|
if (profiling) {
|
|
197
|
+
const tEnd = performance.now();
|
|
325
198
|
const stats = measureTree(this.container.children);
|
|
326
|
-
const nativeCache = this.renderConfig.touchstripCacheMaxBytes > 0 ? getTouchstripNativeCache(this.renderConfig.touchstripCacheMaxBytes) : null;
|
|
327
199
|
this.renderConfig.onProfile({
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
hashMs: 0,
|
|
200
|
+
vnodeConversionMs: 0,
|
|
201
|
+
takumiRenderMs: 0,
|
|
202
|
+
hashMs: tEnd - tPhase2,
|
|
332
203
|
base64Ms: 0,
|
|
333
|
-
totalMs:
|
|
334
|
-
skipped,
|
|
335
|
-
cacheHit:
|
|
204
|
+
totalMs: tEnd - tPhase2,
|
|
205
|
+
skipped: false,
|
|
206
|
+
cacheHit: true,
|
|
336
207
|
treeDepth: stats.depth,
|
|
337
208
|
nodeCount: stats.count,
|
|
338
|
-
cacheStats:
|
|
209
|
+
cacheStats: null
|
|
339
210
|
});
|
|
340
211
|
}
|
|
341
|
-
|
|
212
|
+
this.renderConfig.onRender?.(this.container, "");
|
|
213
|
+
return;
|
|
342
214
|
}
|
|
343
|
-
} else {
|
|
344
|
-
const result = await renderToRaw(this.container, width, SEGMENT_HEIGHT, this.renderConfig);
|
|
345
|
-
if (result === null || this.disposed) return;
|
|
346
|
-
const feedbackPromises = [...this.columns.entries()].map(async ([column, entry]) => {
|
|
347
|
-
const sliceUri = await sliceToDataUriAsync(result.buffer, result.width, result.height, column, SEGMENT_WIDTH, SEGMENT_HEIGHT);
|
|
348
|
-
this.lastSegmentUris.set(column, sliceUri);
|
|
349
|
-
if (!this.suppressHardwarePush) entry.sdkAction.setFeedback({ canvas: sliceUri }).catch(() => {});
|
|
350
|
-
});
|
|
351
|
-
await Promise.all(feedbackPromises);
|
|
352
215
|
}
|
|
216
|
+
const result = await renderToRaw(this.container, width, SEGMENT_HEIGHT, this.renderConfig);
|
|
217
|
+
if (result === null || this.disposed) return;
|
|
218
|
+
const segmentResults = [];
|
|
219
|
+
for (const [column] of this.columns) {
|
|
220
|
+
const sliceUri = sliceToDataUri(result.buffer, result.width, result.height, column, SEGMENT_WIDTH, SEGMENT_HEIGHT);
|
|
221
|
+
segmentResults.push([column, sliceUri]);
|
|
222
|
+
this.lastSegmentUris.set(column, sliceUri);
|
|
223
|
+
}
|
|
224
|
+
segmentResults.sort((a, b) => a[0] - b[0]);
|
|
225
|
+
let skipped = false;
|
|
226
|
+
if (this.renderConfig.caching) {
|
|
227
|
+
const uriHash = fnv1a(segmentResults.map(([col, uri]) => `${col}:${uri}`).join("\0"));
|
|
228
|
+
if (uriHash === this._lastSegmentUriHash) skipped = true;
|
|
229
|
+
else this._lastSegmentUriHash = uriHash;
|
|
230
|
+
}
|
|
231
|
+
if (!skipped && !this.suppressHardwarePush) for (const [column, uri] of segmentResults) this.columns.get(column)?.sdkAction.setFeedback({ canvas: uri }).catch(() => {});
|
|
232
|
+
if (cacheKey !== void 0 && this.renderConfig.caching && this.renderConfig.touchStripCacheMaxBytes > 0) {
|
|
233
|
+
const cache = getTouchStripSegmentCache(this.renderConfig.touchStripCacheMaxBytes);
|
|
234
|
+
let byteSize = 64;
|
|
235
|
+
for (const [, uri] of segmentResults) byteSize += uri.length * 2 + 16;
|
|
236
|
+
cache.set(cacheKey, segmentResults, byteSize);
|
|
237
|
+
}
|
|
238
|
+
clearDirtyFlags(this.container);
|
|
353
239
|
this.renderConfig.onRender?.(this.container, "");
|
|
354
240
|
} catch (err) {
|
|
355
241
|
console.error("[@fcannizzaro/streamdeck-react] TouchStrip render error:", err);
|
|
356
|
-
} finally {
|
|
357
|
-
this._rendering = false;
|
|
358
|
-
if (this._pendingFlush && !this.disposed) {
|
|
359
|
-
this._pendingFlush = false;
|
|
360
|
-
await this.doFlush();
|
|
361
|
-
}
|
|
362
242
|
}
|
|
363
243
|
}
|
|
364
244
|
updateGlobalSettings(settings) {
|
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,10 +74,11 @@ 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;
|
|
@@ -76,12 +89,10 @@ export interface PluginConfig {
|
|
|
76
89
|
debug?: boolean;
|
|
77
90
|
/** Maximum image cache size in bytes for key/dial renders. Set to 0 to disable. @default 16777216 (16 MB) */
|
|
78
91
|
imageCacheMaxBytes?: number;
|
|
79
|
-
/** Maximum
|
|
80
|
-
|
|
92
|
+
/** Maximum TouchStrip raw buffer cache size in bytes. Set to 0 to disable. @default 8388608 (8 MB) */
|
|
93
|
+
touchStripCacheMaxBytes?: number;
|
|
81
94
|
/** Offload Takumi rendering to a worker thread. Set to false to disable. @default true */
|
|
82
95
|
useWorker?: boolean;
|
|
83
|
-
/** Image format for touchstrip segment encoding. `"webp"` renders each segment directly via Takumi (faster, no custom PNG encoder). `"png"` uses the raw+crop+deflate path. @default "webp" */
|
|
84
|
-
touchstripImageFormat?: "webp" | "png";
|
|
85
96
|
}
|
|
86
97
|
export interface Plugin {
|
|
87
98
|
connect(): Promise<void>;
|
|
@@ -112,10 +123,8 @@ export interface ActionConfig<S extends JsonObject = JsonObject> {
|
|
|
112
123
|
uuid: string;
|
|
113
124
|
key?: ComponentType;
|
|
114
125
|
dial?: ComponentType;
|
|
115
|
-
/** Full-strip
|
|
126
|
+
/** Full-strip TouchStrip component. When set, replaces per-encoder `dial` display with a single shared React tree that spans the entire touch strip. */
|
|
116
127
|
touchStrip?: ComponentType;
|
|
117
|
-
/** Target frame rate for the touchstrip animation loop and render pipeline. Controls both `useTick` cadence (via `useTouchStrip().fps`) and the render debounce. @default 60 */
|
|
118
|
-
touchStripFPS?: number;
|
|
119
128
|
/** Encoder feedback layout. Defaults to a full-width `pixmap` canvas layout. Custom layouts should include a `pixmap` item keyed as `canvas`. */
|
|
120
129
|
dialLayout?: EncoderLayout;
|
|
121
130
|
wrapper?: WrapperComponent;
|
|
@@ -127,8 +136,6 @@ export type ActionConfigInput<S extends JsonObject = JsonObject> = [keyof Manife
|
|
|
127
136
|
] ? ActionConfig<S> : {
|
|
128
137
|
[UUID in ActionUUID]: {
|
|
129
138
|
uuid: UUID;
|
|
130
|
-
/** Target frame rate for the touchstrip animation loop and render pipeline. Controls both `useTick` cadence (via `useTouchStrip().fps`) and the render debounce. @default 60 */
|
|
131
|
-
touchStripFPS?: number;
|
|
132
139
|
/** Encoder feedback layout. Defaults to a full-width `pixmap` canvas layout. Custom layouts should include a `pixmap` item keyed as `canvas`. */
|
|
133
140
|
dialLayout?: EncoderLayout;
|
|
134
141
|
wrapper?: WrapperComponent;
|
|
@@ -139,10 +146,8 @@ export interface ActionDefinition<S extends JsonObject = JsonObject> {
|
|
|
139
146
|
uuid: string;
|
|
140
147
|
key?: ComponentType;
|
|
141
148
|
dial?: ComponentType;
|
|
142
|
-
/** Full-strip
|
|
149
|
+
/** Full-strip TouchStrip component. When set, replaces per-encoder `dial` display with a single shared React tree that spans the entire touch strip. */
|
|
143
150
|
touchStrip?: ComponentType;
|
|
144
|
-
/** Target frame rate for the touchstrip animation loop and render pipeline. @default 60 */
|
|
145
|
-
touchStripFPS?: number;
|
|
146
151
|
/** Encoder feedback layout. Defaults to a full-width `pixmap` canvas layout. Custom layouts should include a `pixmap` item keyed as `canvas`. */
|
|
147
152
|
dialLayout?: EncoderLayout;
|
|
148
153
|
wrapper?: WrapperComponent;
|
|
@@ -150,7 +155,8 @@ export interface ActionDefinition<S extends JsonObject = JsonObject> {
|
|
|
150
155
|
}
|
|
151
156
|
export interface DeviceInfo {
|
|
152
157
|
id: string;
|
|
153
|
-
type
|
|
158
|
+
/** Numeric device type matching Elgato DeviceType enum values (e.g. 7 = StreamDeckPlus). */
|
|
159
|
+
type: number;
|
|
154
160
|
size: Size;
|
|
155
161
|
name: string;
|
|
156
162
|
}
|
|
@@ -167,8 +173,8 @@ export interface CanvasInfo {
|
|
|
167
173
|
type: "key" | "dial" | "touch";
|
|
168
174
|
}
|
|
169
175
|
export interface StreamDeckAccess {
|
|
170
|
-
action:
|
|
171
|
-
|
|
176
|
+
action: AdapterActionHandle;
|
|
177
|
+
adapter: StreamDeckAdapter;
|
|
172
178
|
}
|
|
173
179
|
export interface KeyDownPayload {
|
|
174
180
|
settings: JsonObject;
|
|
@@ -216,8 +222,6 @@ export interface TouchStripInfo {
|
|
|
216
222
|
columns: number[];
|
|
217
223
|
/** Width of each encoder segment in pixels (always 200). */
|
|
218
224
|
segmentWidth: number;
|
|
219
|
-
/** Target frame rate for the animation loop. Pass to `useTick` for matched cadence. */
|
|
220
|
-
fps: number;
|
|
221
225
|
}
|
|
222
226
|
export interface TouchStripTapPayload {
|
|
223
227
|
/** Absolute tap position across the full strip width. */
|
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",
|
|
@@ -60,11 +60,9 @@
|
|
|
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
|
-
"react-reconciler": "^0.33.0"
|
|
67
|
-
"xxhash-wasm": "^1.1.0"
|
|
65
|
+
"react-reconciler": "^0.33.0"
|
|
68
66
|
},
|
|
69
67
|
"devDependencies": {
|
|
70
68
|
"@happy-dom/global-registrator": "^20.8.3",
|
|
@@ -77,12 +75,16 @@
|
|
|
77
75
|
"vite-plugin-dts": "^4.5.4"
|
|
78
76
|
},
|
|
79
77
|
"peerDependencies": {
|
|
78
|
+
"@elgato/streamdeck": "^2.0.2",
|
|
80
79
|
"react": "^18.0.0 || ^19.0.0",
|
|
81
80
|
"rollup": "^4.0.0",
|
|
82
81
|
"typescript": "^5",
|
|
83
82
|
"vite": "^7.0.0 || ^8.0.0"
|
|
84
83
|
},
|
|
85
84
|
"peerDependenciesMeta": {
|
|
85
|
+
"@elgato/streamdeck": {
|
|
86
|
+
"optional": true
|
|
87
|
+
},
|
|
86
88
|
"rollup": {
|
|
87
89
|
"optional": true
|
|
88
90
|
},
|