use std::{
cell::{Cell, RefCell},
rc::Rc,
};
use leptos::*;
use crate::{
matching::{resolve_path, PathMatch, RouteDefinition, RouteMatch},
ParamsMap, RouterContext,
};
thread_local! {
static ROUTE_ID: Cell<usize> = Cell::new(0);
}
#[component(transparent)]
pub fn Route<E, F, P>(
cx: Scope,
path: P,
view: F,
#[prop(optional)]
children: Option<Box<dyn FnOnce(Scope) -> Fragment>>,
) -> impl IntoView
where
E: IntoView,
F: Fn(Scope) -> E + 'static,
P: std::fmt::Display,
{
let children = children
.map(|children| {
children(cx)
.as_children()
.iter()
.filter_map(|child| {
child
.as_transparent()
.and_then(|t| t.downcast_ref::<RouteDefinition>())
})
.cloned()
.collect::<Vec<_>>()
})
.unwrap_or_default();
let id = ROUTE_ID.with(|id| {
let next = id.get() + 1;
id.set(next);
next
});
RouteDefinition {
id,
path: path.to_string(),
children,
view: Rc::new(move |cx| view(cx).into_view(cx)),
}
}
impl IntoView for RouteDefinition {
fn into_view(self, cx: Scope) -> View {
Transparent::new(self).into_view(cx)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct RouteContext {
inner: Rc<RouteContextInner>,
}
impl RouteContext {
pub(crate) fn new(
cx: Scope,
router: &RouterContext,
child: impl Fn(Scope) -> Option<RouteContext> + 'static,
matcher: impl Fn() -> Option<RouteMatch> + 'static,
) -> Option<Self> {
let base = router.base();
let base = base.path();
let RouteMatch { path_match, route } = matcher()?;
let PathMatch { path, .. } = path_match;
let RouteDefinition {
view: element, id, ..
} = route.key;
let params = create_memo(cx, move |_| {
matcher()
.map(|matched| matched.path_match.params)
.unwrap_or_default()
});
Some(Self {
inner: Rc::new(RouteContextInner {
cx,
id,
base_path: base,
child: Box::new(child),
path: RefCell::new(path),
original_path: route.original_path.to_string(),
params,
outlet: Box::new(move || Some(element(cx))),
}),
})
}
pub fn cx(&self) -> Scope {
self.inner.cx
}
pub(crate) fn id(&self) -> usize {
self.inner.id
}
pub fn path(&self) -> String {
self.inner.path.borrow().to_string()
}
pub(crate) fn set_path(&mut self, path: String) {
*self.inner.path.borrow_mut() = path;
}
pub fn original_path(&self) -> &str {
&self.inner.original_path
}
pub fn params(&self) -> Memo<ParamsMap> {
self.inner.params
}
pub(crate) fn base(cx: Scope, path: &str, fallback: Option<fn(Scope) -> View>) -> Self {
Self {
inner: Rc::new(RouteContextInner {
cx,
id: 0,
base_path: path.to_string(),
child: Box::new(|_| None),
path: RefCell::new(path.to_string()),
original_path: path.to_string(),
params: create_memo(cx, |_| ParamsMap::new()),
outlet: Box::new(move || fallback.as_ref().map(move |f| f(cx))),
}),
}
}
pub fn resolve_path(&self, to: &str) -> Option<String> {
resolve_path(&self.inner.base_path, to, Some(&self.inner.path.borrow())).map(String::from)
}
pub fn child(&self, cx: Scope) -> Option<RouteContext> {
(self.inner.child)(cx)
}
pub fn outlet(&self) -> impl IntoView {
(self.inner.outlet)()
}
}
pub(crate) struct RouteContextInner {
cx: Scope,
base_path: String,
pub(crate) id: usize,
pub(crate) child: Box<dyn Fn(Scope) -> Option<RouteContext>>,
pub(crate) path: RefCell<String>,
pub(crate) original_path: String,
pub(crate) params: Memo<ParamsMap>,
pub(crate) outlet: Box<dyn Fn() -> Option<View>>,
}
impl PartialEq for RouteContextInner {
fn eq(&self, other: &Self) -> bool {
self.cx == other.cx
&& self.base_path == other.base_path
&& self.path == other.path
&& self.original_path == other.original_path
&& self.params == other.params
}
}
impl std::fmt::Debug for RouteContextInner {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RouteContextInner")
.field("path", &self.path)
.field("ParamsMap", &self.params)
.finish()
}
}