Files
gods-eye-view/src/app/layerPresentation.js
T
Bilawal Sidhuandmanjunath22466 082074a006 feat(layers): Recent Imagery — open satellite imagery for a selected box (#716)
Select a box on the world and browse the last 30 days of open satellite imagery over it, keyless: NASA GIBS Harmonized Landsat Sentinel-2 (30 m) and the VIIRS daily overview (250 m), with a day strip, IMAGE / VS BASEMAP / A / B modes with a swipe divider, PNG export and share links. The before/after swipe from the Nepal scene moves into shared modules so both use it.

Co-authored-by: manjunath22466 <[email protected]>
2026-09-22 20:34:11 -05:00

95 lines
3.1 KiB
JavaScript

import { LayerPanel } from '../ui/layers.js';
import { governorRequestRender } from '../renderGovernor.js';
import { markDetectionSourcesChanged } from '../data/detection.js';
/** Own the layer panel and application reactions to lifecycle activity. */
export class LayerPresentation {
constructor(
manager,
{
weatherClock,
requestRender = governorRequestRender,
invalidateDetection = markDetectionSourcesChanged,
} = {},
) {
this.manager = manager;
this.weatherClock = weatherClock;
this._panel = null;
this.pendingVisible = false;
this._unsubscribe = manager.subscribeActivity((change) => {
if (change.type === 'status') this.refresh();
else if (change.type === 'destroy-all') this.destroy();
else {
const reason =
change.type === 'data-updated'
? `layer-tick:${change.layerId}`
: change.type === 'visibility-settled'
? 'layer-visibility'
: change.type === 'params-settled'
? `layer-params:${change.layerId}`
: null;
if (!reason) return;
requestRender(reason);
if (change.type !== 'params-settled') invalidateDetection(reason);
}
});
}
get panel() {
if (!this._panel)
this._panel = new LayerPanel({
weatherClock: this.weatherClock,
getLayers: () => this.manager.getAll(),
isEnabled: (id) => this.manager.isEnabled(id),
setEnabled: (id, enabled, options) =>
this.manager.setEnabled(id, enabled, options),
setLayerParams: (id, params, options) =>
this.manager.setLayerParams(id, params, options),
getRowControls: (id) => {
const module = this.manager.layers.get(id)?.module;
try {
return module?.getRowControls?.() || null;
} catch (error) {
console.warn(`[Data] ${id} getRowControls error:`, error);
return null;
}
},
hasRowControls: (id) =>
typeof this.manager.layers.get(id)?.module?.getRowControls ===
'function',
subscribeRowControls: (id, listener) => {
const module = this.manager.layers.get(id)?.module;
module?.setRowControlsListener?.(listener);
return () => module?.setRowControlsListener?.(null);
},
onHiddenRefresh: () => {
this.pendingVisible = true;
},
});
return this._panel;
}
mount(container) {
this.panel.mount(container);
}
/** Hand the Recent Imagery readout factory to the panel (see LayerPanel). */
attachRecentImagery(factory) {
// Detaching never builds a panel that teardown already released.
if (factory) this.panel.attachRecentImagery(factory);
else this._panel?.attachRecentImagery(null);
}
refresh() {
this._panel?._refreshTogglePanel();
}
flushVisible() {
if (!this.pendingVisible) return;
this.pendingVisible = false;
this.refresh();
}
destroy() {
this._panel?.destroy();
this._panel = null;
this.pendingVisible = false;
this._unsubscribe?.();
this._unsubscribe = null;
}
}