diff --git a/examples/async.rs b/examples/async.rs index 11b3bac3e..69084c530 100644 --- a/examples/async.rs +++ b/examples/async.rs @@ -2,43 +2,41 @@ //! //! The example from the README.md -use std::{pin::Pin, time::Duration}; - use dioxus::prelude::*; -use futures::Future; fn main() { - env_logger::init(); - log::info!("hello world"); dioxus::desktop::launch(App, |c| c).expect("faield to launch"); } -#[derive(serde::Deserialize)] -struct DogApi { - message: String, -} +pub static App: FC<()> = |cx| { + let count = use_state(cx, || 0); + let mut direction = use_state(cx, || 1); -const ENDPOINT: &str = "https://dog.ceo/api/breeds/image/random"; - -static App: FC<()> = |cx| { - let mut count = use_state(cx, || 0); - let mut fut = Box::pin(async { - let mut tick: i32 = 0; + let (async_count, dir) = (count.for_async(), *direction); + let (task, _result) = cx.use_task(move || async move { loop { - async_std::task::sleep(Duration::from_millis(250)).await; - log::debug!("ticking forward... {}", tick); - tick += 1; - // match surf::get(ENDPOINT).recv_json::().await { - // Ok(_) => (), - // Err(_) => (), - // } + gloo_timers::future::TimeoutFuture::new(250).await; + *async_count.get_mut() += dir; } }); - cx.submit_task(fut); - cx.render(rsx! { 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(); + } + } } }) }; diff --git a/examples/doggo.rs b/examples/doggo.rs deleted file mode 100644 index 2c6f3a203..000000000 --- a/examples/doggo.rs +++ /dev/null @@ -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} - } - }) -}; diff --git a/examples/reference/main.rs b/examples/reference/main.rs deleted file mode 100644 index 5a0b5b69f..000000000 --- a/examples/reference/main.rs +++ /dev/null @@ -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).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), -} - -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 = |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")), -]; diff --git a/examples/reference/tostring.rs b/examples/reference/tostring.rs index c28c1ef4f..5edf45b61 100644 --- a/examples/reference/tostring.rs +++ b/examples/reference/tostring.rs @@ -7,7 +7,7 @@ pub static Example: FC<()> = |cx| { // This is an easy/low hanging fruit to improve upon let mut dom = VirtualDom::new(SomeApp); dom.rebuild_in_place().unwrap(); - ssr::render_root(&dom) + ssr::render_vdom(&dom) }); cx.render(rsx! { diff --git a/examples/showcase.rs b/examples/showcase.rs index ae1b9f08b..ed922cabf 100644 --- a/examples/showcase.rs +++ b/examples/showcase.rs @@ -1,8 +1,84 @@ -//! Example: Reference Showcase -//! --------------------------- -//! -//! This example provides a cute interface for browsing the reference examples. +#![allow( + unused, + dead_code, + 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).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), +} + +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 = |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 antipatterns; mod basics; @@ -22,9 +98,111 @@ mod reference { mod spreadpattern; mod statemanagement; mod suspense; + mod task; mod testing; 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() {} diff --git a/examples/ssr.rs b/examples/ssr.rs index 269ad35f6..b2f87770c 100644 --- a/examples/ssr.rs +++ b/examples/ssr.rs @@ -4,7 +4,7 @@ use dioxus::ssr; fn main() { let mut vdom = VirtualDom::new(App); vdom.rebuild_in_place(); - println!("{}", ssr::render_root(&vdom)); + println!("{}", ssr::render_vdom(&vdom)); } const App: FC<()> = |cx| { diff --git a/examples/tailwind.rs b/examples/tailwind.rs index 1c71caa7f..31f4b04e1 100644 --- a/examples/tailwind.rs +++ b/examples/tailwind.rs @@ -1,7 +1,7 @@ -use dioxus::desktop::wry::application::platform::macos::*; use dioxus::prelude::*; fn main() { + use dioxus::desktop::wry::application::platform::macos::*; dioxus::desktop::launch(App, |c| { c.with_fullsize_content_view(true) .with_titlebar_buttons_hidden(false) diff --git a/packages/core/src/arena.rs b/packages/core/src/arena.rs index 7f3b47f3e..f97f45717 100644 --- a/packages/core/src/arena.rs +++ b/packages/core/src/arena.rs @@ -22,18 +22,16 @@ impl SharedArena { /// THIS METHOD IS CURRENTLY UNSAFE /// 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 scope = inner.get(idx); - scope.ok_or_else(|| Error::FatalInternal("Scope not found")) + inner.get(idx) } /// THIS METHOD IS CURRENTLY UNSAFE /// 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 scope = inner.get_mut(idx); - scope.ok_or_else(|| Error::FatalInternal("Scope not found")) + inner.get_mut(idx) } fn inner(&self) -> &ScopeMap { diff --git a/packages/core/src/context.rs b/packages/core/src/context.rs index 27c735e7a..dd85815db 100644 --- a/packages/core/src/context.rs +++ b/packages/core/src/context.rs @@ -278,8 +278,8 @@ Any function prefixed with "use" should not be called conditionally. Some(parent_id) => { let parent = inner .arena_link - .try_get(parent_id) - .map_err(|_| Error::FatalInternal("Failed to find parent"))?; + .get(parent_id) + .ok_or_else(|| Error::FatalInternal("Failed to find parent"))?; scope = Some(parent); } diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index bc037f4f7..7247ef5a4 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -162,7 +162,7 @@ where new.ass_scope.set(scope_id); // 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(); @@ -406,7 +406,7 @@ where .components .with(|components| { 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; Scope::new( caller, @@ -495,7 +495,7 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> { 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 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() { // Add this node to be explored 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 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 *node = scope.root(); diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index f160e450c..8b4dc7f33 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -10,8 +10,9 @@ //! pub use crate::innerlude::{ - format_args_f, html, rsx, DioxusElement, DomEdit, EventTrigger, LazyNodes, NodeFactory, - Properties, RealDom, RealDomNode, ScopeId, VNode, VNodeKind, VirtualDom, VirtualEvent, FC, + format_args_f, html, rsx, Context, DioxusElement, DomEdit, EventTrigger, LazyNodes, + NodeFactory, Properties, RealDom, RealDomNode, ScopeId, VNode, VNodeKind, VirtualDom, + VirtualEvent, FC, }; pub mod prelude { diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 0fa6fd91e..29f82421e 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -224,7 +224,7 @@ impl VirtualDom { &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()?; let meta = diff_machine.create(cur_component.next_frame()); @@ -312,7 +312,7 @@ impl VirtualDom { // Suspense Events! A component's suspended node is updated 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 let hook = unsafe { scope.hooks.get_mut::(*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 _ => { self.components - .try_get_mut(trigger.originator)? - .call_listener(trigger)?; + .get_mut(trigger.originator) + .map(|f| f.call_listener(trigger)); // Now, there are events in the queue let mut updates = self.event_queue.queue.as_ref().borrow_mut(); @@ -366,7 +366,7 @@ impl VirtualDom { // Start a new mutable borrow to components // 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()?; @@ -380,7 +380,7 @@ impl VirtualDom { } pub fn base_scope(&self) -> &Scope { - self.components.try_get(self.base_scope).unwrap() + self.components.get(self.base_scope).unwrap() } } diff --git a/packages/html/src/lib.rs b/packages/html/src/lib.rs index 9126bed4e..22709da86 100644 --- a/packages/html/src/lib.rs +++ b/packages/html/src/lib.rs @@ -423,7 +423,7 @@ pub trait GlobalAttributes { list_style_position: "list-style-position", /// 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. margin: "margin", @@ -1016,6 +1016,8 @@ builder_constructors! { r#type: Mime, // ping: SpacedList, // rel: SpacedList, + ping: SpacedList, + rel: SpacedList, }; /// Build a @@ -1274,6 +1276,14 @@ builder_constructors! { src: Uri, srcdoc: Uri, width: usize, + + marginWidth: String, + align: String, + longdesc: String, + + scrolling: String, + marginHeight: String, + frameBorder: String, // sandbox: SpacedSet, }; @@ -1850,7 +1860,7 @@ pub trait SvgAttributes { to: "to", transform: "transform", transform_origin: "transform-origin", - _type: "_type", + r#type: "_type", u1: "u1", u2: "u2", underline_position: "underline-position", diff --git a/packages/ssr/examples/tide.rs b/packages/ssr/examples/tide.rs index 37a494b59..b6c96f8c3 100644 --- a/packages/ssr/examples/tide.rs +++ b/packages/ssr/examples/tide.rs @@ -22,7 +22,7 @@ async fn main() -> Result<(), std::io::Error> { let dom = VirtualDom::launch_with_props_in_place(Example, ExampleProps { initial_name }); Ok(Response::builder(200) - .body(format!("{}", dioxus_ssr::render_root(&dom))) + .body(format!("{}", dioxus_ssr::render_vdom(&dom))) .content_type(tide::http::mime::HTML) .build()) }); @@ -38,7 +38,7 @@ struct ExampleProps { initial_name: String, } -pub static Example: FC = |cx| { +static Example: FC = |cx| { let dispaly_name = use_state(cx, move || cx.initial_name.clone()); cx.render(rsx! { diff --git a/packages/ssr/examples/tofile.rs b/packages/ssr/examples/tofile.rs index 963f620dd..eded9265c 100644 --- a/packages/ssr/examples/tofile.rs +++ b/packages/ssr/examples/tofile.rs @@ -15,7 +15,7 @@ fn main() { let mut dom = VirtualDom::new(App); 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(); } diff --git a/packages/ssr/src/lib.rs b/packages/ssr/src/lib.rs index 47c949016..68826ae54 100644 --- a/packages/ssr/src/lib.rs +++ b/packages/ssr/src/lib.rs @@ -10,8 +10,21 @@ use std::fmt::{Display, Formatter}; use dioxus_core::*; -pub fn render_root(vdom: &VirtualDom) -> String { - format!("{:}", TextRenderer::new(vdom)) +pub fn render_vnode(vnode: &VNode, string: &mut String) {} + +pub fn render_vdom(vdom: &VirtualDom) -> String { + format!("{:}", TextRenderer::from_vdom(vdom)) +} + +pub fn render_vdom_scope(vdom: &VirtualDom, scope: ScopeId) -> Option { + Some(format!( + "{:}", + TextRenderer { + cfg: SsrConfig::default(), + root: vdom.components.get(scope).unwrap().root(), + vdom: Some(vdom) + } + )) } pub struct SsrConfig { @@ -56,14 +69,22 @@ impl Default for SsrConfig { /// assert_eq!(output, "
hello world
"); /// ``` pub struct TextRenderer<'a> { - vdom: &'a VirtualDom, + vdom: Option<&'a VirtualDom>, + root: &'a VNode<'a>, 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> { - pub fn new(vdom: &'a VirtualDom) -> Self { + pub fn from_vdom(vdom: &'a VirtualDom) -> Self { Self { - vdom, + root: vdom.base_scope().root(), + vdom: Some(vdom), cfg: SsrConfig::default(), } } @@ -141,10 +162,10 @@ impl<'a> TextRenderer<'a> { } VNodeKind::Component(vcomp) => { let idx = vcomp.ass_scope.get().unwrap(); - - let new_node = self.vdom.components.try_get(idx).unwrap().root(); - - self.html_render(new_node, f, il + 1)?; + if let Some(vdom) = self.vdom { + let new_node = vdom.components.get(idx).unwrap().root(); + self.html_render(new_node, f, il + 1)?; + } } VNodeKind::Suspended { .. } => { // 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)] mod tests { use super::*; @@ -216,21 +229,21 @@ mod tests { fn to_string_works() { let mut dom = VirtualDom::new(SIMPLE_APP); dom.rebuild_in_place().expect("failed to run virtualdom"); - dbg!(render_root(&dom)); + dbg!(render_vdom(&dom)); } #[test] fn nested() { let mut dom = VirtualDom::new(NESTED_APP); dom.rebuild_in_place().expect("failed to run virtualdom"); - dbg!(render_root(&dom)); + dbg!(render_vdom(&dom)); } #[test] fn fragment_app() { let mut dom = VirtualDom::new(FRAGMENT_APP); dom.rebuild_in_place().expect("failed to run virtualdom"); - dbg!(render_root(&dom)); + dbg!(render_vdom(&dom)); } #[test] @@ -243,7 +256,7 @@ mod tests { let mut dom = VirtualDom::new(SLIGHTLY_MORE_COMPLEX); 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(); } @@ -263,6 +276,6 @@ mod tests { let mut dom = VirtualDom::new(STLYE_APP); dom.rebuild_in_place().expect("failed to run virtualdom"); - dbg!(render_root(&dom)); + dbg!(render_vdom(&dom)); } } diff --git a/packages/web/examples/async_web.rs b/packages/web/examples/async_web.rs index a93dd3535..ebafe8c3f 100644 --- a/packages/web/examples/async_web.rs +++ b/packages/web/examples/async_web.rs @@ -13,15 +13,12 @@ use std::{pin::Pin, time::Duration}; use dioxus::prelude::*; -use dioxus_web::*; - fn main() { // Setup logging - wasm_logger::init(wasm_logger::Config::new(log::Level::Debug)); console_error_panic_hook::set_once(); + wasm_logger::init(wasm_logger::Config::new(log::Level::Debug)); - // Run the app - wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App)); + dioxus_web::launch(App, |c| c) } #[derive(serde::Deserialize)] @@ -38,17 +35,12 @@ static App: FC<()> = |cx| { || surf::get(ENDPOINT).recv_json::(), |cx, res| match res { 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! { - div { - style: { - align_items: "center" - } + div { style: { align_items: "center" } section { class: "py-12 px-4 text-center" div { class: "w-full max-w-2xl mx-auto" span { class: "text-sm font-semibold" diff --git a/packages/web/src/cache.rs b/packages/web/src/cache.rs new file mode 100644 index 000000000..e5685b2f6 --- /dev/null +++ b/packages/web/src/cache.rs @@ -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", + "", + "onpageshow", + "onpaste", + "onpause", + "onplay", + "onplaying", + "", + "onprogress", + "onratechange", + "onreset", + "onresize", + "onscroll", + "onsearch", + "onseeked", + "onseeking", + "onselect", + "onstalled", + "", + "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); + } +} diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs index c6a1f3f1f..d517b4744 100644 --- a/packages/web/src/lib.rs +++ b/packages/web/src/lib.rs @@ -3,449 +3,112 @@ //! This crate implements a renderer of the Dioxus Virtual DOM for the web browser using Websys. 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; pub use dioxus_core as dioxus; 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; -// pub mod interpreter; -pub mod new; +mod cache; +mod new; -/// The `WebsysRenderer` provides a way of rendering a Dioxus Virtual DOM to the browser's DOM. -/// Under the hood, we leverage WebSys and interact directly with the DOM +/// Launches the VirtualDOM from the specified component function. /// -pub struct WebsysRenderer { - internal_dom: VirtualDom, +/// This method will block the thread with `spawn_local` +pub fn launch(root: FC<()>, config: F) +where + F: FnOnce(()), +{ + wasm_bindgen_futures::spawn_local(run(root)) } -impl WebsysRenderer { - /// 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. - /// - /// ```ignore - /// fn main() { - /// 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 :("); - } +pub fn launch_with_props(root: FC, root_props: T, config: F) +where + T: Properties + 'static, + F: FnOnce(()), +{ + wasm_bindgen_futures::spawn_local(run_with_props(root, root_props)) +} - /// Create a new instance of the Dioxus Virtual Dom with no properties for the root component. - /// - /// 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. - pub fn new(root: FC<()>) -> Self { - Self::new_with_props(root, ()) - } +/// 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. +/// +/// ```ignore +/// fn main() { +/// 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. - /// Automatically progresses the creation of the VNode tree to completion. - /// - /// A VDom is automatically created. If you want more granular control of the VDom, use `from_vdom` - pub fn new_with_props(root: FC, root_props: T) -> Self { - Self::from_vdom(VirtualDom::new_with_props(root, root_props)) - } +pub async fn run_with_props(root: FC, root_props: T) { + let dom = VirtualDom::new_with_props(root, root_props); + event_loop(dom).await.expect("Event loop failed"); +} - /// Create a new text renderer from an existing Virtual DOM. - pub fn from_vdom(dom: VirtualDom) -> Self { - Self { internal_dom: dom } - } +pub async fn event_loop(mut internal_dom: VirtualDom) -> dioxus_core::error::Result<()> { + use wasm_bindgen::JsCast; - pub async fn run(&mut self) -> dioxus_core::error::Result<()> { - use wasm_bindgen::JsCast; + let root = prepare_websys_dom(); + let root_node = root.clone().dyn_into::().unwrap(); - let root = prepare_websys_dom(); - let root_node = root.clone().dyn_into::().unwrap(); + let mut websys_dom = crate::new::WebsysDom::new(root.clone()); - 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()); - websys_dom.stack.push(root_node); + let mut edits = Vec::new(); + internal_dom.rebuild(&mut websys_dom, &mut edits)?; + websys_dom.process_edits(&mut edits); - let mut edits = Vec::new(); - self.internal_dom.rebuild(&mut websys_dom, &mut edits)?; - websys_dom.process_edits(&mut edits); + log::info!("Going into event loop"); + loop { + 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"); - loop { - 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); + pin_mut!(task_queue); - pin_mut!(real_queue); - pin_mut!(task_queue); - - match futures_util::future::select(real_queue, task_queue).await { - 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 - Ok(()) + if let Some(real_trigger) = trigger { + 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 { - // Initialize the container on the dom - // Hook up the body as the root component to render tinto - let window = web_sys::window().expect("should have access to the Window"); - let document = window + web_sys::window() + .expect("should have access to the Window") .document() - .expect("should have access to the Document"); - - // let body = document.body().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", - "", - "onpageshow", - "onpaste", - "onpause", - "onplay", - "onplaying", - "", - "onprogress", - "onratechange", - "onreset", - "onresize", - "onscroll", - "onsearch", - "onseeked", - "onseeking", - "onselect", - "onstalled", - "", - "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); - } + .expect("should have access to the Document") + .get_element_by_id("dioxusroot") + .unwrap() } diff --git a/packages/web/src/percypatch.rs b/packages/web/src/percypatch.rs deleted file mode 100644 index 9f7b1ae6d..000000000 --- a/packages/web/src/percypatch.rs +++ /dev/null @@ -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>(root_node: N, patches: &Vec) -> Result<(), JsValue> { - // pub fn patch>(root_node: N, patches: &Vec) -> Result { - 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, - element_nodes_to_patch: &mut HashMap, - text_nodes_to_patch: &mut HashMap, -) { - 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>; - -// fn apply_element_patch(node: &Element, patch: &Patch) -> Result { -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 - // -> `` - // 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 `` - // 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 { - /// 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>, -} - -/// Box>> is our js_sys::Closure. Stored this way to allow us to store -/// any Closure regardless of the arguments. -pub type DynClosure = Rc>; - -impl CreatedNode { - pub fn without_closures>(node: N) -> Self { - CreatedNode { - node: node.into(), - closures: HashMap::with_capacity(0), - } - } -} - -impl std::ops::Deref for CreatedNode { - type Target = T; - fn deref(&self) -> &Self::Target { - &self.node - } -} - -impl From> for CreatedNode { - fn from(other: CreatedNode) -> CreatedNode { - CreatedNode { - node: other.node.into(), - closures: other.closures, - } - } -} -fn create_dom_node(node: &VNode<'_>) -> CreatedNode { - 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 { - 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, dom: &VirtualDom) {} -// fn render_diffs() {} diff --git a/src/lib.rs b/src/lib.rs index 5165d0f0b..ee3c5f041 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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 //! 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 @@ -67,7 +67,7 @@ //! //! //! 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. //! //! 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. //! //! ``` -//! pub static Example: FC<()> = |cx| { +//! pub pub static Example: FC<()> = |cx| { //! let (val, set_val) = use_state(cx, || 0); //! cx.render(rsx!( //! button { onclick: move |_| set_val(val + 1) } @@ -156,7 +156,7 @@ //! diouxs::web::launch(Example); //! } //! -//! pub static Example: FC<()> = |cx| { +//! pub pub static Example: FC<()> = |cx| { //! cx.render(rsx! { //! div { "Hello World!" } //! })