#![allow(unused)] use std::any::Any; #[cfg(feature = "tokio_runtime")] use tokio::{fs::File, io::AsyncReadExt}; use dioxus_html::{ geometry::{ClientPoint, Coordinates, ElementPoint, PagePoint, ScreenPoint}, input_data::{MouseButton, MouseButtonSet}, point_interaction::{ InteractionElementOffset, InteractionLocation, ModifiersInteraction, PointerInteraction, }, prelude::{SerializedMouseData, SerializedPointInteraction}, FileEngine, HasDragData, HasFileData, HasFormData, HasMouseData, }; use serde::Deserialize; use std::{ cell::{Cell, RefCell}, path::PathBuf, rc::Rc, str::FromStr, sync::Arc, }; use wry::DragDropEvent; #[derive(Debug, Deserialize)] pub(crate) struct FileDialogRequest { #[serde(default)] accept: Option, multiple: bool, directory: bool, pub event: String, pub target: usize, pub bubbles: bool, } #[allow(unused)] impl FileDialogRequest { #[cfg(not(any( target_os = "windows", target_os = "macos", target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd" )))] pub(crate) fn get_file_event(&self) -> Vec { vec![] } #[cfg(any( target_os = "windows", target_os = "macos", target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd" ))] pub(crate) fn get_file_event(&self) -> Vec { fn get_file_event_for_folder( request: &FileDialogRequest, dialog: rfd::FileDialog, ) -> Vec { if request.multiple { dialog.pick_folders().into_iter().flatten().collect() } else { dialog.pick_folder().into_iter().collect() } } fn get_file_event_for_file( request: &FileDialogRequest, mut dialog: rfd::FileDialog, ) -> Vec { let filters: Vec<_> = request .accept .as_deref() .unwrap_or_default() .split(',') .filter_map(|s| Filters::from_str(s).ok()) .collect(); let file_extensions: Vec<_> = filters .iter() .flat_map(|f| f.as_extensions().into_iter()) .collect(); dialog = dialog.add_filter("name", file_extensions.as_slice()); let files: Vec<_> = if request.multiple { dialog.pick_files().into_iter().flatten().collect() } else { dialog.pick_file().into_iter().collect() }; files } let dialog = rfd::FileDialog::new(); if self.directory { get_file_event_for_folder(self, dialog) } else { get_file_event_for_file(self, dialog) } } } enum Filters { Extension(String), Mime(String), Audio, Video, Image, } impl Filters { fn as_extensions(&self) -> Vec<&str> { match self { Filters::Extension(extension) => vec![extension.as_str()], Filters::Mime(_) => vec![], Filters::Audio => vec!["mp3", "wav", "ogg"], Filters::Video => vec!["mp4", "webm"], Filters::Image => vec!["png", "jpg", "jpeg", "gif", "webp"], } } } impl FromStr for Filters { type Err = String; fn from_str(s: &str) -> Result { if let Some(extension) = s.strip_prefix('.') { Ok(Filters::Extension(extension.to_string())) } else { match s { "audio/*" => Ok(Filters::Audio), "video/*" => Ok(Filters::Video), "image/*" => Ok(Filters::Image), _ => Ok(Filters::Mime(s.to_string())), } } } } #[derive(Clone)] pub(crate) struct DesktopFileUploadForm { pub files: Arc, } impl HasFileData for DesktopFileUploadForm { fn files(&self) -> Option> { Some(self.files.clone()) } } impl HasFormData for DesktopFileUploadForm { fn as_any(&self) -> &dyn std::any::Any { self } } #[derive(Default, Clone)] pub struct NativeFileHover { event: Rc>>, } impl NativeFileHover { pub fn set(&self, event: DragDropEvent) { self.event.borrow_mut().replace(event); } pub fn current(&self) -> Option { self.event.borrow_mut().clone() } } #[derive(Clone)] pub(crate) struct DesktopFileDragEvent { pub mouse: SerializedPointInteraction, pub files: Arc, } impl HasFileData for DesktopFileDragEvent { fn files(&self) -> Option> { Some(self.files.clone()) } } impl HasDragData for DesktopFileDragEvent { fn as_any(&self) -> &dyn std::any::Any { self } } impl HasMouseData for DesktopFileDragEvent { fn as_any(&self) -> &dyn std::any::Any { self } } impl InteractionLocation for DesktopFileDragEvent { fn client_coordinates(&self) -> ClientPoint { self.mouse.client_coordinates() } fn page_coordinates(&self) -> PagePoint { self.mouse.page_coordinates() } fn screen_coordinates(&self) -> ScreenPoint { self.mouse.screen_coordinates() } } impl InteractionElementOffset for DesktopFileDragEvent { fn element_coordinates(&self) -> ElementPoint { self.mouse.element_coordinates() } fn coordinates(&self) -> Coordinates { self.mouse.coordinates() } } impl ModifiersInteraction for DesktopFileDragEvent { fn modifiers(&self) -> dioxus_html::prelude::Modifiers { self.mouse.modifiers() } } impl PointerInteraction for DesktopFileDragEvent { fn held_buttons(&self) -> MouseButtonSet { self.mouse.held_buttons() } fn trigger_button(&self) -> Option { self.mouse.trigger_button() } } pub struct NativeFileEngine { files: Vec, } impl NativeFileEngine { pub fn new(files: Vec) -> Self { Self { files } } } #[async_trait::async_trait(?Send)] impl FileEngine for NativeFileEngine { fn files(&self) -> Vec { self.files .iter() .filter_map(|f| Some(f.to_str()?.to_string())) .collect() } async fn file_size(&self, file: &str) -> Option { #[cfg(feature = "tokio_runtime")] { let file = File::open(file).await.ok()?; Some(file.metadata().await.ok()?.len()) } #[cfg(not(feature = "tokio_runtime"))] { None } } async fn read_file(&self, file: &str) -> Option> { #[cfg(feature = "tokio_runtime")] { let mut file = File::open(file).await.ok()?; let mut contents = Vec::new(); file.read_to_end(&mut contents).await.ok()?; Some(contents) } #[cfg(not(feature = "tokio_runtime"))] { None } } async fn read_file_to_string(&self, file: &str) -> Option { #[cfg(feature = "tokio_runtime")] { let mut file = File::open(file).await.ok()?; let mut contents = String::new(); file.read_to_string(&mut contents).await.ok()?; Some(contents) } #[cfg(not(feature = "tokio_runtime"))] { None } } async fn get_native_file(&self, file: &str) -> Option> { #[cfg(feature = "tokio_runtime")] { let file = File::open(file).await.ok()?; Some(Box::new(file)) } #[cfg(not(feature = "tokio_runtime"))] { None } } }