@@ -38,7 +38,7 @@ static func compare_svg_to_disk_contents(idx := -1) -> FileState:
3838static func _save_svg_with_custom_final_callback (final_callback : Callable ) -> void :
3939 var active_tab := Configs .savedata .get_active_tab ()
4040 var file_path := active_tab .svg_file_path
41- if not file_path .is_empty () and FileAccess .file_exists (file_path ):
41+ if not file_path .is_empty () and ( FileAccess .file_exists (file_path ) or OS . has_feature ( "web" ) ):
4242 active_tab .save_to_bound_path ()
4343 if final_callback .is_valid ():
4444 final_callback .call ()
@@ -62,7 +62,8 @@ static func open_export_dialog(export_data: ImageExportData, final_callback := C
6262 buffer = ImageExportData .svg_to_buffer ()
6363 else :
6464 buffer = export_data .image_to_buffer (export_data .generate_image ())
65- _web_save (buffer , ImageExportData .image_types_dict [export_data .format ])
65+
66+ web_save (buffer , export_data .format , true , true )
6667 if final_callback .is_valid ():
6768 final_callback .call ()
6869 else :
@@ -96,7 +97,7 @@ static func open_export_dialog(export_data: ImageExportData, final_callback := C
9697static func open_xml_export_dialog (xml : String , file_name : String ) -> void :
9798 OS .request_permissions ()
9899 if OS .has_feature ("web" ):
99- _web_save (xml .to_utf8_buffer (), "application/ xml" )
100+ web_quick_save (xml .to_utf8_buffer (), "xml" )
100101 else :
101102 if _is_native_preferred ():
102103 var native_callback := \
@@ -472,6 +473,7 @@ static func _close_tabs_internal(indices: Array[int]) -> void:
472473static var _change_callback : JavaScriptObject
473474static var _cancel_callback : JavaScriptObject
474475static var _file_load_callbacks : Array [JavaScriptObject ] = []
476+ static var _file_save_callback : JavaScriptObject
475477
476478static var _web_file_data_cache : Dictionary [String , Variant ] = {}
477479
@@ -639,9 +641,121 @@ static func _web_on_file_dialog_canceled(_args: Array) -> void:
639641 var window = JavaScriptBridge .get_interface ("window" )
640642 window .godsvgDialogClosed = true
641643
644+ static func _web_on_file_saved (args : Array ) -> void :
645+ var file_handle : JavaScriptObject = args [0 ]
646+ var quick_export : bool = args [1 ]
647+
648+ if not quick_export :
649+ var active_tab := Configs .savedata .get_active_tab ()
650+ active_tab .svg_file_path = file_handle .name
651+ active_tab .marked_unsaved = false
652+ else :
653+ HandlerGUI .remove_all_menus ()
642654
643- static func _web_save (buffer : PackedByteArray , format_name : String ) -> void :
644- var file_name := Utils .get_file_name (Configs .savedata .get_active_tab ().svg_file_path )
655+ ## Currently used for SVG exports on web.
656+ static func web_quick_save (buffer : PackedByteArray , format : String ) -> void :
657+ var active_tab := Configs .savedata .get_active_tab ()
658+ var file_name := Utils .get_file_name (active_tab .svg_file_path )
659+ var format_type := ImageExportData .image_types_dict [format ]
645660 if file_name .is_empty ():
646- file_name = "export"
647- JavaScriptBridge .download_buffer (buffer , file_name , format_name )
661+ file_name = "export." + format
662+ JavaScriptBridge .download_buffer (buffer , file_name , format_type )
663+
664+ ## Currently used for saving on web. [br]
665+ ## [param format]: The file format ("svg", etc) [br]
666+ ## [param ignore_handles]: Doesn't write/read to existing file handles, makes new ones instead. [br]
667+ ## [param quick_export]: Toggle on for exports
668+ static func web_save (buffer : PackedByteArray , format : String , ignore_handles : bool = false , quick_export : bool = false ) -> void :
669+ var window := JavaScriptBridge .get_interface ("window" )
670+
671+ var active_tab := Configs .savedata .get_active_tab ()
672+ var file_path = active_tab .svg_file_path
673+ if file_path .is_empty ():
674+ if not active_tab .presented_name .is_empty () and not quick_export :
675+ file_path = active_tab .presented_name
676+ else :
677+ file_path = "export." + format
678+ var file_name := Utils .get_file_name (file_path )
679+ var format_type := ImageExportData .image_types_dict [format ]
680+
681+ # Currently, only Chromium-based browsers support the new file picker system.
682+ if window ["showSaveFilePicker" ] == null :
683+ JavaScriptBridge .download_buffer (buffer , file_name , format_type )
684+ return
685+
686+ # Initializing file handles.
687+ if window ["godsvgFileHandles" ] == null :
688+ window .godsvgFileHandles = JavaScriptBridge .create_object ("Object" )
689+
690+ # Creating our save function; Should probably be moved to the global context.
691+ JavaScriptBridge .eval ("""
692+ window.godsvgSaveFile = function(id, buffer, onSave, ignoreHandles=false, quickExport=false, options={} ) {
693+ const writeFile = (fileHandle, buff) => {
694+ return fileHandle.createWritable().then((writable) => {
695+ writable.write(buff).then(() => {
696+ onSave(fileHandle, quickExport);
697+ writable.close();
698+ });
699+ });
700+ };
701+
702+ if (quickExport) {
703+ ignoreHandles = true;
704+ }
705+
706+ if (window["godsvgFileHandles"] == undefined) {
707+ window.godsvgFileHandles = {}
708+ }
709+
710+ if (!ignoreHandles && id in window.godsvgFileHandles) {
711+ writeFile(window.godsvgFileHandles[id], buffer);
712+ } else {
713+ window.showSaveFilePicker(options).then((fileHandle) => {
714+ if (!ignoreHandles) window.godsvgFileHandles[id] = fileHandle;
715+ writeFile(fileHandle, buffer);
716+ }).catch((err) => {
717+ if ("SecurityError" in err) {
718+ /*
719+ TODO: Figure out what to do if the user saves without pressing something first.
720+ Popping up a file save dialog without user input is disallowed due to
721+ secure context mumbo jumbo.
722+ */
723+ console.error("Failed to save file due to the secure web context.");
724+ } else {
725+ console.error(err);
726+ }
727+ });
728+ }
729+ }
730+ """ )
731+
732+ # Options
733+ var picker_options := {
734+ "suggestedName" : file_name + "." + format ,
735+ "startIn" : "pictures" ,
736+ "types" : [
737+ {
738+ "description" : "Vector Image" if format == "svg" else "Image" ,
739+ "accept" : {
740+ format_type : ["." + format ],
741+ },
742+ },
743+ ],
744+ "excludeAcceptAllOption" : true ,
745+ "multiple" : false ,
746+ }
747+ JavaScriptBridge .eval ("""
748+ window.godsvgSavePickerOptions = %s ;
749+ """ % JSON .stringify (picker_options ))
750+
751+ # Buffer
752+ var array_buff = JavaScriptBridge .create_object ("ArrayBuffer" , buffer .size ())
753+ var buff = JavaScriptBridge .create_object ("Uint8Array" , array_buff )
754+ for i in len (buffer ):
755+ buff [i ] = buffer [i ]
756+
757+ # Saving
758+ # (godsvgCurrentlySaving is required in order to not accidentally save to the wrong tab)
759+ _file_save_callback = JavaScriptBridge .create_callback (_web_on_file_saved )
760+ window .godsvgCurrentlySaving = active_tab .id
761+ window .godsvgSaveFile (active_tab .id , buff , _file_save_callback , ignore_handles , quick_export , window .godsvgSavePickerOptions )
0 commit comments