mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 10:24:02 +00:00
refactor(street-level): drop unused tile and pose fields; keep the sequence error on the sequence
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
7411ebaf1c
commit
c78c872f92
10 files changed
+32
-60
No files matched your search
@@ -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() {
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user