Przeglądaj źródła

add navigator and use_navigate hook

Adrian Wannenmacher 2 lat temu
rodzic
commit
0cc0cba482

+ 3 - 0
packages/router-core/src/lib.rs

@@ -8,6 +8,9 @@ pub use name::*;
 
 pub mod navigation;
 
+mod navigator;
+pub use navigator::*;
+
 mod outlet;
 pub use outlet::*;
 

+ 97 - 0
packages/router-core/src/navigator.rs

@@ -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 - 0
packages/router-core/src/service.rs

@@ -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 - 0
packages/router/src/hooks/use_navigate.rs

@@ -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
packages/router/src/hooks/use_router.rs

@@ -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 - 0
packages/router/src/lib.rs

@@ -13,6 +13,9 @@ mod contexts {
 }
 
 pub mod hooks {
+    mod use_navigate;
+    pub use use_navigate::*;
+
     mod use_router;
     pub use use_router::*;