Move the history provider into the context (#3048)
* move history providers into a separate crate * start moving route providers into the renderers * clean up intoroutable * remove into routable * fix router tests * Provide history providers in each renderer * implement nested routers * move the lens out of the history crate * re-export dioxus history trait in the prelude * also re-export the history function * fix history doctests * some light cleanups --------- Co-authored-by: Jonathan Kelley <jkelleyrtp@gmail.com>
This commit is contained in:
1 parent
e5a1a62644
commit
281087469a
48 files changed
+875
-1419
No files matched your search
@@ -0,0 +1,261 @@
|
||||
use dioxus_core::prelude::{provide_context, provide_root_context};
|
||||
use std::{rc::Rc, sync::Arc};
|
||||
|
||||
mod memory;
|
||||
pub use memory::*;
|
||||
|
||||
/// Get the history provider for the current platform if the platform doesn't implement a history functionality.
|
||||
pub fn history() -> Rc<dyn History> {
|
||||
match dioxus_core::prelude::try_consume_context::<Rc<dyn History>>() {
|
||||
Some(history) => history,
|
||||
None => {
|
||||
tracing::error!("Unable to find a history provider in the renderer. Make sure your renderer supports the Router. Falling back to the in-memory history provider.");
|
||||
provide_root_context(Rc::new(MemoryHistory::default()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Provide a history context to the current component.
|
||||
pub fn provide_history_context(history: Rc<dyn History>) {
|
||||
provide_context(history);
|
||||
}
|
||||
|
||||
pub trait History {
|
||||
/// Get the path of the current URL.
|
||||
///
|
||||
/// **Must start** with `/`. **Must _not_ contain** the prefix.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use dioxus::prelude::*;
|
||||
/// # #[component]
|
||||
/// # fn Index() -> Element { VNode::empty() }
|
||||
/// # #[component]
|
||||
/// # fn OtherPage() -> Element { VNode::empty() }
|
||||
/// #[derive(Clone, Routable, Debug, PartialEq)]
|
||||
/// enum Route {
|
||||
/// #[route("/")]
|
||||
/// Index {},
|
||||
/// #[route("/some-other-page")]
|
||||
/// OtherPage {},
|
||||
/// }
|
||||
/// let mut history = dioxus::history::MemoryHistory::default();
|
||||
/// assert_eq!(history.current_route(), "/");
|
||||
///
|
||||
/// history.push(Route::OtherPage {}.to_string());
|
||||
/// assert_eq!(history.current_route(), "/some-other-page");
|
||||
/// ```
|
||||
#[must_use]
|
||||
fn current_route(&self) -> String;
|
||||
|
||||
/// 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::prelude::*;
|
||||
/// # #[component]
|
||||
/// # fn Index() -> Element { VNode::empty() }
|
||||
/// # fn Other() -> Element { VNode::empty() }
|
||||
/// #[derive(Clone, Routable, Debug, PartialEq)]
|
||||
/// enum Route {
|
||||
/// #[route("/")]
|
||||
/// Index {},
|
||||
/// #[route("/other")]
|
||||
/// Other {},
|
||||
/// }
|
||||
/// let mut history = dioxus::history::MemoryHistory::default();
|
||||
/// assert_eq!(history.can_go_back(), false);
|
||||
///
|
||||
/// history.push(Route::Other {}.to_string());
|
||||
/// 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::prelude::*;
|
||||
/// # #[component]
|
||||
/// # fn Index() -> Element { VNode::empty() }
|
||||
/// # #[component]
|
||||
/// # fn OtherPage() -> Element { VNode::empty() }
|
||||
/// #[derive(Clone, Routable, Debug, PartialEq)]
|
||||
/// enum Route {
|
||||
/// #[route("/")]
|
||||
/// Index {},
|
||||
/// #[route("/some-other-page")]
|
||||
/// OtherPage {},
|
||||
/// }
|
||||
/// let mut history = dioxus::history::MemoryHistory::default();
|
||||
/// assert_eq!(history.current_route(), "/");
|
||||
///
|
||||
/// history.go_back();
|
||||
/// assert_eq!(history.current_route(), "/");
|
||||
///
|
||||
/// history.push(Route::OtherPage {}.to_string());
|
||||
/// assert_eq!(history.current_route(), "/some-other-page");
|
||||
///
|
||||
/// history.go_back();
|
||||
/// assert_eq!(history.current_route(), "/");
|
||||
/// ```
|
||||
fn go_back(&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::prelude::*;
|
||||
/// # #[component]
|
||||
/// # fn Index() -> Element { VNode::empty() }
|
||||
/// # #[component]
|
||||
/// # fn OtherPage() -> Element { VNode::empty() }
|
||||
/// #[derive(Clone, Routable, Debug, PartialEq)]
|
||||
/// enum Route {
|
||||
/// #[route("/")]
|
||||
/// Index {},
|
||||
/// #[route("/some-other-page")]
|
||||
/// OtherPage {},
|
||||
/// }
|
||||
/// let mut history = dioxus::history::MemoryHistory::default();
|
||||
/// assert_eq!(history.can_go_forward(), false);
|
||||
///
|
||||
/// history.push(Route::OtherPage {}.to_string());
|
||||
/// 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::prelude::*;
|
||||
/// # #[component]
|
||||
/// # fn Index() -> Element { VNode::empty() }
|
||||
/// # #[component]
|
||||
/// # fn OtherPage() -> Element { VNode::empty() }
|
||||
/// #[derive(Clone, Routable, Debug, PartialEq)]
|
||||
/// enum Route {
|
||||
/// #[route("/")]
|
||||
/// Index {},
|
||||
/// #[route("/some-other-page")]
|
||||
/// OtherPage {},
|
||||
/// }
|
||||
/// let mut history = dioxus::history::MemoryHistory::default();
|
||||
/// history.push(Route::OtherPage {}.to_string());
|
||||
/// assert_eq!(history.current_route(), Route::OtherPage {}.to_string());
|
||||
///
|
||||
/// history.go_back();
|
||||
/// assert_eq!(history.current_route(), Route::Index {}.to_string());
|
||||
///
|
||||
/// history.go_forward();
|
||||
/// assert_eq!(history.current_route(), Route::OtherPage {}.to_string());
|
||||
/// ```
|
||||
fn go_forward(&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::prelude::*;
|
||||
/// # #[component]
|
||||
/// # fn Index() -> Element { VNode::empty() }
|
||||
/// # #[component]
|
||||
/// # fn OtherPage() -> Element { VNode::empty() }
|
||||
/// #[derive(Clone, Routable, Debug, PartialEq)]
|
||||
/// enum Route {
|
||||
/// #[route("/")]
|
||||
/// Index {},
|
||||
/// #[route("/some-other-page")]
|
||||
/// OtherPage {},
|
||||
/// }
|
||||
/// let mut history = dioxus::history::MemoryHistory::default();
|
||||
/// assert_eq!(history.current_route(), Route::Index {}.to_string());
|
||||
///
|
||||
/// history.push(Route::OtherPage {}.to_string());
|
||||
/// assert_eq!(history.current_route(), Route::OtherPage {}.to_string());
|
||||
/// assert!(history.can_go_back());
|
||||
/// ```
|
||||
fn push(&self, route: String);
|
||||
|
||||
/// 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::prelude::*;
|
||||
/// # #[component]
|
||||
/// # fn Index() -> Element { VNode::empty() }
|
||||
/// # #[component]
|
||||
/// # fn OtherPage() -> Element { VNode::empty() }
|
||||
/// #[derive(Clone, Routable, Debug, PartialEq)]
|
||||
/// enum Route {
|
||||
/// #[route("/")]
|
||||
/// Index {},
|
||||
/// #[route("/some-other-page")]
|
||||
/// OtherPage {},
|
||||
/// }
|
||||
/// let mut history = dioxus::history::MemoryHistory::default();
|
||||
/// assert_eq!(history.current_route(), Route::Index {}.to_string());
|
||||
///
|
||||
/// history.replace(Route::OtherPage {}.to_string());
|
||||
/// assert_eq!(history.current_route(), Route::OtherPage {}.to_string());
|
||||
/// assert!(!history.can_go_back());
|
||||
/// ```
|
||||
fn replace(&self, path: String);
|
||||
|
||||
/// 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(&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(&self, callback: Arc<dyn Fn() + Send + Sync>) {}
|
||||
|
||||
/// Whether the router should include the legacy prevent default attribute instead of the new
|
||||
/// prevent default method. This should only be used by liveview.
|
||||
fn include_prevent_default(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
use std::cell::RefCell;
|
||||
|
||||
use crate::History;
|
||||
|
||||
struct MemoryHistoryState {
|
||||
current: String,
|
||||
history: Vec<String>,
|
||||
future: Vec<String>,
|
||||
}
|
||||
|
||||
/// A [`History`] provider that stores all navigation information in memory.
|
||||
pub struct MemoryHistory {
|
||||
state: RefCell<MemoryHistoryState>,
|
||||
}
|
||||
|
||||
impl Default for MemoryHistory {
|
||||
fn default() -> Self {
|
||||
Self::with_initial_path("/")
|
||||
}
|
||||
}
|
||||
|
||||
impl MemoryHistory {
|
||||
/// Create a [`MemoryHistory`] starting at `path`.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use dioxus_router::prelude::*;
|
||||
/// # use dioxus::prelude::*;
|
||||
/// # #[component]
|
||||
/// # fn Index() -> Element { VNode::empty() }
|
||||
/// # #[component]
|
||||
/// # fn OtherPage() -> Element { VNode::empty() }
|
||||
/// #[derive(Clone, Routable, Debug, PartialEq)]
|
||||
/// enum Route {
|
||||
/// #[route("/")]
|
||||
/// Index {},
|
||||
/// #[route("/some-other-page")]
|
||||
/// OtherPage {},
|
||||
/// }
|
||||
///
|
||||
/// let mut history = dioxus_history::MemoryHistory::with_initial_path(Route::Index {});
|
||||
/// assert_eq!(history.current_route(), Route::Index {}.to_string());
|
||||
/// assert_eq!(history.can_go_back(), false);
|
||||
/// ```
|
||||
pub fn with_initial_path(path: impl ToString) -> Self {
|
||||
Self {
|
||||
state: MemoryHistoryState{
|
||||
current: path.to_string().parse().unwrap_or_else(|err| {
|
||||
panic!("index route does not exist:\n{err}\n use MemoryHistory::with_initial_path to set a custom path")
|
||||
}),
|
||||
history: Vec::new(),
|
||||
future: Vec::new(),}.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl History for MemoryHistory {
|
||||
fn current_route(&self) -> String {
|
||||
self.state.borrow().current.clone()
|
||||
}
|
||||
|
||||
fn can_go_back(&self) -> bool {
|
||||
!self.state.borrow().history.is_empty()
|
||||
}
|
||||
|
||||
fn go_back(&self) {
|
||||
let mut write = self.state.borrow_mut();
|
||||
if let Some(last) = write.history.pop() {
|
||||
let old = std::mem::replace(&mut write.current, last);
|
||||
write.future.push(old);
|
||||
}
|
||||
}
|
||||
|
||||
fn can_go_forward(&self) -> bool {
|
||||
!self.state.borrow().future.is_empty()
|
||||
}
|
||||
|
||||
fn go_forward(&self) {
|
||||
let mut write = self.state.borrow_mut();
|
||||
if let Some(next) = write.future.pop() {
|
||||
let old = std::mem::replace(&mut write.current, next);
|
||||
write.history.push(old);
|
||||
}
|
||||
}
|
||||
|
||||
fn push(&self, new: String) {
|
||||
let mut write = self.state.borrow_mut();
|
||||
// don't push the same route twice
|
||||
if write.current == new {
|
||||
return;
|
||||
}
|
||||
let old = std::mem::replace(&mut write.current, new);
|
||||
write.history.push(old);
|
||||
write.future.clear();
|
||||
}
|
||||
|
||||
fn replace(&self, path: String) {
|
||||
let mut write = self.state.borrow_mut();
|
||||
write.current = path;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user