signalk-siparu 0.2.12 → 0.2.13
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/package.json +1 -1
- package/plugin/dist/charts.js +11 -0
- package/plugin/dist/contract.d.ts +5 -0
- package/plugin/dist/index.js +30 -15
- package/plugin/dist/keygate.d.ts +10 -0
- package/plugin/dist/keygate.js +19 -0
- package/plugin/dist/keystore.d.ts +3 -1
- package/plugin/dist/keystore.js +19 -3
- package/plugin/dist/keysync.js +4 -2
- package/plugin/dist/live.d.ts +2 -0
- package/plugin/dist/live.js +21 -3
- package/plugin/dist/recording.d.ts +6 -0
- package/plugin/dist/recording.js +10 -0
- package/plugin/dist/store.d.ts +9 -1
- package/plugin/dist/store.js +27 -4
- package/public/assets/Logbook-B9IEQukR.js +1 -0
- package/public/assets/{Map-CEwUZlRh.js → Map-BNJEm3Bu.js} +1 -1
- package/public/assets/{Remote--W7QE6vQ.js → Remote-1C36JQSl.js} +1 -1
- package/public/assets/{SecurityHelp-CmkDtKXa.js → SecurityHelp-DmcNvYO2.js} +1 -1
- package/public/assets/Voyage-DSmv8QxF.js +1 -0
- package/public/assets/{export-CQYXr7nu.js → export-CQSqkXJD.js} +1 -1
- package/public/assets/{index-DnKxzCIS.css → index-CsmkB_U3.css} +1 -1
- package/public/assets/{index-D8cInHV9.js → index-jUObqoMX.js} +2 -2
- package/public/index.html +2 -2
- package/public/assets/Logbook-B8_oMwuZ.js +0 -1
- package/public/assets/Voyage-B5ibpNOg.js +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "signalk-siparu",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.13",
|
|
4
4
|
"description": "Kept aboard, proven ashore. Position, wind, depth and voyage history, recorded on the boat and readable from anywhere: sealed to the devices you name, signed on the way out. A read-only Signal K plugin with a built-in dashboard.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"signalk-node-server-plugin",
|
package/plugin/dist/charts.js
CHANGED
|
@@ -93,6 +93,17 @@ function safeChartPath(dataDir, rel) {
|
|
|
93
93
|
return null;
|
|
94
94
|
if (!SERVABLE_EXT.has(path.extname(abs).toLowerCase()))
|
|
95
95
|
return null;
|
|
96
|
+
let real;
|
|
97
|
+
let realDir;
|
|
98
|
+
try {
|
|
99
|
+
real = fs.realpathSync(abs);
|
|
100
|
+
realDir = fs.realpathSync(dir);
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return abs;
|
|
104
|
+
}
|
|
105
|
+
if (real !== realDir && !real.startsWith(realDir + path.sep))
|
|
106
|
+
return null;
|
|
96
107
|
return abs;
|
|
97
108
|
}
|
|
98
109
|
function chartContentType(absPath) {
|
|
@@ -244,6 +244,11 @@ export interface HealthResult {
|
|
|
244
244
|
cap_bytes: number;
|
|
245
245
|
raw_files: number;
|
|
246
246
|
oldest_raw: string | null;
|
|
247
|
+
writes?: {
|
|
248
|
+
ok: boolean;
|
|
249
|
+
failures: number;
|
|
250
|
+
last_error: string | null;
|
|
251
|
+
};
|
|
247
252
|
};
|
|
248
253
|
rollup: {
|
|
249
254
|
last_hour: string | null;
|
package/plugin/dist/index.js
CHANGED
|
@@ -48,6 +48,8 @@ const remotelink_1 = require("./remotelink");
|
|
|
48
48
|
const rest_1 = require("./rest");
|
|
49
49
|
const rollup_1 = require("./rollup");
|
|
50
50
|
const store_1 = require("./store");
|
|
51
|
+
const recording_1 = require("./recording");
|
|
52
|
+
const keygate_1 = require("./keygate");
|
|
51
53
|
const time_1 = require("./time");
|
|
52
54
|
const live_1 = require("./live");
|
|
53
55
|
const track_1 = require("./track");
|
|
@@ -155,26 +157,31 @@ module.exports = (app) => {
|
|
|
155
157
|
const snap = state.snapshot(now, true, config_1.INTERNAL.fabricationHorizonMs);
|
|
156
158
|
const numeric = state.numericDynamicPaths(now, config_1.INTERNAL.fabricationHorizonMs);
|
|
157
159
|
const stored = Object.keys(numeric).length > 0 ? { ...snap, path_values: numeric } : snap;
|
|
158
|
-
await store.append(stored);
|
|
160
|
+
const written = await store.append(stored);
|
|
159
161
|
if (voyages)
|
|
160
|
-
await voyages.feed(stored);
|
|
162
|
+
await voyages.feed(stored).catch((err) => app.debug(`voyage feed failed: ${err}`));
|
|
161
163
|
if (phases)
|
|
162
|
-
await phases.feed(stored);
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
countedDay
|
|
167
|
-
|
|
164
|
+
await phases.feed(stored).catch((err) => app.debug(`phase feed failed: ${err}`));
|
|
165
|
+
if (written) {
|
|
166
|
+
lastSnapshotTs = now;
|
|
167
|
+
const today = (0, time_1.dayKey)(now);
|
|
168
|
+
if (today !== countedDay) {
|
|
169
|
+
countedDay = today;
|
|
170
|
+
snapshotsToday = 0;
|
|
171
|
+
}
|
|
172
|
+
snapshotsToday++;
|
|
168
173
|
}
|
|
169
|
-
snapshotsToday++;
|
|
170
174
|
const age = state.lastDeltaTs === null ? null : Math.round((now - state.lastDeltaTs) / 1000);
|
|
171
175
|
const held = sealingState();
|
|
172
176
|
const silent = held.mode === 'blocked' ? ` - NOT reporting ashore: ${held.reason}` : '';
|
|
173
|
-
app.setPluginStatus(
|
|
177
|
+
app.setPluginStatus((0, recording_1.statusLine)(store.writes(), snapshotsToday, age) + silent);
|
|
174
178
|
}
|
|
175
179
|
function sealingState() {
|
|
176
180
|
if (!sealer || !ensureRemoteLink().getRemote())
|
|
177
181
|
return { mode: 'none', reason: null };
|
|
182
|
+
const gate = keysStand();
|
|
183
|
+
if (gate)
|
|
184
|
+
return gate;
|
|
178
185
|
const s = sealer.state();
|
|
179
186
|
const view = screensNow?.();
|
|
180
187
|
if (s.mode === 'blocked' && view?.pinned && view.accepted.length === 0 && view.skipped.length > 0) {
|
|
@@ -185,6 +192,12 @@ module.exports = (app) => {
|
|
|
185
192
|
}
|
|
186
193
|
return s;
|
|
187
194
|
}
|
|
195
|
+
function keysStand() {
|
|
196
|
+
if (!keySync || !boatKeyStore)
|
|
197
|
+
return null;
|
|
198
|
+
const status = keySync.status();
|
|
199
|
+
return (0, keygate_1.keyGate)({ state: status.state, lastError: status.lastError, refused: boatKeyStore.refused() });
|
|
200
|
+
}
|
|
188
201
|
function fuelPathsView() {
|
|
189
202
|
return {
|
|
190
203
|
available: state
|
|
@@ -198,7 +211,8 @@ module.exports = (app) => {
|
|
|
198
211
|
throw new Error('not started');
|
|
199
212
|
const now = Date.now();
|
|
200
213
|
const usage = await store.rawUsage();
|
|
201
|
-
const
|
|
214
|
+
const writes = store.writes();
|
|
215
|
+
const degraded = state.lastDeltaTs === null || now - state.lastDeltaTs > config_1.INTERNAL.degradedAfterMs || !writes.ok;
|
|
202
216
|
return {
|
|
203
217
|
status: degraded ? 'degraded' : 'ok',
|
|
204
218
|
now,
|
|
@@ -216,7 +230,8 @@ module.exports = (app) => {
|
|
|
216
230
|
raw_bytes: usage.bytes,
|
|
217
231
|
cap_bytes: opts.maxStorageMB * 1024 * 1024,
|
|
218
232
|
raw_files: usage.files,
|
|
219
|
-
oldest_raw: usage.oldest
|
|
233
|
+
oldest_raw: usage.oldest,
|
|
234
|
+
writes
|
|
220
235
|
},
|
|
221
236
|
rollup: await rollups.status(now),
|
|
222
237
|
ais: aisMemory?.status() ?? { receiver_seen: false, first_seen_ts: null },
|
|
@@ -293,7 +308,7 @@ module.exports = (app) => {
|
|
|
293
308
|
const pl = new phaselog_1.PhaseLog(st, opts, (msg) => app.debug(msg));
|
|
294
309
|
phases = pl;
|
|
295
310
|
st.onHourClosed = async () => {
|
|
296
|
-
await ru.catchUp(Date.now());
|
|
311
|
+
await ru.catchUp(Date.now()).catch((err) => app.debug(`rollup catch-up failed: ${err}`));
|
|
297
312
|
await st.enforceCap();
|
|
298
313
|
};
|
|
299
314
|
try {
|
|
@@ -304,7 +319,7 @@ module.exports = (app) => {
|
|
|
304
319
|
app.setPluginStatus('Starting');
|
|
305
320
|
st.init(now)
|
|
306
321
|
.then(async () => {
|
|
307
|
-
await ru.catchUp(Date.now());
|
|
322
|
+
await ru.catchUp(Date.now()).catch((err) => app.error(`rollup catch-up failed at start: ${err}`));
|
|
308
323
|
await st.enforceCap();
|
|
309
324
|
await vl.init(Date.now());
|
|
310
325
|
await pl.init(Date.now());
|
|
@@ -418,7 +433,7 @@ module.exports = (app) => {
|
|
|
418
433
|
onFuelQuery: async () => fuelPathsView(),
|
|
419
434
|
onAisQuery: async (maxNm, limit) => (0, ais_1.buildAisFeed)(app.getPath('vessels'), app.selfContext, Date.now(), (0, ais_1.clampAisQuery)(maxNm, undefined, limit)),
|
|
420
435
|
onHealthQuery: () => health(),
|
|
421
|
-
seal: (frame) => seal.seal(frame),
|
|
436
|
+
seal: (frame) => keysStand() ?? seal.seal(frame),
|
|
422
437
|
onWatching: () => ks.prompt(),
|
|
423
438
|
openAsk: (envelope) => seal.open(envelope),
|
|
424
439
|
sealAnswer: (payload, id) => seal.answer(payload, id),
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.keyGate = keyGate;
|
|
4
|
+
function keyGate(input) {
|
|
5
|
+
if (input.state === 'mismatch') {
|
|
6
|
+
return {
|
|
7
|
+
mode: 'blocked',
|
|
8
|
+
reason: input.lastError ?? 'Siparu already holds different keys for this boat. Unlink her and pair again.'
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
if (input.refused) {
|
|
12
|
+
return {
|
|
13
|
+
mode: 'blocked',
|
|
14
|
+
reason: input.lastError ??
|
|
15
|
+
'The key file on this boat cannot be read. Restore keys.json from a backup of her data directory, or unlink her and pair again.'
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
@@ -10,12 +10,14 @@ export interface BoatPublicKeys {
|
|
|
10
10
|
export declare class BoatKeyStore {
|
|
11
11
|
private readonly file;
|
|
12
12
|
private keys;
|
|
13
|
+
private unreadable;
|
|
13
14
|
private writeChain;
|
|
14
15
|
constructor(dataDir: string);
|
|
15
16
|
load(): void;
|
|
17
|
+
refused(): boolean;
|
|
16
18
|
get(): BoatKeys | undefined;
|
|
17
19
|
publicKeys(): BoatPublicKeys | undefined;
|
|
18
|
-
ensure(): Promise<BoatKeys>;
|
|
20
|
+
ensure(): Promise<BoatKeys | undefined>;
|
|
19
21
|
flush(): Promise<void>;
|
|
20
22
|
private persist;
|
|
21
23
|
}
|
package/plugin/dist/keystore.js
CHANGED
|
@@ -48,22 +48,32 @@ function isPair(value) {
|
|
|
48
48
|
class BoatKeyStore {
|
|
49
49
|
file;
|
|
50
50
|
keys;
|
|
51
|
+
unreadable = false;
|
|
51
52
|
writeChain = Promise.resolve();
|
|
52
53
|
constructor(dataDir) {
|
|
53
54
|
this.file = path.join(dataDir, 'keys.json');
|
|
54
55
|
}
|
|
55
56
|
load() {
|
|
57
|
+
this.keys = undefined;
|
|
58
|
+
this.unreadable = false;
|
|
59
|
+
let text;
|
|
60
|
+
try {
|
|
61
|
+
text = fs.readFileSync(this.file, 'utf8');
|
|
62
|
+
}
|
|
63
|
+
catch (e) {
|
|
64
|
+
this.unreadable = e?.code !== 'ENOENT';
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
this.unreadable = true;
|
|
56
68
|
let parsed;
|
|
57
69
|
try {
|
|
58
|
-
parsed = JSON.parse(
|
|
70
|
+
parsed = JSON.parse(text);
|
|
59
71
|
}
|
|
60
72
|
catch {
|
|
61
|
-
this.keys = undefined;
|
|
62
73
|
return;
|
|
63
74
|
}
|
|
64
75
|
const raw = (parsed ?? {});
|
|
65
76
|
if (raw.v !== FILE_VERSION || !isPair(raw.identity) || !isPair(raw.inbox)) {
|
|
66
|
-
this.keys = undefined;
|
|
67
77
|
return;
|
|
68
78
|
}
|
|
69
79
|
try {
|
|
@@ -72,11 +82,15 @@ class BoatKeyStore {
|
|
|
72
82
|
assertHalvesAgree(identity, Buffer.from(raw.identity.pub, 'base64url'));
|
|
73
83
|
assertHalvesAgree(inbox, Buffer.from(raw.inbox.pub, 'base64url'));
|
|
74
84
|
this.keys = { identity, inbox };
|
|
85
|
+
this.unreadable = false;
|
|
75
86
|
}
|
|
76
87
|
catch {
|
|
77
88
|
this.keys = undefined;
|
|
78
89
|
}
|
|
79
90
|
}
|
|
91
|
+
refused() {
|
|
92
|
+
return this.unreadable;
|
|
93
|
+
}
|
|
80
94
|
get() {
|
|
81
95
|
return this.keys;
|
|
82
96
|
}
|
|
@@ -91,6 +105,8 @@ class BoatKeyStore {
|
|
|
91
105
|
async ensure() {
|
|
92
106
|
if (this.keys)
|
|
93
107
|
return this.keys;
|
|
108
|
+
if (this.unreadable)
|
|
109
|
+
return undefined;
|
|
94
110
|
const identity = (0, crypto_1.generateKeyPairSync)('ed25519');
|
|
95
111
|
const inbox = (0, crypto_1.generateKeyPairSync)('x25519');
|
|
96
112
|
this.keys = { identity: identity.privateKey, inbox: inbox.privateKey };
|
package/plugin/dist/keysync.js
CHANGED
|
@@ -129,8 +129,10 @@ class KeySync {
|
|
|
129
129
|
if (!pub) {
|
|
130
130
|
this.failures++;
|
|
131
131
|
this.state = 'failing';
|
|
132
|
-
this.lastError =
|
|
133
|
-
|
|
132
|
+
this.lastError = this.deps.keys.refused()
|
|
133
|
+
? 'The key file on this boat cannot be read. Restore keys.json from a backup of her data directory, or unlink her and pair again.'
|
|
134
|
+
: 'Could not create the keys for this boat on disk.';
|
|
135
|
+
this.deps.debug(`keysync: ${this.lastError}`);
|
|
134
136
|
return;
|
|
135
137
|
}
|
|
136
138
|
const controller = new AbortController();
|
package/plugin/dist/live.d.ts
CHANGED
package/plugin/dist/live.js
CHANGED
|
@@ -7,6 +7,9 @@ exports.FRAME_EVERY_MS = 2_000;
|
|
|
7
7
|
exports.STILL_FRAME_EVERY_MS = 60_000;
|
|
8
8
|
exports.REPORTING_STALE_MS = exports.STILL_FRAME_EVERY_MS * 3;
|
|
9
9
|
const UNDERWAY_SOG_MS = 0.3 * 0.514444;
|
|
10
|
+
const CLEARLY_UNDERWAY_SOG_MS = 1.5 * 0.514444;
|
|
11
|
+
const UNDERWAY_STREAK = 3;
|
|
12
|
+
const STILL_NAV_STATES = new Set(['anchored', 'moored', 'aground']);
|
|
10
13
|
exports.PING_EVERY_MS = 25_000;
|
|
11
14
|
const RECONNECT_BASE_MS = 5_000;
|
|
12
15
|
const RECONNECT_CEILING_MS = 15 * 60_000;
|
|
@@ -211,6 +214,7 @@ class LiveUplink {
|
|
|
211
214
|
const frame = this.deps.frame();
|
|
212
215
|
const sog = frame.sog;
|
|
213
216
|
this.lastSog = typeof sog === 'number' && Number.isFinite(sog) ? sog : null;
|
|
217
|
+
this.noteWay(this.lastSog, frame.nav_state);
|
|
214
218
|
const verdict = this.deps.seal(frame);
|
|
215
219
|
if (verdict.mode === 'blocked') {
|
|
216
220
|
return;
|
|
@@ -234,12 +238,26 @@ class LiveUplink {
|
|
|
234
238
|
this.scheduleFrame(gen);
|
|
235
239
|
}, this.nextFrameDelayMs());
|
|
236
240
|
}
|
|
241
|
+
wayStreak = 0;
|
|
242
|
+
noteWay(sog, navState) {
|
|
243
|
+
const heldStill = typeof navState === 'string' && STILL_NAV_STATES.has(navState);
|
|
244
|
+
if (sog === null || sog < UNDERWAY_SOG_MS) {
|
|
245
|
+
this.wayStreak = 0;
|
|
246
|
+
}
|
|
247
|
+
else if (sog >= CLEARLY_UNDERWAY_SOG_MS) {
|
|
248
|
+
this.wayStreak = UNDERWAY_STREAK;
|
|
249
|
+
}
|
|
250
|
+
else if (heldStill) {
|
|
251
|
+
this.wayStreak = 0;
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
this.wayStreak = Math.min(this.wayStreak + 1, UNDERWAY_STREAK);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
237
257
|
nextFrameDelayMs() {
|
|
238
258
|
if (this.fixedFrameMs !== null)
|
|
239
259
|
return this.fixedFrameMs;
|
|
240
|
-
|
|
241
|
-
return exports.STILL_FRAME_EVERY_MS;
|
|
242
|
-
return exports.FRAME_EVERY_MS;
|
|
260
|
+
return this.wayStreak >= UNDERWAY_STREAK ? exports.FRAME_EVERY_MS : exports.STILL_FRAME_EVERY_MS;
|
|
243
261
|
}
|
|
244
262
|
handleHistory(gen, data) {
|
|
245
263
|
const handler = this.deps.onHistoryQuery;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.statusLine = statusLine;
|
|
4
|
+
function statusLine(writes, rowsToday, dataAgeSeconds) {
|
|
5
|
+
const age = dataAgeSeconds === null ? ', no data yet' : dataAgeSeconds > 60 ? `, data age ${dataAgeSeconds}s` : '';
|
|
6
|
+
if (!writes.ok) {
|
|
7
|
+
return `NOT recording - the disk refused the last row (${writes.last_error ?? 'unknown error'}); ${rowsToday} rows today before it did${age}`;
|
|
8
|
+
}
|
|
9
|
+
return `Recording - ${rowsToday} rows today${age}`;
|
|
10
|
+
}
|
package/plugin/dist/store.d.ts
CHANGED
|
@@ -9,12 +9,20 @@ export declare class Store {
|
|
|
9
9
|
private currentHourKey;
|
|
10
10
|
private knownKeys;
|
|
11
11
|
private bytes;
|
|
12
|
+
private lastWriteOk;
|
|
13
|
+
private writeFailures;
|
|
14
|
+
private lastWriteError;
|
|
12
15
|
onHourClosed: ((closedHour: string) => Promise<void>) | null;
|
|
13
16
|
constructor(dataDir: string, capBytes: number, log: Logger);
|
|
14
17
|
init(now: number): Promise<void>;
|
|
15
18
|
private enqueue;
|
|
16
19
|
rawPath(hour: string): string;
|
|
17
|
-
append(snap: Snapshot): Promise<
|
|
20
|
+
append(snap: Snapshot): Promise<boolean>;
|
|
21
|
+
writes(): {
|
|
22
|
+
ok: boolean;
|
|
23
|
+
failures: number;
|
|
24
|
+
last_error: string | null;
|
|
25
|
+
};
|
|
18
26
|
listRawKeys(): Promise<string[]>;
|
|
19
27
|
readRaw(hour: string): Promise<Snapshot[]>;
|
|
20
28
|
rawUsage(): Promise<{
|
package/plugin/dist/store.js
CHANGED
|
@@ -47,6 +47,9 @@ class Store {
|
|
|
47
47
|
currentHourKey = null;
|
|
48
48
|
knownKeys = new Set();
|
|
49
49
|
bytes = 0;
|
|
50
|
+
lastWriteOk = true;
|
|
51
|
+
writeFailures = 0;
|
|
52
|
+
lastWriteError = null;
|
|
50
53
|
onHourClosed = null;
|
|
51
54
|
constructor(dataDir, capBytes, log) {
|
|
52
55
|
this.capBytes = capBytes;
|
|
@@ -75,17 +78,31 @@ class Store {
|
|
|
75
78
|
return path.join(this.rawDir, `${hour}.ndjson`);
|
|
76
79
|
}
|
|
77
80
|
append(snap) {
|
|
81
|
+
let written = false;
|
|
78
82
|
return this.enqueue(async () => {
|
|
79
83
|
const key = (0, time_1.hourKey)(snap.ts);
|
|
80
84
|
const closed = this.currentHourKey && key !== this.currentHourKey ? this.currentHourKey : null;
|
|
81
85
|
this.currentHourKey = key;
|
|
82
86
|
const line = JSON.stringify(snap) + '\n';
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
87
|
+
try {
|
|
88
|
+
await fs.appendFile(this.rawPath(key), line, 'utf8');
|
|
89
|
+
this.knownKeys.add(key);
|
|
90
|
+
this.bytes += Buffer.byteLength(line);
|
|
91
|
+
written = true;
|
|
92
|
+
this.lastWriteOk = true;
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
this.writeFailures++;
|
|
96
|
+
this.lastWriteOk = false;
|
|
97
|
+
this.lastWriteError = describeError(err);
|
|
98
|
+
this.log(`store error: raw append failed: ${this.lastWriteError}`);
|
|
99
|
+
}
|
|
86
100
|
if (closed && this.onHourClosed)
|
|
87
101
|
await this.onHourClosed(closed);
|
|
88
|
-
});
|
|
102
|
+
}).then(() => written);
|
|
103
|
+
}
|
|
104
|
+
writes() {
|
|
105
|
+
return { ok: this.lastWriteOk, failures: this.writeFailures, last_error: this.lastWriteError };
|
|
89
106
|
}
|
|
90
107
|
async listRawKeys() {
|
|
91
108
|
return [...this.knownKeys].sort();
|
|
@@ -129,6 +146,12 @@ class Store {
|
|
|
129
146
|
}
|
|
130
147
|
}
|
|
131
148
|
exports.Store = Store;
|
|
149
|
+
function describeError(err) {
|
|
150
|
+
const e = err;
|
|
151
|
+
const code = typeof e?.code === 'string' ? e.code : null;
|
|
152
|
+
const message = e instanceof Error ? e.message : String(err);
|
|
153
|
+
return code && !message.startsWith(code) ? `${code}: ${message}` : message;
|
|
154
|
+
}
|
|
132
155
|
function parseNdjson(text) {
|
|
133
156
|
const out = [];
|
|
134
157
|
for (const line of text.split('\n')) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{a as e}from"./rolldown-runtime-CNC7AqOf.js";import{n as t,t as n}from"./jsx-runtime-CaR_m4Xc.js";import{t as r}from"./visibleInterval-CFxPzgzx.js";import{A as i,C as a,D as o,E as s,N as c,O as l,P as u,S as d,T as f,_ as p,b as m,c as h,d as g,g as _,k as v,l as y,o as b,p as x,s as S,v as C,w}from"./index-jUObqoMX.js";import{a as T,d as E,i as D,l as O,n as k,o as A,r as j,t as M,u as N}from"./export-CQSqkXJD.js";var P=e(t(),1);function F(e){let t=new Date(e),n=e=>String(e).padStart(2,`0`);return`${n(t.getUTCHours())}:${n(t.getUTCMinutes())}`}var I={key:`ts`,head:`UTC`,book:`bridge`,cell:e=>F(e.ts)};function L(e,t,n,r){if(e===null)return`·`;let i=Math.abs(e),a=Math.floor(i),o=(i-a)*60;return`${String(a).padStart(t,`0`)}°${o.toFixed(3).padStart(6,`0`)}' ${e<0?r:n}`}function R(e){return[{col:{key:`lat`,head:`LAT`,book:`bridge`,lanes:2,cell:e=>L(e.lat,2,`N`,`S`)},has:e=>e.lat!==null},{col:{key:`lon`,head:`LON`,book:`bridge`,lanes:2,cell:e=>L(e.lon,3,`E`,`W`)},has:e=>e.lon!==null},{col:{key:`sog`,head:`SOG`,book:`bridge`,unit:`kn`,cell:e=>m(o(e.sog),1)},has:e=>e.sog!==null},{col:{key:`cog`,head:`COG`,book:`bridge`,cell:e=>{let t=s(e.cog);return t===null?`·`:Math.round(t)+`°`}},has:e=>e.cog!==null},{col:{key:`hdg`,head:`HDG`,book:`bridge`,cell:e=>{let t=s(e.heading_true??e.heading_mag);return t===null?`·`:Math.round(t)+`°`}},has:e=>(e.heading_true??e.heading_mag)!==null},{col:{key:`wind`,head:e===`kn`?`TWS`:`BFT`,book:`bridge`,unit:e===`kn`?`kn`:void 0,tappable:!0,cell:t=>{let n=w(t.wind_speed_true);return n===null?`·`:String(e===`kn`?Math.round(n):a(n)??`·`)}},has:e=>e.wind_speed_true!==null},{col:{key:`awa`,head:`AWA`,book:`bridge`,cell:e=>{let t=s(e.wind_angle_apparent);if(t===null)return`·`;let n=(t+180)%360-180;return`${Math.abs(Math.round(n))}°${n<0?`P`:`S`}`}},has:e=>e.wind_angle_apparent!==null},{col:{key:`baro`,head:`BARO`,book:`bridge`,unit:`hPa`,dim:!0,cell:e=>{let t=f(e.air_pressure_pa);return t===null?`·`:String(Math.round(t))}},has:e=>e.air_pressure_pa!==null},{col:{key:`air`,head:`AIR`,book:`bridge`,unit:`°C`,cell:e=>m(d(e.air_temp_k),1)},has:e=>e.air_temp_k!==null},{col:{key:`sea`,head:`SEA`,book:`bridge`,unit:`°C`,cell:e=>m(d(e.water_temp_k),1)},has:e=>e.water_temp_k!==null},{col:{key:`depth`,head:`DEP`,book:`bridge`,unit:`m`,cell:e=>e.depth===null?`·`:e.depth.toFixed(1)},has:e=>e.depth!==null}]}var z={Revolutions:`RPM`,Temperature:`TEMP`,"Oil pressure":`OIL`,"Oil temperature":`OILT`,"Coolant temperature":`COOL`,"Coolant pressure":`COOLP`,"Exhaust temperature":`EXH`,"Intake manifold temperature":`INTK`,"Boost pressure":`BOOST`,"Engine load":`LOAD`,"Engine torque":`TORQ`,"Run time":`HRS`,"Fuel rate":`FUEL`,"Fuel per mile":`L/NM`,"Fuel used (since reset)":`USED`,"Fuel pressure":`FUELP`,"Alternator voltage":`ALT`,"Gearbox oil temperature":`GBOXT`,"Gearbox oil pressure":`GBOXP`,"Current level":`LEVEL`,Voltage:`VOLT`,Current:`AMP`};function B(e){return z[e]??e.split(` `)[0].toUpperCase()}function V(e){return e.sub===null?e.tab===`tanks`?`Current level`:null:e.sub}function H(e){let t=/^(Engine|Generator|Fuel|Fresh water|Black water|Grey water|Lubrication)\s+(\S+)$/.exec(e);return t?`${t[1].split(` `).map(e=>e[0]).join(``)}${t[2]}`.toUpperCase():e.slice(0,1).toUpperCase()}function ee(e){let t=[];for(let n of e)for(let e of Object.keys(n.path_values??{}))t.includes(e)||t.push(e);let n=[];for(let e of t){let t=l(e);if(!t)continue;let r=V(t);r!==null&&n.push({path:e,d:{...t,sub:r}})}let r=new Map;for(let{d:e}of n)r.has(e.tab)||r.set(e.tab,new Set),r.get(e.tab).add(e.label);let a=[];for(let{path:e,d:t}of n){let n=B(t.sub);a.push({col:{key:`p:${e}`,head:r.get(t.tab).size>1?`${H(t.label)} ${n}`:n,book:`engine`,tab:t.tab,unit:i(e),cell:t=>{let n=t.path_values?.[e];if(typeof n!=`number`)return`·`;let r=v(e,n).value;return Number.isFinite(r)?Number.isInteger(r)?String(r):r.toFixed(1):`·`}},has:t=>typeof t.path_values?.[e]==`number`})}return a}function te(e,t){return[I,...[...R(t),...ee(e)].filter(t=>e.some(t.has)).map(e=>e.col)]}function ne(e,t){return e.filter(e=>e.key===`ts`||e.book===t)}var U={engine:`ENGINES`,generator:`GENERATORS`,tanks:`TANKS`};function re(e){let t=[],n=new Map,r=new Map;for(let i of e)for(let e of Object.keys(i.path_values??{})){let i=l(e);if(!i)continue;let a=V(i);if(a===null)continue;n.has(i.tab)||(t.push(i.tab),n.set(i.tab,new Map),r.set(i.tab,[]));let o=n.get(i.tab);o.has(i.label)||o.set(i.label,new Map),o.get(i.label).set(a,e);let s=r.get(i.tab);s.includes(a)||s.push(a)}return t.map(e=>{let t=[...n.get(e)].map(([e,t])=>({key:e,head:e.toUpperCase(),paths:Object.fromEntries(t)})),a=r.get(e).map(e=>{let n=t.map(t=>t.paths[e]).find(e=>e!==void 0);return{key:e,head:B(e),unit:n===void 0?``:i(n)}});return{tab:e,head:U[e]??e.toUpperCase(),units:t,metrics:a}})}function W(e,t,n){let r=t.paths[n.key];if(r===void 0)return`·`;let i=e.path_values?.[r];if(typeof i!=`number`)return`·`;let a=v(r,i).value;return Number.isFinite(a)?Number.isInteger(a)?String(a):a.toFixed(1):`·`}function G(e,t){let[n,r]=(0,P.useState)(e),[i,a]=(0,P.useState)(!1);return(0,P.useEffect)(()=>{if(e===n)return;a(!0);let i=setTimeout(()=>{r(e),a(!1)},t);return()=>clearTimeout(i)},[e,n,t]),[n,i]}var K=e=>`lb:columns:${e}`,q={off:[]};function J(e){return e.key===`ts`}function Y(e,t){return J(e)||!t.off.includes(e.key)}function ie(e,t){return e.filter(e=>Y(e,t))}function X(e){return e.filter(e=>!J(e))}function ae(e,t){return J(t)?e:{off:Y(t,e)?[...e.off,t.key]:e.off.filter(e=>e!==t.key)}}function oe(e,t){return{off:t?[]:X(e).map(e=>e.key)}}function se(e,t){return ue(e.off)===ue(t.off)}function ce(e){try{let t=globalThis.localStorage?.getItem(K(e));if(!t)return q;let n=JSON.parse(t);return{off:Array.isArray(n.off)?n.off.filter(e=>typeof e==`string`):[]}}catch{return q}}function le(e,t){try{globalThis.localStorage?.setItem(K(e),JSON.stringify(t))}catch{}}function ue(e){return[...new Set(e)].sort().join(` `)}var Z=n();function de({cols:e,applied:t,onApply:n,onCancel:r}){let[i,a]=(0,P.useState)(t),o=se(i,t),s=X(e),c=s.filter(e=>Y(e,i)).length;return(0,Z.jsxs)(`div`,{className:`lb-pick`,children:[(0,Z.jsxs)(`div`,{className:`lbp-book`,children:[(0,Z.jsxs)(`div`,{className:`lbp-h`,children:[(0,Z.jsx)(`span`,{className:`lbp-n`,children:`Columns`}),(0,Z.jsxs)(`span`,{className:`lbp-s`,children:[c,` of `,s.length]}),(0,Z.jsxs)(`span`,{className:`lbp-all`,children:[(0,Z.jsx)(`button`,{onClick:()=>a(oe(e,!0)),children:`All`}),(0,Z.jsx)(`button`,{onClick:()=>a(oe(e,!1)),children:`None`})]})]}),(0,Z.jsx)(`div`,{className:`lbp-chips`,children:s.map(e=>(0,Z.jsx)(`button`,{className:`lbp-c${Y(e,i)?` on`:``}`,"aria-pressed":Y(e,i),onClick:()=>a(ae(i,e)),children:e.head},e.key))})]}),(0,Z.jsxs)(`div`,{className:`lbp-act`,children:[(0,Z.jsx)(`button`,{onClick:r,children:`Cancel`}),(0,Z.jsx)(`button`,{className:`lbp-go`,disabled:o,onClick:()=>n(i),children:`Show these columns`})]})]})}var fe={"1m":1,"1h":60,"6h":360,"1d":1440},pe={"1m":`Every minute`,"1h":`Hourly`,"6h":`Six-hourly`,"1d":`Daily`},me={"1m":60,"1h":48,"6h":40,"1d":30},he=15e3;function ge(e){let t=c(),[n,i]=(0,P.useState)(0),[a,o]=(0,P.useState)(!1),[s,l]=(0,P.useState)([]),[u,d]=(0,P.useState)(null),[f,p]=(0,P.useState)(!1),[m,h]=(0,P.useState)(e);m!==e&&(h(e),i(0),o(!1));let g=fe[e],_=me[e]+n*me[e],v=(0,P.useCallback)(async()=>{p(!0),d(null);try{let e=_+1,n=g===1?(await t.logbook.minutes({limit:e,order:`desc`})).rows:await t.logbook.snapshots({bucket:g,limit:e,order:`desc`});o(n.length>_),l(n.slice(0,_))}catch(e){d(e.message)}finally{p(!1)}},[g,_]);return(0,P.useEffect)(()=>{v();let e=r(v,he);return()=>e()},[v]),{snaps:s,err:u,busy:f,hasMore:a,loadMore:(0,P.useCallback)(()=>i(e=>e+1),[])}}function _e(){let e=c(),[t,n]=(0,P.useState)(C()),[i,a]=(0,P.useState)([]),[o,s]=(0,P.useState)(null),[l,u]=(0,P.useState)(!1),d=(0,P.useRef)(!0),f=x(6e4),m=C(new Date(f)),h=(0,P.useMemo)(()=>p(t),[t]),g=h+24*36e5,_=t===m;(0,P.useEffect)(()=>{d.current&&t!==m&&n(m)},[m,t]);let v=(0,P.useCallback)(e=>{d.current=e===C(),n(e)},[]),y=(0,P.useCallback)(async()=>{u(!0),s(null);try{let t=await e.logbook.snapshots({from:h,to:g,limit:5e3,order:`desc`,bucket:60});a(t)}catch(e){s(e.message)}finally{u(!1)}},[h,g]);return(0,P.useEffect)(()=>{if(y(),!_)return;let e=r(y,he);return()=>e()},[y,_]),{dateStr:t,setDateStr:v,dayStart:h,isToday:_,snaps:i,err:o,busy:l,prevDay:(0,P.useCallback)(()=>v(C(new Date(h-864e5))),[h,v]),nextDay:(0,P.useCallback)(()=>v(C(new Date(h+864e5))),[h,v]),goToday:(0,P.useCallback)(()=>v(C()),[v])}}var ve=5e3;async function ye(e,t,n){let r=await e({from:t,to:n,limit:5001,order:`desc`});return{rows:r.rows.slice(0,ve),truncated:r.clamped||r.rows.length>5e3,minutesFrom:r.minutesFrom}}function be(e,t,n,r=`last`){let i=c(),[a,o]=(0,P.useState)([]),[s,l]=(0,P.useState)(null),[u,d]=(0,P.useState)(!1),[f,m]=(0,P.useState)(!1),[h,g]=(0,P.useState)(!1),[_,v]=(0,P.useState)(null),[y,b]=(0,P.useState)([]),x=(0,P.useMemo)(()=>p(e),[e]),S=(0,P.useMemo)(()=>p(t)+864e5-1,[t]),C=fe[n],w=(0,P.useCallback)(async()=>{if(Number.isNaN(x)||Number.isNaN(S)||S<x){o([]),b([]),m(!1),l(null),g(!0);return}g(!1),d(!0),l(null);try{if(b([]),C===1){let e=await ye(e=>i.logbook.minutes(e),x,S);v(e.minutesFrom),m(e.truncated),o(e.rows);return}let e=await i.logbook.rollupHours(x,S);v(null);let t=N(e,n),a=t.map(e=>E(e,r)),s=e=>e.slice(Math.max(0,e.length-ve));m(a.length>ve),o(s(a)),r!==`last`&&b(s(t.map(e=>E(e,`last`))))}catch(e){l(e.message)}finally{d(!1),g(!0)}},[x,S,C,n,r]);return(0,P.useEffect)(()=>{w()},[w]),{snaps:a,err:s,busy:u,truncated:f,loaded:h,minutesFrom:_,plain:y}}var xe=[`1m`,`1h`,`6h`,`1d`],Se=[{v:`csv`,name:`CSV`,note:`a file for a spreadsheet`},{v:`pdf`,name:`PDF`,note:`the page, through your printer`}],Ce=[{v:`screen`,name:`Screen`},{v:`paper`,name:`Paper`}],we=[`last`,`avg`,`min`,`max`];function Te(e,t,n){return e===`pdf`?[n]:t.includes(n)?t.filter(e=>e!==n):[...t,n]}function Ee(e,t){return e===`pdf`?[t[0]??`last`]:t}function De(){return C(new Date(Date.now()-7*864e5))}function Oe({initial:e,onView:t,onSave:n}){let[r,i]=(0,P.useState)(e?.from??De()),[a,o]=(0,P.useState)(e?.to??C()),[s,c]=(0,P.useState)(e?.gran??`1h`),[l,u]=(0,P.useState)(e?.format??`csv`),[d,f]=(0,P.useState)(e?.stats??[`last`]),[p,m]=(0,P.useState)(e?.distance??!1),[h,g]=(0,P.useState)(e?.samples??!1),[_,v]=(0,P.useState)(e?.style??`screen`),y=a<r,b={from:r,to:a,gran:s,format:l,stats:Ee(l,d),distance:p,samples:h,style:_},x=s!==`1m`,S=l===`pdf`,w=x&&!S&&d.length===0&&!p&&!h,T=e=>f(t=>Te(l,t,e)),E=Ee(l,d),D=e=>E.includes(e);return(0,Z.jsxs)(`div`,{className:`lb-pick lb-exp`,children:[(0,Z.jsxs)(`div`,{className:`lbp-books`,children:[(0,Z.jsxs)(`div`,{className:`lbp-book`,children:[(0,Z.jsxs)(`div`,{className:`lbp-h`,children:[(0,Z.jsx)(`span`,{className:`lbp-n`,children:`Window`}),(0,Z.jsx)(`span`,{className:`lbp-s`,children:`local days, both ends included`})]}),(0,Z.jsxs)(`div`,{className:`lbe-dates`,children:[(0,Z.jsxs)(`label`,{children:[(0,Z.jsx)(`span`,{children:`From`}),(0,Z.jsx)(`input`,{type:`date`,className:`dt`,value:r,max:C(),onChange:e=>i(e.target.value)})]}),(0,Z.jsxs)(`label`,{children:[(0,Z.jsx)(`span`,{children:`To`}),(0,Z.jsx)(`input`,{type:`date`,className:`dt`,value:a,max:C(),onChange:e=>o(e.target.value)})]})]})]}),(0,Z.jsxs)(`div`,{className:`lbp-book`,children:[(0,Z.jsxs)(`div`,{className:`lbp-h`,children:[(0,Z.jsx)(`span`,{className:`lbp-n`,children:`Interval`}),(0,Z.jsx)(`span`,{className:`lbp-s`,children:`one row per`})]}),(0,Z.jsx)(`div`,{className:`lbp-chips`,children:xe.map(e=>(0,Z.jsx)(`button`,{className:`lbp-c${s===e?` on`:``}`,onClick:()=>c(e),children:pe[e]},e))})]}),(0,Z.jsxs)(`div`,{className:`lbp-book`,children:[(0,Z.jsxs)(`div`,{className:`lbp-h`,children:[(0,Z.jsx)(`span`,{className:`lbp-n`,children:`Save as`}),(0,Z.jsx)(`span`,{className:`lbp-s`,children:Se.find(e=>e.v===l)?.note})]}),(0,Z.jsx)(`div`,{className:`lbp-chips`,children:Se.map(e=>(0,Z.jsx)(`button`,{className:`lbp-c${l===e.v?` on`:``}`,onClick:()=>u(e.v),children:e.name},e.v))})]}),(0,Z.jsxs)(`div`,{className:`lbp-book${S?``:` off`}`,children:[(0,Z.jsxs)(`div`,{className:`lbp-h`,children:[(0,Z.jsx)(`span`,{className:`lbp-n`,children:`Style`}),(0,Z.jsx)(`span`,{className:`lbp-s`,children:S?_===`screen`?`the app's own dark ink`:`white, for a printer`:`the page's ink; a file carries none`})]}),(0,Z.jsx)(`div`,{className:`lbp-chips`,children:Ce.map(e=>(0,Z.jsx)(`button`,{className:`lbp-c${_===e.v?` on`:``}`,disabled:!S,onClick:()=>v(e.v),children:e.name},e.v))})]})]}),x&&(0,Z.jsxs)(`div`,{className:`lbp-book lbp-figs`,children:[(0,Z.jsxs)(`div`,{className:`lbp-h`,children:[(0,Z.jsx)(`span`,{className:`lbp-n`,children:`Figures`}),(0,Z.jsx)(`span`,{className:`lbp-s`,children:S?`one: a table cell holds one number`:`one or more: a column each`})]}),(0,Z.jsxs)(`div`,{className:`lbp-chips`,children:[we.map(e=>(0,Z.jsx)(`button`,{className:`lbp-c${D(e)?` on`:``}`,onClick:()=>T(e),children:O[e]},e)),(0,Z.jsx)(`button`,{className:`lbp-c${p?` on`:``}`,disabled:S,onClick:()=>m(!p),children:`Distance`}),(0,Z.jsx)(`button`,{className:`lbp-c${h?` on`:``}`,disabled:S,onClick:()=>g(!h),children:`Samples`})]})]}),y&&(0,Z.jsx)(`div`,{className:`lbe-warn`,children:`That window ends before it begins.`}),w&&(0,Z.jsx)(`div`,{className:`lbe-warn`,children:`Choose at least one figure to put in the file.`}),(0,Z.jsxs)(`div`,{className:`lbp-act`,children:[S&&(0,Z.jsx)(`button`,{disabled:y,onClick:()=>t(b),children:`View`}),(0,Z.jsxs)(`button`,{className:`lbp-go`,disabled:y||w,onClick:()=>n(b),children:[`Save `,l.toUpperCase()]})]})]})}function Q({open:e,style:t,cls:n,children:r}){let[i,a]=(0,P.useState)(e);return e&&!i&&a(!0),i?(0,Z.jsx)(`div`,{className:`lb-open${e?``:` shut`}${n??``}`,style:t,onAnimationEnd:t=>{t.target===t.currentTarget&&!e&&a(!1)},children:(0,Z.jsx)(`div`,{className:`lb-open-in`,children:r})}):null}function ke(e){let t=e-32-58;return Math.max(1,Math.floor(t/52))}function Ae(e){return e.reduce((e,t)=>e+(t.lanes??1),0)}function je(e,t){if(t===null||e.length===0)return e;let n=ke(t),r=e.slice(0,1);for(let t of e.slice(1)){let e=t.lanes??1;if(e>n)break;n-=e,r.push(t)}return r}function Me(e,t,n){return t===null?e:e.slice(0,ke(t-(n?108:0)))}var Ne=180,Pe=[`1m`,`1h`,`6h`,`1d`],Fe={"1m":`Last hour`,"1h":`Last 2 days`,"6h":`Last 10 days`,"1d":`Last month`};function Ie({book:e}){let t=c(),[n,r]=(0,P.useState)(`live`),[i,a]=(0,P.useState)(`1h`),[o,s]=(0,P.useState)(null),[l,u]=G((0,P.useMemo)(()=>({book:e,mode:n,gran:i,family:o}),[e,n,i,o]),Ne),{book:d,mode:f,gran:m,family:h}=l,_=n!==f,[v,y]=(0,P.useState)(()=>localStorage.getItem(`lb:windUnit`)||`kn`),b=()=>y(e=>{let t=e===`kn`?`bft`:`kn`;return localStorage.setItem(`lb:windUnit`,t),t}),x=(0,P.useRef)([]),S=(0,P.useRef)(0),[C,w]=(0,P.useState)(()=>ce(d)),[T,D]=(0,P.useState)(d);T!==d&&(D(d),w(ce(d)),x.current=[],S.current=0);let[O,F]=(0,P.useState)(!1),I=e=>{w(e),le(d,e),F(!1)},[L,R]=g(),z=(0,P.useRef)(null),B=(0,P.useRef)(null),V=(0,P.useCallback)(e=>{B.current=e,L(e)},[L]),H=(0,P.useRef)(null);(0,P.useLayoutEffect)(()=>{let e=B.current;if(!e)return;let t=()=>e.querySelectorAll(`.lb-ctrl, .lb-pick, .lb-frame`);if(u){let e=new Map;t().forEach(t=>e.set(t,t.getBoundingClientRect().width)),H.current=e;return}let n=H.current;if(H.current=null,!n||window.matchMedia(`(prefers-reduced-motion: reduce)`).matches)return;let r=parseFloat(getComputedStyle(e).getPropertyValue(`--lb-fade-in`))||0;t().forEach(e=>{let t=n.get(e);if(t===void 0||typeof e.animate!=`function`)return;let i=e.getBoundingClientRect().width;Math.abs(i-t)<1||e.animate([{maxWidth:`${t}px`},{maxWidth:`${i}px`}],{duration:r,easing:`cubic-bezier(0.32, 0.72, 0, 1)`})})},[u,l]);let[ee,U]=(0,P.useState)(!1),[re,W]=(0,P.useState)(null),[K,q]=(0,P.useState)(!1),[J,Y]=(0,P.useState)(null),[X,ae]=(0,P.useState)(null),oe={book:d,mode:n,setMode:r,gran:i,setGran:a,shownGran:m,family:o,setFamily:s,shownFamily:h,leaving:u,modeLeaving:_,lanesHold:z,groupsHold:x,chosenHold:S,windUnit:v,toggleWind:b,selection:C,picking:O,setPicking:F,applySelection:I,width:R,exporting:ee,setExporting:U,request:re,onView:e=>{W(e),r(`range`),Y(null)},onSave:async e=>{if(U(!1),Y(null),ae(null),e.format===`pdf`){W(e),r(`range`),q(!0);return}try{let n=p(e.from),r=p(e.to)+864e5-1;if(e.gran===`1m`){let{rows:e,truncated:i}=await ye(e=>t.logbook.minutes(e),n,r),a=ie(ne(te(e,v),d),C);k(j(`logbook-${d}`,n,`csv`,i?`-partial`:``),`text/csv`,A(e,a)),i&&ae(`That window holds more than ${ve} minutes, so the file stops at the most recent ${ve} and its name ends in -partial. A longer interval covers the same days in fewer rows.`);return}let i=N(await t.logbook.rollupHours(n,r),e.gran),a=e.stats.map(e=>({stat:e,cols:ie(ne(te(i.map(t=>E(t,e)),v),d),C)})).filter(e=>e.cols.length>1);k(j(`logbook-${d}`,n,`csv`),`text/csv`,M(i,a,{distance:e.distance,samples:e.samples}))}catch(e){Y(e.message)}},saveErr:J,saveNote:X};return(0,Z.jsxs)(`div`,{className:`lb`,ref:V,children:[(0,Z.jsx)(`style`,{children:`@media print { @page { size: A4 landscape; } }`}),f===`live`?(0,Z.jsx)(it,{...oe}):f===`day`?(0,Z.jsx)(at,{...oe}):(0,Z.jsx)(ft,{...oe,req:re,printing:K,donePrinting:()=>q(!1)})]})}function Le({mode:e,setMode:t}){return(0,Z.jsxs)(`div`,{className:`seg`,children:[(0,Z.jsx)(`button`,{className:e===`live`?`on`:``,onClick:()=>t(`live`),children:`Live`}),(0,Z.jsx)(`button`,{className:e===`day`?`on`:``,onClick:()=>t(`day`),children:`Day`})]})}function Re({leaving:e,children:t}){return(0,Z.jsx)(`div`,{className:`lb-win${e?` leaving`:``}`,children:t})}function ze({groups:e,family:t,setFamily:n}){if(e.length<2)return null;let r=e.find(e=>e.tab===t)??e[0];return(0,Z.jsx)(`div`,{className:`seg`,children:e.map(e=>(0,Z.jsx)(`button`,{className:e.tab===r.tab?`on`:``,onClick:()=>n(e.tab),children:e.head},e.tab))})}function Be(e,t,n){if(e===null||p(t.from)>=e)return null;let r=new Date(e).toLocaleDateString(void 0,{day:`numeric`,month:`short`}),i=n[n.length-1];return`Minutes reach back to ${r}.${i!==void 0&&i.ts<e?` Earlier days in this window read one row per hour.`:``}`}function Ve(e,t,n){let r=O[e].toLowerCase(),i=t.length===1?t[0]:`${t.slice(0,-1).join(`, `)} and ${t[t.length-1]}`,a=n.some(e=>e.lat!==null&&e.lon!==null),o=t.length===1;return`${i} ${o?`has`:`have`} no ${r}, so on each row ${o?`it carries`:`they carry`} the reading that window closed on${a?`, as the position does`:``}.`}function He(e,t){if(t.length===0)return e;let n=new Map(t.map(e=>[e.ts,e]));return e.map(e=>{let t=n.get(e.ts);if(!t)return e;let r={...e};for(let[e,n]of Object.entries(t))e!==`path_values`&&(r[e]===null||r[e]===void 0)&&n!=null&&(r[e]=n);if(t.path_values){let n={...e.path_values??{}};for(let[e,r]of Object.entries(t.path_values))(n[e]===void 0||n[e]===null)&&(n[e]=r);r.path_values=n}return r})}function Ue(e){return p(e.to)+864e5-1>=Date.now()&&e.gran!==`1m`?`No row has closed at this interval yet. A shorter interval shows today's.`:`Nothing was logged between these dates.`}function We(e,t,n){return e.length>1||t===null?$(e):{"--lb-cols":n.current??ke(t)}}function Ge({what:e}){return(0,Z.jsxs)(`div`,{className:`sp-empty`,children:[(0,Z.jsx)(`div`,{className:`em-t`,children:`No snapshots`}),(0,Z.jsx)(`div`,{className:`em-s`,children:e})]})}var Ke=(e,t)=>`u:${e}:${t}`;function qe(e,t,n,r,i,a,o,s,c,l=!0){let u=Ye(e,t,n,r,i,a,o,s,c,l);if(t!==`engine`||u.groups.length<2)return u;let d=u,f=Je(u);for(let o of u.groups){if(o===u.group||o.tab===a)continue;let s=Ye(e,t,n,r,i,o.tab,{current:null},{current:[]},{current:0},l),c=Je(s);c>f&&(d=s,f=c)}return d===u?u:{...u,block:d.block,cls:d.cls}}function Je(e){let t=Math.max(1,Number(e.block[`--lb-cols`]??1));return 58+(e.cls.includes(`u`)?108:0)+t*164}function Ye(e,t,n,r,i,a,o,s,c,l){let u=t===`engine`?re(e):[];u.length>0&&(s.current=u);let d=u.length===0&&e.length===0?s.current:u,f=u.find(e=>e.tab===a)??u[0];if(f&&f.units.length>1){let e=f.metrics.map(e=>({key:Ke(f.tab,e.key),head:e.head})),t=f.metrics.filter(e=>Y({key:Ke(f.tab,e.key)},r)),n=l?Me(t,i,!0):t,a={...f,metrics:n};return o.current=Math.max(1,n.length),t.length>0&&(c.current=t.length),{groups:d,group:a,drawn:[],pick:e,btn:{shown:n.length,chosen:t.length,kept:c.current,offered:e.length},block:{"--lb-cols":Math.max(1,n.length)},cls:` u`}}let p=ne(te(e,n),t),m=f?p.filter(e=>e.key===`ts`||e.tab===f.tab):p,h=ie(m,r),g=l?je(h,i):h;return g.length>1&&(o.current=Ae(g.slice(1))),h.length>1&&(c.current=h.length-1),{groups:d,group:null,drawn:g,pick:m,btn:{shown:g.length-1,chosen:h.length-1,kept:c.current,offered:m.length-1},block:We(g,i,o),cls:``}}function Xe(e){return{"--lb-cols":Math.max(1,e.metrics.length)}}function $(e){return{"--lb-cols":Ae(e.slice(1))}}function Ze(e){return(e.lanes??1)>1?` w${e.lanes}`:``}function Qe(e,t){return t===`last`?pe[e]:`${pe[e]} · ${O[t]}`}function $e(e,t,n,r){return r===0?String(n):e<t?`${e} of ${t}`:String(t)}function et({shown:e,chosen:t,kept:n,offered:r,open:i,onOpen:a}){let o=r===0;return(0,Z.jsxs)(`button`,{className:`lb-colbtn${i?` on`:``}${o?` quiet`:``}`,onClick:a,disabled:o,children:[`Columns · `,(0,Z.jsx)(`b`,{children:$e(e,t,n,r)})]})}function tt({open:e,onOpen:t}){return(0,Z.jsx)(`button`,{className:`lb-colbtn${e?` on`:``}`,onClick:t,children:`View`})}function nt({cols:e,group:t,toggleWind:n}){if(t){let e=t.units.length>1;return(0,Z.jsxs)(`div`,{className:`lb-cols${e?` u`:``}`,style:Xe(t),children:[(0,Z.jsx)(`span`,{children:`UTC`}),e&&(0,Z.jsx)(`span`,{className:`un`,children:`Unit`}),t.metrics.map(e=>(0,Z.jsxs)(`span`,{children:[e.head,(0,Z.jsx)(rt,{of:e.unit})]},e.key))]})}return(0,Z.jsx)(`div`,{className:`lb-cols`,style:$(e),children:e.map(e=>e.tappable?(0,Z.jsxs)(`span`,{className:`tap${Ze(e)}`,onClick:n,title:`Tap: knots ⇄ Beaufort`,children:[e.head,(0,Z.jsx)(rt,{of:e.unit})]},e.key):(0,Z.jsxs)(`span`,{className:Ze(e).trim(),children:[e.head,(0,Z.jsx)(rt,{of:e.unit})]},e.key))})}function rt({of:e}){return(0,Z.jsx)(`b`,{className:`lb-u`,children:e===void 0||e===``?`\xA0`:e})}function it({book:e,mode:t,setMode:n,gran:r,setGran:i,shownGran:a,family:o,setFamily:s,shownFamily:c,leaving:l,modeLeaving:u,lanesHold:d,groupsHold:f,chosenHold:p,windUnit:m,toggleWind:h,selection:g,picking:_,setPicking:v,applySelection:y,width:b,exporting:x,setExporting:S,request:C,onView:w,onSave:T,saveErr:E,saveNote:D}){let{snaps:O,err:k,busy:A,hasMore:j,loadMore:M}=ge(a),{groups:N,group:P,drawn:F,pick:I,btn:L,block:R,cls:z}=qe(O,e,m,g,b,c,d,f,p);return(0,Z.jsxs)(Z.Fragment,{children:[(0,Z.jsxs)(`div`,{className:`lb-ctrl${z}`,style:R,children:[(0,Z.jsx)(Le,{mode:t,setMode:n}),(0,Z.jsx)(Re,{leaving:u,children:(0,Z.jsx)(`div`,{className:`seg`,children:Pe.map(e=>(0,Z.jsx)(`button`,{className:r===e?`on`:``,onClick:()=>i(e),children:e},e))})}),(0,Z.jsx)(ze,{groups:N,family:o,setFamily:s}),(0,Z.jsxs)(`div`,{className:`lb-acts`,children:[(0,Z.jsx)(et,{shown:L.shown,chosen:L.chosen,kept:L.kept,offered:L.offered,open:_,onOpen:()=>v(!_)}),(0,Z.jsx)(tt,{open:x,onOpen:()=>S(!x)})]})]}),(0,Z.jsx)(Q,{open:_,style:R,cls:z,children:(0,Z.jsx)(de,{cols:I,applied:g,onApply:y,onCancel:()=>v(!1)})}),(0,Z.jsx)(Q,{open:x,style:R,cls:z,children:(0,Z.jsx)(Oe,{initial:C??void 0,onView:w,onSave:T})}),E&&(0,Z.jsx)(`div`,{className:`lb-err`,children:E}),D&&(0,Z.jsx)(`div`,{className:`lb-note`,children:D}),(0,Z.jsxs)(`div`,{className:`lb-frame${z}${l?` leaving`:``}`,style:R,children:[(0,Z.jsx)(ct,{book:e,window:Fe[a],interval:pe[a]}),(0,Z.jsxs)(`div`,{className:`lb-day`,style:$(F),children:[(0,Z.jsx)(`span`,{children:Fe[a]}),(0,Z.jsx)(`b`,{children:O.length})]}),(0,Z.jsx)(nt,{cols:F,group:P,toggleWind:h}),k&&(0,Z.jsx)(`div`,{className:`lb-err`,children:k}),!A&&!k&&O.length===0?(0,Z.jsx)(Ge,{what:`Nothing was logged in this window (${Fe[a].toLowerCase()}).`}):(0,Z.jsx)(pt,{snaps:O,cols:F,group:P,footer:j?(0,Z.jsx)(`button`,{className:`lb-more`,onClick:M,disabled:A,children:A?`Loading…`:`Load ${me[a]} more`}):null}),(0,Z.jsx)(lt,{})]})]})}function at({book:e,mode:t,setMode:n,family:r,setFamily:i,shownFamily:a,leaving:o,modeLeaving:s,lanesHold:c,groupsHold:l,chosenHold:u,windUnit:d,toggleWind:f,selection:p,picking:m,setPicking:h,applySelection:g,width:_,exporting:v,setExporting:y,request:b,onView:x,onSave:S,saveErr:w,saveNote:T}){let{dateStr:E,setDateStr:D,isToday:O,snaps:k,err:A,busy:j,prevDay:M,nextDay:N,goToday:P}=_e(),{groups:F,group:I,drawn:L,pick:R,btn:z,block:B,cls:V}=qe(k,e,d,p,_,a,c,l,u),H=O?`Today · ${new Date(E).toLocaleDateString(`en-GB`,{day:`2-digit`,month:`short`,timeZone:`UTC`})}`:new Date(E).toLocaleDateString(`en-GB`,{weekday:`short`,day:`2-digit`,month:`short`,timeZone:`UTC`}).replace(/,/g,``);return(0,Z.jsxs)(Z.Fragment,{children:[(0,Z.jsxs)(`div`,{className:`lb-ctrl${V}`,style:B,children:[(0,Z.jsx)(Le,{mode:t,setMode:n}),(0,Z.jsx)(Re,{leaving:s,children:(0,Z.jsxs)(`div`,{className:`lb-date`,children:[(0,Z.jsx)(`button`,{className:`lb-step`,onClick:M,"aria-label":`Previous day`,children:`‹`}),(0,Z.jsx)(`input`,{type:`date`,className:`dt`,value:E,max:C(),onChange:e=>D(e.target.value)}),(0,Z.jsx)(`button`,{className:`lb-step`,onClick:N,disabled:O,"aria-label":`Next day`,children:`›`}),(0,Z.jsx)(`button`,{onClick:P,disabled:O,children:`Now`})]})}),(0,Z.jsx)(ze,{groups:F,family:r,setFamily:i}),(0,Z.jsxs)(`div`,{className:`lb-acts`,children:[(0,Z.jsx)(et,{shown:z.shown,chosen:z.chosen,kept:z.kept,offered:z.offered,open:m,onOpen:()=>h(!m)}),(0,Z.jsx)(tt,{open:v,onOpen:()=>y(!v)})]})]}),(0,Z.jsx)(Q,{open:m,style:B,cls:V,children:(0,Z.jsx)(de,{cols:R,applied:p,onApply:g,onCancel:()=>h(!1)})}),(0,Z.jsx)(Q,{open:v,style:B,cls:V,children:(0,Z.jsx)(Oe,{initial:b??void 0,onView:x,onSave:S})}),w&&(0,Z.jsx)(`div`,{className:`lb-err`,children:w}),T&&(0,Z.jsx)(`div`,{className:`lb-note`,children:T}),(0,Z.jsxs)(`div`,{className:`lb-frame${V}${o?` leaving`:``}`,style:B,children:[(0,Z.jsx)(ct,{book:e,window:H,interval:pe[`1h`]}),(0,Z.jsxs)(`div`,{className:`lb-day`,style:$(L),children:[(0,Z.jsx)(`span`,{children:H}),(0,Z.jsx)(`b`,{children:k.length})]}),(0,Z.jsx)(nt,{cols:L,group:I,toggleWind:f}),A&&(0,Z.jsx)(`div`,{className:`lb-err`,children:A}),!j&&k.length===0?(0,Z.jsx)(Ge,{what:`No telemetry was logged for this day.`}):(0,Z.jsx)(pt,{snaps:k,cols:L,group:I,footer:null}),(0,Z.jsx)(lt,{})]})]})}function ot(){let e=new Date,t=e.toLocaleDateString(`en-GB`,{day:`2-digit`,month:`short`,year:`numeric`,timeZone:`UTC`}),n=e=>String(e).padStart(2,`0`);return`${t.toUpperCase()} · ${n(e.getUTCHours())}:${n(e.getUTCMinutes())} UTC`}var st={bridge:`CHIEF OFFICER`,engine:`CHIEF ENGINEER`};function ct({book:e,window:t,interval:n}){return(0,Z.jsxs)(`div`,{className:`lb-print-hd`,children:[(0,Z.jsxs)(`div`,{className:`ph-id`,children:[(0,Z.jsxs)(`span`,{className:`sp-lockup`,children:[(0,Z.jsx)(S,{className:`sp-glyph`}),(0,Z.jsx)(`span`,{className:`ph-wm`,children:`Siparu`})]}),(0,Z.jsxs)(`span`,{className:`ph-book`,children:[`LOGBOOK · `,(0,Z.jsx)(`b`,{children:e.toUpperCase()}),` · `,st[e]]})]}),(0,Z.jsxs)(`div`,{className:`ph-meta`,children:[(0,Z.jsxs)(`div`,{children:[(0,Z.jsx)(`span`,{className:`l`,children:`GENERATED`}),(0,Z.jsx)(`b`,{children:ot()})]}),(0,Z.jsxs)(`div`,{children:[(0,Z.jsx)(`span`,{className:`l`,children:`WINDOW`}),(0,Z.jsx)(`b`,{children:`${t} · ${n} · UTC`.toUpperCase()})]})]})]})}function lt(){return(0,Z.jsx)(`div`,{className:`lb-print-ft`,children:(0,Z.jsx)(`span`,{children:`siparu.app`})})}function ut(e){return new Date(e).toLocaleDateString(`en-GB`,{weekday:`short`,day:`2-digit`,month:`short`,year:`numeric`,timeZone:`UTC`}).replace(/,\s*/,` · `).toUpperCase()}function dt(e,t){let n=(e,t)=>new Date(e).toLocaleDateString(`en-GB`,{day:`2-digit`,month:`short`,...t?{year:`numeric`}:{},timeZone:`UTC`});return e===t?n(t,!0):`${n(e,!1)} - ${n(t,!0)}`}function ft({book:e,req:t,setMode:n,family:r,setFamily:i,shownFamily:a,leaving:o,modeLeaving:s,lanesHold:c,groupsHold:l,chosenHold:u,windUnit:d,toggleWind:f,selection:p,picking:m,setPicking:h,applySelection:g,width:_,exporting:v,setExporting:y,request:b,onView:x,onSave:S,saveErr:w,saveNote:E,printing:O,donePrinting:k}){let A={from:C(),to:C(),gran:`1h`,format:`csv`,stats:[`last`],distance:!1,samples:!1,style:`screen`},j=t??A,M=j.stats[0]??`last`,{snaps:N,err:F,busy:I,truncated:L,loaded:R,minutesFrom:z,plain:B}=be(j.from,j.to,j.gran,M),V=(0,P.useMemo)(()=>He([...N].sort((e,t)=>e.ts-t.ts),B),[N,B]),{groups:H,group:ee,drawn:U,pick:re,btn:W,block:G,cls:K}=qe(V,e,d,p,_,a,c,l,u,!1),q=(0,P.useCallback)(()=>k(),[k]);(0,P.useEffect)(()=>{!O||!R||I||(q(),D(T(`Siparu-Logbook`,Date.now()),j.style))},[O,R,I,q,j.style]);let J=dt(j.from,j.to),Y=Qe(j.gran,M),X=(0,P.useMemo)(()=>{if(B.length===0)return[];let t=t=>ie(ne(te(t,d),e),p).map(e=>e.head),n=new Set(t(N));return t(B).filter(e=>!n.has(e))},[B,N,d,e,p]);return(0,Z.jsxs)(Z.Fragment,{children:[(0,Z.jsxs)(`div`,{className:`lb-ctrl${K}`,style:G,children:[(0,Z.jsx)(Le,{mode:`range`,setMode:n}),(0,Z.jsx)(Re,{leaving:s,children:(0,Z.jsxs)(`button`,{className:`lb-colbtn on`,onClick:()=>y(!v),children:[J,` · `,(0,Z.jsx)(`b`,{children:Y})]})}),(0,Z.jsx)(ze,{groups:H,family:r,setFamily:i}),(0,Z.jsx)(`div`,{className:`lb-acts`,children:(0,Z.jsx)(et,{shown:W.shown,chosen:W.chosen,kept:W.kept,offered:W.offered,open:m,onOpen:()=>h(!m)})})]}),(0,Z.jsx)(Q,{open:m,style:G,cls:K,children:(0,Z.jsx)(de,{cols:re,applied:p,onApply:g,onCancel:()=>h(!1)})}),(0,Z.jsx)(Q,{open:v,style:G,cls:K,children:(0,Z.jsx)(Oe,{initial:b??void 0,onView:x,onSave:S})}),w&&(0,Z.jsx)(`div`,{className:`lb-err`,children:w}),E&&(0,Z.jsx)(`div`,{className:`lb-note`,children:E}),(0,Z.jsxs)(`div`,{className:`lb-frame dated${K}${o?` leaving`:``}`,style:G,children:[(0,Z.jsx)(ct,{book:e,window:J,interval:Y}),(0,Z.jsxs)(`div`,{className:`lb-day`,style:$(U),children:[(0,Z.jsxs)(`span`,{children:[J,` · `,Y]}),(0,Z.jsx)(`b`,{children:L?`${N.length} of more`:N.length})]}),(0,Z.jsx)(nt,{cols:U,group:ee,toggleWind:f}),F&&(0,Z.jsx)(`div`,{className:`lb-err`,children:F}),X.length>0&&(0,Z.jsx)(`div`,{className:`lb-note`,children:Ve(M,X,N)}),L&&(0,Z.jsxs)(`div`,{className:`lb-note`,children:[`This window holds more than `,5e3,` rows. The most recent `,5e3,` are here; a longer interval covers the same days in fewer.`]}),Be(z,j,N)&&(0,Z.jsx)(`div`,{className:`lb-note`,children:Be(z,j,N)}),!I&&V.length===0?(0,Z.jsx)(Ge,{what:Ue(j)}):(0,Z.jsx)(pt,{snaps:V,cols:U,group:ee,footer:null,dated:!0}),(0,Z.jsx)(lt,{})]})]})}function pt({snaps:e,cols:t,group:n,footer:r,dated:i=!1}){let a=[],o=null;for(let r of e){if(i){let e=ut(r.ts);e!==o&&(a.push((0,Z.jsx)(mt,{day:e,cols:t,group:n},`sep-${r.ts}`)),o=e)}a.push(n?(0,Z.jsx)(ht,{s:r,group:n},r.ts):(0,Z.jsx)(gt,{s:r,cols:t},r.ts))}return(0,Z.jsxs)(`div`,{className:`lb-rows`,style:n?Xe(n):$(t),children:[a,r]})}function mt({day:e,cols:t,group:n}){let r=n!==null&&n.units.length>1,i=n?n.metrics.map(e=>({key:e.key,head:e.head,unit:e.unit,cls:``})):t.slice(1).map(e=>({key:e.key,head:e.head,unit:e.unit,cls:Ze(e).trim()}));return(0,Z.jsxs)(`div`,{className:`lb-sep${r?` u`:``}`,children:[(0,Z.jsx)(`span`,{className:`sd`,children:e}),r&&(0,Z.jsx)(`span`,{className:`sh`}),i.map(e=>(0,Z.jsxs)(`span`,{className:e.cls?`sh ${e.cls}`:`sh`,children:[e.head,(0,Z.jsx)(rt,{of:e.unit})]},e.key))]})}function ht({s:e,group:t}){let n=t.units.length>1;return(0,Z.jsx)(Z.Fragment,{children:t.units.map((r,i)=>(0,Z.jsxs)(`div`,{className:`lb-row${n?` u`:``}${i>0?` cont`:``}`,children:[(0,Z.jsx)(`span`,{className:`tm`,children:F(e.ts)}),n&&(0,Z.jsx)(`span`,{className:`un`,children:r.head}),t.metrics.map(t=>(0,Z.jsx)(`span`,{className:`v`,children:W(e,r,t)},t.key))]},r.key))})}function gt({s:e,cols:t}){return(0,Z.jsx)(`div`,{className:`lb-row`,children:t.map((t,n)=>(0,Z.jsx)(`span`,{className:n===0?`tm`:`${t.dim?`v dim`:`v`}${Ze(t)}`,children:t.cell(e)},t.key))})}var _t=[{to:`/logbook/bridge`,name:`Bridge`,keeper:`Chief officer`,holds:`Position, course and speed, the weather she ran in and the water under her.`,Icon:h},{to:`/logbook/engine`,name:`Engine`,keeper:`Chief engineer`,holds:`Engines, generators and tanks, as she reported them.`,Icon:y}];function vt(){let e=_();return(0,Z.jsx)(`div`,{className:`lb-door`,children:(0,Z.jsx)(`div`,{className:`lbd-cards`,children:_t.map(({to:t,name:n,keeper:r,holds:i,Icon:a})=>(0,Z.jsxs)(u,{to:e(t),replace:!0,className:`lbd-card`,children:[(0,Z.jsx)(a,{size:42}),(0,Z.jsx)(`span`,{className:`lbd-n`,children:n}),(0,Z.jsx)(`span`,{className:`lbd-k`,children:r}),(0,Z.jsx)(`span`,{className:`lbd-h`,children:i}),(0,Z.jsxs)(`span`,{className:`lbd-go`,children:[`Open `,(0,Z.jsx)(b,{size:15})]})]},t))})})}function yt({book:e}){return e?(0,Z.jsx)(Ie,{book:e}):(0,Z.jsx)(vt,{})}export{yt as default};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{a as e}from"./rolldown-runtime-CNC7AqOf.js";import{n as t,t as n}from"./jsx-runtime-CaR_m4Xc.js";import{t as r}from"./visibleInterval-CFxPzgzx.js";import{D as i,E as a,N as o,b as s,r as c,x as l,y as u}from"./index-
|
|
1
|
+
import{a as e}from"./rolldown-runtime-CNC7AqOf.js";import{n as t,t as n}from"./jsx-runtime-CaR_m4Xc.js";import{t as r}from"./visibleInterval-CFxPzgzx.js";import{D as i,E as a,N as o,b as s,r as c,x as l,y as u}from"./index-jUObqoMX.js";import{a as d,i as f,n as p,o as m,r as h,s as g,t as _}from"./style-D1uNa2dX.js";var v=e(t(),1),y={fill:`#ffb938`,stroke:`#b38326`,glow:`rgba(255,185,56,0.65)`,dot:`#050706`};function b(e,t=y){let n=t.fill,r=t.stroke;return e===null||Number.isNaN(e)?`
|
|
2
2
|
<div style="
|
|
3
3
|
width: 14px; height: 14px;
|
|
4
4
|
background: ${n};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{a as e}from"./rolldown-runtime-CNC7AqOf.js";import{n as t,t as n}from"./jsx-runtime-CaR_m4Xc.js";import{M as r,N as i,i as a,m as o,n as s,r as c,t as l,u}from"./index-
|
|
1
|
+
import{a as e}from"./rolldown-runtime-CNC7AqOf.js";import{n as t,t as n}from"./jsx-runtime-CaR_m4Xc.js";import{M as r,N as i,i as a,m as o,n as s,r as c,t as l,u}from"./index-jUObqoMX.js";var d=e(t(),1),f=n(),p=`siparu.app/app`;function m(e){return Math.max(0,Math.round((new Date(e).getTime()-Date.now())/6e4))}function h(e){let{value:t,unit:n}=c((Date.now()-e)/1e3);return`${t}${n===`s`?`s`:` ${n}`} ago`}function g(e){return e?e.unentitled?e.lastError??`Remote watching is not active on this account.`:e.rejected||e.failures>0?e.lastError??`Not reaching Siparu.`:e.lastSentTs?`Sending · last frame ${h(e.lastSentTs)}`:`Waiting to send the first frame.`:`Checking the link…`}function _({sealing:e}){let t=i().pair??r.pair,[n,c]=(0,d.useState)(!1),[u,h]=(0,d.useState)(!1),[_,v]=(0,d.useState)(!1),{data:y,refresh:b}=o(()=>t.status(),n?5e3:3e4,[]);(0,d.useEffect)(()=>{let e=y?.state;c(e===`showing_code`||e===`awaiting_approval`)},[y?.state]);async function x(e){h(!0);try{await e()}catch{}finally{h(!1),v(!1),b()}}let S=s(e),C=S?(0,f.jsx)(`div`,{className:`pair warn`,children:(0,f.jsxs)(`div`,{className:`pl`,children:[(0,f.jsx)(`div`,{className:`t`,children:S.title}),(0,f.jsx)(`div`,{className:`s`,children:S.detail})]})}):null,w=e?.screens??[],T=w.length>0?(0,f.jsx)(`div`,{className:`pair`,children:(0,f.jsxs)(`div`,{className:`pl`,children:[(0,f.jsx)(`div`,{className:`fps`,children:w.map((e,t)=>(0,f.jsx)(`span`,{className:`fp`,children:e},`${e}-${t}`))}),(0,f.jsx)(`div`,{className:`s`,children:`Every screen that can open her reports has a line here. If the line on your phone is missing, she is not sealing to it; if a line here is not one of yours, it can read her.`})]})}):null,E=l(e),D=E?(0,f.jsxs)(f.Fragment,{children:[E.unapproved.length>0?(0,f.jsx)(`div`,{className:`pair warn`,children:(0,f.jsxs)(`div`,{className:`pl`,children:[(0,f.jsx)(`div`,{className:`t`,children:`She will not seal to these screens`}),(0,f.jsx)(`div`,{className:`s`,children:`Nothing she trusts has vouched for them, so they receive nothing at all. A line here you did not add is somebody else's key on your list: remove it from the boat's page ashore, and if you cannot account for it, pair her again from this screen.`}),(0,f.jsx)(`div`,{className:`fps`,children:E.unapproved.map(e=>(0,f.jsxs)(`span`,{className:`fp`,children:[e.kid,` · `,e.reason]},e.kid))})]})}):null,E.unwrapped.length>0?(0,f.jsx)(`div`,{className:`pair warn`,children:(0,f.jsxs)(`div`,{className:`pl`,children:[(0,f.jsx)(`div`,{className:`t`,children:`Authorised, and still receiving nothing`}),(0,f.jsx)(`div`,{className:`s`,children:`These screens are on her list and she could not wrap her last report to them. From ashore that looks exactly like a boat gone quiet, which is why it is named here.`}),(0,f.jsx)(`div`,{className:`fps`,children:E.unwrapped.map(e=>(0,f.jsxs)(`span`,{className:`fp`,children:[e.kid,` · `,e.reason]},e.kid))})]})}):null,E.unpinned?(0,f.jsx)(`div`,{className:`pair`,children:(0,f.jsxs)(`div`,{className:`pl`,children:[(0,f.jsx)(`div`,{className:`t`,children:`Her screens are not pinned`}),(0,f.jsx)(`div`,{className:`s`,children:`She was paired before screens could vouch for one another, so she checks that a key is well formed and nothing about who put it on her list. She still seals every report. Pair her again from this screen to pin the first one, and every later screen will have to chain to it.`})]})}):null]}):null,O=(E?.unapproved.length??0)+(E?.unwrapped.length??0),k=T||D?(0,f.jsxs)(`section`,{className:`sp-sec`,children:[(0,f.jsxs)(`h2`,{className:`sp-sec-h`,children:[(0,f.jsx)(`span`,{className:`sp-sec-n`,children:`Screens`}),(0,f.jsx)(`span`,{className:`sp-sec-badge${O>0?` quiet`:``}`,children:O>0?`${O} refused`:`${w.length} sealed`})]}),(0,f.jsxs)(`div`,{className:`sp-glass`,children:[T,D]})]}):null,A=(e,t)=>(0,f.jsxs)(`section`,{className:`sp-sec`,children:[(0,f.jsxs)(`h2`,{className:`sp-sec-h`,children:[(0,f.jsx)(`span`,{className:`sp-sec-n`,children:`Link ashore`}),e?(0,f.jsxs)(`span`,{className:`sp-sec-badge${e.quiet?` quiet`:``}`,children:[e.live?(0,f.jsx)(`span`,{className:`rm-dot`,"aria-hidden":`true`}):null,e.text]}):null]}),(0,f.jsx)(`div`,{className:`sp-glass`,children:t})]});if(!y)return(0,f.jsxs)(f.Fragment,{children:[A(null,C??(0,f.jsx)(`div`,{className:`pair`,children:(0,f.jsx)(`div`,{className:`pl`,children:(0,f.jsx)(`div`,{className:`s`,children:`Checking the link…`})})})),k]});let j=(e,t,n)=>(0,f.jsx)(`button`,{className:`pbtn${n?` ${n}`:``}`,disabled:u,onClick:t,children:e}),M=y.pairing_locked===!0,N=(0,f.jsx)(a,{on:y.security_off,locked:M}),P=y.revoke_pending?(0,f.jsx)(`div`,{className:`pair warn`,children:(0,f.jsxs)(`div`,{className:`pl`,children:[(0,f.jsx)(`div`,{className:`t`,children:`Still revoking the old key`}),(0,f.jsx)(`div`,{className:`s`,children:`Remote watching is off on this boat, but Siparu could not be reached to revoke its copy of the key. It will keep trying whenever the boat is online.`})]})}):null,F=(()=>{switch(y.state){case`idle`:case`expired`:return(0,f.jsxs)(`div`,{className:`pair`,children:[(0,f.jsxs)(`div`,{className:`pl`,children:[(0,f.jsx)(`div`,{className:`t`,children:`Remote watching`}),(0,f.jsx)(`div`,{className:`s`,children:y.state===`expired`?`The code expired. Nothing was linked.`:`Off - this boat is not linked to an account.`})]}),!M&&j(y.state===`expired`?`New code`:`Turn on`,()=>x(t.start))]});case`showing_code`:return(0,f.jsxs)(`div`,{className:`pair`,children:[(0,f.jsxs)(`div`,{className:`pl`,children:[(0,f.jsx)(`div`,{className:`t`,children:`Remote watching`}),(0,f.jsx)(`div`,{className:`code`,children:y.userCode}),(0,f.jsxs)(`div`,{className:`s`,children:[`Enter this at `,(0,f.jsx)(`b`,{children:p}),` · `,m(y.expiresAt),` min left`]})]}),j(`Cancel`,()=>x(t.deny),`ghost`)]});case`awaiting_approval`:return(0,f.jsxs)(`div`,{className:`pair asking`,children:[(0,f.jsxs)(`div`,{className:`pl`,children:[(0,f.jsx)(`div`,{className:`t`,children:`Someone wants to pair`}),(0,f.jsx)(`div`,{className:`who`,children:y.email??`an account we cannot name`}),y.device?(0,f.jsx)(`div`,{className:`fps`,children:(0,f.jsx)(`span`,{className:`fp`,children:y.device.fingerprint})}):null,(0,f.jsx)(`div`,{className:`s`,children:y.device?`Approve only if this is you, and only if that line matches the one on your phone. They will see where this boat is.`:`Approve only if this is you. They will see where this boat is.`})]}),(0,f.jsxs)(`div`,{className:`acts`,children:[j(`Deny`,()=>x(t.deny),`ghost`),!M&&j(`Approve`,()=>x(t.approve),`accent`)]})]});case`paired`:return(0,f.jsxs)(`div`,{className:`pair${y.uplink?.rejected?` err`:``}`,children:[(0,f.jsxs)(`div`,{className:`pl`,children:[(0,f.jsx)(`div`,{className:`t`,children:`Remote watching`}),(0,f.jsx)(`div`,{className:`who`,children:y.email??`linked account`}),!_&&!S&&(0,f.jsx)(`div`,{className:`s`,children:g(y.uplink)})]}),M?null:_?(0,f.jsxs)(`div`,{className:`acts`,children:[j(`Keep`,()=>v(!1),`ghost`),j(`Unlink`,()=>x(t.reset),`accent`)]}):(0,f.jsxs)(`div`,{className:`acts`,children:[j(`Pair again`,()=>x(t.start),`ghost`),j(`Turn off`,()=>v(!0),`ghost`)]})]});case`error`:return(0,f.jsxs)(`div`,{className:`pair err`,children:[(0,f.jsxs)(`div`,{className:`pl`,children:[(0,f.jsx)(`div`,{className:`t`,children:`Remote watching`}),(0,f.jsx)(`div`,{className:`s`,children:y.message})]}),!M&&(0,f.jsxs)(`div`,{className:`acts`,children:[j(`Dismiss`,()=>x(t.reset),`ghost`),j(`Retry`,()=>x(t.start))]})]})}})();return(0,f.jsxs)(f.Fragment,{children:[A((()=>{switch(y.state){case`paired`:{let e=y.uplink;return e?.unentitled?{text:`inactive`}:e?.rejected||S?{text:`not sending`,quiet:!0}:!e||e.failures>0?{text:`not reaching`,quiet:!0}:e.lastSentTs?{text:`on`,live:!0}:{text:`on`}}case`showing_code`:return{text:`waiting`};case`awaiting_approval`:return{text:`asking`,quiet:!0};case`error`:return{text:`error`,quiet:!0};default:return{text:`off`}}})(),(0,f.jsxs)(f.Fragment,{children:[C,N,P,F]})),k]})}var v=3e4;function y(){let{data:e}=o(u.health,v,[]);return(0,f.jsx)(`div`,{className:`sp-remote`,children:(0,f.jsx)(_,{sealing:e?.sealing??null})})}export{y as default};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{t as e}from"./jsx-runtime-CaR_m4Xc.js";import{P as t,g as n,o as r}from"./index-
|
|
1
|
+
import{t as e}from"./jsx-runtime-CaR_m4Xc.js";import{P as t,g as n,o as r}from"./index-jUObqoMX.js";var i=e(),a=`/admin/#/security/users`;function o({admin:e}){let a=n();return(0,i.jsx)(`div`,{className:`doc`,children:(0,i.jsxs)(`div`,{className:`doc-in`,children:[(0,i.jsx)(`div`,{className:`doc-eyebrow`,children:`Signal K`}),(0,i.jsx)(`h1`,{className:`doc-h`,children:`Turning on security`}),(0,i.jsx)(`p`,{className:`doc-lead`,children:`Signal K is running without an account on it. Anyone who can reach this network can read the boat, and can use this plugin's own controls to link her to another account or cut her loose. Until there is an account, Siparu refuses those writes: pairing, unpairing and log edits stay locked.`}),(0,i.jsx)(`h2`,{className:`doc-h2`,children:`What to do`}),(0,i.jsxs)(`ol`,{className:`doc-steps`,children:[(0,i.jsxs)(`li`,{children:[(0,i.jsx)(`b`,{children:`Open Signal K's admin pages.`}),` `,e?(0,i.jsxs)(i.Fragment,{children:[`They are on this same server, at`,` `,(0,i.jsx)(`a`,{href:e,target:`_blank`,rel:`noreferrer`,children:`/admin`}),`.`]}):(0,i.jsxs)(i.Fragment,{children:[`They are on the boat's own server, under `,(0,i.jsx)(`b`,{children:`/admin`}),`: open them from a device on her network, the way you open her dashboard aboard.`]}),` `,`Nothing here is a Siparu account: this is the boat's own server, and the account you are about to make lives on board.`]}),(0,i.jsxs)(`li`,{children:[(0,i.jsx)(`b`,{children:`Go to Security, then Users.`}),` With no account yet, that page offers to make the first one.`]}),(0,i.jsxs)(`li`,{children:[(0,i.jsx)(`b`,{children:`Pick a username and a password.`}),` Write them down somewhere that is not the boat. There is no reset from ashore.`]}),(0,i.jsxs)(`li`,{children:[(0,i.jsx)(`b`,{children:`Press Enable, then Restart.`}),` Signal K writes the account down but keeps running open until it is restarted; it says so in red under the form. The Restart button is at the top of its admin pages. Log in when it comes back.`]}),(0,i.jsxs)(`li`,{children:[(0,i.jsx)(`b`,{children:`Decide about "Allow Readonly Access".`}),` It is under Security, then Settings, and newer servers also ask on the form you just filled in. Ticked, anyone on the network can still read the boat and use this app without logging in, and only the controls that change something ask for the password. Unticked, everything asks. Signal K's own note is that readonly access exposes your data on the local network and potentially the public internet, so leave it off on a shared crew or marina network and on only where you know who is on the wire.`]})]}),(0,i.jsx)(`h2`,{className:`doc-h2`,children:`After that`}),(0,i.jsx)(`p`,{className:`doc-p`,children:`This notice stops once the server is back up and asking for an account. It follows whether the server asks at all, not who is allowed in, so it clears whichever way you answer the readonly question. Siparu's locked writes unlock for anyone logged in with admin rights.`}),(0,i.jsx)(`h2`,{className:`doc-h2`,children:`If the network really is yours`}),(0,i.jsx)(`p`,{className:`doc-p`,children:`On a boat with nothing else on her wire, you can leave the server open and tell the plugin you have decided so: the setting is in Signal K under Apps & Plugins, in Siparu's own configuration. The writes unlock. The notice stays, because it reports the door and not the decision: the server is still open. It is the weaker answer of the two, and it is not the one to pick because the password is a nuisance. An open server also lets anything on that network install code, which no setting of ours can hold shut.`}),(0,i.jsx)(`div`,{className:`doc-back`,children:(0,i.jsxs)(t,{to:a(`/`),className:`doc-go`,children:[`Back to the boat `,(0,i.jsx)(r,{size:15})]})})]})})}function s(){return(0,i.jsx)(o,{admin:a})}export{s as default};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{a as e}from"./rolldown-runtime-CNC7AqOf.js";import{n as t,t as n}from"./jsx-runtime-CaR_m4Xc.js";import{F as r,N as i,a,b as o,f as s,h as c,j as l,m as u,r as d,y as f}from"./index-jUObqoMX.js";import{a as p,c as m,i as h,n as g,r as _,s as v}from"./export-CQSqkXJD.js";import{a as y,i as b,n as x,o as S,s as C,t as w}from"./style-D1uNa2dX.js";var T=e(t(),1),E=[{mode:`total_l`,label:`Litres`},{mode:`total_usgal`,label:`US gallons`},{mode:`total_impgal`,label:`Imp gallons`},{mode:`per_nm`,label:`Litres / nm`},{mode:`nm_per_l`,label:`nm / L`},{mode:`per_hour`,label:`Litres / hour`}],D=.26417205,O=.21996923,k=1e-6;function A(e,t,n){return`${e.toFixed(n)} ${t}`}function j(e,t,n,r){if(e===null||!Number.isFinite(e))return null;switch(r){case`total_l`:return A(e,`L`,+(e<100));case`total_usgal`:return A(e*D,`US gal`,1);case`total_impgal`:return A(e*O,`Imp gal`,1);case`per_nm`:return t>k?A(e/t,`L/nm`,2):null;case`nm_per_l`:return e>k?A(t/e,`nm/L`,2):null;case`per_hour`:return n>k?A(e/n,`L/h`,1):null}}var M=`Could not load voyage data`;function N(e){return e instanceof l?e.detail||M:e instanceof DOMException&&e.name===`TimeoutError`?`She did not answer in time`:M}function ee(e=0){let t=i(),{data:n,error:r}=u(t.voyage.current,6e4,[e],`voyage:current`),[a,o]=(0,T.useState)(null),[s,l]=(0,T.useState)([]),[d,f]=(0,T.useState)(!0),[p,m]=(0,T.useState)(null);return(0,T.useEffect)(()=>{let e=!1;return(async()=>{try{let[n,r]=await Promise.all([t.voyage.stats(),t.voyage.list(50)]);e||(o(n),l(r),m(null))}catch(t){e||m(N(t))}finally{e||f(!1)}})(),()=>{e=!0}},[e]),{current:n??null,currentStale:r!==null,currentSeenTs:c(`voyage:current`),stats:a,list:s,loading:d,err:p}}var P=e(C(),1),F=new Set;function I(e){return F.add(e),()=>{F.delete(e)}}async function te(){await Promise.all([...F].map(e=>e().catch(()=>void 0)))}var L=n();function R(e,t){let n=(t.querySelector(`.maplibregl-ctrl-attrib-inner`)?.textContent??``).replace(w,``).replace(/^\s*\|\s*/,``).trim();return(e.loaded()?Promise.resolve():new Promise(t=>{let n=setTimeout(t,1500);e.once(`idle`,()=>{clearTimeout(n),t()})})).then(()=>new Promise((t,r)=>{let i=setTimeout(()=>r(Error(`the map did not draw`)),2e3);e.once(`render`,()=>{clearTimeout(i);try{t({url:e.getCanvas().toDataURL(`image/png`),credit:n})}catch(e){r(e)}}),e.triggerRepaint()}))}function z(){return document.documentElement.dataset.theme===`day`?`day`:`night`}function B(e,t){let n=document.createElement(`div`);return n.style.cssText=`width:10px;height:10px;border-radius:50%;border:2px solid ${e};background:${t?e:`transparent`};`,n}function V({track:e}){let t=i(),n=(0,T.useRef)(null),r=(0,T.useRef)(null),[a,o]=(0,T.useState)(null);return(0,T.useEffect)(()=>{if(!n.current||r.current)return;let i=n.current,a=document.documentElement,s=getComputedStyle(a).getPropertyValue(`--accent`).trim()||`#e5484d`,c=!1,l=null,u=null,d=null;return(async()=>{y();let n=await S(t);if(c)return;let f=()=>b(z(),n,{track:{color:s,width:3}}),p=e.filter(e=>e.lat!==null&&e.lon!==null).map(e=>[e.lon,e.lat]);l=new P.default.Map({container:i,style:f(),center:[7.42,43.7],zoom:9,attributionControl:{compact:!1,customAttribution:w},dragRotate:!1,pitchWithRotate:!1}),l.touchZoomRotate.disableRotation(),r.current=l;let m=l;d=I(async()=>{let e=await R(m,i);c||o(e)});let h=()=>{if(!l)return;let e=l.getSource(x);e&&p.length>=2&&e.setData({type:`FeatureCollection`,features:[{type:`Feature`,properties:{},geometry:{type:`LineString`,coordinates:p}}]})};if(l.on(`style.load`,h),h(),p.length>=2){new P.default.Marker({element:B(s,!1),anchor:`center`}).setLngLat(p[0]).addTo(l),new P.default.Marker({element:B(s,!0),anchor:`center`}).setLngLat(p[p.length-1]).addTo(l);let e=new P.default.LngLatBounds;for(let t of p)e.extend(t);l.fitBounds(e,{padding:22,maxZoom:15,animate:!1})}u=new MutationObserver(()=>{l?.setStyle(f()),h()}),u.observe(a,{attributes:!0,attributeFilter:[`data-theme`]})})(),()=>{c=!0,d?.(),u?.disconnect(),l?.remove(),r.current=null}},[e]),(0,L.jsxs)(L.Fragment,{children:[(0,L.jsx)(`div`,{ref:n,className:`vy-map`}),a&&(0,L.jsxs)(`figure`,{className:`vy-map-print`,"aria-hidden":`true`,children:[(0,L.jsx)(`img`,{src:a.url,alt:``}),(0,L.jsx)(`figcaption`,{children:a.credit})]})]})}function H(e,t,n=3){if(e.includes(t))return e.filter(e=>e!==t);let r=[...e,t];return r.length>n?r.slice(r.length-n):r}var U=e(r(),1);function W(e){let t=e.match(/^propulsion\.([^.]+)\.fuel\.rate$/),n=t?t[1]:e;return n.charAt(0).toUpperCase()+n.slice(1)}function G(e){let t=new Set(e.available);return e.selected.filter(e=>!t.has(e))}function ne(e){return e.available.length>1||e.selected.length>0}function K(e){return[...e.available.map(e=>({path:e,reporting:!0})),...G(e).map(e=>({path:e,reporting:!1}))]}function q(e){let t=G(e);return t.length===0||t.length<e.selected.length?null:`No fuel counted: ${t.map(W).join(`, `)} is selected but not reporting a rate.`}function re(e){let t=G(e);return t.length>0&&t.length===e.selected.length?`${t.length===1?W(t[0]):`${t.length} engines`} · not reporting`:e.selected.length===0?`All`:e.selected.length===1?W(e.selected[0]):`${e.selected.length} engines`}function ie({view:e,onClose:t,onApplied:n}){let r=i(),[o,s]=(0,T.useState)(()=>new Set(e.selected)),[c,u]=(0,T.useState)(!1),[d,f]=(0,T.useState)(null),p=(0,T.useRef)(null),m=e=>{f(null),s(t=>{let n=new Set(t);return n.has(e)?n.delete(e):n.add(e),n})},h=async()=>{let e=[...o].sort();u(!0),f(null);try{if(!r.config.setFuelPaths)throw Error(`this setting is written aboard`);await r.config.setFuelPaths(e);let t=e.join(`,`);for(let e=0;e<12;e++){await new Promise(e=>setTimeout(e,700));try{if([...(await r.config.fuelPaths()).selected].sort().join(`,`)===t)break}catch{}}n(),p.current?.()}catch(e){e instanceof l?f(e.code===`security_off`?`Signal K security is off, so the fuel source is locked. Add an admin user in Signal K.`:e.detail||`Could not apply the change`):f(e instanceof Error?e.message:`Could not apply the change`),u(!1)}},g=q(e),_=document.querySelector(`.swiss.sp-screen`)??document.body;return(0,U.createPortal)((0,L.jsxs)(a,{title:`Fuel source`,eyebrow:`voyage fuel`,onClose:t,closeRef:p,footer:(0,L.jsx)(`button`,{type:`button`,className:`fs-apply`,onClick:h,disabled:c,children:c?`Applying…`:`Apply`}),children:[(0,L.jsx)(`p`,{className:`fs-note`,children:`Which engines count toward voyage fuel. With none selected, every reporting engine is summed.`}),g&&(0,L.jsx)(`div`,{className:`fs-quiet`,children:g}),(0,L.jsx)(`div`,{className:`fs-list`,role:`group`,"aria-label":`Fuel-rate sources`,children:K(e).map(({path:e,reporting:t})=>{let n=o.has(e);return(0,L.jsxs)(`button`,{type:`button`,className:`fs-row${n?` on`:``}${t?``:` off-air`}`,role:`checkbox`,"aria-checked":n,onClick:()=>m(e),disabled:c,children:[(0,L.jsxs)(`span`,{className:`fs-name`,children:[W(e),!t&&(0,L.jsx)(`span`,{className:`fs-tag`,children:` not reporting`})]}),(0,L.jsx)(`span`,{className:`fs-path`,children:e})]},e)})}),d&&(0,L.jsx)(`div`,{className:`fs-err`,children:d})]}),_)}var J=`siparu.fuelMode`;function ae(){let e=localStorage.getItem(J);return E.some(t=>t.mode===e)?e:`total_l`}var oe=`(min-width: 1000px)`,Y=[{k:`today`,label:`Today`},{k:`yesterday`,label:`Yesterday`},{k:`rolling_7d`,label:`7 days`},{k:`season`,label:`Season`}],X={not_found:`That passage is no longer in the log.`,no_previous:`There is no earlier passage to join this one to.`,voyage_open:`This passage is still being recorded. It can be edited once it ends.`,nothing_to_undo:`This passage was not joined by hand.`,admin_required:`Sign in to Signal K as an administrator to edit the log.`,security_off:`Signal K security is off, so the log is locked. Add an admin user in Signal K.`};function Z(e){if(e===null||e<=0)return`·`;let t=Math.round(e*60),n=Math.floor(t/60),r=t%60;return n>0?`${n}h ${r}m`:`${r}m`}function Q(e){return new Date(e).toLocaleDateString(`en-GB`,{day:`2-digit`,month:`short`})}function se(e){let{value:t,unit:n}=d((Date.now()-e)/1e3);return`${t}${n===`s`?`s`:` ${n}`} ago`}function ce(){return new Promise(e=>{let t=setTimeout(e,300);requestAnimationFrame(()=>setTimeout(()=>{clearTimeout(t),e()},0))})}function $(e){let t=new Date(e),n=e=>String(e).padStart(2,`0`);return`${n(t.getHours())}:${n(t.getMinutes())}`}function le(e){return!e.start_port&&!e.end_port?null:e.end_ts===null?e.start_port?`${e.start_port} →`:null:`${e.start_port??`·`} → ${e.end_port??`·`}`}function ue(){let e=i(),[t,n]=(0,T.useState)(0),r=ee(t),a=s(oe),[o,c]=(0,T.useState)(`today`),[u,d]=(0,T.useState)([]),[f,v]=(0,T.useState)(null),[y,b]=(0,T.useState)(!1),[x,S]=(0,T.useState)({}),[C,w]=(0,T.useState)(ae),[E,D]=(0,T.useState)(null),[O,k]=(0,T.useState)(!1),[A,j]=(0,T.useState)(null),[M,N]=(0,T.useState)([]),[P,F]=(0,T.useState)(null);(0,T.useEffect)(()=>{localStorage.setItem(J,C)},[C]),(0,T.useEffect)(()=>{let t=!0;return e.health().then(e=>{t&&j(e.boat_name)}).catch(()=>{}),()=>{t=!1}},[]);let I=e.voyage.edits,R=e.voyage.mergePrevious,z=e.voyage.undoMerge;(0,T.useEffect)(()=>{if(!I)return;let e=!0;return I().then(t=>{e&&N(t.merged)}).catch(()=>{}),()=>{e=!1}},[t,I]),(0,T.useEffect)(()=>{let t=!1;return e.config.fuelPaths().then(e=>!t&&D(e)).catch(()=>{}),()=>{t=!0}},[t]);let B=!!E&&ne(E)&&e.config.setFuelPaths!==void 0,V=E?q(E):null,U=r.current&&r.current.end_ts===null?r.current:null,W=async t=>{let n=u.includes(t);if(d(e=>H(e,t)),!n&&!x[t])try{let n=await e.voyage.track(t);S(e=>({...e,[t]:n}))}catch{}},G=r.stats?.[o]??null,K=async e=>{b(!1),await te(),await ce(),h(p(`Siparu-Voyage`,Date.now()),e)},Z=async(e,t)=>{F(null),v(e);try{let e=await t();if(!e.ok){F(X[e.error??``]??`That edit could not be made.`);return}d([]),S({}),n(e=>e+1)}catch(e){e instanceof l?F(X[e.code??``]??(e.detail||`That edit could not be made.`)):F(`The boat did not answer.`)}};return(0,L.jsxs)(`div`,{className:`vy`,children:[(0,L.jsxs)(`div`,{className:`vy-print-hd`,"aria-hidden":`true`,children:[(0,L.jsx)(`span`,{className:`b`,children:A??`Passage record`}),(0,L.jsx)(`span`,{className:`d`,children:new Date().toISOString().slice(0,10)})]}),U&&(0,L.jsxs)(`section`,{className:`sp-sec vy-sec-active`,children:[(0,L.jsxs)(`h2`,{className:`sp-sec-h`,children:[(0,L.jsx)(`span`,{className:`sp-sec-n`,children:`Under way`}),U.start_port?(0,L.jsxs)(`span`,{className:`sp-sec-note`,children:[`from `,U.start_port]}):null,r.currentStale?(0,L.jsxs)(`span`,{className:`sp-sec-badge quiet`,children:[`since `,$(U.start_ts),r.currentSeenTs===null?` · unreachable`:` · last seen ${se(r.currentSeenTs)}`]}):(0,L.jsxs)(`span`,{className:`sp-sec-badge`,children:[(0,L.jsx)(`span`,{className:`vy-pulse`,"aria-hidden":`true`}),`since `,$(U.start_ts)]})]}),(0,L.jsx)(`div`,{className:`sp-glass`,children:(0,L.jsx)(de,{v:U})})]}),(0,L.jsxs)(`section`,{className:`sp-sec vy-sec-totals`,children:[(0,L.jsxs)(`h2`,{className:`sp-sec-h`,children:[(0,L.jsx)(`span`,{className:`sp-sec-n`,children:`Totals`}),!a&&(0,L.jsx)(`span`,{className:`sp-sec-note`,children:Y.find(e=>e.k===o)?.label.toLowerCase()}),r.err?(0,L.jsx)(`span`,{className:`sp-sec-badge quiet`,children:`unreachable`}):null]}),(0,L.jsx)(`div`,{className:`sp-glass`,children:a?(0,L.jsx)(fe,{stats:r.stats,loading:r.loading}):(0,L.jsxs)(L.Fragment,{children:[(0,L.jsx)(`div`,{className:`vy-seg seg`,role:`group`,"aria-label":`Stats window`,children:Y.map(e=>(0,L.jsx)(`button`,{className:o===e.k?`on`:``,onClick:()=>c(e.k),children:e.label},e.k))}),(0,L.jsx)(pe,{roll:G,loading:r.loading})]})})]}),(0,L.jsxs)(`section`,{className:`sp-sec vy-sec-voyages`,children:[(0,L.jsxs)(`h2`,{className:`sp-sec-h`,children:[(0,L.jsx)(`span`,{className:`sp-sec-n`,children:`Voyages`}),r.err?(0,L.jsx)(`span`,{className:`sp-sec-badge quiet`,children:`unreachable`}):r.loading&&r.list.length===0?null:(0,L.jsx)(`span`,{className:`sp-sec-badge`,children:r.list.length===50?`50 shown`:r.list.length})]}),(0,L.jsxs)(`div`,{className:`sp-glass`,children:[(B||r.list.length>0)&&(0,L.jsx)(`div`,{className:`vy-hd`,children:(0,L.jsxs)(`span`,{className:`vy-hd-acts`,children:[B&&(0,L.jsxs)(`button`,{type:`button`,className:`vy-fuelsrc`,onClick:()=>k(!0),children:[`Fuel · `,re(E)]}),r.list.length>0&&(0,L.jsx)(`button`,{type:`button`,className:`vy-fuelsrc`,onClick:()=>g(_(`siparu-voyages`,Date.now(),`csv`),`text/csv`,m(r.list)),children:`CSV`}),r.list.length>0&&y&&(0,L.jsxs)(`span`,{className:`vy-print-pick`,role:`group`,"aria-label":`Page style`,children:[(0,L.jsx)(`button`,{type:`button`,className:`lbp-c`,onClick:()=>K(`paper`),children:`Paper`}),(0,L.jsx)(`button`,{type:`button`,className:`lbp-c`,onClick:()=>K(`screen`),children:`Screen`})]}),r.list.length>0&&(0,L.jsx)(`button`,{type:`button`,className:`vy-fuelsrc${y?` on`:``}`,"aria-expanded":y,onClick:()=>b(e=>!e),children:`Print`})]})}),r.err?(0,L.jsx)(`div`,{className:`vy-err`,children:r.err}):!r.loading&&r.list.length===0?(0,L.jsxs)(`div`,{className:`sp-empty`,children:[(0,L.jsx)(`div`,{className:`em-t`,children:`No voyages yet`}),(0,L.jsx)(`div`,{className:`em-s`,children:`Passages appear here once the boat gets under way.`})]}):(0,L.jsx)(`div`,{className:`vy-list`,children:r.list.map((e,t)=>(0,L.jsx)(me,{v:e,prev:r.list[t+1],wasJoined:M.includes(e.id),open:u.includes(e.id),track:x[e.id],fuelNotice:V,fuelMode:C,onFuelMode:w,onToggle:()=>W(e.id),onMerge:R?()=>Z(e.id,()=>R(e.id)):void 0,onUndoMerge:z?()=>Z(e.id,()=>z(e.id)):void 0,editErr:f===e.id?P:null},e.id))})]})]}),O&&E&&(0,L.jsx)(ie,{view:E,onClose:()=>k(!1),onApplied:()=>n(e=>e+1)})]})}function de({v:e}){return(0,L.jsx)(`div`,{className:`vy-active`,children:(0,L.jsxs)(`div`,{className:`vy-active-grid`,children:[(0,L.jsxs)(`div`,{className:`vy-a-hero`,children:[(0,L.jsxs)(`div`,{className:`t`,children:[`Distance · `,(0,L.jsx)(`span`,{className:`sub`,children:`nm`})]}),(0,L.jsx)(`div`,{className:`n`,children:o(e.distance_nm,1)})]}),(0,L.jsxs)(`div`,{className:`vy-a-cell`,children:[(0,L.jsx)(`div`,{className:`t`,children:`Underway`}),(0,L.jsx)(`div`,{className:`v`,children:Z(e.hours_underway)})]}),(0,L.jsxs)(`div`,{className:`vy-a-cell`,children:[(0,L.jsx)(`div`,{className:`t`,children:`Avg SOG`}),(0,L.jsx)(`div`,{className:`v`,children:e.avg_sog_kn===null?`·`:(0,L.jsxs)(L.Fragment,{children:[e.avg_sog_kn.toFixed(1),(0,L.jsx)(`span`,{className:`u`,children:`kn`})]})})]})]})})}function fe({stats:e,loading:t}){let n=(t,n)=>e?n(e[t]):`·`,r=(e,r,i,a=!1)=>(0,L.jsxs)(L.Fragment,{children:[(0,L.jsxs)(`span`,{className:`mx-r${a?` mx-hero-r`:``}`,children:[e,r?(0,L.jsxs)(`span`,{className:`mx-u`,children:[`· `,r]}):null]}),Y.map(e=>(0,L.jsx)(`span`,{className:`mx-v${a?` mx-hero`:``}`,children:(0,L.jsx)(`span`,{className:t?`skel`:void 0,children:t?`128.4`:n(e.k,i)})},e.k))]});return(0,L.jsxs)(`div`,{className:`vy-matrix`,children:[(0,L.jsx)(`span`,{className:`mx-corner`,"aria-hidden":`true`}),Y.map(e=>(0,L.jsx)(`span`,{className:`mx-h`,children:e.label},e.k)),r(`Distance`,`nm`,e=>o(e.distance_nm,1),!0),r(`Underway`,null,e=>Z(e.hours_underway)),r(`Avg SOG`,`kn`,e=>e.avg_sog_kn==null?`·`:e.avg_sog_kn.toFixed(1)),r(`Max SOG`,`kn`,e=>e.max_sog_kn==null?`·`:e.max_sog_kn.toFixed(1))]})}function pe({roll:e,loading:t}){let n=t?``:`·`;return(0,L.jsxs)(`div`,{className:`vy-cards`,children:[(0,L.jsxs)(`div`,{className:`c vy-hero`,children:[(0,L.jsxs)(`div`,{className:`t`,children:[`Distance · `,(0,L.jsx)(`span`,{className:`sub`,children:`nm`})]}),(0,L.jsx)(`div`,{className:`n${t?` skel`:``}`,children:t?`128.4`:e?o(e.distance_nm,1):n})]}),(0,L.jsxs)(`div`,{className:`c`,children:[(0,L.jsx)(`div`,{className:`t`,children:`Underway`}),(0,L.jsx)(`div`,{className:`v`,children:e?Z(e.hours_underway):n})]}),(0,L.jsxs)(`div`,{className:`c`,children:[(0,L.jsx)(`div`,{className:`t`,children:`Avg SOG`}),(0,L.jsx)(`div`,{className:`v`,children:e?.avg_sog_kn==null?n:(0,L.jsxs)(L.Fragment,{children:[e.avg_sog_kn.toFixed(1),(0,L.jsx)(`span`,{className:`u`,children:`kn`})]})})]}),(0,L.jsxs)(`div`,{className:`c`,children:[(0,L.jsx)(`div`,{className:`t`,children:`Max SOG`}),(0,L.jsx)(`div`,{className:`v`,children:e?.max_sog_kn==null?n:(0,L.jsxs)(L.Fragment,{children:[e.max_sog_kn.toFixed(1),(0,L.jsx)(`span`,{className:`u`,children:`kn`})]})})]})]})}function me({v:e,prev:t,wasJoined:n,open:r,track:i,fuelNotice:a,fuelMode:s,onFuelMode:c,onToggle:l,onMerge:u,onUndoMerge:d,editErr:p}){let m=e.end_ts===null,h=j(e.fuel_used_l,e.distance_nm,e.hours_underway,s),y=m?`${$(e.start_ts)} →`:`${$(e.start_ts)}-${$(e.end_ts)}`,b=e.avg_sog_kn===null?`·`:`${e.avg_sog_kn.toFixed(1)} kn`,x=le(e);return(0,L.jsxs)(`div`,{className:`vy-rowwrap${r?` open`:``}`,children:[(0,L.jsxs)(`button`,{className:`vy-row`,onClick:l,"aria-expanded":r,children:[(0,L.jsxs)(`div`,{className:`vy-row-top`,children:[(0,L.jsx)(`span`,{className:`vy-date`,children:Q(e.start_ts)}),m&&(0,L.jsx)(`span`,{className:`vy-badge`,children:`Under way`}),(0,L.jsxs)(`span`,{className:`vy-dist`,children:[o(e.distance_nm,1),(0,L.jsx)(`span`,{className:`vy-unit`,children:`nm`})]})]}),x&&(0,L.jsx)(`div`,{className:`vy-route`,children:x}),(0,L.jsxs)(`div`,{className:`vy-row-sub`,children:[y,` · `,Z(e.hours_underway),` · `,b]})]}),r&&(0,L.jsxs)(`div`,{className:`vy-detail`,children:[i&&i.length>=2?(0,L.jsx)(V,{track:i}):(0,L.jsx)(`div`,{className:`vy-track-empty`,children:i?`Track too short to plot`:`Loading track…`}),(0,L.jsxs)(`div`,{className:`vy-meta`,children:[(0,L.jsxs)(`div`,{className:`vy-m`,children:[(0,L.jsx)(`span`,{className:`k`,children:`From`}),e.start_port&&(0,L.jsx)(`span`,{className:`val`,children:e.start_port}),(0,L.jsxs)(`span`,{className:e.start_port?`coord`:`val`,children:[f(e.start_lat,[`N`,`S`],2),` · `,f(e.start_lon,[`E`,`W`],2)]})]}),(0,L.jsxs)(`div`,{className:`vy-m`,children:[(0,L.jsx)(`span`,{className:`k`,children:`To`}),m?(0,L.jsx)(`span`,{className:`val`,children:`-`}):(0,L.jsxs)(L.Fragment,{children:[e.end_port&&(0,L.jsx)(`span`,{className:`val`,children:e.end_port}),(0,L.jsxs)(`span`,{className:e.end_port?`coord`:`val`,children:[f(e.end_lat,[`N`,`S`],2),` · `,f(e.end_lon,[`E`,`W`],2)]})]})]}),(0,L.jsxs)(`div`,{className:`vy-m`,children:[(0,L.jsx)(`span`,{className:`k`,children:`Avg SOG`}),(0,L.jsx)(`span`,{className:`val`,children:e.avg_sog_kn===null?`·`:`${e.avg_sog_kn.toFixed(1)} kn`})]}),(0,L.jsxs)(`div`,{className:`vy-m`,children:[(0,L.jsx)(`span`,{className:`k`,children:`Max SOG`}),(0,L.jsx)(`span`,{className:`val`,children:e.max_sog_kn===null?`·`:`${e.max_sog_kn.toFixed(1)} kn`})]}),e.fuel_used_l===null&&a&&(0,L.jsxs)(`div`,{className:`vy-m vy-m-fuel`,children:[(0,L.jsx)(`span`,{className:`k`,children:`Fuel`}),(0,L.jsx)(`span`,{className:`val vy-fuel-quiet`,children:a})]}),e.fuel_used_l!==null&&(0,L.jsxs)(`div`,{className:`vy-m vy-m-fuel`,children:[(0,L.jsx)(`span`,{className:`k`,children:`Fuel`}),(0,L.jsx)(`span`,{className:`val`,children:h??`·`}),(0,L.jsx)(`select`,{className:`vy-fuel-sel`,value:s,"aria-label":`Fuel unit`,onChange:e=>c(e.target.value),children:E.map(e=>(0,L.jsx)(`option`,{value:e.mode,children:e.label},e.mode))})]}),i&&i.length>0&&(0,L.jsxs)(`div`,{className:`vy-m vy-m-track`,children:[(0,L.jsx)(`span`,{className:`k`,children:`Track`}),(0,L.jsxs)(`button`,{type:`button`,className:`vy-fuelsrc`,onClick:()=>g(_(`siparu-voyage-${e.id}`,e.start_ts,`gpx`),`application/gpx+xml`,v(e,i)),children:[`GPX · `,i.length,` fixes`]})]}),!m&&u&&d&&(n||t)&&(0,L.jsxs)(`div`,{className:`vy-m vy-m-edit`,children:[(0,L.jsx)(`span`,{className:`k`,children:`Passage`}),n?(0,L.jsx)(`button`,{type:`button`,className:`vy-fuelsrc`,onClick:d,children:`Separate again`}):(0,L.jsxs)(`button`,{type:`button`,className:`vy-fuelsrc`,onClick:u,children:[`Join to `,$(t.start_ts),t.end_ts===null?``:`-${$(t.end_ts)}`,` · `,Q(t.start_ts)]})]}),p&&(0,L.jsx)(`div`,{className:`vy-m vy-m-editerr`,children:p})]})]})]})}function he(){return(0,L.jsx)(ue,{})}export{he as default};
|