瀏覽代碼

add file property to drag data

Evan Almloff 2 年之前
父節點
當前提交
ada246c12e

+ 25 - 2
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<Vec<String>> = 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() {

+ 1 - 1
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,

+ 4 - 6
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

+ 1 - 0
packages/html/Cargo.toml

@@ -41,6 +41,7 @@ features = [
     "FocusEvent",
     "CompositionEvent",
     "ClipboardEvent",
+    "DragEvent"
 ]
 
 [dev-dependencies]

+ 31 - 3
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<DragData>;
 
 /// 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<DragData>;
 /// (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<std::sync::Arc<dyn FileEngine>>,
 }
 
+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;
 

+ 1 - 1
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<Option<std::sync::Arc<dyn FileEngine>>, D::Error>
 where

+ 1 - 0
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,
         }
     }
 }

+ 56 - 49
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 });
 }

+ 1 - 13
packages/liveview/src/lib.rs

@@ -45,19 +45,7 @@ static INTERPRETER_JS: Lazy<String> = 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) {

+ 2 - 1
packages/web/Cargo.toml

@@ -79,7 +79,8 @@ features = [
     "console",
     "FileList",
     "File",
-    "FileReader"
+    "FileReader",
+    "DataTransfer"
 ]
 
 [features]

+ 10 - 1
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::<web_sys::DragEvent>() {
+                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<dyn FileEngine>);
+                    }
+                }
+            }
             let mouse = MouseData::from(event);
-            Rc::new(DragData { mouse })
+            Rc::new(DragData { mouse, files })
         }
 
         "pointerdown" | "pointermove" | "pointerup" | "pointercancel" | "gotpointercapture"