use std::{cell::Ref, collections::HashMap, sync::Arc};
use crate::{
Handler, State, UseContext,
prelude::{Route, RouteContext, RouteState, history::RouterHistory},
};
mod private {
pub trait Sealed {}
impl Sealed for crate::Hooks<'_, '_> {}
}
pub trait UseRouter<'a>: private::Sealed {
fn use_navigate(&mut self) -> Navigate;
fn try_use_route_state<T: Send + Sync + 'static>(&self) -> Option<Arc<T>>;
fn use_route_state<T: Send + Sync + 'static>(&self) -> Arc<T>;
fn use_route(&self) -> Ref<'a, Route>;
fn use_params(&self) -> Ref<'a, HashMap<String, String>>;
}
impl<'a> UseRouter<'a> for crate::Hooks<'a, '_> {
fn use_navigate(&mut self) -> Navigate {
let history = self.use_context::<State<RouterHistory>>();
Navigate::new(*history)
}
fn try_use_route_state<T: Send + Sync + 'static>(&self) -> Option<Arc<T>> {
let route_context = self.try_use_context::<RouteContext>()?;
route_context
.state
.as_ref()
.cloned()
.and_then(|p| p.downcast::<T>())
}
fn use_route_state<T: Send + Sync + 'static>(&self) -> Arc<T> {
self.try_use_route_state::<T>()
.expect("route state not found or type mismatch")
}
fn use_route(&self) -> Ref<'a, Route> {
self.use_context::<Route>()
}
fn use_params(&self) -> Ref<'a, HashMap<String, String>> {
let ctx = self.use_context::<RouteContext>();
Ref::map(ctx, |c| &c.params)
}
}
#[derive(Clone, Copy)]
pub struct Navigate {
history: State<RouterHistory>,
}
impl Navigate {
pub(crate) fn new(history: State<RouterHistory>) -> Self {
Navigate { history }
}
pub fn push(&mut self, path: &str) {
let mut history = self.history.write();
let mut ctx = history.current_context();
ctx.path = path.to_string();
ctx.state = None;
history.push(ctx);
}
pub fn push_with_state<T>(&mut self, path: &str, state: T)
where
T: Send + Sync + 'static,
{
let mut history = self.history.write();
let mut ctx = history.current_context();
ctx.path = path.to_string();
ctx.state = Some(RouteState::new(state));
history.push(ctx);
}
pub fn replace(&mut self, path: &str) {
let mut history = self.history.write();
let mut ctx = history.current_context();
ctx.path = path.to_string();
ctx.state = None;
history.replace(ctx);
}
pub fn replace_with_state<T>(&mut self, path: &str, state: T)
where
T: Send + Sync + 'static,
{
let mut history = self.history.write();
let mut ctx = history.current_context();
ctx.path = path.to_string();
ctx.state = Some(RouteState::new(state));
history.replace(ctx);
}
pub fn go(&mut self, delta: i32) {
let mut history = self.history.write();
history.go(delta);
}
pub fn back(&mut self) {
let mut history = self.history.write();
history.back();
}
pub fn forward(&mut self) {
let mut history = self.history.write();
history.forward();
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ReactiveHandle;
use generational_box::{Owner, SyncStorage};
fn history_with_state() -> (Owner<SyncStorage>, State<RouterHistory>) {
let owner = Owner::default();
let history = State::new_in(
&owner,
RouterHistory::new(
RouteContext {
path: "/detail".to_string(),
params: HashMap::new(),
state: Some(RouteState::new("from detail".to_string())),
},
10,
),
);
(owner, history)
}
#[test]
fn push_without_state_clears_previous_route_state() {
let (_owner, history) = history_with_state();
let mut navigate = Navigate::new(history);
navigate.push("/plain");
let current = history.read().current_context();
assert_eq!(current.path, "/plain");
assert!(current.state.is_none());
}
#[test]
fn replace_without_state_clears_previous_route_state() {
let (_owner, history) = history_with_state();
let mut navigate = Navigate::new(history);
navigate.replace("/plain");
let current = history.read().current_context();
assert_eq!(current.path, "/plain");
assert!(current.state.is_none());
}
}