Street Level: shorter comments and docs

- Comments in the action, the layer, embed, the Mapillary policy and the
  controls say why in fewer words; restatements of the code are gone.
- CURRENT-STATE's "Show me X on Street View" section is a list by topic and
  no longer says the browser gate aims at coverageLines.
- TOOLS.md's street-level view paragraph is shorter.
This commit is contained in:
German Khokhlov committed 2026-11-10 23:54:16 +04:00
1 parent f3996da0af
commit e34b5103c6
7 files changed
+167 -215

No files matched your search

+97 -105
View File
@@ -5073,111 +5073,103 @@ core's lookups alone. The MCP globe panel allows the meta-catalogue and the
three biggest instances; a picture on another instance shows only in the
app.
**"Show me X on Street View."** The `show_street_level` assistant action
(voice and typed; `src/voice/gevActions.js`) is one call: it checks the
source has its key (a keyless source explains itself and changes nothing, as
a chip does), flies to the place at street framing and waits for arrival (a
name, coordinates, a pointed spot, a numbered item, or nothing for the
ground under the screen centre, as OPEN uses: `aim: 'screen-centre'` with
`armed: false`, so sky refuses and a link's armed point is not used and is
kept until a photo opens), switches to the map the source needs (Google Street View: Google 3D),
lights that source through `setLayerParams` with origin `voice` (one source at
a time, kept in the share link like a chip click; a link runs the action with
origin `programmatic`, for the source switch and the layer, so it never writes
the recipient's saved layer state), turns the layer on and
calls `openNearest`. `provider` is `google` for "Street View", `mapillary`,
`kartaview` or `panoramax` by name, else the lit source. Stages fail separately (`location`, `view`, `map`,
`provider`, `layer`, `key`, `imagery`; `view` when cockpit, clean view,
recording or scene playback hides the panel, checked after the flight and
before anything switches or loads; embed mode exempts its own clean-view
class because the photo is lifted above the globe; an empty `imagery` result names a
narrowing 360°/flat or SINCE filter the source honours, as `filter`;
`noImagery` marks an empty result, `imageryError` a source or viewer failure
and `aim: 'sky'` a sky centre, from the layer's `street.nearestOutcome`) and a
newer turn retires the lookup
(`openNearest`'s `signal` option), so nothing opens after the user moved on.
For a source without a coverage layer the lookup widens to 250 m
(`NEAREST_RADIUS_MAX_M`; Google honours it, Mapillary's API caps at 50 m);
clicks keep 50 m. Mapillary's search gives up after 12 s, body included
(`NEAREST_TIMEOUT_MS`), and asks for 100 photos, the API's most: it
returns the radius's photos in no particular order and cuts them at the limit
first, so the distance sort and the imagery filter run over the spot's photos.
On a phone the panel sits in the Cameras tab sheet: a photo that opens, and a
link that arms, open that sheet and scroll the panel into view
(`revealPanel`, which does nothing above 720px, scrolling included); every
photo the action opens reveals the panel again, also one the user had
collapsed. The layer exposes `requiredMapStack(id)` and
`providerStatus(id)` for callers that must decide before the panel lists a
source, and `coverageLines(id)` (the lines a provider draws now, current and
stale tiles, as `[lon, lat]` parts) for diagnostics and the browser gate,
which aims its line clicks at that geometry, waits for the asynchronous
ground primitives to be ready, and clicks in the same JavaScript turn as the
pick so a rebuilt frame cannot come between them. Views carry the same request as `street_level` (`src/view/index.js`:
source and point, defaulting to where the camera looks; a `google` source
brings `map: photoreal` and refuses another map; not with `follow`), links as
`sl=<source>,<lat>,<lon>`; `show_in_gods_eye_view` accepts it, the globe panel
applies it through the action, and a plain link runs the action once restored
(one shot: the rewritten hash drops `sl`). A link pasted into the open tab
runs it after its restore settles (`gev:share-navigation-restore-settled`),
and only when the restore's camera landed (`share.camera === 'applied'`): a
link whose restore flight the visitor stopped with a drag or a search, or
whose restore a newer link superseded, never runs and never pulls the camera
back. In embed mode the rail holding the
panel is hidden, so an open photo moves to an overlay on the globe
(`sl-viewer-wrap-embed`) and back when it closes; Esc closes it (in full
screen, Esc leaves full screen first). EXPAND stays open under embed mode's
own clean view; cockpit, recording and scene playback hide the overlay. A
posted view whose photo step fails before the imagery stage closes the
earlier photo before its camera flies; one the user overtook (a click on the
map, a newer request) leaves their photo and camera alone, and one that
found a photo it could not load keeps that error in view. The action's
lookup replaces an open photo in place (`openNearest` `replace: true`), so
the viewer and EXPAND stay up, and closes it only when it opens nothing; once
an image is found, `proceed()` asks whether the camera and the panel are
still the request's before a billed photo loads. Image-loading failures render inside
the embedded overlay, since the panel's error region is hidden with the rail.
A caller's abort is
honoured through the viewer's mount and load, not just the lookup, and only
the lookup that still owns the panel may change its spinner or error. Cancelling
a lookup clears its loading state even when an existing photo stays open;
internally retired lookups also release the caller's abort listener. A
passive link's photos stand down if anyone navigated (a gesture, a search,
the assistant) while the link waited behind its own marks. The action itself
checks again after every await (flight, map switch, layer on, lookup): a
newer turn, a shell navigation stamp (`_navigationGeneration`, re-taken where
its own flight lands) or a press or wheel on the globe stops the remaining
stages (`superseded: true`), and cockpit, clean view, recording or scene
playback entered meanwhile ends it at the `view` stage. During the lookup the
same checks run before a found photo loads (`proceed`), so nothing billed
loads for a request that was given up, and a lookup given up leaves the
panel as it was; a photo that opened after either is closed again. The
hidden-panel check also runs before the flight, so a recording's camera never
moves for it. A photo already open stays up while the new request looks (the
viewer is replaced, not closed, so EXPAND stays, also when the request
switches the source) and closes if the request opens nothing, so a place with
no imagery shows its own error, not the previous place's photo; a link that
only arms closes it at once. A plain
link never loads a photo from a source that bills every load
(`billedPerImage` on the provider; Google): the action runs with
`autoOpen: false`, arms the lookup on the layer (`armNearest`: the link's
point and radius, consumed only by the next OPEN press (`aim:
'screen-centre'`), and only while the ground under the screen centre is within
1 km of the point (`ARMED_REACH_M`; a user who moved on opens what the view
shows) or cannot be picked; a sky centre refuses; any other lookup, open, close, disable, its source
switched off or its map leaving drops it), opens the panel and a toast
points at OPEN GOOGLE STREET VIEW. Only the panel page's own link (inline
embed) for a tool result issued in the last 5 minutes, and posted views, open
the photo: `show_in_gods_eye_view` stamps a street-level result `issued_at`,
and a host that mounts the panel again later hands back the old result, which
only arms. The panel checks every result, not just the first: each `gev:view`
it posts carries `fresh`, and the inline app applies a view without it with
`autoOpen: false` (a framing page's posted views still open); `?embed=1` on a published link only
arms, without the panel nudge or toast (the rail is hidden in a frame). A link that follows an entity ignores `sl`.
The look-at default applies below 20 km, at a pitch steeper than -5° and
within 10 km ahead; otherwise the camera's own point is used. A view's
altitude is above the ellipsoid, so `show_in_gods_eye_view` measures it from
the terrain under the camera (`services.terrain`) before taking the look-at
point; without a terrain answer it keeps the view's own estimate.
**"Show me X on Street View."** `show_street_level` (voice and typed;
`src/voice/gevActions.js`) is one call whose stages fail separately
(`location`, `view`, `map`, `provider`, `layer`, `key`, `imagery`):
- **Key first.** A keyless source explains itself and changes nothing, as a
chip does.
- **Place.** A name, coordinates, a pointed spot or a numbered item: it flies
there at street framing and waits for arrival. With no place it looks at the
ground under the screen centre, as OPEN does (`aim: 'screen-centre'`,
`armed: false`: sky refuses, and a link's armed point is not used and is kept
until a photo opens). Coordinates off the globe are refused.
- **Map and source.** It switches to the map the source needs (Google Street
View: Google 3D), lights the source through `setLayerParams`, one at a time
and kept like a chip click (origin `voice`; a link runs the action with
origin `programmatic`, so it never writes the recipient's saved layer state),
turns the layer on and calls `openNearest`. `provider` is `google` for
"Street View", `mapillary`, `kartaview` or `panoramax` by name, else the lit
source.
- **Hidden panel.** Cockpit, clean view, recording or scene playback end it at
`view`, before the flight and after every await; embed mode's own clean view
is exempt, since the photo is lifted over the globe.
- **Superseded.** After every await (flight, map switch, layer on, lookup) a
newer turn, a shell navigation stamp (`_navigationGeneration`, re-taken where
its own flight lands) or a press or wheel on the globe stops it
(`superseded: true`). The same checks run before a found photo loads
(`proceed`), so nothing billed loads for a request that was given up, and a
lookup given up leaves the panel as it was; a photo that opened after either
is closed again.
- **Replacing a photo.** An open photo stays up while the new request looks
(`openNearest` `replace: true`; the viewer is replaced, not closed, so EXPAND
stays, also across sources through `holdViewer`) and closes if nothing
opens, so a place with no imagery shows its own error; a link that only arms
closes it at once. A replacement stops a playing Mapillary sequence.
- **Outcome.** `noImagery` marks an empty result, `imageryError` a source or
viewer failure and `aim: 'sky'` a sky centre (from `street.nearestOutcome`);
an empty result names a narrowing 360°/flat or SINCE filter the source
honours, as `filter`. Every photo the action opens reveals the panel, also
one the user had collapsed.
- **Search.** A source without a coverage layer searches up to 250 m
(`NEAREST_RADIUS_MAX_M`; Google honours it); clicks keep 50 m. Mapillary
asks for 100 photos within 50 m, its API's most (the API returns them
unordered and cuts at the limit), and gives up after 12 s, body included
(`NEAREST_TIMEOUT_MS`).
- **Phone.** The panel sits in the Cameras tab sheet: a photo that opens, or a
link that arms, opens the sheet and scrolls the panel into view
(`revealPanel`, which does nothing above 720px).
The layer also exposes `requiredMapStack(id)` and `providerStatus(id)` for
callers that decide before the panel lists a source, `holdViewer()`, and
`coverageLines(id)` (the lines a provider draws now, as `[lon, lat]` parts)
for diagnostics. A caller's abort is honoured through the viewer's mount and
load, and only the lookup that still owns the panel changes its spinner or
error.
Views carry the request as `street_level` (`src/view/index.js`): a source and
a point, which defaults to where the camera looks (below 20 km, at a pitch
steeper than -5° and within 10 km ahead; else the camera's own point, also
for half a point). A `google` source brings `map: photoreal` and refuses
another map; `street_level` does not combine with `follow`.
`show_in_gods_eye_view` measures a view's altitude from the terrain under the
camera (`services.terrain`) before taking the look-at point, and keeps the
view's own estimate without a terrain answer. Links carry
`sl=<source>,<lat>,<lon>`; the globe panel applies views through the action,
and a plain link runs it once restored (one shot: the rewritten hash drops
`sl`). A link that follows an entity ignores `sl`.
- **Billing gate.** A plain link never loads a photo from a source that bills
every load (`billedPerImage`; Google): the action runs with
`autoOpen: false` and arms the layer (`armNearest`: the link's point and
radius, for the next OPEN press only, and only while the ground under the
screen centre is within 1 km of it (`ARMED_REACH_M`) or cannot be picked; a
sky centre refuses; any other lookup, open, close, disable, the source
switched off or its map leaving drops it), opens the panel and shows a toast
pointing at OPEN GOOGLE STREET VIEW.
- **Who may open.** Posted views, and the panel page's own link (inline embed)
for a tool result issued in the last 5 minutes, open the photo:
`show_in_gods_eye_view` stamps a street-level result `issued_at`, every
`gev:view` the panel posts carries `fresh`, and the inline app applies a
view without it with `autoOpen: false` (a framing page's posted views still
open). `?embed=1` on a published link only arms, without the panel nudge or
toast.
- **Who owns the camera.** A link's photo step is skipped when a newer link
superseded its restore, when its restore flight never landed
(`share.camera !== 'applied'`: the visitor dragged or searched), or after
any navigation since the generation that flight owned (`navigationToken` on
the settled result), also while its layers still load. A link pasted into
the open tab runs its step after its restore settles
(`gev:share-navigation-restore-settled`).
- **Embed.** The rail is hidden, so an open photo moves to an overlay on the
globe (`sl-viewer-wrap-embed`) and back when it closes; Esc closes it (in
full screen, Esc leaves full screen first). EXPAND stays under embed mode's
clean view; cockpit, recording and scene playback hide the overlay. Loading
errors render inside the overlay, and inside the viewer while EXPAND is on.
A posted view whose photo step fails before the imagery stage closes the
earlier photo before its camera flies; one the user overtook (a click on the
map, a newer request) leaves their photo and camera alone; one that found a
photo it could not load keeps that error.
Street Level has two browser gates. `npm run qa:street-level -- --url <server>`
runs against real Mapillary and needs `MAPILLARY_CLIENT_TOKEN`. `npm run
+9 -11
View File
@@ -104,18 +104,16 @@ draws them once the link has been restored.
A view can open street-level photos too: `street_level` names a source
(`google` for Street View, `mapillary`, `kartaview`, `panoramax`, or none for
whichever the panel has lit) and a point, which defaults to the spot the camera looks at. Street View
the lit one) and a point, by default the spot the camera looks at. Street View
shows only on the Google 3D map, so a `google` source sets `map` to
`photoreal` and any other map is refused; a view cannot both follow an entity
and open photos. The `street-level` layer is turned on. Links carry it as
`sl=<source>,<lat>,<lon>`; once the link has restored, the app runs its
`show_street_level` action, which flies to the point at street framing and
opens the nearest photo. A plain link with a `google` source stops before the
billed load: the panel is armed and OPEN GOOGLE STREET VIEW is one click away.
A view posted into the embedded app, or the panel page's own opening link,
opens the photo, since it is the client's own request; `?embed=1` on a
published link does not. Mapillary, KartaView and Panoramax links open the
photo at once; none of them bills per image. A link that follows an entity ignores `sl`.
`photoreal` and refuses another; a view cannot both follow an entity and open
photos. Links carry it as `sl=<source>,<lat>,<lon>`: once restored, the app
runs `show_street_level`, which flies there and opens the nearest photo. A
plain link with a `google` source only arms the panel for OPEN GOOGLE STREET
VIEW; a view posted into the embedded app, or the panel page's own opening
link, opens it, and `?embed=1` on a published link does not. Mapillary,
KartaView and Panoramax, which do not bill per image, open at once. A link
that follows an entity ignores `sl`.
Tools take a view as `VIEW_ARGUMENTS`: an `area` to frame from above, or a
`camera`, plus `layers`, `style`, `map`, `follow`, `annotations` and
+19 -31
View File
@@ -183,8 +183,7 @@ function flyTo(viewer, camera) {
/**
* The `show_street_level` arguments for a view's street-level photos. A plain
* link (`autoOpen: false`) never loads a photo from a source that bills every
* load: the app arms the panel and the OPEN button is one click away. A view
* link (`autoOpen: false`) only arms a source that bills every load; a view
* posted into the embedded app is the client's own request and opens.
*/
function streetLevelArgs(streetLevel, { autoOpen = true } = {}) {
@@ -260,20 +259,18 @@ export async function applyView(
if (view.annotations.length)
await act('annotate_map', { annotations: [...view.annotations] });
if (view.street_level) {
// The photos frame the camera: the action flies to the point at street
// level, lights the source and opens the nearest photo. When it fails
// before flying, the view's own camera still applies.
// The photos frame the camera; when the action fails before flying, the
// view's own camera still applies.
const result = await act(
'show_street_level',
streetLevelArgs(view.street_level, { autoOpen }),
);
if (result?.ok !== true) {
// Overtaken (a click on the map, a newer request): the panel and the
// camera are someone else's now.
// Overtaken (a click on the map, a newer request): hands off.
if (result?.cancelled || result?.superseded) return steps;
// An earlier view's photo must not stay over a place this view left.
// From the imagery stage on the action has settled the panel itself:
// a photo it found but could not load keeps its error in view.
// From the imagery stage on the action has settled the panel itself
// (a photo it could not load keeps its error).
if (result?.stage !== 'imagery')
dataManager.layers?.get?.(STREET_LEVEL_LAYER)?.module?.closeViewer?.();
if (!result?.location)
@@ -331,18 +328,13 @@ export function installViews({
const hashParams = new URLSearchParams(
String(location?.hash || '').replace(/^#/, ''),
);
// What the link itself asks for runs first, one thing at a time; posted
// views queue behind it. One failing step does not take the next with it.
// The camera's owner at restore: anyone who navigates while the link's
// steps wait (a gesture, a search, the assistant) takes it, and a passive
// link must not fly over them.
// A start-up restore that a newer link superseded hands the camera to that
// link: its own steps never run.
// A restore whose camera never landed (the recipient dragged, searched or
// opened another place during its flight) leaves the camera with them.
// The link's own steps run first, one at a time, and posted views queue
// behind them; one failing step does not take the next with it. A passive
// link never flies over someone who took the camera: its steps are skipped
// when a newer link superseded it, when its flight never landed (the
// recipient dragged or searched), or after any navigation since the
// generation its restore flight owned.
const cameraLanded = (outcome) => outcome?.share?.camera === 'applied';
// The generation the restore's own flight owned: a navigation after it
// landed, while the layers still loaded, has moved on from it.
const restoreGeneration = (outcome) =>
Number.isInteger(outcome?.navigationToken)
? outcome.navigationToken
@@ -369,11 +361,9 @@ export function installViews({
const linked = annotationsFromParams(hashParams);
if (linked.length)
queue = linkStep(() => run('annotate_map', { annotations: linked }));
// A link to street-level photos opens them once the link has restored. A
// link anyone can publish stops short of a billed load and leaves it to a
// click; only the panel page's own link for a result issued just now is
// the client's request (a host remounting the panel hands back old ones).
// A link that follows an entity keeps the camera on it.
// A link's street-level photos open once it has restored. A billed source
// only arms, except for the panel page's own link to a result issued just
// now. A link that follows an entity keeps the camera on it.
const linkedStreetLevel = (params) =>
followFromParams(params) ? null : streetLevelFromParams(params);
const openLinkedStreetLevel = async (
@@ -388,9 +378,8 @@ export function installViews({
streetLevelArgs(streetLevel, { autoOpen }),
{ origin: 'programmatic' },
);
// The restore window keeps the panel collapsed; OPEN must be in view.
// Embed mode hides the rail that holds it, so there the link only arms
// (a posted view is the way to open a photo in a frame).
// OPEN must be in view, but embed mode hides the rail that holds it: in
// a frame a posted view is the way to open a photo.
if (result?.armed === true && !isEmbedded(location)) {
shell.setPanelCollapsed?.(STREET_LEVEL_PANEL_ID, false, {
explicit: true,
@@ -410,9 +399,8 @@ export function installViews({
autoOpen: isFreshPanelResult(),
}),
);
// A link pasted into the open tab restores camera and layers on its own;
// its photos follow once that restore settles, under the same rule: a plain
// link never loads a billed photo by itself.
// A link pasted into the open tab: its photos follow once its restore
// settles, under the same rules.
const onNavigatedRestore = (event) => {
if (event?.detail?.status !== 'settled' || !cameraLanded(event.detail))
return;
+22 -32
View File
@@ -697,22 +697,19 @@ export function createStreetLevelLayer({
/**
* Open the nearest image of the active providers (or `providerIds`) near
* a point, else the view centre, or with `aim: 'screen-centre'` the ground
* under the screen centre (sky: nothing). `radiusM` widens the search for
* providers that can (a geocoded landmark is rarely within 50 m of a
* street); clicks keep the default. A caller's `signal` retires the
* lookup: nothing opens after it fires. Providers without
* `nearestImage` are skipped. `armed: false` (a request of its own, such
* as the assistant's) does not use a link's armed point, and keeps it
* while it looks; a photo it opens ends it, as any opened photo does. Why
* nothing opened is left in `street.nearestOutcome`: 'empty' (no imagery
* in reach), 'sky' (the screen centre is not ground), or null (an error).
* `replace: true` (a request that takes over the panel) keeps an open
* photo up while it looks, so the viewer and EXPAND stay, and closes it
* when the lookup opens nothing: the panel shows this request's outcome.
* `proceed()` is asked before a found image loads and before an empty
* or failed lookup reports; false retires the request and leaves the
* panel as it was (a photo that bills every load must not load for a
* request its caller has given up).
* under the screen centre (sky: nothing). Providers without
* `nearestImage` are skipped. Options:
* - `radiusM` widens the search where a provider can (a geocoded landmark
* is rarely within 50 m of a street);
* - `signal` retires the lookup: nothing opens after it fires;
* - `armed: false` (the assistant's own request) ignores a link's armed
* point and keeps it until a photo opens;
* - `replace: true` keeps an open photo (and EXPAND) up while it looks and
* closes it if nothing opens;
* - `proceed()` is asked before a found photo loads or an empty lookup
* reports; false retires the request and leaves the panel as it was.
* Why nothing opened is in `street.nearestOutcome`: 'empty', 'sky' or
* null (an error).
*/
async openNearest(
point,
@@ -737,9 +734,8 @@ export function createStreetLevelLayer({
if (replace && state.street.open) closeViewer();
};
if (!point && aim === 'screen-centre' && armed) {
// The button promises the centre of the view: the link's spot only
// while the view still shows it, or when the view cannot be picked.
// Sky refuses below, as any OPEN press does.
// OPEN promises the view centre: the link's spot only while the view
// still shows it, or when the view cannot be picked.
const centre = armed.point ? screenCentreGround() : null;
if (!armed.point) radiusM ??= armed.radiusM;
else if (
@@ -777,11 +773,9 @@ export function createStreetLevelLayer({
callerSignal?.addEventListener('abort', onCallerAbort, { once: true });
const retired = () => {
callerSignal?.removeEventListener('abort', onCallerAbort);
// Only the lookup that still owns the panel may touch it: a newer one
// has its own spinner and error.
// A newer lookup owns the panel, its spinner and its error.
if (nearestLookup !== lookup) return false;
nearestLookup = null;
// The caller moved on: the panel shows nothing of this lookup.
if (state.street.loading) {
state.street.loading = false;
notify();
@@ -822,8 +816,7 @@ export function createStreetLevelLayer({
if (proceed && proceed() !== true) return retired();
nearestLookup = null;
callerSignal?.removeEventListener('abort', onCallerAbort);
// The photo asked for stays on screen: a sequence playing in the
// photo it replaces stops.
// A sequence playing in the replaced photo would step past this one.
if (replace && state.street.open) parts.viewerHost.stopPlayback();
// The caller's signal still counts while the viewer mounts and loads.
return openImage(entry.def.id, imageId, {
@@ -834,8 +827,7 @@ export function createStreetLevelLayer({
callerSignal?.removeEventListener('abort', onCallerAbort);
// Replaced while it looked: the panel is the newer lookup's.
if (nearestLookup !== lookup) return false;
// Given up by its caller while it looked: the open photo stays, and
// the panel reports nothing of this lookup.
// Given up by its caller: the panel stays as it was.
if (proceed && proceed() !== true) return retired();
nearestLookup = null;
closeReplaced();
@@ -867,8 +859,7 @@ export function createStreetLevelLayer({
closeViewer,
/**
* Keep the open photo up while a request that replaces it switches the
* source (the viewer and EXPAND are replaced, not closed). The returned
* release closes it if its source is still off by then.
* source, so EXPAND stays. Release closes it if its source is still off.
*/
holdViewer() {
viewerHolds += 1;
@@ -883,10 +874,9 @@ export function createStreetLevelLayer({
};
},
/**
* Prepare a nearest lookup for the next OPEN press without running it: a
* link to a source that bills every load stops here. The press opens at
* this point with this radius; any other lookup, open or close, the
* source switched off, or its map leaving clears it.
* Arm a nearest lookup for the next OPEN press without running it (a link
* to a source that bills every load). Any other lookup, open or close, the
* source switched off or its map leaving clears it.
*/
armNearest(point, { providerIds = null, radiusM = null } = {}) {
state.street.armed = {
@@ -75,17 +75,14 @@ export const SEQUENCE_IMAGE_FIELDS =
'id,captured_at,compass_angle,geometry,is_pano';
/**
* Images asked for in a nearest-image search (show_street_level and links
* open the photo nearest a point; the graph API caps the radius at 50 m).
* The API returns the radius's images in no particular order and cuts them
* at the limit first, so this asks for its most (100): the distance sort and
* the imagery filter then choose among the spot's photos, not a few of them.
* Images asked for in a nearest-image search: the API cuts at the limit
* before any order, so this is its most, and the distance sort and the
* imagery filter choose among all of the spot's photos.
*/
export const NEAREST_LIMIT = 100;
/**
* A nearest-image search that has not answered (headers and body) in this
* long is given up: a link or a posted view waits on it, and so does every
* view queued behind that one.
* A nearest-image search not answered in full (body included) by then is
* given up: a link or posted view, and every view queued behind it, waits.
*/
export const NEAREST_TIMEOUT_MS = 12_000;
/** Fields a nearest-image search needs: where each is, and the filter's. */
+3 -7
View File
@@ -316,11 +316,9 @@ export class StreetLevelControls {
}
/**
* Embed mode hides the right rail, panel and viewer with it: an open photo
* moves to <body> as an overlay on the globe (`sl-viewer-wrap-embed`, see
* embed.css) and returns to its place in the panel when it closes. The
* panel view keeps patching the wrap where it is: its siblings are static,
* so Vue never inserts next to it.
* Embed mode hides the right rail: an open photo moves to <body> as an
* overlay on the globe (`sl-viewer-wrap-embed`, embed.css) and goes back
* when it closes. Vue keeps patching the wrap there: its siblings are static.
*/
_syncEmbedOverlay(open) {
const wrap = this._elements.viewerWrap;
@@ -404,8 +402,6 @@ export class StreetLevelControls {
if (!isEmbedPage()) this.actions.revealPanel?.();
}
this._wasOpen = open;
// Embed mode hides the rail the panel sits in: an open photo is shown over
// the globe instead, and goes back into the panel when it closes.
this._syncEmbedOverlay(open);
}
+12 -21
View File
@@ -3819,9 +3819,8 @@ function streetLevelPanelHidden() {
* (links) stops short of a billed load: the panel is armed for OPEN.
*/
export async function showStreetLevel(args, context) {
// Every await can hand the request's moment to someone else: a newer turn,
// a navigation or gesture that takes the camera, or a mode that hides the
// panel. Each stage after the place is settled checks again.
// After any await a newer turn, a navigation, a globe gesture or a mode
// that hides the panel may have taken over: each later stage checks again.
const owner = watchCameraOwner(context.viewer, context.styleManager);
// A photo held up while the request switches its source is let go however
// the request ends: replaced by then, or closed if its source stayed off.
@@ -3912,8 +3911,7 @@ async function requestStreetLevel(
const status = layerModule.providerStatus?.(provider) || null;
providerName = status?.name || provider;
// The key first, on any map: a chip without a key explains it and switches
// nothing, and so does this.
// The key first: without one nothing changes, as with a chip.
if (status && (status.keyRequired === true || status.configured === false))
return fail('key', status.error || `${providerName} needs its key`, {
keyRejected: status.keyRejected === true,
@@ -3998,9 +3996,8 @@ async function requestStreetLevel(
point = { lat: location.latitude, lon: location.longitude };
}
// Cockpit, clean view, recording and scene playback hide the panel the
// photo opens in, and may have started during the flight.
// The camera is the request's from where its own flight landed.
// A mode that hides the panel may have started during the flight. The
// camera is the request's from where its flight landed.
if (location) owner.claim();
const arrived = interrupted('view');
if (arrived) return arrived;
@@ -4076,13 +4073,11 @@ async function requestStreetLevel(
location,
});
// A billed source never loads from a link without a click: everything is
// ready and OPEN is one press away at the view centre.
// A link never loads a billed photo by itself: it arms OPEN instead.
if (args.autoOpen === false && status?.billed === true) {
// A photo of another place must not stay up beside the armed request.
if (layerModule.getUIState?.()?.street?.open === true)
layerModule.closeViewer?.();
// The next OPEN press loads exactly what the link asked for.
layerModule.armNearest?.(point, {
providerIds: [provider],
...(snapshot.groundClick === true
@@ -4101,13 +4096,10 @@ async function requestStreetLevel(
};
}
// A source with no coverage to click (Street View) is searched wider: a
// geocoded landmark is rarely within 50 m of a street. A newer turn
// retires the lookup: nothing opens after the user moved on. A photo of
// another place stays up while it looks (the viewer, EXPAND included, is
// replaced, not closed) and closes if this request opens nothing: the
// panel shows this request's outcome. Once an image is found it loads
// only if the camera and the panel are still the request's.
// Street View has no coverage to click, so it searches wider: a geocoded
// landmark is rarely within 50 m of a street. An open photo stays up while
// this looks and closes if nothing opens; a found photo loads only while
// the camera and the panel are still the request's.
reportProgress('imagery', label);
const opened = await layerModule.openNearest(point, {
providerIds: [provider],
@@ -4123,9 +4115,8 @@ async function requestStreetLevel(
: {}),
});
const street = layerModule.getUIState?.()?.street || {};
// A cancelled request opens nothing (the lookup and the viewer both honour
// the turn's signal). A photo that opened after the camera moved on, or
// into a panel hidden meanwhile, is closed again: nobody asked to see it.
// A photo that opened after the camera moved on, or into a panel hidden
// meanwhile, is closed again.
const late = interrupted('imagery');
if (late) {
if (opened === true) layerModule.closeViewer?.();