signalk-siparu 0.2.3 → 0.2.5

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
@@ -43,6 +43,17 @@ generator tab. The same gauges are also served over the API (`GET /live`,
43
43
  Pi to a Victron Cerbo GX (Venus OS Large). Nothing here needs node-gyp.
44
44
  - **Compatibility floor:** Node 20 + signalk-server 2.18.
45
45
 
46
+ ## Not a safety system
47
+
48
+ Siparu is not a navigation, safety or alarm system, and it is not a substitute for
49
+ seamanship, a watchkeeper, or going aboard to look. It reads instruments that
50
+ drift, break and lie, and it carries what they say over networks that fail,
51
+ starting with the boat's own uplink: a screen with nothing wrong on it is not the
52
+ same thing as a boat that is fine, and the plugin raises no alarm to tell you
53
+ otherwise. The chart is for looking, not for navigating, and a depth reading is
54
+ whichever plane the boat's own sounder reports, which is not always the water
55
+ under the keel. Do not rely on this software to protect life or property.
56
+
46
57
  ## Before you pair: turn on Signal K security
47
58
 
48
59
  Signal K ships with security switched off, and nothing in the setup makes you turn
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "signalk-siparu",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "description": "Kept aboard, proven ashore. An impartial, timestamped record of every voyage: position, wind, depth and logbook, written on the boat and readable from anywhere. A read-only Signal K plugin with a built-in dashboard.",
5
5
  "keywords": [
6
6
  "signalk-node-server-plugin",
@@ -254,7 +254,7 @@ module.exports = (app) => {
254
254
  const plugin = {
255
255
  id: PLUGIN_ID,
256
256
  name: 'Siparu',
257
- description: 'Kept aboard, proven ashore: records position, wind, depth and voyage history on the boat and serves a read-only dashboard. Never writes to the boat. Turn Signal K security on (add an admin user) before pairing: with it off these endpoints answer anyone on your network, and someone else can link this vessel to their account.',
257
+ description: 'Kept aboard, proven ashore: records position, wind, depth and voyage history on the boat and serves a read-only dashboard. Never writes to the boat. Not a navigation, safety or alarm system: it raises no alarm, and a screen with nothing wrong on it is not the same thing as a boat that is fine. Turn Signal K security on (add an admin user) before pairing: with it off these endpoints answer anyone on your network, and someone else can link this vessel to their account.',
258
258
  schema: config_1.CONFIG_SCHEMA,
259
259
  start(config, restart) {
260
260
  const gen = ++startGen;
@@ -19,6 +19,7 @@ export interface LiveStatus {
19
19
  lastFrameTs: number | null;
20
20
  failures: number;
21
21
  rejected: boolean;
22
+ unentitled: boolean;
22
23
  lastError: string | null;
23
24
  }
24
25
  export interface LiveDeps {
@@ -66,6 +67,7 @@ export declare class LiveUplink {
66
67
  private lastFrameTs;
67
68
  private lastSog;
68
69
  private rejected;
70
+ private unentitled;
69
71
  private lastError;
70
72
  private readonly fixedFrameMs;
71
73
  private readonly pingEveryMs;
@@ -14,7 +14,9 @@ const STAND_OFF_MS = 15 * 60_000;
14
14
  const UNPAIRED_RECHECK_MS = 60_000;
15
15
  const CLOSE_UNKNOWN_TOKEN = 1008;
16
16
  const CLOSE_REPLACED = 1012;
17
+ const CLOSE_PLAN_REQUIRED = 4403;
17
18
  const REFUSED_UNKNOWN_TOKEN = 401;
19
+ const REFUSED_PLAN_REQUIRED = 403;
18
20
  const HANDSHAKE_TIMEOUT_MS = 15_000;
19
21
  class LiveUplink {
20
22
  deps;
@@ -30,6 +32,7 @@ class LiveUplink {
30
32
  lastFrameTs = null;
31
33
  lastSog = null;
32
34
  rejected = false;
35
+ unentitled = false;
33
36
  lastError = null;
34
37
  fixedFrameMs;
35
38
  pingEveryMs;
@@ -71,12 +74,14 @@ class LiveUplink {
71
74
  lastFrameTs: this.lastFrameTs,
72
75
  failures: this.failures,
73
76
  rejected: this.rejected,
77
+ unentitled: this.unentitled,
74
78
  lastError: this.lastError
75
79
  };
76
80
  }
77
81
  reset() {
78
82
  this.failures = 0;
79
83
  this.rejected = false;
84
+ this.unentitled = false;
80
85
  this.lastError = null;
81
86
  this.lastFrameTs = null;
82
87
  if (this.stopped)
@@ -116,6 +121,7 @@ class LiveUplink {
116
121
  this.connected = true;
117
122
  this.failures = 0;
118
123
  this.rejected = false;
124
+ this.unentitled = false;
119
125
  this.lastError = null;
120
126
  this.awaitingPong = false;
121
127
  this.sendFrame(gen);
@@ -171,7 +177,11 @@ class LiveUplink {
171
177
  if (gen !== this.gen)
172
178
  return;
173
179
  this.kill(sock);
174
- this.closed(gen, status === REFUSED_UNKNOWN_TOKEN ? CLOSE_UNKNOWN_TOKEN : 1006);
180
+ this.closed(gen, status === REFUSED_UNKNOWN_TOKEN
181
+ ? CLOSE_UNKNOWN_TOKEN
182
+ : status === REFUSED_PLAN_REQUIRED
183
+ ? CLOSE_PLAN_REQUIRED
184
+ : 1006);
175
185
  });
176
186
  }
177
187
  kill(sock) {
@@ -413,6 +423,14 @@ class LiveUplink {
413
423
  this.redial(STAND_OFF_MS);
414
424
  return;
415
425
  }
426
+ if (code === CLOSE_PLAN_REQUIRED) {
427
+ this.unentitled = true;
428
+ this.lastError =
429
+ 'Remote watching is not active on this account. She is recording as usual, and starts sending again when it is.';
430
+ this.deps.debug(`live uplink: ${this.lastError}`);
431
+ this.redial(STAND_OFF_MS);
432
+ return;
433
+ }
416
434
  if (code === CLOSE_REPLACED) {
417
435
  this.lastError = 'Another copy of this boat is connected to Siparu.';
418
436
  this.deps.debug(`live uplink: ${this.lastError}`);
@@ -3,12 +3,15 @@ export interface UplinkStatus {
3
3
  lastSentTs: number | null;
4
4
  failures: number;
5
5
  rejected: boolean;
6
+ unentitled: boolean;
6
7
  lastError: string | null;
7
8
  }
8
9
  export declare function reportedStatus(socket: {
9
10
  connected: boolean;
10
11
  lastFrameTs: number | null;
11
12
  rejected?: boolean;
13
+ unentitled?: boolean;
14
+ lastError?: string | null;
12
15
  } | null | undefined, post: UplinkStatus | null): UplinkStatus | null;
13
16
  export interface UplinkDeps {
14
17
  relayUrl: string;
@@ -4,13 +4,29 @@ exports.Uplink = void 0;
4
4
  exports.reportedStatus = reportedStatus;
5
5
  function reportedStatus(socket, post) {
6
6
  if (socket?.connected) {
7
- return { lastSentTs: socket.lastFrameTs, failures: 0, rejected: false, lastError: null };
7
+ return {
8
+ lastSentTs: socket.lastFrameTs,
9
+ failures: 0,
10
+ rejected: false,
11
+ unentitled: false,
12
+ lastError: null
13
+ };
14
+ }
15
+ if (socket?.unentitled) {
16
+ return {
17
+ lastSentTs: socket.lastFrameTs,
18
+ failures: 0,
19
+ rejected: false,
20
+ unentitled: true,
21
+ lastError: socket.lastError ?? 'Remote watching is not active on this account.'
22
+ };
8
23
  }
9
24
  if (socket?.rejected) {
10
25
  return {
11
26
  lastSentTs: socket.lastFrameTs,
12
27
  failures: 0,
13
28
  rejected: true,
29
+ unentitled: false,
14
30
  lastError: 'Siparu no longer recognises this boat. Pair her again.'
15
31
  };
16
32
  }
@@ -53,6 +69,7 @@ class Uplink {
53
69
  lastSentTs: this.lastSentTs,
54
70
  failures: this.failures,
55
71
  rejected: this.rejected,
72
+ unentitled: false,
56
73
  lastError: this.lastError
57
74
  };
58
75
  }
@@ -1 +1 @@
1
- import{a as e}from"./rolldown-runtime-CNC7AqOf.js";import{_ as t,c as n,g as r,i,l as a,n as o,o as s,p as c,s as l,t as u,u as d,y as f}from"./index-5gz9-vQd.js";import{t as p}from"./visibleInterval-CFxPzgzx.js";var m=e(f(),1),h={"1m":1,"1h":60,"6h":360,"1d":1440},g={"1m":60,"1h":48,"6h":40,"1d":30},_=15e3;function v(){let[e,t]=(0,m.useState)(`1h`),[n,i]=(0,m.useState)(0),[a,o]=(0,m.useState)(!1),[s,c]=(0,m.useState)([]),[l,u]=(0,m.useState)(null),[d,f]=(0,m.useState)(!1),v=h[e],y=g[e]+n*g[e],b=(0,m.useCallback)(e=>{t(e),i(0),c([]),o(!1)},[]),x=(0,m.useCallback)(async()=>{f(!0),u(null);try{let e=y+1,t=await r.logbook.snapshots({bucket:v,limit:e,order:`desc`});o(t.length>y),c(t.slice(0,y))}catch(e){u(e.message)}finally{f(!1)}},[v,y]);return(0,m.useEffect)(()=>{x();let e=p(x,_);return()=>e()},[x]),{granularity:e,changeGran:b,snaps:s,err:l,busy:d,hasMore:a,loadMore:(0,m.useCallback)(()=>i(e=>e+1),[])}}function y(){let[e,t]=(0,m.useState)(o()),[n,i]=(0,m.useState)([]),[a,s]=(0,m.useState)(null),[l,d]=(0,m.useState)(!1),f=(0,m.useRef)(!0),h=c(6e4),g=o(new Date(h)),v=(0,m.useMemo)(()=>u(e),[e]),y=v+24*36e5,b=e===g;(0,m.useEffect)(()=>{f.current&&e!==g&&t(g)},[g,e]);let x=(0,m.useCallback)(e=>{f.current=e===o(),t(e)},[]),S=(0,m.useCallback)(async()=>{d(!0),s(null);try{let e=await r.logbook.snapshots({from:v,to:y,limit:5e3,order:`desc`,bucket:60});i(e)}catch(e){s(e.message)}finally{d(!1)}},[v,y]);return(0,m.useEffect)(()=>{if(S(),!b)return;let e=p(S,_);return()=>e()},[S,b]),{dateStr:e,setDateStr:x,dayStart:v,isToday:b,snaps:n,err:a,busy:l,prevDay:(0,m.useCallback)(()=>x(o(new Date(v-864e5))),[v,x]),nextDay:(0,m.useCallback)(()=>x(o(new Date(v+864e5))),[v,x]),goToday:(0,m.useCallback)(()=>x(o()),[x])}}var b=t(),x=[`1m`,`1h`,`6h`,`1d`],S={"1m":`Last hour`,"1h":`Last 2 days`,"6h":`Last 10 days`,"1d":`Last month`};function C(){let[e,t]=(0,m.useState)(`live`),[n,r]=(0,m.useState)(()=>localStorage.getItem(`lb:windUnit`)||`kn`),i={mode:e,setMode:t,windUnit:n,toggleWind:()=>r(e=>{let t=e===`kn`?`bft`:`kn`;return localStorage.setItem(`lb:windUnit`,t),t})};return(0,b.jsx)(`div`,{className:`lb`,children:e===`live`?(0,b.jsx)(E,{...i}):(0,b.jsx)(D,{...i})})}function w({mode:e,setMode:t}){return(0,b.jsxs)(`div`,{className:`seg`,children:[(0,b.jsx)(`button`,{className:e===`live`?`on`:``,onClick:()=>t(`live`),children:`Live`}),(0,b.jsx)(`button`,{className:e===`day`?`on`:``,onClick:()=>t(`day`),children:`Day`})]})}function T({windUnit:e,toggleWind:t}){return(0,b.jsxs)(`div`,{className:`lb-cols`,children:[(0,b.jsx)(`span`,{children:`UTC`}),(0,b.jsx)(`span`,{children:`SOG`}),(0,b.jsx)(`span`,{children:`HDG`}),(0,b.jsx)(`span`,{className:`tap`,onClick:t,title:`Tap: knots ⇄ Beaufort`,children:e===`kn`?`TWS`:`BFT`}),(0,b.jsx)(`span`,{children:`BARO`}),(0,b.jsx)(`span`,{children:`DEP`})]})}function E({mode:e,setMode:t,windUnit:n,toggleWind:r}){let{granularity:i,changeGran:a,snaps:o,err:s,busy:c,hasMore:l,loadMore:u}=v();return(0,b.jsxs)(b.Fragment,{children:[(0,b.jsxs)(`div`,{className:`lb-ctrl`,children:[(0,b.jsx)(w,{mode:e,setMode:t}),(0,b.jsx)(`div`,{className:`seg`,children:x.map(e=>(0,b.jsx)(`button`,{className:i===e?`on`:``,onClick:()=>a(e),children:e},e))}),(0,b.jsx)(`span`,{className:`lb-count`,children:o.length})]}),(0,b.jsx)(T,{windUnit:n,toggleWind:r}),(0,b.jsxs)(`div`,{className:`lb-day`,children:[(0,b.jsx)(`span`,{children:S[i]}),(0,b.jsx)(`b`,{children:o.length})]}),s&&(0,b.jsx)(`div`,{className:`lb-err`,children:s}),(0,b.jsx)(O,{snaps:o,windUnit:n,footer:l?(0,b.jsx)(`button`,{className:`lb-more`,onClick:u,disabled:c,children:c?`Loading…`:`Load ${g[i]} more`}):null})]})}function D({mode:e,setMode:t,windUnit:n,toggleWind:r}){let{dateStr:i,setDateStr:a,isToday:s,snaps:c,err:l,busy:u,prevDay:d,nextDay:f,goToday:p}=y(),m=s?`Today · ${new Date(i).toLocaleDateString(`en-GB`,{day:`2-digit`,month:`short`,timeZone:`UTC`})}`:new Date(i).toLocaleDateString(`en-GB`,{weekday:`short`,day:`2-digit`,month:`short`,timeZone:`UTC`}).replace(/,/g,``);return(0,b.jsxs)(b.Fragment,{children:[(0,b.jsxs)(`div`,{className:`lb-ctrl`,children:[(0,b.jsx)(w,{mode:e,setMode:t}),(0,b.jsxs)(`div`,{className:`lb-date`,children:[(0,b.jsx)(`button`,{onClick:d,"aria-label":`Previous day`,children:`‹`}),(0,b.jsx)(`input`,{type:`date`,className:`dt`,value:i,max:o(),onChange:e=>a(e.target.value),style:{border:`1.5px solid var(--rule)`,background:`var(--cell)`,color:`var(--text)`,fontFamily:`var(--sp-font)`,fontSize:12,padding:`5px 7px`}}),(0,b.jsx)(`button`,{onClick:f,disabled:s,"aria-label":`Next day`,children:`›`}),(0,b.jsx)(`button`,{onClick:p,disabled:s,children:`Now`})]})]}),(0,b.jsx)(T,{windUnit:n,toggleWind:r}),(0,b.jsxs)(`div`,{className:`lb-day`,children:[(0,b.jsx)(`span`,{children:m}),(0,b.jsx)(`b`,{children:c.length})]}),l&&(0,b.jsx)(`div`,{className:`lb-err`,children:l}),!u&&c.length===0?(0,b.jsxs)(`div`,{className:`sp-empty`,children:[(0,b.jsx)(`div`,{className:`em-t`,children:`No snapshots`}),(0,b.jsx)(`div`,{className:`em-s`,children:`No telemetry was logged for this day.`})]}):(0,b.jsx)(O,{snaps:c,windUnit:n,footer:null})]})}function O({snaps:e,footer:t,windUnit:n}){return(0,b.jsxs)(`div`,{className:`lb-rows`,children:[e.map(e=>(0,b.jsx)(k,{s:e,windUnit:n},e.ts)),t]})}function k({s:e,windUnit:t}){let r=new Date(e.ts),o=e=>String(e).padStart(2,`0`),c=d(e.sog),u=a(e.heading_true??e.heading_mag),f=l(e.wind_speed_true),p=f===null?`·`:String(t===`kn`?Math.round(f):s(f)??`·`),m=n(e.air_pressure_pa);return(0,b.jsxs)(`div`,{className:`lb-row`,children:[(0,b.jsxs)(`span`,{className:`tm`,children:[o(r.getUTCHours()),`:`,o(r.getUTCMinutes())]}),(0,b.jsx)(`span`,{className:`v`,children:i(c,1)}),(0,b.jsx)(`span`,{className:`v`,children:u===null?`·`:Math.round(u)+`°`}),(0,b.jsx)(`span`,{className:`v`,children:p}),(0,b.jsx)(`span`,{className:`v dim`,children:m===null?`·`:Math.round(m)}),(0,b.jsx)(`span`,{className:`v`,children:e.depth===null?`·`:e.depth.toFixed(1)})]})}function A(){return(0,b.jsx)(C,{})}export{A as default};
1
+ import{a as e}from"./rolldown-runtime-CNC7AqOf.js";import{_ as t,c as n,g as r,i,l as a,n as o,o as s,p as c,s as l,t as u,u as d,y as f}from"./index-C_sXr_1m.js";import{t as p}from"./visibleInterval-CFxPzgzx.js";var m=e(f(),1),h={"1m":1,"1h":60,"6h":360,"1d":1440},g={"1m":60,"1h":48,"6h":40,"1d":30},_=15e3;function v(){let[e,t]=(0,m.useState)(`1h`),[n,i]=(0,m.useState)(0),[a,o]=(0,m.useState)(!1),[s,c]=(0,m.useState)([]),[l,u]=(0,m.useState)(null),[d,f]=(0,m.useState)(!1),v=h[e],y=g[e]+n*g[e],b=(0,m.useCallback)(e=>{t(e),i(0),c([]),o(!1)},[]),x=(0,m.useCallback)(async()=>{f(!0),u(null);try{let e=y+1,t=await r.logbook.snapshots({bucket:v,limit:e,order:`desc`});o(t.length>y),c(t.slice(0,y))}catch(e){u(e.message)}finally{f(!1)}},[v,y]);return(0,m.useEffect)(()=>{x();let e=p(x,_);return()=>e()},[x]),{granularity:e,changeGran:b,snaps:s,err:l,busy:d,hasMore:a,loadMore:(0,m.useCallback)(()=>i(e=>e+1),[])}}function y(){let[e,t]=(0,m.useState)(o()),[n,i]=(0,m.useState)([]),[a,s]=(0,m.useState)(null),[l,d]=(0,m.useState)(!1),f=(0,m.useRef)(!0),h=c(6e4),g=o(new Date(h)),v=(0,m.useMemo)(()=>u(e),[e]),y=v+24*36e5,b=e===g;(0,m.useEffect)(()=>{f.current&&e!==g&&t(g)},[g,e]);let x=(0,m.useCallback)(e=>{f.current=e===o(),t(e)},[]),S=(0,m.useCallback)(async()=>{d(!0),s(null);try{let e=await r.logbook.snapshots({from:v,to:y,limit:5e3,order:`desc`,bucket:60});i(e)}catch(e){s(e.message)}finally{d(!1)}},[v,y]);return(0,m.useEffect)(()=>{if(S(),!b)return;let e=p(S,_);return()=>e()},[S,b]),{dateStr:e,setDateStr:x,dayStart:v,isToday:b,snaps:n,err:a,busy:l,prevDay:(0,m.useCallback)(()=>x(o(new Date(v-864e5))),[v,x]),nextDay:(0,m.useCallback)(()=>x(o(new Date(v+864e5))),[v,x]),goToday:(0,m.useCallback)(()=>x(o()),[x])}}var b=t(),x=[`1m`,`1h`,`6h`,`1d`],S={"1m":`Last hour`,"1h":`Last 2 days`,"6h":`Last 10 days`,"1d":`Last month`};function C(){let[e,t]=(0,m.useState)(`live`),[n,r]=(0,m.useState)(()=>localStorage.getItem(`lb:windUnit`)||`kn`),i={mode:e,setMode:t,windUnit:n,toggleWind:()=>r(e=>{let t=e===`kn`?`bft`:`kn`;return localStorage.setItem(`lb:windUnit`,t),t})};return(0,b.jsx)(`div`,{className:`lb`,children:e===`live`?(0,b.jsx)(E,{...i}):(0,b.jsx)(D,{...i})})}function w({mode:e,setMode:t}){return(0,b.jsxs)(`div`,{className:`seg`,children:[(0,b.jsx)(`button`,{className:e===`live`?`on`:``,onClick:()=>t(`live`),children:`Live`}),(0,b.jsx)(`button`,{className:e===`day`?`on`:``,onClick:()=>t(`day`),children:`Day`})]})}function T({windUnit:e,toggleWind:t}){return(0,b.jsxs)(`div`,{className:`lb-cols`,children:[(0,b.jsx)(`span`,{children:`UTC`}),(0,b.jsx)(`span`,{children:`SOG`}),(0,b.jsx)(`span`,{children:`HDG`}),(0,b.jsx)(`span`,{className:`tap`,onClick:t,title:`Tap: knots ⇄ Beaufort`,children:e===`kn`?`TWS`:`BFT`}),(0,b.jsx)(`span`,{children:`BARO`}),(0,b.jsx)(`span`,{children:`DEP`})]})}function E({mode:e,setMode:t,windUnit:n,toggleWind:r}){let{granularity:i,changeGran:a,snaps:o,err:s,busy:c,hasMore:l,loadMore:u}=v();return(0,b.jsxs)(b.Fragment,{children:[(0,b.jsxs)(`div`,{className:`lb-ctrl`,children:[(0,b.jsx)(w,{mode:e,setMode:t}),(0,b.jsx)(`div`,{className:`seg`,children:x.map(e=>(0,b.jsx)(`button`,{className:i===e?`on`:``,onClick:()=>a(e),children:e},e))}),(0,b.jsx)(`span`,{className:`lb-count`,children:o.length})]}),(0,b.jsx)(T,{windUnit:n,toggleWind:r}),(0,b.jsxs)(`div`,{className:`lb-day`,children:[(0,b.jsx)(`span`,{children:S[i]}),(0,b.jsx)(`b`,{children:o.length})]}),s&&(0,b.jsx)(`div`,{className:`lb-err`,children:s}),(0,b.jsx)(O,{snaps:o,windUnit:n,footer:l?(0,b.jsx)(`button`,{className:`lb-more`,onClick:u,disabled:c,children:c?`Loading…`:`Load ${g[i]} more`}):null})]})}function D({mode:e,setMode:t,windUnit:n,toggleWind:r}){let{dateStr:i,setDateStr:a,isToday:s,snaps:c,err:l,busy:u,prevDay:d,nextDay:f,goToday:p}=y(),m=s?`Today · ${new Date(i).toLocaleDateString(`en-GB`,{day:`2-digit`,month:`short`,timeZone:`UTC`})}`:new Date(i).toLocaleDateString(`en-GB`,{weekday:`short`,day:`2-digit`,month:`short`,timeZone:`UTC`}).replace(/,/g,``);return(0,b.jsxs)(b.Fragment,{children:[(0,b.jsxs)(`div`,{className:`lb-ctrl`,children:[(0,b.jsx)(w,{mode:e,setMode:t}),(0,b.jsxs)(`div`,{className:`lb-date`,children:[(0,b.jsx)(`button`,{onClick:d,"aria-label":`Previous day`,children:`‹`}),(0,b.jsx)(`input`,{type:`date`,className:`dt`,value:i,max:o(),onChange:e=>a(e.target.value),style:{border:`1.5px solid var(--rule)`,background:`var(--cell)`,color:`var(--text)`,fontFamily:`var(--sp-font)`,fontSize:12,padding:`5px 7px`}}),(0,b.jsx)(`button`,{onClick:f,disabled:s,"aria-label":`Next day`,children:`›`}),(0,b.jsx)(`button`,{onClick:p,disabled:s,children:`Now`})]})]}),(0,b.jsx)(T,{windUnit:n,toggleWind:r}),(0,b.jsxs)(`div`,{className:`lb-day`,children:[(0,b.jsx)(`span`,{children:m}),(0,b.jsx)(`b`,{children:c.length})]}),l&&(0,b.jsx)(`div`,{className:`lb-err`,children:l}),!u&&c.length===0?(0,b.jsxs)(`div`,{className:`sp-empty`,children:[(0,b.jsx)(`div`,{className:`em-t`,children:`No snapshots`}),(0,b.jsx)(`div`,{className:`em-s`,children:`No telemetry was logged for this day.`})]}):(0,b.jsx)(O,{snaps:c,windUnit:n,footer:null})]})}function O({snaps:e,footer:t,windUnit:n}){return(0,b.jsxs)(`div`,{className:`lb-rows`,children:[e.map(e=>(0,b.jsx)(k,{s:e,windUnit:n},e.ts)),t]})}function k({s:e,windUnit:t}){let r=new Date(e.ts),o=e=>String(e).padStart(2,`0`),c=d(e.sog),u=a(e.heading_true??e.heading_mag),f=l(e.wind_speed_true),p=f===null?`·`:String(t===`kn`?Math.round(f):s(f)??`·`),m=n(e.air_pressure_pa);return(0,b.jsxs)(`div`,{className:`lb-row`,children:[(0,b.jsxs)(`span`,{className:`tm`,children:[o(r.getUTCHours()),`:`,o(r.getUTCMinutes())]}),(0,b.jsx)(`span`,{className:`v`,children:i(c,1)}),(0,b.jsx)(`span`,{className:`v`,children:u===null?`·`:Math.round(u)+`°`}),(0,b.jsx)(`span`,{className:`v`,children:p}),(0,b.jsx)(`span`,{className:`v dim`,children:m===null?`·`:Math.round(m)}),(0,b.jsx)(`span`,{className:`v`,children:e.depth===null?`·`:e.depth.toFixed(1)})]})}function A(){return(0,b.jsx)(C,{})}export{A as default};
@@ -1,4 +1,4 @@
1
- import{a as e}from"./rolldown-runtime-CNC7AqOf.js";import{_ as t,a as n,d as r,g as i,i as a,l as o,r as s,u as c,y as l}from"./index-5gz9-vQd.js";import{t as u}from"./visibleInterval-CFxPzgzx.js";import{a as d,i as f,n as p,o as m,r as h,s as g,t as _}from"./style-CSszoHyM.js";var v=e(l(),1),y={fill:`#ffb938`,stroke:`#b38326`,glow:`rgba(255,185,56,0.65)`,dot:`#050706`};function b(e,t=y){let n=t.fill,r=t.stroke;return e===null||Number.isNaN(e)?`
1
+ import{a as e}from"./rolldown-runtime-CNC7AqOf.js";import{_ as t,a as n,d as r,g as i,i as a,l as o,r as s,u as c,y as l}from"./index-C_sXr_1m.js";import{t as u}from"./visibleInterval-CFxPzgzx.js";import{a as d,i as f,n as p,o as m,r as h,s as g,t as _}from"./style-DroXqNI8.js";var v=e(l(),1),y={fill:`#ffb938`,stroke:`#b38326`,glow:`rgba(255,185,56,0.65)`,dot:`#050706`};function b(e,t=y){let n=t.fill,r=t.stroke;return e===null||Number.isNaN(e)?`
2
2
  <div style="
3
3
  width: 14px; height: 14px;
4
4
  background: ${n};
@@ -0,0 +1,5 @@
1
+ import{a as e}from"./rolldown-runtime-CNC7AqOf.js";import{_ as t,f as n,g as r,h as i,i as a,m as o,r as s,v as c,y as l}from"./index-C_sXr_1m.js";import{a as u,i as d,n as f,o as p,s as m,t as h}from"./style-DroXqNI8.js";var g=e(l(),1),_=[{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`}],v=.26417205,y=.21996923,b=1e-6;function x(e,t,n){return`${e.toFixed(n)} ${t}`}function S(e,t,n,r){if(e===null||!Number.isFinite(e))return null;switch(r){case`total_l`:return x(e,`L`,+(e<100));case`total_usgal`:return x(e*v,`US gal`,1);case`total_impgal`:return x(e*y,`Imp gal`,1);case`per_nm`:return t>b?x(e/t,`L/nm`,2):null;case`nm_per_l`:return e>b?x(t/e,`nm/L`,2):null;case`per_hour`:return n>b?x(e/n,`L/h`,1):null}}function C(e){if(e===null)return``;let t=String(e);return/[",\n\r]/.test(t)?`"${t.replace(/"/g,`""`)}"`:t}function w(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 T=e=>e===null?null:new Date(e).toISOString(),E=[`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 D(e){let t=[...e].sort((e,t)=>e.start_ts-t.start_ts),n=[E.join(`,`)];for(let e of t)n.push([e.id,T(e.start_ts),T(e.end_ts),w(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(C).join(`,`));return n.join(`\r
2
+ `)+`\r
3
+ `}function O(e){return e.replace(/&/g,`&amp;`).replace(/</g,`&lt;`).replace(/>/g,`&gt;`).replace(/"/g,`&quot;`)}function k(e){let t=new Date(e.start_ts).toISOString().slice(0,10);return!e.start_port&&!e.end_port?`Voyage ${e.id} ${t}`:`${t} ${e.start_port??`?`} to ${e.end_port??`?`}`}function A(e,t){let n=O(k(e)),r=t.map(e=>` <trkpt lat="${e.lat}" lon="${e.lon}"><time>${new Date(e.ts).toISOString()}</time></trkpt>`).join(`
4
+ `);return[`<?xml version="1.0" encoding="UTF-8"?>`,`<gpx version="1.1" creator="Siparu" xmlns="http://www.topografix.com/GPX/1/1">`,` <metadata>`,` <name>${n}</name>`,` <time>${new Date(e.start_ts).toISOString()}</time>`,` </metadata>`,` <trk>`,` <name>${n}</name>`,` <trkseg>`,r,` </trkseg>`,` </trk>`,`</gpx>`,``].join(`
5
+ `)}function j(e,t,n){let r=URL.createObjectURL(new Blob([n],{type:`${t};charset=utf-8`})),i=document.createElement(`a`);i.href=r,i.download=e,i.rel=`noopener`,document.body.appendChild(i),i.click(),i.remove(),setTimeout(()=>URL.revokeObjectURL(r),0)}function M(e,t,n){let r=new Date(t),i=e=>String(e).padStart(2,`0`);return`${e}-${r.getUTCFullYear()}${i(r.getUTCMonth()+1)}${i(r.getUTCDate())}.${n}`}var N=`Could not load voyage data`;function P(e){return e instanceof i?e.detail||N:e instanceof DOMException&&e.name===`TimeoutError`?`She did not answer in time`:N}function F(e=0){let{data:t}=n(r.voyage.current,6e4,[e],`voyage:current`),[i,a]=(0,g.useState)(null),[o,s]=(0,g.useState)([]),[c,l]=(0,g.useState)(!0),[u,d]=(0,g.useState)(null);return(0,g.useEffect)(()=>{let e=!1;return(async()=>{try{let[t,n]=await Promise.all([r.voyage.stats(),r.voyage.list(50)]);e||(a(t),s(n),d(null))}catch(t){e||d(P(t))}finally{e||l(!1)}})(),()=>{e=!0}},[e]),{current:t??null,stats:i,list:o,loading:c,err:u}}var I=e(m(),1),L=t();function R(){return document.documentElement.dataset.theme===`day`?`day`:`night`}function z(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 B({track:e}){let t=(0,g.useRef)(null),n=(0,g.useRef)(null);return(0,g.useEffect)(()=>{if(!t.current||n.current)return;let r=t.current,i=document.documentElement,a=getComputedStyle(i).getPropertyValue(`--accent`).trim()||`#e5484d`,o=!1,s=null,c=null;return(async()=>{u();let t=await p();if(o)return;let l=()=>d(R(),t,{track:{color:a,width:3}}),m=e.filter(e=>e.lat!==null&&e.lon!==null).map(e=>[e.lon,e.lat]);s=new I.default.Map({container:r,style:l(),center:[7.42,43.7],zoom:9,attributionControl:{compact:!1,customAttribution:h},dragRotate:!1,pitchWithRotate:!1}),s.touchZoomRotate.disableRotation(),n.current=s;let g=()=>{if(!s)return;let e=s.getSource(f);e&&m.length>=2&&e.setData({type:`FeatureCollection`,features:[{type:`Feature`,properties:{},geometry:{type:`LineString`,coordinates:m}}]})};if(s.on(`style.load`,g),g(),m.length>=2){new I.default.Marker({element:z(a,!1),anchor:`center`}).setLngLat(m[0]).addTo(s),new I.default.Marker({element:z(a,!0),anchor:`center`}).setLngLat(m[m.length-1]).addTo(s);let e=new I.default.LngLatBounds;for(let t of m)e.extend(t);s.fitBounds(e,{padding:22,maxZoom:15,animate:!1})}c=new MutationObserver(()=>{s?.setStyle(l()),g()}),c.observe(i,{attributes:!0,attributeFilter:[`data-theme`]})})(),()=>{o=!0,c?.disconnect(),s?.remove(),n.current=null}},[e]),(0,L.jsx)(`div`,{ref:t,className:`vy-map`})}var V=e(c(),1);function H(e){let t=e.match(/^propulsion\.([^.]+)\.fuel\.rate$/),n=t?t[1]:e;return n.charAt(0).toUpperCase()+n.slice(1)}function U(e){let t=new Set(e.available);return e.selected.filter(e=>!t.has(e))}function W(e){return e.available.length>1||e.selected.length>0}function G(e){return[...e.available.map(e=>({path:e,reporting:!0})),...U(e).map(e=>({path:e,reporting:!1}))]}function K(e){let t=U(e);return t.length===0||t.length<e.selected.length?null:`No fuel counted: ${t.map(H).join(`, `)} is selected but not reporting a rate.`}function q(e){let t=U(e);return t.length>0&&t.length===e.selected.length?`${t.length===1?H(t[0]):`${t.length} engines`} · not reporting`:e.selected.length===0?`All`:e.selected.length===1?H(e.selected[0]):`${e.selected.length} engines`}function J({view:e,onClose:t,onApplied:n}){let[a,s]=(0,g.useState)(()=>new Set(e.selected)),[c,l]=(0,g.useState)(!1),[u,d]=(0,g.useState)(null),f=(0,g.useRef)(null),p=e=>{d(null),s(t=>{let n=new Set(t);return n.has(e)?n.delete(e):n.add(e),n})},m=async()=>{let e=[...a].sort();l(!0),d(null);try{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(),f.current?.()}catch(e){e instanceof i?d(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`):d(e instanceof Error?e.message:`Could not apply the change`),l(!1)}},h=K(e),_=document.querySelector(`.swiss.sp-screen`)??document.body;return(0,V.createPortal)((0,L.jsxs)(o,{title:`Fuel source`,eyebrow:`voyage fuel`,onClose:t,closeRef:f,footer:(0,L.jsx)(`button`,{type:`button`,className:`fs-apply`,onClick:m,disabled:c,children:c?`Applying…`:`Apply`}),children:[(0,L.jsx)(`p`,{className:`fs-note`,children:`Which engines count toward voyage fuel. With none selected, every reporting engine is summed.`}),h&&(0,L.jsx)(`div`,{className:`fs-quiet`,children:h}),(0,L.jsx)(`div`,{className:`fs-list`,role:`group`,"aria-label":`Fuel-rate sources`,children:G(e).map(({path:e,reporting:t})=>{let n=a.has(e);return(0,L.jsxs)(`button`,{type:`button`,className:`fs-row${n?` on`:``}${t?``:` off-air`}`,role:`checkbox`,"aria-checked":n,onClick:()=>p(e),disabled:c,children:[(0,L.jsxs)(`span`,{className:`fs-name`,children:[H(e),!t&&(0,L.jsx)(`span`,{className:`fs-tag`,children:` not reporting`})]}),(0,L.jsx)(`span`,{className:`fs-path`,children:e})]},e)})}),u&&(0,L.jsx)(`div`,{className:`fs-err`,children:u})]}),_)}var Y=`siparu.fuelMode`;function ee(){let e=localStorage.getItem(Y);return _.some(t=>t.mode===e)?e:`total_l`}var te=[{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 $(e){let t=new Date(e),n=e=>String(e).padStart(2,`0`);return`${n(t.getHours())}:${n(t.getMinutes())}`}function ne(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 re(){let[e,t]=(0,g.useState)(0),n=F(e),[a,o]=(0,g.useState)(`today`),[s,c]=(0,g.useState)(null),[l,u]=(0,g.useState)({}),[d,f]=(0,g.useState)(ee),[p,m]=(0,g.useState)(null),[h,_]=(0,g.useState)(!1),[v,y]=(0,g.useState)(null),[b,x]=(0,g.useState)([]),[S,C]=(0,g.useState)(null);(0,g.useEffect)(()=>{localStorage.setItem(Y,d)},[d]),(0,g.useEffect)(()=>{let e=!0;return r.health().then(t=>{e&&y(t.boat_name)}).catch(()=>{}),()=>{e=!1}},[]),(0,g.useEffect)(()=>{let e=!0;return r.voyage.edits().then(t=>{e&&x(t.merged)}).catch(()=>{}),()=>{e=!1}},[e]),(0,g.useEffect)(()=>{let e=!1;return r.config.fuelPaths().then(t=>!e&&m(t)).catch(()=>{}),()=>{e=!0}},[e]);let w=!!p&&W(p),T=p?K(p):null,E=n.current&&n.current.end_ts===null?n.current:null,O=async e=>{if(s===e){c(null);return}if(c(e),!l[e])try{let t=await r.voyage.track(e);u(n=>({...n,[e]:t}))}catch{}},k=n.stats?.[a]??null,A=async e=>{C(null);try{let n=await e();if(!n.ok){C(X[n.error??``]??`That edit could not be made.`);return}c(null),u({}),t(e=>e+1)}catch(e){e instanceof i?C(X[e.code??``]??(e.detail||`That edit could not be made.`)):C(`The boat did not answer.`)}};return(0,L.jsxs)(`div`,{className:`vy`,children:[(0,L.jsxs)(`div`,{className:`vy-print-hd`,"aria-hidden":`true`,children:[(0,L.jsx)(`span`,{className:`b`,children:v??`Passage record`}),(0,L.jsx)(`span`,{className:`d`,children:new Date().toISOString().slice(0,10)})]}),E&&(0,L.jsx)(ie,{v:E}),(0,L.jsx)(`div`,{className:`vy-seg seg`,role:`group`,"aria-label":`Stats window`,children:te.map(e=>(0,L.jsx)(`button`,{className:a===e.k?`on`:``,onClick:()=>o(e.k),children:e.label},e.k))}),(0,L.jsx)(ae,{roll:k,loading:n.loading}),(0,L.jsxs)(`div`,{className:`vy-hd`,children:[(0,L.jsxs)(`span`,{className:`vy-hd-l`,children:[`Voyages `,(0,L.jsx)(`b`,{children:n.list.length})]}),(0,L.jsxs)(`span`,{className:`vy-hd-acts`,children:[w&&(0,L.jsxs)(`button`,{type:`button`,className:`vy-fuelsrc`,onClick:()=>_(!0),children:[`Fuel · `,q(p)]}),n.list.length>0&&(0,L.jsx)(`button`,{type:`button`,className:`vy-fuelsrc`,onClick:()=>j(M(`siparu-voyages`,Date.now(),`csv`),`text/csv`,D(n.list)),children:`CSV`}),n.list.length>0&&(0,L.jsx)(`button`,{type:`button`,className:`vy-fuelsrc`,onClick:()=>window.print(),children:`Print`})]})]}),n.err?(0,L.jsx)(`div`,{className:`vy-err`,children:n.err}):!n.loading&&n.list.length===0?(0,L.jsxs)(`div`,{className:`sp-empty`,children:[(0,L.jsx)(`div`,{className:`em-t`,children:`No voyages yet`}),(0,L.jsx)(`div`,{className:`em-s`,children:`Passages appear here once the boat gets under way.`})]}):(0,L.jsx)(`div`,{className:`vy-list`,children:n.list.map((e,t)=>(0,L.jsx)(oe,{v:e,prev:n.list[t+1],wasJoined:b.includes(e.id),open:s===e.id,track:l[e.id],fuelNotice:T,fuelMode:d,onFuelMode:f,onToggle:()=>O(e.id),onMerge:()=>A(()=>r.voyage.mergePrevious(e.id)),onUndoMerge:()=>A(()=>r.voyage.undoMerge(e.id)),editErr:s===e.id?S:null},e.id))}),h&&p&&(0,L.jsx)(J,{view:p,onClose:()=>_(!1),onApplied:()=>t(e=>e+1)})]})}function ie({v:e}){return(0,L.jsxs)(`div`,{className:`vy-active`,children:[(0,L.jsxs)(`div`,{className:`vy-active-hd`,children:[(0,L.jsx)(`span`,{className:`vy-pulse`,"aria-hidden":`true`}),`Under way`,e.start_port?` · from ${e.start_port}`:``,` · since `,$(e.start_ts)]}),(0,L.jsxs)(`div`,{className:`vy-active-grid`,children:[(0,L.jsxs)(`div`,{className:`vy-a-hero`,children:[(0,L.jsxs)(`div`,{className:`t`,children:[`Distance · `,(0,L.jsx)(`span`,{className:`sub`,children:`nm`})]}),(0,L.jsx)(`div`,{className:`n`,children:a(e.distance_nm,1)})]}),(0,L.jsxs)(`div`,{className:`vy-a-cell`,children:[(0,L.jsxs)(`div`,{className:`t`,children:[`Underway · `,(0,L.jsx)(`span`,{className:`sub`,children:`time`})]}),(0,L.jsx)(`div`,{className:`v`,children:Z(e.hours_underway)})]}),(0,L.jsxs)(`div`,{className:`vy-a-cell`,children:[(0,L.jsx)(`div`,{className:`t`,children:`Avg SOG`}),(0,L.jsx)(`div`,{className:`v`,children:e.avg_sog_kn===null?`·`:(0,L.jsxs)(L.Fragment,{children:[e.avg_sog_kn.toFixed(1),(0,L.jsx)(`span`,{className:`u`,children:`kn`})]})})]})]})]})}function ae({roll:e,loading:t}){let n=t?``:`·`;return(0,L.jsxs)(`div`,{className:`vy-cards`,children:[(0,L.jsxs)(`div`,{className:`c vy-hero`,children:[(0,L.jsxs)(`div`,{className:`t`,children:[`Distance · `,(0,L.jsx)(`span`,{className:`sub`,children:`nm`})]}),(0,L.jsx)(`div`,{className:`n${t?` skel`:``}`,children:t?`128.4`:e?a(e.distance_nm,1):n})]}),(0,L.jsxs)(`div`,{className:`c`,children:[(0,L.jsxs)(`div`,{className:`t`,children:[`Underway · `,(0,L.jsx)(`span`,{className:`sub`,children:`time`})]}),(0,L.jsx)(`div`,{className:`v`,children:e?Z(e.hours_underway):n})]}),(0,L.jsxs)(`div`,{className:`c`,children:[(0,L.jsx)(`div`,{className:`t`,children:`Avg SOG`}),(0,L.jsx)(`div`,{className:`v`,children:e?.avg_sog_kn==null?n:(0,L.jsxs)(L.Fragment,{children:[e.avg_sog_kn.toFixed(1),(0,L.jsx)(`span`,{className:`u`,children:`kn`})]})})]}),(0,L.jsxs)(`div`,{className:`c`,children:[(0,L.jsx)(`div`,{className:`t`,children:`Max SOG`}),(0,L.jsx)(`div`,{className:`v`,children:e?.max_sog_kn==null?n:(0,L.jsxs)(L.Fragment,{children:[e.max_sog_kn.toFixed(1),(0,L.jsx)(`span`,{className:`u`,children:`kn`})]})})]})]})}function oe({v:e,prev:t,wasJoined:n,open:r,track:i,fuelNotice:o,fuelMode:c,onFuelMode:l,onToggle:u,onMerge:d,onUndoMerge:f,editErr:p}){let m=e.end_ts===null,h=S(e.fuel_used_l,e.distance_nm,e.hours_underway,c),g=m?`${$(e.start_ts)} →`:`${$(e.start_ts)}-${$(e.end_ts)}`,v=e.avg_sog_kn===null?`·`:`${e.avg_sog_kn.toFixed(1)} kn`,y=ne(e);return(0,L.jsxs)(`div`,{className:`vy-rowwrap${r?` open`:``}`,children:[(0,L.jsxs)(`button`,{className:`vy-row`,onClick:u,"aria-expanded":r,children:[(0,L.jsxs)(`div`,{className:`vy-row-top`,children:[(0,L.jsx)(`span`,{className:`vy-date`,children:Q(e.start_ts)}),m&&(0,L.jsx)(`span`,{className:`vy-badge`,children:`Under way`}),(0,L.jsxs)(`span`,{className:`vy-dist`,children:[a(e.distance_nm,1),(0,L.jsx)(`span`,{className:`vy-unit`,children:`nm`})]})]}),y&&(0,L.jsx)(`div`,{className:`vy-route`,children:y}),(0,L.jsxs)(`div`,{className:`vy-row-sub`,children:[g,` · `,Z(e.hours_underway),` · `,v]})]}),r&&(0,L.jsxs)(`div`,{className:`vy-detail`,children:[i&&i.length>=2?(0,L.jsx)(B,{track:i}):(0,L.jsx)(`div`,{className:`vy-track-empty`,children:i?`Track too short to plot`:`Loading track…`}),(0,L.jsxs)(`div`,{className:`vy-meta`,children:[(0,L.jsxs)(`div`,{className:`vy-m`,children:[(0,L.jsx)(`span`,{className:`k`,children:`From`}),e.start_port&&(0,L.jsx)(`span`,{className:`val`,children:e.start_port}),(0,L.jsxs)(`span`,{className:e.start_port?`coord`:`val`,children:[s(e.start_lat,[`N`,`S`],2),` · `,s(e.start_lon,[`E`,`W`],2)]})]}),(0,L.jsxs)(`div`,{className:`vy-m`,children:[(0,L.jsx)(`span`,{className:`k`,children:`To`}),m?(0,L.jsx)(`span`,{className:`val`,children:`-`}):(0,L.jsxs)(L.Fragment,{children:[e.end_port&&(0,L.jsx)(`span`,{className:`val`,children:e.end_port}),(0,L.jsxs)(`span`,{className:e.end_port?`coord`:`val`,children:[s(e.end_lat,[`N`,`S`],2),` · `,s(e.end_lon,[`E`,`W`],2)]})]})]}),(0,L.jsxs)(`div`,{className:`vy-m`,children:[(0,L.jsx)(`span`,{className:`k`,children:`Avg SOG`}),(0,L.jsx)(`span`,{className:`val`,children:e.avg_sog_kn===null?`·`:`${e.avg_sog_kn.toFixed(1)} kn`})]}),(0,L.jsxs)(`div`,{className:`vy-m`,children:[(0,L.jsx)(`span`,{className:`k`,children:`Max SOG`}),(0,L.jsx)(`span`,{className:`val`,children:e.max_sog_kn===null?`·`:`${e.max_sog_kn.toFixed(1)} kn`})]}),e.fuel_used_l===null&&o&&(0,L.jsxs)(`div`,{className:`vy-m vy-m-fuel`,children:[(0,L.jsx)(`span`,{className:`k`,children:`Fuel`}),(0,L.jsx)(`span`,{className:`val vy-fuel-quiet`,children:o})]}),e.fuel_used_l!==null&&(0,L.jsxs)(`div`,{className:`vy-m vy-m-fuel`,children:[(0,L.jsx)(`span`,{className:`k`,children:`Fuel`}),(0,L.jsx)(`span`,{className:`val`,children:h??`·`}),(0,L.jsx)(`select`,{className:`vy-fuel-sel`,value:c,"aria-label":`Fuel unit`,onChange:e=>l(e.target.value),children:_.map(e=>(0,L.jsx)(`option`,{value:e.mode,children:e.label},e.mode))})]}),i&&i.length>0&&(0,L.jsxs)(`div`,{className:`vy-m vy-m-track`,children:[(0,L.jsx)(`span`,{className:`k`,children:`Track`}),(0,L.jsxs)(`button`,{type:`button`,className:`vy-fuelsrc`,onClick:()=>j(M(`siparu-voyage-${e.id}`,e.start_ts,`gpx`),`application/gpx+xml`,A(e,i)),children:[`GPX · `,i.length,` fixes`]})]}),!m&&(n||t)&&(0,L.jsxs)(`div`,{className:`vy-m vy-m-edit`,children:[(0,L.jsx)(`span`,{className:`k`,children:`Passage`}),n?(0,L.jsx)(`button`,{type:`button`,className:`vy-fuelsrc`,onClick:f,children:`Separate again`}):(0,L.jsxs)(`button`,{type:`button`,className:`vy-fuelsrc`,onClick:d,children:[`Join to `,$(t.start_ts),t.end_ts===null?``:`-${$(t.end_ts)}`,` · `,Q(t.start_ts)]})]}),p&&(0,L.jsx)(`div`,{className:`vy-m vy-m-editerr`,children:p})]})]})]})}function se(){return(0,L.jsx)(re,{})}export{se as default};