diff --git a/packages/router-macro/src/lib.rs b/packages/router-macro/src/lib.rs index 8fa5aeeed..5a832f02b 100644 --- a/packages/router-macro/src/lib.rs +++ b/packages/router-macro/src/lib.rs @@ -244,7 +244,7 @@ impl RouteEnum { }); quote! { - impl<'a> TryFrom<&'a str> for #name { + impl<'a> core::convert::TryFrom<&'a str> for #name { type Error = ::Err; fn try_from(s: &'a str) -> Result { diff --git a/packages/router/examples/simple_routes.rs b/packages/router/examples/simple_routes.rs index 8357933dd..4ab83cef0 100644 --- a/packages/router/examples/simple_routes.rs +++ b/packages/router/examples/simple_routes.rs @@ -1,81 +1,147 @@ #![allow(non_snake_case)] use dioxus::prelude::*; -use dioxus_router::{history::MemoryHistory, prelude::*}; +use dioxus_router::prelude::*; +use serde::{Deserialize, Serialize}; +use std::str::FromStr; fn main() { - #[cfg(not(feature = "web"))] - dioxus_desktop::launch(App); - #[cfg(feature = "web")] - dioxus_web::launch(App); + #[cfg(not(target_arch = "wasm32"))] + dioxus_desktop::launch(root); + + #[cfg(target_arch = "wasm32")] + dioxus_web::launch(root); } -fn App(cx: Scope) -> Element { - use_router( - cx, - &|| { - #[cfg(not(feature = "web"))] - let history = MemoryHistory::default(); - #[cfg(feature = "web")] - let history = dioxus_router::history::WebHistory::new(None, true); - - RouterConfiguration { - history: Box::new(history), +fn root(cx: Scope) -> Element { + render! { + Router:: { + config: RouterConfiguration { + history: { + #[cfg(not(target_arch = "wasm32"))] + let history = Box::>::default(); + #[cfg(target_arch = "wasm32")] + let history = Box::>::default(); + history + }, ..Default::default() } - }, - &|| { - Segment::content(comp(Home)) - .fixed("apple", comp(Apple)) - .fixed("potato", Route::content(comp(Potato)).name::()) - .fixed("earth_apple", named::()) - }, - ); - - render! { - h1 { "Simple Example App" } - Outlet { } - Link { - target: named::(), - "Go to root" } } } -fn Home(cx: Scope) -> Element { +#[inline_props] +fn UserFrame(cx: Scope, user_id: usize) -> Element { render! { - h2 { "Root Index" } - ul { - li { Link { - target: "/apple", - "Read about apples…" - } } - li { Link { - target: named::(), - "Read about potatoes…" - } } - li { Link { - target: "/earth_apple", - "Read about earth apples (literal translation of a german word for potato)…" - } } + pre { + "UserFrame{{\n\tuser_id:{user_id}\n}}" + } + div { + background_color: "rgba(0,0,0,50%)", + "children:" + Outlet:: {} } } } -fn Apple(cx: Scope) -> Element { +#[inline_props] +fn Route1(cx: Scope, user_id: usize, dynamic: usize, extra: String) -> Element { render! { - h2 { "Apple" } - p { - "An apple is a tasty fruit. It grows on trees and many varieties are either red or " - "green." + pre { + "Route1{{\n\tuser_id:{user_id},\n\tdynamic:{dynamic},\n\textra:{extra}\n}}" + } + Link:: { + target: Route::Route1 { user_id: *user_id, dynamic: *dynamic, extra: extra.clone() + "." }, + "Route1 with extra+\".\"" + } + p { "Footer" } + Link:: { + target: Route::Route3 { dynamic: String::new() }, + "Home" } } } -struct PotatoName; -fn Potato(cx: Scope) -> Element { +#[inline_props] +fn Route2(cx: Scope, user_id: usize) -> Element { render! { - h2 { "Potato" } - p { "The potato grows underground. There are many recipes involving potatoes." } + pre { + "Route2{{\n\tuser_id:{user_id}\n}}" + } + (0..*user_id).map(|i| rsx!{ p { "{i}" } }), + p { "Footer" } + Link:: { + target: Route::Route3 { dynamic: String::new() }, + "Home" + } } } + +#[inline_props] +fn Route3(cx: Scope, dynamic: String) -> Element { + let router = use_router(cx); + let router_route = router.current(); + let current_route = use_ref(cx, String::new); + let parsed = Route::from_str(¤t_route.read()); + + let site_map = Route::SITE_MAP + .iter() + .flat_map(|seg| seg.flatten().into_iter()) + .collect::>(); + + render! { + input { + oninput: move |evt| { + *current_route.write() = evt.value.clone(); + }, + value: "{current_route.read()}" + } + "dynamic: {dynamic}" + Link:: { + target: Route::Route2 { user_id: 8888 }, + "hello world link" + } + p { "Site Map" } + pre { "{site_map:#?}" } + p { "Dynamic link" } + if let Ok(route) = parsed { + if route != router_route { + render! { + Link:: { + target: route.clone(), + "{route}" + } + } + } + else{None} + } + else{None} + } +} + +#[rustfmt::skip] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Routable)] +enum Route { + // Nests with parameters have types taken from child routes + #[nest("/user/:user_id")] + // Everything inside the nest has the added parameter `user_id: String` + // UserFrame is a layout component that will receive the `user_id: String` parameter + #[layout(UserFrame)] + // Route1 is a non-layout component that will receive the `user_id: String` and `dynamic: String` parameters + #[route("/:dynamic", Route1)] + Route1 { + // The type is taken from the first instance of the dynamic parameter + user_id: usize, + dynamic: usize, + extra: String, + }, + // Route2 is a non-layout component that will receive the `user_id: String` parameter + #[route("/hello_world", Route2)] + // You can opt out of the layout by using the `!` prefix + #[layout(!UserFrame)] + Route2 { user_id: usize }, + #[end_layout] + #[end_nest] + #[route("/:dynamic", Route3)] + Route3 { dynamic: String }, +} diff --git a/packages/router/src/components/router.rs b/packages/router/src/components/router.rs index c77e3a9b6..f6dceb8c3 100644 --- a/packages/router/src/components/router.rs +++ b/packages/router/src/components/router.rs @@ -13,7 +13,10 @@ pub struct RouterCfg { config: RefCell>>, } -impl Default for RouterCfg { +impl Default for RouterCfg +where + ::Err: std::fmt::Display, +{ fn default() -> Self { Self { config: RefCell::new(Some(RouterConfiguration::default())), @@ -31,14 +34,20 @@ impl From> for RouterCfg { /// The props for [`Router`]. #[derive(Props)] -pub struct RouterProps { +pub struct RouterProps +where + ::Err: std::fmt::Display, +{ #[props(into)] initial_url: Option, #[props(default, into)] config: RouterCfg, } -impl PartialEq for RouterProps { +impl PartialEq for RouterProps +where + ::Err: std::fmt::Display, +{ fn eq(&self, _: &Self) -> bool { // prevent the router from re-rendering when the initial url or config changes true diff --git a/packages/router/src/history/memory.rs b/packages/router/src/history/memory.rs index b8e4dd505..abd0a90d8 100644 --- a/packages/router/src/history/memory.rs +++ b/packages/router/src/history/memory.rs @@ -6,12 +6,15 @@ use super::HistoryProvider; /// A [`HistoryProvider`] that stores all navigation information in memory. pub struct MemoryHistory { - current: Option, + current: R, history: Vec, future: Vec, } -impl MemoryHistory { +impl MemoryHistory +where + ::Err: std::fmt::Display, +{ /// Create a [`MemoryHistory`] starting at `path`. /// /// ```rust @@ -24,16 +27,19 @@ impl MemoryHistory { let path = path.into(); Ok(Self { - current: Some(R::from_str(&path)?), + current: R::from_str(&path)?, ..Default::default() }) } } -impl Default for MemoryHistory { +impl Default for MemoryHistory +where + ::Err: std::fmt::Display, +{ fn default() -> Self { Self { - current: None, + current: "/".parse().unwrap_or_else(|err| panic!("{}", err)), history: Vec::new(), future: Vec::new(), } @@ -42,7 +48,7 @@ impl Default for MemoryHistory { impl HistoryProvider for MemoryHistory { fn current_route(&self) -> R { - self.current.clone().expect("current route is not set") + self.current.clone() } fn can_go_back(&self) -> bool { @@ -51,10 +57,8 @@ impl HistoryProvider for MemoryHistory { fn go_back(&mut self) { if let Some(last) = self.history.pop() { - let new = self.current.replace(last); - if let Some(new) = new { - self.future.push(new); - } + let old = std::mem::replace(&mut self.current, last); + self.future.push(old); } } @@ -64,23 +68,19 @@ impl HistoryProvider for MemoryHistory { fn go_forward(&mut self) { if let Some(next) = self.future.pop() { - let old = self.current.replace(next); - if let Some(old) = old { - self.history.push(old); - } + let old = std::mem::replace(&mut self.current, next); + self.history.push(old); } } fn push(&mut self, new: R) { - if let Some(current) = self.current.take() { - self.history.push(current); - } - self.current = Some(new); + let old = std::mem::replace(&mut self.current, new); + self.history.push(old); self.future.clear(); } fn replace(&mut self, path: R) { - self.current = Some(path); + self.current = path; } } diff --git a/packages/router/src/lib.rs b/packages/router/src/lib.rs index 6446bf41a..d3ecf37b7 100644 --- a/packages/router/src/lib.rs +++ b/packages/router/src/lib.rs @@ -46,6 +46,7 @@ pub mod hooks { pub mod prelude { pub use crate::components::*; pub use crate::contexts::*; + pub use crate::history::*; pub use crate::hooks::*; pub use crate::routable::*; pub use crate::router_cfg::RouterConfiguration; diff --git a/packages/router/src/router_cfg.rs b/packages/router/src/router_cfg.rs index 1706f5b14..28f8efefc 100644 --- a/packages/router/src/router_cfg.rs +++ b/packages/router/src/router_cfg.rs @@ -58,7 +58,10 @@ pub struct RouterConfiguration { pub on_update: Option>, } -impl Default for RouterConfiguration { +impl Default for RouterConfiguration +where + ::Err: std::fmt::Display, +{ fn default() -> Self { Self { failure_external_navigation: FailureExternalNavigation::,