add navigator and use_navigate hook

This commit is contained in:
Adrian Wannenmacher committed 2022-12-13 00:09:32 +01:00
1 parent 85ac35c198
commit 0cc0cba482
6 files changed
+156 -3

No files matched your search

+3
View File
@@ -8,6 +8,9 @@ pub use name::*;
pub mod navigation;
mod navigator;
pub use navigator::*;
mod outlet;
pub use outlet::*;
+97
View File
@@ -0,0 +1,97 @@
use futures_channel::mpsc::UnboundedSender;
use crate::{navigation::NavigationTarget, RouterMessage};
/// A [`Navigator`] allowing for programmatic navigation.
///
/// The [`Navigator`] is not guaranteed to be able to trigger a navigation. When and if a navigation
/// is actually handled depends on the UI library.
pub struct Navigator<I> {
sender: UnboundedSender<RouterMessage<I>>,
}
impl<I> Navigator<I> {
/// Go back to the previous location.
///
/// Will fail silently if there is no previous location to go to.
pub fn go_back(&self) {
let _ = self.sender.unbounded_send(RouterMessage::GoBack);
}
/// Go back to the next location.
///
/// Will fail silently if there is no next location to go to.
pub fn go_forward(&self) {
let _ = self.sender.unbounded_send(RouterMessage::GoForward);
}
/// Push a new location.
///
/// The previous location will be available to go back to.
pub fn push(&self, target: impl Into<NavigationTarget>) {
let _ = self
.sender
.unbounded_send(RouterMessage::Push(target.into()));
}
/// Replace the current location.
///
/// The previous location will **not** be available to go back to.
pub fn replace(&self, target: impl Into<NavigationTarget>) {
let _ = self
.sender
.unbounded_send(RouterMessage::Replace(target.into()));
}
}
impl<I> From<UnboundedSender<RouterMessage<I>>> for Navigator<I> {
fn from(sender: UnboundedSender<RouterMessage<I>>) -> Self {
Self { sender }
}
}
#[cfg(test)]
mod tests {
use futures_channel::mpsc::{unbounded, UnboundedReceiver};
use super::*;
fn prepare() -> (Navigator<()>, UnboundedReceiver<RouterMessage<()>>) {
let (sender, receiver) = unbounded();
(Navigator::from(sender), receiver)
}
#[test]
fn go_back() {
let (n, mut s) = prepare();
n.go_back();
assert_eq!(s.try_next().unwrap(), Some(RouterMessage::GoBack));
}
#[test]
fn go_forward() {
let (n, mut s) = prepare();
n.go_forward();
assert_eq!(s.try_next().unwrap(), Some(RouterMessage::GoForward));
}
#[test]
fn push() {
let (n, mut s) = prepare();
let target = NavigationTarget::from("https://dioxuslabs.com/");
n.push(target.clone());
assert_eq!(s.try_next().unwrap(), Some(RouterMessage::Push(target)));
}
#[test]
fn replace() {
let (n, mut s) = prepare();
let target = NavigationTarget::from("https://dioxuslabs.com/");
n.replace(target.clone());
assert_eq!(s.try_next().unwrap(), Some(RouterMessage::Replace(target)));
}
}
+27
View File
@@ -1,5 +1,6 @@
use std::{
collections::{BTreeMap, HashMap, HashSet},
fmt::Debug,
sync::{Arc, Weak},
};
@@ -36,6 +37,32 @@ pub enum RouterMessage<I> {
GoForward,
}
impl<I: Debug> Debug for RouterMessage<I> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Subscribe(arg0) => f.debug_tuple("Subscribe").field(arg0).finish(),
Self::Push(arg0) => f.debug_tuple("Push").field(arg0).finish(),
Self::Replace(arg0) => f.debug_tuple("Replace").field(arg0).finish(),
Self::Update => write!(f, "Update"),
Self::GoBack => write!(f, "GoBack"),
Self::GoForward => write!(f, "GoForward"),
}
}
}
impl<I: PartialEq> PartialEq for RouterMessage<I> {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Subscribe(l0), Self::Subscribe(r0)) => l0 == r0,
(Self::Push(l0), Self::Push(r0)) => l0 == r0,
(Self::Replace(l0), Self::Replace(r0)) => l0 == r0,
_ => core::mem::discriminant(self) == core::mem::discriminant(other),
}
}
}
impl<I: Eq> Eq for RouterMessage<I> {}
enum NavigationFailure {
External(String),
Named(Name),
+20
View File
@@ -0,0 +1,20 @@
use dioxus::prelude::{ScopeId, ScopeState};
use dioxus_router_core::Navigator;
use log::error;
use crate::utils::use_router_internal::use_router_internal;
#[must_use]
pub fn use_navigate(cx: &ScopeState) -> Option<Navigator<ScopeId>> {
match use_router_internal(cx) {
Some(r) => Some(r.sender.clone().into()),
None => {
let msg = "`use_navigate` must have access to a parent router";
error!("{msg}, will be inactive");
#[cfg(debug_assertions)]
panic!("{}", msg);
#[cfg(not(debug_assertions))]
None
}
}
}
+6 -3
View File
@@ -3,7 +3,7 @@ use dioxus::{core::Component, prelude::*};
use dioxus_router_core::{
history::{HistoryProvider, MemoryHistory},
routes::{ContentAtom, Segment},
RouterService, RouterState, RoutingCallback,
Navigator, RouterService, RouterState, RoutingCallback,
};
use crate::{
@@ -20,7 +20,10 @@ pub fn use_router<'a>(
cx: &'a ScopeState,
cfg: &dyn Fn() -> RouterConfiguration,
content: &dyn Fn() -> Segment<Component>,
) -> (RwLockReadGuard<'a, RouterState<Component>>, ()) {
) -> (
RwLockReadGuard<'a, RouterState<Component>>,
Navigator<ScopeId>,
) {
let (service, state, sender) = cx.use_hook(|| {
let cfg = cfg();
let content = content();
@@ -62,7 +65,7 @@ pub fn use_router<'a>(
break state;
}
},
(),
sender.clone().into(),
)
}
+3
View File
@@ -13,6 +13,9 @@ mod contexts {
}
pub mod hooks {
mod use_navigate;
pub use use_navigate::*;
mod use_router;
pub use use_router::*;