mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 02:44: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
+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>
|
||||
|
||||
Reference in new issue
Block a user