@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/public/mayara.js
ADDED
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
import van from "./van-1.5.2.debug.js";
|
|
2
|
+
import { fetchRadars, fetchInterfaces, isStandaloneMode, detectMode } from "./api.js";
|
|
3
|
+
|
|
4
|
+
const { a, tr, td, div, p, strong, details, summary, code, br, span } = van.tags;
|
|
5
|
+
|
|
6
|
+
// Network requirements for different radar brands
|
|
7
|
+
const NETWORK_REQUIREMENTS = {
|
|
8
|
+
furuno: {
|
|
9
|
+
ipRange: "172.31.x.x/16",
|
|
10
|
+
description: "Furuno DRS radars require the host to have an IP address in the 172.31.x.x range.",
|
|
11
|
+
setup: [
|
|
12
|
+
"Configure your network interface with an IP like 172.31.3.100/16",
|
|
13
|
+
"Connect to the radar network (usually via ethernet)",
|
|
14
|
+
"Ensure no firewall blocks UDP ports 10010, 10024, 10021"
|
|
15
|
+
],
|
|
16
|
+
example: "ip addr add 172.31.3.100/16 dev eth1"
|
|
17
|
+
},
|
|
18
|
+
navico: {
|
|
19
|
+
ipRange: "236.6.7.x (multicast)",
|
|
20
|
+
description: "Navico (Simrad/Lowrance/B&G) radars use multicast.",
|
|
21
|
+
setup: ["Ensure your network supports multicast routing"]
|
|
22
|
+
},
|
|
23
|
+
raymarine: {
|
|
24
|
+
ipRange: "232.1.1.x (multicast)",
|
|
25
|
+
description: "Raymarine radars use multicast.",
|
|
26
|
+
setup: ["Ensure your network supports multicast routing"]
|
|
27
|
+
},
|
|
28
|
+
garmin: {
|
|
29
|
+
ipRange: "239.254.2.x (multicast)",
|
|
30
|
+
description: "Garmin xHD radars use multicast.",
|
|
31
|
+
setup: ["Ensure your network supports multicast routing"]
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// Detect operating system
|
|
36
|
+
function detectOS() {
|
|
37
|
+
const ua = navigator.userAgent.toLowerCase();
|
|
38
|
+
const platform = navigator.platform?.toLowerCase() || '';
|
|
39
|
+
|
|
40
|
+
// Check mobile/tablet FIRST (iPadOS reports as macOS in Safari)
|
|
41
|
+
if (ua.includes('iphone') || ua.includes('ipad')) return 'ios';
|
|
42
|
+
// Also detect iPad via touch + macOS combination (iPadOS 13+ desktop mode)
|
|
43
|
+
if (navigator.maxTouchPoints > 1 && (ua.includes('mac') || platform.includes('mac'))) return 'ios';
|
|
44
|
+
if (ua.includes('android')) return 'android';
|
|
45
|
+
|
|
46
|
+
// Desktop OS detection
|
|
47
|
+
if (ua.includes('win') || platform.includes('win')) return 'windows';
|
|
48
|
+
if (ua.includes('mac') || platform.includes('mac')) return 'macos';
|
|
49
|
+
if (ua.includes('linux') || platform.includes('linux')) return 'linux';
|
|
50
|
+
return 'unknown';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Detect browser
|
|
54
|
+
function detectBrowser() {
|
|
55
|
+
const ua = navigator.userAgent.toLowerCase();
|
|
56
|
+
|
|
57
|
+
if (ua.includes('edg/')) return 'edge';
|
|
58
|
+
if (ua.includes('chrome')) return 'chrome';
|
|
59
|
+
if (ua.includes('firefox')) return 'firefox';
|
|
60
|
+
if (ua.includes('safari') && !ua.includes('chrome')) return 'safari';
|
|
61
|
+
return 'unknown';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Check if using a secure context
|
|
65
|
+
// Note: localhost and 127.0.0.1 are treated as secure contexts by browsers
|
|
66
|
+
function isSecureContext() {
|
|
67
|
+
return window.isSecureContext;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Check WebGPU support and show appropriate warnings
|
|
71
|
+
async function checkWebGPUSupport() {
|
|
72
|
+
const warningDiv = document.getElementById('webgpu_warning');
|
|
73
|
+
if (!warningDiv) return true;
|
|
74
|
+
|
|
75
|
+
const os = detectOS();
|
|
76
|
+
const browser = detectBrowser();
|
|
77
|
+
const isSecure = isSecureContext();
|
|
78
|
+
const hasWebGPUApi = !!navigator.gpu;
|
|
79
|
+
|
|
80
|
+
// Track why WebGPU failed
|
|
81
|
+
let failureReason = null;
|
|
82
|
+
let hasWorkingAdapter = false;
|
|
83
|
+
|
|
84
|
+
if (hasWebGPUApi) {
|
|
85
|
+
try {
|
|
86
|
+
const adapter = await navigator.gpu.requestAdapter();
|
|
87
|
+
if (adapter) {
|
|
88
|
+
// WebGPU fully working!
|
|
89
|
+
warningDiv.style.display = 'none';
|
|
90
|
+
return true;
|
|
91
|
+
} else {
|
|
92
|
+
failureReason = 'no-adapter'; // API exists but no GPU adapter found
|
|
93
|
+
}
|
|
94
|
+
} catch (e) {
|
|
95
|
+
console.warn('WebGPU adapter request failed:', e);
|
|
96
|
+
failureReason = 'adapter-error';
|
|
97
|
+
}
|
|
98
|
+
} else {
|
|
99
|
+
// API doesn't exist - could be secure context issue or browser doesn't support it
|
|
100
|
+
failureReason = isSecure ? 'no-api' : 'insecure-context';
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Build warning message based on situation
|
|
104
|
+
warningDiv.style.display = 'block';
|
|
105
|
+
warningDiv.innerHTML = '';
|
|
106
|
+
|
|
107
|
+
const title = div({ class: 'myr_warning_title' }, 'WebGPU Not Available');
|
|
108
|
+
van.add(warningDiv, title);
|
|
109
|
+
|
|
110
|
+
const content = div({ class: 'myr_warning_content' });
|
|
111
|
+
van.add(warningDiv, content);
|
|
112
|
+
|
|
113
|
+
// Secure context warning (if not secure)
|
|
114
|
+
if (!isSecure) {
|
|
115
|
+
const hostname = window.location.hostname;
|
|
116
|
+
const port = window.location.port || '80';
|
|
117
|
+
const isMobile = (os === 'ios' || os === 'android');
|
|
118
|
+
|
|
119
|
+
van.add(content, div({ class: 'myr_warning_item myr_warning_https' },
|
|
120
|
+
strong('Secure Context Required'),
|
|
121
|
+
p('WebGPU requires a secure context. You are currently using HTTP on "', hostname, '".'),
|
|
122
|
+
p('Options:'),
|
|
123
|
+
div({ class: 'myr_warning_options' },
|
|
124
|
+
// Only show localhost option for desktop (SignalK won't run on mobile)
|
|
125
|
+
!isMobile ? div({ class: 'myr_warning_option' },
|
|
126
|
+
strong('Option 1 (easiest): '), 'Access via localhost instead:',
|
|
127
|
+
div({ class: 'myr_code_block' },
|
|
128
|
+
p(code('http://localhost:' + port), ' or ', code('http://127.0.0.1:' + port)),
|
|
129
|
+
p({ class: 'myr_note' }, 'Browsers treat localhost as a secure context')
|
|
130
|
+
)
|
|
131
|
+
) : null,
|
|
132
|
+
div({ class: 'myr_warning_option' },
|
|
133
|
+
strong(isMobile ? 'Option 1: ' : 'Option 2: '), 'Add this site to browser exceptions:',
|
|
134
|
+
getInsecureOriginInstructions(browser, os)
|
|
135
|
+
),
|
|
136
|
+
div({ class: 'myr_warning_option' },
|
|
137
|
+
strong(isMobile ? 'Option 2: ' : 'Option 3: '), 'Use HTTPS (requires server configuration)'
|
|
138
|
+
)
|
|
139
|
+
)
|
|
140
|
+
));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Always show browser-specific WebGPU/hardware acceleration instructions
|
|
144
|
+
van.add(content, div({ class: 'myr_warning_item' },
|
|
145
|
+
strong('Enable WebGPU / Hardware Acceleration'),
|
|
146
|
+
getBrowserInstructions(browser, os)
|
|
147
|
+
));
|
|
148
|
+
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function getInsecureOriginInstructions(browser, os) {
|
|
153
|
+
const origin = window.location.origin;
|
|
154
|
+
|
|
155
|
+
// iOS Safari has no way to add insecure origin exceptions
|
|
156
|
+
if (os === 'ios') {
|
|
157
|
+
return div({ class: 'myr_code_block' },
|
|
158
|
+
p('Safari on iOS/iPadOS does not support insecure origin exceptions.'),
|
|
159
|
+
p('Alternatives:'),
|
|
160
|
+
p('• Configure HTTPS on your SignalK server'),
|
|
161
|
+
p('• Use a tunneling service (e.g., ngrok) to get an HTTPS URL'),
|
|
162
|
+
p('• Access from a desktop browser where you can set the flag')
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Android Chrome
|
|
167
|
+
if (os === 'android' && browser === 'chrome') {
|
|
168
|
+
return div({ class: 'myr_code_block' },
|
|
169
|
+
p('1. Open Chrome on your Android device'),
|
|
170
|
+
p('2. Go to: ', code('chrome://flags/#unsafely-treat-insecure-origin-as-secure')),
|
|
171
|
+
p('3. Add: ', code(origin)),
|
|
172
|
+
p('4. Set to "Enabled"'),
|
|
173
|
+
p('5. Tap "Relaunch"')
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
switch (browser) {
|
|
178
|
+
case 'chrome':
|
|
179
|
+
case 'edge':
|
|
180
|
+
const flagPrefix = browser === 'edge' ? 'edge' : 'chrome';
|
|
181
|
+
const flagUrl = `${flagPrefix}://flags/#unsafely-treat-insecure-origin-as-secure`;
|
|
182
|
+
return div({ class: 'myr_code_block' },
|
|
183
|
+
p('1. Copy and paste this into your address bar:'),
|
|
184
|
+
p(a({ href: flagUrl, class: 'myr_flag_link' }, code(flagUrl))),
|
|
185
|
+
p('2. In the text field, add: ', code(origin)),
|
|
186
|
+
p('3. Set dropdown to "Enabled"'),
|
|
187
|
+
p('4. Click "Relaunch" at the bottom')
|
|
188
|
+
);
|
|
189
|
+
case 'firefox':
|
|
190
|
+
return div({ class: 'myr_code_block' },
|
|
191
|
+
p('1. Open: ', a({ href: 'about:config', class: 'myr_flag_link' }, code('about:config'))),
|
|
192
|
+
p('2. Click "Accept the Risk and Continue"'),
|
|
193
|
+
p('3. Search for: ', code('dom.securecontext.allowlist')),
|
|
194
|
+
p('4. Click the + button to add: ', code(window.location.hostname)),
|
|
195
|
+
p('5. Restart Firefox')
|
|
196
|
+
);
|
|
197
|
+
default:
|
|
198
|
+
return div({ class: 'myr_code_block' },
|
|
199
|
+
p('Check your browser settings for allowing insecure origins.')
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function getBrowserInstructions(browser, os) {
|
|
205
|
+
// iOS/iPadOS Safari
|
|
206
|
+
if (browser === 'safari' && os === 'ios') {
|
|
207
|
+
return div({ class: 'myr_code_block' },
|
|
208
|
+
p('Safari on iOS/iPadOS 17+:'),
|
|
209
|
+
p('1. Open ', strong('Settings'), ' app'),
|
|
210
|
+
p('2. Scroll down and tap ', strong('Safari')),
|
|
211
|
+
p('3. Scroll down and tap ', strong('Advanced')),
|
|
212
|
+
p('4. Tap ', strong('Feature Flags')),
|
|
213
|
+
p('5. Enable ', strong('WebGPU')),
|
|
214
|
+
p('6. Return to Safari and reload this page'),
|
|
215
|
+
p({ class: 'myr_note' }, 'Note: Requires iOS/iPadOS 17 or later.')
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
switch (browser) {
|
|
220
|
+
case 'chrome':
|
|
221
|
+
return div({ class: 'myr_code_block' },
|
|
222
|
+
p('Chrome should have WebGPU enabled by default (v113+).'),
|
|
223
|
+
p('If not working, try:'),
|
|
224
|
+
p('1. Open: ', code('chrome://flags/#enable-unsafe-webgpu')),
|
|
225
|
+
p('2. Set to "Enabled"'),
|
|
226
|
+
p('3. Relaunch Chrome'),
|
|
227
|
+
os === 'linux' ? p({ class: 'myr_note' },
|
|
228
|
+
'Note: On Linux, you may need Vulkan drivers installed.') : null
|
|
229
|
+
);
|
|
230
|
+
case 'edge':
|
|
231
|
+
return div({ class: 'myr_code_block' },
|
|
232
|
+
p('Edge should have WebGPU enabled by default.'),
|
|
233
|
+
p('If not working, try:'),
|
|
234
|
+
p('1. Open: ', code('edge://flags/#enable-unsafe-webgpu')),
|
|
235
|
+
p('2. Set to "Enabled"'),
|
|
236
|
+
p('3. Relaunch Edge')
|
|
237
|
+
);
|
|
238
|
+
case 'firefox':
|
|
239
|
+
return div({ class: 'myr_code_block' },
|
|
240
|
+
p('Firefox WebGPU is experimental:'),
|
|
241
|
+
p('1. Open: ', code('about:config')),
|
|
242
|
+
p('2. Search for: ', code('dom.webgpu.enabled')),
|
|
243
|
+
p('3. Set to: ', code('true')),
|
|
244
|
+
p('4. Restart Firefox'),
|
|
245
|
+
p({ class: 'myr_note' }, 'Note: Firefox WebGPU support is still in development.')
|
|
246
|
+
);
|
|
247
|
+
case 'safari':
|
|
248
|
+
return div({ class: 'myr_code_block' },
|
|
249
|
+
p('Safari WebGPU (macOS 14+):'),
|
|
250
|
+
p('1. Open Safari menu > Settings'),
|
|
251
|
+
p('2. Go to Advanced tab'),
|
|
252
|
+
p('3. Check "Show features for web developers"'),
|
|
253
|
+
p('4. Go to Feature Flags tab'),
|
|
254
|
+
p('5. Enable "WebGPU"'),
|
|
255
|
+
p('6. Restart Safari')
|
|
256
|
+
);
|
|
257
|
+
default:
|
|
258
|
+
return div({ class: 'myr_code_block' },
|
|
259
|
+
p('WebGPU requires a modern browser:'),
|
|
260
|
+
p('- Chrome 113+ (recommended)'),
|
|
261
|
+
p('- Edge 113+'),
|
|
262
|
+
p('- Safari 17+ (macOS/iOS)'),
|
|
263
|
+
p('- Firefox Nightly (experimental)')
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function getHardwareAccelerationInstructions(browser, os) {
|
|
269
|
+
// iOS/iPadOS - no hardware acceleration toggle
|
|
270
|
+
if (os === 'ios') {
|
|
271
|
+
return div({ class: 'myr_code_block' },
|
|
272
|
+
p('On iOS/iPadOS, hardware acceleration cannot be disabled.'),
|
|
273
|
+
p('If WebGPU is not working:'),
|
|
274
|
+
p('• Ensure you have iOS/iPadOS 17 or later'),
|
|
275
|
+
p('• Try closing and reopening Safari'),
|
|
276
|
+
p('• Restart your device')
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
switch (browser) {
|
|
281
|
+
case 'chrome':
|
|
282
|
+
return div({ class: 'myr_code_block' },
|
|
283
|
+
p('1. Open: ', code('chrome://settings/system')),
|
|
284
|
+
p('2. Enable "Use graphics acceleration when available"'),
|
|
285
|
+
p('3. Relaunch Chrome')
|
|
286
|
+
);
|
|
287
|
+
case 'edge':
|
|
288
|
+
return div({ class: 'myr_code_block' },
|
|
289
|
+
p('1. Open: ', code('edge://settings/system')),
|
|
290
|
+
p('2. Enable "Use graphics acceleration when available"'),
|
|
291
|
+
p('3. Relaunch Edge')
|
|
292
|
+
);
|
|
293
|
+
case 'firefox':
|
|
294
|
+
return div({ class: 'myr_code_block' },
|
|
295
|
+
p('1. Open: ', code('about:preferences')),
|
|
296
|
+
p('2. Scroll to "Performance"'),
|
|
297
|
+
p('3. Uncheck "Use recommended performance settings"'),
|
|
298
|
+
p('4. Check "Use hardware acceleration when available"'),
|
|
299
|
+
p('5. Restart Firefox')
|
|
300
|
+
);
|
|
301
|
+
case 'safari':
|
|
302
|
+
return div({ class: 'myr_code_block' },
|
|
303
|
+
p('Safari uses hardware acceleration by default on macOS.'),
|
|
304
|
+
p('If WebGPU is not working:'),
|
|
305
|
+
p('• Ensure you have macOS 14 (Sonoma) or later'),
|
|
306
|
+
p('• Check that WebGPU is enabled in Feature Flags'),
|
|
307
|
+
p('• Try restarting Safari')
|
|
308
|
+
);
|
|
309
|
+
default:
|
|
310
|
+
return div({ class: 'myr_code_block' },
|
|
311
|
+
p('Check your browser settings for "Hardware acceleration"'),
|
|
312
|
+
p('or "Use GPU" and ensure it is enabled.'),
|
|
313
|
+
p('Then restart the browser.')
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const RadarEntry = (radar) => {
|
|
319
|
+
// Build display name: "Brand Model (Name)" or "Brand Name" if no model
|
|
320
|
+
const brand = radar.brand || '';
|
|
321
|
+
const model = radar.model || '';
|
|
322
|
+
const name = radar.name || '';
|
|
323
|
+
|
|
324
|
+
let displayName;
|
|
325
|
+
if (model && model !== 'Unknown') {
|
|
326
|
+
displayName = `${brand} ${model} (${name})`;
|
|
327
|
+
} else {
|
|
328
|
+
displayName = `${brand} ${name}`;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return tr({ class: 'myr_radar_row' },
|
|
332
|
+
td({ class: 'myr_radar_name' }, displayName),
|
|
333
|
+
td({ class: 'myr_radar_actions' },
|
|
334
|
+
a({ href: "viewer.html?id=" + radar.id, class: 'myr_radar_link myr_radar_link_primary' },
|
|
335
|
+
'Open Radar Display'
|
|
336
|
+
),
|
|
337
|
+
a({ href: "control.html?id=" + radar.id, class: 'myr_radar_link myr_radar_link_secondary' },
|
|
338
|
+
'Controls Only'
|
|
339
|
+
)
|
|
340
|
+
)
|
|
341
|
+
);
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
// Track previous radar count to avoid unnecessary DOM rebuilds
|
|
345
|
+
let previousRadarCount = -1;
|
|
346
|
+
|
|
347
|
+
function radarsLoaded(d) {
|
|
348
|
+
let radarIds = Object.keys(d);
|
|
349
|
+
let c = radarIds.length;
|
|
350
|
+
let r = document.getElementById("radars");
|
|
351
|
+
|
|
352
|
+
// Only rebuild if radar count changed (avoids collapsing the help details)
|
|
353
|
+
if (c === previousRadarCount && c === 0) {
|
|
354
|
+
// No change, just reschedule poll
|
|
355
|
+
setTimeout(loadRadars, 2000);
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
previousRadarCount = c;
|
|
359
|
+
|
|
360
|
+
// Clear previous content
|
|
361
|
+
r.innerHTML = "";
|
|
362
|
+
|
|
363
|
+
if (c > 0) {
|
|
364
|
+
van.add(r, div({ class: 'myr_section_title' },
|
|
365
|
+
span({ class: 'myr_radar_count' }, c),
|
|
366
|
+
' Radar' + (c > 1 ? 's' : '') + ' Detected'
|
|
367
|
+
));
|
|
368
|
+
|
|
369
|
+
let table = document.createElement("table");
|
|
370
|
+
table.className = 'myr_radar_table';
|
|
371
|
+
r.appendChild(table);
|
|
372
|
+
|
|
373
|
+
radarIds.sort().forEach(function (v, i) {
|
|
374
|
+
// Pass the full radar object (includes id, name, brand, model)
|
|
375
|
+
const radar = { ...d[v], id: v };
|
|
376
|
+
van.add(table, RadarEntry(radar));
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
// Radar found, poll less frequently
|
|
380
|
+
setTimeout(loadRadars, 15000);
|
|
381
|
+
} else {
|
|
382
|
+
van.add(r, div({ class: 'myr_detecting' },
|
|
383
|
+
span({ class: 'myr_pulse' }),
|
|
384
|
+
'Searching for radars...'
|
|
385
|
+
));
|
|
386
|
+
|
|
387
|
+
// Show network requirements help
|
|
388
|
+
van.add(r,
|
|
389
|
+
details({ class: 'myr_network_help' },
|
|
390
|
+
summary('Network Configuration Help'),
|
|
391
|
+
div({ class: 'myr_help_content' },
|
|
392
|
+
// Furuno section
|
|
393
|
+
div({ class: 'myr_brand_section' },
|
|
394
|
+
div({ class: 'myr_brand_header' }, 'Furuno DRS (DRS4D-NXT, DRS6A-NXT, etc.)'),
|
|
395
|
+
p(NETWORK_REQUIREMENTS.furuno.description),
|
|
396
|
+
div({ class: 'myr_setup_steps' },
|
|
397
|
+
NETWORK_REQUIREMENTS.furuno.setup.map((step, i) =>
|
|
398
|
+
div({ class: 'myr_setup_step' }, (i + 1) + '. ' + step)
|
|
399
|
+
)
|
|
400
|
+
),
|
|
401
|
+
div({ class: 'myr_code_example' },
|
|
402
|
+
code(NETWORK_REQUIREMENTS.furuno.example)
|
|
403
|
+
)
|
|
404
|
+
),
|
|
405
|
+
|
|
406
|
+
// Other brands
|
|
407
|
+
div({ class: 'myr_brand_section myr_brand_other' },
|
|
408
|
+
div({ class: 'myr_brand_header' }, 'Navico (Simrad, Lowrance, B&G)'),
|
|
409
|
+
p(NETWORK_REQUIREMENTS.navico.description)
|
|
410
|
+
),
|
|
411
|
+
|
|
412
|
+
div({ class: 'myr_brand_section myr_brand_other' },
|
|
413
|
+
div({ class: 'myr_brand_header' }, 'Raymarine'),
|
|
414
|
+
p(NETWORK_REQUIREMENTS.raymarine.description)
|
|
415
|
+
),
|
|
416
|
+
|
|
417
|
+
div({ class: 'myr_brand_section myr_brand_other' },
|
|
418
|
+
div({ class: 'myr_brand_header' }, 'Garmin xHD'),
|
|
419
|
+
p(NETWORK_REQUIREMENTS.garmin.description)
|
|
420
|
+
)
|
|
421
|
+
)
|
|
422
|
+
)
|
|
423
|
+
);
|
|
424
|
+
|
|
425
|
+
// No radar found, poll more frequently (every 2 seconds)
|
|
426
|
+
setTimeout(loadRadars, 2000);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function interfacesLoaded(d) {
|
|
431
|
+
if (!d || !d.interfaces) {
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
let c = Object.keys(d.interfaces).length;
|
|
436
|
+
if (c > 0) {
|
|
437
|
+
let r = document.getElementById("interfaces");
|
|
438
|
+
r.innerHTML = "";
|
|
439
|
+
|
|
440
|
+
van.add(r, div({ class: 'myr_section_title' }, 'Network Interfaces'));
|
|
441
|
+
|
|
442
|
+
let table = document.createElement("table");
|
|
443
|
+
table.className = 'myr_interface_table';
|
|
444
|
+
r.appendChild(table);
|
|
445
|
+
|
|
446
|
+
let brands = ["Interface", ...d.brands];
|
|
447
|
+
let hdr = van.add(table, tr({ class: 'myr_interface_header' }));
|
|
448
|
+
brands.forEach((v) => van.add(hdr, td(v)));
|
|
449
|
+
|
|
450
|
+
let interfaces = d.interfaces;
|
|
451
|
+
if (interfaces) {
|
|
452
|
+
console.log("interfaces", interfaces);
|
|
453
|
+
Object.keys(interfaces).forEach(function (v, i) {
|
|
454
|
+
let row = van.add(table, tr());
|
|
455
|
+
|
|
456
|
+
van.add(row, td({ class: 'myr_interface_name' }, v));
|
|
457
|
+
if (interfaces[v].status) {
|
|
458
|
+
van.add(row, td({ class: 'myr_interface_error', colspan: d.brands.length }, interfaces[v].status));
|
|
459
|
+
} else {
|
|
460
|
+
d.brands.forEach((b) => {
|
|
461
|
+
let status = interfaces[v].listeners[b];
|
|
462
|
+
let className = (status == "Listening" || status == "Active")
|
|
463
|
+
? 'myr_interface_ok'
|
|
464
|
+
: 'myr_interface_error';
|
|
465
|
+
van.add(row, td({ class: className }, status));
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
async function loadRadars() {
|
|
474
|
+
try {
|
|
475
|
+
const radars = await fetchRadars();
|
|
476
|
+
radarsLoaded(radars);
|
|
477
|
+
} catch (err) {
|
|
478
|
+
console.error("Failed to load radars:", err);
|
|
479
|
+
setTimeout(loadRadars, 15000);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
async function loadInterfaces() {
|
|
484
|
+
try {
|
|
485
|
+
const interfaces = await fetchInterfaces();
|
|
486
|
+
if (interfaces) {
|
|
487
|
+
interfacesLoaded(interfaces);
|
|
488
|
+
} else {
|
|
489
|
+
// Hide interfaces section in SignalK mode
|
|
490
|
+
let r = document.getElementById("interfaces");
|
|
491
|
+
if (r) {
|
|
492
|
+
r.style.display = "none";
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
} catch (err) {
|
|
496
|
+
console.error("Failed to load interfaces:", err);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
window.onload = async function () {
|
|
501
|
+
// Check WebGPU support first
|
|
502
|
+
await checkWebGPUSupport();
|
|
503
|
+
|
|
504
|
+
// Detect mode
|
|
505
|
+
await detectMode();
|
|
506
|
+
|
|
507
|
+
// Load data
|
|
508
|
+
loadRadars();
|
|
509
|
+
loadInterfaces();
|
|
510
|
+
};
|