signalk-performance-processor 0.0.0-stage → 0.0.1
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/CHANGELOG.md +12 -0
- package/LICENSE +21 -0
- package/README.md +114 -2
- package/index.js +368 -0
- package/lib/defaults.js +47 -0
- package/lib/processor.js +287 -0
- package/package.json +44 -4
- package/public/app.js +328 -0
- package/public/icon.svg +15 -0
- package/public/index.html +86 -0
- package/public/style.css +208 -0
|
@@ -0,0 +1,86 @@
|
|
|
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>Performance Processor</title>
|
|
7
|
+
<link rel="icon" href="icon.svg" type="image/svg+xml">
|
|
8
|
+
<link rel="stylesheet" href="style.css">
|
|
9
|
+
</head>
|
|
10
|
+
<body>
|
|
11
|
+
<header>
|
|
12
|
+
<div class="title">
|
|
13
|
+
<h1>Performance Processor</h1>
|
|
14
|
+
<span id="mode" class="badge">--</span>
|
|
15
|
+
</div>
|
|
16
|
+
<nav aria-label="Sections">
|
|
17
|
+
<a href="#live" data-view="live">Live</a>
|
|
18
|
+
<a href="#calibration" data-view="calibration">Calibration</a>
|
|
19
|
+
<a href="#settings" data-view="settings">Settings</a>
|
|
20
|
+
</nav>
|
|
21
|
+
<div id="status" class="muted status"></div>
|
|
22
|
+
<div id="conn" class="warn" hidden></div>
|
|
23
|
+
</header>
|
|
24
|
+
|
|
25
|
+
<div id="saveBar" class="save-bar" hidden>
|
|
26
|
+
<span>Unsaved changes</span>
|
|
27
|
+
<div id="saveErrors" class="errors"></div>
|
|
28
|
+
<button id="revert" type="button" class="ghost">Revert</button>
|
|
29
|
+
<button id="save" type="button" class="primary">Save</button>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<main>
|
|
33
|
+
<section id="view-live" class="view" hidden>
|
|
34
|
+
<div id="tiles" class="tiles"></div>
|
|
35
|
+
<div class="panel">
|
|
36
|
+
<div class="panel-head"><h2>Pipeline</h2><span id="liveAge" class="muted sub"></span></div>
|
|
37
|
+
<div class="table-wrap">
|
|
38
|
+
<table>
|
|
39
|
+
<thead><tr><th>Step</th><th>In</th><th>Correction</th><th>Out</th></tr></thead>
|
|
40
|
+
<tbody id="steps"><tr><td colspan="4" class="muted center">Waiting for data…</td></tr></tbody>
|
|
41
|
+
</table>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
<div class="panel">
|
|
45
|
+
<div class="panel-head"><h2>Inputs</h2></div>
|
|
46
|
+
<div class="table-wrap">
|
|
47
|
+
<table>
|
|
48
|
+
<thead><tr><th>Path</th><th>Source</th><th class="num">Age</th></tr></thead>
|
|
49
|
+
<tbody id="inputs"></tbody>
|
|
50
|
+
</table>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
</section>
|
|
54
|
+
|
|
55
|
+
<section id="view-calibration" class="view" hidden>
|
|
56
|
+
<p class="muted sub intro">Corrections run top to bottom. Tick to enable; select one to edit its values.</p>
|
|
57
|
+
<div class="cal">
|
|
58
|
+
<ul id="corrList" class="corr-list" aria-label="Corrections"></ul>
|
|
59
|
+
<div id="corrEditor" class="panel corr-editor"></div>
|
|
60
|
+
</div>
|
|
61
|
+
</section>
|
|
62
|
+
|
|
63
|
+
<section id="view-settings" class="view" hidden>
|
|
64
|
+
<div class="panel">
|
|
65
|
+
<h2>Output</h2>
|
|
66
|
+
<fieldset id="modes" class="modes">
|
|
67
|
+
<legend class="sr-only">Publish mode</legend>
|
|
68
|
+
</fieldset>
|
|
69
|
+
</div>
|
|
70
|
+
<div class="panel">
|
|
71
|
+
<h2>Magnetic variation</h2>
|
|
72
|
+
<label class="field">Degrees east, for true heading
|
|
73
|
+
<input id="variation" type="number" step="0.1" data-path="variation">
|
|
74
|
+
</label>
|
|
75
|
+
</div>
|
|
76
|
+
<div class="panel">
|
|
77
|
+
<h2>Input sources</h2>
|
|
78
|
+
<p class="muted sub">Choose the instrument to read for each input. "Any" takes every source except this plugin. Pick your instruments, not another correcting plugin, so nothing is corrected twice.</p>
|
|
79
|
+
<div id="sources" class="sources"></div>
|
|
80
|
+
</div>
|
|
81
|
+
</section>
|
|
82
|
+
</main>
|
|
83
|
+
|
|
84
|
+
<script src="app.js"></script>
|
|
85
|
+
</body>
|
|
86
|
+
</html>
|
package/public/style.css
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--bg: #0d1117;
|
|
3
|
+
--panel: #161b22;
|
|
4
|
+
--fg: #e6edf3;
|
|
5
|
+
--muted: #8b949e;
|
|
6
|
+
--line: #30363d;
|
|
7
|
+
--active: #1f3a5f;
|
|
8
|
+
--good: #4cd97b;
|
|
9
|
+
--bad: #ff6b6b;
|
|
10
|
+
--warn: #f0b429;
|
|
11
|
+
--warn-bg: #3d2e05;
|
|
12
|
+
--accent: #58a6ff;
|
|
13
|
+
--cell: #12202d;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@media (prefers-color-scheme: light) {
|
|
17
|
+
:root {
|
|
18
|
+
--bg: #ffffff;
|
|
19
|
+
--panel: #f6f8fa;
|
|
20
|
+
--fg: #1f2328;
|
|
21
|
+
--muted: #656d76;
|
|
22
|
+
--line: #d0d7de;
|
|
23
|
+
--active: #dbeafe;
|
|
24
|
+
--good: #1b7f3b;
|
|
25
|
+
--bad: #c62828;
|
|
26
|
+
--warn: #8a5a00;
|
|
27
|
+
--warn-bg: #fff4d6;
|
|
28
|
+
--accent: #0969da;
|
|
29
|
+
--cell: #eef4fb;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
* { box-sizing: border-box; }
|
|
34
|
+
[hidden] { display: none !important; }
|
|
35
|
+
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
|
|
36
|
+
|
|
37
|
+
body {
|
|
38
|
+
margin: 0;
|
|
39
|
+
padding: 16px;
|
|
40
|
+
background: var(--bg);
|
|
41
|
+
color: var(--fg);
|
|
42
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
43
|
+
font-size: 18px;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
header {
|
|
47
|
+
display: flex;
|
|
48
|
+
flex-wrap: wrap;
|
|
49
|
+
align-items: center;
|
|
50
|
+
gap: 8px 20px;
|
|
51
|
+
margin-bottom: 16px;
|
|
52
|
+
}
|
|
53
|
+
.title { display: flex; align-items: baseline; gap: 10px; }
|
|
54
|
+
h1 { margin: 0; font-size: 1.4em; }
|
|
55
|
+
h2 { margin: 0 0 10px; font-size: 0.85em; color: var(--muted); text-transform: uppercase; font-weight: 600; }
|
|
56
|
+
.status { width: 100%; font-size: 0.8em; }
|
|
57
|
+
|
|
58
|
+
nav { display: flex; gap: 4px; }
|
|
59
|
+
nav a {
|
|
60
|
+
padding: 10px 14px;
|
|
61
|
+
border: 1px solid var(--line);
|
|
62
|
+
border-radius: 8px;
|
|
63
|
+
color: var(--fg);
|
|
64
|
+
text-decoration: none;
|
|
65
|
+
font-size: 0.9em;
|
|
66
|
+
}
|
|
67
|
+
nav a:hover { border-color: var(--accent); }
|
|
68
|
+
nav a.current { background: var(--active); border-color: var(--accent); font-weight: 600; }
|
|
69
|
+
|
|
70
|
+
.badge {
|
|
71
|
+
font-size: 0.7em;
|
|
72
|
+
font-weight: 700;
|
|
73
|
+
padding: 3px 8px;
|
|
74
|
+
border-radius: 999px;
|
|
75
|
+
background: var(--warn-bg);
|
|
76
|
+
color: var(--warn);
|
|
77
|
+
text-transform: uppercase;
|
|
78
|
+
}
|
|
79
|
+
.badge.replace { background: var(--active); color: var(--accent); }
|
|
80
|
+
|
|
81
|
+
.muted { color: var(--muted); }
|
|
82
|
+
.sub { font-size: 0.8em; }
|
|
83
|
+
.warn { color: var(--warn); font-weight: 600; }
|
|
84
|
+
.center { text-align: center; }
|
|
85
|
+
.num { text-align: right; font-variant-numeric: tabular-nums; }
|
|
86
|
+
.intro { margin: 0 0 12px; }
|
|
87
|
+
|
|
88
|
+
main { display: flex; flex-direction: column; gap: 16px; }
|
|
89
|
+
.view { display: flex; flex-direction: column; gap: 16px; }
|
|
90
|
+
|
|
91
|
+
.panel { border: 1px solid var(--line); border-radius: 8px; padding: 12px; background: var(--panel); }
|
|
92
|
+
.panel-head { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; }
|
|
93
|
+
|
|
94
|
+
/* Live */
|
|
95
|
+
.tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; }
|
|
96
|
+
.tile { border: 1px solid var(--line); border-radius: 8px; padding: 10px 12px; background: var(--panel); }
|
|
97
|
+
.tile .label { font-size: 0.7em; color: var(--muted); text-transform: uppercase; font-weight: 600; }
|
|
98
|
+
.tile .value { font-size: 1.9em; font-weight: 600; font-variant-numeric: tabular-nums; }
|
|
99
|
+
.tile .unit { font-size: 0.5em; color: var(--muted); font-weight: 400; margin-left: 4px; }
|
|
100
|
+
.tile .raw { font-size: 0.7em; color: var(--muted); font-variant-numeric: tabular-nums; }
|
|
101
|
+
|
|
102
|
+
.table-wrap { overflow-x: auto; }
|
|
103
|
+
table { width: 100%; border-collapse: collapse; }
|
|
104
|
+
th, td { padding: 8px; border-bottom: 1px solid var(--line); white-space: nowrap; text-align: left; }
|
|
105
|
+
th { font-size: 0.75em; color: var(--muted); text-transform: uppercase; }
|
|
106
|
+
tr.off td { color: var(--muted); }
|
|
107
|
+
td.mono { font-variant-numeric: tabular-nums; }
|
|
108
|
+
|
|
109
|
+
/* Calibration */
|
|
110
|
+
.cal { display: flex; flex-wrap: wrap; gap: 16px; align-items: flex-start; }
|
|
111
|
+
.corr-list {
|
|
112
|
+
list-style: none;
|
|
113
|
+
margin: 0;
|
|
114
|
+
padding: 4px 0;
|
|
115
|
+
flex: 1 1 240px;
|
|
116
|
+
border: 1px solid var(--line);
|
|
117
|
+
border-radius: 8px;
|
|
118
|
+
background: var(--panel);
|
|
119
|
+
}
|
|
120
|
+
.corr-list li { display: flex; align-items: center; }
|
|
121
|
+
.corr-list li.selected { background: var(--active); box-shadow: inset 3px 0 0 var(--accent); }
|
|
122
|
+
.corr-list button {
|
|
123
|
+
flex: 1;
|
|
124
|
+
min-height: 48px;
|
|
125
|
+
padding: 6px 12px;
|
|
126
|
+
background: transparent;
|
|
127
|
+
color: var(--fg);
|
|
128
|
+
border: 0;
|
|
129
|
+
text-align: left;
|
|
130
|
+
font: inherit;
|
|
131
|
+
font-weight: 600;
|
|
132
|
+
cursor: pointer;
|
|
133
|
+
}
|
|
134
|
+
.corr-list label { display: flex; align-items: center; justify-content: center; width: 52px; min-height: 48px; cursor: pointer; }
|
|
135
|
+
.corr-list input, .modes input { width: 20px; height: 20px; accent-color: var(--accent); }
|
|
136
|
+
|
|
137
|
+
.corr-editor { flex: 999 1 520px; min-width: 0; display: flex; flex-direction: column; gap: 12px; }
|
|
138
|
+
.corr-editor h2 { font-size: 1.1em; color: var(--fg); text-transform: none; margin: 0; }
|
|
139
|
+
.corr-editor .what { margin: 0; font-size: 0.85em; }
|
|
140
|
+
.state { font-size: 0.8em; font-weight: 600; color: var(--muted); }
|
|
141
|
+
.state.on { color: var(--good); }
|
|
142
|
+
|
|
143
|
+
.grid input, .field input, .sources select {
|
|
144
|
+
font: inherit;
|
|
145
|
+
font-size: 0.9em;
|
|
146
|
+
background: var(--bg);
|
|
147
|
+
color: var(--fg);
|
|
148
|
+
border: 1px solid var(--line);
|
|
149
|
+
border-radius: 6px;
|
|
150
|
+
padding: 6px 8px;
|
|
151
|
+
}
|
|
152
|
+
.grid { border-collapse: separate; border-spacing: 4px; width: auto; }
|
|
153
|
+
.grid th, .grid td { border: 0; padding: 0; }
|
|
154
|
+
.grid th { padding: 0 6px; }
|
|
155
|
+
.grid input { width: 4.6em; text-align: right; font-variant-numeric: tabular-nums; background: var(--cell); }
|
|
156
|
+
.grid input.axis { background: var(--bg); color: var(--muted); }
|
|
157
|
+
.grid input:focus, .field input:focus { outline: 2px solid var(--accent); outline-offset: 0; }
|
|
158
|
+
.fields { display: flex; flex-wrap: wrap; gap: 16px; }
|
|
159
|
+
.field { display: flex; flex-direction: column; gap: 4px; font-size: 0.8em; font-weight: 600; }
|
|
160
|
+
.field input { width: 7em; }
|
|
161
|
+
|
|
162
|
+
/* Settings */
|
|
163
|
+
.modes { border: 0; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
|
|
164
|
+
.mode { display: flex; gap: 12px; align-items: flex-start; padding: 10px; border: 1px solid var(--line); border-radius: 8px; cursor: pointer; }
|
|
165
|
+
.mode.checked { border-color: var(--accent); background: var(--active); }
|
|
166
|
+
.mode b { display: block; }
|
|
167
|
+
.mode span { font-size: 0.8em; color: var(--muted); }
|
|
168
|
+
.sources { display: flex; flex-direction: column; }
|
|
169
|
+
.sources label { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: 8px; padding: 8px 0; border-top: 1px solid var(--line); }
|
|
170
|
+
.sources select { min-width: 12em; min-height: 40px; }
|
|
171
|
+
|
|
172
|
+
/* Save bar stays in view while editing */
|
|
173
|
+
.save-bar {
|
|
174
|
+
position: sticky;
|
|
175
|
+
top: 0;
|
|
176
|
+
z-index: 2;
|
|
177
|
+
display: flex;
|
|
178
|
+
flex-wrap: wrap;
|
|
179
|
+
align-items: center;
|
|
180
|
+
gap: 8px 12px;
|
|
181
|
+
padding: 8px 12px;
|
|
182
|
+
margin-bottom: 12px;
|
|
183
|
+
border: 1px solid var(--warn);
|
|
184
|
+
border-radius: 8px;
|
|
185
|
+
background: var(--warn-bg);
|
|
186
|
+
color: var(--warn);
|
|
187
|
+
font-weight: 600;
|
|
188
|
+
}
|
|
189
|
+
.save-bar .errors { flex: 1; font-weight: 400; font-size: 0.8em; color: var(--bad); }
|
|
190
|
+
button.primary, button.ghost {
|
|
191
|
+
min-height: 44px;
|
|
192
|
+
padding: 0 16px;
|
|
193
|
+
font: inherit;
|
|
194
|
+
font-size: 0.9em;
|
|
195
|
+
font-weight: 600;
|
|
196
|
+
border-radius: 8px;
|
|
197
|
+
cursor: pointer;
|
|
198
|
+
}
|
|
199
|
+
button.primary { background: var(--accent); color: var(--bg); border: 1px solid var(--accent); }
|
|
200
|
+
button.ghost { background: transparent; color: var(--fg); border: 1px solid var(--line); }
|
|
201
|
+
button:disabled { opacity: 0.5; cursor: default; }
|
|
202
|
+
|
|
203
|
+
@media (max-width: 600px) {
|
|
204
|
+
body { font-size: 16px; padding: 10px; }
|
|
205
|
+
th, td { padding: 6px 5px; }
|
|
206
|
+
.tile .value { font-size: 1.5em; }
|
|
207
|
+
.grid input { width: 3.8em; }
|
|
208
|
+
}
|