file_upload.rs 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. #![allow(unused)]
  2. use std::any::Any;
  3. #[cfg(feature = "tokio_runtime")]
  4. use tokio::{fs::File, io::AsyncReadExt};
  5. use dioxus_html::{
  6. geometry::{ClientPoint, Coordinates, ElementPoint, PagePoint, ScreenPoint},
  7. input_data::{MouseButton, MouseButtonSet},
  8. point_interaction::{
  9. InteractionElementOffset, InteractionLocation, ModifiersInteraction, PointerInteraction,
  10. },
  11. prelude::{SerializedMouseData, SerializedPointInteraction},
  12. FileEngine, HasDragData, HasFileData, HasFormData, HasMouseData,
  13. };
  14. use serde::Deserialize;
  15. use std::{
  16. cell::{Cell, RefCell},
  17. path::PathBuf,
  18. rc::Rc,
  19. str::FromStr,
  20. sync::Arc,
  21. };
  22. use wry::DragDropEvent;
  23. #[derive(Debug, Deserialize)]
  24. pub(crate) struct FileDialogRequest {
  25. #[serde(default)]
  26. accept: Option<String>,
  27. multiple: bool,
  28. directory: bool,
  29. pub event: String,
  30. pub target: usize,
  31. pub bubbles: bool,
  32. }
  33. #[allow(unused)]
  34. impl FileDialogRequest {
  35. #[cfg(not(any(
  36. target_os = "windows",
  37. target_os = "macos",
  38. target_os = "linux",
  39. target_os = "dragonfly",
  40. target_os = "freebsd",
  41. target_os = "netbsd",
  42. target_os = "openbsd"
  43. )))]
  44. pub(crate) fn get_file_event(&self) -> Vec<PathBuf> {
  45. vec![]
  46. }
  47. #[cfg(any(
  48. target_os = "windows",
  49. target_os = "macos",
  50. target_os = "linux",
  51. target_os = "dragonfly",
  52. target_os = "freebsd",
  53. target_os = "netbsd",
  54. target_os = "openbsd"
  55. ))]
  56. pub(crate) fn get_file_event(&self) -> Vec<PathBuf> {
  57. fn get_file_event_for_folder(
  58. request: &FileDialogRequest,
  59. dialog: rfd::FileDialog,
  60. ) -> Vec<PathBuf> {
  61. if request.multiple {
  62. dialog.pick_folders().into_iter().flatten().collect()
  63. } else {
  64. dialog.pick_folder().into_iter().collect()
  65. }
  66. }
  67. fn get_file_event_for_file(
  68. request: &FileDialogRequest,
  69. mut dialog: rfd::FileDialog,
  70. ) -> Vec<PathBuf> {
  71. let filters: Vec<_> = request
  72. .accept
  73. .as_deref()
  74. .unwrap_or_default()
  75. .split(',')
  76. .filter_map(|s| Filters::from_str(s).ok())
  77. .collect();
  78. let file_extensions: Vec<_> = filters
  79. .iter()
  80. .flat_map(|f| f.as_extensions().into_iter())
  81. .collect();
  82. dialog = dialog.add_filter("name", file_extensions.as_slice());
  83. let files: Vec<_> = if request.multiple {
  84. dialog.pick_files().into_iter().flatten().collect()
  85. } else {
  86. dialog.pick_file().into_iter().collect()
  87. };
  88. files
  89. }
  90. let dialog = rfd::FileDialog::new();
  91. if self.directory {
  92. get_file_event_for_folder(self, dialog)
  93. } else {
  94. get_file_event_for_file(self, dialog)
  95. }
  96. }
  97. }
  98. enum Filters {
  99. Extension(String),
  100. Mime(String),
  101. Audio,
  102. Video,
  103. Image,
  104. }
  105. impl Filters {
  106. fn as_extensions(&self) -> Vec<&str> {
  107. match self {
  108. Filters::Extension(extension) => vec![extension.as_str()],
  109. Filters::Mime(_) => vec![],
  110. Filters::Audio => vec!["mp3", "wav", "ogg"],
  111. Filters::Video => vec!["mp4", "webm"],
  112. Filters::Image => vec!["png", "jpg", "jpeg", "gif", "webp"],
  113. }
  114. }
  115. }
  116. impl FromStr for Filters {
  117. type Err = String;
  118. fn from_str(s: &str) -> Result<Self, Self::Err> {
  119. if let Some(extension) = s.strip_prefix('.') {
  120. Ok(Filters::Extension(extension.to_string()))
  121. } else {
  122. match s {
  123. "audio/*" => Ok(Filters::Audio),
  124. "video/*" => Ok(Filters::Video),
  125. "image/*" => Ok(Filters::Image),
  126. _ => Ok(Filters::Mime(s.to_string())),
  127. }
  128. }
  129. }
  130. }
  131. #[derive(Clone)]
  132. pub(crate) struct DesktopFileUploadForm {
  133. pub files: Arc<NativeFileEngine>,
  134. }
  135. impl HasFileData for DesktopFileUploadForm {
  136. fn files(&self) -> Option<Arc<dyn FileEngine>> {
  137. Some(self.files.clone())
  138. }
  139. }
  140. impl HasFormData for DesktopFileUploadForm {
  141. fn as_any(&self) -> &dyn std::any::Any {
  142. self
  143. }
  144. }
  145. #[derive(Default, Clone)]
  146. pub struct NativeFileHover {
  147. event: Rc<RefCell<Option<DragDropEvent>>>,
  148. }
  149. impl NativeFileHover {
  150. pub fn set(&self, event: DragDropEvent) {
  151. self.event.borrow_mut().replace(event);
  152. }
  153. pub fn current(&self) -> Option<DragDropEvent> {
  154. self.event.borrow_mut().clone()
  155. }
  156. }
  157. #[derive(Clone)]
  158. pub(crate) struct DesktopFileDragEvent {
  159. pub mouse: SerializedPointInteraction,
  160. pub files: Arc<NativeFileEngine>,
  161. }
  162. impl HasFileData for DesktopFileDragEvent {
  163. fn files(&self) -> Option<Arc<dyn FileEngine>> {
  164. Some(self.files.clone())
  165. }
  166. }
  167. impl HasDragData for DesktopFileDragEvent {
  168. fn as_any(&self) -> &dyn std::any::Any {
  169. self
  170. }
  171. }
  172. impl HasMouseData for DesktopFileDragEvent {
  173. fn as_any(&self) -> &dyn std::any::Any {
  174. self
  175. }
  176. }
  177. impl InteractionLocation for DesktopFileDragEvent {
  178. fn client_coordinates(&self) -> ClientPoint {
  179. self.mouse.client_coordinates()
  180. }
  181. fn page_coordinates(&self) -> PagePoint {
  182. self.mouse.page_coordinates()
  183. }
  184. fn screen_coordinates(&self) -> ScreenPoint {
  185. self.mouse.screen_coordinates()
  186. }
  187. }
  188. impl InteractionElementOffset for DesktopFileDragEvent {
  189. fn element_coordinates(&self) -> ElementPoint {
  190. self.mouse.element_coordinates()
  191. }
  192. fn coordinates(&self) -> Coordinates {
  193. self.mouse.coordinates()
  194. }
  195. }
  196. impl ModifiersInteraction for DesktopFileDragEvent {
  197. fn modifiers(&self) -> dioxus_html::prelude::Modifiers {
  198. self.mouse.modifiers()
  199. }
  200. }
  201. impl PointerInteraction for DesktopFileDragEvent {
  202. fn held_buttons(&self) -> MouseButtonSet {
  203. self.mouse.held_buttons()
  204. }
  205. fn trigger_button(&self) -> Option<MouseButton> {
  206. self.mouse.trigger_button()
  207. }
  208. }
  209. pub struct NativeFileEngine {
  210. files: Vec<PathBuf>,
  211. }
  212. impl NativeFileEngine {
  213. pub fn new(files: Vec<PathBuf>) -> Self {
  214. Self { files }
  215. }
  216. }
  217. #[async_trait::async_trait(?Send)]
  218. impl FileEngine for NativeFileEngine {
  219. fn files(&self) -> Vec<String> {
  220. self.files
  221. .iter()
  222. .filter_map(|f| Some(f.to_str()?.to_string()))
  223. .collect()
  224. }
  225. async fn file_size(&self, file: &str) -> Option<u64> {
  226. #[cfg(feature = "tokio_runtime")]
  227. {
  228. let file = File::open(file).await.ok()?;
  229. Some(file.metadata().await.ok()?.len())
  230. }
  231. #[cfg(not(feature = "tokio_runtime"))]
  232. {
  233. None
  234. }
  235. }
  236. async fn read_file(&self, file: &str) -> Option<Vec<u8>> {
  237. #[cfg(feature = "tokio_runtime")]
  238. {
  239. let mut file = File::open(file).await.ok()?;
  240. let mut contents = Vec::new();
  241. file.read_to_end(&mut contents).await.ok()?;
  242. Some(contents)
  243. }
  244. #[cfg(not(feature = "tokio_runtime"))]
  245. {
  246. None
  247. }
  248. }
  249. async fn read_file_to_string(&self, file: &str) -> Option<String> {
  250. #[cfg(feature = "tokio_runtime")]
  251. {
  252. let mut file = File::open(file).await.ok()?;
  253. let mut contents = String::new();
  254. file.read_to_string(&mut contents).await.ok()?;
  255. Some(contents)
  256. }
  257. #[cfg(not(feature = "tokio_runtime"))]
  258. {
  259. None
  260. }
  261. }
  262. async fn get_native_file(&self, file: &str) -> Option<Box<dyn Any>> {
  263. #[cfg(feature = "tokio_runtime")]
  264. {
  265. let file = File::open(file).await.ok()?;
  266. Some(Box::new(file))
  267. }
  268. #[cfg(not(feature = "tokio_runtime"))]
  269. {
  270. None
  271. }
  272. }
  273. }