소스 검색

add web history integration

Adrian Wannenmacher 2 년 전
부모
커밋
53d97755fd

+ 4 - 0
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"]

+ 5 - 0
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

+ 118 - 0
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<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)()));
+    }
+}

+ 8 - 3
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" }

+ 3 - 0
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 {

+ 6 - 1
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::<MemoryHistory>::default();
+        #[cfg(feature = "web")]
+        let history = Box::<crate::history::WebHistory>::default();
+
         Self {
             failure_external_navigation: comp(FailureExternalNavigation),
             failure_named_navigation: comp(FailureNamedNavigation),
             failure_redirection_limit: comp(FailureRedirectionLimit),
-            history: Box::<MemoryHistory>::default(),
+            history: history,
             on_update: None,
             synchronous: false,
         }