123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400 |
- //! 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<R: Routable> {
- /// 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::<Route>::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<String> {
- 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::<Route>::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::<Route>::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::<Route>::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::<Route>::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::<Route>::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::<Route>::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<dyn Fn() + Send + Sync>) {}
- }
- pub(crate) trait AnyHistoryProvider {
- fn parse_route(&self, route: &str) -> Result<Rc<dyn Any>, String>;
- #[must_use]
- fn accepts_type_id(&self, type_id: &std::any::TypeId) -> bool;
- #[must_use]
- fn current_route(&self) -> Rc<dyn Any>;
- #[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: Rc<dyn Any>);
- fn replace(&mut self, path: Rc<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<Rc<dyn Any>, String> {
- R::from_str(route)
- .map_err(|err| err.to_string())
- .map(|route| Rc::new(route) as Rc<dyn Any>)
- }
- fn accepts_type_id(&self, type_id: &std::any::TypeId) -> bool {
- type_id == &std::any::TypeId::of::<R>()
- }
- fn current_route(&self) -> Rc<dyn Any> {
- let route = self.inner.current_route();
- Rc::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: Rc<dyn Any>) {
- self.inner
- .push(route.downcast::<R>().unwrap().as_ref().clone())
- }
- fn replace(&mut self, route: Rc<dyn Any>) {
- self.inner
- .replace(route.downcast::<R>().unwrap().as_ref().clone())
- }
- 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)
- }
- }
|