mirror of
https://github.com/DioxusLabs/dioxus
synced 2025-02-16 21:58:25 +00:00
it almost lives?
This commit is contained in:
parent
d637ef187c
commit
5f0dd3af3e
43 changed files with 506 additions and 244 deletions
|
@ -26,7 +26,7 @@ Unlike other routers in the Rust ecosystem, our router is built declaratively. T
|
|||
```rust, no_run
|
||||
rsx!{
|
||||
// All of our routes will be rendered inside this Router component
|
||||
Router {
|
||||
Router::<Route> {
|
||||
// if the current location is "/home", render the Home component
|
||||
Route { to: "/home", Home {} }
|
||||
// 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
|
||||
rsx!{
|
||||
Router {
|
||||
Router::<Route> {
|
||||
Route { to: "/home", Home {} }
|
||||
Route { to: "/blog", Blog {} }
|
||||
// if the current location doesn't match any of the above routes, render the NotFound component
|
||||
|
@ -56,7 +56,7 @@ rsx!{
|
|||
|
||||
```rust, no_run
|
||||
rsx!{
|
||||
Router {
|
||||
Router::<Route> {
|
||||
Route { to: "/home", Home {} }
|
||||
Route { to: "/blog", Blog {} }
|
||||
// if the current location doesn't match any of the above routes, redirect to "/home"
|
||||
|
|
|
@ -28,7 +28,7 @@ Ao contrário de outros roteadores no ecossistema Rust, nosso roteador é constr
|
|||
|
||||
```rust, no_run
|
||||
rsx!{
|
||||
Router {
|
||||
Router::<Route> {
|
||||
Route { to: "/home", Home {} }
|
||||
Route { to: "/blog", Blog {} }
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ Podemos corrigir isso de duas maneiras:
|
|||
|
||||
```rust, no_run
|
||||
rsx!{
|
||||
Router {
|
||||
Router::<Route> {
|
||||
Route { to: "/home", Home {} }
|
||||
Route { to: "/blog", Blog {} }
|
||||
Route { to: "", NotFound {} }
|
||||
|
@ -55,7 +55,7 @@ rsx!{
|
|||
|
||||
```rust, no_run
|
||||
rsx!{
|
||||
Router {
|
||||
Router::<Route> {
|
||||
Route { to: "/home", Home {} }
|
||||
Route { to: "/blog", Blog {} }
|
||||
Redirect { from: "", to: "/home" }
|
||||
|
|
|
@ -72,7 +72,7 @@ Apps with routers are _really_ simple now. It's easy to compose the "Router", a
|
|||
```rust, no_run
|
||||
fn app(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
Router {
|
||||
Router::<Route> {
|
||||
onchange: move |_| log::info!("Route changed!"),
|
||||
ul {
|
||||
Link { to: "/", li { "Go home!" } }
|
||||
|
|
|
@ -17,7 +17,7 @@ enum Route {
|
|||
#[inline_props]
|
||||
fn App(cx: Scope) -> Element {
|
||||
render! {
|
||||
Router {}
|
||||
Router::<Route> {}
|
||||
}
|
||||
}
|
||||
// ANCHOR_END: app
|
||||
|
|
|
@ -35,7 +35,7 @@ enum Route {
|
|||
|
||||
fn App(cx: Scope) -> Element {
|
||||
render! {
|
||||
Router {}
|
||||
Router::<Route> {}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -48,7 +48,7 @@ fn NavBar(cx: Scope) -> Element {
|
|||
li { Link { to: Route::BlogList {}, "Blog" } }
|
||||
}
|
||||
}
|
||||
Outlet {}
|
||||
Outlet::<Route> {}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -64,7 +64,7 @@ fn Home(cx: Scope) -> Element {
|
|||
fn Blog(cx: Scope) -> Element {
|
||||
render! {
|
||||
h1 { "Blog" }
|
||||
Outlet {}
|
||||
Outlet::<Route> {}
|
||||
}
|
||||
}
|
||||
// ANCHOR_END: blog
|
||||
|
|
|
@ -20,7 +20,7 @@ fn main() {}
|
|||
fn GoToDioxus(cx: Scope) -> Element {
|
||||
render! {
|
||||
Link {
|
||||
to: NavigationTarget::External("https://dioxuslabs.com".into()),
|
||||
to: "https://dioxuslabs.com",
|
||||
"ExternalTarget target"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ enum Route {
|
|||
#[inline_props]
|
||||
fn App(cx: Scope) -> Element {
|
||||
render! {
|
||||
Router {}
|
||||
Router::<Route> {}
|
||||
}
|
||||
}
|
||||
// ANCHOR_END: app
|
||||
|
|
|
@ -39,7 +39,7 @@ enum Route {
|
|||
|
||||
fn App(cx: Scope) -> Element {
|
||||
render! {
|
||||
Router {}
|
||||
Router::<Route> {}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -52,7 +52,7 @@ fn NavBar(cx: Scope) -> Element {
|
|||
li { Link { to: Route::BlogList {}, "Blog" } }
|
||||
}
|
||||
}
|
||||
Outlet {}
|
||||
Outlet::<Route> {}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -67,7 +67,7 @@ fn Home(cx: Scope) -> Element {
|
|||
fn Blog(cx: Scope) -> Element {
|
||||
render! {
|
||||
h1 { "Blog" }
|
||||
Outlet {}
|
||||
Outlet::<Route> {}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ enum Route {
|
|||
#[inline_props]
|
||||
fn App(cx: Scope) -> Element {
|
||||
render! {
|
||||
Router {
|
||||
Router::<Route> {
|
||||
config: || RouterConfig::default().history(WebHistory::default())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ fn NavBar(cx: Scope) -> Element {
|
|||
}
|
||||
}
|
||||
}
|
||||
Outlet {}
|
||||
Outlet::<Route> {}
|
||||
}
|
||||
}
|
||||
// ANCHOR_END: nav
|
||||
|
@ -41,7 +41,7 @@ fn NavBar(cx: Scope) -> Element {
|
|||
#[inline_props]
|
||||
fn App(cx: Scope) -> Element {
|
||||
render! {
|
||||
Router {}
|
||||
Router::<Route> {}
|
||||
}
|
||||
}
|
||||
// ANCHOR_END: app
|
||||
|
|
|
@ -14,7 +14,7 @@ enum Route {
|
|||
#[inline_props]
|
||||
fn App(cx: Scope) -> Element {
|
||||
render! {
|
||||
Router {}
|
||||
Router::<Route> {}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
Outlet {}
|
||||
Outlet::<Route> {}
|
||||
}
|
||||
}
|
||||
// ANCHOR_END: nav
|
||||
|
@ -35,7 +35,7 @@ fn NavBar(cx: Scope) -> Element {
|
|||
#[inline_props]
|
||||
fn App(cx: Scope) -> Element {
|
||||
render! {
|
||||
Router {}
|
||||
Router::<Route> {}
|
||||
}
|
||||
}
|
||||
// ANCHOR_END: app
|
||||
|
|
|
@ -16,7 +16,7 @@ fn Wrapper(cx: Scope) -> Element {
|
|||
render! {
|
||||
header { "header" }
|
||||
// The index route will be rendered here
|
||||
Outlet { }
|
||||
Outlet::<Route> { }
|
||||
footer { "footer" }
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ fn Index(cx: Scope) -> Element {
|
|||
|
||||
fn App(cx: Scope) -> Element {
|
||||
render! {
|
||||
Router {}
|
||||
Router::<Route> {}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ enum Route {
|
|||
#[inline_props]
|
||||
fn App(cx: Scope) -> Element {
|
||||
render! {
|
||||
Router {
|
||||
Router::<Route> {
|
||||
config: || RouterConfig::default().history(WebHistory::default())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ fn Index(cx: Scope) -> Element {
|
|||
|
||||
fn app(cx: Scope) -> Element {
|
||||
render! {
|
||||
Router {
|
||||
Router::<Route> {
|
||||
config: || RouterConfig::default().on_update(|state|{
|
||||
(state.current() == Route::Index {}).then_some(
|
||||
NavigationTarget::Internal(Route::Home {})
|
||||
|
|
|
@ -46,7 +46,7 @@ fn App(cx: Scope) -> Element {
|
|||
|
||||
h1 { "Dioxus CRM Example" }
|
||||
|
||||
Router {}
|
||||
Router::<Route> {}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ fn main() {
|
|||
|
||||
fn app(cx: Scope) -> Element {
|
||||
render! {
|
||||
Router {}
|
||||
Router::<Route> {}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -40,7 +40,7 @@ enum Route {
|
|||
fn Footer(cx: Scope) -> Element {
|
||||
render! {
|
||||
div {
|
||||
Outlet { }
|
||||
Outlet::<Route> { }
|
||||
|
||||
p {
|
||||
"----"
|
||||
|
|
|
@ -23,7 +23,7 @@ fn app(cx: Scope) -> Element {
|
|||
}
|
||||
}
|
||||
div {
|
||||
Router {}
|
||||
Router::<Route> {}
|
||||
}
|
||||
))
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ fn Header(cx: Scope) -> Element {
|
|||
li { Link { to: Route::Home {}, "home" } }
|
||||
li { Link { to: Route::Settings {}, "settings" } }
|
||||
}
|
||||
Outlet {}
|
||||
Outlet::<Route> {}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ enum Route {
|
|||
|
||||
fn App(cx: Scope) -> Element {
|
||||
render! {
|
||||
Router {}
|
||||
Router::<Route> {}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -52,7 +52,7 @@ fn NavBar(cx: Scope) -> Element {
|
|||
li { Link { to: Route::BlogList {}, "Blog" } }
|
||||
}
|
||||
}
|
||||
Outlet {}
|
||||
Outlet::<Route> {}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -67,7 +67,7 @@ fn Home(cx: Scope) -> Element {
|
|||
fn Blog(cx: Scope) -> Element {
|
||||
render! {
|
||||
h1 { "Blog" }
|
||||
Outlet {}
|
||||
Outlet::<Route> {}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ fn main() {
|
|||
|
||||
fn app(cx: Scope) -> Element {
|
||||
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: "james".into() }, "james amazing' blog" } }
|
||||
}
|
||||
Outlet {}
|
||||
Outlet::<Route> {}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ where
|
|||
|
||||
let cfg = *cx.props;
|
||||
render! {
|
||||
dioxus_router::prelude::GenericRouter::<R> {
|
||||
dioxus_router::prelude::Router::<R> {
|
||||
config: move || {
|
||||
RouterConfig::default()
|
||||
.failure_external_navigation(cfg.failure_external_navigation)
|
||||
|
@ -54,7 +54,7 @@ where
|
|||
R: dioxus_router::prelude::Routable,
|
||||
<R as std::str::FromStr>::Err: std::fmt::Display,
|
||||
{
|
||||
dioxus_router::prelude::FailureExternalNavigation::<R>
|
||||
dioxus_router::prelude::FailureExternalNavigation
|
||||
}
|
||||
|
||||
/// The configeration for the router
|
||||
|
@ -96,7 +96,7 @@ where
|
|||
{
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
failure_external_navigation: dioxus_router::prelude::FailureExternalNavigation::<R>,
|
||||
failure_external_navigation: dioxus_router::prelude::FailureExternalNavigation,
|
||||
scroll_restoration: true,
|
||||
phantom: std::marker::PhantomData,
|
||||
}
|
||||
|
|
|
@ -211,38 +211,8 @@ pub fn routable(input: TokenStream) -> TokenStream {
|
|||
let parse_impl = route_enum.parse_impl();
|
||||
let display_impl = route_enum.impl_display();
|
||||
let routable_impl = route_enum.routable_impl();
|
||||
let name = &route_enum.name;
|
||||
let vis = &route_enum.vis;
|
||||
|
||||
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
|
||||
|
||||
#display_impl
|
||||
|
@ -255,7 +225,6 @@ pub fn routable(input: TokenStream) -> TokenStream {
|
|||
}
|
||||
|
||||
struct RouteEnum {
|
||||
vis: syn::Visibility,
|
||||
name: Ident,
|
||||
redirects: Vec<Redirect>,
|
||||
routes: Vec<Route>,
|
||||
|
@ -267,7 +236,6 @@ struct RouteEnum {
|
|||
impl RouteEnum {
|
||||
fn parse(data: syn::ItemEnum) -> syn::Result<Self> {
|
||||
let name = &data.ident;
|
||||
let vis = &data.vis;
|
||||
|
||||
let mut site_map = Vec::new();
|
||||
let mut site_map_stack: Vec<Vec<SiteMapSegment>> = Vec::new();
|
||||
|
@ -457,7 +425,6 @@ impl RouteEnum {
|
|||
}
|
||||
|
||||
let myself = Self {
|
||||
vis: vis.clone(),
|
||||
name: name.clone(),
|
||||
routes,
|
||||
redirects,
|
||||
|
|
|
@ -51,7 +51,7 @@ enum Route {
|
|||
|
||||
fn App(cx: Scope) -> Element {
|
||||
render! {
|
||||
Router { }
|
||||
Router::<Route> { }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -70,7 +70,7 @@ fn Index(cx: Scope) -> Element {
|
|||
fn Blog(cx: Scope) -> Element {
|
||||
render! {
|
||||
h1 { "Blog" }
|
||||
Outlet { }
|
||||
Outlet::<Route> { }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -193,7 +193,7 @@ enum Route {
|
|||
fn RenderPath(cx: Scope, path: Route) -> Element {
|
||||
let path = path.clone();
|
||||
render! {
|
||||
Router {
|
||||
Router::<Route> {
|
||||
config: || RouterConfig::default().history(MemoryHistory::with_initial_path(path))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ fn main() {
|
|||
|
||||
fn root(cx: Scope) -> Element {
|
||||
render! {
|
||||
Router {}
|
||||
Router::<Route> {}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,7 @@ fn UserFrame(cx: Scope, user_id: usize) -> Element {
|
|||
div {
|
||||
background_color: "rgba(0,0,0,50%)",
|
||||
"children:"
|
||||
Outlet {}
|
||||
Outlet::<Route> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -90,7 +90,7 @@ fn Route3(cx: Scope, dynamic: String) -> Element {
|
|||
"hello world link"
|
||||
}
|
||||
button {
|
||||
onclick: move |_| { navigator.push(NavigationTarget::External("https://www.google.com".to_string())); },
|
||||
onclick: move |_| { navigator.push(NavigationTarget::<Route>::External("https://www.google.com".to_string())); },
|
||||
"google link"
|
||||
}
|
||||
p { "Site Map" }
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use crate::{hooks::use_generic_router, routable::Routable};
|
||||
use crate::hooks::use_router;
|
||||
use dioxus::prelude::*;
|
||||
|
||||
/// The default component to render when an external navigation fails.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn FailureExternalNavigation<R: Routable + Clone>(cx: Scope) -> Element {
|
||||
let router = use_generic_router::<R>(cx);
|
||||
pub fn FailureExternalNavigation(cx: Scope) -> Element {
|
||||
let router = use_router(cx);
|
||||
|
||||
render! {
|
||||
h1 { "External Navigation Failure!" }
|
||||
|
|
|
@ -1,23 +1,23 @@
|
|||
use dioxus::prelude::*;
|
||||
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)]
|
||||
pub struct GenericHistoryButtonProps<'a> {
|
||||
pub struct HistoryButtonProps<'a> {
|
||||
/// The children to render within the generated HTML button tag.
|
||||
pub children: Element<'a>,
|
||||
}
|
||||
|
||||
/// A button to go back through the navigation history. Similar to a browsers back button.
|
||||
///
|
||||
/// Only works as descendant of a [`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.
|
||||
///
|
||||
/// # 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.
|
||||
///
|
||||
/// # Example
|
||||
|
@ -32,7 +32,7 @@ pub struct GenericHistoryButtonProps<'a> {
|
|||
///
|
||||
/// fn App(cx: Scope) -> Element {
|
||||
/// render! {
|
||||
/// Router {}
|
||||
/// Router::<Route> {}
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
|
@ -53,13 +53,11 @@ pub struct GenericHistoryButtonProps<'a> {
|
|||
/// # );
|
||||
/// ```
|
||||
#[allow(non_snake_case)]
|
||||
pub fn GenericGoBackButton<'a, R: Routable>(
|
||||
cx: Scope<'a, GenericHistoryButtonProps<'a>>,
|
||||
) -> Element {
|
||||
let GenericHistoryButtonProps { children } = cx.props;
|
||||
pub fn GoBackButton<'a>(cx: Scope<'a, HistoryButtonProps<'a>>) -> Element {
|
||||
let HistoryButtonProps { children } = cx.props;
|
||||
|
||||
// hook up to router
|
||||
let router = match use_router_internal::<R>(cx) {
|
||||
let router = match use_router_internal(cx) {
|
||||
Some(r) => r,
|
||||
#[allow(unreachable_code)]
|
||||
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.
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
/// # 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.
|
||||
///
|
||||
/// # Example
|
||||
|
@ -105,7 +103,7 @@ pub fn GenericGoBackButton<'a, R: Routable>(
|
|||
///
|
||||
/// fn App(cx: Scope) -> Element {
|
||||
/// render! {
|
||||
/// Router {}
|
||||
/// Router::<Route> {}
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
|
@ -126,13 +124,11 @@ pub fn GenericGoBackButton<'a, R: Routable>(
|
|||
/// # );
|
||||
/// ```
|
||||
#[allow(non_snake_case)]
|
||||
pub fn GenericGoForwardButton<'a, R: Routable>(
|
||||
cx: Scope<'a, GenericHistoryButtonProps<'a>>,
|
||||
) -> Element {
|
||||
let GenericHistoryButtonProps { children } = cx.props;
|
||||
pub fn GoForwardButton<'a>(cx: Scope<'a, HistoryButtonProps<'a>>) -> Element {
|
||||
let HistoryButtonProps { children } = cx.props;
|
||||
|
||||
// hook up to router
|
||||
let router = match use_router_internal::<R>(cx) {
|
||||
let router = match use_router_internal(cx) {
|
||||
Some(r) => r,
|
||||
#[allow(unreachable_code)]
|
||||
None => {
|
||||
|
|
|
@ -1,15 +1,57 @@
|
|||
use std::any::Any;
|
||||
use std::fmt::Debug;
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use log::error;
|
||||
|
||||
use crate::navigation::NavigationTarget;
|
||||
use crate::prelude::*;
|
||||
use crate::prelude::{AnyDisplay, Routable};
|
||||
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 AnyDisplay>),
|
||||
}
|
||||
|
||||
impl<R: Routable> From<R> for IntoRoutable {
|
||||
fn from(value: R) -> Self {
|
||||
IntoRoutable::Route(Box::new(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Routable> From<NavigationTarget<R>> for IntoRoutable {
|
||||
fn from(value: NavigationTarget<R>) -> Self {
|
||||
match value {
|
||||
NavigationTarget::Internal(route) => IntoRoutable::Route(Box::new(route)),
|
||||
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)]
|
||||
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.
|
||||
pub active_class: Option<&'a str>,
|
||||
/// The children to render within the generated HTML anchor tag.
|
||||
|
@ -23,7 +65,7 @@ pub struct GenericLinkProps<'a, R: Routable> {
|
|||
pub id: Option<&'a str>,
|
||||
/// 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)]
|
||||
pub new_tab: bool,
|
||||
/// The onclick event handler.
|
||||
|
@ -42,10 +84,10 @@ pub struct GenericLinkProps<'a, R: Routable> {
|
|||
pub rel: Option<&'a str>,
|
||||
/// The navigation target. Roughly equivalent to the href attribute of an HTML anchor tag.
|
||||
#[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 {
|
||||
f.debug_struct("LinkProps")
|
||||
.field("active_class", &self.active_class)
|
||||
|
@ -56,27 +98,26 @@ impl<R: Routable> Debug for GenericLinkProps<'_, R> {
|
|||
.field("onclick", &self.onclick.as_ref().map(|_| "onclick is set"))
|
||||
.field("onclick_only", &self.onclick_only)
|
||||
.field("rel", &self.rel)
|
||||
.field("to", &self.to.to_string())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
/// # External targets
|
||||
/// When the [`GenericLink`]s target is an [`NavigationTarget::External`] target, that is used as the `href` directly. This
|
||||
/// means that a [`GenericLink`] can always navigate to an [`NavigationTarget::External`] target, even if the [`HistoryProvider`] does not support it.
|
||||
/// When the [`Link`]s target is an [`NavigationTarget::External`] target, that is used as the `href` directly. This
|
||||
/// means that a [`Link`] can always navigate to an [`NavigationTarget::External`] target, even if the [`HistoryProvider`] does not support it.
|
||||
///
|
||||
/// # 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.
|
||||
///
|
||||
/// # Example
|
||||
|
@ -92,7 +133,7 @@ impl<R: Routable> Debug for GenericLinkProps<'_, R> {
|
|||
///
|
||||
/// fn App(cx: Scope) -> Element {
|
||||
/// render! {
|
||||
/// Router {}
|
||||
/// Router::<Route> {}
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
|
@ -122,8 +163,8 @@ impl<R: Routable> Debug for GenericLinkProps<'_, R> {
|
|||
/// # );
|
||||
/// ```
|
||||
#[allow(non_snake_case)]
|
||||
pub fn GenericLink<'a, R: Routable + Clone>(cx: Scope<'a, GenericLinkProps<'a, R>>) -> Element {
|
||||
let GenericLinkProps {
|
||||
pub fn Link<'a>(cx: Scope<'a, LinkProps<'a>>) -> Element {
|
||||
let LinkProps {
|
||||
active_class,
|
||||
children,
|
||||
class,
|
||||
|
@ -133,10 +174,11 @@ pub fn GenericLink<'a, R: Routable + Clone>(cx: Scope<'a, GenericLinkProps<'a, R
|
|||
onclick_only,
|
||||
rel,
|
||||
to,
|
||||
..
|
||||
} = cx.props;
|
||||
|
||||
// hook up to router
|
||||
let router = match use_router_internal::<R>(cx) {
|
||||
let router = match use_router_internal(cx) {
|
||||
Some(r) => r,
|
||||
#[allow(unreachable_code)]
|
||||
None => {
|
||||
|
@ -148,9 +190,15 @@ pub fn GenericLink<'a, R: Routable + Clone>(cx: Scope<'a, GenericLinkProps<'a, R
|
|||
}
|
||||
};
|
||||
|
||||
let current_route = router.current();
|
||||
let current_url = current_route.to_string();
|
||||
let href = to.to_string();
|
||||
let current_url = router.current_route_string();
|
||||
let href = match to {
|
||||
IntoRoutable::FromStr(url) => url.to_string(),
|
||||
IntoRoutable::Route(route) => route.to_string(),
|
||||
};
|
||||
let parsed_route: NavigationTarget<Box<dyn Any>> = match router.route_from_str(&href) {
|
||||
Ok(route) => NavigationTarget::Internal(route.into()),
|
||||
Err(err) => NavigationTarget::External(err),
|
||||
};
|
||||
let ac = active_class
|
||||
.and_then(|active_class| (href == current_url).then(|| format!(" {active_class}")))
|
||||
.unwrap_or_default();
|
||||
|
@ -159,7 +207,7 @@ pub fn GenericLink<'a, R: Routable + Clone>(cx: Scope<'a, GenericLinkProps<'a, R
|
|||
let class = format!("{}{ac}", class.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 prevent_default = is_router_nav.then_some("onclick").unwrap_or_default();
|
||||
let rel = rel
|
||||
|
@ -169,7 +217,15 @@ pub fn GenericLink<'a, R: Routable + Clone>(cx: Scope<'a, GenericLinkProps<'a, R
|
|||
let do_default = onclick.is_none() || !onclick_only;
|
||||
let action = move |event| {
|
||||
if do_default && is_router_nav {
|
||||
router.push(to.clone());
|
||||
let href = match to {
|
||||
IntoRoutable::FromStr(url) => url.to_string(),
|
||||
IntoRoutable::Route(route) => route.to_string(),
|
||||
};
|
||||
let parsed_route: NavigationTarget<Box<dyn Any>> = match router.route_from_str(&href) {
|
||||
Ok(route) => NavigationTarget::Internal(route.into()),
|
||||
Err(err) => NavigationTarget::External(err),
|
||||
};
|
||||
router.push_any(parsed_route);
|
||||
}
|
||||
|
||||
if let Some(handler) = onclick {
|
||||
|
|
|
@ -3,13 +3,13 @@ use dioxus::prelude::*;
|
|||
|
||||
/// 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__.
|
||||
///
|
||||
/// # Panic
|
||||
/// - When the [`GenericOutlet`] is not nested a [`GenericRouter`] component,
|
||||
/// - When the [`Outlet`] is not nested a [`Link`] component,
|
||||
/// but only in debug builds.
|
||||
///
|
||||
/// # Example
|
||||
|
@ -42,7 +42,7 @@ use dioxus::prelude::*;
|
|||
/// fn Wrapper(cx: Scope) -> Element {
|
||||
/// render! {
|
||||
/// 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 {
|
||||
/// # render! {
|
||||
/// # Router {
|
||||
/// # Router::<Route> {
|
||||
/// # config: || RouterConfig::default().history(MemoryHistory::with_initial_path(Route::Child {}))
|
||||
/// # }
|
||||
/// # }
|
||||
|
@ -67,6 +67,6 @@ use dioxus::prelude::*;
|
|||
/// # let _ = vdom.rebuild();
|
||||
/// # 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)
|
||||
}
|
||||
|
|
|
@ -1,13 +1,9 @@
|
|||
use dioxus::prelude::*;
|
||||
use std::{cell::RefCell, str::FromStr};
|
||||
|
||||
use crate::{
|
||||
prelude::{GenericOutlet, GenericRouterContext},
|
||||
routable::Routable,
|
||||
router_cfg::RouterConfig,
|
||||
};
|
||||
use crate::{prelude::Outlet, routable::Routable, router_cfg::RouterConfig};
|
||||
|
||||
/// The config for [`GenericRouter`].
|
||||
/// The config for [`Router`].
|
||||
pub struct RouterConfigFactory<R: Routable> {
|
||||
#[allow(clippy::type_complexity)]
|
||||
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")]
|
||||
/// The props for [`GenericRouter`].
|
||||
/// The props for [`Router`].
|
||||
#[derive(Props)]
|
||||
pub struct GenericRouterProps<R: Routable>
|
||||
pub struct RouterProps<R: Routable>
|
||||
where
|
||||
<R as FromStr>::Err: std::fmt::Display,
|
||||
R: serde::Serialize + serde::de::DeserializeOwned,
|
||||
|
@ -55,9 +51,9 @@ where
|
|||
}
|
||||
|
||||
#[cfg(not(feature = "serde"))]
|
||||
/// The props for [`GenericRouter`].
|
||||
/// The props for [`Router`].
|
||||
#[derive(Props)]
|
||||
pub struct GenericRouterProps<R: Routable>
|
||||
pub struct RouterProps<R: Routable>
|
||||
where
|
||||
<R as FromStr>::Err: std::fmt::Display,
|
||||
{
|
||||
|
@ -66,7 +62,7 @@ where
|
|||
}
|
||||
|
||||
#[cfg(not(feature = "serde"))]
|
||||
impl<R: Routable> Default for GenericRouterProps<R>
|
||||
impl<R: Routable> Default for RouterProps<R>
|
||||
where
|
||||
<R as FromStr>::Err: std::fmt::Display,
|
||||
{
|
||||
|
@ -78,7 +74,7 @@ where
|
|||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl<R: Routable> Default for GenericRouterProps<R>
|
||||
impl<R: Routable> Default for RouterProps<R>
|
||||
where
|
||||
<R as FromStr>::Err: std::fmt::Display,
|
||||
R: serde::Serialize + serde::de::DeserializeOwned,
|
||||
|
@ -91,7 +87,7 @@ where
|
|||
}
|
||||
|
||||
#[cfg(not(feature = "serde"))]
|
||||
impl<R: Routable> PartialEq for GenericRouterProps<R>
|
||||
impl<R: Routable> PartialEq for RouterProps<R>
|
||||
where
|
||||
<R as FromStr>::Err: std::fmt::Display,
|
||||
{
|
||||
|
@ -102,7 +98,7 @@ where
|
|||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl<R: Routable> PartialEq for GenericRouterProps<R>
|
||||
impl<R: Routable> PartialEq for RouterProps<R>
|
||||
where
|
||||
<R as FromStr>::Err: std::fmt::Display,
|
||||
R: serde::Serialize + serde::de::DeserializeOwned,
|
||||
|
@ -115,14 +111,14 @@ where
|
|||
|
||||
#[cfg(not(feature = "serde"))]
|
||||
/// 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
|
||||
<R as FromStr>::Err: std::fmt::Display,
|
||||
{
|
||||
use crate::prelude::outlet::OutletContext;
|
||||
use crate::prelude::{outlet::OutletContext, RouterContext};
|
||||
|
||||
use_context_provider(cx, || {
|
||||
GenericRouterContext::new(
|
||||
RouterContext::new(
|
||||
(cx.props
|
||||
.config
|
||||
.config
|
||||
|
@ -137,19 +133,19 @@ where
|
|||
});
|
||||
|
||||
render! {
|
||||
GenericOutlet::<R> {}
|
||||
Outlet::<R> {}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
/// 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
|
||||
<R as FromStr>::Err: std::fmt::Display,
|
||||
R: serde::Serialize + serde::de::DeserializeOwned,
|
||||
{
|
||||
use_context_provider(cx, || {
|
||||
GenericRouterContext::new(
|
||||
RouterContext::new(
|
||||
(cx.props
|
||||
.config
|
||||
.config
|
||||
|
@ -164,6 +160,6 @@ where
|
|||
});
|
||||
|
||||
render! {
|
||||
GenericOutlet::<R> {}
|
||||
Outlet::<R> {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use crate::prelude::{ExternalNavigationFailure, GenericRouterContext, NavigationTarget, Routable};
|
||||
use crate::prelude::{ExternalNavigationFailure, NavigationTarget, Routable, RouterContext};
|
||||
|
||||
/// A view into the navigation state of a router.
|
||||
#[derive(Clone)]
|
||||
pub struct GenericNavigator<R: Routable>(pub(crate) GenericRouterContext<R>);
|
||||
pub struct GenericNavigator(pub(crate) RouterContext);
|
||||
|
||||
impl<R: Routable> GenericNavigator<R> {
|
||||
impl GenericNavigator {
|
||||
/// Check whether there is a previous page to navigate back to.
|
||||
#[must_use]
|
||||
pub fn can_go_back(&self) -> bool {
|
||||
|
@ -34,7 +34,7 @@ impl<R: Routable> GenericNavigator<R> {
|
|||
/// Push a new location.
|
||||
///
|
||||
/// The previous location will be available to go back to.
|
||||
pub fn push(
|
||||
pub fn push<R: Routable>(
|
||||
&self,
|
||||
target: impl Into<NavigationTarget<R>>,
|
||||
) -> Option<ExternalNavigationFailure> {
|
||||
|
@ -44,7 +44,7 @@ impl<R: Routable> GenericNavigator<R> {
|
|||
/// Replace the current location.
|
||||
///
|
||||
/// The previous location will **not** be available to go back to.
|
||||
pub fn replace(
|
||||
pub fn replace<R: Routable>(
|
||||
&self,
|
||||
target: impl Into<NavigationTarget<R>>,
|
||||
) -> Option<ExternalNavigationFailure> {
|
||||
|
|
|
@ -31,7 +31,7 @@ impl<R> OutletContext<R> {
|
|||
where
|
||||
R: Routable + Clone,
|
||||
{
|
||||
let router = use_router_internal::<R>(cx)
|
||||
let router = use_router_internal(cx)
|
||||
.as_ref()
|
||||
.expect("Outlet must be inside of a router");
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use std::{
|
||||
any::Any,
|
||||
collections::HashSet,
|
||||
sync::{Arc, RwLock, RwLockWriteGuard},
|
||||
};
|
||||
|
@ -6,7 +7,7 @@ use std::{
|
|||
use dioxus::prelude::*;
|
||||
|
||||
use crate::{
|
||||
history::HistoryProvider, navigation::NavigationTarget, routable::Routable,
|
||||
navigation::NavigationTarget, prelude::AnyHistoryProvider, routable::Routable,
|
||||
router_cfg::RouterConfig,
|
||||
};
|
||||
|
||||
|
@ -15,52 +16,31 @@ use crate::{
|
|||
pub struct ExternalNavigationFailure(String);
|
||||
|
||||
/// A function the router will call after every routing update.
|
||||
pub(crate) type RoutingCallback<R> =
|
||||
Arc<dyn Fn(GenericRouterContext<R>) -> Option<NavigationTarget<R>>>;
|
||||
pub(crate) type RoutingCallback<R> = Arc<dyn Fn(LinkContext<R>) -> Option<NavigationTarget<R>>>;
|
||||
|
||||
struct MutableRouterState<R>
|
||||
where
|
||||
R: Routable,
|
||||
{
|
||||
struct MutableRouterState {
|
||||
/// The current prefix.
|
||||
prefix: Option<String>,
|
||||
|
||||
history: Box<dyn HistoryProvider<R>>,
|
||||
history: Box<dyn AnyHistoryProvider>,
|
||||
|
||||
unresolved_error: Option<ExternalNavigationFailure>,
|
||||
}
|
||||
|
||||
/// A collection of router data that manages all routing functionality.
|
||||
pub struct GenericRouterContext<R>
|
||||
where
|
||||
R: Routable,
|
||||
{
|
||||
state: Arc<RwLock<MutableRouterState<R>>>,
|
||||
#[derive(Clone)]
|
||||
pub struct RouterContext {
|
||||
state: Arc<RwLock<MutableRouterState>>,
|
||||
|
||||
subscribers: Arc<RwLock<HashSet<ScopeId>>>,
|
||||
subscriber_update: Arc<dyn Fn(ScopeId)>,
|
||||
routing_callback: Option<RoutingCallback<R>>,
|
||||
routing_callback: Option<Arc<dyn Fn(RouterContext) -> Option<NavigationTarget<Box<dyn Any>>>>>,
|
||||
|
||||
failure_external_navigation: fn(Scope) -> Element,
|
||||
}
|
||||
|
||||
impl<R: Routable> Clone for GenericRouterContext<R> {
|
||||
fn clone(&self) -> Self {
|
||||
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(
|
||||
impl RouterContext {
|
||||
pub(crate) fn new<R: Routable + 'static>(
|
||||
mut cfg: RouterConfig<R>,
|
||||
mark_dirty: Arc<dyn Fn(ScopeId) + Sync + Send>,
|
||||
) -> Self
|
||||
|
@ -82,7 +62,21 @@ where
|
|||
subscribers: subscribers.clone(),
|
||||
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,
|
||||
};
|
||||
|
@ -100,6 +94,11 @@ where
|
|||
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.
|
||||
#[must_use]
|
||||
pub fn can_go_back(&self) -> bool {
|
||||
|
@ -134,14 +133,10 @@ where
|
|||
self.change_route();
|
||||
}
|
||||
|
||||
/// Push a new location.
|
||||
///
|
||||
/// The previous location will be available to go back to.
|
||||
pub fn push(
|
||||
pub(crate) fn push_any(
|
||||
&self,
|
||||
target: impl Into<NavigationTarget<R>>,
|
||||
target: NavigationTarget<Box<dyn Any>>,
|
||||
) -> Option<ExternalNavigationFailure> {
|
||||
let target = target.into();
|
||||
match target {
|
||||
NavigationTarget::Internal(p) => {
|
||||
let mut state = self.state_mut();
|
||||
|
@ -153,10 +148,29 @@ where
|
|||
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.
|
||||
///
|
||||
/// The previous location will **not** be available to go back to.
|
||||
pub fn replace(
|
||||
pub fn replace<R: Routable>(
|
||||
&self,
|
||||
target: impl Into<NavigationTarget<R>>,
|
||||
) -> Option<ExternalNavigationFailure> {
|
||||
|
@ -165,7 +179,7 @@ where
|
|||
{
|
||||
let mut state = self.state_mut();
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
@ -174,11 +188,24 @@ where
|
|||
}
|
||||
|
||||
/// The route that is currently active.
|
||||
pub fn current(&self) -> R
|
||||
where
|
||||
R: Clone,
|
||||
{
|
||||
self.state.read().unwrap().history.current_route()
|
||||
pub fn current<R: Routable>(&self) -> R {
|
||||
self.state
|
||||
.read()
|
||||
.unwrap()
|
||||
.history
|
||||
.current_route()
|
||||
.downcast::<R>()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// The route that is currently active.
|
||||
pub fn current_route_string(&self) -> String {
|
||||
self.state
|
||||
.read()
|
||||
.unwrap()
|
||||
.history
|
||||
.current_route()
|
||||
.to_string()
|
||||
}
|
||||
|
||||
/// The prefix that is currently active.
|
||||
|
@ -201,7 +228,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
fn state_mut(&self) -> RwLockWriteGuard<MutableRouterState<R>> {
|
||||
fn state_mut(&self) -> RwLockWriteGuard<MutableRouterState> {
|
||||
self.state.write().unwrap()
|
||||
}
|
||||
|
||||
|
@ -254,3 +281,87 @@ where
|
|||
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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
//! 1) [`MemoryHistory`] for desktop/mobile/ssr platforms
|
||||
//! 2) [`WebHistory`] for web platforms
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::{any::Any, fmt::Display, sync::Arc};
|
||||
|
||||
mod memory;
|
||||
pub use memory::*;
|
||||
|
@ -277,3 +277,143 @@ pub trait HistoryProvider<R: Routable> {
|
|||
#[allow(unused_variables)]
|
||||
fn updater(&mut self, callback: Arc<dyn Fn() + Send + Sync>) {}
|
||||
}
|
||||
|
||||
/// Something that can be displayed and is also an [`Any`]
|
||||
pub trait AnyDisplay: Display + Any {
|
||||
/// Get a reference to the inner [`Any`] object.
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
}
|
||||
|
||||
impl dyn AnyDisplay {
|
||||
/// Try to downcast the inner [`Any`] object to a concrete type.
|
||||
pub fn downcast<T: Any + Clone>(self: Box<dyn AnyDisplay>) -> Option<T> {
|
||||
self.as_any().downcast_ref::<T>().cloned()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Display + Any> AnyDisplay for T {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait AnyHistoryProvider {
|
||||
#[must_use]
|
||||
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 AnyDisplay>;
|
||||
|
||||
#[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 AnyDisplay> {
|
||||
let route = self.inner.current_route();
|
||||
println!("current_route {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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
use dioxus::prelude::ScopeState;
|
||||
|
||||
use crate::{
|
||||
prelude::{GenericNavigator, GenericRouterContext},
|
||||
routable::Routable,
|
||||
};
|
||||
use crate::prelude::{GenericNavigator, RouterContext};
|
||||
|
||||
/// 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 {
|
||||
/// render! {
|
||||
/// Router {}
|
||||
/// Router::<Route> {}
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
|
@ -50,10 +47,10 @@ use crate::{
|
|||
/// # let mut vdom = VirtualDom::new(App);
|
||||
/// # let _ = vdom.rebuild();
|
||||
/// ```
|
||||
pub fn use_generic_navigator<R: Routable + Clone>(cx: &ScopeState) -> &GenericNavigator<R> {
|
||||
pub fn use_navigator(cx: &ScopeState) -> &GenericNavigator {
|
||||
&*cx.use_hook(|| {
|
||||
let router = cx
|
||||
.consume_context::<GenericRouterContext<R>>()
|
||||
.consume_context::<RouterContext>()
|
||||
.expect("Must be called in a descendant of a Router component");
|
||||
|
||||
GenericNavigator(router)
|
||||
|
|
|
@ -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.
|
||||
///
|
||||
/// # Return values
|
||||
/// - None, when not called inside a [`GenericRouter`] component.
|
||||
/// - None, when not called inside a [`Link`] component.
|
||||
/// - Otherwise the current route.
|
||||
///
|
||||
/// # 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
|
||||
/// ```rust
|
||||
|
@ -28,7 +28,7 @@ use crate::utils::use_router_internal::use_router_internal;
|
|||
/// fn App(cx: Scope) -> Element {
|
||||
/// render! {
|
||||
/// h1 { "App" }
|
||||
/// Router {}
|
||||
/// Router::<Route> {}
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
|
@ -45,7 +45,7 @@ use crate::utils::use_router_internal::use_router_internal;
|
|||
/// # let _ = vdom.rebuild();
|
||||
/// # 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) {
|
||||
Some(r) => Some(r.current()),
|
||||
None => {
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
use dioxus::prelude::ScopeState;
|
||||
|
||||
use crate::{
|
||||
prelude::GenericRouterContext, routable::Routable,
|
||||
utils::use_router_internal::use_router_internal,
|
||||
};
|
||||
use crate::{prelude::RouterContext, utils::use_router_internal::use_router_internal};
|
||||
|
||||
/// 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)
|
||||
.as_ref()
|
||||
.expect("use_route must have access to a router")
|
||||
|
|
|
@ -95,7 +95,7 @@ where
|
|||
{
|
||||
let path = path.clone();
|
||||
render! {
|
||||
GenericRouter::<R> {
|
||||
Link::<R> {
|
||||
config: || RouterConfig::default().history(MemoryHistory::with_initial_path(path))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ use crate::routable::Routable;
|
|||
|
||||
/// A target for the router to navigate to.
|
||||
#[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.
|
||||
///
|
||||
/// ```rust
|
||||
|
|
|
@ -26,7 +26,7 @@ use crate::prelude::*;
|
|||
/// ```
|
||||
pub struct RouterConfig<R: Routable> {
|
||||
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>>,
|
||||
}
|
||||
|
||||
|
@ -69,7 +69,7 @@ where
|
|||
{
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
failure_external_navigation: FailureExternalNavigation::<R>,
|
||||
failure_external_navigation: FailureExternalNavigation,
|
||||
history: None,
|
||||
on_update: None,
|
||||
}
|
||||
|
@ -81,18 +81,22 @@ impl<R: Routable + Clone> RouterConfig<R>
|
|||
where
|
||||
<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(|| {
|
||||
#[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")))]
|
||||
let history = Box::<MemoryHistory<R>>::default();
|
||||
let history = Box::<AnyHistoryProviderImplWrapper<R, MemoryHistory<R>>>::default();
|
||||
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.
|
||||
///
|
||||
/// 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`].
|
||||
pub fn on_update(
|
||||
self,
|
||||
callback: impl Fn(GenericRouterContext<R>) -> Option<NavigationTarget<R>> + 'static,
|
||||
callback: impl Fn(LinkContext<R>) -> Option<NavigationTarget<R>> + 'static,
|
||||
) -> Self {
|
||||
Self {
|
||||
on_update: Some(Arc::new(callback)),
|
||||
|
@ -121,7 +125,7 @@ impl<R: Routable> RouterConfig<R> {
|
|||
/// Defaults to a default [`MemoryHistory`].
|
||||
pub fn history(self, history: impl HistoryProvider<R> + 'static) -> Self {
|
||||
Self {
|
||||
history: Some(Box::new(history)),
|
||||
history: Some(Box::new(AnyHistoryProviderImplWrapper::new(history))),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use dioxus::prelude::{ScopeId, ScopeState};
|
||||
|
||||
use crate::{contexts::router::GenericRouterContext, prelude::*};
|
||||
use crate::prelude::*;
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// # 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`].
|
||||
pub(crate) fn use_router_internal<R: Routable>(
|
||||
cx: &ScopeState,
|
||||
) -> &Option<GenericRouterContext<R>> {
|
||||
pub(crate) fn use_router_internal(cx: &ScopeState) -> &Option<RouterContext> {
|
||||
let inner = cx.use_hook(|| {
|
||||
let router = cx.consume_context::<GenericRouterContext<R>>()?;
|
||||
let router = cx.consume_context::<RouterContext>()?;
|
||||
|
||||
let id = cx.scope_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()))
|
||||
}
|
||||
|
||||
struct Subscription<R: Routable> {
|
||||
router: GenericRouterContext<R>,
|
||||
struct Subscription {
|
||||
router: RouterContext,
|
||||
id: ScopeId,
|
||||
}
|
||||
|
||||
impl<R: Routable> Drop for Subscription<R> {
|
||||
impl Drop for Subscription {
|
||||
fn drop(&mut self) {
|
||||
self.router.unsubscribe(self.id);
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ where
|
|||
{
|
||||
render! {
|
||||
h1 { "App" }
|
||||
GenericRouter::<R> {
|
||||
Router::<R> {
|
||||
config: || RouterConfig::default().history(MemoryHistory::default())
|
||||
}
|
||||
}
|
||||
|
@ -97,7 +97,7 @@ fn href_external() {
|
|||
fn Root(cx: Scope) -> Element {
|
||||
render! {
|
||||
Link {
|
||||
to: NavigationTarget::External("https://dioxuslabs.com/".into()),
|
||||
to: "https://dioxuslabs.com/",
|
||||
"Link"
|
||||
}
|
||||
}
|
||||
|
@ -318,7 +318,7 @@ fn with_new_tab_external() {
|
|||
fn Root(cx: Scope) -> Element {
|
||||
render! {
|
||||
Link {
|
||||
to: NavigationTarget::External("https://dioxuslabs.com/".into()),
|
||||
to: "https://dioxuslabs.com/",
|
||||
new_tab: true,
|
||||
"Link"
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ fn prepare(path: impl Into<String>) -> VirtualDom {
|
|||
fn App(cx: Scope<AppProps>) -> Element {
|
||||
render! {
|
||||
h1 { "App" }
|
||||
Router {
|
||||
Router::<Route> {
|
||||
config: {
|
||||
let path = cx.props.path.parse().unwrap();
|
||||
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 {
|
||||
render! {
|
||||
h2 { "Fixed" }
|
||||
Outlet { }
|
||||
Outlet::<Route> { }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -79,7 +79,7 @@ fn prepare(path: impl Into<String>) -> VirtualDom {
|
|||
fn Parameter(cx: Scope, id: u8) -> Element {
|
||||
render! {
|
||||
h2 { "Parameter {id}" }
|
||||
Outlet { }
|
||||
Outlet::<Route> { }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue