Skip to content

Core: expose the share-link and map-screenshot as first-class plugin API capabilities #143

Description

@CarsonDavis

Core: expose the share-link and map-screenshot as first-class plugin API capabilities

Motivation

The dashboard already knows how to do the two hardest parts of sharing: it can encode its current view into a self-contained link, and it can capture a picture of the map. But both abilities are locked inside the app's own built-in UI. A plugin has no supported way to say "give me a link to the current view" or "give me an image of the current map."

That's a problem for where this framework is going. Plugins are supposed to be decoupled — they build on a public API and never reach into the core's internals. Any plugin that wants to offer "share" or "export" today would have to break that rule. Exposing these two capabilities as supported, first-class parts of the plugin API is what makes a clean Share/Export plugin (and anything else that wants to share or snapshot the view) possible without coupling.

Note: this is a pure-frontend capability. The link is a full, self-contained URL — there is no backend involved. (The old server-side link shortener is going away; we are not depending on it or replacing it here.)

How it should work

The framework exposes two capabilities to plugins through its public API:

  1. Get a share link to the current dashboard state. A plugin asks for it and receives a complete, self-contained URL — synchronously, with no server round-trip. Opening that link restores the same view the user had (see "What is shared" below). The URL is long because it carries the whole state; that's fine — it's self-contained and needs no backend to resolve.

  2. Get an image of the current map. A plugin asks for it and receives a PNG snapshot of what's currently on screen.

A plugin uses these results however it likes (copy to clipboard, download, display) and never touches the app's internal modules. The existing built-in screenshot button continues to work exactly as before — it just uses the same shared capability under the hood.

What is shared (captured in the link)

When the link is generated, it captures the current:

  • Mission / site being viewed
  • 2D map center and zoom
  • 3D globe center, zoom, and camera orientation
  • Panel layout (the relative sizes of the viewer / map / globe panes)
  • Visible layers and each one's opacity — this is also how the basemap / tiling is captured (the basemap is one of the visible layers)
  • Selected feature, if one is selected
  • Viewer image and its position (for image / photosphere / 3D-model viewers)
  • Time state — start/end time, live mode, follow — when time is enabled
  • State from existing built-in tools that already publish it (e.g. Draw's active files and filters, layer reordering, Shade, Viewshed)

What is NOT shared (won't survive the link)

Be explicit about the gaps so nobody assumes more is captured than is:

  • State that lives only in newer (modern) plugins is not captured. Modern plugins don't currently contribute anything to the link. ⚠️ Concretely: if the date/time picker is ever delivered as a modern plugin, the selected date would silently stop being shared — today it only works because time still lives in the core. Closing this gap (a way for any plugin to contribute its own state to the link) is a larger, separate design and is out of scope here.
  • Runtime layer restyling beyond opacity (color/width/symbol changes made on the fly).
  • Unsaved drawn geometry — only which Draw files are active is shared, not in-progress drawings.
  • Which tool/panel is open, beyond what an individual tool chooses to publish.
  • 2D map rotation (there is none in the 2D map; only the 3D globe camera is captured).

Done when

  • A plugin can request a share link and receive a complete, self-contained URL, using only the public plugin API (no reaching into internal modules, no backend call).
  • Opening that link in a fresh browser session restores the same dashboard view it was generated from (the items under "What is shared").
  • A plugin can request a map screenshot and receive a PNG image.
  • The existing built-in screenshot button still produces the same result as before.
  • Both capabilities are documented as part of the public plugin API surface, including a note on what the link does and does not capture.

Out of scope

  • The end-user button/panel that surfaces these — that's the separate Share/Export plugin issue, which depends on this one.
  • Short links / any link-shortening service. The self-contained full URL is the deliverable. External shorteners or client-side state compression are separate, optional efforts.
  • PDF export.
  • A general mechanism for modern plugins to contribute their own state into the link (the "What is NOT shared" gap). Larger, separate design.
  • Continuously updating the address bar as the user navigates, or making shared states navigable via the browser back button (today nothing pushes browser history; this issue doesn't change that).
Draft implementation plan — written as of d85a6c66 on 2026-06-15. Rough guide; re-verify against latest code.

Current behavior

  • The current view is serialized into a query-string URL by writeCoordinateURL in src/essence/Ancillary/QueryURL.js. It aggregates everything under "What is shared." Built-in tool state is collected by walking each tool's getUrlString() via ToolController_.getToolsUrl() (src/essence/Basics/ToolController_/ToolController_.js:605). Modern TSX plugins return '' and contribute nothing — that's the "What is NOT shared" gap.
  • The full URL is already exposed to plugins: mmgisAPI.writeCoordinateURL() calls QueryURL.writeCoordinateURL(false) (src/essence/mmgisAPI/mmgisAPI.js:440, exported ~720-724) and returns the complete URL synchronously, without touching the address bar. So the "share link" half is largely done — this issue mostly formalizes/documents it as the supported share-link method.
  • Shortening lives only behind shortenURL = true in QueryURL.writeCoordinateURL (the shortener_shorten API call, ~QueryURL.js:430-449), invoked from exactly one place — the built-in flow in src/essence/Basics/UserInterface_/BottomBar.js:35. With the shortener being removed, that branch/dependency goes away; the synchronous false path is all we need.
  • The map screenshot is implemented inline in the camera-button handler in BottomBar.js (~lines 70–169) using html2canvas, including an onclone callback that fixes SVG layer positioning/z-index. It is not callable from anywhere else — this is the real core gap.
  • URL writes everywhere use history.replaceState, never pushState (essence.js:346, modern.js:184, QueryURL.js:440/446/468); the address bar is only set to ?mission=... on load. Hence no back-button history of views.

Where the change lands & rough plan

  • Share-link method: the synchronous full URL is already returned by mmgisAPI.writeCoordinateURL(). Confirm it's the supported, documented entry point; if the shortener removal touches writeCoordinateURL's signature (the shortenURL/callback params), simplify so the public method is unambiguously "return the full URL, no backend." No async, no fallback.
  • Screenshot method (the real work): extract the html2canvas logic out of the BottomBar.js button handler into a reusable function (e.g. a small ScreenshotUtils), expose it through the public API, and re-point the existing button at it so its behavior is unchanged.
  • Expose the screenshot via the same direct-method / provide pattern the API already uses (e.g. the app:* / layers:* handlers registered in Layers_.fina(), src/essence/Basics/Layers_/Layers_.js ~164–232). The event bus (on/emit) is not needed — these are request/response, not observed events.

⚠️ Gotcha: the screenshot's onclone callback does non-obvious SVG / z-index fixups; if you don't preserve it verbatim when extracting, the captured image renders wrong (mislayered or blank).

References

  • src/essence/Ancillary/QueryURL.jswriteCoordinateURL, shortener branch to be removed (~430–449)
  • src/essence/mmgisAPI/mmgisAPI.js — already-exposed full URL (~440, ~720–724)
  • src/essence/Basics/UserInterface_/BottomBar.js — inline screenshot (~70–169) + only shortener caller (line 35)
  • src/essence/Basics/Layers_/Layers_.js — provider registration pattern in fina() (~164–232)
  • src/essence/Basics/ToolController_/ToolController_.jsgetToolsUrl() / getUrlString() aggregation (~605)

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions