signalk-history-widgets 0.0.0 → 0.2.0

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
@@ -10,6 +10,8 @@ moves.
10
10
 
11
11
  ![True wind direction and speed over 30 minutes](docs/screenshots/wind-strips.png)
12
12
 
13
+ The package also brings a **Wind steering** dial, described [below](#wind-steering).
14
+
13
15
  ## Use
14
16
 
15
17
  1. Install **History Widgets** from the App Store and restart the server. The
@@ -40,7 +42,39 @@ Unit Preferences, unless the strip sets its own.
40
42
 
41
43
  ![Apparent wind angle over 24 hours](docs/screenshots/awa-24h.png)
42
44
 
45
+ ## Wind steering
46
+
47
+ A square dial for steering by the wind, bow always up. In its default
48
+ compass mode the card turns with the heading; set to wind angles it shows 0
49
+ to 180 degrees port and starboard instead. On the dial:
50
+
51
+ ![Wind steering dial](docs/screenshots/wind-steering.png)
52
+
53
+ - the apparent wind (blue pointer, **A**) and the true wind (orange marker,
54
+ **T**), with AWS, TWS, AWA and TWA in the corners;
55
+ - dashed close-hauled lines for both tacks: red is the heading on port tack,
56
+ green on starboard tack, at the close-hauled angle off the true wind
57
+ (45 degrees unless set otherwise);
58
+ - the wind shift fan: how far the true wind direction swung over the last
59
+ minute (or the window you choose), so a lift or a header shows at a glance;
60
+ - the next waypoint (purple dot) when a destination is active, course over
61
+ ground (pink diamond) when the boat is moving, the current as an arrow
62
+ with its drift, and the rudder angle as a bar under the dial.
63
+
64
+ Press and hold the dial to choose the dial type, true or magnetic north,
65
+ true wind through the water or over the ground, the close-hauled angle, the
66
+ shift window, and which marks to show. Values older than ten seconds are
67
+ hidden rather than shown stale. It reads standard Signal K paths:
68
+ `navigation.headingTrue`, `navigation.courseOverGroundTrue`,
69
+ `navigation.course.calcValues.bearingTrue` and `environment.current.setTrue`
70
+ (with magnetic north `navigation.headingMagnetic`,
71
+ `navigation.courseOverGroundMagnetic`,
72
+ `navigation.course.calcValues.bearingMagnetic` and
73
+ `environment.current.setMagnetic`), plus `environment.wind.*`,
74
+ `navigation.speedOverGround`, `environment.current.drift` and
75
+ `steering.rudderAngle`.
76
+
43
77
  ## Requirements
44
78
 
45
- - Signal K server 2.22.1 or newer (History API v2)
79
+ - Signal K server 2.22.1 or newer (History API v2) on Node 24 or newer
46
80
  - A chartplotter that hosts Plotter Extensions widgets, such as Freeboard-SK
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "signalk-history-widgets",
3
- "version": "0.0.0",
4
- "description": "History strip widgets for Signal K chartplotters: any value over the last minutes or hours as a vertical strip with min, average and max, like the wind history page on a sailing instrument",
3
+ "version": "0.2.0",
4
+ "description": "History strip and wind steering widgets for Signal K chartplotters: any value over the last minutes or hours as a vertical strip with min, average and max, and a wind steering dial with close-hauled lines and the recent wind shift",
5
5
  "author": "Dirk Wahrheit",
6
6
  "main": "plugin/index.js",
7
7
  "type": "module",
@@ -22,7 +22,8 @@
22
22
  "signalk-category-instruments",
23
23
  "wind",
24
24
  "history",
25
- "trend"
25
+ "trend",
26
+ "steering"
26
27
  ],
27
28
  "signalk": {
28
29
  "displayName": "History Widgets",
@@ -32,7 +33,9 @@
32
33
  ],
33
34
  "screenshots": [
34
35
  "./docs/screenshots/wind-strips.png",
35
- "./docs/screenshots/config.png"
36
+ "./docs/screenshots/config.png",
37
+ "./docs/screenshots/awa-24h.png",
38
+ "./docs/screenshots/wind-steering.png"
36
39
  ]
37
40
  },
38
41
  "files": [
@@ -41,7 +44,7 @@
41
44
  "docs/screenshots/*.png"
42
45
  ],
43
46
  "engines": {
44
- "node": ">=22.15.0",
47
+ "node": ">=24",
45
48
  "signalk": ">=2.22.1"
46
49
  },
47
50
  "repository": {
@@ -53,7 +56,7 @@
53
56
  "@eslint/js": "^10.0.1",
54
57
  "@signalk/server-api": "^2.36.0",
55
58
  "@types/express": "^5.0.6",
56
- "@types/node": "^22.20.5",
59
+ "@types/node": "^24.19.2",
57
60
  "eslint": "^10.14.0",
58
61
  "eslint-config-prettier": "^10.1.8",
59
62
  "eslint-plugin-prettier": "^5.5.7",
package/plugin/index.js CHANGED
@@ -52,7 +52,7 @@ var strip = (id, title, size) => ({
52
52
  function buildManifest(version) {
53
53
  return {
54
54
  name: "History Widgets",
55
- description: "Any value over the last minutes or hours as a vertical strip with its minimum, average and maximum.",
55
+ description: "Any value over the last minutes or hours as a vertical strip with its minimum, average and maximum, and a wind steering dial with the wind shift of the last minutes.",
56
56
  version,
57
57
  apiVersion: "1",
58
58
  requires: [
@@ -61,13 +61,31 @@ function buildManifest(version) {
61
61
  "signalk.stream"
62
62
  ],
63
63
  optional: ["nightMode"],
64
- widgets: [strip("strip", "History strip", "1x2"), strip("strip-wide", "History strip (wide)", "2x2")],
64
+ widgets: [
65
+ strip("strip", "History strip", "1x2"),
66
+ strip("strip-wide", "History strip (wide)", "2x2"),
67
+ {
68
+ id: "wind-steering",
69
+ title: "Wind steering",
70
+ type: "iframe",
71
+ url: `${ASSET_BASE}/steer.html`,
72
+ size: "2x2",
73
+ configPanel: "wind-steering-config",
74
+ lifecycle: "whileEnabled"
75
+ }
76
+ ],
65
77
  panels: [{
66
78
  id: "strip-config",
67
79
  title: "History strip",
68
80
  type: "iframe",
69
81
  url: `${ASSET_BASE}/config.html`,
70
82
  lifecycle: "onOpen"
83
+ }, {
84
+ id: "wind-steering-config",
85
+ title: "Wind steering",
86
+ type: "iframe",
87
+ url: `${ASSET_BASE}/steer-config.html`,
88
+ lifecycle: "onOpen"
71
89
  }]
72
90
  };
73
91
  }
@@ -112,7 +130,7 @@ function historyWidgets(app) {
112
130
  return {
113
131
  id: PLUGIN_ID,
114
132
  name: "History Widgets",
115
- description: "History strip widgets for Freeboard-SK and other Plotter Extensions hosts",
133
+ description: "History strip and wind steering widgets for Freeboard-SK and other Plotter Extensions hosts",
116
134
  schema: () => ({
117
135
  type: "object",
118
136
  properties: {}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/assets.ts","../src/manifest.ts","../src/index.ts"],"sourcesContent":["import { readdir, readFile } from 'node:fs/promises'\nimport { extname, join, relative, sep } from 'node:path'\n\nconst TYPES: Record<string, string> = {\n '.html': 'text/html; charset=utf-8',\n '.js': 'text/javascript; charset=utf-8',\n '.css': 'text/css; charset=utf-8',\n '.svg': 'image/svg+xml',\n '.map': 'application/json'\n}\n\nexport interface Asset {\n type: string\n body: Buffer\n}\n\n/**\n * Every file of the built web pages, keyed by its URL path. Serving from this\n * list rather than from the file system keeps requests out of everything\n * else on disk.\n */\nexport async function loadAssets(dir: string): Promise<Map<string, Asset>> {\n const assets = new Map<string, Asset>()\n const entries = await readdir(dir, { recursive: true, withFileTypes: true })\n for (const entry of entries) {\n if (!entry.isFile()) continue\n const file = join(entry.parentPath, entry.name)\n const url = '/' + relative(dir, file).split(sep).join('/')\n assets.set(url, {\n type: TYPES[extname(file)] ?? 'application/octet-stream',\n body: await readFile(file)\n })\n }\n return assets\n}\n","export const PLUGIN_ID = 'signalk-history-widgets'\n\n/**\n * Where the widget pages are served. Not under /plugins/<id>, which is for\n * admins only: every user of the chartplotter loads these.\n */\nexport const ASSET_BASE = `/plotterext/${PLUGIN_ID}`\n\nconst strip = (id: string, title: string, size: string) => ({\n id,\n title,\n type: 'iframe',\n url: `${ASSET_BASE}/widget.html`,\n size,\n configPanel: 'strip-config',\n lifecycle: 'whileEnabled'\n})\n\n/** The Plotter Extensions manifest (apiVersion 1). */\nexport function buildManifest(version: string) {\n return {\n name: 'History Widgets',\n description:\n 'Any value over the last minutes or hours as a vertical strip with its minimum, average and maximum.',\n version,\n apiVersion: '1',\n requires: ['widgets', 'panels.iframe', 'signalk.stream'],\n optional: ['nightMode'],\n widgets: [\n strip('strip', 'History strip', '1x2'),\n strip('strip-wide', 'History strip (wide)', '2x2')\n ],\n panels: [\n {\n id: 'strip-config',\n title: 'History strip',\n type: 'iframe',\n url: `${ASSET_BASE}/config.html`,\n lifecycle: 'onOpen'\n }\n ]\n }\n}\n","import { readFileSync } from 'node:fs'\nimport { fileURLToPath } from 'node:url'\nimport type { Request, Response, NextFunction } from 'express'\nimport type { Plugin, ServerAPI } from '@signalk/server-api'\nimport { loadAssets, type Asset } from './assets.js'\nimport { ASSET_BASE, PLUGIN_ID, buildManifest } from './manifest.js'\n\nconst PUBLIC_DIR = fileURLToPath(new URL('../public', import.meta.url))\nconst PACKAGE_JSON = fileURLToPath(new URL('../package.json', import.meta.url))\n\n/** The express app the server hands plugins; ServerAPI leaves `use` out. */\ntype App = ServerAPI & {\n use?: (path: string, handler: (req: Request, res: Response, next: NextFunction) => void) => void\n}\n\nfunction packageVersion(): string {\n const pkg: unknown = JSON.parse(readFileSync(PACKAGE_JSON, 'utf8'))\n const version = (pkg as { version?: unknown }).version\n return typeof version === 'string' ? version : '0.0.0'\n}\n\nexport default function historyWidgets(app: App): Plugin {\n let running = false\n let assets = new Map<string, Asset>()\n let mounted = false\n let registered = false\n const manifest = buildManifest(packageVersion())\n\n const mount = () => {\n if (mounted || !app.use) return\n mounted = true\n // express cannot unmount a route, so a stopped plugin answers 404.\n app.use(ASSET_BASE, (req, res, next) => {\n const asset = running ? assets.get(req.path) : undefined\n if (!asset || (req.method !== 'GET' && req.method !== 'HEAD')) {\n next()\n return\n }\n res.type(asset.type).send(asset.body)\n })\n }\n\n const register = () => {\n if (registered) return\n registered = true\n app.registerResourceProvider({\n type: 'plotterExtensions',\n methods: {\n listResources: () => Promise.resolve(running ? { [PLUGIN_ID]: manifest } : {}),\n getResource: (id: string) =>\n running && id === PLUGIN_ID\n ? Promise.resolve(manifest)\n : Promise.reject(new Error(`No such plotterExtensions resource: ${id}`)),\n setResource: () => Promise.reject(new Error(`${PLUGIN_ID} is read-only`)),\n deleteResource: () => Promise.reject(new Error(`${PLUGIN_ID} is read-only`))\n }\n })\n }\n\n return {\n id: PLUGIN_ID,\n name: 'History Widgets',\n description: 'History strip widgets for Freeboard-SK and other Plotter Extensions hosts',\n schema: () => ({ type: 'object', properties: {} }),\n\n start() {\n running = true\n mount()\n register()\n loadAssets(PUBLIC_DIR)\n .then((loaded) => {\n assets = loaded\n app.setPluginStatus(`Serving ${manifest.widgets.length} widgets`)\n })\n .catch((err: unknown) => {\n app.setPluginError(\n `Web pages missing: ${err instanceof Error ? err.message : String(err)}`\n )\n })\n },\n\n stop() {\n running = false\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/assets.ts","../src/manifest.ts","../src/index.ts"],"sourcesContent":["import { readdir, readFile } from 'node:fs/promises'\nimport { extname, join, relative, sep } from 'node:path'\n\nconst TYPES: Record<string, string> = {\n '.html': 'text/html; charset=utf-8',\n '.js': 'text/javascript; charset=utf-8',\n '.css': 'text/css; charset=utf-8',\n '.svg': 'image/svg+xml',\n '.map': 'application/json'\n}\n\nexport interface Asset {\n type: string\n body: Buffer\n}\n\n/**\n * Every file of the built web pages, keyed by its URL path. Serving from this\n * list rather than from the file system keeps requests out of everything\n * else on disk.\n */\nexport async function loadAssets(dir: string): Promise<Map<string, Asset>> {\n const assets = new Map<string, Asset>()\n const entries = await readdir(dir, { recursive: true, withFileTypes: true })\n for (const entry of entries) {\n if (!entry.isFile()) continue\n const file = join(entry.parentPath, entry.name)\n const url = '/' + relative(dir, file).split(sep).join('/')\n assets.set(url, {\n type: TYPES[extname(file)] ?? 'application/octet-stream',\n body: await readFile(file)\n })\n }\n return assets\n}\n","export const PLUGIN_ID = 'signalk-history-widgets'\n\n/**\n * Where the widget pages are served. Not under /plugins/<id>, which is for\n * admins only: every user of the chartplotter loads these.\n */\nexport const ASSET_BASE = `/plotterext/${PLUGIN_ID}`\n\nconst strip = (id: string, title: string, size: string) => ({\n id,\n title,\n type: 'iframe',\n url: `${ASSET_BASE}/widget.html`,\n size,\n configPanel: 'strip-config',\n lifecycle: 'whileEnabled'\n})\n\n/** The Plotter Extensions manifest (apiVersion 1). */\nexport function buildManifest(version: string) {\n return {\n name: 'History Widgets',\n description:\n 'Any value over the last minutes or hours as a vertical strip with its minimum, average and maximum, and a wind steering dial with the wind shift of the last minutes.',\n version,\n apiVersion: '1',\n requires: ['widgets', 'panels.iframe', 'signalk.stream'],\n optional: ['nightMode'],\n widgets: [\n strip('strip', 'History strip', '1x2'),\n strip('strip-wide', 'History strip (wide)', '2x2'),\n {\n id: 'wind-steering',\n title: 'Wind steering',\n type: 'iframe',\n url: `${ASSET_BASE}/steer.html`,\n size: '2x2',\n configPanel: 'wind-steering-config',\n lifecycle: 'whileEnabled'\n }\n ],\n panels: [\n {\n id: 'strip-config',\n title: 'History strip',\n type: 'iframe',\n url: `${ASSET_BASE}/config.html`,\n lifecycle: 'onOpen'\n },\n {\n id: 'wind-steering-config',\n title: 'Wind steering',\n type: 'iframe',\n url: `${ASSET_BASE}/steer-config.html`,\n lifecycle: 'onOpen'\n }\n ]\n }\n}\n","import { readFileSync } from 'node:fs'\nimport { fileURLToPath } from 'node:url'\nimport type { Request, Response, NextFunction } from 'express'\nimport type { Plugin, ServerAPI } from '@signalk/server-api'\nimport { loadAssets, type Asset } from './assets.js'\nimport { ASSET_BASE, PLUGIN_ID, buildManifest } from './manifest.js'\n\nconst PUBLIC_DIR = fileURLToPath(new URL('../public', import.meta.url))\nconst PACKAGE_JSON = fileURLToPath(new URL('../package.json', import.meta.url))\n\n/** The express app the server hands plugins; ServerAPI leaves `use` out. */\ntype App = ServerAPI & {\n use?: (path: string, handler: (req: Request, res: Response, next: NextFunction) => void) => void\n}\n\nfunction packageVersion(): string {\n const pkg: unknown = JSON.parse(readFileSync(PACKAGE_JSON, 'utf8'))\n const version = (pkg as { version?: unknown }).version\n return typeof version === 'string' ? version : '0.0.0'\n}\n\nexport default function historyWidgets(app: App): Plugin {\n let running = false\n let assets = new Map<string, Asset>()\n let mounted = false\n let registered = false\n const manifest = buildManifest(packageVersion())\n\n const mount = () => {\n if (mounted || !app.use) return\n mounted = true\n // express cannot unmount a route, so a stopped plugin answers 404.\n app.use(ASSET_BASE, (req, res, next) => {\n const asset = running ? assets.get(req.path) : undefined\n if (!asset || (req.method !== 'GET' && req.method !== 'HEAD')) {\n next()\n return\n }\n res.type(asset.type).send(asset.body)\n })\n }\n\n const register = () => {\n if (registered) return\n registered = true\n app.registerResourceProvider({\n type: 'plotterExtensions',\n methods: {\n listResources: () => Promise.resolve(running ? { [PLUGIN_ID]: manifest } : {}),\n getResource: (id: string) =>\n running && id === PLUGIN_ID\n ? Promise.resolve(manifest)\n : Promise.reject(new Error(`No such plotterExtensions resource: ${id}`)),\n setResource: () => Promise.reject(new Error(`${PLUGIN_ID} is read-only`)),\n deleteResource: () => Promise.reject(new Error(`${PLUGIN_ID} is read-only`))\n }\n })\n }\n\n return {\n id: PLUGIN_ID,\n name: 'History Widgets',\n description:\n 'History strip and wind steering widgets for Freeboard-SK and other Plotter Extensions hosts',\n schema: () => ({ type: 'object', properties: {} }),\n\n start() {\n running = true\n mount()\n register()\n loadAssets(PUBLIC_DIR)\n .then((loaded) => {\n assets = loaded\n app.setPluginStatus(`Serving ${manifest.widgets.length} widgets`)\n })\n .catch((err: unknown) => {\n app.setPluginError(\n `Web pages missing: ${err instanceof Error ? err.message : String(err)}`\n )\n })\n },\n\n stop() {\n running = false\n }\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- (function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),t.credentials=e.crossOrigin===`use-credentials`?`include`:e.crossOrigin===`anonymous`?`omit`:`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var e=`plotterExt/1`,t={PARSE_ERROR:-32700,INVALID_REQUEST:-32600,METHOD_NOT_FOUND:-32601,INVALID_PARAMS:-32602,INTERNAL_ERROR:-32603,HOST_ERROR:-32e3,TIMEOUT:-32001,CONNECTION_CLOSED:-32002},n=class e extends Error{code;data;constructor(e,n={}){super(e),this.name=`RpcError`,this.code=n.code??t.HOST_ERROR;let r={...n.data??{}};n.reason!==void 0&&(r.reason=n.reason),this.data=Object.keys(r).length>0?r:void 0}get reason(){return typeof this.data?.reason==`string`?this.data.reason:void 0}toErrorObject(){return{code:this.code,message:this.message,...this.data?{data:this.data}:{}}}static fromErrorObject(t){return new e(t.message,{code:t.code,data:t.data})}static from(n){return n instanceof e?n:n instanceof Error?new e(n.message,{code:t.INTERNAL_ERROR}):new e(String(n),{code:t.INTERNAL_ERROR})}},r=`bus.ready`,i=`bus.handshake`;function a(e,t){return e===t||o(e.split(`.`),0,t.split(`.`),0)}function o(e,t,n,r){for(;t<e.length;){let i=e[t];if(i===`**`){if(t===e.length-1)return!0;for(let i=r;i<=n.length;i++)if(o(e,t+1,n,i))return!0;return!1}if(r>=n.length||i!==`*`&&i!==n[r])return!1;t++,r++}return r===n.length}function s(e,t){for(let n of e)if(a(n,t))return!0;return!1}function c(t){return{bus:e,msg:t}}function l(e){if(typeof e!=`object`||!e)return null;let t=e;return t.bus===`plotterExt/1`&&u(t.msg)?t.msg:null}function u(e){if(typeof e!=`object`||!e)return!1;let t=e;if(t.jsonrpc!==`2.0`)return!1;if(typeof t.method==`string`)return t.id===void 0||typeof t.id==`string`||typeof t.id==`number`;if(typeof t.id!=`string`&&typeof t.id!=`number`&&t.id!==null)return!1;let n=`result`in t,r=t.error,i=typeof r==`object`&&!!r&&typeof r.code==`number`&&typeof r.message==`string`;return n?!(`error`in t):i}function d(e){return`method`in e&&`id`in e&&e.id!==void 0}function f(e){return`method`in e&&(!(`id`in e)||e.id===void 0)}function p(e){return!(`method`in e)}function m(e,t={}){let n=t.listenWindow??globalThis,r=t.origin??n.location?.origin??`*`;return{post(t,n){n?.length?e.postMessage(t,{targetOrigin:r,transfer:n}):e.postMessage(t,r)},listen(t){let i=n=>{n.source===e&&(r===`*`||n.origin===r)&&t(n.data)};return n.addEventListener(`message`,i),()=>n.removeEventListener(`message`,i)}}}var h=1e4,g=class{callTimeoutMs;port;unlisten;onError;pending=new Map;methods=new Map;eventHandlers=new Set;idPrefix=Math.random().toString(36).slice(2,8);seq=0;closed=!1;constructor(e){this.port=e.port,this.callTimeoutMs=e.callTimeoutMs??h,this.onError=e.onError??(e=>console.warn(`[plotterext-bus]`,e)),this.unlisten=this.port.listen(e=>this.onData(e))}registerMethod(e,t){this.methods.set(e,t)}unregisterMethod(e){this.methods.delete(e)}onEvent(e,t){let n={patterns:e,fn:t};return this.eventHandlers.add(n),()=>this.eventHandlers.delete(n)}notify(e,t,n={}){this.send({jsonrpc:`2.0`,method:e,...t===void 0?{}:{params:t}},n.transfer)}call(e,r,i={}){if(this.closed)return Promise.reject(new n(`Bus endpoint is closed`,{code:t.CONNECTION_CLOSED,reason:`CLOSED`}));let a=`${this.idPrefix}-${++this.seq}`,o=i.timeoutMs??this.callTimeoutMs;return new Promise((s,c)=>{let l=o>0?setTimeout(()=>{this.pending.delete(a),c(new n(`Call timed out after ${o}ms: ${e}`,{code:t.TIMEOUT,reason:`TIMEOUT`}))},o):null;this.pending.set(a,{resolve:s,reject:c,timer:l}),this.send({jsonrpc:`2.0`,id:a,method:e,...r===void 0?{}:{params:r}},i.transfer)})}close(){if(!this.closed){this.closed=!0,this.unlisten();for(let[,e]of this.pending)e.timer&&clearTimeout(e.timer),e.reject(new n(`Bus endpoint closed`,{code:t.CONNECTION_CLOSED,reason:`CLOSED`}));this.pending.clear(),this.eventHandlers.clear()}}send(e,t){this.closed||this.port.post(c(e),t)}onData(e){let t=l(e);t&&(p(t)?this.onResponse(t):d(t)?this.onRequest(t):f(t)&&this.onNotification(t.method,t.params))}onResponse(e){if(e.id===null)return;let t=this.pending.get(e.id);t&&(this.pending.delete(e.id),t.timer&&clearTimeout(t.timer),`error`in e?t.reject(n.fromErrorObject(e.error)):t.resolve(e.result))}async onRequest(e){let r=this.methods.get(e.method);if(!r)this.send({jsonrpc:`2.0`,id:e.id,error:{code:t.METHOD_NOT_FOUND,message:`Method not found: ${e.method}`}});else try{let t=await r(e.params,{endpoint:this});this.send({jsonrpc:`2.0`,id:e.id,result:t===void 0?null:t})}catch(t){this.send({jsonrpc:`2.0`,id:e.id,error:n.from(t).toErrorObject()})}}onNotification(e,t){for(let n of[...this.eventHandlers])if(s(n.patterns,e))try{n.fn(e,t)}catch(e){this.onError(e)}}},_=class{handshake;endpoint;constructor(e,t){this.endpoint=e,this.handshake=t}get context(){return this.handshake.context}get apiVersion(){return this.handshake.apiVersion}get capabilities(){return this.handshake.capabilities}hasCapability(e){return this.handshake.capabilities.includes(e)}call(e,t,n){return this.endpoint.call(e,t,n)}notify(e,t){this.endpoint.notify(e,t)}async subscribe(e,t){let n=this.endpoint.onEvent(e,t),r;try{r=(await this.call(`events.subscribe`,{patterns:e})).subscriptionId}catch(e){throw n(),e}return async()=>{n(),await this.call(`events.unsubscribe`,{subscriptionId:r}).catch(()=>{})}}async publish(e,t,n){await this.call(`events.publish`,{topic:e,...t===void 0?{}:{params:t},...n?{scope:n}:{}})}state={get:async(e,t)=>(await this.call(`state.get`,{...t?{scope:t}:{},...e?{keys:e}:{}})).values??{},set:async(e,t)=>{await this.call(`state.set`,{...t?{scope:t}:{},values:e})}};signalk={subscribe:async(e,t)=>{let n=e.map(e=>`sk.${e}`),r=await this.subscribe(n,(e,n)=>t(n)),i;try{i=(await this.call(`signalk.subscribe`,{paths:e})).subscriptionId}catch(e){throw await r(),e}return async()=>{await r(),await this.call(`signalk.unsubscribe`,{subscriptionId:i}).catch(()=>{})}},put:(e,t)=>this.call(`signalk.put`,{path:e,value:t})};route={list:async()=>(await this.call(`route.list`)).routes??[],create:async e=>await this.call(`route.create`,e),get:async e=>await this.call(`route.get`,{routeId:e}),replace:async(e,t)=>await this.call(`route.replace`,{routeId:e,points:t}),save:async(e,t)=>await this.call(`route.save`,{routeId:e,...t??{}}),show:async e=>await this.call(`route.show`,{ref:e}),hide:async e=>{await this.call(`route.hide`,{routeId:e})},delete:async e=>{await this.call(`route.delete`,{routeId:e})}};chart={list:async()=>(await this.call(`chart.list`)).charts??[],setVisibility:async(e,t)=>{await this.call(`chart.setVisibility`,{ids:e,visible:t})},setOpacity:async(e,t)=>{await this.call(`chart.setOpacity`,{ids:e,opacity:t})},setOrder:async e=>{await this.call(`chart.setOrder`,{order:e})},setTime:async(e,t)=>{await this.call(`chart.setTime`,{ids:e,time:t})}};nightMode={get:async()=>await this.call(`nightMode.get`),set:async e=>{await this.call(`nightMode.set`,e)}};panels={list:async()=>(await this.call(`ui.listPanels`)).panels};overlays={list:async()=>(await this.call(`overlay.list`)).overlays,setVisibility:async(e,t)=>{await this.call(`overlay.setVisibility`,{ids:e,visible:t})},setOpacity:async(e,t)=>{await this.call(`overlay.setOpacity`,{ids:e,opacity:t})},frame:async e=>await this.call(`overlay.frame`,e,{transfer:[e.image]}),refresh:async e=>{await this.call(`overlay.refresh`,e)},setData:async e=>{await this.call(`overlay.setData`,e)},setLayer:async e=>{await this.call(`overlay.setLayer`,e)},setState:async(e,t)=>{await this.call(`overlay.setState`,{overlayId:e,values:t})}};windows={open:async e=>await this.call(`ui.openWindow`,e),update:async e=>await this.call(`ui.updateWindow`,e),focus:async e=>{await this.call(`ui.focusWindow`,e===void 0?{}:{windowId:e})},close:async e=>{await this.call(`ui.closeWindow`,e===void 0?{}:{windowId:e})},list:async()=>(await this.call(`ui.listWindows`)).windows};resourceGroup={apply:async e=>await this.call(`resourceGroup.apply`,{id:e})};map={getView:async()=>await this.call(`map.getView`),center:async(e,t)=>{await this.call(`map.center`,{position:e,...typeof t==`number`?{zoom:t}:{}})},fitBounds:async e=>{await this.call(`map.fitBounds`,{bounds:e})},pick:async(e={})=>await this.call(`map.pick`,e),cancelPick:async e=>{await this.call(`map.cancelPick`,{pickId:e})}};actions={setVisibility:async(e,t)=>{await this.call(`actions.setVisibility`,{ids:e,visible:t})}};buttons={setVisibility:async(e,t)=>{await this.call(`buttons.setVisibility`,{ids:e,visible:t})},decorate:async(e,t)=>{await this.call(`buttons.decorate`,{...t,ids:e})}};close(){this.endpoint.close()}};function v(e={}){let a=new g({port:e.port??m(globalThis.parent,{origin:`*`}),callTimeoutMs:e.callTimeoutMs,onError:e.onError}),o=e.id?{id:e.id}:void 0;return new Promise((s,c)=>{let l=!1,u=a.onEvent([i],(e,t)=>{l||(l=!0,p(),s(new _(a,t)))}),d=setInterval(()=>a.notify(r,o),e.readyIntervalMs??250),f=setTimeout(()=>{l||(l=!0,p(),a.close(),c(new n(`Timed out waiting for host handshake`,{code:t.TIMEOUT,reason:`HANDSHAKE_TIMEOUT`})))},e.timeoutMs??1e4),p=()=>{u(),clearInterval(d),clearTimeout(f)};a.notify(r,o)})}var y={path:`environment.wind.directionTrue`,label:``,span:30,spanUnit:`min`,unit:``,decimals:-1},b=6e4,x=6048e5,S=[{path:`environment.wind.directionTrue`,label:`TWD`,title:`True wind direction`},{path:`environment.wind.speedTrue`,label:`TWS`,title:`True wind speed`},{path:`environment.wind.angleTrueWater`,label:`TWA`,title:`True wind angle`},{path:`environment.wind.angleApparent`,label:`AWA`,title:`Apparent wind angle`},{path:`environment.wind.speedApparent`,label:`AWS`,title:`Apparent wind speed`},{path:`navigation.speedOverGround`,label:`SOG`,title:`Speed over ground`},{path:`navigation.speedThroughWater`,label:`STW`,title:`Speed through water`},{path:`navigation.courseOverGroundTrue`,label:`COG`,title:`Course over ground`},{path:`navigation.headingTrue`,label:`HDG`,title:`Heading`},{path:`environment.depth.belowTransducer`,label:`Depth`,title:`Depth`},{path:`environment.outside.pressure`,label:`Baro`,title:`Air pressure`},{path:`environment.outside.temperature`,label:`Air`,title:`Air temperature`},{path:`environment.water.temperature`,label:`Water`,title:`Water temperature`}];function C(e){return e.label?e.label:S.find(t=>t.path===e.path)?.label??e.path.split(`.`).pop()??``}function w(e){let t=e.span*(e.spanUnit===`h`?36e5:6e4);return Math.min(x,Math.max(b,t))}function T(e){let t=(e,t)=>typeof e==`string`?e.trim():t,n=(e,t)=>typeof e==`number`&&Number.isFinite(e)?e:t,r=n(e.span,y.span);return{path:t(e.path,y.path),label:t(e.label,y.label),span:r>0?r:y.span,spanUnit:e.spanUnit===`h`?`h`:`min`,unit:t(e.unit,y.unit),decimals:Math.min(4,Math.round(n(e.decimals,y.decimals)))}}function E(e){let t=e.split(`.`).pop()??``;return!/direction|heading|course|bearing/i.test(t)}function D(e){return typeof e==`object`&&!!e}async function O(e){try{let t=await fetch(`/signalk/v1/api/vessels/self/${e.replace(/\./g,`/`)}/meta`,{credentials:`include`});if(!t.ok)return;let n=await t.json();if(!D(n))return;let r={};typeof n.units==`string`&&(r.units=n.units);let i=n.displayUnits;return D(i)&&(r.displayUnits={},typeof i.targetUnit==`string`&&(r.displayUnits.targetUnit=i.targetUnit),typeof i.displayFormat==`string`&&(r.displayUnits.displayFormat=i.displayFormat)),r}catch{return}}function k(e,t=``,n=[]){if(!D(e))return n;if(`value`in e){if(typeof e.value==`number`){let r=e.meta;n.push({path:t,units:D(r)&&typeof r.units==`string`?r.units:void 0})}return n}for(let[r,i]of Object.entries(e))r!==`meta`&&r!==`$source`&&r!==`timestamp`&&r!==`values`&&k(i,t?`${t}.${r}`:r,n);return n}async function A(){try{let e=await fetch(`/signalk/v1/api/vessels/self`,{credentials:`include`});return e.ok?k(await e.json()).sort((e,t)=>e.path.localeCompare(t.path)):[]}catch{return[]}}async function j(){try{let e=await fetch(`/signalk/v2/api/history/_providers`,{credentials:`include`});if(!e.ok)return[];let t=await e.json();return D(t)?Object.keys(t):[]}catch{return[]}}var M=(e,t,n,r=0,i=0)=>({id:e,symbol:t,factor:n,offset:r,add:i}),N={rad:[M(`degree`,`°`,180/Math.PI),M(`radian`,`rad`,1)],"m/s":[M(`kn`,`kn`,1.94384),M(`m/s`,`m/s`,1),M(`km/h`,`km/h`,3.6),M(`mph`,`mph`,2.2369362920544025)],K:[M(`C`,`°C`,1,-273.15),M(`F`,`°F`,9/5,-273.15,32),M(`K`,`K`,1)],Pa:[M(`hPa`,`hPa`,.01),M(`mbar`,`mbar`,.01),M(`bar`,`bar`,1e-5),M(`psi`,`psi`,.0001450376807894691),M(`inHg`,`inHg`,.00029529987601298443),M(`mmHg`,`mmHg`,.0075006168507298),M(`Pa`,`Pa`,1)],m:[M(`m`,`m`,1),M(`meter`,`m`,1),M(`foot`,`ft`,3.280839895013124),M(`fathom`,`fathom`,.5467468562055768),M(`naut-mile`,`nmi`,.0005399568034557236),M(`kilometer`,`km`,.001),M(`mile`,`mi`,.000621371192237334)],"rad/s":[M(`deg/s`,`°/s`,57.29577951308232),M(`rpm`,`rpm`,9.549296585513723),M(`rad/s`,`rad/s`,1)],ratio:[M(`percent`,`%`,100),M(`ratio`,``,1)],Hz:[M(`rpm`,`rpm`,60),M(`hertz`,`Hz`,1)],V:[M(`V`,`V`,1)],A:[M(`A`,`A`,1)],W:[M(`watt`,`W`,1),M(`kW`,`kW`,.001)],m3:[M(`liter`,`l`,1e3),M(`gallon`,`gal`,264.1720512415585),M(`m3`,`m³`,1)],"m3/s":[M(`L/h`,`l/h`,36e5),M(`L/min`,`l/min`,6e4),M(`gal/h`,`gal/h`,264.17205236)]};function P(e,t){return t?(e+(t.offset??0))*t.factor+(t.add??0):e}function F(e){if(e)return/\.(0+)/.exec(e)?.[1]?.length??0}function I(e,t,n){let r=e?.units,i=r?N[r]??[]:[],a=e=>i.find(t=>t.id===e),o=a(t)??a(e?.displayUnits?.targetUnit)??i[0],s=r===`rad`,c=o&&o.id===e?.displayUnits?.targetUnit?F(e.displayUnits.displayFormat):void 0,l=o?.id===`degree`?0:c??1;return{conversion:o,symbol:o?.symbol??r??``,decimals:n>=0?n:l,angular:s}}export{O as a,E as c,w as d,v as f,j as i,C as l,P as n,A as o,I as r,S as s,N as t,T as u};
1
+ (function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),t.credentials=e.crossOrigin===`use-credentials`?`include`:e.crossOrigin===`anonymous`?`omit`:`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var e=`plotterExt/1`,t={PARSE_ERROR:-32700,INVALID_REQUEST:-32600,METHOD_NOT_FOUND:-32601,INVALID_PARAMS:-32602,INTERNAL_ERROR:-32603,HOST_ERROR:-32e3,TIMEOUT:-32001,CONNECTION_CLOSED:-32002},n=class e extends Error{code;data;constructor(e,n={}){super(e),this.name=`RpcError`,this.code=n.code??t.HOST_ERROR;let r={...n.data??{}};n.reason!==void 0&&(r.reason=n.reason),this.data=Object.keys(r).length>0?r:void 0}get reason(){return typeof this.data?.reason==`string`?this.data.reason:void 0}toErrorObject(){return{code:this.code,message:this.message,...this.data?{data:this.data}:{}}}static fromErrorObject(t){return new e(t.message,{code:t.code,data:t.data})}static from(n){return n instanceof e?n:n instanceof Error?new e(n.message,{code:t.INTERNAL_ERROR}):new e(String(n),{code:t.INTERNAL_ERROR})}},r=`bus.ready`,i=`bus.handshake`;function a(e,t){return e===t||o(e.split(`.`),0,t.split(`.`),0)}function o(e,t,n,r){for(;t<e.length;){let i=e[t];if(i===`**`){if(t===e.length-1)return!0;for(let i=r;i<=n.length;i++)if(o(e,t+1,n,i))return!0;return!1}if(r>=n.length||i!==`*`&&i!==n[r])return!1;t++,r++}return r===n.length}function s(e,t){for(let n of e)if(a(n,t))return!0;return!1}function c(t){return{bus:e,msg:t}}function l(e){if(typeof e!=`object`||!e)return null;let t=e;return t.bus===`plotterExt/1`&&u(t.msg)?t.msg:null}function u(e){if(typeof e!=`object`||!e)return!1;let t=e;if(t.jsonrpc!==`2.0`)return!1;if(typeof t.method==`string`)return t.id===void 0||typeof t.id==`string`||typeof t.id==`number`;if(typeof t.id!=`string`&&typeof t.id!=`number`&&t.id!==null)return!1;let n=`result`in t,r=t.error,i=typeof r==`object`&&!!r&&typeof r.code==`number`&&typeof r.message==`string`;return n?!(`error`in t):i}function d(e){return`method`in e&&`id`in e&&e.id!==void 0}function f(e){return`method`in e&&(!(`id`in e)||e.id===void 0)}function p(e){return!(`method`in e)}function m(e,t={}){let n=t.listenWindow??globalThis,r=t.origin??n.location?.origin??`*`;return{post(t,n){n?.length?e.postMessage(t,{targetOrigin:r,transfer:n}):e.postMessage(t,r)},listen(t){let i=n=>{n.source===e&&(r===`*`||n.origin===r)&&t(n.data)};return n.addEventListener(`message`,i),()=>n.removeEventListener(`message`,i)}}}var h=1e4,g=class{callTimeoutMs;port;unlisten;onError;pending=new Map;methods=new Map;eventHandlers=new Set;idPrefix=Math.random().toString(36).slice(2,8);seq=0;closed=!1;constructor(e){this.port=e.port,this.callTimeoutMs=e.callTimeoutMs??h,this.onError=e.onError??(e=>console.warn(`[plotterext-bus]`,e)),this.unlisten=this.port.listen(e=>this.onData(e))}registerMethod(e,t){this.methods.set(e,t)}unregisterMethod(e){this.methods.delete(e)}onEvent(e,t){let n={patterns:e,fn:t};return this.eventHandlers.add(n),()=>this.eventHandlers.delete(n)}notify(e,t,n={}){this.send({jsonrpc:`2.0`,method:e,...t===void 0?{}:{params:t}},n.transfer)}call(e,r,i={}){if(this.closed)return Promise.reject(new n(`Bus endpoint is closed`,{code:t.CONNECTION_CLOSED,reason:`CLOSED`}));let a=`${this.idPrefix}-${++this.seq}`,o=i.timeoutMs??this.callTimeoutMs;return new Promise((s,c)=>{let l=o>0?setTimeout(()=>{this.pending.delete(a),c(new n(`Call timed out after ${o}ms: ${e}`,{code:t.TIMEOUT,reason:`TIMEOUT`}))},o):null;this.pending.set(a,{resolve:s,reject:c,timer:l}),this.send({jsonrpc:`2.0`,id:a,method:e,...r===void 0?{}:{params:r}},i.transfer)})}close(){if(!this.closed){this.closed=!0,this.unlisten();for(let[,e]of this.pending)e.timer&&clearTimeout(e.timer),e.reject(new n(`Bus endpoint closed`,{code:t.CONNECTION_CLOSED,reason:`CLOSED`}));this.pending.clear(),this.eventHandlers.clear()}}send(e,t){this.closed||this.port.post(c(e),t)}onData(e){let t=l(e);t&&(p(t)?this.onResponse(t):d(t)?this.onRequest(t):f(t)&&this.onNotification(t.method,t.params))}onResponse(e){if(e.id===null)return;let t=this.pending.get(e.id);t&&(this.pending.delete(e.id),t.timer&&clearTimeout(t.timer),`error`in e?t.reject(n.fromErrorObject(e.error)):t.resolve(e.result))}async onRequest(e){let r=this.methods.get(e.method);if(!r)this.send({jsonrpc:`2.0`,id:e.id,error:{code:t.METHOD_NOT_FOUND,message:`Method not found: ${e.method}`}});else try{let t=await r(e.params,{endpoint:this});this.send({jsonrpc:`2.0`,id:e.id,result:t===void 0?null:t})}catch(t){this.send({jsonrpc:`2.0`,id:e.id,error:n.from(t).toErrorObject()})}}onNotification(e,t){for(let n of[...this.eventHandlers])if(s(n.patterns,e))try{n.fn(e,t)}catch(e){this.onError(e)}}},_=class{handshake;endpoint;constructor(e,t){this.endpoint=e,this.handshake=t}get context(){return this.handshake.context}get apiVersion(){return this.handshake.apiVersion}get capabilities(){return this.handshake.capabilities}hasCapability(e){return this.handshake.capabilities.includes(e)}call(e,t,n){return this.endpoint.call(e,t,n)}notify(e,t){this.endpoint.notify(e,t)}async subscribe(e,t){let n=this.endpoint.onEvent(e,t),r;try{r=(await this.call(`events.subscribe`,{patterns:e})).subscriptionId}catch(e){throw n(),e}return async()=>{n(),await this.call(`events.unsubscribe`,{subscriptionId:r}).catch(()=>{})}}async publish(e,t,n){await this.call(`events.publish`,{topic:e,...t===void 0?{}:{params:t},...n?{scope:n}:{}})}state={get:async(e,t)=>(await this.call(`state.get`,{...t?{scope:t}:{},...e?{keys:e}:{}})).values??{},set:async(e,t)=>{await this.call(`state.set`,{...t?{scope:t}:{},values:e})}};signalk={subscribe:async(e,t)=>{let n=e.map(e=>`sk.${e}`),r=await this.subscribe(n,(e,n)=>t(n)),i;try{i=(await this.call(`signalk.subscribe`,{paths:e})).subscriptionId}catch(e){throw await r(),e}return async()=>{await r(),await this.call(`signalk.unsubscribe`,{subscriptionId:i}).catch(()=>{})}},put:(e,t)=>this.call(`signalk.put`,{path:e,value:t})};route={list:async()=>(await this.call(`route.list`)).routes??[],create:async e=>await this.call(`route.create`,e),get:async e=>await this.call(`route.get`,{routeId:e}),replace:async(e,t)=>await this.call(`route.replace`,{routeId:e,points:t}),save:async(e,t)=>await this.call(`route.save`,{routeId:e,...t??{}}),show:async e=>await this.call(`route.show`,{ref:e}),hide:async e=>{await this.call(`route.hide`,{routeId:e})},delete:async e=>{await this.call(`route.delete`,{routeId:e})}};chart={list:async()=>(await this.call(`chart.list`)).charts??[],setVisibility:async(e,t)=>{await this.call(`chart.setVisibility`,{ids:e,visible:t})},setOpacity:async(e,t)=>{await this.call(`chart.setOpacity`,{ids:e,opacity:t})},setOrder:async e=>{await this.call(`chart.setOrder`,{order:e})},setTime:async(e,t)=>{await this.call(`chart.setTime`,{ids:e,time:t})}};nightMode={get:async()=>await this.call(`nightMode.get`),set:async e=>{await this.call(`nightMode.set`,e)}};panels={list:async()=>(await this.call(`ui.listPanels`)).panels};overlays={list:async()=>(await this.call(`overlay.list`)).overlays,setVisibility:async(e,t)=>{await this.call(`overlay.setVisibility`,{ids:e,visible:t})},setOpacity:async(e,t)=>{await this.call(`overlay.setOpacity`,{ids:e,opacity:t})},frame:async e=>await this.call(`overlay.frame`,e,{transfer:[e.image]}),refresh:async e=>{await this.call(`overlay.refresh`,e)},setData:async e=>{await this.call(`overlay.setData`,e)},setLayer:async e=>{await this.call(`overlay.setLayer`,e)},setState:async(e,t)=>{await this.call(`overlay.setState`,{overlayId:e,values:t})}};windows={open:async e=>await this.call(`ui.openWindow`,e),update:async e=>await this.call(`ui.updateWindow`,e),focus:async e=>{await this.call(`ui.focusWindow`,e===void 0?{}:{windowId:e})},close:async e=>{await this.call(`ui.closeWindow`,e===void 0?{}:{windowId:e})},list:async()=>(await this.call(`ui.listWindows`)).windows};resourceGroup={apply:async e=>await this.call(`resourceGroup.apply`,{id:e})};map={getView:async()=>await this.call(`map.getView`),center:async(e,t)=>{await this.call(`map.center`,{position:e,...typeof t==`number`?{zoom:t}:{}})},fitBounds:async e=>{await this.call(`map.fitBounds`,{bounds:e})},pick:async(e={})=>await this.call(`map.pick`,e),cancelPick:async e=>{await this.call(`map.cancelPick`,{pickId:e})}};actions={setVisibility:async(e,t)=>{await this.call(`actions.setVisibility`,{ids:e,visible:t})}};buttons={setVisibility:async(e,t)=>{await this.call(`buttons.setVisibility`,{ids:e,visible:t})},decorate:async(e,t)=>{await this.call(`buttons.decorate`,{...t,ids:e})}};close(){this.endpoint.close()}};function v(e={}){let a=new g({port:e.port??m(globalThis.parent,{origin:`*`}),callTimeoutMs:e.callTimeoutMs,onError:e.onError}),o=e.id?{id:e.id}:void 0;return new Promise((s,c)=>{let l=!1,u=a.onEvent([i],(e,t)=>{l||(l=!0,p(),s(new _(a,t)))}),d=setInterval(()=>a.notify(r,o),e.readyIntervalMs??250),f=setTimeout(()=>{l||(l=!0,p(),a.close(),c(new n(`Timed out waiting for host handshake`,{code:t.TIMEOUT,reason:`HANDSHAKE_TIMEOUT`})))},e.timeoutMs??1e4),p=()=>{u(),clearInterval(d),clearTimeout(f)};a.notify(r,o)})}export{v as t};
@@ -1,4 +1,4 @@
1
- import{f as e,i as t,o as n,s as r,t as i,u as a}from"./units-D0PJ17NW.js";function o(e){return e.replace(/[&<>"]/g,e=>`&#${e.charCodeAt(0)};`)}function s(e){let t=document.getElementById(e);if(!t)throw Error(`missing #${e}`);return t}function c(e){let t=s(e);if(t instanceof HTMLInputElement||t instanceof HTMLSelectElement)return t;throw Error(`#${e} is not a form field`)}function l(e,t,n){let i=r.map(t=>`<button type="button" class="chip${t.path===e.path?` on`:``}" data-path="${t.path}" title="${o(t.title)}">${t.label}</button>`).join(``),a=n.length>0?`History from ${o(n.join(`, `))}: the strip fills at once.`:`No history provider on the server: the strip fills from live data. Install one (for example KIP, signalk-parquet or signalk-to-influxdb2) to see the past window at once.`;return`
1
+ import{t as e}from"./chunk-U7YORXZ7-BLTBSYrv.js";import{i as t,t as n}from"./config-model-DvTH1O2f.js";import{i as r,o as i,t as a}from"./units-zSVjGfwB.js";/* empty css */function o(e){return e.replace(/[&<>"]/g,e=>`&#${e.charCodeAt(0)};`)}function s(e){let t=document.getElementById(e);if(!t)throw Error(`missing #${e}`);return t}function c(e){let t=s(e);if(t instanceof HTMLInputElement||t instanceof HTMLSelectElement)return t;throw Error(`#${e} is not a form field`)}function l(e,t,r){let i=n.map(t=>`<button type="button" class="chip${t.path===e.path?` on`:``}" data-path="${t.path}" title="${o(t.title)}">${t.label}</button>`).join(``),a=r.length>0?`History from ${o(r.join(`, `))}: the strip fills at once.`:`No history provider on the server: the strip fills from live data. Install one (for example KIP, signalk-parquet or signalk-to-influxdb2) to see the past window at once.`;return`
2
2
  <div class="chips">${i}</div>
3
3
  <label class="row"><span>Signal K path</span>
4
4
  <input id="path" list="paths" value="${o(e.path)}" placeholder="Type to search…">
@@ -27,4 +27,4 @@ import{f as e,i as t,o as n,s as r,t as i,u as a}from"./units-D0PJ17NW.js";funct
27
27
  <div class="actions">
28
28
  <button type="button" id="cancel">Cancel</button>
29
29
  <button type="button" id="save" class="primary">Save</button>
30
- </div>`}function u(e,t){let n=c(`path`).value.trim(),r=e.find(e=>e.path===n)?.units,a=r?i[r]??[]:[],l=a.some(e=>e.id===t)?t:``;s(`unit`).innerHTML=[`<option value=""${l===``?` selected`:``}>Server default</option>`,...a.filter((e,t,n)=>n.findIndex(t=>t.symbol===e.symbol)===t).map(e=>`<option value="${e.id}"${e.id===l?` selected`:``}>${o(e.symbol||e.id)}</option>`)].join(``)}function d(){let e=Number(c(`span`).value);return{path:c(`path`).value.trim(),label:c(`label`).value.trim(),span:Number.isFinite(e)&&e>0?e:30,spanUnit:c(`spanUnit`).value,unit:c(`unit`).value,decimals:Number(c(`decimals`).value)}}async function f(){let r=s(`root`),i=await e();r.innerHTML=`<p class="status">Loading…</p>`;let[o,f,p]=await Promise.all([i.state.get(),n(),t()]),m=a(o);r.innerHTML=l(m,f,p),u(f,m.unit);let h=c(`path`),g=()=>{u(f,c(`unit`).value);for(let e of document.querySelectorAll(`.chip`))e.classList.toggle(`on`,e.dataset.path===h.value.trim())};h.addEventListener(`change`,g),h.addEventListener(`input`,g);for(let e of document.querySelectorAll(`.chip`))e.addEventListener(`click`,()=>{h.value=e.dataset.path??``,g()});let _=s(`status`);s(`save`).addEventListener(`click`,()=>{i.state.set(d()).then(()=>i.call(`ui.closePanel`)).catch(e=>{_.textContent=`Save failed: ${e instanceof Error?e.message:String(e)}`})}),s(`cancel`).addEventListener(`click`,()=>{i.call(`ui.closePanel`).catch(()=>void 0)})}f().catch(e=>{let t=document.getElementById(`root`);t&&(t.textContent=`Host connection failed: ${e instanceof Error?e.message:String(e)}`)});
30
+ </div>`}function u(e,t){let n=c(`path`).value.trim(),r=e.find(e=>e.path===n)?.units,i=r?a[r]??[]:[],l=i.some(e=>e.id===t)?t:``;s(`unit`).innerHTML=[`<option value=""${l===``?` selected`:``}>Server default</option>`,...i.filter((e,t,n)=>n.findIndex(t=>t.symbol===e.symbol)===t).map(e=>`<option value="${e.id}"${e.id===l?` selected`:``}>${o(e.symbol||e.id)}</option>`)].join(``)}function d(){let e=Number(c(`span`).value);return{path:c(`path`).value.trim(),label:c(`label`).value.trim(),span:Number.isFinite(e)&&e>0?e:30,spanUnit:c(`spanUnit`).value,unit:c(`unit`).value,decimals:Number(c(`decimals`).value)}}async function f(){let n=s(`root`),a=await e();n.innerHTML=`<p class="status">Loading…</p>`;let[o,f,p]=await Promise.all([a.state.get(),i(),r()]),m=t(o);n.innerHTML=l(m,f,p),u(f,m.unit);let h=c(`path`),g=()=>{u(f,c(`unit`).value);for(let e of document.querySelectorAll(`.chip`))e.classList.toggle(`on`,e.dataset.path===h.value.trim())};h.addEventListener(`change`,g),h.addEventListener(`input`,g);for(let e of document.querySelectorAll(`.chip`))e.addEventListener(`click`,()=>{h.value=e.dataset.path??``,g()});let _=s(`status`);s(`save`).addEventListener(`click`,()=>{a.state.set(d()).then(()=>a.call(`ui.closePanel`)).catch(e=>{_.textContent=`Save failed: ${e instanceof Error?e.message:String(e)}`})}),s(`cancel`).addEventListener(`click`,()=>{a.call(`ui.closePanel`).catch(()=>void 0)})}f().catch(e=>{let t=document.getElementById(`root`);t&&(t.textContent=`Host connection failed: ${e instanceof Error?e.message:String(e)}`)});
@@ -1 +1 @@
1
- :root{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light dark;--fg:#1d2329;--muted:#5b6b78;--bg:#fff;--line:#c9d3db;--accent:#1976d2}@media (prefers-color-scheme:dark){:root{--lightningcss-light: ;--lightningcss-dark:initial;--fg:#e8edf2;--muted:#9fb3c4;--bg:#1b2127;--line:#3a4652;--accent:#42a5f5}}body{color:var(--fg);background:var(--bg);margin:0;padding:12px 16px;font-family:Helvetica Neue,Arial,sans-serif;font-size:15px}.chips{flex-wrap:wrap;gap:6px;margin-bottom:12px;display:flex}.chip{border:1px solid var(--line);min-width:52px;min-height:36px;color:var(--fg);background:0 0;border-radius:18px;font-size:14px}.chip.on{background:var(--accent);border-color:var(--accent);color:#fff}.row{grid-template-columns:110px 1fr;align-items:center;gap:8px;margin-bottom:10px;display:grid}.pair{grid-template-columns:1fr 1fr;gap:8px;display:grid}input,select{border:1px solid var(--line);background:var(--bg);min-width:0;min-height:36px;color:var(--fg);border-radius:6px;padding:4px 8px;font-size:15px}.status{color:var(--muted);font-size:13px}.actions{justify-content:flex-end;gap:8px;display:flex}.actions button{border:1px solid var(--line);min-width:90px;min-height:40px;color:var(--fg);background:0 0;border-radius:6px;font-size:15px}.actions .primary{background:var(--accent);border-color:var(--accent);color:#fff}
1
+ :root{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light dark;--fg:#1d2329;--muted:#5b6b78;--bg:#fff;--line:#c9d3db;--accent:#1976d2}@media (prefers-color-scheme:dark){:root{--lightningcss-light: ;--lightningcss-dark:initial;--fg:#e8edf2;--muted:#9fb3c4;--bg:#1b2127;--line:#3a4652;--accent:#42a5f5}}body{color:var(--fg);background:var(--bg);margin:0;padding:12px 16px;font-family:Helvetica Neue,Arial,sans-serif;font-size:15px}.chips{flex-wrap:wrap;gap:6px;margin-bottom:12px;display:flex}.chip{border:1px solid var(--line);min-width:52px;min-height:36px;color:var(--fg);background:0 0;border-radius:18px;font-size:14px}.chip.on{background:var(--accent);border-color:var(--accent);color:#fff}.row{grid-template-columns:110px 1fr;align-items:center;gap:8px;margin-bottom:10px;display:grid}.pair{grid-template-columns:1fr 1fr;gap:8px;display:grid}input,select{border:1px solid var(--line);background:var(--bg);min-width:0;min-height:36px;color:var(--fg);border-radius:6px;padding:4px 8px;font-size:15px}.status{color:var(--muted);font-size:13px}.actions{justify-content:flex-end;gap:8px;display:flex}.actions button{border:1px solid var(--line);min-width:90px;min-height:40px;color:var(--fg);background:0 0;border-radius:6px;font-size:15px}.actions .primary{background:var(--accent);border-color:var(--accent);color:#fff}.toggles{gap:6px;margin:4px 0 10px;display:grid}.toggle{align-items:center;gap:10px;min-height:36px;display:flex}.toggle input{width:22px;height:22px;min-height:0}
@@ -0,0 +1 @@
1
+ var e={path:`environment.wind.directionTrue`,label:``,span:30,spanUnit:`min`,unit:``,decimals:-1},t=6e4,n=6048e5,r=[{path:`environment.wind.directionTrue`,label:`TWD`,title:`True wind direction`},{path:`environment.wind.speedTrue`,label:`TWS`,title:`True wind speed`},{path:`environment.wind.angleTrueWater`,label:`TWA`,title:`True wind angle`},{path:`environment.wind.angleApparent`,label:`AWA`,title:`Apparent wind angle`},{path:`environment.wind.speedApparent`,label:`AWS`,title:`Apparent wind speed`},{path:`navigation.speedOverGround`,label:`SOG`,title:`Speed over ground`},{path:`navigation.speedThroughWater`,label:`STW`,title:`Speed through water`},{path:`navigation.courseOverGroundTrue`,label:`COG`,title:`Course over ground`},{path:`navigation.headingTrue`,label:`HDG`,title:`Heading`},{path:`environment.depth.belowTransducer`,label:`Depth`,title:`Depth`},{path:`environment.outside.pressure`,label:`Baro`,title:`Air pressure`},{path:`environment.outside.temperature`,label:`Air`,title:`Air temperature`},{path:`environment.water.temperature`,label:`Water`,title:`Water temperature`}];function i(e){return e.label?e.label:r.find(t=>t.path===e.path)?.label??e.path.split(`.`).pop()??``}function a(e){let r=e.span*(e.spanUnit===`h`?36e5:6e4);return Math.min(n,Math.max(t,r))}function o(t){let n=(e,t)=>typeof e==`string`?e.trim():t,r=(e,t)=>typeof e==`number`&&Number.isFinite(e)?e:t,i=r(t.span,e.span);return{path:n(t.path,e.path),label:n(t.label,e.label),span:i>0?i:e.span,spanUnit:t.spanUnit===`h`?`h`:`min`,unit:n(t.unit,e.unit),decimals:Math.min(4,Math.round(r(t.decimals,e.decimals)))}}function s(e){let t=e.split(`.`).pop()??``;return!/direction|heading|course|bearing/i.test(t)}export{a,o as i,s as n,i as r,r as t};
@@ -0,0 +1 @@
1
+ import{t as e}from"./chunk-U7YORXZ7-BLTBSYrv.js";import{n as t,t as n}from"./styles-CjtPzFRR.js";import{a as r,n as i,r as a}from"./units-zSVjGfwB.js";import{a as o,c as s,d as c,i as l,l as u,n as d,o as f,r as p,s as m,u as h}from"./steer-model-DA5-9SMr.js";var g={card:`rgba(20, 26, 32, 0.78)`,ring:`#cfd8dc`,ringText:`#1d2329`,text:`#e8edf2`,muted:`#9fb3c4`,port:`#e53935`,starboard:`#43a047`,apparent:`#29b6f6`,trueWind:`#ffa726`,shift:`rgba(255, 167, 38, 0.28)`,waypoint:`#ab47bc`,course:`#ec407a`,current:`#26c6da`},_={card:`rgba(8, 0, 0, 0.85)`,ring:`#4a0c0c`,ringText:`#d32f2f`,text:`#d32f2f`,muted:`#8e2424`,port:`#b71c1c`,starboard:`#7f1212`,apparent:`#ff5252`,trueWind:`#d32f2f`,shift:`rgba(211, 47, 47, 0.22)`,waypoint:`#c62828`,course:`#e57373`,current:`#ef9a9a`},v=Math.PI/180,y=35*v,b=`'Helvetica Neue', Arial, sans-serif`;function x(e,t,n){return[e.cx+n*Math.sin(t),e.cy-n*Math.cos(t)]}function S(e){return e-Math.PI/2}function C(e,t){return c(t-e)}function ee(e,t,n){let r=n.palette,i=t.r-t.band/2;e.lineWidth=t.band,e.strokeStyle=r.ring,e.beginPath(),e.arc(t.cx,t.cy,i,0,2*Math.PI),e.stroke(),e.lineWidth=t.band*.2;for(let[n,i,a]of[[r.port,-60*v,-20*v],[r.starboard,20*v,60*v]])e.strokeStyle=n,e.beginPath(),e.arc(t.cx,t.cy,t.r-t.band*.9,S(i),S(a)),e.stroke();let a=n.dial===`compass`?n.heading:0;if(e.strokeStyle=r.ringText,e.fillStyle=r.ringText,e.textAlign=`center`,e.textBaseline=`middle`,e.font=`600 ${Math.max(8,t.band*.5)}px ${b}`,a!==void 0)for(let r=0;r<360;r+=10){let i=r*v-a,o=r%30==0;e.lineWidth=o?2:1;let[s,c]=x(t,i,t.r),[l,u]=x(t,i,t.r-t.band*(o?.28:.18));if(e.beginPath(),e.moveTo(s,c),e.lineTo(l,u),e.stroke(),!o)continue;let d=n.dial===`compass`?w(r):String(r>180?360-r:r),[f,p]=x(t,i,t.r-t.band*.62);e.save(),e.translate(f,p),e.rotate(i),e.fillText(d,0,0),e.restore()}}function w(e){return{0:`N`,90:`E`,180:`S`,270:`W`}[e]??String(e)}function T(e,t,n,r,i){let[a,o]=x(t,n,t.r-t.band);e.strokeStyle=r,e.lineWidth=Math.max(1.5,t.r*.012),e.setLineDash(i?[t.r*.05,t.r*.04]:[]),e.beginPath(),e.moveTo(t.cx,t.cy),e.lineTo(a,o),e.stroke(),e.setLineDash([])}function E(e,t,n){if(!n.shift)return;e.fillStyle=n.palette.shift,e.beginPath(),e.moveTo(t.cx,t.cy);let r=S(n.shift.from);e.arc(t.cx,t.cy,t.r-t.band,r,r+C(n.shift.from,n.shift.to)),e.closePath(),e.fill()}function D(e,t,n,r,i,a){let o=t.r-t.band,s=x(t,n,o*(1-a)),c=t.r*.07,l=x(t,n,o),u=[l[0]-c*Math.cos(n),l[1]-c*Math.sin(n)],d=[l[0]+c*Math.cos(n),l[1]+c*Math.sin(n)];e.fillStyle=r,e.beginPath(),e.moveTo(s[0],s[1]),e.lineTo(u[0],u[1]),e.lineTo(d[0],d[1]),e.closePath(),e.fill();let[f,p]=x(t,n,o-t.r*.07);e.fillStyle=`#10161b`,e.font=`700 ${Math.max(8,t.r*.075)}px ${b}`,e.textAlign=`center`,e.textBaseline=`middle`,e.fillText(i,f,p)}function O(e,t,n,r,i){let[a,o]=x(t,n,t.r-t.band/2),s=t.band*.32;e.fillStyle=r,e.strokeStyle=`#10161b`,e.lineWidth=1.5,e.beginPath(),i===`dot`?e.arc(a,o,s,0,2*Math.PI):(e.moveTo(a+s*Math.sin(n),o-s*Math.cos(n)),e.lineTo(a+s*Math.cos(n),o+s*Math.sin(n)),e.lineTo(a-s*Math.sin(n),o+s*Math.cos(n)),e.lineTo(a-s*Math.cos(n),o-s*Math.sin(n)),e.closePath()),e.fill(),e.stroke()}function k(e,t,n){let r=n.current;if(!r)return;let i=(t.r-t.band)*(.25+.45*Math.min(1,r.fraction)),[a,o]=x(t,r.angle,i),s=t.r*.06;e.strokeStyle=n.palette.current,e.fillStyle=n.palette.current,e.lineWidth=Math.max(2,t.r*.02),e.beginPath(),e.moveTo(t.cx,t.cy),e.lineTo(a,o),e.stroke();let[c,l]=x(t,r.angle,i-s*1.6);e.beginPath(),e.moveTo(a,o),e.lineTo(c+s*Math.cos(r.angle),l+s*Math.sin(r.angle)),e.lineTo(c-s*Math.cos(r.angle),l-s*Math.sin(r.angle)),e.closePath(),e.fill();let[u,d]=x(t,r.angle,i+t.r*.09);e.font=`600 ${Math.max(8,t.r*.075)}px ${b}`,e.textAlign=`center`,e.textBaseline=`middle`,e.fillText(r.text,u,d)}function A(e,t,n){let r=t.r*.38,i=r*.34;e.strokeStyle=n.palette.muted,e.lineWidth=1.5,e.beginPath(),e.moveTo(t.cx,t.cy-r),e.bezierCurveTo(t.cx+i*1.1,t.cy-r*.45,t.cx+i,t.cy+r*.5,t.cx+i*.7,t.cy+r*.8),e.lineTo(t.cx-i*.7,t.cy+r*.8),e.bezierCurveTo(t.cx-i,t.cy+r*.5,t.cx-i*1.1,t.cy-r*.45,t.cx,t.cy-r),e.stroke()}function j(e,t,n){let r=t.band*.45,i=t.cy-t.r;e.fillStyle=n.palette.text,e.beginPath(),e.moveTo(t.cx,i+r),e.lineTo(t.cx-r*.7,i-r*.25),e.lineTo(t.cx+r*.7,i-r*.25),e.closePath(),e.fill()}function M(e,t,n,r,i,a,o,s){e.textAlign=i,e.textBaseline=`top`;let c=a*.5,l=s?r-a-c-2:r,u=s?r-a:r+c+2;e.fillStyle=o.muted,e.font=`600 ${c}px ${b}`,e.fillText(t.unit?`${t.label} ${t.unit}`:t.label,n,l),e.fillStyle=o.text,e.font=`700 ${a}px ${b}`,e.fillText(t.value,n,u)}function te(e,t,n,r){if(n.rudder===void 0)return;let i=t.r*.55,a=Math.max(4,t.r*.05);e.fillStyle=n.palette.ring,e.globalAlpha=.35,e.fillRect(t.cx-i,r,i*2,a),e.globalAlpha=1;let o=Math.max(-1,Math.min(1,n.rudder/y));e.fillStyle=o<0?n.palette.port:n.palette.starboard,e.fillRect(Math.min(t.cx,t.cx+o*i),r,Math.abs(o)*i,a),e.fillStyle=n.palette.text,e.fillRect(t.cx-1,r-2,2,a+4)}function N(e,t,n,r){let i=r.palette;e.clearRect(0,0,t,n),e.fillStyle=i.card,e.beginPath(),e.roundRect(0,0,t,n,8),e.fill();let a=Math.max(4,Math.min(t,n)*.03),o=r.rudder===void 0?0:Math.min(t,n)*.06,s=Math.max(10,Math.min(t,n-o)/2-a),c={cx:t/2,cy:(n-o)/2,r:s,band:s*.16};E(e,c,r),ee(e,c,r),A(e,c,r),r.trueWind!==void 0&&r.closeHauled!==void 0&&(T(e,c,r.trueWind+r.closeHauled,i.port,!0),T(e,c,r.trueWind-r.closeHauled,i.starboard,!0)),r.waypoint!==void 0&&O(e,c,r.waypoint,i.waypoint,`dot`),r.course!==void 0&&O(e,c,r.course,i.course,`diamond`),k(e,c,r),r.trueWind!==void 0&&D(e,c,r.trueWind,i.trueWind,`T`,.22),r.apparent!==void 0&&D(e,c,r.apparent,i.apparent,`A`,.5),j(e,c,r),e.fillStyle=i.text,e.textAlign=`center`,e.textBaseline=`middle`,e.font=`700 ${Math.max(10,s*.2)}px ${b}`,e.fillText(r.heading===void 0?`---`:`${l(r.heading)}°`,c.cx,c.cy);let u=Math.max(10,Math.min(t,n)*.075);M(e,r.topLeft,a,a,`left`,u,i,!1),M(e,r.topRight,t-a,a,`right`,u,i,!1),M(e,r.bottomLeft,a,n-a,`left`,u,i,!0),M(e,r.bottomRight,t-a,n-a,`right`,u,i,!0),te(e,c,r,c.cy+s+a*.6)}var P=1e4,F=Math.PI/180,I=.1,L=.05,R=1,z=1e3,B=new Set([`angleTrueWater`,`angleTrueGround`,`directionTrue`]),V=document.createElement(`canvas`);document.getElementById(`root`)?.append(V);var H,U=!1,W=0,G,K=!1;function q(e,t){return n=>{let r=e.values.get(n);return r&&t-r.time<P?r.value:void 0}}function J(e,t,n){return{label:e,value:t===void 0?`--`:i(t,n.conversion).toFixed(n.decimals),unit:n.symbol}}function Y(e,t){return{label:e,value:t===void 0?`--`:m(t),unit:``}}function X(e){let t=U?_:g,n={label:``,value:``,unit:``},r=H;if(!r)return{dial:`compass`,topLeft:n,topRight:n,bottomLeft:n,bottomRight:n,palette:t};let a=r.config,s=q(r,e),c=s(`heading`),l=s(`angleApparent`),d=u(s,a.trueWind),f=s(`speedOverGround`),p=s(`drift`),m=o(s(`set`),c),v=a.showShift&&c!==void 0?r.shift.range(e):void 0;return{dial:a.dial,heading:c,apparent:l,trueWind:d,shift:v&&c!==void 0?{from:v.from-c,to:v.to-c}:void 0,closeHauled:a.showTackLines?a.closeHauled*F:void 0,waypoint:a.showWaypoint?o(s(`waypoint`),c):void 0,course:a.showCourse&&f!==void 0&&f>=I?o(s(`course`),c):void 0,current:a.showCurrent&&m!==void 0&&p!==void 0&&p>=L?{angle:m,fraction:p/R,text:`${i(p,r.speed.conversion).toFixed(1)} ${r.speed.symbol}`}:void 0,rudder:a.showRudder?s(`rudder`):void 0,topLeft:J(`AWS`,s(`speedApparent`),r.speed),topRight:J(`TWS`,h(s,a.trueWind),r.speed),bottomLeft:Y(`AWA`,l),bottomRight:Y(`TWA`,d),palette:t}}function Z(){K=!1;let e=V.getBoundingClientRect(),t=window.devicePixelRatio||1,n=Math.max(1,Math.round(e.width*t)),r=Math.max(1,Math.round(e.height*t));(V.width!==n||V.height!==r)&&(V.width=n,V.height=r);let i=V.getContext(`2d`);i&&(i.setTransform(t,0,0,t,0,0),N(i,e.width,e.height,X(Date.now())))}function Q(){K||(K=!0,requestAnimationFrame(Z))}function ne(e,t){let n=q(e,t),r=n(`heading`),i=u(n,e.config.trueWind);r!==void 0&&i!==void 0&&e.shift.add(t,c(r+i))}async function $(e){let t=++W,n=f(await e.state.get()),i=s(n.north),o=await r(i.speedApparent);if(t!==W)return;let c={config:n,values:new Map,shift:new d(n.shiftSeconds*1e3),clock:new p,speed:a({...o,units:o?.units??`m/s`},void 0,1)},l=new Map;for(let[e,t]of Object.entries(i))l.set(t,[...l.get(t)??[],e]);let u=await e.signalk.subscribe([...l.keys()],e=>{let t=l.get(e.path);if(!t||typeof e.value!=`number`||!Number.isFinite(e.value))return;let n=Date.now(),r=c.clock.local(n,e.timestamp);for(let n of t)c.values.set(n,{time:r,value:e.value});t.some(e=>B.has(e))&&ne(c,n),Q()});if(t!==W){await u().catch(()=>void 0);return}let m=G;G=u,H=c,Q(),await m?.().catch(()=>void 0)}async function re(){let r=await e();t(r),new ResizeObserver(Q).observe(V),setInterval(Q,z),await r.subscribe([`state.changed`],()=>{$(r).catch(e=>{console.warn(`wind steering: settings reload failed`,e)})}),await n(r,e=>{U=e,Q()}).catch(e=>{console.warn(`wind steering: night mode unavailable`,e)}),await $(r)}re().catch(e=>{console.warn(`wind steering: host connection failed`,e)});
@@ -0,0 +1,28 @@
1
+ import{t as e}from"./chunk-U7YORXZ7-BLTBSYrv.js";import{o as t,t as n}from"./steer-model-DA5-9SMr.js";/* empty css */var r=[{key:`showTackLines`,label:`Close-hauled lines for both tacks`},{key:`showShift`,label:`Wind shift fan`},{key:`showWaypoint`,label:`Next waypoint`},{key:`showCourse`,label:`Course over ground`},{key:`showCurrent`,label:`Current (set and drift)`},{key:`showRudder`,label:`Rudder angle`}];function i(e){let t=document.getElementById(e);if(!t)throw Error(`missing #${e}`);return t}function a(e){let t=i(e);if(t instanceof HTMLInputElement||t instanceof HTMLSelectElement)return t;throw Error(`#${e} is not a form field`)}function o(e){let t=i(e);if(t instanceof HTMLInputElement)return t;throw Error(`#${e} is not a checkbox`)}function s(e,t,n){return`<select id="${e}">${t.map(([e,t])=>`<option value="${e}"${e===n?` selected`:``}>${t}</option>`).join(``)}</select>`}function c(e){return`
2
+ <label class="row"><span>Dial</span>
3
+ ${s(`dial`,[[`compass`,`Compass card, heading up`],[`wind`,`Wind angles, bow up`]],e.dial)}
4
+ </label>
5
+ <label class="row"><span>North</span>
6
+ ${s(`north`,[[`true`,`True`],[`magnetic`,`Magnetic`]],e.north)}
7
+ </label>
8
+ <label class="row"><span>True wind</span>
9
+ ${s(`trueWind`,[[`water`,`Through water`],[`ground`,`Over ground`]],e.trueWind)}
10
+ </label>
11
+ <label class="row"><span>Close-hauled</span>
12
+ <input id="closeHauled" type="number" min="${n.min}" max="${n.max}" step="1" value="${e.closeHauled}">
13
+ </label>
14
+ <label class="row"><span>Shift window</span>
15
+ <select id="shiftSeconds">
16
+ ${[30,60,120,300,600,1800].map(t=>`<option value="${t}"${t===e.shiftSeconds?` selected`:``}>${t<60?`${t} s`:`${t/60} min`}</option>`).join(``)}
17
+ ${[30,60,120,300,600,1800].includes(e.shiftSeconds)?``:`<option value="${e.shiftSeconds}" selected>${e.shiftSeconds} s</option>`}
18
+ </select>
19
+ </label>
20
+ <div class="toggles">
21
+ ${r.map(t=>`<label class="toggle"><input type="checkbox" id="${t.key}"${e[t.key]?` checked`:``}> ${t.label}</label>`).join(``)}
22
+ </div>
23
+ <p class="status">Close-hauled is the angle off the true wind you sail upwind, ${n.min} to ${n.max} degrees. Wind speeds use the unit set for apparent wind speed in the server's Unit Preferences.</p>
24
+ <p class="status" id="status"></p>
25
+ <div class="actions">
26
+ <button type="button" id="cancel">Cancel</button>
27
+ <button type="button" id="save" class="primary">Save</button>
28
+ </div>`}function l(){let e={dial:a(`dial`).value,north:a(`north`).value,trueWind:a(`trueWind`).value,closeHauled:Number(a(`closeHauled`).value),shiftSeconds:Number(a(`shiftSeconds`).value)};for(let t of r)e[t.key]=o(t.key).checked;return{...t(e)}}async function u(){let n=i(`root`),r=await e();n.innerHTML=`<p class="status">Loading…</p>`,n.innerHTML=c(t(await r.state.get()));let a=i(`status`);i(`save`).addEventListener(`click`,()=>{r.state.set(l()).then(()=>r.call(`ui.closePanel`)).catch(e=>{a.textContent=`Save failed: ${e instanceof Error?e.message:String(e)}`})}),i(`cancel`).addEventListener(`click`,()=>{r.call(`ui.closePanel`).catch(()=>void 0)})}u().catch(e=>{let t=document.getElementById(`root`);t&&(t.textContent=`Host connection failed: ${e instanceof Error?e.message:String(e)}`)});
@@ -0,0 +1 @@
1
+ var e={dial:`compass`,north:`true`,trueWind:`water`,closeHauled:45,shiftSeconds:60,showTackLines:!0,showShift:!0,showWaypoint:!0,showCourse:!0,showCurrent:!0,showRudder:!0},t={min:20,max:80},n={min:10,max:1800};function r(e,t,n){return Math.min(n,Math.max(t,e))}function i(i){let a=e,o=(e,t)=>typeof e==`number`&&Number.isFinite(e)?e:t,s=(e,t)=>typeof e==`boolean`?e:t;return{dial:i.dial===`wind`?`wind`:`compass`,north:i.north===`magnetic`?`magnetic`:`true`,trueWind:i.trueWind===`ground`?`ground`:`water`,closeHauled:r(Math.round(o(i.closeHauled,a.closeHauled)),t.min,t.max),shiftSeconds:r(Math.round(o(i.shiftSeconds,a.shiftSeconds)),n.min,n.max),showTackLines:s(i.showTackLines,a.showTackLines),showShift:s(i.showShift,a.showShift),showWaypoint:s(i.showWaypoint,a.showWaypoint),showCourse:s(i.showCourse,a.showCourse),showCurrent:s(i.showCurrent,a.showCurrent),showRudder:s(i.showRudder,a.showRudder)}}var a=2*Math.PI;function o(e){return((e+Math.PI)%a+a)%a-Math.PI}function s(e){return(e%a+a)%a}function c(e){let t=e===`magnetic`;return{heading:t?`navigation.headingMagnetic`:`navigation.headingTrue`,course:t?`navigation.courseOverGroundMagnetic`:`navigation.courseOverGroundTrue`,waypoint:t?`navigation.course.calcValues.bearingMagnetic`:`navigation.course.calcValues.bearingTrue`,set:t?`environment.current.setMagnetic`:`environment.current.setTrue`,headingTrue:`navigation.headingTrue`,speedOverGround:`navigation.speedOverGround`,drift:`environment.current.drift`,rudder:`steering.rudderAngle`,angleApparent:`environment.wind.angleApparent`,speedApparent:`environment.wind.speedApparent`,angleTrueWater:`environment.wind.angleTrueWater`,angleTrueGround:`environment.wind.angleTrueGround`,directionTrue:`environment.wind.directionTrue`,speedTrue:`environment.wind.speedTrue`,speedOverGroundWind:`environment.wind.speedOverGround`}}function l(e,t){if(t===`water`)return e(`angleTrueWater`);let n=e(`angleTrueGround`);if(n!==void 0)return n;let r=e(`directionTrue`),i=e(`headingTrue`);return r===void 0||i===void 0?void 0:o(r-i)}function u(e,t){return t===`ground`?e(`speedOverGroundWind`)??e(`speedTrue`):e(`speedTrue`)}function d(e,t){return e===void 0||t===void 0?void 0:o(e-t)}var f=class{windowMs;samples=[];constructor(e){this.windowMs=e}add(e,t){this.samples.push({time:e,direction:t}),this.prune(e)}clear(){this.samples=[]}prune(e){let t=e-this.windowMs,n=0;for(;n<this.samples.length&&(this.samples[n]?.time??0)<t;)n++;n>0&&this.samples.splice(0,n)}range(e){this.prune(e);let t=this.samples.at(-1);if(!t)return;let n=0,r=0;for(let e of this.samples){let i=o(e.direction-t.direction);i<n&&(n=i),i>r&&(r=i)}return{from:s(t.direction+n),to:s(t.direction+r)}}};function p(e){let t=Math.round(s(e)*180/Math.PI)%360;return String(t).padStart(3,`0`)}function m(e){let t=Math.round(Math.abs(o(e))*180/Math.PI);return t===0||t===180?`${t}°`:`${t}° ${o(e)<0?`P`:`S`}`}var h=6e4,g=class{gaps=[];offset;carried;local(e,t){let n=t===void 0?NaN:Date.parse(t);if(!Number.isFinite(n))return e;let r=e-n,i=Math.floor(e/1e3),a=i-h/1e3;for(;(this.gaps[0]?.second??1/0)<=a;)this.gaps.shift();this.gaps.length===0&&this.offset!==void 0&&(this.carried={second:i,offset:this.offset});let o=this.gaps.at(-1);o?.second===i?o.gap=Math.min(o.gap,r):this.gaps.push({second:i,gap:r});let s=r;for(let e of this.gaps)s=Math.min(s,e.gap);return this.offset=s,this.carried?.second===i&&(s=Math.min(s,this.carried.offset)),n+s}};export{d as a,c,s as d,p as i,l,f as n,i as o,g as r,m as s,t,u};
@@ -0,0 +1 @@
1
+ function e(e){let t,n=()=>{t&&clearTimeout(t),t=void 0};window.addEventListener(`pointerdown`,()=>{n(),t=setTimeout(()=>{e.call(`ui.openConfigPanel`).catch(()=>void 0)},1500)});for(let e of[`pointerup`,`pointercancel`,`pointerleave`])window.addEventListener(e,n)}async function t(e,t){e.hasCapability(`nightMode`)&&(await e.subscribe([`nightMode.changed`],(e,n)=>{t(n?.enabled===!0)}),t((await e.nightMode.get()).enabled))}export{e as n,t};
@@ -0,0 +1 @@
1
+ function e(e){return typeof e==`object`&&!!e}async function t(t){try{let n=await fetch(`/signalk/v1/api/vessels/self/${t.replace(/\./g,`/`)}/meta`,{credentials:`include`});if(!n.ok)return;let r=await n.json();if(!e(r))return;let i={};typeof r.units==`string`&&(i.units=r.units);let a=r.displayUnits;return e(a)&&(i.displayUnits={},typeof a.targetUnit==`string`&&(i.displayUnits.targetUnit=a.targetUnit),typeof a.displayFormat==`string`&&(i.displayUnits.displayFormat=a.displayFormat)),i}catch{return}}function n(t,r=``,i=[]){if(!e(t))return i;if(`value`in t){if(typeof t.value==`number`){let n=t.meta;i.push({path:r,units:e(n)&&typeof n.units==`string`?n.units:void 0})}return i}for(let[e,a]of Object.entries(t))e!==`meta`&&e!==`$source`&&e!==`timestamp`&&e!==`values`&&n(a,r?`${r}.${e}`:e,i);return i}async function r(){try{let e=await fetch(`/signalk/v1/api/vessels/self`,{credentials:`include`});return e.ok?n(await e.json()).sort((e,t)=>e.path.localeCompare(t.path)):[]}catch{return[]}}async function i(){try{let t=await fetch(`/signalk/v2/api/history/_providers`,{credentials:`include`});if(!t.ok)return[];let n=await t.json();return e(n)?Object.keys(n):[]}catch{return[]}}var a=(e,t,n,r=0,i=0)=>({id:e,symbol:t,factor:n,offset:r,add:i}),o={rad:[a(`degree`,`°`,180/Math.PI),a(`radian`,`rad`,1)],"m/s":[a(`kn`,`kn`,1.94384),a(`m/s`,`m/s`,1),a(`km/h`,`km/h`,3.6),a(`mph`,`mph`,2.2369362920544025)],K:[a(`C`,`°C`,1,-273.15),a(`F`,`°F`,9/5,-273.15,32),a(`K`,`K`,1)],Pa:[a(`hPa`,`hPa`,.01),a(`mbar`,`mbar`,.01),a(`bar`,`bar`,1e-5),a(`psi`,`psi`,.0001450376807894691),a(`inHg`,`inHg`,.00029529987601298443),a(`mmHg`,`mmHg`,.0075006168507298),a(`Pa`,`Pa`,1)],m:[a(`m`,`m`,1),a(`meter`,`m`,1),a(`foot`,`ft`,3.280839895013124),a(`fathom`,`fathom`,.5467468562055768),a(`naut-mile`,`nmi`,.0005399568034557236),a(`kilometer`,`km`,.001),a(`mile`,`mi`,.000621371192237334)],"rad/s":[a(`deg/s`,`°/s`,57.29577951308232),a(`rpm`,`rpm`,9.549296585513723),a(`rad/s`,`rad/s`,1)],ratio:[a(`percent`,`%`,100),a(`ratio`,``,1)],Hz:[a(`rpm`,`rpm`,60),a(`hertz`,`Hz`,1)],V:[a(`V`,`V`,1)],A:[a(`A`,`A`,1)],W:[a(`watt`,`W`,1),a(`kW`,`kW`,.001)],m3:[a(`liter`,`l`,1e3),a(`gallon`,`gal`,264.1720512415585),a(`m3`,`m³`,1)],"m3/s":[a(`L/h`,`l/h`,36e5),a(`L/min`,`l/min`,6e4),a(`gal/h`,`gal/h`,264.17205236)]};function s(e,t){return t?(e+(t.offset??0))*t.factor+(t.add??0):e}function c(e){if(e)return/\.(0+)/.exec(e)?.[1]?.length??0}function l(e,t,n){let r=e?.units,i=r?o[r]??[]:[],a=e=>i.find(t=>t.id===e),s=a(t)??a(e?.displayUnits?.targetUnit)??i[0],l=r===`rad`,u=s&&s.id===e?.displayUnits?.targetUnit?c(e.displayUnits.displayFormat):void 0,d=s?.id===`degree`?0:u??1;return{conversion:s,symbol:s?.symbol??r??``,decimals:n>=0?n:d,angular:l}}export{t as a,i,s as n,r as o,l as r,o as t};
@@ -0,0 +1 @@
1
+ import{t as e}from"./chunk-U7YORXZ7-BLTBSYrv.js";import{a as t,i as n,n as r,r as i}from"./config-model-DvTH1O2f.js";import{n as a,t as o}from"./styles-CjtPzFRR.js";import{a as s,n as c,r as l}from"./units-zSVjGfwB.js";var u=`/signalk/v2/api/history/values`;function d(e,t,n,r){return`${u}?${new URLSearchParams({paths:(r?[`min`,`max`,`average`]:[`average`]).map(t=>`${e}:${t}`).join(`,`),duration:String(Math.ceil(t/1e3)),resolution:String(Math.max(1,Math.round(n/1e3)))}).toString()}`}function f(e,t){let n=n=>e.values.findIndex(e=>e.path===t&&e.method===n),r=n(`average`);if(r<0)return[];let i=n(`min`),a=n(`max`),o=[];for(let t of e.data){let e=Date.parse(t[0]),n=t[r+1];if(Number.isNaN(e)||typeof n!=`number`)continue;let s=i<0?n:t[i+1],c=a<0?n:t[a+1];o.push({time:e,avg:n,min:typeof s==`number`?s:n,max:typeof c==`number`?c:n})}return o}function p(e){if(typeof e!=`object`||!e)return!1;let t=e;return Array.isArray(t.values)&&Array.isArray(t.data)}async function m(e,t,n,r=fetch){for(let i of[!0,!1])try{let a=await r(d(e,t,n,i),{credentials:`include`});if(a.status===404||a.status===501)return[];if(!a.ok)continue;let o=await a.json();if(p(o))return f(o,e)}catch(e){return console.warn(`history query failed`,e),[]}return[]}var h={card:`rgba(20, 26, 32, 0.78)`,text:`#e8edf2`,muted:`#9fb3c4`,bar:`#29b6f6`,avg:`#ef5350`,grid:`rgba(255, 255, 255, 0.22)`},g={card:`rgba(8, 0, 0, 0.85)`,text:`#d32f2f`,muted:`#8e2424`,bar:`#7f1212`,avg:`#ff5252`,grid:`rgba(211, 47, 47, 0.3)`},_=[1,2,5,10,15,20,30,60,120,180,240,360,720,1440],v=4;function y(e){let t=e/6e4,n=_.find(e=>t/e<=v)??Math.ceil(t/v/1440)*1440,r=t>120,i=[];for(let e=n;e<=t+1e-9;e+=n)i.push({age:e*6e4,label:r?`${+(e/60).toFixed(1)}h`:`${e}`});return i}function b(e,t){let n=e.min,r=e.max;if(r-n<t){let e=(r+n)/2;n=e-t/2,r=e+t/2}let i=(r-n)*.05;return{lo:n-i,hi:r+i}}function x(e,t,n,r,i=``){let a=r;for(e.font=`${i} ${a}px 'Helvetica Neue', Arial, sans-serif`;a>7&&e.measureText(t).width>n;)a--,e.font=`${i} ${a}px 'Helvetica Neue', Arial, sans-serif`;return a}function S(e,t,n,r){let i=r.palette,a=Math.max(4,Math.round(t*.05));e.clearRect(0,0,t,n),e.fillStyle=i.card,e.beginPath(),e.roundRect(0,0,t,n,10),e.fill(),e.textBaseline=`alphabetic`;let o=t-2*a,s=Math.max(8,Math.round(Math.min(t/9,n/18)));e.fillStyle=i.muted,e.font=`600 ${s}px 'Helvetica Neue', Arial, sans-serif`,e.textAlign=`left`;let c=a+s;e.fillText(r.label,a,c,o*.7),e.textAlign=`right`,e.fillText(r.symbol,t-a,c,o*.3);let l=x(e,r.value,o,Math.round(Math.min(t/3.2,n/7)),`600`);e.fillStyle=i.text,e.textAlign=`center`,c+=l+2,e.fillText(r.value,t/2,c);let u=x(e,`${r.min} ${r.avg} ${r.max}`,o,s);e.font=`${u}px 'Helvetica Neue', Arial, sans-serif`,c+=u+4,e.textAlign=`left`,e.fillStyle=i.muted,e.fillText(r.min,a,c),e.textAlign=`right`,e.fillText(r.max,t-a,c),e.textAlign=`center`,e.fillStyle=i.avg,e.fillText(r.avg,t/2,c);let d=Math.max(7,s-2);e.font=`${d}px 'Helvetica Neue', Arial, sans-serif`;let f=y(r.windowMs),p=Math.ceil(Math.max(...f.map(t=>e.measureText(t.label).width),0))+3,m=c+4,h=a,g=t-a-p,_=n-a,v=_-m,S=g-h;if(v<10||S<10)return;e.strokeStyle=i.grid,e.lineWidth=1,e.strokeRect(h+.5,m+.5,S-1,v-1);let C=e=>m+e/r.windowMs*v;e.fillStyle=i.muted,e.textAlign=`left`,e.textBaseline=`middle`;for(let t of f){let n=Math.round(C(t.age))+.5;t.age<r.windowMs&&(e.beginPath(),e.moveTo(h,n),e.lineTo(g,n),e.stroke()),e.fillText(t.label,g+3,Math.min(n,_-d/2))}if(!r.stats){r.message&&(e.textAlign=`center`,e.fillStyle=i.muted,e.fillText(r.message,h+S/2,m+v/2,S-4));return}let w=b(r.stats,r.minSpan),T=e=>h+(e-w.lo)/(w.hi-w.lo)*S;e.fillStyle=i.bar;for(let t of r.stats.bands){let n=C(t.ageStart),r=Math.min(_,C(t.ageEnd));if(r<=m)continue;let i=T(t.lo),a=Math.max(i+2,T(t.hi)),o=Math.max(m,n);e.fillRect(i,o,a-i,Math.max(1,r-o)+.5)}let E=Math.round(T(r.stats.avg))+.5;e.strokeStyle=i.avg,e.beginPath(),e.moveTo(E,m),e.lineTo(E,_),e.stroke()}var C=2*Math.PI;function w(e){let t=((e+Math.PI)%C+C)%C;return t===0?Math.PI:t-Math.PI}var T=class{windowMs;bucketMs;angular;buckets=new Map;constructor(e,t,n=240){this.windowMs=e,this.bucketMs=e/n,this.angular=t}get size(){return this.buckets.size}add(e,t){this.merge(e,t,t,t)}merge(e,t,n,r){if(![e,t,n,r].every(Number.isFinite))return;let i=Math.floor(e/this.bucketMs),a=this.buckets.get(i);if(a||(a={index:i,ref:this.angular?r:0,lo:1/0,hi:-1/0,sum:0,sin:0,cos:0,n:0},this.buckets.set(i,a)),this.angular){let e=w(r-a.ref),i=w(t-r),o=w(n-r);a.lo=Math.min(a.lo,e+Math.min(0,i,o)),a.hi=Math.max(a.hi,e+Math.max(0,i,o)),a.sin+=Math.sin(r),a.cos+=Math.cos(r)}else a.lo=Math.min(a.lo,t,r),a.hi=Math.max(a.hi,n,r),a.sum+=r;a.n++}prune(e){let t=Math.floor((e-this.windowMs)/this.bucketMs);for(let e of this.buckets.keys())e<t&&this.buckets.delete(e)}clear(){this.buckets.clear()}stats(e){this.prune(e);let t=[...this.buckets.values()].filter(e=>e.n>0);if(t.length===0)return;let n=0;if(this.angular){let e=0,r=0;for(let n of t){let t=Math.atan2(n.sin,n.cos);e+=Math.sin(t),r+=Math.cos(t)}n=Math.atan2(e,r)}let r=1/0,i=-1/0,a=0,o=[];for(let s of t){let t=this.angular?w(s.ref-n):0,c=s.lo+t,l=s.hi+t;r=Math.min(r,c),i=Math.max(i,l),this.angular||(a+=s.sum/s.n);let u=s.index*this.bucketMs;o.push({ageStart:Math.max(0,e-(u+this.bucketMs)),ageEnd:Math.max(0,e-u),lo:c,hi:l})}return{centre:n,min:r,max:i,avg:this.angular?0:a/t.length,bands:o}}},E=6e4,D=2*Math.PI,O=2,k=document.createElement(`canvas`);document.getElementById(`root`)?.append(k);var A,j=!1,M=0,N,P,F=!1;function I(e,t){if(e===void 0||!Number.isFinite(e))return`--`;let n=e;t.display.angular&&(n=t.signed?w(e):(e%D+D)%D);let r=c(n,t.display.conversion).toFixed(t.display.decimals);return t.display.angular&&!t.signed&&Number(r)>=360?0 .toFixed(t.display.decimals):r}function L(e){let t=j?g:h,n=A;if(!n)return{label:``,value:`--`,symbol:``,min:``,avg:``,max:``,windowMs:1,stats:void 0,minSpan:1,palette:t};let r=n.series.stats(e),a=e=>e===void 0?void 0:(r?.centre??0)+e,o=n.last&&e-n.last.time<E?n.last.value:void 0,s=n.display.conversion?1/Math.abs(n.display.conversion.factor):1;return{label:i(n.config),value:I(o,n),symbol:n.display.symbol,min:r?I(a(r.min),n):``,avg:r?I(a(r.avg),n):``,max:r?I(a(r.max),n):``,windowMs:n.series.windowMs,stats:r,minSpan:O*s,message:n.loading?`Loading…`:`No data`,palette:t}}function R(){F=!1;let e=k.getBoundingClientRect(),t=window.devicePixelRatio||1,n=Math.max(1,Math.round(e.width*t)),r=Math.max(1,Math.round(e.height*t));(k.width!==n||k.height!==r)&&(k.width=n,k.height=r);let i=k.getContext(`2d`);i&&(i.setTransform(t,0,0,t,0,0),S(i,e.width,e.height,L(Date.now())))}function z(){F||(F=!0,requestAnimationFrame(R))}async function B(e,t){let n=await m(e.config.path,e.series.windowMs,e.series.bucketMs);if(t===M){if(n.length>0){e.series.clear();for(let t of n)e.series.merge(t.time,t.min,t.max,t.avg);e.last&&e.series.add(e.last.time,e.last.value)}e.loading=!1,z()}}async function V(e){let i=++M;if(N){let e=N;N=void 0,await e().catch(()=>void 0)}P&&clearInterval(P);let a=n(await e.state.get()),o=await s(a.path);if(i!==M)return;let c=l(o,a.unit||void 0,a.decimals),u={config:a,display:c,series:new T(t(a),c.angular),signed:r(a.path),loading:!0};A=u,z(),N=await e.signalk.subscribe([a.path],e=>{if(typeof e.value!=`number`||!Number.isFinite(e.value))return;let t=Date.now();u.last={time:t,value:e.value},u.series.add(t,e.value),z()}),P=setInterval(z,Math.max(1e3,u.series.bucketMs)),await B(u,i)}async function H(){let t=await e();a(t),new ResizeObserver(z).observe(k),await t.subscribe([`state.changed`],()=>{V(t).catch(e=>{console.warn(`history strip: settings reload failed`,e)})}),document.addEventListener(`visibilitychange`,()=>{document.visibilityState===`visible`&&A&&B(A,M)}),await o(t,e=>{j=e,z()}).catch(e=>{console.warn(`history strip: night mode unavailable`,e)}),await V(t)}H().catch(e=>{console.warn(`history strip: host connection failed`,e)});
@@ -4,9 +4,11 @@
4
4
  <meta charset="utf-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1" />
6
6
  <title>History strip setup</title>
7
- <script type="module" crossorigin src="./assets/config-CzQzAyc1.js"></script>
8
- <link rel="modulepreload" crossorigin href="./assets/units-D0PJ17NW.js">
9
- <link rel="stylesheet" crossorigin href="./assets/config-CVQm5UWB.css">
7
+ <script type="module" crossorigin src="./assets/config-BKRpYFX2.js"></script>
8
+ <link rel="modulepreload" crossorigin href="./assets/chunk-U7YORXZ7-BLTBSYrv.js">
9
+ <link rel="modulepreload" crossorigin href="./assets/config-model-DvTH1O2f.js">
10
+ <link rel="modulepreload" crossorigin href="./assets/units-zSVjGfwB.js">
11
+ <link rel="stylesheet" crossorigin href="./assets/config-BRhovzXK.css">
10
12
  </head>
11
13
  <body>
12
14
  <div id="root"></div>
@@ -0,0 +1,15 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>Wind steering setup</title>
7
+ <script type="module" crossorigin src="./assets/steer-config-VgFoNl7x.js"></script>
8
+ <link rel="modulepreload" crossorigin href="./assets/chunk-U7YORXZ7-BLTBSYrv.js">
9
+ <link rel="modulepreload" crossorigin href="./assets/steer-model-DA5-9SMr.js">
10
+ <link rel="stylesheet" crossorigin href="./assets/config-BRhovzXK.css">
11
+ </head>
12
+ <body>
13
+ <div id="root"></div>
14
+ </body>
15
+ </html>
@@ -0,0 +1,17 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>Wind steering</title>
7
+ <script type="module" crossorigin src="./assets/steer-CY6FnXbG.js"></script>
8
+ <link rel="modulepreload" crossorigin href="./assets/chunk-U7YORXZ7-BLTBSYrv.js">
9
+ <link rel="modulepreload" crossorigin href="./assets/styles-CjtPzFRR.js">
10
+ <link rel="modulepreload" crossorigin href="./assets/units-zSVjGfwB.js">
11
+ <link rel="modulepreload" crossorigin href="./assets/steer-model-DA5-9SMr.js">
12
+ <link rel="stylesheet" crossorigin href="./assets/styles-D6wkrAFq.css">
13
+ </head>
14
+ <body>
15
+ <div id="root"></div>
16
+ </body>
17
+ </html>
@@ -4,9 +4,12 @@
4
4
  <meta charset="utf-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1" />
6
6
  <title>History strip</title>
7
- <script type="module" crossorigin src="./assets/widget-BPqCcSdj.js"></script>
8
- <link rel="modulepreload" crossorigin href="./assets/units-D0PJ17NW.js">
9
- <link rel="stylesheet" crossorigin href="./assets/widget-D6wkrAFq.css">
7
+ <script type="module" crossorigin src="./assets/widget-DjBm6Z7F.js"></script>
8
+ <link rel="modulepreload" crossorigin href="./assets/chunk-U7YORXZ7-BLTBSYrv.js">
9
+ <link rel="modulepreload" crossorigin href="./assets/config-model-DvTH1O2f.js">
10
+ <link rel="modulepreload" crossorigin href="./assets/styles-CjtPzFRR.js">
11
+ <link rel="modulepreload" crossorigin href="./assets/units-zSVjGfwB.js">
12
+ <link rel="stylesheet" crossorigin href="./assets/styles-D6wkrAFq.css">
10
13
  </head>
11
14
  <body>
12
15
  <div id="root"></div>
@@ -1 +0,0 @@
1
- import{a as e,c as t,d as n,f as r,l as i,n as a,r as o,u as s}from"./units-D0PJ17NW.js";var c=`/signalk/v2/api/history/values`;function l(e,t,n,r){return`${c}?${new URLSearchParams({paths:(r?[`min`,`max`,`average`]:[`average`]).map(t=>`${e}:${t}`).join(`,`),duration:String(Math.ceil(t/1e3)),resolution:String(Math.max(1,Math.round(n/1e3)))}).toString()}`}function u(e,t){let n=n=>e.values.findIndex(e=>e.path===t&&e.method===n),r=n(`average`);if(r<0)return[];let i=n(`min`),a=n(`max`),o=[];for(let t of e.data){let e=Date.parse(t[0]),n=t[r+1];if(Number.isNaN(e)||typeof n!=`number`)continue;let s=i<0?n:t[i+1],c=a<0?n:t[a+1];o.push({time:e,avg:n,min:typeof s==`number`?s:n,max:typeof c==`number`?c:n})}return o}function d(e){if(typeof e!=`object`||!e)return!1;let t=e;return Array.isArray(t.values)&&Array.isArray(t.data)}async function f(e,t,n,r=fetch){for(let i of[!0,!1])try{let a=await r(l(e,t,n,i),{credentials:`include`});if(a.status===404||a.status===501)return[];if(!a.ok)continue;let o=await a.json();if(d(o))return u(o,e)}catch(e){return console.warn(`history query failed`,e),[]}return[]}var p={card:`rgba(20, 26, 32, 0.78)`,text:`#e8edf2`,muted:`#9fb3c4`,bar:`#29b6f6`,avg:`#ef5350`,grid:`rgba(255, 255, 255, 0.22)`},m={card:`rgba(8, 0, 0, 0.85)`,text:`#d32f2f`,muted:`#8e2424`,bar:`#7f1212`,avg:`#ff5252`,grid:`rgba(211, 47, 47, 0.3)`},h=[1,2,5,10,15,20,30,60,120,180,240,360,720,1440],g=4;function _(e){let t=e/6e4,n=h.find(e=>t/e<=g)??Math.ceil(t/g/1440)*1440,r=t>120,i=[];for(let e=n;e<=t+1e-9;e+=n)i.push({age:e*6e4,label:r?`${+(e/60).toFixed(1)}h`:`${e}`});return i}function v(e,t){let n=e.min,r=e.max;if(r-n<t){let e=(r+n)/2;n=e-t/2,r=e+t/2}let i=(r-n)*.05;return{lo:n-i,hi:r+i}}function y(e,t,n,r,i=``){let a=r;for(e.font=`${i} ${a}px 'Helvetica Neue', Arial, sans-serif`;a>7&&e.measureText(t).width>n;)a--,e.font=`${i} ${a}px 'Helvetica Neue', Arial, sans-serif`;return a}function b(e,t,n,r){let i=r.palette,a=Math.max(4,Math.round(t*.05));e.clearRect(0,0,t,n),e.fillStyle=i.card,e.beginPath(),e.roundRect(0,0,t,n,10),e.fill(),e.textBaseline=`alphabetic`;let o=t-2*a,s=Math.max(8,Math.round(Math.min(t/9,n/18)));e.fillStyle=i.muted,e.font=`600 ${s}px 'Helvetica Neue', Arial, sans-serif`,e.textAlign=`left`;let c=a+s;e.fillText(r.label,a,c,o*.7),e.textAlign=`right`,e.fillText(r.symbol,t-a,c,o*.3);let l=y(e,r.value,o,Math.round(Math.min(t/3.2,n/7)),`600`);e.fillStyle=i.text,e.textAlign=`center`,c+=l+2,e.fillText(r.value,t/2,c);let u=y(e,`${r.min} ${r.avg} ${r.max}`,o,s);e.font=`${u}px 'Helvetica Neue', Arial, sans-serif`,c+=u+4,e.textAlign=`left`,e.fillStyle=i.muted,e.fillText(r.min,a,c),e.textAlign=`right`,e.fillText(r.max,t-a,c),e.textAlign=`center`,e.fillStyle=i.avg,e.fillText(r.avg,t/2,c);let d=Math.max(7,s-2);e.font=`${d}px 'Helvetica Neue', Arial, sans-serif`;let f=_(r.windowMs),p=Math.ceil(Math.max(...f.map(t=>e.measureText(t.label).width),0))+3,m=c+4,h=a,g=t-a-p,b=n-a,x=b-m,S=g-h;if(x<10||S<10)return;e.strokeStyle=i.grid,e.lineWidth=1,e.strokeRect(h+.5,m+.5,S-1,x-1);let C=e=>m+e/r.windowMs*x;e.fillStyle=i.muted,e.textAlign=`left`,e.textBaseline=`middle`;for(let t of f){let n=Math.round(C(t.age))+.5;t.age<r.windowMs&&(e.beginPath(),e.moveTo(h,n),e.lineTo(g,n),e.stroke()),e.fillText(t.label,g+3,Math.min(n,b-d/2))}if(!r.stats){r.message&&(e.textAlign=`center`,e.fillStyle=i.muted,e.fillText(r.message,h+S/2,m+x/2,S-4));return}let w=v(r.stats,r.minSpan),T=e=>h+(e-w.lo)/(w.hi-w.lo)*S;e.fillStyle=i.bar;for(let t of r.stats.bands){let n=C(t.ageStart),r=Math.min(b,C(t.ageEnd));if(r<=m)continue;let i=T(t.lo),a=Math.max(i+2,T(t.hi)),o=Math.max(m,n);e.fillRect(i,o,a-i,Math.max(1,r-o)+.5)}let E=Math.round(T(r.stats.avg))+.5;e.strokeStyle=i.avg,e.beginPath(),e.moveTo(E,m),e.lineTo(E,b),e.stroke()}var x=2*Math.PI;function S(e){let t=((e+Math.PI)%x+x)%x;return t===0?Math.PI:t-Math.PI}var C=class{windowMs;bucketMs;angular;buckets=new Map;constructor(e,t,n=240){this.windowMs=e,this.bucketMs=e/n,this.angular=t}get size(){return this.buckets.size}add(e,t){this.merge(e,t,t,t)}merge(e,t,n,r){if(![e,t,n,r].every(Number.isFinite))return;let i=Math.floor(e/this.bucketMs),a=this.buckets.get(i);if(a||(a={index:i,ref:this.angular?r:0,lo:1/0,hi:-1/0,sum:0,sin:0,cos:0,n:0},this.buckets.set(i,a)),this.angular){let e=S(r-a.ref),i=S(t-r),o=S(n-r);a.lo=Math.min(a.lo,e+Math.min(0,i,o)),a.hi=Math.max(a.hi,e+Math.max(0,i,o)),a.sin+=Math.sin(r),a.cos+=Math.cos(r)}else a.lo=Math.min(a.lo,t,r),a.hi=Math.max(a.hi,n,r),a.sum+=r;a.n++}prune(e){let t=Math.floor((e-this.windowMs)/this.bucketMs);for(let e of this.buckets.keys())e<t&&this.buckets.delete(e)}clear(){this.buckets.clear()}stats(e){this.prune(e);let t=[...this.buckets.values()].filter(e=>e.n>0);if(t.length===0)return;let n=0;if(this.angular){let e=0,r=0;for(let n of t){let t=Math.atan2(n.sin,n.cos);e+=Math.sin(t),r+=Math.cos(t)}n=Math.atan2(e,r)}let r=1/0,i=-1/0,a=0,o=[];for(let s of t){let t=this.angular?S(s.ref-n):0,c=s.lo+t,l=s.hi+t;r=Math.min(r,c),i=Math.max(i,l),this.angular||(a+=s.sum/s.n);let u=s.index*this.bucketMs;o.push({ageStart:Math.max(0,e-(u+this.bucketMs)),ageEnd:Math.max(0,e-u),lo:c,hi:l})}return{centre:n,min:r,max:i,avg:this.angular?0:a/t.length,bands:o}}},w=1500,T=6e4,E=2*Math.PI,D=2,O=document.createElement(`canvas`);document.getElementById(`root`)?.append(O);var k,A=!1,j=0,M,N,P=!1;function F(e,t){if(e===void 0||!Number.isFinite(e))return`--`;let n=e;t.display.angular&&(n=t.signed?S(e):(e%E+E)%E);let r=a(n,t.display.conversion).toFixed(t.display.decimals);return t.display.angular&&!t.signed&&Number(r)>=360?0 .toFixed(t.display.decimals):r}function I(e){let t=A?m:p,n=k;if(!n)return{label:``,value:`--`,symbol:``,min:``,avg:``,max:``,windowMs:1,stats:void 0,minSpan:1,palette:t};let r=n.series.stats(e),a=e=>e===void 0?void 0:(r?.centre??0)+e,o=n.last&&e-n.last.time<T?n.last.value:void 0,s=n.display.conversion?1/Math.abs(n.display.conversion.factor):1;return{label:i(n.config),value:F(o,n),symbol:n.display.symbol,min:r?F(a(r.min),n):``,avg:r?F(a(r.avg),n):``,max:r?F(a(r.max),n):``,windowMs:n.series.windowMs,stats:r,minSpan:D*s,message:n.loading?`Loading…`:`No data`,palette:t}}function L(){P=!1;let e=O.getBoundingClientRect(),t=window.devicePixelRatio||1,n=Math.max(1,Math.round(e.width*t)),r=Math.max(1,Math.round(e.height*t));(O.width!==n||O.height!==r)&&(O.width=n,O.height=r);let i=O.getContext(`2d`);i&&(i.setTransform(t,0,0,t,0,0),b(i,e.width,e.height,I(Date.now())))}function R(){P||(P=!0,requestAnimationFrame(L))}async function z(e,t){let n=await f(e.config.path,e.series.windowMs,e.series.bucketMs);if(t===j){if(n.length>0){e.series.clear();for(let t of n)e.series.merge(t.time,t.min,t.max,t.avg);e.last&&e.series.add(e.last.time,e.last.value)}e.loading=!1,R()}}async function B(r){let i=++j;if(M){let e=M;M=void 0,await e().catch(()=>void 0)}N&&clearInterval(N);let a=s(await r.state.get()),c=await e(a.path);if(i!==j)return;let l=o(c,a.unit||void 0,a.decimals),u={config:a,display:l,series:new C(n(a),l.angular),signed:t(a.path),loading:!0};k=u,R(),M=await r.signalk.subscribe([a.path],e=>{if(typeof e.value!=`number`||!Number.isFinite(e.value))return;let t=Date.now();u.last={time:t,value:e.value},u.series.add(t,e.value),R()}),N=setInterval(R,Math.max(1e3,u.series.bucketMs)),await z(u,i)}function V(e){let t,n=()=>{t&&clearTimeout(t),t=void 0};window.addEventListener(`pointerdown`,()=>{n(),t=setTimeout(()=>{e.call(`ui.openConfigPanel`).catch(()=>void 0)},w)});for(let e of[`pointerup`,`pointercancel`,`pointerleave`])window.addEventListener(e,n)}async function H(e){e.hasCapability(`nightMode`)&&(await e.subscribe([`nightMode.changed`],(e,t)=>{A=t?.enabled===!0,R()}),A=(await e.nightMode.get()).enabled,R())}async function U(){let e=await r();V(e),new ResizeObserver(R).observe(O),await e.subscribe([`state.changed`],()=>{B(e).catch(e=>{console.warn(`history strip: settings reload failed`,e)})}),document.addEventListener(`visibilitychange`,()=>{document.visibilityState===`visible`&&k&&z(k,j)}),await H(e).catch(e=>{console.warn(`history strip: night mode unavailable`,e)}),await B(e)}U().catch(e=>{console.warn(`history strip: host connection failed`,e)});