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:
Evan AlmloffandJonathan Kelley authored and GitHub committed 2024-10-31 12:44:04 -07:00
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
}
+8 -78
View File
@@ -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() }"
)
};
+3 -2
View File
@@ -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> {}
/// # }
/// # }
/// # }
+1 -4
View File
@@ -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,