signalk-siparu 0.2.23 → 0.2.25

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/README.md CHANGED
@@ -154,8 +154,8 @@ she is still there, not where she is. Either way the shore keeps none of it:
154
154
  when she drops off, all that remains ashore is her name and when she was last
155
155
  seen. Both are outbound: she dials the relay, the relay never dials her.
156
156
 
157
- Five kinds of message travel the other way, and every one of them is a read of
158
- the store she wrote herself:
157
+ Ten kinds of message travel the other way, and every one of them is a read of
158
+ the store she wrote herself or of her own state:
159
159
 
160
160
  | Inbound | Description |
161
161
  |---|---|
@@ -164,6 +164,11 @@ the store she wrote herself:
164
164
  | `{ type: 'voyages', id, limit }` | Her recent voyages, newest first. She clamps the count, so a request cannot ask for more than she will give. |
165
165
  | `{ type: 'track', id, voyageId }` | One voyage's recorded path, decimated aboard before it goes over the wire. |
166
166
  | `{ type: 'phases', id, limit }` | The activity band beneath the voyages, newest first, clamped the same way. |
167
+ | `{ type: 'rollups', id, from, to }` | Her hourly summaries over a window, unflattened: what an export of figures reads. As the local `GET /rollups/hourly`. |
168
+ | `{ type: 'stats', id }` | The voyage screen's four windows: today, yesterday, the week, the season. As the local `GET /voyages/stats`. |
169
+ | `{ type: 'fuel', id }` | Which engine fuel-rate paths she reports and which are counted, so the voyage screen can say why a passage shows no fuel. The choice itself is made at her helm. As the local `GET /config/fuel-paths`. |
170
+ | `{ type: 'ais', id, maxNm, limit }` | The vessels her receiver hears within a radius, newest first, clamped as the route is. As the local `GET /ais/targets`. |
171
+ | `{ type: 'health', id }` | Her own account of herself, as the local `GET /health` serves it. |
167
172
 
168
173
  Anything else the shore sends is ignored, because a boat takes no command and so
169
174
  there is nothing else to hear.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "signalk-siparu",
3
- "version": "0.2.23",
3
+ "version": "0.2.25",
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",
@@ -104,6 +104,7 @@ export interface SnapshotsQuery {
104
104
  export interface SnapshotsResult {
105
105
  rows: Snapshot[];
106
106
  clamped: boolean;
107
+ next?: number;
107
108
  minutesFrom?: number;
108
109
  }
109
110
  export interface PathSeriesPoint {
@@ -360,6 +361,13 @@ export interface ApiError {
360
361
  message: string;
361
362
  };
362
363
  }
364
+ export interface UnreadableAskReply {
365
+ id: string;
366
+ error: {
367
+ code: 'UNREADABLE';
368
+ message: string;
369
+ };
370
+ }
363
371
  export interface WrappedKey {
364
372
  kid: string;
365
373
  wrap: string;
@@ -421,7 +421,7 @@ module.exports = (app) => {
421
421
  getRemote: () => rl.getRemote(),
422
422
  frame: () => live(),
423
423
  onHistoryQuery: (p, q) => qs.pathSeries(p, q, Date.now()),
424
- onSnapshotsQuery: (q) => qs.snapshots(q, Date.now()),
424
+ onSnapshotsQuery: (q) => qs.snapshots(q, Date.now(), query_1.PAGE_MAX_BYTES),
425
425
  onVoyagesQuery: async (limit) => ({
426
426
  voyages: vl.list(Math.min(Math.max(1, limit || 50), 500))
427
427
  }),
@@ -117,4 +117,7 @@ export declare function rpcEnvelope(data: string): {
117
117
  id: string;
118
118
  } | null;
119
119
  export declare function isWatchingNote(data: string): boolean;
120
+ export declare function unopenedAsk(data: string): {
121
+ id: string;
122
+ } | null;
120
123
  export {};
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
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
+ exports.unopenedAsk = unopenedAsk;
6
7
  exports.FRAME_EVERY_MS = 2_000;
7
8
  exports.STILL_FRAME_EVERY_MS = 60_000;
8
9
  exports.PROMPTED_FRAME_FLOOR_MS = 5_000;
@@ -14,6 +15,11 @@ const STILL_NAV_STATES = new Set(['anchored', 'moored', 'aground']);
14
15
  exports.PING_EVERY_MS = 25_000;
15
16
  const RECONNECT_BASE_MS = 5_000;
16
17
  const RECONNECT_CEILING_MS = 15 * 60_000;
18
+ const UNREADABLE_ASK = {
19
+ code: 'UNREADABLE',
20
+ message: 'This boat could not open the question as it was sealed. Open this screen again; if it ' +
21
+ 'keeps happening, update the Siparu plugin aboard and the app.'
22
+ };
17
23
  const STAND_OFF_MS = 15 * 60_000;
18
24
  const UNPAIRED_RECHECK_MS = 60_000;
19
25
  const CLOSE_UNKNOWN_TOKEN = 1008;
@@ -150,6 +156,11 @@ class LiveUplink {
150
156
  this.dispatch(gen, opened.plaintext);
151
157
  return;
152
158
  }
159
+ const unread = unopenedAsk(data);
160
+ if (unread) {
161
+ this.reply(gen, { id: unread.id, error: UNREADABLE_ASK });
162
+ return;
163
+ }
153
164
  const asked = rpcEnvelope(data);
154
165
  if (asked) {
155
166
  const refusal = {
@@ -684,3 +695,16 @@ function isWatchingNote(data) {
684
695
  return false;
685
696
  return msg.type === 'watching';
686
697
  }
698
+ function unopenedAsk(data) {
699
+ let msg;
700
+ try {
701
+ msg = JSON.parse(data);
702
+ }
703
+ catch {
704
+ return null;
705
+ }
706
+ if (!msg || typeof msg !== 'object')
707
+ return null;
708
+ const { type, id } = msg;
709
+ return type === 'ask' && typeof id === 'string' && id.length > 0 ? { id } : null;
710
+ }
@@ -1,14 +1,16 @@
1
- import { PathSeriesResult, SnapshotsQuery, SnapshotsResult } from './contract';
1
+ import { PathSeriesResult, Snapshot, SnapshotsQuery, SnapshotsResult } from './contract';
2
2
  import { RollupEngine } from './rollup';
3
3
  import { Store } from './store';
4
4
  export declare const LIMIT_DEFAULT = 200;
5
5
  export declare const LIMIT_MAX = 5000;
6
+ export declare const PAGE_MAX_BYTES: number;
7
+ export declare function fitPage(rows: Snapshot[], budget: number): Snapshot[];
6
8
  export declare const RAW_WINDOW_DAYS = 7;
7
9
  export declare class QueryService {
8
10
  private store;
9
11
  private rollups;
10
12
  constructor(store: Store, rollups: RollupEngine);
11
- snapshots(q: SnapshotsQuery, now: number): Promise<SnapshotsResult>;
13
+ snapshots(q: SnapshotsQuery, now: number, maxBytes?: number): Promise<SnapshotsResult>;
12
14
  pathSeries(pathName: string, q: SnapshotsQuery, now: number): Promise<PathSeriesResult>;
13
15
  private minutesFrom;
14
16
  private readRawWindow;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.QueryError = exports.QueryService = exports.RAW_WINDOW_DAYS = exports.LIMIT_MAX = exports.LIMIT_DEFAULT = void 0;
3
+ exports.QueryError = exports.QueryService = exports.RAW_WINDOW_DAYS = exports.PAGE_MAX_BYTES = exports.LIMIT_MAX = exports.LIMIT_DEFAULT = void 0;
4
+ exports.fitPage = fitPage;
4
5
  const contract_1 = require("./contract");
5
6
  const aggregate_1 = require("./aggregate");
6
7
  const time_1 = require("./time");
@@ -21,6 +22,16 @@ function sixHourWindows(hours) {
21
22
  }
22
23
  exports.LIMIT_DEFAULT = 200;
23
24
  exports.LIMIT_MAX = 5000;
25
+ exports.PAGE_MAX_BYTES = 16 * 1024 * 1024;
26
+ function fitPage(rows, budget) {
27
+ let total = 0;
28
+ for (let i = 0; i < rows.length; i++) {
29
+ total += Buffer.byteLength(JSON.stringify(rows[i]));
30
+ if (total > budget && i > 0)
31
+ return rows.slice(0, i);
32
+ }
33
+ return rows;
34
+ }
24
35
  exports.RAW_WINDOW_DAYS = 7;
25
36
  function aggFor(r, coreField, pathName) {
26
37
  return coreField ? r.metrics[coreField] : r.path_metrics?.[pathName];
@@ -59,7 +70,7 @@ class QueryService {
59
70
  this.store = store;
60
71
  this.rollups = rollups;
61
72
  }
62
- async snapshots(q, now) {
73
+ async snapshots(q, now, maxBytes = Infinity) {
63
74
  const limit = Math.min(Math.max(1, q.limit ?? exports.LIMIT_DEFAULT), exports.LIMIT_MAX);
64
75
  const offset = Math.max(0, q.offset ?? 0);
65
76
  const order = q.order ?? 'desc';
@@ -92,10 +103,16 @@ class QueryService {
92
103
  rows = rows.filter((r) => r.ts >= (q.bucket === 1 ? 0 : (q.from ?? 0)) && r.ts <= to);
93
104
  rows.sort((a, b) => (order === 'asc' ? a.ts - b.ts : b.ts - a.ts));
94
105
  const total = rows.length;
95
- rows = rows.slice(offset, offset + limit);
96
- if (offset + rows.length < total)
106
+ rows = fitPage(rows.slice(offset, offset + limit), maxBytes);
107
+ const next = offset + rows.length < total ? offset + rows.length : undefined;
108
+ if (next !== undefined)
97
109
  clamped = true;
98
- return { rows, clamped, ...(floor === undefined ? {} : { minutesFrom: floor }) };
110
+ return {
111
+ rows,
112
+ clamped,
113
+ ...(next === undefined ? {} : { next }),
114
+ ...(floor === undefined ? {} : { minutesFrom: floor })
115
+ };
99
116
  }
100
117
  async pathSeries(pathName, q, now) {
101
118
  const limit = Math.min(Math.max(1, q.limit ?? exports.LIMIT_DEFAULT), exports.LIMIT_MAX);
@@ -0,0 +1,2 @@
1
+ const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./tz-CvILeWul.js","./rolldown-runtime-BWaJ3C7D.js"])))=>i.map(i=>d[i]);
2
+ import{a as e}from"./rolldown-runtime-BWaJ3C7D.js";import{n as t,t as n}from"./jsx-runtime-CgYtVKZV.js";import{t as r}from"./visibleInterval-CFxPzgzx.js";import{A as i,D as a,E as o,F as s,I as c,M as l,O as u,S as d,T as f,a as p,b as m,c as h,f as g,j as _,k as v,l as y,m as b,s as x,u as S,v as C,w,y as T}from"./index-zmkelveU.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-Beek6XVZ.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=>d(v(e.sog),1)},has:e=>e.sog!==null},{col:{key:`cog`,head:`COG`,book:`bridge`,cell:e=>{let t=u(e.cog);return t===null?`·`:Math.round(t)+`°`}},has:e=>e.cog!==null},{col:{key:`hdg`,head:`HDG`,book:`bridge`,cell:e=>{let t=u(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=o(t.wind_speed_true);return n===null?`·`:String(e===`kn`?Math.round(n):f(n)??`·`)}},has:e=>e.wind_speed_true!==null},{col:{key:`awa`,head:`AWA`,book:`bridge`,cell:e=>{let t=u(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=a(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=>d(w(e.air_temp_k),1)},has:e=>e.air_temp_k!==null},{col:{key:`sea`,head:`SEA`,book:`bridge`,unit:`°C`,cell:e=>d(w(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=i(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:l(e),cell:t=>{let n=t.path_values?.[e];if(typeof n!=`number`)return`·`;let r=_(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 a of e)for(let e of Object.keys(a.path_values??{})){let a=i(e);if(!a)continue;let o=V(a);if(o===null)continue;n.has(a.tab)||(t.push(a.tab),n.set(a.tab,new Map),r.set(a.tab,[]));let s=n.get(a.tab);s.has(a.label)||s.set(a.label,new Map),s.get(a.label).set(o,e);let c=r.get(a.tab);c.includes(o)||c.push(o)}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?``:l(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 i=e.path_values?.[r];if(typeof i!=`number`)return`·`;let a=_(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]);else{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 p(()=>import(`./tz-CvILeWul.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=s(),[n,i]=(0,F.useState)(0),[a,o]=(0,F.useState)(!1),[c,l]=(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>_),l(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:c,err:u,busy:f,hasMore:a,loadMore:(0,F.useCallback)(()=>i(e=>e+1),[])}}function Oe(){let e=s(),[t,n]=(0,F.useState)(m()),[i,a]=(0,F.useState)([]),[o,c]=(0,F.useState)(null),[l,u]=(0,F.useState)(!1),d=(0,F.useRef)(!0),f=b(6e4),p=m(new Date(f)),h=(0,F.useMemo)(()=>T(t),[t]),g=h+864e5,_=t===p;(0,F.useEffect)(()=>{d.current&&t!==p&&n(p)},[p,t]);let v=(0,F.useCallback)(e=>{d.current=e===m(),n(e)},[]),y=(0,F.useCallback)(async()=>{u(!0),c(null);try{let t=await e.logbook.snapshots({from:h,to:g,limit:5e3,order:`desc`,bucket:60});a(t)}catch(e){c(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:l,prevDay:(0,F.useCallback)(()=>v(m(new Date(h-864e5))),[h,v]),nextDay:(0,F.useCallback)(()=>v(m(new Date(h+864e5))),[h,v]),goToday:(0,F.useCallback)(()=>v(m()),[v])}}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=s(),[a,o]=(0,F.useState)([]),[c,l]=(0,F.useState)(null),[u,d]=(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)(()=>T(e),[e]),x=(0,F.useMemo)(()=>T(t)+864e5-1,[t]),S=Ce[n],C=(0,F.useCallback)(async()=>{if(Number.isNaN(b)||Number.isNaN(x)||x<b)o([]),y([]),p(!1),l(null),h(!0);else{h(!1),d(!0),l(null);try{if(y([]),S===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){l(e.message)}finally{d(!1),h(!0)}}},[b,x,S,n,r]);return(0,F.useEffect)(()=>{C()},[C]),{snaps:a,err:c,busy:u,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 m(new Date(Date.now()-6048e5))}function ze({initial:e,onView:t,onSave:n}){let[r,i]=(0,F.useState)(e?.from??Re()),[a,o]=(0,F.useState)(e?.to??m()),[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,h]=(0,F.useState)(e?.distance??!1),[g,_]=(0,F.useState)(e?.samples??!1),[v,y]=(0,F.useState)(e?.style??`screen`),b=a<r,x={from:r,to:a,gran:s,format:l,stats:Le(l,d),distance:p,samples:g,style:v},S=s!==`1m`,C=l===`pdf`,w=S&&!C&&d.length===0&&!p&&!g,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:m(),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:m(),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${C?``:` off`}`,children:[(0,$.jsxs)(`div`,{className:`lbp-h`,children:[(0,$.jsx)(`span`,{className:`lbp-n`,children:`Style`}),(0,$.jsx)(`span`,{className:`lbp-s`,children:C?v===`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${v===e.v?` on`:``}`,disabled:!C,onClick:()=>y(e.v),children:e.name},e.v))})]})]}),S&&(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:C?`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:C,onClick:()=>h(!p),children:`Distance`}),(0,$.jsx)(`button`,{className:`lbp-c${g?` on`:``}`,disabled:C,onClick:()=>_(!g),children:`Samples`})]})]}),b&&(0,$.jsx)(`div`,{className:`lbe-warn`,children:`That window ends before it begins.`}),w&&(0,$.jsx)(`div`,{className:`lbe-warn`,children:`Choose at least one figure to put in the file.`}),(0,$.jsxs)(`div`,{className:`lbp-act`,children:[C&&(0,$.jsx)(`button`,{disabled:b,onClick:()=>t(x),children:`View`}),(0,$.jsxs)(`button`,{className:`lbp-go`,disabled:b||w,onClick:()=>n(x),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=s(),[n,r]=(0,F.useState)(`live`),[i,a]=(0,F.useState)(`1h`),[o,c]=(0,F.useState)(null),[l,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:m}=l,h=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),[S,C]=(0,F.useState)(()=>ye(d)),[w,E]=(0,F.useState)(d);w!==d&&(E(d),C(ye(d)),b.current=[],x.current=0);let[O,k]=(0,F.useState)(!1),I=e=>{C(e),be(d,e),k(!1)},[L,R]=g(),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,l]);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:c,shownFamily:m,leaving:u,modeLeaving:h,lanesHold:z,groupsHold:b,chosenHold:x,windUnit:_,toggleWind:y,selection:S,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);else try{let n=T(e.from),r=T(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,_),d),S),o=M(`logbook-${d}`,n,`csv`,i?`-partial`:``);A(o,`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=await t.logbook.rollupHours(n,r),a=P(i,e.gran),o=e.stats.map(e=>({stat:e,cols:me(G(W(a.map(t=>D(t,e)),_),d),S)})).filter(e=>e.cols.length>1);A(M(`logbook-${d}`,n,`csv`),`text/csv`,N(a,o,{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||T(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 T(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:h,setPicking:g,applySelection:_,width:v,exporting:y,setExporting:b,request:x,onView:S,onSave:C,saveErr:w,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,v,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:m(),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:h,onOpen:()=>g(!h)}),(0,$.jsx)(mt,{open:y,onOpen:()=>b(!y)})]})]}),(0,$.jsx)(Be,{open:h,style:U,cls:W,children:(0,$.jsx)(Se,{cols:V,applied:p,onApply:_,onCancel:()=>g(!1)})}),(0,$.jsx)(Be,{open:y,style:U,cls:W,children:(0,$.jsx)(ze,{initial:x??void 0,onView:S,onSave:C})}),w&&(0,$.jsx)(`div`,{className:`lb-err`,children:w}),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)(h,{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:h,setPicking:g,applySelection:_,width:v,exporting:y,setExporting:b,request:x,onView:S,onSave:C,saveErr:w,saveNote:T,printing:D,donePrinting:k}){let A={from:m(),to:m(),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,v,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:()=>b(!y),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:h,onOpen:()=>g(!h)})})]}),(0,$.jsx)(Be,{open:h,style:q,cls:J,children:(0,$.jsx)(Se,{cols:te,applied:p,onApply:_,onCancel:()=>g(!1)})}),(0,$.jsx)(Be,{open:y,style:q,cls:J,children:(0,$.jsx)(ze,{initial:x??void 0,onView:S,onSave:C})}),w&&(0,$.jsx)(`div`,{className:`lb-err`,children:w}),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 `,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:y},{to:`/logbook/engine`,name:`Engine`,keeper:`Chief engineer`,holds:`Engines, generators and tanks, as she reported them.`,Icon:S}];function jt(){let e=C();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)(x,{size:15})]})]},t))})})}function Mt({book:e}){return e?(0,$.jsx)(Je,{book:e}):(0,$.jsx)(jt,{})}export{Mt 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,O as a,P as o,S as s,b as c,g as l,r as u,x as d}from"./index-DPNvHdqr.js";import{a as f,c as p,d as m,i as h,l as g,n as _,o as v,r as y,s as b,t as x,u as S}from"./style-BRg0vyat.js";var C=e(t(),1),w={fill:`#ffb938`,stroke:`#b38326`,glow:`rgba(255,185,56,0.65)`,dot:`#050706`};function T(e,t=w){let n=t.fill,r=t.stroke;return e===null||Number.isNaN(e)?`
1
+ import{a as e}from"./rolldown-runtime-BWaJ3C7D.js";import{n as t,t as n}from"./jsx-runtime-CgYtVKZV.js";import{t as r}from"./visibleInterval-CFxPzgzx.js";import{C as i,F as a,O as o,S as s,_ as c,k as l,r as u,x as d}from"./index-zmkelveU.js";import{a as f,c as p,d as m,i as h,l as g,n as _,o as v,r as y,s as b,t as x,u as S}from"./style-C6gN2Vu6.js";var C=e(t(),1),w={fill:`#ffb938`,stroke:`#b38326`,glow:`rgba(255,185,56,0.65)`,dot:`#050706`};function T(e,t=w){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};
@@ -36,7 +36,7 @@ import{a as e}from"./rolldown-runtime-CNC7AqOf.js";import{n as t,t as n}from"./j
36
36
  stroke-width="1.6"
37
37
  stroke-linejoin="round"
38
38
  style="filter: drop-shadow(0 0 3px ${n}88);"/>
39
- </svg>`}var D={s:`s`,min:`m`,h:`h`,d:`d`};function O(e,t=Date.now()){let{value:n,unit:r}=u((t-e)/1e3);return`${n}${D[r]} ago`}var k=`siparu.aisPrefs`;function A(e,t,n){return Number.isFinite(e)?Math.min(n,Math.max(t,e)):t}function j(){try{let e=localStorage.getItem(k);if(!e)return{maxNm:5,limit:30};let t=JSON.parse(e);return{maxNm:A(Number(t.maxNm),1,20),limit:A(Number(t.limit),5,100)}}catch{return{maxNm:5,limit:30}}}function M(e){try{localStorage.setItem(k,JSON.stringify(e))}catch{}}var ee=[7.4246,43.7384],te=8,ne=1e4,re=3e4,ie=5*6e4,ae=24*3600*1e3,oe=3e4,se=250;function N(e,t,n){return Math.min(n,Math.max(t,Math.round(e)))}function P(){return document.documentElement.dataset.theme===`day`?`day`:`night`}function F(e){return e.length<2?{type:`FeatureCollection`,features:[]}:{type:`FeatureCollection`,features:[{type:`Feature`,properties:{},geometry:{type:`LineString`,coordinates:e.map(e=>[e.lon,e.lat])}}]}}function I(e){let t=o(),n=(0,C.useRef)(null),a=(0,C.useRef)(null),s=(0,C.useRef)(null),c=(0,C.useRef)(null),u=(0,C.useRef)(null),d=(0,C.useRef)([]),w=(0,C.useRef)(!1),T=(0,C.useRef)(F([])),E=(0,C.useRef)(e);(0,C.useEffect)(()=>{E.current=e});let[D,O]=(0,C.useState)(!0),[k,A]=(0,C.useState)(0),[I,L]=(0,C.useState)(!1),[R,z]=(0,C.useState)(null),[B,V]=(0,C.useState)(null),[H,U]=(0,C.useState)(()=>j().maxNm),[W,G]=(0,C.useState)(()=>j().limit),K=(0,C.useCallback)(e=>{let t=N(e,1,20);U(t),M({maxNm:t,limit:W})},[W]),q=(0,C.useCallback)(e=>{let t=N(e,5,100);G(t),M({maxNm:H,limit:t})},[H]),[J,Y]=(0,C.useState)([]),[X,Z]=(0,C.useState)(null),Q=(0,C.useRef)([]);(0,C.useEffect)(()=>{Q.current=J},[J]);let ce=(0,C.useCallback)(()=>{let e=a.current;if(!e)return;let t=Q.current;if(t.length>=2){let n=new m;for(let e of t)n.extend([e.lon,e.lat]);e.fitBounds(n,{padding:40,maxZoom:14});return}let n=s.current;n&&e.easeTo({center:n.getLngLat(),zoom:Math.max(e.getZoom(),12)})},[]),$=(0,C.useCallback)(e=>{let t=e.getSource(_);t&&t.setData(T.current)},[]);return(0,C.useEffect)(()=>{if(!n.current||a.current)return;let e=n.current,r=!1,i=null,o=null,l;return(async()=>{f();let n=await v(t);if(r)return;let s=y(n);s||V(`Chart unavailable - position and track only`);let c=e=>h(e,n,{track:E.current.track});i=new g({container:e,style:c(P()),center:ee,zoom:te,attributionControl:{compact:!1,customAttribution:x},dragRotate:!1,pitchWithRotate:!1}),i.touchZoomRotate.disableRotation(),i.keyboard.disableRotation(),a.current=i,i.addControl(new p({showCompass:!1}),E.current.zoomPosition??`bottom-left`);let u=!1;i.on(`sourcedata`,e=>{let t=!!e.tile;e.sourceId===s&&t&&!u&&(u=!0,V(null))}),l=window.setTimeout(()=>{!u&&s&&V(`Chart tiles unreachable - position and track only`)},ne),i.on(`style.load`,()=>{i&&$(i)}),o=new MutationObserver(()=>{i&&(i.setStyle(c(P())),$(i))}),o.observe(document.documentElement,{attributes:!0,attributeFilter:[`data-theme`]})})(),()=>{r=!0,window.clearTimeout(l),o?.disconnect(),d.current.forEach(e=>e.remove()),d.current=[],s.current=null,c.current=null,i?.remove(),a.current=null}},[$]),(0,C.useEffect)(()=>{let e=!0;return t.health().then(t=>{e&&(z(t.boat_name),L(t.ais?.receiver_seen===!0))}).catch(()=>{}),()=>{e=!1}},[]),(0,C.useEffect)(()=>{if(!D||!I){d.current.forEach(e=>e.remove()),d.current=[],A(0);return}let e=!1,n=async()=>{if(t.ais)try{let n=await t.ais.targets({maxNm:H,limit:W});if(e)return;let r=a.current;if(!r)return;d.current.forEach(e=>e.remove()),d.current=[];for(let e of n.targets){let t=e.cog_deg??e.heading_deg??null,n=document.createElement(`div`);n.className=`sp-ais-marker`,n.innerHTML=E.current.makeAis(t);let i=new S({element:n,anchor:`center`}).setLngLat([e.lon,e.lat]).setPopup(new b({closeButton:!0,offset:12,maxWidth:`280px`}).setHTML(E.current.aisPopupHtml(e))).addTo(r);d.current.push(i)}A(n.targets.length)}catch{}},i=window.setTimeout(n,se),o=r(n,oe);return()=>{e=!0,window.clearTimeout(i),o()}},[D,I,H,W]),(0,C.useEffect)(()=>{let e=!1,n=async()=>{try{let n=Date.now(),r=await t.logbook.snapshots({from:n-ae,order:`asc`,bucket:1,limit:2e3});if(e)return;let i=r.filter(e=>e.lat!==null&&e.lon!==null).map(e=>({lat:e.lat,lon:e.lon}));Y(i)}catch{}};n();let i=r(n,ie);return()=>{e=!0,i()}},[]),(0,C.useEffect)(()=>{T.current=F(J);let e=a.current;if(e&&($(e),!w.current&&J.length>=2)){w.current=!0;let t=new m;for(let e of J)t.extend([e.lon,e.lat]);e.fitBounds(t,{padding:40,maxZoom:14})}},[J,$]),(0,C.useEffect)(()=>{let e=!1,n=async()=>{try{let n=await t.logbook.snapshotLatest();if(e)return;Z(n),l(`map:latest`,n);let r=a.current;if(!r||n.lat===null||n.lon===null)return;u.current=n;let o=i(n.heading_true),d=E.current.makeBoat(o),f=[n.lon,n.lat];if(s.current)s.current.setLngLat(f),s.current.getElement().innerHTML=d,c.current?.setHTML(E.current.popupHtml(n,Date.now()));else{let e=document.createElement(`div`);e.className=`sp-boat-marker`,e.innerHTML=d;let t=new b({closeButton:!0,offset:16,maxWidth:`300px`}).setHTML(E.current.popupHtml(n,Date.now()));t.on(`open`,()=>{let e=u.current;e&&t.setHTML(E.current.popupHtml(e,Date.now()))}),c.current=t,s.current=new S({element:e,anchor:`center`}).setLngLat(f).setPopup(t).addTo(r),w.current||r.easeTo({center:f,zoom:Math.max(r.getZoom(),11)})}}catch{}};n();let o=r(n,re);return()=>{e=!0,o()}},[]),{containerRef:n,aisOn:D,setAisOn:O,aisCount:k,aisMaxNm:H,setAisMaxNm:K,aisLimit:W,setAisLimit:q,aisAvailable:I,boatName:R,latest:X,chartNote:B,recenter:ce}}var L=n();function R({open:e,onClose:t,maxNm:n,setMaxNm:r,limit:i,setLimit:a,variant:o}){let s=(0,C.useRef)(null);return(0,C.useEffect)(()=>{let t=s.current;if(!t||!e)return;let n=e=>e.stopPropagation(),r=[`pointerdown`,`mousedown`,`touchstart`,`dblclick`,`wheel`];for(let e of r)t.addEventListener(e,n);return()=>{for(let e of r)t.removeEventListener(e,n)}},[e]),(0,C.useEffect)(()=>{if(!e)return;let n=e=>{e.key===`Escape`&&t()};return window.addEventListener(`keydown`,n),()=>window.removeEventListener(`keydown`,n)},[e,t]),(0,C.useEffect)(()=>{if(!e)return;let n=e=>{let n=s.current;if(!n||!(e.target instanceof Node)||n.contains(e.target))return;let r=n.parentElement;r&&r.contains(e.target)||t()},r=window.setTimeout(()=>{document.addEventListener(`mousedown`,n),document.addEventListener(`touchstart`,n)},0);return()=>{window.clearTimeout(r),document.removeEventListener(`mousedown`,n),document.removeEventListener(`touchstart`,n)}},[e,t]),e?(0,L.jsxs)(`div`,{ref:s,className:`ais-filter ais-filter-${o}`,role:`dialog`,children:[(0,L.jsxs)(`div`,{className:`aisf-row`,children:[(0,L.jsxs)(`div`,{className:`aisf-label`,children:[(0,L.jsx)(`span`,{children:`Range`}),(0,L.jsxs)(`span`,{className:`aisf-value`,children:[n,` NM`]})]}),(0,L.jsx)(`input`,{type:`range`,min:1,max:20,step:1,value:n,onChange:e=>r(Number(e.target.value))})]}),(0,L.jsxs)(`div`,{className:`aisf-row`,children:[(0,L.jsxs)(`div`,{className:`aisf-label`,children:[(0,L.jsx)(`span`,{children:`Targets`}),(0,L.jsx)(`span`,{className:`aisf-value`,children:i})]}),(0,L.jsx)(`input`,{type:`range`,min:5,max:100,step:5,value:i,onChange:e=>a(Number(e.target.value))})]})]}):null}var z=`bottom-left`,B=`
39
+ </svg>`}var D={s:`s`,min:`m`,h:`h`,d:`d`};function O(e,t=Date.now()){let{value:n,unit:r}=u((t-e)/1e3);return`${n}${D[r]} ago`}var k=`siparu.aisPrefs`;function A(e,t,n){return Number.isFinite(e)?Math.min(n,Math.max(t,e)):t}function j(){try{let e=localStorage.getItem(k);if(!e)return{maxNm:5,limit:30};let t=JSON.parse(e);return{maxNm:A(Number(t.maxNm),1,20),limit:A(Number(t.limit),5,100)}}catch{return{maxNm:5,limit:30}}}function M(e){try{localStorage.setItem(k,JSON.stringify(e))}catch{}}var ee=[7.4246,43.7384],te=8,ne=1e4,re=3e4,ie=3e5,ae=864e5,oe=3e4,se=250;function N(e,t,n){return Math.min(n,Math.max(t,Math.round(e)))}function P(){return document.documentElement.dataset.theme===`day`?`day`:`night`}function F(e){return e.length<2?{type:`FeatureCollection`,features:[]}:{type:`FeatureCollection`,features:[{type:`Feature`,properties:{},geometry:{type:`LineString`,coordinates:e.map(e=>[e.lon,e.lat])}}]}}function I(e){let t=a(),n=(0,C.useRef)(null),i=(0,C.useRef)(null),s=(0,C.useRef)(null),l=(0,C.useRef)(null),u=(0,C.useRef)(null),d=(0,C.useRef)([]),w=(0,C.useRef)(!1),T=(0,C.useRef)(F([])),E=(0,C.useRef)(e);(0,C.useEffect)(()=>{E.current=e});let[D,O]=(0,C.useState)(!0),[k,A]=(0,C.useState)(0),[I,L]=(0,C.useState)(!1),[R,z]=(0,C.useState)(null),[B,V]=(0,C.useState)(null),[H,U]=(0,C.useState)(()=>j().maxNm),[W,G]=(0,C.useState)(()=>j().limit),K=(0,C.useCallback)(e=>{let t=N(e,1,20);U(t),M({maxNm:t,limit:W})},[W]),q=(0,C.useCallback)(e=>{let t=N(e,5,100);G(t),M({maxNm:H,limit:t})},[H]),[J,Y]=(0,C.useState)([]),[X,Z]=(0,C.useState)(null),Q=(0,C.useRef)([]);(0,C.useEffect)(()=>{Q.current=J},[J]);let ce=(0,C.useCallback)(()=>{let e=i.current;if(!e)return;let t=Q.current;if(t.length>=2){let n=new m;for(let e of t)n.extend([e.lon,e.lat]);e.fitBounds(n,{padding:40,maxZoom:14});return}let n=s.current;n&&e.easeTo({center:n.getLngLat(),zoom:Math.max(e.getZoom(),12)})},[]),$=(0,C.useCallback)(e=>{let t=e.getSource(_);t&&t.setData(T.current)},[]);return(0,C.useEffect)(()=>{if(!n.current||i.current)return;let e=n.current,r=!1,a=null,o=null,c;return(async()=>{f();let n=await v(t);if(r)return;let s=y(n);s||V(`Chart unavailable - position and track only`);let l=e=>h(e,n,{track:E.current.track});a=new g({container:e,style:l(P()),center:ee,zoom:te,attributionControl:{compact:!1,customAttribution:x},dragRotate:!1,pitchWithRotate:!1}),a.touchZoomRotate.disableRotation(),a.keyboard.disableRotation(),i.current=a,a.addControl(new p({showCompass:!1}),E.current.zoomPosition??`bottom-left`);let u=!1;a.on(`sourcedata`,e=>{let t=!!e.tile;e.sourceId===s&&t&&!u&&(u=!0,V(null))}),c=window.setTimeout(()=>{!u&&s&&V(`Chart tiles unreachable - position and track only`)},ne),a.on(`style.load`,()=>{a&&$(a)}),o=new MutationObserver(()=>{a&&(a.setStyle(l(P())),$(a))}),o.observe(document.documentElement,{attributes:!0,attributeFilter:[`data-theme`]})})(),()=>{r=!0,window.clearTimeout(c),o?.disconnect(),d.current.forEach(e=>e.remove()),d.current=[],s.current=null,l.current=null,a?.remove(),i.current=null}},[$]),(0,C.useEffect)(()=>{let e=!0;return t.health().then(t=>{e&&(z(t.boat_name),L(t.ais?.receiver_seen===!0))}).catch(()=>{}),()=>{e=!1}},[]),(0,C.useEffect)(()=>{if(!D||!I){d.current.forEach(e=>e.remove()),d.current=[],A(0);return}let e=!1,n=async()=>{if(t.ais)try{let n=await t.ais.targets({maxNm:H,limit:W});if(e)return;let r=i.current;if(!r)return;d.current.forEach(e=>e.remove()),d.current=[];for(let e of n.targets){let t=e.cog_deg??e.heading_deg??null,n=document.createElement(`div`);n.className=`sp-ais-marker`,n.innerHTML=E.current.makeAis(t);let i=new S({element:n,anchor:`center`}).setLngLat([e.lon,e.lat]).setPopup(new b({closeButton:!0,offset:12,maxWidth:`280px`}).setHTML(E.current.aisPopupHtml(e))).addTo(r);d.current.push(i)}A(n.targets.length)}catch{}},a=window.setTimeout(n,se),o=r(n,oe);return()=>{e=!0,window.clearTimeout(a),o()}},[D,I,H,W]),(0,C.useEffect)(()=>{let e=!1,n=async()=>{try{let n=Date.now(),r=await t.logbook.snapshots({from:n-ae,order:`asc`,bucket:1,limit:2e3});if(e)return;let i=r.filter(e=>e.lat!==null&&e.lon!==null).map(e=>({lat:e.lat,lon:e.lon}));Y(i)}catch{}};n();let i=r(n,ie);return()=>{e=!0,i()}},[]),(0,C.useEffect)(()=>{T.current=F(J);let e=i.current;if(e&&($(e),!w.current&&J.length>=2)){w.current=!0;let t=new m;for(let e of J)t.extend([e.lon,e.lat]);e.fitBounds(t,{padding:40,maxZoom:14})}},[J,$]),(0,C.useEffect)(()=>{let e=!1,n=async()=>{try{let n=await t.logbook.snapshotLatest();if(e)return;Z(n),c(`map:latest`,n);let r=i.current;if(!r||n.lat===null||n.lon===null)return;u.current=n;let a=o(n.heading_true),d=E.current.makeBoat(a),f=[n.lon,n.lat];if(s.current)s.current.setLngLat(f),s.current.getElement().innerHTML=d,l.current?.setHTML(E.current.popupHtml(n,Date.now()));else{let e=document.createElement(`div`);e.className=`sp-boat-marker`,e.innerHTML=d;let t=new b({closeButton:!0,offset:16,maxWidth:`300px`}).setHTML(E.current.popupHtml(n,Date.now()));t.on(`open`,()=>{let e=u.current;e&&t.setHTML(E.current.popupHtml(e,Date.now()))}),l.current=t,s.current=new S({element:e,anchor:`center`}).setLngLat(f).setPopup(t).addTo(r),w.current||r.easeTo({center:f,zoom:Math.max(r.getZoom(),11)})}}catch{}};n();let a=r(n,re);return()=>{e=!0,a()}},[]),{containerRef:n,aisOn:D,setAisOn:O,aisCount:k,aisMaxNm:H,setAisMaxNm:K,aisLimit:W,setAisLimit:q,aisAvailable:I,boatName:R,latest:X,chartNote:B,recenter:ce}}var L=n();function R({open:e,onClose:t,maxNm:n,setMaxNm:r,limit:i,setLimit:a,variant:o}){let s=(0,C.useRef)(null);return(0,C.useEffect)(()=>{let t=s.current;if(!t||!e)return;let n=e=>e.stopPropagation(),r=[`pointerdown`,`mousedown`,`touchstart`,`dblclick`,`wheel`];for(let e of r)t.addEventListener(e,n);return()=>{for(let e of r)t.removeEventListener(e,n)}},[e]),(0,C.useEffect)(()=>{if(!e)return;let n=e=>{e.key===`Escape`&&t()};return window.addEventListener(`keydown`,n),()=>window.removeEventListener(`keydown`,n)},[e,t]),(0,C.useEffect)(()=>{if(!e)return;let n=e=>{let n=s.current;if(!n||!(e.target instanceof Node)||n.contains(e.target))return;let r=n.parentElement;r&&r.contains(e.target)||t()},r=window.setTimeout(()=>{document.addEventListener(`mousedown`,n),document.addEventListener(`touchstart`,n)},0);return()=>{window.clearTimeout(r),document.removeEventListener(`mousedown`,n),document.removeEventListener(`touchstart`,n)}},[e,t]),e?(0,L.jsxs)(`div`,{ref:s,className:`ais-filter ais-filter-${o}`,role:`dialog`,children:[(0,L.jsxs)(`div`,{className:`aisf-row`,children:[(0,L.jsxs)(`div`,{className:`aisf-label`,children:[(0,L.jsx)(`span`,{children:`Range`}),(0,L.jsxs)(`span`,{className:`aisf-value`,children:[n,` NM`]})]}),(0,L.jsx)(`input`,{type:`range`,min:1,max:20,step:1,value:n,onChange:e=>r(Number(e.target.value))})]}),(0,L.jsxs)(`div`,{className:`aisf-row`,children:[(0,L.jsxs)(`div`,{className:`aisf-label`,children:[(0,L.jsx)(`span`,{children:`Targets`}),(0,L.jsx)(`span`,{className:`aisf-value`,children:i})]}),(0,L.jsx)(`input`,{type:`range`,min:5,max:100,step:5,value:i,onChange:e=>a(Number(e.target.value))})]})]}):null}var z=`bottom-left`,B=`
40
40
  .mp .maplibregl-map { background: var(--map-sea); font-family: var(--sp-font); font-size: 11px; outline: none; }
41
41
  .mp .maplibregl-canvas { outline: none; }
42
42
 
@@ -88,15 +88,15 @@ import{a as e}from"./rolldown-runtime-CNC7AqOf.js";import{n as t,t as n}from"./j
88
88
  .mp .ais-filter input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 14px; height: 14px; margin-top: -6px; background: var(--accent); }
89
89
  .mp .ais-filter input[type="range"]::-moz-range-track { height: 2px; background: var(--rule); }
90
90
  .mp .ais-filter input[type="range"]::-moz-range-thumb { width: 14px; height: 14px; border: none; background: var(--accent); }
91
- `,V={fill:`#f2f3f5`,stroke:`#e5484d`,glow:`rgba(229,72,77,0.45)`,dot:`#e5484d`},H=`color: var(--muted); font-family: var(--sp-font); font-size: 8.5px; letter-spacing: 0.1em; text-transform: uppercase;`,U=`color: var(--text); font-family: var(--sp-font); font-size: 11px; font-weight: 600; font-variant-numeric: tabular-nums;`,W=`color: var(--text); font-family: var(--sp-font); font-weight: 600; font-size: 14px; margin-bottom: 8px;`,G=`font-family: var(--sp-font); font-size: 8px; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; color: #fff; background: var(--map-ais-hi); padding: 3px 5px;`,K=e=>e.replace(/[&<>"']/g,e=>({"&":`&amp;`,"<":`&lt;`,">":`&gt;`,'"':`&quot;`,"'":`&#39;`})[e]);function q(e,t,n){let r=a(e.sog),o=r===null?null:i(e.cog),l=i(e.heading_true),u=(e.nav_state??`·`).toUpperCase();return`
91
+ `,V={fill:`#f2f3f5`,stroke:`#e5484d`,glow:`rgba(229,72,77,0.45)`,dot:`#e5484d`},H=`color: var(--muted); font-family: var(--sp-font); font-size: 8.5px; letter-spacing: 0.1em; text-transform: uppercase;`,U=`color: var(--text); font-family: var(--sp-font); font-size: 11px; font-weight: 600; font-variant-numeric: tabular-nums;`,W=`color: var(--text); font-family: var(--sp-font); font-weight: 600; font-size: 14px; margin-bottom: 8px;`,G=`font-family: var(--sp-font); font-size: 8px; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; color: #fff; background: var(--map-ais-hi); padding: 3px 5px;`,K=e=>e.replace(/[&<>"']/g,e=>({"&":`&amp;`,"<":`&lt;`,">":`&gt;`,'"':`&quot;`,"'":`&#39;`})[e]);function q(e,t,n){let r=l(e.sog),a=r===null?null:o(e.cog),c=o(e.heading_true),u=(e.nav_state??`·`).toUpperCase();return`
92
92
  <div style="${W}">${K(n)}</div>
93
93
  <div style="display:grid;grid-template-columns:auto 1fr;column-gap:14px;row-gap:4px;align-items:baseline;">
94
- <span style="${H}">POS</span><span style="${U}">${c(e.lat,[`N`,`S`],2)} · ${c(e.lon,[`E`,`W`],3)}</span>
95
- <span style="${H}">SOG</span><span style="${U}">${d(r,1,` kt`)}</span>
96
- <span style="${H}">COG</span><span style="${U}">${o===null?`·`:d(o,0,`°`)}</span>
97
- <span style="${H}">HDG</span><span style="${U}">${l===null?`·`:d(l,0,`°`)}</span>
94
+ <span style="${H}">POS</span><span style="${U}">${d(e.lat,[`N`,`S`],2)} · ${d(e.lon,[`E`,`W`],3)}</span>
95
+ <span style="${H}">SOG</span><span style="${U}">${s(r,1,` kt`)}</span>
96
+ <span style="${H}">COG</span><span style="${U}">${a===null?`·`:s(a,0,`°`)}</span>
97
+ <span style="${H}">HDG</span><span style="${U}">${c===null?`·`:s(c,0,`°`)}</span>
98
98
  <span style="${H}">NAV</span><span style="${U}">${K(u)}</span>
99
- <span style="${H}">UPD</span><span style="${U}">${O(e.ts,t)} <span style="color:var(--muted)">(${s(e.ts).slice(11,19)})</span></span>
99
+ <span style="${H}">UPD</span><span style="${U}">${O(e.ts,t)} <span style="color:var(--muted)">(${i(e.ts).slice(11,19)})</span></span>
100
100
  </div>
101
101
  `}function J(e){let t=e.name||`MMSI ${e.mmsi}`,n=e.sog_kn===null?`·`:`${e.sog_kn.toFixed(1)} kt`,r=e.cog_deg===null?`·`:`${String(Math.round(e.cog_deg)).padStart(3,`0`)}°`,i=e.distance_nm===null?`·`:`${e.distance_nm.toFixed(1)} NM`,a=e.length_m===null?`·`:`${Math.round(e.length_m)} m`,o=[e.ship_type,e.ais_class?`Class ${e.ais_class}`:null].filter(Boolean).join(` · `)||`·`;return`
102
102
  <div style="${W}; display:flex; justify-content:space-between; gap:10px; align-items:center;">${K(t)} <span style="${G}">AIS</span></div>
@@ -108,4 +108,4 @@ import{a as e}from"./rolldown-runtime-CNC7AqOf.js";import{n as t,t as n}from"./j
108
108
  <span style="${H}">DIST</span><span style="${U}">${i}</span>
109
109
  <span style="${H}">NAV</span><span style="${U}">${K((e.nav_state??`·`).toUpperCase())}</span>
110
110
  </div>
111
- `}function Y(e){return e.charAt(0).toUpperCase()+e.slice(1).toLowerCase()}function X(){let{containerRef:e,aisOn:t,setAisOn:n,aisCount:r,aisMaxNm:o,setAisMaxNm:s,aisLimit:l,setAisLimit:u,latest:d,chartNote:f,aisAvailable:p,boatName:m}=I({track:{color:`#c23a3f`,width:1.8,dash:[2.8,2.2]},zoomPosition:z,popupHtml:(e,t)=>q(e,t,m??`This vessel`),aisPopupHtml:J,makeBoat:e=>T(e,V),makeAis:e=>E(e,`#7a8a86`)}),[h,g]=(0,C.useState)(!1),_=d?i(d.heading_true):null,v=d?a(d.sog):null,y=d?.nav_state?Y(d.nav_state):null,b=d?.lat!=null&&d?.lon!=null,x=b?`${c(d.lat,[`N`,`S`],2)} ${c(d.lon,[`E`,`W`],3)}`:`·`,S=[d&&!b?`Awaiting fix - the boat is not on this chart yet`:null,f].filter(e=>e!==null);return(0,L.jsxs)(`div`,{className:`mp`,children:[(0,L.jsx)(`style`,{children:B}),(0,L.jsx)(`div`,{ref:e,className:`mp-canvas`}),(0,L.jsxs)(`div`,{className:`mp-strip`,children:[(0,L.jsx)(`span`,{className:`co`,children:x}),(0,L.jsxs)(`span`,{className:`vec`,children:[(0,L.jsx)(`b`,{children:_===null?`·`:`${Math.round(_)}°`}),` · `,(0,L.jsx)(`b`,{children:v===null?`·`:v.toFixed(1)}),`kn`]}),y&&(0,L.jsx)(`span`,{className:`navp`,children:y})]}),S.length>0&&(0,L.jsx)(`div`,{className:`mp-notes`,children:S.map(e=>(0,L.jsx)(`div`,{className:`mp-chartnote`,children:e},e))}),p&&(0,L.jsxs)(`div`,{className:`mp-ctrl`,children:[(0,L.jsx)(R,{open:h,onClose:()=>g(!1),maxNm:o,setMaxNm:s,limit:l,setLimit:u,variant:`marine`}),(0,L.jsxs)(`button`,{className:`mp-btn${t?` on`:``}`,onClick:()=>n(!t),children:[`AIS`,t&&(0,L.jsx)(`span`,{className:`n`,children:r})]}),(0,L.jsxs)(`button`,{className:`mp-btn`,onClick:()=>g(e=>!e),children:[`Range · `,(0,L.jsx)(`span`,{className:`n`,children:o}),` Nm`]})]})]})}function Z(){return(0,L.jsx)(X,{})}export{Z as default};
111
+ `}function Y(e){return e.charAt(0).toUpperCase()+e.slice(1).toLowerCase()}function X(){let{containerRef:e,aisOn:t,setAisOn:n,aisCount:r,aisMaxNm:i,setAisMaxNm:a,aisLimit:s,setAisLimit:c,latest:u,chartNote:f,aisAvailable:p,boatName:m}=I({track:{color:`#c23a3f`,width:1.8,dash:[2.8,2.2]},zoomPosition:z,popupHtml:(e,t)=>q(e,t,m??`This vessel`),aisPopupHtml:J,makeBoat:e=>T(e,V),makeAis:e=>E(e,`#7a8a86`)}),[h,g]=(0,C.useState)(!1),_=u?o(u.heading_true):null,v=u?l(u.sog):null,y=u?.nav_state?Y(u.nav_state):null,b=u?.lat!=null&&u?.lon!=null,x=b?`${d(u.lat,[`N`,`S`],2)} ${d(u.lon,[`E`,`W`],3)}`:`·`,S=[u&&!b?`Awaiting fix - the boat is not on this chart yet`:null,f].filter(e=>e!==null);return(0,L.jsxs)(`div`,{className:`mp`,children:[(0,L.jsx)(`style`,{children:B}),(0,L.jsx)(`div`,{ref:e,className:`mp-canvas`}),(0,L.jsxs)(`div`,{className:`mp-strip`,children:[(0,L.jsx)(`span`,{className:`co`,children:x}),(0,L.jsxs)(`span`,{className:`vec`,children:[(0,L.jsx)(`b`,{children:_===null?`·`:`${Math.round(_)}°`}),` · `,(0,L.jsx)(`b`,{children:v===null?`·`:v.toFixed(1)}),`kn`]}),y&&(0,L.jsx)(`span`,{className:`navp`,children:y})]}),S.length>0&&(0,L.jsx)(`div`,{className:`mp-notes`,children:S.map(e=>(0,L.jsx)(`div`,{className:`mp-chartnote`,children:e},e))}),p&&(0,L.jsxs)(`div`,{className:`mp-ctrl`,children:[(0,L.jsx)(R,{open:h,onClose:()=>g(!1),maxNm:i,setMaxNm:a,limit:s,setLimit:c,variant:`marine`}),(0,L.jsxs)(`button`,{className:`mp-btn${t?` on`:``}`,onClick:()=>n(!t),children:[`AIS`,t&&(0,L.jsx)(`span`,{className:`n`,children:r})]}),(0,L.jsxs)(`button`,{className:`mp-btn`,onClick:()=>g(e=>!e),children:[`Range · `,(0,L.jsx)(`span`,{className:`n`,children:i}),` Nm`]})]})]})}function Z(){return(0,L.jsx)(X,{})}export{Z as default};
@@ -0,0 +1 @@
1
+ import{a as e}from"./rolldown-runtime-BWaJ3C7D.js";import{n as t,t as n}from"./jsx-runtime-CgYtVKZV.js";import{F as r,P as i,d as a,h as o,i as s,n as c,r as l,t as u}from"./index-zmkelveU.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}=l((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=r().pair??i.pair,[n,a]=(0,d.useState)(!1),[l,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;a(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=c(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=u(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:l,onClick:t,children:e}),M=y.pairing_locked===!0,N=(0,f.jsx)(s,{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.jsx)(`div`,{className:`s`,children:y.userCode?(0,f.jsxs)(f.Fragment,{children:[`Enter this at `,(0,f.jsx)(`b`,{children:p}),` · `,m(y.expiresAt),` min left`]}):(0,f.jsxs)(f.Fragment,{children:[`The code shows on an administrator's screen · `,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))]})]})}})(),I=(()=>{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`}}})();return(0,f.jsxs)(f.Fragment,{children:[A(I,(0,f.jsxs)(f.Fragment,{children:[C,N,P,F]})),k]})}var v=3e4;function y(){let{data:e}=o(a.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{F as t,_ as n,o as r}from"./index-DPNvHdqr.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};
1
+ import{t as e}from"./jsx-runtime-CgYtVKZV.js";import{I as t,s as n,v as r}from"./index-zmkelveU.js";var i=e(),a=`/admin/#/security/users`;function o({admin:e}){let a=r();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)(n,{size:15})]})})]})})}function s(){return(0,i.jsx)(o,{admin:a})}export{s as default};
@@ -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{L as r,M as i,P as a,a as o,b as s,f as c,h as l,m as u,r as d,x as f}from"./index-DPNvHdqr.js";import{a as p,c as m,i as h,n as g,r as _,s as v}from"./export-CGdOfqNp.js";import{a as y,d as b,i as x,l as S,n as C,o as w,t as T,u as E}from"./style-BRg0vyat.js";var D=e(t(),1),O=[{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`}],k=.26417205,A=.21996923,j=1e-6;function M(e,t,n){return`${e.toFixed(n)} ${t}`}function N(e,t,n,r){if(e===null||!Number.isFinite(e))return null;switch(r){case`total_l`:return M(e,`L`,+(e<100));case`total_usgal`:return M(e*k,`US gal`,1);case`total_impgal`:return M(e*A,`Imp gal`,1);case`per_nm`:return t>j?M(e/t,`L/nm`,2):null;case`nm_per_l`:return e>j?M(t/e,`nm/L`,2):null;case`per_hour`:return n>j?M(e/n,`L/h`,1):null}}var P=`Could not load voyage data`;function F(e){return e instanceof i?e.detail||P:e instanceof DOMException&&e.name===`TimeoutError`?`She did not answer in time`:P}function ee(e=0){let t=a(),{data:n,error:r}=u(t.voyage.current,6e4,[e],`voyage:current`),[i,o]=(0,D.useState)(null),[s,c]=(0,D.useState)([]),[d,f]=(0,D.useState)(!0),[p,m]=(0,D.useState)(null);return(0,D.useEffect)(()=>{let e=!1;return(async()=>{try{let[n,r]=await Promise.all([t.voyage.stats(),t.voyage.list(50)]);e||(o(n),c(r),m(null))}catch(t){e||m(F(t))}finally{e||f(!1)}})(),()=>{e=!0}},[e]),{current:n??null,currentStale:r!==null,currentSeenTs:l(`voyage:current`),stats:i,list:s,loading:d,err:p}}var I=new Set;function L(e){return I.add(e),()=>{I.delete(e)}}async function te(){await Promise.all([...I].map(e=>e().catch(()=>void 0)))}var R=n();function z(e,t){let n=(t.querySelector(`.maplibregl-ctrl-attrib-inner`)?.textContent??``).replace(T,``).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 B(){return document.documentElement.dataset.theme===`day`?`day`:`night`}function V(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 H({track:e}){let t=a(),n=(0,D.useRef)(null),r=(0,D.useRef)(null),[i,o]=(0,D.useState)(null);return(0,D.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 w(t);if(c)return;let f=()=>x(B(),n,{track:{color:s,width:3}}),p=e.filter(e=>e.lat!==null&&e.lon!==null).map(e=>[e.lon,e.lat]);l=new S({container:i,style:f(),center:[7.42,43.7],zoom:9,attributionControl:{compact:!1,customAttribution:T},dragRotate:!1,pitchWithRotate:!1}),l.touchZoomRotate.disableRotation(),r.current=l;let m=l;d=L(async()=>{let e=await z(m,i);c||o(e)});let h=()=>{if(!l)return;let e=l.getSource(C);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 E({element:V(s,!1),anchor:`center`}).setLngLat(p[0]).addTo(l),new E({element:V(s,!0),anchor:`center`}).setLngLat(p[p.length-1]).addTo(l);let e=new b;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,R.jsxs)(R.Fragment,{children:[(0,R.jsx)(`div`,{ref:n,className:`vy-map`}),i&&(0,R.jsxs)(`figure`,{className:`vy-map-print`,"aria-hidden":`true`,children:[(0,R.jsx)(`img`,{src:i.url,alt:``}),(0,R.jsx)(`figcaption`,{children:i.credit})]})]})}function ne(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 re(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 ie(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 ae({view:e,onClose:t,onApplied:n}){let r=a(),[s,c]=(0,D.useState)(()=>new Set(e.selected)),[l,u]=(0,D.useState)(!1),[d,f]=(0,D.useState)(null),p=(0,D.useRef)(null),m=e=>{f(null),c(t=>{let n=new Set(t);return n.has(e)?n.delete(e):n.add(e),n})},h=async()=>{let e=[...s].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 i?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,R.jsxs)(o,{title:`Fuel source`,eyebrow:`voyage fuel`,onClose:t,closeRef:p,footer:(0,R.jsx)(`button`,{type:`button`,className:`fs-apply`,onClick:h,disabled:l,children:l?`Applying…`:`Apply`}),children:[(0,R.jsx)(`p`,{className:`fs-note`,children:`Which engines count toward voyage fuel. With none selected, every reporting engine is summed.`}),g&&(0,R.jsx)(`div`,{className:`fs-quiet`,children:g}),(0,R.jsx)(`div`,{className:`fs-list`,role:`group`,"aria-label":`Fuel-rate sources`,children:K(e).map(({path:e,reporting:t})=>{let n=s.has(e);return(0,R.jsxs)(`button`,{type:`button`,className:`fs-row${n?` on`:``}${t?``:` off-air`}`,role:`checkbox`,"aria-checked":n,onClick:()=>m(e),disabled:l,children:[(0,R.jsxs)(`span`,{className:`fs-name`,children:[W(e),!t&&(0,R.jsx)(`span`,{className:`fs-tag`,children:` not reporting`})]}),(0,R.jsx)(`span`,{className:`fs-path`,children:e})]},e)})}),d&&(0,R.jsx)(`div`,{className:`fs-err`,children:d})]}),_)}var J=`siparu.fuelMode`;function oe(){let e=localStorage.getItem(J);return O.some(t=>t.mode===e)?e:`total_l`}var se=`(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 ce(e){let{value:t,unit:n}=d((Date.now()-e)/1e3);return`${t}${n===`s`?`s`:` ${n}`} ago`}function le(){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 ue(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 de(){let e=a(),[t,n]=(0,D.useState)(0),r=ee(t),o=c(se),[s,l]=(0,D.useState)(`today`),[u,d]=(0,D.useState)([]),[f,v]=(0,D.useState)(null),[y,b]=(0,D.useState)(!1),[x,S]=(0,D.useState)({}),[C,w]=(0,D.useState)(oe),[T,E]=(0,D.useState)(null),[O,k]=(0,D.useState)(!1),[A,j]=(0,D.useState)(null),[M,N]=(0,D.useState)([]),[P,F]=(0,D.useState)(null);(0,D.useEffect)(()=>{localStorage.setItem(J,C)},[C]),(0,D.useEffect)(()=>{let t=!0;return e.health().then(e=>{t&&j(e.boat_name)}).catch(()=>{}),()=>{t=!1}},[]);let I=e.voyage.edits,L=e.voyage.mergePrevious,z=e.voyage.undoMerge;(0,D.useEffect)(()=>{if(!I)return;let e=!0;return I().then(t=>{e&&N(t.merged)}).catch(()=>{}),()=>{e=!1}},[t,I]),(0,D.useEffect)(()=>{let t=!1;return e.config.fuelPaths().then(e=>!t&&E(e)).catch(()=>{}),()=>{t=!0}},[t]);let B=!!T&&re(T)&&e.config.setFuelPaths!==void 0,V=T?q(T):null,H=r.current&&r.current.end_ts===null?r.current:null,U=async t=>{let n=u.includes(t);if(d(e=>ne(e,t)),!n&&!x[t])try{let n=await e.voyage.track(t);S(e=>({...e,[t]:n}))}catch{}},W=r.stats?.[s]??null,G=async e=>{b(!1),await te(),await le(),h(p(`Siparu-Voyage`,Date.now()),e)},K=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 i?F(X[e.code??``]??(e.detail||`That edit could not be made.`)):F(`The boat did not answer.`)}};return(0,R.jsxs)(`div`,{className:`vy`,children:[(0,R.jsxs)(`div`,{className:`vy-print-hd`,"aria-hidden":`true`,children:[(0,R.jsx)(`span`,{className:`b`,children:A??`Passage record`}),(0,R.jsx)(`span`,{className:`d`,children:new Date().toISOString().slice(0,10)})]}),H&&(0,R.jsxs)(`section`,{className:`sp-sec vy-sec-active`,children:[(0,R.jsxs)(`h2`,{className:`sp-sec-h`,children:[(0,R.jsx)(`span`,{className:`sp-sec-n`,children:`Under way`}),H.start_port?(0,R.jsxs)(`span`,{className:`sp-sec-note`,children:[`from `,H.start_port]}):null,r.currentStale?(0,R.jsxs)(`span`,{className:`sp-sec-badge quiet`,children:[`since `,$(H.start_ts),r.currentSeenTs===null?` · unreachable`:` · last seen ${ce(r.currentSeenTs)}`]}):(0,R.jsxs)(`span`,{className:`sp-sec-badge`,children:[(0,R.jsx)(`span`,{className:`vy-pulse`,"aria-hidden":`true`}),`since `,$(H.start_ts)]})]}),(0,R.jsx)(`div`,{className:`sp-glass`,children:(0,R.jsx)(fe,{v:H})})]}),(0,R.jsxs)(`section`,{className:`sp-sec vy-sec-totals`,children:[(0,R.jsxs)(`h2`,{className:`sp-sec-h`,children:[(0,R.jsx)(`span`,{className:`sp-sec-n`,children:`Totals`}),!o&&(0,R.jsx)(`span`,{className:`sp-sec-note`,children:Y.find(e=>e.k===s)?.label.toLowerCase()}),r.err?(0,R.jsx)(`span`,{className:`sp-sec-badge quiet`,children:`unreachable`}):null]}),(0,R.jsx)(`div`,{className:`sp-glass`,children:o?(0,R.jsx)(pe,{stats:r.stats,loading:r.loading}):(0,R.jsxs)(R.Fragment,{children:[(0,R.jsx)(`div`,{className:`vy-seg seg`,role:`group`,"aria-label":`Stats window`,children:Y.map(e=>(0,R.jsx)(`button`,{className:s===e.k?`on`:``,onClick:()=>l(e.k),children:e.label},e.k))}),(0,R.jsx)(me,{roll:W,loading:r.loading})]})})]}),(0,R.jsxs)(`section`,{className:`sp-sec vy-sec-voyages`,children:[(0,R.jsxs)(`h2`,{className:`sp-sec-h`,children:[(0,R.jsx)(`span`,{className:`sp-sec-n`,children:`Voyages`}),r.err?(0,R.jsx)(`span`,{className:`sp-sec-badge quiet`,children:`unreachable`}):r.loading&&r.list.length===0?null:(0,R.jsx)(`span`,{className:`sp-sec-badge`,children:r.list.length===50?`50 shown`:r.list.length})]}),(0,R.jsxs)(`div`,{className:`sp-glass`,children:[(B||r.list.length>0)&&(0,R.jsx)(`div`,{className:`vy-hd`,children:(0,R.jsxs)(`span`,{className:`vy-hd-acts`,children:[B&&(0,R.jsxs)(`button`,{type:`button`,className:`vy-fuelsrc`,onClick:()=>k(!0),children:[`Fuel · `,ie(T)]}),r.list.length>0&&(0,R.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,R.jsxs)(`span`,{className:`vy-print-pick`,role:`group`,"aria-label":`Page style`,children:[(0,R.jsx)(`button`,{type:`button`,className:`lbp-c`,onClick:()=>G(`paper`),children:`Paper`}),(0,R.jsx)(`button`,{type:`button`,className:`lbp-c`,onClick:()=>G(`screen`),children:`Screen`})]}),r.list.length>0&&(0,R.jsx)(`button`,{type:`button`,className:`vy-fuelsrc${y?` on`:``}`,"aria-expanded":y,onClick:()=>b(e=>!e),children:`Print`})]})}),r.err?(0,R.jsx)(`div`,{className:`vy-err`,children:r.err}):!r.loading&&r.list.length===0?(0,R.jsxs)(`div`,{className:`sp-empty`,children:[(0,R.jsx)(`div`,{className:`em-t`,children:`No voyages yet`}),(0,R.jsx)(`div`,{className:`em-s`,children:`Passages appear here once the boat gets under way.`})]}):(0,R.jsx)(`div`,{className:`vy-list`,children:r.list.map((e,t)=>(0,R.jsx)(he,{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:()=>U(e.id),onMerge:L?()=>K(e.id,()=>L(e.id)):void 0,onUndoMerge:z?()=>K(e.id,()=>z(e.id)):void 0,editErr:f===e.id?P:null},e.id))})]})]}),O&&T&&(0,R.jsx)(ae,{view:T,onClose:()=>k(!1),onApplied:()=>n(e=>e+1)})]})}function fe({v:e}){return(0,R.jsx)(`div`,{className:`vy-active`,children:(0,R.jsxs)(`div`,{className:`vy-active-grid`,children:[(0,R.jsxs)(`div`,{className:`vy-a-hero`,children:[(0,R.jsxs)(`div`,{className:`t`,children:[`Distance · `,(0,R.jsx)(`span`,{className:`sub`,children:`nm`})]}),(0,R.jsx)(`div`,{className:`n`,children:f(e.distance_nm,1)})]}),(0,R.jsxs)(`div`,{className:`vy-a-cell`,children:[(0,R.jsx)(`div`,{className:`t`,children:`Underway`}),(0,R.jsx)(`div`,{className:`v`,children:Z(e.hours_underway)})]}),(0,R.jsxs)(`div`,{className:`vy-a-cell`,children:[(0,R.jsx)(`div`,{className:`t`,children:`Avg SOG`}),(0,R.jsx)(`div`,{className:`v`,children:e.avg_sog_kn===null?`·`:(0,R.jsxs)(R.Fragment,{children:[e.avg_sog_kn.toFixed(1),(0,R.jsx)(`span`,{className:`u`,children:`kn`})]})})]})]})})}function pe({stats:e,loading:t}){let n=(t,n)=>e?n(e[t]):`·`,r=(e,r,i,a=!1)=>(0,R.jsxs)(R.Fragment,{children:[(0,R.jsxs)(`span`,{className:`mx-r${a?` mx-hero-r`:``}`,children:[e,r?(0,R.jsxs)(`span`,{className:`mx-u`,children:[`· `,r]}):null]}),Y.map(e=>(0,R.jsx)(`span`,{className:`mx-v${a?` mx-hero`:``}`,children:(0,R.jsx)(`span`,{className:t?`skel`:void 0,children:t?`128.4`:n(e.k,i)})},e.k))]});return(0,R.jsxs)(`div`,{className:`vy-matrix`,children:[(0,R.jsx)(`span`,{className:`mx-corner`,"aria-hidden":`true`}),Y.map(e=>(0,R.jsx)(`span`,{className:`mx-h`,children:e.label},e.k)),r(`Distance`,`nm`,e=>f(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 me({roll:e,loading:t}){let n=t?``:`·`;return(0,R.jsxs)(`div`,{className:`vy-cards`,children:[(0,R.jsxs)(`div`,{className:`c vy-hero`,children:[(0,R.jsxs)(`div`,{className:`t`,children:[`Distance · `,(0,R.jsx)(`span`,{className:`sub`,children:`nm`})]}),(0,R.jsx)(`div`,{className:`n${t?` skel`:``}`,children:t?`128.4`:e?f(e.distance_nm,1):n})]}),(0,R.jsxs)(`div`,{className:`c`,children:[(0,R.jsx)(`div`,{className:`t`,children:`Underway`}),(0,R.jsx)(`div`,{className:`v`,children:e?Z(e.hours_underway):n})]}),(0,R.jsxs)(`div`,{className:`c`,children:[(0,R.jsx)(`div`,{className:`t`,children:`Avg SOG`}),(0,R.jsx)(`div`,{className:`v`,children:e?.avg_sog_kn==null?n:(0,R.jsxs)(R.Fragment,{children:[e.avg_sog_kn.toFixed(1),(0,R.jsx)(`span`,{className:`u`,children:`kn`})]})})]}),(0,R.jsxs)(`div`,{className:`c`,children:[(0,R.jsx)(`div`,{className:`t`,children:`Max SOG`}),(0,R.jsx)(`div`,{className:`v`,children:e?.max_sog_kn==null?n:(0,R.jsxs)(R.Fragment,{children:[e.max_sog_kn.toFixed(1),(0,R.jsx)(`span`,{className:`u`,children:`kn`})]})})]})]})}function he({v:e,prev:t,wasJoined:n,open:r,track:i,fuelNotice:a,fuelMode:o,onFuelMode:c,onToggle:l,onMerge:u,onUndoMerge:d,editErr:p}){let m=e.end_ts===null,h=N(e.fuel_used_l,e.distance_nm,e.hours_underway,o),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=ue(e);return(0,R.jsxs)(`div`,{className:`vy-rowwrap${r?` open`:``}`,children:[(0,R.jsxs)(`button`,{className:`vy-row`,onClick:l,"aria-expanded":r,children:[(0,R.jsxs)(`div`,{className:`vy-row-top`,children:[(0,R.jsx)(`span`,{className:`vy-date`,children:Q(e.start_ts)}),m&&(0,R.jsx)(`span`,{className:`vy-badge`,children:`Under way`}),(0,R.jsxs)(`span`,{className:`vy-dist`,children:[f(e.distance_nm,1),(0,R.jsx)(`span`,{className:`vy-unit`,children:`nm`})]})]}),x&&(0,R.jsx)(`div`,{className:`vy-route`,children:x}),(0,R.jsxs)(`div`,{className:`vy-row-sub`,children:[y,` · `,Z(e.hours_underway),` · `,b]})]}),r&&(0,R.jsxs)(`div`,{className:`vy-detail`,children:[i&&i.length>=2?(0,R.jsx)(H,{track:i}):(0,R.jsx)(`div`,{className:`vy-track-empty`,children:i?`Track too short to plot`:`Loading track…`}),(0,R.jsxs)(`div`,{className:`vy-meta`,children:[(0,R.jsxs)(`div`,{className:`vy-m`,children:[(0,R.jsx)(`span`,{className:`k`,children:`From`}),e.start_port&&(0,R.jsx)(`span`,{className:`val`,children:e.start_port}),(0,R.jsxs)(`span`,{className:e.start_port?`coord`:`val`,children:[s(e.start_lat,[`N`,`S`],2),` · `,s(e.start_lon,[`E`,`W`],2)]})]}),(0,R.jsxs)(`div`,{className:`vy-m`,children:[(0,R.jsx)(`span`,{className:`k`,children:`To`}),m?(0,R.jsx)(`span`,{className:`val`,children:`-`}):(0,R.jsxs)(R.Fragment,{children:[e.end_port&&(0,R.jsx)(`span`,{className:`val`,children:e.end_port}),(0,R.jsxs)(`span`,{className:e.end_port?`coord`:`val`,children:[s(e.end_lat,[`N`,`S`],2),` · `,s(e.end_lon,[`E`,`W`],2)]})]})]}),(0,R.jsxs)(`div`,{className:`vy-m`,children:[(0,R.jsx)(`span`,{className:`k`,children:`Avg SOG`}),(0,R.jsx)(`span`,{className:`val`,children:e.avg_sog_kn===null?`·`:`${e.avg_sog_kn.toFixed(1)} kn`})]}),(0,R.jsxs)(`div`,{className:`vy-m`,children:[(0,R.jsx)(`span`,{className:`k`,children:`Max SOG`}),(0,R.jsx)(`span`,{className:`val`,children:e.max_sog_kn===null?`·`:`${e.max_sog_kn.toFixed(1)} kn`})]}),e.fuel_used_l===null&&a&&(0,R.jsxs)(`div`,{className:`vy-m vy-m-fuel`,children:[(0,R.jsx)(`span`,{className:`k`,children:`Fuel`}),(0,R.jsx)(`span`,{className:`val vy-fuel-quiet`,children:a})]}),e.fuel_used_l!==null&&(0,R.jsxs)(`div`,{className:`vy-m vy-m-fuel`,children:[(0,R.jsx)(`span`,{className:`k`,children:`Fuel`}),(0,R.jsx)(`span`,{className:`val`,children:h??`·`}),(0,R.jsx)(`select`,{className:`vy-fuel-sel`,value:o,"aria-label":`Fuel unit`,onChange:e=>c(e.target.value),children:O.map(e=>(0,R.jsx)(`option`,{value:e.mode,children:e.label},e.mode))})]}),i&&i.length>0&&(0,R.jsxs)(`div`,{className:`vy-m vy-m-track`,children:[(0,R.jsx)(`span`,{className:`k`,children:`Track`}),(0,R.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,R.jsxs)(`div`,{className:`vy-m vy-m-edit`,children:[(0,R.jsx)(`span`,{className:`k`,children:`Passage`}),n?(0,R.jsx)(`button`,{type:`button`,className:`vy-fuelsrc`,onClick:d,children:`Separate again`}):(0,R.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,R.jsx)(`div`,{className:`vy-m vy-m-editerr`,children:p})]})]})]})}function ge(){return(0,R.jsx)(de,{})}export{ge as default};
1
+ import{a as e}from"./rolldown-runtime-BWaJ3C7D.js";import{n as t,t as n}from"./jsx-runtime-CgYtVKZV.js";import{F as r,L as i,N as a,S as o,g as s,h as c,o as l,p as u,r as d,x as f}from"./index-zmkelveU.js";import{a as p,c as m,i as h,n as g,r as _,s as v}from"./export-Beek6XVZ.js";import{a as y,d as b,i as x,l as S,n as C,o as w,t as T,u as E}from"./style-C6gN2Vu6.js";var D=e(t(),1),O=[{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`}],k=.26417205,A=.21996923,j=1e-6;function M(e,t,n){return`${e.toFixed(n)} ${t}`}function N(e,t,n,r){if(e===null||!Number.isFinite(e))return null;switch(r){case`total_l`:return M(e,`L`,+(e<100));case`total_usgal`:return M(e*k,`US gal`,1);case`total_impgal`:return M(e*A,`Imp gal`,1);case`per_nm`:return t>j?M(e/t,`L/nm`,2):null;case`nm_per_l`:return e>j?M(t/e,`nm/L`,2):null;case`per_hour`:return n>j?M(e/n,`L/h`,1):null}}var P=`Could not load voyage data`;function F(e){return e instanceof a?e.detail||P:e instanceof DOMException&&e.name===`TimeoutError`?`She did not answer in time`:P}function ee(e=0){let t=r(),{data:n,error:i}=c(t.voyage.current,6e4,[e],`voyage:current`),[a,o]=(0,D.useState)(null),[l,u]=(0,D.useState)([]),[d,f]=(0,D.useState)(!0),[p,m]=(0,D.useState)(null);return(0,D.useEffect)(()=>{let e=!1;return(async()=>{try{let[n,r]=await Promise.all([t.voyage.stats(),t.voyage.list(50)]);e||(o(n),u(r),m(null))}catch(t){e||m(F(t))}finally{e||f(!1)}})(),()=>{e=!0}},[e]),{current:n??null,currentStale:i!==null,currentSeenTs:s(`voyage:current`),stats:a,list:l,loading:d,err:p}}var I=new Set;function L(e){return I.add(e),()=>{I.delete(e)}}async function te(){await Promise.all([...I].map(e=>e().catch(()=>void 0)))}var R=n();function z(e,t){let n=(t.querySelector(`.maplibregl-ctrl-attrib-inner`)?.textContent??``).replace(T,``).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 B(){return document.documentElement.dataset.theme===`day`?`day`:`night`}function V(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 H({track:e}){let t=r(),n=(0,D.useRef)(null),i=(0,D.useRef)(null),[a,o]=(0,D.useState)(null);return(0,D.useEffect)(()=>{if(!n.current||i.current)return;let r=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 w(t);if(c)return;let f=()=>x(B(),n,{track:{color:s,width:3}}),p=e.filter(e=>e.lat!==null&&e.lon!==null).map(e=>[e.lon,e.lat]);l=new S({container:r,style:f(),center:[7.42,43.7],zoom:9,attributionControl:{compact:!1,customAttribution:T},dragRotate:!1,pitchWithRotate:!1}),l.touchZoomRotate.disableRotation(),i.current=l;let m=l;d=L(async()=>{let e=await z(m,r);c||o(e)});let h=()=>{if(!l)return;let e=l.getSource(C);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 E({element:V(s,!1),anchor:`center`}).setLngLat(p[0]).addTo(l),new E({element:V(s,!0),anchor:`center`}).setLngLat(p[p.length-1]).addTo(l);let e=new b;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(),i.current=null}},[e]),(0,R.jsxs)(R.Fragment,{children:[(0,R.jsx)(`div`,{ref:n,className:`vy-map`}),a&&(0,R.jsxs)(`figure`,{className:`vy-map-print`,"aria-hidden":`true`,children:[(0,R.jsx)(`img`,{src:a.url,alt:``}),(0,R.jsx)(`figcaption`,{children:a.credit})]})]})}function ne(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(i(),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 re(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 ie(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 ae({view:e,onClose:t,onApplied:n}){let i=r(),[o,s]=(0,D.useState)(()=>new Set(e.selected)),[c,u]=(0,D.useState)(!1),[d,f]=(0,D.useState)(null),p=(0,D.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(!i.config.setFuelPaths)throw Error(`this setting is written aboard`);await i.config.setFuelPaths(e);let t=e.join(`,`);for(let e=0;e<12;e++){await new Promise(e=>setTimeout(e,700));try{if([...(await i.config.fuelPaths()).selected].sort().join(`,`)===t)break}catch{}}n(),p.current?.()}catch(e){e instanceof a?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,R.jsxs)(l,{title:`Fuel source`,eyebrow:`voyage fuel`,onClose:t,closeRef:p,footer:(0,R.jsx)(`button`,{type:`button`,className:`fs-apply`,onClick:h,disabled:c,children:c?`Applying…`:`Apply`}),children:[(0,R.jsx)(`p`,{className:`fs-note`,children:`Which engines count toward voyage fuel. With none selected, every reporting engine is summed.`}),g&&(0,R.jsx)(`div`,{className:`fs-quiet`,children:g}),(0,R.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,R.jsxs)(`button`,{type:`button`,className:`fs-row${n?` on`:``}${t?``:` off-air`}`,role:`checkbox`,"aria-checked":n,onClick:()=>m(e),disabled:c,children:[(0,R.jsxs)(`span`,{className:`fs-name`,children:[W(e),!t&&(0,R.jsx)(`span`,{className:`fs-tag`,children:` not reporting`})]}),(0,R.jsx)(`span`,{className:`fs-path`,children:e})]},e)})}),d&&(0,R.jsx)(`div`,{className:`fs-err`,children:d})]}),_)}var J=`siparu.fuelMode`;function oe(){let e=localStorage.getItem(J);return O.some(t=>t.mode===e)?e:`total_l`}var se=`(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 ce(e){let{value:t,unit:n}=d((Date.now()-e)/1e3);return`${t}${n===`s`?`s`:` ${n}`} ago`}function le(){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 ue(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 de(){let e=r(),[t,n]=(0,D.useState)(0),i=ee(t),o=u(se),[s,c]=(0,D.useState)(`today`),[l,d]=(0,D.useState)([]),[f,v]=(0,D.useState)(null),[y,b]=(0,D.useState)(!1),[x,S]=(0,D.useState)({}),[C,w]=(0,D.useState)(oe),[T,E]=(0,D.useState)(null),[O,k]=(0,D.useState)(!1),[A,j]=(0,D.useState)(null),[M,N]=(0,D.useState)([]),[P,F]=(0,D.useState)(null);(0,D.useEffect)(()=>{localStorage.setItem(J,C)},[C]),(0,D.useEffect)(()=>{let t=!0;return e.health().then(e=>{t&&j(e.boat_name)}).catch(()=>{}),()=>{t=!1}},[]);let I=e.voyage.edits,L=e.voyage.mergePrevious,z=e.voyage.undoMerge;(0,D.useEffect)(()=>{if(!I)return;let e=!0;return I().then(t=>{e&&N(t.merged)}).catch(()=>{}),()=>{e=!1}},[t,I]),(0,D.useEffect)(()=>{let t=!1;return e.config.fuelPaths().then(e=>!t&&E(e)).catch(()=>{}),()=>{t=!0}},[t]);let B=!!T&&re(T)&&e.config.setFuelPaths!==void 0,V=T?q(T):null,H=i.current&&i.current.end_ts===null?i.current:null,U=async t=>{let n=l.includes(t);if(d(e=>ne(e,t)),!n&&!x[t])try{let n=await e.voyage.track(t);S(e=>({...e,[t]:n}))}catch{}},W=i.stats?.[s]??null,G=async e=>{b(!1),await te(),await le(),h(p(`Siparu-Voyage`,Date.now()),e)},K=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 a?F(X[e.code??``]??(e.detail||`That edit could not be made.`)):F(`The boat did not answer.`)}};return(0,R.jsxs)(`div`,{className:`vy`,children:[(0,R.jsxs)(`div`,{className:`vy-print-hd`,"aria-hidden":`true`,children:[(0,R.jsx)(`span`,{className:`b`,children:A??`Passage record`}),(0,R.jsx)(`span`,{className:`d`,children:new Date().toISOString().slice(0,10)})]}),H&&(0,R.jsxs)(`section`,{className:`sp-sec vy-sec-active`,children:[(0,R.jsxs)(`h2`,{className:`sp-sec-h`,children:[(0,R.jsx)(`span`,{className:`sp-sec-n`,children:`Under way`}),H.start_port?(0,R.jsxs)(`span`,{className:`sp-sec-note`,children:[`from `,H.start_port]}):null,i.currentStale?(0,R.jsxs)(`span`,{className:`sp-sec-badge quiet`,children:[`since `,$(H.start_ts),i.currentSeenTs===null?` · unreachable`:` · last seen ${ce(i.currentSeenTs)}`]}):(0,R.jsxs)(`span`,{className:`sp-sec-badge`,children:[(0,R.jsx)(`span`,{className:`vy-pulse`,"aria-hidden":`true`}),`since `,$(H.start_ts)]})]}),(0,R.jsx)(`div`,{className:`sp-glass`,children:(0,R.jsx)(fe,{v:H})})]}),(0,R.jsxs)(`section`,{className:`sp-sec vy-sec-totals`,children:[(0,R.jsxs)(`h2`,{className:`sp-sec-h`,children:[(0,R.jsx)(`span`,{className:`sp-sec-n`,children:`Totals`}),!o&&(0,R.jsx)(`span`,{className:`sp-sec-note`,children:Y.find(e=>e.k===s)?.label.toLowerCase()}),i.err?(0,R.jsx)(`span`,{className:`sp-sec-badge quiet`,children:`unreachable`}):null]}),(0,R.jsx)(`div`,{className:`sp-glass`,children:o?(0,R.jsx)(pe,{stats:i.stats,loading:i.loading}):(0,R.jsxs)(R.Fragment,{children:[(0,R.jsx)(`div`,{className:`vy-seg seg`,role:`group`,"aria-label":`Stats window`,children:Y.map(e=>(0,R.jsx)(`button`,{className:s===e.k?`on`:``,onClick:()=>c(e.k),children:e.label},e.k))}),(0,R.jsx)(me,{roll:W,loading:i.loading})]})})]}),(0,R.jsxs)(`section`,{className:`sp-sec vy-sec-voyages`,children:[(0,R.jsxs)(`h2`,{className:`sp-sec-h`,children:[(0,R.jsx)(`span`,{className:`sp-sec-n`,children:`Voyages`}),i.err?(0,R.jsx)(`span`,{className:`sp-sec-badge quiet`,children:`unreachable`}):i.loading&&i.list.length===0?null:(0,R.jsx)(`span`,{className:`sp-sec-badge`,children:i.list.length===50?`50 shown`:i.list.length})]}),(0,R.jsxs)(`div`,{className:`sp-glass`,children:[(B||i.list.length>0)&&(0,R.jsx)(`div`,{className:`vy-hd`,children:(0,R.jsxs)(`span`,{className:`vy-hd-acts`,children:[B&&(0,R.jsxs)(`button`,{type:`button`,className:`vy-fuelsrc`,onClick:()=>k(!0),children:[`Fuel · `,ie(T)]}),i.list.length>0&&(0,R.jsx)(`button`,{type:`button`,className:`vy-fuelsrc`,onClick:()=>g(_(`siparu-voyages`,Date.now(),`csv`),`text/csv`,m(i.list)),children:`CSV`}),i.list.length>0&&y&&(0,R.jsxs)(`span`,{className:`vy-print-pick`,role:`group`,"aria-label":`Page style`,children:[(0,R.jsx)(`button`,{type:`button`,className:`lbp-c`,onClick:()=>G(`paper`),children:`Paper`}),(0,R.jsx)(`button`,{type:`button`,className:`lbp-c`,onClick:()=>G(`screen`),children:`Screen`})]}),i.list.length>0&&(0,R.jsx)(`button`,{type:`button`,className:`vy-fuelsrc${y?` on`:``}`,"aria-expanded":y,onClick:()=>b(e=>!e),children:`Print`})]})}),i.err?(0,R.jsx)(`div`,{className:`vy-err`,children:i.err}):!i.loading&&i.list.length===0?(0,R.jsxs)(`div`,{className:`sp-empty`,children:[(0,R.jsx)(`div`,{className:`em-t`,children:`No voyages yet`}),(0,R.jsx)(`div`,{className:`em-s`,children:`Passages appear here once the boat gets under way.`})]}):(0,R.jsx)(`div`,{className:`vy-list`,children:i.list.map((e,t)=>(0,R.jsx)(he,{v:e,prev:i.list[t+1],wasJoined:M.includes(e.id),open:l.includes(e.id),track:x[e.id],fuelNotice:V,fuelMode:C,onFuelMode:w,onToggle:()=>U(e.id),onMerge:L?()=>K(e.id,()=>L(e.id)):void 0,onUndoMerge:z?()=>K(e.id,()=>z(e.id)):void 0,editErr:f===e.id?P:null},e.id))})]})]}),O&&T&&(0,R.jsx)(ae,{view:T,onClose:()=>k(!1),onApplied:()=>n(e=>e+1)})]})}function fe({v:e}){return(0,R.jsx)(`div`,{className:`vy-active`,children:(0,R.jsxs)(`div`,{className:`vy-active-grid`,children:[(0,R.jsxs)(`div`,{className:`vy-a-hero`,children:[(0,R.jsxs)(`div`,{className:`t`,children:[`Distance · `,(0,R.jsx)(`span`,{className:`sub`,children:`nm`})]}),(0,R.jsx)(`div`,{className:`n`,children:o(e.distance_nm,1)})]}),(0,R.jsxs)(`div`,{className:`vy-a-cell`,children:[(0,R.jsx)(`div`,{className:`t`,children:`Underway`}),(0,R.jsx)(`div`,{className:`v`,children:Z(e.hours_underway)})]}),(0,R.jsxs)(`div`,{className:`vy-a-cell`,children:[(0,R.jsx)(`div`,{className:`t`,children:`Avg SOG`}),(0,R.jsx)(`div`,{className:`v`,children:e.avg_sog_kn===null?`·`:(0,R.jsxs)(R.Fragment,{children:[e.avg_sog_kn.toFixed(1),(0,R.jsx)(`span`,{className:`u`,children:`kn`})]})})]})]})})}function pe({stats:e,loading:t}){let n=(t,n)=>e?n(e[t]):`·`,r=(e,r,i,a=!1)=>(0,R.jsxs)(R.Fragment,{children:[(0,R.jsxs)(`span`,{className:`mx-r${a?` mx-hero-r`:``}`,children:[e,r?(0,R.jsxs)(`span`,{className:`mx-u`,children:[`· `,r]}):null]}),Y.map(e=>(0,R.jsx)(`span`,{className:`mx-v${a?` mx-hero`:``}`,children:(0,R.jsx)(`span`,{className:t?`skel`:void 0,children:t?`128.4`:n(e.k,i)})},e.k))]});return(0,R.jsxs)(`div`,{className:`vy-matrix`,children:[(0,R.jsx)(`span`,{className:`mx-corner`,"aria-hidden":`true`}),Y.map(e=>(0,R.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 me({roll:e,loading:t}){let n=t?``:`·`;return(0,R.jsxs)(`div`,{className:`vy-cards`,children:[(0,R.jsxs)(`div`,{className:`c vy-hero`,children:[(0,R.jsxs)(`div`,{className:`t`,children:[`Distance · `,(0,R.jsx)(`span`,{className:`sub`,children:`nm`})]}),(0,R.jsx)(`div`,{className:`n${t?` skel`:``}`,children:t?`128.4`:e?o(e.distance_nm,1):n})]}),(0,R.jsxs)(`div`,{className:`c`,children:[(0,R.jsx)(`div`,{className:`t`,children:`Underway`}),(0,R.jsx)(`div`,{className:`v`,children:e?Z(e.hours_underway):n})]}),(0,R.jsxs)(`div`,{className:`c`,children:[(0,R.jsx)(`div`,{className:`t`,children:`Avg SOG`}),(0,R.jsx)(`div`,{className:`v`,children:e?.avg_sog_kn==null?n:(0,R.jsxs)(R.Fragment,{children:[e.avg_sog_kn.toFixed(1),(0,R.jsx)(`span`,{className:`u`,children:`kn`})]})})]}),(0,R.jsxs)(`div`,{className:`c`,children:[(0,R.jsx)(`div`,{className:`t`,children:`Max SOG`}),(0,R.jsx)(`div`,{className:`v`,children:e?.max_sog_kn==null?n:(0,R.jsxs)(R.Fragment,{children:[e.max_sog_kn.toFixed(1),(0,R.jsx)(`span`,{className:`u`,children:`kn`})]})})]})]})}function he({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=N(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=ue(e);return(0,R.jsxs)(`div`,{className:`vy-rowwrap${r?` open`:``}`,children:[(0,R.jsxs)(`button`,{className:`vy-row`,onClick:l,"aria-expanded":r,children:[(0,R.jsxs)(`div`,{className:`vy-row-top`,children:[(0,R.jsx)(`span`,{className:`vy-date`,children:Q(e.start_ts)}),m&&(0,R.jsx)(`span`,{className:`vy-badge`,children:`Under way`}),(0,R.jsxs)(`span`,{className:`vy-dist`,children:[o(e.distance_nm,1),(0,R.jsx)(`span`,{className:`vy-unit`,children:`nm`})]})]}),x&&(0,R.jsx)(`div`,{className:`vy-route`,children:x}),(0,R.jsxs)(`div`,{className:`vy-row-sub`,children:[y,` · `,Z(e.hours_underway),` · `,b]})]}),r&&(0,R.jsxs)(`div`,{className:`vy-detail`,children:[i&&i.length>=2?(0,R.jsx)(H,{track:i}):(0,R.jsx)(`div`,{className:`vy-track-empty`,children:i?`Track too short to plot`:`Loading track…`}),(0,R.jsxs)(`div`,{className:`vy-meta`,children:[(0,R.jsxs)(`div`,{className:`vy-m`,children:[(0,R.jsx)(`span`,{className:`k`,children:`From`}),e.start_port&&(0,R.jsx)(`span`,{className:`val`,children:e.start_port}),(0,R.jsxs)(`span`,{className:e.start_port?`coord`:`val`,children:[f(e.start_lat,[`N`,`S`],2),` · `,f(e.start_lon,[`E`,`W`],2)]})]}),(0,R.jsxs)(`div`,{className:`vy-m`,children:[(0,R.jsx)(`span`,{className:`k`,children:`To`}),m?(0,R.jsx)(`span`,{className:`val`,children:`-`}):(0,R.jsxs)(R.Fragment,{children:[e.end_port&&(0,R.jsx)(`span`,{className:`val`,children:e.end_port}),(0,R.jsxs)(`span`,{className:e.end_port?`coord`:`val`,children:[f(e.end_lat,[`N`,`S`],2),` · `,f(e.end_lon,[`E`,`W`],2)]})]})]}),(0,R.jsxs)(`div`,{className:`vy-m`,children:[(0,R.jsx)(`span`,{className:`k`,children:`Avg SOG`}),(0,R.jsx)(`span`,{className:`val`,children:e.avg_sog_kn===null?`·`:`${e.avg_sog_kn.toFixed(1)} kn`})]}),(0,R.jsxs)(`div`,{className:`vy-m`,children:[(0,R.jsx)(`span`,{className:`k`,children:`Max SOG`}),(0,R.jsx)(`span`,{className:`val`,children:e.max_sog_kn===null?`·`:`${e.max_sog_kn.toFixed(1)} kn`})]}),e.fuel_used_l===null&&a&&(0,R.jsxs)(`div`,{className:`vy-m vy-m-fuel`,children:[(0,R.jsx)(`span`,{className:`k`,children:`Fuel`}),(0,R.jsx)(`span`,{className:`val vy-fuel-quiet`,children:a})]}),e.fuel_used_l!==null&&(0,R.jsxs)(`div`,{className:`vy-m vy-m-fuel`,children:[(0,R.jsx)(`span`,{className:`k`,children:`Fuel`}),(0,R.jsx)(`span`,{className:`val`,children:h??`·`}),(0,R.jsx)(`select`,{className:`vy-fuel-sel`,value:s,"aria-label":`Fuel unit`,onChange:e=>c(e.target.value),children:O.map(e=>(0,R.jsx)(`option`,{value:e.mode,children:e.label},e.mode))})]}),i&&i.length>0&&(0,R.jsxs)(`div`,{className:`vy-m vy-m-track`,children:[(0,R.jsx)(`span`,{className:`k`,children:`Track`}),(0,R.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,R.jsxs)(`div`,{className:`vy-m vy-m-edit`,children:[(0,R.jsx)(`span`,{className:`k`,children:`Passage`}),n?(0,R.jsx)(`button`,{type:`button`,className:`vy-fuelsrc`,onClick:d,children:`Separate again`}):(0,R.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,R.jsx)(`div`,{className:`vy-m vy-m-editerr`,children:p})]})]})]})}function ge(){return(0,R.jsx)(de,{})}export{ge as default};
@@ -1,4 +1,4 @@
1
- var e={lat:`position`,lon:`position`,sog:`linear`,rate_of_turn:`linear`,magnetic_variation:`linear`,magnetic_deviation:`linear`,wind_speed_apparent:`linear`,wind_speed_true:`linear`,wind_gust:`linear`,air_temp_k:`linear`,air_pressure_pa:`linear`,depth:`linear`,water_temp_k:`linear`,gps_satellites:`linear`,cog:`angular`,heading_mag:`angular`,heading_true:`angular`,wind_angle_apparent:`angular`,wind_direction_true:`angular`,nav_state:`text`,ais_class:`text`,depth_datum:`text`};function t(...t){return Object.keys(e).filter(n=>t.includes(e[n]))}var n=3440.065,r=t(`linear`),i=t(`angular`,`text`);function a(e,t,r,i){let a=e=>e*Math.PI/180,o=a(r-e),s=a(i-t),c=Math.sin(o/2)**2+Math.cos(a(e))*Math.cos(a(r))*Math.sin(s/2)**2;return 2*n*Math.asin(Math.min(1,Math.sqrt(c)))}function o(e){let t=1/0,n=-1/0,r=0,i=0,a=null;for(let o of e){if(typeof o.min!=`number`||typeof o.max!=`number`||typeof o.avg!=`number`)continue;let e=o.n??1;o.min<t&&(t=o.min),o.max>n&&(n=o.max),r+=o.avg*e,i+=e,o.last!==null&&o.last!==void 0&&(a=o.last)}return i>0?{min:t,max:n,avg:r/i,n:i,last:a}:null}function s(e,t=80){let n=[...e].sort((e,t)=>e.hour.localeCompare(t.hour)),s={};for(let e of r){let t=o(n.map(t=>t.metrics[e]).filter(e=>e!==void 0));t&&(s[e]=t)}for(let e of i)for(let t=n.length-1;t>=0;t--){let r=n[t]?.metrics[e];if(r&&r.last!==null&&r.last!==void 0){s[e]={last:r.last};break}}let c={},l=new Set;for(let e of n)if(e.path_metrics)for(let t of Object.keys(e.path_metrics))l.add(t);for(let e of l){let t=o(n.map(t=>t.path_metrics?.[e]).filter(e=>e!==void 0));t&&(c[e]=t)}let u=n.reduce((e,t)=>e+t.distance_nm,0);for(let e=0;e<n.length-1;e++){let r=n[e],i=n[e+1];if(!r?.pos_last||!i?.pos_first)continue;let o=(i.first_ts-r.last_ts)/36e5;if(o<=0)continue;let s=a(r.pos_last.lat,r.pos_last.lon,i.pos_first.lat,i.pos_first.lon);s/o<=t&&(u+=s)}return{count:n.reduce((e,t)=>e+t.count,0),first_ts:n[0]?.first_ts??0,last_ts:n[n.length-1]?.last_ts??0,distance_nm:u,pos_first:n.map(e=>e.pos_first).find(e=>e!==null)??null,pos_last:[...n].reverse().map(e=>e.pos_last).find(e=>e!==null)??null,metrics:s,...Object.keys(c).length>0?{path_metrics:c}:{}}}function c(e){return new Date(e).toISOString().slice(0,10)}var l={last:`Last`,avg:`Average`,min:`Minimum`,max:`Maximum`},u={last:`last`,avg:`avg`,min:`min`,max:`max`};function d(e,t){if(t===`1h`)return e.hour;if(t===`1d`)return c(e.last_ts);let n=Math.floor(e.last_ts/(6*36e5));return`${c(e.last_ts)}#${n%4}`}function f(e,t){let n=new Map;for(let r of e){let e=d(r,t),i=n.get(e);i?i.push(r):n.set(e,[r])}return[...n.entries()].map(([e,t])=>({key:e,...s(t)})).sort((e,t)=>e.last_ts-t.last_ts)}function p(t,n){let r={ts:t.last_ts,lat:t.pos_last?.lat??null,lon:t.pos_last?.lon??null},i=e=>e===void 0?null:e;for(let[a,o]of Object.entries(e)){if(o===`position`)continue;let e=a,s=t.metrics[e];r[e]=o===`linear`?i(s?.[n]):n===`last`?i(s?.last):null}let a={};for(let[e,r]of Object.entries(t.path_metrics??{})){let t=r[n];typeof t==`number`&&(a[e]=t)}return Object.keys(a).length>0&&(r.path_values=a),r}function m(e){if(e===null)return``;let t=String(e);return/[",\n\r]/.test(t)?`"${t.replace(/"/g,`""`)}"`:t}function h(e){if(!(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`}var g=e=>e===null?null:new Date(e).toISOString(),_=[`id`,`start_utc`,`end_utc`,`duration`,`from`,`to`,`distance_nm`,`hours_underway`,`avg_sog_kn`,`max_sog_kn`,`fuel_used_l`,`start_lat`,`start_lon`,`end_lat`,`end_lon`];function v(e){let t=[...e].sort((e,t)=>e.start_ts-t.start_ts),n=[_.join(`,`)];for(let e of t)n.push([e.id,g(e.start_ts),g(e.end_ts),h(e.hours_underway),e.start_port,e.end_port,e.distance_nm,e.hours_underway,e.avg_sog_kn,e.max_sog_kn,e.fuel_used_l,e.start_lat,e.start_lon,e.end_lat,e.end_lon].map(m).join(`,`));return n.join(`\r
1
+ var e={lat:`position`,lon:`position`,sog:`linear`,rate_of_turn:`linear`,magnetic_variation:`linear`,magnetic_deviation:`linear`,wind_speed_apparent:`linear`,wind_speed_true:`linear`,wind_gust:`linear`,air_temp_k:`linear`,air_pressure_pa:`linear`,depth:`linear`,water_temp_k:`linear`,gps_satellites:`linear`,cog:`angular`,heading_mag:`angular`,heading_true:`angular`,wind_angle_apparent:`angular`,wind_direction_true:`angular`,nav_state:`text`,ais_class:`text`,depth_datum:`text`};function t(...t){return Object.keys(e).filter(n=>t.includes(e[n]))}var n=3440.065,r=t(`linear`),i=t(`angular`,`text`);function a(e,t,r,i){let a=e=>e*Math.PI/180,o=a(r-e),s=a(i-t),c=Math.sin(o/2)**2+Math.cos(a(e))*Math.cos(a(r))*Math.sin(s/2)**2;return 2*n*Math.asin(Math.min(1,Math.sqrt(c)))}function o(e){let t=1/0,n=-1/0,r=0,i=0,a=null;for(let o of e){if(typeof o.min!=`number`||typeof o.max!=`number`||typeof o.avg!=`number`)continue;let e=o.n??1;o.min<t&&(t=o.min),o.max>n&&(n=o.max),r+=o.avg*e,i+=e,o.last!==null&&o.last!==void 0&&(a=o.last)}return i>0?{min:t,max:n,avg:r/i,n:i,last:a}:null}function s(e,t=80){let n=[...e].sort((e,t)=>e.hour.localeCompare(t.hour)),s={};for(let e of r){let t=o(n.map(t=>t.metrics[e]).filter(e=>e!==void 0));t&&(s[e]=t)}for(let e of i)for(let t=n.length-1;t>=0;t--){let r=n[t]?.metrics[e];if(r&&r.last!==null&&r.last!==void 0){s[e]={last:r.last};break}}let c={},l=new Set;for(let e of n)if(e.path_metrics)for(let t of Object.keys(e.path_metrics))l.add(t);for(let e of l){let t=o(n.map(t=>t.path_metrics?.[e]).filter(e=>e!==void 0));t&&(c[e]=t)}let u=n.reduce((e,t)=>e+t.distance_nm,0);for(let e=0;e<n.length-1;e++){let r=n[e],i=n[e+1];if(!r?.pos_last||!i?.pos_first)continue;let o=(i.first_ts-r.last_ts)/36e5;if(o<=0)continue;let s=a(r.pos_last.lat,r.pos_last.lon,i.pos_first.lat,i.pos_first.lon);s/o<=t&&(u+=s)}return{count:n.reduce((e,t)=>e+t.count,0),first_ts:n[0]?.first_ts??0,last_ts:n[n.length-1]?.last_ts??0,distance_nm:u,pos_first:n.map(e=>e.pos_first).find(e=>e!==null)??null,pos_last:[...n].reverse().map(e=>e.pos_last).find(e=>e!==null)??null,metrics:s,...Object.keys(c).length>0?{path_metrics:c}:{}}}function c(e){return new Date(e).toISOString().slice(0,10)}var l={last:`Last`,avg:`Average`,min:`Minimum`,max:`Maximum`},u={last:`last`,avg:`avg`,min:`min`,max:`max`};function d(e,t){if(t===`1h`)return e.hour;if(t===`1d`)return c(e.last_ts);let n=Math.floor(e.last_ts/216e5);return`${c(e.last_ts)}#${n%4}`}function f(e,t){let n=new Map;for(let r of e){let e=d(r,t),i=n.get(e);i?i.push(r):n.set(e,[r])}return[...n.entries()].map(([e,t])=>({key:e,...s(t)})).sort((e,t)=>e.last_ts-t.last_ts)}function p(t,n){let r={ts:t.last_ts,lat:t.pos_last?.lat??null,lon:t.pos_last?.lon??null},i=e=>e===void 0?null:e;for(let[a,o]of Object.entries(e)){if(o===`position`)continue;let e=a,s=t.metrics[e];r[e]=o===`linear`?i(s?.[n]):n===`last`?i(s?.last):null}let a={};for(let[e,r]of Object.entries(t.path_metrics??{})){let t=r[n];typeof t==`number`&&(a[e]=t)}return Object.keys(a).length>0&&(r.path_values=a),r}function m(e){if(e===null)return``;let t=String(e);return/[",\n\r]/.test(t)?`"${t.replace(/"/g,`""`)}"`:t}function h(e){if(!(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`}var g=e=>e===null?null:new Date(e).toISOString(),_=[`id`,`start_utc`,`end_utc`,`duration`,`from`,`to`,`distance_nm`,`hours_underway`,`avg_sog_kn`,`max_sog_kn`,`fuel_used_l`,`start_lat`,`start_lon`,`end_lat`,`end_lon`];function v(e){let t=[...e].sort((e,t)=>e.start_ts-t.start_ts),n=[_.join(`,`)];for(let e of t)n.push([e.id,g(e.start_ts),g(e.end_ts),h(e.hours_underway),e.start_port,e.end_port,e.distance_nm,e.hours_underway,e.avg_sog_kn,e.max_sog_kn,e.fuel_used_l,e.start_lat,e.start_lon,e.end_lat,e.end_lon].map(m).join(`,`));return n.join(`\r
2
2
  `)+`\r
3
3
  `}function y(e,t){let n=t.slice(1),r=[[`utc`,...n.map(e=>e.head)].map(m).join(`,`)];for(let t of[...e].sort((e,t)=>e.ts-t.ts))r.push([g(t.ts),...n.map(e=>e.cell(t))].map(m).join(`,`));return r.join(`\r
4
4
  `)+`\r