1
0

lib.rs 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. mod utils;
  2. use std::{cell::RefCell, rc::Rc};
  3. use dioxus_core as dioxus;
  4. use dioxus_core::prelude::*;
  5. use dioxus_core_macro::{rsx, Props};
  6. use dioxus_html as dioxus_elements;
  7. use wasm_bindgen::{JsCast, JsValue};
  8. use web_sys::{window, Event};
  9. use crate::utils::strip_slash_suffix;
  10. pub trait Routable: 'static + Send + Clone + PartialEq {}
  11. impl<T> Routable for T where T: 'static + Send + Clone + PartialEq {}
  12. pub struct RouterService<R: Routable> {
  13. historic_routes: Vec<R>,
  14. history_service: RefCell<web_sys::History>,
  15. base_ur: RefCell<Option<String>>,
  16. }
  17. impl<R: Routable> RouterService<R> {
  18. fn push_route(&self, r: R) {
  19. todo!()
  20. // self.historic_routes.borrow_mut().push(r);
  21. }
  22. fn get_current_route(&self) -> &str {
  23. todo!()
  24. }
  25. fn update_route_impl(&self, url: String, push: bool) {
  26. let history = web_sys::window().unwrap().history().expect("no history");
  27. let base = self.base_ur.borrow();
  28. let path = match base.as_ref() {
  29. Some(base) => {
  30. let path = format!("{}{}", base, url);
  31. if path.is_empty() {
  32. "/".to_string()
  33. } else {
  34. path
  35. }
  36. }
  37. None => url,
  38. };
  39. if push {
  40. history
  41. .push_state_with_url(&JsValue::NULL, "", Some(&path))
  42. .expect("push history");
  43. } else {
  44. history
  45. .replace_state_with_url(&JsValue::NULL, "", Some(&path))
  46. .expect("replace history");
  47. }
  48. let event = Event::new("popstate").unwrap();
  49. web_sys::window()
  50. .unwrap()
  51. .dispatch_event(&event)
  52. .expect("dispatch");
  53. }
  54. }
  55. /// This hould only be used once per app
  56. ///
  57. /// You can manually parse the route if you want, but the derived `parse` method on `Routable` will also work just fine
  58. pub fn use_router<R: Routable>(cx: Context, mut parse: impl FnMut(&str) -> R + 'static) -> &R {
  59. // for the web, attach to the history api
  60. cx.use_hook(
  61. |f| {
  62. //
  63. use gloo::events::EventListener;
  64. let base = window()
  65. .unwrap()
  66. .document()
  67. .unwrap()
  68. .query_selector("base[href]")
  69. .ok()
  70. .flatten()
  71. .and_then(|base| {
  72. let base = JsCast::unchecked_into::<web_sys::HtmlBaseElement>(base).href();
  73. let url = web_sys::Url::new(&base).unwrap();
  74. if url.pathname() != "/" {
  75. Some(strip_slash_suffix(&base).to_string())
  76. } else {
  77. None
  78. }
  79. });
  80. let location = window().unwrap().location();
  81. let pathname = location.pathname().unwrap();
  82. let initial_route = parse(&pathname);
  83. let service: RouterService<R> = RouterService {
  84. historic_routes: vec![initial_route],
  85. history_service: RefCell::new(
  86. web_sys::window().unwrap().history().expect("no history"),
  87. ),
  88. base_ur: RefCell::new(base),
  89. };
  90. // let base = base_url();
  91. // let url = route.to_path();
  92. // pending_routes: RefCell::new(vec![]),
  93. // service.history_service.push_state(data, title);
  94. // cx.provide_state(service);
  95. let regenerate = cx.schedule_update();
  96. // // when "back" is called by the user, we want to to re-render the component
  97. let listener = EventListener::new(&web_sys::window().unwrap(), "popstate", move |_| {
  98. //
  99. regenerate();
  100. });
  101. service
  102. },
  103. |state| {
  104. let base = state.base_ur.borrow();
  105. if let Some(base) = base.as_ref() {
  106. //
  107. let path = format!("{}{}", base, state.get_current_route());
  108. }
  109. let history = state.history_service.borrow();
  110. state.historic_routes.last().unwrap()
  111. },
  112. )
  113. }
  114. pub fn use_router_service<R: Routable>(cx: Context) -> Option<&Rc<RouterService<R>>> {
  115. cx.use_hook(|_| cx.consume_state::<RouterService<R>>(), |f| f.as_ref())
  116. }
  117. #[derive(Props)]
  118. pub struct LinkProps<R: Routable> {
  119. to: R,
  120. /// The url that gets pushed to the history stack
  121. ///
  122. /// You can either put it your own inline method or just autoderive the route using `derive(Routable)`
  123. ///
  124. /// ```rust
  125. ///
  126. /// Link { to: Route::Home, href: |_| "home".to_string() }
  127. ///
  128. /// // or
  129. ///
  130. /// Link { to: Route::Home, href: Route::as_url }
  131. ///
  132. /// ```
  133. href: fn(&R) -> String,
  134. #[builder(default)]
  135. children: Element,
  136. }
  137. pub fn Link<R: Routable>(cx: Context, props: &LinkProps<R>) -> Element {
  138. let service = use_router_service::<R>(cx)?;
  139. cx.render(rsx! {
  140. a {
  141. href: format_args!("{}", (props.href)(&props.to)),
  142. onclick: move |_| service.push_route(props.to.clone()),
  143. {&props.children},
  144. }
  145. })
  146. }