signalk-history-widgets 0.2.0 → 0.3.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,7 +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).
13
+ The package also brings a **Wind steering** dial and **Tank level** and
14
+ **Battery charge** widgets, described [below](#wind-steering).
14
15
 
15
16
  ## Use
16
17
 
@@ -74,6 +75,27 @@ hidden rather than shown stale. It reads standard Signal K paths:
74
75
  `navigation.speedOverGround`, `environment.current.drift` and
75
76
  `steering.rudderAngle`.
76
77
 
78
+ ## Tank level and battery charge
79
+
80
+ A tank's level or a battery bank's state of charge as a glass cylinder or, if
81
+ you prefer, a flat bar, with the percentage underneath. A tank that publishes
82
+ its capacity also shows the volume left, and a battery bank its voltage, in
83
+ the units set in the server's Unit Preferences. The colour follows the zones
84
+ set for the level in the server's metadata (`meta.zones`): green when the
85
+ level is normal, yellow in an alert or warn zone, red in an alarm or
86
+ emergency zone. Marks beside the bar show where the zones begin, so you see
87
+ how far the level is from the next one. Without zones the bar stays green.
88
+
89
+ ![A battery bank as a cylinder and as a flat bar, and a tank as a flat bar](docs/screenshots/levels.png)
90
+
91
+ Press and hold the widget to pick another tank or bank. The panel lists every
92
+ `tanks.*.*.currentLevel` (or every
93
+ `electrical.batteries.*.capacity.stateOfCharge`) on the server with its
94
+ current level, plus any other 0 to 100 % value, and offers the look (3D or
95
+ flat) and whether to show the volume or voltage as buttons, so it is set up
96
+ with a few taps and no keyboard. A level older than two minutes shows as
97
+ `--`.
98
+
77
99
  ## Requirements
78
100
 
79
101
  - Signal K server 2.22.1 or newer (History API v2) on Node 24 or newer
Binary file
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "signalk-history-widgets",
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",
3
+ "version": "0.3.0",
4
+ "description": "History strip, wind steering and tank level widgets for Signal K chartplotters: any value over the last minutes or hours as a vertical strip with min, average and max, a wind steering dial with close-hauled lines and the recent wind shift, and tank levels and battery charge coloured by their zones",
5
5
  "author": "Dirk Wahrheit",
6
6
  "main": "plugin/index.js",
7
7
  "type": "module",
package/plugin/index.js CHANGED
@@ -48,11 +48,27 @@ var strip = (id, title, size) => ({
48
48
  configPanel: "strip-config",
49
49
  lifecycle: "whileEnabled"
50
50
  });
51
+ var level = (id, title) => ({
52
+ id,
53
+ title,
54
+ type: "iframe",
55
+ url: `${ASSET_BASE}/${id}.html`,
56
+ size: "1x2",
57
+ configPanel: `${id}-config`,
58
+ lifecycle: "whileEnabled"
59
+ });
60
+ var levelPanel = (id, title) => ({
61
+ id: `${id}-config`,
62
+ title,
63
+ type: "iframe",
64
+ url: `${ASSET_BASE}/${id}-config.html`,
65
+ lifecycle: "onOpen"
66
+ });
51
67
  /** The Plotter Extensions manifest (apiVersion 1). */
52
68
  function buildManifest(version) {
53
69
  return {
54
70
  name: "History Widgets",
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.",
71
+ description: "Any value over the last minutes or hours as a vertical strip with its minimum, average and maximum, a wind steering dial with the wind shift of the last minutes, and tank levels and battery charge coloured by their zones.",
56
72
  version,
57
73
  apiVersion: "1",
58
74
  requires: [
@@ -72,21 +88,28 @@ function buildManifest(version) {
72
88
  size: "2x2",
73
89
  configPanel: "wind-steering-config",
74
90
  lifecycle: "whileEnabled"
75
- }
91
+ },
92
+ level("tank", "Tank level"),
93
+ level("battery", "Battery charge")
76
94
  ],
77
- panels: [{
78
- id: "strip-config",
79
- title: "History strip",
80
- type: "iframe",
81
- url: `${ASSET_BASE}/config.html`,
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"
89
- }]
95
+ panels: [
96
+ {
97
+ id: "strip-config",
98
+ title: "History strip",
99
+ type: "iframe",
100
+ url: `${ASSET_BASE}/config.html`,
101
+ lifecycle: "onOpen"
102
+ },
103
+ {
104
+ id: "wind-steering-config",
105
+ title: "Wind steering",
106
+ type: "iframe",
107
+ url: `${ASSET_BASE}/steer-config.html`,
108
+ lifecycle: "onOpen"
109
+ },
110
+ levelPanel("tank", "Tank level"),
111
+ levelPanel("battery", "Battery charge")
112
+ ]
90
113
  };
91
114
  }
92
115
  // src/index.ts
@@ -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, 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
+ {"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\nconst level = (id: string, title: string) => ({\n id,\n title,\n type: 'iframe',\n url: `${ASSET_BASE}/${id}.html`,\n size: '1x2',\n configPanel: `${id}-config`,\n lifecycle: 'whileEnabled'\n})\n\nconst levelPanel = (id: string, title: string) => ({\n id: `${id}-config`,\n title,\n type: 'iframe',\n url: `${ASSET_BASE}/${id}-config.html`,\n lifecycle: 'onOpen'\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, a wind steering dial with the wind shift of the last minutes, and tank levels and battery charge coloured by their zones.',\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 level('tank', 'Tank level'),\n level('battery', 'Battery charge')\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 levelPanel('tank', 'Tank level'),\n levelPanel('battery', 'Battery charge')\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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"}
@@ -0,0 +1 @@
1
+ import"./chunk-U7YORXZ7-BLTBSYrv.js";import{t as e}from"./level-CwEJ7gss.js";e(`battery`);
@@ -0,0 +1 @@
1
+ import"./chunk-U7YORXZ7-BLTBSYrv.js";import{t as e}from"./level-config-B0mMmED6.js";e(`battery`);
@@ -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}.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}
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}.section-title{color:var(--muted);margin:4px 0 6px;font-size:13px}.picks{gap:6px;margin-bottom:12px;display:grid}.pick{border:1px solid var(--line);min-height:48px;color:var(--fg);text-align:left;background:0 0;border-radius:8px;align-items:center;gap:10px;padding:0 14px;font-size:15px;display:flex}.pick .name{text-overflow:ellipsis;white-space:nowrap;flex:1;overflow:hidden}.pick .reading{color:var(--muted);font-size:13px}.pick.on{background:var(--accent);border-color:var(--accent);color:#fff}.pick.on .reading{color:#fff}.actions.sticky{background:var(--bg);padding:10px 0;position:sticky;bottom:0}
@@ -1,4 +1,4 @@
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`
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-Cn4uhL0-.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…">
@@ -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,s as o}from"./units-Cn4uhL0-.js";import{r as s}from"./steer-model-DA5-9SMr.js";import{a as c,i as l,n as u,o as d,r as f,t as p}from"./level-model-0XbpyS2Q.js";var m={card:`rgba(20, 26, 32, 0.78)`,text:`#e8edf2`,muted:`#9fb3c4`,glass:`rgba(255, 255, 255, 0.07)`,glassEdge:`rgba(207, 216, 220, 0.75)`,glint:`rgba(255, 255, 255, 0.28)`,liquid:{ok:{edge:`#1b5e20`,middle:`#66bb6a`,surface:`#a5d6a7`},warn:{edge:`#e65100`,middle:`#fdd835`,surface:`#fff59d`},alarm:{edge:`#7f0000`,middle:`#e53935`,surface:`#ef9a9a`}},mark:{ok:`#66bb6a`,warn:`#fdd835`,alarm:`#e53935`}},h={card:`rgba(8, 0, 0, 0.85)`,text:`#d32f2f`,muted:`#8e2424`,glass:`rgba(120, 20, 20, 0.08)`,glassEdge:`rgba(110, 24, 24, 0.9)`,glint:`rgba(160, 40, 40, 0.18)`,liquid:{ok:{edge:`#0b2a0d`,middle:`#2e5d31`,surface:`#3d7340`},warn:{edge:`#3d2600`,middle:`#7a6a10`,surface:`#8f7d1c`},alarm:{edge:`#3a0000`,middle:`#8e1b1b`,surface:`#b02a2a`}},mark:{ok:`#2e5d31`,warn:`#7a6a10`,alarm:`#b02a2a`}};function g(e,t,n,r,i,a){e.beginPath(),e.moveTo(t+a,n),e.arcTo(t+r,n,t+r,n+i,a),e.arcTo(t+r,n+i,t,n+i,a),e.arcTo(t,n+i,t,n,a),e.arcTo(t,n,t+r,n,a),e.closePath()}function _(e,t,n,r,i=``){let a=n;for(e.font=`${i} ${a}px 'Helvetica Neue', Arial, sans-serif`;a>8&&e.measureText(t).width>r;)--a,e.font=`${i} ${a}px 'Helvetica Neue', Arial, sans-serif`}function v(e,t,n,r,i,a){e.beginPath(),e.moveTo(t-n,i),e.lineTo(t-n,a),e.ellipse(t,a,n,r,0,Math.PI,0,!0),e.lineTo(t+n,i),e.ellipse(t,i,n,r,0,0,Math.PI,!1),e.closePath()}function y(e,t,n,r,i){let a=e.createLinearGradient(t-n,0,t+n,0);return a.addColorStop(0,r),a.addColorStop(.35,i),a.addColorStop(.55,i),a.addColorStop(1,r),a}function b(e,t){return e.bottom-Math.min(1,Math.max(0,t))*(e.bottom-e.top)}function x(e,t,n){let r=n.palette,{cx:i,rx:a,top:o,bottom:s}=t,c=a*.22;if(v(e,i,a,c,o,s),e.fillStyle=r.glass,e.fill(),n.fraction!==void 0){let o=r.liquid[n.level],l=b(t,n.fraction);v(e,i,a,c,l,s),e.fillStyle=y(e,i,a,o.edge,o.middle),e.fill(),e.beginPath(),e.ellipse(i,l,a,c,0,0,Math.PI*2),e.fillStyle=o.surface,e.fill()}let l=e.createLinearGradient(i-a*.7,0,i-a*.35,0);if(l.addColorStop(0,`rgba(255, 255, 255, 0)`),l.addColorStop(.5,r.glint),l.addColorStop(1,`rgba(255, 255, 255, 0)`),e.fillStyle=l,e.fillRect(i-a*.7,o+c,a*.35,s-o-c),e.lineWidth=Math.max(1,a*.04),e.strokeStyle=r.glassEdge,v(e,i,a,c,o,s),e.stroke(),e.beginPath(),e.ellipse(i,o,a,c,0,0,Math.PI*2),e.stroke(),n.kind===`battery`){let t=a*.32,n=t*.22,s=a*.22;v(e,i,t,n,o-s,o),e.fillStyle=y(e,i,t,r.glassEdge,r.text),e.fill(),e.beginPath(),e.ellipse(i,o-s,t,n,0,0,Math.PI*2),e.fillStyle=r.text,e.fill()}}function S(e,t,n){let r=n.palette,{cx:i,rx:a,top:o,bottom:s}=t,c=Math.min(a*.25,8),l=Math.max(1,a*.04);if(g(e,i-a,o,2*a,s-o,c),e.fillStyle=r.glass,e.fill(),n.fraction!==void 0){let l=b(t,n.fraction);e.save(),g(e,i-a,o,2*a,s-o,c),e.clip(),e.fillStyle=r.liquid[n.level].middle,e.fillRect(i-a,l,2*a,s-l),e.restore()}if(e.lineWidth=l,e.strokeStyle=r.glassEdge,g(e,i-a,o,2*a,s-o,c),e.stroke(),n.kind===`battery`){let t=a*.6,n=a*.18;g(e,i-t/2,o-n,t,n,Math.min(n/2,3)),e.fillStyle=r.glassEdge,e.fill()}}function C(e,t,n,r){let i=r.palette;e.clearRect(0,0,t,n),g(e,0,0,t,n,Math.min(10,t/10)),e.fillStyle=i.card,e.fill();let a=Math.max(6,Math.min(t,n)*.06),o=Math.max(10,Math.min(t*.11,n*.06)),s=Math.max(14,Math.min(t*.2,n*.11)),c=Math.max(9,s*.5);e.textAlign=`center`,e.textBaseline=`top`,e.fillStyle=i.muted,_(e,r.title,o,t-2*a),e.fillText(r.title,t/2,a);let l=s+(r.detailText?c*1.2:0)+a,u=a+o*1.5,d=n-l-a*.5,f=Math.min(t*.27,(d-u)*.4),p=f*(r.style===`3d`?.22:0)+(r.kind===`battery`?f*.22:0),m={cx:t/2-f*.15,rx:f,top:u+p,bottom:d-(r.style===`3d`?f*.22:0)};if(m.bottom-m.top<4)return;r.style===`flat`?S(e,m,r):x(e,m,r);let h=m.cx+f+Math.max(3,f*.12),v=e=>b(m,e);e.lineCap=`round`,e.strokeStyle=i.muted,e.lineWidth=Math.max(1,f*.03);for(let t of[.25,.5,.75])e.beginPath(),e.moveTo(h,v(t)),e.lineTo(h+f*.18,v(t)),e.stroke();e.lineWidth=Math.max(2,f*.09);for(let t of r.marks)e.strokeStyle=i.mark[t.level],e.beginPath(),e.moveTo(h,v(t.at)),e.lineTo(h+f*.32,v(t.at)),e.stroke();e.textBaseline=`alphabetic`,e.fillStyle=r.fraction===void 0?i.muted:i.text,_(e,r.valueText,s,t-2*a,`bold`);let y=n-a-(r.detailText?c*1.2:0);e.fillText(r.valueText,t/2,y),r.detailText&&(e.fillStyle=i.muted,_(e,r.detailText,c,t-2*a),e.fillText(r.detailText,t/2,n-a))}var w=12e4,T=5e3;function E(g){let _=p[g],v=document.createElement(`canvas`);document.getElementById(`root`)?.append(v);let y,b=!1,x=0,S,E=!1,D=(e,t,n=!0)=>e&&(!n||t-e.time<w)?e.value:void 0;function O(e){let t=b?h:m,n=y;if(!n?.config.path)return{title:_.noun,fraction:void 0,level:`ok`,valueText:`Not set up`,detailText:``,marks:[],kind:g,style:n?.config.style??`3d`,palette:t};let r=D(n.levelReading,e),a=D(n.detailReading,e,_.detailAges),o=n.config.showDetail&&r!==void 0&&a!==void 0?`${i(_.detail(r,a),n.detail.conversion).toFixed(n.detail.decimals)} ${n.detail.symbol}`.trim():``;return{title:n.title,fraction:r,level:r===void 0?`ok`:u(d(r,n.zones)),valueText:r===void 0?`--`:`${i(r,n.level.conversion).toFixed(n.level.decimals)} ${n.level.symbol}`.trim(),detailText:o,marks:c(n.zones),kind:g,style:n.config.style,palette:t}}function k(){E=!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),C(i,e.width,e.height,O(Date.now())))}function A(){E||(E=!0,requestAnimationFrame(k))}async function j(e){let t=++x,n=f(await e.state.get()),i=_.matches(n.path)?_.detailPath(n.path):void 0,[c,u,d]=n.path?await Promise.all([r(n.path),i?r(i):void 0,i&&!_.detailAges?o(i):void 0]):[void 0,void 0,void 0];if(t!==x)return;let p={config:n,title:n.label||c?.displayName||_.name(n.path),zones:l(c?.zones),level:a({...c,units:c?.units??`ratio`},`percent`,0),detail:a({...u,units:u?.units??_.detailUnits},void 0,_.detailDecimals)};typeof d==`number`&&Number.isFinite(d)&&(p.detailReading={time:Date.now(),value:d});let m;if(n.path){let r=i?[n.path,i]:[n.path],a=new s;if(m=await e.signalk.subscribe(r,e=>{if(typeof e.value!=`number`||!Number.isFinite(e.value))return;let t={time:a.local(Date.now(),e.timestamp),value:e.value};e.path===n.path?p.levelReading=t:p.detailReading=t,A()}),t!==x){await m().catch(()=>void 0);return}}let h=S;S=m,y=p,A(),await h?.().catch(()=>void 0)}async function M(){let r=await e();t(r),new ResizeObserver(A).observe(v),setInterval(A,T),await r.subscribe([`state.changed`],()=>{j(r).catch(e=>{console.warn(`${g}: settings reload failed`,e)})}),await n(r,e=>{b=e,A()}).catch(e=>{console.warn(`${g}: night mode unavailable`,e)}),await j(r)}M().catch(e=>{console.warn(`${g}: host connection failed`,e)})}export{E as t};
@@ -0,0 +1,21 @@
1
+ import{t as e}from"./chunk-U7YORXZ7-BLTBSYrv.js";import{n as t,o as n,r}from"./units-Cn4uhL0-.js";import{r as i,t as a}from"./level-model-0XbpyS2Q.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)return t;throw Error(`#${e} is not an input`)}var l=r({units:`ratio`},`percent`,0);function u(e,n,r){let i=e.matches(n.path)?n.displayName||e.name(n.path):n.path;return`<button type="button" class="pick${n.path===r?` on`:``}" aria-pressed="${n.path===r}" data-path="${o(n.path)}" title="${o(n.path)}">
2
+ <span class="name">${o(i)}</span>
3
+ <span class="reading">${t(n.value,l.conversion).toFixed(l.decimals)} ${l.symbol}</span>
4
+ </button>`}function d(e,t,n,r){return n.length===0?``:`<div class="section-title">${t}</div>
5
+ <div class="picks">${n.map(t=>u(e,t,r)).join(``)}</div>`}function f(e,t){e.classList.toggle(`on`,t),e.setAttribute(`aria-pressed`,String(t))}function p(e,t,n){return`<div class="chips">${t.map(([t,r])=>`<button type="button" class="chip${t===n?` on`:``}" aria-pressed="${t===n}" data-${e}="${t}">${r}</button>`).join(``)}</div>`}function m(e,t){let n=document.querySelectorAll(`[data-${e}]`);for(let r of n)r.addEventListener(`click`,()=>{for(let e of n)f(e,e===r);t(r.dataset[e]??``)})}async function h(t){let r=a[t],l=s(`root`),u=await e();l.innerHTML=`<p class="status">Loading…</p>`;let[h,g]=await Promise.all([u.state.get(),n()]),_=i(h),v=g.filter(e=>e.units===`ratio`||r.matches(e.path)),y=v.filter(e=>r.matches(e.path)),b=v.filter(e=>!r.matches(e.path)),{path:x,style:S,showDetail:C}=_;l.innerHTML=`
6
+ ${d(r,r.plural,y,x)}
7
+ ${d(r,`Other levels`,b,x)}
8
+ ${v.length===0?`<p class="status">No levels on this server yet.</p>`:``}
9
+ <label class="row"><span>Label</span>
10
+ <input id="label" value="${o(_.label)}" placeholder="Automatic">
11
+ </label>
12
+ <div class="section-title">Look</div>
13
+ ${p(`look`,[[`3d`,`3D`],[`flat`,`Flat`]],S)}
14
+ <div class="section-title">${r.detailTitle}</div>
15
+ ${p(`detail`,[[`true`,`Show`],[`false`,`Hide`]],String(C))}
16
+ <p class="status">Colours follow the zones set for the path in the server's metadata: green when normal, yellow for alert and warn, red for alarm and emergency.</p>
17
+ <p class="status" id="status"></p>
18
+ <div class="actions sticky">
19
+ <button type="button" id="cancel">Cancel</button>
20
+ <button type="button" id="save" class="primary">Save</button>
21
+ </div>`;for(let e of document.querySelectorAll(`.pick`))e.addEventListener(`click`,()=>{x=e.dataset.path??``;for(let t of document.querySelectorAll(`.pick`))f(t,t===e)});m(`look`,e=>{S=e===`flat`?`flat`:`3d`}),m(`detail`,e=>{C=e===`true`});let w=s(`status`);s(`save`).addEventListener(`click`,()=>{u.state.set({path:x,label:c(`label`).value.trim(),style:S,showDetail:C}).then(()=>u.call(`ui.closePanel`)).catch(e=>{w.textContent=`Save failed: ${e instanceof Error?e.message:String(e)}`})}),s(`cancel`).addEventListener(`click`,()=>{u.call(`ui.closePanel`).catch(()=>void 0)})}function g(e){h(e).catch(e=>{let t=document.getElementById(`root`);t&&(t.textContent=`Host connection failed: ${e instanceof Error?e.message:String(e)}`)})}export{g as t};
@@ -0,0 +1 @@
1
+ var e={path:``,label:``,style:`3d`,showDetail:!0};function t(t){let n=typeof t==`object`&&t?t:{};return{path:typeof n.path==`string`?n.path:e.path,label:typeof n.label==`string`?n.label:e.label,style:n.style===`flat`||n.style===`3d`?n.style:e.style,showDetail:typeof n.showDetail==`boolean`?n.showDetail:e.showDetail}}var n=/^tanks\.([^.]+)\.([^.]+)\.currentLevel$/,r=/^electrical\.batteries\.([^.]+)\.capacity\.stateOfCharge$/,i={fuel:`Fuel`,freshWater:`Fresh water`,wasteWater:`Grey water`,blackWater:`Black water`,lubrication:`Oil`,liveWell:`Live well`,baitWell:`Bait well`,ballast:`Ballast`,gas:`Gas`},a={tank:{noun:`Tank`,plural:`Tanks`,matches:e=>n.test(e),name:e=>{let t=n.exec(e);if(!t)return e;let[,r=``,a=``]=t;return`${i[r]??r} ${a}`.trim()},detailPath:e=>e.replace(/\.currentLevel$/,`.capacity`),detail:(e,t)=>e*t,detailUnits:`m3`,detailDecimals:0,detailAges:!1,detailTitle:`Volume left (needs the tank's capacity)`},battery:{noun:`Battery`,plural:`Battery banks`,matches:e=>r.test(e),name:e=>{let t=r.exec(e)?.[1];return t===void 0?e:`Battery ${t}`},detailPath:e=>e.replace(/\.capacity\.stateOfCharge$/,`.voltage`),detail:(e,t)=>t,detailUnits:`V`,detailDecimals:1,detailAges:!0,detailTitle:`Voltage (needs the bank's voltage)`}},o={nominal:0,normal:0,alert:1,warn:2,alarm:3,emergency:4};function s(e){return typeof e==`string`&&e in o}function c(e){if(!Array.isArray(e))return[];let t=[];for(let n of e){if(typeof n!=`object`||!n)continue;let e=n;if(!s(e.state))continue;let r={state:e.state};typeof e.lower==`number`&&(r.lower=e.lower),typeof e.upper==`number`&&(r.upper=e.upper),typeof e.message==`string`&&(r.message=e.message),t.push(r)}return t}function l(e,t){let n=`normal`;for(let r of t)r.lower!==void 0&&e<r.lower||r.upper!==void 0&&e>r.upper||o[r.state]>o[n]&&(n=r.state);return n}function u(e){let t=o[e];return t>=3?`alarm`:t>=1?`warn`:`ok`}function d(e){let t={ok:0,warn:1,alarm:2},n=new Map;for(let r of e){let e=u(r.state);if(e!==`ok`)for(let i of[r.lower,r.upper]){if(i===void 0||i<=0||i>=1)continue;let r=n.get(i);(!r||t[e]>t[r])&&n.set(i,e)}}return[...n].map(([e,t])=>({at:e,level:t})).sort((e,t)=>e.at-t.at)}export{d as a,c as i,u as n,l as o,t as r,a as t};
@@ -1 +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)});
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-Cn4uhL0-.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 @@
1
+ import"./chunk-U7YORXZ7-BLTBSYrv.js";import{t as e}from"./level-CwEJ7gss.js";e(`tank`);
@@ -0,0 +1 @@
1
+ import"./chunk-U7YORXZ7-BLTBSYrv.js";import{t as e}from"./level-config-B0mMmED6.js";e(`tank`);
@@ -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),typeof r.displayName==`string`&&(i.displayName=r.displayName),Array.isArray(r.zones)&&(i.zones=r.zones);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,value:t.value,displayName:e(n)&&typeof n.displayName==`string`?n.displayName: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[]}}async function a(e){try{let t=await fetch(`/signalk/v1/api/vessels/self/${e.replace(/\./g,`/`)}/value`,{credentials:`include`});return t.ok?await t.json():void 0}catch{return}}var o=(e,t,n,r=0,i=0)=>({id:e,symbol:t,factor:n,offset:r,add:i}),s={rad:[o(`degree`,`°`,180/Math.PI),o(`radian`,`rad`,1)],"m/s":[o(`kn`,`kn`,1.94384),o(`m/s`,`m/s`,1),o(`km/h`,`km/h`,3.6),o(`mph`,`mph`,2.2369362920544025)],K:[o(`C`,`°C`,1,-273.15),o(`F`,`°F`,9/5,-273.15,32),o(`K`,`K`,1)],Pa:[o(`hPa`,`hPa`,.01),o(`mbar`,`mbar`,.01),o(`bar`,`bar`,1e-5),o(`psi`,`psi`,.0001450376807894691),o(`inHg`,`inHg`,.00029529987601298443),o(`mmHg`,`mmHg`,.0075006168507298),o(`Pa`,`Pa`,1)],m:[o(`m`,`m`,1),o(`meter`,`m`,1),o(`foot`,`ft`,3.280839895013124),o(`fathom`,`fathom`,.5467468562055768),o(`naut-mile`,`nmi`,.0005399568034557236),o(`kilometer`,`km`,.001),o(`mile`,`mi`,.000621371192237334)],"rad/s":[o(`deg/s`,`°/s`,57.29577951308232),o(`rpm`,`rpm`,9.549296585513723),o(`rad/s`,`rad/s`,1)],ratio:[o(`percent`,`%`,100),o(`ratio`,``,1)],Hz:[o(`rpm`,`rpm`,60),o(`hertz`,`Hz`,1)],V:[o(`V`,`V`,1)],A:[o(`A`,`A`,1)],W:[o(`watt`,`W`,1),o(`kW`,`kW`,.001)],m3:[o(`liter`,`l`,1e3),o(`gallon`,`gal`,264.1720512415585),o(`m3`,`m³`,1)],"m3/s":[o(`L/h`,`l/h`,36e5),o(`L/min`,`l/min`,6e4),o(`gal/h`,`gal/h`,264.17205236)]};function c(e,t){return t?(e+(t.offset??0))*t.factor+(t.add??0):e}function l(e){if(e)return/\.(0+)/.exec(e)?.[1]?.length??0}function u(e,t,n){let r=e?.units,i=r?s[r]??[]:[],a=e=>i.find(t=>t.id===e),o=a(t)??a(e?.displayUnits?.targetUnit)??i[0],c=r===`rad`,u=o&&o.id===e?.displayUnits?.targetUnit?l(e.displayUnits.displayFormat):void 0,d=o?.id===`degree`?0:u??1;return{conversion:o,symbol:o?.symbol??r??``,decimals:n>=0?n:d,angular:c}}export{t as a,i,c as n,r as o,u as r,a as s,s as t};
@@ -1 +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)});
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-Cn4uhL0-.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)});
@@ -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>Battery setup</title>
7
+ <script type="module" crossorigin src="./assets/battery-config-CUDy2wrq.js"></script>
8
+ <link rel="modulepreload" crossorigin href="./assets/chunk-U7YORXZ7-BLTBSYrv.js">
9
+ <link rel="modulepreload" crossorigin href="./assets/units-Cn4uhL0-.js">
10
+ <link rel="modulepreload" crossorigin href="./assets/level-model-0XbpyS2Q.js">
11
+ <link rel="modulepreload" crossorigin href="./assets/level-config-B0mMmED6.js">
12
+ <link rel="stylesheet" crossorigin href="./assets/config-DBAHPNIf.css">
13
+ </head>
14
+ <body>
15
+ <div id="root"></div>
16
+ </body>
17
+ </html>
@@ -0,0 +1,19 @@
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>Battery</title>
7
+ <script type="module" crossorigin src="./assets/battery-BhqU92AD.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-Cn4uhL0-.js">
11
+ <link rel="modulepreload" crossorigin href="./assets/steer-model-DA5-9SMr.js">
12
+ <link rel="modulepreload" crossorigin href="./assets/level-model-0XbpyS2Q.js">
13
+ <link rel="modulepreload" crossorigin href="./assets/level-CwEJ7gss.js">
14
+ <link rel="stylesheet" crossorigin href="./assets/styles-D6wkrAFq.css">
15
+ </head>
16
+ <body>
17
+ <div id="root"></div>
18
+ </body>
19
+ </html>
@@ -4,11 +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-BKRpYFX2.js"></script>
7
+ <script type="module" crossorigin src="./assets/config-n3H05jK1.js"></script>
8
8
  <link rel="modulepreload" crossorigin href="./assets/chunk-U7YORXZ7-BLTBSYrv.js">
9
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
+ <link rel="modulepreload" crossorigin href="./assets/units-Cn4uhL0-.js">
11
+ <link rel="stylesheet" crossorigin href="./assets/config-DBAHPNIf.css">
12
12
  </head>
13
13
  <body>
14
14
  <div id="root"></div>
@@ -4,10 +4,10 @@
4
4
  <meta charset="utf-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1" />
6
6
  <title>Wind steering setup</title>
7
- <script type="module" crossorigin src="./assets/steer-config-VgFoNl7x.js"></script>
7
+ <script type="module" crossorigin src="./assets/steer-config-CxpZ3N7O.js"></script>
8
8
  <link rel="modulepreload" crossorigin href="./assets/chunk-U7YORXZ7-BLTBSYrv.js">
9
9
  <link rel="modulepreload" crossorigin href="./assets/steer-model-DA5-9SMr.js">
10
- <link rel="stylesheet" crossorigin href="./assets/config-BRhovzXK.css">
10
+ <link rel="stylesheet" crossorigin href="./assets/config-DBAHPNIf.css">
11
11
  </head>
12
12
  <body>
13
13
  <div id="root"></div>
package/public/steer.html CHANGED
@@ -4,10 +4,10 @@
4
4
  <meta charset="utf-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1" />
6
6
  <title>Wind steering</title>
7
- <script type="module" crossorigin src="./assets/steer-CY6FnXbG.js"></script>
7
+ <script type="module" crossorigin src="./assets/steer-BhT70PR7.js"></script>
8
8
  <link rel="modulepreload" crossorigin href="./assets/chunk-U7YORXZ7-BLTBSYrv.js">
9
9
  <link rel="modulepreload" crossorigin href="./assets/styles-CjtPzFRR.js">
10
- <link rel="modulepreload" crossorigin href="./assets/units-zSVjGfwB.js">
10
+ <link rel="modulepreload" crossorigin href="./assets/units-Cn4uhL0-.js">
11
11
  <link rel="modulepreload" crossorigin href="./assets/steer-model-DA5-9SMr.js">
12
12
  <link rel="stylesheet" crossorigin href="./assets/styles-D6wkrAFq.css">
13
13
  </head>
@@ -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>Tank setup</title>
7
+ <script type="module" crossorigin src="./assets/tank-config-C6CTiHir.js"></script>
8
+ <link rel="modulepreload" crossorigin href="./assets/chunk-U7YORXZ7-BLTBSYrv.js">
9
+ <link rel="modulepreload" crossorigin href="./assets/units-Cn4uhL0-.js">
10
+ <link rel="modulepreload" crossorigin href="./assets/level-model-0XbpyS2Q.js">
11
+ <link rel="modulepreload" crossorigin href="./assets/level-config-B0mMmED6.js">
12
+ <link rel="stylesheet" crossorigin href="./assets/config-DBAHPNIf.css">
13
+ </head>
14
+ <body>
15
+ <div id="root"></div>
16
+ </body>
17
+ </html>
@@ -0,0 +1,19 @@
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>Tank</title>
7
+ <script type="module" crossorigin src="./assets/tank-Gyfo7ABV.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-Cn4uhL0-.js">
11
+ <link rel="modulepreload" crossorigin href="./assets/steer-model-DA5-9SMr.js">
12
+ <link rel="modulepreload" crossorigin href="./assets/level-model-0XbpyS2Q.js">
13
+ <link rel="modulepreload" crossorigin href="./assets/level-CwEJ7gss.js">
14
+ <link rel="stylesheet" crossorigin href="./assets/styles-D6wkrAFq.css">
15
+ </head>
16
+ <body>
17
+ <div id="root"></div>
18
+ </body>
19
+ </html>
@@ -4,11 +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</title>
7
- <script type="module" crossorigin src="./assets/widget-DjBm6Z7F.js"></script>
7
+ <script type="module" crossorigin src="./assets/widget-CH38Wyvl.js"></script>
8
8
  <link rel="modulepreload" crossorigin href="./assets/chunk-U7YORXZ7-BLTBSYrv.js">
9
9
  <link rel="modulepreload" crossorigin href="./assets/config-model-DvTH1O2f.js">
10
10
  <link rel="modulepreload" crossorigin href="./assets/styles-CjtPzFRR.js">
11
- <link rel="modulepreload" crossorigin href="./assets/units-zSVjGfwB.js">
11
+ <link rel="modulepreload" crossorigin href="./assets/units-Cn4uhL0-.js">
12
12
  <link rel="stylesheet" crossorigin href="./assets/styles-D6wkrAFq.css">
13
13
  </head>
14
14
  <body>
@@ -1 +0,0 @@
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};