freya_router/contexts/
router.rsuse std::{
collections::HashSet,
error::Error,
fmt::Display,
sync::{
Arc,
Mutex,
},
};
use dioxus_lib::prelude::*;
use tracing::error;
use crate::{
components::child_router::consume_child_route_mapping,
memory::MemoryHistory,
navigation::NavigationTarget,
prelude::SiteMapSegment,
routable::Routable,
router_cfg::RouterConfig,
};
#[derive(Debug, Clone)]
pub struct ParseRouteError {
message: String,
}
impl Error for ParseRouteError {}
impl Display for ParseRouteError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.message.fmt(f)
}
}
#[derive(Debug, Clone)]
pub struct ExternalNavigationFailure(pub String);
struct RouterContextInner {
subscribers: Arc<Mutex<HashSet<ReactiveContext>>>,
internal_route: fn(&str) -> bool,
site_map: &'static [SiteMapSegment],
history: MemoryHistory,
}
impl RouterContextInner {
fn update_subscribers(&self) {
for &id in self.subscribers.lock().unwrap().iter() {
id.mark_dirty();
}
}
fn subscribe_to_current_context(&self) {
if let Some(rc) = ReactiveContext::current() {
rc.subscribe(self.subscribers.clone());
}
}
fn external(&mut self, external: String) -> Option<ExternalNavigationFailure> {
let failure = ExternalNavigationFailure(external);
self.update_subscribers();
Some(failure)
}
}
#[derive(Clone, Copy)]
pub struct RouterContext {
inner: CopyValue<RouterContextInner>,
}
impl RouterContext {
pub(crate) fn new<R: Routable + 'static>(cfg: RouterConfig<R>) -> Self {
let subscribers = Arc::new(Mutex::new(HashSet::new()));
let history = if let Some(initial_path) = cfg.initial_path {
MemoryHistory::with_initial_path(initial_path)
} else {
MemoryHistory::default()
};
Self {
inner: CopyValue::new(RouterContextInner {
subscribers: subscribers.clone(),
internal_route: |route| R::from_str(route).is_ok(),
site_map: R::SITE_MAP,
history,
}),
}
}
#[must_use]
pub fn can_go_back(&self) -> bool {
self.inner.peek().history.can_go_back()
}
#[must_use]
pub fn can_go_forward(&self) -> bool {
self.inner.peek().history.can_go_forward()
}
pub fn go_back(&self) {
self.inner.peek().history.go_back();
self.change_route();
}
pub fn go_forward(&self) {
self.inner.peek().history.go_forward();
self.change_route();
}
pub fn push(&self, target: impl Into<NavigationTarget>) -> Option<ExternalNavigationFailure> {
let target = target.into();
{
let mut write = self.inner.write_unchecked();
match target {
NavigationTarget::Internal(p) => write.history.push(p),
NavigationTarget::External(e) => return write.external(e),
}
}
self.change_route();
None
}
pub fn replace(
&self,
target: impl Into<NavigationTarget>,
) -> Option<ExternalNavigationFailure> {
let target = target.into();
{
let mut write = self.inner.write_unchecked();
match target {
NavigationTarget::Internal(p) => write.history.replace(p),
NavigationTarget::External(e) => return write.external(e),
}
}
self.change_route();
None
}
pub fn current<R: Routable>(&self) -> R {
let absolute_route = self.full_route_string();
let mapping = consume_child_route_mapping::<R>();
let route = match mapping.as_ref() {
Some(mapping) => mapping
.parse_route_from_root_route(&absolute_route)
.ok_or_else(|| "Failed to parse route".to_string()),
None => {
R::from_str(&absolute_route).map_err(|err| format!("Failed to parse route {err}"))
}
};
match route {
Ok(route) => route,
Err(err) => {
error!("Parse route error: {err:?}");
throw_error(ParseRouteError { message: err });
"/".parse().unwrap_or_else(|err| panic!("{err}"))
}
}
}
pub fn full_route_string(&self) -> String {
let inner = self.inner.read();
inner.subscribe_to_current_context();
self.inner.peek_unchecked().history.current_route()
}
pub fn site_map(&self) -> &'static [SiteMapSegment] {
self.inner.read().site_map
}
fn change_route(&self) {
self.inner.read().update_subscribers();
}
pub(crate) fn internal_route(&self, route: &str) -> bool {
(self.inner.read().internal_route)(route)
}
}
pub struct GenericRouterContext<R> {
inner: RouterContext,
_marker: std::marker::PhantomData<R>,
}
impl<R> GenericRouterContext<R>
where
R: Routable,
{
#[must_use]
pub fn can_go_back(&self) -> bool {
self.inner.can_go_back()
}
#[must_use]
pub fn can_go_forward(&self) -> bool {
self.inner.can_go_forward()
}
pub fn go_back(&self) {
self.inner.go_back();
}
pub fn go_forward(&self) {
self.inner.go_forward();
}
pub fn push(
&self,
target: impl Into<NavigationTarget<R>>,
) -> Option<ExternalNavigationFailure> {
self.inner.push(target.into())
}
pub fn replace(
&self,
target: impl Into<NavigationTarget<R>>,
) -> Option<ExternalNavigationFailure> {
self.inner.replace(target.into())
}
pub fn current(&self) -> R
where
R: Clone,
{
self.inner.current()
}
}