add web hash history integration

This commit is contained in:
Adrian Wannenmacher committed 2022-12-15 15:45:19 +01:00
1 parent 53d97755fd
commit 9912c32ffd
4 files changed
+164 -5

No files matched your search

+2 -1
View File
@@ -17,9 +17,10 @@ serde = { version = "1.0.147", optional = true }
serde_urlencoded = { version = "0.7.1", optional = true }
url = "2.3.1"
urlencoding = "2.1.2"
wasm-bindgen = { version = "0.2.83", optional = true }
web-sys = { version = "0.3.60", optional = true }
[features]
regex = ["dep:regex"]
serde = ["dep:serde", "serde_urlencoded"]
web = ["gloo", "web-sys"]
web = ["gloo", "wasm-bindgen", "web-sys"]
+5
View File
@@ -8,6 +8,11 @@
use std::sync::Arc;
#[cfg(feature = "web")]
mod web_hash;
#[cfg(feature = "web")]
pub use web_hash::*;
mod memory;
pub use memory::*;
+4 -4
View File
@@ -31,9 +31,9 @@ pub struct WebHistory {
impl WebHistory {
/// Create a new [`WebHistory`] with a prefix.
#[must_use]
pub fn with_prefix(prefix: String) -> Self {
pub fn with_prefix(prefix: impl Into<String>) -> Self {
Self {
prefix: Some(prefix),
prefix: Some(prefix.into()),
..Default::default()
}
}
@@ -105,8 +105,8 @@ impl HistoryProvider for WebHistory {
fn external(&mut self, url: String) -> bool {
match self.window.location().set_href(&url) {
Ok(_) => true,
Err(_) => {
error!("`WebHistory` failed to navigate to external target: {url}");
Err(e) => {
error!("failed to navigate to external url (`{url}): {e:?}");
false
}
}
@@ -0,0 +1,153 @@
use gloo::{events::EventListener, utils::window};
use log::error;
use url::Url;
use wasm_bindgen::JsValue;
use web_sys::{History, Window};
use super::HistoryProvider;
const INITIAL_URL: &str = "dioxus-router-core://initial_url.invalid/";
/// A [`HistoryProvider`] that integrates with a browser via the [History API]. It uses the URLs
/// hash instead of its path.
///
/// Early web applications used the hash to store the current path because there was no other way
/// for them to interact with the history without triggering a browser navigation, as the
/// [History API] did not yet exist. While this implementation could have been written that way, it
/// was not, because no browser supports WebAssembly without the [History API].
///
/// [History API]: https://developer.mozilla.org/en-US/docs/Web/API/History_API
pub struct WebHashHistory {
history: History,
window: Window,
}
impl Default for WebHashHistory {
fn default() -> Self {
let window = window();
Self {
history: window.history().expect("window has history"),
window,
}
}
}
impl WebHashHistory {
fn join_url_to_hash(&self, path: String) -> Option<String> {
if path.starts_with("//") {
error!("cannot navigate to paths starting with `//`, got `{path}`");
return None;
}
let url = match self.url() {
Some(c) => match c.join(&path) {
Ok(new) => new,
Err(e) => {
error!("failed to join location with target: {e}");
return None;
}
},
None => {
error!("current location unknown");
return None;
}
};
Some(format!(
"#{path}{query}",
path = url.path(),
query = url.query().map(|q| format!("?{q}")).unwrap_or_default()
))
}
fn url(&self) -> Option<Url> {
let mut path = self.window.location().hash().ok()?;
if path.starts_with('#') {
path.remove(0);
}
if path.starts_with('/') {
path.remove(0);
}
match Url::parse(&format!("{INITIAL_URL}/{path}")) {
Ok(url) => Some(url),
Err(e) => {
error!("failed to parse hash path: {e}");
None
}
}
}
}
impl HistoryProvider for WebHashHistory {
fn current_path(&self) -> String {
self.url()
.map(|url| url.path().to_string())
.unwrap_or(String::from("/"))
}
fn current_query(&self) -> Option<String> {
self.url().and_then(|url| url.query().map(String::from))
}
fn current_prefix(&self) -> Option<String> {
Some(String::from("#"))
}
fn go_back(&mut self) {
if let Err(e) = self.history.back() {
error!("failed to go back: {e:?}")
}
}
fn go_forward(&mut self) {
if let Err(e) = self.history.forward() {
error!("failed to go forward: {e:?}")
}
}
fn push(&mut self, path: String) {
let hash = match self.join_url_to_hash(path) {
Some(hash) => hash,
None => return,
};
if let Err(e) = self
.history
.push_state_with_url(&JsValue::NULL, "", Some(&hash))
{
error!("failed to push state: {e:?}");
}
}
fn replace(&mut self, path: String) {
let hash = match self.join_url_to_hash(path) {
Some(hash) => hash,
None => return,
};
if let Err(e) = self
.history
.replace_state_with_url(&JsValue::NULL, "", Some(&hash))
{
error!("failed to replace state: {e:?}");
}
}
fn external(&mut self, url: String) -> bool {
match self.window.location().set_href(&url) {
Ok(_) => true,
Err(e) => {
error!("failed to navigate to external url (`{url}): {e:?}");
false
}
}
}
fn updater(&mut self, callback: std::sync::Arc<dyn Fn() + Send + Sync>) {
let listener = EventListener::new(&self.window, "popstate", move |_| (*callback)());
}
}