// HUD controller: categorized collapsible layer list, per-feed status lines, // LIVE/SCRUBBED chip. const rows = new Map(); // Category scheme is owned here so HUD order is stable regardless of layer // registration order. Categories collapsed-by-default are marked below. const CATEGORY_ORDER = ['Space', 'Air', 'Sea', 'Earth & hazards', 'Context', 'Regional — Australia']; const COLLAPSED_BY_DEFAULT = new Set(['Regional — Australia']); const CATEGORY_BY_ID = { satellites: 'Space', 'sat-paths': 'Space', launches: 'Space', aircraft: 'Air', military: 'Air', 'aircraft-mil': 'Air', ships: 'Sea', quakes: 'Earth & hazards', fires: 'Earth & hazards', gdacs: 'Earth & hazards', nws: 'Earth & hazards', infra: 'Context', events: 'Context', rfs: 'Regional — Australia', }; const sections = new Map(); // category → { body } // Lazily create a category section, inserted in CATEGORY_ORDER position. function sectionFor(category) { if (sections.has(category)) return sections.get(category); const wrap = document.createElement('div'); wrap.className = 'cat'; if (COLLAPSED_BY_DEFAULT.has(category)) wrap.classList.add('collapsed'); const header = document.createElement('div'); header.className = 'cat-header'; header.innerHTML = `▾`; header.querySelector('.cat-name').textContent = category; header.addEventListener('click', () => wrap.classList.toggle('collapsed')); const body = document.createElement('div'); body.className = 'cat-body'; wrap.appendChild(header); wrap.appendChild(body); // Insert before the existing section that is this one's immediate successor in // CATEGORY_ORDER (smallest index greater than ours), so DOM order stays stable // regardless of registration order. Unknown categories sort last. const container = document.getElementById('layers'); const ord = (c) => { const i = CATEGORY_ORDER.indexOf(c); return i < 0 ? Infinity : i; }; const myOrder = ord(category); let before = null, bestOrder = Infinity; for (const [cat, s] of sections) { const idx = ord(cat); if (idx > myOrder && idx < bestOrder) { bestOrder = idx; before = s.wrap; } } container.insertBefore(wrap, before); const rec = { wrap, body }; sections.set(category, rec); return rec; } // Register a layer row. Returns a handle so callers can flip the checkbox. // `category` is optional; a known id maps to a fixed category regardless. export function addLayer(id, name, defaultOn, onToggle, category) { const cat = CATEGORY_BY_ID[id] || category || 'Other'; const row = document.createElement('div'); row.className = 'layer-row'; row.innerHTML = `
`; row.querySelector('.lname').textContent = name; const cb = row.querySelector('input'); cb.addEventListener('change', () => onToggle(cb.checked)); sectionFor(cat).body.appendChild(row); rows.set(id, row); return { setChecked: (v) => { cb.checked = v; } }; } // Registered layer ids, in registration order (for URL-state serialization). export function getLayerIds() { return [...rows.keys()]; } // Is a layer's checkbox currently checked? export function getLayerChecked(id) { const row = rows.get(id); return row ? row.querySelector('input').checked : false; } // Set a layer's checkbox AND fire its onToggle (replays a real user toggle), // so restoring URL state actually shows/hides the layer. No-op if unchanged. export function setLayerChecked(id, on) { const row = rows.get(id); if (!row) return; const cb = row.querySelector('input'); if (cb.checked !== on) { cb.checked = on; cb.dispatchEvent(new Event('change')); } } // state ∈ 'ok' | 'warn' | 'err' | 'off' export function setStatus(id, text, state = 'ok') { const row = rows.get(id); if (!row) return; row.querySelector('.lstatus').textContent = text; row.querySelector('.dot').dataset.state = state; } export function setLiveChip(isLive) { const chip = document.getElementById('live-chip'); if (!chip) return; chip.textContent = isLive ? 'LIVE' : 'SCRUBBED'; chip.title = isLive ? 'Clock is at wall-clock now' : 'Live layers paused — scrubbed off now'; chip.dataset.state = isLive ? 'live' : 'scrub'; } // Small fixed overlay for primitive picks (aircraft), which have no Cesium InfoBox. export function showPickOverlay(title, rowPairs) { const el = document.getElementById('pick-overlay'); if (!el) return; el.innerHTML = `