signalk-history-widgets 0.3.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 (38) hide show
  1. package/README.md +27 -6
  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/package.json +2 -2
  6. package/plugin/index.js +26 -7
  7. package/plugin/index.js.map +1 -1
  8. package/public/assets/{battery-BhqU92AD.js → battery-BP5w2HNc.js} +1 -1
  9. package/public/assets/{battery-config-CUDy2wrq.js → battery-config-DYeoAWMp.js} +1 -1
  10. package/public/assets/clock-DGa0UjBJ.js +1 -0
  11. package/public/assets/clock-config-DBFHzMXm.js +13 -0
  12. package/public/assets/clock-model-D8TrsRP6.js +1 -0
  13. package/public/assets/config-4Cx9vP2h.js +24 -0
  14. package/public/assets/{config-model-DvTH1O2f.js → config-model-BkGUzGeC.js} +1 -1
  15. package/public/assets/{level-config-B0mMmED6.js → level-config-COx4EZHZ.js} +7 -7
  16. package/public/assets/level-model-CINplXoI.js +1 -0
  17. package/public/assets/level-wmEozZnD.js +1 -0
  18. package/public/assets/signalk-NgaYaNkQ.js +1 -0
  19. package/public/assets/steer-DVXoKR46.js +1 -0
  20. package/public/assets/{tank-Gyfo7ABV.js → tank-B7og9w7x.js} +1 -1
  21. package/public/assets/{tank-config-C6CTiHir.js → tank-config-BUfMKMvt.js} +1 -1
  22. package/public/assets/units-nn6bpgTR.js +1 -0
  23. package/public/assets/widget-VvPC6-2G.js +1 -0
  24. package/public/battery-config.html +5 -4
  25. package/public/battery.html +5 -4
  26. package/public/clock-config.html +16 -0
  27. package/public/clock.html +17 -0
  28. package/public/config.html +4 -3
  29. package/public/steer.html +3 -2
  30. package/public/tank-config.html +5 -4
  31. package/public/tank.html +5 -4
  32. package/public/widget.html +4 -3
  33. package/public/assets/config-n3H05jK1.js +0 -30
  34. package/public/assets/level-CwEJ7gss.js +0 -1
  35. package/public/assets/level-model-0XbpyS2Q.js +0 -1
  36. package/public/assets/steer-BhT70PR7.js +0 -1
  37. package/public/assets/units-Cn4uhL0-.js +0 -1
  38. package/public/assets/widget-CH38Wyvl.js +0 -1
package/README.md CHANGED
@@ -10,8 +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 and **Tank level** and
14
- **Battery charge** widgets, 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).
15
15
 
16
16
  ## Use
17
17
 
@@ -22,7 +22,7 @@ The package also brings a **Wind steering** dial and **Tank level** and
22
22
  (wide)**.
23
23
  3. Pick the value: a quick button (TWD, TWS, TWA, AWA, AWS, SOG, STW, COG,
24
24
  HDG, depth, air pressure, air and water temperature) or any numeric Signal K
25
- 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.
26
26
 
27
27
  For several values, place several strips next to each other. Press and hold a
28
28
  placed strip to change it or remove it.
@@ -79,8 +79,9 @@ hidden rather than shown stale. It reads standard Signal K paths:
79
79
 
80
80
  A tank's level or a battery bank's state of charge as a glass cylinder or, if
81
81
  you prefer, a flat bar, with the percentage underneath. A tank that publishes
82
- its capacity also shows the volume left, and a battery bank its voltage, in
83
- the units set in the server's Unit Preferences. The colour follows the zones
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
84
85
  set for the level in the server's metadata (`meta.zones`): green when the
85
86
  level is normal, yellow in an alert or warn zone, red in an alarm or
86
87
  emergency zone. Marks beside the bar show where the zones begin, so you see
@@ -88,14 +89,34 @@ how far the level is from the next one. Without zones the bar stays green.
88
89
 
89
90
  ![A battery bank as a cylinder and as a flat bar, and a tank as a flat bar](docs/screenshots/levels.png)
90
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
+
91
96
  Press and hold the widget to pick another tank or bank. The panel lists every
92
97
  `tanks.*.*.currentLevel` (or every
93
98
  `electrical.batteries.*.capacity.stateOfCharge`) on the server with its
94
99
  current level, plus any other 0 to 100 % value, and offers the look (3D or
95
- flat) and whether to show the volume or voltage as buttons, so it is set up
100
+ flat) and whether to show the volume or voltage and current as buttons, so it is set up
96
101
  with a few taps and no keyboard. A level older than two minutes shows as
97
102
  `--`.
98
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
+
99
120
  ## Requirements
100
121
 
101
122
  - Signal K server 2.22.1 or newer (History API v2) on Node 24 or newer
Binary file
Binary file
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "signalk-history-widgets",
3
- "version": "0.3.0",
4
- "description": "History strip, wind steering and tank level widgets for Signal K chartplotters: any value over the last minutes or hours as a vertical strip with min, average and max, a wind steering dial with close-hauled lines and the recent wind shift, and tank levels and battery charge coloured by their zones",
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,13 +48,14 @@ var strip = (id, title, size) => ({
48
48
  configPanel: "strip-config",
49
49
  lifecycle: "whileEnabled"
50
50
  });
51
- var level = (id, title) => ({
51
+ /** A level widget; the small one shares its page and panel. */
52
+ var level = (id, title, size = "1x2", page = id) => ({
52
53
  id,
53
54
  title,
54
55
  type: "iframe",
55
- url: `${ASSET_BASE}/${id}.html`,
56
- size: "1x2",
57
- configPanel: `${id}-config`,
56
+ url: `${ASSET_BASE}/${page}.html`,
57
+ size,
58
+ configPanel: `${page}-config`,
58
59
  lifecycle: "whileEnabled"
59
60
  });
60
61
  var levelPanel = (id, title) => ({
@@ -68,7 +69,7 @@ var levelPanel = (id, title) => ({
68
69
  function buildManifest(version) {
69
70
  return {
70
71
  name: "History Widgets",
71
- description: "Any value over the last minutes or hours as a vertical strip with its minimum, average and maximum, a wind steering dial with the wind shift of the last minutes, and tank levels and battery charge coloured by their zones.",
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.",
72
73
  version,
73
74
  apiVersion: "1",
74
75
  requires: [
@@ -90,7 +91,18 @@ function buildManifest(version) {
90
91
  lifecycle: "whileEnabled"
91
92
  },
92
93
  level("tank", "Tank level"),
93
- level("battery", "Battery charge")
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"
105
+ }
94
106
  ],
95
107
  panels: [
96
108
  {
@@ -108,7 +120,14 @@ function buildManifest(version) {
108
120
  lifecycle: "onOpen"
109
121
  },
110
122
  levelPanel("tank", "Tank level"),
111
- levelPanel("battery", "Battery charge")
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
+ }
112
131
  ]
113
132
  };
114
133
  }
@@ -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\nconst level = (id: string, title: string) => ({\n id,\n title,\n type: 'iframe',\n url: `${ASSET_BASE}/${id}.html`,\n size: '1x2',\n configPanel: `${id}-config`,\n lifecycle: 'whileEnabled'\n})\n\nconst levelPanel = (id: string, title: string) => ({\n id: `${id}-config`,\n title,\n type: 'iframe',\n url: `${ASSET_BASE}/${id}-config.html`,\n lifecycle: 'onOpen'\n})\n\n/** The Plotter Extensions manifest (apiVersion 1). */\nexport function buildManifest(version: string) {\n return {\n name: 'History Widgets',\n description:\n 'Any value over the last minutes or hours as a vertical strip with its minimum, average and maximum, a wind steering dial with the wind shift of the last minutes, and tank levels and battery charge coloured by their zones.',\n version,\n apiVersion: '1',\n requires: ['widgets', 'panels.iframe', 'signalk.stream'],\n optional: ['nightMode'],\n widgets: [\n strip('strip', 'History strip', '1x2'),\n strip('strip-wide', 'History strip (wide)', '2x2'),\n {\n id: 'wind-steering',\n title: 'Wind steering',\n type: 'iframe',\n url: `${ASSET_BASE}/steer.html`,\n size: '2x2',\n configPanel: 'wind-steering-config',\n lifecycle: 'whileEnabled'\n },\n level('tank', 'Tank level'),\n level('battery', 'Battery charge')\n ],\n panels: [\n {\n id: 'strip-config',\n title: 'History strip',\n type: 'iframe',\n url: `${ASSET_BASE}/config.html`,\n lifecycle: 'onOpen'\n },\n {\n id: 'wind-steering-config',\n title: 'Wind steering',\n type: 'iframe',\n url: `${ASSET_BASE}/steer-config.html`,\n lifecycle: 'onOpen'\n },\n levelPanel('tank', 'Tank level'),\n levelPanel('battery', 'Battery charge')\n ]\n }\n}\n","import { readFileSync } from 'node:fs'\nimport { fileURLToPath } from 'node:url'\nimport type { Request, Response, NextFunction } from 'express'\nimport type { Plugin, ServerAPI } from '@signalk/server-api'\nimport { loadAssets, type Asset } from './assets.js'\nimport { ASSET_BASE, PLUGIN_ID, buildManifest } from './manifest.js'\n\nconst PUBLIC_DIR = fileURLToPath(new URL('../public', import.meta.url))\nconst PACKAGE_JSON = fileURLToPath(new URL('../package.json', import.meta.url))\n\n/** The express app the server hands plugins; ServerAPI leaves `use` out. */\ntype App = ServerAPI & {\n use?: (path: string, handler: (req: Request, res: Response, next: NextFunction) => void) => void\n}\n\nfunction packageVersion(): string {\n const pkg: unknown = JSON.parse(readFileSync(PACKAGE_JSON, 'utf8'))\n const version = (pkg as { version?: unknown }).version\n return typeof version === 'string' ? version : '0.0.0'\n}\n\nexport default function historyWidgets(app: App): Plugin {\n let running = false\n let assets = new Map<string, Asset>()\n let mounted = false\n let registered = false\n const manifest = buildManifest(packageVersion())\n\n const mount = () => {\n if (mounted || !app.use) return\n mounted = true\n // express cannot unmount a route, so a stopped plugin answers 404.\n app.use(ASSET_BASE, (req, res, next) => {\n const asset = running ? assets.get(req.path) : undefined\n if (!asset || (req.method !== 'GET' && req.method !== 'HEAD')) {\n next()\n return\n }\n res.type(asset.type).send(asset.body)\n })\n }\n\n const register = () => {\n if (registered) return\n registered = true\n app.registerResourceProvider({\n type: 'plotterExtensions',\n methods: {\n listResources: () => Promise.resolve(running ? { [PLUGIN_ID]: manifest } : {}),\n getResource: (id: string) =>\n running && id === PLUGIN_ID\n ? Promise.resolve(manifest)\n : Promise.reject(new Error(`No such plotterExtensions resource: ${id}`)),\n setResource: () => Promise.reject(new Error(`${PLUGIN_ID} is read-only`)),\n deleteResource: () => Promise.reject(new Error(`${PLUGIN_ID} is read-only`))\n }\n })\n }\n\n return {\n id: PLUGIN_ID,\n name: 'History Widgets',\n description:\n 'History strip and wind steering widgets for Freeboard-SK and other Plotter Extensions hosts',\n schema: () => ({ type: 'object', properties: {} }),\n\n start() {\n running = true\n mount()\n register()\n loadAssets(PUBLIC_DIR)\n .then((loaded) => {\n assets = loaded\n app.setPluginStatus(`Serving ${manifest.widgets.length} widgets`)\n })\n .catch((err: unknown) => {\n app.setPluginError(\n `Web pages missing: ${err instanceof Error ? err.message : String(err)}`\n )\n })\n },\n\n stop() {\n running = false\n }\n }\n}\n"],"mappings":"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"}
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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"}
@@ -1 +1 @@
1
- import"./chunk-U7YORXZ7-BLTBSYrv.js";import{t as e}from"./level-CwEJ7gss.js";e(`battery`);
1
+ import"./chunk-U7YORXZ7-BLTBSYrv.js";import{t as e}from"./level-wmEozZnD.js";e(`battery`);
@@ -1 +1 @@
1
- import"./chunk-U7YORXZ7-BLTBSYrv.js";import{t as e}from"./level-config-B0mMmED6.js";e(`battery`);
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
- 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};
@@ -1,17 +1,17 @@
1
- import{t as e}from"./chunk-U7YORXZ7-BLTBSYrv.js";import{n as t,o as n,r}from"./units-Cn4uhL0-.js";import{r as i,t as a}from"./level-model-0XbpyS2Q.js";/* empty css */function o(e){return e.replace(/[&<>"]/g,e=>`&#${e.charCodeAt(0)};`)}function s(e){let t=document.getElementById(e);if(!t)throw Error(`missing #${e}`);return t}function c(e){let t=s(e);if(t instanceof HTMLInputElement)return t;throw Error(`#${e} is not an input`)}var l=r({units:`ratio`},`percent`,0);function u(e,n,r){let i=e.matches(n.path)?n.displayName||e.name(n.path):n.path;return`<button type="button" class="pick${n.path===r?` on`:``}" aria-pressed="${n.path===r}" data-path="${o(n.path)}" title="${o(n.path)}">
2
- <span class="name">${o(i)}</span>
3
- <span class="reading">${t(n.value,l.conversion).toFixed(l.decimals)} ${l.symbol}</span>
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
4
  </button>`}function d(e,t,n,r){return n.length===0?``:`<div class="section-title">${t}</div>
5
- <div class="picks">${n.map(t=>u(e,t,r)).join(``)}</div>`}function f(e,t){e.classList.toggle(`on`,t),e.setAttribute(`aria-pressed`,String(t))}function p(e,t,n){return`<div class="chips">${t.map(([t,r])=>`<button type="button" class="chip${t===n?` on`:``}" aria-pressed="${t===n}" data-${e}="${t}">${r}</button>`).join(``)}</div>`}function m(e,t){let n=document.querySelectorAll(`[data-${e}]`);for(let r of n)r.addEventListener(`click`,()=>{for(let e of n)f(e,e===r);t(r.dataset[e]??``)})}async function h(t){let r=a[t],l=s(`root`),u=await e();l.innerHTML=`<p class="status">Loading…</p>`;let[h,g]=await Promise.all([u.state.get(),n()]),_=i(h),v=g.filter(e=>e.units===`ratio`||r.matches(e.path)),y=v.filter(e=>r.matches(e.path)),b=v.filter(e=>!r.matches(e.path)),{path:x,style:S,showDetail:C}=_;l.innerHTML=`
6
- ${d(r,r.plural,y,x)}
7
- ${d(r,`Other levels`,b,x)}
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
8
  ${v.length===0?`<p class="status">No levels on this server yet.</p>`:``}
9
9
  <label class="row"><span>Label</span>
10
10
  <input id="label" value="${o(_.label)}" placeholder="Automatic">
11
11
  </label>
12
12
  <div class="section-title">Look</div>
13
13
  ${p(`look`,[[`3d`,`3D`],[`flat`,`Flat`]],S)}
14
- <div class="section-title">${r.detailTitle}</div>
14
+ <div class="section-title">${a.detailTitle}</div>
15
15
  ${p(`detail`,[[`true`,`Show`],[`false`,`Hide`]],String(C))}
16
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
17
  <p class="status" id="status"></p>
@@ -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)});
@@ -1 +1 @@
1
- import"./chunk-U7YORXZ7-BLTBSYrv.js";import{t as e}from"./level-CwEJ7gss.js";e(`tank`);
1
+ import"./chunk-U7YORXZ7-BLTBSYrv.js";import{t as e}from"./level-wmEozZnD.js";e(`tank`);
@@ -1 +1 @@
1
- import"./chunk-U7YORXZ7-BLTBSYrv.js";import{t as e}from"./level-config-B0mMmED6.js";e(`tank`);
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)});
@@ -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>Battery setup</title>
7
- <script type="module" crossorigin src="./assets/battery-config-CUDy2wrq.js"></script>
7
+ <script type="module" crossorigin src="./assets/battery-config-DYeoAWMp.js"></script>
8
8
  <link rel="modulepreload" crossorigin href="./assets/chunk-U7YORXZ7-BLTBSYrv.js">
9
- <link rel="modulepreload" crossorigin href="./assets/units-Cn4uhL0-.js">
10
- <link rel="modulepreload" crossorigin href="./assets/level-model-0XbpyS2Q.js">
11
- <link rel="modulepreload" crossorigin href="./assets/level-config-B0mMmED6.js">
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">
12
13
  <link rel="stylesheet" crossorigin href="./assets/config-DBAHPNIf.css">
13
14
  </head>
14
15
  <body>
@@ -4,13 +4,14 @@
4
4
  <meta charset="utf-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1" />
6
6
  <title>Battery</title>
7
- <script type="module" crossorigin src="./assets/battery-BhqU92AD.js"></script>
7
+ <script type="module" crossorigin src="./assets/battery-BP5w2HNc.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-Cn4uhL0-.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/level-model-0XbpyS2Q.js">
13
- <link rel="modulepreload" crossorigin href="./assets/level-CwEJ7gss.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">
14
15
  <link rel="stylesheet" crossorigin href="./assets/styles-D6wkrAFq.css">
15
16
  </head>
16
17
  <body>
@@ -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,10 +4,11 @@
4
4
  <meta charset="utf-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1" />
6
6
  <title>History strip setup</title>
7
- <script type="module" crossorigin src="./assets/config-n3H05jK1.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-Cn4uhL0-.js">
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">
11
12
  <link rel="stylesheet" crossorigin href="./assets/config-DBAHPNIf.css">
12
13
  </head>
13
14
  <body>
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-BhT70PR7.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-Cn4uhL0-.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>
@@ -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>Tank setup</title>
7
- <script type="module" crossorigin src="./assets/tank-config-C6CTiHir.js"></script>
7
+ <script type="module" crossorigin src="./assets/tank-config-BUfMKMvt.js"></script>
8
8
  <link rel="modulepreload" crossorigin href="./assets/chunk-U7YORXZ7-BLTBSYrv.js">
9
- <link rel="modulepreload" crossorigin href="./assets/units-Cn4uhL0-.js">
10
- <link rel="modulepreload" crossorigin href="./assets/level-model-0XbpyS2Q.js">
11
- <link rel="modulepreload" crossorigin href="./assets/level-config-B0mMmED6.js">
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">
12
13
  <link rel="stylesheet" crossorigin href="./assets/config-DBAHPNIf.css">
13
14
  </head>
14
15
  <body>
package/public/tank.html CHANGED
@@ -4,13 +4,14 @@
4
4
  <meta charset="utf-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1" />
6
6
  <title>Tank</title>
7
- <script type="module" crossorigin src="./assets/tank-Gyfo7ABV.js"></script>
7
+ <script type="module" crossorigin src="./assets/tank-B7og9w7x.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-Cn4uhL0-.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/level-model-0XbpyS2Q.js">
13
- <link rel="modulepreload" crossorigin href="./assets/level-CwEJ7gss.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">
14
15
  <link rel="stylesheet" crossorigin href="./assets/styles-D6wkrAFq.css">
15
16
  </head>
16
17
  <body>
@@ -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-CH38Wyvl.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-Cn4uhL0-.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-Cn4uhL0-.js";/* empty css */function o(e){return e.replace(/[&<>"]/g,e=>`&#${e.charCodeAt(0)};`)}function s(e){let t=document.getElementById(e);if(!t)throw Error(`missing #${e}`);return t}function c(e){let t=s(e);if(t instanceof HTMLInputElement||t instanceof HTMLSelectElement)return t;throw Error(`#${e} is not a form field`)}function l(e,t,r){let i=n.map(t=>`<button type="button" class="chip${t.path===e.path?` on`:``}" data-path="${t.path}" title="${o(t.title)}">${t.label}</button>`).join(``),a=r.length>0?`History from ${o(r.join(`, `))}: the strip fills at once.`:`No history provider on the server: the strip fills from live data. Install one (for example KIP, signalk-parquet or signalk-to-influxdb2) to see the past window at once.`;return`
2
- <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,s as o}from"./units-Cn4uhL0-.js";import{r as s}from"./steer-model-DA5-9SMr.js";import{a as c,i as l,n as u,o as d,r as f,t as p}from"./level-model-0XbpyS2Q.js";var m={card:`rgba(20, 26, 32, 0.78)`,text:`#e8edf2`,muted:`#9fb3c4`,glass:`rgba(255, 255, 255, 0.07)`,glassEdge:`rgba(207, 216, 220, 0.75)`,glint:`rgba(255, 255, 255, 0.28)`,liquid:{ok:{edge:`#1b5e20`,middle:`#66bb6a`,surface:`#a5d6a7`},warn:{edge:`#e65100`,middle:`#fdd835`,surface:`#fff59d`},alarm:{edge:`#7f0000`,middle:`#e53935`,surface:`#ef9a9a`}},mark:{ok:`#66bb6a`,warn:`#fdd835`,alarm:`#e53935`}},h={card:`rgba(8, 0, 0, 0.85)`,text:`#d32f2f`,muted:`#8e2424`,glass:`rgba(120, 20, 20, 0.08)`,glassEdge:`rgba(110, 24, 24, 0.9)`,glint:`rgba(160, 40, 40, 0.18)`,liquid:{ok:{edge:`#0b2a0d`,middle:`#2e5d31`,surface:`#3d7340`},warn:{edge:`#3d2600`,middle:`#7a6a10`,surface:`#8f7d1c`},alarm:{edge:`#3a0000`,middle:`#8e1b1b`,surface:`#b02a2a`}},mark:{ok:`#2e5d31`,warn:`#7a6a10`,alarm:`#b02a2a`}};function g(e,t,n,r,i,a){e.beginPath(),e.moveTo(t+a,n),e.arcTo(t+r,n,t+r,n+i,a),e.arcTo(t+r,n+i,t,n+i,a),e.arcTo(t,n+i,t,n,a),e.arcTo(t,n,t+r,n,a),e.closePath()}function _(e,t,n,r,i=``){let a=n;for(e.font=`${i} ${a}px 'Helvetica Neue', Arial, sans-serif`;a>8&&e.measureText(t).width>r;)--a,e.font=`${i} ${a}px 'Helvetica Neue', Arial, sans-serif`}function v(e,t,n,r,i,a){e.beginPath(),e.moveTo(t-n,i),e.lineTo(t-n,a),e.ellipse(t,a,n,r,0,Math.PI,0,!0),e.lineTo(t+n,i),e.ellipse(t,i,n,r,0,0,Math.PI,!1),e.closePath()}function y(e,t,n,r,i){let a=e.createLinearGradient(t-n,0,t+n,0);return a.addColorStop(0,r),a.addColorStop(.35,i),a.addColorStop(.55,i),a.addColorStop(1,r),a}function b(e,t){return e.bottom-Math.min(1,Math.max(0,t))*(e.bottom-e.top)}function x(e,t,n){let r=n.palette,{cx:i,rx:a,top:o,bottom:s}=t,c=a*.22;if(v(e,i,a,c,o,s),e.fillStyle=r.glass,e.fill(),n.fraction!==void 0){let o=r.liquid[n.level],l=b(t,n.fraction);v(e,i,a,c,l,s),e.fillStyle=y(e,i,a,o.edge,o.middle),e.fill(),e.beginPath(),e.ellipse(i,l,a,c,0,0,Math.PI*2),e.fillStyle=o.surface,e.fill()}let l=e.createLinearGradient(i-a*.7,0,i-a*.35,0);if(l.addColorStop(0,`rgba(255, 255, 255, 0)`),l.addColorStop(.5,r.glint),l.addColorStop(1,`rgba(255, 255, 255, 0)`),e.fillStyle=l,e.fillRect(i-a*.7,o+c,a*.35,s-o-c),e.lineWidth=Math.max(1,a*.04),e.strokeStyle=r.glassEdge,v(e,i,a,c,o,s),e.stroke(),e.beginPath(),e.ellipse(i,o,a,c,0,0,Math.PI*2),e.stroke(),n.kind===`battery`){let t=a*.32,n=t*.22,s=a*.22;v(e,i,t,n,o-s,o),e.fillStyle=y(e,i,t,r.glassEdge,r.text),e.fill(),e.beginPath(),e.ellipse(i,o-s,t,n,0,0,Math.PI*2),e.fillStyle=r.text,e.fill()}}function S(e,t,n){let r=n.palette,{cx:i,rx:a,top:o,bottom:s}=t,c=Math.min(a*.25,8),l=Math.max(1,a*.04);if(g(e,i-a,o,2*a,s-o,c),e.fillStyle=r.glass,e.fill(),n.fraction!==void 0){let l=b(t,n.fraction);e.save(),g(e,i-a,o,2*a,s-o,c),e.clip(),e.fillStyle=r.liquid[n.level].middle,e.fillRect(i-a,l,2*a,s-l),e.restore()}if(e.lineWidth=l,e.strokeStyle=r.glassEdge,g(e,i-a,o,2*a,s-o,c),e.stroke(),n.kind===`battery`){let t=a*.6,n=a*.18;g(e,i-t/2,o-n,t,n,Math.min(n/2,3)),e.fillStyle=r.glassEdge,e.fill()}}function C(e,t,n,r){let i=r.palette;e.clearRect(0,0,t,n),g(e,0,0,t,n,Math.min(10,t/10)),e.fillStyle=i.card,e.fill();let a=Math.max(6,Math.min(t,n)*.06),o=Math.max(10,Math.min(t*.11,n*.06)),s=Math.max(14,Math.min(t*.2,n*.11)),c=Math.max(9,s*.5);e.textAlign=`center`,e.textBaseline=`top`,e.fillStyle=i.muted,_(e,r.title,o,t-2*a),e.fillText(r.title,t/2,a);let l=s+(r.detailText?c*1.2:0)+a,u=a+o*1.5,d=n-l-a*.5,f=Math.min(t*.27,(d-u)*.4),p=f*(r.style===`3d`?.22:0)+(r.kind===`battery`?f*.22:0),m={cx:t/2-f*.15,rx:f,top:u+p,bottom:d-(r.style===`3d`?f*.22:0)};if(m.bottom-m.top<4)return;r.style===`flat`?S(e,m,r):x(e,m,r);let h=m.cx+f+Math.max(3,f*.12),v=e=>b(m,e);e.lineCap=`round`,e.strokeStyle=i.muted,e.lineWidth=Math.max(1,f*.03);for(let t of[.25,.5,.75])e.beginPath(),e.moveTo(h,v(t)),e.lineTo(h+f*.18,v(t)),e.stroke();e.lineWidth=Math.max(2,f*.09);for(let t of r.marks)e.strokeStyle=i.mark[t.level],e.beginPath(),e.moveTo(h,v(t.at)),e.lineTo(h+f*.32,v(t.at)),e.stroke();e.textBaseline=`alphabetic`,e.fillStyle=r.fraction===void 0?i.muted:i.text,_(e,r.valueText,s,t-2*a,`bold`);let y=n-a-(r.detailText?c*1.2:0);e.fillText(r.valueText,t/2,y),r.detailText&&(e.fillStyle=i.muted,_(e,r.detailText,c,t-2*a),e.fillText(r.detailText,t/2,n-a))}var w=12e4,T=5e3;function E(g){let _=p[g],v=document.createElement(`canvas`);document.getElementById(`root`)?.append(v);let y,b=!1,x=0,S,E=!1,D=(e,t,n=!0)=>e&&(!n||t-e.time<w)?e.value:void 0;function O(e){let t=b?h:m,n=y;if(!n?.config.path)return{title:_.noun,fraction:void 0,level:`ok`,valueText:`Not set up`,detailText:``,marks:[],kind:g,style:n?.config.style??`3d`,palette:t};let r=D(n.levelReading,e),a=D(n.detailReading,e,_.detailAges),o=n.config.showDetail&&r!==void 0&&a!==void 0?`${i(_.detail(r,a),n.detail.conversion).toFixed(n.detail.decimals)} ${n.detail.symbol}`.trim():``;return{title:n.title,fraction:r,level:r===void 0?`ok`:u(d(r,n.zones)),valueText:r===void 0?`--`:`${i(r,n.level.conversion).toFixed(n.level.decimals)} ${n.level.symbol}`.trim(),detailText:o,marks:c(n.zones),kind:g,style:n.config.style,palette:t}}function k(){E=!1;let e=v.getBoundingClientRect(),t=window.devicePixelRatio||1,n=Math.max(1,Math.round(e.width*t)),r=Math.max(1,Math.round(e.height*t));(v.width!==n||v.height!==r)&&(v.width=n,v.height=r);let i=v.getContext(`2d`);i&&(i.setTransform(t,0,0,t,0,0),C(i,e.width,e.height,O(Date.now())))}function A(){E||(E=!0,requestAnimationFrame(k))}async function j(e){let t=++x,n=f(await e.state.get()),i=_.matches(n.path)?_.detailPath(n.path):void 0,[c,u,d]=n.path?await Promise.all([r(n.path),i?r(i):void 0,i&&!_.detailAges?o(i):void 0]):[void 0,void 0,void 0];if(t!==x)return;let p={config:n,title:n.label||c?.displayName||_.name(n.path),zones:l(c?.zones),level:a({...c,units:c?.units??`ratio`},`percent`,0),detail:a({...u,units:u?.units??_.detailUnits},void 0,_.detailDecimals)};typeof d==`number`&&Number.isFinite(d)&&(p.detailReading={time:Date.now(),value:d});let m;if(n.path){let r=i?[n.path,i]:[n.path],a=new s;if(m=await e.signalk.subscribe(r,e=>{if(typeof e.value!=`number`||!Number.isFinite(e.value))return;let t={time:a.local(Date.now(),e.timestamp),value:e.value};e.path===n.path?p.levelReading=t:p.detailReading=t,A()}),t!==x){await m().catch(()=>void 0);return}}let h=S;S=m,y=p,A(),await h?.().catch(()=>void 0)}async function M(){let r=await e();t(r),new ResizeObserver(A).observe(v),setInterval(A,T),await r.subscribe([`state.changed`],()=>{j(r).catch(e=>{console.warn(`${g}: settings reload failed`,e)})}),await n(r,e=>{b=e,A()}).catch(e=>{console.warn(`${g}: night mode unavailable`,e)}),await j(r)}M().catch(e=>{console.warn(`${g}: host connection failed`,e)})}export{E as t};
@@ -1 +0,0 @@
1
- var e={path:``,label:``,style:`3d`,showDetail:!0};function t(t){let n=typeof t==`object`&&t?t:{};return{path:typeof n.path==`string`?n.path:e.path,label:typeof n.label==`string`?n.label:e.label,style:n.style===`flat`||n.style===`3d`?n.style:e.style,showDetail:typeof n.showDetail==`boolean`?n.showDetail:e.showDetail}}var n=/^tanks\.([^.]+)\.([^.]+)\.currentLevel$/,r=/^electrical\.batteries\.([^.]+)\.capacity\.stateOfCharge$/,i={fuel:`Fuel`,freshWater:`Fresh water`,wasteWater:`Grey water`,blackWater:`Black water`,lubrication:`Oil`,liveWell:`Live well`,baitWell:`Bait well`,ballast:`Ballast`,gas:`Gas`},a={tank:{noun:`Tank`,plural:`Tanks`,matches:e=>n.test(e),name:e=>{let t=n.exec(e);if(!t)return e;let[,r=``,a=``]=t;return`${i[r]??r} ${a}`.trim()},detailPath:e=>e.replace(/\.currentLevel$/,`.capacity`),detail:(e,t)=>e*t,detailUnits:`m3`,detailDecimals:0,detailAges:!1,detailTitle:`Volume left (needs the tank's capacity)`},battery:{noun:`Battery`,plural:`Battery banks`,matches:e=>r.test(e),name:e=>{let t=r.exec(e)?.[1];return t===void 0?e:`Battery ${t}`},detailPath:e=>e.replace(/\.capacity\.stateOfCharge$/,`.voltage`),detail:(e,t)=>t,detailUnits:`V`,detailDecimals:1,detailAges:!0,detailTitle:`Voltage (needs the bank's voltage)`}},o={nominal:0,normal:0,alert:1,warn:2,alarm:3,emergency:4};function s(e){return typeof e==`string`&&e in o}function c(e){if(!Array.isArray(e))return[];let t=[];for(let n of e){if(typeof n!=`object`||!n)continue;let e=n;if(!s(e.state))continue;let r={state:e.state};typeof e.lower==`number`&&(r.lower=e.lower),typeof e.upper==`number`&&(r.upper=e.upper),typeof e.message==`string`&&(r.message=e.message),t.push(r)}return t}function l(e,t){let n=`normal`;for(let r of t)r.lower!==void 0&&e<r.lower||r.upper!==void 0&&e>r.upper||o[r.state]>o[n]&&(n=r.state);return n}function u(e){let t=o[e];return t>=3?`alarm`:t>=1?`warn`:`ok`}function d(e){let t={ok:0,warn:1,alarm:2},n=new Map;for(let r of e){let e=u(r.state);if(e!==`ok`)for(let i of[r.lower,r.upper]){if(i===void 0||i<=0||i>=1)continue;let r=n.get(i);(!r||t[e]>t[r])&&n.set(i,e)}}return[...n].map(([e,t])=>({at:e,level:t})).sort((e,t)=>e.at-t.at)}export{d as a,c as i,u as n,l as o,t as r,a as t};
@@ -1 +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-Cn4uhL0-.js";import{a as o,c as s,d as c,i as l,l as u,n as d,o as f,r as p,s as m,u as h}from"./steer-model-DA5-9SMr.js";var g={card:`rgba(20, 26, 32, 0.78)`,ring:`#cfd8dc`,ringText:`#1d2329`,text:`#e8edf2`,muted:`#9fb3c4`,port:`#e53935`,starboard:`#43a047`,apparent:`#29b6f6`,trueWind:`#ffa726`,shift:`rgba(255, 167, 38, 0.28)`,waypoint:`#ab47bc`,course:`#ec407a`,current:`#26c6da`},_={card:`rgba(8, 0, 0, 0.85)`,ring:`#4a0c0c`,ringText:`#d32f2f`,text:`#d32f2f`,muted:`#8e2424`,port:`#b71c1c`,starboard:`#7f1212`,apparent:`#ff5252`,trueWind:`#d32f2f`,shift:`rgba(211, 47, 47, 0.22)`,waypoint:`#c62828`,course:`#e57373`,current:`#ef9a9a`},v=Math.PI/180,y=35*v,b=`'Helvetica Neue', Arial, sans-serif`;function x(e,t,n){return[e.cx+n*Math.sin(t),e.cy-n*Math.cos(t)]}function S(e){return e-Math.PI/2}function C(e,t){return c(t-e)}function ee(e,t,n){let r=n.palette,i=t.r-t.band/2;e.lineWidth=t.band,e.strokeStyle=r.ring,e.beginPath(),e.arc(t.cx,t.cy,i,0,2*Math.PI),e.stroke(),e.lineWidth=t.band*.2;for(let[n,i,a]of[[r.port,-60*v,-20*v],[r.starboard,20*v,60*v]])e.strokeStyle=n,e.beginPath(),e.arc(t.cx,t.cy,t.r-t.band*.9,S(i),S(a)),e.stroke();let a=n.dial===`compass`?n.heading:0;if(e.strokeStyle=r.ringText,e.fillStyle=r.ringText,e.textAlign=`center`,e.textBaseline=`middle`,e.font=`600 ${Math.max(8,t.band*.5)}px ${b}`,a!==void 0)for(let r=0;r<360;r+=10){let i=r*v-a,o=r%30==0;e.lineWidth=o?2:1;let[s,c]=x(t,i,t.r),[l,u]=x(t,i,t.r-t.band*(o?.28:.18));if(e.beginPath(),e.moveTo(s,c),e.lineTo(l,u),e.stroke(),!o)continue;let d=n.dial===`compass`?w(r):String(r>180?360-r:r),[f,p]=x(t,i,t.r-t.band*.62);e.save(),e.translate(f,p),e.rotate(i),e.fillText(d,0,0),e.restore()}}function w(e){return{0:`N`,90:`E`,180:`S`,270:`W`}[e]??String(e)}function T(e,t,n,r,i){let[a,o]=x(t,n,t.r-t.band);e.strokeStyle=r,e.lineWidth=Math.max(1.5,t.r*.012),e.setLineDash(i?[t.r*.05,t.r*.04]:[]),e.beginPath(),e.moveTo(t.cx,t.cy),e.lineTo(a,o),e.stroke(),e.setLineDash([])}function E(e,t,n){if(!n.shift)return;e.fillStyle=n.palette.shift,e.beginPath(),e.moveTo(t.cx,t.cy);let r=S(n.shift.from);e.arc(t.cx,t.cy,t.r-t.band,r,r+C(n.shift.from,n.shift.to)),e.closePath(),e.fill()}function D(e,t,n,r,i,a){let o=t.r-t.band,s=x(t,n,o*(1-a)),c=t.r*.07,l=x(t,n,o),u=[l[0]-c*Math.cos(n),l[1]-c*Math.sin(n)],d=[l[0]+c*Math.cos(n),l[1]+c*Math.sin(n)];e.fillStyle=r,e.beginPath(),e.moveTo(s[0],s[1]),e.lineTo(u[0],u[1]),e.lineTo(d[0],d[1]),e.closePath(),e.fill();let[f,p]=x(t,n,o-t.r*.07);e.fillStyle=`#10161b`,e.font=`700 ${Math.max(8,t.r*.075)}px ${b}`,e.textAlign=`center`,e.textBaseline=`middle`,e.fillText(i,f,p)}function O(e,t,n,r,i){let[a,o]=x(t,n,t.r-t.band/2),s=t.band*.32;e.fillStyle=r,e.strokeStyle=`#10161b`,e.lineWidth=1.5,e.beginPath(),i===`dot`?e.arc(a,o,s,0,2*Math.PI):(e.moveTo(a+s*Math.sin(n),o-s*Math.cos(n)),e.lineTo(a+s*Math.cos(n),o+s*Math.sin(n)),e.lineTo(a-s*Math.sin(n),o+s*Math.cos(n)),e.lineTo(a-s*Math.cos(n),o-s*Math.sin(n)),e.closePath()),e.fill(),e.stroke()}function k(e,t,n){let r=n.current;if(!r)return;let i=(t.r-t.band)*(.25+.45*Math.min(1,r.fraction)),[a,o]=x(t,r.angle,i),s=t.r*.06;e.strokeStyle=n.palette.current,e.fillStyle=n.palette.current,e.lineWidth=Math.max(2,t.r*.02),e.beginPath(),e.moveTo(t.cx,t.cy),e.lineTo(a,o),e.stroke();let[c,l]=x(t,r.angle,i-s*1.6);e.beginPath(),e.moveTo(a,o),e.lineTo(c+s*Math.cos(r.angle),l+s*Math.sin(r.angle)),e.lineTo(c-s*Math.cos(r.angle),l-s*Math.sin(r.angle)),e.closePath(),e.fill();let[u,d]=x(t,r.angle,i+t.r*.09);e.font=`600 ${Math.max(8,t.r*.075)}px ${b}`,e.textAlign=`center`,e.textBaseline=`middle`,e.fillText(r.text,u,d)}function A(e,t,n){let r=t.r*.38,i=r*.34;e.strokeStyle=n.palette.muted,e.lineWidth=1.5,e.beginPath(),e.moveTo(t.cx,t.cy-r),e.bezierCurveTo(t.cx+i*1.1,t.cy-r*.45,t.cx+i,t.cy+r*.5,t.cx+i*.7,t.cy+r*.8),e.lineTo(t.cx-i*.7,t.cy+r*.8),e.bezierCurveTo(t.cx-i,t.cy+r*.5,t.cx-i*1.1,t.cy-r*.45,t.cx,t.cy-r),e.stroke()}function j(e,t,n){let r=t.band*.45,i=t.cy-t.r;e.fillStyle=n.palette.text,e.beginPath(),e.moveTo(t.cx,i+r),e.lineTo(t.cx-r*.7,i-r*.25),e.lineTo(t.cx+r*.7,i-r*.25),e.closePath(),e.fill()}function M(e,t,n,r,i,a,o,s){e.textAlign=i,e.textBaseline=`top`;let c=a*.5,l=s?r-a-c-2:r,u=s?r-a:r+c+2;e.fillStyle=o.muted,e.font=`600 ${c}px ${b}`,e.fillText(t.unit?`${t.label} ${t.unit}`:t.label,n,l),e.fillStyle=o.text,e.font=`700 ${a}px ${b}`,e.fillText(t.value,n,u)}function te(e,t,n,r){if(n.rudder===void 0)return;let i=t.r*.55,a=Math.max(4,t.r*.05);e.fillStyle=n.palette.ring,e.globalAlpha=.35,e.fillRect(t.cx-i,r,i*2,a),e.globalAlpha=1;let o=Math.max(-1,Math.min(1,n.rudder/y));e.fillStyle=o<0?n.palette.port:n.palette.starboard,e.fillRect(Math.min(t.cx,t.cx+o*i),r,Math.abs(o)*i,a),e.fillStyle=n.palette.text,e.fillRect(t.cx-1,r-2,2,a+4)}function N(e,t,n,r){let i=r.palette;e.clearRect(0,0,t,n),e.fillStyle=i.card,e.beginPath(),e.roundRect(0,0,t,n,8),e.fill();let a=Math.max(4,Math.min(t,n)*.03),o=r.rudder===void 0?0:Math.min(t,n)*.06,s=Math.max(10,Math.min(t,n-o)/2-a),c={cx:t/2,cy:(n-o)/2,r:s,band:s*.16};E(e,c,r),ee(e,c,r),A(e,c,r),r.trueWind!==void 0&&r.closeHauled!==void 0&&(T(e,c,r.trueWind+r.closeHauled,i.port,!0),T(e,c,r.trueWind-r.closeHauled,i.starboard,!0)),r.waypoint!==void 0&&O(e,c,r.waypoint,i.waypoint,`dot`),r.course!==void 0&&O(e,c,r.course,i.course,`diamond`),k(e,c,r),r.trueWind!==void 0&&D(e,c,r.trueWind,i.trueWind,`T`,.22),r.apparent!==void 0&&D(e,c,r.apparent,i.apparent,`A`,.5),j(e,c,r),e.fillStyle=i.text,e.textAlign=`center`,e.textBaseline=`middle`,e.font=`700 ${Math.max(10,s*.2)}px ${b}`,e.fillText(r.heading===void 0?`---`:`${l(r.heading)}°`,c.cx,c.cy);let u=Math.max(10,Math.min(t,n)*.075);M(e,r.topLeft,a,a,`left`,u,i,!1),M(e,r.topRight,t-a,a,`right`,u,i,!1),M(e,r.bottomLeft,a,n-a,`left`,u,i,!0),M(e,r.bottomRight,t-a,n-a,`right`,u,i,!0),te(e,c,r,c.cy+s+a*.6)}var P=1e4,F=Math.PI/180,I=.1,L=.05,R=1,z=1e3,B=new Set([`angleTrueWater`,`angleTrueGround`,`directionTrue`]),V=document.createElement(`canvas`);document.getElementById(`root`)?.append(V);var H,U=!1,W=0,G,K=!1;function q(e,t){return n=>{let r=e.values.get(n);return r&&t-r.time<P?r.value:void 0}}function J(e,t,n){return{label:e,value:t===void 0?`--`:i(t,n.conversion).toFixed(n.decimals),unit:n.symbol}}function Y(e,t){return{label:e,value:t===void 0?`--`:m(t),unit:``}}function X(e){let t=U?_:g,n={label:``,value:``,unit:``},r=H;if(!r)return{dial:`compass`,topLeft:n,topRight:n,bottomLeft:n,bottomRight:n,palette:t};let a=r.config,s=q(r,e),c=s(`heading`),l=s(`angleApparent`),d=u(s,a.trueWind),f=s(`speedOverGround`),p=s(`drift`),m=o(s(`set`),c),v=a.showShift&&c!==void 0?r.shift.range(e):void 0;return{dial:a.dial,heading:c,apparent:l,trueWind:d,shift:v&&c!==void 0?{from:v.from-c,to:v.to-c}:void 0,closeHauled:a.showTackLines?a.closeHauled*F:void 0,waypoint:a.showWaypoint?o(s(`waypoint`),c):void 0,course:a.showCourse&&f!==void 0&&f>=I?o(s(`course`),c):void 0,current:a.showCurrent&&m!==void 0&&p!==void 0&&p>=L?{angle:m,fraction:p/R,text:`${i(p,r.speed.conversion).toFixed(1)} ${r.speed.symbol}`}:void 0,rudder:a.showRudder?s(`rudder`):void 0,topLeft:J(`AWS`,s(`speedApparent`),r.speed),topRight:J(`TWS`,h(s,a.trueWind),r.speed),bottomLeft:Y(`AWA`,l),bottomRight:Y(`TWA`,d),palette:t}}function Z(){K=!1;let e=V.getBoundingClientRect(),t=window.devicePixelRatio||1,n=Math.max(1,Math.round(e.width*t)),r=Math.max(1,Math.round(e.height*t));(V.width!==n||V.height!==r)&&(V.width=n,V.height=r);let i=V.getContext(`2d`);i&&(i.setTransform(t,0,0,t,0,0),N(i,e.width,e.height,X(Date.now())))}function Q(){K||(K=!0,requestAnimationFrame(Z))}function ne(e,t){let n=q(e,t),r=n(`heading`),i=u(n,e.config.trueWind);r!==void 0&&i!==void 0&&e.shift.add(t,c(r+i))}async function $(e){let t=++W,n=f(await e.state.get()),i=s(n.north),o=await r(i.speedApparent);if(t!==W)return;let c={config:n,values:new Map,shift:new d(n.shiftSeconds*1e3),clock:new p,speed:a({...o,units:o?.units??`m/s`},void 0,1)},l=new Map;for(let[e,t]of Object.entries(i))l.set(t,[...l.get(t)??[],e]);let u=await e.signalk.subscribe([...l.keys()],e=>{let t=l.get(e.path);if(!t||typeof e.value!=`number`||!Number.isFinite(e.value))return;let n=Date.now(),r=c.clock.local(n,e.timestamp);for(let n of t)c.values.set(n,{time:r,value:e.value});t.some(e=>B.has(e))&&ne(c,n),Q()});if(t!==W){await u().catch(()=>void 0);return}let m=G;G=u,H=c,Q(),await m?.().catch(()=>void 0)}async function re(){let r=await e();t(r),new ResizeObserver(Q).observe(V),setInterval(Q,z),await r.subscribe([`state.changed`],()=>{$(r).catch(e=>{console.warn(`wind steering: settings reload failed`,e)})}),await n(r,e=>{U=e,Q()}).catch(e=>{console.warn(`wind steering: night mode unavailable`,e)}),await $(r)}re().catch(e=>{console.warn(`wind steering: host connection failed`,e)});
@@ -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),typeof r.displayName==`string`&&(i.displayName=r.displayName),Array.isArray(r.zones)&&(i.zones=r.zones);let a=r.displayUnits;return e(a)&&(i.displayUnits={},typeof a.targetUnit==`string`&&(i.displayUnits.targetUnit=a.targetUnit),typeof a.displayFormat==`string`&&(i.displayUnits.displayFormat=a.displayFormat)),i}catch{return}}function n(t,r=``,i=[]){if(!e(t))return i;if(`value`in t){if(typeof t.value==`number`){let n=t.meta;i.push({path:r,units:e(n)&&typeof n.units==`string`?n.units:void 0,value:t.value,displayName:e(n)&&typeof n.displayName==`string`?n.displayName:void 0})}return i}for(let[e,a]of Object.entries(t))e!==`meta`&&e!==`$source`&&e!==`timestamp`&&e!==`values`&&n(a,r?`${r}.${e}`:e,i);return i}async function r(){try{let e=await fetch(`/signalk/v1/api/vessels/self`,{credentials:`include`});return e.ok?n(await e.json()).sort((e,t)=>e.path.localeCompare(t.path)):[]}catch{return[]}}async function i(){try{let t=await fetch(`/signalk/v2/api/history/_providers`,{credentials:`include`});if(!t.ok)return[];let n=await t.json();return e(n)?Object.keys(n):[]}catch{return[]}}async function a(e){try{let t=await fetch(`/signalk/v1/api/vessels/self/${e.replace(/\./g,`/`)}/value`,{credentials:`include`});return t.ok?await t.json():void 0}catch{return}}var o=(e,t,n,r=0,i=0)=>({id:e,symbol:t,factor:n,offset:r,add:i}),s={rad:[o(`degree`,`°`,180/Math.PI),o(`radian`,`rad`,1)],"m/s":[o(`kn`,`kn`,1.94384),o(`m/s`,`m/s`,1),o(`km/h`,`km/h`,3.6),o(`mph`,`mph`,2.2369362920544025)],K:[o(`C`,`°C`,1,-273.15),o(`F`,`°F`,9/5,-273.15,32),o(`K`,`K`,1)],Pa:[o(`hPa`,`hPa`,.01),o(`mbar`,`mbar`,.01),o(`bar`,`bar`,1e-5),o(`psi`,`psi`,.0001450376807894691),o(`inHg`,`inHg`,.00029529987601298443),o(`mmHg`,`mmHg`,.0075006168507298),o(`Pa`,`Pa`,1)],m:[o(`m`,`m`,1),o(`meter`,`m`,1),o(`foot`,`ft`,3.280839895013124),o(`fathom`,`fathom`,.5467468562055768),o(`naut-mile`,`nmi`,.0005399568034557236),o(`kilometer`,`km`,.001),o(`mile`,`mi`,.000621371192237334)],"rad/s":[o(`deg/s`,`°/s`,57.29577951308232),o(`rpm`,`rpm`,9.549296585513723),o(`rad/s`,`rad/s`,1)],ratio:[o(`percent`,`%`,100),o(`ratio`,``,1)],Hz:[o(`rpm`,`rpm`,60),o(`hertz`,`Hz`,1)],V:[o(`V`,`V`,1)],A:[o(`A`,`A`,1)],W:[o(`watt`,`W`,1),o(`kW`,`kW`,.001)],m3:[o(`liter`,`l`,1e3),o(`gallon`,`gal`,264.1720512415585),o(`m3`,`m³`,1)],"m3/s":[o(`L/h`,`l/h`,36e5),o(`L/min`,`l/min`,6e4),o(`gal/h`,`gal/h`,264.17205236)]};function c(e,t){return t?(e+(t.offset??0))*t.factor+(t.add??0):e}function l(e){if(e)return/\.(0+)/.exec(e)?.[1]?.length??0}function u(e,t,n){let r=e?.units,i=r?s[r]??[]:[],a=e=>i.find(t=>t.id===e),o=a(t)??a(e?.displayUnits?.targetUnit)??i[0],c=r===`rad`,u=o&&o.id===e?.displayUnits?.targetUnit?l(e.displayUnits.displayFormat):void 0,d=o?.id===`degree`?0:u??1;return{conversion:o,symbol:o?.symbol??r??``,decimals:n>=0?n:d,angular:c}}export{t as a,i,c as n,r as o,u as r,a as s,s as t};
@@ -1 +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-Cn4uhL0-.js";var u=`/signalk/v2/api/history/values`;function d(e,t,n,r){return`${u}?${new URLSearchParams({paths:(r?[`min`,`max`,`average`]:[`average`]).map(t=>`${e}:${t}`).join(`,`),duration:String(Math.ceil(t/1e3)),resolution:String(Math.max(1,Math.round(n/1e3)))}).toString()}`}function f(e,t){let n=n=>e.values.findIndex(e=>e.path===t&&e.method===n),r=n(`average`);if(r<0)return[];let i=n(`min`),a=n(`max`),o=[];for(let t of e.data){let e=Date.parse(t[0]),n=t[r+1];if(Number.isNaN(e)||typeof n!=`number`)continue;let s=i<0?n:t[i+1],c=a<0?n:t[a+1];o.push({time:e,avg:n,min:typeof s==`number`?s:n,max:typeof c==`number`?c:n})}return o}function p(e){if(typeof e!=`object`||!e)return!1;let t=e;return Array.isArray(t.values)&&Array.isArray(t.data)}async function m(e,t,n,r=fetch){for(let i of[!0,!1])try{let a=await r(d(e,t,n,i),{credentials:`include`});if(a.status===404||a.status===501)return[];if(!a.ok)continue;let o=await a.json();if(p(o))return f(o,e)}catch(e){return console.warn(`history query failed`,e),[]}return[]}var h={card:`rgba(20, 26, 32, 0.78)`,text:`#e8edf2`,muted:`#9fb3c4`,bar:`#29b6f6`,avg:`#ef5350`,grid:`rgba(255, 255, 255, 0.22)`},g={card:`rgba(8, 0, 0, 0.85)`,text:`#d32f2f`,muted:`#8e2424`,bar:`#7f1212`,avg:`#ff5252`,grid:`rgba(211, 47, 47, 0.3)`},_=[1,2,5,10,15,20,30,60,120,180,240,360,720,1440],v=4;function y(e){let t=e/6e4,n=_.find(e=>t/e<=v)??Math.ceil(t/v/1440)*1440,r=t>120,i=[];for(let e=n;e<=t+1e-9;e+=n)i.push({age:e*6e4,label:r?`${+(e/60).toFixed(1)}h`:`${e}`});return i}function b(e,t){let n=e.min,r=e.max;if(r-n<t){let e=(r+n)/2;n=e-t/2,r=e+t/2}let i=(r-n)*.05;return{lo:n-i,hi:r+i}}function x(e,t,n,r,i=``){let a=r;for(e.font=`${i} ${a}px 'Helvetica Neue', Arial, sans-serif`;a>7&&e.measureText(t).width>n;)a--,e.font=`${i} ${a}px 'Helvetica Neue', Arial, sans-serif`;return a}function S(e,t,n,r){let i=r.palette,a=Math.max(4,Math.round(t*.05));e.clearRect(0,0,t,n),e.fillStyle=i.card,e.beginPath(),e.roundRect(0,0,t,n,10),e.fill(),e.textBaseline=`alphabetic`;let o=t-2*a,s=Math.max(8,Math.round(Math.min(t/9,n/18)));e.fillStyle=i.muted,e.font=`600 ${s}px 'Helvetica Neue', Arial, sans-serif`,e.textAlign=`left`;let c=a+s;e.fillText(r.label,a,c,o*.7),e.textAlign=`right`,e.fillText(r.symbol,t-a,c,o*.3);let l=x(e,r.value,o,Math.round(Math.min(t/3.2,n/7)),`600`);e.fillStyle=i.text,e.textAlign=`center`,c+=l+2,e.fillText(r.value,t/2,c);let u=x(e,`${r.min} ${r.avg} ${r.max}`,o,s);e.font=`${u}px 'Helvetica Neue', Arial, sans-serif`,c+=u+4,e.textAlign=`left`,e.fillStyle=i.muted,e.fillText(r.min,a,c),e.textAlign=`right`,e.fillText(r.max,t-a,c),e.textAlign=`center`,e.fillStyle=i.avg,e.fillText(r.avg,t/2,c);let d=Math.max(7,s-2);e.font=`${d}px 'Helvetica Neue', Arial, sans-serif`;let f=y(r.windowMs),p=Math.ceil(Math.max(...f.map(t=>e.measureText(t.label).width),0))+3,m=c+4,h=a,g=t-a-p,_=n-a,v=_-m,S=g-h;if(v<10||S<10)return;e.strokeStyle=i.grid,e.lineWidth=1,e.strokeRect(h+.5,m+.5,S-1,v-1);let C=e=>m+e/r.windowMs*v;e.fillStyle=i.muted,e.textAlign=`left`,e.textBaseline=`middle`;for(let t of f){let n=Math.round(C(t.age))+.5;t.age<r.windowMs&&(e.beginPath(),e.moveTo(h,n),e.lineTo(g,n),e.stroke()),e.fillText(t.label,g+3,Math.min(n,_-d/2))}if(!r.stats){r.message&&(e.textAlign=`center`,e.fillStyle=i.muted,e.fillText(r.message,h+S/2,m+v/2,S-4));return}let w=b(r.stats,r.minSpan),T=e=>h+(e-w.lo)/(w.hi-w.lo)*S;e.fillStyle=i.bar;for(let t of r.stats.bands){let n=C(t.ageStart),r=Math.min(_,C(t.ageEnd));if(r<=m)continue;let i=T(t.lo),a=Math.max(i+2,T(t.hi)),o=Math.max(m,n);e.fillRect(i,o,a-i,Math.max(1,r-o)+.5)}let E=Math.round(T(r.stats.avg))+.5;e.strokeStyle=i.avg,e.beginPath(),e.moveTo(E,m),e.lineTo(E,_),e.stroke()}var C=2*Math.PI;function w(e){let t=((e+Math.PI)%C+C)%C;return t===0?Math.PI:t-Math.PI}var T=class{windowMs;bucketMs;angular;buckets=new Map;constructor(e,t,n=240){this.windowMs=e,this.bucketMs=e/n,this.angular=t}get size(){return this.buckets.size}add(e,t){this.merge(e,t,t,t)}merge(e,t,n,r){if(![e,t,n,r].every(Number.isFinite))return;let i=Math.floor(e/this.bucketMs),a=this.buckets.get(i);if(a||(a={index:i,ref:this.angular?r:0,lo:1/0,hi:-1/0,sum:0,sin:0,cos:0,n:0},this.buckets.set(i,a)),this.angular){let e=w(r-a.ref),i=w(t-r),o=w(n-r);a.lo=Math.min(a.lo,e+Math.min(0,i,o)),a.hi=Math.max(a.hi,e+Math.max(0,i,o)),a.sin+=Math.sin(r),a.cos+=Math.cos(r)}else a.lo=Math.min(a.lo,t,r),a.hi=Math.max(a.hi,n,r),a.sum+=r;a.n++}prune(e){let t=Math.floor((e-this.windowMs)/this.bucketMs);for(let e of this.buckets.keys())e<t&&this.buckets.delete(e)}clear(){this.buckets.clear()}stats(e){this.prune(e);let t=[...this.buckets.values()].filter(e=>e.n>0);if(t.length===0)return;let n=0;if(this.angular){let e=0,r=0;for(let n of t){let t=Math.atan2(n.sin,n.cos);e+=Math.sin(t),r+=Math.cos(t)}n=Math.atan2(e,r)}let r=1/0,i=-1/0,a=0,o=[];for(let s of t){let t=this.angular?w(s.ref-n):0,c=s.lo+t,l=s.hi+t;r=Math.min(r,c),i=Math.max(i,l),this.angular||(a+=s.sum/s.n);let u=s.index*this.bucketMs;o.push({ageStart:Math.max(0,e-(u+this.bucketMs)),ageEnd:Math.max(0,e-u),lo:c,hi:l})}return{centre:n,min:r,max:i,avg:this.angular?0:a/t.length,bands:o}}},E=6e4,D=2*Math.PI,O=2,k=document.createElement(`canvas`);document.getElementById(`root`)?.append(k);var A,j=!1,M=0,N,P,F=!1;function I(e,t){if(e===void 0||!Number.isFinite(e))return`--`;let n=e;t.display.angular&&(n=t.signed?w(e):(e%D+D)%D);let r=c(n,t.display.conversion).toFixed(t.display.decimals);return t.display.angular&&!t.signed&&Number(r)>=360?0 .toFixed(t.display.decimals):r}function L(e){let t=j?g:h,n=A;if(!n)return{label:``,value:`--`,symbol:``,min:``,avg:``,max:``,windowMs:1,stats:void 0,minSpan:1,palette:t};let r=n.series.stats(e),a=e=>e===void 0?void 0:(r?.centre??0)+e,o=n.last&&e-n.last.time<E?n.last.value:void 0,s=n.display.conversion?1/Math.abs(n.display.conversion.factor):1;return{label:i(n.config),value:I(o,n),symbol:n.display.symbol,min:r?I(a(r.min),n):``,avg:r?I(a(r.avg),n):``,max:r?I(a(r.max),n):``,windowMs:n.series.windowMs,stats:r,minSpan:O*s,message:n.loading?`Loading…`:`No data`,palette:t}}function R(){F=!1;let e=k.getBoundingClientRect(),t=window.devicePixelRatio||1,n=Math.max(1,Math.round(e.width*t)),r=Math.max(1,Math.round(e.height*t));(k.width!==n||k.height!==r)&&(k.width=n,k.height=r);let i=k.getContext(`2d`);i&&(i.setTransform(t,0,0,t,0,0),S(i,e.width,e.height,L(Date.now())))}function z(){F||(F=!0,requestAnimationFrame(R))}async function B(e,t){let n=await m(e.config.path,e.series.windowMs,e.series.bucketMs);if(t===M){if(n.length>0){e.series.clear();for(let t of n)e.series.merge(t.time,t.min,t.max,t.avg);e.last&&e.series.add(e.last.time,e.last.value)}e.loading=!1,z()}}async function V(e){let i=++M;if(N){let e=N;N=void 0,await e().catch(()=>void 0)}P&&clearInterval(P);let a=n(await e.state.get()),o=await s(a.path);if(i!==M)return;let c=l(o,a.unit||void 0,a.decimals),u={config:a,display:c,series:new T(t(a),c.angular),signed:r(a.path),loading:!0};A=u,z(),N=await e.signalk.subscribe([a.path],e=>{if(typeof e.value!=`number`||!Number.isFinite(e.value))return;let t=Date.now();u.last={time:t,value:e.value},u.series.add(t,e.value),z()}),P=setInterval(z,Math.max(1e3,u.series.bucketMs)),await B(u,i)}async function H(){let t=await e();a(t),new ResizeObserver(z).observe(k),await t.subscribe([`state.changed`],()=>{V(t).catch(e=>{console.warn(`history strip: settings reload failed`,e)})}),document.addEventListener(`visibilitychange`,()=>{document.visibilityState===`visible`&&A&&B(A,M)}),await o(t,e=>{j=e,z()}).catch(e=>{console.warn(`history strip: night mode unavailable`,e)}),await V(t)}H().catch(e=>{console.warn(`history strip: host connection failed`,e)});