//! History Integration //! //! 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). //! //! To integrate dioxus-router with a any type of history, all you have to do is implement the //! [`HistoryProvider`] trait. //! //! dioxus-router contains two built in history providers: //! 1) [`MemoryHistory`] for desktop/mobile/ssr platforms //! 2) [`WebHistory`] for web platforms use std::{any::Any, rc::Rc, sync::Arc}; mod memory; pub use memory::*; #[cfg(feature = "web")] mod web; #[cfg(feature = "web")] pub use web::*; #[cfg(feature = "web")] pub(crate) mod web_history; // #[cfg(feature = "web")] // mod web_hash; // #[cfg(feature = "web")] // pub use web_hash::*; use crate::routable::Routable; #[cfg(feature = "web")] pub(crate) mod web_scroll; /// An integration with some kind of navigation history. /// /// Depending on your use case, your implementation may deviate from the described procedure. This /// is fine, as long as both `current_route` and `current_query` match the described format. /// /// However, you should document all deviations. Also, make sure the navigation is user-friendly. /// The described behaviors are designed to mimic a web browser, which most users should already /// know. Deviations might confuse them. pub trait HistoryProvider { /// Get the path of the current URL. /// /// **Must start** with `/`. **Must _not_ contain** the prefix. /// /// ```rust /// # use dioxus_router::prelude::*; /// # use dioxus::prelude::*; /// # #[inline_props] /// # fn Index(cx: Scope) -> Element { todo!() } /// # #[inline_props] /// # fn OtherPage(cx: Scope) -> Element { todo!() } /// #[derive(Clone, Routable, Debug, PartialEq)] /// enum Route { /// #[route("/")] /// Index {}, /// #[route("/some-other-page")] /// OtherPage {}, /// } /// let mut history = MemoryHistory::::default(); /// assert_eq!(history.current_route().to_string(), "/"); /// /// history.push(Route::OtherPage {}); /// assert_eq!(history.current_route().to_string(), "/some-other-page"); /// ``` #[must_use] fn current_route(&self) -> R; /// Get the current path prefix of the URL. /// /// Not all [`HistoryProvider`]s need a prefix feature. It is meant for environments where a /// dioxus-router-core-routed application is not running on `/`. The [`HistoryProvider`] is responsible /// for removing the prefix from the dioxus-router-core-internal path, and also for adding it back in /// during navigation. This functions value is only used for creating `href`s (e.g. for SSR or /// display (but not navigation) in a web app). fn current_prefix(&self) -> Option { None } /// Check whether there is a previous page to navigate back to. /// /// If a [`HistoryProvider`] cannot know this, it should return [`true`]. /// /// ```rust /// # use dioxus_router::prelude::*; /// # use dioxus::prelude::*; /// # #[inline_props] /// # fn Index(cx: Scope) -> Element { todo!() } /// #[derive(Clone, Routable, Debug, PartialEq)] /// enum Route { /// #[route("/")] /// Index {}, /// } /// let mut history = MemoryHistory::::default(); /// assert_eq!(history.can_go_back(), false); /// /// history.push(Route::Index {}); /// assert_eq!(history.can_go_back(), true); /// ``` #[must_use] fn can_go_back(&self) -> bool { true } /// Go back to a previous page. /// /// If a [`HistoryProvider`] cannot go to a previous page, it should do nothing. This method /// might be called, even if `can_go_back` returns [`false`]. /// /// ```rust /// # use dioxus_router::prelude::*; /// # use dioxus::prelude::*; /// # #[inline_props] /// # fn Index(cx: Scope) -> Element { todo!() } /// # #[inline_props] /// # fn OtherPage(cx: Scope) -> Element { todo!() } /// #[derive(Clone, Routable, Debug, PartialEq)] /// enum Route { /// #[route("/")] /// Index {}, /// #[route("/some-other-page")] /// OtherPage {}, /// } /// let mut history = MemoryHistory::::default(); /// assert_eq!(history.current_route().to_string(), "/"); /// /// history.go_back(); /// assert_eq!(history.current_route().to_string(), "/"); /// /// history.push(Route::OtherPage {}); /// assert_eq!(history.current_route().to_string(), "/some-other-page"); /// /// history.go_back(); /// assert_eq!(history.current_route().to_string(), "/"); /// ``` fn go_back(&mut self); /// Check whether there is a future page to navigate forward to. /// /// If a [`HistoryProvider`] cannot know this, it should return [`true`]. /// /// ```rust /// # use dioxus_router::prelude::*; /// # use dioxus::prelude::*; /// # #[inline_props] /// # fn Index(cx: Scope) -> Element { todo!() } /// # #[inline_props] /// # fn OtherPage(cx: Scope) -> Element { todo!() } /// #[derive(Clone, Routable, Debug, PartialEq)] /// enum Route { /// #[route("/")] /// Index {}, /// #[route("/some-other-page")] /// OtherPage {}, /// } /// let mut history = MemoryHistory::::default(); /// assert_eq!(history.can_go_forward(), false); /// /// history.push(Route::OtherPage {}); /// assert_eq!(history.can_go_forward(), false); /// /// history.go_back(); /// assert_eq!(history.can_go_forward(), true); /// ``` #[must_use] fn can_go_forward(&self) -> bool { true } /// Go forward to a future page. /// /// If a [`HistoryProvider`] cannot go to a previous page, it should do nothing. This method /// might be called, even if `can_go_forward` returns [`false`]. /// /// ```rust /// # use dioxus_router::prelude::*; /// # use dioxus::prelude::*; /// # #[inline_props] /// # fn Index(cx: Scope) -> Element { todo!() } /// # #[inline_props] /// # fn OtherPage(cx: Scope) -> Element { todo!() } /// #[derive(Clone, Routable, Debug, PartialEq)] /// enum Route { /// #[route("/")] /// Index {}, /// #[route("/some-other-page")] /// OtherPage {}, /// } /// let mut history = MemoryHistory::::default(); /// history.push(Route::OtherPage {}); /// assert_eq!(history.current_route(), Route::OtherPage {}); /// /// history.go_back(); /// assert_eq!(history.current_route(), Route::Index {}); /// /// history.go_forward(); /// assert_eq!(history.current_route(), Route::OtherPage {}); /// ``` fn go_forward(&mut self); /// Go to another page. /// /// This should do three things: /// 1. Merge the current URL with the `path` parameter (which may also include a query part). /// 2. Remove the previous URL to the navigation history. /// 3. Clear the navigation future. /// /// ```rust /// # use dioxus_router::prelude::*; /// # use dioxus::prelude::*; /// # #[inline_props] /// # fn Index(cx: Scope) -> Element { todo!() } /// # #[inline_props] /// # fn OtherPage(cx: Scope) -> Element { todo!() } /// #[derive(Clone, Routable, Debug, PartialEq)] /// enum Route { /// #[route("/")] /// Index {}, /// #[route("/some-other-page")] /// OtherPage {}, /// } /// let mut history = MemoryHistory::::default(); /// assert_eq!(history.current_route(), Route::Index {}); /// /// history.push(Route::OtherPage {}); /// assert_eq!(history.current_route(), Route::OtherPage {}); /// assert!(history.can_go_back()); /// ``` fn push(&mut self, route: R); /// Replace the current page with another one. /// /// This should merge the current URL with the `path` parameter (which may also include a query /// part). In contrast to the `push` function, the navigation history and future should stay /// untouched. /// /// ```rust /// # use dioxus_router::prelude::*; /// # use dioxus::prelude::*; /// # #[inline_props] /// # fn Index(cx: Scope) -> Element { todo!() } /// # #[inline_props] /// # fn OtherPage(cx: Scope) -> Element { todo!() } /// #[derive(Clone, Routable, Debug, PartialEq)] /// enum Route { /// #[route("/")] /// Index {}, /// #[route("/some-other-page")] /// OtherPage {}, /// } /// let mut history = MemoryHistory::::default(); /// assert_eq!(history.current_route(), Route::Index {}); /// /// history.replace(Route::OtherPage {}); /// assert_eq!(history.current_route(), Route::OtherPage {}); /// assert!(!history.can_go_back()); /// ``` fn replace(&mut self, path: R); /// Navigate to an external URL. /// /// This should navigate to an external URL, which isn't controlled by the router. If a /// [`HistoryProvider`] cannot do that, it should return [`false`], otherwise [`true`]. /// /// Returning [`false`] will cause the router to handle the external navigation failure. #[allow(unused_variables)] fn external(&mut self, url: String) -> bool { false } /// Provide the [`HistoryProvider`] with an update callback. /// /// Some [`HistoryProvider`]s may receive URL updates from outside the router. When such /// updates are received, they should call `callback`, which will cause the router to update. #[allow(unused_variables)] fn updater(&mut self, callback: Arc) {} } pub(crate) trait AnyHistoryProvider { fn parse_route(&self, route: &str) -> Result, String>; #[must_use] fn accepts_type_id(&self, type_id: &std::any::TypeId) -> bool; #[must_use] fn current_route(&self) -> Rc; #[must_use] fn current_prefix(&self) -> Option { 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: Rc); fn replace(&mut self, path: Rc); #[allow(unused_variables)] fn external(&mut self, url: String) -> bool { false } #[allow(unused_variables)] fn updater(&mut self, callback: Arc) {} } pub(crate) struct AnyHistoryProviderImplWrapper { inner: H, _marker: std::marker::PhantomData, } impl AnyHistoryProviderImplWrapper { pub fn new(inner: H) -> Self { Self { inner, _marker: std::marker::PhantomData, } } } impl Default for AnyHistoryProviderImplWrapper { fn default() -> Self { Self::new(H::default()) } } impl AnyHistoryProvider for AnyHistoryProviderImplWrapper where R: Routable, ::Err: std::fmt::Display, H: HistoryProvider, { fn parse_route(&self, route: &str) -> Result, String> { R::from_str(route) .map_err(|err| err.to_string()) .map(|route| Rc::new(route) as Rc) } fn accepts_type_id(&self, type_id: &std::any::TypeId) -> bool { type_id == &std::any::TypeId::of::() } fn current_route(&self) -> Rc { let route = self.inner.current_route(); Rc::new(route) } fn current_prefix(&self) -> Option { 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: Rc) { self.inner .push(route.downcast::().unwrap().as_ref().clone()) } fn replace(&mut self, route: Rc) { self.inner .replace(route.downcast::().unwrap().as_ref().clone()) } fn external(&mut self, url: String) -> bool { self.inner.external(url) } fn updater(&mut self, callback: Arc) { self.inner.updater(callback) } }