@zendir/ui 0.1.8 → 0.1.10
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/dist/index.js +0 -169
- package/dist/index.js.map +1 -1
- package/dist/react/context/DisplaySettingsContext.js +0 -12
- package/dist/react/context/DisplaySettingsContext.js.map +1 -1
- package/dist/react/core/AstroIcon.js +1 -816
- package/dist/react/core/AstroIcon.js.map +1 -1
- package/dist/react/core/index.d.ts +0 -1
- package/dist/react/index.d.ts +2 -42
- package/dist/react/utils/index.js +0 -8
- package/dist/react/utils/index.js.map +1 -1
- package/dist/react.js +0 -169
- package/dist/react.js.map +1 -1
- package/package.json +1 -1
- package/dist/react/3d/EarthViewer.js +0 -836
- package/dist/react/3d/EarthViewer.js.map +0 -1
- package/dist/react/3d/SolarSystemViewer.js +0 -372
- package/dist/react/3d/SolarSystemViewer.js.map +0 -1
- package/dist/react/3d/ZenSpace3D.js +0 -1253
- package/dist/react/3d/ZenSpace3D.js.map +0 -1
- package/dist/react/3d/ZenSpace3DCesium.js +0 -186
- package/dist/react/3d/ZenSpace3DCesium.js.map +0 -1
- package/dist/react/3d/ZenSpace3DShaders.js +0 -94
- package/dist/react/3d/ZenSpace3DShaders.js.map +0 -1
- package/dist/react/3d/ZenSpace3DUtils.js +0 -213
- package/dist/react/3d/ZenSpace3DUtils.js.map +0 -1
- package/dist/react/3d/threeLoader.js +0 -18
- package/dist/react/3d/threeLoader.js.map +0 -1
- package/dist/react/cards/AccessCard.js +0 -410
- package/dist/react/cards/AccessCard.js.map +0 -1
- package/dist/react/cards/OrbitCard.js +0 -372
- package/dist/react/cards/OrbitCard.js.map +0 -1
- package/dist/react/cards/SpacecraftCard.js +0 -941
- package/dist/react/cards/SpacecraftCard.js.map +0 -1
- package/dist/react/cards/TelemetryCard.js +0 -742
- package/dist/react/cards/TelemetryCard.js.map +0 -1
- package/dist/react/cards/TelemetryStreamCard.js +0 -309
- package/dist/react/cards/TelemetryStreamCard.js.map +0 -1
- package/dist/react/charts/GroundTrackMap.js +0 -1123
- package/dist/react/charts/GroundTrackMap.js.map +0 -1
- package/dist/react/charts/GroundTrackMapLeaflet.js +0 -571
- package/dist/react/charts/GroundTrackMapLeaflet.js.map +0 -1
- package/dist/react/charts/groundTrackMapLeafletTiles.js +0 -11
- package/dist/react/charts/groundTrackMapLeafletTiles.js.map +0 -1
- package/dist/react/charts/groundTrackMapLeafletUtils.js +0 -109
- package/dist/react/charts/groundTrackMapLeafletUtils.js.map +0 -1
- package/dist/react/charts/unified/AstroChart.js +0 -1405
- package/dist/react/charts/unified/AstroChart.js.map +0 -1
- package/dist/react/charts/unified/PowerOverviewChart.js +0 -488
- package/dist/react/charts/unified/PowerOverviewChart.js.map +0 -1
- package/dist/react/charts/unified/domain.js +0 -3168
- package/dist/react/charts/unified/domain.js.map +0 -1
- package/dist/react/charts/unified/generators.js +0 -518
- package/dist/react/charts/unified/generators.js.map +0 -1
- package/dist/react/charts/unified/presets.js +0 -999
- package/dist/react/charts/unified/presets.js.map +0 -1
- package/dist/react/charts/unified/sync.js +0 -219
- package/dist/react/charts/unified/sync.js.map +0 -1
- package/dist/react/charts/unified/theme.js +0 -562
- package/dist/react/charts/unified/theme.js.map +0 -1
- package/dist/react/charts/unified/useChartStream.js +0 -226
- package/dist/react/charts/unified/useChartStream.js.map +0 -1
- package/dist/react/chatgpt/AppCard.js +0 -306
- package/dist/react/chatgpt/AppCard.js.map +0 -1
- package/dist/react/chatgpt/index.js +0 -166
- package/dist/react/chatgpt/index.js.map +0 -1
- package/dist/react/hooks/useSpacecraftPosition.js +0 -89
- package/dist/react/hooks/useSpacecraftPosition.js.map +0 -1
- package/dist/react/hooks/useTelemetry.js +0 -73
- package/dist/react/hooks/useTelemetry.js.map +0 -1
- package/dist/react/hooks/useZendirSession.js +0 -148
- package/dist/react/hooks/useZendirSession.js.map +0 -1
- package/dist/react/visualizations/EclipseTimerCard.js +0 -250
- package/dist/react/visualizations/EclipseTimerCard.js.map +0 -1
- package/dist/react/visualizations/LinkBudgetCard.js +0 -444
- package/dist/react/visualizations/LinkBudgetCard.js.map +0 -1
- package/dist/react/visualizations/NavBallCard.js +0 -243
- package/dist/react/visualizations/NavBallCard.js.map +0 -1
- package/dist/react/visualizations/PropulsionCard.js +0 -298
- package/dist/react/visualizations/PropulsionCard.js.map +0 -1
- package/dist/react/visualizations/SensorFootprintCard.js +0 -326
- package/dist/react/visualizations/SensorFootprintCard.js.map +0 -1
- package/dist/react/visualizations/ThermalHeatmapCard.js +0 -372
- package/dist/react/visualizations/ThermalHeatmapCard.js.map +0 -1
- package/dist/shaders/atmosphere.frag.js +0 -5
- package/dist/shaders/atmosphere.frag.js.map +0 -1
- package/dist/shaders/atmosphere.vert.js +0 -5
- package/dist/shaders/atmosphere.vert.js.map +0 -1
- package/dist/shaders/stars.frag.js +0 -5
- package/dist/shaders/stars.frag.js.map +0 -1
- package/dist/shaders/stars.vert.js +0 -5
- package/dist/shaders/stars.vert.js.map +0 -1
- 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":";AAmDO,SAAS,eACd,UACA,UAAyB,IACP;AAClB,QAAM;AAAA,IACJ,WAAW,aAAa;AAAA,IACxB,YAAY;AAAA,IACZ,aAAa;AAAA;AAAA,IACb,aAAa;AAAA,IACb,WAAW;AAAA,IACX,oBAAoB;AAAA,EAAA,IAClB;AAEJ,QAAM,YAAY,OAAqD,oBAAI,KAAK;AAChF,QAAM,eAAe,OAAe,CAAC;AACrC,QAAM,gBAAgB,OAAe,CAAC;AACtC,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,KAAK;AAC9C,QAAM,oBAAoB,OAAA;AAG1B,QAAM,QAAQ,YAAY,MAAM;AAC9B,UAAM,QAAQ,SAAS;AACvB,QAAI,CAAC,SAAS,SAAU;AAExB,UAAM,MAAM,KAAK,IAAA;AACjB,QAAI,MAAM,aAAa,UAAU,YAAY;AAE3C,wBAAkB,UAAU,sBAAsB,KAAK;AACvD;AAAA,IACF;AAEA,cAAU,QAAQ,QAAQ,CAAC,QAAQ,gBAAgB;AACjD,UAAI,OAAO,WAAW,EAAG;AAGzB,UAAI,kBAAkB;AACtB,UAAI,YAAY,cAAc,UAAU,mBAAmB;AACzD,0BAAkB,aAAa,QAAQ,KAAK,KAAK,OAAO,SAAS,CAAC,CAAC;AAAA,MACrE;AAGA,sBAAgB,QAAQ,CAAA,UAAS;AAC/B,cAAM,WAAW,aAAa,KAAK;AAAA,MACrC,CAAC;AAED,oBAAc,WAAW,gBAAgB;AAAA,IAC3C,CAAC;AAGD,cAAU,QAAQ,MAAA;AAClB,iBAAa,UAAU;AAAA,EACzB,GAAG,CAAC,UAAU,UAAU,YAAY,UAAU,iBAAiB,CAAC;AAGhE,QAAM,OAAO,YAAY,CAAC,aAAqB,UAAuC;AACpF,QAAI,SAAU;AAEd,QAAI,SAAS,UAAU,QAAQ,IAAI,WAAW;AAC9C,QAAI,CAAC,QAAQ;AACX,eAAS,CAAA;AACT,gBAAU,QAAQ,IAAI,aAAa,MAAM;AAAA,IAC3C;AAEA,WAAO,KAAK,KAAK;AAGjB,QAAI,OAAO,UAAU,YAAY;AAC/B,YAAA;AAAA,IACF,WAAW,CAAC,kBAAkB,SAAS;AAErC,wBAAkB,UAAU,sBAAsB,MAAM;AACtD,0BAAkB,UAAU;AAC5B,cAAA;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF,GAAG,CAAC,UAAU,YAAY,KAAK,CAAC;AAGhC,QAAM,YAAY,YAAY,CAAC,aAAqB,WAA4C;AAC9F,QAAI,YAAY,OAAO,WAAW,EAAG;AAErC,QAAI,SAAS,UAAU,QAAQ,IAAI,WAAW;AAC9C,QAAI,CAAC,QAAQ;AACX,eAAS,CAAA;AACT,gBAAU,QAAQ,IAAI,aAAa,MAAM;AAAA,IAC3C;AAEA,WAAO,KAAK,GAAG,MAAM;AACrB,UAAA;AAAA,EACF,GAAG,CAAC,UAAU,KAAK,CAAC;AAGpB,QAAM,QAAQ,YAAY,MAAM;;AAC9B,cAAU,QAAQ,MAAA;AAClB,kBAAc,UAAU;AACxB,mBAAS,YAAT,mBAAkB;AAAA,EACpB,GAAG,CAAC,QAAQ,CAAC;AAGb,QAAM,QAAQ,YAAY,MAAM,YAAY,IAAI,GAAG,CAAA,CAAE;AACrD,QAAM,SAAS,YAAY,MAAM,YAAY,KAAK,GAAG,CAAA,CAAE;AAGvD,YAAU,MAAM;AACd,WAAO,MAAM;AACX,UAAI,kBAAkB,SAAS;AAC7B,6BAAqB,kBAAkB,OAAO;AAAA,MAChD;AAAA,IACF;AAAA,EACF,GAAG,CAAA,CAAE;AAEL,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,YAAY,cAAc;AAAA,EAAA;AAE9B;AAUA,SAAS,aACP,MACA,WACK;AACL,MAAI,KAAK,UAAU,UAAW,QAAO;AAErC,QAAM,SAAc,CAAA;AACpB,QAAM,cAAc,KAAK,SAAS,MAAM,YAAY;AAGpD,SAAO,KAAK,KAAK,CAAC,CAAC;AAEnB,MAAI,IAAI;AAER,WAAS,IAAI,GAAG,IAAI,YAAY,GAAG,KAAK;AAEtC,UAAM,cAAc,KAAK,OAAO,IAAI,KAAK,UAAU,IAAI;AACvD,UAAM,YAAY,KAAK,OAAO,IAAI,KAAK,UAAU,IAAI;AACrD,UAAM,eAAe,KAAK,IAAI,WAAW,KAAK,MAAM,IAAI;AAGxD,QAAI,OAAO;AACX,QAAI,OAAO;AACX,UAAM,kBAAkB,KAAK,OAAO,IAAI,KAAK,UAAU,IAAI;AAC3D,UAAM,gBAAgB,KAAK,IAAI,KAAK,OAAO,IAAI,KAAK,UAAU,IAAI,GAAG,KAAK,MAAM;AAChF,UAAM,mBAAmB,gBAAgB;AAEzC,aAAS,IAAI,iBAAiB,IAAI,eAAe,KAAK;AACpD,YAAM,QAAQ,KAAK,CAAC;AACpB,YAAM,CAAC,GAAG,CAAC,IAAI,WAAW,KAAK;AAC/B,cAAQ;AACR,cAAQ;AAAA,IACV;AACA,YAAQ,oBAAoB;AAC5B,YAAQ,oBAAoB;AAG5B,UAAM,CAAC,IAAI,EAAE,IAAI,WAAW,KAAK,CAAC,CAAC;AACnC,QAAI,UAAU;AACd,QAAI,SAAS;AAEb,aAAS,IAAI,aAAa,IAAI,cAAc,cAAc,KAAK;AAC7D,YAAM,QAAQ,KAAK,CAAC;AACpB,YAAM,CAAC,GAAG,CAAC,IAAI,WAAW,KAAK;AAG/B,YAAM,OAAO,KAAK;AAAA,SACf,KAAK,SAAS,IAAI,OAAO,KAAK,MAAM,OAAO;AAAA,MAAA;AAG9C,UAAI,OAAO,SAAS;AAClB,kBAAU;AACV,iBAAS;AAAA,MACX;AAAA,IACF;AAEA,WAAO,KAAK,KAAK,MAAM,CAAC;AACxB,QAAI;AAAA,EACN;AAGA,SAAO,KAAK,KAAK,KAAK,SAAS,CAAC,CAAC;AAEjC,SAAO;AACT;AAEA,SAAS,WAAW,OAAsD;AACxE,MAAI,UAAU,OAAO;AACnB,WAAO;AAAA,MACL,OAAO,MAAM,SAAS,WAAW,MAAM,OAAO,IAAI,KAAK,MAAM,IAAI,EAAE,QAAA;AAAA,MACnE,MAAM;AAAA,IAAA;AAAA,EAEV;AACA,SAAO;AAAA,IACL,OAAO,MAAM,MAAM,WAAW,MAAM,IAAI;AAAA,IACxC,MAAM;AAAA,EAAA;AAEV;AA4BO,SAAS,mBACd,UACA,SAC2B;AAC3B,QAAM;AAAA,IACJ;AAAA,IACA,YAAY;AAAA,IACZ,iBAAiB;AAAA,IACjB,gBAAgB;AAAA,IAChB;AAAA,IACA,GAAG;AAAA,EAAA,IACD;AAEJ,QAAM,QAAQ,OAAyB,IAAI;AAC3C,QAAM,oBAAoB,OAAO,CAAC;AAClC,QAAM,oBAAoB,OAAA;AAC1B,QAAM,CAAC,iBAAiB,kBAAkB,IAAI,SAAuD,cAAc;AAEnH,QAAM,SAAS,eAAe,UAAU,aAAa;AAErD,QAAM,UAAU,YAAY,MAAM;;AAChC,UAAI,WAAM,YAAN,mBAAe,gBAAe,UAAU,KAAM;AAElD,uBAAmB,YAAY;AAC/B,UAAM,KAAK,IAAI,UAAU,GAAG;AAE5B,OAAG,SAAS,MAAM;AAChB,yBAAmB,WAAW;AAC9B,wBAAkB,UAAU;AAAA,IAC9B;AAEA,OAAG,YAAY,CAAC,UAAU;AACxB,UAAI;AACF,cAAM,OAAO,KAAK,MAAM,MAAM,IAAI;AAClC,cAAM,SAAS,eACX,aAAa,IAAI,IACjB,oBAAoB,IAAI;AAE5B,YAAI,QAAQ;AACV,iBAAO,KAAK,OAAO,aAAa,OAAO,KAAK;AAAA,QAC9C;AAAA,MACF,SAAS,GAAG;AAAA,MAIZ;AAAA,IACF;AAEA,OAAG,UAAU,MAAM;AACjB,yBAAmB,OAAO;AAAA,IAC5B;AAEA,OAAG,UAAU,MAAM;AACjB,yBAAmB,cAAc;AAEjC,UAAI,aAAa,kBAAkB,UAAU,eAAe;AAC1D,0BAAkB;AAClB,0BAAkB,UAAU,WAAW,SAAS,iBAAiB,kBAAkB,OAAO;AAAA,MAC5F;AAAA,IACF;AAEA,UAAM,UAAU;AAAA,EAClB,GAAG,CAAC,KAAK,WAAW,gBAAgB,eAAe,cAAc,MAAM,CAAC;AAExE,QAAM,aAAa,YAAY,MAAM;;AACnC,iBAAa,kBAAkB,OAAO;AACtC,gBAAM,YAAN,mBAAe;AACf,UAAM,UAAU;AAAA,EAClB,GAAG,CAAA,CAAE;AAGL,YAAU,MAAM;AACd,YAAA;AACA,WAAO;AAAA,EACT,GAAG,CAAC,SAAS,UAAU,CAAC;AAExB,SAAO;AAAA,IACL,GAAG;AAAA,IACH;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAEJ;AAEA,SAAS,oBAAoB,MAAuE;AAClG,MAAI,OAAO,SAAS,YAAY,SAAS,KAAM,QAAO;AAEtD,QAAM,MAAM;AAEZ,MAAI,UAAU,OAAO,WAAW,KAAK;AACnC,WAAO;AAAA,MACL,aAAa,OAAO,IAAI,gBAAgB,WAAW,IAAI,cAAc;AAAA,MACrE,OAAO;AAAA,QACL,MAAM,IAAI;AAAA,QACV,OAAO,IAAI;AAAA,MAAA;AAAA,IACb;AAAA,EAEJ;AAEA,SAAO;AACT;"}
|
|
@@ -1,306 +0,0 @@
|
|
|
1
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Component, useRef } from "react";
|
|
3
|
-
import { useChatGPTTheme, useDisplayMode, useMaxHeight, isInChatGPT } from "./index.js";
|
|
4
|
-
import { FONT_FAMILY_PRIMARY } from "../core/Typography.js";
|
|
5
|
-
const STATUS_COLORS_INTERNAL = {
|
|
6
|
-
off: "#a4abb6",
|
|
7
|
-
standby: "#2dccff",
|
|
8
|
-
normal: "#56f000",
|
|
9
|
-
caution: "#fce83a",
|
|
10
|
-
serious: "#ffb302",
|
|
11
|
-
critical: "#ff3838"
|
|
12
|
-
};
|
|
13
|
-
function StatusShape({ status, size = 10 }) {
|
|
14
|
-
const color = STATUS_COLORS_INTERNAL[status] ?? STATUS_COLORS_INTERNAL.off;
|
|
15
|
-
const glow = `${color}50`;
|
|
16
|
-
switch (status) {
|
|
17
|
-
case "caution":
|
|
18
|
-
return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 12 12", width: size, height: size, style: { filter: `drop-shadow(0 0 3px ${glow})` }, "aria-hidden": "true", children: /* @__PURE__ */ jsx("rect", { x: "1", y: "1", width: "10", height: "10", fill: color }) });
|
|
19
|
-
case "serious":
|
|
20
|
-
return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 12 12", width: size, height: size, style: { filter: `drop-shadow(0 0 3px ${glow})` }, "aria-hidden": "true", children: /* @__PURE__ */ jsx("polygon", { points: "6,1 11,6 6,11 1,6", fill: color }) });
|
|
21
|
-
case "critical":
|
|
22
|
-
return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 12 12", width: size, height: size, style: { filter: `drop-shadow(0 0 3px ${glow})` }, "aria-hidden": "true", children: /* @__PURE__ */ jsx("polygon", { points: "6,11 1,2 11,2", fill: color }) });
|
|
23
|
-
case "standby":
|
|
24
|
-
return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 12 12", width: size, height: size, style: { filter: `drop-shadow(0 0 3px ${glow})` }, "aria-hidden": "true", children: /* @__PURE__ */ jsx("circle", { cx: "6", cy: "6", r: "3.5", fill: "none", stroke: color, strokeWidth: "2" }) });
|
|
25
|
-
case "off":
|
|
26
|
-
return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 12 12", width: size, height: size, style: { filter: `drop-shadow(0 0 3px ${glow})` }, "aria-hidden": "true", children: /* @__PURE__ */ jsx("circle", { cx: "6", cy: "6", r: "3", fill: color }) });
|
|
27
|
-
default:
|
|
28
|
-
return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 12 12", width: size, height: size, style: { filter: `drop-shadow(0 0 3px ${glow})` }, "aria-hidden": "true", children: /* @__PURE__ */ jsx("circle", { cx: "6", cy: "6", r: "5", fill: color }) });
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
const LEGACY_VARIANT_MAP = {
|
|
32
|
-
success: "normal",
|
|
33
|
-
warning: "caution",
|
|
34
|
-
error: "critical",
|
|
35
|
-
info: "standby",
|
|
36
|
-
neutral: "off"
|
|
37
|
-
};
|
|
38
|
-
function resolveStatusLevel(input) {
|
|
39
|
-
if (["off", "standby", "normal", "caution", "serious", "critical"].includes(input)) {
|
|
40
|
-
return input;
|
|
41
|
-
}
|
|
42
|
-
return LEGACY_VARIANT_MAP[input] ?? "off";
|
|
43
|
-
}
|
|
44
|
-
class CardErrorBoundary extends Component {
|
|
45
|
-
constructor(props) {
|
|
46
|
-
super(props);
|
|
47
|
-
this.state = { hasError: false };
|
|
48
|
-
}
|
|
49
|
-
static getDerivedStateFromError(error) {
|
|
50
|
-
return { hasError: true, error };
|
|
51
|
-
}
|
|
52
|
-
render() {
|
|
53
|
-
var _a;
|
|
54
|
-
if (this.state.hasError) {
|
|
55
|
-
return /* @__PURE__ */ jsxs(
|
|
56
|
-
"div",
|
|
57
|
-
{
|
|
58
|
-
style: {
|
|
59
|
-
padding: "16px",
|
|
60
|
-
borderRadius: "12px",
|
|
61
|
-
background: "rgba(255, 56, 56, 0.08)",
|
|
62
|
-
border: "1px solid rgba(255, 56, 56, 0.3)",
|
|
63
|
-
fontFamily: FONT_FAMILY_PRIMARY,
|
|
64
|
-
color: "#ff8a8a"
|
|
65
|
-
// WCAG AA: lighter red for readable text on dark error background
|
|
66
|
-
},
|
|
67
|
-
role: "alert",
|
|
68
|
-
children: [
|
|
69
|
-
/* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "8px", marginBottom: "8px" }, children: [
|
|
70
|
-
/* @__PURE__ */ jsx(StatusShape, { status: "critical", size: 12 }),
|
|
71
|
-
/* @__PURE__ */ jsxs("strong", { style: { fontSize: "0.875rem" }, children: [
|
|
72
|
-
this.props.componentName ?? "Widget",
|
|
73
|
-
" Error"
|
|
74
|
-
] })
|
|
75
|
-
] }),
|
|
76
|
-
/* @__PURE__ */ jsx("p", { style: { margin: 0, fontSize: "0.75rem", opacity: 0.8 }, children: ((_a = this.state.error) == null ? void 0 : _a.message) ?? "An unexpected error occurred." })
|
|
77
|
-
]
|
|
78
|
-
}
|
|
79
|
-
);
|
|
80
|
-
}
|
|
81
|
-
return this.props.children;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
function LoadingSkeleton({ compact, isDark }) {
|
|
85
|
-
const bar = (w, h = 12) => ({
|
|
86
|
-
width: w,
|
|
87
|
-
height: h,
|
|
88
|
-
borderRadius: 4,
|
|
89
|
-
background: isDark ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.06)",
|
|
90
|
-
animation: "chatgpt-card-pulse 1.5s ease-in-out infinite"
|
|
91
|
-
});
|
|
92
|
-
return /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: compact ? 8 : 12 }, "aria-label": "Loading...", role: "status", children: [
|
|
93
|
-
/* @__PURE__ */ jsx("div", { style: bar("70%", 14) }),
|
|
94
|
-
/* @__PURE__ */ jsx("div", { style: bar("100%") }),
|
|
95
|
-
/* @__PURE__ */ jsx("div", { style: bar("85%") }),
|
|
96
|
-
/* @__PURE__ */ jsx("div", { style: bar("60%") }),
|
|
97
|
-
/* @__PURE__ */ jsx("style", { children: `@keyframes chatgpt-card-pulse { 0%,100% { opacity: 0.4 } 50% { opacity: 1 } }` })
|
|
98
|
-
] });
|
|
99
|
-
}
|
|
100
|
-
function ErrorDisplay({ error, onRetry, isDark }) {
|
|
101
|
-
const message = typeof error === "string" ? error : error.message;
|
|
102
|
-
return /* @__PURE__ */ jsxs(
|
|
103
|
-
"div",
|
|
104
|
-
{
|
|
105
|
-
role: "alert",
|
|
106
|
-
style: {
|
|
107
|
-
display: "flex",
|
|
108
|
-
flexDirection: "column",
|
|
109
|
-
gap: 8,
|
|
110
|
-
padding: "12px",
|
|
111
|
-
borderRadius: 8,
|
|
112
|
-
background: "rgba(255, 56, 56, 0.08)",
|
|
113
|
-
border: "1px solid rgba(255, 56, 56, 0.2)"
|
|
114
|
-
},
|
|
115
|
-
children: [
|
|
116
|
-
/* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
|
|
117
|
-
/* @__PURE__ */ jsx(StatusShape, { status: "critical", size: 12 }),
|
|
118
|
-
/* @__PURE__ */ jsx("span", { style: { fontSize: "0.75rem", fontWeight: 500, color: "#ff8a8a" }, children: message })
|
|
119
|
-
] }),
|
|
120
|
-
onRetry && /* @__PURE__ */ jsx(
|
|
121
|
-
"button",
|
|
122
|
-
{
|
|
123
|
-
onClick: onRetry,
|
|
124
|
-
style: {
|
|
125
|
-
alignSelf: "flex-start",
|
|
126
|
-
padding: "4px 12px",
|
|
127
|
-
borderRadius: 4,
|
|
128
|
-
border: `1px solid ${isDark ? "rgba(255,255,255,0.15)" : "rgba(0,0,0,0.15)"}`,
|
|
129
|
-
background: "transparent",
|
|
130
|
-
color: isDark ? "#a1a1aa" : "#636370",
|
|
131
|
-
// WCAG AA compliant on light backgrounds
|
|
132
|
-
cursor: "pointer",
|
|
133
|
-
fontSize: "0.6875rem",
|
|
134
|
-
fontFamily: FONT_FAMILY_PRIMARY
|
|
135
|
-
},
|
|
136
|
-
"aria-label": "Retry loading widget",
|
|
137
|
-
children: "Retry"
|
|
138
|
-
}
|
|
139
|
-
)
|
|
140
|
-
]
|
|
141
|
-
}
|
|
142
|
-
);
|
|
143
|
-
}
|
|
144
|
-
const AppCardInner = ({
|
|
145
|
-
children,
|
|
146
|
-
title,
|
|
147
|
-
subtitle,
|
|
148
|
-
status,
|
|
149
|
-
allowFullscreen = false,
|
|
150
|
-
onFullscreenChange,
|
|
151
|
-
className = "",
|
|
152
|
-
compact = false,
|
|
153
|
-
icon,
|
|
154
|
-
footer,
|
|
155
|
-
actions,
|
|
156
|
-
loading = false,
|
|
157
|
-
error,
|
|
158
|
-
onRetry,
|
|
159
|
-
style
|
|
160
|
-
}) => {
|
|
161
|
-
const containerRef = useRef(null);
|
|
162
|
-
const { isDark, colors } = useChatGPTTheme();
|
|
163
|
-
const { mode, requestMode } = useDisplayMode();
|
|
164
|
-
const maxHeight = useMaxHeight();
|
|
165
|
-
const resolvedStatus = status ? resolveStatusLevel(status.level ?? status.variant ?? "off") : void 0;
|
|
166
|
-
const statusColor = resolvedStatus ? STATUS_COLORS_INTERNAL[resolvedStatus] : void 0;
|
|
167
|
-
const toggleFullscreen = async () => {
|
|
168
|
-
const newMode = mode === "fullscreen" ? "inline" : "fullscreen";
|
|
169
|
-
await requestMode(newMode);
|
|
170
|
-
onFullscreenChange == null ? void 0 : onFullscreenChange(newMode === "fullscreen");
|
|
171
|
-
};
|
|
172
|
-
return /* @__PURE__ */ jsxs(
|
|
173
|
-
"div",
|
|
174
|
-
{
|
|
175
|
-
ref: containerRef,
|
|
176
|
-
className: `chatgpt-card ${className}`,
|
|
177
|
-
role: "region",
|
|
178
|
-
"aria-label": title ?? "Widget",
|
|
179
|
-
style: {
|
|
180
|
-
background: colors.surface,
|
|
181
|
-
border: `1px solid ${colors.border}`,
|
|
182
|
-
borderRadius: "12px",
|
|
183
|
-
padding: compact ? "12px" : "16px",
|
|
184
|
-
fontFamily: FONT_FAMILY_PRIMARY,
|
|
185
|
-
color: colors.text,
|
|
186
|
-
transition: "all 0.2s ease",
|
|
187
|
-
...maxHeight ? { maxHeight, overflow: "auto" } : {},
|
|
188
|
-
...style
|
|
189
|
-
},
|
|
190
|
-
children: [
|
|
191
|
-
(title || status || allowFullscreen || actions) && /* @__PURE__ */ jsxs(
|
|
192
|
-
"div",
|
|
193
|
-
{
|
|
194
|
-
style: {
|
|
195
|
-
display: "flex",
|
|
196
|
-
alignItems: "flex-start",
|
|
197
|
-
justifyContent: "space-between",
|
|
198
|
-
marginBottom: compact ? "8px" : "12px"
|
|
199
|
-
},
|
|
200
|
-
children: [
|
|
201
|
-
/* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "8px", minWidth: 0 }, children: [
|
|
202
|
-
icon && /* @__PURE__ */ jsx("span", { style: { fontSize: compact ? "16px" : "20px", flexShrink: 0 }, children: icon }),
|
|
203
|
-
/* @__PURE__ */ jsxs("div", { style: { minWidth: 0 }, children: [
|
|
204
|
-
title && /* @__PURE__ */ jsx(
|
|
205
|
-
"h3",
|
|
206
|
-
{
|
|
207
|
-
style: {
|
|
208
|
-
margin: 0,
|
|
209
|
-
fontSize: compact ? "0.8125rem" : "0.875rem",
|
|
210
|
-
fontWeight: 500,
|
|
211
|
-
color: colors.text,
|
|
212
|
-
whiteSpace: "nowrap",
|
|
213
|
-
overflow: "hidden",
|
|
214
|
-
textOverflow: "ellipsis"
|
|
215
|
-
},
|
|
216
|
-
children: title
|
|
217
|
-
}
|
|
218
|
-
),
|
|
219
|
-
subtitle && /* @__PURE__ */ jsx(
|
|
220
|
-
"p",
|
|
221
|
-
{
|
|
222
|
-
style: {
|
|
223
|
-
margin: "2px 0 0 0",
|
|
224
|
-
fontSize: "0.6875rem",
|
|
225
|
-
color: colors.textMuted,
|
|
226
|
-
whiteSpace: "nowrap",
|
|
227
|
-
overflow: "hidden",
|
|
228
|
-
textOverflow: "ellipsis"
|
|
229
|
-
},
|
|
230
|
-
children: subtitle
|
|
231
|
-
}
|
|
232
|
-
)
|
|
233
|
-
] })
|
|
234
|
-
] }),
|
|
235
|
-
/* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "8px", flexShrink: 0 }, children: [
|
|
236
|
-
actions,
|
|
237
|
-
status && resolvedStatus && statusColor && /* @__PURE__ */ jsxs(
|
|
238
|
-
"span",
|
|
239
|
-
{
|
|
240
|
-
role: "status",
|
|
241
|
-
"aria-label": `Status: ${status.label} (${resolvedStatus})`,
|
|
242
|
-
style: {
|
|
243
|
-
display: "inline-flex",
|
|
244
|
-
alignItems: "center",
|
|
245
|
-
gap: "4px",
|
|
246
|
-
fontSize: "0.5625rem",
|
|
247
|
-
fontWeight: 500,
|
|
248
|
-
letterSpacing: "0.5px",
|
|
249
|
-
padding: "2px 8px 2px 4px",
|
|
250
|
-
borderRadius: "4px",
|
|
251
|
-
background: `${statusColor}18`,
|
|
252
|
-
color: statusColor,
|
|
253
|
-
whiteSpace: "nowrap"
|
|
254
|
-
},
|
|
255
|
-
children: [
|
|
256
|
-
/* @__PURE__ */ jsx(StatusShape, { status: resolvedStatus, size: 8 }),
|
|
257
|
-
status.label
|
|
258
|
-
]
|
|
259
|
-
}
|
|
260
|
-
),
|
|
261
|
-
allowFullscreen && isInChatGPT() && /* @__PURE__ */ jsx(
|
|
262
|
-
"button",
|
|
263
|
-
{
|
|
264
|
-
onClick: toggleFullscreen,
|
|
265
|
-
"aria-label": mode === "fullscreen" ? "Exit fullscreen" : "Enter fullscreen",
|
|
266
|
-
style: {
|
|
267
|
-
background: "transparent",
|
|
268
|
-
border: "none",
|
|
269
|
-
cursor: "pointer",
|
|
270
|
-
fontSize: "14px",
|
|
271
|
-
opacity: 0.6,
|
|
272
|
-
padding: "4px",
|
|
273
|
-
color: colors.text,
|
|
274
|
-
borderRadius: "4px",
|
|
275
|
-
lineHeight: 1
|
|
276
|
-
},
|
|
277
|
-
children: mode === "fullscreen" ? "⤓" : "⤢"
|
|
278
|
-
}
|
|
279
|
-
)
|
|
280
|
-
] })
|
|
281
|
-
]
|
|
282
|
-
}
|
|
283
|
-
),
|
|
284
|
-
/* @__PURE__ */ jsx("div", { children: loading ? /* @__PURE__ */ jsx(LoadingSkeleton, { compact, isDark }) : error ? /* @__PURE__ */ jsx(ErrorDisplay, { error, onRetry, isDark }) : children }),
|
|
285
|
-
footer && !loading && !error && /* @__PURE__ */ jsx(
|
|
286
|
-
"div",
|
|
287
|
-
{
|
|
288
|
-
style: {
|
|
289
|
-
marginTop: compact ? "8px" : "12px",
|
|
290
|
-
paddingTop: compact ? "8px" : "12px",
|
|
291
|
-
borderTop: `1px solid ${colors.border}`
|
|
292
|
-
},
|
|
293
|
-
children: footer
|
|
294
|
-
}
|
|
295
|
-
)
|
|
296
|
-
]
|
|
297
|
-
}
|
|
298
|
-
);
|
|
299
|
-
};
|
|
300
|
-
const AppCard = (props) => /* @__PURE__ */ jsx(CardErrorBoundary, { componentName: props.title ?? "AppCard", children: /* @__PURE__ */ jsx(AppCardInner, { ...props }) });
|
|
301
|
-
const ChatGPTCard = AppCard;
|
|
302
|
-
export {
|
|
303
|
-
AppCard,
|
|
304
|
-
ChatGPTCard
|
|
305
|
-
};
|
|
306
|
-
//# sourceMappingURL=AppCard.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"AppCard.js","sources":["../../../src/react/chatgpt/AppCard.tsx"],"sourcesContent":["/**\n * AppCard — Universal widget wrapper for AI host environments.\n *\n * Works with: ChatGPT Apps, Anthropic MCP Apps, Google Gemini, any MCP-compatible host.\n *\n * Features:\n * - Theme-aware styling (light/dark via host environment)\n * - Astro UX 6-level status system with dual-coded indicators (color + shape)\n * - Error boundary protection with graceful fallback UI\n * - Loading and error state management\n * - Display mode support (inline/PiP/fullscreen)\n * - Widget state persistence\n * - WCAG 2.1 AA accessibility (ARIA labels, keyboard nav, screen readers)\n *\n * @example\n * ```tsx\n * import { AppCard, useToolOutput } from '@zendir/ui/react';\n *\n * function SatelliteWidget() {\n * const data = useToolOutput<SatelliteHealth>();\n * return (\n * <AppCard\n * title=\"SAT-001\"\n * subtitle=\"Health Monitor\"\n * status={{ level: 'caution', label: 'Battery Low' }}\n * allowFullscreen\n * >\n * <TelemetryCard data={data} />\n * </AppCard>\n * );\n * }\n * ```\n */\n\nimport React, { useRef, ReactNode, Component } from 'react';\nimport { useChatGPTTheme, useMaxHeight, useDisplayMode, isInChatGPT } from './index';\nimport { FONT_FAMILY_PRIMARY } from '../core/Typography';\nimport type { StatusLevel } from '../utils';\n\n// ─── Status shape helper (matches ChatPanel & Astro UX components) ───────────\n\nconst STATUS_COLORS_INTERNAL: Record<StatusLevel, string> = {\n off: '#a4abb6',\n standby: '#2dccff',\n normal: '#56f000',\n caution: '#fce83a',\n serious: '#ffb302',\n critical: '#ff3838',\n};\n\nfunction StatusShape({ status, size = 10 }: { status: StatusLevel; size?: number }) {\n const color = STATUS_COLORS_INTERNAL[status] ?? STATUS_COLORS_INTERNAL.off;\n const glow = `${color}50`;\n switch (status) {\n case 'caution':\n return <svg viewBox=\"0 0 12 12\" width={size} height={size} style={{ filter: `drop-shadow(0 0 3px ${glow})` }} aria-hidden=\"true\"><rect x=\"1\" y=\"1\" width=\"10\" height=\"10\" fill={color} /></svg>;\n case 'serious':\n return <svg viewBox=\"0 0 12 12\" width={size} height={size} style={{ filter: `drop-shadow(0 0 3px ${glow})` }} aria-hidden=\"true\"><polygon points=\"6,1 11,6 6,11 1,6\" fill={color} /></svg>;\n case 'critical':\n return <svg viewBox=\"0 0 12 12\" width={size} height={size} style={{ filter: `drop-shadow(0 0 3px ${glow})` }} aria-hidden=\"true\"><polygon points=\"6,11 1,2 11,2\" fill={color} /></svg>;\n case 'standby':\n return <svg viewBox=\"0 0 12 12\" width={size} height={size} style={{ filter: `drop-shadow(0 0 3px ${glow})` }} aria-hidden=\"true\"><circle cx=\"6\" cy=\"6\" r=\"3.5\" fill=\"none\" stroke={color} strokeWidth=\"2\" /></svg>;\n case 'off':\n return <svg viewBox=\"0 0 12 12\" width={size} height={size} style={{ filter: `drop-shadow(0 0 3px ${glow})` }} aria-hidden=\"true\"><circle cx=\"6\" cy=\"6\" r=\"3\" fill={color} /></svg>;\n default: // normal\n return <svg viewBox=\"0 0 12 12\" width={size} height={size} style={{ filter: `drop-shadow(0 0 3px ${glow})` }} aria-hidden=\"true\"><circle cx=\"6\" cy=\"6\" r=\"5\" fill={color} /></svg>;\n }\n}\n\n// ─── Legacy status mapping (for backward compatibility) ──────────────────────\n\n/**\n * Maps legacy variant strings to Astro UX StatusLevel.\n * Supports both new `StatusLevel` and old `success/warning/error/info/neutral` variants.\n */\nconst LEGACY_VARIANT_MAP: Record<string, StatusLevel> = {\n success: 'normal',\n warning: 'caution',\n error: 'critical',\n info: 'standby',\n neutral: 'off',\n};\n\nfunction resolveStatusLevel(input: string): StatusLevel {\n if (['off', 'standby', 'normal', 'caution', 'serious', 'critical'].includes(input)) {\n return input as StatusLevel;\n }\n return LEGACY_VARIANT_MAP[input] ?? 'off';\n}\n\n// ─── Error Boundary ──────────────────────────────────────────────────────────\n\ninterface CardErrorBoundaryState {\n hasError: boolean;\n error?: Error;\n}\n\nclass CardErrorBoundary extends Component<\n { children: ReactNode; componentName?: string },\n CardErrorBoundaryState\n> {\n constructor(props: { children: ReactNode; componentName?: string }) {\n super(props);\n this.state = { hasError: false };\n }\n\n static getDerivedStateFromError(error: Error): CardErrorBoundaryState {\n return { hasError: true, error };\n }\n\n render() {\n if (this.state.hasError) {\n return (\n <div\n style={{\n padding: '16px',\n borderRadius: '12px',\n background: 'rgba(255, 56, 56, 0.08)',\n border: '1px solid rgba(255, 56, 56, 0.3)',\n fontFamily: FONT_FAMILY_PRIMARY,\n color: '#ff8a8a', // WCAG AA: lighter red for readable text on dark error background\n }}\n role=\"alert\"\n >\n <div style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '8px' }}>\n <StatusShape status=\"critical\" size={12} />\n <strong style={{ fontSize: '0.875rem' }}>\n {this.props.componentName ?? 'Widget'} Error\n </strong>\n </div>\n <p style={{ margin: 0, fontSize: '0.75rem', opacity: 0.8 }}>\n {this.state.error?.message ?? 'An unexpected error occurred.'}\n </p>\n </div>\n );\n }\n return this.props.children;\n }\n}\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\nexport interface AppCardProps {\n /** Card content */\n children: ReactNode;\n /** Card title */\n title?: string;\n /** Subtitle or description */\n subtitle?: string;\n /**\n * Status badge with Astro UX dual-coded indicator (color + shape).\n *\n * Supports both the Astro UX `StatusLevel` and legacy variants:\n * - Astro UX: `{ level: 'normal' | 'standby' | 'caution' | 'serious' | 'critical' | 'off', label: '...' }`\n * - Legacy: `{ variant: 'success' | 'warning' | 'error' | 'info' | 'neutral', label: '...' }`\n */\n status?: {\n label: string;\n level?: StatusLevel;\n /** @deprecated Use `level` (Astro UX StatusLevel) instead. Still supported for backward compatibility. */\n variant?: 'success' | 'warning' | 'error' | 'info' | 'neutral';\n };\n /** Show fullscreen button (only rendered inside ChatGPT host) */\n allowFullscreen?: boolean;\n /** Callback when fullscreen is toggled */\n onFullscreenChange?: (isFullscreen: boolean) => void;\n /** Additional CSS classes */\n className?: string;\n /** Enable compact mode (reduced padding and font sizes) */\n compact?: boolean;\n /** Header icon (ReactNode, rendered before title) */\n icon?: ReactNode;\n /** Footer content (rendered below content with a divider) */\n footer?: ReactNode;\n /** Actions (buttons) rendered in the header, right-aligned */\n actions?: ReactNode;\n /** Show a loading skeleton over the content area */\n loading?: boolean;\n /** Error state — renders an error message with a critical alert */\n error?: string | Error;\n /** Retry callback (shown when `error` is set) */\n onRetry?: () => void;\n /** Custom inline styles for the root container */\n style?: React.CSSProperties;\n}\n\n// ─── Loading Skeleton ────────────────────────────────────────────────────────\n\nfunction LoadingSkeleton({ compact, isDark }: { compact: boolean; isDark: boolean }) {\n const bar = (w: string, h = 12) => ({\n width: w,\n height: h,\n borderRadius: 4,\n background: isDark ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.06)',\n animation: 'chatgpt-card-pulse 1.5s ease-in-out infinite',\n });\n\n return (\n <div style={{ display: 'flex', flexDirection: 'column', gap: compact ? 8 : 12 }} aria-label=\"Loading...\" role=\"status\">\n <div style={bar('70%', 14)} />\n <div style={bar('100%')} />\n <div style={bar('85%')} />\n <div style={bar('60%')} />\n <style>{`@keyframes chatgpt-card-pulse { 0%,100% { opacity: 0.4 } 50% { opacity: 1 } }`}</style>\n </div>\n );\n}\n\n// ─── Error Display ───────────────────────────────────────────────────────────\n\nfunction ErrorDisplay({ error, onRetry, isDark }: { error: string | Error; onRetry?: () => void; isDark: boolean }) {\n const message = typeof error === 'string' ? error : error.message;\n return (\n <div\n role=\"alert\"\n style={{\n display: 'flex',\n flexDirection: 'column',\n gap: 8,\n padding: '12px',\n borderRadius: 8,\n background: 'rgba(255, 56, 56, 0.08)',\n border: '1px solid rgba(255, 56, 56, 0.2)',\n }}\n >\n <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>\n <StatusShape status=\"critical\" size={12} />\n <span style={{ fontSize: '0.75rem', fontWeight: 500, color: '#ff8a8a' }}>{message}</span>\n </div>\n {onRetry && (\n <button\n onClick={onRetry}\n style={{\n alignSelf: 'flex-start',\n padding: '4px 12px',\n borderRadius: 4,\n border: `1px solid ${isDark ? 'rgba(255,255,255,0.15)' : 'rgba(0,0,0,0.15)'}`,\n background: 'transparent',\n color: isDark ? '#a1a1aa' : '#636370', // WCAG AA compliant on light backgrounds\n cursor: 'pointer',\n fontSize: '0.6875rem',\n fontFamily: FONT_FAMILY_PRIMARY,\n }}\n aria-label=\"Retry loading widget\"\n >\n Retry\n </button>\n )}\n </div>\n );\n}\n\n// ─── Main Component ──────────────────────────────────────────────────────────\n\nconst AppCardInner: React.FC<AppCardProps> = ({\n children,\n title,\n subtitle,\n status,\n allowFullscreen = false,\n onFullscreenChange,\n className = '',\n compact = false,\n icon,\n footer,\n actions,\n loading = false,\n error,\n onRetry,\n style,\n}) => {\n const containerRef = useRef<HTMLDivElement>(null);\n const { isDark, colors } = useChatGPTTheme();\n const { mode, requestMode } = useDisplayMode();\n const maxHeight = useMaxHeight();\n\n // Resolve status level (supports both Astro UX and legacy variants)\n const resolvedStatus = status\n ? resolveStatusLevel(status.level ?? status.variant ?? 'off')\n : undefined;\n const statusColor = resolvedStatus ? STATUS_COLORS_INTERNAL[resolvedStatus] : undefined;\n\n // Handle fullscreen toggle\n const toggleFullscreen = async () => {\n const newMode = mode === 'fullscreen' ? 'inline' : 'fullscreen';\n await requestMode(newMode);\n onFullscreenChange?.(newMode === 'fullscreen');\n };\n\n return (\n <div\n ref={containerRef}\n className={`chatgpt-card ${className}`}\n role=\"region\"\n aria-label={title ?? 'Widget'}\n style={{\n background: colors.surface,\n border: `1px solid ${colors.border}`,\n borderRadius: '12px',\n padding: compact ? '12px' : '16px',\n fontFamily: FONT_FAMILY_PRIMARY,\n color: colors.text,\n transition: 'all 0.2s ease',\n ...(maxHeight ? { maxHeight, overflow: 'auto' } : {}),\n ...style,\n }}\n >\n {/* Header */}\n {(title || status || allowFullscreen || actions) && (\n <div\n style={{\n display: 'flex',\n alignItems: 'flex-start',\n justifyContent: 'space-between',\n marginBottom: compact ? '8px' : '12px',\n }}\n >\n <div style={{ display: 'flex', alignItems: 'center', gap: '8px', minWidth: 0 }}>\n {icon && <span style={{ fontSize: compact ? '16px' : '20px', flexShrink: 0 }}>{icon}</span>}\n <div style={{ minWidth: 0 }}>\n {title && (\n <h3\n style={{\n margin: 0,\n fontSize: compact ? '0.8125rem' : '0.875rem',\n fontWeight: 500,\n color: colors.text,\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n }}\n >\n {title}\n </h3>\n )}\n {subtitle && (\n <p\n style={{\n margin: '2px 0 0 0',\n fontSize: '0.6875rem',\n color: colors.textMuted,\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n }}\n >\n {subtitle}\n </p>\n )}\n </div>\n </div>\n\n <div style={{ display: 'flex', alignItems: 'center', gap: '8px', flexShrink: 0 }}>\n {actions}\n\n {status && resolvedStatus && statusColor && (\n <span\n role=\"status\"\n aria-label={`Status: ${status.label} (${resolvedStatus})`}\n style={{\n display: 'inline-flex',\n alignItems: 'center',\n gap: '4px',\n fontSize: '0.5625rem',\n fontWeight: 500,\n letterSpacing: '0.5px',\n padding: '2px 8px 2px 4px',\n borderRadius: '4px',\n background: `${statusColor}18`,\n color: statusColor,\n whiteSpace: 'nowrap',\n }}\n >\n <StatusShape status={resolvedStatus} size={8} />\n {status.label}\n </span>\n )}\n\n {allowFullscreen && isInChatGPT() && (\n <button\n onClick={toggleFullscreen}\n aria-label={mode === 'fullscreen' ? 'Exit fullscreen' : 'Enter fullscreen'}\n style={{\n background: 'transparent',\n border: 'none',\n cursor: 'pointer',\n fontSize: '14px',\n opacity: 0.6,\n padding: '4px',\n color: colors.text,\n borderRadius: '4px',\n lineHeight: 1,\n }}\n >\n {mode === 'fullscreen' ? '⤓' : '⤢'}\n </button>\n )}\n </div>\n </div>\n )}\n\n {/* Content: loading → error → children */}\n <div>\n {loading ? (\n <LoadingSkeleton compact={compact} isDark={isDark} />\n ) : error ? (\n <ErrorDisplay error={error} onRetry={onRetry} isDark={isDark} />\n ) : (\n children\n )}\n </div>\n\n {/* Footer */}\n {footer && !loading && !error && (\n <div\n style={{\n marginTop: compact ? '8px' : '12px',\n paddingTop: compact ? '8px' : '12px',\n borderTop: `1px solid ${colors.border}`,\n }}\n >\n {footer}\n </div>\n )}\n </div>\n );\n};\n\n/**\n * AppCard — enterprise-ready widget wrapper for AI host environments.\n *\n * Works with ChatGPT Apps, Anthropic MCP Apps, Google Gemini, or any MCP host.\n * Wrapped in an error boundary so rendering failures in child components never\n * crash the host application — critical for sandboxed iframes.\n */\nexport const AppCard: React.FC<AppCardProps> = (props) => (\n <CardErrorBoundary componentName={props.title ?? 'AppCard'}>\n <AppCardInner {...props} />\n </CardErrorBoundary>\n);\n\n/** @deprecated Use `AppCard` instead. Alias kept for backward compatibility. */\nexport const ChatGPTCard = AppCard;\n/** @deprecated Use `AppCardProps` instead. */\nexport type ChatGPTCardProps = AppCardProps;\n\nexport default AppCard;\n"],"names":[],"mappings":"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"}
|