@zendir/ui 0.5.0 → 0.6.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 (391) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +640 -631
  3. package/dist/_virtual/_rolldown/runtime.js +13 -0
  4. package/dist/index.js +17 -221
  5. package/dist/react/3d/CesiumCaptureSource.d.ts +0 -1
  6. package/dist/react/3d/CesiumCaptureSource.js +364 -283
  7. package/dist/react/3d/CesiumCaptureSource.js.map +1 -1
  8. package/dist/react/3d/ZenSpace3D.d.ts +2 -3
  9. package/dist/react/3d/ZenSpace3D.js +1681 -1237
  10. package/dist/react/3d/ZenSpace3D.js.map +1 -1
  11. package/dist/react/3d/ZenSpace3DCesium.d.ts +0 -1
  12. package/dist/react/3d/ZenSpace3DCesium.js +513 -560
  13. package/dist/react/3d/ZenSpace3DCesium.js.map +1 -1
  14. package/dist/react/3d/ZenSpace3DShaders.js +61 -66
  15. package/dist/react/3d/ZenSpace3DShaders.js.map +1 -1
  16. package/dist/react/3d/ZenSpace3DTypes.d.ts +19 -20
  17. package/dist/react/3d/ZenSpace3DUtils.d.ts +0 -1
  18. package/dist/react/3d/ZenSpace3DUtils.js +37 -24
  19. package/dist/react/3d/ZenSpace3DUtils.js.map +1 -1
  20. package/dist/react/3d/capturePngAnalysis.js +42 -36
  21. package/dist/react/3d/capturePngAnalysis.js.map +1 -1
  22. package/dist/react/3d/index.d.ts +3 -3
  23. package/dist/react/3d/index.js +5 -53
  24. package/dist/react/3d/threeLoader.d.ts +1 -1
  25. package/dist/react/3d/threeLoader.js +23 -18
  26. package/dist/react/3d/threeLoader.js.map +1 -1
  27. package/dist/react/astro/ClassificationBanner.d.ts +2 -3
  28. package/dist/react/astro/ClassificationBanner.js +95 -79
  29. package/dist/react/astro/ClassificationBanner.js.map +1 -1
  30. package/dist/react/astro/GlobalStatusBar.d.ts +0 -1
  31. package/dist/react/astro/GlobalStatusBar.js +157 -163
  32. package/dist/react/astro/GlobalStatusBar.js.map +1 -1
  33. package/dist/react/astro/MissionClock.d.ts +7 -8
  34. package/dist/react/astro/MissionClock.js +377 -400
  35. package/dist/react/astro/MissionClock.js.map +1 -1
  36. package/dist/react/astro/MonitoringIcon.d.ts +1 -2
  37. package/dist/react/astro/MonitoringIcon.js +422 -358
  38. package/dist/react/astro/MonitoringIcon.js.map +1 -1
  39. package/dist/react/astro/Notification.d.ts +1 -2
  40. package/dist/react/astro/Notification.js +229 -126
  41. package/dist/react/astro/Notification.js.map +1 -1
  42. package/dist/react/astro/Progress.d.ts +0 -1
  43. package/dist/react/astro/Progress.js +199 -141
  44. package/dist/react/astro/Progress.js.map +1 -1
  45. package/dist/react/astro/SimulationControls.d.ts +3 -4
  46. package/dist/react/astro/SimulationControls.js +596 -650
  47. package/dist/react/astro/SimulationControls.js.map +1 -1
  48. package/dist/react/astro/StatusIndicator.d.ts +0 -1
  49. package/dist/react/astro/StatusIndicator.js +236 -170
  50. package/dist/react/astro/StatusIndicator.js.map +1 -1
  51. package/dist/react/astro/UnifiedTimeline.d.ts +9 -10
  52. package/dist/react/astro/UnifiedTimeline.js +2345 -2392
  53. package/dist/react/astro/UnifiedTimeline.js.map +1 -1
  54. package/dist/react/astro/index.d.ts +5 -5
  55. package/dist/react/astro/index.js +1 -25
  56. package/dist/react/cards/AccessCard.d.ts +0 -1
  57. package/dist/react/cards/AccessCard.js +471 -405
  58. package/dist/react/cards/AccessCard.js.map +1 -1
  59. package/dist/react/cards/OrbitCard.d.ts +0 -1
  60. package/dist/react/cards/OrbitCard.js +487 -364
  61. package/dist/react/cards/OrbitCard.js.map +1 -1
  62. package/dist/react/cards/SpacecraftCard.d.ts +0 -1
  63. package/dist/react/cards/SpacecraftCard.js +813 -925
  64. package/dist/react/cards/SpacecraftCard.js.map +1 -1
  65. package/dist/react/cards/TelemetryCard.d.ts +0 -1
  66. package/dist/react/cards/TelemetryCard.js +1055 -732
  67. package/dist/react/cards/TelemetryCard.js.map +1 -1
  68. package/dist/react/cards/TelemetryStreamCard.d.ts +0 -1
  69. package/dist/react/cards/TelemetryStreamCard.js +323 -297
  70. package/dist/react/cards/TelemetryStreamCard.js.map +1 -1
  71. package/dist/react/cards/index.d.ts +1 -1
  72. package/dist/react/cards/index.js +1 -8
  73. package/dist/react/charts/GroundTrackMap.d.ts +6 -7
  74. package/dist/react/charts/GroundTrackMap.js +1377 -1256
  75. package/dist/react/charts/GroundTrackMap.js.map +1 -1
  76. package/dist/react/charts/GroundTrackMapLeaflet.d.ts +1 -2
  77. package/dist/react/charts/GroundTrackMapLeaflet.js +1065 -987
  78. package/dist/react/charts/GroundTrackMapLeaflet.js.map +1 -1
  79. package/dist/react/charts/groundTrackMapLeafletTiles.js +34 -25
  80. package/dist/react/charts/groundTrackMapLeafletTiles.js.map +1 -1
  81. package/dist/react/charts/groundTrackMapLeafletUtils.js +70 -61
  82. package/dist/react/charts/groundTrackMapLeafletUtils.js.map +1 -1
  83. package/dist/react/charts/index.d.ts +1 -1
  84. package/dist/react/charts/index.js +7 -100
  85. package/dist/react/charts/unified/AstroChart.d.ts +1 -2
  86. package/dist/react/charts/unified/AstroChart.js +1331 -1411
  87. package/dist/react/charts/unified/AstroChart.js.map +1 -1
  88. package/dist/react/charts/unified/PowerOverviewChart.d.ts +3 -3
  89. package/dist/react/charts/unified/PowerOverviewChart.js +495 -478
  90. package/dist/react/charts/unified/PowerOverviewChart.js.map +1 -1
  91. package/dist/react/charts/unified/domain.d.ts +12 -13
  92. package/dist/react/charts/unified/domain.js +3999 -3145
  93. package/dist/react/charts/unified/domain.js.map +1 -1
  94. package/dist/react/charts/unified/generators.d.ts +3 -4
  95. package/dist/react/charts/unified/generators.js +561 -498
  96. package/dist/react/charts/unified/generators.js.map +1 -1
  97. package/dist/react/charts/unified/index.d.ts +1 -1
  98. package/dist/react/charts/unified/presets.d.ts +14 -15
  99. package/dist/react/charts/unified/presets.js +857 -994
  100. package/dist/react/charts/unified/presets.js.map +1 -1
  101. package/dist/react/charts/unified/sync.d.ts +2 -3
  102. package/dist/react/charts/unified/sync.js +229 -195
  103. package/dist/react/charts/unified/sync.js.map +1 -1
  104. package/dist/react/charts/unified/theme.d.ts +11 -12
  105. package/dist/react/charts/unified/theme.js +606 -642
  106. package/dist/react/charts/unified/theme.js.map +1 -1
  107. package/dist/react/charts/unified/types.d.ts +32 -33
  108. package/dist/react/charts/unified/useChartStream.d.ts +1 -2
  109. package/dist/react/charts/unified/useChartStream.js +203 -216
  110. package/dist/react/charts/unified/useChartStream.js.map +1 -1
  111. package/dist/react/chatgpt/AppCard.d.ts +1 -2
  112. package/dist/react/chatgpt/index.d.ts +7 -7
  113. package/dist/react/chatgpt/index.js +160 -139
  114. package/dist/react/chatgpt/index.js.map +1 -1
  115. package/dist/react/context/CategoryContext.d.ts +0 -1
  116. package/dist/react/context/CategoryContext.js +78 -29
  117. package/dist/react/context/CategoryContext.js.map +1 -1
  118. package/dist/react/context/DisplaySettingsContext.d.ts +1 -2
  119. package/dist/react/context/DisplaySettingsContext.js +205 -156
  120. package/dist/react/context/DisplaySettingsContext.js.map +1 -1
  121. package/dist/react/context/SpatialSelectionContext.d.ts +0 -1
  122. package/dist/react/context/SpatialSelectionContext.js +36 -7
  123. package/dist/react/context/SpatialSelectionContext.js.map +1 -1
  124. package/dist/react/core/data/DataTable.d.ts +3 -4
  125. package/dist/react/core/data/DataTable.js +473 -440
  126. package/dist/react/core/data/DataTable.js.map +1 -1
  127. package/dist/react/core/data/DataValue.d.ts +4 -5
  128. package/dist/react/core/data/DataValue.js +605 -571
  129. package/dist/react/core/data/DataValue.js.map +1 -1
  130. package/dist/react/core/data/propertyConfig.d.ts +1 -2
  131. package/dist/react/core/data/propertyConfig.js +446 -382
  132. package/dist/react/core/data/propertyConfig.js.map +1 -1
  133. package/dist/react/core/display/AstroIcon.d.ts +4 -5
  134. package/dist/react/core/display/AstroIcon.js +339 -429
  135. package/dist/react/core/display/AstroIcon.js.map +1 -1
  136. package/dist/react/core/display/Badge.d.ts +2 -3
  137. package/dist/react/core/display/Badge.js +131 -131
  138. package/dist/react/core/display/Badge.js.map +1 -1
  139. package/dist/react/core/display/CardHeader.d.ts +0 -1
  140. package/dist/react/core/display/CardHeader.js +398 -311
  141. package/dist/react/core/display/CardHeader.js.map +1 -1
  142. package/dist/react/core/display/Container.d.ts +2 -3
  143. package/dist/react/core/display/Container.js +640 -668
  144. package/dist/react/core/display/Container.js.map +1 -1
  145. package/dist/react/core/display/CopyButton.d.ts +1 -2
  146. package/dist/react/core/display/CopyButton.js +158 -101
  147. package/dist/react/core/display/CopyButton.js.map +1 -1
  148. package/dist/react/core/display/GlassCard.d.ts +3 -4
  149. package/dist/react/core/display/GlassCard.js +416 -369
  150. package/dist/react/core/display/GlassCard.js.map +1 -1
  151. package/dist/react/core/display/HeaderIconWithStatus.d.ts +0 -1
  152. package/dist/react/core/display/HeaderIconWithStatus.js +152 -152
  153. package/dist/react/core/display/HeaderIconWithStatus.js.map +1 -1
  154. package/dist/react/core/display/Icon.d.ts +2 -3
  155. package/dist/react/core/display/Icon.js +172 -143
  156. package/dist/react/core/display/Icon.js.map +1 -1
  157. package/dist/react/core/display/Typography.d.ts +24 -25
  158. package/dist/react/core/display/Typography.js +360 -178
  159. package/dist/react/core/display/Typography.js.map +1 -1
  160. package/dist/react/core/feedback/ConfirmDialog.d.ts +1 -2
  161. package/dist/react/core/feedback/ConfirmDialog.js +343 -304
  162. package/dist/react/core/feedback/ConfirmDialog.js.map +1 -1
  163. package/dist/react/core/feedback/Dialog.d.ts +2 -3
  164. package/dist/react/core/feedback/Dialog.js +189 -197
  165. package/dist/react/core/feedback/Dialog.js.map +1 -1
  166. package/dist/react/core/feedback/Toast.d.ts +2 -3
  167. package/dist/react/core/feedback/Toast.js +346 -207
  168. package/dist/react/core/feedback/Toast.js.map +1 -1
  169. package/dist/react/core/index.d.ts +26 -26
  170. package/dist/react/core/index.js +7 -119
  171. package/dist/react/core/inputs/Button.d.ts +2 -3
  172. package/dist/react/core/inputs/Button.js +281 -304
  173. package/dist/react/core/inputs/Button.js.map +1 -1
  174. package/dist/react/core/inputs/Checkbox.d.ts +2 -3
  175. package/dist/react/core/inputs/Checkbox.js +139 -127
  176. package/dist/react/core/inputs/Checkbox.js.map +1 -1
  177. package/dist/react/core/inputs/Input.d.ts +3 -4
  178. package/dist/react/core/inputs/Input.js +277 -285
  179. package/dist/react/core/inputs/Input.js.map +1 -1
  180. package/dist/react/core/inputs/LimitsBar.d.ts +1 -2
  181. package/dist/react/core/inputs/LimitsBar.js +271 -196
  182. package/dist/react/core/inputs/LimitsBar.js.map +1 -1
  183. package/dist/react/core/inputs/NumberInput.d.ts +2 -3
  184. package/dist/react/core/inputs/NumberInput.js +531 -503
  185. package/dist/react/core/inputs/NumberInput.js.map +1 -1
  186. package/dist/react/core/inputs/PinInput.d.ts +2 -3
  187. package/dist/react/core/inputs/PinInput.js +224 -207
  188. package/dist/react/core/inputs/PinInput.js.map +1 -1
  189. package/dist/react/core/inputs/Select.d.ts +1 -2
  190. package/dist/react/core/inputs/Select.js +298 -299
  191. package/dist/react/core/inputs/Select.js.map +1 -1
  192. package/dist/react/core/inputs/Toggle.d.ts +2 -3
  193. package/dist/react/core/inputs/Toggle.js +142 -148
  194. package/dist/react/core/inputs/Toggle.js.map +1 -1
  195. package/dist/react/core/layout/Box.d.ts +3 -4
  196. package/dist/react/core/layout/Box.js +118 -122
  197. package/dist/react/core/layout/Box.js.map +1 -1
  198. package/dist/react/core/layout/Center.d.ts +0 -1
  199. package/dist/react/core/layout/Center.js +45 -32
  200. package/dist/react/core/layout/Center.js.map +1 -1
  201. package/dist/react/core/layout/Divider.d.ts +1 -2
  202. package/dist/react/core/layout/Divider.js +112 -106
  203. package/dist/react/core/layout/Divider.js.map +1 -1
  204. package/dist/react/core/layout/Flex.d.ts +4 -5
  205. package/dist/react/core/layout/Flex.js +120 -124
  206. package/dist/react/core/layout/Flex.js.map +1 -1
  207. package/dist/react/core/layout/Grid.d.ts +5 -6
  208. package/dist/react/core/layout/Grid.js +131 -138
  209. package/dist/react/core/layout/Grid.js.map +1 -1
  210. package/dist/react/core/layout/Spacer.d.ts +0 -1
  211. package/dist/react/core/layout/Spacer.js +36 -29
  212. package/dist/react/core/layout/Spacer.js.map +1 -1
  213. package/dist/react/core/layout/Stack.d.ts +4 -5
  214. package/dist/react/core/layout/Stack.js +127 -70
  215. package/dist/react/core/layout/Stack.js.map +1 -1
  216. package/dist/react/core/layout/responsive.d.ts +1 -2
  217. package/dist/react/core/layout/responsive.js +25 -19
  218. package/dist/react/core/layout/responsive.js.map +1 -1
  219. package/dist/react/core/layout/useBreakpoint.d.ts +1 -1
  220. package/dist/react/core/layout/useBreakpoint.js +119 -66
  221. package/dist/react/core/layout/useBreakpoint.js.map +1 -1
  222. package/dist/react/core/navigation/AppBar.d.ts +1 -2
  223. package/dist/react/core/navigation/AppBar.js +827 -805
  224. package/dist/react/core/navigation/AppBar.js.map +1 -1
  225. package/dist/react/core/navigation/Pagination.d.ts +2 -3
  226. package/dist/react/core/navigation/Pagination.js +207 -187
  227. package/dist/react/core/navigation/Pagination.js.map +1 -1
  228. package/dist/react/core/navigation/SideNav.d.ts +5 -6
  229. package/dist/react/core/navigation/SideNav.js +855 -629
  230. package/dist/react/core/navigation/SideNav.js.map +1 -1
  231. package/dist/react/core/navigation/Tabs.d.ts +0 -1
  232. package/dist/react/core/navigation/Tabs.js +113 -119
  233. package/dist/react/core/navigation/Tabs.js.map +1 -1
  234. package/dist/react/core/overlays/Popover.d.ts +2 -3
  235. package/dist/react/core/overlays/Popover.js +303 -282
  236. package/dist/react/core/overlays/Popover.js.map +1 -1
  237. package/dist/react/core/overlays/SidePanel.d.ts +1 -2
  238. package/dist/react/core/overlays/SidePanel.js +217 -212
  239. package/dist/react/core/overlays/SidePanel.js.map +1 -1
  240. package/dist/react/core/overlays/Tooltip.d.ts +1 -2
  241. package/dist/react/core/overlays/Tooltip.js +170 -176
  242. package/dist/react/core/overlays/Tooltip.js.map +1 -1
  243. package/dist/react/core/widgets/ActivityPlanner.d.ts +0 -1
  244. package/dist/react/core/widgets/ActivityPlanner.js +855 -517
  245. package/dist/react/core/widgets/ActivityPlanner.js.map +1 -1
  246. package/dist/react/core/widgets/Capture.d.ts +2 -3
  247. package/dist/react/core/widgets/Capture.js +965 -779
  248. package/dist/react/core/widgets/Capture.js.map +1 -1
  249. package/dist/react/core/widgets/ChatPanel.d.ts +16 -17
  250. package/dist/react/core/widgets/ChatPanel.js +1734 -1034
  251. package/dist/react/core/widgets/ChatPanel.js.map +1 -1
  252. package/dist/react/core/widgets/ColorPickerPanel.d.ts +0 -1
  253. package/dist/react/core/widgets/ColorPickerPanel.js +298 -287
  254. package/dist/react/core/widgets/ColorPickerPanel.js.map +1 -1
  255. package/dist/react/core/widgets/CommandBuilder.d.ts +2 -3
  256. package/dist/react/core/widgets/CommandBuilder.js +624 -516
  257. package/dist/react/core/widgets/CommandBuilder.js.map +1 -1
  258. package/dist/react/core/widgets/ConnectionForm.d.ts +2 -3
  259. package/dist/react/core/widgets/ConnectionForm.js +528 -492
  260. package/dist/react/core/widgets/ConnectionForm.js.map +1 -1
  261. package/dist/react/core/widgets/FileExplorer.d.ts +3 -4
  262. package/dist/react/core/widgets/FileExplorer.js +617 -505
  263. package/dist/react/core/widgets/FileExplorer.js.map +1 -1
  264. package/dist/react/core/widgets/HexViewer.d.ts +2 -3
  265. package/dist/react/core/widgets/HexViewer.js +1353 -1091
  266. package/dist/react/core/widgets/HexViewer.js.map +1 -1
  267. package/dist/react/core/widgets/ImageFrame.d.ts +1 -2
  268. package/dist/react/core/widgets/ImageFrame.js +230 -193
  269. package/dist/react/core/widgets/ImageFrame.js.map +1 -1
  270. package/dist/react/core/widgets/ImageGallery.d.ts +2 -1
  271. package/dist/react/core/widgets/ImageGallery.js +380 -314
  272. package/dist/react/core/widgets/ImageGallery.js.map +1 -1
  273. package/dist/react/core/widgets/ImagePanel.d.ts +0 -1
  274. package/dist/react/core/widgets/ImagePanel.js +110 -101
  275. package/dist/react/core/widgets/ImagePanel.js.map +1 -1
  276. package/dist/react/core/widgets/LogViewer.d.ts +2 -3
  277. package/dist/react/core/widgets/LogViewer.js +711 -607
  278. package/dist/react/core/widgets/LogViewer.js.map +1 -1
  279. package/dist/react/core/widgets/MessageStream.d.ts +3 -4
  280. package/dist/react/core/widgets/MessageStream.js +514 -444
  281. package/dist/react/core/widgets/MessageStream.js.map +1 -1
  282. package/dist/react/core/widgets/MissionCalendar.d.ts +3 -4
  283. package/dist/react/core/widgets/MissionCalendar.js +1488 -1020
  284. package/dist/react/core/widgets/MissionCalendar.js.map +1 -1
  285. package/dist/react/core/widgets/PacketViewer.d.ts +2 -3
  286. package/dist/react/core/widgets/PacketViewer.js +550 -421
  287. package/dist/react/core/widgets/PacketViewer.js.map +1 -1
  288. package/dist/react/core/widgets/capture-placeholder.png.js +6 -5
  289. package/dist/react/core/widgets/capture-placeholder.png.js.map +1 -1
  290. package/dist/react/hooks/index.d.ts +5 -5
  291. package/dist/react/hooks/useAccessWindows.d.ts +0 -1
  292. package/dist/react/hooks/useCompactMode.d.ts +1 -1
  293. package/dist/react/hooks/useCompactMode.js +104 -58
  294. package/dist/react/hooks/useCompactMode.js.map +1 -1
  295. package/dist/react/hooks/useGroundTrackHistory.d.ts +0 -1
  296. package/dist/react/hooks/useSimulationScene.d.ts +1 -2
  297. package/dist/react/hooks/useSimulationScene.js +481 -374
  298. package/dist/react/hooks/useSimulationScene.js.map +1 -1
  299. package/dist/react/index.d.ts +3 -3
  300. package/dist/react/panels/LayerControlPanel.d.ts +1 -2
  301. package/dist/react/panels/LayerControlPanel.js +223 -180
  302. package/dist/react/panels/LayerControlPanel.js.map +1 -1
  303. package/dist/react/panels/ObjectInventoryPanel.d.ts +2 -3
  304. package/dist/react/panels/ObjectInventoryPanel.js +315 -250
  305. package/dist/react/panels/ObjectInventoryPanel.js.map +1 -1
  306. package/dist/react/panels/index.d.ts +1 -1
  307. package/dist/react/panels/index.js +1 -6
  308. package/dist/react/shared/ErrorBoundary.d.ts +1 -2
  309. package/dist/react/shared/ErrorBoundary.js +170 -139
  310. package/dist/react/shared/ErrorBoundary.js.map +1 -1
  311. package/dist/react/shared/Skeleton.d.ts +1 -2
  312. package/dist/react/shared/Skeleton.js +300 -301
  313. package/dist/react/shared/Skeleton.js.map +1 -1
  314. package/dist/react/theme/ThemeProvider.d.ts +2 -321
  315. package/dist/react/theme/ThemeProvider.js +312 -939
  316. package/dist/react/theme/ThemeProvider.js.map +1 -1
  317. package/dist/react/theme/cardAccent.d.ts +1 -2
  318. package/dist/react/theme/cardAccent.js +196 -126
  319. package/dist/react/theme/cardAccent.js.map +1 -1
  320. package/dist/react/theme/config.d.ts +0 -1
  321. package/dist/react/theme/config.js +47 -25
  322. package/dist/react/theme/config.js.map +1 -1
  323. package/dist/react/theme/index.d.ts +2 -2
  324. package/dist/react/theme/index.js +1 -16
  325. package/dist/react/theme/types.d.ts +332 -0
  326. package/dist/react/theme/types.js +39 -0
  327. package/dist/react/theme/types.js.map +1 -0
  328. package/dist/react/types.d.ts +8 -8
  329. package/dist/react/types.js +147 -47
  330. package/dist/react/types.js.map +1 -1
  331. package/dist/react/utils/categoryPalette.d.ts +0 -1
  332. package/dist/react/utils/categoryPalette.js +91 -102
  333. package/dist/react/utils/categoryPalette.js.map +1 -1
  334. package/dist/react/utils/index.d.ts +5 -5
  335. package/dist/react/utils/index.js +457 -371
  336. package/dist/react/utils/index.js.map +1 -1
  337. package/dist/react/visualizations/EclipseTimerCard.d.ts +0 -1
  338. package/dist/react/visualizations/LinkBudgetCard.d.ts +0 -1
  339. package/dist/react/visualizations/NavBallCard.d.ts +0 -1
  340. package/dist/react/visualizations/PropulsionCard.d.ts +1 -2
  341. package/dist/react/visualizations/SensorFootprintCard.d.ts +1 -2
  342. package/dist/react/visualizations/ThermalHeatmapCard.d.ts +0 -1
  343. package/dist/react/visualizations/index.d.ts +3 -3
  344. package/dist/react.d.ts +2 -1
  345. package/dist/react.js +17 -221
  346. package/dist/shaders/atmosphere.frag.js +6 -5
  347. package/dist/shaders/atmosphere.frag.js.map +1 -1
  348. package/dist/shaders/atmosphere.vert.js +6 -5
  349. package/dist/shaders/atmosphere.vert.js.map +1 -1
  350. package/dist/shaders/stars.frag.js +6 -5
  351. package/dist/shaders/stars.frag.js.map +1 -1
  352. package/dist/shaders/stars.vert.js +6 -5
  353. package/dist/shaders/stars.vert.js.map +1 -1
  354. package/dist/style.css +217 -194
  355. package/dist/tokens/core.d.ts +159 -0
  356. package/dist/tokens/core.js +144 -0
  357. package/dist/tokens/core.js.map +1 -0
  358. package/dist/tokens/css-vars.d.ts +213 -2
  359. package/dist/tokens/css-vars.js +590 -227
  360. package/dist/tokens/css-vars.js.map +1 -1
  361. package/dist/tokens/index.d.ts +31 -228
  362. package/dist/tokens/index.js +281 -366
  363. package/dist/tokens/index.js.map +1 -1
  364. package/dist/tokens/palettes/astro.d.ts +129 -0
  365. package/dist/tokens/palettes/astro.js +112 -0
  366. package/dist/tokens/palettes/astro.js.map +1 -0
  367. package/dist/tokens/palettes/index.d.ts +288 -0
  368. package/dist/tokens/palettes/index.js +35 -0
  369. package/dist/tokens/palettes/index.js.map +1 -0
  370. package/dist/tokens/palettes/tailwind.d.ts +324 -0
  371. package/dist/tokens/palettes/tailwind.js +201 -0
  372. package/dist/tokens/palettes/tailwind.js.map +1 -0
  373. package/dist/tokens/palettes/zendir.d.ts +126 -0
  374. package/dist/tokens/palettes/zendir.js +92 -0
  375. package/dist/tokens/palettes/zendir.js.map +1 -0
  376. package/dist/tokens/themes.d.ts +30 -0
  377. package/dist/tokens/themes.js +704 -0
  378. package/dist/tokens/themes.js.map +1 -0
  379. package/dist/tokens/tokens.css +419 -41
  380. package/dist/types/index.d.ts +2 -2
  381. package/package.json +310 -313
  382. package/dist/index.js.map +0 -1
  383. package/dist/react/3d/index.js.map +0 -1
  384. package/dist/react/astro/index.js.map +0 -1
  385. package/dist/react/cards/index.js.map +0 -1
  386. package/dist/react/charts/index.js.map +0 -1
  387. package/dist/react/core/index.js.map +0 -1
  388. package/dist/react/panels/index.js.map +0 -1
  389. package/dist/react/theme/astro-tokens.d.ts +0 -153
  390. package/dist/react/theme/index.js.map +0 -1
  391. package/dist/react.js.map +0 -1
@@ -1 +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
+ {"version":3,"file":"useSimulationScene.js","names":[],"sources":["../../../src/react/hooks/useSimulationScene.ts"],"sourcesContent":["/**\n * @zendir/ui - useSimulationScene Hook\n *\n * Bridge between a `zendir-ts` `ZendirClient` and the `ZenSpace3D`\n * Cesium component. On mount the hook calls `getSimulationStructure`\n * to bucket simulation objects (spacecraft / ground stations) and then\n * polls each spacecraft's `Position_BN_N` on a fixed interval, emitting\n * ZenSpace3D-shaped state.\n *\n * Ground stations are resolved once per structure load (lat/lon is\n * static — no ECI polling).\n *\n * Structure JSON may use `ID`/`id` and `Type`/`type` interchangeably;\n * the walker accepts common variants. Call `refresh()` to re-fetch the\n * structure (e.g. on a consumer-owned timer) so newly spawned objects\n * appear without remounting.\n *\n * `referenceDate` is the wall-clock instant of the last successful tick\n * — pass it to `<ZenSpace3D referenceTime={...} />` so ECI/J2000\n * positions rotate to ECEF at the engine's \"now\".\n *\n * OPTIONAL DEPENDENCY: requires a `zendir-ts` client instance. The\n * client is passed in to avoid coupling this hook to a specific\n * session-management strategy (the consumer app owns sessions).\n */\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport type { GroundStation } from \"../types\";\nimport type { Satellite, Vector3D } from \"../3d/ZenSpace3DTypes\";\n\n/**\n * Non-zero ECI placeholder (km) used until the first `Position_BN_N`\n * poll lands. The 3D viewer expects ECI / J2000 km positions; the\n * origin (0,0,0) sits at Earth's centre and would crash Cesium's\n * `Cartesian3.normalize` with \"normalized result is not a number\".\n *\n * Value: ~400 km altitude on the +X axis (vernal-equinox direction).\n * Renders just above the equator and is invisible after one tick.\n */\nconst PLACEHOLDER_SCENECRAFT_POSITION: Vector3D = { x: 6771, y: 0, z: 0 };\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Minimal client interface this hook depends on. Compatible with the\n * `zendir-ts` `ZendirClient` surface but typed structurally so the hook\n * can be used with any compatible client (e.g. a test fake).\n */\nexport interface SimulationSceneClient {\n getSimulationStructure(\n simulationId: string,\n containerId?: string,\n opts?: { signal?: AbortSignal; retry?: unknown },\n ): Promise<Record<string, unknown>>;\n getProperties(\n objectId: string,\n properties: string | string[],\n containerId?: string,\n opts?: { signal?: AbortSignal; retry?: unknown },\n ): Promise<unknown>;\n getAllProperties(\n objectId: string,\n containerId?: string,\n opts?: { signal?: AbortSignal; retry?: unknown },\n ): Promise<Record<string, unknown>>;\n}\n\n/**\n * Per-tick retry config for the position-polling loop.\n *\n * The SDK defaults `getProperties` to `engineWarmup` (24 retries /\n * ~3.6 min). That budget is right for a one-shot mount call, but\n * disastrous for a 2-second polling tick — a single warmup blip would\n * stall every subsequent tick (skip-if-busy guard) for minutes, freezing\n * the viewer. We override with a tight 2-attempt budget that rides\n * through quick blips (~1.5 s total) but cedes to the next tick fast.\n */\nconst TICK_RETRY: {\n maxRetries: number;\n initialDelayMs: number;\n maxDelayMs: number;\n} = {\n maxRetries: 2,\n initialDelayMs: 500,\n maxDelayMs: 1500,\n};\n\nexport interface UseSimulationSceneOptions {\n /** ZendirClient (or any compatible client). Pass `null` to defer. */\n client: SimulationSceneClient | null;\n /** Container ID hosting the simulation. */\n containerId: string;\n /** Simulation ID to introspect. */\n simulationId: string;\n /** Polling interval in ms for spacecraft positions. Default: 2000. */\n pollIntervalMs?: number;\n /** Pause polling without unmounting. Default: true. */\n enabled?: boolean;\n}\n\nexport interface SimulationSceneObject {\n id: string;\n name: string;\n kind: \"spacecraft\" | \"groundStation\" | \"celestialBody\";\n}\n\n/**\n * Lightweight diagnostic snapshot of the raw `getSimulationStructure`\n * response — useful for debugging parsing mismatches in the UI without\n * shipping the full raw blob.\n */\nexport interface StructureDiag {\n /** Every top-level key the engine returned (e.g. \"Objects\",\"objects\",\"Time\"). */\n topLevelKeys: string[];\n /** Length of the `Objects` / `objects` array in the raw response (-1 if absent). */\n rawObjectCount: number;\n /** Length of the `Systems` / `systems` array in the raw response (-1 if absent). */\n rawSystemCount: number;\n}\n\n/**\n * Per-tick diagnostic — answers \"did the position fetch succeed and what came back?\".\n * Surfaced in the debug UI so a failed-but-silent tick (parser miss, engine\n * returning a different property name, all-zero positions, etc.) is visible.\n */\nexport interface PositionTickDiag {\n /** Wall-clock instant of the most recent tick attempt. */\n attemptedAt: Date;\n /** Number of spacecraft IDs the tick requested positions for. */\n requested: number;\n /** Number of position responses that parsed successfully. */\n parsedOk: number;\n /** Number of position responses that arrived but couldn't be parsed. */\n parseFailed: number;\n /** Number of position responses rejected by the SDK (network / 4xx / 5xx). */\n requestFailed: number;\n /**\n * The exact property names this tick asked the engine for. `null` means\n * the schema probe failed and the tick fell back to `getAllProperties`.\n * Surfaced so the debug UI can show which schema variant is in use.\n */\n requestedProps: string[] | null;\n /** All keys present on the first satellite's response (helps spot wrong property names). */\n firstResponseKeys: string[];\n /** Compact JSON of the first satellite's raw response (capped to 200 chars). */\n firstResponseSample: string;\n}\n\nexport interface UseSimulationSceneResult {\n /** Spacecraft with live ECI position/velocity (km, km/s). */\n satellites: Satellite[];\n /** Static ground stations (lat/lon). */\n groundStations: GroundStation[];\n /**\n * Celestial bodies (planets, moons, sun) with live ECI positions in km.\n * The simulation's central body is filtered out — Cesium renders it\n * natively as the focal globe. Pass directly to\n * `<ZenSpace3D customObjects={scene.celestialBodies} />`.\n */\n celestialBodies: CelestialBody[];\n /** Objects discovered from `getSimulationStructure` (for diagnostics/UI lists). */\n objects: SimulationSceneObject[];\n /** True until the first structure fetch resolves. */\n isLoading: boolean;\n /**\n * Set when the structure-fetch fails (polling stops). Per-tick\n * polling errors do NOT surface here — they retry silently to keep\n * the UI from flapping during transient network blips.\n */\n error: string | null;\n /** Re-run structure fetch and a position tick. */\n refresh: () => Promise<void>;\n /**\n * UTC `Date` representing wall-clock at the most recent successful\n * tick. Pass straight into `<ZenSpace3D referenceTime={...} />` so\n * ECI/J2000 positions rotate to ECEF at \"now\".\n */\n referenceDate: Date;\n /**\n * Diagnostic snapshot of the raw structure response — null until the\n * first fetch resolves. Use in debug panels to verify key casing and\n * array lengths returned by the engine.\n */\n structureDiag: StructureDiag | null;\n /**\n * Diagnostic for the most recent position-polling tick — null until\n * the first tick has run. Use in debug panels to verify that\n * `getProperties` is actually returning parsable position vectors.\n */\n positionTickDiag: PositionTickDiag | null;\n}\n\nconst DEFAULT_POLL_MS = 2000;\nconst M_TO_KM = 1 / 1000;\n\n// ============================================================================\n// Hook\n// ============================================================================\n\nexport function useSimulationScene(\n options: UseSimulationSceneOptions,\n): UseSimulationSceneResult {\n const {\n client,\n containerId,\n simulationId,\n pollIntervalMs = DEFAULT_POLL_MS,\n enabled = true,\n } = options;\n\n const [satellites, setSatellites] = useState<Satellite[]>([]);\n const [groundStations, setGroundStations] = useState<GroundStation[]>([]);\n const [celestialBodies, setCelestialBodies] = useState<CelestialBody[]>([]);\n const [objects, setObjects] = useState<SimulationSceneObject[]>([]);\n const [isLoading, setIsLoading] = useState<boolean>(true);\n const [error, setError] = useState<string | null>(null);\n const [referenceDate, setReferenceDate] = useState<Date>(() => new Date());\n const [structureDiag, setStructureDiag] = useState<StructureDiag | null>(\n null,\n );\n const [positionTickDiag, setPositionTickDiag] =\n useState<PositionTickDiag | null>(null);\n\n // Initial structure load on (client, cid, sid) change. Polling tick reads\n // positions for bucketed spacecraft; IDs live in a ref so the tick\n // callback stays stable across renders.\n const spacecraftIdsRef = useRef<string[]>([]);\n // Celestial-body IDs + names collected at structure-load time. The tick\n // polls each one's `Position` (note: NOT `Position_BN_N` — that's a\n // Spacecraft-only naming convention) plus `IsCentralBody` so we can\n // filter the focal body (Earth in Earth-centric sims) out before\n // emitting markers — Cesium renders the focal globe natively.\n const celestialBodyIdsRef = useRef<string[]>([]);\n const celestialBodyNamesRef = useRef<(string | undefined)[]>([]);\n const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);\n const abortRef = useRef<AbortController | null>(null);\n // Skip-if-busy flag: when a tick takes longer than the poll interval\n // (slow network), the next interval fires anyway. Without this guard\n // we'd stack overlapping in-flight requests indefinitely.\n const tickInFlightRef = useRef<boolean>(false);\n // Property-name schema detected from a one-time getAllProperties probe at\n // structure-load time. Different engine versions name spacecraft state\n // under different keys (`Position_BN_N` vs `Position`). The Zendir\n // `getProperties` endpoint returns 400 if ANY requested name is unknown,\n // so we cannot ask for \"either\" in a single call — we must know which\n // name this engine uses before the polling loop starts. `null` until\n // probed; `[]` if probe ran but no recognised key was found.\n const positionPropsRef = useRef<string[] | null>(null);\n\n // -------- Position polling tick (defined first so loadStructure can call it) --------\n\n const tickPositions = useCallback(\n async (signal: AbortSignal) => {\n if (!client) return;\n const ids = spacecraftIdsRef.current;\n if (ids.length === 0) return;\n // Skip-if-busy: never let two ticks overlap. The previous tick\n // will update state when it lands; the user sees fewer frames\n // but the engine isn't hammered with stacked requests.\n if (tickInFlightRef.current) return;\n tickInFlightRef.current = true;\n\n try {\n // The polling tick uses the property-name schema detected at\n // structure-load time. The Zendir `getProperties` endpoint 400s if\n // ANY requested name is unknown to the object, so we MUST know the\n // exact names before the loop starts — see `positionPropsRef`.\n //\n // Falls back to `getAllProperties` if the probe never resolved a\n // recognised position key (e.g. an exotic engine variant). That's\n // heavier per tick but bulletproof against unknown schemas.\n const propsToFetch = positionPropsRef.current;\n const useFallbackAll = !propsToFetch || propsToFetch.length === 0;\n\n // Fetch all spacecraft in parallel. A per-spacecraft failure is\n // settled (not thrown) so one bad object can't blank the frame —\n // we keep the previous position and retry on the next tick.\n const updates = await Promise.allSettled(\n ids.map((id) =>\n useFallbackAll\n ? // `retry: TICK_RETRY` overrides the SDK's engineWarmup default\n // for this one call. See the TICK_RETRY definition above for\n // the rationale (avoid a stuck tick stalling the viewer).\n client.getAllProperties(id, containerId, {\n signal,\n retry: TICK_RETRY,\n })\n : client.getProperties(id, propsToFetch!, containerId, {\n signal,\n retry: TICK_RETRY,\n }),\n ),\n );\n if (signal.aborted) return;\n\n // ---- Diagnostic snapshot ------------------------------------------\n // Captured BEFORE we update satellites so a parser bug doesn't hide\n // the raw response shape. Surfaced via `positionTickDiag` for the\n // debug UI; helps spot wrong property names / silent engine returns.\n let parsedOk = 0;\n let parseFailed = 0;\n let requestFailed = 0;\n let firstResponseKeys: string[] = [];\n let firstResponseSample = \"\";\n for (let i = 0; i < ids.length; i++) {\n const result = updates[i];\n if (result.status !== \"fulfilled\") {\n requestFailed += 1;\n continue;\n }\n if (i === 0) {\n const v = result.value as unknown;\n if (v && typeof v === \"object\")\n firstResponseKeys = Object.keys(v as Record<string, unknown>);\n try {\n const json = JSON.stringify(v);\n firstResponseSample =\n json.length > 200 ? `${json.slice(0, 200)}…` : json;\n } catch {\n firstResponseSample = String(v);\n }\n }\n if (parsePositionVelocity(result.value)) parsedOk += 1;\n else parseFailed += 1;\n }\n setPositionTickDiag({\n attemptedAt: new Date(),\n requested: ids.length,\n parsedOk,\n parseFailed,\n requestFailed,\n requestedProps: useFallbackAll ? null : propsToFetch,\n firstResponseKeys,\n firstResponseSample,\n });\n\n setSatellites((prev) => {\n if (prev.length === 0) return prev;\n const next = prev.slice();\n for (let i = 0; i < ids.length; i++) {\n const result = updates[i];\n if (result.status !== \"fulfilled\") continue;\n const idx = next.findIndex((sc) => sc.id === ids[i]);\n if (idx < 0) continue;\n const parsed = parsePositionVelocity(result.value);\n if (!parsed) continue;\n next[idx] = {\n ...next[idx],\n position: parsed.position,\n velocity: parsed.velocity,\n };\n }\n return next;\n });\n\n // ---- Celestial-body positions (independent of spacecraft tick) ----\n // Per Zendir API: CelestialBody exposes `Position` (m, ECI) and\n // `IsCentralBody` (bool). Filter the central body — it's the focal\n // globe Cesium already renders. All others become custom-object\n // markers in the viewer.\n const celIds = celestialBodyIdsRef.current;\n if (celIds.length > 0) {\n const celResults = await Promise.allSettled(\n celIds.map((id) =>\n client.getProperties(\n id,\n [\"Position\", \"IsCentralBody\"],\n containerId,\n {\n signal,\n retry: TICK_RETRY,\n },\n ),\n ),\n );\n if (signal.aborted) return;\n\n const next: CelestialBody[] = [];\n for (let i = 0; i < celIds.length; i++) {\n const result = celResults[i];\n if (result.status !== \"fulfilled\") continue;\n const raw = result.value as Record<string, unknown> | unknown;\n const obj =\n raw && typeof raw === \"object\"\n ? (raw as Record<string, unknown>)\n : null;\n if (!obj) continue;\n if (obj.IsCentralBody === true) continue; // focal globe — skip\n const posVec = parseVec3(obj.Position);\n if (!posVec) continue;\n // Engine returns metres; convert to km to match Satellite shape.\n const positionKm = scaleVec(posVec, M_TO_KM);\n const id = celIds[i];\n const name =\n celestialBodyNamesRef.current[i] ??\n `CelestialBody ${shortId(id)}`;\n next.push({ id, name, position: positionKm });\n }\n setCelestialBodies(next);\n }\n } catch {\n // Whole-tick failure (e.g. AbortError, transport blowup). The\n // outer `enabled` effect already swallows so the UI stays put.\n } finally {\n tickInFlightRef.current = false;\n if (!signal.aborted) {\n // Wall-clock reference instant — drives ECI→ECEF rotation in\n // the viewer. Bumped per tick so Cesium re-derives the\n // pseudo-fixed transform at \"now\".\n setReferenceDate(new Date());\n }\n }\n },\n [client, containerId],\n );\n\n // -------- Initial structure load + ground station resolution --------\n\n const loadStructure = useCallback(\n async (signal: AbortSignal) => {\n if (!client) return;\n setIsLoading(true);\n setError(null);\n\n try {\n const structure = await client.getSimulationStructure(\n simulationId,\n containerId,\n { signal },\n );\n if (signal.aborted) return;\n\n // Capture a lightweight diagnostic before any parsing so the UI can\n // show exactly what the engine returned (key names, array lengths).\n // This is the most common source of \"0 objects\" bugs — key-casing mismatch.\n setStructureDiag({\n topLevelKeys: Object.keys(structure),\n rawObjectCount: Array.isArray(structure.Objects)\n ? structure.Objects.length\n : Array.isArray((structure as Record<string, unknown>).objects)\n ? ((structure as Record<string, unknown>).objects as unknown[])\n .length\n : -1,\n rawSystemCount: Array.isArray(structure.Systems)\n ? structure.Systems.length\n : Array.isArray((structure as Record<string, unknown>).systems)\n ? ((structure as Record<string, unknown>).systems as unknown[])\n .length\n : -1,\n });\n\n const buckets = bucketStructure(structure);\n spacecraftIdsRef.current = buckets.spacecraft.map((s) => s.id);\n celestialBodyIdsRef.current = buckets.celestialBodyIds;\n celestialBodyNamesRef.current = buckets.celestialBodyNames;\n setSatellites(buckets.spacecraft);\n setObjects(buckets.objects);\n\n // ---- One-time property-name schema probe -------------------------\n // The Zendir `/get` endpoint 400s if any requested property name is\n // unknown to the object, so we cannot blindly request both legacy\n // (`Position_BN_N`) and short-form (`Position`) names in one tick.\n // Probe the first spacecraft once via `getAllProperties` (which is\n // schema-agnostic) and detect which subset of recognised names it\n // exposes. Cached in a ref for every subsequent tick.\n if (buckets.spacecraft.length > 0) {\n try {\n const probeId = buckets.spacecraft[0].id;\n const all = await client.getAllProperties(probeId, containerId, {\n signal,\n });\n if (signal.aborted) return;\n positionPropsRef.current = detectPositionPropertyNames(all);\n } catch {\n // Probe failure shouldn't block the rest of the load — leave\n // positionPropsRef as null and the tick will fall back to\n // getAllProperties per spacecraft.\n positionPropsRef.current = null;\n }\n } else {\n positionPropsRef.current = null;\n }\n\n // Ground stations are static — resolve their lat/lon once per structure\n // fetch. Parallel fetch with per-station settle so one bad station\n // can't block the whole render.\n const stationResults = await Promise.allSettled(\n buckets.groundStationIds.map((id) =>\n client.getAllProperties(id, containerId, { signal }),\n ),\n );\n if (signal.aborted) return;\n const stations: GroundStation[] = [];\n for (let i = 0; i < buckets.groundStationIds.length; i++) {\n const result = stationResults[i];\n const id = buckets.groundStationIds[i];\n const fallbackName = buckets.groundStationNames[i] ?? shortId(id);\n if (result.status !== \"fulfilled\") {\n stations.push({\n id,\n name: fallbackName,\n latitude: 0,\n longitude: 0,\n });\n continue;\n }\n stations.push(toGroundStation(id, fallbackName, result.value));\n }\n setGroundStations(stations);\n\n setIsLoading(false);\n\n // Kick off the first position tick immediately so consumers see\n // live positions without waiting a full poll interval.\n if (spacecraftIdsRef.current.length > 0) {\n await tickPositions(signal);\n }\n } catch (err) {\n if (signal.aborted) return;\n setError(\n err instanceof Error\n ? err.message\n : \"Failed to load simulation structure\",\n );\n setIsLoading(false);\n }\n },\n [client, containerId, simulationId, tickPositions],\n );\n\n // -------- Lifecycle: load on mount/id-change, poll while enabled --------\n\n useEffect(() => {\n if (!client || !containerId || !simulationId) {\n setIsLoading(false);\n return;\n }\n\n const controller = new AbortController();\n abortRef.current = controller;\n void loadStructure(controller.signal);\n\n return () => {\n controller.abort();\n if (abortRef.current === controller) abortRef.current = null;\n };\n }, [client, containerId, simulationId, loadStructure]);\n\n useEffect(() => {\n if (!enabled || !client || error) {\n // No polling when disabled or after a fatal structure-fetch error.\n // The structure-load effect above will still re-fire on id change.\n if (intervalRef.current) {\n clearInterval(intervalRef.current);\n intervalRef.current = null;\n }\n return;\n }\n\n const interval = setInterval(() => {\n const controller = new AbortController();\n // Tick errors are intentionally swallowed — see tickPositions for\n // the per-spacecraft settle policy. A network blip should not\n // crash the viewer; it should just leave the previous frame\n // visible until the next tick succeeds.\n void tickPositions(controller.signal).catch(() => undefined);\n }, pollIntervalMs);\n intervalRef.current = interval;\n\n return () => {\n clearInterval(interval);\n if (intervalRef.current === interval) intervalRef.current = null;\n };\n }, [client, enabled, error, pollIntervalMs, tickPositions]);\n\n const refresh = useCallback(async () => {\n const controller = new AbortController();\n await loadStructure(controller.signal);\n }, [loadStructure]);\n\n return {\n satellites,\n groundStations,\n celestialBodies,\n objects,\n isLoading,\n error,\n refresh,\n referenceDate,\n structureDiag,\n positionTickDiag,\n };\n}\n\nexport default useSimulationScene;\n\n// ============================================================================\n// Structure walker\n// ============================================================================\n\ninterface BucketedStructure {\n spacecraft: Satellite[];\n groundStationIds: string[];\n groundStationNames: (string | undefined)[];\n /**\n * Celestial-body IDs collected from `Type === 'CelestialBody'` nodes. Used by\n * the per-tick position poll. Names are looked up from `objects` in the\n * upstream consumer; we only need IDs here.\n */\n celestialBodyIds: string[];\n celestialBodyNames: (string | undefined)[];\n objects: SimulationSceneObject[];\n}\n\n/**\n * Celestial body with the position polled from the engine's `Position`\n * property (in km, ECI), suitable to feed straight into\n * `<ZenSpace3D customObjects={...} />`. The simulation's central body\n * (e.g. Earth in Earth-centric sims) is filtered out — Cesium renders\n * the focal globe natively, so emitting it as a marker would collide.\n */\nexport interface CelestialBody {\n id: string;\n name: string;\n /** ECI position in km. */\n position: { x: number; y: number; z: number };\n}\n\n/**\n * First non-empty string among PascalCase / camelCase keys for one logical field.\n */\nfunction firstStringField(\n obj: Record<string, unknown>,\n keys: readonly string[],\n): string | undefined {\n for (const key of keys) {\n const v = obj[key];\n if (typeof v === \"string\" && v.length > 0) return v;\n }\n return undefined;\n}\n\n/**\n * Walk a `GetSimulationStructure` response and bucket objects by Type.\n *\n * Tolerant of:\n * - PascalCase (`Objects`, `Systems`, `Children`) and camelCase\n * (`objects`, `systems`, `children`) — the engine has shipped both.\n * - Objects nested at the top level, inside Systems children, or under\n * any depth of `Children` / `children` arrays.\n */\nfunction bucketStructure(\n structure: Record<string, unknown>,\n): BucketedStructure {\n const out: BucketedStructure = {\n spacecraft: [],\n groundStationIds: [],\n groundStationNames: [],\n celestialBodyIds: [],\n celestialBodyNames: [],\n objects: [],\n };\n\n // Accept both PascalCase and camelCase for the top-level array keys.\n const topObjects = pickArray(structure, [\"Objects\", \"objects\"]);\n for (const obj of topObjects) walkObject(obj, out);\n\n // Some engine builds put spacecraft / ground stations inside a System\n // (e.g. ExtensionSystem). Walk every system entry so their children\n // are discovered regardless of nesting depth.\n const topSystems = pickArray(structure, [\"Systems\", \"systems\"]);\n for (const sys of topSystems) walkObject(sys, out);\n\n return out;\n}\n\n/**\n * Return the first non-empty array found under any of `keys` on `obj`.\n * Allows the caller to tolerate PascalCase / camelCase top-level array keys\n * without duplicating logic at every call site.\n */\nfunction pickArray(obj: Record<string, unknown>, keys: string[]): unknown[] {\n for (const key of keys) {\n const v = obj[key];\n if (Array.isArray(v) && v.length > 0) return v;\n }\n // If none has items, return the first non-null array (even if empty) so\n // the caller still walks it.\n for (const key of keys) {\n const v = obj[key];\n if (Array.isArray(v)) return v;\n }\n return [];\n}\n\nfunction walkObject(node: unknown, out: BucketedStructure): void {\n if (!node || typeof node !== \"object\") return;\n const obj = node as Record<string, unknown>;\n const id = firstStringField(obj, [\"ID\", \"id\", \"Id\"]);\n const type = firstStringField(obj, [\"Type\", \"type\"]);\n const name = firstStringField(obj, [\"Name\", \"name\"]);\n\n if (id && type) {\n if (type === \"Spacecraft\" || type.endsWith(\".Spacecraft\")) {\n upsertObjectSummary(out, {\n id,\n name: name ?? `Spacecraft ${shortId(id)}`,\n kind: \"spacecraft\",\n });\n out.spacecraft.push({\n id,\n name: name ?? `Spacecraft ${shortId(id)}`,\n category: \"satellite\",\n position: { ...PLACEHOLDER_SCENECRAFT_POSITION },\n visualRadius: 50,\n });\n } else if (type === \"GroundStation\" || type.endsWith(\".GroundStation\")) {\n upsertObjectSummary(out, {\n id,\n name: name ?? `GroundStation ${shortId(id)}`,\n kind: \"groundStation\",\n });\n out.groundStationIds.push(id);\n out.groundStationNames.push(name);\n } else if (type === \"CelestialBody\" || type.endsWith(\".CelestialBody\")) {\n upsertObjectSummary(out, {\n id,\n name: name ?? `CelestialBody ${shortId(id)}`,\n kind: \"celestialBody\",\n });\n out.celestialBodyIds.push(id);\n out.celestialBodyNames.push(name);\n }\n }\n\n // Recurse into children regardless of parent type so a Spacecraft\n // nested under a system is still discovered. Accept both PascalCase\n // and camelCase — the engine has shipped both.\n const children = pickArray(obj, [\"Children\", \"children\"]);\n for (const child of children) walkObject(child, out);\n}\n\nfunction upsertObjectSummary(\n out: BucketedStructure,\n next: SimulationSceneObject,\n): void {\n const idx = out.objects.findIndex((o) => o.id === next.id);\n if (idx >= 0) {\n out.objects[idx] = next;\n return;\n }\n out.objects.push(next);\n}\n\n// ============================================================================\n// Schema detection\n// ============================================================================\n\n/**\n * Inspect a `getAllProperties` response and return the position property name\n * this engine exposes on a spacecraft.\n *\n * Only position is probed — velocity is intentionally excluded. The engine's\n * `/get` endpoint validates names more strictly than `getAllProperties`, and\n * `Velocity_BN_N` exists in the probe response but triggers a 400 when passed\n * to `getProperties`. Since velocity is optional (only position is used for\n * rendering), we never request it in the polling tick.\n *\n * Returns at most one name — the first position candidate whose value parses\n * as a 3-vector in the probe response.\n */\nfunction detectPositionPropertyNames(props: Record<string, unknown>): string[] {\n const positionCandidates = [\"Position_BN_N\", \"Position\", \"r_BN_N\", \"r_N\"];\n\n for (const name of positionCandidates) {\n if (name in props && parseVec3(props[name]) !== null) {\n return [name];\n }\n }\n return [];\n}\n\n// ============================================================================\n// Property parsing\n// ============================================================================\n\n/**\n * Engines have shipped two property-naming conventions for spacecraft state:\n *\n * - Basilisk-style ECI: `Position_BN_N`, `Velocity_BN_N` (m, m/s)\n * - Shorter form: `Position`, `Velocity` (m, m/s)\n *\n * Both are in metres. ZenSpace3D's `Vector3D` is documented as km / km/s,\n * so we scale by 1e-3. The first key that produces a parseable 3-vector wins.\n */\nfunction parsePositionVelocity(\n raw: unknown,\n): { position: Vector3D; velocity?: Vector3D } | null {\n if (!raw || typeof raw !== \"object\") return null;\n const obj = raw as Record<string, unknown>;\n const position =\n parseVec3(obj.Position_BN_N) ??\n parseVec3(obj.Position) ??\n parseVec3((obj as { position?: unknown }).position);\n if (!position) return null;\n // Engine returns [0,0,0] for un-initialised state (sim hasn't been ticked\n // / invoked yet). Treat that as \"no data\" so the upstream tick keeps the\n // previous (or placeholder) position rather than dropping the satellite\n // to Earth's centre — which crashes Cesium's Cartesian3.normalize on the\n // first render frame.\n if (position.x === 0 && position.y === 0 && position.z === 0) return null;\n const velocity =\n parseVec3(obj.Velocity_BN_N) ??\n parseVec3(obj.Velocity) ??\n parseVec3((obj as { velocity?: unknown }).velocity);\n return {\n position: scaleVec(position, M_TO_KM),\n velocity: velocity ? scaleVec(velocity, M_TO_KM) : undefined,\n };\n}\n\n/**\n * Accepts the engine's two known shapes for a 3-vector property:\n * either a `[x, y, z]` array or an object with `X/Y/Z` keys (the\n * gateway's JSON normalizer flips between the two depending on how\n * the property is declared in the model).\n */\nfunction parseVec3(raw: unknown): Vector3D | null {\n if (Array.isArray(raw) && raw.length >= 3) {\n const [x, y, z] = raw;\n if (\n typeof x === \"number\" &&\n typeof y === \"number\" &&\n typeof z === \"number\"\n ) {\n return { x, y, z };\n }\n }\n if (raw && typeof raw === \"object\") {\n const obj = raw as Record<string, unknown>;\n const x = obj.X ?? obj.x;\n const y = obj.Y ?? obj.y;\n const z = obj.Z ?? obj.z;\n if (\n typeof x === \"number\" &&\n typeof y === \"number\" &&\n typeof z === \"number\"\n ) {\n return { x, y, z };\n }\n }\n return null;\n}\n\nfunction scaleVec(v: Vector3D, k: number): Vector3D {\n return { x: v.x * k, y: v.y * k, z: v.z * k };\n}\n\n// ============================================================================\n// Ground station construction\n// ============================================================================\n\nfunction toGroundStation(\n id: string,\n fallbackName: string,\n props: Record<string, unknown>,\n): GroundStation {\n const name = firstStringField(props, [\"Name\"]) ?? fallbackName;\n const latitude = pickNumber(props, [\"Latitude\", \"latitude\"]) ?? 0;\n const longitude = pickNumber(props, [\"Longitude\", \"longitude\"]) ?? 0;\n const elevation = pickNumber(props, [\"Altitude\", \"altitude\", \"Elevation\"]);\n const minElevation = pickNumber(props, [\"MinElevation\", \"MinimumElevation\"]);\n const station: GroundStation = { id, name, latitude, longitude };\n if (elevation !== undefined) station.elevation = elevation;\n if (minElevation !== undefined) station.minElevation = minElevation;\n return station;\n}\n\nfunction pickNumber(\n obj: Record<string, unknown>,\n keys: string[],\n): number | undefined {\n for (const key of keys) {\n const v = obj[key];\n if (typeof v === \"number\" && Number.isFinite(v)) return v;\n }\n return undefined;\n}\n\nfunction shortId(id: string): string {\n return id.length > 8 ? id.slice(0, 8) : id;\n}\n"],"mappings":"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"}
@@ -24,7 +24,7 @@ export type { ThemeProviderProps, ThemeContextValue, ThemeVariant, ThemeMode, Th
24
24
  export { DisplaySettingsProvider, useDisplaySettings, useDisplaySettingsOptional, getEffectiveCompactMode, PRESET_COLORS, GLASS_TINTS, } from './context';
25
25
  export type { DisplaySettings, DisplaySettingsContextValue, DisplaySettingsProviderProps, ColorTarget, PresetColorKey, GlassTintKey, } from './context';
26
26
  export { CategoryProvider, useCategoryPalette, useCategoryPaletteRequired, } from './context';
27
- export type { CategoryContextValue, CategoryProviderProps, } from './context';
27
+ export type { CategoryContextValue, CategoryProviderProps } from './context';
28
28
  export { ErrorBoundary, withErrorBoundary } from './shared/ErrorBoundary';
29
29
  export type { ErrorBoundaryProps } from './shared/ErrorBoundary';
30
30
  export { SkeletonText, SkeletonBox, SkeletonCircle, SkeletonCard, SkeletonChart, Skeleton3D, } from './shared/Skeleton';
@@ -36,9 +36,9 @@ export type { StatusIndicatorProps, StatusVariant, ClassificationBannerProps, Cl
36
36
  export { AstroChart } from './charts';
37
37
  export type { AstroChartProps, AstroChartHandle } from './charts';
38
38
  export { GroundTrackMap } from './charts';
39
- export type { GroundTrackMapProps, MapPin, LightSource, MapLayerDef } from './charts';
39
+ export type { GroundTrackMapProps, MapPin, LightSource, MapLayerDef, } from './charts';
40
40
  export { useCompactMode } from './hooks/useCompactMode';
41
- export type { UseCompactModeOptions, UseCompactModeResult } from './hooks/useCompactMode';
41
+ export type { UseCompactModeOptions, UseCompactModeResult, } from './hooks/useCompactMode';
42
42
  export { ObjectInventoryPanel, LayerControlPanel, recommendedFlyDistance, } from './panels';
43
43
  export type { ObjectInventoryPanelProps, ObjectInventoryKind, LayerControlPanelProps, RenderLayers, GroupVisibility, GroupCounts, } from './panels';
44
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';
@@ -1,6 +1,5 @@
1
1
  import { CSSProperties } from 'react';
2
2
  import { LayerDef } from '../types';
3
-
4
3
  /** Render-flag layers — passed straight through to `<ZenSpace3D layers={...} />`. */
5
4
  export interface RenderLayers {
6
5
  labels?: boolean;
@@ -36,7 +35,7 @@ export interface LayerControlPanelProps {
36
35
  customLayerState?: Record<string, boolean>;
37
36
  /** Fires when a custom layer is toggled. The consumer renders the overlay itself. */
38
37
  onCustomLayerChange?: (id: string, enabled: boolean) => void;
39
- position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
38
+ position?: "top-left" | "top-right" | "bottom-left" | "bottom-right";
40
39
  offset?: {
41
40
  x?: number;
42
41
  y?: number;