signalk-siparu 0.2.20 → 0.2.23

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "signalk-siparu",
3
- "version": "0.2.20",
3
+ "version": "0.2.23",
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",
@@ -0,0 +1,7 @@
1
+ export declare const RELAY_ANSWER_MAX_BYTES: number;
2
+ export declare class AnswerTooLarge extends Error {
3
+ readonly bytes: number;
4
+ readonly limit: number;
5
+ constructor(bytes: number, limit: number);
6
+ }
7
+ export declare function readBounded(res: Response, max?: number): Promise<string>;
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AnswerTooLarge = exports.RELAY_ANSWER_MAX_BYTES = void 0;
4
+ exports.readBounded = readBounded;
5
+ exports.RELAY_ANSWER_MAX_BYTES = 64 * 1024;
6
+ class AnswerTooLarge extends Error {
7
+ bytes;
8
+ limit;
9
+ constructor(bytes, limit) {
10
+ super(`relay answer exceeds ${limit} bytes (${bytes} seen)`);
11
+ this.bytes = bytes;
12
+ this.limit = limit;
13
+ this.name = 'AnswerTooLarge';
14
+ }
15
+ }
16
+ exports.AnswerTooLarge = AnswerTooLarge;
17
+ async function readBounded(res, max = exports.RELAY_ANSWER_MAX_BYTES) {
18
+ const declared = res.headers.get('content-length');
19
+ if (declared !== null) {
20
+ const n = Number(declared);
21
+ if (Number.isFinite(n) && n > max)
22
+ throw new AnswerTooLarge(n, max);
23
+ }
24
+ if (!res.body)
25
+ return '';
26
+ const reader = res.body.getReader();
27
+ const chunks = [];
28
+ let total = 0;
29
+ for (;;) {
30
+ const { done, value } = await reader.read();
31
+ if (done)
32
+ break;
33
+ total += value.byteLength;
34
+ if (total > max) {
35
+ await reader.cancel().catch(() => { });
36
+ throw new AnswerTooLarge(total, max);
37
+ }
38
+ chunks.push(value);
39
+ }
40
+ return new TextDecoder().decode(Buffer.concat(chunks));
41
+ }
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.KeySync = exports.MAX_APPROVALS = exports.PROMPTED_FLOOR_MS = void 0;
4
+ const bounded_1 = require("./bounded");
4
5
  const sealing_1 = require("./sealing");
5
6
  const DEFAULT_INTERVAL_MS = 5 * 60_000;
6
7
  const REQUEST_TIMEOUT_MS = 20_000;
@@ -159,7 +160,7 @@ class KeySync {
159
160
  this.deps.debug(`keysync: ${this.lastError}`);
160
161
  return;
161
162
  }
162
- const answer = (await res.json());
163
+ const answer = JSON.parse(await (0, bounded_1.readBounded)(res));
163
164
  if (generation !== this.generation)
164
165
  return;
165
166
  if (answer?.keys === 'mismatch') {
@@ -194,7 +195,10 @@ class KeySync {
194
195
  this.failures++;
195
196
  if (this.state !== 'mismatch') {
196
197
  this.state = 'failing';
197
- this.lastError = 'Cannot reach Siparu. Is the boat online?';
198
+ this.lastError =
199
+ e instanceof bounded_1.AnswerTooLarge
200
+ ? 'Siparu answered with more than this boat will read. If it persists, contact support.'
201
+ : 'Cannot reach Siparu. Is the boat online?';
198
202
  }
199
203
  this.deps.debug(`keysync unreachable: ${String(e)}`);
200
204
  }
@@ -5,6 +5,7 @@ export declare const STILL_FRAME_EVERY_MS = 60000;
5
5
  export declare const PROMPTED_FRAME_FLOOR_MS = 5000;
6
6
  export declare const REPORTING_STALE_MS: number;
7
7
  export declare const PING_EVERY_MS = 25000;
8
+ export declare const SHORE_MESSAGE_MAX_BYTES: number;
8
9
  export interface LiveSocket {
9
10
  send(data: string): void;
10
11
  close(code?: number, reason?: string): void;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.LiveUplink = exports.PING_EVERY_MS = exports.REPORTING_STALE_MS = exports.PROMPTED_FRAME_FLOOR_MS = exports.STILL_FRAME_EVERY_MS = exports.FRAME_EVERY_MS = void 0;
3
+ exports.LiveUplink = exports.SHORE_MESSAGE_MAX_BYTES = exports.PING_EVERY_MS = exports.REPORTING_STALE_MS = exports.PROMPTED_FRAME_FLOOR_MS = exports.STILL_FRAME_EVERY_MS = exports.FRAME_EVERY_MS = void 0;
4
4
  exports.rpcEnvelope = rpcEnvelope;
5
5
  exports.isWatchingNote = isWatchingNote;
6
6
  exports.FRAME_EVERY_MS = 2_000;
@@ -22,6 +22,7 @@ const CLOSE_PLAN_REQUIRED = 4403;
22
22
  const REFUSED_UNKNOWN_TOKEN = 401;
23
23
  const REFUSED_PLAN_REQUIRED = 403;
24
24
  const HANDSHAKE_TIMEOUT_MS = 15_000;
25
+ exports.SHORE_MESSAGE_MAX_BYTES = 64 * 1024;
25
26
  class LiveUplink {
26
27
  deps;
27
28
  sock = null;
@@ -629,7 +630,9 @@ function wsConnect(url, token) {
629
630
  const WS = require('ws');
630
631
  const sock = new WS(url, {
631
632
  headers: { authorization: `Bearer ${token}` },
632
- handshakeTimeout: HANDSHAKE_TIMEOUT_MS
633
+ handshakeTimeout: HANDSHAKE_TIMEOUT_MS,
634
+ maxPayload: exports.SHORE_MESSAGE_MAX_BYTES,
635
+ perMessageDeflate: false
633
636
  });
634
637
  return {
635
638
  send: (data) => sock.send(data),
@@ -11,6 +11,7 @@ exports.registerPairRoutes = registerPairRoutes;
11
11
  exports.retryPendingUnlinks = retryPendingUnlinks;
12
12
  exports.__resetPairingState = __resetPairingState;
13
13
  const access_1 = require("./access");
14
+ const bounded_1 = require("./bounded");
14
15
  const fingerprint_1 = require("./fingerprint");
15
16
  const origin_guard_1 = require("./origin-guard");
16
17
  function securityOff(app, req) {
@@ -87,7 +88,7 @@ async function relay(url, path, body, boatToken) {
87
88
  throw new Error(`relay ${path} unreachable${cause}`);
88
89
  }
89
90
  if (!res.ok) {
90
- const text = await res.text().catch(() => '');
91
+ const text = await (0, bounded_1.readBounded)(res).catch(() => '');
91
92
  let code = null;
92
93
  try {
93
94
  code = JSON.parse(text).error ?? null;
@@ -96,7 +97,7 @@ async function relay(url, path, body, boatToken) {
96
97
  }
97
98
  throw new RelayRefused(res.status, code, `relay ${path} -> ${res.status} ${text.slice(0, 120)}`);
98
99
  }
99
- return (await res.json());
100
+ return JSON.parse(await (0, bounded_1.readBounded)(res));
100
101
  }
101
102
  finally {
102
103
  clearTimeout(timeout);
@@ -193,7 +194,9 @@ function registerPairRoutes(router, deps) {
193
194
  catch (e) {
194
195
  json({
195
196
  state: 'error',
196
- message: 'Cannot reach Siparu. Is the boat online?'
197
+ message: e instanceof bounded_1.AnswerTooLarge
198
+ ? 'Siparu answered with more than this boat will read. Try again later.'
199
+ : 'Cannot reach Siparu. Is the boat online?'
197
200
  });
198
201
  app.debug(`pair status: ${String(e)}`);
199
202
  }
@@ -219,7 +222,10 @@ function registerPairRoutes(router, deps) {
219
222
  e instanceof RelayRefused && e.status === 429
220
223
  ?
221
224
  'Too many pairing attempts from this network. Wait an hour and try again.'
222
- : 'Cannot reach Siparu. Check the boat is online (DNS, port 443, captive portal).';
225
+ : e instanceof bounded_1.AnswerTooLarge
226
+ ?
227
+ 'Siparu answered with more than this boat will read. Try again later.'
228
+ : 'Cannot reach Siparu. Check the boat is online (DNS, port 443, captive portal).';
223
229
  app.error(`pair start failed: ${String(e)}`);
224
230
  res.status(502).json({ state: 'error', message: lastError });
225
231
  }
@@ -22,6 +22,7 @@ export declare class PhaseStateMachine {
22
22
  private lastTs;
23
23
  constructor(opts: VoyageOptions, restored?: OpenPhase | null);
24
24
  feed(row: VoyageRow): Phase | null;
25
+ private close;
25
26
  current(): Phase | null;
26
27
  }
27
28
  export {};
@@ -47,12 +47,22 @@ class PhaseStateMachine {
47
47
  return null;
48
48
  }
49
49
  const dt = this.lastTs !== null ? row.ts - this.lastTs : 0;
50
+ const before = this.last;
50
51
  this.lastTs = row.ts;
51
52
  this.last = pt;
52
53
  if (dt < 0 || dt > MAX_GAP_MS) {
54
+ if (dt < 0 || raw === this.curKind) {
55
+ this.candKind = null;
56
+ this.candStreakMs = 0;
57
+ return null;
58
+ }
59
+ const boundary = raw === this.candKind && this.candStart ? this.candStart : null;
60
+ const closed = this.close(boundary ?? before);
61
+ this.curKind = raw;
62
+ this.curStart = boundary ?? pt;
53
63
  this.candKind = null;
54
64
  this.candStreakMs = 0;
55
- return null;
65
+ return closed;
56
66
  }
57
67
  if (raw === this.curKind) {
58
68
  this.candKind = null;
@@ -69,16 +79,7 @@ class PhaseStateMachine {
69
79
  }
70
80
  if (this.candStreakMs >= this.opts.phaseMinMinutes * 60_000) {
71
81
  const start = this.candStart;
72
- const cur = this.curStart;
73
- const closed = {
74
- kind: this.curKind,
75
- start_ts: cur.ts,
76
- end_ts: start.ts,
77
- start_lat: cur.lat,
78
- start_lon: cur.lon,
79
- end_lat: start.lat,
80
- end_lon: start.lon
81
- };
82
+ const closed = this.close(start);
82
83
  this.curKind = this.candKind;
83
84
  this.curStart = start;
84
85
  this.candKind = null;
@@ -87,6 +88,18 @@ class PhaseStateMachine {
87
88
  }
88
89
  return null;
89
90
  }
91
+ close(end) {
92
+ const cur = this.curStart;
93
+ return {
94
+ kind: this.curKind,
95
+ start_ts: cur.ts,
96
+ end_ts: end.ts,
97
+ start_lat: cur.lat,
98
+ start_lon: cur.lon,
99
+ end_lat: end.lat,
100
+ end_lon: end.lon
101
+ };
102
+ }
90
103
  current() {
91
104
  if (this.curKind === null || this.curStart === null)
92
105
  return null;
@@ -2,9 +2,23 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.QueryError = exports.QueryService = exports.RAW_WINDOW_DAYS = exports.LIMIT_MAX = exports.LIMIT_DEFAULT = void 0;
4
4
  const contract_1 = require("./contract");
5
+ const aggregate_1 = require("./aggregate");
5
6
  const time_1 = require("./time");
6
7
  const units_1 = require("./units");
7
8
  const ROLLUP_FIELDS = (0, contract_1.metricFields)('linear', 'angular', 'text');
9
+ const SIX_HOURS_MS = 6 * 3_600_000;
10
+ function sixHourWindows(hours) {
11
+ const byWindow = new Map();
12
+ for (const h of hours) {
13
+ const win = Math.floor((0, time_1.hourStartOf)(h.hour) / SIX_HOURS_MS);
14
+ const list = byWindow.get(win);
15
+ if (list)
16
+ list.push(h);
17
+ else
18
+ byWindow.set(win, [h]);
19
+ }
20
+ return [...byWindow.entries()].map(([win, hs]) => [win * SIX_HOURS_MS, (0, aggregate_1.mergeHours)(hs)]);
21
+ }
8
22
  exports.LIMIT_DEFAULT = 200;
9
23
  exports.LIMIT_MAX = 5000;
10
24
  exports.RAW_WINDOW_DAYS = 7;
@@ -66,13 +80,7 @@ class QueryService {
66
80
  }
67
81
  else if (q.bucket === 360) {
68
82
  const from = q.from ?? 0;
69
- const hours = await this.rollups.readHourly(from, to);
70
- const byWindow = new Map();
71
- for (const h of hours) {
72
- const win = Math.floor((0, time_1.hourStartOf)(h.hour) / (6 * 3_600_000));
73
- byWindow.set(win, h);
74
- }
75
- rows = [...byWindow.entries()].map(([win, h]) => rollupToRow(h, win * 6 * 3_600_000));
83
+ rows = sixHourWindows(await this.rollups.readHourly(from, to)).map(([ts, w]) => rollupToRow(w, ts));
76
84
  }
77
85
  else if (q.bucket === 1440) {
78
86
  const from = q.from ?? 0;
@@ -116,11 +124,7 @@ class QueryService {
116
124
  }
117
125
  else if (q.bucket === 360) {
118
126
  const from = q.from ?? 0;
119
- const byWindow = new Map();
120
- for (const h of await this.rollups.readHourly(from, to)) {
121
- byWindow.set(Math.floor((0, time_1.hourStartOf)(h.hour) / (6 * 3_600_000)), h);
122
- }
123
- points = [...byWindow.entries()].flatMap(([win, h]) => aggToPoint(win * 6 * 3_600_000, aggFor(h, coreField, pathName)));
127
+ points = sixHourWindows(await this.rollups.readHourly(from, to)).flatMap(([ts, w]) => aggToPoint(ts, aggFor(w, coreField, pathName)));
124
128
  }
125
129
  else if (q.bucket === 1440) {
126
130
  const from = q.from ?? 0;
@@ -19,12 +19,14 @@ export declare class VoyageStateMachine {
19
19
  private movingStreakMs;
20
20
  private stationaryStreakMs;
21
21
  private lastTs;
22
+ private endBeforeGap;
22
23
  constructor(opts: VoyageOptions, open: boolean);
23
24
  feed(row: VoyageRow): {
24
25
  type: 'open';
25
26
  startTs: number;
26
27
  } | {
27
28
  type: 'close';
29
+ endTs: number;
28
30
  } | null;
29
31
  }
30
32
  export declare function mergeContiguousVoyages(voyages: Voyage[], opts: VoyageOptions, reintegrate: (v: Voyage) => Promise<void>, manual?: ReadonlySet<number>): Promise<Voyage[]>;
@@ -112,6 +112,7 @@ class VoyageStateMachine {
112
112
  movingStreakMs = 0;
113
113
  stationaryStreakMs = 0;
114
114
  lastTs = null;
115
+ endBeforeGap = null;
115
116
  constructor(opts, open) {
116
117
  this.opts = opts;
117
118
  this.open = open;
@@ -125,6 +126,9 @@ class VoyageStateMachine {
125
126
  (isStationaryState(row.nav_state) && sogValid === null);
126
127
  const dtMs = this.lastTs !== null ? ts - this.lastTs : 0;
127
128
  if (dtMs < 0 || dtMs > MAX_GAP_MS) {
129
+ if (dtMs > MAX_GAP_MS && this.open && this.lastTs !== null && this.stationaryStreakMs > 0) {
130
+ this.endBeforeGap ??= this.lastTs;
131
+ }
128
132
  this.movingStreakMs = 0;
129
133
  this.stationaryStreakMs = 0;
130
134
  this.lastTs = ts;
@@ -144,11 +148,15 @@ class VoyageStateMachine {
144
148
  }
145
149
  else {
146
150
  this.stationaryStreakMs = stationary ? this.stationaryStreakMs + dtMs : 0;
151
+ if (!stationary)
152
+ this.endBeforeGap = null;
147
153
  if (this.stationaryStreakMs >= this.opts.closeMinutes * 60_000) {
154
+ const endTs = this.endBeforeGap ?? ts;
148
155
  this.open = false;
149
156
  this.movingStreakMs = 0;
150
157
  this.stationaryStreakMs = 0;
151
- return { type: 'close' };
158
+ this.endBeforeGap = null;
159
+ return { type: 'close', endTs };
152
160
  }
153
161
  }
154
162
  return null;
@@ -215,9 +223,7 @@ async function reconcile(state, rows, opts, ports, readRange, now, fuelRatePaths
215
223
  state.voyages.push(openVoyage);
216
224
  }
217
225
  else if (event.type === 'close' && openVoyage) {
218
- openVoyage.end_ts = row.ts;
219
- openVoyage.end_lat = row.lat;
220
- openVoyage.end_lon = row.lon;
226
+ openVoyage.end_ts = event.endTs;
221
227
  openVoyage.status = 'closed';
222
228
  await applyMetrics(openVoyage, readRange, now, fuelRatePaths);
223
229
  openVoyage.end_port = nearestPort(openVoyage.end_lat, openVoyage.end_lon, ports);
@@ -156,7 +156,7 @@ class VoyageLog {
156
156
  let hours = 0;
157
157
  for (const v of this.state.voyages) {
158
158
  const vStart = v.start_ts;
159
- const vEnd = v.end_ts ?? winEnd;
159
+ const vEnd = v.end_ts ?? now;
160
160
  const s = Math.max(vStart, winStart);
161
161
  const e = Math.min(vEnd, winEnd);
162
162
  if (e <= s)
@@ -1,2 +1,2 @@
1
1
  const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./tz-DxphYhHy.js","./rolldown-runtime-CNC7AqOf.js"])))=>i.map(i=>d[i]);
2
- 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,F as c,N as l,O as u,P as d,S as f,T as p,_ as m,b as h,c as g,d as _,g as v,k as y,l as b,o as x,p as S,s as C,v as w,w as T}from"./index-BtvisMV4.js";import{a as E,d as D,i as O,l as k,n as A,o as j,r as M,t as N,u as P}from"./export-CGdOfqNp.js";var F=e(t(),1),I={key:`ts`,head:`UTC`,book:`bridge`,cell:()=>``};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=>h(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=T(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=p(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=>h(f(e.air_temp_k),1)},has:e=>e.air_temp_k!==null},{col:{key:`sea`,head:`SEA`,book:`bridge`,unit:`°C`,cell:e=>h(f(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 U(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=u(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=y(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 W(e,t){return[I,...[...R(t),...U(e)].filter(t=>e.some(t.has)).map(e=>e.col)]}function G(e,t){return e.filter(e=>e.key===`ts`||e.book===t)}var ee={engine:`ENGINES`,generator:`GENERATORS`,tanks:`TANKS`};function K(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=u(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:ee[e]??e.toUpperCase(),units:t,metrics:a}})}function te(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=y(r,i).value;return Number.isFinite(a)?Number.isInteger(a)?String(a):a.toFixed(1):`·`}function ne(e,t){let[n,r]=(0,F.useState)(e),[i,a]=(0,F.useState)(!1);return(0,F.useEffect)(()=>{if(e===n)return;a(!0);let i=setTimeout(()=>{r(e),a(!1)},t);return()=>clearTimeout(i)},[e,n,t]),[n,i]}var q=new Map;function J(e,t,n){let r=`${t}|${e}`,i=q.get(r);if(i===void 0){try{i=new Intl.DateTimeFormat(`en-GB`,{...n,timeZone:e})}catch{i=null}q.set(r,i)}return i}var re={hour:`2-digit`,minute:`2-digit`,hourCycle:`h23`},Y={hour:`2-digit`,timeZoneName:`shortOffset`},X={weekday:`short`,day:`2-digit`,month:`short`,year:`numeric`},Z=e=>e!==null&&J(e,`clock`,re)&&J(e,`offset`,Y)&&J(e,`day`,X)?e:`UTC`;function ie(e,t){return J(Z(t),`clock`,re).format(e)}function ae(e,t){let n=J(Z(t),`offset`,Y);if(!n)return`UTC`;let r=(n.formatToParts(e).find(e=>e.type===`timeZoneName`)?.value??``).replace(/^(GMT|UTC)/,``);return/[1-9]/.test(r)?`UTC${r}`:`UTC`}function oe(e,t){return J(Z(t),`day`,X).format(e).replace(/,\s*/,` · `).toUpperCase()}function se(e,t){let n=new Map,r=new Map,i=(e,n)=>{let i=`${Math.round(e*10)},${Math.round(n*10)}`,a=r.get(i);if(a===void 0){try{a=t(e,n)}catch{a=null}r.set(i,a)}return a},a=[...e].sort((e,t)=>e.ts-t.ts),o=a.map(e=>t!==null&&e.lat!==null&&e.lon!==null?i(e.lat,e.lon):null),s=a.flatMap((e,t)=>o[t]===null?[]:[{ts:e.ts,zone:o[t]}]),c=0;return a.forEach((e,t)=>{if(o[t]!==null||s.length===0){n.set(e.ts,o[t]);return}for(;c+1<s.length&&Math.abs(s[c+1].ts-e.ts)<Math.abs(s[c].ts-e.ts);)c++;n.set(e.ts,s[c].zone)}),n}function ce(e,t){let n=se(e,t),r=e=>n.get(e)??null,i=new Set(e.map(e=>ae(e.ts,r(e.ts)))),a=i.size>1;return{head:a?`SHIP`:i.values().next().value??`UTC`,mixed:a,clock:e=>ie(e,r(e)),day:e=>oe(e,r(e)),offset:e=>ae(e,r(e)),dayLine:e=>`${oe(e,r(e))} · ${ae(e,r(e))}`}}var le=null;function ue(t){let[n,r]=(0,F.useState)(()=>le);return(0,F.useEffect)(()=>{let t=!0;return c(()=>import(`./tz-DxphYhHy.js`).then(t=>e(t.default,1)).then(e=>{le=e.default,t&&r(()=>e.default)}),__vite__mapDeps([0,1]),import.meta.url).catch(()=>{}),()=>{t=!1}},[]),(0,F.useMemo)(()=>ce(t,n),[t,n])}var de=e=>`lb:columns:${e}`,fe={off:[]};function pe(e){return e.key===`ts`}function Q(e,t){return pe(e)||!t.off.includes(e.key)}function me(e,t){return e.filter(e=>Q(e,t))}function he(e){return e.filter(e=>!pe(e))}function ge(e,t){return pe(t)?e:{off:Q(t,e)?[...e.off,t.key]:e.off.filter(e=>e!==t.key)}}function _e(e,t){return{off:t?[]:he(e).map(e=>e.key)}}function ve(e,t){return xe(e.off)===xe(t.off)}function ye(e){try{let t=globalThis.localStorage?.getItem(de(e));if(!t)return fe;let n=JSON.parse(t);return{off:Array.isArray(n.off)?n.off.filter(e=>typeof e==`string`):[]}}catch{return fe}}function be(e,t){try{globalThis.localStorage?.setItem(de(e),JSON.stringify(t))}catch{}}function xe(e){return[...new Set(e)].sort().join(` `)}var $=n();function Se({cols:e,applied:t,onApply:n,onCancel:r}){let[i,a]=(0,F.useState)(t),o=ve(i,t),s=he(e),c=s.filter(e=>Q(e,i)).length;return(0,$.jsxs)(`div`,{className:`lb-pick`,children:[(0,$.jsxs)(`div`,{className:`lbp-book`,children:[(0,$.jsxs)(`div`,{className:`lbp-h`,children:[(0,$.jsx)(`span`,{className:`lbp-n`,children:`Columns`}),(0,$.jsxs)(`span`,{className:`lbp-s`,children:[c,` of `,s.length]}),(0,$.jsxs)(`span`,{className:`lbp-all`,children:[(0,$.jsx)(`button`,{onClick:()=>a(_e(e,!0)),children:`All`}),(0,$.jsx)(`button`,{onClick:()=>a(_e(e,!1)),children:`None`})]})]}),(0,$.jsx)(`div`,{className:`lbp-chips`,children:s.map(e=>(0,$.jsx)(`button`,{className:`lbp-c${Q(e,i)?` on`:``}`,"aria-pressed":Q(e,i),onClick:()=>a(ge(i,e)),children:e.head},e.key))})]}),(0,$.jsxs)(`div`,{className:`lbp-act`,children:[(0,$.jsx)(`button`,{onClick:r,children:`Cancel`}),(0,$.jsx)(`button`,{className:`lbp-go`,disabled:o,onClick:()=>n(i),children:`Show these columns`})]})]})}var Ce={"1m":1,"1h":60,"6h":360,"1d":1440},we={"1m":`Every minute`,"1h":`Hourly`,"6h":`Six-hourly`,"1d":`Daily`},Te={"1m":60,"1h":48,"6h":40,"1d":30},Ee=15e3;function De(e){let t=l(),[n,i]=(0,F.useState)(0),[a,o]=(0,F.useState)(!1),[s,c]=(0,F.useState)([]),[u,d]=(0,F.useState)(null),[f,p]=(0,F.useState)(!1),[m,h]=(0,F.useState)(e);m!==e&&(h(e),i(0),o(!1));let g=Ce[e],_=Te[e]+n*Te[e],v=(0,F.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>_),c(n.slice(0,_))}catch(e){d(e.message)}finally{p(!1)}},[g,_]);return(0,F.useEffect)(()=>{v();let e=r(v,Ee);return()=>e()},[v]),{snaps:s,err:u,busy:f,hasMore:a,loadMore:(0,F.useCallback)(()=>i(e=>e+1),[])}}function Oe(){let e=l(),[t,n]=(0,F.useState)(w()),[i,a]=(0,F.useState)([]),[o,s]=(0,F.useState)(null),[c,u]=(0,F.useState)(!1),d=(0,F.useRef)(!0),f=S(6e4),p=w(new Date(f)),h=(0,F.useMemo)(()=>m(t),[t]),g=h+24*36e5,_=t===p;(0,F.useEffect)(()=>{d.current&&t!==p&&n(p)},[p,t]);let v=(0,F.useCallback)(e=>{d.current=e===w(),n(e)},[]),y=(0,F.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,F.useEffect)(()=>{if(y(),!_)return;let e=r(y,Ee);return()=>e()},[y,_]),{dateStr:t,setDateStr:v,dayStart:h,isToday:_,snaps:i,err:o,busy:c,prevDay:(0,F.useCallback)(()=>v(w(new Date(h-864e5))),[h,v]),nextDay:(0,F.useCallback)(()=>v(w(new Date(h+864e5))),[h,v]),goToday:(0,F.useCallback)(()=>v(w()),[v])}}var ke=5e3;async function Ae(e,t,n){let r=await e({from:t,to:n,limit:5001,order:`desc`});return{rows:r.rows.slice(0,ke),truncated:r.clamped||r.rows.length>5e3,minutesFrom:r.minutesFrom}}function je(e,t,n,r=`last`){let i=l(),[a,o]=(0,F.useState)([]),[s,c]=(0,F.useState)(null),[u,d]=(0,F.useState)(!1),[f,p]=(0,F.useState)(!1),[h,g]=(0,F.useState)(!1),[_,v]=(0,F.useState)(null),[y,b]=(0,F.useState)([]),x=(0,F.useMemo)(()=>m(e),[e]),S=(0,F.useMemo)(()=>m(t)+864e5-1,[t]),C=Ce[n],w=(0,F.useCallback)(async()=>{if(Number.isNaN(x)||Number.isNaN(S)||S<x){o([]),b([]),p(!1),c(null),g(!0);return}g(!1),d(!0),c(null);try{if(b([]),C===1){let e=await Ae(e=>i.logbook.minutes(e),x,S);v(e.minutesFrom),p(e.truncated),o(e.rows);return}let e=await i.logbook.rollupHours(x,S);v(null);let t=P(e,n),a=t.map(e=>D(e,r)),s=e=>e.slice(Math.max(0,e.length-ke));p(a.length>ke),o(s(a)),r!==`last`&&b(s(t.map(e=>D(e,`last`))))}catch(e){c(e.message)}finally{d(!1),g(!0)}},[x,S,C,n,r]);return(0,F.useEffect)(()=>{w()},[w]),{snaps:a,err:s,busy:u,truncated:f,loaded:h,minutesFrom:_,plain:y}}var Me=[`1m`,`1h`,`6h`,`1d`],Ne=[{v:`csv`,name:`CSV`,note:`a file for a spreadsheet`},{v:`pdf`,name:`PDF`,note:`the page, through your printer`}],Pe=[{v:`screen`,name:`Screen`},{v:`paper`,name:`Paper`}],Fe=[`last`,`avg`,`min`,`max`];function Ie(e,t,n){return e===`pdf`?[n]:t.includes(n)?t.filter(e=>e!==n):[...t,n]}function Le(e,t){return e===`pdf`?[t[0]??`last`]:t}function Re(){return w(new Date(Date.now()-7*864e5))}function ze({initial:e,onView:t,onSave:n}){let[r,i]=(0,F.useState)(e?.from??Re()),[a,o]=(0,F.useState)(e?.to??w()),[s,c]=(0,F.useState)(e?.gran??`1h`),[l,u]=(0,F.useState)(e?.format??`csv`),[d,f]=(0,F.useState)(e?.stats??[`last`]),[p,m]=(0,F.useState)(e?.distance??!1),[h,g]=(0,F.useState)(e?.samples??!1),[_,v]=(0,F.useState)(e?.style??`screen`),y=a<r,b={from:r,to:a,gran:s,format:l,stats:Le(l,d),distance:p,samples:h,style:_},x=s!==`1m`,S=l===`pdf`,C=x&&!S&&d.length===0&&!p&&!h,T=e=>f(t=>Ie(l,t,e)),E=Le(l,d),D=e=>E.includes(e);return(0,$.jsxs)(`div`,{className:`lb-pick lb-exp`,children:[(0,$.jsxs)(`div`,{className:`lbp-books`,children:[(0,$.jsxs)(`div`,{className:`lbp-book`,children:[(0,$.jsxs)(`div`,{className:`lbp-h`,children:[(0,$.jsx)(`span`,{className:`lbp-n`,children:`Window`}),(0,$.jsx)(`span`,{className:`lbp-s`,children:`UTC days, both ends included`})]}),(0,$.jsxs)(`div`,{className:`lbe-dates`,children:[(0,$.jsxs)(`label`,{children:[(0,$.jsx)(`span`,{children:`From`}),(0,$.jsx)(`input`,{type:`date`,className:`dt`,value:r,max:w(),onChange:e=>i(e.target.value)})]}),(0,$.jsxs)(`label`,{children:[(0,$.jsx)(`span`,{children:`To`}),(0,$.jsx)(`input`,{type:`date`,className:`dt`,value:a,max:w(),onChange:e=>o(e.target.value)})]})]})]}),(0,$.jsxs)(`div`,{className:`lbp-book`,children:[(0,$.jsxs)(`div`,{className:`lbp-h`,children:[(0,$.jsx)(`span`,{className:`lbp-n`,children:`Interval`}),(0,$.jsx)(`span`,{className:`lbp-s`,children:`one row per`})]}),(0,$.jsx)(`div`,{className:`lbp-chips`,children:Me.map(e=>(0,$.jsx)(`button`,{className:`lbp-c${s===e?` on`:``}`,onClick:()=>c(e),children:we[e]},e))})]}),(0,$.jsxs)(`div`,{className:`lbp-book`,children:[(0,$.jsxs)(`div`,{className:`lbp-h`,children:[(0,$.jsx)(`span`,{className:`lbp-n`,children:`Save as`}),(0,$.jsx)(`span`,{className:`lbp-s`,children:Ne.find(e=>e.v===l)?.note})]}),(0,$.jsx)(`div`,{className:`lbp-chips`,children:Ne.map(e=>(0,$.jsx)(`button`,{className:`lbp-c${l===e.v?` on`:``}`,onClick:()=>u(e.v),children:e.name},e.v))})]}),(0,$.jsxs)(`div`,{className:`lbp-book${S?``:` off`}`,children:[(0,$.jsxs)(`div`,{className:`lbp-h`,children:[(0,$.jsx)(`span`,{className:`lbp-n`,children:`Style`}),(0,$.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,$.jsx)(`div`,{className:`lbp-chips`,children:Pe.map(e=>(0,$.jsx)(`button`,{className:`lbp-c${_===e.v?` on`:``}`,disabled:!S,onClick:()=>v(e.v),children:e.name},e.v))})]})]}),x&&(0,$.jsxs)(`div`,{className:`lbp-book lbp-figs`,children:[(0,$.jsxs)(`div`,{className:`lbp-h`,children:[(0,$.jsx)(`span`,{className:`lbp-n`,children:`Figures`}),(0,$.jsx)(`span`,{className:`lbp-s`,children:S?`one: a table cell holds one number`:`one or more: a column each`})]}),(0,$.jsxs)(`div`,{className:`lbp-chips`,children:[Fe.map(e=>(0,$.jsx)(`button`,{className:`lbp-c${D(e)?` on`:``}`,onClick:()=>T(e),children:k[e]},e)),(0,$.jsx)(`button`,{className:`lbp-c${p?` on`:``}`,disabled:S,onClick:()=>m(!p),children:`Distance`}),(0,$.jsx)(`button`,{className:`lbp-c${h?` on`:``}`,disabled:S,onClick:()=>g(!h),children:`Samples`})]})]}),y&&(0,$.jsx)(`div`,{className:`lbe-warn`,children:`That window ends before it begins.`}),C&&(0,$.jsx)(`div`,{className:`lbe-warn`,children:`Choose at least one figure to put in the file.`}),(0,$.jsxs)(`div`,{className:`lbp-act`,children:[S&&(0,$.jsx)(`button`,{disabled:y,onClick:()=>t(b),children:`View`}),(0,$.jsxs)(`button`,{className:`lbp-go`,disabled:y||C,onClick:()=>n(b),children:[`Save `,l.toUpperCase()]})]})]})}function Be({open:e,style:t,cls:n,children:r}){let[i,a]=(0,F.useState)(e);return e&&!i&&a(!0),i?(0,$.jsx)(`div`,{className:`lb-open${e?``:` shut`}${n??``}`,style:t,onAnimationEnd:t=>{t.target===t.currentTarget&&!e&&a(!1)},children:(0,$.jsx)(`div`,{className:`lb-open-in`,children:r})}):null}function Ve(e){let t=e-32-58;return Math.max(1,Math.floor(t/52))}function He(e){return e.reduce((e,t)=>e+(t.lanes??1),0)}function Ue(e,t){if(t===null||e.length===0)return e;let n=Ve(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 We(e,t,n){return t===null?e:e.slice(0,Ve(t-(n?108:0)))}var Ge=180,Ke=[`1m`,`1h`,`6h`,`1d`],qe={"1m":`Last hour`,"1h":`Last 2 days`,"6h":`Last 10 days`,"1d":`Last month`};function Je({book:e}){let t=l(),[n,r]=(0,F.useState)(`live`),[i,a]=(0,F.useState)(`1h`),[o,s]=(0,F.useState)(null),[c,u]=ne((0,F.useMemo)(()=>({book:e,mode:n,gran:i,family:o}),[e,n,i,o]),Ge),{book:d,mode:f,gran:p,family:h}=c,g=n!==f,[v,y]=(0,F.useState)(()=>localStorage.getItem(`lb:windUnit`)||`kn`),b=()=>y(e=>{let t=e===`kn`?`bft`:`kn`;return localStorage.setItem(`lb:windUnit`,t),t}),x=(0,F.useRef)([]),S=(0,F.useRef)(0),[C,w]=(0,F.useState)(()=>ye(d)),[T,E]=(0,F.useState)(d);T!==d&&(E(d),w(ye(d)),x.current=[],S.current=0);let[O,k]=(0,F.useState)(!1),I=e=>{w(e),be(d,e),k(!1)},[L,R]=_(),z=(0,F.useRef)(null),B=(0,F.useRef)(null),V=(0,F.useCallback)(e=>{B.current=e,L(e)},[L]),H=(0,F.useRef)(null);(0,F.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,c]);let[U,ee]=(0,F.useState)(!1),[K,te]=(0,F.useState)(null),[q,J]=(0,F.useState)(!1),[re,Y]=(0,F.useState)(null),[X,Z]=(0,F.useState)(null),ie={book:d,mode:n,setMode:r,gran:i,setGran:a,shownGran:p,family:o,setFamily:s,shownFamily:h,leaving:u,modeLeaving:g,lanesHold:z,groupsHold:x,chosenHold:S,windUnit:v,toggleWind:b,selection:C,picking:O,setPicking:k,applySelection:I,width:R,exporting:U,setExporting:ee,request:K,onView:e=>{te(e),r(`range`),Y(null)},onSave:async e=>{if(ee(!1),Y(null),Z(null),e.format===`pdf`){te(e),r(`range`),J(!0);return}try{let n=m(e.from),r=m(e.to)+864e5-1;if(e.gran===`1m`){let{rows:e,truncated:i}=await Ae(e=>t.logbook.minutes(e),n,r),a=me(G(W(e,v),d),C);A(M(`logbook-${d}`,n,`csv`,i?`-partial`:``),`text/csv`,j(e,a)),i&&Z(`That window holds more than ${ke} minutes, so the file stops at the most recent ${ke} and its name ends in -partial. A longer interval covers the same days in fewer rows.`);return}let i=P(await t.logbook.rollupHours(n,r),e.gran),a=e.stats.map(e=>({stat:e,cols:me(G(W(i.map(t=>D(t,e)),v),d),C)})).filter(e=>e.cols.length>1);A(M(`logbook-${d}`,n,`csv`),`text/csv`,N(i,a,{distance:e.distance,samples:e.samples}))}catch(e){Y(e.message)}},saveErr:re,saveNote:X};return(0,$.jsxs)(`div`,{className:`lb`,ref:V,children:[(0,$.jsx)(`style`,{children:`@media print { @page { size: A4 landscape; } }`}),f===`live`?(0,$.jsx)(_t,{...ie}):f===`day`?(0,$.jsx)(vt,{...ie}):(0,$.jsx)(wt,{...ie,req:K,printing:q,donePrinting:()=>J(!1)})]})}function Ye({mode:e,setMode:t}){return(0,$.jsxs)(`div`,{className:`seg`,children:[(0,$.jsx)(`button`,{className:e===`live`?`on`:``,onClick:()=>t(`live`),children:`Live`}),(0,$.jsx)(`button`,{className:e===`day`?`on`:``,onClick:()=>t(`day`),children:`Day`})]})}function Xe({leaving:e,children:t}){return(0,$.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,$.jsx)(`div`,{className:`seg`,children:e.map(e=>(0,$.jsx)(`button`,{className:e.tab===r.tab?`on`:``,onClick:()=>n(e.tab),children:e.head},e.tab))})}function Qe(e,t,n){if(e===null||m(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 $e(e,t,n){let r=k[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 et(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 tt(e){return m(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 nt(e,t,n){return e.length>1||t===null?lt(e):{"--lb-cols":n.current??Ve(t)}}function rt({what:e}){return(0,$.jsxs)(`div`,{className:`sp-empty`,children:[(0,$.jsx)(`div`,{className:`em-t`,children:`No snapshots`}),(0,$.jsx)(`div`,{className:`em-s`,children:e})]})}var it=(e,t)=>`u:${e}:${t}`;function at(e,t,n,r,i,a,o,s,c,l=!0){let u=st(e,t,n,r,i,a,o,s,c,l);if(t!==`engine`||u.groups.length<2)return u;let d=u,f=ot(u);for(let o of u.groups){if(o===u.group||o.tab===a)continue;let s=st(e,t,n,r,i,o.tab,{current:null},{current:[]},{current:0},l),c=ot(s);c>f&&(d=s,f=c)}return d===u?u:{...u,block:d.block,cls:d.cls}}function ot(e){let t=Math.max(1,Number(e.block[`--lb-cols`]??1));return 58+(e.cls.includes(`u`)?108:0)+t*164}function st(e,t,n,r,i,a,o,s,c,l){let u=t===`engine`?K(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:it(f.tab,e.key),head:e.head})),t=f.metrics.filter(e=>Q({key:it(f.tab,e.key)},r)),n=l?We(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=G(W(e,n),t),m=f?p.filter(e=>e.key===`ts`||e.tab===f.tab):p,h=me(m,r),g=l?Ue(h,i):h;return g.length>1&&(o.current=He(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:nt(g,i,o),cls:``}}function ct(e){return{"--lb-cols":Math.max(1,e.metrics.length)}}function lt(e){return{"--lb-cols":He(e.slice(1))}}function ut(e){return(e.lanes??1)>1?` w${e.lanes}`:``}function dt(e,t){return t===`last`?we[e]:`${we[e]} · ${k[t]}`}function ft(e,t,n,r){return r===0?String(n):e<t?`${e} of ${t}`:String(t)}function pt({shown:e,chosen:t,kept:n,offered:r,open:i,onOpen:a}){let o=r===0;return(0,$.jsxs)(`button`,{className:`lb-colbtn${i?` on`:``}${o?` quiet`:``}`,onClick:a,disabled:o,children:[`Columns · `,(0,$.jsx)(`b`,{children:ft(e,t,n,r)})]})}function mt({open:e,onOpen:t}){return(0,$.jsx)(`button`,{className:`lb-colbtn${e?` on`:``}`,onClick:t,children:`View`})}function ht({cols:e,group:t,times:n,toggleWind:r}){if(t){let e=t.units.length>1;return(0,$.jsxs)(`div`,{className:`lb-cols${e?` u`:``}`,style:ct(t),children:[(0,$.jsx)(`span`,{children:n.head}),e&&(0,$.jsx)(`span`,{className:`un`,children:`Unit`}),t.metrics.map(e=>(0,$.jsxs)(`span`,{children:[e.head,(0,$.jsx)(gt,{of:e.unit})]},e.key))]})}return(0,$.jsx)(`div`,{className:`lb-cols`,style:lt(e),children:e.map(e=>e.tappable?(0,$.jsxs)(`span`,{className:`tap${ut(e)}`,onClick:r,title:`Tap: knots ⇄ Beaufort`,children:[e.head,(0,$.jsx)(gt,{of:e.unit})]},e.key):(0,$.jsxs)(`span`,{className:ut(e).trim(),children:[e.key===`ts`?n.head:e.head,(0,$.jsx)(gt,{of:e.unit})]},e.key))})}function gt({of:e}){return(0,$.jsx)(`b`,{className:`lb-u`,children:e===void 0||e===``?`\xA0`:e})}function _t({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}=De(a),N=ue(O),P=(0,F.useMemo)(()=>Tt(O,N,!1),[O,N]),{groups:I,group:L,drawn:R,pick:z,btn:B,block:V,cls:H}=at(O,e,m,g,b,c,d,f,p);return(0,$.jsxs)($.Fragment,{children:[(0,$.jsxs)(`div`,{className:`lb-ctrl${H}`,style:V,children:[(0,$.jsx)(Ye,{mode:t,setMode:n}),(0,$.jsx)(Xe,{leaving:u,children:(0,$.jsx)(`div`,{className:`seg`,children:Ke.map(e=>(0,$.jsx)(`button`,{className:r===e?`on`:``,onClick:()=>i(e),children:e},e))})}),(0,$.jsx)(Ze,{groups:I,family:o,setFamily:s}),(0,$.jsxs)(`div`,{className:`lb-acts`,children:[(0,$.jsx)(pt,{shown:B.shown,chosen:B.chosen,kept:B.kept,offered:B.offered,open:_,onOpen:()=>v(!_)}),(0,$.jsx)(mt,{open:x,onOpen:()=>S(!x)})]})]}),(0,$.jsx)(Be,{open:_,style:V,cls:H,children:(0,$.jsx)(Se,{cols:z,applied:g,onApply:y,onCancel:()=>v(!1)})}),(0,$.jsx)(Be,{open:x,style:V,cls:H,children:(0,$.jsx)(ze,{initial:C??void 0,onView:w,onSave:T})}),E&&(0,$.jsx)(`div`,{className:`lb-err`,children:E}),D&&(0,$.jsx)(`div`,{className:`lb-note`,children:D}),(0,$.jsxs)(`div`,{className:`lb-frame${P?` dated`:``}${N.mixed?` mixed`:``}${H}${l?` leaving`:``}`,style:V,children:[(0,$.jsx)(xt,{book:e,window:qe[a],interval:we[a],times:N}),(0,$.jsxs)(`div`,{className:`lb-day`,style:lt(R),children:[(0,$.jsx)(`span`,{children:qe[a]}),(0,$.jsx)(`b`,{children:O.length})]}),(0,$.jsx)(ht,{cols:R,group:L,times:N,toggleWind:h}),k&&(0,$.jsx)(`div`,{className:`lb-err`,children:k}),!A&&!k&&O.length===0?(0,$.jsx)(rt,{what:`Nothing was logged in this window (${qe[a].toLowerCase()}).`}):(0,$.jsx)(Et,{snaps:O,cols:R,group:L,times:N,lined:P,footer:j?(0,$.jsx)(`button`,{className:`lb-more`,onClick:M,disabled:A,children:A?`Loading…`:`Load ${Te[a]} more`}):null}),(0,$.jsx)(St,{})]})]})}function vt({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:C,saveNote:T}){let{dateStr:E,setDateStr:D,isToday:O,snaps:k,err:A,busy:j,prevDay:M,nextDay:N,goToday:P}=Oe(),I=ue(k),L=(0,F.useMemo)(()=>Tt(k,I,`turns`),[k,I]),{groups:R,group:z,drawn:B,pick:V,btn:H,block:U,cls:W}=at(k,e,d,p,_,a,c,l,u),G=`${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,``)} · UTC day`;return(0,$.jsxs)($.Fragment,{children:[(0,$.jsxs)(`div`,{className:`lb-ctrl${W}`,style:U,children:[(0,$.jsx)(Ye,{mode:t,setMode:n}),(0,$.jsx)(Xe,{leaving:s,children:(0,$.jsxs)(`div`,{className:`lb-date`,children:[(0,$.jsx)(`button`,{className:`lb-step`,onClick:M,"aria-label":`Previous day`,children:`‹`}),(0,$.jsx)(`input`,{type:`date`,className:`dt`,value:E,max:w(),onChange:e=>D(e.target.value)}),(0,$.jsx)(`button`,{className:`lb-step`,onClick:N,disabled:O,"aria-label":`Next day`,children:`›`}),(0,$.jsx)(`button`,{onClick:P,disabled:O,children:`Now`})]})}),(0,$.jsx)(Ze,{groups:R,family:r,setFamily:i}),(0,$.jsxs)(`div`,{className:`lb-acts`,children:[(0,$.jsx)(pt,{shown:H.shown,chosen:H.chosen,kept:H.kept,offered:H.offered,open:m,onOpen:()=>h(!m)}),(0,$.jsx)(mt,{open:v,onOpen:()=>y(!v)})]})]}),(0,$.jsx)(Be,{open:m,style:U,cls:W,children:(0,$.jsx)(Se,{cols:V,applied:p,onApply:g,onCancel:()=>h(!1)})}),(0,$.jsx)(Be,{open:v,style:U,cls:W,children:(0,$.jsx)(ze,{initial:b??void 0,onView:x,onSave:S})}),C&&(0,$.jsx)(`div`,{className:`lb-err`,children:C}),T&&(0,$.jsx)(`div`,{className:`lb-note`,children:T}),(0,$.jsxs)(`div`,{className:`lb-frame${L?` dated`:``}${I.mixed?` mixed`:``}${W}${o?` leaving`:``}`,style:U,children:[(0,$.jsx)(xt,{book:e,window:G,interval:we[`1h`],times:I}),(0,$.jsxs)(`div`,{className:`lb-day`,style:lt(B),children:[(0,$.jsx)(`span`,{children:G}),(0,$.jsx)(`b`,{children:k.length})]}),(0,$.jsx)(ht,{cols:B,group:z,times:I,toggleWind:f}),A&&(0,$.jsx)(`div`,{className:`lb-err`,children:A}),!j&&k.length===0?(0,$.jsx)(rt,{what:`No telemetry was logged for this day.`}):(0,$.jsx)(Et,{snaps:k,cols:B,group:z,times:I,footer:null,lined:L}),(0,$.jsx)(St,{})]})]})}function yt(){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 bt={bridge:`CHIEF OFFICER`,engine:`CHIEF ENGINEER`};function xt({book:e,window:t,interval:n,times:r}){let i=r.mixed?`SHIP'S TIME · OFFSET ON EACH DATED LINE`:r.head===`UTC`?`UTC`:`SHIP'S TIME · ${r.head}`;return(0,$.jsxs)(`div`,{className:`lb-print-hd`,children:[(0,$.jsxs)(`div`,{className:`ph-id`,children:[(0,$.jsxs)(`span`,{className:`sp-lockup`,children:[(0,$.jsx)(C,{className:`sp-glyph`}),(0,$.jsx)(`span`,{className:`ph-wm`,children:`Siparu`})]}),(0,$.jsxs)(`span`,{className:`ph-book`,children:[`LOGBOOK · `,(0,$.jsx)(`b`,{children:e.toUpperCase()}),` · `,bt[e]]})]}),(0,$.jsxs)(`div`,{className:`ph-meta`,children:[(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`span`,{className:`l`,children:`GENERATED`}),(0,$.jsx)(`b`,{children:yt()})]}),(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`span`,{className:`l`,children:`WINDOW`}),(0,$.jsx)(`b`,{children:`${t} · ${n}`.toUpperCase()})]}),(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`span`,{className:`l`,children:`CLOCK`}),(0,$.jsx)(`b`,{children:i})]})]})]})}function St(){return(0,$.jsx)(`div`,{className:`lb-print-ft`,children:(0,$.jsx)(`span`,{children:`siparu.app`})})}function Ct(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)} · UTC day`:`${n(e,!1)} - ${n(t,!0)} · UTC days`}function wt({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:C,saveNote:T,printing:D,donePrinting:k}){let A={from:w(),to:w(),gran:`1h`,format:`csv`,stats:[`last`],distance:!1,samples:!1,style:`screen`},j=t??A,M=j.stats[0]??`last`,{snaps:N,err:P,busy:I,truncated:L,loaded:R,minutesFrom:z,plain:B}=je(j.from,j.to,j.gran,M),V=(0,F.useMemo)(()=>et([...N].sort((e,t)=>e.ts-t.ts),B),[N,B]),H=ue(V),{groups:U,group:ee,drawn:K,pick:te,btn:ne,block:q,cls:J}=at(V,e,d,p,_,a,c,l,u,!1),re=(0,F.useCallback)(()=>k(),[k]);(0,F.useEffect)(()=>{!D||!R||I||(re(),O(E(`Siparu-Logbook`,Date.now()),j.style))},[D,R,I,re,j.style]);let Y=Ct(j.from,j.to),X=dt(j.gran,M),Z=(0,F.useMemo)(()=>{if(B.length===0)return[];let t=t=>me(G(W(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,$.jsxs)($.Fragment,{children:[(0,$.jsxs)(`div`,{className:`lb-ctrl${J}`,style:q,children:[(0,$.jsx)(Ye,{mode:`range`,setMode:n}),(0,$.jsx)(Xe,{leaving:s,children:(0,$.jsxs)(`button`,{className:`lb-colbtn on`,onClick:()=>y(!v),children:[Y,` · `,(0,$.jsx)(`b`,{children:X})]})}),(0,$.jsx)(Ze,{groups:U,family:r,setFamily:i}),(0,$.jsx)(`div`,{className:`lb-acts`,children:(0,$.jsx)(pt,{shown:ne.shown,chosen:ne.chosen,kept:ne.kept,offered:ne.offered,open:m,onOpen:()=>h(!m)})})]}),(0,$.jsx)(Be,{open:m,style:q,cls:J,children:(0,$.jsx)(Se,{cols:te,applied:p,onApply:g,onCancel:()=>h(!1)})}),(0,$.jsx)(Be,{open:v,style:q,cls:J,children:(0,$.jsx)(ze,{initial:b??void 0,onView:x,onSave:S})}),C&&(0,$.jsx)(`div`,{className:`lb-err`,children:C}),T&&(0,$.jsx)(`div`,{className:`lb-note`,children:T}),(0,$.jsxs)(`div`,{className:`lb-frame dated${H.mixed?` mixed`:``}${J}${o?` leaving`:``}`,style:q,children:[(0,$.jsx)(xt,{book:e,window:Y,interval:X,times:H}),(0,$.jsxs)(`div`,{className:`lb-day`,style:lt(K),children:[(0,$.jsxs)(`span`,{children:[Y,` · `,X]}),(0,$.jsx)(`b`,{children:L?`${N.length} of more`:N.length})]}),(0,$.jsx)(ht,{cols:K,group:ee,times:H,toggleWind:f}),P&&(0,$.jsx)(`div`,{className:`lb-err`,children:P}),Z.length>0&&(0,$.jsx)(`div`,{className:`lb-note`,children:$e(M,Z,N)}),L&&(0,$.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.`]}),Qe(z,j,N)&&(0,$.jsx)(`div`,{className:`lb-note`,children:Qe(z,j,N)}),!I&&V.length===0?(0,$.jsx)(rt,{what:tt(j)}):(0,$.jsx)(Et,{snaps:V,cols:K,group:ee,times:H,footer:null,lined:!0}),(0,$.jsx)(St,{})]})]})}function Tt(e,t,n){return n===!0?!0:n===!1&&!t.mixed?!1:new Set(e.map(e=>t.dayLine(e.ts))).size>1}function Et({snaps:e,cols:t,group:n,times:r,footer:i,lined:a}){let o=[],s=null;for(let i of e){if(a){let e=r.dayLine(i.ts);e!==s&&(o.push((0,$.jsx)(Dt,{day:r.day(i.ts),offset:r.offset(i.ts),cols:t,group:n},`sep-${i.ts}`)),s=e)}o.push(n?(0,$.jsx)(Ot,{s:i,group:n,times:r},i.ts):(0,$.jsx)(kt,{s:i,cols:t,times:r},i.ts))}return(0,$.jsxs)(`div`,{className:`lb-rows`,style:n?ct(n):lt(t),children:[o,i]})}function Dt({day:e,offset:t,cols:n,group:r}){let i=r!==null&&r.units.length>1,a=r?r.metrics.map(e=>({key:e.key,head:e.head,unit:e.unit,cls:``})):n.slice(1).map(e=>({key:e.key,head:e.head,unit:e.unit,cls:ut(e).trim()}));return(0,$.jsxs)(`div`,{className:`lb-sep${i?` u`:``}`,children:[(0,$.jsxs)(`span`,{className:`sd`,children:[e,t&&(0,$.jsxs)(`span`,{className:`so`,children:[` · `,t]})]}),i&&(0,$.jsx)(`span`,{className:`sh`}),a.map(e=>(0,$.jsxs)(`span`,{className:e.cls?`sh ${e.cls}`:`sh`,children:[e.head,(0,$.jsx)(gt,{of:e.unit})]},e.key))]})}function Ot({s:e,group:t,times:n}){let r=t.units.length>1;return(0,$.jsx)($.Fragment,{children:t.units.map((i,a)=>(0,$.jsxs)(`div`,{className:`lb-row${r?` u`:``}${a>0?` cont`:``}`,children:[(0,$.jsx)(`span`,{className:`tm`,children:n.clock(e.ts)}),r&&(0,$.jsx)(`span`,{className:`un`,children:i.head}),t.metrics.map(t=>(0,$.jsx)(`span`,{className:`v`,children:te(e,i,t)},t.key))]},i.key))})}function kt({s:e,cols:t,times:n}){return(0,$.jsx)(`div`,{className:`lb-row`,children:t.map((t,r)=>(0,$.jsx)(`span`,{className:r===0?`tm`:`${t.dim?`v dim`:`v`}${ut(t)}`,children:t.key===`ts`?n.clock(e.ts):t.cell(e)},t.key))})}var At=[{to:`/logbook/bridge`,name:`Bridge`,keeper:`Chief officer`,holds:`Position, course and speed, the weather she ran in and the water under her.`,Icon:g},{to:`/logbook/engine`,name:`Engine`,keeper:`Chief engineer`,holds:`Engines, generators and tanks, as she reported them.`,Icon:b}];function jt(){let e=v();return(0,$.jsx)(`div`,{className:`lb-door`,children:(0,$.jsx)(`div`,{className:`lbd-cards`,children:At.map(({to:t,name:n,keeper:r,holds:i,Icon:a})=>(0,$.jsxs)(d,{to:e(t),replace:!0,className:`lbd-card`,children:[(0,$.jsx)(a,{size:42}),(0,$.jsx)(`span`,{className:`lbd-n`,children:n}),(0,$.jsx)(`span`,{className:`lbd-k`,children:r}),(0,$.jsx)(`span`,{className:`lbd-h`,children:i}),(0,$.jsxs)(`span`,{className:`lbd-go`,children:[`Open `,(0,$.jsx)(x,{size:15})]})]},t))})})}function Mt({book:e}){return e?(0,$.jsx)(Je,{book:e}):(0,$.jsx)(jt,{})}export{Mt as default};
2
+ 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,F as c,I as l,O as u,P as d,T as f,_ as p,c as m,d as h,j as g,k as _,l as v,o as y,p as b,s as x,v as S,w as C,x as w,y as T}from"./index-DPNvHdqr.js";import{a as E,d as D,i as O,l as k,n as A,o as j,r as M,t as N,u as P}from"./export-CGdOfqNp.js";var F=e(t(),1),I={key:`ts`,head:`UTC`,book:`bridge`,cell:()=>``};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=>w(u(e.sog),1)},has:e=>e.sog!==null},{col:{key:`cog`,head:`COG`,book:`bridge`,cell:e=>{let t=o(e.cog);return t===null?`·`:Math.round(t)+`°`}},has:e=>e.cog!==null},{col:{key:`hdg`,head:`HDG`,book:`bridge`,cell:e=>{let t=o(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=f(t.wind_speed_true);return n===null?`·`:String(e===`kn`?Math.round(n):C(n)??`·`)}},has:e=>e.wind_speed_true!==null},{col:{key:`awa`,head:`AWA`,book:`bridge`,cell:e=>{let t=o(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=s(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=>w(a(e.air_temp_k),1)},has:e=>e.air_temp_k!==null},{col:{key:`sea`,head:`SEA`,book:`bridge`,unit:`°C`,cell:e=>w(a(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 U(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=_(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:g(e),cell:t=>{let n=t.path_values?.[e];if(typeof n!=`number`)return`·`;let r=i(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 W(e,t){return[I,...[...R(t),...U(e)].filter(t=>e.some(t.has)).map(e=>e.col)]}function G(e,t){return e.filter(e=>e.key===`ts`||e.book===t)}var ee={engine:`ENGINES`,generator:`GENERATORS`,tanks:`TANKS`};function K(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=_(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)})),i=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?``:g(n)}});return{tab:e,head:ee[e]??e.toUpperCase(),units:t,metrics:i}})}function te(e,t,n){let r=t.paths[n.key];if(r===void 0)return`·`;let a=e.path_values?.[r];if(typeof a!=`number`)return`·`;let o=i(r,a).value;return Number.isFinite(o)?Number.isInteger(o)?String(o):o.toFixed(1):`·`}function ne(e,t){let[n,r]=(0,F.useState)(e),[i,a]=(0,F.useState)(!1);return(0,F.useEffect)(()=>{if(e===n)return;a(!0);let i=setTimeout(()=>{r(e),a(!1)},t);return()=>clearTimeout(i)},[e,n,t]),[n,i]}var q=new Map;function J(e,t,n){let r=`${t}|${e}`,i=q.get(r);if(i===void 0){try{i=new Intl.DateTimeFormat(`en-GB`,{...n,timeZone:e})}catch{i=null}q.set(r,i)}return i}var re={hour:`2-digit`,minute:`2-digit`,hourCycle:`h23`},Y={hour:`2-digit`,timeZoneName:`shortOffset`},X={weekday:`short`,day:`2-digit`,month:`short`,year:`numeric`},Z=e=>e!==null&&J(e,`clock`,re)&&J(e,`offset`,Y)&&J(e,`day`,X)?e:`UTC`;function ie(e,t){return J(Z(t),`clock`,re).format(e)}function ae(e,t){let n=J(Z(t),`offset`,Y);if(!n)return`UTC`;let r=(n.formatToParts(e).find(e=>e.type===`timeZoneName`)?.value??``).replace(/^(GMT|UTC)/,``);return/[1-9]/.test(r)?`UTC${r}`:`UTC`}function oe(e,t){return J(Z(t),`day`,X).format(e).replace(/,\s*/,` · `).toUpperCase()}function se(e,t){let n=new Map,r=new Map,i=(e,n)=>{let i=`${Math.round(e*10)},${Math.round(n*10)}`,a=r.get(i);if(a===void 0){try{a=t(e,n)}catch{a=null}r.set(i,a)}return a},a=[...e].sort((e,t)=>e.ts-t.ts),o=a.map(e=>t!==null&&e.lat!==null&&e.lon!==null?i(e.lat,e.lon):null),s=a.flatMap((e,t)=>o[t]===null?[]:[{ts:e.ts,zone:o[t]}]),c=0;return a.forEach((e,t)=>{if(o[t]!==null||s.length===0){n.set(e.ts,o[t]);return}for(;c+1<s.length&&Math.abs(s[c+1].ts-e.ts)<Math.abs(s[c].ts-e.ts);)c++;n.set(e.ts,s[c].zone)}),n}function ce(e,t){let n=se(e,t),r=e=>n.get(e)??null,i=new Set(e.map(e=>ae(e.ts,r(e.ts)))),a=i.size>1;return{head:a?`SHIP`:i.values().next().value??`UTC`,mixed:a,clock:e=>ie(e,r(e)),day:e=>oe(e,r(e)),offset:e=>ae(e,r(e)),dayLine:e=>`${oe(e,r(e))} · ${ae(e,r(e))}`}}var le=null;function ue(t){let[n,r]=(0,F.useState)(()=>le);return(0,F.useEffect)(()=>{let t=!0;return l(()=>import(`./tz-DxphYhHy.js`).then(t=>e(t.default,1)).then(e=>{le=e.default,t&&r(()=>e.default)}),__vite__mapDeps([0,1]),import.meta.url).catch(()=>{}),()=>{t=!1}},[]),(0,F.useMemo)(()=>ce(t,n),[t,n])}var de=e=>`lb:columns:${e}`,fe={off:[]};function pe(e){return e.key===`ts`}function Q(e,t){return pe(e)||!t.off.includes(e.key)}function me(e,t){return e.filter(e=>Q(e,t))}function he(e){return e.filter(e=>!pe(e))}function ge(e,t){return pe(t)?e:{off:Q(t,e)?[...e.off,t.key]:e.off.filter(e=>e!==t.key)}}function _e(e,t){return{off:t?[]:he(e).map(e=>e.key)}}function ve(e,t){return xe(e.off)===xe(t.off)}function ye(e){try{let t=globalThis.localStorage?.getItem(de(e));if(!t)return fe;let n=JSON.parse(t);return{off:Array.isArray(n.off)?n.off.filter(e=>typeof e==`string`):[]}}catch{return fe}}function be(e,t){try{globalThis.localStorage?.setItem(de(e),JSON.stringify(t))}catch{}}function xe(e){return[...new Set(e)].sort().join(` `)}var $=n();function Se({cols:e,applied:t,onApply:n,onCancel:r}){let[i,a]=(0,F.useState)(t),o=ve(i,t),s=he(e),c=s.filter(e=>Q(e,i)).length;return(0,$.jsxs)(`div`,{className:`lb-pick`,children:[(0,$.jsxs)(`div`,{className:`lbp-book`,children:[(0,$.jsxs)(`div`,{className:`lbp-h`,children:[(0,$.jsx)(`span`,{className:`lbp-n`,children:`Columns`}),(0,$.jsxs)(`span`,{className:`lbp-s`,children:[c,` of `,s.length]}),(0,$.jsxs)(`span`,{className:`lbp-all`,children:[(0,$.jsx)(`button`,{onClick:()=>a(_e(e,!0)),children:`All`}),(0,$.jsx)(`button`,{onClick:()=>a(_e(e,!1)),children:`None`})]})]}),(0,$.jsx)(`div`,{className:`lbp-chips`,children:s.map(e=>(0,$.jsx)(`button`,{className:`lbp-c${Q(e,i)?` on`:``}`,"aria-pressed":Q(e,i),onClick:()=>a(ge(i,e)),children:e.head},e.key))})]}),(0,$.jsxs)(`div`,{className:`lbp-act`,children:[(0,$.jsx)(`button`,{onClick:r,children:`Cancel`}),(0,$.jsx)(`button`,{className:`lbp-go`,disabled:o,onClick:()=>n(i),children:`Show these columns`})]})]})}var Ce={"1m":1,"1h":60,"6h":360,"1d":1440},we={"1m":`Every minute`,"1h":`Hourly`,"6h":`Six-hourly`,"1d":`Daily`},Te={"1m":60,"1h":48,"6h":40,"1d":30},Ee=15e3;function De(e){let t=d(),[n,i]=(0,F.useState)(0),[a,o]=(0,F.useState)(!1),[s,c]=(0,F.useState)([]),[l,u]=(0,F.useState)(null),[f,p]=(0,F.useState)(!1),[m,h]=(0,F.useState)(e);m!==e&&(h(e),i(0),o(!1));let g=Ce[e],_=Te[e]+n*Te[e],v=(0,F.useCallback)(async()=>{p(!0),u(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>_),c(n.slice(0,_))}catch(e){u(e.message)}finally{p(!1)}},[g,_]);return(0,F.useEffect)(()=>{v();let e=r(v,Ee);return()=>e()},[v]),{snaps:s,err:l,busy:f,hasMore:a,loadMore:(0,F.useCallback)(()=>i(e=>e+1),[])}}function Oe(){let e=d(),[t,n]=(0,F.useState)(T()),[i,a]=(0,F.useState)([]),[o,s]=(0,F.useState)(null),[c,l]=(0,F.useState)(!1),u=(0,F.useRef)(!0),f=b(6e4),p=T(new Date(f)),m=(0,F.useMemo)(()=>S(t),[t]),h=m+24*36e5,g=t===p;(0,F.useEffect)(()=>{u.current&&t!==p&&n(p)},[p,t]);let _=(0,F.useCallback)(e=>{u.current=e===T(),n(e)},[]),v=(0,F.useCallback)(async()=>{l(!0),s(null);try{let t=await e.logbook.snapshots({from:m,to:h,limit:5e3,order:`desc`,bucket:60});a(t)}catch(e){s(e.message)}finally{l(!1)}},[m,h]);return(0,F.useEffect)(()=>{if(v(),!g)return;let e=r(v,Ee);return()=>e()},[v,g]),{dateStr:t,setDateStr:_,dayStart:m,isToday:g,snaps:i,err:o,busy:c,prevDay:(0,F.useCallback)(()=>_(T(new Date(m-864e5))),[m,_]),nextDay:(0,F.useCallback)(()=>_(T(new Date(m+864e5))),[m,_]),goToday:(0,F.useCallback)(()=>_(T()),[_])}}var ke=2e3;async function Ae(e,t,n){let r=await e({from:t,to:n,limit:2001,order:`desc`});return{rows:r.rows.slice(0,ke),truncated:r.clamped||r.rows.length>2e3,minutesFrom:r.minutesFrom}}function je(e,t,n,r=`last`){let i=d(),[a,o]=(0,F.useState)([]),[s,c]=(0,F.useState)(null),[l,u]=(0,F.useState)(!1),[f,p]=(0,F.useState)(!1),[m,h]=(0,F.useState)(!1),[g,_]=(0,F.useState)(null),[v,y]=(0,F.useState)([]),b=(0,F.useMemo)(()=>S(e),[e]),x=(0,F.useMemo)(()=>S(t)+864e5-1,[t]),C=Ce[n],w=(0,F.useCallback)(async()=>{if(Number.isNaN(b)||Number.isNaN(x)||x<b){o([]),y([]),p(!1),c(null),h(!0);return}h(!1),u(!0),c(null);try{if(y([]),C===1){let e=await Ae(e=>i.logbook.minutes(e),b,x);_(e.minutesFrom),p(e.truncated),o(e.rows);return}let e=await i.logbook.rollupHours(b,x);_(null);let t=P(e,n),a=t.map(e=>D(e,r)),s=e=>e.slice(Math.max(0,e.length-ke));p(a.length>ke),o(s(a)),r!==`last`&&y(s(t.map(e=>D(e,`last`))))}catch(e){c(e.message)}finally{u(!1),h(!0)}},[b,x,C,n,r]);return(0,F.useEffect)(()=>{w()},[w]),{snaps:a,err:s,busy:l,truncated:f,loaded:m,minutesFrom:g,plain:v}}var Me=[`1m`,`1h`,`6h`,`1d`],Ne=[{v:`csv`,name:`CSV`,note:`a file for a spreadsheet`},{v:`pdf`,name:`PDF`,note:`the page, through your printer`}],Pe=[{v:`screen`,name:`Screen`},{v:`paper`,name:`Paper`}],Fe=[`last`,`avg`,`min`,`max`];function Ie(e,t,n){return e===`pdf`?[n]:t.includes(n)?t.filter(e=>e!==n):[...t,n]}function Le(e,t){return e===`pdf`?[t[0]??`last`]:t}function Re(){return T(new Date(Date.now()-7*864e5))}function ze({initial:e,onView:t,onSave:n}){let[r,i]=(0,F.useState)(e?.from??Re()),[a,o]=(0,F.useState)(e?.to??T()),[s,c]=(0,F.useState)(e?.gran??`1h`),[l,u]=(0,F.useState)(e?.format??`csv`),[d,f]=(0,F.useState)(e?.stats??[`last`]),[p,m]=(0,F.useState)(e?.distance??!1),[h,g]=(0,F.useState)(e?.samples??!1),[_,v]=(0,F.useState)(e?.style??`screen`),y=a<r,b={from:r,to:a,gran:s,format:l,stats:Le(l,d),distance:p,samples:h,style:_},x=s!==`1m`,S=l===`pdf`,C=x&&!S&&d.length===0&&!p&&!h,w=e=>f(t=>Ie(l,t,e)),E=Le(l,d),D=e=>E.includes(e);return(0,$.jsxs)(`div`,{className:`lb-pick lb-exp`,children:[(0,$.jsxs)(`div`,{className:`lbp-books`,children:[(0,$.jsxs)(`div`,{className:`lbp-book`,children:[(0,$.jsxs)(`div`,{className:`lbp-h`,children:[(0,$.jsx)(`span`,{className:`lbp-n`,children:`Window`}),(0,$.jsx)(`span`,{className:`lbp-s`,children:`UTC days, both ends included`})]}),(0,$.jsxs)(`div`,{className:`lbe-dates`,children:[(0,$.jsxs)(`label`,{children:[(0,$.jsx)(`span`,{children:`From`}),(0,$.jsx)(`input`,{type:`date`,className:`dt`,value:r,max:T(),onChange:e=>i(e.target.value)})]}),(0,$.jsxs)(`label`,{children:[(0,$.jsx)(`span`,{children:`To`}),(0,$.jsx)(`input`,{type:`date`,className:`dt`,value:a,max:T(),onChange:e=>o(e.target.value)})]})]})]}),(0,$.jsxs)(`div`,{className:`lbp-book`,children:[(0,$.jsxs)(`div`,{className:`lbp-h`,children:[(0,$.jsx)(`span`,{className:`lbp-n`,children:`Interval`}),(0,$.jsx)(`span`,{className:`lbp-s`,children:`one row per`})]}),(0,$.jsx)(`div`,{className:`lbp-chips`,children:Me.map(e=>(0,$.jsx)(`button`,{className:`lbp-c${s===e?` on`:``}`,onClick:()=>c(e),children:we[e]},e))})]}),(0,$.jsxs)(`div`,{className:`lbp-book`,children:[(0,$.jsxs)(`div`,{className:`lbp-h`,children:[(0,$.jsx)(`span`,{className:`lbp-n`,children:`Save as`}),(0,$.jsx)(`span`,{className:`lbp-s`,children:Ne.find(e=>e.v===l)?.note})]}),(0,$.jsx)(`div`,{className:`lbp-chips`,children:Ne.map(e=>(0,$.jsx)(`button`,{className:`lbp-c${l===e.v?` on`:``}`,onClick:()=>u(e.v),children:e.name},e.v))})]}),(0,$.jsxs)(`div`,{className:`lbp-book${S?``:` off`}`,children:[(0,$.jsxs)(`div`,{className:`lbp-h`,children:[(0,$.jsx)(`span`,{className:`lbp-n`,children:`Style`}),(0,$.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,$.jsx)(`div`,{className:`lbp-chips`,children:Pe.map(e=>(0,$.jsx)(`button`,{className:`lbp-c${_===e.v?` on`:``}`,disabled:!S,onClick:()=>v(e.v),children:e.name},e.v))})]})]}),x&&(0,$.jsxs)(`div`,{className:`lbp-book lbp-figs`,children:[(0,$.jsxs)(`div`,{className:`lbp-h`,children:[(0,$.jsx)(`span`,{className:`lbp-n`,children:`Figures`}),(0,$.jsx)(`span`,{className:`lbp-s`,children:S?`one: a table cell holds one number`:`one or more: a column each`})]}),(0,$.jsxs)(`div`,{className:`lbp-chips`,children:[Fe.map(e=>(0,$.jsx)(`button`,{className:`lbp-c${D(e)?` on`:``}`,onClick:()=>w(e),children:k[e]},e)),(0,$.jsx)(`button`,{className:`lbp-c${p?` on`:``}`,disabled:S,onClick:()=>m(!p),children:`Distance`}),(0,$.jsx)(`button`,{className:`lbp-c${h?` on`:``}`,disabled:S,onClick:()=>g(!h),children:`Samples`})]})]}),y&&(0,$.jsx)(`div`,{className:`lbe-warn`,children:`That window ends before it begins.`}),C&&(0,$.jsx)(`div`,{className:`lbe-warn`,children:`Choose at least one figure to put in the file.`}),(0,$.jsxs)(`div`,{className:`lbp-act`,children:[S&&(0,$.jsx)(`button`,{disabled:y,onClick:()=>t(b),children:`View`}),(0,$.jsxs)(`button`,{className:`lbp-go`,disabled:y||C,onClick:()=>n(b),children:[`Save `,l.toUpperCase()]})]})]})}function Be({open:e,style:t,cls:n,children:r}){let[i,a]=(0,F.useState)(e);return e&&!i&&a(!0),i?(0,$.jsx)(`div`,{className:`lb-open${e?``:` shut`}${n??``}`,style:t,onAnimationEnd:t=>{t.target===t.currentTarget&&!e&&a(!1)},children:(0,$.jsx)(`div`,{className:`lb-open-in`,children:r})}):null}function Ve(e){let t=e-32-58;return Math.max(1,Math.floor(t/52))}function He(e){return e.reduce((e,t)=>e+(t.lanes??1),0)}function Ue(e,t){if(t===null||e.length===0)return e;let n=Ve(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 We(e,t,n){return t===null?e:e.slice(0,Ve(t-(n?108:0)))}var Ge=180,Ke=[`1m`,`1h`,`6h`,`1d`],qe={"1m":`Last hour`,"1h":`Last 2 days`,"6h":`Last 10 days`,"1d":`Last month`};function Je({book:e}){let t=d(),[n,r]=(0,F.useState)(`live`),[i,a]=(0,F.useState)(`1h`),[o,s]=(0,F.useState)(null),[c,l]=ne((0,F.useMemo)(()=>({book:e,mode:n,gran:i,family:o}),[e,n,i,o]),Ge),{book:u,mode:f,gran:p,family:m}=c,g=n!==f,[_,v]=(0,F.useState)(()=>localStorage.getItem(`lb:windUnit`)||`kn`),y=()=>v(e=>{let t=e===`kn`?`bft`:`kn`;return localStorage.setItem(`lb:windUnit`,t),t}),b=(0,F.useRef)([]),x=(0,F.useRef)(0),[C,w]=(0,F.useState)(()=>ye(u)),[T,E]=(0,F.useState)(u);T!==u&&(E(u),w(ye(u)),b.current=[],x.current=0);let[O,k]=(0,F.useState)(!1),I=e=>{w(e),be(u,e),k(!1)},[L,R]=h(),z=(0,F.useRef)(null),B=(0,F.useRef)(null),V=(0,F.useCallback)(e=>{B.current=e,L(e)},[L]),H=(0,F.useRef)(null);(0,F.useLayoutEffect)(()=>{let e=B.current;if(!e)return;let t=()=>e.querySelectorAll(`.lb-ctrl, .lb-pick, .lb-frame`);if(l){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)`})})},[l,c]);let[U,ee]=(0,F.useState)(!1),[K,te]=(0,F.useState)(null),[q,J]=(0,F.useState)(!1),[re,Y]=(0,F.useState)(null),[X,Z]=(0,F.useState)(null),ie={book:u,mode:n,setMode:r,gran:i,setGran:a,shownGran:p,family:o,setFamily:s,shownFamily:m,leaving:l,modeLeaving:g,lanesHold:z,groupsHold:b,chosenHold:x,windUnit:_,toggleWind:y,selection:C,picking:O,setPicking:k,applySelection:I,width:R,exporting:U,setExporting:ee,request:K,onView:e=>{te(e),r(`range`),Y(null)},onSave:async e=>{if(ee(!1),Y(null),Z(null),e.format===`pdf`){te(e),r(`range`),J(!0);return}try{let n=S(e.from),r=S(e.to)+864e5-1;if(e.gran===`1m`){let{rows:e,truncated:i}=await Ae(e=>t.logbook.minutes(e),n,r),a=me(G(W(e,_),u),C);A(M(`logbook-${u}`,n,`csv`,i?`-partial`:``),`text/csv`,j(e,a)),i&&Z(`That window holds more than ${ke} minutes, so the file stops at the most recent ${ke} and its name ends in -partial. A longer interval covers the same days in fewer rows.`);return}let i=P(await t.logbook.rollupHours(n,r),e.gran),a=e.stats.map(e=>({stat:e,cols:me(G(W(i.map(t=>D(t,e)),_),u),C)})).filter(e=>e.cols.length>1);A(M(`logbook-${u}`,n,`csv`),`text/csv`,N(i,a,{distance:e.distance,samples:e.samples}))}catch(e){Y(e.message)}},saveErr:re,saveNote:X};return(0,$.jsxs)(`div`,{className:`lb`,ref:V,children:[(0,$.jsx)(`style`,{children:`@media print { @page { size: A4 landscape; } }`}),f===`live`?(0,$.jsx)(_t,{...ie}):f===`day`?(0,$.jsx)(vt,{...ie}):(0,$.jsx)(wt,{...ie,req:K,printing:q,donePrinting:()=>J(!1)})]})}function Ye({mode:e,setMode:t}){return(0,$.jsxs)(`div`,{className:`seg`,children:[(0,$.jsx)(`button`,{className:e===`live`?`on`:``,onClick:()=>t(`live`),children:`Live`}),(0,$.jsx)(`button`,{className:e===`day`?`on`:``,onClick:()=>t(`day`),children:`Day`})]})}function Xe({leaving:e,children:t}){return(0,$.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,$.jsx)(`div`,{className:`seg`,children:e.map(e=>(0,$.jsx)(`button`,{className:e.tab===r.tab?`on`:``,onClick:()=>n(e.tab),children:e.head},e.tab))})}function Qe(e,t,n){if(e===null||S(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 $e(e,t,n){let r=k[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 et(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 tt(e){return S(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 nt(e,t,n){return e.length>1||t===null?lt(e):{"--lb-cols":n.current??Ve(t)}}function rt({what:e}){return(0,$.jsxs)(`div`,{className:`sp-empty`,children:[(0,$.jsx)(`div`,{className:`em-t`,children:`No snapshots`}),(0,$.jsx)(`div`,{className:`em-s`,children:e})]})}var it=(e,t)=>`u:${e}:${t}`;function at(e,t,n,r,i,a,o,s,c,l=!0){let u=st(e,t,n,r,i,a,o,s,c,l);if(t!==`engine`||u.groups.length<2)return u;let d=u,f=ot(u);for(let o of u.groups){if(o===u.group||o.tab===a)continue;let s=st(e,t,n,r,i,o.tab,{current:null},{current:[]},{current:0},l),c=ot(s);c>f&&(d=s,f=c)}return d===u?u:{...u,block:d.block,cls:d.cls}}function ot(e){let t=Math.max(1,Number(e.block[`--lb-cols`]??1));return 58+(e.cls.includes(`u`)?108:0)+t*164}function st(e,t,n,r,i,a,o,s,c,l){let u=t===`engine`?K(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:it(f.tab,e.key),head:e.head})),t=f.metrics.filter(e=>Q({key:it(f.tab,e.key)},r)),n=l?We(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=G(W(e,n),t),m=f?p.filter(e=>e.key===`ts`||e.tab===f.tab):p,h=me(m,r),g=l?Ue(h,i):h;return g.length>1&&(o.current=He(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:nt(g,i,o),cls:``}}function ct(e){return{"--lb-cols":Math.max(1,e.metrics.length)}}function lt(e){return{"--lb-cols":He(e.slice(1))}}function ut(e){return(e.lanes??1)>1?` w${e.lanes}`:``}function dt(e,t){return t===`last`?we[e]:`${we[e]} · ${k[t]}`}function ft(e,t,n,r){return r===0?String(n):e<t?`${e} of ${t}`:String(t)}function pt({shown:e,chosen:t,kept:n,offered:r,open:i,onOpen:a}){let o=r===0;return(0,$.jsxs)(`button`,{className:`lb-colbtn${i?` on`:``}${o?` quiet`:``}`,onClick:a,disabled:o,children:[`Columns · `,(0,$.jsx)(`b`,{children:ft(e,t,n,r)})]})}function mt({open:e,onOpen:t}){return(0,$.jsx)(`button`,{className:`lb-colbtn${e?` on`:``}`,onClick:t,children:`View`})}function ht({cols:e,group:t,times:n,toggleWind:r}){if(t){let e=t.units.length>1;return(0,$.jsxs)(`div`,{className:`lb-cols${e?` u`:``}`,style:ct(t),children:[(0,$.jsx)(`span`,{children:n.head}),e&&(0,$.jsx)(`span`,{className:`un`,children:`Unit`}),t.metrics.map(e=>(0,$.jsxs)(`span`,{children:[e.head,(0,$.jsx)(gt,{of:e.unit})]},e.key))]})}return(0,$.jsx)(`div`,{className:`lb-cols`,style:lt(e),children:e.map(e=>e.tappable?(0,$.jsxs)(`span`,{className:`tap${ut(e)}`,onClick:r,title:`Tap: knots ⇄ Beaufort`,children:[e.head,(0,$.jsx)(gt,{of:e.unit})]},e.key):(0,$.jsxs)(`span`,{className:ut(e).trim(),children:[e.key===`ts`?n.head:e.head,(0,$.jsx)(gt,{of:e.unit})]},e.key))})}function gt({of:e}){return(0,$.jsx)(`b`,{className:`lb-u`,children:e===void 0||e===``?`\xA0`:e})}function _t({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}=De(a),N=ue(O),P=(0,F.useMemo)(()=>Tt(O,N,!1),[O,N]),{groups:I,group:L,drawn:R,pick:z,btn:B,block:V,cls:H}=at(O,e,m,g,b,c,d,f,p);return(0,$.jsxs)($.Fragment,{children:[(0,$.jsxs)(`div`,{className:`lb-ctrl${H}`,style:V,children:[(0,$.jsx)(Ye,{mode:t,setMode:n}),(0,$.jsx)(Xe,{leaving:u,children:(0,$.jsx)(`div`,{className:`seg`,children:Ke.map(e=>(0,$.jsx)(`button`,{className:r===e?`on`:``,onClick:()=>i(e),children:e},e))})}),(0,$.jsx)(Ze,{groups:I,family:o,setFamily:s}),(0,$.jsxs)(`div`,{className:`lb-acts`,children:[(0,$.jsx)(pt,{shown:B.shown,chosen:B.chosen,kept:B.kept,offered:B.offered,open:_,onOpen:()=>v(!_)}),(0,$.jsx)(mt,{open:x,onOpen:()=>S(!x)})]})]}),(0,$.jsx)(Be,{open:_,style:V,cls:H,children:(0,$.jsx)(Se,{cols:z,applied:g,onApply:y,onCancel:()=>v(!1)})}),(0,$.jsx)(Be,{open:x,style:V,cls:H,children:(0,$.jsx)(ze,{initial:C??void 0,onView:w,onSave:T})}),E&&(0,$.jsx)(`div`,{className:`lb-err`,children:E}),D&&(0,$.jsx)(`div`,{className:`lb-note`,children:D}),(0,$.jsxs)(`div`,{className:`lb-frame${P?` dated`:``}${N.mixed?` mixed`:``}${H}${l?` leaving`:``}`,style:V,children:[(0,$.jsx)(xt,{book:e,window:qe[a],interval:we[a],times:N}),(0,$.jsxs)(`div`,{className:`lb-day`,style:lt(R),children:[(0,$.jsx)(`span`,{children:qe[a]}),(0,$.jsx)(`b`,{children:O.length})]}),(0,$.jsx)(ht,{cols:R,group:L,times:N,toggleWind:h}),k&&(0,$.jsx)(`div`,{className:`lb-err`,children:k}),!A&&!k&&O.length===0?(0,$.jsx)(rt,{what:`Nothing was logged in this window (${qe[a].toLowerCase()}).`}):(0,$.jsx)(Et,{snaps:O,cols:R,group:L,times:N,lined:P,footer:j?(0,$.jsx)(`button`,{className:`lb-more`,onClick:M,disabled:A,children:A?`Loading…`:`Load ${Te[a]} more`}):null}),(0,$.jsx)(St,{})]})]})}function vt({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:C,saveNote:w}){let{dateStr:E,setDateStr:D,isToday:O,snaps:k,err:A,busy:j,prevDay:M,nextDay:N,goToday:P}=Oe(),I=ue(k),L=(0,F.useMemo)(()=>Tt(k,I,`turns`),[k,I]),{groups:R,group:z,drawn:B,pick:V,btn:H,block:U,cls:W}=at(k,e,d,p,_,a,c,l,u),G=`${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,``)} · UTC day`;return(0,$.jsxs)($.Fragment,{children:[(0,$.jsxs)(`div`,{className:`lb-ctrl${W}`,style:U,children:[(0,$.jsx)(Ye,{mode:t,setMode:n}),(0,$.jsx)(Xe,{leaving:s,children:(0,$.jsxs)(`div`,{className:`lb-date`,children:[(0,$.jsx)(`button`,{className:`lb-step`,onClick:M,"aria-label":`Previous day`,children:`‹`}),(0,$.jsx)(`input`,{type:`date`,className:`dt`,value:E,max:T(),onChange:e=>D(e.target.value)}),(0,$.jsx)(`button`,{className:`lb-step`,onClick:N,disabled:O,"aria-label":`Next day`,children:`›`}),(0,$.jsx)(`button`,{onClick:P,disabled:O,children:`Now`})]})}),(0,$.jsx)(Ze,{groups:R,family:r,setFamily:i}),(0,$.jsxs)(`div`,{className:`lb-acts`,children:[(0,$.jsx)(pt,{shown:H.shown,chosen:H.chosen,kept:H.kept,offered:H.offered,open:m,onOpen:()=>h(!m)}),(0,$.jsx)(mt,{open:v,onOpen:()=>y(!v)})]})]}),(0,$.jsx)(Be,{open:m,style:U,cls:W,children:(0,$.jsx)(Se,{cols:V,applied:p,onApply:g,onCancel:()=>h(!1)})}),(0,$.jsx)(Be,{open:v,style:U,cls:W,children:(0,$.jsx)(ze,{initial:b??void 0,onView:x,onSave:S})}),C&&(0,$.jsx)(`div`,{className:`lb-err`,children:C}),w&&(0,$.jsx)(`div`,{className:`lb-note`,children:w}),(0,$.jsxs)(`div`,{className:`lb-frame${L?` dated`:``}${I.mixed?` mixed`:``}${W}${o?` leaving`:``}`,style:U,children:[(0,$.jsx)(xt,{book:e,window:G,interval:we[`1h`],times:I}),(0,$.jsxs)(`div`,{className:`lb-day`,style:lt(B),children:[(0,$.jsx)(`span`,{children:G}),(0,$.jsx)(`b`,{children:k.length})]}),(0,$.jsx)(ht,{cols:B,group:z,times:I,toggleWind:f}),A&&(0,$.jsx)(`div`,{className:`lb-err`,children:A}),!j&&k.length===0?(0,$.jsx)(rt,{what:`No telemetry was logged for this day.`}):(0,$.jsx)(Et,{snaps:k,cols:B,group:z,times:I,footer:null,lined:L}),(0,$.jsx)(St,{})]})]})}function yt(){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 bt={bridge:`CHIEF OFFICER`,engine:`CHIEF ENGINEER`};function xt({book:e,window:t,interval:n,times:r}){let i=r.mixed?`SHIP'S TIME · OFFSET ON EACH DATED LINE`:r.head===`UTC`?`UTC`:`SHIP'S TIME · ${r.head}`;return(0,$.jsxs)(`div`,{className:`lb-print-hd`,children:[(0,$.jsxs)(`div`,{className:`ph-id`,children:[(0,$.jsxs)(`span`,{className:`sp-lockup`,children:[(0,$.jsx)(x,{className:`sp-glyph`}),(0,$.jsx)(`span`,{className:`ph-wm`,children:`Siparu`})]}),(0,$.jsxs)(`span`,{className:`ph-book`,children:[`LOGBOOK · `,(0,$.jsx)(`b`,{children:e.toUpperCase()}),` · `,bt[e]]})]}),(0,$.jsxs)(`div`,{className:`ph-meta`,children:[(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`span`,{className:`l`,children:`GENERATED`}),(0,$.jsx)(`b`,{children:yt()})]}),(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`span`,{className:`l`,children:`WINDOW`}),(0,$.jsx)(`b`,{children:`${t} · ${n}`.toUpperCase()})]}),(0,$.jsxs)(`div`,{children:[(0,$.jsx)(`span`,{className:`l`,children:`CLOCK`}),(0,$.jsx)(`b`,{children:i})]})]})]})}function St(){return(0,$.jsx)(`div`,{className:`lb-print-ft`,children:(0,$.jsx)(`span`,{children:`siparu.app`})})}function Ct(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)} · UTC day`:`${n(e,!1)} - ${n(t,!0)} · UTC days`}function wt({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:C,saveNote:w,printing:D,donePrinting:k}){let A={from:T(),to:T(),gran:`1h`,format:`csv`,stats:[`last`],distance:!1,samples:!1,style:`screen`},j=t??A,M=j.stats[0]??`last`,{snaps:N,err:P,busy:I,truncated:L,loaded:R,minutesFrom:z,plain:B}=je(j.from,j.to,j.gran,M),V=(0,F.useMemo)(()=>et([...N].sort((e,t)=>e.ts-t.ts),B),[N,B]),H=ue(V),{groups:U,group:ee,drawn:K,pick:te,btn:ne,block:q,cls:J}=at(V,e,d,p,_,a,c,l,u,!1),re=(0,F.useCallback)(()=>k(),[k]);(0,F.useEffect)(()=>{!D||!R||I||(re(),O(E(`Siparu-Logbook`,Date.now()),j.style))},[D,R,I,re,j.style]);let Y=Ct(j.from,j.to),X=dt(j.gran,M),Z=(0,F.useMemo)(()=>{if(B.length===0)return[];let t=t=>me(G(W(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,$.jsxs)($.Fragment,{children:[(0,$.jsxs)(`div`,{className:`lb-ctrl${J}`,style:q,children:[(0,$.jsx)(Ye,{mode:`range`,setMode:n}),(0,$.jsx)(Xe,{leaving:s,children:(0,$.jsxs)(`button`,{className:`lb-colbtn on`,onClick:()=>y(!v),children:[Y,` · `,(0,$.jsx)(`b`,{children:X})]})}),(0,$.jsx)(Ze,{groups:U,family:r,setFamily:i}),(0,$.jsx)(`div`,{className:`lb-acts`,children:(0,$.jsx)(pt,{shown:ne.shown,chosen:ne.chosen,kept:ne.kept,offered:ne.offered,open:m,onOpen:()=>h(!m)})})]}),(0,$.jsx)(Be,{open:m,style:q,cls:J,children:(0,$.jsx)(Se,{cols:te,applied:p,onApply:g,onCancel:()=>h(!1)})}),(0,$.jsx)(Be,{open:v,style:q,cls:J,children:(0,$.jsx)(ze,{initial:b??void 0,onView:x,onSave:S})}),C&&(0,$.jsx)(`div`,{className:`lb-err`,children:C}),w&&(0,$.jsx)(`div`,{className:`lb-note`,children:w}),(0,$.jsxs)(`div`,{className:`lb-frame dated${H.mixed?` mixed`:``}${J}${o?` leaving`:``}`,style:q,children:[(0,$.jsx)(xt,{book:e,window:Y,interval:X,times:H}),(0,$.jsxs)(`div`,{className:`lb-day`,style:lt(K),children:[(0,$.jsxs)(`span`,{children:[Y,` · `,X]}),(0,$.jsx)(`b`,{children:L?`${N.length} of more`:N.length})]}),(0,$.jsx)(ht,{cols:K,group:ee,times:H,toggleWind:f}),P&&(0,$.jsx)(`div`,{className:`lb-err`,children:P}),Z.length>0&&(0,$.jsx)(`div`,{className:`lb-note`,children:$e(M,Z,N)}),L&&(0,$.jsxs)(`div`,{className:`lb-note`,children:[`This window holds more than `,2e3,` rows. The most recent `,2e3,` are here; a longer interval covers the same days in fewer.`]}),Qe(z,j,N)&&(0,$.jsx)(`div`,{className:`lb-note`,children:Qe(z,j,N)}),!I&&V.length===0?(0,$.jsx)(rt,{what:tt(j)}):(0,$.jsx)(Et,{snaps:V,cols:K,group:ee,times:H,footer:null,lined:!0}),(0,$.jsx)(St,{})]})]})}function Tt(e,t,n){return n===!0?!0:n===!1&&!t.mixed?!1:new Set(e.map(e=>t.dayLine(e.ts))).size>1}function Et({snaps:e,cols:t,group:n,times:r,footer:i,lined:a}){let o=[],s=null;for(let i of e){if(a){let e=r.dayLine(i.ts);e!==s&&(o.push((0,$.jsx)(Dt,{day:r.day(i.ts),offset:r.offset(i.ts),cols:t,group:n},`sep-${i.ts}`)),s=e)}o.push(n?(0,$.jsx)(Ot,{s:i,group:n,times:r},i.ts):(0,$.jsx)(kt,{s:i,cols:t,times:r},i.ts))}return(0,$.jsxs)(`div`,{className:`lb-rows`,style:n?ct(n):lt(t),children:[o,i]})}function Dt({day:e,offset:t,cols:n,group:r}){let i=r!==null&&r.units.length>1,a=r?r.metrics.map(e=>({key:e.key,head:e.head,unit:e.unit,cls:``})):n.slice(1).map(e=>({key:e.key,head:e.head,unit:e.unit,cls:ut(e).trim()}));return(0,$.jsxs)(`div`,{className:`lb-sep${i?` u`:``}`,children:[(0,$.jsxs)(`span`,{className:`sd`,children:[e,t&&(0,$.jsxs)(`span`,{className:`so`,children:[` · `,t]})]}),i&&(0,$.jsx)(`span`,{className:`sh`}),a.map(e=>(0,$.jsxs)(`span`,{className:e.cls?`sh ${e.cls}`:`sh`,children:[e.head,(0,$.jsx)(gt,{of:e.unit})]},e.key))]})}function Ot({s:e,group:t,times:n}){let r=t.units.length>1;return(0,$.jsx)($.Fragment,{children:t.units.map((i,a)=>(0,$.jsxs)(`div`,{className:`lb-row${r?` u`:``}${a>0?` cont`:``}`,children:[(0,$.jsx)(`span`,{className:`tm`,children:n.clock(e.ts)}),r&&(0,$.jsx)(`span`,{className:`un`,children:i.head}),t.metrics.map(t=>(0,$.jsx)(`span`,{className:`v`,children:te(e,i,t)},t.key))]},i.key))})}function kt({s:e,cols:t,times:n}){return(0,$.jsx)(`div`,{className:`lb-row`,children:t.map((t,r)=>(0,$.jsx)(`span`,{className:r===0?`tm`:`${t.dim?`v dim`:`v`}${ut(t)}`,children:t.key===`ts`?n.clock(e.ts):t.cell(e)},t.key))})}var At=[{to:`/logbook/bridge`,name:`Bridge`,keeper:`Chief officer`,holds:`Position, course and speed, the weather she ran in and the water under her.`,Icon:m},{to:`/logbook/engine`,name:`Engine`,keeper:`Chief engineer`,holds:`Engines, generators and tanks, as she reported them.`,Icon:v}];function jt(){let e=p();return(0,$.jsx)(`div`,{className:`lb-door`,children:(0,$.jsx)(`div`,{className:`lbd-cards`,children:At.map(({to:t,name:n,keeper:r,holds:i,Icon:a})=>(0,$.jsxs)(c,{to:e(t),replace:!0,className:`lbd-card`,children:[(0,$.jsx)(a,{size:42}),(0,$.jsx)(`span`,{className:`lbd-n`,children:n}),(0,$.jsx)(`span`,{className:`lbd-k`,children:r}),(0,$.jsx)(`span`,{className:`lbd-h`,children:i}),(0,$.jsxs)(`span`,{className:`lbd-go`,children:[`Open `,(0,$.jsx)(y,{size:15})]})]},t))})})}function Mt({book:e}){return e?(0,$.jsx)(Je,{book:e}):(0,$.jsx)(jt,{})}export{Mt as default};