@zendir/ui 0.1.15 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +39 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/react/astro/SimulationControls.js +3 -3
- package/dist/react/astro/SimulationControls.js.map +1 -1
- package/dist/react/astro/UnifiedTimeline.d.ts +56 -6
- package/dist/react/astro/UnifiedTimeline.js +628 -428
- package/dist/react/astro/UnifiedTimeline.js.map +1 -1
- package/dist/react/astro/index.d.ts +2 -2
- package/dist/react/charts/GroundTrackMap.d.ts +40 -1
- package/dist/react/charts/GroundTrackMap.js +112 -60
- package/dist/react/charts/GroundTrackMap.js.map +1 -1
- package/dist/react/charts/GroundTrackMapLeaflet.d.ts +11 -2
- package/dist/react/charts/GroundTrackMapLeaflet.js +140 -39
- package/dist/react/charts/GroundTrackMapLeaflet.js.map +1 -1
- package/dist/react/charts/index.d.ts +1 -1
- package/dist/react/charts/unified/theme.d.ts +7 -7
- package/dist/react/context/CategoryContext.d.ts +51 -0
- package/dist/react/context/CategoryContext.js +36 -0
- package/dist/react/context/CategoryContext.js.map +1 -0
- package/dist/react/context/index.d.ts +2 -0
- package/dist/react/index.d.ts +6 -4
- package/dist/react/types.d.ts +26 -0
- package/dist/react/types.js.map +1 -1
- package/dist/react/utils/categoryPalette.d.ts +43 -0
- package/dist/react/utils/categoryPalette.js +104 -0
- package/dist/react/utils/categoryPalette.js.map +1 -0
- package/dist/react/utils/index.d.ts +1 -0
- package/dist/react/utils/index.js.map +1 -1
- package/dist/react.js +6 -0
- package/dist/react.js.map +1 -1
- package/dist/style.css +49 -0
- package/package.json +1 -1
|
@@ -50,8 +50,8 @@ export type { NotificationProps, NotificationStatus } from './Notification';
|
|
|
50
50
|
* ```
|
|
51
51
|
*/
|
|
52
52
|
export { UnifiedTimeline as Timeline } from './UnifiedTimeline';
|
|
53
|
-
export type { UnifiedTimelineProps as TimelineProps, TimelineEvent, TimelineTrackDef, TimelineViewMode, TimeFormat, TimelineFilter, } from './UnifiedTimeline';
|
|
54
|
-
export { getTimelineEventDisplayStatus, isTimelineFilterActive, matchesTimelineFilter, } from './UnifiedTimeline';
|
|
53
|
+
export type { UnifiedTimelineProps as TimelineProps, TimelineEvent, TimelineTeam, TimelineTrackDef, TimelineViewMode, TimeFormat, TimelineFilter, } from './UnifiedTimeline';
|
|
54
|
+
export { TIMELINE_FILTER_TEAM_LEGACY, TIMELINE_FILTER_TEAM_NONE, getTimelineEventDisplayStatus, getTimelineTeamAccent, getTimelineTeamDisplayLabel, getTimelineTeamFilterKey, isTimelineFilterActive, matchesTimelineFilter, } from './UnifiedTimeline';
|
|
55
55
|
/**
|
|
56
56
|
* Status type (shared across components)
|
|
57
57
|
* Follows Astro UX 6-level status semantics
|
|
@@ -62,6 +62,27 @@ export interface SROGroundStation {
|
|
|
62
62
|
longitude: number;
|
|
63
63
|
altitude?: number | null;
|
|
64
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* User-placed point-of-interest pin on the map.
|
|
67
|
+
* Designed for collaborative scenarios where operators share annotated locations
|
|
68
|
+
* across terminals in real time (e.g. targets, waypoints, hazard markers).
|
|
69
|
+
*/
|
|
70
|
+
export interface MapPin {
|
|
71
|
+
/** Unique identifier (used as key and for update/remove callbacks) */
|
|
72
|
+
id: string;
|
|
73
|
+
/** Latitude in degrees (−90 … 90) */
|
|
74
|
+
latitude: number;
|
|
75
|
+
/** Longitude in degrees (−180 … 180) */
|
|
76
|
+
longitude: number;
|
|
77
|
+
/** Short label displayed next to the pin (e.g. "Target Alpha") */
|
|
78
|
+
label?: string;
|
|
79
|
+
/** Pin color — any CSS color string. Defaults to accent primary. */
|
|
80
|
+
color?: string;
|
|
81
|
+
/** Optional description shown in tooltip on hover */
|
|
82
|
+
description?: string;
|
|
83
|
+
/** Who placed the pin (display-only; not enforced) */
|
|
84
|
+
createdBy?: string;
|
|
85
|
+
}
|
|
65
86
|
export interface GroundTrackMapProps {
|
|
66
87
|
/** Single satellite ground track (legacy API) */
|
|
67
88
|
groundTrack?: GroundTrackPoint[];
|
|
@@ -75,6 +96,10 @@ export interface GroundTrackMapProps {
|
|
|
75
96
|
teamPaths?: TeamPath[];
|
|
76
97
|
/** Show day/night terminator */
|
|
77
98
|
showTerminator?: boolean;
|
|
99
|
+
/** Override the time used for the day/night terminator calculation.
|
|
100
|
+
* Defaults to wall-clock `new Date()` when omitted.
|
|
101
|
+
* Pass a simulation/mission UTC to keep the terminator in sync with a sim clock. */
|
|
102
|
+
terminatorTime?: Date;
|
|
78
103
|
/** Show grid lines */
|
|
79
104
|
showGrid?: boolean;
|
|
80
105
|
/** Show legend */
|
|
@@ -107,6 +132,20 @@ export interface GroundTrackMapProps {
|
|
|
107
132
|
mapProvider?: 'leaflet' | 'canvas';
|
|
108
133
|
/** Tile URL for Leaflet (default: CartoDB dark). Ignored when mapProvider is 'canvas'. */
|
|
109
134
|
tileUrl?: string;
|
|
135
|
+
/** Array of user-placed pins displayed on the map */
|
|
136
|
+
pins?: MapPin[];
|
|
137
|
+
/**
|
|
138
|
+
* Allow operators to place new pins by clicking the map.
|
|
139
|
+
* When true, a single-click on an empty area opens a pin creation flow.
|
|
140
|
+
* Requires `onPinAdd` to be set. Default false.
|
|
141
|
+
*/
|
|
142
|
+
pinsEditable?: boolean;
|
|
143
|
+
/** Called when the user places a new pin (provides lat/lon; consumer assigns id and persists) */
|
|
144
|
+
onPinAdd?: (pin: Omit<MapPin, 'id'>) => void;
|
|
145
|
+
/** Called when the user edits an existing pin (label, color, description change) */
|
|
146
|
+
onPinUpdate?: (pin: MapPin) => void;
|
|
147
|
+
/** Called when the user removes a pin */
|
|
148
|
+
onPinRemove?: (pinId: string) => void;
|
|
110
149
|
}
|
|
111
|
-
export declare function GroundTrackMap({ groundTrack, satellites, groundStations, accessMask, teamPaths, showTerminator, showGrid, showLegend, showEquator, showRecenterButton, isLoading, emptyMessage, width, height, minHeight, defaultCenter, defaultZoom, className, onSatelliteClick, onStationClick, mapProvider, tileUrl, }: GroundTrackMapProps): React.ReactElement;
|
|
150
|
+
export declare function GroundTrackMap({ groundTrack, satellites, groundStations, accessMask, teamPaths, showTerminator, terminatorTime, showGrid, showLegend, showEquator, showRecenterButton, isLoading, emptyMessage, width, height, minHeight, defaultCenter, defaultZoom, className, onSatelliteClick, onStationClick, mapProvider, tileUrl, pins, pinsEditable, onPinAdd, onPinUpdate, onPinRemove, }: GroundTrackMapProps): React.ReactElement;
|
|
112
151
|
export default GroundTrackMap;
|
|
@@ -68,24 +68,37 @@ function drawCoverageEllipse(ctx, centerLat, centerLon, radiusDeg, W, H, lonToX,
|
|
|
68
68
|
}
|
|
69
69
|
ctx.closePath();
|
|
70
70
|
}
|
|
71
|
-
function
|
|
71
|
+
function calculateTerminatorContinuous(date, depressionDeg = 0, numPoints = 360) {
|
|
72
72
|
const dayOfYear = Math.floor((date.getTime() - new Date(date.getFullYear(), 0, 0).getTime()) / 864e5);
|
|
73
73
|
const declination = -23.44 * Math.cos(2 * Math.PI / 365 * (dayOfYear + 10));
|
|
74
74
|
const decRad = declination * Math.PI / 180;
|
|
75
75
|
const hourAngle = (date.getUTCHours() + date.getUTCMinutes() / 60) / 24 * 360 - 180;
|
|
76
|
-
const
|
|
76
|
+
const depRad = depressionDeg * Math.PI / 180;
|
|
77
|
+
const sunset = [];
|
|
78
|
+
const sunrise = [];
|
|
77
79
|
for (let i = 0; i <= numPoints; i++) {
|
|
78
|
-
const
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
if (cosH < -1)
|
|
82
|
-
else if (cosH > 1)
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
80
|
+
const lat = -89.999 + i / numPoints * 179.998;
|
|
81
|
+
const latRad = lat * (Math.PI / 180);
|
|
82
|
+
const cosH = -(Math.sin(depRad) + Math.sin(latRad) * Math.sin(decRad)) / (Math.cos(latRad) * Math.cos(decRad));
|
|
83
|
+
if (cosH < -1) ;
|
|
84
|
+
else if (cosH > 1) {
|
|
85
|
+
sunset.push([lat, hourAngle]);
|
|
86
|
+
sunrise.push([lat, hourAngle + 360]);
|
|
87
|
+
} else {
|
|
88
|
+
const H = Math.acos(cosH) * 180 / Math.PI;
|
|
89
|
+
sunset.push([lat, hourAngle + H]);
|
|
90
|
+
sunrise.push([lat, hourAngle + 360 - H]);
|
|
91
|
+
}
|
|
87
92
|
}
|
|
88
|
-
return
|
|
93
|
+
return { sunset, sunrise };
|
|
94
|
+
}
|
|
95
|
+
function buildNightPolygon(terminator) {
|
|
96
|
+
const { sunset, sunrise } = terminator;
|
|
97
|
+
if (!sunset.length || !sunrise.length) return [];
|
|
98
|
+
const poly = [];
|
|
99
|
+
poly.push(...sunset);
|
|
100
|
+
poly.push(...[...sunrise].reverse());
|
|
101
|
+
return poly;
|
|
89
102
|
}
|
|
90
103
|
function GroundTrackMap({
|
|
91
104
|
groundTrack,
|
|
@@ -94,6 +107,7 @@ function GroundTrackMap({
|
|
|
94
107
|
accessMask,
|
|
95
108
|
teamPaths,
|
|
96
109
|
showTerminator = true,
|
|
110
|
+
terminatorTime,
|
|
97
111
|
showGrid = true,
|
|
98
112
|
showLegend = true,
|
|
99
113
|
showEquator = true,
|
|
@@ -109,7 +123,12 @@ function GroundTrackMap({
|
|
|
109
123
|
onSatelliteClick,
|
|
110
124
|
onStationClick,
|
|
111
125
|
mapProvider = "leaflet",
|
|
112
|
-
tileUrl
|
|
126
|
+
tileUrl,
|
|
127
|
+
pins,
|
|
128
|
+
pinsEditable = false,
|
|
129
|
+
onPinAdd,
|
|
130
|
+
onPinUpdate,
|
|
131
|
+
onPinRemove
|
|
113
132
|
}) {
|
|
114
133
|
const { tokens } = useTheme();
|
|
115
134
|
const canvasRef = useRef(null);
|
|
@@ -194,55 +213,54 @@ function GroundTrackMap({
|
|
|
194
213
|
ctx.fillStyle = COLORS.background;
|
|
195
214
|
ctx.fillRect(0, 0, W, H);
|
|
196
215
|
if (showTerminator) {
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
let
|
|
203
|
-
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
if (Math.abs(x - prevRX) > W * 0.5 && i < terminator.length - 1) {
|
|
207
|
-
const edgeX = prevRX > W / 2 ? W : 0;
|
|
208
|
-
ctx.lineTo(edgeX, y);
|
|
209
|
-
ctx.closePath();
|
|
210
|
-
ctx.fill();
|
|
211
|
-
ctx.beginPath();
|
|
212
|
-
ctx.moveTo(W, y);
|
|
213
|
-
}
|
|
214
|
-
ctx.lineTo(x, y);
|
|
215
|
-
prevRX = x;
|
|
216
|
+
const now = terminatorTime ?? /* @__PURE__ */ new Date();
|
|
217
|
+
const BAND_STEP = 2;
|
|
218
|
+
const MAX_DEP = 24;
|
|
219
|
+
const NIGHT_ALPHA = 0.55;
|
|
220
|
+
const bandSteps = [];
|
|
221
|
+
for (let d = 0; d <= MAX_DEP; d += BAND_STEP) {
|
|
222
|
+
const t = Math.min(d / 18, 1);
|
|
223
|
+
const alpha = t * t * (NIGHT_ALPHA * 0.82) + (d > 18 ? (d - 18) / 6 * (NIGHT_ALPHA * 0.18) : 0);
|
|
224
|
+
bandSteps.push({ depression: d, alpha: Math.min(alpha, NIGHT_ALPHA) });
|
|
216
225
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
226
|
+
bandSteps.push({ depression: 90, alpha: NIGHT_ALPHA });
|
|
227
|
+
let prevAlpha = 0;
|
|
228
|
+
for (const zone of bandSteps) {
|
|
229
|
+
const dep = Math.min(zone.depression, 89);
|
|
230
|
+
const bandAlpha = zone.alpha - prevAlpha;
|
|
231
|
+
if (bandAlpha < 2e-3) {
|
|
232
|
+
prevAlpha = zone.alpha;
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
const terminator = calculateTerminatorContinuous(now, dep);
|
|
236
|
+
if (terminator.sunset.length === 0) {
|
|
237
|
+
prevAlpha = zone.alpha;
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
const poly = buildNightPolygon(terminator);
|
|
241
|
+
if (poly.length < 3) {
|
|
242
|
+
prevAlpha = zone.alpha;
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
ctx.fillStyle = `rgba(0, 6, 24, ${bandAlpha.toFixed(4)})`;
|
|
246
|
+
for (const offset of [-360, 0, 360]) {
|
|
247
|
+
ctx.beginPath();
|
|
248
|
+
for (let i = 0; i < poly.length; i++) {
|
|
249
|
+
const lat = poly[i][0];
|
|
250
|
+
const lon = poly[i][1] + offset;
|
|
251
|
+
const x = lonToX(lon, W);
|
|
252
|
+
const y = latToY(lat, H);
|
|
253
|
+
if (i === 0) {
|
|
254
|
+
ctx.moveTo(x, y);
|
|
255
|
+
} else {
|
|
256
|
+
ctx.lineTo(x, y);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
236
259
|
ctx.closePath();
|
|
237
260
|
ctx.fill();
|
|
238
|
-
ctx.beginPath();
|
|
239
|
-
ctx.moveTo(0, y);
|
|
240
261
|
}
|
|
241
|
-
|
|
242
|
-
prevLX = x;
|
|
262
|
+
prevAlpha = zone.alpha;
|
|
243
263
|
}
|
|
244
|
-
ctx.closePath();
|
|
245
|
-
ctx.fill();
|
|
246
264
|
}
|
|
247
265
|
ctx.fillStyle = COLORS.land;
|
|
248
266
|
ctx.strokeStyle = "rgba(100, 120, 160, 0.25)";
|
|
@@ -739,13 +757,41 @@ function GroundTrackMap({
|
|
|
739
757
|
ctx.fillText(item.label, legendX + 20, iy + 7);
|
|
740
758
|
});
|
|
741
759
|
}
|
|
742
|
-
if (
|
|
760
|
+
if (pins && pins.length > 0) {
|
|
761
|
+
pins.forEach((pin) => {
|
|
762
|
+
const px = lonToX(pin.longitude, W);
|
|
763
|
+
const py = latToY(pin.latitude, H);
|
|
764
|
+
const pinColor = pin.color || tokens.colors.accent.primary;
|
|
765
|
+
ctx.save();
|
|
766
|
+
ctx.shadowColor = "rgba(0,0,0,0.4)";
|
|
767
|
+
ctx.shadowBlur = 6;
|
|
768
|
+
ctx.shadowOffsetY = 2;
|
|
769
|
+
ctx.beginPath();
|
|
770
|
+
ctx.arc(px, py - 10, 6, Math.PI, 0);
|
|
771
|
+
ctx.lineTo(px, py);
|
|
772
|
+
ctx.closePath();
|
|
773
|
+
ctx.fillStyle = pinColor;
|
|
774
|
+
ctx.fill();
|
|
775
|
+
ctx.restore();
|
|
776
|
+
ctx.beginPath();
|
|
777
|
+
ctx.arc(px, py - 10, 2.5, 0, Math.PI * 2);
|
|
778
|
+
ctx.fillStyle = "#0d1323";
|
|
779
|
+
ctx.fill();
|
|
780
|
+
if (pin.label) {
|
|
781
|
+
ctx.fillStyle = "rgba(255,255,255,0.9)";
|
|
782
|
+
ctx.font = '10px "Inter", system-ui, sans-serif';
|
|
783
|
+
ctx.textAlign = "center";
|
|
784
|
+
ctx.fillText(pin.label, px, py - 20);
|
|
785
|
+
}
|
|
786
|
+
});
|
|
787
|
+
}
|
|
788
|
+
if (allSatellites.length === 0 && groundStations.length === 0 && (!pins || pins.length === 0)) {
|
|
743
789
|
ctx.fillStyle = COLORS.text;
|
|
744
790
|
ctx.font = '12px "Inter", system-ui, sans-serif';
|
|
745
791
|
ctx.textAlign = "center";
|
|
746
792
|
ctx.fillText(emptyMessage, W / 2, H / 2);
|
|
747
793
|
}
|
|
748
|
-
}, [allSatellites, groundStations, height, showTerminator, showGrid, showLegend, showEquator, lonToX, latToY, COLORS, emptyMessage]);
|
|
794
|
+
}, [allSatellites, groundStations, height, showTerminator, terminatorTime, showGrid, showLegend, showEquator, lonToX, latToY, COLORS, emptyMessage, pins, tokens.colors.accent.primary]);
|
|
749
795
|
const handleMouseMove = useCallback((e) => {
|
|
750
796
|
const canvas = canvasRef.current;
|
|
751
797
|
if (!canvas) return;
|
|
@@ -861,6 +907,7 @@ function GroundTrackMap({
|
|
|
861
907
|
allSatellites,
|
|
862
908
|
groundStations,
|
|
863
909
|
showTerminator,
|
|
910
|
+
terminatorTime,
|
|
864
911
|
showGrid,
|
|
865
912
|
showLegend,
|
|
866
913
|
showEquator,
|
|
@@ -874,7 +921,12 @@ function GroundTrackMap({
|
|
|
874
921
|
tileUrl,
|
|
875
922
|
className,
|
|
876
923
|
onSatelliteClick,
|
|
877
|
-
onStationClick
|
|
924
|
+
onStationClick,
|
|
925
|
+
pins,
|
|
926
|
+
pinsEditable,
|
|
927
|
+
onPinAdd,
|
|
928
|
+
onPinUpdate,
|
|
929
|
+
onPinRemove
|
|
878
930
|
}
|
|
879
931
|
)
|
|
880
932
|
}
|