add web history integration

This commit is contained in:
Adrian Wannenmacher committed 2022-12-15 14:46:40 +01:00
1 parent e26e44fbc5
commit 53d97755fd
6 files changed
+144 -4

No files matched your search

+5
View File
@@ -11,6 +11,11 @@ use std::sync::Arc;
mod memory;
pub use memory::*;
#[cfg(feature = "web")]
mod web;
#[cfg(feature = "web")]
pub use web::*;
/// An integration with some kind of navigation history.
///
/// Depending on your use case, your implementation may deviate from the described procedure. This
+118
View File
@@ -0,0 +1,118 @@
use gloo::{
history::{BrowserHistory, History, HistoryListener},
utils::window,
};
use log::error;
use web_sys::Window;
use super::HistoryProvider;
/// A [`HistoryProvider`] that integrates with a browser via the [History API].
///
/// # Prefix
/// This [`HistoryProvider`] supports a prefix, which can be used for web apps that aren't located
/// at the root of their domain.
///
/// Application developers are responsible for ensuring that right after the prefix comes a `/`. If
/// that is not the case, this [`HistoryProvider`] will replace the first character after the prefix
/// with one.
///
/// Application developers are responsible for not rendering the router if the prefix is not present
/// in the URL. Otherwise, if a router navigation is triggered, the prefix will be added.
///
/// [History API]: https://developer.mozilla.org/en-US/docs/Web/API/History_API
pub struct WebHistory {
history: BrowserHistory,
listener_navigation: Option<HistoryListener>,
prefix: Option<String>,
window: Window,
}
impl WebHistory {
/// Create a new [`WebHistory`] with a prefix.
#[must_use]
pub fn with_prefix(prefix: String) -> Self {
Self {
prefix: Some(prefix),
..Default::default()
}
}
}
impl Default for WebHistory {
fn default() -> Self {
Self {
history: BrowserHistory::new(),
listener_navigation: None,
prefix: None,
window: window(),
}
}
}
impl HistoryProvider for WebHistory {
fn current_path(&self) -> String {
let location = self.history.location();
let path = location.path();
match &self.prefix {
None => path.to_string(),
Some(prefix) => path
.starts_with(prefix)
.then(|| path.split_at(prefix.len()).1)
.unwrap_or("/")
.to_string(),
}
}
fn current_query(&self) -> Option<String> {
let location = self.history.location();
let query = location.query_str();
if query.is_empty() {
None
} else {
Some(query.to_string())
}
}
fn current_prefix(&self) -> Option<String> {
self.prefix.clone()
}
fn go_back(&mut self) {
self.history.back();
}
fn go_forward(&mut self) {
self.history.forward();
}
fn push(&mut self, path: String) {
self.history.push(match &self.prefix {
None => path,
Some(prefix) => format!("{prefix}{path}"),
});
}
fn replace(&mut self, path: String) {
self.history.replace(match &self.prefix {
None => path,
Some(prefix) => format!("{prefix}{path}"),
});
}
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}");
false
}
}
}
fn updater(&mut self, callback: std::sync::Arc<dyn Fn() + Send + Sync>) {
self.listener_navigation = Some(self.history.listen(move || (*callback)()));
}
}