@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 @@
1
+ {"version":3,"file":"useSimulationScene.js","sources":["../../../src/react/hooks/useSimulationScene.ts"],"sourcesContent":["/**\r\n * @zendir/ui - useSimulationScene Hook\r\n *\r\n * Bridge between a `zendir-ts` `ZendirClient` and the `ZenSpace3D`\r\n * Cesium component. On mount the hook calls `getSimulationStructure`\r\n * to bucket simulation objects (spacecraft / ground stations) and then\r\n * polls each spacecraft's `Position_BN_N` on a fixed interval, emitting\r\n * ZenSpace3D-shaped state.\r\n *\r\n * Ground stations are resolved once per structure load (lat/lon is\r\n * static — no ECI polling).\r\n *\r\n * Structure JSON may use `ID`/`id` and `Type`/`type` interchangeably;\r\n * the walker accepts common variants. Call `refresh()` to re-fetch the\r\n * structure (e.g. on a consumer-owned timer) so newly spawned objects\r\n * appear without remounting.\r\n *\r\n * `referenceDate` is the wall-clock instant of the last successful tick\r\n * — pass it to `<ZenSpace3D referenceTime={...} />` so ECI/J2000\r\n * positions rotate to ECEF at the engine's \"now\".\r\n *\r\n * OPTIONAL DEPENDENCY: requires a `zendir-ts` client instance. The\r\n * client is passed in to avoid coupling this hook to a specific\r\n * session-management strategy (the consumer app owns sessions).\r\n */\r\n\r\nimport { useCallback, useEffect, useRef, useState } from 'react';\r\nimport type { GroundStation } from '../types';\r\nimport type { Satellite, Vector3D } from '../3d/ZenSpace3DTypes';\r\n\r\n/**\r\n * Non-zero ECI placeholder (km) used until the first `Position_BN_N`\r\n * poll lands. The 3D viewer expects ECI / J2000 km positions; the\r\n * origin (0,0,0) sits at Earth's centre and would crash Cesium's\r\n * `Cartesian3.normalize` with \"normalized result is not a number\".\r\n *\r\n * Value: ~400 km altitude on the +X axis (vernal-equinox direction).\r\n * Renders just above the equator and is invisible after one tick.\r\n */\r\nconst PLACEHOLDER_SCENECRAFT_POSITION: Vector3D = { x: 6771, y: 0, z: 0 };\r\n\r\n// ============================================================================\r\n// Types\r\n// ============================================================================\r\n\r\n/**\r\n * Minimal client interface this hook depends on. Compatible with the\r\n * `zendir-ts` `ZendirClient` surface but typed structurally so the hook\r\n * can be used with any compatible client (e.g. a test fake).\r\n */\r\nexport interface SimulationSceneClient {\r\n getSimulationStructure(\r\n simulationId: string,\r\n containerId?: string,\r\n opts?: { signal?: AbortSignal; retry?: unknown },\r\n ): Promise<Record<string, unknown>>;\r\n getProperties(\r\n objectId: string,\r\n properties: string | string[],\r\n containerId?: string,\r\n opts?: { signal?: AbortSignal; retry?: unknown },\r\n ): Promise<unknown>;\r\n getAllProperties(\r\n objectId: string,\r\n containerId?: string,\r\n opts?: { signal?: AbortSignal; retry?: unknown },\r\n ): Promise<Record<string, unknown>>;\r\n}\r\n\r\n/**\r\n * Per-tick retry config for the position-polling loop.\r\n *\r\n * The SDK defaults `getProperties` to `engineWarmup` (24 retries /\r\n * ~3.6 min). That budget is right for a one-shot mount call, but\r\n * disastrous for a 2-second polling tick — a single warmup blip would\r\n * stall every subsequent tick (skip-if-busy guard) for minutes, freezing\r\n * the viewer. We override with a tight 2-attempt budget that rides\r\n * through quick blips (~1.5 s total) but cedes to the next tick fast.\r\n */\r\nconst TICK_RETRY: { maxRetries: number; initialDelayMs: number; maxDelayMs: number } = {\r\n maxRetries: 2,\r\n initialDelayMs: 500,\r\n maxDelayMs: 1500,\r\n};\r\n\r\nexport interface UseSimulationSceneOptions {\r\n /** ZendirClient (or any compatible client). Pass `null` to defer. */\r\n client: SimulationSceneClient | null;\r\n /** Container ID hosting the simulation. */\r\n containerId: string;\r\n /** Simulation ID to introspect. */\r\n simulationId: string;\r\n /** Polling interval in ms for spacecraft positions. Default: 2000. */\r\n pollIntervalMs?: number;\r\n /** Pause polling without unmounting. Default: true. */\r\n enabled?: boolean;\r\n}\r\n\r\nexport interface SimulationSceneObject {\r\n id: string;\r\n name: string;\r\n kind: 'spacecraft' | 'groundStation' | 'celestialBody';\r\n}\r\n\r\n/**\r\n * Lightweight diagnostic snapshot of the raw `getSimulationStructure`\r\n * response — useful for debugging parsing mismatches in the UI without\r\n * shipping the full raw blob.\r\n */\r\nexport interface StructureDiag {\r\n /** Every top-level key the engine returned (e.g. \"Objects\",\"objects\",\"Time\"). */\r\n topLevelKeys: string[];\r\n /** Length of the `Objects` / `objects` array in the raw response (-1 if absent). */\r\n rawObjectCount: number;\r\n /** Length of the `Systems` / `systems` array in the raw response (-1 if absent). */\r\n rawSystemCount: number;\r\n}\r\n\r\n/**\r\n * Per-tick diagnostic — answers \"did the position fetch succeed and what came back?\".\r\n * Surfaced in the debug UI so a failed-but-silent tick (parser miss, engine\r\n * returning a different property name, all-zero positions, etc.) is visible.\r\n */\r\nexport interface PositionTickDiag {\r\n /** Wall-clock instant of the most recent tick attempt. */\r\n attemptedAt: Date;\r\n /** Number of spacecraft IDs the tick requested positions for. */\r\n requested: number;\r\n /** Number of position responses that parsed successfully. */\r\n parsedOk: number;\r\n /** Number of position responses that arrived but couldn't be parsed. */\r\n parseFailed: number;\r\n /** Number of position responses rejected by the SDK (network / 4xx / 5xx). */\r\n requestFailed: number;\r\n /**\r\n * The exact property names this tick asked the engine for. `null` means\r\n * the schema probe failed and the tick fell back to `getAllProperties`.\r\n * Surfaced so the debug UI can show which schema variant is in use.\r\n */\r\n requestedProps: string[] | null;\r\n /** All keys present on the first satellite's response (helps spot wrong property names). */\r\n firstResponseKeys: string[];\r\n /** Compact JSON of the first satellite's raw response (capped to 200 chars). */\r\n firstResponseSample: string;\r\n}\r\n\r\nexport interface UseSimulationSceneResult {\r\n /** Spacecraft with live ECI position/velocity (km, km/s). */\r\n satellites: Satellite[];\r\n /** Static ground stations (lat/lon). */\r\n groundStations: GroundStation[];\r\n /**\r\n * Celestial bodies (planets, moons, sun) with live ECI positions in km.\r\n * The simulation's central body is filtered out — Cesium renders it\r\n * natively as the focal globe. Pass directly to\r\n * `<ZenSpace3D customObjects={scene.celestialBodies} />`.\r\n */\r\n celestialBodies: CelestialBody[];\r\n /** Objects discovered from `getSimulationStructure` (for diagnostics/UI lists). */\r\n objects: SimulationSceneObject[];\r\n /** True until the first structure fetch resolves. */\r\n isLoading: boolean;\r\n /**\r\n * Set when the structure-fetch fails (polling stops). Per-tick\r\n * polling errors do NOT surface here — they retry silently to keep\r\n * the UI from flapping during transient network blips.\r\n */\r\n error: string | null;\r\n /** Re-run structure fetch and a position tick. */\r\n refresh: () => Promise<void>;\r\n /**\r\n * UTC `Date` representing wall-clock at the most recent successful\r\n * tick. Pass straight into `<ZenSpace3D referenceTime={...} />` so\r\n * ECI/J2000 positions rotate to ECEF at \"now\".\r\n */\r\n referenceDate: Date;\r\n /**\r\n * Diagnostic snapshot of the raw structure response — null until the\r\n * first fetch resolves. Use in debug panels to verify key casing and\r\n * array lengths returned by the engine.\r\n */\r\n structureDiag: StructureDiag | null;\r\n /**\r\n * Diagnostic for the most recent position-polling tick — null until\r\n * the first tick has run. Use in debug panels to verify that\r\n * `getProperties` is actually returning parsable position vectors.\r\n */\r\n positionTickDiag: PositionTickDiag | null;\r\n}\r\n\r\nconst DEFAULT_POLL_MS = 2000;\r\nconst M_TO_KM = 1 / 1000;\r\n\r\n// ============================================================================\r\n// Hook\r\n// ============================================================================\r\n\r\nexport function useSimulationScene(\r\n options: UseSimulationSceneOptions,\r\n): UseSimulationSceneResult {\r\n const {\r\n client,\r\n containerId,\r\n simulationId,\r\n pollIntervalMs = DEFAULT_POLL_MS,\r\n enabled = true,\r\n } = options;\r\n\r\n const [satellites, setSatellites] = useState<Satellite[]>([]);\r\n const [groundStations, setGroundStations] = useState<GroundStation[]>([]);\r\n const [celestialBodies, setCelestialBodies] = useState<CelestialBody[]>([]);\r\n const [objects, setObjects] = useState<SimulationSceneObject[]>([]);\r\n const [isLoading, setIsLoading] = useState<boolean>(true);\r\n const [error, setError] = useState<string | null>(null);\r\n const [referenceDate, setReferenceDate] = useState<Date>(() => new Date());\r\n const [structureDiag, setStructureDiag] = useState<StructureDiag | null>(null);\r\n const [positionTickDiag, setPositionTickDiag] = useState<PositionTickDiag | null>(null);\r\n\r\n // Initial structure load on (client, cid, sid) change. Polling tick reads\r\n // positions for bucketed spacecraft; IDs live in a ref so the tick\r\n // callback stays stable across renders.\r\n const spacecraftIdsRef = useRef<string[]>([]);\r\n // Celestial-body IDs + names collected at structure-load time. The tick\r\n // polls each one's `Position` (note: NOT `Position_BN_N` — that's a\r\n // Spacecraft-only naming convention) plus `IsCentralBody` so we can\r\n // filter the focal body (Earth in Earth-centric sims) out before\r\n // emitting markers — Cesium renders the focal globe natively.\r\n const celestialBodyIdsRef = useRef<string[]>([]);\r\n const celestialBodyNamesRef = useRef<(string | undefined)[]>([]);\r\n const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);\r\n const abortRef = useRef<AbortController | null>(null);\r\n // Skip-if-busy flag: when a tick takes longer than the poll interval\r\n // (slow network), the next interval fires anyway. Without this guard\r\n // we'd stack overlapping in-flight requests indefinitely.\r\n const tickInFlightRef = useRef<boolean>(false);\r\n // Property-name schema detected from a one-time getAllProperties probe at\r\n // structure-load time. Different engine versions name spacecraft state\r\n // under different keys (`Position_BN_N` vs `Position`). The Zendir\r\n // `getProperties` endpoint returns 400 if ANY requested name is unknown,\r\n // so we cannot ask for \"either\" in a single call — we must know which\r\n // name this engine uses before the polling loop starts. `null` until\r\n // probed; `[]` if probe ran but no recognised key was found.\r\n const positionPropsRef = useRef<string[] | null>(null);\r\n\r\n // -------- Position polling tick (defined first so loadStructure can call it) --------\r\n\r\n const tickPositions = useCallback(\r\n async (signal: AbortSignal) => {\r\n if (!client) return;\r\n const ids = spacecraftIdsRef.current;\r\n if (ids.length === 0) return;\r\n // Skip-if-busy: never let two ticks overlap. The previous tick\r\n // will update state when it lands; the user sees fewer frames\r\n // but the engine isn't hammered with stacked requests.\r\n if (tickInFlightRef.current) return;\r\n tickInFlightRef.current = true;\r\n\r\n try {\r\n // The polling tick uses the property-name schema detected at\r\n // structure-load time. The Zendir `getProperties` endpoint 400s if\r\n // ANY requested name is unknown to the object, so we MUST know the\r\n // exact names before the loop starts — see `positionPropsRef`.\r\n //\r\n // Falls back to `getAllProperties` if the probe never resolved a\r\n // recognised position key (e.g. an exotic engine variant). That's\r\n // heavier per tick but bulletproof against unknown schemas.\r\n const propsToFetch = positionPropsRef.current;\r\n const useFallbackAll = !propsToFetch || propsToFetch.length === 0;\r\n\r\n // Fetch all spacecraft in parallel. A per-spacecraft failure is\r\n // settled (not thrown) so one bad object can't blank the frame —\r\n // we keep the previous position and retry on the next tick.\r\n const updates = await Promise.allSettled(\r\n ids.map((id) =>\r\n useFallbackAll\r\n ? // `retry: TICK_RETRY` overrides the SDK's engineWarmup default\r\n // for this one call. See the TICK_RETRY definition above for\r\n // the rationale (avoid a stuck tick stalling the viewer).\r\n client.getAllProperties(id, containerId, { signal, retry: TICK_RETRY })\r\n : client.getProperties(id, propsToFetch!, containerId, {\r\n signal,\r\n retry: TICK_RETRY,\r\n }),\r\n ),\r\n );\r\n if (signal.aborted) return;\r\n\r\n // ---- Diagnostic snapshot ------------------------------------------\r\n // Captured BEFORE we update satellites so a parser bug doesn't hide\r\n // the raw response shape. Surfaced via `positionTickDiag` for the\r\n // debug UI; helps spot wrong property names / silent engine returns.\r\n let parsedOk = 0;\r\n let parseFailed = 0;\r\n let requestFailed = 0;\r\n let firstResponseKeys: string[] = [];\r\n let firstResponseSample = '';\r\n for (let i = 0; i < ids.length; i++) {\r\n const result = updates[i];\r\n if (result.status !== 'fulfilled') {\r\n requestFailed += 1;\r\n continue;\r\n }\r\n if (i === 0) {\r\n const v = result.value as unknown;\r\n if (v && typeof v === 'object') firstResponseKeys = Object.keys(v as Record<string, unknown>);\r\n try {\r\n const json = JSON.stringify(v);\r\n firstResponseSample = json.length > 200 ? `${json.slice(0, 200)}…` : json;\r\n } catch {\r\n firstResponseSample = String(v);\r\n }\r\n }\r\n if (parsePositionVelocity(result.value)) parsedOk += 1;\r\n else parseFailed += 1;\r\n }\r\n setPositionTickDiag({\r\n attemptedAt: new Date(),\r\n requested: ids.length,\r\n parsedOk,\r\n parseFailed,\r\n requestFailed,\r\n requestedProps: useFallbackAll ? null : propsToFetch,\r\n firstResponseKeys,\r\n firstResponseSample,\r\n });\r\n\r\n setSatellites((prev) => {\r\n if (prev.length === 0) return prev;\r\n const next = prev.slice();\r\n for (let i = 0; i < ids.length; i++) {\r\n const result = updates[i];\r\n if (result.status !== 'fulfilled') continue;\r\n const idx = next.findIndex((sc) => sc.id === ids[i]);\r\n if (idx < 0) continue;\r\n const parsed = parsePositionVelocity(result.value);\r\n if (!parsed) continue;\r\n next[idx] = { ...next[idx], position: parsed.position, velocity: parsed.velocity };\r\n }\r\n return next;\r\n });\r\n\r\n // ---- Celestial-body positions (independent of spacecraft tick) ----\r\n // Per Zendir API: CelestialBody exposes `Position` (m, ECI) and\r\n // `IsCentralBody` (bool). Filter the central body — it's the focal\r\n // globe Cesium already renders. All others become custom-object\r\n // markers in the viewer.\r\n const celIds = celestialBodyIdsRef.current;\r\n if (celIds.length > 0) {\r\n const celResults = await Promise.allSettled(\r\n celIds.map((id) =>\r\n client.getProperties(id, ['Position', 'IsCentralBody'], containerId, {\r\n signal,\r\n retry: TICK_RETRY,\r\n }),\r\n ),\r\n );\r\n if (signal.aborted) return;\r\n\r\n const next: CelestialBody[] = [];\r\n for (let i = 0; i < celIds.length; i++) {\r\n const result = celResults[i];\r\n if (result.status !== 'fulfilled') continue;\r\n const raw = result.value as Record<string, unknown> | unknown;\r\n const obj = raw && typeof raw === 'object' ? (raw as Record<string, unknown>) : null;\r\n if (!obj) continue;\r\n if (obj.IsCentralBody === true) continue; // focal globe — skip\r\n const posVec = parseVec3(obj.Position);\r\n if (!posVec) continue;\r\n // Engine returns metres; convert to km to match Satellite shape.\r\n const positionKm = scaleVec(posVec, M_TO_KM);\r\n const id = celIds[i];\r\n const name = celestialBodyNamesRef.current[i] ?? `CelestialBody ${shortId(id)}`;\r\n next.push({ id, name, position: positionKm });\r\n }\r\n setCelestialBodies(next);\r\n }\r\n } catch {\r\n // Whole-tick failure (e.g. AbortError, transport blowup). The\r\n // outer `enabled` effect already swallows so the UI stays put.\r\n } finally {\r\n tickInFlightRef.current = false;\r\n if (!signal.aborted) {\r\n // Wall-clock reference instant — drives ECI→ECEF rotation in\r\n // the viewer. Bumped per tick so Cesium re-derives the\r\n // pseudo-fixed transform at \"now\".\r\n setReferenceDate(new Date());\r\n }\r\n }\r\n },\r\n [client, containerId],\r\n );\r\n\r\n // -------- Initial structure load + ground station resolution --------\r\n\r\n const loadStructure = useCallback(\r\n async (signal: AbortSignal) => {\r\n if (!client) return;\r\n setIsLoading(true);\r\n setError(null);\r\n\r\n try {\r\n const structure = await client.getSimulationStructure(simulationId, containerId, { signal });\r\n if (signal.aborted) return;\r\n\r\n // Capture a lightweight diagnostic before any parsing so the UI can\r\n // show exactly what the engine returned (key names, array lengths).\r\n // This is the most common source of \"0 objects\" bugs — key-casing mismatch.\r\n setStructureDiag({\r\n topLevelKeys: Object.keys(structure),\r\n rawObjectCount: Array.isArray(structure.Objects)\r\n ? structure.Objects.length\r\n : Array.isArray((structure as Record<string, unknown>).objects)\r\n ? ((structure as Record<string, unknown>).objects as unknown[]).length\r\n : -1,\r\n rawSystemCount: Array.isArray(structure.Systems)\r\n ? structure.Systems.length\r\n : Array.isArray((structure as Record<string, unknown>).systems)\r\n ? ((structure as Record<string, unknown>).systems as unknown[]).length\r\n : -1,\r\n });\r\n\r\n const buckets = bucketStructure(structure);\r\n spacecraftIdsRef.current = buckets.spacecraft.map((s) => s.id);\r\n celestialBodyIdsRef.current = buckets.celestialBodyIds;\r\n celestialBodyNamesRef.current = buckets.celestialBodyNames;\r\n setSatellites(buckets.spacecraft);\r\n setObjects(buckets.objects);\r\n\r\n // ---- One-time property-name schema probe -------------------------\r\n // The Zendir `/get` endpoint 400s if any requested property name is\r\n // unknown to the object, so we cannot blindly request both legacy\r\n // (`Position_BN_N`) and short-form (`Position`) names in one tick.\r\n // Probe the first spacecraft once via `getAllProperties` (which is\r\n // schema-agnostic) and detect which subset of recognised names it\r\n // exposes. Cached in a ref for every subsequent tick.\r\n if (buckets.spacecraft.length > 0) {\r\n try {\r\n const probeId = buckets.spacecraft[0].id;\r\n const all = await client.getAllProperties(probeId, containerId, { signal });\r\n if (signal.aborted) return;\r\n positionPropsRef.current = detectPositionPropertyNames(all);\r\n } catch {\r\n // Probe failure shouldn't block the rest of the load — leave\r\n // positionPropsRef as null and the tick will fall back to\r\n // getAllProperties per spacecraft.\r\n positionPropsRef.current = null;\r\n }\r\n } else {\r\n positionPropsRef.current = null;\r\n }\r\n\r\n // Ground stations are static — resolve their lat/lon once per structure\r\n // fetch. Parallel fetch with per-station settle so one bad station\r\n // can't block the whole render.\r\n const stationResults = await Promise.allSettled(\r\n buckets.groundStationIds.map((id) =>\r\n client.getAllProperties(id, containerId, { signal }),\r\n ),\r\n );\r\n if (signal.aborted) return;\r\n const stations: GroundStation[] = [];\r\n for (let i = 0; i < buckets.groundStationIds.length; i++) {\r\n const result = stationResults[i];\r\n const id = buckets.groundStationIds[i];\r\n const fallbackName = buckets.groundStationNames[i] ?? shortId(id);\r\n if (result.status !== 'fulfilled') {\r\n stations.push({ id, name: fallbackName, latitude: 0, longitude: 0 });\r\n continue;\r\n }\r\n stations.push(toGroundStation(id, fallbackName, result.value));\r\n }\r\n setGroundStations(stations);\r\n\r\n setIsLoading(false);\r\n\r\n // Kick off the first position tick immediately so consumers see\r\n // live positions without waiting a full poll interval.\r\n if (spacecraftIdsRef.current.length > 0) {\r\n await tickPositions(signal);\r\n }\r\n } catch (err) {\r\n if (signal.aborted) return;\r\n setError(err instanceof Error ? err.message : 'Failed to load simulation structure');\r\n setIsLoading(false);\r\n }\r\n },\r\n [client, containerId, simulationId, tickPositions],\r\n );\r\n\r\n // -------- Lifecycle: load on mount/id-change, poll while enabled --------\r\n\r\n useEffect(() => {\r\n if (!client || !containerId || !simulationId) {\r\n setIsLoading(false);\r\n return;\r\n }\r\n\r\n const controller = new AbortController();\r\n abortRef.current = controller;\r\n void loadStructure(controller.signal);\r\n\r\n return () => {\r\n controller.abort();\r\n if (abortRef.current === controller) abortRef.current = null;\r\n };\r\n }, [client, containerId, simulationId, loadStructure]);\r\n\r\n useEffect(() => {\r\n if (!enabled || !client || error) {\r\n // No polling when disabled or after a fatal structure-fetch error.\r\n // The structure-load effect above will still re-fire on id change.\r\n if (intervalRef.current) {\r\n clearInterval(intervalRef.current);\r\n intervalRef.current = null;\r\n }\r\n return;\r\n }\r\n\r\n const interval = setInterval(() => {\r\n const controller = new AbortController();\r\n // Tick errors are intentionally swallowed — see tickPositions for\r\n // the per-spacecraft settle policy. A network blip should not\r\n // crash the viewer; it should just leave the previous frame\r\n // visible until the next tick succeeds.\r\n void tickPositions(controller.signal).catch(() => undefined);\r\n }, pollIntervalMs);\r\n intervalRef.current = interval;\r\n\r\n return () => {\r\n clearInterval(interval);\r\n if (intervalRef.current === interval) intervalRef.current = null;\r\n };\r\n }, [client, enabled, error, pollIntervalMs, tickPositions]);\r\n\r\n const refresh = useCallback(async () => {\r\n const controller = new AbortController();\r\n await loadStructure(controller.signal);\r\n }, [loadStructure]);\r\n\r\n return {\r\n satellites,\r\n groundStations,\r\n celestialBodies,\r\n objects,\r\n isLoading,\r\n error,\r\n refresh,\r\n referenceDate,\r\n structureDiag,\r\n positionTickDiag,\r\n };\r\n}\r\n\r\nexport default useSimulationScene;\r\n\r\n// ============================================================================\r\n// Structure walker\r\n// ============================================================================\r\n\r\ninterface BucketedStructure {\r\n spacecraft: Satellite[];\r\n groundStationIds: string[];\r\n groundStationNames: (string | undefined)[];\r\n /**\r\n * Celestial-body IDs collected from `Type === 'CelestialBody'` nodes. Used by\r\n * the per-tick position poll. Names are looked up from `objects` in the\r\n * upstream consumer; we only need IDs here.\r\n */\r\n celestialBodyIds: string[];\r\n celestialBodyNames: (string | undefined)[];\r\n objects: SimulationSceneObject[];\r\n}\r\n\r\n/**\r\n * Celestial body with the position polled from the engine's `Position`\r\n * property (in km, ECI), suitable to feed straight into\r\n * `<ZenSpace3D customObjects={...} />`. The simulation's central body\r\n * (e.g. Earth in Earth-centric sims) is filtered out — Cesium renders\r\n * the focal globe natively, so emitting it as a marker would collide.\r\n */\r\nexport interface CelestialBody {\r\n id: string;\r\n name: string;\r\n /** ECI position in km. */\r\n position: { x: number; y: number; z: number };\r\n}\r\n\r\n/**\r\n * First non-empty string among PascalCase / camelCase keys for one logical field.\r\n */\r\nfunction firstStringField(obj: Record<string, unknown>, keys: readonly string[]): string | undefined {\r\n for (const key of keys) {\r\n const v = obj[key];\r\n if (typeof v === 'string' && v.length > 0) return v;\r\n }\r\n return undefined;\r\n}\r\n\r\n/**\r\n * Walk a `GetSimulationStructure` response and bucket objects by Type.\r\n *\r\n * Tolerant of:\r\n * - PascalCase (`Objects`, `Systems`, `Children`) and camelCase\r\n * (`objects`, `systems`, `children`) — the engine has shipped both.\r\n * - Objects nested at the top level, inside Systems children, or under\r\n * any depth of `Children` / `children` arrays.\r\n */\r\nfunction bucketStructure(structure: Record<string, unknown>): BucketedStructure {\r\n const out: BucketedStructure = {\r\n spacecraft: [],\r\n groundStationIds: [],\r\n groundStationNames: [],\r\n celestialBodyIds: [],\r\n celestialBodyNames: [],\r\n objects: [],\r\n };\r\n\r\n // Accept both PascalCase and camelCase for the top-level array keys.\r\n const topObjects = pickArray(structure, ['Objects', 'objects']);\r\n for (const obj of topObjects) walkObject(obj, out);\r\n\r\n // Some engine builds put spacecraft / ground stations inside a System\r\n // (e.g. ExtensionSystem). Walk every system entry so their children\r\n // are discovered regardless of nesting depth.\r\n const topSystems = pickArray(structure, ['Systems', 'systems']);\r\n for (const sys of topSystems) walkObject(sys, out);\r\n\r\n return out;\r\n}\r\n\r\n/**\r\n * Return the first non-empty array found under any of `keys` on `obj`.\r\n * Allows the caller to tolerate PascalCase / camelCase top-level array keys\r\n * without duplicating logic at every call site.\r\n */\r\nfunction pickArray(obj: Record<string, unknown>, keys: string[]): unknown[] {\r\n for (const key of keys) {\r\n const v = obj[key];\r\n if (Array.isArray(v) && v.length > 0) return v;\r\n }\r\n // If none has items, return the first non-null array (even if empty) so\r\n // the caller still walks it.\r\n for (const key of keys) {\r\n const v = obj[key];\r\n if (Array.isArray(v)) return v;\r\n }\r\n return [];\r\n}\r\n\r\nfunction walkObject(node: unknown, out: BucketedStructure): void {\r\n if (!node || typeof node !== 'object') return;\r\n const obj = node as Record<string, unknown>;\r\n const id = firstStringField(obj, ['ID', 'id', 'Id']);\r\n const type = firstStringField(obj, ['Type', 'type']);\r\n const name = firstStringField(obj, ['Name', 'name']);\r\n\r\n if (id && type) {\r\n if (type === 'Spacecraft' || type.endsWith('.Spacecraft')) {\r\n upsertObjectSummary(out, {\r\n id,\r\n name: name ?? `Spacecraft ${shortId(id)}`,\r\n kind: 'spacecraft',\r\n });\r\n out.spacecraft.push({\r\n id,\r\n name: name ?? `Spacecraft ${shortId(id)}`,\r\n category: 'satellite',\r\n position: { ...PLACEHOLDER_SCENECRAFT_POSITION },\r\n visualRadius: 50,\r\n });\r\n } else if (type === 'GroundStation' || type.endsWith('.GroundStation')) {\r\n upsertObjectSummary(out, {\r\n id,\r\n name: name ?? `GroundStation ${shortId(id)}`,\r\n kind: 'groundStation',\r\n });\r\n out.groundStationIds.push(id);\r\n out.groundStationNames.push(name);\r\n } else if (type === 'CelestialBody' || type.endsWith('.CelestialBody')) {\r\n upsertObjectSummary(out, {\r\n id,\r\n name: name ?? `CelestialBody ${shortId(id)}`,\r\n kind: 'celestialBody',\r\n });\r\n out.celestialBodyIds.push(id);\r\n out.celestialBodyNames.push(name);\r\n }\r\n }\r\n\r\n // Recurse into children regardless of parent type so a Spacecraft\r\n // nested under a system is still discovered. Accept both PascalCase\r\n // and camelCase — the engine has shipped both.\r\n const children = pickArray(obj, ['Children', 'children']);\r\n for (const child of children) walkObject(child, out);\r\n}\r\n\r\nfunction upsertObjectSummary(out: BucketedStructure, next: SimulationSceneObject): void {\r\n const idx = out.objects.findIndex((o) => o.id === next.id);\r\n if (idx >= 0) {\r\n out.objects[idx] = next;\r\n return;\r\n }\r\n out.objects.push(next);\r\n}\r\n\r\n// ============================================================================\r\n// Schema detection\r\n// ============================================================================\r\n\r\n/**\r\n * Inspect a `getAllProperties` response and return the position property name\r\n * this engine exposes on a spacecraft.\r\n *\r\n * Only position is probed — velocity is intentionally excluded. The engine's\r\n * `/get` endpoint validates names more strictly than `getAllProperties`, and\r\n * `Velocity_BN_N` exists in the probe response but triggers a 400 when passed\r\n * to `getProperties`. Since velocity is optional (only position is used for\r\n * rendering), we never request it in the polling tick.\r\n *\r\n * Returns at most one name — the first position candidate whose value parses\r\n * as a 3-vector in the probe response.\r\n */\r\nfunction detectPositionPropertyNames(props: Record<string, unknown>): string[] {\r\n const positionCandidates = ['Position_BN_N', 'Position', 'r_BN_N', 'r_N'];\r\n\r\n for (const name of positionCandidates) {\r\n if (name in props && parseVec3(props[name]) !== null) {\r\n return [name];\r\n }\r\n }\r\n return [];\r\n}\r\n\r\n// ============================================================================\r\n// Property parsing\r\n// ============================================================================\r\n\r\n/**\r\n * Engines have shipped two property-naming conventions for spacecraft state:\r\n *\r\n * - Basilisk-style ECI: `Position_BN_N`, `Velocity_BN_N` (m, m/s)\r\n * - Shorter form: `Position`, `Velocity` (m, m/s)\r\n *\r\n * Both are in metres. ZenSpace3D's `Vector3D` is documented as km / km/s,\r\n * so we scale by 1e-3. The first key that produces a parseable 3-vector wins.\r\n */\r\nfunction parsePositionVelocity(\r\n raw: unknown,\r\n): { position: Vector3D; velocity?: Vector3D } | null {\r\n if (!raw || typeof raw !== 'object') return null;\r\n const obj = raw as Record<string, unknown>;\r\n const position =\r\n parseVec3(obj.Position_BN_N) ??\r\n parseVec3(obj.Position) ??\r\n parseVec3((obj as { position?: unknown }).position);\r\n if (!position) return null;\r\n // Engine returns [0,0,0] for un-initialised state (sim hasn't been ticked\r\n // / invoked yet). Treat that as \"no data\" so the upstream tick keeps the\r\n // previous (or placeholder) position rather than dropping the satellite\r\n // to Earth's centre — which crashes Cesium's Cartesian3.normalize on the\r\n // first render frame.\r\n if (position.x === 0 && position.y === 0 && position.z === 0) return null;\r\n const velocity =\r\n parseVec3(obj.Velocity_BN_N) ??\r\n parseVec3(obj.Velocity) ??\r\n parseVec3((obj as { velocity?: unknown }).velocity);\r\n return {\r\n position: scaleVec(position, M_TO_KM),\r\n velocity: velocity ? scaleVec(velocity, M_TO_KM) : undefined,\r\n };\r\n}\r\n\r\n/**\r\n * Accepts the engine's two known shapes for a 3-vector property:\r\n * either a `[x, y, z]` array or an object with `X/Y/Z` keys (the\r\n * gateway's JSON normalizer flips between the two depending on how\r\n * the property is declared in the model).\r\n */\r\nfunction parseVec3(raw: unknown): Vector3D | null {\r\n if (Array.isArray(raw) && raw.length >= 3) {\r\n const [x, y, z] = raw;\r\n if (typeof x === 'number' && typeof y === 'number' && typeof z === 'number') {\r\n return { x, y, z };\r\n }\r\n }\r\n if (raw && typeof raw === 'object') {\r\n const obj = raw as Record<string, unknown>;\r\n const x = obj.X ?? obj.x;\r\n const y = obj.Y ?? obj.y;\r\n const z = obj.Z ?? obj.z;\r\n if (typeof x === 'number' && typeof y === 'number' && typeof z === 'number') {\r\n return { x, y, z };\r\n }\r\n }\r\n return null;\r\n}\r\n\r\nfunction scaleVec(v: Vector3D, k: number): Vector3D {\r\n return { x: v.x * k, y: v.y * k, z: v.z * k };\r\n}\r\n\r\n// ============================================================================\r\n// Ground station construction\r\n// ============================================================================\r\n\r\nfunction toGroundStation(\r\n id: string,\r\n fallbackName: string,\r\n props: Record<string, unknown>,\r\n): GroundStation {\r\n const name = firstStringField(props, ['Name']) ?? fallbackName;\r\n const latitude = pickNumber(props, ['Latitude', 'latitude']) ?? 0;\r\n const longitude = pickNumber(props, ['Longitude', 'longitude']) ?? 0;\r\n const elevation = pickNumber(props, ['Altitude', 'altitude', 'Elevation']);\r\n const minElevation = pickNumber(props, ['MinElevation', 'MinimumElevation']);\r\n const station: GroundStation = { id, name, latitude, longitude };\r\n if (elevation !== undefined) station.elevation = elevation;\r\n if (minElevation !== undefined) station.minElevation = minElevation;\r\n return station;\r\n}\r\n\r\nfunction pickNumber(obj: Record<string, unknown>, keys: string[]): number | undefined {\r\n for (const key of keys) {\r\n const v = obj[key];\r\n if (typeof v === 'number' && Number.isFinite(v)) return v;\r\n }\r\n return undefined;\r\n}\r\n\r\nfunction shortId(id: string): string {\r\n return id.length > 8 ? id.slice(0, 8) : id;\r\n}\r\n"],"names":[],"mappings":"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"}
@@ -1,109 +1,84 @@
1
1
  /**
2
2
  * @zendir/ui - useZendirSession Hook
3
3
  *
4
- * React hook for managing Zendir API session lifecycle.
4
+ * React hook for managing a Zendir API session lifecycle:
5
+ * `createContainer` → `waitForContainerRunning` → `createSimulation`
6
+ * → `deleteContainer` on disconnect.
5
7
  *
6
- * OPTIONAL DEPENDENCY: Requires @zendir/sdk to be installed.
7
- * If not installed, the hook returns an error state.
8
+ * "Session" here is the consumer-facing concept used by the UI; under
9
+ * the hood it's a `(containerId, simulationId)` pair on a `zendir-ts`
10
+ * `ZendirClient` instance.
11
+ *
12
+ * OPTIONAL DEPENDENCY: requires `zendir-ts`. If not installed, the hook
13
+ * surfaces an error state and the rest of the UI continues to work.
8
14
  *
9
15
  * @example
10
16
  * ```tsx
11
17
  * import { useZendirSession } from '@zendir/ui/react';
12
18
  *
13
19
  * function App() {
14
- * const { session, connect, isLoading, error } = useZendirSession({
20
+ * const { client, session, connect, disconnect, isLoading, error } = useZendirSession({
15
21
  * apiKey: 'your-api-key',
16
22
  * autoConnect: true,
17
23
  * });
18
24
  *
19
- * if (error) return <div>Error: {error}</div>;
20
- * if (isLoading) return <div>Connecting...</div>;
21
- * if (!session) return <button onClick={connect}>Connect</button>;
25
+ * if (error) return <div>Error: {error}</div>;
26
+ * if (isLoading) return <div>Connecting…</div>;
27
+ * if (!session?.simulationId) return <button onClick={connect}>Connect</button>;
22
28
  *
23
- * return <div>Connected: {session.id}</div>;
29
+ * return <div>Connected: {session.containerId} / {session.simulationId}</div>;
24
30
  * }
25
31
  * ```
26
32
  */
27
- /** Session info from Zendir API */
33
+ /**
34
+ * Resolved session — the IDs needed to address a simulation against the
35
+ * Zendir REST API. Both are returned by the SDK's create flow and cached
36
+ * on the client (see `ZendirClient.getContainerId / getSimulationId`).
37
+ */
28
38
  export interface SessionInfo {
29
- id: string;
30
- status: 'active' | 'expired' | 'error';
31
- createdAt?: string;
32
- expiresAt?: string;
33
- }
34
- /** Simulation info from Zendir API */
35
- export interface SimulationInfo {
36
- id: string;
37
- name?: string;
38
- status: 'running' | 'paused' | 'stopped';
39
- spacecraftCount?: number;
40
- }
41
- /** Zendir client interface (matches @zendir/sdk) */
42
- export interface ZendirClientInterface {
43
- createSession: () => Promise<SessionInfo>;
44
- createSimulation: (params: {
45
- name?: string;
46
- spacecraft: Array<{
47
- name: string;
48
- tle?: {
49
- line1: string;
50
- line2: string;
51
- };
52
- catalogNumber?: number;
53
- }>;
54
- }) => Promise<SimulationInfo>;
55
- clearSession: () => void;
39
+ /** Container that hosts the simulation engine. */
40
+ containerId: string;
41
+ /** Simulation root id inside the container. */
42
+ simulationId: string;
43
+ /** Wall-clock instant the session was established. */
44
+ createdAt: Date;
56
45
  }
57
46
  export interface UseZendirSessionOptions {
58
- /** API key (optional - can be set via client) */
47
+ /** API key (or set via `ZENDIR_API_KEY` env var). */
59
48
  apiKey?: string;
60
- /** Base URL override */
49
+ /** Base URL override. */
61
50
  baseUrl?: string;
62
- /** Auto-connect on mount */
51
+ /** Container engine version (defaults to the SDK default). */
52
+ version?: string;
53
+ /** Auto-connect on mount. */
63
54
  autoConnect?: boolean;
64
- /** Enable debug logging */
55
+ /** Enable debug logging. */
65
56
  debug?: boolean;
66
57
  }
67
58
  export interface UseZendirSessionResult {
68
- /** Current session info */
59
+ /** The live `ZendirClient` instance — null until the SDK loads. */
60
+ client: any | null;
61
+ /** Resolved container + simulation IDs once `connect()` succeeds. */
69
62
  session: SessionInfo | null;
70
- /** Current simulation info */
71
- simulation: SimulationInfo | null;
72
- /** Loading state */
63
+ /** Loading state for `connect()` / `disconnect()`. */
73
64
  isLoading: boolean;
74
- /** Error message */
65
+ /** Error message from the last `connect()` / `disconnect()` (or SDK load). */
75
66
  error: string | null;
76
- /** Connected status */
67
+ /** True while a session is active. */
77
68
  isConnected: boolean;
78
- /** Whether SDK is available */
69
+ /** True once `zendir-ts` has been dynamically imported. */
79
70
  isSdkAvailable: boolean;
80
- /** Zendir client instance (null if SDK not loaded) */
81
- client: ZendirClientInterface | null;
82
- /** Connect/create session */
71
+ /** Provision a container, wait for `RUNNING`, and create a simulation. */
83
72
  connect: () => Promise<void>;
84
- /** Create simulation */
85
- createSimulation: (params: {
86
- name?: string;
87
- spacecraft: Array<{
88
- name: string;
89
- tle?: {
90
- line1: string;
91
- line2: string;
92
- };
93
- catalogNumber?: number;
94
- }>;
95
- }) => Promise<void>;
96
- /** Disconnect/clear session */
97
- disconnect: () => void;
73
+ /** Tear down the container; the simulation goes with it. */
74
+ disconnect: () => Promise<void>;
98
75
  }
99
76
  export declare function useZendirSession(options?: UseZendirSessionOptions): UseZendirSessionResult;
100
- /**
101
- * Check if @zendir/sdk is available
102
- */
77
+ /** True iff `zendir-ts` has been successfully loaded into the page. */
103
78
  export declare function isZendirSdkAvailable(): boolean;
104
79
  /**
105
- * Preload the @zendir/sdk module
106
- * Call this early in your app to ensure SDK is ready
80
+ * Preload `zendir-ts` so the first `useZendirSession` render doesn't pay
81
+ * the dynamic-import cost. Call once early in app bootstrap.
107
82
  */
108
83
  export declare function preloadZendirSdk(): Promise<boolean>;
109
84
  export default useZendirSession;
@@ -5,17 +5,20 @@
5
5
  * Designed for use in ChatGPT Apps, Next.js, and standard React applications.
6
6
  *
7
7
  * Features:
8
- * - Full TypeScript support (works with or without @zendir/sdk)
8
+ * - Full TypeScript support (works with or without `zendir-ts`)
9
9
  * - WCAG 2.1 AA accessibility
10
10
  * - Astro UX Design System compliance
11
11
  * - Reduced motion support
12
12
  * - Error boundaries and loading states
13
13
  *
14
- * Note: @zendir/sdk is optional. All types are provided locally.
15
- * Install @zendir/sdk for API client functionality.
14
+ * Note: `zendir-ts` (the Zendir TypeScript SDK) is optional. All types are
15
+ * provided locally. Install `zendir-ts` for API client functionality.
16
16
  */
17
- export { Icon, getIconNames, isValidIconName, Button, Input, Select, Toggle, Checkbox, Tooltip, Dialog, Badge, Container, Tabs, Pagination, GlassCard, GLASS_COLOR_OVERLAYS, DataValue, DataValueGroup, MessageStream, AppBar, ColorPickerPanel, getPropertyConfig, formatPropertyLabel, deriveStatus, deriveBatteryStatus, formatPropertyValue, createPropertyConfig, getPropertiesByCategory, PROPERTY_PRESETS, CATEGORY_ICONS, CATEGORY_LABELS, NumberInput, SideNav, SIDENAV_HEADER_LOGO_SLOT_HEIGHT_PX, ToastProvider, useToast, useToastManager, Popover, Menu, Box, Flex, Grid, Stack, HStack, VStack, Center, Spacer, Divider, useBreakpoint, BREAKPOINTS, resolveResponsive, resolveSpacing, ConfirmDialog, ConfirmProvider, useConfirm, PinInput, CopyButton, useCopyToClipboard, DataTable, DataTableRowDetail, ImageGallery, ChatPanel, parseChatResponse, createChatResponseParser, parseMcpToolResult, CHAT_RESPONSE_TOOL_SCHEMA, CHAT_RESPONSE_MCP_TOOL, CHAT_RESPONSE_JSON_PROMPT, CHAT_RESPONSE_YAML_PROMPT, CHAT_STATUS_RULES_PROMPT, ConnectionForm, SidePanel, HexViewer, REGION_COLORS, REGION_BORDER_COLORS, LimitsBar, LogViewer, PacketViewer, CommandBuilder, FileExplorer, MissionCalendar, ActivityPlanner, Typography, Display1, Display2, H1, H2, H3, H4, H5, H6, Body1, Body2, Body3, Compact, Micro, Mono, DataText, Label, FONT_FAMILY_PRIMARY, FONT_FAMILY_MONO, FONT_WEIGHTS, CardHeader, HeaderIconWithStatus, } from './core';
18
- export type { NumberInputProps, NumberInputSize, SliderStatus, StatusThresholds, SideNavProps, SideNavHeaderProps, SideNavItemProps, SideNavSectionProps, SideNavFooterProps, ToastOptions, ToastStatus, ToastPosition, ToastProviderProps, PopoverProps, PopoverPlacement, MenuProps, MenuItemProps, BoxProps, FlexProps, GridProps, StackProps, CenterProps, SpacerProps, DividerProps, Breakpoint, ResponsiveValue, SpacingToken, ConfirmDialogProps, ConfirmOptions, ConfirmStatus, PinInputProps, CopyButtonProps, UseCopyToClipboardReturn, DataTableProps, DataTableColumn, DataTableRowDetailProps, DataTableRowDetailField, ImageGalleryProps, GalleryImage, ChatPanelProps, ChatMessage, ChatBlock, ChatBlockEvent, ChatBlockAlert, ChatBlockTelemetry, ChatBlockProgress, ChatBlockTable, ChatBlockActions, ChatBlockChoice, ChatBlockConfirm, ChatBlockCommand, ChatBlockKV, ChatResponseFormat, ChatResponsePayload, ChatResponseParserOptions, McpToolContent, McpToolResult, ConnectionFormProps, ConnectionConfig, SidePanelProps, SidePanelPosition, HexViewerProps, HexHighlight, HexRegion, DecodedField, PacketHeaderEntry, ByteGrouping, OffsetBase, Endianness, LimitsBarProps, LimitsState, LogViewerProps, LogEntry, LogSeverity, PacketViewerProps, PacketItem, PacketItemLimits, PacketItemLimitsState, PacketViewMode, CommandBuilderProps, CommandParameter, CommandParamType, CommandHistoryEntry, FileExplorerProps, FileNode, FileViewMode, FileSortBy, MissionCalendarProps, CalendarEvent, CalendarTimeline, CalendarViewMode, ActivityStatus, ActivityType, ActivityPlannerProps, ActivityFormData, TypographyProps, TypographyVariant, TypographyElement, TypographyColor, CardHeaderProps, HeaderIconWithStatusProps, IconProps, IconName, ButtonProps, ButtonVariant, ButtonSize, InputProps, InputSize, LabelPlacement, SelectProps, SelectOption, ToggleProps, CheckboxProps, TooltipProps, TooltipPlacement, DialogProps, DialogActionsProps, DialogSize, BadgeProps, BadgeVariant, BadgeSize, ContainerProps, ContainerVariant, ContainerPadding, TabsProps, TabsListProps, TabProps, TabsPanelProps, PaginationProps, GlassCardProps, GlassColorOverlay, DataValueProps, DataValueGroupProps, DataValueVariant, DataValueSize, MessageStreamProps, StreamMessage, AppBarProps, AppBarBranding, ColorPickerPanelProps, PropertyConfig, PropertyKey, PropertyCategory, GlassAccentPosition, } from './core';
17
+ export { Icon, getIconNames, isValidIconName, Button, Input, Select, Toggle, Checkbox, Tooltip, Dialog, Badge, Container, Tabs, Pagination, GlassCard, GLASS_COLOR_OVERLAYS, DataValue, DataValueGroup, MessageStream, AppBar, ColorPickerPanel, getPropertyConfig, formatPropertyLabel, deriveStatus, deriveBatteryStatus, formatPropertyValue, createPropertyConfig, getPropertiesByCategory, PROPERTY_PRESETS, CATEGORY_ICONS, CATEGORY_LABELS, NumberInput, SideNav, SIDENAV_HEADER_LOGO_SLOT_HEIGHT_PX, ToastProvider, useToast, useToastManager, Popover, Menu, Box, Flex, Grid, Stack, HStack, VStack, Center, Spacer, Divider, useBreakpoint, BREAKPOINTS, resolveResponsive, resolveSpacing, ConfirmDialog, ConfirmProvider, useConfirm, PinInput, CopyButton, useCopyToClipboard, DataTable, DataTableRowDetail, ImageGallery, ChatPanel, parseChatResponse, createChatResponseParser, parseMcpToolResult, CHAT_RESPONSE_TOOL_SCHEMA, CHAT_RESPONSE_MCP_TOOL, CHAT_RESPONSE_JSON_PROMPT, CHAT_RESPONSE_YAML_PROMPT, CHAT_STATUS_RULES_PROMPT, ConnectionForm, SidePanel, HexViewer, REGION_COLORS, REGION_BORDER_COLORS, LimitsBar, LogViewer, PacketViewer, CommandBuilder, FileExplorer, MissionCalendar, ActivityPlanner, Typography, Display1, Display2, H1, H2, H3, H4, H5, H6, Body1, Body2, Body3, Compact, Micro, Mono, DataText, Label, FONT_FAMILY_PRIMARY, FONT_FAMILY_MONO, FONT_WEIGHTS, CardHeader, HeaderIconWithStatus, Capture, useCapture, loadCapturePlaceholderImage, } from './core';
18
+ export { ecefMetersToSdkPosition, ecefEulerXyzToHpr } from './3d';
19
+ export { ZenSpace3D, useSimulationScene } from './3d';
20
+ export type { ZenSpace3DProps, ZenSpace3DHandle, UseSimulationSceneOptions, UseSimulationSceneResult, SimulationSceneClient, SimulationSceneObject, StructureDiag, PositionTickDiag, } from './3d';
21
+ export type { NumberInputProps, NumberInputSize, SliderStatus, StatusThresholds, SideNavProps, SideNavHeaderProps, SideNavItemProps, SideNavSectionProps, SideNavFooterProps, ToastOptions, ToastStatus, ToastPosition, ToastProviderProps, PopoverProps, PopoverPlacement, MenuProps, MenuItemProps, BoxProps, FlexProps, GridProps, StackProps, CenterProps, SpacerProps, DividerProps, Breakpoint, ResponsiveValue, SpacingToken, ConfirmDialogProps, ConfirmOptions, ConfirmStatus, PinInputProps, CopyButtonProps, UseCopyToClipboardReturn, DataTableProps, DataTableColumn, DataTableRowDetailProps, DataTableRowDetailField, ImageGalleryProps, GalleryImage, ChatPanelProps, ChatMessage, ChatBlock, ChatBlockEvent, ChatBlockAlert, ChatBlockTelemetry, ChatBlockProgress, ChatBlockTable, ChatBlockActions, ChatBlockChoice, ChatBlockConfirm, ChatBlockCommand, ChatBlockKV, ChatResponseFormat, ChatResponsePayload, ChatResponseParserOptions, McpToolContent, McpToolResult, ConnectionFormProps, ConnectionConfig, SidePanelProps, SidePanelPosition, HexViewerProps, HexHighlight, HexRegion, DecodedField, PacketHeaderEntry, ByteGrouping, OffsetBase, Endianness, LimitsBarProps, LimitsState, LogViewerProps, LogEntry, LogSeverity, PacketViewerProps, PacketItem, PacketItemLimits, PacketItemLimitsState, PacketViewMode, CommandBuilderProps, CommandParameter, CommandParamType, CommandHistoryEntry, FileExplorerProps, FileNode, FileViewMode, FileSortBy, MissionCalendarProps, CalendarEvent, CalendarTimeline, CalendarViewMode, ActivityStatus, ActivityType, ActivityPlannerProps, ActivityFormData, TypographyProps, TypographyVariant, TypographyElement, TypographyColor, CardHeaderProps, HeaderIconWithStatusProps, IconProps, IconName, ButtonProps, ButtonVariant, ButtonSize, InputProps, InputSize, LabelPlacement, SelectProps, SelectOption, ToggleProps, CheckboxProps, TooltipProps, TooltipPlacement, DialogProps, DialogActionsProps, DialogSize, BadgeProps, BadgeVariant, BadgeSize, ContainerProps, ContainerVariant, ContainerPadding, TabsProps, TabsListProps, TabProps, TabsPanelProps, PaginationProps, GlassCardProps, GlassColorOverlay, DataValueProps, DataValueGroupProps, DataValueVariant, DataValueSize, MessageStreamProps, StreamMessage, AppBarProps, AppBarBranding, ColorPickerPanelProps, PropertyConfig, PropertyKey, PropertyCategory, GlassAccentPosition, CaptureProps, CaptureHandle, CaptureRequest, CaptureArgs, CapturedImage, UseCaptureOptions, UseCaptureResult, } from './core';
19
22
  export { ThemeProvider, useTheme, useThemeTokens, useScrollbarStyles, CardAccentProvider, useCardAccent, getSystemAccentColor, getAccentColorOptions, CARD_ACCENT_COLORS, SPACE_SYSTEM_COLORS, } from './theme';
20
23
  export type { ThemeProviderProps, ThemeContextValue, ThemeVariant, ThemeMode, ThemeTokens, ThemeColors, ThemeAnimation, ThemeFocus, LayoutTokens, BorderTokens, CardAccentKey, CardAccentColor, CardAccentContextValue, CardAccentProviderProps, } from './theme';
21
24
  export { DisplaySettingsProvider, useDisplaySettings, useDisplaySettingsOptional, getEffectiveCompactMode, PRESET_COLORS, GLASS_TINTS, } from './context';
@@ -36,5 +39,7 @@ export { GroundTrackMap } from './charts';
36
39
  export type { GroundTrackMapProps, MapPin, LightSource, MapLayerDef } from './charts';
37
40
  export { useCompactMode } from './hooks/useCompactMode';
38
41
  export type { UseCompactModeOptions, UseCompactModeResult } from './hooks/useCompactMode';
42
+ export { ObjectInventoryPanel, LayerControlPanel, recommendedFlyDistance, } from './panels';
43
+ export type { ObjectInventoryPanelProps, ObjectInventoryKind, LayerControlPanelProps, RenderLayers, GroupVisibility, GroupCounts, } from './panels';
39
44
  export type { SpacecraftPosition, Spacecraft, GroundStation, GroundTrackPoint, AccessData, AccessWindow, TelemetryData, OrbitalElements, Quaternion, EulerAngles, AngularVelocity, AttitudeData, PointingMode, EclipseInfo, DetailedLinkBudget, ThermalZone, ThermalData, ThrusterStatus, PropulsionSummary, ReactionWheelData, LVLHVector, LVLHState, ThrusterFireEvent, PlanetId, PlanetInfo, CategoryDef, } from './types';
40
45
  export { estimateOrbitalPeriod, estimateOrbitalVelocity, auToKm, normalizePlanetName, getPlanet, PLANETS, } from './types';
@@ -0,0 +1,54 @@
1
+ import { CSSProperties } from 'react';
2
+ import { LayerDef } from '../types';
3
+
4
+ /** Render-flag layers — passed straight through to `<ZenSpace3D layers={...} />`. */
5
+ export interface RenderLayers {
6
+ labels?: boolean;
7
+ coverage?: boolean;
8
+ orbits?: boolean;
9
+ }
10
+ /** Object-group visibility — passed straight through to `<ZenSpace3D visibility={...} />`. */
11
+ export interface GroupVisibility {
12
+ spacecraft?: boolean;
13
+ groundStations?: boolean;
14
+ celestialBodies?: boolean;
15
+ }
16
+ /** Counts shown in the group toggles ("Spacecraft (5)"). All optional. */
17
+ export interface GroupCounts {
18
+ spacecraft?: number;
19
+ groundStations?: number;
20
+ celestialBodies?: number;
21
+ }
22
+ export interface LayerControlPanelProps {
23
+ /** Current render-flag state. Each key defaults to: orbits=on, labels=off, coverage=off. */
24
+ layers?: RenderLayers;
25
+ /** Called with the next render-flag state when any toggle is clicked. */
26
+ onLayersChange?: (next: RenderLayers) => void;
27
+ /** Current group-visibility state. Each key defaults to true (visible). */
28
+ visibility?: GroupVisibility;
29
+ /** Called with the next visibility state when any toggle is clicked. */
30
+ onVisibilityChange?: (next: GroupVisibility) => void;
31
+ /** Counts to render in group toggle labels (e.g. "Spacecraft (5)"). */
32
+ groupCounts?: GroupCounts;
33
+ /** App-defined layer toggles. The panel only renders the toggle UI. */
34
+ customLayers?: LayerDef[];
35
+ /** Current state of each custom layer (id → on/off). */
36
+ customLayerState?: Record<string, boolean>;
37
+ /** Fires when a custom layer is toggled. The consumer renders the overlay itself. */
38
+ onCustomLayerChange?: (id: string, enabled: boolean) => void;
39
+ position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
40
+ offset?: {
41
+ x?: number;
42
+ y?: number;
43
+ };
44
+ hidden?: boolean;
45
+ style?: CSSProperties;
46
+ /** Hide the render-flags section (`true` to suppress). */
47
+ hideRenderFlags?: boolean;
48
+ /** Hide the object-group section. */
49
+ hideGroups?: boolean;
50
+ /** Hide the custom-layers section even if `customLayers` is non-empty. */
51
+ hideCustomLayers?: boolean;
52
+ }
53
+ export declare const LayerControlPanel: import('react').NamedExoticComponent<LayerControlPanelProps>;
54
+ export default LayerControlPanel;
@@ -0,0 +1,184 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import { memo, useCallback } from "react";
3
+ import { useThemeTokens } from "../theme/ThemeProvider.js";
4
+ const LayerControlPanel = memo(function LayerControlPanel2({
5
+ layers,
6
+ onLayersChange,
7
+ visibility,
8
+ onVisibilityChange,
9
+ groupCounts,
10
+ customLayers,
11
+ customLayerState,
12
+ onCustomLayerChange,
13
+ position = "top-right",
14
+ offset,
15
+ hidden = false,
16
+ style,
17
+ hideRenderFlags = false,
18
+ hideGroups = false,
19
+ hideCustomLayers = false
20
+ }) {
21
+ var _a;
22
+ const tokens = useThemeTokens();
23
+ const showLabels = (layers == null ? void 0 : layers.labels) === true;
24
+ const showCoverage = (layers == null ? void 0 : layers.coverage) === true;
25
+ const showOrbits = (layers == null ? void 0 : layers.orbits) !== false;
26
+ const showSpacecraft = (visibility == null ? void 0 : visibility.spacecraft) !== false;
27
+ const showGroundStations = (visibility == null ? void 0 : visibility.groundStations) !== false;
28
+ const showCelestialBodies = (visibility == null ? void 0 : visibility.celestialBodies) !== false;
29
+ const toggleLayer = useCallback(
30
+ (key) => {
31
+ if (!onLayersChange) return;
32
+ const current = key === "orbits" ? showOrbits : key === "labels" ? showLabels : showCoverage;
33
+ onLayersChange({ ...layers ?? {}, [key]: !current });
34
+ },
35
+ [layers, onLayersChange, showLabels, showCoverage, showOrbits]
36
+ );
37
+ const toggleVisibility = useCallback(
38
+ (key) => {
39
+ if (!onVisibilityChange) return;
40
+ const current = key === "spacecraft" ? showSpacecraft : key === "groundStations" ? showGroundStations : showCelestialBodies;
41
+ onVisibilityChange({ ...visibility ?? {}, [key]: !current });
42
+ },
43
+ [visibility, onVisibilityChange, showSpacecraft, showGroundStations, showCelestialBodies]
44
+ );
45
+ const offX = (offset == null ? void 0 : offset.x) ?? 16;
46
+ const offY = (offset == null ? void 0 : offset.y) ?? 16;
47
+ const anchor = (() => {
48
+ switch (position) {
49
+ case "top-left":
50
+ return { top: offY, left: offX };
51
+ case "bottom-left":
52
+ return { bottom: offY, left: offX };
53
+ case "bottom-right":
54
+ return { bottom: offY, right: offX };
55
+ case "top-right":
56
+ default:
57
+ return { top: offY, right: offX };
58
+ }
59
+ })();
60
+ if (hidden) return null;
61
+ const showRender = !hideRenderFlags && onLayersChange != null;
62
+ const showGroupsSection = !hideGroups && onVisibilityChange != null;
63
+ const showCustom = !hideCustomLayers && ((customLayers == null ? void 0 : customLayers.length) ?? 0) > 0;
64
+ if (!showRender && !showGroupsSection && !showCustom) return null;
65
+ const cnt = (n) => typeof n === "number" ? ` (${n})` : "";
66
+ return /* @__PURE__ */ jsxs(
67
+ "div",
68
+ {
69
+ "data-testid": "zendir-layer-control",
70
+ style: {
71
+ position: "absolute",
72
+ zIndex: 2,
73
+ display: "flex",
74
+ flexDirection: "column",
75
+ gap: 6,
76
+ padding: tokens.spacing.sm,
77
+ borderRadius: ((_a = tokens.borderRadius) == null ? void 0 : _a.md) ?? 6,
78
+ border: "1px solid rgba(255,255,255,0.12)",
79
+ background: "rgba(0,0,0,0.72)",
80
+ color: tokens.colors.text.primary,
81
+ fontSize: tokens.typography.fontSize.xs,
82
+ lineHeight: 1.2,
83
+ ...anchor,
84
+ ...style
85
+ },
86
+ children: [
87
+ showRender && /* @__PURE__ */ jsxs(Section, { title: "Render", children: [
88
+ /* @__PURE__ */ jsx(Toggle, { label: "Orbits", active: showOrbits, onClick: () => toggleLayer("orbits") }),
89
+ /* @__PURE__ */ jsx(Toggle, { label: "Labels", active: showLabels, onClick: () => toggleLayer("labels") }),
90
+ /* @__PURE__ */ jsx(Toggle, { label: "Coverage", active: showCoverage, onClick: () => toggleLayer("coverage") })
91
+ ] }),
92
+ showGroupsSection && /* @__PURE__ */ jsxs(Section, { title: "Objects", children: [
93
+ /* @__PURE__ */ jsx(
94
+ Toggle,
95
+ {
96
+ label: `Spacecraft${cnt(groupCounts == null ? void 0 : groupCounts.spacecraft)}`,
97
+ active: showSpacecraft,
98
+ onClick: () => toggleVisibility("spacecraft")
99
+ }
100
+ ),
101
+ /* @__PURE__ */ jsx(
102
+ Toggle,
103
+ {
104
+ label: `Ground Stations${cnt(groupCounts == null ? void 0 : groupCounts.groundStations)}`,
105
+ active: showGroundStations,
106
+ onClick: () => toggleVisibility("groundStations")
107
+ }
108
+ ),
109
+ /* @__PURE__ */ jsx(
110
+ Toggle,
111
+ {
112
+ label: `Celestial${cnt(groupCounts == null ? void 0 : groupCounts.celestialBodies)}`,
113
+ active: showCelestialBodies,
114
+ onClick: () => toggleVisibility("celestialBodies")
115
+ }
116
+ )
117
+ ] }),
118
+ showCustom && /* @__PURE__ */ jsx(Section, { title: "Overlays", children: (customLayers ?? []).map((layer) => /* @__PURE__ */ jsx(
119
+ Toggle,
120
+ {
121
+ label: layer.label,
122
+ active: (customLayerState == null ? void 0 : customLayerState[layer.id]) ?? layer.defaultEnabled ?? false,
123
+ onClick: () => {
124
+ const current = (customLayerState == null ? void 0 : customLayerState[layer.id]) ?? layer.defaultEnabled ?? false;
125
+ onCustomLayerChange == null ? void 0 : onCustomLayerChange(layer.id, !current);
126
+ }
127
+ },
128
+ layer.id
129
+ )) })
130
+ ]
131
+ }
132
+ );
133
+ });
134
+ const Section = ({ title, children }) => {
135
+ const tokens = useThemeTokens();
136
+ return /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 4 }, children: [
137
+ /* @__PURE__ */ jsx(
138
+ "div",
139
+ {
140
+ style: {
141
+ opacity: 0.6,
142
+ fontSize: "0.85em",
143
+ textTransform: "uppercase",
144
+ letterSpacing: 0.5,
145
+ color: tokens.colors.text.secondary
146
+ },
147
+ children: title
148
+ }
149
+ ),
150
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", flexDirection: "column", gap: 4 }, children })
151
+ ] });
152
+ };
153
+ const Toggle = ({
154
+ label,
155
+ active,
156
+ onClick
157
+ }) => {
158
+ var _a;
159
+ const tokens = useThemeTokens();
160
+ return /* @__PURE__ */ jsx(
161
+ "button",
162
+ {
163
+ type: "button",
164
+ onClick,
165
+ "aria-pressed": active,
166
+ style: {
167
+ padding: `${tokens.spacing.xs}px ${tokens.spacing.sm}px`,
168
+ borderRadius: ((_a = tokens.borderRadius) == null ? void 0 : _a.sm) ?? 4,
169
+ background: active ? "rgba(100,200,255,0.2)" : "rgba(0,0,0,0.6)",
170
+ border: `1px solid ${active ? "rgba(100,200,255,0.6)" : "rgba(255,255,255,0.15)"}`,
171
+ color: active ? "rgba(100,200,255,1)" : tokens.colors.text.secondary,
172
+ fontSize: tokens.typography.fontSize.xs,
173
+ cursor: "pointer",
174
+ letterSpacing: 0.3,
175
+ textAlign: "left"
176
+ },
177
+ children: label
178
+ }
179
+ );
180
+ };
181
+ export {
182
+ LayerControlPanel
183
+ };
184
+ //# sourceMappingURL=LayerControlPanel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LayerControlPanel.js","sources":["../../../src/react/panels/LayerControlPanel.tsx"],"sourcesContent":["/**\n * @zendir/ui - LayerControlPanel\n *\n * Floating layer-toggle panel for the 3D viewer (mirrors the\n * `MapLayerDef` pattern from `<GroundTrackMap />`). Three sections:\n *\n * 1. Render flags — labels / coverage / orbits (HOW objects draw)\n * 2. Object groups — spacecraft / GS / celestial (WHICH objects draw)\n * 3. Custom layers — app-defined overlays (caller renders them)\n *\n * The panel is **toggle UI only**. It emits `onChange` callbacks; the\n * consumer wires them to `<ZenSpace3D layers={...} visibility={...} />`\n * (or to its own overlay state for custom layers). Same contract as\n * `MapLayerDef` — keeps the panel reusable and the renderer\n * untouched.\n *\n * ```tsx\n * const [layers, setLayers] = useState({ orbits: true, labels: false, coverage: false });\n * const [visibility, setVisibility] = useState({\n * spacecraft: true, groundStations: true, celestialBodies: true,\n * });\n * const [customLayers, setCustomLayers] = useState<Record<string, boolean>>({ rf: false });\n *\n * <ZenSpace3D layers={layers} visibility={visibility} ... />\n * <LayerControlPanel\n * layers={layers}\n * visibility={visibility}\n * customLayers={[{ id: 'rf', label: 'RF Coverage' }]}\n * customLayerState={customLayers}\n * groupCounts={{ spacecraft: scene.satellites.length, ... }}\n * onLayersChange={setLayers}\n * onVisibilityChange={setVisibility}\n * onCustomLayerChange={(id, on) => setCustomLayers((c) => ({ ...c, [id]: on }))}\n * />\n * ```\n */\n\nimport { memo, useCallback } from 'react';\nimport type { CSSProperties, ReactNode } from 'react';\nimport { useThemeTokens } from '../theme/ThemeProvider';\nimport type { LayerDef } from '../types';\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\n/** Render-flag layers — passed straight through to `<ZenSpace3D layers={...} />`. */\nexport interface RenderLayers {\n labels?: boolean;\n coverage?: boolean;\n orbits?: boolean;\n}\n\n/** Object-group visibility — passed straight through to `<ZenSpace3D visibility={...} />`. */\nexport interface GroupVisibility {\n spacecraft?: boolean;\n groundStations?: boolean;\n celestialBodies?: boolean;\n}\n\n/** Counts shown in the group toggles (\"Spacecraft (5)\"). All optional. */\nexport interface GroupCounts {\n spacecraft?: number;\n groundStations?: number;\n celestialBodies?: number;\n}\n\nexport interface LayerControlPanelProps {\n // ─── Render flags ─────────────────────────────────────────────────────────\n /** Current render-flag state. Each key defaults to: orbits=on, labels=off, coverage=off. */\n layers?: RenderLayers;\n /** Called with the next render-flag state when any toggle is clicked. */\n onLayersChange?: (next: RenderLayers) => void;\n\n // ─── Object-group visibility ──────────────────────────────────────────────\n /** Current group-visibility state. Each key defaults to true (visible). */\n visibility?: GroupVisibility;\n /** Called with the next visibility state when any toggle is clicked. */\n onVisibilityChange?: (next: GroupVisibility) => void;\n /** Counts to render in group toggle labels (e.g. \"Spacecraft (5)\"). */\n groupCounts?: GroupCounts;\n\n // ─── Custom layers (app-defined overlays) ─────────────────────────────────\n /** App-defined layer toggles. The panel only renders the toggle UI. */\n customLayers?: LayerDef[];\n /** Current state of each custom layer (id → on/off). */\n customLayerState?: Record<string, boolean>;\n /** Fires when a custom layer is toggled. The consumer renders the overlay itself. */\n onCustomLayerChange?: (id: string, enabled: boolean) => void;\n\n // ─── Layout / theming ─────────────────────────────────────────────────────\n position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';\n offset?: { x?: number; y?: number };\n hidden?: boolean;\n style?: CSSProperties;\n\n /** Hide the render-flags section (`true` to suppress). */\n hideRenderFlags?: boolean;\n /** Hide the object-group section. */\n hideGroups?: boolean;\n /** Hide the custom-layers section even if `customLayers` is non-empty. */\n hideCustomLayers?: boolean;\n}\n\n// ─── Component ────────────────────────────────────────────────────────────────\n\nexport const LayerControlPanel = memo(function LayerControlPanel({\n layers,\n onLayersChange,\n visibility,\n onVisibilityChange,\n groupCounts,\n customLayers,\n customLayerState,\n onCustomLayerChange,\n position = 'top-right',\n offset,\n hidden = false,\n style,\n hideRenderFlags = false,\n hideGroups = false,\n hideCustomLayers = false,\n}: LayerControlPanelProps) {\n const tokens = useThemeTokens();\n\n // Default render-flag state — matches ZenSpace3D's defaults.\n const showLabels = layers?.labels === true;\n const showCoverage = layers?.coverage === true;\n const showOrbits = layers?.orbits !== false;\n\n // Default group-visibility — matches ZenSpace3D's defaults (everything on).\n const showSpacecraft = visibility?.spacecraft !== false;\n const showGroundStations = visibility?.groundStations !== false;\n const showCelestialBodies = visibility?.celestialBodies !== false;\n\n const toggleLayer = useCallback(\n (key: keyof RenderLayers) => {\n if (!onLayersChange) return;\n const current =\n key === 'orbits'\n ? showOrbits\n : key === 'labels'\n ? showLabels\n : showCoverage;\n onLayersChange({ ...(layers ?? {}), [key]: !current });\n },\n [layers, onLayersChange, showLabels, showCoverage, showOrbits],\n );\n\n const toggleVisibility = useCallback(\n (key: keyof GroupVisibility) => {\n if (!onVisibilityChange) return;\n const current =\n key === 'spacecraft'\n ? showSpacecraft\n : key === 'groundStations'\n ? showGroundStations\n : showCelestialBodies;\n onVisibilityChange({ ...(visibility ?? {}), [key]: !current });\n },\n [visibility, onVisibilityChange, showSpacecraft, showGroundStations, showCelestialBodies],\n );\n\n const offX = offset?.x ?? 16;\n const offY = offset?.y ?? 16;\n const anchor: CSSProperties = (() => {\n switch (position) {\n case 'top-left': return { top: offY, left: offX };\n case 'bottom-left': return { bottom: offY, left: offX };\n case 'bottom-right': return { bottom: offY, right: offX };\n case 'top-right':\n default: return { top: offY, right: offX };\n }\n })();\n\n if (hidden) return null;\n\n const showRender = !hideRenderFlags && onLayersChange != null;\n const showGroupsSection = !hideGroups && onVisibilityChange != null;\n const showCustom = !hideCustomLayers && (customLayers?.length ?? 0) > 0;\n\n if (!showRender && !showGroupsSection && !showCustom) return null;\n\n const cnt = (n: number | undefined) =>\n typeof n === 'number' ? ` (${n})` : '';\n\n return (\n <div\n data-testid=\"zendir-layer-control\"\n style={{\n position: 'absolute',\n zIndex: 2,\n display: 'flex',\n flexDirection: 'column',\n gap: 6,\n padding: tokens.spacing.sm,\n borderRadius: tokens.borderRadius?.md ?? 6,\n border: '1px solid rgba(255,255,255,0.12)',\n background: 'rgba(0,0,0,0.72)',\n color: tokens.colors.text.primary,\n fontSize: tokens.typography.fontSize.xs,\n lineHeight: 1.2,\n ...anchor,\n ...style,\n }}\n >\n {showRender && (\n <Section title=\"Render\">\n <Toggle label=\"Orbits\" active={showOrbits} onClick={() => toggleLayer('orbits')} />\n <Toggle label=\"Labels\" active={showLabels} onClick={() => toggleLayer('labels')} />\n <Toggle label=\"Coverage\" active={showCoverage} onClick={() => toggleLayer('coverage')} />\n </Section>\n )}\n\n {showGroupsSection && (\n <Section title=\"Objects\">\n <Toggle\n label={`Spacecraft${cnt(groupCounts?.spacecraft)}`}\n active={showSpacecraft}\n onClick={() => toggleVisibility('spacecraft')}\n />\n <Toggle\n label={`Ground Stations${cnt(groupCounts?.groundStations)}`}\n active={showGroundStations}\n onClick={() => toggleVisibility('groundStations')}\n />\n <Toggle\n label={`Celestial${cnt(groupCounts?.celestialBodies)}`}\n active={showCelestialBodies}\n onClick={() => toggleVisibility('celestialBodies')}\n />\n </Section>\n )}\n\n {showCustom && (\n <Section title=\"Overlays\">\n {(customLayers ?? []).map((layer) => (\n <Toggle\n key={layer.id}\n label={layer.label}\n active={\n customLayerState?.[layer.id] ??\n layer.defaultEnabled ??\n false\n }\n onClick={() => {\n const current =\n customLayerState?.[layer.id] ??\n layer.defaultEnabled ??\n false;\n onCustomLayerChange?.(layer.id, !current);\n }}\n />\n ))}\n </Section>\n )}\n </div>\n );\n});\n\n// ─── Sub-components ───────────────────────────────────────────────────────────\n\nconst Section = ({ title, children }: { title: string; children: ReactNode }) => {\n const tokens = useThemeTokens();\n return (\n <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>\n <div\n style={{\n opacity: 0.6,\n fontSize: '0.85em',\n textTransform: 'uppercase',\n letterSpacing: 0.5,\n color: tokens.colors.text.secondary,\n }}\n >\n {title}\n </div>\n <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>{children}</div>\n </div>\n );\n};\n\nconst Toggle = ({\n label,\n active,\n onClick,\n}: {\n label: string;\n active: boolean;\n onClick: () => void;\n}) => {\n const tokens = useThemeTokens();\n return (\n <button\n type=\"button\"\n onClick={onClick}\n aria-pressed={active}\n style={{\n padding: `${tokens.spacing.xs}px ${tokens.spacing.sm}px`,\n borderRadius: tokens.borderRadius?.sm ?? 4,\n background: active ? 'rgba(100,200,255,0.2)' : 'rgba(0,0,0,0.6)',\n border: `1px solid ${active ? 'rgba(100,200,255,0.6)' : 'rgba(255,255,255,0.15)'}`,\n color: active ? 'rgba(100,200,255,1)' : tokens.colors.text.secondary,\n fontSize: tokens.typography.fontSize.xs,\n cursor: 'pointer',\n letterSpacing: 0.3,\n textAlign: 'left',\n }}\n >\n {label}\n </button>\n );\n};\n\nexport default LayerControlPanel;\n"],"names":["LayerControlPanel"],"mappings":";;;AAwGO,MAAM,oBAAoB,KAAK,SAASA,mBAAkB;AAAA,EAC/D;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX;AAAA,EACA,SAAS;AAAA,EACT;AAAA,EACA,kBAAkB;AAAA,EAClB,aAAa;AAAA,EACb,mBAAmB;AACrB,GAA2B;;AACzB,QAAM,SAAS,eAAA;AAGf,QAAM,cAAe,iCAAQ,YAAa;AAC1C,QAAM,gBAAe,iCAAQ,cAAa;AAC1C,QAAM,cAAe,iCAAQ,YAAa;AAG1C,QAAM,kBAAsB,yCAAY,gBAAoB;AAC5D,QAAM,sBAAsB,yCAAY,oBAAoB;AAC5D,QAAM,uBAAsB,yCAAY,qBAAoB;AAE5D,QAAM,cAAc;AAAA,IAClB,CAAC,QAA4B;AAC3B,UAAI,CAAC,eAAgB;AACrB,YAAM,UACJ,QAAQ,WACJ,aACA,QAAQ,WACN,aACA;AACR,qBAAe,EAAE,GAAI,UAAU,CAAA,GAAK,CAAC,GAAG,GAAG,CAAC,SAAS;AAAA,IACvD;AAAA,IACA,CAAC,QAAQ,gBAAgB,YAAY,cAAc,UAAU;AAAA,EAAA;AAG/D,QAAM,mBAAmB;AAAA,IACvB,CAAC,QAA+B;AAC9B,UAAI,CAAC,mBAAoB;AACzB,YAAM,UACJ,QAAQ,eACJ,iBACA,QAAQ,mBACN,qBACA;AACR,yBAAmB,EAAE,GAAI,cAAc,CAAA,GAAK,CAAC,GAAG,GAAG,CAAC,SAAS;AAAA,IAC/D;AAAA,IACA,CAAC,YAAY,oBAAoB,gBAAgB,oBAAoB,mBAAmB;AAAA,EAAA;AAG1F,QAAM,QAAO,iCAAQ,MAAK;AAC1B,QAAM,QAAO,iCAAQ,MAAK;AAC1B,QAAM,UAAyB,MAAM;AACnC,YAAQ,UAAA;AAAA,MACN,KAAK;AAAgB,eAAO,EAAE,KAAK,MAAM,MAAM,KAAA;AAAA,MAC/C,KAAK;AAAgB,eAAO,EAAE,QAAQ,MAAM,MAAM,KAAA;AAAA,MAClD,KAAK;AAAgB,eAAO,EAAE,QAAQ,MAAM,OAAO,KAAA;AAAA,MACnD,KAAK;AAAA,MACL;AAAqB,eAAO,EAAE,KAAK,MAAM,OAAO,KAAA;AAAA,IAAK;AAAA,EAEzD,GAAA;AAEA,MAAI,OAAQ,QAAO;AAEnB,QAAM,aAAa,CAAC,mBAAmB,kBAAkB;AACzD,QAAM,oBAAoB,CAAC,cAAc,sBAAsB;AAC/D,QAAM,aAAa,CAAC,sBAAqB,6CAAc,WAAU,KAAK;AAEtE,MAAI,CAAC,cAAc,CAAC,qBAAqB,CAAC,WAAY,QAAO;AAE7D,QAAM,MAAM,CAAC,MACX,OAAO,MAAM,WAAW,KAAK,CAAC,MAAM;AAEtC,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,eAAY;AAAA,MACZ,OAAO;AAAA,QACL,UAAU;AAAA,QACV,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,eAAe;AAAA,QACf,KAAK;AAAA,QACL,SAAS,OAAO,QAAQ;AAAA,QACxB,gBAAc,YAAO,iBAAP,mBAAqB,OAAM;AAAA,QACzC,QAAQ;AAAA,QACR,YAAY;AAAA,QACZ,OAAO,OAAO,OAAO,KAAK;AAAA,QAC1B,UAAU,OAAO,WAAW,SAAS;AAAA,QACrC,YAAY;AAAA,QACZ,GAAG;AAAA,QACH,GAAG;AAAA,MAAA;AAAA,MAGJ,UAAA;AAAA,QAAA,cACC,qBAAC,SAAA,EAAQ,OAAM,UACb,UAAA;AAAA,UAAA,oBAAC,QAAA,EAAO,OAAM,UAAW,QAAQ,YAAc,SAAS,MAAM,YAAY,QAAQ,EAAA,CAAG;AAAA,UACrF,oBAAC,QAAA,EAAO,OAAM,UAAW,QAAQ,YAAc,SAAS,MAAM,YAAY,QAAQ,EAAA,CAAG;AAAA,UACrF,oBAAC,QAAA,EAAO,OAAM,YAAW,QAAQ,cAAc,SAAS,MAAM,YAAY,UAAU,EAAA,CAAG;AAAA,QAAA,GACzF;AAAA,QAGD,qBACC,qBAAC,SAAA,EAAQ,OAAM,WACb,UAAA;AAAA,UAAA;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,OAAO,aAAa,IAAI,2CAAa,UAAU,CAAC;AAAA,cAChD,QAAQ;AAAA,cACR,SAAS,MAAM,iBAAiB,YAAY;AAAA,YAAA;AAAA,UAAA;AAAA,UAE9C;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,OAAO,kBAAkB,IAAI,2CAAa,cAAc,CAAC;AAAA,cACzD,QAAQ;AAAA,cACR,SAAS,MAAM,iBAAiB,gBAAgB;AAAA,YAAA;AAAA,UAAA;AAAA,UAElD;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,OAAO,YAAY,IAAI,2CAAa,eAAe,CAAC;AAAA,cACpD,QAAQ;AAAA,cACR,SAAS,MAAM,iBAAiB,iBAAiB;AAAA,YAAA;AAAA,UAAA;AAAA,QACnD,GACF;AAAA,QAGD,cACC,oBAAC,SAAA,EAAQ,OAAM,YACX,2BAAgB,CAAA,GAAI,IAAI,CAAC,UACzB;AAAA,UAAC;AAAA,UAAA;AAAA,YAEC,OAAO,MAAM;AAAA,YACb,SACE,qDAAmB,MAAM,QACzB,MAAM,kBACN;AAAA,YAEF,SAAS,MAAM;AACb,oBAAM,WACJ,qDAAmB,MAAM,QACzB,MAAM,kBACN;AACF,yEAAsB,MAAM,IAAI,CAAC;AAAA,YACnC;AAAA,UAAA;AAAA,UAbK,MAAM;AAAA,QAAA,CAed,EAAA,CACH;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAIR,CAAC;AAID,MAAM,UAAU,CAAC,EAAE,OAAO,eAAuD;AAC/E,QAAM,SAAS,eAAA;AACf,SACE,qBAAC,OAAA,EAAI,OAAO,EAAE,SAAS,QAAQ,eAAe,UAAU,KAAK,EAAA,GAC3D,UAAA;AAAA,IAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,OAAO;AAAA,UACL,SAAS;AAAA,UACT,UAAU;AAAA,UACV,eAAe;AAAA,UACf,eAAe;AAAA,UACf,OAAO,OAAO,OAAO,KAAK;AAAA,QAAA;AAAA,QAG3B,UAAA;AAAA,MAAA;AAAA,IAAA;AAAA,IAEH,oBAAC,OAAA,EAAI,OAAO,EAAE,SAAS,QAAQ,eAAe,UAAU,KAAK,EAAA,GAAM,SAAA,CAAS;AAAA,EAAA,GAC9E;AAEJ;AAEA,MAAM,SAAS,CAAC;AAAA,EACd;AAAA,EACA;AAAA,EACA;AACF,MAIM;;AACJ,QAAM,SAAS,eAAA;AACf,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,MAAK;AAAA,MACL;AAAA,MACA,gBAAc;AAAA,MACd,OAAO;AAAA,QACL,SAAS,GAAG,OAAO,QAAQ,EAAE,MAAM,OAAO,QAAQ,EAAE;AAAA,QACpD,gBAAc,YAAO,iBAAP,mBAAqB,OAAM;AAAA,QACzC,YAAY,SAAS,0BAA0B;AAAA,QAC/C,QAAQ,aAAa,SAAS,0BAA0B,wBAAwB;AAAA,QAChF,OAAO,SAAS,wBAAwB,OAAO,OAAO,KAAK;AAAA,QAC3D,UAAU,OAAO,WAAW,SAAS;AAAA,QACrC,QAAQ;AAAA,QACR,eAAe;AAAA,QACf,WAAW;AAAA,MAAA;AAAA,MAGZ,UAAA;AAAA,IAAA;AAAA,EAAA;AAGP;"}