Merge pull request #1252 from Demonthos/non-generic-link

Remove autogenerated functions/components from the routable macro
This commit is contained in:
Jonathan Kelley authored and GitHub committed 2023-07-26 18:11:32 -07:00
commit 418c03e2cd
47 files changed
+569 -284

No files matched your search

+3 -3
View File
@@ -26,7 +26,7 @@ Unlike other routers in the Rust ecosystem, our router is built declaratively. T
```rust, no_run ```rust, no_run
rsx!{ rsx!{
// All of our routes will be rendered inside this Router component // All of our routes will be rendered inside this Router component
Router { Router::<Route> {
// if the current location is "/home", render the Home component // if the current location is "/home", render the Home component
Route { to: "/home", Home {} } Route { to: "/home", Home {} }
// if the current location is "/blog", render the Blog component // if the current location is "/blog", render the Blog component
@@ -43,7 +43,7 @@ We can fix this one of two ways:
```rust, no_run ```rust, no_run
rsx!{ rsx!{
Router { Router::<Route> {
Route { to: "/home", Home {} } Route { to: "/home", Home {} }
Route { to: "/blog", Blog {} } Route { to: "/blog", Blog {} }
// if the current location doesn't match any of the above routes, render the NotFound component // if the current location doesn't match any of the above routes, render the NotFound component
@@ -56,7 +56,7 @@ rsx!{
```rust, no_run ```rust, no_run
rsx!{ rsx!{
Router { Router::<Route> {
Route { to: "/home", Home {} } Route { to: "/home", Home {} }
Route { to: "/blog", Blog {} } Route { to: "/blog", Blog {} }
// if the current location doesn't match any of the above routes, redirect to "/home" // if the current location doesn't match any of the above routes, redirect to "/home"
+3 -3
View File
@@ -28,7 +28,7 @@ Ao contrário de outros roteadores no ecossistema Rust, nosso roteador é constr
```rust, no_run ```rust, no_run
rsx!{ rsx!{
Router { Router::<Route> {
Route { to: "/home", Home {} } Route { to: "/home", Home {} }
Route { to: "/blog", Blog {} } Route { to: "/blog", Blog {} }
} }
@@ -43,7 +43,7 @@ Podemos corrigir isso de duas maneiras:
```rust, no_run ```rust, no_run
rsx!{ rsx!{
Router { Router::<Route> {
Route { to: "/home", Home {} } Route { to: "/home", Home {} }
Route { to: "/blog", Blog {} } Route { to: "/blog", Blog {} }
Route { to: "", NotFound {} } Route { to: "", NotFound {} }
@@ -55,7 +55,7 @@ rsx!{
```rust, no_run ```rust, no_run
rsx!{ rsx!{
Router { Router::<Route> {
Route { to: "/home", Home {} } Route { to: "/home", Home {} }
Route { to: "/blog", Blog {} } Route { to: "/blog", Blog {} }
Redirect { from: "", to: "/home" } Redirect { from: "", to: "/home" }
+1 -1
View File
@@ -72,7 +72,7 @@ Apps with routers are _really_ simple now. It's easy to compose the "Router", a
```rust, no_run ```rust, no_run
fn app(cx: Scope) -> Element { fn app(cx: Scope) -> Element {
cx.render(rsx! { cx.render(rsx! {
Router { Router::<Route> {
onchange: move |_| log::info!("Route changed!"), onchange: move |_| log::info!("Route changed!"),
ul { ul {
Link { to: "/", li { "Go home!" } } Link { to: "/", li { "Go home!" } }
+1 -1
View File
@@ -17,7 +17,7 @@ enum Route {
#[inline_props] #[inline_props]
fn App(cx: Scope) -> Element { fn App(cx: Scope) -> Element {
render! { render! {
Router {} Router::<Route> {}
} }
} }
// ANCHOR_END: app // ANCHOR_END: app
+3 -3
View File
@@ -35,7 +35,7 @@ enum Route {
fn App(cx: Scope) -> Element { fn App(cx: Scope) -> Element {
render! { render! {
Router {} Router::<Route> {}
} }
} }
@@ -48,7 +48,7 @@ fn NavBar(cx: Scope) -> Element {
li { Link { to: Route::BlogList {}, "Blog" } } li { Link { to: Route::BlogList {}, "Blog" } }
} }
} }
Outlet {} Outlet::<Route> {}
} }
} }
@@ -64,7 +64,7 @@ fn Home(cx: Scope) -> Element {
fn Blog(cx: Scope) -> Element { fn Blog(cx: Scope) -> Element {
render! { render! {
h1 { "Blog" } h1 { "Blog" }
Outlet {} Outlet::<Route> {}
} }
} }
// ANCHOR_END: blog // ANCHOR_END: blog
+1 -1
View File
@@ -20,7 +20,7 @@ fn main() {}
fn GoToDioxus(cx: Scope) -> Element { fn GoToDioxus(cx: Scope) -> Element {
render! { render! {
Link { Link {
to: NavigationTarget::External("https://dioxuslabs.com".into()), to: "https://dioxuslabs.com",
"ExternalTarget target" "ExternalTarget target"
} }
} }
+1 -1
View File
@@ -19,7 +19,7 @@ enum Route {
#[inline_props] #[inline_props]
fn App(cx: Scope) -> Element { fn App(cx: Scope) -> Element {
render! { render! {
Router {} Router::<Route> {}
} }
} }
// ANCHOR_END: app // ANCHOR_END: app
+3 -3
View File
@@ -39,7 +39,7 @@ enum Route {
fn App(cx: Scope) -> Element { fn App(cx: Scope) -> Element {
render! { render! {
Router {} Router::<Route> {}
} }
} }
@@ -52,7 +52,7 @@ fn NavBar(cx: Scope) -> Element {
li { Link { to: Route::BlogList {}, "Blog" } } li { Link { to: Route::BlogList {}, "Blog" } }
} }
} }
Outlet {} Outlet::<Route> {}
} }
} }
@@ -67,7 +67,7 @@ fn Home(cx: Scope) -> Element {
fn Blog(cx: Scope) -> Element { fn Blog(cx: Scope) -> Element {
render! { render! {
h1 { "Blog" } h1 { "Blog" }
Outlet {} Outlet::<Route> {}
} }
} }
+1 -1
View File
@@ -12,7 +12,7 @@ enum Route {
#[inline_props] #[inline_props]
fn App(cx: Scope) -> Element { fn App(cx: Scope) -> Element {
render! { render! {
Router { Router::<Route> {
config: || RouterConfig::default().history(WebHistory::default()) config: || RouterConfig::default().history(WebHistory::default())
} }
} }
+2 -2
View File
@@ -32,7 +32,7 @@ fn NavBar(cx: Scope) -> Element {
} }
} }
} }
Outlet {} Outlet::<Route> {}
} }
} }
// ANCHOR_END: nav // ANCHOR_END: nav
@@ -41,7 +41,7 @@ fn NavBar(cx: Scope) -> Element {
#[inline_props] #[inline_props]
fn App(cx: Scope) -> Element { fn App(cx: Scope) -> Element {
render! { render! {
Router {} Router::<Route> {}
} }
} }
// ANCHOR_END: app // ANCHOR_END: app
+1 -1
View File
@@ -14,7 +14,7 @@ enum Route {
#[inline_props] #[inline_props]
fn App(cx: Scope) -> Element { fn App(cx: Scope) -> Element {
render! { render! {
Router {} Router::<Route> {}
} }
} }
+2 -2
View File
@@ -26,7 +26,7 @@ fn NavBar(cx: Scope) -> Element {
} }
} }
// The Outlet component will render child routes (In this case just the Home component) inside the Outlet component // The Outlet component will render child routes (In this case just the Home component) inside the Outlet component
Outlet {} Outlet::<Route> {}
} }
} }
// ANCHOR_END: nav // ANCHOR_END: nav
@@ -35,7 +35,7 @@ fn NavBar(cx: Scope) -> Element {
#[inline_props] #[inline_props]
fn App(cx: Scope) -> Element { fn App(cx: Scope) -> Element {
render! { render! {
Router {} Router::<Route> {}
} }
} }
// ANCHOR_END: app // ANCHOR_END: app
+2 -2
View File
@@ -16,7 +16,7 @@ fn Wrapper(cx: Scope) -> Element {
render! { render! {
header { "header" } header { "header" }
// The index route will be rendered here // The index route will be rendered here
Outlet { } Outlet::<Route> { }
footer { "footer" } footer { "footer" }
} }
} }
@@ -31,7 +31,7 @@ fn Index(cx: Scope) -> Element {
fn App(cx: Scope) -> Element { fn App(cx: Scope) -> Element {
render! { render! {
Router {} Router::<Route> {}
} }
} }
+1 -1
View File
@@ -18,7 +18,7 @@ enum Route {
#[inline_props] #[inline_props]
fn App(cx: Scope) -> Element { fn App(cx: Scope) -> Element {
render! { render! {
Router { Router::<Route> {
config: || RouterConfig::default().history(WebHistory::default()) config: || RouterConfig::default().history(WebHistory::default())
} }
} }
+1 -1
View File
@@ -27,7 +27,7 @@ fn Index(cx: Scope) -> Element {
fn app(cx: Scope) -> Element { fn app(cx: Scope) -> Element {
render! { render! {
Router { Router::<Route> {
config: || RouterConfig::default().on_update(|state|{ config: || RouterConfig::default().on_update(|state|{
(state.current() == Route::Index {}).then_some( (state.current() == Route::Index {}).then_some(
NavigationTarget::Internal(Route::Home {}) NavigationTarget::Internal(Route::Home {})
+1 -1
View File
@@ -46,7 +46,7 @@ fn App(cx: Scope) -> Element {
h1 { "Dioxus CRM Example" } h1 { "Dioxus CRM Example" }
Router {} Router::<Route> {}
} }
} }
+2 -2
View File
@@ -18,7 +18,7 @@ fn main() {
fn app(cx: Scope) -> Element { fn app(cx: Scope) -> Element {
render! { render! {
Router {} Router::<Route> {}
} }
} }
@@ -40,7 +40,7 @@ enum Route {
fn Footer(cx: Scope) -> Element { fn Footer(cx: Scope) -> Element {
render! { render! {
div { div {
Outlet { } Outlet::<Route> { }
p { p {
"----" "----"
+2 -2
View File
@@ -23,7 +23,7 @@ fn app(cx: Scope) -> Element {
} }
} }
div { div {
Router {} Router::<Route> {}
} }
)) ))
} }
@@ -46,7 +46,7 @@ fn Header(cx: Scope) -> Element {
li { Link { to: Route::Home {}, "home" } } li { Link { to: Route::Home {}, "home" } }
li { Link { to: Route::Settings {}, "settings" } } li { Link { to: Route::Settings {}, "settings" } }
} }
Outlet {} Outlet::<Route> {}
} }
} }
+1 -1
View File
@@ -48,7 +48,7 @@ fn app(cx: Scope) -> Element {
let read = div_element.read(); let read = div_element.read();
let client_rect = read.as_ref().map(|el| el.get_client_rect()); let client_rect = read.as_ref().map(|el| el.get_client_rect());
if let Some(client_rect) = client_rect { if let Some(client_rect) = client_rect {
if let Ok(rect) = dbg!(client_rect.await) { if let Ok(rect) = client_rect.await {
dimentions.set(rect); dimentions.set(rect);
} }
} }
+3 -3
View File
@@ -39,7 +39,7 @@ enum Route {
fn App(cx: Scope) -> Element { fn App(cx: Scope) -> Element {
render! { render! {
Router {} Router::<Route> {}
} }
} }
@@ -52,7 +52,7 @@ fn NavBar(cx: Scope) -> Element {
li { Link { to: Route::BlogList {}, "Blog" } } li { Link { to: Route::BlogList {}, "Blog" } }
} }
} }
Outlet {} Outlet::<Route> {}
} }
} }
@@ -67,7 +67,7 @@ fn Home(cx: Scope) -> Element {
fn Blog(cx: Scope) -> Element { fn Blog(cx: Scope) -> Element {
render! { render! {
h1 { "Blog" } h1 { "Blog" }
Outlet {} Outlet::<Route> {}
} }
} }
+2 -2
View File
@@ -14,7 +14,7 @@ fn main() {
fn app(cx: Scope) -> Element { fn app(cx: Scope) -> Element {
render! { render! {
Router {} Router::<Route> {}
} }
} }
@@ -47,7 +47,7 @@ fn NavBar(cx: Scope) -> Element {
li { Link { to: Route::BlogPost { post: "bill".into() }, "bills' blog" } } li { Link { to: Route::BlogPost { post: "bill".into() }, "bills' blog" } }
li { Link { to: Route::BlogPost { post: "james".into() }, "james amazing' blog" } } li { Link { to: Route::BlogPost { post: "james".into() }, "james amazing' blog" } }
} }
Outlet {} Outlet::<Route> {}
} }
} }
+6 -2
View File
@@ -106,12 +106,16 @@ async fn check_files_and_report(files_to_check: Vec<PathBuf>) -> Result<()> {
} }
fn collect_rs_files(folder: &Path, files: &mut Vec<PathBuf>) { fn collect_rs_files(folder: &Path, files: &mut Vec<PathBuf>) {
let Ok(folder) = folder.read_dir() else { return }; let Ok(folder) = folder.read_dir() else {
return;
};
// load the gitignore // load the gitignore
for entry in folder { for entry in folder {
let Ok(entry) = entry else { continue; }; let Ok(entry) = entry else {
continue;
};
let path = entry.path(); let path = entry.path();
+5 -9
View File
@@ -16,7 +16,7 @@ where
let cfg = *cx.props; let cfg = *cx.props;
render! { render! {
dioxus_router::prelude::GenericRouter::<R> { dioxus_router::prelude::Router::<R> {
config: move || { config: move || {
RouterConfig::default() RouterConfig::default()
.failure_external_navigation(cfg.failure_external_navigation) .failure_external_navigation(cfg.failure_external_navigation)
@@ -49,12 +49,8 @@ where
} }
} }
fn default_external_navigation_handler<R>() -> fn(Scope) -> Element fn default_external_navigation_handler() -> fn(Scope) -> Element {
where dioxus_router::prelude::FailureExternalNavigation
R: dioxus_router::prelude::Routable,
<R as std::str::FromStr>::Err: std::fmt::Display,
{
dioxus_router::prelude::FailureExternalNavigation::<R>
} }
/// The configeration for the router /// The configeration for the router
@@ -65,7 +61,7 @@ where
<R as std::str::FromStr>::Err: std::fmt::Display, <R as std::str::FromStr>::Err: std::fmt::Display,
{ {
#[serde(skip)] #[serde(skip)]
#[serde(default = "default_external_navigation_handler::<R>")] #[serde(default = "default_external_navigation_handler")]
failure_external_navigation: fn(Scope) -> Element, failure_external_navigation: fn(Scope) -> Element,
scroll_restoration: bool, scroll_restoration: bool,
#[serde(skip)] #[serde(skip)]
@@ -96,7 +92,7 @@ where
{ {
fn default() -> Self { fn default() -> Self {
Self { Self {
failure_external_navigation: dioxus_router::prelude::FailureExternalNavigation::<R>, failure_external_navigation: dioxus_router::prelude::FailureExternalNavigation,
scroll_restoration: true, scroll_restoration: true,
phantom: std::marker::PhantomData, phantom: std::marker::PhantomData,
} }
-33
View File
@@ -211,38 +211,8 @@ pub fn routable(input: TokenStream) -> TokenStream {
let parse_impl = route_enum.parse_impl(); let parse_impl = route_enum.parse_impl();
let display_impl = route_enum.impl_display(); let display_impl = route_enum.impl_display();
let routable_impl = route_enum.routable_impl(); let routable_impl = route_enum.routable_impl();
let name = &route_enum.name;
let vis = &route_enum.vis;
quote! { quote! {
#vis fn Outlet(cx: dioxus::prelude::Scope) -> dioxus::prelude::Element {
dioxus_router::prelude::GenericOutlet::<#name>(cx)
}
#vis fn Router(cx: dioxus::prelude::Scope<dioxus_router::prelude::GenericRouterProps<#name>>) -> dioxus::prelude::Element {
dioxus_router::prelude::GenericRouter(cx)
}
#vis fn Link<'a>(cx: dioxus::prelude::Scope<'a, dioxus_router::prelude::GenericLinkProps<'a, #name>>) -> dioxus::prelude::Element<'a> {
dioxus_router::prelude::GenericLink(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_route(cx: &dioxus::prelude::ScopeState) -> Option<#name> {
dioxus_router::prelude::use_generic_route(cx)
}
#vis fn use_navigator(cx: &dioxus::prelude::ScopeState) -> &dioxus_router::prelude::GenericNavigator<#name> {
dioxus_router::prelude::use_generic_navigator(cx)
}
#error_type #error_type
#display_impl #display_impl
@@ -255,7 +225,6 @@ pub fn routable(input: TokenStream) -> TokenStream {
} }
struct RouteEnum { struct RouteEnum {
vis: syn::Visibility,
name: Ident, name: Ident,
redirects: Vec<Redirect>, redirects: Vec<Redirect>,
routes: Vec<Route>, routes: Vec<Route>,
@@ -267,7 +236,6 @@ struct RouteEnum {
impl RouteEnum { impl RouteEnum {
fn parse(data: syn::ItemEnum) -> syn::Result<Self> { fn parse(data: syn::ItemEnum) -> syn::Result<Self> {
let name = &data.ident; let name = &data.ident;
let vis = &data.vis;
let mut site_map = Vec::new(); let mut site_map = Vec::new();
let mut site_map_stack: Vec<Vec<SiteMapSegment>> = Vec::new(); let mut site_map_stack: Vec<Vec<SiteMapSegment>> = Vec::new();
@@ -457,7 +425,6 @@ impl RouteEnum {
} }
let myself = Self { let myself = Self {
vis: vis.clone(),
name: name.clone(), name: name.clone(),
routes, routes,
redirects, redirects,
+2 -2
View File
@@ -51,7 +51,7 @@ enum Route {
fn App(cx: Scope) -> Element { fn App(cx: Scope) -> Element {
render! { render! {
Router { } Router::<Route> { }
} }
} }
@@ -70,7 +70,7 @@ fn Index(cx: Scope) -> Element {
fn Blog(cx: Scope) -> Element { fn Blog(cx: Scope) -> Element {
render! { render! {
h1 { "Blog" } h1 { "Blog" }
Outlet { } Outlet::<Route> { }
} }
} }
+1 -1
View File
@@ -193,7 +193,7 @@ enum Route {
fn RenderPath(cx: Scope, path: Route) -> Element { fn RenderPath(cx: Scope, path: Route) -> Element {
let path = path.clone(); let path = path.clone();
render! { render! {
Router { Router::<Route> {
config: || RouterConfig::default().history(MemoryHistory::with_initial_path(path)) config: || RouterConfig::default().history(MemoryHistory::with_initial_path(path))
} }
} }
+3 -3
View File
@@ -14,7 +14,7 @@ fn main() {
fn root(cx: Scope) -> Element { fn root(cx: Scope) -> Element {
render! { render! {
Router {} Router::<Route> {}
} }
} }
@@ -27,7 +27,7 @@ fn UserFrame(cx: Scope, user_id: usize) -> Element {
div { div {
background_color: "rgba(0,0,0,50%)", background_color: "rgba(0,0,0,50%)",
"children:" "children:"
Outlet {} Outlet::<Route> {}
} }
} }
} }
@@ -90,7 +90,7 @@ fn Route3(cx: Scope, dynamic: String) -> Element {
"hello world link" "hello world link"
} }
button { button {
onclick: move |_| { navigator.push(NavigationTarget::External("https://www.google.com".to_string())); }, onclick: move |_| { navigator.push("https://www.google.com"); },
"google link" "google link"
} }
p { "Site Map" } p { "Site Map" }
+24 -21
View File
@@ -9,24 +9,10 @@ use dioxus_ssr::incremental::{DefaultRenderer, IncrementalRendererConfig};
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
let mut renderer = IncrementalRendererConfig::new(DefaultRenderer { let mut renderer = IncrementalRendererConfig::new()
before_body: r#"<!DOCTYPE html> .static_dir("./static")
<html lang="en"> .invalidate_after(Duration::from_secs(10))
<head> .build();
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,
initial-scale=1.0">
<title>Dioxus Application</title>
</head>
<body>"#
.to_string(),
after_body: r#"</body>
</html>"#
.to_string(),
})
.static_dir("./static")
.invalidate_after(Duration::from_secs(10))
.build();
println!( println!(
"SITE MAP:\n{}", "SITE MAP:\n{}",
@@ -44,9 +30,26 @@ async fn main() {
.join("\n") .join("\n")
); );
pre_cache_static_routes::<Route, _>(&mut renderer) pre_cache_static_routes::<Route, _>(
.await &mut renderer,
.unwrap(); &DefaultRenderer {
before_body: r#"<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,
initial-scale=1.0">
<title>Dioxus Application</title>
</head>
<body>"#
.to_string(),
after_body: r#"</body>
</html>"#
.to_string(),
},
)
.await
.unwrap();
} }
#[inline_props] #[inline_props]
@@ -1,10 +1,10 @@
use crate::{hooks::use_generic_router, routable::Routable}; use crate::hooks::use_router;
use dioxus::prelude::*; use dioxus::prelude::*;
/// The default component to render when an external navigation fails. /// The default component to render when an external navigation fails.
#[allow(non_snake_case)] #[allow(non_snake_case)]
pub fn FailureExternalNavigation<R: Routable + Clone>(cx: Scope) -> Element { pub fn FailureExternalNavigation(cx: Scope) -> Element {
let router = use_generic_router::<R>(cx); let router = use_router(cx);
render! { render! {
h1 { "External Navigation Failure!" } h1 { "External Navigation Failure!" }
@@ -1,23 +1,23 @@
use dioxus::prelude::*; use dioxus::prelude::*;
use log::error; use log::error;
use crate::{prelude::*, utils::use_router_internal::use_router_internal}; use crate::utils::use_router_internal::use_router_internal;
/// The properties for a [`GenericGoBackButton`] or a [`GenericGoForwardButton`]. /// The properties for a [`GoBackButton`] or a [`GoForwardButton`].
#[derive(Debug, Props)] #[derive(Debug, Props)]
pub struct GenericHistoryButtonProps<'a> { pub struct HistoryButtonProps<'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 [`GenericRouter`] component, otherwise it will be inactive. /// Only works as descendant of a [`Link`] 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.
/// ///
/// # Panic /// # Panic
/// - When the [`GenericGoBackButton`] is not nested within a [`GenericRouter`] component /// - When the [`GoBackButton`] is not nested within a [`Link`] component
/// hook, but only in debug builds. /// hook, but only in debug builds.
/// ///
/// # Example /// # Example
@@ -32,7 +32,7 @@ pub struct GenericHistoryButtonProps<'a> {
/// ///
/// fn App(cx: Scope) -> Element { /// fn App(cx: Scope) -> Element {
/// render! { /// render! {
/// Router {} /// Router::<Route> {}
/// } /// }
/// } /// }
/// ///
@@ -53,13 +53,11 @@ pub struct GenericHistoryButtonProps<'a> {
/// # ); /// # );
/// ``` /// ```
#[allow(non_snake_case)] #[allow(non_snake_case)]
pub fn GenericGoBackButton<'a, R: Routable>( pub fn GoBackButton<'a>(cx: Scope<'a, HistoryButtonProps<'a>>) -> Element {
cx: Scope<'a, GenericHistoryButtonProps<'a>>, let HistoryButtonProps { children } = cx.props;
) -> 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(cx) {
Some(r) => r, Some(r) => r,
#[allow(unreachable_code)] #[allow(unreachable_code)]
None => { None => {
@@ -85,12 +83,12 @@ pub fn GenericGoBackButton<'a, R: Routable>(
/// 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 [`GenericRouter`] component, otherwise it will be inactive. /// Only works as descendant of a [`Link`] 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.
/// ///
/// # Panic /// # Panic
/// - When the [`GenericGoForwardButton`] is not nested within a [`GenericRouter`] component /// - When the [`GoForwardButton`] is not nested within a [`Link`] component
/// hook, but only in debug builds. /// hook, but only in debug builds.
/// ///
/// # Example /// # Example
@@ -105,7 +103,7 @@ pub fn GenericGoBackButton<'a, R: Routable>(
/// ///
/// fn App(cx: Scope) -> Element { /// fn App(cx: Scope) -> Element {
/// render! { /// render! {
/// Router {} /// Router::<Route> {}
/// } /// }
/// } /// }
/// ///
@@ -126,13 +124,11 @@ pub fn GenericGoBackButton<'a, R: Routable>(
/// # ); /// # );
/// ``` /// ```
#[allow(non_snake_case)] #[allow(non_snake_case)]
pub fn GenericGoForwardButton<'a, R: Routable>( pub fn GoForwardButton<'a>(cx: Scope<'a, HistoryButtonProps<'a>>) -> Element {
cx: Scope<'a, GenericHistoryButtonProps<'a>>, let HistoryButtonProps { children } = cx.props;
) -> 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(cx) {
Some(r) => r, Some(r) => r,
#[allow(unreachable_code)] #[allow(unreachable_code)]
None => { None => {
+72 -22
View File
@@ -1,15 +1,59 @@
use std::any::Any;
use std::fmt::Debug; use std::fmt::Debug;
use dioxus::prelude::*; use dioxus::prelude::*;
use log::error; use log::error;
use crate::navigation::NavigationTarget; use crate::navigation::NavigationTarget;
use crate::prelude::*; use crate::prelude::Routable;
use crate::utils::use_router_internal::use_router_internal; use crate::utils::use_router_internal::use_router_internal;
/// The properties for a [`GenericLink`]. /// Something that can be converted into a [`NavigationTarget`].
pub enum IntoRoutable {
/// A raw string target.
FromStr(String),
/// A internal target.
Route(Box<dyn Any>),
}
impl<R: Routable> From<R> for IntoRoutable {
fn from(value: R) -> Self {
IntoRoutable::Route(Box::new(value) as Box<dyn Any>)
}
}
impl<R: Routable> From<NavigationTarget<R>> for IntoRoutable {
fn from(value: NavigationTarget<R>) -> Self {
match value {
NavigationTarget::Internal(route) => {
IntoRoutable::Route(Box::new(route) as Box<dyn Any>)
}
NavigationTarget::External(url) => IntoRoutable::FromStr(url),
}
}
}
impl From<String> for IntoRoutable {
fn from(value: String) -> Self {
IntoRoutable::FromStr(value)
}
}
impl From<&String> for IntoRoutable {
fn from(value: &String) -> Self {
IntoRoutable::FromStr(value.to_string())
}
}
impl From<&str> for IntoRoutable {
fn from(value: &str) -> Self {
IntoRoutable::FromStr(value.to_string())
}
}
/// The properties for a [`Link`].
#[derive(Props)] #[derive(Props)]
pub struct GenericLinkProps<'a, R: Routable> { pub struct LinkProps<'a> {
/// A class to apply to the generate HTML anchor tag if the `target` route is active. /// A class to apply to the generate HTML anchor tag if the `target` route is active.
pub active_class: Option<&'a str>, pub active_class: Option<&'a str>,
/// The children to render within the generated HTML anchor tag. /// The children to render within the generated HTML anchor tag.
@@ -23,7 +67,7 @@ pub struct GenericLinkProps<'a, R: Routable> {
pub id: Option<&'a str>, pub id: Option<&'a str>,
/// When [`true`], the `target` route will be opened in a new tab. /// When [`true`], the `target` route will be opened in a new tab.
/// ///
/// This does not change whether the [`GenericLink`] is active or not. /// This does not change whether the [`Link`] is active or not.
#[props(default)] #[props(default)]
pub new_tab: bool, pub new_tab: bool,
/// The onclick event handler. /// The onclick event handler.
@@ -42,10 +86,10 @@ pub struct GenericLinkProps<'a, R: Routable> {
pub rel: Option<&'a str>, pub rel: Option<&'a str>,
/// The navigation target. Roughly equivalent to the href attribute of an HTML anchor tag. /// The navigation target. Roughly equivalent to the href attribute of an HTML anchor tag.
#[props(into)] #[props(into)]
pub to: NavigationTarget<R>, pub to: IntoRoutable,
} }
impl<R: Routable> Debug for GenericLinkProps<'_, R> { impl Debug for LinkProps<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("LinkProps") f.debug_struct("LinkProps")
.field("active_class", &self.active_class) .field("active_class", &self.active_class)
@@ -56,27 +100,26 @@ impl<R: Routable> Debug for GenericLinkProps<'_, R> {
.field("onclick", &self.onclick.as_ref().map(|_| "onclick is set")) .field("onclick", &self.onclick.as_ref().map(|_| "onclick is set"))
.field("onclick_only", &self.onclick_only) .field("onclick_only", &self.onclick_only)
.field("rel", &self.rel) .field("rel", &self.rel)
.field("to", &self.to.to_string())
.finish() .finish()
} }
} }
/// A link to navigate to another route. /// A link to navigate to another route.
/// ///
/// Only works as descendant of a [`GenericRouter`] component, otherwise it will be inactive. /// Only works as descendant of a [`Router`] component, otherwise it will be inactive.
/// ///
/// Unlike a regular HTML anchor, a [`GenericLink`] allows the router to handle the navigation and doesn't /// Unlike a regular HTML anchor, a [`Link`] 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 [`GenericLink`] still generates an anchor, which you can use for styling /// However, in the background a [`Link`] still generates an anchor, which you can use for styling
/// as normal. /// as normal.
/// ///
/// # External targets /// # External targets
/// When the [`GenericLink`]s target is an [`NavigationTarget::External`] target, that is used as the `href` directly. This /// When the [`Link`]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. /// means that a [`Link`] can always navigate to an [`NavigationTarget::External`] target, even if the [`HistoryProvider`] does not support it.
/// ///
/// # Panic /// # Panic
/// - When the [`GenericLink`] is not nested within a [`GenericRouter`], but /// - When the [`Link`] is not nested within a [`Router`], but
/// only in debug builds. /// only in debug builds.
/// ///
/// # Example /// # Example
@@ -92,7 +135,7 @@ impl<R: Routable> Debug for GenericLinkProps<'_, R> {
/// ///
/// fn App(cx: Scope) -> Element { /// fn App(cx: Scope) -> Element {
/// render! { /// render! {
/// Router {} /// Router::<Route> {}
/// } /// }
/// } /// }
/// ///
@@ -122,8 +165,8 @@ impl<R: Routable> Debug for GenericLinkProps<'_, R> {
/// # ); /// # );
/// ``` /// ```
#[allow(non_snake_case)] #[allow(non_snake_case)]
pub fn GenericLink<'a, R: Routable + Clone>(cx: Scope<'a, GenericLinkProps<'a, R>>) -> Element { pub fn Link<'a>(cx: Scope<'a, LinkProps<'a>>) -> Element {
let GenericLinkProps { let LinkProps {
active_class, active_class,
children, children,
class, class,
@@ -133,10 +176,11 @@ pub fn GenericLink<'a, R: Routable + Clone>(cx: Scope<'a, GenericLinkProps<'a, R
onclick_only, onclick_only,
rel, rel,
to, to,
..
} = cx.props; } = cx.props;
// hook up to router // hook up to router
let router = match use_router_internal::<R>(cx) { let router = match use_router_internal(cx) {
Some(r) => r, Some(r) => r,
#[allow(unreachable_code)] #[allow(unreachable_code)]
None => { None => {
@@ -148,9 +192,15 @@ pub fn GenericLink<'a, R: Routable + Clone>(cx: Scope<'a, GenericLinkProps<'a, R
} }
}; };
let current_route = router.current(); let current_url = router.current_route_string();
let current_url = current_route.to_string(); let href = match to {
let href = to.to_string(); IntoRoutable::FromStr(url) => url.to_string(),
IntoRoutable::Route(route) => router.any_route_to_string(&**route),
};
let parsed_route: NavigationTarget<Box<dyn Any>> = match router.route_from_str(&href) {
Ok(route) => NavigationTarget::Internal(route),
Err(err) => NavigationTarget::External(err),
};
let ac = active_class let ac = active_class
.and_then(|active_class| (href == current_url).then(|| format!(" {active_class}"))) .and_then(|active_class| (href == current_url).then(|| format!(" {active_class}")))
.unwrap_or_default(); .unwrap_or_default();
@@ -159,7 +209,7 @@ pub fn GenericLink<'a, R: Routable + Clone>(cx: Scope<'a, GenericLinkProps<'a, R
let class = format!("{}{ac}", class.unwrap_or_default()); let class = format!("{}{ac}", class.unwrap_or_default());
let tag_target = new_tab.then_some("_blank").unwrap_or_default(); let tag_target = new_tab.then_some("_blank").unwrap_or_default();
let is_external = matches!(to, NavigationTarget::External(_)); let is_external = matches!(parsed_route, NavigationTarget::External(_));
let is_router_nav = !is_external && !new_tab; let is_router_nav = !is_external && !new_tab;
let prevent_default = is_router_nav.then_some("onclick").unwrap_or_default(); let prevent_default = is_router_nav.then_some("onclick").unwrap_or_default();
let rel = rel let rel = rel
@@ -169,7 +219,7 @@ pub fn GenericLink<'a, R: Routable + Clone>(cx: Scope<'a, GenericLinkProps<'a, R
let do_default = onclick.is_none() || !onclick_only; let do_default = onclick.is_none() || !onclick_only;
let action = move |event| { let action = move |event| {
if do_default && is_router_nav { if do_default && is_router_nav {
router.push(to.clone()); router.push_any(router.resolve_into_routable(to));
} }
if let Some(handler) = onclick { if let Some(handler) = onclick {
+6 -6
View File
@@ -3,13 +3,13 @@ use dioxus::prelude::*;
/// An outlet for the current content. /// An outlet for the current content.
/// ///
/// Only works as descendant of a [`GenericRouter`] component, otherwise it will be inactive. /// Only works as descendant of a [`Link`] component, otherwise it will be inactive.
/// ///
/// The [`GenericOutlet`] is aware of how many [`GenericOutlet`]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__.
/// ///
/// # Panic /// # Panic
/// - When the [`GenericOutlet`] is not nested a [`GenericRouter`] component, /// - When the [`Outlet`] is not nested a [`Link`] component,
/// but only in debug builds. /// but only in debug builds.
/// ///
/// # Example /// # Example
@@ -42,7 +42,7 @@ use dioxus::prelude::*;
/// fn Wrapper(cx: Scope) -> Element { /// fn Wrapper(cx: Scope) -> Element {
/// render! { /// render! {
/// h1 { "App" } /// h1 { "App" }
/// Outlet {} // The content of child routes will be rendered here /// Outlet::<Route> {} // The content of child routes will be rendered here
/// } /// }
/// } /// }
/// ///
@@ -57,7 +57,7 @@ use dioxus::prelude::*;
/// ///
/// # fn App(cx: Scope) -> Element { /// # fn App(cx: Scope) -> Element {
/// # render! { /// # render! {
/// # Router { /// # Router::<Route> {
/// # config: || RouterConfig::default().history(MemoryHistory::with_initial_path(Route::Child {})) /// # config: || RouterConfig::default().history(MemoryHistory::with_initial_path(Route::Child {}))
/// # } /// # }
/// # } /// # }
@@ -67,6 +67,6 @@ use dioxus::prelude::*;
/// # let _ = vdom.rebuild(); /// # let _ = vdom.rebuild();
/// # assert_eq!(dioxus_ssr::render(&vdom), "<h1>App</h1><p>Child</p>"); /// # assert_eq!(dioxus_ssr::render(&vdom), "<h1>App</h1><p>Child</p>");
/// ``` /// ```
pub fn GenericOutlet<R: Routable + Clone>(cx: Scope) -> Element { pub fn Outlet<R: Routable + Clone>(cx: Scope) -> Element {
OutletContext::<R>::render(cx) OutletContext::<R>::render(cx)
} }
+17 -21
View File
@@ -1,13 +1,9 @@
use dioxus::prelude::*; use dioxus::prelude::*;
use std::{cell::RefCell, str::FromStr}; use std::{cell::RefCell, str::FromStr};
use crate::{ use crate::{prelude::Outlet, routable::Routable, router_cfg::RouterConfig};
prelude::{GenericOutlet, GenericRouterContext},
routable::Routable,
router_cfg::RouterConfig,
};
/// The config for [`GenericRouter`]. /// The config for [`Router`].
pub struct RouterConfigFactory<R: Routable> { pub struct RouterConfigFactory<R: Routable> {
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
config: RefCell<Option<Box<dyn FnOnce() -> RouterConfig<R>>>>, config: RefCell<Option<Box<dyn FnOnce() -> RouterConfig<R>>>>,
@@ -43,9 +39,9 @@ impl<R: Routable, F: FnOnce() -> RouterConfig<R> + 'static> From<F> for RouterCo
} }
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
/// The props for [`GenericRouter`]. /// The props for [`Router`].
#[derive(Props)] #[derive(Props)]
pub struct GenericRouterProps<R: Routable> pub struct RouterProps<R: Routable>
where where
<R as FromStr>::Err: std::fmt::Display, <R as FromStr>::Err: std::fmt::Display,
R: serde::Serialize + serde::de::DeserializeOwned, R: serde::Serialize + serde::de::DeserializeOwned,
@@ -55,9 +51,9 @@ where
} }
#[cfg(not(feature = "serde"))] #[cfg(not(feature = "serde"))]
/// The props for [`GenericRouter`]. /// The props for [`Router`].
#[derive(Props)] #[derive(Props)]
pub struct GenericRouterProps<R: Routable> pub struct RouterProps<R: Routable>
where where
<R as FromStr>::Err: std::fmt::Display, <R as FromStr>::Err: std::fmt::Display,
{ {
@@ -66,7 +62,7 @@ where
} }
#[cfg(not(feature = "serde"))] #[cfg(not(feature = "serde"))]
impl<R: Routable> Default for GenericRouterProps<R> impl<R: Routable> Default for RouterProps<R>
where where
<R as FromStr>::Err: std::fmt::Display, <R as FromStr>::Err: std::fmt::Display,
{ {
@@ -78,7 +74,7 @@ where
} }
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
impl<R: Routable> Default for GenericRouterProps<R> impl<R: Routable> Default for RouterProps<R>
where where
<R as FromStr>::Err: std::fmt::Display, <R as FromStr>::Err: std::fmt::Display,
R: serde::Serialize + serde::de::DeserializeOwned, R: serde::Serialize + serde::de::DeserializeOwned,
@@ -91,7 +87,7 @@ where
} }
#[cfg(not(feature = "serde"))] #[cfg(not(feature = "serde"))]
impl<R: Routable> PartialEq for GenericRouterProps<R> impl<R: Routable> PartialEq for RouterProps<R>
where where
<R as FromStr>::Err: std::fmt::Display, <R as FromStr>::Err: std::fmt::Display,
{ {
@@ -102,7 +98,7 @@ where
} }
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
impl<R: Routable> PartialEq for GenericRouterProps<R> impl<R: Routable> PartialEq for RouterProps<R>
where where
<R as FromStr>::Err: std::fmt::Display, <R as FromStr>::Err: std::fmt::Display,
R: serde::Serialize + serde::de::DeserializeOwned, R: serde::Serialize + serde::de::DeserializeOwned,
@@ -115,14 +111,14 @@ where
#[cfg(not(feature = "serde"))] #[cfg(not(feature = "serde"))]
/// 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 Router<R: Routable + Clone>(cx: Scope<RouterProps<R>>) -> Element
where where
<R as FromStr>::Err: std::fmt::Display, <R as FromStr>::Err: std::fmt::Display,
{ {
use crate::prelude::outlet::OutletContext; use crate::prelude::{outlet::OutletContext, RouterContext};
use_context_provider(cx, || { use_context_provider(cx, || {
GenericRouterContext::new( RouterContext::new(
(cx.props (cx.props
.config .config
.config .config
@@ -137,19 +133,19 @@ where
}); });
render! { render! {
GenericOutlet::<R> {} Outlet::<R> {}
} }
} }
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
/// 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 Router<R: Routable + Clone>(cx: Scope<RouterProps<R>>) -> Element
where where
<R as FromStr>::Err: std::fmt::Display, <R as FromStr>::Err: std::fmt::Display,
R: serde::Serialize + serde::de::DeserializeOwned, R: serde::Serialize + serde::de::DeserializeOwned,
{ {
use_context_provider(cx, || { use_context_provider(cx, || {
GenericRouterContext::new( RouterContext::new(
(cx.props (cx.props
.config .config
.config .config
@@ -164,6 +160,6 @@ where
}); });
render! { render! {
GenericOutlet::<R> {} Outlet::<R> {}
} }
} }
+11 -13
View File
@@ -1,10 +1,10 @@
use crate::prelude::{ExternalNavigationFailure, GenericRouterContext, NavigationTarget, Routable}; use crate::prelude::{ExternalNavigationFailure, IntoRoutable, RouterContext};
/// A view into the navigation state of a router. /// A view into the navigation state of a router.
#[derive(Clone)] #[derive(Clone)]
pub struct GenericNavigator<R: Routable>(pub(crate) GenericRouterContext<R>); pub struct Navigator(pub(crate) RouterContext);
impl<R: Routable> GenericNavigator<R> { impl Navigator {
/// Check whether there is a previous page to navigate back to. /// Check whether there is a previous page to navigate back to.
#[must_use] #[must_use]
pub fn can_go_back(&self) -> bool { pub fn can_go_back(&self) -> bool {
@@ -34,20 +34,18 @@ impl<R: Routable> GenericNavigator<R> {
/// 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( pub fn push(&self, target: impl Into<IntoRoutable>) -> Option<ExternalNavigationFailure> {
&self, let target = target.into();
target: impl Into<NavigationTarget<R>>, let as_any_navigation_target = self.0.resolve_into_routable(&target);
) -> Option<ExternalNavigationFailure> { self.0.push_any(as_any_navigation_target)
self.0.push(target)
} }
/// 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( pub fn replace(&self, target: impl Into<IntoRoutable>) -> Option<ExternalNavigationFailure> {
&self, let target = target.into();
target: impl Into<NavigationTarget<R>>, let as_any_navigation_target = self.0.resolve_into_routable(&target);
) -> Option<ExternalNavigationFailure> { self.0.replace_any(as_any_navigation_target)
self.0.replace(target)
} }
} }
+2 -2
View File
@@ -31,7 +31,7 @@ impl<R> OutletContext<R> {
where where
R: Routable + Clone, R: Routable + Clone,
{ {
let router = use_router_internal::<R>(cx) let router = use_router_internal(cx)
.as_ref() .as_ref()
.expect("Outlet must be inside of a router"); .expect("Outlet must be inside of a router");
let outlet: &OutletContext<R> = use_outlet_context(cx); let outlet: &OutletContext<R> = use_outlet_context(cx);
@@ -51,6 +51,6 @@ impl<R> OutletContext<R> {
} }
} }
router.current().render(cx, current_level) router.current::<R>().render(cx, current_level)
} }
} }
+207 -46
View File
@@ -1,4 +1,5 @@
use std::{ use std::{
any::Any,
collections::HashSet, collections::HashSet,
sync::{Arc, RwLock, RwLockWriteGuard}, sync::{Arc, RwLock, RwLockWriteGuard},
}; };
@@ -6,7 +7,9 @@ use std::{
use dioxus::prelude::*; use dioxus::prelude::*;
use crate::{ use crate::{
history::HistoryProvider, navigation::NavigationTarget, routable::Routable, navigation::NavigationTarget,
prelude::{AnyHistoryProvider, IntoRoutable},
routable::Routable,
router_cfg::RouterConfig, router_cfg::RouterConfig,
}; };
@@ -15,52 +18,35 @@ use crate::{
pub struct ExternalNavigationFailure(String); pub struct ExternalNavigationFailure(String);
/// A function the router will call after every routing update. /// A function the router will call after every routing update.
pub(crate) type RoutingCallback<R> = pub(crate) type RoutingCallback<R> = Arc<dyn Fn(LinkContext<R>) -> Option<NavigationTarget<R>>>;
Arc<dyn Fn(GenericRouterContext<R>) -> Option<NavigationTarget<R>>>; pub(crate) type AnyRoutingCallback =
Arc<dyn Fn(RouterContext) -> Option<NavigationTarget<Box<dyn Any>>>>;
struct MutableRouterState<R> struct MutableRouterState {
where
R: Routable,
{
/// The current prefix. /// The current prefix.
prefix: Option<String>, prefix: Option<String>,
history: Box<dyn HistoryProvider<R>>, history: Box<dyn AnyHistoryProvider>,
unresolved_error: Option<ExternalNavigationFailure>, unresolved_error: Option<ExternalNavigationFailure>,
} }
/// A collection of router data that manages all routing functionality. /// A collection of router data that manages all routing functionality.
pub struct GenericRouterContext<R> #[derive(Clone)]
where pub struct RouterContext {
R: Routable, state: Arc<RwLock<MutableRouterState>>,
{
state: Arc<RwLock<MutableRouterState<R>>>,
subscribers: Arc<RwLock<HashSet<ScopeId>>>, subscribers: Arc<RwLock<HashSet<ScopeId>>>,
subscriber_update: Arc<dyn Fn(ScopeId)>, subscriber_update: Arc<dyn Fn(ScopeId)>,
routing_callback: Option<RoutingCallback<R>>, routing_callback: Option<AnyRoutingCallback>,
failure_external_navigation: fn(Scope) -> Element, failure_external_navigation: fn(Scope) -> Element,
any_route_to_string: fn(&dyn Any) -> String,
} }
impl<R: Routable> Clone for GenericRouterContext<R> { impl RouterContext {
fn clone(&self) -> Self { pub(crate) fn new<R: Routable + 'static>(
Self {
state: self.state.clone(),
subscribers: self.subscribers.clone(),
subscriber_update: self.subscriber_update.clone(),
routing_callback: self.routing_callback.clone(),
failure_external_navigation: self.failure_external_navigation,
}
}
}
impl<R> GenericRouterContext<R>
where
R: Routable,
{
pub(crate) fn new(
mut cfg: RouterConfig<R>, mut cfg: RouterConfig<R>,
mark_dirty: Arc<dyn Fn(ScopeId) + Sync + Send>, mark_dirty: Arc<dyn Fn(ScopeId) + Sync + Send>,
) -> Self ) -> Self
@@ -82,9 +68,37 @@ where
subscribers: subscribers.clone(), subscribers: subscribers.clone(),
subscriber_update, subscriber_update,
routing_callback: cfg.on_update, routing_callback: cfg.on_update.map(|update| {
Arc::new(move |ctx| {
let ctx = LinkContext {
inner: ctx,
_marker: std::marker::PhantomData,
};
update(ctx).map(|t| match t {
NavigationTarget::Internal(r) => {
NavigationTarget::Internal(Box::new(r) as Box<dyn Any>)
}
NavigationTarget::External(s) => NavigationTarget::External(s),
})
})
as Arc<dyn Fn(RouterContext) -> Option<NavigationTarget<Box<dyn Any>>>>
}),
failure_external_navigation: cfg.failure_external_navigation, failure_external_navigation: cfg.failure_external_navigation,
any_route_to_string: |route| {
route
.downcast_ref::<R>()
.unwrap_or_else(|| {
panic!(
"Route is not of the expected type: {}\n found typeid: {:?}\n expected typeid: {:?}",
std::any::type_name::<R>(),
route.type_id(),
std::any::TypeId::of::<R>()
)
})
.to_string()
},
}; };
// set the updater // set the updater
@@ -100,6 +114,11 @@ where
myself myself
} }
pub(crate) fn route_from_str(&self, route: &str) -> Result<Box<dyn Any>, String> {
let state = self.state.read().unwrap();
state.history.parse_route(route)
}
/// Check whether there is a previous page to navigate back to. /// Check whether there is a previous page to navigate back to.
#[must_use] #[must_use]
pub fn can_go_back(&self) -> bool { pub fn can_go_back(&self) -> bool {
@@ -134,14 +153,10 @@ where
self.change_route(); self.change_route();
} }
/// Push a new location. pub(crate) fn push_any(
///
/// The previous location will be available to go back to.
pub fn push(
&self, &self,
target: impl Into<NavigationTarget<R>>, target: NavigationTarget<Box<dyn Any>>,
) -> Option<ExternalNavigationFailure> { ) -> Option<ExternalNavigationFailure> {
let target = target.into();
match target { match target {
NavigationTarget::Internal(p) => { NavigationTarget::Internal(p) => {
let mut state = self.state_mut(); let mut state = self.state_mut();
@@ -153,10 +168,29 @@ where
self.change_route() self.change_route()
} }
/// Push a new location.
///
/// The previous location will be available to go back to.
pub fn push<R: Routable>(
&self,
target: impl Into<NavigationTarget<R>>,
) -> Option<ExternalNavigationFailure> {
let target = target.into();
match target {
NavigationTarget::Internal(p) => {
let mut state = self.state_mut();
state.history.push(Box::new(p))
}
NavigationTarget::External(e) => return self.external(e),
}
self.change_route()
}
/// 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( pub fn replace<R: Routable>(
&self, &self,
target: impl Into<NavigationTarget<R>>, target: impl Into<NavigationTarget<R>>,
) -> Option<ExternalNavigationFailure> { ) -> Option<ExternalNavigationFailure> {
@@ -165,7 +199,7 @@ where
{ {
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(Box::new(p)),
NavigationTarget::External(e) => return self.external(e), NavigationTarget::External(e) => return self.external(e),
} }
} }
@@ -173,12 +207,55 @@ where
self.change_route() self.change_route()
} }
pub(crate) fn replace_any(
&self,
target: NavigationTarget<Box<dyn Any>>,
) -> Option<ExternalNavigationFailure> {
match target {
NavigationTarget::Internal(p) => {
let mut state = self.state_mut();
state.history.replace(p)
}
NavigationTarget::External(e) => return self.external(e),
}
self.change_route()
}
/// The route that is currently active. /// The route that is currently active.
pub fn current(&self) -> R pub fn current<R: Routable>(&self) -> R {
where *self
R: Clone, .state
{ .read()
self.state.read().unwrap().history.current_route() .unwrap()
.history
.current_route()
.downcast::<R>()
.unwrap()
}
/// The route that is currently active.
pub fn current_route_string(&self) -> String {
self.any_route_to_string(&*self.state.read().unwrap().history.current_route())
}
pub(crate) fn any_route_to_string(&self, route: &dyn Any) -> String {
(self.any_route_to_string)(route)
}
pub(crate) fn resolve_into_routable(
&self,
into_routable: &IntoRoutable,
) -> NavigationTarget<Box<dyn Any>> {
let href = match into_routable {
IntoRoutable::FromStr(url) => url.to_string(),
IntoRoutable::Route(route) => self.any_route_to_string(&**route),
};
let parsed_route: NavigationTarget<Box<dyn Any>> = match self.route_from_str(&href) {
Ok(route) => NavigationTarget::Internal(route),
Err(err) => NavigationTarget::External(err),
};
parsed_route
} }
/// The prefix that is currently active. /// The prefix that is currently active.
@@ -201,7 +278,7 @@ where
} }
} }
fn state_mut(&self) -> RwLockWriteGuard<MutableRouterState<R>> { fn state_mut(&self) -> RwLockWriteGuard<MutableRouterState> {
self.state.write().unwrap() self.state.write().unwrap()
} }
@@ -254,3 +331,87 @@ where
None None
} }
} }
pub struct LinkContext<R> {
inner: RouterContext,
_marker: std::marker::PhantomData<R>,
}
impl<R> LinkContext<R>
where
R: Routable,
{
/// Check whether there is a previous page to navigate back to.
#[must_use]
pub fn can_go_back(&self) -> bool {
self.inner.can_go_back()
}
/// Check whether there is a future page to navigate forward to.
#[must_use]
pub fn can_go_forward(&self) -> bool {
self.inner.can_go_forward()
}
/// Go back to the previous location.
///
/// Will fail silently if there is no previous location to go to.
pub fn go_back(&self) {
self.inner.go_back();
}
/// Go back to the next location.
///
/// Will fail silently if there is no next location to go to.
pub fn go_forward(&self) {
self.inner.go_forward();
}
/// Push a new location.
///
/// The previous location will be available to go back to.
pub fn push(
&self,
target: impl Into<NavigationTarget<R>>,
) -> Option<ExternalNavigationFailure> {
self.inner.push(target)
}
/// Replace the current location.
///
/// The previous location will **not** be available to go back to.
pub fn replace(
&self,
target: impl Into<NavigationTarget<R>>,
) -> Option<ExternalNavigationFailure> {
self.inner.replace(target)
}
/// The route that is currently active.
pub fn current(&self) -> R
where
R: Clone,
{
self.inner.current()
}
/// The prefix that is currently active.
pub fn prefix(&self) -> Option<String> {
self.inner.prefix()
}
/// Manually subscribe to the current route
pub fn subscribe(&self, id: ScopeId) {
self.inner.subscribe(id)
}
/// Manually unsubscribe from the current route
pub fn unsubscribe(&self, id: ScopeId) {
self.inner.unsubscribe(id)
}
/// Clear any unresolved errors
pub fn clear_error(&self) {
self.inner.clear_error()
}
}
+120 -1
View File
@@ -10,7 +10,7 @@
//! 1) [`MemoryHistory`] for desktop/mobile/ssr platforms //! 1) [`MemoryHistory`] for desktop/mobile/ssr platforms
//! 2) [`WebHistory`] for web platforms //! 2) [`WebHistory`] for web platforms
use std::sync::Arc; use std::{any::Any, sync::Arc};
mod memory; mod memory;
pub use memory::*; pub use memory::*;
@@ -277,3 +277,122 @@ pub trait HistoryProvider<R: Routable> {
#[allow(unused_variables)] #[allow(unused_variables)]
fn updater(&mut self, callback: Arc<dyn Fn() + Send + Sync>) {} fn updater(&mut self, callback: Arc<dyn Fn() + Send + Sync>) {}
} }
pub(crate) trait AnyHistoryProvider {
fn parse_route(&self, route: &str) -> Result<Box<dyn Any>, String>;
#[must_use]
fn accepts_type_id(&self, type_id: &std::any::TypeId) -> bool;
#[must_use]
fn current_route(&self) -> Box<dyn Any>;
#[must_use]
fn current_prefix(&self) -> Option<String> {
None
}
#[must_use]
fn can_go_back(&self) -> bool {
true
}
fn go_back(&mut self);
#[must_use]
fn can_go_forward(&self) -> bool {
true
}
fn go_forward(&mut self);
fn push(&mut self, route: Box<dyn Any>);
fn replace(&mut self, path: Box<dyn Any>);
#[allow(unused_variables)]
fn external(&mut self, url: String) -> bool {
false
}
#[allow(unused_variables)]
fn updater(&mut self, callback: Arc<dyn Fn() + Send + Sync>) {}
}
pub(crate) struct AnyHistoryProviderImplWrapper<R, H> {
inner: H,
_marker: std::marker::PhantomData<R>,
}
impl<R, H> AnyHistoryProviderImplWrapper<R, H> {
pub fn new(inner: H) -> Self {
Self {
inner,
_marker: std::marker::PhantomData,
}
}
}
impl<R, H: Default> Default for AnyHistoryProviderImplWrapper<R, H> {
fn default() -> Self {
Self::new(H::default())
}
}
impl<R, H> AnyHistoryProvider for AnyHistoryProviderImplWrapper<R, H>
where
R: Routable,
<R as std::str::FromStr>::Err: std::fmt::Display,
H: HistoryProvider<R>,
{
fn parse_route(&self, route: &str) -> Result<Box<dyn Any>, String> {
R::from_str(route)
.map_err(|err| err.to_string())
.map(|route| Box::new(route) as Box<dyn Any>)
}
fn accepts_type_id(&self, type_id: &std::any::TypeId) -> bool {
type_id == &std::any::TypeId::of::<R>()
}
fn current_route(&self) -> Box<dyn Any> {
let route = self.inner.current_route();
Box::new(route)
}
fn current_prefix(&self) -> Option<String> {
self.inner.current_prefix()
}
fn can_go_back(&self) -> bool {
self.inner.can_go_back()
}
fn go_back(&mut self) {
self.inner.go_back()
}
fn can_go_forward(&self) -> bool {
self.inner.can_go_forward()
}
fn go_forward(&mut self) {
self.inner.go_forward()
}
fn push(&mut self, route: Box<dyn Any>) {
self.inner.push(*route.downcast().unwrap())
}
fn replace(&mut self, path: Box<dyn Any>) {
self.inner.replace(*path.downcast().unwrap())
}
fn external(&mut self, url: String) -> bool {
self.inner.external(url)
}
fn updater(&mut self, callback: Arc<dyn Fn() + Send + Sync>) {
self.inner.updater(callback)
}
}
+5 -8
View File
@@ -1,9 +1,6 @@
use dioxus::prelude::ScopeState; use dioxus::prelude::ScopeState;
use crate::{ use crate::prelude::{Navigator, RouterContext};
prelude::{GenericNavigator, GenericRouterContext},
routable::Routable,
};
/// A hook that provides access to the navigator to change the router history. Unlike [`use_router`], this hook will not cause a rerender when the current route changes /// A hook that provides access to the navigator to change the router history. Unlike [`use_router`], this hook will not cause a rerender when the current route changes
/// ///
@@ -22,7 +19,7 @@ use crate::{
/// ///
/// fn App(cx: Scope) -> Element { /// fn App(cx: Scope) -> Element {
/// render! { /// render! {
/// Router {} /// Router::<Route> {}
/// } /// }
/// } /// }
/// ///
@@ -50,12 +47,12 @@ use crate::{
/// # let mut vdom = VirtualDom::new(App); /// # let mut vdom = VirtualDom::new(App);
/// # let _ = vdom.rebuild(); /// # let _ = vdom.rebuild();
/// ``` /// ```
pub fn use_generic_navigator<R: Routable + Clone>(cx: &ScopeState) -> &GenericNavigator<R> { pub fn use_navigator(cx: &ScopeState) -> &Navigator {
&*cx.use_hook(|| { &*cx.use_hook(|| {
let router = cx let router = cx
.consume_context::<GenericRouterContext<R>>() .consume_context::<RouterContext>()
.expect("Must be called in a descendant of a Router component"); .expect("Must be called in a descendant of a Router component");
GenericNavigator(router) Navigator(router)
}) })
} }
+4 -4
View File
@@ -8,11 +8,11 @@ use crate::utils::use_router_internal::use_router_internal;
/// > The Routable macro will define a version of this hook with an explicit type. /// > The Routable macro will define a version of this hook with an explicit type.
/// ///
/// # Return values /// # Return values
/// - None, when not called inside a [`GenericRouter`] component. /// - None, when not called inside a [`Link`] component.
/// - Otherwise the current route. /// - Otherwise the current route.
/// ///
/// # Panic /// # Panic
/// - When the calling component is not nested within a [`GenericRouter`] component durring a debug build. /// - When the calling component is not nested within a [`Link`] component durring a debug build.
/// ///
/// # Example /// # Example
/// ```rust /// ```rust
@@ -28,7 +28,7 @@ use crate::utils::use_router_internal::use_router_internal;
/// fn App(cx: Scope) -> Element { /// fn App(cx: Scope) -> Element {
/// render! { /// render! {
/// h1 { "App" } /// h1 { "App" }
/// Router {} /// Router::<Route> {}
/// } /// }
/// } /// }
/// ///
@@ -45,7 +45,7 @@ use crate::utils::use_router_internal::use_router_internal;
/// # let _ = vdom.rebuild(); /// # let _ = vdom.rebuild();
/// # assert_eq!(dioxus_ssr::render(&vdom), "<h1>App</h1><h2>Current Path</h2><p>/</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> { 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 => {
+2 -5
View File
@@ -1,12 +1,9 @@
use dioxus::prelude::ScopeState; use dioxus::prelude::ScopeState;
use crate::{ use crate::{prelude::RouterContext, utils::use_router_internal::use_router_internal};
prelude::GenericRouterContext, routable::Routable,
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.
pub fn use_generic_router<R: Routable + Clone>(cx: &ScopeState) -> &GenericRouterContext<R> { pub fn use_router(cx: &ScopeState) -> &RouterContext {
use_router_internal(cx) use_router_internal(cx)
.as_ref() .as_ref()
.expect("use_route must have access to a router") .expect("use_route must have access to a router")
+1 -1
View File
@@ -95,7 +95,7 @@ where
{ {
let path = path.clone(); let path = path.clone();
render! { render! {
GenericRouter::<R> { Router::<R> {
config: || RouterConfig::default().history(MemoryHistory::with_initial_path(path)) config: || RouterConfig::default().history(MemoryHistory::with_initial_path(path))
} }
} }
+1 -1
View File
@@ -11,7 +11,7 @@ use crate::routable::Routable;
/// A target for the router to navigate to. /// A target for the router to navigate to.
#[derive(Clone, PartialEq, Eq, Debug)] #[derive(Clone, PartialEq, Eq, Debug)]
pub enum NavigationTarget<R: Routable> { pub enum NavigationTarget<R> {
/// An internal path that the router can navigate to by itself. /// An internal path that the router can navigate to by itself.
/// ///
/// ```rust /// ```rust
-1
View File
@@ -141,7 +141,6 @@ pub trait Routable: std::fmt::Display + std::str::FromStr + Clone + 'static {
let self_segments = self_str.split('/'); let self_segments = self_str.split('/');
let other_segments = other_str.split('/'); let other_segments = other_str.split('/');
for (self_seg, other_seg) in self_segments.zip(other_segments) { for (self_seg, other_seg) in self_segments.zip(other_segments) {
dbg!(self_seg, other_seg);
if self_seg != other_seg { if self_seg != other_seg {
return false; return false;
} }
+12 -8
View File
@@ -26,7 +26,7 @@ use crate::prelude::*;
/// ``` /// ```
pub struct RouterConfig<R: Routable> { pub struct RouterConfig<R: Routable> {
pub(crate) failure_external_navigation: fn(Scope) -> Element, pub(crate) failure_external_navigation: fn(Scope) -> Element,
pub(crate) history: Option<Box<dyn HistoryProvider<R>>>, pub(crate) history: Option<Box<dyn AnyHistoryProvider>>,
pub(crate) on_update: Option<RoutingCallback<R>>, pub(crate) on_update: Option<RoutingCallback<R>>,
} }
@@ -69,7 +69,7 @@ where
{ {
fn default() -> Self { fn default() -> Self {
Self { Self {
failure_external_navigation: FailureExternalNavigation::<R>, failure_external_navigation: FailureExternalNavigation,
history: None, history: None,
on_update: None, on_update: None,
} }
@@ -81,18 +81,22 @@ impl<R: Routable + Clone> RouterConfig<R>
where where
<R as std::str::FromStr>::Err: std::fmt::Display, <R as std::str::FromStr>::Err: std::fmt::Display,
{ {
pub(crate) fn take_history(&mut self) -> Box<dyn HistoryProvider<R>> { pub(crate) fn take_history(&mut self) -> Box<dyn AnyHistoryProvider> {
self.history.take().unwrap_or_else(|| { self.history.take().unwrap_or_else(|| {
#[cfg(all(target_arch = "wasm32", feature = "web"))] #[cfg(all(target_arch = "wasm32", feature = "web"))]
let history = Box::<WebHistory<R>>::default(); let history = Box::<AnyHistoryProviderImplWrapper<R, WebHistory<R>>>::default();
#[cfg(not(all(target_arch = "wasm32", feature = "web")))] #[cfg(not(all(target_arch = "wasm32", feature = "web")))]
let history = Box::<MemoryHistory<R>>::default(); let history = Box::<AnyHistoryProviderImplWrapper<R, MemoryHistory<R>>>::default();
history history
}) })
} }
} }
impl<R: Routable> RouterConfig<R> { impl<R> RouterConfig<R>
where
R: Routable,
<R as std::str::FromStr>::Err: std::fmt::Display,
{
/// A function to be called whenever the routing is updated. /// A function to be called whenever the routing is updated.
/// ///
/// 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
@@ -108,7 +112,7 @@ impl<R: Routable> RouterConfig<R> {
/// Defaults to [`None`]. /// Defaults to [`None`].
pub fn on_update( pub fn on_update(
self, self,
callback: impl Fn(GenericRouterContext<R>) -> Option<NavigationTarget<R>> + 'static, callback: impl Fn(LinkContext<R>) -> Option<NavigationTarget<R>> + 'static,
) -> Self { ) -> Self {
Self { Self {
on_update: Some(Arc::new(callback)), on_update: Some(Arc::new(callback)),
@@ -121,7 +125,7 @@ impl<R: Routable> RouterConfig<R> {
/// Defaults to a default [`MemoryHistory`]. /// Defaults to a default [`MemoryHistory`].
pub fn history(self, history: impl HistoryProvider<R> + 'static) -> Self { pub fn history(self, history: impl HistoryProvider<R> + 'static) -> Self {
Self { Self {
history: Some(Box::new(history)), history: Some(Box::new(AnyHistoryProviderImplWrapper::new(history))),
..self ..self
} }
} }
@@ -1,6 +1,6 @@
use dioxus::prelude::{ScopeId, ScopeState}; use dioxus::prelude::{ScopeId, ScopeState};
use crate::{contexts::router::GenericRouterContext, prelude::*}; use crate::prelude::*;
/// A private hook to subscribe to the router. /// A private hook to subscribe to the router.
/// ///
@@ -8,13 +8,11 @@ use crate::{contexts::router::GenericRouterContext, prelude::*};
/// 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 [`GenericRouter`] component. /// - [`None`], when the current component isn't a descendant of a [`Link`] component.
/// - Otherwise [`Some`]. /// - Otherwise [`Some`].
pub(crate) fn use_router_internal<R: Routable>( pub(crate) fn use_router_internal(cx: &ScopeState) -> &Option<RouterContext> {
cx: &ScopeState,
) -> &Option<GenericRouterContext<R>> {
let inner = cx.use_hook(|| { let inner = cx.use_hook(|| {
let router = cx.consume_context::<GenericRouterContext<R>>()?; let router = cx.consume_context::<RouterContext>()?;
let id = cx.scope_id(); let id = cx.scope_id();
router.subscribe(id); router.subscribe(id);
@@ -24,12 +22,12 @@ pub(crate) fn use_router_internal<R: Routable>(
cx.use_hook(|| inner.as_ref().map(|s| s.router.clone())) cx.use_hook(|| inner.as_ref().map(|s| s.router.clone()))
} }
struct Subscription<R: Routable> { struct Subscription {
router: GenericRouterContext<R>, router: RouterContext,
id: ScopeId, id: ScopeId,
} }
impl<R: Routable> Drop for Subscription<R> { impl Drop for Subscription {
fn drop(&mut self) { fn drop(&mut self) {
self.router.unsubscribe(self.id); self.router.unsubscribe(self.id);
} }
+3 -3
View File
@@ -33,7 +33,7 @@ where
{ {
render! { render! {
h1 { "App" } h1 { "App" }
GenericRouter::<R> { Router::<R> {
config: || RouterConfig::default().history(MemoryHistory::default()) config: || RouterConfig::default().history(MemoryHistory::default())
} }
} }
@@ -97,7 +97,7 @@ fn href_external() {
fn Root(cx: Scope) -> Element { fn Root(cx: Scope) -> Element {
render! { render! {
Link { Link {
to: NavigationTarget::External("https://dioxuslabs.com/".into()), to: "https://dioxuslabs.com/",
"Link" "Link"
} }
} }
@@ -318,7 +318,7 @@ fn with_new_tab_external() {
fn Root(cx: Scope) -> Element { fn Root(cx: Scope) -> Element {
render! { render! {
Link { Link {
to: NavigationTarget::External("https://dioxuslabs.com/".into()), to: "https://dioxuslabs.com/",
new_tab: true, new_tab: true,
"Link" "Link"
} }
+3 -3
View File
@@ -37,7 +37,7 @@ fn prepare(path: impl Into<String>) -> VirtualDom {
fn App(cx: Scope<AppProps>) -> Element { fn App(cx: Scope<AppProps>) -> Element {
render! { render! {
h1 { "App" } h1 { "App" }
Router { Router::<Route> {
config: { config: {
let path = cx.props.path.parse().unwrap(); let path = cx.props.path.parse().unwrap();
move || RouterConfig::default().history(MemoryHistory::with_initial_path(path)) move || RouterConfig::default().history(MemoryHistory::with_initial_path(path))
@@ -57,7 +57,7 @@ fn prepare(path: impl Into<String>) -> VirtualDom {
fn Fixed(cx: Scope) -> Element { fn Fixed(cx: Scope) -> Element {
render! { render! {
h2 { "Fixed" } h2 { "Fixed" }
Outlet { } Outlet::<Route> { }
} }
} }
@@ -79,7 +79,7 @@ fn prepare(path: impl Into<String>) -> VirtualDom {
fn Parameter(cx: Scope, id: u8) -> Element { fn Parameter(cx: Scope, id: u8) -> Element {
render! { render! {
h2 { "Parameter {id}" } h2 { "Parameter {id}" }
Outlet { } Outlet::<Route> { }
} }
} }