@@ -250,7 +250,7 @@ def _generate_csv_data(filtered_df: pd.DataFrame) -> bytes | None:
250250def render_export_section (filtered_ef_df : pd .DataFrame ) -> None :
251251 """
252252 Renders the export section, allowing users to download selected files
253- as a ZIP archive.
253+ as a ZIP archive using a state-driven UI to prevent widget duplication .
254254
255255 Args:
256256 filtered_ef_df (pd.DataFrame): DataFrame filtered by all previous selections.
@@ -261,70 +261,88 @@ def render_export_section(filtered_ef_df: pd.DataFrame) -> None:
261261 st .info ("No EyeFlow data is selected to be exported." )
262262 return
263263
264- st .subheader ("Create an export package" )
265- col1 , col2 = st .columns (2 )
264+ # Initialize state if it doesn't exist
265+ if "export_status" not in st .session_state :
266+ st .session_state .export_status = "ready_to_export"
266267
267- with col1 :
268- if st .button ("Export pdf reports + csv data" , use_container_width = True ):
269- # Clear previous zip from session state to avoid showing old downloads
270- if "zip_buffer" in st .session_state :
271- del st .session_state ["zip_buffer" ]
268+ # --- STATE 3: Ready to Download ---
269+ # If a zip file has been created, show the download button.
270+ if st .session_state .export_status == "ready_to_download" :
271+ st .success ("Your export package is ready to be downloaded." )
272+ st .download_button (
273+ label = "Download ZIP" ,
274+ data = st .session_state .zip_buffer .getvalue (),
275+ file_name = st .session_state .get ("zip_file_name" , "eyeflow_export.zip" ),
276+ mime = "application/zip" ,
277+ on_click = lambda : st .session_state .update (export_status = "ready_to_export" ),
278+ )
279+ if st .session_state .get ("skipped_files" ):
280+ st .warning ("The following files were not found and were skipped:" )
281+ st .code ("\n " .join (st .session_state .get ("skipped_files" , [])))
272282
283+ # --- STATE 2: Processing ---
284+ # If an export has been triggered, run the zipping process.
285+ # The buttons from the 'else' block will NOT be rendered.
286+ elif st .session_state .export_status == "processing" :
287+ export_type = st .session_state .get ("export_type" , "full" )
288+
289+ # Determine which files to collect based on the button clicked
290+ if export_type == "pdf_csv" :
273291 files_to_zip = _collect_files_to_zip (
274292 filtered_ef_df ,
275293 export_pdfs = True ,
276294 export_h5s = False ,
277295 export_jsons = False ,
278296 export_input_params = False ,
279297 )
280- csv_data = _generate_csv_data (filtered_ef_df )
281-
282- if not files_to_zip and not csv_data :
283- st .warning ("No PDF reports or CSV data are available to export." )
284- else :
285- zip_buffer , skipped_files = _create_zip_archive (files_to_zip , csv_data )
286- st .session_state .zip_buffer = zip_buffer
287- st .session_state .skipped_files = skipped_files
288- st .session_state .zip_file_name = "eyeflow_pdf_csv_export.zip"
289-
290- with col2 :
291- if st .button (
292- "Export all (pdf reports, csv data, h5 outputs, json outputs and params)" ,
293- use_container_width = True ,
294- ):
295- # Clear previous zip from session state
296- if "zip_buffer" in st .session_state :
297- del st .session_state ["zip_buffer" ]
298-
298+ st .session_state .zip_file_name = "eyeflow_pdf_csv_export.zip"
299+ else : # 'full' export
299300 files_to_zip = _collect_files_to_zip (
300301 filtered_ef_df ,
301302 export_pdfs = True ,
302303 export_h5s = True ,
303304 export_jsons = True ,
304305 export_input_params = True ,
305306 )
306- csv_data = _generate_csv_data ( filtered_ef_df )
307+ st . session_state . zip_file_name = "eyeflow_full_export.zip"
307308
308- if not files_to_zip and not csv_data :
309- st .warning ("No files or data are available to export." )
310- else :
311- zip_buffer , skipped_files = _create_zip_archive (files_to_zip , csv_data )
312- st .session_state .zip_buffer = zip_buffer
313- st .session_state .skipped_files = skipped_files
314- st .session_state .zip_file_name = "eyeflow_full_export.zip"
315-
316- # --- Download Section ---
317- # This part remains active as long as a zip_buffer is in the session state
318- if "zip_buffer" in st .session_state :
319- st .success ("Your export package is ready to be downloaded." )
320- st .download_button (
321- label = "Download ZIP" ,
322- data = st .session_state .zip_buffer .getvalue (),
323- file_name = st .session_state .get ("zip_file_name" , "eyeflow_export.zip" ),
324- mime = "application/zip" ,
325- # Remove the buffer from state after clicking download
326- on_click = lambda : st .session_state .pop ("zip_buffer" , None ),
327- )
328- if st .session_state .get ("skipped_files" ):
329- st .warning ("The following files were not found and were skipped:" )
330- st .code ("\n " .join (st .session_state .get ("skipped_files" , [])))
309+ csv_data = _generate_csv_data (filtered_ef_df )
310+
311+ if not files_to_zip and not csv_data :
312+ st .warning ("No files or data are available to export." )
313+ st .session_state .export_status = "ready_to_export" # Reset state
314+ st .rerun ()
315+ else :
316+ zip_buffer , skipped_files = _create_zip_archive (files_to_zip , csv_data )
317+ st .session_state .zip_buffer = zip_buffer
318+ st .session_state .skipped_files = skipped_files
319+
320+ # Transition to the next state and rerun
321+ st .session_state .export_status = "ready_to_download"
322+ st .rerun ()
323+
324+ # --- STATE 1: Ready to Export (Default) ---
325+ # Otherwise, show the export buttons.
326+ else :
327+ st .subheader ("Create an export package" )
328+ col1 , col2 = st .columns (2 )
329+
330+ def set_export_type (export_type : str ):
331+ st .session_state .export_status = "processing"
332+ st .session_state .export_type = export_type
333+
334+ with col1 :
335+ st .button (
336+ "Export pdf reports + csv data" ,
337+ use_container_width = True ,
338+ on_click = set_export_type ,
339+ args = ("pdf_csv" ,),
340+ )
341+
342+ with col2 :
343+ st .button (
344+ "Export all (pdf reports, csv data, h5 outputs, json outputs and params)" ,
345+ use_container_width = True ,
346+ on_click = set_export_type ,
347+ args = ("full" ,),
348+ )
0 commit comments