refactor(street-level): rename the Mapillary layer to street-level

Pure move: the layer id, panel id, CSS prefix, classes, exports and config
paths become provider-neutral (street-level, #street-level-panel, sl-);
the Mapillary-specific modules move under providers/mapillary/. Behaviour
is unchanged. The two sprite collections now register under distinct ids
instead of overwriting each other under 'mapillary'.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
Said TurkseverandClaude Fable 5.1 committed 2026-09-25 18:13:53 +01:00
1 parent 547b706f67
commit 2dbc684fb1
38 files changed
+401 -381

No files matched your search

+3 -3
View File
@@ -42,7 +42,7 @@
"opensky:import": "./scripts/opensky-import-client.sh", "opensky:import": "./scripts/opensky-import-client.sh",
"preview": "vite preview", "preview": "vite preview",
"qa:map-source-tray": "node scripts/qa-map-source-tray.mjs", "qa:map-source-tray": "node scripts/qa-map-source-tray.mjs",
"qa:mapillary": "node scripts/qa-mapillary.mjs", "qa:street-level": "node scripts/qa-street-level.mjs",
"qa:transit": "node scripts/qa-transit.mjs", "qa:transit": "node scripts/qa-transit.mjs",
"test": "node scripts/run-unit-tests.mjs", "test": "node scripts/run-unit-tests.mjs",
"test:track": "node scripts/track-regression.mjs" "test:track": "node scripts/track-regression.mjs"
@@ -254,7 +254,7 @@
"./server/providers/mapillary": { "./server/providers/mapillary": {
"node": "./server/providers/mapillary.js" "node": "./server/providers/mapillary.js"
}, },
"./layers/mapillary": "./src/layers/mapillary/index.js", "./layers/street-level": "./src/layers/streetLevel/index.js",
"./layers/mapillary/source": "./src/layers/mapillary/source.js" "./layers/street-level/mapillary/source": "./src/layers/streetLevel/providers/mapillary/source.js"
} }
} }
+20 -20
View File
@@ -840,30 +840,30 @@
"src/ui/recentImagery.test.mjs", "src/ui/recentImagery.test.mjs",
"src/layers/recentImagery/testDoubles.mjs", "src/layers/recentImagery/testDoubles.mjs",
"scripts/qa-recent-imagery.mjs", "scripts/qa-recent-imagery.mjs",
"src/ui/mapillaryControls.js", "src/ui/streetLevelControls.js",
"src/ui/mapillaryPresentation.js", "src/ui/streetLevelPresentation.js",
"src/ui/mapillaryPresentation.test.mjs", "src/ui/streetLevelPresentation.test.mjs",
"src/app/layers/mapillary.js", "src/app/layers/streetLevel.js",
"src/tooling/mapillaryProvider.test.mjs", "src/tooling/mapillaryProvider.test.mjs",
"src/layers/mapillary/source.test.mjs", "src/layers/streetLevel/providers/mapillary/source.test.mjs",
"src/mapillaryMarkup.test.mjs", "src/streetLevelMarkup.test.mjs",
"src/layers/mapillary/index.js", "src/layers/streetLevel/index.js",
"src/layers/mapillary/state.js", "src/layers/streetLevel/state.js",
"src/layers/mapillary/policy.js", "src/layers/streetLevel/providers/mapillary/policy.js",
"src/layers/mapillary/coverage.js", "src/layers/streetLevel/providers/mapillary/coverage.js",
"src/layers/mapillary/sequences.js", "src/layers/streetLevel/providers/mapillary/sequences.js",
"src/layers/mapillary/glyphs.js", "src/layers/streetLevel/glyphs.js",
"src/layers/mapillary/decode.js", "src/layers/streetLevel/providers/mapillary/decode.js",
"src/layers/mapillary/tileMath.js", "src/layers/streetLevel/tileMath.js",
"src/layers/mapillary/selection.js", "src/layers/streetLevel/selection.js",
"src/layers/mapillary/viewerBridge.js", "src/layers/streetLevel/providers/mapillary/viewer.js",
"src/layers/mapillary/source.js", "src/layers/streetLevel/providers/mapillary/source.js",
"server/providers/mapillary.js", "server/providers/mapillary.js",
"server/providers/mapillary/routes.js", "server/providers/mapillary/routes.js",
"server/providers/mapillary/constants.js", "server/providers/mapillary/constants.js",
"server/providers/mapillary/tiles.js", "server/providers/mapillary/tiles.js",
"server/providers/mapillary/trim.js", "server/providers/mapillary/trim.js",
"src/layers/mapillary/tileMath.test.mjs", "src/layers/streetLevel/tileMath.test.mjs",
"scripts/qa-mapillary.mjs", "scripts/qa-street-level.mjs",
"src/tooling/mapillaryQa.test.mjs" "src/tooling/streetLevelQa.test.mjs"
] ]
+46 -43
View File
@@ -182,7 +182,7 @@
"server/providers/mapillary/constants.js", "server/providers/mapillary/constants.js",
"server/providers/mapillary/tiles.js", "server/providers/mapillary/tiles.js",
"server/providers/mapillary/trim.js", "server/providers/mapillary/trim.js",
"src/layers/mapillary/tileMath.js", "src/layers/streetLevel/tileMath.js",
"server/providers/common/request.js", "server/providers/common/request.js",
"src/sources/rateLimit.js", "src/sources/rateLimit.js",
"server/providers/common/rate-limit.js" "server/providers/common/rate-limit.js"
@@ -628,8 +628,8 @@
"src/ui/layerBindings.js", "src/ui/layerBindings.js",
"src/ui/displayBindings.js", "src/ui/displayBindings.js",
"src/ui/shellFacade.js", "src/ui/shellFacade.js",
"src/ui/mapillaryControls.js", "src/ui/streetLevelControls.js",
"src/ui/mapillaryPresentation.js", "src/ui/streetLevelPresentation.js",
"src/ui/localSdrControls.js", "src/ui/localSdrControls.js",
"src/layers/localAdsb/feedNames.js", "src/layers/localAdsb/feedNames.js",
"src/ui/localSdrPresentation.js" "src/ui/localSdrPresentation.js"
@@ -1114,21 +1114,24 @@
], ],
"external": ["hls.js", "cesium"] "external": ["hls.js", "cesium"]
}, },
"mapillary-layer": { "street-level-layer": {
"exports": ["./layers/mapillary", "./layers/mapillary/source"], "exports": [
"./layers/street-level",
"./layers/street-level/mapillary/source"
],
"modules": [ "modules": [
"src/layers/mapillary/index.js", "src/layers/streetLevel/index.js",
"src/layers/mapillary/state.js", "src/layers/streetLevel/state.js",
"src/layers/mapillary/policy.js", "src/layers/streetLevel/providers/mapillary/policy.js",
"src/layers/mapillary/coverage.js", "src/layers/streetLevel/providers/mapillary/coverage.js",
"src/layers/mapillary/sequences.js", "src/layers/streetLevel/providers/mapillary/sequences.js",
"src/layers/mapillary/glyphs.js", "src/layers/streetLevel/glyphs.js",
"src/layers/mapillary/decode.js", "src/layers/streetLevel/providers/mapillary/decode.js",
"src/layers/mapillary/tileMath.js", "src/layers/streetLevel/tileMath.js",
"src/layers/mapillary/selection.js", "src/layers/streetLevel/selection.js",
"src/layers/mapillary/viewerBridge.js", "src/layers/streetLevel/providers/mapillary/viewer.js",
"src/layers/mapillary/source.js", "src/layers/streetLevel/providers/mapillary/source.js",
"scripts/qa-mapillary.mjs" "scripts/qa-street-level.mjs"
], ],
"external": ["cesium", "pbf", "@mapbox/vector-tile", "mapillary-js"] "external": ["cesium", "pbf", "@mapbox/vector-tile", "mapillary-js"]
}, },
@@ -1992,20 +1995,20 @@
"src/maps/imageryHost.js", "src/maps/imageryHost.js",
"src/ui/recentImagery.js", "src/ui/recentImagery.js",
"src/ui/imageryBoxTool.js", "src/ui/imageryBoxTool.js",
"src/ui/mapillaryControls.js", "src/ui/streetLevelControls.js",
"src/ui/mapillaryPresentation.js", "src/ui/streetLevelPresentation.js",
"src/layers/mapillary/index.js", "src/layers/streetLevel/index.js",
"src/layers/mapillary/state.js", "src/layers/streetLevel/state.js",
"src/layers/mapillary/policy.js", "src/layers/streetLevel/providers/mapillary/policy.js",
"src/layers/mapillary/coverage.js", "src/layers/streetLevel/providers/mapillary/coverage.js",
"src/layers/mapillary/sequences.js", "src/layers/streetLevel/providers/mapillary/sequences.js",
"src/layers/mapillary/glyphs.js", "src/layers/streetLevel/glyphs.js",
"src/layers/mapillary/decode.js", "src/layers/streetLevel/providers/mapillary/decode.js",
"src/layers/mapillary/tileMath.js", "src/layers/streetLevel/tileMath.js",
"src/layers/mapillary/selection.js", "src/layers/streetLevel/selection.js",
"src/layers/mapillary/viewerBridge.js", "src/layers/streetLevel/providers/mapillary/viewer.js",
"src/layers/mapillary/source.js", "src/layers/streetLevel/providers/mapillary/source.js",
"src/app/layers/mapillary.js" "src/app/layers/streetLevel.js"
], ],
"external": [ "external": [
"hls.js", "hls.js",
@@ -2467,18 +2470,18 @@
"src/layers/recentImagery/rendering.js", "src/layers/recentImagery/rendering.js",
"src/layers/recentImagery/thumbnails.js", "src/layers/recentImagery/thumbnails.js",
"src/maps/imageryHost.js", "src/maps/imageryHost.js",
"src/layers/mapillary/index.js", "src/layers/streetLevel/index.js",
"src/layers/mapillary/state.js", "src/layers/streetLevel/state.js",
"src/layers/mapillary/policy.js", "src/layers/streetLevel/providers/mapillary/policy.js",
"src/layers/mapillary/coverage.js", "src/layers/streetLevel/providers/mapillary/coverage.js",
"src/layers/mapillary/sequences.js", "src/layers/streetLevel/providers/mapillary/sequences.js",
"src/layers/mapillary/glyphs.js", "src/layers/streetLevel/glyphs.js",
"src/layers/mapillary/decode.js", "src/layers/streetLevel/providers/mapillary/decode.js",
"src/layers/mapillary/tileMath.js", "src/layers/streetLevel/tileMath.js",
"src/layers/mapillary/selection.js", "src/layers/streetLevel/selection.js",
"src/layers/mapillary/viewerBridge.js", "src/layers/streetLevel/providers/mapillary/viewer.js",
"src/layers/mapillary/source.js", "src/layers/streetLevel/providers/mapillary/source.js",
"src/app/layers/mapillary.js" "src/app/layers/streetLevel.js"
], ],
"external": [ "external": [
"hls.js", "hls.js",
@@ -1,8 +1,8 @@
#!/usr/bin/env node #!/usr/bin/env node
/** /**
* Browser QA for the Street Level (Mapillary) layer against a running dev * Browser QA for the Street Level layer against a running dev
* server: the panel's place in the right rail, the keyless gate, coverage, * server: the panel's place in the right rail, the keyless gate, coverage,
* the embedded viewer and its expanded dialog. Run with `npm run qa:mapillary -- --url http://localhost:4173`. * the embedded viewer and its expanded dialog. Run with `npm run qa:street-level -- --url http://localhost:4173`.
* Without MAPILLARY_CLIENT_TOKEN on the server only the keyless steps run. * Without MAPILLARY_CLIENT_TOKEN on the server only the keyless steps run.
*/ */
import assert from 'node:assert/strict'; import assert from 'node:assert/strict';
@@ -20,7 +20,7 @@ export const RAIL_ORDER = Object.freeze([
'cctv-panel', 'cctv-panel',
'weather-panel', 'weather-panel',
'recent-imagery-panel', 'recent-imagery-panel',
'mapillary-panel', 'street-level-panel',
'global-context-panel', 'global-context-panel',
]); ]);
@@ -79,10 +79,10 @@ async function main() {
el.remove(); el.remove();
}); });
const module = () => const module = () =>
window.__godsEyeView.dataManager.layers.get('mapillary').module; window.__godsEyeView.dataManager.layers.get('street-level').module;
const panel = () => const panel = () =>
page.evaluate(() => { page.evaluate(() => {
const el = document.getElementById('mapillary-panel'); const el = document.getElementById('street-level-panel');
const rail = document.getElementById('right-context-rail'); const rail = document.getElementById('right-context-rail');
const box = el.getBoundingClientRect(); const box = el.getBoundingClientRect();
return { return {
@@ -90,9 +90,9 @@ async function main() {
classes: [...el.classList], classes: [...el.classList],
width: Math.round(box.width), width: Math.round(box.width),
right: Math.round(box.right), right: Math.round(box.right),
status: document.getElementById('mly-status').textContent, status: document.getElementById('sl-status').textContent,
controlsDisabled: document.getElementById('mly-controls').disabled, controlsDisabled: document.getElementById('sl-controls').disabled,
bodyDisplay: getComputedStyle(document.getElementById('mly-body')) bodyDisplay: getComputedStyle(document.getElementById('sl-body'))
.display, .display,
}; };
}); });
@@ -115,7 +115,7 @@ async function main() {
} }
await page.setViewport(VIEWPORTS[0]); await page.setViewport(VIEWPORTS[0]);
await page.click( await page.click(
'.panel-collapse-btn[data-collapse-target="mapillary-panel"]', '.panel-collapse-btn[data-collapse-target="street-level-panel"]',
); );
await sleep(500); await sleep(500);
await step( await step(
@@ -126,7 +126,7 @@ async function main() {
assert.equal(info.bodyDisplay, 'flex'); assert.equal(info.bodyDisplay, 'flex');
assert.equal( assert.equal(
await page.evaluate( await page.evaluate(
() => document.querySelectorAll('#mly-legend li').length, () => document.querySelectorAll('#sl-legend li').length,
), ),
4, 4,
); );
@@ -137,7 +137,7 @@ async function main() {
'keyless install gates the controls and reports KEY REQUIRED', 'keyless install gates the controls and reports KEY REQUIRED',
async () => { async () => {
await page.evaluate(() => await page.evaluate(() =>
window.__godsEyeView.dataManager.setEnabled('mapillary', true, { window.__godsEyeView.dataManager.setEnabled('street-level', true, {
origin: 'user', origin: 'user',
}), }),
); );
@@ -179,14 +179,14 @@ async function main() {
'enabling draws coverage and registers the on-globe credit', 'enabling draws coverage and registers the on-globe credit',
async () => { async () => {
await page.evaluate(() => await page.evaluate(() =>
window.__godsEyeView.dataManager.setEnabled('mapillary', true, { window.__godsEyeView.dataManager.setEnabled('street-level', true, {
origin: 'user', origin: 'user',
}), }),
); );
await page.waitForFunction( await page.waitForFunction(
() => { () => {
const u = window.__godsEyeView.dataManager.layers const u = window.__godsEyeView.dataManager.layers
.get('mapillary') .get('street-level')
.module.getUIState(); .module.getUIState();
return u.coverage.sequences > 0 && !u.coverage.loading; return u.coverage.sequences > 0 && !u.coverage.loading;
}, },
@@ -206,13 +206,13 @@ async function main() {
async () => { async () => {
await page.evaluate(() => await page.evaluate(() =>
window.__godsEyeView.dataManager.layers window.__godsEyeView.dataManager.layers
.get('mapillary') .get('street-level')
.module.openNearest(), .module.openNearest(),
); );
await page.waitForFunction( await page.waitForFunction(
() => { () => {
const s = window.__godsEyeView.dataManager.layers const s = window.__godsEyeView.dataManager.layers
.get('mapillary') .get('street-level')
.module.getUIState().street; .module.getUIState().street;
return s.imageId || s.error; return s.imageId || s.error;
}, },
@@ -221,7 +221,7 @@ async function main() {
const street = await page.evaluate( const street = await page.evaluate(
() => () =>
window.__godsEyeView.dataManager.layers window.__godsEyeView.dataManager.layers
.get('mapillary') .get('street-level')
.module.getUIState().street, .module.getUIState().street,
); );
assert.equal(street.error, null, `viewer error: ${street.error}`); assert.equal(street.error, null, `viewer error: ${street.error}`);
@@ -229,20 +229,20 @@ async function main() {
try { try {
await page.waitForFunction( await page.waitForFunction(
() => () =>
document.getElementById('mly-image-when').textContent.trim() document.getElementById('sl-image-when').textContent.trim()
.length > 0, .length > 0,
{ timeout: 30_000 }, { timeout: 30_000 },
); );
} catch (error) { } catch (error) {
const dump = await page.evaluate(() => { const dump = await page.evaluate(() => {
const u = window.__godsEyeView.dataManager.layers const u = window.__godsEyeView.dataManager.layers
.get('mapillary') .get('street-level')
.module.getUIState(); .module.getUIState();
return { return {
street: u.street, street: u.street,
wrapHidden: document.getElementById('mly-viewer-wrap').hidden, wrapHidden: document.getElementById('sl-viewer-wrap').hidden,
collapsed: document collapsed: document
.getElementById('mapillary-panel') .getElementById('street-level-panel')
.classList.contains('collapsed'), .classList.contains('collapsed'),
}; };
}); });
@@ -252,10 +252,10 @@ async function main() {
); );
} }
const view = await page.evaluate(() => ({ const view = await page.evaluate(() => ({
hidden: document.getElementById('mly-viewer-wrap').hidden, hidden: document.getElementById('sl-viewer-wrap').hidden,
when: document.getElementById('mly-image-when').textContent.trim(), when: document.getElementById('sl-image-when').textContent.trim(),
width: Math.round( width: Math.round(
document.getElementById('mly-viewer').getBoundingClientRect().width, document.getElementById('sl-viewer').getBoundingClientRect().width,
), ),
})); }));
assert.equal(view.hidden, false); assert.equal(view.hidden, false);
@@ -266,10 +266,10 @@ async function main() {
await step( await step(
'EXPAND opens a modal dialog and Esc returns focus to the button', 'EXPAND opens a modal dialog and Esc returns focus to the button',
async () => { async () => {
await page.click('#mly-viewer-expand'); await page.click('#sl-viewer-expand');
await sleep(600); await sleep(600);
const dialog = await page.evaluate(() => { const dialog = await page.evaluate(() => {
const wrap = document.getElementById('mly-viewer-wrap'); const wrap = document.getElementById('sl-viewer-wrap');
return { return {
role: wrap.getAttribute('role'), role: wrap.getAttribute('role'),
modal: wrap.getAttribute('aria-modal'), modal: wrap.getAttribute('aria-modal'),
@@ -287,21 +287,21 @@ async function main() {
await page await page
.evaluate(() => ({ .evaluate(() => ({
expanded: document expanded: document
.getElementById('mly-viewer-wrap') .getElementById('sl-viewer-wrap')
.classList.contains('mly-viewer-wrap-expanded'), .classList.contains('sl-viewer-wrap-expanded'),
focus: document.activeElement?.id, focus: document.activeElement?.id,
})) }))
.then((r) => `${r.expanded}:${r.focus}`), .then((r) => `${r.expanded}:${r.focus}`),
'false:mly-viewer-expand', 'false:sl-viewer-expand',
); );
}, },
); );
await step('× closes the image and deselects it on the globe', async () => { await step('× closes the image and deselects it on the globe', async () => {
await page.click('#mly-viewer-close'); await page.click('#sl-viewer-close');
await sleep(500); await sleep(500);
const after = await page.evaluate(() => { const after = await page.evaluate(() => {
const u = window.__godsEyeView.dataManager.layers const u = window.__godsEyeView.dataManager.layers
.get('mapillary') .get('street-level')
.module.getUIState(); .module.getUIState();
return { open: u.street.open, sequence: u.sequence.selectedId }; return { open: u.street.open, sequence: u.sequence.selectedId };
}); });
+1 -1
View File
@@ -4,7 +4,7 @@ const CONTROL_LAYER_IDS = Object.freeze({
militaryFlightsLayer: 'military', militaryFlightsLayer: 'military',
satellitesLayer: 'satellites', satellitesLayer: 'satellites',
cctvLayer: 'cctv', cctvLayer: 'cctv',
mapillaryLayer: 'mapillary', streetLevelLayer: 'street-level',
radioLayer: 'radio', radioLayer: 'radio',
bikeshareLayer: 'bikeshare', bikeshareLayer: 'bikeshare',
transitLayer: 'transit', transitLayer: 'transit',
+1 -1
View File
@@ -93,7 +93,7 @@ test('controls bind catalog instances rather than similarly named defaults', ()
'military-awareness', 'military-awareness',
'military-installations', 'military-installations',
'rocket-launches', 'rocket-launches',
'mapillary', 'street-level',
'local-adsb', 'local-adsb',
]; ];
const layers = ids.map((id) => ({ id })); const layers = ids.map((id) => ({ id }));
+2 -2
View File
@@ -25,7 +25,7 @@ import { createApplicationFirms } from './layers/firms.js';
import { createApplicationEarthquakes } from './layers/earthquakes.js'; import { createApplicationEarthquakes } from './layers/earthquakes.js';
import { createApplicationFirePerimeters } from './layers/perimeters.js'; import { createApplicationFirePerimeters } from './layers/perimeters.js';
import { createApplicationCables } from './layers/submarineCables.js'; import { createApplicationCables } from './layers/submarineCables.js';
import { createApplicationMapillary } from './layers/mapillary.js'; import { createApplicationStreetLevel } from './layers/streetLevel.js';
import { createInfrastructureLayers } from '../data/infrastructure.js'; import { createInfrastructureLayers } from '../data/infrastructure.js';
import { localGeoJsonServices } from './localGeojsonServices.js'; import { localGeoJsonServices } from './localGeojsonServices.js';
import { createBhoteKoshiEventLayer } from '../data/bhoteKoshiEvent.js'; import { createBhoteKoshiEventLayer } from '../data/bhoteKoshiEvent.js';
@@ -156,7 +156,7 @@ export function createApplicationCatalog({
source: sources['fire-perimeters'], source: sources['fire-perimeters'],
}), }),
createApplicationAlpr({ surface, source: sources.alpr }), createApplicationAlpr({ surface, source: sources.alpr }),
createApplicationMapillary({ surface, source: sources.mapillary }), createApplicationStreetLevel({ surface, source: sources.mapillary }),
satellites, satellites,
createApplicationLaunches({ source: sources.launches, satellites }), createApplicationLaunches({ source: sources.launches, satellites }),
createApplicationTraffic({ source: sources.traffic }), createApplicationTraffic({ source: sources.traffic }),
@@ -1,12 +1,12 @@
import { createMapillaryLayer } from '../../layers/mapillary/index.js'; import { createStreetLevelLayer } from '../../layers/streetLevel/index.js';
import * as sprites from '../../data/spriteOrder.js'; import * as sprites from '../../data/spriteOrder.js';
import * as picking from '../../data/pickRegistry.js'; import * as picking from '../../data/pickRegistry.js';
import * as input from '../../data/inputOwnership.js'; import * as input from '../../data/inputOwnership.js';
import * as render from '../../renderGovernor.js'; import * as render from '../../renderGovernor.js';
/** Construct the Mapillary layer using the application scene owners and a supplied source. */ /** Construct the Street Level layer using the application scene owners and a supplied source. */
export function createApplicationMapillary({ surface, source }) { export function createApplicationStreetLevel({ surface, source }) {
return createMapillaryLayer({ return createStreetLevelLayer({
source, source,
services: { services: {
sprites, sprites,
+5 -1
View File
@@ -498,7 +498,6 @@ export const LAYER_STATE_REGISTRY = Object.freeze([
disposition: 'enabled-only', disposition: 'enabled-only',
}), }),
Object.freeze({ id: 'local-firms', token: 'w', disposition: 'enabled-only' }), Object.freeze({ id: 'local-firms', token: 'w', disposition: 'enabled-only' }),
Object.freeze({ id: 'mapillary', token: '3', disposition: 'enabled-only' }),
Object.freeze({ Object.freeze({
id: 'military', id: 'military',
token: 'm', token: 'm',
@@ -538,6 +537,11 @@ export const LAYER_STATE_REGISTRY = Object.freeze([
disposition: 'enabled+options', disposition: 'enabled+options',
optionOwner: 'satellites', optionOwner: 'satellites',
}), }),
Object.freeze({
id: 'street-level',
token: '3',
disposition: 'enabled-only',
}),
Object.freeze({ Object.freeze({
id: 'telegeography-submarine-cables', id: 'telegeography-submarine-cables',
token: 'u', token: 'u',
File renamed without changes.
@@ -1,7 +1,7 @@
import { createState } from './state.js'; import { createState } from './state.js';
import { createCoverage, visibleBbox } from './coverage.js'; import { createCoverage, visibleBbox } from './providers/mapillary/coverage.js';
import { createSequences } from './sequences.js'; import { createSequences } from './providers/mapillary/sequences.js';
import { createViewerBridge } from './viewerBridge.js'; import { createViewerBridge } from './providers/mapillary/viewer.js';
import { createSelection } from './selection.js'; import { createSelection } from './selection.js';
import * as Cesium from 'cesium'; import * as Cesium from 'cesium';
import { import {
@@ -9,12 +9,12 @@ import {
COVERAGE_RECENT_DAYS, COVERAGE_RECENT_DAYS,
MAPILLARY_CREDIT_HTML, MAPILLARY_CREDIT_HTML,
MAPILLARY_KEY_ID, MAPILLARY_KEY_ID,
MAPILLARY_LAYER_ID, STREET_LEVEL_LAYER_ID,
NEAREST_LIMIT, NEAREST_LIMIT,
NEAREST_RADIUS_M, NEAREST_RADIUS_M,
} from './policy.js'; } from './providers/mapillary/policy.js';
export { MAPILLARY_LAYER_ID } from './policy.js'; export { STREET_LEVEL_LAYER_ID } from './providers/mapillary/policy.js';
const SOURCE_METHODS = [ const SOURCE_METHODS = [
'getStatus', 'getStatus',
@@ -30,7 +30,7 @@ const SOURCE_METHODS = [
* are injected; nothing here reads the DOM except the viewer host the UI * are injected; nothing here reads the DOM except the viewer host the UI
* hands over. * hands over.
*/ */
export function createMapillaryLayer({ source, services = {} }) { export function createStreetLevelLayer({ source, services = {} }) {
if (!SOURCE_METHODS.every((method) => typeof source?.[method] === 'function')) if (!SOURCE_METHODS.every((method) => typeof source?.[method] === 'function'))
throw new TypeError('A Mapillary source is required'); throw new TypeError('A Mapillary source is required');
const state = createState({ services }); const state = createState({ services });
@@ -72,7 +72,7 @@ export function createMapillaryLayer({ source, services = {} }) {
try { try {
listener(snapshot); listener(snapshot);
} catch (error) { } catch (error) {
console.warn('[Data:Mapillary] listener error:', error); console.warn('[Data:StreetLevel] listener error:', error);
} }
} }
}); });
@@ -174,7 +174,7 @@ export function createMapillaryLayer({ source, services = {} }) {
} }
const layer = { const layer = {
id: MAPILLARY_LAYER_ID, id: STREET_LEVEL_LAYER_ID,
name: 'Street Level', name: 'Street Level',
icon: '📷', icon: '📷',
source: 'Mapillary', source: 'Mapillary',
@@ -184,13 +184,13 @@ export function createMapillaryLayer({ source, services = {} }) {
init(viewer) { init(viewer) {
if (state.initialized) if (state.initialized)
throw new Error('Mapillary layer is already initialized'); throw new Error('Street Level layer is already initialized');
state.viewer = viewer; state.viewer = viewer;
state.initialized = true; state.initialized = true;
parts.sequences.ensureCollections(viewer); parts.sequences.ensureCollections(viewer);
parts.sequences.setVisible(false); parts.sequences.setVisible(false);
refreshStatus(); refreshStatus();
console.log('[Data:Mapillary] Initialized'); console.log('[Data:StreetLevel] Initialized');
}, },
enable(viewer) { enable(viewer) {
@@ -4,7 +4,7 @@ import {
coverageZoomForHeight, coverageZoomForHeight,
overviewZoomForHeight, overviewZoomForHeight,
tilesForBbox, tilesForBbox,
} from './tileMath.js'; } from '../../tileMath.js';
import { import {
COLORS, COLORS,
COVERAGE_LINE_WIDTH_PX, COVERAGE_LINE_WIDTH_PX,
@@ -1,6 +1,6 @@
import { PbfReader } from 'pbf'; import { PbfReader } from 'pbf';
import { VectorTile } from '@mapbox/vector-tile'; import { VectorTile } from '@mapbox/vector-tile';
import { tileLocalToLonLat } from './tileMath.js'; import { tileLocalToLonLat } from '../../tileMath.js';
/** /**
* Decode a Mapillary coverage tile (`mly1_public`) in the browser. * Decode a Mapillary coverage tile (`mly1_public`) in the browser.
@@ -1,5 +1,5 @@
/** Identity and tuning for the Mapillary street-level layer. */ /** Identity and tuning for the Mapillary street-level layer. */
export const MAPILLARY_LAYER_ID = 'mapillary'; export const STREET_LEVEL_LAYER_ID = 'street-level';
export const MAPILLARY_KEY_ID = 'mapillary'; export const MAPILLARY_KEY_ID = 'mapillary';
export const MAPILLARY_GRAPH_HOST = 'https://graph.mapillary.com'; export const MAPILLARY_GRAPH_HOST = 'https://graph.mapillary.com';
@@ -1,5 +1,5 @@
import * as Cesium from 'cesium'; import * as Cesium from 'cesium';
import { imageConeGlyph } from './glyphs.js'; import { imageConeGlyph } from '../../glyphs.js';
import { passesImageryFilter } from './coverage.js'; import { passesImageryFilter } from './coverage.js';
import { import {
COLORS, COLORS,
@@ -64,7 +64,7 @@ export function createSequences({ state, source, parts }) {
}); });
viewer.scene.primitives.add(state.sequence.collection); viewer.scene.primitives.add(state.sequence.collection);
sprites?.registerSpriteCollection?.( sprites?.registerSpriteCollection?.(
'mapillary', 'street-level:mapillary-cones',
state.sequence.collection, state.sequence.collection,
); );
} }
@@ -74,7 +74,7 @@ export function createSequences({ state, source, parts }) {
}); });
viewer.scene.primitives.add(state.street.markerCollection); viewer.scene.primitives.add(state.street.markerCollection);
sprites?.registerSpriteCollection?.( sprites?.registerSpriteCollection?.(
'mapillary', 'street-level:marker',
state.street.markerCollection, state.street.markerCollection,
); );
} }
@@ -199,7 +199,7 @@ export function createSequences({ state, source, parts }) {
id: PICK_PREFIX.position, id: PICK_PREFIX.position,
position: cartesian, position: cartesian,
image: (id) => image: (id) =>
import('./glyphs.js').then((m) => import('../../glyphs.js').then((m) =>
m.positionMarkerGlyph({ color: COLORS.position }), m.positionMarkerGlyph({ color: COLORS.position }),
), ),
imageId: 'mly-position', imageId: 'mly-position',
@@ -232,14 +232,17 @@ export function createSequences({ state, source, parts }) {
for (const key of ['collection']) { for (const key of ['collection']) {
const collection = state.sequence[key]; const collection = state.sequence[key];
if (collection) { if (collection) {
sprites?.unregisterSpriteCollection?.('mapillary', collection); sprites?.unregisterSpriteCollection?.(
'street-level:mapillary-cones',
collection,
);
viewer?.scene?.primitives?.remove(collection); viewer?.scene?.primitives?.remove(collection);
state.sequence[key] = null; state.sequence[key] = null;
} }
} }
if (state.street.markerCollection) { if (state.street.markerCollection) {
sprites?.unregisterSpriteCollection?.( sprites?.unregisterSpriteCollection?.(
'mapillary', 'street-level:marker',
state.street.markerCollection, state.street.markerCollection,
); );
viewer?.scene?.primitives?.remove(state.street.markerCollection); viewer?.scene?.primitives?.remove(state.street.markerCollection);
@@ -1,5 +1,8 @@
import * as Cesium from 'cesium'; import * as Cesium from 'cesium';
import { MAPILLARY_LAYER_ID, PICK_PREFIX } from './policy.js'; import {
STREET_LEVEL_LAYER_ID,
PICK_PREFIX,
} from './providers/mapillary/policy.js';
/** Click handling for coverage lines and image cones. */ /** Click handling for coverage lines and image cones. */
export function createSelection({ state, parts }) { export function createSelection({ state, parts }) {
@@ -90,7 +93,7 @@ export function createSelection({ state, parts }) {
Cesium.ScreenSpaceEventType.MOUSE_MOVE, Cesium.ScreenSpaceEventType.MOUSE_MOVE,
); );
document.addEventListener('keydown', onKeyDown); document.addEventListener('keydown', onKeyDown);
picking?.registerPickOwner?.(MAPILLARY_LAYER_ID, ownsPick); picking?.registerPickOwner?.(STREET_LEVEL_LAYER_ID, ownsPick);
} }
function uninstall() { function uninstall() {
@@ -102,7 +105,7 @@ export function createSelection({ state, parts }) {
state.clickHandler.destroy(); state.clickHandler.destroy();
state.clickHandler = null; state.clickHandler = null;
document.removeEventListener('keydown', onKeyDown); document.removeEventListener('keydown', onKeyDown);
picking?.unregisterPickOwner?.(MAPILLARY_LAYER_ID); picking?.unregisterPickOwner?.(STREET_LEVEL_LAYER_ID);
} }
return { install, uninstall, ownsPick }; return { install, uninstall, ownsPick };
File renamed without changes.
File renamed without changes.
+1 -1
View File
@@ -16,7 +16,7 @@ import { createWeatherSource } from '../layers/weather/source.js';
import { createCycloneSource } from '../layers/cyclones/source.js'; import { createCycloneSource } from '../layers/cyclones/source.js';
import { createWindSource } from '../layers/wind/source.js'; import { createWindSource } from '../layers/wind/source.js';
import { createFirmsSource } from '../layers/firms/source.js'; import { createFirmsSource } from '../layers/firms/source.js';
import { createMapillarySource } from '../layers/mapillary/source.js'; import { createMapillarySource } from '../layers/streetLevel/providers/mapillary/source.js';
import { createReferenceSources } from '../sources/reference.js'; import { createReferenceSources } from '../sources/reference.js';
export { createReferenceSources as createStandaloneReferenceSources } from '../sources/reference.js'; export { createReferenceSources as createStandaloneReferenceSources } from '../sources/reference.js';
@@ -13,66 +13,69 @@ const html = expandApplicationHtml(read('index.html'));
const css = readStylesheet(path.join(ROOT, 'style.css')); const css = readStylesheet(path.join(ROOT, 'style.css'));
const panelChrome = read('src/ui/panelChrome.js'); const panelChrome = read('src/ui/panelChrome.js');
const layoutController = read('src/ui/panelLayoutController.js'); const layoutController = read('src/ui/panelLayoutController.js');
const mapillaryCss = read('src/ui/styles/mapillary.css'); const panelCss = read('src/ui/styles/street-level.css');
const controls = read('src/ui/mapillaryControls.js'); const controls = read('src/ui/streetLevelControls.js');
test('Street Level is an ordinary collapsible GEV panel that starts collapsed', () => { test('Street Level is an ordinary collapsible GEV panel that starts collapsed', () => {
assert.match( assert.match(
html, html,
/<div id="mapillary-panel" class="panel-collapsible collapsed" data-panel-id="mapillary-panel">/, /<div id="street-level-panel" class="panel-collapsible collapsed" data-panel-id="street-level-panel">/,
); );
assert.match( assert.match(
html, html,
/<button class="panel-collapse-btn" data-collapse-target="mapillary-panel"/, /<button class="panel-collapse-btn" data-collapse-target="street-level-panel"/,
); );
assert.match(html, /<span class="panel-title">STREET LEVEL<\/span>/); assert.match(html, /<span class="panel-title">STREET LEVEL<\/span>/);
assert.match(html, /<svg class="mly-mark"/); assert.match(html, /<svg class="sl-mark"/);
assert.doesNotMatch( assert.doesNotMatch(
html, html,
/mapillary-dock|mly-minimized|mly-3d-btn|mly-photoreal-btn/, /mapillary-dock|sl-minimized|sl-3d-btn|sl-photoreal-btn/,
); );
}); });
test('the panel is registered with panel chrome, cockpit entry and the right rail', () => { test('the panel is registered with panel chrome, cockpit entry and the right rail', () => {
assert.match(panelChrome, /\{ id: 'mapillary-panel' \}/); assert.match(panelChrome, /\{ id: 'street-level-panel' \}/);
assert.match( assert.match(
panelChrome, panelChrome,
/COCKPIT_ENTRY_COLLAPSE_PANEL_IDS = Object\.freeze\(\[[\s\S]*'mapillary-panel'/, /COCKPIT_ENTRY_COLLAPSE_PANEL_IDS = Object\.freeze\(\[[\s\S]*'street-level-panel'/,
);
assert.match(
panelChrome,
/const isRightRail = \[[\s\S]*'street-level-panel'/,
); );
assert.match(panelChrome, /const isRightRail = \[[\s\S]*'mapillary-panel'/);
// The layout controller moves every rail panel in one loop; ours is listed. // The layout controller moves every rail panel in one loop; ours is listed.
assert.match( assert.match(
layoutController, layoutController,
/for \(const panel of \[[\s\S]*?this\._mapillaryPanel,[\s\S]*?\]\) \{[\s\S]*?stack\.insertBefore\(panel, globalContextPanel\)/, /for \(const panel of \[[\s\S]*?this\._streetLevelPanel,[\s\S]*?\]\) \{[\s\S]*?stack\.insertBefore\(panel, globalContextPanel\)/,
); );
for (const rule of [ for (const rule of [
'#right-context-rail > #mapillary-panel', '#right-context-rail > #street-level-panel',
'#right-context-rail #mapillary-panel.collapsed', '#right-context-rail #street-level-panel.collapsed',
'#right-context-rail.layout-focus > #mapillary-panel:not(.collapsed)', '#right-context-rail.layout-focus > #street-level-panel:not(.collapsed)',
]) ])
assert.ok(css.includes(rule), `layers.css names ${rule}`); assert.ok(css.includes(rule), `layers.css names ${rule}`);
}); });
test('panel styles stay inside GEV conventions: no !important, no fixed panel', () => { test('panel styles stay inside GEV conventions: no !important, no fixed panel', () => {
assert.equal((mapillaryCss.match(/!important/g) || []).length, 0); assert.equal((panelCss.match(/!important/g) || []).length, 0);
const fixed = mapillaryCss const fixed = panelCss
.replace(/\/\*[\s\S]*?\*\//g, '') .replace(/\/\*[\s\S]*?\*\//g, '')
.split('}') .split('}')
.filter((block) => /position:\s*fixed/.test(block)) .filter((block) => /position:\s*fixed/.test(block))
.map((block) => block.slice(0, block.indexOf('{')).trim()); .map((block) => block.slice(0, block.indexOf('{')).trim());
assert.deepEqual(fixed, ['.mly-viewer-wrap-expanded']); assert.deepEqual(fixed, ['.sl-viewer-wrap-expanded']);
}); });
test('the keyless state gates the controls rather than leaving dead buttons', () => { test('the keyless state gates the controls rather than leaving dead buttons', () => {
// The key requirement is documented (README, .env.example), not repeated in the panel. // The key requirement is documented (README, .env.example), not repeated in the panel.
assert.doesNotMatch( assert.doesNotMatch(
html, html,
/mly-keyless|mly-query|mly-results|data-mly-suggestion/, /sl-keyless|sl-query|sl-results|data-sl-suggestion/,
); );
assert.match(html, /<fieldset id="mly-controls"/); assert.match(html, /<fieldset id="sl-controls"/);
assert.match(html, /<ul id="mly-legend"/); assert.match(html, /<ul id="sl-legend"/);
assert.match(html, /id="mly-error"[^>]*role="alert"/); assert.match(html, /id="sl-error"[^>]*role="alert"/);
assert.match(html, /id="mly-status"[^>]*role="status"/); assert.match(html, /id="sl-status"[^>]*role="status"/);
}); });
test('the expanded viewer is a modal dialog that restores focus', () => { test('the expanded viewer is a modal dialog that restores focus', () => {
@@ -5,7 +5,7 @@ import {
isCollapsed, isCollapsed,
RAIL_ORDER, RAIL_ORDER,
VIEWPORTS, VIEWPORTS,
} from '../../scripts/qa-mapillary.mjs'; } from '../../scripts/qa-street-level.mjs';
test('the QA harness exercises both review viewports', () => { test('the QA harness exercises both review viewports', () => {
assert.deepEqual( assert.deepEqual(
@@ -20,7 +20,7 @@ test('rail order puts Street Level between CCTV and Context', () => {
'cctv-panel', 'cctv-panel',
'weather-panel', 'weather-panel',
'recent-imagery-panel', 'recent-imagery-panel',
'mapillary-panel', 'street-level-panel',
'global-context-panel', 'global-context-panel',
]); ]);
assert.equal(isCollapsed(['panel-collapsible', 'collapsed']), true); assert.equal(isCollapsed(['panel-collapsible', 'collapsed']), true);
@@ -29,7 +29,7 @@ test('rail order puts Street Level between CCTV and Context', () => {
test('the harness only runs its browser flow when executed directly', () => { test('the harness only runs its browser flow when executed directly', () => {
const source = fs.readFileSync( const source = fs.readFileSync(
new URL('../../scripts/qa-mapillary.mjs', import.meta.url), new URL('../../scripts/qa-street-level.mjs', import.meta.url),
'utf8', 'utf8',
); );
assert.match( assert.match(
+15 -15
View File
@@ -15,7 +15,7 @@ import { readShellElements } from './shellElements.js';
import { CockpitCoordinator } from './cockpitCoordinator.js'; import { CockpitCoordinator } from './cockpitCoordinator.js';
import { ContextControls } from './context.js'; import { ContextControls } from './context.js';
import { CctvControls } from './cctv.js'; import { CctvControls } from './cctv.js';
import { MapillaryControls } from './mapillaryControls.js'; import { StreetLevelControls } from './streetLevelControls.js';
import { RadioControls } from './radio.js'; import { RadioControls } from './radio.js';
import { LocalSdrControls } from './localSdrControls.js'; import { LocalSdrControls } from './localSdrControls.js';
import { LocationNavigation } from './locationNavigation.js'; import { LocationNavigation } from './locationNavigation.js';
@@ -570,7 +570,7 @@ export class StyleManager extends ShellFacade {
this._initRightPanelAdaptiveLayout(); this._initRightPanelAdaptiveLayout();
this._initRadioPanel(); this._initRadioPanel();
this._initCctvPanel(); this._initCctvPanel();
this._initMapillaryPanel(); this._initStreetLevelPanel();
this._initGlobalContextPanel(); this._initGlobalContextPanel();
this._initLocationBar(); this._initLocationBar();
this._initShareButton(); this._initShareButton();
@@ -933,26 +933,26 @@ export class StyleManager extends ShellFacade {
} }
/** Compose camera panel controls from the existing camera port and application actions. */ /** Compose camera panel controls from the existing camera port and application actions. */
_initMapillaryPanel() { _initStreetLevelPanel() {
const { mapillaryLayer } = this.services; const { streetLevelLayer } = this.services;
this._mapillaryControls?.destroy(); this._streetLevelControls?.destroy();
this._mapillaryControls = null; this._streetLevelControls = null;
if (!this._mapillaryPanel || !mapillaryLayer) return; if (!this._streetLevelPanel || !streetLevelLayer) return;
this._mapillaryControls = new MapillaryControls({ this._streetLevelControls = new StreetLevelControls({
root: this._mapillaryPanel, root: this._streetLevelPanel,
mapillary: mapillaryLayer, layer: streetLevelLayer,
actions: { actions: {
isEnabled: () => this._dataManager?.isEnabled('mapillary') === true, isEnabled: () => this._dataManager?.isEnabled('street-level') === true,
setEnabled: (enabled) => setEnabled: (enabled) =>
this._dataManager?.setEnabled('mapillary', enabled, { this._dataManager?.setEnabled('street-level', enabled, {
origin: 'user', origin: 'user',
}), }),
setPanelCollapsed: (collapsed, options) => setPanelCollapsed: (collapsed, options) =>
this.setPanelCollapsed('mapillary-panel', collapsed, options), this.setPanelCollapsed('street-level-panel', collapsed, options),
showToast: (message) => this._showToast(message), showToast: (message) => this._showToast(message),
}, },
}); });
this._mapillaryControls.connect(); this._streetLevelControls.connect();
} }
_initCctvPanel() { _initCctvPanel() {
@@ -1557,7 +1557,7 @@ export class StyleManager extends ShellFacade {
this._cameraOrientationControls?.destroy(); this._cameraOrientationControls?.destroy();
this._clearLayersControl?.destroy(); this._clearLayersControl?.destroy();
this._cctvControls?.destroy(); this._cctvControls?.destroy();
this._mapillaryControls?.destroy(); this._streetLevelControls?.destroy();
this._radioControls?.destroy(); this._radioControls?.destroy();
this._localSdrControls?.destroy(); this._localSdrControls?.destroy();
this._cockpitCoordinator.stop(); this._cockpitCoordinator.stop();
+2 -2
View File
@@ -32,7 +32,7 @@ const PANEL_GROUPS = [
}, },
{ {
label: 'Cameras', label: 'Cameras',
ids: ['cctv', 'recent-imagery', 'mapillary'], ids: ['cctv', 'recent-imagery', 'street-level'],
}, },
{ {
label: 'Infrastructure', label: 'Infrastructure',
@@ -73,7 +73,7 @@ const PANEL_LABELS = {
'ais-live-vessels': 'Live Vessels', 'ais-live-vessels': 'Live Vessels',
bikeshare: 'Bike Share', bikeshare: 'Bike Share',
cctv: 'Cameras', cctv: 'Cameras',
mapillary: 'Street Level (Mapillary)', 'street-level': 'Street Level',
'alpr-cameras': 'Mapped ALPR Cameras', 'alpr-cameras': 'Mapped ALPR Cameras',
'local-datacenters': 'Data Centers', 'local-datacenters': 'Data Centers',
'local-firms': 'Active Fires', 'local-firms': 'Active Fires',
+3 -3
View File
@@ -14,7 +14,7 @@ const SHARE_PANEL_STATE_SPECS = Object.freeze([
{ id: 'cctv-panel' }, { id: 'cctv-panel' },
{ id: 'weather-panel' }, { id: 'weather-panel' },
{ id: 'recent-imagery-panel' }, { id: 'recent-imagery-panel' },
{ id: 'mapillary-panel' }, { id: 'street-level-panel' },
{ id: 'radio-panel' }, { id: 'radio-panel' },
{ id: 'scene-panel' }, { id: 'scene-panel' },
{ id: 'global-context-panel' }, { id: 'global-context-panel' },
@@ -27,7 +27,7 @@ const COCKPIT_ENTRY_COLLAPSE_PANEL_IDS = Object.freeze([
'cctv-panel', 'cctv-panel',
'weather-panel', 'weather-panel',
'recent-imagery-panel', 'recent-imagery-panel',
'mapillary-panel', 'street-level-panel',
'scene-panel', 'scene-panel',
'pp-toggles', 'pp-toggles',
'global-context-panel', 'global-context-panel',
@@ -290,7 +290,7 @@ export class PanelChrome {
'cctv-panel', 'cctv-panel',
'weather-panel', 'weather-panel',
'recent-imagery-panel', 'recent-imagery-panel',
'mapillary-panel', 'street-level-panel',
'global-context-panel', 'global-context-panel',
].includes(panelEl?.id); ].includes(panelEl?.id);
const collapsed = panelEl.classList.contains('collapsed'); const collapsed = panelEl.classList.contains('collapsed');
+3 -3
View File
@@ -97,7 +97,7 @@ export class PanelLayoutController {
this._cctvPanel = document.getElementById('cctv-panel'); this._cctvPanel = document.getElementById('cctv-panel');
this._weatherPanel = document.getElementById('weather-panel'); this._weatherPanel = document.getElementById('weather-panel');
this._recentImageryPanel = document.getElementById('recent-imagery-panel'); this._recentImageryPanel = document.getElementById('recent-imagery-panel');
this._mapillaryPanel = document.getElementById('mapillary-panel'); this._streetLevelPanel = document.getElementById('street-level-panel');
this._sliderPanel = document.getElementById('param-slider-panel'); this._sliderPanel = document.getElementById('param-slider-panel');
this._detectionBtn = document.getElementById('detection-toggle'); this._detectionBtn = document.getElementById('detection-toggle');
} }
@@ -202,7 +202,7 @@ export class PanelLayoutController {
this._cctvPanel, this._cctvPanel,
this._weatherPanel, this._weatherPanel,
this._recentImageryPanel, this._recentImageryPanel,
this._mapillaryPanel, this._streetLevelPanel,
]) { ]) {
if (!panel) continue; if (!panel) continue;
for (const property of ['top', 'right', 'bottom', 'left', 'z-index']) for (const property of ['top', 'right', 'bottom', 'left', 'z-index'])
@@ -231,7 +231,7 @@ export class PanelLayoutController {
this._cctvPanel, this._cctvPanel,
this._weatherPanel, this._weatherPanel,
this._recentImageryPanel, this._recentImageryPanel,
this._mapillaryPanel, this._streetLevelPanel,
globalContextPanel, globalContextPanel,
document.getElementById('radio-panel'), document.getElementById('radio-panel'),
]) { ]) {
+1 -1
View File
@@ -59,7 +59,7 @@ export function readShellElements(document = globalThis.document) {
_dataPanel: document.getElementById('data-panel'), _dataPanel: document.getElementById('data-panel'),
_scenePanel: document.getElementById('scene-panel'), _scenePanel: document.getElementById('scene-panel'),
_cctvPanel: document.getElementById('cctv-panel'), _cctvPanel: document.getElementById('cctv-panel'),
_mapillaryPanel: document.getElementById('mapillary-panel'), _streetLevelPanel: document.getElementById('street-level-panel'),
_radioPanel: document.getElementById('radio-panel'), _radioPanel: document.getElementById('radio-panel'),
_contextRadioDock: document.getElementById('context-radio-dock'), _contextRadioDock: document.getElementById('context-radio-dock'),
_contextRadioToggleBtn: document.getElementById('context-radio-toggle-btn'), _contextRadioToggleBtn: document.getElementById('context-radio-toggle-btn'),
@@ -1,22 +1,22 @@
import { presentMapillaryPanel } from './mapillaryPresentation.js'; import { presentStreetLevelPanel } from './streetLevelPresentation.js';
export { mapillaryImageUrl } from './mapillaryPresentation.js'; const RENDER_MODE_KEY = 'gev:street-level:render-mode';
/** Key the panel used before it became provider-neutral. */
const RENDER_MODE_KEY = 'gev:mapillary:render-mode'; const LEGACY_RENDER_MODE_KEY = 'gev:mapillary:render-mode';
const FOCUSABLE = const FOCUSABLE =
'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])'; 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';
/** /**
* Own the Street Level panel: the imagery filters, the legend and the * Own the Street Level panel: the imagery filters, the legend and the
* embedded MapillaryJS viewer. The panel itself is * embedded street-level viewer. The panel itself is
* ordinary GEV chrome (collapse button, rail layout, persistence) driven by * ordinary GEV chrome (collapse button, rail layout, persistence) driven by
* the application shell; this class only fills the body and asks the shell * the application shell; this class only fills the body and asks the shell
* to open the panel when something worth seeing arrives. * to open the panel when something worth seeing arrives.
*/ */
export class MapillaryControls { export class StreetLevelControls {
constructor({ root, mapillary, actions }) { constructor({ root, layer, actions }) {
this.root = root; this.root = root;
this.mapillary = mapillary; this.layer = layer;
this.actions = actions; this.actions = actions;
this.destroyed = false; this.destroyed = false;
this.listeners = new AbortController(); this.listeners = new AbortController();
@@ -34,24 +34,24 @@ export class MapillaryControls {
_collect() { _collect() {
const byId = (id) => this.root?.querySelector(`#${id}`) || null; const byId = (id) => this.root?.querySelector(`#${id}`) || null;
return { return {
status: byId('mly-status'), status: byId('sl-status'),
controls: byId('mly-controls'), controls: byId('sl-controls'),
enableBtn: byId('mly-enable-btn'), enableBtn: byId('sl-enable-btn'),
lookBtn: byId('mly-look-btn'), lookBtn: byId('sl-look-btn'),
error: byId('mly-error'), error: byId('sl-error'),
errorText: byId('mly-error-text'), errorText: byId('sl-error-text'),
sinceSelect: byId('mly-since'), sinceSelect: byId('sl-since'),
legend: byId('mly-legend'), legend: byId('sl-legend'),
followBtn: byId('mly-follow-btn'), followBtn: byId('sl-follow-btn'),
viewerWrap: byId('mly-viewer-wrap'), viewerWrap: byId('sl-viewer-wrap'),
viewerExpand: byId('mly-viewer-expand'), viewerExpand: byId('sl-viewer-expand'),
viewerClose: byId('mly-viewer-close'), viewerClose: byId('sl-viewer-close'),
viewerPlaceholder: byId('mly-viewer-placeholder'), viewerPlaceholder: byId('sl-viewer-placeholder'),
viewer: byId('mly-viewer'), viewer: byId('sl-viewer'),
imageBy: byId('mly-image-by'), imageBy: byId('sl-image-by'),
imageWhen: byId('mly-image-when'), imageWhen: byId('sl-image-when'),
imageLink: byId('mly-image-link'), imageLink: byId('sl-image-link'),
coverageMeta: byId('mly-coverage-meta'), coverageMeta: byId('sl-coverage-meta'),
}; };
} }
@@ -65,16 +65,16 @@ export class MapillaryControls {
_bind() { _bind() {
const el = this._elements; const el = this._elements;
if (!this.root) return; if (!this.root) return;
this.mapillary.attachViewerHost?.(el.viewer); this.layer.attachViewerHost?.(el.viewer);
this.listen(el.enableBtn, 'click', () => this._toggleEnabled()); this.listen(el.enableBtn, 'click', () => this._toggleEnabled());
this.listen(el.lookBtn, 'click', async () => { this.listen(el.lookBtn, 'click', async () => {
if (!(await this._ensureEnabled())) return; if (!(await this._ensureEnabled())) return;
this.mapillary.openNearest?.(); this.layer.openNearest?.();
}); });
for (const button of this.root.querySelectorAll('[data-mly-pano]')) { for (const button of this.root.querySelectorAll('[data-sl-pano]')) {
this.listen(button, 'click', () => this.listen(button, 'click', () =>
this.mapillary.setCoverageFilter?.({ pano: button.dataset.mlyPano }), this.layer.setCoverageFilter?.({ pano: button.dataset.slPano }),
); );
} }
this.listen(el.sinceSelect, 'change', () => { this.listen(el.sinceSelect, 'change', () => {
@@ -84,19 +84,19 @@ export class MapillaryControls {
sinceMs = Date.UTC(Number(value.slice(5)), 0, 1); sinceMs = Date.UTC(Number(value.slice(5)), 0, 1);
else if (Number(value) > 0) else if (Number(value) > 0)
sinceMs = Date.now() - Number(value) * 86_400_000; sinceMs = Date.now() - Number(value) * 86_400_000;
this.mapillary.setCoverageFilter?.({ sinceMs }); this.layer.setCoverageFilter?.({ sinceMs });
}); });
this.listen(el.followBtn, 'click', () => { this.listen(el.followBtn, 'click', () => {
this.mapillary.setFollow?.(!(this._state?.street?.follow === true)); this.layer.setFollow?.(!(this._state?.street?.follow === true));
}); });
this.listen(el.viewerClose, 'click', () => this.mapillary.closeViewer?.()); this.listen(el.viewerClose, 'click', () => this.layer.closeViewer?.());
this.listen(el.viewerExpand, 'click', () => this.listen(el.viewerExpand, 'click', () =>
this.setViewerExpanded(!this.isViewerExpanded()), this.setViewerExpanded(!this.isViewerExpanded()),
); );
for (const button of this.root.querySelectorAll('[data-mly-render]')) { for (const button of this.root.querySelectorAll('[data-sl-render]')) {
this.listen(button, 'click', () => { this.listen(button, 'click', () => {
const mode = button.dataset.mlyRender; const mode = button.dataset.slRender;
this.mapillary.setViewerRenderMode?.(mode); this.layer.setViewerRenderMode?.(mode);
try { try {
localStorage.setItem(RENDER_MODE_KEY, mode); localStorage.setItem(RENDER_MODE_KEY, mode);
} catch { } catch {
@@ -105,9 +105,11 @@ export class MapillaryControls {
}); });
} }
try { try {
const stored = localStorage.getItem(RENDER_MODE_KEY); const stored =
localStorage.getItem(RENDER_MODE_KEY) ??
localStorage.getItem(LEGACY_RENDER_MODE_KEY);
if (stored === 'fill' || stored === 'letterbox') if (stored === 'fill' || stored === 'letterbox')
this.mapillary.setViewerRenderMode?.(stored); this.layer.setViewerRenderMode?.(stored);
} catch { } catch {
/* storage unavailable */ /* storage unavailable */
} }
@@ -121,7 +123,7 @@ export class MapillaryControls {
queued = true; queued = true;
requestAnimationFrame(() => { requestAnimationFrame(() => {
queued = false; queued = false;
this.mapillary.resizeViewer?.(); this.layer.resizeViewer?.();
}); });
}); });
this._resizeObserver.observe(el.viewer); this._resizeObserver.observe(el.viewer);
@@ -154,23 +156,20 @@ export class MapillaryControls {
this._unsubscribe?.(); this._unsubscribe?.();
this._unsubscribe = null; this._unsubscribe = null;
if (this.destroyed || !this.root) return; if (this.destroyed || !this.root) return;
this._unsubscribe = this.mapillary.subscribe?.((state) => this._unsubscribe = this.layer.subscribe?.((state) => this.render(state));
this.render(state), if (this.layer.getUIState) this.render(this.layer.getUIState());
);
if (this.mapillary.getUIState) this.render(this.mapillary.getUIState());
} }
/** Ask the shell to open (or close) the rail panel. */ /** Ask the shell to open (or close) the rail panel. */
setCollapsed(collapsed, options = {}) { setCollapsed(collapsed, options = {}) {
this.actions.setPanelCollapsed?.(collapsed, options); this.actions.setPanelCollapsed?.(collapsed, options);
if (!collapsed) if (!collapsed) requestAnimationFrame(() => this.layer.resizeViewer?.());
requestAnimationFrame(() => this.mapillary.resizeViewer?.());
} }
isViewerExpanded() { isViewerExpanded() {
return ( return (
this._elements.viewerWrap?.classList.contains( this._elements.viewerWrap?.classList.contains(
'mly-viewer-wrap-expanded', 'sl-viewer-wrap-expanded',
) === true ) === true
); );
} }
@@ -187,14 +186,14 @@ export class MapillaryControls {
this._wrapHome = { parent: wrap.parentNode, next: wrap.nextSibling }; this._wrapHome = { parent: wrap.parentNode, next: wrap.nextSibling };
this._expandReturnFocus = document.activeElement; this._expandReturnFocus = document.activeElement;
document.body.appendChild(wrap); document.body.appendChild(wrap);
wrap.classList.add('mly-viewer-wrap-expanded'); wrap.classList.add('sl-viewer-wrap-expanded');
wrap.setAttribute('role', 'dialog'); wrap.setAttribute('role', 'dialog');
wrap.setAttribute('aria-modal', 'true'); wrap.setAttribute('aria-modal', 'true');
wrap.setAttribute('aria-label', 'Street-level image'); wrap.setAttribute('aria-label', 'Street-level image');
wrap.tabIndex = -1; wrap.tabIndex = -1;
wrap.focus({ preventScroll: true }); wrap.focus({ preventScroll: true });
} else { } else {
wrap.classList.remove('mly-viewer-wrap-expanded'); wrap.classList.remove('sl-viewer-wrap-expanded');
wrap.removeAttribute('role'); wrap.removeAttribute('role');
wrap.removeAttribute('aria-modal'); wrap.removeAttribute('aria-modal');
wrap.removeAttribute('aria-label'); wrap.removeAttribute('aria-label');
@@ -210,14 +209,14 @@ export class MapillaryControls {
} }
const button = this._elements.viewerExpand; const button = this._elements.viewerExpand;
if (button) { if (button) {
const icon = button.querySelector('.mly-btn-icon'); const icon = button.querySelector('.sl-btn-icon');
const text = button.querySelector('.mly-btn-text'); const text = button.querySelector('.sl-btn-text');
if (icon) icon.textContent = on ? '⤡' : '⤢'; if (icon) icon.textContent = on ? '⤡' : '⤢';
if (text) text.textContent = on ? 'SHRINK' : 'EXPAND'; if (text) text.textContent = on ? 'SHRINK' : 'EXPAND';
button.setAttribute('aria-pressed', String(on)); button.setAttribute('aria-pressed', String(on));
button.setAttribute('aria-label', on ? 'Shrink' : 'Expand'); button.setAttribute('aria-label', on ? 'Shrink' : 'Expand');
} }
requestAnimationFrame(() => this.mapillary.resizeViewer?.()); requestAnimationFrame(() => this.layer.resizeViewer?.());
} }
_onDialogKey(event) { _onDialogKey(event) {
@@ -251,7 +250,7 @@ export class MapillaryControls {
render(state) { render(state) {
if (this.destroyed || !state || !this.root) return; if (this.destroyed || !state || !this.root) return;
this._state = state; this._state = state;
const view = presentMapillaryPanel(state); const view = presentStreetLevelPanel(state);
this._view = view; this._view = view;
this._renderHeader(view); this._renderHeader(view);
this._renderGate(view); this._renderGate(view);
@@ -264,10 +263,10 @@ export class MapillaryControls {
_renderHeader(view) { _renderHeader(view) {
const el = this._elements; const el = this._elements;
this.root.dataset.mlyEnabled = String(view.enabled); this.root.dataset.slEnabled = String(view.enabled);
if (el.status) { if (el.status) {
el.status.textContent = view.status.text; el.status.textContent = view.status.text;
el.status.className = `mly-status${view.status.tone ? ` is-${view.status.tone}` : ''}`; el.status.className = `sl-status${view.status.tone ? ` is-${view.status.tone}` : ''}`;
} }
if (el.enableBtn) { if (el.enableBtn) {
el.enableBtn.textContent = view.enableButton.text; el.enableBtn.textContent = view.enableButton.text;
@@ -292,8 +291,8 @@ export class MapillaryControls {
_renderFilters(view) { _renderFilters(view) {
const el = this._elements; const el = this._elements;
for (const button of this.root.querySelectorAll('[data-mly-pano]')) { for (const button of this.root.querySelectorAll('[data-sl-pano]')) {
const active = button.dataset.mlyPano === view.filter.pano; const active = button.dataset.slPano === view.filter.pano;
button.classList.toggle('is-active', active); button.classList.toggle('is-active', active);
button.setAttribute('aria-checked', String(active)); button.setAttribute('aria-checked', String(active));
} }
@@ -302,7 +301,7 @@ export class MapillaryControls {
...view.legend.map((entry) => { ...view.legend.map((entry) => {
const item = document.createElement('li'); const item = document.createElement('li');
const swatch = document.createElement('i'); const swatch = document.createElement('i');
swatch.className = 'mly-legend-swatch'; swatch.className = 'sl-legend-swatch';
swatch.style.background = entry.color; swatch.style.background = entry.color;
const label = document.createElement('span'); const label = document.createElement('span');
label.textContent = entry.label; label.textContent = entry.label;
@@ -323,8 +322,8 @@ export class MapillaryControls {
el.followBtn.setAttribute('aria-pressed', String(viewer.follow.pressed)); el.followBtn.setAttribute('aria-pressed', String(viewer.follow.pressed));
el.followBtn.disabled = viewer.follow.disabled; el.followBtn.disabled = viewer.follow.disabled;
} }
for (const button of this.root.querySelectorAll('[data-mly-render]')) { for (const button of this.root.querySelectorAll('[data-sl-render]')) {
const active = button.dataset.mlyRender === viewer.renderMode; const active = button.dataset.slRender === viewer.renderMode;
button.classList.toggle('is-active', active); button.classList.toggle('is-active', active);
button.setAttribute('aria-checked', String(active)); button.setAttribute('aria-checked', String(active));
} }
@@ -335,7 +334,7 @@ export class MapillaryControls {
el.imageLink.hidden = !viewer.link; el.imageLink.hidden = !viewer.link;
if (viewer.link) el.imageLink.href = viewer.link; if (viewer.link) el.imageLink.href = viewer.link;
} }
if (state.street.open) this.mapillary.resizeViewer?.(); if (state.street.open) this.layer.resizeViewer?.();
} }
_renderMeta(view) { _renderMeta(view) {
@@ -361,6 +360,6 @@ export class MapillaryControls {
this._resizeObserver = null; this._resizeObserver = null;
this._unsubscribe?.(); this._unsubscribe?.();
this._unsubscribe = null; this._unsubscribe = null;
this.mapillary.attachViewerHost?.(null); this.layer.attachViewerHost?.(null);
} }
} }
@@ -61,7 +61,7 @@ function presentMeta(state) {
* @param {object} state Snapshot from the layer's `getUIState()`. * @param {object} state Snapshot from the layer's `getUIState()`.
* @returns {object} Everything the panel needs, already worded. * @returns {object} Everything the panel needs, already worded.
*/ */
export function presentMapillaryPanel(state) { export function presentStreetLevelPanel(state) {
const enabled = state.enabled === true; const enabled = state.enabled === true;
const keyRequired = state.keyRequired === true; const keyRequired = state.keyRequired === true;
return { return {
@@ -2,8 +2,8 @@ import assert from 'node:assert/strict';
import test from 'node:test'; import test from 'node:test';
import { import {
mapillaryImageUrl, mapillaryImageUrl,
presentMapillaryPanel, presentStreetLevelPanel,
} from './mapillaryPresentation.js'; } from './streetLevelPresentation.js';
function snapshot(overrides = {}) { function snapshot(overrides = {}) {
const base = { const base = {
@@ -59,45 +59,48 @@ function deepMerge(target, source) {
} }
test('a missing Mapillary token gates every control and flags KEY REQUIRED', () => { test('a missing Mapillary token gates every control and flags KEY REQUIRED', () => {
const view = presentMapillaryPanel(snapshot({ keyRequired: true })); const view = presentStreetLevelPanel(snapshot({ keyRequired: true }));
assert.equal(view.controlsDisabled, true); assert.equal(view.controlsDisabled, true);
assert.deepEqual(view.status, { text: 'KEY REQUIRED', tone: 'warn' }); assert.deepEqual(view.status, { text: 'KEY REQUIRED', tone: 'warn' });
}); });
test('status reads LOADING while coverage streams, then ON or OFF', () => { test('status reads LOADING while coverage streams, then ON or OFF', () => {
assert.deepEqual(presentMapillaryPanel(snapshot()).status, { assert.deepEqual(presentStreetLevelPanel(snapshot()).status, {
text: 'OFF', text: 'OFF',
tone: '', tone: '',
}); });
assert.deepEqual( assert.deepEqual(
presentMapillaryPanel( presentStreetLevelPanel(
snapshot({ enabled: true, coverage: { loading: true } }), snapshot({ enabled: true, coverage: { loading: true } }),
).status, ).status,
{ text: 'LOADING', tone: 'busy' }, { text: 'LOADING', tone: 'busy' },
); );
assert.deepEqual(presentMapillaryPanel(snapshot({ enabled: true })).status, { assert.deepEqual(
text: 'ON', presentStreetLevelPanel(snapshot({ enabled: true })).status,
tone: 'on', {
}); text: 'ON',
tone: 'on',
},
);
}); });
test('errors from the viewer or the coverage web surface in one alert', () => { test('errors from the viewer or the coverage web surface in one alert', () => {
assert.equal(presentMapillaryPanel(snapshot()).error, null); assert.equal(presentStreetLevelPanel(snapshot()).error, null);
assert.equal( assert.equal(
presentMapillaryPanel( presentStreetLevelPanel(
snapshot({ street: { error: 'Image could not be opened' } }), snapshot({ street: { error: 'Image could not be opened' } }),
).error, ).error,
'Image could not be opened', 'Image could not be opened',
); );
assert.equal( assert.equal(
presentMapillaryPanel(snapshot({ coverage: { error: 'Tile HTTP 502' } })) presentStreetLevelPanel(snapshot({ coverage: { error: 'Tile HTTP 502' } }))
.error, .error,
'Tile HTTP 502', 'Tile HTTP 502',
); );
}); });
test('legend passes through in the layer’s order', () => { test('legend passes through in the layer’s order', () => {
const view = presentMapillaryPanel(snapshot()); const view = presentStreetLevelPanel(snapshot());
assert.deepEqual( assert.deepEqual(
view.legend.map((entry) => entry.key), view.legend.map((entry) => entry.key),
['recent', 'older', 'pano', 'selected'], ['recent', 'older', 'pano', 'selected'],
@@ -105,12 +108,12 @@ test('legend passes through in the layer’s order', () => {
}); });
test('the meta line never mixes the visible-sequence count with the selected sequence', () => { test('the meta line never mixes the visible-sequence count with the selected sequence', () => {
assert.equal(presentMapillaryPanel(snapshot()).meta, ''); assert.equal(presentStreetLevelPanel(snapshot()).meta, '');
const browsing = presentMapillaryPanel( const browsing = presentStreetLevelPanel(
snapshot({ enabled: true, coverage: { zoom: 14, sequences: 812 } }), snapshot({ enabled: true, coverage: { zoom: 14, sequences: 812 } }),
); );
assert.match(browsing.meta, /^812 sequences in view · click a line/); assert.match(browsing.meta, /^812 sequences in view · click a line/);
const selected = presentMapillaryPanel( const selected = presentStreetLevelPanel(
snapshot({ snapshot({
enabled: true, enabled: true,
coverage: { zoom: 14, sequences: 812 }, coverage: { zoom: 14, sequences: 812 },
@@ -119,7 +122,7 @@ test('the meta line never mixes the visible-sequence count with the selected seq
); );
assert.equal(selected.meta, '33 images in this sequence · Esc clears'); assert.equal(selected.meta, '33 images in this sequence · Esc clears');
assert.doesNotMatch(selected.meta, /sequences in view/); assert.doesNotMatch(selected.meta, /sequences in view/);
const hinted = presentMapillaryPanel( const hinted = presentStreetLevelPanel(
snapshot({ snapshot({
enabled: true, enabled: true,
coverage: { hint: 'Point the camera at the globe' }, coverage: { hint: 'Point the camera at the globe' },
@@ -129,7 +132,7 @@ test('the meta line never mixes the visible-sequence count with the selected seq
}); });
test('viewer caption reads "Image by" left and date right, with a deep link', () => { test('viewer caption reads "Image by" left and date right, with a deep link', () => {
const view = presentMapillaryPanel( const view = presentStreetLevelPanel(
snapshot({ snapshot({
enabled: true, enabled: true,
street: { street: {
@@ -153,7 +156,7 @@ test('viewer caption reads "Image by" left and date right, with a deep link', ()
}); });
test('an image without a creator name leaves the left caption empty', () => { test('an image without a creator name leaves the left caption empty', () => {
const view = presentMapillaryPanel( const view = presentStreetLevelPanel(
snapshot({ snapshot({
street: { open: true, imageId: '1', capturedAt: Date.UTC(2024, 0, 2) }, street: { open: true, imageId: '1', capturedAt: Date.UTC(2024, 0, 2) },
}), }),
+10 -8
View File
@@ -162,7 +162,7 @@
} }
#right-context-rail > #pp-toggles, #right-context-rail > #pp-toggles,
#right-context-rail > #cctv-panel, #right-context-rail > #cctv-panel,
#right-context-rail > #mapillary-panel, #right-context-rail > #street-level-panel,
#right-context-rail > #weather-panel, #right-context-rail > #weather-panel,
#right-context-rail > #recent-imagery-panel, #right-context-rail > #recent-imagery-panel,
#right-context-rail > #global-context-panel { #right-context-rail > #global-context-panel {
@@ -177,7 +177,7 @@
pointer-events: auto; pointer-events: auto;
} }
#right-context-rail #cctv-panel, #right-context-rail #cctv-panel,
#right-context-rail #mapillary-panel, #right-context-rail #street-level-panel,
#right-context-rail #weather-panel, #right-context-rail #weather-panel,
#right-context-rail #recent-imagery-panel { #right-context-rail #recent-imagery-panel {
width: 100%; width: 100%;
@@ -193,7 +193,7 @@
min-height: 0; min-height: 0;
} }
#right-context-rail #cctv-panel.collapsed, #right-context-rail #cctv-panel.collapsed,
#right-context-rail #mapillary-panel.collapsed, #right-context-rail #street-level-panel.collapsed,
#right-context-rail #weather-panel.collapsed, #right-context-rail #weather-panel.collapsed,
#right-context-rail #recent-imagery-panel.collapsed { #right-context-rail #recent-imagery-panel.collapsed {
width: var(--right-display-collapsed-width); width: var(--right-display-collapsed-width);
@@ -208,7 +208,7 @@
} }
#right-context-rail > #pp-toggles.collapsed .pp-header-row, #right-context-rail > #pp-toggles.collapsed .pp-header-row,
#right-context-rail #cctv-panel.collapsed .cctv-panel-inner, #right-context-rail #cctv-panel.collapsed .cctv-panel-inner,
#right-context-rail #mapillary-panel.collapsed .mapillary-panel-inner, #right-context-rail #street-level-panel.collapsed .street-level-panel-inner,
#right-context-rail #weather-panel.collapsed .weather-panel-inner, #right-context-rail #weather-panel.collapsed .weather-panel-inner,
#right-context-rail #recent-imagery-panel.collapsed .recent-imagery-panel-inner, #right-context-rail #recent-imagery-panel.collapsed .recent-imagery-panel-inner,
#right-context-rail #right-context-rail
@@ -219,7 +219,9 @@
box-sizing: border-box; box-sizing: border-box;
} }
#right-context-rail #cctv-panel:not(.collapsed) .cctv-panel-inner, #right-context-rail #cctv-panel:not(.collapsed) .cctv-panel-inner,
#right-context-rail #mapillary-panel:not(.collapsed) .mapillary-panel-inner { #right-context-rail
#street-level-panel:not(.collapsed)
.street-level-panel-inner {
max-height: 100%; max-height: 100%;
overflow-y: auto; overflow-y: auto;
scrollbar-gutter: stable; scrollbar-gutter: stable;
@@ -267,7 +269,7 @@
} }
#right-context-rail.layout-focus > #pp-toggles:not(.collapsed), #right-context-rail.layout-focus > #pp-toggles:not(.collapsed),
#right-context-rail.layout-focus > #cctv-panel:not(.collapsed), #right-context-rail.layout-focus > #cctv-panel:not(.collapsed),
#right-context-rail.layout-focus > #mapillary-panel:not(.collapsed), #right-context-rail.layout-focus > #street-level-panel:not(.collapsed),
#right-context-rail.layout-focus > #weather-panel:not(.collapsed), #right-context-rail.layout-focus > #weather-panel:not(.collapsed),
#right-context-rail.layout-focus > #recent-imagery-panel:not(.collapsed), #right-context-rail.layout-focus > #recent-imagery-panel:not(.collapsed),
#right-context-rail.layout-focus > #global-context-panel:not(.collapsed) { #right-context-rail.layout-focus > #global-context-panel:not(.collapsed) {
@@ -289,8 +291,8 @@
> #cctv-panel:not(.collapsed) > #cctv-panel:not(.collapsed)
.cctv-panel-inner, .cctv-panel-inner,
#right-context-rail.layout-focus #right-context-rail.layout-focus
> #mapillary-panel:not(.collapsed) > #street-level-panel:not(.collapsed)
.mapillary-panel-inner, .street-level-panel-inner,
#right-context-rail.layout-focus #right-context-rail.layout-focus
> #recent-imagery-panel:not(.collapsed) > #recent-imagery-panel:not(.collapsed)
.recent-imagery-panel-inner, .recent-imagery-panel-inner,
+4 -4
View File
@@ -217,9 +217,9 @@
#right-context-rail #pp-toggles.collapsed .pp-header-row, #right-context-rail #pp-toggles.collapsed .pp-header-row,
#right-context-rail #cctv-panel, #right-context-rail #cctv-panel,
#right-context-rail #mapillary-panel, #right-context-rail #street-level-panel,
#right-context-rail #cctv-panel.collapsed, #right-context-rail #cctv-panel.collapsed,
#right-context-rail #mapillary-panel.collapsed, #right-context-rail #street-level-panel.collapsed,
#right-context-rail #recent-imagery-panel, #right-context-rail #recent-imagery-panel,
#right-context-rail #recent-imagery-panel.collapsed, #right-context-rail #recent-imagery-panel.collapsed,
#right-context-rail #radio-panel, #right-context-rail #radio-panel,
@@ -235,12 +235,12 @@
#left-panel-stack > #data-panel, #left-panel-stack > #data-panel,
#left-panel-stack > #cctv-panel, #left-panel-stack > #cctv-panel,
#left-panel-stack > #mapillary-panel, #left-panel-stack > #street-level-panel,
#left-panel-stack > #global-context-panel, #left-panel-stack > #global-context-panel,
#left-panel-stack > #scene-panel, #left-panel-stack > #scene-panel,
#left-panel-stack > #data-panel.collapsed, #left-panel-stack > #data-panel.collapsed,
#left-panel-stack > #cctv-panel.collapsed, #left-panel-stack > #cctv-panel.collapsed,
#left-panel-stack > #mapillary-panel.collapsed, #left-panel-stack > #street-level-panel.collapsed,
#left-panel-stack > #global-context-panel.collapsed, #left-panel-stack > #global-context-panel.collapsed,
#left-panel-stack > #scene-panel.collapsed { #left-panel-stack > #scene-panel.collapsed {
position: relative; position: relative;
@@ -1,13 +1,13 @@
/* ── Street Level (Mapillary) panel ───────────────────────────────────────── /* ── Street Level panel ─────────────────────────────────────────
Lives in the right context rail next to CCTV; the rail owns position and Lives in the right context rail next to CCTV; the rail owns position and
width (see layers.css). Everything here is panel-internal. */ width (see layers.css). Everything here is panel-internal. */
#mapillary-panel { #street-level-panel {
--mly-green: #05cb63; --sl-green: #05cb63;
--mly-text: 0.62rem; --sl-text: 0.62rem;
--mly-text-small: 0.6rem; --sl-text-small: 0.6rem;
} }
#mapillary-panel .panel-glow { #street-level-panel .panel-glow {
position: absolute; position: absolute;
inset: -18px; inset: -18px;
background: radial-gradient( background: radial-gradient(
@@ -20,7 +20,7 @@
pointer-events: none; pointer-events: none;
} }
.mapillary-panel-inner { .street-level-panel-inner {
position: relative; position: relative;
background: var(--glass-bg); background: var(--glass-bg);
border: 1px solid var(--glass-border); border: 1px solid var(--glass-border);
@@ -39,11 +39,11 @@
scrollbar-color: rgba(0, 212, 255, 0.46) rgba(255, 255, 255, 0.06); scrollbar-color: rgba(0, 212, 255, 0.46) rgba(255, 255, 255, 0.06);
} }
#mapillary-panel .panel-header { #street-level-panel .panel-header {
gap: 8px; gap: 8px;
} }
.mly-mark { .sl-mark {
width: 14px; width: 14px;
height: 14px; height: 14px;
flex: 0 0 auto; flex: 0 0 auto;
@@ -51,14 +51,14 @@
transition: color var(--transition-fast); transition: color var(--transition-fast);
} }
#mapillary-panel[data-mly-enabled='true'] .mly-mark { #street-level-panel[data-sl-enabled='true'] .sl-mark {
color: var(--mly-green); color: var(--sl-green);
filter: drop-shadow(0 0 6px rgba(5, 203, 99, 0.7)); filter: drop-shadow(0 0 6px rgba(5, 203, 99, 0.7));
} }
.mly-status { .sl-status {
font-family: var(--font-mono); font-family: var(--font-mono);
font-size: var(--mly-text-small); font-size: var(--sl-text-small);
letter-spacing: 0.1em; letter-spacing: 0.1em;
padding: 2px 7px; padding: 2px 7px;
border-radius: 999px; border-radius: 999px;
@@ -67,35 +67,35 @@
white-space: nowrap; white-space: nowrap;
} }
.mly-status.is-on { .sl-status.is-on {
color: var(--mly-green); color: var(--sl-green);
border-color: rgba(5, 203, 99, 0.55); border-color: rgba(5, 203, 99, 0.55);
} }
.mly-status.is-busy { .sl-status.is-busy {
color: var(--accent); color: var(--accent);
border-color: rgba(0, 212, 255, 0.55); border-color: rgba(0, 212, 255, 0.55);
} }
.mly-status.is-warn { .sl-status.is-warn {
color: #ffb300; color: #ffb300;
border-color: rgba(255, 179, 0, 0.55); border-color: rgba(255, 179, 0, 0.55);
} }
/* Collapsed strips hide the body entirely: nothing to paint or tab into. */ /* Collapsed strips hide the body entirely: nothing to paint or tab into. */
#mapillary-panel.collapsed .mly-body, #street-level-panel.collapsed .sl-body,
#mapillary-panel.collapsed .mly-status { #street-level-panel.collapsed .sl-status {
display: none; display: none;
} }
.mly-body { .sl-body {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 8px; gap: 8px;
min-height: 0; min-height: 0;
} }
.mly-controls { .sl-controls {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 8px; gap: 8px;
@@ -105,13 +105,13 @@
border: 0; border: 0;
} }
.mly-controls:disabled { .sl-controls:disabled {
opacity: 0.45; opacity: 0.45;
pointer-events: none; pointer-events: none;
} }
/* ── Errors ──────────────────────────────────────────────────────────────── */ /* ── Errors ──────────────────────────────────────────────────────────────── */
.mly-error { .sl-error {
font-family: var(--font-sans); font-family: var(--font-sans);
font-size: 0.72rem; font-size: 0.72rem;
line-height: 1.4; line-height: 1.4;
@@ -119,23 +119,23 @@
} }
/* ── Filters and legend ──────────────────────────────────────────────────── */ /* ── Filters and legend ──────────────────────────────────────────────────── */
.mly-filters { .sl-filters {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: 8px;
flex-wrap: wrap; flex-wrap: wrap;
} }
.mly-seg { .sl-seg {
display: inline-flex; display: inline-flex;
border: 1px solid var(--glass-border-hover); border: 1px solid var(--glass-border-hover);
border-radius: 999px; border-radius: 999px;
overflow: hidden; overflow: hidden;
} }
.mly-seg-btn { .sl-seg-btn {
font-family: var(--font-mono); font-family: var(--font-mono);
font-size: var(--mly-text-small); font-size: var(--sl-text-small);
letter-spacing: 0.08em; letter-spacing: 0.08em;
padding: 4px 9px; padding: 4px 9px;
border: 0; border: 0;
@@ -144,18 +144,18 @@
cursor: pointer; cursor: pointer;
} }
.mly-seg-btn + .mly-seg-btn { .sl-seg-btn + .sl-seg-btn {
border-left: 1px solid var(--glass-border); border-left: 1px solid var(--glass-border);
} }
.mly-seg-btn.is-active { .sl-seg-btn.is-active {
background: rgba(5, 203, 99, 0.22); background: rgba(5, 203, 99, 0.22);
color: var(--text-primary); color: var(--text-primary);
} }
.mly-select { .sl-select {
font-family: var(--font-mono); font-family: var(--font-mono);
font-size: var(--mly-text-small); font-size: var(--sl-text-small);
letter-spacing: 0.06em; letter-spacing: 0.06em;
padding: 4px 8px; padding: 4px 8px;
border-radius: 999px; border-radius: 999px;
@@ -164,11 +164,11 @@
color: var(--text-primary); color: var(--text-primary);
} }
.mly-select option { .sl-select option {
background: #0a0a0f; background: #0a0a0f;
} }
.mly-legend { .sl-legend {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 4px 12px; gap: 4px 12px;
@@ -177,17 +177,17 @@
list-style: none; list-style: none;
} }
.mly-legend li { .sl-legend li {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
gap: 6px; gap: 6px;
font-family: var(--font-mono); font-family: var(--font-mono);
font-size: var(--mly-text-small); font-size: var(--sl-text-small);
letter-spacing: 0.06em; letter-spacing: 0.06em;
color: var(--text-secondary); color: var(--text-secondary);
} }
.mly-legend-swatch { .sl-legend-swatch {
width: 14px; width: 14px;
height: 3px; height: 3px;
border-radius: 2px; border-radius: 2px;
@@ -195,7 +195,7 @@
} }
/* ── Embedded viewer ─────────────────────────────────────────────────────── */ /* ── Embedded viewer ─────────────────────────────────────────────────────── */
.mly-viewer-wrap { .sl-viewer-wrap {
position: relative; position: relative;
container-type: inline-size; container-type: inline-size;
display: flex; display: flex;
@@ -207,17 +207,17 @@
background: #000; background: #000;
} }
.mly-viewer-wrap[hidden] { .sl-viewer-wrap[hidden] {
display: none; display: none;
} }
.mly-viewer { .sl-viewer {
width: 100%; width: 100%;
aspect-ratio: 16 / 10; aspect-ratio: 16 / 10;
min-height: 180px; min-height: 180px;
} }
.mly-viewer-placeholder { .sl-viewer-placeholder {
position: absolute; position: absolute;
inset: 0 0 26px; inset: 0 0 26px;
display: grid; display: grid;
@@ -226,27 +226,27 @@
background: rgba(0, 0, 0, 0.35); background: rgba(0, 0, 0, 0.35);
} }
.mly-viewer-placeholder[hidden] { .sl-viewer-placeholder[hidden] {
display: none; display: none;
} }
.mly-viewer-placeholder::after { .sl-viewer-placeholder::after {
content: ''; content: '';
width: 28px; width: 28px;
height: 28px; height: 28px;
border-radius: 50%; border-radius: 50%;
border: 2px solid rgba(255, 255, 255, 0.2); border: 2px solid rgba(255, 255, 255, 0.2);
border-top-color: var(--mly-green); border-top-color: var(--sl-green);
animation: mly-spin 0.9s linear infinite; animation: sl-spin 0.9s linear infinite;
} }
@keyframes mly-spin { @keyframes sl-spin {
to { to {
transform: rotate(360deg); transform: rotate(360deg);
} }
} }
.mly-viewer-tools { .sl-viewer-tools {
position: absolute; position: absolute;
top: 6px; top: 6px;
left: 6px; left: 6px;
@@ -259,18 +259,18 @@
pointer-events: none; pointer-events: none;
} }
.mly-viewer-tools > * { .sl-viewer-tools > * {
pointer-events: auto; pointer-events: auto;
} }
.mly-viewer-tools.mly-viewer-tools-right { .sl-viewer-tools.sl-viewer-tools-right {
left: auto; left: auto;
right: 6px; right: 6px;
} }
.mly-viewer-tools .scene-btn { .sl-viewer-tools .scene-btn {
padding: 4px 8px; padding: 4px 8px;
font-size: var(--mly-text-small); font-size: var(--sl-text-small);
background: rgba(10, 10, 15, 0.75); background: rgba(10, 10, 15, 0.75);
flex: none; flex: none;
min-width: 0; min-width: 0;
@@ -279,17 +279,17 @@
gap: 4px; gap: 4px;
} }
.mly-viewer-tools .mly-seg { .sl-viewer-tools .sl-seg {
background: rgba(10, 10, 15, 0.75); background: rgba(10, 10, 15, 0.75);
} }
@container (max-width: 380px) { @container (max-width: 380px) {
.mly-viewer-tools .mly-btn-text { .sl-viewer-tools .sl-btn-text {
display: none; display: none;
} }
} }
.mly-viewer-x { .sl-viewer-x {
width: 30px; width: 30px;
height: 30px; height: 30px;
border-radius: 50%; border-radius: 50%;
@@ -305,17 +305,17 @@
transition: all var(--transition-fast); transition: all var(--transition-fast);
} }
.mly-viewer-x:hover { .sl-viewer-x:hover {
color: #0a0a0f; color: #0a0a0f;
background: #fff; background: #fff;
border-color: #fff; border-color: #fff;
} }
.mly-image-meta { .sl-image-meta {
flex: 0 0 auto; flex: 0 0 auto;
padding: 6px 10px; padding: 6px 10px;
font-family: var(--font-mono); font-family: var(--font-mono);
font-size: var(--mly-text-small); font-size: var(--sl-text-small);
letter-spacing: 0.06em; letter-spacing: 0.06em;
color: var(--text-secondary); color: var(--text-secondary);
background: rgba(0, 0, 0, 0.7); background: rgba(0, 0, 0, 0.7);
@@ -326,7 +326,7 @@
min-height: 26px; min-height: 26px;
} }
.mly-image-by { .sl-image-by {
color: var(--text-primary); color: var(--text-primary);
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
@@ -334,35 +334,35 @@
min-width: 0; min-width: 0;
} }
.mly-image-right { .sl-image-right {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
gap: 10px; gap: 10px;
white-space: nowrap; white-space: nowrap;
} }
.mly-image-link { .sl-image-link {
color: var(--mly-green); color: var(--sl-green);
text-decoration: none; text-decoration: none;
letter-spacing: 0.1em; letter-spacing: 0.1em;
} }
.mly-image-link[hidden] { .sl-image-link[hidden] {
display: none; display: none;
} }
.mly-image-link:hover { .sl-image-link:hover {
text-decoration: underline; text-decoration: underline;
} }
.mly-meta { .sl-meta {
font-family: var(--font-mono); font-family: var(--font-mono);
font-size: var(--mly-text-small); font-size: var(--sl-text-small);
letter-spacing: 0.06em; letter-spacing: 0.06em;
color: var(--text-secondary); color: var(--text-secondary);
} }
.mly-meta:empty { .sl-meta:empty {
display: none; display: none;
} }
@@ -370,7 +370,7 @@
/* The panel inner's backdrop-filter makes it the containing block for fixed /* The panel inner's backdrop-filter makes it the containing block for fixed
descendants, so the expanded viewer is re-parented to <body>. z-index sits descendants, so the expanded viewer is re-parented to <body>. z-index sits
above panels (≤139) and below the voice pill (150) and toasts (200). */ above panels (≤139) and below the voice pill (150) and toasts (200). */
.mly-viewer-wrap-expanded { .sl-viewer-wrap-expanded {
position: fixed; position: fixed;
left: 4vw; left: 4vw;
right: 4vw; right: 4vw;
@@ -385,28 +385,28 @@
outline: none; outline: none;
} }
.mly-viewer-wrap-expanded .mly-viewer { .sl-viewer-wrap-expanded .sl-viewer {
flex: 1 1 auto; flex: 1 1 auto;
min-height: 0; min-height: 0;
aspect-ratio: auto; aspect-ratio: auto;
} }
.mly-viewer-wrap-expanded .mly-image-meta { .sl-viewer-wrap-expanded .sl-image-meta {
font-size: 0.66rem; font-size: 0.66rem;
padding: 10px 14px; padding: 10px 14px;
} }
.mly-viewer-wrap-expanded .mly-viewer-tools { .sl-viewer-wrap-expanded .sl-viewer-tools {
top: 10px; top: 10px;
left: 10px; left: 10px;
} }
.mly-viewer-wrap-expanded .mly-viewer-tools.mly-viewer-tools-right { .sl-viewer-wrap-expanded .sl-viewer-tools.sl-viewer-tools-right {
left: auto; left: auto;
right: 10px; right: 10px;
} }
.mly-viewer-wrap-expanded .mly-viewer-tools .scene-btn { .sl-viewer-wrap-expanded .sl-viewer-tools .scene-btn {
padding: 8px 14px; padding: 8px 14px;
font-size: 0.7rem; font-size: 0.7rem;
letter-spacing: 0.12em; letter-spacing: 0.12em;
@@ -415,12 +415,12 @@
background: rgba(10, 10, 15, 0.82); background: rgba(10, 10, 15, 0.82);
} }
.mly-viewer-wrap-expanded #mly-viewer-expand::after { .sl-viewer-wrap-expanded #sl-viewer-expand::after {
content: ' · ESC'; content: ' · ESC';
color: var(--text-secondary); color: var(--text-secondary);
} }
.mly-viewer-wrap-expanded .mly-viewer-x { .sl-viewer-wrap-expanded .sl-viewer-x {
width: 40px; width: 40px;
height: 40px; height: 40px;
font-size: 1.5rem; font-size: 1.5rem;
@@ -428,12 +428,12 @@
/* ── Short viewports ─────────────────────────────────────────────────────── */ /* ── Short viewports ─────────────────────────────────────────────────────── */
@media (max-height: 640px) { @media (max-height: 640px) {
.mapillary-panel-inner { .street-level-panel-inner {
padding: 10px; padding: 10px;
gap: 6px; gap: 6px;
} }
.mly-viewer { .sl-viewer {
min-height: 150px; min-height: 150px;
} }
} }
+35 -35
View File
@@ -73,35 +73,35 @@
<!-- Street Level (Mapillary) Panel — moved into #right-context-rail at <!-- Street Level (Mapillary) Panel — moved into #right-context-rail at
runtime next to CCTV (see PanelLayoutController). --> runtime next to CCTV (see PanelLayoutController). -->
<div id="mapillary-panel" class="panel-collapsible collapsed" data-panel-id="mapillary-panel"> <div id="street-level-panel" class="panel-collapsible collapsed" data-panel-id="street-level-panel">
<div class="panel-glow"></div> <div class="panel-glow"></div>
<div class="mapillary-panel-inner"> <div class="street-level-panel-inner">
<div class="panel-header"> <div class="panel-header">
<!-- Mapillary mark: path from the MapillaryJS attribution component <!-- Mapillary mark: path from the MapillaryJS attribution component
(mapillary-js, MIT, © Mapillary). Mapillary is a trademark of (mapillary-js, MIT, © Mapillary). Mapillary is a trademark of
Meta Platforms, Inc.; it is used here to identify the data source. --> Meta Platforms, Inc.; it is used here to identify the data source. -->
<svg class="mly-mark" viewBox="0 0 13.5 13.8" aria-hidden="true" focusable="false"><path fill="currentColor" d="M6.38154274 9.6505067C6.14007412 9.52786404 6.08124006 9.31709453 6.21044037 9.08865565 6.27614941 8.97247561 6.41859234 8.74074422 6.53468168 8.5539822 6.69432724 8.29714786 6.92701434 8.29637588 7.05835866 8.37253362 7.18970298 8.44869135 9.04853604 9.42087063 9.16874871 9.48670656 9.4525926 9.64215711 9.87118629 9.28623741 9.68342251 8.97441326 9.59375332 8.82549731 8.8103938 7.4044783 8.5614978 6.93610438 8.44825156 6.72299696 8.47921005 6.53132251 8.731647 6.40377463 8.85656436 6.3406581 9.08004239 6.20890798 9.27394762 6.09190586 9.47189219 5.9724664 9.7353488 6.06223885 9.84245555 6.24022621 9.94956229 6.41821357 13.1209182 12.2294019 13.2221517 12.4098169 13.4231294 12.7679928 13.0322853 13.1511137 12.662949 12.953094 12.4781436 12.8540105 6.62301136 9.77314936 6.38154274 9.6505067zM.200146062 6.40081205C-.111554282 6.23274106-.0545442328 5.79604052.338782648 5.68466036.672030083 5.59029317 3.37820647 4.6882634 4.19715115 4.4149007 4.34479443 4.36561757 4.46005809 4.25228232 4.50965757 4.1075867 4.78858853 3.29386671 5.72024556.575961334 5.82468911.27126979 5.9477048-.087601979 6.39304077-.0889184867 6.57454671.258502251 6.63901566.381902346 9.08940484 4.79813543 9.17465946 4.98547051 9.25991408 5.17280559 9.20498731 5.38031434 9.01176406 5.50000515 8.8185408 5.61969596 8.50343522 5.79872065 8.3936507 5.86654323 8.19489463 5.98933057 8.00439135 5.90384324 7.91365501 5.71364816 7.82291866 5.52345307 7.12387828 4.29626886 6.83882952 3.77527795 6.69634941 3.51486341 6.2671132 3.41492796 6.12283721 3.83582159 6.01848488 4.14024669 5.77526264 4.84979439 5.64864557 5.219172 5.60020375 5.3604904 5.47741944 5.48212554 5.33398124 5.52937001 4.9780163 5.64661478 4.30162944 5.86939744 3.90672375 5.99946818 3.62491469 6.0922881 3.48793523 6.52150257 3.86841776 6.70224674 3.94789525 6.74000167 5.66097153 7.65404391 5.83772054 7.73764558 6.01446954 7.82124724 6.11275281 8.06257945 6.00925316 8.23355004 5.8680804 8.46675268 5.68734796 8.76272052 5.6217862 8.85880815 5.50815838 9.02534163 5.28495077 9.08080286 5.09748996 8.98251076 4.91002915 8.88421866.311522289 6.46086689.200146062 6.40081205z"/></svg> <svg class="sl-mark" viewBox="0 0 13.5 13.8" aria-hidden="true" focusable="false"><path fill="currentColor" d="M6.38154274 9.6505067C6.14007412 9.52786404 6.08124006 9.31709453 6.21044037 9.08865565 6.27614941 8.97247561 6.41859234 8.74074422 6.53468168 8.5539822 6.69432724 8.29714786 6.92701434 8.29637588 7.05835866 8.37253362 7.18970298 8.44869135 9.04853604 9.42087063 9.16874871 9.48670656 9.4525926 9.64215711 9.87118629 9.28623741 9.68342251 8.97441326 9.59375332 8.82549731 8.8103938 7.4044783 8.5614978 6.93610438 8.44825156 6.72299696 8.47921005 6.53132251 8.731647 6.40377463 8.85656436 6.3406581 9.08004239 6.20890798 9.27394762 6.09190586 9.47189219 5.9724664 9.7353488 6.06223885 9.84245555 6.24022621 9.94956229 6.41821357 13.1209182 12.2294019 13.2221517 12.4098169 13.4231294 12.7679928 13.0322853 13.1511137 12.662949 12.953094 12.4781436 12.8540105 6.62301136 9.77314936 6.38154274 9.6505067zM.200146062 6.40081205C-.111554282 6.23274106-.0545442328 5.79604052.338782648 5.68466036.672030083 5.59029317 3.37820647 4.6882634 4.19715115 4.4149007 4.34479443 4.36561757 4.46005809 4.25228232 4.50965757 4.1075867 4.78858853 3.29386671 5.72024556.575961334 5.82468911.27126979 5.9477048-.087601979 6.39304077-.0889184867 6.57454671.258502251 6.63901566.381902346 9.08940484 4.79813543 9.17465946 4.98547051 9.25991408 5.17280559 9.20498731 5.38031434 9.01176406 5.50000515 8.8185408 5.61969596 8.50343522 5.79872065 8.3936507 5.86654323 8.19489463 5.98933057 8.00439135 5.90384324 7.91365501 5.71364816 7.82291866 5.52345307 7.12387828 4.29626886 6.83882952 3.77527795 6.69634941 3.51486341 6.2671132 3.41492796 6.12283721 3.83582159 6.01848488 4.14024669 5.77526264 4.84979439 5.64864557 5.219172 5.60020375 5.3604904 5.47741944 5.48212554 5.33398124 5.52937001 4.9780163 5.64661478 4.30162944 5.86939744 3.90672375 5.99946818 3.62491469 6.0922881 3.48793523 6.52150257 3.86841776 6.70224674 3.94789525 6.74000167 5.66097153 7.65404391 5.83772054 7.73764558 6.01446954 7.82124724 6.11275281 8.06257945 6.00925316 8.23355004 5.8680804 8.46675268 5.68734796 8.76272052 5.6217862 8.85880815 5.50815838 9.02534163 5.28495077 9.08080286 5.09748996 8.98251076 4.91002915 8.88421866.311522289 6.46086689.200146062 6.40081205z"/></svg>
<span class="panel-title">STREET LEVEL</span> <span class="panel-title">STREET LEVEL</span>
<span class="panel-divider"></span> <span class="panel-divider"></span>
<span id="mly-status" class="mly-status" role="status" aria-live="polite">OFF</span> <span id="sl-status" class="sl-status" role="status" aria-live="polite">OFF</span>
<button class="panel-collapse-btn" data-collapse-target="mapillary-panel" title="Expand panel">◀</button> <button class="panel-collapse-btn" data-collapse-target="street-level-panel" title="Expand panel">◀</button>
</div> </div>
<div id="mly-body" class="mly-body"> <div id="sl-body" class="sl-body">
<fieldset id="mly-controls" class="mly-controls"> <fieldset id="sl-controls" class="sl-controls">
<div class="cctv-controls"> <div class="cctv-controls">
<button id="mly-enable-btn" class="scene-btn" type="button" aria-pressed="false" title="Turn the Street Level layer on or off">STREET LEVEL OFF</button> <button id="sl-enable-btn" class="scene-btn" type="button" aria-pressed="false" title="Turn the Street Level layer on or off">STREET LEVEL OFF</button>
<button id="mly-look-btn" class="scene-btn" type="button" title="Open the nearest Mapillary photo to the view centre">OPEN NEAREST PHOTO</button> <button id="sl-look-btn" class="scene-btn" type="button" title="Open the nearest Mapillary photo to the view centre">OPEN NEAREST PHOTO</button>
</div> </div>
<div id="mly-error" class="mly-error" role="alert" hidden><span aria-hidden="true">⚠</span> <span id="mly-error-text"></span></div> <div id="sl-error" class="sl-error" role="alert" hidden><span aria-hidden="true">⚠</span> <span id="sl-error-text"></span></div>
<div class="mly-filters" role="group" aria-label="Imagery filters"> <div class="sl-filters" role="group" aria-label="Imagery filters">
<span class="cctv-summary-label">IMAGERY</span> <span class="cctv-summary-label">IMAGERY</span>
<div class="mly-seg" role="radiogroup" aria-label="Panorama filter"> <div class="sl-seg" role="radiogroup" aria-label="Panorama filter">
<button type="button" class="mly-seg-btn is-active" data-mly-pano="all" role="radio" aria-checked="true">ALL</button> <button type="button" class="sl-seg-btn is-active" data-sl-pano="all" role="radio" aria-checked="true">ALL</button>
<button type="button" class="mly-seg-btn" data-mly-pano="pano" role="radio" aria-checked="false">360°</button> <button type="button" class="sl-seg-btn" data-sl-pano="pano" role="radio" aria-checked="false">360°</button>
<button type="button" class="mly-seg-btn" data-mly-pano="flat" role="radio" aria-checked="false">FLAT</button> <button type="button" class="sl-seg-btn" data-sl-pano="flat" role="radio" aria-checked="false">FLAT</button>
</div> </div>
<label class="cctv-summary-label" for="mly-since">SINCE</label> <label class="cctv-summary-label" for="sl-since">SINCE</label>
<select id="mly-since" class="mly-select" aria-label="Show imagery captured since"> <select id="sl-since" class="sl-select" aria-label="Show imagery captured since">
<option value="0">any date</option> <option value="0">any date</option>
<option value="365">last year</option> <option value="365">last year</option>
<option value="730">2 years</option> <option value="730">2 years</option>
@@ -111,32 +111,32 @@
<option value="year:2020">2020</option> <option value="year:2020">2020</option>
</select> </select>
</div> </div>
<ul id="mly-legend" class="mly-legend" aria-label="Coverage colours"></ul> <ul id="sl-legend" class="sl-legend" aria-label="Coverage colours"></ul>
<div class="cctv-controls"> <div class="cctv-controls">
<button id="mly-follow-btn" class="scene-btn" type="button" aria-pressed="false" title="Move the globe camera wherever the street-level view looks">CAMERA FOLLOWS VIEW</button> <button id="sl-follow-btn" class="scene-btn" type="button" aria-pressed="false" title="Move the globe camera wherever the street-level view looks">CAMERA FOLLOWS VIEW</button>
</div> </div>
<div id="mly-viewer-wrap" class="mly-viewer-wrap" hidden> <div id="sl-viewer-wrap" class="sl-viewer-wrap" hidden>
<div class="mly-viewer-tools"> <div class="sl-viewer-tools">
<button id="mly-viewer-expand" class="scene-btn" type="button" aria-pressed="false" title="Expand the street-level view (Esc shrinks it)"><span class="mly-btn-icon" aria-hidden="true">⤢</span><span class="mly-btn-text">EXPAND</span></button> <button id="sl-viewer-expand" class="scene-btn" type="button" aria-pressed="false" title="Expand the street-level view (Esc shrinks it)"><span class="sl-btn-icon" aria-hidden="true">⤢</span><span class="sl-btn-text">EXPAND</span></button>
<div class="mly-seg" role="radiogroup" aria-label="Image fit"> <div class="sl-seg" role="radiogroup" aria-label="Image fit">
<button type="button" class="mly-seg-btn is-active" data-mly-render="letterbox" role="radio" aria-checked="true" title="Show the whole image">FIT</button> <button type="button" class="sl-seg-btn is-active" data-sl-render="letterbox" role="radio" aria-checked="true" title="Show the whole image">FIT</button>
<button type="button" class="mly-seg-btn" data-mly-render="fill" role="radio" aria-checked="false" title="Fill the frame (cropped)">FILL</button> <button type="button" class="sl-seg-btn" data-sl-render="fill" role="radio" aria-checked="false" title="Fill the frame (cropped)">FILL</button>
</div> </div>
</div> </div>
<div class="mly-viewer-tools mly-viewer-tools-right"> <div class="sl-viewer-tools sl-viewer-tools-right">
<button id="mly-viewer-close" class="mly-viewer-x" type="button" title="Close the image and deselect it on the map" aria-label="Close the street-level image">×</button> <button id="sl-viewer-close" class="sl-viewer-x" type="button" title="Close the image and deselect it on the map" aria-label="Close the street-level image">×</button>
</div> </div>
<div id="mly-viewer-placeholder" class="mly-viewer-placeholder" hidden></div> <div id="sl-viewer-placeholder" class="sl-viewer-placeholder" hidden></div>
<div id="mly-viewer" class="mly-viewer" aria-label="Mapillary street-level viewer"></div> <div id="sl-viewer" class="sl-viewer" aria-label="Mapillary street-level viewer"></div>
<div id="mly-image-meta" class="mly-image-meta"> <div id="sl-image-meta" class="sl-image-meta">
<span id="mly-image-by" class="mly-image-by"></span> <span id="sl-image-by" class="sl-image-by"></span>
<span class="mly-image-right"> <span class="sl-image-right">
<span id="mly-image-when" class="mly-image-when"></span> <span id="sl-image-when" class="sl-image-when"></span>
<a id="mly-image-link" class="mly-image-link" href="https://www.mapillary.com/app/" target="_blank" rel="noopener noreferrer" title="Open this image on mapillary.com">MAPILLARY ↗</a> <a id="sl-image-link" class="sl-image-link" href="https://www.mapillary.com/app/" target="_blank" rel="noopener noreferrer" title="Open this image on mapillary.com">MAPILLARY ↗</a>
</span> </span>
</div> </div>
</div> </div>
<div id="mly-coverage-meta" class="mly-meta"></div> <div id="sl-coverage-meta" class="sl-meta"></div>
</fieldset> </fieldset>
</div> </div>
</div> </div>
+1 -1
View File
@@ -12,7 +12,7 @@
loaded lazily on first image open while its controls need styling as soon loaded lazily on first image open while its controls need styling as soon
as that first image lands; ~9 KB gzipped. */ as that first image lands; ~9 KB gzipped. */
@import './src/ui/styles/mapillary-js.css'; @import './src/ui/styles/mapillary-js.css';
@import './src/ui/styles/mapillary.css'; @import './src/ui/styles/street-level.css';
@import './src/ui/styles/scenes.css'; @import './src/ui/styles/scenes.css';
@import './src/ui/styles/bhote-koshi.css'; @import './src/ui/styles/bhote-koshi.css';
@import './src/ui/styles/recent-imagery.css'; @import './src/ui/styles/recent-imagery.css';