diff --git a/docs/CURRENT-STATE.md b/docs/CURRENT-STATE.md index 32494ab44..7863c09fa 100644 --- a/docs/CURRENT-STATE.md +++ b/docs/CURRENT-STATE.md @@ -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=,,`; `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=,,`; 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 ` runs against real Mapillary and needs `MAPILLARY_CLIENT_TOKEN`. `npm run diff --git a/docs/TOOLS.md b/docs/TOOLS.md index 738ccd541..99dfee5ec 100644 --- a/docs/TOOLS.md +++ b/docs/TOOLS.md @@ -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=,,`; 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=,,`: 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 diff --git a/src/app/embed.js b/src/app/embed.js index d1ad4653c..ed196b0b6 100644 --- a/src/app/embed.js +++ b/src/app/embed.js @@ -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; diff --git a/src/layers/streetLevel/index.js b/src/layers/streetLevel/index.js index 5e13e647d..c2b36509b 100644 --- a/src/layers/streetLevel/index.js +++ b/src/layers/streetLevel/index.js @@ -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 = { diff --git a/src/layers/streetLevel/providers/mapillary/policy.js b/src/layers/streetLevel/providers/mapillary/policy.js index b582b8cdd..78ca14a49 100644 --- a/src/layers/streetLevel/providers/mapillary/policy.js +++ b/src/layers/streetLevel/providers/mapillary/policy.js @@ -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. */ diff --git a/src/ui/streetLevelControls.js b/src/ui/streetLevelControls.js index b84c8b9e1..fbb73eb15 100644 --- a/src/ui/streetLevelControls.js +++ b/src/ui/streetLevelControls.js @@ -316,11 +316,9 @@ export class StreetLevelControls { } /** - * Embed mode hides the right rail, panel and viewer with it: an open photo - * moves to 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 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); } diff --git a/src/voice/gevActions.js b/src/voice/gevActions.js index a4cb18a16..09390f309 100644 --- a/src/voice/gevActions.js +++ b/src/voice/gevActions.js @@ -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?.();