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 >
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