diff --git a/src/layers/streetLevel/index.js b/src/layers/streetLevel/index.js index f86b5fd00..67bbfb52d 100644 --- a/src/layers/streetLevel/index.js +++ b/src/layers/streetLevel/index.js @@ -60,7 +60,6 @@ export function createStreetLevelLayer({ photoViewer || createMapillaryViewer({ source, render: services.render }), }); parts.openImage = openImage; - parts.reportSequenceError = reportSequenceError; parts.selection = createSelection(context); let notifyQueued = false; @@ -81,24 +80,6 @@ export function createStreetLevelLayer({ } state.notify = notify; - /** The error a sequence lookup last showed, until it withdraws it. */ - let sequenceError = null; - - /** - * Show a sequence error, or withdraw it (null). A withdrawal clears only - * that error, never a newer one from the viewer. - */ - function reportSequenceError(message) { - if (message) { - state.street.error = sequenceError = message; - } else { - if (!sequenceError) return; - if (state.street.error === sequenceError) state.street.error = null; - sequenceError = null; - } - notify(); - } - /** Coverage waits for the key status: no tile requests without a key. */ async function checkKey() { let configured; @@ -172,6 +153,7 @@ export function createStreetLevelLayer({ selectedId: state.sequence.selectedId, images: state.sequence.images.length, loading: state.sequence.loading, + error: state.sequence.error, }, street, }; @@ -228,7 +210,6 @@ export function createStreetLevelLayer({ state.listeners.clear(); state.viewer = null; state.initialized = false; - state.destroyed = true; }, getStats() { diff --git a/src/layers/streetLevel/index.test.mjs b/src/layers/streetLevel/index.test.mjs index 3af933b05..26ce49345 100644 --- a/src/layers/streetLevel/index.test.mjs +++ b/src/layers/streetLevel/index.test.mjs @@ -205,7 +205,7 @@ test('switching Mapillary off clears coverage and closes the image it shows', as assert.equal(layer.getUIState().providerOn, false); }); -test('a sequence error is withdrawn with the sequence, never the viewer’s own', async (t) => { +test('a sequence error goes with the sequence and leaves the viewer’s own', async (t) => { const source = fakeMapillarySource({ getSequenceImages: async () => { throw new Error('Sequence images unavailable'); @@ -213,16 +213,15 @@ test('a sequence error is withdrawn with the sequence, never the viewer’s own' }); const { layer } = await startLayer(t, { source }); await layer.selectSequence('seq-1'); - assert.equal(layer.getUIState().street.error, 'Sequence images unavailable'); - layer.clearSequence(); - assert.equal(layer.getUIState().street.error, null, 'withdrawn'); - - // The viewer's own error is not the sequence's to clear. - await layer.selectSequence('seq-2'); layer.attachViewerHost(null); await layer.openImage('img1'); + let ui = layer.getUIState(); + assert.equal(ui.sequence.error, 'Sequence images unavailable'); + assert.match(ui.street.error, /Open the Street Level panel/); layer.clearSequence(); - assert.match(layer.getUIState().street.error, /Open the Street Level panel/); + ui = layer.getUIState(); + assert.equal(ui.sequence.error, null, 'withdrawn with the sequence'); + assert.match(ui.street.error, /Open the Street Level panel/, 'kept'); }); test('closing the photo, switching the layer off or destroying it stops the framing flight', async (t) => { diff --git a/src/layers/streetLevel/providers/mapillary/decode.js b/src/layers/streetLevel/providers/mapillary/decode.js index a3e06a5e1..ac2538b34 100644 --- a/src/layers/streetLevel/providers/mapillary/decode.js +++ b/src/layers/streetLevel/providers/mapillary/decode.js @@ -30,13 +30,8 @@ export function decodeCoverageTile(bytes, address) { if (!parts.length) continue; result.sequences.push({ id: String(props.id ?? feature.id ?? `${x}/${y}/${i}`), - imageId: props.image_id != null ? String(props.image_id) : null, capturedAt: Number(props.captured_at) || 0, isPano: props.is_pano === true, - onFoot: props.foot === true, - quality: Number.isFinite(props.quality_score) - ? props.quality_score - : null, parts, }); } diff --git a/src/layers/streetLevel/providers/mapillary/sequences.js b/src/layers/streetLevel/providers/mapillary/sequences.js index 887145a16..d71f24ee1 100644 --- a/src/layers/streetLevel/providers/mapillary/sequences.js +++ b/src/layers/streetLevel/providers/mapillary/sequences.js @@ -25,9 +25,6 @@ function normalizeSequenceImage(record) { compassAngle: Number(record.compass_angle) || 0, capturedAt: Number(record.captured_at) || 0, isPano: record.is_pano === true, - altitude: Number.isFinite(record.computed_altitude) - ? record.computed_altitude - : null, }; } @@ -56,11 +53,6 @@ export function createSequences({ state, source, parts }) { state.notify?.(); } - /** Show a sequence error, or withdraw it (null); see `reportSequenceError`. */ - function reportError(message) { - parts.reportSequenceError?.(message); - } - /** Drawn cone billboards. */ let cones = []; @@ -151,7 +143,7 @@ export function createSequences({ state, source, parts }) { state.sequence.loading = false; state.sequence.images = cached; renderCones(cached); - reportError(null); + state.sequence.error = null; notify(); return; } @@ -177,13 +169,13 @@ export function createSequences({ state, source, parts }) { remember(sequenceId, images); state.sequence.images = images; renderCones(images); - reportError(null); + state.sequence.error = null; } catch (error) { if (!controller.signal.aborted) { // Nothing to show: drop the highlight, so a click asks again. parts.coverage.recolorSequence(sequenceId, false); state.sequence.selectedId = null; - reportError(error?.message || 'Sequence images unavailable'); + state.sequence.error = error?.message || 'Sequence images unavailable'; } } finally { if (state.sequence.abort === controller) { @@ -203,7 +195,7 @@ export function createSequences({ state, source, parts }) { state.sequence.loading = false; clearCones(); // An error about the sequence goes with it. - reportError(null); + state.sequence.error = null; requestRender(); notify(); } diff --git a/src/layers/streetLevel/providers/mapillary/sequences.test.mjs b/src/layers/streetLevel/providers/mapillary/sequences.test.mjs index 03c9834b0..5e2b8e0c7 100644 --- a/src/layers/streetLevel/providers/mapillary/sequences.test.mjs +++ b/src/layers/streetLevel/providers/mapillary/sequences.test.mjs @@ -58,7 +58,6 @@ function records(sequenceId, count = 2) { function setup() { const source = deferredSource(); const colours = []; - const errors = []; const state = { services: {}, viewer: {}, @@ -70,6 +69,7 @@ function setup() { cache: new Map(), collection: cones(), loading: false, + error: null, abort: null, }, }; @@ -77,16 +77,15 @@ function setup() { coverage: { recolorSequence: (id, selected) => colours.push([id, selected]), }, - reportSequenceError: (message) => errors.push(message), }; const sequences = createSequences({ state, source, parts }); const drawn = () => state.sequence.collection.items.map((cone) => cone.id.split(':').at(-1)); - return { state, source, sequences, colours, errors, drawn }; + return { state, source, sequences, colours, drawn }; } test('a failed sequence load leaves no stale cones and can be retried', async () => { - const { state, source, sequences, colours, errors, drawn } = setup(); + const { state, source, sequences, colours, drawn } = setup(); sequences.select('A'); source.last('A').resolve(records('A')); await settle(); @@ -100,7 +99,7 @@ test('a failed sequence load leaves no stale cones and can be retried', async () assert.equal(state.sequence.loading, false); assert.equal(state.sequence.selectedId, null, 'B is not left highlighted'); assert.deepEqual(colours.at(-1), ['B', false]); - assert.deepEqual(errors, [null, 'Sequence images unavailable']); + assert.equal(state.sequence.error, 'Sequence images unavailable'); // Clicking B again asks again. sequences.select('B'); @@ -109,7 +108,7 @@ test('a failed sequence load leaves no stale cones and can be retried', async () await settle(); assert.deepEqual(drawn(), ['B-0', 'B-1']); assert.equal(state.sequence.selectedId, 'B'); - assert.equal(errors.at(-1), null, 'the error is cleared once B loads'); + assert.equal(state.sequence.error, null, 'cleared once B loads'); }); test('a late answer for a superseded sequence neither draws nor is cached', async () => { @@ -151,7 +150,7 @@ test('a cached sequence is re-selected without a lookup', async () => { }); test('clearSelection cancels the lookup and uncolours the sequence', async () => { - const { state, source, sequences, colours, errors, drawn } = setup(); + const { state, source, sequences, colours, drawn } = setup(); sequences.select('A'); sequences.clearSelection(); assert.equal(source.last('A').signal.aborted, true); @@ -161,7 +160,7 @@ test('clearSelection cancels the lookup and uncolours the sequence', async () => ]); assert.equal(state.sequence.selectedId, null); assert.equal(state.sequence.loading, false); - assert.equal(errors.at(-1), null, 'an error about it goes too'); + assert.equal(state.sequence.error, null, 'an error about it goes too'); source.last('A').resolve(records('A')); await settle(); assert.deepEqual(drawn(), [], 'the late answer draws nothing'); diff --git a/src/layers/streetLevel/providers/mapillary/viewer.js b/src/layers/streetLevel/providers/mapillary/viewer.js index 8a9a041bf..36b6c4927 100644 --- a/src/layers/streetLevel/providers/mapillary/viewer.js +++ b/src/layers/streetLevel/providers/mapillary/viewer.js @@ -5,7 +5,6 @@ import { mapillaryImageUrl } from './policy.js'; * @property {string} imageId * @property {{lon: number, lat: number}} position * @property {number|null} bearing - * @property {number|null} tilt * @property {number|null} altitude * @property {boolean} isPano * @property {number|null} capturedAt Epoch milliseconds. @@ -94,7 +93,6 @@ export function createMapillaryViewer({ source, render } = {}) { ...current, position: { lon: lngLat.lng, lat: lngLat.lat }, bearing: Number.isFinite(pov?.bearing) ? pov.bearing : null, - tilt: Number.isFinite(pov?.tilt) ? pov.tilt : 0, externalUrl: mapillaryImageUrl(current.imageId), }); requestRender(); diff --git a/src/layers/streetLevel/state.js b/src/layers/streetLevel/state.js index 1c8424447..fad616c1e 100644 --- a/src/layers/streetLevel/state.js +++ b/src/layers/streetLevel/state.js @@ -10,7 +10,6 @@ export function freshStreet() { imageId: null, position: null, bearing: null, - tilt: null, altitude: null, isPano: false, capturedAt: null, @@ -55,7 +54,6 @@ export function createState({ services }) { viewer: null, enabled: false, initialized: false, - destroyed: false, listeners: new Set(), notify: null, /** The share link's `mapillary` switch: off draws nothing. */ @@ -78,6 +76,8 @@ export function createState({ services }) { cache: new Map(), collection: null, loading: false, + /** Why the selected sequence's images could not load, until it goes. */ + error: null, abort: null, }, diff --git a/src/layers/streetLevel/viewerHost.js b/src/layers/streetLevel/viewerHost.js index 87d58e85d..ad05fec47 100644 --- a/src/layers/streetLevel/viewerHost.js +++ b/src/layers/streetLevel/viewerHost.js @@ -23,7 +23,6 @@ export function createViewerHost({ state, parts, adapter }) { imageId: pose.imageId, position: pose.position ? { ...pose.position } : null, bearing: Number.isFinite(pose.bearing) ? pose.bearing : null, - tilt: Number.isFinite(pose.tilt) ? pose.tilt : 0, altitude: Number.isFinite(pose.altitude) ? pose.altitude : null, isPano: pose.isPano === true, capturedAt: pose.capturedAt ?? null, diff --git a/src/ui/streetLevelPresentation.js b/src/ui/streetLevelPresentation.js index 57fcad81f..cbe645417 100644 --- a/src/ui/streetLevelPresentation.js +++ b/src/ui/streetLevelPresentation.js @@ -118,7 +118,10 @@ export function presentStreetLevelPanel(state, { now = Date.now() } = {}) { controlsDisabled: state.keyRequired === true, error: keyMissing ? keyHint() - : state.street.error || state.coverage.error || null, + : state.street.error || + state.sequence.error || + state.coverage.error || + null, filter: { pano: filter.pano, sinceDays: Number(filter.sinceDays) || 0 }, since: presentSince(filter.sinceDays, now), legend: state.providerOn !== false, diff --git a/src/ui/streetLevelPresentation.test.mjs b/src/ui/streetLevelPresentation.test.mjs index cbcf4c34f..69b6a784f 100644 --- a/src/ui/streetLevelPresentation.test.mjs +++ b/src/ui/streetLevelPresentation.test.mjs @@ -124,7 +124,7 @@ test('the header pill is the on/off switch and reads OFF, LOADING or ON', () => ); }); -test('errors from the viewer or the coverage web surface in one alert', () => { +test('errors from the viewer, the sequence or the coverage surface in one alert', () => { assert.equal(presentStreetLevelPanel(snapshot()).error, null); assert.equal( presentStreetLevelPanel( @@ -132,6 +132,12 @@ test('errors from the viewer or the coverage web surface in one alert', () => { ).error, 'Image could not be opened', ); + assert.equal( + presentStreetLevelPanel( + snapshot({ sequence: { error: 'Sequence images unavailable' } }), + ).error, + 'Sequence images unavailable', + ); assert.equal( presentStreetLevelPanel(snapshot({ coverage: { error: 'Tile HTTP 502' } })) .error,