speedandcurrent 2.1.0 → 2.1.2
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/LICENSE +21 -21
- package/README.md +294 -294
- package/correctionTable.js +332 -332
- package/icon.png +0 -0
- package/index.js +780 -780
- package/package.json +50 -49
- package/public/TableRenderer.js +143 -143
- package/public/app.js +843 -843
- package/public/index.html +345 -345
- package/public/main.css +123 -123
package/public/index.html
CHANGED
|
@@ -1,346 +1,346 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
|
|
4
|
-
<head>
|
|
5
|
-
<meta charset="UTF-8">
|
|
6
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
-
<title>Speed and Current</title>
|
|
8
|
-
<script>
|
|
9
|
-
(async () => {
|
|
10
|
-
const appendStylesheet = (href) => {
|
|
11
|
-
const el = document.createElement('link');
|
|
12
|
-
el.rel = 'stylesheet';
|
|
13
|
-
el.href = href;
|
|
14
|
-
document.head.appendChild(el);
|
|
15
|
-
};
|
|
16
|
-
try {
|
|
17
|
-
const res = await fetch('/admin/.vite/manifest.json');
|
|
18
|
-
if (res.ok) {
|
|
19
|
-
const manifest = await res.json();
|
|
20
|
-
let found = false;
|
|
21
|
-
for (const entry of Object.values(manifest)) {
|
|
22
|
-
if (!entry.isEntry) continue;
|
|
23
|
-
for (const css of entry.css ?? []) {
|
|
24
|
-
appendStylesheet('/admin/' + css);
|
|
25
|
-
found = true;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
if (found) return;
|
|
29
|
-
}
|
|
30
|
-
} catch (e) {
|
|
31
|
-
// fall through to legacy scrape
|
|
32
|
-
}
|
|
33
|
-
try {
|
|
34
|
-
const res = await fetch('/');
|
|
35
|
-
const html = await res.text();
|
|
36
|
-
const doc = new DOMParser().parseFromString(html, 'text/html');
|
|
37
|
-
const link = doc.querySelector('link[rel="stylesheet"]');
|
|
38
|
-
if (link) {
|
|
39
|
-
appendStylesheet(new URL(link.getAttribute('href'), res.url).href);
|
|
40
|
-
}
|
|
41
|
-
} catch (e) {
|
|
42
|
-
console.warn('[webapp] Could not load SignalK admin stylesheet:', e);
|
|
43
|
-
}
|
|
44
|
-
})();
|
|
45
|
-
</script>
|
|
46
|
-
<link rel="stylesheet" href="main.css">
|
|
47
|
-
</head>
|
|
48
|
-
|
|
49
|
-
<body class="app header-fixed sidebar-fixed aside-menu-fixed aside-menu-hidden">
|
|
50
|
-
|
|
51
|
-
<header class="app-header navbar navbar-light bg-white">
|
|
52
|
-
<div class="d-flex align-items-center">
|
|
53
|
-
<a class="d-flex align-items-center gap-2 me-0 text-decoration-none" href="#">
|
|
54
|
-
<img src="icon.png" alt="" style="height:48px;width:auto">
|
|
55
|
-
<span class="app-title">Speed & Current</span>
|
|
56
|
-
</a>
|
|
57
|
-
<button class="navbar-toggler sidebar-toggler ms-2" type="button" id="sidebarToggler">
|
|
58
|
-
<span class="navbar-toggler-icon"></span>
|
|
59
|
-
</button>
|
|
60
|
-
</div>
|
|
61
|
-
<span id="message" class="small ms-auto me-3"></span>
|
|
62
|
-
</header>
|
|
63
|
-
|
|
64
|
-
<div class="app-body">
|
|
65
|
-
|
|
66
|
-
<div class="sidebar">
|
|
67
|
-
<nav class="sidebar-nav">
|
|
68
|
-
<ul id="main-nav" class="nav">
|
|
69
|
-
<li class="nav-item">
|
|
70
|
-
<a class="nav-link active" href="#" data-section="inputs">Inputs</a>
|
|
71
|
-
</li>
|
|
72
|
-
<li class="nav-item">
|
|
73
|
-
<a class="nav-link" href="#" data-section="estimation">Boatspeed Estimation</a>
|
|
74
|
-
</li>
|
|
75
|
-
<li class="nav-item">
|
|
76
|
-
<a class="nav-link" href="#" data-section="learning">Correction Table Learning</a>
|
|
77
|
-
</li>
|
|
78
|
-
<li class="nav-item">
|
|
79
|
-
<a class="nav-link" href="#" data-section="table">Correction Table</a>
|
|
80
|
-
</li>
|
|
81
|
-
</ul>
|
|
82
|
-
</nav>
|
|
83
|
-
<button class="sidebar-minimizer brand-minimizer" type="button" id="sidebarMinimizer"></button>
|
|
84
|
-
</div>
|
|
85
|
-
|
|
86
|
-
<main class="main">
|
|
87
|
-
<div class="container-fluid pt-3">
|
|
88
|
-
|
|
89
|
-
<!-- ===== INPUTS SECTION ===== -->
|
|
90
|
-
<div id="section-inputs" class="app-section">
|
|
91
|
-
<div class="card">
|
|
92
|
-
<div class="card-header fw-bold text-uppercase">Inputs</div>
|
|
93
|
-
<div class="card-body">
|
|
94
|
-
<h6 class="text-uppercase fw-bold text-muted border-bottom pb-1 mb-1 small">Settings</h6>
|
|
95
|
-
<table class="table table-sm table-borderless mb-0" id="inputs-settings-table">
|
|
96
|
-
<tbody></tbody>
|
|
97
|
-
</table>
|
|
98
|
-
<h6 class="text-uppercase fw-bold text-muted border-bottom pb-1 mt-3 mb-1 small">Live Values</h6>
|
|
99
|
-
<div id="inputs-values"></div>
|
|
100
|
-
</div>
|
|
101
|
-
</div>
|
|
102
|
-
</div>
|
|
103
|
-
|
|
104
|
-
<!-- ===== BOATSPEED ESTIMATION SECTION ===== -->
|
|
105
|
-
<div id="section-estimation" class="app-section" style="display:none">
|
|
106
|
-
<div class="card">
|
|
107
|
-
<div class="card-header d-flex align-items-center justify-content-between">
|
|
108
|
-
<span class="fw-bold text-uppercase">Boatspeed Estimation</span>
|
|
109
|
-
<label class="switch switch-text switch-primary mb-0" id="toggle-estimateBoatSpeed-label" title="Enable boatspeed estimation">
|
|
110
|
-
<input type="checkbox" class="switch-input form-check-input" id="toggle-estimateBoatSpeed">
|
|
111
|
-
<span class="switch-label" data-on="On" data-off="Off"></span>
|
|
112
|
-
<span class="switch-handle"></span>
|
|
113
|
-
</label>
|
|
114
|
-
</div>
|
|
115
|
-
<div class="card-body">
|
|
116
|
-
<h6 class="text-uppercase fw-bold text-muted border-bottom pb-1 mb-1 small">Settings</h6>
|
|
117
|
-
<table class="table table-sm table-borderless mb-0" id="estimation-settings-table">
|
|
118
|
-
<tbody></tbody>
|
|
119
|
-
</table>
|
|
120
|
-
<h6 class="text-uppercase fw-bold text-muted border-bottom pb-1 mt-3 mb-1 small">Inputs</h6>
|
|
121
|
-
<div id="estimation-inputs"></div>
|
|
122
|
-
<h6 class="text-uppercase fw-bold text-muted border-bottom pb-1 mt-3 mb-1 small">Intermediates</h6>
|
|
123
|
-
<div id="estimation-intermediates"></div>
|
|
124
|
-
<h6 class="text-uppercase fw-bold text-muted border-bottom pb-1 mt-3 mb-1 small">Outputs</h6>
|
|
125
|
-
<div id="estimation-outputs"></div>
|
|
126
|
-
<div id="estimation-warnings"></div>
|
|
127
|
-
</div>
|
|
128
|
-
</div>
|
|
129
|
-
</div>
|
|
130
|
-
|
|
131
|
-
<!-- ===== CORRECTION TABLE LEARNING SECTION ===== -->
|
|
132
|
-
<div id="section-learning" class="app-section" style="display:none">
|
|
133
|
-
<div class="card">
|
|
134
|
-
<div class="card-header d-flex align-items-center justify-content-between">
|
|
135
|
-
<span class="fw-bold text-uppercase">Correction Table Learning</span>
|
|
136
|
-
<label class="switch switch-text switch-primary mb-0" id="toggle-updateCorrectionTable-label" title="Enable correction table learning">
|
|
137
|
-
<input type="checkbox" class="switch-input form-check-input" id="toggle-updateCorrectionTable">
|
|
138
|
-
<span class="switch-label" data-on="On" data-off="Off"></span>
|
|
139
|
-
<span class="switch-handle"></span>
|
|
140
|
-
</label>
|
|
141
|
-
</div>
|
|
142
|
-
<div class="card-body">
|
|
143
|
-
<h6 class="text-uppercase fw-bold text-muted border-bottom pb-1 mb-1 small">Settings</h6>
|
|
144
|
-
<table class="table table-sm table-borderless mb-0" id="learning-settings-table">
|
|
145
|
-
<tbody></tbody>
|
|
146
|
-
</table>
|
|
147
|
-
<h6 class="text-uppercase fw-bold text-muted border-bottom pb-1 mt-3 mb-1 small">Smoother</h6>
|
|
148
|
-
<table class="table table-sm table-borderless mb-0" id="smoother-settings-table">
|
|
149
|
-
<tbody></tbody>
|
|
150
|
-
</table>
|
|
151
|
-
<h6 class="text-uppercase fw-bold text-muted border-bottom pb-1 mt-3 mb-1 small">Inputs</h6>
|
|
152
|
-
<div id="learning-inputs"></div>
|
|
153
|
-
<h6 class="text-uppercase fw-bold text-muted border-bottom pb-1 mt-3 mb-1 small">Status</h6>
|
|
154
|
-
<table class="table table-sm table-borderless mb-0" id="learning-status-table">
|
|
155
|
-
<tbody></tbody>
|
|
156
|
-
</table>
|
|
157
|
-
<div id="learning-warnings"></div>
|
|
158
|
-
</div>
|
|
159
|
-
</div>
|
|
160
|
-
</div>
|
|
161
|
-
|
|
162
|
-
<!-- ===== CORRECTION TABLE SECTION ===== -->
|
|
163
|
-
<div id="section-table" class="app-section" style="display:none">
|
|
164
|
-
|
|
165
|
-
<div class="card">
|
|
166
|
-
<div class="card-header" style="position:relative">
|
|
167
|
-
<span class="fw-bold text-uppercase">Correction Table</span>
|
|
168
|
-
<span id="active-table-name" class="ms-2 text-muted small"></span>
|
|
169
|
-
<div style="position:absolute;right:1rem;top:0;bottom:0;display:flex;align-items:center;gap:0.5rem;">
|
|
170
|
-
<button id="btn-tbl-new" class="btn btn-sm btn-outline-secondary">New</button>
|
|
171
|
-
<button id="btn-tbl-load" class="btn btn-sm btn-outline-secondary">Load</button>
|
|
172
|
-
<button id="btn-tbl-copy" class="btn btn-sm btn-outline-secondary">Copy</button>
|
|
173
|
-
<button id="btn-tbl-resize" class="btn btn-sm btn-outline-secondary">Resize</button>
|
|
174
|
-
</div>
|
|
175
|
-
</div>
|
|
176
|
-
<div class="card-body">
|
|
177
|
-
<div class="table-legend small text-muted mb-3">
|
|
178
|
-
<div class="table-legend-row">
|
|
179
|
-
<span class="legend-swatch legend-swatch--green"></span>
|
|
180
|
-
<span><strong>Green</strong> — paddlewheel reads slow (factor > 1, speed is corrected up)</span>
|
|
181
|
-
</div>
|
|
182
|
-
<div class="table-legend-row">
|
|
183
|
-
<span class="legend-swatch legend-swatch--orange"></span>
|
|
184
|
-
<span><strong>Orange</strong> — paddlewheel reads fast (factor < 1, speed is corrected down)</span>
|
|
185
|
-
</div>
|
|
186
|
-
<div class="table-legend-row">
|
|
187
|
-
<span class="legend-swatch legend-swatch--stripe"></span>
|
|
188
|
-
<span><strong>Stripe angle</strong> — encodes leeway direction; stripes are tilted proportionally to the leeway correction (°)</span>
|
|
189
|
-
</div>
|
|
190
|
-
<div class="table-legend-row">
|
|
191
|
-
<span class="legend-swatch legend-swatch--active">+1.2%</span>
|
|
192
|
-
<span><strong>Blue text</strong> — most recently updated cell</span>
|
|
193
|
-
</div>
|
|
194
|
-
<div class="table-legend-row">
|
|
195
|
-
<span class="legend-swatch legend-swatch--neighbour"></span>
|
|
196
|
-
<span><strong>Faint tint</strong> — interpolation neighbours used in the current estimate</span>
|
|
197
|
-
</div>
|
|
198
|
-
<div class="table-legend-row mt-1">
|
|
199
|
-
<span class="legend-label-wide">Cell values:</span>
|
|
200
|
-
<span>top = speed-correction factor (±%), bottom = leeway correction (±°)</span>
|
|
201
|
-
</div>
|
|
202
|
-
</div>
|
|
203
|
-
<div id="table-container"></div>
|
|
204
|
-
</div>
|
|
205
|
-
</div>
|
|
206
|
-
</div>
|
|
207
|
-
|
|
208
|
-
</div>
|
|
209
|
-
</main>
|
|
210
|
-
</div>
|
|
211
|
-
|
|
212
|
-
<script>
|
|
213
|
-
document.getElementById('sidebarMinimizer').addEventListener('click', () => {
|
|
214
|
-
document.body.classList.toggle('sidebar-minimized');
|
|
215
|
-
document.body.classList.toggle('brand-minimized');
|
|
216
|
-
});
|
|
217
|
-
const isMobile = () => window.matchMedia('(max-width: 767.98px)').matches;
|
|
218
|
-
document.getElementById('sidebarToggler').addEventListener('click', () => {
|
|
219
|
-
if (isMobile()) {
|
|
220
|
-
document.body.classList.toggle('sidebar-mobile-show');
|
|
221
|
-
} else {
|
|
222
|
-
document.body.classList.toggle('sidebar-hidden');
|
|
223
|
-
}
|
|
224
|
-
});
|
|
225
|
-
document.querySelectorAll('#main-nav .nav-link').forEach(link => {
|
|
226
|
-
link.addEventListener('click', e => {
|
|
227
|
-
e.preventDefault();
|
|
228
|
-
document.querySelectorAll('#main-nav .nav-link').forEach(l => l.classList.remove('active'));
|
|
229
|
-
link.classList.add('active');
|
|
230
|
-
const target = link.dataset.section;
|
|
231
|
-
document.querySelectorAll('.app-section').forEach(s => s.style.display = 'none');
|
|
232
|
-
document.getElementById('section-' + target).style.display = '';
|
|
233
|
-
if (isMobile()) document.body.classList.remove('sidebar-mobile-show');
|
|
234
|
-
});
|
|
235
|
-
});
|
|
236
|
-
</script>
|
|
237
|
-
|
|
238
|
-
<!-- ===== TABLE MANAGER MODALS ===== -->
|
|
239
|
-
|
|
240
|
-
<!-- Create table -->
|
|
241
|
-
<div class="modal fade" id="modal-create" tabindex="-1" role="dialog">
|
|
242
|
-
<div class="modal-dialog" role="document">
|
|
243
|
-
<div class="modal-content">
|
|
244
|
-
<div class="modal-header">
|
|
245
|
-
<h5 class="modal-title">New Correction Table</h5>
|
|
246
|
-
<button type="button" class="close" data-dismiss="modal"><span>×</span></button>
|
|
247
|
-
</div>
|
|
248
|
-
<div class="modal-body">
|
|
249
|
-
<table class="table table-sm table-borderless">
|
|
250
|
-
<tbody>
|
|
251
|
-
<tr><td>Name</td><td><input type="text" id="create-name" class="form-control form-control-sm" placeholder="e.g. table-2026" style="width:180px"></td></tr>
|
|
252
|
-
<tr><td id="create-maxSpeed-label">Max speed (kn)</td><td><input type="number" id="create-maxSpeed" class="form-control form-control-sm" value="9" min="1" max="30" step="1" style="width:80px"></td></tr>
|
|
253
|
-
<tr><td id="create-speedStep-label">Speed step (kn)</td><td><input type="number" id="create-speedStep" class="form-control form-control-sm" value="1" min="0.1" max="5" step="0.1" style="width:80px"></td></tr>
|
|
254
|
-
<tr><td id="create-maxHeel-label">Max heel (°)</td><td><input type="number" id="create-maxHeel" class="form-control form-control-sm" value="32" min="1" max="90" step="1" style="width:80px"></td></tr>
|
|
255
|
-
<tr><td id="create-heelStep-label">Heel step (°)</td><td><input type="number" id="create-heelStep" class="form-control form-control-sm" value="8" min="1" max="30" step="1" style="width:80px"></td></tr>
|
|
256
|
-
</tbody>
|
|
257
|
-
</table>
|
|
258
|
-
</div>
|
|
259
|
-
<div class="modal-footer">
|
|
260
|
-
<span id="modal-create-status" class="small text-danger mr-auto"></span>
|
|
261
|
-
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
|
|
262
|
-
<button type="button" class="btn btn-primary" id="btn-create-confirm">Create</button>
|
|
263
|
-
</div>
|
|
264
|
-
</div>
|
|
265
|
-
</div>
|
|
266
|
-
</div>
|
|
267
|
-
|
|
268
|
-
<!-- Load table -->
|
|
269
|
-
<div class="modal fade" id="modal-load" tabindex="-1" role="dialog">
|
|
270
|
-
<div class="modal-dialog" role="document">
|
|
271
|
-
<div class="modal-content">
|
|
272
|
-
<div class="modal-header">
|
|
273
|
-
<h5 class="modal-title">Load Correction Table</h5>
|
|
274
|
-
<button type="button" class="close" data-dismiss="modal"><span>×</span></button>
|
|
275
|
-
</div>
|
|
276
|
-
<div class="modal-body">
|
|
277
|
-
<p class="text-muted small">Select a saved table to load and activate.</p>
|
|
278
|
-
<ul id="table-list" class="list-group"></ul>
|
|
279
|
-
</div>
|
|
280
|
-
<div class="modal-footer">
|
|
281
|
-
<span id="modal-load-status" class="small text-danger mr-auto"></span>
|
|
282
|
-
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
|
|
283
|
-
<button type="button" class="btn btn-primary" id="btn-load-confirm" disabled>Load</button>
|
|
284
|
-
</div>
|
|
285
|
-
</div>
|
|
286
|
-
</div>
|
|
287
|
-
</div>
|
|
288
|
-
|
|
289
|
-
<!-- Copy table -->
|
|
290
|
-
<div class="modal fade" id="modal-copy" tabindex="-1" role="dialog">
|
|
291
|
-
<div class="modal-dialog" role="document">
|
|
292
|
-
<div class="modal-content">
|
|
293
|
-
<div class="modal-header">
|
|
294
|
-
<h5 class="modal-title">Copy Active Table</h5>
|
|
295
|
-
<button type="button" class="close" data-dismiss="modal"><span>×</span></button>
|
|
296
|
-
</div>
|
|
297
|
-
<div class="modal-body">
|
|
298
|
-
<p class="text-muted small">Save the active table under a new name and activate it.</p>
|
|
299
|
-
<table class="table table-sm table-borderless">
|
|
300
|
-
<tbody>
|
|
301
|
-
<tr><td>New name</td><td><input type="text" id="copy-name" class="form-control form-control-sm" placeholder="e.g. backup-2026" style="width:180px"></td></tr>
|
|
302
|
-
</tbody>
|
|
303
|
-
</table>
|
|
304
|
-
</div>
|
|
305
|
-
<div class="modal-footer">
|
|
306
|
-
<span id="modal-copy-status" class="small text-danger mr-auto"></span>
|
|
307
|
-
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
|
|
308
|
-
<button type="button" class="btn btn-primary" id="btn-copy-confirm">Copy</button>
|
|
309
|
-
</div>
|
|
310
|
-
</div>
|
|
311
|
-
</div>
|
|
312
|
-
</div>
|
|
313
|
-
|
|
314
|
-
<!-- Resize table -->
|
|
315
|
-
<div class="modal fade" id="modal-resize" tabindex="-1" role="dialog">
|
|
316
|
-
<div class="modal-dialog" role="document">
|
|
317
|
-
<div class="modal-content">
|
|
318
|
-
<div class="modal-header">
|
|
319
|
-
<h5 class="modal-title">Resize Active Table</h5>
|
|
320
|
-
<button type="button" class="close" data-dismiss="modal"><span>×</span></button>
|
|
321
|
-
</div>
|
|
322
|
-
<div class="modal-body">
|
|
323
|
-
<p class="text-muted small">Resamples learned corrections onto a new grid. The active table file is overwritten.</p>
|
|
324
|
-
<table class="table table-sm table-borderless">
|
|
325
|
-
<tbody>
|
|
326
|
-
<tr><td id="resize-maxSpeed-label">Max speed (kn)</td><td><input type="number" id="resize-maxSpeed" class="form-control form-control-sm" min="1" max="30" step="1" style="width:80px"></td></tr>
|
|
327
|
-
<tr><td id="resize-speedStep-label">Speed step (kn)</td><td><input type="number" id="resize-speedStep" class="form-control form-control-sm" min="0.1" max="5" step="0.1" style="width:80px"></td></tr>
|
|
328
|
-
<tr><td id="resize-maxHeel-label">Max heel (°)</td><td><input type="number" id="resize-maxHeel" class="form-control form-control-sm" min="1" max="90" step="1" style="width:80px"></td></tr>
|
|
329
|
-
<tr><td id="resize-heelStep-label">Heel step (°)</td><td><input type="number" id="resize-heelStep" class="form-control form-control-sm" min="1" max="30" step="1" style="width:80px"></td></tr>
|
|
330
|
-
</tbody>
|
|
331
|
-
</table>
|
|
332
|
-
</div>
|
|
333
|
-
<div class="modal-footer">
|
|
334
|
-
<span id="modal-resize-status" class="small mr-auto"></span>
|
|
335
|
-
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
|
|
336
|
-
<button type="button" class="btn btn-primary" id="btn-resize-confirm">Resize</button>
|
|
337
|
-
</div>
|
|
338
|
-
</div>
|
|
339
|
-
</div>
|
|
340
|
-
</div>
|
|
341
|
-
|
|
342
|
-
<script type="module" src="app.js"></script>
|
|
343
|
-
|
|
344
|
-
</body>
|
|
345
|
-
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
+
<title>Speed and Current</title>
|
|
8
|
+
<script>
|
|
9
|
+
(async () => {
|
|
10
|
+
const appendStylesheet = (href) => {
|
|
11
|
+
const el = document.createElement('link');
|
|
12
|
+
el.rel = 'stylesheet';
|
|
13
|
+
el.href = href;
|
|
14
|
+
document.head.appendChild(el);
|
|
15
|
+
};
|
|
16
|
+
try {
|
|
17
|
+
const res = await fetch('/admin/.vite/manifest.json');
|
|
18
|
+
if (res.ok) {
|
|
19
|
+
const manifest = await res.json();
|
|
20
|
+
let found = false;
|
|
21
|
+
for (const entry of Object.values(manifest)) {
|
|
22
|
+
if (!entry.isEntry) continue;
|
|
23
|
+
for (const css of entry.css ?? []) {
|
|
24
|
+
appendStylesheet('/admin/' + css);
|
|
25
|
+
found = true;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (found) return;
|
|
29
|
+
}
|
|
30
|
+
} catch (e) {
|
|
31
|
+
// fall through to legacy scrape
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
const res = await fetch('/');
|
|
35
|
+
const html = await res.text();
|
|
36
|
+
const doc = new DOMParser().parseFromString(html, 'text/html');
|
|
37
|
+
const link = doc.querySelector('link[rel="stylesheet"]');
|
|
38
|
+
if (link) {
|
|
39
|
+
appendStylesheet(new URL(link.getAttribute('href'), res.url).href);
|
|
40
|
+
}
|
|
41
|
+
} catch (e) {
|
|
42
|
+
console.warn('[webapp] Could not load SignalK admin stylesheet:', e);
|
|
43
|
+
}
|
|
44
|
+
})();
|
|
45
|
+
</script>
|
|
46
|
+
<link rel="stylesheet" href="main.css">
|
|
47
|
+
</head>
|
|
48
|
+
|
|
49
|
+
<body class="app header-fixed sidebar-fixed aside-menu-fixed aside-menu-hidden">
|
|
50
|
+
|
|
51
|
+
<header class="app-header navbar navbar-light bg-white">
|
|
52
|
+
<div class="d-flex align-items-center">
|
|
53
|
+
<a class="d-flex align-items-center gap-2 me-0 text-decoration-none" href="#">
|
|
54
|
+
<img src="icon.png" alt="" style="height:48px;width:auto">
|
|
55
|
+
<span class="app-title">Speed & Current</span>
|
|
56
|
+
</a>
|
|
57
|
+
<button class="navbar-toggler sidebar-toggler ms-2" type="button" id="sidebarToggler">
|
|
58
|
+
<span class="navbar-toggler-icon"></span>
|
|
59
|
+
</button>
|
|
60
|
+
</div>
|
|
61
|
+
<span id="message" class="small ms-auto me-3"></span>
|
|
62
|
+
</header>
|
|
63
|
+
|
|
64
|
+
<div class="app-body">
|
|
65
|
+
|
|
66
|
+
<div class="sidebar">
|
|
67
|
+
<nav class="sidebar-nav">
|
|
68
|
+
<ul id="main-nav" class="nav">
|
|
69
|
+
<li class="nav-item">
|
|
70
|
+
<a class="nav-link active" href="#" data-section="inputs">Inputs</a>
|
|
71
|
+
</li>
|
|
72
|
+
<li class="nav-item">
|
|
73
|
+
<a class="nav-link" href="#" data-section="estimation">Boatspeed Estimation</a>
|
|
74
|
+
</li>
|
|
75
|
+
<li class="nav-item">
|
|
76
|
+
<a class="nav-link" href="#" data-section="learning">Correction Table Learning</a>
|
|
77
|
+
</li>
|
|
78
|
+
<li class="nav-item">
|
|
79
|
+
<a class="nav-link" href="#" data-section="table">Correction Table</a>
|
|
80
|
+
</li>
|
|
81
|
+
</ul>
|
|
82
|
+
</nav>
|
|
83
|
+
<button class="sidebar-minimizer brand-minimizer" type="button" id="sidebarMinimizer"></button>
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
<main class="main">
|
|
87
|
+
<div class="container-fluid pt-3">
|
|
88
|
+
|
|
89
|
+
<!-- ===== INPUTS SECTION ===== -->
|
|
90
|
+
<div id="section-inputs" class="app-section">
|
|
91
|
+
<div class="card">
|
|
92
|
+
<div class="card-header fw-bold text-uppercase">Inputs</div>
|
|
93
|
+
<div class="card-body">
|
|
94
|
+
<h6 class="text-uppercase fw-bold text-muted border-bottom pb-1 mb-1 small">Settings</h6>
|
|
95
|
+
<table class="table table-sm table-borderless mb-0" id="inputs-settings-table">
|
|
96
|
+
<tbody></tbody>
|
|
97
|
+
</table>
|
|
98
|
+
<h6 class="text-uppercase fw-bold text-muted border-bottom pb-1 mt-3 mb-1 small">Live Values</h6>
|
|
99
|
+
<div id="inputs-values"></div>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
<!-- ===== BOATSPEED ESTIMATION SECTION ===== -->
|
|
105
|
+
<div id="section-estimation" class="app-section" style="display:none">
|
|
106
|
+
<div class="card">
|
|
107
|
+
<div class="card-header d-flex align-items-center justify-content-between">
|
|
108
|
+
<span class="fw-bold text-uppercase">Boatspeed Estimation</span>
|
|
109
|
+
<label class="switch switch-text switch-primary mb-0" id="toggle-estimateBoatSpeed-label" title="Enable boatspeed estimation">
|
|
110
|
+
<input type="checkbox" class="switch-input form-check-input" id="toggle-estimateBoatSpeed">
|
|
111
|
+
<span class="switch-label" data-on="On" data-off="Off"></span>
|
|
112
|
+
<span class="switch-handle"></span>
|
|
113
|
+
</label>
|
|
114
|
+
</div>
|
|
115
|
+
<div class="card-body">
|
|
116
|
+
<h6 class="text-uppercase fw-bold text-muted border-bottom pb-1 mb-1 small">Settings</h6>
|
|
117
|
+
<table class="table table-sm table-borderless mb-0" id="estimation-settings-table">
|
|
118
|
+
<tbody></tbody>
|
|
119
|
+
</table>
|
|
120
|
+
<h6 class="text-uppercase fw-bold text-muted border-bottom pb-1 mt-3 mb-1 small">Inputs</h6>
|
|
121
|
+
<div id="estimation-inputs"></div>
|
|
122
|
+
<h6 class="text-uppercase fw-bold text-muted border-bottom pb-1 mt-3 mb-1 small">Intermediates</h6>
|
|
123
|
+
<div id="estimation-intermediates"></div>
|
|
124
|
+
<h6 class="text-uppercase fw-bold text-muted border-bottom pb-1 mt-3 mb-1 small">Outputs</h6>
|
|
125
|
+
<div id="estimation-outputs"></div>
|
|
126
|
+
<div id="estimation-warnings"></div>
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
|
|
131
|
+
<!-- ===== CORRECTION TABLE LEARNING SECTION ===== -->
|
|
132
|
+
<div id="section-learning" class="app-section" style="display:none">
|
|
133
|
+
<div class="card">
|
|
134
|
+
<div class="card-header d-flex align-items-center justify-content-between">
|
|
135
|
+
<span class="fw-bold text-uppercase">Correction Table Learning</span>
|
|
136
|
+
<label class="switch switch-text switch-primary mb-0" id="toggle-updateCorrectionTable-label" title="Enable correction table learning">
|
|
137
|
+
<input type="checkbox" class="switch-input form-check-input" id="toggle-updateCorrectionTable">
|
|
138
|
+
<span class="switch-label" data-on="On" data-off="Off"></span>
|
|
139
|
+
<span class="switch-handle"></span>
|
|
140
|
+
</label>
|
|
141
|
+
</div>
|
|
142
|
+
<div class="card-body">
|
|
143
|
+
<h6 class="text-uppercase fw-bold text-muted border-bottom pb-1 mb-1 small">Settings</h6>
|
|
144
|
+
<table class="table table-sm table-borderless mb-0" id="learning-settings-table">
|
|
145
|
+
<tbody></tbody>
|
|
146
|
+
</table>
|
|
147
|
+
<h6 class="text-uppercase fw-bold text-muted border-bottom pb-1 mt-3 mb-1 small">Smoother</h6>
|
|
148
|
+
<table class="table table-sm table-borderless mb-0" id="smoother-settings-table">
|
|
149
|
+
<tbody></tbody>
|
|
150
|
+
</table>
|
|
151
|
+
<h6 class="text-uppercase fw-bold text-muted border-bottom pb-1 mt-3 mb-1 small">Inputs</h6>
|
|
152
|
+
<div id="learning-inputs"></div>
|
|
153
|
+
<h6 class="text-uppercase fw-bold text-muted border-bottom pb-1 mt-3 mb-1 small">Status</h6>
|
|
154
|
+
<table class="table table-sm table-borderless mb-0" id="learning-status-table">
|
|
155
|
+
<tbody></tbody>
|
|
156
|
+
</table>
|
|
157
|
+
<div id="learning-warnings"></div>
|
|
158
|
+
</div>
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
|
|
162
|
+
<!-- ===== CORRECTION TABLE SECTION ===== -->
|
|
163
|
+
<div id="section-table" class="app-section" style="display:none">
|
|
164
|
+
|
|
165
|
+
<div class="card">
|
|
166
|
+
<div class="card-header" style="position:relative">
|
|
167
|
+
<span class="fw-bold text-uppercase">Correction Table</span>
|
|
168
|
+
<span id="active-table-name" class="ms-2 text-muted small"></span>
|
|
169
|
+
<div style="position:absolute;right:1rem;top:0;bottom:0;display:flex;align-items:center;gap:0.5rem;">
|
|
170
|
+
<button id="btn-tbl-new" class="btn btn-sm btn-outline-secondary">New</button>
|
|
171
|
+
<button id="btn-tbl-load" class="btn btn-sm btn-outline-secondary">Load</button>
|
|
172
|
+
<button id="btn-tbl-copy" class="btn btn-sm btn-outline-secondary">Copy</button>
|
|
173
|
+
<button id="btn-tbl-resize" class="btn btn-sm btn-outline-secondary">Resize</button>
|
|
174
|
+
</div>
|
|
175
|
+
</div>
|
|
176
|
+
<div class="card-body">
|
|
177
|
+
<div class="table-legend small text-muted mb-3">
|
|
178
|
+
<div class="table-legend-row">
|
|
179
|
+
<span class="legend-swatch legend-swatch--green"></span>
|
|
180
|
+
<span><strong>Green</strong> — paddlewheel reads slow (factor > 1, speed is corrected up)</span>
|
|
181
|
+
</div>
|
|
182
|
+
<div class="table-legend-row">
|
|
183
|
+
<span class="legend-swatch legend-swatch--orange"></span>
|
|
184
|
+
<span><strong>Orange</strong> — paddlewheel reads fast (factor < 1, speed is corrected down)</span>
|
|
185
|
+
</div>
|
|
186
|
+
<div class="table-legend-row">
|
|
187
|
+
<span class="legend-swatch legend-swatch--stripe"></span>
|
|
188
|
+
<span><strong>Stripe angle</strong> — encodes leeway direction; stripes are tilted proportionally to the leeway correction (°)</span>
|
|
189
|
+
</div>
|
|
190
|
+
<div class="table-legend-row">
|
|
191
|
+
<span class="legend-swatch legend-swatch--active">+1.2%</span>
|
|
192
|
+
<span><strong>Blue text</strong> — most recently updated cell</span>
|
|
193
|
+
</div>
|
|
194
|
+
<div class="table-legend-row">
|
|
195
|
+
<span class="legend-swatch legend-swatch--neighbour"></span>
|
|
196
|
+
<span><strong>Faint tint</strong> — interpolation neighbours used in the current estimate</span>
|
|
197
|
+
</div>
|
|
198
|
+
<div class="table-legend-row mt-1">
|
|
199
|
+
<span class="legend-label-wide">Cell values:</span>
|
|
200
|
+
<span>top = speed-correction factor (±%), bottom = leeway correction (±°)</span>
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
203
|
+
<div id="table-container"></div>
|
|
204
|
+
</div>
|
|
205
|
+
</div>
|
|
206
|
+
</div>
|
|
207
|
+
|
|
208
|
+
</div>
|
|
209
|
+
</main>
|
|
210
|
+
</div>
|
|
211
|
+
|
|
212
|
+
<script>
|
|
213
|
+
document.getElementById('sidebarMinimizer').addEventListener('click', () => {
|
|
214
|
+
document.body.classList.toggle('sidebar-minimized');
|
|
215
|
+
document.body.classList.toggle('brand-minimized');
|
|
216
|
+
});
|
|
217
|
+
const isMobile = () => window.matchMedia('(max-width: 767.98px)').matches;
|
|
218
|
+
document.getElementById('sidebarToggler').addEventListener('click', () => {
|
|
219
|
+
if (isMobile()) {
|
|
220
|
+
document.body.classList.toggle('sidebar-mobile-show');
|
|
221
|
+
} else {
|
|
222
|
+
document.body.classList.toggle('sidebar-hidden');
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
document.querySelectorAll('#main-nav .nav-link').forEach(link => {
|
|
226
|
+
link.addEventListener('click', e => {
|
|
227
|
+
e.preventDefault();
|
|
228
|
+
document.querySelectorAll('#main-nav .nav-link').forEach(l => l.classList.remove('active'));
|
|
229
|
+
link.classList.add('active');
|
|
230
|
+
const target = link.dataset.section;
|
|
231
|
+
document.querySelectorAll('.app-section').forEach(s => s.style.display = 'none');
|
|
232
|
+
document.getElementById('section-' + target).style.display = '';
|
|
233
|
+
if (isMobile()) document.body.classList.remove('sidebar-mobile-show');
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
</script>
|
|
237
|
+
|
|
238
|
+
<!-- ===== TABLE MANAGER MODALS ===== -->
|
|
239
|
+
|
|
240
|
+
<!-- Create table -->
|
|
241
|
+
<div class="modal fade" id="modal-create" tabindex="-1" role="dialog">
|
|
242
|
+
<div class="modal-dialog" role="document">
|
|
243
|
+
<div class="modal-content">
|
|
244
|
+
<div class="modal-header">
|
|
245
|
+
<h5 class="modal-title">New Correction Table</h5>
|
|
246
|
+
<button type="button" class="close" data-dismiss="modal"><span>×</span></button>
|
|
247
|
+
</div>
|
|
248
|
+
<div class="modal-body">
|
|
249
|
+
<table class="table table-sm table-borderless">
|
|
250
|
+
<tbody>
|
|
251
|
+
<tr><td>Name</td><td><input type="text" id="create-name" class="form-control form-control-sm" placeholder="e.g. table-2026" style="width:180px"></td></tr>
|
|
252
|
+
<tr><td id="create-maxSpeed-label">Max speed (kn)</td><td><input type="number" id="create-maxSpeed" class="form-control form-control-sm" value="9" min="1" max="30" step="1" style="width:80px"></td></tr>
|
|
253
|
+
<tr><td id="create-speedStep-label">Speed step (kn)</td><td><input type="number" id="create-speedStep" class="form-control form-control-sm" value="1" min="0.1" max="5" step="0.1" style="width:80px"></td></tr>
|
|
254
|
+
<tr><td id="create-maxHeel-label">Max heel (°)</td><td><input type="number" id="create-maxHeel" class="form-control form-control-sm" value="32" min="1" max="90" step="1" style="width:80px"></td></tr>
|
|
255
|
+
<tr><td id="create-heelStep-label">Heel step (°)</td><td><input type="number" id="create-heelStep" class="form-control form-control-sm" value="8" min="1" max="30" step="1" style="width:80px"></td></tr>
|
|
256
|
+
</tbody>
|
|
257
|
+
</table>
|
|
258
|
+
</div>
|
|
259
|
+
<div class="modal-footer">
|
|
260
|
+
<span id="modal-create-status" class="small text-danger mr-auto"></span>
|
|
261
|
+
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
|
|
262
|
+
<button type="button" class="btn btn-primary" id="btn-create-confirm">Create</button>
|
|
263
|
+
</div>
|
|
264
|
+
</div>
|
|
265
|
+
</div>
|
|
266
|
+
</div>
|
|
267
|
+
|
|
268
|
+
<!-- Load table -->
|
|
269
|
+
<div class="modal fade" id="modal-load" tabindex="-1" role="dialog">
|
|
270
|
+
<div class="modal-dialog" role="document">
|
|
271
|
+
<div class="modal-content">
|
|
272
|
+
<div class="modal-header">
|
|
273
|
+
<h5 class="modal-title">Load Correction Table</h5>
|
|
274
|
+
<button type="button" class="close" data-dismiss="modal"><span>×</span></button>
|
|
275
|
+
</div>
|
|
276
|
+
<div class="modal-body">
|
|
277
|
+
<p class="text-muted small">Select a saved table to load and activate.</p>
|
|
278
|
+
<ul id="table-list" class="list-group"></ul>
|
|
279
|
+
</div>
|
|
280
|
+
<div class="modal-footer">
|
|
281
|
+
<span id="modal-load-status" class="small text-danger mr-auto"></span>
|
|
282
|
+
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
|
|
283
|
+
<button type="button" class="btn btn-primary" id="btn-load-confirm" disabled>Load</button>
|
|
284
|
+
</div>
|
|
285
|
+
</div>
|
|
286
|
+
</div>
|
|
287
|
+
</div>
|
|
288
|
+
|
|
289
|
+
<!-- Copy table -->
|
|
290
|
+
<div class="modal fade" id="modal-copy" tabindex="-1" role="dialog">
|
|
291
|
+
<div class="modal-dialog" role="document">
|
|
292
|
+
<div class="modal-content">
|
|
293
|
+
<div class="modal-header">
|
|
294
|
+
<h5 class="modal-title">Copy Active Table</h5>
|
|
295
|
+
<button type="button" class="close" data-dismiss="modal"><span>×</span></button>
|
|
296
|
+
</div>
|
|
297
|
+
<div class="modal-body">
|
|
298
|
+
<p class="text-muted small">Save the active table under a new name and activate it.</p>
|
|
299
|
+
<table class="table table-sm table-borderless">
|
|
300
|
+
<tbody>
|
|
301
|
+
<tr><td>New name</td><td><input type="text" id="copy-name" class="form-control form-control-sm" placeholder="e.g. backup-2026" style="width:180px"></td></tr>
|
|
302
|
+
</tbody>
|
|
303
|
+
</table>
|
|
304
|
+
</div>
|
|
305
|
+
<div class="modal-footer">
|
|
306
|
+
<span id="modal-copy-status" class="small text-danger mr-auto"></span>
|
|
307
|
+
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
|
|
308
|
+
<button type="button" class="btn btn-primary" id="btn-copy-confirm">Copy</button>
|
|
309
|
+
</div>
|
|
310
|
+
</div>
|
|
311
|
+
</div>
|
|
312
|
+
</div>
|
|
313
|
+
|
|
314
|
+
<!-- Resize table -->
|
|
315
|
+
<div class="modal fade" id="modal-resize" tabindex="-1" role="dialog">
|
|
316
|
+
<div class="modal-dialog" role="document">
|
|
317
|
+
<div class="modal-content">
|
|
318
|
+
<div class="modal-header">
|
|
319
|
+
<h5 class="modal-title">Resize Active Table</h5>
|
|
320
|
+
<button type="button" class="close" data-dismiss="modal"><span>×</span></button>
|
|
321
|
+
</div>
|
|
322
|
+
<div class="modal-body">
|
|
323
|
+
<p class="text-muted small">Resamples learned corrections onto a new grid. The active table file is overwritten.</p>
|
|
324
|
+
<table class="table table-sm table-borderless">
|
|
325
|
+
<tbody>
|
|
326
|
+
<tr><td id="resize-maxSpeed-label">Max speed (kn)</td><td><input type="number" id="resize-maxSpeed" class="form-control form-control-sm" min="1" max="30" step="1" style="width:80px"></td></tr>
|
|
327
|
+
<tr><td id="resize-speedStep-label">Speed step (kn)</td><td><input type="number" id="resize-speedStep" class="form-control form-control-sm" min="0.1" max="5" step="0.1" style="width:80px"></td></tr>
|
|
328
|
+
<tr><td id="resize-maxHeel-label">Max heel (°)</td><td><input type="number" id="resize-maxHeel" class="form-control form-control-sm" min="1" max="90" step="1" style="width:80px"></td></tr>
|
|
329
|
+
<tr><td id="resize-heelStep-label">Heel step (°)</td><td><input type="number" id="resize-heelStep" class="form-control form-control-sm" min="1" max="30" step="1" style="width:80px"></td></tr>
|
|
330
|
+
</tbody>
|
|
331
|
+
</table>
|
|
332
|
+
</div>
|
|
333
|
+
<div class="modal-footer">
|
|
334
|
+
<span id="modal-resize-status" class="small mr-auto"></span>
|
|
335
|
+
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
|
|
336
|
+
<button type="button" class="btn btn-primary" id="btn-resize-confirm">Resize</button>
|
|
337
|
+
</div>
|
|
338
|
+
</div>
|
|
339
|
+
</div>
|
|
340
|
+
</div>
|
|
341
|
+
|
|
342
|
+
<script type="module" src="app.js"></script>
|
|
343
|
+
|
|
344
|
+
</body>
|
|
345
|
+
|
|
346
346
|
</html>
|