@three-flatland/devtools 0.1.0-alpha.3
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 +21 -0
- package/README.md +82 -0
- package/dist/buffers-modal.cjs +491 -0
- package/dist/buffers-modal.cjs.map +1 -0
- package/dist/buffers-modal.d.cts +62 -0
- package/dist/buffers-modal.d.ts +62 -0
- package/dist/buffers-modal.js +467 -0
- package/dist/buffers-modal.js.map +1 -0
- package/dist/buffers-view.cjs +269 -0
- package/dist/buffers-view.cjs.map +1 -0
- package/dist/buffers-view.d.cts +39 -0
- package/dist/buffers-view.d.ts +39 -0
- package/dist/buffers-view.js +245 -0
- package/dist/buffers-view.js.map +1 -0
- package/dist/create-pane.cjs +203 -0
- package/dist/create-pane.cjs.map +1 -0
- package/dist/create-pane.d.cts +66 -0
- package/dist/create-pane.d.ts +66 -0
- package/dist/create-pane.js +178 -0
- package/dist/create-pane.js.map +1 -0
- package/dist/dashboard/app.tsx +42 -0
- package/dist/dashboard/client.ts +21 -0
- package/dist/dashboard/export.ts +84 -0
- package/dist/dashboard/hooks.ts +75 -0
- package/dist/dashboard/index.html +1149 -0
- package/dist/dashboard/index.tsx +18 -0
- package/dist/dashboard/panels/batches.tsx +282 -0
- package/dist/dashboard/panels/buffers.tsx +425 -0
- package/dist/dashboard/panels/env.tsx +85 -0
- package/dist/dashboard/panels/header-stats.tsx +30 -0
- package/dist/dashboard/panels/producer-select.tsx +81 -0
- package/dist/dashboard/panels/protocol-log.tsx +743 -0
- package/dist/dashboard/panels/registry.tsx +642 -0
- package/dist/dashboard/panels/stats.tsx +431 -0
- package/dist/dashboard/protocol-store.ts +312 -0
- package/dist/dashboard/tsconfig.json +19 -0
- package/dist/dashboard/vendor/hooks.module.js +1 -0
- package/dist/dashboard/vendor/jsx-runtime.js +22 -0
- package/dist/dashboard/vendor/preact.module.js +12 -0
- package/dist/devtools-client.cjs +870 -0
- package/dist/devtools-client.cjs.map +1 -0
- package/dist/devtools-client.d.cts +362 -0
- package/dist/devtools-client.d.ts +362 -0
- package/dist/devtools-client.js +852 -0
- package/dist/devtools-client.js.map +1 -0
- package/dist/index.cjs +42 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/perf-trace.cjs +103 -0
- package/dist/perf-trace.cjs.map +1 -0
- package/dist/perf-trace.d.cts +27 -0
- package/dist/perf-trace.d.ts +27 -0
- package/dist/perf-trace.js +79 -0
- package/dist/perf-trace.js.map +1 -0
- package/dist/plugins.cjs +44 -0
- package/dist/plugins.cjs.map +1 -0
- package/dist/plugins.d.cts +7 -0
- package/dist/plugins.d.ts +7 -0
- package/dist/plugins.js +9 -0
- package/dist/plugins.js.map +1 -0
- package/dist/provider-switcher.cjs +95 -0
- package/dist/provider-switcher.cjs.map +1 -0
- package/dist/provider-switcher.d.cts +18 -0
- package/dist/provider-switcher.d.ts +18 -0
- package/dist/provider-switcher.js +71 -0
- package/dist/provider-switcher.js.map +1 -0
- package/dist/react/devtools-provider.cjs +59 -0
- package/dist/react/devtools-provider.cjs.map +1 -0
- package/dist/react/devtools-provider.d.cts +39 -0
- package/dist/react/devtools-provider.d.ts +39 -0
- package/dist/react/devtools-provider.js +38 -0
- package/dist/react/devtools-provider.js.map +1 -0
- package/dist/react/use-fps-graph.cjs +49 -0
- package/dist/react/use-fps-graph.cjs.map +1 -0
- package/dist/react/use-fps-graph.d.cts +15 -0
- package/dist/react/use-fps-graph.d.ts +15 -0
- package/dist/react/use-fps-graph.js +25 -0
- package/dist/react/use-fps-graph.js.map +1 -0
- package/dist/react/use-pane-button.cjs +58 -0
- package/dist/react/use-pane-button.cjs.map +1 -0
- package/dist/react/use-pane-button.d.cts +10 -0
- package/dist/react/use-pane-button.d.ts +10 -0
- package/dist/react/use-pane-button.js +34 -0
- package/dist/react/use-pane-button.js.map +1 -0
- package/dist/react/use-pane-folder.cjs +48 -0
- package/dist/react/use-pane-folder.cjs.map +1 -0
- package/dist/react/use-pane-folder.d.cts +15 -0
- package/dist/react/use-pane-folder.d.ts +15 -0
- package/dist/react/use-pane-folder.js +24 -0
- package/dist/react/use-pane-folder.js.map +1 -0
- package/dist/react/use-pane-input.cjs +70 -0
- package/dist/react/use-pane-input.cjs.map +1 -0
- package/dist/react/use-pane-input.d.cts +44 -0
- package/dist/react/use-pane-input.d.ts +44 -0
- package/dist/react/use-pane-input.js +46 -0
- package/dist/react/use-pane-input.js.map +1 -0
- package/dist/react/use-pane-radio-grid.cjs +78 -0
- package/dist/react/use-pane-radio-grid.cjs.map +1 -0
- package/dist/react/use-pane-radio-grid.d.cts +35 -0
- package/dist/react/use-pane-radio-grid.d.ts +35 -0
- package/dist/react/use-pane-radio-grid.js +54 -0
- package/dist/react/use-pane-radio-grid.js.map +1 -0
- package/dist/react/use-pane.cjs +45 -0
- package/dist/react/use-pane.cjs.map +1 -0
- package/dist/react/use-pane.d.cts +40 -0
- package/dist/react/use-pane.d.ts +40 -0
- package/dist/react/use-pane.js +21 -0
- package/dist/react/use-pane.js.map +1 -0
- package/dist/react.cjs +50 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +12 -0
- package/dist/react.d.ts +12 -0
- package/dist/react.js +19 -0
- package/dist/react.js.map +1 -0
- package/dist/registry-view.cjs +371 -0
- package/dist/registry-view.cjs.map +1 -0
- package/dist/registry-view.d.cts +24 -0
- package/dist/registry-view.d.ts +24 -0
- package/dist/registry-view.js +347 -0
- package/dist/registry-view.js.map +1 -0
- package/dist/stats-graph.cjs +297 -0
- package/dist/stats-graph.cjs.map +1 -0
- package/dist/stats-graph.d.cts +40 -0
- package/dist/stats-graph.d.ts +40 -0
- package/dist/stats-graph.js +273 -0
- package/dist/stats-graph.js.map +1 -0
- package/dist/stats-row.cjs +153 -0
- package/dist/stats-row.cjs.map +1 -0
- package/dist/stats-row.d.cts +24 -0
- package/dist/stats-row.d.ts +24 -0
- package/dist/stats-row.js +129 -0
- package/dist/stats-row.js.map +1 -0
- package/dist/theme.cjs +260 -0
- package/dist/theme.cjs.map +1 -0
- package/dist/theme.d.cts +10 -0
- package/dist/theme.d.ts +10 -0
- package/dist/theme.js +235 -0
- package/dist/theme.js.map +1 -0
- package/dist/vite-plugin.cjs +113 -0
- package/dist/vite-plugin.cjs.map +1 -0
- package/dist/vite-plugin.d.cts +9 -0
- package/dist/vite-plugin.d.ts +9 -0
- package/dist/vite-plugin.js +88 -0
- package/dist/vite-plugin.js.map +1 -0
- package/package.json +124 -0
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var buffers_view_exports = {};
|
|
20
|
+
__export(buffers_view_exports, {
|
|
21
|
+
addBuffersView: () => addBuffersView
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(buffers_view_exports);
|
|
24
|
+
const THUMB_WIDTH = 240;
|
|
25
|
+
const THUMB_HEIGHT = 120;
|
|
26
|
+
function addBuffersView(parent, client, options = {}) {
|
|
27
|
+
const blade = parent.addBlade({ view: "separator" });
|
|
28
|
+
const bladeEl = blade.element;
|
|
29
|
+
bladeEl.innerHTML = "";
|
|
30
|
+
bladeEl.className = "tp-cntv";
|
|
31
|
+
bladeEl.style.cssText = "display:none;flex-direction:column;background:rgba(0,0,0,0.3)";
|
|
32
|
+
const header = document.createElement("div");
|
|
33
|
+
header.style.cssText = [
|
|
34
|
+
"display:grid",
|
|
35
|
+
"grid-template-columns:2em 1fr 2em",
|
|
36
|
+
"align-items:center",
|
|
37
|
+
"padding:4px 6px 6px",
|
|
38
|
+
"font-size:11px",
|
|
39
|
+
"color:var(--tp-label-foreground-color)",
|
|
40
|
+
"user-select:none",
|
|
41
|
+
"-webkit-user-select:none",
|
|
42
|
+
"font-variant-numeric:tabular-nums",
|
|
43
|
+
"cursor:pointer"
|
|
44
|
+
].join(";");
|
|
45
|
+
const arrowStyle = "cursor:pointer;padding:0 4px;opacity:0.8;font-family:ui-monospace,monospace;text-align:center";
|
|
46
|
+
const prevBtn = document.createElement("span");
|
|
47
|
+
prevBtn.textContent = "\u25C0";
|
|
48
|
+
prevBtn.style.cssText = arrowStyle;
|
|
49
|
+
prevBtn.setAttribute("role", "button");
|
|
50
|
+
prevBtn.setAttribute("aria-label", "Previous buffer");
|
|
51
|
+
const nameLabel = document.createElement("span");
|
|
52
|
+
nameLabel.style.cssText = "font-weight:500;text-align:center;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;pointer-events:none";
|
|
53
|
+
const nextBtn = document.createElement("span");
|
|
54
|
+
nextBtn.textContent = "\u25B6";
|
|
55
|
+
nextBtn.style.cssText = arrowStyle;
|
|
56
|
+
nextBtn.setAttribute("role", "button");
|
|
57
|
+
nextBtn.setAttribute("aria-label", "Next buffer");
|
|
58
|
+
header.appendChild(prevBtn);
|
|
59
|
+
header.appendChild(nameLabel);
|
|
60
|
+
header.appendChild(nextBtn);
|
|
61
|
+
const body = document.createElement("div");
|
|
62
|
+
body.style.cssText = "display:none;flex-direction:column;padding:2px 6px 6px;position:relative";
|
|
63
|
+
const canvas = document.createElement("canvas");
|
|
64
|
+
canvas.width = THUMB_WIDTH;
|
|
65
|
+
canvas.height = THUMB_HEIGHT;
|
|
66
|
+
canvas.style.cssText = `display:block;width:100%;height:${THUMB_HEIGHT}px;background:rgba(0,2,28,0.45);border-radius:2px;image-rendering:pixelated`;
|
|
67
|
+
const offscreen = document.createElement("canvas");
|
|
68
|
+
offscreen.width = 1;
|
|
69
|
+
offscreen.height = 1;
|
|
70
|
+
const valueLabel = document.createElement("span");
|
|
71
|
+
valueLabel.style.cssText = [
|
|
72
|
+
"position:absolute",
|
|
73
|
+
"left:10px",
|
|
74
|
+
"bottom:10px",
|
|
75
|
+
"padding:1px 4px",
|
|
76
|
+
"font-size:10px",
|
|
77
|
+
"background:rgba(0,2,28,0.65)",
|
|
78
|
+
"border-radius:2px",
|
|
79
|
+
"color:var(--tp-monitor-foreground-color)",
|
|
80
|
+
"opacity:0.85",
|
|
81
|
+
"font-family:var(--tp-base-font-family, ui-monospace, monospace)",
|
|
82
|
+
"font-variant-numeric:tabular-nums",
|
|
83
|
+
"pointer-events:none"
|
|
84
|
+
].join(";");
|
|
85
|
+
const expandBtn = document.createElement("span");
|
|
86
|
+
expandBtn.textContent = "\u2922";
|
|
87
|
+
expandBtn.setAttribute("role", "button");
|
|
88
|
+
expandBtn.setAttribute("aria-label", "Open fullscreen buffer viewer");
|
|
89
|
+
expandBtn.title = "Open fullscreen buffer viewer";
|
|
90
|
+
expandBtn.style.cssText = [
|
|
91
|
+
"position:absolute",
|
|
92
|
+
"right:10px",
|
|
93
|
+
"bottom:10px",
|
|
94
|
+
"padding:1px 4px",
|
|
95
|
+
"font-size:11px",
|
|
96
|
+
"background:rgba(0,2,28,0.65)",
|
|
97
|
+
"border-radius:2px",
|
|
98
|
+
"cursor:pointer",
|
|
99
|
+
"color:var(--tp-monitor-foreground-color)",
|
|
100
|
+
"user-select:none",
|
|
101
|
+
"-webkit-user-select:none"
|
|
102
|
+
].join(";");
|
|
103
|
+
body.appendChild(canvas);
|
|
104
|
+
body.appendChild(valueLabel);
|
|
105
|
+
body.appendChild(expandBtn);
|
|
106
|
+
bladeEl.appendChild(header);
|
|
107
|
+
bladeEl.appendChild(body);
|
|
108
|
+
let collapsed = true;
|
|
109
|
+
let paneFolded = false;
|
|
110
|
+
let activeName = null;
|
|
111
|
+
let lastRenderedVersion = -1;
|
|
112
|
+
const ctx = canvas.getContext("2d");
|
|
113
|
+
let cachedImgData = null;
|
|
114
|
+
let cachedImgDataW = 0;
|
|
115
|
+
let cachedImgDataH = 0;
|
|
116
|
+
const resizeObserver = new ResizeObserver(([entry]) => {
|
|
117
|
+
if (!entry) return;
|
|
118
|
+
const dpr = window.devicePixelRatio || 1;
|
|
119
|
+
const cssW = Math.max(1, Math.round(entry.contentRect.width));
|
|
120
|
+
const cssH = Math.max(1, Math.round(entry.contentRect.height));
|
|
121
|
+
const w = Math.max(1, Math.round(cssW * dpr));
|
|
122
|
+
const h = Math.max(1, Math.round(cssH * dpr));
|
|
123
|
+
if (canvas.width !== w || canvas.height !== h) {
|
|
124
|
+
canvas.width = w;
|
|
125
|
+
canvas.height = h;
|
|
126
|
+
lastRenderedVersion = -1;
|
|
127
|
+
const cur = activeName !== null ? client.state.buffers.get(activeName) : void 0;
|
|
128
|
+
if (cur && !collapsed) renderThumb(cur);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
resizeObserver.observe(canvas);
|
|
132
|
+
function currentNames() {
|
|
133
|
+
return Array.from(client.state.buffers.keys());
|
|
134
|
+
}
|
|
135
|
+
let modalOpen = false;
|
|
136
|
+
function syncSelection() {
|
|
137
|
+
if (modalOpen) return;
|
|
138
|
+
if (paneFolded || collapsed || activeName === null) {
|
|
139
|
+
client.setBuffers({});
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
client.setBuffers({ [activeName]: { mode: "thumbnail", thumbSize: 256 } });
|
|
143
|
+
}
|
|
144
|
+
function setActive(name) {
|
|
145
|
+
if (activeName === name) return;
|
|
146
|
+
activeName = name;
|
|
147
|
+
nameLabel.textContent = name ?? "\u2014";
|
|
148
|
+
lastRenderedVersion = -1;
|
|
149
|
+
syncSelection();
|
|
150
|
+
}
|
|
151
|
+
function cycle(delta) {
|
|
152
|
+
const names = currentNames();
|
|
153
|
+
if (names.length === 0) return;
|
|
154
|
+
const idx = activeName === null ? -1 : names.indexOf(activeName);
|
|
155
|
+
const next = names[(idx + delta + names.length) % names.length];
|
|
156
|
+
setActive(next);
|
|
157
|
+
}
|
|
158
|
+
const toggleCollapse = (e) => {
|
|
159
|
+
e.stopPropagation();
|
|
160
|
+
collapsed = !collapsed;
|
|
161
|
+
body.style.display = collapsed ? "none" : "flex";
|
|
162
|
+
if (!collapsed) lastRenderedVersion = -1;
|
|
163
|
+
syncSelection();
|
|
164
|
+
};
|
|
165
|
+
header.addEventListener("click", toggleCollapse);
|
|
166
|
+
prevBtn.addEventListener("click", (e) => {
|
|
167
|
+
e.stopPropagation();
|
|
168
|
+
cycle(-1);
|
|
169
|
+
});
|
|
170
|
+
nextBtn.addEventListener("click", (e) => {
|
|
171
|
+
e.stopPropagation();
|
|
172
|
+
cycle(1);
|
|
173
|
+
});
|
|
174
|
+
expandBtn.addEventListener("click", (e) => {
|
|
175
|
+
e.stopPropagation();
|
|
176
|
+
if (activeName !== null && options.onExpand !== void 0) {
|
|
177
|
+
options.onExpand(activeName);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
function renderThumb(entry) {
|
|
181
|
+
if (!ctx) return;
|
|
182
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
183
|
+
const { width, height, pixels } = entry;
|
|
184
|
+
if (width === 0 || height === 0 || pixels === null) return;
|
|
185
|
+
if (offscreen.width !== width || offscreen.height !== height) {
|
|
186
|
+
offscreen.width = width;
|
|
187
|
+
offscreen.height = height;
|
|
188
|
+
cachedImgData = null;
|
|
189
|
+
}
|
|
190
|
+
const offCtx = offscreen.getContext("2d");
|
|
191
|
+
if (!offCtx) return;
|
|
192
|
+
if (cachedImgData === null || cachedImgDataW !== width || cachedImgDataH !== height) {
|
|
193
|
+
cachedImgData = offCtx.createImageData(width, height);
|
|
194
|
+
cachedImgDataW = width;
|
|
195
|
+
cachedImgDataH = height;
|
|
196
|
+
}
|
|
197
|
+
const imgData = cachedImgData;
|
|
198
|
+
const out = imgData.data;
|
|
199
|
+
if (pixels instanceof Uint8Array) {
|
|
200
|
+
out.set(pixels.subarray(0, out.length));
|
|
201
|
+
} else {
|
|
202
|
+
const count = width * height;
|
|
203
|
+
for (let i = 0; i < count; i++) {
|
|
204
|
+
const o = i * 4;
|
|
205
|
+
out[o] = Math.round(Math.max(0, Math.min(1, pixels[i * 4] ?? 0)) * 255);
|
|
206
|
+
out[o + 1] = Math.round(Math.max(0, Math.min(1, pixels[i * 4 + 1] ?? 0)) * 255);
|
|
207
|
+
out[o + 2] = Math.round(Math.max(0, Math.min(1, pixels[i * 4 + 2] ?? 0)) * 255);
|
|
208
|
+
out[o + 3] = Math.round(Math.max(0, Math.min(1, pixels[i * 4 + 3] ?? 1)) * 255);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
offCtx.putImageData(imgData, 0, 0);
|
|
212
|
+
ctx.imageSmoothingEnabled = false;
|
|
213
|
+
ctx.drawImage(offscreen, 0, 0, canvas.width, canvas.height);
|
|
214
|
+
}
|
|
215
|
+
const unsubscribe = client.addListener((state) => {
|
|
216
|
+
const names = Array.from(state.buffers.keys());
|
|
217
|
+
bladeEl.style.display = names.length > 0 ? "flex" : "none";
|
|
218
|
+
if (names.length === 0) {
|
|
219
|
+
setActive(null);
|
|
220
|
+
} else if (activeName === null || !names.includes(activeName)) {
|
|
221
|
+
setActive(names[0]);
|
|
222
|
+
}
|
|
223
|
+
const multi = names.length > 1;
|
|
224
|
+
prevBtn.style.opacity = multi ? "0.8" : "0.25";
|
|
225
|
+
nextBtn.style.opacity = multi ? "0.8" : "0.25";
|
|
226
|
+
prevBtn.style.cursor = multi ? "pointer" : "default";
|
|
227
|
+
nextBtn.style.cursor = multi ? "pointer" : "default";
|
|
228
|
+
syncSelection();
|
|
229
|
+
if (collapsed || activeName === null) return;
|
|
230
|
+
const entry = state.buffers.get(activeName);
|
|
231
|
+
if (!entry) return;
|
|
232
|
+
if (entry.version === lastRenderedVersion) return;
|
|
233
|
+
lastRenderedVersion = entry.version;
|
|
234
|
+
renderThumb(entry);
|
|
235
|
+
const lw = entry.srcWidth > 0 ? entry.srcWidth : entry.width;
|
|
236
|
+
const lh = entry.srcHeight > 0 ? entry.srcHeight : entry.height;
|
|
237
|
+
valueLabel.textContent = `${lw}\xD7${lh} \xB7 ${entry.pixelType}`;
|
|
238
|
+
nameLabel.title = `${entry.name}${entry.label ? ` \u2014 ${entry.label}` : ""}`;
|
|
239
|
+
});
|
|
240
|
+
return {
|
|
241
|
+
element: bladeEl,
|
|
242
|
+
setModalOpen(open) {
|
|
243
|
+
modalOpen = open;
|
|
244
|
+
if (!open) syncSelection();
|
|
245
|
+
},
|
|
246
|
+
setActiveFromModal(name) {
|
|
247
|
+
if (activeName === name) return;
|
|
248
|
+
activeName = name;
|
|
249
|
+
nameLabel.textContent = name;
|
|
250
|
+
lastRenderedVersion = -1;
|
|
251
|
+
},
|
|
252
|
+
setPaneFolded(folded) {
|
|
253
|
+
if (paneFolded === folded) return;
|
|
254
|
+
paneFolded = folded;
|
|
255
|
+
if (folded) lastRenderedVersion = -1;
|
|
256
|
+
syncSelection();
|
|
257
|
+
},
|
|
258
|
+
dispose() {
|
|
259
|
+
resizeObserver.disconnect();
|
|
260
|
+
unsubscribe();
|
|
261
|
+
blade.dispose();
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
266
|
+
0 && (module.exports = {
|
|
267
|
+
addBuffersView
|
|
268
|
+
});
|
|
269
|
+
//# sourceMappingURL=buffers-view.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/buffers-view.ts"],"sourcesContent":["/**\n * Buffers view — compact one-row preview of the currently-selected\n * debug buffer, with `◀ name ▶` arrows to cycle through every\n * registered entry and a ⤢ \"expand\" icon overlay on the thumbnail\n * that opens (a future) fullscreen modal.\n *\n * Main-pane real estate is tight: there's no grouping UI here. The\n * fullscreen modal is where the group tree / channel toggles / pan &\n * zoom live. Starts collapsed — no pixel readback happens until the\n * user opens the blade.\n */\n\nimport type { FolderApi, Pane } from 'tweakpane'\n\nimport type { BufferSnapshot, DevtoolsClient } from './devtools-client.js'\n\nexport interface BuffersViewHandle {\n readonly element: HTMLElement\n /** Tell the view the modal is open/closed. While open, the view\n * defers buffer selection to the modal. */\n setModalOpen(open: boolean): void\n /** Sync the view's active buffer to match the modal's selection. */\n setActiveFromModal(name: string): void\n /** Tell the view the outer pane was folded/unfolded. When folded the\n * view unsubscribes regardless of its own collapse state. */\n setPaneFolded(folded: boolean): void\n dispose(): void\n}\n\nconst THUMB_WIDTH = 240\n// A bit taller than before now that the size/format text overlays the\n// canvas instead of taking its own row below.\nconst THUMB_HEIGHT = 120\n\nexport interface AddBuffersViewOptions {\n /**\n * Invoked when the user clicks the bottom-right ⤢ button. Wired by\n * `createPane` to open the fullscreen modal viewer with the active\n * buffer pre-selected. When omitted, the click is a no-op.\n */\n onExpand?: (name: string) => void\n}\n\nexport function addBuffersView(\n parent: Pane | FolderApi,\n client: DevtoolsClient,\n options: AddBuffersViewOptions = {},\n): BuffersViewHandle {\n const blade = parent.addBlade({ view: 'separator' }) as unknown as {\n element: HTMLElement\n dispose(): void\n }\n const bladeEl = blade.element\n bladeEl.innerHTML = ''\n bladeEl.className = 'tp-cntv'\n // Same darker-bg treatment as registry-view to sink debug readouts\n // visually behind the controls.\n bladeEl.style.cssText = 'display:none;flex-direction:column;background:rgba(0,0,0,0.3)'\n\n // Header — identical pattern to registry-view (click to collapse,\n // arrows cycle with stopPropagation so they don't also toggle).\n const header = document.createElement('div')\n header.style.cssText = [\n 'display:grid',\n 'grid-template-columns:2em 1fr 2em',\n 'align-items:center',\n 'padding:4px 6px 6px',\n 'font-size:11px',\n 'color:var(--tp-label-foreground-color)',\n 'user-select:none',\n '-webkit-user-select:none',\n 'font-variant-numeric:tabular-nums',\n 'cursor:pointer',\n ].join(';')\n\n const arrowStyle = 'cursor:pointer;padding:0 4px;opacity:0.8;font-family:ui-monospace,monospace;text-align:center'\n const prevBtn = document.createElement('span')\n prevBtn.textContent = '◀'\n prevBtn.style.cssText = arrowStyle\n prevBtn.setAttribute('role', 'button')\n prevBtn.setAttribute('aria-label', 'Previous buffer')\n const nameLabel = document.createElement('span')\n nameLabel.style.cssText = 'font-weight:500;text-align:center;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;pointer-events:none'\n const nextBtn = document.createElement('span')\n nextBtn.textContent = '▶'\n nextBtn.style.cssText = arrowStyle\n nextBtn.setAttribute('role', 'button')\n nextBtn.setAttribute('aria-label', 'Next buffer')\n header.appendChild(prevBtn)\n header.appendChild(nameLabel)\n header.appendChild(nextBtn)\n\n // Body — thumbnail canvas with two corner overlays: dimensions/format\n // bottom-left, expand icon bottom-right. No separate text row, so the\n // canvas itself gets the full body height.\n const body = document.createElement('div')\n body.style.cssText = 'display:none;flex-direction:column;padding:2px 6px 6px;position:relative'\n\n // Display canvas. Backing dimensions are kept in sync with the\n // actual rendered CSS box via ResizeObserver — same pattern as\n // `stats-graph.ts`. Without this, the bitmap stays at its initial\n // 240×96 intrinsic size and (depending on the parent layout) ends up\n // displayed unscaled in the upper-left of a stretched-CSS box.\n const canvas = document.createElement('canvas')\n canvas.width = THUMB_WIDTH\n canvas.height = THUMB_HEIGHT\n canvas.style.cssText = `display:block;width:100%;height:${THUMB_HEIGHT}px;background:rgba(0,2,28,0.45);border-radius:2px;image-rendering:pixelated`\n\n // Off-screen canvas, sized per source bitmap. Used to materialise\n // `putImageData` once per frame; we then `drawImage` it onto the\n // display canvas with an aspect-correct destination rect.\n const offscreen = document.createElement('canvas')\n offscreen.width = 1\n offscreen.height = 1\n\n // Bottom-left overlay: dimensions + pixel format. Same translucent\n // chip background as the FPS-graph value badge so the text stays\n // legible regardless of the buffer's content underneath.\n const valueLabel = document.createElement('span')\n valueLabel.style.cssText = [\n 'position:absolute',\n 'left:10px',\n 'bottom:10px',\n 'padding:1px 4px',\n 'font-size:10px',\n 'background:rgba(0,2,28,0.65)',\n 'border-radius:2px',\n 'color:var(--tp-monitor-foreground-color)',\n 'opacity:0.85',\n 'font-family:var(--tp-base-font-family, ui-monospace, monospace)',\n 'font-variant-numeric:tabular-nums',\n 'pointer-events:none',\n ].join(';')\n\n // Bottom-right overlay: expand-to-fullscreen button.\n const expandBtn = document.createElement('span')\n expandBtn.textContent = '⤢'\n expandBtn.setAttribute('role', 'button')\n expandBtn.setAttribute('aria-label', 'Open fullscreen buffer viewer')\n expandBtn.title = 'Open fullscreen buffer viewer'\n expandBtn.style.cssText = [\n 'position:absolute',\n 'right:10px',\n 'bottom:10px',\n 'padding:1px 4px',\n 'font-size:11px',\n 'background:rgba(0,2,28,0.65)',\n 'border-radius:2px',\n 'cursor:pointer',\n 'color:var(--tp-monitor-foreground-color)',\n 'user-select:none',\n '-webkit-user-select:none',\n ].join(';')\n\n body.appendChild(canvas)\n body.appendChild(valueLabel)\n body.appendChild(expandBtn)\n\n bladeEl.appendChild(header)\n bladeEl.appendChild(body)\n\n // ── State ─────────────────────────────────────────────────────────────\n\n let collapsed = true\n let paneFolded = false\n let activeName: string | null = null\n let lastRenderedVersion = -1\n const ctx = canvas.getContext('2d')\n\n // Cached ImageData reused across paints when dimensions match — without\n // this, `createImageData(w, h)` allocates a fresh Uint8ClampedArray\n // (~w*h*4 bytes) every frame, ~400 KB/s of throwaway garbage at 4 Hz\n // for a 256×96 thumb.\n let cachedImgData: ImageData | null = null\n let cachedImgDataW = 0\n let cachedImgDataH = 0\n\n // Keep the canvas backing locked to its actual rendered pixel size so\n // `drawImage(0,0,canvas.width,canvas.height)` truly fills what the\n // user sees. Re-render the active buffer on resize so a freshly-\n // expanded blade isn't stuck on a stale stretched bitmap.\n const resizeObserver = new ResizeObserver(([entry]) => {\n if (!entry) return\n const dpr = window.devicePixelRatio || 1\n const cssW = Math.max(1, Math.round(entry.contentRect.width))\n const cssH = Math.max(1, Math.round(entry.contentRect.height))\n const w = Math.max(1, Math.round(cssW * dpr))\n const h = Math.max(1, Math.round(cssH * dpr))\n if (canvas.width !== w || canvas.height !== h) {\n canvas.width = w\n canvas.height = h\n lastRenderedVersion = -1 // force a redraw at the new backing size\n const cur = activeName !== null ? client.state.buffers.get(activeName) : undefined\n if (cur && !collapsed) renderThumb(cur)\n }\n })\n resizeObserver.observe(canvas)\n\n function currentNames(): string[] {\n return Array.from(client.state.buffers.keys())\n }\n\n let modalOpen = false\n\n function syncSelection(): void {\n if (modalOpen) return\n if (paneFolded || collapsed || activeName === null) {\n // Fully idle — provider skips readbacks entirely.\n client.setBuffers({})\n return\n }\n // Thumbnail panel is visible → ask for a small downsampled preview.\n client.setBuffers({ [activeName]: { mode: 'thumbnail', thumbSize: 256 } })\n }\n\n function setActive(name: string | null): void {\n if (activeName === name) return\n activeName = name\n nameLabel.textContent = name ?? '—'\n lastRenderedVersion = -1\n syncSelection()\n }\n\n function cycle(delta: number): void {\n const names = currentNames()\n if (names.length === 0) return\n const idx = activeName === null ? -1 : names.indexOf(activeName)\n const next = names[(idx + delta + names.length) % names.length]!\n setActive(next)\n }\n\n const toggleCollapse = (e: Event): void => {\n e.stopPropagation()\n collapsed = !collapsed\n body.style.display = collapsed ? 'none' : 'flex'\n if (!collapsed) lastRenderedVersion = -1\n syncSelection()\n }\n header.addEventListener('click', toggleCollapse)\n prevBtn.addEventListener('click', (e) => { e.stopPropagation(); cycle(-1) })\n nextBtn.addEventListener('click', (e) => { e.stopPropagation(); cycle(1) })\n expandBtn.addEventListener('click', (e) => {\n e.stopPropagation()\n if (activeName !== null && options.onExpand !== undefined) {\n options.onExpand(activeName)\n }\n })\n\n // ── Render ────────────────────────────────────────────────────────────\n\n /**\n * Paint the latest sample for the currently-selected buffer into the\n * thumbnail. Float kinds are clamped to [0,1] for the preview;\n * proper min-max tonemap lives in the fullscreen viewer (TBD).\n *\n * Pipeline:\n * 1. Decode the typed-array sample into RGBA8 inside `offscreen`\n * (sized to match the source — 256×4, 64×64, etc.).\n * 2. Clear the display canvas.\n * 3. Compute an aspect-correct destination rect inside the display\n * canvas (THUMB_WIDTH × THUMB_HEIGHT) and `drawImage` into it.\n *\n * Disabling `imageSmoothingEnabled` keeps the upscale crisp instead\n * of bilinear-blurry — matches the \"image-rendering: pixelated\" hint.\n */\n function renderThumb(entry: BufferSnapshot): void {\n if (!ctx) return\n ctx.clearRect(0, 0, canvas.width, canvas.height)\n\n const { width, height, pixels } = entry\n if (width === 0 || height === 0 || pixels === null) return\n\n if (offscreen.width !== width || offscreen.height !== height) {\n offscreen.width = width\n offscreen.height = height\n // Backing dims changed → invalidate the ImageData cache.\n cachedImgData = null\n }\n const offCtx = offscreen.getContext('2d')\n if (!offCtx) return\n\n if (cachedImgData === null || cachedImgDataW !== width || cachedImgDataH !== height) {\n cachedImgData = offCtx.createImageData(width, height)\n cachedImgDataW = width\n cachedImgDataH = height\n }\n const imgData = cachedImgData\n const out = imgData.data\n\n // Pixels arrive as display-ready RGBA8 from the worker — copy\n // directly into the ImageData without any decoder logic.\n if (pixels instanceof Uint8Array) {\n out.set(pixels.subarray(0, out.length))\n } else {\n // Float32Array fallback (shouldn't happen post-pipeline, but\n // guard against legacy callers).\n const count = width * height\n for (let i = 0; i < count; i++) {\n const o = i * 4\n out[o] = Math.round(Math.max(0, Math.min(1, pixels[i * 4] ?? 0)) * 255)\n out[o + 1] = Math.round(Math.max(0, Math.min(1, pixels[i * 4 + 1] ?? 0)) * 255)\n out[o + 2] = Math.round(Math.max(0, Math.min(1, pixels[i * 4 + 2] ?? 0)) * 255)\n out[o + 3] = Math.round(Math.max(0, Math.min(1, pixels[i * 4 + 3] ?? 1)) * 255)\n }\n }\n\n offCtx.putImageData(imgData, 0, 0)\n\n // Stretch to fill: every source pixel maps somewhere in the\n // viewport. This deliberately distorts (a 256×4 buffer reads as\n // 4 stretched rows across the full panel) but guarantees you can\n // SEE every value in a debug thumbnail. Aspect-correct inspection\n // belongs in the fullscreen viewer.\n ctx.imageSmoothingEnabled = false\n ctx.drawImage(offscreen, 0, 0, canvas.width, canvas.height)\n }\n\n // ── Listener ──────────────────────────────────────────────────────────\n\n const unsubscribe = client.addListener((state) => {\n const names = Array.from(state.buffers.keys())\n\n // Visibility.\n bladeEl.style.display = names.length > 0 ? 'flex' : 'none'\n\n // Establish/repair active selection.\n if (names.length === 0) {\n setActive(null)\n } else if (activeName === null || !names.includes(activeName)) {\n setActive(names[0]!)\n }\n\n // Arrow affordance.\n const multi = names.length > 1\n prevBtn.style.opacity = multi ? '0.8' : '0.25'\n nextBtn.style.opacity = multi ? '0.8' : '0.25'\n prevBtn.style.cursor = multi ? 'pointer' : 'default'\n nextBtn.style.cursor = multi ? 'pointer' : 'default'\n\n // Ensure the provider is streaming the current selection (new\n // entries appearing while we're expanded should auto-subscribe).\n syncSelection()\n\n // Paint.\n if (collapsed || activeName === null) return\n const entry = state.buffers.get(activeName)\n if (!entry) return\n if (entry.version === lastRenderedVersion) return\n lastRenderedVersion = entry.version\n renderThumb(entry)\n // Prefer source dims for the label — the shipped thumbnail dims\n // (e.g. 256×16) confuse users who want to know the real buffer size.\n const lw = entry.srcWidth > 0 ? entry.srcWidth : entry.width\n const lh = entry.srcHeight > 0 ? entry.srcHeight : entry.height\n valueLabel.textContent = `${lw}×${lh} · ${entry.pixelType}`\n nameLabel.title = `${entry.name}${entry.label ? ` — ${entry.label}` : ''}`\n })\n\n return {\n element: bladeEl,\n setModalOpen(open: boolean) {\n modalOpen = open\n if (!open) syncSelection()\n },\n setActiveFromModal(name: string) {\n if (activeName === name) return\n activeName = name\n nameLabel.textContent = name\n lastRenderedVersion = -1\n },\n setPaneFolded(folded: boolean) {\n if (paneFolded === folded) return\n paneFolded = folded\n if (folded) lastRenderedVersion = -1\n syncSelection()\n },\n dispose() {\n resizeObserver.disconnect()\n unsubscribe()\n blade.dispose()\n },\n }\n}\n\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Pane, FolderApi } from 'tweakpane';
|
|
2
|
+
import { DevtoolsClient } from './devtools-client.cjs';
|
|
3
|
+
import 'three-flatland/debug-protocol';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Buffers view — compact one-row preview of the currently-selected
|
|
7
|
+
* debug buffer, with `◀ name ▶` arrows to cycle through every
|
|
8
|
+
* registered entry and a ⤢ "expand" icon overlay on the thumbnail
|
|
9
|
+
* that opens (a future) fullscreen modal.
|
|
10
|
+
*
|
|
11
|
+
* Main-pane real estate is tight: there's no grouping UI here. The
|
|
12
|
+
* fullscreen modal is where the group tree / channel toggles / pan &
|
|
13
|
+
* zoom live. Starts collapsed — no pixel readback happens until the
|
|
14
|
+
* user opens the blade.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
interface BuffersViewHandle {
|
|
18
|
+
readonly element: HTMLElement;
|
|
19
|
+
/** Tell the view the modal is open/closed. While open, the view
|
|
20
|
+
* defers buffer selection to the modal. */
|
|
21
|
+
setModalOpen(open: boolean): void;
|
|
22
|
+
/** Sync the view's active buffer to match the modal's selection. */
|
|
23
|
+
setActiveFromModal(name: string): void;
|
|
24
|
+
/** Tell the view the outer pane was folded/unfolded. When folded the
|
|
25
|
+
* view unsubscribes regardless of its own collapse state. */
|
|
26
|
+
setPaneFolded(folded: boolean): void;
|
|
27
|
+
dispose(): void;
|
|
28
|
+
}
|
|
29
|
+
interface AddBuffersViewOptions {
|
|
30
|
+
/**
|
|
31
|
+
* Invoked when the user clicks the bottom-right ⤢ button. Wired by
|
|
32
|
+
* `createPane` to open the fullscreen modal viewer with the active
|
|
33
|
+
* buffer pre-selected. When omitted, the click is a no-op.
|
|
34
|
+
*/
|
|
35
|
+
onExpand?: (name: string) => void;
|
|
36
|
+
}
|
|
37
|
+
declare function addBuffersView(parent: Pane | FolderApi, client: DevtoolsClient, options?: AddBuffersViewOptions): BuffersViewHandle;
|
|
38
|
+
|
|
39
|
+
export { type AddBuffersViewOptions, type BuffersViewHandle, addBuffersView };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Pane, FolderApi } from 'tweakpane';
|
|
2
|
+
import { DevtoolsClient } from './devtools-client.js';
|
|
3
|
+
import 'three-flatland/debug-protocol';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Buffers view — compact one-row preview of the currently-selected
|
|
7
|
+
* debug buffer, with `◀ name ▶` arrows to cycle through every
|
|
8
|
+
* registered entry and a ⤢ "expand" icon overlay on the thumbnail
|
|
9
|
+
* that opens (a future) fullscreen modal.
|
|
10
|
+
*
|
|
11
|
+
* Main-pane real estate is tight: there's no grouping UI here. The
|
|
12
|
+
* fullscreen modal is where the group tree / channel toggles / pan &
|
|
13
|
+
* zoom live. Starts collapsed — no pixel readback happens until the
|
|
14
|
+
* user opens the blade.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
interface BuffersViewHandle {
|
|
18
|
+
readonly element: HTMLElement;
|
|
19
|
+
/** Tell the view the modal is open/closed. While open, the view
|
|
20
|
+
* defers buffer selection to the modal. */
|
|
21
|
+
setModalOpen(open: boolean): void;
|
|
22
|
+
/** Sync the view's active buffer to match the modal's selection. */
|
|
23
|
+
setActiveFromModal(name: string): void;
|
|
24
|
+
/** Tell the view the outer pane was folded/unfolded. When folded the
|
|
25
|
+
* view unsubscribes regardless of its own collapse state. */
|
|
26
|
+
setPaneFolded(folded: boolean): void;
|
|
27
|
+
dispose(): void;
|
|
28
|
+
}
|
|
29
|
+
interface AddBuffersViewOptions {
|
|
30
|
+
/**
|
|
31
|
+
* Invoked when the user clicks the bottom-right ⤢ button. Wired by
|
|
32
|
+
* `createPane` to open the fullscreen modal viewer with the active
|
|
33
|
+
* buffer pre-selected. When omitted, the click is a no-op.
|
|
34
|
+
*/
|
|
35
|
+
onExpand?: (name: string) => void;
|
|
36
|
+
}
|
|
37
|
+
declare function addBuffersView(parent: Pane | FolderApi, client: DevtoolsClient, options?: AddBuffersViewOptions): BuffersViewHandle;
|
|
38
|
+
|
|
39
|
+
export { type AddBuffersViewOptions, type BuffersViewHandle, addBuffersView };
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
const THUMB_WIDTH = 240;
|
|
2
|
+
const THUMB_HEIGHT = 120;
|
|
3
|
+
function addBuffersView(parent, client, options = {}) {
|
|
4
|
+
const blade = parent.addBlade({ view: "separator" });
|
|
5
|
+
const bladeEl = blade.element;
|
|
6
|
+
bladeEl.innerHTML = "";
|
|
7
|
+
bladeEl.className = "tp-cntv";
|
|
8
|
+
bladeEl.style.cssText = "display:none;flex-direction:column;background:rgba(0,0,0,0.3)";
|
|
9
|
+
const header = document.createElement("div");
|
|
10
|
+
header.style.cssText = [
|
|
11
|
+
"display:grid",
|
|
12
|
+
"grid-template-columns:2em 1fr 2em",
|
|
13
|
+
"align-items:center",
|
|
14
|
+
"padding:4px 6px 6px",
|
|
15
|
+
"font-size:11px",
|
|
16
|
+
"color:var(--tp-label-foreground-color)",
|
|
17
|
+
"user-select:none",
|
|
18
|
+
"-webkit-user-select:none",
|
|
19
|
+
"font-variant-numeric:tabular-nums",
|
|
20
|
+
"cursor:pointer"
|
|
21
|
+
].join(";");
|
|
22
|
+
const arrowStyle = "cursor:pointer;padding:0 4px;opacity:0.8;font-family:ui-monospace,monospace;text-align:center";
|
|
23
|
+
const prevBtn = document.createElement("span");
|
|
24
|
+
prevBtn.textContent = "\u25C0";
|
|
25
|
+
prevBtn.style.cssText = arrowStyle;
|
|
26
|
+
prevBtn.setAttribute("role", "button");
|
|
27
|
+
prevBtn.setAttribute("aria-label", "Previous buffer");
|
|
28
|
+
const nameLabel = document.createElement("span");
|
|
29
|
+
nameLabel.style.cssText = "font-weight:500;text-align:center;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;pointer-events:none";
|
|
30
|
+
const nextBtn = document.createElement("span");
|
|
31
|
+
nextBtn.textContent = "\u25B6";
|
|
32
|
+
nextBtn.style.cssText = arrowStyle;
|
|
33
|
+
nextBtn.setAttribute("role", "button");
|
|
34
|
+
nextBtn.setAttribute("aria-label", "Next buffer");
|
|
35
|
+
header.appendChild(prevBtn);
|
|
36
|
+
header.appendChild(nameLabel);
|
|
37
|
+
header.appendChild(nextBtn);
|
|
38
|
+
const body = document.createElement("div");
|
|
39
|
+
body.style.cssText = "display:none;flex-direction:column;padding:2px 6px 6px;position:relative";
|
|
40
|
+
const canvas = document.createElement("canvas");
|
|
41
|
+
canvas.width = THUMB_WIDTH;
|
|
42
|
+
canvas.height = THUMB_HEIGHT;
|
|
43
|
+
canvas.style.cssText = `display:block;width:100%;height:${THUMB_HEIGHT}px;background:rgba(0,2,28,0.45);border-radius:2px;image-rendering:pixelated`;
|
|
44
|
+
const offscreen = document.createElement("canvas");
|
|
45
|
+
offscreen.width = 1;
|
|
46
|
+
offscreen.height = 1;
|
|
47
|
+
const valueLabel = document.createElement("span");
|
|
48
|
+
valueLabel.style.cssText = [
|
|
49
|
+
"position:absolute",
|
|
50
|
+
"left:10px",
|
|
51
|
+
"bottom:10px",
|
|
52
|
+
"padding:1px 4px",
|
|
53
|
+
"font-size:10px",
|
|
54
|
+
"background:rgba(0,2,28,0.65)",
|
|
55
|
+
"border-radius:2px",
|
|
56
|
+
"color:var(--tp-monitor-foreground-color)",
|
|
57
|
+
"opacity:0.85",
|
|
58
|
+
"font-family:var(--tp-base-font-family, ui-monospace, monospace)",
|
|
59
|
+
"font-variant-numeric:tabular-nums",
|
|
60
|
+
"pointer-events:none"
|
|
61
|
+
].join(";");
|
|
62
|
+
const expandBtn = document.createElement("span");
|
|
63
|
+
expandBtn.textContent = "\u2922";
|
|
64
|
+
expandBtn.setAttribute("role", "button");
|
|
65
|
+
expandBtn.setAttribute("aria-label", "Open fullscreen buffer viewer");
|
|
66
|
+
expandBtn.title = "Open fullscreen buffer viewer";
|
|
67
|
+
expandBtn.style.cssText = [
|
|
68
|
+
"position:absolute",
|
|
69
|
+
"right:10px",
|
|
70
|
+
"bottom:10px",
|
|
71
|
+
"padding:1px 4px",
|
|
72
|
+
"font-size:11px",
|
|
73
|
+
"background:rgba(0,2,28,0.65)",
|
|
74
|
+
"border-radius:2px",
|
|
75
|
+
"cursor:pointer",
|
|
76
|
+
"color:var(--tp-monitor-foreground-color)",
|
|
77
|
+
"user-select:none",
|
|
78
|
+
"-webkit-user-select:none"
|
|
79
|
+
].join(";");
|
|
80
|
+
body.appendChild(canvas);
|
|
81
|
+
body.appendChild(valueLabel);
|
|
82
|
+
body.appendChild(expandBtn);
|
|
83
|
+
bladeEl.appendChild(header);
|
|
84
|
+
bladeEl.appendChild(body);
|
|
85
|
+
let collapsed = true;
|
|
86
|
+
let paneFolded = false;
|
|
87
|
+
let activeName = null;
|
|
88
|
+
let lastRenderedVersion = -1;
|
|
89
|
+
const ctx = canvas.getContext("2d");
|
|
90
|
+
let cachedImgData = null;
|
|
91
|
+
let cachedImgDataW = 0;
|
|
92
|
+
let cachedImgDataH = 0;
|
|
93
|
+
const resizeObserver = new ResizeObserver(([entry]) => {
|
|
94
|
+
if (!entry) return;
|
|
95
|
+
const dpr = window.devicePixelRatio || 1;
|
|
96
|
+
const cssW = Math.max(1, Math.round(entry.contentRect.width));
|
|
97
|
+
const cssH = Math.max(1, Math.round(entry.contentRect.height));
|
|
98
|
+
const w = Math.max(1, Math.round(cssW * dpr));
|
|
99
|
+
const h = Math.max(1, Math.round(cssH * dpr));
|
|
100
|
+
if (canvas.width !== w || canvas.height !== h) {
|
|
101
|
+
canvas.width = w;
|
|
102
|
+
canvas.height = h;
|
|
103
|
+
lastRenderedVersion = -1;
|
|
104
|
+
const cur = activeName !== null ? client.state.buffers.get(activeName) : void 0;
|
|
105
|
+
if (cur && !collapsed) renderThumb(cur);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
resizeObserver.observe(canvas);
|
|
109
|
+
function currentNames() {
|
|
110
|
+
return Array.from(client.state.buffers.keys());
|
|
111
|
+
}
|
|
112
|
+
let modalOpen = false;
|
|
113
|
+
function syncSelection() {
|
|
114
|
+
if (modalOpen) return;
|
|
115
|
+
if (paneFolded || collapsed || activeName === null) {
|
|
116
|
+
client.setBuffers({});
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
client.setBuffers({ [activeName]: { mode: "thumbnail", thumbSize: 256 } });
|
|
120
|
+
}
|
|
121
|
+
function setActive(name) {
|
|
122
|
+
if (activeName === name) return;
|
|
123
|
+
activeName = name;
|
|
124
|
+
nameLabel.textContent = name ?? "\u2014";
|
|
125
|
+
lastRenderedVersion = -1;
|
|
126
|
+
syncSelection();
|
|
127
|
+
}
|
|
128
|
+
function cycle(delta) {
|
|
129
|
+
const names = currentNames();
|
|
130
|
+
if (names.length === 0) return;
|
|
131
|
+
const idx = activeName === null ? -1 : names.indexOf(activeName);
|
|
132
|
+
const next = names[(idx + delta + names.length) % names.length];
|
|
133
|
+
setActive(next);
|
|
134
|
+
}
|
|
135
|
+
const toggleCollapse = (e) => {
|
|
136
|
+
e.stopPropagation();
|
|
137
|
+
collapsed = !collapsed;
|
|
138
|
+
body.style.display = collapsed ? "none" : "flex";
|
|
139
|
+
if (!collapsed) lastRenderedVersion = -1;
|
|
140
|
+
syncSelection();
|
|
141
|
+
};
|
|
142
|
+
header.addEventListener("click", toggleCollapse);
|
|
143
|
+
prevBtn.addEventListener("click", (e) => {
|
|
144
|
+
e.stopPropagation();
|
|
145
|
+
cycle(-1);
|
|
146
|
+
});
|
|
147
|
+
nextBtn.addEventListener("click", (e) => {
|
|
148
|
+
e.stopPropagation();
|
|
149
|
+
cycle(1);
|
|
150
|
+
});
|
|
151
|
+
expandBtn.addEventListener("click", (e) => {
|
|
152
|
+
e.stopPropagation();
|
|
153
|
+
if (activeName !== null && options.onExpand !== void 0) {
|
|
154
|
+
options.onExpand(activeName);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
function renderThumb(entry) {
|
|
158
|
+
if (!ctx) return;
|
|
159
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
160
|
+
const { width, height, pixels } = entry;
|
|
161
|
+
if (width === 0 || height === 0 || pixels === null) return;
|
|
162
|
+
if (offscreen.width !== width || offscreen.height !== height) {
|
|
163
|
+
offscreen.width = width;
|
|
164
|
+
offscreen.height = height;
|
|
165
|
+
cachedImgData = null;
|
|
166
|
+
}
|
|
167
|
+
const offCtx = offscreen.getContext("2d");
|
|
168
|
+
if (!offCtx) return;
|
|
169
|
+
if (cachedImgData === null || cachedImgDataW !== width || cachedImgDataH !== height) {
|
|
170
|
+
cachedImgData = offCtx.createImageData(width, height);
|
|
171
|
+
cachedImgDataW = width;
|
|
172
|
+
cachedImgDataH = height;
|
|
173
|
+
}
|
|
174
|
+
const imgData = cachedImgData;
|
|
175
|
+
const out = imgData.data;
|
|
176
|
+
if (pixels instanceof Uint8Array) {
|
|
177
|
+
out.set(pixels.subarray(0, out.length));
|
|
178
|
+
} else {
|
|
179
|
+
const count = width * height;
|
|
180
|
+
for (let i = 0; i < count; i++) {
|
|
181
|
+
const o = i * 4;
|
|
182
|
+
out[o] = Math.round(Math.max(0, Math.min(1, pixels[i * 4] ?? 0)) * 255);
|
|
183
|
+
out[o + 1] = Math.round(Math.max(0, Math.min(1, pixels[i * 4 + 1] ?? 0)) * 255);
|
|
184
|
+
out[o + 2] = Math.round(Math.max(0, Math.min(1, pixels[i * 4 + 2] ?? 0)) * 255);
|
|
185
|
+
out[o + 3] = Math.round(Math.max(0, Math.min(1, pixels[i * 4 + 3] ?? 1)) * 255);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
offCtx.putImageData(imgData, 0, 0);
|
|
189
|
+
ctx.imageSmoothingEnabled = false;
|
|
190
|
+
ctx.drawImage(offscreen, 0, 0, canvas.width, canvas.height);
|
|
191
|
+
}
|
|
192
|
+
const unsubscribe = client.addListener((state) => {
|
|
193
|
+
const names = Array.from(state.buffers.keys());
|
|
194
|
+
bladeEl.style.display = names.length > 0 ? "flex" : "none";
|
|
195
|
+
if (names.length === 0) {
|
|
196
|
+
setActive(null);
|
|
197
|
+
} else if (activeName === null || !names.includes(activeName)) {
|
|
198
|
+
setActive(names[0]);
|
|
199
|
+
}
|
|
200
|
+
const multi = names.length > 1;
|
|
201
|
+
prevBtn.style.opacity = multi ? "0.8" : "0.25";
|
|
202
|
+
nextBtn.style.opacity = multi ? "0.8" : "0.25";
|
|
203
|
+
prevBtn.style.cursor = multi ? "pointer" : "default";
|
|
204
|
+
nextBtn.style.cursor = multi ? "pointer" : "default";
|
|
205
|
+
syncSelection();
|
|
206
|
+
if (collapsed || activeName === null) return;
|
|
207
|
+
const entry = state.buffers.get(activeName);
|
|
208
|
+
if (!entry) return;
|
|
209
|
+
if (entry.version === lastRenderedVersion) return;
|
|
210
|
+
lastRenderedVersion = entry.version;
|
|
211
|
+
renderThumb(entry);
|
|
212
|
+
const lw = entry.srcWidth > 0 ? entry.srcWidth : entry.width;
|
|
213
|
+
const lh = entry.srcHeight > 0 ? entry.srcHeight : entry.height;
|
|
214
|
+
valueLabel.textContent = `${lw}\xD7${lh} \xB7 ${entry.pixelType}`;
|
|
215
|
+
nameLabel.title = `${entry.name}${entry.label ? ` \u2014 ${entry.label}` : ""}`;
|
|
216
|
+
});
|
|
217
|
+
return {
|
|
218
|
+
element: bladeEl,
|
|
219
|
+
setModalOpen(open) {
|
|
220
|
+
modalOpen = open;
|
|
221
|
+
if (!open) syncSelection();
|
|
222
|
+
},
|
|
223
|
+
setActiveFromModal(name) {
|
|
224
|
+
if (activeName === name) return;
|
|
225
|
+
activeName = name;
|
|
226
|
+
nameLabel.textContent = name;
|
|
227
|
+
lastRenderedVersion = -1;
|
|
228
|
+
},
|
|
229
|
+
setPaneFolded(folded) {
|
|
230
|
+
if (paneFolded === folded) return;
|
|
231
|
+
paneFolded = folded;
|
|
232
|
+
if (folded) lastRenderedVersion = -1;
|
|
233
|
+
syncSelection();
|
|
234
|
+
},
|
|
235
|
+
dispose() {
|
|
236
|
+
resizeObserver.disconnect();
|
|
237
|
+
unsubscribe();
|
|
238
|
+
blade.dispose();
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
export {
|
|
243
|
+
addBuffersView
|
|
244
|
+
};
|
|
245
|
+
//# sourceMappingURL=buffers-view.js.map
|