@@ -68,18 +68,18 @@ <h3>Add a {{ pretty_title }} {{ tab.title() }} from Google Drive</h3>
6868
6969 <!-- Link a FOLDER (import all allowed files) -->
7070 < form method ="post " class ="stack " style ="margin-bottom:1em; ">
71- < div class ="small "> Or link a Drive FOLDER (we’ll import all allowed files, including subfolders).</ div >
71+ < div class ="small "> Or link a Drive FOLDER (we’ll import all allowed files, incl. subfolders).</ div >
7272 < div >
7373 < input type ="text " name ="drive_folder_link " placeholder ="Drive FOLDER link or folder ID " style ="width:560px " required >
7474 < button type ="submit " name ="link_folder " value ="1 "> Link Folder</ button >
7575 </ div >
7676 </ form >
7777
78- <!-- Upload a ZIP and push to Drive (currently optional) -->
78+ <!-- Upload a ZIP and push to Drive -->
7979 < form method ="post " enctype ="multipart/form-data " class ="stack ">
8080 < div class ="small "> Or upload a .zip; allowed files inside will be uploaded to Drive automatically.</ div >
8181 < div >
82- < input type ="file " name ="zipfile " accept =".zip ">
82+ < input type ="file " name ="zipfile " accept =".zip " required >
8383 < button type ="submit " name ="zip_upload " value ="1 "> Upload ZIP to Drive</ button >
8484 </ div >
8585 </ form >
@@ -108,16 +108,16 @@ <h3>Add a {{ pretty_title }} {{ tab.title() }} from Google Drive</h3>
108108 {% set uploaded_at = (row.uploaded_at if row.uploaded_at is defined else row['uploaded_at']) %}
109109 {% set preview_url = (row.preview_url if row.preview_url is defined else row['preview_url']) %}
110110 {% set download_url = (row.download_url if row.download_url is defined else row['download_url']) %}
111+ {% set form_id = 'f_' ~ loop.index %}
111112
112113 < tr >
113114 < td > {{ filename }}</ td >
114115
115116 {% if tab == 'dataset' %}
116117 < td >
117118 {% if admin %}
118- < form method ="post " class ="row-edit " style ="display:inline; ">
119- < input type ="hidden " name ="row_filename " value ="{{ filename }} ">
120- < input type ="text " name ="row_source " value ="{{ source or '' }} " size ="18 ">
119+ <!-- This input belongs to the row form via the HTML5 'form' attribute -->
120+ < input type ="text " name ="row_source " form ="{{ form_id }} " value ="{{ source or '' }} " size ="18 ">
121121 {% else %}
122122 {{ source or '' }}
123123 {% endif %}
@@ -126,17 +126,15 @@ <h3>Add a {{ pretty_title }} {{ tab.title() }} from Google Drive</h3>
126126
127127 < td >
128128 {% if admin %}
129- {% if tab == 'dataset' %}
129+ <!-- One form per row; keeps page from reloading via JS -->
130+ < form id ="{{ form_id }} " class ="row-edit-form "
131+ action ="{{ url_for('materials_edit_row', property_name=property_name, tab=tab) }} "
132+ method ="post " style ="display:inline; ">
133+ < input type ="hidden " name ="row_filename " value ="{{ filename }} ">
130134 < input type ="text " name ="row_description " value ="{{ description or '' }} " size ="22 ">
131135 < button type ="submit " name ="edit_row " value ="1 " style ="margin-left:6px; "> Save</ button >
132- </ form >
133- {% else %}
134- < form method ="post " class ="row-edit " style ="display:inline; ">
135- < input type ="hidden " name ="row_filename " value ="{{ filename }} ">
136- < input type ="text " name ="row_description " value ="{{ description or '' }} " size ="22 ">
137- < button type ="submit " name ="edit_row " value ="1 " style ="margin-left:6px; "> Save</ button >
138- </ form >
139- {% endif %}
136+ < span class ="save-result " style ="margin-left:.5em;font-size:.9em; "> </ span >
137+ </ form >
140138 {% else %}
141139 {{ description or '' }}
142140 {% endif %}
@@ -150,20 +148,20 @@ <h3>Add a {{ pretty_title }} {{ tab.title() }} from Google Drive</h3>
150148
151149 < td >
152150 {% if (storage or 'local') == 'drive' %}
153- {% if preview_url %}< a href ="{{ preview_url }} " target ="_blank " rel =" noopener " > View</ a > {% endif %}
154- {% if download_url %} | < a href ="{{ download_url }} " target ="_blank " rel =" noopener " > Download</ a > {% endif %}
151+ {% if preview_url %}< a href ="{{ preview_url }} " target ="_blank "> View</ a > {% endif %}
152+ {% if download_url %} | < a href ="{{ download_url }} " target ="_blank "> Download</ a > {% endif %}
155153 {% else %}
156154 {% if tab == 'dataset' and (filename.endswith('.csv') or filename.endswith('.npy')) %}
157155 {% set tname = table_map.get(filename) %}
158156 {% if tname %}
159- < a href ="{{ url_for('public_view', table=tname) }} " target ="_blank " rel =" noopener " > View</ a >
157+ < a href ="{{ url_for('public_view', table=tname) }} " target ="_blank "> View</ a >
160158 {% else %}
161159 < span class ="small "> No table yet</ span >
162160 {% endif %}
163161 {% elif tab == 'results' %}
164- < a href ="{{ url_for('view_result_file', property_name=property_name, tab=tab, filename=filename) }} " target ="_blank " rel =" noopener " > View</ a >
162+ < a href ="{{ url_for('view_result_file', property_name=property_name, tab=tab, filename=filename) }} " target ="_blank "> View</ a >
165163 {% endif %}
166- | < a href ="{{ url_for('uploaded_file', filename=property_name ~ '/' ~ tab ~ '/' ~ filename) }} " download > Download</ a >
164+ | < a href ="{{ url_for('uploaded_file', filename=property_name ~ '/' ~ tab ~ '/' ~ filename) }} " download > Download</ a >
167165 {% endif %}
168166
169167 {% if admin %}
@@ -190,33 +188,35 @@ <h3>Add a {{ pretty_title }} {{ tab.title() }} from Google Drive</h3>
190188
191189 {% if admin %}
192190 < script >
193- // Intercept per-row Save to avoid page reload (preserves other unsaved edits)
194- document . querySelectorAll ( 'form .row-edit' ) . forEach ( function ( form ) {
191+ document . addEventListener ( 'DOMContentLoaded' , function ( ) {
192+ document . querySelectorAll ( '.row-edit-form ' ) . forEach ( function ( form ) {
195193 form . addEventListener ( 'submit' , async function ( e ) {
196194 e . preventDefault ( ) ;
197- const fd = new FormData ( form ) ;
198- // Ensure the intent flag is present
199- if ( ! fd . has ( 'edit_row' ) ) fd . append ( 'edit_row' , '1' ) ;
200- const btn = form . querySelector ( 'button[type="submit"]' ) ;
201- const old = btn . textContent ;
202- btn . disabled = true ;
203-
195+ const btn = form . querySelector ( 'button[type=submit]' ) ;
196+ const result = form . querySelector ( '.save-result' ) ;
197+ if ( btn ) { btn . disabled = true ; btn . textContent = 'Saving…' ; }
204198 try {
205- const resp = await fetch ( location . href , {
199+ const fd = new FormData ( form ) ;
200+ const resp = await fetch ( form . action , {
206201 method : 'POST' ,
207202 body : fd ,
208203 headers : { 'X-Requested-With' : 'XMLHttpRequest' }
209204 } ) ;
210- let data = { } ;
211- try { data = await resp . json ( ) ; } catch { /* ignore */ }
212- btn . textContent = data . ok ? 'Saved ✓' : 'Saved' ;
205+ const data = await resp . json ( ) ;
206+ if ( data . ok ) {
207+ if ( result ) result . textContent = 'Saved ✓' ;
208+ } else {
209+ if ( result ) result . textContent = 'Error: ' + ( data . error || 'failed' ) ;
210+ }
213211 } catch ( err ) {
214- btn . textContent = 'Error' ;
212+ if ( result ) result . textContent = 'Error: ' + err ;
213+ } finally {
214+ if ( btn ) { btn . disabled = false ; btn . textContent = 'Save' ; }
215+ setTimeout ( ( ) => { if ( result ) result . textContent = '' ; } , 2500 ) ;
215216 }
216-
217- setTimeout ( ( ) => { btn . disabled = false ; btn . textContent = old ; } , 1200 ) ;
218217 } ) ;
219218 } ) ;
219+ } ) ;
220220 </ script >
221221 {% endif %}
222222</ body >
0 commit comments