From 4e51dce6ca21c7664de779bc78a04be1051371f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Didrik=20Nordstr=C3=B6m?= Date: Wed, 15 Jun 2022 15:24:28 -0700 Subject: [PATCH] fix: dialog open supports multiple dirs, fixes #4091 (#4354) Co-authored-by: Lucas Nogueira --- .changes/dialog-multiple-folders.md | 6 ++++ core/tauri/Cargo.toml | 2 +- core/tauri/src/api/dialog.rs | 50 +++++++++++++++++++++++++++++ core/tauri/src/endpoints/dialog.rs | 24 ++++++++++---- 4 files changed, 75 insertions(+), 7 deletions(-) create mode 100644 .changes/dialog-multiple-folders.md diff --git a/.changes/dialog-multiple-folders.md b/.changes/dialog-multiple-folders.md new file mode 100644 index 000000000..e82db4fd4 --- /dev/null +++ b/.changes/dialog-multiple-folders.md @@ -0,0 +1,6 @@ +--- +'api': patch +'tauri': patch +--- + +Allow choosing multiple folders in `dialog.open`. \ No newline at end of file diff --git a/core/tauri/Cargo.toml b/core/tauri/Cargo.toml index 620e39272..be73c7b23 100644 --- a/core/tauri/Cargo.toml +++ b/core/tauri/Cargo.toml @@ -79,7 +79,7 @@ attohttpc = { version = "0.19", features = [ "json", "form" ], optional = true } open = { version = "3.0", optional = true } shared_child = { version = "1.0", optional = true } os_pipe = { version = "1.0", optional = true } -rfd = { version = "0.8", optional = true } +rfd = { version = "0.9", optional = true } raw-window-handle = "0.4.3" minisign-verify = { version = "0.2", optional = true } time = { version = "0.3", features = [ "parsing", "formatting" ], optional = true } diff --git a/core/tauri/src/api/dialog.rs b/core/tauri/src/api/dialog.rs index d45f35e31..2ebe1f552 100644 --- a/core/tauri/src/api/dialog.rs +++ b/core/tauri/src/api/dialog.rs @@ -300,6 +300,30 @@ pub mod blocking { response } + /// Shows the dialog to select multiple folders. + /// This is a blocking operation, + /// and should *NOT* be used when running on the main thread context. + /// + /// # Examples + /// + /// ```rust,no_run + /// use tauri::api::dialog::blocking::FileDialogBuilder; + /// #[tauri::command] + /// fn my_command() { + /// let folder_paths = FileDialogBuilder::new().pick_folders(); + /// // do something with the optional folder paths here + /// // the folder paths value is `None` if the user closed the dialog + /// } + /// ``` + pub fn pick_folders(self) -> Option> { + #[allow(clippy::let_and_return)] + let response = run_dialog_sync!(self.0.pick_folders()); + #[cfg(not(target_os = "linux"))] + let response = + response.map(|paths| paths.into_iter().map(|p| p.path().to_path_buf()).collect()); + response + } + /// Shows the dialog to save a file. /// This is a blocking operation, /// and should *NOT* be used when running on the main thread context. @@ -515,6 +539,32 @@ mod nonblocking { run_file_dialog!(self.0.pick_folder(), f) } + /// Shows the dialog to select multiple folders. + /// This is not a blocking operation, + /// and should be used when running on the main thread to avoid deadlocks with the event loop. + /// + /// # Examples + /// + /// ```rust,no_run + /// use tauri::api::dialog::FileDialogBuilder; + /// tauri::Builder::default() + /// .build(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json")) + /// .expect("failed to build tauri app") + /// .run(|_app, _event| { + /// FileDialogBuilder::new().pick_folders(|file_paths| { + /// // do something with the optional folder paths here + /// // the folder paths value is `None` if the user closed the dialog + /// }) + /// }) + /// ``` + pub fn pick_folders>) + Send + 'static>(self, f: F) { + #[cfg(not(target_os = "linux"))] + let f = |paths: Option>| { + f(paths.map(|list| list.into_iter().map(|p| p.path().to_path_buf()).collect())) + }; + run_file_dialog!(self.0.pick_folders(), f) + } + /// Shows the dialog to save a file. /// /// This is not a blocking operation, diff --git a/core/tauri/src/endpoints/dialog.rs b/core/tauri/src/endpoints/dialog.rs index 96cb36d97..170b65f04 100644 --- a/core/tauri/src/endpoints/dialog.rs +++ b/core/tauri/src/endpoints/dialog.rs @@ -182,13 +182,25 @@ impl Cmd { let scopes = context.window.state::(); let res = if options.directory { - let folder = dialog_builder.pick_folder(); - if let Some(path) = &folder { - scopes - .allow_directory(path, options.recursive) - .map_err(crate::error::into_anyhow)?; + if options.multiple { + let folders = dialog_builder.pick_folders(); + if let Some(folders) = &folders { + for folder in folders { + scopes + .allow_directory(folder, options.recursive) + .map_err(crate::error::into_anyhow)?; + } + } + folders.into() + } else { + let folder = dialog_builder.pick_folder(); + if let Some(path) = &folder { + scopes + .allow_directory(path, options.recursive) + .map_err(crate::error::into_anyhow)?; + } + folder.into() } - folder.into() } else if options.multiple { let files = dialog_builder.pick_files(); if let Some(files) = &files {