mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 19:04:02 +00:00
Merge upstream/main: KartaView provider meets i18n
Upstream's KartaView PR (#1121) extracted the Mapillary coverage engine into a shared lineCoverage.js and made the panel wording provider-specific (coverage noun/action, SINCE upload-date note, highlightsSequence legend gate), again in the regions this branch localizes. - mapillary/coverage.js: taken as upstream's thin re-export of the new lineCoverage.js; the generic too-high hint stays msg()-marked, now in lineCoverage.js where the default lives. - kartaview/policy.js: coverage hints (tooHigh, noCoverage) marked as msg() data so they translate like every other coverage hint; thrown error messages stay English, matching the Mapillary precedent. - presentMeta: KartaView counts streets, and Russian plurals need the noun inside the template, so the count line gets its own literal tn pair keyed off coverageLabel.noun, falling back to the sequences pair; upstream's noun/action data contract is untouched. - uiState legend: upstream highlightsSequence gate with t('Selected'); sinceNote localized with a {names} placeholder. - layer-panels.html: sl-since-note wiring kept, data-i18n hooks kept. - CHANGELOG: both sides kept. es/ru: 4 new keys each. npm run i18n complete (953 messages each); npm test 6504/6506 (the one failure is the pre-existing environmental OPENAI_API_KEY leak in devFreshDotenv, unrelated); build passes.
This commit is contained in:
commit
40fb2031bb
63 files changed
+10862
-1264
No files matched your search
@@ -74,6 +74,7 @@ export class StreetLevelControls {
|
||||
sinceRow: byId('sl-since-row'),
|
||||
sinceRange: byId('sl-since'),
|
||||
sinceLabel: byId('sl-since-label'),
|
||||
sinceNote: byId('sl-since-note'),
|
||||
legend: byId('sl-legend'),
|
||||
followBtn: byId('sl-follow-btn'),
|
||||
viewerWrap: byId('sl-viewer-wrap'),
|
||||
@@ -532,6 +533,9 @@ export class StreetLevelControls {
|
||||
const shows = view.filterControls || { pano: true, since: true };
|
||||
setProp(el.panoRow, 'hidden', !shows.pano);
|
||||
setProp(el.sinceRow, 'hidden', !shows.since);
|
||||
// A lit source that dates by upload says so under the slider.
|
||||
setProp(el.sinceNote, 'textContent', view.sinceNote || '');
|
||||
setProp(el.sinceNote, 'hidden', !view.sinceNote);
|
||||
setProp(el.controls, 'hidden', !shows.pano && !shows.since);
|
||||
setProp(el.legend, 'hidden', view.legend.length === 0);
|
||||
this._renderRadios(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs';
|
||||
import test from 'node:test';
|
||||
import { StreetLevelControls } from './streetLevelControls.js';
|
||||
import { createStreetLevelLayer } from '../layers/streetLevel/index.js';
|
||||
@@ -313,6 +314,7 @@ function panelDom() {
|
||||
const sinceRow = add(controls, 'div', { id: 'sl-since-row' });
|
||||
add(sinceRow, 'input', { id: 'sl-since' });
|
||||
add(sinceRow, 'output', { id: 'sl-since-label' });
|
||||
add(sinceRow, 'small', { id: 'sl-since-note' });
|
||||
add(settings, 'ul', { id: 'sl-legend' });
|
||||
const metaRow = add(settings, 'div', { classes: ['sl-meta-row'] });
|
||||
add(metaRow, 'div', { id: 'sl-coverage-meta' });
|
||||
@@ -1224,6 +1226,26 @@ test('a swap names every registered provider, also those hidden on this map', ()
|
||||
]);
|
||||
}));
|
||||
|
||||
test('a note under SINCE says what date it compares, only while one is given', () =>
|
||||
withDom(async (dom) => {
|
||||
const state = uiState();
|
||||
const { layer } = stubPanel(dom, state);
|
||||
const note = () => dom.root.querySelector('#sl-since-note');
|
||||
assert.equal(note().hidden, true, 'none for Mapillary');
|
||||
layer.publish({ ...state, sinceNote: 'KartaView: by upload date' });
|
||||
assert.equal(note().hidden, false);
|
||||
assert.equal(note().textContent, 'KartaView: by upload date');
|
||||
layer.publish(state);
|
||||
assert.equal(note().hidden, true);
|
||||
// The panel's own markup has it, read out with the slider.
|
||||
const template = fs.readFileSync(
|
||||
new URL('./templates/layer-panels.html', import.meta.url),
|
||||
'utf8',
|
||||
);
|
||||
assert.match(template, /<small id="sl-since-note"[^>]* hidden>/);
|
||||
assert.match(template, /aria-describedby="sl-since-label sl-since-note"/);
|
||||
}));
|
||||
|
||||
test('Street View lit hides the imagery filters and the legend; Mapillary lit shows them', () =>
|
||||
withDom(async (dom) => {
|
||||
const state = uiState();
|
||||
|
||||
@@ -211,18 +211,28 @@ function presentMeta(state) {
|
||||
{ n: formatNumber(state.sequence.images) },
|
||||
);
|
||||
const hint = state.coverage.hint ? t(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) },
|
||||
),
|
||||
hint,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' · ');
|
||||
if (state.coverage.count > 0) {
|
||||
// A provider whose lines are not sequences carries its wording as data
|
||||
// (coverageLabel); its count line needs its own tn pair so every language
|
||||
// gets real plural forms. New wording: add a pair here and to the catalogs.
|
||||
const count = state.coverage.count;
|
||||
const n = () => formatNumber(count);
|
||||
const countLine =
|
||||
state.coverage.label?.noun === 'streets'
|
||||
? tn(
|
||||
count,
|
||||
'{n} street in view · click a line for its nearest photo',
|
||||
'{n} streets in view · click a line for its nearest photo',
|
||||
{ n: n() },
|
||||
)
|
||||
: tn(
|
||||
count,
|
||||
'{n} sequence in view · click a line for its photos',
|
||||
'{n} sequences in view · click a line for its photos',
|
||||
{ n: n() },
|
||||
);
|
||||
return [countLine, hint].filter(Boolean).join(' · ');
|
||||
}
|
||||
return hint;
|
||||
}
|
||||
|
||||
@@ -249,6 +259,7 @@ export function presentStreetLevelPanel(state, { now = Date.now() } = {}) {
|
||||
since: state.filterControls?.since !== false,
|
||||
},
|
||||
since: presentSince(filter.sinceDays, now),
|
||||
sinceNote: state.sinceNote || null,
|
||||
legend: state.legend || [],
|
||||
viewer: presentViewer(state),
|
||||
meta: presentMeta(state),
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
#street-level-panel,
|
||||
.sl-viewer-wrap {
|
||||
--sl-green: #05cb63;
|
||||
/* KartaView's purple for lines; a lighter one where it is text (WCAG AA). */
|
||||
--sl-kartaview: #635bff;
|
||||
--sl-kartaview-text: #8f8aff;
|
||||
--sl-text: 0.62rem;
|
||||
--sl-text-small: 0.6rem;
|
||||
}
|
||||
@@ -174,6 +177,11 @@ button.sl-status:focus-visible {
|
||||
);
|
||||
}
|
||||
|
||||
/* #635bff text on its own tint is 3.7:1 at this size; the lighter one is ~6:1. */
|
||||
.sl-chips .data-toggle-chip.active[data-chip-id='kartaview'] {
|
||||
color: var(--sl-kartaview-text);
|
||||
}
|
||||
|
||||
.sl-chips .data-toggle-chip.active::before {
|
||||
opacity: 1;
|
||||
}
|
||||
@@ -291,6 +299,17 @@ button.sl-status:focus-visible {
|
||||
outline-offset: 3px;
|
||||
}
|
||||
|
||||
.sl-since-note {
|
||||
grid-column: 2;
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--sl-text-small);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.sl-since-note[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sl-since-label {
|
||||
grid-column: 2;
|
||||
font-family: var(--font-mono);
|
||||
@@ -613,6 +632,97 @@ button.sl-status:focus-visible {
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
/* ── KartaView photo viewer ──────────────────────────────────────────────── */
|
||||
.sl-kv-viewer {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: #000;
|
||||
outline: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sl-kv-viewer:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* The photo and canvas fill the viewer and would cover a ring drawn on it:
|
||||
draw the ring on a layer above them (clicks pass through). */
|
||||
.sl-kv-viewer:focus-visible::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 3;
|
||||
border: 2px solid var(--sl-kartaview-text);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.sl-kv-photo,
|
||||
.sl-kv-pano {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.sl-kv-photo {
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.sl-kv-pano {
|
||||
cursor: grab;
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
.sl-kv-pano.sl-kv-dragging {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.sl-kv-photo[hidden],
|
||||
.sl-kv-pano[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sl-kv-step {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
z-index: 2;
|
||||
width: 34px;
|
||||
height: 44px;
|
||||
margin-top: -22px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.18);
|
||||
border-radius: 8px;
|
||||
background: rgba(10, 10, 15, 0.62);
|
||||
color: #fff;
|
||||
font-size: 1.5rem;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sl-kv-step-prev {
|
||||
left: 6px;
|
||||
}
|
||||
|
||||
.sl-kv-step-next {
|
||||
right: 6px;
|
||||
}
|
||||
|
||||
.sl-kv-step:hover:not(:disabled),
|
||||
.sl-kv-step:focus-visible {
|
||||
background: color-mix(in srgb, var(--sl-kartaview) 85%, transparent);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.sl-kv-step:disabled {
|
||||
opacity: 0.3;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* Nothing to step through until a photo shows. */
|
||||
.sl-kv-viewer:not([data-kind='flat']):not([data-kind='pano']) .sl-kv-step {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* ── Short viewports ─────────────────────────────────────────────────────── */
|
||||
@media (max-height: 640px) {
|
||||
.street-level-panel-inner {
|
||||
|
||||
@@ -128,8 +128,9 @@
|
||||
</div>
|
||||
<div id="sl-since-row" class="sl-since">
|
||||
<label class="cctv-summary-label" for="sl-since" data-i18n>SINCE</label>
|
||||
<input id="sl-since" class="sl-range" type="range" min="0" max="8" step="1" value="0" aria-valuetext="ANY DATE" aria-describedby="sl-since-label" />
|
||||
<input id="sl-since" class="sl-range" type="range" min="0" max="8" step="1" value="0" aria-valuetext="ANY DATE" aria-describedby="sl-since-label sl-since-note" />
|
||||
<output id="sl-since-label" class="sl-since-label" for="sl-since">ANY DATE</output>
|
||||
<small id="sl-since-note" class="sl-since-note" hidden></small>
|
||||
</div>
|
||||
</fieldset>
|
||||
<ul id="sl-legend" class="sl-legend" aria-label="Coverage colours" data-i18n-aria-label></ul>
|
||||
|
||||
Reference in new issue
Block a user