Ver Fonte

feat(core): expose option to set dialog type, closes #4183 (#4187)

Lucas Fernandes Nogueira há 3 anos atrás
pai
commit
f46175d5d4

+ 6 - 0
.changes/dialog-type.md

@@ -0,0 +1,6 @@
+---
+"tauri": patch
+"api": patch
+---
+
+Expose option to set the dialog type.

Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
core/tauri/scripts/bundle.js


+ 133 - 2
core/tauri/src/api/dialog.rs

@@ -124,6 +124,95 @@ macro_rules! file_dialog_builder {
   };
 }
 
+macro_rules! message_dialog_builder {
+  () => {
+    /// A builder for message dialogs.
+    pub struct MessageDialogBuilder(rfd::MessageDialog);
+
+    impl MessageDialogBuilder {
+      /// Creates a new message dialog builder.
+      pub fn new(title: impl AsRef<str>, message: impl AsRef<str>) -> Self {
+        let title = title.as_ref().to_string();
+        let message = message.as_ref().to_string();
+        Self(
+          rfd::MessageDialog::new()
+            .set_title(&title)
+            .set_description(&message),
+        )
+      }
+
+      /// Set parent windows explicitly (optional)
+      ///
+      /// ## Platform-specific
+      ///
+      /// - **Linux:** Unsupported.
+      pub fn parent<W: raw_window_handle::HasRawWindowHandle>(mut self, parent: &W) -> Self {
+        self.0 = self.0.set_parent(parent);
+        self
+      }
+
+      /// Set the set of button that will be displayed on the dialog.
+      pub fn buttons(mut self, buttons: MessageDialogButtons) -> Self {
+        self.0 = self.0.set_buttons(buttons.into());
+        self
+      }
+
+      /// Set type of a dialog.
+      ///
+      /// Depending on the system it can result in type specific icon to show up,
+      /// the will inform user it message is a error, warning or just information.
+      pub fn kind(mut self, kind: MessageDialogKind) -> Self {
+        self.0 = self.0.set_level(kind.into());
+        self
+      }
+    }
+  };
+}
+
+/// Options for action buttons on message dialogs.
+#[non_exhaustive]
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+pub enum MessageDialogButtons {
+  /// Ok button.
+  Ok,
+  /// Ok and Cancel buttons.
+  OkCancel,
+  /// Yes and No buttons.
+  YesNo,
+}
+
+impl From<MessageDialogButtons> for rfd::MessageButtons {
+  fn from(kind: MessageDialogButtons) -> Self {
+    match kind {
+      MessageDialogButtons::Ok => Self::Ok,
+      MessageDialogButtons::OkCancel => Self::OkCancel,
+      MessageDialogButtons::YesNo => Self::YesNo,
+    }
+  }
+}
+
+/// Types of message, ask and confirm dialogs.
+#[non_exhaustive]
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+pub enum MessageDialogKind {
+  /// Information dialog.
+  Info,
+  /// Warning dialog.
+  Warning,
+  /// Error dialog.
+  Error,
+}
+
+impl From<MessageDialogKind> for rfd::MessageLevel {
+  fn from(kind: MessageDialogKind) -> Self {
+    match kind {
+      MessageDialogKind::Info => Self::Info,
+      MessageDialogKind::Warning => Self::Warning,
+      MessageDialogKind::Error => Self::Error,
+    }
+  }
+}
+
 /// Blocking interfaces for the dialog APIs.
 ///
 /// The blocking APIs will block the current thread to execute instead of relying on callback closures,
@@ -132,11 +221,13 @@ macro_rules! file_dialog_builder {
 /// **NOTE:** You cannot block the main thread when executing the dialog APIs, so you must use the [`crate::api::dialog`] methods instead.
 /// Examples of main thread context are the [`crate::App::run`] closure and non-async commmands.
 pub mod blocking {
+  use super::{MessageDialogButtons, MessageDialogKind};
   use crate::{Runtime, Window};
   use std::path::{Path, PathBuf};
   use std::sync::mpsc::sync_channel;
 
   file_dialog_builder!();
+  message_dialog_builder!();
 
   impl FileDialogBuilder {
     /// Shows the dialog to select a single file.
@@ -233,6 +324,22 @@ pub mod blocking {
     }
   }
 
+  impl MessageDialogBuilder {
+    //// Shows a message dialog.
+    ///
+    /// - In `Ok` dialog, it will return `true` when `OK` was pressed.
+    /// - In `OkCancel` dialog, it will return `true` when `OK` was pressed.
+    /// - In `YesNo` dialog, it will return `true` when `Yes` was pressed.
+    pub fn show(self) -> bool {
+      let (tx, rx) = sync_channel(1);
+      let f = move |response| {
+        tx.send(response).unwrap();
+      };
+      run_dialog!(self.0.show(), f);
+      rx.recv().unwrap()
+    }
+  }
+
   /// Displays a dialog with a message and an optional title with a "yes" and a "no" button and wait for it to be closed.
   ///
   /// This is a blocking operation,
@@ -314,6 +421,7 @@ pub mod blocking {
       title,
       message,
       buttons,
+      MessageDialogKind::Info,
       move |response| {
         tx.send(response).unwrap();
       },
@@ -323,10 +431,12 @@ pub mod blocking {
 }
 
 mod nonblocking {
+  use super::{MessageDialogButtons, MessageDialogKind};
   use crate::{Runtime, Window};
   use std::path::{Path, PathBuf};
 
   file_dialog_builder!();
+  message_dialog_builder!();
 
   impl FileDialogBuilder {
     /// Shows the dialog to select a single file.
@@ -431,6 +541,17 @@ mod nonblocking {
     }
   }
 
+  impl MessageDialogBuilder {
+    /// Shows a message dialog:
+    ///
+    /// - In `Ok` dialog, it will call the closure with `true` when `OK` was pressed
+    /// - In `OkCancel` dialog, it will call the closure with `true` when `OK` was pressed
+    /// - In `YesNo` dialog, it will call the closure with `true` when `Yes` was pressed
+    pub fn show<F: FnOnce(bool) + Send + 'static>(self, f: F) {
+      run_dialog!(self.0.show(), f);
+    }
+  }
+
   /// Displays a non-blocking dialog with a message and an optional title with a "yes" and a "no" button.
   ///
   /// This is not a blocking operation,
@@ -453,7 +574,14 @@ mod nonblocking {
     message: impl AsRef<str>,
     f: F,
   ) {
-    run_message_dialog(parent_window, title, message, rfd::MessageButtons::YesNo, f)
+    run_message_dialog(
+      parent_window,
+      title,
+      message,
+      rfd::MessageButtons::YesNo,
+      MessageDialogKind::Info,
+      f,
+    )
   }
 
   /// Displays a non-blocking dialog with a message and an optional title with an "ok" and a "cancel" button.
@@ -483,6 +611,7 @@ mod nonblocking {
       title,
       message,
       rfd::MessageButtons::OkCancel,
+      MessageDialogKind::Info,
       f,
     )
   }
@@ -511,6 +640,7 @@ mod nonblocking {
       title,
       message,
       rfd::MessageButtons::Ok,
+      MessageDialogKind::Info,
       |_| {},
     )
   }
@@ -521,6 +651,7 @@ mod nonblocking {
     title: impl AsRef<str>,
     message: impl AsRef<str>,
     buttons: rfd::MessageButtons,
+    level: MessageDialogKind,
     f: F,
   ) {
     let title = title.as_ref().to_string();
@@ -530,7 +661,7 @@ mod nonblocking {
       .set_title(&title)
       .set_description(&message)
       .set_buttons(buttons)
-      .set_level(rfd::MessageLevel::Info);
+      .set_level(level.into());
 
     #[cfg(any(windows, target_os = "macos"))]
     {

+ 87 - 38
core/tauri/src/endpoints/dialog.rs

@@ -8,11 +8,38 @@ use super::{InvokeContext, InvokeResponse};
 use crate::Runtime;
 #[cfg(any(dialog_open, dialog_save))]
 use crate::{api::dialog::blocking::FileDialogBuilder, Manager, Scopes};
-use serde::Deserialize;
+use serde::{Deserialize, Deserializer};
 use tauri_macros::{command_enum, module_command_handler, CommandModule};
 
 use std::path::PathBuf;
 
+#[cfg(any(dialog_message, dialog_ask, dialog_confirm))]
+macro_rules! message_dialog {
+  ($fn_name: ident, $allowlist: ident, $buttons: expr) => {
+    #[module_command_handler($allowlist)]
+    fn $fn_name<R: Runtime>(
+      context: InvokeContext<R>,
+      title: Option<String>,
+      message: String,
+      level: Option<MessageDialogType>,
+    ) -> super::Result<bool> {
+      let mut builder = crate::api::dialog::blocking::MessageDialogBuilder::new(
+        title.unwrap_or_else(|| context.window.app_handle.package_info().name.clone()),
+        message,
+      )
+      .buttons($buttons);
+      #[cfg(any(windows, target_os = "macos"))]
+      {
+        builder = builder.parent(&context.window);
+      }
+      if let Some(level) = level {
+        builder = builder.kind(level.into());
+      }
+      Ok(builder.show())
+    }
+  };
+}
+
 #[allow(dead_code)]
 #[derive(Debug, Clone, Deserialize)]
 #[serde(rename_all = "camelCase")]
@@ -57,6 +84,44 @@ pub struct SaveDialogOptions {
   pub default_path: Option<PathBuf>,
 }
 
+/// Types of message, ask and confirm dialogs.
+#[non_exhaustive]
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+pub enum MessageDialogType {
+  /// Information dialog.
+  Info,
+  /// Warning dialog.
+  Warning,
+  /// Error dialog.
+  Error,
+}
+
+impl<'de> Deserialize<'de> for MessageDialogType {
+  fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+  where
+    D: Deserializer<'de>,
+  {
+    let s = String::deserialize(deserializer)?;
+    Ok(match s.to_lowercase().as_str() {
+      "info" => MessageDialogType::Info,
+      "warning" => MessageDialogType::Warning,
+      "error" => MessageDialogType::Error,
+      _ => MessageDialogType::Info,
+    })
+  }
+}
+
+#[cfg(any(dialog_message, dialog_ask, dialog_confirm))]
+impl From<MessageDialogType> for crate::api::dialog::MessageDialogKind {
+  fn from(kind: MessageDialogType) -> Self {
+    match kind {
+      MessageDialogType::Info => Self::Info,
+      MessageDialogType::Warning => Self::Warning,
+      MessageDialogType::Error => Self::Error,
+    }
+  }
+}
+
 /// The API descriptor.
 #[command_enum]
 #[derive(Deserialize, CommandModule)]
@@ -73,16 +138,22 @@ pub enum Cmd {
   MessageDialog {
     title: Option<String>,
     message: String,
+    #[serde(rename = "type")]
+    level: Option<MessageDialogType>,
   },
   #[cmd(dialog_ask, "dialog > ask")]
   AskDialog {
     title: Option<String>,
     message: String,
+    #[serde(rename = "type")]
+    level: Option<MessageDialogType>,
   },
   #[cmd(dialog_confirm, "dialog > confirm")]
   ConfirmDialog {
     title: Option<String>,
     message: String,
+    #[serde(rename = "type")]
+    level: Option<MessageDialogType>,
   },
 }
 
@@ -170,45 +241,23 @@ impl Cmd {
     Ok(path)
   }
 
-  #[module_command_handler(dialog_message)]
-  fn message_dialog<R: Runtime>(
-    context: InvokeContext<R>,
-    title: Option<String>,
-    message: String,
-  ) -> super::Result<()> {
-    crate::api::dialog::blocking::message(
-      Some(&context.window),
-      title.unwrap_or_else(|| context.window.app_handle.package_info().name.clone()),
-      message,
-    );
-    Ok(())
-  }
+  message_dialog!(
+    message_dialog,
+    dialog_message,
+    crate::api::dialog::MessageDialogButtons::Ok
+  );
 
-  #[module_command_handler(dialog_ask)]
-  fn ask_dialog<R: Runtime>(
-    context: InvokeContext<R>,
-    title: Option<String>,
-    message: String,
-  ) -> super::Result<bool> {
-    Ok(crate::api::dialog::blocking::ask(
-      Some(&context.window),
-      title.unwrap_or_else(|| context.window.app_handle.package_info().name.clone()),
-      message,
-    ))
-  }
+  message_dialog!(
+    ask_dialog,
+    dialog_ask,
+    crate::api::dialog::MessageDialogButtons::YesNo
+  );
 
-  #[module_command_handler(dialog_confirm)]
-  fn confirm_dialog<R: Runtime>(
-    context: InvokeContext<R>,
-    title: Option<String>,
-    message: String,
-  ) -> super::Result<bool> {
-    Ok(crate::api::dialog::blocking::confirm(
-      Some(&context.window),
-      title.unwrap_or_else(|| context.window.app_handle.package_info().name.clone()),
-      message,
-    ))
-  }
+  message_dialog!(
+    confirm_dialog,
+    dialog_confirm,
+    crate::api::dialog::MessageDialogButtons::OkCancel
+  );
 }
 
 #[cfg(any(dialog_open, dialog_save))]

+ 7 - 7
examples/api/src-tauri/Cargo.lock

@@ -3112,7 +3112,7 @@ dependencies = [
 
 [[package]]
 name = "tauri"
-version = "1.0.0-rc.10"
+version = "1.0.0-rc.11"
 dependencies = [
  "anyhow",
  "attohttpc",
@@ -3173,7 +3173,7 @@ dependencies = [
 
 [[package]]
 name = "tauri-build"
-version = "1.0.0-rc.8"
+version = "1.0.0-rc.9"
 dependencies = [
  "anyhow",
  "cargo_toml",
@@ -3186,7 +3186,7 @@ dependencies = [
 
 [[package]]
 name = "tauri-codegen"
-version = "1.0.0-rc.6"
+version = "1.0.0-rc.7"
 dependencies = [
  "base64",
  "brotli",
@@ -3206,7 +3206,7 @@ dependencies = [
 
 [[package]]
 name = "tauri-macros"
-version = "1.0.0-rc.6"
+version = "1.0.0-rc.7"
 dependencies = [
  "heck 0.4.0",
  "proc-macro2",
@@ -3218,7 +3218,7 @@ dependencies = [
 
 [[package]]
 name = "tauri-runtime"
-version = "0.5.0"
+version = "0.5.1"
 dependencies = [
  "gtk",
  "http",
@@ -3235,7 +3235,7 @@ dependencies = [
 
 [[package]]
 name = "tauri-runtime-wry"
-version = "0.5.1"
+version = "0.5.2"
 dependencies = [
  "cocoa",
  "gtk",
@@ -3252,7 +3252,7 @@ dependencies = [
 
 [[package]]
 name = "tauri-utils"
-version = "1.0.0-rc.6"
+version = "1.0.0-rc.7"
 dependencies = [
  "aes-gcm",
  "brotli",

+ 34 - 11
tooling/api/src/dialog.ts

@@ -74,6 +74,13 @@ interface SaveDialogOptions {
   defaultPath?: string
 }
 
+interface MessageDialogOptions {
+  /** The title of the dialog. Defaults to the app name. */
+  title?: string
+  /** The type of the dialog. Defaults to `info`. */
+  type?: 'info' | 'warning' | 'error'
+}
+
 /**
  * Open a file/directory selection dialog.
  *
@@ -132,16 +139,22 @@ async function save(options: SaveDialogOptions = {}): Promise<string> {
  * Shows a message dialog with an `Ok` button.
  *
  * @param {string} message The message to show.
+ * @param {string|MessageDialogOptions|undefined} options The dialog's options. If a string, it represents the dialog title.
  *
  * @return {Promise<void>} A promise indicating the success or failure of the operation.
  */
-async function message(message: string, title?: string): Promise<void> {
+async function message(
+  message: string,
+  options?: string | MessageDialogOptions
+): Promise<void> {
+  const opts = typeof options === 'string' ? { title: options } : options
   return invokeTauriCommand({
     __tauriModule: 'Dialog',
     message: {
       cmd: 'messageDialog',
-      title,
-      message
+      message,
+      title: opts?.title,
+      type: opts?.type
     }
   })
 }
@@ -150,17 +163,22 @@ async function message(message: string, title?: string): Promise<void> {
  * Shows a question dialog with `Yes` and `No` buttons.
  *
  * @param {string} message The message to show.
- * @param {string|undefined} title The dialog's title. Defaults to the application name.
+ * @param {string|MessageDialogOptions|undefined} options The dialog's options. If a string, it represents the dialog title.
  *
  * @return {Promise<void>} A promise resolving to a boolean indicating whether `Yes` was clicked or not.
  */
-async function ask(message: string, title?: string): Promise<boolean> {
+async function ask(
+  message: string,
+  options?: string | MessageDialogOptions
+): Promise<boolean> {
+  const opts = typeof options === 'string' ? { title: options } : options
   return invokeTauriCommand({
     __tauriModule: 'Dialog',
     message: {
       cmd: 'askDialog',
-      title,
-      message
+      message,
+      title: opts?.title,
+      type: opts?.type
     }
   })
 }
@@ -169,17 +187,22 @@ async function ask(message: string, title?: string): Promise<boolean> {
  * Shows a question dialog with `Ok` and `Cancel` buttons.
  *
  * @param {string} message The message to show.
- * @param {string|undefined} title The dialog's title. Defaults to the application name.
+ * @param {string|MessageDialogOptions|undefined} options The dialog's options. If a string, it represents the dialog title.
  *
  * @return {Promise<void>} A promise resolving to a boolean indicating whether `Ok` was clicked or not.
  */
-async function confirm(message: string, title?: string): Promise<boolean> {
+async function confirm(
+  message: string,
+  options?: string | MessageDialogOptions
+): Promise<boolean> {
+  const opts = typeof options === 'string' ? { title: options } : options
   return invokeTauriCommand({
     __tauriModule: 'Dialog',
     message: {
       cmd: 'confirmDialog',
-      title,
-      message
+      message,
+      title: opts?.title,
+      type: opts?.type
     }
   })
 }

Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff