@signalk/charts-plugin 3.7.0 → 3.8.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/.prettierignore +1 -0
- package/CHANGELOG.md +109 -0
- package/README.md +72 -0
- package/docs/screenshots/1_overview.png +0 -0
- package/docs/screenshots/2_region.png +0 -0
- package/docs/screenshots/3_jobs.png +0 -0
- package/package.json +16 -11
- package/plugin/cacheLayout.d.ts +72 -0
- package/plugin/cacheLayout.d.ts.map +1 -0
- package/plugin/cacheLayout.js +243 -0
- package/plugin/cacheLayout.js.map +1 -0
- package/plugin/chartDownloader.d.ts +122 -29
- package/plugin/chartDownloader.d.ts.map +1 -1
- package/plugin/chartDownloader.js +491 -314
- package/plugin/chartDownloader.js.map +1 -1
- package/plugin/chartDownloaderMBTilesHelpers.d.ts +10 -0
- package/plugin/chartDownloaderMBTilesHelpers.d.ts.map +1 -0
- package/plugin/chartDownloaderMBTilesHelpers.js +202 -0
- package/plugin/chartDownloaderMBTilesHelpers.js.map +1 -0
- package/plugin/chartDownloaderTileHelpers.d.ts +30 -0
- package/plugin/chartDownloaderTileHelpers.d.ts.map +1 -0
- package/plugin/chartDownloaderTileHelpers.js +277 -0
- package/plugin/chartDownloaderTileHelpers.js.map +1 -0
- package/plugin/charts.d.ts.map +1 -1
- package/plugin/charts.js +24 -39
- package/plugin/charts.js.map +1 -1
- package/plugin/constants.d.ts.map +1 -1
- package/plugin/index.d.ts.map +1 -1
- package/plugin/index.js +564 -47
- package/plugin/index.js.map +1 -1
- package/plugin/pluginStatus.js.map +1 -1
- package/plugin/projection.js.map +1 -1
- package/plugin/tileServer.d.ts +4 -2
- package/plugin/tileServer.d.ts.map +1 -1
- package/plugin/tileServer.js +37 -8
- package/plugin/tileServer.js.map +1 -1
- package/plugin/tokenProvider.d.ts +63 -0
- package/plugin/tokenProvider.d.ts.map +1 -0
- package/plugin/tokenProvider.js +258 -0
- package/plugin/tokenProvider.js.map +1 -0
- package/plugin/types.d.ts +37 -2
- package/plugin/types.d.ts.map +1 -1
- package/public/images/jobs.png +0 -0
- package/public/images/overview.png +0 -0
- package/public/images/region.png +0 -0
- package/public/index.html +392 -113
- package/public/js/index.js +620 -0
- package/public/leaflet/images/downloads.png +0 -0
- package/public/leaflet/images/layers-2x.png +0 -0
- package/public/leaflet/images/layers.png +0 -0
- package/public/leaflet/images/lifebuoy.png +0 -0
- package/public/leaflet/images/spritesheet-2x.png +0 -0
- package/public/leaflet/images/spritesheet.svg +156 -0
- package/public/leaflet/leaflet.css +677 -0
- package/public/leaflet/leaflet.draw.css +253 -0
- package/public/leaflet/leaflet.draw.js +2957 -0
- package/public/leaflet/leaflet.js +7467 -0
- package/public/style.css +351 -37
- package/docs/screenshots/1_placeholder.png +0 -0
- package/public/index.js +0 -349
package/public/index.js
DELETED
|
@@ -1,349 +0,0 @@
|
|
|
1
|
-
// POSTs a seed-job request and returns the parsed job info. Extracted so the
|
|
2
|
-
// fetch + response-validation logic can be unit-tested in node without jsdom.
|
|
3
|
-
// The browser still calls this from the click handler below.
|
|
4
|
-
async function submitSeedJob(chart, body, fetchImpl) {
|
|
5
|
-
const fetchFn = fetchImpl || globalThis.fetch
|
|
6
|
-
const resp = await fetchFn(
|
|
7
|
-
`/signalk/chart-tiles/cache/${encodeURIComponent(chart)}`,
|
|
8
|
-
{
|
|
9
|
-
method: 'POST',
|
|
10
|
-
headers: { 'Content-Type': 'application/json' },
|
|
11
|
-
body: JSON.stringify(body)
|
|
12
|
-
}
|
|
13
|
-
)
|
|
14
|
-
if (!resp.ok) {
|
|
15
|
-
let text = ''
|
|
16
|
-
try {
|
|
17
|
-
text = await resp.text()
|
|
18
|
-
} catch (_readErr) {
|
|
19
|
-
// Ignore read errors when composing the error message; the status code
|
|
20
|
-
// and our surrounding context are enough to diagnose.
|
|
21
|
-
}
|
|
22
|
-
throw new Error(
|
|
23
|
-
`Failed to seed charts: ${resp.status}${text ? ` - ${text}` : ''}`
|
|
24
|
-
)
|
|
25
|
-
}
|
|
26
|
-
const data = await resp.json()
|
|
27
|
-
if (!data || typeof data.id !== 'number') {
|
|
28
|
-
throw new Error('Job response is missing a numeric id')
|
|
29
|
-
}
|
|
30
|
-
return data
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Node-only export for the unit test. The browser script tag ignores this
|
|
34
|
-
// branch because `module` is undefined in a plain <script>.
|
|
35
|
-
if (typeof module !== 'undefined' && module.exports) {
|
|
36
|
-
module.exports = { submitSeedJob }
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// The browser attaches the DOM handlers; node (test context) skips them.
|
|
40
|
-
if (typeof document !== 'undefined') registerDomHandlers()
|
|
41
|
-
|
|
42
|
-
function registerDomHandlers() {
|
|
43
|
-
document.addEventListener('DOMContentLoaded', async () => {
|
|
44
|
-
const regionSelect = document.getElementById('region')
|
|
45
|
-
const bboxInputs = document.querySelectorAll(
|
|
46
|
-
'#bboxMinLon, #bboxMinLat, #bboxMaxLon, #bboxMaxLat'
|
|
47
|
-
)
|
|
48
|
-
const tileInputs = document.querySelectorAll('#tileX, #tileY, #tileZ')
|
|
49
|
-
|
|
50
|
-
function updateInputs() {
|
|
51
|
-
const type = document.querySelector(
|
|
52
|
-
'input[name="inputType"]:checked'
|
|
53
|
-
).value
|
|
54
|
-
|
|
55
|
-
regionSelect.disabled = type !== 'region'
|
|
56
|
-
bboxInputs.forEach((i) => (i.disabled = type !== 'bbox'))
|
|
57
|
-
tileInputs.forEach((i) => (i.disabled = type !== 'tile'))
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Attach listeners
|
|
61
|
-
document.querySelectorAll('input[name="inputType"]').forEach((r) => {
|
|
62
|
-
r.addEventListener('change', updateInputs)
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
updateInputs() // initial load
|
|
66
|
-
|
|
67
|
-
if (!regionSelect) return
|
|
68
|
-
|
|
69
|
-
try {
|
|
70
|
-
const resp = await fetch('/signalk/v2/api/resources/regions')
|
|
71
|
-
if (!resp.ok) throw new Error(`Failed to fetch regions: ${resp.status}`)
|
|
72
|
-
const data = await resp.json()
|
|
73
|
-
// clear existing options
|
|
74
|
-
regionSelect.innerHTML = ''
|
|
75
|
-
|
|
76
|
-
const entries = Object.entries(data || {})
|
|
77
|
-
entries
|
|
78
|
-
.sort(([, a], [, b]) => {
|
|
79
|
-
const na = a && a.name ? String(a.name) : ''
|
|
80
|
-
const nb = b && b.name ? String(b.name) : ''
|
|
81
|
-
return na.localeCompare(nb)
|
|
82
|
-
})
|
|
83
|
-
.forEach(([id, info]) => {
|
|
84
|
-
const opt = document.createElement('option')
|
|
85
|
-
opt.value = id
|
|
86
|
-
opt.textContent = info && info.name ? info.name : id
|
|
87
|
-
regionSelect.appendChild(opt)
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
// if no regions returned, add a fallback option
|
|
91
|
-
if (!regionSelect.options.length) {
|
|
92
|
-
const opt = document.createElement('option')
|
|
93
|
-
opt.value = ''
|
|
94
|
-
opt.textContent = '-- No regions available --'
|
|
95
|
-
regionSelect.appendChild(opt)
|
|
96
|
-
}
|
|
97
|
-
} catch (err) {
|
|
98
|
-
console.error('Error loading regions:', err)
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
//Fetch available maps and populate the chart select box
|
|
102
|
-
const chartSelect = document.getElementById('chart')
|
|
103
|
-
if (!chartSelect) return
|
|
104
|
-
|
|
105
|
-
try {
|
|
106
|
-
const resp = await fetch('/signalk/v2/api/resources/charts')
|
|
107
|
-
if (!resp.ok) throw new Error(`Failed to fetch charts: ${resp.status}`)
|
|
108
|
-
const data = await resp.json()
|
|
109
|
-
// clear existing options
|
|
110
|
-
chartSelect.innerHTML = ''
|
|
111
|
-
|
|
112
|
-
const entries = Object.entries(data || {})
|
|
113
|
-
entries
|
|
114
|
-
.sort(([, a], [, b]) => {
|
|
115
|
-
const na = a && a.name ? String(a.name) : ''
|
|
116
|
-
const nb = b && b.name ? String(b.name) : ''
|
|
117
|
-
return na.localeCompare(nb)
|
|
118
|
-
})
|
|
119
|
-
.forEach(([id, info]) => {
|
|
120
|
-
if (info.proxy === true) {
|
|
121
|
-
const opt = document.createElement('option')
|
|
122
|
-
opt.value = id
|
|
123
|
-
opt.textContent = info && info.name ? info.name : id
|
|
124
|
-
chartSelect.appendChild(opt)
|
|
125
|
-
}
|
|
126
|
-
})
|
|
127
|
-
|
|
128
|
-
// if no maps returned, add a fallback option
|
|
129
|
-
if (!chartSelect.options.length) {
|
|
130
|
-
const opt = document.createElement('option')
|
|
131
|
-
opt.value = ''
|
|
132
|
-
opt.textContent = '-- No maps available --'
|
|
133
|
-
chartSelect.appendChild(opt)
|
|
134
|
-
}
|
|
135
|
-
} catch (err) {
|
|
136
|
-
console.error('Error loading maps:', err)
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
document
|
|
140
|
-
.getElementById('createJobButton')
|
|
141
|
-
.addEventListener('click', async () => {
|
|
142
|
-
try {
|
|
143
|
-
const chart = document.getElementById('chart').value
|
|
144
|
-
const regionGUID = document.getElementById('region').value
|
|
145
|
-
const maxZoom = document.getElementById('maxZoom').value
|
|
146
|
-
const bbox = {
|
|
147
|
-
minLon: parseFloat(document.getElementById('bboxMinLon').value),
|
|
148
|
-
minLat: parseFloat(document.getElementById('bboxMinLat').value),
|
|
149
|
-
maxLon: parseFloat(document.getElementById('bboxMaxLon').value),
|
|
150
|
-
maxLat: parseFloat(document.getElementById('bboxMaxLat').value)
|
|
151
|
-
}
|
|
152
|
-
// const tile = {
|
|
153
|
-
// z: parseInt(document.getElementById('tileZ').value),
|
|
154
|
-
// x: parseInt(document.getElementById('tileX').value),
|
|
155
|
-
// y: parseInt(document.getElementById('tileY').value),
|
|
156
|
-
|
|
157
|
-
// };
|
|
158
|
-
// console.log(tile);
|
|
159
|
-
const type = document.querySelector(
|
|
160
|
-
'input[name="inputType"]:checked'
|
|
161
|
-
).value
|
|
162
|
-
const body = {}
|
|
163
|
-
if (type === 'region') {
|
|
164
|
-
body.regionGUID = regionGUID
|
|
165
|
-
} else if (type === 'bbox') {
|
|
166
|
-
body.bbox = bbox
|
|
167
|
-
}
|
|
168
|
-
// else if (type === 'tile') {
|
|
169
|
-
// body.tile = tile;
|
|
170
|
-
// }
|
|
171
|
-
body.maxZoom = maxZoom
|
|
172
|
-
await submitSeedJob(chart, body)
|
|
173
|
-
// Trigger an immediate refresh so the new job appears without waiting
|
|
174
|
-
// for the 2s polling interval.
|
|
175
|
-
fetchActiveJobs()
|
|
176
|
-
} catch (err) {
|
|
177
|
-
console.error('Error seeding charts:', err)
|
|
178
|
-
alert(`Failed to start seeding job: ${err.message}`)
|
|
179
|
-
}
|
|
180
|
-
})
|
|
181
|
-
|
|
182
|
-
const fmt = (n) => (typeof n === 'number' ? n.toLocaleString() : '-')
|
|
183
|
-
const statusText = (s) => {
|
|
184
|
-
switch (s) {
|
|
185
|
-
case 0:
|
|
186
|
-
return 'Stopped'
|
|
187
|
-
case 1:
|
|
188
|
-
return 'Running'
|
|
189
|
-
default:
|
|
190
|
-
return String(s)
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
function renderJobs(items) {
|
|
195
|
-
const tbody = document.querySelector('#activeJobsTable tbody')
|
|
196
|
-
if (!tbody) return
|
|
197
|
-
tbody.innerHTML = ''
|
|
198
|
-
if (!Array.isArray(items) || items.length === 0) {
|
|
199
|
-
const tr = document.createElement('tr')
|
|
200
|
-
tr.innerHTML = '<td colspan="10" class="muted">No active jobs</td>'
|
|
201
|
-
tbody.appendChild(tr)
|
|
202
|
-
return
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
items.forEach((it, idx) => {
|
|
206
|
-
const tr = document.createElement('tr')
|
|
207
|
-
|
|
208
|
-
const pct =
|
|
209
|
-
typeof it.progress === 'number' && isFinite(it.progress)
|
|
210
|
-
? Math.max(0, Math.min(100, it.progress * 100))
|
|
211
|
-
: 0
|
|
212
|
-
const pctText = `${pct.toFixed(1)}%`
|
|
213
|
-
tr.innerHTML = [
|
|
214
|
-
`<td>${idx + 1}</td>`,
|
|
215
|
-
`<td>${it.chartName || '-'}</td>`,
|
|
216
|
-
`<td>${it.regionName || '-'}</td>`,
|
|
217
|
-
`<td>${fmt(it.totalTiles)}</td>`,
|
|
218
|
-
`<td>${fmt(it.downloadedTiles)}</td>`,
|
|
219
|
-
`<td>${fmt(it.cachedTiles)}</td>`,
|
|
220
|
-
`<td>${fmt(it.failedTiles)}</td>`,
|
|
221
|
-
`<td>
|
|
222
|
-
<span class="progress-bar" aria-hidden="true">
|
|
223
|
-
<span class="progress-fill" style="width:${pct}%"></span>
|
|
224
|
-
</span>
|
|
225
|
-
<span class="percent">${pctText}</span>
|
|
226
|
-
</td>`,
|
|
227
|
-
`<td>${statusText(it.status)}</td>`,
|
|
228
|
-
`
|
|
229
|
-
<td class="action-buttons">
|
|
230
|
-
<button class="btn startstop-btn" data-id="${it.id}" data-totalTiles="${it.totalTiles}" title="Start">
|
|
231
|
-
<svg class="icon-play ${it.status === 1 ? 'hidden' : ''}" viewBox="0 0 24 24" width="16" height="16" fill="white">
|
|
232
|
-
<path d="M8 5v14l11-7z"/>
|
|
233
|
-
</svg>
|
|
234
|
-
<svg class="icon-pause ${it.status === 1 ? '' : 'hidden'}" viewBox="0 0 24 24" width="16" height="16" fill="white">
|
|
235
|
-
<path d="M6 19h4V5H6zm8-14v14h4V5z"/>
|
|
236
|
-
</svg>
|
|
237
|
-
</button>
|
|
238
|
-
<button class="btn delete-btn" data-id="${it.id}" title="Delete">
|
|
239
|
-
<svg viewBox="0 0 24 24" width="16" height="16" fill="white">
|
|
240
|
-
<path d="M3 6h18v2H3zm3 3h12v12H6zM8 2h8v2H8z"/>
|
|
241
|
-
</svg>
|
|
242
|
-
</button>
|
|
243
|
-
<button class="btn remove-btn" data-id="${it.id}" title="Remove">
|
|
244
|
-
<svg viewBox="0 0 24 24" width="16" height="16" fill="white">
|
|
245
|
-
<path d="M18.3 5.71L12 12l6.3 6.29-1.41 1.42L10.59 13.4 4.29 19.71 2.88 18.3 9.17 12 2.88 5.71 4.29 4.3 10.59 10.6l6.3-6.3z"/>
|
|
246
|
-
</svg>
|
|
247
|
-
</button>
|
|
248
|
-
</td>
|
|
249
|
-
`
|
|
250
|
-
].join('')
|
|
251
|
-
|
|
252
|
-
tr.querySelectorAll('.startstop-btn').forEach((button) => {
|
|
253
|
-
button.addEventListener('click', () => {
|
|
254
|
-
const playIcon = button.querySelector('.icon-play')
|
|
255
|
-
const pauseIcon = button.querySelector('.icon-pause')
|
|
256
|
-
const jobId = button.getAttribute('data-id')
|
|
257
|
-
const totalTiles = button.getAttribute('data-totalTiles')
|
|
258
|
-
const isRunning = pauseIcon.classList.contains('hidden')
|
|
259
|
-
console.log(isRunning)
|
|
260
|
-
if (isRunning) {
|
|
261
|
-
// Switch to stop
|
|
262
|
-
if (
|
|
263
|
-
totalTiles > 100000 &&
|
|
264
|
-
!confirm(
|
|
265
|
-
`This job has more than ${totalTiles} tiles to download. Starting it may take a long time and put high load on the server. Are you sure you want to start it?`
|
|
266
|
-
)
|
|
267
|
-
) {
|
|
268
|
-
return
|
|
269
|
-
}
|
|
270
|
-
takeAction(jobId, 'start')
|
|
271
|
-
button.title = 'Stop'
|
|
272
|
-
} else {
|
|
273
|
-
// Switch to start
|
|
274
|
-
takeAction(jobId, 'stop')
|
|
275
|
-
button.title = 'Start'
|
|
276
|
-
}
|
|
277
|
-
})
|
|
278
|
-
})
|
|
279
|
-
|
|
280
|
-
tr.querySelectorAll('.delete-btn').forEach((button) => {
|
|
281
|
-
button.addEventListener('click', () => {
|
|
282
|
-
if (
|
|
283
|
-
confirm(
|
|
284
|
-
'This will delete all cached tiles for this job. Are you sure?'
|
|
285
|
-
)
|
|
286
|
-
) {
|
|
287
|
-
const jobId = button.getAttribute('data-id')
|
|
288
|
-
if (jobId) {
|
|
289
|
-
takeAction(jobId, 'delete')
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
})
|
|
293
|
-
})
|
|
294
|
-
|
|
295
|
-
tr.querySelectorAll('.remove-btn').forEach((button) => {
|
|
296
|
-
button.addEventListener('click', () => {
|
|
297
|
-
const jobId = button.getAttribute('data-id')
|
|
298
|
-
if (jobId) {
|
|
299
|
-
takeAction(jobId, 'remove')
|
|
300
|
-
}
|
|
301
|
-
})
|
|
302
|
-
})
|
|
303
|
-
|
|
304
|
-
tbody.appendChild(tr)
|
|
305
|
-
})
|
|
306
|
-
|
|
307
|
-
function takeAction(jobId, action) {
|
|
308
|
-
fetch(`/signalk/chart-tiles/cache/jobs/${jobId}`, {
|
|
309
|
-
method: 'POST',
|
|
310
|
-
headers: {
|
|
311
|
-
'Content-Type': 'application/json'
|
|
312
|
-
},
|
|
313
|
-
body: JSON.stringify({
|
|
314
|
-
action: action
|
|
315
|
-
})
|
|
316
|
-
})
|
|
317
|
-
.then((response) => {
|
|
318
|
-
if (!response.ok) {
|
|
319
|
-
throw new Error(
|
|
320
|
-
`Failed to ${action} job ${jobId}: ${response.status}`
|
|
321
|
-
)
|
|
322
|
-
}
|
|
323
|
-
fetchActiveJobs()
|
|
324
|
-
})
|
|
325
|
-
.catch((error) => {
|
|
326
|
-
console.error(`Error ${action} job:`, error)
|
|
327
|
-
})
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
async function fetchActiveJobs() {
|
|
332
|
-
const tbody = document.querySelector('#activeJobsTable tbody')
|
|
333
|
-
try {
|
|
334
|
-
const resp = await fetch('/signalk/chart-tiles/cache/jobs')
|
|
335
|
-
if (!resp.ok) throw new Error('HTTP ' + resp.status)
|
|
336
|
-
const data = await resp.json()
|
|
337
|
-
renderJobs(data)
|
|
338
|
-
} catch (err) {
|
|
339
|
-
console.error('Error fetching active jobs:', err)
|
|
340
|
-
tbody.innerHTML =
|
|
341
|
-
'<tr><td colspan="10" class="muted">Error loading active jobs</td></tr>'
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
fetchActiveJobs()
|
|
346
|
-
// refresh every 2 seconds
|
|
347
|
-
setInterval(fetchActiveJobs, 2000)
|
|
348
|
-
})
|
|
349
|
-
}
|