service.rs 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. use gloo::history::{BrowserHistory, History, HistoryListener, Location};
  2. use std::{
  3. cell::{Cell, Ref, RefCell},
  4. collections::{HashMap, HashSet},
  5. rc::Rc,
  6. };
  7. use dioxus_core::ScopeId;
  8. use crate::platform::RouterProvider;
  9. pub struct RouterService {
  10. pub(crate) regen_route: Rc<dyn Fn(ScopeId)>,
  11. pub(crate) pending_events: Rc<RefCell<Vec<RouteEvent>>>,
  12. slots: Rc<RefCell<Vec<(ScopeId, String)>>>,
  13. onchange_listeners: Rc<RefCell<HashSet<ScopeId>>>,
  14. root_found: Rc<Cell<Option<ScopeId>>>,
  15. cur_path_params: Rc<RefCell<HashMap<String, String>>>,
  16. // history: Rc<dyn RouterProvider>,
  17. history: Rc<RefCell<BrowserHistory>>,
  18. listener: HistoryListener,
  19. }
  20. pub enum RouteEvent {
  21. Change,
  22. Pop,
  23. Push,
  24. }
  25. enum RouteSlot {
  26. Routes {
  27. // the partial route
  28. partial: String,
  29. // the total route
  30. total: String,
  31. // Connections to other routs
  32. rest: Vec<RouteSlot>,
  33. },
  34. }
  35. impl RouterService {
  36. pub fn new(regen_route: Rc<dyn Fn(ScopeId)>, root_scope: ScopeId) -> Self {
  37. let history = BrowserHistory::default();
  38. let location = history.location();
  39. let path = location.path();
  40. let onchange_listeners = Rc::new(RefCell::new(HashSet::new()));
  41. let slots: Rc<RefCell<Vec<(ScopeId, String)>>> = Default::default();
  42. let pending_events: Rc<RefCell<Vec<RouteEvent>>> = Default::default();
  43. let root_found = Rc::new(Cell::new(None));
  44. let listener = history.listen({
  45. let pending_events = pending_events.clone();
  46. let regen_route = regen_route.clone();
  47. let root_found = root_found.clone();
  48. let slots = slots.clone();
  49. let onchange_listeners = onchange_listeners.clone();
  50. move || {
  51. root_found.set(None);
  52. // checking if the route is valid is cheap, so we do it
  53. for (slot, root) in slots.borrow_mut().iter().rev() {
  54. // log::trace!("regenerating slot {:?} for root '{}'", slot, root);
  55. regen_route(*slot);
  56. }
  57. for listener in onchange_listeners.borrow_mut().iter() {
  58. // log::trace!("regenerating listener {:?}", listener);
  59. regen_route(*listener);
  60. }
  61. // also regenerate the root
  62. regen_route(root_scope);
  63. pending_events.borrow_mut().push(RouteEvent::Change)
  64. }
  65. });
  66. Self {
  67. listener,
  68. root_found,
  69. history: Rc::new(RefCell::new(history)),
  70. regen_route,
  71. slots,
  72. pending_events,
  73. onchange_listeners,
  74. cur_path_params: Rc::new(RefCell::new(HashMap::new())),
  75. }
  76. }
  77. pub fn push_route(&self, route: &str) {
  78. // log::trace!("Pushing route: {}", route);
  79. self.history.borrow_mut().push(route);
  80. }
  81. pub fn register_total_route(&self, route: String, scope: ScopeId, fallback: bool) {
  82. let clean = clean_route(route);
  83. // log::trace!("Registered route '{}' with scope id {:?}", clean, scope);
  84. self.slots.borrow_mut().push((scope, clean));
  85. }
  86. pub fn should_render(&self, scope: ScopeId) -> bool {
  87. // log::trace!("Should render scope id {:?}?", scope);
  88. if let Some(root_id) = self.root_found.get() {
  89. // log::trace!(" we already found a root with scope id {:?}", root_id);
  90. if root_id == scope {
  91. // log::trace!(" yes - it's a match");
  92. return true;
  93. }
  94. // log::trace!(" no - it's not a match");
  95. return false;
  96. }
  97. let location = self.history.borrow().location();
  98. let path = location.path();
  99. // log::trace!(" current path is '{}'", path);
  100. let roots = self.slots.borrow();
  101. let root = roots.iter().find(|(id, route)| id == &scope);
  102. // fallback logic
  103. match root {
  104. Some((id, route)) => {
  105. // log::trace!(
  106. // " matched given scope id {:?} with route root '{}'",
  107. // scope,
  108. // route,
  109. // );
  110. if let Some(params) = route_matches_path(route, path) {
  111. // log::trace!(" and it matches the current path '{}'", path);
  112. self.root_found.set(Some(*id));
  113. *self.cur_path_params.borrow_mut() = params;
  114. true
  115. } else {
  116. if route == "" {
  117. // log::trace!(" and the route is the root, so we will use that without a better match");
  118. self.root_found.set(Some(*id));
  119. true
  120. } else {
  121. // log::trace!(" and the route '{}' is not the root nor does it match the current path", route);
  122. false
  123. }
  124. }
  125. }
  126. None => false,
  127. }
  128. }
  129. pub fn current_location(&self) -> Location {
  130. self.history.borrow().location().clone()
  131. }
  132. pub fn current_path_params(&self) -> Ref<HashMap<String, String>> {
  133. self.cur_path_params.borrow()
  134. }
  135. pub fn subscribe_onchange(&self, id: ScopeId) {
  136. // log::trace!("Subscribing onchange for scope id {:?}", id);
  137. self.onchange_listeners.borrow_mut().insert(id);
  138. }
  139. pub fn unsubscribe_onchange(&self, id: ScopeId) {
  140. // log::trace!("Subscribing onchange for scope id {:?}", id);
  141. self.onchange_listeners.borrow_mut().remove(&id);
  142. }
  143. }
  144. fn clean_route(route: String) -> String {
  145. if route.as_str() == "/" {
  146. return route;
  147. }
  148. route.trim_end_matches('/').to_string()
  149. }
  150. fn clean_path(path: &str) -> &str {
  151. if path == "/" {
  152. return path;
  153. }
  154. path.trim_end_matches('/')
  155. }
  156. fn route_matches_path(route: &str, path: &str) -> Option<HashMap<String, String>> {
  157. let route_pieces = route.split('/').collect::<Vec<_>>();
  158. let path_pieces = clean_path(path).split('/').collect::<Vec<_>>();
  159. // log::trace!(
  160. // " checking route pieces {:?} vs path pieces {:?}",
  161. // route_pieces,
  162. // path_pieces,
  163. // );
  164. if route_pieces.len() != path_pieces.len() {
  165. // log::trace!(" the routes are different lengths");
  166. return None;
  167. }
  168. let mut matches = HashMap::new();
  169. for (i, r) in route_pieces.iter().enumerate() {
  170. // log::trace!(" checking route piece '{}' vs path", r);
  171. // If this is a parameter then it matches as long as there's
  172. // _any_thing in that spot in the path.
  173. if r.starts_with(':') {
  174. // log::trace!(
  175. // " route piece '{}' starts with a colon so it matches anything",
  176. // r,
  177. // );
  178. let param = &r[1..];
  179. matches.insert(param.to_string(), path_pieces[i].to_string());
  180. continue;
  181. }
  182. // log::trace!(
  183. // " route piece '{}' must be an exact match for path piece '{}'",
  184. // r,
  185. // path_pieces[i],
  186. // );
  187. if path_pieces[i] != *r {
  188. return None;
  189. }
  190. }
  191. Some(matches)
  192. }
  193. pub struct RouterCfg {
  194. initial_route: String,
  195. }
  196. impl RouterCfg {
  197. pub fn new(initial_route: String) -> Self {
  198. Self { initial_route }
  199. }
  200. }