use serde::Serialize; use std::path::{Path, PathBuf}; #[derive(Debug, Serialize)] struct DialogFilter<'a> { extensions: &'a [&'a str], name: &'a str, } #[derive(Debug, Default, 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(()) /// # } /// ``` 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(()) /// # } /// ``` 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(()) /// # } /// ``` 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(()) /// # } /// ``` 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(()) /// # } /// ``` 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)?) } } #[derive(Debug, Default)] pub enum MessageDialogType { #[default] Info, Warning, Error, } impl Serialize for MessageDialogType { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { match self { MessageDialogType::Info => serializer.serialize_str("info"), MessageDialogType::Warning => serializer.serialize_str("warning"), MessageDialogType::Error => serializer.serialize_str("error"), } } } #[derive(Debug, Default, Serialize)] pub struct MessageDialogBuilder<'a> { title: Option<&'a str>, r#type: MessageDialogType, } 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,MessageDialogType}; /// /// # fn main() -> Result<(), Box> { /// let _builder = MessageDialogBuilder::new().set_type(MessageDialogType::Error); /// # Ok(()) /// # } /// ``` pub fn set_type(&mut self, r#type: MessageDialogType) { self.r#type = r#type; } /// 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(()) /// # } /// ``` 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(()) /// # } /// ``` 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(()) /// # } /// ``` 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)?) } } // //! User interaction with the file system using dialog boxes. // //! // //! # Example // //! // //! ```rust,no_run // //! use tauri_api::dialog::open; // //! // //! let path = open(None).await; // //! ``` // use serde::Serialize; // use std::path::PathBuf; // /// Extension filter for the file dialog. // /// // /// # Example // /// // /// ```rust,no_run // /// let filter = DialogFilter { // /// extension: vec![".jpg", ".jpeg", ".png", ".bmp"], // /// name: "images", // /// }; // /// ``` // #[derive(Serialize)] // pub struct DialogFilter { // /// Extensions to filter, without a `.` prefix. // pub extensions: Vec, // /// Filter name // pub name: String, // } // /// Types of a [`message`] dialog. // #[derive(Serialize)] // pub enum MessageDialogType { // Error, // Info, // Warning, // } // /// Options for the [`message`] dialog. // #[derive(Serialize)] // pub struct MessageDialogOptions { // /// The title of the dialog. Defaults to the app name. // pub title: Option, // /// The type of the dialog. Defaults to MessageDialogType::Info. // #[serde(rename(serialize = "type"))] // pub kind: MessageDialogType, // } // impl MessageDialogOptions { // /// Creates a new `MessageDialogOptions` with sensible default values. // pub fn new() -> Self { // Self { // title: None, // kind: MessageDialogType::Info, // } // } // } // /// Options for an [`open`] dialog. // #[derive(Serialize)] // pub struct OpenDialogOptions { // /// Initial directory or file path. // #[serde(rename(serialize = "defaultPath"))] // pub default_path: Option, // /// Whether the dialog is a directory selection or not. // pub directory: bool, // /// The filters of the dialog. // pub filters: Vec, // /// Whether the dialog allows multiple selection or not. // pub multiple: bool, // /// If `directory` is `true`, indicatees that it will be read recursivley later. // /// Defines whether subdirectories will be allowed on the scope or not. // pub recursive: bool, // /// The title of the dialog window. // pub title: Option, // } // impl OpenDialogOptions { // /// Creates a new `OpenDialogOptions` with sensible default values. // pub fn new() -> Self { // Self { // default_path: None, // directory: false, // filters: Vec::new(), // multiple: false, // recursive: false, // title: None, // } // } // } // /// Options for the save dialog. // #[derive(Serialize)] // pub struct SaveDialogOptions { // /// Initial directory of the file path. // /// If it's not a directory path, the dialog interface will change to that folder. // /// If it's not an existing directory, the file name will be set to the dialog's // /// file name input and the dialog will be set to the parent folder. // #[serde(rename(serialize = "defaultPath"))] // pub default_path: Option, // /// The filters of the dialog. // pub filters: Vec, // /// The title of the dialog window. // pub title: Option, // } // impl SaveDialogOptions { // /// Creates a new `SaveDialogOptions` with sensible default values. // pub fn new() -> Self { // Self { // default_path: None, // filters: Vec::new(), // title: None, // } // } // } // /// Show a question dialog with `Yes` and `No` buttons. // /// // /// # Example // /// // /// ```rust,no_run // /// use tauri_api::dialog::{ask, MessageDialogOptions}; // /// // /// let yes = ask("Are you sure?", None).await; // /// ``` // /// @param message Message to display. // /// @param options Dialog options. // /// @returns Whether the user selected `Yes` or `No`. // #[inline(always)] // pub async fn ask(message: &str, options: Option) -> crate::Result { // let js_val = inner::ask(message, serde_wasm_bindgen::to_value(&options)?).await?; // Ok(serde_wasm_bindgen::from_value(js_val)?) // } // /// Shows a question dialog with `Ok` and `Cancel` buttons. // /// // /// # Example // /// // /// ```rust,no_run // /// use tauri_api::dialog::{confirm, MessageDialogOptions}; // /// // /// let confirmed = confirm("Are you sure?", None).await; // /// ``` // /// @returns Whether the user selelced `Ok` or `Cancel`. // pub async fn confirm(message: &str, options: Option) -> crate::Result { // let js_val = inner::confirm(message, serde_wasm_bindgen::to_value(&options)?).await?; // Ok(serde_wasm_bindgen::from_value(js_val)?) // } // /// Shows a message dialog with an `Ok` button. // /// // /// # Example // /// // /// ```rust,no_run // /// use tauri_api::dialog::{message, MessageDialogOptions}; // /// // /// message("Tauri is awesome", None).await; // /// ``` // /// @param message Message to display. // /// @param options Dialog options. // /// @returns Promise resolved when user closes the dialog. // pub async fn message(message: &str, options: Option) -> crate::Result<()> { // Ok(inner::message(message, serde_wasm_bindgen::to_value(&options)?).await?) // } // /// Opens a file/directory selection dialog for a single file. // /// `multiple` field of [`options`](OpenDialogOptions) must be `false`, if provided. // /// // /// The selected paths are added to the filesystem and asset protocol allowlist scopes. // /// When security is mroe important than the ease of use of this API, // /// prefer writing a dedicated command instead. // /// // /// Note that the allowlist scope change is not persisited, // /// so the values are cleared when the applicaiton is restarted. // /// You can save it to the filessytem using the [tauri-plugin-persisted-scope](https://github.com/tauri-apps/tauri-plugin-persisted-scope). // /// // /// # Example // /// // /// ```rust,no_run // /// use tauri_api::dialog::{open, OpenDialogOptions}; // /// // /// let file = open(None).await; // /// // /// let mut opts = OpenDialogOptions::new(); // /// opts.directory = true; // /// let dir = open(Some(opts)).await; // /// ``` // /// @param options Dialog options. // /// @returns List of file paths, or `None` if user cancelled the dialog. // pub async fn open(options: Option) -> crate::Result> { // let file = inner::open(serde_wasm_bindgen::to_value(&options)?).await?; // Ok(serde_wasm_bindgen::from_value(file)?) // } // /// Opens a file/directory selection dialog for multiple files. // /// `multiple` field of [`options`](OpenDialogOptions) must be `true`, if provided. // /// // /// The selected paths are added to the filesystem and asset protocol allowlist scopes. // /// When security is mroe important than the ease of use of this API, // /// prefer writing a dedicated command instead. // /// // /// Note that the allowlist scope change is not persisited, // /// so the values are cleared when the applicaiton is restarted. // /// You can save it to the filessytem using the [tauri-plugin-persisted-scope](https://github.com/tauri-apps/tauri-plugin-persisted-scope). // /// // /// # Example // /// // /// ```rust,no_run // /// use tauri_api::dialog::{open, OpenDialogOptions}; // /// // /// let files = open_multiple(None).await; // /// // /// let mut opts = OpenDialogOptions::new(); // /// opts.multiple = true; // /// opts.directory = true; // /// let dirs = open(Some(opts)).await; // /// ``` // /// @param options Dialog options. // /// @returns List of file paths, or `None` if user cancelled the dialog. // pub async fn open_multiple( // options: Option, // ) -> crate::Result>> { // let files = inner::open_multiple(serde_wasm_bindgen::to_value(&options)?).await?; // Ok(serde_wasm_bindgen::from_value(files)?) // } // /// Opens a file/directory save dialog. // /// // /// The selected paths are added to the filesystem and asset protocol allowlist scopes. // /// When security is mroe important than the ease of use of this API, // /// prefer writing a dedicated command instead. // /// // /// Note that the allowlist scope change is not persisited, // /// so the values are cleared when the applicaiton is restarted. // /// You can save it to the filessytem using the [tauri-plugin-persisted-scope](https://github.com/tauri-apps/tauri-plugin-persisted-scope). // /// // /// # Example // /// // /// ```rust,no_run // /// use tauri_api::dialog::{save, SaveDialogOptions}; // /// // /// let file = save(None).await; // /// ``` // /// @param options Dialog options. // /// @returns File path, or `None` if user cancelled the dialog. // pub async fn save(options: Option) -> crate::Result> { // let path = inner::save(serde_wasm_bindgen::to_value(&options)?).await?; // Ok(serde_wasm_bindgen::from_value(path)?) // } 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; } }