mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 19:04:02 +00:00
Merge origin/main: Google Street View provider, translated
Main added the Google Street View street-level provider and key guidance that offers another source. Its new text goes through t()/msg() with ru and es entries; the key guidance the panel shows is built in the UI language by src/ui/keyGuidance.js, while keySetupCore.mjs keeps the English sentences the server and AI tools use. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
commit
8b0bb031b6
63 files changed
+4097
-210
No files matched your search
@@ -1,5 +1,5 @@
|
||||
import { keySetupRequirementEnvVars } from '../keySetupCore.mjs';
|
||||
import { formatNumber, msg, t, tn } from '../i18n/index.js';
|
||||
import { formatNumber, msg, t, tn, translateKnown } from '../i18n/index.js';
|
||||
import { keyGuidanceText, keyRequirementText } from './keyGuidance.js';
|
||||
|
||||
// Street Level UI state to panel strings and flags. Pure, so it is testable.
|
||||
|
||||
@@ -51,21 +51,29 @@ function presentSince(days, now) {
|
||||
};
|
||||
}
|
||||
|
||||
function formatDate(ms) {
|
||||
/** YYYY-MM-DD, or YYYY-MM for a provider that dates images by month. */
|
||||
function formatDate(ms, precision = 'day') {
|
||||
if (!Number.isFinite(ms)) return '';
|
||||
try {
|
||||
return new Date(ms).toISOString().slice(0, 10);
|
||||
return new Date(ms).toISOString().slice(0, precision === 'month' ? 7 : 10);
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/** The Provider Settings hint for a missing key, or '' for a hidden key. */
|
||||
function keyRequirement(id) {
|
||||
const envVars = keySetupRequirementEnvVars(id);
|
||||
return envVars
|
||||
? t('Needs {envVars} — add it in Provider Settings', { envVars })
|
||||
: '';
|
||||
/**
|
||||
* What the key-gated layer needs: each switched-on provider's key, and the
|
||||
* switched-off ones that would draw with a key already set.
|
||||
*/
|
||||
function keyGuidance(state) {
|
||||
const needs = (state.providers || [])
|
||||
.filter((p) => p.on && p.keyRequired && p.requiresKeyId)
|
||||
.map((p) => keyRequirementText(p.requiresKeyId))
|
||||
.filter(Boolean);
|
||||
return keyGuidanceText(
|
||||
[...new Set(needs)].join('; '),
|
||||
state.keyAlternatives || [],
|
||||
);
|
||||
}
|
||||
|
||||
function presentStatus(state) {
|
||||
@@ -76,7 +84,12 @@ function presentStatus(state) {
|
||||
if (state.keyRejected)
|
||||
return { text: t('KEY REJECTED'), tone: 'warn', pressed, title };
|
||||
if (state.keyRequired)
|
||||
return { text: t('KEY REQUIRED'), tone: 'warn', pressed, title };
|
||||
return {
|
||||
text: t('KEY REQUIRED'),
|
||||
tone: 'warn',
|
||||
pressed,
|
||||
title: keyGuidance(state) || title,
|
||||
};
|
||||
if (state.coverage.loading)
|
||||
return { text: t('LOADING'), tone: 'busy', pressed, title };
|
||||
return pressed
|
||||
@@ -94,13 +107,14 @@ function presentProviders(state) {
|
||||
? t('{name} imagery on', { name: provider.name })
|
||||
: t('{name} imagery off', { name: provider.name });
|
||||
if (provider.keyRejected && provider.error)
|
||||
title = `${provider.name}: ${provider.error}`;
|
||||
title = `${provider.name}: ${translateKnown(provider.error)}`;
|
||||
else if (keyRequired && provider.requiresKeyId)
|
||||
title = `${provider.name}: ${keyRequirement(provider.requiresKeyId)}`;
|
||||
else if (provider.error) title = `${provider.name}: ${provider.error}`;
|
||||
title = `${provider.name}: ${keyRequirementText(provider.requiresKeyId)}`;
|
||||
else if (provider.error)
|
||||
title = `${provider.name}: ${translateKnown(provider.error)}`;
|
||||
return {
|
||||
id: provider.id,
|
||||
label: provider.label,
|
||||
label: translateKnown(provider.label),
|
||||
color: provider.color || null,
|
||||
title,
|
||||
active: on,
|
||||
@@ -123,17 +137,23 @@ function presentViewer(state) {
|
||||
if (street.isPano) right.push('360°');
|
||||
if (Number.isFinite(street.bearing))
|
||||
right.push(`${Math.round(street.bearing)}°`);
|
||||
if (street.capturedAt) right.push(formatDate(street.capturedAt));
|
||||
if (street.capturedAt)
|
||||
right.push(formatDate(street.capturedAt, street.capturedAtPrecision));
|
||||
const left = [];
|
||||
if (street.title) left.push(street.title);
|
||||
if (street.creator)
|
||||
left.push(t('Image by {creator}', { creator: street.creator }));
|
||||
return {
|
||||
open: street.open === true,
|
||||
loading: street.loading === true && !street.imageId,
|
||||
renderMode: street.renderMode === 'fill' ? 'fill' : 'letterbox',
|
||||
captionLeft: street.creator
|
||||
? t('Image by {creator}', { creator: street.creator })
|
||||
: '',
|
||||
renderModes: street.renderModes !== false,
|
||||
captionLeft: left.join(' · '),
|
||||
captionRight: right.join(' · '),
|
||||
link: street.externalUrl || null,
|
||||
linkLabel: street.providerLabel ? `${street.providerLabel} ↗` : '',
|
||||
linkLabel: street.providerLabel
|
||||
? `${translateKnown(street.providerLabel)} ↗`
|
||||
: '',
|
||||
follow: {
|
||||
pressed: street.follow === true,
|
||||
disabled: street.open !== true || street.followAvailable !== true,
|
||||
@@ -149,10 +169,49 @@ function presentViewer(state) {
|
||||
};
|
||||
}
|
||||
|
||||
/** OPEN STREET VIEW: the keyboard path to providers with no coverage to click. */
|
||||
function presentNearest(state) {
|
||||
const targets = (state.providers || []).filter(
|
||||
(provider) =>
|
||||
provider.on && provider.groundClick && provider.keyRequired !== true,
|
||||
);
|
||||
if (state.enabled !== true || !targets.length)
|
||||
return {
|
||||
visible: false,
|
||||
label: '',
|
||||
title: '',
|
||||
disabled: true,
|
||||
providerIds: [],
|
||||
};
|
||||
const names = targets
|
||||
.map((provider) => translateKnown(provider.name))
|
||||
.join(t(' or '));
|
||||
const labels = targets
|
||||
.map((provider) => translateKnown(provider.label))
|
||||
.join(' / ');
|
||||
const ready = state.groundClickReady === true;
|
||||
return {
|
||||
visible: true,
|
||||
label: t('OPEN {labels}', { labels }),
|
||||
title: ready
|
||||
? t('Open {names} at the centre of the view', { names })
|
||||
: t('Zoom in to a street to open {names} here', { names }),
|
||||
disabled: !ready || state.street.loading === true,
|
||||
providerIds: targets.map((provider) => provider.id),
|
||||
};
|
||||
}
|
||||
|
||||
/** Provider hints are English data joined by " · "; translate each part. */
|
||||
function translateHint(hint) {
|
||||
return hint ? hint.split(' · ').map(translateKnown).join(' · ') : '';
|
||||
}
|
||||
|
||||
function presentMeta(state) {
|
||||
if (state.providers.length === 0)
|
||||
return state.coverage.hint ? t(state.coverage.hint) : '';
|
||||
if (state.providers.length === 0) return translateHint(state.coverage.hint);
|
||||
if (!state.enabled) return t('Switch a provider on to draw its coverage.');
|
||||
// Key-gated with another source ready: say how to use it.
|
||||
if (state.keyRequired && state.keyAlternatives?.length)
|
||||
return keyGuidance(state);
|
||||
if (state.sequence.selectedId)
|
||||
return state.sequence.loading
|
||||
? t('Loading this sequence…')
|
||||
@@ -162,14 +221,20 @@ function presentMeta(state) {
|
||||
'{n} images in this sequence · Esc clears',
|
||||
{ n: formatNumber(state.sequence.images) },
|
||||
);
|
||||
const hint = translateHint(state.coverage.hint);
|
||||
if (state.coverage.count > 0)
|
||||
return tn(
|
||||
state.coverage.count,
|
||||
'{n} sequence in view · click a line for its photos',
|
||||
'{n} sequences in view · click a line for its photos',
|
||||
{ n: formatNumber(state.coverage.count) },
|
||||
);
|
||||
return state.coverage.hint ? t(state.coverage.hint) : '';
|
||||
return [
|
||||
tn(
|
||||
state.coverage.count,
|
||||
'{n} sequence in view · click a line for its photos',
|
||||
'{n} sequences in view · click a line for its photos',
|
||||
{ n: formatNumber(state.coverage.count) },
|
||||
),
|
||||
hint,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' · ');
|
||||
return hint;
|
||||
}
|
||||
|
||||
/** @param {{now?: number}} [options] Clock for the SINCE readout. */
|
||||
@@ -190,10 +255,15 @@ export function presentStreetLevelPanel(state, { now = Date.now() } = {}) {
|
||||
? null
|
||||
: state.street.error || state.coverage.error || null,
|
||||
filter: { pano: filter.pano, sinceDays: Number(filter.sinceDays) || 0 },
|
||||
filterControls: {
|
||||
pano: state.filterControls?.pano !== false,
|
||||
since: state.filterControls?.since !== false,
|
||||
},
|
||||
since: presentSince(filter.sinceDays, now),
|
||||
legend: state.legend || [],
|
||||
viewer: presentViewer(state),
|
||||
meta: presentMeta(state),
|
||||
nearest: presentNearest(state),
|
||||
wantsOpen: state.street.open === true,
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user