use crate::{Layout, RouterState};
use gpui::*;
use matchit::Router as MatchitRouter;
use smallvec::SmallVec;
use std::fmt::{Debug, Display};
type RouteElementFactory = Box<dyn Fn(&mut Window, &mut App) -> AnyElement>;
pub fn route() -> impl IntoElement {
Route::new()
}
#[derive(IntoElement)]
pub struct Route {
basename: SharedString,
path: Option<SharedString>,
pub(crate) element: Option<RouteElementFactory>,
pub(crate) routes: SmallVec<[Box<Route>; 1]>,
pub(crate) layout: Option<Box<dyn Layout>>,
}
impl Default for Route {
fn default() -> Self {
Self {
basename: SharedString::default(),
path: None,
element: None,
routes: SmallVec::new(),
layout: None,
}
}
}
impl Debug for Route {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Route")
.field("basename", &self.basename)
.field("path", &self.path)
.field("layout", &self.layout.is_some())
.field("element", &self.element.is_some())
.field("routes", &self.routes.len())
.finish()
}
}
impl Display for Route {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Route")
}
}
impl Route {
pub fn new() -> Self {
Self::default()
}
pub(crate) fn basename(mut self, basename: impl Into<SharedString>) -> Self {
self.basename = basename.into();
self
}
pub fn path(mut self, path: impl Into<SharedString>) -> Self {
self.path = Some(path.into());
self
}
pub fn element<F, E>(mut self, element_fn: F) -> Self
where
F: Fn(&mut Window, &mut App) -> E + 'static,
E: IntoElement,
{
if cfg!(debug_assertions) && self.layout.is_some() {
panic!("Route element and layout cannot be set at the same time");
}
self.element = Some(Box::new(move |window, cx| element_fn(window, cx).into_any_element()));
self
}
pub fn layout(mut self, layout: impl Layout + 'static) -> Self {
if cfg!(debug_assertions) && self.element.is_some() {
panic!("Route element and layout cannot be set at the same time");
}
self.layout = Some(Box::new(layout));
self
}
pub fn index(self) -> Self {
if cfg!(debug_assertions) && self.path.is_some() {
panic!("Route index and path cannot be set at the same time");
}
self.path("")
}
pub fn child(mut self, child: Route) -> Self {
self.routes.push(Box::new(child));
self
}
pub fn children(mut self, children: impl IntoIterator<Item = Route>) -> Self {
for child in children.into_iter() {
self = self.child(child);
}
self
}
pub(crate) fn build_route_map(&self, basename: &str) -> MatchitRouter<()> {
let basename = basename.trim_end_matches('/');
let mut router_map = MatchitRouter::new();
let path = match self.path {
Some(ref path) => format!("{}/{}", basename, path),
None => basename.to_string(),
};
let path = if path != "/" { path.trim_end_matches('/') } else { &path };
if self.element.is_some() {
router_map.insert(path, ()).unwrap();
return router_map;
}
for route in self.routes.iter() {
router_map.merge(route.build_route_map(path)).unwrap();
}
router_map
}
pub(crate) fn in_pattern(&self, basename: &str, path: &str) -> bool {
self.build_route_map(basename).at(path).is_ok()
}
}
impl RenderOnce for Route {
fn render(mut self, window: &mut Window, cx: &mut App) -> impl IntoElement {
if let Some(element_fn) = self.element {
return element_fn(window, cx);
}
if let Some(mut layout) = self.layout {
let pathname = cx.global::<RouterState>().location.pathname.clone();
let basename = self.basename.trim_end_matches('/');
let basename = match self.path {
Some(ref path) => format!("{}/{}", basename, path),
None => basename.to_string(),
};
let routes = std::mem::take(&mut self.routes);
let route = routes.into_iter().find(|route| route.in_pattern(&basename, &pathname));
if let Some(route) = route {
layout.outlet(route.basename(basename).render(window, cx).into_any_element());
}
return layout.render_layout(window, cx).into_any_element();
}
Empty {}.into_any_element()
}
}