@signalk/app-dock 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/public/dock.js ADDED
@@ -0,0 +1,456 @@
1
+ ;(async function DockApp() {
2
+ 'use strict'
3
+
4
+ const DEFAULTS = {
5
+ position: 'bottom',
6
+ triggerCorner: 'bottom-right',
7
+ iframeMode: 'keep-alive',
8
+ iconSize: 56,
9
+ magnification: true,
10
+ magnificationScale: 1.7,
11
+ apps: []
12
+ }
13
+
14
+ // ─── Load config from plugin endpoints ───────────────────────────────────────
15
+ let cfg = { ...DEFAULTS }
16
+ try {
17
+ const [configRes, settingsRes] = await Promise.all([
18
+ fetch('/plugins/signalk-app-dock/config'),
19
+ fetch('/plugins/signalk-app-dock/settings')
20
+ ])
21
+ if (configRes.ok) {
22
+ const data = await configRes.json()
23
+ const pluginCfg = data.configuration || data
24
+ cfg = { ...DEFAULTS, ...pluginCfg }
25
+ }
26
+ if (settingsRes.ok) {
27
+ const data = await settingsRes.json()
28
+ if (Array.isArray(data.apps) && data.apps.length > 0) {
29
+ cfg.apps = data.apps
30
+ }
31
+ }
32
+ } catch (e) {
33
+ console.warn('[Dock] Could not load config, using defaults.', e)
34
+ }
35
+
36
+ if (!Array.isArray(cfg.apps) || cfg.apps.length === 0) {
37
+ console.warn('[Dock] No apps configured — open Plugin Config to add webapps.')
38
+ }
39
+
40
+ // ─── DOM refs ────────────────────────────────────────────────────────────────
41
+ const $iframeContainer = document.getElementById('iframe-container')
42
+ const $backdrop = document.getElementById('dock-backdrop')
43
+ const $dock = document.getElementById('dock')
44
+ const $dockInner = document.getElementById('dock-inner')
45
+ const $triggerCorner = document.getElementById('trigger-corner')
46
+ const $loadingOverlay = document.getElementById('loading-overlay')
47
+ const $loadingLabel = document.getElementById('loading-label')
48
+ const $idleHint = document.getElementById('idle-hint')
49
+ const $idleHintText = document.getElementById('idle-hint-text')
50
+
51
+ // ─── State ───────────────────────────────────────────────────────────────────
52
+ let dockVisible = false
53
+ const iframes = {}
54
+
55
+ // ─── Apply dock position class & alignment ───────────────────────────────────
56
+ const pos = cfg.position
57
+ $dock.classList.add(`pos-${pos}`)
58
+ $dockInner.classList.add(`align-${pos}`)
59
+
60
+ const isVertical = pos === 'left' || pos === 'right'
61
+ if (isVertical) $dockInner.classList.add('layout-vertical')
62
+
63
+ // ─── Magnification constants ─────────────────────────────────────────────────
64
+ const MAG_ENABLED = cfg.magnification !== false
65
+ const MAG_SCALE = Math.max(1, Math.min(cfg.magnificationScale || 1.7, 2.5))
66
+ const MAG_RADIUS = cfg.iconSize * 3.5
67
+
68
+ // ─── Welcome screen ──────────────────────────────────────────────────────────
69
+ function setIdleHint() {
70
+ const $version = document.getElementById('idle-version')
71
+ if ($version) {
72
+ fetch('/skServer/webapps')
73
+ .then((r) => r.json())
74
+ .then((apps) => {
75
+ const me = apps.find((a) => a.name === 'signalk-app-dock')
76
+ if (me) $version.textContent = 'v' + me.version
77
+ })
78
+ .catch(() => {})
79
+ }
80
+
81
+ if (!cfg.apps || cfg.apps.length === 0) {
82
+ $idleHintText.innerHTML =
83
+ 'No apps configured yet.<br>' +
84
+ 'Open <strong>Admin UI \u2192 Plugin Config \u2192 App Dock</strong><br>' +
85
+ 'and click <strong>Discover Installed Webapps</strong> to get started.'
86
+ return
87
+ }
88
+
89
+ const cornerNames = {
90
+ 'bottom-right': 'bottom-right',
91
+ 'bottom-left': 'bottom-left',
92
+ 'top-right': 'top-right',
93
+ 'top-left': 'top-left'
94
+ }
95
+ const corner = cornerNames[cfg.triggerCorner] || 'bottom-right'
96
+
97
+ $idleHintText.innerHTML =
98
+ 'Double-tap the <strong>' +
99
+ corner +
100
+ ' corner</strong> to open the dock' +
101
+ '<br><br><span style="font-size:12px;opacity:0.6">Configure in Admin UI \u2192 Plugin Config \u2192 App Dock</span>'
102
+ }
103
+
104
+ function hideIdleHint() {
105
+ if ($idleHint) $idleHint.classList.add('hidden')
106
+ }
107
+
108
+ // ─── Tooltip direction based on dock edge ────────────────────────────────────
109
+ function labelClass() {
110
+ switch (pos) {
111
+ case 'top':
112
+ return 'dock-label-below'
113
+ case 'left':
114
+ return 'dock-label-right'
115
+ case 'right':
116
+ return 'dock-label-left'
117
+ default:
118
+ return ''
119
+ }
120
+ }
121
+
122
+ // ─── Build dock items ─────────────────────────────────────────────────────────
123
+ function buildDock() {
124
+ $dockInner.innerHTML = ''
125
+ const sz = cfg.iconSize
126
+ const radius = Math.round(sz * 0.25) + 'px'
127
+ const lblCls = labelClass()
128
+
129
+ cfg.apps.forEach((app, i) => {
130
+ const $item = document.createElement('div')
131
+ $item.className = 'dock-item'
132
+ $item.dataset.index = i
133
+
134
+ const $icon = document.createElement('div')
135
+ $icon.className = 'dock-icon'
136
+ $icon.style.width = sz + 'px'
137
+ $icon.style.height = sz + 'px'
138
+ $icon.style.borderRadius = radius
139
+ if (app.color) $icon.style.background = app.color
140
+
141
+ const iconVal = app.icon || ''
142
+ if (iconVal.startsWith('/') || iconVal.startsWith('http')) {
143
+ const $img = document.createElement('img')
144
+ $img.src = iconVal
145
+ $img.alt = app.label
146
+ $img.onerror = () => {
147
+ $icon.removeChild($img)
148
+ $icon.style.fontSize = Math.round(sz * 0.42) + 'px'
149
+ $icon.textContent = app.label.slice(0, 2).toUpperCase()
150
+ }
151
+ $icon.appendChild($img)
152
+ } else {
153
+ $icon.style.fontSize = Math.round(sz * 0.48) + 'px'
154
+ $icon.textContent = iconVal || app.label.slice(0, 2).toUpperCase()
155
+ }
156
+
157
+ const $dot = document.createElement('div')
158
+ $dot.className = 'dock-dot'
159
+
160
+ const $label = document.createElement('div')
161
+ $label.className = 'dock-label' + (lblCls ? ' ' + lblCls : '')
162
+ $label.textContent = app.label
163
+
164
+ $item.appendChild($icon)
165
+ $item.appendChild($dot)
166
+ $item.appendChild($label)
167
+
168
+ $item.addEventListener('touchstart', () => $item.classList.add('pressing'), { passive: true })
169
+ $item.addEventListener('touchend', () => $item.classList.remove('pressing'), { passive: true })
170
+ $item.addEventListener('touchcancel', () => $item.classList.remove('pressing'), { passive: true })
171
+
172
+ $item.addEventListener('click', (e) => {
173
+ if (magScrubbed) {
174
+ e.preventDefault()
175
+ return
176
+ }
177
+ switchToApp(i)
178
+ })
179
+
180
+ $dockInner.appendChild($item)
181
+ })
182
+ }
183
+
184
+ // ─── Magnification ───────────────────────────────────────────────────────────
185
+ let magRafPending = false
186
+ let magScrubbed = false
187
+ let magTouchStartAxis = 0
188
+
189
+ function applyMagnification(pointerPos) {
190
+ const items = $dockInner.querySelectorAll('.dock-item')
191
+ let closestIdx = -1
192
+ let closestDist = Infinity
193
+
194
+ items.forEach((item, i) => {
195
+ const icon = item.querySelector('.dock-icon')
196
+ const rect = item.getBoundingClientRect()
197
+ const center = isVertical ? rect.top + rect.height / 2 : rect.left + rect.width / 2
198
+ const p = isVertical ? pointerPos.y : pointerPos.x
199
+
200
+ const dist = Math.abs(p - center)
201
+ if (dist < closestDist) {
202
+ closestDist = dist
203
+ closestIdx = i
204
+ }
205
+
206
+ const ratio = Math.min(dist / MAG_RADIUS, 1)
207
+ const scale = 1 + (MAG_SCALE - 1) * (1 - ratio * ratio)
208
+ const newSize = Math.round(cfg.iconSize * scale)
209
+
210
+ icon.classList.remove('settling')
211
+ icon.style.width = newSize + 'px'
212
+ icon.style.height = newSize + 'px'
213
+ icon.style.borderRadius = Math.round(newSize * 0.25) + 'px'
214
+
215
+ if (icon.querySelector('img')) {
216
+ // image icons scale via width/height, fontSize not needed
217
+ } else {
218
+ icon.style.fontSize = Math.round(newSize * 0.48) + 'px'
219
+ }
220
+ })
221
+
222
+ items.forEach((item, i) => {
223
+ item.classList.toggle('mag-closest', i === closestIdx)
224
+ })
225
+ }
226
+
227
+ function resetMagnification() {
228
+ const items = $dockInner.querySelectorAll('.dock-item')
229
+ const sz = cfg.iconSize
230
+
231
+ items.forEach((item) => {
232
+ const icon = item.querySelector('.dock-icon')
233
+ icon.classList.add('settling')
234
+ icon.style.width = sz + 'px'
235
+ icon.style.height = sz + 'px'
236
+ icon.style.borderRadius = Math.round(sz * 0.25) + 'px'
237
+
238
+ if (!icon.querySelector('img')) {
239
+ icon.style.fontSize = Math.round(sz * 0.48) + 'px'
240
+ }
241
+
242
+ item.classList.remove('mag-closest')
243
+ })
244
+ }
245
+
246
+ if (MAG_ENABLED) {
247
+ $dockInner.addEventListener('mousemove', (e) => {
248
+ if (magRafPending) return
249
+ magRafPending = true
250
+ const pos = { x: e.clientX, y: e.clientY }
251
+ requestAnimationFrame(() => {
252
+ applyMagnification(pos)
253
+ magRafPending = false
254
+ })
255
+ })
256
+
257
+ $dockInner.addEventListener('mouseleave', () => {
258
+ magRafPending = false
259
+ resetMagnification()
260
+ })
261
+
262
+ $dockInner.addEventListener(
263
+ 'touchstart',
264
+ (e) => {
265
+ const t = e.touches[0]
266
+ magScrubbed = false
267
+ magTouchStartAxis = isVertical ? t.clientY : t.clientX
268
+ },
269
+ { passive: true }
270
+ )
271
+
272
+ $dockInner.addEventListener(
273
+ 'touchmove',
274
+ (e) => {
275
+ const t = e.touches[0]
276
+ const current = isVertical ? t.clientY : t.clientX
277
+ if (Math.abs(current - magTouchStartAxis) > 10) {
278
+ magScrubbed = true
279
+ }
280
+ if (magRafPending) return
281
+ magRafPending = true
282
+ const pos = { x: t.clientX, y: t.clientY }
283
+ requestAnimationFrame(() => {
284
+ applyMagnification(pos)
285
+ magRafPending = false
286
+ })
287
+ },
288
+ { passive: true }
289
+ )
290
+
291
+ $dockInner.addEventListener(
292
+ 'touchend',
293
+ () => {
294
+ magRafPending = false
295
+ resetMagnification()
296
+ setTimeout(() => {
297
+ magScrubbed = false
298
+ }, 50)
299
+ },
300
+ { passive: true }
301
+ )
302
+ }
303
+
304
+ // ─── Show / hide dock ────────────────────────────────────────────────────────
305
+ function showDock() {
306
+ if (dockVisible) return
307
+ dockVisible = true
308
+ $dock.classList.add('visible')
309
+ $backdrop.classList.add('visible')
310
+ }
311
+
312
+ function hideDock() {
313
+ if (!dockVisible) return
314
+ dockVisible = false
315
+ $dock.classList.remove('visible')
316
+ $backdrop.classList.remove('visible')
317
+ resetMagnification()
318
+ }
319
+
320
+ $backdrop.addEventListener('click', hideDock)
321
+
322
+ // ─── Loading overlay helpers ──────────────────────────────────────────────────
323
+ function showLoading(label) {
324
+ $loadingLabel.textContent = `Loading ${label}\u2026`
325
+ $loadingOverlay.classList.add('visible')
326
+ }
327
+
328
+ function hideLoading() {
329
+ $loadingOverlay.classList.remove('visible')
330
+ }
331
+
332
+ // ─── Switch to app ────────────────────────────────────────────────────────────
333
+ function switchToApp(index) {
334
+ const app = cfg.apps[index]
335
+ if (!app) return
336
+
337
+ hideIdleHint()
338
+
339
+ document.querySelectorAll('.dock-item').forEach((el, i) => {
340
+ el.classList.toggle('active', i === index)
341
+ })
342
+
343
+ const alreadyLoaded = !!iframes[app.url]
344
+
345
+ if (cfg.iframeMode === 'destroy') {
346
+ $iframeContainer.innerHTML = ''
347
+ Object.keys(iframes).forEach((k) => delete iframes[k])
348
+ } else {
349
+ Object.values(iframes).forEach((f) => f.classList.remove('active'))
350
+ }
351
+
352
+ if (!iframes[app.url]) {
353
+ if (!alreadyLoaded) showLoading(app.label)
354
+
355
+ const $frame = document.createElement('iframe')
356
+ $frame.allow = 'fullscreen; geolocation'
357
+ $frame.sandbox = 'allow-same-origin allow-scripts allow-forms allow-popups allow-pointer-lock'
358
+ $frame.style.touchAction = 'auto'
359
+
360
+ $frame.addEventListener('load', hideLoading, { once: true })
361
+
362
+ $iframeContainer.appendChild($frame)
363
+ $frame.src = app.url
364
+
365
+ iframes[app.url] = $frame
366
+ }
367
+
368
+ iframes[app.url].classList.add('active')
369
+ hideDock()
370
+ }
371
+
372
+ // ─── Corner trigger zone: position it ────────────────────────────────────────
373
+ function positionCornerZone() {
374
+ const [vert, horiz] = cfg.triggerCorner.split('-')
375
+ const sz = 120
376
+
377
+ $triggerCorner.style.width = sz + 'px'
378
+ $triggerCorner.style.height = sz + 'px'
379
+
380
+ $triggerCorner.style.top = ''
381
+ $triggerCorner.style.bottom = ''
382
+ $triggerCorner.style.left = ''
383
+ $triggerCorner.style.right = ''
384
+
385
+ $triggerCorner.style[vert] = '0'
386
+ $triggerCorner.style[horiz] = '0'
387
+ }
388
+
389
+ // ─── Gesture: double-tap on corner (touch) + double-click (mouse) ─────────────
390
+ {
391
+ let lastTapTime = 0
392
+ const DOUBLE_TAP_MS = 400
393
+
394
+ $triggerCorner.addEventListener(
395
+ 'touchend',
396
+ (e) => {
397
+ const now = Date.now()
398
+ if (now - lastTapTime < DOUBLE_TAP_MS) {
399
+ e.preventDefault()
400
+ lastTapTime = 0
401
+ showDock()
402
+ if (navigator.vibrate) navigator.vibrate(8)
403
+ } else {
404
+ lastTapTime = now
405
+ }
406
+ },
407
+ { passive: false }
408
+ )
409
+
410
+ $triggerCorner.addEventListener('dblclick', (e) => {
411
+ e.preventDefault()
412
+ showDock()
413
+ })
414
+
415
+ $triggerCorner.addEventListener('contextmenu', (e) => e.preventDefault())
416
+ }
417
+
418
+ // ─── Gesture: mouse hover at screen edge ─────────────────────────────────────
419
+ {
420
+ const EDGE_ZONE = 4
421
+ let edgeTimer = null
422
+
423
+ document.addEventListener('mousemove', (e) => {
424
+ if (dockVisible) return
425
+ const W = window.innerWidth
426
+ const H = window.innerHeight
427
+ let atEdge = false
428
+
429
+ if (pos === 'bottom' && e.clientY >= H - EDGE_ZONE) atEdge = true
430
+ if (pos === 'top' && e.clientY <= EDGE_ZONE) atEdge = true
431
+ if (pos === 'left' && e.clientX <= EDGE_ZONE) atEdge = true
432
+ if (pos === 'right' && e.clientX >= W - EDGE_ZONE) atEdge = true
433
+
434
+ if (atEdge && !edgeTimer) {
435
+ edgeTimer = setTimeout(() => {
436
+ showDock()
437
+ edgeTimer = null
438
+ }, 300)
439
+ } else if (!atEdge && edgeTimer) {
440
+ clearTimeout(edgeTimer)
441
+ edgeTimer = null
442
+ }
443
+ })
444
+ }
445
+
446
+ // ─── Init ────────────────────────────────────────────────────────────────────
447
+ positionCornerZone()
448
+ buildDock()
449
+
450
+ const autostartIdx = cfg.apps.findIndex((a) => a.autostart)
451
+ if (autostartIdx >= 0) {
452
+ switchToApp(autostartIdx)
453
+ } else {
454
+ setIdleHint()
455
+ }
456
+ })()
@@ -0,0 +1,21 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 72 72" width="72" height="72">
2
+ <defs>
3
+ <linearGradient id="bg" x1="0" y1="0" x2="0" y2="1">
4
+ <stop offset="0%" stop-color="#8e8e93"/>
5
+ <stop offset="100%" stop-color="#636366"/>
6
+ </linearGradient>
7
+ </defs>
8
+ <rect width="72" height="72" rx="16" fill="url(#bg)"/>
9
+ <g transform="translate(36,36)">
10
+ <!-- Gear body -->
11
+ <path d="
12
+ M-2.8-16.5 a2.8 2.8 0 0 1 5.6 0 l.4 2.8 a13 13 0 0 1 3.6 1.5 l2.4-1.4 a2.8 2.8 0 0 1 3.8 1 l2.8 4.9 a2.8 2.8 0 0 1-1 3.8 l-2 1.4 a13 13 0 0 1 0 3.9 l2 1.4 a2.8 2.8 0 0 1 1 3.8 l-2.8 4.9 a2.8 2.8 0 0 1-3.8 1 l-2.4-1.4 a13 13 0 0 1-3.6 1.5 l-.4 2.8 a2.8 2.8 0 0 1-5.6 0 l-.4-2.8 a13 13 0 0 1-3.6-1.5 l-2.4 1.4 a2.8 2.8 0 0 1-3.8-1 l-2.8-4.9 a2.8 2.8 0 0 1 1-3.8 l2-1.4 a13 13 0 0 1 0-3.9 l-2-1.4 a2.8 2.8 0 0 1-1-3.8 l2.8-4.9 a2.8 2.8 0 0 1 3.8-1 l2.4 1.4 a13 13 0 0 1 3.6-1.5 Z
13
+ " fill="#e5e5ea" stroke="none"/>
14
+ <!-- Center hole -->
15
+ <circle r="5.5" fill="url(#bg)"/>
16
+ <!-- Inner ring highlight -->
17
+ <circle r="5.5" fill="none" stroke="#c7c7cc" stroke-width="1"/>
18
+ <!-- Outer subtle highlight -->
19
+ <circle r="9" fill="none" stroke="#d1d1d6" stroke-width="0.5" opacity="0.5"/>
20
+ </g>
21
+ </svg>
@@ -0,0 +1,41 @@
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.0, user-scalable=no, viewport-fit=cover" />
6
+ <meta name="mobile-web-app-capable" content="yes" />
7
+ <title>SK Dock</title>
8
+ <link rel="stylesheet" href="dock.css" />
9
+ </head>
10
+ <body>
11
+ <!-- Full-screen iframe container: only the active iframe is visible -->
12
+ <div id="iframe-container"></div>
13
+
14
+ <!-- Welcome screen shown before any app is opened -->
15
+ <div id="idle-hint">
16
+ <img id="idle-logo" src="app-icon.svg" alt="App Dock" />
17
+ <div id="idle-title">App Dock</div>
18
+ <div id="idle-version"></div>
19
+ <div id="idle-hint-text"></div>
20
+ </div>
21
+
22
+ <!-- Dimming overlay shown when dock is open; tap to close -->
23
+ <div id="dock-backdrop"></div>
24
+
25
+ <!-- The dock itself - slides in from the configured edge -->
26
+ <nav id="dock" role="navigation" aria-label="App dock">
27
+ <div id="dock-inner"></div>
28
+ </nav>
29
+
30
+ <!-- Invisible corner hot-zone for long-press trigger -->
31
+ <div id="trigger-corner" aria-hidden="true"></div>
32
+
33
+ <!-- Loading indicator shown while an iframe is loading for the first time -->
34
+ <div id="loading-overlay" aria-hidden="true">
35
+ <div class="spinner"></div>
36
+ <span id="loading-label"></span>
37
+ </div>
38
+
39
+ <script src="dock.js"></script>
40
+ </body>
41
+ </html>
package/public/main.js ADDED
@@ -0,0 +1 @@
1
+ (()=>{var e,r,t={316(){}},a={};function o(e){var r=a[e];if(void 0!==r)return r.exports;var n=a[e]={exports:{}};return t[e](n,n.exports,o),n.exports}o.m=t,o.c=a,o.f={},o.e=e=>Promise.all(Object.keys(o.f).reduce((r,t)=>(o.f[t](e,r),r),[])),o.u=e=>e+".js",o.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),o.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),e={},r="signalk-app-dock:",o.l=(t,a,n,i)=>{if(e[t])e[t].push(a);else{var l,c;if(void 0!==n)for(var s=document.getElementsByTagName("script"),p=0;p<s.length;p++){var u=s[p];if(u.getAttribute("src")==t||u.getAttribute("data-webpack")==r+n){l=u;break}}l||(c=!0,(l=document.createElement("script")).charset="utf-8",o.nc&&l.setAttribute("nonce",o.nc),l.setAttribute("data-webpack",r+n),l.src=t),e[t]=[a];var d=(r,a)=>{l.onerror=l.onload=null,clearTimeout(f);var o=e[t];if(delete e[t],l.parentNode&&l.parentNode.removeChild(l),o&&o.forEach(e=>e(a)),r)return r(a)},f=setTimeout(d.bind(null,void 0,{type:"timeout",target:l}),12e4);l.onerror=d.bind(null,l.onerror),l.onload=d.bind(null,l.onload),c&&document.head.appendChild(l)}},(()=>{o.S={};var e={},r={};o.I=(t,a)=>{a||(a=[]);var n=r[t];if(n||(n=r[t]={}),!(a.indexOf(n)>=0)){if(a.push(n),e[t])return e[t];o.o(o.S,t)||(o.S[t]={});var i=o.S[t],l="signalk-app-dock",c=[];return"default"===t&&((e,r,t,a)=>{var n=i[e]=i[e]||{},c=n[r];(!c||!c.loaded&&(1!=!c.eager?a:l>c.from))&&(n[r]={get:()=>o.e(540).then(()=>()=>o(540)),from:l,eager:!1})})("react","19.2.4"),e[t]=c.length?Promise.all(c).then(()=>e[t]=1):1}}})(),(()=>{var e;o.g.importScripts&&(e=o.g.location+"");var r=o.g.document;if(!e&&r&&(r.currentScript&&"SCRIPT"===r.currentScript.tagName.toUpperCase()&&(e=r.currentScript.src),!e)){var t=r.getElementsByTagName("script");if(t.length)for(var a=t.length-1;a>-1&&(!e||!/^http(s?):/.test(e));)e=t[a--].src}if(!e)throw new Error("Automatic publicPath is not supported in this browser");e=e.replace(/^blob:/,"").replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),o.p=e})(),(()=>{var e={792:0};o.f.j=(r,t)=>{var a=o.o(e,r)?e[r]:void 0;if(0!==a)if(a)t.push(a[2]);else{var n=new Promise((t,o)=>a=e[r]=[t,o]);t.push(a[2]=n);var i=o.p+o.u(r),l=new Error;o.l(i,t=>{if(o.o(e,r)&&(0!==(a=e[r])&&(e[r]=void 0),a)){var n=t&&("load"===t.type?"missing":t.type),i=t&&t.target&&t.target.src;l.message="Loading chunk "+r+" failed.\n("+n+": "+i+")",l.name="ChunkLoadError",l.type=n,l.request=i,a[1](l)}},"chunk-"+r,r)}};var r=(r,t)=>{var a,n,[i,l,c]=t,s=0;if(i.some(r=>0!==e[r])){for(a in l)o.o(l,a)&&(o.m[a]=l[a]);c&&c(o)}for(r&&r(t);s<i.length;s++)n=i[s],o.o(e,n)&&e[n]&&e[n][0](),e[n]=0},t=self.webpackChunksignalk_app_dock=self.webpackChunksignalk_app_dock||[];t.forEach(r.bind(null,0)),t.push=r.bind(null,t.push.bind(t))})(),o(316)})();
@@ -0,0 +1 @@
1
+ var signalk_app_dock;(()=>{"use strict";var e,r,t,n,o,a,i,u,l,s,p,f,c,d,h,v,g,m,b,y={623(e,r,t){var n={"./PluginConfigurationPanel":()=>t.e(805).then(()=>()=>t(805))},o=(e,r)=>(t.R=r,r=t.o(n,e)?n[e]():Promise.resolve().then(()=>{throw new Error('Module "'+e+'" does not exist in container.')}),t.R=void 0,r),a=(e,r)=>{if(t.S){var n="default",o=t.S[n];if(o&&o!==e)throw new Error("Container initialization failed as it has already been initialized with a different share scope");return t.S[n]=e,t.I(n,r)}};t.d(r,{get:()=>o,init:()=>a})}},k={};function w(e){var r=k[e];if(void 0!==r)return r.exports;var t=k[e]={exports:{}};return y[e](t,t.exports,w),t.exports}w.m=y,w.c=k,w.d=(e,r)=>{for(var t in r)w.o(r,t)&&!w.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:r[t]})},w.f={},w.e=e=>Promise.all(Object.keys(w.f).reduce((r,t)=>(w.f[t](e,r),r),[])),w.u=e=>e+".js",w.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),w.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),e={},r="signalk-app-dock:",w.l=(t,n,o,a)=>{if(e[t])e[t].push(n);else{var i,u;if(void 0!==o)for(var l=document.getElementsByTagName("script"),s=0;s<l.length;s++){var p=l[s];if(p.getAttribute("src")==t||p.getAttribute("data-webpack")==r+o){i=p;break}}i||(u=!0,(i=document.createElement("script")).charset="utf-8",w.nc&&i.setAttribute("nonce",w.nc),i.setAttribute("data-webpack",r+o),i.src=t),e[t]=[n];var f=(r,n)=>{i.onerror=i.onload=null,clearTimeout(c);var o=e[t];if(delete e[t],i.parentNode&&i.parentNode.removeChild(i),o&&o.forEach(e=>e(n)),r)return r(n)},c=setTimeout(f.bind(null,void 0,{type:"timeout",target:i}),12e4);i.onerror=f.bind(null,i.onerror),i.onload=f.bind(null,i.onload),u&&document.head.appendChild(i)}},w.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},(()=>{w.S={};var e={},r={};w.I=(t,n)=>{n||(n=[]);var o=r[t];if(o||(o=r[t]={}),!(n.indexOf(o)>=0)){if(n.push(o),e[t])return e[t];w.o(w.S,t)||(w.S[t]={});var a=w.S[t],i="signalk-app-dock",u=[];return"default"===t&&((e,r,t,n)=>{var o=a[e]=a[e]||{},u=o[r];(!u||!u.loaded&&(1!=!u.eager?n:i>u.from))&&(o[r]={get:()=>w.e(540).then(()=>()=>w(540)),from:i,eager:!1})})("react","19.2.4"),e[t]=u.length?Promise.all(u).then(()=>e[t]=1):1}}})(),(()=>{var e;w.g.importScripts&&(e=w.g.location+"");var r=w.g.document;if(!e&&r&&(r.currentScript&&"SCRIPT"===r.currentScript.tagName.toUpperCase()&&(e=r.currentScript.src),!e)){var t=r.getElementsByTagName("script");if(t.length)for(var n=t.length-1;n>-1&&(!e||!/^http(s?):/.test(e));)e=t[n--].src}if(!e)throw new Error("Automatic publicPath is not supported in this browser");e=e.replace(/^blob:/,"").replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),w.p=e})(),t=e=>{var r=e=>e.split(".").map(e=>+e==e?+e:e),t=/^([^-+]+)?(?:-([^+]+))?(?:\+(.+))?$/.exec(e),n=t[1]?r(t[1]):[];return t[2]&&(n.length++,n.push.apply(n,r(t[2]))),t[3]&&(n.push([]),n.push.apply(n,r(t[3]))),n},n=(e,r)=>{e=t(e),r=t(r);for(var n=0;;){if(n>=e.length)return n<r.length&&"u"!=(typeof r[n])[0];var o=e[n],a=(typeof o)[0];if(n>=r.length)return"u"==a;var i=r[n],u=(typeof i)[0];if(a!=u)return"o"==a&&"n"==u||"s"==u||"u"==a;if("o"!=a&&"u"!=a&&o!=i)return o<i;n++}},o=e=>{var r=e[0],t="";if(1===e.length)return"*";if(r+.5){t+=0==r?">=":-1==r?"<":1==r?"^":2==r?"~":r>0?"=":"!=";for(var n=1,a=1;a<e.length;a++)n--,t+="u"==(typeof(u=e[a]))[0]?"-":(n>0?".":"")+(n=2,u);return t}var i=[];for(a=1;a<e.length;a++){var u=e[a];i.push(0===u?"not("+l()+")":1===u?"("+l()+" || "+l()+")":2===u?i.pop()+" "+i.pop():o(u))}return l();function l(){return i.pop().replace(/^\((.+)\)$/,"$1")}},a=(e,r)=>{if(0 in e){r=t(r);var n=e[0],o=n<0;o&&(n=-n-1);for(var i=0,u=1,l=!0;;u++,i++){var s,p,f=u<e.length?(typeof e[u])[0]:"";if(i>=r.length||"o"==(p=(typeof(s=r[i]))[0]))return!l||("u"==f?u>n&&!o:""==f!=o);if("u"==p){if(!l||"u"!=f)return!1}else if(l)if(f==p)if(u<=n){if(s!=e[u])return!1}else{if(o?s>e[u]:s<e[u])return!1;s!=e[u]&&(l=!1)}else if("s"!=f&&"n"!=f){if(o||u<=n)return!1;l=!1,u--}else{if(u<=n||p<f!=o)return!1;l=!1}else"s"!=f&&"n"!=f&&(l=!1,u--)}}var c=[],d=c.pop.bind(c);for(i=1;i<e.length;i++){var h=e[i];c.push(1==h?d()|d():2==h?d()&d():h?a(h,r):!d())}return!!d()},i=(e,r)=>e&&w.o(e,r),u=e=>(e.loaded=1,e.get()),l=e=>Object.keys(e).reduce((r,t)=>(e[t].eager&&(r[t]=e[t]),r),{}),s=(e,r,t)=>{var o=t?l(e[r]):e[r];return Object.keys(o).reduce((e,r)=>!e||!o[e].loaded&&n(e,r)?r:e,0)},p=(e,r,t,n)=>"Unsatisfied version "+t+" from "+(t&&e[r][t].from)+" of shared singleton module "+r+" (required "+o(n)+")",f=e=>{throw new Error(e)},c=e=>{"undefined"!=typeof console&&console.warn&&console.warn(e)},d=(e,r,t)=>t?t():((e,r)=>f("Shared module "+r+" doesn't exist in shared scope "+e))(e,r),h=(e=>function(r,t,n,o,a){var i=w.I(r);return i&&i.then&&!n?i.then(e.bind(e,r,w.S[r],t,!1,o,a)):e(r,w.S[r],t,n,o,a)})((e,r,t,n,o,l)=>{if(!i(r,t))return d(e,t,l);var f=s(r,t,n);return a(o,f)||c(p(r,t,f,o)),u(r[t][f])}),v={},g={231:()=>h("default","react",!1,[1,19],()=>w.e(540).then(()=>()=>w(540)))},m={805:[231]},b={},w.f.consumes=(e,r)=>{w.o(m,e)&&m[e].forEach(e=>{if(w.o(v,e))return r.push(v[e]);if(!b[e]){var t=r=>{v[e]=0,w.m[e]=t=>{delete w.c[e],t.exports=r()}};b[e]=!0;var n=r=>{delete v[e],w.m[e]=t=>{throw delete w.c[e],r}};try{var o=g[e]();o.then?r.push(v[e]=o.then(t).catch(n)):t(o)}catch(e){n(e)}}})},(()=>{var e={228:0};w.f.j=(r,t)=>{var n=w.o(e,r)?e[r]:void 0;if(0!==n)if(n)t.push(n[2]);else{var o=new Promise((t,o)=>n=e[r]=[t,o]);t.push(n[2]=o);var a=w.p+w.u(r),i=new Error;w.l(a,t=>{if(w.o(e,r)&&(0!==(n=e[r])&&(e[r]=void 0),n)){var o=t&&("load"===t.type?"missing":t.type),a=t&&t.target&&t.target.src;i.message="Loading chunk "+r+" failed.\n("+o+": "+a+")",i.name="ChunkLoadError",i.type=o,i.request=a,n[1](i)}},"chunk-"+r,r)}};var r=(r,t)=>{var n,o,[a,i,u]=t,l=0;if(a.some(r=>0!==e[r])){for(n in i)w.o(i,n)&&(w.m[n]=i[n]);u&&u(w)}for(r&&r(t);l<a.length;l++)o=a[l],w.o(e,o)&&e[o]&&e[o][0](),e[o]=0},t=self.webpackChunksignalk_app_dock=self.webpackChunksignalk_app_dock||[];t.forEach(r.bind(null,0)),t.push=r.bind(null,t.push.bind(t))})();var S=w(623);signalk_app_dock=S})();