Merge branch 'master' into implement-file-engine
This commit is contained in:
commit
22cfff647c
12 files changed
+259
-61
No files matched your search
@@ -11,7 +11,7 @@ fn App(cx: Scope) -> Element {
|
|||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
input {
|
input {
|
||||||
r#type: "file",
|
r#type: "file",
|
||||||
accept: ".txt",
|
accept: ".txt, .rs",
|
||||||
multiple: true,
|
multiple: true,
|
||||||
onchange: |evt| {
|
onchange: |evt| {
|
||||||
to_owned![files_uploaded];
|
to_owned![files_uploaded];
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
@@ -5,9 +7,20 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn app(cx: Scope) -> Element {
|
fn app(cx: Scope) -> Element {
|
||||||
cx.render(rsx! { generic_child::<i32>{} })
|
cx.render(rsx! { generic_child {
|
||||||
|
data: 0i32
|
||||||
|
} })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generic_child<T>(cx: Scope) -> Element {
|
#[derive(PartialEq, Props)]
|
||||||
cx.render(rsx! { div {} })
|
struct GenericChildProps<T: Display + PartialEq> {
|
||||||
|
data: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generic_child<T: Display + PartialEq>(cx: Scope<GenericChildProps<T>>) -> Element {
|
||||||
|
let data = &cx.props.data;
|
||||||
|
|
||||||
|
cx.render(rsx! { div {
|
||||||
|
"{data}"
|
||||||
|
} })
|
||||||
}
|
}
|
||||||
@@ -276,13 +276,12 @@ impl Writer<'_> {
|
|||||||
let start = location.start();
|
let start = location.start();
|
||||||
let line_start = start.line - 1;
|
let line_start = start.line - 1;
|
||||||
|
|
||||||
let this_line = self.src[line_start];
|
let beginning = self
|
||||||
|
.src
|
||||||
let beginning = if this_line.len() > start.column {
|
.get(line_start)
|
||||||
this_line[..start.column].trim()
|
.filter(|this_line| this_line.len() > start.column)
|
||||||
} else {
|
.map(|this_line| this_line[..start.column].trim())
|
||||||
""
|
.unwrap_or_default();
|
||||||
};
|
|
||||||
|
|
||||||
beginning.is_empty()
|
beginning.is_empty()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -384,51 +384,73 @@ impl VirtualDom {
|
|||||||
data,
|
data,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Loop through each dynamic attribute in this template before moving up to the template's parent.
|
// If the event bubbles, we traverse through the tree until we find the target element.
|
||||||
while let Some(el_ref) = parent_path {
|
if bubbles {
|
||||||
// safety: we maintain references of all vnodes in the element slab
|
// Loop through each dynamic attribute (in a depth first order) in this template before moving up to the template's parent.
|
||||||
let template = unsafe { el_ref.template.unwrap().as_ref() };
|
while let Some(el_ref) = parent_path {
|
||||||
let node_template = template.template.get();
|
// safety: we maintain references of all vnodes in the element slab
|
||||||
let target_path = el_ref.path;
|
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() {
|
for (idx, attr) in template.dynamic_attrs.iter().enumerate() {
|
||||||
let this_path = node_template.attr_paths[idx];
|
let this_path = node_template.attr_paths[idx];
|
||||||
|
|
||||||
// Remove the "on" prefix if it exists, TODO, we should remove this and settle on one
|
// Remove the "on" prefix if it exists, TODO, we should remove this and settle on one
|
||||||
if attr.name.trim_start_matches("on") == name
|
if attr.name.trim_start_matches("on") == name
|
||||||
&& target_path.is_decendant(&this_path)
|
&& target_path.is_decendant(&this_path)
|
||||||
{
|
{
|
||||||
listeners.push(&attr.value);
|
listeners.push(&attr.value);
|
||||||
|
|
||||||
// Break if the event doesn't bubble anyways
|
// Break if this is the exact target element.
|
||||||
if !bubbles {
|
// This means we won't call two listeners with the same name on the same element. This should be
|
||||||
break;
|
// documented, or be rejected from the rsx! macro outright
|
||||||
|
if target_path == this_path {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Break if this is the exact target element.
|
// Now that we've accumulated all the parent attributes for the target element, call them in reverse order
|
||||||
// This means we won't call two listeners with the same name on the same element. This should be
|
// We check the bubble state between each call to see if the event has been stopped from bubbling
|
||||||
// documented, or be rejected from the rsx! macro outright
|
for listener in listeners.drain(..).rev() {
|
||||||
if target_path == this_path {
|
if let AttributeValue::Listener(listener) = listener {
|
||||||
break;
|
if let Some(cb) = listener.borrow_mut().as_deref_mut() {
|
||||||
|
cb(uievent.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
if !uievent.propagates.get() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
parent_path = template.parent.and_then(|id| self.elements.get(id.0));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -167,10 +167,10 @@ class Interpreter {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "checked":
|
case "checked":
|
||||||
node.checked = value === "true";
|
node.checked = value === "true" || value === true;
|
||||||
break;
|
break;
|
||||||
case "selected":
|
case "selected":
|
||||||
node.selected = value === "true";
|
node.selected = value === "true" || value === true;
|
||||||
break;
|
break;
|
||||||
case "dangerous_inner_html":
|
case "dangerous_inner_html":
|
||||||
node.innerHTML = value;
|
node.innerHTML = value;
|
||||||
@@ -344,11 +344,104 @@ class Interpreter {
|
|||||||
this.RemoveEventListener(edit.id, edit.name);
|
this.RemoveEventListener(edit.id, edit.name);
|
||||||
break;
|
break;
|
||||||
case "NewEventListener":
|
case "NewEventListener":
|
||||||
const bubbles = event_bubbles(edit.name);
|
let bubbles = event_bubbles(edit.name);
|
||||||
|
|
||||||
this.NewEventListener(edit.name, edit.id, bubbles, (event) =>
|
// this handler is only provided on desktop implementations since this
|
||||||
handler(event, edit.name, bubbles)
|
// 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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ futures-util = { version = "0.3.25", default-features = false, features = [
|
|||||||
"sink",
|
"sink",
|
||||||
] }
|
] }
|
||||||
futures-channel = { version = "0.3.25", 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-stream = { version = "0.1.11", features = ["net"] }
|
||||||
tokio-util = { version = "0.7.4", features = ["rt"] }
|
tokio-util = { version = "0.7.4", features = ["rt"] }
|
||||||
serde = { version = "1.0.151", features = ["derive"] }
|
serde = { version = "1.0.151", features = ["derive"] }
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ lightningcss = "1.0.0-alpha.39"
|
|||||||
|
|
||||||
rayon = "1.6.1"
|
rayon = "1.6.1"
|
||||||
shipyard = { version = "0.6.2", features = ["proc", "std"], default-features = false }
|
shipyard = { version = "0.6.2", features = ["proc", "std"], default-features = false }
|
||||||
shipyard_hierarchy = "0.6.0"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
|
|||||||
@@ -98,7 +98,8 @@ pub fn render<R: Driver>(
|
|||||||
let event_tx_clone = event_tx.clone();
|
let event_tx_clone = event_tx.clone();
|
||||||
if !cfg.headless {
|
if !cfg.headless {
|
||||||
std::thread::spawn(move || {
|
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 {
|
loop {
|
||||||
if crossterm::event::poll(tick_rate).unwrap() {
|
if crossterm::event::poll(tick_rate).unwrap() {
|
||||||
let evt = crossterm::event::read().unwrap();
|
let evt = crossterm::event::read().unwrap();
|
||||||
|
|||||||
@@ -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());
|
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()) {
|
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))
|
cx.render(rsx!(&cx.props.children))
|
||||||
} else {
|
} else {
|
||||||
log::debug!("Route should *not* render: {:?}", cx.scope_id());
|
log::trace!("Route should *not* render: {:?}", cx.scope_id());
|
||||||
cx.render(rsx!(()))
|
cx.render(rsx!(()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -25,3 +25,6 @@ convert_case = "0.5.0"
|
|||||||
# default = ["html"]
|
# default = ["html"]
|
||||||
|
|
||||||
# eventually more output options
|
# eventually more output options
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
pretty_assertions = "1.2.1"
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -226,7 +226,7 @@ pub async fn run_with_props<T: 'static>(root: fn(Scope<T>) -> Element, root_prop
|
|||||||
websys_dom.mount();
|
websys_dom.mount();
|
||||||
|
|
||||||
loop {
|
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 virtualdom has nothing, wait for it to have something before requesting idle time
|
||||||
// if there is work then this future resolves immediately.
|
// if there is work then this future resolves immediately.
|
||||||
|
|||||||
Reference in new issue
Block a user