@signalk/app-dock 1.4.0 → 1.4.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/README.md +23 -12
- package/app-icon.svg +21 -0
- package/dist/plugin/index.js +141 -0
- package/dist/plugin/schema.js +58 -0
- package/package.json +32 -14
- package/public/config.html +4 -437
- package/public/config.js +22 -0
- package/public/configpanel/PluginConfigurationPanel.js +1 -0
- package/public/configpanel/_virtual_mf-localSharedImportMap___mfe_internal___signalk_app_dock__mf_owner__1-_R-vaMcG.js +1 -0
- package/public/configpanel/dist-D1jGdgWH.js +4 -0
- package/public/configpanel/hostInit-D-z9PzRy.js +1 -0
- package/public/configpanel/pendingShares-BqCOmgFj.js +1 -0
- package/public/configpanel/vite-preload-helper-DHhhL8Zf.js +1 -0
- package/public/dock.css +1 -610
- package/public/dock.js +1 -1129
- package/public/index.html +6 -6
- package/public/remoteEntry.js +1 -1
- package/plugin/index.js +0 -273
- package/public/540.js +0 -2
- package/public/540.js.LICENSE.txt +0 -9
- package/public/805.js +0 -1
- package/public/main.js +0 -1
package/public/config.html
CHANGED
|
@@ -4,232 +4,8 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>App Dock - Configuration</title>
|
|
7
|
-
<style>
|
|
8
|
-
|
|
9
|
-
box-sizing: border-box;
|
|
10
|
-
margin: 0;
|
|
11
|
-
padding: 0;
|
|
12
|
-
}
|
|
13
|
-
body {
|
|
14
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
15
|
-
background: #1a1a2e;
|
|
16
|
-
color: #e0e0e0;
|
|
17
|
-
padding: 24px;
|
|
18
|
-
min-height: 100vh;
|
|
19
|
-
}
|
|
20
|
-
h1 {
|
|
21
|
-
font-size: 22px;
|
|
22
|
-
font-weight: 600;
|
|
23
|
-
margin-bottom: 8px;
|
|
24
|
-
}
|
|
25
|
-
.subtitle {
|
|
26
|
-
color: #888;
|
|
27
|
-
font-size: 13px;
|
|
28
|
-
margin-bottom: 24px;
|
|
29
|
-
}
|
|
30
|
-
.section {
|
|
31
|
-
margin-bottom: 32px;
|
|
32
|
-
}
|
|
33
|
-
.section-title {
|
|
34
|
-
font-size: 14px;
|
|
35
|
-
font-weight: 600;
|
|
36
|
-
color: #aaa;
|
|
37
|
-
text-transform: uppercase;
|
|
38
|
-
letter-spacing: 0.05em;
|
|
39
|
-
margin-bottom: 12px;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/* Discover button */
|
|
43
|
-
.btn {
|
|
44
|
-
display: inline-flex;
|
|
45
|
-
align-items: center;
|
|
46
|
-
gap: 8px;
|
|
47
|
-
padding: 10px 20px;
|
|
48
|
-
border: none;
|
|
49
|
-
border-radius: 8px;
|
|
50
|
-
font-size: 14px;
|
|
51
|
-
font-weight: 600;
|
|
52
|
-
cursor: pointer;
|
|
53
|
-
transition: all 0.15s ease;
|
|
54
|
-
}
|
|
55
|
-
.btn-primary {
|
|
56
|
-
background: #3b82f6;
|
|
57
|
-
color: #fff;
|
|
58
|
-
}
|
|
59
|
-
.btn-primary:hover {
|
|
60
|
-
background: #2563eb;
|
|
61
|
-
}
|
|
62
|
-
.btn-primary:disabled {
|
|
63
|
-
background: #555;
|
|
64
|
-
cursor: not-allowed;
|
|
65
|
-
}
|
|
66
|
-
.btn-save {
|
|
67
|
-
background: #10b981;
|
|
68
|
-
color: #fff;
|
|
69
|
-
}
|
|
70
|
-
.btn-save:hover {
|
|
71
|
-
background: #059669;
|
|
72
|
-
}
|
|
73
|
-
.btn-danger {
|
|
74
|
-
background: #ef4444;
|
|
75
|
-
color: #fff;
|
|
76
|
-
padding: 6px 12px;
|
|
77
|
-
font-size: 12px;
|
|
78
|
-
}
|
|
79
|
-
.btn-danger:hover {
|
|
80
|
-
background: #dc2626;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/* App list */
|
|
84
|
-
.app-list {
|
|
85
|
-
list-style: none;
|
|
86
|
-
}
|
|
87
|
-
.app-item {
|
|
88
|
-
display: flex;
|
|
89
|
-
align-items: center;
|
|
90
|
-
gap: 12px;
|
|
91
|
-
padding: 10px 14px;
|
|
92
|
-
background: rgba(255, 255, 255, 0.05);
|
|
93
|
-
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
94
|
-
border-radius: 10px;
|
|
95
|
-
margin-bottom: 8px;
|
|
96
|
-
cursor: grab;
|
|
97
|
-
transition:
|
|
98
|
-
background 0.15s ease,
|
|
99
|
-
border-color 0.15s ease;
|
|
100
|
-
}
|
|
101
|
-
.app-item:hover {
|
|
102
|
-
background: rgba(255, 255, 255, 0.08);
|
|
103
|
-
border-color: rgba(255, 255, 255, 0.15);
|
|
104
|
-
}
|
|
105
|
-
.app-item.dragging {
|
|
106
|
-
opacity: 0.5;
|
|
107
|
-
background: rgba(59, 130, 246, 0.15);
|
|
108
|
-
}
|
|
109
|
-
.app-item.drag-over {
|
|
110
|
-
border-color: #3b82f6;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
.drag-handle {
|
|
114
|
-
color: #555;
|
|
115
|
-
font-size: 18px;
|
|
116
|
-
cursor: grab;
|
|
117
|
-
user-select: none;
|
|
118
|
-
flex-shrink: 0;
|
|
119
|
-
width: 20px;
|
|
120
|
-
text-align: center;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
.app-icon-preview {
|
|
124
|
-
width: 40px;
|
|
125
|
-
height: 40px;
|
|
126
|
-
border-radius: 10px;
|
|
127
|
-
display: flex;
|
|
128
|
-
align-items: center;
|
|
129
|
-
justify-content: center;
|
|
130
|
-
background: rgba(58, 58, 62, 0.9);
|
|
131
|
-
overflow: hidden;
|
|
132
|
-
flex-shrink: 0;
|
|
133
|
-
font-size: 20px;
|
|
134
|
-
}
|
|
135
|
-
.app-icon-preview img {
|
|
136
|
-
width: 100%;
|
|
137
|
-
height: 100%;
|
|
138
|
-
object-fit: contain;
|
|
139
|
-
padding: 6px;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
.app-info {
|
|
143
|
-
flex: 1;
|
|
144
|
-
min-width: 0;
|
|
145
|
-
}
|
|
146
|
-
.app-name {
|
|
147
|
-
font-size: 14px;
|
|
148
|
-
font-weight: 600;
|
|
149
|
-
}
|
|
150
|
-
.app-url {
|
|
151
|
-
font-size: 11px;
|
|
152
|
-
color: #777;
|
|
153
|
-
overflow: hidden;
|
|
154
|
-
text-overflow: ellipsis;
|
|
155
|
-
white-space: nowrap;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
.app-toggle {
|
|
159
|
-
flex-shrink: 0;
|
|
160
|
-
}
|
|
161
|
-
.app-toggle input[type='checkbox'] {
|
|
162
|
-
width: 18px;
|
|
163
|
-
height: 18px;
|
|
164
|
-
cursor: pointer;
|
|
165
|
-
accent-color: #3b82f6;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
.app-actions {
|
|
169
|
-
flex-shrink: 0;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
.empty-state {
|
|
173
|
-
text-align: center;
|
|
174
|
-
padding: 40px 20px;
|
|
175
|
-
color: #666;
|
|
176
|
-
font-size: 14px;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
.status {
|
|
180
|
-
margin-top: 12px;
|
|
181
|
-
font-size: 13px;
|
|
182
|
-
color: #10b981;
|
|
183
|
-
min-height: 20px;
|
|
184
|
-
}
|
|
185
|
-
.status.error {
|
|
186
|
-
color: #ef4444;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
.actions-bar {
|
|
190
|
-
display: flex;
|
|
191
|
-
gap: 12px;
|
|
192
|
-
align-items: center;
|
|
193
|
-
margin-top: 20px;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/* Dock preview */
|
|
197
|
-
.dock-preview {
|
|
198
|
-
display: flex;
|
|
199
|
-
align-items: flex-end;
|
|
200
|
-
justify-content: center;
|
|
201
|
-
gap: 8px;
|
|
202
|
-
padding: 14px 20px;
|
|
203
|
-
background: rgba(30, 30, 32, 0.55);
|
|
204
|
-
backdrop-filter: blur(60px);
|
|
205
|
-
border: 1px solid rgba(255, 255, 255, 0.18);
|
|
206
|
-
border-radius: 22px;
|
|
207
|
-
box-shadow: 0 16px 48px rgba(0, 0, 0, 0.5);
|
|
208
|
-
margin: 20px auto;
|
|
209
|
-
width: fit-content;
|
|
210
|
-
}
|
|
211
|
-
.dock-preview-icon {
|
|
212
|
-
width: 48px;
|
|
213
|
-
height: 48px;
|
|
214
|
-
border-radius: 12px;
|
|
215
|
-
display: flex;
|
|
216
|
-
align-items: center;
|
|
217
|
-
justify-content: center;
|
|
218
|
-
background: rgba(58, 58, 62, 0.9);
|
|
219
|
-
overflow: hidden;
|
|
220
|
-
font-size: 22px;
|
|
221
|
-
transition: transform 0.15s ease;
|
|
222
|
-
}
|
|
223
|
-
.dock-preview-icon:hover {
|
|
224
|
-
transform: scale(1.3);
|
|
225
|
-
}
|
|
226
|
-
.dock-preview-icon img {
|
|
227
|
-
width: 100%;
|
|
228
|
-
height: 100%;
|
|
229
|
-
object-fit: contain;
|
|
230
|
-
padding: 6px;
|
|
231
|
-
}
|
|
232
|
-
</style>
|
|
7
|
+
<style>*{box-sizing:border-box;margin:0;padding:0}body{color:#e0e0e0;background:#1a1a2e;min-height:100vh;padding:24px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}h1{margin-bottom:8px;font-size:22px;font-weight:600}.subtitle{color:#888;margin-bottom:24px;font-size:13px}.section{margin-bottom:32px}.section-title{color:#aaa;text-transform:uppercase;letter-spacing:.05em;margin-bottom:12px;font-size:14px;font-weight:600}.btn{cursor:pointer;border:none;border-radius:8px;align-items:center;gap:8px;padding:10px 20px;font-size:14px;font-weight:600;transition:all .15s;display:inline-flex}.btn-primary{color:#fff;background:#3b82f6}.btn-primary:hover{background:#2563eb}.btn-primary:disabled{cursor:not-allowed;background:#555}.btn-save{color:#fff;background:#10b981}.btn-save:hover{background:#059669}.btn-danger{color:#fff;background:#ef4444;padding:6px 12px;font-size:12px}.btn-danger:hover{background:#dc2626}.app-list{list-style:none}.app-item{cursor:grab;background:#ffffff0d;border:1px solid #ffffff14;border-radius:10px;align-items:center;gap:12px;margin-bottom:8px;padding:10px 14px;transition:background .15s,border-color .15s;display:flex}.app-item:hover{background:#ffffff14;border-color:#ffffff26}.app-item.dragging{opacity:.5;background:#3b82f626}.app-item.drag-over{border-color:#3b82f6}.drag-handle{color:#555;cursor:grab;-webkit-user-select:none;user-select:none;text-align:center;flex-shrink:0;width:20px;font-size:18px}.app-icon-preview{background:#3a3a3ee6;border-radius:10px;flex-shrink:0;justify-content:center;align-items:center;width:40px;height:40px;font-size:20px;display:flex;overflow:hidden}.app-icon-preview img{object-fit:contain;width:100%;height:100%;padding:6px}.app-info{flex:1;min-width:0}.app-name{font-size:14px;font-weight:600}.app-url{color:#777;text-overflow:ellipsis;white-space:nowrap;font-size:11px;overflow:hidden}.app-toggle{flex-shrink:0}.app-toggle input[type=checkbox]{cursor:pointer;accent-color:#3b82f6;width:18px;height:18px}.app-actions{flex-shrink:0}.empty-state{text-align:center;color:#666;padding:40px 20px;font-size:14px}.status{color:#10b981;min-height:20px;margin-top:12px;font-size:13px}.status.error{color:#ef4444}.actions-bar{align-items:center;gap:12px;margin-top:20px;display:flex}.dock-preview{-webkit-backdrop-filter:blur(60px);backdrop-filter:blur(60px);background:#1e1e208c;border:1px solid #ffffff2e;border-radius:22px;justify-content:center;align-items:flex-end;gap:8px;width:-moz-fit-content;width:fit-content;margin:20px auto;padding:14px 20px;display:flex;box-shadow:0 16px 48px #00000080}.dock-preview-icon{background:#3a3a3ee6;border-radius:12px;justify-content:center;align-items:center;width:48px;height:48px;font-size:22px;transition:transform .15s;display:flex;overflow:hidden}.dock-preview-icon:hover{transform:scale(1.3)}.dock-preview-icon img{object-fit:contain;width:100%;height:100%;padding:6px}</style>
|
|
8
|
+
<script type="module" crossorigin src="./config.js"></script>
|
|
233
9
|
</head>
|
|
234
10
|
<body>
|
|
235
11
|
<h1>App Dock Configuration</h1>
|
|
@@ -237,7 +13,7 @@
|
|
|
237
13
|
|
|
238
14
|
<div class="section">
|
|
239
15
|
<div class="section-title">Webapps</div>
|
|
240
|
-
<button class="btn btn-primary" id="discover-btn"
|
|
16
|
+
<button class="btn btn-primary" id="discover-btn">Discover Installed Webapps</button>
|
|
241
17
|
<div class="status" id="status"></div>
|
|
242
18
|
</div>
|
|
243
19
|
|
|
@@ -253,217 +29,8 @@
|
|
|
253
29
|
</div>
|
|
254
30
|
|
|
255
31
|
<div class="actions-bar">
|
|
256
|
-
<button class="btn btn-save"
|
|
32
|
+
<button class="btn btn-save" id="save-btn">Save Configuration</button>
|
|
257
33
|
</div>
|
|
258
34
|
|
|
259
|
-
<script>
|
|
260
|
-
let apps = []
|
|
261
|
-
|
|
262
|
-
async function loadCurrent() {
|
|
263
|
-
try {
|
|
264
|
-
const res = await fetch('/plugins/signalk-app-dock/config')
|
|
265
|
-
if (res.ok) {
|
|
266
|
-
const data = await res.json()
|
|
267
|
-
apps = (data.configuration && data.configuration.apps) || []
|
|
268
|
-
render()
|
|
269
|
-
}
|
|
270
|
-
} catch (e) {
|
|
271
|
-
console.warn('Could not load current config', e)
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
async function discover() {
|
|
276
|
-
const btn = document.getElementById('discover-btn')
|
|
277
|
-
const status = document.getElementById('status')
|
|
278
|
-
btn.disabled = true
|
|
279
|
-
btn.textContent = 'Discovering...'
|
|
280
|
-
status.textContent = ''
|
|
281
|
-
status.className = 'status'
|
|
282
|
-
|
|
283
|
-
try {
|
|
284
|
-
const res = await fetch('/skServer/webapps')
|
|
285
|
-
const allWebapps = await res.json()
|
|
286
|
-
const webapps = allWebapps
|
|
287
|
-
.filter((w) => w.name !== '@signalk/app-dock' && w.name !== '@signalk/server-admin-ui')
|
|
288
|
-
.map((w) => ({
|
|
289
|
-
name: w.name,
|
|
290
|
-
label: w.signalk?.displayName || w.name,
|
|
291
|
-
url: `/${w.name}/`,
|
|
292
|
-
icon: w.signalk?.appIcon ? `/${w.name}/${w.signalk.appIcon}` : null
|
|
293
|
-
}))
|
|
294
|
-
|
|
295
|
-
if (webapps.length === 0) {
|
|
296
|
-
status.textContent = 'No webapps found. Is the server fully started?'
|
|
297
|
-
status.className = 'status error'
|
|
298
|
-
btn.disabled = false
|
|
299
|
-
btn.textContent = 'Discover Installed Webapps'
|
|
300
|
-
return
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
const existingUrls = new Set(apps.map((a) => a.url))
|
|
304
|
-
let added = 0
|
|
305
|
-
for (const w of webapps) {
|
|
306
|
-
if (!existingUrls.has(w.url)) {
|
|
307
|
-
apps.push({
|
|
308
|
-
enabled: true,
|
|
309
|
-
url: w.url,
|
|
310
|
-
label: w.label || '',
|
|
311
|
-
icon: w.icon || '',
|
|
312
|
-
color: ''
|
|
313
|
-
})
|
|
314
|
-
added++
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
status.textContent =
|
|
319
|
-
added > 0
|
|
320
|
-
? `Found ${webapps.length} webapps, added ${added} new.`
|
|
321
|
-
: `All ${webapps.length} webapps already in list.`
|
|
322
|
-
render()
|
|
323
|
-
} catch (e) {
|
|
324
|
-
status.textContent = 'Discovery failed: ' + e.message
|
|
325
|
-
status.className = 'status error'
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
btn.disabled = false
|
|
329
|
-
btn.textContent = 'Discover Installed Webapps'
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
async function save() {
|
|
333
|
-
const status = document.getElementById('status')
|
|
334
|
-
try {
|
|
335
|
-
const cfgRes = await fetch('/plugins/signalk-app-dock/config')
|
|
336
|
-
const current = await cfgRes.json()
|
|
337
|
-
const newConfig = {
|
|
338
|
-
...current,
|
|
339
|
-
configuration: {
|
|
340
|
-
...(current.configuration || {}),
|
|
341
|
-
apps: apps
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
const res = await fetch('/plugins/signalk-app-dock/config', {
|
|
346
|
-
method: 'POST',
|
|
347
|
-
headers: { 'Content-Type': 'application/json' },
|
|
348
|
-
body: JSON.stringify(newConfig)
|
|
349
|
-
})
|
|
350
|
-
|
|
351
|
-
if (res.ok) {
|
|
352
|
-
status.textContent = 'Saved! Plugin will restart with new config.'
|
|
353
|
-
status.className = 'status'
|
|
354
|
-
} else {
|
|
355
|
-
status.textContent = 'Save failed: ' + res.statusText
|
|
356
|
-
status.className = 'status error'
|
|
357
|
-
}
|
|
358
|
-
} catch (e) {
|
|
359
|
-
status.textContent = 'Save failed: ' + e.message
|
|
360
|
-
status.className = 'status error'
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
function removeApp(index) {
|
|
365
|
-
apps.splice(index, 1)
|
|
366
|
-
render()
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
function toggleApp(index) {
|
|
370
|
-
apps[index].enabled = !apps[index].enabled
|
|
371
|
-
render()
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
function renderIcon(app) {
|
|
375
|
-
const iconVal = app.icon || ''
|
|
376
|
-
if (iconVal.startsWith('/') || iconVal.startsWith('http')) {
|
|
377
|
-
return `<img src="${iconVal}" alt="${app.label}" onerror="this.parentNode.textContent='${(app.label || app.url || '??').slice(0, 2).toUpperCase()}'">`
|
|
378
|
-
}
|
|
379
|
-
return iconVal || (app.label || app.url || '??').slice(0, 2).toUpperCase()
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
function render() {
|
|
383
|
-
const list = document.getElementById('app-list')
|
|
384
|
-
const empty = document.getElementById('empty-state')
|
|
385
|
-
const preview = document.getElementById('dock-preview')
|
|
386
|
-
|
|
387
|
-
if (apps.length === 0) {
|
|
388
|
-
list.innerHTML = ''
|
|
389
|
-
empty.style.display = ''
|
|
390
|
-
preview.innerHTML = '<span style="color:#555;font-size:13px">No apps</span>'
|
|
391
|
-
return
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
empty.style.display = 'none'
|
|
395
|
-
|
|
396
|
-
list.innerHTML = apps
|
|
397
|
-
.map(
|
|
398
|
-
(app, i) => `
|
|
399
|
-
<li class="app-item" draggable="true" data-index="${i}" style="opacity:${app.enabled === false ? 0.4 : 1}">
|
|
400
|
-
<span class="drag-handle">\u2261</span>
|
|
401
|
-
<div class="app-icon-preview" ${app.color ? `style="background:${app.color}"` : ''}>
|
|
402
|
-
${renderIcon(app)}
|
|
403
|
-
</div>
|
|
404
|
-
<div class="app-info">
|
|
405
|
-
<div class="app-name">${app.label || app.url}</div>
|
|
406
|
-
<div class="app-url">${app.url}</div>
|
|
407
|
-
</div>
|
|
408
|
-
<div class="app-toggle">
|
|
409
|
-
<input type="checkbox" ${app.enabled !== false ? 'checked' : ''} onchange="toggleApp(${i})" title="Enable/disable">
|
|
410
|
-
</div>
|
|
411
|
-
<div class="app-actions">
|
|
412
|
-
<button class="btn btn-danger" onclick="removeApp(${i})">Remove</button>
|
|
413
|
-
</div>
|
|
414
|
-
</li>
|
|
415
|
-
`
|
|
416
|
-
)
|
|
417
|
-
.join('')
|
|
418
|
-
|
|
419
|
-
initDragDrop()
|
|
420
|
-
|
|
421
|
-
preview.innerHTML = apps
|
|
422
|
-
.filter((a) => a.enabled !== false)
|
|
423
|
-
.map(
|
|
424
|
-
(app) => `
|
|
425
|
-
<div class="dock-preview-icon" ${app.color ? `style="background:${app.color}"` : ''}>
|
|
426
|
-
${renderIcon(app)}
|
|
427
|
-
</div>
|
|
428
|
-
`
|
|
429
|
-
)
|
|
430
|
-
.join('')
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
let dragIndex = null
|
|
434
|
-
function initDragDrop() {
|
|
435
|
-
const items = document.querySelectorAll('.app-item')
|
|
436
|
-
items.forEach((item) => {
|
|
437
|
-
item.addEventListener('dragstart', (e) => {
|
|
438
|
-
dragIndex = parseInt(item.dataset.index)
|
|
439
|
-
item.classList.add('dragging')
|
|
440
|
-
e.dataTransfer.effectAllowed = 'move'
|
|
441
|
-
})
|
|
442
|
-
item.addEventListener('dragend', () => {
|
|
443
|
-
item.classList.remove('dragging')
|
|
444
|
-
document.querySelectorAll('.app-item').forEach((el) => el.classList.remove('drag-over'))
|
|
445
|
-
})
|
|
446
|
-
item.addEventListener('dragover', (e) => {
|
|
447
|
-
e.preventDefault()
|
|
448
|
-
e.dataTransfer.dropEffect = 'move'
|
|
449
|
-
item.classList.add('drag-over')
|
|
450
|
-
})
|
|
451
|
-
item.addEventListener('dragleave', () => {
|
|
452
|
-
item.classList.remove('drag-over')
|
|
453
|
-
})
|
|
454
|
-
item.addEventListener('drop', (e) => {
|
|
455
|
-
e.preventDefault()
|
|
456
|
-
const dropIndex = parseInt(item.dataset.index)
|
|
457
|
-
if (dragIndex !== null && dragIndex !== dropIndex) {
|
|
458
|
-
const moved = apps.splice(dragIndex, 1)[0]
|
|
459
|
-
apps.splice(dropIndex, 0, moved)
|
|
460
|
-
render()
|
|
461
|
-
}
|
|
462
|
-
})
|
|
463
|
-
})
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
loadCurrent()
|
|
467
|
-
</script>
|
|
468
35
|
</body>
|
|
469
36
|
</html>
|
package/public/config.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
function e(e){let t=document.getElementById(e);if(!t)throw Error(`config.html: #${e} is missing`);return t}function t(e){return e instanceof Error?e.message:String(e)}var n=e(`discover-btn`),r=e(`save-btn`),i=e(`status`),a=e(`app-list`),o=e(`empty-state`),s=e(`dock-preview`),c=[];async function l(){try{let e=await fetch(`/plugins/signalk-app-dock/config`);if(e.ok){let t=await e.json();c=t.configuration&&t.configuration.apps||[],h()}}catch(e){console.warn(`Could not load current config`,e)}}async function u(){n.disabled=!0,n.textContent=`Discovering...`,i.textContent=``,i.className=`status`;try{let e=(await(await fetch(`/skServer/webapps`)).json()).filter(e=>e.name!==`@signalk/app-dock`&&e.name!==`@signalk/server-admin-ui`).map(e=>({name:e.name,label:e.signalk?.displayName||e.name,url:`/${e.name}/`,icon:e.signalk?.appIcon?`/${e.name}/${e.signalk.appIcon}`:null}));if(e.length===0){i.textContent=`No webapps found. Is the server fully started?`,i.className=`status error`,n.disabled=!1,n.textContent=`Discover Installed Webapps`;return}let t=new Set(c.map(e=>e.url)),r=0;for(let n of e)t.has(n.url)||(c.push({enabled:!0,url:n.url,label:n.label||``,icon:n.icon||``,color:``}),r++);i.textContent=r>0?`Found ${e.length} webapps, added ${r} new.`:`All ${e.length} webapps already in list.`,h()}catch(e){i.textContent=`Discovery failed: `+t(e),i.className=`status error`}n.disabled=!1,n.textContent=`Discover Installed Webapps`}async function d(){try{let e=await(await fetch(`/plugins/signalk-app-dock/config`)).json(),t={...e,configuration:{...e.configuration||{},apps:c}},n=await fetch(`/plugins/signalk-app-dock/config`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify(t)});n.ok?(i.textContent=`Saved! Plugin will restart with new config.`,i.className=`status`):(i.textContent=`Save failed: `+n.statusText,i.className=`status error`)}catch(e){i.textContent=`Save failed: `+t(e),i.className=`status error`}}function f(e){c.splice(e,1),h()}function p(e){c[e].enabled=!c[e].enabled,h()}function m(e){let t=e.icon||``;return t.startsWith(`/`)||t.startsWith(`http`)?`<img src="${t}" alt="${e.label}" onerror="this.parentNode.textContent='${(e.label||e.url||`??`).slice(0,2).toUpperCase()}'">`:t||(e.label||e.url||`??`).slice(0,2).toUpperCase()}function h(){c.length===0?(a.innerHTML=``,o.style.display=``,s.innerHTML=`<span style="color:#555;font-size:13px">No apps</span>`):(o.style.display=`none`,a.innerHTML=c.map((e,t)=>`
|
|
2
|
+
<li class="app-item" draggable="true" data-index="${t}" style="opacity:${e.enabled===!1?.4:1}">
|
|
3
|
+
<span class="drag-handle">≡</span>
|
|
4
|
+
<div class="app-icon-preview" ${e.color?`style="background:${e.color}"`:``}>
|
|
5
|
+
${m(e)}
|
|
6
|
+
</div>
|
|
7
|
+
<div class="app-info">
|
|
8
|
+
<div class="app-name">${e.label||e.url}</div>
|
|
9
|
+
<div class="app-url">${e.url}</div>
|
|
10
|
+
</div>
|
|
11
|
+
<div class="app-toggle">
|
|
12
|
+
<input type="checkbox" ${e.enabled===!1?``:`checked`} data-action="toggle" title="Enable/disable">
|
|
13
|
+
</div>
|
|
14
|
+
<div class="app-actions">
|
|
15
|
+
<button class="btn btn-danger" data-action="remove">Remove</button>
|
|
16
|
+
</div>
|
|
17
|
+
</li>
|
|
18
|
+
`).join(``),_(),s.innerHTML=c.filter(e=>e.enabled!==!1).map(e=>`
|
|
19
|
+
<div class="dock-preview-icon" ${e.color?`style="background:${e.color}"`:``}>
|
|
20
|
+
${m(e)}
|
|
21
|
+
</div>
|
|
22
|
+
`).join(``))}var g=null;function _(){document.querySelectorAll(`.app-item`).forEach(e=>{e.addEventListener(`dragstart`,t=>{g=parseInt(e.dataset.index||``,10),e.classList.add(`dragging`),t.dataTransfer&&(t.dataTransfer.effectAllowed=`move`)}),e.addEventListener(`dragend`,()=>{e.classList.remove(`dragging`),document.querySelectorAll(`.app-item`).forEach(e=>e.classList.remove(`drag-over`))}),e.addEventListener(`dragover`,t=>{t.preventDefault(),t.dataTransfer&&(t.dataTransfer.dropEffect=`move`),e.classList.add(`drag-over`)}),e.addEventListener(`dragleave`,()=>{e.classList.remove(`drag-over`)}),e.addEventListener(`drop`,t=>{t.preventDefault();let n=parseInt(e.dataset.index||``,10);if(g!==null&&g!==n){let e=c.splice(g,1)[0];c.splice(n,0,e),h()}})})}function v(e){let t=e.target instanceof HTMLElement?e.target.closest(`[data-action]`):null,n=t?.closest(`.app-item`);return!t||!n?null:{action:t.dataset.action||``,index:parseInt(n.dataset.index||``,10)}}a.addEventListener(`change`,e=>{let t=v(e);t?.action===`toggle`&&p(t.index)}),a.addEventListener(`click`,e=>{let t=v(e);t?.action===`remove`&&f(t.index)}),n.addEventListener(`click`,()=>void u()),r.addEventListener(`click`,()=>void d()),l();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var e=window.__SK_REACT__;if(!e)throw Error(`App Dock: window.__SK_REACT__ is not set. The host signalk-server admin UI must expose React on window for plugin federation. Requires signalk-server 2.27.0 or newer.`);var{Activity:t,Children:n,Component:r,Fragment:i,Profiler:a,PureComponent:o,StrictMode:s,Suspense:c,cache:l,cacheSignal:u,cloneElement:d,createContext:f,createElement:p,createRef:m,forwardRef:h,isValidElement:g,lazy:_,memo:v,startTransition:y,use:b,useActionState:x,useCallback:S,useContext:C,useDebugValue:w,useDeferredValue:T,useEffect:E,useEffectEvent:D,useId:O,useImperativeHandle:k,useInsertionEffect:A,useLayoutEffect:j,useMemo:M,useOptimistic:N,useReducer:ee,useRef:P,useState:F,useSyncExternalStore:I,useTransition:L,version:R}=e,z=window.__SK_REACT_JSX_RUNTIME__;if(!z)throw Error(`App Dock: window.__SK_REACT_JSX_RUNTIME__ is not set by the host.`);var B=z,{Fragment:V,jsx:H,jsxs:U}=B;B.jsxDEV;function W(e){return e instanceof Error?e.message:String(e)}var G={root:{fontFamily:`-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif`,color:`#333`,padding:`16px 0`},sectionTitle:{fontSize:13,fontWeight:600,color:`#888`,textTransform:`uppercase`,letterSpacing:`0.05em`,marginBottom:10,marginTop:24},btn:{display:`inline-flex`,alignItems:`center`,gap:8,padding:`8px 16px`,border:`none`,borderRadius:6,fontSize:13,fontWeight:600,cursor:`pointer`},btnPrimary:{background:`#3b82f6`,color:`#fff`},btnSave:{background:`#10b981`,color:`#fff`},btnDanger:{background:`#ef4444`,color:`#fff`,padding:`4px 10px`,fontSize:11},status:{marginTop:8,fontSize:12,minHeight:18},item:{display:`flex`,alignItems:`center`,gap:10,padding:`8px 12px`,background:`#f8f9fa`,border:`1px solid #e0e0e0`,borderRadius:8,marginBottom:6,cursor:`grab`},itemDisabled:{opacity:.4},dragOver:{borderColor:`#3b82f6`,background:`#eef4ff`},handle:{color:`#bbb`,fontSize:16,cursor:`grab`,userSelect:`none`,flexShrink:0,width:18,textAlign:`center`},iconBox:{width:36,height:36,borderRadius:8,display:`flex`,alignItems:`center`,justifyContent:`center`,background:`#e2e8f0`,overflow:`hidden`,flexShrink:0,fontSize:18},iconImg:{width:`100%`,height:`100%`,objectFit:`contain`,padding:5},info:{flex:1,minWidth:0},name:{fontSize:13,fontWeight:600,color:`#333`},url:{fontSize:10,color:`#999`,overflow:`hidden`,textOverflow:`ellipsis`,whiteSpace:`nowrap`},toggle:{flexShrink:0},checkbox:{width:16,height:16,cursor:`pointer`,accentColor:`#3b82f6`},preview:{display:`flex`,alignItems:`flex-end`,justifyContent:`center`,gap:6,padding:`12px 18px`,background:`rgba(30,30,32,0.85)`,border:`1px solid rgba(255,255,255,0.18)`,borderRadius:18,margin:`16px auto`,width:`fit-content`},actions:{display:`flex`,gap:10,alignItems:`center`,marginTop:16},empty:{textAlign:`center`,padding:`30px 16px`,color:`#999`,fontSize:13},fieldRow:{display:`flex`,alignItems:`center`,gap:12,marginBottom:10},label:{fontSize:13,fontWeight:500,color:`#555`,width:160,flexShrink:0},select:{padding:`6px 10px`,borderRadius:6,border:`1px solid #ccc`,fontSize:13,background:`#fff`,color:`#333`},input:{padding:`6px 10px`,borderRadius:6,border:`1px solid #ccc`,fontSize:13,width:80,background:`#fff`,color:`#333`},checkboxField:{width:16,height:16,accentColor:`#3b82f6`},hint:{fontSize:11,color:`#aaa`,marginLeft:8}};function K({icon:e,label:t,color:n,size:r}){let i=r||36,a={...G.iconBox,width:i,height:i,borderRadius:i*.22,...n?{background:n}:{}},o=e||``;return o.startsWith(`/`)||o.startsWith(`http`)?H(`div`,{style:a,children:H(`img`,{src:o,alt:t,style:{...G.iconImg,padding:i*.12},onError:e=>{let n=e.currentTarget;n.style.display=`none`,n.parentElement&&(n.parentElement.textContent=(t||`??`).slice(0,2).toUpperCase())}})}):H(`div`,{style:a,children:o||(t||`??`).slice(0,2).toUpperCase()})}function q({label:e,value:t,options:n,onChange:r,hint:i}){return U(`div`,{style:G.fieldRow,children:[H(`span`,{style:G.label,children:e}),H(`select`,{style:G.select,value:t,onChange:e=>r(e.target.value),children:n.map(e=>H(`option`,{value:e.value,children:e.label},e.value))}),i&&H(`span`,{style:G.hint,children:i})]})}function J({label:e,value:t,onChange:n,hint:r}){return U(`div`,{style:G.fieldRow,children:[H(`span`,{style:G.label,children:e}),H(`input`,{style:G.input,type:`number`,value:t,onChange:e=>n(Number(e.target.value))}),r&&H(`span`,{style:G.hint,children:r})]})}function Y({label:e,checked:t,onChange:n,hint:r}){return U(`div`,{style:G.fieldRow,children:[H(`span`,{style:G.label,children:e}),H(`input`,{type:`checkbox`,style:G.checkboxField,checked:t,onChange:e=>n(e.target.checked)}),r&&H(`span`,{style:G.hint,children:r})]})}function X({configuration:e,save:t}){let n=e||{},[r,i]=F(()=>n.apps||[]),[a,o]=F(n.position||`bottom`),[s,c]=F(n.iframeMode||`keep-alive`),[l,u]=F(n.iconSize||56),[d,f]=F(n.magnification!==!1),[p,m]=F(n.magnificationScale||1.7),[h,g]=F(n.showNightModeButton||!1),[_,v]=F(n.showFullscreenButton||!1),[y,b]=F(n.showExitButton||!1),x=n.tourDismissed||!1,[C,w]=F(``),[T,D]=F(!1),[O,k]=F(!1),[A,j]=F(!1),[M,N]=F(null),[ee,I]=F(null),L=P(r);E(()=>{L.current=r},[r]);let R=P(null);E(()=>()=>{R.current&&clearTimeout(R.current)},[]);let z=S(e=>({position:a,iframeMode:s,iconSize:l,magnification:d,magnificationScale:p,showNightModeButton:h,showFullscreenButton:_,showExitButton:y,tourDismissed:x,apps:e}),[a,s,l,d,p,h,_,y,x]),B=S(async()=>{R.current&&clearTimeout(R.current),j(!0),w(`Saving…`),D(!1);try{await t(z(r)),w(`Configuration saved — plugin will restart`),D(!1),R.current=setTimeout(()=>w(``),5e3)}catch(e){w(`Save failed: `+W(e)),D(!0)}finally{j(!1)}},[r,z,t]),X=async()=>{k(!0),w(``),D(!1);try{let e=(await(await fetch(`/skServer/webapps`)).json()).filter(e=>e.name!==`@signalk/app-dock`).map(e=>e.name===`@signalk/server-admin-ui`?{name:e.name,label:`Settings`,url:`/admin/`,icon:`/@signalk/app-dock/icon-settings.svg`,isAdmin:!0}:{name:e.name,label:e.signalk?.displayName||e.name,url:`/${e.name}/`,icon:e.signalk?.appIcon?`/${e.name}/${e.signalk.appIcon}`:null});if(e.length===0){w(`No webapps found. Is the server fully started?`),D(!0),k(!1);return}let t=L.current,n=new Set(t.map(e=>e.url)),r=0,a=[...t];for(let t of e)n.has(t.url)||(a.push({enabled:!0,autostart:!1,url:t.url,label:t.label||``,icon:t.icon||``,color:t.isAdmin?`#78788c`:``}),r++);i(a),w(r>0?`Found ${e.length} webapps, added ${r} new.`:`All ${e.length} webapps already in list.`),D(!1)}catch(e){w(`Discovery failed: `+W(e)),D(!0)}k(!1)},Z=e=>i(r.map((t,n)=>n===e?{...t,enabled:!t.enabled}:t)),Q=e=>i(r.map((t,n)=>n===e?{...t,autostart:!t.autostart}:{...t,autostart:!1})),te=e=>i(r.filter((t,n)=>n!==e)),ne=e=>N(e),re=(e,t)=>{e.preventDefault(),I(t)},ie=()=>I(null),ae=(e,t)=>{if(e.preventDefault(),I(null),M!==null&&M!==t){let e=[...r],[n]=e.splice(M,1);e.splice(t,0,n),i(e)}N(null)},oe=()=>{N(null),I(null)},$=r.filter(e=>e.enabled!==!1);return U(`div`,{style:G.root,children:[H(`div`,{style:G.sectionTitle,children:`Dock Settings`}),H(q,{label:`Dock position`,value:a,onChange:o,options:[{value:`bottom`,label:`Bottom`},{value:`top`,label:`Top`},{value:`left`,label:`Left`},{value:`right`,label:`Right`}]}),H(q,{label:`iFrame lifecycle`,value:s,onChange:c,options:[{value:`keep-alive`,label:`Keep-alive (faster switching, more RAM)`},{value:`destroy`,label:`Destroy (reload each time, less RAM)`}]}),H(J,{label:`Icon size`,value:l,onChange:u,hint:`px`}),H(Y,{label:`Magnification effect`,checked:d,onChange:f}),d&&H(J,{label:`Magnification scale`,value:p,onChange:m,hint:`1.0 - 2.5`}),H(`div`,{style:G.sectionTitle,children:`Utility Buttons`}),H(Y,{label:`Night/Day mode toggle`,checked:h,onChange:g,hint:`Sun/moon button to toggle environment.mode`}),H(Y,{label:`Fullscreen toggle`,checked:_,onChange:v,hint:`Toggles browser fullscreen. Hidden on devices without Fullscreen API support (e.g. iPadOS Safari).`}),H(Y,{label:`Exit button`,checked:y,onChange:b,hint:`X button to return to Signal K admin UI`}),H(`div`,{style:G.sectionTitle,children:`Webapps`}),H(`button`,{style:{...G.btn,...G.btnPrimary,...O?{opacity:.6}:{}},onClick:()=>void X(),disabled:O,children:O?`Discovering…`:`Discover Installed Webapps`}),C&&H(`div`,{style:{...G.status,color:T?`#ef4444`:`#10b981`},children:C}),H(`div`,{style:G.sectionTitle,children:`Dock Apps (drag to reorder)`}),r.length===0?H(`div`,{style:G.empty,children:`No apps yet. Click Discover above.`}):H(`div`,{children:r.map((e,t)=>U(`div`,{draggable:!0,onDragStart:()=>ne(t),onDragOver:e=>re(e,t),onDragLeave:ie,onDrop:e=>ae(e,t),onDragEnd:oe,style:{...G.item,...e.enabled===!1?G.itemDisabled:{},...ee===t?G.dragOver:{},...M===t?{opacity:.4}:{}},children:[H(`span`,{style:G.handle,children:`≡`}),H(K,{icon:e.icon,label:e.label||e.url,color:e.color}),U(`div`,{style:G.info,children:[H(`div`,{style:G.name,children:e.label||e.url}),H(`div`,{style:G.url,children:e.url})]}),H(`div`,{style:G.toggle,title:`Enabled`,children:H(`input`,{type:`checkbox`,checked:e.enabled!==!1,onChange:()=>Z(t),style:G.checkbox})}),H(`div`,{style:{...G.toggle,cursor:`pointer`,fontSize:16,opacity:e.autostart?1:.25},onClick:()=>Q(t),title:e.autostart?`Autostart: ON (click to disable)`:`Click to set as autostart app`,children:`▶`}),H(`button`,{style:{...G.btn,...G.btnDanger},onClick:()=>te(t),children:`Remove`})]},e.url+t))}),($.length>0||h||_||y)&&U(V,{children:[H(`div`,{style:G.sectionTitle,children:`Preview`}),U(`div`,{style:G.preview,children:[h&&H(`div`,{style:{...G.iconBox,width:40,height:40,background:`rgba(255,255,255,0.08)`,border:`1px solid rgba(255,255,255,0.12)`,fontSize:20,borderRadius:10},children:`☀️`}),_&&H(`div`,{style:{...G.iconBox,width:40,height:40,background:`rgba(255,255,255,0.08)`,border:`1px solid rgba(255,255,255,0.12)`,fontSize:20,borderRadius:10,color:`#fff`},children:`⛶`}),(h||_)&&H(`div`,{style:{width:1,height:24,background:`rgba(255,255,255,0.15)`,alignSelf:`center`}}),$.map((e,t)=>H(K,{icon:e.icon,label:e.label||e.url,color:e.color,size:40},t)),y&&U(V,{children:[H(`div`,{style:{width:1,height:24,background:`rgba(255,255,255,0.15)`,alignSelf:`center`}}),H(`div`,{style:{...G.iconBox,width:40,height:40,background:`rgba(255,255,255,0.08)`,border:`1px solid rgba(255,255,255,0.12)`,fontSize:16,borderRadius:10,color:`#fff`},children:`✕`})]})]})]}),U(`div`,{style:G.actions,children:[H(`button`,{style:{...G.btn,...G.btnSave,...A?{opacity:.6}:{}},onClick:()=>void B(),disabled:A,children:A?`Saving…`:`Save Configuration`}),C&&H(`div`,{style:{...G.status,color:T?`#ef4444`:`#10b981`},children:C})]})]})}export{X as default};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./dist-D1jGdgWH.js";var e={},t=[];export{t as usedRemotes,e as usedShared};
|