use std::any::Any;
use std::borrow::Cow;
use std::cell::RefCell;
use std::cmp::Ordering;
use std::collections::VecDeque;
use std::fmt;
use std::rc::Rc;
#[cfg(feature = "query")]
use serde::Serialize;
#[cfg(feature = "query")]
use crate::error::HistoryResult;
use crate::history::History;
use crate::listener::HistoryListener;
use crate::location::Location;
use crate::utils::{
assert_absolute_path, assert_no_fragment, assert_no_query, get_id, WeakCallback,
};
#[derive(Debug)]
struct LocationStack {
prev: Vec<Location>,
next: VecDeque<Location>,
current: Location,
}
impl LocationStack {
fn current(&self) -> Location {
self.current.clone()
}
fn len(&self) -> usize {
self.prev.len() + self.next.len() + 1
}
fn go(&mut self, delta: isize) {
match delta.cmp(&0) {
Ordering::Greater => {
for _i in 0..delta {
if let Some(mut m) = self.next.pop_front() {
std::mem::swap(&mut m, &mut self.current);
self.prev.push(m);
}
}
}
Ordering::Less => {
for _i in 0..-delta {
if let Some(mut m) = self.prev.pop() {
std::mem::swap(&mut m, &mut self.current);
self.next.push_front(m);
}
}
}
Ordering::Equal => {}
}
}
fn push(&mut self, mut location: Location) {
std::mem::swap(&mut location, &mut self.current);
self.prev.push(location);
self.next.clear();
}
fn replace(&mut self, location: Location) {
self.current = location;
}
}
impl Default for LocationStack {
fn default() -> Self {
Self {
prev: Vec::new(),
next: VecDeque::new(),
current: Location {
path: "/".to_string().into(),
query_str: "".to_string().into(),
hash: "".to_string().into(),
state: None,
id: Some(get_id()),
},
}
}
}
#[derive(Clone, Default)]
pub struct MemoryHistory {
inner: Rc<RefCell<LocationStack>>,
callbacks: Rc<RefCell<Vec<WeakCallback>>>,
}
impl PartialEq for MemoryHistory {
fn eq(&self, rhs: &Self) -> bool {
Rc::ptr_eq(&self.inner, &rhs.inner)
}
}
impl fmt::Debug for MemoryHistory {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MemoryHistory").finish()
}
}
impl History for MemoryHistory {
fn len(&self) -> usize {
self.inner.borrow().len()
}
fn go(&self, delta: isize) {
self.inner.borrow_mut().go(delta)
}
fn push<'a>(&self, route: impl Into<Cow<'a, str>>) {
let route = route.into();
assert_absolute_path(&route);
assert_no_query(&route);
assert_no_fragment(&route);
let location = Location {
path: route.to_string().into(),
query_str: "".to_string().into(),
hash: "".to_string().into(),
state: None,
id: Some(get_id()),
};
self.inner.borrow_mut().push(location);
self.notify_callbacks();
}
fn replace<'a>(&self, route: impl Into<Cow<'a, str>>) {
let route = route.into();
assert_absolute_path(&route);
assert_no_query(&route);
assert_no_fragment(&route);
let location = Location {
path: route.to_string().into(),
query_str: "".to_string().into(),
hash: "".to_string().into(),
state: None,
id: Some(get_id()),
};
self.inner.borrow_mut().replace(location);
self.notify_callbacks();
}
fn push_with_state<'a, T>(&self, route: impl Into<Cow<'a, str>>, state: T)
where
T: 'static,
{
let route = route.into();
assert_absolute_path(&route);
assert_no_query(&route);
assert_no_fragment(&route);
let location = Location {
path: route.to_string().into(),
query_str: "".to_string().into(),
hash: "".to_string().into(),
state: Some(Rc::new(state) as Rc<dyn Any>),
id: Some(get_id()),
};
self.inner.borrow_mut().push(location);
self.notify_callbacks();
}
fn replace_with_state<'a, T>(&self, route: impl Into<Cow<'a, str>>, state: T)
where
T: 'static,
{
let route = route.into();
assert_absolute_path(&route);
assert_no_query(&route);
assert_no_fragment(&route);
let location = Location {
path: route.to_string().into(),
query_str: "".to_string().into(),
hash: "".to_string().into(),
state: Some(Rc::new(state) as Rc<dyn Any>),
id: Some(get_id()),
};
self.inner.borrow_mut().replace(location);
self.notify_callbacks();
}
#[cfg(feature = "query")]
fn push_with_query<'a, Q>(&self, route: impl Into<Cow<'a, str>>, query: Q) -> HistoryResult<()>
where
Q: Serialize,
{
let query = serde_urlencoded::to_string(query)?;
let route = route.into();
assert_absolute_path(&route);
assert_no_query(&route);
assert_no_fragment(&route);
let location = Location {
path: route.to_string().into(),
query_str: format!("?{query}").into(),
hash: "".to_string().into(),
state: None,
id: Some(get_id()),
};
self.inner.borrow_mut().push(location);
self.notify_callbacks();
Ok(())
}
#[cfg(feature = "query")]
fn replace_with_query<'a, Q>(
&self,
route: impl Into<Cow<'a, str>>,
query: Q,
) -> HistoryResult<()>
where
Q: Serialize,
{
let query = serde_urlencoded::to_string(query)?;
let route = route.into();
assert_absolute_path(&route);
assert_no_query(&route);
assert_no_fragment(&route);
let location = Location {
path: route.to_string().into(),
query_str: format!("?{query}").into(),
hash: "".to_string().into(),
state: None,
id: Some(get_id()),
};
self.inner.borrow_mut().replace(location);
self.notify_callbacks();
Ok(())
}
#[cfg(feature = "query")]
fn push_with_query_and_state<'a, Q, T>(
&self,
route: impl Into<Cow<'a, str>>,
query: Q,
state: T,
) -> HistoryResult<()>
where
Q: Serialize,
T: 'static,
{
let query = serde_urlencoded::to_string(query)?;
let route = route.into();
assert_absolute_path(&route);
assert_no_query(&route);
assert_no_fragment(&route);
let location = Location {
path: route.to_string().into(),
query_str: format!("?{query}").into(),
hash: "".to_string().into(),
state: Some(Rc::new(state) as Rc<dyn Any>),
id: Some(get_id()),
};
self.inner.borrow_mut().push(location);
self.notify_callbacks();
Ok(())
}
#[cfg(feature = "query")]
fn replace_with_query_and_state<'a, Q, T>(
&self,
route: impl Into<Cow<'a, str>>,
query: Q,
state: T,
) -> HistoryResult<()>
where
Q: Serialize,
T: 'static,
{
let query = serde_urlencoded::to_string(query)?;
let route = route.into();
assert_absolute_path(&route);
assert_no_query(&route);
assert_no_fragment(&route);
let location = Location {
path: route.to_string().into(),
query_str: format!("?{query}").into(),
hash: "".to_string().into(),
state: Some(Rc::new(state) as Rc<dyn Any>),
id: Some(get_id()),
};
self.inner.borrow_mut().replace(location);
self.notify_callbacks();
Ok(())
}
fn listen<CB>(&self, callback: CB) -> HistoryListener
where
CB: Fn() + 'static,
{
let cb = Rc::new(callback) as Rc<dyn Fn()>;
self.callbacks.borrow_mut().push(Rc::downgrade(&cb));
HistoryListener { _listener: cb }
}
fn location(&self) -> Location {
self.inner.borrow().current()
}
}
impl MemoryHistory {
pub fn new() -> Self {
Self::default()
}
pub fn with_entries<'a>(entries: impl IntoIterator<Item = impl Into<Cow<'a, str>>>) -> Self {
let self_ = Self::new();
for (index, entry) in entries.into_iter().enumerate() {
if index == 0 {
self_.replace(entry);
} else {
self_.push(entry);
}
}
self_
}
fn notify_callbacks(&self) {
crate::utils::notify_callbacks(self.callbacks.clone());
}
}