Merge pull request #67 from DioxusLabs/jk/router2dotoh

Fix/Feat: Memoize VComponents from their ptrs. Add new router!
This commit is contained in:
Jonathan Kelley authored and GitHub committed 2022-01-05 16:35:02 -05:00
commit 8994467189
33 files changed
+614 -320

No files matched your search

+2 -2
View File
@@ -20,7 +20,7 @@ dioxus-web = { path = "./packages/web", version = "^0.0.3", optional = true }
dioxus-desktop = { path = "./packages/desktop", version = "^0.1.3", optional = true } dioxus-desktop = { path = "./packages/desktop", version = "^0.1.3", optional = true }
dioxus-ssr = { path = "./packages/ssr", version = "0.1.1", optional = true } dioxus-ssr = { path = "./packages/ssr", version = "0.1.1", optional = true }
# dioxus-router = { path = "./packages/router", optional = true } dioxus-router = { path = "./packages/router", optional = true }
dioxus-mobile = { path = "./packages/mobile", version = "^0.0.2", optional = true } dioxus-mobile = { path = "./packages/mobile", version = "^0.0.2", optional = true }
# dioxus-liveview = { path = "./packages/liveview", optional = true } # dioxus-liveview = { path = "./packages/liveview", optional = true }
@@ -33,7 +33,7 @@ html = ["dioxus-html"]
ssr = ["dioxus-ssr"] ssr = ["dioxus-ssr"]
web = ["dioxus-web"] web = ["dioxus-web"]
desktop = ["dioxus-desktop"] desktop = ["dioxus-desktop"]
# router = ["dioxus-router"] router = ["dioxus-router"]
# "dioxus-router/web" # "dioxus-router/web"
# "dioxus-router/desktop" # "dioxus-router/desktop"
+1 -1
View File
@@ -27,7 +27,7 @@ Head over to [https://rust-lang.org](http://rust-lang.org) and install the Rust
Once installed, make sure to install wasm32-unknown-unknown as a target if you're planning on deploying your app to the web. Once installed, make sure to install wasm32-unknown-unknown as a target if you're planning on deploying your app to the web.
``` ```
rustup target add wasm32-unknown-uknown rustup target add wasm32-unknown-unknown
``` ```
### Dioxus-CLI for dev server, bundling, etc. ### Dioxus-CLI for dev server, bundling, etc.
+1 -1
View File
@@ -19,7 +19,7 @@ $ cargo install trunk
Make sure the `wasm32-unknown-unknown` target is installed: Make sure the `wasm32-unknown-unknown` target is installed:
```shell ```shell
$ rustup target add wasm32-unknown-uknown $ rustup target add wasm32-unknown-unknown
``` ```
Create a new project: Create a new project:
+2 -1
View File
@@ -1,7 +1,8 @@
use dioxus::prelude::*; use dioxus::prelude::*;
use dioxus_desktop::tao::menu::MenuBar;
fn main() { fn main() {
dioxus::desktop::launch(app); dioxus::desktop::launch_cfg(app, |c| c.with_window(|w| w.with_menu(MenuBar::default())));
} }
fn app(cx: Scope) -> Element { fn app(cx: Scope) -> Element {
+25 -10
View File
@@ -19,7 +19,7 @@ use quote::{quote, ToTokens, TokenStreamExt};
use syn::{ use syn::{
ext::IdentExt, ext::IdentExt,
parse::{Parse, ParseBuffer, ParseStream}, parse::{Parse, ParseBuffer, ParseStream},
token, Expr, Ident, Result, Token, token, Expr, Ident, LitStr, Result, Token,
}; };
pub struct Component { pub struct Component {
@@ -135,8 +135,6 @@ impl ToTokens for Component {
None => quote! { None }, None => quote! { None },
}; };
// #children
tokens.append_all(quote! { tokens.append_all(quote! {
__cx.component( __cx.component(
#name, #name,
@@ -155,7 +153,7 @@ pub struct ComponentField {
enum ContentField { enum ContentField {
ManExpr(Expr), ManExpr(Expr),
Formatted(LitStr),
OnHandlerRaw(Expr), OnHandlerRaw(Expr),
} }
@@ -163,6 +161,9 @@ impl ToTokens for ContentField {
fn to_tokens(&self, tokens: &mut TokenStream2) { fn to_tokens(&self, tokens: &mut TokenStream2) {
match self { match self {
ContentField::ManExpr(e) => e.to_tokens(tokens), ContentField::ManExpr(e) => e.to_tokens(tokens),
ContentField::Formatted(s) => tokens.append_all(quote! {
__cx.raw_text(format_args_f!(#s)).0
}),
ContentField::OnHandlerRaw(e) => tokens.append_all(quote! { ContentField::OnHandlerRaw(e) => tokens.append_all(quote! {
__cx.bump().alloc(#e) __cx.bump().alloc(#e)
}), }),
@@ -175,13 +176,27 @@ impl Parse for ComponentField {
let name = Ident::parse_any(input)?; let name = Ident::parse_any(input)?;
input.parse::<Token![:]>()?; input.parse::<Token![:]>()?;
let name_str = name.to_string(); if name.to_string().starts_with("on") {
let content = if name_str.starts_with("on") { let content = ContentField::OnHandlerRaw(input.parse()?);
ContentField::OnHandlerRaw(input.parse()?) return Ok(Self { name, content });
} else { }
ContentField::ManExpr(input.parse::<Expr>()?)
};
if name.to_string() == "key" {
let content = ContentField::ManExpr(input.parse()?);
return Ok(Self { name, content });
}
if input.peek(LitStr) && input.peek2(Token![,]) {
let content = ContentField::Formatted(input.parse()?);
return Ok(Self { name, content });
}
if input.peek(LitStr) && input.peek2(LitStr) {
let item = input.parse::<LitStr>().unwrap();
proc_macro_error::emit_error!(item, "This attribute is misisng a trailing comma")
}
let content = ContentField::ManExpr(input.parse()?);
Ok(Self { name, content }) Ok(Self { name, content })
} }
} }
+1 -1
View File
@@ -46,7 +46,7 @@ impl Parse for Element {
content.parse::<Token![:]>()?; content.parse::<Token![:]>()?;
if content.peek(LitStr) { if content.peek(LitStr) && content.peek2(Token![,]) {
let value = content.parse::<LitStr>()?; let value = content.parse::<LitStr>()?;
attributes.push(ElementAttrNamed { attributes.push(ElementAttrNamed {
el_name: el_name.clone(), el_name: el_name.clone(),
+38 -29
View File
@@ -263,7 +263,7 @@ impl<'bump> DiffState<'bump> {
}; };
if deadline_expired() { if deadline_expired() {
log::debug!("Deadline expired before we could finish!"); log::trace!("Deadline expired before we could finish!");
return false; return false;
} }
} }
@@ -390,10 +390,7 @@ impl<'bump> DiffState<'bump> {
self.stack.add_child_count(1); self.stack.add_child_count(1);
if let Some(cur_scope_id) = self.stack.current_scope() { if let Some(cur_scope_id) = self.stack.current_scope() {
let scope = self.scopes.get_scope(cur_scope_id).unwrap();
for listener in *listeners { for listener in *listeners {
self.attach_listener_to_scope(listener, scope);
listener.mounted_node.set(Some(real_id)); listener.mounted_node.set(Some(real_id));
self.mutations.new_event_listener(listener, cur_scope_id); self.mutations.new_event_listener(listener, cur_scope_id);
} }
@@ -426,16 +423,25 @@ impl<'bump> DiffState<'bump> {
fn create_component_node(&mut self, vcomponent: &'bump VComponent<'bump>) { fn create_component_node(&mut self, vcomponent: &'bump VComponent<'bump>) {
let parent_idx = self.stack.current_scope().unwrap(); let parent_idx = self.stack.current_scope().unwrap();
// Insert a new scope into our component list // the component might already exist - if it does, we need to reuse it
let props: Box<dyn AnyProps + 'bump> = vcomponent.props.borrow_mut().take().unwrap(); // this makes figure out when to drop the component more complicated
let props: Box<dyn AnyProps + 'static> = unsafe { std::mem::transmute(props) }; let new_idx = if let Some(idx) = vcomponent.scope.get() {
let new_idx = self.scopes.new_with_key( assert!(self.scopes.get_scope(idx).is_some());
vcomponent.user_fc, idx
props, } else {
Some(parent_idx), // Insert a new scope into our component list
self.stack.element_stack.last().copied().unwrap(), let props: Box<dyn AnyProps + 'bump> = vcomponent.props.borrow_mut().take().unwrap();
0, let props: Box<dyn AnyProps + 'static> = unsafe { std::mem::transmute(props) };
); let new_idx = self.scopes.new_with_key(
vcomponent.user_fc,
props,
Some(parent_idx),
self.stack.element_stack.last().copied().unwrap(),
0,
);
new_idx
};
// Actually initialize the caller's slot with the right address // Actually initialize the caller's slot with the right address
vcomponent.scope.set(Some(new_idx)); vcomponent.scope.set(Some(new_idx));
@@ -446,9 +452,6 @@ impl<'bump> DiffState<'bump> {
} }
false => { false => {
// track this component internally so we know the right drop order // track this component internally so we know the right drop order
let cur_scope = self.scopes.get_scope(parent_idx).unwrap();
let extended = unsafe { std::mem::transmute(vcomponent) };
cur_scope.items.borrow_mut().borrowed_props.push(extended);
} }
} }
@@ -580,8 +583,6 @@ impl<'bump> DiffState<'bump> {
// //
// TODO: take a more efficient path than this // TODO: take a more efficient path than this
if let Some(cur_scope_id) = self.stack.current_scope() { if let Some(cur_scope_id) = self.stack.current_scope() {
let scope = self.scopes.get_scope(cur_scope_id).unwrap();
if old.listeners.len() == new.listeners.len() { if old.listeners.len() == new.listeners.len() {
for (old_l, new_l) in old.listeners.iter().zip(new.listeners.iter()) { for (old_l, new_l) in old.listeners.iter().zip(new.listeners.iter()) {
if old_l.event != new_l.event { if old_l.event != new_l.event {
@@ -590,7 +591,6 @@ impl<'bump> DiffState<'bump> {
self.mutations.new_event_listener(new_l, cur_scope_id); self.mutations.new_event_listener(new_l, cur_scope_id);
} }
new_l.mounted_node.set(old_l.mounted_node.get()); new_l.mounted_node.set(old_l.mounted_node.get());
self.attach_listener_to_scope(new_l, scope);
} }
} else { } else {
for listener in old.listeners { for listener in old.listeners {
@@ -600,7 +600,6 @@ impl<'bump> DiffState<'bump> {
for listener in new.listeners { for listener in new.listeners {
listener.mounted_node.set(Some(root)); listener.mounted_node.set(Some(root));
self.mutations.new_event_listener(listener, cur_scope_id); self.mutations.new_event_listener(listener, cur_scope_id);
self.attach_listener_to_scope(listener, scope);
} }
} }
} }
@@ -687,6 +686,12 @@ impl<'bump> DiffState<'bump> {
new: &'bump VComponent<'bump>, new: &'bump VComponent<'bump>,
) { ) {
let scope_addr = old.scope.get().unwrap(); let scope_addr = old.scope.get().unwrap();
log::trace!("diff_component_nodes: {:?}", scope_addr);
if std::ptr::eq(old, new) {
log::trace!("skipping component diff - component is the sames");
return;
}
// Make sure we're dealing with the same component (by function pointer) // Make sure we're dealing with the same component (by function pointer)
if old.user_fc == new.user_fc { if old.user_fc == new.user_fc {
@@ -728,11 +733,14 @@ impl<'bump> DiffState<'bump> {
// this should auto drop the previous props // this should auto drop the previous props
self.scopes.run_scope(scope_addr); self.scopes.run_scope(scope_addr);
self.mutations.dirty_scopes.insert(scope_addr);
self.diff_node( self.diff_node(
self.scopes.wip_head(scope_addr), self.scopes.wip_head(scope_addr),
self.scopes.fin_head(scope_addr), self.scopes.fin_head(scope_addr),
); );
} else { } else {
log::trace!("memoized");
// memoization has taken place // memoization has taken place
drop(new_props); drop(new_props);
}; };
@@ -1237,7 +1245,10 @@ impl<'bump> DiffState<'bump> {
let scope_id = c.scope.get().unwrap(); let scope_id = c.scope.get().unwrap();
self.scopes.try_remove(scope_id).unwrap(); // we can only remove components if they are actively being diffed
if self.stack.scope_stack.contains(&c.originator) {
self.scopes.try_remove(scope_id).unwrap();
}
} }
} }
} }
@@ -1288,15 +1299,13 @@ impl<'bump> DiffState<'bump> {
let scope_id = c.scope.get().unwrap(); let scope_id = c.scope.get().unwrap();
let root = self.scopes.root_node(scope_id); let root = self.scopes.root_node(scope_id);
self.remove_nodes(Some(root), gen_muts); self.remove_nodes(Some(root), gen_muts);
self.scopes.try_remove(scope_id).unwrap();
// we can only remove this node if the originator is actively
if self.stack.scope_stack.contains(&c.originator) {
self.scopes.try_remove(scope_id).unwrap();
}
} }
} }
} }
} }
/// Adds a listener closure to a scope during diff.
fn attach_listener_to_scope(&mut self, listener: &'bump Listener<'bump>, scope: &ScopeState) {
let long_listener = unsafe { std::mem::transmute(listener) };
scope.items.borrow_mut().listeners.push(long_listener)
}
} }
+66 -49
View File
@@ -60,10 +60,7 @@ impl<'a, 'b> LazyNodes<'a, 'b> {
} }
} }
pub fn new<F>(_val: F) -> Self pub fn new(_val: impl FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b) -> Self {
where
F: FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b,
{
// there's no way to call FnOnce without a box, so we need to store it in a slot and use static dispatch // there's no way to call FnOnce without a box, so we need to store it in a slot and use static dispatch
let mut slot = Some(_val); let mut slot = Some(_val);
@@ -240,57 +237,77 @@ fn round_to_words(len: usize) -> usize {
(len + mem::size_of::<usize>() - 1) / mem::size_of::<usize>() (len + mem::size_of::<usize>() - 1) / mem::size_of::<usize>()
} }
#[test] #[cfg(test)]
fn it_works() { mod tests {
let bump = bumpalo::Bump::new(); use super::*;
use crate::innerlude::{Element, Scope, VirtualDom};
let factory = NodeFactory { bump: &bump }; #[test]
fn it_works() {
let caller = LazyNodes::new_some(|f| { fn app(cx: Scope<()>) -> Element {
// cx.render(LazyNodes::new_some(|f| {
f.text(format_args!("hello world!")) f.text(format_args!("hello world!"))
}); }))
let g = caller.call(factory);
dbg!(g);
}
#[test]
fn it_drops() {
use std::rc::Rc;
let bump = bumpalo::Bump::new();
let factory = NodeFactory { bump: &bump };
struct DropInner {
id: i32,
}
impl Drop for DropInner {
fn drop(&mut self) {
log::debug!("dropping inner");
} }
let mut dom = VirtualDom::new(app);
dom.rebuild();
let g = dom.base_scope().root_node();
dbg!(g);
} }
let val = Rc::new(10);
let caller = { #[test]
let it = (0..10) fn it_drops() {
.map(|i| { use std::rc::Rc;
let val = val.clone();
LazyNodes::new_some(move |f| { struct AppProps {
log::debug!("hell closure"); inner: Rc<i32>,
let inner = DropInner { id: i }; }
f.text(format_args!("hello world {:?}, {:?}", inner.id, val))
fn app(cx: Scope<AppProps>) -> Element {
struct DropInner {
id: i32,
}
impl Drop for DropInner {
fn drop(&mut self) {
log::debug!("dropping inner");
}
}
let caller = {
let it = (0..10)
.map(|i| {
let val = cx.props.inner.clone();
LazyNodes::new_some(move |f| {
log::debug!("hell closure");
let inner = DropInner { id: i };
f.text(format_args!("hello world {:?}, {:?}", inner.id, val))
})
})
.collect::<Vec<_>>();
LazyNodes::new_some(|f| {
log::debug!("main closure");
f.fragment_from_iter(it)
}) })
}) };
.collect::<Vec<_>>();
LazyNodes::new_some(|f| { cx.render(caller)
log::debug!("main closure"); }
f.fragment_from_iter(it)
})
};
let _ = caller.call(factory); let inner = Rc::new(0);
assert_eq!(Rc::strong_count(&val), 1); let mut dom = VirtualDom::new_with_props(
app,
AppProps {
inner: inner.clone(),
},
);
dom.rebuild();
drop(dom);
assert_eq!(Rc::strong_count(&inner), 1);
}
} }
+28 -10
View File
@@ -350,6 +350,7 @@ impl Clone for EventHandler<'_> {
/// Only supports the functional syntax /// Only supports the functional syntax
pub struct VComponent<'src> { pub struct VComponent<'src> {
pub key: Option<&'src str>, pub key: Option<&'src str>,
pub originator: ScopeId,
pub scope: Cell<Option<ScopeId>>, pub scope: Cell<Option<ScopeId>>,
pub can_memoize: bool, pub can_memoize: bool,
pub user_fc: *const (), pub user_fc: *const (),
@@ -395,12 +396,16 @@ impl<P> AnyProps for VComponentProps<P> {
/// This struct adds metadata to the final VNode about listeners, attributes, and children /// This struct adds metadata to the final VNode about listeners, attributes, and children
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub struct NodeFactory<'a> { pub struct NodeFactory<'a> {
pub(crate) scope: &'a ScopeState,
pub(crate) bump: &'a Bump, pub(crate) bump: &'a Bump,
} }
impl<'a> NodeFactory<'a> { impl<'a> NodeFactory<'a> {
pub fn new(bump: &'a Bump) -> NodeFactory<'a> { pub fn new(scope: &'a ScopeState) -> NodeFactory<'a> {
NodeFactory { bump } NodeFactory {
scope,
bump: &scope.wip_frame().bump,
}
} }
#[inline] #[inline]
@@ -473,6 +478,12 @@ impl<'a> NodeFactory<'a> {
) -> VNode<'a> { ) -> VNode<'a> {
let key = key.map(|f| self.raw_text(f).0); let key = key.map(|f| self.raw_text(f).0);
let mut items = self.scope.items.borrow_mut();
for listener in listeners {
let long_listener = unsafe { std::mem::transmute(listener) };
items.listeners.push(long_listener);
}
VNode::Element(self.bump.alloc(VElement { VNode::Element(self.bump.alloc(VElement {
tag: tag_name, tag: tag_name,
key, key,
@@ -511,11 +522,12 @@ impl<'a> NodeFactory<'a> {
where where
P: Properties + 'a, P: Properties + 'a,
{ {
VNode::Component(self.bump.alloc(VComponent { let vcomp = self.bump.alloc(VComponent {
key: key.map(|f| self.raw_text(f).0), key: key.map(|f| self.raw_text(f).0),
scope: Default::default(), scope: Default::default(),
can_memoize: P::IS_STATIC, can_memoize: P::IS_STATIC,
user_fc: component as *const (), user_fc: component as *const (),
originator: self.scope.scope_id(),
props: RefCell::new(Some(Box::new(VComponentProps { props: RefCell::new(Some(Box::new(VComponentProps {
// local_props: RefCell::new(Some(props)), // local_props: RefCell::new(Some(props)),
// heap_props: RefCell::new(None), // heap_props: RefCell::new(None),
@@ -528,7 +540,15 @@ impl<'a> NodeFactory<'a> {
// the transformation from this specific lifetime to the for<'a> lifetime // the transformation from this specific lifetime to the for<'a> lifetime
render_fn: unsafe { std::mem::transmute(component) }, render_fn: unsafe { std::mem::transmute(component) },
}))), }))),
})) });
if !P::IS_STATIC {
let vcomp = &*vcomp;
let vcomp = unsafe { std::mem::transmute(vcomp) };
self.scope.items.borrow_mut().borrowed_props.push(vcomp);
}
VNode::Component(vcomp)
} }
pub fn listener(self, event: &'static str, callback: EventHandler<'a>) -> Listener<'a> { pub fn listener(self, event: &'static str, callback: EventHandler<'a>) -> Listener<'a> {
@@ -730,17 +750,15 @@ impl IntoVNode<'_> for Arguments<'_> {
impl<'a> IntoVNode<'a> for &Option<VNode<'a>> { impl<'a> IntoVNode<'a> for &Option<VNode<'a>> {
fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> { fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> {
let r = self.as_ref().map(|f| f.decouple()); self.as_ref()
cx.fragment_from_iter(r) .map(|f| f.into_vnode(cx))
.unwrap_or_else(|| cx.fragment_from_iter(None as Option<VNode>))
} }
} }
impl<'a> IntoVNode<'a> for &VNode<'a> { impl<'a> IntoVNode<'a> for &VNode<'a> {
fn into_vnode(self, _cx: NodeFactory<'a>) -> VNode<'a> { fn into_vnode(self, _cx: NodeFactory<'a>) -> VNode<'a> {
// borrowed nodes are strange
self.decouple() self.decouple()
} }
} }
trait IntoAcceptedVnode<'a> {
fn into_accepted_vnode(self, cx: NodeFactory<'a>) -> VNode<'a>;
}
+15 -3
View File
@@ -143,6 +143,7 @@ impl ScopeArena {
// Removes a scope and its descendents from the arena // Removes a scope and its descendents from the arena
pub fn try_remove(&self, id: ScopeId) -> Option<()> { pub fn try_remove(&self, id: ScopeId) -> Option<()> {
log::trace!("removing scope {:?}", id);
self.ensure_drop_safety(id); self.ensure_drop_safety(id);
// Safety: // Safety:
@@ -188,6 +189,8 @@ impl ScopeArena {
/// This also makes sure that drop order is consistent and predictable. All resources that rely on being dropped will /// This also makes sure that drop order is consistent and predictable. All resources that rely on being dropped will
/// be dropped. /// be dropped.
pub(crate) fn ensure_drop_safety(&self, scope_id: ScopeId) { pub(crate) fn ensure_drop_safety(&self, scope_id: ScopeId) {
log::trace!("Ensuring drop safety for scope {:?}", scope_id);
if let Some(scope) = self.get_scope(scope_id) { if let Some(scope) = self.get_scope(scope_id) {
let mut items = scope.items.borrow_mut(); let mut items = scope.items.borrow_mut();
@@ -217,6 +220,7 @@ impl ScopeArena {
// Cycle to the next frame and then reset it // Cycle to the next frame and then reset it
// This breaks any latent references, invalidating every pointer referencing into it. // This breaks any latent references, invalidating every pointer referencing into it.
// Remove all the outdated listeners // Remove all the outdated listeners
log::trace!("Running scope {:?}", id);
self.ensure_drop_safety(id); self.ensure_drop_safety(id);
// todo: we *know* that this is aliased by the contents of the scope itself // todo: we *know* that this is aliased by the contents of the scope itself
@@ -278,14 +282,19 @@ impl ScopeArena {
let nodes = self.nodes.borrow(); let nodes = self.nodes.borrow();
let mut cur_el = Some(element); let mut cur_el = Some(element);
log::trace!("calling listener {:?}, {:?}", event, element);
let state = Rc::new(BubbleState::new()); let state = Rc::new(BubbleState::new());
while let Some(id) = cur_el.take() { while let Some(id) = cur_el.take() {
if let Some(el) = nodes.get(id.0) { if let Some(el) = nodes.get(id.0) {
log::trace!("Found valid receiver element");
let real_el = unsafe { &**el }; let real_el = unsafe { &**el };
if let VNode::Element(real_el) = real_el { if let VNode::Element(real_el) = real_el {
for listener in real_el.listeners.borrow().iter() { for listener in real_el.listeners.borrow().iter() {
if listener.event == event.name { if listener.event == event.name {
log::trace!("Found valid receiver event");
if state.canceled.get() { if state.canceled.get() {
// stop bubbling if canceled // stop bubbling if canceled
break; break;
@@ -651,12 +660,14 @@ impl ScopeState {
/// rsx!(cx, div { "hello {state.0}" }) /// rsx!(cx, div { "hello {state.0}" })
/// } /// }
/// ``` /// ```
pub fn provide_context<T: 'static>(&self, value: T) { pub fn provide_context<T: 'static>(&self, value: T) -> Rc<T> {
let value = Rc::new(value);
self.shared_contexts self.shared_contexts
.borrow_mut() .borrow_mut()
.insert(TypeId::of::<T>(), Rc::new(value)) .insert(TypeId::of::<T>(), value.clone())
.map(|f| f.downcast::<T>().ok()) .map(|f| f.downcast::<T>().ok())
.flatten(); .flatten();
value
} }
/// Try to retrieve a SharedState with type T from the any parent Scope. /// Try to retrieve a SharedState with type T from the any parent Scope.
@@ -713,6 +724,7 @@ impl ScopeState {
///``` ///```
pub fn render<'src>(&'src self, rsx: LazyNodes<'src, '_>) -> Option<VNode<'src>> { pub fn render<'src>(&'src self, rsx: LazyNodes<'src, '_>) -> Option<VNode<'src>> {
Some(rsx.call(NodeFactory { Some(rsx.call(NodeFactory {
scope: self,
bump: &self.wip_frame().bump, bump: &self.wip_frame().bump,
})) }))
} }
@@ -900,7 +912,7 @@ impl TaskQueue {
} else { } else {
// todo: it should be okay to remote a fut while the queue is being polled // todo: it should be okay to remote a fut while the queue is being polled
// However, it's not currently possible to do that. // However, it's not currently possible to do that.
log::debug!("Unable to remove task from task queue. This is probably a bug."); log::trace!("Unable to remove task from task queue. This is probably a bug.");
} }
} }
pub(crate) fn has_tasks(&self) -> bool { pub(crate) fn has_tasks(&self) -> bool {
+4 -1
View File
@@ -531,6 +531,9 @@ impl VirtualDom {
diff_state.stack.element_stack.push(ElementId(0)); diff_state.stack.element_stack.push(ElementId(0));
diff_state.stack.scope_stack.push(scope_id); diff_state.stack.scope_stack.push(scope_id);
diff_state.work(|| false); diff_state.work(|| false);
self.dirty_scopes.clear();
assert!(self.dirty_scopes.is_empty());
diff_state.mutations diff_state.mutations
} }
@@ -599,7 +602,7 @@ impl VirtualDom {
pub fn render_vnodes<'a>(&'a self, lazy_nodes: LazyNodes<'a, '_>) -> &'a VNode<'a> { pub fn render_vnodes<'a>(&'a self, lazy_nodes: LazyNodes<'a, '_>) -> &'a VNode<'a> {
let scope = self.scopes.get_scope(ScopeId(0)).unwrap(); let scope = self.scopes.get_scope(ScopeId(0)).unwrap();
let frame = scope.wip_frame(); let frame = scope.wip_frame();
let factory = NodeFactory { bump: &frame.bump }; let factory = NodeFactory::new(scope);
let node = lazy_nodes.call(factory); let node = lazy_nodes.call(factory);
frame.bump.alloc(node) frame.bump.alloc(node)
} }
+29 -1
View File
@@ -97,7 +97,7 @@ fn memo_works_properly() {
cx.render(rsx!( cx.render(rsx!(
div { "Hello, world! {name}" } div { "Hello, world! {name}" }
child(na: "asdfg".to_string()) child(na: "asdfg".to_string() )
)) ))
} }
@@ -285,3 +285,31 @@ fn basic() {
dom.handle_message(SchedulerMsg::Immediate(ScopeId(0))); dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
dom.work_with_deadline(|| false); dom.work_with_deadline(|| false);
} }
#[test]
fn leak_thru_children() {
fn app(cx: Scope) -> Element {
cx.render(rsx! {
Child {
name: "asd".to_string(),
}
});
cx.render(rsx! {
div {}
})
}
#[inline_props]
fn Child(cx: Scope, name: String) -> Element {
rsx!(cx, div { "child {name}" })
}
let mut dom = new_dom(app, ());
let _ = dom.rebuild();
dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
dom.work_with_deadline(|| false);
dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
dom.work_with_deadline(|| false);
}
+34 -35
View File
@@ -1,4 +1,3 @@
function serialize_event(event) { function serialize_event(event) {
switch (event.type) { switch (event.type) {
case "copy": case "copy":
@@ -10,8 +9,8 @@ function serialize_event(event) {
case "compositionstart": case "compositionstart":
case "compositionupdate": case "compositionupdate":
return { return {
data: event.data data: event.data,
} };
case "keydown": case "keydown":
case "keypress": case "keypress":
@@ -29,7 +28,7 @@ function serialize_event(event) {
repeat: event.repeat, repeat: event.repeat,
which: event.which, which: event.which,
// locale: event.locale, // locale: event.locale,
} };
case "focus": case "focus":
case "blur": case "blur":
@@ -45,7 +44,7 @@ function serialize_event(event) {
} }
return { return {
value: value value: value,
}; };
case "input": case "input":
@@ -60,7 +59,7 @@ function serialize_event(event) {
} }
return { return {
value: value value: value,
}; };
} }
@@ -95,7 +94,7 @@ function serialize_event(event) {
screen_x: event.screenX, screen_x: event.screenX,
screen_y: event.screenY, screen_y: event.screenY,
shift_key: event.shiftKey, shift_key: event.shiftKey,
} };
case "pointerdown": case "pointerdown":
case "pointermove": case "pointermove":
@@ -130,7 +129,7 @@ function serialize_event(event) {
twist: event.twist, twist: event.twist,
pointer_type: event.pointerType, pointer_type: event.pointerType,
is_primary: event.isPrimary, is_primary: event.isPrimary,
} };
case "select": case "select":
return {}; return {};
@@ -148,7 +147,7 @@ function serialize_event(event) {
// changed_touches: event.changedTouches, // changed_touches: event.changedTouches,
// target_touches: event.targetTouches, // target_touches: event.targetTouches,
// touches: event.touches, // touches: event.touches,
} };
case "scroll": case "scroll":
return {}; return {};
@@ -208,17 +207,14 @@ function serialize_event(event) {
default: default:
return {}; return {};
} }
} }
class Interpreter { class Interpreter {
constructor(root) { constructor(root) {
this.root = root; this.root = root;
this.stack = [root]; this.stack = [root];
this.listeners = { this.listeners = {
"onclick": {} onclick: {},
}; };
this.lastNodeWasText = false; this.lastNodeWasText = false;
this.nodes = [root]; this.nodes = [root];
@@ -304,35 +300,45 @@ class Interpreter {
this.nodes[edit.root] = el; this.nodes[edit.root] = el;
} }
RemoveEventListener(edit) { } RemoveEventListener(edit) {}
NewEventListener(edit) { NewEventListener(edit) {
const event_name = edit.event_name; const event_name = edit.event_name;
const mounted_node_id = edit.root; const mounted_node_id = edit.root;
const scope = edit.scope; const scope = edit.scope;
const element = this.nodes[edit.root] const element = this.nodes[edit.root];
element.setAttribute(`dioxus-event-${event_name}`, `${scope}.${mounted_node_id}`); element.setAttribute(
`dioxus-event-${event_name}`,
`${scope}.${mounted_node_id}`
);
if (this.listeners[event_name] === undefined) { if (this.listeners[event_name] === undefined) {
this.listeners[event_name] = true; this.listeners[event_name] = true;
this.root.addEventListener(event_name, (event) => { this.root.addEventListener(event_name, (event) => {
console.log("handling event", event);
const target = event.target; const target = event.target;
const real_id = target.getAttribute(`dioxus-id`); const real_id = target.getAttribute(`dioxus-id`);
const should_prevent_default = target.getAttribute(
`dioxus-prevent-default`
);
let contents = serialize_event(event);
if (should_prevent_default === `on${event.type}`) {
event.preventDefault();
}
if (real_id == null) { if (real_id == null) {
alert("no id");
return; return;
} }
rpc.call('user_event', { rpc.call("user_event", {
event: event_name, event: event_name,
mounted_dom_id: parseInt(real_id), mounted_dom_id: parseInt(real_id),
contents: serialize_event(event), contents: contents,
}); });
}); });
} }
} }
@@ -346,11 +352,11 @@ class Interpreter {
const name = edit.field; const name = edit.field;
const value = edit.value; const value = edit.value;
const ns = edit.ns; const ns = edit.ns;
const node = this.nodes[edit.root] const node = this.nodes[edit.root];
if (ns == "style") { if (ns == "style") {
node.style[name] = value; node.style[name] = value;
} else if ((ns != null) || (ns != undefined)) { } else if (ns != null || ns != undefined) {
node.setAttributeNS(ns, name, value); node.setAttributeNS(ns, name, value);
} else { } else {
switch (name) { switch (name) {
@@ -358,17 +364,15 @@ class Interpreter {
node.value = value; node.value = value;
break; break;
case "checked": case "checked":
// console.log("setting checked"); node.checked = value === "true";
node.checked = (value === "true");
break; break;
case "selected": case "selected":
node.selected = (value === "true"); node.selected = value === "true";
break; break;
case "dangerous_inner_html": case "dangerous_inner_html":
node.innerHTML = value; node.innerHTML = value;
break; break;
default: default:
// console.log("setting attr directly ", name, value);
node.setAttribute(name, value); node.setAttribute(name, value);
} }
} }
@@ -390,11 +394,6 @@ class Interpreter {
} }
handleEdits(edits) { handleEdits(edits) {
// console.log("handling raw edits", rawedits);
// let edits = JSON.parse(rawedits);
// console.log("handling edits", edits);
this.stack.push(this.root); this.stack.push(this.root);
for (let x = 0; x < edits.length; x++) { for (let x = 0; x < edits.length; x++) {
@@ -410,7 +409,7 @@ function main() {
window.interpreter = new Interpreter(root); window.interpreter = new Interpreter(root);
console.log(window.interpreter); console.log(window.interpreter);
rpc.call('initialize'); rpc.call("initialize");
} }
main() main();
+1 -31
View File
@@ -279,37 +279,7 @@ impl DesktopController {
cfg: &DesktopConfig, cfg: &DesktopConfig,
event_loop: &EventLoopWindowTarget<UserWindowEvent>, event_loop: &EventLoopWindowTarget<UserWindowEvent>,
) { ) {
let builder = cfg.window.clone().with_menu({ let builder = cfg.window.clone();
// create main menubar menu
let mut menu_bar_menu = MenuBar::new();
// create `first_menu`
let mut first_menu = MenuBar::new();
first_menu.add_native_item(MenuItem::About("App".to_string()));
first_menu.add_native_item(MenuItem::Services);
first_menu.add_native_item(MenuItem::Separator);
first_menu.add_native_item(MenuItem::Hide);
first_menu.add_native_item(MenuItem::HideOthers);
first_menu.add_native_item(MenuItem::ShowAll);
first_menu.add_native_item(MenuItem::Quit);
first_menu.add_native_item(MenuItem::CloseWindow);
// create second menu
let mut second_menu = MenuBar::new();
// second_menu.add_submenu("Sub menu", true, my_sub_menu);
second_menu.add_native_item(MenuItem::Copy);
second_menu.add_native_item(MenuItem::Paste);
second_menu.add_native_item(MenuItem::SelectAll);
menu_bar_menu.add_submenu("First menu", true, first_menu);
menu_bar_menu.add_submenu("Second menu", true, second_menu);
menu_bar_menu
});
let window = builder.build(event_loop).unwrap(); let window = builder.build(event_loop).unwrap();
let window_id = window.id(); let window_id = window.id();
View File
Whitespace-only changes.
+5 -4
View File
@@ -12,8 +12,8 @@ pub struct UseStateOwned<T: 'static> {
} }
impl<T> UseStateOwned<T> { impl<T> UseStateOwned<T> {
pub fn get(&self) -> Ref<T> { pub fn get(&self) -> Ref<Option<T>> {
Ref::map(self.wip.borrow(), |x| x.as_ref().unwrap()) self.wip.borrow()
} }
pub fn set(&self, new_val: T) { pub fn set(&self, new_val: T) {
@@ -21,8 +21,9 @@ impl<T> UseStateOwned<T> {
(self.update_callback)(); (self.update_callback)();
} }
pub fn modify(&self) -> RefMut<T> { pub fn modify(&self) -> RefMut<Option<T>> {
RefMut::map(self.wip.borrow_mut(), |x| x.as_mut().unwrap()) (self.update_callback)();
self.wip.borrow_mut()
} }
} }
+5
View File
@@ -1100,6 +1100,11 @@ impl label {
cx.attr("for", val, None, false) cx.attr("for", val, None, false)
} }
} }
impl a {
pub fn prevent_default<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
cx.attr("dioxus-prevent-default", val, None, false)
}
}
builder_constructors! { builder_constructors! {
// SVG components // SVG components
+3
View File
@@ -0,0 +1,3 @@
{
"rust-analyzer.cargo.allFeatures": true
}
+4 -2
View File
@@ -19,6 +19,7 @@ serde = "1"
url = "2.2.2" url = "2.2.2"
serde_urlencoded = "0.7" serde_urlencoded = "0.7"
# for wasm
web-sys = { version = "0.3", features = [ web-sys = { version = "0.3", features = [
"Attr", "Attr",
"Document", "Document",
@@ -33,11 +34,12 @@ web-sys = { version = "0.3", features = [
wasm-bindgen = { version = "0.2", optional = true } wasm-bindgen = { version = "0.2", optional = true }
js-sys = { version = "0.3", optional = true } js-sys = { version = "0.3", optional = true }
gloo = { version = "0.5", optional = true } gloo = { version = "0.5", optional = true }
log = "0.4.14"
[features] [features]
default = ["derive"] default = ["derive", "web"]
web = ["web-sys"] web = ["web-sys", "gloo", "js-sys", "wasm-bindgen"]
desktop = [] desktop = []
mobile = [] mobile = []
derive = [] derive = []
+21 -32
View File
@@ -1,40 +1,29 @@
# Router hook for Dioxus apps # Routing for Dioxus App
Dioxus-router provides a use_router hook that returns a different value depending on the route. DioxusRouter adds React-Router style routing to your Dioxus apps. Works in browser, SSR, and natively.
The router is generic over any value, however it makes sense to return a different set of VNodes
and feed them into the App's return VNodes.
Using the router should feel similar to tide's routing framework where an "address" book is assembled at the head of the app.
Here's an example of how to use the router hook:
```rust ```rust
#[derive(Clone, PartialEq, Serialize, Deserialize, Routable)] fn app() {
enum AppRoute { cx.render(rsx! {
Home, Routes {
Posts, Route { to: "/", Component {} },
NotFound Route { to: "/blog", Blog {} },
} Route { to: "/blog/:id", BlogPost {} },
}
static App: Component = |cx| {
let route = use_router(cx, AppRoute::parse);
match route {
AppRoute::Home => rsx!(cx, Home {})
AppRoute::Posts => rsx!(cx, Posts {})
AppRoute::Notfound => rsx!(cx, Notfound {})
}
};
```
Adding links into your app:
```rust
static Leaf: Component = |cx| {
rsx!(cx, div {
Link { to: AppRoute::Home }
}) })
} }
``` ```
Then, in your route, you can choose to parse the Route any way you want through `use_route`.
```rust
let id: usize = use_route(&cx).path("id")?;
let state: CustomState = use_route(&cx).parse()?;
```
Adding links into your app:
```rust
Link { to: "id/{id}" }
```
Currently, the router is only supported in a web environment, but we plan to add 1st-party support via the context API when new renderers are available. Currently, the router is only supported in a web environment, but we plan to add 1st-party support via the context API when new renderers are available.
+32 -23
View File
@@ -11,31 +11,40 @@ fn main() {
} }
static APP: Component = |cx| { static APP: Component = |cx| {
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
enum Route {
Home,
About,
NotFound,
}
impl Default for Route {
fn default() -> Self {
Route::Home
}
}
let route = use_router(&cx, |c| {});
cx.render(rsx! { cx.render(rsx! {
div { Router {
{match route { onchange: move |route| log::info!("route changed to {}", route),
Route::Home => rsx!(h1 { "Home" }), Route { to: "/", Home {} }
Route::About => rsx!(h1 { "About" }), Route { to: "blog"
Route::NotFound => rsx!(h1 { "NotFound" }), Route { to: "/", BlogList {} }
}} Route { to: ":id", BlogPost {} }
nav {
Link { to: Route::Home, href: "/" }
Link { to: Route::About, href: "/about" }
} }
} }
}) })
}; };
fn Home(cx: Scope) -> Element {
cx.render(rsx! {
div {
h1 { "Home" }
}
})
}
fn BlogList(cx: Scope) -> Element {
cx.render(rsx! {
div {
}
})
}
fn BlogPost(cx: Scope) -> Element {
let id = use_route(&cx).segment::<usize>("id")?;
cx.render(rsx! {
div {
}
})
}
@@ -1,4 +1,4 @@
use crate::{Routable, RouterService}; use crate::RouterService;
use dioxus::Attribute; use dioxus::Attribute;
use dioxus_core as dioxus; use dioxus_core as dioxus;
use dioxus_core::prelude::*; use dioxus_core::prelude::*;
@@ -6,8 +6,8 @@ use dioxus_core_macro::{format_args_f, rsx, Props};
use dioxus_html as dioxus_elements; use dioxus_html as dioxus_elements;
#[derive(Props)] #[derive(Props)]
pub struct LinkProps<'a, R: Routable> { pub struct LinkProps<'a> {
to: R, to: &'a str,
/// The url that gets pushed to the history stack /// The url that gets pushed to the history stack
/// ///
@@ -28,20 +28,26 @@ pub struct LinkProps<'a, R: Routable> {
#[props(default, setter(strip_option))] #[props(default, setter(strip_option))]
class: Option<&'a str>, class: Option<&'a str>,
#[props(default, setter(strip_option))]
id: Option<&'a str>,
children: Element<'a>, children: Element<'a>,
#[props(default)] #[props(default)]
attributes: Option<&'a [Attribute<'a>]>, attributes: Option<&'a [Attribute<'a>]>,
} }
#[allow(non_snake_case)] pub fn Link<'a>(cx: Scope<'a, LinkProps<'a>>) -> Element {
pub fn Link<'a, R: Routable>(cx: Scope<'a, LinkProps<'a, R>>) -> Element { let service = cx.consume_context::<RouterService>().unwrap();
let service = cx.consume_context::<RouterService<R>>()?;
cx.render(rsx! { cx.render(rsx! {
a { a {
href: "#", href: "{cx.props.to}",
class: format_args!("{}", cx.props.class.unwrap_or("")), class: format_args!("{}", cx.props.class.unwrap_or("")),
id: format_args!("{}", cx.props.id.unwrap_or("")),
prevent_default: "onclick",
onclick: move |_| service.push_route(cx.props.to.clone()), onclick: move |_| service.push_route(cx.props.to.clone()),
&cx.props.children &cx.props.children
} }
}) })
+65
View File
@@ -0,0 +1,65 @@
use dioxus_core::Element;
use dioxus_core as dioxus;
use dioxus_core::prelude::*;
use dioxus_core_macro::Props;
use dioxus_core_macro::*;
use dioxus_html as dioxus_elements;
use crate::{RouteContext, RouterService};
#[derive(Props)]
pub struct RouteProps<'a> {
to: &'a str,
children: Element<'a>,
#[props(default)]
fallback: bool,
}
pub fn Route<'a>(cx: Scope<'a, RouteProps<'a>>) -> Element {
// now we want to submit
let router_root = cx
.use_hook(|_| cx.consume_context::<RouterService>())
.as_ref()?;
cx.use_hook(|_| {
// create a bigger, better, longer route if one above us exists
let total_route = match cx.consume_context::<RouteContext>() {
Some(ctx) => format!("{}", ctx.total_route.clone()),
None => format!("{}", cx.props.to.clone()),
};
// provide our route context
let route_context = cx.provide_context(RouteContext {
declared_route: cx.props.to.to_string(),
total_route,
});
// submit our rout
router_root.register_total_route(
route_context.total_route.clone(),
cx.scope_id(),
cx.props.fallback,
);
Some(RouteInner {})
});
log::debug!("Checking route {}", cx.props.to);
if router_root.should_render(cx.scope_id()) {
cx.render(rsx!(&cx.props.children))
} else {
None
}
}
struct RouteInner {}
impl Drop for RouteInner {
fn drop(&mut self) {
// todo!()
}
}
+28
View File
@@ -0,0 +1,28 @@
use dioxus_core::Element;
use dioxus_core as dioxus;
use dioxus_core::prelude::*;
use dioxus_core_macro::*;
use dioxus_html as dioxus_elements;
use crate::RouterService;
#[derive(Props)]
pub struct RouterProps<'a> {
children: Element<'a>,
#[props(default, setter(strip_option))]
onchange: Option<&'a Fn(&'a str)>,
}
#[allow(non_snake_case)]
pub fn Router<'a>(cx: Scope<'a, RouterProps<'a>>) -> Element {
cx.use_hook(|_| {
let update = cx.schedule_update_any();
cx.provide_context(RouterService::new(update, cx.scope_id()))
});
cx.render(rsx!(
div { &cx.props.children }
))
}
+30
View File
@@ -0,0 +1,30 @@
use dioxus_core::ScopeState;
pub struct UseRoute<'a> {
cur_route: String,
cx: &'a ScopeState,
}
impl<'a> UseRoute<'a> {
/// Parse the query part of the URL
pub fn param<T>(&self, param: &str) -> Option<&T> {
todo!()
}
pub fn nth_segment(&self, n: usize) -> Option<&str> {
todo!()
}
pub fn last_segment(&self) -> Option<&str> {
todo!()
}
/// Parse the segments of the URL, using named parameters (defined in your router)
pub fn segment<T>(&self, name: &str) -> Option<&T> {
todo!()
}
}
pub fn use_route(cx: &ScopeState) -> UseRoute {
todo!()
}
+22 -25
View File
@@ -7,17 +7,6 @@
//! //!
//! ```rust //! ```rust
//! fn app(cx: Scope) -> Element { //! fn app(cx: Scope) -> Element {
//! let route = use_router(&cx, |svc, path| {
//! match path {
//! "/about" => Route::About,
//! _ => Route::Home,
//! }
//! });
//!
//! match route {
//! Route::Home => rsx!(cx, h1 { "Home" }),
//! Route::About => rsx!(cx, h1 { "About" }),
//! }
//! } //! }
//! //!
//! //!
@@ -35,22 +24,30 @@
//! //!
//! //!
mod link; mod hooks {
mod use_route;
pub use use_route::*;
}
pub use hooks::*;
mod components {
#![allow(non_snake_case)]
mod router;
pub use router::*;
mod route;
pub use route::*;
mod link;
pub use link::*;
}
pub use components::*;
mod platform; mod platform;
mod routecontext;
mod service; mod service;
mod userouter;
mod utils; mod utils;
pub use link::*; pub use routecontext::*;
use serde::{de::DeserializeOwned, Deserialize, Serialize};
pub use service::*; pub use service::*;
pub use userouter::*;
pub trait Routable:
'static + Send + Clone + PartialEq + Serialize + DeserializeOwned + Default
{
}
impl<T> Routable for T where
T: 'static + Send + Clone + PartialEq + Serialize + DeserializeOwned + Default
{
}
-7
View File
@@ -1,7 +0,0 @@
pub fn parse_current_route() -> String {
todo!()
}
pub(crate) fn get_base_route() -> String {
todo!()
}
+11
View File
@@ -0,0 +1,11 @@
use url::Url;
pub trait RouterProvider {
fn get_current_route(&self) -> String;
fn subscribe_to_route_changes(&self, callback: Box<dyn Fn(String)>);
}
enum RouteChange {
LinkTo(String),
Back(String),
}
+7
View File
@@ -0,0 +1,7 @@
pub struct RouteContext {
// "/name/:id"
pub declared_route: String,
// "app/name/:id"
pub total_route: String,
}
+102 -11
View File
@@ -1,18 +1,109 @@
use crate::Routable; use gloo::history::{BrowserHistory, History, HistoryListener};
use std::{cell::RefCell, rc::Rc}; use std::{
cell::{Cell, RefCell},
collections::HashMap,
rc::Rc,
};
pub struct RouterService<R: Routable> { use dioxus_core::ScopeId;
pub(crate) regen_route: Rc<dyn Fn()>,
pub(crate) pending_routes: RefCell<Vec<R>>, pub struct RouterService {
pub(crate) regen_route: Rc<dyn Fn(ScopeId)>,
history: Rc<RefCell<BrowserHistory>>,
registerd_routes: RefCell<RouteSlot>,
slots: Rc<RefCell<Vec<(ScopeId, String)>>>,
root_found: Rc<Cell<bool>>,
cur_root: RefCell<String>,
listener: HistoryListener,
} }
impl<R: Routable> RouterService<R> { enum RouteSlot {
pub fn current_path(&self) -> &str { Routes {
todo!() // the partial route
partial: String,
// the total route
total: String,
// Connections to other routs
rest: Vec<RouteSlot>,
},
}
impl RouterService {
pub fn new(regen_route: Rc<dyn Fn(ScopeId)>, root_scope: ScopeId) -> Self {
let history = BrowserHistory::default();
let location = history.location();
let path = location.path();
let slots: Rc<RefCell<Vec<(ScopeId, String)>>> = Default::default();
let _slots = slots.clone();
let root_found = Rc::new(Cell::new(false));
let regen = regen_route.clone();
let _root_found = root_found.clone();
let listener = history.listen(move || {
_root_found.set(false);
// checking if the route is valid is cheap, so we do it
for (slot, _) in _slots.borrow_mut().iter().rev() {
log::debug!("regenerating slot {:?}", slot);
regen(*slot);
}
});
Self {
registerd_routes: RefCell::new(RouteSlot::Routes {
partial: String::from("/"),
total: String::from("/"),
rest: Vec::new(),
}),
root_found,
history: Rc::new(RefCell::new(history)),
regen_route,
slots,
cur_root: RefCell::new(path.to_string()),
listener,
}
} }
pub fn push_route(&self, route: R) {
self.pending_routes.borrow_mut().push(route); pub fn push_route(&self, route: &str) {
(self.regen_route)(); self.history.borrow_mut().push(route);
}
pub fn register_total_route(&self, route: String, scope: ScopeId, fallback: bool) {
self.slots.borrow_mut().push((scope, route));
}
pub fn should_render(&self, scope: ScopeId) -> bool {
if self.root_found.get() {
return false;
}
let location = self.history.borrow().location();
let path = location.path();
let roots = self.slots.borrow();
let root = roots.iter().find(|(id, route)| id == &scope);
// fallback logic
match root {
Some((_id, route)) => {
if route == path {
self.root_found.set(true);
true
} else {
if route == "" {
self.root_found.set(true);
true
} else {
false
}
}
}
None => false,
}
} }
} }
-33
View File
@@ -1,33 +0,0 @@
use std::{cell::RefCell, rc::Rc};
use crate::{Routable, RouterCfg, RouterService};
use dioxus_core::ScopeState;
/// Initialize the app's router service and provide access to `Link` components
pub fn use_router<'a, R: Routable>(cx: &'a ScopeState, _cfg: impl FnOnce(&mut RouterCfg)) -> &'a R {
let router = cx.use_hook(|_| {
let svc: RouterService<R> = RouterService {
regen_route: cx.schedule_update(),
pending_routes: RefCell::new(Vec::new()),
};
let first_path = R::default();
cx.provide_context(svc);
UseRouterInner {
svc: cx.consume_context::<RouterService<R>>().unwrap(),
history: vec![first_path],
}
});
let mut pending_routes = router.svc.pending_routes.borrow_mut();
for route in pending_routes.drain(..) {
router.history.push(route);
}
router.history.last().unwrap()
}
struct UseRouterInner<R: Routable> {
svc: Rc<RouterService<R>>,
history: Vec<R>,
}
+15 -1
View File
@@ -274,7 +274,21 @@ impl WebsysDom {
// "Result" cannot be received from JS // "Result" cannot be received from JS
// Instead, we just build and immediately execute a closure that returns result // Instead, we just build and immediately execute a closure that returns result
match decode_trigger(event) { match decode_trigger(event) {
Ok(synthetic_event) => trigger.as_ref()(SchedulerMsg::Event(synthetic_event)), Ok(synthetic_event) => {
let target = event.target().unwrap();
if let Some(node) = target.dyn_ref::<HtmlElement>() {
if let Some(name) = node.get_attribute("dioxus-prevent-default") {
if name == synthetic_event.name
|| name.trim_start_matches("on") == synthetic_event.name
{
log::debug!("Preventing default");
event.prevent_default();
}
}
}
trigger.as_ref()(SchedulerMsg::Event(synthetic_event))
}
Err(e) => log::error!("Error decoding Dioxus event attribute. {:#?}", e), Err(e) => log::error!("Error decoding Dioxus event attribute. {:#?}", e),
}; };
}); });
+4
View File
@@ -157,6 +157,7 @@ pub async fn run_with_props<T: 'static + Send>(root: Component<T>, root_props: T
let mut websys_dom = dom::WebsysDom::new(root_el, cfg, sender_callback); let mut websys_dom = dom::WebsysDom::new(root_el, cfg, sender_callback);
log::debug!("rebuilding app");
let mut mutations = dom.rebuild(); let mut mutations = dom.rebuild();
// hydrating is simply running the dom for a single render. If the page is already written, then the corresponding // hydrating is simply running the dom for a single render. If the page is already written, then the corresponding
@@ -168,10 +169,13 @@ pub async fn run_with_props<T: 'static + Send>(root: Component<T>, root_props: T
let work_loop = ric_raf::RafLoop::new(); let work_loop = ric_raf::RafLoop::new();
loop { loop {
log::debug!("waiting for work");
// if virtualdom has nothing, wait for it to have something before requesting idle time // if virtualdom has nothing, wait for it to have something before requesting idle time
// if there is work then this future resolves immediately. // if there is work then this future resolves immediately.
dom.wait_for_work().await; dom.wait_for_work().await;
log::debug!("working..");
// wait for the mainthread to schedule us in // wait for the mainthread to schedule us in
let mut deadline = work_loop.wait_for_idle_time().await; let mut deadline = work_loop.wait_for_idle_time().await;