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! {
|
||||
input {
|
||||
r#type: "file",
|
||||
accept: ".txt",
|
||||
accept: ".txt, .rs",
|
||||
multiple: true,
|
||||
onchange: |evt| {
|
||||
to_owned![files_uploaded];
|
||||
|
||||
@@ -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
|
||||
} })
|
||||
}
|
||||
|
||||
fn generic_child<T>(cx: Scope) -> Element {
|
||||
cx.render(rsx! { div {} })
|
||||
#[derive(PartialEq, Props)]
|
||||
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 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()
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
// 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];
|
||||
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);
|
||||
// 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;
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"] }
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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!(()))
|
||||
}
|
||||
}
|
||||
@@ -25,3 +25,6 @@ convert_case = "0.5.0"
|
||||
# default = ["html"]
|
||||
|
||||
# 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();
|
||||
|
||||
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.
|
||||
|
||||
Reference in new issue
Block a user