From 53d97755fd3274d30c45b8663f7939d0918a99f9 Mon Sep 17 00:00:00 2001 From: Adrian Wannenmacher Date: Thu, 15 Dec 2022 14:46:40 +0100 Subject: [PATCH] add web history integration --- packages/router-core/Cargo.toml | 4 + packages/router-core/src/history/mod.rs | 5 + packages/router-core/src/history/web.rs | 118 ++++++++++++++++++++++++ packages/router/Cargo.toml | 11 ++- packages/router/examples/simple.rs | 3 + packages/router/src/hooks/use_router.rs | 7 +- 6 files changed, 144 insertions(+), 4 deletions(-) create mode 100644 packages/router-core/src/history/web.rs diff --git a/packages/router-core/Cargo.toml b/packages/router-core/Cargo.toml index 5fcf3d2df..8ef8c4fdb 100644 --- a/packages/router-core/Cargo.toml +++ b/packages/router-core/Cargo.toml @@ -10,12 +10,16 @@ async-rwlock = "1.3.0" either = "1.8.0" futures-channel = "0.3.25" futures-util = "0.3.25" +gloo = { version = "0.8.0", optional = true } +log = "0.4.17" regex = { version = "1.6.0", optional = true } serde = { version = "1.0.147", optional = true } serde_urlencoded = { version = "0.7.1", optional = true } url = "2.3.1" urlencoding = "2.1.2" +web-sys = { version = "0.3.60", optional = true } [features] regex = ["dep:regex"] serde = ["dep:serde", "serde_urlencoded"] +web = ["gloo", "web-sys"] diff --git a/packages/router-core/src/history/mod.rs b/packages/router-core/src/history/mod.rs index 1a443ed42..01768579c 100644 --- a/packages/router-core/src/history/mod.rs +++ b/packages/router-core/src/history/mod.rs @@ -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 diff --git a/packages/router-core/src/history/web.rs b/packages/router-core/src/history/web.rs new file mode 100644 index 000000000..ab90fa1cc --- /dev/null +++ b/packages/router-core/src/history/web.rs @@ -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, + prefix: Option, + 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 { + 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 { + 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) { + self.listener_navigation = Some(self.history.listen(move || (*callback)())); + } +} diff --git a/packages/router/Cargo.toml b/packages/router/Cargo.toml index 74f7cffeb..17580375b 100644 --- a/packages/router/Cargo.toml +++ b/packages/router/Cargo.toml @@ -20,13 +20,18 @@ futures-util = "0.3.25" log = "0.4.17" thiserror = "1.0.37" -# for wasm - [features] regex = ["dioxus-router-core/regex"] serde = ["dioxus-router-core/serde"] +web = ["dioxus-router-core/web"] [dev-dependencies] dioxus = { path = "../dioxus" } -dioxus-desktop = { path = "../desktop" } dioxus-ssr = { path = "../ssr" } + +[target.'cfg(not(target_family = "wasm"))'.dev-dependencies] +dioxus-desktop = { path = "../desktop" } + +[target.'cfg(target_family = "wasm")'.dev-dependencies] +dioxus-router = { path = ".", features = ["web"] } +dioxus-web = { path= "../web" } diff --git a/packages/router/examples/simple.rs b/packages/router/examples/simple.rs index 6820a8598..72ffb6b2f 100644 --- a/packages/router/examples/simple.rs +++ b/packages/router/examples/simple.rs @@ -4,7 +4,10 @@ use dioxus::prelude::*; use dioxus_router::prelude::*; fn main() { + #[cfg(not(feature = "web"))] dioxus_desktop::launch(App); + #[cfg(feature = "web")] + dioxus_web::launch(App); } fn App(cx: Scope) -> Element { diff --git a/packages/router/src/hooks/use_router.rs b/packages/router/src/hooks/use_router.rs index 667b7b63d..5dc58993a 100644 --- a/packages/router/src/hooks/use_router.rs +++ b/packages/router/src/hooks/use_router.rs @@ -188,11 +188,16 @@ pub struct RouterConfiguration { impl Default for RouterConfiguration { fn default() -> Self { + #[cfg(not(feature = "web"))] + let history = Box::::default(); + #[cfg(feature = "web")] + let history = Box::::default(); + Self { failure_external_navigation: comp(FailureExternalNavigation), failure_named_navigation: comp(FailureNamedNavigation), failure_redirection_limit: comp(FailureRedirectionLimit), - history: Box::::default(), + history: history, on_update: None, synchronous: false, }