web.rs 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. use std::sync::{Arc, Mutex};
  2. use gloo::{events::EventListener, render::AnimationFrame};
  3. use log::error;
  4. use serde::{de::DeserializeOwned, Deserialize, Serialize};
  5. use web_sys::{window, History, ScrollRestoration, Window};
  6. use crate::routable::Routable;
  7. use super::{
  8. web_history::{get_current, push_state_and_url, replace_state_with_url},
  9. web_scroll::ScrollPosition,
  10. HistoryProvider,
  11. };
  12. #[derive(Deserialize, Serialize)]
  13. struct WebHistoryState<R> {
  14. state: R,
  15. scroll: ScrollPosition,
  16. }
  17. /// A [`HistoryProvider`] that integrates with a browser via the [History API].
  18. ///
  19. /// # Prefix
  20. /// This [`HistoryProvider`] supports a prefix, which can be used for web apps that aren't located
  21. /// at the root of their domain.
  22. ///
  23. /// Application developers are responsible for ensuring that right after the prefix comes a `/`. If
  24. /// that is not the case, this [`HistoryProvider`] will replace the first character after the prefix
  25. /// with one.
  26. ///
  27. /// Application developers are responsible for not rendering the router if the prefix is not present
  28. /// in the URL. Otherwise, if a router navigation is triggered, the prefix will be added.
  29. ///
  30. /// [History API]: https://developer.mozilla.org/en-US/docs/Web/API/History_API
  31. pub struct WebHistory<R: Serialize + DeserializeOwned> {
  32. do_scroll_restoration: bool,
  33. history: History,
  34. listener_navigation: Option<EventListener>,
  35. #[allow(dead_code)]
  36. listener_scroll: Option<EventListener>,
  37. listener_animation_frame: Arc<Mutex<Option<AnimationFrame>>>,
  38. prefix: Option<String>,
  39. window: Window,
  40. phantom: std::marker::PhantomData<R>,
  41. }
  42. impl<R: Serialize + DeserializeOwned> Default for WebHistory<R> {
  43. fn default() -> Self {
  44. Self::new(None, true)
  45. }
  46. }
  47. impl<R: Serialize + DeserializeOwned> WebHistory<R> {
  48. /// Create a new [`WebHistory`].
  49. ///
  50. /// If `do_scroll_restoration` is [`true`], [`WebHistory`] will take control of the history
  51. /// state. It'll also set the browsers scroll restoration to `manual`.
  52. pub fn new(prefix: Option<String>, do_scroll_restoration: bool) -> Self {
  53. let window = window().expect("access to `window`");
  54. let history = window.history().expect("`window` has access to `history`");
  55. let listener_scroll = match do_scroll_restoration {
  56. true => {
  57. history
  58. .set_scroll_restoration(ScrollRestoration::Manual)
  59. .expect("`history` can set scroll restoration");
  60. let w = window.clone();
  61. let h = history.clone();
  62. let document = w.document().expect("`window` has access to `document`");
  63. // Some(EventListener::new(&document, "scroll", move |_| {
  64. // set_state(&w, &h);
  65. // }))
  66. None
  67. }
  68. false => None,
  69. };
  70. Self {
  71. do_scroll_restoration,
  72. history,
  73. listener_navigation: None,
  74. listener_scroll,
  75. listener_animation_frame: Default::default(),
  76. prefix,
  77. window,
  78. phantom: Default::default(),
  79. }
  80. }
  81. fn create_state(&self, state: R) -> WebHistoryState<R> {
  82. let scroll = self
  83. .do_scroll_restoration
  84. .then(|| ScrollPosition::of_window(&self.window))
  85. .unwrap_or_default();
  86. WebHistoryState { state, scroll }
  87. }
  88. }
  89. impl<R: Serialize + DeserializeOwned + Routable> HistoryProvider<R> for WebHistory<R>
  90. where
  91. <R as std::str::FromStr>::Err: std::fmt::Display,
  92. {
  93. fn current_route(&self) -> R {
  94. match get_current::<WebHistoryState<_>>(&self.history) {
  95. // Try to get the route from the history state
  96. Some(route) => route.state,
  97. // If that fails, get the route from the current URL
  98. None => R::from_str(
  99. &self
  100. .window
  101. .location()
  102. .pathname()
  103. .unwrap_or_else(|_| String::from("/")),
  104. )
  105. .unwrap_or_else(|err| panic!("{}", err)),
  106. }
  107. }
  108. fn current_prefix(&self) -> Option<String> {
  109. self.prefix.clone()
  110. }
  111. fn go_back(&mut self) {
  112. if let Err(e) = self.history.back() {
  113. error!("failed to go back: {e:?}")
  114. }
  115. }
  116. fn go_forward(&mut self) {
  117. if let Err(e) = self.history.forward() {
  118. error!("failed to go forward: {e:?}")
  119. }
  120. }
  121. fn push(&mut self, state: R) {
  122. let path = match &self.prefix {
  123. None => format!("{state}"),
  124. Some(prefix) => format!("{prefix}{state}"),
  125. };
  126. let state = self.create_state(state);
  127. let nav = push_state_and_url(&self.history, &state, path);
  128. match nav {
  129. Ok(_) => {
  130. if self.do_scroll_restoration {
  131. self.window.scroll_to_with_x_and_y(0.0, 0.0)
  132. }
  133. }
  134. Err(e) => error!("failed to push state: {e:?}"),
  135. }
  136. }
  137. fn replace(&mut self, state: R) {
  138. let path = match &self.prefix {
  139. None => format!("{state}"),
  140. Some(prefix) => format!("{prefix}{state}"),
  141. };
  142. let state = self.create_state(state);
  143. let nav = replace_state_with_url(&self.history, &state, Some(&path));
  144. match nav {
  145. Ok(_) => {
  146. if self.do_scroll_restoration {
  147. self.window.scroll_to_with_x_and_y(0.0, 0.0)
  148. }
  149. }
  150. Err(e) => error!("failed to replace state: {e:?}"),
  151. }
  152. }
  153. fn external(&mut self, url: String) -> bool {
  154. match self.window.location().set_href(&url) {
  155. Ok(_) => true,
  156. Err(e) => {
  157. error!("failed to navigate to external url (`{url}): {e:?}");
  158. false
  159. }
  160. }
  161. }
  162. fn updater(&mut self, callback: std::sync::Arc<dyn Fn() + Send + Sync>) {
  163. let w = self.window.clone();
  164. let h = self.history.clone();
  165. let s = self.listener_animation_frame.clone();
  166. let d = self.do_scroll_restoration;
  167. self.listener_navigation = Some(EventListener::new(&self.window, "popstate", move |_| {
  168. (*callback)();
  169. if d {
  170. // let mut s = s.lock().expect("unpoisoned scroll mutex");
  171. // *s = Some(update_scroll(&w, &h));
  172. }
  173. }));
  174. }
  175. }