tauri-sys/src/dialog.rs
Jonas Kruckenberg e638b5c289 wip
2022-11-17 19:03:46 +01:00

372 lines
12 KiB
Rust

//! 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<DialogFilter<'a>>,
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<dyn std::error::Error>> {
/// 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<dyn std::error::Error>> {
/// 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<dyn std::error::Error>> {
/// 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<dyn std::error::Error>> {
/// let _builder = FileDialogBuilder::new().add_filters(&[("Image", &["png", "jpeg"]),("Video", &["mp4"])]);
/// # Ok(())
/// # }
/// ```
pub fn add_filters(&mut self, filters: impl IntoIterator<Item = (&'a str, &'a [&'a str])>) {
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<dyn std::error::Error>> {
/// 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<Option<PathBuf>> {
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<dyn std::error::Error>> {
/// 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<Option<Vec<PathBuf>>> {
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<dyn std::error::Error>> {
/// 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<Option<PathBuf>> {
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<dyn std::error::Error>> {
/// 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<Option<Vec<PathBuf>>> {
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<dyn std::error::Error>> {
/// 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<Option<PathBuf>> {
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<dyn std::error::Error>> {
/// 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<dyn std::error::Error>> {
/// 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<dyn std::error::Error>> {
/// 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<dyn std::error::Error>> {
/// 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<bool> {
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<dyn std::error::Error>> {
/// 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<bool> {
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<JsValue, JsValue>;
#[wasm_bindgen(catch)]
pub async fn confirm(message: &str, options: JsValue) -> Result<JsValue, JsValue>;
#[wasm_bindgen(catch)]
pub async fn open(options: JsValue) -> Result<JsValue, JsValue>;
#[wasm_bindgen(catch)]
pub async fn message(message: &str, option: JsValue) -> Result<(), JsValue>;
#[wasm_bindgen(catch)]
pub async fn save(options: JsValue) -> Result<JsValue, JsValue>;
}
}