rustolio_web/router/
mod.rs1use crate::prelude::*;
12
13pub trait Routes: Clone + PartialEq + 'static {
14 fn all_routes() -> impl Iterator<Item = (&'static str, usize)>;
15
16 fn resolve(route_match: Option<matchit::Match<&usize>>) -> Option<Self>;
17
18 fn element(self) -> Element;
19
20 fn href(&self) -> String;
21}
22
23#[derive(Debug, PartialEq, Eq)]
24pub struct Router<R> {
25 current_route: GlobalSignal<Option<R>>,
26}
27
28impl<T> Clone for Router<T> {
30 fn clone(&self) -> Self {
31 *self
32 }
33}
34impl<T> Copy for Router<T> {}
35
36impl<R: Routes> Router<R> {
37 pub fn new() -> crate::Result<Self> {
38 let mut router = matchit::Router::new();
39
40 for (path, id) in R::all_routes() {
41 router.insert(path, id).expect("Failed to insert route");
42 }
43
44 let current_route = GlobalSignal::new({
45 let pathname = location()?
46 .pathname()
47 .context("Failed to get global pathname")?;
48 let route_match = router.at(&pathname).ok();
49 R::resolve(route_match)
50 });
51
52 let listener = html::Listener::new_result("popstate", move |_: Event| {
54 let router = router.clone();
55 let pathname = location()?
56 .pathname()
57 .context("Failed to get global pathname")?;
58 let route_match = router.at(&pathname).ok();
59 current_route.set(R::resolve(route_match));
60 Ok(())
61 });
62 window()?.add_event_listener(&listener)?;
63
64 Ok(Self { current_route })
65 }
66
67 pub fn current_route(&self) -> Option<R> {
68 self.current_route.value()
69 }
70
71 pub fn component(&self) -> Elements {
72 let current_route = self.current_route;
73 elements! {
74 move || {
75 let route = current_route.value();
76 let Some(route) = route else {
77 return Self::fallback();
78 };
79 route.element()
80 }
81 }
82 }
83
84 #[track_caller]
85 pub fn navigate(route: R) -> crate::Result<()> {
86 let path = route.href();
87
88 if location()?.pathname().unwrap() == path {
89 return Ok(());
91 }
92
93 history()?
94 .push_state_with_url(&JsValue::NULL, "", Some(&path))
95 .context("Failed to push new url")?;
96 window()?
97 .dispatch_event(&Event::new("popstate").context("Failed to construct custom event")?)
98 .context("Failed to dispatch new url event")?;
99
100 Ok(())
101 }
102
103 fn fallback() -> Element {
104 panic!(
105 "{}",
106 r#"
107URL not found or a param could not be parsed.
108
109A fallback route could be implemented:
110
111#[derive(Debug, Clone, PartialEq, rustolio::Router)]
112enum AppRouter {
113 #[fallback]
114 AppFallbackPage,
115}
116
117#[component
118fn AppFallbackPage() -> Element {
119 h1! { "NOT FOUND" }
120}
121 "#
122 )
123 }
124}