@@ -275,6 +275,56 @@ def action_close(self) -> None:
275275 self .dismiss ()
276276
277277
278+ class SaveFileModal (ModalScreen ):
279+ """Modal to choose save filename before downloading."""
280+
281+ BINDINGS = [("escape" , "cancel" , "Cancel" )]
282+
283+ def __init__ (self , default_filename : str , file_path : str , layer_idx : int ):
284+ super ().__init__ ()
285+ self .default_filename = default_filename
286+ self .file_path = file_path
287+ self .layer_idx = layer_idx
288+
289+ def compose (self ) -> ComposeResult :
290+ with Vertical (id = "save-file-dialog" ):
291+ yield Label ("Save File As" , id = "save-file-title" )
292+ yield Label (f"Source: { self .file_path } (Layer { self .layer_idx } )" , id = "save-file-source" )
293+ yield Input (value = self .default_filename , id = "save-filename-input" , placeholder = "Enter filename..." )
294+ with Horizontal (id = "save-file-buttons" ):
295+ yield Button ("Save" , id = "btn-confirm-save" , variant = "primary" )
296+ yield Button ("Cancel" , id = "btn-cancel-save" , variant = "warning" )
297+
298+ def on_button_pressed (self , event : Button .Pressed ) -> None :
299+ if event .button .id == "btn-confirm-save" :
300+ filename_input = self .query_one ("#save-filename-input" , Input )
301+ filename = filename_input .value .strip ()
302+ if filename :
303+ self .dismiss (result = {
304+ "filename" : filename ,
305+ "path" : self .file_path ,
306+ "layer" : self .layer_idx
307+ })
308+ else :
309+ self .notify ("Please enter a filename" , severity = "warning" )
310+ else :
311+ self .dismiss (result = None )
312+
313+ def on_input_submitted (self , event : Input .Submitted ) -> None :
314+ """Handle Enter key in the filename input."""
315+ if event .input .id == "save-filename-input" :
316+ filename = event .value .strip ()
317+ if filename :
318+ self .dismiss (result = {
319+ "filename" : filename ,
320+ "path" : self .file_path ,
321+ "layer" : self .layer_idx
322+ })
323+
324+ def action_cancel (self ) -> None :
325+ self .dismiss (result = None )
326+
327+
278328def parse_slug (slug : str ) -> tuple [str , str ]:
279329 """Extract namespace and repo from slug.
280330
@@ -629,7 +679,30 @@ def _on_file_action_chosen(self, result: dict | None) -> None:
629679 if action == "view" :
630680 self .carve_file_as_text (file_path , layer , filename )
631681 elif action == "save" :
632- self .carve_file_download (file_path , layer , filename )
682+ # Generate a unique default filename with layer number
683+ # Split filename into name and extension
684+ if "." in filename :
685+ name_parts = filename .rsplit ("." , 1 )
686+ default_filename = f"{ name_parts [0 ]} _L{ layer } .{ name_parts [1 ]} "
687+ else :
688+ default_filename = f"{ filename } _L{ layer } "
689+
690+ # Show save filename modal
691+ self .push_screen (
692+ SaveFileModal (default_filename , file_path , layer ),
693+ callback = self ._on_save_filename_chosen
694+ )
695+
696+ def _on_save_filename_chosen (self , result : dict | None ) -> None :
697+ """Handle save filename modal result."""
698+ if result is None :
699+ return
700+
701+ filename = result .get ("filename" )
702+ file_path = result .get ("path" )
703+ layer = result .get ("layer" )
704+
705+ self .carve_file_download (file_path , layer , filename )
633706
634707 @work (exclusive = True , group = "carve" )
635708 async def carve_file_as_text (self , file_path : str , layer : int , filename : str ) -> None :
0 commit comments