diff --git a/packages/core/src/diff/node.rs b/packages/core/src/diff/node.rs new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/packages/core/src/diff/node.rs @@ -0,0 +1 @@ + diff --git a/packages/desktop/src/app.rs b/packages/desktop/src/app.rs index 8d26a48f1..bcf012ea1 100644 --- a/packages/desktop/src/app.rs +++ b/packages/desktop/src/app.rs @@ -12,7 +12,7 @@ use crate::{ use crossbeam_channel::Receiver; use dioxus_core::{Component, ElementId, VirtualDom}; use dioxus_html::{ - native_bind::NativeFileEngine, FileEngine, HasFormData, HtmlEvent, MountedData, + native_bind::NativeFileEngine, FileEngine, HasFileData, HasFormData, HtmlEvent, MountedData, PlatformEventData, SerializedHtmlEventConverter, }; use std::{ @@ -280,11 +280,13 @@ impl App

{ files: Arc, } - impl HasFormData for DesktopFileUploadForm { + 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 } diff --git a/packages/desktop/src/webview.rs b/packages/desktop/src/webview.rs index 9684142af..b23bd5ddd 100644 --- a/packages/desktop/src/webview.rs +++ b/packages/desktop/src/webview.rs @@ -94,11 +94,10 @@ impl WebviewInstance { .unwrap() .with_ipc_handler(ipc_handler) .with_asynchronous_custom_protocol(String::from("dioxus"), request_handler) - .with_file_drop_handler(file_drop_handler) .with_web_context(&mut web_context); if let Some(handler) = file_handler { - webview = webview.with_file_drop_handler(handler) + webview = webview.with_file_drop_handler(move |evt| handler(window_id, evt)) } // This was removed from wry, I'm not sure what replaced it diff --git a/packages/html/src/events/drag.rs b/packages/html/src/events/drag.rs index a985e5ecd..d8770872d 100644 --- a/packages/html/src/events/drag.rs +++ b/packages/html/src/events/drag.rs @@ -1,14 +1,13 @@ +use crate::file_data::{FileEngine, HasFileData}; use crate::geometry::{ClientPoint, Coordinates, ElementPoint, PagePoint, ScreenPoint}; use crate::input_data::{MouseButton, MouseButtonSet}; use crate::prelude::*; -use crate::FileEngine; use dioxus_core::Event; use keyboard_types::Modifiers; use crate::HasMouseData; -use dioxus_core::Event; use std::fmt::Debug; pub type DragEvent = Event; @@ -57,11 +56,12 @@ impl DragData { /// Downcast this event data to a specific type pub fn downcast(&self) -> Option<&T> { - self.inner.as_any().downcast_ref::() + HasDragData::as_any(&*self.inner).downcast_ref::() } +} - /// Get the files of the form event - pub fn files(&self) -> Option> { +impl HasFileData for DragData { + fn files(&self) -> Option> { self.inner.files() } } @@ -112,19 +112,34 @@ impl PointerInteraction for DragData { #[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone)] pub struct SerializedDragData { mouse: crate::point_interaction::SerializedPointInteraction, + files: Option, } #[cfg(feature = "serialize")] -impl From<&DragData> for SerializedDragData { - fn from(data: &DragData) -> Self { +impl SerializedDragData { + fn new(drag: &DragData, files: Option) -> Self { Self { - mouse: crate::point_interaction::SerializedPointInteraction::from(data), + mouse: crate::point_interaction::SerializedPointInteraction::from(drag), + files, } } } #[cfg(feature = "serialize")] -impl HasDragData for SerializedDragData {} +impl HasDragData for SerializedDragData { + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + +#[cfg(feature = "serialize")] +impl HasFileData for SerializedDragData { + fn files(&self) -> Option> { + self.files + .as_ref() + .map(|files| std::sync::Arc::new(files.clone()) as _) + } +} #[cfg(feature = "serialize")] impl HasMouseData for SerializedDragData { @@ -180,7 +195,7 @@ impl PointerInteraction for SerializedDragData { #[cfg(feature = "serialize")] impl serde::Serialize for DragData { fn serialize(&self, serializer: S) -> Result { - SerializedDragData::from(self).serialize(serializer) + SerializedDragData::new(self, None).serialize(serializer) } } @@ -195,7 +210,10 @@ impl<'de> serde::Deserialize<'de> for DragData { } /// A trait for any object that has the data for a drag event -pub trait HasDragData: HasMouseData {} +pub trait HasDragData: HasMouseData + HasFileData { + /// return self as Any + fn as_any(&self) -> &dyn std::any::Any; +} impl_event! { DragData; diff --git a/packages/html/src/events/form.rs b/packages/html/src/events/form.rs index 8f83a3a66..d22a5219f 100644 --- a/packages/html/src/events/form.rs +++ b/packages/html/src/events/form.rs @@ -1,4 +1,6 @@ -use std::{any::Any, collections::HashMap, fmt::Debug}; +use crate::file_data::FileEngine; +use crate::file_data::HasFileData; +use std::{collections::HashMap, fmt::Debug}; use dioxus_core::Event; @@ -116,7 +118,7 @@ impl FormData { pub struct SerializedFormData { value: String, values: HashMap, - files: Option, + files: Option, } #[cfg(feature = "serialize")] @@ -125,7 +127,7 @@ impl SerializedFormData { pub fn new( value: String, values: HashMap, - files: Option, + files: Option, ) -> Self { Self { value, @@ -148,7 +150,7 @@ impl SerializedFormData { resolved_files.insert(file, bytes.unwrap_or_default()); } - Some(SerializedFileEngine { + Some(crate::file_data::SerializedFileEngine { files: resolved_files, }) } diff --git a/packages/html/src/file_data.rs b/packages/html/src/file_data.rs new file mode 100644 index 000000000..e206015bb --- /dev/null +++ b/packages/html/src/file_data.rs @@ -0,0 +1,53 @@ +use std::{any::Any, collections::HashMap}; + +pub trait HasFileData: std::any::Any { + fn files(&self) -> Option> { + None + } +} + +#[cfg(feature = "serialize")] +/// A file engine that serializes files to bytes +#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone)] +pub struct SerializedFileEngine { + pub files: HashMap>, +} + +#[cfg(feature = "serialize")] +#[async_trait::async_trait(?Send)] +impl FileEngine for SerializedFileEngine { + fn files(&self) -> Vec { + self.files.keys().cloned().collect() + } + + async fn read_file(&self, file: &str) -> Option> { + self.files.get(file).cloned() + } + + async fn read_file_to_string(&self, file: &str) -> Option { + self.read_file(file) + .await + .map(|bytes| String::from_utf8_lossy(&bytes).to_string()) + } + + async fn get_native_file(&self, file: &str) -> Option> { + self.read_file(file) + .await + .map(|val| Box::new(val) as Box) + } +} + +#[async_trait::async_trait(?Send)] +pub trait FileEngine { + // get a list of file names + fn files(&self) -> Vec; + + // read a file to bytes + async fn read_file(&self, file: &str) -> Option>; + + // read a file to string + async fn read_file_to_string(&self, file: &str) -> Option; + + // returns a file in platform's native representation + async fn get_native_file(&self, file: &str) -> Option>; +} diff --git a/packages/html/src/lib.rs b/packages/html/src/lib.rs index 379b5f581..17ca25aef 100644 --- a/packages/html/src/lib.rs +++ b/packages/html/src/lib.rs @@ -22,6 +22,8 @@ pub use elements::HtmlCtx; #[cfg(feature = "html-to-rsx")] pub use elements::{map_html_attribute_to_rsx, map_html_element_to_rsx}; pub mod events; +pub(crate) mod file_data; +pub use file_data::*; pub mod geometry; mod global_attributes; pub mod input_data; diff --git a/packages/html/src/native_bind/native_file_engine.rs b/packages/html/src/native_bind/native_file_engine.rs index 88b996a4d..81070bf0d 100644 --- a/packages/html/src/native_bind/native_file_engine.rs +++ b/packages/html/src/native_bind/native_file_engine.rs @@ -1,10 +1,11 @@ use std::any::Any; use std::path::PathBuf; -use crate::FileEngine; use tokio::fs::File; use tokio::io::AsyncReadExt; +use crate::file_data::FileEngine; + pub struct NativeFileEngine { files: Vec, } diff --git a/packages/html/src/web_sys_bind/events.rs b/packages/html/src/web_sys_bind/events.rs index b1e6c5341..40d14f28b 100644 --- a/packages/html/src/web_sys_bind/events.rs +++ b/packages/html/src/web_sys_bind/events.rs @@ -3,6 +3,7 @@ use crate::events::{ AnimationData, CompositionData, KeyboardData, MouseData, PointerData, TouchData, TransitionData, WheelData, }; +use crate::file_data::{FileEngine, HasFileData}; use crate::geometry::{ClientPoint, ElementPoint, PagePoint, ScreenPoint}; use crate::input_data::{decode_key_location, decode_mouse_button_set, MouseButton}; use crate::prelude::*; @@ -103,8 +104,6 @@ impl ModifiersInteraction for KeyboardEvent { } } -impl HasDragData for MouseEvent {} - impl InteractionLocation for MouseEvent { fn client_coordinates(&self) -> ClientPoint { ClientPoint::new(self.client_x().into(), self.client_y().into()) @@ -162,6 +161,14 @@ impl HasMouseData for MouseEvent { } } +impl HasFileData for MouseEvent {} + +impl HasDragData for MouseEvent { + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + impl ModifiersInteraction for TouchEvent { fn modifiers(&self) -> Modifiers { let mut modifiers = Modifiers::empty(); @@ -512,3 +519,23 @@ impl HasMediaData for web_sys::Event { self } } + +impl HasFileData for web_sys::Event { + fn files(&self) -> Option> { + #[cfg(not(feature = "file_engine"))] + let files = None; + #[cfg(feature = "file_engine")] + let files = element + .dyn_ref() + .and_then(|input: &web_sys::HtmlInputElement| { + input.files().and_then(|files| { + #[allow(clippy::arc_with_non_send_sync)] + crate::file_engine::WebFileEngine::new(files).map(|f| { + std::sync::Arc::new(f) as std::sync::Arc + }) + }) + }); + + files + } +} diff --git a/packages/rink/src/hooks.rs b/packages/rink/src/hooks.rs index 012c0c1eb..d519273f0 100644 --- a/packages/rink/src/hooks.rs +++ b/packages/rink/src/hooks.rs @@ -3,8 +3,8 @@ use crossterm::event::{ MouseEventKind, }; use dioxus_html::{ - HasFormData, HasKeyboardData, HasWheelData, SerializedFocusData, SerializedKeyboardData, - SerializedMouseData, SerializedWheelData, + HasFileData, HasFormData, HasKeyboardData, HasWheelData, SerializedFocusData, + SerializedKeyboardData, SerializedMouseData, SerializedWheelData, }; use dioxus_native_core::prelude::*; use dioxus_native_core::real_dom::NodeImmutable; @@ -86,6 +86,8 @@ impl HasFormData for FormData { } } +impl HasFileData for FormData {} + #[derive(Clone, Debug, PartialEq)] pub struct Files { files: FxHashMap, diff --git a/packages/web/src/event.rs b/packages/web/src/event.rs index 994bf304a..ae21414db 100644 --- a/packages/web/src/event.rs +++ b/packages/web/src/event.rs @@ -1,12 +1,16 @@ use std::{any::Any, collections::HashMap}; use dioxus_html::{ - prelude::FormValue, FileEngine, FormData, HasFormData, HasImageData, HtmlEventConverter, - ImageData, MountedData, PlatformEventData, ScrollData, + point_interaction::{ + InteractionElementOffset, InteractionLocation, ModifiersInteraction, PointerInteraction, + }, + prelude::FormValue, + DragData, FileEngine, FormData, HasDragData, HasFileData, HasFormData, HasImageData, + HasMouseData, HtmlEventConverter, ImageData, MountedData, PlatformEventData, ScrollData, }; use js_sys::Array; use wasm_bindgen::{prelude::wasm_bindgen, JsCast, JsValue}; -use web_sys::{Document, Element, Event}; +use web_sys::{Document, Element, Event, MouseEvent}; pub(crate) struct WebEventConverter; @@ -44,7 +48,11 @@ impl HtmlEventConverter for WebEventConverter { #[inline(always)] fn convert_drag_data(&self, event: &dioxus_html::PlatformEventData) -> dioxus_html::DragData { - downcast_event(event).raw.clone().into() + let event = downcast_event(event); + DragData::new(WebDragData::new( + event.element.clone(), + event.raw.clone().unchecked_into(), + )) } #[inline(always)] @@ -180,8 +188,10 @@ impl WebEventExt for dioxus_html::CompositionData { impl WebEventExt for dioxus_html::DragData { fn web_event(&self) -> &web_sys::MouseEvent { - self.downcast::() + &self + .downcast::() .expect("event should be a WebMouseEvent") + .raw } } @@ -403,6 +413,12 @@ impl HasFormData for WebFormData { values } + fn as_any(&self) -> &dyn Any { + &self.raw as &dyn Any + } +} + +impl HasFileData for WebFormData { fn files(&self) -> Option> { #[cfg(not(feature = "file_engine"))] let files = None; @@ -421,9 +437,89 @@ impl HasFormData for WebFormData { files } +} - fn as_any(&self) -> &dyn Any { - &self.raw as &dyn Any +struct WebDragData { + element: Element, + raw: MouseEvent, +} + +impl WebDragData { + fn new(element: Element, raw: MouseEvent) -> Self { + Self { element, raw } + } +} + +impl HasDragData for WebDragData { + fn as_any(&self) -> &dyn std::any::Any { + &self.raw as &dyn std::any::Any + } +} + +impl HasMouseData for WebDragData { + fn as_any(&self) -> &dyn std::any::Any { + &self.raw as &dyn std::any::Any + } +} + +impl PointerInteraction for WebDragData { + fn trigger_button(&self) -> Option { + self.raw.trigger_button() + } + + fn held_buttons(&self) -> dioxus_html::input_data::MouseButtonSet { + self.raw.held_buttons() + } +} + +impl ModifiersInteraction for WebDragData { + fn modifiers(&self) -> dioxus_html::prelude::Modifiers { + self.raw.modifiers() + } +} + +impl InteractionElementOffset for WebDragData { + fn coordinates(&self) -> dioxus_html::geometry::Coordinates { + self.raw.coordinates() + } + + fn element_coordinates(&self) -> dioxus_html::geometry::ElementPoint { + self.raw.element_coordinates() + } +} + +impl InteractionLocation for WebDragData { + fn client_coordinates(&self) -> dioxus_html::geometry::ClientPoint { + self.raw.client_coordinates() + } + + fn screen_coordinates(&self) -> dioxus_html::geometry::ScreenPoint { + self.raw.screen_coordinates() + } + + fn page_coordinates(&self) -> dioxus_html::geometry::PagePoint { + self.raw.page_coordinates() + } +} + +impl HasFileData for WebDragData { + fn files(&self) -> Option> { + #[cfg(not(feature = "file_engine"))] + let files = None; + #[cfg(feature = "file_engine")] + let files = self + .element + .dyn_ref() + .and_then(|input: &web_sys::HtmlInputElement| { + input.files().and_then(|files| { + #[allow(clippy::arc_with_non_send_sync)] + crate::file_engine::WebFileEngine::new(files).map(|f| { + std::sync::Arc::new(f) as std::sync::Arc + }) + }) + }); + + files } }