ported Routes

This commit is contained in:
Jose Quesada 2022-12-15 13:00:53 -06:00
parent 610e38a967
commit 8adf9108c5

View file

@ -16,159 +16,150 @@ use crate::{
RouteContext, RouterContext,
};
/// Props for the [Routes] component, which contains route definitions and manages routing.
#[derive(TypedBuilder)]
pub struct RoutesProps {
#[builder(default, setter(strip_option))]
base: Option<String>,
children: Box<dyn Fn(Scope) -> Fragment>,
}
/// Contains route definitions and manages the actual routing process.
///
/// You should locate the `<Routes/>` component wherever on the page you want the routes to appear.
#[allow(non_snake_case)]
pub fn Routes(cx: Scope, props: RoutesProps) -> impl IntoView {
Component::new("Routes", move |cx| {
let router = use_context::<RouterContext>(cx).unwrap_or_else(|| {
log::warn!("<Routes/> component should be nested within a <Router/>.");
panic!()
});
let mut branches = Vec::new();
let children = (props.children)(cx)
.as_children()
.iter()
.filter_map(|child| child.as_transparent().and_then(|t| t.downcast_ref::<RouteDefinition>()))
.cloned()
.collect::<Vec<_>>();
create_branches(
&children,
&props.base.unwrap_or_default(),
&mut Vec::new(),
&mut branches,
);
// whenever path changes, update matches
let matches = create_memo(cx, {
let router = router.clone();
move |_| get_route_matches(branches.clone(), router.pathname().get())
});
// Rebuild the list of nested routes conservatively, and show the root route here
let disposers = RefCell::new(Vec::<ScopeDisposer>::new());
// iterate over the new matches, reusing old routes when they are the same
// and replacing them with new routes when they differ
let next: Rc<RefCell<Vec<RouteContext>>> = Default::default();
let root_equal = Rc::new(Cell::new(true));
let route_states: Memo<RouterState> = create_memo(cx, {
let root_equal = root_equal.clone();
move |prev: Option<&RouterState>| {
root_equal.set(true);
next.borrow_mut().clear();
let next_matches = matches.get();
let prev_matches = prev.as_ref().map(|p| &p.matches);
let prev_routes = prev.as_ref().map(|p| &p.routes);
// are the new route matches the same as the previous route matches so far?
let mut equal = prev_matches
.map(|prev_matches| next_matches.len() == prev_matches.len())
.unwrap_or(false);
for i in 0..next_matches.len() {
let next = next.clone();
let prev_match = prev_matches.and_then(|p| p.get(i));
let next_match = next_matches.get(i).unwrap();
match (prev_routes, prev_match) {
(Some(prev), Some(prev_match))
if next_match.route.key == prev_match.route.key =>
{
let prev_one = { prev.borrow()[i].clone() };
if i >= next.borrow().len() {
next.borrow_mut().push(prev_one);
} else {
*(next.borrow_mut().index_mut(i)) = prev_one;
}
#[component]
pub fn Routes(
cx: Scope,
#[prop(optional)] base: Option<String>,
children: Box<dyn Fn(Scope) -> Fragment>,
) -> impl IntoView {
let router = use_context::<RouterContext>(cx).unwrap_or_else(|| {
log::warn!("<Routes/> component should be nested within a <Router/>.");
panic!()
});
let mut branches = Vec::new();
let children = children(cx)
.as_children()
.iter()
.filter_map(|child| {
child
.as_transparent()
.and_then(|t| t.downcast_ref::<RouteDefinition>())
})
.cloned()
.collect::<Vec<_>>();
create_branches(
&children,
&base.unwrap_or_default(),
&mut Vec::new(),
&mut branches,
);
// whenever path changes, update matches
let matches = create_memo(cx, {
let router = router.clone();
move |_| get_route_matches(branches.clone(), router.pathname().get())
});
// Rebuild the list of nested routes conservatively, and show the root route here
let disposers = RefCell::new(Vec::<ScopeDisposer>::new());
// iterate over the new matches, reusing old routes when they are the same
// and replacing them with new routes when they differ
let next: Rc<RefCell<Vec<RouteContext>>> = Default::default();
let root_equal = Rc::new(Cell::new(true));
let route_states: Memo<RouterState> = create_memo(cx, {
let root_equal = root_equal.clone();
move |prev: Option<&RouterState>| {
root_equal.set(true);
next.borrow_mut().clear();
let next_matches = matches.get();
let prev_matches = prev.as_ref().map(|p| &p.matches);
let prev_routes = prev.as_ref().map(|p| &p.routes);
// are the new route matches the same as the previous route matches so far?
let mut equal = prev_matches
.map(|prev_matches| next_matches.len() == prev_matches.len())
.unwrap_or(false);
for i in 0..next_matches.len() {
let next = next.clone();
let prev_match = prev_matches.and_then(|p| p.get(i));
let next_match = next_matches.get(i).unwrap();
match (prev_routes, prev_match) {
(Some(prev), Some(prev_match))
if next_match.route.key == prev_match.route.key =>
{
let prev_one = { prev.borrow()[i].clone() };
if i >= next.borrow().len() {
next.borrow_mut().push(prev_one);
} else {
*(next.borrow_mut().index_mut(i)) = prev_one;
}
_ => {
equal = false;
if i == 0 {
root_equal.set(false);
}
let disposer = cx.child_scope({
}
_ => {
equal = false;
if i == 0 {
root_equal.set(false);
}
let disposer = cx.child_scope({
let next = next.clone();
let router = Rc::clone(&router.inner);
move |cx| {
let next = next.clone();
let router = Rc::clone(&router.inner);
move |cx| {
let next = next.clone();
let next_ctx = RouteContext::new(
cx,
&RouterContext { inner: router },
{
let next = next.clone();
move || {
if let Some(route_states) =
use_context::<Memo<RouterState>>(cx)
{
route_states.with(|route_states| {
let routes = route_states.routes.borrow();
routes.get(i + 1).cloned()
})
} else {
next.borrow().get(i + 1).cloned()
}
let next_ctx = RouteContext::new(
cx,
&RouterContext { inner: router },
{
let next = next.clone();
move || {
if let Some(route_states) =
use_context::<Memo<RouterState>>(cx)
{
route_states.with(|route_states| {
let routes = route_states.routes.borrow();
routes.get(i + 1).cloned()
})
} else {
next.borrow().get(i + 1).cloned()
}
},
move || matches.with(|m| m.get(i).cloned()),
);
if let Some(next_ctx) = next_ctx {
if next.borrow().len() > i + 1 {
next.borrow_mut()[i] = next_ctx;
} else {
next.borrow_mut().push(next_ctx);
}
},
move || matches.with(|m| m.get(i).cloned()),
);
if let Some(next_ctx) = next_ctx {
if next.borrow().len() > i + 1 {
next.borrow_mut()[i] = next_ctx;
} else {
next.borrow_mut().push(next_ctx);
}
}
});
if disposers.borrow().len() > i + 1 {
let mut disposers = disposers.borrow_mut();
let old_route_disposer = std::mem::replace(&mut disposers[i], disposer);
old_route_disposer.dispose();
} else {
disposers.borrow_mut().push(disposer);
}
});
if disposers.borrow().len() > i + 1 {
let mut disposers = disposers.borrow_mut();
let old_route_disposer = std::mem::replace(&mut disposers[i], disposer);
old_route_disposer.dispose();
} else {
disposers.borrow_mut().push(disposer);
}
}
}
if disposers.borrow().len() > next_matches.len() {
let surplus_disposers = disposers.borrow_mut().split_off(next_matches.len() + 1);
for disposer in surplus_disposers {
disposer.dispose();
}
}
if disposers.borrow().len() > next_matches.len() {
let surplus_disposers = disposers.borrow_mut().split_off(next_matches.len() + 1);
for disposer in surplus_disposers {
disposer.dispose();
}
if let Some(prev) = &prev {
if equal {
RouterState {
matches: next_matches.to_vec(),
routes: prev_routes.cloned().unwrap_or_default(),
root: prev.root.clone(),
}
} else {
let root = next.borrow().get(0).cloned();
RouterState {
matches: next_matches.to_vec(),
routes: Rc::new(RefCell::new(next.borrow().to_vec())),
root,
}
}
if let Some(prev) = &prev {
if equal {
RouterState {
matches: next_matches.to_vec(),
routes: prev_routes.cloned().unwrap_or_default(),
root: prev.root.clone(),
}
} else {
let root = next.borrow().get(0).cloned();
@ -178,29 +169,36 @@ pub fn Routes(cx: Scope, props: RoutesProps) -> impl IntoView {
root,
}
}
} else {
let root = next.borrow().get(0).cloned();
RouterState {
matches: next_matches.to_vec(),
routes: Rc::new(RefCell::new(next.borrow().to_vec())),
root,
}
}
});
// show the root route
let root = create_memo(cx, move |prev| {
provide_context(cx, route_states);
route_states.with(|state| {
let root = state.routes.borrow();
let root = root.get(0);
if let Some(route) = root {
provide_context(cx, route.clone());
}
if prev.is_none() || !root_equal.get() {
root.as_ref().map(|route| route.outlet().into_view(cx))
} else {
prev.cloned().unwrap()
}
})
});
move || root.get()
})
}
});
// show the root route
let root = create_memo(cx, move |prev| {
provide_context(cx, route_states);
route_states.with(|state| {
let root = state.routes.borrow();
let root = root.get(0);
if let Some(route) = root {
provide_context(cx, route.clone());
}
if prev.is_none() || !root_equal.get() {
root.as_ref().map(|route| route.outlet().into_view(cx))
} else {
prev.cloned().unwrap()
}
})
});
move || root.get()
}
#[derive(Clone, Debug, PartialEq)]