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"