From b3d6f2ccde2d034c8459fc51e836add77fbe827f Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Sat, 4 Dec 2021 21:29:14 +0900 Subject: [PATCH 1/5] Better route and query handling. --- crates/history/src/browser.rs | 53 +++++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/crates/history/src/browser.rs b/crates/history/src/browser.rs index c4e8bdb0..d7187eda 100644 --- a/crates/history/src/browser.rs +++ b/crates/history/src/browser.rs @@ -9,6 +9,7 @@ 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; @@ -110,14 +111,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(); @@ -133,14 +133,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(); @@ -158,15 +157,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); + + 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(); @@ -184,15 +186,17 @@ impl History for BrowserHistory { Q: Serialize, T: 'static, { - let url = route.into(); + let route = route.into(); let query = serde_urlencoded::to_string(query)?; + let url = Self::combine_url(&route, &query); + let (id, history_state) = Self::create_history_state(); let mut states = self.states.borrow_mut(); states.insert(id, Rc::new(state) as Rc); 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(); @@ -327,4 +331,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() + } } From a6c320a5fb2c85a1a6a9d3ca957ee33956d972fa Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Sat, 4 Dec 2021 22:34:13 +0900 Subject: [PATCH 2/5] Add memory history. --- crates/history/src/browser.rs | 39 +- crates/history/src/hash.rs | 48 +-- crates/history/src/lib.rs | 2 + crates/history/src/memory.rs | 375 ++++++++++++++++++ crates/history/src/utils.rs | 51 +++ crates/history/tests/memory_history.rs | 23 ++ .../tests/memory_history_feat_serialize.rs | 81 ++++ 7 files changed, 559 insertions(+), 60 deletions(-) create mode 100644 crates/history/src/memory.rs create mode 100644 crates/history/tests/memory_history.rs create mode 100644 crates/history/tests/memory_history_feat_serialize.rs diff --git a/crates/history/src/browser.rs b/crates/history/src/browser.rs index d7187eda..dbe64ef0 100644 --- a/crates/history/src/browser.rs +++ b/crates/history/src/browser.rs @@ -2,7 +2,7 @@ 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; @@ -17,8 +17,7 @@ use crate::history::History; use crate::listener::HistoryListener; use crate::location::Location; use crate::state::{HistoryState, StateMap}; - -type WeakCallback = Weak; +use crate::utils::WeakCallback; /// A [`History`] that is implemented with [`web_sys::History`] that provides native browser /// history and state access. @@ -186,15 +185,16 @@ impl History for BrowserHistory { Q: Serialize, T: 'static, { + let (id, history_state) = Self::create_history_state(); + + let mut states = self.states.borrow_mut(); + states.insert(id, Rc::new(state) as Rc); + let route = route.into(); let query = serde_urlencoded::to_string(query)?; let url = Self::combine_url(&route, &query); - let (id, history_state) = Self::create_history_state(); - - let mut states = self.states.borrow_mut(); - states.insert(id, Rc::new(state) as Rc); self.inner .replace_state_with_url(&history_state, "", Some(&url)) .expect_throw("failed to replace history."); @@ -296,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) { diff --git a/crates/history/src/hash.rs b/crates/history/src/hash.rs index 3f8d4d1d..b2d37ea2 100644 --- a/crates/history/src/hash.rs +++ b/crates/history/src/hash.rs @@ -5,7 +5,7 @@ use std::fmt; use gloo_utils::window; #[cfg(feature = "query")] use serde::Serialize; -use wasm_bindgen::{throw_str, UnwrapThrowExt}; +use wasm_bindgen::UnwrapThrowExt; use web_sys::Url; use crate::browser::BrowserHistory; @@ -14,6 +14,7 @@ use crate::error::HistoryResult; use crate::history::History; use crate::listener::HistoryListener; use crate::location::Location; +use crate::utils::{assert_absolute_path, assert_no_query}; /// A [`History`] that is implemented with [`web_sys::History`] and stores path in `#`(fragment). /// @@ -43,9 +44,8 @@ impl History for HashHistory { fn push<'a>(&self, route: impl Into>) { let route = route.into(); - if !route.starts_with('/') { - throw_str("You cannot push relative path in hash history."); - } + assert_absolute_path(&route); + assert_no_query(&route); let url = Self::get_url(); url.set_hash(&route); @@ -56,9 +56,8 @@ impl History for HashHistory { fn replace<'a>(&self, route: impl Into>) { let route = route.into(); - if !route.starts_with('/') { - throw_str("You cannot push relative path in hash history."); - } + assert_absolute_path(&route); + assert_no_query(&route); let url = Self::get_url(); url.set_hash(&route); @@ -72,9 +71,8 @@ impl History for HashHistory { { let route = route.into(); - if !route.starts_with('/') { - throw_str("You cannot push relative path in hash history."); - } + assert_absolute_path(&route); + assert_no_query(&route); let url = Self::get_url(); url.set_hash(&route); @@ -88,9 +86,8 @@ impl History for HashHistory { { let route = route.into(); - if !route.starts_with('/') { - throw_str("You cannot push relative path in hash history."); - } + assert_absolute_path(&route); + assert_no_query(&route); let url = Self::get_url(); url.set_hash(&route); @@ -106,9 +103,8 @@ impl History for HashHistory { let query = serde_urlencoded::to_string(query)?; let route = route.into(); - if !route.starts_with('/') { - throw_str("You cannot push relative path in hash history."); - } + assert_absolute_path(&route); + assert_no_query(&route); let url = Self::get_url(); url.set_hash(&format!("{}?{}", route, query)); @@ -128,9 +124,8 @@ impl History for HashHistory { let query = serde_urlencoded::to_string(query)?; let route = route.into(); - if !route.starts_with('/') { - throw_str("You cannot push relative path in hash history."); - } + assert_absolute_path(&route); + assert_no_query(&route); let url = Self::get_url(); url.set_hash(&format!("{}?{}", route, query)); @@ -152,9 +147,8 @@ impl History for HashHistory { { let route = route.into(); - if !route.starts_with('/') { - throw_str("You cannot push relative path in hash history."); - } + assert_absolute_path(&route); + assert_no_query(&route); let url = Self::get_url(); @@ -179,9 +173,8 @@ impl History for HashHistory { { let route = route.into(); - if !route.starts_with('/') { - throw_str("You cannot push relative path in hash history."); - } + assert_absolute_path(&route); + assert_no_query(&route); let url = Self::get_url(); @@ -205,10 +198,7 @@ impl History for HashHistory { // We strip # from hash. let hash_url = inner_loc.hash().chars().skip(1).collect::(); - assert!( - hash_url.starts_with('/'), - "hash-based url cannot be relative path." - ); + assert_absolute_path(&hash_url); let hash_url = Url::new_with_base( &hash_url, diff --git a/crates/history/src/lib.rs b/crates/history/src/lib.rs index 3c4ae73e..70aea8a2 100644 --- a/crates/history/src/lib.rs +++ b/crates/history/src/lib.rs @@ -11,12 +11,14 @@ mod hash; mod history; mod listener; mod location; +mod memory; mod state; mod utils; pub use any::AnyHistory; pub use browser::BrowserHistory; pub use hash::HashHistory; +pub use memory::MemoryHistory; #[cfg(feature = "query")] pub use error::{HistoryError, HistoryResult}; diff --git a/crates/history/src/memory.rs b/crates/history/src/memory.rs new file mode 100644 index 00000000..afb5dd41 --- /dev/null +++ b/crates/history/src/memory.rs @@ -0,0 +1,375 @@ +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; + +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, +}; + +/// A History Stack. +#[derive(Debug)] +struct LocationStack { + prev: Vec, + next: VecDeque, + 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) { + // Go forward. + 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); + } + } + } + // Go backward. + 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); + } + } + } + // Do nothing. + Ordering::Equal => {} + } + } + + fn push(&mut self, mut location: Location) { + std::mem::swap(&mut location, &mut self.current); + + self.prev.push(location); + // When a history is pushed, we clear all forward states. + 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()), + }, + } + } +} + +/// A [`History`] that is implemented with in memory history stack and is usable in most targets. +/// +/// # Panics +/// +/// MemoryHistory does not support relative paths and will panic if routes are not starting with `/`. +#[derive(Clone, Default)] +pub struct MemoryHistory { + inner: Rc>, + callbacks: Rc>>, +} + +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>) { + 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>) { + 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>, 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), + id: Some(get_id()), + }; + + self.inner.borrow_mut().push(location); + + self.notify_callbacks(); + } + + fn replace_with_state<'a, T>(&self, route: impl Into>, 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), + 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>, 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>, + 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(all(feature = "query"))] + fn push_with_query_and_state<'a, Q, T>( + &self, + route: impl Into>, + 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), + id: Some(get_id()), + }; + + self.inner.borrow_mut().push(location); + + self.notify_callbacks(); + + Ok(()) + } + + #[cfg(all(feature = "query"))] + fn replace_with_query_and_state<'a, Q, T>( + &self, + route: impl Into>, + 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), + id: Some(get_id()), + }; + + self.inner.borrow_mut().replace(location); + + self.notify_callbacks(); + + Ok(()) + } + + fn listen(&self, callback: CB) -> HistoryListener + where + CB: Fn() + 'static, + { + // Callbacks do not receive a copy of [`History`] to prevent reference cycle. + let cb = Rc::new(callback) as Rc; + + self.callbacks.borrow_mut().push(Rc::downgrade(&cb)); + + HistoryListener { _listener: cb } + } + + fn location(&self) -> Location { + self.inner.borrow().current() + } +} + +impl MemoryHistory { + /// Creates a new [`MemoryHistory`] with a default entry of '/'. + pub fn new() -> Self { + Self::default() + } + + /// Creates a new [`MemoryHistory`] with entires. + pub fn with_entries<'a>(entries: impl IntoIterator>>) -> 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()); + } +} diff --git a/crates/history/src/utils.rs b/crates/history/src/utils.rs index d7a42194..3994c91f 100644 --- a/crates/history/src/utils.rs +++ b/crates/history/src/utils.rs @@ -1,7 +1,58 @@ +use std::cell::RefCell; +use std::rc::{Rc, Weak}; use std::sync::atomic::{AtomicU32, Ordering}; +use wasm_bindgen::throw_str; + pub(crate) fn get_id() -> u32 { static ID_CTR: AtomicU32 = AtomicU32::new(0); ID_CTR.fetch_add(1, Ordering::SeqCst) } + +pub(crate) fn assert_absolute_path(path: &str) { + if !path.starts_with('/') { + throw_str("You cannot use relative path with this history type."); + } +} + +pub(crate) fn assert_no_query(path: &str) { + if path.contains('?') { + throw_str("You cannot have query in path, try use a variant of this method with `_query`."); + } +} + +pub(crate) fn assert_no_fragment(path: &str) { + if path.contains('#') { + throw_str("You cannot use fragments (hash) in memory history."); + } +} + +pub(crate) type WeakCallback = Weak; + +pub(crate) fn notify_callbacks(callbacks: Rc>>) { + let callables = { + let mut callbacks_ref = 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() + } +} diff --git a/crates/history/tests/memory_history.rs b/crates/history/tests/memory_history.rs new file mode 100644 index 00000000..41af77db --- /dev/null +++ b/crates/history/tests/memory_history.rs @@ -0,0 +1,23 @@ +use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure}; + +use gloo_history::{History, MemoryHistory}; + +wasm_bindgen_test_configure!(run_in_browser); + +#[test] +fn history_works() { + let history = MemoryHistory::new(); + assert_eq!(history.location().path(), "/"); + + history.push("/path-a"); + assert_eq!(history.location().path(), "/path-a"); + + history.replace("/path-b"); + assert_eq!(history.location().path(), "/path-b"); + + history.back(); + assert_eq!(history.location().path(), "/"); + + history.forward(); + assert_eq!(history.location().path(), "/path-b"); +} diff --git a/crates/history/tests/memory_history_feat_serialize.rs b/crates/history/tests/memory_history_feat_serialize.rs new file mode 100644 index 00000000..0683b25c --- /dev/null +++ b/crates/history/tests/memory_history_feat_serialize.rs @@ -0,0 +1,81 @@ +use wasm_bindgen_test::wasm_bindgen_test_configure; + +wasm_bindgen_test_configure!(run_in_browser); + +#[cfg(all(feature = "query"))] +mod feat_serialize { + use wasm_bindgen_test::wasm_bindgen_test as test; + + use std::rc::Rc; + + use serde::{Deserialize, Serialize}; + + use gloo_history::{History, MemoryHistory}; + + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct Query { + a: String, + b: u64, + } + + #[derive(Debug, Serialize, Deserialize, PartialEq)] + struct State { + i: String, + ii: u64, + } + + #[test] + fn history_serialize_works() { + let history = MemoryHistory::new(); + assert_eq!(history.location().path(), "/"); + + history.push("/path-a"); + assert_eq!(history.location().path(), "/path-a"); + + history.replace("/path-b"); + assert_eq!(history.location().path(), "/path-b"); + + history.back(); + assert_eq!(history.location().path(), "/"); + + history.forward(); + assert_eq!(history.location().path(), "/path-b"); + + history + .push_with_query( + "/path", + Query { + a: "something".to_string(), + b: 123, + }, + ) + .unwrap(); + + assert_eq!(history.location().path(), "/path"); + assert_eq!(history.location().query_str(), "?a=something&b=123"); + assert_eq!( + history.location().query::().unwrap(), + Query { + a: "something".to_string(), + b: 123, + } + ); + + history.push_with_state( + "/path-c", + State { + i: "something".to_string(), + ii: 123, + }, + ); + + assert_eq!(history.location().path(), "/path-c"); + assert_eq!( + history.location().state::().unwrap(), + Rc::new(State { + i: "something".to_string(), + ii: 123, + }) + ); + } +} From a750dfee59ed237b978651622c450de413b64e2a Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Sat, 4 Dec 2021 22:40:05 +0900 Subject: [PATCH 3/5] Fix clippy. --- crates/history/src/history.rs | 1 + crates/history/src/memory.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/crates/history/src/history.rs b/crates/history/src/history.rs index ec3d1f38..e26a7afc 100644 --- a/crates/history/src/history.rs +++ b/crates/history/src/history.rs @@ -1,5 +1,6 @@ use std::borrow::Cow; +#[cfg(feature = "query")] use serde::Serialize; #[cfg(feature = "query")] diff --git a/crates/history/src/memory.rs b/crates/history/src/memory.rs index afb5dd41..d3d09a89 100644 --- a/crates/history/src/memory.rs +++ b/crates/history/src/memory.rs @@ -9,6 +9,7 @@ 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; From e5374502e83aa970356f62d3d9b3f53cee4bb6dd Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Sat, 4 Dec 2021 22:44:24 +0900 Subject: [PATCH 4/5] Add MemoryHistory to AnyHistory. --- crates/history/src/any.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/crates/history/src/any.rs b/crates/history/src/any.rs index 3d894b77..7fbf1b0c 100644 --- a/crates/history/src/any.rs +++ b/crates/history/src/any.rs @@ -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)] @@ -18,6 +19,8 @@ pub enum AnyHistory { Browser(BrowserHistory), /// A Hash History Hash(HashHistory), + /// A Memory History + Memory(MemoryHistory), } impl History for AnyHistory { @@ -25,6 +28,7 @@ impl History for AnyHistory { match self { Self::Browser(m) => m.len(), Self::Hash(m) => m.len(), + Self::Memory(m) => m.len(), } } @@ -32,6 +36,7 @@ impl History for AnyHistory { match self { Self::Browser(m) => m.go(delta), Self::Hash(m) => m.go(delta), + Self::Memory(m) => m.go(delta), } } @@ -39,6 +44,7 @@ impl History for AnyHistory { match self { Self::Browser(m) => m.push(route), Self::Hash(m) => m.push(route), + Self::Memory(m) => m.push(route), } } @@ -46,6 +52,7 @@ impl History for AnyHistory { match self { Self::Browser(m) => m.replace(route), Self::Hash(m) => m.replace(route), + Self::Memory(m) => m.replace(route), } } @@ -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), } } @@ -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), } } @@ -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")] @@ -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), } } @@ -108,6 +119,7 @@ impl History for AnyHistory { 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), } } @@ -125,6 +137,7 @@ impl History for AnyHistory { 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), } } @@ -135,6 +148,7 @@ impl History for AnyHistory { match self { Self::Browser(m) => m.listen(callback), Self::Hash(m) => m.listen(callback), + Self::Memory(m) => m.listen(callback), } } @@ -142,6 +156,7 @@ impl History for AnyHistory { match self { Self::Browser(m) => m.location(), Self::Hash(m) => m.location(), + Self::Memory(m) => m.location(), } } } @@ -157,3 +172,9 @@ impl From for AnyHistory { AnyHistory::Hash(m) } } + +impl From for AnyHistory { + fn from(m: MemoryHistory) -> AnyHistory { + AnyHistory::Memory(m) + } +} From 76e2456e7f14170ad6b786490d50c487d337d50e Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Sun, 5 Dec 2021 12:05:24 +0900 Subject: [PATCH 5/5] Remove extra traits. --- crates/history/src/any.rs | 4 ++-- crates/history/src/history.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/history/src/any.rs b/crates/history/src/any.rs index 7fbf1b0c..02986c38 100644 --- a/crates/history/src/any.rs +++ b/crates/history/src/any.rs @@ -114,7 +114,7 @@ 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), @@ -132,7 +132,7 @@ 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), diff --git a/crates/history/src/history.rs b/crates/history/src/history.rs index e26a7afc..9f97e65f 100644 --- a/crates/history/src/history.rs +++ b/crates/history/src/history.rs @@ -80,7 +80,7 @@ pub trait History: Clone + PartialEq { ) -> HistoryResult<()> where Q: Serialize, - T: Serialize + 'static; + T: 'static; /// Same as `.replace_with_state()` but affix the queries to the end of the route. #[cfg(all(feature = "query"))] @@ -92,7 +92,7 @@ pub trait History: Clone + PartialEq { ) -> HistoryResult<()> where Q: Serialize, - T: Serialize + 'static; + T: 'static; /// Creates a Listener that will be notified when current state changes. ///