400 lines
12 KiB
Rust
400 lines
12 KiB
Rust
//! 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, 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 {
|
|
#[must_use]
|
|
fn parse_route(&self, route: &str) -> Result<Box<dyn Any>, String>;
|
|
|
|
#[must_use]
|
|
fn accepts_type_id(&self, type_id: &std::any::TypeId) -> bool;
|
|
|
|
#[must_use]
|
|
fn current_route(&self) -> Box<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: Box<dyn Any>);
|
|
|
|
fn replace(&mut self, path: Box<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<Box<dyn Any>, String> {
|
|
R::from_str(route)
|
|
.map_err(|err| err.to_string())
|
|
.map(|route| Box::new(route) as Box<dyn Any>)
|
|
}
|
|
|
|
fn accepts_type_id(&self, type_id: &std::any::TypeId) -> bool {
|
|
type_id == &std::any::TypeId::of::<R>()
|
|
}
|
|
|
|
fn current_route(&self) -> Box<dyn Any> {
|
|
let route = self.inner.current_route();
|
|
Box::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: Box<dyn Any>) {
|
|
self.inner.push(*route.downcast().unwrap())
|
|
}
|
|
|
|
fn replace(&mut self, path: Box<dyn Any>) {
|
|
self.inner.replace(*path.downcast().unwrap())
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|