Skip to content

Commit 47b94c0

Browse files
committed
recorder: add multi-select download and delete
1 parent 953bfbb commit 47b94c0

File tree

1 file changed

+100
-45
lines changed

1 file changed

+100
-45
lines changed

apps/recorder/interface.html

Lines changed: 100 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,20 @@
5858
.track-loading { text-align: center; padding: 20px; color: #666; }
5959
html, body { height: auto; min-height: 100%; }
6060
body { margin: 0; padding-bottom: 50px; }
61+
.download-checkbox {
62+
/* TODO */
63+
}
64+
.vertical {
65+
display: flex;
66+
flex-direction: column;
67+
}
68+
.horizontal {
69+
display: flex;
70+
flex-direction: row;
71+
}
72+
.hidden {
73+
display: none;
74+
}
6175
</style>
6276
</head>
6377
<body>
@@ -441,34 +455,50 @@
441455
return o;
442456
}
443457

444-
function downloadTrack(filename, callback) {
445-
function onData(data) {
458+
async function downloadTrack(filename) {
459+
function parse(data) {
446460
const lines = data.trim().split("\n"), headers = lines.shift().split(",");
447-
callback(lines.map(l=>trackLineToObject(headers, l)).filter(t => t.Time));
461+
return lines.map(l=>trackLineToObject(headers, l)).filter(t => t.Time);
448462
}
449463
const data = fileCache.get(filename);
450-
if (data) onData(data);
451-
else {
452-
Util.showModal(`Downloading ${filename}...`);
453-
Util.readStorageFile(filename, data => {
454-
fileCache.set(filename, data);
455-
onData(data);
456-
Util.hideModal();
457-
});
464+
if (data) return parse(data);
465+
466+
Util.showModal(`Downloading ${filename}...`);
467+
try {
468+
const data = await new Promise(resolve => Util.readStorageFile(filename, resolve));
469+
fileCache.set(filename, data);
470+
return parse(data);
471+
} finally {
472+
Util.hideModal();
473+
}
474+
}
475+
476+
function isSelected(track) {
477+
const trackNumber = track.number;
478+
return document.getElementById(`track-download-${trackNumber}`).checked;
479+
}
480+
481+
async function downloadTracks(tracks, saveCb) {
482+
for(const track of tracks){
483+
const lines = await downloadTrack(track.filename);
484+
const title = `Bangle.js Track ${track.number}`;
485+
486+
saveCb(lines, title);
458487
}
488+
489+
showToast("Finished downloading.", "success");
459490
}
460491

461-
function downloadAll(trackList, cb) {
462-
const tracks = [...trackList];
463-
const downloadOne = () => {
464-
const track = tracks.pop();
465-
if(!track) return showToast("Finished downloading all.", "success");
466-
downloadTrack(track.filename, lines => {
467-
cb(lines, `Bangle.js Track ${track.number}`);
468-
downloadOne();
492+
async function deleteTrack(filename) {
493+
Util.showModal(`Deleting ${filename}...`);
494+
495+
try {
496+
await new Promise(resolve => {
497+
Util.eraseStorageFile(filename, () => resolve());
469498
});
470-
};
471-
downloadOne();
499+
} finally {
500+
Util.hideModal();
501+
}
472502
}
473503

474504
// ========================================
@@ -709,13 +739,18 @@ <h2>GPS Tracks</h2>`;
709739

710740
html += `
711741
<div class="accordion-item">
712-
<input type="checkbox" id="accordion-track-${track.number}" name="accordion-tracks" hidden>
713-
<label class="accordion-header" for="accordion-track-${track.number}" data-track-index="${index}">
714-
<i class="icon icon-arrow-right mr-1"></i>
715-
<strong>Track ${track.number}</strong> - ${dateStr}
716-
</label>
717-
<div class="accordion-body" id="track-content-${track.number}">
718-
<div class="track-loading">Click to load track data...</div>
742+
<div class="horizontal">
743+
<input type="checkbox" id="track-download-${track.number}" class="download-checkbox">
744+
<div class="vertical">
745+
<input type="checkbox" id="accordion-track-${track.number}" name="accordion-tracks" hidden>
746+
<label class="accordion-header" for="accordion-track-${track.number}" data-track-index="${index}">
747+
<i class="icon icon-arrow-right mr-1"></i>
748+
<strong>Track ${track.number}</strong> - ${dateStr}
749+
</label>
750+
<div class="accordion-body" id="track-content-${track.number}">
751+
<div class="track-loading">Click to load track data...</div>
752+
</div>
753+
</div>
719754
</div>
720755
</div>`;
721756
});
@@ -735,9 +770,12 @@ <h2>GPS Tracks</h2>`;
735770
}
736771

737772
html += `
738-
<h2>Batch Operations</h2>
739-
<div class="form-group">
740-
${['KML', 'GPX', 'CSV'].map(fmt => `<button class="btn btn-primary" task="download${fmt.toLowerCase()}_all">Download all ${fmt}</button>`).join(' ')}
773+
<div id="batch">
774+
<h2>Batch Operations</h2>
775+
<div class="form-group">
776+
${['KML', 'GPX', 'CSV'].map(fmt => `<button class="btn btn-primary" task="download${fmt.toLowerCase()}_selected">Download selected ${fmt}</button>`).join(' ')}
777+
<button class="btn btn-primary" id="delete-selected">Delete selected</button>
778+
</div>
741779
</div>
742780
<h2>Settings</h2>
743781
<div class="form-group">
@@ -791,7 +829,7 @@ <h2>Settings</h2>
791829
trackContainer.dataset.loaded = 'true';
792830
attachTrackButtonListeners(trackContainer);
793831

794-
downloadTrack(track.filename, fullTrack => {
832+
downloadTrack(track.filename).then(fullTrack => {
795833
if (trackData.Latitude) {
796834
const coordinates = fullTrack
797835
.filter(hasValidGPS)
@@ -820,7 +858,7 @@ <h2>Settings</h2>
820858
const buttons = container.querySelectorAll("button[task]");
821859

822860
buttons.forEach(button => {
823-
button.addEventListener("click", event => {
861+
button.addEventListener("click", async event => {
824862
const button = event.currentTarget;
825863
const filename = button.getAttribute("filename");
826864
const trackid = button.getAttribute("trackid");
@@ -832,18 +870,15 @@ <h2>Settings</h2>
832870
case "delete":
833871
if (button.dataset.confirmDelete === "true") {
834872
// Second click - proceed with deletion
835-
Util.showModal(`Deleting ${filename}...`);
836-
Util.eraseStorageFile(filename, () => {
837-
Util.hideModal();
838-
getTrackList();
839-
});
873+
await deleteTrack(filename);
874+
getTrackList();
840875
} else {
841876
// First click - change to confirm state
842877
const originalText = button.textContent;
843878
button.textContent = "Confirm Delete";
844879
button.classList.add("btn-error");
845880
button.dataset.confirmDelete = "true";
846-
881+
847882
// Reset after 3 seconds
848883
setTimeout(() => {
849884
if (button.dataset.confirmDelete === "true") {
@@ -855,20 +890,27 @@ <h2>Settings</h2>
855890
}
856891
break;
857892
case "downloadkml":
858-
downloadTrack(filename, track => saveKML(track, `Bangle.js Track ${trackid}`));
893+
await downloadTracks([filename], track => saveKML(track, `Bangle.js Track ${trackid}`));
859894
break;
860895
case "downloadgpx":
861-
downloadTrack(filename, track => saveGPX(track, `Bangle.js Track ${trackid}`));
896+
await downloadTracks([filename], track => saveGPX(track, `Bangle.js Track ${trackid}`));
862897
break;
863898
case "downloadcsv":
864-
downloadTrack(filename, track => saveCSV(track, `Bangle.js Track ${trackid}`));
899+
await downloadTracks([filename], track => saveCSV(track, `Bangle.js Track ${trackid}`));
865900
break;
866901
}
867902
});
868903
});
869904
}
870905

871906
if (trackList.length > 0) {
907+
const handleCheckboxCheck = () => {
908+
const checkboxes = [...document.querySelectorAll(".download-checkbox")];
909+
const batch = document.querySelector("#batch");
910+
911+
batch.classList.toggle("hidden", !checkboxes.some(b => b.checked));
912+
};
913+
872914
document.querySelectorAll('.accordion-header').forEach(header => {
873915
header.addEventListener('click', e => {
874916
const trackIndex = parseInt(header.dataset.trackIndex);
@@ -877,6 +919,10 @@ <h2>Settings</h2>
877919
setTimeout(() => displayTrack(trackIndex, trackNumber), 10);
878920
}
879921
});
922+
923+
header.closest(".horizontal")
924+
.querySelector(".download-checkbox")
925+
.addEventListener('click', handleCheckboxCheck);
880926
});
881927
}
882928

@@ -899,12 +945,21 @@ <h2>Settings</h2>
899945
getTrackList();
900946
});
901947
Util.hideModal();
902-
domTracks.querySelectorAll("button[task$='_all']").forEach(button => {
903-
button.addEventListener("click", e => {
948+
domTracks.querySelectorAll("button[task$='_selected']").forEach(button => {
949+
button.addEventListener("click", async e => {
904950
const task = e.currentTarget.getAttribute("task");
905-
downloadAll(trackList, task.includes('kml') ? saveKML : task.includes('gpx') ? saveGPX : saveCSV);
951+
await downloadTracks(
952+
trackList.filter(isSelected),
953+
task.includes('kml') ? saveKML : task.includes('gpx') ? saveGPX : saveCSV
954+
);
906955
});
907956
});
957+
domTracks.querySelector("button#delete-selected").addEventListener("click", async e => {
958+
const selected = trackList.filter(isSelected);
959+
960+
for(const track of selected)
961+
await deleteTrack(track.filename);
962+
});
908963
});
909964
});
910965
}

0 commit comments

Comments
 (0)