Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Memory-based History #178

Merged
merged 5 commits into from
Dec 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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