Browse Source

implement the file engine for liveview

Evan Almloff 2 years ago
parent
commit
9cfb655478

+ 6 - 5
packages/desktop/src/protocol.rs

@@ -23,11 +23,12 @@ fn module_loader(root_name: &str) -> String {
           if (type === "file") {
           if (type === "file") {
             let target_id = find_real_id(target);
             let target_id = find_real_id(target);
             if (target_id !== null) {
             if (target_id !== null) {
-              const event_name = "change";
-              const message = serializeIpcMessage("file_diolog", { accept: target.getAttribute("accept"), multiple: target.hasAttribute("multiple"), target: parseInt(target_id), bubbles: event_bubbles(event_name), event: event_name });
-              console.log(message);
-              console.log(event);
-              window.ipc.postMessage(message);
+              const send = (event_name) => {
+                const message = serializeIpcMessage("file_diolog", { accept: target.getAttribute("accept"), multiple: target.hasAttribute("multiple"), target: parseInt(target_id), bubbles: event_bubbles(event_name), event: event_name });
+                window.ipc.postMessage(message);
+              };
+              send("change");
+              send("input");
             }
             }
             event.preventDefault();
             event.preventDefault();
           }
           }

+ 46 - 1
packages/html/src/events/form.rs

@@ -12,10 +12,55 @@ pub struct FormData {
 
 
     pub values: HashMap<String, Vec<String>>,
     pub values: HashMap<String, Vec<String>>,
 
 
-    #[cfg_attr(feature = "serialize", serde(skip))]
+    #[cfg_attr(
+        feature = "serialize",
+        serde(skip_serializing, deserialize_with = "deserialize_file_engine")
+    )]
     pub files: Option<std::sync::Arc<dyn FileEngine>>,
     pub files: Option<std::sync::Arc<dyn FileEngine>>,
 }
 }
 
 
+#[cfg(feature = "serialize")]
+#[derive(serde::Serialize, serde::Deserialize)]
+struct SerializedFileEngine {
+    files: HashMap<String, Vec<u8>>,
+}
+
+#[cfg(feature = "serialize")]
+#[async_trait::async_trait(?Send)]
+impl FileEngine for SerializedFileEngine {
+    fn files(&self) -> Vec<String> {
+        self.files.keys().cloned().collect()
+    }
+
+    async fn read_file(&self, file: &str) -> Option<Vec<u8>> {
+        self.files.get(file).cloned()
+    }
+
+    async fn read_file_to_string(&self, file: &str) -> Option<String> {
+        self.read_file(file)
+            .await
+            .map(|bytes| String::from_utf8_lossy(&bytes).to_string())
+    }
+}
+
+#[cfg(feature = "serialize")]
+fn deserialize_file_engine<'de, D>(
+    deserializer: D,
+) -> Result<Option<std::sync::Arc<dyn FileEngine>>, D::Error>
+where
+    D: serde::Deserializer<'de>,
+{
+    use serde::Deserialize;
+
+    let Ok(file_engine) =
+        SerializedFileEngine::deserialize(deserializer) else{
+            return Ok(None);
+        };
+
+    let file_engine = std::sync::Arc::new(file_engine);
+    Ok(Some(file_engine))
+}
+
 impl PartialEq for FormData {
 impl PartialEq for FormData {
     fn eq(&self, other: &Self) -> bool {
     fn eq(&self, other: &Self) -> bool {
         self.value == other.value && self.values == other.values
         self.value == other.value && self.values == other.values

+ 13 - 8
packages/interpreter/src/interpreter.js

@@ -344,17 +344,19 @@ class Interpreter {
         this.RemoveEventListener(edit.id, edit.name);
         this.RemoveEventListener(edit.id, edit.name);
         break;
         break;
       case "NewEventListener":
       case "NewEventListener":
-        let bubbles = event_bubbles(edit.name);
+        const bubbles = event_bubbles(edit.name);
 
 
-        this.NewEventListener(edit.name, edit.id, bubbles, handler);
+        this.NewEventListener(edit.name, edit.id, bubbles, (event) =>
+          handler(event, edit.name, bubbles)
+        );
         break;
         break;
     }
     }
   }
   }
 }
 }
 
 
-// this handler is only provided on desktop implementations since this
+// this handler is only provided on the desktop and liveview implementations since this
 // method is not used by the web implementation
 // method is not used by the web implementation
-function handler(event) {
+function handler(event, name, bubbles) {
   let target = event.target;
   let target = event.target;
   if (target != null) {
   if (target != null) {
     let shouldPreventDefault = target.getAttribute(`dioxus-prevent-default`);
     let shouldPreventDefault = target.getAttribute(`dioxus-prevent-default`);
@@ -387,8 +389,6 @@ function handler(event) {
 
 
     shouldPreventDefault = target.getAttribute(`dioxus-prevent-default`);
     shouldPreventDefault = target.getAttribute(`dioxus-prevent-default`);
 
 
-    let contents = serialize_event(event);
-
     if (shouldPreventDefault === `on${event.type}`) {
     if (shouldPreventDefault === `on${event.type}`) {
       event.preventDefault();
       event.preventDefault();
     }
     }
@@ -397,6 +397,10 @@ function handler(event) {
       event.preventDefault();
       event.preventDefault();
     }
     }
 
 
+    let contents = serialize_event(event);
+
+    /*POST_EVENT_SERIALIZATION*/
+
     if (
     if (
       target.tagName === "FORM" &&
       target.tagName === "FORM" &&
       (event.type === "submit" || event.type === "input")
       (event.type === "submit" || event.type === "input")
@@ -408,7 +412,8 @@ function handler(event) {
         const formData = new FormData(target);
         const formData = new FormData(target);
 
 
         for (let name of formData.keys()) {
         for (let name of formData.keys()) {
-          contents.values[name] = formData.getAll(name);
+          let value = formData.getAll(name);
+          contents.values[name] = value;
         }
         }
       }
       }
     }
     }
@@ -418,7 +423,7 @@ function handler(event) {
     }
     }
     window.ipc.postMessage(
     window.ipc.postMessage(
       serializeIpcMessage("user_event", {
       serializeIpcMessage("user_event", {
-        name: edit.name,
+        name: name,
         element: parseInt(realId),
         element: parseInt(realId),
         data: contents,
         data: contents,
         bubbles,
         bubbles,

+ 1 - 0
packages/liveview/Cargo.toml

@@ -36,6 +36,7 @@ axum = { version = "0.6.1", optional = true, features = ["ws"] }
 
 
 # salvo
 # salvo
 salvo = { version = "0.37.7", optional = true, features = ["ws"] }
 salvo = { version = "0.37.7", optional = true, features = ["ws"] }
+once_cell = "1.17.1"
 
 
 # actix is ... complicated?
 # actix is ... complicated?
 # actix-files = { version = "0.6.2", optional = true }
 # actix-files = { version = "0.6.2", optional = true }

+ 47 - 2
packages/liveview/src/lib.rs

@@ -34,7 +34,51 @@ pub enum LiveViewError {
     SendingFailed,
     SendingFailed,
 }
 }
 
 
-use dioxus_interpreter_js::INTERPRETER_JS;
+use once_cell::sync::Lazy;
+
+static INTERPRETER_JS: Lazy<String> = Lazy::new(|| {
+    let interpreter = dioxus_interpreter_js::INTERPRETER_JS;
+    let serialize_file_uploads = r#"if (
+      target.tagName === "INPUT" &&
+      (event.type === "change" || event.type === "input")
+    ) {
+      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,
+          };
+          contents.files = file_engine;
+
+          if (realId === null) {
+            return;
+          }
+          const message = serializeIpcMessage("user_event", {
+            name: name,
+            element: parseInt(realId),
+            data: contents,
+            bubbles,
+          });
+          window.ipc.postMessage(message);
+        }
+        read_files();
+        return;
+      }
+    }"#;
+
+    interpreter.replace("/*POST_EVENT_SERIALIZATION*/", serialize_file_uploads)
+});
+
 static MAIN_JS: &str = include_str!("./main.js");
 static MAIN_JS: &str = include_str!("./main.js");
 
 
 /// This script that gets injected into your app connects this page to the websocket endpoint
 /// This script that gets injected into your app connects this page to the websocket endpoint
@@ -42,11 +86,12 @@ static MAIN_JS: &str = include_str!("./main.js");
 /// Once the endpoint is connected, it will send the initial state of the app, and then start
 /// Once the endpoint is connected, it will send the initial state of the app, and then start
 /// processing user events and returning edits to the liveview instance
 /// processing user events and returning edits to the liveview instance
 pub fn interpreter_glue(url: &str) -> String {
 pub fn interpreter_glue(url: &str) -> String {
+    let js = &*INTERPRETER_JS;
     format!(
     format!(
         r#"
         r#"
 <script>
 <script>
     var WS_ADDR = "{url}";
     var WS_ADDR = "{url}";
-    {INTERPRETER_JS}
+    {js}
     {MAIN_JS}
     {MAIN_JS}
     main();
     main();
 </script>
 </script>