From 427b126bc17336d5d14d56eb7fddb8e07752495f Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 5 Jan 2022 16:34:24 -0500 Subject: [PATCH] feat: add prevent default attribute and upgrade router --- docs/guide/src/setup.md | 2 +- docs/reference/src/web/index.md | 2 +- examples/hello_world.rs | 3 +- packages/core-macro/src/rsx/component.rs | 5 + packages/core/src/diff.rs | 51 +++++---- packages/core/src/lazynodes.rs | 125 +++++++++++++---------- packages/core/src/nodes.rs | 13 +-- packages/core/src/scopes.rs | 14 +-- packages/desktop/src/index.js | 69 ++++++------- packages/desktop/src/lib.rs | 32 +----- packages/desktop/src/serialize.js | 0 packages/router/src/components/link.rs | 9 +- packages/router/src/components/route.rs | 13 ++- packages/router/src/components/router.rs | 5 +- packages/router/src/lib.rs | 2 + packages/router/src/service.rs | 56 ++++++++-- 16 files changed, 220 insertions(+), 181 deletions(-) delete mode 100644 packages/desktop/src/serialize.js diff --git a/docs/guide/src/setup.md b/docs/guide/src/setup.md index 11855d487..142ba5413 100644 --- a/docs/guide/src/setup.md +++ b/docs/guide/src/setup.md @@ -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. ``` -rustup target add wasm32-unknown-uknown +rustup target add wasm32-unknown-unknown ``` ### Dioxus-CLI for dev server, bundling, etc. diff --git a/docs/reference/src/web/index.md b/docs/reference/src/web/index.md index eefa5e6d8..86581f9f8 100644 --- a/docs/reference/src/web/index.md +++ b/docs/reference/src/web/index.md @@ -19,7 +19,7 @@ $ cargo install trunk Make sure the `wasm32-unknown-unknown` target is installed: ```shell -$ rustup target add wasm32-unknown-uknown +$ rustup target add wasm32-unknown-unknown ``` Create a new project: diff --git a/examples/hello_world.rs b/examples/hello_world.rs index ab7bfffa3..5b732f140 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -1,7 +1,8 @@ use dioxus::prelude::*; +use dioxus_desktop::tao::menu::MenuBar; 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 { diff --git a/packages/core-macro/src/rsx/component.rs b/packages/core-macro/src/rsx/component.rs index 3b1097536..211539bbd 100644 --- a/packages/core-macro/src/rsx/component.rs +++ b/packages/core-macro/src/rsx/component.rs @@ -191,6 +191,11 @@ impl Parse for ComponentField { return Ok(Self { name, content }); } + if input.peek(LitStr) && input.peek2(LitStr) { + let item = input.parse::().unwrap(); + proc_macro_error::emit_error!(item, "This attribute is misisng a trailing comma") + } + let content = ContentField::ManExpr(input.parse()?); Ok(Self { name, content }) } diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 2d4111c43..499d9543e 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -263,7 +263,7 @@ impl<'bump> DiffState<'bump> { }; if deadline_expired() { - log::debug!("Deadline expired before we could finish!"); + log::trace!("Deadline expired before we could finish!"); return false; } } @@ -423,16 +423,25 @@ impl<'bump> DiffState<'bump> { fn create_component_node(&mut self, vcomponent: &'bump VComponent<'bump>) { let parent_idx = self.stack.current_scope().unwrap(); - // Insert a new scope into our component list - let props: Box = vcomponent.props.borrow_mut().take().unwrap(); - let props: Box = 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, - ); + // the component might already exist - if it does, we need to reuse it + // this makes figure out when to drop the component more complicated + let new_idx = if let Some(idx) = vcomponent.scope.get() { + assert!(self.scopes.get_scope(idx).is_some()); + idx + } else { + // Insert a new scope into our component list + let props: Box = vcomponent.props.borrow_mut().take().unwrap(); + let props: Box = 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 vcomponent.scope.set(Some(new_idx)); @@ -443,9 +452,6 @@ impl<'bump> DiffState<'bump> { } false => { // 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); } } @@ -680,10 +686,10 @@ impl<'bump> DiffState<'bump> { new: &'bump VComponent<'bump>, ) { let scope_addr = old.scope.get().unwrap(); - log::debug!("diff_component_nodes: {:?}", scope_addr); + log::trace!("diff_component_nodes: {:?}", scope_addr); if std::ptr::eq(old, new) { - log::debug!("skipping component diff - component is the sames"); + log::trace!("skipping component diff - component is the sames"); return; } @@ -734,7 +740,7 @@ impl<'bump> DiffState<'bump> { self.scopes.fin_head(scope_addr), ); } else { - log::debug!("memoized"); + log::trace!("memoized"); // memoization has taken place drop(new_props); }; @@ -1239,7 +1245,10 @@ impl<'bump> DiffState<'bump> { 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(); + } } } } @@ -1290,7 +1299,11 @@ impl<'bump> DiffState<'bump> { let scope_id = c.scope.get().unwrap(); let root = self.scopes.root_node(scope_id); 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(); + } } } } diff --git a/packages/core/src/lazynodes.rs b/packages/core/src/lazynodes.rs index b22d09100..1340c916c 100644 --- a/packages/core/src/lazynodes.rs +++ b/packages/core/src/lazynodes.rs @@ -60,10 +60,7 @@ impl<'a, 'b> LazyNodes<'a, 'b> { } } - pub fn new(_val: F) -> Self - where - F: FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b, - { + pub fn new(_val: impl FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b) -> Self { // 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); @@ -240,57 +237,77 @@ fn round_to_words(len: usize) -> usize { (len + mem::size_of::() - 1) / mem::size_of::() } -#[test] -fn it_works() { - let bump = bumpalo::Bump::new(); +#[cfg(test)] +mod tests { + use super::*; + use crate::innerlude::{Element, Scope, VirtualDom}; - // let factory = NodeFactory { bump: &bump }; + #[test] + fn it_works() { + fn app(cx: Scope<()>) -> Element { + cx.render(LazyNodes::new_some(|f| { + f.text(format_args!("hello world!")) + })) + } - // let caller = LazyNodes::new_some(|f| { - // // - // f.text(format_args!("hello world!")) - // }); - // let g = caller.call(factory); + let mut dom = VirtualDom::new(app); + dom.rebuild(); - // 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 val = Rc::new(10); - - // let caller = { - // let it = (0..10) - // .map(|i| { - // let val = val.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::>(); - - // LazyNodes::new_some(|f| { - // log::debug!("main closure"); - // f.fragment_from_iter(it) - // }) - // }; - - // let _ = caller.call(factory); - // assert_eq!(Rc::strong_count(&val), 1); + let g = dom.base_scope().root_node(); + dbg!(g); + } + + #[test] + fn it_drops() { + use std::rc::Rc; + + struct AppProps { + inner: Rc, + } + + fn app(cx: Scope) -> 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::>(); + + LazyNodes::new_some(|f| { + log::debug!("main closure"); + f.fragment_from_iter(it) + }) + }; + + cx.render(caller) + } + + let inner = Rc::new(0); + let mut dom = VirtualDom::new_with_props( + app, + AppProps { + inner: inner.clone(), + }, + ); + dom.rebuild(); + + drop(dom); + + assert_eq!(Rc::strong_count(&inner), 1); + } } diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 18a5c32ad..ed4a6b0df 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -350,6 +350,7 @@ impl Clone for EventHandler<'_> { /// Only supports the functional syntax pub struct VComponent<'src> { pub key: Option<&'src str>, + pub originator: ScopeId, pub scope: Cell>, pub can_memoize: bool, pub user_fc: *const (), @@ -526,6 +527,7 @@ impl<'a> NodeFactory<'a> { scope: Default::default(), can_memoize: P::IS_STATIC, user_fc: component as *const (), + originator: self.scope.scope_id(), props: RefCell::new(Some(Box::new(VComponentProps { // local_props: RefCell::new(Some(props)), // heap_props: RefCell::new(None), @@ -749,19 +751,14 @@ impl IntoVNode<'_> for Arguments<'_> { impl<'a> IntoVNode<'a> for &Option> { fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> { self.as_ref() - .map(|f| f.decouple()) - .unwrap_or(VNode::Placeholder( - cx.bump.alloc(VPlaceholder { id: empty_cell() }), - )) + .map(|f| f.into_vnode(cx)) + .unwrap_or_else(|| cx.fragment_from_iter(None as Option)) } } impl<'a> IntoVNode<'a> for &VNode<'a> { fn into_vnode(self, _cx: NodeFactory<'a>) -> VNode<'a> { + // borrowed nodes are strange self.decouple() } } - -trait IntoAcceptedVnode<'a> { - fn into_accepted_vnode(self, cx: NodeFactory<'a>) -> VNode<'a>; -} diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index a7ee64299..66d6cdff5 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -143,7 +143,7 @@ impl ScopeArena { // Removes a scope and its descendents from the arena pub fn try_remove(&self, id: ScopeId) -> Option<()> { - log::debug!("removing scope {:?}", id); + log::trace!("removing scope {:?}", id); self.ensure_drop_safety(id); // Safety: @@ -189,7 +189,7 @@ impl ScopeArena { /// This also makes sure that drop order is consistent and predictable. All resources that rely on being dropped will /// be dropped. pub(crate) fn ensure_drop_safety(&self, scope_id: ScopeId) { - log::debug!("Ensuring drop safety for scope {:?}", scope_id); + log::trace!("Ensuring drop safety for scope {:?}", scope_id); if let Some(scope) = self.get_scope(scope_id) { let mut items = scope.items.borrow_mut(); @@ -220,7 +220,7 @@ impl ScopeArena { // Cycle to the next frame and then reset it // This breaks any latent references, invalidating every pointer referencing into it. // Remove all the outdated listeners - log::debug!("Running scope {:?}", id); + log::trace!("Running scope {:?}", id); self.ensure_drop_safety(id); // todo: we *know* that this is aliased by the contents of the scope itself @@ -282,18 +282,18 @@ impl ScopeArena { let nodes = self.nodes.borrow(); let mut cur_el = Some(element); - log::debug!("calling listener {:?}, {:?}", event, element); + log::trace!("calling listener {:?}, {:?}", event, element); let state = Rc::new(BubbleState::new()); while let Some(id) = cur_el.take() { if let Some(el) = nodes.get(id.0) { - log::debug!("Found valid receiver element"); + log::trace!("Found valid receiver element"); let real_el = unsafe { &**el }; if let VNode::Element(real_el) = real_el { for listener in real_el.listeners.borrow().iter() { if listener.event == event.name { - log::debug!("Found valid receiver event"); + log::trace!("Found valid receiver event"); if state.canceled.get() { // stop bubbling if canceled @@ -912,7 +912,7 @@ impl TaskQueue { } else { // todo: it should be okay to remote a fut while the queue is being polled // 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 { diff --git a/packages/desktop/src/index.js b/packages/desktop/src/index.js index 2ef907f78..7860c67f0 100644 --- a/packages/desktop/src/index.js +++ b/packages/desktop/src/index.js @@ -1,4 +1,3 @@ - function serialize_event(event) { switch (event.type) { case "copy": @@ -10,8 +9,8 @@ function serialize_event(event) { case "compositionstart": case "compositionupdate": return { - data: event.data - } + data: event.data, + }; case "keydown": case "keypress": @@ -29,7 +28,7 @@ function serialize_event(event) { repeat: event.repeat, which: event.which, // locale: event.locale, - } + }; case "focus": case "blur": @@ -45,7 +44,7 @@ function serialize_event(event) { } return { - value: value + value: value, }; case "input": @@ -60,7 +59,7 @@ function serialize_event(event) { } return { - value: value + value: value, }; } @@ -95,7 +94,7 @@ function serialize_event(event) { screen_x: event.screenX, screen_y: event.screenY, shift_key: event.shiftKey, - } + }; case "pointerdown": case "pointermove": @@ -130,7 +129,7 @@ function serialize_event(event) { twist: event.twist, pointer_type: event.pointerType, is_primary: event.isPrimary, - } + }; case "select": return {}; @@ -148,7 +147,7 @@ function serialize_event(event) { // changed_touches: event.changedTouches, // target_touches: event.targetTouches, // touches: event.touches, - } + }; case "scroll": return {}; @@ -208,17 +207,14 @@ function serialize_event(event) { default: return {}; } - - } - class Interpreter { constructor(root) { this.root = root; this.stack = [root]; this.listeners = { - "onclick": {} + onclick: {}, }; this.lastNodeWasText = false; this.nodes = [root]; @@ -304,35 +300,45 @@ class Interpreter { this.nodes[edit.root] = el; } - RemoveEventListener(edit) { } + RemoveEventListener(edit) {} NewEventListener(edit) { const event_name = edit.event_name; const mounted_node_id = edit.root; const scope = edit.scope; - const element = this.nodes[edit.root] - element.setAttribute(`dioxus-event-${event_name}`, `${scope}.${mounted_node_id}`); + const element = this.nodes[edit.root]; + element.setAttribute( + `dioxus-event-${event_name}`, + `${scope}.${mounted_node_id}` + ); if (this.listeners[event_name] === undefined) { this.listeners[event_name] = true; this.root.addEventListener(event_name, (event) => { - console.log("handling event", event); - const target = event.target; 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) { - alert("no id"); return; } - rpc.call('user_event', { + rpc.call("user_event", { event: event_name, mounted_dom_id: parseInt(real_id), - contents: serialize_event(event), + contents: contents, }); - }); } } @@ -346,11 +352,11 @@ class Interpreter { const name = edit.field; const value = edit.value; const ns = edit.ns; - const node = this.nodes[edit.root] + const node = this.nodes[edit.root]; if (ns == "style") { node.style[name] = value; - } else if ((ns != null) || (ns != undefined)) { + } else if (ns != null || ns != undefined) { node.setAttributeNS(ns, name, value); } else { switch (name) { @@ -358,17 +364,15 @@ class Interpreter { node.value = value; break; case "checked": - // console.log("setting checked"); - node.checked = (value === "true"); + node.checked = value === "true"; break; case "selected": - node.selected = (value === "true"); + node.selected = value === "true"; break; case "dangerous_inner_html": node.innerHTML = value; break; default: - // console.log("setting attr directly ", name, value); node.setAttribute(name, value); } } @@ -390,11 +394,6 @@ class Interpreter { } handleEdits(edits) { - - // console.log("handling raw edits", rawedits); - // let edits = JSON.parse(rawedits); - // console.log("handling edits", edits); - this.stack.push(this.root); for (let x = 0; x < edits.length; x++) { @@ -410,7 +409,7 @@ function main() { window.interpreter = new Interpreter(root); console.log(window.interpreter); - rpc.call('initialize'); + rpc.call("initialize"); } -main() +main(); diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index bcc8d1b82..1d5ebba43 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -279,37 +279,7 @@ impl DesktopController { cfg: &DesktopConfig, event_loop: &EventLoopWindowTarget, ) { - let builder = cfg.window.clone().with_menu({ - // 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 builder = cfg.window.clone(); let window = builder.build(event_loop).unwrap(); let window_id = window.id(); diff --git a/packages/desktop/src/serialize.js b/packages/desktop/src/serialize.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/router/src/components/link.rs b/packages/router/src/components/link.rs index 66fa5a7f8..a08eb288b 100644 --- a/packages/router/src/components/link.rs +++ b/packages/router/src/components/link.rs @@ -37,10 +37,8 @@ pub struct LinkProps<'a> { attributes: Option<&'a [Attribute<'a>]>, } -#[allow(non_snake_case)] pub fn Link<'a>(cx: Scope<'a, LinkProps<'a>>) -> Element { - log::debug!("rendering link {:?}", cx.scope_id()); - let service = cx.consume_context::()?; + let service = cx.consume_context::().unwrap(); cx.render(rsx! { a { href: "{cx.props.to}", @@ -48,10 +46,7 @@ pub fn Link<'a>(cx: Scope<'a, LinkProps<'a>>) -> Element { id: format_args!("{}", cx.props.id.unwrap_or("")), prevent_default: "onclick", - onclick: move |_| { - log::debug!("clicked"); - service.push_route(cx.props.to.clone()); - }, + onclick: move |_| service.push_route(cx.props.to.clone()), &cx.props.children } diff --git a/packages/router/src/components/route.rs b/packages/router/src/components/route.rs index 99b25a0b2..09dabb28b 100644 --- a/packages/router/src/components/route.rs +++ b/packages/router/src/components/route.rs @@ -11,7 +11,11 @@ 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 { @@ -34,15 +38,18 @@ pub fn Route<'a>(cx: Scope<'a, RouteProps<'a>>) -> Element { }); // submit our rout - router_root.register_total_route(route_context.total_route.clone(), cx.scope_id()); + router_root.register_total_route( + route_context.total_route.clone(), + cx.scope_id(), + cx.props.fallback, + ); Some(RouteInner {}) }); - log::debug!("rendering route {:?}", cx.scope_id()); + log::debug!("Checking route {}", cx.props.to); if router_root.should_render(cx.scope_id()) { - log::debug!("route {:?} produced nodes", cx.scope_id()); cx.render(rsx!(&cx.props.children)) } else { None diff --git a/packages/router/src/components/router.rs b/packages/router/src/components/router.rs index 416b93722..90c101fa8 100644 --- a/packages/router/src/components/router.rs +++ b/packages/router/src/components/router.rs @@ -15,14 +15,13 @@ pub struct RouterProps<'a> { onchange: Option<&'a Fn(&'a str)>, } +#[allow(non_snake_case)] pub fn Router<'a>(cx: Scope<'a, RouterProps<'a>>) -> Element { - let p = cx.use_hook(|_| { + cx.use_hook(|_| { let update = cx.schedule_update_any(); cx.provide_context(RouterService::new(update, cx.scope_id())) }); - log::debug!("rendering router {:?}", cx.scope_id()); - cx.render(rsx!( div { &cx.props.children } )) diff --git a/packages/router/src/lib.rs b/packages/router/src/lib.rs index 160cd9861..23d1c4a07 100644 --- a/packages/router/src/lib.rs +++ b/packages/router/src/lib.rs @@ -31,6 +31,8 @@ mod hooks { pub use hooks::*; mod components { + #![allow(non_snake_case)] + mod router; pub use router::*; diff --git a/packages/router/src/service.rs b/packages/router/src/service.rs index 56a57abdb..29a892748 100644 --- a/packages/router/src/service.rs +++ b/packages/router/src/service.rs @@ -1,13 +1,18 @@ use gloo::history::{BrowserHistory, History, HistoryListener}; -use std::{cell::RefCell, collections::HashMap, rc::Rc}; +use std::{ + cell::{Cell, RefCell}, + collections::HashMap, + rc::Rc, +}; use dioxus_core::ScopeId; pub struct RouterService { pub(crate) regen_route: Rc, - history: RefCell, + history: Rc>, registerd_routes: RefCell, - slots: RefCell>, + slots: Rc>>, + root_found: Rc>, cur_root: RefCell, listener: HistoryListener, } @@ -31,9 +36,20 @@ impl RouterService { let location = history.location(); let path = location.path(); - let regen = Rc::clone(®en_route); + let slots: Rc>> = 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 || { - regen(root_scope); + _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 { @@ -42,9 +58,10 @@ impl RouterService { total: String::from("/"), rest: Vec::new(), }), - history: RefCell::new(history), + root_found, + history: Rc::new(RefCell::new(history)), regen_route, - slots: Default::default(), + slots, cur_root: RefCell::new(path.to_string()), listener, } @@ -54,20 +71,37 @@ impl RouterService { self.history.borrow_mut().push(route); } - pub fn register_total_route(&self, route: String, scope: ScopeId) { - self.slots.borrow_mut().insert(scope, 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.get(&scope); + let root = roots.iter().find(|(id, route)| id == &scope); + // fallback logic match root { - Some(r) => r == path, + 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, } }