use std::future::Future;
use std::path::PathBuf;
use std::pin::Pin;
use serde::Deserialize;
use tauri::{AppHandle, Runtime, Window};
use crate::error::{Error, Result};
#[derive(Debug, Clone, Deserialize)]
pub struct Filter {
pub name: String,
pub extensions: Vec<String>,
}
type Pending<T> = Pin<Box<dyn Future<Output = T> + Send>>;
async fn on_main<R: Runtime, T: Send + 'static>(app: &AppHandle<R>, make: impl FnOnce() -> Pending<T> + Send + 'static) -> Result<T> {
let (tx, rx) = tokio::sync::oneshot::channel();
app.run_on_main_thread(move || {
let _ = tx.send(make());
})?;
let pending = rx.await.map_err(|_| Error::internal("对话框没有打开"))?;
Ok(pending.await)
}
#[cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))]
pub async fn pick<R: Runtime>(app: &AppHandle<R>, parent: Option<Window<R>>, multiple: bool, filters: Vec<Filter>) -> Result<Vec<PathBuf>> {
on_main(app, move || {
let mut dialog = rfd::AsyncFileDialog::new();
for filter in &filters {
let extensions: Vec<&str> = filter.extensions.iter().map(|e| e.trim_start_matches('.')).collect();
dialog = dialog.add_filter(filter.name.clone(), &extensions);
}
if let Some(window) = &parent {
dialog = dialog.set_parent(window);
}
if multiple {
let picked = dialog.pick_files();
Box::pin(async move { picked.await.map(|files| files.into_iter().map(|f| f.path().to_path_buf()).collect()).unwrap_or_default() })
} else {
let picked = dialog.pick_file();
Box::pin(async move { picked.await.map(|f| vec![f.path().to_path_buf()]).unwrap_or_default() })
}
})
.await
}
#[cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))]
pub async fn pick_save<R: Runtime>(app: &AppHandle<R>, parent: Option<Window<R>>, default_name: Option<String>, directory: Option<PathBuf>) -> Result<Option<PathBuf>> {
on_main(app, move || {
let mut dialog = rfd::AsyncFileDialog::new().set_can_create_directories(true);
if let Some(name) = default_name.as_deref().filter(|n| !n.is_empty()) {
dialog = dialog.set_file_name(super::transfer::sanitize_file_name(name, "download"));
}
if let Some(dir) = &directory {
dialog = dialog.set_directory(dir);
}
if let Some(window) = &parent {
dialog = dialog.set_parent(window);
}
let picked = dialog.save_file();
Box::pin(async move { picked.await.map(|f| f.path().to_path_buf()) })
})
.await
}
#[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))]
pub async fn pick<R: Runtime>(_app: &AppHandle<R>, _parent: Option<Window<R>>, _multiple: bool, _filters: Vec<Filter>) -> Result<Vec<PathBuf>> {
Err(Error::unsupported("dialog_unavailable"))
}
#[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))]
pub async fn pick_save<R: Runtime>(_app: &AppHandle<R>, _parent: Option<Window<R>>, _default_name: Option<String>, _directory: Option<PathBuf>) -> Result<Option<PathBuf>> {
Err(Error::unsupported("dialog_unavailable"))
}