signalk-polar-performance-plugin 1.3.3 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/public/index.html CHANGED
@@ -82,9 +82,7 @@
82
82
  <ul id="main-nav" class="nav">
83
83
  <li class="nav-item"><a class="nav-link active" href="#" data-page="overview">Overview</a></li>
84
84
  <li class="nav-item"><a class="nav-link" href="#" data-page="inputs">Inputs</a></li>
85
- <li class="nav-item"><a class="nav-link" href="#" data-page="settings">Polar</a></li>
86
85
  <li class="nav-item"><a class="nav-link" href="#" data-page="outputs">Outputs</a></li>
87
- <li class="nav-item"><a class="nav-link" href="#" data-page="polars">Polar management</a></li>
88
86
  </ul>
89
87
  </nav>
90
88
  <button class="sidebar-minimizer brand-minimizer" type="button" id="sidebarMinimizer"></button>
@@ -126,19 +126,12 @@
126
126
 
127
127
  <div class="settings-row">
128
128
  <span class="settings-row-label">Active polar</span>
129
- <button id="btn-polar-current" aria-label="Select polar">
130
- <span id="polar-name-display">—</span>
131
- <span class="polar-arrow">›</span>
132
- </button>
129
+ <span id="polar-name-display">—</span>
133
130
  </div>
134
131
 
135
132
  <div class="settings-row">
136
133
  <span class="settings-row-label">Performance</span>
137
- <div class="stepper">
138
- <button class="btn-step" id="btn-perf-minus" aria-label="Decrease">−</button>
139
- <span class="stepper-value" id="perf-display">100%</span>
140
- <button class="btn-step" id="btn-perf-plus" aria-label="Increase">+</button>
141
- </div>
134
+ <span id="perf-display">100%</span>
142
135
  </div>
143
136
 
144
137
  <div class="settings-row">
@@ -147,15 +140,6 @@
147
140
  </div>
148
141
  </div>
149
142
 
150
- <!-- Polar picker (opens on top of settings panel) -->
151
- <div id="polar-list-panel" class="overlay" style="z-index:30" role="dialog" aria-label="Select polar">
152
- <div class="panel-header">
153
- <button class="btn-icon" id="btn-back-from-polar" aria-label="Back">‹</button>
154
- <span class="panel-header-title">Select polar</span>
155
- </div>
156
- <div id="polar-list-items"></div>
157
- </div>
158
-
159
143
  <script src="polar-canvas.js"></script>
160
144
  <script>
161
145
  (function () {
@@ -189,10 +173,11 @@
189
173
  let libraryVersion = '' // change detector
190
174
  let liveTws = null // last fetched live TWS (m/s)
191
175
  let liveCurve = null // interpolated curve for liveTws
192
- let activePolarId = null // current active polar id
193
176
 
194
- // Settings state — kept in sync with the server
195
- let perfAdjust = 1 // multiplier (1 = 100%)
177
+ // Active polar / performance factor are read-only here — selected in a
178
+ // separate polar management webapp (e.g. signalk-polar-management).
179
+ let activePolarName = null
180
+ let performanceFactor = 1
196
181
  let showAllTwsLines = true
197
182
 
198
183
  // -----------------------------------------------------------------------
@@ -220,26 +205,16 @@
220
205
  // -----------------------------------------------------------------------
221
206
 
222
207
  async function refreshLibrary() {
223
- // Resolve active polar ID first
224
- let polarId
225
- try {
226
- const active = await fetchJSON(BASE + '/polars/active')
227
- polarId = active.id
228
- } catch (_) {
229
- return // no active polar — try again on next cycle
230
- }
231
-
232
208
  let newList
233
209
  try {
234
- newList = await fetchJSON(BASE + '/polars/' + polarId + '/axes/tws')
210
+ newList = await fetchJSON(BASE + '/polar/axes/tws')
235
211
  } catch (_) {
236
- return // polar not yet loaded — try again on next cycle
212
+ return // no active polar yet — try again on next cycle
237
213
  }
238
214
 
239
- const version = polarId + ':' + JSON.stringify(newList)
215
+ const version = JSON.stringify(newList)
240
216
  if (version === libraryVersion) return // no change
241
217
 
242
- activePolarId = polarId
243
218
  libraryVersion = version
244
219
  twsList = newList
245
220
  curves = {}
@@ -248,7 +223,7 @@
248
223
  await Promise.all(twsList.map(async (tws) => {
249
224
  try {
250
225
  curves[tws] = await fetchJSON(
251
- BASE + '/polars/' + polarId + '/queries/curve?tws=' + tws + '&step=' + STEP
226
+ BASE + '/polar/queries/curve?tws=' + tws + '&step=' + STEP
252
227
  )
253
228
  } catch (_) { /* skip individual failure */ }
254
229
  }))
@@ -272,10 +247,10 @@
272
247
  // -----------------------------------------------------------------------
273
248
 
274
249
  async function fetchCurveForTws(tws) {
275
- if (!Number.isFinite(tws) || tws <= 0 || !activePolarId) return null
250
+ if (!Number.isFinite(tws) || tws <= 0) return null
276
251
  try {
277
252
  return await fetchJSON(
278
- BASE + '/polars/' + activePolarId + '/queries/curve?tws=' + tws.toFixed(5) + '&step=' + STEP
253
+ BASE + '/polar/queries/curve?tws=' + tws.toFixed(5) + '&step=' + STEP
279
254
  )
280
255
  } catch (_) {
281
256
  return null
@@ -311,12 +286,16 @@
311
286
  async function loadSettings() {
312
287
  try {
313
288
  const s = await fetchJSON(BASE + '/settings')
314
- perfAdjust = Number.isFinite(s.perfAdjust) ? s.perfAdjust : 1
315
289
  showAllTwsLines = s.showAllTwsLines !== false
316
- updatePerfDisplay()
317
290
  updateTwsToggle()
318
291
  polar.setShowAllTwsLines(showAllTwsLines)
319
292
  } catch (_) { /* keep defaults */ }
293
+ try {
294
+ const m = await fetchJSON(BASE + '/meta')
295
+ activePolarName = m.activePolar ? (m.activePolar.name || m.activePolar.id) : null
296
+ performanceFactor = Number.isFinite(m.performanceFactor) ? m.performanceFactor : 1
297
+ updatePerfDisplay()
298
+ } catch (_) { /* keep defaults */ }
320
299
  }
321
300
 
322
301
  // -----------------------------------------------------------------------
@@ -324,14 +303,12 @@
324
303
  // -----------------------------------------------------------------------
325
304
 
326
305
  var settingsPanel = document.getElementById('settings-panel')
327
- var polarListPanel = document.getElementById('polar-list-panel')
328
306
  var perfDisplay = document.getElementById('perf-display')
329
307
  var twsToggle = document.getElementById('btn-tws-toggle')
330
308
  var polarNameEl = document.getElementById('polar-name-display')
331
- var polarListEl = document.getElementById('polar-list-items')
332
309
 
333
310
  function updatePerfDisplay() {
334
- perfDisplay.textContent = Math.round(perfAdjust * 100) + '%'
311
+ perfDisplay.textContent = Math.round(performanceFactor * 100) + '%'
335
312
  }
336
313
 
337
314
  function updateTwsToggle() {
@@ -347,14 +324,7 @@
347
324
  // Open settings: fetch fresh state first
348
325
  document.getElementById('btn-settings').addEventListener('click', function () {
349
326
  loadSettings().then(function () {
350
- // Update active polar name display
351
- if (activePolarId) {
352
- fetchJSON(BASE + '/polars/' + activePolarId)
353
- .then(function (p) { polarNameEl.textContent = p.name || activePolarId })
354
- .catch(function () { polarNameEl.textContent = activePolarId })
355
- } else {
356
- polarNameEl.textContent = '—'
357
- }
327
+ polarNameEl.textContent = activePolarName || '—'
358
328
  settingsPanel.classList.add('open')
359
329
  })
360
330
  })
@@ -363,25 +333,6 @@
363
333
  settingsPanel.classList.remove('open')
364
334
  })
365
335
 
366
- // Performance stepper — ±5%, clamped 50%–200%
367
- function applyPerfAdjust(next) {
368
- if (next === perfAdjust) return
369
- perfAdjust = next
370
- updatePerfDisplay()
371
- putJSON(BASE + '/settings', { perfAdjust: perfAdjust }).then(function () {
372
- libraryVersion = '' // force full curve reload — perfAdjust changes speeds but not the TWS list
373
- return refreshLibrary()
374
- }).catch(function () {})
375
- }
376
-
377
- document.getElementById('btn-perf-minus').addEventListener('click', function () {
378
- applyPerfAdjust(Math.max(0.5, Math.round((perfAdjust - 0.05) * 100) / 100))
379
- })
380
-
381
- document.getElementById('btn-perf-plus').addEventListener('click', function () {
382
- applyPerfAdjust(Math.min(2.0, Math.round((perfAdjust + 0.05) * 100) / 100))
383
- })
384
-
385
336
  // TWS toggle
386
337
  twsToggle.addEventListener('click', function () {
387
338
  showAllTwsLines = !showAllTwsLines
@@ -390,43 +341,6 @@
390
341
  putJSON(BASE + '/settings', { showAllTwsLines: showAllTwsLines }).catch(function () {})
391
342
  })
392
343
 
393
- // Polar picker
394
- document.getElementById('btn-polar-current').addEventListener('click', function () {
395
- polarListEl.innerHTML = ''
396
- fetchJSON(BASE + '/polars').then(function (list) {
397
- list.forEach(function (p) {
398
- var btn = document.createElement('button')
399
- btn.className = 'polar-item' + (p.id === activePolarId ? ' is-active' : '')
400
- var nameSpan = document.createElement('span')
401
- nameSpan.className = 'polar-item-name'
402
- nameSpan.textContent = p.name || p.id
403
- btn.appendChild(nameSpan)
404
- if (p.id === activePolarId) {
405
- var check = document.createElement('span')
406
- check.className = 'polar-item-check'
407
- check.textContent = '✓'
408
- btn.appendChild(check)
409
- }
410
- btn.addEventListener('click', function () {
411
- putJSON(BASE + '/polars/active', { id: p.id })
412
- .then(function () {
413
- polarNameEl.textContent = p.name || p.id
414
- polarListPanel.classList.remove('open')
415
- // Force library refresh on next cycle
416
- libraryVersion = ''
417
- })
418
- .catch(function () {})
419
- })
420
- polarListEl.appendChild(btn)
421
- })
422
- polarListPanel.classList.add('open')
423
- }).catch(function () {})
424
- })
425
-
426
- document.getElementById('btn-back-from-polar').addEventListener('click', function () {
427
- polarListPanel.classList.remove('open')
428
- })
429
-
430
344
  // -----------------------------------------------------------------------
431
345
  // Start
432
346
  // -----------------------------------------------------------------------
@@ -16,7 +16,12 @@ function makeApp(dataDir) {
16
16
  error: () => {},
17
17
  setPluginStatus: () => {},
18
18
  setPluginError: () => {},
19
- savePluginOptions: () => {},
19
+ savePluginOptions: (_options, callback) => {
20
+ if (typeof callback !== 'function') {
21
+ throw new TypeError('callback is not a function')
22
+ }
23
+ callback(null)
24
+ },
20
25
  getDataDirPath: () => dataDir,
21
26
  handleMessage: () => {},
22
27
  config: { port: 3000 },
package/docs/import.png DELETED
Binary file
package/docs/polar.png DELETED
Binary file
Binary file
@@ -1,133 +0,0 @@
1
- 'use strict'
2
-
3
- const fs = require('fs')
4
- const path = require('path')
5
- const { PolarTable } = require('./PolarTable')
6
-
7
- /**
8
- * PolarFileStore — manages stored polar resources in the plugin data directory.
9
- *
10
- * All file names are bare names (no path, no extension).
11
- * Files are stored as canonical <name>.json resources in the configured data directory.
12
- */
13
- class PolarFileStore {
14
- /**
15
- * @param {string} dataDir - Absolute path to the directory where polar files are stored.
16
- */
17
- constructor(dataDir) {
18
- this.dataDir = dataDir
19
- if (!fs.existsSync(dataDir)) {
20
- fs.mkdirSync(dataDir, { recursive: true })
21
- }
22
- }
23
-
24
- /** Resolves a bare name to its full JSON file path. */
25
- _jsonPath(name) {
26
- const safe = path.basename(name)
27
- return path.join(this.dataDir, `${safe}.json`)
28
- }
29
-
30
- /** Checks whether a canonical polar resource exists. */
31
- exists(name) {
32
- return fs.existsSync(this._jsonPath(name))
33
- }
34
-
35
- /**
36
- * List all canonical polar resources in the data directory.
37
- * @returns {string[]} Array of bare names, sorted alphabetically.
38
- */
39
- list() {
40
- return fs.readdirSync(this.dataDir)
41
- .filter(fileName => fileName.endsWith('.json'))
42
- .map(fileName => fileName.slice(0, -5))
43
- .sort()
44
- }
45
-
46
- /**
47
- * Read the raw JSON content of a stored canonical polar resource.
48
- * @param {string} name - Bare name of the polar.
49
- * @returns {string} Raw file content.
50
- * @throws {Error} If the file does not exist.
51
- */
52
- read(name) {
53
- const jp = this._jsonPath(name)
54
- if (!fs.existsSync(jp)) throw new Error(`Polar not found: ${name}`)
55
- return fs.readFileSync(jp, 'utf8')
56
- }
57
-
58
- /**
59
- * Load a canonical polar resource and return a populated PolarTable.
60
- * @param {string} name - Bare name of the polar.
61
- * @returns {PolarTable}
62
- * @throws {Error} If the file does not exist or is not a canonical resource.
63
- */
64
- load(name) {
65
- const stored = this.readObject(name)
66
- if (stored.kind !== 'polarTable') {
67
- throw new Error(`Stored polar is not a canonical polarTable resource: ${name}`)
68
- }
69
- return new PolarTable().loadFromCanonical(stored)
70
- }
71
-
72
- /** Reads and parses a stored JSON polar resource. */
73
- readObject(name) {
74
- const jp = this._jsonPath(name)
75
- if (!fs.existsSync(jp)) throw new Error(`Polar not found: ${name}`)
76
- return JSON.parse(fs.readFileSync(jp, 'utf8'))
77
- }
78
-
79
- /**
80
- * Read metadata for a polar.
81
- * Reads directly from the stored canonical resource.
82
- */
83
- readMeta(name) {
84
- try {
85
- const stored = this.readObject(name)
86
- return {
87
- ...(stored.name ? { boatName: stored.name } : {}),
88
- ...(stored.boatType ? { boatType: stored.boatType } : {}),
89
- ...(stored.sailnumber ? { sailnumber: stored.sailnumber } : {}),
90
- ...(Number.isInteger(stored.year) ? { year: stored.year } : {}),
91
- ...(stored.source ? { source: stored.source } : {}),
92
- ...(stored.notes ? { notes: stored.notes } : {}),
93
- }
94
- } catch (_) { return {} }
95
- }
96
-
97
- /**
98
- * List all polar files with their stored metadata.
99
- * @returns {{ name: string, boatName?: string, boatType?: string, sailnumber?: string }[]}
100
- */
101
- listWithMeta() {
102
- return this.list().map(name => ({ name, ...this.readMeta(name) }))
103
- }
104
-
105
- /** Save a canonical polar resource as JSON. */
106
- saveCanonical(name, resource) {
107
- const stored = { ...resource, id: name, kind: 'polarTable' }
108
- fs.writeFileSync(this._jsonPath(name), JSON.stringify(stored, null, 2), 'utf8')
109
- return name
110
- }
111
-
112
- /**
113
- * Copy a stored canonical polar resource to a new name.
114
- */
115
- copy(srcName, dstName) {
116
- const srcJson = this._jsonPath(srcName)
117
- if (!fs.existsSync(srcJson)) throw new Error(`Polar not found: ${srcName}`)
118
- fs.copyFileSync(srcJson, this._jsonPath(dstName))
119
- }
120
-
121
- /**
122
- * Delete a stored canonical polar resource.
123
- * @param {string} name - Bare name of the polar to delete.
124
- * @throws {Error} If the file does not exist.
125
- */
126
- delete(name) {
127
- const jp = this._jsonPath(name)
128
- if (!fs.existsSync(jp)) throw new Error(`Polar not found: ${name}`)
129
- fs.unlinkSync(jp)
130
- }
131
- }
132
-
133
- module.exports = PolarFileStore