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.
- package/CHANGELOG.md +113 -0
- package/README.md +67 -41
- package/dist/collector.d.ts +1 -1
- package/dist/collector.d.ts.map +1 -1
- package/dist/config.d.ts +52 -3
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +41 -4
- package/dist/config.js.map +1 -1
- package/dist/plotterext/manifest.d.ts +24 -0
- package/dist/plotterext/manifest.d.ts.map +1 -0
- package/dist/plotterext/manifest.js +79 -0
- package/dist/plotterext/manifest.js.map +1 -0
- package/dist/plotterext/provider.d.ts +7 -0
- package/dist/plotterext/provider.d.ts.map +1 -0
- package/dist/plotterext/provider.js +61 -0
- package/dist/plotterext/provider.js.map +1 -0
- package/dist/plugin.d.ts +15 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +40 -8
- package/dist/plugin.js.map +1 -1
- package/dist/streams/live.d.ts +16 -4
- package/dist/streams/live.d.ts.map +1 -1
- package/dist/streams/live.js +164 -43
- package/dist/streams/live.js.map +1 -1
- package/dist/tiles/api.d.ts +21 -11
- package/dist/tiles/api.d.ts.map +1 -1
- package/dist/tiles/api.js +36 -27
- package/dist/tiles/api.js.map +1 -1
- package/dist/tiles/core/accumulator.d.ts +37 -0
- package/dist/tiles/core/accumulator.d.ts.map +1 -0
- package/dist/tiles/core/accumulator.js +75 -0
- package/dist/tiles/core/accumulator.js.map +1 -0
- package/dist/tiles/core/correction.d.ts +32 -0
- package/dist/tiles/core/correction.d.ts.map +1 -0
- package/dist/tiles/core/correction.js +43 -0
- package/dist/tiles/core/correction.js.map +1 -0
- package/dist/tiles/core/grid.d.ts +64 -0
- package/dist/tiles/core/grid.d.ts.map +1 -0
- package/dist/tiles/core/grid.js +127 -0
- package/dist/tiles/core/grid.js.map +1 -0
- package/dist/tiles/core/index.d.ts +7 -0
- package/dist/tiles/core/index.d.ts.map +1 -0
- package/dist/tiles/core/index.js +7 -0
- package/dist/tiles/core/index.js.map +1 -0
- package/dist/tiles/core/store.d.ts +37 -0
- package/dist/tiles/core/store.d.ts.map +1 -0
- package/dist/tiles/core/store.js +86 -0
- package/dist/tiles/core/store.js.map +1 -0
- package/dist/tiles/core/tile-source.d.ts +50 -0
- package/dist/tiles/core/tile-source.d.ts.map +1 -0
- package/dist/tiles/core/tile-source.js +86 -0
- package/dist/tiles/core/tile-source.js.map +1 -0
- package/dist/tiles/core/types.d.ts +11 -0
- package/dist/tiles/core/types.d.ts.map +1 -0
- package/dist/tiles/core/types.js +2 -0
- package/dist/tiles/core/types.js.map +1 -0
- package/dist/tiles/index.d.ts +1 -1
- package/dist/tiles/index.d.ts.map +1 -1
- package/dist/tiles/index.js +1 -1
- package/dist/tiles/index.js.map +1 -1
- package/dist/tiles/seed.d.ts +1 -1
- package/dist/tiles/seed.d.ts.map +1 -1
- package/dist/tiles/seed.js +1 -1
- package/dist/tiles/seed.js.map +1 -1
- package/dist/tiles/store.d.ts +1 -1
- package/dist/tiles/store.d.ts.map +1 -1
- package/dist/tiles/store.js +1 -1
- package/dist/tiles/store.js.map +1 -1
- package/dist/tiles/writer.d.ts +1 -1
- package/dist/tiles/writer.d.ts.map +1 -1
- package/dist/tiles/writer.js +1 -1
- package/dist/tiles/writer.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 +27 -6
- package/public/config.html +14 -0
- package/public/index.html +15 -0
- package/public/js/config.js +939 -0
- package/public/js/config.js.map +7 -0
- package/public/js/render-worker.js +866 -0
- package/public/js/render-worker.js.map +7 -0
- package/public/js/renderer.js +1159 -0
- package/public/js/renderer.js.map +7 -0
- package/public/renderer.html +14 -0
- 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
|