@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
|
@@ -1,8 +1,8 @@
|
|
|
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
|
import { VContainer } from '../reconciler/vnode';
|
|
5
|
-
import { RenderConfig } from '../render/pipeline';
|
|
5
|
+
import { RenderConfig, RenderProfile } from '../render/pipeline';
|
|
6
6
|
import { RegistryObserver } from './observers/lifecycle';
|
|
7
7
|
import { DevtoolsServer } from './server';
|
|
8
8
|
import { SnapshotMessage } from './types';
|
|
@@ -14,12 +14,14 @@ export declare class DevtoolsBridge implements RegistryObserver {
|
|
|
14
14
|
private networkRing;
|
|
15
15
|
private eventRing;
|
|
16
16
|
private actions;
|
|
17
|
-
private
|
|
17
|
+
private touchStrips;
|
|
18
18
|
private lastRenderSent;
|
|
19
19
|
private pendingTrailing;
|
|
20
20
|
private eventBusOwners;
|
|
21
21
|
private highlightedActionId;
|
|
22
22
|
private highlightedNodeId;
|
|
23
|
+
private _lastProfile;
|
|
24
|
+
private _metricsTimer;
|
|
23
25
|
constructor(server: DevtoolsServer, devtoolsName: string, renderConfig: RenderConfig);
|
|
24
26
|
onRootCreated(actionId: string, root: ReactRoot, meta: {
|
|
25
27
|
actionUuid: string;
|
|
@@ -32,22 +34,48 @@ export declare class DevtoolsBridge implements RegistryObserver {
|
|
|
32
34
|
};
|
|
33
35
|
}): void;
|
|
34
36
|
onRootDestroyed(actionId: string): void;
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
onTouchStripCreated(deviceId: string, root: TouchStripRoot, deviceInfo: DeviceInfo): void;
|
|
38
|
+
onTouchStripColumnChanged(deviceId: string, columns: number[], actionMap: Map<number, string>): void;
|
|
39
|
+
onTouchStripDestroyed(deviceId: string): void;
|
|
38
40
|
onDispatch(actionId: string, event: string, payload: unknown): void;
|
|
39
41
|
onEventBusEmit(bus: object, event: string, payload: unknown): void;
|
|
40
42
|
onConsole(level: string, args: unknown[], stack: string | undefined): void;
|
|
43
|
+
/**
|
|
44
|
+
* Stash the render profile for the next onRender call.
|
|
45
|
+
* In the pipeline, onProfile fires synchronously before onRender,
|
|
46
|
+
* so the stash is always consumed immediately.
|
|
47
|
+
*/
|
|
48
|
+
onProfile(profile: RenderProfile): void;
|
|
49
|
+
private static readonly METRICS_INTERVAL_MS;
|
|
50
|
+
/** Start periodic metrics emission to connected devtools clients. */
|
|
51
|
+
startMetricsEmitter(): void;
|
|
52
|
+
/** Stop periodic metrics emission. */
|
|
53
|
+
stopMetricsEmitter(): void;
|
|
41
54
|
onFetchRequest(id: string, method: string, url: string, headers: Record<string, string>, body?: string): void;
|
|
42
55
|
onFetchResponse(id: string, status: number, statusText: string, headers: Record<string, string>, body: string | undefined, durationMs: number): void;
|
|
43
56
|
onFetchError(id: string, error: string, durationMs: number): void;
|
|
44
57
|
onRender(container: VContainer, dataUri: string): void;
|
|
45
58
|
private throttledRender;
|
|
46
59
|
private emitRender;
|
|
47
|
-
|
|
60
|
+
/** Convert internal RenderProfile to wire-protocol ProfileData. */
|
|
61
|
+
private toProfileData;
|
|
62
|
+
private emitTouchStripRender;
|
|
63
|
+
private static readonly TB_PREFIX;
|
|
48
64
|
private handleHighlight;
|
|
65
|
+
/**
|
|
66
|
+
* Restore a highlighted action or TouchStrip to its normal state.
|
|
67
|
+
* Un-suppresses hardware pushes and restores the original image(s).
|
|
68
|
+
*/
|
|
69
|
+
private restoreHighlight;
|
|
49
70
|
private applyHighlight;
|
|
71
|
+
private applyTouchStripHighlight;
|
|
50
72
|
/** Broadcast highlight render image (or null to clear) to devtools UI. */
|
|
51
73
|
private broadcastHighlightRender;
|
|
74
|
+
/**
|
|
75
|
+
* Clear highlight URIs for the given actionId.
|
|
76
|
+
* For TouchStrip IDs, clears all per-segment keys (touchStrip:*:seg:N).
|
|
77
|
+
* For regular actions, clears the single actionId key.
|
|
78
|
+
*/
|
|
79
|
+
private broadcastHighlightClear;
|
|
52
80
|
buildSnapshot(): SnapshotMessage;
|
|
53
81
|
}
|
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,26 @@ 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
|
+
vnodeConversionMs: profile.vnodeConversionMs,
|
|
343
|
+
takumiRenderMs: profile.takumiRenderMs,
|
|
344
|
+
hashMs: profile.hashMs,
|
|
345
|
+
base64Ms: profile.base64Ms,
|
|
346
|
+
totalMs: profile.totalMs,
|
|
347
|
+
skipped: profile.skipped,
|
|
348
|
+
cacheHit: profile.cacheHit,
|
|
349
|
+
treeDepth: profile.treeDepth,
|
|
350
|
+
nodeCount: profile.nodeCount
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
emitTouchStripRender(deviceId, tb, profile) {
|
|
301
354
|
const tree = serializeVNode(tb.root.vcontainer);
|
|
302
355
|
const segments = [];
|
|
303
356
|
for (const [column, actionId] of tb.columns) {
|
|
@@ -309,7 +362,7 @@ var DevtoolsBridge = class {
|
|
|
309
362
|
});
|
|
310
363
|
}
|
|
311
364
|
const msg = {
|
|
312
|
-
type: "render:
|
|
365
|
+
type: "render:touchStrip",
|
|
313
366
|
ts: Date.now(),
|
|
314
367
|
deviceId,
|
|
315
368
|
canvas: {
|
|
@@ -318,46 +371,72 @@ var DevtoolsBridge = class {
|
|
|
318
371
|
},
|
|
319
372
|
tree,
|
|
320
373
|
segments,
|
|
321
|
-
renderMs: 0
|
|
374
|
+
renderMs: profile?.totalMs ?? 0,
|
|
375
|
+
...profile ? { profile: this.toProfileData(profile) } : {}
|
|
322
376
|
};
|
|
323
377
|
this.server.broadcast(msg);
|
|
324
378
|
}
|
|
379
|
+
static TB_PREFIX = "touchStrip:";
|
|
325
380
|
async handleHighlight(actionId, nodeId) {
|
|
326
381
|
try {
|
|
327
382
|
const prevId = this.highlightedActionId;
|
|
328
383
|
this.highlightedActionId = actionId;
|
|
329
384
|
this.highlightedNodeId = nodeId;
|
|
330
385
|
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);
|
|
386
|
+
await this.restoreHighlight(prevId);
|
|
387
|
+
this.broadcastHighlightClear(prevId);
|
|
337
388
|
}
|
|
338
389
|
if (!actionId || nodeId === null) {
|
|
339
390
|
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);
|
|
391
|
+
await this.restoreHighlight(actionId);
|
|
392
|
+
this.broadcastHighlightClear(actionId);
|
|
346
393
|
}
|
|
347
394
|
this.highlightedActionId = null;
|
|
348
395
|
this.highlightedNodeId = null;
|
|
349
396
|
return;
|
|
350
397
|
}
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
398
|
+
if (actionId.startsWith(DevtoolsBridge.TB_PREFIX)) {
|
|
399
|
+
const deviceId = actionId.slice(DevtoolsBridge.TB_PREFIX.length);
|
|
400
|
+
const tb = this.touchStrips.get(deviceId);
|
|
401
|
+
if (!tb) {
|
|
402
|
+
this.highlightedActionId = null;
|
|
403
|
+
this.highlightedNodeId = null;
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
tb.root.suppressHardwarePush = true;
|
|
407
|
+
await this.applyTouchStripHighlight(actionId, deviceId, nodeId, tb);
|
|
408
|
+
} else {
|
|
409
|
+
const meta = this.actions.get(actionId);
|
|
410
|
+
if (!meta) {
|
|
411
|
+
this.highlightedActionId = null;
|
|
412
|
+
this.highlightedNodeId = null;
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
meta.root.suppressHardwarePush = true;
|
|
416
|
+
await this.applyHighlight(actionId, nodeId, meta);
|
|
356
417
|
}
|
|
357
|
-
meta.root.suppressHardwarePush = true;
|
|
358
|
-
await this.applyHighlight(actionId, nodeId, meta);
|
|
359
418
|
} catch {}
|
|
360
419
|
}
|
|
420
|
+
/**
|
|
421
|
+
* Restore a highlighted action or TouchStrip to its normal state.
|
|
422
|
+
* Un-suppresses hardware pushes and restores the original image(s).
|
|
423
|
+
*/
|
|
424
|
+
async restoreHighlight(id) {
|
|
425
|
+
if (id.startsWith(DevtoolsBridge.TB_PREFIX)) {
|
|
426
|
+
const deviceId = id.slice(DevtoolsBridge.TB_PREFIX.length);
|
|
427
|
+
const tb = this.touchStrips.get(deviceId);
|
|
428
|
+
if (tb) {
|
|
429
|
+
tb.root.suppressHardwarePush = false;
|
|
430
|
+
await tb.root.pushSegmentImages(tb.root.lastSegmentUris);
|
|
431
|
+
}
|
|
432
|
+
} else {
|
|
433
|
+
const prevMeta = this.actions.get(id);
|
|
434
|
+
if (prevMeta) {
|
|
435
|
+
prevMeta.root.suppressHardwarePush = false;
|
|
436
|
+
if (prevMeta.root.lastDataUri) await prevMeta.root.pushImage(prevMeta.root.lastDataUri).catch(() => {});
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
361
440
|
async applyHighlight(actionId, nodeId, meta) {
|
|
362
441
|
try {
|
|
363
442
|
const uri = await renderWithHighlight(meta.root.vcontainer, meta.canvas.width, meta.canvas.height, this.renderConfig, nodeId);
|
|
@@ -367,6 +446,20 @@ var DevtoolsBridge = class {
|
|
|
367
446
|
}
|
|
368
447
|
} catch {}
|
|
369
448
|
}
|
|
449
|
+
async applyTouchStripHighlight(actionId, deviceId, nodeId, tb) {
|
|
450
|
+
try {
|
|
451
|
+
const columns = tb.root.columnNumbers;
|
|
452
|
+
if (columns.length === 0) return;
|
|
453
|
+
const segmentWidth = 200;
|
|
454
|
+
const segmentHeight = 100;
|
|
455
|
+
const fullWidth = (Math.max(...columns) + 1) * segmentWidth;
|
|
456
|
+
const result = await renderTouchStripWithHighlight(tb.root.vcontainer, fullWidth, segmentHeight, columns, segmentWidth, this.renderConfig, nodeId);
|
|
457
|
+
if (result && this.highlightedActionId === actionId && this.highlightedNodeId === nodeId) {
|
|
458
|
+
await tb.root.pushSegmentImages(result.segmentUris);
|
|
459
|
+
for (const [col, uri] of result.segmentUris) this.broadcastHighlightRender(`${actionId}:seg:${col}`, uri);
|
|
460
|
+
}
|
|
461
|
+
} catch {}
|
|
462
|
+
}
|
|
370
463
|
/** Broadcast highlight render image (or null to clear) to devtools UI. */
|
|
371
464
|
broadcastHighlightRender(actionId, dataUri) {
|
|
372
465
|
const msg = {
|
|
@@ -377,6 +470,18 @@ var DevtoolsBridge = class {
|
|
|
377
470
|
};
|
|
378
471
|
this.server.broadcast(msg);
|
|
379
472
|
}
|
|
473
|
+
/**
|
|
474
|
+
* Clear highlight URIs for the given actionId.
|
|
475
|
+
* For TouchStrip IDs, clears all per-segment keys (touchStrip:*:seg:N).
|
|
476
|
+
* For regular actions, clears the single actionId key.
|
|
477
|
+
*/
|
|
478
|
+
broadcastHighlightClear(id) {
|
|
479
|
+
if (id.startsWith(DevtoolsBridge.TB_PREFIX)) {
|
|
480
|
+
const deviceId = id.slice(DevtoolsBridge.TB_PREFIX.length);
|
|
481
|
+
const tb = this.touchStrips.get(deviceId);
|
|
482
|
+
if (tb) for (const col of tb.root.columnNumbers) this.broadcastHighlightRender(`${id}:seg:${col}`, null);
|
|
483
|
+
} else this.broadcastHighlightRender(id, null);
|
|
484
|
+
}
|
|
380
485
|
buildSnapshot() {
|
|
381
486
|
const actions = [];
|
|
382
487
|
for (const [actionId, meta] of this.actions) {
|
|
@@ -402,8 +507,8 @@ var DevtoolsBridge = class {
|
|
|
402
507
|
dataUri: meta.root.lastDataUri
|
|
403
508
|
});
|
|
404
509
|
}
|
|
405
|
-
const
|
|
406
|
-
for (const [deviceId, tb] of this.
|
|
510
|
+
const touchStrips = [];
|
|
511
|
+
for (const [deviceId, tb] of this.touchStrips) {
|
|
407
512
|
let tree = null;
|
|
408
513
|
try {
|
|
409
514
|
tree = serializeVNode(tb.root.vcontainer);
|
|
@@ -414,7 +519,7 @@ var DevtoolsBridge = class {
|
|
|
414
519
|
actionId,
|
|
415
520
|
dataUri: tb.root.lastSegmentUris.get(column) ?? null
|
|
416
521
|
});
|
|
417
|
-
|
|
522
|
+
touchStrips.push({
|
|
418
523
|
deviceId,
|
|
419
524
|
deviceName: tb.deviceInfo.name,
|
|
420
525
|
canvas: {
|
|
@@ -429,10 +534,11 @@ var DevtoolsBridge = class {
|
|
|
429
534
|
type: "snapshot",
|
|
430
535
|
ts: Date.now(),
|
|
431
536
|
actions,
|
|
432
|
-
|
|
537
|
+
touchStrips,
|
|
433
538
|
recentConsole: this.consoleRing.toArray(),
|
|
434
539
|
recentNetwork: this.networkRing.toArray(),
|
|
435
|
-
recentEvents: this.eventRing.toArray()
|
|
540
|
+
recentEvents: this.eventRing.toArray(),
|
|
541
|
+
metrics: metrics.snapshot()
|
|
436
542
|
};
|
|
437
543
|
}
|
|
438
544
|
};
|
|
@@ -1,3 +1,8 @@
|
|
|
1
1
|
import { VContainer } from '../reconciler/vnode';
|
|
2
2
|
import { RenderConfig } from '../render/pipeline';
|
|
3
3
|
export declare function renderWithHighlight(container: VContainer, width: number, height: number, config: RenderConfig, targetNid: number): Promise<string | null>;
|
|
4
|
+
export interface TouchStripHighlightResult {
|
|
5
|
+
/** Per-column data URIs for ALL segments. */
|
|
6
|
+
segmentUris: Map<number, string>;
|
|
7
|
+
}
|
|
8
|
+
export declare function renderTouchStripWithHighlight(container: VContainer, fullWidth: number, segmentHeight: number, columns: number[], segmentWidth: number, config: RenderConfig, targetNid: number): Promise<TouchStripHighlightResult | null>;
|
|
@@ -1,92 +1,142 @@
|
|
|
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
|
+
var TOUCHSTRIP_HIGHLIGHT_FORMAT = "png";
|
|
22
|
+
async function renderTouchStripWithHighlight(container, fullWidth, segmentHeight, columns, segmentWidth, config, targetNid) {
|
|
23
|
+
if (container.children.length === 0) return null;
|
|
24
|
+
const effectiveNid = resolveTargetNid(container, targetNid);
|
|
25
|
+
const rootNode = buildTakumiRoot(container);
|
|
27
26
|
const counter = { value: 0 };
|
|
28
27
|
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
|
-
|
|
28
|
+
const target = findTargetTakumiNode(container.children, rootNode.children ?? [], effectiveNid, counter);
|
|
29
|
+
if (!target) return null;
|
|
30
|
+
injectHighlightOverlay(target);
|
|
31
|
+
const segmentUris = /* @__PURE__ */ new Map();
|
|
32
|
+
const segmentPromises = columns.map(async (column) => {
|
|
33
|
+
const clipNode = {
|
|
34
|
+
type: "container",
|
|
35
|
+
style: {
|
|
36
|
+
width: segmentWidth,
|
|
37
|
+
height: segmentHeight,
|
|
38
|
+
overflow: "hidden"
|
|
39
|
+
},
|
|
40
|
+
children: [{
|
|
41
|
+
type: "container",
|
|
42
|
+
style: {
|
|
43
|
+
display: "flex",
|
|
44
|
+
width: fullWidth,
|
|
45
|
+
height: segmentHeight,
|
|
46
|
+
marginLeft: -(column * segmentWidth)
|
|
47
|
+
},
|
|
48
|
+
children: rootNode.children
|
|
49
|
+
}]
|
|
50
|
+
};
|
|
51
|
+
const segBuffer = await config.renderer.render(clipNode, {
|
|
52
|
+
width: segmentWidth,
|
|
53
|
+
height: segmentHeight,
|
|
54
|
+
format: TOUCHSTRIP_HIGHLIGHT_FORMAT,
|
|
55
|
+
devicePixelRatio: config.devicePixelRatio
|
|
56
|
+
});
|
|
57
|
+
segmentUris.set(column, bufferToDataUri(segBuffer, TOUCHSTRIP_HIGHLIGHT_FORMAT));
|
|
58
|
+
});
|
|
59
|
+
await Promise.all(segmentPromises);
|
|
60
|
+
return { segmentUris };
|
|
51
61
|
}
|
|
52
|
-
function
|
|
53
|
-
|
|
62
|
+
function injectHighlightOverlay(target) {
|
|
63
|
+
const node = target.node;
|
|
64
|
+
const overlay = {
|
|
65
|
+
type: "container",
|
|
66
|
+
style: {
|
|
67
|
+
position: "absolute",
|
|
68
|
+
top: 0,
|
|
69
|
+
left: 0,
|
|
70
|
+
width: "100%",
|
|
71
|
+
height: "100%",
|
|
72
|
+
backgroundColor: HIGHLIGHT_BG,
|
|
73
|
+
boxShadow: HIGHLIGHT_BOX_SHADOW
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
if (node.type === "container") {
|
|
77
|
+
node.style = {
|
|
78
|
+
...node.style ?? {},
|
|
79
|
+
position: "relative"
|
|
80
|
+
};
|
|
81
|
+
if (!node.children) node.children = [];
|
|
82
|
+
node.children.push(overlay);
|
|
83
|
+
} else {
|
|
84
|
+
const wrapper = {
|
|
85
|
+
type: "container",
|
|
86
|
+
style: { position: "relative" },
|
|
87
|
+
children: [target.node, overlay]
|
|
88
|
+
};
|
|
89
|
+
target.parentChildren[target.indexInParent] = wrapper;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function findTargetTakumiNode(vnodes, takumiNodes, targetNid, counter) {
|
|
93
|
+
let idx = 0;
|
|
54
94
|
for (const vnode of vnodes) {
|
|
55
95
|
const nid = counter.value++;
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
if (!
|
|
59
|
-
const absX = parentX + measured.transform[4];
|
|
60
|
-
const absY = parentY + measured.transform[5];
|
|
96
|
+
const takumiNode = takumiNodes[idx];
|
|
97
|
+
idx++;
|
|
98
|
+
if (!takumiNode) continue;
|
|
61
99
|
if (nid === targetNid) return {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
height: measured.height
|
|
100
|
+
node: takumiNode,
|
|
101
|
+
parentChildren: takumiNodes,
|
|
102
|
+
indexInParent: idx - 1
|
|
66
103
|
};
|
|
67
|
-
|
|
104
|
+
if (vnode.type === "#text" || vnode.type === "img") continue;
|
|
105
|
+
if (vnode.type === "svg") {
|
|
106
|
+
advanceCounterThroughSubtree(vnode.children, counter);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
const childTakumiNodes = takumiNode.children ?? [];
|
|
110
|
+
const found = findTargetTakumiNode(vnode.children, childTakumiNodes, targetNid, counter);
|
|
68
111
|
if (found) return found;
|
|
69
112
|
}
|
|
70
113
|
return null;
|
|
71
114
|
}
|
|
115
|
+
function advanceCounterThroughSubtree(vnodes, counter) {
|
|
116
|
+
for (const node of vnodes) {
|
|
117
|
+
counter.value++;
|
|
118
|
+
advanceCounterThroughSubtree(node.children, counter);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
72
121
|
function resolveTargetNid(container, targetNid) {
|
|
73
122
|
const counter = { value: 0 };
|
|
74
123
|
counter.value++;
|
|
75
124
|
for (const child of container.children) {
|
|
76
|
-
const resolved = resolveInSubtree(child, targetNid, counter, 0);
|
|
125
|
+
const resolved = resolveInSubtree(child, targetNid, counter, 0, false);
|
|
77
126
|
if (resolved !== null) return resolved;
|
|
78
127
|
}
|
|
79
128
|
return targetNid;
|
|
80
129
|
}
|
|
81
|
-
function resolveInSubtree(node, targetNid, counter, parentNid) {
|
|
130
|
+
function resolveInSubtree(node, targetNid, counter, parentNid, insideSvg) {
|
|
82
131
|
const nid = counter.value++;
|
|
83
|
-
if (nid === targetNid) return node.type === "#text" ? parentNid : nid;
|
|
132
|
+
if (nid === targetNid) return node.type === "#text" || insideSvg ? parentNid : nid;
|
|
84
133
|
if (node.type === "#text") return null;
|
|
134
|
+
const childInsideSvg = insideSvg || node.type === "svg";
|
|
85
135
|
for (const child of node.children) {
|
|
86
|
-
const resolved = resolveInSubtree(child, targetNid, counter, nid);
|
|
136
|
+
const resolved = resolveInSubtree(child, targetNid, counter, nid, childInsideSvg);
|
|
87
137
|
if (resolved !== null) return resolved;
|
|
88
138
|
}
|
|
89
139
|
return null;
|
|
90
140
|
}
|
|
91
141
|
//#endregion
|
|
92
|
-
export { renderWithHighlight };
|
|
142
|
+
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
|
}
|