general cleanup
This commit is contained in:
1 parent
09cabe4e8b
commit
ece8f0fb22
15 files changed
+155
-767
No files matched your search
@@ -101,6 +101,10 @@ fn Route3(cx: Scope, dynamic: String) -> Element {
|
|||||||
target: Route::Route2 { user_id: 8888 },
|
target: Route::Route2 { user_id: 8888 },
|
||||||
"hello world link"
|
"hello world link"
|
||||||
}
|
}
|
||||||
|
button {
|
||||||
|
onclick: move |_| { router.push(NavigationTarget::External("https://www.google.com".to_string())); },
|
||||||
|
"google link"
|
||||||
|
}
|
||||||
p { "Site Map" }
|
p { "Site Map" }
|
||||||
pre { "{site_map:#?}" }
|
pre { "{site_map:#?}" }
|
||||||
p { "Dynamic link" }
|
p { "Dynamic link" }
|
||||||
|
|||||||
@@ -1,15 +1,10 @@
|
|||||||
use crate::{
|
use crate::{hooks::use_generic_router, routable::Routable};
|
||||||
components::GenericLink, hooks::use_generic_route, navigation::NavigationTarget,
|
|
||||||
routable::Routable,
|
|
||||||
};
|
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
|
/// 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<R: Routable + Clone>(cx: Scope) -> Element {
|
||||||
let href = use_generic_route::<R>(cx).expect(
|
let router = use_generic_router::<R>(cx);
|
||||||
"`FailureExternalNavigation` can only be mounted by the router itself, \
|
|
||||||
since it is not exposed",
|
|
||||||
);
|
|
||||||
|
|
||||||
render! {
|
render! {
|
||||||
h1 { "External Navigation Failure!" }
|
h1 { "External Navigation Failure!" }
|
||||||
@@ -18,53 +13,10 @@ pub fn FailureExternalNavigation<R: Routable + Clone>(cx: Scope) -> Element {
|
|||||||
"operation has failed. Click the link below to complete the navigation manually."
|
"operation has failed. Click the link below to complete the navigation manually."
|
||||||
}
|
}
|
||||||
a {
|
a {
|
||||||
href: "{href}",
|
onclick: move |_| {
|
||||||
rel: "noopener noreferrer",
|
router.clear_error()
|
||||||
"Click here to fix the failure."
|
},
|
||||||
}
|
"Click here to go back"
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
pub fn FailureNamedNavigation<R: Routable + Clone>(cx: Scope) -> Element {
|
|
||||||
render! {
|
|
||||||
h1 { "Named Navigation Failure!" }
|
|
||||||
p {
|
|
||||||
"The application has tried to navigate to an unknown name. This is a bug. Please "
|
|
||||||
"inform the developer, so they can fix it."
|
|
||||||
b { "Thank you!" }
|
|
||||||
}
|
|
||||||
p {
|
|
||||||
"We are sorry for the inconvenience. The link below may help to fix the problem, but "
|
|
||||||
"there is no guarantee."
|
|
||||||
}
|
|
||||||
GenericLink::<R> {
|
|
||||||
target: NavigationTarget::Internal(R::from_str("/").unwrap_or_else(|_| {
|
|
||||||
panic!("Failed to parse `/` as a Route")
|
|
||||||
})),
|
|
||||||
"Click here to try to fix the failure."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
pub fn FailureRedirectionLimit<R: Routable + Clone>(cx: Scope) -> Element {
|
|
||||||
render! {
|
|
||||||
h1 { "Redirection Limit Failure!" }
|
|
||||||
p {
|
|
||||||
"The application seems to have entered into an endless redirection loop. This is a "
|
|
||||||
"bug. Please inform the developer, so they can fix it."
|
|
||||||
b { "Thank you!" }
|
|
||||||
}
|
|
||||||
p {
|
|
||||||
"We are sorry for the inconvenience. The link below may help to fix the problem, but "
|
|
||||||
"there is no guarantee."
|
|
||||||
}
|
|
||||||
GenericLink::<R> {
|
|
||||||
target: NavigationTarget::Internal(R::from_str("/").unwrap_or_else(|_| {
|
|
||||||
panic!("Failed to parse `/` as a Route")
|
|
||||||
})),
|
|
||||||
"Click here to try to fix the failure."
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
use crate::{contexts::outlet::OutletContext, routable::Routable};
|
use crate::prelude::{outlet::OutletContext, *};
|
||||||
use dioxus::prelude::*;
|
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 [`GenericRouter`] component, otherwise it will be inactive.
|
||||||
///
|
///
|
||||||
/// The [`Outlet`] is aware of how many [`Outlet`]s it is nested within. It will render the content
|
/// The [`GenericOutlet`] 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 [`Outlet`] is not nested within another component calling the [`use_router`] hook,
|
/// - When the [`GenericOutlet`] is not nested a [`GenericRouter`] component,
|
||||||
/// but only in debug builds.
|
/// but only in debug builds.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use crate::{
|
|||||||
router_cfg::RouterConfiguration,
|
router_cfg::RouterConfiguration,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The config for [`Router`].
|
/// The config for [`GenericRouter`].
|
||||||
pub struct RouterCfg<R: Routable> {
|
pub struct RouterCfg<R: Routable> {
|
||||||
config: RefCell<Option<RouterConfiguration<R>>>,
|
config: RefCell<Option<RouterConfiguration<R>>>,
|
||||||
}
|
}
|
||||||
@@ -33,7 +33,7 @@ impl<R: Routable> From<RouterConfiguration<R>> for RouterCfg<R> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The props for [`Router`].
|
/// The props for [`GenericRouter`].
|
||||||
#[derive(Props)]
|
#[derive(Props)]
|
||||||
pub struct GenericRouterProps<R: Routable + Serialize + DeserializeOwned>
|
pub struct GenericRouterProps<R: Routable + Serialize + DeserializeOwned>
|
||||||
where
|
where
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
use crate::{hooks::use_generic_route, routable::Routable};
|
use crate::{routable::Routable, utils::use_router_internal::use_router_internal};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub(crate) struct OutletContext {
|
pub(crate) struct OutletContext {
|
||||||
@@ -16,7 +16,10 @@ pub(crate) fn use_outlet_context(cx: &ScopeState) -> &OutletContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl OutletContext {
|
impl OutletContext {
|
||||||
pub(crate) fn render<R: Routable + Clone>(cx: &ScopeState) -> Element<'_> {
|
pub(crate) fn render<R: Routable + Clone>(cx: Scope) -> Element<'_> {
|
||||||
|
let router = use_router_internal::<R>(cx)
|
||||||
|
.as_ref()
|
||||||
|
.expect("Outlet must be inside of a router");
|
||||||
let outlet = use_outlet_context(cx);
|
let outlet = use_outlet_context(cx);
|
||||||
let current_level = outlet.current_level;
|
let current_level = outlet.current_level;
|
||||||
cx.provide_context({
|
cx.provide_context({
|
||||||
@@ -25,8 +28,14 @@ impl OutletContext {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
use_generic_route::<R>(cx)
|
if let Some(error) = router.render_error(cx) {
|
||||||
.expect("Outlet must be inside of a router")
|
if current_level == 0 {
|
||||||
.render(cx, current_level)
|
return Some(error);
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
router.current().render(cx, current_level)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -11,12 +11,8 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// An error that can occur when navigating.
|
/// An error that can occur when navigating.
|
||||||
pub enum NavigationFailure<R: Routable> {
|
#[derive(Debug, Clone)]
|
||||||
/// The router failed to navigate to an external URL.
|
pub struct ExternalNavigationFailure(String);
|
||||||
External(String),
|
|
||||||
/// The router failed to navigate to an internal URL.
|
|
||||||
Internal(<R as std::str::FromStr>::Err),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A function the router will call after every routing update.
|
/// A function the router will call after every routing update.
|
||||||
pub type RoutingCallback<R> = Arc<dyn Fn(GenericRouterContext<R>) -> Option<NavigationTarget<R>>>;
|
pub type RoutingCallback<R> = Arc<dyn Fn(GenericRouterContext<R>) -> Option<NavigationTarget<R>>>;
|
||||||
@@ -40,6 +36,8 @@ where
|
|||||||
prefix: Option<String>,
|
prefix: Option<String>,
|
||||||
|
|
||||||
history: Box<dyn HistoryProvider<R>>,
|
history: Box<dyn HistoryProvider<R>>,
|
||||||
|
|
||||||
|
unresolved_error: Option<ExternalNavigationFailure>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A collection of router data that manages all routing functionality.
|
/// A collection of router data that manages all routing functionality.
|
||||||
@@ -54,8 +52,6 @@ where
|
|||||||
routing_callback: Option<RoutingCallback<R>>,
|
routing_callback: Option<RoutingCallback<R>>,
|
||||||
|
|
||||||
failure_external_navigation: fn(Scope) -> Element,
|
failure_external_navigation: fn(Scope) -> Element,
|
||||||
failure_named_navigation: fn(Scope) -> Element,
|
|
||||||
failure_redirection_limit: fn(Scope) -> Element,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: Routable> Clone for GenericRouterContext<R> {
|
impl<R: Routable> Clone for GenericRouterContext<R> {
|
||||||
@@ -66,8 +62,6 @@ impl<R: Routable> Clone for GenericRouterContext<R> {
|
|||||||
subscriber_update: self.subscriber_update.clone(),
|
subscriber_update: self.subscriber_update.clone(),
|
||||||
routing_callback: self.routing_callback.clone(),
|
routing_callback: self.routing_callback.clone(),
|
||||||
failure_external_navigation: self.failure_external_navigation,
|
failure_external_navigation: self.failure_external_navigation,
|
||||||
failure_named_navigation: self.failure_named_navigation,
|
|
||||||
failure_redirection_limit: self.failure_redirection_limit,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -88,6 +82,7 @@ where
|
|||||||
can_go_forward: false,
|
can_go_forward: false,
|
||||||
prefix: Default::default(),
|
prefix: Default::default(),
|
||||||
history: cfg.history,
|
history: cfg.history,
|
||||||
|
unresolved_error: None,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let subscriber_update = mark_dirty.clone();
|
let subscriber_update = mark_dirty.clone();
|
||||||
@@ -101,8 +96,6 @@ where
|
|||||||
routing_callback: cfg.on_update,
|
routing_callback: cfg.on_update,
|
||||||
|
|
||||||
failure_external_navigation: cfg.failure_external_navigation,
|
failure_external_navigation: cfg.failure_external_navigation,
|
||||||
failure_named_navigation: cfg.failure_named_navigation,
|
|
||||||
failure_redirection_limit: cfg.failure_redirection_limit,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// set the updater
|
// set the updater
|
||||||
@@ -134,46 +127,61 @@ where
|
|||||||
///
|
///
|
||||||
/// Will fail silently if there is no previous location to go to.
|
/// Will fail silently if there is no previous location to go to.
|
||||||
pub fn go_back(&self) {
|
pub fn go_back(&self) {
|
||||||
self.state.write().unwrap().history.go_back();
|
{
|
||||||
self.update_subscribers();
|
self.state.write().unwrap().history.go_back();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.change_route();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Go back to the next location.
|
/// Go back to the next location.
|
||||||
///
|
///
|
||||||
/// Will fail silently if there is no next location to go to.
|
/// Will fail silently if there is no next location to go to.
|
||||||
pub fn go_forward(&self) {
|
pub fn go_forward(&self) {
|
||||||
self.state.write().unwrap().history.go_forward();
|
{
|
||||||
self.update_subscribers();
|
self.state.write().unwrap().history.go_forward();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.change_route();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Push a new location.
|
/// Push a new location.
|
||||||
///
|
///
|
||||||
/// The previous location will be available to go back to.
|
/// The previous location will be available to go back to.
|
||||||
pub fn push(&self, target: impl Into<NavigationTarget<R>>) -> Option<NavigationFailure<R>> {
|
pub fn push(
|
||||||
|
&self,
|
||||||
|
target: impl Into<NavigationTarget<R>>,
|
||||||
|
) -> Option<ExternalNavigationFailure> {
|
||||||
let target = target.into();
|
let target = target.into();
|
||||||
let mut state = self.state_mut();
|
|
||||||
match target {
|
match target {
|
||||||
NavigationTarget::Internal(p) => state.history.push(p),
|
NavigationTarget::Internal(p) => {
|
||||||
|
let mut state = self.state_mut();
|
||||||
|
state.history.push(p)
|
||||||
|
}
|
||||||
NavigationTarget::External(e) => return self.external(e),
|
NavigationTarget::External(e) => return self.external(e),
|
||||||
}
|
}
|
||||||
|
|
||||||
self.update_subscribers();
|
self.change_route()
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Replace the current location.
|
/// Replace the current location.
|
||||||
///
|
///
|
||||||
/// The previous location will **not** be available to go back to.
|
/// The previous location will **not** be available to go back to.
|
||||||
pub fn replace(&self, target: impl Into<NavigationTarget<R>>) -> Option<NavigationFailure<R>> {
|
pub fn replace(
|
||||||
|
&self,
|
||||||
|
target: impl Into<NavigationTarget<R>>,
|
||||||
|
) -> Option<ExternalNavigationFailure> {
|
||||||
let target = target.into();
|
let target = target.into();
|
||||||
let mut state = self.state_mut();
|
|
||||||
match target {
|
{
|
||||||
NavigationTarget::Internal(p) => state.history.replace(p),
|
let mut state = self.state_mut();
|
||||||
NavigationTarget::External(e) => return self.external(e),
|
match target {
|
||||||
|
NavigationTarget::Internal(p) => state.history.replace(p),
|
||||||
|
NavigationTarget::External(e) => return self.external(e),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.update_subscribers();
|
self.change_route()
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The route that is currently active.
|
/// The route that is currently active.
|
||||||
@@ -189,11 +197,18 @@ where
|
|||||||
self.state.read().unwrap().prefix.clone()
|
self.state.read().unwrap().prefix.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn external(&self, external: String) -> Option<NavigationFailure<R>> {
|
fn external(&self, external: String) -> Option<ExternalNavigationFailure> {
|
||||||
let mut state = self.state_mut();
|
let mut state = self.state_mut();
|
||||||
match state.history.external(external.clone()) {
|
match state.history.external(external.clone()) {
|
||||||
true => None,
|
true => None,
|
||||||
false => Some(NavigationFailure::External(external)),
|
false => {
|
||||||
|
let failure = ExternalNavigationFailure(external);
|
||||||
|
state.unresolved_error = Some(failure.clone());
|
||||||
|
|
||||||
|
self.update_subscribers();
|
||||||
|
|
||||||
|
Some(failure)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,614 +231,37 @@ where
|
|||||||
(self.subscriber_update)(id);
|
(self.subscriber_update)(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Clear any unresolved errors
|
||||||
|
pub fn clear_error(&self) {
|
||||||
|
self.state.write().unwrap().unresolved_error = None;
|
||||||
|
|
||||||
|
self.update_subscribers();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn render_error<'a>(&self, cx: Scope<'a>) -> Element<'a> {
|
||||||
|
self.state
|
||||||
|
.read()
|
||||||
|
.unwrap()
|
||||||
|
.unresolved_error
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|_| (self.failure_external_navigation)(cx))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn change_route(&self) -> Option<ExternalNavigationFailure> {
|
||||||
|
if let Some(callback) = &self.routing_callback {
|
||||||
|
let myself = self.clone();
|
||||||
|
if let Some(new) = callback(myself) {
|
||||||
|
let mut state = self.state_mut();
|
||||||
|
match new {
|
||||||
|
NavigationTarget::Internal(p) => state.history.replace(p),
|
||||||
|
NavigationTarget::External(e) => return self.external(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.update_subscribers();
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[cfg(test)]
|
|
||||||
// mod tests {
|
|
||||||
// //! The tests for [`RouterContext`] test various functions that are not exposed as public.
|
|
||||||
// //! However, several of those have an observable effect on the behavior of exposed functions.
|
|
||||||
// //!
|
|
||||||
// //! The alternative would be to send messages via the services channel and calling one of the
|
|
||||||
// //! `run` functions. However, for readability and clarity, it was chosen to directly call the
|
|
||||||
// //! private functions.
|
|
||||||
|
|
||||||
// use std::sync::Mutex;
|
|
||||||
|
|
||||||
// use crate::{
|
|
||||||
// history::MemoryHistory,
|
|
||||||
// routes::{ParameterRoute, Route, RouteContent},
|
|
||||||
// };
|
|
||||||
|
|
||||||
// use super::*;
|
|
||||||
|
|
||||||
// fn test_segment() -> Segment<&'static str> {
|
|
||||||
// Segment::content(RouteContent::Content(ContentAtom("index")))
|
|
||||||
// .fixed(
|
|
||||||
// "fixed",
|
|
||||||
// Route::content(RouteContent::Content(ContentAtom("fixed"))).name::<bool>(),
|
|
||||||
// )
|
|
||||||
// .fixed(
|
|
||||||
// "redirect",
|
|
||||||
// Route::content(RouteContent::Redirect(NavigationTarget::Internal(
|
|
||||||
// String::from("fixed"),
|
|
||||||
// ))),
|
|
||||||
// )
|
|
||||||
// .fixed(
|
|
||||||
// "redirection-loop",
|
|
||||||
// Route::content(RouteContent::Redirect(NavigationTarget::Internal(
|
|
||||||
// String::from("/redirection-loop"),
|
|
||||||
// ))),
|
|
||||||
// )
|
|
||||||
// .fixed(
|
|
||||||
// "%F0%9F%8E%BA",
|
|
||||||
// Route::content(RouteContent::Content(ContentAtom("🎺"))),
|
|
||||||
// )
|
|
||||||
// .catch_all(ParameterRoute::empty::<bool>())
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn new_provides_update_to_history() {
|
|
||||||
// struct TestHistory {}
|
|
||||||
|
|
||||||
// impl HistoryProvider for TestHistory {
|
|
||||||
// fn current_route(&self) -> String {
|
|
||||||
// todo!()
|
|
||||||
// }
|
|
||||||
|
|
||||||
// fn current_query(&self) -> Option<String> {
|
|
||||||
// todo!()
|
|
||||||
// }
|
|
||||||
|
|
||||||
// fn go_back(&mut self) {
|
|
||||||
// todo!()
|
|
||||||
// }
|
|
||||||
|
|
||||||
// fn go_forward(&mut self) {
|
|
||||||
// todo!()
|
|
||||||
// }
|
|
||||||
|
|
||||||
// fn push(&mut self, _path: String) {
|
|
||||||
// todo!()
|
|
||||||
// }
|
|
||||||
|
|
||||||
// fn replace(&mut self, _path: String) {
|
|
||||||
// todo!()
|
|
||||||
// }
|
|
||||||
|
|
||||||
// fn updater(&mut self, callback: Arc<dyn Fn() + Send + Sync>) {
|
|
||||||
// callback();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// let (mut s, _, _) = RouterContext::<_, u8>::new(
|
|
||||||
// test_segment(),
|
|
||||||
// Box::new(TestHistory {}),
|
|
||||||
// Arc::new(|_| {}),
|
|
||||||
// None,
|
|
||||||
// ContentAtom("external target"),
|
|
||||||
// ContentAtom("named target"),
|
|
||||||
// ContentAtom("redirect limit"),
|
|
||||||
// );
|
|
||||||
|
|
||||||
// assert!(matches!(
|
|
||||||
// s.receiver.try_next().unwrap().unwrap(),
|
|
||||||
// RouterMessage::Update
|
|
||||||
// ));
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn update_routing() {
|
|
||||||
// let (mut s, _, _) = RouterContext::<_, u8>::new(
|
|
||||||
// test_segment(),
|
|
||||||
// Box::new(MemoryHistory::with_initial_path("/fixed?test=value").unwrap()),
|
|
||||||
// Arc::new(|_| {}),
|
|
||||||
// None,
|
|
||||||
// ContentAtom("external target"),
|
|
||||||
// ContentAtom("named target"),
|
|
||||||
// ContentAtom("redirect limit"),
|
|
||||||
// );
|
|
||||||
// assert_eq!(s.names, s.state.try_read().unwrap().name_map);
|
|
||||||
// s.init();
|
|
||||||
|
|
||||||
// let state = s.state.try_read().unwrap();
|
|
||||||
// assert_eq!(state.content, vec![ContentAtom("fixed")]);
|
|
||||||
// assert_eq!(state.names, {
|
|
||||||
// let mut r = HashSet::new();
|
|
||||||
// r.insert(Name::of::<bool>());
|
|
||||||
// r
|
|
||||||
// });
|
|
||||||
// assert!(state.parameters.is_empty());
|
|
||||||
// assert_eq!(state.path, String::from("/fixed"));
|
|
||||||
// assert_eq!(state.query, Some(String::from("test=value")));
|
|
||||||
// assert!(!state.can_go_back);
|
|
||||||
// assert!(!state.can_go_forward);
|
|
||||||
// assert_eq!(s.names, state.name_map);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn update_routing_root_index() {
|
|
||||||
// let (mut s, _, _) = RouterContext::<_, u8>::new(
|
|
||||||
// test_segment(),
|
|
||||||
// Box::<MemoryHistory>::default(),
|
|
||||||
// Arc::new(|_| {}),
|
|
||||||
// None,
|
|
||||||
// ContentAtom("external target"),
|
|
||||||
// ContentAtom("named target"),
|
|
||||||
// ContentAtom("redirect limit"),
|
|
||||||
// );
|
|
||||||
// s.init();
|
|
||||||
|
|
||||||
// let state = s.state.try_read().unwrap();
|
|
||||||
// assert_eq!(state.content, vec![ContentAtom("index")]);
|
|
||||||
// assert_eq!(state.names, {
|
|
||||||
// let mut r = HashSet::new();
|
|
||||||
// r.insert(Name::of::<RootIndex>());
|
|
||||||
// r
|
|
||||||
// });
|
|
||||||
// assert!(state.parameters.is_empty());
|
|
||||||
// assert_eq!(state.path, String::from("/"));
|
|
||||||
// assert!(state.query.is_none());
|
|
||||||
// assert!(state.prefix.is_none());
|
|
||||||
// assert!(!state.can_go_back);
|
|
||||||
// assert!(!state.can_go_forward);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn update_routing_redirect() {
|
|
||||||
// let (mut s, _, _) = RouterContext::<_, u8>::new(
|
|
||||||
// test_segment(),
|
|
||||||
// Box::new(MemoryHistory::with_initial_path("/redirect").unwrap()),
|
|
||||||
// Arc::new(|_| {}),
|
|
||||||
// None,
|
|
||||||
// ContentAtom("external target"),
|
|
||||||
// ContentAtom("named target"),
|
|
||||||
// ContentAtom("redirect limit"),
|
|
||||||
// );
|
|
||||||
// s.init();
|
|
||||||
|
|
||||||
// let state = s.state.try_read().unwrap();
|
|
||||||
// assert_eq!(state.content, vec![ContentAtom("fixed")]);
|
|
||||||
// assert_eq!(state.names, {
|
|
||||||
// let mut r = HashSet::new();
|
|
||||||
// r.insert(Name::of::<bool>());
|
|
||||||
// r
|
|
||||||
// });
|
|
||||||
// assert!(state.parameters.is_empty());
|
|
||||||
// assert_eq!(state.path, String::from("/fixed"));
|
|
||||||
// assert!(!state.can_go_back);
|
|
||||||
// assert!(!state.can_go_forward);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// #[should_panic = "reached redirect limit of 25"]
|
|
||||||
// #[cfg(debug_assertions)]
|
|
||||||
// fn update_routing_redirect_debug() {
|
|
||||||
// let (mut s, _, _) = RouterContext::<_, u8>::new(
|
|
||||||
// test_segment(),
|
|
||||||
// Box::new(MemoryHistory::with_initial_path("/redirection-loop").unwrap()),
|
|
||||||
// Arc::new(|_| {}),
|
|
||||||
// None,
|
|
||||||
// ContentAtom("external target"),
|
|
||||||
// ContentAtom("named target"),
|
|
||||||
// ContentAtom("redirect limit"),
|
|
||||||
// );
|
|
||||||
// s.init();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// #[cfg(not(debug_assertions))]
|
|
||||||
// fn update_routing_redirect_release() {
|
|
||||||
// let (mut s, _, _) = RouterContext::<_, u8>::new(
|
|
||||||
// test_segment(),
|
|
||||||
// Box::new(MemoryHistory::with_initial_path("/redirection-loop").unwrap()),
|
|
||||||
// Arc::new(|_| {}),
|
|
||||||
// None,
|
|
||||||
// ContentAtom("external target"),
|
|
||||||
// ContentAtom("named target"),
|
|
||||||
// ContentAtom("redirect limit"),
|
|
||||||
// );
|
|
||||||
// s.init();
|
|
||||||
|
|
||||||
// let state = s.state.try_read().unwrap();
|
|
||||||
// assert_eq!(state.content, vec![ContentAtom("redirect limit")]);
|
|
||||||
// assert_eq!(state.names, {
|
|
||||||
// let mut r = HashSet::new();
|
|
||||||
// r.insert(Name::of::<FailureRedirectionLimit>());
|
|
||||||
// r
|
|
||||||
// });
|
|
||||||
// assert!(state.parameters.is_empty());
|
|
||||||
// assert_eq!(state.path, String::from("/redirection-loop"));
|
|
||||||
// assert_eq!(state.can_go_back, false);
|
|
||||||
// assert_eq!(state.can_go_forward, false);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn update_subscribers() {
|
|
||||||
// let ids = Arc::new(Mutex::new(Vec::new()));
|
|
||||||
// let ids2 = Arc::clone(&ids);
|
|
||||||
|
|
||||||
// let (mut s, _, _) = RouterContext::<_, u8>::new(
|
|
||||||
// Segment::empty(),
|
|
||||||
// Box::<MemoryHistory>::default(),
|
|
||||||
// Arc::new(move |id| {
|
|
||||||
// ids2.lock().unwrap().push(id);
|
|
||||||
// }),
|
|
||||||
// None,
|
|
||||||
// ContentAtom("external target"),
|
|
||||||
// ContentAtom("named target"),
|
|
||||||
// ContentAtom("redirect limit"),
|
|
||||||
// );
|
|
||||||
|
|
||||||
// let id0 = Arc::new(0);
|
|
||||||
// s.subscribe(Arc::clone(&id0));
|
|
||||||
|
|
||||||
// let id1 = Arc::new(1);
|
|
||||||
// s.subscribe(Arc::clone(&id1));
|
|
||||||
|
|
||||||
// let id1 = Arc::try_unwrap(id1).unwrap();
|
|
||||||
// s.update_subscribers();
|
|
||||||
|
|
||||||
// assert_eq!(s.subscribers.len(), 1);
|
|
||||||
// assert_eq!(s.subscribers[0].upgrade().unwrap(), id0);
|
|
||||||
// assert_eq!(*ids.lock().unwrap(), vec![*id0, id1, *id0]);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn push_internal() {
|
|
||||||
// let (mut s, _, _) = RouterContext::<_, u8>::new(
|
|
||||||
// test_segment(),
|
|
||||||
// Box::<MemoryHistory>::default(),
|
|
||||||
// Arc::new(|_| {}),
|
|
||||||
// None,
|
|
||||||
// ContentAtom("external target"),
|
|
||||||
// ContentAtom("named target"),
|
|
||||||
// ContentAtom("redirect limit"),
|
|
||||||
// );
|
|
||||||
// s.push(NavigationTarget::Internal(String::from("/fixed")));
|
|
||||||
// s.init();
|
|
||||||
|
|
||||||
// let state = s.state.try_read().unwrap();
|
|
||||||
// assert_eq!(state.content, vec![ContentAtom("fixed")]);
|
|
||||||
// assert_eq!(state.names, {
|
|
||||||
// let mut r = HashSet::new();
|
|
||||||
// r.insert(Name::of::<bool>());
|
|
||||||
// r
|
|
||||||
// });
|
|
||||||
// assert!(state.parameters.is_empty());
|
|
||||||
// assert_eq!(state.path, String::from("/fixed"));
|
|
||||||
// assert!(state.can_go_back);
|
|
||||||
// assert!(!state.can_go_forward);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn push_named() {
|
|
||||||
// let (mut s, _, _) = RouterContext::<_, u8>::new(
|
|
||||||
// test_segment(),
|
|
||||||
// Box::<MemoryHistory>::default(),
|
|
||||||
// Arc::new(|_| {}),
|
|
||||||
// None,
|
|
||||||
// ContentAtom("external target"),
|
|
||||||
// ContentAtom("named target"),
|
|
||||||
// ContentAtom("redirect limit"),
|
|
||||||
// );
|
|
||||||
// s.push(NavigationTarget::named::<bool>());
|
|
||||||
// s.init();
|
|
||||||
|
|
||||||
// let state = s.state.try_read().unwrap();
|
|
||||||
// assert_eq!(state.content, vec![ContentAtom("fixed")]);
|
|
||||||
// assert_eq!(state.names, {
|
|
||||||
// let mut r = HashSet::new();
|
|
||||||
// r.insert(Name::of::<bool>());
|
|
||||||
// r
|
|
||||||
// });
|
|
||||||
// assert!(state.parameters.is_empty());
|
|
||||||
// assert_eq!(state.path, String::from("/fixed"));
|
|
||||||
// assert!(state.can_go_back);
|
|
||||||
// assert!(!state.can_go_forward);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn push_external() {
|
|
||||||
// let (mut s, tx, _) = RouterContext::<_, u8>::new(
|
|
||||||
// test_segment(),
|
|
||||||
// Box::new(MemoryHistory::with_initial_path("/fixed").unwrap()),
|
|
||||||
// Arc::new(|_| {}),
|
|
||||||
// None,
|
|
||||||
// ContentAtom("external target"),
|
|
||||||
// ContentAtom("named target"),
|
|
||||||
// ContentAtom("redirect limit"),
|
|
||||||
// );
|
|
||||||
// s.init();
|
|
||||||
// tx.unbounded_send(RouterMessage::Push(NavigationTarget::External(
|
|
||||||
// String::from("https://dioxuslabs.com/"),
|
|
||||||
// )))
|
|
||||||
// .unwrap();
|
|
||||||
// s.run_current();
|
|
||||||
|
|
||||||
// let state = s.state.try_read().unwrap();
|
|
||||||
// assert_eq!(state.content, vec![ContentAtom("external target")]);
|
|
||||||
// assert_eq!(state.names, {
|
|
||||||
// let mut r = HashSet::new();
|
|
||||||
// r.insert(Name::of::<FailureExternalNavigation>());
|
|
||||||
// r
|
|
||||||
// });
|
|
||||||
// assert_eq!(state.parameters, {
|
|
||||||
// let mut r = HashMap::new();
|
|
||||||
// r.insert(
|
|
||||||
// Name::of::<FailureExternalNavigation>(),
|
|
||||||
// String::from("https://dioxuslabs.com/"),
|
|
||||||
// );
|
|
||||||
// r
|
|
||||||
// });
|
|
||||||
// assert_eq!(state.path, String::from("/fixed"));
|
|
||||||
// assert!(!state.can_go_back);
|
|
||||||
// assert!(!state.can_go_forward);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn replace_named() {
|
|
||||||
// let (mut s, _, _) = RouterContext::<_, u8>::new(
|
|
||||||
// test_segment(),
|
|
||||||
// Box::<MemoryHistory>::default(),
|
|
||||||
// Arc::new(|_| {}),
|
|
||||||
// None,
|
|
||||||
// ContentAtom("external target"),
|
|
||||||
// ContentAtom("named target"),
|
|
||||||
// ContentAtom("redirect limit"),
|
|
||||||
// );
|
|
||||||
// s.replace(NavigationTarget::named::<bool>());
|
|
||||||
// s.init();
|
|
||||||
|
|
||||||
// let state = s.state.try_read().unwrap();
|
|
||||||
// assert_eq!(state.content, vec![ContentAtom("fixed")]);
|
|
||||||
// assert_eq!(state.names, {
|
|
||||||
// let mut r = HashSet::new();
|
|
||||||
// r.insert(Name::of::<bool>());
|
|
||||||
// r
|
|
||||||
// });
|
|
||||||
// assert!(state.parameters.is_empty());
|
|
||||||
// assert_eq!(state.path, String::from("/fixed"));
|
|
||||||
// assert!(!state.can_go_back);
|
|
||||||
// assert!(!state.can_go_forward);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn replace_internal() {
|
|
||||||
// let (mut s, _, _) = RouterContext::<_, u8>::new(
|
|
||||||
// test_segment(),
|
|
||||||
// Box::<MemoryHistory>::default(),
|
|
||||||
// Arc::new(|_| {}),
|
|
||||||
// None,
|
|
||||||
// ContentAtom("external target"),
|
|
||||||
// ContentAtom("named target"),
|
|
||||||
// ContentAtom("redirect limit"),
|
|
||||||
// );
|
|
||||||
// s.replace(NavigationTarget::Internal(String::from("/fixed")));
|
|
||||||
// s.init();
|
|
||||||
|
|
||||||
// let state = s.state.try_read().unwrap();
|
|
||||||
// assert_eq!(state.content, vec![ContentAtom("fixed")]);
|
|
||||||
// assert_eq!(state.names, {
|
|
||||||
// let mut r = HashSet::new();
|
|
||||||
// r.insert(Name::of::<bool>());
|
|
||||||
// r
|
|
||||||
// });
|
|
||||||
// assert!(state.parameters.is_empty());
|
|
||||||
// assert_eq!(state.path, String::from("/fixed"));
|
|
||||||
// assert!(!state.can_go_back);
|
|
||||||
// assert!(!state.can_go_forward);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn replace_external() {
|
|
||||||
// let (mut s, tx, _) = RouterContext::<_, u8>::new(
|
|
||||||
// test_segment(),
|
|
||||||
// Box::new(MemoryHistory::with_initial_path("/fixed").unwrap()),
|
|
||||||
// Arc::new(|_| {}),
|
|
||||||
// None,
|
|
||||||
// ContentAtom("external target"),
|
|
||||||
// ContentAtom("named target"),
|
|
||||||
// ContentAtom("redirect limit"),
|
|
||||||
// );
|
|
||||||
// s.init();
|
|
||||||
// tx.unbounded_send(RouterMessage::Replace(NavigationTarget::External(
|
|
||||||
// String::from("https://dioxuslabs.com/"),
|
|
||||||
// )))
|
|
||||||
// .unwrap();
|
|
||||||
// s.run_current();
|
|
||||||
|
|
||||||
// let state = s.state.try_read().unwrap();
|
|
||||||
// assert_eq!(state.content, vec![ContentAtom("external target")]);
|
|
||||||
// assert_eq!(state.names, {
|
|
||||||
// let mut r = HashSet::new();
|
|
||||||
// r.insert(Name::of::<FailureExternalNavigation>());
|
|
||||||
// r
|
|
||||||
// });
|
|
||||||
// assert_eq!(state.parameters, {
|
|
||||||
// let mut r = HashMap::new();
|
|
||||||
// r.insert(
|
|
||||||
// Name::of::<FailureExternalNavigation>(),
|
|
||||||
// String::from("https://dioxuslabs.com/"),
|
|
||||||
// );
|
|
||||||
// r
|
|
||||||
// });
|
|
||||||
// assert_eq!(state.path, String::from("/fixed"));
|
|
||||||
// assert!(!state.can_go_back);
|
|
||||||
// assert!(!state.can_go_forward);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn subscribe() {
|
|
||||||
// let (mut s, _, _) = RouterContext::<_, u8>::new(
|
|
||||||
// Segment::empty(),
|
|
||||||
// Box::<MemoryHistory>::default(),
|
|
||||||
// Arc::new(|_| {}),
|
|
||||||
// None,
|
|
||||||
// ContentAtom("external target"),
|
|
||||||
// ContentAtom("named target"),
|
|
||||||
// ContentAtom("redirect limit"),
|
|
||||||
// );
|
|
||||||
|
|
||||||
// let id = Arc::new(0);
|
|
||||||
// s.subscribe(Arc::clone(&id));
|
|
||||||
|
|
||||||
// assert_eq!(s.subscribers.len(), 1);
|
|
||||||
// assert_eq!(s.subscribers[0].upgrade().unwrap(), id);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn routing_callback() {
|
|
||||||
// let paths = Arc::new(Mutex::new(Vec::new()));
|
|
||||||
// let paths2 = Arc::clone(&paths);
|
|
||||||
|
|
||||||
// let (mut s, c, _) = RouterContext::<_, u8>::new(
|
|
||||||
// test_segment(),
|
|
||||||
// Box::new(MemoryHistory::with_initial_path("/fixed").unwrap()),
|
|
||||||
// Arc::new(|_| {}),
|
|
||||||
// Some(Arc::new(move |state| {
|
|
||||||
// paths2.lock().unwrap().push(state.path.clone());
|
|
||||||
// Some("/%F0%9F%8E%BA".into())
|
|
||||||
// })),
|
|
||||||
// ContentAtom("external target"),
|
|
||||||
// ContentAtom("named target"),
|
|
||||||
// ContentAtom("redirect limit"),
|
|
||||||
// );
|
|
||||||
|
|
||||||
// assert!(paths.lock().unwrap().is_empty());
|
|
||||||
|
|
||||||
// s.init();
|
|
||||||
// assert_eq!(*paths.lock().unwrap(), vec![String::from("/fixed")]);
|
|
||||||
|
|
||||||
// c.unbounded_send(RouterMessage::Update).unwrap();
|
|
||||||
// s.run_current();
|
|
||||||
// assert_eq!(
|
|
||||||
// *paths.lock().unwrap(),
|
|
||||||
// vec![String::from("/fixed"), String::from("/%F0%9F%8E%BA")]
|
|
||||||
// );
|
|
||||||
|
|
||||||
// let state = s.state.try_read().unwrap();
|
|
||||||
// assert_eq!(state.content, vec![ContentAtom("🎺")])
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn url_decoding_do() {
|
|
||||||
// let (mut s, _, _) = RouterContext::<_, u8>::new(
|
|
||||||
// test_segment(),
|
|
||||||
// Box::new(MemoryHistory::with_initial_path("/%F0%9F%A5%B3").unwrap()),
|
|
||||||
// Arc::new(|_| {}),
|
|
||||||
// None,
|
|
||||||
// ContentAtom("external target"),
|
|
||||||
// ContentAtom("named target"),
|
|
||||||
// ContentAtom("redirect limit"),
|
|
||||||
// );
|
|
||||||
// s.init();
|
|
||||||
|
|
||||||
// let state = s.state.try_read().unwrap();
|
|
||||||
// assert!(state.content.is_empty());
|
|
||||||
// assert!(state.names.is_empty());
|
|
||||||
// assert_eq!(state.parameters, {
|
|
||||||
// let mut r = HashMap::new();
|
|
||||||
// r.insert(Name::of::<bool>(), String::from("🥳"));
|
|
||||||
// r
|
|
||||||
// });
|
|
||||||
// assert_eq!(state.path, String::from("/%F0%9F%A5%B3"));
|
|
||||||
// assert!(!state.can_go_back);
|
|
||||||
// assert!(!state.can_go_forward);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn url_decoding_do_not() {
|
|
||||||
// let (mut s, c, _) = RouterContext::<_, u8>::new(
|
|
||||||
// test_segment(),
|
|
||||||
// Box::new(MemoryHistory::with_initial_path("/%F0%9F%8E%BA").unwrap()),
|
|
||||||
// Arc::new(|_| {}),
|
|
||||||
// None,
|
|
||||||
// ContentAtom("external target"),
|
|
||||||
// ContentAtom("named target"),
|
|
||||||
// ContentAtom("redirect limit"),
|
|
||||||
// );
|
|
||||||
// s.init();
|
|
||||||
// c.unbounded_send(RouterMessage::Update).unwrap();
|
|
||||||
// s.run_current();
|
|
||||||
|
|
||||||
// let state = s.state.try_read().unwrap();
|
|
||||||
// assert_eq!(state.content, vec![ContentAtom("🎺")]);
|
|
||||||
// assert!(state.names.is_empty());
|
|
||||||
// assert!(state.parameters.is_empty());
|
|
||||||
// assert_eq!(state.path, String::from("/%F0%9F%8E%BA"));
|
|
||||||
// assert!(!state.can_go_back);
|
|
||||||
// assert!(!state.can_go_forward);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn prefix() {
|
|
||||||
// struct TestHistory {}
|
|
||||||
|
|
||||||
// impl HistoryProvider for TestHistory {
|
|
||||||
// fn current_route(&self) -> String {
|
|
||||||
// String::from("/")
|
|
||||||
// }
|
|
||||||
|
|
||||||
// fn current_query(&self) -> Option<String> {
|
|
||||||
// None
|
|
||||||
// }
|
|
||||||
|
|
||||||
// fn current_prefix(&self) -> Option<String> {
|
|
||||||
// Some(String::from("/prefix"))
|
|
||||||
// }
|
|
||||||
|
|
||||||
// fn can_go_back(&self) -> bool {
|
|
||||||
// false
|
|
||||||
// }
|
|
||||||
|
|
||||||
// fn can_go_forward(&self) -> bool {
|
|
||||||
// false
|
|
||||||
// }
|
|
||||||
|
|
||||||
// fn go_back(&mut self) {
|
|
||||||
// todo!()
|
|
||||||
// }
|
|
||||||
|
|
||||||
// fn go_forward(&mut self) {
|
|
||||||
// todo!()
|
|
||||||
// }
|
|
||||||
|
|
||||||
// fn push(&mut self, _path: String) {
|
|
||||||
// todo!()
|
|
||||||
// }
|
|
||||||
|
|
||||||
// fn replace(&mut self, _path: String) {
|
|
||||||
// todo!()
|
|
||||||
// }
|
|
||||||
|
|
||||||
// fn updater(&mut self, callback: Arc<dyn Fn() + Send + Sync>) {
|
|
||||||
// callback();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// let (mut s, _, _) = RouterContext::<_, u8>::new(
|
|
||||||
// test_segment(),
|
|
||||||
// Box::new(TestHistory {}),
|
|
||||||
// Arc::new(|_| {}),
|
|
||||||
// None,
|
|
||||||
// ContentAtom("external target"),
|
|
||||||
// ContentAtom("named target"),
|
|
||||||
// ContentAtom("redirect limit"),
|
|
||||||
// );
|
|
||||||
// s.init();
|
|
||||||
|
|
||||||
// let state = s.state.try_read().unwrap();
|
|
||||||
// assert_eq!(state.content, vec![ContentAtom("index")]);
|
|
||||||
// assert_eq!(state.names, {
|
|
||||||
// let mut r = HashSet::new();
|
|
||||||
// r.insert(Name::of::<RootIndex>());
|
|
||||||
// r
|
|
||||||
// });
|
|
||||||
// assert!(state.parameters.is_empty());
|
|
||||||
// assert_eq!(state.path, String::from("/"));
|
|
||||||
// assert!(state.query.is_none());
|
|
||||||
// assert_eq!(state.prefix, Some(String::from("/prefix")));
|
|
||||||
// assert!(!state.can_go_back);
|
|
||||||
// assert!(!state.can_go_forward);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
@@ -18,7 +18,6 @@ where
|
|||||||
/// Create a [`MemoryHistory`] starting at `path`.
|
/// Create a [`MemoryHistory`] starting at `path`.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use dioxus_router::history::{HistoryProvider, MemoryHistory};
|
|
||||||
/// # use dioxus_router::prelude::*;
|
/// # use dioxus_router::prelude::*;
|
||||||
/// # use serde::{Deserialize, Serialize};
|
/// # use serde::{Deserialize, Serialize};
|
||||||
/// # use dioxus::prelude::*;
|
/// # use dioxus::prelude::*;
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
//! History Integration
|
//! History Integration
|
||||||
//!
|
//!
|
||||||
//! dioxus-router-core relies on so-called [`HistoryProvider`]s to store the current URL, and possibly a
|
//! dioxus-router-core relies on [`HistoryProvider`]s to store the current Route, and possibly a
|
||||||
//! history (i.e. a browsers back button) and future (i.e. a browsers forward button).
|
//! history (i.e. a browsers back button) and future (i.e. a browsers forward button).
|
||||||
//!
|
//!
|
||||||
//! To integrate dioxus-router-core with a any type of history, all you have to do is implement the
|
//! To integrate dioxus-router with a any type of history, all you have to do is implement the
|
||||||
//! [`HistoryProvider`] trait. dioxus-router-core also comes with some (for now one) default implementations.
|
//! [`HistoryProvider`] trait.
|
||||||
|
//!
|
||||||
|
//! dioxus-router contains two built in history providers:
|
||||||
|
//! 1) [`MemoryHistory`] for desktop/mobile/ssr platforms
|
||||||
|
//! 2) [`WebHistory`] for web platforms
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@@ -42,7 +46,6 @@ pub trait HistoryProvider<R: Routable> {
|
|||||||
/// **Must start** with `/`. **Must _not_ contain** the prefix.
|
/// **Must start** with `/`. **Must _not_ contain** the prefix.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use dioxus_router::history::{HistoryProvider, MemoryHistory};
|
|
||||||
/// # use dioxus_router::prelude::*;
|
/// # use dioxus_router::prelude::*;
|
||||||
/// # use serde::{Deserialize, Serialize};
|
/// # use serde::{Deserialize, Serialize};
|
||||||
/// # use dioxus::prelude::*;
|
/// # use dioxus::prelude::*;
|
||||||
@@ -82,7 +85,6 @@ pub trait HistoryProvider<R: Routable> {
|
|||||||
/// If a [`HistoryProvider`] cannot know this, it should return [`true`].
|
/// If a [`HistoryProvider`] cannot know this, it should return [`true`].
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use dioxus_router::history::{HistoryProvider, MemoryHistory};
|
|
||||||
/// # use dioxus_router::prelude::*;
|
/// # use dioxus_router::prelude::*;
|
||||||
/// # use serde::{Deserialize, Serialize};
|
/// # use serde::{Deserialize, Serialize};
|
||||||
/// # use dioxus::prelude::*;
|
/// # use dioxus::prelude::*;
|
||||||
@@ -110,7 +112,6 @@ pub trait HistoryProvider<R: Routable> {
|
|||||||
/// might be called, even if `can_go_back` returns [`false`].
|
/// might be called, even if `can_go_back` returns [`false`].
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use dioxus_router::history::{HistoryProvider, MemoryHistory};
|
|
||||||
/// # use dioxus_router::prelude::*;
|
/// # use dioxus_router::prelude::*;
|
||||||
/// # use serde::{Deserialize, Serialize};
|
/// # use serde::{Deserialize, Serialize};
|
||||||
/// # use dioxus::prelude::*;
|
/// # use dioxus::prelude::*;
|
||||||
@@ -144,7 +145,6 @@ pub trait HistoryProvider<R: Routable> {
|
|||||||
/// If a [`HistoryProvider`] cannot know this, it should return [`true`].
|
/// If a [`HistoryProvider`] cannot know this, it should return [`true`].
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use dioxus_router::history::{HistoryProvider, MemoryHistory};
|
|
||||||
/// # use dioxus_router::prelude::*;
|
/// # use dioxus_router::prelude::*;
|
||||||
/// # use serde::{Deserialize, Serialize};
|
/// # use serde::{Deserialize, Serialize};
|
||||||
/// # use dioxus::prelude::*;
|
/// # use dioxus::prelude::*;
|
||||||
@@ -179,7 +179,6 @@ pub trait HistoryProvider<R: Routable> {
|
|||||||
/// might be called, even if `can_go_forward` returns [`false`].
|
/// might be called, even if `can_go_forward` returns [`false`].
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use dioxus_router::history::{HistoryProvider, MemoryHistory};
|
|
||||||
/// # use dioxus_router::prelude::*;
|
/// # use dioxus_router::prelude::*;
|
||||||
/// # use serde::{Deserialize, Serialize};
|
/// # use serde::{Deserialize, Serialize};
|
||||||
/// # use dioxus::prelude::*;
|
/// # use dioxus::prelude::*;
|
||||||
@@ -214,7 +213,6 @@ pub trait HistoryProvider<R: Routable> {
|
|||||||
/// 3. Clear the navigation future.
|
/// 3. Clear the navigation future.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use dioxus_router::history::{HistoryProvider, MemoryHistory};
|
|
||||||
/// # use dioxus_router::prelude::*;
|
/// # use dioxus_router::prelude::*;
|
||||||
/// # use serde::{Deserialize, Serialize};
|
/// # use serde::{Deserialize, Serialize};
|
||||||
/// # use dioxus::prelude::*;
|
/// # use dioxus::prelude::*;
|
||||||
@@ -245,7 +243,6 @@ pub trait HistoryProvider<R: Routable> {
|
|||||||
/// untouched.
|
/// untouched.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use dioxus_router::history::{HistoryProvider, MemoryHistory};
|
|
||||||
/// # use dioxus_router::prelude::*;
|
/// # use dioxus_router::prelude::*;
|
||||||
/// # use serde::{Deserialize, Serialize};
|
/// # use serde::{Deserialize, Serialize};
|
||||||
/// # use dioxus::prelude::*;
|
/// # use dioxus::prelude::*;
|
||||||
|
|||||||
@@ -10,14 +10,13 @@ use crate::utils::use_router_internal::use_router_internal;
|
|||||||
/// - Otherwise the current route.
|
/// - Otherwise the current route.
|
||||||
///
|
///
|
||||||
/// # Panic
|
/// # Panic
|
||||||
/// - When the calling component is not nested within another component calling the [`use_router`]
|
/// - When the calling component is not nested within a [`GenericRouter`] component durring a debug build.
|
||||||
/// hook, but only in debug builds.
|
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use dioxus::prelude::*;
|
/// # use dioxus::prelude::*;
|
||||||
/// # use serde::{Deserialize, Serialize};
|
/// # use serde::{Deserialize, Serialize};
|
||||||
/// # use dioxus_router::{history::*, prelude::*};
|
/// # use dioxus_router::{prelude::*};
|
||||||
///
|
///
|
||||||
/// #[derive(Clone, Serialize, Deserialize, Routable)]
|
/// #[derive(Clone, Serialize, Deserialize, Routable)]
|
||||||
/// enum Route {
|
/// enum Route {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use crate::{
|
|||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use dioxus::prelude::*;
|
/// # use dioxus::prelude::*;
|
||||||
/// # use dioxus_router::{history::*, prelude::*};
|
/// # use dioxus_router::prelude::*;
|
||||||
/// # use serde::{Deserialize, Serialize};
|
/// # use serde::{Deserialize, Serialize};
|
||||||
/// #[derive(Clone, Serialize, Deserialize, Routable)]
|
/// #[derive(Clone, Serialize, Deserialize, Routable)]
|
||||||
/// enum Route {
|
/// enum Route {
|
||||||
|
|||||||
@@ -7,8 +7,9 @@ pub mod navigation;
|
|||||||
pub mod routable;
|
pub mod routable;
|
||||||
|
|
||||||
/// Components interacting with the router.
|
/// Components interacting with the router.
|
||||||
pub mod components {
|
mod components {
|
||||||
pub(crate) mod default_errors;
|
mod default_errors;
|
||||||
|
pub use default_errors::*;
|
||||||
|
|
||||||
mod history_buttons;
|
mod history_buttons;
|
||||||
pub use history_buttons::*;
|
pub use history_buttons::*;
|
||||||
@@ -31,10 +32,10 @@ mod contexts {
|
|||||||
|
|
||||||
mod router_cfg;
|
mod router_cfg;
|
||||||
|
|
||||||
pub mod history;
|
mod history;
|
||||||
|
|
||||||
/// Hooks for interacting with the router in components.
|
/// Hooks for interacting with the router in components.
|
||||||
pub mod hooks {
|
mod hooks {
|
||||||
mod use_router;
|
mod use_router;
|
||||||
pub use use_router::*;
|
pub use use_router::*;
|
||||||
|
|
||||||
@@ -48,6 +49,7 @@ pub mod prelude {
|
|||||||
pub use crate::contexts::*;
|
pub use crate::contexts::*;
|
||||||
pub use crate::history::*;
|
pub use crate::history::*;
|
||||||
pub use crate::hooks::*;
|
pub use crate::hooks::*;
|
||||||
|
pub use crate::navigation::*;
|
||||||
pub use crate::routable::*;
|
pub use crate::routable::*;
|
||||||
pub use crate::router_cfg::RouterConfiguration;
|
pub use crate::router_cfg::RouterConfiguration;
|
||||||
pub use dioxus_router_macro::Routable;
|
pub use dioxus_router_macro::Routable;
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
//! Types pertaining to navigation.
|
//! Types pertaining to navigation.
|
||||||
|
|
||||||
use std::{fmt::Display, str::FromStr};
|
use std::{
|
||||||
|
convert::TryFrom,
|
||||||
|
fmt::{Debug, Display},
|
||||||
|
str::FromStr,
|
||||||
|
};
|
||||||
|
|
||||||
use url::{ParseError, Url};
|
use url::{ParseError, Url};
|
||||||
|
|
||||||
use crate::routable::Routable;
|
use crate::routable::Routable;
|
||||||
|
|
||||||
/// A target for the router to navigate to.
|
/// A target for the router to navigate to.
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
pub enum NavigationTarget<R: Routable> {
|
pub enum NavigationTarget<R: Routable> {
|
||||||
/// An internal path that the router can navigate to by itself.
|
/// An internal path that the router can navigate to by itself.
|
||||||
///
|
///
|
||||||
@@ -26,7 +30,7 @@ pub enum NavigationTarget<R: Routable> {
|
|||||||
/// Index {},
|
/// Index {},
|
||||||
/// }
|
/// }
|
||||||
/// let explicit = NavigationTarget::Internal(Route::Index {});
|
/// let explicit = NavigationTarget::Internal(Route::Index {});
|
||||||
/// let implicit: NavigationTarget::<Route> = "/".into();
|
/// let implicit: NavigationTarget::<Route> = "/".parse().unwrap();
|
||||||
/// assert_eq!(explicit, implicit);
|
/// assert_eq!(explicit, implicit);
|
||||||
/// ```
|
/// ```
|
||||||
Internal(R),
|
Internal(R),
|
||||||
@@ -47,25 +51,17 @@ pub enum NavigationTarget<R: Routable> {
|
|||||||
/// Index {},
|
/// Index {},
|
||||||
/// }
|
/// }
|
||||||
/// let explicit = NavigationTarget::<Route>::External(String::from("https://dioxuslabs.com/"));
|
/// let explicit = NavigationTarget::<Route>::External(String::from("https://dioxuslabs.com/"));
|
||||||
/// let implicit: NavigationTarget::<Route> = "https://dioxuslabs.com/".into();
|
/// let implicit: NavigationTarget::<Route> = "https://dioxuslabs.com/".parse().unwrap();
|
||||||
/// assert_eq!(explicit, implicit);
|
/// assert_eq!(explicit, implicit);
|
||||||
/// ```
|
/// ```
|
||||||
External(String),
|
External(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: Routable> From<&str> for NavigationTarget<R>
|
impl<R: Routable> TryFrom<&str> for NavigationTarget<R> {
|
||||||
where
|
type Error = NavigationTargetParseError<R>;
|
||||||
<R as FromStr>::Err: Display,
|
|
||||||
{
|
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||||
fn from(value: &str) -> Self {
|
value.parse()
|
||||||
Self::from_str(value).unwrap_or_else(|err| match err {
|
|
||||||
NavigationTargetParseError::InvalidUrl(e) => {
|
|
||||||
panic!("Failed to parse `{}` as a URL: {}", value, e)
|
|
||||||
}
|
|
||||||
NavigationTargetParseError::InvalidInternalURL(e) => {
|
|
||||||
panic!("Failed to parse `{}` as a `Routable`: {}", value, e)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,6 +88,17 @@ pub enum NavigationTargetParseError<R: Routable> {
|
|||||||
InvalidInternalURL(<R as FromStr>::Err),
|
InvalidInternalURL(<R as FromStr>::Err),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<R: Routable> Debug for NavigationTargetParseError<R> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
NavigationTargetParseError::InvalidUrl(e) => write!(f, "Invalid URL: {}", e),
|
||||||
|
NavigationTargetParseError::InvalidInternalURL(_) => {
|
||||||
|
write!(f, "Invalid internal URL")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<R: Routable> FromStr for NavigationTarget<R> {
|
impl<R: Routable> FromStr for NavigationTarget<R> {
|
||||||
type Err = NavigationTargetParseError<R>;
|
type Err = NavigationTargetParseError<R>;
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
use crate::contexts::router::RoutingCallback;
|
use crate::contexts::router::RoutingCallback;
|
||||||
use crate::history::HistoryProvider;
|
use crate::history::HistoryProvider;
|
||||||
use crate::prelude::*;
|
|
||||||
use crate::routable::Routable;
|
use crate::routable::Routable;
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use serde::{de::DeserializeOwned, Serialize};
|
use serde::{de::DeserializeOwned, Serialize};
|
||||||
|
|
||||||
use crate::prelude::default_errors::{
|
use crate::prelude::*;
|
||||||
FailureExternalNavigation, FailureNamedNavigation, FailureRedirectionLimit,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Global configuration options for the router.
|
/// Global configuration options for the router.
|
||||||
///
|
///
|
||||||
@@ -33,22 +30,8 @@ use crate::prelude::default_errors::{
|
|||||||
pub struct RouterConfiguration<R: Routable> {
|
pub struct RouterConfiguration<R: Routable> {
|
||||||
/// A component to render when an external navigation fails.
|
/// A component to render when an external navigation fails.
|
||||||
///
|
///
|
||||||
/// Defaults to a router-internal component called `FailureExternalNavigation`. It is not part
|
/// Defaults to a router-internal component called [`FailureExternalNavigation`]
|
||||||
/// of the public API. Do not confuse it with
|
|
||||||
/// [`dioxus_router::prelude::FailureExternalNavigation`].
|
|
||||||
pub failure_external_navigation: fn(Scope) -> Element,
|
pub failure_external_navigation: fn(Scope) -> Element,
|
||||||
/// A component to render when a named navigation fails.
|
|
||||||
///
|
|
||||||
/// Defaults to a router-internal component called `FailureNamedNavigation`. It is not part of
|
|
||||||
/// the public API. Do not confuse it with
|
|
||||||
/// [`dioxus_router::prelude::FailureNamedNavigation`].
|
|
||||||
pub failure_named_navigation: fn(Scope) -> Element,
|
|
||||||
/// A component to render when the redirect limit is reached.
|
|
||||||
///
|
|
||||||
/// Defaults to a router-internal component called `FailureRedirectionLimit`. It is not part of
|
|
||||||
/// the public API. Do not confuse it with
|
|
||||||
/// [`dioxus_router::prelude::FailureRedirectionLimit`].
|
|
||||||
pub failure_redirection_limit: fn(Scope) -> Element,
|
|
||||||
/// The [`HistoryProvider`] the router should use.
|
/// The [`HistoryProvider`] the router should use.
|
||||||
///
|
///
|
||||||
/// Defaults to a default [`MemoryHistory`].
|
/// Defaults to a default [`MemoryHistory`].
|
||||||
@@ -58,7 +41,7 @@ pub struct RouterConfiguration<R: Routable> {
|
|||||||
/// The callback is invoked after the routing is updated, but before components and hooks are
|
/// The callback is invoked after the routing is updated, but before components and hooks are
|
||||||
/// updated.
|
/// updated.
|
||||||
///
|
///
|
||||||
/// If the callback returns a [`dioxus_router::navigation::NavigationTarget`] the router will replace the current location
|
/// If the callback returns a [`NavigationTarget`] the router will replace the current location
|
||||||
/// with it. If no navigation failure was triggered, the router will then updated dependent
|
/// with it. If no navigation failure was triggered, the router will then updated dependent
|
||||||
/// components and hooks.
|
/// components and hooks.
|
||||||
///
|
///
|
||||||
@@ -76,8 +59,6 @@ where
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
failure_external_navigation: FailureExternalNavigation::<R>,
|
failure_external_navigation: FailureExternalNavigation::<R>,
|
||||||
failure_named_navigation: FailureNamedNavigation::<R>,
|
|
||||||
failure_redirection_limit: FailureRedirectionLimit::<R>,
|
|
||||||
history: Box::<MemoryHistory<R>>::default(),
|
history: Box::<MemoryHistory<R>>::default(),
|
||||||
on_update: None,
|
on_update: None,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ fn href_external() {
|
|||||||
fn Root(cx: Scope) -> Element {
|
fn Root(cx: Scope) -> Element {
|
||||||
render! {
|
render! {
|
||||||
Link {
|
Link {
|
||||||
target: "https://dioxuslabs.com/",
|
target: NavigationTarget::External("https://dioxuslabs.com/".into()),
|
||||||
"Link"
|
"Link"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -171,7 +171,7 @@ fn with_active_class_active() {
|
|||||||
fn Root(cx: Scope) -> Element {
|
fn Root(cx: Scope) -> Element {
|
||||||
render! {
|
render! {
|
||||||
Link {
|
Link {
|
||||||
target: "/",
|
target: Route::Root {},
|
||||||
active_class: "active_class",
|
active_class: "active_class",
|
||||||
class: "test_class",
|
class: "test_class",
|
||||||
"Link"
|
"Link"
|
||||||
@@ -322,7 +322,7 @@ fn with_new_tab_external() {
|
|||||||
fn Root(cx: Scope) -> Element {
|
fn Root(cx: Scope) -> Element {
|
||||||
render! {
|
render! {
|
||||||
Link {
|
Link {
|
||||||
target: "https://dioxuslabs.com/",
|
target: NavigationTarget::External("https://dioxuslabs.com/".into()),
|
||||||
new_tab: true,
|
new_tab: true,
|
||||||
"Link"
|
"Link"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#![allow(non_snake_case, unused)]
|
#![allow(non_snake_case, unused)]
|
||||||
|
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use dioxus_router::{history::MemoryHistory, prelude::*};
|
use dioxus_router::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
fn prepare(path: impl Into<String>) -> VirtualDom {
|
fn prepare(path: impl Into<String>) -> VirtualDom {
|
||||||
|
|||||||
Reference in new issue
Block a user