From e34d6199494c003751cdd9f204805d654e59d150 Mon Sep 17 00:00:00 2001 From: Sameh Khamis Date: Sat, 12 Sep 2026 22:36:06 -0700 Subject: [PATCH] style: format Layers panel components --- scripts/package-boundaries.json | 207 ++++++++------------------------ src/ui/clearLayersControl.js | 9 +- src/ui/layerPanel.js | 147 +++++++++++++++-------- 3 files changed, 158 insertions(+), 205 deletions(-) diff --git a/scripts/package-boundaries.json b/scripts/package-boundaries.json index 433fa873f..c79b5843b 100644 --- a/scripts/package-boundaries.json +++ b/scripts/package-boundaries.json @@ -10,47 +10,27 @@ "src/data/localGeojsonCore.js", "src/data/localGeojsonLod.js" ], - "external": [ - "cesium" - ] + "external": ["cesium"] }, "application": { - "exports": [ - "./application" - ], - "modules": [ - "src/app/application.js" - ], + "exports": ["./application"], + "modules": ["src/app/application.js"], "external": [] }, "viewer": { - "exports": [ - "./application/viewer" - ], - "modules": [ - "src/app/viewer.js" - ], - "external": [ - "cesium" - ] + "exports": ["./application/viewer"], + "modules": ["src/app/viewer.js"], + "external": ["cesium"] }, "vite-build": { "runtime": "node", - "exports": [ - "./build/vite" - ], - "modules": [ - "build/vite.js" - ], - "external": [ - "vite-plugin-cesium" - ] + "exports": ["./build/vite"], + "modules": ["build/vite.js"], + "external": ["vite-plugin-cesium"] }, "live-providers": { "runtime": "node", - "exports": [ - "./server/providers/live" - ], + "exports": ["./server/providers/live"], "modules": [ "server/providers/live.js", "server/providers/common/http.js", @@ -65,24 +45,16 @@ "src/data/aisStreamAdapter.js", "src/data/aisWatchdog.js" ], - "external": [ - "ws" - ] + "external": ["ws"] }, "adsb-lol-source": { - "exports": [ - "./sources/adsb-lol" - ], - "modules": [ - "src/data/adsbLolFallback.js" - ], + "exports": ["./sources/adsb-lol"], + "modules": ["src/data/adsbLolFallback.js"], "external": [] }, "place-providers": { "runtime": "node", - "exports": [ - "./server/providers/places" - ], + "exports": ["./server/providers/places"], "modules": [ "server/providers/places.js", "server/providers/places/google.js", @@ -97,19 +69,13 @@ "external": [] }, "place-payloads": { - "exports": [ - "./sources/places" - ], - "modules": [ - "src/data/placeProviderPayloads.js" - ], + "exports": ["./sources/places"], + "modules": ["src/data/placeProviderPayloads.js"], "external": [] }, "space-providers": { "runtime": "node", - "exports": [ - "./server/providers/space" - ], + "exports": ["./server/providers/space"], "modules": [ "server/providers/space.js", "server/providers/space/celestrak.js", @@ -120,19 +86,13 @@ "external": [] }, "space-requests": { - "exports": [ - "./sources/space" - ], - "modules": [ - "src/data/spaceProviderRequests.js" - ], + "exports": ["./sources/space"], + "modules": ["src/data/spaceProviderRequests.js"], "external": [] }, "terrain-provider": { "runtime": "node", - "exports": [ - "./server/providers/terrain" - ], + "exports": ["./server/providers/terrain"], "modules": [ "server/providers/terrain.js", "src/data/terrainHeightsProxy.js" @@ -140,50 +100,30 @@ "external": [] }, "terrain-source": { - "exports": [ - "./sources/terrain" - ], - "modules": [ - "src/data/terrainHeightsProxy.js" - ], + "exports": ["./sources/terrain"], + "modules": ["src/data/terrainHeightsProxy.js"], "external": [] }, "traffic-provider": { "runtime": "node", - "exports": [ - "./server/providers/traffic" - ], - "modules": [ - "server/providers/traffic.js", - "src/data/tomtomTiles.js" - ], + "exports": ["./server/providers/traffic"], + "modules": ["server/providers/traffic.js", "src/data/tomtomTiles.js"], "external": [] }, "traffic-source": { - "exports": [ - "./sources/traffic" - ], - "modules": [ - "src/data/tomtomTiles.js" - ], + "exports": ["./sources/traffic"], + "modules": ["src/data/tomtomTiles.js"], "external": [] }, "firms-provider": { "runtime": "node", - "exports": [ - "./server/providers/firms" - ], - "modules": [ - "server/providers/firms.js", - "src/data/firmsCsv.js" - ], + "exports": ["./server/providers/firms"], + "modules": ["server/providers/firms.js", "src/data/firmsCsv.js"], "external": [] }, "gbfs-provider": { "runtime": "node", - "exports": [ - "./server/providers/gbfs" - ], + "exports": ["./server/providers/gbfs"], "modules": [ "server/providers/gbfs.js", "server/providers/common/http.js", @@ -192,19 +132,13 @@ "external": [] }, "gbfs-source": { - "exports": [ - "./sources/gbfs" - ], - "modules": [ - "src/data/gbfsSource.js" - ], + "exports": ["./sources/gbfs"], + "modules": ["src/data/gbfsSource.js"], "external": [] }, "cctv-provider": { "runtime": "node", - "exports": [ - "./server/providers/cctv" - ], + "exports": ["./server/providers/cctv"], "modules": [ "server/providers/cctv.js", "server/providers/cctv/catalog.js", @@ -221,9 +155,7 @@ }, "radio-provider": { "runtime": "node", - "exports": [ - "./server/providers/radio" - ], + "exports": ["./server/providers/radio"], "modules": [ "server/providers/radio.js", "server/providers/radio/catalog.js", @@ -237,9 +169,7 @@ }, "overpass-provider": { "runtime": "node", - "exports": [ - "./server/providers/overpass" - ], + "exports": ["./server/providers/overpass"], "modules": [ "server/providers/common/geo.js", "server/providers/common/http.js", @@ -258,9 +188,7 @@ }, "military-installations-provider": { "runtime": "node", - "exports": [ - "./server/providers/military-installations" - ], + "exports": ["./server/providers/military-installations"], "modules": [ "server/providers/common/http.js", "server/providers/common/query.js", @@ -277,9 +205,7 @@ }, "regional-provider": { "runtime": "node", - "exports": [ - "./server/providers/regional" - ], + "exports": ["./server/providers/regional"], "modules": [ "server/providers/common/http.js", "server/providers/common/query.js", @@ -298,9 +224,7 @@ }, "openai-provider": { "runtime": "node", - "exports": [ - "./server/providers/openai" - ], + "exports": ["./server/providers/openai"], "modules": [ "server/providers/common/rate-limit.js", "server/providers/common/request.js", @@ -320,9 +244,7 @@ }, "standalone-key-setup": { "runtime": "node", - "exports": [ - "./server/standalone/key-setup" - ], + "exports": ["./server/standalone/key-setup"], "modules": [ "scripts/pinokio-environment.mjs", "server/providers/common/source-root.js", @@ -333,9 +255,7 @@ "external": [] }, "place-search": { - "exports": [ - "./search" - ], + "exports": ["./search"], "modules": [ "src/search/index.js", "src/search/placeSearch.js", @@ -345,27 +265,17 @@ "external": [] }, "panel-disclosure": { - "exports": [ - "./ui/panels" - ], - "modules": [ - "src/ui/panelDisclosure.js" - ], + "exports": ["./ui/panels"], + "modules": ["src/ui/panelDisclosure.js"], "external": [] }, "surface-keyboard": { - "exports": [ - "./ui/surfaces" - ], - "modules": [ - "src/ui/surfaceKeyboard.js" - ], + "exports": ["./ui/surfaces"], + "modules": ["src/ui/surfaceKeyboard.js"], "external": [] }, "panel-rail-layout": { - "exports": [ - "./ui/layout" - ], + "exports": ["./ui/layout"], "modules": [ "src/ui/panelRails.js", "src/ui/leftPanelRail.js", @@ -377,9 +287,7 @@ "external": [] }, "visual-input": { - "exports": [ - "./ui/input" - ], + "exports": ["./ui/input"], "modules": [ "src/ui/visualInput.js", "src/ui/applicationShortcuts.js", @@ -388,19 +296,12 @@ "external": [] }, "display-controls": { - "exports": [ - "./ui/display" - ], - "modules": [ - "src/ui/displayControls.js" - ], + "exports": ["./ui/display"], + "modules": ["src/ui/displayControls.js"], "external": [] }, "visual-effects": { - "exports": [ - "./ui/effects", - "./ui/effects/bloom" - ], + "exports": ["./ui/effects", "./ui/effects/bloom"], "modules": [ "src/ui/effects.js", "src/ui/visualEffects.js", @@ -413,14 +314,10 @@ "src/styles/noir.js", "src/styles/snow.js" ], - "external": [ - "cesium" - ] + "external": ["cesium"] }, "map-source-controls": { - "exports": [ - "./ui/maps" - ], + "exports": ["./ui/maps"], "modules": [ "src/ui/mapSource.js", "src/ui/mapSourceControls.js", @@ -430,9 +327,7 @@ "external": [] }, "layer-panel": { - "exports": [ - "./ui/layers" - ], + "exports": ["./ui/layers"], "modules": [ "src/ui/layers.js", "src/ui/layerPanel.js", diff --git a/src/ui/clearLayersControl.js b/src/ui/clearLayersControl.js index 2036f416e..a3e0e078f 100644 --- a/src/ui/clearLayersControl.js +++ b/src/ui/clearLayersControl.js @@ -1,14 +1,19 @@ /** Keep the clear action focusable while busy; the caller owns its transaction. */ export function bindClearLayersControl(button, clear) { let destroyed = false; - const click = () => { if (!destroyed) void clear(); }; + const click = () => { + if (!destroyed) void clear(); + }; button?.addEventListener('click', click); return { setBusy(busy) { if (destroyed || !button) return; button.setAttribute('aria-disabled', String(busy)); button.setAttribute('aria-busy', String(busy)); - button.setAttribute('aria-label', busy ? 'Clearing selected data layers' : 'Clear selected data layers'); + button.setAttribute( + 'aria-label', + busy ? 'Clearing selected data layers' : 'Clear selected data layers', + ); }, destroy() { destroyed = true; diff --git a/src/ui/layerPanel.js b/src/ui/layerPanel.js index 126b7d80b..6bd94bf9f 100644 --- a/src/ui/layerPanel.js +++ b/src/ui/layerPanel.js @@ -15,16 +15,21 @@ const FEED_STATE_LABELS = Object.freeze({ */ export function layerFeedState(stats = {}) { const state = stats || {}; - const status = typeof state.status === 'string' ? state.status.toLowerCase() : ''; + const status = + typeof state.status === 'string' ? state.status.toLowerCase() : ''; const source = `${state.source || ''} ${state.coverage || ''}`; const hasExplicitFallback = typeof state.fallback === 'boolean'; const hasPriorData = Number(state.count) > 0 || Boolean(state.lastUpdate); - const presentedError = state.error || state.lastError || state.managerRefreshError; - if (['unavailable', 'offline', 'down', 'error'].includes(status)) return 'unavailable'; + const presentedError = + state.error || state.lastError || state.managerRefreshError; + if (['unavailable', 'offline', 'down', 'error'].includes(status)) + return 'unavailable'; if ( - (presentedError || state.unavailable === true || state.available === false) - && !hasPriorData - && !GUIDANCE_STATUSES.includes(status) + (presentedError || + state.unavailable === true || + state.available === false) && + !hasPriorData && + !GUIDANCE_STATUSES.includes(status) ) { return 'unavailable'; } @@ -37,29 +42,37 @@ export function layerFeedState(stats = {}) { return state.stale ? 'stale' : 'nominal'; } if ( - state.fallback === true - || status === 'fallback' - || state.mode === 'sim' - || /\bfallback\b/i.test(source) - || (!hasExplicitFallback && /\badsb\.lol\b/i.test(source)) + state.fallback === true || + status === 'fallback' || + state.mode === 'sim' || + /\bfallback\b/i.test(source) || + (!hasExplicitFallback && /\badsb\.lol\b/i.test(source)) ) { return 'fallback'; } if (state.stale || status === 'stale') return 'stale'; if ( - state.degraded - || presentedError - || state.unavailable === true - || state.available === false - ) return 'degraded'; + state.degraded || + presentedError || + state.unavailable === true || + state.available === false + ) + return 'degraded'; return 'nominal'; } - /** Layer row presentation over supplied state and actions; no layer imports. */ export class LayerPanel { - constructor({ getLayers, isEnabled, setEnabled, setLayerParams, getRowControls, - hasRowControls, subscribeRowControls, onHiddenRefresh = () => {} }) { + constructor({ + getLayers, + isEnabled, + setEnabled, + setLayerParams, + getRowControls, + hasRowControls, + subscribeRowControls, + onHiddenRefresh = () => {}, + }) { this.getAll = getLayers; this.isEnabled = isEnabled; this.setEnabled = setEnabled; @@ -123,7 +136,9 @@ export class LayerPanel { const count = document.createElement('span'); count.className = 'data-count'; - count.textContent = layer.stats.count ? this._formatCount(layer.stats.count) : '—'; + count.textContent = layer.stats.count + ? this._formatCount(layer.stats.count) + : '—'; const toggle = document.createElement('button'); toggle.type = 'button'; @@ -133,16 +148,24 @@ export class LayerPanel { // Native `disabled` immediately evicts keyboard focus in Chromium. Keep // the lifecycle control focusable while it is busy, and enforce the // same single-flight interaction contract through ARIA instead. - if (this._destroyed || this._generation !== generation || toggle.getAttribute('aria-disabled') === 'true') return; + if ( + this._destroyed || + this._generation !== generation || + toggle.getAttribute('aria-disabled') === 'true' + ) + return; toggle.setAttribute('aria-disabled', 'true'); toggle.setAttribute('aria-busy', 'true'); try { - await this.setEnabled(layer.id, !this.isEnabled(layer.id), { origin: 'user' }); + await this.setEnabled(layer.id, !this.isEnabled(layer.id), { + origin: 'user', + }); } catch (error) { console.warn(`[Data] ${layer.id} toggle error:`, error); } finally { const current = this.getAll().find(({ id }) => id === layer.id); - if (!this._destroyed && current && this._generation === generation) this._syncToggleButton(toggle, current); + if (!this._destroyed && current && this._generation === generation) + this._syncToggleButton(toggle, current); } }); @@ -165,7 +188,9 @@ export class LayerPanel { // A layer whose controls settle asynchronously (a chunked catalog load // that can also fail) pushes a re-render through this; nothing else // would repaint the row before its next scheduled refresh. - const unsubscribe = this.subscribeRowControls(layer.id, () => this._refreshTogglePanel()); + const unsubscribe = this.subscribeRowControls(layer.id, () => + this._refreshTogglePanel(), + ); if (unsubscribe) this._removers.push(unsubscribe); const controls = document.createElement('div'); controls.className = 'data-toggle-controls'; @@ -174,9 +199,11 @@ export class LayerPanel { if (!button || button.disabled) return; // Re-read the live descriptor rather than trusting the rendered // chip, so a stale row can never apply an inverted toggle. - const chip = this._rowControlsFor(layer.id)?.chips - ?.find((entry) => entry.id === button.dataset.chipId); - if (chip?.params) this.setLayerParams(layer.id, chip.params, { origin: 'user' }); + const chip = this._rowControlsFor(layer.id)?.chips?.find( + (entry) => entry.id === button.dataset.chipId, + ); + if (chip?.params) + this.setLayerParams(layer.id, chip.params, { origin: 'user' }); }); row.appendChild(controls); this._syncRowControls(controls, layer); @@ -215,7 +242,10 @@ export class LayerPanel { container.hidden = chips.length === 0 && legend.length === 0; for (const node of [...container.children]) { - if (String(node.className).split(/\s+/).includes('data-toggle-legend-item')) node.remove(); + if ( + String(node.className).split(/\s+/).includes('data-toggle-legend-item') + ) + node.remove(); } const stale = new Map(); @@ -265,7 +295,9 @@ export class LayerPanel { return; } for (const layer of this.getAll()) { - const row = this._toggleContainer.querySelector(`[data-layer-id="${layer.id}"]`); + const row = this._toggleContainer.querySelector( + `[data-layer-id="${layer.id}"]`, + ); if (!row) continue; const btn = row.querySelector('.data-toggle-btn'); @@ -275,7 +307,9 @@ export class LayerPanel { const count = row.querySelector('.data-count'); if (count) { - count.textContent = layer.stats.count ? this._formatCount(layer.stats.count) : '—'; + count.textContent = layer.stats.count + ? this._formatCount(layer.stats.count) + : '—'; } const meta = row.querySelector('.data-toggle-meta'); @@ -292,14 +326,16 @@ export class LayerPanel { const feedState = layerFeedState(stats); const stateLabel = FEED_STATE_LABELS[feedState]; const source = stats.source || layer.source; - const lifecycleState = layer.lifecycleState || (layer.enabled ? 'enabled' : 'disabled'); + const lifecycleState = + layer.lifecycleState || (layer.enabled ? 'enabled' : 'disabled'); if (lifecycleState === 'enabling' || lifecycleState === 'disabling') { return `${lifecycleState.toUpperCase()} · ${source}`; } if (layer.lifecycleUncertain) { return `UNCERTAIN · ${source} · lifecycle state requires reconciliation`; } - const presentedError = stats.error || stats.lastError || stats.managerRefreshError; + const presentedError = + stats.error || stats.lastError || stats.managerRefreshError; if (presentedError) { if (typeof stats.retryInSec === 'number' && stats.retryInSec > 0) { return `${stateLabel} · ${source} · ${presentedError} · retry ${stats.retryInSec}s`; @@ -308,27 +344,33 @@ export class LayerPanel { } // A guidance status carries its prompt in `statusMessage`, not `error`, so // the row still tells the operator what to do without reporting a fault. - if (GUIDANCE_STATUSES.includes(String(stats.status || '').toLowerCase()) - && typeof stats.statusMessage === 'string' && stats.statusMessage.trim()) { + if ( + GUIDANCE_STATUSES.includes(String(stats.status || '').toLowerCase()) && + typeof stats.statusMessage === 'string' && + stats.statusMessage.trim() + ) { return `${source} · ${stats.statusMessage.trim()}`; } const ago = stats.lastUpdate ? this._timeAgo(stats.lastUpdate) : 'never'; if (stats.loading) { - const loadingLabel = typeof stats.loadingLabel === 'string' && stats.loadingLabel.trim() - ? stats.loadingLabel.trim() - : 'loading...'; + const loadingLabel = + typeof stats.loadingLabel === 'string' && stats.loadingLabel.trim() + ? stats.loadingLabel.trim() + : 'loading...'; return `${source} · ${loadingLabel}`; } if (feedState === 'fallback') { - const detail = typeof stats.loadingLabel === 'string' && stats.loadingLabel.trim() - ? stats.loadingLabel.trim() - : (stats.coverage || ago); + const detail = + typeof stats.loadingLabel === 'string' && stats.loadingLabel.trim() + ? stats.loadingLabel.trim() + : stats.coverage || ago; return `${stateLabel} · ${source} · ${detail}`; } if (feedState === 'stale') { - const retry = typeof stats.retryInSec === 'number' && stats.retryInSec > 0 - ? ` · retrying in ${stats.retryInSec}s` - : ''; + const retry = + typeof stats.retryInSec === 'number' && stats.retryInSec > 0 + ? ` · retrying in ${stats.retryInSec}s` + : ''; return `${stateLabel} · ${source} · ${ago}${retry}`; } if (typeof stats.loadingLabel === 'string' && stats.loadingLabel.trim()) { @@ -339,7 +381,9 @@ export class LayerPanel { _syncToggleButton(button, layer) { const feedState = layer.enabled ? layerFeedState(layer.stats) : 'off'; - const transitioning = layer.lifecycleState === 'enabling' || layer.lifecycleState === 'disabling'; + const transitioning = + layer.lifecycleState === 'enabling' || + layer.lifecycleState === 'disabling'; const uncertain = Boolean(layer.lifecycleUncertain); button.classList.toggle('active', layer.enabled); button.classList.toggle('transitioning', transitioning); @@ -347,11 +391,16 @@ export class LayerPanel { button.classList.toggle('disabling', layer.lifecycleState === 'disabling'); button.classList.toggle('lifecycle-uncertain', uncertain); for (const state of Object.keys(FEED_STATE_LABELS)) { - button.classList.toggle(`feed-${state}`, layer.enabled && !uncertain && feedState === state); + button.classList.toggle( + `feed-${state}`, + layer.enabled && !uncertain && feedState === state, + ); } button.dataset.feedState = transitioning ? layer.lifecycleState - : (uncertain ? 'uncertain' : feedState); + : uncertain + ? 'uncertain' + : feedState; // A busy toggle remains the keyboard focus owner. `aria-disabled` plus the // click guard above prevents repeat activation without the focus loss caused // by native `disabled`. @@ -360,7 +409,11 @@ export class LayerPanel { button.setAttribute('aria-busy', String(transitioning)); button.textContent = transitioning ? layer.lifecycleState.toUpperCase() - : (uncertain ? 'UNCERTAIN' : (layer.enabled ? FEED_STATE_LABELS[feedState] : 'OFF')); + : uncertain + ? 'UNCERTAIN' + : layer.enabled + ? FEED_STATE_LABELS[feedState] + : 'OFF'; button.setAttribute('aria-label', `${layer.name}: ${button.textContent}`); }