mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 18:34:02 +00:00
feat(street-level): provider contract with Mapillary as the first provider
Street Level is now a provider-neutral layer modelled on the iD editor's photo overlay. The core owns what every imagery provider shares: the enable state and a switch per provider, the imagery filter (360°/flat and captured-since in relative days), one click handler routed by pick prefix, one viewer host that mounts whichever provider's viewer adapter owns the open image, the position marker, camera follow, one on-globe credit per active provider, and share-link parameters (one bit per provider plus the filter). Providers implement the contract in registry.js: coverage, sequences, nearest-image lookup and a viewer adapter emitting a common pose. Mapillary is the only registered provider; Google Street View, KartaView and Panoramax slot in beside it. Panel: a PROVIDERS chip row (one chip per registered provider, keyless chips read as errors), relative-day SINCE options, a provider-neutral caption whose link label follows the provider, and no vendor mark in the header. The panel inner is a rail scroller like CCTV's. Tests cover the registry, params codec, filter, pick router, UI-state composition, the provider definition and coverage-tile decoding; the QA harness switches the provider off and on and checks the filter. Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
1 parent
2dbc684fb1
commit
28e6a1faa7
41 files changed
+2460
-744
No files matched your search
@@ -848,6 +848,18 @@
|
||||
"src/layers/streetLevel/providers/mapillary/source.test.mjs",
|
||||
"src/streetLevelMarkup.test.mjs",
|
||||
"src/layers/streetLevel/index.js",
|
||||
"src/layers/streetLevel/policy.js",
|
||||
"src/layers/streetLevel/filter.js",
|
||||
"src/layers/streetLevel/registry.js",
|
||||
"src/layers/streetLevel/params.js",
|
||||
"src/layers/streetLevel/pickRouter.js",
|
||||
"src/layers/streetLevel/uiState.js",
|
||||
"src/layers/streetLevel/view.js",
|
||||
"src/layers/streetLevel/marker.js",
|
||||
"src/layers/streetLevel/cameraFollow.js",
|
||||
"src/layers/streetLevel/credits.js",
|
||||
"src/layers/streetLevel/viewerHost.js",
|
||||
"src/layers/streetLevel/providers/mapillary/index.js",
|
||||
"src/layers/streetLevel/state.js",
|
||||
"src/layers/streetLevel/providers/mapillary/policy.js",
|
||||
"src/layers/streetLevel/providers/mapillary/coverage.js",
|
||||
@@ -865,5 +877,12 @@
|
||||
"server/providers/mapillary/trim.js",
|
||||
"src/layers/streetLevel/tileMath.test.mjs",
|
||||
"scripts/qa-street-level.mjs",
|
||||
"src/tooling/streetLevelQa.test.mjs"
|
||||
"src/tooling/streetLevelQa.test.mjs",
|
||||
"src/layers/streetLevel/filter.test.mjs",
|
||||
"src/layers/streetLevel/registry.test.mjs",
|
||||
"src/layers/streetLevel/params.test.mjs",
|
||||
"src/layers/streetLevel/pickRouter.test.mjs",
|
||||
"src/layers/streetLevel/uiState.test.mjs",
|
||||
"src/layers/streetLevel/providers/mapillary/provider.test.mjs",
|
||||
"src/layers/streetLevel/providers/mapillary/decode.test.mjs"
|
||||
]
|
||||
@@ -1117,10 +1117,23 @@
|
||||
"street-level-layer": {
|
||||
"exports": [
|
||||
"./layers/street-level",
|
||||
"./layers/street-level/mapillary",
|
||||
"./layers/street-level/mapillary/source"
|
||||
],
|
||||
"modules": [
|
||||
"src/layers/streetLevel/index.js",
|
||||
"src/layers/streetLevel/providers/mapillary/index.js",
|
||||
"src/layers/streetLevel/viewerHost.js",
|
||||
"src/layers/streetLevel/credits.js",
|
||||
"src/layers/streetLevel/cameraFollow.js",
|
||||
"src/layers/streetLevel/marker.js",
|
||||
"src/layers/streetLevel/view.js",
|
||||
"src/layers/streetLevel/uiState.js",
|
||||
"src/layers/streetLevel/pickRouter.js",
|
||||
"src/layers/streetLevel/params.js",
|
||||
"src/layers/streetLevel/registry.js",
|
||||
"src/layers/streetLevel/filter.js",
|
||||
"src/layers/streetLevel/policy.js",
|
||||
"src/layers/streetLevel/state.js",
|
||||
"src/layers/streetLevel/providers/mapillary/policy.js",
|
||||
"src/layers/streetLevel/providers/mapillary/coverage.js",
|
||||
@@ -1998,6 +2011,18 @@
|
||||
"src/ui/streetLevelControls.js",
|
||||
"src/ui/streetLevelPresentation.js",
|
||||
"src/layers/streetLevel/index.js",
|
||||
"src/layers/streetLevel/providers/mapillary/index.js",
|
||||
"src/layers/streetLevel/viewerHost.js",
|
||||
"src/layers/streetLevel/credits.js",
|
||||
"src/layers/streetLevel/cameraFollow.js",
|
||||
"src/layers/streetLevel/marker.js",
|
||||
"src/layers/streetLevel/view.js",
|
||||
"src/layers/streetLevel/uiState.js",
|
||||
"src/layers/streetLevel/pickRouter.js",
|
||||
"src/layers/streetLevel/params.js",
|
||||
"src/layers/streetLevel/registry.js",
|
||||
"src/layers/streetLevel/filter.js",
|
||||
"src/layers/streetLevel/policy.js",
|
||||
"src/layers/streetLevel/state.js",
|
||||
"src/layers/streetLevel/providers/mapillary/policy.js",
|
||||
"src/layers/streetLevel/providers/mapillary/coverage.js",
|
||||
@@ -2471,6 +2496,18 @@
|
||||
"src/layers/recentImagery/thumbnails.js",
|
||||
"src/maps/imageryHost.js",
|
||||
"src/layers/streetLevel/index.js",
|
||||
"src/layers/streetLevel/providers/mapillary/index.js",
|
||||
"src/layers/streetLevel/viewerHost.js",
|
||||
"src/layers/streetLevel/credits.js",
|
||||
"src/layers/streetLevel/cameraFollow.js",
|
||||
"src/layers/streetLevel/marker.js",
|
||||
"src/layers/streetLevel/view.js",
|
||||
"src/layers/streetLevel/uiState.js",
|
||||
"src/layers/streetLevel/pickRouter.js",
|
||||
"src/layers/streetLevel/params.js",
|
||||
"src/layers/streetLevel/registry.js",
|
||||
"src/layers/streetLevel/filter.js",
|
||||
"src/layers/streetLevel/policy.js",
|
||||
"src/layers/streetLevel/state.js",
|
||||
"src/layers/streetLevel/providers/mapillary/policy.js",
|
||||
"src/layers/streetLevel/providers/mapillary/coverage.js",
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* 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:street-level -- --url http://localhost:4173`.
|
||||
* server: the panel's place in the right rail, the provider chips, the
|
||||
* keyless gate, coverage and its credit, switching a provider off and on,
|
||||
* the imagery filter, 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';
|
||||
@@ -14,6 +15,9 @@ export const VIEWPORTS = Object.freeze([
|
||||
{ width: 1280, height: 800 },
|
||||
]);
|
||||
|
||||
/** Provider chips the panel must show, in order (one per registered provider). */
|
||||
export const EXPECTED_PROVIDERS = Object.freeze(['mapillary']);
|
||||
|
||||
/** Expected right-rail order once the layout controller has run. */
|
||||
export const RAIL_ORDER = Object.freeze([
|
||||
'pp-toggles',
|
||||
@@ -119,11 +123,19 @@ async function main() {
|
||||
);
|
||||
await sleep(500);
|
||||
await step(
|
||||
'expanding the strip shows the body and the four-swatch legend',
|
||||
'expanding the strip shows the body, one chip per provider and the legend',
|
||||
async () => {
|
||||
const info = await panel();
|
||||
assert.ok(!isCollapsed(info.classes));
|
||||
assert.equal(info.bodyDisplay, 'flex');
|
||||
const chips = await page.evaluate(() =>
|
||||
[
|
||||
...document.querySelectorAll(
|
||||
'#sl-provider-chips .data-toggle-chip',
|
||||
),
|
||||
].map((chip) => chip.dataset.chipId),
|
||||
);
|
||||
assert.deepEqual(chips, EXPECTED_PROVIDERS);
|
||||
assert.equal(
|
||||
await page.evaluate(
|
||||
() => document.querySelectorAll('#sl-legend li').length,
|
||||
@@ -145,6 +157,15 @@ async function main() {
|
||||
const info = await panel();
|
||||
assert.equal(info.controlsDisabled, true);
|
||||
assert.equal(info.status, 'KEY REQUIRED');
|
||||
assert.equal(
|
||||
await page.evaluate(() =>
|
||||
document
|
||||
.querySelector('#sl-provider-chips [data-chip-id="mapillary"]')
|
||||
.classList.contains('chip-error'),
|
||||
),
|
||||
true,
|
||||
'the keyless provider chip reads as an error',
|
||||
);
|
||||
},
|
||||
);
|
||||
console.log(
|
||||
@@ -188,7 +209,7 @@ async function main() {
|
||||
const u = window.__godsEyeView.dataManager.layers
|
||||
.get('street-level')
|
||||
.module.getUIState();
|
||||
return u.coverage.sequences > 0 && !u.coverage.loading;
|
||||
return u.coverage.count > 0 && !u.coverage.loading;
|
||||
},
|
||||
{ timeout: 90_000 },
|
||||
);
|
||||
@@ -201,14 +222,76 @@ async function main() {
|
||||
);
|
||||
},
|
||||
);
|
||||
const ui = () =>
|
||||
page.evaluate(() =>
|
||||
window.__godsEyeView.dataManager.layers
|
||||
.get('street-level')
|
||||
.module.getUIState(),
|
||||
);
|
||||
await step(
|
||||
'switching the provider chip off clears its coverage and credit',
|
||||
async () => {
|
||||
await page.click('#sl-provider-chips [data-chip-id="mapillary"]');
|
||||
await page.waitForFunction(
|
||||
() =>
|
||||
window.__godsEyeView.dataManager.layers
|
||||
.get('street-level')
|
||||
.module.getUIState().coverage.count === 0,
|
||||
{ timeout: 15_000 },
|
||||
);
|
||||
const state = await ui();
|
||||
assert.equal(state.providers[0].on, false);
|
||||
assert.deepEqual(state.legend, [], 'no active provider, no legend');
|
||||
await page.waitForFunction(
|
||||
() => !document.body.innerHTML.includes('Mapillary</a> contributors'),
|
||||
{ timeout: 15_000 },
|
||||
);
|
||||
assert.deepEqual(
|
||||
await page.evaluate(() =>
|
||||
window.__godsEyeView.dataManager.layers
|
||||
.get('street-level')
|
||||
.module.getParams(),
|
||||
),
|
||||
{ mapillary: false, pano: 'all', sinceDays: 0 },
|
||||
);
|
||||
},
|
||||
);
|
||||
await step('switching it back on restores coverage', async () => {
|
||||
await page.click('#sl-provider-chips [data-chip-id="mapillary"]');
|
||||
await page.waitForFunction(
|
||||
() => {
|
||||
const u = window.__godsEyeView.dataManager.layers
|
||||
.get('street-level')
|
||||
.module.getUIState();
|
||||
return u.coverage.count > 0 && !u.coverage.loading;
|
||||
},
|
||||
{ timeout: 90_000 },
|
||||
);
|
||||
assert.equal((await ui()).providers[0].on, true);
|
||||
});
|
||||
await step(
|
||||
'the 360° filter keeps at most the unfiltered sequence count',
|
||||
async () => {
|
||||
const before = (await ui()).coverage.count;
|
||||
await page.click('[data-sl-pano="pano"]');
|
||||
await sleep(600);
|
||||
const after = (await ui()).coverage.count;
|
||||
assert.ok(after <= before, `${after} ≤ ${before}`);
|
||||
assert.equal((await ui()).filter.pano, 'pano');
|
||||
await page.click('[data-sl-pano="all"]');
|
||||
await sleep(600);
|
||||
assert.equal((await ui()).coverage.count, before);
|
||||
},
|
||||
);
|
||||
await step(
|
||||
'opening the nearest image shows the viewer with a caption',
|
||||
async () => {
|
||||
await page.evaluate(() =>
|
||||
window.__godsEyeView.dataManager.layers
|
||||
// Fire and forget: the open can outlive one CDP call, so poll instead.
|
||||
await page.evaluate(() => {
|
||||
void window.__godsEyeView.dataManager.layers
|
||||
.get('street-level')
|
||||
.module.openNearest(),
|
||||
);
|
||||
.module.openNearest();
|
||||
});
|
||||
await page.waitForFunction(
|
||||
() => {
|
||||
const s = window.__godsEyeView.dataManager.layers
|
||||
@@ -254,6 +337,8 @@ async function main() {
|
||||
const view = await page.evaluate(() => ({
|
||||
hidden: document.getElementById('sl-viewer-wrap').hidden,
|
||||
when: document.getElementById('sl-image-when').textContent.trim(),
|
||||
link: document.getElementById('sl-image-link').textContent.trim(),
|
||||
href: document.getElementById('sl-image-link').href,
|
||||
width: Math.round(
|
||||
document.getElementById('sl-viewer').getBoundingClientRect().width,
|
||||
),
|
||||
@@ -261,6 +346,9 @@ async function main() {
|
||||
assert.equal(view.hidden, false);
|
||||
assert.ok(view.width > 200);
|
||||
assert.ok(view.when.length > 0, 'caption shows the capture date');
|
||||
assert.equal(street.providerId, 'mapillary');
|
||||
assert.equal(view.link, 'MAPILLARY ↗');
|
||||
assert.match(view.href, /mapillary\.com\/app\/\?pKey=/);
|
||||
},
|
||||
);
|
||||
await step(
|
||||
|
||||
Reference in new issue
Block a user