@marineyachtradar/signalk-playback-plugin 0.1.1 → 0.2.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/README.md +8 -13
- package/package.json +3 -3
- package/public/api.js +402 -0
- package/public/assets/MaYaRa_RED.png +0 -0
- package/public/base.css +91 -0
- package/public/control.html +23 -0
- package/public/control.js +1155 -0
- package/public/controls.css +538 -0
- package/public/discovery.css +478 -0
- package/public/favicon.ico +0 -0
- package/public/index.html +10 -0
- package/public/layout.css +87 -0
- package/public/mayara.js +510 -0
- package/public/playback.html +572 -0
- package/public/proto/RadarMessage.proto +41 -0
- package/public/protobuf/protobuf.js +9112 -0
- package/public/protobuf/protobuf.js.map +1 -0
- package/public/protobuf/protobuf.min.js +8 -0
- package/public/protobuf/protobuf.min.js.map +1 -0
- package/public/radar.svg +29 -0
- package/public/render_webgpu.js +886 -0
- package/public/responsive.css +29 -0
- package/public/van-1.5.2.debug.js +126 -0
- package/public/van-1.5.2.js +140 -0
- package/public/van-1.5.2.min.js +1 -0
- package/public/viewer.html +30 -0
- package/public/viewer.js +797 -0
- package/build.js +0 -248
package/README.md
CHANGED
|
@@ -119,27 +119,22 @@ This plugin:
|
|
|
119
119
|
|
|
120
120
|
## Development
|
|
121
121
|
|
|
122
|
-
|
|
122
|
+
After cloning, install dependencies and build the GUI:
|
|
123
123
|
|
|
124
124
|
```bash
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
# Build with local mayara-gui (for development)
|
|
129
|
-
node build.js --local-gui
|
|
130
|
-
|
|
131
|
-
# Create tarball for manual install (includes public/)
|
|
132
|
-
node build.js --local-gui --pack
|
|
125
|
+
npm install
|
|
126
|
+
npm run build
|
|
133
127
|
```
|
|
134
128
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
The `--pack` option creates a `.tgz` tarball with `public/` included (normally excluded by `.npmignore`). Install with:
|
|
129
|
+
To use a local `mayara-gui` checkout (sibling directory) instead of npm:
|
|
138
130
|
|
|
139
131
|
```bash
|
|
140
|
-
npm
|
|
132
|
+
npm run build -- --local-gui
|
|
141
133
|
```
|
|
142
134
|
|
|
135
|
+
> **Note:** The `public/` directory is gitignored but included in the npm tarball.
|
|
136
|
+
> It's built automatically during `npm publish` via `prepublishOnly`.
|
|
137
|
+
|
|
143
138
|
## Related Projects
|
|
144
139
|
|
|
145
140
|
- **[mayara-server](https://github.com/MaYaRa-Marine/mayara-server)** - Standalone radar server (creates recordings)
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@marineyachtradar/signalk-playback-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "MaYaRa Radar Playback - Play .mrr radar recordings through SignalK Radar API (Developer Tool)",
|
|
5
5
|
"main": "plugin/index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"build": "node build.js",
|
|
8
|
-
"
|
|
8
|
+
"prepublishOnly": "node build.js",
|
|
9
9
|
"test": "echo \"No tests yet\" && exit 0"
|
|
10
10
|
},
|
|
11
11
|
"keywords": [
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
},
|
|
30
30
|
"files": [
|
|
31
31
|
"plugin/**/*",
|
|
32
|
-
"
|
|
32
|
+
"public/**/*"
|
|
33
33
|
],
|
|
34
34
|
"engines": {
|
|
35
35
|
"signalk": ">=2.0.0"
|
package/public/api.js
ADDED
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API adapter for Mayara Radar v5
|
|
3
|
+
*
|
|
4
|
+
* Automatically detects whether running in SignalK or standalone mode
|
|
5
|
+
* and provides a unified API interface for the capabilities-driven v5 API.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// API endpoints for different modes
|
|
9
|
+
const SIGNALK_RADARS_API = "/signalk/v2/api/vessels/self/radars";
|
|
10
|
+
const STANDALONE_RADARS_API = "/v2/api/radars";
|
|
11
|
+
const STANDALONE_INTERFACES_API = "/v2/api/interfaces";
|
|
12
|
+
|
|
13
|
+
// Application Data API path - aligned with WASM SignalK plugin
|
|
14
|
+
// Uses same path so settings are shared between standalone and SignalK modes
|
|
15
|
+
const APPDATA_PATH = "/signalk/v1/applicationData/global/@mayara/signalk-radar";
|
|
16
|
+
|
|
17
|
+
// Detected mode (null = not detected yet)
|
|
18
|
+
let detectedMode = null;
|
|
19
|
+
|
|
20
|
+
// Cache for capabilities (fetched once per radar)
|
|
21
|
+
const capabilitiesCache = new Map();
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Detect which API mode we're running in
|
|
25
|
+
* @returns {Promise<string>} 'signalk' or 'standalone'
|
|
26
|
+
*/
|
|
27
|
+
export async function detectMode() {
|
|
28
|
+
if (detectedMode) {
|
|
29
|
+
return detectedMode;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Try standalone first - check if /v2/api/radars returns 200
|
|
33
|
+
try {
|
|
34
|
+
const response = await fetch(STANDALONE_RADARS_API, { method: 'HEAD' });
|
|
35
|
+
if (response.ok) {
|
|
36
|
+
detectedMode = 'standalone';
|
|
37
|
+
console.log("Detected standalone mode");
|
|
38
|
+
return detectedMode;
|
|
39
|
+
}
|
|
40
|
+
} catch (e) {
|
|
41
|
+
// Standalone not available
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Try SignalK - check if endpoint returns 200
|
|
45
|
+
try {
|
|
46
|
+
const response = await fetch(SIGNALK_RADARS_API, { method: 'HEAD' });
|
|
47
|
+
if (response.ok) {
|
|
48
|
+
detectedMode = 'signalk';
|
|
49
|
+
console.log("Detected SignalK mode");
|
|
50
|
+
return detectedMode;
|
|
51
|
+
}
|
|
52
|
+
} catch (e) {
|
|
53
|
+
// SignalK not available
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Default to standalone
|
|
57
|
+
detectedMode = 'standalone';
|
|
58
|
+
console.log("Defaulting to standalone mode");
|
|
59
|
+
return detectedMode;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get the radars API URL for current mode
|
|
64
|
+
* @returns {string} API URL
|
|
65
|
+
*/
|
|
66
|
+
export function getRadarsUrl() {
|
|
67
|
+
return detectedMode === 'signalk' ? SIGNALK_RADARS_API : STANDALONE_RADARS_API;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Get the interfaces API URL (standalone only)
|
|
72
|
+
* @returns {string|null} API URL or null if not available
|
|
73
|
+
*/
|
|
74
|
+
export function getInterfacesUrl() {
|
|
75
|
+
return detectedMode === 'standalone' ? STANDALONE_INTERFACES_API : null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Fetch list of radar IDs
|
|
80
|
+
* @returns {Promise<string[]>} Array of radar IDs
|
|
81
|
+
*/
|
|
82
|
+
export async function fetchRadarIds() {
|
|
83
|
+
await detectMode();
|
|
84
|
+
|
|
85
|
+
const response = await fetch(getRadarsUrl());
|
|
86
|
+
const data = await response.json();
|
|
87
|
+
|
|
88
|
+
// SignalK v5 returns array of IDs: ["Furuno-RD003212", "Navico-HALO"]
|
|
89
|
+
if (Array.isArray(data)) {
|
|
90
|
+
// Could be array of IDs (strings) or array of radar objects
|
|
91
|
+
if (data.length > 0 && typeof data[0] === 'string') {
|
|
92
|
+
return data;
|
|
93
|
+
}
|
|
94
|
+
// Legacy: array of radar objects
|
|
95
|
+
return data.map(r => r.id);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Standalone returns object keyed by ID
|
|
99
|
+
return Object.keys(data);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Fetch list of radars (legacy compatibility)
|
|
104
|
+
* @returns {Promise<Object>} Radars object keyed by ID
|
|
105
|
+
*/
|
|
106
|
+
export async function fetchRadars() {
|
|
107
|
+
await detectMode();
|
|
108
|
+
|
|
109
|
+
const response = await fetch(getRadarsUrl());
|
|
110
|
+
const data = await response.json();
|
|
111
|
+
|
|
112
|
+
// SignalK returns an array, standalone returns an object
|
|
113
|
+
if (detectedMode === 'signalk' && Array.isArray(data)) {
|
|
114
|
+
// Convert array to object keyed by id
|
|
115
|
+
const radars = {};
|
|
116
|
+
for (const radar of data) {
|
|
117
|
+
radars[radar.id] = radar;
|
|
118
|
+
}
|
|
119
|
+
return radars;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return data;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Fetch radar capabilities (v5 API)
|
|
127
|
+
* Returns the capability manifest with controls schema, characteristics, etc.
|
|
128
|
+
* @param {string} radarId - The radar ID
|
|
129
|
+
* @returns {Promise<Object>} Capability manifest
|
|
130
|
+
*/
|
|
131
|
+
export async function fetchCapabilities(radarId) {
|
|
132
|
+
await detectMode();
|
|
133
|
+
|
|
134
|
+
// Don't cache capabilities - model info may be updated after TCP connects
|
|
135
|
+
// The radar model is identified via TCP $N96 response, which happens after
|
|
136
|
+
// initial discovery. Caching would return stale "Unknown" model.
|
|
137
|
+
|
|
138
|
+
const url = `${getRadarsUrl()}/${radarId}/capabilities`;
|
|
139
|
+
console.log(`Fetching capabilities: GET ${url}`);
|
|
140
|
+
|
|
141
|
+
const response = await fetch(url);
|
|
142
|
+
if (!response.ok) {
|
|
143
|
+
throw new Error(`Failed to fetch capabilities: ${response.status}`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return response.json();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Fetch radar state (v5 API)
|
|
151
|
+
* Returns current values of all controls
|
|
152
|
+
* @param {string} radarId - The radar ID
|
|
153
|
+
* @returns {Promise<Object>} Radar state
|
|
154
|
+
*/
|
|
155
|
+
export async function fetchState(radarId) {
|
|
156
|
+
await detectMode();
|
|
157
|
+
|
|
158
|
+
const url = `${getRadarsUrl()}/${radarId}/state`;
|
|
159
|
+
|
|
160
|
+
const response = await fetch(url);
|
|
161
|
+
if (!response.ok) {
|
|
162
|
+
throw new Error(`Failed to fetch state: ${response.status}`);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return response.json();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Clear cached capabilities (e.g., when radar disconnects)
|
|
170
|
+
* @param {string} radarId - The radar ID, or omit to clear all
|
|
171
|
+
*/
|
|
172
|
+
export function clearCapabilitiesCache(radarId) {
|
|
173
|
+
if (radarId) {
|
|
174
|
+
capabilitiesCache.delete(radarId);
|
|
175
|
+
} else {
|
|
176
|
+
capabilitiesCache.clear();
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Fetch list of interfaces (standalone mode only)
|
|
182
|
+
* @returns {Promise<Object|null>} Interfaces object or null
|
|
183
|
+
*/
|
|
184
|
+
export async function fetchInterfaces() {
|
|
185
|
+
await detectMode();
|
|
186
|
+
|
|
187
|
+
const url = getInterfacesUrl();
|
|
188
|
+
if (!url) {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const response = await fetch(url);
|
|
193
|
+
return response.json();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Check if we're in SignalK mode
|
|
198
|
+
* @returns {boolean}
|
|
199
|
+
*/
|
|
200
|
+
export function isSignalKMode() {
|
|
201
|
+
return detectedMode === 'signalk';
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Check if we're in standalone mode
|
|
206
|
+
* @returns {boolean}
|
|
207
|
+
*/
|
|
208
|
+
export function isStandaloneMode() {
|
|
209
|
+
return detectedMode === 'standalone';
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Map power control values to SignalK RadarStatus
|
|
214
|
+
* SignalK expects: 'off' | 'standby' | 'transmit' | 'warming'
|
|
215
|
+
*/
|
|
216
|
+
function mapPowerValue(value) {
|
|
217
|
+
// Handle numeric or string values
|
|
218
|
+
const v = String(value);
|
|
219
|
+
if (v === '0' || v === 'off' || v === 'Off') return 'standby';
|
|
220
|
+
if (v === '1' || v === 'on' || v === 'On') return 'transmit';
|
|
221
|
+
// Pass through if already a valid RadarStatus
|
|
222
|
+
if (['off', 'standby', 'transmit', 'warming'].includes(v)) return v;
|
|
223
|
+
return v;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Send a control command to a radar via REST API (v5 format)
|
|
228
|
+
*
|
|
229
|
+
* SignalK Radar API v5 format:
|
|
230
|
+
* PUT /signalk/v2/api/vessels/self/radars/{radarId}/controls/{controlId}
|
|
231
|
+
* Body: { value: ... }
|
|
232
|
+
*
|
|
233
|
+
* @param {string} radarId - The radar ID
|
|
234
|
+
* @param {string} controlId - The control ID (e.g., "power", "gain", "range")
|
|
235
|
+
* @param {any} value - The value to set (type depends on control)
|
|
236
|
+
* @returns {Promise<boolean>} True if successful
|
|
237
|
+
*/
|
|
238
|
+
export async function setControl(radarId, controlId, value) {
|
|
239
|
+
await detectMode();
|
|
240
|
+
|
|
241
|
+
const url = `${getRadarsUrl()}/${radarId}/controls/${controlId}`;
|
|
242
|
+
const body = { value };
|
|
243
|
+
|
|
244
|
+
console.log(`Setting control: PUT ${url}`, body);
|
|
245
|
+
|
|
246
|
+
try {
|
|
247
|
+
const response = await fetch(url, {
|
|
248
|
+
method: 'PUT',
|
|
249
|
+
headers: {
|
|
250
|
+
'Content-Type': 'application/json',
|
|
251
|
+
},
|
|
252
|
+
body: JSON.stringify(body),
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
if (response.ok) {
|
|
256
|
+
console.log(`Control ${controlId} set successfully`);
|
|
257
|
+
return true;
|
|
258
|
+
} else {
|
|
259
|
+
const errorText = await response.text();
|
|
260
|
+
console.error(`Control command failed: ${response.status} ${response.statusText} for ${url}`, errorText);
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
} catch (e) {
|
|
264
|
+
console.error(`Control command error: ${e}`);
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Get installation settings for a radar from Application Data API
|
|
271
|
+
* Uses same path as WASM SignalK plugin: @mayara/signalk-radar/1.0.0
|
|
272
|
+
* Structure: { "radars": { "radar-id": { "bearingAlignment": ..., ... } } }
|
|
273
|
+
* @param {string} radarId - The radar ID
|
|
274
|
+
* @returns {Promise<Object>} Installation settings object for this radar
|
|
275
|
+
*/
|
|
276
|
+
export async function getInstallationSettings(radarId) {
|
|
277
|
+
const url = `${APPDATA_PATH}/1.0.0`;
|
|
278
|
+
try {
|
|
279
|
+
const response = await fetch(url);
|
|
280
|
+
if (!response.ok) return {};
|
|
281
|
+
const data = await response.json();
|
|
282
|
+
return data?.radars?.[radarId] || {};
|
|
283
|
+
} catch (e) {
|
|
284
|
+
console.warn('Failed to load installation settings:', e.message);
|
|
285
|
+
return {};
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Save an installation setting to Application Data API
|
|
291
|
+
* Preserves the nested structure used by WASM SignalK plugin
|
|
292
|
+
* @param {string} radarId - The radar ID
|
|
293
|
+
* @param {string} key - The setting key (e.g., "bearingAlignment")
|
|
294
|
+
* @param {any} value - The value to save
|
|
295
|
+
* @returns {Promise<boolean>} True if successful
|
|
296
|
+
*/
|
|
297
|
+
export async function saveInstallationSetting(radarId, key, value) {
|
|
298
|
+
const url = `${APPDATA_PATH}/1.0.0`;
|
|
299
|
+
try {
|
|
300
|
+
// Load full structure (preserve other radars' settings)
|
|
301
|
+
const getResponse = await fetch(url);
|
|
302
|
+
const data = getResponse.ok ? await getResponse.json() : { radars: {} };
|
|
303
|
+
|
|
304
|
+
// Update nested value
|
|
305
|
+
if (!data.radars) data.radars = {};
|
|
306
|
+
if (!data.radars[radarId]) data.radars[radarId] = {};
|
|
307
|
+
data.radars[radarId][key] = value;
|
|
308
|
+
|
|
309
|
+
// Save back
|
|
310
|
+
const putResponse = await fetch(url, {
|
|
311
|
+
method: 'PUT',
|
|
312
|
+
headers: { 'Content-Type': 'application/json' },
|
|
313
|
+
body: JSON.stringify(data)
|
|
314
|
+
});
|
|
315
|
+
if (putResponse.ok) {
|
|
316
|
+
console.log(`Installation setting '${key}' saved for ${radarId}`);
|
|
317
|
+
return true;
|
|
318
|
+
} else {
|
|
319
|
+
console.error(`Failed to save installation setting: ${putResponse.status}`);
|
|
320
|
+
return false;
|
|
321
|
+
}
|
|
322
|
+
} catch (e) {
|
|
323
|
+
console.error('Failed to save installation setting:', e);
|
|
324
|
+
return false;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Send a control command to a radar via REST API (legacy format)
|
|
330
|
+
*
|
|
331
|
+
* SignalK Radar API format:
|
|
332
|
+
* PUT /signalk/v2/api/vessels/self/radars/{radarId}/{controlName}
|
|
333
|
+
* Body: { value: ... }
|
|
334
|
+
*
|
|
335
|
+
* Power endpoint expects: { value: 'off' | 'standby' | 'transmit' | 'warming' }
|
|
336
|
+
* Range endpoint expects: { value: number } (meters)
|
|
337
|
+
* Gain endpoint expects: { auto: boolean, value?: number }
|
|
338
|
+
*
|
|
339
|
+
* @param {string} radarId - The radar ID
|
|
340
|
+
* @param {Object} controlData - The control data (id, value, auto, enabled)
|
|
341
|
+
* @param {Object} controls - The radar controls definition to map id to name
|
|
342
|
+
* @returns {Promise<boolean>} True if successful
|
|
343
|
+
*/
|
|
344
|
+
export async function sendControlCommand(radarId, controlData, controls) {
|
|
345
|
+
await detectMode();
|
|
346
|
+
|
|
347
|
+
// Map control id to control name for the endpoint
|
|
348
|
+
// controlData.id is the control key (e.g., "1" for Power)
|
|
349
|
+
const controlDef = controls ? controls[controlData.id] : null;
|
|
350
|
+
const controlName = controlDef ? controlDef.name.toLowerCase() : `control-${controlData.id}`;
|
|
351
|
+
|
|
352
|
+
const url = `${getRadarsUrl()}/${radarId}/${controlName}`;
|
|
353
|
+
|
|
354
|
+
// Build the request body based on controlData and control type
|
|
355
|
+
let body;
|
|
356
|
+
if (controlName === 'power') {
|
|
357
|
+
// Power expects RadarStatus string
|
|
358
|
+
body = { value: mapPowerValue(controlData.value) };
|
|
359
|
+
} else if (controlName === 'range') {
|
|
360
|
+
// Range expects number in meters
|
|
361
|
+
body = { value: parseFloat(controlData.value) };
|
|
362
|
+
} else if (controlName === 'gain' || controlName === 'sea' || controlName === 'rain') {
|
|
363
|
+
// Gain/sea/rain expect { auto: boolean, value?: number }
|
|
364
|
+
body = {};
|
|
365
|
+
if ('auto' in controlData) {
|
|
366
|
+
body.auto = controlData.auto;
|
|
367
|
+
}
|
|
368
|
+
if (controlData.value !== undefined) {
|
|
369
|
+
body.value = parseFloat(controlData.value);
|
|
370
|
+
}
|
|
371
|
+
} else {
|
|
372
|
+
// Generic control
|
|
373
|
+
body = { value: controlData.value };
|
|
374
|
+
if ('auto' in controlData) {
|
|
375
|
+
body.auto = controlData.auto;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
console.log(`Sending control: PUT ${url}`, body);
|
|
380
|
+
|
|
381
|
+
try {
|
|
382
|
+
const response = await fetch(url, {
|
|
383
|
+
method: 'PUT',
|
|
384
|
+
headers: {
|
|
385
|
+
'Content-Type': 'application/json',
|
|
386
|
+
},
|
|
387
|
+
body: JSON.stringify(body),
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
if (response.ok) {
|
|
391
|
+
console.log(`Control command sent successfully: PUT ${url}`);
|
|
392
|
+
return true;
|
|
393
|
+
} else {
|
|
394
|
+
const errorText = await response.text();
|
|
395
|
+
console.error(`Control command failed: ${response.status} ${response.statusText} for ${url}`, errorText);
|
|
396
|
+
return false;
|
|
397
|
+
}
|
|
398
|
+
} catch (e) {
|
|
399
|
+
console.error(`Control command error: ${e}`);
|
|
400
|
+
return false;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
Binary file
|
package/public/base.css
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/* ============================================
|
|
2
|
+
Base Styles - Reset, Typography, Variables
|
|
3
|
+
============================================ */
|
|
4
|
+
|
|
5
|
+
:root {
|
|
6
|
+
color-scheme: dark;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
html, body {
|
|
10
|
+
margin: 0;
|
|
11
|
+
padding: 0;
|
|
12
|
+
height: 100%;
|
|
13
|
+
overflow: hidden;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
html {
|
|
17
|
+
box-sizing: content-box;
|
|
18
|
+
border: none;
|
|
19
|
+
font: normal 16px/1 Verdana, Geneva, sans-serif;
|
|
20
|
+
color: rgb(152, 217, 204);
|
|
21
|
+
-o-text-overflow: ellipsis;
|
|
22
|
+
text-overflow: ellipsis;
|
|
23
|
+
background-color: #000000;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/* Base table cells */
|
|
27
|
+
td.myr {
|
|
28
|
+
background-color: rgb(3, 37, 37);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
td.myr_error {
|
|
32
|
+
color: firebrick;
|
|
33
|
+
background-color: rgb(3, 37, 37);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/* Error and warning messages */
|
|
37
|
+
div.myr_error {
|
|
38
|
+
background-color: firebrick;
|
|
39
|
+
color: wheat;
|
|
40
|
+
transition-property: opacity, display;
|
|
41
|
+
transition-duration: 0.5s;
|
|
42
|
+
transition-behavior: allow-discrete;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
div.myr_error.myr_vanish {
|
|
46
|
+
display: none;
|
|
47
|
+
opacity: 0;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
div.myr_warning {
|
|
51
|
+
background-color: rgb(60, 50, 10);
|
|
52
|
+
color: rgb(255, 200, 100);
|
|
53
|
+
padding: 10px;
|
|
54
|
+
border-radius: 5px;
|
|
55
|
+
margin: 10px 0;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* Network help details styling for dark mode */
|
|
59
|
+
details {
|
|
60
|
+
color: rgb(152, 217, 204);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
details div {
|
|
64
|
+
background: rgb(20, 40, 40) !important;
|
|
65
|
+
color: rgb(180, 200, 200);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
details p {
|
|
69
|
+
margin: 5px 0;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
details strong {
|
|
73
|
+
color: rgb(100, 200, 180);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* Base form elements */
|
|
77
|
+
label {
|
|
78
|
+
display: block;
|
|
79
|
+
max-width: 150px;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
input {
|
|
83
|
+
display: block;
|
|
84
|
+
background-color: darkblue;
|
|
85
|
+
color: wheat;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
input:read-only {
|
|
89
|
+
background-color: #000000;
|
|
90
|
+
color:rgb(124, 124, 237);
|
|
91
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<title>Mayara - Marine Yacht Radar Client Control</title>
|
|
5
|
+
<meta charset="utf-8" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
7
|
+
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
|
|
8
|
+
<meta http-equiv="Pragma" content="no-cache" />
|
|
9
|
+
<meta http-equiv="Expires" content="0" />
|
|
10
|
+
<link type="text/css" rel="stylesheet" href="base.css?v=1" />
|
|
11
|
+
<link type="text/css" rel="stylesheet" href="controls.css?v=1" />
|
|
12
|
+
<link type="text/css" rel="stylesheet" href="responsive.css?v=1" />
|
|
13
|
+
<script type="module" src="control.js?v=10"></script>
|
|
14
|
+
</head>
|
|
15
|
+
<body>
|
|
16
|
+
<div id="myr_controller" class="myr_controller">
|
|
17
|
+
<div id="myr_title">Radar Controls</div>
|
|
18
|
+
<div id="myr_error" class="myr_error" style="visibility: hidden;"></div>
|
|
19
|
+
<div id="myr_controls" class="myr_control">
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
</body>
|
|
23
|
+
</html>
|