mirror of
https://github.com/tauri-apps/plugins-workspace.git
synced 2026-06-06 13:53:54 +02:00
feat(fs): resolve content URIs on Android (#1658)
* Implemented writeTextFile on Android. * Added license headers. * fix fmt checks. * implement more file APIs * change file * cleanup * refactor dialog plugin to leverage new FS APIs * implement metadata functions * fix build * expose FS rust API * resolve resources on android * update pnpm * update docs --------- Co-authored-by: Lucas Nogueira <lucas@tauri.app>
This commit is contained in:
@@ -8,17 +8,17 @@ use serde::{Deserialize, Serialize};
|
||||
use tauri::{command, Manager, Runtime, State, Window};
|
||||
use tauri_plugin_fs::FsExt;
|
||||
|
||||
use crate::{Dialog, FileDialogBuilder, FileResponse, MessageDialogKind, Result};
|
||||
use crate::{Dialog, FileDialogBuilder, FilePath, MessageDialogKind, Result};
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum OpenResponse {
|
||||
#[cfg(desktop)]
|
||||
Folders(Option<Vec<PathBuf>>),
|
||||
Folders(Option<Vec<FilePath>>),
|
||||
#[cfg(desktop)]
|
||||
Folder(Option<PathBuf>),
|
||||
Files(Option<Vec<FileResponse>>),
|
||||
File(Option<FileResponse>),
|
||||
Folder(Option<FilePath>),
|
||||
Files(Option<Vec<FilePath>>),
|
||||
File(Option<FilePath>),
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
@@ -136,25 +136,26 @@ pub(crate) async fn open<R: Runtime>(
|
||||
let folders = dialog_builder.blocking_pick_folders();
|
||||
if let Some(folders) = &folders {
|
||||
for folder in folders {
|
||||
if let Some(s) = window.try_fs_scope() {
|
||||
s.allow_directory(folder, options.recursive);
|
||||
if let Ok(path) = folder.path() {
|
||||
if let Some(s) = window.try_fs_scope() {
|
||||
s.allow_directory(path, options.recursive);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
OpenResponse::Folders(folders.map(|folders| {
|
||||
folders
|
||||
.iter()
|
||||
.map(|p| dunce::simplified(p).to_path_buf())
|
||||
.collect()
|
||||
}))
|
||||
OpenResponse::Folders(
|
||||
folders.map(|folders| folders.into_iter().map(|p| p.simplified()).collect()),
|
||||
)
|
||||
} else {
|
||||
let folder = dialog_builder.blocking_pick_folder();
|
||||
if let Some(path) = &folder {
|
||||
if let Some(s) = window.try_fs_scope() {
|
||||
s.allow_directory(path, options.recursive);
|
||||
if let Some(folder) = &folder {
|
||||
if let Ok(path) = folder.path() {
|
||||
if let Some(s) = window.try_fs_scope() {
|
||||
s.allow_directory(path, options.recursive);
|
||||
}
|
||||
}
|
||||
}
|
||||
OpenResponse::Folder(folder.map(|p| dunce::simplified(&p).to_path_buf()))
|
||||
OpenResponse::Folder(folder.map(|p| p.simplified()))
|
||||
}
|
||||
}
|
||||
#[cfg(mobile)]
|
||||
@@ -163,37 +164,28 @@ pub(crate) async fn open<R: Runtime>(
|
||||
let files = dialog_builder.blocking_pick_files();
|
||||
if let Some(files) = &files {
|
||||
for file in files {
|
||||
if let Some(s) = window.try_fs_scope() {
|
||||
s.allow_file(&file.path);
|
||||
if let Ok(path) = file.path() {
|
||||
if let Some(s) = window.try_fs_scope() {
|
||||
s.allow_file(&path);
|
||||
}
|
||||
|
||||
window.state::<tauri::scope::Scopes>().allow_file(&path)?;
|
||||
}
|
||||
window
|
||||
.state::<tauri::scope::Scopes>()
|
||||
.allow_file(&file.path)?;
|
||||
}
|
||||
}
|
||||
OpenResponse::Files(files.map(|files| {
|
||||
files
|
||||
.into_iter()
|
||||
.map(|mut f| {
|
||||
f.path = dunce::simplified(&f.path).to_path_buf();
|
||||
f
|
||||
})
|
||||
.collect()
|
||||
}))
|
||||
OpenResponse::Files(files.map(|files| files.into_iter().map(|f| f.simplified()).collect()))
|
||||
} else {
|
||||
let file = dialog_builder.blocking_pick_file();
|
||||
|
||||
if let Some(file) = &file {
|
||||
if let Some(s) = window.try_fs_scope() {
|
||||
s.allow_file(&file.path);
|
||||
if let Ok(path) = file.path() {
|
||||
if let Some(s) = window.try_fs_scope() {
|
||||
s.allow_file(&path);
|
||||
}
|
||||
window.state::<tauri::scope::Scopes>().allow_file(&path)?;
|
||||
}
|
||||
window
|
||||
.state::<tauri::scope::Scopes>()
|
||||
.allow_file(&file.path)?;
|
||||
}
|
||||
OpenResponse::File(file.map(|mut f| {
|
||||
f.path = dunce::simplified(&f.path).to_path_buf();
|
||||
f
|
||||
}))
|
||||
OpenResponse::File(file.map(|f| f.simplified()))
|
||||
};
|
||||
Ok(res)
|
||||
}
|
||||
@@ -204,7 +196,7 @@ pub(crate) async fn save<R: Runtime>(
|
||||
window: Window<R>,
|
||||
dialog: State<'_, Dialog<R>>,
|
||||
options: SaveDialogOptions,
|
||||
) -> Result<Option<PathBuf>> {
|
||||
) -> Result<Option<FilePath>> {
|
||||
#[cfg(target_os = "ios")]
|
||||
return Err(crate::Error::FileSaveDialogNotImplemented);
|
||||
#[cfg(any(desktop, target_os = "android"))]
|
||||
@@ -230,13 +222,15 @@ pub(crate) async fn save<R: Runtime>(
|
||||
|
||||
let path = dialog_builder.blocking_save_file();
|
||||
if let Some(p) = &path {
|
||||
if let Some(s) = window.try_fs_scope() {
|
||||
s.allow_file(p);
|
||||
if let Ok(path) = p.path() {
|
||||
if let Some(s) = window.try_fs_scope() {
|
||||
s.allow_file(&path);
|
||||
}
|
||||
window.state::<tauri::scope::Scopes>().allow_file(&path)?;
|
||||
}
|
||||
window.state::<tauri::scope::Scopes>().allow_file(p)?;
|
||||
}
|
||||
|
||||
Ok(path.map(|p| dunce::simplified(&p).to_path_buf()))
|
||||
Ok(path.map(|p| p.simplified()))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,14 +8,12 @@
|
||||
//! to give results back. This is particularly useful when running dialogs from the main thread.
|
||||
//! When using on asynchronous contexts such as async commands, the [`blocking`] APIs are recommended.
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use raw_window_handle::{HasWindowHandle, RawWindowHandle};
|
||||
use rfd::{AsyncFileDialog, AsyncMessageDialog};
|
||||
use serde::de::DeserializeOwned;
|
||||
use tauri::{plugin::PluginApi, AppHandle, Runtime};
|
||||
|
||||
use crate::{models::*, FileDialogBuilder, MessageDialogBuilder};
|
||||
use crate::{models::*, FileDialogBuilder, FilePath, MessageDialogBuilder};
|
||||
|
||||
const OK: &str = "Ok";
|
||||
|
||||
@@ -115,11 +113,11 @@ impl<R: Runtime> From<MessageDialogBuilder<R>> for AsyncMessageDialog {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pick_file<R: Runtime, F: FnOnce(Option<PathBuf>) + Send + 'static>(
|
||||
pub fn pick_file<R: Runtime, F: FnOnce(Option<FilePath>) + Send + 'static>(
|
||||
dialog: FileDialogBuilder<R>,
|
||||
f: F,
|
||||
) {
|
||||
let f = |path: Option<rfd::FileHandle>| f(path.map(|p| p.path().to_path_buf()));
|
||||
let f = |path: Option<rfd::FileHandle>| f(path.map(|p| p.path().to_path_buf().into()));
|
||||
let handle = dialog.dialog.app_handle().to_owned();
|
||||
let _ = handle.run_on_main_thread(move || {
|
||||
let dialog = AsyncFileDialog::from(dialog).pick_file();
|
||||
@@ -127,12 +125,16 @@ pub fn pick_file<R: Runtime, F: FnOnce(Option<PathBuf>) + Send + 'static>(
|
||||
});
|
||||
}
|
||||
|
||||
pub fn pick_files<R: Runtime, F: FnOnce(Option<Vec<PathBuf>>) + Send + 'static>(
|
||||
pub fn pick_files<R: Runtime, F: FnOnce(Option<Vec<FilePath>>) + Send + 'static>(
|
||||
dialog: FileDialogBuilder<R>,
|
||||
f: F,
|
||||
) {
|
||||
let f = |paths: Option<Vec<rfd::FileHandle>>| {
|
||||
f(paths.map(|list| list.into_iter().map(|p| p.path().to_path_buf()).collect()))
|
||||
f(paths.map(|list| {
|
||||
list.into_iter()
|
||||
.map(|p| p.path().to_path_buf().into())
|
||||
.collect()
|
||||
}))
|
||||
};
|
||||
let handle = dialog.dialog.app_handle().to_owned();
|
||||
let _ = handle.run_on_main_thread(move || {
|
||||
@@ -141,11 +143,11 @@ pub fn pick_files<R: Runtime, F: FnOnce(Option<Vec<PathBuf>>) + Send + 'static>(
|
||||
});
|
||||
}
|
||||
|
||||
pub fn pick_folder<R: Runtime, F: FnOnce(Option<PathBuf>) + Send + 'static>(
|
||||
pub fn pick_folder<R: Runtime, F: FnOnce(Option<FilePath>) + Send + 'static>(
|
||||
dialog: FileDialogBuilder<R>,
|
||||
f: F,
|
||||
) {
|
||||
let f = |path: Option<rfd::FileHandle>| f(path.map(|p| p.path().to_path_buf()));
|
||||
let f = |path: Option<rfd::FileHandle>| f(path.map(|p| p.path().to_path_buf().into()));
|
||||
let handle = dialog.dialog.app_handle().to_owned();
|
||||
let _ = handle.run_on_main_thread(move || {
|
||||
let dialog = AsyncFileDialog::from(dialog).pick_folder();
|
||||
@@ -153,12 +155,16 @@ pub fn pick_folder<R: Runtime, F: FnOnce(Option<PathBuf>) + Send + 'static>(
|
||||
});
|
||||
}
|
||||
|
||||
pub fn pick_folders<R: Runtime, F: FnOnce(Option<Vec<PathBuf>>) + Send + 'static>(
|
||||
pub fn pick_folders<R: Runtime, F: FnOnce(Option<Vec<FilePath>>) + Send + 'static>(
|
||||
dialog: FileDialogBuilder<R>,
|
||||
f: F,
|
||||
) {
|
||||
let f = |paths: Option<Vec<rfd::FileHandle>>| {
|
||||
f(paths.map(|list| list.into_iter().map(|p| p.path().to_path_buf()).collect()))
|
||||
f(paths.map(|list| {
|
||||
list.into_iter()
|
||||
.map(|p| p.path().to_path_buf().into())
|
||||
.collect()
|
||||
}))
|
||||
};
|
||||
let handle = dialog.dialog.app_handle().to_owned();
|
||||
let _ = handle.run_on_main_thread(move || {
|
||||
@@ -167,11 +173,11 @@ pub fn pick_folders<R: Runtime, F: FnOnce(Option<Vec<PathBuf>>) + Send + 'static
|
||||
});
|
||||
}
|
||||
|
||||
pub fn save_file<R: Runtime, F: FnOnce(Option<PathBuf>) + Send + 'static>(
|
||||
pub fn save_file<R: Runtime, F: FnOnce(Option<FilePath>) + Send + 'static>(
|
||||
dialog: FileDialogBuilder<R>,
|
||||
f: F,
|
||||
) {
|
||||
let f = |path: Option<rfd::FileHandle>| f(path.map(|p| p.path().to_path_buf()));
|
||||
let f = |path: Option<rfd::FileHandle>| f(path.map(|p| p.path().to_path_buf().into()));
|
||||
let handle = dialog.dialog.app_handle().to_owned();
|
||||
let _ = handle.run_on_main_thread(move || {
|
||||
let dialog = AsyncFileDialog::from(dialog).save_file();
|
||||
|
||||
@@ -23,6 +23,8 @@ pub enum Error {
|
||||
FileSaveDialogNotImplemented,
|
||||
#[error(transparent)]
|
||||
Fs(#[from] tauri_plugin_fs::Error),
|
||||
#[error("URL is not a valid path")]
|
||||
InvalidPathUrl,
|
||||
}
|
||||
|
||||
impl Serialize for Error {
|
||||
|
||||
+190
-102
@@ -17,9 +17,6 @@ use tauri::{
|
||||
Manager, Runtime,
|
||||
};
|
||||
|
||||
#[cfg(any(desktop, target_os = "ios"))]
|
||||
use std::fs;
|
||||
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
sync::mpsc::sync_channel,
|
||||
@@ -66,6 +63,87 @@ impl<R: Runtime, T: Manager<R>> crate::DialogExt<R> for T {
|
||||
}
|
||||
|
||||
impl<R: Runtime> Dialog<R> {
|
||||
/// Create a new messaging dialog builder.
|
||||
/// The dialog can optionally ask the user for confirmation or include an OK button.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// - Message dialog:
|
||||
///
|
||||
/// ```
|
||||
/// use tauri_plugin_dialog::DialogExt;
|
||||
///
|
||||
/// tauri::Builder::default()
|
||||
/// .setup(|app| {
|
||||
/// app
|
||||
/// .dialog()
|
||||
/// .message("Tauri is Awesome!")
|
||||
/// .show(|_| {
|
||||
/// println!("dialog closed");
|
||||
/// });
|
||||
/// Ok(())
|
||||
/// });
|
||||
/// ```
|
||||
///
|
||||
/// - Ask dialog:
|
||||
///
|
||||
/// ```
|
||||
/// use tauri_plugin_dialog::DialogExt;
|
||||
///
|
||||
/// tauri::Builder::default()
|
||||
/// .setup(|app| {
|
||||
/// app.dialog()
|
||||
/// .message("Are you sure?")
|
||||
/// .ok_button_label("Yes")
|
||||
/// .cancel_button_label("No")
|
||||
/// .show(|yes| {
|
||||
/// println!("user said {}", if yes { "yes" } else { "no" });
|
||||
/// });
|
||||
/// Ok(())
|
||||
/// });
|
||||
/// ```
|
||||
///
|
||||
/// - Message dialog with OK button:
|
||||
///
|
||||
/// ```
|
||||
/// use tauri_plugin_dialog::DialogExt;
|
||||
///
|
||||
/// tauri::Builder::default()
|
||||
/// .setup(|app| {
|
||||
/// app.dialog()
|
||||
/// .message("Job completed successfully")
|
||||
/// .ok_button_label("Ok")
|
||||
/// .show(|_| {
|
||||
/// println!("dialog closed");
|
||||
/// });
|
||||
/// Ok(())
|
||||
/// });
|
||||
/// ```
|
||||
///
|
||||
/// # `show` vs `blocking_show`
|
||||
///
|
||||
/// The dialog builder includes two separate APIs for rendering the dialog: `show` and `blocking_show`.
|
||||
/// The `show` function is asynchronous and takes a closure to be executed when the dialog is closed.
|
||||
/// To block the current thread until the user acted on the dialog, you can use `blocking_show`,
|
||||
/// but note that it cannot be executed on the main thread as it will freeze your application.
|
||||
///
|
||||
/// ```
|
||||
/// use tauri_plugin_dialog::DialogExt;
|
||||
///
|
||||
/// tauri::Builder::default()
|
||||
/// .setup(|app| {
|
||||
/// let handle = app.handle().clone();
|
||||
/// std::thread::spawn(move || {
|
||||
/// let yes = handle.dialog()
|
||||
/// .message("Are you sure?")
|
||||
/// .ok_button_label("Yes")
|
||||
/// .cancel_button_label("No")
|
||||
/// .blocking_show();
|
||||
/// });
|
||||
///
|
||||
/// Ok(())
|
||||
/// });
|
||||
/// ```
|
||||
pub fn message(&self, message: impl Into<String>) -> MessageDialogBuilder<R> {
|
||||
MessageDialogBuilder::new(
|
||||
self.clone(),
|
||||
@@ -74,6 +152,7 @@ impl<R: Runtime> Dialog<R> {
|
||||
)
|
||||
}
|
||||
|
||||
/// Creates a new builder for dialogs that lets ths user select file(s) or folder(s).
|
||||
pub fn file(&self) -> FileDialogBuilder<R> {
|
||||
FileDialogBuilder::new(self.clone())
|
||||
}
|
||||
@@ -216,36 +295,52 @@ impl<R: Runtime> MessageDialogBuilder<R> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[non_exhaustive]
|
||||
pub struct FileResponse {
|
||||
pub base64_data: Option<String>,
|
||||
pub duration: Option<u64>,
|
||||
pub height: Option<usize>,
|
||||
pub width: Option<usize>,
|
||||
pub mime_type: Option<String>,
|
||||
pub modified_at: Option<u64>,
|
||||
pub name: Option<String>,
|
||||
pub path: PathBuf,
|
||||
pub size: u64,
|
||||
/// Represents either a filesystem path or a URI pointing to a file
|
||||
/// such as `file://` URIs or Android `content://` URIs.
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum FilePath {
|
||||
Url(url::Url),
|
||||
Path(PathBuf),
|
||||
}
|
||||
|
||||
impl FileResponse {
|
||||
#[cfg(desktop)]
|
||||
fn new(path: PathBuf) -> Self {
|
||||
let metadata = fs::metadata(&path);
|
||||
let metadata = metadata.as_ref();
|
||||
Self {
|
||||
base64_data: None,
|
||||
duration: None,
|
||||
height: None,
|
||||
width: None,
|
||||
mime_type: None,
|
||||
modified_at: metadata.ok().and_then(|m| to_msec(m.modified())),
|
||||
name: path.file_name().map(|f| f.to_string_lossy().into_owned()),
|
||||
path,
|
||||
size: metadata.map(|m| m.len()).unwrap_or(0),
|
||||
impl From<PathBuf> for FilePath {
|
||||
fn from(value: PathBuf) -> Self {
|
||||
Self::Path(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<url::Url> for FilePath {
|
||||
fn from(value: url::Url) -> Self {
|
||||
Self::Url(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FilePath> for tauri_plugin_fs::FilePath {
|
||||
fn from(value: FilePath) -> Self {
|
||||
match value {
|
||||
FilePath::Path(p) => tauri_plugin_fs::FilePath::Path(p),
|
||||
FilePath::Url(url) => tauri_plugin_fs::FilePath::Url(url),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FilePath {
|
||||
fn simplified(self) -> Self {
|
||||
match self {
|
||||
Self::Url(url) => Self::Url(url),
|
||||
Self::Path(p) => Self::Path(dunce::simplified(&p).to_path_buf()),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn path(&self) -> Result<PathBuf> {
|
||||
match self {
|
||||
Self::Url(url) => url
|
||||
.to_file_path()
|
||||
.map(PathBuf::from)
|
||||
.map_err(|_| Error::InvalidPathUrl),
|
||||
Self::Path(p) => Ok(p.to_owned()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -363,21 +458,18 @@ impl<R: Runtime> FileDialogBuilder<R> {
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// ```
|
||||
/// use tauri_plugin_dialog::DialogExt;
|
||||
/// tauri::Builder::default()
|
||||
/// .build(tauri::generate_context!("test/tauri.conf.json"))
|
||||
/// .expect("failed to build tauri app")
|
||||
/// .run(|app, _event| {
|
||||
/// .setup(|app| {
|
||||
/// app.dialog().file().pick_file(|file_path| {
|
||||
/// // do something with the optional file path here
|
||||
/// // the file path is `None` if the user closed the dialog
|
||||
/// })
|
||||
/// })
|
||||
/// });
|
||||
/// Ok(())
|
||||
/// });
|
||||
/// ```
|
||||
pub fn pick_file<F: FnOnce(Option<FileResponse>) + Send + 'static>(self, f: F) {
|
||||
#[cfg(desktop)]
|
||||
let f = |path: Option<PathBuf>| f(path.map(FileResponse::new));
|
||||
pub fn pick_file<F: FnOnce(Option<FilePath>) + Send + 'static>(self, f: F) {
|
||||
pick_file(self, f)
|
||||
}
|
||||
|
||||
@@ -385,29 +477,44 @@ impl<R: Runtime> FileDialogBuilder<R> {
|
||||
/// This is not a blocking operation,
|
||||
/// and should be used when running on the main thread to avoid deadlocks with the event loop.
|
||||
///
|
||||
/// # Reading the files
|
||||
///
|
||||
/// The file paths cannot be read directly on Android as they are behind a content URI.
|
||||
/// The recommended way to read the files is using the [`fs`](https://v2.tauri.app/plugin/file-system/) plugin:
|
||||
///
|
||||
/// ```
|
||||
/// use tauri_plugin_dialog::DialogExt;
|
||||
/// use tauri_plugin_fs::FsExt;
|
||||
/// tauri::Builder::default()
|
||||
/// .setup(|app| {
|
||||
/// let handle = app.handle().clone();
|
||||
/// app.dialog().file().pick_file(move |file_path| {
|
||||
/// let Some(path) = file_path else { return };
|
||||
/// let Ok(contents) = handle.fs().read_to_string(path) else {
|
||||
/// eprintln!("failed to read file, <todo add error handling!>");
|
||||
/// return;
|
||||
/// };
|
||||
/// });
|
||||
/// Ok(())
|
||||
/// });
|
||||
/// ```
|
||||
///
|
||||
/// See <https://developer.android.com/guide/topics/providers/content-provider-basics> for more information.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// ```
|
||||
/// use tauri_plugin_dialog::DialogExt;
|
||||
/// tauri::Builder::default()
|
||||
/// .build(tauri::generate_context!("test/tauri.conf.json"))
|
||||
/// .expect("failed to build tauri app")
|
||||
/// .run(|app, _event| {
|
||||
/// .setup(|app| {
|
||||
/// app.dialog().file().pick_files(|file_paths| {
|
||||
/// // do something with the optional file paths here
|
||||
/// // the file paths value is `None` if the user closed the dialog
|
||||
/// })
|
||||
/// })
|
||||
/// });
|
||||
/// Ok(())
|
||||
/// });
|
||||
/// ```
|
||||
pub fn pick_files<F: FnOnce(Option<Vec<FileResponse>>) + Send + 'static>(self, f: F) {
|
||||
#[cfg(desktop)]
|
||||
let f = |paths: Option<Vec<PathBuf>>| {
|
||||
f(paths.map(|p| {
|
||||
p.into_iter()
|
||||
.map(FileResponse::new)
|
||||
.collect::<Vec<FileResponse>>()
|
||||
}))
|
||||
};
|
||||
pub fn pick_files<F: FnOnce(Option<Vec<FilePath>>) + Send + 'static>(self, f: F) {
|
||||
pick_files(self, f)
|
||||
}
|
||||
|
||||
@@ -417,20 +524,19 @@ impl<R: Runtime> FileDialogBuilder<R> {
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// ```
|
||||
/// use tauri_plugin_dialog::DialogExt;
|
||||
/// tauri::Builder::default()
|
||||
/// .build(tauri::generate_context!("test/tauri.conf.json"))
|
||||
/// .expect("failed to build tauri app")
|
||||
/// .run(|app, _event| {
|
||||
/// .setup(|app| {
|
||||
/// app.dialog().file().pick_folder(|folder_path| {
|
||||
/// // do something with the optional folder path here
|
||||
/// // the folder path is `None` if the user closed the dialog
|
||||
/// })
|
||||
/// })
|
||||
/// });
|
||||
/// Ok(())
|
||||
/// });
|
||||
/// ```
|
||||
#[cfg(desktop)]
|
||||
pub fn pick_folder<F: FnOnce(Option<PathBuf>) + Send + 'static>(self, f: F) {
|
||||
pub fn pick_folder<F: FnOnce(Option<FilePath>) + Send + 'static>(self, f: F) {
|
||||
pick_folder(self, f)
|
||||
}
|
||||
|
||||
@@ -440,20 +546,19 @@ impl<R: Runtime> FileDialogBuilder<R> {
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// ```
|
||||
/// use tauri_plugin_dialog::DialogExt;
|
||||
/// tauri::Builder::default()
|
||||
/// .build(tauri::generate_context!("test/tauri.conf.json"))
|
||||
/// .expect("failed to build tauri app")
|
||||
/// .run(|app, _event| {
|
||||
/// .setup(|app| {
|
||||
/// app.dialog().file().pick_folders(|file_paths| {
|
||||
/// // do something with the optional folder paths here
|
||||
/// // the folder paths value is `None` if the user closed the dialog
|
||||
/// })
|
||||
/// })
|
||||
/// });
|
||||
/// Ok(())
|
||||
/// });
|
||||
/// ```
|
||||
#[cfg(desktop)]
|
||||
pub fn pick_folders<F: FnOnce(Option<Vec<PathBuf>>) + Send + 'static>(self, f: F) {
|
||||
pub fn pick_folders<F: FnOnce(Option<Vec<FilePath>>) + Send + 'static>(self, f: F) {
|
||||
pick_folders(self, f)
|
||||
}
|
||||
|
||||
@@ -464,19 +569,18 @@ impl<R: Runtime> FileDialogBuilder<R> {
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// ```
|
||||
/// use tauri_plugin_dialog::DialogExt;
|
||||
/// tauri::Builder::default()
|
||||
/// .build(tauri::generate_context!("test/tauri.conf.json"))
|
||||
/// .expect("failed to build tauri app")
|
||||
/// .run(|app, _event| {
|
||||
/// .setup(|app| {
|
||||
/// app.dialog().file().save_file(|file_path| {
|
||||
/// // do something with the optional file path here
|
||||
/// // the file path is `None` if the user closed the dialog
|
||||
/// })
|
||||
/// })
|
||||
/// });
|
||||
/// Ok(())
|
||||
/// });
|
||||
/// ```
|
||||
pub fn save_file<F: FnOnce(Option<PathBuf>) + Send + 'static>(self, f: F) {
|
||||
pub fn save_file<F: FnOnce(Option<FilePath>) + Send + 'static>(self, f: F) {
|
||||
save_file(self, f)
|
||||
}
|
||||
}
|
||||
@@ -489,7 +593,7 @@ impl<R: Runtime> FileDialogBuilder<R> {
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// ```
|
||||
/// use tauri_plugin_dialog::DialogExt;
|
||||
/// #[tauri::command]
|
||||
/// async fn my_command(app: tauri::AppHandle) {
|
||||
@@ -498,7 +602,7 @@ impl<R: Runtime> FileDialogBuilder<R> {
|
||||
/// // the file path is `None` if the user closed the dialog
|
||||
/// }
|
||||
/// ```
|
||||
pub fn blocking_pick_file(self) -> Option<FileResponse> {
|
||||
pub fn blocking_pick_file(self) -> Option<FilePath> {
|
||||
blocking_fn!(self, pick_file)
|
||||
}
|
||||
|
||||
@@ -508,7 +612,7 @@ impl<R: Runtime> FileDialogBuilder<R> {
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// ```
|
||||
/// use tauri_plugin_dialog::DialogExt;
|
||||
/// #[tauri::command]
|
||||
/// async fn my_command(app: tauri::AppHandle) {
|
||||
@@ -517,7 +621,7 @@ impl<R: Runtime> FileDialogBuilder<R> {
|
||||
/// // the file paths value is `None` if the user closed the dialog
|
||||
/// }
|
||||
/// ```
|
||||
pub fn blocking_pick_files(self) -> Option<Vec<FileResponse>> {
|
||||
pub fn blocking_pick_files(self) -> Option<Vec<FilePath>> {
|
||||
blocking_fn!(self, pick_files)
|
||||
}
|
||||
|
||||
@@ -527,7 +631,7 @@ impl<R: Runtime> FileDialogBuilder<R> {
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// ```
|
||||
/// use tauri_plugin_dialog::DialogExt;
|
||||
/// #[tauri::command]
|
||||
/// async fn my_command(app: tauri::AppHandle) {
|
||||
@@ -537,7 +641,7 @@ impl<R: Runtime> FileDialogBuilder<R> {
|
||||
/// }
|
||||
/// ```
|
||||
#[cfg(desktop)]
|
||||
pub fn blocking_pick_folder(self) -> Option<PathBuf> {
|
||||
pub fn blocking_pick_folder(self) -> Option<FilePath> {
|
||||
blocking_fn!(self, pick_folder)
|
||||
}
|
||||
|
||||
@@ -547,7 +651,7 @@ impl<R: Runtime> FileDialogBuilder<R> {
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// ```
|
||||
/// use tauri_plugin_dialog::DialogExt;
|
||||
/// #[tauri::command]
|
||||
/// async fn my_command(app: tauri::AppHandle) {
|
||||
@@ -557,7 +661,7 @@ impl<R: Runtime> FileDialogBuilder<R> {
|
||||
/// }
|
||||
/// ```
|
||||
#[cfg(desktop)]
|
||||
pub fn blocking_pick_folders(self) -> Option<Vec<PathBuf>> {
|
||||
pub fn blocking_pick_folders(self) -> Option<Vec<FilePath>> {
|
||||
blocking_fn!(self, pick_folders)
|
||||
}
|
||||
|
||||
@@ -567,7 +671,7 @@ impl<R: Runtime> FileDialogBuilder<R> {
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// ```
|
||||
/// use tauri_plugin_dialog::DialogExt;
|
||||
/// #[tauri::command]
|
||||
/// async fn my_command(app: tauri::AppHandle) {
|
||||
@@ -576,23 +680,7 @@ impl<R: Runtime> FileDialogBuilder<R> {
|
||||
/// // the file path is `None` if the user closed the dialog
|
||||
/// }
|
||||
/// ```
|
||||
pub fn blocking_save_file(self) -> Option<PathBuf> {
|
||||
pub fn blocking_save_file(self) -> Option<FilePath> {
|
||||
blocking_fn!(self, save_file)
|
||||
}
|
||||
}
|
||||
|
||||
// taken from deno source code: https://github.com/denoland/deno/blob/ffffa2f7c44bd26aec5ae1957e0534487d099f48/runtime/ops/fs.rs#L913
|
||||
#[cfg(desktop)]
|
||||
#[inline]
|
||||
fn to_msec(maybe_time: std::result::Result<std::time::SystemTime, std::io::Error>) -> Option<u64> {
|
||||
match maybe_time {
|
||||
Ok(time) => {
|
||||
let msec = time
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.map(|t| t.as_millis() as u64)
|
||||
.unwrap_or_else(|err| err.duration().as_millis() as u64);
|
||||
Some(msec)
|
||||
}
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
use std::path::PathBuf;
|
||||
|
||||
use serde::{de::DeserializeOwned, Deserialize};
|
||||
use tauri::{
|
||||
@@ -9,7 +8,7 @@ use tauri::{
|
||||
AppHandle, Runtime,
|
||||
};
|
||||
|
||||
use crate::{FileDialogBuilder, FileResponse, MessageDialogBuilder};
|
||||
use crate::{FileDialogBuilder, FilePath, MessageDialogBuilder};
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
const PLUGIN_IDENTIFIER: &str = "app.tauri.dialog";
|
||||
@@ -47,15 +46,15 @@ impl<R: Runtime> Dialog<R> {
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct FilePickerResponse {
|
||||
files: Vec<FileResponse>,
|
||||
files: Vec<FilePath>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct SaveFileResponse {
|
||||
file: PathBuf,
|
||||
file: FilePath,
|
||||
}
|
||||
|
||||
pub fn pick_file<R: Runtime, F: FnOnce(Option<FileResponse>) + Send + 'static>(
|
||||
pub fn pick_file<R: Runtime, F: FnOnce(Option<FilePath>) + Send + 'static>(
|
||||
dialog: FileDialogBuilder<R>,
|
||||
f: F,
|
||||
) {
|
||||
@@ -72,7 +71,7 @@ pub fn pick_file<R: Runtime, F: FnOnce(Option<FileResponse>) + Send + 'static>(
|
||||
});
|
||||
}
|
||||
|
||||
pub fn pick_files<R: Runtime, F: FnOnce(Option<Vec<FileResponse>>) + Send + 'static>(
|
||||
pub fn pick_files<R: Runtime, F: FnOnce(Option<Vec<FilePath>>) + Send + 'static>(
|
||||
dialog: FileDialogBuilder<R>,
|
||||
f: F,
|
||||
) {
|
||||
@@ -89,7 +88,7 @@ pub fn pick_files<R: Runtime, F: FnOnce(Option<Vec<FileResponse>>) + Send + 'sta
|
||||
});
|
||||
}
|
||||
|
||||
pub fn save_file<R: Runtime, F: FnOnce(Option<PathBuf>) + Send + 'static>(
|
||||
pub fn save_file<R: Runtime, F: FnOnce(Option<FilePath>) + Send + 'static>(
|
||||
dialog: FileDialogBuilder<R>,
|
||||
f: F,
|
||||
) {
|
||||
|
||||
Reference in New Issue
Block a user