mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 10:24:02 +00:00
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:
1 parent
547b706f67
commit
2dbc684fb1
38 files changed
+401
-381
No files matched your search
+3
-3
@@ -42,7 +42,7 @@
|
||||
"opensky:import": "./scripts/opensky-import-client.sh",
|
||||
"preview": "vite preview",
|
||||
"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",
|
||||
"test": "node scripts/run-unit-tests.mjs",
|
||||
"test:track": "node scripts/track-regression.mjs"
|
||||
@@ -254,7 +254,7 @@
|
||||
"./server/providers/mapillary": {
|
||||
"node": "./server/providers/mapillary.js"
|
||||
},
|
||||
"./layers/mapillary": "./src/layers/mapillary/index.js",
|
||||
"./layers/mapillary/source": "./src/layers/mapillary/source.js"
|
||||
"./layers/street-level": "./src/layers/streetLevel/index.js",
|
||||
"./layers/street-level/mapillary/source": "./src/layers/streetLevel/providers/mapillary/source.js"
|
||||
}
|
||||
}
|
||||
+20
-20
@@ -840,30 +840,30 @@
|
||||
"src/ui/recentImagery.test.mjs",
|
||||
"src/layers/recentImagery/testDoubles.mjs",
|
||||
"scripts/qa-recent-imagery.mjs",
|
||||
"src/ui/mapillaryControls.js",
|
||||
"src/ui/mapillaryPresentation.js",
|
||||
"src/ui/mapillaryPresentation.test.mjs",
|
||||
"src/app/layers/mapillary.js",
|
||||
"src/ui/streetLevelControls.js",
|
||||
"src/ui/streetLevelPresentation.js",
|
||||
"src/ui/streetLevelPresentation.test.mjs",
|
||||
"src/app/layers/streetLevel.js",
|
||||
"src/tooling/mapillaryProvider.test.mjs",
|
||||
"src/layers/mapillary/source.test.mjs",
|
||||
"src/mapillaryMarkup.test.mjs",
|
||||
"src/layers/mapillary/index.js",
|
||||
"src/layers/mapillary/state.js",
|
||||
"src/layers/mapillary/policy.js",
|
||||
"src/layers/mapillary/coverage.js",
|
||||
"src/layers/mapillary/sequences.js",
|
||||
"src/layers/mapillary/glyphs.js",
|
||||
"src/layers/mapillary/decode.js",
|
||||
"src/layers/mapillary/tileMath.js",
|
||||
"src/layers/mapillary/selection.js",
|
||||
"src/layers/mapillary/viewerBridge.js",
|
||||
"src/layers/mapillary/source.js",
|
||||
"src/layers/streetLevel/providers/mapillary/source.test.mjs",
|
||||
"src/streetLevelMarkup.test.mjs",
|
||||
"src/layers/streetLevel/index.js",
|
||||
"src/layers/streetLevel/state.js",
|
||||
"src/layers/streetLevel/providers/mapillary/policy.js",
|
||||
"src/layers/streetLevel/providers/mapillary/coverage.js",
|
||||
"src/layers/streetLevel/providers/mapillary/sequences.js",
|
||||
"src/layers/streetLevel/glyphs.js",
|
||||
"src/layers/streetLevel/providers/mapillary/decode.js",
|
||||
"src/layers/streetLevel/tileMath.js",
|
||||
"src/layers/streetLevel/selection.js",
|
||||
"src/layers/streetLevel/providers/mapillary/viewer.js",
|
||||
"src/layers/streetLevel/providers/mapillary/source.js",
|
||||
"server/providers/mapillary.js",
|
||||
"server/providers/mapillary/routes.js",
|
||||
"server/providers/mapillary/constants.js",
|
||||
"server/providers/mapillary/tiles.js",
|
||||
"server/providers/mapillary/trim.js",
|
||||
"src/layers/mapillary/tileMath.test.mjs",
|
||||
"scripts/qa-mapillary.mjs",
|
||||
"src/tooling/mapillaryQa.test.mjs"
|
||||
"src/layers/streetLevel/tileMath.test.mjs",
|
||||
"scripts/qa-street-level.mjs",
|
||||
"src/tooling/streetLevelQa.test.mjs"
|
||||
]
|
||||
@@ -182,7 +182,7 @@
|
||||
"server/providers/mapillary/constants.js",
|
||||
"server/providers/mapillary/tiles.js",
|
||||
"server/providers/mapillary/trim.js",
|
||||
"src/layers/mapillary/tileMath.js",
|
||||
"src/layers/streetLevel/tileMath.js",
|
||||
"server/providers/common/request.js",
|
||||
"src/sources/rateLimit.js",
|
||||
"server/providers/common/rate-limit.js"
|
||||
@@ -628,8 +628,8 @@
|
||||
"src/ui/layerBindings.js",
|
||||
"src/ui/displayBindings.js",
|
||||
"src/ui/shellFacade.js",
|
||||
"src/ui/mapillaryControls.js",
|
||||
"src/ui/mapillaryPresentation.js",
|
||||
"src/ui/streetLevelControls.js",
|
||||
"src/ui/streetLevelPresentation.js",
|
||||
"src/ui/localSdrControls.js",
|
||||
"src/layers/localAdsb/feedNames.js",
|
||||
"src/ui/localSdrPresentation.js"
|
||||
@@ -1114,21 +1114,24 @@
|
||||
],
|
||||
"external": ["hls.js", "cesium"]
|
||||
},
|
||||
"mapillary-layer": {
|
||||
"exports": ["./layers/mapillary", "./layers/mapillary/source"],
|
||||
"street-level-layer": {
|
||||
"exports": [
|
||||
"./layers/street-level",
|
||||
"./layers/street-level/mapillary/source"
|
||||
],
|
||||
"modules": [
|
||||
"src/layers/mapillary/index.js",
|
||||
"src/layers/mapillary/state.js",
|
||||
"src/layers/mapillary/policy.js",
|
||||
"src/layers/mapillary/coverage.js",
|
||||
"src/layers/mapillary/sequences.js",
|
||||
"src/layers/mapillary/glyphs.js",
|
||||
"src/layers/mapillary/decode.js",
|
||||
"src/layers/mapillary/tileMath.js",
|
||||
"src/layers/mapillary/selection.js",
|
||||
"src/layers/mapillary/viewerBridge.js",
|
||||
"src/layers/mapillary/source.js",
|
||||
"scripts/qa-mapillary.mjs"
|
||||
"src/layers/streetLevel/index.js",
|
||||
"src/layers/streetLevel/state.js",
|
||||
"src/layers/streetLevel/providers/mapillary/policy.js",
|
||||
"src/layers/streetLevel/providers/mapillary/coverage.js",
|
||||
"src/layers/streetLevel/providers/mapillary/sequences.js",
|
||||
"src/layers/streetLevel/glyphs.js",
|
||||
"src/layers/streetLevel/providers/mapillary/decode.js",
|
||||
"src/layers/streetLevel/tileMath.js",
|
||||
"src/layers/streetLevel/selection.js",
|
||||
"src/layers/streetLevel/providers/mapillary/viewer.js",
|
||||
"src/layers/streetLevel/providers/mapillary/source.js",
|
||||
"scripts/qa-street-level.mjs"
|
||||
],
|
||||
"external": ["cesium", "pbf", "@mapbox/vector-tile", "mapillary-js"]
|
||||
},
|
||||
@@ -1992,20 +1995,20 @@
|
||||
"src/maps/imageryHost.js",
|
||||
"src/ui/recentImagery.js",
|
||||
"src/ui/imageryBoxTool.js",
|
||||
"src/ui/mapillaryControls.js",
|
||||
"src/ui/mapillaryPresentation.js",
|
||||
"src/layers/mapillary/index.js",
|
||||
"src/layers/mapillary/state.js",
|
||||
"src/layers/mapillary/policy.js",
|
||||
"src/layers/mapillary/coverage.js",
|
||||
"src/layers/mapillary/sequences.js",
|
||||
"src/layers/mapillary/glyphs.js",
|
||||
"src/layers/mapillary/decode.js",
|
||||
"src/layers/mapillary/tileMath.js",
|
||||
"src/layers/mapillary/selection.js",
|
||||
"src/layers/mapillary/viewerBridge.js",
|
||||
"src/layers/mapillary/source.js",
|
||||
"src/app/layers/mapillary.js"
|
||||
"src/ui/streetLevelControls.js",
|
||||
"src/ui/streetLevelPresentation.js",
|
||||
"src/layers/streetLevel/index.js",
|
||||
"src/layers/streetLevel/state.js",
|
||||
"src/layers/streetLevel/providers/mapillary/policy.js",
|
||||
"src/layers/streetLevel/providers/mapillary/coverage.js",
|
||||
"src/layers/streetLevel/providers/mapillary/sequences.js",
|
||||
"src/layers/streetLevel/glyphs.js",
|
||||
"src/layers/streetLevel/providers/mapillary/decode.js",
|
||||
"src/layers/streetLevel/tileMath.js",
|
||||
"src/layers/streetLevel/selection.js",
|
||||
"src/layers/streetLevel/providers/mapillary/viewer.js",
|
||||
"src/layers/streetLevel/providers/mapillary/source.js",
|
||||
"src/app/layers/streetLevel.js"
|
||||
],
|
||||
"external": [
|
||||
"hls.js",
|
||||
@@ -2467,18 +2470,18 @@
|
||||
"src/layers/recentImagery/rendering.js",
|
||||
"src/layers/recentImagery/thumbnails.js",
|
||||
"src/maps/imageryHost.js",
|
||||
"src/layers/mapillary/index.js",
|
||||
"src/layers/mapillary/state.js",
|
||||
"src/layers/mapillary/policy.js",
|
||||
"src/layers/mapillary/coverage.js",
|
||||
"src/layers/mapillary/sequences.js",
|
||||
"src/layers/mapillary/glyphs.js",
|
||||
"src/layers/mapillary/decode.js",
|
||||
"src/layers/mapillary/tileMath.js",
|
||||
"src/layers/mapillary/selection.js",
|
||||
"src/layers/mapillary/viewerBridge.js",
|
||||
"src/layers/mapillary/source.js",
|
||||
"src/app/layers/mapillary.js"
|
||||
"src/layers/streetLevel/index.js",
|
||||
"src/layers/streetLevel/state.js",
|
||||
"src/layers/streetLevel/providers/mapillary/policy.js",
|
||||
"src/layers/streetLevel/providers/mapillary/coverage.js",
|
||||
"src/layers/streetLevel/providers/mapillary/sequences.js",
|
||||
"src/layers/streetLevel/glyphs.js",
|
||||
"src/layers/streetLevel/providers/mapillary/decode.js",
|
||||
"src/layers/streetLevel/tileMath.js",
|
||||
"src/layers/streetLevel/selection.js",
|
||||
"src/layers/streetLevel/providers/mapillary/viewer.js",
|
||||
"src/layers/streetLevel/providers/mapillary/source.js",
|
||||
"src/app/layers/streetLevel.js"
|
||||
],
|
||||
"external": [
|
||||
"hls.js",
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#!/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,
|
||||
* 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.
|
||||
*/
|
||||
import assert from 'node:assert/strict';
|
||||
@@ -20,7 +20,7 @@ export const RAIL_ORDER = Object.freeze([
|
||||
'cctv-panel',
|
||||
'weather-panel',
|
||||
'recent-imagery-panel',
|
||||
'mapillary-panel',
|
||||
'street-level-panel',
|
||||
'global-context-panel',
|
||||
]);
|
||||
|
||||
@@ -79,10 +79,10 @@ async function main() {
|
||||
el.remove();
|
||||
});
|
||||
const module = () =>
|
||||
window.__godsEyeView.dataManager.layers.get('mapillary').module;
|
||||
window.__godsEyeView.dataManager.layers.get('street-level').module;
|
||||
const panel = () =>
|
||||
page.evaluate(() => {
|
||||
const el = document.getElementById('mapillary-panel');
|
||||
const el = document.getElementById('street-level-panel');
|
||||
const rail = document.getElementById('right-context-rail');
|
||||
const box = el.getBoundingClientRect();
|
||||
return {
|
||||
@@ -90,9 +90,9 @@ async function main() {
|
||||
classes: [...el.classList],
|
||||
width: Math.round(box.width),
|
||||
right: Math.round(box.right),
|
||||
status: document.getElementById('mly-status').textContent,
|
||||
controlsDisabled: document.getElementById('mly-controls').disabled,
|
||||
bodyDisplay: getComputedStyle(document.getElementById('mly-body'))
|
||||
status: document.getElementById('sl-status').textContent,
|
||||
controlsDisabled: document.getElementById('sl-controls').disabled,
|
||||
bodyDisplay: getComputedStyle(document.getElementById('sl-body'))
|
||||
.display,
|
||||
};
|
||||
});
|
||||
@@ -115,7 +115,7 @@ async function main() {
|
||||
}
|
||||
await page.setViewport(VIEWPORTS[0]);
|
||||
await page.click(
|
||||
'.panel-collapse-btn[data-collapse-target="mapillary-panel"]',
|
||||
'.panel-collapse-btn[data-collapse-target="street-level-panel"]',
|
||||
);
|
||||
await sleep(500);
|
||||
await step(
|
||||
@@ -126,7 +126,7 @@ async function main() {
|
||||
assert.equal(info.bodyDisplay, 'flex');
|
||||
assert.equal(
|
||||
await page.evaluate(
|
||||
() => document.querySelectorAll('#mly-legend li').length,
|
||||
() => document.querySelectorAll('#sl-legend li').length,
|
||||
),
|
||||
4,
|
||||
);
|
||||
@@ -137,7 +137,7 @@ async function main() {
|
||||
'keyless install gates the controls and reports KEY REQUIRED',
|
||||
async () => {
|
||||
await page.evaluate(() =>
|
||||
window.__godsEyeView.dataManager.setEnabled('mapillary', true, {
|
||||
window.__godsEyeView.dataManager.setEnabled('street-level', true, {
|
||||
origin: 'user',
|
||||
}),
|
||||
);
|
||||
@@ -179,14 +179,14 @@ async function main() {
|
||||
'enabling draws coverage and registers the on-globe credit',
|
||||
async () => {
|
||||
await page.evaluate(() =>
|
||||
window.__godsEyeView.dataManager.setEnabled('mapillary', true, {
|
||||
window.__godsEyeView.dataManager.setEnabled('street-level', true, {
|
||||
origin: 'user',
|
||||
}),
|
||||
);
|
||||
await page.waitForFunction(
|
||||
() => {
|
||||
const u = window.__godsEyeView.dataManager.layers
|
||||
.get('mapillary')
|
||||
.get('street-level')
|
||||
.module.getUIState();
|
||||
return u.coverage.sequences > 0 && !u.coverage.loading;
|
||||
},
|
||||
@@ -206,13 +206,13 @@ async function main() {
|
||||
async () => {
|
||||
await page.evaluate(() =>
|
||||
window.__godsEyeView.dataManager.layers
|
||||
.get('mapillary')
|
||||
.get('street-level')
|
||||
.module.openNearest(),
|
||||
);
|
||||
await page.waitForFunction(
|
||||
() => {
|
||||
const s = window.__godsEyeView.dataManager.layers
|
||||
.get('mapillary')
|
||||
.get('street-level')
|
||||
.module.getUIState().street;
|
||||
return s.imageId || s.error;
|
||||
},
|
||||
@@ -221,7 +221,7 @@ async function main() {
|
||||
const street = await page.evaluate(
|
||||
() =>
|
||||
window.__godsEyeView.dataManager.layers
|
||||
.get('mapillary')
|
||||
.get('street-level')
|
||||
.module.getUIState().street,
|
||||
);
|
||||
assert.equal(street.error, null, `viewer error: ${street.error}`);
|
||||
@@ -229,20 +229,20 @@ async function main() {
|
||||
try {
|
||||
await page.waitForFunction(
|
||||
() =>
|
||||
document.getElementById('mly-image-when').textContent.trim()
|
||||
document.getElementById('sl-image-when').textContent.trim()
|
||||
.length > 0,
|
||||
{ timeout: 30_000 },
|
||||
);
|
||||
} catch (error) {
|
||||
const dump = await page.evaluate(() => {
|
||||
const u = window.__godsEyeView.dataManager.layers
|
||||
.get('mapillary')
|
||||
.get('street-level')
|
||||
.module.getUIState();
|
||||
return {
|
||||
street: u.street,
|
||||
wrapHidden: document.getElementById('mly-viewer-wrap').hidden,
|
||||
wrapHidden: document.getElementById('sl-viewer-wrap').hidden,
|
||||
collapsed: document
|
||||
.getElementById('mapillary-panel')
|
||||
.getElementById('street-level-panel')
|
||||
.classList.contains('collapsed'),
|
||||
};
|
||||
});
|
||||
@@ -252,10 +252,10 @@ async function main() {
|
||||
);
|
||||
}
|
||||
const view = await page.evaluate(() => ({
|
||||
hidden: document.getElementById('mly-viewer-wrap').hidden,
|
||||
when: document.getElementById('mly-image-when').textContent.trim(),
|
||||
hidden: document.getElementById('sl-viewer-wrap').hidden,
|
||||
when: document.getElementById('sl-image-when').textContent.trim(),
|
||||
width: Math.round(
|
||||
document.getElementById('mly-viewer').getBoundingClientRect().width,
|
||||
document.getElementById('sl-viewer').getBoundingClientRect().width,
|
||||
),
|
||||
}));
|
||||
assert.equal(view.hidden, false);
|
||||
@@ -266,10 +266,10 @@ async function main() {
|
||||
await step(
|
||||
'EXPAND opens a modal dialog and Esc returns focus to the button',
|
||||
async () => {
|
||||
await page.click('#mly-viewer-expand');
|
||||
await page.click('#sl-viewer-expand');
|
||||
await sleep(600);
|
||||
const dialog = await page.evaluate(() => {
|
||||
const wrap = document.getElementById('mly-viewer-wrap');
|
||||
const wrap = document.getElementById('sl-viewer-wrap');
|
||||
return {
|
||||
role: wrap.getAttribute('role'),
|
||||
modal: wrap.getAttribute('aria-modal'),
|
||||
@@ -287,21 +287,21 @@ async function main() {
|
||||
await page
|
||||
.evaluate(() => ({
|
||||
expanded: document
|
||||
.getElementById('mly-viewer-wrap')
|
||||
.classList.contains('mly-viewer-wrap-expanded'),
|
||||
.getElementById('sl-viewer-wrap')
|
||||
.classList.contains('sl-viewer-wrap-expanded'),
|
||||
focus: document.activeElement?.id,
|
||||
}))
|
||||
.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 page.click('#mly-viewer-close');
|
||||
await page.click('#sl-viewer-close');
|
||||
await sleep(500);
|
||||
const after = await page.evaluate(() => {
|
||||
const u = window.__godsEyeView.dataManager.layers
|
||||
.get('mapillary')
|
||||
.get('street-level')
|
||||
.module.getUIState();
|
||||
return { open: u.street.open, sequence: u.sequence.selectedId };
|
||||
});
|
||||
+1
-1
@@ -4,7 +4,7 @@ const CONTROL_LAYER_IDS = Object.freeze({
|
||||
militaryFlightsLayer: 'military',
|
||||
satellitesLayer: 'satellites',
|
||||
cctvLayer: 'cctv',
|
||||
mapillaryLayer: 'mapillary',
|
||||
streetLevelLayer: 'street-level',
|
||||
radioLayer: 'radio',
|
||||
bikeshareLayer: 'bikeshare',
|
||||
transitLayer: 'transit',
|
||||
|
||||
@@ -93,7 +93,7 @@ test('controls bind catalog instances rather than similarly named defaults', ()
|
||||
'military-awareness',
|
||||
'military-installations',
|
||||
'rocket-launches',
|
||||
'mapillary',
|
||||
'street-level',
|
||||
'local-adsb',
|
||||
];
|
||||
const layers = ids.map((id) => ({ id }));
|
||||
|
||||
@@ -25,7 +25,7 @@ import { createApplicationFirms } from './layers/firms.js';
|
||||
import { createApplicationEarthquakes } from './layers/earthquakes.js';
|
||||
import { createApplicationFirePerimeters } from './layers/perimeters.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 { localGeoJsonServices } from './localGeojsonServices.js';
|
||||
import { createBhoteKoshiEventLayer } from '../data/bhoteKoshiEvent.js';
|
||||
@@ -156,7 +156,7 @@ export function createApplicationCatalog({
|
||||
source: sources['fire-perimeters'],
|
||||
}),
|
||||
createApplicationAlpr({ surface, source: sources.alpr }),
|
||||
createApplicationMapillary({ surface, source: sources.mapillary }),
|
||||
createApplicationStreetLevel({ surface, source: sources.mapillary }),
|
||||
satellites,
|
||||
createApplicationLaunches({ source: sources.launches, satellites }),
|
||||
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 picking from '../../data/pickRegistry.js';
|
||||
import * as input from '../../data/inputOwnership.js';
|
||||
import * as render from '../../renderGovernor.js';
|
||||
|
||||
/** Construct the Mapillary layer using the application scene owners and a supplied source. */
|
||||
export function createApplicationMapillary({ surface, source }) {
|
||||
return createMapillaryLayer({
|
||||
/** Construct the Street Level layer using the application scene owners and a supplied source. */
|
||||
export function createApplicationStreetLevel({ surface, source }) {
|
||||
return createStreetLevelLayer({
|
||||
source,
|
||||
services: {
|
||||
sprites,
|
||||
@@ -498,7 +498,6 @@ export const LAYER_STATE_REGISTRY = Object.freeze([
|
||||
disposition: 'enabled-only',
|
||||
}),
|
||||
Object.freeze({ id: 'local-firms', token: 'w', disposition: 'enabled-only' }),
|
||||
Object.freeze({ id: 'mapillary', token: '3', disposition: 'enabled-only' }),
|
||||
Object.freeze({
|
||||
id: 'military',
|
||||
token: 'm',
|
||||
@@ -538,6 +537,11 @@ export const LAYER_STATE_REGISTRY = Object.freeze([
|
||||
disposition: 'enabled+options',
|
||||
optionOwner: 'satellites',
|
||||
}),
|
||||
Object.freeze({
|
||||
id: 'street-level',
|
||||
token: '3',
|
||||
disposition: 'enabled-only',
|
||||
}),
|
||||
Object.freeze({
|
||||
id: 'telegeography-submarine-cables',
|
||||
token: 'u',
|
||||
|
||||
File renamed without changes.
@@ -1,7 +1,7 @@
|
||||
import { createState } from './state.js';
|
||||
import { createCoverage, visibleBbox } from './coverage.js';
|
||||
import { createSequences } from './sequences.js';
|
||||
import { createViewerBridge } from './viewerBridge.js';
|
||||
import { createCoverage, visibleBbox } from './providers/mapillary/coverage.js';
|
||||
import { createSequences } from './providers/mapillary/sequences.js';
|
||||
import { createViewerBridge } from './providers/mapillary/viewer.js';
|
||||
import { createSelection } from './selection.js';
|
||||
import * as Cesium from 'cesium';
|
||||
import {
|
||||
@@ -9,12 +9,12 @@ import {
|
||||
COVERAGE_RECENT_DAYS,
|
||||
MAPILLARY_CREDIT_HTML,
|
||||
MAPILLARY_KEY_ID,
|
||||
MAPILLARY_LAYER_ID,
|
||||
STREET_LEVEL_LAYER_ID,
|
||||
NEAREST_LIMIT,
|
||||
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 = [
|
||||
'getStatus',
|
||||
@@ -30,7 +30,7 @@ const SOURCE_METHODS = [
|
||||
* are injected; nothing here reads the DOM except the viewer host the UI
|
||||
* hands over.
|
||||
*/
|
||||
export function createMapillaryLayer({ source, services = {} }) {
|
||||
export function createStreetLevelLayer({ source, services = {} }) {
|
||||
if (!SOURCE_METHODS.every((method) => typeof source?.[method] === 'function'))
|
||||
throw new TypeError('A Mapillary source is required');
|
||||
const state = createState({ services });
|
||||
@@ -72,7 +72,7 @@ export function createMapillaryLayer({ source, services = {} }) {
|
||||
try {
|
||||
listener(snapshot);
|
||||
} 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 = {
|
||||
id: MAPILLARY_LAYER_ID,
|
||||
id: STREET_LEVEL_LAYER_ID,
|
||||
name: 'Street Level',
|
||||
icon: '📷',
|
||||
source: 'Mapillary',
|
||||
@@ -184,13 +184,13 @@ export function createMapillaryLayer({ source, services = {} }) {
|
||||
|
||||
init(viewer) {
|
||||
if (state.initialized)
|
||||
throw new Error('Mapillary layer is already initialized');
|
||||
throw new Error('Street Level layer is already initialized');
|
||||
state.viewer = viewer;
|
||||
state.initialized = true;
|
||||
parts.sequences.ensureCollections(viewer);
|
||||
parts.sequences.setVisible(false);
|
||||
refreshStatus();
|
||||
console.log('[Data:Mapillary] Initialized');
|
||||
console.log('[Data:StreetLevel] Initialized');
|
||||
},
|
||||
|
||||
enable(viewer) {
|
||||
+1
-1
@@ -4,7 +4,7 @@ import {
|
||||
coverageZoomForHeight,
|
||||
overviewZoomForHeight,
|
||||
tilesForBbox,
|
||||
} from './tileMath.js';
|
||||
} from '../../tileMath.js';
|
||||
import {
|
||||
COLORS,
|
||||
COVERAGE_LINE_WIDTH_PX,
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import { PbfReader } from 'pbf';
|
||||
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.
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/** 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_GRAPH_HOST = 'https://graph.mapillary.com';
|
||||
|
||||
+9
-6
@@ -1,5 +1,5 @@
|
||||
import * as Cesium from 'cesium';
|
||||
import { imageConeGlyph } from './glyphs.js';
|
||||
import { imageConeGlyph } from '../../glyphs.js';
|
||||
import { passesImageryFilter } from './coverage.js';
|
||||
import {
|
||||
COLORS,
|
||||
@@ -64,7 +64,7 @@ export function createSequences({ state, source, parts }) {
|
||||
});
|
||||
viewer.scene.primitives.add(state.sequence.collection);
|
||||
sprites?.registerSpriteCollection?.(
|
||||
'mapillary',
|
||||
'street-level:mapillary-cones',
|
||||
state.sequence.collection,
|
||||
);
|
||||
}
|
||||
@@ -74,7 +74,7 @@ export function createSequences({ state, source, parts }) {
|
||||
});
|
||||
viewer.scene.primitives.add(state.street.markerCollection);
|
||||
sprites?.registerSpriteCollection?.(
|
||||
'mapillary',
|
||||
'street-level:marker',
|
||||
state.street.markerCollection,
|
||||
);
|
||||
}
|
||||
@@ -199,7 +199,7 @@ export function createSequences({ state, source, parts }) {
|
||||
id: PICK_PREFIX.position,
|
||||
position: cartesian,
|
||||
image: (id) =>
|
||||
import('./glyphs.js').then((m) =>
|
||||
import('../../glyphs.js').then((m) =>
|
||||
m.positionMarkerGlyph({ color: COLORS.position }),
|
||||
),
|
||||
imageId: 'mly-position',
|
||||
@@ -232,14 +232,17 @@ export function createSequences({ state, source, parts }) {
|
||||
for (const key of ['collection']) {
|
||||
const collection = state.sequence[key];
|
||||
if (collection) {
|
||||
sprites?.unregisterSpriteCollection?.('mapillary', collection);
|
||||
sprites?.unregisterSpriteCollection?.(
|
||||
'street-level:mapillary-cones',
|
||||
collection,
|
||||
);
|
||||
viewer?.scene?.primitives?.remove(collection);
|
||||
state.sequence[key] = null;
|
||||
}
|
||||
}
|
||||
if (state.street.markerCollection) {
|
||||
sprites?.unregisterSpriteCollection?.(
|
||||
'mapillary',
|
||||
'street-level:marker',
|
||||
state.street.markerCollection,
|
||||
);
|
||||
viewer?.scene?.primitives?.remove(state.street.markerCollection);
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -1,5 +1,8 @@
|
||||
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. */
|
||||
export function createSelection({ state, parts }) {
|
||||
@@ -90,7 +93,7 @@ export function createSelection({ state, parts }) {
|
||||
Cesium.ScreenSpaceEventType.MOUSE_MOVE,
|
||||
);
|
||||
document.addEventListener('keydown', onKeyDown);
|
||||
picking?.registerPickOwner?.(MAPILLARY_LAYER_ID, ownsPick);
|
||||
picking?.registerPickOwner?.(STREET_LEVEL_LAYER_ID, ownsPick);
|
||||
}
|
||||
|
||||
function uninstall() {
|
||||
@@ -102,7 +105,7 @@ export function createSelection({ state, parts }) {
|
||||
state.clickHandler.destroy();
|
||||
state.clickHandler = null;
|
||||
document.removeEventListener('keydown', onKeyDown);
|
||||
picking?.unregisterPickOwner?.(MAPILLARY_LAYER_ID);
|
||||
picking?.unregisterPickOwner?.(STREET_LEVEL_LAYER_ID);
|
||||
}
|
||||
|
||||
return { install, uninstall, ownsPick };
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -16,7 +16,7 @@ import { createWeatherSource } from '../layers/weather/source.js';
|
||||
import { createCycloneSource } from '../layers/cyclones/source.js';
|
||||
import { createWindSource } from '../layers/wind/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';
|
||||
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 panelChrome = read('src/ui/panelChrome.js');
|
||||
const layoutController = read('src/ui/panelLayoutController.js');
|
||||
const mapillaryCss = read('src/ui/styles/mapillary.css');
|
||||
const controls = read('src/ui/mapillaryControls.js');
|
||||
const panelCss = read('src/ui/styles/street-level.css');
|
||||
const controls = read('src/ui/streetLevelControls.js');
|
||||
|
||||
test('Street Level is an ordinary collapsible GEV panel that starts collapsed', () => {
|
||||
assert.match(
|
||||
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(
|
||||
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, /<svg class="mly-mark"/);
|
||||
assert.match(html, /<svg class="sl-mark"/);
|
||||
assert.doesNotMatch(
|
||||
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', () => {
|
||||
assert.match(panelChrome, /\{ id: 'mapillary-panel' \}/);
|
||||
assert.match(panelChrome, /\{ id: 'street-level-panel' \}/);
|
||||
assert.match(
|
||||
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.
|
||||
assert.match(
|
||||
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 [
|
||||
'#right-context-rail > #mapillary-panel',
|
||||
'#right-context-rail #mapillary-panel.collapsed',
|
||||
'#right-context-rail.layout-focus > #mapillary-panel:not(.collapsed)',
|
||||
'#right-context-rail > #street-level-panel',
|
||||
'#right-context-rail #street-level-panel.collapsed',
|
||||
'#right-context-rail.layout-focus > #street-level-panel:not(.collapsed)',
|
||||
])
|
||||
assert.ok(css.includes(rule), `layers.css names ${rule}`);
|
||||
});
|
||||
|
||||
test('panel styles stay inside GEV conventions: no !important, no fixed panel', () => {
|
||||
assert.equal((mapillaryCss.match(/!important/g) || []).length, 0);
|
||||
const fixed = mapillaryCss
|
||||
assert.equal((panelCss.match(/!important/g) || []).length, 0);
|
||||
const fixed = panelCss
|
||||
.replace(/\/\*[\s\S]*?\*\//g, '')
|
||||
.split('}')
|
||||
.filter((block) => /position:\s*fixed/.test(block))
|
||||
.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', () => {
|
||||
// The key requirement is documented (README, .env.example), not repeated in the panel.
|
||||
assert.doesNotMatch(
|
||||
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, /<ul id="mly-legend"/);
|
||||
assert.match(html, /id="mly-error"[^>]*role="alert"/);
|
||||
assert.match(html, /id="mly-status"[^>]*role="status"/);
|
||||
assert.match(html, /<fieldset id="sl-controls"/);
|
||||
assert.match(html, /<ul id="sl-legend"/);
|
||||
assert.match(html, /id="sl-error"[^>]*role="alert"/);
|
||||
assert.match(html, /id="sl-status"[^>]*role="status"/);
|
||||
});
|
||||
|
||||
test('the expanded viewer is a modal dialog that restores focus', () => {
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
isCollapsed,
|
||||
RAIL_ORDER,
|
||||
VIEWPORTS,
|
||||
} from '../../scripts/qa-mapillary.mjs';
|
||||
} from '../../scripts/qa-street-level.mjs';
|
||||
|
||||
test('the QA harness exercises both review viewports', () => {
|
||||
assert.deepEqual(
|
||||
@@ -20,7 +20,7 @@ test('rail order puts Street Level between CCTV and Context', () => {
|
||||
'cctv-panel',
|
||||
'weather-panel',
|
||||
'recent-imagery-panel',
|
||||
'mapillary-panel',
|
||||
'street-level-panel',
|
||||
'global-context-panel',
|
||||
]);
|
||||
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', () => {
|
||||
const source = fs.readFileSync(
|
||||
new URL('../../scripts/qa-mapillary.mjs', import.meta.url),
|
||||
new URL('../../scripts/qa-street-level.mjs', import.meta.url),
|
||||
'utf8',
|
||||
);
|
||||
assert.match(
|
||||
+15
-15
@@ -15,7 +15,7 @@ import { readShellElements } from './shellElements.js';
|
||||
import { CockpitCoordinator } from './cockpitCoordinator.js';
|
||||
import { ContextControls } from './context.js';
|
||||
import { CctvControls } from './cctv.js';
|
||||
import { MapillaryControls } from './mapillaryControls.js';
|
||||
import { StreetLevelControls } from './streetLevelControls.js';
|
||||
import { RadioControls } from './radio.js';
|
||||
import { LocalSdrControls } from './localSdrControls.js';
|
||||
import { LocationNavigation } from './locationNavigation.js';
|
||||
@@ -570,7 +570,7 @@ export class StyleManager extends ShellFacade {
|
||||
this._initRightPanelAdaptiveLayout();
|
||||
this._initRadioPanel();
|
||||
this._initCctvPanel();
|
||||
this._initMapillaryPanel();
|
||||
this._initStreetLevelPanel();
|
||||
this._initGlobalContextPanel();
|
||||
this._initLocationBar();
|
||||
this._initShareButton();
|
||||
@@ -933,26 +933,26 @@ export class StyleManager extends ShellFacade {
|
||||
}
|
||||
|
||||
/** Compose camera panel controls from the existing camera port and application actions. */
|
||||
_initMapillaryPanel() {
|
||||
const { mapillaryLayer } = this.services;
|
||||
this._mapillaryControls?.destroy();
|
||||
this._mapillaryControls = null;
|
||||
if (!this._mapillaryPanel || !mapillaryLayer) return;
|
||||
this._mapillaryControls = new MapillaryControls({
|
||||
root: this._mapillaryPanel,
|
||||
mapillary: mapillaryLayer,
|
||||
_initStreetLevelPanel() {
|
||||
const { streetLevelLayer } = this.services;
|
||||
this._streetLevelControls?.destroy();
|
||||
this._streetLevelControls = null;
|
||||
if (!this._streetLevelPanel || !streetLevelLayer) return;
|
||||
this._streetLevelControls = new StreetLevelControls({
|
||||
root: this._streetLevelPanel,
|
||||
layer: streetLevelLayer,
|
||||
actions: {
|
||||
isEnabled: () => this._dataManager?.isEnabled('mapillary') === true,
|
||||
isEnabled: () => this._dataManager?.isEnabled('street-level') === true,
|
||||
setEnabled: (enabled) =>
|
||||
this._dataManager?.setEnabled('mapillary', enabled, {
|
||||
this._dataManager?.setEnabled('street-level', enabled, {
|
||||
origin: 'user',
|
||||
}),
|
||||
setPanelCollapsed: (collapsed, options) =>
|
||||
this.setPanelCollapsed('mapillary-panel', collapsed, options),
|
||||
this.setPanelCollapsed('street-level-panel', collapsed, options),
|
||||
showToast: (message) => this._showToast(message),
|
||||
},
|
||||
});
|
||||
this._mapillaryControls.connect();
|
||||
this._streetLevelControls.connect();
|
||||
}
|
||||
|
||||
_initCctvPanel() {
|
||||
@@ -1557,7 +1557,7 @@ export class StyleManager extends ShellFacade {
|
||||
this._cameraOrientationControls?.destroy();
|
||||
this._clearLayersControl?.destroy();
|
||||
this._cctvControls?.destroy();
|
||||
this._mapillaryControls?.destroy();
|
||||
this._streetLevelControls?.destroy();
|
||||
this._radioControls?.destroy();
|
||||
this._localSdrControls?.destroy();
|
||||
this._cockpitCoordinator.stop();
|
||||
|
||||
@@ -32,7 +32,7 @@ const PANEL_GROUPS = [
|
||||
},
|
||||
{
|
||||
label: 'Cameras',
|
||||
ids: ['cctv', 'recent-imagery', 'mapillary'],
|
||||
ids: ['cctv', 'recent-imagery', 'street-level'],
|
||||
},
|
||||
{
|
||||
label: 'Infrastructure',
|
||||
@@ -73,7 +73,7 @@ const PANEL_LABELS = {
|
||||
'ais-live-vessels': 'Live Vessels',
|
||||
bikeshare: 'Bike Share',
|
||||
cctv: 'Cameras',
|
||||
mapillary: 'Street Level (Mapillary)',
|
||||
'street-level': 'Street Level',
|
||||
'alpr-cameras': 'Mapped ALPR Cameras',
|
||||
'local-datacenters': 'Data Centers',
|
||||
'local-firms': 'Active Fires',
|
||||
|
||||
@@ -14,7 +14,7 @@ const SHARE_PANEL_STATE_SPECS = Object.freeze([
|
||||
{ id: 'cctv-panel' },
|
||||
{ id: 'weather-panel' },
|
||||
{ id: 'recent-imagery-panel' },
|
||||
{ id: 'mapillary-panel' },
|
||||
{ id: 'street-level-panel' },
|
||||
{ id: 'radio-panel' },
|
||||
{ id: 'scene-panel' },
|
||||
{ id: 'global-context-panel' },
|
||||
@@ -27,7 +27,7 @@ const COCKPIT_ENTRY_COLLAPSE_PANEL_IDS = Object.freeze([
|
||||
'cctv-panel',
|
||||
'weather-panel',
|
||||
'recent-imagery-panel',
|
||||
'mapillary-panel',
|
||||
'street-level-panel',
|
||||
'scene-panel',
|
||||
'pp-toggles',
|
||||
'global-context-panel',
|
||||
@@ -290,7 +290,7 @@ export class PanelChrome {
|
||||
'cctv-panel',
|
||||
'weather-panel',
|
||||
'recent-imagery-panel',
|
||||
'mapillary-panel',
|
||||
'street-level-panel',
|
||||
'global-context-panel',
|
||||
].includes(panelEl?.id);
|
||||
const collapsed = panelEl.classList.contains('collapsed');
|
||||
|
||||
@@ -97,7 +97,7 @@ export class PanelLayoutController {
|
||||
this._cctvPanel = document.getElementById('cctv-panel');
|
||||
this._weatherPanel = document.getElementById('weather-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._detectionBtn = document.getElementById('detection-toggle');
|
||||
}
|
||||
@@ -202,7 +202,7 @@ export class PanelLayoutController {
|
||||
this._cctvPanel,
|
||||
this._weatherPanel,
|
||||
this._recentImageryPanel,
|
||||
this._mapillaryPanel,
|
||||
this._streetLevelPanel,
|
||||
]) {
|
||||
if (!panel) continue;
|
||||
for (const property of ['top', 'right', 'bottom', 'left', 'z-index'])
|
||||
@@ -231,7 +231,7 @@ export class PanelLayoutController {
|
||||
this._cctvPanel,
|
||||
this._weatherPanel,
|
||||
this._recentImageryPanel,
|
||||
this._mapillaryPanel,
|
||||
this._streetLevelPanel,
|
||||
globalContextPanel,
|
||||
document.getElementById('radio-panel'),
|
||||
]) {
|
||||
|
||||
@@ -59,7 +59,7 @@ export function readShellElements(document = globalThis.document) {
|
||||
_dataPanel: document.getElementById('data-panel'),
|
||||
_scenePanel: document.getElementById('scene-panel'),
|
||||
_cctvPanel: document.getElementById('cctv-panel'),
|
||||
_mapillaryPanel: document.getElementById('mapillary-panel'),
|
||||
_streetLevelPanel: document.getElementById('street-level-panel'),
|
||||
_radioPanel: document.getElementById('radio-panel'),
|
||||
_contextRadioDock: document.getElementById('context-radio-dock'),
|
||||
_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:mapillary:render-mode';
|
||||
const RENDER_MODE_KEY = 'gev:street-level:render-mode';
|
||||
/** Key the panel used before it became provider-neutral. */
|
||||
const LEGACY_RENDER_MODE_KEY = 'gev:mapillary:render-mode';
|
||||
const FOCUSABLE =
|
||||
'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
|
||||
* 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
|
||||
* the application shell; this class only fills the body and asks the shell
|
||||
* to open the panel when something worth seeing arrives.
|
||||
*/
|
||||
export class MapillaryControls {
|
||||
constructor({ root, mapillary, actions }) {
|
||||
export class StreetLevelControls {
|
||||
constructor({ root, layer, actions }) {
|
||||
this.root = root;
|
||||
this.mapillary = mapillary;
|
||||
this.layer = layer;
|
||||
this.actions = actions;
|
||||
this.destroyed = false;
|
||||
this.listeners = new AbortController();
|
||||
@@ -34,24 +34,24 @@ export class MapillaryControls {
|
||||
_collect() {
|
||||
const byId = (id) => this.root?.querySelector(`#${id}`) || null;
|
||||
return {
|
||||
status: byId('mly-status'),
|
||||
controls: byId('mly-controls'),
|
||||
enableBtn: byId('mly-enable-btn'),
|
||||
lookBtn: byId('mly-look-btn'),
|
||||
error: byId('mly-error'),
|
||||
errorText: byId('mly-error-text'),
|
||||
sinceSelect: byId('mly-since'),
|
||||
legend: byId('mly-legend'),
|
||||
followBtn: byId('mly-follow-btn'),
|
||||
viewerWrap: byId('mly-viewer-wrap'),
|
||||
viewerExpand: byId('mly-viewer-expand'),
|
||||
viewerClose: byId('mly-viewer-close'),
|
||||
viewerPlaceholder: byId('mly-viewer-placeholder'),
|
||||
viewer: byId('mly-viewer'),
|
||||
imageBy: byId('mly-image-by'),
|
||||
imageWhen: byId('mly-image-when'),
|
||||
imageLink: byId('mly-image-link'),
|
||||
coverageMeta: byId('mly-coverage-meta'),
|
||||
status: byId('sl-status'),
|
||||
controls: byId('sl-controls'),
|
||||
enableBtn: byId('sl-enable-btn'),
|
||||
lookBtn: byId('sl-look-btn'),
|
||||
error: byId('sl-error'),
|
||||
errorText: byId('sl-error-text'),
|
||||
sinceSelect: byId('sl-since'),
|
||||
legend: byId('sl-legend'),
|
||||
followBtn: byId('sl-follow-btn'),
|
||||
viewerWrap: byId('sl-viewer-wrap'),
|
||||
viewerExpand: byId('sl-viewer-expand'),
|
||||
viewerClose: byId('sl-viewer-close'),
|
||||
viewerPlaceholder: byId('sl-viewer-placeholder'),
|
||||
viewer: byId('sl-viewer'),
|
||||
imageBy: byId('sl-image-by'),
|
||||
imageWhen: byId('sl-image-when'),
|
||||
imageLink: byId('sl-image-link'),
|
||||
coverageMeta: byId('sl-coverage-meta'),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -65,16 +65,16 @@ export class MapillaryControls {
|
||||
_bind() {
|
||||
const el = this._elements;
|
||||
if (!this.root) return;
|
||||
this.mapillary.attachViewerHost?.(el.viewer);
|
||||
this.layer.attachViewerHost?.(el.viewer);
|
||||
|
||||
this.listen(el.enableBtn, 'click', () => this._toggleEnabled());
|
||||
this.listen(el.lookBtn, 'click', async () => {
|
||||
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.mapillary.setCoverageFilter?.({ pano: button.dataset.mlyPano }),
|
||||
this.layer.setCoverageFilter?.({ pano: button.dataset.slPano }),
|
||||
);
|
||||
}
|
||||
this.listen(el.sinceSelect, 'change', () => {
|
||||
@@ -84,19 +84,19 @@ export class MapillaryControls {
|
||||
sinceMs = Date.UTC(Number(value.slice(5)), 0, 1);
|
||||
else if (Number(value) > 0)
|
||||
sinceMs = Date.now() - Number(value) * 86_400_000;
|
||||
this.mapillary.setCoverageFilter?.({ sinceMs });
|
||||
this.layer.setCoverageFilter?.({ sinceMs });
|
||||
});
|
||||
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.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', () => {
|
||||
const mode = button.dataset.mlyRender;
|
||||
this.mapillary.setViewerRenderMode?.(mode);
|
||||
const mode = button.dataset.slRender;
|
||||
this.layer.setViewerRenderMode?.(mode);
|
||||
try {
|
||||
localStorage.setItem(RENDER_MODE_KEY, mode);
|
||||
} catch {
|
||||
@@ -105,9 +105,11 @@ export class MapillaryControls {
|
||||
});
|
||||
}
|
||||
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')
|
||||
this.mapillary.setViewerRenderMode?.(stored);
|
||||
this.layer.setViewerRenderMode?.(stored);
|
||||
} catch {
|
||||
/* storage unavailable */
|
||||
}
|
||||
@@ -121,7 +123,7 @@ export class MapillaryControls {
|
||||
queued = true;
|
||||
requestAnimationFrame(() => {
|
||||
queued = false;
|
||||
this.mapillary.resizeViewer?.();
|
||||
this.layer.resizeViewer?.();
|
||||
});
|
||||
});
|
||||
this._resizeObserver.observe(el.viewer);
|
||||
@@ -154,23 +156,20 @@ export class MapillaryControls {
|
||||
this._unsubscribe?.();
|
||||
this._unsubscribe = null;
|
||||
if (this.destroyed || !this.root) return;
|
||||
this._unsubscribe = this.mapillary.subscribe?.((state) =>
|
||||
this.render(state),
|
||||
);
|
||||
if (this.mapillary.getUIState) this.render(this.mapillary.getUIState());
|
||||
this._unsubscribe = this.layer.subscribe?.((state) => this.render(state));
|
||||
if (this.layer.getUIState) this.render(this.layer.getUIState());
|
||||
}
|
||||
|
||||
/** Ask the shell to open (or close) the rail panel. */
|
||||
setCollapsed(collapsed, options = {}) {
|
||||
this.actions.setPanelCollapsed?.(collapsed, options);
|
||||
if (!collapsed)
|
||||
requestAnimationFrame(() => this.mapillary.resizeViewer?.());
|
||||
if (!collapsed) requestAnimationFrame(() => this.layer.resizeViewer?.());
|
||||
}
|
||||
|
||||
isViewerExpanded() {
|
||||
return (
|
||||
this._elements.viewerWrap?.classList.contains(
|
||||
'mly-viewer-wrap-expanded',
|
||||
'sl-viewer-wrap-expanded',
|
||||
) === true
|
||||
);
|
||||
}
|
||||
@@ -187,14 +186,14 @@ export class MapillaryControls {
|
||||
this._wrapHome = { parent: wrap.parentNode, next: wrap.nextSibling };
|
||||
this._expandReturnFocus = document.activeElement;
|
||||
document.body.appendChild(wrap);
|
||||
wrap.classList.add('mly-viewer-wrap-expanded');
|
||||
wrap.classList.add('sl-viewer-wrap-expanded');
|
||||
wrap.setAttribute('role', 'dialog');
|
||||
wrap.setAttribute('aria-modal', 'true');
|
||||
wrap.setAttribute('aria-label', 'Street-level image');
|
||||
wrap.tabIndex = -1;
|
||||
wrap.focus({ preventScroll: true });
|
||||
} else {
|
||||
wrap.classList.remove('mly-viewer-wrap-expanded');
|
||||
wrap.classList.remove('sl-viewer-wrap-expanded');
|
||||
wrap.removeAttribute('role');
|
||||
wrap.removeAttribute('aria-modal');
|
||||
wrap.removeAttribute('aria-label');
|
||||
@@ -210,14 +209,14 @@ export class MapillaryControls {
|
||||
}
|
||||
const button = this._elements.viewerExpand;
|
||||
if (button) {
|
||||
const icon = button.querySelector('.mly-btn-icon');
|
||||
const text = button.querySelector('.mly-btn-text');
|
||||
const icon = button.querySelector('.sl-btn-icon');
|
||||
const text = button.querySelector('.sl-btn-text');
|
||||
if (icon) icon.textContent = on ? '⤡' : '⤢';
|
||||
if (text) text.textContent = on ? 'SHRINK' : 'EXPAND';
|
||||
button.setAttribute('aria-pressed', String(on));
|
||||
button.setAttribute('aria-label', on ? 'Shrink' : 'Expand');
|
||||
}
|
||||
requestAnimationFrame(() => this.mapillary.resizeViewer?.());
|
||||
requestAnimationFrame(() => this.layer.resizeViewer?.());
|
||||
}
|
||||
|
||||
_onDialogKey(event) {
|
||||
@@ -251,7 +250,7 @@ export class MapillaryControls {
|
||||
render(state) {
|
||||
if (this.destroyed || !state || !this.root) return;
|
||||
this._state = state;
|
||||
const view = presentMapillaryPanel(state);
|
||||
const view = presentStreetLevelPanel(state);
|
||||
this._view = view;
|
||||
this._renderHeader(view);
|
||||
this._renderGate(view);
|
||||
@@ -264,10 +263,10 @@ export class MapillaryControls {
|
||||
|
||||
_renderHeader(view) {
|
||||
const el = this._elements;
|
||||
this.root.dataset.mlyEnabled = String(view.enabled);
|
||||
this.root.dataset.slEnabled = String(view.enabled);
|
||||
if (el.status) {
|
||||
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) {
|
||||
el.enableBtn.textContent = view.enableButton.text;
|
||||
@@ -292,8 +291,8 @@ export class MapillaryControls {
|
||||
|
||||
_renderFilters(view) {
|
||||
const el = this._elements;
|
||||
for (const button of this.root.querySelectorAll('[data-mly-pano]')) {
|
||||
const active = button.dataset.mlyPano === view.filter.pano;
|
||||
for (const button of this.root.querySelectorAll('[data-sl-pano]')) {
|
||||
const active = button.dataset.slPano === view.filter.pano;
|
||||
button.classList.toggle('is-active', active);
|
||||
button.setAttribute('aria-checked', String(active));
|
||||
}
|
||||
@@ -302,7 +301,7 @@ export class MapillaryControls {
|
||||
...view.legend.map((entry) => {
|
||||
const item = document.createElement('li');
|
||||
const swatch = document.createElement('i');
|
||||
swatch.className = 'mly-legend-swatch';
|
||||
swatch.className = 'sl-legend-swatch';
|
||||
swatch.style.background = entry.color;
|
||||
const label = document.createElement('span');
|
||||
label.textContent = entry.label;
|
||||
@@ -323,8 +322,8 @@ export class MapillaryControls {
|
||||
el.followBtn.setAttribute('aria-pressed', String(viewer.follow.pressed));
|
||||
el.followBtn.disabled = viewer.follow.disabled;
|
||||
}
|
||||
for (const button of this.root.querySelectorAll('[data-mly-render]')) {
|
||||
const active = button.dataset.mlyRender === viewer.renderMode;
|
||||
for (const button of this.root.querySelectorAll('[data-sl-render]')) {
|
||||
const active = button.dataset.slRender === viewer.renderMode;
|
||||
button.classList.toggle('is-active', active);
|
||||
button.setAttribute('aria-checked', String(active));
|
||||
}
|
||||
@@ -335,7 +334,7 @@ export class MapillaryControls {
|
||||
el.imageLink.hidden = !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) {
|
||||
@@ -361,6 +360,6 @@ export class MapillaryControls {
|
||||
this._resizeObserver = null;
|
||||
this._unsubscribe?.();
|
||||
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()`.
|
||||
* @returns {object} Everything the panel needs, already worded.
|
||||
*/
|
||||
export function presentMapillaryPanel(state) {
|
||||
export function presentStreetLevelPanel(state) {
|
||||
const enabled = state.enabled === true;
|
||||
const keyRequired = state.keyRequired === true;
|
||||
return {
|
||||
@@ -2,8 +2,8 @@ import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import {
|
||||
mapillaryImageUrl,
|
||||
presentMapillaryPanel,
|
||||
} from './mapillaryPresentation.js';
|
||||
presentStreetLevelPanel,
|
||||
} from './streetLevelPresentation.js';
|
||||
|
||||
function snapshot(overrides = {}) {
|
||||
const base = {
|
||||
@@ -59,45 +59,48 @@ function deepMerge(target, source) {
|
||||
}
|
||||
|
||||
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.deepEqual(view.status, { text: 'KEY REQUIRED', tone: 'warn' });
|
||||
});
|
||||
|
||||
test('status reads LOADING while coverage streams, then ON or OFF', () => {
|
||||
assert.deepEqual(presentMapillaryPanel(snapshot()).status, {
|
||||
assert.deepEqual(presentStreetLevelPanel(snapshot()).status, {
|
||||
text: 'OFF',
|
||||
tone: '',
|
||||
});
|
||||
assert.deepEqual(
|
||||
presentMapillaryPanel(
|
||||
presentStreetLevelPanel(
|
||||
snapshot({ enabled: true, coverage: { loading: true } }),
|
||||
).status,
|
||||
{ text: 'LOADING', tone: 'busy' },
|
||||
);
|
||||
assert.deepEqual(presentMapillaryPanel(snapshot({ enabled: true })).status, {
|
||||
text: 'ON',
|
||||
tone: 'on',
|
||||
});
|
||||
assert.deepEqual(
|
||||
presentStreetLevelPanel(snapshot({ enabled: true })).status,
|
||||
{
|
||||
text: 'ON',
|
||||
tone: 'on',
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
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(
|
||||
presentMapillaryPanel(
|
||||
presentStreetLevelPanel(
|
||||
snapshot({ street: { error: 'Image could not be opened' } }),
|
||||
).error,
|
||||
'Image could not be opened',
|
||||
);
|
||||
assert.equal(
|
||||
presentMapillaryPanel(snapshot({ coverage: { error: 'Tile HTTP 502' } }))
|
||||
presentStreetLevelPanel(snapshot({ coverage: { error: 'Tile HTTP 502' } }))
|
||||
.error,
|
||||
'Tile HTTP 502',
|
||||
);
|
||||
});
|
||||
|
||||
test('legend passes through in the layer’s order', () => {
|
||||
const view = presentMapillaryPanel(snapshot());
|
||||
const view = presentStreetLevelPanel(snapshot());
|
||||
assert.deepEqual(
|
||||
view.legend.map((entry) => entry.key),
|
||||
['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', () => {
|
||||
assert.equal(presentMapillaryPanel(snapshot()).meta, '');
|
||||
const browsing = presentMapillaryPanel(
|
||||
assert.equal(presentStreetLevelPanel(snapshot()).meta, '');
|
||||
const browsing = presentStreetLevelPanel(
|
||||
snapshot({ enabled: true, coverage: { zoom: 14, sequences: 812 } }),
|
||||
);
|
||||
assert.match(browsing.meta, /^812 sequences in view · click a line/);
|
||||
const selected = presentMapillaryPanel(
|
||||
const selected = presentStreetLevelPanel(
|
||||
snapshot({
|
||||
enabled: true,
|
||||
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.doesNotMatch(selected.meta, /sequences in view/);
|
||||
const hinted = presentMapillaryPanel(
|
||||
const hinted = presentStreetLevelPanel(
|
||||
snapshot({
|
||||
enabled: true,
|
||||
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', () => {
|
||||
const view = presentMapillaryPanel(
|
||||
const view = presentStreetLevelPanel(
|
||||
snapshot({
|
||||
enabled: true,
|
||||
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', () => {
|
||||
const view = presentMapillaryPanel(
|
||||
const view = presentStreetLevelPanel(
|
||||
snapshot({
|
||||
street: { open: true, imageId: '1', capturedAt: Date.UTC(2024, 0, 2) },
|
||||
}),
|
||||
@@ -162,7 +162,7 @@
|
||||
}
|
||||
#right-context-rail > #pp-toggles,
|
||||
#right-context-rail > #cctv-panel,
|
||||
#right-context-rail > #mapillary-panel,
|
||||
#right-context-rail > #street-level-panel,
|
||||
#right-context-rail > #weather-panel,
|
||||
#right-context-rail > #recent-imagery-panel,
|
||||
#right-context-rail > #global-context-panel {
|
||||
@@ -177,7 +177,7 @@
|
||||
pointer-events: auto;
|
||||
}
|
||||
#right-context-rail #cctv-panel,
|
||||
#right-context-rail #mapillary-panel,
|
||||
#right-context-rail #street-level-panel,
|
||||
#right-context-rail #weather-panel,
|
||||
#right-context-rail #recent-imagery-panel {
|
||||
width: 100%;
|
||||
@@ -193,7 +193,7 @@
|
||||
min-height: 0;
|
||||
}
|
||||
#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 #recent-imagery-panel.collapsed {
|
||||
width: var(--right-display-collapsed-width);
|
||||
@@ -208,7 +208,7 @@
|
||||
}
|
||||
#right-context-rail > #pp-toggles.collapsed .pp-header-row,
|
||||
#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 #recent-imagery-panel.collapsed .recent-imagery-panel-inner,
|
||||
#right-context-rail
|
||||
@@ -219,7 +219,9 @@
|
||||
box-sizing: border-box;
|
||||
}
|
||||
#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%;
|
||||
overflow-y: auto;
|
||||
scrollbar-gutter: stable;
|
||||
@@ -267,7 +269,7 @@
|
||||
}
|
||||
#right-context-rail.layout-focus > #pp-toggles: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 > #recent-imagery-panel:not(.collapsed),
|
||||
#right-context-rail.layout-focus > #global-context-panel:not(.collapsed) {
|
||||
@@ -289,8 +291,8 @@
|
||||
> #cctv-panel:not(.collapsed)
|
||||
.cctv-panel-inner,
|
||||
#right-context-rail.layout-focus
|
||||
> #mapillary-panel:not(.collapsed)
|
||||
.mapillary-panel-inner,
|
||||
> #street-level-panel:not(.collapsed)
|
||||
.street-level-panel-inner,
|
||||
#right-context-rail.layout-focus
|
||||
> #recent-imagery-panel:not(.collapsed)
|
||||
.recent-imagery-panel-inner,
|
||||
|
||||
@@ -217,9 +217,9 @@
|
||||
|
||||
#right-context-rail #pp-toggles.collapsed .pp-header-row,
|
||||
#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 #mapillary-panel.collapsed,
|
||||
#right-context-rail #street-level-panel.collapsed,
|
||||
#right-context-rail #recent-imagery-panel,
|
||||
#right-context-rail #recent-imagery-panel.collapsed,
|
||||
#right-context-rail #radio-panel,
|
||||
@@ -235,12 +235,12 @@
|
||||
|
||||
#left-panel-stack > #data-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 > #scene-panel,
|
||||
#left-panel-stack > #data-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 > #scene-panel.collapsed {
|
||||
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
|
||||
width (see layers.css). Everything here is panel-internal. */
|
||||
#mapillary-panel {
|
||||
--mly-green: #05cb63;
|
||||
--mly-text: 0.62rem;
|
||||
--mly-text-small: 0.6rem;
|
||||
#street-level-panel {
|
||||
--sl-green: #05cb63;
|
||||
--sl-text: 0.62rem;
|
||||
--sl-text-small: 0.6rem;
|
||||
}
|
||||
|
||||
#mapillary-panel .panel-glow {
|
||||
#street-level-panel .panel-glow {
|
||||
position: absolute;
|
||||
inset: -18px;
|
||||
background: radial-gradient(
|
||||
@@ -20,7 +20,7 @@
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.mapillary-panel-inner {
|
||||
.street-level-panel-inner {
|
||||
position: relative;
|
||||
background: var(--glass-bg);
|
||||
border: 1px solid var(--glass-border);
|
||||
@@ -39,11 +39,11 @@
|
||||
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;
|
||||
}
|
||||
|
||||
.mly-mark {
|
||||
.sl-mark {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
flex: 0 0 auto;
|
||||
@@ -51,14 +51,14 @@
|
||||
transition: color var(--transition-fast);
|
||||
}
|
||||
|
||||
#mapillary-panel[data-mly-enabled='true'] .mly-mark {
|
||||
color: var(--mly-green);
|
||||
#street-level-panel[data-sl-enabled='true'] .sl-mark {
|
||||
color: var(--sl-green);
|
||||
filter: drop-shadow(0 0 6px rgba(5, 203, 99, 0.7));
|
||||
}
|
||||
|
||||
.mly-status {
|
||||
.sl-status {
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--mly-text-small);
|
||||
font-size: var(--sl-text-small);
|
||||
letter-spacing: 0.1em;
|
||||
padding: 2px 7px;
|
||||
border-radius: 999px;
|
||||
@@ -67,35 +67,35 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.mly-status.is-on {
|
||||
color: var(--mly-green);
|
||||
.sl-status.is-on {
|
||||
color: var(--sl-green);
|
||||
border-color: rgba(5, 203, 99, 0.55);
|
||||
}
|
||||
|
||||
.mly-status.is-busy {
|
||||
.sl-status.is-busy {
|
||||
color: var(--accent);
|
||||
border-color: rgba(0, 212, 255, 0.55);
|
||||
}
|
||||
|
||||
.mly-status.is-warn {
|
||||
.sl-status.is-warn {
|
||||
color: #ffb300;
|
||||
border-color: rgba(255, 179, 0, 0.55);
|
||||
}
|
||||
|
||||
/* Collapsed strips hide the body entirely: nothing to paint or tab into. */
|
||||
#mapillary-panel.collapsed .mly-body,
|
||||
#mapillary-panel.collapsed .mly-status {
|
||||
#street-level-panel.collapsed .sl-body,
|
||||
#street-level-panel.collapsed .sl-status {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mly-body {
|
||||
.sl-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.mly-controls {
|
||||
.sl-controls {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
@@ -105,13 +105,13 @@
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.mly-controls:disabled {
|
||||
.sl-controls:disabled {
|
||||
opacity: 0.45;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* ── Errors ──────────────────────────────────────────────────────────────── */
|
||||
.mly-error {
|
||||
.sl-error {
|
||||
font-family: var(--font-sans);
|
||||
font-size: 0.72rem;
|
||||
line-height: 1.4;
|
||||
@@ -119,23 +119,23 @@
|
||||
}
|
||||
|
||||
/* ── Filters and legend ──────────────────────────────────────────────────── */
|
||||
.mly-filters {
|
||||
.sl-filters {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.mly-seg {
|
||||
.sl-seg {
|
||||
display: inline-flex;
|
||||
border: 1px solid var(--glass-border-hover);
|
||||
border-radius: 999px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.mly-seg-btn {
|
||||
.sl-seg-btn {
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--mly-text-small);
|
||||
font-size: var(--sl-text-small);
|
||||
letter-spacing: 0.08em;
|
||||
padding: 4px 9px;
|
||||
border: 0;
|
||||
@@ -144,18 +144,18 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mly-seg-btn + .mly-seg-btn {
|
||||
.sl-seg-btn + .sl-seg-btn {
|
||||
border-left: 1px solid var(--glass-border);
|
||||
}
|
||||
|
||||
.mly-seg-btn.is-active {
|
||||
.sl-seg-btn.is-active {
|
||||
background: rgba(5, 203, 99, 0.22);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.mly-select {
|
||||
.sl-select {
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--mly-text-small);
|
||||
font-size: var(--sl-text-small);
|
||||
letter-spacing: 0.06em;
|
||||
padding: 4px 8px;
|
||||
border-radius: 999px;
|
||||
@@ -164,11 +164,11 @@
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.mly-select option {
|
||||
.sl-select option {
|
||||
background: #0a0a0f;
|
||||
}
|
||||
|
||||
.mly-legend {
|
||||
.sl-legend {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px 12px;
|
||||
@@ -177,17 +177,17 @@
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.mly-legend li {
|
||||
.sl-legend li {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--mly-text-small);
|
||||
font-size: var(--sl-text-small);
|
||||
letter-spacing: 0.06em;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.mly-legend-swatch {
|
||||
.sl-legend-swatch {
|
||||
width: 14px;
|
||||
height: 3px;
|
||||
border-radius: 2px;
|
||||
@@ -195,7 +195,7 @@
|
||||
}
|
||||
|
||||
/* ── Embedded viewer ─────────────────────────────────────────────────────── */
|
||||
.mly-viewer-wrap {
|
||||
.sl-viewer-wrap {
|
||||
position: relative;
|
||||
container-type: inline-size;
|
||||
display: flex;
|
||||
@@ -207,17 +207,17 @@
|
||||
background: #000;
|
||||
}
|
||||
|
||||
.mly-viewer-wrap[hidden] {
|
||||
.sl-viewer-wrap[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mly-viewer {
|
||||
.sl-viewer {
|
||||
width: 100%;
|
||||
aspect-ratio: 16 / 10;
|
||||
min-height: 180px;
|
||||
}
|
||||
|
||||
.mly-viewer-placeholder {
|
||||
.sl-viewer-placeholder {
|
||||
position: absolute;
|
||||
inset: 0 0 26px;
|
||||
display: grid;
|
||||
@@ -226,27 +226,27 @@
|
||||
background: rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
|
||||
.mly-viewer-placeholder[hidden] {
|
||||
.sl-viewer-placeholder[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mly-viewer-placeholder::after {
|
||||
.sl-viewer-placeholder::after {
|
||||
content: '';
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid rgba(255, 255, 255, 0.2);
|
||||
border-top-color: var(--mly-green);
|
||||
animation: mly-spin 0.9s linear infinite;
|
||||
border-top-color: var(--sl-green);
|
||||
animation: sl-spin 0.9s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes mly-spin {
|
||||
@keyframes sl-spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.mly-viewer-tools {
|
||||
.sl-viewer-tools {
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
left: 6px;
|
||||
@@ -259,18 +259,18 @@
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.mly-viewer-tools > * {
|
||||
.sl-viewer-tools > * {
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.mly-viewer-tools.mly-viewer-tools-right {
|
||||
.sl-viewer-tools.sl-viewer-tools-right {
|
||||
left: auto;
|
||||
right: 6px;
|
||||
}
|
||||
|
||||
.mly-viewer-tools .scene-btn {
|
||||
.sl-viewer-tools .scene-btn {
|
||||
padding: 4px 8px;
|
||||
font-size: var(--mly-text-small);
|
||||
font-size: var(--sl-text-small);
|
||||
background: rgba(10, 10, 15, 0.75);
|
||||
flex: none;
|
||||
min-width: 0;
|
||||
@@ -279,17 +279,17 @@
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.mly-viewer-tools .mly-seg {
|
||||
.sl-viewer-tools .sl-seg {
|
||||
background: rgba(10, 10, 15, 0.75);
|
||||
}
|
||||
|
||||
@container (max-width: 380px) {
|
||||
.mly-viewer-tools .mly-btn-text {
|
||||
.sl-viewer-tools .sl-btn-text {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.mly-viewer-x {
|
||||
.sl-viewer-x {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 50%;
|
||||
@@ -305,17 +305,17 @@
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
|
||||
.mly-viewer-x:hover {
|
||||
.sl-viewer-x:hover {
|
||||
color: #0a0a0f;
|
||||
background: #fff;
|
||||
border-color: #fff;
|
||||
}
|
||||
|
||||
.mly-image-meta {
|
||||
.sl-image-meta {
|
||||
flex: 0 0 auto;
|
||||
padding: 6px 10px;
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--mly-text-small);
|
||||
font-size: var(--sl-text-small);
|
||||
letter-spacing: 0.06em;
|
||||
color: var(--text-secondary);
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
@@ -326,7 +326,7 @@
|
||||
min-height: 26px;
|
||||
}
|
||||
|
||||
.mly-image-by {
|
||||
.sl-image-by {
|
||||
color: var(--text-primary);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@@ -334,35 +334,35 @@
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.mly-image-right {
|
||||
.sl-image-right {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.mly-image-link {
|
||||
color: var(--mly-green);
|
||||
.sl-image-link {
|
||||
color: var(--sl-green);
|
||||
text-decoration: none;
|
||||
letter-spacing: 0.1em;
|
||||
}
|
||||
|
||||
.mly-image-link[hidden] {
|
||||
.sl-image-link[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mly-image-link:hover {
|
||||
.sl-image-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.mly-meta {
|
||||
.sl-meta {
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--mly-text-small);
|
||||
font-size: var(--sl-text-small);
|
||||
letter-spacing: 0.06em;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.mly-meta:empty {
|
||||
.sl-meta:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -370,7 +370,7 @@
|
||||
/* 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
|
||||
above panels (≤139) and below the voice pill (150) and toasts (200). */
|
||||
.mly-viewer-wrap-expanded {
|
||||
.sl-viewer-wrap-expanded {
|
||||
position: fixed;
|
||||
left: 4vw;
|
||||
right: 4vw;
|
||||
@@ -385,28 +385,28 @@
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.mly-viewer-wrap-expanded .mly-viewer {
|
||||
.sl-viewer-wrap-expanded .sl-viewer {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
aspect-ratio: auto;
|
||||
}
|
||||
|
||||
.mly-viewer-wrap-expanded .mly-image-meta {
|
||||
.sl-viewer-wrap-expanded .sl-image-meta {
|
||||
font-size: 0.66rem;
|
||||
padding: 10px 14px;
|
||||
}
|
||||
|
||||
.mly-viewer-wrap-expanded .mly-viewer-tools {
|
||||
.sl-viewer-wrap-expanded .sl-viewer-tools {
|
||||
top: 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;
|
||||
right: 10px;
|
||||
}
|
||||
|
||||
.mly-viewer-wrap-expanded .mly-viewer-tools .scene-btn {
|
||||
.sl-viewer-wrap-expanded .sl-viewer-tools .scene-btn {
|
||||
padding: 8px 14px;
|
||||
font-size: 0.7rem;
|
||||
letter-spacing: 0.12em;
|
||||
@@ -415,12 +415,12 @@
|
||||
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';
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.mly-viewer-wrap-expanded .mly-viewer-x {
|
||||
.sl-viewer-wrap-expanded .sl-viewer-x {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
font-size: 1.5rem;
|
||||
@@ -428,12 +428,12 @@
|
||||
|
||||
/* ── Short viewports ─────────────────────────────────────────────────────── */
|
||||
@media (max-height: 640px) {
|
||||
.mapillary-panel-inner {
|
||||
.street-level-panel-inner {
|
||||
padding: 10px;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.mly-viewer {
|
||||
.sl-viewer {
|
||||
min-height: 150px;
|
||||
}
|
||||
}
|
||||
@@ -73,35 +73,35 @@
|
||||
|
||||
<!-- Street Level (Mapillary) Panel — moved into #right-context-rail at
|
||||
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="mapillary-panel-inner">
|
||||
<div class="street-level-panel-inner">
|
||||
<div class="panel-header">
|
||||
<!-- Mapillary mark: path from the MapillaryJS attribution component
|
||||
(mapillary-js, MIT, © Mapillary). Mapillary is a trademark of
|
||||
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-divider"></span>
|
||||
<span id="mly-status" class="mly-status" role="status" aria-live="polite">OFF</span>
|
||||
<button class="panel-collapse-btn" data-collapse-target="mapillary-panel" title="Expand panel">◀</button>
|
||||
<span id="sl-status" class="sl-status" role="status" aria-live="polite">OFF</span>
|
||||
<button class="panel-collapse-btn" data-collapse-target="street-level-panel" title="Expand panel">◀</button>
|
||||
</div>
|
||||
<div id="mly-body" class="mly-body">
|
||||
<fieldset id="mly-controls" class="mly-controls">
|
||||
<div id="sl-body" class="sl-body">
|
||||
<fieldset id="sl-controls" class="sl-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="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-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-look-btn" class="scene-btn" type="button" title="Open the nearest Mapillary photo to the view centre">OPEN NEAREST PHOTO</button>
|
||||
</div>
|
||||
<div id="mly-error" class="mly-error" role="alert" hidden><span aria-hidden="true">⚠</span> <span id="mly-error-text"></span></div>
|
||||
<div class="mly-filters" role="group" aria-label="Imagery filters">
|
||||
<div id="sl-error" class="sl-error" role="alert" hidden><span aria-hidden="true">⚠</span> <span id="sl-error-text"></span></div>
|
||||
<div class="sl-filters" role="group" aria-label="Imagery filters">
|
||||
<span class="cctv-summary-label">IMAGERY</span>
|
||||
<div class="mly-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="mly-seg-btn" data-mly-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>
|
||||
<div class="sl-seg" role="radiogroup" aria-label="Panorama filter">
|
||||
<button type="button" class="sl-seg-btn is-active" data-sl-pano="all" role="radio" aria-checked="true">ALL</button>
|
||||
<button type="button" class="sl-seg-btn" data-sl-pano="pano" role="radio" aria-checked="false">360°</button>
|
||||
<button type="button" class="sl-seg-btn" data-sl-pano="flat" role="radio" aria-checked="false">FLAT</button>
|
||||
</div>
|
||||
<label class="cctv-summary-label" for="mly-since">SINCE</label>
|
||||
<select id="mly-since" class="mly-select" aria-label="Show imagery captured since">
|
||||
<label class="cctv-summary-label" for="sl-since">SINCE</label>
|
||||
<select id="sl-since" class="sl-select" aria-label="Show imagery captured since">
|
||||
<option value="0">any date</option>
|
||||
<option value="365">last year</option>
|
||||
<option value="730">2 years</option>
|
||||
@@ -111,32 +111,32 @@
|
||||
<option value="year:2020">2020</option>
|
||||
</select>
|
||||
</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">
|
||||
<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 id="mly-viewer-wrap" class="mly-viewer-wrap" hidden>
|
||||
<div class="mly-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>
|
||||
<div class="mly-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="mly-seg-btn" data-mly-render="fill" role="radio" aria-checked="false" title="Fill the frame (cropped)">FILL</button>
|
||||
<div id="sl-viewer-wrap" class="sl-viewer-wrap" hidden>
|
||||
<div class="sl-viewer-tools">
|
||||
<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="sl-seg" role="radiogroup" aria-label="Image fit">
|
||||
<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="sl-seg-btn" data-sl-render="fill" role="radio" aria-checked="false" title="Fill the frame (cropped)">FILL</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mly-viewer-tools mly-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>
|
||||
<div class="sl-viewer-tools sl-viewer-tools-right">
|
||||
<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 id="mly-viewer-placeholder" class="mly-viewer-placeholder" hidden></div>
|
||||
<div id="mly-viewer" class="mly-viewer" aria-label="Mapillary street-level viewer"></div>
|
||||
<div id="mly-image-meta" class="mly-image-meta">
|
||||
<span id="mly-image-by" class="mly-image-by"></span>
|
||||
<span class="mly-image-right">
|
||||
<span id="mly-image-when" class="mly-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>
|
||||
<div id="sl-viewer-placeholder" class="sl-viewer-placeholder" hidden></div>
|
||||
<div id="sl-viewer" class="sl-viewer" aria-label="Mapillary street-level viewer"></div>
|
||||
<div id="sl-image-meta" class="sl-image-meta">
|
||||
<span id="sl-image-by" class="sl-image-by"></span>
|
||||
<span class="sl-image-right">
|
||||
<span id="sl-image-when" class="sl-image-when"></span>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
<div id="mly-coverage-meta" class="mly-meta"></div>
|
||||
<div id="sl-coverage-meta" class="sl-meta"></div>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
loaded lazily on first image open while its controls need styling as soon
|
||||
as that first image lands; ~9 KB gzipped. */
|
||||
@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/bhote-koshi.css';
|
||||
@import './src/ui/styles/recent-imagery.css';
|
||||
|
||||
Reference in new issue
Block a user