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 +11 -0
- package/package.json +1 -1
- package/plugin/dist/index.js +1 -1
- package/plugin/dist/live.d.ts +2 -0
- package/plugin/dist/live.js +19 -1
- package/plugin/dist/uplink.d.ts +3 -0
- package/plugin/dist/uplink.js +18 -1
- package/public/assets/{Logbook-CZb9OaA4.js → Logbook-VNvBbXql.js} +1 -1
- package/public/assets/{Map-CN4rx5A-.js → Map-Bdfx5YSk.js} +1 -1
- package/public/assets/Voyage-CrdZ-Mdn.js +5 -0
- package/public/assets/{index-5gz9-vQd.js → index-C_sXr_1m.js} +3 -3
- package/public/assets/{style-CSszoHyM.js → style-DroXqNI8.js} +1 -1
- package/public/index.html +1 -1
- package/public/assets/Voyage-CzIqnUBA.js +0 -5
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
|
+
"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",
|
package/plugin/dist/index.js
CHANGED
|
@@ -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;
|
package/plugin/dist/live.d.ts
CHANGED
|
@@ -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;
|
package/plugin/dist/live.js
CHANGED
|
@@ -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
|
|
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}`);
|
package/plugin/dist/uplink.d.ts
CHANGED
|
@@ -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;
|
package/plugin/dist/uplink.js
CHANGED
|
@@ -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 {
|
|
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-
|
|
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-
|
|
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,`&`).replace(/</g,`<`).replace(/>/g,`>`).replace(/"/g,`"`)}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};
|