GODSIGH/js/ui.js
jing 2e061b2cdf wave2 phase 5: polish pack (military, ground tracks, URL state, screenshots)
1. Military callsigns (aircraft.js): callsigns matching CONFIG.aircraft.
   militaryPrefixes tint red regardless of altitude band and bump larger. New
   "Military filter" HUD row (default off) shows only military aircraft when on;
   status "N military of M". Pick overlay shows Military/Civil class.
2. Satellite ground track (satellites.js): selecting a satellite draws its
   sub-satellite path (height 0, ±half orbit around now) as a dashed polyline in
   the sat's colour; deselect hides it; one track at a time. Recomputed on
   demand from the satrec (no upfront precompute of 97 tracks).
3. Shareable URL state (main.js + ui.js): camera / mode / layer toggles / clock
   offset serialize to location.hash (replaceState, 1 s cadence, only on change)
   and are restored on boot; malformed hashes ignored silently.
4. Screenshot endpoint (serve.py): dev-only POST snap?name= writes a base64 PNG
   body to docs/<name>.png (name sanitized to [a-z0-9-], can't escape docs/).
   Captured docs/screenshot-data.png + screenshot-photo.png (render() called
   synchronously before toDataURL — the preserveDrawingBuffer pitfall).

README rewritten for Wave 2: quakes/fires layer rows (marked REAL), replay,
shared cache, shareable links, ground tracks, military, screenshots, updated
architecture + roadmap.

Verified in browser: 11 military of 6062 + filter; ground track 180 pts on
select / hidden on deselect; URL round-trips camera+mode+layers+clock; both
screenshots written as valid 1280x720 PNGs; subpath audit clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 16:53:05 +10:00

86 lines
2.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// HUD controller: layer registry, per-feed status lines, LIVE/SCRUBBED chip.
const rows = new Map();
// Register a layer row. Returns a handle so callers can flip the checkbox.
export function addLayer(id, name, defaultOn, onToggle) {
const row = document.createElement('div');
row.className = 'layer-row';
row.innerHTML = `
<label>
<input type="checkbox" ${defaultOn ? 'checked' : ''} />
<span class="dot" data-state="${defaultOn ? 'ok' : 'off'}"></span>
<span class="lname"></span>
</label>
<div class="lstatus"></div>`;
row.querySelector('.lname').textContent = name;
const cb = row.querySelector('input');
cb.addEventListener('change', () => onToggle(cb.checked));
document.getElementById('layers').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 =
`<div class="po-close" role="button" aria-label="close">×</div>` +
`<div class="po-title"></div>` +
rowPairs.map(() => `<div class="po-row"><span></span><span></span></div>`).join('');
el.querySelector('.po-title').textContent = title;
const rowEls = el.querySelectorAll('.po-row');
rowPairs.forEach(([k, v], i) => {
const spans = rowEls[i].querySelectorAll('span');
spans[0].textContent = k;
spans[1].textContent = v;
});
el.querySelector('.po-close').addEventListener('click', hidePickOverlay);
el.hidden = false;
}
export function hidePickOverlay() {
const el = document.getElementById('pick-overlay');
if (el) el.hidden = true;
}