mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 18:34:02 +00:00
KartaView's server filters by upload date only, so SINCE goes by it throughout for KartaView: the lines (as before), the photo a click opens and the cones, the cut-off taken as its UTC day as KartaView's startDate (passesKartaViewFilter). Providers may declare sinceBasis: 'upload' (validated); while one is lit the panel shows "KartaView: by upload date" under the SINCE slider, read out with it. The gate checks the note, and step 13's picks get 60 s on a loaded machine. Co-Authored-By: Claude Opus 5.5 <[email protected]>
137 lines
4.7 KiB
JavaScript
137 lines
4.7 KiB
JavaScript
import { COLORS } from './policy.js';
|
|
|
|
/**
|
|
* @typedef {object} ProviderSnapshot
|
|
* @property {string} id
|
|
* @property {string} name
|
|
* @property {string} label
|
|
* @property {boolean} on
|
|
* @property {boolean|null} configured null until the status call answers
|
|
* @property {boolean} keyRequired no usable key: missing or rejected
|
|
* @property {boolean} [keyRejected] the provider refused the configured key
|
|
* @property {string|null} requiresKeyId
|
|
* @property {boolean} loading
|
|
* @property {number} count
|
|
* @property {string} hint
|
|
* @property {string|null} error
|
|
* @property {string} color The source's one colour.
|
|
* @property {boolean} [groundClick]
|
|
* @property {Array<'pano'|'since'>} [filters]
|
|
* @property {{noun: string, action: string}|null} [coverageLabel]
|
|
* @property {boolean} [highlightsSequence] Its lines turn cyan when their sequence is selected.
|
|
* @property {'capture'|'upload'} [sinceBasis] What its SINCE compares.
|
|
*/
|
|
|
|
/**
|
|
* Totals over switched-on providers. Key-gated only when every one lacks its
|
|
* key; `keyRejected` when that is because a key was refused.
|
|
* @param {Array<ProviderSnapshot>} providers
|
|
*/
|
|
export function summarizeCoverage(providers) {
|
|
const active = providers.filter((p) => p.on);
|
|
return {
|
|
count: active.reduce((sum, p) => sum + (p.count || 0), 0),
|
|
loading: active.some((p) => p.loading),
|
|
hint:
|
|
providers.length === 0
|
|
? 'No street-level imagery providers configured'
|
|
: [...new Set(active.map((p) => p.hint).filter(Boolean))].join(' · '),
|
|
error: active.find((p) => p.error)?.error || null,
|
|
// One source is lit at a time; its wording counts the lines.
|
|
label: active.find((p) => p.coverageLabel)?.coverageLabel || null,
|
|
keyRequired:
|
|
active.length > 0 && active.every((p) => p.keyRequired === true),
|
|
keyRejected:
|
|
active.length > 0 &&
|
|
active.every((p) => p.keyRequired === true) &&
|
|
active.some((p) => p.keyRejected === true),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Switched-off providers that would draw with a key already set, offered
|
|
* while the switched-on ones lack theirs.
|
|
* @param {Array<ProviderSnapshot>} providers
|
|
* @returns {Array<{id: string, label: string, requiresKeyId: string|null}>}
|
|
*/
|
|
export function keyAlternatives(providers) {
|
|
if (!summarizeCoverage(providers).keyRequired) return [];
|
|
return providers
|
|
.filter((p) => !p.on && p.configured === true && p.keyRequired !== true)
|
|
.map(({ id, label, requiresKeyId }) => ({ id, label, requiresKeyId }));
|
|
}
|
|
|
|
/**
|
|
* The filter controls a switched-on provider honours (both when none is on).
|
|
* @param {Array<ProviderSnapshot>} providers
|
|
*/
|
|
export function filterControls(providers) {
|
|
const active = providers.filter((p) => p.on);
|
|
const honoured = (key) =>
|
|
active.length === 0 ||
|
|
active.some((p) => (p.filters ?? ['pano', 'since']).includes(key));
|
|
return { pano: honoured('pano'), since: honoured('since') };
|
|
}
|
|
|
|
/**
|
|
* The note under SINCE when a lit provider dates its imagery by upload,
|
|
* e.g. "KartaView: by upload date", or null.
|
|
* @param {Array<ProviderSnapshot>} providers
|
|
*/
|
|
export function sinceNote(providers) {
|
|
const names = providers
|
|
.filter((p) => p.on && p.sinceBasis === 'upload')
|
|
.map((p) => p.name);
|
|
return names.length ? `${names.join(', ')}: by upload date` : null;
|
|
}
|
|
|
|
/**
|
|
* The panel snapshot from core state and provider snapshots. The legend has
|
|
* one swatch per active source that draws coverage lines, then Selected.
|
|
* @param {{enabled: boolean, filter: object, providers: Array<ProviderSnapshot>, street: object, sequence: object}} input
|
|
*/
|
|
export function composeUIState({
|
|
enabled,
|
|
groundClickReady = false,
|
|
filter,
|
|
providers,
|
|
street,
|
|
sequence,
|
|
surface = 'draped',
|
|
}) {
|
|
const active = providers.filter((p) => p.on);
|
|
const { keyRequired, keyRejected, ...coverage } =
|
|
summarizeCoverage(providers);
|
|
const legend = active
|
|
.filter((provider) => provider.groundClick !== true)
|
|
.map((provider) => ({
|
|
key: provider.id,
|
|
label: provider.name,
|
|
color: provider.color,
|
|
}));
|
|
// "Selected" only beside a source whose lines turn cyan when selected.
|
|
if (
|
|
active.some(
|
|
(provider) =>
|
|
provider.groundClick !== true && provider.highlightsSequence !== false,
|
|
)
|
|
)
|
|
legend.push({ key: 'selected', label: 'Selected', color: COLORS.selected });
|
|
return {
|
|
enabled,
|
|
groundClickReady: groundClickReady === true,
|
|
keyRequired,
|
|
keyAlternatives: keyAlternatives(providers),
|
|
keyRejected,
|
|
filter: { ...filter },
|
|
filterControls: filterControls(providers),
|
|
sinceNote: sinceNote(providers),
|
|
providers: providers.map((p) => ({ ...p })),
|
|
coverage,
|
|
legend,
|
|
sequence: { ...sequence },
|
|
street: { ...street },
|
|
surface,
|
|
};
|
|
}
|