wip: some ideas

This commit is contained in:
Jonathan Kelley 2021-07-18 03:54:42 -04:00
parent 583fdfa561
commit 05c909f320
21 changed files with 662 additions and 1112 deletions

View file

@ -2,43 +2,41 @@
//! //!
//! The example from the README.md //! The example from the README.md
use std::{pin::Pin, time::Duration};
use dioxus::prelude::*; use dioxus::prelude::*;
use futures::Future;
fn main() { fn main() {
env_logger::init();
log::info!("hello world");
dioxus::desktop::launch(App, |c| c).expect("faield to launch"); dioxus::desktop::launch(App, |c| c).expect("faield to launch");
} }
#[derive(serde::Deserialize)] pub static App: FC<()> = |cx| {
struct DogApi { let count = use_state(cx, || 0);
message: String, let mut direction = use_state(cx, || 1);
}
const ENDPOINT: &str = "https://dog.ceo/api/breeds/image/random"; let (async_count, dir) = (count.for_async(), *direction);
let (task, _result) = cx.use_task(move || async move {
static App: FC<()> = |cx| {
let mut count = use_state(cx, || 0);
let mut fut = Box::pin(async {
let mut tick: i32 = 0;
loop { loop {
async_std::task::sleep(Duration::from_millis(250)).await; gloo_timers::future::TimeoutFuture::new(250).await;
log::debug!("ticking forward... {}", tick); *async_count.get_mut() += dir;
tick += 1;
// match surf::get(ENDPOINT).recv_json::<DogApi>().await {
// Ok(_) => (),
// Err(_) => (),
// }
} }
}); });
cx.submit_task(fut);
cx.render(rsx! { cx.render(rsx! {
div { div {
h1 {"it's working somewhat properly"} h1 {"count is {count}"}
button {
"Stop counting"
onclick: move |_| task.stop()
}
button {
"Start counting"
onclick: move |_| task.start()
}
button {
"Switch counting direcion"
onclick: move |_| {
direction *= -1;
task.restart();
}
}
} }
}) })
}; };

View file

@ -1,16 +0,0 @@
use dioxus::prelude::*;
fn main() {}
pub static Example: FC<()> = |cx| {
let (g, set_g) = use_state(cx, || 0).classic();
let v = (0..10).map(move |f| {
rsx!(li {
onclick: move |_| set_g(10)
})
});
cx.render(rsx! {
div {
{v}
}
})
};

View file

@ -1,170 +0,0 @@
#![allow(
unused,
dead_code,
non_upper_case_globals,
non_camel_case_types,
non_snake_case
)]
mod antipatterns;
mod basics;
mod children;
mod conditional_rendering;
mod controlled_inputs;
mod custom_elements;
mod empty;
mod fragments;
mod global_css;
mod inline_styles;
mod iterators;
mod listener;
mod memo;
mod noderefs;
mod signals;
mod spreadpattern;
mod statemanagement;
mod suspense;
mod task;
mod testing;
mod tostring;
fn main() {}
use std::rc::Rc;
use dioxus::prelude::*;
static App: FC<()> = |cx| {
let (selection, set_selection) = use_state(cx, || None as Option<usize>).classic();
let body = match selection {
Some(id) => rsx!(in cx, ReferenceItem { selected: *id }),
None => rsx!(in cx, div { "Select an concept to explore" }),
};
cx.render(rsx! {
div {
ScrollSelector { onselect: move |id| set_selection(id) }
{body}
}
})
};
// this is its own component to stay memoized
#[derive(Props)]
struct ScrollSelectorProps<'a> {
onselect: &'a dyn Fn(Option<usize>),
}
fn ScrollSelector<'a>(cx: Context<'a, ScrollSelectorProps>) -> VNode<'a> {
let selection_list = (&REFERENCES).iter().enumerate().map(|(id, _)| {
rsx! {
li {
h3 {}
}
}
});
cx.render(rsx! {
div {
h1 {""}
ul {
{selection_list}
button {
onclick: move |_| (cx.onselect)(Some(10))
}
}
}
})
}
#[derive(PartialEq, Props)]
struct ReferenceItemProps {
selected: usize,
}
static ReferenceItem: FC<ReferenceItemProps> = |cx| {
let (caller, name, code) = REFERENCES[cx.selected];
// Create the component using the factory API directly
let caller_node = LazyNodes::new(move |f| f.component(caller, (), None, &[]));
cx.render(rsx! {
div {
// Source of the left, rendered on the right
div {
code { "{code}" }
}
div {
{caller_node}
}
}
})
};
static REFERENCES: &[(FC<()>, &str, &str)] = &[
(basics::Example, "Basics", include_str!("./basics.rs")),
(children::Example, "Children", include_str!("./children.rs")),
(
conditional_rendering::Example,
"Conditional Rendering",
include_str!("./conditional_rendering.rs"),
),
// TODO
(
controlled_inputs::Example,
"Controlled Inputs",
include_str!("./controlled_inputs.rs"),
),
(empty::Example, "empty", include_str!("./empty.rs")),
(
custom_elements::Example,
"Custom Elements & Web Components",
include_str!("./custom_elements.rs"),
),
(
fragments::Example,
"Fragments",
include_str!("./fragments.rs"),
),
(
iterators::Example,
"Iterators",
include_str!("./iterators.rs"),
),
(
global_css::Example,
"Global CSS",
include_str!("./global_css.rs"),
),
(
inline_styles::Example,
"Inline Styles",
include_str!("./inline_styles.rs"),
),
(listener::Example, "Listener", include_str!("./listener.rs")),
(memo::Example, "Memo", include_str!("./memo.rs")),
(
spreadpattern::Example,
"Spread Pattern",
include_str!("./spreadpattern.rs"),
),
(suspense::Example, "Suspense", include_str!("./suspense.rs")),
(task::Example, "Task", include_str!("./task.rs")),
(tostring::Example, "Tostring", include_str!("./tostring.rs")),
(
antipatterns::Example,
"Anti-patterns",
include_str!("./antipatterns.rs"),
),
/*
TODO!
*/
(signals::Example, "Signals", include_str!("./signals.rs")),
(noderefs::Example, "NodeRefs", include_str!("./noderefs.rs")),
(
statemanagement::Example,
"State Management",
include_str!("./statemanagement.rs"),
),
(testing::Example, "Testing", include_str!("./testing.rs")),
];

View file

@ -7,7 +7,7 @@ pub static Example: FC<()> = |cx| {
// This is an easy/low hanging fruit to improve upon // This is an easy/low hanging fruit to improve upon
let mut dom = VirtualDom::new(SomeApp); let mut dom = VirtualDom::new(SomeApp);
dom.rebuild_in_place().unwrap(); dom.rebuild_in_place().unwrap();
ssr::render_root(&dom) ssr::render_vdom(&dom)
}); });
cx.render(rsx! { cx.render(rsx! {

View file

@ -1,8 +1,84 @@
//! Example: Reference Showcase #![allow(
//! --------------------------- unused,
//! dead_code,
//! This example provides a cute interface for browsing the reference examples. non_upper_case_globals,
non_camel_case_types,
non_snake_case
)]
fn main() {}
use dioxus::prelude::*;
use std::rc::Rc;
static App: FC<()> = |cx| {
let (selection, set_selection) = use_state(cx, || None as Option<usize>).classic();
let body = match selection {
Some(id) => rsx!(in cx, ReferenceItem { selected: *id }),
None => rsx!(in cx, div { "Select an concept to explore" }),
};
cx.render(rsx! {
div {
ScrollSelector { onselect: move |id| set_selection(id) }
{body}
}
})
};
// this is its own component to stay memoized
#[derive(Props)]
struct ScrollSelectorProps<'a> {
onselect: &'a dyn Fn(Option<usize>),
}
fn ScrollSelector<'a>(cx: Context<'a, ScrollSelectorProps>) -> VNode<'a> {
let selection_list = (&REFERENCES).iter().enumerate().map(|(id, _)| {
rsx! {
li {
h3 {}
}
}
});
cx.render(rsx! {
div {
h1 {""}
ul {
{selection_list}
button {
onclick: move |_| (cx.onselect)(Some(10))
}
}
}
})
}
#[derive(PartialEq, Props)]
struct ReferenceItemProps {
selected: usize,
}
static ReferenceItem: FC<ReferenceItemProps> = |cx| {
let (caller, name, code) = REFERENCES[cx.selected];
// Create the component using the factory API directly
let caller_node = LazyNodes::new(move |f| f.component(caller, (), None, &[]));
cx.render(rsx! {
div {
// Source of the left, rendered on the right
div {
code { "{code}" }
}
div {
{caller_node}
}
}
})
};
use reference::REFERENCES;
mod reference { mod reference {
mod antipatterns; mod antipatterns;
mod basics; mod basics;
@ -22,9 +98,111 @@ mod reference {
mod spreadpattern; mod spreadpattern;
mod statemanagement; mod statemanagement;
mod suspense; mod suspense;
mod task;
mod testing; mod testing;
mod tostring; mod tostring;
mod webcomponents; use super::*;
pub static REFERENCES: &[(FC<()>, &str, &str)] = &[
(
basics::Example,
"Basics",
include_str!("./reference/basics.rs"),
),
(
children::Example,
"Children",
include_str!("./reference/children.rs"),
),
(
conditional_rendering::Example,
"Conditional Rendering",
include_str!("./reference/conditional_rendering.rs"),
),
// TODO
(
controlled_inputs::Example,
"Controlled Inputs",
include_str!("./reference/controlled_inputs.rs"),
),
(
empty::Example,
"empty",
include_str!("./reference/empty.rs"),
),
(
custom_elements::Example,
"Custom Elements & Web Components",
include_str!("./reference/custom_elements.rs"),
),
(
fragments::Example,
"Fragments",
include_str!("./reference/fragments.rs"),
),
(
iterators::Example,
"Iterators",
include_str!("./reference/iterators.rs"),
),
(
global_css::Example,
"Global CSS",
include_str!("./reference/global_css.rs"),
),
(
inline_styles::Example,
"Inline Styles",
include_str!("./reference/inline_styles.rs"),
),
(
listener::Example,
"Listener",
include_str!("./reference/listener.rs"),
),
(memo::Example, "Memo", include_str!("./reference/memo.rs")),
(
spreadpattern::Example,
"Spread Pattern",
include_str!("./reference/spreadpattern.rs"),
),
(
suspense::Example,
"Suspense",
include_str!("./reference/suspense.rs"),
),
(task::Example, "Task", include_str!("./reference/task.rs")),
(
tostring::Example,
"Tostring",
include_str!("./reference/tostring.rs"),
),
(
antipatterns::Example,
"Anti-patterns",
include_str!("./reference/antipatterns.rs"),
),
/*
TODO! these showcase features that aren't quite ready
*/
(
signals::Example,
"Signals",
include_str!("./reference/signals.rs"),
),
(
noderefs::Example,
"NodeRefs",
include_str!("./reference/noderefs.rs"),
),
(
statemanagement::Example,
"State Management",
include_str!("./reference/statemanagement.rs"),
),
(
testing::Example,
"Testing",
include_str!("./reference/testing.rs"),
),
];
} }
fn main() {}

View file

@ -4,7 +4,7 @@ use dioxus::ssr;
fn main() { fn main() {
let mut vdom = VirtualDom::new(App); let mut vdom = VirtualDom::new(App);
vdom.rebuild_in_place(); vdom.rebuild_in_place();
println!("{}", ssr::render_root(&vdom)); println!("{}", ssr::render_vdom(&vdom));
} }
const App: FC<()> = |cx| { const App: FC<()> = |cx| {

View file

@ -1,7 +1,7 @@
use dioxus::desktop::wry::application::platform::macos::*;
use dioxus::prelude::*; use dioxus::prelude::*;
fn main() { fn main() {
use dioxus::desktop::wry::application::platform::macos::*;
dioxus::desktop::launch(App, |c| { dioxus::desktop::launch(App, |c| {
c.with_fullsize_content_view(true) c.with_fullsize_content_view(true)
.with_titlebar_buttons_hidden(false) .with_titlebar_buttons_hidden(false)

View file

@ -22,18 +22,16 @@ impl SharedArena {
/// THIS METHOD IS CURRENTLY UNSAFE /// THIS METHOD IS CURRENTLY UNSAFE
/// THERE ARE NO CHECKS TO VERIFY THAT WE ARE ALLOWED TO DO THIS /// THERE ARE NO CHECKS TO VERIFY THAT WE ARE ALLOWED TO DO THIS
pub fn try_get(&self, idx: ScopeId) -> Result<&Scope> { pub fn get(&self, idx: ScopeId) -> Option<&Scope> {
let inner = unsafe { &*self.components.get() }; let inner = unsafe { &*self.components.get() };
let scope = inner.get(idx); inner.get(idx)
scope.ok_or_else(|| Error::FatalInternal("Scope not found"))
} }
/// THIS METHOD IS CURRENTLY UNSAFE /// THIS METHOD IS CURRENTLY UNSAFE
/// THERE ARE NO CHECKS TO VERIFY THAT WE ARE ALLOWED TO DO THIS /// THERE ARE NO CHECKS TO VERIFY THAT WE ARE ALLOWED TO DO THIS
pub fn try_get_mut(&self, idx: ScopeId) -> Result<&mut Scope> { pub fn get_mut(&self, idx: ScopeId) -> Option<&mut Scope> {
let inner = unsafe { &mut *self.components.get() }; let inner = unsafe { &mut *self.components.get() };
let scope = inner.get_mut(idx); inner.get_mut(idx)
scope.ok_or_else(|| Error::FatalInternal("Scope not found"))
} }
fn inner(&self) -> &ScopeMap { fn inner(&self) -> &ScopeMap {

View file

@ -278,8 +278,8 @@ Any function prefixed with "use" should not be called conditionally.
Some(parent_id) => { Some(parent_id) => {
let parent = inner let parent = inner
.arena_link .arena_link
.try_get(parent_id) .get(parent_id)
.map_err(|_| Error::FatalInternal("Failed to find parent"))?; .ok_or_else(|| Error::FatalInternal("Failed to find parent"))?;
scope = Some(parent); scope = Some(parent);
} }

View file

@ -162,7 +162,7 @@ where
new.ass_scope.set(scope_id); new.ass_scope.set(scope_id);
// make sure the component's caller function is up to date // make sure the component's caller function is up to date
let scope = self.components.try_get_mut(scope_id.unwrap()).unwrap(); let scope = self.components.get_mut(scope_id.unwrap()).unwrap();
scope.caller = new.caller.clone(); scope.caller = new.caller.clone();
@ -406,7 +406,7 @@ where
.components .components
.with(|components| { .with(|components| {
components.insert_with_key(|new_idx| { components.insert_with_key(|new_idx| {
let parent_scope = self.components.try_get(parent_idx).unwrap(); let parent_scope = self.components.get(parent_idx).unwrap();
let height = parent_scope.height + 1; let height = parent_scope.height + 1;
Scope::new( Scope::new(
caller, caller,
@ -495,7 +495,7 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> {
while let Some(scope_id) = scopes_to_explore.pop() { while let Some(scope_id) = scopes_to_explore.pop() {
// If we're planning on deleting this node, then we don't need to both rendering it // If we're planning on deleting this node, then we don't need to both rendering it
self.seen_nodes.insert(scope_id); self.seen_nodes.insert(scope_id);
let scope = self.components.try_get(scope_id).unwrap(); let scope = self.components.get(scope_id).unwrap();
for child in scope.descendents.borrow().iter() { for child in scope.descendents.borrow().iter() {
// Add this node to be explored // Add this node to be explored
scopes_to_explore.push(child.clone()); scopes_to_explore.push(child.clone());
@ -1324,7 +1324,7 @@ impl<'a> Iterator for RealChildIterator<'a> {
// For components, we load their root and push them onto the stack // For components, we load their root and push them onto the stack
VNodeKind::Component(sc) => { VNodeKind::Component(sc) => {
let scope = self.scopes.try_get(sc.ass_scope.get().unwrap()).unwrap(); let scope = self.scopes.get(sc.ass_scope.get().unwrap()).unwrap();
// Simply swap the current node on the stack with the root of the component // Simply swap the current node on the stack with the root of the component
*node = scope.root(); *node = scope.root();

View file

@ -10,8 +10,9 @@
//! //!
pub use crate::innerlude::{ pub use crate::innerlude::{
format_args_f, html, rsx, DioxusElement, DomEdit, EventTrigger, LazyNodes, NodeFactory, format_args_f, html, rsx, Context, DioxusElement, DomEdit, EventTrigger, LazyNodes,
Properties, RealDom, RealDomNode, ScopeId, VNode, VNodeKind, VirtualDom, VirtualEvent, FC, NodeFactory, Properties, RealDom, RealDomNode, ScopeId, VNode, VNodeKind, VirtualDom,
VirtualEvent, FC,
}; };
pub mod prelude { pub mod prelude {

View file

@ -224,7 +224,7 @@ impl VirtualDom {
&self.tasks, &self.tasks,
); );
let cur_component = self.components.try_get_mut(self.base_scope).unwrap(); let cur_component = self.components.get_mut(self.base_scope).unwrap();
cur_component.run_scope()?; cur_component.run_scope()?;
let meta = diff_machine.create(cur_component.next_frame()); let meta = diff_machine.create(cur_component.next_frame());
@ -312,7 +312,7 @@ impl VirtualDom {
// Suspense Events! A component's suspended node is updated // Suspense Events! A component's suspended node is updated
VirtualEvent::SuspenseEvent { hook_idx, domnode } => { VirtualEvent::SuspenseEvent { hook_idx, domnode } => {
let scope = self.components.try_get_mut(trigger.originator).unwrap(); let scope = self.components.get_mut(trigger.originator).unwrap();
// safety: we are sure that there are no other references to the inner content of this hook // safety: we are sure that there are no other references to the inner content of this hook
let hook = unsafe { scope.hooks.get_mut::<SuspenseHook>(*hook_idx) }.unwrap(); let hook = unsafe { scope.hooks.get_mut::<SuspenseHook>(*hook_idx) }.unwrap();
@ -339,8 +339,8 @@ impl VirtualDom {
// We use the reconciler to request new IDs and then commit/uncommit the IDs when the scheduler is finished // We use the reconciler to request new IDs and then commit/uncommit the IDs when the scheduler is finished
_ => { _ => {
self.components self.components
.try_get_mut(trigger.originator)? .get_mut(trigger.originator)
.call_listener(trigger)?; .map(|f| f.call_listener(trigger));
// Now, there are events in the queue // Now, there are events in the queue
let mut updates = self.event_queue.queue.as_ref().borrow_mut(); let mut updates = self.event_queue.queue.as_ref().borrow_mut();
@ -366,7 +366,7 @@ impl VirtualDom {
// Start a new mutable borrow to components // Start a new mutable borrow to components
// We are guaranteeed that this scope is unique because we are tracking which nodes have modified // We are guaranteeed that this scope is unique because we are tracking which nodes have modified
let cur_component = self.components.try_get_mut(update.idx).unwrap(); let cur_component = self.components.get_mut(update.idx).unwrap();
cur_component.run_scope()?; cur_component.run_scope()?;
@ -380,7 +380,7 @@ impl VirtualDom {
} }
pub fn base_scope(&self) -> &Scope { pub fn base_scope(&self) -> &Scope {
self.components.try_get(self.base_scope).unwrap() self.components.get(self.base_scope).unwrap()
} }
} }

View file

@ -423,7 +423,7 @@ pub trait GlobalAttributes {
list_style_position: "list-style-position", list_style_position: "list-style-position",
/// Specifies the marker style for a list_item. /// Specifies the marker style for a list_item.
list_style_type: "list-style-type", list_styler_type: "list-style-type",
/// Sets the margin on all four sides of the element. /// Sets the margin on all four sides of the element.
margin: "margin", margin: "margin",
@ -1016,6 +1016,8 @@ builder_constructors! {
r#type: Mime, r#type: Mime,
// ping: SpacedList<Uri>, // ping: SpacedList<Uri>,
// rel: SpacedList<LinkType>, // rel: SpacedList<LinkType>,
ping: SpacedList,
rel: SpacedList,
}; };
/// Build a /// Build a
@ -1274,6 +1276,14 @@ builder_constructors! {
src: Uri, src: Uri,
srcdoc: Uri, srcdoc: Uri,
width: usize, width: usize,
marginWidth: String,
align: String,
longdesc: String,
scrolling: String,
marginHeight: String,
frameBorder: String,
// sandbox: SpacedSet<Sandbox>, // sandbox: SpacedSet<Sandbox>,
}; };
@ -1850,7 +1860,7 @@ pub trait SvgAttributes {
to: "to", to: "to",
transform: "transform", transform: "transform",
transform_origin: "transform-origin", transform_origin: "transform-origin",
_type: "_type", r#type: "_type",
u1: "u1", u1: "u1",
u2: "u2", u2: "u2",
underline_position: "underline-position", underline_position: "underline-position",

View file

@ -22,7 +22,7 @@ async fn main() -> Result<(), std::io::Error> {
let dom = VirtualDom::launch_with_props_in_place(Example, ExampleProps { initial_name }); let dom = VirtualDom::launch_with_props_in_place(Example, ExampleProps { initial_name });
Ok(Response::builder(200) Ok(Response::builder(200)
.body(format!("{}", dioxus_ssr::render_root(&dom))) .body(format!("{}", dioxus_ssr::render_vdom(&dom)))
.content_type(tide::http::mime::HTML) .content_type(tide::http::mime::HTML)
.build()) .build())
}); });
@ -38,7 +38,7 @@ struct ExampleProps {
initial_name: String, initial_name: String,
} }
pub static Example: FC<ExampleProps> = |cx| { static Example: FC<ExampleProps> = |cx| {
let dispaly_name = use_state(cx, move || cx.initial_name.clone()); let dispaly_name = use_state(cx, move || cx.initial_name.clone());
cx.render(rsx! { cx.render(rsx! {

View file

@ -15,7 +15,7 @@ fn main() {
let mut dom = VirtualDom::new(App); let mut dom = VirtualDom::new(App);
dom.rebuild_in_place().expect("failed to run virtualdom"); dom.rebuild_in_place().expect("failed to run virtualdom");
file.write_fmt(format_args!("{}", TextRenderer::new(&dom))) file.write_fmt(format_args!("{}", TextRenderer::from_vdom(&dom)))
.unwrap(); .unwrap();
} }

View file

@ -10,8 +10,21 @@ use std::fmt::{Display, Formatter};
use dioxus_core::*; use dioxus_core::*;
pub fn render_root(vdom: &VirtualDom) -> String { pub fn render_vnode(vnode: &VNode, string: &mut String) {}
format!("{:}", TextRenderer::new(vdom))
pub fn render_vdom(vdom: &VirtualDom) -> String {
format!("{:}", TextRenderer::from_vdom(vdom))
}
pub fn render_vdom_scope(vdom: &VirtualDom, scope: ScopeId) -> Option<String> {
Some(format!(
"{:}",
TextRenderer {
cfg: SsrConfig::default(),
root: vdom.components.get(scope).unwrap().root(),
vdom: Some(vdom)
}
))
} }
pub struct SsrConfig { pub struct SsrConfig {
@ -56,14 +69,22 @@ impl Default for SsrConfig {
/// assert_eq!(output, "<div>hello world</div>"); /// assert_eq!(output, "<div>hello world</div>");
/// ``` /// ```
pub struct TextRenderer<'a> { pub struct TextRenderer<'a> {
vdom: &'a VirtualDom, vdom: Option<&'a VirtualDom>,
root: &'a VNode<'a>,
cfg: SsrConfig, cfg: SsrConfig,
} }
impl Display for TextRenderer<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.html_render(self.root, f, 0)
}
}
impl<'a> TextRenderer<'a> { impl<'a> TextRenderer<'a> {
pub fn new(vdom: &'a VirtualDom) -> Self { pub fn from_vdom(vdom: &'a VirtualDom) -> Self {
Self { Self {
vdom, root: vdom.base_scope().root(),
vdom: Some(vdom),
cfg: SsrConfig::default(), cfg: SsrConfig::default(),
} }
} }
@ -141,10 +162,10 @@ impl<'a> TextRenderer<'a> {
} }
VNodeKind::Component(vcomp) => { VNodeKind::Component(vcomp) => {
let idx = vcomp.ass_scope.get().unwrap(); let idx = vcomp.ass_scope.get().unwrap();
if let Some(vdom) = self.vdom {
let new_node = self.vdom.components.try_get(idx).unwrap().root(); let new_node = vdom.components.get(idx).unwrap().root();
self.html_render(new_node, f, il + 1)?;
self.html_render(new_node, f, il + 1)?; }
} }
VNodeKind::Suspended { .. } => { VNodeKind::Suspended { .. } => {
// we can't do anything with suspended nodes // we can't do anything with suspended nodes
@ -154,14 +175,6 @@ impl<'a> TextRenderer<'a> {
} }
} }
impl Display for TextRenderer<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let root = self.vdom.base_scope();
let root_node = root.root();
self.html_render(root_node, f, 0)
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -216,21 +229,21 @@ mod tests {
fn to_string_works() { fn to_string_works() {
let mut dom = VirtualDom::new(SIMPLE_APP); let mut dom = VirtualDom::new(SIMPLE_APP);
dom.rebuild_in_place().expect("failed to run virtualdom"); dom.rebuild_in_place().expect("failed to run virtualdom");
dbg!(render_root(&dom)); dbg!(render_vdom(&dom));
} }
#[test] #[test]
fn nested() { fn nested() {
let mut dom = VirtualDom::new(NESTED_APP); let mut dom = VirtualDom::new(NESTED_APP);
dom.rebuild_in_place().expect("failed to run virtualdom"); dom.rebuild_in_place().expect("failed to run virtualdom");
dbg!(render_root(&dom)); dbg!(render_vdom(&dom));
} }
#[test] #[test]
fn fragment_app() { fn fragment_app() {
let mut dom = VirtualDom::new(FRAGMENT_APP); let mut dom = VirtualDom::new(FRAGMENT_APP);
dom.rebuild_in_place().expect("failed to run virtualdom"); dom.rebuild_in_place().expect("failed to run virtualdom");
dbg!(render_root(&dom)); dbg!(render_vdom(&dom));
} }
#[test] #[test]
@ -243,7 +256,7 @@ mod tests {
let mut dom = VirtualDom::new(SLIGHTLY_MORE_COMPLEX); let mut dom = VirtualDom::new(SLIGHTLY_MORE_COMPLEX);
dom.rebuild_in_place().expect("failed to run virtualdom"); dom.rebuild_in_place().expect("failed to run virtualdom");
file.write_fmt(format_args!("{}", TextRenderer::new(&dom))) file.write_fmt(format_args!("{}", TextRenderer::from_vdom(&dom)))
.unwrap(); .unwrap();
} }
@ -263,6 +276,6 @@ mod tests {
let mut dom = VirtualDom::new(STLYE_APP); let mut dom = VirtualDom::new(STLYE_APP);
dom.rebuild_in_place().expect("failed to run virtualdom"); dom.rebuild_in_place().expect("failed to run virtualdom");
dbg!(render_root(&dom)); dbg!(render_vdom(&dom));
} }
} }

View file

@ -13,15 +13,12 @@ use std::{pin::Pin, time::Duration};
use dioxus::prelude::*; use dioxus::prelude::*;
use dioxus_web::*;
fn main() { fn main() {
// Setup logging // Setup logging
wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
console_error_panic_hook::set_once(); console_error_panic_hook::set_once();
wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
// Run the app dioxus_web::launch(App, |c| c)
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App));
} }
#[derive(serde::Deserialize)] #[derive(serde::Deserialize)]
@ -38,17 +35,12 @@ static App: FC<()> = |cx| {
|| surf::get(ENDPOINT).recv_json::<DogApi>(), || surf::get(ENDPOINT).recv_json::<DogApi>(),
|cx, res| match res { |cx, res| match res {
Ok(res) => rsx!(in cx, img { src: "{res.message}" }), Ok(res) => rsx!(in cx, img { src: "{res.message}" }),
Err(err) => rsx!(in cx, div { "No doggos for you :(" }), Err(_err) => rsx!(in cx, div { "No doggos for you :(" }),
}, },
); );
log::error!("RIP WE RAN THE COMPONENT");
cx.render(rsx! { cx.render(rsx! {
div { div { style: { align_items: "center" }
style: {
align_items: "center"
}
section { class: "py-12 px-4 text-center" section { class: "py-12 px-4 text-center"
div { class: "w-full max-w-2xl mx-auto" div { class: "w-full max-w-2xl mx-auto"
span { class: "text-sm font-semibold" span { class: "text-sm font-semibold"

292
packages/web/src/cache.rs Normal file
View file

@ -0,0 +1,292 @@
/// Wasm-bindgen has a performance option to intern commonly used phrases
/// This saves the decoding cost, making the interaction of Rust<->JS more performant.
/// We intern all the HTML tags and attributes, making most operations much faster.
///
/// Interning takes about 1ms at the start of the app, but saves a *ton* of time later on.
pub fn intern_cache() {
let cached_words = [
// All the HTML Tags
"a",
"abbr",
"address",
"area",
"article",
"aside",
"audio",
"b",
"base",
"bdi",
"bdo",
"big",
"blockquote",
"body",
"br",
"button",
"canvas",
"caption",
"cite",
"code",
"col",
"colgroup",
"command",
"data",
"datalist",
"dd",
"del",
"details",
"dfn",
"dialog",
"div",
"dl",
"dt",
"em",
"embed",
"fieldset",
"figcaption",
"figure",
"footer",
"form",
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
"head",
"header",
"hr",
"html",
"i",
"iframe",
"img",
"input",
"ins",
"kbd",
"keygen",
"label",
"legend",
"li",
"link",
"main",
"map",
"mark",
"menu",
"menuitem",
"meta",
"meter",
"nav",
"noscript",
"object",
"ol",
"optgroup",
"option",
"output",
"p",
"param",
"picture",
"pre",
"progress",
"q",
"rp",
"rt",
"ruby",
"s",
"samp",
"script",
"section",
"select",
"small",
"source",
"span",
"strong",
"style",
"sub",
"summary",
"sup",
"table",
"tbody",
"td",
"textarea",
"tfoot",
"th",
"thead",
"time",
"title",
"tr",
"track",
"u",
"ul",
"var",
"video",
"wbr",
// All the event handlers
"Attribute",
"accept",
"accept-charset",
"accesskey",
"action",
"alt",
"async",
"autocomplete",
"autofocus",
"autoplay",
"charset",
"checked",
"cite",
"class",
"cols",
"colspan",
"content",
"contenteditable",
"controls",
"coords",
"data",
"data-*",
"datetime",
"default",
"defer",
"dir",
"dirname",
"disabled",
"download",
"draggable",
"enctype",
"for",
"form",
"formaction",
"headers",
"height",
"hidden",
"high",
"href",
"hreflang",
"http-equiv",
"id",
"ismap",
"kind",
"label",
"lang",
"list",
"loop",
"low",
"max",
"maxlength",
"media",
"method",
"min",
"multiple",
"muted",
"name",
"novalidate",
"onabort",
"onafterprint",
"onbeforeprint",
"onbeforeunload",
"onblur",
"oncanplay",
"oncanplaythrough",
"onchange",
"onclick",
"oncontextmenu",
"oncopy",
"oncuechange",
"oncut",
"ondblclick",
"ondrag",
"ondragend",
"ondragenter",
"ondragleave",
"ondragover",
"ondragstart",
"ondrop",
"ondurationchange",
"onemptied",
"onended",
"onerror",
"onfocus",
"onhashchange",
"oninput",
"oninvalid",
"onkeydown",
"onkeypress",
"onkeyup",
"onload",
"onloadeddata",
"onloadedmetadata",
"onloadstart",
"onmousedown",
"onmousemove",
"onmouseout",
"onmouseover",
"onmouseup",
"onmousewheel",
"onoffline",
"ononline",
"<body>",
"onpageshow",
"onpaste",
"onpause",
"onplay",
"onplaying",
"<body>",
"onprogress",
"onratechange",
"onreset",
"onresize",
"onscroll",
"onsearch",
"onseeked",
"onseeking",
"onselect",
"onstalled",
"<body>",
"onsubmit",
"onsuspend",
"ontimeupdate",
"ontoggle",
"onunload",
"onvolumechange",
"onwaiting",
"onwheel",
"open",
"optimum",
"pattern",
"placeholder",
"poster",
"preload",
"readonly",
"rel",
"required",
"reversed",
"rows",
"rowspan",
"sandbox",
"scope",
"selected",
"shape",
"size",
"sizes",
"span",
"spellcheck",
"src",
"srcdoc",
"srclang",
"srcset",
"start",
"step",
"style",
"tabindex",
"target",
"title",
"translate",
"type",
"usemap",
"value",
"width",
"wrap",
];
for s in cached_words {
wasm_bindgen::intern(s);
}
}

View file

@ -3,449 +3,112 @@
//! This crate implements a renderer of the Dioxus Virtual DOM for the web browser using Websys. //! This crate implements a renderer of the Dioxus Virtual DOM for the web browser using Websys.
use dioxus::prelude::{Context, Properties, VNode}; use dioxus::prelude::{Context, Properties, VNode};
use futures_util::{pin_mut, Stream, StreamExt};
use fxhash::FxHashMap;
use web_sys::{window, Document, Element, Event, Node};
// use futures::{channel::mpsc, SinkExt, StreamExt};
use dioxus::virtual_dom::VirtualDom; use dioxus::virtual_dom::VirtualDom;
pub use dioxus_core as dioxus; pub use dioxus_core as dioxus;
use dioxus_core::{events::EventTrigger, prelude::FC}; use dioxus_core::{events::EventTrigger, prelude::FC};
use futures_util::{pin_mut, Stream, StreamExt};
use fxhash::FxHashMap;
use web_sys::{window, Document, Element, Event, Node};
pub use dioxus_core::prelude; mod cache;
// pub mod interpreter; mod new;
pub mod new;
/// The `WebsysRenderer` provides a way of rendering a Dioxus Virtual DOM to the browser's DOM. /// Launches the VirtualDOM from the specified component function.
/// Under the hood, we leverage WebSys and interact directly with the DOM
/// ///
pub struct WebsysRenderer { /// This method will block the thread with `spawn_local`
internal_dom: VirtualDom, pub fn launch<F>(root: FC<()>, config: F)
where
F: FnOnce(()),
{
wasm_bindgen_futures::spawn_local(run(root))
} }
impl WebsysRenderer { pub fn launch_with_props<T, F>(root: FC<T>, root_props: T, config: F)
/// This method is the primary entrypoint for Websys Dioxus apps. Will panic if an error occurs while rendering. where
/// See DioxusErrors for more information on how these errors could occour. T: Properties + 'static,
/// F: FnOnce(()),
/// ```ignore {
/// fn main() { wasm_bindgen_futures::spawn_local(run_with_props(root, root_props))
/// wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example)); }
/// }
/// ```
///
/// Run the app to completion, panicing if any error occurs while rendering.
/// Pairs well with the wasm_bindgen async handler
pub async fn start(root: FC<()>) {
Self::new(root).run().await.expect("Virtual DOM failed :(");
}
/// Create a new instance of the Dioxus Virtual Dom with no properties for the root component. /// This method is the primary entrypoint for Websys Dioxus apps. Will panic if an error occurs while rendering.
/// /// See DioxusErrors for more information on how these errors could occour.
/// This means that the root component must either consumes its own context, or statics are used to generate the page. ///
/// The root component can access things like routing in its context. /// ```ignore
pub fn new(root: FC<()>) -> Self { /// fn main() {
Self::new_with_props(root, ()) /// wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example));
} /// }
/// ```
///
/// Run the app to completion, panicing if any error occurs while rendering.
/// Pairs well with the wasm_bindgen async handler
pub async fn run(root: FC<()>) {
run_with_props(root, ()).await;
}
/// Create a new text-renderer instance from a functional component root. pub async fn run_with_props<T: Properties + 'static>(root: FC<T>, root_props: T) {
/// Automatically progresses the creation of the VNode tree to completion. let dom = VirtualDom::new_with_props(root, root_props);
/// event_loop(dom).await.expect("Event loop failed");
/// A VDom is automatically created. If you want more granular control of the VDom, use `from_vdom` }
pub fn new_with_props<T: Properties + 'static>(root: FC<T>, root_props: T) -> Self {
Self::from_vdom(VirtualDom::new_with_props(root, root_props))
}
/// Create a new text renderer from an existing Virtual DOM. pub async fn event_loop(mut internal_dom: VirtualDom) -> dioxus_core::error::Result<()> {
pub fn from_vdom(dom: VirtualDom) -> Self { use wasm_bindgen::JsCast;
Self { internal_dom: dom }
}
pub async fn run(&mut self) -> dioxus_core::error::Result<()> { let root = prepare_websys_dom();
use wasm_bindgen::JsCast; let root_node = root.clone().dyn_into::<Node>().unwrap();
let root = prepare_websys_dom(); let mut websys_dom = crate::new::WebsysDom::new(root.clone());
let root_node = root.clone().dyn_into::<Node>().unwrap();
let mut websys_dom = crate::new::WebsysDom::new(root.clone()); websys_dom.stack.push(root_node.clone());
websys_dom.stack.push(root_node);
websys_dom.stack.push(root_node.clone()); let mut edits = Vec::new();
websys_dom.stack.push(root_node); internal_dom.rebuild(&mut websys_dom, &mut edits)?;
websys_dom.process_edits(&mut edits);
let mut edits = Vec::new(); log::info!("Going into event loop");
self.internal_dom.rebuild(&mut websys_dom, &mut edits)?; loop {
websys_dom.process_edits(&mut edits); let trigger = {
let real_queue = websys_dom.wait_for_event();
if internal_dom.tasks.is_empty() {
log::info!("tasks is empty, waiting for dom event to trigger soemthing");
real_queue.await
} else {
log::info!("tasks is not empty, waiting for either tasks or event system");
let task_queue = (&mut internal_dom.tasks).next();
log::info!("Going into event loop"); pin_mut!(real_queue);
loop { pin_mut!(task_queue);
let trigger = {
let real_queue = websys_dom.wait_for_event();
if self.internal_dom.tasks.is_empty() {
log::info!("tasks is empty, waiting for dom event to trigger soemthing");
real_queue.await
} else {
log::info!("tasks is not empty, waiting for either tasks or event system");
let task_queue = (&mut self.internal_dom.tasks).next();
pin_mut!(real_queue); match futures_util::future::select(real_queue, task_queue).await {
pin_mut!(task_queue); futures_util::future::Either::Left((trigger, _)) => trigger,
futures_util::future::Either::Right((trigger, _)) => trigger,
match futures_util::future::select(real_queue, task_queue).await {
futures_util::future::Either::Left((trigger, _)) => trigger,
futures_util::future::Either::Right((trigger, _)) => trigger,
}
} }
};
if let Some(real_trigger) = trigger {
log::info!("event received");
self.internal_dom.queue_event(real_trigger)?;
let mut edits = Vec::new();
self.internal_dom
.progress_with_event(&mut websys_dom, &mut edits)
.await?;
websys_dom.process_edits(&mut edits);
} }
} };
// should actually never return from this, should be an error, rustc just cant see it if let Some(real_trigger) = trigger {
Ok(()) log::info!("event received");
internal_dom.queue_event(real_trigger)?;
let mut edits = Vec::new();
internal_dom
.progress_with_event(&mut websys_dom, &mut edits)
.await?;
websys_dom.process_edits(&mut edits);
}
} }
// should actually never return from this, should be an error, rustc just cant see it
Ok(())
} }
fn prepare_websys_dom() -> Element { fn prepare_websys_dom() -> Element {
// Initialize the container on the dom web_sys::window()
// Hook up the body as the root component to render tinto .expect("should have access to the Window")
let window = web_sys::window().expect("should have access to the Window");
let document = window
.document() .document()
.expect("should have access to the Document"); .expect("should have access to the Document")
.get_element_by_id("dioxusroot")
// let body = document.body().unwrap(); .unwrap()
let el = document.get_element_by_id("dioxusroot").unwrap();
// Build a dummy div
// let container: &Element = body.as_ref();
// container.set_inner_html("");
// container
// .append_child(
// document
// .create_element("div")
// .expect("should create element OK")
// .as_ref(),
// )
// .expect("should append child OK");
el
// container.clone()
}
// Progress the mount of the root component
// Iterate through the nodes, attaching the closure and sender to the listener
// {
// let mut remote_sender = sender.clone();
// let listener = move || {
// let event = EventTrigger::new();
// wasm_bindgen_futures::spawn_local(async move {
// remote_sender
// .send(event)
// .await
// .expect("Updating receiver failed");
// })
// };
// }
/// Wasm-bindgen has a performance option to intern commonly used phrases
/// This saves the decoding cost, making the interaction of Rust<->JS more performant.
/// We intern all the HTML tags and attributes, making most operations much faster.
///
/// Interning takes about 1ms at the start of the app, but saves a *ton* of time later on.
pub fn intern_cache() {
let cached_words = [
// All the HTML Tags
"a",
"abbr",
"address",
"area",
"article",
"aside",
"audio",
"b",
"base",
"bdi",
"bdo",
"big",
"blockquote",
"body",
"br",
"button",
"canvas",
"caption",
"cite",
"code",
"col",
"colgroup",
"command",
"data",
"datalist",
"dd",
"del",
"details",
"dfn",
"dialog",
"div",
"dl",
"dt",
"em",
"embed",
"fieldset",
"figcaption",
"figure",
"footer",
"form",
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
"head",
"header",
"hr",
"html",
"i",
"iframe",
"img",
"input",
"ins",
"kbd",
"keygen",
"label",
"legend",
"li",
"link",
"main",
"map",
"mark",
"menu",
"menuitem",
"meta",
"meter",
"nav",
"noscript",
"object",
"ol",
"optgroup",
"option",
"output",
"p",
"param",
"picture",
"pre",
"progress",
"q",
"rp",
"rt",
"ruby",
"s",
"samp",
"script",
"section",
"select",
"small",
"source",
"span",
"strong",
"style",
"sub",
"summary",
"sup",
"table",
"tbody",
"td",
"textarea",
"tfoot",
"th",
"thead",
"time",
"title",
"tr",
"track",
"u",
"ul",
"var",
"video",
"wbr",
// All the event handlers
"Attribute",
"accept",
"accept-charset",
"accesskey",
"action",
"alt",
"async",
"autocomplete",
"autofocus",
"autoplay",
"charset",
"checked",
"cite",
"class",
"cols",
"colspan",
"content",
"contenteditable",
"controls",
"coords",
"data",
"data-*",
"datetime",
"default",
"defer",
"dir",
"dirname",
"disabled",
"download",
"draggable",
"enctype",
"for",
"form",
"formaction",
"headers",
"height",
"hidden",
"high",
"href",
"hreflang",
"http-equiv",
"id",
"ismap",
"kind",
"label",
"lang",
"list",
"loop",
"low",
"max",
"maxlength",
"media",
"method",
"min",
"multiple",
"muted",
"name",
"novalidate",
"onabort",
"onafterprint",
"onbeforeprint",
"onbeforeunload",
"onblur",
"oncanplay",
"oncanplaythrough",
"onchange",
"onclick",
"oncontextmenu",
"oncopy",
"oncuechange",
"oncut",
"ondblclick",
"ondrag",
"ondragend",
"ondragenter",
"ondragleave",
"ondragover",
"ondragstart",
"ondrop",
"ondurationchange",
"onemptied",
"onended",
"onerror",
"onfocus",
"onhashchange",
"oninput",
"oninvalid",
"onkeydown",
"onkeypress",
"onkeyup",
"onload",
"onloadeddata",
"onloadedmetadata",
"onloadstart",
"onmousedown",
"onmousemove",
"onmouseout",
"onmouseover",
"onmouseup",
"onmousewheel",
"onoffline",
"ononline",
"<body>",
"onpageshow",
"onpaste",
"onpause",
"onplay",
"onplaying",
"<body>",
"onprogress",
"onratechange",
"onreset",
"onresize",
"onscroll",
"onsearch",
"onseeked",
"onseeking",
"onselect",
"onstalled",
"<body>",
"onsubmit",
"onsuspend",
"ontimeupdate",
"ontoggle",
"onunload",
"onvolumechange",
"onwaiting",
"onwheel",
"open",
"optimum",
"pattern",
"placeholder",
"poster",
"preload",
"readonly",
"rel",
"required",
"reversed",
"rows",
"rowspan",
"sandbox",
"scope",
"selected",
"shape",
"size",
"sizes",
"span",
"spellcheck",
"src",
"srcdoc",
"srclang",
"srcset",
"start",
"step",
"style",
"tabindex",
"target",
"title",
"translate",
"type",
"usemap",
"value",
"width",
"wrap",
];
for s in cached_words {
wasm_bindgen::intern(s);
}
} }

View file

@ -1,409 +0,0 @@
use std::collections::HashMap;
use std::collections::HashSet;
use std::{cmp::min, rc::Rc};
use wasm_bindgen::JsCast;
use wasm_bindgen::JsValue;
use web_sys::{Element, Node, Text};
/// Apply all of the patches to our old root node in order to create the new root node
/// that we desire.
/// This is usually used after diffing two virtual nodes.
pub fn patch<N: Into<Node>>(root_node: N, patches: &Vec<Patch>) -> Result<(), JsValue> {
// pub fn patch<N: Into<Node>>(root_node: N, patches: &Vec<Patch>) -> Result<ActiveClosures, JsValue> {
let root_node: Node = root_node.into();
let mut cur_node_idx = 0;
let mut nodes_to_find = HashSet::new();
for patch in patches {
nodes_to_find.insert(patch.node_idx());
}
let mut element_nodes_to_patch = HashMap::new();
let mut text_nodes_to_patch = HashMap::new();
// Closures that were added to the DOM during this patch operation.
// let mut active_closures = HashMap::new();
find_nodes(
root_node,
&mut cur_node_idx,
&mut nodes_to_find,
&mut element_nodes_to_patch,
&mut text_nodes_to_patch,
);
for patch in patches {
let patch_node_idx = patch.node_idx();
if let Some(element) = element_nodes_to_patch.get(&patch_node_idx) {
let new_closures = apply_element_patch(&element, &patch)?;
// active_closures.extend(new_closures);
continue;
}
if let Some(text_node) = text_nodes_to_patch.get(&patch_node_idx) {
apply_text_patch(&text_node, &patch)?;
continue;
}
unreachable!("Getting here means we didn't find the element or next node that we were supposed to patch.")
}
// Ok(active_closures)
Ok(())
}
fn find_nodes(
root_node: Node,
cur_node_idx: &mut usize,
nodes_to_find: &mut HashSet<usize>,
element_nodes_to_patch: &mut HashMap<usize, Element>,
text_nodes_to_patch: &mut HashMap<usize, Text>,
) {
if nodes_to_find.len() == 0 {
return;
}
// We use child_nodes() instead of children() because children() ignores text nodes
let children = root_node.child_nodes();
let child_node_count = children.length();
// If the root node matches, mark it for patching
if nodes_to_find.get(&cur_node_idx).is_some() {
match root_node.node_type() {
Node::ELEMENT_NODE => {
element_nodes_to_patch.insert(*cur_node_idx, root_node.unchecked_into());
}
Node::TEXT_NODE => {
text_nodes_to_patch.insert(*cur_node_idx, root_node.unchecked_into());
}
other => unimplemented!("Unsupported root node type: {}", other),
}
nodes_to_find.remove(&cur_node_idx);
}
*cur_node_idx += 1;
for i in 0..child_node_count {
let node = children.item(i).unwrap();
match node.node_type() {
Node::ELEMENT_NODE => {
find_nodes(
node,
cur_node_idx,
nodes_to_find,
element_nodes_to_patch,
text_nodes_to_patch,
);
}
Node::TEXT_NODE => {
if nodes_to_find.get(&cur_node_idx).is_some() {
text_nodes_to_patch.insert(*cur_node_idx, node.unchecked_into());
}
*cur_node_idx += 1;
}
Node::COMMENT_NODE => {
// At this time we do not support user entered comment nodes, so if we see a comment
// then it was a delimiter created by virtual-dom-rs in order to ensure that two
// neighboring text nodes did not get merged into one by the browser. So we skip
// over this virtual-dom-rs generated comment node.
}
_other => {
// Ignoring unsupported child node type
// TODO: What do we do with this situation? Log a warning?
}
}
}
}
// pub type ActiveClosures = HashMap<u32, Vec<DynClosure>>;
// fn apply_element_patch(node: &Element, patch: &Patch) -> Result<ActiveClosures, JsValue> {
fn apply_element_patch(node: &Element, patch: &Patch) -> Result<(), JsValue> {
// let active_closures = HashMap::new();
match patch {
Patch::AddAttributes(_node_idx, attributes) => {
for (attrib_name, attrib_val) in attributes.iter() {
node.set_attribute(attrib_name, attrib_val)?;
}
// Ok(active_closures)
Ok(())
}
Patch::RemoveAttributes(_node_idx, attributes) => {
for attrib_name in attributes.iter() {
node.remove_attribute(attrib_name)?;
}
// Ok(active_closures)
Ok(())
}
Patch::Replace(_node_idx, new_node) => {
let created_node = create_dom_node(&new_node);
node.replace_with_with_node_1(&created_node.node)?;
Ok(())
// Ok(created_node.closures)
}
Patch::TruncateChildren(_node_idx, num_children_remaining) => {
let children = node.child_nodes();
let mut child_count = children.length();
// We skip over any separators that we placed between two text nodes
// -> `<!--ptns-->`
// and trim all children that come after our new desired `num_children_remaining`
let mut non_separator_children_found = 0;
for index in 0 as u32..child_count {
let child = children
.get(min(index, child_count - 1))
.expect("Potential child to truncate");
// If this is a comment node then we know that it is a `<!--ptns-->`
// text node separator that was created in virtual_node/mod.rs.
if child.node_type() == Node::COMMENT_NODE {
continue;
}
non_separator_children_found += 1;
if non_separator_children_found <= *num_children_remaining as u32 {
continue;
}
node.remove_child(&child).expect("Truncated children");
child_count -= 1;
}
Ok(())
// Ok(active_closures)
}
Patch::AppendChildren(_node_idx, new_nodes) => {
let parent = &node;
let mut active_closures = HashMap::new();
for new_node in new_nodes {
let created_node = create_dom_node(&new_node);
// let created_node = new_node.create_dom_node();
parent.append_child(&created_node.node)?;
active_closures.extend(created_node.closures);
}
Ok(())
// Ok(active_closures)
}
Patch::ChangeText(_node_idx, _new_node) => {
unreachable!("Elements should not receive ChangeText patches.")
}
}
}
fn apply_text_patch(node: &Text, patch: &Patch) -> Result<(), JsValue> {
match patch {
Patch::ChangeText(_node_idx, new_node) => {
node.set_node_value(Some(&new_node.text));
}
Patch::Replace(_node_idx, new_node) => {
node.replace_with_with_node_1(&create_dom_node(&new_node).node)?;
// node.replace_with_with_node_1(&new_node.create_dom_node().node)?;
}
other => unreachable!(
"Text nodes should only receive ChangeText or Replace patches, not ",
// other,
// "Text nodes should only receive ChangeText or Replace patches, not {:?}.",
// other,
),
};
Ok(())
}
/// A node along with all of the closures that were created for that
/// node's events and all of it's child node's events.
pub struct CreatedNode<T> {
/// A `Node` or `Element` that was created from a `VirtualNode`
pub node: T,
/// A map of a node's unique identifier along with all of the Closures for that node.
///
/// The DomUpdater uses this to look up nodes and see if they're still in the page. If not
/// the reference that we maintain to their closure will be dropped, thus freeing the Closure's
/// memory.
pub closures: HashMap<u32, Vec<DynClosure>>,
}
/// Box<dyn AsRef<JsValue>>> is our js_sys::Closure. Stored this way to allow us to store
/// any Closure regardless of the arguments.
pub type DynClosure = Rc<dyn AsRef<JsValue>>;
impl<T> CreatedNode<T> {
pub fn without_closures<N: Into<T>>(node: N) -> Self {
CreatedNode {
node: node.into(),
closures: HashMap::with_capacity(0),
}
}
}
impl<T> std::ops::Deref for CreatedNode<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.node
}
}
impl From<CreatedNode<Element>> for CreatedNode<Node> {
fn from(other: CreatedNode<Element>) -> CreatedNode<Node> {
CreatedNode {
node: other.node.into(),
closures: other.closures,
}
}
}
fn create_dom_node(node: &VNode<'_>) -> CreatedNode<Node> {
match node {
VNode::Text(text_node) => CreatedNode::without_closures(create_text_node(text_node)),
VNode::Element(element_node) => create_element_node(element_node).into(),
// VNode::Element(element_node) => element_node.create_element_node().into(),
VNode::Suspended => todo!(" not iimplemented yet"),
VNode::Component(_) => todo!(" not iimplemented yet"),
}
}
/// Build a DOM element by recursively creating DOM nodes for this element and it's
/// children, it's children's children, etc.
pub fn create_element_node(node: &dioxus_core::nodes::VElement) -> CreatedNode<Element> {
let document = web_sys::window().unwrap().document().unwrap();
// TODO: enable svg again
// let element = if html_validation::is_svg_namespace(&node.tag_name) {
// document
// .create_element_ns(Some("http://www.w3.org/2000/svg"), &node.tag_name)
// .unwrap()
// } else {
let element = document.create_element(&node.tag_name).unwrap();
// };
let mut closures = HashMap::new();
node.attributes
.iter()
.map(|f| (f.name, f.value))
.for_each(|(name, value)| {
if name == "unsafe_inner_html" {
element.set_inner_html(value);
return;
}
element
.set_attribute(name, value)
.expect("Set element attribute in create element");
});
// if node.events.0.len() > 0 {
// let unique_id = create_unique_identifier();
// element
// .set_attribute("data-vdom-id".into(), &unique_id.to_string())
// .expect("Could not set attribute on element");
// closures.insert(unique_id, vec![]);
// node.events.0.iter().for_each(|(onevent, callback)| {
// // onclick -> click
// let event = &onevent[2..];
// let current_elem: &EventTarget = element.dyn_ref().unwrap();
// current_elem
// .add_event_listener_with_callback(event, callback.as_ref().as_ref().unchecked_ref())
// .unwrap();
// closures
// .get_mut(&unique_id)
// .unwrap()
// .push(Rc::clone(callback));
// });
// }
let mut previous_node_was_text = false;
node.children.iter().for_each(|child| {
// log::info!("Patching child");
match child {
VNode::Text(text_node) => {
let current_node = element.as_ref() as &web_sys::Node;
// We ensure that the text siblings are patched by preventing the browser from merging
// neighboring text nodes. Originally inspired by some of React's work from 2016.
// -> https://reactjs.org/blog/2016/04/07/react-v15.html#major-changes
// -> https://github.com/facebook/react/pull/5753
//
// `ptns` = Percy text node separator
if previous_node_was_text {
let separator = document.create_comment("ptns");
current_node
.append_child(separator.as_ref() as &web_sys::Node)
.unwrap();
}
current_node
.append_child(&create_text_node(&text_node))
.unwrap();
previous_node_was_text = true;
}
VNode::Element(element_node) => {
previous_node_was_text = false;
let child = create_element_node(element_node);
// let child = element_node.create_element_node();
let child_elem: Element = child.node;
closures.extend(child.closures);
element.append_child(&child_elem).unwrap();
}
VNode::Suspended => {
todo!("Not yet supported")
}
VNode::Component(_) => {
todo!("Not yet supported")
}
}
});
// TODO: connect on mount to the event system somehow
// if let Some(on_create_elem) = node.events.0.get("on_create_elem") {
// let on_create_elem: &js_sys::Function = on_create_elem.as_ref().as_ref().unchecked_ref();
// on_create_elem
// .call1(&wasm_bindgen::JsValue::NULL, &element)
// .unwrap();
// }
CreatedNode {
node: element,
closures,
}
}
/// Return a `Text` element from a `VirtualNode`, typically right before adding it
/// into the DOM.
pub fn create_text_node(node: &VText) -> Text {
let document = web_sys::window().unwrap().document().unwrap();
document.create_text_node(&node.text)
}
// /// For any listeners in the tree, attach the sender closure.
// /// When a event is triggered, we convert it into the synthetic event type and dump it back in the Virtual Dom's queu
// fn attach_listeners(sender: &UnboundedSender<EventTrigger>, dom: &VirtualDom) {}
// fn render_diffs() {}

View file

@ -49,7 +49,7 @@
//! } //! }
//! ``` //! ```
//! //!
//! Props that are valid for the `'static` lifetime automatically get memoized by Diouxs. This means the component won't //! Props that are valid for the `'pub static` lifetime automatically get memoized by Diouxs. This means the component won't
//! re-render if its Props didn't change. However, Props that borrow data from their parent cannot be safely memoized, and //! re-render if its Props didn't change. However, Props that borrow data from their parent cannot be safely memoized, and
//! will always re-render if their parent changes. To borrow data from a parent, your component needs to add explicit lifetimes, //! will always re-render if their parent changes. To borrow data from a parent, your component needs to add explicit lifetimes,
//! otherwise Rust will get confused about whether data is borrowed from either Props or Context. Since Dioxus manages //! otherwise Rust will get confused about whether data is borrowed from either Props or Context. Since Dioxus manages
@ -67,7 +67,7 @@
//! //!
//! //!
//! The lifetimes might look a little messy, but are crucially important for Dioxus's efficiency and overall ergonimics. //! The lifetimes might look a little messy, but are crucially important for Dioxus's efficiency and overall ergonimics.
//! Components can also be crafted as static closures, enabling type inference without all the type signature noise. However, //! Components can also be crafted as pub static closures, enabling type inference without all the type signature noise. However,
//! closure-style components cannot work with borrowed data due to limitations in Rust's lifetime system. //! closure-style components cannot work with borrowed data due to limitations in Rust's lifetime system.
//! //!
//! To use custom properties for components, you'll need to derive the `Props` trait for your properties. This trait //! To use custom properties for components, you'll need to derive the `Props` trait for your properties. This trait
@ -93,7 +93,7 @@
//! Dioxus uses hooks for state management. Hooks are a form of state persisted between calls of the function component. //! Dioxus uses hooks for state management. Hooks are a form of state persisted between calls of the function component.
//! //!
//! ``` //! ```
//! pub static Example: FC<()> = |cx| { //! pub pub static Example: FC<()> = |cx| {
//! let (val, set_val) = use_state(cx, || 0); //! let (val, set_val) = use_state(cx, || 0);
//! cx.render(rsx!( //! cx.render(rsx!(
//! button { onclick: move |_| set_val(val + 1) } //! button { onclick: move |_| set_val(val + 1) }
@ -156,7 +156,7 @@
//! diouxs::web::launch(Example); //! diouxs::web::launch(Example);
//! } //! }
//! //!
//! pub static Example: FC<()> = |cx| { //! pub pub static Example: FC<()> = |cx| {
//! cx.render(rsx! { //! cx.render(rsx! {
//! div { "Hello World!" } //! div { "Hello World!" }
//! }) //! })