diff --git a/changelog.d/1241-street-level-panel-cuts.md b/changelog.d/1241-street-level-panel-cuts.md index 8064bc5ef..d10d0c613 100644 --- a/changelog.d/1241-street-level-panel-cuts.md +++ b/changelog.d/1241-street-level-panel-cuts.md @@ -1,4 +1,5 @@ - 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, - and the panel opens by itself only when a photo opens, no longer when the - layer is switched on. FIT / FILL starts at FIT each session (gekh, #1241). + and the panel opens by itself only when a photo opens or fails to, no + longer when the layer is switched on. FIT / FILL starts at FIT each + session (gekh, #1241). diff --git a/docs/CURRENT-STATE.md b/docs/CURRENT-STATE.md index e23ade350..22df1c290 100644 --- a/docs/CURRENT-STATE.md +++ b/docs/CURRENT-STATE.md @@ -3539,8 +3539,8 @@ 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 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 -photo opens (a click on the map or a line would otherwise load it out of -sight), never when the layer is switched on. +photo opens or fails to (a click on the map or a line would otherwise load +it, or its error, out of sight), never when the layer is switched on. MapillaryJS loads on the first photo. Opening one frames it with a single camera flight through the application's deferred navigation (`begin` / diff --git a/src/ui/streetLevelControls.js b/src/ui/streetLevelControls.js index 3a71ad2f3..0b65b2583 100644 --- a/src/ui/streetLevelControls.js +++ b/src/ui/streetLevelControls.js @@ -29,6 +29,8 @@ export class StreetLevelControls { this._state = null; this._view = null; this._wasOpen = false; + /** The open error last shown, so the same one never reopens the panel. */ + this._streetError = null; /** The SINCE option for a day count between the stops, while one is set. */ this._customSince = null; this._resizeQueued = false; @@ -430,16 +432,21 @@ export class StreetLevelControls { } /** - * Open the panel when an image opens (a click on the map or a line would - * otherwise load it out of sight). Never persisted: the stored collapse - * state stays the user's own choice. + * Open the panel when an image opens, or fails to (a click on the map or a + * line would otherwise load it, or its error, out of sight). Never + * persisted: the stored collapse state stays the user's own choice. */ _reactToTransitions(view) { // Other size changes reach the viewer through the ResizeObserver; opening // asks for one resize because the element may not have a size yet. const open = view.viewer.open; - if (open && !this._wasOpen) this.setCollapsed(false, { persist: false }); + // A photo that failed to open (a line or ground click, OPEN) says why in + // the panel: it opens for that too, or the click would seem to do nothing. + const error = this._state?.street?.error || null; + if ((open && !this._wasOpen) || (error && error !== this._streetError)) + this.setCollapsed(false, { persist: false }); this._wasOpen = open; + this._streetError = error; } destroy() { diff --git a/src/ui/streetLevelControls.test.mjs b/src/ui/streetLevelControls.test.mjs index 311c8686b..f7d2f98cd 100644 --- a/src/ui/streetLevelControls.test.mjs +++ b/src/ui/streetLevelControls.test.mjs @@ -425,6 +425,7 @@ function uiState({ sinceDays = 0, renderMode = 'letterbox', keyRequired = false, + streetError = null, } = {}) { return { enabled, @@ -439,7 +440,7 @@ function uiState({ loading: false, renderMode, imageId: open ? 'img-1' : null, - error: null, + error: streetError, }, }; } @@ -787,6 +788,21 @@ test('a photo opening opens the panel, unstored; switching the layer on does not controls.destroy(); })); +test('a photo that fails to open opens the panel too, so its error shows', () => + withDom(async (dom) => { + const { layer, calls, controls } = stubPanel(dom, uiState()); + // A line click whose lookup KartaView never answered. + layer.publish(uiState({ streetError: 'KartaView did not answer in time' })); + assert.deepEqual(calls.collapsed, [[false, { persist: false }]]); + // The same error again opens nothing more; a new one does. + layer.publish(uiState({ streetError: 'KartaView did not answer in time' })); + assert.equal(calls.collapsed.length, 1); + layer.publish(uiState()); + layer.publish(uiState({ streetError: 'No street-level imagery here' })); + assert.equal(calls.collapsed.length, 2); + controls.destroy(); + })); + test('FIT / FILL show the selected render mode (P3)', () => withDom(async (dom) => { const { layer, controls } = stubPanel(dom, uiState({ open: true }));