Move the history provider into the context (#3048)
* move history providers into a separate crate * start moving route providers into the renderers * clean up intoroutable * remove into routable * fix router tests * Provide history providers in each renderer * implement nested routers * move the lens out of the history crate * re-export dioxus history trait in the prelude * also re-export the history function * fix history doctests * some light cleanups --------- Co-authored-by: Jonathan Kelley <jkelleyrtp@gmail.com>
This commit is contained in:
1 parent
e5a1a62644
commit
281087469a
48 files changed
+875
-1419
No files matched your search
@@ -0,0 +1,68 @@
|
||||
/// Components that allow the macro to add child routers. This component provides a context
|
||||
/// to the child router that maps child routes to root routes and vice versa.
|
||||
use dioxus_lib::prelude::*;
|
||||
|
||||
use crate::prelude::Routable;
|
||||
|
||||
/// Maps a child route into the root router and vice versa
|
||||
// NOTE: Currently child routers only support simple static prefixes, but this
|
||||
// API could be expanded to support dynamic prefixes as well
|
||||
pub(crate) struct ChildRouteMapping<R> {
|
||||
format_route_as_root_route: fn(R) -> String,
|
||||
parse_route_from_root_route: fn(&str) -> Option<R>,
|
||||
}
|
||||
|
||||
impl<R: Routable> ChildRouteMapping<R> {
|
||||
pub(crate) fn format_route_as_root_route(&self, route: R) -> String {
|
||||
(self.format_route_as_root_route)(route)
|
||||
}
|
||||
|
||||
pub(crate) fn parse_route_from_root_route(&self, route: &str) -> Option<R> {
|
||||
(self.parse_route_from_root_route)(route)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the formatter that handles adding and stripping the prefix from a child route
|
||||
pub(crate) fn consume_child_route_mapping<R: Routable>() -> Option<ChildRouteMapping<R>> {
|
||||
try_consume_context()
|
||||
}
|
||||
|
||||
impl<R> Clone for ChildRouteMapping<R> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
format_route_as_root_route: self.format_route_as_root_route,
|
||||
parse_route_from_root_route: self.parse_route_from_root_route,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Props for the [`ChildHistoryProvider`] component.
|
||||
#[derive(Props, Clone)]
|
||||
pub struct ChildRouterProps<R: Routable> {
|
||||
/// The child route to render
|
||||
route: R,
|
||||
/// Take a parent route and return a child route or none if the route is not part of the child
|
||||
parse_route_from_root_route: fn(&str) -> Option<R>,
|
||||
/// Take a child route and return a parent route
|
||||
format_route_as_root_route: fn(R) -> String,
|
||||
}
|
||||
|
||||
impl<R: Routable> PartialEq for ChildRouterProps<R> {
|
||||
fn eq(&self, _: &Self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// A component that provides a [`History`] to a child router. The `#[child]` attribute on the router macro will insert this automatically.
|
||||
#[component]
|
||||
#[allow(missing_docs)]
|
||||
pub fn ChildRouter<R: Routable>(props: ChildRouterProps<R>) -> Element {
|
||||
use_hook(|| {
|
||||
provide_context(ChildRouteMapping {
|
||||
format_route_as_root_route: props.format_route_as_root_route,
|
||||
parse_route_from_root_route: props.parse_route_from_root_route,
|
||||
})
|
||||
});
|
||||
|
||||
props.route.render(0)
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
use dioxus_history::{provide_history_context, History};
|
||||
use dioxus_lib::prelude::*;
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
/// A component that provides a [`History`] for all child [`Router`] components. Renderers generally provide a default history automatically.
|
||||
#[component]
|
||||
#[allow(missing_docs)]
|
||||
pub fn HistoryProvider(
|
||||
/// The history to provide to child components.
|
||||
history: Callback<(), Rc<dyn History>>,
|
||||
/// The children to render within the history provider.
|
||||
children: Element,
|
||||
) -> Element {
|
||||
use_hook(|| {
|
||||
provide_history_context(history(()));
|
||||
});
|
||||
|
||||
children
|
||||
}
|
||||
@@ -1,83 +1,14 @@
|
||||
#![allow(clippy::type_complexity)]
|
||||
|
||||
use std::any::Any;
|
||||
use std::fmt::Debug;
|
||||
use std::rc::Rc;
|
||||
|
||||
use dioxus_lib::prelude::*;
|
||||
|
||||
use tracing::error;
|
||||
|
||||
use crate::navigation::NavigationTarget;
|
||||
use crate::prelude::Routable;
|
||||
use crate::utils::use_router_internal::use_router_internal;
|
||||
|
||||
use url::Url;
|
||||
|
||||
/// Something that can be converted into a [`NavigationTarget`].
|
||||
#[derive(Clone)]
|
||||
pub enum IntoRoutable {
|
||||
/// A raw string target.
|
||||
FromStr(String),
|
||||
/// A internal target.
|
||||
Route(Rc<dyn Any>),
|
||||
}
|
||||
|
||||
impl PartialEq for IntoRoutable {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(IntoRoutable::FromStr(a), IntoRoutable::FromStr(b)) => a == b,
|
||||
(IntoRoutable::Route(a), IntoRoutable::Route(b)) => Rc::ptr_eq(a, b),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Routable> From<R> for IntoRoutable {
|
||||
fn from(value: R) -> Self {
|
||||
IntoRoutable::Route(Rc::new(value) as Rc<dyn Any>)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Routable> From<NavigationTarget<R>> for IntoRoutable {
|
||||
fn from(value: NavigationTarget<R>) -> Self {
|
||||
match value {
|
||||
NavigationTarget::Internal(route) => IntoRoutable::Route(Rc::new(route) as Rc<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())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Url> for IntoRoutable {
|
||||
fn from(url: Url) -> Self {
|
||||
IntoRoutable::FromStr(url.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Url> for IntoRoutable {
|
||||
fn from(url: &Url) -> Self {
|
||||
IntoRoutable::FromStr(url.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// The properties for a [`Link`].
|
||||
#[derive(Props, Clone, PartialEq)]
|
||||
pub struct LinkProps {
|
||||
@@ -119,7 +50,7 @@ pub struct LinkProps {
|
||||
|
||||
/// The navigation target. Roughly equivalent to the href attribute of an HTML anchor tag.
|
||||
#[props(into)]
|
||||
pub to: IntoRoutable,
|
||||
pub to: NavigationTarget,
|
||||
|
||||
#[props(extends = GlobalAttributes)]
|
||||
attributes: Vec<Attribute>,
|
||||
@@ -227,12 +158,11 @@ pub fn Link(props: LinkProps) -> Element {
|
||||
}
|
||||
};
|
||||
|
||||
let current_url = router.current_route_string();
|
||||
let current_url = router.full_route_string();
|
||||
let href = match &to {
|
||||
IntoRoutable::FromStr(url) => url.to_string(),
|
||||
IntoRoutable::Route(route) => router.any_route_to_string(&**route),
|
||||
NavigationTarget::Internal(url) => url.clone(),
|
||||
NavigationTarget::External(route) => route.clone(),
|
||||
};
|
||||
let parsed_route: NavigationTarget<Rc<dyn Any>> = router.resolve_into_routable(to.clone());
|
||||
|
||||
let mut class_ = String::new();
|
||||
if let Some(c) = class {
|
||||
@@ -257,7 +187,7 @@ pub fn Link(props: LinkProps) -> Element {
|
||||
|
||||
let tag_target = new_tab.then_some("_blank");
|
||||
|
||||
let is_external = matches!(parsed_route, NavigationTarget::External(_));
|
||||
let is_external = matches!(to, NavigationTarget::External(_));
|
||||
let is_router_nav = !is_external && !new_tab;
|
||||
let rel = rel.or_else(|| is_external.then_some("noopener noreferrer".to_string()));
|
||||
|
||||
@@ -281,7 +211,7 @@ pub fn Link(props: LinkProps) -> Element {
|
||||
event.prevent_default();
|
||||
|
||||
if do_default && is_router_nav {
|
||||
router.push_any(router.resolve_into_routable(to.clone()));
|
||||
router.push_any(to.clone());
|
||||
}
|
||||
|
||||
if let Some(handler) = onclick {
|
||||
@@ -301,8 +231,8 @@ pub fn Link(props: LinkProps) -> Element {
|
||||
let liveview_prevent_default = {
|
||||
// If the event is a click with the left mouse button and no modifiers, prevent the default action
|
||||
// and navigate to the href with client side routing
|
||||
router.is_liveview().then_some(
|
||||
"if (event.button === 0 && !event.ctrlKey && !event.metaKey && !event.shiftKey && !event.altKey) { event.preventDefault() }"
|
||||
router.include_prevent_default().then_some(
|
||||
"if (event.button === 0 && !event.ctrlKey && !event.metaKey && !event.shiftKey && !event.altKey) { event.preventDefault() }"
|
||||
)
|
||||
};
|
||||
|
||||
|
||||
@@ -58,8 +58,9 @@ use dioxus_lib::prelude::*;
|
||||
/// # #[component]
|
||||
/// # fn App() -> Element {
|
||||
/// # rsx! {
|
||||
/// # Router::<Route> {
|
||||
/// # config: || RouterConfig::default().history(MemoryHistory::with_initial_path(Route::Child {}))
|
||||
/// # dioxus_router::components::HistoryProvider {
|
||||
/// # history: move |_| std::rc::Rc::new(dioxus_history::MemoryHistory::with_initial_path(Route::Child {}.to_string())) as std::rc::Rc<dyn dioxus_history::History>,
|
||||
/// # Router::<Route> {}
|
||||
/// # }
|
||||
/// # }
|
||||
/// # }
|
||||
|
||||
@@ -46,10 +46,7 @@ where
|
||||
use crate::prelude::{outlet::OutletContext, RouterContext};
|
||||
|
||||
use_hook(|| {
|
||||
provide_router_context(RouterContext::new(
|
||||
props.config.call(()),
|
||||
schedule_update_any(),
|
||||
));
|
||||
provide_router_context(RouterContext::new(props.config.call(())));
|
||||
|
||||
provide_context(OutletContext::<R> {
|
||||
current_level: 0,
|
||||
|
||||
Reference in new issue
Block a user