@zendir/ui 0.1.7 → 0.1.9

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 (79) hide show
  1. package/dist/index.js +0 -137
  2. package/dist/index.js.map +1 -1
  3. package/dist/react/context/DisplaySettingsContext.js +0 -12
  4. package/dist/react/context/DisplaySettingsContext.js.map +1 -1
  5. package/dist/react/index.d.ts +0 -30
  6. package/dist/react/utils/index.js +0 -8
  7. package/dist/react/utils/index.js.map +1 -1
  8. package/dist/react.js +0 -137
  9. package/dist/react.js.map +1 -1
  10. package/package.json +2 -2
  11. package/dist/react/3d/EarthViewer.js +0 -836
  12. package/dist/react/3d/EarthViewer.js.map +0 -1
  13. package/dist/react/3d/SolarSystemViewer.js +0 -372
  14. package/dist/react/3d/SolarSystemViewer.js.map +0 -1
  15. package/dist/react/3d/ZenSpace3D.js +0 -1253
  16. package/dist/react/3d/ZenSpace3D.js.map +0 -1
  17. package/dist/react/3d/ZenSpace3DCesium.js +0 -186
  18. package/dist/react/3d/ZenSpace3DCesium.js.map +0 -1
  19. package/dist/react/3d/ZenSpace3DShaders.js +0 -94
  20. package/dist/react/3d/ZenSpace3DShaders.js.map +0 -1
  21. package/dist/react/3d/ZenSpace3DUtils.js +0 -213
  22. package/dist/react/3d/ZenSpace3DUtils.js.map +0 -1
  23. package/dist/react/3d/threeLoader.js +0 -18
  24. package/dist/react/3d/threeLoader.js.map +0 -1
  25. package/dist/react/cards/AccessCard.js +0 -410
  26. package/dist/react/cards/AccessCard.js.map +0 -1
  27. package/dist/react/cards/OrbitCard.js +0 -372
  28. package/dist/react/cards/OrbitCard.js.map +0 -1
  29. package/dist/react/cards/SpacecraftCard.js +0 -941
  30. package/dist/react/cards/SpacecraftCard.js.map +0 -1
  31. package/dist/react/cards/TelemetryCard.js +0 -742
  32. package/dist/react/cards/TelemetryCard.js.map +0 -1
  33. package/dist/react/cards/TelemetryStreamCard.js +0 -309
  34. package/dist/react/cards/TelemetryStreamCard.js.map +0 -1
  35. package/dist/react/charts/GroundTrackMap.js +0 -1123
  36. package/dist/react/charts/GroundTrackMap.js.map +0 -1
  37. package/dist/react/charts/GroundTrackMapLeaflet.js +0 -571
  38. package/dist/react/charts/GroundTrackMapLeaflet.js.map +0 -1
  39. package/dist/react/charts/groundTrackMapLeafletTiles.js +0 -11
  40. package/dist/react/charts/groundTrackMapLeafletTiles.js.map +0 -1
  41. package/dist/react/charts/groundTrackMapLeafletUtils.js +0 -109
  42. package/dist/react/charts/groundTrackMapLeafletUtils.js.map +0 -1
  43. package/dist/react/charts/unified/AstroChart.js +0 -1405
  44. package/dist/react/charts/unified/AstroChart.js.map +0 -1
  45. package/dist/react/charts/unified/PowerOverviewChart.js +0 -488
  46. package/dist/react/charts/unified/PowerOverviewChart.js.map +0 -1
  47. package/dist/react/charts/unified/domain.js +0 -3168
  48. package/dist/react/charts/unified/domain.js.map +0 -1
  49. package/dist/react/charts/unified/generators.js +0 -518
  50. package/dist/react/charts/unified/generators.js.map +0 -1
  51. package/dist/react/charts/unified/presets.js +0 -999
  52. package/dist/react/charts/unified/presets.js.map +0 -1
  53. package/dist/react/charts/unified/sync.js +0 -219
  54. package/dist/react/charts/unified/sync.js.map +0 -1
  55. package/dist/react/charts/unified/theme.js +0 -562
  56. package/dist/react/charts/unified/theme.js.map +0 -1
  57. package/dist/react/charts/unified/useChartStream.js +0 -226
  58. package/dist/react/charts/unified/useChartStream.js.map +0 -1
  59. package/dist/react/visualizations/EclipseTimerCard.js +0 -250
  60. package/dist/react/visualizations/EclipseTimerCard.js.map +0 -1
  61. package/dist/react/visualizations/LinkBudgetCard.js +0 -444
  62. package/dist/react/visualizations/LinkBudgetCard.js.map +0 -1
  63. package/dist/react/visualizations/NavBallCard.js +0 -243
  64. package/dist/react/visualizations/NavBallCard.js.map +0 -1
  65. package/dist/react/visualizations/PropulsionCard.js +0 -298
  66. package/dist/react/visualizations/PropulsionCard.js.map +0 -1
  67. package/dist/react/visualizations/SensorFootprintCard.js +0 -326
  68. package/dist/react/visualizations/SensorFootprintCard.js.map +0 -1
  69. package/dist/react/visualizations/ThermalHeatmapCard.js +0 -372
  70. package/dist/react/visualizations/ThermalHeatmapCard.js.map +0 -1
  71. package/dist/shaders/atmosphere.frag.js +0 -5
  72. package/dist/shaders/atmosphere.frag.js.map +0 -1
  73. package/dist/shaders/atmosphere.vert.js +0 -5
  74. package/dist/shaders/atmosphere.vert.js.map +0 -1
  75. package/dist/shaders/stars.frag.js +0 -5
  76. package/dist/shaders/stars.frag.js.map +0 -1
  77. package/dist/shaders/stars.vert.js +0 -5
  78. package/dist/shaders/stars.vert.js.map +0 -1
  79. package/dist/style.css +0 -143
@@ -1,226 +0,0 @@
1
- import { useRef, useState, useCallback, useEffect } from "react";
2
- function useChartStream(chartRef, options = {}) {
3
- const {
4
- maxPoints: _maxPoints = 1e3,
5
- timeWindow: _timeWindow,
6
- throttleMs = 16,
7
- // ~60fps
8
- bufferSize = 10,
9
- decimate = true,
10
- decimateThreshold = 5e3
11
- } = options;
12
- const bufferRef = useRef(/* @__PURE__ */ new Map());
13
- const lastFlushRef = useRef(0);
14
- const pointCountRef = useRef(0);
15
- const [isPaused, setIsPaused] = useState(false);
16
- const animationFrameRef = useRef();
17
- const flush = useCallback(() => {
18
- const chart = chartRef.current;
19
- if (!chart || isPaused) return;
20
- const now = Date.now();
21
- if (now - lastFlushRef.current < throttleMs) {
22
- animationFrameRef.current = requestAnimationFrame(flush);
23
- return;
24
- }
25
- bufferRef.current.forEach((points, seriesIndex) => {
26
- if (points.length === 0) return;
27
- let processedPoints = points;
28
- if (decimate && pointCountRef.current > decimateThreshold) {
29
- processedPoints = decimateData(points, Math.ceil(points.length / 2));
30
- }
31
- processedPoints.forEach((point) => {
32
- chart.appendData(seriesIndex, point);
33
- });
34
- pointCountRef.current += processedPoints.length;
35
- });
36
- bufferRef.current.clear();
37
- lastFlushRef.current = now;
38
- }, [chartRef, isPaused, throttleMs, decimate, decimateThreshold]);
39
- const push = useCallback((seriesIndex, point) => {
40
- if (isPaused) return;
41
- let buffer = bufferRef.current.get(seriesIndex);
42
- if (!buffer) {
43
- buffer = [];
44
- bufferRef.current.set(seriesIndex, buffer);
45
- }
46
- buffer.push(point);
47
- if (buffer.length >= bufferSize) {
48
- flush();
49
- } else if (!animationFrameRef.current) {
50
- animationFrameRef.current = requestAnimationFrame(() => {
51
- animationFrameRef.current = void 0;
52
- flush();
53
- });
54
- }
55
- }, [isPaused, bufferSize, flush]);
56
- const pushBatch = useCallback((seriesIndex, points) => {
57
- if (isPaused || points.length === 0) return;
58
- let buffer = bufferRef.current.get(seriesIndex);
59
- if (!buffer) {
60
- buffer = [];
61
- bufferRef.current.set(seriesIndex, buffer);
62
- }
63
- buffer.push(...points);
64
- flush();
65
- }, [isPaused, flush]);
66
- const clear = useCallback(() => {
67
- var _a;
68
- bufferRef.current.clear();
69
- pointCountRef.current = 0;
70
- (_a = chartRef.current) == null ? void 0 : _a.clearData();
71
- }, [chartRef]);
72
- const pause = useCallback(() => setIsPaused(true), []);
73
- const resume = useCallback(() => setIsPaused(false), []);
74
- useEffect(() => {
75
- return () => {
76
- if (animationFrameRef.current) {
77
- cancelAnimationFrame(animationFrameRef.current);
78
- }
79
- };
80
- }, []);
81
- return {
82
- push,
83
- pushBatch,
84
- clear,
85
- pause,
86
- resume,
87
- isPaused,
88
- pointCount: pointCountRef.current
89
- };
90
- }
91
- function decimateData(data, threshold) {
92
- if (data.length <= threshold) return data;
93
- const result = [];
94
- const bucketSize = (data.length - 2) / (threshold - 2);
95
- result.push(data[0]);
96
- let a = 0;
97
- for (let i = 0; i < threshold - 2; i++) {
98
- const bucketStart = Math.floor((i + 1) * bucketSize) + 1;
99
- const bucketEnd = Math.floor((i + 2) * bucketSize) + 1;
100
- const bucketLength = Math.min(bucketEnd, data.length) - bucketStart;
101
- let avgX = 0;
102
- let avgY = 0;
103
- const nextBucketStart = Math.floor((i + 2) * bucketSize) + 1;
104
- const nextBucketEnd = Math.min(Math.floor((i + 3) * bucketSize) + 1, data.length);
105
- const nextBucketLength = nextBucketEnd - nextBucketStart;
106
- for (let j = nextBucketStart; j < nextBucketEnd; j++) {
107
- const point = data[j];
108
- const [x, y] = getPointXY(point);
109
- avgX += x;
110
- avgY += y;
111
- }
112
- avgX /= nextBucketLength || 1;
113
- avgY /= nextBucketLength || 1;
114
- const [aX, aY] = getPointXY(data[a]);
115
- let maxArea = -1;
116
- let maxIdx = bucketStart;
117
- for (let j = bucketStart; j < bucketStart + bucketLength; j++) {
118
- const point = data[j];
119
- const [x, y] = getPointXY(point);
120
- const area = Math.abs(
121
- (aX - avgX) * (y - aY) - (aX - x) * (avgY - aY)
122
- );
123
- if (area > maxArea) {
124
- maxArea = area;
125
- maxIdx = j;
126
- }
127
- }
128
- result.push(data[maxIdx]);
129
- a = maxIdx;
130
- }
131
- result.push(data[data.length - 1]);
132
- return result;
133
- }
134
- function getPointXY(point) {
135
- if ("time" in point) {
136
- return [
137
- typeof point.time === "number" ? point.time : new Date(point.time).getTime(),
138
- point.value
139
- ];
140
- }
141
- return [
142
- typeof point.x === "number" ? point.x : 0,
143
- point.y
144
- ];
145
- }
146
- function useWebSocketStream(chartRef, options) {
147
- const {
148
- url,
149
- reconnect = true,
150
- reconnectDelay = 1e3,
151
- maxReconnects = 10,
152
- parseMessage,
153
- ...streamOptions
154
- } = options;
155
- const wsRef = useRef(null);
156
- const reconnectCountRef = useRef(0);
157
- const reconnectTimerRef = useRef();
158
- const [connectionState, setConnectionState] = useState("disconnected");
159
- const stream = useChartStream(chartRef, streamOptions);
160
- const connect = useCallback(() => {
161
- var _a;
162
- if (((_a = wsRef.current) == null ? void 0 : _a.readyState) === WebSocket.OPEN) return;
163
- setConnectionState("connecting");
164
- const ws = new WebSocket(url);
165
- ws.onopen = () => {
166
- setConnectionState("connected");
167
- reconnectCountRef.current = 0;
168
- };
169
- ws.onmessage = (event) => {
170
- try {
171
- const data = JSON.parse(event.data);
172
- const parsed = parseMessage ? parseMessage(data) : defaultParseMessage(data);
173
- if (parsed) {
174
- stream.push(parsed.seriesIndex, parsed.point);
175
- }
176
- } catch (e) {
177
- }
178
- };
179
- ws.onerror = () => {
180
- setConnectionState("error");
181
- };
182
- ws.onclose = () => {
183
- setConnectionState("disconnected");
184
- if (reconnect && reconnectCountRef.current < maxReconnects) {
185
- reconnectCountRef.current++;
186
- reconnectTimerRef.current = setTimeout(connect, reconnectDelay * reconnectCountRef.current);
187
- }
188
- };
189
- wsRef.current = ws;
190
- }, [url, reconnect, reconnectDelay, maxReconnects, parseMessage, stream]);
191
- const disconnect = useCallback(() => {
192
- var _a;
193
- clearTimeout(reconnectTimerRef.current);
194
- (_a = wsRef.current) == null ? void 0 : _a.close();
195
- wsRef.current = null;
196
- }, []);
197
- useEffect(() => {
198
- connect();
199
- return disconnect;
200
- }, [connect, disconnect]);
201
- return {
202
- ...stream,
203
- connectionState,
204
- connect,
205
- disconnect
206
- };
207
- }
208
- function defaultParseMessage(data) {
209
- if (typeof data !== "object" || data === null) return null;
210
- const obj = data;
211
- if ("time" in obj && "value" in obj) {
212
- return {
213
- seriesIndex: typeof obj.seriesIndex === "number" ? obj.seriesIndex : 0,
214
- point: {
215
- time: obj.time,
216
- value: obj.value
217
- }
218
- };
219
- }
220
- return null;
221
- }
222
- export {
223
- useChartStream,
224
- useWebSocketStream
225
- };
226
- //# sourceMappingURL=useChartStream.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"useChartStream.js","sources":["../../../../src/react/charts/unified/useChartStream.ts"],"sourcesContent":["/**\n * @zendir/ui - Real-time Chart Streaming Hook\n * \n * Provides efficient real-time data streaming for charts.\n * Handles buffering, throttling, and memory management.\n */\n\nimport { useRef, useCallback, useEffect, useState } from 'react';\nimport type { AstroChartHandle } from './AstroChart';\nimport type { TimeSeriesPoint, DataPoint } from './types';\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface StreamOptions {\n /** Maximum data points to keep */\n maxPoints?: number;\n /** Time window in milliseconds (alternative to maxPoints) */\n timeWindow?: number;\n /** Throttle interval in milliseconds */\n throttleMs?: number;\n /** Buffer size before flushing */\n bufferSize?: number;\n /** Enable data decimation for large datasets */\n decimate?: boolean;\n /** Decimation threshold (start decimating above this count) */\n decimateThreshold?: number;\n}\n\nexport interface StreamController {\n /** Push a single data point */\n push: (seriesIndex: number, point: TimeSeriesPoint | DataPoint) => void;\n /** Push multiple data points */\n pushBatch: (seriesIndex: number, points: (TimeSeriesPoint | DataPoint)[]) => void;\n /** Clear all data */\n clear: () => void;\n /** Pause streaming */\n pause: () => void;\n /** Resume streaming */\n resume: () => void;\n /** Get current state */\n isPaused: boolean;\n /** Get current point count */\n pointCount: number;\n}\n\n// =============================================================================\n// Hook Implementation\n// =============================================================================\n\nexport function useChartStream(\n chartRef: React.RefObject<AstroChartHandle>,\n options: StreamOptions = {}\n): StreamController {\n const {\n maxPoints: _maxPoints = 1000,\n timeWindow: _timeWindow,\n throttleMs = 16, // ~60fps\n bufferSize = 10,\n decimate = true,\n decimateThreshold = 5000,\n } = options;\n\n const bufferRef = useRef<Map<number, (TimeSeriesPoint | DataPoint)[]>>(new Map());\n const lastFlushRef = useRef<number>(0);\n const pointCountRef = useRef<number>(0);\n const [isPaused, setIsPaused] = useState(false);\n const animationFrameRef = useRef<number>();\n\n // Flush buffer to chart\n const flush = useCallback(() => {\n const chart = chartRef.current;\n if (!chart || isPaused) return;\n\n const now = Date.now();\n if (now - lastFlushRef.current < throttleMs) {\n // Schedule next flush\n animationFrameRef.current = requestAnimationFrame(flush);\n return;\n }\n\n bufferRef.current.forEach((points, seriesIndex) => {\n if (points.length === 0) return;\n\n // Apply decimation if needed\n let processedPoints = points;\n if (decimate && pointCountRef.current > decimateThreshold) {\n processedPoints = decimateData(points, Math.ceil(points.length / 2));\n }\n\n // Append each point\n processedPoints.forEach(point => {\n chart.appendData(seriesIndex, point);\n });\n\n pointCountRef.current += processedPoints.length;\n });\n\n // Clear buffer\n bufferRef.current.clear();\n lastFlushRef.current = now;\n }, [chartRef, isPaused, throttleMs, decimate, decimateThreshold]);\n\n // Push single point\n const push = useCallback((seriesIndex: number, point: TimeSeriesPoint | DataPoint) => {\n if (isPaused) return;\n\n let buffer = bufferRef.current.get(seriesIndex);\n if (!buffer) {\n buffer = [];\n bufferRef.current.set(seriesIndex, buffer);\n }\n\n buffer.push(point);\n\n // Flush if buffer is full\n if (buffer.length >= bufferSize) {\n flush();\n } else if (!animationFrameRef.current) {\n // Schedule flush\n animationFrameRef.current = requestAnimationFrame(() => {\n animationFrameRef.current = undefined;\n flush();\n });\n }\n }, [isPaused, bufferSize, flush]);\n\n // Push batch\n const pushBatch = useCallback((seriesIndex: number, points: (TimeSeriesPoint | DataPoint)[]) => {\n if (isPaused || points.length === 0) return;\n\n let buffer = bufferRef.current.get(seriesIndex);\n if (!buffer) {\n buffer = [];\n bufferRef.current.set(seriesIndex, buffer);\n }\n\n buffer.push(...points);\n flush();\n }, [isPaused, flush]);\n\n // Clear all data\n const clear = useCallback(() => {\n bufferRef.current.clear();\n pointCountRef.current = 0;\n chartRef.current?.clearData();\n }, [chartRef]);\n\n // Pause/resume\n const pause = useCallback(() => setIsPaused(true), []);\n const resume = useCallback(() => setIsPaused(false), []);\n\n // Cleanup\n useEffect(() => {\n return () => {\n if (animationFrameRef.current) {\n cancelAnimationFrame(animationFrameRef.current);\n }\n };\n }, []);\n\n return {\n push,\n pushBatch,\n clear,\n pause,\n resume,\n isPaused,\n pointCount: pointCountRef.current,\n };\n}\n\n// =============================================================================\n// Data Decimation\n// =============================================================================\n\n/**\n * Largest Triangle Three Buckets (LTTB) algorithm for data decimation\n * Preserves visual shape while reducing point count\n */\nfunction decimateData<T extends TimeSeriesPoint | DataPoint>(\n data: T[],\n threshold: number\n): T[] {\n if (data.length <= threshold) return data;\n\n const result: T[] = [];\n const bucketSize = (data.length - 2) / (threshold - 2);\n\n // Always include first point\n result.push(data[0]);\n\n let a = 0; // Previous selected point\n\n for (let i = 0; i < threshold - 2; i++) {\n // Calculate bucket range\n const bucketStart = Math.floor((i + 1) * bucketSize) + 1;\n const bucketEnd = Math.floor((i + 2) * bucketSize) + 1;\n const bucketLength = Math.min(bucketEnd, data.length) - bucketStart;\n\n // Calculate average point in next bucket\n let avgX = 0;\n let avgY = 0;\n const nextBucketStart = Math.floor((i + 2) * bucketSize) + 1;\n const nextBucketEnd = Math.min(Math.floor((i + 3) * bucketSize) + 1, data.length);\n const nextBucketLength = nextBucketEnd - nextBucketStart;\n\n for (let j = nextBucketStart; j < nextBucketEnd; j++) {\n const point = data[j];\n const [x, y] = getPointXY(point);\n avgX += x;\n avgY += y;\n }\n avgX /= nextBucketLength || 1;\n avgY /= nextBucketLength || 1;\n\n // Find point with largest triangle area\n const [aX, aY] = getPointXY(data[a]);\n let maxArea = -1;\n let maxIdx = bucketStart;\n\n for (let j = bucketStart; j < bucketStart + bucketLength; j++) {\n const point = data[j];\n const [x, y] = getPointXY(point);\n \n // Triangle area\n const area = Math.abs(\n (aX - avgX) * (y - aY) - (aX - x) * (avgY - aY)\n );\n\n if (area > maxArea) {\n maxArea = area;\n maxIdx = j;\n }\n }\n\n result.push(data[maxIdx]);\n a = maxIdx;\n }\n\n // Always include last point\n result.push(data[data.length - 1]);\n\n return result;\n}\n\nfunction getPointXY(point: TimeSeriesPoint | DataPoint): [number, number] {\n if ('time' in point) {\n return [\n typeof point.time === 'number' ? point.time : new Date(point.time).getTime(),\n point.value,\n ];\n }\n return [\n typeof point.x === 'number' ? point.x : 0,\n point.y,\n ];\n}\n\n// =============================================================================\n// WebSocket Stream Hook\n// =============================================================================\n\nexport interface WebSocketStreamOptions extends StreamOptions {\n /** WebSocket URL */\n url: string;\n /** Reconnect on disconnect */\n reconnect?: boolean;\n /** Reconnect delay in milliseconds */\n reconnectDelay?: number;\n /** Maximum reconnect attempts */\n maxReconnects?: number;\n /** Message parser */\n parseMessage?: (data: unknown) => { seriesIndex: number; point: TimeSeriesPoint | DataPoint } | null;\n}\n\nexport interface WebSocketStreamController extends StreamController {\n /** WebSocket connection state */\n connectionState: 'connecting' | 'connected' | 'disconnected' | 'error';\n /** Manually connect */\n connect: () => void;\n /** Manually disconnect */\n disconnect: () => void;\n}\n\nexport function useWebSocketStream(\n chartRef: React.RefObject<AstroChartHandle>,\n options: WebSocketStreamOptions\n): WebSocketStreamController {\n const {\n url,\n reconnect = true,\n reconnectDelay = 1000,\n maxReconnects = 10,\n parseMessage,\n ...streamOptions\n } = options;\n\n const wsRef = useRef<WebSocket | null>(null);\n const reconnectCountRef = useRef(0);\n const reconnectTimerRef = useRef<ReturnType<typeof setTimeout>>();\n const [connectionState, setConnectionState] = useState<WebSocketStreamController['connectionState']>('disconnected');\n \n const stream = useChartStream(chartRef, streamOptions);\n\n const connect = useCallback(() => {\n if (wsRef.current?.readyState === WebSocket.OPEN) return;\n\n setConnectionState('connecting');\n const ws = new WebSocket(url);\n\n ws.onopen = () => {\n setConnectionState('connected');\n reconnectCountRef.current = 0;\n };\n\n ws.onmessage = (event) => {\n try {\n const data = JSON.parse(event.data);\n const parsed = parseMessage \n ? parseMessage(data) \n : defaultParseMessage(data);\n \n if (parsed) {\n stream.push(parsed.seriesIndex, parsed.point);\n }\n } catch (e) {\n if (import.meta.env.DEV) {\n console.error('Failed to parse WebSocket message:', e);\n }\n }\n };\n\n ws.onerror = () => {\n setConnectionState('error');\n };\n\n ws.onclose = () => {\n setConnectionState('disconnected');\n \n if (reconnect && reconnectCountRef.current < maxReconnects) {\n reconnectCountRef.current++;\n reconnectTimerRef.current = setTimeout(connect, reconnectDelay * reconnectCountRef.current);\n }\n };\n\n wsRef.current = ws;\n }, [url, reconnect, reconnectDelay, maxReconnects, parseMessage, stream]);\n\n const disconnect = useCallback(() => {\n clearTimeout(reconnectTimerRef.current);\n wsRef.current?.close();\n wsRef.current = null;\n }, []);\n\n // Auto-connect on mount\n useEffect(() => {\n connect();\n return disconnect;\n }, [connect, disconnect]);\n\n return {\n ...stream,\n connectionState,\n connect,\n disconnect,\n };\n}\n\nfunction defaultParseMessage(data: unknown): { seriesIndex: number; point: TimeSeriesPoint } | null {\n if (typeof data !== 'object' || data === null) return null;\n \n const obj = data as Record<string, unknown>;\n \n if ('time' in obj && 'value' in obj) {\n return {\n seriesIndex: typeof obj.seriesIndex === 'number' ? obj.seriesIndex : 0,\n point: {\n time: obj.time as number,\n value: obj.value as number,\n },\n };\n }\n \n return null;\n}\n"],"names":[],"mappings":"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"}
@@ -1,250 +0,0 @@
1
- import { jsxs, jsx } from "react/jsx-runtime";
2
- import { useMemo } from "react";
3
- import { useTheme } from "../theme/ThemeProvider.js";
4
- const EclipseTimerCard = ({
5
- eclipse,
6
- orbitalPeriodMinutes,
7
- orbitPhase = 0,
8
- compact = false,
9
- className = ""
10
- }) => {
11
- const { tokens, theme } = useTheme();
12
- const isTransparentTheme = theme === "transparent" || theme === "transparent-bold" || theme === "transparent-minimal";
13
- const cardBg = isTransparentTheme ? "transparent" : tokens.colors.background.surface;
14
- const cardGlass = isTransparentTheme ? {
15
- backdropFilter: "blur(12px)",
16
- WebkitBackdropFilter: "blur(12px)"
17
- } : {};
18
- const ECLIPSE_COLORS = {
19
- sunlight: "#ffd54f",
20
- penumbra: "#78909c"
21
- };
22
- const { sunlightArc, penumbraArc, umbraArc } = useMemo(() => {
23
- const totalPeriod = orbitalPeriodMinutes * 60;
24
- const sunlight = eclipse.sunlightDuration / totalPeriod;
25
- const umbraSec = eclipse.umbraDuration ?? eclipse.eclipseDuration * 0.5;
26
- const penumbraSec = eclipse.penumbraDuration ?? eclipse.eclipseDuration * 0.5;
27
- const umbra = umbraSec / totalPeriod;
28
- const penumbra = penumbraSec / totalPeriod;
29
- return {
30
- sunlightArc: sunlight * 100,
31
- penumbraArc: penumbra * 100,
32
- umbraArc: umbra * 100
33
- };
34
- }, [eclipse, orbitalPeriodMinutes]);
35
- const formatTime = (seconds) => {
36
- if (seconds < 0) return "--:--";
37
- const mins = Math.floor(seconds / 60);
38
- const secs = Math.floor(seconds % 60);
39
- return `${mins.toString().padStart(2, "0")}:${secs.toString().padStart(2, "0")}`;
40
- };
41
- const radius = 45;
42
- const circumference = 2 * Math.PI * radius;
43
- const sunlightDash = sunlightArc / 100 * circumference;
44
- const penumbraDash = penumbraArc / 100 * circumference;
45
- const umbraDash = umbraArc / 100 * circumference;
46
- const cursorRotation = orbitPhase * 360 - 90;
47
- const sizeStyle = compact ? { width: "128px", height: "128px" } : { width: "192px", height: "192px" };
48
- const cardStyle = {
49
- backgroundColor: cardBg,
50
- ...cardGlass,
51
- border: "none",
52
- borderRadius: tokens.borderRadius.lg,
53
- padding: tokens.spacing.md,
54
- fontFamily: tokens.typography.fontFamily.primary,
55
- color: tokens.colors.text.primary
56
- };
57
- return /* @__PURE__ */ jsxs("div", { className, style: cardStyle, children: [
58
- !compact && /* @__PURE__ */ jsxs("div", { style: {
59
- display: "flex",
60
- alignItems: "center",
61
- justifyContent: "space-between",
62
- marginBottom: tokens.spacing.md
63
- }, children: [
64
- /* @__PURE__ */ jsxs("div", { children: [
65
- /* @__PURE__ */ jsx("h3", { style: {
66
- fontWeight: tokens.typography.fontWeight.semibold,
67
- color: tokens.colors.text.primary,
68
- fontSize: tokens.typography.fontSize.md,
69
- margin: 0
70
- }, children: "Eclipse timer" }),
71
- /* @__PURE__ */ jsx("p", { style: {
72
- color: tokens.colors.text.tertiary,
73
- fontSize: tokens.typography.fontSize.xs,
74
- margin: 0,
75
- marginTop: tokens.spacing.xs
76
- }, children: "Orbit phase" })
77
- ] }),
78
- /* @__PURE__ */ jsx("span", { style: {
79
- padding: `${tokens.spacing.xs} ${tokens.spacing.sm}`,
80
- borderRadius: tokens.borderRadius.sm,
81
- fontSize: tokens.typography.fontSize.xxs,
82
- // 0.625rem / 10px (AstroUXDS compact)
83
- fontWeight: tokens.typography.fontWeight.medium,
84
- letterSpacing: "0.5px",
85
- backgroundColor: eclipse.inEclipse ? `${tokens.colors.status.off}20` : `${tokens.colors.status.caution}20`,
86
- color: eclipse.inEclipse ? tokens.colors.status.off : tokens.colors.status.caution
87
- }, children: eclipse.inEclipse ? "Eclipse" : "Sunlit" })
88
- ] }),
89
- /* @__PURE__ */ jsx("div", { style: { display: "flex", justifyContent: "center" }, children: /* @__PURE__ */ jsxs("div", { style: { position: "relative", ...sizeStyle }, children: [
90
- /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 100 100", style: { width: "100%", height: "100%", transform: "rotate(-90deg)" }, children: [
91
- /* @__PURE__ */ jsx(
92
- "circle",
93
- {
94
- cx: "50",
95
- cy: "50",
96
- r: radius,
97
- fill: "none",
98
- stroke: tokens.colors.border.muted,
99
- strokeWidth: "8"
100
- }
101
- ),
102
- /* @__PURE__ */ jsx(
103
- "circle",
104
- {
105
- cx: "50",
106
- cy: "50",
107
- r: radius,
108
- fill: "none",
109
- stroke: ECLIPSE_COLORS.sunlight,
110
- strokeWidth: "8",
111
- strokeDasharray: `${sunlightDash} ${circumference}`,
112
- strokeDashoffset: "0",
113
- strokeLinecap: "round",
114
- style: { filter: `drop-shadow(0 0 8px ${ECLIPSE_COLORS.sunlight}40)` }
115
- }
116
- ),
117
- /* @__PURE__ */ jsx(
118
- "circle",
119
- {
120
- cx: "50",
121
- cy: "50",
122
- r: radius,
123
- fill: "none",
124
- stroke: ECLIPSE_COLORS.penumbra,
125
- strokeWidth: "8",
126
- strokeDasharray: `${penumbraDash} ${circumference}`,
127
- strokeDashoffset: `${-sunlightDash}`,
128
- strokeLinecap: "round"
129
- }
130
- ),
131
- /* @__PURE__ */ jsx(
132
- "circle",
133
- {
134
- cx: "50",
135
- cy: "50",
136
- r: radius,
137
- fill: "none",
138
- stroke: tokens.colors.background.base,
139
- strokeWidth: "8",
140
- strokeDasharray: `${umbraDash} ${circumference}`,
141
- strokeDashoffset: `${-(sunlightDash + penumbraDash)}`,
142
- strokeLinecap: "round"
143
- }
144
- )
145
- ] }),
146
- /* @__PURE__ */ jsx(
147
- "div",
148
- {
149
- style: {
150
- position: "absolute",
151
- inset: 0,
152
- transition: "transform 1s",
153
- transform: `rotate(${cursorRotation}deg)`
154
- },
155
- children: /* @__PURE__ */ jsx("div", { style: {
156
- position: "absolute",
157
- top: 0,
158
- left: "50%",
159
- transform: "translate(-50%, -4px)"
160
- }, children: /* @__PURE__ */ jsx("div", { style: {
161
- width: compact ? "12px" : "16px",
162
- height: compact ? "12px" : "16px",
163
- backgroundColor: "#fff",
164
- borderRadius: "50%",
165
- border: `2px solid ${tokens.colors.status.standby}`,
166
- boxShadow: `0 0 12px ${tokens.colors.status.standby}`
167
- } }) })
168
- }
169
- ),
170
- /* @__PURE__ */ jsxs("div", { style: {
171
- position: "absolute",
172
- inset: 0,
173
- display: "flex",
174
- flexDirection: "column",
175
- alignItems: "center",
176
- justifyContent: "center"
177
- }, children: [
178
- /* @__PURE__ */ jsx("span", { style: {
179
- fontSize: compact ? "8px" : tokens.typography.fontSize.xs,
180
- color: tokens.colors.text.tertiary,
181
- letterSpacing: "0.5px"
182
- }, children: eclipse.inEclipse ? "Sunlight in" : "Eclipse in" }),
183
- /* @__PURE__ */ jsx("span", { style: {
184
- fontSize: compact ? tokens.typography.fontSize.lg : tokens.typography.fontSize.xxl,
185
- fontFamily: tokens.typography.fontFamily.mono,
186
- fontWeight: tokens.typography.fontWeight.bold,
187
- fontVariantNumeric: "tabular-nums",
188
- color: eclipse.inEclipse ? tokens.colors.status.caution : tokens.colors.text.tertiary
189
- }, children: formatTime(eclipse.inEclipse ? eclipse.timeToSunlight ?? 0 : eclipse.timeToEclipse) }),
190
- !compact && /* @__PURE__ */ jsxs("span", { style: {
191
- fontSize: tokens.typography.fontSize.xxs,
192
- // 0.625rem / 10px (AstroUXDS compact)
193
- color: tokens.colors.text.tertiary,
194
- marginTop: tokens.spacing.xs
195
- }, children: [
196
- "Period: ",
197
- orbitalPeriodMinutes.toFixed(1),
198
- "m"
199
- ] })
200
- ] })
201
- ] }) }),
202
- /* @__PURE__ */ jsxs("div", { style: {
203
- display: "grid",
204
- gridTemplateColumns: "repeat(2, 1fr)",
205
- gap: tokens.spacing.sm,
206
- marginTop: tokens.spacing.md,
207
- fontSize: compact ? tokens.typography.fontSize.xxs : tokens.typography.fontSize.xs
208
- // AstroUXDS compliant
209
- }, children: [
210
- /* @__PURE__ */ jsxs("div", { style: {
211
- backgroundColor: tokens.colors.background.base,
212
- borderRadius: tokens.borderRadius.md,
213
- padding: tokens.spacing.sm,
214
- textAlign: "center"
215
- }, children: [
216
- /* @__PURE__ */ jsxs("div", { style: {
217
- color: tokens.colors.status.caution,
218
- fontWeight: tokens.typography.fontWeight.bold,
219
- fontFamily: tokens.typography.fontFamily.mono,
220
- fontVariantNumeric: "tabular-nums"
221
- }, children: [
222
- Math.round(eclipse.sunlightDuration / 60),
223
- "m"
224
- ] }),
225
- /* @__PURE__ */ jsx("div", { style: { color: tokens.colors.text.tertiary }, children: "Daylight" })
226
- ] }),
227
- /* @__PURE__ */ jsxs("div", { style: {
228
- backgroundColor: tokens.colors.background.base,
229
- borderRadius: tokens.borderRadius.md,
230
- padding: tokens.spacing.sm,
231
- textAlign: "center"
232
- }, children: [
233
- /* @__PURE__ */ jsxs("div", { style: {
234
- color: tokens.colors.text.tertiary,
235
- fontWeight: tokens.typography.fontWeight.bold,
236
- fontFamily: tokens.typography.fontFamily.mono,
237
- fontVariantNumeric: "tabular-nums"
238
- }, children: [
239
- Math.round(((eclipse.umbraDuration ?? 0) + (eclipse.penumbraDuration ?? 0)) / 60),
240
- "m"
241
- ] }),
242
- /* @__PURE__ */ jsx("div", { style: { color: tokens.colors.text.tertiary }, children: "Eclipse" })
243
- ] })
244
- ] })
245
- ] });
246
- };
247
- export {
248
- EclipseTimerCard
249
- };
250
- //# sourceMappingURL=EclipseTimerCard.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"EclipseTimerCard.js","sources":["../../../src/react/visualizations/EclipseTimerCard.tsx"],"sourcesContent":["/**\n * EclipseTimerCard - Circular orbit phase visualization\n * \n * Shows the spacecraft's position in its orbital day/night cycle with\n * visual sectors for sunlight, penumbra, and umbra (eclipse).\n * \n * Astro UX Compliance:\n * - Uses official Astro status colors\n * - Sentence-case labels\n * - Tabular numbers for data\n * - Roboto/Roboto Mono typography\n * - 4px-based spacing\n */\n\nimport React, { useMemo } from 'react';\nimport type { EclipseInfo } from '../types';\nimport { useTheme } from '../theme';\n\nexport interface EclipseTimerCardProps {\n /** Eclipse information from API */\n eclipse: EclipseInfo;\n /** Orbital period in minutes */\n orbitalPeriodMinutes: number;\n /** Current phase in orbit (0-1) */\n orbitPhase?: number;\n /** Compact mode for smaller displays */\n compact?: boolean;\n /** Custom class name */\n className?: string;\n}\n\nexport const EclipseTimerCard: React.FC<EclipseTimerCardProps> = ({\n eclipse,\n orbitalPeriodMinutes,\n orbitPhase = 0,\n compact = false,\n className = '',\n}) => {\n const { tokens, theme } = useTheme();\n const isTransparentTheme = theme === 'transparent' || theme === 'transparent-bold' || theme === 'transparent-minimal';\n // Use transparent background with blur for transparent themes (matching Timeline)\n const cardBg = isTransparentTheme ? 'transparent' : tokens.colors.background.surface;\n const cardGlass = isTransparentTheme ? { \n backdropFilter: 'blur(12px)' as const, \n WebkitBackdropFilter: 'blur(12px)' as const,\n } : {};\n\n // Medium-soft colors for eclipse phases (readable, not too pale)\n const ECLIPSE_COLORS = {\n sunlight: '#ffd54f',\n penumbra: '#78909c',\n umbra: '#546e7a',\n };\n const { sunlightArc, penumbraArc, umbraArc } = useMemo(() => {\n const totalPeriod = orbitalPeriodMinutes * 60; // seconds\n const sunlight = eclipse.sunlightDuration / totalPeriod;\n const umbraSec = eclipse.umbraDuration ?? eclipse.eclipseDuration * 0.5;\n const penumbraSec = eclipse.penumbraDuration ?? eclipse.eclipseDuration * 0.5;\n const umbra = umbraSec / totalPeriod;\n const penumbra = penumbraSec / totalPeriod;\n return {\n sunlightArc: sunlight * 100,\n penumbraArc: penumbra * 100,\n umbraArc: umbra * 100,\n };\n }, [eclipse, orbitalPeriodMinutes]);\n\n // Format time for display (Astro: tabular numbers)\n const formatTime = (seconds: number): string => {\n if (seconds < 0) return '--:--';\n const mins = Math.floor(seconds / 60);\n const secs = Math.floor(seconds % 60);\n return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;\n };\n\n // Calculate SVG arc parameters\n const radius = 45;\n const circumference = 2 * Math.PI * radius;\n \n const sunlightDash = (sunlightArc / 100) * circumference;\n const penumbraDash = (penumbraArc / 100) * circumference;\n const umbraDash = (umbraArc / 100) * circumference;\n\n // Cursor rotation based on orbit phase\n const cursorRotation = orbitPhase * 360 - 90; // -90 to start at top\n\n const sizeStyle: React.CSSProperties = compact \n ? { width: '128px', height: '128px' } \n : { width: '192px', height: '192px' };\n\n // Astro UX compliant card styling (transparent theme: glass background)\n const cardStyle: React.CSSProperties = {\n backgroundColor: cardBg,\n ...cardGlass,\n border: 'none',\n borderRadius: tokens.borderRadius.lg,\n padding: tokens.spacing.md,\n fontFamily: tokens.typography.fontFamily.primary,\n color: tokens.colors.text.primary,\n };\n\n return (\n <div className={className} style={cardStyle}>\n {!compact && (\n <div style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n marginBottom: tokens.spacing.md,\n }}>\n <div>\n {/* Astro: Sentence-case labels */}\n <h3 style={{ \n fontWeight: tokens.typography.fontWeight.semibold, \n color: tokens.colors.text.primary, \n fontSize: tokens.typography.fontSize.md,\n margin: 0,\n }}>\n Eclipse timer\n </h3>\n <p style={{ \n color: tokens.colors.text.tertiary, \n fontSize: tokens.typography.fontSize.xs,\n margin: 0,\n marginTop: tokens.spacing.xs,\n }}>\n Orbit phase\n </p>\n </div>\n {/* Status badge - Astro style */}\n <span style={{\n padding: `${tokens.spacing.xs} ${tokens.spacing.sm}`,\n borderRadius: tokens.borderRadius.sm,\n fontSize: tokens.typography.fontSize.xxs, // 0.625rem / 10px (AstroUXDS compact)\n fontWeight: tokens.typography.fontWeight.medium,\n letterSpacing: '0.5px',\n backgroundColor: eclipse.inEclipse \n ? `${tokens.colors.status.off}20` \n : `${tokens.colors.status.caution}20`,\n color: eclipse.inEclipse \n ? tokens.colors.status.off \n : tokens.colors.status.caution,\n }}>\n {eclipse.inEclipse ? 'Eclipse' : 'Sunlit'}\n </span>\n </div>\n )}\n\n <div style={{ display: 'flex', justifyContent: 'center' }}>\n <div style={{ position: 'relative', ...sizeStyle }}>\n {/* SVG Ring */}\n <svg viewBox=\"0 0 100 100\" style={{ width: '100%', height: '100%', transform: 'rotate(-90deg)' }}>\n {/* Background ring */}\n <circle \n cx=\"50\" cy=\"50\" r={radius} \n fill=\"none\" \n stroke={tokens.colors.border.muted}\n strokeWidth=\"8\"\n />\n \n {/* Sunlight sector - soft amber */}\n <circle \n cx=\"50\" cy=\"50\" r={radius}\n fill=\"none\"\n stroke={ECLIPSE_COLORS.sunlight}\n strokeWidth=\"8\"\n strokeDasharray={`${sunlightDash} ${circumference}`}\n strokeDashoffset=\"0\"\n strokeLinecap=\"round\"\n style={{ filter: `drop-shadow(0 0 8px ${ECLIPSE_COLORS.sunlight}40)` }}\n />\n \n {/* Penumbra sector */}\n <circle \n cx=\"50\" cy=\"50\" r={radius}\n fill=\"none\"\n stroke={ECLIPSE_COLORS.penumbra}\n strokeWidth=\"8\"\n strokeDasharray={`${penumbraDash} ${circumference}`}\n strokeDashoffset={`${-sunlightDash}`}\n strokeLinecap=\"round\"\n />\n \n {/* Umbra sector - dark */}\n <circle \n cx=\"50\" cy=\"50\" r={radius}\n fill=\"none\"\n stroke={tokens.colors.background.base}\n strokeWidth=\"8\"\n strokeDasharray={`${umbraDash} ${circumference}`}\n strokeDashoffset={`${-(sunlightDash + penumbraDash)}`}\n strokeLinecap=\"round\"\n />\n </svg>\n\n {/* Position cursor */}\n <div \n style={{ \n position: 'absolute',\n inset: 0,\n transition: 'transform 1s',\n transform: `rotate(${cursorRotation}deg)`,\n }}\n >\n <div style={{\n position: 'absolute',\n top: 0,\n left: '50%',\n transform: 'translate(-50%, -4px)',\n }}>\n <div style={{\n width: compact ? '12px' : '16px',\n height: compact ? '12px' : '16px',\n backgroundColor: '#fff',\n borderRadius: '50%',\n border: `2px solid ${tokens.colors.status.standby}`,\n boxShadow: `0 0 12px ${tokens.colors.status.standby}`,\n }} />\n </div>\n </div>\n\n {/* Center content */}\n <div style={{\n position: 'absolute',\n inset: 0,\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n justifyContent: 'center',\n }}>\n {/* Astro: Sentence-case labels (Tier 3 compliance) */}\n <span style={{ \n fontSize: compact ? '8px' : tokens.typography.fontSize.xs, \n color: tokens.colors.text.tertiary,\n letterSpacing: '0.5px',\n }}>\n {eclipse.inEclipse ? 'Sunlight in' : 'Eclipse in'}\n </span>\n {/* Astro: Tabular numbers */}\n <span style={{ \n fontSize: compact ? tokens.typography.fontSize.lg : tokens.typography.fontSize.xxl, \n fontFamily: tokens.typography.fontFamily.mono,\n fontWeight: tokens.typography.fontWeight.bold,\n fontVariantNumeric: 'tabular-nums',\n color: eclipse.inEclipse ? tokens.colors.status.caution : tokens.colors.text.tertiary,\n }}>\n {formatTime(eclipse.inEclipse ? (eclipse.timeToSunlight ?? 0) : eclipse.timeToEclipse)}\n </span>\n {!compact && (\n <span style={{ \n fontSize: tokens.typography.fontSize.xxs, // 0.625rem / 10px (AstroUXDS compact)\n color: tokens.colors.text.tertiary,\n marginTop: tokens.spacing.xs,\n }}>\n Period: {orbitalPeriodMinutes.toFixed(1)}m\n </span>\n )}\n </div>\n </div>\n </div>\n\n {/* Stats row - Astro: Sentence-case, tabular numbers */}\n <div style={{\n display: 'grid',\n gridTemplateColumns: 'repeat(2, 1fr)',\n gap: tokens.spacing.sm,\n marginTop: tokens.spacing.md,\n fontSize: compact ? tokens.typography.fontSize.xxs : tokens.typography.fontSize.xs, // AstroUXDS compliant\n }}>\n <div style={{\n backgroundColor: tokens.colors.background.base,\n borderRadius: tokens.borderRadius.md,\n padding: tokens.spacing.sm,\n textAlign: 'center',\n }}>\n <div style={{ \n color: tokens.colors.status.caution, \n fontWeight: tokens.typography.fontWeight.bold,\n fontFamily: tokens.typography.fontFamily.mono,\n fontVariantNumeric: 'tabular-nums',\n }}>\n {Math.round(eclipse.sunlightDuration / 60)}m\n </div>\n <div style={{ color: tokens.colors.text.tertiary }}>Daylight</div>\n </div>\n <div style={{\n backgroundColor: tokens.colors.background.base,\n borderRadius: tokens.borderRadius.md,\n padding: tokens.spacing.sm,\n textAlign: 'center',\n }}>\n <div style={{ \n color: tokens.colors.text.tertiary, \n fontWeight: tokens.typography.fontWeight.bold,\n fontFamily: tokens.typography.fontFamily.mono,\n fontVariantNumeric: 'tabular-nums',\n }}>\n {Math.round(((eclipse.umbraDuration ?? 0) + (eclipse.penumbraDuration ?? 0)) / 60)}m\n </div>\n <div style={{ color: tokens.colors.text.tertiary }}>Eclipse</div>\n </div>\n </div>\n </div>\n );\n};\n\nexport default EclipseTimerCard;\n\n\n\n\n"],"names":[],"mappings":"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"}