@signalk/charts-plugin 3.3.0 → 3.5.0-beta

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/index.js CHANGED
@@ -1,195 +1,199 @@
1
-
2
-
3
-
4
- (function () {
5
-
6
-
7
- })();
1
+ ;(function () {})()
8
2
 
9
3
  document.addEventListener('DOMContentLoaded', async () => {
10
- const regionSelect = document.getElementById("region");
11
- const bboxInputs = document.querySelectorAll("#bboxMinLon, #bboxMinLat, #bboxMaxLon, #bboxMaxLat");
12
- const tileInputs = document.querySelectorAll("#tileX, #tileY, #tileZ");
13
-
14
- function updateInputs() {
15
- const type = document.querySelector('input[name="inputType"]:checked').value;
16
-
17
- regionSelect.disabled = (type !== "region");
18
- bboxInputs.forEach(i => i.disabled = (type !== "bbox"));
19
- tileInputs.forEach(i => i.disabled = (type !== "tile"));
4
+ const regionSelect = document.getElementById('region')
5
+ const bboxInputs = document.querySelectorAll(
6
+ '#bboxMinLon, #bboxMinLat, #bboxMaxLon, #bboxMaxLat'
7
+ )
8
+ const tileInputs = document.querySelectorAll('#tileX, #tileY, #tileZ')
9
+
10
+ function updateInputs() {
11
+ const type = document.querySelector('input[name="inputType"]:checked').value
12
+
13
+ regionSelect.disabled = type !== 'region'
14
+ bboxInputs.forEach((i) => (i.disabled = type !== 'bbox'))
15
+ tileInputs.forEach((i) => (i.disabled = type !== 'tile'))
16
+ }
17
+
18
+ // Attach listeners
19
+ document.querySelectorAll('input[name="inputType"]').forEach((r) => {
20
+ r.addEventListener('change', updateInputs)
21
+ })
22
+
23
+ updateInputs() // initial load
24
+
25
+ if (!regionSelect) return
26
+
27
+ try {
28
+ const resp = await fetch('/signalk/v2/api/resources/regions')
29
+ if (!resp.ok) throw new Error(`Failed to fetch regions: ${resp.status}`)
30
+ const data = await resp.json()
31
+ // clear existing options
32
+ regionSelect.innerHTML = ''
33
+
34
+ const entries = Object.entries(data || {})
35
+ entries
36
+ .sort(([, a], [, b]) => {
37
+ const na = a && a.name ? String(a.name) : ''
38
+ const nb = b && b.name ? String(b.name) : ''
39
+ return na.localeCompare(nb)
40
+ })
41
+ .forEach(([id, info]) => {
42
+ const opt = document.createElement('option')
43
+ opt.value = id
44
+ opt.textContent = info && info.name ? info.name : id
45
+ regionSelect.appendChild(opt)
46
+ })
47
+
48
+ // if no regions returned, add a fallback option
49
+ if (!regionSelect.options.length) {
50
+ const opt = document.createElement('option')
51
+ opt.value = ''
52
+ opt.textContent = '-- No regions available --'
53
+ regionSelect.appendChild(opt)
20
54
  }
21
-
22
- // Attach listeners
23
- document.querySelectorAll('input[name="inputType"]').forEach(r => {
24
- r.addEventListener("change", updateInputs);
25
- });
26
-
27
- updateInputs(); // initial load
28
-
29
- if (!regionSelect) return;
30
-
31
- try {
32
- const resp = await fetch('/signalk/v2/api/resources/regions');
33
- if (!resp.ok) throw new Error(`Failed to fetch regions: ${resp.status}`);
34
- const data = await resp.json();
35
- // clear existing options
36
- regionSelect.innerHTML = '';
37
-
38
- const entries = Object.entries(data || {});
39
- entries
40
- .sort(([, a], [, b]) => {
41
- const na = (a && a.name) ? String(a.name) : '';
42
- const nb = (b && b.name) ? String(b.name) : '';
43
- return na.localeCompare(nb);
44
- })
45
- .forEach(([id, info]) => {
46
- const opt = document.createElement('option');
47
- opt.value = id;
48
- opt.textContent = (info && info.name) ? info.name : id;
49
- regionSelect.appendChild(opt);
50
-
51
- });
52
-
53
- // if no regions returned, add a fallback option
54
- if (!regionSelect.options.length) {
55
- const opt = document.createElement('option');
56
- opt.value = '';
57
- opt.textContent = '-- No regions available --';
58
- regionSelect.appendChild(opt);
55
+ } catch (err) {
56
+ console.error('Error loading regions:', err)
57
+ }
58
+
59
+ //Fetch available maps and populate the chart select box
60
+ const chartSelect = document.getElementById('chart')
61
+ if (!chartSelect) return
62
+
63
+ try {
64
+ const resp = await fetch('/signalk/v2/api/resources/charts')
65
+ if (!resp.ok) throw new Error(`Failed to fetch charts: ${resp.status}`)
66
+ const data = await resp.json()
67
+ // clear existing options
68
+ chartSelect.innerHTML = ''
69
+
70
+ const entries = Object.entries(data || {})
71
+ entries
72
+ .sort(([, a], [, b]) => {
73
+ const na = a && a.name ? String(a.name) : ''
74
+ const nb = b && b.name ? String(b.name) : ''
75
+ return na.localeCompare(nb)
76
+ })
77
+ .forEach(([id, info]) => {
78
+ if (info.proxy === true) {
79
+ const opt = document.createElement('option')
80
+ opt.value = id
81
+ opt.textContent = info && info.name ? info.name : id
82
+ chartSelect.appendChild(opt)
59
83
  }
60
- } catch (err) {
61
- console.error('Error loading regions:', err);
84
+ })
85
+
86
+ // if no maps returned, add a fallback option
87
+ if (!chartSelect.options.length) {
88
+ const opt = document.createElement('option')
89
+ opt.value = ''
90
+ opt.textContent = '-- No maps available --'
91
+ chartSelect.appendChild(opt)
62
92
  }
63
-
64
- //Fetch available maps and populate the chart select box
65
- const chartSelect = document.getElementById('chart');
66
- if (!chartSelect) return;
67
-
68
- try {
69
- const resp = await fetch('/signalk/v2/api/resources/charts');
70
- if (!resp.ok) throw new Error(`Failed to fetch charts: ${resp.status}`);
71
- const data = await resp.json();
72
- // clear existing options
73
- chartSelect.innerHTML = '';
74
-
75
- const entries = Object.entries(data || {});
76
- entries
77
- .sort(([, a], [, b]) => {
78
- const na = (a && a.name) ? String(a.name) : '';
79
- const nb = (b && b.name) ? String(b.name) : '';
80
- return na.localeCompare(nb);
81
- })
82
- .forEach(([id, info]) => {
83
-
84
- if (info.proxy === true) {
85
- const opt = document.createElement('option');
86
- opt.value = id;
87
- opt.textContent = (info && info.name) ? info.name : id;
88
- chartSelect.appendChild(opt);
89
- }
90
- });
91
-
92
- // if no maps returned, add a fallback option
93
- if (!chartSelect.options.length) {
94
- const opt = document.createElement('option');
95
- opt.value = '';
96
- opt.textContent = '-- No maps available --';
97
- chartSelect.appendChild(opt);
93
+ } catch (err) {
94
+ console.error('Error loading maps:', err)
95
+ }
96
+
97
+ document
98
+ .getElementById('createJobButton')
99
+ .addEventListener('click', async () => {
100
+ try {
101
+ const chart = document.getElementById('chart').value
102
+ const regionGUID = document.getElementById('region').value
103
+ const maxZoom = document.getElementById('maxZoom').value
104
+ const bbox = {
105
+ minLon: parseFloat(document.getElementById('bboxMinLon').value),
106
+ minLat: parseFloat(document.getElementById('bboxMinLat').value),
107
+ maxLon: parseFloat(document.getElementById('bboxMaxLon').value),
108
+ maxLat: parseFloat(document.getElementById('bboxMaxLat').value)
98
109
  }
99
- } catch (err) {
100
- console.error('Error loading maps:', err);
101
- }
102
-
103
- document.getElementById('createJobButton').addEventListener('click', async () => {
104
- try {
105
- const chart = document.getElementById('chart').value;
106
- const regionGUID = document.getElementById('region').value;
107
- const maxZoom = document.getElementById('maxZoom').value;
108
- const bbox = {
109
- minLon: parseFloat(document.getElementById('bboxMinLon').value),
110
- minLat: parseFloat(document.getElementById('bboxMinLat').value),
111
- maxLon: parseFloat(document.getElementById('bboxMaxLon').value),
112
- maxLat: parseFloat(document.getElementById('bboxMaxLat').value)
113
- };
114
- // const tile = {
115
- // z: parseInt(document.getElementById('tileZ').value),
116
- // x: parseInt(document.getElementById('tileX').value),
117
- // y: parseInt(document.getElementById('tileY').value),
118
-
119
- // };
120
- // console.log(tile);
121
- const type = document.querySelector('input[name="inputType"]:checked').value;
122
- const body = {};
123
- if (type === 'region') {
124
- body.regionGUID = regionGUID;
125
- } else if (type === 'bbox') {
126
- body.bbox = bbox;
127
- }
128
- // else if (type === 'tile') {
129
- // body.tile = tile;
130
- // }
131
- body.maxZoom = maxZoom;
132
- const resp = await fetch(`/signalk/chart-tiles/cache/${chart}`, {
133
- method: 'POST',
134
- headers: {
135
- 'Content-Type': 'application/json'
136
- },
137
- body: JSON.stringify(body)
138
- });
139
- if (!resp.ok) throw new Error(`Failed to seed charts: ${resp.status}`);
140
- const data = await resp.json();
141
- } catch (err) {
142
- console.error('Error seeding charts:', err);
110
+ // const tile = {
111
+ // z: parseInt(document.getElementById('tileZ').value),
112
+ // x: parseInt(document.getElementById('tileX').value),
113
+ // y: parseInt(document.getElementById('tileY').value),
114
+
115
+ // };
116
+ // console.log(tile);
117
+ const type = document.querySelector(
118
+ 'input[name="inputType"]:checked'
119
+ ).value
120
+ const body = {}
121
+ if (type === 'region') {
122
+ body.regionGUID = regionGUID
123
+ } else if (type === 'bbox') {
124
+ body.bbox = bbox
143
125
  }
144
- });
145
-
146
- const fmt = n => (typeof n === 'number' ? n.toLocaleString() : '-');
147
- const statusText = s => {
148
- switch (s) {
149
- case 0: return 'Stopped';
150
- case 1: return 'Running';
151
- default: return String(s);
152
- }
153
- };
154
-
155
- function renderJobs(items) {
156
- const tbody = document.querySelector('#activeJobsTable tbody');
157
- if (!tbody) return;
158
- tbody.innerHTML = '';
159
- if (!Array.isArray(items) || items.length === 0) {
160
- const tr = document.createElement('tr');
161
- tr.innerHTML = '<td colspan="10" class="muted">No active jobs</td>';
162
- tbody.appendChild(tr);
163
- return;
164
- }
165
-
166
- items.forEach((it, idx) => {
167
- const tr = document.createElement('tr');
126
+ // else if (type === 'tile') {
127
+ // body.tile = tile;
128
+ // }
129
+ body.maxZoom = maxZoom
130
+ const resp = await fetch(`/signalk/chart-tiles/cache/${chart}`, {
131
+ method: 'POST',
132
+ headers: {
133
+ 'Content-Type': 'application/json'
134
+ },
135
+ body: JSON.stringify(body)
136
+ })
137
+ if (!resp.ok) throw new Error(`Failed to seed charts: ${resp.status}`)
138
+ const data = await resp.json()
139
+ } catch (err) {
140
+ console.error('Error seeding charts:', err)
141
+ }
142
+ })
143
+
144
+ const fmt = (n) => (typeof n === 'number' ? n.toLocaleString() : '-')
145
+ const statusText = (s) => {
146
+ switch (s) {
147
+ case 0:
148
+ return 'Stopped'
149
+ case 1:
150
+ return 'Running'
151
+ default:
152
+ return String(s)
153
+ }
154
+ }
155
+
156
+ function renderJobs(items) {
157
+ const tbody = document.querySelector('#activeJobsTable tbody')
158
+ if (!tbody) return
159
+ tbody.innerHTML = ''
160
+ if (!Array.isArray(items) || items.length === 0) {
161
+ const tr = document.createElement('tr')
162
+ tr.innerHTML = '<td colspan="10" class="muted">No active jobs</td>'
163
+ tbody.appendChild(tr)
164
+ return
165
+ }
168
166
 
169
- const pct = (typeof it.progress === 'number' && isFinite(it.progress)) ? Math.max(0, Math.min(100, it.progress * 100)) : 0;
170
- const pctText = `${pct.toFixed(1)}%`;
171
- tr.innerHTML = [
172
- `<td>${idx + 1}</td>`,
173
- `<td>${it.chartName || '-'}</td>`,
174
- `<td>${it.regionName || '-'}</td>`,
175
- `<td>${fmt(it.totalTiles)}</td>`,
176
- `<td>${fmt(it.downloadedTiles)}</td>`,
177
- `<td>${fmt(it.cachedTiles)}</td>`,
178
- `<td>${fmt(it.failedTiles)}</td>`,
179
- `<td>
167
+ items.forEach((it, idx) => {
168
+ const tr = document.createElement('tr')
169
+
170
+ const pct =
171
+ typeof it.progress === 'number' && isFinite(it.progress)
172
+ ? Math.max(0, Math.min(100, it.progress * 100))
173
+ : 0
174
+ const pctText = `${pct.toFixed(1)}%`
175
+ tr.innerHTML = [
176
+ `<td>${idx + 1}</td>`,
177
+ `<td>${it.chartName || '-'}</td>`,
178
+ `<td>${it.regionName || '-'}</td>`,
179
+ `<td>${fmt(it.totalTiles)}</td>`,
180
+ `<td>${fmt(it.downloadedTiles)}</td>`,
181
+ `<td>${fmt(it.cachedTiles)}</td>`,
182
+ `<td>${fmt(it.failedTiles)}</td>`,
183
+ `<td>
180
184
  <span class="progress-bar" aria-hidden="true">
181
185
  <span class="progress-fill" style="width:${pct}%"></span>
182
186
  </span>
183
187
  <span class="percent">${pctText}</span>
184
188
  </td>`,
185
- `<td>${statusText(it.status)}</td>`,
186
- `
189
+ `<td>${statusText(it.status)}</td>`,
190
+ `
187
191
  <td class="action-buttons">
188
192
  <button class="btn startstop-btn" data-id="${it.id}" data-totalTiles="${it.totalTiles}" title="Start">
189
- <svg class="icon-play ${it.status === 1 ? "hidden" : ""}" viewBox="0 0 24 24" width="16" height="16" fill="white">
193
+ <svg class="icon-play ${it.status === 1 ? 'hidden' : ''}" viewBox="0 0 24 24" width="16" height="16" fill="white">
190
194
  <path d="M8 5v14l11-7z"/>
191
195
  </svg>
192
- <svg class="icon-pause ${it.status === 1 ? "" : "hidden"}" viewBox="0 0 24 24" width="16" height="16" fill="white">
196
+ <svg class="icon-pause ${it.status === 1 ? '' : 'hidden'}" viewBox="0 0 24 24" width="16" height="16" fill="white">
193
197
  <path d="M6 19h4V5H6zm8-14v14h4V5z"/>
194
198
  </svg>
195
199
  </button>
@@ -205,99 +209,102 @@ document.addEventListener('DOMContentLoaded', async () => {
205
209
  </button>
206
210
  </td>
207
211
  `
208
- ].join('');
209
-
210
- tr.querySelectorAll('.startstop-btn').forEach(button => {
211
- button.addEventListener('click', () => {
212
- const playIcon = button.querySelector('.icon-play');
213
- const pauseIcon = button.querySelector('.icon-pause');
214
- const jobId = button.getAttribute('data-id');
215
- const totalTiles = button.getAttribute('data-totalTiles');
216
- const isRunning = pauseIcon.classList.contains('hidden');
217
- console.log(isRunning)
218
- if (isRunning) {
219
- // Switch to stop
220
- if (totalTiles > 100000 && !confirm(`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?`)) {
221
- return;
222
- }
223
- takeAction(jobId, 'start');
224
- button.title = 'Stop';
225
- } else {
226
- // Switch to start
227
- takeAction(jobId, 'stop');
228
- button.title = 'Start';
229
- }
230
- });
231
- });
232
-
233
- tr.querySelectorAll('.delete-btn').forEach(button => {
234
- button.addEventListener('click', () => {
235
- if (confirm("This will delete all cached tiles for this job. Are you sure?")) {
236
- const jobId = button.getAttribute('data-id');
237
- if (jobId) {
238
- takeAction(jobId, 'delete');
239
- }
240
- }
241
- });
242
- });
243
-
244
- tr.querySelectorAll('.remove-btn').forEach(button => {
245
- button.addEventListener('click', () => {
246
- const jobId = button.getAttribute('data-id');
247
- if (jobId) {
248
- takeAction(jobId, 'remove');
249
- }
250
- });
251
- });
252
-
253
- tbody.appendChild(tr);
254
- });
255
-
256
-
257
-
258
- function takeAction(jobId, action) {
259
- fetch(`/signalk/chart-tiles/cache/jobs/${jobId}`, {
260
- method: 'POST',
261
- headers: {
262
- 'Content-Type': 'application/json'
263
- },
264
- body: JSON.stringify({
265
- action: action,
266
- })
267
- })
268
- .then(response => {
269
- if (!response.ok) {
270
- throw new Error(`Failed to ${action} job ${jobId}: ${response.status}`);
271
- }
272
- fetchActiveJobs();
273
- })
274
- .catch(error => {
275
- console.error(`Error ${action} job:`, error);
276
- });
277
- }
212
+ ].join('')
213
+
214
+ tr.querySelectorAll('.startstop-btn').forEach((button) => {
215
+ button.addEventListener('click', () => {
216
+ const playIcon = button.querySelector('.icon-play')
217
+ const pauseIcon = button.querySelector('.icon-pause')
218
+ const jobId = button.getAttribute('data-id')
219
+ const totalTiles = button.getAttribute('data-totalTiles')
220
+ const isRunning = pauseIcon.classList.contains('hidden')
221
+ console.log(isRunning)
222
+ if (isRunning) {
223
+ // Switch to stop
224
+ if (
225
+ totalTiles > 100000 &&
226
+ !confirm(
227
+ `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?`
228
+ )
229
+ ) {
230
+ return
231
+ }
232
+ takeAction(jobId, 'start')
233
+ button.title = 'Stop'
234
+ } else {
235
+ // Switch to start
236
+ takeAction(jobId, 'stop')
237
+ button.title = 'Start'
238
+ }
239
+ })
240
+ })
241
+
242
+ tr.querySelectorAll('.delete-btn').forEach((button) => {
243
+ button.addEventListener('click', () => {
244
+ if (
245
+ confirm(
246
+ 'This will delete all cached tiles for this job. Are you sure?'
247
+ )
248
+ ) {
249
+ const jobId = button.getAttribute('data-id')
250
+ if (jobId) {
251
+ takeAction(jobId, 'delete')
252
+ }
253
+ }
254
+ })
255
+ })
256
+
257
+ tr.querySelectorAll('.remove-btn').forEach((button) => {
258
+ button.addEventListener('click', () => {
259
+ const jobId = button.getAttribute('data-id')
260
+ if (jobId) {
261
+ takeAction(jobId, 'remove')
262
+ }
263
+ })
264
+ })
265
+
266
+ tbody.appendChild(tr)
267
+ })
268
+
269
+ function takeAction(jobId, action) {
270
+ fetch(`/signalk/chart-tiles/cache/jobs/${jobId}`, {
271
+ method: 'POST',
272
+ headers: {
273
+ 'Content-Type': 'application/json'
274
+ },
275
+ body: JSON.stringify({
276
+ action: action
277
+ })
278
+ })
279
+ .then((response) => {
280
+ if (!response.ok) {
281
+ throw new Error(
282
+ `Failed to ${action} job ${jobId}: ${response.status}`
283
+ )
284
+ }
285
+ fetchActiveJobs()
286
+ })
287
+ .catch((error) => {
288
+ console.error(`Error ${action} job:`, error)
289
+ })
278
290
  }
291
+ }
279
292
 
280
-
281
-
282
- async function fetchActiveJobs() {
283
- const tbody = document.querySelector('#activeJobsTable tbody');
284
- try {
285
- const resp = await fetch('/signalk/chart-tiles/cache/jobs');
286
- if (!resp.ok) throw new Error('HTTP ' + resp.status);
287
- const data = await resp.json();
288
- renderJobs(data);
289
- } catch (err) {
290
- console.error('Error fetching active jobs:', err);
291
- tbody.innerHTML = '<tr><td colspan="10" class="muted">Error loading active jobs</td></tr>';
292
- }
293
+ async function fetchActiveJobs() {
294
+ const tbody = document.querySelector('#activeJobsTable tbody')
295
+ try {
296
+ const resp = await fetch('/signalk/chart-tiles/cache/jobs')
297
+ if (!resp.ok) throw new Error('HTTP ' + resp.status)
298
+ const data = await resp.json()
299
+ renderJobs(data)
300
+ } catch (err) {
301
+ console.error('Error fetching active jobs:', err)
302
+ tbody.innerHTML =
303
+ '<tr><td colspan="10" class="muted">Error loading active jobs</td></tr>'
293
304
  }
305
+ }
294
306
 
295
-
296
-
297
- fetchActiveJobs();
298
- // refresh every 2 seconds
299
- setInterval(fetchActiveJobs, 2000);
300
-
301
-
302
- });
303
-
307
+ fetchActiveJobs()
308
+ // refresh every 2 seconds
309
+ setInterval(fetchActiveJobs, 2000)
310
+ })