signalk-history-widgets 0.2.0 → 0.4.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.
Files changed (40) hide show
  1. package/README.md +45 -2
  2. package/docs/screenshots/clock.png +0 -0
  3. package/docs/screenshots/config.png +0 -0
  4. package/docs/screenshots/levels-small.png +0 -0
  5. package/docs/screenshots/levels.png +0 -0
  6. package/package.json +2 -2
  7. package/plugin/index.js +56 -14
  8. package/plugin/index.js.map +1 -1
  9. package/public/assets/battery-BP5w2HNc.js +1 -0
  10. package/public/assets/battery-config-DYeoAWMp.js +1 -0
  11. package/public/assets/clock-DGa0UjBJ.js +1 -0
  12. package/public/assets/clock-config-DBFHzMXm.js +13 -0
  13. package/public/assets/clock-model-D8TrsRP6.js +1 -0
  14. package/public/assets/config-4Cx9vP2h.js +24 -0
  15. package/public/assets/{config-BRhovzXK.css → config-DBAHPNIf.css} +1 -1
  16. package/public/assets/{config-model-DvTH1O2f.js → config-model-BkGUzGeC.js} +1 -1
  17. package/public/assets/level-config-COx4EZHZ.js +21 -0
  18. package/public/assets/level-model-CINplXoI.js +1 -0
  19. package/public/assets/level-wmEozZnD.js +1 -0
  20. package/public/assets/signalk-NgaYaNkQ.js +1 -0
  21. package/public/assets/steer-DVXoKR46.js +1 -0
  22. package/public/assets/tank-B7og9w7x.js +1 -0
  23. package/public/assets/tank-config-BUfMKMvt.js +1 -0
  24. package/public/assets/units-nn6bpgTR.js +1 -0
  25. package/public/assets/widget-VvPC6-2G.js +1 -0
  26. package/public/battery-config.html +18 -0
  27. package/public/battery.html +20 -0
  28. package/public/clock-config.html +16 -0
  29. package/public/clock.html +17 -0
  30. package/public/config.html +5 -4
  31. package/public/steer-config.html +2 -2
  32. package/public/steer.html +3 -2
  33. package/public/tank-config.html +18 -0
  34. package/public/tank.html +20 -0
  35. package/public/widget.html +4 -3
  36. package/public/assets/config-BKRpYFX2.js +0 -30
  37. package/public/assets/steer-CY6FnXbG.js +0 -1
  38. package/public/assets/units-zSVjGfwB.js +0 -1
  39. package/public/assets/widget-DjBm6Z7F.js +0 -1
  40. /package/public/assets/{steer-config-VgFoNl7x.js → steer-config-CxpZ3N7O.js} +0 -0
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, **Tank level** and
14
+ **Battery charge** widgets and a **Clock**, described [below](#wind-steering).
14
15
 
15
16
  ## Use
16
17
 
@@ -21,7 +22,7 @@ The package also brings a **Wind steering** dial, described [below](#wind-steeri
21
22
  (wide)**.
22
23
  3. Pick the value: a quick button (TWD, TWS, TWA, AWA, AWS, SOG, STW, COG,
23
24
  HDG, depth, air pressure, air and water temperature) or any numeric Signal K
24
- path. Set the time span in minutes or hours, from one minute to a week.
25
+ path. Tap the time span, from 5 minutes to 24 hours.
25
26
 
26
27
  For several values, place several strips next to each other. Press and hold a
27
28
  placed strip to change it or remove it.
@@ -74,6 +75,48 @@ 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 and
83
+ current, in the units set in the server's Unit Preferences. A green arrow up
84
+ means the bank is charging, a red arrow down that it is discharging. The colour follows the zones
85
+ set for the level in the server's metadata (`meta.zones`): green when the
86
+ level is normal, yellow in an alert or warn zone, red in an alarm or
87
+ emergency zone. Marks beside the bar show where the zones begin, so you see
88
+ how far the level is from the next one. Without zones the bar stays green.
89
+
90
+ ![A battery bank as a cylinder and as a flat bar, and a tank as a flat bar](docs/screenshots/levels.png)
91
+
92
+ Both come in a small size too (1x1), with the reading beside the bar:
93
+
94
+ ![Small battery and tank widgets](docs/screenshots/levels-small.png)
95
+
96
+ Press and hold the widget to pick another tank or bank. The panel lists every
97
+ `tanks.*.*.currentLevel` (or every
98
+ `electrical.batteries.*.capacity.stateOfCharge`) on the server with its
99
+ current level, plus any other 0 to 100 % value, and offers the look (3D or
100
+ flat) and whether to show the volume or voltage and current as buttons, so it is set up
101
+ with a few taps and no keyboard. A level older than two minutes shows as
102
+ `--`.
103
+
104
+ ## Clock
105
+
106
+ The time, as large as the widget allows, with the date underneath. It shows
107
+ this screen's time zone, UTC, or the boat's time zone as the server reports
108
+ it (`environment.time.timezoneRegion`, or `environment.time.timezoneOffset`).
109
+ While the server reports no usable boat zone, a clock set to boat time shows
110
+ this screen's own zone instead and says so above the time.
111
+ A screen whose clock is more than two seconds off the GPS time
112
+ (`navigation.datetime`) follows the GPS, which keeps a display that booted
113
+ without a network right.
114
+
115
+ ![The clock in boat time](docs/screenshots/clock.png)
116
+
117
+ Press and hold the clock to choose the zone, 24 or 12 hours, and whether to
118
+ show seconds and the date, all with buttons.
119
+
77
120
  ## Requirements
78
121
 
79
122
  - Signal K server 2.22.1 or newer (History API v2) on Node 24 or newer
Binary file
Binary file
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.4.0",
4
+ "description": "History strip, wind steering, tank level and clock 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, tank levels and battery charge coloured by their zones, and a clock in local, UTC or boat time",
5
5
  "author": "Dirk Wahrheit",
6
6
  "main": "plugin/index.js",
7
7
  "type": "module",
package/plugin/index.js CHANGED
@@ -48,11 +48,28 @@ var strip = (id, title, size) => ({
48
48
  configPanel: "strip-config",
49
49
  lifecycle: "whileEnabled"
50
50
  });
51
+ /** A level widget; the small one shares its page and panel. */
52
+ var level = (id, title, size = "1x2", page = id) => ({
53
+ id,
54
+ title,
55
+ type: "iframe",
56
+ url: `${ASSET_BASE}/${page}.html`,
57
+ size,
58
+ configPanel: `${page}-config`,
59
+ lifecycle: "whileEnabled"
60
+ });
61
+ var levelPanel = (id, title) => ({
62
+ id: `${id}-config`,
63
+ title,
64
+ type: "iframe",
65
+ url: `${ASSET_BASE}/${id}-config.html`,
66
+ lifecycle: "onOpen"
67
+ });
51
68
  /** The Plotter Extensions manifest (apiVersion 1). */
52
69
  function buildManifest(version) {
53
70
  return {
54
71
  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.",
72
+ 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, tank levels and battery charge coloured by their zones, and a clock in local, UTC or boat time.",
56
73
  version,
57
74
  apiVersion: "1",
58
75
  requires: [
@@ -72,21 +89,46 @@ function buildManifest(version) {
72
89
  size: "2x2",
73
90
  configPanel: "wind-steering-config",
74
91
  lifecycle: "whileEnabled"
92
+ },
93
+ level("tank", "Tank level"),
94
+ level("tank-small", "Tank level (small)", "1x1", "tank"),
95
+ level("battery", "Battery charge"),
96
+ level("battery-small", "Battery charge (small)", "1x1", "battery"),
97
+ {
98
+ id: "clock",
99
+ title: "Clock",
100
+ type: "iframe",
101
+ url: `${ASSET_BASE}/clock.html`,
102
+ size: "2x1",
103
+ configPanel: "clock-config",
104
+ lifecycle: "whileEnabled"
75
105
  }
76
106
  ],
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
- }]
107
+ panels: [
108
+ {
109
+ id: "strip-config",
110
+ title: "History strip",
111
+ type: "iframe",
112
+ url: `${ASSET_BASE}/config.html`,
113
+ lifecycle: "onOpen"
114
+ },
115
+ {
116
+ id: "wind-steering-config",
117
+ title: "Wind steering",
118
+ type: "iframe",
119
+ url: `${ASSET_BASE}/steer-config.html`,
120
+ lifecycle: "onOpen"
121
+ },
122
+ levelPanel("tank", "Tank level"),
123
+ levelPanel("battery", "Battery charge"),
124
+ {
125
+ id: "clock-config",
126
+ title: "Clock",
127
+ type: "iframe",
128
+ url: `${ASSET_BASE}/clock-config.html`,
129
+ lifecycle: "onOpen"
130
+ }
131
+ ]
90
132
  };
91
133
  }
92
134
  // 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\n/** A level widget; the small one shares its page and panel. */\nconst level = (id: string, title: string, size = '1x2', page = id) => ({\n id,\n title,\n type: 'iframe',\n url: `${ASSET_BASE}/${page}.html`,\n size,\n configPanel: `${page}-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, tank levels and battery charge coloured by their zones, and a clock in local, UTC or boat time.',\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('tank-small', 'Tank level (small)', '1x1', 'tank'),\n level('battery', 'Battery charge'),\n level('battery-small', 'Battery charge (small)', '1x1', 'battery'),\n {\n id: 'clock',\n title: 'Clock',\n type: 'iframe',\n url: `${ASSET_BASE}/clock.html`,\n size: '2x1',\n configPanel: 'clock-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 levelPanel('tank', 'Tank level'),\n levelPanel('battery', 'Battery charge'),\n {\n id: 'clock-config',\n title: 'Clock',\n type: 'iframe',\n url: `${ASSET_BASE}/clock-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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"}
@@ -0,0 +1 @@
1
+ import"./chunk-U7YORXZ7-BLTBSYrv.js";import{t as e}from"./level-wmEozZnD.js";e(`battery`);
@@ -0,0 +1 @@
1
+ import"./chunk-U7YORXZ7-BLTBSYrv.js";import{t as e}from"./level-config-COx4EZHZ.js";e(`battery`);
@@ -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,i,n as a,o,r as s,s as c,t as l}from"./clock-model-D8TrsRP6.js";import{i as u}from"./signalk-NgaYaNkQ.js";var d={card:`rgba(20, 26, 32, 0.78)`,text:`#e8edf2`,muted:`#9fb3c4`},f={card:`rgba(8, 0, 0, 0.85)`,text:`#d32f2f`,muted:`#8e2424`},p=`'Helvetica Neue', Arial, sans-serif`;function m(e,t,n){let r=Math.floor(n);for(;r>7&&e(r)>t;)r--;return r}function h(e,t,n){if(n(e))return e;let r=t&&e.endsWith(t)?t:``,i=e.slice(0,e.length-r.length);for(let e=i.length-1;e>0;e--){let t=`${i.slice(0,e).trimEnd()}…${r?` ${r}`:``}`;if(n(t))return t}for(let e=(r||i).length-1;e>0;e--){let t=`${(r||i).slice(0,e).trimEnd()}…`;if(n(t))return t}return`…`}function g(e,t,n,r,i=``){let a=m(n=>(e.font=`${i} ${n}px ${p}`,e.measureText(t).width),n,r);return e.font=`${i} ${a}px ${p}`,a}function _(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)*.06),o=t-2*a,s=Math.max(9,Math.min(n*.14,t*.08));e.textAlign=`center`,e.textBaseline=`alphabetic`;let c=g(e,r.title,o,s),l=h(r.title,r.keep??``,t=>e.measureText(t).width<=o);e.fillStyle=i.muted,e.fillText(l,t/2,a+c);let u=r.date?s:0,d=a+c,f=n-a-(r.date?u*1.3:0),p=g(e,r.time,o,(f-d)*.9,`600`);e.fillStyle=i.text,e.fillText(r.time,t/2,(d+f)/2+p*.36),r.date&&(g(e,r.date,o,u),e.fillStyle=i.muted,e.fillText(r.date,t/2,n-a))}var v=document.createElement(`canvas`);document.getElementById(`root`)?.append(v);var y=o({}),b=!1,x=new s,S=new l,C=!1;function w(){C=!1;let e=v.getBoundingClientRect(),t=window.devicePixelRatio||1,n=Math.max(1,Math.round(e.width*t)),a=Math.max(1,Math.round(e.height*t));(v.width!==n||v.height!==a)&&(v.width=n,v.height=a);let o=v.getContext(`2d`);if(!o)return;o.setTransform(t,0,0,t,0,0);let s=i(Date.now()+x.correction,y,S.zone());_(o,e.width,e.height,{title:r(y,s),keep:s.fallback?s.zoneName:void 0,time:s.time,date:y.showDate?s.date:``,palette:b?f:d})}function T(){C||(C=!0,requestAnimationFrame(w))}function E(){T();let e=Date.now()+x.correction;setTimeout(E,1e3-e%1e3+20)}async function D(e){t(e),new ResizeObserver(T).observe(v);let r=c(()=>e.state.get().then(e=>{y=o(e),T()}).catch(e=>{console.warn(`clock: settings unavailable`,e)}));await e.subscribe([`state.changed`],r),await n(e,e=>{b=e,T()}).catch(e=>{console.warn(`clock: night mode unavailable`,e)}),E(),r();for(let e of[a.region,a.offset])u(e).then(t=>{S.stored(e,t),T()});await e.signalk.subscribe(Object.values(a),e=>{e.path===a.gpsTime?x.read(Date.now(),e.value):S.live(e.path,e.value),T()})}e().then(D).catch(e=>{console.warn(`clock: host connection failed`,e)});
@@ -0,0 +1,13 @@
1
+ import{t as e}from"./chunk-U7YORXZ7-BLTBSYrv.js";import{a as t,i as n,n as r,o as i,t as a}from"./clock-model-D8TrsRP6.js";import{i as o}from"./signalk-NgaYaNkQ.js";/* empty css */var s=[{key:`zone`,title:`Time zone`,options:[[`local`,`This screen`],[`utc`,`UTC`],[`boat`,`Boat`]]},{key:`hours`,title:`Hours`,options:[[`24`,`24 h`],[`12`,`12 h`]]},{key:`seconds`,title:`Seconds`,options:[[!0,`Show`],[!1,`Hide`]]},{key:`showDate`,title:`Date`,options:[[!0,`Show`],[!1,`Hide`]]}];function c(e){let t=document.getElementById(e);if(!t)throw Error(`missing #${e}`);return t}function l(){let e=c(`label`);return e instanceof HTMLInputElement?e.value.trim():``}function u(e){return e.replace(/[&<>"]/g,e=>`&#${e.charCodeAt(0)};`)}async function d(){let d=c(`root`),f=await e();d.innerHTML=`<p class="status">Loading…</p>`;let p=i(await f.state.get()),m=new a,h=()=>{let e={...p,label:l()},r=n(Date.now(),e,m.zone()),i=p.showDate?`, ${u(r.date)}`:``;return`${u(t(e,r))}: ${u(r.time)}${i}`},g=()=>{c(`preview`).innerHTML=h()},_=()=>{d.innerHTML=`
2
+ ${s.map(e=>`
3
+ <div class="row"><span>${e.title}</span><div class="chips">${e.options.map(([t,n],r)=>`<button type="button" class="chip${p[e.key]===t?` on`:``}" data-key="${e.key}" data-index="${r}">${n}</button>`).join(``)}</div></div>`).join(``)}
4
+ <label class="row"><span>Label</span>
5
+ <input id="label" type="text" placeholder="Automatic" value="${u(p.label)}">
6
+ </label>
7
+ <p class="status" id="preview"></p>
8
+ <p class="status">Boat is the zone set on the server, the same on every screen.</p>
9
+ <p class="status" id="status"></p>
10
+ <div class="actions">
11
+ <button type="button" id="cancel">Cancel</button>
12
+ <button type="button" id="save" class="primary">Save</button>
13
+ </div>`,g(),c(`label`).addEventListener(`input`,g);for(let e of d.querySelectorAll(`.chip`))e.addEventListener(`click`,()=>{let t=s.find(t=>t.key===e.dataset.key),n=t?.options[Number(e.dataset.index)];t&&n&&(Object.assign(p,{[t.key]:n[0]}),p.label=l(),_())});c(`save`).addEventListener(`click`,()=>{p.label=l(),f.state.set({...p}).then(()=>f.call(`ui.closePanel`)).catch(e=>{c(`status`).textContent=`Save failed: ${e instanceof Error?e.message:String(e)}`})}),c(`cancel`).addEventListener(`click`,()=>{f.call(`ui.closePanel`).catch(()=>void 0)})};_();let v=setInterval(g,1e3);window.addEventListener(`pagehide`,()=>{clearInterval(v)});for(let e of[r.region,r.offset])o(e).then(t=>{m.stored(e,t),g()})}d().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={zone:`local`,hours:`24`,seconds:!1,showDate:!0,label:``};function t(t){let n=e,r=(e,t)=>typeof e==`boolean`?e:t;return{zone:t.zone===`utc`||t.zone===`boat`?t.zone:n.zone,hours:t.hours===`12`?`12`:n.hours,seconds:r(t.seconds,n.seconds),showDate:r(t.showDate,n.showDate),label:typeof t.label==`string`?t.label.trim():n.label}}var n={gpsTime:`navigation.datetime`,region:`environment.time.timezoneRegion`,offset:`environment.time.timezoneOffset`};function r(e){try{return new Intl.DateTimeFormat(`en`,{timeZone:e}),!0}catch{return!1}}function i(e,t){if(typeof e==`string`&&e&&r(e))return{region:e};if(typeof t==`number`&&Number.isFinite(t)){let e=Math.abs(Math.trunc(t)),n=Math.floor(e/100)*60+e%100;if(e%100<60&&n<=840)return{minutes:t<0?-n:n}}}var a=class{values=new Map;live(e,t){this.values.set(e,t)}stored(e,t){this.values.has(e)||this.values.set(e,t)}zone(){return i(this.values.get(n.region),this.values.get(n.offset))}},o=class{correction=0;last;read(e,t){let n=typeof t==`string`?Date.parse(t):NaN;if(!Number.isFinite(n))return this.correction;let r=this.last;if(this.last={arrival:e,gps:n},!r)return this.correction;let i=n-r.gps;if(i>0&&i<=6e4&&Math.abs(i-(e-r.arrival))<=1e3){let t=n-e;this.correction=Math.abs(t)>2e3?t:0}return this.correction}};function s(e){if(e===0)return`UTC`;let t=Math.abs(e),n=Math.floor(t/60),r=t%60;return`UTC${e<0?`-`:`+`}${n}${r?`:${String(r).padStart(2,`0`)}`:``}`}function c(e,t,n,r){let i,a=e,o,c=t.zone===`boat`&&n===void 0;return t.zone===`utc`?(i=`UTC`,o=`UTC`):t.zone===`boat`&&n&&`region`in n?(i=n.region,o=`Boat time, ${n.region.split(`/`).pop()?.replace(/_/g,` `)??n.region}`):t.zone===`boat`&&n&&`minutes`in n?(i=`UTC`,a=e+n.minutes*6e4,o=`Boat time, ${s(n.minutes)}`):o=c?`Local time (no boat zone)`:`Local time`,{time:new Intl.DateTimeFormat(r,{timeZone:i,hour:`2-digit`,minute:`2-digit`,second:t.seconds?`2-digit`:void 0,hourCycle:t.hours===`12`?`h12`:`h23`}).format(a),date:new Intl.DateTimeFormat(r,{timeZone:i,weekday:`short`,day:`numeric`,month:`short`,year:`numeric`}).format(a),zoneName:o,fallback:c}}function l(e,t){return e.label?t.fallback?`${e.label} · ${t.zoneName}`:e.label:t.zoneName}function u(e){let t=!1,n=!1,r=()=>{t?n=!0:(t=!0,e().finally(()=>{t=!1,n&&(n=!1,r())}))};return r}export{l as a,c as i,n,t as o,o as r,u as s,a as t};
@@ -0,0 +1,24 @@
1
+ import{t as e}from"./chunk-U7YORXZ7-BLTBSYrv.js";import{a as t,i as n,o as r,t as i}from"./config-model-BkGUzGeC.js";import{r as a,t as o}from"./signalk-NgaYaNkQ.js";import{t as s}from"./units-nn6bpgTR.js";/* empty css */function c(e){return e.replace(/[&<>"]/g,e=>`&#${e.charCodeAt(0)};`)}function l(e){let t=document.getElementById(e);if(!t)throw Error(`missing #${e}`);return t}function u(e){let t=l(e);if(t instanceof HTMLInputElement||t instanceof HTMLSelectElement)return t;throw Error(`#${e} is not a form field`)}function d(e,n,a){let o=i.map(t=>`<button type="button" class="chip${t.path===e.path?` on`:``}" data-path="${t.path}" title="${c(t.title)}">${t.label}</button>`).join(``),s=a.length>0?`History from ${c(a.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
+ <div class="chips">${o}</div>
3
+ <label class="row"><span>Signal K path</span>
4
+ <input id="path" list="paths" value="${c(e.path)}" placeholder="Type to search…">
5
+ <datalist id="paths">${n.map(e=>`<option value="${c(e.path)}">`).join(``)}</datalist>
6
+ </label>
7
+ <label class="row"><span>Label</span>
8
+ <input id="label" value="${c(e.label)}" placeholder="Automatic">
9
+ </label>
10
+ <div class="row"><span id="span-title">Time span</span>
11
+ <div class="chips" role="group" aria-labelledby="span-title">${t(e).map(t=>`<button type="button" class="chip${r({...e,...t})===r(e)?` on`:``}" aria-pressed="${r({...e,...t})===r(e)}" data-span="${t.span}" data-span-unit="${t.spanUnit}">${t.label}</button>`).join(``)}</div>
12
+ </div>
13
+ <label class="row"><span>Units</span><select id="unit"></select></label>
14
+ <label class="row"><span>Decimals</span>
15
+ <select id="decimals">
16
+ ${[-1,0,1,2,3].map(t=>`<option value="${t}"${t===e.decimals?` selected`:``}>${t<0?`Automatic`:t}</option>`).join(``)}
17
+ </select>
18
+ </label>
19
+ <p class="status">${s}</p>
20
+ <p class="status" id="status"></p>
21
+ <div class="actions">
22
+ <button type="button" id="cancel">Cancel</button>
23
+ <button type="button" id="save" class="primary">Save</button>
24
+ </div>`}function f(e,t){let n=u(`path`).value.trim(),r=e.find(e=>e.path===n)?.units,i=r?s[r]??[]:[],a=i.some(e=>e.id===t)?t:``;l(`unit`).innerHTML=[`<option value=""${a===``?` 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===a?` selected`:``}>${c(e.symbol||e.id)}</option>`)].join(``)}function p(){let e=document.querySelector(`[data-span].on`);return{path:u(`path`).value.trim(),label:u(`label`).value.trim(),span:Number(e?.dataset.span),spanUnit:e?.dataset.spanUnit,unit:u(`unit`).value,decimals:Number(u(`decimals`).value)}}async function m(){let t=l(`root`),r=await e();t.innerHTML=`<p class="status">Loading…</p>`;let[i,s,c]=await Promise.all([r.state.get(),a(),o()]),m=n(i);t.innerHTML=d(m,s,c),f(s,m.unit);let h=u(`path`),g=()=>{f(s,u(`unit`).value);for(let e of document.querySelectorAll(`[data-path]`))e.classList.toggle(`on`,e.dataset.path===h.value.trim())};h.addEventListener(`change`,g),h.addEventListener(`input`,g);for(let e of document.querySelectorAll(`[data-path]`))e.addEventListener(`click`,()=>{h.value=e.dataset.path??``,g()});let _=document.querySelectorAll(`[data-span]`);for(let e of _)e.addEventListener(`click`,()=>{for(let t of _)t.classList.toggle(`on`,t===e),t.setAttribute(`aria-pressed`,String(t===e))});let v=l(`status`);l(`save`).addEventListener(`click`,()=>{r.state.set(p()).then(()=>r.call(`ui.closePanel`)).catch(e=>{v.textContent=`Save failed: ${e instanceof Error?e.message:String(e)}`})}),l(`cancel`).addEventListener(`click`,()=>{r.call(`ui.closePanel`).catch(()=>void 0)})}m().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}.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 +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};
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)}var c=(e,t)=>({span:e,spanUnit:t,label:`${e} ${t}`}),l=[c(5,`min`),c(15,`min`),c(30,`min`),c(1,`h`),c(3,`h`),c(6,`h`),c(12,`h`),c(24,`h`)];function u(e){let t=a(e);if(l.some(n=>a({...e,...n})===t))return[...l];let n=c(e.span,e.spanUnit);return[...l,n].sort((t,n)=>a({...e,...t})-a({...e,...n}))}export{u as a,o as i,s as n,a as o,i as r,r as t};
@@ -0,0 +1,21 @@
1
+ import{t as e}from"./chunk-U7YORXZ7-BLTBSYrv.js";import{r as t}from"./signalk-NgaYaNkQ.js";import{i as n,t as r}from"./level-model-CINplXoI.js";import{n as i,r as a}from"./units-nn6bpgTR.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=a({units:`ratio`},`percent`,0);function u(e,t,n){let r=e.matches(t.path)?t.displayName||e.name(t.path):t.path;return`<button type="button" class="pick${t.path===n?` on`:``}" aria-pressed="${t.path===n}" data-path="${o(t.path)}" title="${o(t.path)}">
2
+ <span class="name">${o(r)}</span>
3
+ <span class="reading">${i(t.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(i){let a=r[i],l=s(`root`),u=await e();l.innerHTML=`<p class="status">Loading…</p>`;let[h,g]=await Promise.all([u.state.get(),t()]),_=n(h),v=g.filter(e=>e.units===`ratio`||a.matches(e.path)),y=v.filter(e=>a.matches(e.path)),b=v.filter(e=>!a.matches(e.path)),{path:x,style:S,showDetail:C}=_;l.innerHTML=`
6
+ ${d(a,a.plural,y,x)}
7
+ ${d(a,`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">${a.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,flowPath:e=>e.replace(/\.capacity\.stateOfCharge$/,`.current`),detailTitle:`Voltage and current (needs the bank's readings)`}};function o(e){return e>=.05?`charging`:e<=-.05?`discharging`:`idle`}var s={nominal:0,normal:0,alert:1,warn:2,alarm:3,emergency:4};function c(e){return typeof e==`string`&&e in s}function l(e){if(!Array.isArray(e))return[];let t=[];for(let n of e){if(typeof n!=`object`||!n)continue;let e=n;if(!c(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 u(e,t){let n=`normal`;for(let r of t)r.lower!==void 0&&e<r.lower||r.upper!==void 0&&e>r.upper||s[r.state]>s[n]&&(n=r.state);return n}function d(e){let t=s[e];return t>=3?`alarm`:t>=1?`warn`:`ok`}function f(e){let t={ok:0,warn:1,alarm:2},n=new Map;for(let r of e){let e=d(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{l as a,t as i,o as n,f as o,d as r,u as s,a 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{i as r,n as i}from"./signalk-NgaYaNkQ.js";import{r as a}from"./steer-model-DA5-9SMr.js";import{a as o,i as s,n as c,o as l,r as u,s as d,t as f}from"./level-model-CINplXoI.js";import{n as p,r as m}from"./units-nn6bpgTR.js";var h={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`}},g={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 _(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 v(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 y(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 b(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 x(e,t){return e.bottom-Math.min(1,Math.max(0,t))*(e.bottom-e.top)}function S(e,t,n){let r=n.palette,{cx:i,rx:a,top:o,bottom:s}=t,c=a*.22;if(y(e,i,a,c,o,s),e.fillStyle=r.glass,e.fill(),n.fraction!==void 0){let o=r.liquid[n.level],l=x(t,n.fraction);y(e,i,a,c,l,s),e.fillStyle=b(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,y(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;y(e,i,t,n,o-s,o),e.fillStyle=b(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 C(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(_(e,i-a,o,2*a,s-o,c),e.fillStyle=r.glass,e.fill(),n.fraction!==void 0){let l=x(t,n.fraction);e.save(),_(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,_(e,i-a,o,2*a,s-o,c),e.stroke(),n.kind===`battery`){let t=a*.6,n=a*.18;_(e,i-t/2,o-n,t,n,Math.min(n/2,3)),e.fillStyle=r.glassEdge,e.fill()}}function w(e,t,n,r,i,a,o){let s=t.direction===`idle`?0:i*.7,c=s?i*.3:0;v(e,t.text,i,a-s-c,`bold`);let l=n-(s+c+e.measureText(t.text).width)/2;if(e.fillStyle=t.direction===`charging`?o.mark.ok:t.direction===`discharging`?o.mark.alarm:o.muted,s){let n=r-i*.8,a=t.direction===`charging`?n:r,o=t.direction===`charging`?r:n;e.beginPath(),e.moveTo(l+s/2,a),e.lineTo(l+s,o),e.lineTo(l,o),e.closePath(),e.fill()}e.textAlign=`left`,e.fillText(t.text,l+s+c,r),e.textAlign=`center`}function T(e,t,n,r){let i=r.palette;e.clearRect(0,0,t,n),_(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=t>=n*.8,s=Math.max(10,Math.min(t*.11,n*(o?.1:.06))),c=Math.max(14,o?Math.min(t*.17,n*.2):Math.min(t*.2,n*.11)),l=Math.max(9,c*.5);e.textAlign=`center`,e.textBaseline=`top`,e.fillStyle=i.muted,v(e,r.title,s,t-2*a),e.fillText(r.title,t/2,a);let u=l*1.2,d=(+!!r.detailText+ +!!r.flow)*u,f=o?0:c+d+a,p=a+s*1.5,m=n-f-a*.5,h=Math.min(o?t*.15:t*.27,(m-p)*.4),g=h*(r.style===`3d`?.22:0)+(r.kind===`battery`?h*.22:0),y={cx:o?a+h:t/2-h*.15,rx:h,top:p+g,bottom:m-(r.style===`3d`?h*.22:0)};if(y.bottom-y.top<4)return;r.style===`flat`?C(e,y,r):S(e,y,r);let b=y.cx+h+Math.max(3,h*.12),T=e=>x(y,e);e.lineCap=`round`,e.strokeStyle=i.muted,e.lineWidth=Math.max(1,h*.03);for(let t of[.25,.5,.75])e.beginPath(),e.moveTo(b,T(t)),e.lineTo(b+h*.18,T(t)),e.stroke();e.lineWidth=Math.max(2,h*.09);for(let t of r.marks)e.strokeStyle=i.mark[t.level],e.beginPath(),e.moveTo(b,T(t.at)),e.lineTo(b+h*.32,T(t.at)),e.stroke();let E=o?b+h*.32+a*.5:a,D=t-a-E,O=E+D/2,k=c+d,A=o?(p+m+k)/2:n-a;e.textBaseline=`alphabetic`,e.fillStyle=r.fraction===void 0?i.muted:i.text,v(e,r.valueText,c,D,`bold`);let j=A-d;e.fillText(r.valueText,O,j),r.detailText&&(j+=u,e.fillStyle=i.muted,v(e,r.detailText,l,D),e.fillText(r.detailText,O,j)),r.flow&&w(e,r.flow,O,j+u,l,D,i)}var E=12e4,D=5e3;function O(_){let v=f[_],y=document.createElement(`canvas`);document.getElementById(`root`)?.append(y);let b,x=!1,S=0,C,w=!1,O=(e,t,n=!0)=>e&&(!n||t-e.time<E)?e.value:void 0;function k(e){let t=x?g:h,n=b;if(!n?.config.path)return{title:v.noun,fraction:void 0,level:`ok`,valueText:`Not set up`,detailText:``,marks:[],kind:_,style:n?.config.style??`3d`,palette:t};let r=O(n.levelReading,e),i=O(n.detailReading,e,v.detailAges),a=n.config.showDetail&&r!==void 0&&i!==void 0?`${p(v.detail(r,i),n.detail.conversion).toFixed(n.detail.decimals)} ${n.detail.symbol}`.trim():``,o=n.config.showDetail?O(n.flowReading,e):void 0,s=o===void 0?void 0:{text:`${p(Math.abs(o),n.flow.conversion).toFixed(n.flow.decimals)} ${n.flow.symbol}`.trim(),direction:c(o)};return{title:n.title,fraction:r,level:r===void 0?`ok`:u(d(r,n.zones)),valueText:r===void 0?`--`:`${p(r,n.level.conversion).toFixed(n.level.decimals)} ${n.level.symbol}`.trim(),detailText:a,flow:s,marks:l(n.zones),kind:_,style:n.config.style,palette:t}}function A(){w=!1;let e=y.getBoundingClientRect(),t=window.devicePixelRatio||1,n=Math.max(1,Math.round(e.width*t)),r=Math.max(1,Math.round(e.height*t));(y.width!==n||y.height!==r)&&(y.width=n,y.height=r);let i=y.getContext(`2d`);i&&(i.setTransform(t,0,0,t,0,0),T(i,e.width,e.height,k(Date.now())))}function j(){w||(w=!0,requestAnimationFrame(A))}async function M(e){let t=++S,n=s(await e.state.get()),c=v.matches(n.path),l=c?v.detailPath(n.path):void 0,u=c?v.flowPath?.(n.path):void 0,[d,f,p,h]=n.path?await Promise.all([i(n.path),l?i(l):void 0,l&&!v.detailAges?r(l):void 0,u?i(u):void 0]):[void 0,void 0,void 0,void 0];if(t!==S)return;let g={config:n,title:n.label||d?.displayName||v.name(n.path),zones:o(d?.zones),level:m({...d,units:d?.units??`ratio`},`percent`,0),detail:m({...f,units:f?.units??v.detailUnits},void 0,v.detailDecimals),flow:m({...h,units:h?.units??`A`},void 0,1)};typeof p==`number`&&Number.isFinite(p)&&(g.detailReading={time:Date.now(),value:p});let _;if(n.path){let r=[n.path,l,u].filter(e=>e!==void 0),i=new a;if(_=await e.signalk.subscribe(r,e=>{if(typeof e.value!=`number`||!Number.isFinite(e.value))return;let t={time:i.local(Date.now(),e.timestamp),value:e.value};e.path===n.path?g.levelReading=t:e.path===u?g.flowReading=t:g.detailReading=t,j()}),t!==S){await _().catch(()=>void 0);return}}let y=C;C=_,b=g,j(),await y?.().catch(()=>void 0)}async function N(){let r=await e();t(r),new ResizeObserver(j).observe(y),setInterval(j,D),await r.subscribe([`state.changed`],()=>{M(r).catch(e=>{console.warn(`${_}: settings reload failed`,e)})}),await n(r,e=>{x=e,j()}).catch(e=>{console.warn(`${_}: night mode unavailable`,e)}),await M(r)}N().catch(e=>{console.warn(`${_}: host connection failed`,e)})}export{O as 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),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}}export{a as i,t as n,r,i 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{n as r}from"./signalk-NgaYaNkQ.js";import{a as i,c as a,d as o,i as s,l as c,n as l,o as u,r as d,s as f,u as p}from"./steer-model-DA5-9SMr.js";import{n as m,r as h}from"./units-nn6bpgTR.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 o(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 N(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 te(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,c=Math.max(10,Math.min(t,n-o)/2-a),l={cx:t/2,cy:(n-o)/2,r:c,band:c*.16};E(e,l,r),ee(e,l,r),A(e,l,r),r.trueWind!==void 0&&r.closeHauled!==void 0&&(T(e,l,r.trueWind+r.closeHauled,i.port,!0),T(e,l,r.trueWind-r.closeHauled,i.starboard,!0)),r.waypoint!==void 0&&O(e,l,r.waypoint,i.waypoint,`dot`),r.course!==void 0&&O(e,l,r.course,i.course,`diamond`),k(e,l,r),r.trueWind!==void 0&&D(e,l,r.trueWind,i.trueWind,`T`,.22),r.apparent!==void 0&&D(e,l,r.apparent,i.apparent,`A`,.5),j(e,l,r),e.fillStyle=i.text,e.textAlign=`center`,e.textBaseline=`middle`,e.font=`700 ${Math.max(10,c*.2)}px ${b}`,e.fillText(r.heading===void 0?`---`:`${s(r.heading)}°`,l.cx,l.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),N(e,l,r,l.cy+c+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?`--`:m(t,n.conversion).toFixed(n.decimals),unit:n.symbol}}function Y(e,t){return{label:e,value:t===void 0?`--`:f(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,o=q(r,e),s=o(`heading`),l=o(`angleApparent`),u=c(o,a.trueWind),d=o(`speedOverGround`),f=o(`drift`),h=i(o(`set`),s),v=a.showShift&&s!==void 0?r.shift.range(e):void 0;return{dial:a.dial,heading:s,apparent:l,trueWind:u,shift:v&&s!==void 0?{from:v.from-s,to:v.to-s}:void 0,closeHauled:a.showTackLines?a.closeHauled*F:void 0,waypoint:a.showWaypoint?i(o(`waypoint`),s):void 0,course:a.showCourse&&d!==void 0&&d>=I?i(o(`course`),s):void 0,current:a.showCurrent&&h!==void 0&&f!==void 0&&f>=L?{angle:h,fraction:f/R,text:`${m(f,r.speed.conversion).toFixed(1)} ${r.speed.symbol}`}:void 0,rudder:a.showRudder?o(`rudder`):void 0,topLeft:J(`AWS`,o(`speedApparent`),r.speed),topRight:J(`TWS`,p(o,a.trueWind),r.speed),bottomLeft:Y(`AWA`,l),bottomRight:Y(`TWA`,u),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),te(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=c(n,e.config.trueWind);r!==void 0&&i!==void 0&&e.shift.add(t,o(r+i))}async function $(e){let t=++W,n=u(await e.state.get()),i=a(n.north),o=await r(i.speedApparent);if(t!==W)return;let s={config:n,values:new Map,shift:new l(n.shiftSeconds*1e3),clock:new d,speed:h({...o,units:o?.units??`m/s`},void 0,1)},c=new Map;for(let[e,t]of Object.entries(i))c.set(t,[...c.get(t)??[],e]);let f=await e.signalk.subscribe([...c.keys()],e=>{let t=c.get(e.path);if(!t||typeof e.value!=`number`||!Number.isFinite(e.value))return;let n=Date.now(),r=s.clock.local(n,e.timestamp);for(let n of t)s.values.set(n,{time:r,value:e.value});t.some(e=>B.has(e))&&ne(s,n),Q()});if(t!==W){await f().catch(()=>void 0);return}let p=G;G=f,H=s,Q(),await p?.().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-wmEozZnD.js";e(`tank`);
@@ -0,0 +1 @@
1
+ import"./chunk-U7YORXZ7-BLTBSYrv.js";import{t as e}from"./level-config-COx4EZHZ.js";e(`tank`);
@@ -0,0 +1 @@
1
+ var e=(e,t,n,r=0,i=0)=>({id:e,symbol:t,factor:n,offset:r,add:i}),t={rad:[e(`degree`,`°`,180/Math.PI),e(`radian`,`rad`,1)],"m/s":[e(`kn`,`kn`,1.94384),e(`m/s`,`m/s`,1),e(`km/h`,`km/h`,3.6),e(`mph`,`mph`,2.2369362920544025)],K:[e(`C`,`°C`,1,-273.15),e(`F`,`°F`,9/5,-273.15,32),e(`K`,`K`,1)],Pa:[e(`hPa`,`hPa`,.01),e(`mbar`,`mbar`,.01),e(`bar`,`bar`,1e-5),e(`psi`,`psi`,.0001450376807894691),e(`inHg`,`inHg`,.00029529987601298443),e(`mmHg`,`mmHg`,.0075006168507298),e(`Pa`,`Pa`,1)],m:[e(`m`,`m`,1),e(`meter`,`m`,1),e(`foot`,`ft`,3.280839895013124),e(`fathom`,`fathom`,.5467468562055768),e(`naut-mile`,`nmi`,.0005399568034557236),e(`kilometer`,`km`,.001),e(`mile`,`mi`,.000621371192237334)],"rad/s":[e(`deg/s`,`°/s`,57.29577951308232),e(`rpm`,`rpm`,9.549296585513723),e(`rad/s`,`rad/s`,1)],ratio:[e(`percent`,`%`,100),e(`ratio`,``,1)],Hz:[e(`rpm`,`rpm`,60),e(`hertz`,`Hz`,1)],V:[e(`V`,`V`,1)],A:[e(`A`,`A`,1)],W:[e(`watt`,`W`,1),e(`kW`,`kW`,.001)],m3:[e(`liter`,`l`,1e3),e(`gallon`,`gal`,264.1720512415585),e(`m3`,`m³`,1)],"m3/s":[e(`L/h`,`l/h`,36e5),e(`L/min`,`l/min`,6e4),e(`gal/h`,`gal/h`,264.17205236)]};function n(e,t){return t?(e+(t.offset??0))*t.factor+(t.add??0):e}function r(e){if(e)return/\.(0+)/.exec(e)?.[1]?.length??0}function i(e,n,i){let a=e?.units,o=a?t[a]??[]:[],s=e=>o.find(t=>t.id===e),c=s(n)??s(e?.displayUnits?.targetUnit)??o[0],l=a===`rad`,u=c&&c.id===e?.displayUnits?.targetUnit?r(e.displayUnits.displayFormat):void 0,d=c?.id===`degree`?0:u??1;return{conversion:c,symbol:c?.symbol??a??``,decimals:i>=0?i:d,angular:l}}export{n,i as r,t};
@@ -0,0 +1 @@
1
+ import{t as e}from"./chunk-U7YORXZ7-BLTBSYrv.js";import{i as t,n,o as r,r as i}from"./config-model-BkGUzGeC.js";import{n as a,t as o}from"./styles-CjtPzFRR.js";import{n as s}from"./signalk-NgaYaNkQ.js";import{n as c,r as l}from"./units-nn6bpgTR.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=t(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(r(a),c.angular),signed:n(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,18 @@
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-DYeoAWMp.js"></script>
8
+ <link rel="modulepreload" crossorigin href="./assets/chunk-U7YORXZ7-BLTBSYrv.js">
9
+ <link rel="modulepreload" crossorigin href="./assets/signalk-NgaYaNkQ.js">
10
+ <link rel="modulepreload" crossorigin href="./assets/level-model-CINplXoI.js">
11
+ <link rel="modulepreload" crossorigin href="./assets/units-nn6bpgTR.js">
12
+ <link rel="modulepreload" crossorigin href="./assets/level-config-COx4EZHZ.js">
13
+ <link rel="stylesheet" crossorigin href="./assets/config-DBAHPNIf.css">
14
+ </head>
15
+ <body>
16
+ <div id="root"></div>
17
+ </body>
18
+ </html>
@@ -0,0 +1,20 @@
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-BP5w2HNc.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/signalk-NgaYaNkQ.js">
11
+ <link rel="modulepreload" crossorigin href="./assets/steer-model-DA5-9SMr.js">
12
+ <link rel="modulepreload" crossorigin href="./assets/level-model-CINplXoI.js">
13
+ <link rel="modulepreload" crossorigin href="./assets/units-nn6bpgTR.js">
14
+ <link rel="modulepreload" crossorigin href="./assets/level-wmEozZnD.js">
15
+ <link rel="stylesheet" crossorigin href="./assets/styles-D6wkrAFq.css">
16
+ </head>
17
+ <body>
18
+ <div id="root"></div>
19
+ </body>
20
+ </html>
@@ -0,0 +1,16 @@
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>Clock setup</title>
7
+ <script type="module" crossorigin src="./assets/clock-config-DBFHzMXm.js"></script>
8
+ <link rel="modulepreload" crossorigin href="./assets/chunk-U7YORXZ7-BLTBSYrv.js">
9
+ <link rel="modulepreload" crossorigin href="./assets/clock-model-D8TrsRP6.js">
10
+ <link rel="modulepreload" crossorigin href="./assets/signalk-NgaYaNkQ.js">
11
+ <link rel="stylesheet" crossorigin href="./assets/config-DBAHPNIf.css">
12
+ </head>
13
+ <body>
14
+ <div id="root"></div>
15
+ </body>
16
+ </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>Clock</title>
7
+ <script type="module" crossorigin src="./assets/clock-DGa0UjBJ.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/clock-model-D8TrsRP6.js">
11
+ <link rel="modulepreload" crossorigin href="./assets/signalk-NgaYaNkQ.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,11 +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 setup</title>
7
- <script type="module" crossorigin src="./assets/config-BKRpYFX2.js"></script>
7
+ <script type="module" crossorigin src="./assets/config-4Cx9vP2h.js"></script>
8
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">
9
+ <link rel="modulepreload" crossorigin href="./assets/config-model-BkGUzGeC.js">
10
+ <link rel="modulepreload" crossorigin href="./assets/signalk-NgaYaNkQ.js">
11
+ <link rel="modulepreload" crossorigin href="./assets/units-nn6bpgTR.js">
12
+ <link rel="stylesheet" crossorigin href="./assets/config-DBAHPNIf.css">
12
13
  </head>
13
14
  <body>
14
15
  <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,11 +4,12 @@
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-DVXoKR46.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/signalk-NgaYaNkQ.js">
11
11
  <link rel="modulepreload" crossorigin href="./assets/steer-model-DA5-9SMr.js">
12
+ <link rel="modulepreload" crossorigin href="./assets/units-nn6bpgTR.js">
12
13
  <link rel="stylesheet" crossorigin href="./assets/styles-D6wkrAFq.css">
13
14
  </head>
14
15
  <body>
@@ -0,0 +1,18 @@
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-BUfMKMvt.js"></script>
8
+ <link rel="modulepreload" crossorigin href="./assets/chunk-U7YORXZ7-BLTBSYrv.js">
9
+ <link rel="modulepreload" crossorigin href="./assets/signalk-NgaYaNkQ.js">
10
+ <link rel="modulepreload" crossorigin href="./assets/level-model-CINplXoI.js">
11
+ <link rel="modulepreload" crossorigin href="./assets/units-nn6bpgTR.js">
12
+ <link rel="modulepreload" crossorigin href="./assets/level-config-COx4EZHZ.js">
13
+ <link rel="stylesheet" crossorigin href="./assets/config-DBAHPNIf.css">
14
+ </head>
15
+ <body>
16
+ <div id="root"></div>
17
+ </body>
18
+ </html>
@@ -0,0 +1,20 @@
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-B7og9w7x.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/signalk-NgaYaNkQ.js">
11
+ <link rel="modulepreload" crossorigin href="./assets/steer-model-DA5-9SMr.js">
12
+ <link rel="modulepreload" crossorigin href="./assets/level-model-CINplXoI.js">
13
+ <link rel="modulepreload" crossorigin href="./assets/units-nn6bpgTR.js">
14
+ <link rel="modulepreload" crossorigin href="./assets/level-wmEozZnD.js">
15
+ <link rel="stylesheet" crossorigin href="./assets/styles-D6wkrAFq.css">
16
+ </head>
17
+ <body>
18
+ <div id="root"></div>
19
+ </body>
20
+ </html>
@@ -4,11 +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-DjBm6Z7F.js"></script>
7
+ <script type="module" crossorigin src="./assets/widget-VvPC6-2G.js"></script>
8
8
  <link rel="modulepreload" crossorigin href="./assets/chunk-U7YORXZ7-BLTBSYrv.js">
9
- <link rel="modulepreload" crossorigin href="./assets/config-model-DvTH1O2f.js">
9
+ <link rel="modulepreload" crossorigin href="./assets/config-model-BkGUzGeC.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/signalk-NgaYaNkQ.js">
12
+ <link rel="modulepreload" crossorigin href="./assets/units-nn6bpgTR.js">
12
13
  <link rel="stylesheet" crossorigin href="./assets/styles-D6wkrAFq.css">
13
14
  </head>
14
15
  <body>
@@ -1,30 +0,0 @@
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
- <div class="chips">${i}</div>
3
- <label class="row"><span>Signal K path</span>
4
- <input id="path" list="paths" value="${o(e.path)}" placeholder="Type to search…">
5
- <datalist id="paths">${t.map(e=>`<option value="${o(e.path)}">`).join(``)}</datalist>
6
- </label>
7
- <label class="row"><span>Label</span>
8
- <input id="label" value="${o(e.label)}" placeholder="Automatic">
9
- </label>
10
- <div class="row"><span>Time span</span>
11
- <div class="pair">
12
- <input id="span" type="number" min="1" step="1" value="${e.span}">
13
- <select id="spanUnit">
14
- <option value="min"${e.spanUnit===`min`?` selected`:``}>minutes</option>
15
- <option value="h"${e.spanUnit===`h`?` selected`:``}>hours</option>
16
- </select>
17
- </div>
18
- </div>
19
- <label class="row"><span>Units</span><select id="unit"></select></label>
20
- <label class="row"><span>Decimals</span>
21
- <select id="decimals">
22
- ${[-1,0,1,2,3].map(t=>`<option value="${t}"${t===e.decimals?` selected`:``}>${t<0?`Automatic`:t}</option>`).join(``)}
23
- </select>
24
- </label>
25
- <p class="status">${a}</p>
26
- <p class="status" id="status"></p>
27
- <div class="actions">
28
- <button type="button" id="cancel">Cancel</button>
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,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 +0,0 @@
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 +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};
@@ -1 +0,0 @@
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)});