|
@@ -1,4 +1,5 @@
|
|
|
use std::{
|
|
|
+ any::Any,
|
|
|
collections::HashSet,
|
|
|
sync::{Arc, RwLock, RwLockWriteGuard},
|
|
|
};
|
|
@@ -6,7 +7,7 @@ use std::{
|
|
|
use dioxus::prelude::*;
|
|
|
|
|
|
use crate::{
|
|
|
- history::HistoryProvider, navigation::NavigationTarget, routable::Routable,
|
|
|
+ navigation::NavigationTarget, prelude::AnyHistoryProvider, routable::Routable,
|
|
|
router_cfg::RouterConfig,
|
|
|
};
|
|
|
|
|
@@ -15,52 +16,31 @@ use crate::{
|
|
|
pub struct ExternalNavigationFailure(String);
|
|
|
|
|
|
/// A function the router will call after every routing update.
|
|
|
-pub(crate) type RoutingCallback<R> =
|
|
|
- Arc<dyn Fn(GenericRouterContext<R>) -> Option<NavigationTarget<R>>>;
|
|
|
+pub(crate) type RoutingCallback<R> = Arc<dyn Fn(LinkContext<R>) -> Option<NavigationTarget<R>>>;
|
|
|
|
|
|
-struct MutableRouterState<R>
|
|
|
-where
|
|
|
- R: Routable,
|
|
|
-{
|
|
|
+struct MutableRouterState {
|
|
|
/// The current prefix.
|
|
|
prefix: Option<String>,
|
|
|
|
|
|
- history: Box<dyn HistoryProvider<R>>,
|
|
|
+ history: Box<dyn AnyHistoryProvider>,
|
|
|
|
|
|
unresolved_error: Option<ExternalNavigationFailure>,
|
|
|
}
|
|
|
|
|
|
/// A collection of router data that manages all routing functionality.
|
|
|
-pub struct GenericRouterContext<R>
|
|
|
-where
|
|
|
- R: Routable,
|
|
|
-{
|
|
|
- state: Arc<RwLock<MutableRouterState<R>>>,
|
|
|
+#[derive(Clone)]
|
|
|
+pub struct RouterContext {
|
|
|
+ state: Arc<RwLock<MutableRouterState>>,
|
|
|
|
|
|
subscribers: Arc<RwLock<HashSet<ScopeId>>>,
|
|
|
subscriber_update: Arc<dyn Fn(ScopeId)>,
|
|
|
- routing_callback: Option<RoutingCallback<R>>,
|
|
|
+ routing_callback: Option<Arc<dyn Fn(RouterContext) -> Option<NavigationTarget<Box<dyn Any>>>>>,
|
|
|
|
|
|
failure_external_navigation: fn(Scope) -> Element,
|
|
|
}
|
|
|
|
|
|
-impl<R: Routable> Clone for GenericRouterContext<R> {
|
|
|
- fn clone(&self) -> Self {
|
|
|
- Self {
|
|
|
- state: self.state.clone(),
|
|
|
- subscribers: self.subscribers.clone(),
|
|
|
- subscriber_update: self.subscriber_update.clone(),
|
|
|
- routing_callback: self.routing_callback.clone(),
|
|
|
- failure_external_navigation: self.failure_external_navigation,
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-impl<R> GenericRouterContext<R>
|
|
|
-where
|
|
|
- R: Routable,
|
|
|
-{
|
|
|
- pub(crate) fn new(
|
|
|
+impl RouterContext {
|
|
|
+ pub(crate) fn new<R: Routable + 'static>(
|
|
|
mut cfg: RouterConfig<R>,
|
|
|
mark_dirty: Arc<dyn Fn(ScopeId) + Sync + Send>,
|
|
|
) -> Self
|
|
@@ -82,7 +62,21 @@ where
|
|
|
subscribers: subscribers.clone(),
|
|
|
subscriber_update,
|
|
|
|
|
|
- routing_callback: cfg.on_update,
|
|
|
+ routing_callback: cfg.on_update.map(|update| {
|
|
|
+ Arc::new(move |ctx| {
|
|
|
+ let ctx = LinkContext {
|
|
|
+ inner: ctx,
|
|
|
+ _marker: std::marker::PhantomData,
|
|
|
+ };
|
|
|
+ update(ctx).map(|t| match t {
|
|
|
+ NavigationTarget::Internal(r) => {
|
|
|
+ NavigationTarget::Internal(Box::new(r) as Box<dyn Any>)
|
|
|
+ }
|
|
|
+ NavigationTarget::External(s) => NavigationTarget::External(s),
|
|
|
+ })
|
|
|
+ })
|
|
|
+ as Arc<dyn Fn(RouterContext) -> Option<NavigationTarget<Box<dyn Any>>>>
|
|
|
+ }),
|
|
|
|
|
|
failure_external_navigation: cfg.failure_external_navigation,
|
|
|
};
|
|
@@ -100,6 +94,11 @@ where
|
|
|
myself
|
|
|
}
|
|
|
|
|
|
+ pub(crate) fn route_from_str(&self, route: &str) -> Result<Box<dyn Any>, String> {
|
|
|
+ let state = self.state.read().unwrap();
|
|
|
+ state.history.parse_route(route)
|
|
|
+ }
|
|
|
+
|
|
|
/// Check whether there is a previous page to navigate back to.
|
|
|
#[must_use]
|
|
|
pub fn can_go_back(&self) -> bool {
|
|
@@ -134,10 +133,25 @@ where
|
|
|
self.change_route();
|
|
|
}
|
|
|
|
|
|
+ pub(crate) fn push_any(
|
|
|
+ &self,
|
|
|
+ target: NavigationTarget<Box<dyn Any>>,
|
|
|
+ ) -> Option<ExternalNavigationFailure> {
|
|
|
+ match target {
|
|
|
+ NavigationTarget::Internal(p) => {
|
|
|
+ let mut state = self.state_mut();
|
|
|
+ state.history.push(p)
|
|
|
+ }
|
|
|
+ NavigationTarget::External(e) => return self.external(e),
|
|
|
+ }
|
|
|
+
|
|
|
+ self.change_route()
|
|
|
+ }
|
|
|
+
|
|
|
/// Push a new location.
|
|
|
///
|
|
|
/// The previous location will be available to go back to.
|
|
|
- pub fn push(
|
|
|
+ pub fn push<R: Routable>(
|
|
|
&self,
|
|
|
target: impl Into<NavigationTarget<R>>,
|
|
|
) -> Option<ExternalNavigationFailure> {
|
|
@@ -145,7 +159,7 @@ where
|
|
|
match target {
|
|
|
NavigationTarget::Internal(p) => {
|
|
|
let mut state = self.state_mut();
|
|
|
- state.history.push(p)
|
|
|
+ state.history.push(Box::new(p))
|
|
|
}
|
|
|
NavigationTarget::External(e) => return self.external(e),
|
|
|
}
|
|
@@ -156,7 +170,7 @@ where
|
|
|
/// Replace the current location.
|
|
|
///
|
|
|
/// The previous location will **not** be available to go back to.
|
|
|
- pub fn replace(
|
|
|
+ pub fn replace<R: Routable>(
|
|
|
&self,
|
|
|
target: impl Into<NavigationTarget<R>>,
|
|
|
) -> Option<ExternalNavigationFailure> {
|
|
@@ -165,7 +179,7 @@ where
|
|
|
{
|
|
|
let mut state = self.state_mut();
|
|
|
match target {
|
|
|
- NavigationTarget::Internal(p) => state.history.replace(p),
|
|
|
+ NavigationTarget::Internal(p) => state.history.replace(Box::new(p)),
|
|
|
NavigationTarget::External(e) => return self.external(e),
|
|
|
}
|
|
|
}
|
|
@@ -174,11 +188,24 @@ where
|
|
|
}
|
|
|
|
|
|
/// The route that is currently active.
|
|
|
- pub fn current(&self) -> R
|
|
|
- where
|
|
|
- R: Clone,
|
|
|
- {
|
|
|
- self.state.read().unwrap().history.current_route()
|
|
|
+ pub fn current<R: Routable>(&self) -> R {
|
|
|
+ self.state
|
|
|
+ .read()
|
|
|
+ .unwrap()
|
|
|
+ .history
|
|
|
+ .current_route()
|
|
|
+ .downcast::<R>()
|
|
|
+ .unwrap()
|
|
|
+ }
|
|
|
+
|
|
|
+ /// The route that is currently active.
|
|
|
+ pub fn current_route_string(&self) -> String {
|
|
|
+ self.state
|
|
|
+ .read()
|
|
|
+ .unwrap()
|
|
|
+ .history
|
|
|
+ .current_route()
|
|
|
+ .to_string()
|
|
|
}
|
|
|
|
|
|
/// The prefix that is currently active.
|
|
@@ -201,7 +228,7 @@ where
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- fn state_mut(&self) -> RwLockWriteGuard<MutableRouterState<R>> {
|
|
|
+ fn state_mut(&self) -> RwLockWriteGuard<MutableRouterState> {
|
|
|
self.state.write().unwrap()
|
|
|
}
|
|
|
|
|
@@ -254,3 +281,87 @@ where
|
|
|
None
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+pub struct LinkContext<R> {
|
|
|
+ inner: RouterContext,
|
|
|
+ _marker: std::marker::PhantomData<R>,
|
|
|
+}
|
|
|
+
|
|
|
+impl<R> LinkContext<R>
|
|
|
+where
|
|
|
+ R: Routable,
|
|
|
+{
|
|
|
+ /// Check whether there is a previous page to navigate back to.
|
|
|
+ #[must_use]
|
|
|
+ pub fn can_go_back(&self) -> bool {
|
|
|
+ self.inner.can_go_back()
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Check whether there is a future page to navigate forward to.
|
|
|
+ #[must_use]
|
|
|
+ pub fn can_go_forward(&self) -> bool {
|
|
|
+ self.inner.can_go_forward()
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Go back to the previous location.
|
|
|
+ ///
|
|
|
+ /// Will fail silently if there is no previous location to go to.
|
|
|
+ pub fn go_back(&self) {
|
|
|
+ self.inner.go_back();
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Go back to the next location.
|
|
|
+ ///
|
|
|
+ /// Will fail silently if there is no next location to go to.
|
|
|
+ pub fn go_forward(&self) {
|
|
|
+ self.inner.go_forward();
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Push a new location.
|
|
|
+ ///
|
|
|
+ /// The previous location will be available to go back to.
|
|
|
+ pub fn push(
|
|
|
+ &self,
|
|
|
+ target: impl Into<NavigationTarget<R>>,
|
|
|
+ ) -> Option<ExternalNavigationFailure> {
|
|
|
+ self.inner.push(target)
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Replace the current location.
|
|
|
+ ///
|
|
|
+ /// The previous location will **not** be available to go back to.
|
|
|
+ pub fn replace(
|
|
|
+ &self,
|
|
|
+ target: impl Into<NavigationTarget<R>>,
|
|
|
+ ) -> Option<ExternalNavigationFailure> {
|
|
|
+ self.inner.replace(target)
|
|
|
+ }
|
|
|
+
|
|
|
+ /// The route that is currently active.
|
|
|
+ pub fn current(&self) -> R
|
|
|
+ where
|
|
|
+ R: Clone,
|
|
|
+ {
|
|
|
+ self.inner.current()
|
|
|
+ }
|
|
|
+
|
|
|
+ /// The prefix that is currently active.
|
|
|
+ pub fn prefix(&self) -> Option<String> {
|
|
|
+ self.inner.prefix()
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Manually subscribe to the current route
|
|
|
+ pub fn subscribe(&self, id: ScopeId) {
|
|
|
+ self.inner.subscribe(id)
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Manually unsubscribe from the current route
|
|
|
+ pub fn unsubscribe(&self, id: ScopeId) {
|
|
|
+ self.inner.unsubscribe(id)
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Clear any unresolved errors
|
|
|
+ pub fn clear_error(&self) {
|
|
|
+ self.inner.clear_error()
|
|
|
+ }
|
|
|
+}
|