PageSourceSearch

https://orbitalradar.com/ui.js

js orbitalradar.com collected 2026-09-24 18:25:01 UTC 10,494 bytes, 248 lines download raw bytes

1// ui.js
2// UI utility functions for loading overlays, animated bars, and Cesium lighting.
3export let showLabels = false;
4export let showOrbits = false;
5
6// Shared orbit type labels — single source of truth (used by operatorPanel, filterNav, etc.)
7export const ORBIT_TYPE_LABELS = {
8  LEO: 'Low Earth Orbit',
9  MEO: 'Medium Earth Orbit',
10  GEO: 'Geostationary Orbit',
11  HEO: 'Highly Elliptical Orbit'
12};
13
14export function getOrbitType(h) {
15    if (h < 2000) return "LEO";
16    if (h < 35786 - 300) return "MEO";
17    if (Math.abs(h - 35786) <= 300) return "GEO";
18    return "HEO";
19}
20
21// ── Loading overlay lifecycle ──────────────────────────────────────────────
22// While the overlay is visible we add `or-booting` to <html>; CSS uses it to hide the
23// bottom #orbitalradar-branding watermark so "Orbital Radar" isn't shown twice (loader hero
24// + footer watermark). The flag is also set pre-paint in index.html (covers first paint);
25// we keep it in sync here so mid-session reloads (e.g. toggling debris) hide it too.
26function _setBooting(on) {
27  try { document.documentElement.classList.toggle("or-booting", !!on); } catch {}
28}
29
30// First display (page boot) uses the full-page scene; every display AFTER the first
31// dismissal (debris toggle, satellite reloads) uses the compact card (.or-loader-mid)
32// at the classic footprint with the live globe dimly visible behind. Boot itself also
33// passes through startFakeLoadingBar (loadSatellitesFromServer reason:'initial'), so
34// the mode is keyed off "dismissed at least once", never off the entry function.
35let _overlayDismissedOnce = false;
36// Bumped on every show; delayed hide steps bail out if a newer show has happened
37// (guards the "debris toggled right as the boot fade finishes" race).
38let _overlayShowSeq = 0;
39function _applyOverlayMode(o) {
40  if (_overlayDismissedOnce) o.classList.add("or-loader-mid");
41}
42
43export function showLoadingOverlay() {
44    const o = document.getElementById("loadingOverlay");
45    if (!o) return;
46    _overlayShowSeq++;
47    _setBooting(true);
48    _applyOverlayMode(o);
49    o.style.opacity = "1";
50    o.style.display = "flex";
51}
52export function hideLoadingOverlay() {
53    const o = document.getElementById("loadingOverlay");
54    _setBooting(false);                 // reveals the branding watermark (fades in via CSS transition)
55    _overlayDismissedOnce = true;       // any later show is a mid-session reload → compact card
56    if (!o) return;
57    const seq = _overlayShowSeq;
58    o.style.opacity = "0";              // soft fade (CSS transition on .or-loading-overlay)
59    setTimeout(() => { if (seq === _overlayShowSeq) o.style.display = "none"; }, 420);
60}
61
62// ── Loader context: themed telemetry messages + live object counter ─────────
63// Counter target comes from the serve-time totals injected into window.__OR_COUNTS
64// (__TRACKED_TOTAL_EXACT__ / __ACTIVE_SATS_EXACT__ / __PAYLOADS_IN_ORBIT_EXACT__).
65// Boot counts to the TRACKED total — deliberately the same figure the globe headline
66// shows on arrival, so the loader and the globe can never disagree. Since the 2026-09
67// basis (b) split, `active` is the OPERATIONAL estimate and `payloads` is the full
68// payload count; non-payload arithmetic (the debris view) must use `payloads`, because
69// tracked − operational would count dead-but-orbiting payloads as debris. No hardcoded
70// numbers: if the tokens weren't injected (raw static serve) we fall back to a generic
71// label with no count.
72let _loaderCtx = "satellites";
73let _loaderTarget = null;
74
75const _LOADER_MSGS = {
76  satellites: [[0, "Loading satellites…"], [22, "Fetching TLE catalog…"], [48, "Propagating orbits…"], [74, "Calibrating ground tracks…"]],
77  debris:     [[0, "Loading debris…"],     [22, "Scanning debris field…"], [48, "Propagating fragment orbits…"], [74, "Mapping collision risk…"]]
78};
79
80function _parseCount(s) {
81  const n = parseInt(String(s == null ? "" : s).replace(/[^0-9]/g, ""), 10);
82  return Number.isFinite(n) && n > 0 ? n : null;
83}
84
85function _telemetryFor(percent) {
86  const set = _LOADER_MSGS[_loaderCtx] || _LOADER_MSGS.satellites;
87  let msg = set[0][1];
88  for (const [threshold, text] of set) { if (percent >= threshold) msg = text; }
89  return msg;
90}
91
92// Infers satellites vs debris from the start message and wires up the counter label/target.
93function setLoaderContext(message) {
94  const counts = (typeof window !== "undefined" && window.__OR_COUNTS) || {};
95  const active = _parseCount(counts.active);
96  const payloads = _parseCount(counts.payloads) || active; // pre-split pages carry no payloads key
97  const tracked = _parseCount(counts.tracked);
98  const isDebris = /debris/i.test(message || "");
99  _loaderCtx = isDebris ? "debris" : "satellites";
100
101  // Debris context recolours the loader accents amber (CSS .or-loader-debris).
102  try { document.getElementById("loadingOverlay")?.classList.toggle("or-loader-debris", isDebris); } catch {}
103
104  const labelEl = document.getElementById("loadingCountLabel");
105  const countEl = document.getElementById("loadingCount");
106  const unitEl  = document.getElementById("loadingCountUnit");
107
108  if (isDebris) {
109    // debris view layers debris + rocket bodies on top of the satellites → (tracked − payloads).
110    // Must be the PAYLOAD count, not the operational estimate: tracked − operational would
111    // relabel every dead-but-orbiting payload as a debris object.
112    _loaderTarget = (tracked && payloads && tracked > payloads) ? (tracked - payloads) : tracked;
113  } else {
114    // Boot counts to the tracked total — matches the globe's own headline figure.
115    _loaderTarget = tracked || active;
116  }
117
118  if (_loaderTarget) {
119    const totalStr = _loaderTarget.toLocaleString("en-US");
120    if (labelEl) labelEl.textContent = isDebris ? "Cataloguing" : "Acquiring";
121    if (unitEl)  unitEl.textContent  = `of ${totalStr} ${isDebris ? "debris objects" : "tracked objects"}`;
122    if (countEl) countEl.textContent = "0";
123  } else {
124    if (labelEl) labelEl.textContent = "Syncing live orbital data…";
125    if (unitEl)  unitEl.textContent  = "";
126    if (countEl) countEl.textContent = "";
127  }
128}
129
130export function setLoadingBarPercent(percent, text) {
131  percent = Math.max(0, Math.min(100, percent));
132
133  const bar = document.getElementById("loadingBar");
134  const rocket = document.getElementById("rocket");
135  const pctEl = document.getElementById("loadingPct");
136
137  if (bar) {
138    bar.style.width = percent + "%";
139  }
140
141  if (rocket) {
142    const parent = bar?.parentElement;
143    const totalWidth = parent ? parent.offsetWidth : 320;
144    const rocketWidth = 40;
145    const px = Math.floor((percent / 100) * (totalWidth - rocketWidth));
146    rocket.style.left = px + "px";
147  }
148
149  if (pctEl) {
150    pctEl.textContent = `${Math.round(percent)}%`;
151  }
152
153  // Live object counter climbs with the bar toward the injected total.
154  if (_loaderTarget) {
155    const countEl = document.getElementById("loadingCount");
156    if (countEl) countEl.textContent = Math.round((percent / 100) * _loaderTarget).toLocaleString("en-US");
157  }
158
159  // Status line: explicit text wins (e.g. the final "…loaded!"); otherwise themed telemetry by %.
160  const loadingText = document.getElementById("loadingText");
161  if (loadingText) loadingText.textContent = text || _telemetryFor(percent);
162}
163
164
165// --- Optionally: Retain this for initial startup sequence ---
166export function runSmoothLoadingStages(stageList, onComplete) {
167    let idx = 0;
168    let percent = 0;
169    function nextStage() {
170        if (idx >= stageList.length) {
171            setLoadingBarPercent(100, "Link established—visualizing satellites…");
172            setTimeout(onComplete, 850);
173            return;
174        }
175        const { msg, pct, dur } = stageList[idx];
176        let start = percent;
177        let startTime = performance.now();
178        function animate(now) {
179            let elapsed = now - startTime;
180            let localPct = Math.min(1, elapsed / dur);
181            let cur = Math.round(start + (pct - start) * localPct);
182            setLoadingBarPercent(cur, msg);
183            if (localPct < 1) {
184                requestAnimationFrame(animate);
185            } else {
186                percent = pct;
187                idx++;
188                nextStage();
189            }
190        }
191        animate(performance.now());
192    }
193    nextStage();
194}
195let fakeLoadingInterval = null;
196
197/**
198 * Starts a smooth, fake loading bar that animates up to 90%, with themed telemetry
199 * messages and a live object counter. Context (satellites vs debris) is inferred from
200 * the message, so existing call sites need no changes.
201 */
202export function startFakeLoadingBar(message = "Loading…") {
203    const overlay = document.getElementById("loadingOverlay");
204    const bar = document.getElementById("loadingBar");
205    if (!overlay || !bar) return;
206
207    _overlayShowSeq++;
208    _applyOverlayMode(overlay);         // compact card iff this is a mid-session reload
209    _setBooting(true);
210    setLoaderContext(message);
211
212    let percent = 10;
213    overlay.style.opacity = "1";
214    overlay.style.display = "flex";
215    setLoadingBarPercent(percent);
216    clearInterval(fakeLoadingInterval);
217    fakeLoadingInterval = setInterval(() => {
218        if (percent < 90) {
219            percent += Math.random() * 3 + 0.5;
220            setLoadingBarPercent(Math.min(percent, 90));
221        }
222    }, 120);
223}
224
225/**
226 * Completes the bar, then fades the overlay out and reveals the branding watermark
227 * (clears the boot flag).
228 */
229export function finishFakeLoadingBar(finalMessage = "Complete!") {
230    const overlay = document.getElementById("loadingOverlay");
231    clearInterval(fakeLoadingInterval);
232    setLoadingBarPercent(100, finalMessage);   // also snaps the counter to its full target
233
234    const seq = _overlayShowSeq;
235    setTimeout(() => {
236        if (seq !== _overlayShowSeq) return;    // a newer show superseded this dismiss
237        _setBooting(false);                     // reveal branding (fades in via CSS transition)
238        _overlayDismissedOnce = true;           // any later show is a mid-session reload → compact card
239        if (overlay) {
240            overlay.style.opacity = "0";        // soft fade-out (CSS transition on .or-loading-overlay)
241            setTimeout(() => { if (seq === _overlayShowSeq) overlay.style.display = "none"; }, 420);
242        }
243    }, 650);
244}
245
246export function enableLighting(viewer) {
247    viewer.scene.globe.enableLighting = true;
248}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.