liveview.rs 16 KB

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