From ada246c12e336a4700611d4d9cab318074842dea Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 27 Apr 2023 10:21:05 -0500 Subject: [PATCH 1/7] add file property to drag data --- examples/file_upload.rs | 27 +++++- packages/desktop/src/cfg.rs | 2 +- packages/desktop/src/webview.rs | 10 +-- packages/html/Cargo.toml | 1 + packages/html/src/events/drag.rs | 34 +++++++- packages/html/src/events/form.rs | 2 +- packages/html/src/web_sys_bind/events.rs | 1 + packages/interpreter/src/interpreter.js | 105 ++++++++++++----------- packages/liveview/src/lib.rs | 14 +-- packages/web/Cargo.toml | 3 +- packages/web/src/dom.rs | 11 ++- 11 files changed, 133 insertions(+), 77 deletions(-) diff --git a/examples/file_upload.rs b/examples/file_upload.rs index b23648b50..a1d2ff88c 100644 --- a/examples/file_upload.rs +++ b/examples/file_upload.rs @@ -2,10 +2,10 @@ use dioxus::prelude::*; fn main() { - dioxus_desktop::launch(App); + dioxus_desktop::launch(app); } -fn App(cx: Scope) -> Element { +fn app(cx: Scope) -> Element { let files_uploaded: &UseRef> = use_ref(cx, Vec::new); cx.render(rsx! { @@ -27,6 +27,29 @@ fn App(cx: Scope) -> Element { } }, } + div { + width: "100px", + height: "100px", + border: "1px solid black", + prevent_default: "ondrop dragover dragenter", + ondrop: move |evt| { + to_owned![files_uploaded]; + async move { + if let Some(file_engine) = &evt.files { + let files = file_engine.files(); + for file_name in &files { + if let Some(file) = file_engine.read_file_to_string(file_name).await{ + files_uploaded.write().push(file); + } + } + } + } + }, + ondragover: move |event: DragEvent| { + event.stop_propagation(); + }, + "Drop files here" + } ul { for file in files_uploaded.read().iter() { diff --git a/packages/desktop/src/cfg.rs b/packages/desktop/src/cfg.rs index d7f1b5dfa..2d8578644 100644 --- a/packages/desktop/src/cfg.rs +++ b/packages/desktop/src/cfg.rs @@ -87,7 +87,7 @@ impl Config { self } - /// Set a file drop handler + /// Set a file drop handler. If this is enabled, html drag events will be disabled. pub fn with_file_drop_handler( mut self, handler: impl Fn(&Window, FileDropEvent) -> bool + 'static, diff --git a/packages/desktop/src/webview.rs b/packages/desktop/src/webview.rs index 5d3acf44f..7eb6a4490 100644 --- a/packages/desktop/src/webview.rs +++ b/packages/desktop/src/webview.rs @@ -49,14 +49,12 @@ pub fn build( .with_custom_protocol(String::from("dioxus"), move |r| { protocol::desktop_handler(r, custom_head.clone(), index_file.clone(), &root_name) }) - .with_file_drop_handler(move |window, evet| { - file_handler - .as_ref() - .map(|handler| handler(window, evet)) - .unwrap_or_default() - }) .with_web_context(&mut web_context); + if let Some(handler) = file_handler { + webview = webview.with_file_drop_handler(handler) + } + #[cfg(windows)] { // Windows has a platform specific settings to disable the browser shortcut keys diff --git a/packages/html/Cargo.toml b/packages/html/Cargo.toml index 91917ee4b..95700f153 100644 --- a/packages/html/Cargo.toml +++ b/packages/html/Cargo.toml @@ -41,6 +41,7 @@ features = [ "FocusEvent", "CompositionEvent", "ClipboardEvent", + "DragEvent" ] [dev-dependencies] diff --git a/packages/html/src/events/drag.rs b/packages/html/src/events/drag.rs index fa3219631..323bcc87a 100644 --- a/packages/html/src/events/drag.rs +++ b/packages/html/src/events/drag.rs @@ -1,7 +1,9 @@ -use dioxus_core::Event; - +use crate::FileEngine; use crate::MouseData; +use dioxus_core::Event; +use std::fmt::Debug; + pub type DragEvent = Event; /// The DragEvent interface is a DOM event that represents a drag and drop interaction. The user initiates a drag by @@ -9,12 +11,38 @@ pub type DragEvent = Event; /// (such as another DOM element). Applications are free to interpret a drag and drop interaction in an /// application-specific way. #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Clone)] pub struct DragData { /// Inherit mouse data pub mouse: MouseData, + + #[cfg_attr( + feature = "serialize", + serde( + default, + skip_serializing, + deserialize_with = "crate::events::deserialize_file_engine" + ) + )] + pub files: Option>, } +impl Debug for DragData { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("DragData") + .field("mouse", &self.mouse) + .finish() + } +} + +impl PartialEq for DragData { + fn eq(&self, other: &Self) -> bool { + self.mouse == other.mouse + } +} + +impl Eq for DragData {} + impl_event! { DragData; diff --git a/packages/html/src/events/form.rs b/packages/html/src/events/form.rs index 113c765c5..4fa09d2a6 100644 --- a/packages/html/src/events/form.rs +++ b/packages/html/src/events/form.rs @@ -48,7 +48,7 @@ impl FileEngine for SerializedFileEngine { } #[cfg(feature = "serialize")] -fn deserialize_file_engine<'de, D>( +pub(crate) fn deserialize_file_engine<'de, D>( deserializer: D, ) -> Result>, D::Error> where diff --git a/packages/html/src/web_sys_bind/events.rs b/packages/html/src/web_sys_bind/events.rs index 196099444..0204bdd3d 100644 --- a/packages/html/src/web_sys_bind/events.rs +++ b/packages/html/src/web_sys_bind/events.rs @@ -123,6 +123,7 @@ impl From<&MouseEvent> for DragData { fn from(value: &MouseEvent) -> Self { Self { mouse: MouseData::from(value), + files: None, } } } diff --git a/packages/interpreter/src/interpreter.js b/packages/interpreter/src/interpreter.js index 9b93ec9e3..e35105044 100644 --- a/packages/interpreter/src/interpreter.js +++ b/packages/interpreter/src/interpreter.js @@ -351,7 +351,11 @@ class Interpreter { let handler = (event) => { let target = event.target; if (target != null) { - let realId = target.getAttribute(`data-dioxus-id`); + const realId = find_real_id(target); + if (realId === null) { + return; + } + let shouldPreventDefault = target.getAttribute( `dioxus-prevent-default` ); @@ -379,22 +383,6 @@ class Interpreter { event.preventDefault(); } } - // walk the tree to find the real element - while (realId == null) { - // we've reached the root we don't want to send an event - if (target.parentElement === null) { - return; - } - - target = target.parentElement; - realId = target.getAttribute(`data-dioxus-id`); - } - - shouldPreventDefault = target.getAttribute( - `dioxus-prevent-default` - ); - - let contents = serialize_event(event); if (shouldPreventDefault === `on${event.type}`) { event.preventDefault(); @@ -404,41 +392,42 @@ class Interpreter { event.preventDefault(); } - if ( - target.tagName === "FORM" && - (event.type === "submit" || event.type === "input") - ) { - for (let x = 0; x < target.elements.length; x++) { - let element = target.elements[x]; - let name = element.getAttribute("name"); - if (name != null) { - if (element.getAttribute("type") === "checkbox") { - // @ts-ignore - contents.values[name] = element.checked ? "true" : "false"; - } else if (element.getAttribute("type") === "radio") { - if (element.checked) { - contents.values[name] = element.value; + serialize_event(event).then((contents) => { + if ( + target.tagName === "FORM" && + (event.type === "submit" || event.type === "input") + ) { + for (let x = 0; x < target.elements.length; x++) { + let element = target.elements[x]; + let name = element.getAttribute("name"); + if (name != null) { + if (element.getAttribute("type") === "checkbox") { + // @ts-ignore + contents.values[name] = element.checked + ? "true" + : "false"; + } else if (element.getAttribute("type") === "radio") { + if (element.checked) { + contents.values[name] = element.value; + } + } else { + // @ts-ignore + contents.values[name] = + element.value ?? element.textContent; } - } else { - // @ts-ignore - contents.values[name] = - element.value ?? element.textContent; } } } - } - if (realId === null) { - return; - } - window.ipc.postMessage( - serializeIpcMessage("user_event", { - name: edit.name, - element: parseInt(realId), - data: contents, - bubbles, - }) - ); + window.ipc.postMessage( + serializeIpcMessage("user_event", { + name: edit.name, + element: parseInt(realId), + data: contents, + bubbles, + }) + ); + }); } }; this.NewEventListener(edit.name, edit.id, bubbles, handler); @@ -575,7 +564,7 @@ function get_mouse_data(event) { }; } -function serialize_event(event) { +async function serialize_event(event) { switch (event.type) { case "copy": case "cut": @@ -661,7 +650,11 @@ function serialize_event(event) { case "dragover": case "dragstart": case "drop": { - return { mouse: get_mouse_data(event) }; + let files = null; + if (event.dataTransfer && event.dataTransfer.files) { + files = await serializeFileList(event.dataTransfer.files); + } + return { mouse: get_mouse_data(event), files }; } case "click": case "contextmenu": @@ -816,6 +809,20 @@ function serialize_event(event) { } } } +async function serializeFileList(fileList) { + const file_contents = {}; + + for (let i = 0; i < fileList.length; i++) { + const file = fileList[i]; + + file_contents[file.name] = Array.from( + new Uint8Array(await file.arrayBuffer()) + ); + } + return { + files: file_contents, + }; +} function serializeIpcMessage(method, params = {}) { return JSON.stringify({ method, params }); } diff --git a/packages/liveview/src/lib.rs b/packages/liveview/src/lib.rs index bea21a71b..b35d36ab2 100644 --- a/packages/liveview/src/lib.rs +++ b/packages/liveview/src/lib.rs @@ -45,19 +45,7 @@ static INTERPRETER_JS: Lazy = Lazy::new(|| { const type = target.getAttribute("type"); if (type === "file") { async function read_files() { - const files = target.files; - const file_contents = {}; - - for (let i = 0; i < files.length; i++) { - const file = files[i]; - - file_contents[file.name] = Array.from( - new Uint8Array(await file.arrayBuffer()) - ); - } - let file_engine = { - files: file_contents, - }; + const file_engine=await serializeFileList( target.files); contents.files = file_engine; if (realId === null) { diff --git a/packages/web/Cargo.toml b/packages/web/Cargo.toml index fcbb996bb..7730bacc8 100644 --- a/packages/web/Cargo.toml +++ b/packages/web/Cargo.toml @@ -79,7 +79,8 @@ features = [ "console", "FileList", "File", - "FileReader" + "FileReader", + "DataTransfer" ] [features] diff --git a/packages/web/src/dom.rs b/packages/web/src/dom.rs index e1e3b6365..b66918c38 100644 --- a/packages/web/src/dom.rs +++ b/packages/web/src/dom.rs @@ -244,8 +244,17 @@ pub fn virtual_event_from_websys_event(event: web_sys::Event, target: Element) - } "drag" | "dragend" | "dragenter" | "dragexit" | "dragleave" | "dragover" | "dragstart" | "drop" => { + let mut files = None; + if let Some(event) = event.dyn_ref::() { + if let Some(data) = event.data_transfer() { + if let Some(file_list) = data.files() { + files = WebFileEngine::new(file_list) + .map(|f| Arc::new(f) as Arc); + } + } + } let mouse = MouseData::from(event); - Rc::new(DragData { mouse }) + Rc::new(DragData { mouse, files }) } "pointerdown" | "pointermove" | "pointerup" | "pointercancel" | "gotpointercapture" From 26f6561de56636b186f815b65c884d0a4c409cb4 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 11 Jul 2023 17:55:55 -0500 Subject: [PATCH 2/7] fix html cargo.toml --- packages/html/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/html/Cargo.toml b/packages/html/Cargo.toml index 23787286c..0ef32fb15 100644 --- a/packages/html/Cargo.toml +++ b/packages/html/Cargo.toml @@ -39,7 +39,7 @@ features = [ "FocusEvent", "CompositionEvent", "ClipboardEvent", - "DragEvent" + "DragEvent", "Element", "DomRect", "ScrollIntoViewOptions", From 8d38b6c11b0c2d328607a5a50753784b609318b8 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 11 Jul 2023 18:03:13 -0500 Subject: [PATCH 3/7] fix interpreter js --- packages/interpreter/src/interpreter.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/interpreter/src/interpreter.js b/packages/interpreter/src/interpreter.js index 03b1f390a..759f52e34 100644 --- a/packages/interpreter/src/interpreter.js +++ b/packages/interpreter/src/interpreter.js @@ -380,7 +380,7 @@ class Interpreter { // this handler is only provided on the desktop and liveview implementations since this // method is not used by the web implementation -function handler(event, name, bubbles, config) { +async function handler(event, name, bubbles, config) { let target = event.target; if (target != null) { let preventDefaultRequests = target.getAttribute(`dioxus-prevent-default`); @@ -431,7 +431,7 @@ function handler(event, name, bubbles, config) { event.preventDefault(); } - let contents = serialize_event(event); + let contents = await serialize_event(event); /*POST_EVENT_SERIALIZATION*/ @@ -606,6 +606,7 @@ async function serialize_event(event) { if (event.dataTransfer && event.dataTransfer.files) { files = await serializeFileList(event.dataTransfer.files); } + return { mouse: get_mouse_data(event), files }; } case "click": From fe971ce181291bbdb432f5d03422f3506f3fac5e Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 20 Jul 2023 13:20:14 -0700 Subject: [PATCH 4/7] fix file upload example --- examples/file_upload.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/file_upload.rs b/examples/file_upload.rs index b498c5250..f5deab173 100644 --- a/examples/file_upload.rs +++ b/examples/file_upload.rs @@ -3,7 +3,7 @@ use dioxus::prelude::*; use tokio::time::sleep; fn main() { - dioxus_desktop::launch(app); + dioxus_desktop::launch(App); } fn App(cx: Scope) -> Element { From 22c659d9952fa79d68d35af0da685aa5679aeffd Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sun, 17 Sep 2023 19:01:00 -0500 Subject: [PATCH 5/7] fix formatting --- packages/fullstack/src/adapters/warp_adapter.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/fullstack/src/adapters/warp_adapter.rs b/packages/fullstack/src/adapters/warp_adapter.rs index 5afcb5077..316d70e56 100644 --- a/packages/fullstack/src/adapters/warp_adapter.rs +++ b/packages/fullstack/src/adapters/warp_adapter.rs @@ -143,7 +143,7 @@ pub fn register_server_fns(server_fn_route: &'static str) -> BoxedFilter<(impl R let req = warp::hyper::Request::from_parts(parts, bytes.into()); service.run(req).await.map_err(|err| { tracing::error!("Server function error: {}", err); - + struct WarpServerFnError(String); impl std::fmt::Debug for WarpServerFnError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -152,7 +152,7 @@ pub fn register_server_fns(server_fn_route: &'static str) -> BoxedFilter<(impl R } impl warp::reject::Reject for WarpServerFnError {} - + warp::reject::custom(WarpServerFnError(err.to_string())) }) } From d3eefe4a27f85812ba4044c94cc37c8f087be2b3 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 19 Sep 2023 09:16:58 -0500 Subject: [PATCH 6/7] fix clippy --- packages/core/src/scope_context.rs | 2 +- packages/web/src/dom.rs | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/core/src/scope_context.rs b/packages/core/src/scope_context.rs index 79ed05d75..59bed3b1f 100644 --- a/packages/core/src/scope_context.rs +++ b/packages/core/src/scope_context.rs @@ -128,7 +128,7 @@ impl ScopeContext { parent.name ); if let Some(shared) = parent.shared_contexts.borrow().iter().find_map(|any| { - tracing::trace!("found context {:?}", any.type_id()); + tracing::trace!("found context {:?}", (**any).type_id()); any.downcast_ref::() }) { return Some(shared.clone()); diff --git a/packages/web/src/dom.rs b/packages/web/src/dom.rs index b47f71757..183eb9c14 100644 --- a/packages/web/src/dom.rs +++ b/packages/web/src/dom.rs @@ -7,7 +7,6 @@ //! - tests to ensure dyn_into works for various event types. //! - Partial delegation?> -use crate::file_engine::WebFileEngine; use dioxus_core::{ BorrowedAttributeValue, ElementId, Mutation, Template, TemplateAttribute, TemplateNode, }; @@ -288,8 +287,10 @@ pub fn virtual_event_from_websys_event(event: web_sys::Event, target: Element) - let mut files = None; if let Some(event) = event.dyn_ref::() { if let Some(data) = event.data_transfer() { + #[cfg(feature = "file_engine")] + #[allow(clippy::arc_with_non_send_sync)] if let Some(file_list) = data.files() { - files = WebFileEngine::new(file_list) + files = crate::file_engine::WebFileEngine::new(file_list) .map(|f| Arc::new(f) as Arc); } } From 194c9d43e3ed1fdbe6fe77b66014708b3ed6ffd5 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 8 Jan 2024 14:51:35 -0600 Subject: [PATCH 7/7] fix merge --- packages/core/src/diff/node.rs | 1 + packages/desktop/src/app.rs | 6 +- packages/desktop/src/webview.rs | 3 +- packages/html/src/events/drag.rs | 40 +++++-- packages/html/src/events/form.rs | 10 +- packages/html/src/file_data.rs | 53 +++++++++ packages/html/src/lib.rs | 2 + .../src/native_bind/native_file_engine.rs | 3 +- packages/html/src/web_sys_bind/events.rs | 31 ++++- packages/rink/src/hooks.rs | 6 +- packages/web/src/event.rs | 110 ++++++++++++++++-- 11 files changed, 234 insertions(+), 31 deletions(-) create mode 100644 packages/core/src/diff/node.rs create mode 100644 packages/html/src/file_data.rs 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 } }