mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-30 08:00:21 +00:00
update doc examples
This commit is contained in:
parent
35befa1784
commit
09cabe4e8b
22 changed files with 474 additions and 518 deletions
|
@ -53,8 +53,20 @@ pub fn routable(input: TokenStream) -> TokenStream {
|
||||||
dioxus_router::prelude::GenericLink(cx)
|
dioxus_router::prelude::GenericLink(cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
#vis fn use_router<R: dioxus_router::prelude::Routable + Clone>(cx: &dioxus::prelude::ScopeState) -> &dioxus_router::prelude::GenericRouterContext<R> {
|
#vis fn GoBackButton<'a>(cx: dioxus::prelude::Scope<'a, dioxus_router::prelude::GenericHistoryButtonProps<'a>>) -> dioxus::prelude::Element<'a> {
|
||||||
dioxus_router::prelude::use_generic_router::<R>(cx)
|
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
|
#error_type
|
||||||
|
|
|
@ -7,13 +7,10 @@
|
||||||
|
|
||||||
[crates-badge]: https://img.shields.io/crates/v/dioxus-router.svg
|
[crates-badge]: https://img.shields.io/crates/v/dioxus-router.svg
|
||||||
[crates-url]: https://crates.io/crates/dioxus-router
|
[crates-url]: https://crates.io/crates/dioxus-router
|
||||||
|
|
||||||
[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg
|
[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg
|
||||||
[mit-url]: https://github.com/dioxuslabs/dioxus/blob/master/LICENSE
|
[mit-url]: https://github.com/dioxuslabs/dioxus/blob/master/LICENSE
|
||||||
|
|
||||||
[actions-badge]: https://github.com/dioxuslabs/dioxus/actions/workflows/main.yml/badge.svg
|
[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
|
[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-badge]: https://img.shields.io/discord/899851952891002890.svg?logo=discord&style=flat-square
|
||||||
[discord-url]: https://discord.gg/XgGxMSkvUM
|
[discord-url]: https://discord.gg/XgGxMSkvUM
|
||||||
|
|
||||||
|
@ -29,37 +26,48 @@ interface similar to React Router, but takes advantage of types for more
|
||||||
expressiveness.
|
expressiveness.
|
||||||
|
|
||||||
```rust, no_run
|
```rust, no_run
|
||||||
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use dioxus_router::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 {
|
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! {
|
render! {
|
||||||
Outlet { }
|
Router { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
fn Index(cx: Scope) -> Element {
|
fn Index(cx: Scope) -> Element {
|
||||||
render! {
|
render! {
|
||||||
h1 { "Index" }
|
h1 { "Index" }
|
||||||
Link {
|
Link {
|
||||||
target: "/blog",
|
target: Route::BlogList {},
|
||||||
"Go to the blog"
|
"Go to the blog"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
fn Blog(cx: Scope) -> Element {
|
fn Blog(cx: Scope) -> Element {
|
||||||
render! {
|
render! {
|
||||||
h1 { "Blog" }
|
h1 { "Blog" }
|
||||||
|
@ -67,21 +75,23 @@ fn Blog(cx: Scope) -> Element {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
fn BlogList(cx: Scope) -> Element {
|
fn BlogList(cx: Scope) -> Element {
|
||||||
render! {
|
render! {
|
||||||
h2 { "List of blog posts" }
|
h2 { "List of blog posts" }
|
||||||
Link {
|
Link {
|
||||||
target: "/blog/1",
|
target: Route::BlogPost { blog_id: 0 },
|
||||||
"Blog post 1"
|
"Blog post 1"
|
||||||
}
|
}
|
||||||
Link {
|
Link {
|
||||||
target: "/blog/1",
|
target: Route::BlogPost { blog_id: 1 },
|
||||||
"Blog post 2"
|
"Blog post 2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn BlogPost(cx: Scope) -> Element {
|
#[inline_props]
|
||||||
|
fn BlogPost(cx: Scope, blog_id: usize) -> Element {
|
||||||
render! {
|
render! {
|
||||||
h2 { "Blog Post" }
|
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!
|
- Join the discord and ask questions!
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This project is licensed under the [MIT license].
|
This project is licensed under the [MIT license].
|
||||||
|
|
||||||
[mit license]: https://github.com/DioxusLabs/dioxus/blob/master/LICENSE-MIT
|
[mit license]: https://github.com/DioxusLabs/dioxus/blob/master/LICENSE-MIT
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
components::GenericLink, hooks::use_route, navigation::NavigationTarget, routable::Routable,
|
components::GenericLink, hooks::use_generic_route, navigation::NavigationTarget,
|
||||||
|
routable::Routable,
|
||||||
};
|
};
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
pub fn FailureExternalNavigation<R: Routable + Clone>(cx: Scope) -> Element {
|
pub fn FailureExternalNavigation<R: Routable + Clone>(cx: Scope) -> Element {
|
||||||
let href = use_route::<R>(cx).expect(
|
let href = use_generic_route::<R>(cx).expect(
|
||||||
"`FailureExternalNavigation` can only be mounted by the router itself, \
|
"`FailureExternalNavigation` can only be mounted by the router itself, \
|
||||||
since it is not exposed",
|
since it is not exposed",
|
||||||
);
|
);
|
||||||
|
@ -38,7 +39,9 @@ pub fn FailureNamedNavigation<R: Routable + Clone>(cx: Scope) -> Element {
|
||||||
"there is no guarantee."
|
"there is no guarantee."
|
||||||
}
|
}
|
||||||
GenericLink::<R> {
|
GenericLink::<R> {
|
||||||
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."
|
"Click here to try to fix the failure."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,7 +61,9 @@ pub fn FailureRedirectionLimit<R: Routable + Clone>(cx: Scope) -> Element {
|
||||||
"there is no guarantee."
|
"there is no guarantee."
|
||||||
}
|
}
|
||||||
GenericLink::<R> {
|
GenericLink::<R> {
|
||||||
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."
|
"Click here to try to fix the failure."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,41 +1,44 @@
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use log::error;
|
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`].
|
/// The properties for a [`GoBackButton`] or a [`GoForwardButton`].
|
||||||
#[derive(Debug, Props)]
|
#[derive(Debug, Props)]
|
||||||
pub struct HistoryButtonProps<'a> {
|
pub struct GenericHistoryButtonProps<'a> {
|
||||||
/// The children to render within the generated HTML button tag.
|
/// The children to render within the generated HTML button tag.
|
||||||
pub children: Element<'a>,
|
pub children: Element<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A button to go back through the navigation history. Similar to a browsers back button.
|
/// 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.
|
/// The button will disable itself if it is known that no prior history is available.
|
||||||
///
|
///
|
||||||
/// [`use_router`]: crate::hooks::use_router
|
|
||||||
///
|
|
||||||
/// # Panic
|
/// # 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.
|
/// hook, but only in debug builds.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use dioxus::prelude::*;
|
/// # use dioxus::prelude::*;
|
||||||
/// # use dioxus_router::prelude::*;
|
/// # use dioxus_router::prelude::*;
|
||||||
/// fn App(cx: Scope) -> Element {
|
/// # use serde::{Deserialize, Serialize};
|
||||||
/// use_router(
|
/// #[derive(Clone, Serialize, Deserialize, Routable)]
|
||||||
/// &cx,
|
/// enum Route {
|
||||||
/// &|| RouterConfiguration {
|
/// #[route("/")]
|
||||||
/// synchronous: true, // asynchronicity not needed for doc test
|
/// Index {},
|
||||||
/// ..Default::default()
|
/// }
|
||||||
/// },
|
|
||||||
/// &|| Segment::empty()
|
|
||||||
/// );
|
|
||||||
///
|
///
|
||||||
|
/// fn App(cx: Scope) -> Element {
|
||||||
|
/// render! {
|
||||||
|
/// Router {}
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// #[inline_props]
|
||||||
|
/// fn Index(cx: Scope) -> Element {
|
||||||
/// render! {
|
/// render! {
|
||||||
/// GoBackButton {
|
/// GoBackButton {
|
||||||
/// "go back"
|
/// "go back"
|
||||||
|
@ -51,8 +54,10 @@ pub struct HistoryButtonProps<'a> {
|
||||||
/// # );
|
/// # );
|
||||||
/// ```
|
/// ```
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
pub fn GoBackButton<'a, R: Routable>(cx: Scope<'a, HistoryButtonProps<'a>>) -> Element {
|
pub fn GenericGoBackButton<'a, R: Routable>(
|
||||||
let HistoryButtonProps { children } = cx.props;
|
cx: Scope<'a, GenericHistoryButtonProps<'a>>,
|
||||||
|
) -> Element {
|
||||||
|
let GenericHistoryButtonProps { children } = cx.props;
|
||||||
|
|
||||||
// hook up to router
|
// hook up to router
|
||||||
let router = match use_router_internal::<R>(cx) {
|
let router = match use_router_internal::<R>(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.
|
/// 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.
|
/// The button will disable itself if it is known that no later history is available.
|
||||||
///
|
///
|
||||||
/// [`use_router`]: crate::hooks::use_router
|
|
||||||
///
|
|
||||||
/// # Panic
|
/// # 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.
|
/// hook, but only in debug builds.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use dioxus::prelude::*;
|
/// # use dioxus::prelude::*;
|
||||||
/// # use dioxus_router::prelude::*;
|
/// # use dioxus_router::prelude::*;
|
||||||
/// fn App(cx: Scope) -> Element {
|
/// # use serde::{Deserialize, Serialize};
|
||||||
/// use_router(
|
/// #[derive(Clone, Serialize, Deserialize, Routable)]
|
||||||
/// &cx,
|
/// enum Route {
|
||||||
/// &|| RouterConfiguration {
|
/// #[route("/")]
|
||||||
/// synchronous: true, // asynchronicity not needed for doc test
|
/// Index {},
|
||||||
/// ..Default::default()
|
/// }
|
||||||
/// },
|
|
||||||
/// &|| Segment::empty()
|
|
||||||
/// );
|
|
||||||
///
|
///
|
||||||
|
/// fn App(cx: Scope) -> Element {
|
||||||
|
/// render! {
|
||||||
|
/// Router {}
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// #[inline_props]
|
||||||
|
/// fn Index(cx: Scope) -> Element {
|
||||||
/// render! {
|
/// render! {
|
||||||
/// GoForwardButton {
|
/// GoForwardButton {
|
||||||
/// "go forward"
|
/// "go forward"
|
||||||
|
@ -120,8 +128,10 @@ pub fn GoBackButton<'a, R: Routable>(cx: Scope<'a, HistoryButtonProps<'a>>) -> E
|
||||||
/// # );
|
/// # );
|
||||||
/// ```
|
/// ```
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
pub fn GoForwardButton<'a, R: Routable>(cx: Scope<'a, HistoryButtonProps<'a>>) -> Element {
|
pub fn GenericGoForwardButton<'a, R: Routable>(
|
||||||
let HistoryButtonProps { children } = cx.props;
|
cx: Scope<'a, GenericHistoryButtonProps<'a>>,
|
||||||
|
) -> Element {
|
||||||
|
let GenericHistoryButtonProps { children } = cx.props;
|
||||||
|
|
||||||
// hook up to router
|
// hook up to router
|
||||||
let router = match use_router_internal::<R>(cx) {
|
let router = match use_router_internal::<R>(cx) {
|
||||||
|
|
|
@ -4,7 +4,7 @@ use dioxus::prelude::*;
|
||||||
use log::error;
|
use log::error;
|
||||||
|
|
||||||
use crate::navigation::NavigationTarget;
|
use crate::navigation::NavigationTarget;
|
||||||
use crate::routable::Routable;
|
use crate::prelude::*;
|
||||||
use crate::utils::use_router_internal::use_router_internal;
|
use crate::utils::use_router_internal::use_router_internal;
|
||||||
|
|
||||||
/// The properties for a [`Link`].
|
/// The properties for a [`Link`].
|
||||||
|
@ -63,59 +63,57 @@ impl<R: Routable> Debug for GenericLinkProps<'_, R> {
|
||||||
|
|
||||||
/// A link to navigate to another route.
|
/// 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.
|
/// 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.
|
/// as normal.
|
||||||
///
|
///
|
||||||
/// [`use_router`]: crate::hooks::use_router
|
|
||||||
///
|
|
||||||
/// # External targets
|
/// # External targets
|
||||||
/// When the [`Link`]s target is an [`External`] target, that is used as the `href` directly. This
|
/// When the [`GenericLink`]s target is an [`NavigationTarget::External`] target, that is used as the `href` directly. This
|
||||||
/// means that a [`Link`] can always navigate to an [`External`] target.
|
/// means that a [`GenericLink`] can always navigate to an [`NavigationTarget::External`] target, even if the [`HistoryProvider`] does not support it.
|
||||||
///
|
|
||||||
/// 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
|
|
||||||
///
|
///
|
||||||
/// # Panic
|
/// # 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.
|
/// only in debug builds.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use dioxus::prelude::*;
|
/// # use dioxus::prelude::*;
|
||||||
/// # use dioxus_router::prelude::*;
|
/// # use dioxus_router::prelude::*;
|
||||||
/// fn App(cx: Scope) -> Element {
|
/// # use serde::{Deserialize, Serialize};
|
||||||
/// use_router(
|
|
||||||
/// &cx,
|
|
||||||
/// &|| RouterConfiguration {
|
|
||||||
/// synchronous: true, // asynchronicity not needed for doc test
|
|
||||||
/// ..Default::default()
|
|
||||||
/// },
|
|
||||||
/// &|| Segment::empty()
|
|
||||||
/// );
|
|
||||||
///
|
///
|
||||||
|
/// #[derive(Clone, Serialize, Deserialize, Routable)]
|
||||||
|
/// enum Route {
|
||||||
|
/// #[route("/")]
|
||||||
|
/// Index {},
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn App(cx: Scope) -> Element {
|
||||||
|
/// render! {
|
||||||
|
/// Router {}
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// #[inline_props]
|
||||||
|
/// fn Index(cx: Scope) -> Element {
|
||||||
|
/// render! {
|
||||||
/// render! {
|
/// render! {
|
||||||
/// Link {
|
/// Link {
|
||||||
/// active_class: "active",
|
/// active_class: "active",
|
||||||
/// class: "link_class",
|
/// class: "link_class",
|
||||||
/// exact: true,
|
|
||||||
/// id: "link_id",
|
/// id: "link_id",
|
||||||
/// new_tab: true,
|
/// new_tab: true,
|
||||||
/// rel: "link_rel",
|
/// rel: "link_rel",
|
||||||
/// target: "/",
|
/// target: Route::Index {},
|
||||||
///
|
///
|
||||||
/// "A fully configured link"
|
/// "A fully configured link"
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
|
/// }
|
||||||
/// #
|
/// #
|
||||||
/// # let mut vdom = VirtualDom::new(App);
|
/// # let mut vdom = VirtualDom::new(App);
|
||||||
/// # let _ = vdom.rebuild();
|
/// # let _ = vdom.rebuild();
|
||||||
|
|
|
@ -3,13 +3,11 @@ use dioxus::prelude::*;
|
||||||
|
|
||||||
/// An outlet for the current content.
|
/// 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
|
/// 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__.
|
/// of the active route that is __exactly as deep__.
|
||||||
///
|
///
|
||||||
/// [`use_router`]: crate::hooks::use_router
|
|
||||||
///
|
|
||||||
/// # Panic
|
/// # Panic
|
||||||
/// - When the [`Outlet`] is not nested within another component calling the [`use_router`] hook,
|
/// - When the [`Outlet`] is not nested within another component calling the [`use_router`] hook,
|
||||||
/// but only in debug builds.
|
/// but only in debug builds.
|
||||||
|
@ -17,32 +15,61 @@ use dioxus::prelude::*;
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use dioxus::prelude::*;
|
/// # use dioxus::prelude::*;
|
||||||
|
/// # use serde::{Deserialize, Serialize};
|
||||||
/// # use dioxus_router::prelude::*;
|
/// # use dioxus_router::prelude::*;
|
||||||
/// fn App(cx: Scope) -> Element {
|
/// #[derive(Clone, Serialize, Deserialize, Routable)]
|
||||||
/// use_router(
|
/// #[rustfmt::skip]
|
||||||
/// &cx,
|
/// enum Route {
|
||||||
/// &|| RouterConfiguration {
|
/// #[nest("/wrap")]
|
||||||
/// synchronous: true, // asynchronicity not needed for doc test
|
/// #[layout(Wrapper)] // Every layout component must have one Outlet
|
||||||
/// ..Default::default()
|
/// #[route("/")]
|
||||||
/// },
|
/// Child {},
|
||||||
/// &|| Segment::content(comp(Content))
|
/// #[end_layout]
|
||||||
/// );
|
/// #[end_nest]
|
||||||
|
/// #[route("/")]
|
||||||
|
/// Index {},
|
||||||
|
/// }
|
||||||
///
|
///
|
||||||
|
/// #[inline_props]
|
||||||
|
/// fn Index(cx: Scope) -> Element {
|
||||||
|
/// render! {
|
||||||
|
/// div {
|
||||||
|
/// "Index"
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// #[inline_props]
|
||||||
|
/// fn Wrapper(cx: Scope) -> Element {
|
||||||
/// render! {
|
/// render! {
|
||||||
/// h1 { "App" }
|
/// 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! {
|
/// 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 mut vdom = VirtualDom::new(App);
|
||||||
/// # let _ = vdom.rebuild();
|
/// # let _ = vdom.rebuild();
|
||||||
/// # assert_eq!(dioxus_ssr::render(&vdom), "<h1>App</h1><p>Content</p>");
|
/// # assert_eq!(dioxus_ssr::render(&vdom), "<h1>App</h1><p>Child</p>");
|
||||||
/// ```
|
/// ```
|
||||||
pub fn GenericOutlet<R: Routable + Clone>(cx: Scope) -> Element {
|
pub fn GenericOutlet<R: Routable + Clone>(cx: Scope) -> Element {
|
||||||
OutletContext::render::<R>(cx)
|
OutletContext::render::<R>(cx)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use log::error;
|
use log::error;
|
||||||
|
use serde::{de::DeserializeOwned, Serialize};
|
||||||
use std::{cell::RefCell, str::FromStr};
|
use std::{cell::RefCell, str::FromStr};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -13,7 +14,7 @@ pub struct RouterCfg<R: Routable> {
|
||||||
config: RefCell<Option<RouterConfiguration<R>>>,
|
config: RefCell<Option<RouterConfiguration<R>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: Routable> Default for RouterCfg<R>
|
impl<R: Routable + Serialize + DeserializeOwned> Default for RouterCfg<R>
|
||||||
where
|
where
|
||||||
<R as FromStr>::Err: std::fmt::Display,
|
<R as FromStr>::Err: std::fmt::Display,
|
||||||
{
|
{
|
||||||
|
@ -34,7 +35,7 @@ impl<R: Routable> From<RouterConfiguration<R>> for RouterCfg<R> {
|
||||||
|
|
||||||
/// The props for [`Router`].
|
/// The props for [`Router`].
|
||||||
#[derive(Props)]
|
#[derive(Props)]
|
||||||
pub struct GenericRouterProps<R: Routable>
|
pub struct GenericRouterProps<R: Routable + Serialize + DeserializeOwned>
|
||||||
where
|
where
|
||||||
<R as FromStr>::Err: std::fmt::Display,
|
<R as FromStr>::Err: std::fmt::Display,
|
||||||
{
|
{
|
||||||
|
@ -42,7 +43,7 @@ where
|
||||||
config: RouterCfg<R>,
|
config: RouterCfg<R>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: Routable> PartialEq for GenericRouterProps<R>
|
impl<R: Routable + Serialize + DeserializeOwned> PartialEq for GenericRouterProps<R>
|
||||||
where
|
where
|
||||||
<R as FromStr>::Err: std::fmt::Display,
|
<R as FromStr>::Err: std::fmt::Display,
|
||||||
{
|
{
|
||||||
|
@ -53,7 +54,9 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A component that renders the current route.
|
/// A component that renders the current route.
|
||||||
pub fn GenericRouter<R: Routable + Clone>(cx: Scope<GenericRouterProps<R>>) -> Element
|
pub fn GenericRouter<R: Routable + Clone + Serialize + DeserializeOwned>(
|
||||||
|
cx: Scope<GenericRouterProps<R>>,
|
||||||
|
) -> Element
|
||||||
where
|
where
|
||||||
<R as FromStr>::Err: std::fmt::Display,
|
<R as FromStr>::Err: std::fmt::Display,
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
use crate::{hooks::use_route, routable::Routable};
|
use crate::{hooks::use_generic_route, routable::Routable};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub(crate) struct OutletContext {
|
pub(crate) struct OutletContext {
|
||||||
|
@ -25,7 +25,7 @@ impl OutletContext {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
use_route::<R>(cx)
|
use_generic_route::<R>(cx)
|
||||||
.expect("Outlet must be inside of a router")
|
.expect("Outlet must be inside of a router")
|
||||||
.render(cx, current_level)
|
.render(cx, current_level)
|
||||||
}
|
}
|
||||||
|
|
|
@ -149,7 +149,8 @@ where
|
||||||
/// Push a new location.
|
/// Push a new location.
|
||||||
///
|
///
|
||||||
/// The previous location will be available to go back to.
|
/// The previous location will be available to go back to.
|
||||||
pub fn push(&self, target: NavigationTarget<R>) -> Option<NavigationFailure<R>> {
|
pub fn push(&self, target: impl Into<NavigationTarget<R>>) -> Option<NavigationFailure<R>> {
|
||||||
|
let target = target.into();
|
||||||
let mut state = self.state_mut();
|
let mut state = self.state_mut();
|
||||||
match target {
|
match target {
|
||||||
NavigationTarget::Internal(p) => state.history.push(p),
|
NavigationTarget::Internal(p) => state.history.push(p),
|
||||||
|
@ -163,7 +164,8 @@ where
|
||||||
/// Replace the current location.
|
/// Replace the current location.
|
||||||
///
|
///
|
||||||
/// The previous location will **not** be available to go back to.
|
/// The previous location will **not** be available to go back to.
|
||||||
pub fn replace(&self, target: NavigationTarget<R>) -> Option<NavigationFailure<R>> {
|
pub fn replace(&self, target: impl Into<NavigationTarget<R>>) -> Option<NavigationFailure<R>> {
|
||||||
|
let target = target.into();
|
||||||
let mut state = self.state_mut();
|
let mut state = self.state_mut();
|
||||||
match target {
|
match target {
|
||||||
NavigationTarget::Internal(p) => state.history.replace(p),
|
NavigationTarget::Internal(p) => state.history.replace(p),
|
||||||
|
@ -264,7 +266,7 @@ where
|
||||||
// struct TestHistory {}
|
// struct TestHistory {}
|
||||||
|
|
||||||
// impl HistoryProvider for TestHistory {
|
// impl HistoryProvider for TestHistory {
|
||||||
// fn current_path(&self) -> String {
|
// fn current_route(&self) -> String {
|
||||||
// todo!()
|
// todo!()
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
@ -758,7 +760,7 @@ where
|
||||||
// struct TestHistory {}
|
// struct TestHistory {}
|
||||||
|
|
||||||
// impl HistoryProvider for TestHistory {
|
// impl HistoryProvider for TestHistory {
|
||||||
// fn current_path(&self) -> String {
|
// fn current_route(&self) -> String {
|
||||||
// String::from("/")
|
// String::from("/")
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
|
|
@ -18,16 +18,31 @@ where
|
||||||
/// Create a [`MemoryHistory`] starting at `path`.
|
/// Create a [`MemoryHistory`] starting at `path`.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use dioxus_router_core::history::{HistoryProvider, MemoryHistory};
|
/// # use dioxus_router::history::{HistoryProvider, MemoryHistory};
|
||||||
/// let mut history = MemoryHistory::with_initial_path("/some/path").unwrap();
|
/// # use dioxus_router::prelude::*;
|
||||||
/// assert_eq!(history.current_path(), "/some/path");
|
/// # 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::<Route>::with_initial_path("/").unwrap();
|
||||||
|
/// assert_eq!(history.current_route(), Route::Index {});
|
||||||
/// assert_eq!(history.can_go_back(), false);
|
/// assert_eq!(history.can_go_back(), false);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn with_initial_path(path: impl Into<String>) -> Result<Self, <R as FromStr>::Err> {
|
pub fn with_initial_path(path: impl AsRef<str>) -> Result<Self, <R as FromStr>::Err> {
|
||||||
let path = path.into();
|
let path = path.as_ref();
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
current: R::from_str(&path)?,
|
current: R::from_str(path)?,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -83,148 +98,3 @@ impl<R: Routable> HistoryProvider<R> for MemoryHistory<R> {
|
||||||
self.current = path;
|
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::<String>::new());
|
|
||||||
// assert_eq!(mem.future, Vec::<String>::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::<String>::new());
|
|
||||||
// assert_eq!(mem.future, Vec::<String>::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::<String>::new());
|
|
||||||
// assert_eq!(mem.future, Vec::<String>::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());
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ pub(crate) mod web_scroll;
|
||||||
/// An integration with some kind of navigation history.
|
/// An integration with some kind of navigation history.
|
||||||
///
|
///
|
||||||
/// Depending on your use case, your implementation may deviate from the described procedure. This
|
/// 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.
|
/// 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
|
/// The described behaviors are designed to mimic a web browser, which most users should already
|
||||||
|
@ -42,12 +42,26 @@ pub trait HistoryProvider<R: Routable> {
|
||||||
/// **Must start** with `/`. **Must _not_ contain** the prefix.
|
/// **Must start** with `/`. **Must _not_ contain** the prefix.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use dioxus_router_core::history::{HistoryProvider, MemoryHistory};
|
/// # use dioxus_router::history::{HistoryProvider, MemoryHistory};
|
||||||
/// let mut history = MemoryHistory::default();
|
/// # use dioxus_router::prelude::*;
|
||||||
/// assert_eq!(history.current_path(), "/");
|
/// # 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::<Route>::default();
|
||||||
|
/// assert_eq!(history.current_route().to_string(), "/");
|
||||||
///
|
///
|
||||||
/// history.push(String::from("/path"));
|
/// history.push(Route::OtherPage {});
|
||||||
/// assert_eq!(history.current_path(), "/path");
|
/// assert_eq!(history.current_route().to_string(), "/some-other-page");
|
||||||
/// ```
|
/// ```
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn current_route(&self) -> R;
|
fn current_route(&self) -> R;
|
||||||
|
@ -68,11 +82,21 @@ pub trait HistoryProvider<R: Routable> {
|
||||||
/// If a [`HistoryProvider`] cannot know this, it should return [`true`].
|
/// If a [`HistoryProvider`] cannot know this, it should return [`true`].
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use dioxus_router_core::history::{HistoryProvider, MemoryHistory};
|
/// # use dioxus_router::history::{HistoryProvider, MemoryHistory};
|
||||||
/// let mut history = MemoryHistory::default();
|
/// # 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::<Route>::default();
|
||||||
/// assert_eq!(history.can_go_back(), false);
|
/// 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);
|
/// assert_eq!(history.can_go_back(), true);
|
||||||
/// ```
|
/// ```
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
@ -86,18 +110,32 @@ pub trait HistoryProvider<R: Routable> {
|
||||||
/// might be called, even if `can_go_back` returns [`false`].
|
/// might be called, even if `can_go_back` returns [`false`].
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use dioxus_router_core::history::{HistoryProvider, MemoryHistory};
|
/// # use dioxus_router::history::{HistoryProvider, MemoryHistory};
|
||||||
/// let mut history = MemoryHistory::default();
|
/// # use dioxus_router::prelude::*;
|
||||||
/// assert_eq!(history.current_path(), "/");
|
/// # 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::<Route>::default();
|
||||||
|
/// assert_eq!(history.current_route().to_string(), "/");
|
||||||
///
|
///
|
||||||
/// history.go_back();
|
/// history.go_back();
|
||||||
/// assert_eq!(history.current_path(), "/");
|
/// assert_eq!(history.current_route().to_string(), "/");
|
||||||
///
|
///
|
||||||
/// history.push(String::from("/some-other-page"));
|
/// history.push(Route::OtherPage {});
|
||||||
/// assert_eq!(history.current_path(), "/some-other-page");
|
/// assert_eq!(history.current_route().to_string(), "/some-other-page");
|
||||||
///
|
///
|
||||||
/// history.go_back();
|
/// history.go_back();
|
||||||
/// assert_eq!(history.current_path(), "/");
|
/// assert_eq!(history.current_route().to_string(), "/");
|
||||||
/// ```
|
/// ```
|
||||||
fn go_back(&mut self);
|
fn go_back(&mut self);
|
||||||
|
|
||||||
|
@ -106,11 +144,25 @@ pub trait HistoryProvider<R: Routable> {
|
||||||
/// If a [`HistoryProvider`] cannot know this, it should return [`true`].
|
/// If a [`HistoryProvider`] cannot know this, it should return [`true`].
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use dioxus_router_core::history::{HistoryProvider, MemoryHistory};
|
/// # use dioxus_router::history::{HistoryProvider, MemoryHistory};
|
||||||
/// let mut history = MemoryHistory::default();
|
/// # 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::<Route>::default();
|
||||||
/// assert_eq!(history.can_go_forward(), false);
|
/// 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);
|
/// assert_eq!(history.can_go_forward(), false);
|
||||||
///
|
///
|
||||||
/// history.go_back();
|
/// history.go_back();
|
||||||
|
@ -127,16 +179,30 @@ pub trait HistoryProvider<R: Routable> {
|
||||||
/// might be called, even if `can_go_forward` returns [`false`].
|
/// might be called, even if `can_go_forward` returns [`false`].
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use dioxus_router_core::history::{HistoryProvider, MemoryHistory};
|
/// # use dioxus_router::history::{HistoryProvider, MemoryHistory};
|
||||||
/// let mut history = MemoryHistory::default();
|
/// # use dioxus_router::prelude::*;
|
||||||
/// history.push(String::from("/some-other-page"));
|
/// # use serde::{Deserialize, Serialize};
|
||||||
/// assert_eq!(history.current_path(), "/some-other-page");
|
/// # 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::<Route>::default();
|
||||||
|
/// history.push(Route::OtherPage {});
|
||||||
|
/// assert_eq!(history.current_route(), Route::OtherPage {});
|
||||||
///
|
///
|
||||||
/// history.go_back();
|
/// history.go_back();
|
||||||
/// assert_eq!(history.current_path(), "/");
|
/// assert_eq!(history.current_route(), Route::Index {});
|
||||||
///
|
///
|
||||||
/// history.go_forward();
|
/// history.go_forward();
|
||||||
/// assert_eq!(history.current_path(), "/some-other-page");
|
/// assert_eq!(history.current_route(), Route::OtherPage {});
|
||||||
/// ```
|
/// ```
|
||||||
fn go_forward(&mut self);
|
fn go_forward(&mut self);
|
||||||
|
|
||||||
|
@ -148,12 +214,26 @@ pub trait HistoryProvider<R: Routable> {
|
||||||
/// 3. Clear the navigation future.
|
/// 3. Clear the navigation future.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use dioxus_router_core::history::{HistoryProvider, MemoryHistory};
|
/// # use dioxus_router::history::{HistoryProvider, MemoryHistory};
|
||||||
/// let mut history = MemoryHistory::default();
|
/// # use dioxus_router::prelude::*;
|
||||||
/// assert_eq!(history.current_path(), "/");
|
/// # 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::<Route>::default();
|
||||||
|
/// assert_eq!(history.current_route(), Route::Index {});
|
||||||
///
|
///
|
||||||
/// history.push(String::from("/some-other-page"));
|
/// history.push(Route::OtherPage {});
|
||||||
/// assert_eq!(history.current_path(), "/some-other-page");
|
/// assert_eq!(history.current_route(), Route::OtherPage {});
|
||||||
/// assert!(history.can_go_back());
|
/// assert!(history.can_go_back());
|
||||||
/// ```
|
/// ```
|
||||||
fn push(&mut self, route: R);
|
fn push(&mut self, route: R);
|
||||||
|
@ -165,12 +245,26 @@ pub trait HistoryProvider<R: Routable> {
|
||||||
/// untouched.
|
/// untouched.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use dioxus_router_core::history::{HistoryProvider, MemoryHistory};
|
/// # use dioxus_router::history::{HistoryProvider, MemoryHistory};
|
||||||
/// let mut history = MemoryHistory::default();
|
/// # use dioxus_router::prelude::*;
|
||||||
/// assert_eq!(history.current_path(), "/");
|
/// # 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::<Route>::default();
|
||||||
|
/// assert_eq!(history.current_route(), Route::Index {});
|
||||||
///
|
///
|
||||||
/// history.replace(String::from("/some-other-page"));
|
/// history.replace(Route::OtherPage {});
|
||||||
/// assert_eq!(history.current_path(), "/some-other-page");
|
/// assert_eq!(history.current_route(), Route::OtherPage {});
|
||||||
/// assert!(!history.can_go_back());
|
/// assert!(!history.can_go_back());
|
||||||
/// ```
|
/// ```
|
||||||
fn replace(&mut self, path: R);
|
fn replace(&mut self, path: R);
|
||||||
|
|
|
@ -28,7 +28,7 @@ struct WebHistoryState<R> {
|
||||||
scroll: ScrollPosition,
|
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
|
/// # Prefix
|
||||||
/// This [`HistoryProvider`] supports a prefix, which can be used for web apps that aren't located
|
/// This [`HistoryProvider`] supports a prefix, which can be used for web apps that aren't located
|
||||||
|
@ -40,8 +40,6 @@ struct WebHistoryState<R> {
|
||||||
///
|
///
|
||||||
/// Application developers are responsible for not rendering the router if the prefix is not present
|
/// 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.
|
/// 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<R: Serialize + DeserializeOwned + Routable> {
|
pub struct WebHistory<R: Serialize + DeserializeOwned + Routable> {
|
||||||
do_scroll_restoration: bool,
|
do_scroll_restoration: bool,
|
||||||
history: History,
|
history: History,
|
||||||
|
|
|
@ -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
|
/// 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
|
/// 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].
|
/// 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<R: Serialize + DeserializeOwned> {
|
pub struct WebHashHistory<R: Serialize + DeserializeOwned> {
|
||||||
do_scroll_restoration: bool,
|
do_scroll_restoration: bool,
|
||||||
history: History,
|
history: History,
|
||||||
|
|
|
@ -1,17 +1,13 @@
|
||||||
use dioxus::prelude::ScopeState;
|
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.
|
/// A hook that provides access to information about the current routing location.
|
||||||
///
|
///
|
||||||
/// # Return values
|
/// # Return values
|
||||||
/// - [`RouterError::NotInsideRouter`], when the calling component is not nested within another
|
/// - None, when not called inside a [`GenericRouter`] component.
|
||||||
/// component calling the [`use_router`] hook.
|
/// - Otherwise the current route.
|
||||||
/// - Otherwise [`Ok`].
|
|
||||||
///
|
|
||||||
/// # Important usage information
|
|
||||||
/// Make sure to [`drop`] the returned [`RwLockReadGuard`] when done rendering. Otherwise the router
|
|
||||||
/// will be frozen.
|
|
||||||
///
|
///
|
||||||
/// # Panic
|
/// # Panic
|
||||||
/// - When the calling component is not nested within another component calling the [`use_router`]
|
/// - 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
|
/// # Example
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use dioxus::prelude::*;
|
/// # use dioxus::prelude::*;
|
||||||
|
/// # use serde::{Deserialize, Serialize};
|
||||||
/// # use dioxus_router::{history::*, prelude::*};
|
/// # 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! {
|
/// render! {
|
||||||
/// h1 { "App" }
|
/// h1 { "App" }
|
||||||
/// Content { }
|
/// Router {}
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// fn Content(cx: Scope) -> Element {
|
/// #[inline_props]
|
||||||
/// let state = use_route(&cx)?;
|
/// fn Index(cx: Scope) -> Element {
|
||||||
/// let path = state.path.clone();
|
/// let path = use_route(&cx).unwrap();
|
||||||
///
|
|
||||||
/// render! {
|
/// render! {
|
||||||
/// h2 { "Current Path" }
|
/// h2 { "Current Path" }
|
||||||
/// p { "{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 mut vdom = VirtualDom::new(App);
|
||||||
/// # let _ = vdom.rebuild();
|
/// # let _ = vdom.rebuild();
|
||||||
/// # assert_eq!(dioxus_ssr::render(&vdom), "<h1>App</h1><h2>Current Path</h2><p>/some/path</p>")
|
/// # assert_eq!(dioxus_ssr::render(&vdom), "<h1>App</h1><h2>Current Path</h2><p>/</p>")
|
||||||
/// ```
|
/// ```
|
||||||
///
|
pub fn use_generic_route<R: Routable + Clone>(cx: &ScopeState) -> Option<R> {
|
||||||
/// [`use_router`]: crate::hooks::use_router
|
|
||||||
pub fn use_route<R: Routable + Clone>(cx: &ScopeState) -> Option<R> {
|
|
||||||
match use_router_internal(cx) {
|
match use_router_internal(cx) {
|
||||||
Some(r) => Some(r.current()),
|
Some(r) => Some(r.current()),
|
||||||
None => {
|
None => {
|
||||||
|
|
|
@ -5,7 +5,50 @@ use crate::{
|
||||||
utils::use_router_internal::use_router_internal,
|
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<R: Routable + Clone>(cx: &ScopeState) -> &GenericRouterContext<R> {
|
pub fn use_generic_router<R: Routable + Clone>(cx: &ScopeState) -> &GenericRouterContext<R> {
|
||||||
use_router_internal(cx)
|
use_router_internal(cx)
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
|
|
@ -12,18 +12,42 @@ pub enum NavigationTarget<R: Routable> {
|
||||||
/// An internal path that the router can navigate to by itself.
|
/// An internal path that the router can navigate to by itself.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use dioxus_router_core::navigation::NavigationTarget;
|
/// # use dioxus::prelude::*;
|
||||||
/// let explicit = NavigationTarget::Internal(String::from("/internal"));
|
/// # use dioxus_router::prelude::*;
|
||||||
/// let implicit: NavigationTarget = "/internal".into();
|
/// # 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::<Route> = "/".into();
|
||||||
/// assert_eq!(explicit, implicit);
|
/// assert_eq!(explicit, implicit);
|
||||||
/// ```
|
/// ```
|
||||||
Internal(R),
|
Internal(R),
|
||||||
/// An external target that the router doesn't control.
|
/// An external target that the router doesn't control.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use dioxus_router_core::navigation::NavigationTarget;
|
/// # use dioxus::prelude::*;
|
||||||
/// let explicit = NavigationTarget::External(String::from("https://dioxuslabs.com/"));
|
/// # use dioxus_router::prelude::*;
|
||||||
/// let implicit: NavigationTarget = "https://dioxuslabs.com/".into();
|
/// # 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::<Route>::External(String::from("https://dioxuslabs.com/"));
|
||||||
|
/// let implicit: NavigationTarget::<Route> = "https://dioxuslabs.com/".into();
|
||||||
/// assert_eq!(explicit, implicit);
|
/// assert_eq!(explicit, implicit);
|
||||||
/// ```
|
/// ```
|
||||||
External(String),
|
External(String),
|
||||||
|
|
|
@ -93,7 +93,12 @@ impl<I: std::iter::FromIterator<String>> 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 {
|
pub trait Routable: std::fmt::Display + std::str::FromStr + Clone + 'static {
|
||||||
/// The error that can occur when parsing a route
|
/// The error that can occur when parsing a route
|
||||||
const SITE_MAP: &'static [SiteMapSegment];
|
const SITE_MAP: &'static [SiteMapSegment];
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
use crate::contexts::router::RoutingCallback;
|
use crate::contexts::router::RoutingCallback;
|
||||||
use crate::history::{HistoryProvider, MemoryHistory};
|
use crate::history::HistoryProvider;
|
||||||
|
use crate::prelude::*;
|
||||||
use crate::routable::Routable;
|
use crate::routable::Routable;
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
use serde::{de::DeserializeOwned, Serialize};
|
||||||
|
|
||||||
use crate::prelude::default_errors::{
|
use crate::prelude::default_errors::{
|
||||||
FailureExternalNavigation, FailureNamedNavigation, FailureRedirectionLimit,
|
FailureExternalNavigation, FailureNamedNavigation, FailureRedirectionLimit,
|
||||||
|
@ -11,9 +13,20 @@ use crate::prelude::default_errors::{
|
||||||
///
|
///
|
||||||
/// This implements [`Default`], so you can use it like this:
|
/// This implements [`Default`], so you can use it like this:
|
||||||
/// ```rust,no_run
|
/// ```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 {
|
/// let cfg = RouterConfiguration {
|
||||||
/// synchronous: false,
|
/// history: Box::<WebHistory<Route>>::default(),
|
||||||
/// ..Default::default()
|
/// ..Default::default()
|
||||||
/// };
|
/// };
|
||||||
/// ```
|
/// ```
|
||||||
|
@ -22,19 +35,19 @@ pub struct RouterConfiguration<R: Routable> {
|
||||||
///
|
///
|
||||||
/// Defaults to a router-internal component called `FailureExternalNavigation`. It is not part
|
/// Defaults to a router-internal component called `FailureExternalNavigation`. It is not part
|
||||||
/// of the public API. Do not confuse it with
|
/// 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,
|
pub failure_external_navigation: fn(Scope) -> Element,
|
||||||
/// A component to render when a named navigation fails.
|
/// A component to render when a named navigation fails.
|
||||||
///
|
///
|
||||||
/// Defaults to a router-internal component called `FailureNamedNavigation`. It is not part of
|
/// Defaults to a router-internal component called `FailureNamedNavigation`. It is not part of
|
||||||
/// the public API. Do not confuse it with
|
/// the public API. Do not confuse it with
|
||||||
/// [`dioxus_router_core::prelude::FailureNamedNavigation`].
|
/// [`dioxus_router::prelude::FailureNamedNavigation`].
|
||||||
pub failure_named_navigation: fn(Scope) -> Element,
|
pub failure_named_navigation: fn(Scope) -> Element,
|
||||||
/// A component to render when the redirect limit is reached.
|
/// A component to render when the redirect limit is reached.
|
||||||
///
|
///
|
||||||
/// Defaults to a router-internal component called `FailureRedirectionLimit`. It is not part of
|
/// Defaults to a router-internal component called `FailureRedirectionLimit`. It is not part of
|
||||||
/// the public API. Do not confuse it with
|
/// the public API. Do not confuse it with
|
||||||
/// [`dioxus_router_core::prelude::FailureRedirectionLimit`].
|
/// [`dioxus_router::prelude::FailureRedirectionLimit`].
|
||||||
pub failure_redirection_limit: fn(Scope) -> Element,
|
pub failure_redirection_limit: fn(Scope) -> Element,
|
||||||
/// The [`HistoryProvider`] the router should use.
|
/// The [`HistoryProvider`] the router should use.
|
||||||
///
|
///
|
||||||
|
@ -45,7 +58,7 @@ pub struct RouterConfiguration<R: Routable> {
|
||||||
/// The callback is invoked after the routing is updated, but before components and hooks are
|
/// The callback is invoked after the routing is updated, but before components and hooks are
|
||||||
/// updated.
|
/// 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
|
/// with it. If no navigation failure was triggered, the router will then updated dependent
|
||||||
/// components and hooks.
|
/// components and hooks.
|
||||||
///
|
///
|
||||||
|
@ -53,12 +66,10 @@ pub struct RouterConfiguration<R: Routable> {
|
||||||
/// navigation failure occurs.
|
/// navigation failure occurs.
|
||||||
///
|
///
|
||||||
/// Defaults to [`None`].
|
/// Defaults to [`None`].
|
||||||
///
|
|
||||||
/// [`NavigationTarget`]: dioxus_router_core::navigation::NavigationTarget
|
|
||||||
pub on_update: Option<RoutingCallback<R>>,
|
pub on_update: Option<RoutingCallback<R>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: Routable + Clone> Default for RouterConfiguration<R>
|
impl<R: Routable + Clone + Serialize + DeserializeOwned> Default for RouterConfiguration<R>
|
||||||
where
|
where
|
||||||
<R as std::str::FromStr>::Err: std::fmt::Display,
|
<R as std::str::FromStr>::Err: std::fmt::Display,
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,145 +0,0 @@
|
||||||
use std::collections::BTreeMap;
|
|
||||||
|
|
||||||
use urlencoding::encode;
|
|
||||||
|
|
||||||
pub fn gen_sitemap<T: Clone>(seg: &Segment<T>, current: &str, map: &mut Vec<String>) {
|
|
||||||
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<T: Clone>(
|
|
||||||
seg: &Segment<T>,
|
|
||||||
parameters: &BTreeMap<Name, Vec<String>>,
|
|
||||||
current: &str,
|
|
||||||
map: &mut Vec<String>,
|
|
||||||
) {
|
|
||||||
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::<u8>().nested(
|
|
||||||
Segment::empty().matching(String::from("n2"), ParameterRoute::empty::<u16>()),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.matching(String::from("no match"), ParameterRoute::empty::<u32>())
|
|
||||||
.matching(String::from("no parameter"), ParameterRoute::empty::<u64>())
|
|
||||||
.catch_all(
|
|
||||||
ParameterRoute::empty::<u32>()
|
|
||||||
.nested(Segment::empty().catch_all(ParameterRoute::empty::<u16>())),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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::<u8>(), vec!["m1".to_string(), "m2".to_string()]);
|
|
||||||
parameters.insert(Name::of::<u16>(), vec!["n1".to_string(), "n2".to_string()]);
|
|
||||||
parameters.insert(Name::of::<u32>(), 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"
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +1,6 @@
|
||||||
use dioxus::prelude::{ScopeId, ScopeState};
|
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.
|
/// 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.
|
/// single component, but not recommended. Multiple subscriptions will be discarded.
|
||||||
///
|
///
|
||||||
/// # Return values
|
/// # 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`].
|
/// - Otherwise [`Some`].
|
||||||
///
|
|
||||||
/// [`use_router`]: crate::hooks::use_router
|
|
||||||
pub(crate) fn use_router_internal<R: Routable>(
|
pub(crate) fn use_router_internal<R: Routable>(
|
||||||
cx: &ScopeState,
|
cx: &ScopeState,
|
||||||
) -> &Option<GenericRouterContext<R>> {
|
) -> &Option<GenericRouterContext<R>> {
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
#![allow(non_snake_case)]
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use dioxus_router::prelude::*;
|
use dioxus_router::prelude::*;
|
||||||
|
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
fn prepare<R: Routable>() -> String
|
fn prepare<R: Routable + Serialize + DeserializeOwned>() -> String
|
||||||
where
|
where
|
||||||
<R as FromStr>::Err: std::fmt::Display,
|
<R as FromStr>::Err: std::fmt::Display,
|
||||||
{
|
{
|
||||||
|
@ -28,7 +28,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn App<R: Routable>(cx: Scope<AppProps<R>>) -> Element
|
fn App<R: Routable + Serialize + DeserializeOwned>(cx: Scope<AppProps<R>>) -> Element
|
||||||
where
|
where
|
||||||
<R as FromStr>::Err: std::fmt::Display,
|
<R as FromStr>::Err: std::fmt::Display,
|
||||||
{
|
{
|
||||||
|
@ -46,7 +46,7 @@ where
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn href_internal() {
|
fn href_internal() {
|
||||||
#[derive(Routable, Clone)]
|
#[derive(Routable, Clone, Serialize, Deserialize)]
|
||||||
enum Route {
|
enum Route {
|
||||||
#[route("/")]
|
#[route("/")]
|
||||||
Root {},
|
Root {},
|
||||||
|
@ -84,7 +84,7 @@ fn href_internal() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn href_external() {
|
fn href_external() {
|
||||||
#[derive(Routable, Clone)]
|
#[derive(Routable, Clone, Serialize, Deserialize)]
|
||||||
enum Route {
|
enum Route {
|
||||||
#[route("/")]
|
#[route("/")]
|
||||||
Root {},
|
Root {},
|
||||||
|
@ -122,7 +122,7 @@ fn href_external() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn with_class() {
|
fn with_class() {
|
||||||
#[derive(Routable, Clone)]
|
#[derive(Routable, Clone, Serialize, Deserialize)]
|
||||||
enum Route {
|
enum Route {
|
||||||
#[route("/")]
|
#[route("/")]
|
||||||
Root {},
|
Root {},
|
||||||
|
@ -161,7 +161,7 @@ fn with_class() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn with_active_class_active() {
|
fn with_active_class_active() {
|
||||||
#[derive(Routable, Clone)]
|
#[derive(Routable, Clone, Serialize, Deserialize)]
|
||||||
enum Route {
|
enum Route {
|
||||||
#[route("/")]
|
#[route("/")]
|
||||||
Root {},
|
Root {},
|
||||||
|
@ -194,7 +194,7 @@ fn with_active_class_active() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn with_active_class_inactive() {
|
fn with_active_class_inactive() {
|
||||||
#[derive(Routable, Clone)]
|
#[derive(Routable, Clone, Serialize, Deserialize)]
|
||||||
enum Route {
|
enum Route {
|
||||||
#[route("/")]
|
#[route("/")]
|
||||||
Root {},
|
Root {},
|
||||||
|
@ -234,7 +234,7 @@ fn with_active_class_inactive() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn with_id() {
|
fn with_id() {
|
||||||
#[derive(Routable, Clone)]
|
#[derive(Routable, Clone, Serialize, Deserialize)]
|
||||||
enum Route {
|
enum Route {
|
||||||
#[route("/")]
|
#[route("/")]
|
||||||
Root {},
|
Root {},
|
||||||
|
@ -273,7 +273,7 @@ fn with_id() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn with_new_tab() {
|
fn with_new_tab() {
|
||||||
#[derive(Routable, Clone)]
|
#[derive(Routable, Clone, Serialize, Deserialize)]
|
||||||
enum Route {
|
enum Route {
|
||||||
#[route("/")]
|
#[route("/")]
|
||||||
Root {},
|
Root {},
|
||||||
|
@ -312,7 +312,7 @@ fn with_new_tab() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn with_new_tab_external() {
|
fn with_new_tab_external() {
|
||||||
#[derive(Routable, Clone)]
|
#[derive(Routable, Clone, Serialize, Deserialize)]
|
||||||
enum Route {
|
enum Route {
|
||||||
#[route("/")]
|
#[route("/")]
|
||||||
Root {},
|
Root {},
|
||||||
|
@ -344,7 +344,7 @@ fn with_new_tab_external() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn with_rel() {
|
fn with_rel() {
|
||||||
#[derive(Routable, Clone)]
|
#[derive(Routable, Clone, Serialize, Deserialize)]
|
||||||
enum Route {
|
enum Route {
|
||||||
#[route("/")]
|
#[route("/")]
|
||||||
Root {},
|
Root {},
|
||||||
|
|
|
@ -2,13 +2,14 @@
|
||||||
|
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use dioxus_router::{history::MemoryHistory, prelude::*};
|
use dioxus_router::{history::MemoryHistory, prelude::*};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
fn prepare(path: impl Into<String>) -> VirtualDom {
|
fn prepare(path: impl Into<String>) -> VirtualDom {
|
||||||
let mut vdom = VirtualDom::new_with_props(App, AppProps { path: path.into() });
|
let mut vdom = VirtualDom::new_with_props(App, AppProps { path: path.into() });
|
||||||
let _ = vdom.rebuild();
|
let _ = vdom.rebuild();
|
||||||
return vdom;
|
return vdom;
|
||||||
|
|
||||||
#[derive(Routable, Clone)]
|
#[derive(Routable, Clone, Serialize, Deserialize)]
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
enum Route {
|
enum Route {
|
||||||
#[route("/")]
|
#[route("/")]
|
||||||
|
|
Loading…
Reference in a new issue