@three-flatland/devtools 0.1.0-alpha.3 → 1.0.0-alpha.6

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.
Files changed (172) hide show
  1. package/README.md +1 -1
  2. package/dist/buffers-modal.d.ts +16 -52
  3. package/dist/buffers-modal.d.ts.map +1 -0
  4. package/dist/buffers-modal.js +459 -464
  5. package/dist/buffers-modal.js.map +1 -1
  6. package/dist/buffers-view.d.ts +22 -34
  7. package/dist/buffers-view.d.ts.map +1 -0
  8. package/dist/buffers-view.js +252 -240
  9. package/dist/buffers-view.js.map +1 -1
  10. package/dist/create-pane.d.ts +38 -37
  11. package/dist/create-pane.d.ts.map +1 -0
  12. package/dist/create-pane.js +197 -166
  13. package/dist/create-pane.js.map +1 -1
  14. package/dist/dashboard/app.tsx +2 -0
  15. package/dist/dashboard/export.ts +1 -3
  16. package/dist/dashboard/frame-cursor.ts +81 -0
  17. package/dist/dashboard/hooks.ts +10 -2
  18. package/dist/dashboard/index.html +61 -0
  19. package/dist/dashboard/panels/batches.tsx +5 -18
  20. package/dist/dashboard/panels/buffers.tsx +52 -26
  21. package/dist/dashboard/panels/env.tsx +5 -6
  22. package/dist/dashboard/panels/protocol-log.tsx +146 -58
  23. package/dist/dashboard/panels/registry.tsx +55 -69
  24. package/dist/dashboard/panels/scrubber.tsx +100 -0
  25. package/dist/dashboard/panels/stats.tsx +60 -32
  26. package/dist/dashboard/protocol-store.ts +25 -8
  27. package/dist/dashboard/vendor/hooks.module.d.ts +146 -0
  28. package/dist/dashboard/vendor/jsx-runtime.d.ts +55 -0
  29. package/dist/dashboard/vendor/jsx.d.ts +3123 -0
  30. package/dist/dashboard/vendor/preact.module.d.ts +397 -0
  31. package/dist/devtools-client.d.ts +312 -300
  32. package/dist/devtools-client.d.ts.map +1 -0
  33. package/dist/devtools-client.js +866 -838
  34. package/dist/devtools-client.js.map +1 -1
  35. package/dist/index.d.ts +6 -8
  36. package/dist/index.js +5 -12
  37. package/dist/perf-trace.d.ts +6 -4
  38. package/dist/perf-trace.d.ts.map +1 -0
  39. package/dist/perf-trace.js +80 -66
  40. package/dist/perf-trace.js.map +1 -1
  41. package/dist/plugins.d.ts +6 -6
  42. package/dist/plugins.d.ts.map +1 -0
  43. package/dist/plugins.js +5 -5
  44. package/dist/plugins.js.map +1 -1
  45. package/dist/provider-switcher.d.ts +8 -15
  46. package/dist/provider-switcher.d.ts.map +1 -0
  47. package/dist/provider-switcher.js +68 -68
  48. package/dist/provider-switcher.js.map +1 -1
  49. package/dist/react/devtools-provider.d.ts +11 -10
  50. package/dist/react/devtools-provider.d.ts.map +1 -0
  51. package/dist/react/devtools-provider.js +60 -32
  52. package/dist/react/devtools-provider.js.map +1 -1
  53. package/dist/react/use-fps-graph.d.ts +7 -7
  54. package/dist/react/use-fps-graph.d.ts.map +1 -0
  55. package/dist/react/use-fps-graph.js +27 -21
  56. package/dist/react/use-fps-graph.js.map +1 -1
  57. package/dist/react/use-pane-button.d.ts +4 -4
  58. package/dist/react/use-pane-button.d.ts.map +1 -0
  59. package/dist/react/use-pane-button.js +35 -30
  60. package/dist/react/use-pane-button.js.map +1 -1
  61. package/dist/react/use-pane-folder.d.ts +5 -4
  62. package/dist/react/use-pane-folder.d.ts.map +1 -0
  63. package/dist/react/use-pane-folder.js +33 -20
  64. package/dist/react/use-pane-folder.js.map +1 -1
  65. package/dist/react/use-pane-input.d.ts +31 -30
  66. package/dist/react/use-pane-input.d.ts.map +1 -0
  67. package/dist/react/use-pane-input.js +46 -42
  68. package/dist/react/use-pane-input.js.map +1 -1
  69. package/dist/react/use-pane-radio-grid.d.ts +20 -20
  70. package/dist/react/use-pane-radio-grid.d.ts.map +1 -0
  71. package/dist/react/use-pane-radio-grid.js +72 -50
  72. package/dist/react/use-pane-radio-grid.js.map +1 -1
  73. package/dist/react/use-pane.d.ts +4 -4
  74. package/dist/react/use-pane.d.ts.map +1 -0
  75. package/dist/react/use-pane.js +52 -17
  76. package/dist/react/use-pane.js.map +1 -1
  77. package/dist/react.d.ts +10 -12
  78. package/dist/react.js +2 -12
  79. package/dist/registry-view.d.ts +8 -21
  80. package/dist/registry-view.d.ts.map +1 -0
  81. package/dist/registry-view.js +357 -338
  82. package/dist/registry-view.js.map +1 -1
  83. package/dist/relay.d.ts +16 -0
  84. package/dist/relay.d.ts.map +1 -0
  85. package/dist/relay.js +189 -0
  86. package/dist/relay.js.map +1 -0
  87. package/dist/remote.d.ts +10 -0
  88. package/dist/remote.d.ts.map +1 -0
  89. package/dist/remote.js +28 -0
  90. package/dist/remote.js.map +1 -0
  91. package/dist/stats-graph.d.ts +23 -31
  92. package/dist/stats-graph.d.ts.map +1 -0
  93. package/dist/stats-graph.js +323 -264
  94. package/dist/stats-graph.js.map +1 -1
  95. package/dist/stats-row.d.ts +8 -21
  96. package/dist/stats-row.d.ts.map +1 -0
  97. package/dist/stats-row.js +99 -101
  98. package/dist/stats-row.js.map +1 -1
  99. package/dist/theme.d.ts +3 -1
  100. package/dist/theme.d.ts.map +1 -0
  101. package/dist/theme.js +43 -54
  102. package/dist/theme.js.map +1 -1
  103. package/dist/vite-plugin.d.ts +7 -6
  104. package/dist/vite-plugin.d.ts.map +1 -0
  105. package/dist/vite-plugin.js +87 -78
  106. package/dist/vite-plugin.js.map +1 -1
  107. package/package.json +33 -29
  108. package/dist/buffers-modal.cjs +0 -491
  109. package/dist/buffers-modal.cjs.map +0 -1
  110. package/dist/buffers-modal.d.cts +0 -62
  111. package/dist/buffers-view.cjs +0 -269
  112. package/dist/buffers-view.cjs.map +0 -1
  113. package/dist/buffers-view.d.cts +0 -39
  114. package/dist/create-pane.cjs +0 -203
  115. package/dist/create-pane.cjs.map +0 -1
  116. package/dist/create-pane.d.cts +0 -66
  117. package/dist/devtools-client.cjs +0 -870
  118. package/dist/devtools-client.cjs.map +0 -1
  119. package/dist/devtools-client.d.cts +0 -362
  120. package/dist/index.cjs +0 -42
  121. package/dist/index.cjs.map +0 -1
  122. package/dist/index.d.cts +0 -8
  123. package/dist/index.js.map +0 -1
  124. package/dist/perf-trace.cjs +0 -103
  125. package/dist/perf-trace.cjs.map +0 -1
  126. package/dist/perf-trace.d.cts +0 -27
  127. package/dist/plugins.cjs +0 -44
  128. package/dist/plugins.cjs.map +0 -1
  129. package/dist/plugins.d.cts +0 -7
  130. package/dist/provider-switcher.cjs +0 -95
  131. package/dist/provider-switcher.cjs.map +0 -1
  132. package/dist/provider-switcher.d.cts +0 -18
  133. package/dist/react/devtools-provider.cjs +0 -59
  134. package/dist/react/devtools-provider.cjs.map +0 -1
  135. package/dist/react/devtools-provider.d.cts +0 -39
  136. package/dist/react/use-fps-graph.cjs +0 -49
  137. package/dist/react/use-fps-graph.cjs.map +0 -1
  138. package/dist/react/use-fps-graph.d.cts +0 -15
  139. package/dist/react/use-pane-button.cjs +0 -58
  140. package/dist/react/use-pane-button.cjs.map +0 -1
  141. package/dist/react/use-pane-button.d.cts +0 -10
  142. package/dist/react/use-pane-folder.cjs +0 -48
  143. package/dist/react/use-pane-folder.cjs.map +0 -1
  144. package/dist/react/use-pane-folder.d.cts +0 -15
  145. package/dist/react/use-pane-input.cjs +0 -70
  146. package/dist/react/use-pane-input.cjs.map +0 -1
  147. package/dist/react/use-pane-input.d.cts +0 -44
  148. package/dist/react/use-pane-radio-grid.cjs +0 -78
  149. package/dist/react/use-pane-radio-grid.cjs.map +0 -1
  150. package/dist/react/use-pane-radio-grid.d.cts +0 -35
  151. package/dist/react/use-pane.cjs +0 -45
  152. package/dist/react/use-pane.cjs.map +0 -1
  153. package/dist/react/use-pane.d.cts +0 -40
  154. package/dist/react.cjs +0 -50
  155. package/dist/react.cjs.map +0 -1
  156. package/dist/react.d.cts +0 -12
  157. package/dist/react.js.map +0 -1
  158. package/dist/registry-view.cjs +0 -371
  159. package/dist/registry-view.cjs.map +0 -1
  160. package/dist/registry-view.d.cts +0 -24
  161. package/dist/stats-graph.cjs +0 -297
  162. package/dist/stats-graph.cjs.map +0 -1
  163. package/dist/stats-graph.d.cts +0 -40
  164. package/dist/stats-row.cjs +0 -153
  165. package/dist/stats-row.cjs.map +0 -1
  166. package/dist/stats-row.d.cts +0 -24
  167. package/dist/theme.cjs +0 -260
  168. package/dist/theme.cjs.map +0 -1
  169. package/dist/theme.d.cts +0 -10
  170. package/dist/vite-plugin.cjs +0 -113
  171. package/dist/vite-plugin.cjs.map +0 -1
  172. package/dist/vite-plugin.d.cts +0 -9
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/buffers-modal.ts"],"sourcesContent":["/**\n * Fullscreen buffer viewer.\n *\n * Lives outside the Tweakpane DOM tree — mounts a top-level\n * `<div>` to `document.body` with a high `z-index`, fully blocks\n * interaction with the host page until closed. Layout:\n *\n * ┌───────────────────────────────────────────┐\n * │ ◀ tree │ ⤡ × │ ← header bar\n * │────────│ │\n * │ group1 │ canvas │\n * │ • a │ (aspect-correct) │\n * │ • b │ │\n * │ group2 │ │\n * │ • c │ │\n * │ │ │\n * └───────────────────────────────────────────┘\n *\n * Sidebar (defaults expanded, collapsible via the ◀ at the top-left):\n * - Group tree: every registered buffer, grouped by `name` prefix.\n * - Click a buffer to make it active. Active row highlighted.\n *\n * Main area:\n * - Canvas sized to fill remaining space, source bitmap rendered\n * aspect-correct (`object-fit: contain` semantics) with the same\n * decoder pipeline as the in-pane thumbnail.\n * - Header chips: dimensions, pixel type, display mode.\n * - `×` button (top-right) closes; `Esc` also closes; outer-click\n * does not close (modal is intentionally sticky to avoid\n * accidental dismiss while inspecting).\n *\n * Selection drives `client.setBuffers([activeName])` so only the\n * one buffer the user is looking at gets streamed. Closing the\n * modal restores whatever the in-pane thumbnail's selection was.\n */\n\nimport type { BufferChunkPayload, BufferSnapshot, DevtoolsClient } from './devtools-client.js'\n\nexport interface BuffersModalHandle {\n /** Show the modal with the given buffer pre-selected. */\n open(name: string): void\n /** Close it (no-op if already closed). */\n close(): void\n /** True when visible. */\n readonly isOpen: boolean\n /** Tear down DOM + listener. */\n dispose(): void\n}\n\nexport interface BuffersModalOptions {\n onActiveChange?: (name: string) => void\n onOpen?: () => void\n onClose?: () => void\n}\n\n/**\n * Build a fullscreen modal anchored to `document.body`. Returns an\n * imperative handle — the in-pane thumbnail's expand button calls\n * `open(activeName)` to surface it.\n */\nexport function createBuffersModal(\n client: DevtoolsClient,\n options: BuffersModalOptions = {},\n): BuffersModalHandle {\n // ── Root + overlay ────────────────────────────────────────────────────\n\n const root = document.createElement('div')\n root.style.cssText = [\n 'position:fixed',\n 'inset:0',\n 'z-index:99999',\n 'background:rgba(0,2,28,0.92)',\n 'color:var(--tp-label-foreground-color, #f0edd8)',\n 'font-family:var(--tp-base-font-family, ui-monospace, monospace)',\n 'font-size:12px',\n 'display:none',\n 'flex-direction:column',\n ].join(';')\n\n // ── Header bar ────────────────────────────────────────────────────────\n\n const header = document.createElement('div')\n header.style.cssText = [\n 'display:flex',\n 'align-items:center',\n 'gap:12px',\n 'padding:8px 12px',\n 'border-bottom:1px solid rgba(240,237,216,0.08)',\n 'flex:0 0 auto',\n ].join(';')\n\n const sidebarToggle = document.createElement('span')\n sidebarToggle.textContent = '◀'\n sidebarToggle.style.cssText = 'cursor:pointer;padding:2px 6px;opacity:0.8;user-select:none'\n sidebarToggle.title = 'Toggle sidebar'\n\n const titleEl = document.createElement('span')\n titleEl.style.cssText = 'flex:1;font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap'\n\n const dimsEl = document.createElement('span')\n dimsEl.style.cssText = 'opacity:0.7;font-variant-numeric:tabular-nums'\n\n const closeBtn = document.createElement('span')\n closeBtn.textContent = '×'\n closeBtn.style.cssText = 'cursor:pointer;padding:2px 10px;font-size:18px;line-height:1;opacity:0.8;user-select:none'\n closeBtn.title = 'Close'\n\n header.appendChild(sidebarToggle)\n header.appendChild(titleEl)\n header.appendChild(dimsEl)\n header.appendChild(closeBtn)\n\n // ── Body: sidebar + main ──────────────────────────────────────────────\n\n const body = document.createElement('div')\n body.style.cssText = 'display:flex;flex:1;min-height:0'\n\n const sidebar = document.createElement('div')\n sidebar.style.cssText = [\n 'display:flex',\n 'flex-direction:column',\n 'width:240px',\n 'border-right:1px solid rgba(240,237,216,0.08)',\n 'overflow-y:auto',\n 'padding:8px 0',\n 'flex:0 0 auto',\n ].join(';')\n\n const main = document.createElement('div')\n main.style.cssText = 'flex:1;display:flex;align-items:center;justify-content:center;padding:16px;min-width:0;min-height:0;overflow:hidden;position:relative'\n\n const canvas = document.createElement('canvas')\n canvas.style.cssText = 'display:block;background:rgba(0,2,28,0.6);image-rendering:pixelated;max-width:100%;max-height:100%'\n const ctx = canvas.getContext('2d')\n\n const offscreen = document.createElement('canvas')\n offscreen.width = 1\n offscreen.height = 1\n\n // Zoom info + reset — top-left to avoid conflict with docs page controls\n const zoomBar = document.createElement('div')\n zoomBar.style.cssText = [\n 'position:absolute', 'top:8px', 'left:8px',\n 'display:flex', 'gap:6px', 'align-items:center',\n 'z-index:1',\n ].join(';')\n\n const zoomInfo = document.createElement('span')\n zoomInfo.style.cssText = [\n 'padding:3px 8px', 'border-radius:4px',\n 'background:rgba(0,0,0,0.6)', 'color:#aaa',\n 'font:11px/1.4 monospace', 'pointer-events:none',\n 'user-select:none',\n ].join(';')\n zoomInfo.textContent = '1.0×'\n\n const resetBtn = document.createElement('span')\n resetBtn.style.cssText = [\n 'padding:3px 8px', 'border-radius:4px',\n 'background:rgba(0,0,0,0.6)', 'color:#aaa',\n 'font:11px/1.4 monospace', 'cursor:pointer',\n 'user-select:none',\n ].join(';')\n resetBtn.textContent = '⟲ Reset'\n resetBtn.title = 'Reset zoom & pan (double-click also resets)'\n\n zoomBar.appendChild(zoomInfo)\n zoomBar.appendChild(resetBtn)\n\n main.appendChild(canvas)\n main.appendChild(zoomBar)\n body.appendChild(sidebar)\n body.appendChild(main)\n\n root.appendChild(header)\n root.appendChild(body)\n document.body.appendChild(root)\n\n // ── State ─────────────────────────────────────────────────────────────\n\n let isOpen = false\n let activeName: string | null = null\n let lastRenderedVersion = -1\n let sidebarCollapsed = false\n\n // ── Pan + zoom ─────────────────────────────────────────────────────────\n\n const MIN_ZOOM = 0.25\n const MAX_ZOOM = 64\n\n let zoom = 1\n let panX = 0\n let panY = 0\n let isDragging = false\n let dragStartX = 0\n let dragStartY = 0\n let panStartX = 0\n let panStartY = 0\n\n function updateZoomInfo(): void {\n const z = zoom < 10 ? zoom.toFixed(1) : Math.round(zoom).toString()\n zoomInfo.textContent = zoom === 1 ? '1.0×' : `${z}× · (${Math.round(panX)}, ${Math.round(panY)})`\n const atIdentity = zoom === 1 && panX === 0 && panY === 0\n resetBtn.style.display = atIdentity ? 'none' : 'inline'\n }\n\n function applyTransform(): void {\n canvas.style.transform = `translate(${panX}px, ${panY}px) scale(${zoom})`\n canvas.style.transformOrigin = 'center center'\n updateZoomInfo()\n }\n\n function resetTransform(): void {\n zoom = 1\n panX = 0\n panY = 0\n applyTransform()\n }\n\n // Prevent page scroll when pointer is over the main area\n main.addEventListener('wheel', (e) => {\n e.preventDefault()\n e.stopPropagation()\n\n const factor = e.deltaY < 0 ? 1.15 : 1 / 1.15\n const newZoom = Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, zoom * factor))\n if (newZoom === zoom) return\n\n // Zoom toward cursor position\n const rect = main.getBoundingClientRect()\n const mx = e.clientX - rect.left - rect.width / 2\n const my = e.clientY - rect.top - rect.height / 2\n const ratio = 1 - newZoom / zoom\n panX += (mx - panX) * ratio\n panY += (my - panY) * ratio\n zoom = newZoom\n applyTransform()\n }, { passive: false })\n\n main.addEventListener('mousedown', (e) => {\n if (e.button !== 0) return\n isDragging = true\n dragStartX = e.clientX\n dragStartY = e.clientY\n panStartX = panX\n panStartY = panY\n main.style.cursor = 'grabbing'\n e.preventDefault()\n })\n\n const onMouseMove = (e: MouseEvent) => {\n if (!isDragging) return\n panX = panStartX + (e.clientX - dragStartX)\n panY = panStartY + (e.clientY - dragStartY)\n applyTransform()\n }\n\n const onMouseUp = () => {\n if (!isDragging) return\n isDragging = false\n main.style.cursor = 'grab'\n }\n\n window.addEventListener('mousemove', onMouseMove)\n window.addEventListener('mouseup', onMouseUp)\n\n main.addEventListener('dblclick', () => resetTransform())\n resetBtn.addEventListener('click', () => resetTransform())\n\n main.style.cursor = 'grab'\n\n // Track the per-row DOM nodes so we can update highlight without\n // re-rendering the whole sidebar each batch.\n const rowEls = new Map<string, HTMLDivElement>()\n\n // ── Sidebar render ────────────────────────────────────────────────────\n\n const groupContainers = new Map<string, HTMLDivElement>()\n\n function ensureGroup(group: string): HTMLDivElement {\n let el = groupContainers.get(group)\n if (el !== undefined) return el\n el = document.createElement('div')\n el.style.cssText = 'margin:6px 0'\n const head = document.createElement('div')\n head.textContent = group\n head.style.cssText = 'padding:4px 12px;opacity:0.6;font-size:11px;text-transform:uppercase;letter-spacing:0.05em;user-select:none'\n el.appendChild(head)\n sidebar.appendChild(el)\n groupContainers.set(group, el)\n return el\n }\n\n function ensureRow(group: string, name: string, shortName: string): HTMLDivElement {\n let row = rowEls.get(name)\n if (row !== undefined) return row\n const groupEl = ensureGroup(group)\n row = document.createElement('div')\n row.textContent = shortName\n row.dataset['name'] = name\n row.style.cssText = 'padding:4px 12px 4px 22px;cursor:pointer;user-select:none;border-left:2px solid transparent'\n row.addEventListener('click', () => setActive(name))\n groupEl.appendChild(row)\n rowEls.set(name, row)\n return row\n }\n\n function destroyRow(name: string): void {\n const row = rowEls.get(name)\n if (row === undefined) return\n row.remove()\n rowEls.delete(name)\n }\n\n function highlightActive(): void {\n for (const [name, row] of rowEls) {\n const on = name === activeName\n row.style.background = on ? 'rgba(71,204,169,0.15)' : 'transparent'\n row.style.borderLeftColor = on ? 'var(--tp-button-foreground-color-active, #47cca9)' : 'transparent'\n row.style.color = on ? 'var(--tp-label-foreground-color, #f0edd8)' : 'rgba(240,237,216,0.7)'\n }\n }\n\n // ── Render the active buffer into the canvas ──────────────────────────\n\n function paint(snap: BufferSnapshot): void {\n if (ctx === null) return\n const { width, height, pixels } = snap\n if (width === 0 || height === 0 || pixels === null) {\n // Size canvas to a placeholder so it still occupies space.\n canvas.width = 1; canvas.height = 1\n return\n }\n\n if (offscreen.width !== width || offscreen.height !== height) {\n offscreen.width = width\n offscreen.height = height\n }\n const offCtx = offscreen.getContext('2d')\n if (offCtx === null) return\n\n const imgData = offCtx.createImageData(width, height)\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 offCtx.putImageData(imgData, 0, 0)\n\n // Backing matches displayed pixels at 1:1 (no DPR mul — modal is\n // about inspection, not pretty rendering, and 1:1 keeps the\n // pixel grid clearly visible).\n if (canvas.width !== width || canvas.height !== height) {\n canvas.width = width\n canvas.height = height\n }\n // Fit the canvas CSS box to the SOURCE aspect — thumbnails are\n // downsampled, but the modal should still display at native aspect\n // so the user doesn't see a wildly-stretched preview while the\n // stream mode warms up.\n const fitW = snap.srcWidth > 0 ? snap.srcWidth : width\n const fitH = snap.srcHeight > 0 ? snap.srcHeight : height\n canvasSrcW = fitW\n canvasSrcH = fitH\n fitCanvas(fitW, fitH)\n ctx.imageSmoothingEnabled = false\n ctx.drawImage(offscreen, 0, 0)\n }\n\n function refresh(): void {\n if (!isOpen) return\n const state = client.state\n\n // Sync the sidebar tree.\n const present = new Set<string>()\n for (const [name] of state.buffers) {\n present.add(name)\n const dot = name.indexOf('.')\n const group = dot === -1 ? 'ungrouped' : name.slice(0, dot)\n const short = dot === -1 ? name : name.slice(dot + 1)\n ensureRow(group, name, short)\n }\n for (const name of rowEls.keys()) {\n if (!present.has(name)) destroyRow(name)\n }\n\n // If active was removed, fall back to first available.\n if (activeName !== null && !state.buffers.has(activeName)) {\n const first = state.buffers.keys().next().value\n activeName = first ?? null\n // setActive resyncs server filter, but we may be in mid-refresh\n // so call the underlying setter directly.\n if (activeName !== null) client.setBuffers({ [activeName]: { mode: 'stream' } })\n else client.setBuffers({})\n }\n highlightActive()\n\n if (activeName === null) {\n titleEl.textContent = '—'\n dimsEl.textContent = ''\n return\n }\n const snap = state.buffers.get(activeName)\n if (snap === undefined) return\n titleEl.textContent = snap.label !== undefined ? `${activeName} — ${snap.label}` : activeName\n dimsEl.textContent = `${snap.width}×${snap.height} · ${snap.pixelType} · ${snap.display}`\n // In stream mode the VideoDecoder draws directly to the canvas —\n // don't let paint() overwrite it (pixels are stripped from the\n // batch so snap.pixels is null, which would clear the canvas).\n if (decoder === null && snap.version !== lastRenderedVersion) {\n lastRenderedVersion = snap.version\n paint(snap)\n }\n }\n\n function setActive(name: string): void {\n if (activeName === name) return\n activeName = name\n lastRenderedVersion = -1\n resetTransform()\n stopDecoder()\n decoderWidth = 0\n decoderHeight = 0\n // Modal always wants the live stream — codec fallback is handled\n // provider-side (falls back to raw broadcasts when WebCodecs is\n // unavailable).\n client.setBuffers({ [name]: { mode: 'stream' } })\n options.onActiveChange?.(name)\n highlightActive()\n refresh()\n }\n\n // ── Event wiring ──────────────────────────────────────────────────────\n\n const unsubscribe = client.addListener(() => refresh())\n\n closeBtn.addEventListener('click', () => close())\n sidebarToggle.addEventListener('click', () => {\n sidebarCollapsed = !sidebarCollapsed\n sidebar.style.display = sidebarCollapsed ? 'none' : 'flex'\n sidebarToggle.textContent = sidebarCollapsed ? '▶' : '◀'\n refresh()\n })\n const onKeydown = (e: KeyboardEvent) => {\n if (!isOpen) return\n if (e.key === 'Escape') close()\n }\n document.addEventListener('keydown', onKeydown)\n\n // Aspect-fit the canvas box inside `main`, given source dimensions.\n // Shared by `paint()`, the decoder output callback, and the\n // ResizeObserver below so the CSS box always tracks the container\n // regardless of which path last touched the canvas.\n function fitCanvas(srcW: number, srcH: number): void {\n if (srcW <= 0 || srcH <= 0) return\n const mainRect = main.getBoundingClientRect()\n const maxW = Math.max(1, mainRect.width - 32)\n const maxH = Math.max(1, mainRect.height - 32)\n const srcAspect = srcW / srcH\n const boxAspect = maxW / maxH\n let cssW: number\n let cssH: number\n if (srcAspect > boxAspect) {\n cssW = maxW\n cssH = Math.max(1, Math.round(maxW / srcAspect))\n } else {\n cssH = maxH\n cssW = Math.max(1, Math.round(maxH * srcAspect))\n }\n canvas.style.width = `${cssW}px`\n canvas.style.height = `${cssH}px`\n }\n\n // Track current source dimensions so the ResizeObserver can re-fit\n // without waiting for a new frame. Set by both paint() (thumbnail\n // mode) and the decoder output callback (stream mode).\n let canvasSrcW = 0\n let canvasSrcH = 0\n\n // Re-paint on viewport resize so the canvas re-fits its container.\n const onResize = () => {\n if (!isOpen || activeName === null) return\n const snap = client.state.buffers.get(activeName)\n if (snap !== undefined) paint(snap)\n }\n window.addEventListener('resize', onResize)\n\n // Observe the container itself: sidebar collapse, viewport resize,\n // or ancestor layout changes can all change `main`'s box without\n // firing a window resize. Without this, a zoomed-in canvas keeps\n // its stale CSS size and clips against the shrunken main container.\n const mainResizeObserver = new ResizeObserver(() => {\n if (!isOpen) return\n if (canvasSrcW > 0 && canvasSrcH > 0) fitCanvas(canvasSrcW, canvasSrcH)\n })\n mainResizeObserver.observe(main)\n\n // ── Open / close ──────────────────────────────────────────────────────\n\n // ── WebCodecs decoder ─────────────────────────────────────────────────\n\n const codecAvailable = typeof VideoDecoder !== 'undefined'\n let decoder: VideoDecoder | null = null\n let unsubChunks: (() => void) | null = null\n let waitingForKeyFrame = true\n\n function startDecoder(width: number, height: number): void {\n stopDecoder()\n if (!codecAvailable) return\n waitingForKeyFrame = true\n decoder = new VideoDecoder({\n output: (frame) => {\n if (ctx === null) { frame.close(); return }\n if (canvas.width !== frame.codedWidth || canvas.height !== frame.codedHeight) {\n canvas.width = frame.codedWidth\n canvas.height = frame.codedHeight\n }\n canvasSrcW = frame.codedWidth\n canvasSrcH = frame.codedHeight\n fitCanvas(frame.codedWidth, frame.codedHeight)\n ctx.drawImage(frame as unknown as CanvasImageSource, 0, 0)\n frame.close()\n },\n error: () => {\n stopDecoder()\n },\n })\n decoder.configure({\n codec: 'vp09.00.10.08',\n codedWidth: width,\n codedHeight: height,\n })\n }\n\n function stopDecoder(): void {\n if (decoder !== null && decoder.state !== 'closed') {\n try { decoder.close() } catch { /* may already be errored */ }\n }\n decoder = null\n waitingForKeyFrame = true\n }\n\n let decoderWidth = 0\n let decoderHeight = 0\n\n function onChunk(chunk: BufferChunkPayload): void {\n if (!isOpen) return\n if (chunk.name !== activeName) return\n\n // Start or reconfigure decoder on dimension change\n if (decoder === null || chunk.width !== decoderWidth || chunk.height !== decoderHeight) {\n decoderWidth = chunk.width\n decoderHeight = chunk.height\n startDecoder(chunk.width, chunk.height)\n }\n if (decoder === null) return\n\n if (waitingForKeyFrame && !chunk.keyFrame) return\n if (chunk.keyFrame) waitingForKeyFrame = false\n\n try {\n const encoded = new EncodedVideoChunk({\n type: chunk.keyFrame ? 'key' : 'delta',\n timestamp: chunk.capturedAt * 1000,\n data: chunk.data,\n })\n decoder.decode(encoded)\n } catch {\n stopDecoder()\n }\n }\n\n // ── Open / close ──────────────────────────────────────────────────────\n\n function open(name: string): void {\n if (isOpen && activeName === name) return\n isOpen = true\n root.style.display = 'flex'\n options.onOpen?.()\n setActive(name)\n // setActive short-circuits when re-opening the same target, but\n // close() already wiped the subscription (and buffers-view may have\n // switched it to thumbnail). Force stream mode here — no-op when\n // setActive just set it.\n client.setBuffers({ [name]: { mode: 'stream' } })\n if (codecAvailable && unsubChunks === null) {\n unsubChunks = client.addChunkListener(onChunk)\n }\n }\n\n function close(): void {\n if (!isOpen) return\n isOpen = false\n root.style.display = 'none'\n stopDecoder()\n if (unsubChunks !== null) {\n unsubChunks()\n unsubChunks = null\n }\n // Revert to idle — thumbnail view (if any) will re-subscribe\n // in thumbnail mode itself when it becomes the active consumer.\n client.setBuffers({})\n options.onClose?.()\n }\n\n return {\n get isOpen(): boolean { return isOpen },\n open,\n close,\n dispose() {\n stopDecoder()\n if (unsubChunks !== null) {\n unsubChunks()\n unsubChunks = null\n }\n unsubscribe()\n mainResizeObserver.disconnect()\n document.removeEventListener('keydown', onKeydown)\n window.removeEventListener('resize', onResize)\n window.removeEventListener('mousemove', onMouseMove)\n window.removeEventListener('mouseup', onMouseUp)\n root.remove()\n rowEls.clear()\n groupContainers.clear()\n },\n }\n}\n\n"],"mappings":"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","names":[]}
1
+ {"version":3,"file":"buffers-modal.js","names":[],"sources":["../src/buffers-modal.ts"],"sourcesContent":["/**\n * Fullscreen buffer viewer.\n *\n * Lives outside the Tweakpane DOM tree — mounts a top-level\n * `<div>` to `document.body` with a high `z-index`, fully blocks\n * interaction with the host page until closed. Layout:\n *\n * ┌───────────────────────────────────────────┐\n * │ ◀ tree │ ⤡ × │ ← header bar\n * │────────│ │\n * │ group1 │ canvas │\n * │ • a │ (aspect-correct) │\n * │ • b │ │\n * │ group2 │ │\n * │ • c │ │\n * │ │ │\n * └───────────────────────────────────────────┘\n *\n * Sidebar (defaults expanded, collapsible via the ◀ at the top-left):\n * - Group tree: every registered buffer, grouped by `name` prefix.\n * - Click a buffer to make it active. Active row highlighted.\n *\n * Main area:\n * - Canvas sized to fill remaining space, source bitmap rendered\n * aspect-correct (`object-fit: contain` semantics) with the same\n * decoder pipeline as the in-pane thumbnail.\n * - Header chips: dimensions, pixel type, display mode.\n * - `×` button (top-right) closes; `Esc` also closes; outer-click\n * does not close (modal is intentionally sticky to avoid\n * accidental dismiss while inspecting).\n *\n * Selection drives `client.setBuffers([activeName])` so only the\n * one buffer the user is looking at gets streamed. Closing the\n * modal restores whatever the in-pane thumbnail's selection was.\n */\n\nimport type { BufferChunkPayload, BufferSnapshot, DevtoolsClient } from './devtools-client.js'\n\nexport interface BuffersModalHandle {\n /** Show the modal with the given buffer pre-selected. */\n open(name: string): void\n /** Close it (no-op if already closed). */\n close(): void\n /** True when visible. */\n readonly isOpen: boolean\n /** Tear down DOM + listener. */\n dispose(): void\n}\n\nexport interface BuffersModalOptions {\n onActiveChange?: (name: string) => void\n onOpen?: () => void\n onClose?: () => void\n}\n\n/**\n * Build a fullscreen modal anchored to `document.body`. Returns an\n * imperative handle — the in-pane thumbnail's expand button calls\n * `open(activeName)` to surface it.\n */\nexport function createBuffersModal(client: DevtoolsClient, options: BuffersModalOptions = {}): BuffersModalHandle {\n // ── Root + overlay ────────────────────────────────────────────────────\n\n const root = document.createElement('div')\n root.style.cssText = [\n 'position:fixed',\n 'inset:0',\n 'z-index:99999',\n 'background:rgba(0,2,28,0.92)',\n 'color:var(--tp-label-foreground-color, #f0edd8)',\n 'font-family:var(--tp-base-font-family, ui-monospace, monospace)',\n 'font-size:12px',\n 'display:none',\n 'flex-direction:column',\n ].join(';')\n\n // ── Header bar ────────────────────────────────────────────────────────\n\n const header = document.createElement('div')\n header.style.cssText = [\n 'display:flex',\n 'align-items:center',\n 'gap:12px',\n 'padding:8px 12px',\n 'border-bottom:1px solid rgba(240,237,216,0.08)',\n 'flex:0 0 auto',\n ].join(';')\n\n const sidebarToggle = document.createElement('span')\n sidebarToggle.textContent = '◀'\n sidebarToggle.style.cssText = 'cursor:pointer;padding:2px 6px;opacity:0.8;user-select:none'\n sidebarToggle.title = 'Toggle sidebar'\n\n const titleEl = document.createElement('span')\n titleEl.style.cssText = 'flex:1;font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap'\n\n const dimsEl = document.createElement('span')\n dimsEl.style.cssText = 'opacity:0.7;font-variant-numeric:tabular-nums'\n\n const closeBtn = document.createElement('span')\n closeBtn.textContent = '×'\n closeBtn.style.cssText = 'cursor:pointer;padding:2px 10px;font-size:18px;line-height:1;opacity:0.8;user-select:none'\n closeBtn.title = 'Close'\n\n header.appendChild(sidebarToggle)\n header.appendChild(titleEl)\n header.appendChild(dimsEl)\n header.appendChild(closeBtn)\n\n // ── Body: sidebar + main ──────────────────────────────────────────────\n\n const body = document.createElement('div')\n body.style.cssText = 'display:flex;flex:1;min-height:0'\n\n const sidebar = document.createElement('div')\n sidebar.style.cssText = [\n 'display:flex',\n 'flex-direction:column',\n 'width:240px',\n 'border-right:1px solid rgba(240,237,216,0.08)',\n 'overflow-y:auto',\n 'padding:8px 0',\n 'flex:0 0 auto',\n ].join(';')\n\n const main = document.createElement('div')\n main.style.cssText =\n 'flex:1;display:flex;align-items:center;justify-content:center;padding:16px;min-width:0;min-height:0;overflow:hidden;position:relative'\n\n const canvas = document.createElement('canvas')\n canvas.style.cssText =\n 'display:block;background:rgba(0,2,28,0.6);image-rendering:pixelated;max-width:100%;max-height:100%'\n const ctx = canvas.getContext('2d')\n\n const offscreen = document.createElement('canvas')\n offscreen.width = 1\n offscreen.height = 1\n\n // Zoom info + reset — top-left to avoid conflict with docs page controls\n const zoomBar = document.createElement('div')\n zoomBar.style.cssText = [\n 'position:absolute',\n 'top:8px',\n 'left:8px',\n 'display:flex',\n 'gap:6px',\n 'align-items:center',\n 'z-index:1',\n ].join(';')\n\n const zoomInfo = document.createElement('span')\n zoomInfo.style.cssText = [\n 'padding:3px 8px',\n 'border-radius:4px',\n 'background:rgba(0,0,0,0.6)',\n 'color:#aaa',\n 'font:11px/1.4 monospace',\n 'pointer-events:none',\n 'user-select:none',\n ].join(';')\n zoomInfo.textContent = '1.0×'\n\n const resetBtn = document.createElement('span')\n resetBtn.style.cssText = [\n 'padding:3px 8px',\n 'border-radius:4px',\n 'background:rgba(0,0,0,0.6)',\n 'color:#aaa',\n 'font:11px/1.4 monospace',\n 'cursor:pointer',\n 'user-select:none',\n ].join(';')\n resetBtn.textContent = '⟲ Reset'\n resetBtn.title = 'Reset zoom & pan (double-click also resets)'\n\n zoomBar.appendChild(zoomInfo)\n zoomBar.appendChild(resetBtn)\n\n main.appendChild(canvas)\n main.appendChild(zoomBar)\n body.appendChild(sidebar)\n body.appendChild(main)\n\n root.appendChild(header)\n root.appendChild(body)\n document.body.appendChild(root)\n\n // ── State ─────────────────────────────────────────────────────────────\n\n let isOpen = false\n let activeName: string | null = null\n let lastRenderedVersion = -1\n let sidebarCollapsed = false\n\n // ── Pan + zoom ─────────────────────────────────────────────────────────\n\n const MIN_ZOOM = 0.25\n const MAX_ZOOM = 64\n\n let zoom = 1\n let panX = 0\n let panY = 0\n let isDragging = false\n let dragStartX = 0\n let dragStartY = 0\n let panStartX = 0\n let panStartY = 0\n\n function updateZoomInfo(): void {\n const z = zoom < 10 ? zoom.toFixed(1) : Math.round(zoom).toString()\n zoomInfo.textContent = zoom === 1 ? '1.0×' : `${z}× · (${Math.round(panX)}, ${Math.round(panY)})`\n const atIdentity = zoom === 1 && panX === 0 && panY === 0\n resetBtn.style.display = atIdentity ? 'none' : 'inline'\n }\n\n function applyTransform(): void {\n canvas.style.transform = `translate(${panX}px, ${panY}px) scale(${zoom})`\n canvas.style.transformOrigin = 'center center'\n updateZoomInfo()\n }\n\n function resetTransform(): void {\n zoom = 1\n panX = 0\n panY = 0\n applyTransform()\n }\n\n // Prevent page scroll when pointer is over the main area\n main.addEventListener(\n 'wheel',\n (e) => {\n e.preventDefault()\n e.stopPropagation()\n\n const factor = e.deltaY < 0 ? 1.15 : 1 / 1.15\n const newZoom = Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, zoom * factor))\n if (newZoom === zoom) return\n\n // Zoom toward cursor position\n const rect = main.getBoundingClientRect()\n const mx = e.clientX - rect.left - rect.width / 2\n const my = e.clientY - rect.top - rect.height / 2\n const ratio = 1 - newZoom / zoom\n panX += (mx - panX) * ratio\n panY += (my - panY) * ratio\n zoom = newZoom\n applyTransform()\n },\n { passive: false }\n )\n\n main.addEventListener('mousedown', (e) => {\n if (e.button !== 0) return\n isDragging = true\n dragStartX = e.clientX\n dragStartY = e.clientY\n panStartX = panX\n panStartY = panY\n main.style.cursor = 'grabbing'\n e.preventDefault()\n })\n\n const onMouseMove = (e: MouseEvent) => {\n if (!isDragging) return\n panX = panStartX + (e.clientX - dragStartX)\n panY = panStartY + (e.clientY - dragStartY)\n applyTransform()\n }\n\n const onMouseUp = () => {\n if (!isDragging) return\n isDragging = false\n main.style.cursor = 'grab'\n }\n\n window.addEventListener('mousemove', onMouseMove)\n window.addEventListener('mouseup', onMouseUp)\n\n main.addEventListener('dblclick', () => resetTransform())\n resetBtn.addEventListener('click', () => resetTransform())\n\n main.style.cursor = 'grab'\n\n // Track the per-row DOM nodes so we can update highlight without\n // re-rendering the whole sidebar each batch.\n const rowEls = new Map<string, HTMLDivElement>()\n\n // ── Sidebar render ────────────────────────────────────────────────────\n\n const groupContainers = new Map<string, HTMLDivElement>()\n\n function ensureGroup(group: string): HTMLDivElement {\n let el = groupContainers.get(group)\n if (el !== undefined) return el\n el = document.createElement('div')\n el.style.cssText = 'margin:6px 0'\n const head = document.createElement('div')\n head.textContent = group\n head.style.cssText =\n 'padding:4px 12px;opacity:0.6;font-size:11px;text-transform:uppercase;letter-spacing:0.05em;user-select:none'\n el.appendChild(head)\n sidebar.appendChild(el)\n groupContainers.set(group, el)\n return el\n }\n\n function ensureRow(group: string, name: string, shortName: string): HTMLDivElement {\n let row = rowEls.get(name)\n if (row !== undefined) return row\n const groupEl = ensureGroup(group)\n row = document.createElement('div')\n row.textContent = shortName\n row.dataset['name'] = name\n row.style.cssText = 'padding:4px 12px 4px 22px;cursor:pointer;user-select:none;border-left:2px solid transparent'\n row.addEventListener('click', () => setActive(name))\n groupEl.appendChild(row)\n rowEls.set(name, row)\n return row\n }\n\n function destroyRow(name: string): void {\n const row = rowEls.get(name)\n if (row === undefined) return\n row.remove()\n rowEls.delete(name)\n }\n\n function highlightActive(): void {\n for (const [name, row] of rowEls) {\n const on = name === activeName\n row.style.background = on ? 'rgba(71,204,169,0.15)' : 'transparent'\n row.style.borderLeftColor = on ? 'var(--tp-button-foreground-color-active, #47cca9)' : 'transparent'\n row.style.color = on ? 'var(--tp-label-foreground-color, #f0edd8)' : 'rgba(240,237,216,0.7)'\n }\n }\n\n // ── Render the active buffer into the canvas ──────────────────────────\n\n function paint(snap: BufferSnapshot): void {\n if (ctx === null) return\n const { width, height, pixels } = snap\n if (width === 0 || height === 0 || pixels === null) {\n // Size canvas to a placeholder so it still occupies space.\n canvas.width = 1\n canvas.height = 1\n return\n }\n\n if (offscreen.width !== width || offscreen.height !== height) {\n offscreen.width = width\n offscreen.height = height\n }\n const offCtx = offscreen.getContext('2d')\n if (offCtx === null) return\n\n const imgData = offCtx.createImageData(width, height)\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 offCtx.putImageData(imgData, 0, 0)\n\n // Backing matches displayed pixels at 1:1 (no DPR mul — modal is\n // about inspection, not pretty rendering, and 1:1 keeps the\n // pixel grid clearly visible).\n if (canvas.width !== width || canvas.height !== height) {\n canvas.width = width\n canvas.height = height\n }\n // Fit the canvas CSS box to the SOURCE aspect — thumbnails are\n // downsampled, but the modal should still display at native aspect\n // so the user doesn't see a wildly-stretched preview while the\n // stream mode warms up.\n const fitW = snap.srcWidth > 0 ? snap.srcWidth : width\n const fitH = snap.srcHeight > 0 ? snap.srcHeight : height\n canvasSrcW = fitW\n canvasSrcH = fitH\n fitCanvas(fitW, fitH)\n ctx.imageSmoothingEnabled = false\n ctx.drawImage(offscreen, 0, 0)\n }\n\n function refresh(): void {\n if (!isOpen) return\n const state = client.state\n\n // Sync the sidebar tree.\n const present = new Set<string>()\n for (const [name] of state.buffers) {\n present.add(name)\n const dot = name.indexOf('.')\n const group = dot === -1 ? 'ungrouped' : name.slice(0, dot)\n const short = dot === -1 ? name : name.slice(dot + 1)\n ensureRow(group, name, short)\n }\n for (const name of rowEls.keys()) {\n if (!present.has(name)) destroyRow(name)\n }\n\n // If active was removed, fall back to first available.\n if (activeName !== null && !state.buffers.has(activeName)) {\n const first = state.buffers.keys().next().value\n activeName = first ?? null\n // setActive resyncs server filter, but we may be in mid-refresh\n // so call the underlying setter directly.\n if (activeName !== null) client.setBuffers({ [activeName]: { mode: 'stream' } })\n else client.setBuffers({})\n }\n highlightActive()\n\n if (activeName === null) {\n titleEl.textContent = '—'\n dimsEl.textContent = ''\n return\n }\n const snap = state.buffers.get(activeName)\n if (snap === undefined) return\n titleEl.textContent = snap.label !== undefined ? `${activeName} — ${snap.label}` : activeName\n dimsEl.textContent = `${snap.width}×${snap.height} · ${snap.pixelType} · ${snap.display}`\n // In stream mode the VideoDecoder draws directly to the canvas —\n // don't let paint() overwrite it (pixels are stripped from the\n // batch so snap.pixels is null, which would clear the canvas).\n if (decoder === null && snap.version !== lastRenderedVersion) {\n lastRenderedVersion = snap.version\n paint(snap)\n }\n }\n\n function setActive(name: string): void {\n if (activeName === name) return\n activeName = name\n lastRenderedVersion = -1\n resetTransform()\n stopDecoder()\n decoderWidth = 0\n decoderHeight = 0\n // Modal always wants the live stream — codec fallback is handled\n // provider-side (falls back to raw broadcasts when WebCodecs is\n // unavailable).\n client.setBuffers({ [name]: { mode: 'stream' } })\n options.onActiveChange?.(name)\n highlightActive()\n refresh()\n }\n\n // ── Event wiring ──────────────────────────────────────────────────────\n\n const unsubscribe = client.addListener(() => refresh())\n\n closeBtn.addEventListener('click', () => close())\n sidebarToggle.addEventListener('click', () => {\n sidebarCollapsed = !sidebarCollapsed\n sidebar.style.display = sidebarCollapsed ? 'none' : 'flex'\n sidebarToggle.textContent = sidebarCollapsed ? '▶' : '◀'\n refresh()\n })\n const onKeydown = (e: KeyboardEvent) => {\n if (!isOpen) return\n if (e.key === 'Escape') close()\n }\n document.addEventListener('keydown', onKeydown)\n\n // Aspect-fit the canvas box inside `main`, given source dimensions.\n // Shared by `paint()`, the decoder output callback, and the\n // ResizeObserver below so the CSS box always tracks the container\n // regardless of which path last touched the canvas.\n function fitCanvas(srcW: number, srcH: number): void {\n if (srcW <= 0 || srcH <= 0) return\n const mainRect = main.getBoundingClientRect()\n const maxW = Math.max(1, mainRect.width - 32)\n const maxH = Math.max(1, mainRect.height - 32)\n const srcAspect = srcW / srcH\n const boxAspect = maxW / maxH\n let cssW: number\n let cssH: number\n if (srcAspect > boxAspect) {\n cssW = maxW\n cssH = Math.max(1, Math.round(maxW / srcAspect))\n } else {\n cssH = maxH\n cssW = Math.max(1, Math.round(maxH * srcAspect))\n }\n canvas.style.width = `${cssW}px`\n canvas.style.height = `${cssH}px`\n }\n\n // Track current source dimensions so the ResizeObserver can re-fit\n // without waiting for a new frame. Set by both paint() (thumbnail\n // mode) and the decoder output callback (stream mode).\n let canvasSrcW = 0\n let canvasSrcH = 0\n\n // Re-paint on viewport resize so the canvas re-fits its container.\n const onResize = () => {\n if (!isOpen || activeName === null) return\n const snap = client.state.buffers.get(activeName)\n if (snap !== undefined) paint(snap)\n }\n window.addEventListener('resize', onResize)\n\n // Observe the container itself: sidebar collapse, viewport resize,\n // or ancestor layout changes can all change `main`'s box without\n // firing a window resize. Without this, a zoomed-in canvas keeps\n // its stale CSS size and clips against the shrunken main container.\n const mainResizeObserver = new ResizeObserver(() => {\n if (!isOpen) return\n if (canvasSrcW > 0 && canvasSrcH > 0) fitCanvas(canvasSrcW, canvasSrcH)\n })\n mainResizeObserver.observe(main)\n\n // ── Open / close ──────────────────────────────────────────────────────\n\n // ── WebCodecs decoder ─────────────────────────────────────────────────\n\n const codecAvailable = typeof VideoDecoder !== 'undefined'\n let decoder: VideoDecoder | null = null\n let unsubChunks: (() => void) | null = null\n let waitingForKeyFrame = true\n\n function startDecoder(width: number, height: number): void {\n stopDecoder()\n if (!codecAvailable) return\n waitingForKeyFrame = true\n decoder = new VideoDecoder({\n output: (frame) => {\n if (ctx === null) {\n frame.close()\n return\n }\n if (canvas.width !== frame.codedWidth || canvas.height !== frame.codedHeight) {\n canvas.width = frame.codedWidth\n canvas.height = frame.codedHeight\n }\n canvasSrcW = frame.codedWidth\n canvasSrcH = frame.codedHeight\n fitCanvas(frame.codedWidth, frame.codedHeight)\n ctx.drawImage(frame as unknown as CanvasImageSource, 0, 0)\n frame.close()\n },\n error: () => {\n stopDecoder()\n },\n })\n decoder.configure({\n codec: 'vp09.00.10.08',\n codedWidth: width,\n codedHeight: height,\n })\n }\n\n function stopDecoder(): void {\n if (decoder !== null && decoder.state !== 'closed') {\n try {\n decoder.close()\n } catch {\n /* may already be errored */\n }\n }\n decoder = null\n waitingForKeyFrame = true\n }\n\n let decoderWidth = 0\n let decoderHeight = 0\n\n function onChunk(chunk: BufferChunkPayload): void {\n if (!isOpen) return\n if (chunk.name !== activeName) return\n\n // Start or reconfigure decoder on dimension change\n if (decoder === null || chunk.width !== decoderWidth || chunk.height !== decoderHeight) {\n decoderWidth = chunk.width\n decoderHeight = chunk.height\n startDecoder(chunk.width, chunk.height)\n }\n if (decoder === null) return\n\n if (waitingForKeyFrame && !chunk.keyFrame) return\n if (chunk.keyFrame) waitingForKeyFrame = false\n\n try {\n const encoded = new EncodedVideoChunk({\n type: chunk.keyFrame ? 'key' : 'delta',\n timestamp: chunk.capturedAt * 1000,\n data: chunk.data,\n })\n decoder.decode(encoded)\n } catch {\n stopDecoder()\n }\n }\n\n // ── Open / close ──────────────────────────────────────────────────────\n\n function open(name: string): void {\n if (isOpen && activeName === name) return\n isOpen = true\n root.style.display = 'flex'\n options.onOpen?.()\n setActive(name)\n // setActive short-circuits when re-opening the same target, but\n // close() already wiped the subscription (and buffers-view may have\n // switched it to thumbnail). Force stream mode here — no-op when\n // setActive just set it.\n client.setBuffers({ [name]: { mode: 'stream' } })\n if (codecAvailable && unsubChunks === null) {\n unsubChunks = client.addChunkListener(onChunk)\n }\n }\n\n function close(): void {\n if (!isOpen) return\n isOpen = false\n root.style.display = 'none'\n stopDecoder()\n if (unsubChunks !== null) {\n unsubChunks()\n unsubChunks = null\n }\n // Revert to idle — thumbnail view (if any) will re-subscribe\n // in thumbnail mode itself when it becomes the active consumer.\n client.setBuffers({})\n options.onClose?.()\n }\n\n return {\n get isOpen(): boolean {\n return isOpen\n },\n open,\n close,\n dispose() {\n stopDecoder()\n if (unsubChunks !== null) {\n unsubChunks()\n unsubChunks = null\n }\n unsubscribe()\n mainResizeObserver.disconnect()\n document.removeEventListener('keydown', onKeydown)\n window.removeEventListener('resize', onResize)\n window.removeEventListener('mousemove', onMouseMove)\n window.removeEventListener('mouseup', onMouseUp)\n root.remove()\n rowEls.clear()\n groupContainers.clear()\n },\n }\n}\n"],"mappings":"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"}
@@ -1,39 +1,27 @@
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
-
1
+ import { DevtoolsClient } from "./devtools-client.js";
2
+ import { FolderApi, Pane } from "tweakpane";
3
+ //#region src/buffers-view.d.ts
17
4
  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;
5
+ readonly element: HTMLElement;
6
+ /** Tell the view the modal is open/closed. While open, the view
7
+ * defers buffer selection to the modal. */
8
+ setModalOpen(open: boolean): void;
9
+ /** Sync the view's active buffer to match the modal's selection. */
10
+ setActiveFromModal(name: string): void;
11
+ /** Tell the view the outer pane was folded/unfolded. When folded the
12
+ * view unsubscribes regardless of its own collapse state. */
13
+ setPaneFolded(folded: boolean): void;
14
+ dispose(): void;
28
15
  }
29
16
  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;
17
+ /**
18
+ * Invoked when the user clicks the bottom-right ⤢ button. Wired by
19
+ * `createPane` to open the fullscreen modal viewer with the active
20
+ * buffer pre-selected. When omitted, the click is a no-op.
21
+ */
22
+ onExpand?: (name: string) => void;
36
23
  }
37
24
  declare function addBuffersView(parent: Pane | FolderApi, client: DevtoolsClient, options?: AddBuffersViewOptions): BuffersViewHandle;
38
-
39
- export { type AddBuffersViewOptions, type BuffersViewHandle, addBuffersView };
25
+ //#endregion
26
+ export { AddBuffersViewOptions, BuffersViewHandle, addBuffersView };
27
+ //# sourceMappingURL=buffers-view.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buffers-view.d.ts","names":[],"sources":["../src/buffers-view.ts"],"mappings":";;;UAgBiB;WACN,SAAS;;;EAGlB,aAAa;;EAEb,mBAAmB;;;EAGnB,cAAc;EACd;;UAQe;;;;;;EAMf,YAAY;;iBAGE,eACd,QAAQ,OAAO,WACf,QAAQ,gBACR,UAAS,wBACR"}