history.rs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  1. use dioxus_core::prelude::spawn;
  2. use dioxus_document::Eval;
  3. use dioxus_history::History;
  4. use serde::{Deserialize, Serialize};
  5. use std::rc::Rc;
  6. use std::sync::{Mutex, RwLock};
  7. use std::{collections::BTreeMap, sync::Arc};
  8. /// A [`HistoryProvider`] that evaluates history through JS.
  9. pub(crate) struct LiveviewHistory {
  10. action_tx: tokio::sync::mpsc::UnboundedSender<Action>,
  11. timeline: Arc<Mutex<Timeline>>,
  12. updater_callback: Arc<RwLock<Arc<dyn Fn() + Send + Sync>>>,
  13. }
  14. struct Timeline {
  15. current_index: usize,
  16. routes: BTreeMap<usize, String>,
  17. }
  18. #[derive(Serialize, Deserialize, Debug)]
  19. struct State {
  20. index: usize,
  21. }
  22. #[derive(Serialize, Deserialize, Debug)]
  23. struct Session {
  24. #[serde(with = "routes")]
  25. routes: BTreeMap<usize, String>,
  26. last_visited: usize,
  27. }
  28. enum Action {
  29. GoBack,
  30. GoForward,
  31. Push(String),
  32. Replace(String),
  33. External(String),
  34. }
  35. impl Timeline {
  36. fn new(initial_path: String) -> Self {
  37. Self {
  38. current_index: 0,
  39. routes: BTreeMap::from([(0, initial_path)]),
  40. }
  41. }
  42. fn init(
  43. &mut self,
  44. route: String,
  45. state: Option<State>,
  46. session: Option<Session>,
  47. depth: usize,
  48. ) -> State {
  49. if let Some(session) = session {
  50. self.routes = session.routes;
  51. if state.is_none() {
  52. // top of stack
  53. let last_visited = session.last_visited;
  54. self.routes.retain(|&lhs, _| lhs <= last_visited);
  55. }
  56. };
  57. let state = match state {
  58. Some(state) => {
  59. self.current_index = state.index;
  60. state
  61. }
  62. None => {
  63. let index = depth - 1;
  64. self.current_index = index;
  65. State { index }
  66. }
  67. };
  68. self.routes.insert(state.index, route);
  69. state
  70. }
  71. fn update(&mut self, route: String, state: Option<State>) -> State {
  72. if let Some(state) = state {
  73. self.current_index = state.index;
  74. self.routes.insert(self.current_index, route);
  75. state
  76. } else {
  77. self.push(route)
  78. }
  79. }
  80. fn push(&mut self, route: String) -> State {
  81. // top of stack
  82. let index = self.current_index + 1;
  83. self.current_index = index;
  84. self.routes.insert(index, route);
  85. self.routes.retain(|&rhs, _| index >= rhs);
  86. State {
  87. index: self.current_index,
  88. }
  89. }
  90. fn replace(&mut self, route: String) -> State {
  91. self.routes.insert(self.current_index, route);
  92. State {
  93. index: self.current_index,
  94. }
  95. }
  96. fn current_route(&self) -> &str {
  97. &self.routes[&self.current_index]
  98. }
  99. fn session(&self) -> Session {
  100. Session {
  101. routes: self.routes.clone(),
  102. last_visited: self.current_index,
  103. }
  104. }
  105. }
  106. impl LiveviewHistory {
  107. /// Create a [`LiveviewHistory`] in the given scope.
  108. /// When using a [`LiveviewHistory`] in combination with use_eval, history must be untampered with.
  109. ///
  110. /// # Panics
  111. ///
  112. /// Panics if the function is not called in a dioxus runtime with a Liveview context.
  113. pub(crate) fn new(eval: Rc<dyn Fn(&str) -> Eval>) -> Self {
  114. Self::new_with_initial_path(
  115. "/".parse().unwrap_or_else(|err| {
  116. panic!("index route does not exist:\n{}\n use LiveviewHistory::new_with_initial_path to set a custom path", err)
  117. }),
  118. eval
  119. )
  120. }
  121. /// Create a [`LiveviewHistory`] in the given scope, starting at `initial_path`.
  122. /// When using a [`LiveviewHistory`] in combination with use_eval, history must be untampered with.
  123. ///
  124. /// # Panics
  125. ///
  126. /// Panics if the function is not called in a dioxus runtime with a Liveview context.
  127. fn new_with_initial_path(initial_path: String, eval: Rc<dyn Fn(&str) -> Eval>) -> Self {
  128. let (action_tx, mut action_rx) = tokio::sync::mpsc::unbounded_channel::<Action>();
  129. let timeline = Arc::new(Mutex::new(Timeline::new(initial_path)));
  130. let updater_callback: Arc<RwLock<Arc<dyn Fn() + Send + Sync>>> =
  131. Arc::new(RwLock::new(Arc::new(|| {})));
  132. // Listen to server actions
  133. spawn({
  134. let timeline = timeline.clone();
  135. let create_eval = eval.clone();
  136. async move {
  137. loop {
  138. let eval = action_rx.recv().await.expect("sender to exist");
  139. let _ = match eval {
  140. Action::GoBack => create_eval(
  141. r#"
  142. // this triggers a PopState event
  143. history.back();
  144. "#,
  145. ),
  146. Action::GoForward => create_eval(
  147. r#"
  148. // this triggers a PopState event
  149. history.forward();
  150. "#,
  151. ),
  152. Action::Push(route) => {
  153. let mut timeline = timeline.lock().expect("unpoisoned mutex");
  154. let state = timeline.push(route.clone());
  155. let state = serde_json::to_string(&state).expect("serializable state");
  156. let session = serde_json::to_string(&timeline.session())
  157. .expect("serializable session");
  158. create_eval(&format!(
  159. r#"
  160. // this does not trigger a PopState event
  161. history.pushState({state}, "", "{route}");
  162. sessionStorage.setItem("liveview", '{session}');
  163. "#
  164. ))
  165. }
  166. Action::Replace(route) => {
  167. let mut timeline = timeline.lock().expect("unpoisoned mutex");
  168. let state = timeline.replace(route.clone());
  169. let state = serde_json::to_string(&state).expect("serializable state");
  170. let session = serde_json::to_string(&timeline.session())
  171. .expect("serializable session");
  172. create_eval(&format!(
  173. r#"
  174. // this does not trigger a PopState event
  175. history.replaceState({state}, "", "{route}");
  176. sessionStorage.setItem("liveview", '{session}');
  177. "#
  178. ))
  179. }
  180. Action::External(url) => create_eval(&format!(
  181. r#"
  182. location.href = "{url}";
  183. "#
  184. )),
  185. };
  186. }
  187. }
  188. });
  189. // Listen to browser actions
  190. spawn({
  191. let updater = updater_callback.clone();
  192. let timeline = timeline.clone();
  193. let create_eval = eval.clone();
  194. async move {
  195. let mut popstate_eval = {
  196. let init_eval = create_eval(
  197. r#"
  198. return [
  199. document.location.pathname + "?" + document.location.search + "\#" + document.location.hash,
  200. history.state,
  201. JSON.parse(sessionStorage.getItem("liveview")),
  202. history.length,
  203. ];
  204. "#,
  205. ).await.expect("serializable state");
  206. let (route, state, session, depth) =
  207. serde_json::from_value::<(String, Option<State>, Option<Session>, usize)>(
  208. init_eval,
  209. )
  210. .expect("serializable state");
  211. let mut timeline = timeline.lock().expect("unpoisoned mutex");
  212. let state = timeline.init(route.clone(), state, session, depth);
  213. let state = serde_json::to_string(&state).expect("serializable state");
  214. let session =
  215. serde_json::to_string(&timeline.session()).expect("serializable session");
  216. // Call the updater callback
  217. (updater.read().unwrap())();
  218. create_eval(&format!(
  219. r#"
  220. // this does not trigger a PopState event
  221. history.replaceState({state}, "", "{route}");
  222. sessionStorage.setItem("liveview", '{session}');
  223. window.addEventListener("popstate", (event) => {{
  224. dioxus.send([
  225. document.location.pathname + "?" + document.location.search + "\#" + document.location.hash,
  226. event.state,
  227. ]);
  228. }});
  229. "#
  230. ))
  231. };
  232. loop {
  233. let event = match popstate_eval.recv().await {
  234. Ok(event) => event,
  235. Err(_) => continue,
  236. };
  237. let (route, state) = serde_json::from_value::<(String, Option<State>)>(event)
  238. .expect("serializable state");
  239. let mut timeline = timeline.lock().expect("unpoisoned mutex");
  240. let state = timeline.update(route.clone(), state);
  241. let state = serde_json::to_string(&state).expect("serializable state");
  242. let session =
  243. serde_json::to_string(&timeline.session()).expect("serializable session");
  244. let _ = create_eval(&format!(
  245. r#"
  246. // this does not trigger a PopState event
  247. history.replaceState({state}, "", "{route}");
  248. sessionStorage.setItem("liveview", '{session}');
  249. "#
  250. ));
  251. // Call the updater callback
  252. (updater.read().unwrap())();
  253. }
  254. }
  255. });
  256. Self {
  257. action_tx,
  258. timeline,
  259. updater_callback,
  260. }
  261. }
  262. }
  263. impl History for LiveviewHistory {
  264. fn go_back(&self) {
  265. let _ = self.action_tx.send(Action::GoBack);
  266. }
  267. fn go_forward(&self) {
  268. let _ = self.action_tx.send(Action::GoForward);
  269. }
  270. fn push(&self, route: String) {
  271. let _ = self.action_tx.send(Action::Push(route));
  272. }
  273. fn replace(&self, route: String) {
  274. let _ = self.action_tx.send(Action::Replace(route));
  275. }
  276. fn external(&self, url: String) -> bool {
  277. let _ = self.action_tx.send(Action::External(url));
  278. true
  279. }
  280. fn current_route(&self) -> String {
  281. let timeline = self.timeline.lock().expect("unpoisoned mutex");
  282. timeline.current_route().to_string()
  283. }
  284. fn can_go_back(&self) -> bool {
  285. let timeline = self.timeline.lock().expect("unpoisoned mutex");
  286. // Check if the one before is contiguous (i.e., not an external page)
  287. let visited_indices: Vec<usize> = timeline.routes.keys().cloned().collect();
  288. visited_indices
  289. .iter()
  290. .position(|&rhs| timeline.current_index == rhs)
  291. .map_or(false, |index| {
  292. index > 0 && visited_indices[index - 1] == timeline.current_index - 1
  293. })
  294. }
  295. fn can_go_forward(&self) -> bool {
  296. let timeline = self.timeline.lock().expect("unpoisoned mutex");
  297. // Check if the one after is contiguous (i.e., not an external page)
  298. let visited_indices: Vec<usize> = timeline.routes.keys().cloned().collect();
  299. visited_indices
  300. .iter()
  301. .rposition(|&rhs| timeline.current_index == rhs)
  302. .map_or(false, |index| {
  303. index < visited_indices.len() - 1
  304. && visited_indices[index + 1] == timeline.current_index + 1
  305. })
  306. }
  307. fn updater(&self, callback: Arc<dyn Fn() + Send + Sync>) {
  308. let mut updater_callback = self.updater_callback.write().unwrap();
  309. *updater_callback = callback;
  310. }
  311. fn include_prevent_default(&self) -> bool {
  312. true
  313. }
  314. }
  315. mod routes {
  316. use serde::de::{MapAccess, Visitor};
  317. use serde::{ser::SerializeMap, Deserializer, Serializer};
  318. use std::collections::BTreeMap;
  319. pub fn serialize<S>(routes: &BTreeMap<usize, String>, serializer: S) -> Result<S::Ok, S::Error>
  320. where
  321. S: Serializer,
  322. {
  323. let mut map = serializer.serialize_map(Some(routes.len()))?;
  324. for (index, route) in routes.iter() {
  325. map.serialize_entry(&index.to_string(), &route.to_string())?;
  326. }
  327. map.end()
  328. }
  329. pub fn deserialize<'de, D>(deserializer: D) -> Result<BTreeMap<usize, String>, D::Error>
  330. where
  331. D: Deserializer<'de>,
  332. {
  333. struct BTreeMapVisitor {}
  334. impl<'de> Visitor<'de> for BTreeMapVisitor {
  335. type Value = BTreeMap<usize, String>;
  336. fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
  337. formatter.write_str("a map with indices and routable values")
  338. }
  339. fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
  340. where
  341. M: MapAccess<'de>,
  342. {
  343. let mut routes = BTreeMap::new();
  344. while let Some((index, route)) = map.next_entry::<String, String>()? {
  345. let index = index.parse::<usize>().map_err(serde::de::Error::custom)?;
  346. routes.insert(index, route);
  347. }
  348. Ok(routes)
  349. }
  350. }
  351. deserializer.deserialize_map(BTreeMapVisitor {})
  352. }
  353. }