diff --git a/packages/router-macro/src/lib.rs b/packages/router-macro/src/lib.rs index 381627571..5f62fabbd 100644 --- a/packages/router-macro/src/lib.rs +++ b/packages/router-macro/src/lib.rs @@ -53,8 +53,20 @@ pub fn routable(input: TokenStream) -> TokenStream { dioxus_router::prelude::GenericLink(cx) } - #vis fn use_router(cx: &dioxus::prelude::ScopeState) -> &dioxus_router::prelude::GenericRouterContext { - dioxus_router::prelude::use_generic_router::(cx) + #vis fn GoBackButton<'a>(cx: dioxus::prelude::Scope<'a, dioxus_router::prelude::GenericHistoryButtonProps<'a>>) -> dioxus::prelude::Element<'a> { + dioxus_router::prelude::GenericGoBackButton::<#name>(cx) + } + + #vis fn GoForwardButton<'a>(cx: dioxus::prelude::Scope<'a, dioxus_router::prelude::GenericHistoryButtonProps<'a>>) -> dioxus::prelude::Element<'a> { + dioxus_router::prelude::GenericGoForwardButton::<#name>(cx) + } + + #vis fn use_router(cx: &dioxus::prelude::ScopeState) -> &dioxus_router::prelude::GenericRouterContext<#name> { + dioxus_router::prelude::use_generic_router(cx) + } + + #vis fn use_route(cx: &dioxus::prelude::ScopeState) -> Option<#name> { + dioxus_router::prelude::use_generic_route(cx) } #error_type diff --git a/packages/router/README.md b/packages/router/README.md index 0db79c6d6..fd8100d66 100644 --- a/packages/router/README.md +++ b/packages/router/README.md @@ -7,13 +7,10 @@ [crates-badge]: https://img.shields.io/crates/v/dioxus-router.svg [crates-url]: https://crates.io/crates/dioxus-router - [mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg [mit-url]: https://github.com/dioxuslabs/dioxus/blob/master/LICENSE - [actions-badge]: https://github.com/dioxuslabs/dioxus/actions/workflows/main.yml/badge.svg [actions-url]: https://github.com/dioxuslabs/dioxus/actions?query=workflow%3ACI+branch%3Amaster - [discord-badge]: https://img.shields.io/discord/899851952891002890.svg?logo=discord&style=flat-square [discord-url]: https://discord.gg/XgGxMSkvUM @@ -28,38 +25,49 @@ Dioxus Router is a first-party Router for all your Dioxus Apps. It provides an interface similar to React Router, but takes advantage of types for more expressiveness. -```rust ,no_run +```rust, no_run +#![allow(non_snake_case)] + use dioxus::prelude::*; use dioxus_router::prelude::*; +use serde::{Deserialize, Serialize}; +use std::str::FromStr; + +#[rustfmt::skip] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Routable)] +enum Route { + #[nest("/blog")] + #[layout(Blog)] + #[route("/")] + BlogList {}, + + #[route("/:blog_id")] + BlogPost { blog_id: usize }, + #[end_layout] + #[end_nest] + #[route("/")] + Index {}, +} + fn App(cx: Scope) -> Element { - use_router( - &cx, - &|| Default::default(), - &|| Segment::content(comp(Index)).fixed( - "blog", - Route::content(comp(Blog)).nested( - Segment::content(comp(BlogList)) - .catch_all((comp(BlogPost), BlogPost)) - ) - ) - ); - render! { - Outlet { } + Router { } } } +#[inline_props] fn Index(cx: Scope) -> Element { render! { h1 { "Index" } Link { - target: "/blog", + target: Route::BlogList {}, "Go to the blog" } } } +#[inline_props] fn Blog(cx: Scope) -> Element { render! { h1 { "Blog" } @@ -67,21 +75,23 @@ fn Blog(cx: Scope) -> Element { } } +#[inline_props] fn BlogList(cx: Scope) -> Element { render! { h2 { "List of blog posts" } Link { - target: "/blog/1", + target: Route::BlogPost { blog_id: 0 }, "Blog post 1" } Link { - target: "/blog/1", + target: Route::BlogPost { blog_id: 1 }, "Blog post 2" } } } -fn BlogPost(cx: Scope) -> Element { +#[inline_props] +fn BlogPost(cx: Scope, blog_id: usize) -> Element { render! { h2 { "Blog Post" } } @@ -96,6 +106,7 @@ You need to enable the right features for the platform you're targeting since th - Join the discord and ask questions! ## License + This project is licensed under the [MIT license]. [mit license]: https://github.com/DioxusLabs/dioxus/blob/master/LICENSE-MIT diff --git a/packages/router/src/components/default_errors.rs b/packages/router/src/components/default_errors.rs index dde582b42..9264942c0 100644 --- a/packages/router/src/components/default_errors.rs +++ b/packages/router/src/components/default_errors.rs @@ -1,11 +1,12 @@ use crate::{ - components::GenericLink, hooks::use_route, navigation::NavigationTarget, routable::Routable, + components::GenericLink, hooks::use_generic_route, navigation::NavigationTarget, + routable::Routable, }; use dioxus::prelude::*; #[allow(non_snake_case)] pub fn FailureExternalNavigation(cx: Scope) -> Element { - let href = use_route::(cx).expect( + let href = use_generic_route::(cx).expect( "`FailureExternalNavigation` can only be mounted by the router itself, \ since it is not exposed", ); @@ -38,7 +39,9 @@ pub fn FailureNamedNavigation(cx: Scope) -> Element { "there is no guarantee." } GenericLink:: { - target: NavigationTarget::External("https://google.com".into()), + target: NavigationTarget::Internal(R::from_str("/").unwrap_or_else(|_| { + panic!("Failed to parse `/` as a Route") + })), "Click here to try to fix the failure." } } @@ -58,7 +61,9 @@ pub fn FailureRedirectionLimit(cx: Scope) -> Element { "there is no guarantee." } GenericLink:: { - target: NavigationTarget::External("https://google.com".into()), + target: NavigationTarget::Internal(R::from_str("/").unwrap_or_else(|_| { + panic!("Failed to parse `/` as a Route") + })), "Click here to try to fix the failure." } } diff --git a/packages/router/src/components/history_buttons.rs b/packages/router/src/components/history_buttons.rs index 916a24280..b2e8b363f 100644 --- a/packages/router/src/components/history_buttons.rs +++ b/packages/router/src/components/history_buttons.rs @@ -1,41 +1,44 @@ use dioxus::prelude::*; use log::error; -use crate::{routable::Routable, utils::use_router_internal::use_router_internal}; +use crate::{prelude::*, utils::use_router_internal::use_router_internal}; /// The properties for a [`GoBackButton`] or a [`GoForwardButton`]. #[derive(Debug, Props)] -pub struct HistoryButtonProps<'a> { +pub struct GenericHistoryButtonProps<'a> { /// The children to render within the generated HTML button tag. pub children: Element<'a>, } /// A button to go back through the navigation history. Similar to a browsers back button. /// -/// Only works as descendant of a component calling [`use_router`], otherwise it will be inactive. +/// Only works as descendant of a [`GenericRouter`] component, otherwise it will be inactive. /// /// The button will disable itself if it is known that no prior history is available. /// -/// [`use_router`]: crate::hooks::use_router -/// /// # Panic -/// - When the [`GoBackButton`] is not nested within another component calling the [`use_router`] +/// - When the [`GoBackButton`] is not nested within a [`GenericRouter`] component /// hook, but only in debug builds. /// /// # Example /// ```rust /// # use dioxus::prelude::*; /// # use dioxus_router::prelude::*; -/// fn App(cx: Scope) -> Element { -/// use_router( -/// &cx, -/// &|| RouterConfiguration { -/// synchronous: true, // asynchronicity not needed for doc test -/// ..Default::default() -/// }, -/// &|| Segment::empty() -/// ); +/// # use serde::{Deserialize, Serialize}; +/// #[derive(Clone, Serialize, Deserialize, Routable)] +/// enum Route { +/// #[route("/")] +/// Index {}, +/// } /// +/// fn App(cx: Scope) -> Element { +/// render! { +/// Router {} +/// } +/// } +/// +/// #[inline_props] +/// fn Index(cx: Scope) -> Element { /// render! { /// GoBackButton { /// "go back" @@ -51,8 +54,10 @@ pub struct HistoryButtonProps<'a> { /// # ); /// ``` #[allow(non_snake_case)] -pub fn GoBackButton<'a, R: Routable>(cx: Scope<'a, HistoryButtonProps<'a>>) -> Element { - let HistoryButtonProps { children } = cx.props; +pub fn GenericGoBackButton<'a, R: Routable>( + cx: Scope<'a, GenericHistoryButtonProps<'a>>, +) -> Element { + let GenericHistoryButtonProps { children } = cx.props; // hook up to router let router = match use_router_internal::(cx) { @@ -81,30 +86,33 @@ pub fn GoBackButton<'a, R: Routable>(cx: Scope<'a, HistoryButtonProps<'a>>) -> E /// A button to go forward through the navigation history. Similar to a browsers forward button. /// -/// Only works as descendant of a component calling [`use_router`], otherwise it will be inactive. +/// Only works as descendant of a [`GenericRouter`] component, otherwise it will be inactive. /// /// The button will disable itself if it is known that no later history is available. /// -/// [`use_router`]: crate::hooks::use_router -/// /// # Panic -/// - When the [`GoForwardButton`] is not nested within another component calling the [`use_router`] +/// - When the [`GoForwardButton`] is not nested within a [`GenericRouter`] component /// hook, but only in debug builds. /// /// # Example /// ```rust /// # use dioxus::prelude::*; /// # use dioxus_router::prelude::*; -/// fn App(cx: Scope) -> Element { -/// use_router( -/// &cx, -/// &|| RouterConfiguration { -/// synchronous: true, // asynchronicity not needed for doc test -/// ..Default::default() -/// }, -/// &|| Segment::empty() -/// ); +/// # use serde::{Deserialize, Serialize}; +/// #[derive(Clone, Serialize, Deserialize, Routable)] +/// enum Route { +/// #[route("/")] +/// Index {}, +/// } /// +/// fn App(cx: Scope) -> Element { +/// render! { +/// Router {} +/// } +/// } +/// +/// #[inline_props] +/// fn Index(cx: Scope) -> Element { /// render! { /// GoForwardButton { /// "go forward" @@ -120,8 +128,10 @@ pub fn GoBackButton<'a, R: Routable>(cx: Scope<'a, HistoryButtonProps<'a>>) -> E /// # ); /// ``` #[allow(non_snake_case)] -pub fn GoForwardButton<'a, R: Routable>(cx: Scope<'a, HistoryButtonProps<'a>>) -> Element { - let HistoryButtonProps { children } = cx.props; +pub fn GenericGoForwardButton<'a, R: Routable>( + cx: Scope<'a, GenericHistoryButtonProps<'a>>, +) -> Element { + let GenericHistoryButtonProps { children } = cx.props; // hook up to router let router = match use_router_internal::(cx) { diff --git a/packages/router/src/components/link.rs b/packages/router/src/components/link.rs index 478b8cc91..44740bce8 100644 --- a/packages/router/src/components/link.rs +++ b/packages/router/src/components/link.rs @@ -4,7 +4,7 @@ use dioxus::prelude::*; use log::error; use crate::navigation::NavigationTarget; -use crate::routable::Routable; +use crate::prelude::*; use crate::utils::use_router_internal::use_router_internal; /// The properties for a [`Link`]. @@ -63,56 +63,54 @@ impl Debug for GenericLinkProps<'_, R> { /// A link to navigate to another route. /// -/// Only works as descendant of a component calling [`use_router`], otherwise it will be inactive. +/// Only works as descendant of a [`GenericRouter`] component, otherwise it will be inactive. /// -/// Unlike a regular HTML anchor, a [`Link`] allows the router to handle the navigation and doesn't +/// Unlike a regular HTML anchor, a [`GenericLink`] allows the router to handle the navigation and doesn't /// cause the browser to load a new page. /// -/// However, in the background a [`Link`] still generates an anchor, which you can use for styling +/// However, in the background a [`GenericLink`] still generates an anchor, which you can use for styling /// as normal. /// -/// [`use_router`]: crate::hooks::use_router -/// /// # External targets -/// When the [`Link`]s target is an [`External`] target, that is used as the `href` directly. This -/// means that a [`Link`] can always navigate to an [`External`] target. -/// -/// This is different from a [`Navigator`], which can only navigate to external targets when the -/// routers [`HistoryProvider`] supports it. -/// -/// [`External`]: dioxus_router_core::navigation::NavigationTarget::External -/// [`HistoryProvider`]: dioxus_router_core::history::HistoryProvider -/// [`Navigator`]: dioxus_router_core::Navigator +/// When the [`GenericLink`]s target is an [`NavigationTarget::External`] target, that is used as the `href` directly. This +/// means that a [`GenericLink`] can always navigate to an [`NavigationTarget::External`] target, even if the [`HistoryProvider`] does not support it. /// /// # Panic -/// - When the [`Link`] is not nested within another component calling the [`use_router`] hook, but +/// - When the [`GenericLink`] is not nested within a [`GenericRouter`], but /// only in debug builds. /// /// # Example /// ```rust /// # use dioxus::prelude::*; /// # use dioxus_router::prelude::*; +/// # use serde::{Deserialize, Serialize}; +/// +/// #[derive(Clone, Serialize, Deserialize, Routable)] +/// enum Route { +/// #[route("/")] +/// Index {}, +/// } +/// /// fn App(cx: Scope) -> Element { -/// use_router( -/// &cx, -/// &|| RouterConfiguration { -/// synchronous: true, // asynchronicity not needed for doc test -/// ..Default::default() -/// }, -/// &|| Segment::empty() -/// ); -/// /// render! { -/// Link { -/// active_class: "active", -/// class: "link_class", -/// exact: true, -/// id: "link_id", -/// new_tab: true, -/// rel: "link_rel", -/// target: "/", +/// Router {} +/// } +/// } /// -/// "A fully configured link" +/// #[inline_props] +/// fn Index(cx: Scope) -> Element { +/// render! { +/// render! { +/// Link { +/// active_class: "active", +/// class: "link_class", +/// id: "link_id", +/// new_tab: true, +/// rel: "link_rel", +/// target: Route::Index {}, +/// +/// "A fully configured link" +/// } /// } /// } /// } diff --git a/packages/router/src/components/outlet.rs b/packages/router/src/components/outlet.rs index 61cf43c22..d45c9fd82 100644 --- a/packages/router/src/components/outlet.rs +++ b/packages/router/src/components/outlet.rs @@ -3,13 +3,11 @@ use dioxus::prelude::*; /// An outlet for the current content. /// -/// Only works as descendant of a component calling [`use_router`], otherwise it will be inactive. +/// Only works as descendant of a [`GenericRouter`] component, otherwise it will be inactive. /// /// The [`Outlet`] is aware of how many [`Outlet`]s it is nested within. It will render the content /// of the active route that is __exactly as deep__. /// -/// [`use_router`]: crate::hooks::use_router -/// /// # Panic /// - When the [`Outlet`] is not nested within another component calling the [`use_router`] hook, /// but only in debug builds. @@ -17,32 +15,61 @@ use dioxus::prelude::*; /// # Example /// ```rust /// # use dioxus::prelude::*; +/// # use serde::{Deserialize, Serialize}; /// # use dioxus_router::prelude::*; -/// fn App(cx: Scope) -> Element { -/// use_router( -/// &cx, -/// &|| RouterConfiguration { -/// synchronous: true, // asynchronicity not needed for doc test -/// ..Default::default() -/// }, -/// &|| Segment::content(comp(Content)) -/// ); +/// #[derive(Clone, Serialize, Deserialize, Routable)] +/// #[rustfmt::skip] +/// enum Route { +/// #[nest("/wrap")] +/// #[layout(Wrapper)] // Every layout component must have one Outlet +/// #[route("/")] +/// Child {}, +/// #[end_layout] +/// #[end_nest] +/// #[route("/")] +/// Index {}, +/// } /// +/// #[inline_props] +/// fn Index(cx: Scope) -> Element { +/// render! { +/// div { +/// "Index" +/// } +/// } +/// } +/// +/// #[inline_props] +/// fn Wrapper(cx: Scope) -> Element { /// render! { /// h1 { "App" } -/// Outlet { } // The content component will be rendered here +/// Outlet {} // The content of child routes will be rendered here /// } /// } /// -/// fn Content(cx: Scope) -> Element { +/// #[inline_props] +/// fn Child(cx: Scope) -> Element { /// render! { -/// p { "Content" } +/// p { +/// "Child" +/// } /// } /// } +/// +/// # fn App(cx: Scope) -> Element { +/// # render! { +/// # Router { +/// # config: RouterConfiguration { +/// # history: Box::new(MemoryHistory::with_initial_path("/wrap").unwrap()), +/// # ..Default::default() +/// # } +/// # } +/// # } +/// # } /// # /// # let mut vdom = VirtualDom::new(App); /// # let _ = vdom.rebuild(); -/// # assert_eq!(dioxus_ssr::render(&vdom), "

App

Content

"); +/// # assert_eq!(dioxus_ssr::render(&vdom), "

App

Child

"); /// ``` pub fn GenericOutlet(cx: Scope) -> Element { OutletContext::render::(cx) diff --git a/packages/router/src/components/router.rs b/packages/router/src/components/router.rs index 4ceae0fde..977df4b64 100644 --- a/packages/router/src/components/router.rs +++ b/packages/router/src/components/router.rs @@ -1,5 +1,6 @@ use dioxus::prelude::*; use log::error; +use serde::{de::DeserializeOwned, Serialize}; use std::{cell::RefCell, str::FromStr}; use crate::{ @@ -13,7 +14,7 @@ pub struct RouterCfg { config: RefCell>>, } -impl Default for RouterCfg +impl Default for RouterCfg where ::Err: std::fmt::Display, { @@ -34,7 +35,7 @@ impl From> for RouterCfg { /// The props for [`Router`]. #[derive(Props)] -pub struct GenericRouterProps +pub struct GenericRouterProps where ::Err: std::fmt::Display, { @@ -42,7 +43,7 @@ where config: RouterCfg, } -impl PartialEq for GenericRouterProps +impl PartialEq for GenericRouterProps where ::Err: std::fmt::Display, { @@ -53,7 +54,9 @@ where } /// A component that renders the current route. -pub fn GenericRouter(cx: Scope>) -> Element +pub fn GenericRouter( + cx: Scope>, +) -> Element where ::Err: std::fmt::Display, { diff --git a/packages/router/src/contexts/outlet.rs b/packages/router/src/contexts/outlet.rs index dd5caf49c..f5a78ab98 100644 --- a/packages/router/src/contexts/outlet.rs +++ b/packages/router/src/contexts/outlet.rs @@ -1,6 +1,6 @@ use dioxus::prelude::*; -use crate::{hooks::use_route, routable::Routable}; +use crate::{hooks::use_generic_route, routable::Routable}; #[derive(Clone)] pub(crate) struct OutletContext { @@ -25,7 +25,7 @@ impl OutletContext { } }); - use_route::(cx) + use_generic_route::(cx) .expect("Outlet must be inside of a router") .render(cx, current_level) } diff --git a/packages/router/src/contexts/router.rs b/packages/router/src/contexts/router.rs index fa0692e9f..832593f0d 100644 --- a/packages/router/src/contexts/router.rs +++ b/packages/router/src/contexts/router.rs @@ -149,7 +149,8 @@ where /// Push a new location. /// /// The previous location will be available to go back to. - pub fn push(&self, target: NavigationTarget) -> Option> { + pub fn push(&self, target: impl Into>) -> Option> { + let target = target.into(); let mut state = self.state_mut(); match target { NavigationTarget::Internal(p) => state.history.push(p), @@ -163,7 +164,8 @@ where /// Replace the current location. /// /// The previous location will **not** be available to go back to. - pub fn replace(&self, target: NavigationTarget) -> Option> { + pub fn replace(&self, target: impl Into>) -> Option> { + let target = target.into(); let mut state = self.state_mut(); match target { NavigationTarget::Internal(p) => state.history.replace(p), @@ -264,7 +266,7 @@ where // struct TestHistory {} // impl HistoryProvider for TestHistory { -// fn current_path(&self) -> String { +// fn current_route(&self) -> String { // todo!() // } @@ -758,7 +760,7 @@ where // struct TestHistory {} // impl HistoryProvider for TestHistory { -// fn current_path(&self) -> String { +// fn current_route(&self) -> String { // String::from("/") // } diff --git a/packages/router/src/history/memory.rs b/packages/router/src/history/memory.rs index abd0a90d8..08949f8ea 100644 --- a/packages/router/src/history/memory.rs +++ b/packages/router/src/history/memory.rs @@ -18,16 +18,31 @@ where /// Create a [`MemoryHistory`] starting at `path`. /// /// ```rust - /// # use dioxus_router_core::history::{HistoryProvider, MemoryHistory}; - /// let mut history = MemoryHistory::with_initial_path("/some/path").unwrap(); - /// assert_eq!(history.current_path(), "/some/path"); + /// # use dioxus_router::history::{HistoryProvider, MemoryHistory}; + /// # use dioxus_router::prelude::*; + /// # use serde::{Deserialize, Serialize}; + /// # use dioxus::prelude::*; + /// # #[inline_props] + /// # fn Index(cx: Scope) -> Element { todo!() } + /// # #[inline_props] + /// # fn OtherPage(cx: Scope) -> Element { todo!() } + /// #[derive(Clone, Serialize, Deserialize, Routable, Debug, PartialEq)] + /// enum Route { + /// #[route("/")] + /// Index {}, + /// #[route("/some-other-page")] + /// OtherPage {}, + /// } + /// + /// let mut history = MemoryHistory::::with_initial_path("/").unwrap(); + /// assert_eq!(history.current_route(), Route::Index {}); /// assert_eq!(history.can_go_back(), false); /// ``` - pub fn with_initial_path(path: impl Into) -> Result::Err> { - let path = path.into(); + pub fn with_initial_path(path: impl AsRef) -> Result::Err> { + let path = path.as_ref(); Ok(Self { - current: R::from_str(&path)?, + current: R::from_str(path)?, ..Default::default() }) } @@ -83,148 +98,3 @@ impl HistoryProvider for MemoryHistory { self.current = path; } } - -// #[cfg(test)] -// mod tests { -// use super::*; - -// #[test] -// fn default() { -// let mem = MemoryHistory::default(); -// assert_eq!(mem.current, Url::parse(INITIAL_URL).unwrap()); -// assert_eq!(mem.history, Vec::::new()); -// assert_eq!(mem.future, Vec::::new()); -// } - -// #[test] -// fn with_initial_path() { -// let mem = MemoryHistory::with_initial_path("something").unwrap(); -// assert_eq!( -// mem.current, -// Url::parse(&format!("{INITIAL_URL}something")).unwrap() -// ); -// assert_eq!(mem.history, Vec::::new()); -// assert_eq!(mem.future, Vec::::new()); -// } - -// #[test] -// fn with_initial_path_with_leading_slash() { -// let mem = MemoryHistory::with_initial_path("/something").unwrap(); -// assert_eq!( -// mem.current, -// Url::parse(&format!("{INITIAL_URL}something")).unwrap() -// ); -// assert_eq!(mem.history, Vec::::new()); -// assert_eq!(mem.future, Vec::::new()); -// } - -// #[test] -// fn can_go_back() { -// let mut mem = MemoryHistory::default(); -// assert!(!mem.can_go_back()); - -// mem.push(String::from("/test")); -// assert!(mem.can_go_back()); -// } - -// #[test] -// fn go_back() { -// let mut mem = MemoryHistory::default(); -// mem.push(String::from("/test")); -// mem.go_back(); - -// assert_eq!(mem.current, Url::parse(INITIAL_URL).unwrap()); -// assert!(mem.history.is_empty()); -// assert_eq!(mem.future, vec![format!("{INITIAL_URL}test")]); -// } - -// #[test] -// fn can_go_forward() { -// let mut mem = MemoryHistory::default(); -// assert!(!mem.can_go_forward()); - -// mem.push(String::from("/test")); -// mem.go_back(); - -// assert!(mem.can_go_forward()); -// } - -// #[test] -// fn go_forward() { -// let mut mem = MemoryHistory::default(); -// mem.push(String::from("/test")); -// mem.go_back(); -// mem.go_forward(); - -// assert_eq!( -// mem.current, -// Url::parse(&format!("{INITIAL_URL}test")).unwrap() -// ); -// assert_eq!(mem.history, vec![INITIAL_URL.to_string()]); -// assert!(mem.future.is_empty()); -// } - -// #[test] -// fn push() { -// let mut mem = MemoryHistory::default(); -// mem.push(String::from("/test")); - -// assert_eq!( -// mem.current, -// Url::parse(&format!("{INITIAL_URL}test")).unwrap() -// ); -// assert_eq!(mem.history, vec![INITIAL_URL.to_string()]); -// assert!(mem.future.is_empty()); -// } - -// #[test] -// #[should_panic = r#"cannot navigate to paths starting with "//": //test"#] -// #[cfg(debug_assertions)] -// fn push_debug() { -// let mut mem = MemoryHistory::default(); -// mem.push(String::from("//test")); -// } - -// #[test] -// #[cfg(not(debug_assertions))] -// fn push_release() { -// let mut mem = MemoryHistory::default(); -// mem.push(String::from("//test")); - -// assert_eq!(mem.current, Url::parse(INITIAL_URL).unwrap()); -// assert!(mem.history.is_empty()) -// } - -// #[test] -// fn replace() { -// let mut mem = MemoryHistory::default(); -// mem.push(String::from("/test")); -// mem.push(String::from("/other")); -// mem.go_back(); -// mem.replace(String::from("/replace")); - -// assert_eq!( -// mem.current, -// Url::parse(&format!("{INITIAL_URL}replace")).unwrap() -// ); -// assert_eq!(mem.history, vec![INITIAL_URL.to_string()]); -// assert_eq!(mem.future, vec![format!("{INITIAL_URL}other")]); -// } - -// #[test] -// #[should_panic = r#"cannot navigate to paths starting with "//": //test"#] -// #[cfg(debug_assertions)] -// fn replace_debug() { -// let mut mem = MemoryHistory::default(); -// mem.replace(String::from("//test")); -// } - -// #[test] -// #[cfg(not(debug_assertions))] -// fn replace_release() { -// let mut mem = MemoryHistory::default(); -// mem.replace(String::from("//test")); - -// assert_eq!(mem.current, Url::parse(INITIAL_URL).unwrap()); -// } -// } diff --git a/packages/router/src/history/mod.rs b/packages/router/src/history/mod.rs index a8cffc405..0b04b4ed4 100644 --- a/packages/router/src/history/mod.rs +++ b/packages/router/src/history/mod.rs @@ -31,7 +31,7 @@ pub(crate) mod web_scroll; /// An integration with some kind of navigation history. /// /// Depending on your use case, your implementation may deviate from the described procedure. This -/// is fine, as long as both `current_path` and `current_query` match the described format. +/// is fine, as long as both `current_route` and `current_query` match the described format. /// /// However, you should document all deviations. Also, make sure the navigation is user-friendly. /// The described behaviors are designed to mimic a web browser, which most users should already @@ -42,12 +42,26 @@ pub trait HistoryProvider { /// **Must start** with `/`. **Must _not_ contain** the prefix. /// /// ```rust - /// # use dioxus_router_core::history::{HistoryProvider, MemoryHistory}; - /// let mut history = MemoryHistory::default(); - /// assert_eq!(history.current_path(), "/"); + /// # use dioxus_router::history::{HistoryProvider, MemoryHistory}; + /// # use dioxus_router::prelude::*; + /// # use serde::{Deserialize, Serialize}; + /// # use dioxus::prelude::*; + /// # #[inline_props] + /// # fn Index(cx: Scope) -> Element { todo!() } + /// # #[inline_props] + /// # fn OtherPage(cx: Scope) -> Element { todo!() } + /// #[derive(Clone, Serialize, Deserialize, Routable, Debug, PartialEq)] + /// enum Route { + /// #[route("/")] + /// Index {}, + /// #[route("/some-other-page")] + /// OtherPage {}, + /// } + /// let mut history = MemoryHistory::::default(); + /// assert_eq!(history.current_route().to_string(), "/"); /// - /// history.push(String::from("/path")); - /// assert_eq!(history.current_path(), "/path"); + /// history.push(Route::OtherPage {}); + /// assert_eq!(history.current_route().to_string(), "/some-other-page"); /// ``` #[must_use] fn current_route(&self) -> R; @@ -68,11 +82,21 @@ pub trait HistoryProvider { /// If a [`HistoryProvider`] cannot know this, it should return [`true`]. /// /// ```rust - /// # use dioxus_router_core::history::{HistoryProvider, MemoryHistory}; - /// let mut history = MemoryHistory::default(); + /// # use dioxus_router::history::{HistoryProvider, MemoryHistory}; + /// # use dioxus_router::prelude::*; + /// # use serde::{Deserialize, Serialize}; + /// # use dioxus::prelude::*; + /// # #[inline_props] + /// # fn Index(cx: Scope) -> Element { todo!() } + /// #[derive(Clone, Serialize, Deserialize, Routable, Debug, PartialEq)] + /// enum Route { + /// #[route("/")] + /// Index {}, + /// } + /// let mut history = MemoryHistory::::default(); /// assert_eq!(history.can_go_back(), false); /// - /// history.push(String::from("/some-other-page")); + /// history.push(Route::Index {}); /// assert_eq!(history.can_go_back(), true); /// ``` #[must_use] @@ -86,18 +110,32 @@ pub trait HistoryProvider { /// might be called, even if `can_go_back` returns [`false`]. /// /// ```rust - /// # use dioxus_router_core::history::{HistoryProvider, MemoryHistory}; - /// let mut history = MemoryHistory::default(); - /// assert_eq!(history.current_path(), "/"); + /// # use dioxus_router::history::{HistoryProvider, MemoryHistory}; + /// # use dioxus_router::prelude::*; + /// # use serde::{Deserialize, Serialize}; + /// # use dioxus::prelude::*; + /// # #[inline_props] + /// # fn Index(cx: Scope) -> Element { todo!() } + /// # #[inline_props] + /// # fn OtherPage(cx: Scope) -> Element { todo!() } + /// #[derive(Clone, Serialize, Deserialize, Routable, Debug, PartialEq)] + /// enum Route { + /// #[route("/")] + /// Index {}, + /// #[route("/some-other-page")] + /// OtherPage {}, + /// } + /// let mut history = MemoryHistory::::default(); + /// assert_eq!(history.current_route().to_string(), "/"); /// /// history.go_back(); - /// assert_eq!(history.current_path(), "/"); + /// assert_eq!(history.current_route().to_string(), "/"); /// - /// history.push(String::from("/some-other-page")); - /// assert_eq!(history.current_path(), "/some-other-page"); + /// history.push(Route::OtherPage {}); + /// assert_eq!(history.current_route().to_string(), "/some-other-page"); /// /// history.go_back(); - /// assert_eq!(history.current_path(), "/"); + /// assert_eq!(history.current_route().to_string(), "/"); /// ``` fn go_back(&mut self); @@ -106,11 +144,25 @@ pub trait HistoryProvider { /// If a [`HistoryProvider`] cannot know this, it should return [`true`]. /// /// ```rust - /// # use dioxus_router_core::history::{HistoryProvider, MemoryHistory}; - /// let mut history = MemoryHistory::default(); + /// # use dioxus_router::history::{HistoryProvider, MemoryHistory}; + /// # use dioxus_router::prelude::*; + /// # use serde::{Deserialize, Serialize}; + /// # use dioxus::prelude::*; + /// # #[inline_props] + /// # fn Index(cx: Scope) -> Element { todo!() } + /// # #[inline_props] + /// # fn OtherPage(cx: Scope) -> Element { todo!() } + /// #[derive(Clone, Serialize, Deserialize, Routable, Debug, PartialEq)] + /// enum Route { + /// #[route("/")] + /// Index {}, + /// #[route("/some-other-page")] + /// OtherPage {}, + /// } + /// let mut history = MemoryHistory::::default(); /// assert_eq!(history.can_go_forward(), false); /// - /// history.push(String::from("/some-other-page")); + /// history.push(Route::OtherPage {}); /// assert_eq!(history.can_go_forward(), false); /// /// history.go_back(); @@ -127,16 +179,30 @@ pub trait HistoryProvider { /// might be called, even if `can_go_forward` returns [`false`]. /// /// ```rust - /// # use dioxus_router_core::history::{HistoryProvider, MemoryHistory}; - /// let mut history = MemoryHistory::default(); - /// history.push(String::from("/some-other-page")); - /// assert_eq!(history.current_path(), "/some-other-page"); + /// # use dioxus_router::history::{HistoryProvider, MemoryHistory}; + /// # use dioxus_router::prelude::*; + /// # use serde::{Deserialize, Serialize}; + /// # use dioxus::prelude::*; + /// # #[inline_props] + /// # fn Index(cx: Scope) -> Element { todo!() } + /// # #[inline_props] + /// # fn OtherPage(cx: Scope) -> Element { todo!() } + /// #[derive(Clone, Serialize, Deserialize, Routable, Debug, PartialEq)] + /// enum Route { + /// #[route("/")] + /// Index {}, + /// #[route("/some-other-page")] + /// OtherPage {}, + /// } + /// let mut history = MemoryHistory::::default(); + /// history.push(Route::OtherPage {}); + /// assert_eq!(history.current_route(), Route::OtherPage {}); /// /// history.go_back(); - /// assert_eq!(history.current_path(), "/"); + /// assert_eq!(history.current_route(), Route::Index {}); /// /// history.go_forward(); - /// assert_eq!(history.current_path(), "/some-other-page"); + /// assert_eq!(history.current_route(), Route::OtherPage {}); /// ``` fn go_forward(&mut self); @@ -148,12 +214,26 @@ pub trait HistoryProvider { /// 3. Clear the navigation future. /// /// ```rust - /// # use dioxus_router_core::history::{HistoryProvider, MemoryHistory}; - /// let mut history = MemoryHistory::default(); - /// assert_eq!(history.current_path(), "/"); + /// # use dioxus_router::history::{HistoryProvider, MemoryHistory}; + /// # use dioxus_router::prelude::*; + /// # use serde::{Deserialize, Serialize}; + /// # use dioxus::prelude::*; + /// # #[inline_props] + /// # fn Index(cx: Scope) -> Element { todo!() } + /// # #[inline_props] + /// # fn OtherPage(cx: Scope) -> Element { todo!() } + /// #[derive(Clone, Serialize, Deserialize, Routable, Debug, PartialEq)] + /// enum Route { + /// #[route("/")] + /// Index {}, + /// #[route("/some-other-page")] + /// OtherPage {}, + /// } + /// let mut history = MemoryHistory::::default(); + /// assert_eq!(history.current_route(), Route::Index {}); /// - /// history.push(String::from("/some-other-page")); - /// assert_eq!(history.current_path(), "/some-other-page"); + /// history.push(Route::OtherPage {}); + /// assert_eq!(history.current_route(), Route::OtherPage {}); /// assert!(history.can_go_back()); /// ``` fn push(&mut self, route: R); @@ -165,12 +245,26 @@ pub trait HistoryProvider { /// untouched. /// /// ```rust - /// # use dioxus_router_core::history::{HistoryProvider, MemoryHistory}; - /// let mut history = MemoryHistory::default(); - /// assert_eq!(history.current_path(), "/"); + /// # use dioxus_router::history::{HistoryProvider, MemoryHistory}; + /// # use dioxus_router::prelude::*; + /// # use serde::{Deserialize, Serialize}; + /// # use dioxus::prelude::*; + /// # #[inline_props] + /// # fn Index(cx: Scope) -> Element { todo!() } + /// # #[inline_props] + /// # fn OtherPage(cx: Scope) -> Element { todo!() } + /// #[derive(Clone, Serialize, Deserialize, Routable, Debug, PartialEq)] + /// enum Route { + /// #[route("/")] + /// Index {}, + /// #[route("/some-other-page")] + /// OtherPage {}, + /// } + /// let mut history = MemoryHistory::::default(); + /// assert_eq!(history.current_route(), Route::Index {}); /// - /// history.replace(String::from("/some-other-page")); - /// assert_eq!(history.current_path(), "/some-other-page"); + /// history.replace(Route::OtherPage {}); + /// assert_eq!(history.current_route(), Route::OtherPage {}); /// assert!(!history.can_go_back()); /// ``` fn replace(&mut self, path: R); diff --git a/packages/router/src/history/web.rs b/packages/router/src/history/web.rs index ec085f88c..9f4ccb486 100644 --- a/packages/router/src/history/web.rs +++ b/packages/router/src/history/web.rs @@ -28,7 +28,7 @@ struct WebHistoryState { scroll: ScrollPosition, } -/// A [`HistoryProvider`] that integrates with a browser via the [History API]. +/// A [`HistoryProvider`] that integrates with a browser via the [History API](https://developer.mozilla.org/en-US/docs/Web/API/History_API). /// /// # Prefix /// This [`HistoryProvider`] supports a prefix, which can be used for web apps that aren't located @@ -40,8 +40,6 @@ struct WebHistoryState { /// /// Application developers are responsible for not rendering the router if the prefix is not present /// in the URL. Otherwise, if a router navigation is triggered, the prefix will be added. -/// -/// [History API]: https://developer.mozilla.org/en-US/docs/Web/API/History_API pub struct WebHistory { do_scroll_restoration: bool, history: History, diff --git a/packages/router/src/history/web_hash.rs b/packages/router/src/history/web_hash.rs index 224afd411..c910b1c97 100644 --- a/packages/router/src/history/web_hash.rs +++ b/packages/router/src/history/web_hash.rs @@ -17,10 +17,8 @@ const INITIAL_URL: &str = "dioxus-router-core://initial_url.invalid/"; /// /// Early web applications used the hash to store the current path because there was no other way /// for them to interact with the history without triggering a browser navigation, as the -/// [History API] did not yet exist. While this implementation could have been written that way, it +/// [History API](https://developer.mozilla.org/en-US/docs/Web/API/History_API) did not yet exist. While this implementation could have been written that way, it /// was not, because no browser supports WebAssembly without the [History API]. -/// -/// [History API]: https://developer.mozilla.org/en-US/docs/Web/API/History_API pub struct WebHashHistory { do_scroll_restoration: bool, history: History, diff --git a/packages/router/src/hooks/use_route.rs b/packages/router/src/hooks/use_route.rs index b6a1f0bf5..6fbddcccc 100644 --- a/packages/router/src/hooks/use_route.rs +++ b/packages/router/src/hooks/use_route.rs @@ -1,17 +1,13 @@ use dioxus::prelude::ScopeState; -use crate::{routable::Routable, utils::use_router_internal::use_router_internal}; +use crate::prelude::*; +use crate::utils::use_router_internal::use_router_internal; /// A hook that provides access to information about the current routing location. /// /// # Return values -/// - [`RouterError::NotInsideRouter`], when the calling component is not nested within another -/// component calling the [`use_router`] hook. -/// - Otherwise [`Ok`]. -/// -/// # Important usage information -/// Make sure to [`drop`] the returned [`RwLockReadGuard`] when done rendering. Otherwise the router -/// will be frozen. +/// - None, when not called inside a [`GenericRouter`] component. +/// - Otherwise the current route. /// /// # Panic /// - When the calling component is not nested within another component calling the [`use_router`] @@ -20,28 +16,25 @@ use crate::{routable::Routable, utils::use_router_internal::use_router_internal} /// # Example /// ```rust /// # use dioxus::prelude::*; +/// # use serde::{Deserialize, Serialize}; /// # use dioxus_router::{history::*, prelude::*}; -/// fn App(cx: Scope) -> Element { -/// use_router( -/// &cx, -/// &|| RouterConfiguration { -/// synchronous: true, // asynchronicity not needed for doc test -/// history: Box::new(MemoryHistory::with_initial_path("/some/path").unwrap()), -/// ..Default::default() -/// }, -/// &|| Segment::empty() -/// ); /// +/// #[derive(Clone, Serialize, Deserialize, Routable)] +/// enum Route { +/// #[route("/")] +/// Index {}, +/// } +/// +/// fn App(cx: Scope) -> Element { /// render! { /// h1 { "App" } -/// Content { } +/// Router {} /// } /// } /// -/// fn Content(cx: Scope) -> Element { -/// let state = use_route(&cx)?; -/// let path = state.path.clone(); -/// +/// #[inline_props] +/// fn Index(cx: Scope) -> Element { +/// let path = use_route(&cx).unwrap(); /// render! { /// h2 { "Current Path" } /// p { "{path}" } @@ -50,11 +43,9 @@ use crate::{routable::Routable, utils::use_router_internal::use_router_internal} /// # /// # let mut vdom = VirtualDom::new(App); /// # let _ = vdom.rebuild(); -/// # assert_eq!(dioxus_ssr::render(&vdom), "

App

Current Path

/some/path

") +/// # assert_eq!(dioxus_ssr::render(&vdom), "

App

Current Path

/

") /// ``` -/// -/// [`use_router`]: crate::hooks::use_router -pub fn use_route(cx: &ScopeState) -> Option { +pub fn use_generic_route(cx: &ScopeState) -> Option { match use_router_internal(cx) { Some(r) => Some(r.current()), None => { diff --git a/packages/router/src/hooks/use_router.rs b/packages/router/src/hooks/use_router.rs index d0dba00f4..666426517 100644 --- a/packages/router/src/hooks/use_router.rs +++ b/packages/router/src/hooks/use_router.rs @@ -5,7 +5,50 @@ use crate::{ utils::use_router_internal::use_router_internal, }; -/// A hook that provides access to information about the router +/// A hook that provides access to information about the router. The Router will define a version of this hook with an explicit type. +/// +/// ```rust +/// # use dioxus::prelude::*; +/// # use dioxus_router::{history::*, prelude::*}; +/// # use serde::{Deserialize, Serialize}; +/// #[derive(Clone, Serialize, Deserialize, Routable)] +/// enum Route { +/// #[route("/")] +/// Index {}, +/// #[route("/:id")] +/// Dynamic { id: usize }, +/// } +/// +/// fn App(cx: Scope) -> Element { +/// render! { +/// Router {} +/// } +/// } +/// +/// #[inline_props] +/// fn Index(cx: Scope) -> Element { +/// let router = use_router(&cx); +/// +/// render! { +/// button { +/// onclick: move |_| { router.push(Route::Dynamic { id: 1234 }); }, +/// "Go to /1234" +/// } +/// } +/// } +/// +/// #[inline_props] +/// fn Dynamic(cx: Scope, id: usize) -> Element { +/// render! { +/// p { +/// "Current ID: {id}" +/// } +/// } +/// } +/// +/// # let mut vdom = VirtualDom::new(App); +/// # let _ = vdom.rebuild(); +/// ``` pub fn use_generic_router(cx: &ScopeState) -> &GenericRouterContext { use_router_internal(cx) .as_ref() diff --git a/packages/router/src/navigation.rs b/packages/router/src/navigation.rs index ffb75eeb5..069469eb4 100644 --- a/packages/router/src/navigation.rs +++ b/packages/router/src/navigation.rs @@ -12,18 +12,42 @@ pub enum NavigationTarget { /// An internal path that the router can navigate to by itself. /// /// ```rust - /// # use dioxus_router_core::navigation::NavigationTarget; - /// let explicit = NavigationTarget::Internal(String::from("/internal")); - /// let implicit: NavigationTarget = "/internal".into(); + /// # use dioxus::prelude::*; + /// # use dioxus_router::prelude::*; + /// # use dioxus_router::navigation::NavigationTarget; + /// # use serde::{Deserialize, Serialize}; + /// # #[inline_props] + /// # fn Index(cx: Scope) -> Element { + /// # todo!() + /// # } + /// #[derive(Clone, Serialize, Deserialize, Routable, PartialEq, Debug)] + /// enum Route { + /// #[route("/")] + /// Index {}, + /// } + /// let explicit = NavigationTarget::Internal(Route::Index {}); + /// let implicit: NavigationTarget:: = "/".into(); /// assert_eq!(explicit, implicit); /// ``` Internal(R), /// An external target that the router doesn't control. /// /// ```rust - /// # use dioxus_router_core::navigation::NavigationTarget; - /// let explicit = NavigationTarget::External(String::from("https://dioxuslabs.com/")); - /// let implicit: NavigationTarget = "https://dioxuslabs.com/".into(); + /// # use dioxus::prelude::*; + /// # use dioxus_router::prelude::*; + /// # use dioxus_router::navigation::NavigationTarget; + /// # use serde::{Deserialize, Serialize}; + /// # #[inline_props] + /// # fn Index(cx: Scope) -> Element { + /// # todo!() + /// # } + /// #[derive(Clone, Serialize, Deserialize, Routable, PartialEq, Debug)] + /// enum Route { + /// #[route("/")] + /// Index {}, + /// } + /// let explicit = NavigationTarget::::External(String::from("https://dioxuslabs.com/")); + /// let implicit: NavigationTarget:: = "https://dioxuslabs.com/".into(); /// assert_eq!(explicit, implicit); /// ``` External(String), diff --git a/packages/router/src/routable.rs b/packages/router/src/routable.rs index 71ab8ffc6..b83560542 100644 --- a/packages/router/src/routable.rs +++ b/packages/router/src/routable.rs @@ -93,7 +93,12 @@ impl> FromRouteSegments for I { } } -/// Something that can be routed to +/// Something that can be: +/// 1) Converted from a route +/// 2) Converted to a route +/// 3) Rendered as a component +/// +/// This trait can be derived using the `#[derive(Routable)]` macro pub trait Routable: std::fmt::Display + std::str::FromStr + Clone + 'static { /// The error that can occur when parsing a route const SITE_MAP: &'static [SiteMapSegment]; diff --git a/packages/router/src/router_cfg.rs b/packages/router/src/router_cfg.rs index 28f8efefc..040978333 100644 --- a/packages/router/src/router_cfg.rs +++ b/packages/router/src/router_cfg.rs @@ -1,7 +1,9 @@ use crate::contexts::router::RoutingCallback; -use crate::history::{HistoryProvider, MemoryHistory}; +use crate::history::HistoryProvider; +use crate::prelude::*; use crate::routable::Routable; use dioxus::prelude::*; +use serde::{de::DeserializeOwned, Serialize}; use crate::prelude::default_errors::{ FailureExternalNavigation, FailureNamedNavigation, FailureRedirectionLimit, @@ -11,9 +13,20 @@ use crate::prelude::default_errors::{ /// /// This implements [`Default`], so you can use it like this: /// ```rust,no_run -/// # use dioxus_router::prelude::RouterConfiguration; +/// # use dioxus_router::prelude::*; +/// # use serde::{Deserialize, Serialize}; +/// # use dioxus::prelude::*; +/// # #[inline_props] +/// # fn Index(cx: Scope) -> Element { +/// # todo!() +/// # } +/// #[derive(Clone, Serialize, Deserialize, Routable)] +/// enum Route { +/// #[route("/")] +/// Index {}, +/// } /// let cfg = RouterConfiguration { -/// synchronous: false, +/// history: Box::>::default(), /// ..Default::default() /// }; /// ``` @@ -22,19 +35,19 @@ pub struct RouterConfiguration { /// /// Defaults to a router-internal component called `FailureExternalNavigation`. It is not part /// of the public API. Do not confuse it with - /// [`dioxus_router_core::prelude::FailureExternalNavigation`]. + /// [`dioxus_router::prelude::FailureExternalNavigation`]. pub failure_external_navigation: fn(Scope) -> Element, /// A component to render when a named navigation fails. /// /// Defaults to a router-internal component called `FailureNamedNavigation`. It is not part of /// the public API. Do not confuse it with - /// [`dioxus_router_core::prelude::FailureNamedNavigation`]. + /// [`dioxus_router::prelude::FailureNamedNavigation`]. pub failure_named_navigation: fn(Scope) -> Element, /// A component to render when the redirect limit is reached. /// /// Defaults to a router-internal component called `FailureRedirectionLimit`. It is not part of /// the public API. Do not confuse it with - /// [`dioxus_router_core::prelude::FailureRedirectionLimit`]. + /// [`dioxus_router::prelude::FailureRedirectionLimit`]. pub failure_redirection_limit: fn(Scope) -> Element, /// The [`HistoryProvider`] the router should use. /// @@ -45,7 +58,7 @@ pub struct RouterConfiguration { /// The callback is invoked after the routing is updated, but before components and hooks are /// updated. /// - /// If the callback returns a [`NavigationTarget`] the router will replace the current location + /// If the callback returns a [`dioxus_router::navigation::NavigationTarget`] the router will replace the current location /// with it. If no navigation failure was triggered, the router will then updated dependent /// components and hooks. /// @@ -53,12 +66,10 @@ pub struct RouterConfiguration { /// navigation failure occurs. /// /// Defaults to [`None`]. - /// - /// [`NavigationTarget`]: dioxus_router_core::navigation::NavigationTarget pub on_update: Option>, } -impl Default for RouterConfiguration +impl Default for RouterConfiguration where ::Err: std::fmt::Display, { diff --git a/packages/router/src/utils/sitemap.rs b/packages/router/src/utils/sitemap.rs deleted file mode 100644 index c3fd731c7..000000000 --- a/packages/router/src/utils/sitemap.rs +++ /dev/null @@ -1,145 +0,0 @@ -use std::collections::BTreeMap; - -use urlencoding::encode; - -pub fn gen_sitemap(seg: &Segment, current: &str, map: &mut Vec) { - for (p, r) in &seg.fixed { - let current = format!("{current}/{p}"); - map.push(current.clone()); - if let Some(n) = &r.nested { - gen_sitemap(n, ¤t, map); - } - } - - for (_, r) in &seg.matching { - let current = format!("{current}/\\{}", r.key); - map.push(current.clone()); - if let Some(n) = &r.nested { - gen_sitemap(n, ¤t, map); - } - } - - if let Some(r) = &seg.catch_all { - let current = format!("{current}/\\{}", r.key); - map.push(current.clone()); - if let Some(n) = &r.nested { - gen_sitemap(n, ¤t, map) - } - } -} - -pub fn gen_parameter_sitemap( - seg: &Segment, - parameters: &BTreeMap>, - current: &str, - map: &mut Vec, -) { - for (p, r) in &seg.fixed { - let current = format!("{current}/{p}"); - map.push(current.clone()); - if let Some(n) = &r.nested { - gen_parameter_sitemap(n, parameters, ¤t, map); - } - } - - for (m, r) in &seg.matching { - if let Some(rp) = parameters.get(&r.key) { - for p in rp { - if m.matches(p) { - let current = format!("{current}/{}", encode(p).into_owned()); - map.push(current.clone()); - if let Some(n) = &r.nested { - gen_parameter_sitemap(n, parameters, ¤t, map); - } - } - } - } - } - - if let Some(r) = &seg.catch_all { - if let Some(rp) = parameters.get(&r.key) { - for p in rp { - let current = format!("{current}/{}", encode(p).into_owned()); - map.push(current.clone()); - if let Some(n) = &r.nested { - gen_parameter_sitemap(n, parameters, ¤t, map); - } - } - } - } -} - -#[cfg(test)] -mod tests { - use crate::routes::{ParameterRoute, Route}; - - use super::*; - - fn test_segment() -> Segment<&'static str> { - Segment::empty() - .fixed( - "fixed", - Route::empty().nested(Segment::empty().fixed("nested", Route::empty())), - ) - .matching( - String::from("m1"), - ParameterRoute::empty::().nested( - Segment::empty().matching(String::from("n2"), ParameterRoute::empty::()), - ), - ) - .matching(String::from("no match"), ParameterRoute::empty::()) - .matching(String::from("no parameter"), ParameterRoute::empty::()) - .catch_all( - ParameterRoute::empty::() - .nested(Segment::empty().catch_all(ParameterRoute::empty::())), - ) - } - - #[test] - fn sitemap() { - let mut result = Vec::new(); - result.push(String::from("/")); - gen_sitemap(&test_segment(), "", &mut result); - - assert_eq!( - result, - vec![ - "/", - "/fixed", - "/fixed/nested", - "/\\u8", - "/\\u8/\\u16", - "/\\u32", - "/\\u64", - "/\\u32", - "/\\u32/\\u16" - ] - ); - } - - #[test] - fn sitemap_with_parameters() { - let mut parameters = BTreeMap::new(); - parameters.insert(Name::of::(), vec!["m1".to_string(), "m2".to_string()]); - parameters.insert(Name::of::(), vec!["n1".to_string(), "n2".to_string()]); - parameters.insert(Name::of::(), vec!["catch all".to_string()]); - - let mut result = Vec::new(); - result.push(String::from("/")); - gen_parameter_sitemap(&test_segment(), ¶meters, "", &mut result); - - assert_eq!( - result, - vec![ - "/", - "/fixed", - "/fixed/nested", - "/m1", - "/m1/n2", - "/catch%20all", - "/catch%20all/n1", - "/catch%20all/n2" - ] - ); - } -} diff --git a/packages/router/src/utils/use_router_internal.rs b/packages/router/src/utils/use_router_internal.rs index ee84a452e..898ef539f 100644 --- a/packages/router/src/utils/use_router_internal.rs +++ b/packages/router/src/utils/use_router_internal.rs @@ -1,6 +1,6 @@ use dioxus::prelude::{ScopeId, ScopeState}; -use crate::{contexts::router::GenericRouterContext, routable::Routable}; +use crate::{contexts::router::GenericRouterContext, prelude::*}; /// A private hook to subscribe to the router. /// @@ -8,10 +8,8 @@ use crate::{contexts::router::GenericRouterContext, routable::Routable}; /// single component, but not recommended. Multiple subscriptions will be discarded. /// /// # Return values -/// - [`None`], when the current component isn't a descendant of a [`use_router`] component. +/// - [`None`], when the current component isn't a descendant of a [`GenericRouter`] component. /// - Otherwise [`Some`]. -/// -/// [`use_router`]: crate::hooks::use_router pub(crate) fn use_router_internal( cx: &ScopeState, ) -> &Option> { diff --git a/packages/router/tests/via_ssr/link.rs b/packages/router/tests/via_ssr/link.rs index 88d53d923..d6f8527ea 100644 --- a/packages/router/tests/via_ssr/link.rs +++ b/packages/router/tests/via_ssr/link.rs @@ -1,10 +1,10 @@ #![allow(non_snake_case)] - use dioxus::prelude::*; use dioxus_router::prelude::*; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::str::FromStr; -fn prepare() -> String +fn prepare() -> String where ::Err: std::fmt::Display, { @@ -28,7 +28,7 @@ where } } - fn App(cx: Scope>) -> Element + fn App(cx: Scope>) -> Element where ::Err: std::fmt::Display, { @@ -46,7 +46,7 @@ where #[test] fn href_internal() { - #[derive(Routable, Clone)] + #[derive(Routable, Clone, Serialize, Deserialize)] enum Route { #[route("/")] Root {}, @@ -84,7 +84,7 @@ fn href_internal() { #[test] fn href_external() { - #[derive(Routable, Clone)] + #[derive(Routable, Clone, Serialize, Deserialize)] enum Route { #[route("/")] Root {}, @@ -122,7 +122,7 @@ fn href_external() { #[test] fn with_class() { - #[derive(Routable, Clone)] + #[derive(Routable, Clone, Serialize, Deserialize)] enum Route { #[route("/")] Root {}, @@ -161,7 +161,7 @@ fn with_class() { #[test] fn with_active_class_active() { - #[derive(Routable, Clone)] + #[derive(Routable, Clone, Serialize, Deserialize)] enum Route { #[route("/")] Root {}, @@ -194,7 +194,7 @@ fn with_active_class_active() { #[test] fn with_active_class_inactive() { - #[derive(Routable, Clone)] + #[derive(Routable, Clone, Serialize, Deserialize)] enum Route { #[route("/")] Root {}, @@ -234,7 +234,7 @@ fn with_active_class_inactive() { #[test] fn with_id() { - #[derive(Routable, Clone)] + #[derive(Routable, Clone, Serialize, Deserialize)] enum Route { #[route("/")] Root {}, @@ -273,7 +273,7 @@ fn with_id() { #[test] fn with_new_tab() { - #[derive(Routable, Clone)] + #[derive(Routable, Clone, Serialize, Deserialize)] enum Route { #[route("/")] Root {}, @@ -312,7 +312,7 @@ fn with_new_tab() { #[test] fn with_new_tab_external() { - #[derive(Routable, Clone)] + #[derive(Routable, Clone, Serialize, Deserialize)] enum Route { #[route("/")] Root {}, @@ -344,7 +344,7 @@ fn with_new_tab_external() { #[test] fn with_rel() { - #[derive(Routable, Clone)] + #[derive(Routable, Clone, Serialize, Deserialize)] enum Route { #[route("/")] Root {}, diff --git a/packages/router/tests/via_ssr/outlet.rs b/packages/router/tests/via_ssr/outlet.rs index 9d83e3cf3..7eeb46299 100644 --- a/packages/router/tests/via_ssr/outlet.rs +++ b/packages/router/tests/via_ssr/outlet.rs @@ -2,13 +2,14 @@ use dioxus::prelude::*; use dioxus_router::{history::MemoryHistory, prelude::*}; +use serde::{Deserialize, Serialize}; fn prepare(path: impl Into) -> VirtualDom { let mut vdom = VirtualDom::new_with_props(App, AppProps { path: path.into() }); let _ = vdom.rebuild(); return vdom; - #[derive(Routable, Clone)] + #[derive(Routable, Clone, Serialize, Deserialize)] #[rustfmt::skip] enum Route { #[route("/")]