Просмотр исходного кода

Merge branch 'master' into implement-file-engine

Jonathan Kelley 2 лет назад
Родитель
Сommit
22cfff647c

+ 1 - 1
examples/file_upload.rs

@@ -11,7 +11,7 @@ fn App(cx: Scope) -> Element {
     cx.render(rsx! {
         input {
             r#type: "file",
-            accept: ".txt",
+            accept: ".txt, .rs",
             multiple: true,
             onchange: |evt| {
                 to_owned![files_uploaded];

+ 16 - 3
examples/generic_component.rs

@@ -1,3 +1,5 @@
+use std::fmt::Display;
+
 use dioxus::prelude::*;
 
 fn main() {
@@ -5,9 +7,20 @@ fn main() {
 }
 
 fn app(cx: Scope) -> Element {
-    cx.render(rsx! { generic_child::<i32>{} })
+    cx.render(rsx! { generic_child {
+        data: 0i32
+    } })
+}
+
+#[derive(PartialEq, Props)]
+struct GenericChildProps<T: Display + PartialEq> {
+    data: T,
 }
 
-fn generic_child<T>(cx: Scope) -> Element {
-    cx.render(rsx! { div {} })
+fn generic_child<T: Display + PartialEq>(cx: Scope<GenericChildProps<T>>) -> Element {
+    let data = &cx.props.data;
+
+    cx.render(rsx! { div {
+        "{data}"
+    } })
 }

+ 6 - 7
packages/autofmt/src/element.rs

@@ -276,13 +276,12 @@ impl Writer<'_> {
         let start = location.start();
         let line_start = start.line - 1;
 
-        let this_line = self.src[line_start];
-
-        let beginning = if this_line.len() > start.column {
-            this_line[..start.column].trim()
-        } else {
-            ""
-        };
+        let beginning = self
+            .src
+            .get(line_start)
+            .filter(|this_line| this_line.len() > start.column)
+            .map(|this_line| this_line[..start.column].trim())
+            .unwrap_or_default();
 
         beginning.is_empty()
     }

+ 58 - 36
packages/core/src/virtual_dom.rs

@@ -384,51 +384,73 @@ impl VirtualDom {
             data,
         };
 
-        // Loop through each dynamic attribute in this template before moving up to the template's parent.
-        while let Some(el_ref) = parent_path {
-            // safety: we maintain references of all vnodes in the element slab
-            let template = unsafe { el_ref.template.unwrap().as_ref() };
-            let node_template = template.template.get();
-            let target_path = el_ref.path;
-
-            for (idx, attr) in template.dynamic_attrs.iter().enumerate() {
-                let this_path = node_template.attr_paths[idx];
-
-                // Remove the "on" prefix if it exists, TODO, we should remove this and settle on one
-                if attr.name.trim_start_matches("on") == name
-                    && target_path.is_decendant(&this_path)
-                {
-                    listeners.push(&attr.value);
-
-                    // Break if the event doesn't bubble anyways
-                    if !bubbles {
-                        break;
+        // If the event bubbles, we traverse through the tree until we find the target element.
+        if bubbles {
+            // Loop through each dynamic attribute (in a depth first order) in this template before moving up to the template's parent.
+            while let Some(el_ref) = parent_path {
+                // safety: we maintain references of all vnodes in the element slab
+                let template = unsafe { el_ref.template.unwrap().as_ref() };
+                let node_template = template.template.get();
+                let target_path = el_ref.path;
+
+                for (idx, attr) in template.dynamic_attrs.iter().enumerate() {
+                    let this_path = node_template.attr_paths[idx];
+
+                    // Remove the "on" prefix if it exists, TODO, we should remove this and settle on one
+                    if attr.name.trim_start_matches("on") == name
+                        && target_path.is_decendant(&this_path)
+                    {
+                        listeners.push(&attr.value);
+
+                        // Break if this is the exact target element.
+                        // This means we won't call two listeners with the same name on the same element. This should be
+                        // documented, or be rejected from the rsx! macro outright
+                        if target_path == this_path {
+                            break;
+                        }
                     }
+                }
 
-                    // Break if this is the exact target element.
-                    // This means we won't call two listeners with the same name on the same element. This should be
-                    // documented, or be rejected from the rsx! macro outright
-                    if target_path == this_path {
-                        break;
+                // Now that we've accumulated all the parent attributes for the target element, call them in reverse order
+                // We check the bubble state between each call to see if the event has been stopped from bubbling
+                for listener in listeners.drain(..).rev() {
+                    if let AttributeValue::Listener(listener) = listener {
+                        if let Some(cb) = listener.borrow_mut().as_deref_mut() {
+                            cb(uievent.clone());
+                        }
+
+                        if !uievent.propagates.get() {
+                            return;
+                        }
                     }
                 }
-            }
 
-            // Now that we've accumulated all the parent attributes for the target element, call them in reverse order
-            // We check the bubble state between each call to see if the event has been stopped from bubbling
-            for listener in listeners.drain(..).rev() {
-                if let AttributeValue::Listener(listener) = listener {
-                    if let Some(cb) = listener.borrow_mut().as_deref_mut() {
-                        cb(uievent.clone());
-                    }
+                parent_path = template.parent.and_then(|id| self.elements.get(id.0));
+            }
+        } else {
+            // Otherwise, we just call the listener on the target element
+            if let Some(el_ref) = parent_path {
+                // safety: we maintain references of all vnodes in the element slab
+                let template = unsafe { el_ref.template.unwrap().as_ref() };
+                let node_template = template.template.get();
+                let target_path = el_ref.path;
+
+                for (idx, attr) in template.dynamic_attrs.iter().enumerate() {
+                    let this_path = node_template.attr_paths[idx];
+
+                    // Remove the "on" prefix if it exists, TODO, we should remove this and settle on one
+                    // Only call the listener if this is the exact target element.
+                    if attr.name.trim_start_matches("on") == name && target_path == this_path {
+                        if let AttributeValue::Listener(listener) = &attr.value {
+                            if let Some(cb) = listener.borrow_mut().as_deref_mut() {
+                                cb(uievent.clone());
+                            }
 
-                    if !uievent.propagates.get() {
-                        return;
+                            break;
+                        }
                     }
                 }
             }
-
-            parent_path = template.parent.and_then(|id| self.elements.get(id.0));
         }
     }
 

+ 99 - 6
packages/interpreter/src/interpreter.js

@@ -167,10 +167,10 @@ class Interpreter {
           }
           break;
         case "checked":
-          node.checked = value === "true";
+          node.checked = value === "true" || value === true;
           break;
         case "selected":
-          node.selected = value === "true";
+          node.selected = value === "true" || value === true;
           break;
         case "dangerous_inner_html":
           node.innerHTML = value;
@@ -344,11 +344,104 @@ class Interpreter {
         this.RemoveEventListener(edit.id, edit.name);
         break;
       case "NewEventListener":
-        const bubbles = event_bubbles(edit.name);
+        let bubbles = event_bubbles(edit.name);
 
-        this.NewEventListener(edit.name, edit.id, bubbles, (event) =>
-          handler(event, edit.name, bubbles)
-        );
+        // this handler is only provided on desktop implementations since this
+        // method is not used by the web implementation
+        let handler = (event) => {
+          let target = event.target;
+          if (target != null) {
+            let realId = target.getAttribute(`data-dioxus-id`);
+            let shouldPreventDefault = target.getAttribute(
+              `dioxus-prevent-default`
+            );
+
+            if (event.type === "click") {
+              // todo call prevent default if it's the right type of event
+              let a_element = target.closest("a");
+              if (a_element != null) {
+                event.preventDefault();
+                if (
+                  shouldPreventDefault !== `onclick` &&
+                  a_element.getAttribute(`dioxus-prevent-default`) !== `onclick`
+                ) {
+                  const href = a_element.getAttribute("href");
+                  if (href !== "" && href !== null && href !== undefined) {
+                    window.ipc.postMessage(
+                      serializeIpcMessage("browser_open", { href })
+                    );
+                  }
+                }
+              }
+
+              // also prevent buttons from submitting
+              if (target.tagName === "BUTTON" && event.type == "submit") {
+                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();
+            }
+
+            if (event.type === "submit") {
+              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;
+                    }
+                  } 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,
+              })
+            );
+          }
+        };
+        this.NewEventListener(edit.name, edit.id, bubbles, handler);
         break;
     }
   }

+ 1 - 1
packages/liveview/Cargo.toml

@@ -18,7 +18,7 @@ futures-util = { version = "0.3.25", default-features = false, features = [
     "sink",
 ] }
 futures-channel = { version = "0.3.25", features = ["sink"] }
-tokio = { version = "1.22.0", features = ["time"] }
+tokio = { version = "1.22.0", features = ["time", "macros"] }
 tokio-stream = { version = "0.1.11", features = ["net"] }
 tokio-util = { version = "0.7.4", features = ["rt"] }
 serde = { version = "1.0.151", features = ["derive"] }

+ 0 - 1
packages/native-core/Cargo.toml

@@ -29,7 +29,6 @@ lightningcss = "1.0.0-alpha.39"
 
 rayon = "1.6.1"
 shipyard = { version = "0.6.2", features = ["proc", "std"], default-features = false }
-shipyard_hierarchy = "0.6.0"
 
 [dev-dependencies]
 rand = "0.8.5"

+ 2 - 1
packages/rink/src/lib.rs

@@ -98,7 +98,8 @@ pub fn render<R: Driver>(
     let event_tx_clone = event_tx.clone();
     if !cfg.headless {
         std::thread::spawn(move || {
-            let tick_rate = Duration::from_millis(1000);
+            // Timeout after 10ms when waiting for events
+            let tick_rate = Duration::from_millis(10);
             loop {
                 if crossterm::event::poll(tick_rate).unwrap() {
                     let evt = crossterm::event::read().unwrap();

+ 3 - 3
packages/router/src/components/route.rs

@@ -45,13 +45,13 @@ pub fn Route<'a>(cx: Scope<'a, RouteProps<'a>>) -> Element {
         router_root.register_total_route(route_context.total_route, cx.scope_id());
     });
 
-    log::debug!("Checking Route: {:?}", cx.props.to);
+    log::trace!("Checking Route: {:?}", cx.props.to);
 
     if router_root.should_render(cx.scope_id()) {
-        log::debug!("Route should render: {:?}", cx.scope_id());
+        log::trace!("Route should render: {:?}", cx.scope_id());
         cx.render(rsx!(&cx.props.children))
     } else {
-        log::debug!("Route should *not* render: {:?}", cx.scope_id());
+        log::trace!("Route should *not* render: {:?}", cx.scope_id());
         cx.render(rsx!(()))
     }
 }

+ 3 - 0
packages/rsx-rosetta/Cargo.toml

@@ -25,3 +25,6 @@ convert_case = "0.5.0"
 # default = ["html"]
 
 # eventually more output options
+
+[dev-dependencies]
+pretty_assertions = "1.2.1"

+ 68 - 0
packages/rsx-rosetta/tests/simple.rs

@@ -0,0 +1,68 @@
+use html_parser::Dom;
+
+#[test]
+fn simple_elements() {
+    let html = r#"
+    <div>
+        <div class="asd">hello world!</div>
+        <div id="asd">hello world!</div>
+        <div id="asd">hello world!</div>
+        <div for="asd">hello world!</div>
+        <div async="asd">hello world!</div>
+        <div LargeThing="asd">hello world!</div>
+        <ai-is-awesome>hello world!</ai-is-awesome>
+    </div>
+    "#
+    .trim();
+
+    let dom = Dom::parse(html).unwrap();
+
+    let body = rsx_rosetta::rsx_from_html(&dom);
+
+    let out = dioxus_autofmt::write_block_out(body).unwrap();
+
+    let expected = r#"
+    div {
+        div { class: "asd", "hello world!" }
+        div { id: "asd", "hello world!" }
+        div { id: "asd", "hello world!" }
+        div { r#for: "asd", "hello world!" }
+        div { r#async: "asd", "hello world!" }
+        div { large_thing: "asd", "hello world!" }
+        ai_is_awesome { "hello world!" }
+    }"#;
+    pretty_assertions::assert_eq!(&out, &expected);
+}
+
+#[test]
+fn deeply_nested() {
+    let html = r#"
+    <div>
+        <div class="asd">
+            <div class="asd">
+                <div class="asd">
+                    <div class="asd">
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+    "#
+    .trim();
+
+    let dom = Dom::parse(html).unwrap();
+
+    let body = rsx_rosetta::rsx_from_html(&dom);
+
+    let out = dioxus_autofmt::write_block_out(body).unwrap();
+
+    let expected = r#"
+    div {
+        div { class: "asd",
+            div { class: "asd",
+                div { class: "asd", div { class: "asd" } }
+            }
+        }
+    }"#;
+    pretty_assertions::assert_eq!(&out, &expected);
+}

+ 1 - 1
packages/web/src/lib.rs

@@ -226,7 +226,7 @@ pub async fn run_with_props<T: 'static>(root: fn(Scope<T>) -> Element, root_prop
     websys_dom.mount();
 
     loop {
-        log::debug!("waiting for work");
+        log::trace!("waiting for work");
 
         // if virtualdom has nothing, wait for it to have something before requesting idle time
         // if there is work then this future resolves immediately.