Skip to content

Commit 4e51dce

Browse files
betamoslucasfernog
andauthored
fix: dialog open supports multiple dirs, fixes #4091 (#4354)
Co-authored-by: Lucas Nogueira <lucas@tauri.studio>
1 parent f4bb30c commit 4e51dce

File tree

4 files changed

+75
-7
lines changed

4 files changed

+75
-7
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'api': patch
3+
'tauri': patch
4+
---
5+
6+
Allow choosing multiple folders in `dialog.open`.

core/tauri/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ attohttpc = { version = "0.19", features = [ "json", "form" ], optional = true }
7979
open = { version = "3.0", optional = true }
8080
shared_child = { version = "1.0", optional = true }
8181
os_pipe = { version = "1.0", optional = true }
82-
rfd = { version = "0.8", optional = true }
82+
rfd = { version = "0.9", optional = true }
8383
raw-window-handle = "0.4.3"
8484
minisign-verify = { version = "0.2", optional = true }
8585
time = { version = "0.3", features = [ "parsing", "formatting" ], optional = true }

core/tauri/src/api/dialog.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,30 @@ pub mod blocking {
300300
response
301301
}
302302

303+
/// Shows the dialog to select multiple folders.
304+
/// This is a blocking operation,
305+
/// and should *NOT* be used when running on the main thread context.
306+
///
307+
/// # Examples
308+
///
309+
/// ```rust,no_run
310+
/// use tauri::api::dialog::blocking::FileDialogBuilder;
311+
/// #[tauri::command]
312+
/// fn my_command() {
313+
/// let folder_paths = FileDialogBuilder::new().pick_folders();
314+
/// // do something with the optional folder paths here
315+
/// // the folder paths value is `None` if the user closed the dialog
316+
/// }
317+
/// ```
318+
pub fn pick_folders(self) -> Option<Vec<PathBuf>> {
319+
#[allow(clippy::let_and_return)]
320+
let response = run_dialog_sync!(self.0.pick_folders());
321+
#[cfg(not(target_os = "linux"))]
322+
let response =
323+
response.map(|paths| paths.into_iter().map(|p| p.path().to_path_buf()).collect());
324+
response
325+
}
326+
303327
/// Shows the dialog to save a file.
304328
/// This is a blocking operation,
305329
/// and should *NOT* be used when running on the main thread context.
@@ -515,6 +539,32 @@ mod nonblocking {
515539
run_file_dialog!(self.0.pick_folder(), f)
516540
}
517541

542+
/// Shows the dialog to select multiple folders.
543+
/// This is not a blocking operation,
544+
/// and should be used when running on the main thread to avoid deadlocks with the event loop.
545+
///
546+
/// # Examples
547+
///
548+
/// ```rust,no_run
549+
/// use tauri::api::dialog::FileDialogBuilder;
550+
/// tauri::Builder::default()
551+
/// .build(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json"))
552+
/// .expect("failed to build tauri app")
553+
/// .run(|_app, _event| {
554+
/// FileDialogBuilder::new().pick_folders(|file_paths| {
555+
/// // do something with the optional folder paths here
556+
/// // the folder paths value is `None` if the user closed the dialog
557+
/// })
558+
/// })
559+
/// ```
560+
pub fn pick_folders<F: FnOnce(Option<Vec<PathBuf>>) + Send + 'static>(self, f: F) {
561+
#[cfg(not(target_os = "linux"))]
562+
let f = |paths: Option<Vec<rfd::FileHandle>>| {
563+
f(paths.map(|list| list.into_iter().map(|p| p.path().to_path_buf()).collect()))
564+
};
565+
run_file_dialog!(self.0.pick_folders(), f)
566+
}
567+
518568
/// Shows the dialog to save a file.
519569
///
520570
/// This is not a blocking operation,

core/tauri/src/endpoints/dialog.rs

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -182,13 +182,25 @@ impl Cmd {
182182
let scopes = context.window.state::<Scopes>();
183183

184184
let res = if options.directory {
185-
let folder = dialog_builder.pick_folder();
186-
if let Some(path) = &folder {
187-
scopes
188-
.allow_directory(path, options.recursive)
189-
.map_err(crate::error::into_anyhow)?;
185+
if options.multiple {
186+
let folders = dialog_builder.pick_folders();
187+
if let Some(folders) = &folders {
188+
for folder in folders {
189+
scopes
190+
.allow_directory(folder, options.recursive)
191+
.map_err(crate::error::into_anyhow)?;
192+
}
193+
}
194+
folders.into()
195+
} else {
196+
let folder = dialog_builder.pick_folder();
197+
if let Some(path) = &folder {
198+
scopes
199+
.allow_directory(path, options.recursive)
200+
.map_err(crate::error::into_anyhow)?;
201+
}
202+
folder.into()
190203
}
191-
folder.into()
192204
} else if options.multiple {
193205
let files = dialog_builder.pick_files();
194206
if let Some(files) = &files {

0 commit comments

Comments
 (0)