@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.
- package/CHANGELOG.md +20 -0
- package/README.md +640 -631
- package/dist/_virtual/_rolldown/runtime.js +13 -0
- package/dist/index.js +17 -221
- package/dist/react/3d/CesiumCaptureSource.d.ts +0 -1
- package/dist/react/3d/CesiumCaptureSource.js +364 -283
- package/dist/react/3d/CesiumCaptureSource.js.map +1 -1
- package/dist/react/3d/ZenSpace3D.d.ts +2 -3
- package/dist/react/3d/ZenSpace3D.js +1681 -1237
- package/dist/react/3d/ZenSpace3D.js.map +1 -1
- package/dist/react/3d/ZenSpace3DCesium.d.ts +0 -1
- package/dist/react/3d/ZenSpace3DCesium.js +513 -560
- package/dist/react/3d/ZenSpace3DCesium.js.map +1 -1
- package/dist/react/3d/ZenSpace3DShaders.js +61 -66
- package/dist/react/3d/ZenSpace3DShaders.js.map +1 -1
- package/dist/react/3d/ZenSpace3DTypes.d.ts +19 -20
- package/dist/react/3d/ZenSpace3DUtils.d.ts +0 -1
- package/dist/react/3d/ZenSpace3DUtils.js +37 -24
- package/dist/react/3d/ZenSpace3DUtils.js.map +1 -1
- package/dist/react/3d/capturePngAnalysis.js +42 -36
- package/dist/react/3d/capturePngAnalysis.js.map +1 -1
- package/dist/react/3d/index.d.ts +3 -3
- package/dist/react/3d/index.js +5 -53
- package/dist/react/3d/threeLoader.d.ts +1 -1
- package/dist/react/3d/threeLoader.js +23 -18
- package/dist/react/3d/threeLoader.js.map +1 -1
- package/dist/react/astro/ClassificationBanner.d.ts +2 -3
- package/dist/react/astro/ClassificationBanner.js +95 -79
- package/dist/react/astro/ClassificationBanner.js.map +1 -1
- package/dist/react/astro/GlobalStatusBar.d.ts +0 -1
- package/dist/react/astro/GlobalStatusBar.js +157 -163
- package/dist/react/astro/GlobalStatusBar.js.map +1 -1
- package/dist/react/astro/MissionClock.d.ts +7 -8
- package/dist/react/astro/MissionClock.js +377 -400
- package/dist/react/astro/MissionClock.js.map +1 -1
- package/dist/react/astro/MonitoringIcon.d.ts +1 -2
- package/dist/react/astro/MonitoringIcon.js +422 -358
- package/dist/react/astro/MonitoringIcon.js.map +1 -1
- package/dist/react/astro/Notification.d.ts +1 -2
- package/dist/react/astro/Notification.js +229 -126
- package/dist/react/astro/Notification.js.map +1 -1
- package/dist/react/astro/Progress.d.ts +0 -1
- package/dist/react/astro/Progress.js +199 -141
- package/dist/react/astro/Progress.js.map +1 -1
- package/dist/react/astro/SimulationControls.d.ts +3 -4
- package/dist/react/astro/SimulationControls.js +596 -650
- package/dist/react/astro/SimulationControls.js.map +1 -1
- package/dist/react/astro/StatusIndicator.d.ts +0 -1
- package/dist/react/astro/StatusIndicator.js +236 -170
- package/dist/react/astro/StatusIndicator.js.map +1 -1
- package/dist/react/astro/UnifiedTimeline.d.ts +9 -10
- package/dist/react/astro/UnifiedTimeline.js +2345 -2392
- package/dist/react/astro/UnifiedTimeline.js.map +1 -1
- package/dist/react/astro/index.d.ts +5 -5
- package/dist/react/astro/index.js +1 -25
- package/dist/react/cards/AccessCard.d.ts +0 -1
- package/dist/react/cards/AccessCard.js +471 -405
- package/dist/react/cards/AccessCard.js.map +1 -1
- package/dist/react/cards/OrbitCard.d.ts +0 -1
- package/dist/react/cards/OrbitCard.js +487 -364
- package/dist/react/cards/OrbitCard.js.map +1 -1
- package/dist/react/cards/SpacecraftCard.d.ts +0 -1
- package/dist/react/cards/SpacecraftCard.js +813 -925
- package/dist/react/cards/SpacecraftCard.js.map +1 -1
- package/dist/react/cards/TelemetryCard.d.ts +0 -1
- package/dist/react/cards/TelemetryCard.js +1055 -732
- package/dist/react/cards/TelemetryCard.js.map +1 -1
- package/dist/react/cards/TelemetryStreamCard.d.ts +0 -1
- package/dist/react/cards/TelemetryStreamCard.js +323 -297
- package/dist/react/cards/TelemetryStreamCard.js.map +1 -1
- package/dist/react/cards/index.d.ts +1 -1
- package/dist/react/cards/index.js +1 -8
- package/dist/react/charts/GroundTrackMap.d.ts +6 -7
- package/dist/react/charts/GroundTrackMap.js +1377 -1256
- package/dist/react/charts/GroundTrackMap.js.map +1 -1
- package/dist/react/charts/GroundTrackMapLeaflet.d.ts +1 -2
- package/dist/react/charts/GroundTrackMapLeaflet.js +1065 -987
- package/dist/react/charts/GroundTrackMapLeaflet.js.map +1 -1
- package/dist/react/charts/groundTrackMapLeafletTiles.js +34 -25
- package/dist/react/charts/groundTrackMapLeafletTiles.js.map +1 -1
- package/dist/react/charts/groundTrackMapLeafletUtils.js +70 -61
- package/dist/react/charts/groundTrackMapLeafletUtils.js.map +1 -1
- package/dist/react/charts/index.d.ts +1 -1
- package/dist/react/charts/index.js +7 -100
- package/dist/react/charts/unified/AstroChart.d.ts +1 -2
- package/dist/react/charts/unified/AstroChart.js +1331 -1411
- package/dist/react/charts/unified/AstroChart.js.map +1 -1
- package/dist/react/charts/unified/PowerOverviewChart.d.ts +3 -3
- package/dist/react/charts/unified/PowerOverviewChart.js +495 -478
- package/dist/react/charts/unified/PowerOverviewChart.js.map +1 -1
- package/dist/react/charts/unified/domain.d.ts +12 -13
- package/dist/react/charts/unified/domain.js +3999 -3145
- package/dist/react/charts/unified/domain.js.map +1 -1
- package/dist/react/charts/unified/generators.d.ts +3 -4
- package/dist/react/charts/unified/generators.js +561 -498
- package/dist/react/charts/unified/generators.js.map +1 -1
- package/dist/react/charts/unified/index.d.ts +1 -1
- package/dist/react/charts/unified/presets.d.ts +14 -15
- package/dist/react/charts/unified/presets.js +857 -994
- package/dist/react/charts/unified/presets.js.map +1 -1
- package/dist/react/charts/unified/sync.d.ts +2 -3
- package/dist/react/charts/unified/sync.js +229 -195
- package/dist/react/charts/unified/sync.js.map +1 -1
- package/dist/react/charts/unified/theme.d.ts +11 -12
- package/dist/react/charts/unified/theme.js +606 -642
- package/dist/react/charts/unified/theme.js.map +1 -1
- package/dist/react/charts/unified/types.d.ts +32 -33
- package/dist/react/charts/unified/useChartStream.d.ts +1 -2
- package/dist/react/charts/unified/useChartStream.js +203 -216
- package/dist/react/charts/unified/useChartStream.js.map +1 -1
- package/dist/react/chatgpt/AppCard.d.ts +1 -2
- package/dist/react/chatgpt/index.d.ts +7 -7
- package/dist/react/chatgpt/index.js +160 -139
- package/dist/react/chatgpt/index.js.map +1 -1
- package/dist/react/context/CategoryContext.d.ts +0 -1
- package/dist/react/context/CategoryContext.js +78 -29
- package/dist/react/context/CategoryContext.js.map +1 -1
- package/dist/react/context/DisplaySettingsContext.d.ts +1 -2
- package/dist/react/context/DisplaySettingsContext.js +205 -156
- package/dist/react/context/DisplaySettingsContext.js.map +1 -1
- package/dist/react/context/SpatialSelectionContext.d.ts +0 -1
- package/dist/react/context/SpatialSelectionContext.js +36 -7
- package/dist/react/context/SpatialSelectionContext.js.map +1 -1
- package/dist/react/core/data/DataTable.d.ts +3 -4
- package/dist/react/core/data/DataTable.js +473 -440
- package/dist/react/core/data/DataTable.js.map +1 -1
- package/dist/react/core/data/DataValue.d.ts +4 -5
- package/dist/react/core/data/DataValue.js +605 -571
- package/dist/react/core/data/DataValue.js.map +1 -1
- package/dist/react/core/data/propertyConfig.d.ts +1 -2
- package/dist/react/core/data/propertyConfig.js +446 -382
- package/dist/react/core/data/propertyConfig.js.map +1 -1
- package/dist/react/core/display/AstroIcon.d.ts +4 -5
- package/dist/react/core/display/AstroIcon.js +339 -429
- package/dist/react/core/display/AstroIcon.js.map +1 -1
- package/dist/react/core/display/Badge.d.ts +2 -3
- package/dist/react/core/display/Badge.js +131 -131
- package/dist/react/core/display/Badge.js.map +1 -1
- package/dist/react/core/display/CardHeader.d.ts +0 -1
- package/dist/react/core/display/CardHeader.js +398 -311
- package/dist/react/core/display/CardHeader.js.map +1 -1
- package/dist/react/core/display/Container.d.ts +2 -3
- package/dist/react/core/display/Container.js +640 -668
- package/dist/react/core/display/Container.js.map +1 -1
- package/dist/react/core/display/CopyButton.d.ts +1 -2
- package/dist/react/core/display/CopyButton.js +158 -101
- package/dist/react/core/display/CopyButton.js.map +1 -1
- package/dist/react/core/display/GlassCard.d.ts +3 -4
- package/dist/react/core/display/GlassCard.js +416 -369
- package/dist/react/core/display/GlassCard.js.map +1 -1
- package/dist/react/core/display/HeaderIconWithStatus.d.ts +0 -1
- package/dist/react/core/display/HeaderIconWithStatus.js +152 -152
- package/dist/react/core/display/HeaderIconWithStatus.js.map +1 -1
- package/dist/react/core/display/Icon.d.ts +2 -3
- package/dist/react/core/display/Icon.js +172 -143
- package/dist/react/core/display/Icon.js.map +1 -1
- package/dist/react/core/display/Typography.d.ts +24 -25
- package/dist/react/core/display/Typography.js +360 -178
- package/dist/react/core/display/Typography.js.map +1 -1
- package/dist/react/core/feedback/ConfirmDialog.d.ts +1 -2
- package/dist/react/core/feedback/ConfirmDialog.js +343 -304
- package/dist/react/core/feedback/ConfirmDialog.js.map +1 -1
- package/dist/react/core/feedback/Dialog.d.ts +2 -3
- package/dist/react/core/feedback/Dialog.js +189 -197
- package/dist/react/core/feedback/Dialog.js.map +1 -1
- package/dist/react/core/feedback/Toast.d.ts +2 -3
- package/dist/react/core/feedback/Toast.js +346 -207
- package/dist/react/core/feedback/Toast.js.map +1 -1
- package/dist/react/core/index.d.ts +26 -26
- package/dist/react/core/index.js +7 -119
- package/dist/react/core/inputs/Button.d.ts +2 -3
- package/dist/react/core/inputs/Button.js +281 -304
- package/dist/react/core/inputs/Button.js.map +1 -1
- package/dist/react/core/inputs/Checkbox.d.ts +2 -3
- package/dist/react/core/inputs/Checkbox.js +139 -127
- package/dist/react/core/inputs/Checkbox.js.map +1 -1
- package/dist/react/core/inputs/Input.d.ts +3 -4
- package/dist/react/core/inputs/Input.js +277 -285
- package/dist/react/core/inputs/Input.js.map +1 -1
- package/dist/react/core/inputs/LimitsBar.d.ts +1 -2
- package/dist/react/core/inputs/LimitsBar.js +271 -196
- package/dist/react/core/inputs/LimitsBar.js.map +1 -1
- package/dist/react/core/inputs/NumberInput.d.ts +2 -3
- package/dist/react/core/inputs/NumberInput.js +531 -503
- package/dist/react/core/inputs/NumberInput.js.map +1 -1
- package/dist/react/core/inputs/PinInput.d.ts +2 -3
- package/dist/react/core/inputs/PinInput.js +224 -207
- package/dist/react/core/inputs/PinInput.js.map +1 -1
- package/dist/react/core/inputs/Select.d.ts +1 -2
- package/dist/react/core/inputs/Select.js +298 -299
- package/dist/react/core/inputs/Select.js.map +1 -1
- package/dist/react/core/inputs/Toggle.d.ts +2 -3
- package/dist/react/core/inputs/Toggle.js +142 -148
- package/dist/react/core/inputs/Toggle.js.map +1 -1
- package/dist/react/core/layout/Box.d.ts +3 -4
- package/dist/react/core/layout/Box.js +118 -122
- package/dist/react/core/layout/Box.js.map +1 -1
- package/dist/react/core/layout/Center.d.ts +0 -1
- package/dist/react/core/layout/Center.js +45 -32
- package/dist/react/core/layout/Center.js.map +1 -1
- package/dist/react/core/layout/Divider.d.ts +1 -2
- package/dist/react/core/layout/Divider.js +112 -106
- package/dist/react/core/layout/Divider.js.map +1 -1
- package/dist/react/core/layout/Flex.d.ts +4 -5
- package/dist/react/core/layout/Flex.js +120 -124
- package/dist/react/core/layout/Flex.js.map +1 -1
- package/dist/react/core/layout/Grid.d.ts +5 -6
- package/dist/react/core/layout/Grid.js +131 -138
- package/dist/react/core/layout/Grid.js.map +1 -1
- package/dist/react/core/layout/Spacer.d.ts +0 -1
- package/dist/react/core/layout/Spacer.js +36 -29
- package/dist/react/core/layout/Spacer.js.map +1 -1
- package/dist/react/core/layout/Stack.d.ts +4 -5
- package/dist/react/core/layout/Stack.js +127 -70
- package/dist/react/core/layout/Stack.js.map +1 -1
- package/dist/react/core/layout/responsive.d.ts +1 -2
- package/dist/react/core/layout/responsive.js +25 -19
- package/dist/react/core/layout/responsive.js.map +1 -1
- package/dist/react/core/layout/useBreakpoint.d.ts +1 -1
- package/dist/react/core/layout/useBreakpoint.js +119 -66
- package/dist/react/core/layout/useBreakpoint.js.map +1 -1
- package/dist/react/core/navigation/AppBar.d.ts +1 -2
- package/dist/react/core/navigation/AppBar.js +827 -805
- package/dist/react/core/navigation/AppBar.js.map +1 -1
- package/dist/react/core/navigation/Pagination.d.ts +2 -3
- package/dist/react/core/navigation/Pagination.js +207 -187
- package/dist/react/core/navigation/Pagination.js.map +1 -1
- package/dist/react/core/navigation/SideNav.d.ts +5 -6
- package/dist/react/core/navigation/SideNav.js +855 -629
- package/dist/react/core/navigation/SideNav.js.map +1 -1
- package/dist/react/core/navigation/Tabs.d.ts +0 -1
- package/dist/react/core/navigation/Tabs.js +113 -119
- package/dist/react/core/navigation/Tabs.js.map +1 -1
- package/dist/react/core/overlays/Popover.d.ts +2 -3
- package/dist/react/core/overlays/Popover.js +303 -282
- package/dist/react/core/overlays/Popover.js.map +1 -1
- package/dist/react/core/overlays/SidePanel.d.ts +1 -2
- package/dist/react/core/overlays/SidePanel.js +217 -212
- package/dist/react/core/overlays/SidePanel.js.map +1 -1
- package/dist/react/core/overlays/Tooltip.d.ts +1 -2
- package/dist/react/core/overlays/Tooltip.js +170 -176
- package/dist/react/core/overlays/Tooltip.js.map +1 -1
- package/dist/react/core/widgets/ActivityPlanner.d.ts +0 -1
- package/dist/react/core/widgets/ActivityPlanner.js +855 -517
- package/dist/react/core/widgets/ActivityPlanner.js.map +1 -1
- package/dist/react/core/widgets/Capture.d.ts +2 -3
- package/dist/react/core/widgets/Capture.js +965 -779
- package/dist/react/core/widgets/Capture.js.map +1 -1
- package/dist/react/core/widgets/ChatPanel.d.ts +16 -17
- package/dist/react/core/widgets/ChatPanel.js +1734 -1034
- package/dist/react/core/widgets/ChatPanel.js.map +1 -1
- package/dist/react/core/widgets/ColorPickerPanel.d.ts +0 -1
- package/dist/react/core/widgets/ColorPickerPanel.js +298 -287
- package/dist/react/core/widgets/ColorPickerPanel.js.map +1 -1
- package/dist/react/core/widgets/CommandBuilder.d.ts +2 -3
- package/dist/react/core/widgets/CommandBuilder.js +624 -516
- package/dist/react/core/widgets/CommandBuilder.js.map +1 -1
- package/dist/react/core/widgets/ConnectionForm.d.ts +2 -3
- package/dist/react/core/widgets/ConnectionForm.js +528 -492
- package/dist/react/core/widgets/ConnectionForm.js.map +1 -1
- package/dist/react/core/widgets/FileExplorer.d.ts +3 -4
- package/dist/react/core/widgets/FileExplorer.js +617 -505
- package/dist/react/core/widgets/FileExplorer.js.map +1 -1
- package/dist/react/core/widgets/HexViewer.d.ts +2 -3
- package/dist/react/core/widgets/HexViewer.js +1353 -1091
- package/dist/react/core/widgets/HexViewer.js.map +1 -1
- package/dist/react/core/widgets/ImageFrame.d.ts +1 -2
- package/dist/react/core/widgets/ImageFrame.js +230 -193
- package/dist/react/core/widgets/ImageFrame.js.map +1 -1
- package/dist/react/core/widgets/ImageGallery.d.ts +2 -1
- package/dist/react/core/widgets/ImageGallery.js +380 -314
- package/dist/react/core/widgets/ImageGallery.js.map +1 -1
- package/dist/react/core/widgets/ImagePanel.d.ts +0 -1
- package/dist/react/core/widgets/ImagePanel.js +110 -101
- package/dist/react/core/widgets/ImagePanel.js.map +1 -1
- package/dist/react/core/widgets/LogViewer.d.ts +2 -3
- package/dist/react/core/widgets/LogViewer.js +711 -607
- package/dist/react/core/widgets/LogViewer.js.map +1 -1
- package/dist/react/core/widgets/MessageStream.d.ts +3 -4
- package/dist/react/core/widgets/MessageStream.js +514 -444
- package/dist/react/core/widgets/MessageStream.js.map +1 -1
- package/dist/react/core/widgets/MissionCalendar.d.ts +3 -4
- package/dist/react/core/widgets/MissionCalendar.js +1488 -1020
- package/dist/react/core/widgets/MissionCalendar.js.map +1 -1
- package/dist/react/core/widgets/PacketViewer.d.ts +2 -3
- package/dist/react/core/widgets/PacketViewer.js +550 -421
- package/dist/react/core/widgets/PacketViewer.js.map +1 -1
- package/dist/react/core/widgets/capture-placeholder.png.js +6 -5
- package/dist/react/core/widgets/capture-placeholder.png.js.map +1 -1
- package/dist/react/hooks/index.d.ts +5 -5
- package/dist/react/hooks/useAccessWindows.d.ts +0 -1
- package/dist/react/hooks/useCompactMode.d.ts +1 -1
- package/dist/react/hooks/useCompactMode.js +104 -58
- package/dist/react/hooks/useCompactMode.js.map +1 -1
- package/dist/react/hooks/useGroundTrackHistory.d.ts +0 -1
- package/dist/react/hooks/useSimulationScene.d.ts +1 -2
- package/dist/react/hooks/useSimulationScene.js +481 -374
- package/dist/react/hooks/useSimulationScene.js.map +1 -1
- package/dist/react/index.d.ts +3 -3
- package/dist/react/panels/LayerControlPanel.d.ts +1 -2
- package/dist/react/panels/LayerControlPanel.js +223 -180
- package/dist/react/panels/LayerControlPanel.js.map +1 -1
- package/dist/react/panels/ObjectInventoryPanel.d.ts +2 -3
- package/dist/react/panels/ObjectInventoryPanel.js +315 -250
- package/dist/react/panels/ObjectInventoryPanel.js.map +1 -1
- package/dist/react/panels/index.d.ts +1 -1
- package/dist/react/panels/index.js +1 -6
- package/dist/react/shared/ErrorBoundary.d.ts +1 -2
- package/dist/react/shared/ErrorBoundary.js +170 -139
- package/dist/react/shared/ErrorBoundary.js.map +1 -1
- package/dist/react/shared/Skeleton.d.ts +1 -2
- package/dist/react/shared/Skeleton.js +300 -301
- package/dist/react/shared/Skeleton.js.map +1 -1
- package/dist/react/theme/ThemeProvider.d.ts +2 -321
- package/dist/react/theme/ThemeProvider.js +312 -939
- package/dist/react/theme/ThemeProvider.js.map +1 -1
- package/dist/react/theme/cardAccent.d.ts +1 -2
- package/dist/react/theme/cardAccent.js +196 -126
- package/dist/react/theme/cardAccent.js.map +1 -1
- package/dist/react/theme/config.d.ts +0 -1
- package/dist/react/theme/config.js +47 -25
- package/dist/react/theme/config.js.map +1 -1
- package/dist/react/theme/index.d.ts +2 -2
- package/dist/react/theme/index.js +1 -16
- package/dist/react/theme/types.d.ts +332 -0
- package/dist/react/theme/types.js +39 -0
- package/dist/react/theme/types.js.map +1 -0
- package/dist/react/types.d.ts +8 -8
- package/dist/react/types.js +147 -47
- package/dist/react/types.js.map +1 -1
- package/dist/react/utils/categoryPalette.d.ts +0 -1
- package/dist/react/utils/categoryPalette.js +91 -102
- package/dist/react/utils/categoryPalette.js.map +1 -1
- package/dist/react/utils/index.d.ts +5 -5
- package/dist/react/utils/index.js +457 -371
- package/dist/react/utils/index.js.map +1 -1
- package/dist/react/visualizations/EclipseTimerCard.d.ts +0 -1
- package/dist/react/visualizations/LinkBudgetCard.d.ts +0 -1
- package/dist/react/visualizations/NavBallCard.d.ts +0 -1
- package/dist/react/visualizations/PropulsionCard.d.ts +1 -2
- package/dist/react/visualizations/SensorFootprintCard.d.ts +1 -2
- package/dist/react/visualizations/ThermalHeatmapCard.d.ts +0 -1
- package/dist/react/visualizations/index.d.ts +3 -3
- package/dist/react.d.ts +2 -1
- package/dist/react.js +17 -221
- package/dist/shaders/atmosphere.frag.js +6 -5
- package/dist/shaders/atmosphere.frag.js.map +1 -1
- package/dist/shaders/atmosphere.vert.js +6 -5
- package/dist/shaders/atmosphere.vert.js.map +1 -1
- package/dist/shaders/stars.frag.js +6 -5
- package/dist/shaders/stars.frag.js.map +1 -1
- package/dist/shaders/stars.vert.js +6 -5
- package/dist/shaders/stars.vert.js.map +1 -1
- package/dist/style.css +217 -194
- package/dist/tokens/core.d.ts +159 -0
- package/dist/tokens/core.js +144 -0
- package/dist/tokens/core.js.map +1 -0
- package/dist/tokens/css-vars.d.ts +213 -2
- package/dist/tokens/css-vars.js +590 -227
- package/dist/tokens/css-vars.js.map +1 -1
- package/dist/tokens/index.d.ts +31 -228
- package/dist/tokens/index.js +281 -366
- package/dist/tokens/index.js.map +1 -1
- package/dist/tokens/palettes/astro.d.ts +129 -0
- package/dist/tokens/palettes/astro.js +112 -0
- package/dist/tokens/palettes/astro.js.map +1 -0
- package/dist/tokens/palettes/index.d.ts +288 -0
- package/dist/tokens/palettes/index.js +35 -0
- package/dist/tokens/palettes/index.js.map +1 -0
- package/dist/tokens/palettes/tailwind.d.ts +324 -0
- package/dist/tokens/palettes/tailwind.js +201 -0
- package/dist/tokens/palettes/tailwind.js.map +1 -0
- package/dist/tokens/palettes/zendir.d.ts +126 -0
- package/dist/tokens/palettes/zendir.js +92 -0
- package/dist/tokens/palettes/zendir.js.map +1 -0
- package/dist/tokens/themes.d.ts +30 -0
- package/dist/tokens/themes.js +704 -0
- package/dist/tokens/themes.js.map +1 -0
- package/dist/tokens/tokens.css +419 -41
- package/dist/types/index.d.ts +2 -2
- package/package.json +310 -313
- package/dist/index.js.map +0 -1
- package/dist/react/3d/index.js.map +0 -1
- package/dist/react/astro/index.js.map +0 -1
- package/dist/react/cards/index.js.map +0 -1
- package/dist/react/charts/index.js.map +0 -1
- package/dist/react/core/index.js.map +0 -1
- package/dist/react/panels/index.js.map +0 -1
- package/dist/react/theme/astro-tokens.d.ts +0 -153
- package/dist/react/theme/index.js.map +0 -1
- package/dist/react.js.map +0 -1
|
@@ -1,201 +1,238 @@
|
|
|
1
|
-
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
-
import { memo, useRef, useMemo, useEffect } from "react";
|
|
3
1
|
import { useTheme } from "../../theme/ThemeProvider.js";
|
|
4
2
|
import { classNames, lerp } from "../../utils/index.js";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
3
|
+
import { memo, useEffect, useMemo, useRef } from "react";
|
|
4
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
//#region src/react/core/widgets/ImageFrame.tsx
|
|
6
|
+
/**
|
|
7
|
+
* @zendir/ui - ImageFrame Component
|
|
8
|
+
*
|
|
9
|
+
* Renders a single-channel data frame (e.g. a CCD/sensor readout) as a
|
|
10
|
+
* false-colour image on a <canvas>. Purely presentational: hand it an
|
|
11
|
+
* array of samples and it paints them — it has no knowledge of where
|
|
12
|
+
* the data came from (engine, file, websocket, console). This is the
|
|
13
|
+
* reusable surface that callers "dump" a frame into.
|
|
14
|
+
*
|
|
15
|
+
* Pipeline per sample: normalise against a fixed ceiling (`maxADU`) to
|
|
16
|
+
* an intensity in [0,1], run that through a colormap, pack RGBA. The
|
|
17
|
+
* default `cividis` ramp (dark teal → yellow) reads well on dark UI.
|
|
18
|
+
*
|
|
19
|
+
* The canvas's native resolution is the frame's width×height, so each
|
|
20
|
+
* sample is exactly one pixel; CSS scales it up with
|
|
21
|
+
* `image-rendering: pixelated`, keeping small sensor frames crisp.
|
|
22
|
+
*
|
|
23
|
+
* Astro UX Compliance:
|
|
24
|
+
* - Themed surface, border radius, and typography via `useTheme`.
|
|
25
|
+
* - Loading and empty states with friendly messaging.
|
|
26
|
+
* - Monospace raw-value grid for analysis.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```tsx
|
|
30
|
+
* <ImageFrame
|
|
31
|
+
* data={ccdSamples} // number[] length = width*height
|
|
32
|
+
* width={28}
|
|
33
|
+
* maxADU={65535}
|
|
34
|
+
* title="CCD capture"
|
|
35
|
+
* showValues
|
|
36
|
+
* />
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
var DEFAULT_MAX_ADU = 65535;
|
|
40
|
+
/** Two-stop colormaps: intensity t in [0,1] → [r,g,b] (0..255). */
|
|
41
|
+
var COLORMAPS = {
|
|
42
|
+
cividis: (t) => [
|
|
43
|
+
lerp(42, 237, t),
|
|
44
|
+
lerp(123, 222, t),
|
|
45
|
+
lerp(155, 83, t)
|
|
46
|
+
],
|
|
47
|
+
gray: (t) => {
|
|
48
|
+
const v = lerp(0, 255, t);
|
|
49
|
+
return [
|
|
50
|
+
v,
|
|
51
|
+
v,
|
|
52
|
+
v
|
|
53
|
+
];
|
|
54
|
+
}
|
|
13
55
|
};
|
|
56
|
+
/**
|
|
57
|
+
* decodeImage — pure transform: single-channel samples → canvas-ready
|
|
58
|
+
* RGBA. Fixed-ceiling normalisation (`sample / maxADU`) so the output
|
|
59
|
+
* reflects the real signal level relative to saturation rather than
|
|
60
|
+
* rescaling per frame. Returns null on invalid input.
|
|
61
|
+
*/
|
|
14
62
|
function decodeImage(opts) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
63
|
+
const { data, width, height, maxADU = DEFAULT_MAX_ADU, colormap = "cividis" } = opts;
|
|
64
|
+
if (!Array.isArray(data)) return null;
|
|
65
|
+
if (!Number.isFinite(width) || width <= 0) return null;
|
|
66
|
+
const W = Math.floor(width);
|
|
67
|
+
const H = Number.isFinite(height) && height > 0 ? Math.floor(height) : W;
|
|
68
|
+
if (data.length !== W * H) return null;
|
|
69
|
+
const ramp = COLORMAPS[colormap] || COLORMAPS.cividis;
|
|
70
|
+
const ceiling = Number.isFinite(maxADU) && maxADU > 0 ? maxADU : DEFAULT_MAX_ADU;
|
|
71
|
+
const pixels = new Uint8ClampedArray(W * H * 4);
|
|
72
|
+
for (let i = 0; i < W * H; i++) {
|
|
73
|
+
const raw = data[i];
|
|
74
|
+
const [r, g, b] = ramp(Number.isFinite(raw) ? raw / ceiling : 0);
|
|
75
|
+
const off = i * 4;
|
|
76
|
+
pixels[off + 0] = r;
|
|
77
|
+
pixels[off + 1] = g;
|
|
78
|
+
pixels[off + 2] = b;
|
|
79
|
+
pixels[off + 3] = 255;
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
width: W,
|
|
83
|
+
height: H,
|
|
84
|
+
pixels
|
|
85
|
+
};
|
|
35
86
|
}
|
|
87
|
+
/** Pretty-print samples as a width-padded grid for the values view. */
|
|
36
88
|
function formatValues(data, width, height, maxADU) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
return rows.join("\n");
|
|
89
|
+
if (width * height !== data.length) return data.join(", ");
|
|
90
|
+
const colW = Math.max(3, String(Math.floor(maxADU) || 1).length);
|
|
91
|
+
const rows = [];
|
|
92
|
+
for (let y = 0; y < height; y++) {
|
|
93
|
+
const cells = [];
|
|
94
|
+
for (let x = 0; x < width; x++) cells.push(String(data[y * width + x]).padStart(colW, " "));
|
|
95
|
+
rows.push(cells.join(" "));
|
|
96
|
+
}
|
|
97
|
+
return rows.join("\n");
|
|
48
98
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
whiteSpace: "pre",
|
|
175
|
-
overflow: "auto",
|
|
176
|
-
maxHeight: 220
|
|
177
|
-
},
|
|
178
|
-
children: formatValues(data, decoded.width, decoded.height, maxADU)
|
|
179
|
-
}
|
|
180
|
-
)
|
|
181
|
-
]
|
|
182
|
-
}
|
|
183
|
-
);
|
|
99
|
+
var ImageFrame = memo(function ImageFrame({ data, pixels, width, height, maxADU = DEFAULT_MAX_ADU, colormap = "cividis", showValues = false, title, loading = false, emptyMessage = "No frame to display.", className = "" }) {
|
|
100
|
+
const { tokens } = useTheme();
|
|
101
|
+
const canvasRef = useRef(null);
|
|
102
|
+
const H = Number.isFinite(height) && height > 0 ? Math.floor(height) : Math.floor(width);
|
|
103
|
+
const W = Math.floor(width);
|
|
104
|
+
const decoded = useMemo(() => {
|
|
105
|
+
if (pixels instanceof Uint8ClampedArray && pixels.length === W * H * 4) return {
|
|
106
|
+
width: W,
|
|
107
|
+
height: H,
|
|
108
|
+
pixels
|
|
109
|
+
};
|
|
110
|
+
if (Array.isArray(data)) return decodeImage({
|
|
111
|
+
data,
|
|
112
|
+
width: W,
|
|
113
|
+
height: H,
|
|
114
|
+
maxADU,
|
|
115
|
+
colormap
|
|
116
|
+
});
|
|
117
|
+
return null;
|
|
118
|
+
}, [
|
|
119
|
+
data,
|
|
120
|
+
pixels,
|
|
121
|
+
W,
|
|
122
|
+
H,
|
|
123
|
+
maxADU,
|
|
124
|
+
colormap
|
|
125
|
+
]);
|
|
126
|
+
useEffect(() => {
|
|
127
|
+
if (!decoded) return;
|
|
128
|
+
const canvas = canvasRef.current;
|
|
129
|
+
if (!canvas) return;
|
|
130
|
+
canvas.width = decoded.width;
|
|
131
|
+
canvas.height = decoded.height;
|
|
132
|
+
const ctx = canvas.getContext("2d");
|
|
133
|
+
if (!ctx) return;
|
|
134
|
+
const img = ctx.createImageData(decoded.width, decoded.height);
|
|
135
|
+
img.data.set(decoded.pixels);
|
|
136
|
+
ctx.putImageData(img, 0, 0);
|
|
137
|
+
}, [decoded]);
|
|
138
|
+
const surfaceStyle = {
|
|
139
|
+
backgroundColor: tokens.colors.background.surface,
|
|
140
|
+
borderRadius: tokens.borderRadius.lg,
|
|
141
|
+
padding: tokens.spacing.md,
|
|
142
|
+
color: tokens.colors.text.primary,
|
|
143
|
+
fontFamily: tokens.typography.fontFamily.primary,
|
|
144
|
+
display: "flex",
|
|
145
|
+
flexDirection: "column",
|
|
146
|
+
gap: tokens.spacing.sm
|
|
147
|
+
};
|
|
148
|
+
if (loading) return /* @__PURE__ */ jsxs("div", {
|
|
149
|
+
className: classNames("zendir-image-frame", "loading", className),
|
|
150
|
+
role: "img",
|
|
151
|
+
"aria-busy": "true",
|
|
152
|
+
"aria-label": "Loading image",
|
|
153
|
+
style: surfaceStyle,
|
|
154
|
+
children: [title && /* @__PURE__ */ jsx(FrameTitle, {
|
|
155
|
+
tokens,
|
|
156
|
+
title
|
|
157
|
+
}), /* @__PURE__ */ jsx("div", { style: {
|
|
158
|
+
width: "100%",
|
|
159
|
+
aspectRatio: `${W} / ${H}`,
|
|
160
|
+
background: "rgba(255,255,255,0.06)",
|
|
161
|
+
borderRadius: tokens.borderRadius.sm
|
|
162
|
+
} })]
|
|
163
|
+
});
|
|
164
|
+
if (!decoded) return /* @__PURE__ */ jsxs("div", {
|
|
165
|
+
className: classNames("zendir-image-frame", "empty", className),
|
|
166
|
+
role: "img",
|
|
167
|
+
"aria-label": "No image",
|
|
168
|
+
style: {
|
|
169
|
+
...surfaceStyle,
|
|
170
|
+
textAlign: "center",
|
|
171
|
+
color: tokens.colors.text.tertiary
|
|
172
|
+
},
|
|
173
|
+
children: [title && /* @__PURE__ */ jsx(FrameTitle, {
|
|
174
|
+
tokens,
|
|
175
|
+
title
|
|
176
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
177
|
+
style: {
|
|
178
|
+
margin: 0,
|
|
179
|
+
fontSize: tokens.typography.fontSize.xs
|
|
180
|
+
},
|
|
181
|
+
children: emptyMessage
|
|
182
|
+
})]
|
|
183
|
+
});
|
|
184
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
185
|
+
className: classNames("zendir-image-frame", className),
|
|
186
|
+
"aria-label": title || "Image frame",
|
|
187
|
+
style: surfaceStyle,
|
|
188
|
+
children: [
|
|
189
|
+
title && /* @__PURE__ */ jsx(FrameTitle, {
|
|
190
|
+
tokens,
|
|
191
|
+
title
|
|
192
|
+
}),
|
|
193
|
+
/* @__PURE__ */ jsx("canvas", {
|
|
194
|
+
ref: canvasRef,
|
|
195
|
+
"data-testid": "image-frame-canvas",
|
|
196
|
+
style: {
|
|
197
|
+
width: "100%",
|
|
198
|
+
aspectRatio: `${decoded.width} / ${decoded.height}`,
|
|
199
|
+
imageRendering: "pixelated",
|
|
200
|
+
display: "block",
|
|
201
|
+
background: "rgba(0,0,0,0.4)",
|
|
202
|
+
borderRadius: tokens.borderRadius.sm
|
|
203
|
+
}
|
|
204
|
+
}),
|
|
205
|
+
showValues && Array.isArray(data) && /* @__PURE__ */ jsx("pre", {
|
|
206
|
+
"data-testid": "image-frame-values",
|
|
207
|
+
style: {
|
|
208
|
+
margin: 0,
|
|
209
|
+
padding: tokens.spacing.sm,
|
|
210
|
+
background: "rgba(0,0,0,0.45)",
|
|
211
|
+
borderRadius: tokens.borderRadius.sm,
|
|
212
|
+
color: tokens.colors.text.primary,
|
|
213
|
+
fontSize: 10,
|
|
214
|
+
lineHeight: 1.25,
|
|
215
|
+
fontFamily: tokens.typography.fontFamily.mono,
|
|
216
|
+
whiteSpace: "pre",
|
|
217
|
+
overflow: "auto",
|
|
218
|
+
maxHeight: 220
|
|
219
|
+
},
|
|
220
|
+
children: formatValues(data, decoded.width, decoded.height, maxADU)
|
|
221
|
+
})
|
|
222
|
+
]
|
|
223
|
+
});
|
|
184
224
|
});
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
decodeImage
|
|
200
|
-
};
|
|
201
|
-
//# sourceMappingURL=ImageFrame.js.map
|
|
225
|
+
/** Small header row shared across states. */
|
|
226
|
+
var FrameTitle = ({ tokens, title }) => /* @__PURE__ */ jsx("h3", {
|
|
227
|
+
style: {
|
|
228
|
+
margin: 0,
|
|
229
|
+
fontSize: tokens.typography.fontSize.md,
|
|
230
|
+
fontWeight: tokens.typography.fontWeight.semibold,
|
|
231
|
+
color: tokens.colors.text.primary
|
|
232
|
+
},
|
|
233
|
+
children: title
|
|
234
|
+
});
|
|
235
|
+
//#endregion
|
|
236
|
+
export { ImageFrame, ImageFrame as default, decodeImage };
|
|
237
|
+
|
|
238
|
+
//# sourceMappingURL=ImageFrame.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ImageFrame.js","sources":["../../../../src/react/core/widgets/ImageFrame.tsx"],"sourcesContent":["/**\r\n * @zendir/ui - ImageFrame Component\r\n *\r\n * Renders a single-channel data frame (e.g. a CCD/sensor readout) as a\r\n * false-colour image on a <canvas>. Purely presentational: hand it an\r\n * array of samples and it paints them — it has no knowledge of where\r\n * the data came from (engine, file, websocket, console). This is the\r\n * reusable surface that callers \"dump\" a frame into.\r\n *\r\n * Pipeline per sample: normalise against a fixed ceiling (`maxADU`) to\r\n * an intensity in [0,1], run that through a colormap, pack RGBA. The\r\n * default `cividis` ramp (dark teal → yellow) reads well on dark UI.\r\n *\r\n * The canvas's native resolution is the frame's width×height, so each\r\n * sample is exactly one pixel; CSS scales it up with\r\n * `image-rendering: pixelated`, keeping small sensor frames crisp.\r\n *\r\n * Astro UX Compliance:\r\n * - Themed surface, border radius, and typography via `useTheme`.\r\n * - Loading and empty states with friendly messaging.\r\n * - Monospace raw-value grid for analysis.\r\n *\r\n * @example\r\n * ```tsx\r\n * <ImageFrame\r\n * data={ccdSamples} // number[] length = width*height\r\n * width={28}\r\n * maxADU={65535}\r\n * title=\"CCD capture\"\r\n * showValues\r\n * />\r\n * ```\r\n */\r\n\r\nimport React, { memo, useEffect, useMemo, useRef } from 'react';\r\nimport { useTheme } from '../../theme';\r\nimport { classNames, lerp } from '../../utils';\r\n\r\n// =============================================================================\r\n// Types\r\n// =============================================================================\r\n\r\n/** Built-in colormap names. */\r\nexport type ImageColormap = 'cividis' | 'gray';\r\n\r\nexport interface ImageFrameProps {\r\n /** Flat, row-major single-channel samples. Length must equal width*height. */\r\n data?: number[];\r\n /**\r\n * Pre-decoded RGBA pixels (length = width*height*4). When provided this\r\n * is painted directly and `data` / `maxADU` / `colormap` are ignored.\r\n * Escape hatch for callers that already have an image buffer.\r\n */\r\n pixels?: Uint8ClampedArray;\r\n /** Frame width in pixels. */\r\n width: number;\r\n /** Frame height in pixels. Defaults to `width` (square). */\r\n height?: number;\r\n /** Saturation ceiling used to normalise `data` → [0,1]. Default 65535. */\r\n maxADU?: number;\r\n /** Colormap applied to normalised intensities. Default 'cividis'. */\r\n colormap?: ImageColormap;\r\n /** Show the raw sample values as a grid below the image. Default false. */\r\n showValues?: boolean;\r\n /** Optional title shown above the image. */\r\n title?: string;\r\n /** Loading state (skeleton). */\r\n loading?: boolean;\r\n /** Message shown when there is no frame to display. */\r\n emptyMessage?: string;\r\n /** Custom class name. */\r\n className?: string;\r\n}\r\n\r\n// =============================================================================\r\n// Pure helpers (exported for testing / reuse)\r\n// =============================================================================\r\n\r\nconst DEFAULT_MAX_ADU = 65535;\r\n\r\n/** Two-stop colormaps: intensity t in [0,1] → [r,g,b] (0..255). */\r\nconst COLORMAPS: Record<ImageColormap, (t: number) => [number, number, number]> = {\r\n // Dark teal → yellow; a cividis-style ramp that reads well on dark UI.\r\n cividis: (t) => [lerp(42, 237, t), lerp(123, 222, t), lerp(155, 83, t)],\r\n gray: (t) => {\r\n const v = lerp(0, 255, t);\r\n return [v, v, v];\r\n },\r\n};\r\n\r\n/**\r\n * decodeImage — pure transform: single-channel samples → canvas-ready\r\n * RGBA. Fixed-ceiling normalisation (`sample / maxADU`) so the output\r\n * reflects the real signal level relative to saturation rather than\r\n * rescaling per frame. Returns null on invalid input.\r\n */\r\nexport function decodeImage(opts: {\r\n data: number[];\r\n width: number;\r\n height?: number;\r\n maxADU?: number;\r\n colormap?: ImageColormap;\r\n}): { width: number; height: number; pixels: Uint8ClampedArray } | null {\r\n const { data, width, height, maxADU = DEFAULT_MAX_ADU, colormap = 'cividis' } = opts;\r\n if (!Array.isArray(data)) return null;\r\n if (!Number.isFinite(width) || width <= 0) return null;\r\n const W = Math.floor(width);\r\n const H = Number.isFinite(height) && (height as number) > 0 ? Math.floor(height as number) : W;\r\n if (data.length !== W * H) return null;\r\n\r\n const ramp = COLORMAPS[colormap] || COLORMAPS.cividis;\r\n const ceiling = Number.isFinite(maxADU) && maxADU > 0 ? maxADU : DEFAULT_MAX_ADU;\r\n\r\n const pixels = new Uint8ClampedArray(W * H * 4);\r\n for (let i = 0; i < W * H; i++) {\r\n const raw = data[i];\r\n // sample / ceiling (lerp clamps t to [0,1]); non-finite reads as 0.\r\n const t = Number.isFinite(raw) ? raw / ceiling : 0;\r\n const [r, g, b] = ramp(t);\r\n const off = i * 4;\r\n pixels[off + 0] = r;\r\n pixels[off + 1] = g;\r\n pixels[off + 2] = b;\r\n pixels[off + 3] = 255;\r\n }\r\n return { width: W, height: H, pixels };\r\n}\r\n\r\n/** Pretty-print samples as a width-padded grid for the values view. */\r\nfunction formatValues(data: number[], width: number, height: number, maxADU: number): string {\r\n if (width * height !== data.length) return data.join(', ');\r\n const colW = Math.max(3, String(Math.floor(maxADU) || 1).length);\r\n const rows: string[] = [];\r\n for (let y = 0; y < height; y++) {\r\n const cells: string[] = [];\r\n for (let x = 0; x < width; x++) {\r\n cells.push(String(data[y * width + x]).padStart(colW, ' '));\r\n }\r\n rows.push(cells.join(' '));\r\n }\r\n return rows.join('\\n');\r\n}\r\n\r\n// =============================================================================\r\n// Component\r\n// =============================================================================\r\n\r\nexport const ImageFrame = memo(function ImageFrame({\r\n data,\r\n pixels,\r\n width,\r\n height,\r\n maxADU = DEFAULT_MAX_ADU,\r\n colormap = 'cividis',\r\n showValues = false,\r\n title,\r\n loading = false,\r\n emptyMessage = 'No frame to display.',\r\n className = '',\r\n}: ImageFrameProps): React.ReactElement {\r\n const { tokens } = useTheme();\r\n const canvasRef = useRef<HTMLCanvasElement>(null);\r\n\r\n const H = Number.isFinite(height) && (height as number) > 0 ? Math.floor(height as number) : Math.floor(width);\r\n const W = Math.floor(width);\r\n\r\n // Decode once per frame/option change. A pre-decoded `pixels` buffer\r\n // wins; otherwise decode `data` through the colormap.\r\n const decoded = useMemo(() => {\r\n if (pixels instanceof Uint8ClampedArray && pixels.length === W * H * 4) {\r\n return { width: W, height: H, pixels };\r\n }\r\n if (Array.isArray(data)) {\r\n return decodeImage({ data, width: W, height: H, maxADU, colormap });\r\n }\r\n return null;\r\n }, [data, pixels, W, H, maxADU, colormap]);\r\n\r\n useEffect(() => {\r\n if (!decoded) return;\r\n const canvas = canvasRef.current;\r\n if (!canvas) return;\r\n canvas.width = decoded.width;\r\n canvas.height = decoded.height;\r\n const ctx = canvas.getContext('2d');\r\n if (!ctx) return;\r\n // Build via createImageData + set (rather than `new ImageData(buf,…)`)\r\n // to sidestep the lib.dom ArrayBuffer/ArrayBufferLike typing mismatch\r\n // on newer TypeScript, and it's the same cost.\r\n const img = ctx.createImageData(decoded.width, decoded.height);\r\n img.data.set(decoded.pixels);\r\n ctx.putImageData(img, 0, 0);\r\n }, [decoded]);\r\n\r\n const surfaceStyle: React.CSSProperties = {\r\n backgroundColor: tokens.colors.background.surface,\r\n borderRadius: tokens.borderRadius.lg,\r\n padding: tokens.spacing.md,\r\n color: tokens.colors.text.primary,\r\n fontFamily: tokens.typography.fontFamily.primary,\r\n display: 'flex',\r\n flexDirection: 'column',\r\n gap: tokens.spacing.sm,\r\n };\r\n\r\n // Loading state\r\n if (loading) {\r\n return (\r\n <div\r\n className={classNames('zendir-image-frame', 'loading', className)}\r\n role=\"img\"\r\n aria-busy=\"true\"\r\n aria-label=\"Loading image\"\r\n style={surfaceStyle}\r\n >\r\n {title && <FrameTitle tokens={tokens} title={title} />}\r\n <div\r\n style={{\r\n width: '100%',\r\n aspectRatio: `${W} / ${H}`,\r\n background: 'rgba(255,255,255,0.06)',\r\n borderRadius: tokens.borderRadius.sm,\r\n }}\r\n />\r\n </div>\r\n );\r\n }\r\n\r\n // Empty state\r\n if (!decoded) {\r\n return (\r\n <div\r\n className={classNames('zendir-image-frame', 'empty', className)}\r\n role=\"img\"\r\n aria-label=\"No image\"\r\n style={{ ...surfaceStyle, textAlign: 'center', color: tokens.colors.text.tertiary }}\r\n >\r\n {title && <FrameTitle tokens={tokens} title={title} />}\r\n <p style={{ margin: 0, fontSize: tokens.typography.fontSize.xs }}>{emptyMessage}</p>\r\n </div>\r\n );\r\n }\r\n\r\n return (\r\n <div\r\n className={classNames('zendir-image-frame', className)}\r\n aria-label={title || 'Image frame'}\r\n style={surfaceStyle}\r\n >\r\n {title && <FrameTitle tokens={tokens} title={title} />}\r\n\r\n <canvas\r\n ref={canvasRef}\r\n data-testid=\"image-frame-canvas\"\r\n style={{\r\n width: '100%',\r\n aspectRatio: `${decoded.width} / ${decoded.height}`,\r\n imageRendering: 'pixelated',\r\n display: 'block',\r\n background: 'rgba(0,0,0,0.4)',\r\n borderRadius: tokens.borderRadius.sm,\r\n }}\r\n />\r\n\r\n {showValues && Array.isArray(data) && (\r\n <pre\r\n data-testid=\"image-frame-values\"\r\n style={{\r\n margin: 0,\r\n padding: tokens.spacing.sm,\r\n background: 'rgba(0,0,0,0.45)',\r\n borderRadius: tokens.borderRadius.sm,\r\n color: tokens.colors.text.primary,\r\n fontSize: 10,\r\n lineHeight: 1.25,\r\n fontFamily: tokens.typography.fontFamily.mono,\r\n whiteSpace: 'pre',\r\n overflow: 'auto',\r\n maxHeight: 220,\r\n }}\r\n >\r\n {formatValues(data, decoded.width, decoded.height, maxADU)}\r\n </pre>\r\n )}\r\n </div>\r\n );\r\n});\r\n\r\n/** Small header row shared across states. */\r\nconst FrameTitle = ({ tokens, title }: { tokens: ReturnType<typeof useTheme>['tokens']; title: string }) => (\r\n <h3\r\n style={{\r\n margin: 0,\r\n fontSize: tokens.typography.fontSize.md,\r\n fontWeight: tokens.typography.fontWeight.semibold,\r\n color: tokens.colors.text.primary,\r\n }}\r\n >\r\n {title}\r\n </h3>\r\n);\r\n\r\nexport default ImageFrame;\r\n"],"names":["ImageFrame"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ImageFrame.js","names":[],"sources":["../../../../src/react/core/widgets/ImageFrame.tsx"],"sourcesContent":["/**\n * @zendir/ui - ImageFrame Component\n *\n * Renders a single-channel data frame (e.g. a CCD/sensor readout) as a\n * false-colour image on a <canvas>. Purely presentational: hand it an\n * array of samples and it paints them — it has no knowledge of where\n * the data came from (engine, file, websocket, console). This is the\n * reusable surface that callers \"dump\" a frame into.\n *\n * Pipeline per sample: normalise against a fixed ceiling (`maxADU`) to\n * an intensity in [0,1], run that through a colormap, pack RGBA. The\n * default `cividis` ramp (dark teal → yellow) reads well on dark UI.\n *\n * The canvas's native resolution is the frame's width×height, so each\n * sample is exactly one pixel; CSS scales it up with\n * `image-rendering: pixelated`, keeping small sensor frames crisp.\n *\n * Astro UX Compliance:\n * - Themed surface, border radius, and typography via `useTheme`.\n * - Loading and empty states with friendly messaging.\n * - Monospace raw-value grid for analysis.\n *\n * @example\n * ```tsx\n * <ImageFrame\n * data={ccdSamples} // number[] length = width*height\n * width={28}\n * maxADU={65535}\n * title=\"CCD capture\"\n * showValues\n * />\n * ```\n */\n\nimport React, { memo, useEffect, useMemo, useRef } from \"react\";\nimport { useTheme } from \"../../theme\";\nimport { classNames, lerp } from \"../../utils\";\n\n// =============================================================================\n// Types\n// =============================================================================\n\n/** Built-in colormap names. */\nexport type ImageColormap = \"cividis\" | \"gray\";\n\nexport interface ImageFrameProps {\n /** Flat, row-major single-channel samples. Length must equal width*height. */\n data?: number[];\n /**\n * Pre-decoded RGBA pixels (length = width*height*4). When provided this\n * is painted directly and `data` / `maxADU` / `colormap` are ignored.\n * Escape hatch for callers that already have an image buffer.\n */\n pixels?: Uint8ClampedArray;\n /** Frame width in pixels. */\n width: number;\n /** Frame height in pixels. Defaults to `width` (square). */\n height?: number;\n /** Saturation ceiling used to normalise `data` → [0,1]. Default 65535. */\n maxADU?: number;\n /** Colormap applied to normalised intensities. Default 'cividis'. */\n colormap?: ImageColormap;\n /** Show the raw sample values as a grid below the image. Default false. */\n showValues?: boolean;\n /** Optional title shown above the image. */\n title?: string;\n /** Loading state (skeleton). */\n loading?: boolean;\n /** Message shown when there is no frame to display. */\n emptyMessage?: string;\n /** Custom class name. */\n className?: string;\n}\n\n// =============================================================================\n// Pure helpers (exported for testing / reuse)\n// =============================================================================\n\nconst DEFAULT_MAX_ADU = 65535;\n\n/** Two-stop colormaps: intensity t in [0,1] → [r,g,b] (0..255). */\nconst COLORMAPS: Record<\n ImageColormap,\n (t: number) => [number, number, number]\n> = {\n // Dark teal → yellow; a cividis-style ramp that reads well on dark UI.\n cividis: (t) => [lerp(42, 237, t), lerp(123, 222, t), lerp(155, 83, t)],\n gray: (t) => {\n const v = lerp(0, 255, t);\n return [v, v, v];\n },\n};\n\n/**\n * decodeImage — pure transform: single-channel samples → canvas-ready\n * RGBA. Fixed-ceiling normalisation (`sample / maxADU`) so the output\n * reflects the real signal level relative to saturation rather than\n * rescaling per frame. Returns null on invalid input.\n */\nexport function decodeImage(opts: {\n data: number[];\n width: number;\n height?: number;\n maxADU?: number;\n colormap?: ImageColormap;\n}): { width: number; height: number; pixels: Uint8ClampedArray } | null {\n const {\n data,\n width,\n height,\n maxADU = DEFAULT_MAX_ADU,\n colormap = \"cividis\",\n } = opts;\n if (!Array.isArray(data)) return null;\n if (!Number.isFinite(width) || width <= 0) return null;\n const W = Math.floor(width);\n const H =\n Number.isFinite(height) && (height as number) > 0\n ? Math.floor(height as number)\n : W;\n if (data.length !== W * H) return null;\n\n const ramp = COLORMAPS[colormap] || COLORMAPS.cividis;\n const ceiling =\n Number.isFinite(maxADU) && maxADU > 0 ? maxADU : DEFAULT_MAX_ADU;\n\n const pixels = new Uint8ClampedArray(W * H * 4);\n for (let i = 0; i < W * H; i++) {\n const raw = data[i];\n // sample / ceiling (lerp clamps t to [0,1]); non-finite reads as 0.\n const t = Number.isFinite(raw) ? raw / ceiling : 0;\n const [r, g, b] = ramp(t);\n const off = i * 4;\n pixels[off + 0] = r;\n pixels[off + 1] = g;\n pixels[off + 2] = b;\n pixels[off + 3] = 255;\n }\n return { width: W, height: H, pixels };\n}\n\n/** Pretty-print samples as a width-padded grid for the values view. */\nfunction formatValues(\n data: number[],\n width: number,\n height: number,\n maxADU: number,\n): string {\n if (width * height !== data.length) return data.join(\", \");\n const colW = Math.max(3, String(Math.floor(maxADU) || 1).length);\n const rows: string[] = [];\n for (let y = 0; y < height; y++) {\n const cells: string[] = [];\n for (let x = 0; x < width; x++) {\n cells.push(String(data[y * width + x]).padStart(colW, \" \"));\n }\n rows.push(cells.join(\" \"));\n }\n return rows.join(\"\\n\");\n}\n\n// =============================================================================\n// Component\n// =============================================================================\n\nexport const ImageFrame = memo(function ImageFrame({\n data,\n pixels,\n width,\n height,\n maxADU = DEFAULT_MAX_ADU,\n colormap = \"cividis\",\n showValues = false,\n title,\n loading = false,\n emptyMessage = \"No frame to display.\",\n className = \"\",\n}: ImageFrameProps): React.ReactElement {\n const { tokens } = useTheme();\n const canvasRef = useRef<HTMLCanvasElement>(null);\n\n const H =\n Number.isFinite(height) && (height as number) > 0\n ? Math.floor(height as number)\n : Math.floor(width);\n const W = Math.floor(width);\n\n // Decode once per frame/option change. A pre-decoded `pixels` buffer\n // wins; otherwise decode `data` through the colormap.\n const decoded = useMemo(() => {\n if (pixels instanceof Uint8ClampedArray && pixels.length === W * H * 4) {\n return { width: W, height: H, pixels };\n }\n if (Array.isArray(data)) {\n return decodeImage({ data, width: W, height: H, maxADU, colormap });\n }\n return null;\n }, [data, pixels, W, H, maxADU, colormap]);\n\n useEffect(() => {\n if (!decoded) return;\n const canvas = canvasRef.current;\n if (!canvas) return;\n canvas.width = decoded.width;\n canvas.height = decoded.height;\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) return;\n // Build via createImageData + set (rather than `new ImageData(buf,…)`)\n // to sidestep the lib.dom ArrayBuffer/ArrayBufferLike typing mismatch\n // on newer TypeScript, and it's the same cost.\n const img = ctx.createImageData(decoded.width, decoded.height);\n img.data.set(decoded.pixels);\n ctx.putImageData(img, 0, 0);\n }, [decoded]);\n\n const surfaceStyle: React.CSSProperties = {\n backgroundColor: tokens.colors.background.surface,\n borderRadius: tokens.borderRadius.lg,\n padding: tokens.spacing.md,\n color: tokens.colors.text.primary,\n fontFamily: tokens.typography.fontFamily.primary,\n display: \"flex\",\n flexDirection: \"column\",\n gap: tokens.spacing.sm,\n };\n\n // Loading state\n if (loading) {\n return (\n <div\n className={classNames(\"zendir-image-frame\", \"loading\", className)}\n role=\"img\"\n aria-busy=\"true\"\n aria-label=\"Loading image\"\n style={surfaceStyle}\n >\n {title && <FrameTitle tokens={tokens} title={title} />}\n <div\n style={{\n width: \"100%\",\n aspectRatio: `${W} / ${H}`,\n background: \"rgba(255,255,255,0.06)\",\n borderRadius: tokens.borderRadius.sm,\n }}\n />\n </div>\n );\n }\n\n // Empty state\n if (!decoded) {\n return (\n <div\n className={classNames(\"zendir-image-frame\", \"empty\", className)}\n role=\"img\"\n aria-label=\"No image\"\n style={{\n ...surfaceStyle,\n textAlign: \"center\",\n color: tokens.colors.text.tertiary,\n }}\n >\n {title && <FrameTitle tokens={tokens} title={title} />}\n <p style={{ margin: 0, fontSize: tokens.typography.fontSize.xs }}>\n {emptyMessage}\n </p>\n </div>\n );\n }\n\n return (\n <div\n className={classNames(\"zendir-image-frame\", className)}\n aria-label={title || \"Image frame\"}\n style={surfaceStyle}\n >\n {title && <FrameTitle tokens={tokens} title={title} />}\n\n <canvas\n ref={canvasRef}\n data-testid=\"image-frame-canvas\"\n style={{\n width: \"100%\",\n aspectRatio: `${decoded.width} / ${decoded.height}`,\n imageRendering: \"pixelated\",\n display: \"block\",\n background: \"rgba(0,0,0,0.4)\",\n borderRadius: tokens.borderRadius.sm,\n }}\n />\n\n {showValues && Array.isArray(data) && (\n <pre\n data-testid=\"image-frame-values\"\n style={{\n margin: 0,\n padding: tokens.spacing.sm,\n background: \"rgba(0,0,0,0.45)\",\n borderRadius: tokens.borderRadius.sm,\n color: tokens.colors.text.primary,\n fontSize: 10,\n lineHeight: 1.25,\n fontFamily: tokens.typography.fontFamily.mono,\n whiteSpace: \"pre\",\n overflow: \"auto\",\n maxHeight: 220,\n }}\n >\n {formatValues(data, decoded.width, decoded.height, maxADU)}\n </pre>\n )}\n </div>\n );\n});\n\n/** Small header row shared across states. */\nconst FrameTitle = ({\n tokens,\n title,\n}: {\n tokens: ReturnType<typeof useTheme>[\"tokens\"];\n title: string;\n}) => (\n <h3\n style={{\n margin: 0,\n fontSize: tokens.typography.fontSize.md,\n fontWeight: tokens.typography.fontWeight.semibold,\n color: tokens.colors.text.primary,\n }}\n >\n {title}\n </h3>\n);\n\nexport default ImageFrame;\n"],"mappings":"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"}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { default as React } from 'react';
|
|
2
2
|
import { ResponsiveValue } from '../layout/useBreakpoint';
|
|
3
|
-
|
|
4
3
|
export interface GalleryImage {
|
|
5
4
|
/** Unique identifier */
|
|
6
5
|
id: string;
|
|
@@ -10,6 +9,8 @@ export interface GalleryImage {
|
|
|
10
9
|
src: string;
|
|
11
10
|
/** Timestamp (ms since epoch) */
|
|
12
11
|
timestamp?: number;
|
|
12
|
+
/** Optional short label shown as a pill over the thumbnail (e.g. "NEW"). */
|
|
13
|
+
badge?: string;
|
|
13
14
|
/** Optional metadata */
|
|
14
15
|
metadata?: Record<string, unknown>;
|
|
15
16
|
}
|