@@ -17,10 +17,10 @@ enum FilePickerEvent {
17
17
}
18
18
19
19
struct MessageDialogOptions : Decodable {
20
- let title : String ?
20
+ var title : String ?
21
21
let message : String
22
- let okButtonLabel : String ?
23
- let cancelButtonLabel : String ?
22
+ var okButtonLabel : String ?
23
+ var cancelButtonLabel : String ?
24
24
}
25
25
26
26
struct Filter : Decodable {
@@ -30,13 +30,18 @@ struct Filter: Decodable {
30
30
struct FilePickerOptions : Decodable {
31
31
var multiple : Bool ?
32
32
var filters : [ Filter ] ?
33
+ var defaultPath : String ?
34
+ }
35
+
36
+ struct SaveFileDialogOptions : Decodable {
37
+ var fileName : String ?
38
+ var defaultPath : String ?
33
39
}
34
40
35
41
class DialogPlugin : Plugin {
36
42
37
43
var filePickerController : FilePickerController !
38
- var pendingInvoke : Invoke ? = nil
39
- var pendingInvokeArgs : FilePickerOptions ? = nil
44
+ var onFilePickerResult : ( ( FilePickerEvent ) -> Void ) ? = nil
40
45
41
46
override init ( ) {
42
47
super. init ( )
@@ -66,8 +71,16 @@ class DialogPlugin: Plugin {
66
71
}
67
72
}
68
73
69
- pendingInvoke = invoke
70
- pendingInvokeArgs = args
74
+ onFilePickerResult = { ( event: FilePickerEvent ) -> Void in
75
+ switch event {
76
+ case . selected( let urls) :
77
+ invoke. resolve ( [ " files " : urls] )
78
+ case . cancelled:
79
+ invoke. resolve ( [ " files " : nil ] )
80
+ case . error( let error) :
81
+ invoke. reject ( error)
82
+ }
83
+ }
71
84
72
85
if uniqueMimeType == true || isMedia {
73
86
DispatchQueue . main. async {
@@ -104,6 +117,9 @@ class DialogPlugin: Plugin {
104
117
let documentTypes = parsedTypes. isEmpty ? [ " public.data " ] : parsedTypes
105
118
DispatchQueue . main. async {
106
119
let picker = UIDocumentPickerViewController ( documentTypes: documentTypes, in: . import)
120
+ if let defaultPath = args. defaultPath {
121
+ picker. directoryURL = URL ( string: defaultPath)
122
+ }
107
123
picker. delegate = self . filePickerController
108
124
picker. allowsMultipleSelection = args. multiple ?? false
109
125
picker. modalPresentationStyle = . fullScreen
@@ -112,6 +128,46 @@ class DialogPlugin: Plugin {
112
128
}
113
129
}
114
130
131
+ @objc public func saveFileDialog( _ invoke: Invoke ) throws {
132
+ let args = try invoke. parseArgs ( SaveFileDialogOptions . self)
133
+
134
+ // The Tauri save dialog API prompts the user to select a path where a file must be saved
135
+ // This behavior maps to the operating system interfaces on all platforms except iOS,
136
+ // which only exposes a mechanism to "move file `srcPath` to a location defined by the user"
137
+ //
138
+ // so we have to work around it by creating an empty file matching the requested `args.fileName`,
139
+ // and using it as `srcPath` for the operation - returning the path the user selected
140
+ // so the app dev can write to it later - matching cross platform behavior as mentioned above
141
+ let fileManager = FileManager . default
142
+ let srcFolder = fileManager. urls ( for: . documentDirectory, in: . userDomainMask) . first!
143
+ let srcPath = srcFolder. appendingPathComponent ( args. fileName ?? " file " )
144
+ if !fileManager. fileExists ( atPath: srcPath. path) {
145
+ // the file contents must be actually provided by the tauri dev after the path is resolved by the save API
146
+ try " " . write ( to: srcPath, atomically: true , encoding: . utf8)
147
+ }
148
+
149
+ onFilePickerResult = { ( event: FilePickerEvent ) -> Void in
150
+ switch event {
151
+ case . selected( let urls) :
152
+ invoke. resolve ( [ " file " : urls. first!] )
153
+ case . cancelled:
154
+ invoke. resolve ( [ " file " : nil ] )
155
+ case . error( let error) :
156
+ invoke. reject ( error)
157
+ }
158
+ }
159
+
160
+ DispatchQueue . main. async {
161
+ let picker = UIDocumentPickerViewController ( url: srcPath, in: . exportToService)
162
+ if let defaultPath = args. defaultPath {
163
+ picker. directoryURL = URL ( string: defaultPath)
164
+ }
165
+ picker. delegate = self . filePickerController
166
+ picker. modalPresentationStyle = . fullScreen
167
+ self . presentViewController ( picker)
168
+ }
169
+ }
170
+
115
171
private func presentViewController( _ viewControllerToPresent: UIViewController ) {
116
172
self . manager. viewController? . present ( viewControllerToPresent, animated: true , completion: nil )
117
173
}
@@ -133,14 +189,7 @@ class DialogPlugin: Plugin {
133
189
}
134
190
135
191
public func onFilePickerEvent( _ event: FilePickerEvent ) {
136
- switch event {
137
- case . selected( let urls) :
138
- pendingInvoke? . resolve ( [ " files " : urls] )
139
- case . cancelled:
140
- pendingInvoke? . resolve ( [ " files " : nil ] )
141
- case . error( let error) :
142
- pendingInvoke? . reject ( error)
143
- }
192
+ self . onFilePickerResult ? ( event)
144
193
}
145
194
146
195
@objc public func showMessageDialog( _ invoke: Invoke ) throws {
0 commit comments