fix(street-level): a sequence that fails to load opens the panel too

A Mapillary line click loads the sequence's cones, not a photo: when the
Graph API refused them, the error went to sequence.error, which the new
auto-open did not watch, so with the panel collapsed the click seemed to do
nothing. The panel now opens for an open error or a sequence error alike.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
German KhokhlovandClaude Opus 5.5 committed 2026-10-19 23:41:04 +04:00
1 parent fce328b7cd
commit 78fc5262f4
4 files changed
+35 -13

No files matched your search

+3 -3
View File
@@ -1,5 +1,5 @@
- Street Level panel: SINCE is a dropdown of its nine windows (the tooltip - Street Level panel: SINCE is a dropdown of its nine windows (the tooltip
names the cut-off date), IMAGERY and FIT / FILL are plain toggle buttons, names the cut-off date), IMAGERY and FIT / FILL are plain toggle buttons,
and the panel opens by itself only when a photo opens or fails to, no and the panel opens by itself only when a photo opens or a click fails to
longer when the layer is switched on. FIT / FILL starts at FIT each load one, no longer when the layer is switched on. FIT / FILL starts at
session (gekh, #1241). FIT each session (gekh, #1241).
+3 -2
View File
@@ -3539,8 +3539,9 @@ image; FIT/FILL starts at FIT each session. IMAGERY and FIT/FILL are rows of
toggle buttons (`aria-pressed`), each its own tab stop. SINCE is a select of toggle buttons (`aria-pressed`), each its own tab stop. SINCE is a select of
the nine windows; a link's day count between them gets its own option, and the nine windows; a link's day count between them gets its own option, and
the tooltip names the cut-off date. The panel opens by itself only when a the tooltip names the cut-off date. The panel opens by itself only when a
photo opens or fails to (a click on the map or a line would otherwise load photo opens, or a click fails to open one or to load its sequence (the
it, or its error, out of sight), never when the layer is switched on. result, or its error, would otherwise be out of sight), never when the layer
is switched on.
MapillaryJS loads on the first photo. Opening one frames it with a single MapillaryJS loads on the first photo. Opening one frames it with a single
camera flight through the application's deferred navigation (`begin` / camera flight through the application's deferred navigation (`begin` /
+10 -7
View File
@@ -29,7 +29,7 @@ export class StreetLevelControls {
this._state = null; this._state = null;
this._view = null; this._view = null;
this._wasOpen = false; this._wasOpen = false;
/** The open error last shown, so the same one never reopens the panel. */ /** The error last shown: the same one never reopens the panel. */
this._streetError = null; this._streetError = null;
/** The SINCE option for a day count between the stops, while one is set. */ /** The SINCE option for a day count between the stops, while one is set. */
this._customSince = null; this._customSince = null;
@@ -432,17 +432,20 @@ export class StreetLevelControls {
} }
/** /**
* Open the panel when an image opens, or fails to (a click on the map or a * Open the panel when an image opens, or when a click fails to open one or
* line would otherwise load it, or its error, out of sight). Never * to load its sequence (the result, or its error, would otherwise be out of
* persisted: the stored collapse state stays the user's own choice. * sight). Never persisted: the stored collapse state stays the user's own
* choice.
*/ */
_reactToTransitions(view) { _reactToTransitions(view) {
// Other size changes reach the viewer through the ResizeObserver; opening // Other size changes reach the viewer through the ResizeObserver; opening
// asks for one resize because the element may not have a size yet. // asks for one resize because the element may not have a size yet.
const open = view.viewer.open; const open = view.viewer.open;
// A photo that failed to open (a line or ground click, OPEN) says why in // A photo that failed to open (a line or ground click, OPEN), or a
// the panel: it opens for that too, or the click would seem to do nothing. // Mapillary line whose sequence failed to load, says why in the panel:
const error = this._state?.street?.error || null; // it opens for that too, or the click would seem to do nothing.
const error =
this._state?.street?.error || this._state?.sequence?.error || null;
if ((open && !this._wasOpen) || (error && error !== this._streetError)) if ((open && !this._wasOpen) || (error && error !== this._streetError))
this.setCollapsed(false, { persist: false }); this.setCollapsed(false, { persist: false });
this._wasOpen = open; this._wasOpen = open;
+19 -1
View File
@@ -426,6 +426,7 @@ function uiState({
renderMode = 'letterbox', renderMode = 'letterbox',
keyRequired = false, keyRequired = false,
streetError = null, streetError = null,
sequenceError = null,
} = {}) { } = {}) {
return { return {
enabled, enabled,
@@ -434,7 +435,12 @@ function uiState({
keyRejected: false, keyRejected: false,
filter: { pano, sinceDays }, filter: { pano, sinceDays },
coverage: { count: 3, loading: false, hint: '', error: null }, coverage: { count: 3, loading: false, hint: '', error: null },
sequence: { selectedId: null, images: 0, loading: false }, sequence: {
selectedId: null,
images: 0,
loading: false,
error: sequenceError,
},
street: { street: {
open, open,
loading: false, loading: false,
@@ -803,6 +809,18 @@ test('a photo that fails to open opens the panel too, so its error shows', () =>
controls.destroy(); controls.destroy();
})); }));
test('a sequence that fails to load opens the panel, so its error shows', () =>
withDom(async (dom) => {
const { layer, calls, controls } = stubPanel(dom, uiState());
// A Mapillary line click whose sequence lookup the Graph API refused.
layer.publish(uiState({ sequenceError: 'Mapillary did not answer' }));
assert.deepEqual(calls.collapsed, [[false, { persist: false }]]);
assert.equal(dom.root.querySelector('#sl-error').hidden, false);
layer.publish(uiState({ sequenceError: 'Mapillary did not answer' }));
assert.equal(calls.collapsed.length, 1, 'the same error opens it once');
controls.destroy();
}));
test('FIT / FILL show the selected render mode (P3)', () => test('FIT / FILL show the selected render mode (P3)', () =>
withDom(async (dom) => { withDom(async (dom) => {
const { layer, controls } = stubPanel(dom, uiState({ open: true })); const { layer, controls } = stubPanel(dom, uiState({ open: true }));