wave3: categorized collapsible HUD (Space/Air/Sea/Earth/Context/Regional)
12+ layers in a flat list was unusable. ui.js now groups rows into ordered, collapsible category sections (scheme owned in ui.js so order is stable regardless of registration order); Regional — Australia collapsed by default; empty categories auto-hidden via :has(:empty). URL-state get/set layer APIs unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
941f5032e0
commit
3dde813d7d
@ -115,9 +115,32 @@ html, body {
|
||||
#layers {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
gap: 1px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* ---- collapsible category sections ---- */
|
||||
.cat:has(.cat-body:empty) { display: none; } /* hide unused categories */
|
||||
.cat-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 4px 4px 3px;
|
||||
margin-top: 4px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
color: var(--dim);
|
||||
font-size: 9.5px;
|
||||
letter-spacing: 0.6px;
|
||||
text-transform: uppercase;
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
.cat-header:hover { color: var(--text); }
|
||||
.cat-chevron { font-size: 8px; width: 8px; transition: transform 0.15s; }
|
||||
.cat.collapsed .cat-chevron { transform: rotate(-90deg); }
|
||||
.cat.collapsed .cat-body { display: none; }
|
||||
.cat-body { display: flex; flex-direction: column; gap: 1px; }
|
||||
|
||||
.layer-row {
|
||||
padding: 5px 4px;
|
||||
border-radius: 6px;
|
||||
|
||||
57
js/ui.js
57
js/ui.js
@ -1,9 +1,60 @@
|
||||
// HUD controller: layer registry, per-feed status lines, LIVE/SCRUBBED chip.
|
||||
// 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 = `<span class="cat-chevron">▾</span><span class="cat-name"></span>`;
|
||||
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.
|
||||
export function addLayer(id, name, defaultOn, onToggle) {
|
||||
// `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 = `
|
||||
@ -16,7 +67,7 @@ export function addLayer(id, name, defaultOn, onToggle) {
|
||||
row.querySelector('.lname').textContent = name;
|
||||
const cb = row.querySelector('input');
|
||||
cb.addEventListener('change', () => onToggle(cb.checked));
|
||||
document.getElementById('layers').appendChild(row);
|
||||
sectionFor(cat).body.appendChild(row);
|
||||
rows.set(id, row);
|
||||
return { setChecked: (v) => { cb.checked = v; } };
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user