Intigrate macro with router hooks and components
This commit is contained in:
1 parent
ef6551a6cd
commit
e4b8fbfafe
37 files changed
+1442
-3009
No files matched your search
@@ -0,0 +1,220 @@
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use gloo::{events::EventListener, render::AnimationFrame, utils::window};
|
||||
use log::error;
|
||||
use url::Url;
|
||||
use web_sys::{History, ScrollRestoration, Window};
|
||||
|
||||
use super::{
|
||||
web_scroll::{top_left, update_history, update_scroll},
|
||||
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<R: Serialize + DeserializeOwned> {
|
||||
do_scroll_restoration: bool,
|
||||
history: History,
|
||||
listener_navigation: Option<EventListener>,
|
||||
#[allow(dead_code)]
|
||||
listener_scroll: Option<EventListener>,
|
||||
listener_animation_frame: Arc<Mutex<Option<AnimationFrame>>>,
|
||||
window: Window,
|
||||
}
|
||||
|
||||
impl WebHashHistory {
|
||||
/// Create a new [`WebHashHistory`].
|
||||
///
|
||||
/// If `do_scroll_restoration` is [`true`], [`WebHashHistory`] will take control of the history
|
||||
/// state. It'll also set the browsers scroll restoration to `manual`.
|
||||
pub fn new(do_scroll_restoration: bool) -> Self {
|
||||
let window = window();
|
||||
let history = window.history().expect("`window` has access to `history`");
|
||||
|
||||
history
|
||||
.set_scroll_restoration(ScrollRestoration::Manual)
|
||||
.expect("`history` can set scroll restoration");
|
||||
|
||||
let listener_scroll = match do_scroll_restoration {
|
||||
true => {
|
||||
history
|
||||
.set_scroll_restoration(ScrollRestoration::Manual)
|
||||
.expect("`history` can set scroll restoration");
|
||||
let w = window.clone();
|
||||
let h = history.clone();
|
||||
let document = w.document().expect("`window` has access to `document`");
|
||||
|
||||
Some(EventListener::new(&document, "scroll", move |_| {
|
||||
update_history(&w, &h);
|
||||
}))
|
||||
}
|
||||
false => None,
|
||||
};
|
||||
|
||||
Self {
|
||||
do_scroll_restoration,
|
||||
history,
|
||||
listener_navigation: None,
|
||||
listener_scroll,
|
||||
listener_animation_frame: Default::default(),
|
||||
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_route(&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,
|
||||
};
|
||||
|
||||
let state = match self.do_scroll_restoration {
|
||||
true => top_left(),
|
||||
false => self.history.state().unwrap_or_default(),
|
||||
};
|
||||
|
||||
let nav = self.history.push_state_with_url(&state, "", Some(&hash));
|
||||
|
||||
match nav {
|
||||
Ok(_) => {
|
||||
if self.do_scroll_restoration {
|
||||
self.window.scroll_to_with_x_and_y(0.0, 0.0)
|
||||
}
|
||||
}
|
||||
Err(e) => 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,
|
||||
};
|
||||
|
||||
let state = match self.do_scroll_restoration {
|
||||
true => top_left(),
|
||||
false => self.history.state().unwrap_or_default(),
|
||||
};
|
||||
|
||||
let nav = self.history.replace_state_with_url(&state, "", Some(&hash));
|
||||
|
||||
match nav {
|
||||
Ok(_) => {
|
||||
if self.do_scroll_restoration {
|
||||
self.window.scroll_to_with_x_and_y(0.0, 0.0)
|
||||
}
|
||||
}
|
||||
Err(e) => 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 w = self.window.clone();
|
||||
let h = self.history.clone();
|
||||
let s = self.listener_animation_frame.clone();
|
||||
let d = self.do_scroll_restoration;
|
||||
|
||||
self.listener_navigation = Some(EventListener::new(&self.window, "popstate", move |_| {
|
||||
(*callback)();
|
||||
if d {
|
||||
let mut s = s.lock().expect("unpoisoned scroll mutex");
|
||||
*s = Some(update_scroll(&w, &h));
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user