1use crate::{Layout, RouterState};
2use gpui::*;
3use matchit::Router as MatchitRouter;
4use smallvec::SmallVec;
5use std::fmt::{Debug, Display};
6
7pub fn route() -> impl IntoElement {
8 Route::new()
9}
10
11#[derive(IntoElement, Default)]
14pub struct Route {
15 basename: SharedString,
16 path: Option<SharedString>,
17 pub(crate) element: Option<AnyElement>,
18 pub(crate) routes: SmallVec<[Box<Route>; 1]>,
19 pub(crate) layout: Option<Box<dyn Layout>>,
20}
21
22impl Debug for Route {
23 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24 f.debug_struct("Route")
25 .field("basename", &self.basename)
26 .field("path", &self.path)
27 .field("layout", &self.layout.is_some())
28 .field("element", &self.element.is_some())
29 .field("routes", &self.routes.len())
30 .finish()
31 }
32}
33
34impl Display for Route {
35 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36 write!(f, "Route")
37 }
38}
39
40impl Route {
41 pub fn new() -> Self {
42 Self::default()
43 }
44
45 pub(crate) fn basename(mut self, basename: impl Into<SharedString>) -> Self {
46 self.basename = basename.into();
47 self
48 }
49
50 pub fn path(mut self, path: impl Into<SharedString>) -> Self {
52 self.path = Some(path.into());
53 self
54 }
55
56 pub fn element<E: IntoElement>(mut self, element: E) -> Self {
57 if cfg!(debug_assertions) && self.layout.is_some() {
58 panic!("Route element and layout cannot be set at the same time");
59 }
60
61 self.element = Some(element.into_any_element());
62 self
63 }
64
65 pub fn layout(mut self, layout: impl Layout + 'static) -> Self {
66 if cfg!(debug_assertions) && self.element.is_some() {
67 panic!("Route element and layout cannot be set at the same time");
68 }
69
70 self.layout = Some(Box::new(layout));
71 self
72 }
73
74 pub fn index(self) -> Self {
75 if cfg!(debug_assertions) && self.path.is_some() {
76 panic!("Route index and path cannot be set at the same time");
77 }
78 self.path("")
79 }
80
81 pub fn child(mut self, child: Route) -> Self {
83 self.routes.push(Box::new(child));
84 self
85 }
86
87 pub fn children(mut self, children: impl IntoIterator<Item = Route>) -> Self {
89 for child in children.into_iter() {
90 self = self.child(child);
91 }
92 self
93 }
94
95 pub(crate) fn build_route_map(&self, basename: &str) -> MatchitRouter<()> {
96 let basename = basename.trim_end_matches('/');
97 let mut router_map = MatchitRouter::new();
98
99 let path = match self.path {
100 Some(ref path) => format!("{}/{}", basename, path),
101 None => basename.to_string(),
102 };
103
104 let path = if path != "/" { path.trim_end_matches('/') } else { &path };
105
106 if self.element.is_some() {
107 router_map.insert(path, ()).unwrap();
108 return router_map;
109 }
110
111 for route in self.routes.iter() {
113 router_map.merge(route.build_route_map(path)).unwrap();
114 }
115
116 router_map
117 }
118
119 pub(crate) fn in_pattern(&self, basename: &str, path: &str) -> bool {
120 self.build_route_map(basename).at(path).is_ok()
121 }
122}
123
124impl RenderOnce for Route {
125 fn render(mut self, window: &mut Window, cx: &mut App) -> impl IntoElement {
126 if let Some(element) = self.element {
127 return element;
128 }
129
130 if let Some(mut layout) = self.layout {
131 let pathname = cx.global::<RouterState>().location.pathname.clone();
132 let basename = self.basename.trim_end_matches('/');
133 let basename = match self.path {
134 Some(ref path) => format!("{}/{}", basename, path),
135 None => basename.to_string(),
136 };
137 let routes = std::mem::take(&mut self.routes);
138 let route = routes.into_iter().find(|route| route.in_pattern(&basename, &pathname));
139 if let Some(route) = route {
140 layout.outlet(route.basename(basename).render(window, cx).into_any_element());
141 }
142 return layout.render_layout(window, cx).into_any_element();
143 }
144 Empty {}.into_any_element()
145 }
146}