//! Native system dialogs for opening and saving files. //! //! The APIs must be added to tauri.allowlist.dialog in tauri.conf.json: //! ```json //! { //! "tauri": { //! "allowlist": { //! "dialog": { //! "all": true, // enable all dialog APIs //! "open": true, // enable file open API //! "save": true // enable file save API //! "message": true, //! "ask": true, //! "confirm": true //! } //! } //! } //! } //! ``` //! It is recommended to allowlist only the APIs you use for optimal bundle size and security. use serde::Serialize; use std::path::{Path, PathBuf}; #[derive(Debug, Clone, Copy, Hash, Serialize)] struct DialogFilter<'a> { extensions: &'a [&'a str], name: &'a str, } /// The file dialog builder. /// /// Constructs file picker dialogs that can select single/multiple files or directories. #[derive(Debug, Default, Clone, Hash, Serialize)] #[serde(rename = "camelCase")] pub struct FileDialogBuilder<'a> { default_path: Option<&'a Path>, filters: Vec>, title: Option<&'a str>, directory: bool, multiple: bool, recursive: bool, } impl<'a> FileDialogBuilder<'a> { /// Gets the default file dialog builder. pub fn new() -> Self { Self::default() } /// Set starting file name or directory of the dialog. pub fn set_default_path(&mut self, default_path: &'a Path) { self.default_path = Some(default_path); } /// If directory is true, indicates that it will be read recursively later. /// Defines whether subdirectories will be allowed on the scope or not. /// /// # Example /// /// ```rust /// use tauri_sys::dialog::FileDialogBuilder; /// /// # fn main() -> Result<(), Box> { /// let _builder = FileDialogBuilder::new().set_recursive(true); /// # Ok(()) /// # } /// ``` pub fn set_recursive(&mut self, recursive: bool) { self.recursive = recursive; } /// Set the title of the dialog. /// /// # Example /// /// ```rust /// use tauri_sys::dialog::FileDialogBuilder; /// /// # fn main() -> Result<(), Box> { /// let _builder = FileDialogBuilder::new().set_title("Test Title"); /// # Ok(()) /// # } /// ``` pub fn set_title(&mut self, title: &'a str) { self.title = Some(title); } /// Add file extension filter. Takes in the name of the filter, and list of extensions /// /// # Example /// /// ```rust /// use tauri_sys::dialog::FileDialogBuilder; /// /// # fn main() -> Result<(), Box> { /// let _builder = FileDialogBuilder::new().add_filter("Image", &["png", "jpeg"]); /// # Ok(()) /// # } /// ``` pub fn add_filter(&mut self, name: &'a str, extensions: &'a [&'a str]) { self.filters.push(DialogFilter { name, extensions }); } /// Add many file extension filters. /// /// # Example /// /// ```rust /// use tauri_sys::dialog::FileDialogBuilder; /// /// # fn main() -> Result<(), Box> { /// let _builder = FileDialogBuilder::new().add_filters(&[("Image", &["png", "jpeg"]),("Video", &["mp4"])]); /// # Ok(()) /// # } /// ``` pub fn add_filters(&mut self, filters: impl IntoIterator) { for (name, extensions) in filters.into_iter() { self.filters.push(DialogFilter { name: name.as_ref(), extensions, }); } } /// Shows the dialog to select a single file. /// /// # Example /// /// ```rust,no_run /// use tauri_sys::dialog::FileDialogBuilder; /// /// # async fn main() -> Result<(), Box> { /// let file = FileDialogBuilder::new().pick_file().await?; /// # Ok(()) /// # } /// ``` /// /// Requires [`allowlist > dialog > open`](https://tauri.app/v1/api/config#dialogallowlistconfig.open) to be enabled. pub async fn pick_file(self) -> crate::Result> { let raw = inner::open(serde_wasm_bindgen::to_value(&self)?).await?; Ok(serde_wasm_bindgen::from_value(raw)?) } /// Shows the dialog to select multiple files. /// /// # Example /// /// ```rust,no_run /// use tauri_sys::dialog::FileDialogBuilder; /// /// # async fn main() -> Result<(), Box> { /// let files = FileDialogBuilder::new().pick_files().await?; /// # Ok(()) /// # } /// ``` /// /// Requires [`allowlist > dialog > open`](https://tauri.app/v1/api/config#dialogallowlistconfig.open) to be enabled. pub async fn pick_files(mut self) -> crate::Result>> { self.multiple = true; let raw = inner::open(serde_wasm_bindgen::to_value(&self)?).await?; Ok(serde_wasm_bindgen::from_value(raw)?) } /// Shows the dialog to select a single folder. /// /// # Example /// /// ```rust,no_run /// use tauri_sys::dialog::FileDialogBuilder; /// /// # async fn main() -> Result<(), Box> { /// let files = FileDialogBuilder::new().pick_folder().await?; /// # Ok(()) /// # } /// ``` /// /// Requires [`allowlist > dialog > open`](https://tauri.app/v1/api/config#dialogallowlistconfig.open) to be enabled. pub async fn pick_folder(mut self) -> crate::Result> { self.directory = true; let raw = inner::open(serde_wasm_bindgen::to_value(&self)?).await?; Ok(serde_wasm_bindgen::from_value(raw)?) } /// Shows the dialog to select multiple folders. /// /// # Example /// /// ```rust,no_run /// use tauri_sys::dialog::FileDialogBuilder; /// /// # async fn main() -> Result<(), Box> { /// let files = FileDialogBuilder::new().pick_folders().await?; /// # Ok(()) /// # } /// ``` /// /// Requires [`allowlist > dialog > open`](https://tauri.app/v1/api/config#dialogallowlistconfig.open) to be enabled. pub async fn pick_folders(mut self) -> crate::Result>> { self.directory = true; self.multiple = true; let raw = inner::open(serde_wasm_bindgen::to_value(&self)?).await?; Ok(serde_wasm_bindgen::from_value(raw)?) } /// Open a file/directory save dialog. /// /// The selected path is added to the filesystem and asset protocol allowlist scopes. /// When security is more important than the easy of use of this API, prefer writing a dedicated command instead. /// /// Note that the allowlist scope change is not persisted, so the values are cleared when the application is restarted. /// You can save it to the filesystem using tauri-plugin-persisted-scope. /// /// # Example /// /// ```rust,no_run /// use tauri_sys::dialog::FileDialogBuilder; /// /// # async fn main() -> Result<(), Box> { /// let file = FileDialogBuilder::new().save().await?; /// # Ok(()) /// # } /// ``` /// /// Requires [`allowlist > dialog > save`](https://tauri.app/v1/api/config#dialogallowlistconfig.save) to be enabled. pub async fn save(self) -> crate::Result> { let raw = inner::save(serde_wasm_bindgen::to_value(&self)?).await?; Ok(serde_wasm_bindgen::from_value(raw)?) } } /// Types of message, ask and confirm dialogs. #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)] pub enum MessageDialogKind { #[default] #[serde(rename = "info")] Info, #[serde(rename = "warning")] Warning, #[serde(rename = "error")] Error, } /// A builder for message dialogs. #[derive(Debug, Default, Clone, Copy, Hash, Serialize)] pub struct MessageDialogBuilder<'a> { title: Option<&'a str>, #[serde(rename = "type")] kind: MessageDialogKind, } impl<'a> MessageDialogBuilder<'a> { pub fn new() -> Self { Self::default() } /// Set the title of the dialog. /// /// # Example /// /// ```rust /// use tauri_sys::dialog::MessageDialogBuilder; /// /// # fn main() -> Result<(), Box> { /// let _builder = MessageDialogBuilder::new().set_title("Test Title"); /// # Ok(()) /// # } /// ``` pub fn set_title(&mut self, title: &'a str) { self.title = Some(title); } /// Set the type of the dialog. /// /// # Example /// /// ```rust /// use tauri_sys::dialog::{MessageDialogBuilder,MessageDialogKind}; /// /// # fn main() -> Result<(), Box> { /// let _builder = MessageDialogBuilder::new().set_kind(MessageDialogKind::Error); /// # Ok(()) /// # } /// ``` pub fn set_kind(&mut self, kind: MessageDialogKind) { self.kind = r#kind; } /// Shows a message dialog with an `Ok` button. /// /// # Example /// /// ```rust,no_run /// use tauri_sys::dialog::MessageDialogBuilder; /// /// # async fn main() -> Result<(), Box> { /// let file = MessageDialogBuilder::new().message("Tauri is awesome").await?; /// # Ok(()) /// # } /// ``` /// /// Requires [`allowlist > dialog > message`](https://tauri.app/v1/api/config#dialogallowlistconfig.message) to be enabled. pub async fn message(self, message: &str) -> crate::Result<()> { Ok(inner::message(message, serde_wasm_bindgen::to_value(&self)?).await?) } /// Shows a question dialog with `Yes` and `No` buttons. /// /// # Example /// /// ```rust,no_run /// use tauri_sys::dialog::MessageDialogBuilder; /// /// # async fn main() -> Result<(), Box> { /// let confirmation = MessageDialogBuilder::new().ask("Are you sure?").await?; /// # Ok(()) /// # } /// ``` /// /// Requires [`allowlist > dialog > ask`](https://tauri.app/v1/api/config#dialogallowlistconfig.ask) to be enabled. pub async fn ask(self, message: &str) -> crate::Result { let raw = inner::ask(message, serde_wasm_bindgen::to_value(&self)?).await?; Ok(serde_wasm_bindgen::from_value(raw)?) } /// Shows a question dialog with `Ok` and `Cancel` buttons. /// /// # Example /// /// ```rust,no_run /// use tauri_sys::dialog::MessageDialogBuilder; /// /// # async fn main() -> Result<(), Box> { /// let confirmation = MessageDialogBuilder::new().confirm("Are you sure?").await?; /// # Ok(()) /// # } /// ``` /// /// Requires [`allowlist > dialog > confirm`](https://tauri.app/v1/api/config#dialogallowlistconfig.confirm) to be enabled. pub async fn confirm(self, message: &str) -> crate::Result { let raw = inner::confirm(message, serde_wasm_bindgen::to_value(&self)?).await?; Ok(serde_wasm_bindgen::from_value(raw)?) } } mod inner { use wasm_bindgen::{prelude::wasm_bindgen, JsValue}; #[wasm_bindgen(module = "/src/dialog.js")] extern "C" { #[wasm_bindgen(catch)] pub async fn ask(message: &str, options: JsValue) -> Result; #[wasm_bindgen(catch)] pub async fn confirm(message: &str, options: JsValue) -> Result; #[wasm_bindgen(catch)] pub async fn open(options: JsValue) -> Result; #[wasm_bindgen(catch)] pub async fn message(message: &str, option: JsValue) -> Result<(), JsValue>; #[wasm_bindgen(catch)] pub async fn save(options: JsValue) -> Result; } }