signalk-siparu 0.2.4 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/plugin/dist/live.d.ts +2 -0
- package/plugin/dist/live.js +19 -1
- package/plugin/dist/uplink.d.ts +4 -0
- package/plugin/dist/uplink.js +25 -2
- package/public/assets/{Logbook-CpMK73wj.js → Logbook-VNvBbXql.js} +1 -1
- package/public/assets/{Map-BmL-Ri3l.js → Map-Bdfx5YSk.js} +1 -1
- package/public/assets/Voyage-CrdZ-Mdn.js +5 -0
- package/public/assets/{index-RgGUQ86M.js → index-C_sXr_1m.js} +3 -3
- package/public/assets/{style-Bk78SX0A.js → style-DroXqNI8.js} +1 -1
- package/public/index.html +1 -1
- package/public/assets/Voyage-DqTyveli.js +0 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "signalk-siparu",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
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/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;
|
|
@@ -26,6 +29,7 @@ export declare class Uplink {
|
|
|
26
29
|
private lastSentTs;
|
|
27
30
|
private failures;
|
|
28
31
|
private rejected;
|
|
32
|
+
private unentitled;
|
|
29
33
|
private lastError;
|
|
30
34
|
constructor(deps: UplinkDeps);
|
|
31
35
|
start(): void;
|
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
|
}
|
|
@@ -28,6 +44,7 @@ class Uplink {
|
|
|
28
44
|
lastSentTs = null;
|
|
29
45
|
failures = 0;
|
|
30
46
|
rejected = false;
|
|
47
|
+
unentitled = false;
|
|
31
48
|
lastError = null;
|
|
32
49
|
constructor(deps) {
|
|
33
50
|
this.deps = deps;
|
|
@@ -53,6 +70,7 @@ class Uplink {
|
|
|
53
70
|
lastSentTs: this.lastSentTs,
|
|
54
71
|
failures: this.failures,
|
|
55
72
|
rejected: this.rejected,
|
|
73
|
+
unentitled: this.unentitled,
|
|
56
74
|
lastError: this.lastError
|
|
57
75
|
};
|
|
58
76
|
}
|
|
@@ -60,6 +78,7 @@ class Uplink {
|
|
|
60
78
|
this.lastSentTs = null;
|
|
61
79
|
this.failures = 0;
|
|
62
80
|
this.rejected = false;
|
|
81
|
+
this.unentitled = false;
|
|
63
82
|
this.lastError = null;
|
|
64
83
|
}
|
|
65
84
|
schedule(delayMs) {
|
|
@@ -109,14 +128,18 @@ class Uplink {
|
|
|
109
128
|
this.lastSentTs = Date.now();
|
|
110
129
|
this.failures = 0;
|
|
111
130
|
this.rejected = false;
|
|
131
|
+
this.unentitled = false;
|
|
112
132
|
this.lastError = null;
|
|
113
133
|
return;
|
|
114
134
|
}
|
|
115
135
|
this.failures++;
|
|
116
136
|
this.rejected = res.status === 401;
|
|
137
|
+
this.unentitled = res.status === 403;
|
|
117
138
|
this.lastError = this.rejected
|
|
118
139
|
? 'Siparu no longer recognises this boat. Pair her again.'
|
|
119
|
-
:
|
|
140
|
+
: this.unentitled
|
|
141
|
+
? 'Remote watching is not active on this account. She is recording as usual, and starts sending again when it is.'
|
|
142
|
+
: `Relay refused the frame (${res.status}).`;
|
|
120
143
|
this.deps.debug(`uplink: ${this.lastError}`);
|
|
121
144
|
}
|
|
122
145
|
catch (e) {
|
|
@@ -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};
|