Merge upstream/main (KartaView #1121 merged) into pr/panoramax

KartaView's later viewer changes are carried into the shared
photoViewer.js: the camera bearing (`bearingOf`), redrawing the same
photo after a GPU reset through `onReopen`, no pose while a lost context
recovers, the 2,560 px flat version only once the viewer outgrows
1,280 px, `refreshPose`, and one sharper-panorama load at a time with a
second look once it lands. Its tile cache's `keyOf` and `clear` move to
the shared tileCache.js. KartaView gets `onEscape` beside `onReopen`.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Said TurkseverandClaude Opus 5.5 committed 2026-10-14 15:52:21 +01:00
commit da0d370fa3
43 files changed
+3299 -178

No files matched your search

+4
View File
@@ -73,6 +73,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'),
@@ -527,6 +528,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(
+22
View File
@@ -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();
+1
View File
@@ -225,6 +225,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),
+11
View File
@@ -302,6 +302,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);
+2 -1
View File
@@ -128,8 +128,9 @@
</div>
<div id="sl-since-row" class="sl-since">
<label class="cctv-summary-label" for="sl-since">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"></ul>