Skip to content

Commit

Permalink
Memory-based History (#178)
Browse files Browse the repository at this point in the history
* Better route and query handling.

* Add memory history.

* Fix clippy.

* Add MemoryHistory to AnyHistory.

* Remove extra traits.
  • Loading branch information
futursolo authored Dec 5, 2021
1 parent 816b8f1 commit e0603ac
Show file tree
Hide file tree
Showing 9 changed files with 619 additions and 80 deletions.
25 changes: 23 additions & 2 deletions crates/history/src/any.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use crate::hash::HashHistory;
use crate::history::History;
use crate::listener::HistoryListener;
use crate::location::Location;
use crate::memory::MemoryHistory;

/// A [`History`] that provides a universial API to the underlying history type.
#[derive(Clone, PartialEq, Debug)]
Expand All @@ -18,34 +19,40 @@ pub enum AnyHistory {
Browser(BrowserHistory),
/// A Hash History
Hash(HashHistory),
/// A Memory History
Memory(MemoryHistory),
}

impl History for AnyHistory {
fn len(&self) -> usize {
match self {
Self::Browser(m) => m.len(),
Self::Hash(m) => m.len(),
Self::Memory(m) => m.len(),
}
}

fn go(&self, delta: isize) {
match self {
Self::Browser(m) => m.go(delta),
Self::Hash(m) => m.go(delta),
Self::Memory(m) => m.go(delta),
}
}

fn push<'a>(&self, route: impl Into<Cow<'a, str>>) {
match self {
Self::Browser(m) => m.push(route),
Self::Hash(m) => m.push(route),
Self::Memory(m) => m.push(route),
}
}

fn replace<'a>(&self, route: impl Into<Cow<'a, str>>) {
match self {
Self::Browser(m) => m.replace(route),
Self::Hash(m) => m.replace(route),
Self::Memory(m) => m.replace(route),
}
}

Expand All @@ -56,6 +63,7 @@ impl History for AnyHistory {
match self {
Self::Browser(m) => m.push_with_state(route, state),
Self::Hash(m) => m.push_with_state(route, state),
Self::Memory(m) => m.push_with_state(route, state),
}
}

Expand All @@ -66,6 +74,7 @@ impl History for AnyHistory {
match self {
Self::Browser(m) => m.replace_with_state(route, state),
Self::Hash(m) => m.replace_with_state(route, state),
Self::Memory(m) => m.replace_with_state(route, state),
}
}

Expand All @@ -77,6 +86,7 @@ impl History for AnyHistory {
match self {
Self::Browser(m) => m.push_with_query(route, query),
Self::Hash(m) => m.push_with_query(route, query),
Self::Memory(m) => m.push_with_query(route, query),
}
}
#[cfg(feature = "query")]
Expand All @@ -91,6 +101,7 @@ impl History for AnyHistory {
match self {
Self::Browser(m) => m.replace_with_query(route, query),
Self::Hash(m) => m.replace_with_query(route, query),
Self::Memory(m) => m.replace_with_query(route, query),
}
}

Expand All @@ -103,11 +114,12 @@ impl History for AnyHistory {
) -> HistoryResult<()>
where
Q: Serialize,
T: Serialize + 'static,
T: 'static,
{
match self {
Self::Browser(m) => m.push_with_query_and_state(route, query, state),
Self::Hash(m) => m.push_with_query_and_state(route, query, state),
Self::Memory(m) => m.push_with_query_and_state(route, query, state),
}
}

Expand All @@ -120,11 +132,12 @@ impl History for AnyHistory {
) -> HistoryResult<()>
where
Q: Serialize,
T: Serialize + 'static,
T: 'static,
{
match self {
Self::Browser(m) => m.replace_with_query_and_state(route, query, state),
Self::Hash(m) => m.replace_with_query_and_state(route, query, state),
Self::Memory(m) => m.replace_with_query_and_state(route, query, state),
}
}

Expand All @@ -135,13 +148,15 @@ impl History for AnyHistory {
match self {
Self::Browser(m) => m.listen(callback),
Self::Hash(m) => m.listen(callback),
Self::Memory(m) => m.listen(callback),
}
}

fn location(&self) -> Location {
match self {
Self::Browser(m) => m.location(),
Self::Hash(m) => m.location(),
Self::Memory(m) => m.location(),
}
}
}
Expand All @@ -157,3 +172,9 @@ impl From<HashHistory> for AnyHistory {
AnyHistory::Hash(m)
}
}

impl From<MemoryHistory> for AnyHistory {
fn from(m: MemoryHistory) -> AnyHistory {
AnyHistory::Memory(m)
}
}
88 changes: 41 additions & 47 deletions crates/history/src/browser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,22 @@ use std::any::Any;
use std::borrow::Cow;
use std::cell::RefCell;
use std::fmt;
use std::rc::{Rc, Weak};
use std::rc::Rc;

use gloo_events::EventListener;
use gloo_utils::window;
#[cfg(feature = "query")]
use serde::Serialize;
use wasm_bindgen::{JsValue, UnwrapThrowExt};
use web_sys::Url;

#[cfg(feature = "query")]
use crate::error::HistoryResult;
use crate::history::History;
use crate::listener::HistoryListener;
use crate::location::Location;
use crate::state::{HistoryState, StateMap};

type WeakCallback = Weak<dyn Fn()>;
use crate::utils::WeakCallback;

/// A [`History`] that is implemented with [`web_sys::History`] that provides native browser
/// history and state access.
Expand Down Expand Up @@ -110,14 +110,13 @@ impl History for BrowserHistory {
where
Q: Serialize,
{
let url = route.into();
let route = route.into();
let query = serde_urlencoded::to_string(query)?;

let url = Self::combine_url(&route, &query);

self.inner
.push_state_with_url(
&Self::create_history_state().1,
"",
Some(&format!("{}?{}", url, query)),
)
.push_state_with_url(&Self::create_history_state().1, "", Some(&url))
.expect_throw("failed to push history.");

self.notify_callbacks();
Expand All @@ -133,14 +132,13 @@ impl History for BrowserHistory {
where
Q: Serialize,
{
let url = route.into();
let route = route.into();
let query = serde_urlencoded::to_string(query)?;

let url = Self::combine_url(&route, &query);

self.inner
.replace_state_with_url(
&Self::create_history_state().1,
"",
Some(&format!("{}?{}", url, query)),
)
.replace_state_with_url(&Self::create_history_state().1, "", Some(&url))
.expect_throw("failed to replace history.");

self.notify_callbacks();
Expand All @@ -158,15 +156,18 @@ impl History for BrowserHistory {
Q: Serialize,
T: 'static,
{
let url = route.into();
let query = serde_urlencoded::to_string(query)?;

let (id, history_state) = Self::create_history_state();

let mut states = self.states.borrow_mut();
states.insert(id, Rc::new(state) as Rc<dyn Any>);

let route = route.into();
let query = serde_urlencoded::to_string(query)?;

let url = Self::combine_url(&route, &query);

self.inner
.push_state_with_url(&history_state, "", Some(&format!("{}?{}", url, query)))
.push_state_with_url(&history_state, "", Some(&url))
.expect_throw("failed to push history.");

self.notify_callbacks();
Expand All @@ -184,15 +185,18 @@ impl History for BrowserHistory {
Q: Serialize,
T: 'static,
{
let url = route.into();
let query = serde_urlencoded::to_string(query)?;

let (id, history_state) = Self::create_history_state();

let mut states = self.states.borrow_mut();
states.insert(id, Rc::new(state) as Rc<dyn Any>);

let route = route.into();
let query = serde_urlencoded::to_string(query)?;

let url = Self::combine_url(&route, &query);

self.inner
.replace_state_with_url(&history_state, "", Some(&format!("{}?{}", url, query)))
.replace_state_with_url(&history_state, "", Some(&url))
.expect_throw("failed to replace history.");

self.notify_callbacks();
Expand Down Expand Up @@ -292,30 +296,7 @@ impl BrowserHistory {
}

fn notify_callbacks(&self) {
let callables = {
let mut callbacks_ref = self.callbacks.borrow_mut();

// Any gone weak references are removed when called.
let (callbacks, callbacks_weak) = callbacks_ref.iter().cloned().fold(
(Vec::new(), Vec::new()),
|(mut callbacks, mut callbacks_weak), m| {
if let Some(m_strong) = m.clone().upgrade() {
callbacks.push(m_strong);
callbacks_weak.push(m);
}

(callbacks, callbacks_weak)
},
);

*callbacks_ref = callbacks_weak;

callbacks
};

for callback in callables {
callback()
}
crate::utils::notify_callbacks(self.callbacks.clone());
}

fn create_history_state() -> (u32, JsValue) {
Expand All @@ -327,4 +308,17 @@ impl BrowserHistory {
.expect_throw("fails to create history state."),
)
}

pub(crate) fn combine_url(route: &str, query: &str) -> String {
let href = window()
.location()
.href()
.expect_throw("Failed to read location href");

let url = Url::new_with_base(route, &href).expect_throw("current url is not valid.");

url.set_search(query);

url.href()
}
}
Loading

0 comments on commit e0603ac

Please sign in to comment.