mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-06 03:14:00 +00:00
ui(mapillary): drop the keyless empty state
The panel no longer shows a separate keyless call-to-action; the fieldset gate and the layer-row tooltip already cover the missing-key case. The planner flag is tri-state (null until status answers). Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
1 parent
10a00873ca
commit
7f65a32877
11 files changed
+31
-60
No files matched your search
@@ -330,6 +330,13 @@ _The Space Missions layer replaying a Falcon 9 ascent — labeled `RECONSTRUCTED
|
|||||||
|
|
||||||
> Needs a free **Mapillary client token**; the plain-English query box additionally needs an **Anthropic key**. Both go in POWER UP.
|
> Needs a free **Mapillary client token**; the plain-English query box additionally needs an **Anthropic key**. Both go in POWER UP.
|
||||||
|
|
||||||
|
**Getting the keys**
|
||||||
|
|
||||||
|
1. **Mapillary client token (required, free).** Sign in at [mapillary.com/dashboard/developers](https://www.mapillary.com/dashboard/developers), choose **Register application**, give it any name and website, tick **Read** access, and copy the **Client Token** (it starts with `MLY|`). Paste it into **POWER UP → Mapillary**, or set `MAPILLARY_CLIENT_TOKEN` in `.env` and restart `npm run dev`.
|
||||||
|
2. **Anthropic key (optional).** Create one at [console.anthropic.com](https://console.anthropic.com/settings/keys) and add it in **POWER UP → Anthropic** or as `ANTHROPIC_API_KEY` in `.env`. It stays on the server; only the plain-English query box uses it.
|
||||||
|
|
||||||
|
Without the Mapillary token the STREET LEVEL panel reads **KEY REQUIRED** and its controls stay greyed out; without the Anthropic key everything except the query box works.
|
||||||
|
|
||||||
Turn on **Street Level (Mapillary)** under DATA LAYERS → Cameras, or open the STREET LEVEL strip in the right rail. Mapillary's coverage draws as ground-hugging lines (recent captures bright green, older ones dim, 360° panoramas magenta); click a line for its image cones and a cone to open the photo in the embedded MapillaryJS viewer. **EXPAND** grows the viewer to most of the screen and **CAMERA FOLLOWS VIEW** drives the globe camera from the panorama. With an Anthropic key, type a request such as _"stop signs in downtown Detroit"_ and the matching detections stream onto the globe tile by tile with Mapillary's own icons; click one to see the photo with the detection outlined. Mapillary blurs faces and licence plates before publishing imagery, and the globe credits "© Mapillary contributors, CC BY-SA 4.0" while the layer is on.
|
Turn on **Street Level (Mapillary)** under DATA LAYERS → Cameras, or open the STREET LEVEL strip in the right rail. Mapillary's coverage draws as ground-hugging lines (recent captures bright green, older ones dim, 360° panoramas magenta); click a line for its image cones and a cone to open the photo in the embedded MapillaryJS viewer. **EXPAND** grows the viewer to most of the screen and **CAMERA FOLLOWS VIEW** drives the globe camera from the panorama. With an Anthropic key, type a request such as _"stop signs in downtown Detroit"_ and the matching detections stream onto the globe tile by tile with Mapillary's own icons; click one to see the photo with the detection outlined. Mapillary blurs faces and licence plates before publishing imagery, and the globe credits "© Mapillary contributors, CC BY-SA 4.0" while the layer is on.
|
||||||
|
|
||||||
## 🎖️ Field Missions
|
## 🎖️ Field Missions
|
||||||
|
|||||||
@@ -2697,7 +2697,8 @@ its criteria cannot be silently ignored.
|
|||||||
|
|
||||||
Street Level (Mapillary) lives in the right context rail next to CCTV as an
|
Street Level (Mapillary) lives in the right context rail next to CCTV as an
|
||||||
ordinary collapsible panel and starts collapsed. Without `MAPILLARY_CLIENT_TOKEN`
|
ordinary collapsible panel and starts collapsed. Without `MAPILLARY_CLIENT_TOKEN`
|
||||||
the panel shows a gated empty state naming the variable; with it, coverage
|
the header status reads KEY REQUIRED and the controls stay disabled (key setup
|
||||||
|
is documented in the README's Street Level section); with it, coverage
|
||||||
draws as ground-clamped sequence lines (z0–5 overview points from orbit,
|
draws as ground-clamped sequence lines (z0–5 overview points from orbit,
|
||||||
z11–14 sequences below 60 km) and the proxy strips the unused `image` point
|
z11–14 sequences below 60 km) and the proxy strips the unused `image` point
|
||||||
layer from z14 tiles in transit (12 MB → ~80 KB). The plain-English query box
|
layer from z14 tiles in transit (12 MB → ~80 KB). The plain-English query box
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ async function main() {
|
|||||||
classes: [...el.classList],
|
classes: [...el.classList],
|
||||||
width: Math.round(box.width),
|
width: Math.round(box.width),
|
||||||
right: Math.round(box.right),
|
right: Math.round(box.right),
|
||||||
keyless: !document.getElementById('mly-keyless').hidden,
|
status: document.getElementById('mly-status').textContent,
|
||||||
controlsDisabled: document.getElementById('mly-controls').disabled,
|
controlsDisabled: document.getElementById('mly-controls').disabled,
|
||||||
bodyDisplay: getComputedStyle(document.getElementById('mly-body'))
|
bodyDisplay: getComputedStyle(document.getElementById('mly-body'))
|
||||||
.display,
|
.display,
|
||||||
@@ -152,7 +152,7 @@ async function main() {
|
|||||||
);
|
);
|
||||||
if (!status.configured) {
|
if (!status.configured) {
|
||||||
await step(
|
await step(
|
||||||
'keyless install gates the controls and names the token',
|
'keyless install gates the controls and reports KEY REQUIRED',
|
||||||
async () => {
|
async () => {
|
||||||
await page.evaluate(() =>
|
await page.evaluate(() =>
|
||||||
window.__godsEyeView.dataManager.setEnabled('mapillary', true, {
|
window.__godsEyeView.dataManager.setEnabled('mapillary', true, {
|
||||||
@@ -161,14 +161,8 @@ async function main() {
|
|||||||
);
|
);
|
||||||
await sleep(800);
|
await sleep(800);
|
||||||
const info = await panel();
|
const info = await panel();
|
||||||
assert.equal(info.keyless, true);
|
|
||||||
assert.equal(info.controlsDisabled, true);
|
assert.equal(info.controlsDisabled, true);
|
||||||
assert.match(
|
assert.equal(info.status, 'KEY REQUIRED');
|
||||||
await page.evaluate(
|
|
||||||
() => document.getElementById('mly-keyless').textContent,
|
|
||||||
),
|
|
||||||
/MAPILLARY_CLIENT_TOKEN/,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
console.log(
|
console.log(
|
||||||
@@ -217,7 +211,7 @@ async function main() {
|
|||||||
{ timeout: 90_000 },
|
{ timeout: 90_000 },
|
||||||
);
|
);
|
||||||
const info = await panel();
|
const info = await panel();
|
||||||
assert.equal(info.keyless, false);
|
assert.equal(info.controlsDisabled, false);
|
||||||
// Cesium paints on-screen credits a frame or two after they register.
|
// Cesium paints on-screen credits a frame or two after they register.
|
||||||
await page.waitForFunction(
|
await page.waitForFunction(
|
||||||
() => document.body.innerHTML.includes('Mapillary</a> contributors'),
|
() => document.body.innerHTML.includes('Mapillary</a> contributors'),
|
||||||
|
|||||||
@@ -180,7 +180,8 @@ export function createMapillaryLayer({ source, services = {} }) {
|
|||||||
return {
|
return {
|
||||||
enabled: state.enabled,
|
enabled: state.enabled,
|
||||||
keyRequired: state.keyRequired,
|
keyRequired: state.keyRequired,
|
||||||
planner: state.status?.planner === true,
|
// null until the status call answers (the layer initialises lazily).
|
||||||
|
planner: state.status ? state.status.planner === true : null,
|
||||||
plannerModel: state.status?.plannerModel || null,
|
plannerModel: state.status?.plannerModel || null,
|
||||||
coverage: {
|
coverage: {
|
||||||
zoom: state.coverage.zoom,
|
zoom: state.coverage.zoom,
|
||||||
|
|||||||
@@ -63,9 +63,9 @@ test('panel styles stay inside GEV conventions: no !important, no fixed panel',
|
|||||||
assert.deepEqual(fixed, ['.mly-viewer-wrap-expanded']);
|
assert.deepEqual(fixed, ['.mly-viewer-wrap-expanded']);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('keyless and no-planner states are real, gated UI rather than dead buttons', () => {
|
test('keyless and no-planner states gate the controls rather than leave dead buttons', () => {
|
||||||
assert.match(html, /id="mly-keyless"[^>]*hidden/);
|
// The key requirement is documented (README, .env.example), not repeated in the panel.
|
||||||
assert.match(html, /MAPILLARY_CLIENT_TOKEN/);
|
assert.doesNotMatch(html, /mly-keyless/);
|
||||||
assert.match(html, /<fieldset id="mly-controls"/);
|
assert.match(html, /<fieldset id="mly-controls"/);
|
||||||
assert.match(html, /<ul id="mly-legend"/);
|
assert.match(html, /<ul id="mly-legend"/);
|
||||||
assert.match(html, /id="mly-error"[^>]*role="alert"/);
|
assert.match(html, /id="mly-error"[^>]*role="alert"/);
|
||||||
|
|||||||
@@ -900,7 +900,6 @@ export class StyleManager extends ShellFacade {
|
|||||||
}),
|
}),
|
||||||
setPanelCollapsed: (collapsed, options) =>
|
setPanelCollapsed: (collapsed, options) =>
|
||||||
this.setPanelCollapsed('mapillary-panel', collapsed, options),
|
this.setPanelCollapsed('mapillary-panel', collapsed, options),
|
||||||
openKeySetup: () => document.getElementById('key-setup-chip')?.click(),
|
|
||||||
showToast: (message) => this._showToast(message),
|
showToast: (message) => this._showToast(message),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -36,8 +36,6 @@ export class MapillaryControls {
|
|||||||
const byId = (id) => this.root?.querySelector(`#${id}`) || null;
|
const byId = (id) => this.root?.querySelector(`#${id}`) || null;
|
||||||
return {
|
return {
|
||||||
status: byId('mly-status'),
|
status: byId('mly-status'),
|
||||||
keyless: byId('mly-keyless'),
|
|
||||||
keylessBtn: byId('mly-keyless-btn'),
|
|
||||||
controls: byId('mly-controls'),
|
controls: byId('mly-controls'),
|
||||||
enableBtn: byId('mly-enable-btn'),
|
enableBtn: byId('mly-enable-btn'),
|
||||||
lookBtn: byId('mly-look-btn'),
|
lookBtn: byId('mly-look-btn'),
|
||||||
@@ -82,7 +80,6 @@ export class MapillaryControls {
|
|||||||
if (!this.root) return;
|
if (!this.root) return;
|
||||||
this.mapillary.attachViewerHost?.(el.viewer);
|
this.mapillary.attachViewerHost?.(el.viewer);
|
||||||
|
|
||||||
this.listen(el.keylessBtn, 'click', () => this.actions.openKeySetup?.());
|
|
||||||
this.listen(el.enableBtn, 'click', () => this._toggleEnabled());
|
this.listen(el.enableBtn, 'click', () => this._toggleEnabled());
|
||||||
this.listen(el.lookBtn, 'click', async () => {
|
this.listen(el.lookBtn, 'click', async () => {
|
||||||
if (!(await this._ensureEnabled())) return;
|
if (!(await this._ensureEnabled())) return;
|
||||||
@@ -306,7 +303,7 @@ export class MapillaryControls {
|
|||||||
const view = presentMapillaryPanel(state);
|
const view = presentMapillaryPanel(state);
|
||||||
this._view = view;
|
this._view = view;
|
||||||
this._renderHeader(view);
|
this._renderHeader(view);
|
||||||
this._renderEmptyState(view);
|
this._renderGate(view);
|
||||||
this._renderQuery(view);
|
this._renderQuery(view);
|
||||||
this._renderResults(view);
|
this._renderResults(view);
|
||||||
this._renderFilters(view);
|
this._renderFilters(view);
|
||||||
@@ -331,9 +328,8 @@ export class MapillaryControls {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_renderEmptyState(view) {
|
_renderGate(view) {
|
||||||
const el = this._elements;
|
const el = this._elements;
|
||||||
if (el.keyless) el.keyless.hidden = !view.keyless.visible;
|
|
||||||
if (el.controls) el.controls.disabled = view.controlsDisabled;
|
if (el.controls) el.controls.disabled = view.controlsDisabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -153,7 +153,9 @@ function presentMeta(state) {
|
|||||||
export function presentMapillaryPanel(state) {
|
export function presentMapillaryPanel(state) {
|
||||||
const enabled = state.enabled === true;
|
const enabled = state.enabled === true;
|
||||||
const keyRequired = state.keyRequired === true;
|
const keyRequired = state.keyRequired === true;
|
||||||
const planner = state.planner === true;
|
// Unknown (status not fetched yet) reads as available: the server answers
|
||||||
|
// a missing key with its own error, and the panel must not claim one is missing.
|
||||||
|
const planner = state.planner !== false;
|
||||||
const busy = state.query.busy === true;
|
const busy = state.query.busy === true;
|
||||||
const error = state.query.error || state.street.error || null;
|
const error = state.query.error || state.street.error || null;
|
||||||
let answer = state.query.answer || '';
|
let answer = state.query.answer || '';
|
||||||
@@ -167,10 +169,6 @@ export function presentMapillaryPanel(state) {
|
|||||||
enabled,
|
enabled,
|
||||||
keyRequired,
|
keyRequired,
|
||||||
status: presentStatus(state),
|
status: presentStatus(state),
|
||||||
keyless: {
|
|
||||||
visible: keyRequired,
|
|
||||||
text: 'Street Level needs a free Mapillary client token (MAPILLARY_CLIENT_TOKEN). Coverage, photos and queries stay off until one is added.',
|
|
||||||
},
|
|
||||||
controlsDisabled: keyRequired,
|
controlsDisabled: keyRequired,
|
||||||
enableButton: {
|
enableButton: {
|
||||||
text: enabled ? 'STREET LEVEL ON' : 'STREET LEVEL OFF',
|
text: enabled ? 'STREET LEVEL ON' : 'STREET LEVEL OFF',
|
||||||
|
|||||||
@@ -86,10 +86,8 @@ function deepMerge(target, source) {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
test('a missing Mapillary token gates every control and names the variable', () => {
|
test('a missing Mapillary token gates every control and flags KEY REQUIRED', () => {
|
||||||
const view = presentMapillaryPanel(snapshot({ keyRequired: true }));
|
const view = presentMapillaryPanel(snapshot({ keyRequired: true }));
|
||||||
assert.equal(view.keyless.visible, true);
|
|
||||||
assert.match(view.keyless.text, /MAPILLARY_CLIENT_TOKEN/);
|
|
||||||
assert.equal(view.controlsDisabled, true);
|
assert.equal(view.controlsDisabled, true);
|
||||||
assert.deepEqual(view.status, { text: 'KEY REQUIRED', tone: 'warn' });
|
assert.deepEqual(view.status, { text: 'KEY REQUIRED', tone: 'warn' });
|
||||||
});
|
});
|
||||||
@@ -103,10 +101,16 @@ test('without an Anthropic key the query box explains itself and stays disabled'
|
|||||||
assert.equal(view.query.hintWarn, true);
|
assert.equal(view.query.hintWarn, true);
|
||||||
assert.match(view.query.hint, /Anthropic key/);
|
assert.match(view.query.hint, /Anthropic key/);
|
||||||
assert.ok(view.query.placeholder.length < 40, 'placeholder fits the field');
|
assert.ok(view.query.placeholder.length < 40, 'placeholder fits the field');
|
||||||
assert.equal(view.keyless.visible, false);
|
|
||||||
assert.equal(view.controlsDisabled, false);
|
assert.equal(view.controlsDisabled, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('before the status call answers the query box does not claim a missing key', () => {
|
||||||
|
const view = presentMapillaryPanel(snapshot({ planner: null }));
|
||||||
|
assert.equal(view.query.inputDisabled, false);
|
||||||
|
assert.equal(view.query.hintWarn, false);
|
||||||
|
assert.doesNotMatch(view.query.placeholder, /Anthropic/);
|
||||||
|
});
|
||||||
|
|
||||||
test('a running query turns ASK into STOP and shows indeterminate progress per stage', () => {
|
test('a running query turns ASK into STOP and shows indeterminate progress per stage', () => {
|
||||||
const planning = presentMapillaryPanel(
|
const planning = presentMapillaryPanel(
|
||||||
snapshot({ enabled: true, query: { busy: true, stage: 'planning' } }),
|
snapshot({ enabled: true, query: { busy: true, stage: 'planning' } }),
|
||||||
|
|||||||
@@ -110,31 +110,6 @@
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Keyless empty state ─────────────────────────────────────────────────── */
|
|
||||||
.mly-keyless {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 8px;
|
|
||||||
padding: 10px 12px;
|
|
||||||
border: 1px solid rgba(255, 179, 0, 0.45);
|
|
||||||
border-radius: 9px;
|
|
||||||
background: rgba(255, 179, 0, 0.06);
|
|
||||||
}
|
|
||||||
|
|
||||||
.mly-keyless-text {
|
|
||||||
margin: 0;
|
|
||||||
font-family: var(--font-sans);
|
|
||||||
font-size: 0.72rem;
|
|
||||||
line-height: 1.4;
|
|
||||||
color: var(--text-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.mly-keyless-text code {
|
|
||||||
font-family: var(--font-mono);
|
|
||||||
font-size: 0.66rem;
|
|
||||||
color: #ffb300;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Query ───────────────────────────────────────────────────────────────── */
|
/* ── Query ───────────────────────────────────────────────────────────────── */
|
||||||
.mly-query {
|
.mly-query {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -85,10 +85,6 @@
|
|||||||
<button class="panel-collapse-btn" data-collapse-target="mapillary-panel" title="Expand panel">◀</button>
|
<button class="panel-collapse-btn" data-collapse-target="mapillary-panel" title="Expand panel">◀</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="mly-body" class="mly-body">
|
<div id="mly-body" class="mly-body">
|
||||||
<div id="mly-keyless" class="mly-keyless" hidden>
|
|
||||||
<p class="mly-keyless-text">Street Level needs a free Mapillary client token (<code>MAPILLARY_CLIENT_TOKEN</code>). Coverage, photos and queries stay off until one is added.</p>
|
|
||||||
<button id="mly-keyless-btn" class="scene-btn" type="button">ADD KEY IN POWER UP</button>
|
|
||||||
</div>
|
|
||||||
<fieldset id="mly-controls" class="mly-controls">
|
<fieldset id="mly-controls" class="mly-controls">
|
||||||
<div class="cctv-controls">
|
<div class="cctv-controls">
|
||||||
<button id="mly-enable-btn" class="scene-btn" type="button" aria-pressed="false" title="Turn the Street Level layer on or off">STREET LEVEL OFF</button>
|
<button id="mly-enable-btn" class="scene-btn" type="button" aria-pressed="false" title="Turn the Street Level layer on or off">STREET LEVEL OFF</button>
|
||||||
|
|||||||
Reference in new issue
Block a user