diff --git a/README.md b/README.md index 12b0705e3..00fb9c24f 100644 --- a/README.md +++ b/README.md @@ -425,7 +425,7 @@ _The Space Missions layer replaying a Falcon 9 ascent — labeled `RECONSTRUCTED **Setup.** At [mapillary.com/dashboard/developers](https://www.mapillary.com/dashboard/developers), register an application with **Read** access and copy its **Client Token** (`MLY|…`). Paste it into **POWER UP → Mapillary**, or set `MAPILLARY_CLIENT_TOKEN` in `.env` and restart. Without it the panel reads **KEY REQUIRED**. **Google Street View** uses `GOOGLE_MAPS_API_KEY` with the **Maps JavaScript API** enabled for it in Google Cloud; its chip starts off, since every panorama is billed to the key. **KartaView** and **Panoramax** need nothing: their public APIs are keyless. -**Use.** Turn on **Street Level** under DATA LAYERS → Cameras, or with the pill in the STREET LEVEL panel header. Each provider has a chip in its own colour (Mapillary green), and one is lit at a time: lighting one switches the others off. Mapillary, which is free, is lit by default. Zoom in to street level (below 60 km) for the coverage lines; from orbit the panel says to zoom in. The **IMAGERY** (ALL / 360° / FLAT) and **SINCE** filters apply to Mapillary, KartaView and Panoramax; Street View panoramas are all 360°, so with Street View lit the filters are hidden. Click a coverage line for its image cones and a cone to open the photo. Light **KARTAVIEW** (purple) and zoom in below about 6 km: its lines are the streets KartaView has photos of, and a click on one opens the photo nearest the click, flat or 360° (drag to look around), with cones along its drive; **‹ ›** or ← → step through the drive (Shift+← →, ↑ ↓ and + − look around a 360° photo; pinch zooms on a phone). Photos KartaView has archived are skipped, and where all are archived the panel says so. For KartaView, **SINCE** goes by upload date, as KartaView's own map does (it can filter by nothing else), and the panel notes it under the slider; KartaView filters its lines on its own server, and a filtered area KartaView has not drawn before can take several seconds. Light **PANORAMAX** (orange) for the open, federated commons (strongest in France and Belgium): below about 60 km its lines are sequences. A click on a line opens that sequence's picture nearest the click without moving the camera, flat or 360°, with cones along the sequence around it; **‹ ›** or ← → step through it, and both filters apply. Its caption names the picture's authors (or the instance holding it) and licence (mostly CC BY-SA 4.0; pictures on the IGN instance are Licence Ouverte 2.0). On the **Google 3D** map (Google's terms keep Street View off other maps, where it does not appear), light **GOOGLE STREET VIEW** and click a street from below 3 km, or press **OPEN STREET VIEW** for the view centre, to open Google's panorama there; step along its arrows. Above the photo: **EXPAND** (full screen; Esc leaves) and **FIT / FILL** (Mapillary, and flat KartaView and Panoramax photos). The caption links to the image on the provider's site, and the globe credits each provider that is on ("© Mapillary contributors, CC BY-SA 4.0", "Street View imagery © Google", "© KartaView contributors, CC BY-SA 4.0", "© Panoramax contributors, licence per picture"). KartaView's own photo pages fail for signed-out visitors, so its link opens the full-size photo. +**Use.** Turn on **Street Level** under DATA LAYERS → Cameras, or with the pill in the STREET LEVEL panel header. Each provider has a chip in its own colour (Mapillary green), and one is lit at a time: lighting one switches the others off. Mapillary, which is free, is lit by default. Zoom in to street level (below 60 km) for the coverage lines; from orbit the panel says to zoom in. The **IMAGERY** (ALL / 360° / FLAT) and **SINCE** filters apply to Mapillary, KartaView and Panoramax; Street View panoramas are all 360°, so with Street View lit the filters are hidden. Click a coverage line for its image cones and a cone to open the photo. Light **KARTAVIEW** (purple) and zoom in below about 6 km: its lines are the streets KartaView has photos of, and a click on one opens the photo nearest the click, flat or 360° (drag to look around), with cones along its drive; **‹ ›** or ← → step through the drive (Shift+← →, ↑ ↓ and + − look around a 360° photo; pinch zooms on a phone). Photos KartaView has archived are skipped, and where all are archived the panel says so. For KartaView, **SINCE** goes by upload date, as KartaView's own map does (it can filter by nothing else), and the panel notes it beside the SINCE value (ANY DATE (by upload date)); KartaView filters its lines on its own server, and a filtered area KartaView has not drawn before can take several seconds. Light **PANORAMAX** (orange) for the open, federated commons (strongest in France and Belgium): below about 60 km its lines are sequences. A click on a line opens that sequence's picture nearest the click without moving the camera, flat or 360°, with cones along the sequence around it; **‹ ›** or ← → step through it, and both filters apply. Its caption names the picture's authors (or the instance holding it) and licence (mostly CC BY-SA 4.0; pictures on the IGN instance are Licence Ouverte 2.0). On the **Google 3D** map (Google's terms keep Street View off other maps, where it does not appear), light **GOOGLE STREET VIEW** and click a street from below 3 km, or press **OPEN STREET VIEW** for the view centre, to open Google's panorama there; step along its arrows. Above the photo: **EXPAND** (full screen; Esc leaves) and **FIT / FILL** (Mapillary, and flat KartaView and Panoramax photos). The caption links to the image on the provider's site, and the globe credits each provider that is on ("© Mapillary contributors, CC BY-SA 4.0", "Street View imagery © Google", "© KartaView contributors, CC BY-SA 4.0", "© Panoramax contributors, licence per picture"). KartaView's own photo pages fail for signed-out visitors, so its link opens the full-size photo. Share links carry the provider switches and filters (`0.m.0` Mapillary off, `0.m.0_0.r.google-1` Street View on, `0.m.0_0.r.google-0*kartaview-1*panoramax-0` KartaView on and `0.m.0_0.r.google-0*kartaview-0*panoramax-1` Panoramax on, as the app writes them (`0.m.0_0.r.kartaview-1` and `0.m.0_0.r.panoramax-1` work too; a link that leaves out `m` keeps Mapillary, the default, lit), `0.p.p` panoramas only, `0.s.365` the last year). To add a provider, implement the contract in `src/layers/streetLevel/registry.js` and register it in `src/app/layers/streetLevel.js`. diff --git a/changelog.d/1286-since-note-inline.md b/changelog.d/1286-since-note-inline.md new file mode 100644 index 000000000..f66467d67 --- /dev/null +++ b/changelog.d/1286-since-note-inline.md @@ -0,0 +1,4 @@ +- The Street Level SINCE note sits on the value's line, in its type and a + size smaller: with KartaView lit it reads ANY DATE (by upload date). It + had lost its size variable in the Street Level slim-down and rendered + larger than the value, on a line of its own (asturksever, #1286). diff --git a/docs/CURRENT-STATE.md b/docs/CURRENT-STATE.md index b3930264c..3b33c0561 100644 --- a/docs/CURRENT-STATE.md +++ b/docs/CURRENT-STATE.md @@ -3711,8 +3711,8 @@ lookup asks the photo API for the projection (`SPHERE`/`PLANE`; it ignores `fieldOfView`) and upload date and checks each answer the same way (when photos are near but none passes, the panel says so), and the cones pass on each photo's projection and upload date. The provider declares -`sinceBasis: 'upload'`, so while it is lit the panel shows "KartaView: by -upload date" under the SINCE slider (`sinceNote` in the UI state). Tiles come uncompressed and +`sinceBasis: 'upload'`, so while it is lit the panel shows "(by +upload date)" beside the SINCE value, as ANY DATE (by upload date) (`sinceNote` in the UI state). Tiles come uncompressed and uncacheable, so the last 24 decoded tiles are kept for 10 minutes while KartaView is lit (`createTileCache`, dropped when it is switched off); one that does not answer in 20 s is asked once more, and one that is not a line diff --git a/src/i18n/locales/es.json b/src/i18n/locales/es.json index d86ab843d..76ed72b3d 100644 --- a/src/i18n/locales/es.json +++ b/src/i18n/locales/es.json @@ -945,7 +945,7 @@ "Image by {creator}": "Imagen de {creator}", "zoom in to see KartaView coverage": "acércate para ver la cobertura de KartaView", "no KartaView photos in view": "no hay fotos de KartaView a la vista", - "{names}: by upload date": "{names}: por fecha de subida", + "(by upload date)": "(por fecha de subida)", "{n} streets in view · click a line for its nearest photo": { "one": "{n} calle a la vista · haz clic en una línea para su foto más cercana", "many": "{n} calles a la vista · haz clic en una línea para su foto más cercana", diff --git a/src/i18n/locales/ru.json b/src/i18n/locales/ru.json index 7ee31fd1c..81cd13b55 100644 --- a/src/i18n/locales/ru.json +++ b/src/i18n/locales/ru.json @@ -954,7 +954,7 @@ "Image by {creator}": "Автор снимка: {creator}", "zoom in to see KartaView coverage": "приблизьтесь, чтобы увидеть покрытие KartaView", "no KartaView photos in view": "фотографий KartaView в зоне видимости нет", - "{names}: by upload date": "{names}: по дате загрузки", + "(by upload date)": "(по дате загрузки)", "{n} streets in view · click a line for its nearest photo": { "one": "{n} улица в кадре · щёлкните линию, чтобы открыть ближайшую фотографию", "few": "{n} улицы в кадре · щёлкните линию, чтобы открыть ближайшую фотографию", diff --git a/src/layers/streetLevel/providers/kartaview/policy.js b/src/layers/streetLevel/providers/kartaview/policy.js index 184d1ceb8..939e0ec1c 100644 --- a/src/layers/streetLevel/providers/kartaview/policy.js +++ b/src/layers/streetLevel/providers/kartaview/policy.js @@ -141,7 +141,7 @@ export const FILTERED_OUT_MESSAGE = msg( 'None of KartaView’s photos here pass the imagery filter', ); /** Under the SINCE slider while KartaView is lit. */ -export const SINCE_NOTE = 'KartaView: by upload date'; +export const SINCE_NOTE = '(by upload date)'; /** The UTC calendar day of `ms`, as KartaView's startDate reads it. */ function utcDay(ms) { diff --git a/src/layers/streetLevel/registry.js b/src/layers/streetLevel/registry.js index ba0f986f2..6770cd133 100644 --- a/src/layers/streetLevel/registry.js +++ b/src/layers/streetLevel/registry.js @@ -23,7 +23,7 @@ export function filtersOf(provider) { * @property {{kind: 'view-cone', color: string}} [marker] Default: GEV's pin. * @property {Array<'pano'|'since'>} [filters] Filters it honours (default both). * @property {{noun: string, action: string}} [coverageLabel] How the panel counts its lines and says what a click does (default: sequences, "click a line for its photos"). - * @property {'capture'|'upload'} [sinceBasis] What SINCE compares (default 'capture'); 'upload' puts a note under the slider while the provider is lit. + * @property {'capture'|'upload'} [sinceBasis] What SINCE compares (default 'capture'); 'upload' notes "(by upload date)" beside the SINCE value while the provider is lit. * @property {(context: ProviderContext) => ProviderInstance} create * * @typedef {object} ProviderContext Handed to `create()` once by the core. diff --git a/src/layers/streetLevel/uiState.js b/src/layers/streetLevel/uiState.js index efb0dd750..577bb8ae1 100644 --- a/src/layers/streetLevel/uiState.js +++ b/src/layers/streetLevel/uiState.js @@ -73,16 +73,14 @@ export function filterControls(providers) { } /** - * The note under SINCE when a lit provider dates its imagery by upload, - * e.g. "KartaView: by upload date", or null. + * The note beside the SINCE value when a lit provider dates its imagery by + * upload, "(by upload date)", or null. One source is lit at a time, so the + * note needs no provider name. * @param {Array} providers */ export function sinceNote(providers) { - const names = providers - .filter((p) => p.on && p.sinceBasis === 'upload') - .map((p) => p.name); - return names.length - ? t('{names}: by upload date', { names: names.join(', ') }) + return providers.some((p) => p.on && p.sinceBasis === 'upload') + ? t('(by upload date)') : null; } diff --git a/src/layers/streetLevel/uiState.test.mjs b/src/layers/streetLevel/uiState.test.mjs index 5b9b0bf41..009600026 100644 --- a/src/layers/streetLevel/uiState.test.mjs +++ b/src/layers/streetLevel/uiState.test.mjs @@ -67,7 +67,7 @@ test('the filter controls show only for a lit provider that honours them', () => ); }); -test('a lit provider that dates by upload puts a note under SINCE', () => { +test('a lit provider that dates by upload notes it beside the SINCE value', () => { const kartaView = provider({ id: 'kartaview', name: 'KartaView', @@ -75,7 +75,7 @@ test('a lit provider that dates by upload puts a note under SINCE', () => { }); const note = (providers) => composeUIState({ ...base, providers }).sinceNote; assert.equal(note([provider()]), null, 'Mapillary: capture date'); - assert.equal(note([kartaView]), 'KartaView: by upload date'); + assert.equal(note([kartaView]), '(by upload date)'); assert.equal(note([{ ...kartaView, on: false }]), null, 'only while lit'); }); diff --git a/src/ui/streetLevelControls.test.mjs b/src/ui/streetLevelControls.test.mjs index adeaf2ae4..6f7d35688 100644 --- a/src/ui/streetLevelControls.test.mjs +++ b/src/ui/streetLevelControls.test.mjs @@ -981,9 +981,9 @@ test('a note under SINCE says what date it compares, only while one is given', ( 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' }); + layer.publish({ ...state, sinceNote: '(by upload date)' }); assert.equal(note().hidden, false); - assert.equal(note().textContent, 'KartaView: by upload date'); + assert.equal(note().textContent, '(by upload date)'); layer.publish(state); assert.equal(note().hidden, true); // The panel's own markup has it, read out with the slider. diff --git a/src/ui/styles/street-level.css b/src/ui/styles/street-level.css index 5d34ca642..d46c49660 100644 --- a/src/ui/styles/street-level.css +++ b/src/ui/styles/street-level.css @@ -236,10 +236,33 @@ html:not([lang='en']) .sl-meta-row .sl-nearest-btn { gap: 4px 10px; } -.sl-since-note { +.sl-range { + width: 100%; + min-width: 0; + margin: 0; + accent-color: var(--sl-green); + cursor: pointer; +} + +.sl-range:focus-visible { + outline: 1px solid var(--accent); + outline-offset: 3px; +} + +/* The value and its note share a line: ANY DATE (by upload date). */ +.sl-since-value { grid-column: 2; + display: flex; + flex-wrap: wrap; + align-items: baseline; + gap: 0 6px; + min-width: 0; +} + +.sl-since-note { font-family: var(--font-mono); - font-size: var(--sl-text-small); + font-size: 0.5rem; + letter-spacing: 0.08em; color: var(--text-secondary); } @@ -248,7 +271,6 @@ html:not([lang='en']) .sl-meta-row .sl-nearest-btn { } .sl-since-label { - grid-column: 2; font-family: var(--font-mono); font-size: var(--sl-text); letter-spacing: 0.08em; diff --git a/src/ui/templates/layer-panels.html b/src/ui/templates/layer-panels.html index 78ecfd777..0ad60d0ea 100644 --- a/src/ui/templates/layer-panels.html +++ b/src/ui/templates/layer-panels.html @@ -126,9 +126,11 @@
- - ANY DATE - + + + ANY DATE + +