@fcannizzaro/streamdeck-react 0.1.9 → 0.1.11
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 +3 -1
- package/dist/action.d.ts +2 -2
- package/dist/action.js +2 -2
- 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 +153 -46
- package/dist/devtools/highlight.d.ts +6 -0
- package/dist/devtools/highlight.js +106 -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 +1 -5
- package/dist/hooks/touchstrip.d.ts +6 -0
- package/dist/hooks/touchstrip.js +37 -0
- package/dist/index.d.ts +7 -2
- package/dist/index.js +3 -2
- package/dist/manifest-codegen.d.ts +38 -0
- package/dist/manifest-codegen.js +110 -0
- package/dist/node_modules/.bun/xxhash-wasm@1.1.0/node_modules/xxhash-wasm/esm/xxhash-wasm.js +3157 -0
- package/dist/plugin.js +20 -9
- package/dist/reconciler/host-config.js +19 -1
- package/dist/reconciler/vnode.d.ts +26 -0
- package/dist/reconciler/vnode.js +41 -10
- package/dist/render/buffer-pool.d.ts +19 -0
- package/dist/render/buffer-pool.js +51 -0
- package/dist/render/cache.d.ts +41 -0
- package/dist/render/cache.js +159 -5
- package/dist/render/image-cache.d.ts +53 -0
- package/dist/render/image-cache.js +128 -0
- package/dist/render/metrics.d.ts +58 -0
- package/dist/render/metrics.js +101 -0
- package/dist/render/pipeline.d.ts +46 -1
- package/dist/render/pipeline.js +370 -36
- package/dist/render/png.d.ts +10 -1
- package/dist/render/png.js +31 -13
- package/dist/render/render-pool.d.ts +26 -0
- package/dist/render/render-pool.js +141 -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/flush-coordinator.d.ts +18 -0
- package/dist/roots/flush-coordinator.js +38 -0
- package/dist/roots/registry.d.ts +6 -4
- package/dist/roots/registry.js +47 -33
- package/dist/roots/root.d.ts +32 -2
- package/dist/roots/root.js +104 -14
- package/dist/roots/settings-equality.d.ts +5 -0
- package/dist/roots/settings-equality.js +24 -0
- package/dist/roots/touchstrip-root.d.ts +93 -0
- package/dist/roots/touchstrip-root.js +383 -0
- package/dist/types.d.ts +62 -16
- package/dist/vite.d.ts +22 -8
- package/dist/vite.js +24 -8
- package/package.json +5 -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.d.ts +0 -45
- package/dist/roots/touchbar-root.js +0 -175
package/dist/devtools/bridge.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { metrics } from "../render/metrics.js";
|
|
1
2
|
import { serializeValue } from "./serialization/value.js";
|
|
2
3
|
import { serializeVNode } from "./serialization/vnode.js";
|
|
3
|
-
import { renderWithHighlight } from "./highlight.js";
|
|
4
|
+
import { renderTouchStripWithHighlight, renderWithHighlight } from "./highlight.js";
|
|
4
5
|
//#region src/devtools/bridge.ts
|
|
5
6
|
var RingBuffer = class {
|
|
6
7
|
items;
|
|
@@ -27,7 +28,7 @@ var NETWORK_RING_SIZE = 100;
|
|
|
27
28
|
var EVENT_RING_SIZE = 200;
|
|
28
29
|
var consoleIdCounter = 0;
|
|
29
30
|
var eventIdCounter = 0;
|
|
30
|
-
var DevtoolsBridge = class {
|
|
31
|
+
var DevtoolsBridge = class DevtoolsBridge {
|
|
31
32
|
server;
|
|
32
33
|
devtoolsName;
|
|
33
34
|
renderConfig;
|
|
@@ -35,12 +36,14 @@ var DevtoolsBridge = class {
|
|
|
35
36
|
networkRing = new RingBuffer(NETWORK_RING_SIZE);
|
|
36
37
|
eventRing = new RingBuffer(EVENT_RING_SIZE);
|
|
37
38
|
actions = /* @__PURE__ */ new Map();
|
|
38
|
-
|
|
39
|
+
touchStrips = /* @__PURE__ */ new Map();
|
|
39
40
|
lastRenderSent = /* @__PURE__ */ new Map();
|
|
40
41
|
pendingTrailing = /* @__PURE__ */ new Map();
|
|
41
42
|
eventBusOwners = /* @__PURE__ */ new Map();
|
|
42
43
|
highlightedActionId = null;
|
|
43
44
|
highlightedNodeId = null;
|
|
45
|
+
_lastProfile = null;
|
|
46
|
+
_metricsTimer = null;
|
|
44
47
|
constructor(server, devtoolsName, renderConfig) {
|
|
45
48
|
this.server = server;
|
|
46
49
|
this.devtoolsName = devtoolsName;
|
|
@@ -131,25 +134,25 @@ var DevtoolsBridge = class {
|
|
|
131
134
|
this.pendingTrailing.delete(actionId);
|
|
132
135
|
}
|
|
133
136
|
}
|
|
134
|
-
|
|
135
|
-
this.
|
|
137
|
+
onTouchStripCreated(deviceId, root, deviceInfo) {
|
|
138
|
+
this.touchStrips.set(deviceId, {
|
|
136
139
|
root,
|
|
137
140
|
deviceInfo,
|
|
138
141
|
columns: /* @__PURE__ */ new Map()
|
|
139
142
|
});
|
|
140
143
|
this.eventBusOwners.set(root.eventBus, {
|
|
141
|
-
actionId: `
|
|
144
|
+
actionId: `touchstrip:${deviceId}`,
|
|
142
145
|
uuid: ""
|
|
143
146
|
});
|
|
144
147
|
}
|
|
145
|
-
|
|
146
|
-
const tb = this.
|
|
148
|
+
onTouchStripColumnChanged(deviceId, columns, actionMap) {
|
|
149
|
+
const tb = this.touchStrips.get(deviceId);
|
|
147
150
|
if (tb) tb.columns = new Map(actionMap);
|
|
148
151
|
}
|
|
149
|
-
|
|
150
|
-
const tb = this.
|
|
152
|
+
onTouchStripDestroyed(deviceId) {
|
|
153
|
+
const tb = this.touchStrips.get(deviceId);
|
|
151
154
|
if (tb) this.eventBusOwners.delete(tb.root.eventBus);
|
|
152
|
-
this.
|
|
155
|
+
this.touchStrips.delete(deviceId);
|
|
153
156
|
}
|
|
154
157
|
onDispatch(actionId, event, payload) {
|
|
155
158
|
if (!this.server.hasClients()) return;
|
|
@@ -207,6 +210,37 @@ var DevtoolsBridge = class {
|
|
|
207
210
|
this.consoleRing.push(msg);
|
|
208
211
|
this.server.broadcast(msg);
|
|
209
212
|
}
|
|
213
|
+
/**
|
|
214
|
+
* Stash the render profile for the next onRender call.
|
|
215
|
+
* In the pipeline, onProfile fires synchronously before onRender,
|
|
216
|
+
* so the stash is always consumed immediately.
|
|
217
|
+
*/
|
|
218
|
+
onProfile(profile) {
|
|
219
|
+
this._lastProfile = profile;
|
|
220
|
+
}
|
|
221
|
+
static METRICS_INTERVAL_MS = 3e3;
|
|
222
|
+
/** Start periodic metrics emission to connected devtools clients. */
|
|
223
|
+
startMetricsEmitter() {
|
|
224
|
+
if (this._metricsTimer) return;
|
|
225
|
+
this._metricsTimer = setInterval(() => {
|
|
226
|
+
if (!this.server.hasClients()) return;
|
|
227
|
+
const snapshot = metrics.snapshot();
|
|
228
|
+
const msg = {
|
|
229
|
+
type: "metrics",
|
|
230
|
+
ts: Date.now(),
|
|
231
|
+
metrics: snapshot
|
|
232
|
+
};
|
|
233
|
+
this.server.broadcast(msg);
|
|
234
|
+
}, DevtoolsBridge.METRICS_INTERVAL_MS);
|
|
235
|
+
if (typeof this._metricsTimer === "object" && "unref" in this._metricsTimer) this._metricsTimer.unref();
|
|
236
|
+
}
|
|
237
|
+
/** Stop periodic metrics emission. */
|
|
238
|
+
stopMetricsEmitter() {
|
|
239
|
+
if (this._metricsTimer) {
|
|
240
|
+
clearInterval(this._metricsTimer);
|
|
241
|
+
this._metricsTimer = null;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
210
244
|
onFetchRequest(id, method, url, headers, body) {
|
|
211
245
|
const msg = {
|
|
212
246
|
type: "network:request",
|
|
@@ -247,6 +281,8 @@ var DevtoolsBridge = class {
|
|
|
247
281
|
}
|
|
248
282
|
onRender(container, dataUri) {
|
|
249
283
|
if (!this.server.hasClients()) return;
|
|
284
|
+
const profile = this._lastProfile;
|
|
285
|
+
this._lastProfile = null;
|
|
250
286
|
let actionId = null;
|
|
251
287
|
let meta = null;
|
|
252
288
|
for (const [id, m] of this.actions) if (m.root.vcontainer === container) {
|
|
@@ -256,30 +292,32 @@ var DevtoolsBridge = class {
|
|
|
256
292
|
}
|
|
257
293
|
if (actionId && meta) {
|
|
258
294
|
meta.root.lastDataUri = dataUri;
|
|
259
|
-
this.throttledRender(actionId, container, dataUri, meta);
|
|
295
|
+
this.throttledRender(actionId, container, dataUri, meta, profile);
|
|
260
296
|
if (this.highlightedActionId === actionId && this.highlightedNodeId !== null) this.applyHighlight(actionId, this.highlightedNodeId, meta).catch(() => {});
|
|
261
297
|
return;
|
|
262
298
|
}
|
|
263
|
-
for (const [deviceId, tb] of this.
|
|
264
|
-
this.
|
|
299
|
+
for (const [deviceId, tb] of this.touchStrips) if (tb.root.vcontainer === container) {
|
|
300
|
+
this.emitTouchStripRender(deviceId, tb, profile);
|
|
301
|
+
const tbActionId = `${DevtoolsBridge.TB_PREFIX}${deviceId}`;
|
|
302
|
+
if (this.highlightedActionId === tbActionId && this.highlightedNodeId !== null) this.applyTouchStripHighlight(tbActionId, deviceId, this.highlightedNodeId, tb).catch(() => {});
|
|
265
303
|
return;
|
|
266
304
|
}
|
|
267
305
|
}
|
|
268
|
-
throttledRender(actionId, container, dataUri, meta) {
|
|
306
|
+
throttledRender(actionId, container, dataUri, meta, profile) {
|
|
269
307
|
const now = Date.now();
|
|
270
308
|
const elapsed = now - (this.lastRenderSent.get(actionId) ?? 0);
|
|
271
309
|
const pending = this.pendingTrailing.get(actionId);
|
|
272
310
|
if (pending) clearTimeout(pending);
|
|
273
311
|
if (elapsed >= RENDER_THROTTLE_MS) {
|
|
274
|
-
this.emitRender(actionId, container, dataUri, meta, now);
|
|
312
|
+
this.emitRender(actionId, container, dataUri, meta, now, profile);
|
|
275
313
|
this.lastRenderSent.set(actionId, now);
|
|
276
314
|
} else this.pendingTrailing.set(actionId, setTimeout(() => {
|
|
277
|
-
this.emitRender(actionId, container, dataUri, meta, Date.now());
|
|
315
|
+
this.emitRender(actionId, container, dataUri, meta, Date.now(), profile);
|
|
278
316
|
this.lastRenderSent.set(actionId, Date.now());
|
|
279
317
|
this.pendingTrailing.delete(actionId);
|
|
280
318
|
}, RENDER_THROTTLE_MS - elapsed));
|
|
281
319
|
}
|
|
282
|
-
emitRender(actionId, container, dataUri, meta, ts) {
|
|
320
|
+
emitRender(actionId, container, dataUri, meta, ts, profile) {
|
|
283
321
|
const tree = serializeVNode(container);
|
|
284
322
|
const msg = {
|
|
285
323
|
type: "render",
|
|
@@ -293,11 +331,27 @@ var DevtoolsBridge = class {
|
|
|
293
331
|
},
|
|
294
332
|
tree,
|
|
295
333
|
dataUri,
|
|
296
|
-
renderMs: 0
|
|
334
|
+
renderMs: profile?.totalMs ?? 0,
|
|
335
|
+
...profile ? { profile: this.toProfileData(profile) } : {}
|
|
297
336
|
};
|
|
298
337
|
this.server.broadcast(msg);
|
|
299
338
|
}
|
|
300
|
-
|
|
339
|
+
/** Convert internal RenderProfile to wire-protocol ProfileData. */
|
|
340
|
+
toProfileData(profile) {
|
|
341
|
+
return {
|
|
342
|
+
vnodeToElementMs: profile.vnodeToElementMs,
|
|
343
|
+
fromJsxMs: profile.fromJsxMs,
|
|
344
|
+
takumiRenderMs: profile.takumiRenderMs,
|
|
345
|
+
hashMs: profile.hashMs,
|
|
346
|
+
base64Ms: profile.base64Ms,
|
|
347
|
+
totalMs: profile.totalMs,
|
|
348
|
+
skipped: profile.skipped,
|
|
349
|
+
cacheHit: profile.cacheHit,
|
|
350
|
+
treeDepth: profile.treeDepth,
|
|
351
|
+
nodeCount: profile.nodeCount
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
emitTouchStripRender(deviceId, tb, profile) {
|
|
301
355
|
const tree = serializeVNode(tb.root.vcontainer);
|
|
302
356
|
const segments = [];
|
|
303
357
|
for (const [column, actionId] of tb.columns) {
|
|
@@ -309,7 +363,7 @@ var DevtoolsBridge = class {
|
|
|
309
363
|
});
|
|
310
364
|
}
|
|
311
365
|
const msg = {
|
|
312
|
-
type: "render:
|
|
366
|
+
type: "render:touchstrip",
|
|
313
367
|
ts: Date.now(),
|
|
314
368
|
deviceId,
|
|
315
369
|
canvas: {
|
|
@@ -318,46 +372,72 @@ var DevtoolsBridge = class {
|
|
|
318
372
|
},
|
|
319
373
|
tree,
|
|
320
374
|
segments,
|
|
321
|
-
renderMs: 0
|
|
375
|
+
renderMs: profile?.totalMs ?? 0,
|
|
376
|
+
...profile ? { profile: this.toProfileData(profile) } : {}
|
|
322
377
|
};
|
|
323
378
|
this.server.broadcast(msg);
|
|
324
379
|
}
|
|
380
|
+
static TB_PREFIX = "touchstrip:";
|
|
325
381
|
async handleHighlight(actionId, nodeId) {
|
|
326
382
|
try {
|
|
327
383
|
const prevId = this.highlightedActionId;
|
|
328
384
|
this.highlightedActionId = actionId;
|
|
329
385
|
this.highlightedNodeId = nodeId;
|
|
330
386
|
if (prevId && prevId !== actionId) {
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
prevMeta.root.suppressHardwarePush = false;
|
|
334
|
-
if (prevMeta.root.lastDataUri) await prevMeta.root.pushImage(prevMeta.root.lastDataUri).catch(() => {});
|
|
335
|
-
}
|
|
336
|
-
this.broadcastHighlightRender(prevId, null);
|
|
387
|
+
await this.restoreHighlight(prevId);
|
|
388
|
+
this.broadcastHighlightClear(prevId);
|
|
337
389
|
}
|
|
338
390
|
if (!actionId || nodeId === null) {
|
|
339
391
|
if (actionId && prevId === actionId) {
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
meta.root.suppressHardwarePush = false;
|
|
343
|
-
if (meta.root.lastDataUri) await meta.root.pushImage(meta.root.lastDataUri).catch(() => {});
|
|
344
|
-
}
|
|
345
|
-
this.broadcastHighlightRender(actionId, null);
|
|
392
|
+
await this.restoreHighlight(actionId);
|
|
393
|
+
this.broadcastHighlightClear(actionId);
|
|
346
394
|
}
|
|
347
395
|
this.highlightedActionId = null;
|
|
348
396
|
this.highlightedNodeId = null;
|
|
349
397
|
return;
|
|
350
398
|
}
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
399
|
+
if (actionId.startsWith(DevtoolsBridge.TB_PREFIX)) {
|
|
400
|
+
const deviceId = actionId.slice(DevtoolsBridge.TB_PREFIX.length);
|
|
401
|
+
const tb = this.touchStrips.get(deviceId);
|
|
402
|
+
if (!tb) {
|
|
403
|
+
this.highlightedActionId = null;
|
|
404
|
+
this.highlightedNodeId = null;
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
tb.root.suppressHardwarePush = true;
|
|
408
|
+
await this.applyTouchStripHighlight(actionId, deviceId, nodeId, tb);
|
|
409
|
+
} else {
|
|
410
|
+
const meta = this.actions.get(actionId);
|
|
411
|
+
if (!meta) {
|
|
412
|
+
this.highlightedActionId = null;
|
|
413
|
+
this.highlightedNodeId = null;
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
meta.root.suppressHardwarePush = true;
|
|
417
|
+
await this.applyHighlight(actionId, nodeId, meta);
|
|
356
418
|
}
|
|
357
|
-
meta.root.suppressHardwarePush = true;
|
|
358
|
-
await this.applyHighlight(actionId, nodeId, meta);
|
|
359
419
|
} catch {}
|
|
360
420
|
}
|
|
421
|
+
/**
|
|
422
|
+
* Restore a highlighted action or touchstrip to its normal state.
|
|
423
|
+
* Un-suppresses hardware pushes and restores the original image(s).
|
|
424
|
+
*/
|
|
425
|
+
async restoreHighlight(id) {
|
|
426
|
+
if (id.startsWith(DevtoolsBridge.TB_PREFIX)) {
|
|
427
|
+
const deviceId = id.slice(DevtoolsBridge.TB_PREFIX.length);
|
|
428
|
+
const tb = this.touchStrips.get(deviceId);
|
|
429
|
+
if (tb) {
|
|
430
|
+
tb.root.suppressHardwarePush = false;
|
|
431
|
+
await tb.root.pushSegmentImages(tb.root.lastSegmentUris);
|
|
432
|
+
}
|
|
433
|
+
} else {
|
|
434
|
+
const prevMeta = this.actions.get(id);
|
|
435
|
+
if (prevMeta) {
|
|
436
|
+
prevMeta.root.suppressHardwarePush = false;
|
|
437
|
+
if (prevMeta.root.lastDataUri) await prevMeta.root.pushImage(prevMeta.root.lastDataUri).catch(() => {});
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
361
441
|
async applyHighlight(actionId, nodeId, meta) {
|
|
362
442
|
try {
|
|
363
443
|
const uri = await renderWithHighlight(meta.root.vcontainer, meta.canvas.width, meta.canvas.height, this.renderConfig, nodeId);
|
|
@@ -367,6 +447,20 @@ var DevtoolsBridge = class {
|
|
|
367
447
|
}
|
|
368
448
|
} catch {}
|
|
369
449
|
}
|
|
450
|
+
async applyTouchStripHighlight(actionId, deviceId, nodeId, tb) {
|
|
451
|
+
try {
|
|
452
|
+
const columns = tb.root.columnNumbers;
|
|
453
|
+
if (columns.length === 0) return;
|
|
454
|
+
const segmentWidth = 200;
|
|
455
|
+
const segmentHeight = 100;
|
|
456
|
+
const fullWidth = (Math.max(...columns) + 1) * segmentWidth;
|
|
457
|
+
const result = await renderTouchStripWithHighlight(tb.root.vcontainer, fullWidth, segmentHeight, columns, segmentWidth, this.renderConfig.touchstripImageFormat, this.renderConfig, nodeId);
|
|
458
|
+
if (result && this.highlightedActionId === actionId && this.highlightedNodeId === nodeId) {
|
|
459
|
+
await tb.root.pushSegmentImages(result.segmentUris);
|
|
460
|
+
for (const [col, uri] of result.segmentUris) this.broadcastHighlightRender(`${actionId}:seg:${col}`, uri);
|
|
461
|
+
}
|
|
462
|
+
} catch {}
|
|
463
|
+
}
|
|
370
464
|
/** Broadcast highlight render image (or null to clear) to devtools UI. */
|
|
371
465
|
broadcastHighlightRender(actionId, dataUri) {
|
|
372
466
|
const msg = {
|
|
@@ -377,6 +471,18 @@ var DevtoolsBridge = class {
|
|
|
377
471
|
};
|
|
378
472
|
this.server.broadcast(msg);
|
|
379
473
|
}
|
|
474
|
+
/**
|
|
475
|
+
* Clear highlight URIs for the given actionId.
|
|
476
|
+
* For touchstrip IDs, clears all per-segment keys (touchstrip:*:seg:N).
|
|
477
|
+
* For regular actions, clears the single actionId key.
|
|
478
|
+
*/
|
|
479
|
+
broadcastHighlightClear(id) {
|
|
480
|
+
if (id.startsWith(DevtoolsBridge.TB_PREFIX)) {
|
|
481
|
+
const deviceId = id.slice(DevtoolsBridge.TB_PREFIX.length);
|
|
482
|
+
const tb = this.touchStrips.get(deviceId);
|
|
483
|
+
if (tb) for (const col of tb.root.columnNumbers) this.broadcastHighlightRender(`${id}:seg:${col}`, null);
|
|
484
|
+
} else this.broadcastHighlightRender(id, null);
|
|
485
|
+
}
|
|
380
486
|
buildSnapshot() {
|
|
381
487
|
const actions = [];
|
|
382
488
|
for (const [actionId, meta] of this.actions) {
|
|
@@ -402,8 +508,8 @@ var DevtoolsBridge = class {
|
|
|
402
508
|
dataUri: meta.root.lastDataUri
|
|
403
509
|
});
|
|
404
510
|
}
|
|
405
|
-
const
|
|
406
|
-
for (const [deviceId, tb] of this.
|
|
511
|
+
const touchStrips = [];
|
|
512
|
+
for (const [deviceId, tb] of this.touchStrips) {
|
|
407
513
|
let tree = null;
|
|
408
514
|
try {
|
|
409
515
|
tree = serializeVNode(tb.root.vcontainer);
|
|
@@ -414,7 +520,7 @@ var DevtoolsBridge = class {
|
|
|
414
520
|
actionId,
|
|
415
521
|
dataUri: tb.root.lastSegmentUris.get(column) ?? null
|
|
416
522
|
});
|
|
417
|
-
|
|
523
|
+
touchStrips.push({
|
|
418
524
|
deviceId,
|
|
419
525
|
deviceName: tb.deviceInfo.name,
|
|
420
526
|
canvas: {
|
|
@@ -429,10 +535,11 @@ var DevtoolsBridge = class {
|
|
|
429
535
|
type: "snapshot",
|
|
430
536
|
ts: Date.now(),
|
|
431
537
|
actions,
|
|
432
|
-
|
|
538
|
+
touchStrips,
|
|
433
539
|
recentConsole: this.consoleRing.toArray(),
|
|
434
540
|
recentNetwork: this.networkRing.toArray(),
|
|
435
|
-
recentEvents: this.eventRing.toArray()
|
|
541
|
+
recentEvents: this.eventRing.toArray(),
|
|
542
|
+
metrics: metrics.snapshot()
|
|
436
543
|
};
|
|
437
544
|
}
|
|
438
545
|
};
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import { OutputFormat } from '@takumi-rs/core';
|
|
1
2
|
import { VContainer } from '../reconciler/vnode';
|
|
2
3
|
import { RenderConfig } from '../render/pipeline';
|
|
3
4
|
export declare function renderWithHighlight(container: VContainer, width: number, height: number, config: RenderConfig, targetNid: number): Promise<string | null>;
|
|
5
|
+
export interface TouchStripHighlightResult {
|
|
6
|
+
/** Per-column data URIs for ALL segments. */
|
|
7
|
+
segmentUris: Map<number, string>;
|
|
8
|
+
}
|
|
9
|
+
export declare function renderTouchStripWithHighlight(container: VContainer, fullWidth: number, segmentHeight: number, columns: number[], segmentWidth: number, format: OutputFormat, config: RenderConfig, targetNid: number): Promise<TouchStripHighlightResult | null>;
|
|
@@ -1,92 +1,141 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { bufferToDataUri } from "../render/pipeline.js";
|
|
3
|
-
import { createElement } from "react";
|
|
4
|
-
import { fromJsx } from "@takumi-rs/helpers/jsx";
|
|
1
|
+
import { bufferToDataUri, buildTakumiRoot } from "../render/pipeline.js";
|
|
5
2
|
//#region src/devtools/highlight.ts
|
|
6
|
-
var HIGHLIGHT_BORDER_COLOR = "rgba(111, 168, 220, 0.85)";
|
|
7
|
-
var HIGHLIGHT_BORDER_WIDTH = 2;
|
|
8
3
|
var HIGHLIGHT_BG = "rgba(111, 168, 220, 0.66)";
|
|
9
|
-
var HIGHLIGHT_BOX_SHADOW = `inset 0 0 0
|
|
4
|
+
var HIGHLIGHT_BOX_SHADOW = `inset 0 0 0 2px rgba(111, 168, 220, 0.85)`;
|
|
10
5
|
async function renderWithHighlight(container, width, height, config, targetNid) {
|
|
11
6
|
if (container.children.length === 0) return null;
|
|
12
7
|
const effectiveNid = resolveTargetNid(container, targetNid);
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const renderOpts = {
|
|
8
|
+
const rootNode = buildTakumiRoot(container);
|
|
9
|
+
const counter = { value: 0 };
|
|
10
|
+
counter.value++;
|
|
11
|
+
const target = findTargetTakumiNode(container.children, rootNode.children ?? [], effectiveNid, counter);
|
|
12
|
+
if (!target) return null;
|
|
13
|
+
injectHighlightOverlay(target);
|
|
14
|
+
return bufferToDataUri(await config.renderer.render(rootNode, {
|
|
21
15
|
width,
|
|
22
16
|
height,
|
|
23
|
-
|
|
17
|
+
format: config.imageFormat,
|
|
24
18
|
devicePixelRatio: config.devicePixelRatio
|
|
25
|
-
};
|
|
26
|
-
|
|
19
|
+
}), config.imageFormat);
|
|
20
|
+
}
|
|
21
|
+
async function renderTouchStripWithHighlight(container, fullWidth, segmentHeight, columns, segmentWidth, format, config, targetNid) {
|
|
22
|
+
if (container.children.length === 0) return null;
|
|
23
|
+
const effectiveNid = resolveTargetNid(container, targetNid);
|
|
24
|
+
const rootNode = buildTakumiRoot(container);
|
|
27
25
|
const counter = { value: 0 };
|
|
28
26
|
counter.value++;
|
|
29
|
-
const
|
|
30
|
-
if (!
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
27
|
+
const target = findTargetTakumiNode(container.children, rootNode.children ?? [], effectiveNid, counter);
|
|
28
|
+
if (!target) return null;
|
|
29
|
+
injectHighlightOverlay(target);
|
|
30
|
+
const segmentUris = /* @__PURE__ */ new Map();
|
|
31
|
+
const segmentPromises = columns.map(async (column) => {
|
|
32
|
+
const clipNode = {
|
|
33
|
+
type: "container",
|
|
34
|
+
style: {
|
|
35
|
+
width: segmentWidth,
|
|
36
|
+
height: segmentHeight,
|
|
37
|
+
overflow: "hidden"
|
|
38
|
+
},
|
|
39
|
+
children: [{
|
|
40
|
+
type: "container",
|
|
41
|
+
style: {
|
|
42
|
+
display: "flex",
|
|
43
|
+
width: fullWidth,
|
|
44
|
+
height: segmentHeight,
|
|
45
|
+
marginLeft: -(column * segmentWidth)
|
|
46
|
+
},
|
|
47
|
+
children: rootNode.children
|
|
48
|
+
}]
|
|
49
|
+
};
|
|
50
|
+
const segBuffer = await config.renderer.render(clipNode, {
|
|
51
|
+
width: segmentWidth,
|
|
52
|
+
height: segmentHeight,
|
|
53
|
+
format,
|
|
54
|
+
devicePixelRatio: config.devicePixelRatio
|
|
55
|
+
});
|
|
56
|
+
segmentUris.set(column, bufferToDataUri(segBuffer, format));
|
|
57
|
+
});
|
|
58
|
+
await Promise.all(segmentPromises);
|
|
59
|
+
return { segmentUris };
|
|
51
60
|
}
|
|
52
|
-
function
|
|
53
|
-
|
|
61
|
+
function injectHighlightOverlay(target) {
|
|
62
|
+
const node = target.node;
|
|
63
|
+
const overlay = {
|
|
64
|
+
type: "container",
|
|
65
|
+
style: {
|
|
66
|
+
position: "absolute",
|
|
67
|
+
top: 0,
|
|
68
|
+
left: 0,
|
|
69
|
+
width: "100%",
|
|
70
|
+
height: "100%",
|
|
71
|
+
backgroundColor: HIGHLIGHT_BG,
|
|
72
|
+
boxShadow: HIGHLIGHT_BOX_SHADOW
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
if (node.type === "container") {
|
|
76
|
+
node.style = {
|
|
77
|
+
...node.style ?? {},
|
|
78
|
+
position: "relative"
|
|
79
|
+
};
|
|
80
|
+
if (!node.children) node.children = [];
|
|
81
|
+
node.children.push(overlay);
|
|
82
|
+
} else {
|
|
83
|
+
const wrapper = {
|
|
84
|
+
type: "container",
|
|
85
|
+
style: { position: "relative" },
|
|
86
|
+
children: [target.node, overlay]
|
|
87
|
+
};
|
|
88
|
+
target.parentChildren[target.indexInParent] = wrapper;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function findTargetTakumiNode(vnodes, takumiNodes, targetNid, counter) {
|
|
92
|
+
let idx = 0;
|
|
54
93
|
for (const vnode of vnodes) {
|
|
55
94
|
const nid = counter.value++;
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
if (!
|
|
59
|
-
const absX = parentX + measured.transform[4];
|
|
60
|
-
const absY = parentY + measured.transform[5];
|
|
95
|
+
const takumiNode = takumiNodes[idx];
|
|
96
|
+
idx++;
|
|
97
|
+
if (!takumiNode) continue;
|
|
61
98
|
if (nid === targetNid) return {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
height: measured.height
|
|
99
|
+
node: takumiNode,
|
|
100
|
+
parentChildren: takumiNodes,
|
|
101
|
+
indexInParent: idx - 1
|
|
66
102
|
};
|
|
67
|
-
|
|
103
|
+
if (vnode.type === "#text" || vnode.type === "img") continue;
|
|
104
|
+
if (vnode.type === "svg") {
|
|
105
|
+
advanceCounterThroughSubtree(vnode.children, counter);
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
const childTakumiNodes = takumiNode.children ?? [];
|
|
109
|
+
const found = findTargetTakumiNode(vnode.children, childTakumiNodes, targetNid, counter);
|
|
68
110
|
if (found) return found;
|
|
69
111
|
}
|
|
70
112
|
return null;
|
|
71
113
|
}
|
|
114
|
+
function advanceCounterThroughSubtree(vnodes, counter) {
|
|
115
|
+
for (const node of vnodes) {
|
|
116
|
+
counter.value++;
|
|
117
|
+
advanceCounterThroughSubtree(node.children, counter);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
72
120
|
function resolveTargetNid(container, targetNid) {
|
|
73
121
|
const counter = { value: 0 };
|
|
74
122
|
counter.value++;
|
|
75
123
|
for (const child of container.children) {
|
|
76
|
-
const resolved = resolveInSubtree(child, targetNid, counter, 0);
|
|
124
|
+
const resolved = resolveInSubtree(child, targetNid, counter, 0, false);
|
|
77
125
|
if (resolved !== null) return resolved;
|
|
78
126
|
}
|
|
79
127
|
return targetNid;
|
|
80
128
|
}
|
|
81
|
-
function resolveInSubtree(node, targetNid, counter, parentNid) {
|
|
129
|
+
function resolveInSubtree(node, targetNid, counter, parentNid, insideSvg) {
|
|
82
130
|
const nid = counter.value++;
|
|
83
|
-
if (nid === targetNid) return node.type === "#text" ? parentNid : nid;
|
|
131
|
+
if (nid === targetNid) return node.type === "#text" || insideSvg ? parentNid : nid;
|
|
84
132
|
if (node.type === "#text") return null;
|
|
133
|
+
const childInsideSvg = insideSvg || node.type === "svg";
|
|
85
134
|
for (const child of node.children) {
|
|
86
|
-
const resolved = resolveInSubtree(child, targetNid, counter, nid);
|
|
135
|
+
const resolved = resolveInSubtree(child, targetNid, counter, nid, childInsideSvg);
|
|
87
136
|
if (resolved !== null) return resolved;
|
|
88
137
|
}
|
|
89
138
|
return null;
|
|
90
139
|
}
|
|
91
140
|
//#endregion
|
|
92
|
-
export { renderWithHighlight };
|
|
141
|
+
export { renderTouchStripWithHighlight, renderWithHighlight };
|
package/dist/devtools/index.js
CHANGED
|
@@ -11,6 +11,9 @@ function startDevtoolsServer(config) {
|
|
|
11
11
|
config.renderConfig.onRender = (container, dataUri) => {
|
|
12
12
|
bridge.onRender(container, dataUri);
|
|
13
13
|
};
|
|
14
|
+
config.renderConfig.onProfile = (profile) => {
|
|
15
|
+
bridge.onProfile(profile);
|
|
16
|
+
};
|
|
14
17
|
EventBus.devtoolsObserver = (bus, event, payload) => {
|
|
15
18
|
bridge.onEventBusEmit(bus, event, payload);
|
|
16
19
|
};
|
|
@@ -23,12 +26,15 @@ function startDevtoolsServer(config) {
|
|
|
23
26
|
onError: (id, error, dur) => bridge.onFetchError(id, error, dur)
|
|
24
27
|
});
|
|
25
28
|
server.start();
|
|
29
|
+
bridge.startMetricsEmitter();
|
|
26
30
|
process.on("exit", () => {
|
|
31
|
+
bridge.stopMetricsEmitter();
|
|
27
32
|
restoreConsole();
|
|
28
33
|
restoreFetch();
|
|
29
34
|
EventBus.devtoolsObserver = null;
|
|
30
35
|
config.registry.observer = null;
|
|
31
36
|
config.renderConfig.onRender = void 0;
|
|
37
|
+
config.renderConfig.onProfile = void 0;
|
|
32
38
|
server.stop();
|
|
33
39
|
});
|
|
34
40
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ReactRoot } from '../../roots/root';
|
|
2
|
-
import {
|
|
2
|
+
import { TouchStripRoot } from '../../roots/touchstrip-root';
|
|
3
3
|
import { CanvasInfo, DeviceInfo } from '../../types';
|
|
4
4
|
export interface RegistryObserver {
|
|
5
5
|
onRootCreated(actionId: string, root: ReactRoot, meta: {
|
|
@@ -13,8 +13,8 @@ export interface RegistryObserver {
|
|
|
13
13
|
};
|
|
14
14
|
}): void;
|
|
15
15
|
onRootDestroyed(actionId: string): void;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
onTouchStripCreated(deviceId: string, root: TouchStripRoot, deviceInfo: DeviceInfo): void;
|
|
17
|
+
onTouchStripColumnChanged(deviceId: string, columns: number[], actionMap: Map<number, string>): void;
|
|
18
|
+
onTouchStripDestroyed(deviceId: string): void;
|
|
19
19
|
onDispatch(actionId: string, event: string, payload: unknown): void;
|
|
20
20
|
}
|
|
@@ -35,7 +35,12 @@ export declare class DevtoolsServer {
|
|
|
35
35
|
/**
|
|
36
36
|
* GET /message?d=<json> — fire-and-forget client→server messages.
|
|
37
37
|
* Used via `new Image().src` to bypass CORS/PNA preflight entirely.
|
|
38
|
-
* Returns a 1x1 transparent GIF.
|
|
38
|
+
* Returns a 1x1 transparent GIF (smallest valid image response).
|
|
39
|
+
*
|
|
40
|
+
* Why: browsers enforce Private Network Access (PNA) restrictions
|
|
41
|
+
* on fetch/XHR to localhost from public origins. Image loading
|
|
42
|
+
* is exempt from PNA preflight, so `new Image().src = url` works
|
|
43
|
+
* without triggering CORS errors.
|
|
39
44
|
*/
|
|
40
45
|
private handleGetMessage;
|
|
41
46
|
/** POST /message — client→server messages (JSON body). Fallback for programmatic use. */
|
package/dist/devtools/server.js
CHANGED
|
@@ -153,7 +153,12 @@ var DevtoolsServer = class {
|
|
|
153
153
|
/**
|
|
154
154
|
* GET /message?d=<json> — fire-and-forget client→server messages.
|
|
155
155
|
* Used via `new Image().src` to bypass CORS/PNA preflight entirely.
|
|
156
|
-
* Returns a 1x1 transparent GIF.
|
|
156
|
+
* Returns a 1x1 transparent GIF (smallest valid image response).
|
|
157
|
+
*
|
|
158
|
+
* Why: browsers enforce Private Network Access (PNA) restrictions
|
|
159
|
+
* on fetch/XHR to localhost from public origins. Image loading
|
|
160
|
+
* is exempt from PNA preflight, so `new Image().src = url` works
|
|
161
|
+
* without triggering CORS errors.
|
|
157
162
|
*/
|
|
158
163
|
handleGetMessage(req, res) {
|
|
159
164
|
const qs = (req.url ?? "").split("?")[1] ?? "";
|