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