signalk-livedepth-plugin 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +46 -0
- package/dist/config.d.ts +49 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +33 -1
- package/dist/config.js.map +1 -1
- package/dist/plotterext/manifest.d.ts.map +1 -1
- package/dist/plotterext/manifest.js +4 -3
- package/dist/plotterext/manifest.js.map +1 -1
- package/dist/plugin.d.ts +15 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +21 -3
- package/dist/plugin.js.map +1 -1
- package/dist/streams/live.d.ts +15 -3
- package/dist/streams/live.d.ts.map +1 -1
- package/dist/streams/live.js +163 -42
- package/dist/streams/live.js.map +1 -1
- package/dist/tiles/core/tile-source.d.ts +9 -0
- package/dist/tiles/core/tile-source.d.ts.map +1 -1
- package/dist/tiles/core/tile-source.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types.d.ts +3 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/public/js/config.js +40 -2
- package/public/js/config.js.map +2 -2
- package/public/js/render-worker.js +225 -65
- package/public/js/render-worker.js.map +3 -3
- package/public/js/renderer.js +76 -190
- package/public/js/renderer.js.map +4 -4
package/dist/types.d.ts
CHANGED
|
@@ -6,5 +6,8 @@ export type BathymetryData = {
|
|
|
6
6
|
depth: number;
|
|
7
7
|
timestamp: Temporal.Instant;
|
|
8
8
|
heading?: number;
|
|
9
|
+
/** The Signal K context the sounding came from — `vessels.self` or a target
|
|
10
|
+
* vessel's URN. Kept for provenance; the tile store ignores it. */
|
|
11
|
+
context?: string;
|
|
9
12
|
};
|
|
10
13
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEjD,sEAAsE;AACtE,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,QAAQ,CAAC,OAAO,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEjD,sEAAsE;AACtE,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,QAAQ,CAAC,OAAO,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;uEACmE;IACnE,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC"}
|
package/package.json
CHANGED
package/public/js/config.js
CHANGED
|
@@ -666,6 +666,7 @@ function connectExtension(opts = {}) {
|
|
|
666
666
|
var DEFAULT_SETTINGS = {
|
|
667
667
|
enabled: true,
|
|
668
668
|
source: "history",
|
|
669
|
+
quality: "auto",
|
|
669
670
|
historyWindowHours: 1,
|
|
670
671
|
historyResolutionS: 0.1,
|
|
671
672
|
shadeLimitMetres: 10,
|
|
@@ -675,6 +676,10 @@ var DEFAULT_SETTINGS = {
|
|
|
675
676
|
maxDepthMetres: 100,
|
|
676
677
|
safetyDepthMetres: 3
|
|
677
678
|
};
|
|
679
|
+
var ENUM_VALUES = {
|
|
680
|
+
source: ["history", "tiles"],
|
|
681
|
+
quality: ["auto", "high", "balanced", "low"]
|
|
682
|
+
};
|
|
678
683
|
var SETTINGS_BOUNDS = {
|
|
679
684
|
historyWindowHours: { min: 0.1, max: 168, step: 0.1 },
|
|
680
685
|
historyResolutionS: { min: 0.01, max: 60, step: 0.01 },
|
|
@@ -694,9 +699,14 @@ function mergeSettings(stored) {
|
|
|
694
699
|
DEFAULT_SETTINGS
|
|
695
700
|
)) {
|
|
696
701
|
const value = stored[key];
|
|
697
|
-
if (typeof value
|
|
698
|
-
|
|
702
|
+
if (typeof value !== typeof DEFAULT_SETTINGS[key]) {
|
|
703
|
+
continue;
|
|
704
|
+
}
|
|
705
|
+
const allowed = ENUM_VALUES[key];
|
|
706
|
+
if (allowed && !allowed.includes(value)) {
|
|
707
|
+
continue;
|
|
699
708
|
}
|
|
709
|
+
out[key] = value;
|
|
700
710
|
}
|
|
701
711
|
return out;
|
|
702
712
|
}
|
|
@@ -873,6 +883,34 @@ function build() {
|
|
|
873
883
|
sourceHint.textContent = "History draws the vessel's own recorded track; tiles draw the server-built bathymetry store, extended live.";
|
|
874
884
|
source.appendChild(sourceHint);
|
|
875
885
|
root.appendChild(source);
|
|
886
|
+
const quality = document.createElement("label");
|
|
887
|
+
quality.className = "field";
|
|
888
|
+
const qualityName = document.createElement("span");
|
|
889
|
+
qualityName.className = "name";
|
|
890
|
+
qualityName.textContent = "Render quality";
|
|
891
|
+
const qualitySelect = document.createElement("select");
|
|
892
|
+
for (const [value, label] of [
|
|
893
|
+
["auto", "Auto (adapt to this GPU)"],
|
|
894
|
+
["high", "High"],
|
|
895
|
+
["balanced", "Balanced"],
|
|
896
|
+
["low", "Low"]
|
|
897
|
+
]) {
|
|
898
|
+
const option = document.createElement("option");
|
|
899
|
+
option.value = value;
|
|
900
|
+
option.textContent = label;
|
|
901
|
+
option.selected = settings.quality === value;
|
|
902
|
+
qualitySelect.appendChild(option);
|
|
903
|
+
}
|
|
904
|
+
qualitySelect.addEventListener("change", () => {
|
|
905
|
+
settings.quality = qualitySelect.value;
|
|
906
|
+
void save();
|
|
907
|
+
});
|
|
908
|
+
quality.append(qualityName, qualitySelect);
|
|
909
|
+
const qualityHint = document.createElement("span");
|
|
910
|
+
qualityHint.className = "hint";
|
|
911
|
+
qualityHint.textContent = "Scales the shaded depth surface only; soundings, circles and labels always render at full resolution. Auto adapts to this GPU.";
|
|
912
|
+
quality.appendChild(qualityHint);
|
|
913
|
+
root.appendChild(quality);
|
|
876
914
|
for (const field of FIELDS) {
|
|
877
915
|
root.appendChild(numberField(field));
|
|
878
916
|
}
|
package/public/js/config.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../node_modules/signalk-plotterext-bus/src/protocol.ts", "../../node_modules/signalk-plotterext-bus/src/wildcard.ts", "../../node_modules/signalk-plotterext-bus/src/bounds.ts", "../../node_modules/signalk-plotterext-bus/src/codec.ts", "../../node_modules/signalk-plotterext-bus/src/port.ts", "../../node_modules/signalk-plotterext-bus/src/endpoint.ts", "../../node_modules/signalk-plotterext-bus/src/extension.ts", "../../src/web/shared/settings.ts", "../../src/web/config.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Wire protocol types and constants for the Signal K plotter extension bus.\n *\n * The wire format is JSON-RPC 2.0 (https://www.jsonrpc.org/specification)\n * inside a routing envelope: `{ bus: \"plotterExt/1\", msg: <JSON-RPC object> }`.\n * Calls are JSON-RPC requests; events are JSON-RPC notifications whose\n * `method` is a hierarchical dot-separated event name.\n */\n\nexport const BUS_ID = 'plotterExt/1'\n\nexport type JsonRpcId = string | number\n\nexport interface JsonRpcRequest {\n jsonrpc: '2.0'\n id: JsonRpcId\n method: string\n params?: unknown\n}\n\nexport interface JsonRpcNotification {\n jsonrpc: '2.0'\n method: string\n params?: unknown\n}\n\nexport interface JsonRpcErrorObject {\n code: number\n message: string\n data?: { reason?: string; [key: string]: unknown }\n}\n\nexport interface JsonRpcSuccessResponse {\n jsonrpc: '2.0'\n id: JsonRpcId\n result: unknown\n}\n\nexport interface JsonRpcErrorResponse {\n jsonrpc: '2.0'\n id: JsonRpcId | null\n error: JsonRpcErrorObject\n}\n\nexport type JsonRpcResponse = JsonRpcSuccessResponse | JsonRpcErrorResponse\n\nexport type JsonRpcMessage =\n | JsonRpcRequest\n | JsonRpcNotification\n | JsonRpcResponse\n\nexport interface Envelope {\n bus: typeof BUS_ID\n msg: JsonRpcMessage\n}\n\n/**\n * JSON-RPC reserved codes for protocol errors, plus implementation-defined\n * codes (-32000..-32099 range) used by this package. Host API errors should\n * use HOST_ERROR with a stable string identifier in `error.data.reason`.\n */\nexport const RPC_ERRORS = {\n PARSE_ERROR: -32700,\n INVALID_REQUEST: -32600,\n METHOD_NOT_FOUND: -32601,\n INVALID_PARAMS: -32602,\n INTERNAL_ERROR: -32603,\n HOST_ERROR: -32000,\n TIMEOUT: -32001,\n CONNECTION_CLOSED: -32002\n} as const\n\nexport class RpcError extends Error {\n readonly code: number\n readonly data?: { reason?: string; [key: string]: unknown }\n\n constructor(\n message: string,\n opts: {\n code?: number\n reason?: string\n data?: Record<string, unknown>\n } = {}\n ) {\n super(message)\n this.name = 'RpcError'\n this.code = opts.code ?? RPC_ERRORS.HOST_ERROR\n const data: Record<string, unknown> = { ...(opts.data ?? {}) }\n if (opts.reason !== undefined) data.reason = opts.reason\n this.data = Object.keys(data).length > 0 ? data : undefined\n }\n\n get reason(): string | undefined {\n return typeof this.data?.reason === 'string' ? this.data.reason : undefined\n }\n\n toErrorObject(): JsonRpcErrorObject {\n return {\n code: this.code,\n message: this.message,\n ...(this.data ? { data: this.data } : {})\n }\n }\n\n static fromErrorObject(err: JsonRpcErrorObject): RpcError {\n return new RpcError(err.message, { code: err.code, data: err.data })\n }\n\n /** Normalize any thrown value into an RpcError suitable for the wire. */\n static from(err: unknown): RpcError {\n if (err instanceof RpcError) return err\n if (err instanceof Error) {\n return new RpcError(err.message, { code: RPC_ERRORS.INTERNAL_ERROR })\n }\n return new RpcError(String(err), { code: RPC_ERRORS.INTERNAL_ERROR })\n }\n}\n\n/** Reserved event names used to establish a connection. */\nexport const EVENT_READY = 'bus.ready'\nexport const EVENT_HANDSHAKE = 'bus.handshake'\n\n/**\n * Optional payload a caller may send with `bus.ready`. Additive and\n * backward-compatible: a host that predates it (or does not adopt caller ids)\n * ignores it.\n */\nexport interface ReadyParams {\n /**\n * Caller-asserted context id. A host that adopts caller ids — an\n * embedding-host connection, where the host does not know the caller in\n * advance — uses it as `context.id`; otherwise it is ignored.\n */\n id?: string\n}\n\nexport type ContextKind =\n | 'panel'\n | 'widget'\n | 'window'\n | 'background'\n | 'embedding-host'\n\nexport interface HandshakeContext {\n kind: ContextKind\n /** Manifest-local contribution id. */\n id: string\n /** Host-assigned unique id for this placed instance (widgets). */\n instanceId?: string | null\n /** Widget instance a configuration panel was opened for. */\n targetInstance?: string | null\n /** Manifest-local widget id of the target instance (configuration panels). */\n targetWidget?: string | null\n /** Image overlay a configuration panel was opened for (capability `overlays.image`). */\n targetOverlay?: string | null\n /** Host-assigned id of this window (`window` contexts; capability `windows`). */\n windowId?: string\n /** The `params` the window was opened with (`window` contexts). */\n params?: Record<string, unknown>\n}\n\nexport interface Handshake {\n host: string\n hostVersion: string\n apiVersion: string\n capabilities: string[]\n context: HandshakeContext\n}\n\n/** Payload of an `sk.<path>` Signal K value event. */\nexport interface SignalKValueEvent {\n path: string\n value: unknown\n timestamp?: string\n $source?: string\n}\n\nexport type StateScope = 'instance' | 'extension'\n\n/** Payload of a `state.changed` host event. */\nexport interface StateChangedEvent {\n scope: StateScope\n instanceId?: string | null\n keys: string[]\n}\n\n/**\n * Types for the `routes` capability — the routes the host currently has visible\n * on the chart (drafts plus stored routes the user is displaying), each\n * addressed by an opaque host-assigned `routeId`. Extensions read/write them and\n * follow lifecycle + mutation events (`route.visible` / `route.dirty` /\n * `route.saved` / `route.hidden`). Two orthogonal flags travel with a route:\n * `saved` (backed by a persisted resource) and `dirty` (has pending unsaved\n * changes). See the Plotter Extensions API spec, \"Live routes\".\n */\n\n/** A single point in a route edit buffer (`[lon, lat, alt?]`). */\nexport interface RoutePoint {\n position: [number, number, number?]\n name?: string\n description?: string\n /**\n * Link to a saved waypoint, as a Signal K resource path\n * (`/resources/waypoints/<id>`). A reference, not a copy: `position` alone\n * defines the route's geometry, and the host does not keep the point and the\n * waypoint in step — drop the link when moving the point off its waypoint. A\n * host built before this field existed drops it. See the Plotter Extensions\n * API spec, \"Waypoint links\".\n */\n href?: string\n}\n\n/** Snapshot of a route — result of `route.get`. */\nexport interface RouteData {\n routeId: string\n name: string | null\n /** Route-level description (distinct from a waypoint's RoutePoint.description). */\n description: string | null\n /** Monotonic revision; increments on every mutation. */\n rev: number\n /** Whether the route is backed by a persisted routes resource. */\n saved: boolean\n /** Whether the in-memory route has pending unsaved changes. */\n dirty: boolean\n points: RoutePoint[]\n}\n\n/** Summary entry in a `route.list` result (the visible set). */\nexport interface RouteSummary {\n routeId: string\n name: string | null\n rev: number\n pointCount: number\n saved: boolean\n dirty: boolean\n}\n\n/**\n * Payload of a `route.visible` host event — a route entered the visible set\n * (became rendered on the chart). A freshly drawn/created draft arrives\n * `saved:false, dirty:true`; a stored route brought into view arrives\n * `saved:true, dirty:false`.\n */\nexport interface RouteVisibleEvent {\n routeId: string\n rev: number\n name: string | null\n pointCount: number\n saved: boolean\n dirty: boolean\n}\n\n/**\n * Payload of a `route.hidden` host event — a route left the visible set.\n * `saved:true` ⇒ a stored route was made invisible (the resource is untouched);\n * `saved:false` ⇒ an unsaved draft was deleted (gone for good).\n */\nexport interface RouteHiddenEvent {\n routeId: string\n rev: number\n saved: boolean\n}\n\n/**\n * Payload of a `route.dirty` host event — the conformance-floor catch-all for\n * any structural/bulk change the host does not express granularly. A subscriber\n * should re-seed via `route.get`.\n */\nexport interface RouteDirtyEvent {\n routeId: string\n rev: number\n reason?: string\n}\n\n/**\n * Payload of a `route.saved` host event — the route's current state was\n * persisted to the routes resource collection. Arrives `saved:true,\n * dirty:false`; the route stays visible under the same `routeId`.\n */\nexport interface RouteSavedEvent {\n routeId: string\n rev: number\n /** Stored resource id of the persisted route. */\n href: string\n /** The route's name as persisted (may have just been set in the host's save\n * dialog), so followers can update a label without re-fetching. */\n name: string | null\n saved: boolean\n dirty: boolean\n}\n\n/** Stable `error.data.reason` strings for `route.*` host-method failures. */\nexport type RouteErrorReason =\n | 'routes.unknownId'\n | 'routes.badRequest'\n | 'routes.badRef'\n | 'routes.saveFailed'\n | 'routes.deleteFailed'\n | 'routes.saveCancelled'\n | 'routes.notSupported'\n\n/**\n * Types for the `charts` capability — a lightweight facade over the chart\n * layers the host already manages. An extension enumerates them, reads which\n * are shown and in what stacking order, and toggles visibility, opacity and\n * order. It does **not** add, create, or delete chart sources — only manages\n * charts the host already knows about. Follow changes (from any origin,\n * including the user's own chart controls) via the `chart.visibility` /\n * `chart.opacity` / `chart.order` events. With the `charts.time` sub-capability\n * a host also retargets time-varying charts (`chart.setTime`, `ChartTime`,\n * `chart.time`). See the Plotter Extensions API spec, \"Chart layers\" and\n * \"Time-varying charts\".\n */\n\n/**\n * One chart layer the host manages — an entry in a `chart.list` result. The\n * array order is the display/stacking order (index 0 = topmost/frontmost).\n */\nexport interface ChartLayer {\n /** Opaque, stable, host-assigned id. Treat as a token; never parse it. */\n id: string\n /** Human-readable chart name. */\n name: string\n /** Whether the chart is currently displayed. */\n visible: boolean\n /** Display opacity, 0..1. */\n opacity: number\n /** Best-effort source kind, e.g. 'raster' | 'vector' | 'S-57' | 'WMS'. */\n type?: string\n /**\n * Geographic extent `[west, south, east, north]`, when known. Longitudes are\n * in `[-180, 180]`; a chart that crosses the antimeridian has `west > east`.\n * See `LonLatBounds`.\n */\n bounds?: [number, number, number, number]\n /** Minimum usable zoom level, when known. */\n minZoom?: number\n /** Maximum usable zoom level, when known. */\n maxZoom?: number\n /**\n * Present only on a time-addressable chart (hosts with `charts.time`): the\n * instant currently shown and the source's timeline, as far as the host knows\n * it. Absent on a chart with no time dimension.\n */\n time?: ChartTime\n}\n\n/**\n * The time dimension of a time-varying chart (weather radar, satellite,\n * nowcast) — the `time` object on a `chart.list` entry. `value` and `current`\n * are always present; the timeline fields are best-effort metadata as the host\n * last learned them (from the chart resource or the source's capabilities),\n * not the contract's source of truth — a rolling product's newest frame moves\n * on between reads.\n */\nexport interface ChartTime {\n /** Instant currently shown (ISO 8601), or `null` for the live/current frame. */\n value: string | null\n /** Whether the source serves a live/current frame (so `null` is a valid target). */\n current: boolean\n /** Earliest instant offered (ISO 8601), when known. */\n from?: string\n /** Latest instant offered (ISO 8601), when known. */\n to?: string\n /** Milliseconds between frames, when the timeline is regular. */\n step?: number\n /** The explicit instants offered (ISO 8601), when the timeline is a list. */\n values?: string[]\n}\n\n/**\n * Params of `chart.setTime` — retarget each named time-varying chart to an\n * instant (ISO 8601) or back to its live frame (`null`). The host passes the\n * instant through to the source without snapping or clamping.\n */\nexport interface ChartSetTimeParams {\n ids: string[]\n time: string | null\n}\n\n/**\n * Payload of a `chart.time` host event — a time-varying chart was retargeted\n * (`time` is an ISO 8601 instant) or returned to live (`time: null`). Emitted\n * for every change regardless of origin; a batch `chart.setTime` emits one\n * event per changed chart.\n */\nexport interface ChartTimeEvent {\n id: string\n time: string | null\n}\n\n/**\n * Payload of a `chart.visibility` host event — one chart's display was turned\n * on or off. A batch `chart.setVisibility` emits one event per changed chart,\n * as does the user toggling a chart in the host's own chart controls.\n */\nexport interface ChartVisibilityEvent {\n id: string\n visible: boolean\n}\n\n/** Payload of a `chart.opacity` host event — one chart's opacity changed. */\nexport interface ChartOpacityEvent {\n id: string\n /** New opacity, 0..1. */\n opacity: number\n}\n\n/**\n * Payload of a `chart.order` host event — the display/stacking order changed.\n * `order` is the new full order, topmost first (the same order `chart.list`\n * returns).\n */\nexport interface ChartOrderEvent {\n order: string[]\n}\n\n/** Stable `error.data.reason` strings for `chart.*` host-method failures. */\nexport type ChartErrorReason =\n | 'charts.unknownId'\n | 'charts.badRequest'\n | 'charts.notSupported'\n | 'charts.notTemporal'\n\n/**\n * Types for the `nightMode` capability — the host's night-vision display mode\n * (a dimmed, low-blue appearance for use after dark). An extension reads the\n * current state, changes it, and follows changes so an embedded panel matches\n * the host instead of glowing white on a dark bridge. See the Plotter\n * Extensions API spec, \"Night mode\".\n */\n\n/**\n * The host's night-mode state — the `nightMode.get` result and the payload of\n * the `nightMode.changed` event.\n */\nexport interface NightModeState {\n /** Whether night mode is currently applied (the resolved state the user sees). */\n enabled: boolean\n /**\n * Whether the host is deriving `enabled` from the server's `environment.mode`\n * (`night` -> on). While `true`, `enabled` tracks the server automatically.\n */\n auto: boolean\n}\n\n/**\n * Payload of a `nightMode.changed` host event — the night-mode state changed.\n * Emitted **origin-transparently** for every change: an extension's own\n * `nightMode.set`, the user toggling the host's night-mode control, or the\n * server's `environment.mode` flipping while `auto` is on.\n */\nexport type NightModeChangedEvent = NightModeState\n\n/** Stable `error.data.reason` strings for `nightMode.*` host-method failures. */\nexport type NightModeErrorReason =\n | 'nightMode.badRequest'\n | 'nightMode.notSupported'\n\n/**\n * Types for the `map` capability — the chart viewport. An extension reads it\n * with `map.getView`, drives it with `map.center` / `map.fitBounds`, and follows\n * it with the `map.view` event. See the Plotter Extensions API spec, \"Map view\".\n */\n\n/**\n * The chart viewport — the `map.getView` result and the payload of the\n * `map.view` event, which carry the same shape.\n */\nexport interface MapView {\n /** Viewport centre as `[lon, lat]`. */\n center: [number, number]\n /** Current zoom level; may be fractional. */\n zoom: number\n /**\n * Axis-aligned lon/lat box covering what is rendered, as\n * `[west, south, east, north]`. On a host whose map can be rotated this is the\n * box containing the rotated view — \"at least this much is on screen\".\n * Longitudes are always in `[-180, 180]`, even when the chart has been panned\n * into another copy of the world; a view straddling the antimeridian has\n * `west > east`, and one showing every longitude is `-180` to `180`. See\n * `LonLatBounds`.\n */\n bounds: [number, number, number, number]\n}\n\n/**\n * Payload of a `map.view` host event — the chart viewport was panned and/or\n * zoomed. Emitted once the view has **settled** (the gesture and any kinetic\n * glide have come to rest), not continuously during the gesture, and\n * **origin-transparently**: for the user dragging the chart, the host recentring\n * on the vessel, or an extension's own `map.center` / `map.fitBounds`.\n */\nexport type MapViewEvent = MapView\n\n/**\n * Types for the `resourceGroups` capability — applying a stored resource group\n * to the host's display. Groups are ordinary Signal K resources in the `groups`\n * collection; an extension creates, edits and lists them through the server's\n * resources API. The capability adds only what the host alone can do: apply one.\n * See the Plotter Extensions API spec, \"Resource groups\".\n */\n\n/** The resource types a group can carry an instruction for. */\nexport type ResourceGroupType = 'routes' | 'waypoints' | 'regions' | 'charts'\n\n/**\n * A resource group document, as stored at `/resources/groups/{id}`. Each list\n * is an instruction for its type, with three distinct meanings:\n * - `[\"a\", …]` — display these (ids that resolve to no resource are ignored);\n * - `[]` — display none of this type;\n * - key absent — leave this type's display as it is.\n * `[]` and an absent key are **not** interchangeable.\n */\nexport interface ResourceGroup {\n name: string\n description?: string\n routes?: string[]\n waypoints?: string[]\n regions?: string[]\n charts?: string[]\n}\n\n/** Params of `resourceGroup.apply`. */\nexport interface ResourceGroupApplyParams {\n /** Id of the group in the server's `groups` collection. */\n id: string\n}\n\n/**\n * Result of `resourceGroup.apply`. `applied` lists every type for which the\n * host made a best-effort attempt to carry out the group's instruction. A type\n * the host did not act on — or whose key is absent from the group — is omitted.\n * It reports what the host **attempted**, not the resulting display.\n */\nexport interface ResourceGroupApplyResult {\n applied: ResourceGroupType[]\n}\n\n/**\n * Payload of a `resourceGroup.applied` host event — a group was applied.\n * Emitted **origin-transparently**: for an extension's `resourceGroup.apply`\n * and for the user choosing a group in the host's own UI. It reports an action,\n * not a readable state — there is no \"current group\" query.\n */\nexport interface ResourceGroupAppliedEvent extends ResourceGroupApplyResult {\n /** Id of the group that was applied. */\n id: string\n}\n\n/** Stable `error.data.reason` strings for `resourceGroup.*` host-method failures. */\nexport type ResourceGroupErrorReason =\n | 'resourceGroups.unknownId'\n | 'resourceGroups.fetchFailed'\n | 'resourceGroups.badRequest'\n | 'resourceGroups.notSupported'\n\n/**\n * Types for the `windows` capability — an extension's iframe panels shown as\n * floating windows over the chart. Each window is its own context\n * (`context.kind === 'window'`) carrying `windowId` and `params`. Geometry the\n * extension sends is a request; the host clamps it and reports the actual\n * `bounds` in the window state. See the Plotter Extensions API spec, \"Windows\".\n */\n\n/** Where a window is anchored in the host's window area. */\nexport type WindowAnchor =\n | 'top-left'\n | 'top-center'\n | 'top-right'\n | 'center-left'\n | 'center'\n | 'center-right'\n | 'bottom-left'\n | 'bottom-center'\n | 'bottom-right'\n\n/**\n * A length: CSS pixels as a number, or a percentage of the window area as a\n * string such as `\"40%\"`.\n */\nexport type WindowLength = number | `${number}%`\n\n/**\n * Requested size and position, relative to the host's window area. `offset` is\n * measured inward from the anchored edges (on a centered axis, positive moves\n * right/down). Fields left out take the host's choice (on open) or keep their\n * current value (on update).\n */\nexport interface WindowGeometry {\n anchor?: WindowAnchor\n offset?: { x?: WindowLength; y?: WindowLength }\n width?: WindowLength\n height?: WindowLength\n minWidth?: WindowLength\n minHeight?: WindowLength\n maxWidth?: WindowLength\n maxHeight?: WindowLength\n}\n\n/** Params of `ui.openWindow`. */\nexport interface WindowOpenParams {\n /** Id of an iframe panel in the caller's manifest. */\n panel: string\n /** Handed to the window in its handshake as `context.params`. */\n params?: Record<string, unknown>\n /** Title-bar text; defaults to the panel's `title`. */\n title?: string\n geometry?: WindowGeometry\n /** Block the chart and every other window until closed. Default `false`. */\n modal?: boolean\n /** Whether the user may resize it. Default `true`. */\n resizable?: boolean\n /** Whether the user may move it. Default `true`. */\n movable?: boolean\n /** Default `'fixed'`; `'autoHide'` hides the title bar when idle. */\n titleBar?: 'fixed' | 'autoHide'\n /** What the user's close control does. Default `'close'`. */\n userClose?: 'close' | 'hide'\n /** `false` opens the window hidden (loaded, not shown). Default `true`. */\n visible?: boolean\n /** Reuse an open window of this panel instead of opening another. */\n single?: boolean\n /** Remember this window's geometry under this key (per extension). */\n restoreKey?: string\n}\n\n/** Params of `ui.updateWindow`. `windowId` defaults to self in a window context. */\nexport interface WindowUpdateParams {\n windowId?: string\n title?: string\n geometry?: WindowGeometry\n visible?: boolean\n}\n\n/** A window's actual position and size, in CSS px from the window area's top-left. */\nexport interface WindowBounds {\n x: number\n y: number\n width: number\n height: number\n}\n\n/** The size of the host's window area, in CSS px. */\nexport interface WindowArea {\n width: number\n height: number\n}\n\n/** How the host is showing a window. */\nexport type WindowPresentation = 'floating' | 'sheet' | 'fullscreen'\n\n/**\n * A window's state — the result of `ui.openWindow` / `ui.updateWindow`, an\n * entry of `ui.listWindows`, and the payload of `window.state`.\n */\nexport interface WindowState {\n windowId: string\n /** The manifest panel the window shows. */\n panel: string\n title: string\n presentation: WindowPresentation\n bounds: WindowBounds\n area: WindowArea\n visible: boolean\n /** Host feature: collapsed to its title bar (still running). */\n collapsed: boolean\n /** Host feature: popped out into a separate browser window. */\n poppedOut: boolean\n modal: boolean\n}\n\n/** Result of `ui.listWindows` — the caller's extension's open windows. */\nexport interface WindowListResult {\n windows: WindowState[]\n}\n\n/** Payload of a `window.bounds` host event — a window's actual geometry changed. */\nexport interface WindowBoundsEvent {\n windowId: string\n bounds: WindowBounds\n area: WindowArea\n}\n\n/** Payload of a `window.state` host event (visibility, collapse, pop-out, presentation or title changed). */\nexport type WindowStateEvent = WindowState\n\n/** Why a window closed. */\nexport type WindowCloseReason = 'user' | 'extension' | 'host'\n\n/** Payload of a `window.closed` host event — the window closed and its page unloaded. */\nexport interface WindowClosedEvent {\n windowId: string\n reason: WindowCloseReason\n}\n\n/** Stable `error.data.reason` strings for window host-method failures. */\nexport type WindowErrorReason =\n | 'windows.badRequest'\n | 'windows.unknownId'\n | 'windows.limit'\n | 'windows.modalOpen'\n | 'windows.notSupported'\n | 'UNKNOWN_PANEL'\n\n/**\n * Types for the `overlays.image` capability — map layers whose pixels an\n * extension's background runtime draws and the host composites. The host asks\n * for frames (`overlay.request`); the runtime answers with `overlay.frame`,\n * whose `image` is an `ImageBitmap` sent in the `postMessage` transfer list —\n * the one non-JSON value on the wire. See the Plotter Extensions API spec,\n * \"Map overlays\".\n */\n\n/** Where an overlay sits relative to the host's own content. */\nexport type OverlayBand = 'aboveCharts' | 'aboveResources'\n\n/** An overlay's current state — an entry of `overlay.list`. */\nexport interface OverlayInfo {\n /** The overlay's manifest id. */\n id: string\n title: string\n band: OverlayBand\n visible: boolean\n /** 0..1 */\n opacity: number\n /** The host has paused the overlay to stay within its budget. */\n suspended: boolean\n}\n\n/** Result of `overlay.list` — the caller's extension's overlays. */\nexport interface OverlayListResult {\n overlays: OverlayInfo[]\n}\n\n/**\n * Payload of an `overlay.request` host event — the host wants a frame. The\n * image covers `extent`, a north-up axis-aligned box in EPSG:3857 metres\n * `[minX, minY, maxX, maxY]` (it may run past ±20037508.34 across the\n * antimeridian), with pixels spaced evenly in Mercator; row 0 is the north\n * edge.\n */\nexport interface OverlayRequestEvent {\n overlayId: string\n requestId: string\n extent: [number, number, number, number]\n /** The same box as lon/lat `[west, south, east, north]` (west > east across the antimeridian). */\n bounds: [number, number, number, number]\n /** Image size in pixels. */\n width: number\n height: number\n /** Image pixels per CSS pixel, chosen by the host. */\n pixelRatio: number\n /** Metres per image pixel. */\n resolution: number\n zoom: number\n /** Map rotation, radians clockwise. Advisory: for keeping labels upright only. */\n rotation: number\n /**\n * Least time, in ms, the host leaves between live frames it accepts. Absent\n * when the host does not say; a faster live frame is refused as\n * `overlays.throttled`.\n */\n liveInterval?: number\n}\n\n/** Params of `overlay.frame`. */\nexport interface OverlayFrameParams {\n overlayId: string\n /** The request this frame answers; omit for a live update of the latest request's extent. */\n requestId?: string\n extent: [number, number, number, number]\n /** Transparent, premultiplied-alpha image of the requested size; transferred. */\n image: ImageBitmap\n}\n\n/** Why the host refused a frame (a result, not an error). */\nexport type OverlayFrameRefusal = 'overlays.superseded' | 'overlays.throttled'\n\n/** Result of `overlay.frame`. */\nexport interface OverlayFrameResult {\n accepted: boolean\n reason?: OverlayFrameRefusal\n}\n\n/** Payload of an `overlay.state` host event — an overlay was shown, hidden, faded, suspended or resumed. */\nexport interface OverlayStateEvent {\n overlayId: string\n visible: boolean\n opacity: number\n suspended: boolean\n}\n\n/** Stable `error.data.reason` strings for `overlay.*` host-method failures. */\nexport type OverlayErrorReason =\n | 'overlays.unknownId'\n | 'overlays.badRequest'\n | 'overlays.notSupported'\n\n/**\n * Types for the `panels.state` capability — whether each of an extension's\n * loaded panels is on screen. `visible` is the host's presentation (its drawer\n * showing the panel, its dialog open), not the browser tab's visibility. A\n * panel shown in a window is a `window` context and is not covered here. See\n * the Plotter Extensions API spec, \"Panel state\".\n */\n\n/** A loaded panel's state — an entry of `ui.listPanels` and the payload of `panel.state`. */\nexport interface PanelState {\n /** The panel's manifest id. */\n panel: string\n /** Whether the host is presenting the panel in its UI. */\n visible: boolean\n /** Host feature: shown reduced to its header (still `visible`). Always `false` without it. */\n collapsed: boolean\n /** Configuration panels opened for a widget instance: the instance being configured. */\n targetInstance?: string\n /** Configuration panels opened for an image overlay: the overlay being configured. */\n targetOverlay?: string\n}\n\n/** Result of `ui.listPanels` — the caller's extension's loaded panels. */\nexport interface PanelListResult {\n panels: PanelState[]\n}\n\n/** Payload of a `panel.state` host event — a loaded panel was shown, hidden, collapsed or expanded. */\nexport type PanelStateEvent = PanelState\n\n/** Stable `error.data.reason` strings for `ui.listPanels` failures. */\nexport type PanelErrorReason = 'panels.notSupported'\n\n/**\n * Types for the `events.publish` capability — extension-originated events. A\n * context publishes a topic and the host delivers it, exactly like a host\n * event, to every subscribed context in `scope` (the publisher included). The\n * delivered event carries no sender identity. The `publish` button action\n * (deprecated alias `sendMessage`) is the same operation. See the Plotter\n * Extensions API spec, \"Publishing events\".\n */\n\n/** Who may receive a published event. */\nexport type PublishScope = 'all' | 'extension'\n\n/** Params of `events.publish`. */\nexport interface PublishParams {\n /** Literal event name: non-empty, no `*`, not in the `bus.*` namespace. */\n topic: string\n /** Delivered unchanged as the event's `params`. */\n params?: unknown\n /** Default `'all'`. */\n scope?: PublishScope\n}\n\n/** Stable `error.data.reason` strings for `events.publish` failures. */\nexport type PublishErrorReason = 'events.badRequest'\n", "/**\n * eventemitter2-style wildcard matching for dot-separated event names.\n *\n * - `*` matches exactly one segment.\n * - `**` matches zero or more segments (any remainder when trailing).\n */\nexport function matchesPattern(pattern: string, name: string): boolean {\n if (pattern === name) return true\n return match(pattern.split('.'), 0, name.split('.'), 0)\n}\n\nfunction match(p: string[], pi: number, n: string[], ni: number): boolean {\n while (pi < p.length) {\n const seg = p[pi]\n if (seg === '**') {\n if (pi === p.length - 1) return true\n for (let skip = ni; skip <= n.length; skip++) {\n if (match(p, pi + 1, n, skip)) return true\n }\n return false\n }\n if (ni >= n.length) return false\n if (seg !== '*' && seg !== n[ni]) return false\n pi++\n ni++\n }\n return ni === n.length\n}\n\nexport function matchesAny(patterns: Iterable<string>, name: string): boolean {\n for (const pattern of patterns) {\n if (matchesPattern(pattern, name)) return true\n }\n return false\n}\n", "/**\n * Lon/lat bounding boxes, as every box in the Plotter Extensions API is written:\n * `[west, south, east, north]` in decimal degrees (the GeoJSON / RFC 7946 §5\n * order), with longitudes in `[-180, 180]`. A box that crosses the antimeridian\n * has `west` greater than `east` (RFC 7946 §5.2): `[175, -21, -175, -13]` is ten\n * degrees wide around 180°. See the spec, \"Bounding boxes\".\n *\n * Map engines work in the *unwrapped* form instead — `west < east`, with\n * longitudes running past ±180 once the view scrolls into another copy of the\n * world. {@link normalizeBounds} converts either form to the API's.\n */\nexport type LonLatBounds = [number, number, number, number]\n\n/** `lon` wrapped into `[-180, 180]`; a longitude already in range is kept. */\nfunction wrapLon(lon: number): number {\n if (lon >= -180 && lon <= 180) {\n return lon\n }\n return ((((lon + 180) % 360) + 360) % 360) - 180\n}\n\n/**\n * Degrees of longitude a box in the API's form spans, 0–360: `east - west`, or\n * the way round the antimeridian when `west > east`. A box from `-180` to `180`\n * spans 360.\n */\nexport function boundsLonSpan(bounds: LonLatBounds): number {\n const [west, , east] = bounds\n return east >= west ? east - west : east - west + 360\n}\n\n/** Whether a box in the API's form takes in longitude `lon`. */\nexport function boundsContainsLon(bounds: LonLatBounds, lon: number): boolean {\n const [west, , east] = bounds\n const l = wrapLon(lon)\n return west <= east ? l >= west && l <= east : l >= west || l <= east\n}\n\n/**\n * A box in the API's form, from either that form or the unwrapped form map\n * engines use (`east` past 180 or `west` past -180, with `west < east`).\n * Longitudes are wrapped into `[-180, 180]`, so a box straddling 180° comes out\n * with `west > east`; a box 360 or more degrees wide becomes\n * `[-180, south, 180, north]`. Undefined when it is not a box: not four finite\n * numbers, a latitude outside `[-90, 90]`, or `south` greater than `north`.\n */\nexport function normalizeBounds(bounds: unknown): LonLatBounds | undefined {\n if (\n !Array.isArray(bounds) ||\n bounds.length !== 4 ||\n !bounds.every((v) => typeof v === 'number' && Number.isFinite(v))\n ) {\n return undefined\n }\n const [west, south, east, north] = bounds as LonLatBounds\n if (south < -90 || north > 90 || south > north) {\n return undefined\n }\n const w = wrapLon(west)\n const e = wrapLon(east)\n const span = west <= east ? east - west : boundsLonSpan([w, south, e, north])\n if (span >= 360) {\n return [-180, south, 180, north]\n }\n return [w, south, e, north]\n}\n", "import {\n BUS_ID,\n Envelope,\n JsonRpcMessage,\n JsonRpcNotification,\n JsonRpcRequest,\n JsonRpcResponse\n} from './protocol'\n\nexport function wrap(msg: JsonRpcMessage): Envelope {\n return { bus: BUS_ID, msg }\n}\n\n/**\n * Returns the JSON-RPC message inside a bus envelope, or null when the data\n * is not valid protocol traffic (so unrelated postMessage noise is ignored).\n */\nexport function unwrap(data: unknown): JsonRpcMessage | null {\n if (typeof data !== 'object' || data === null) return null\n const env = data as Record<string, unknown>\n if (env.bus !== BUS_ID) return null\n return isJsonRpcMessage(env.msg) ? env.msg : null\n}\n\nexport function isJsonRpcMessage(v: unknown): v is JsonRpcMessage {\n if (typeof v !== 'object' || v === null) return false\n const m = v as Record<string, unknown>\n if (m.jsonrpc !== '2.0') return false\n if (typeof m.method === 'string') {\n // Request (with id) or notification (without).\n return (\n m.id === undefined ||\n typeof m.id === 'string' ||\n typeof m.id === 'number'\n )\n }\n // Response: id required (null allowed for unroutable errors), exactly one\n // of result / error.\n const idOk =\n typeof m.id === 'string' || typeof m.id === 'number' || m.id === null\n if (!idOk) return false\n const hasResult = 'result' in m\n const err = m.error as Record<string, unknown> | undefined\n const hasError =\n typeof err === 'object' &&\n err !== null &&\n typeof err.code === 'number' &&\n typeof err.message === 'string'\n return hasResult ? !('error' in m) : hasError\n}\n\nexport function isRequest(msg: JsonRpcMessage): msg is JsonRpcRequest {\n return 'method' in msg && 'id' in msg && msg.id !== undefined\n}\n\nexport function isNotification(\n msg: JsonRpcMessage\n): msg is JsonRpcNotification {\n return 'method' in msg && (!('id' in msg) || msg.id === undefined)\n}\n\nexport function isResponse(msg: JsonRpcMessage): msg is JsonRpcResponse {\n return !('method' in msg)\n}\n", "/**\n * Transport abstraction. The bus runs over anything that can post and\n * receive structured-cloneable data: window.postMessage between a host page\n * and an iframe, a MessageChannel port, or a test double.\n */\nexport interface BusPort {\n /**\n * Send `data`. `transfer` lists objects (an `ImageBitmap`, an `ArrayBuffer`)\n * to move rather than copy; after the post the sender no longer owns them.\n */\n post(data: unknown, transfer?: Transferable[]): void\n /** Register a receive handler; returns an unlisten function. */\n listen(handler: (data: unknown) => void): () => void\n}\n\nexport interface WindowPortOptions {\n /**\n * targetOrigin for outgoing postMessage and required origin of incoming\n * messages. Defaults to the listening window's own origin (the baseline\n * deployment is same-origin). Pass '*' to disable origin checks.\n */\n origin?: string\n /** Window whose 'message' events are listened to. Defaults to globalThis. */\n listenWindow?: Window\n}\n\n/**\n * A port over window.postMessage to a peer window. On the host side the peer\n * is an iframe's contentWindow; on the extension side it is window.parent.\n * Only messages whose source is the peer (and whose origin matches) are\n * delivered.\n */\nexport function windowPort(peer: Window, opts: WindowPortOptions = {}): BusPort {\n const listenWindow =\n opts.listenWindow ?? (globalThis as unknown as Window)\n const origin = opts.origin ?? listenWindow.location?.origin ?? '*'\n return {\n post(data, transfer) {\n if (transfer?.length) {\n peer.postMessage(data, { targetOrigin: origin, transfer })\n } else {\n peer.postMessage(data, origin)\n }\n },\n listen(handler) {\n const fn = (ev: MessageEvent) => {\n if (ev.source !== peer) return\n if (origin !== '*' && ev.origin !== origin) return\n handler(ev.data)\n }\n listenWindow.addEventListener('message', fn as EventListener)\n return () =>\n listenWindow.removeEventListener('message', fn as EventListener)\n }\n }\n}\n\ninterface MessagePortLike {\n postMessage(data: unknown, transfer?: Transferable[]): void\n addEventListener(type: 'message', fn: (ev: { data: unknown }) => void): void\n removeEventListener(\n type: 'message',\n fn: (ev: { data: unknown }) => void\n ): void\n start?(): void\n}\n\n/** A port over a MessageChannel/MessagePort (browser or Node). */\nexport function messagePort(port: MessagePortLike): BusPort {\n return {\n post(data, transfer) {\n if (transfer?.length) {\n port.postMessage(data, transfer)\n } else {\n port.postMessage(data)\n }\n },\n listen(handler) {\n const fn = (ev: { data: unknown }) => handler(ev.data)\n port.addEventListener('message', fn)\n port.start?.()\n return () => port.removeEventListener('message', fn)\n }\n }\n}\n", "import {\n isNotification,\n isRequest,\n isResponse,\n unwrap,\n wrap\n} from './codec'\nimport {\n JsonRpcId,\n JsonRpcMessage,\n JsonRpcResponse,\n RPC_ERRORS,\n RpcError\n} from './protocol'\nimport { BusPort } from './port'\nimport { matchesAny } from './wildcard'\n\nexport interface MethodContext {\n endpoint: BusEndpoint\n}\n\nexport type MethodHandler = (\n params: unknown,\n ctx: MethodContext\n) => unknown | Promise<unknown>\n\nexport type EventHandler = (name: string, params: unknown) => void\n\nexport interface BusEndpointOptions {\n port: BusPort\n /** Default timeout for outgoing calls. */\n callTimeoutMs?: number\n /** Reporter for handler/dispatch errors. Defaults to console.warn. */\n onError?: (err: unknown) => void\n}\n\ninterface PendingCall {\n resolve: (value: unknown) => void\n reject: (err: RpcError) => void\n timer: ReturnType<typeof setTimeout> | null\n}\n\nconst DEFAULT_CALL_TIMEOUT_MS = 10_000\n\n/**\n * One end of the bus: sends/receives envelopes over a BusPort, dispatches\n * incoming requests to registered methods, correlates responses to pending\n * calls by per-call nonce, and fans incoming notifications out to wildcard\n * event handlers. Symmetric — both host and extension build on this.\n */\nexport class BusEndpoint {\n readonly callTimeoutMs: number\n\n private readonly port: BusPort\n private readonly unlisten: () => void\n private readonly onError: (err: unknown) => void\n private readonly pending = new Map<JsonRpcId, PendingCall>()\n private readonly methods = new Map<string, MethodHandler>()\n private readonly eventHandlers = new Set<{\n patterns: string[]\n fn: EventHandler\n }>()\n private readonly idPrefix = Math.random().toString(36).slice(2, 8)\n private seq = 0\n private closed = false\n\n constructor(opts: BusEndpointOptions) {\n this.port = opts.port\n this.callTimeoutMs = opts.callTimeoutMs ?? DEFAULT_CALL_TIMEOUT_MS\n this.onError =\n opts.onError ??\n ((err) => console.warn('[plotterext-bus]', err))\n this.unlisten = this.port.listen((data) => this.onData(data))\n }\n\n registerMethod(name: string, handler: MethodHandler): void {\n this.methods.set(name, handler)\n }\n\n unregisterMethod(name: string): void {\n this.methods.delete(name)\n }\n\n /**\n * Handle incoming notifications whose names match any of the wildcard\n * patterns. Returns an unsubscribe function. This is local dispatch only;\n * telling the peer which events to forward is a separate concern\n * (`events.subscribe`).\n */\n onEvent(patterns: string[], fn: EventHandler): () => void {\n const entry = { patterns, fn }\n this.eventHandlers.add(entry)\n return () => this.eventHandlers.delete(entry)\n }\n\n /**\n * Send a notification (an event) to the peer. `opts.transfer` moves the\n * listed objects instead of copying them.\n */\n notify(\n method: string,\n params?: unknown,\n opts: { transfer?: Transferable[] } = {}\n ): void {\n this.send(\n { jsonrpc: '2.0', method, ...(params !== undefined ? { params } : {}) },\n opts.transfer\n )\n }\n\n /**\n * Call a method on the peer; resolves with its result. `opts.transfer`\n * moves the listed objects (e.g. an `ImageBitmap` inside `params`) instead\n * of copying them.\n */\n call(\n method: string,\n params?: unknown,\n opts: { timeoutMs?: number; transfer?: Transferable[] } = {}\n ): Promise<unknown> {\n if (this.closed) {\n return Promise.reject(\n new RpcError('Bus endpoint is closed', {\n code: RPC_ERRORS.CONNECTION_CLOSED,\n reason: 'CLOSED'\n })\n )\n }\n const id = `${this.idPrefix}-${++this.seq}`\n const timeoutMs = opts.timeoutMs ?? this.callTimeoutMs\n return new Promise<unknown>((resolve, reject) => {\n const timer =\n timeoutMs > 0\n ? setTimeout(() => {\n this.pending.delete(id)\n reject(\n new RpcError(`Call timed out after ${timeoutMs}ms: ${method}`, {\n code: RPC_ERRORS.TIMEOUT,\n reason: 'TIMEOUT'\n })\n )\n }, timeoutMs)\n : null\n this.pending.set(id, { resolve, reject, timer })\n this.send(\n {\n jsonrpc: '2.0',\n id,\n method,\n ...(params !== undefined ? { params } : {})\n },\n opts.transfer\n )\n })\n }\n\n close(): void {\n if (this.closed) return\n this.closed = true\n this.unlisten()\n for (const [, p] of this.pending) {\n if (p.timer) clearTimeout(p.timer)\n p.reject(\n new RpcError('Bus endpoint closed', {\n code: RPC_ERRORS.CONNECTION_CLOSED,\n reason: 'CLOSED'\n })\n )\n }\n this.pending.clear()\n this.eventHandlers.clear()\n }\n\n private send(msg: JsonRpcMessage, transfer?: Transferable[]): void {\n if (this.closed) return\n this.port.post(wrap(msg), transfer)\n }\n\n private onData(data: unknown): void {\n const msg = unwrap(data)\n if (!msg) return\n if (isResponse(msg)) {\n this.onResponse(msg)\n } else if (isRequest(msg)) {\n void this.onRequest(msg)\n } else if (isNotification(msg)) {\n this.onNotification(msg.method, msg.params)\n }\n }\n\n private onResponse(msg: JsonRpcResponse): void {\n if (msg.id === null) return\n const p = this.pending.get(msg.id)\n if (!p) return\n this.pending.delete(msg.id)\n if (p.timer) clearTimeout(p.timer)\n if ('error' in msg) {\n p.reject(RpcError.fromErrorObject(msg.error))\n } else {\n p.resolve(msg.result)\n }\n }\n\n private async onRequest(msg: {\n id: JsonRpcId\n method: string\n params?: unknown\n }): Promise<void> {\n const handler = this.methods.get(msg.method)\n if (!handler) {\n this.send({\n jsonrpc: '2.0',\n id: msg.id,\n error: {\n code: RPC_ERRORS.METHOD_NOT_FOUND,\n message: `Method not found: ${msg.method}`\n }\n })\n return\n }\n try {\n const result = await handler(msg.params, { endpoint: this })\n this.send({\n jsonrpc: '2.0',\n id: msg.id,\n result: result === undefined ? null : result\n })\n } catch (err) {\n this.send({\n jsonrpc: '2.0',\n id: msg.id,\n error: RpcError.from(err).toErrorObject()\n })\n }\n }\n\n private onNotification(name: string, params: unknown): void {\n for (const entry of [...this.eventHandlers]) {\n if (matchesAny(entry.patterns, name)) {\n try {\n entry.fn(name, params)\n } catch (err) {\n this.onError(err)\n }\n }\n }\n }\n}\n", "import { BusEndpoint, EventHandler } from './endpoint'\nimport { BusPort, windowPort } from './port'\nimport {\n ChartLayer,\n EVENT_HANDSHAKE,\n EVENT_READY,\n Handshake,\n HandshakeContext,\n MapView,\n NightModeState,\n OverlayFrameParams,\n OverlayFrameResult,\n OverlayInfo,\n OverlayListResult,\n PanelListResult,\n PanelState,\n PublishScope,\n ReadyParams,\n ResourceGroupApplyResult,\n RouteData,\n RoutePoint,\n RouteSummary,\n RPC_ERRORS,\n RpcError,\n SignalKValueEvent,\n StateScope,\n WindowListResult,\n WindowOpenParams,\n WindowState,\n WindowUpdateParams\n} from './protocol'\n\nexport interface ConnectOptions {\n /** Transport. Defaults to window.postMessage to window.parent. */\n port?: BusPort\n /**\n * Caller-asserted context id, sent in the `bus.ready` payload. A host that\n * adopts caller ids (an embedding-host connection) uses it as `context.id`;\n * standard extension hosts ignore it. Omit for the standard extension case.\n */\n id?: string\n /** Interval for re-sending bus.ready until the handshake arrives. */\n readyIntervalMs?: number\n /** How long to wait for the host handshake before rejecting. */\n timeoutMs?: number\n /** Default timeout for host API calls. */\n callTimeoutMs?: number\n onError?: (err: unknown) => void\n}\n\nexport type Unsubscribe = () => Promise<void>\n\n/**\n * The extension side of the bus: one per iframe context (panel, widget, or\n * background runtime). Create via connectExtension().\n */\nexport class ExtensionClient {\n readonly handshake: Handshake\n readonly endpoint: BusEndpoint\n\n constructor(endpoint: BusEndpoint, handshake: Handshake) {\n this.endpoint = endpoint\n this.handshake = handshake\n }\n\n get context(): HandshakeContext {\n return this.handshake.context\n }\n\n get apiVersion(): string {\n return this.handshake.apiVersion\n }\n\n get capabilities(): string[] {\n return this.handshake.capabilities\n }\n\n hasCapability(id: string): boolean {\n return this.handshake.capabilities.includes(id)\n }\n\n /**\n * Call a host API method. `opts.transfer` moves the listed objects instead\n * of copying them — only `overlay.frame` needs it in this version.\n */\n call(\n method: string,\n params?: unknown,\n opts?: { timeoutMs?: number; transfer?: Transferable[] }\n ): Promise<unknown> {\n return this.endpoint.call(method, params, opts)\n }\n\n /** Send a notification to the host. */\n notify(method: string, params?: unknown): void {\n this.endpoint.notify(method, params)\n }\n\n /**\n * Subscribe to host events matching wildcard patterns. Registers both the\n * host-side forwarding subscription and local dispatch; the returned\n * function tears down both.\n */\n async subscribe(\n patterns: string[],\n handler: EventHandler\n ): Promise<Unsubscribe> {\n const off = this.endpoint.onEvent(patterns, handler)\n let subscriptionId: string\n try {\n const result = (await this.call('events.subscribe', { patterns })) as {\n subscriptionId: string\n }\n subscriptionId = result.subscriptionId\n } catch (err) {\n off()\n throw err\n }\n return async () => {\n off()\n await this.call('events.unsubscribe', { subscriptionId }).catch(() => {\n // Best-effort: the host may already have dropped the connection.\n })\n }\n }\n\n /**\n * Publish an event onto the bus (capability `events.publish`). The host\n * delivers it to every context in `scope` whose subscriptions match —\n * including this one — exactly as it delivers host events. `scope` defaults\n * to `'all'` (any extension); `'extension'` keeps it to this extension's own\n * contexts. Resolves once the host has delivered it; there is no reply.\n */\n async publish(\n topic: string,\n params?: unknown,\n scope?: PublishScope\n ): Promise<void> {\n await this.call('events.publish', {\n topic,\n ...(params !== undefined ? { params } : {}),\n ...(scope ? { scope } : {})\n })\n }\n\n /** Host-persisted key/value state (see spec: State Storage). */\n readonly state = {\n get: async (\n keys?: string[],\n scope?: StateScope\n ): Promise<Record<string, unknown>> => {\n const result = (await this.call('state.get', {\n ...(scope ? { scope } : {}),\n ...(keys ? { keys } : {})\n })) as { values: Record<string, unknown> }\n return result.values ?? {}\n },\n set: async (\n values: Record<string, unknown>,\n scope?: StateScope\n ): Promise<void> => {\n await this.call('state.set', {\n ...(scope ? { scope } : {}),\n values\n })\n }\n }\n\n /** Signal K data relayed by the host (capabilities signalk.stream / .put). */\n readonly signalk = {\n /**\n * Subscribe to Signal K path values. The host publishes them as\n * `sk.<path>` events; this helper hides the event-name mapping and\n * establishes both the event-forwarding subscription and the host's\n * upstream Signal K subscription.\n */\n subscribe: async (\n paths: string[],\n handler: (ev: SignalKValueEvent) => void\n ): Promise<Unsubscribe> => {\n const patterns = paths.map((p) => `sk.${p}`)\n const offEvents = await this.subscribe(patterns, (_name, params) =>\n handler(params as SignalKValueEvent)\n )\n let subscriptionId: string\n try {\n const result = (await this.call('signalk.subscribe', { paths })) as {\n subscriptionId: string\n }\n subscriptionId = result.subscriptionId\n } catch (err) {\n await offEvents()\n throw err\n }\n return async () => {\n await offEvents()\n await this.call('signalk.unsubscribe', { subscriptionId }).catch(\n () => {}\n )\n }\n },\n put: (path: string, value: unknown): Promise<unknown> => {\n return this.call('signalk.put', { path, value })\n }\n }\n\n /**\n * The host's visible routes (capability `routes`). Thin **typed** wrappers over\n * the host's `route.*` methods — each just delegates to `this.call(...)`, so a\n * plain-JS extension can call `client.call('route.replace', …)` directly and a\n * TypeScript extension gets the typed `client.route.replace(…)` sugar with no\n * behavioural difference. Follow lifecycle + mutations by subscribing to\n * `route.**` events (`RouteVisibleEvent` / `RouteDirtyEvent` / `RouteSavedEvent`\n * / `RouteHiddenEvent`). Further operations (rename/point ops) extend this\n * surface as the capability fills out.\n */\n readonly route = {\n list: async (): Promise<RouteSummary[]> => {\n const result = (await this.call('route.list')) as {\n routes?: RouteSummary[]\n }\n return result.routes ?? []\n },\n create: async (opts: {\n points: RoutePoint[]\n name?: string\n description?: string\n }): Promise<{ routeId: string; rev: number }> => {\n // A route needs at least two points to form a segment; the host rejects\n // fewer with routes.badRequest.\n return (await this.call('route.create', opts)) as {\n routeId: string\n rev: number\n }\n },\n get: async (routeId: string): Promise<RouteData> => {\n return (await this.call('route.get', { routeId })) as RouteData\n },\n replace: async (\n routeId: string,\n points: RoutePoint[]\n ): Promise<{ rev: number }> => {\n return (await this.call('route.replace', { routeId, points })) as {\n rev: number\n }\n },\n save: async (\n routeId: string,\n opts?: { name?: string; description?: string; dialog?: boolean }\n ): Promise<{ href: string; rev: number }> => {\n return (await this.call('route.save', {\n routeId,\n ...(opts ?? {})\n })) as { href: string; rev: number }\n },\n show: async (ref: string): Promise<{ routeId: string; rev: number }> => {\n return (await this.call('route.show', { ref })) as {\n routeId: string\n rev: number\n }\n },\n hide: async (routeId: string): Promise<void> => {\n await this.call('route.hide', { routeId })\n },\n delete: async (routeId: string): Promise<void> => {\n await this.call('route.delete', { routeId })\n }\n }\n\n /**\n * The host's chart layers (capability `charts`). Thin **typed** wrappers over\n * the host's `chart.*` methods — a lightweight facade over the charts the host\n * already manages: enumerate them, toggle visibility, opacity and stacking\n * order (all batch). It does not create, add, or delete chart sources. Follow\n * changes (from any origin, including the host's own chart controls) by\n * subscribing to `chart.**` events (`ChartVisibilityEvent` / `ChartOpacityEvent`\n * / `ChartOrderEvent` / `ChartTimeEvent`) and re-reading `chart.list` where\n * needed. As with every wrapper, a plain-JS extension can call\n * `client.call('chart.list', …)` directly with no behavioural difference.\n */\n readonly chart = {\n list: async (): Promise<ChartLayer[]> => {\n const result = (await this.call('chart.list')) as { charts?: ChartLayer[] }\n return result.charts ?? []\n },\n setVisibility: async (ids: string[], visible: boolean): Promise<void> => {\n await this.call('chart.setVisibility', { ids, visible })\n },\n setOpacity: async (ids: string[], opacity: number): Promise<void> => {\n await this.call('chart.setOpacity', { ids, opacity })\n },\n setOrder: async (order: string[]): Promise<void> => {\n await this.call('chart.setOrder', { order })\n },\n /**\n * Retarget time-varying charts to an ISO 8601 instant, or back to their\n * live frame with `null` (sub-capability `charts.time`). Follow changes via\n * the `chart.time` event (`ChartTimeEvent`).\n */\n setTime: async (ids: string[], time: string | null): Promise<void> => {\n await this.call('chart.setTime', { ids, time })\n }\n }\n\n /**\n * The host's night-vision display mode (capability `nightMode`). Read the\n * current `{ enabled, auto }` state, change it, and follow changes by\n * subscribing to the `nightMode.changed` event (`NightModeChangedEvent`).\n * `set` expresses the three states the spec defines: force on\n * (`{ enabled: true }`), force off (`{ enabled: false }`), follow the server\n * (`{ auto: true }`); setting `enabled` implies `auto: false`. As with every\n * wrapper, a plain-JS extension can call `client.call('nightMode.get' |\n * 'nightMode.set', …)` directly with no behavioural difference.\n */\n readonly nightMode = {\n get: async (): Promise<NightModeState> => {\n return (await this.call('nightMode.get')) as NightModeState\n },\n set: async (state: Partial<NightModeState>): Promise<void> => {\n await this.call('nightMode.set', state)\n }\n }\n\n /**\n * Panel visibility (capability `panels.state`): the state of each of this\n * extension's loaded panels. Follow changes with the `panel.state` event,\n * which every context of the extension receives; subscribe first, then\n * `list()`, so no change is missed. A plain-JS extension can call\n * `client.call('ui.listPanels')` with no behavioural difference.\n */\n readonly panels = {\n list: async (): Promise<PanelState[]> => {\n const r = (await this.call('ui.listPanels')) as PanelListResult\n return r.panels\n }\n }\n\n /**\n * Image map overlays (capability `overlays.image`): map layers this\n * extension's background runtime draws. The host asks for frames with the\n * `overlay.request` event (`OverlayRequestEvent`) and reports visibility\n * with `overlay.state` (`OverlayStateEvent`); the runtime answers each\n * request with `frame()`, keeping one frame in flight. `frame()` transfers\n * `image`, so the caller no longer owns it afterwards. A plain-JS extension\n * can call `client.call('overlay.frame', params, { transfer: [image] })`\n * with no behavioural difference.\n */\n readonly overlays = {\n list: async (): Promise<OverlayInfo[]> => {\n const r = (await this.call('overlay.list')) as OverlayListResult\n return r.overlays\n },\n setVisibility: async (ids: string[], visible: boolean): Promise<void> => {\n await this.call('overlay.setVisibility', { ids, visible })\n },\n setOpacity: async (ids: string[], opacity: number): Promise<void> => {\n await this.call('overlay.setOpacity', { ids, opacity })\n },\n frame: async (params: OverlayFrameParams): Promise<OverlayFrameResult> => {\n return (await this.call('overlay.frame', params, {\n transfer: [params.image]\n })) as OverlayFrameResult\n }\n }\n\n /**\n * Floating windows (capability `windows`): show one of this extension's\n * iframe panels in a window over the chart. Geometry is a request — read the\n * actual `bounds` from the returned `WindowState`, and follow changes with\n * the `window.bounds` / `window.state` / `window.closed` events. Windows\n * belong to the extension, so any of its contexts may manage them; in a\n * `window` context, `update` / `focus` / `close` default to the window\n * itself when `windowId` is left out. A plain-JS extension can call\n * `client.call('ui.openWindow', …)` and friends directly with no\n * behavioural difference.\n */\n readonly windows = {\n open: async (params: WindowOpenParams): Promise<WindowState> => {\n return (await this.call('ui.openWindow', params)) as WindowState\n },\n update: async (params: WindowUpdateParams): Promise<WindowState> => {\n return (await this.call('ui.updateWindow', params)) as WindowState\n },\n focus: async (windowId?: string): Promise<void> => {\n await this.call('ui.focusWindow', windowId === undefined ? {} : { windowId })\n },\n close: async (windowId?: string): Promise<void> => {\n await this.call('ui.closeWindow', windowId === undefined ? {} : { windowId })\n },\n list: async (): Promise<WindowState[]> => {\n const r = (await this.call('ui.listWindows')) as WindowListResult\n return r.windows\n }\n }\n\n /**\n * Apply a stored resource group to the host's display (capability\n * `resourceGroups`). Resolves to `{ applied }` — the types the host made a\n * best-effort attempt to carry out; a type it did not act on is omitted.\n * Follow applies from any origin (including the user's own group picker) via\n * the `resourceGroup.applied` event (`ResourceGroupAppliedEvent`). Group CRUD\n * is not here: use the server's resources API (`/resources/groups`). A\n * plain-JS extension can call `client.call('resourceGroup.apply', { id })`\n * directly with no behavioural difference.\n */\n readonly resourceGroup = {\n apply: async (id: string): Promise<ResourceGroupApplyResult> => {\n return (await this.call('resourceGroup.apply', {\n id\n })) as ResourceGroupApplyResult\n }\n }\n\n /**\n * The host's chart viewport (capability `map`). Read the current\n * `{ center, zoom, bounds }`, drive it, and follow it by subscribing to the\n * `map.view` event (`MapViewEvent`), which carries the same shape and fires\n * once per settled pan/zoom. Seed with `getView` so a follower has a view\n * before the first change. As with every wrapper, a plain-JS extension can\n * call `client.call('map.getView' | 'map.center' | 'map.fitBounds', …)`\n * directly with no behavioural difference.\n */\n readonly map = {\n getView: async (): Promise<MapView> => {\n return (await this.call('map.getView')) as MapView\n },\n center: async (\n position: [number, number],\n zoom?: number\n ): Promise<void> => {\n await this.call('map.center', {\n position,\n ...(typeof zoom === 'number' ? { zoom } : {})\n })\n },\n /**\n * Fit the chart to `[west, south, east, north]`. A box crossing the\n * antimeridian may be given as `west > east` (the API's form) or unwrapped\n * (`east` past 180); the host fits it the short way round.\n */\n fitBounds: async (\n bounds: [number, number, number, number]\n ): Promise<void> => {\n await this.call('map.fitBounds', { bounds })\n }\n }\n\n close(): void {\n this.endpoint.close()\n }\n}\n\n/**\n * Connect to the host from inside an extension iframe. Sends `bus.ready`\n * (repeating until answered) and resolves once the host's `bus.handshake`\n * arrives.\n */\nexport function connectExtension(\n opts: ConnectOptions = {}\n): Promise<ExtensionClient> {\n // Default transport: postMessage to the embedding window. Origin checks\n // are relaxed to '*' on the extension side because the host page's origin\n // may legitimately differ from the extension asset origin (e.g. a host dev\n // server embedding extension assets served by the Signal K server); the\n // peer-source check in windowPort still applies, and the host side\n // enforces a strict origin for the extension's frame.\n const port =\n opts.port ??\n windowPort((globalThis as unknown as Window).parent as Window, {\n origin: '*'\n })\n const endpoint = new BusEndpoint({\n port,\n callTimeoutMs: opts.callTimeoutMs,\n onError: opts.onError\n })\n const readyParams: ReadyParams | undefined = opts.id ? { id: opts.id } : undefined\n return new Promise<ExtensionClient>((resolve, reject) => {\n let done = false\n const off = endpoint.onEvent([EVENT_HANDSHAKE], (_name, params) => {\n if (done) return\n done = true\n cleanup()\n resolve(new ExtensionClient(endpoint, params as Handshake))\n })\n const interval = setInterval(\n () => endpoint.notify(EVENT_READY, readyParams),\n opts.readyIntervalMs ?? 250\n )\n const timeout = setTimeout(() => {\n if (done) return\n done = true\n cleanup()\n endpoint.close()\n reject(\n new RpcError('Timed out waiting for host handshake', {\n code: RPC_ERRORS.TIMEOUT,\n reason: 'HANDSHAKE_TIMEOUT'\n })\n )\n }, opts.timeoutMs ?? 10_000)\n const cleanup = () => {\n off()\n clearInterval(interval)\n clearTimeout(timeout)\n }\n endpoint.notify(EVENT_READY, readyParams)\n })\n}\n\nexport * from './protocol'\nexport * from './wildcard'\nexport * from './bounds'\nexport { BusEndpoint } from './endpoint'\nexport type { MethodHandler, MethodContext, EventHandler } from './endpoint'\nexport * from './port'\n", "/** The extension's user settings, shared by the config panel (which edits\n * them), the runtime (which applies them) and the render Worker (which draws\n * with them). Persisted in host state under the extension scope. */\n\nexport type DepthSource = \"history\" | \"tiles\";\n\nexport interface LiveDepthSettings {\n enabled: boolean;\n /** Where the soundings come from: the server's History API (the vessel's own\n * recent track) or the plugin's gridded bathymetry tiles. */\n source: DepthSource;\n /** History look-back window, in hours. */\n historyWindowHours: number;\n /** History sample interval, in seconds. */\n historyResolutionS: number;\n /** Depth (metres) the shading runs to; deeper is left to the chart. */\n shadeLimitMetres: number;\n /** Depth (metres) between isobaths. */\n contourIntervalMetres: number;\n /** Grid cell size in screen pixels; also the base label size. */\n cellPx: number;\n /** Fractional depth difference (0.15 = 15%) within which adjacent cells\n * merge into one group. */\n mergeTolerance: number;\n /** Samples deeper than this (metres) are not drawn. */\n maxDepthMetres: number;\n /** Samples at or below this depth (metres) are marked individually. */\n safetyDepthMetres: number;\n}\n\nexport const DEFAULT_SETTINGS: LiveDepthSettings = {\n enabled: true,\n source: \"history\",\n historyWindowHours: 1,\n historyResolutionS: 0.1,\n shadeLimitMetres: 10,\n contourIntervalMetres: 1,\n cellPx: 24,\n mergeTolerance: 0.15,\n maxDepthMetres: 100,\n safetyDepthMetres: 3,\n};\n\n/** The settings keys, with the bounds the config panel clamps to. */\nexport const SETTINGS_BOUNDS: Partial<\n Record<keyof LiveDepthSettings, { min: number; max: number; step: number }>\n> = {\n historyWindowHours: { min: 0.1, max: 168, step: 0.1 },\n historyResolutionS: { min: 0.01, max: 60, step: 0.01 },\n shadeLimitMetres: { min: 1, max: 200, step: 1 },\n contourIntervalMetres: { min: 0.5, max: 50, step: 0.5 },\n cellPx: { min: 4, max: 128, step: 1 },\n mergeTolerance: { min: 0, max: 1, step: 0.01 },\n maxDepthMetres: { min: 1, max: 500, step: 1 },\n safetyDepthMetres: { min: 0, max: 50, step: 0.5 },\n};\n\n/** Merge stored state over the defaults, ignoring unknown/ill-typed keys. */\nexport function mergeSettings(\n stored: Record<string, unknown> | undefined,\n): LiveDepthSettings {\n const out = { ...DEFAULT_SETTINGS };\n if (!stored) {\n return out;\n }\n for (const key of Object.keys(\n DEFAULT_SETTINGS,\n ) as (keyof LiveDepthSettings)[]) {\n const value = stored[key];\n if (typeof value === typeof DEFAULT_SETTINGS[key]) {\n (out[key] as unknown) = value;\n }\n }\n return out;\n}\n", "// The LiveDepth settings panel: a keepAlive iframe the host opens from the\n// LiveDepth toolbar button or the layer's \"Layer settings\" row. It edits the\n// extension's state, which the host persists and rebroadcasts (`state.changed`),\n// so the runtime picks changes up without a direct connection between the two\n// contexts.\n//\n// Depth-valued settings are shown in the user's preferred depth unit\n// (`units.get`) and stored in metres; the history-only settings are greyed while\n// the depth source is the plugin's tiles.\n\nimport { connectExtension } from \"signalk-plotterext-bus/extension\";\nimport {\n DEFAULT_SETTINGS,\n SETTINGS_BOUNDS,\n mergeSettings,\n type DepthSource,\n type LiveDepthSettings,\n} from \"./shared/settings.js\";\n\n/** Metres in a foot. */\nconst METRES_PER_FOOT = 0.3048;\n\nconst client = await connectExtension();\nlet settings: LiveDepthSettings = { ...DEFAULT_SETTINGS };\nlet depthUnit: \"m\" | \"foot\" = \"m\";\n\nconst root = document.getElementById(\"root\")!;\n\ninterface Field {\n key: keyof LiveDepthSettings;\n label: string;\n hint?: string;\n /** A unit shown after the input. */\n suffix?: string;\n /** Shown in the user's depth unit, stored in metres. */\n depth?: boolean;\n /** Only meaningful for the History source; greyed while the source is tiles. */\n historyOnly?: boolean;\n}\n\nconst FIELDS: Field[] = [\n {\n key: \"historyWindowHours\",\n label: \"History window\",\n hint: \"How far back to fetch depth.\",\n suffix: \"h\",\n historyOnly: true,\n },\n {\n key: \"historyResolutionS\",\n label: \"Sample interval\",\n hint: \"Seconds between depth samples; smaller is finer.\",\n suffix: \"s\",\n historyOnly: true,\n },\n {\n key: \"shadeLimitMetres\",\n label: \"Shade limit\",\n hint: \"Depth the shading runs to; deeper is left to the chart.\",\n depth: true,\n },\n {\n key: \"contourIntervalMetres\",\n label: \"Isobath interval\",\n hint: \"Depth between isobaths.\",\n depth: true,\n },\n {\n key: \"cellPx\",\n label: \"Grid cell\",\n hint: \"Circle diameter, in screen pixels.\",\n suffix: \"px\",\n },\n {\n key: \"mergeTolerance\",\n label: \"Merge band\",\n hint: \"Adjacent positions within this depth fraction merge into one circle.\",\n },\n {\n key: \"maxDepthMetres\",\n label: \"Max depth\",\n hint: \"Samples deeper than this are not drawn.\",\n depth: true,\n },\n {\n key: \"safetyDepthMetres\",\n label: \"Safety depth\",\n hint: \"Samples at or below this depth are marked individually.\",\n depth: true,\n },\n];\n\nconst unitSymbol = () => (depthUnit === \"foot\" ? \"ft\" : \"m\");\nconst toDisplay = (metres: number) =>\n depthUnit === \"foot\" ? metres / METRES_PER_FOOT : metres;\nconst fromDisplay = (value: number) =>\n depthUnit === \"foot\" ? value * METRES_PER_FOOT : value;\nconst round = (v: number) => Math.round(v * 100) / 100;\n\nconst historyRows: HTMLElement[] = [];\nconst historyInputs: HTMLInputElement[] = [];\n\n/** Grey the history-only fields while the source is the plugin's tiles. */\nfunction syncHistoryDisabled(): void {\n const disabled = settings.source === \"tiles\";\n for (const input of historyInputs) {\n input.disabled = disabled;\n }\n for (const row of historyRows) {\n row.classList.toggle(\"disabled\", disabled);\n }\n}\n\n/** The bounds a field is shown and clamped with, in the unit it is shown in.\n * A depth setting is stored in metres but read in the user's unit; converting\n * the metre bounds outright gives awkward values in feet (0.5 m = 1.64 ft), so\n * a typed whole foot would be snapped away. In feet the bounds are whole\n * numbers and the step is one; in metres they are used as-is. */\nfunction displayBounds(field: Field): {\n min: number;\n max: number;\n step: number;\n} {\n const b = SETTINGS_BOUNDS[field.key]!;\n if (!field.depth || depthUnit !== \"foot\") {\n return b;\n }\n return {\n min: Math.floor(toDisplay(b.min)),\n max: Math.round(toDisplay(b.max)),\n step: 1,\n };\n}\n\nfunction numberField(field: Field): HTMLElement {\n const { min: lo, max: hi, step } = displayBounds(field);\n\n const wrap = document.createElement(\"label\");\n wrap.className = \"field\";\n const name = document.createElement(\"span\");\n name.className = \"name\";\n name.textContent = field.label;\n wrap.appendChild(name);\n\n const control = document.createElement(\"span\");\n control.className = \"control\";\n const input = document.createElement(\"input\");\n input.type = \"number\";\n input.min = `${lo}`;\n input.max = `${hi}`;\n input.step = `${step}`;\n const stored = settings[field.key] as number;\n input.value = `${round(field.depth ? toDisplay(stored) : stored)}`;\n input.addEventListener(\"change\", () => {\n const clamped = Math.min(hi, Math.max(lo, Number(input.value)));\n input.value = `${round(clamped)}`;\n (settings[field.key] as number) = field.depth\n ? fromDisplay(clamped)\n : clamped;\n void save();\n });\n control.appendChild(input);\n const suffix = document.createElement(\"span\");\n suffix.className = \"suffix\";\n suffix.textContent = field.depth ? unitSymbol() : (field.suffix ?? \"\");\n control.appendChild(suffix);\n wrap.appendChild(control);\n\n if (field.hint) {\n const hint = document.createElement(\"span\");\n hint.className = \"hint\";\n hint.textContent = field.hint;\n wrap.appendChild(hint);\n }\n\n if (field.historyOnly) {\n historyRows.push(wrap);\n historyInputs.push(input);\n }\n return wrap;\n}\n\nfunction build(): void {\n root.replaceChildren();\n historyRows.length = 0;\n historyInputs.length = 0;\n\n const heading = document.createElement(\"h2\");\n heading.textContent = \"LiveDepth\";\n root.appendChild(heading);\n\n const enabled = document.createElement(\"label\");\n enabled.className = \"field row\";\n const enabledName = document.createElement(\"span\");\n enabledName.className = \"name\";\n enabledName.textContent = \"Show the layer\";\n const enabledInput = document.createElement(\"input\");\n enabledInput.type = \"checkbox\";\n enabledInput.checked = settings.enabled;\n enabledInput.addEventListener(\"change\", () => {\n settings.enabled = enabledInput.checked;\n void save();\n });\n enabled.append(enabledName, enabledInput);\n root.appendChild(enabled);\n\n const source = document.createElement(\"label\");\n source.className = \"field\";\n const sourceName = document.createElement(\"span\");\n sourceName.className = \"name\";\n sourceName.textContent = \"Depth source\";\n const select = document.createElement(\"select\");\n for (const [value, label] of [\n [\"history\", \"History (this vessel's track)\"],\n [\"tiles\", \"LiveDepth plugin tiles\"],\n ] as [DepthSource, string][]) {\n const option = document.createElement(\"option\");\n option.value = value;\n option.textContent = label;\n option.selected = settings.source === value;\n select.appendChild(option);\n }\n select.addEventListener(\"change\", () => {\n settings.source = select.value as DepthSource;\n syncHistoryDisabled();\n void save();\n });\n source.append(sourceName, select);\n const sourceHint = document.createElement(\"span\");\n sourceHint.className = \"hint\";\n sourceHint.textContent =\n \"History draws the vessel's own recorded track; tiles draw the \" +\n \"server-built bathymetry store, extended live.\";\n source.appendChild(sourceHint);\n root.appendChild(source);\n\n for (const field of FIELDS) {\n root.appendChild(numberField(field));\n }\n syncHistoryDisabled();\n}\n\nasync function save(): Promise<void> {\n await client.state.set(settings as unknown as Record<string, unknown>);\n}\n\nasync function load(): Promise<void> {\n try {\n settings = mergeSettings(\n (await client.state.get()) as Record<string, unknown>,\n );\n } catch {\n settings = { ...DEFAULT_SETTINGS };\n }\n try {\n const res = (await client.call(\"units.get\")) as {\n units?: { depth?: string };\n };\n depthUnit = res?.units?.depth === \"foot\" ? \"foot\" : \"m\";\n } catch {\n depthUnit = \"m\";\n }\n build();\n}\n\nawait load();\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["/**\n * Wire protocol types and constants for the Signal K plotter extension bus.\n *\n * The wire format is JSON-RPC 2.0 (https://www.jsonrpc.org/specification)\n * inside a routing envelope: `{ bus: \"plotterExt/1\", msg: <JSON-RPC object> }`.\n * Calls are JSON-RPC requests; events are JSON-RPC notifications whose\n * `method` is a hierarchical dot-separated event name.\n */\n\nexport const BUS_ID = 'plotterExt/1'\n\nexport type JsonRpcId = string | number\n\nexport interface JsonRpcRequest {\n jsonrpc: '2.0'\n id: JsonRpcId\n method: string\n params?: unknown\n}\n\nexport interface JsonRpcNotification {\n jsonrpc: '2.0'\n method: string\n params?: unknown\n}\n\nexport interface JsonRpcErrorObject {\n code: number\n message: string\n data?: { reason?: string; [key: string]: unknown }\n}\n\nexport interface JsonRpcSuccessResponse {\n jsonrpc: '2.0'\n id: JsonRpcId\n result: unknown\n}\n\nexport interface JsonRpcErrorResponse {\n jsonrpc: '2.0'\n id: JsonRpcId | null\n error: JsonRpcErrorObject\n}\n\nexport type JsonRpcResponse = JsonRpcSuccessResponse | JsonRpcErrorResponse\n\nexport type JsonRpcMessage =\n | JsonRpcRequest\n | JsonRpcNotification\n | JsonRpcResponse\n\nexport interface Envelope {\n bus: typeof BUS_ID\n msg: JsonRpcMessage\n}\n\n/**\n * JSON-RPC reserved codes for protocol errors, plus implementation-defined\n * codes (-32000..-32099 range) used by this package. Host API errors should\n * use HOST_ERROR with a stable string identifier in `error.data.reason`.\n */\nexport const RPC_ERRORS = {\n PARSE_ERROR: -32700,\n INVALID_REQUEST: -32600,\n METHOD_NOT_FOUND: -32601,\n INVALID_PARAMS: -32602,\n INTERNAL_ERROR: -32603,\n HOST_ERROR: -32000,\n TIMEOUT: -32001,\n CONNECTION_CLOSED: -32002\n} as const\n\nexport class RpcError extends Error {\n readonly code: number\n readonly data?: { reason?: string; [key: string]: unknown }\n\n constructor(\n message: string,\n opts: {\n code?: number\n reason?: string\n data?: Record<string, unknown>\n } = {}\n ) {\n super(message)\n this.name = 'RpcError'\n this.code = opts.code ?? RPC_ERRORS.HOST_ERROR\n const data: Record<string, unknown> = { ...(opts.data ?? {}) }\n if (opts.reason !== undefined) data.reason = opts.reason\n this.data = Object.keys(data).length > 0 ? data : undefined\n }\n\n get reason(): string | undefined {\n return typeof this.data?.reason === 'string' ? this.data.reason : undefined\n }\n\n toErrorObject(): JsonRpcErrorObject {\n return {\n code: this.code,\n message: this.message,\n ...(this.data ? { data: this.data } : {})\n }\n }\n\n static fromErrorObject(err: JsonRpcErrorObject): RpcError {\n return new RpcError(err.message, { code: err.code, data: err.data })\n }\n\n /** Normalize any thrown value into an RpcError suitable for the wire. */\n static from(err: unknown): RpcError {\n if (err instanceof RpcError) return err\n if (err instanceof Error) {\n return new RpcError(err.message, { code: RPC_ERRORS.INTERNAL_ERROR })\n }\n return new RpcError(String(err), { code: RPC_ERRORS.INTERNAL_ERROR })\n }\n}\n\n/** Reserved event names used to establish a connection. */\nexport const EVENT_READY = 'bus.ready'\nexport const EVENT_HANDSHAKE = 'bus.handshake'\n\n/**\n * Optional payload a caller may send with `bus.ready`. Additive and\n * backward-compatible: a host that predates it (or does not adopt caller ids)\n * ignores it.\n */\nexport interface ReadyParams {\n /**\n * Caller-asserted context id. A host that adopts caller ids — an\n * embedding-host connection, where the host does not know the caller in\n * advance — uses it as `context.id`; otherwise it is ignored.\n */\n id?: string\n}\n\nexport type ContextKind =\n | 'panel'\n | 'widget'\n | 'window'\n | 'background'\n | 'embedding-host'\n\nexport interface HandshakeContext {\n kind: ContextKind\n /** Manifest-local contribution id. */\n id: string\n /** Host-assigned unique id for this placed instance (widgets). */\n instanceId?: string | null\n /** Widget instance a configuration panel was opened for. */\n targetInstance?: string | null\n /** Manifest-local widget id of the target instance (configuration panels). */\n targetWidget?: string | null\n /** Image overlay a configuration panel was opened for (capability `overlays.image`). */\n targetOverlay?: string | null\n /** Host-assigned id of this window (`window` contexts; capability `windows`). */\n windowId?: string\n /** The `params` the window was opened with (`window` contexts). */\n params?: Record<string, unknown>\n}\n\nexport interface Handshake {\n host: string\n hostVersion: string\n apiVersion: string\n capabilities: string[]\n context: HandshakeContext\n}\n\n/** Payload of an `sk.<path>` Signal K value event. */\nexport interface SignalKValueEvent {\n path: string\n value: unknown\n timestamp?: string\n $source?: string\n}\n\nexport type StateScope = 'instance' | 'extension'\n\n/** Payload of a `state.changed` host event. */\nexport interface StateChangedEvent {\n scope: StateScope\n instanceId?: string | null\n keys: string[]\n}\n\n/**\n * Types for the `routes` capability — the routes the host currently has visible\n * on the chart (drafts plus stored routes the user is displaying), each\n * addressed by an opaque host-assigned `routeId`. Extensions read/write them and\n * follow lifecycle + mutation events (`route.visible` / `route.dirty` /\n * `route.saved` / `route.hidden`). Two orthogonal flags travel with a route:\n * `saved` (backed by a persisted resource) and `dirty` (has pending unsaved\n * changes). See the Plotter Extensions API spec, \"Live routes\".\n */\n\n/** A single point in a route edit buffer (`[lon, lat, alt?]`). */\nexport interface RoutePoint {\n position: [number, number, number?]\n name?: string\n description?: string\n /**\n * Link to a saved waypoint, as a Signal K resource path\n * (`/resources/waypoints/<id>`). A reference, not a copy: `position` alone\n * defines the route's geometry, and the host does not keep the point and the\n * waypoint in step — drop the link when moving the point off its waypoint. A\n * host built before this field existed drops it. See the Plotter Extensions\n * API spec, \"Waypoint links\".\n */\n href?: string\n}\n\n/** Snapshot of a route — result of `route.get`. */\nexport interface RouteData {\n routeId: string\n name: string | null\n /** Route-level description (distinct from a waypoint's RoutePoint.description). */\n description: string | null\n /** Monotonic revision; increments on every mutation. */\n rev: number\n /** Whether the route is backed by a persisted routes resource. */\n saved: boolean\n /** Whether the in-memory route has pending unsaved changes. */\n dirty: boolean\n points: RoutePoint[]\n}\n\n/** Summary entry in a `route.list` result (the visible set). */\nexport interface RouteSummary {\n routeId: string\n name: string | null\n rev: number\n pointCount: number\n saved: boolean\n dirty: boolean\n}\n\n/**\n * Payload of a `route.visible` host event — a route entered the visible set\n * (became rendered on the chart). A freshly drawn/created draft arrives\n * `saved:false, dirty:true`; a stored route brought into view arrives\n * `saved:true, dirty:false`.\n */\nexport interface RouteVisibleEvent {\n routeId: string\n rev: number\n name: string | null\n pointCount: number\n saved: boolean\n dirty: boolean\n}\n\n/**\n * Payload of a `route.hidden` host event — a route left the visible set.\n * `saved:true` ⇒ a stored route was made invisible (the resource is untouched);\n * `saved:false` ⇒ an unsaved draft was deleted (gone for good).\n */\nexport interface RouteHiddenEvent {\n routeId: string\n rev: number\n saved: boolean\n}\n\n/**\n * Payload of a `route.dirty` host event — the conformance-floor catch-all for\n * any structural/bulk change the host does not express granularly. A subscriber\n * should re-seed via `route.get`.\n */\nexport interface RouteDirtyEvent {\n routeId: string\n rev: number\n reason?: string\n}\n\n/**\n * Payload of a `route.saved` host event — the route's current state was\n * persisted to the routes resource collection. Arrives `saved:true,\n * dirty:false`; the route stays visible under the same `routeId`.\n */\nexport interface RouteSavedEvent {\n routeId: string\n rev: number\n /** Stored resource id of the persisted route. */\n href: string\n /** The route's name as persisted (may have just been set in the host's save\n * dialog), so followers can update a label without re-fetching. */\n name: string | null\n saved: boolean\n dirty: boolean\n}\n\n/** Stable `error.data.reason` strings for `route.*` host-method failures. */\nexport type RouteErrorReason =\n | 'routes.unknownId'\n | 'routes.badRequest'\n | 'routes.badRef'\n | 'routes.saveFailed'\n | 'routes.deleteFailed'\n | 'routes.saveCancelled'\n | 'routes.notSupported'\n\n/**\n * Types for the `charts` capability — a lightweight facade over the chart\n * layers the host already manages. An extension enumerates them, reads which\n * are shown and in what stacking order, and toggles visibility, opacity and\n * order. It does **not** add, create, or delete chart sources — only manages\n * charts the host already knows about. Follow changes (from any origin,\n * including the user's own chart controls) via the `chart.visibility` /\n * `chart.opacity` / `chart.order` events. With the `charts.time` sub-capability\n * a host also retargets time-varying charts (`chart.setTime`, `ChartTime`,\n * `chart.time`). See the Plotter Extensions API spec, \"Chart layers\" and\n * \"Time-varying charts\".\n */\n\n/**\n * One chart layer the host manages — an entry in a `chart.list` result. The\n * array order is the display/stacking order (index 0 = topmost/frontmost).\n */\nexport interface ChartLayer {\n /** Opaque, stable, host-assigned id. Treat as a token; never parse it. */\n id: string\n /** Human-readable chart name. */\n name: string\n /** Whether the chart is currently displayed. */\n visible: boolean\n /** Display opacity, 0..1. */\n opacity: number\n /** Best-effort source kind, e.g. 'raster' | 'vector' | 'S-57' | 'WMS'. */\n type?: string\n /**\n * Geographic extent `[west, south, east, north]`, when known. Longitudes are\n * in `[-180, 180]`; a chart that crosses the antimeridian has `west > east`.\n * See `LonLatBounds`.\n */\n bounds?: [number, number, number, number]\n /** Minimum usable zoom level, when known. */\n minZoom?: number\n /** Maximum usable zoom level, when known. */\n maxZoom?: number\n /**\n * Present only on a time-addressable chart (hosts with `charts.time`): the\n * instant currently shown and the source's timeline, as far as the host knows\n * it. Absent on a chart with no time dimension.\n */\n time?: ChartTime\n}\n\n/**\n * The time dimension of a time-varying chart (weather radar, satellite,\n * nowcast) — the `time` object on a `chart.list` entry. `value` and `current`\n * are always present; the timeline fields are best-effort metadata as the host\n * last learned them (from the chart resource or the source's capabilities),\n * not the contract's source of truth — a rolling product's newest frame moves\n * on between reads.\n */\nexport interface ChartTime {\n /** Instant currently shown (ISO 8601), or `null` for the live/current frame. */\n value: string | null\n /** Whether the source serves a live/current frame (so `null` is a valid target). */\n current: boolean\n /** Earliest instant offered (ISO 8601), when known. */\n from?: string\n /** Latest instant offered (ISO 8601), when known. */\n to?: string\n /** Milliseconds between frames, when the timeline is regular. */\n step?: number\n /** The explicit instants offered (ISO 8601), when the timeline is a list. */\n values?: string[]\n}\n\n/**\n * Params of `chart.setTime` — retarget each named time-varying chart to an\n * instant (ISO 8601) or back to its live frame (`null`). The host passes the\n * instant through to the source without snapping or clamping.\n */\nexport interface ChartSetTimeParams {\n ids: string[]\n time: string | null\n}\n\n/**\n * Payload of a `chart.time` host event — a time-varying chart was retargeted\n * (`time` is an ISO 8601 instant) or returned to live (`time: null`). Emitted\n * for every change regardless of origin; a batch `chart.setTime` emits one\n * event per changed chart.\n */\nexport interface ChartTimeEvent {\n id: string\n time: string | null\n}\n\n/**\n * Payload of a `chart.visibility` host event — one chart's display was turned\n * on or off. A batch `chart.setVisibility` emits one event per changed chart,\n * as does the user toggling a chart in the host's own chart controls.\n */\nexport interface ChartVisibilityEvent {\n id: string\n visible: boolean\n}\n\n/** Payload of a `chart.opacity` host event — one chart's opacity changed. */\nexport interface ChartOpacityEvent {\n id: string\n /** New opacity, 0..1. */\n opacity: number\n}\n\n/**\n * Payload of a `chart.order` host event — the display/stacking order changed.\n * `order` is the new full order, topmost first (the same order `chart.list`\n * returns).\n */\nexport interface ChartOrderEvent {\n order: string[]\n}\n\n/** Stable `error.data.reason` strings for `chart.*` host-method failures. */\nexport type ChartErrorReason =\n | 'charts.unknownId'\n | 'charts.badRequest'\n | 'charts.notSupported'\n | 'charts.notTemporal'\n\n/**\n * Types for the `nightMode` capability — the host's night-vision display mode\n * (a dimmed, low-blue appearance for use after dark). An extension reads the\n * current state, changes it, and follows changes so an embedded panel matches\n * the host instead of glowing white on a dark bridge. See the Plotter\n * Extensions API spec, \"Night mode\".\n */\n\n/**\n * The host's night-mode state — the `nightMode.get` result and the payload of\n * the `nightMode.changed` event.\n */\nexport interface NightModeState {\n /** Whether night mode is currently applied (the resolved state the user sees). */\n enabled: boolean\n /**\n * Whether the host is deriving `enabled` from the server's `environment.mode`\n * (`night` -> on). While `true`, `enabled` tracks the server automatically.\n */\n auto: boolean\n}\n\n/**\n * Payload of a `nightMode.changed` host event — the night-mode state changed.\n * Emitted **origin-transparently** for every change: an extension's own\n * `nightMode.set`, the user toggling the host's night-mode control, or the\n * server's `environment.mode` flipping while `auto` is on.\n */\nexport type NightModeChangedEvent = NightModeState\n\n/** Stable `error.data.reason` strings for `nightMode.*` host-method failures. */\nexport type NightModeErrorReason =\n | 'nightMode.badRequest'\n | 'nightMode.notSupported'\n\n/**\n * Types for the `map` capability — the chart viewport. An extension reads it\n * with `map.getView`, drives it with `map.center` / `map.fitBounds`, and follows\n * it with the `map.view` event. See the Plotter Extensions API spec, \"Map view\".\n */\n\n/**\n * The chart viewport — the `map.getView` result and the payload of the\n * `map.view` event, which carry the same shape.\n */\nexport interface MapView {\n /** Viewport centre as `[lon, lat]`. */\n center: [number, number]\n /** Current zoom level; may be fractional. */\n zoom: number\n /**\n * Axis-aligned lon/lat box covering what is rendered, as\n * `[west, south, east, north]`. On a host whose map can be rotated this is the\n * box containing the rotated view — \"at least this much is on screen\".\n * Longitudes are always in `[-180, 180]`, even when the chart has been panned\n * into another copy of the world; a view straddling the antimeridian has\n * `west > east`, and one showing every longitude is `-180` to `180`. See\n * `LonLatBounds`.\n */\n bounds: [number, number, number, number]\n}\n\n/**\n * Payload of a `map.view` host event — the chart viewport was panned and/or\n * zoomed. Emitted once the view has **settled** (the gesture and any kinetic\n * glide have come to rest), not continuously during the gesture, and\n * **origin-transparently**: for the user dragging the chart, the host recentring\n * on the vessel, or an extension's own `map.center` / `map.fitBounds`.\n */\nexport type MapViewEvent = MapView\n\n/**\n * Types for the `resourceGroups` capability — applying a stored resource group\n * to the host's display. Groups are ordinary Signal K resources in the `groups`\n * collection; an extension creates, edits and lists them through the server's\n * resources API. The capability adds only what the host alone can do: apply one.\n * See the Plotter Extensions API spec, \"Resource groups\".\n */\n\n/** The resource types a group can carry an instruction for. */\nexport type ResourceGroupType = 'routes' | 'waypoints' | 'regions' | 'charts'\n\n/**\n * A resource group document, as stored at `/resources/groups/{id}`. Each list\n * is an instruction for its type, with three distinct meanings:\n * - `[\"a\", …]` — display these (ids that resolve to no resource are ignored);\n * - `[]` — display none of this type;\n * - key absent — leave this type's display as it is.\n * `[]` and an absent key are **not** interchangeable.\n */\nexport interface ResourceGroup {\n name: string\n description?: string\n routes?: string[]\n waypoints?: string[]\n regions?: string[]\n charts?: string[]\n}\n\n/** Params of `resourceGroup.apply`. */\nexport interface ResourceGroupApplyParams {\n /** Id of the group in the server's `groups` collection. */\n id: string\n}\n\n/**\n * Result of `resourceGroup.apply`. `applied` lists every type for which the\n * host made a best-effort attempt to carry out the group's instruction. A type\n * the host did not act on — or whose key is absent from the group — is omitted.\n * It reports what the host **attempted**, not the resulting display.\n */\nexport interface ResourceGroupApplyResult {\n applied: ResourceGroupType[]\n}\n\n/**\n * Payload of a `resourceGroup.applied` host event — a group was applied.\n * Emitted **origin-transparently**: for an extension's `resourceGroup.apply`\n * and for the user choosing a group in the host's own UI. It reports an action,\n * not a readable state — there is no \"current group\" query.\n */\nexport interface ResourceGroupAppliedEvent extends ResourceGroupApplyResult {\n /** Id of the group that was applied. */\n id: string\n}\n\n/** Stable `error.data.reason` strings for `resourceGroup.*` host-method failures. */\nexport type ResourceGroupErrorReason =\n | 'resourceGroups.unknownId'\n | 'resourceGroups.fetchFailed'\n | 'resourceGroups.badRequest'\n | 'resourceGroups.notSupported'\n\n/**\n * Types for the `windows` capability — an extension's iframe panels shown as\n * floating windows over the chart. Each window is its own context\n * (`context.kind === 'window'`) carrying `windowId` and `params`. Geometry the\n * extension sends is a request; the host clamps it and reports the actual\n * `bounds` in the window state. See the Plotter Extensions API spec, \"Windows\".\n */\n\n/** Where a window is anchored in the host's window area. */\nexport type WindowAnchor =\n | 'top-left'\n | 'top-center'\n | 'top-right'\n | 'center-left'\n | 'center'\n | 'center-right'\n | 'bottom-left'\n | 'bottom-center'\n | 'bottom-right'\n\n/**\n * A length: CSS pixels as a number, or a percentage of the window area as a\n * string such as `\"40%\"`.\n */\nexport type WindowLength = number | `${number}%`\n\n/**\n * Requested size and position, relative to the host's window area. `offset` is\n * measured inward from the anchored edges (on a centered axis, positive moves\n * right/down). Fields left out take the host's choice (on open) or keep their\n * current value (on update).\n */\nexport interface WindowGeometry {\n anchor?: WindowAnchor\n offset?: { x?: WindowLength; y?: WindowLength }\n width?: WindowLength\n height?: WindowLength\n minWidth?: WindowLength\n minHeight?: WindowLength\n maxWidth?: WindowLength\n maxHeight?: WindowLength\n}\n\n/** Params of `ui.openWindow`. */\nexport interface WindowOpenParams {\n /** Id of an iframe panel in the caller's manifest. */\n panel: string\n /** Handed to the window in its handshake as `context.params`. */\n params?: Record<string, unknown>\n /** Title-bar text; defaults to the panel's `title`. */\n title?: string\n geometry?: WindowGeometry\n /** Block the chart and every other window until closed. Default `false`. */\n modal?: boolean\n /** Whether the user may resize it. Default `true`. */\n resizable?: boolean\n /** Whether the user may move it. Default `true`. */\n movable?: boolean\n /** Default `'fixed'`; `'autoHide'` hides the title bar when idle. */\n titleBar?: 'fixed' | 'autoHide'\n /** What the user's close control does. Default `'close'`. */\n userClose?: 'close' | 'hide'\n /** `false` opens the window hidden (loaded, not shown). Default `true`. */\n visible?: boolean\n /** Reuse an open window of this panel instead of opening another. */\n single?: boolean\n /** Remember this window's geometry under this key (per extension). */\n restoreKey?: string\n}\n\n/** Params of `ui.updateWindow`. `windowId` defaults to self in a window context. */\nexport interface WindowUpdateParams {\n windowId?: string\n title?: string\n geometry?: WindowGeometry\n visible?: boolean\n}\n\n/** A window's actual position and size, in CSS px from the window area's top-left. */\nexport interface WindowBounds {\n x: number\n y: number\n width: number\n height: number\n}\n\n/** The size of the host's window area, in CSS px. */\nexport interface WindowArea {\n width: number\n height: number\n}\n\n/** How the host is showing a window. */\nexport type WindowPresentation = 'floating' | 'sheet' | 'fullscreen'\n\n/**\n * A window's state — the result of `ui.openWindow` / `ui.updateWindow`, an\n * entry of `ui.listWindows`, and the payload of `window.state`.\n */\nexport interface WindowState {\n windowId: string\n /** The manifest panel the window shows. */\n panel: string\n title: string\n presentation: WindowPresentation\n bounds: WindowBounds\n area: WindowArea\n visible: boolean\n /** Host feature: collapsed to its title bar (still running). */\n collapsed: boolean\n /** Host feature: popped out into a separate browser window. */\n poppedOut: boolean\n modal: boolean\n}\n\n/** Result of `ui.listWindows` — the caller's extension's open windows. */\nexport interface WindowListResult {\n windows: WindowState[]\n}\n\n/** Payload of a `window.bounds` host event — a window's actual geometry changed. */\nexport interface WindowBoundsEvent {\n windowId: string\n bounds: WindowBounds\n area: WindowArea\n}\n\n/** Payload of a `window.state` host event (visibility, collapse, pop-out, presentation or title changed). */\nexport type WindowStateEvent = WindowState\n\n/** Why a window closed. */\nexport type WindowCloseReason = 'user' | 'extension' | 'host'\n\n/** Payload of a `window.closed` host event — the window closed and its page unloaded. */\nexport interface WindowClosedEvent {\n windowId: string\n reason: WindowCloseReason\n}\n\n/** Stable `error.data.reason` strings for window host-method failures. */\nexport type WindowErrorReason =\n | 'windows.badRequest'\n | 'windows.unknownId'\n | 'windows.limit'\n | 'windows.modalOpen'\n | 'windows.notSupported'\n | 'UNKNOWN_PANEL'\n\n/**\n * Types for the `overlays.image` capability — map layers whose pixels an\n * extension's background runtime draws and the host composites. The host asks\n * for frames (`overlay.request`); the runtime answers with `overlay.frame`,\n * whose `image` is an `ImageBitmap` sent in the `postMessage` transfer list —\n * the one non-JSON value on the wire. See the Plotter Extensions API spec,\n * \"Map overlays\".\n */\n\n/** Where an overlay sits relative to the host's own content. */\nexport type OverlayBand = 'aboveCharts' | 'aboveResources'\n\n/** An overlay's current state — an entry of `overlay.list`. */\nexport interface OverlayInfo {\n /** The overlay's manifest id. */\n id: string\n title: string\n band: OverlayBand\n visible: boolean\n /** 0..1 */\n opacity: number\n /** The host has paused the overlay to stay within its budget. */\n suspended: boolean\n}\n\n/** Result of `overlay.list` — the caller's extension's overlays. */\nexport interface OverlayListResult {\n overlays: OverlayInfo[]\n}\n\n/**\n * Payload of an `overlay.request` host event — the host wants a frame. The\n * image covers `extent`, a north-up axis-aligned box in EPSG:3857 metres\n * `[minX, minY, maxX, maxY]` (it may run past ±20037508.34 across the\n * antimeridian), with pixels spaced evenly in Mercator; row 0 is the north\n * edge.\n */\nexport interface OverlayRequestEvent {\n overlayId: string\n requestId: string\n extent: [number, number, number, number]\n /** The same box as lon/lat `[west, south, east, north]` (west > east across the antimeridian). */\n bounds: [number, number, number, number]\n /** Image size in pixels. */\n width: number\n height: number\n /** Image pixels per CSS pixel, chosen by the host. */\n pixelRatio: number\n /** Metres per image pixel. */\n resolution: number\n zoom: number\n /** Map rotation, radians clockwise. Advisory: for keeping labels upright only. */\n rotation: number\n /**\n * Least time, in ms, the host leaves between live frames it accepts. Absent\n * when the host does not say; a faster live frame is refused as\n * `overlays.throttled`.\n */\n liveInterval?: number\n}\n\n/** Params of `overlay.frame`. */\nexport interface OverlayFrameParams {\n overlayId: string\n /** The request this frame answers; omit for a live update of the latest request's extent. */\n requestId?: string\n extent: [number, number, number, number]\n /** Transparent, premultiplied-alpha image of the requested size; transferred. */\n image: ImageBitmap\n}\n\n/** Why the host refused a frame (a result, not an error). */\nexport type OverlayFrameRefusal = 'overlays.superseded' | 'overlays.throttled'\n\n/** Result of `overlay.frame`. */\nexport interface OverlayFrameResult {\n accepted: boolean\n reason?: OverlayFrameRefusal\n}\n\n/** Payload of an `overlay.state` host event — an overlay was shown, hidden, faded, suspended or resumed. */\nexport interface OverlayStateEvent {\n overlayId: string\n visible: boolean\n opacity: number\n suspended: boolean\n}\n\n/** Stable `error.data.reason` strings for `overlay.*` host-method failures. */\nexport type OverlayErrorReason =\n | 'overlays.unknownId'\n | 'overlays.badRequest'\n | 'overlays.notSupported'\n\n/**\n * Types for the `panels.state` capability — whether each of an extension's\n * loaded panels is on screen. `visible` is the host's presentation (its drawer\n * showing the panel, its dialog open), not the browser tab's visibility. A\n * panel shown in a window is a `window` context and is not covered here. See\n * the Plotter Extensions API spec, \"Panel state\".\n */\n\n/** A loaded panel's state — an entry of `ui.listPanels` and the payload of `panel.state`. */\nexport interface PanelState {\n /** The panel's manifest id. */\n panel: string\n /** Whether the host is presenting the panel in its UI. */\n visible: boolean\n /** Host feature: shown reduced to its header (still `visible`). Always `false` without it. */\n collapsed: boolean\n /** Configuration panels opened for a widget instance: the instance being configured. */\n targetInstance?: string\n /** Configuration panels opened for an image overlay: the overlay being configured. */\n targetOverlay?: string\n}\n\n/** Result of `ui.listPanels` — the caller's extension's loaded panels. */\nexport interface PanelListResult {\n panels: PanelState[]\n}\n\n/** Payload of a `panel.state` host event — a loaded panel was shown, hidden, collapsed or expanded. */\nexport type PanelStateEvent = PanelState\n\n/** Stable `error.data.reason` strings for `ui.listPanels` failures. */\nexport type PanelErrorReason = 'panels.notSupported'\n\n/**\n * Types for the `events.publish` capability — extension-originated events. A\n * context publishes a topic and the host delivers it, exactly like a host\n * event, to every subscribed context in `scope` (the publisher included). The\n * delivered event carries no sender identity. The `publish` button action\n * (deprecated alias `sendMessage`) is the same operation. See the Plotter\n * Extensions API spec, \"Publishing events\".\n */\n\n/** Who may receive a published event. */\nexport type PublishScope = 'all' | 'extension'\n\n/** Params of `events.publish`. */\nexport interface PublishParams {\n /** Literal event name: non-empty, no `*`, not in the `bus.*` namespace. */\n topic: string\n /** Delivered unchanged as the event's `params`. */\n params?: unknown\n /** Default `'all'`. */\n scope?: PublishScope\n}\n\n/** Stable `error.data.reason` strings for `events.publish` failures. */\nexport type PublishErrorReason = 'events.badRequest'\n", "/**\n * eventemitter2-style wildcard matching for dot-separated event names.\n *\n * - `*` matches exactly one segment.\n * - `**` matches zero or more segments (any remainder when trailing).\n */\nexport function matchesPattern(pattern: string, name: string): boolean {\n if (pattern === name) return true\n return match(pattern.split('.'), 0, name.split('.'), 0)\n}\n\nfunction match(p: string[], pi: number, n: string[], ni: number): boolean {\n while (pi < p.length) {\n const seg = p[pi]\n if (seg === '**') {\n if (pi === p.length - 1) return true\n for (let skip = ni; skip <= n.length; skip++) {\n if (match(p, pi + 1, n, skip)) return true\n }\n return false\n }\n if (ni >= n.length) return false\n if (seg !== '*' && seg !== n[ni]) return false\n pi++\n ni++\n }\n return ni === n.length\n}\n\nexport function matchesAny(patterns: Iterable<string>, name: string): boolean {\n for (const pattern of patterns) {\n if (matchesPattern(pattern, name)) return true\n }\n return false\n}\n", "/**\n * Lon/lat bounding boxes, as every box in the Plotter Extensions API is written:\n * `[west, south, east, north]` in decimal degrees (the GeoJSON / RFC 7946 §5\n * order), with longitudes in `[-180, 180]`. A box that crosses the antimeridian\n * has `west` greater than `east` (RFC 7946 §5.2): `[175, -21, -175, -13]` is ten\n * degrees wide around 180°. See the spec, \"Bounding boxes\".\n *\n * Map engines work in the *unwrapped* form instead — `west < east`, with\n * longitudes running past ±180 once the view scrolls into another copy of the\n * world. {@link normalizeBounds} converts either form to the API's.\n */\nexport type LonLatBounds = [number, number, number, number]\n\n/** `lon` wrapped into `[-180, 180]`; a longitude already in range is kept. */\nfunction wrapLon(lon: number): number {\n if (lon >= -180 && lon <= 180) {\n return lon\n }\n return ((((lon + 180) % 360) + 360) % 360) - 180\n}\n\n/**\n * Degrees of longitude a box in the API's form spans, 0–360: `east - west`, or\n * the way round the antimeridian when `west > east`. A box from `-180` to `180`\n * spans 360.\n */\nexport function boundsLonSpan(bounds: LonLatBounds): number {\n const [west, , east] = bounds\n return east >= west ? east - west : east - west + 360\n}\n\n/** Whether a box in the API's form takes in longitude `lon`. */\nexport function boundsContainsLon(bounds: LonLatBounds, lon: number): boolean {\n const [west, , east] = bounds\n const l = wrapLon(lon)\n return west <= east ? l >= west && l <= east : l >= west || l <= east\n}\n\n/**\n * A box in the API's form, from either that form or the unwrapped form map\n * engines use (`east` past 180 or `west` past -180, with `west < east`).\n * Longitudes are wrapped into `[-180, 180]`, so a box straddling 180° comes out\n * with `west > east`; a box 360 or more degrees wide becomes\n * `[-180, south, 180, north]`. Undefined when it is not a box: not four finite\n * numbers, a latitude outside `[-90, 90]`, or `south` greater than `north`.\n */\nexport function normalizeBounds(bounds: unknown): LonLatBounds | undefined {\n if (\n !Array.isArray(bounds) ||\n bounds.length !== 4 ||\n !bounds.every((v) => typeof v === 'number' && Number.isFinite(v))\n ) {\n return undefined\n }\n const [west, south, east, north] = bounds as LonLatBounds\n if (south < -90 || north > 90 || south > north) {\n return undefined\n }\n const w = wrapLon(west)\n const e = wrapLon(east)\n const span = west <= east ? east - west : boundsLonSpan([w, south, e, north])\n if (span >= 360) {\n return [-180, south, 180, north]\n }\n return [w, south, e, north]\n}\n", "import {\n BUS_ID,\n Envelope,\n JsonRpcMessage,\n JsonRpcNotification,\n JsonRpcRequest,\n JsonRpcResponse\n} from './protocol'\n\nexport function wrap(msg: JsonRpcMessage): Envelope {\n return { bus: BUS_ID, msg }\n}\n\n/**\n * Returns the JSON-RPC message inside a bus envelope, or null when the data\n * is not valid protocol traffic (so unrelated postMessage noise is ignored).\n */\nexport function unwrap(data: unknown): JsonRpcMessage | null {\n if (typeof data !== 'object' || data === null) return null\n const env = data as Record<string, unknown>\n if (env.bus !== BUS_ID) return null\n return isJsonRpcMessage(env.msg) ? env.msg : null\n}\n\nexport function isJsonRpcMessage(v: unknown): v is JsonRpcMessage {\n if (typeof v !== 'object' || v === null) return false\n const m = v as Record<string, unknown>\n if (m.jsonrpc !== '2.0') return false\n if (typeof m.method === 'string') {\n // Request (with id) or notification (without).\n return (\n m.id === undefined ||\n typeof m.id === 'string' ||\n typeof m.id === 'number'\n )\n }\n // Response: id required (null allowed for unroutable errors), exactly one\n // of result / error.\n const idOk =\n typeof m.id === 'string' || typeof m.id === 'number' || m.id === null\n if (!idOk) return false\n const hasResult = 'result' in m\n const err = m.error as Record<string, unknown> | undefined\n const hasError =\n typeof err === 'object' &&\n err !== null &&\n typeof err.code === 'number' &&\n typeof err.message === 'string'\n return hasResult ? !('error' in m) : hasError\n}\n\nexport function isRequest(msg: JsonRpcMessage): msg is JsonRpcRequest {\n return 'method' in msg && 'id' in msg && msg.id !== undefined\n}\n\nexport function isNotification(\n msg: JsonRpcMessage\n): msg is JsonRpcNotification {\n return 'method' in msg && (!('id' in msg) || msg.id === undefined)\n}\n\nexport function isResponse(msg: JsonRpcMessage): msg is JsonRpcResponse {\n return !('method' in msg)\n}\n", "/**\n * Transport abstraction. The bus runs over anything that can post and\n * receive structured-cloneable data: window.postMessage between a host page\n * and an iframe, a MessageChannel port, or a test double.\n */\nexport interface BusPort {\n /**\n * Send `data`. `transfer` lists objects (an `ImageBitmap`, an `ArrayBuffer`)\n * to move rather than copy; after the post the sender no longer owns them.\n */\n post(data: unknown, transfer?: Transferable[]): void\n /** Register a receive handler; returns an unlisten function. */\n listen(handler: (data: unknown) => void): () => void\n}\n\nexport interface WindowPortOptions {\n /**\n * targetOrigin for outgoing postMessage and required origin of incoming\n * messages. Defaults to the listening window's own origin (the baseline\n * deployment is same-origin). Pass '*' to disable origin checks.\n */\n origin?: string\n /** Window whose 'message' events are listened to. Defaults to globalThis. */\n listenWindow?: Window\n}\n\n/**\n * A port over window.postMessage to a peer window. On the host side the peer\n * is an iframe's contentWindow; on the extension side it is window.parent.\n * Only messages whose source is the peer (and whose origin matches) are\n * delivered.\n */\nexport function windowPort(peer: Window, opts: WindowPortOptions = {}): BusPort {\n const listenWindow =\n opts.listenWindow ?? (globalThis as unknown as Window)\n const origin = opts.origin ?? listenWindow.location?.origin ?? '*'\n return {\n post(data, transfer) {\n if (transfer?.length) {\n peer.postMessage(data, { targetOrigin: origin, transfer })\n } else {\n peer.postMessage(data, origin)\n }\n },\n listen(handler) {\n const fn = (ev: MessageEvent) => {\n if (ev.source !== peer) return\n if (origin !== '*' && ev.origin !== origin) return\n handler(ev.data)\n }\n listenWindow.addEventListener('message', fn as EventListener)\n return () =>\n listenWindow.removeEventListener('message', fn as EventListener)\n }\n }\n}\n\ninterface MessagePortLike {\n postMessage(data: unknown, transfer?: Transferable[]): void\n addEventListener(type: 'message', fn: (ev: { data: unknown }) => void): void\n removeEventListener(\n type: 'message',\n fn: (ev: { data: unknown }) => void\n ): void\n start?(): void\n}\n\n/** A port over a MessageChannel/MessagePort (browser or Node). */\nexport function messagePort(port: MessagePortLike): BusPort {\n return {\n post(data, transfer) {\n if (transfer?.length) {\n port.postMessage(data, transfer)\n } else {\n port.postMessage(data)\n }\n },\n listen(handler) {\n const fn = (ev: { data: unknown }) => handler(ev.data)\n port.addEventListener('message', fn)\n port.start?.()\n return () => port.removeEventListener('message', fn)\n }\n }\n}\n", "import {\n isNotification,\n isRequest,\n isResponse,\n unwrap,\n wrap\n} from './codec'\nimport {\n JsonRpcId,\n JsonRpcMessage,\n JsonRpcResponse,\n RPC_ERRORS,\n RpcError\n} from './protocol'\nimport { BusPort } from './port'\nimport { matchesAny } from './wildcard'\n\nexport interface MethodContext {\n endpoint: BusEndpoint\n}\n\nexport type MethodHandler = (\n params: unknown,\n ctx: MethodContext\n) => unknown | Promise<unknown>\n\nexport type EventHandler = (name: string, params: unknown) => void\n\nexport interface BusEndpointOptions {\n port: BusPort\n /** Default timeout for outgoing calls. */\n callTimeoutMs?: number\n /** Reporter for handler/dispatch errors. Defaults to console.warn. */\n onError?: (err: unknown) => void\n}\n\ninterface PendingCall {\n resolve: (value: unknown) => void\n reject: (err: RpcError) => void\n timer: ReturnType<typeof setTimeout> | null\n}\n\nconst DEFAULT_CALL_TIMEOUT_MS = 10_000\n\n/**\n * One end of the bus: sends/receives envelopes over a BusPort, dispatches\n * incoming requests to registered methods, correlates responses to pending\n * calls by per-call nonce, and fans incoming notifications out to wildcard\n * event handlers. Symmetric — both host and extension build on this.\n */\nexport class BusEndpoint {\n readonly callTimeoutMs: number\n\n private readonly port: BusPort\n private readonly unlisten: () => void\n private readonly onError: (err: unknown) => void\n private readonly pending = new Map<JsonRpcId, PendingCall>()\n private readonly methods = new Map<string, MethodHandler>()\n private readonly eventHandlers = new Set<{\n patterns: string[]\n fn: EventHandler\n }>()\n private readonly idPrefix = Math.random().toString(36).slice(2, 8)\n private seq = 0\n private closed = false\n\n constructor(opts: BusEndpointOptions) {\n this.port = opts.port\n this.callTimeoutMs = opts.callTimeoutMs ?? DEFAULT_CALL_TIMEOUT_MS\n this.onError =\n opts.onError ??\n ((err) => console.warn('[plotterext-bus]', err))\n this.unlisten = this.port.listen((data) => this.onData(data))\n }\n\n registerMethod(name: string, handler: MethodHandler): void {\n this.methods.set(name, handler)\n }\n\n unregisterMethod(name: string): void {\n this.methods.delete(name)\n }\n\n /**\n * Handle incoming notifications whose names match any of the wildcard\n * patterns. Returns an unsubscribe function. This is local dispatch only;\n * telling the peer which events to forward is a separate concern\n * (`events.subscribe`).\n */\n onEvent(patterns: string[], fn: EventHandler): () => void {\n const entry = { patterns, fn }\n this.eventHandlers.add(entry)\n return () => this.eventHandlers.delete(entry)\n }\n\n /**\n * Send a notification (an event) to the peer. `opts.transfer` moves the\n * listed objects instead of copying them.\n */\n notify(\n method: string,\n params?: unknown,\n opts: { transfer?: Transferable[] } = {}\n ): void {\n this.send(\n { jsonrpc: '2.0', method, ...(params !== undefined ? { params } : {}) },\n opts.transfer\n )\n }\n\n /**\n * Call a method on the peer; resolves with its result. `opts.transfer`\n * moves the listed objects (e.g. an `ImageBitmap` inside `params`) instead\n * of copying them.\n */\n call(\n method: string,\n params?: unknown,\n opts: { timeoutMs?: number; transfer?: Transferable[] } = {}\n ): Promise<unknown> {\n if (this.closed) {\n return Promise.reject(\n new RpcError('Bus endpoint is closed', {\n code: RPC_ERRORS.CONNECTION_CLOSED,\n reason: 'CLOSED'\n })\n )\n }\n const id = `${this.idPrefix}-${++this.seq}`\n const timeoutMs = opts.timeoutMs ?? this.callTimeoutMs\n return new Promise<unknown>((resolve, reject) => {\n const timer =\n timeoutMs > 0\n ? setTimeout(() => {\n this.pending.delete(id)\n reject(\n new RpcError(`Call timed out after ${timeoutMs}ms: ${method}`, {\n code: RPC_ERRORS.TIMEOUT,\n reason: 'TIMEOUT'\n })\n )\n }, timeoutMs)\n : null\n this.pending.set(id, { resolve, reject, timer })\n this.send(\n {\n jsonrpc: '2.0',\n id,\n method,\n ...(params !== undefined ? { params } : {})\n },\n opts.transfer\n )\n })\n }\n\n close(): void {\n if (this.closed) return\n this.closed = true\n this.unlisten()\n for (const [, p] of this.pending) {\n if (p.timer) clearTimeout(p.timer)\n p.reject(\n new RpcError('Bus endpoint closed', {\n code: RPC_ERRORS.CONNECTION_CLOSED,\n reason: 'CLOSED'\n })\n )\n }\n this.pending.clear()\n this.eventHandlers.clear()\n }\n\n private send(msg: JsonRpcMessage, transfer?: Transferable[]): void {\n if (this.closed) return\n this.port.post(wrap(msg), transfer)\n }\n\n private onData(data: unknown): void {\n const msg = unwrap(data)\n if (!msg) return\n if (isResponse(msg)) {\n this.onResponse(msg)\n } else if (isRequest(msg)) {\n void this.onRequest(msg)\n } else if (isNotification(msg)) {\n this.onNotification(msg.method, msg.params)\n }\n }\n\n private onResponse(msg: JsonRpcResponse): void {\n if (msg.id === null) return\n const p = this.pending.get(msg.id)\n if (!p) return\n this.pending.delete(msg.id)\n if (p.timer) clearTimeout(p.timer)\n if ('error' in msg) {\n p.reject(RpcError.fromErrorObject(msg.error))\n } else {\n p.resolve(msg.result)\n }\n }\n\n private async onRequest(msg: {\n id: JsonRpcId\n method: string\n params?: unknown\n }): Promise<void> {\n const handler = this.methods.get(msg.method)\n if (!handler) {\n this.send({\n jsonrpc: '2.0',\n id: msg.id,\n error: {\n code: RPC_ERRORS.METHOD_NOT_FOUND,\n message: `Method not found: ${msg.method}`\n }\n })\n return\n }\n try {\n const result = await handler(msg.params, { endpoint: this })\n this.send({\n jsonrpc: '2.0',\n id: msg.id,\n result: result === undefined ? null : result\n })\n } catch (err) {\n this.send({\n jsonrpc: '2.0',\n id: msg.id,\n error: RpcError.from(err).toErrorObject()\n })\n }\n }\n\n private onNotification(name: string, params: unknown): void {\n for (const entry of [...this.eventHandlers]) {\n if (matchesAny(entry.patterns, name)) {\n try {\n entry.fn(name, params)\n } catch (err) {\n this.onError(err)\n }\n }\n }\n }\n}\n", "import { BusEndpoint, EventHandler } from './endpoint'\nimport { BusPort, windowPort } from './port'\nimport {\n ChartLayer,\n EVENT_HANDSHAKE,\n EVENT_READY,\n Handshake,\n HandshakeContext,\n MapView,\n NightModeState,\n OverlayFrameParams,\n OverlayFrameResult,\n OverlayInfo,\n OverlayListResult,\n PanelListResult,\n PanelState,\n PublishScope,\n ReadyParams,\n ResourceGroupApplyResult,\n RouteData,\n RoutePoint,\n RouteSummary,\n RPC_ERRORS,\n RpcError,\n SignalKValueEvent,\n StateScope,\n WindowListResult,\n WindowOpenParams,\n WindowState,\n WindowUpdateParams\n} from './protocol'\n\nexport interface ConnectOptions {\n /** Transport. Defaults to window.postMessage to window.parent. */\n port?: BusPort\n /**\n * Caller-asserted context id, sent in the `bus.ready` payload. A host that\n * adopts caller ids (an embedding-host connection) uses it as `context.id`;\n * standard extension hosts ignore it. Omit for the standard extension case.\n */\n id?: string\n /** Interval for re-sending bus.ready until the handshake arrives. */\n readyIntervalMs?: number\n /** How long to wait for the host handshake before rejecting. */\n timeoutMs?: number\n /** Default timeout for host API calls. */\n callTimeoutMs?: number\n onError?: (err: unknown) => void\n}\n\nexport type Unsubscribe = () => Promise<void>\n\n/**\n * The extension side of the bus: one per iframe context (panel, widget, or\n * background runtime). Create via connectExtension().\n */\nexport class ExtensionClient {\n readonly handshake: Handshake\n readonly endpoint: BusEndpoint\n\n constructor(endpoint: BusEndpoint, handshake: Handshake) {\n this.endpoint = endpoint\n this.handshake = handshake\n }\n\n get context(): HandshakeContext {\n return this.handshake.context\n }\n\n get apiVersion(): string {\n return this.handshake.apiVersion\n }\n\n get capabilities(): string[] {\n return this.handshake.capabilities\n }\n\n hasCapability(id: string): boolean {\n return this.handshake.capabilities.includes(id)\n }\n\n /**\n * Call a host API method. `opts.transfer` moves the listed objects instead\n * of copying them — only `overlay.frame` needs it in this version.\n */\n call(\n method: string,\n params?: unknown,\n opts?: { timeoutMs?: number; transfer?: Transferable[] }\n ): Promise<unknown> {\n return this.endpoint.call(method, params, opts)\n }\n\n /** Send a notification to the host. */\n notify(method: string, params?: unknown): void {\n this.endpoint.notify(method, params)\n }\n\n /**\n * Subscribe to host events matching wildcard patterns. Registers both the\n * host-side forwarding subscription and local dispatch; the returned\n * function tears down both.\n */\n async subscribe(\n patterns: string[],\n handler: EventHandler\n ): Promise<Unsubscribe> {\n const off = this.endpoint.onEvent(patterns, handler)\n let subscriptionId: string\n try {\n const result = (await this.call('events.subscribe', { patterns })) as {\n subscriptionId: string\n }\n subscriptionId = result.subscriptionId\n } catch (err) {\n off()\n throw err\n }\n return async () => {\n off()\n await this.call('events.unsubscribe', { subscriptionId }).catch(() => {\n // Best-effort: the host may already have dropped the connection.\n })\n }\n }\n\n /**\n * Publish an event onto the bus (capability `events.publish`). The host\n * delivers it to every context in `scope` whose subscriptions match —\n * including this one — exactly as it delivers host events. `scope` defaults\n * to `'all'` (any extension); `'extension'` keeps it to this extension's own\n * contexts. Resolves once the host has delivered it; there is no reply.\n */\n async publish(\n topic: string,\n params?: unknown,\n scope?: PublishScope\n ): Promise<void> {\n await this.call('events.publish', {\n topic,\n ...(params !== undefined ? { params } : {}),\n ...(scope ? { scope } : {})\n })\n }\n\n /** Host-persisted key/value state (see spec: State Storage). */\n readonly state = {\n get: async (\n keys?: string[],\n scope?: StateScope\n ): Promise<Record<string, unknown>> => {\n const result = (await this.call('state.get', {\n ...(scope ? { scope } : {}),\n ...(keys ? { keys } : {})\n })) as { values: Record<string, unknown> }\n return result.values ?? {}\n },\n set: async (\n values: Record<string, unknown>,\n scope?: StateScope\n ): Promise<void> => {\n await this.call('state.set', {\n ...(scope ? { scope } : {}),\n values\n })\n }\n }\n\n /** Signal K data relayed by the host (capabilities signalk.stream / .put). */\n readonly signalk = {\n /**\n * Subscribe to Signal K path values. The host publishes them as\n * `sk.<path>` events; this helper hides the event-name mapping and\n * establishes both the event-forwarding subscription and the host's\n * upstream Signal K subscription.\n */\n subscribe: async (\n paths: string[],\n handler: (ev: SignalKValueEvent) => void\n ): Promise<Unsubscribe> => {\n const patterns = paths.map((p) => `sk.${p}`)\n const offEvents = await this.subscribe(patterns, (_name, params) =>\n handler(params as SignalKValueEvent)\n )\n let subscriptionId: string\n try {\n const result = (await this.call('signalk.subscribe', { paths })) as {\n subscriptionId: string\n }\n subscriptionId = result.subscriptionId\n } catch (err) {\n await offEvents()\n throw err\n }\n return async () => {\n await offEvents()\n await this.call('signalk.unsubscribe', { subscriptionId }).catch(\n () => {}\n )\n }\n },\n put: (path: string, value: unknown): Promise<unknown> => {\n return this.call('signalk.put', { path, value })\n }\n }\n\n /**\n * The host's visible routes (capability `routes`). Thin **typed** wrappers over\n * the host's `route.*` methods — each just delegates to `this.call(...)`, so a\n * plain-JS extension can call `client.call('route.replace', …)` directly and a\n * TypeScript extension gets the typed `client.route.replace(…)` sugar with no\n * behavioural difference. Follow lifecycle + mutations by subscribing to\n * `route.**` events (`RouteVisibleEvent` / `RouteDirtyEvent` / `RouteSavedEvent`\n * / `RouteHiddenEvent`). Further operations (rename/point ops) extend this\n * surface as the capability fills out.\n */\n readonly route = {\n list: async (): Promise<RouteSummary[]> => {\n const result = (await this.call('route.list')) as {\n routes?: RouteSummary[]\n }\n return result.routes ?? []\n },\n create: async (opts: {\n points: RoutePoint[]\n name?: string\n description?: string\n }): Promise<{ routeId: string; rev: number }> => {\n // A route needs at least two points to form a segment; the host rejects\n // fewer with routes.badRequest.\n return (await this.call('route.create', opts)) as {\n routeId: string\n rev: number\n }\n },\n get: async (routeId: string): Promise<RouteData> => {\n return (await this.call('route.get', { routeId })) as RouteData\n },\n replace: async (\n routeId: string,\n points: RoutePoint[]\n ): Promise<{ rev: number }> => {\n return (await this.call('route.replace', { routeId, points })) as {\n rev: number\n }\n },\n save: async (\n routeId: string,\n opts?: { name?: string; description?: string; dialog?: boolean }\n ): Promise<{ href: string; rev: number }> => {\n return (await this.call('route.save', {\n routeId,\n ...(opts ?? {})\n })) as { href: string; rev: number }\n },\n show: async (ref: string): Promise<{ routeId: string; rev: number }> => {\n return (await this.call('route.show', { ref })) as {\n routeId: string\n rev: number\n }\n },\n hide: async (routeId: string): Promise<void> => {\n await this.call('route.hide', { routeId })\n },\n delete: async (routeId: string): Promise<void> => {\n await this.call('route.delete', { routeId })\n }\n }\n\n /**\n * The host's chart layers (capability `charts`). Thin **typed** wrappers over\n * the host's `chart.*` methods — a lightweight facade over the charts the host\n * already manages: enumerate them, toggle visibility, opacity and stacking\n * order (all batch). It does not create, add, or delete chart sources. Follow\n * changes (from any origin, including the host's own chart controls) by\n * subscribing to `chart.**` events (`ChartVisibilityEvent` / `ChartOpacityEvent`\n * / `ChartOrderEvent` / `ChartTimeEvent`) and re-reading `chart.list` where\n * needed. As with every wrapper, a plain-JS extension can call\n * `client.call('chart.list', …)` directly with no behavioural difference.\n */\n readonly chart = {\n list: async (): Promise<ChartLayer[]> => {\n const result = (await this.call('chart.list')) as { charts?: ChartLayer[] }\n return result.charts ?? []\n },\n setVisibility: async (ids: string[], visible: boolean): Promise<void> => {\n await this.call('chart.setVisibility', { ids, visible })\n },\n setOpacity: async (ids: string[], opacity: number): Promise<void> => {\n await this.call('chart.setOpacity', { ids, opacity })\n },\n setOrder: async (order: string[]): Promise<void> => {\n await this.call('chart.setOrder', { order })\n },\n /**\n * Retarget time-varying charts to an ISO 8601 instant, or back to their\n * live frame with `null` (sub-capability `charts.time`). Follow changes via\n * the `chart.time` event (`ChartTimeEvent`).\n */\n setTime: async (ids: string[], time: string | null): Promise<void> => {\n await this.call('chart.setTime', { ids, time })\n }\n }\n\n /**\n * The host's night-vision display mode (capability `nightMode`). Read the\n * current `{ enabled, auto }` state, change it, and follow changes by\n * subscribing to the `nightMode.changed` event (`NightModeChangedEvent`).\n * `set` expresses the three states the spec defines: force on\n * (`{ enabled: true }`), force off (`{ enabled: false }`), follow the server\n * (`{ auto: true }`); setting `enabled` implies `auto: false`. As with every\n * wrapper, a plain-JS extension can call `client.call('nightMode.get' |\n * 'nightMode.set', …)` directly with no behavioural difference.\n */\n readonly nightMode = {\n get: async (): Promise<NightModeState> => {\n return (await this.call('nightMode.get')) as NightModeState\n },\n set: async (state: Partial<NightModeState>): Promise<void> => {\n await this.call('nightMode.set', state)\n }\n }\n\n /**\n * Panel visibility (capability `panels.state`): the state of each of this\n * extension's loaded panels. Follow changes with the `panel.state` event,\n * which every context of the extension receives; subscribe first, then\n * `list()`, so no change is missed. A plain-JS extension can call\n * `client.call('ui.listPanels')` with no behavioural difference.\n */\n readonly panels = {\n list: async (): Promise<PanelState[]> => {\n const r = (await this.call('ui.listPanels')) as PanelListResult\n return r.panels\n }\n }\n\n /**\n * Image map overlays (capability `overlays.image`): map layers this\n * extension's background runtime draws. The host asks for frames with the\n * `overlay.request` event (`OverlayRequestEvent`) and reports visibility\n * with `overlay.state` (`OverlayStateEvent`); the runtime answers each\n * request with `frame()`, keeping one frame in flight. `frame()` transfers\n * `image`, so the caller no longer owns it afterwards. A plain-JS extension\n * can call `client.call('overlay.frame', params, { transfer: [image] })`\n * with no behavioural difference.\n */\n readonly overlays = {\n list: async (): Promise<OverlayInfo[]> => {\n const r = (await this.call('overlay.list')) as OverlayListResult\n return r.overlays\n },\n setVisibility: async (ids: string[], visible: boolean): Promise<void> => {\n await this.call('overlay.setVisibility', { ids, visible })\n },\n setOpacity: async (ids: string[], opacity: number): Promise<void> => {\n await this.call('overlay.setOpacity', { ids, opacity })\n },\n frame: async (params: OverlayFrameParams): Promise<OverlayFrameResult> => {\n return (await this.call('overlay.frame', params, {\n transfer: [params.image]\n })) as OverlayFrameResult\n }\n }\n\n /**\n * Floating windows (capability `windows`): show one of this extension's\n * iframe panels in a window over the chart. Geometry is a request — read the\n * actual `bounds` from the returned `WindowState`, and follow changes with\n * the `window.bounds` / `window.state` / `window.closed` events. Windows\n * belong to the extension, so any of its contexts may manage them; in a\n * `window` context, `update` / `focus` / `close` default to the window\n * itself when `windowId` is left out. A plain-JS extension can call\n * `client.call('ui.openWindow', …)` and friends directly with no\n * behavioural difference.\n */\n readonly windows = {\n open: async (params: WindowOpenParams): Promise<WindowState> => {\n return (await this.call('ui.openWindow', params)) as WindowState\n },\n update: async (params: WindowUpdateParams): Promise<WindowState> => {\n return (await this.call('ui.updateWindow', params)) as WindowState\n },\n focus: async (windowId?: string): Promise<void> => {\n await this.call('ui.focusWindow', windowId === undefined ? {} : { windowId })\n },\n close: async (windowId?: string): Promise<void> => {\n await this.call('ui.closeWindow', windowId === undefined ? {} : { windowId })\n },\n list: async (): Promise<WindowState[]> => {\n const r = (await this.call('ui.listWindows')) as WindowListResult\n return r.windows\n }\n }\n\n /**\n * Apply a stored resource group to the host's display (capability\n * `resourceGroups`). Resolves to `{ applied }` — the types the host made a\n * best-effort attempt to carry out; a type it did not act on is omitted.\n * Follow applies from any origin (including the user's own group picker) via\n * the `resourceGroup.applied` event (`ResourceGroupAppliedEvent`). Group CRUD\n * is not here: use the server's resources API (`/resources/groups`). A\n * plain-JS extension can call `client.call('resourceGroup.apply', { id })`\n * directly with no behavioural difference.\n */\n readonly resourceGroup = {\n apply: async (id: string): Promise<ResourceGroupApplyResult> => {\n return (await this.call('resourceGroup.apply', {\n id\n })) as ResourceGroupApplyResult\n }\n }\n\n /**\n * The host's chart viewport (capability `map`). Read the current\n * `{ center, zoom, bounds }`, drive it, and follow it by subscribing to the\n * `map.view` event (`MapViewEvent`), which carries the same shape and fires\n * once per settled pan/zoom. Seed with `getView` so a follower has a view\n * before the first change. As with every wrapper, a plain-JS extension can\n * call `client.call('map.getView' | 'map.center' | 'map.fitBounds', …)`\n * directly with no behavioural difference.\n */\n readonly map = {\n getView: async (): Promise<MapView> => {\n return (await this.call('map.getView')) as MapView\n },\n center: async (\n position: [number, number],\n zoom?: number\n ): Promise<void> => {\n await this.call('map.center', {\n position,\n ...(typeof zoom === 'number' ? { zoom } : {})\n })\n },\n /**\n * Fit the chart to `[west, south, east, north]`. A box crossing the\n * antimeridian may be given as `west > east` (the API's form) or unwrapped\n * (`east` past 180); the host fits it the short way round.\n */\n fitBounds: async (\n bounds: [number, number, number, number]\n ): Promise<void> => {\n await this.call('map.fitBounds', { bounds })\n }\n }\n\n close(): void {\n this.endpoint.close()\n }\n}\n\n/**\n * Connect to the host from inside an extension iframe. Sends `bus.ready`\n * (repeating until answered) and resolves once the host's `bus.handshake`\n * arrives.\n */\nexport function connectExtension(\n opts: ConnectOptions = {}\n): Promise<ExtensionClient> {\n // Default transport: postMessage to the embedding window. Origin checks\n // are relaxed to '*' on the extension side because the host page's origin\n // may legitimately differ from the extension asset origin (e.g. a host dev\n // server embedding extension assets served by the Signal K server); the\n // peer-source check in windowPort still applies, and the host side\n // enforces a strict origin for the extension's frame.\n const port =\n opts.port ??\n windowPort((globalThis as unknown as Window).parent as Window, {\n origin: '*'\n })\n const endpoint = new BusEndpoint({\n port,\n callTimeoutMs: opts.callTimeoutMs,\n onError: opts.onError\n })\n const readyParams: ReadyParams | undefined = opts.id ? { id: opts.id } : undefined\n return new Promise<ExtensionClient>((resolve, reject) => {\n let done = false\n const off = endpoint.onEvent([EVENT_HANDSHAKE], (_name, params) => {\n if (done) return\n done = true\n cleanup()\n resolve(new ExtensionClient(endpoint, params as Handshake))\n })\n const interval = setInterval(\n () => endpoint.notify(EVENT_READY, readyParams),\n opts.readyIntervalMs ?? 250\n )\n const timeout = setTimeout(() => {\n if (done) return\n done = true\n cleanup()\n endpoint.close()\n reject(\n new RpcError('Timed out waiting for host handshake', {\n code: RPC_ERRORS.TIMEOUT,\n reason: 'HANDSHAKE_TIMEOUT'\n })\n )\n }, opts.timeoutMs ?? 10_000)\n const cleanup = () => {\n off()\n clearInterval(interval)\n clearTimeout(timeout)\n }\n endpoint.notify(EVENT_READY, readyParams)\n })\n}\n\nexport * from './protocol'\nexport * from './wildcard'\nexport * from './bounds'\nexport { BusEndpoint } from './endpoint'\nexport type { MethodHandler, MethodContext, EventHandler } from './endpoint'\nexport * from './port'\n", "/** The extension's user settings, shared by the config panel (which edits\n * them), the runtime (which applies them) and the render Worker (which draws\n * with them). Persisted in host state under the extension scope. */\n\nexport type DepthSource = \"history\" | \"tiles\";\n\n/** Overlay render quality. `auto` measures the worker's own frame cost and\n * scales to a budget; the rest pin a scale, so a weak GPU (a laptop iGPU) can\n * be dialled down and a fast one dialled up. */\nexport type DepthQuality = \"auto\" | \"high\" | \"balanced\" | \"low\";\n\nexport interface LiveDepthSettings {\n enabled: boolean;\n /** Where the soundings come from: the server's History API (the vessel's own\n * recent track) or the plugin's gridded bathymetry tiles. */\n source: DepthSource;\n /** Overlay render quality (see {@link DepthQuality}). */\n quality: DepthQuality;\n /** History look-back window, in hours. */\n historyWindowHours: number;\n /** History sample interval, in seconds. */\n historyResolutionS: number;\n /** Depth (metres) the shading runs to; deeper is left to the chart. */\n shadeLimitMetres: number;\n /** Depth (metres) between isobaths. */\n contourIntervalMetres: number;\n /** Grid cell size in screen pixels; also the base label size. */\n cellPx: number;\n /** Fractional depth difference (0.15 = 15%) within which adjacent cells\n * merge into one group. */\n mergeTolerance: number;\n /** Samples deeper than this (metres) are not drawn. */\n maxDepthMetres: number;\n /** Samples at or below this depth (metres) are marked individually. */\n safetyDepthMetres: number;\n}\n\nexport const DEFAULT_SETTINGS: LiveDepthSettings = {\n enabled: true,\n source: \"history\",\n quality: \"auto\",\n historyWindowHours: 1,\n historyResolutionS: 0.1,\n shadeLimitMetres: 10,\n contourIntervalMetres: 1,\n cellPx: 24,\n mergeTolerance: 0.15,\n maxDepthMetres: 100,\n safetyDepthMetres: 3,\n};\n\n/** The render scale (fraction of the host's requested frame size) each fixed\n * quality pins; `auto` is driven by the worker's own measurement instead. */\nexport const QUALITY_SCALE: Record<Exclude<DepthQuality, \"auto\">, number> = {\n high: 1,\n balanced: 0.6,\n low: 0.4,\n};\n\n/** The allowed values of the settings that are a closed set of strings. */\nconst ENUM_VALUES: Partial<Record<keyof LiveDepthSettings, readonly string[]>> =\n {\n source: [\"history\", \"tiles\"],\n quality: [\"auto\", \"high\", \"balanced\", \"low\"],\n };\n\n/** The settings keys, with the bounds the config panel clamps to. */\nexport const SETTINGS_BOUNDS: Partial<\n Record<keyof LiveDepthSettings, { min: number; max: number; step: number }>\n> = {\n historyWindowHours: { min: 0.1, max: 168, step: 0.1 },\n historyResolutionS: { min: 0.01, max: 60, step: 0.01 },\n shadeLimitMetres: { min: 1, max: 200, step: 1 },\n contourIntervalMetres: { min: 0.5, max: 50, step: 0.5 },\n cellPx: { min: 4, max: 128, step: 1 },\n mergeTolerance: { min: 0, max: 1, step: 0.01 },\n maxDepthMetres: { min: 1, max: 500, step: 1 },\n safetyDepthMetres: { min: 0, max: 50, step: 0.5 },\n};\n\n/** Merge stored state over the defaults, ignoring unknown/ill-typed keys. */\nexport function mergeSettings(\n stored: Record<string, unknown> | undefined,\n): LiveDepthSettings {\n const out = { ...DEFAULT_SETTINGS };\n if (!stored) {\n return out;\n }\n for (const key of Object.keys(\n DEFAULT_SETTINGS,\n ) as (keyof LiveDepthSettings)[]) {\n const value = stored[key];\n if (typeof value !== typeof DEFAULT_SETTINGS[key]) {\n continue;\n }\n const allowed = ENUM_VALUES[key];\n if (allowed && !allowed.includes(value as string)) {\n continue;\n }\n (out[key] as unknown) = value;\n }\n return out;\n}\n", "// The LiveDepth settings panel: a keepAlive iframe the host opens from the\n// LiveDepth toolbar button or the layer's \"Layer settings\" row. It edits the\n// extension's state, which the host persists and rebroadcasts (`state.changed`),\n// so the runtime picks changes up without a direct connection between the two\n// contexts.\n//\n// Depth-valued settings are shown in the user's preferred depth unit\n// (`units.get`) and stored in metres; the history-only settings are greyed while\n// the depth source is the plugin's tiles.\n\nimport { connectExtension } from \"signalk-plotterext-bus/extension\";\nimport {\n DEFAULT_SETTINGS,\n SETTINGS_BOUNDS,\n mergeSettings,\n type DepthQuality,\n type DepthSource,\n type LiveDepthSettings,\n} from \"./shared/settings.js\";\n\n/** Metres in a foot. */\nconst METRES_PER_FOOT = 0.3048;\n\nconst client = await connectExtension();\nlet settings: LiveDepthSettings = { ...DEFAULT_SETTINGS };\nlet depthUnit: \"m\" | \"foot\" = \"m\";\n\nconst root = document.getElementById(\"root\")!;\n\ninterface Field {\n key: keyof LiveDepthSettings;\n label: string;\n hint?: string;\n /** A unit shown after the input. */\n suffix?: string;\n /** Shown in the user's depth unit, stored in metres. */\n depth?: boolean;\n /** Only meaningful for the History source; greyed while the source is tiles. */\n historyOnly?: boolean;\n}\n\nconst FIELDS: Field[] = [\n {\n key: \"historyWindowHours\",\n label: \"History window\",\n hint: \"How far back to fetch depth.\",\n suffix: \"h\",\n historyOnly: true,\n },\n {\n key: \"historyResolutionS\",\n label: \"Sample interval\",\n hint: \"Seconds between depth samples; smaller is finer.\",\n suffix: \"s\",\n historyOnly: true,\n },\n {\n key: \"shadeLimitMetres\",\n label: \"Shade limit\",\n hint: \"Depth the shading runs to; deeper is left to the chart.\",\n depth: true,\n },\n {\n key: \"contourIntervalMetres\",\n label: \"Isobath interval\",\n hint: \"Depth between isobaths.\",\n depth: true,\n },\n {\n key: \"cellPx\",\n label: \"Grid cell\",\n hint: \"Circle diameter, in screen pixels.\",\n suffix: \"px\",\n },\n {\n key: \"mergeTolerance\",\n label: \"Merge band\",\n hint: \"Adjacent positions within this depth fraction merge into one circle.\",\n },\n {\n key: \"maxDepthMetres\",\n label: \"Max depth\",\n hint: \"Samples deeper than this are not drawn.\",\n depth: true,\n },\n {\n key: \"safetyDepthMetres\",\n label: \"Safety depth\",\n hint: \"Samples at or below this depth are marked individually.\",\n depth: true,\n },\n];\n\nconst unitSymbol = () => (depthUnit === \"foot\" ? \"ft\" : \"m\");\nconst toDisplay = (metres: number) =>\n depthUnit === \"foot\" ? metres / METRES_PER_FOOT : metres;\nconst fromDisplay = (value: number) =>\n depthUnit === \"foot\" ? value * METRES_PER_FOOT : value;\nconst round = (v: number) => Math.round(v * 100) / 100;\n\nconst historyRows: HTMLElement[] = [];\nconst historyInputs: HTMLInputElement[] = [];\n\n/** Grey the history-only fields while the source is the plugin's tiles. */\nfunction syncHistoryDisabled(): void {\n const disabled = settings.source === \"tiles\";\n for (const input of historyInputs) {\n input.disabled = disabled;\n }\n for (const row of historyRows) {\n row.classList.toggle(\"disabled\", disabled);\n }\n}\n\n/** The bounds a field is shown and clamped with, in the unit it is shown in.\n * A depth setting is stored in metres but read in the user's unit; converting\n * the metre bounds outright gives awkward values in feet (0.5 m = 1.64 ft), so\n * a typed whole foot would be snapped away. In feet the bounds are whole\n * numbers and the step is one; in metres they are used as-is. */\nfunction displayBounds(field: Field): {\n min: number;\n max: number;\n step: number;\n} {\n const b = SETTINGS_BOUNDS[field.key]!;\n if (!field.depth || depthUnit !== \"foot\") {\n return b;\n }\n return {\n min: Math.floor(toDisplay(b.min)),\n max: Math.round(toDisplay(b.max)),\n step: 1,\n };\n}\n\nfunction numberField(field: Field): HTMLElement {\n const { min: lo, max: hi, step } = displayBounds(field);\n\n const wrap = document.createElement(\"label\");\n wrap.className = \"field\";\n const name = document.createElement(\"span\");\n name.className = \"name\";\n name.textContent = field.label;\n wrap.appendChild(name);\n\n const control = document.createElement(\"span\");\n control.className = \"control\";\n const input = document.createElement(\"input\");\n input.type = \"number\";\n input.min = `${lo}`;\n input.max = `${hi}`;\n input.step = `${step}`;\n const stored = settings[field.key] as number;\n input.value = `${round(field.depth ? toDisplay(stored) : stored)}`;\n input.addEventListener(\"change\", () => {\n const clamped = Math.min(hi, Math.max(lo, Number(input.value)));\n input.value = `${round(clamped)}`;\n (settings[field.key] as number) = field.depth\n ? fromDisplay(clamped)\n : clamped;\n void save();\n });\n control.appendChild(input);\n const suffix = document.createElement(\"span\");\n suffix.className = \"suffix\";\n suffix.textContent = field.depth ? unitSymbol() : (field.suffix ?? \"\");\n control.appendChild(suffix);\n wrap.appendChild(control);\n\n if (field.hint) {\n const hint = document.createElement(\"span\");\n hint.className = \"hint\";\n hint.textContent = field.hint;\n wrap.appendChild(hint);\n }\n\n if (field.historyOnly) {\n historyRows.push(wrap);\n historyInputs.push(input);\n }\n return wrap;\n}\n\nfunction build(): void {\n root.replaceChildren();\n historyRows.length = 0;\n historyInputs.length = 0;\n\n const heading = document.createElement(\"h2\");\n heading.textContent = \"LiveDepth\";\n root.appendChild(heading);\n\n const enabled = document.createElement(\"label\");\n enabled.className = \"field row\";\n const enabledName = document.createElement(\"span\");\n enabledName.className = \"name\";\n enabledName.textContent = \"Show the layer\";\n const enabledInput = document.createElement(\"input\");\n enabledInput.type = \"checkbox\";\n enabledInput.checked = settings.enabled;\n enabledInput.addEventListener(\"change\", () => {\n settings.enabled = enabledInput.checked;\n void save();\n });\n enabled.append(enabledName, enabledInput);\n root.appendChild(enabled);\n\n const source = document.createElement(\"label\");\n source.className = \"field\";\n const sourceName = document.createElement(\"span\");\n sourceName.className = \"name\";\n sourceName.textContent = \"Depth source\";\n const select = document.createElement(\"select\");\n for (const [value, label] of [\n [\"history\", \"History (this vessel's track)\"],\n [\"tiles\", \"LiveDepth plugin tiles\"],\n ] as [DepthSource, string][]) {\n const option = document.createElement(\"option\");\n option.value = value;\n option.textContent = label;\n option.selected = settings.source === value;\n select.appendChild(option);\n }\n select.addEventListener(\"change\", () => {\n settings.source = select.value as DepthSource;\n syncHistoryDisabled();\n void save();\n });\n source.append(sourceName, select);\n const sourceHint = document.createElement(\"span\");\n sourceHint.className = \"hint\";\n sourceHint.textContent =\n \"History draws the vessel's own recorded track; tiles draw the \" +\n \"server-built bathymetry store, extended live.\";\n source.appendChild(sourceHint);\n root.appendChild(source);\n\n const quality = document.createElement(\"label\");\n quality.className = \"field\";\n const qualityName = document.createElement(\"span\");\n qualityName.className = \"name\";\n qualityName.textContent = \"Render quality\";\n const qualitySelect = document.createElement(\"select\");\n for (const [value, label] of [\n [\"auto\", \"Auto (adapt to this GPU)\"],\n [\"high\", \"High\"],\n [\"balanced\", \"Balanced\"],\n [\"low\", \"Low\"],\n ] as [DepthQuality, string][]) {\n const option = document.createElement(\"option\");\n option.value = value;\n option.textContent = label;\n option.selected = settings.quality === value;\n qualitySelect.appendChild(option);\n }\n qualitySelect.addEventListener(\"change\", () => {\n settings.quality = qualitySelect.value as DepthQuality;\n void save();\n });\n quality.append(qualityName, qualitySelect);\n const qualityHint = document.createElement(\"span\");\n qualityHint.className = \"hint\";\n qualityHint.textContent =\n \"Scales the shaded depth surface only; soundings, circles and labels \" +\n \"always render at full resolution. Auto adapts to this GPU.\";\n quality.appendChild(qualityHint);\n root.appendChild(quality);\n\n for (const field of FIELDS) {\n root.appendChild(numberField(field));\n }\n syncHistoryDisabled();\n}\n\nasync function save(): Promise<void> {\n await client.state.set(settings as unknown as Record<string, unknown>);\n}\n\nasync function load(): Promise<void> {\n try {\n settings = mergeSettings(\n (await client.state.get()) as Record<string, unknown>,\n );\n } catch {\n settings = { ...DEFAULT_SETTINGS };\n }\n try {\n const res = (await client.call(\"units.get\")) as {\n units?: { depth?: string };\n };\n depthUnit = res?.units?.depth === \"foot\" ? \"foot\" : \"m\";\n } catch {\n depthUnit = \"m\";\n }\n build();\n}\n\nawait load();\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["wrap"]
|
|
7
7
|
}
|