signalk-livedepth-plugin 0.2.1 → 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.
Files changed (86) hide show
  1. package/CHANGELOG.md +113 -0
  2. package/README.md +67 -41
  3. package/dist/collector.d.ts +1 -1
  4. package/dist/collector.d.ts.map +1 -1
  5. package/dist/config.d.ts +52 -3
  6. package/dist/config.d.ts.map +1 -1
  7. package/dist/config.js +41 -4
  8. package/dist/config.js.map +1 -1
  9. package/dist/plotterext/manifest.d.ts +24 -0
  10. package/dist/plotterext/manifest.d.ts.map +1 -0
  11. package/dist/plotterext/manifest.js +79 -0
  12. package/dist/plotterext/manifest.js.map +1 -0
  13. package/dist/plotterext/provider.d.ts +7 -0
  14. package/dist/plotterext/provider.d.ts.map +1 -0
  15. package/dist/plotterext/provider.js +61 -0
  16. package/dist/plotterext/provider.js.map +1 -0
  17. package/dist/plugin.d.ts +15 -0
  18. package/dist/plugin.d.ts.map +1 -1
  19. package/dist/plugin.js +40 -8
  20. package/dist/plugin.js.map +1 -1
  21. package/dist/streams/live.d.ts +16 -4
  22. package/dist/streams/live.d.ts.map +1 -1
  23. package/dist/streams/live.js +164 -43
  24. package/dist/streams/live.js.map +1 -1
  25. package/dist/tiles/api.d.ts +21 -11
  26. package/dist/tiles/api.d.ts.map +1 -1
  27. package/dist/tiles/api.js +36 -27
  28. package/dist/tiles/api.js.map +1 -1
  29. package/dist/tiles/core/accumulator.d.ts +37 -0
  30. package/dist/tiles/core/accumulator.d.ts.map +1 -0
  31. package/dist/tiles/core/accumulator.js +75 -0
  32. package/dist/tiles/core/accumulator.js.map +1 -0
  33. package/dist/tiles/core/correction.d.ts +32 -0
  34. package/dist/tiles/core/correction.d.ts.map +1 -0
  35. package/dist/tiles/core/correction.js +43 -0
  36. package/dist/tiles/core/correction.js.map +1 -0
  37. package/dist/tiles/core/grid.d.ts +64 -0
  38. package/dist/tiles/core/grid.d.ts.map +1 -0
  39. package/dist/tiles/core/grid.js +127 -0
  40. package/dist/tiles/core/grid.js.map +1 -0
  41. package/dist/tiles/core/index.d.ts +7 -0
  42. package/dist/tiles/core/index.d.ts.map +1 -0
  43. package/dist/tiles/core/index.js +7 -0
  44. package/dist/tiles/core/index.js.map +1 -0
  45. package/dist/tiles/core/store.d.ts +37 -0
  46. package/dist/tiles/core/store.d.ts.map +1 -0
  47. package/dist/tiles/core/store.js +86 -0
  48. package/dist/tiles/core/store.js.map +1 -0
  49. package/dist/tiles/core/tile-source.d.ts +50 -0
  50. package/dist/tiles/core/tile-source.d.ts.map +1 -0
  51. package/dist/tiles/core/tile-source.js +86 -0
  52. package/dist/tiles/core/tile-source.js.map +1 -0
  53. package/dist/tiles/core/types.d.ts +11 -0
  54. package/dist/tiles/core/types.d.ts.map +1 -0
  55. package/dist/tiles/core/types.js +2 -0
  56. package/dist/tiles/core/types.js.map +1 -0
  57. package/dist/tiles/index.d.ts +1 -1
  58. package/dist/tiles/index.d.ts.map +1 -1
  59. package/dist/tiles/index.js +1 -1
  60. package/dist/tiles/index.js.map +1 -1
  61. package/dist/tiles/seed.d.ts +1 -1
  62. package/dist/tiles/seed.d.ts.map +1 -1
  63. package/dist/tiles/seed.js +1 -1
  64. package/dist/tiles/seed.js.map +1 -1
  65. package/dist/tiles/store.d.ts +1 -1
  66. package/dist/tiles/store.d.ts.map +1 -1
  67. package/dist/tiles/store.js +1 -1
  68. package/dist/tiles/store.js.map +1 -1
  69. package/dist/tiles/writer.d.ts +1 -1
  70. package/dist/tiles/writer.d.ts.map +1 -1
  71. package/dist/tiles/writer.js +1 -1
  72. package/dist/tiles/writer.js.map +1 -1
  73. package/dist/tsconfig.tsbuildinfo +1 -1
  74. package/dist/types.d.ts +3 -0
  75. package/dist/types.d.ts.map +1 -1
  76. package/package.json +27 -6
  77. package/public/config.html +14 -0
  78. package/public/index.html +15 -0
  79. package/public/js/config.js +939 -0
  80. package/public/js/config.js.map +7 -0
  81. package/public/js/render-worker.js +866 -0
  82. package/public/js/render-worker.js.map +7 -0
  83. package/public/js/renderer.js +1159 -0
  84. package/public/js/renderer.js.map +7 -0
  85. package/public/renderer.html +14 -0
  86. package/public/styles.css +110 -0
@@ -0,0 +1,939 @@
1
+ // node_modules/signalk-plotterext-bus/dist/chunk-4JN5YUOC.js
2
+ var BUS_ID = "plotterExt/1";
3
+ var RPC_ERRORS = {
4
+ PARSE_ERROR: -32700,
5
+ INVALID_REQUEST: -32600,
6
+ METHOD_NOT_FOUND: -32601,
7
+ INVALID_PARAMS: -32602,
8
+ INTERNAL_ERROR: -32603,
9
+ HOST_ERROR: -32e3,
10
+ TIMEOUT: -32001,
11
+ CONNECTION_CLOSED: -32002
12
+ };
13
+ var RpcError = class _RpcError extends Error {
14
+ code;
15
+ data;
16
+ constructor(message, opts = {}) {
17
+ super(message);
18
+ this.name = "RpcError";
19
+ this.code = opts.code ?? RPC_ERRORS.HOST_ERROR;
20
+ const data = { ...opts.data ?? {} };
21
+ if (opts.reason !== void 0) data.reason = opts.reason;
22
+ this.data = Object.keys(data).length > 0 ? data : void 0;
23
+ }
24
+ get reason() {
25
+ return typeof this.data?.reason === "string" ? this.data.reason : void 0;
26
+ }
27
+ toErrorObject() {
28
+ return {
29
+ code: this.code,
30
+ message: this.message,
31
+ ...this.data ? { data: this.data } : {}
32
+ };
33
+ }
34
+ static fromErrorObject(err) {
35
+ return new _RpcError(err.message, { code: err.code, data: err.data });
36
+ }
37
+ /** Normalize any thrown value into an RpcError suitable for the wire. */
38
+ static from(err) {
39
+ if (err instanceof _RpcError) return err;
40
+ if (err instanceof Error) {
41
+ return new _RpcError(err.message, { code: RPC_ERRORS.INTERNAL_ERROR });
42
+ }
43
+ return new _RpcError(String(err), { code: RPC_ERRORS.INTERNAL_ERROR });
44
+ }
45
+ };
46
+ var EVENT_READY = "bus.ready";
47
+ var EVENT_HANDSHAKE = "bus.handshake";
48
+ function matchesPattern(pattern, name) {
49
+ if (pattern === name) return true;
50
+ return match(pattern.split("."), 0, name.split("."), 0);
51
+ }
52
+ function match(p, pi, n, ni) {
53
+ while (pi < p.length) {
54
+ const seg = p[pi];
55
+ if (seg === "**") {
56
+ if (pi === p.length - 1) return true;
57
+ for (let skip = ni; skip <= n.length; skip++) {
58
+ if (match(p, pi + 1, n, skip)) return true;
59
+ }
60
+ return false;
61
+ }
62
+ if (ni >= n.length) return false;
63
+ if (seg !== "*" && seg !== n[ni]) return false;
64
+ pi++;
65
+ ni++;
66
+ }
67
+ return ni === n.length;
68
+ }
69
+ function matchesAny(patterns, name) {
70
+ for (const pattern of patterns) {
71
+ if (matchesPattern(pattern, name)) return true;
72
+ }
73
+ return false;
74
+ }
75
+ function wrap(msg) {
76
+ return { bus: BUS_ID, msg };
77
+ }
78
+ function unwrap(data) {
79
+ if (typeof data !== "object" || data === null) return null;
80
+ const env = data;
81
+ if (env.bus !== BUS_ID) return null;
82
+ return isJsonRpcMessage(env.msg) ? env.msg : null;
83
+ }
84
+ function isJsonRpcMessage(v) {
85
+ if (typeof v !== "object" || v === null) return false;
86
+ const m = v;
87
+ if (m.jsonrpc !== "2.0") return false;
88
+ if (typeof m.method === "string") {
89
+ return m.id === void 0 || typeof m.id === "string" || typeof m.id === "number";
90
+ }
91
+ const idOk = typeof m.id === "string" || typeof m.id === "number" || m.id === null;
92
+ if (!idOk) return false;
93
+ const hasResult = "result" in m;
94
+ const err = m.error;
95
+ const hasError = typeof err === "object" && err !== null && typeof err.code === "number" && typeof err.message === "string";
96
+ return hasResult ? !("error" in m) : hasError;
97
+ }
98
+ function isRequest(msg) {
99
+ return "method" in msg && "id" in msg && msg.id !== void 0;
100
+ }
101
+ function isNotification(msg) {
102
+ return "method" in msg && (!("id" in msg) || msg.id === void 0);
103
+ }
104
+ function isResponse(msg) {
105
+ return !("method" in msg);
106
+ }
107
+ function windowPort(peer, opts = {}) {
108
+ const listenWindow = opts.listenWindow ?? globalThis;
109
+ const origin = opts.origin ?? listenWindow.location?.origin ?? "*";
110
+ return {
111
+ post(data, transfer) {
112
+ if (transfer?.length) {
113
+ peer.postMessage(data, { targetOrigin: origin, transfer });
114
+ } else {
115
+ peer.postMessage(data, origin);
116
+ }
117
+ },
118
+ listen(handler) {
119
+ const fn = (ev) => {
120
+ if (ev.source !== peer) return;
121
+ if (origin !== "*" && ev.origin !== origin) return;
122
+ handler(ev.data);
123
+ };
124
+ listenWindow.addEventListener("message", fn);
125
+ return () => listenWindow.removeEventListener("message", fn);
126
+ }
127
+ };
128
+ }
129
+ var DEFAULT_CALL_TIMEOUT_MS = 1e4;
130
+ var BusEndpoint = class {
131
+ callTimeoutMs;
132
+ port;
133
+ unlisten;
134
+ onError;
135
+ pending = /* @__PURE__ */ new Map();
136
+ methods = /* @__PURE__ */ new Map();
137
+ eventHandlers = /* @__PURE__ */ new Set();
138
+ idPrefix = Math.random().toString(36).slice(2, 8);
139
+ seq = 0;
140
+ closed = false;
141
+ constructor(opts) {
142
+ this.port = opts.port;
143
+ this.callTimeoutMs = opts.callTimeoutMs ?? DEFAULT_CALL_TIMEOUT_MS;
144
+ this.onError = opts.onError ?? ((err) => console.warn("[plotterext-bus]", err));
145
+ this.unlisten = this.port.listen((data) => this.onData(data));
146
+ }
147
+ registerMethod(name, handler) {
148
+ this.methods.set(name, handler);
149
+ }
150
+ unregisterMethod(name) {
151
+ this.methods.delete(name);
152
+ }
153
+ /**
154
+ * Handle incoming notifications whose names match any of the wildcard
155
+ * patterns. Returns an unsubscribe function. This is local dispatch only;
156
+ * telling the peer which events to forward is a separate concern
157
+ * (`events.subscribe`).
158
+ */
159
+ onEvent(patterns, fn) {
160
+ const entry = { patterns, fn };
161
+ this.eventHandlers.add(entry);
162
+ return () => this.eventHandlers.delete(entry);
163
+ }
164
+ /**
165
+ * Send a notification (an event) to the peer. `opts.transfer` moves the
166
+ * listed objects instead of copying them.
167
+ */
168
+ notify(method, params, opts = {}) {
169
+ this.send(
170
+ { jsonrpc: "2.0", method, ...params !== void 0 ? { params } : {} },
171
+ opts.transfer
172
+ );
173
+ }
174
+ /**
175
+ * Call a method on the peer; resolves with its result. `opts.transfer`
176
+ * moves the listed objects (e.g. an `ImageBitmap` inside `params`) instead
177
+ * of copying them.
178
+ */
179
+ call(method, params, opts = {}) {
180
+ if (this.closed) {
181
+ return Promise.reject(
182
+ new RpcError("Bus endpoint is closed", {
183
+ code: RPC_ERRORS.CONNECTION_CLOSED,
184
+ reason: "CLOSED"
185
+ })
186
+ );
187
+ }
188
+ const id = `${this.idPrefix}-${++this.seq}`;
189
+ const timeoutMs = opts.timeoutMs ?? this.callTimeoutMs;
190
+ return new Promise((resolve, reject) => {
191
+ const timer = timeoutMs > 0 ? setTimeout(() => {
192
+ this.pending.delete(id);
193
+ reject(
194
+ new RpcError(`Call timed out after ${timeoutMs}ms: ${method}`, {
195
+ code: RPC_ERRORS.TIMEOUT,
196
+ reason: "TIMEOUT"
197
+ })
198
+ );
199
+ }, timeoutMs) : null;
200
+ this.pending.set(id, { resolve, reject, timer });
201
+ this.send(
202
+ {
203
+ jsonrpc: "2.0",
204
+ id,
205
+ method,
206
+ ...params !== void 0 ? { params } : {}
207
+ },
208
+ opts.transfer
209
+ );
210
+ });
211
+ }
212
+ close() {
213
+ if (this.closed) return;
214
+ this.closed = true;
215
+ this.unlisten();
216
+ for (const [, p] of this.pending) {
217
+ if (p.timer) clearTimeout(p.timer);
218
+ p.reject(
219
+ new RpcError("Bus endpoint closed", {
220
+ code: RPC_ERRORS.CONNECTION_CLOSED,
221
+ reason: "CLOSED"
222
+ })
223
+ );
224
+ }
225
+ this.pending.clear();
226
+ this.eventHandlers.clear();
227
+ }
228
+ send(msg, transfer) {
229
+ if (this.closed) return;
230
+ this.port.post(wrap(msg), transfer);
231
+ }
232
+ onData(data) {
233
+ const msg = unwrap(data);
234
+ if (!msg) return;
235
+ if (isResponse(msg)) {
236
+ this.onResponse(msg);
237
+ } else if (isRequest(msg)) {
238
+ void this.onRequest(msg);
239
+ } else if (isNotification(msg)) {
240
+ this.onNotification(msg.method, msg.params);
241
+ }
242
+ }
243
+ onResponse(msg) {
244
+ if (msg.id === null) return;
245
+ const p = this.pending.get(msg.id);
246
+ if (!p) return;
247
+ this.pending.delete(msg.id);
248
+ if (p.timer) clearTimeout(p.timer);
249
+ if ("error" in msg) {
250
+ p.reject(RpcError.fromErrorObject(msg.error));
251
+ } else {
252
+ p.resolve(msg.result);
253
+ }
254
+ }
255
+ async onRequest(msg) {
256
+ const handler = this.methods.get(msg.method);
257
+ if (!handler) {
258
+ this.send({
259
+ jsonrpc: "2.0",
260
+ id: msg.id,
261
+ error: {
262
+ code: RPC_ERRORS.METHOD_NOT_FOUND,
263
+ message: `Method not found: ${msg.method}`
264
+ }
265
+ });
266
+ return;
267
+ }
268
+ try {
269
+ const result = await handler(msg.params, { endpoint: this });
270
+ this.send({
271
+ jsonrpc: "2.0",
272
+ id: msg.id,
273
+ result: result === void 0 ? null : result
274
+ });
275
+ } catch (err) {
276
+ this.send({
277
+ jsonrpc: "2.0",
278
+ id: msg.id,
279
+ error: RpcError.from(err).toErrorObject()
280
+ });
281
+ }
282
+ }
283
+ onNotification(name, params) {
284
+ for (const entry of [...this.eventHandlers]) {
285
+ if (matchesAny(entry.patterns, name)) {
286
+ try {
287
+ entry.fn(name, params);
288
+ } catch (err) {
289
+ this.onError(err);
290
+ }
291
+ }
292
+ }
293
+ }
294
+ };
295
+
296
+ // node_modules/signalk-plotterext-bus/dist/chunk-N42ZRM4R.js
297
+ var ExtensionClient = class {
298
+ handshake;
299
+ endpoint;
300
+ constructor(endpoint, handshake) {
301
+ this.endpoint = endpoint;
302
+ this.handshake = handshake;
303
+ }
304
+ get context() {
305
+ return this.handshake.context;
306
+ }
307
+ get apiVersion() {
308
+ return this.handshake.apiVersion;
309
+ }
310
+ get capabilities() {
311
+ return this.handshake.capabilities;
312
+ }
313
+ hasCapability(id) {
314
+ return this.handshake.capabilities.includes(id);
315
+ }
316
+ /**
317
+ * Call a host API method. `opts.transfer` moves the listed objects instead
318
+ * of copying them — only `overlay.frame` needs it in this version.
319
+ */
320
+ call(method, params, opts) {
321
+ return this.endpoint.call(method, params, opts);
322
+ }
323
+ /** Send a notification to the host. */
324
+ notify(method, params) {
325
+ this.endpoint.notify(method, params);
326
+ }
327
+ /**
328
+ * Subscribe to host events matching wildcard patterns. Registers both the
329
+ * host-side forwarding subscription and local dispatch; the returned
330
+ * function tears down both.
331
+ */
332
+ async subscribe(patterns, handler) {
333
+ const off = this.endpoint.onEvent(patterns, handler);
334
+ let subscriptionId;
335
+ try {
336
+ const result = await this.call("events.subscribe", { patterns });
337
+ subscriptionId = result.subscriptionId;
338
+ } catch (err) {
339
+ off();
340
+ throw err;
341
+ }
342
+ return async () => {
343
+ off();
344
+ await this.call("events.unsubscribe", { subscriptionId }).catch(() => {
345
+ });
346
+ };
347
+ }
348
+ /**
349
+ * Publish an event onto the bus (capability `events.publish`). The host
350
+ * delivers it to every context in `scope` whose subscriptions match —
351
+ * including this one — exactly as it delivers host events. `scope` defaults
352
+ * to `'all'` (any extension); `'extension'` keeps it to this extension's own
353
+ * contexts. Resolves once the host has delivered it; there is no reply.
354
+ */
355
+ async publish(topic, params, scope) {
356
+ await this.call("events.publish", {
357
+ topic,
358
+ ...params !== void 0 ? { params } : {},
359
+ ...scope ? { scope } : {}
360
+ });
361
+ }
362
+ /** Host-persisted key/value state (see spec: State Storage). */
363
+ state = {
364
+ get: async (keys, scope) => {
365
+ const result = await this.call("state.get", {
366
+ ...scope ? { scope } : {},
367
+ ...keys ? { keys } : {}
368
+ });
369
+ return result.values ?? {};
370
+ },
371
+ set: async (values, scope) => {
372
+ await this.call("state.set", {
373
+ ...scope ? { scope } : {},
374
+ values
375
+ });
376
+ }
377
+ };
378
+ /** Signal K data relayed by the host (capabilities signalk.stream / .put). */
379
+ signalk = {
380
+ /**
381
+ * Subscribe to Signal K path values. The host publishes them as
382
+ * `sk.<path>` events; this helper hides the event-name mapping and
383
+ * establishes both the event-forwarding subscription and the host's
384
+ * upstream Signal K subscription.
385
+ */
386
+ subscribe: async (paths, handler) => {
387
+ const patterns = paths.map((p) => `sk.${p}`);
388
+ const offEvents = await this.subscribe(
389
+ patterns,
390
+ (_name, params) => handler(params)
391
+ );
392
+ let subscriptionId;
393
+ try {
394
+ const result = await this.call("signalk.subscribe", { paths });
395
+ subscriptionId = result.subscriptionId;
396
+ } catch (err) {
397
+ await offEvents();
398
+ throw err;
399
+ }
400
+ return async () => {
401
+ await offEvents();
402
+ await this.call("signalk.unsubscribe", { subscriptionId }).catch(
403
+ () => {
404
+ }
405
+ );
406
+ };
407
+ },
408
+ put: (path, value) => {
409
+ return this.call("signalk.put", { path, value });
410
+ }
411
+ };
412
+ /**
413
+ * The host's visible routes (capability `routes`). Thin **typed** wrappers over
414
+ * the host's `route.*` methods — each just delegates to `this.call(...)`, so a
415
+ * plain-JS extension can call `client.call('route.replace', …)` directly and a
416
+ * TypeScript extension gets the typed `client.route.replace(…)` sugar with no
417
+ * behavioural difference. Follow lifecycle + mutations by subscribing to
418
+ * `route.**` events (`RouteVisibleEvent` / `RouteDirtyEvent` / `RouteSavedEvent`
419
+ * / `RouteHiddenEvent`). Further operations (rename/point ops) extend this
420
+ * surface as the capability fills out.
421
+ */
422
+ route = {
423
+ list: async () => {
424
+ const result = await this.call("route.list");
425
+ return result.routes ?? [];
426
+ },
427
+ create: async (opts) => {
428
+ return await this.call("route.create", opts);
429
+ },
430
+ get: async (routeId) => {
431
+ return await this.call("route.get", { routeId });
432
+ },
433
+ replace: async (routeId, points) => {
434
+ return await this.call("route.replace", { routeId, points });
435
+ },
436
+ save: async (routeId, opts) => {
437
+ return await this.call("route.save", {
438
+ routeId,
439
+ ...opts ?? {}
440
+ });
441
+ },
442
+ show: async (ref) => {
443
+ return await this.call("route.show", { ref });
444
+ },
445
+ hide: async (routeId) => {
446
+ await this.call("route.hide", { routeId });
447
+ },
448
+ delete: async (routeId) => {
449
+ await this.call("route.delete", { routeId });
450
+ }
451
+ };
452
+ /**
453
+ * The host's chart layers (capability `charts`). Thin **typed** wrappers over
454
+ * the host's `chart.*` methods — a lightweight facade over the charts the host
455
+ * already manages: enumerate them, toggle visibility, opacity and stacking
456
+ * order (all batch). It does not create, add, or delete chart sources. Follow
457
+ * changes (from any origin, including the host's own chart controls) by
458
+ * subscribing to `chart.**` events (`ChartVisibilityEvent` / `ChartOpacityEvent`
459
+ * / `ChartOrderEvent` / `ChartTimeEvent`) and re-reading `chart.list` where
460
+ * needed. As with every wrapper, a plain-JS extension can call
461
+ * `client.call('chart.list', …)` directly with no behavioural difference.
462
+ */
463
+ chart = {
464
+ list: async () => {
465
+ const result = await this.call("chart.list");
466
+ return result.charts ?? [];
467
+ },
468
+ setVisibility: async (ids, visible) => {
469
+ await this.call("chart.setVisibility", { ids, visible });
470
+ },
471
+ setOpacity: async (ids, opacity) => {
472
+ await this.call("chart.setOpacity", { ids, opacity });
473
+ },
474
+ setOrder: async (order) => {
475
+ await this.call("chart.setOrder", { order });
476
+ },
477
+ /**
478
+ * Retarget time-varying charts to an ISO 8601 instant, or back to their
479
+ * live frame with `null` (sub-capability `charts.time`). Follow changes via
480
+ * the `chart.time` event (`ChartTimeEvent`).
481
+ */
482
+ setTime: async (ids, time) => {
483
+ await this.call("chart.setTime", { ids, time });
484
+ }
485
+ };
486
+ /**
487
+ * The host's night-vision display mode (capability `nightMode`). Read the
488
+ * current `{ enabled, auto }` state, change it, and follow changes by
489
+ * subscribing to the `nightMode.changed` event (`NightModeChangedEvent`).
490
+ * `set` expresses the three states the spec defines: force on
491
+ * (`{ enabled: true }`), force off (`{ enabled: false }`), follow the server
492
+ * (`{ auto: true }`); setting `enabled` implies `auto: false`. As with every
493
+ * wrapper, a plain-JS extension can call `client.call('nightMode.get' |
494
+ * 'nightMode.set', …)` directly with no behavioural difference.
495
+ */
496
+ nightMode = {
497
+ get: async () => {
498
+ return await this.call("nightMode.get");
499
+ },
500
+ set: async (state) => {
501
+ await this.call("nightMode.set", state);
502
+ }
503
+ };
504
+ /**
505
+ * Panel visibility (capability `panels.state`): the state of each of this
506
+ * extension's loaded panels. Follow changes with the `panel.state` event,
507
+ * which every context of the extension receives; subscribe first, then
508
+ * `list()`, so no change is missed. A plain-JS extension can call
509
+ * `client.call('ui.listPanels')` with no behavioural difference.
510
+ */
511
+ panels = {
512
+ list: async () => {
513
+ const r = await this.call("ui.listPanels");
514
+ return r.panels;
515
+ }
516
+ };
517
+ /**
518
+ * Image map overlays (capability `overlays.image`): map layers this
519
+ * extension's background runtime draws. The host asks for frames with the
520
+ * `overlay.request` event (`OverlayRequestEvent`) and reports visibility
521
+ * with `overlay.state` (`OverlayStateEvent`); the runtime answers each
522
+ * request with `frame()`, keeping one frame in flight. `frame()` transfers
523
+ * `image`, so the caller no longer owns it afterwards. A plain-JS extension
524
+ * can call `client.call('overlay.frame', params, { transfer: [image] })`
525
+ * with no behavioural difference.
526
+ */
527
+ overlays = {
528
+ list: async () => {
529
+ const r = await this.call("overlay.list");
530
+ return r.overlays;
531
+ },
532
+ setVisibility: async (ids, visible) => {
533
+ await this.call("overlay.setVisibility", { ids, visible });
534
+ },
535
+ setOpacity: async (ids, opacity) => {
536
+ await this.call("overlay.setOpacity", { ids, opacity });
537
+ },
538
+ frame: async (params) => {
539
+ return await this.call("overlay.frame", params, {
540
+ transfer: [params.image]
541
+ });
542
+ }
543
+ };
544
+ /**
545
+ * Floating windows (capability `windows`): show one of this extension's
546
+ * iframe panels in a window over the chart. Geometry is a request — read the
547
+ * actual `bounds` from the returned `WindowState`, and follow changes with
548
+ * the `window.bounds` / `window.state` / `window.closed` events. Windows
549
+ * belong to the extension, so any of its contexts may manage them; in a
550
+ * `window` context, `update` / `focus` / `close` default to the window
551
+ * itself when `windowId` is left out. A plain-JS extension can call
552
+ * `client.call('ui.openWindow', …)` and friends directly with no
553
+ * behavioural difference.
554
+ */
555
+ windows = {
556
+ open: async (params) => {
557
+ return await this.call("ui.openWindow", params);
558
+ },
559
+ update: async (params) => {
560
+ return await this.call("ui.updateWindow", params);
561
+ },
562
+ focus: async (windowId) => {
563
+ await this.call("ui.focusWindow", windowId === void 0 ? {} : { windowId });
564
+ },
565
+ close: async (windowId) => {
566
+ await this.call("ui.closeWindow", windowId === void 0 ? {} : { windowId });
567
+ },
568
+ list: async () => {
569
+ const r = await this.call("ui.listWindows");
570
+ return r.windows;
571
+ }
572
+ };
573
+ /**
574
+ * Apply a stored resource group to the host's display (capability
575
+ * `resourceGroups`). Resolves to `{ applied }` — the types the host made a
576
+ * best-effort attempt to carry out; a type it did not act on is omitted.
577
+ * Follow applies from any origin (including the user's own group picker) via
578
+ * the `resourceGroup.applied` event (`ResourceGroupAppliedEvent`). Group CRUD
579
+ * is not here: use the server's resources API (`/resources/groups`). A
580
+ * plain-JS extension can call `client.call('resourceGroup.apply', { id })`
581
+ * directly with no behavioural difference.
582
+ */
583
+ resourceGroup = {
584
+ apply: async (id) => {
585
+ return await this.call("resourceGroup.apply", {
586
+ id
587
+ });
588
+ }
589
+ };
590
+ /**
591
+ * The host's chart viewport (capability `map`). Read the current
592
+ * `{ center, zoom, bounds }`, drive it, and follow it by subscribing to the
593
+ * `map.view` event (`MapViewEvent`), which carries the same shape and fires
594
+ * once per settled pan/zoom. Seed with `getView` so a follower has a view
595
+ * before the first change. As with every wrapper, a plain-JS extension can
596
+ * call `client.call('map.getView' | 'map.center' | 'map.fitBounds', …)`
597
+ * directly with no behavioural difference.
598
+ */
599
+ map = {
600
+ getView: async () => {
601
+ return await this.call("map.getView");
602
+ },
603
+ center: async (position, zoom) => {
604
+ await this.call("map.center", {
605
+ position,
606
+ ...typeof zoom === "number" ? { zoom } : {}
607
+ });
608
+ },
609
+ /**
610
+ * Fit the chart to `[west, south, east, north]`. A box crossing the
611
+ * antimeridian may be given as `west > east` (the API's form) or unwrapped
612
+ * (`east` past 180); the host fits it the short way round.
613
+ */
614
+ fitBounds: async (bounds) => {
615
+ await this.call("map.fitBounds", { bounds });
616
+ }
617
+ };
618
+ close() {
619
+ this.endpoint.close();
620
+ }
621
+ };
622
+ function connectExtension(opts = {}) {
623
+ const port = opts.port ?? windowPort(globalThis.parent, {
624
+ origin: "*"
625
+ });
626
+ const endpoint = new BusEndpoint({
627
+ port,
628
+ callTimeoutMs: opts.callTimeoutMs,
629
+ onError: opts.onError
630
+ });
631
+ const readyParams = opts.id ? { id: opts.id } : void 0;
632
+ return new Promise((resolve, reject) => {
633
+ let done = false;
634
+ const off = endpoint.onEvent([EVENT_HANDSHAKE], (_name, params) => {
635
+ if (done) return;
636
+ done = true;
637
+ cleanup();
638
+ resolve(new ExtensionClient(endpoint, params));
639
+ });
640
+ const interval = setInterval(
641
+ () => endpoint.notify(EVENT_READY, readyParams),
642
+ opts.readyIntervalMs ?? 250
643
+ );
644
+ const timeout = setTimeout(() => {
645
+ if (done) return;
646
+ done = true;
647
+ cleanup();
648
+ endpoint.close();
649
+ reject(
650
+ new RpcError("Timed out waiting for host handshake", {
651
+ code: RPC_ERRORS.TIMEOUT,
652
+ reason: "HANDSHAKE_TIMEOUT"
653
+ })
654
+ );
655
+ }, opts.timeoutMs ?? 1e4);
656
+ const cleanup = () => {
657
+ off();
658
+ clearInterval(interval);
659
+ clearTimeout(timeout);
660
+ };
661
+ endpoint.notify(EVENT_READY, readyParams);
662
+ });
663
+ }
664
+
665
+ // src/web/shared/settings.ts
666
+ var DEFAULT_SETTINGS = {
667
+ enabled: true,
668
+ source: "history",
669
+ quality: "auto",
670
+ historyWindowHours: 1,
671
+ historyResolutionS: 0.1,
672
+ shadeLimitMetres: 10,
673
+ contourIntervalMetres: 1,
674
+ cellPx: 24,
675
+ mergeTolerance: 0.15,
676
+ maxDepthMetres: 100,
677
+ safetyDepthMetres: 3
678
+ };
679
+ var ENUM_VALUES = {
680
+ source: ["history", "tiles"],
681
+ quality: ["auto", "high", "balanced", "low"]
682
+ };
683
+ var SETTINGS_BOUNDS = {
684
+ historyWindowHours: { min: 0.1, max: 168, step: 0.1 },
685
+ historyResolutionS: { min: 0.01, max: 60, step: 0.01 },
686
+ shadeLimitMetres: { min: 1, max: 200, step: 1 },
687
+ contourIntervalMetres: { min: 0.5, max: 50, step: 0.5 },
688
+ cellPx: { min: 4, max: 128, step: 1 },
689
+ mergeTolerance: { min: 0, max: 1, step: 0.01 },
690
+ maxDepthMetres: { min: 1, max: 500, step: 1 },
691
+ safetyDepthMetres: { min: 0, max: 50, step: 0.5 }
692
+ };
693
+ function mergeSettings(stored) {
694
+ const out = { ...DEFAULT_SETTINGS };
695
+ if (!stored) {
696
+ return out;
697
+ }
698
+ for (const key of Object.keys(
699
+ DEFAULT_SETTINGS
700
+ )) {
701
+ const value = stored[key];
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;
708
+ }
709
+ out[key] = value;
710
+ }
711
+ return out;
712
+ }
713
+
714
+ // src/web/config.ts
715
+ var METRES_PER_FOOT = 0.3048;
716
+ var client = await connectExtension();
717
+ var settings = { ...DEFAULT_SETTINGS };
718
+ var depthUnit = "m";
719
+ var root = document.getElementById("root");
720
+ var FIELDS = [
721
+ {
722
+ key: "historyWindowHours",
723
+ label: "History window",
724
+ hint: "How far back to fetch depth.",
725
+ suffix: "h",
726
+ historyOnly: true
727
+ },
728
+ {
729
+ key: "historyResolutionS",
730
+ label: "Sample interval",
731
+ hint: "Seconds between depth samples; smaller is finer.",
732
+ suffix: "s",
733
+ historyOnly: true
734
+ },
735
+ {
736
+ key: "shadeLimitMetres",
737
+ label: "Shade limit",
738
+ hint: "Depth the shading runs to; deeper is left to the chart.",
739
+ depth: true
740
+ },
741
+ {
742
+ key: "contourIntervalMetres",
743
+ label: "Isobath interval",
744
+ hint: "Depth between isobaths.",
745
+ depth: true
746
+ },
747
+ {
748
+ key: "cellPx",
749
+ label: "Grid cell",
750
+ hint: "Circle diameter, in screen pixels.",
751
+ suffix: "px"
752
+ },
753
+ {
754
+ key: "mergeTolerance",
755
+ label: "Merge band",
756
+ hint: "Adjacent positions within this depth fraction merge into one circle."
757
+ },
758
+ {
759
+ key: "maxDepthMetres",
760
+ label: "Max depth",
761
+ hint: "Samples deeper than this are not drawn.",
762
+ depth: true
763
+ },
764
+ {
765
+ key: "safetyDepthMetres",
766
+ label: "Safety depth",
767
+ hint: "Samples at or below this depth are marked individually.",
768
+ depth: true
769
+ }
770
+ ];
771
+ var unitSymbol = () => depthUnit === "foot" ? "ft" : "m";
772
+ var toDisplay = (metres) => depthUnit === "foot" ? metres / METRES_PER_FOOT : metres;
773
+ var fromDisplay = (value) => depthUnit === "foot" ? value * METRES_PER_FOOT : value;
774
+ var round = (v) => Math.round(v * 100) / 100;
775
+ var historyRows = [];
776
+ var historyInputs = [];
777
+ function syncHistoryDisabled() {
778
+ const disabled = settings.source === "tiles";
779
+ for (const input of historyInputs) {
780
+ input.disabled = disabled;
781
+ }
782
+ for (const row of historyRows) {
783
+ row.classList.toggle("disabled", disabled);
784
+ }
785
+ }
786
+ function displayBounds(field) {
787
+ const b = SETTINGS_BOUNDS[field.key];
788
+ if (!field.depth || depthUnit !== "foot") {
789
+ return b;
790
+ }
791
+ return {
792
+ min: Math.floor(toDisplay(b.min)),
793
+ max: Math.round(toDisplay(b.max)),
794
+ step: 1
795
+ };
796
+ }
797
+ function numberField(field) {
798
+ const { min: lo, max: hi, step } = displayBounds(field);
799
+ const wrap2 = document.createElement("label");
800
+ wrap2.className = "field";
801
+ const name = document.createElement("span");
802
+ name.className = "name";
803
+ name.textContent = field.label;
804
+ wrap2.appendChild(name);
805
+ const control = document.createElement("span");
806
+ control.className = "control";
807
+ const input = document.createElement("input");
808
+ input.type = "number";
809
+ input.min = `${lo}`;
810
+ input.max = `${hi}`;
811
+ input.step = `${step}`;
812
+ const stored = settings[field.key];
813
+ input.value = `${round(field.depth ? toDisplay(stored) : stored)}`;
814
+ input.addEventListener("change", () => {
815
+ const clamped = Math.min(hi, Math.max(lo, Number(input.value)));
816
+ input.value = `${round(clamped)}`;
817
+ settings[field.key] = field.depth ? fromDisplay(clamped) : clamped;
818
+ void save();
819
+ });
820
+ control.appendChild(input);
821
+ const suffix = document.createElement("span");
822
+ suffix.className = "suffix";
823
+ suffix.textContent = field.depth ? unitSymbol() : field.suffix ?? "";
824
+ control.appendChild(suffix);
825
+ wrap2.appendChild(control);
826
+ if (field.hint) {
827
+ const hint = document.createElement("span");
828
+ hint.className = "hint";
829
+ hint.textContent = field.hint;
830
+ wrap2.appendChild(hint);
831
+ }
832
+ if (field.historyOnly) {
833
+ historyRows.push(wrap2);
834
+ historyInputs.push(input);
835
+ }
836
+ return wrap2;
837
+ }
838
+ function build() {
839
+ root.replaceChildren();
840
+ historyRows.length = 0;
841
+ historyInputs.length = 0;
842
+ const heading = document.createElement("h2");
843
+ heading.textContent = "LiveDepth";
844
+ root.appendChild(heading);
845
+ const enabled = document.createElement("label");
846
+ enabled.className = "field row";
847
+ const enabledName = document.createElement("span");
848
+ enabledName.className = "name";
849
+ enabledName.textContent = "Show the layer";
850
+ const enabledInput = document.createElement("input");
851
+ enabledInput.type = "checkbox";
852
+ enabledInput.checked = settings.enabled;
853
+ enabledInput.addEventListener("change", () => {
854
+ settings.enabled = enabledInput.checked;
855
+ void save();
856
+ });
857
+ enabled.append(enabledName, enabledInput);
858
+ root.appendChild(enabled);
859
+ const source = document.createElement("label");
860
+ source.className = "field";
861
+ const sourceName = document.createElement("span");
862
+ sourceName.className = "name";
863
+ sourceName.textContent = "Depth source";
864
+ const select = document.createElement("select");
865
+ for (const [value, label] of [
866
+ ["history", "History (this vessel's track)"],
867
+ ["tiles", "LiveDepth plugin tiles"]
868
+ ]) {
869
+ const option = document.createElement("option");
870
+ option.value = value;
871
+ option.textContent = label;
872
+ option.selected = settings.source === value;
873
+ select.appendChild(option);
874
+ }
875
+ select.addEventListener("change", () => {
876
+ settings.source = select.value;
877
+ syncHistoryDisabled();
878
+ void save();
879
+ });
880
+ source.append(sourceName, select);
881
+ const sourceHint = document.createElement("span");
882
+ sourceHint.className = "hint";
883
+ sourceHint.textContent = "History draws the vessel's own recorded track; tiles draw the server-built bathymetry store, extended live.";
884
+ source.appendChild(sourceHint);
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);
914
+ for (const field of FIELDS) {
915
+ root.appendChild(numberField(field));
916
+ }
917
+ syncHistoryDisabled();
918
+ }
919
+ async function save() {
920
+ await client.state.set(settings);
921
+ }
922
+ async function load() {
923
+ try {
924
+ settings = mergeSettings(
925
+ await client.state.get()
926
+ );
927
+ } catch {
928
+ settings = { ...DEFAULT_SETTINGS };
929
+ }
930
+ try {
931
+ const res = await client.call("units.get");
932
+ depthUnit = res?.units?.depth === "foot" ? "foot" : "m";
933
+ } catch {
934
+ depthUnit = "m";
935
+ }
936
+ build();
937
+ }
938
+ await load();
939
+ //# sourceMappingURL=config.js.map