@zendir/ui 0.2.20 → 0.3.0

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 (260) hide show
  1. package/CHANGELOG.md +192 -1
  2. package/README.md +70 -28
  3. package/dist/index.d.ts +1 -1
  4. package/dist/index.js +57 -41
  5. package/dist/index.js.map +1 -1
  6. package/dist/react/3d/CesiumCaptureSource.d.ts +119 -0
  7. package/dist/react/3d/CesiumCaptureSource.js +307 -0
  8. package/dist/react/3d/CesiumCaptureSource.js.map +1 -0
  9. package/dist/react/3d/ZenSpace3D.js +1253 -0
  10. package/dist/react/3d/ZenSpace3D.js.map +1 -0
  11. package/dist/react/3d/ZenSpace3DCesium.js +579 -0
  12. package/dist/react/3d/ZenSpace3DCesium.js.map +1 -0
  13. package/dist/react/3d/ZenSpace3DTypes.d.ts +28 -1
  14. package/dist/react/3d/ZenSpace3DUtils.d.ts +17 -173
  15. package/dist/react/3d/ZenSpace3DUtils.js +28 -0
  16. package/dist/react/3d/ZenSpace3DUtils.js.map +1 -0
  17. package/dist/react/3d/capturePngAnalysis.d.ts +16 -0
  18. package/dist/react/3d/index.d.ts +10 -12
  19. package/dist/react/3d/threeLoader.js +18 -0
  20. package/dist/react/3d/threeLoader.js.map +1 -0
  21. package/dist/react/astro/MonitoringIcon.js +1 -1
  22. package/dist/react/astro/MonitoringIcon.js.map +1 -1
  23. package/dist/react/astro/SimulationControls.js +2 -2
  24. package/dist/react/astro/SimulationControls.js.map +1 -1
  25. package/dist/react/astro/UnifiedTimeline.js +4 -4
  26. package/dist/react/astro/UnifiedTimeline.js.map +1 -1
  27. package/dist/react/charts/GroundTrackMap.d.ts +2 -15
  28. package/dist/react/charts/GroundTrackMap.js +1 -1
  29. package/dist/react/charts/GroundTrackMap.js.map +1 -1
  30. package/dist/react/charts/unified/AstroChart.js +34 -13
  31. package/dist/react/charts/unified/AstroChart.js.map +1 -1
  32. package/dist/react/chatgpt/AppCard.d.ts +0 -4
  33. package/dist/react/chatgpt/index.d.ts +0 -19
  34. package/dist/react/context/SpatialSelectionContext.d.ts +40 -0
  35. package/dist/react/context/SpatialSelectionContext.js +10 -0
  36. package/dist/react/context/SpatialSelectionContext.js.map +1 -0
  37. package/dist/react/context/index.d.ts +2 -0
  38. package/dist/react/core/{DataTable.d.ts → data/DataTable.d.ts} +1 -1
  39. package/dist/react/core/{DataTable.js → data/DataTable.js} +4 -4
  40. package/dist/react/core/data/DataTable.js.map +1 -0
  41. package/dist/react/core/{DataValue.d.ts → data/DataValue.d.ts} +2 -2
  42. package/dist/react/core/{DataValue.js → data/DataValue.js} +2 -2
  43. package/dist/react/core/data/DataValue.js.map +1 -0
  44. package/dist/react/core/{propertyConfig.d.ts → data/propertyConfig.d.ts} +2 -2
  45. package/dist/react/core/data/propertyConfig.js.map +1 -0
  46. package/dist/react/core/{AstroIcon.js → display/AstroIcon.js} +1 -1
  47. package/dist/react/core/display/AstroIcon.js.map +1 -0
  48. package/dist/react/core/{Badge.d.ts → display/Badge.d.ts} +1 -1
  49. package/dist/react/core/{Badge.js → display/Badge.js} +2 -2
  50. package/dist/react/core/display/Badge.js.map +1 -0
  51. package/dist/react/core/{CardHeader.d.ts → display/CardHeader.d.ts} +1 -1
  52. package/dist/react/core/{CardHeader.js → display/CardHeader.js} +2 -2
  53. package/dist/react/core/display/CardHeader.js.map +1 -0
  54. package/dist/react/core/{Container.d.ts → display/Container.d.ts} +1 -1
  55. package/dist/react/core/{Container.js → display/Container.js} +3 -3
  56. package/dist/react/core/display/Container.js.map +1 -0
  57. package/dist/react/core/{CopyButton.js → display/CopyButton.js} +1 -1
  58. package/dist/react/core/display/CopyButton.js.map +1 -0
  59. package/dist/react/core/{GlassCard.d.ts → display/GlassCard.d.ts} +1 -1
  60. package/dist/react/core/{GlassCard.js → display/GlassCard.js} +2 -2
  61. package/dist/react/core/display/GlassCard.js.map +1 -0
  62. package/dist/react/core/{HeaderIconWithStatus.d.ts → display/HeaderIconWithStatus.d.ts} +1 -1
  63. package/dist/react/core/{HeaderIconWithStatus.js → display/HeaderIconWithStatus.js} +1 -1
  64. package/dist/react/core/display/HeaderIconWithStatus.js.map +1 -0
  65. package/dist/react/core/{Icon.d.ts → display/Icon.d.ts} +1 -1
  66. package/dist/react/core/{Icon.js → display/Icon.js} +1 -1
  67. package/dist/react/core/display/Icon.js.map +1 -0
  68. package/dist/react/core/{Typography.d.ts → display/Typography.d.ts} +13 -4
  69. package/dist/react/core/{Typography.js → display/Typography.js} +1 -1
  70. package/dist/react/core/display/Typography.js.map +1 -0
  71. package/dist/react/core/{ConfirmDialog.js → feedback/ConfirmDialog.js} +1 -1
  72. package/dist/react/core/feedback/ConfirmDialog.js.map +1 -0
  73. package/dist/react/core/{Dialog.js → feedback/Dialog.js} +2 -2
  74. package/dist/react/core/feedback/Dialog.js.map +1 -0
  75. package/dist/react/core/{Toast.js → feedback/Toast.js} +3 -3
  76. package/dist/react/core/feedback/Toast.js.map +1 -0
  77. package/dist/react/core/index.d.ts +85 -83
  78. package/dist/react/core/{Button.js → inputs/Button.js} +2 -2
  79. package/dist/react/core/inputs/Button.js.map +1 -0
  80. package/dist/react/core/{Checkbox.js → inputs/Checkbox.js} +2 -2
  81. package/dist/react/core/inputs/Checkbox.js.map +1 -0
  82. package/dist/react/core/{Input.d.ts → inputs/Input.d.ts} +1 -1
  83. package/dist/react/core/{Input.js → inputs/Input.js} +3 -3
  84. package/dist/react/core/inputs/Input.js.map +1 -0
  85. package/dist/react/core/{LimitsBar.js → inputs/LimitsBar.js} +1 -1
  86. package/dist/react/core/inputs/LimitsBar.js.map +1 -0
  87. package/dist/react/core/{NumberInput.d.ts → inputs/NumberInput.d.ts} +2 -2
  88. package/dist/react/core/{NumberInput.js → inputs/NumberInput.js} +3 -3
  89. package/dist/react/core/inputs/NumberInput.js.map +1 -0
  90. package/dist/react/core/{PinInput.js → inputs/PinInput.js} +2 -2
  91. package/dist/react/core/inputs/PinInput.js.map +1 -0
  92. package/dist/react/core/{Select.js → inputs/Select.js} +3 -3
  93. package/dist/react/core/inputs/Select.js.map +1 -0
  94. package/dist/react/core/{Toggle.js → inputs/Toggle.js} +2 -2
  95. package/dist/react/core/inputs/Toggle.js.map +1 -0
  96. package/dist/react/core/{AppBar.d.ts → navigation/AppBar.d.ts} +1 -1
  97. package/dist/react/core/{AppBar.js → navigation/AppBar.js} +7 -7
  98. package/dist/react/core/navigation/AppBar.js.map +1 -0
  99. package/dist/react/core/{Pagination.js → navigation/Pagination.js} +2 -2
  100. package/dist/react/core/navigation/Pagination.js.map +1 -0
  101. package/dist/react/core/{SideNav.d.ts → navigation/SideNav.d.ts} +1 -1
  102. package/dist/react/core/{SideNav.js → navigation/SideNav.js} +8 -9
  103. package/dist/react/core/navigation/SideNav.js.map +1 -0
  104. package/dist/react/core/{Tabs.js → navigation/Tabs.js} +2 -2
  105. package/dist/react/core/navigation/Tabs.js.map +1 -0
  106. package/dist/react/core/{Popover.js → overlays/Popover.js} +1 -1
  107. package/dist/react/core/overlays/Popover.js.map +1 -0
  108. package/dist/react/core/{SidePanel.js → overlays/SidePanel.js} +7 -7
  109. package/dist/react/core/overlays/SidePanel.js.map +1 -0
  110. package/dist/react/core/{Tooltip.js → overlays/Tooltip.js} +2 -2
  111. package/dist/react/core/overlays/Tooltip.js.map +1 -0
  112. package/dist/react/core/{ActivityPlanner.js → widgets/ActivityPlanner.js} +1 -1
  113. package/dist/react/core/widgets/ActivityPlanner.js.map +1 -0
  114. package/dist/react/core/widgets/Capture.d.ts +140 -0
  115. package/dist/react/core/widgets/Capture.js +804 -0
  116. package/dist/react/core/widgets/Capture.js.map +1 -0
  117. package/dist/react/core/{ChatPanel.d.ts → widgets/ChatPanel.d.ts} +1 -1
  118. package/dist/react/core/{ChatPanel.js → widgets/ChatPanel.js} +5 -4
  119. package/dist/react/core/widgets/ChatPanel.js.map +1 -0
  120. package/dist/react/core/{ColorPickerPanel.d.ts → widgets/ColorPickerPanel.d.ts} +1 -1
  121. package/dist/react/core/{ColorPickerPanel.js → widgets/ColorPickerPanel.js} +3 -3
  122. package/dist/react/core/widgets/ColorPickerPanel.js.map +1 -0
  123. package/dist/react/core/{CommandBuilder.js → widgets/CommandBuilder.js} +1 -1
  124. package/dist/react/core/widgets/CommandBuilder.js.map +1 -0
  125. package/dist/react/core/{ConnectionForm.d.ts → widgets/ConnectionForm.d.ts} +1 -1
  126. package/dist/react/core/{ConnectionForm.js → widgets/ConnectionForm.js} +2 -2
  127. package/dist/react/core/widgets/ConnectionForm.js.map +1 -0
  128. package/dist/react/core/{FileExplorer.js → widgets/FileExplorer.js} +2 -2
  129. package/dist/react/core/widgets/FileExplorer.js.map +1 -0
  130. package/dist/react/core/{HexViewer.js → widgets/HexViewer.js} +1 -1
  131. package/dist/react/core/widgets/HexViewer.js.map +1 -0
  132. package/dist/react/core/{ImageGallery.d.ts → widgets/ImageGallery.d.ts} +1 -1
  133. package/dist/react/core/{ImageGallery.js → widgets/ImageGallery.js} +3 -3
  134. package/dist/react/core/widgets/ImageGallery.js.map +1 -0
  135. package/dist/react/core/{LogViewer.d.ts → widgets/LogViewer.d.ts} +13 -3
  136. package/dist/react/core/{LogViewer.js → widgets/LogViewer.js} +28 -8
  137. package/dist/react/core/widgets/LogViewer.js.map +1 -0
  138. package/dist/react/core/{MessageStream.d.ts → widgets/MessageStream.d.ts} +2 -2
  139. package/dist/react/core/{MessageStream.js → widgets/MessageStream.js} +4 -4
  140. package/dist/react/core/widgets/MessageStream.js.map +1 -0
  141. package/dist/react/core/{MissionCalendar.js → widgets/MissionCalendar.js} +2 -2
  142. package/dist/react/core/widgets/MissionCalendar.js.map +1 -0
  143. package/dist/react/core/{PacketViewer.js → widgets/PacketViewer.js} +1 -1
  144. package/dist/react/core/widgets/PacketViewer.js.map +1 -0
  145. package/dist/react/core/widgets/capture-placeholder.png.js +5 -0
  146. package/dist/react/core/widgets/capture-placeholder.png.js.map +1 -0
  147. package/dist/react/hooks/index.d.ts +9 -11
  148. package/dist/react/hooks/useAccessWindows.d.ts +15 -19
  149. package/dist/react/hooks/useGroundTrackHistory.d.ts +34 -0
  150. package/dist/react/hooks/useSimulationScene.d.ts +141 -0
  151. package/dist/react/hooks/useSimulationScene.js +401 -0
  152. package/dist/react/hooks/useSimulationScene.js.map +1 -0
  153. package/dist/react/hooks/useZendirSession.d.ts +44 -69
  154. package/dist/react/index.d.ts +10 -5
  155. package/dist/react/panels/LayerControlPanel.d.ts +54 -0
  156. package/dist/react/panels/LayerControlPanel.js +184 -0
  157. package/dist/react/panels/LayerControlPanel.js.map +1 -0
  158. package/dist/react/panels/ObjectInventoryPanel.d.ts +57 -0
  159. package/dist/react/panels/ObjectInventoryPanel.js +261 -0
  160. package/dist/react/panels/ObjectInventoryPanel.js.map +1 -0
  161. package/dist/react/panels/index.d.ts +15 -0
  162. package/dist/react/theme/ThemeProvider.d.ts +2 -0
  163. package/dist/react/theme/ThemeProvider.js +50 -72
  164. package/dist/react/theme/ThemeProvider.js.map +1 -1
  165. package/dist/react/types.d.ts +32 -3
  166. package/dist/react/types.js.map +1 -1
  167. package/dist/react.js +57 -41
  168. package/dist/react.js.map +1 -1
  169. package/dist/shaders/atmosphere.frag.js +5 -0
  170. package/dist/shaders/atmosphere.frag.js.map +1 -0
  171. package/dist/shaders/atmosphere.vert.js +5 -0
  172. package/dist/shaders/atmosphere.vert.js.map +1 -0
  173. package/dist/shaders/stars.frag.js +5 -0
  174. package/dist/shaders/stars.frag.js.map +1 -0
  175. package/dist/shaders/stars.vert.js +5 -0
  176. package/dist/shaders/stars.vert.js.map +1 -0
  177. package/dist/style.css +6 -4
  178. package/dist/tokens/css-vars.d.ts +91 -0
  179. package/dist/tokens/css-vars.js +228 -0
  180. package/dist/tokens/css-vars.js.map +1 -0
  181. package/dist/tokens/index.d.ts +71 -18
  182. package/dist/tokens/index.js +206 -97
  183. package/dist/tokens/index.js.map +1 -1
  184. package/dist/tokens/tokens.css +50 -50
  185. package/package.json +27 -22
  186. package/sdk-stub.js +10 -5
  187. package/dist/react/3d/EarthViewer.d.ts +0 -46
  188. package/dist/react/3d/SolarSystemViewer.d.ts +0 -43
  189. package/dist/react/chatgpt/ChatGPTCard.d.ts +0 -6
  190. package/dist/react/core/ActivityPlanner.js.map +0 -1
  191. package/dist/react/core/AppBar.js.map +0 -1
  192. package/dist/react/core/AstroIcon.js.map +0 -1
  193. package/dist/react/core/Badge.js.map +0 -1
  194. package/dist/react/core/Button.js.map +0 -1
  195. package/dist/react/core/CardHeader.js.map +0 -1
  196. package/dist/react/core/ChatPanel.js.map +0 -1
  197. package/dist/react/core/Checkbox.js.map +0 -1
  198. package/dist/react/core/ColorPickerPanel.js.map +0 -1
  199. package/dist/react/core/CommandBuilder.js.map +0 -1
  200. package/dist/react/core/ConfirmDialog.js.map +0 -1
  201. package/dist/react/core/ConnectionForm.js.map +0 -1
  202. package/dist/react/core/Container.js.map +0 -1
  203. package/dist/react/core/CopyButton.js.map +0 -1
  204. package/dist/react/core/DataTable.js.map +0 -1
  205. package/dist/react/core/DataValue.js.map +0 -1
  206. package/dist/react/core/Dialog.js.map +0 -1
  207. package/dist/react/core/FileExplorer.js.map +0 -1
  208. package/dist/react/core/GlassCard.js.map +0 -1
  209. package/dist/react/core/HeaderIconWithStatus.js.map +0 -1
  210. package/dist/react/core/HexViewer.js.map +0 -1
  211. package/dist/react/core/Icon.js.map +0 -1
  212. package/dist/react/core/ImageGallery.js.map +0 -1
  213. package/dist/react/core/Input.js.map +0 -1
  214. package/dist/react/core/LimitsBar.js.map +0 -1
  215. package/dist/react/core/LogViewer.js.map +0 -1
  216. package/dist/react/core/MessageStream.js.map +0 -1
  217. package/dist/react/core/MissionCalendar.js.map +0 -1
  218. package/dist/react/core/NumberInput.js.map +0 -1
  219. package/dist/react/core/PacketViewer.js.map +0 -1
  220. package/dist/react/core/Pagination.js.map +0 -1
  221. package/dist/react/core/PinInput.js.map +0 -1
  222. package/dist/react/core/Popover.js.map +0 -1
  223. package/dist/react/core/Select.js.map +0 -1
  224. package/dist/react/core/SideNav.js.map +0 -1
  225. package/dist/react/core/SidePanel.js.map +0 -1
  226. package/dist/react/core/Tabs.js.map +0 -1
  227. package/dist/react/core/Toast.js.map +0 -1
  228. package/dist/react/core/Toggle.js.map +0 -1
  229. package/dist/react/core/Tooltip.js.map +0 -1
  230. package/dist/react/core/Typography.js.map +0 -1
  231. package/dist/react/core/propertyConfig.js.map +0 -1
  232. package/dist/react/hooks/useSimulationTime.d.ts +0 -61
  233. package/dist/react/hooks/useSpacecraftPosition.d.ts +0 -50
  234. package/dist/react/hooks/useTelemetry.d.ts +0 -55
  235. package/dist/types.d.ts +0 -1
  236. package/dist/types.js +0 -2
  237. package/dist/types.js.map +0 -1
  238. /package/dist/react/core/{propertyConfig.js → data/propertyConfig.js} +0 -0
  239. /package/dist/react/core/{AstroIcon.d.ts → display/AstroIcon.d.ts} +0 -0
  240. /package/dist/react/core/{CopyButton.d.ts → display/CopyButton.d.ts} +0 -0
  241. /package/dist/react/core/{ConfirmDialog.d.ts → feedback/ConfirmDialog.d.ts} +0 -0
  242. /package/dist/react/core/{Dialog.d.ts → feedback/Dialog.d.ts} +0 -0
  243. /package/dist/react/core/{Toast.d.ts → feedback/Toast.d.ts} +0 -0
  244. /package/dist/react/core/{Button.d.ts → inputs/Button.d.ts} +0 -0
  245. /package/dist/react/core/{Checkbox.d.ts → inputs/Checkbox.d.ts} +0 -0
  246. /package/dist/react/core/{LimitsBar.d.ts → inputs/LimitsBar.d.ts} +0 -0
  247. /package/dist/react/core/{PinInput.d.ts → inputs/PinInput.d.ts} +0 -0
  248. /package/dist/react/core/{Select.d.ts → inputs/Select.d.ts} +0 -0
  249. /package/dist/react/core/{Toggle.d.ts → inputs/Toggle.d.ts} +0 -0
  250. /package/dist/react/core/{Pagination.d.ts → navigation/Pagination.d.ts} +0 -0
  251. /package/dist/react/core/{Tabs.d.ts → navigation/Tabs.d.ts} +0 -0
  252. /package/dist/react/core/{Popover.d.ts → overlays/Popover.d.ts} +0 -0
  253. /package/dist/react/core/{SidePanel.d.ts → overlays/SidePanel.d.ts} +0 -0
  254. /package/dist/react/core/{Tooltip.d.ts → overlays/Tooltip.d.ts} +0 -0
  255. /package/dist/react/core/{ActivityPlanner.d.ts → widgets/ActivityPlanner.d.ts} +0 -0
  256. /package/dist/react/core/{CommandBuilder.d.ts → widgets/CommandBuilder.d.ts} +0 -0
  257. /package/dist/react/core/{FileExplorer.d.ts → widgets/FileExplorer.d.ts} +0 -0
  258. /package/dist/react/core/{HexViewer.d.ts → widgets/HexViewer.d.ts} +0 -0
  259. /package/dist/react/core/{MissionCalendar.d.ts → widgets/MissionCalendar.d.ts} +0 -0
  260. /package/dist/react/core/{PacketViewer.d.ts → widgets/PacketViewer.d.ts} +0 -0
@@ -0,0 +1,804 @@
1
+ import { jsxs, jsx, Fragment } from "react/jsx-runtime";
2
+ import { memo, forwardRef, useState, useRef, useEffect, useCallback, useImperativeHandle } from "react";
3
+ import { useCopyToClipboard } from "../display/CopyButton.js";
4
+ import placeholderImageUrl from "./capture-placeholder.png.js";
5
+ import { useTheme } from "../../theme/ThemeProvider.js";
6
+ async function generateCanvasFallback() {
7
+ try {
8
+ const size = 256;
9
+ const canvas = document.createElement("canvas");
10
+ canvas.width = size;
11
+ canvas.height = size;
12
+ const ctx = canvas.getContext("2d");
13
+ const bg = ctx.createRadialGradient(size / 2, size / 2, 20, size / 2, size / 2, size);
14
+ bg.addColorStop(0, "#1e2a4a");
15
+ bg.addColorStop(1, "#0a0e1a");
16
+ ctx.fillStyle = bg;
17
+ ctx.fillRect(0, 0, size, size);
18
+ ctx.fillStyle = "rgba(255, 255, 255, 0.5)";
19
+ for (let i = 0; i < 40; i++) {
20
+ const sx = (i * 97 + 31) % size;
21
+ const sy = (i * 61 + 17) % size;
22
+ const sr = (i % 3 + 1) * 0.6;
23
+ ctx.beginPath();
24
+ ctx.arc(sx, sy, sr, 0, Math.PI * 2);
25
+ ctx.fill();
26
+ }
27
+ ctx.strokeStyle = "rgba(74, 143, 255, 0.35)";
28
+ ctx.lineWidth = 1;
29
+ ctx.beginPath();
30
+ ctx.moveTo(size / 2, 40);
31
+ ctx.lineTo(size / 2, size - 40);
32
+ ctx.moveTo(40, size / 2);
33
+ ctx.lineTo(size - 40, size / 2);
34
+ ctx.stroke();
35
+ ctx.beginPath();
36
+ ctx.arc(size / 2, size / 2, 50, 0, Math.PI * 2);
37
+ ctx.stroke();
38
+ ctx.fillStyle = "#4a8fff";
39
+ ctx.font = "bold 28px monospace";
40
+ ctx.textAlign = "center";
41
+ ctx.textBaseline = "middle";
42
+ ctx.fillText("ZENDIR", size / 2, size / 2 - 16);
43
+ ctx.fillStyle = "rgba(255, 255, 255, 0.4)";
44
+ ctx.font = "13px monospace";
45
+ ctx.fillText("PLACEHOLDER", size / 2, size / 2 + 14);
46
+ ctx.fillStyle = "rgba(255, 255, 255, 0.25)";
47
+ ctx.font = "10px monospace";
48
+ ctx.fillText(`${size}x${size}`, size / 2, size - 20);
49
+ const blob = await new Promise(
50
+ (resolve) => canvas.toBlob((b) => resolve(b), "image/png")
51
+ );
52
+ return new Uint8Array(await blob.arrayBuffer());
53
+ } catch {
54
+ return new Uint8Array([
55
+ 137,
56
+ 80,
57
+ 78,
58
+ 71,
59
+ 13,
60
+ 10,
61
+ 26,
62
+ 10,
63
+ 0,
64
+ 0,
65
+ 0,
66
+ 13,
67
+ 73,
68
+ 72,
69
+ 68,
70
+ 82,
71
+ 0,
72
+ 0,
73
+ 0,
74
+ 1,
75
+ 0,
76
+ 0,
77
+ 0,
78
+ 1,
79
+ 8,
80
+ 2,
81
+ 0,
82
+ 0,
83
+ 0,
84
+ 144,
85
+ 119,
86
+ 83,
87
+ 222,
88
+ 0,
89
+ 0,
90
+ 0,
91
+ 12,
92
+ 73,
93
+ 68,
94
+ 65,
95
+ 84,
96
+ 120,
97
+ 156,
98
+ 98,
99
+ 104,
100
+ 96,
101
+ 96,
102
+ 0,
103
+ 0,
104
+ 0,
105
+ 4,
106
+ 0,
107
+ 1,
108
+ 39,
109
+ 52,
110
+ 39,
111
+ 10,
112
+ 0,
113
+ 0,
114
+ 0,
115
+ 0,
116
+ 73,
117
+ 69,
118
+ 78,
119
+ 68,
120
+ 174,
121
+ 66,
122
+ 96,
123
+ 130
124
+ ]);
125
+ }
126
+ }
127
+ let placeholderCache = null;
128
+ async function loadPlaceholderImage() {
129
+ if (placeholderCache) return placeholderCache;
130
+ try {
131
+ const response = await fetch(placeholderImageUrl);
132
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
133
+ const buf = await response.arrayBuffer();
134
+ if (buf.byteLength < 100) throw new Error("Suspiciously small image");
135
+ placeholderCache = new Uint8Array(buf);
136
+ } catch {
137
+ placeholderCache = await generateCanvasFallback();
138
+ }
139
+ return placeholderCache;
140
+ }
141
+ async function loadCapturePlaceholderImage() {
142
+ return loadPlaceholderImage();
143
+ }
144
+ function useCapture(options) {
145
+ const serverIdRef = useRef((options == null ? void 0 : options.serverId) || crypto.randomUUID());
146
+ const [paused, setPaused] = useState(false);
147
+ const [requestCount, setRequestCount] = useState(0);
148
+ const [lastRequest, setLastRequest] = useState(null);
149
+ const [capturedImages, setCapturedImages] = useState([]);
150
+ const imageSourceRef = useRef(options == null ? void 0 : options.imageSource);
151
+ imageSourceRef.current = options == null ? void 0 : options.imageSource;
152
+ const pausedRef = useRef(paused);
153
+ pausedRef.current = paused;
154
+ const imageCacheRef = useRef(/* @__PURE__ */ new Map());
155
+ const cesiumSourceRef = useRef(null);
156
+ const cesiumFailedRef = useRef(false);
157
+ const cesiumLoadingRef = useRef(null);
158
+ const initCesium = useCallback(async () => {
159
+ if (cesiumSourceRef.current || cesiumFailedRef.current) return;
160
+ try {
161
+ const { createCesiumCaptureSource } = await import("../../3d/CesiumCaptureSource.js");
162
+ cesiumSourceRef.current = await createCesiumCaptureSource();
163
+ } catch {
164
+ cesiumFailedRef.current = true;
165
+ }
166
+ }, []);
167
+ const preload = (options == null ? void 0 : options.preload) !== false;
168
+ useEffect(() => {
169
+ if (imageSourceRef.current || !preload) return;
170
+ cesiumLoadingRef.current = initCesium();
171
+ }, [preload, initCesium]);
172
+ useEffect(() => {
173
+ return () => {
174
+ var _a;
175
+ (_a = cesiumSourceRef.current) == null ? void 0 : _a.destroy();
176
+ cesiumSourceRef.current = null;
177
+ };
178
+ }, []);
179
+ const capture = useCallback(async (request) => {
180
+ if (pausedRef.current) return new Uint8Array(0);
181
+ if (!request || request.type !== "capture" || typeof request.req_id !== "number") {
182
+ return new Uint8Array(0);
183
+ }
184
+ const cached = imageCacheRef.current.get(request.req_id);
185
+ if (cached) return cached;
186
+ let bytes;
187
+ const src = imageSourceRef.current;
188
+ if (src instanceof Uint8Array) {
189
+ bytes = src;
190
+ } else if (typeof src === "function") {
191
+ bytes = await src(request.args);
192
+ } else {
193
+ if (!cesiumSourceRef.current && !cesiumFailedRef.current) {
194
+ if (cesiumLoadingRef.current) {
195
+ await cesiumLoadingRef.current;
196
+ } else {
197
+ await initCesium();
198
+ }
199
+ }
200
+ if (cesiumSourceRef.current) {
201
+ bytes = await cesiumSourceRef.current.capture(request.args);
202
+ } else {
203
+ bytes = await loadPlaceholderImage();
204
+ }
205
+ }
206
+ imageCacheRef.current.set(request.req_id, bytes);
207
+ setRequestCount((c) => c + 1);
208
+ setLastRequest(request);
209
+ const mimeType = request.args.format === "jpeg" ? "image/jpeg" : "image/png";
210
+ const blob = new Blob([bytes], { type: mimeType });
211
+ const dataUrl = URL.createObjectURL(blob);
212
+ setCapturedImages((prev) => [
213
+ { reqId: request.req_id, dataUrl, timestamp: Date.now(), bytes: bytes.length, args: request.args },
214
+ ...prev
215
+ ]);
216
+ return bytes;
217
+ }, []);
218
+ return {
219
+ serverId: serverIdRef.current,
220
+ capture,
221
+ paused,
222
+ setPaused,
223
+ requestCount,
224
+ lastRequest,
225
+ capturedImages
226
+ };
227
+ }
228
+ const PauseIcon = ({ size = 14 }) => /* @__PURE__ */ jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "currentColor", children: [
229
+ /* @__PURE__ */ jsx("rect", { x: "6", y: "4", width: "4", height: "16", rx: "1" }),
230
+ /* @__PURE__ */ jsx("rect", { x: "14", y: "4", width: "4", height: "16", rx: "1" })
231
+ ] });
232
+ const PlayIcon = ({ size = 14 }) => /* @__PURE__ */ jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsx("polygon", { points: "6,4 20,12 6,20" }) });
233
+ const ChevronIcon = ({ size = 14, expanded }) => /* @__PURE__ */ jsx(
234
+ "svg",
235
+ {
236
+ width: size,
237
+ height: size,
238
+ viewBox: "0 0 24 24",
239
+ fill: "none",
240
+ stroke: "currentColor",
241
+ strokeWidth: "2",
242
+ strokeLinecap: "round",
243
+ strokeLinejoin: "round",
244
+ style: {
245
+ transition: "transform 0.2s ease",
246
+ transform: expanded ? "rotate(90deg)" : "rotate(0deg)"
247
+ },
248
+ children: /* @__PURE__ */ jsx("polyline", { points: "9 18 15 12 9 6" })
249
+ }
250
+ );
251
+ const CopyIcon = ({ size = 14 }) => /* @__PURE__ */ jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
252
+ /* @__PURE__ */ jsx("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2", ry: "2" }),
253
+ /* @__PURE__ */ jsx("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" })
254
+ ] });
255
+ const CheckIcon = ({ size = 14, color }) => /* @__PURE__ */ jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("polyline", { points: "20 6 9 17 4 12" }) });
256
+ const Capture = memo(forwardRef(function Capture2({
257
+ connected = false,
258
+ imageSource,
259
+ preload,
260
+ serverId: serverIdProp,
261
+ onCapture,
262
+ onReady,
263
+ defaultGalleryExpanded = false,
264
+ style
265
+ }, ref) {
266
+ const { tokens } = useTheme();
267
+ const {
268
+ serverId,
269
+ capture,
270
+ paused,
271
+ setPaused,
272
+ requestCount,
273
+ capturedImages
274
+ } = useCapture({ imageSource, preload, serverId: serverIdProp });
275
+ const { copy, copied } = useCopyToClipboard();
276
+ const [galleryExpanded, setGalleryExpanded] = useState(defaultGalleryExpanded);
277
+ const [hoveredBtn, setHoveredBtn] = useState(null);
278
+ const readyFiredRef = useRef(false);
279
+ useEffect(() => {
280
+ if (onReady && !readyFiredRef.current) {
281
+ readyFiredRef.current = true;
282
+ onReady(serverId);
283
+ }
284
+ }, [onReady, serverId]);
285
+ const wrappedCapture = useCallback(async (request) => {
286
+ const bytes = await capture(request);
287
+ if (bytes.length > 0) {
288
+ onCapture == null ? void 0 : onCapture(request);
289
+ }
290
+ return bytes;
291
+ }, [capture, onCapture]);
292
+ useImperativeHandle(ref, () => ({
293
+ capture: wrappedCapture,
294
+ serverId
295
+ }), [wrappedCapture, serverId]);
296
+ const statusColor = connected ? tokens.colors.status.normal : tokens.colors.status.off;
297
+ const statusLabel = connected ? "Connected" : "Disconnected";
298
+ const btnBase = {
299
+ display: "inline-flex",
300
+ alignItems: "center",
301
+ justifyContent: "center",
302
+ gap: "6px",
303
+ height: 32,
304
+ padding: "0 12px",
305
+ fontSize: tokens.typography.fontSize.sm,
306
+ fontWeight: 500,
307
+ fontFamily: tokens.typography.fontFamily.primary,
308
+ color: tokens.colors.text.secondary,
309
+ backgroundColor: "transparent",
310
+ border: `1px solid ${tokens.colors.border.muted}`,
311
+ borderRadius: tokens.borderRadius.md,
312
+ cursor: "pointer",
313
+ transition: tokens.animation.fast,
314
+ outline: "none",
315
+ whiteSpace: "nowrap"
316
+ };
317
+ return /* @__PURE__ */ jsxs(
318
+ "div",
319
+ {
320
+ style: {
321
+ display: "flex",
322
+ flexDirection: "column",
323
+ borderRadius: tokens.borderRadius.lg,
324
+ border: `1px solid ${tokens.colors.border.default}`,
325
+ backgroundColor: tokens.colors.background.surface,
326
+ overflow: "hidden",
327
+ /* When gallery is expanded, allow container to stretch to fill parent */
328
+ ...galleryExpanded ? { flex: 1, minHeight: 0 } : {},
329
+ ...style
330
+ },
331
+ children: [
332
+ /* @__PURE__ */ jsxs(
333
+ "div",
334
+ {
335
+ style: {
336
+ display: "flex",
337
+ alignItems: "center",
338
+ justifyContent: "space-between",
339
+ padding: "8px 12px",
340
+ gap: "12px",
341
+ minHeight: 48,
342
+ flexShrink: 0
343
+ },
344
+ children: [
345
+ /* @__PURE__ */ jsxs(
346
+ "button",
347
+ {
348
+ type: "button",
349
+ onClick: () => setPaused(!paused),
350
+ onMouseEnter: () => setHoveredBtn("pause"),
351
+ onMouseLeave: () => setHoveredBtn(null),
352
+ "aria-label": paused ? "Resume capture server" : "Pause capture server",
353
+ style: {
354
+ ...btnBase,
355
+ color: paused ? tokens.colors.status.caution : tokens.colors.text.secondary,
356
+ borderColor: paused ? `${tokens.colors.status.caution}40` : tokens.colors.border.muted,
357
+ backgroundColor: hoveredBtn === "pause" ? `${tokens.colors.accent.primary}12` : "transparent"
358
+ },
359
+ children: [
360
+ paused ? /* @__PURE__ */ jsx(PlayIcon, {}) : /* @__PURE__ */ jsx(PauseIcon, {}),
361
+ /* @__PURE__ */ jsx("span", { children: paused ? "Resume" : "Pause" })
362
+ ]
363
+ }
364
+ ),
365
+ /* @__PURE__ */ jsxs(
366
+ "div",
367
+ {
368
+ style: {
369
+ display: "flex",
370
+ alignItems: "center",
371
+ gap: "8px",
372
+ fontSize: tokens.typography.fontSize.sm,
373
+ color: tokens.colors.text.secondary
374
+ },
375
+ children: [
376
+ /* @__PURE__ */ jsx(
377
+ "span",
378
+ {
379
+ style: {
380
+ width: 8,
381
+ height: 8,
382
+ borderRadius: "50%",
383
+ backgroundColor: statusColor,
384
+ flexShrink: 0
385
+ }
386
+ }
387
+ ),
388
+ /* @__PURE__ */ jsx("span", { children: statusLabel }),
389
+ requestCount > 0 && /* @__PURE__ */ jsxs(
390
+ "span",
391
+ {
392
+ style: {
393
+ fontSize: tokens.typography.fontSize.xs,
394
+ color: tokens.colors.text.tertiary,
395
+ fontFamily: tokens.typography.fontFamily.mono
396
+ },
397
+ children: [
398
+ "(",
399
+ requestCount,
400
+ ")"
401
+ ]
402
+ }
403
+ )
404
+ ]
405
+ }
406
+ ),
407
+ /* @__PURE__ */ jsxs(
408
+ "button",
409
+ {
410
+ type: "button",
411
+ onClick: () => copy(serverId),
412
+ onMouseEnter: () => setHoveredBtn("copy"),
413
+ onMouseLeave: () => setHoveredBtn(null),
414
+ title: copied ? "Copied!" : `Copy Server ID: ${serverId}`,
415
+ "aria-label": copied ? "Server ID copied" : "Copy server ID",
416
+ style: {
417
+ ...btnBase,
418
+ color: copied ? tokens.colors.status.normal : tokens.colors.text.secondary,
419
+ borderColor: copied ? `${tokens.colors.status.normal}40` : tokens.colors.border.muted,
420
+ backgroundColor: hoveredBtn === "copy" ? `${tokens.colors.accent.primary}12` : "transparent"
421
+ },
422
+ children: [
423
+ copied ? /* @__PURE__ */ jsx(CheckIcon, { color: tokens.colors.status.normal }) : /* @__PURE__ */ jsx(CopyIcon, {}),
424
+ /* @__PURE__ */ jsx(
425
+ "span",
426
+ {
427
+ style: {
428
+ fontFamily: tokens.typography.fontFamily.mono,
429
+ fontSize: tokens.typography.fontSize.xs
430
+ },
431
+ children: copied ? "Copied!" : serverId.slice(0, 8)
432
+ }
433
+ )
434
+ ]
435
+ }
436
+ )
437
+ ]
438
+ }
439
+ ),
440
+ /* @__PURE__ */ jsx(
441
+ CaptureGallery,
442
+ {
443
+ images: capturedImages,
444
+ expanded: galleryExpanded,
445
+ onToggle: () => setGalleryExpanded(!galleryExpanded),
446
+ tokens,
447
+ serverId
448
+ }
449
+ )
450
+ ]
451
+ }
452
+ );
453
+ }));
454
+ function fmtBytes(b) {
455
+ if (b > 1048576) return `${(b / 1048576).toFixed(1)} MB`;
456
+ if (b > 1024) return `${(b / 1024).toFixed(1)} KB`;
457
+ return `${b} B`;
458
+ }
459
+ function downloadCapturedImage(img, serverId) {
460
+ var _a;
461
+ const ext = ((_a = img.args) == null ? void 0 : _a.format) === "jpeg" ? "jpg" : "png";
462
+ const a = document.createElement("a");
463
+ a.href = img.dataUrl;
464
+ a.download = `capture_${serverId}_${img.reqId}.${ext}`;
465
+ a.click();
466
+ }
467
+ async function downloadAllImages(images, serverId) {
468
+ for (const img of images) {
469
+ downloadCapturedImage(img, serverId);
470
+ await new Promise((r) => setTimeout(r, 200));
471
+ }
472
+ }
473
+ const DownloadIcon = ({ size = 12 }) => /* @__PURE__ */ jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
474
+ /* @__PURE__ */ jsx("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
475
+ /* @__PURE__ */ jsx("polyline", { points: "7 10 12 15 17 10" }),
476
+ /* @__PURE__ */ jsx("line", { x1: "12", y1: "15", x2: "12", y2: "3" })
477
+ ] });
478
+ const CloseIcon = ({ size = 12 }) => /* @__PURE__ */ jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", children: [
479
+ /* @__PURE__ */ jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
480
+ /* @__PURE__ */ jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
481
+ ] });
482
+ function MetaRow({ label, value, tokens, mono }) {
483
+ return /* @__PURE__ */ jsxs("div", { style: {
484
+ display: "flex",
485
+ justifyContent: "space-between",
486
+ alignItems: "center",
487
+ padding: "2px 0",
488
+ fontSize: tokens.typography.fontSize.xs
489
+ }, children: [
490
+ /* @__PURE__ */ jsx("span", { style: { color: tokens.colors.text.tertiary }, children: label }),
491
+ /* @__PURE__ */ jsx("span", { style: {
492
+ color: tokens.colors.text.primary,
493
+ fontFamily: mono ? tokens.typography.fontFamily.mono : "inherit",
494
+ fontSize: mono ? "0.68rem" : tokens.typography.fontSize.xs
495
+ }, children: value })
496
+ ] });
497
+ }
498
+ function MetaGroup({ label, tokens, children }) {
499
+ return /* @__PURE__ */ jsxs("div", { style: { marginTop: 6 }, children: [
500
+ /* @__PURE__ */ jsx("div", { style: {
501
+ fontSize: "0.6rem",
502
+ fontWeight: 600,
503
+ color: tokens.colors.text.tertiary,
504
+ textTransform: "uppercase",
505
+ letterSpacing: "0.06em",
506
+ padding: "4px 0 2px",
507
+ borderTop: `1px solid ${tokens.colors.border.muted}`
508
+ }, children: label }),
509
+ children
510
+ ] });
511
+ }
512
+ function CaptureImageDetail({ img, tokens, serverId, onClose }) {
513
+ var _a, _b;
514
+ const a = img.args;
515
+ const time = new Date(img.timestamp).toLocaleString("en-US", {
516
+ hour12: false,
517
+ year: "numeric",
518
+ month: "short",
519
+ day: "numeric",
520
+ hour: "2-digit",
521
+ minute: "2-digit",
522
+ second: "2-digit"
523
+ });
524
+ return /* @__PURE__ */ jsxs("div", { style: {
525
+ backgroundColor: tokens.colors.background.elevated,
526
+ display: "flex",
527
+ gap: "8px",
528
+ padding: "8px",
529
+ minHeight: 0
530
+ }, children: [
531
+ /* @__PURE__ */ jsxs("div", { style: {
532
+ flex: "1 1 50%",
533
+ display: "flex",
534
+ flexDirection: "column",
535
+ gap: "6px",
536
+ minWidth: 0
537
+ }, children: [
538
+ /* @__PURE__ */ jsx(
539
+ "img",
540
+ {
541
+ src: img.dataUrl,
542
+ alt: `capture_${serverId}_${img.reqId}`,
543
+ style: {
544
+ width: "100%",
545
+ borderRadius: tokens.borderRadius.sm,
546
+ border: `1px solid ${tokens.colors.border.muted}`,
547
+ objectFit: "contain"
548
+ }
549
+ }
550
+ ),
551
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "4px" }, children: [
552
+ /* @__PURE__ */ jsxs(
553
+ "button",
554
+ {
555
+ type: "button",
556
+ onClick: () => downloadCapturedImage(img, serverId),
557
+ style: {
558
+ flex: 1,
559
+ display: "flex",
560
+ alignItems: "center",
561
+ justifyContent: "center",
562
+ gap: "4px",
563
+ padding: "5px 0",
564
+ borderRadius: tokens.borderRadius.sm,
565
+ fontSize: tokens.typography.fontSize.xs,
566
+ fontWeight: 500,
567
+ fontFamily: tokens.typography.fontFamily.mono,
568
+ color: tokens.colors.accent.primary,
569
+ backgroundColor: `${tokens.colors.accent.primary}12`,
570
+ border: `1px solid ${tokens.colors.accent.primary}30`,
571
+ cursor: "pointer"
572
+ },
573
+ children: [
574
+ /* @__PURE__ */ jsx(DownloadIcon, {}),
575
+ " Download PNG"
576
+ ]
577
+ }
578
+ ),
579
+ /* @__PURE__ */ jsx(
580
+ "button",
581
+ {
582
+ type: "button",
583
+ onClick: onClose,
584
+ style: {
585
+ padding: "5px 8px",
586
+ borderRadius: tokens.borderRadius.sm,
587
+ fontSize: tokens.typography.fontSize.xs,
588
+ color: tokens.colors.text.tertiary,
589
+ backgroundColor: "transparent",
590
+ border: `1px solid ${tokens.colors.border.muted}`,
591
+ cursor: "pointer"
592
+ },
593
+ children: /* @__PURE__ */ jsx(CloseIcon, {})
594
+ }
595
+ )
596
+ ] })
597
+ ] }),
598
+ /* @__PURE__ */ jsxs("div", { style: {
599
+ flex: "1 1 50%",
600
+ overflowY: "auto",
601
+ minWidth: 0
602
+ }, children: [
603
+ /* @__PURE__ */ jsxs(MetaGroup, { label: "File Info", tokens, children: [
604
+ /* @__PURE__ */ jsx(MetaRow, { label: "Filename", value: `capture_${serverId}_${img.reqId}.${((_a = img.args) == null ? void 0 : _a.format) === "jpeg" ? "jpg" : "png"}`, tokens, mono: true }),
605
+ /* @__PURE__ */ jsx(MetaRow, { label: "Req ID", value: `#${img.reqId}`, tokens, mono: true }),
606
+ /* @__PURE__ */ jsx(MetaRow, { label: "Size", value: fmtBytes(img.bytes), tokens }),
607
+ /* @__PURE__ */ jsx(MetaRow, { label: "Format", value: ((_b = img.args) == null ? void 0 : _b.format) === "jpeg" ? `JPEG (q=${Math.round((img.args.jpeg_quality ?? 0.92) * 100)}%)` : "PNG", tokens }),
608
+ /* @__PURE__ */ jsx(MetaRow, { label: "Captured", value: time, tokens })
609
+ ] }),
610
+ a && /* @__PURE__ */ jsxs(Fragment, { children: [
611
+ /* @__PURE__ */ jsxs(MetaGroup, { label: "Camera", tokens, children: [
612
+ /* @__PURE__ */ jsx(MetaRow, { label: "Resolution", value: `${a.resolution}px`, tokens }),
613
+ /* @__PURE__ */ jsx(MetaRow, { label: "FOV", value: `${a.fov}°`, tokens }),
614
+ /* @__PURE__ */ jsx(MetaRow, { label: "Focal Length", value: `${a.focal_length}mm`, tokens }),
615
+ /* @__PURE__ */ jsx(MetaRow, { label: "Aperture", value: `f/${a.aperture}`, tokens }),
616
+ /* @__PURE__ */ jsx(MetaRow, { label: "CoC", value: `${a.coc}mm`, tokens }),
617
+ /* @__PURE__ */ jsx(MetaRow, { label: "Pixel Pitch", value: `${a.pixel_pitch}mm`, tokens }),
618
+ /* @__PURE__ */ jsx(MetaRow, { label: "Focus Dist", value: `${a.focusing_distance}m`, tokens }),
619
+ /* @__PURE__ */ jsx(MetaRow, { label: "Monochrome", value: a.monochromatic ? "Yes" : "No", tokens }),
620
+ /* @__PURE__ */ jsx(MetaRow, { label: "Sample", value: a.sample ? "Yes (½ res)" : "No", tokens })
621
+ ] }),
622
+ /* @__PURE__ */ jsxs(MetaGroup, { label: "Position & Orientation", tokens, children: [
623
+ /* @__PURE__ */ jsx(MetaRow, { label: "X", value: `${a.position[0].toFixed(2)} km`, tokens, mono: true }),
624
+ /* @__PURE__ */ jsx(MetaRow, { label: "Y", value: `${a.position[1].toFixed(2)} km`, tokens, mono: true }),
625
+ /* @__PURE__ */ jsx(MetaRow, { label: "Z", value: `${a.position[2].toFixed(2)} km`, tokens, mono: true }),
626
+ /* @__PURE__ */ jsx(MetaRow, { label: "Heading", value: `${a.rotation[0].toFixed(1)}°`, tokens, mono: true }),
627
+ /* @__PURE__ */ jsx(MetaRow, { label: "Pitch", value: `${a.rotation[1].toFixed(1)}°`, tokens, mono: true }),
628
+ /* @__PURE__ */ jsx(MetaRow, { label: "Roll", value: `${a.rotation[2].toFixed(1)}°`, tokens, mono: true })
629
+ ] })
630
+ ] })
631
+ ] })
632
+ ] });
633
+ }
634
+ function CaptureGallery({ images, expanded, onToggle, tokens, serverId }) {
635
+ const [selectedIdx, setSelectedIdx] = useState(null);
636
+ const selectedImg = selectedIdx !== null ? images[selectedIdx] : null;
637
+ return /* @__PURE__ */ jsxs("div", { style: {
638
+ borderTop: `1px solid ${tokens.colors.border.muted}`,
639
+ /* Fill remaining space when expanded */
640
+ ...expanded ? { flex: 1, display: "flex", flexDirection: "column", minHeight: 0, overflow: "hidden" } : {}
641
+ }, children: [
642
+ /* @__PURE__ */ jsxs("div", { style: {
643
+ display: "flex",
644
+ alignItems: "center",
645
+ gap: "4px",
646
+ flexShrink: 0
647
+ }, children: [
648
+ /* @__PURE__ */ jsxs(
649
+ "button",
650
+ {
651
+ type: "button",
652
+ onClick: onToggle,
653
+ "aria-expanded": expanded,
654
+ "aria-label": `Captured images, ${images.length} total`,
655
+ style: {
656
+ flex: 1,
657
+ display: "flex",
658
+ alignItems: "center",
659
+ gap: "8px",
660
+ padding: "8px 12px",
661
+ fontSize: tokens.typography.fontSize.sm,
662
+ fontWeight: 500,
663
+ fontFamily: tokens.typography.fontFamily.primary,
664
+ color: tokens.colors.text.secondary,
665
+ backgroundColor: "transparent",
666
+ border: "none",
667
+ cursor: "pointer",
668
+ outline: "none",
669
+ textAlign: "left"
670
+ },
671
+ children: [
672
+ /* @__PURE__ */ jsx(ChevronIcon, { expanded }),
673
+ /* @__PURE__ */ jsx("span", { children: "Captured Images" }),
674
+ images.length > 0 && /* @__PURE__ */ jsxs("span", { style: {
675
+ fontSize: tokens.typography.fontSize.xs,
676
+ color: tokens.colors.text.tertiary,
677
+ fontFamily: tokens.typography.fontFamily.mono
678
+ }, children: [
679
+ "(",
680
+ images.length,
681
+ ")"
682
+ ] })
683
+ ]
684
+ }
685
+ ),
686
+ expanded && images.length > 0 && /* @__PURE__ */ jsxs(
687
+ "button",
688
+ {
689
+ type: "button",
690
+ onClick: () => downloadAllImages(images, serverId),
691
+ title: `Download all ${images.length} images`,
692
+ style: {
693
+ display: "flex",
694
+ alignItems: "center",
695
+ gap: "4px",
696
+ padding: "4px 8px",
697
+ marginRight: 8,
698
+ fontSize: "0.62rem",
699
+ fontWeight: 500,
700
+ fontFamily: tokens.typography.fontFamily.mono,
701
+ color: tokens.colors.text.tertiary,
702
+ backgroundColor: "transparent",
703
+ border: `1px solid ${tokens.colors.border.muted}`,
704
+ borderRadius: tokens.borderRadius.sm,
705
+ cursor: "pointer",
706
+ whiteSpace: "nowrap"
707
+ },
708
+ children: [
709
+ /* @__PURE__ */ jsx(DownloadIcon, { size: 10 }),
710
+ " All"
711
+ ]
712
+ }
713
+ )
714
+ ] }),
715
+ expanded && /* @__PURE__ */ jsx("div", { style: {
716
+ flex: 1,
717
+ minHeight: 0,
718
+ display: "flex",
719
+ overflow: "hidden"
720
+ }, children: images.length === 0 ? /* @__PURE__ */ jsx("div", { style: {
721
+ fontSize: tokens.typography.fontSize.sm,
722
+ color: tokens.colors.text.tertiary,
723
+ padding: "12px",
724
+ textAlign: "center",
725
+ width: "100%"
726
+ }, children: "No images captured yet" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
727
+ /* @__PURE__ */ jsx("div", { style: {
728
+ width: selectedImg ? 200 : "100%",
729
+ flexShrink: 0,
730
+ overflowY: "auto",
731
+ padding: "6px",
732
+ borderRight: selectedImg ? `1px solid ${tokens.colors.border.muted}` : "none",
733
+ display: "grid",
734
+ gridTemplateColumns: selectedImg ? "repeat(2, 1fr)" : "repeat(auto-fill, minmax(min(120px, 100%), 1fr))",
735
+ gridAutoRows: "min-content",
736
+ gap: "4px",
737
+ alignContent: "start"
738
+ }, children: images.map((img, idx) => {
739
+ const isSelected = selectedIdx === idx;
740
+ return /* @__PURE__ */ jsxs(
741
+ "div",
742
+ {
743
+ onClick: () => setSelectedIdx(isSelected ? null : idx),
744
+ style: {
745
+ position: "relative",
746
+ aspectRatio: "1",
747
+ borderRadius: tokens.borderRadius.sm,
748
+ overflow: "hidden",
749
+ border: `2px solid ${isSelected ? tokens.colors.accent.primary : tokens.colors.border.muted}`,
750
+ backgroundColor: tokens.colors.background.elevated,
751
+ cursor: "pointer",
752
+ transition: "border-color 0.15s"
753
+ },
754
+ children: [
755
+ /* @__PURE__ */ jsx(
756
+ "img",
757
+ {
758
+ src: img.dataUrl,
759
+ alt: `capture_${serverId}_${img.reqId}`,
760
+ style: { width: "100%", height: "100%", objectFit: "cover", display: "block" }
761
+ }
762
+ ),
763
+ /* @__PURE__ */ jsxs("div", { style: {
764
+ position: "absolute",
765
+ bottom: 0,
766
+ left: 0,
767
+ right: 0,
768
+ display: "flex",
769
+ justifyContent: "space-between",
770
+ padding: "2px 4px",
771
+ fontSize: "9px",
772
+ fontFamily: tokens.typography.fontFamily.mono,
773
+ color: "#fff",
774
+ backgroundColor: "rgba(0, 0, 0, 0.65)"
775
+ }, children: [
776
+ /* @__PURE__ */ jsxs("span", { children: [
777
+ "#",
778
+ img.reqId
779
+ ] }),
780
+ /* @__PURE__ */ jsx("span", { children: fmtBytes(img.bytes) })
781
+ ] })
782
+ ]
783
+ },
784
+ `${img.reqId}-${img.timestamp}`
785
+ );
786
+ }) }),
787
+ selectedImg && /* @__PURE__ */ jsx("div", { style: { flex: 1, minWidth: 0, overflowY: "auto" }, children: /* @__PURE__ */ jsx(
788
+ CaptureImageDetail,
789
+ {
790
+ img: selectedImg,
791
+ tokens,
792
+ serverId,
793
+ onClose: () => setSelectedIdx(null)
794
+ }
795
+ ) })
796
+ ] }) })
797
+ ] });
798
+ }
799
+ export {
800
+ Capture,
801
+ loadCapturePlaceholderImage,
802
+ useCapture
803
+ };
804
+ //# sourceMappingURL=Capture.js.map