navigation between nested routes

This commit is contained in:
Greg Johnston 2024-03-09 20:29:41 -05:00
parent 13cccced06
commit 16bd2942db
11 changed files with 450 additions and 187 deletions

View file

@ -1,3 +1,3 @@
[unstable]
build-std = ["std", "panic_abort", "core", "alloc"]
build-std-features = ["panic_immediate_abort"]
#[unstable]
#build-std = ["std", "panic_abort", "core", "alloc"]
#build-std-features = ["panic_immediate_abort"]

View file

@ -12,15 +12,15 @@ panic = "abort"
[dependencies]
console_log = "1"
log = "0.4"
leptos = { path = "../../leptos", features = ["csr"] } #, "tracing"] }
routing = { path = "../../routing" } #, features = ["tracing"] }
leptos = { path = "../../leptos", features = ["csr", "tracing"] }
routing = { path = "../../routing", features = ["tracing"] }
#leptos_router = { path = "../../router", features = ["csr", "nightly"] }
serde = { version = "1", features = ["derive"] }
futures = "0.3"
#console_error_panic_hook = "0.1.7"
#tracing-subscriber = "0.3.18"
#tracing-subscriber-wasm = "0.1.0"
#tracing = "0.1.40"
console_error_panic_hook = "0.1.7"
tracing-subscriber = "0.3.18"
tracing-subscriber-wasm = "0.1.0"
tracing = "0.1.40"
#leptos_meta = { path = "../../meta", features = ["csr", "nightly"] }
[dev-dependencies]

View file

@ -21,39 +21,7 @@ struct ExampleContext(i32);
#[component]
pub fn RouterExample() -> impl IntoView {
let router = Router::new(
BrowserUrl::new().unwrap(),
Routes::new((
NestedRoute::new(StaticSegment(""), Home),
NestedRoute::new(StaticSegment("notfound"), NotFount),
NestedRoute::new(StaticSegment("about"), About),
)),
|| "This page could not be found.",
);
view! {
<h3>"Leptos Router Bloat"</h3>
<nav>
<a href="/">"Home"</a>
" | "
<a href="/about">"About"</a>
</nav>
<br />
<main>
{router}
// un-comment the following lines and build again
// <Router>
// <Routes>
// <Route path="/" view=|cx| view! { cx, <Home /> } />
// <Route path="/about" view=|cx| view! { cx, <About /> } />
// <Route path="/*any" view=|cx| view! { cx, <NotFount /> } />
// </Routes>
// </Router>
</main>
}
}
/*info!("rendering <RouterExample/>");
info!("rendering <RouterExample/>");
// contexts are passed down through the route tree
provide_context(ExampleContext(0));
@ -93,7 +61,7 @@ pub fn RouterExample() -> impl IntoView {
<a href="/redirect-home">"Redirect to Home"</a>
</nav>
{router}
<Router>
/*<Router>
<nav>
// ordinary <a> elements can be used for client-side navigation
// using <A> has two effects:
@ -127,45 +95,11 @@ pub fn RouterExample() -> impl IntoView {
/>
</AnimatedRoutes>
</main>
</Router>
</Router>*/
}
}*/
fn Home(data: RouteData<Dom>) -> impl IntoView {
"Home Page"
}
fn About(data: RouteData<Dom>) -> impl IntoView {
"About Page"
}
fn NotFount(data: RouteData<Dom>) -> impl IntoView {
"Not Found!"
}
/*
// You can define other routes in their own component.
// Use a #[component(transparent)] that returns a <Route/>.
#[component(transparent)]
pub fn ContactRoutes() -> impl IntoView {
view! {
<Route
path=""
view=|| view! { <ContactList/> }
>
<Route
path=":id"
view=|| view! { <Contact/> }
/>
<Route
path="/"
view=|| view! { <p>"Select a contact."</p> }
/>
</Route>
}
}*/
/*pub fn ContactList(route_data: RouteData<Dom>) -> impl IntoView {
pub fn ContactList(route_data: RouteData<Dom>) -> impl IntoView {
info!("rendering <ContactList/>");
// contexts are passed down through the route tree
@ -178,10 +112,10 @@ pub fn ContactRoutes() -> impl IntoView {
view! {
<div class="contact-list">
<h1>"Contacts"</h1>
<li><a href="/1">1</a></li>
<li><a href="/2">2</a></li>
<li><a href="/3">3</a></li>
{(route_data.outlet)()}
<li><a href="/contacts/1">1</a></li>
<li><a href="/contacts/2">2</a></li>
<li><a href="/contacts/3">3</a></li>
{route_data.outlet}
</div>
}
@ -215,14 +149,15 @@ pub fn ContactRoutes() -> impl IntoView {
</div>
}*/
}
/*
#[derive(Params, PartialEq, Clone, Debug)]
/*#[derive(Params, PartialEq, Clone, Debug)]
pub struct ContactParams {
// Params isn't implemented for usize, only Option<usize>
id: Option<usize>,
}*/
pub fn Contact(route_data: RouteData<Dom>) -> impl IntoView {
let params = route_data.params;
info!("rendering <Contact/>");
info!(
@ -237,6 +172,7 @@ pub fn Contact(route_data: RouteData<Dom>) -> impl IntoView {
view! {
<div class="contact">
<h2>"Contact"</h2>
{move || format!("{:#?}", params.get())}
</div>
}
@ -335,4 +271,4 @@ pub fn Settings(route_data: RouteData<Dom>) -> impl IntoView {
<pre>"This page is just a placeholder."</pre>
</form>
}
}*/
}

View file

@ -1,16 +1,16 @@
use leptos::*;
use router::*;
//use tracing_subscriber::fmt;
//use tracing_subscriber_wasm::MakeConsoleWriter;
use tracing_subscriber::fmt;
use tracing_subscriber_wasm::MakeConsoleWriter;
pub fn main() {
/*fmt()
fmt()
.with_writer(
MakeConsoleWriter::default()
.map_trace_level_to(tracing::Level::DEBUG),
)
.without_time()
.init();
console_error_panic_hook::set_once();*/
mount_to_body(|| view! { <RouterExample/> })
console_error_panic_hook::set_once();
mount_to_body(RouterExample);
}

View file

@ -169,7 +169,6 @@ impl Location for BrowserUrl {
fn search_params_from_web_url(
params: &web_sys::UrlSearchParams,
) -> Result<Params, JsValue> {
let mut search_params = Params::new();
try_iter(params)?
.into_iter()
.flatten()
@ -177,8 +176,8 @@ fn search_params_from_web_url(
pair.and_then(|pair| {
let row = pair.dyn_into::<Array>()?;
Ok((
row.get(0).dyn_into::<JsString>()?.into(),
row.get(1).dyn_into::<JsString>()?.into(),
String::from(row.get(0).dyn_into::<JsString>()?),
String::from(row.get(1).dyn_into::<JsString>()?),
))
})
})

View file

@ -5,7 +5,7 @@ use tachys::{renderer::Renderer, view::Render};
pub trait ChooseView<R>
where
Self: 'static,
R: Renderer,
R: Renderer + 'static,
{
type Output: Render<R>;
@ -16,7 +16,7 @@ impl<F, View, R> ChooseView<R> for F
where
F: Fn(RouteData<R>) -> View + 'static,
View: Render<R>,
R: Renderer,
R: Renderer + 'static,
{
type Output = View;
@ -27,7 +27,7 @@ where
impl<R> ChooseView<R> for ()
where
R: Renderer,
R: Renderer + 'static,
{
type Output = ();
@ -38,7 +38,7 @@ impl<A, B, Rndr> ChooseView<Rndr> for Either<A, B>
where
A: ChooseView<Rndr>,
B: ChooseView<Rndr>,
Rndr: Renderer,
Rndr: Renderer + 'static,
{
type Output = Either<A::Output, B::Output>;
@ -55,7 +55,7 @@ macro_rules! tuples {
impl<$($ty,)* Rndr> ChooseView<Rndr> for $either<$($ty,)*>
where
$($ty: ChooseView<Rndr>,)*
Rndr: Renderer,
Rndr: Renderer + 'static,
{
type Output = $either<$($ty::Output,)*>;

View file

@ -84,7 +84,7 @@ where
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct RouteMatchId(pub(crate) u8);
pub struct RouteMatchId(pub(crate) u16);
pub trait MatchInterface<R>
where

View file

@ -4,7 +4,8 @@ use super::{
};
use crate::{ChooseView, MatchParams, RouteData};
use core::{fmt, iter};
use std::{borrow::Cow, marker::PhantomData};
use std::{borrow::Cow, marker::PhantomData, any::Any, sync::atomic::{AtomicU16, Ordering}};
use either_of::Either;
use tachys::{
renderer::Renderer,
view::{Render, RenderHtml},
@ -12,10 +13,13 @@ use tachys::{
mod tuples;
static ROUTE_ID: AtomicU16 = AtomicU16::new(1);
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct NestedRoute<Segments, Children, Data, ViewFn, R> {
id: u16,
pub segments: Segments,
pub children: Children,
pub children: Option<Children>,
pub data: Data,
pub view: ViewFn,
pub rndr: PhantomData<R>,
@ -25,11 +29,12 @@ impl<Segments, ViewFn, R> NestedRoute<Segments, (), (), ViewFn, R> {
pub fn new<View>(path: Segments, view: ViewFn) -> Self
where
ViewFn: Fn(RouteData<R>) -> View,
R: Renderer,
R: Renderer + 'static,
{
Self {
id: ROUTE_ID.fetch_add(1, Ordering::Relaxed),
segments: path,
children: (),
children: None,
data: (),
view,
rndr: PhantomData,
@ -43,6 +48,7 @@ impl<Segments, Data, ViewFn, R> NestedRoute<Segments, (), Data, ViewFn, R> {
child: Children,
) -> NestedRoute<Segments, Children, Data, ViewFn, R> {
let Self {
id,
segments,
data,
view,
@ -50,8 +56,9 @@ impl<Segments, Data, ViewFn, R> NestedRoute<Segments, (), Data, ViewFn, R> {
..
} = self;
NestedRoute {
id,
segments,
children: child,
children: Some(child),
data,
view,
rndr,
@ -67,7 +74,7 @@ pub struct NestedMatch<ParamsIter, Child, ViewFn> {
/// The map of params matched only by this nested route.
params: ParamsIter,
/// The nested route.
child: Child,
child: Option<Child>,
view_fn: ViewFn,
}
@ -124,15 +131,16 @@ where
impl ChooseView<Rndr, Output = Self::View>,
Option<Self::Child>,
) {
(self.view_fn, Some(self.child))
(self.view_fn, self.child)
}
}
impl<Segments, Children, Data, ViewFn, View, Rndr> MatchNestedRoutes<Rndr>
for NestedRoute<Segments, Children, Data, ViewFn, Rndr>
where
Self: 'static,
Rndr: Renderer + 'static,
Segments: PossibleRouteMatch,
Segments: PossibleRouteMatch + std::fmt::Debug,
<<Segments as PossibleRouteMatch>::ParamsIter as IntoIterator>::IntoIter: Clone,
Children: MatchNestedRoutes<Rndr>,
<<<Children as MatchNestedRoutes<Rndr>>::Match as MatchParams>::Params as IntoIterator>::IntoIter: Clone,
@ -146,7 +154,9 @@ where
type View = View;
type Match = NestedMatch<iter::Chain<
<Segments::ParamsIter as IntoIterator>::IntoIter,
<<Children::Match as MatchParams>::Params as IntoIterator>::IntoIter,
Either<iter::Empty::<
(Cow<'static, str>, String)
>, <<Children::Match as MatchParams>::Params as IntoIterator>::IntoIter>
>, Children::Match, ViewFn>;
fn match_nested<'a>(
@ -161,10 +171,21 @@ where
params,
matched,
}| {
let (inner, remaining) =
self.children.match_nested(remaining);
let (id, inner) = inner?;
let (id, inner, remaining) = match &self.children {
None => (None, None, remaining),
Some(children) => {
let (inner, remaining) = children.match_nested(remaining);
let (id, inner) = inner?;
(Some(id), Some(inner), remaining)
}
};
let params = params.into_iter();
let inner_params = match &inner {
None => Either::Left(iter::empty()),
Some(inner) => Either::Right(inner.to_params().into_iter())
};
let id = RouteMatchId(self.id);
if remaining.is_empty() || remaining == "/" {
Some((
@ -173,7 +194,7 @@ where
NestedMatch {
id,
matched: matched.to_string(),
params: params.chain(inner.to_params()),
params: params.chain(inner_params),
child: inner,
view_fn: self.view.clone(),
},
@ -194,7 +215,7 @@ where
let mut segment_routes = Vec::new();
self.segments.generate_path(&mut segment_routes);
let segment_routes = segment_routes.into_iter();
let children_routes = self.children.generate_routes().into_iter();
let children_routes = self.children.as_ref().into_iter().flat_map(|child| child.generate_routes().into_iter());
children_routes.map(move |child_routes| {
segment_routes
.clone()

View file

@ -2,7 +2,7 @@ use super::{MatchInterface, MatchNestedRoutes, PathSegment, RouteMatchId};
use crate::{ChooseView, MatchParams};
use core::iter;
use either_of::*;
use std::borrow::Cow;
use std::{any::Any, borrow::Cow};
use tachys::renderer::Renderer;
impl MatchParams for () {
@ -73,14 +73,14 @@ where
impl<A, Rndr> MatchInterface<Rndr> for (A,)
where
A: MatchInterface<Rndr>,
A: MatchInterface<Rndr> + 'static,
Rndr: Renderer + 'static,
{
type Child = A::Child;
type View = A::View;
fn as_id(&self) -> RouteMatchId {
RouteMatchId(0)
self.0.as_id()
}
fn as_matched(&self) -> &str {
@ -99,7 +99,7 @@ where
impl<A, Rndr> MatchNestedRoutes<Rndr> for (A,)
where
A: MatchNestedRoutes<Rndr>,
A: MatchNestedRoutes<Rndr> + 'static,
Rndr: Renderer + 'static,
{
type Data = A::Data;
@ -149,8 +149,8 @@ where
fn as_id(&self) -> RouteMatchId {
match self {
Either::Left(_) => RouteMatchId(0),
Either::Right(_) => RouteMatchId(1),
Either::Left(i) => i.as_id(),
Either::Right(i) => i.as_id(),
}
}
@ -251,14 +251,14 @@ macro_rules! tuples {
impl<Rndr, $($ty,)*> MatchInterface<Rndr> for $either <$($ty,)*>
where
Rndr: Renderer + 'static,
$($ty: MatchInterface<Rndr>),*,
$($ty: MatchInterface<Rndr> + 'static),*,
{
type Child = $either<$($ty::Child,)*>;
type View = $either<$($ty::View,)*>;
fn as_id(&self) -> RouteMatchId {
match self {
$($either::$ty(_) => RouteMatchId($count),)*
$($either::$ty(i) => i.as_id(),)*
}
}
@ -286,7 +286,7 @@ macro_rules! tuples {
impl<Rndr, $($ty),*> MatchNestedRoutes<Rndr> for ($($ty,)*)
where
Rndr: Renderer + 'static,
$($ty: MatchNestedRoutes<Rndr>),*,
$($ty: MatchNestedRoutes<Rndr> + 'static),*,
{
type Data = ($($ty::Data,)*);
type View = $either<$($ty::View,)*>;
@ -296,11 +296,8 @@ macro_rules! tuples {
#[allow(non_snake_case)]
let ($($ty,)*) = &self;
let mut id = 0;
$(if let (Some((_, matched)), remaining) = $ty.match_nested(path) {
return (Some((RouteMatchId(id), $either::$ty(matched))), remaining);
} else {
id += 1;
return (Some((RouteMatchId($count), $either::$ty(matched))), remaining);
})*
(None, path)
}

View file

@ -1,5 +1,7 @@
use std::borrow::Cow;
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)]
pub struct Params(Vec<(String, String)>);
pub struct Params(Vec<(Cow<'static, str>, String)>);
impl Params {
pub fn new() -> Self {
@ -7,8 +9,16 @@ impl Params {
}
}
impl FromIterator<(String, String)> for Params {
fn from_iter<T: IntoIterator<Item = (String, String)>>(iter: T) -> Self {
Self(iter.into_iter().collect())
impl<K, V> FromIterator<(K, V)> for Params
where
K: Into<Cow<'static, str>>,
V: Into<String>,
{
fn from_iter<T: IntoIterator<Item = (K, V)>>(iter: T) -> Self {
Self(
iter.into_iter()
.map(|(k, v)| (k.into(), v.into()))
.collect(),
)
}
}

View file

@ -14,9 +14,11 @@ use reactive_graph::{
effect::RenderEffect,
owner::Owner,
signal::ArcRwSignal,
traits::{Get, Read, Track},
traits::{Get, Read, Set, Track},
};
use std::{
any::Any, borrow::Cow, cell::RefCell, collections::VecDeque, rc::Rc,
};
use std::borrow::Cow;
use tachys::{
html::attribute::Attribute,
hydration::Cursor,
@ -24,7 +26,7 @@ use tachys::{
ssr::StreamBuilder,
view::{
add_attr::AddAnyAttr,
any_view::{AnyView, IntoAny},
any_view::{AnyView, AnyViewState, IntoAny},
either::EitherState,
Mountable, Position, PositionState, Render, RenderHtml,
},
@ -45,7 +47,6 @@ where
Rndr: Renderer,
FallbackFn: Fn() -> Fallback,
{
#[inline(always)]
pub fn new(
location: Loc,
routes: Routes<Children, Rndr>,
@ -59,7 +60,6 @@ where
}
}
#[inline(always)]
pub fn new_with_base(
base: impl Into<Cow<'static, str>>,
location: Loc,
@ -88,10 +88,10 @@ where
pub struct RouteData<R>
where
R: Renderer,
R: Renderer + 'static,
{
pub params: ArcMemo<Params>,
pub outlet: Box<dyn FnOnce() -> AnyView<R>>,
pub outlet: Outlet<R>,
}
impl<Rndr, Loc, FallbackFn, Fallback, Children> Render<Rndr>
@ -106,6 +106,8 @@ where
View::State: 'static,*/
Fallback::State: 'static,
Rndr: Renderer + 'static,
Children::Match: std::fmt::Debug,
<Children::Match as MatchInterface<Rndr>>::Child: std::fmt::Debug,
{
type State = RenderEffect<
EitherState<
@ -139,15 +141,13 @@ where
match &mut prev.state {
Either::Left(prev) => {
// TODO!
//nested_rebuild(&outer_owner, prev, new_match);
rebuild_nested(&outer_owner, prev, new_match);
}
Either::Right(_) => {
Either::<_, Fallback>::Left(
NestedRouteView::create(
&outer_owner,
new_match,
),
)
Either::<_, Fallback>::Left(NestedRouteView::new(
&outer_owner,
new_match,
))
.rebuild(&mut prev);
}
}
@ -160,7 +160,7 @@ where
prev
} else {
match new_match {
Some(matched) => Either::Left(NestedRouteView::create(
Some(matched) => Either::Left(NestedRouteView::new(
&outer_owner,
matched,
)),
@ -193,9 +193,8 @@ where
id: RouteMatchId,
owner: Owner,
params: ArcRwSignal<Params>,
matched: ArcRwSignal<String>,
outlets: VecDeque<Outlet<R>>,
view: Matcher::View,
//child: Option<Box<dyn FnOnce() -> NestedRouteView<Matcher::Child, R>>>,
ty: PhantomData<(Matcher, R)>,
}
@ -206,30 +205,35 @@ where
Matcher::View: 'static,
Rndr: Renderer + 'static,
{
pub fn create(outer_owner: &Owner, route_match: Matcher) -> Self {
let id = route_match.as_id();
pub fn new(outer_owner: &Owner, route_match: Matcher) -> Self {
// keep track of all outlets, for diffing
let mut outlets = VecDeque::new();
// build this view
let owner = outer_owner.child();
let params = ArcRwSignal::new(
route_match
.to_params()
.into_iter()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect(),
);
let matched = ArcRwSignal::new(route_match.as_matched().to_string());
/*let (view, child) = route_match.into_view_and_child();
let child = child.map(|child| {
let owner = owner.clone();
Box::new(move || NestedRouteView::create(&owner, child))
as Box<dyn FnOnce() -> NestedRouteView<Matcher::Child, Rndr>>
});*/
let view = build_nested(route_match);
let id = route_match.as_id();
let params =
ArcRwSignal::new(route_match.to_params().into_iter().collect());
let (view, child) = route_match.into_view_and_child();
let outlet = child
.map(|child| get_inner_view(&mut outlets, &owner, child))
.unwrap_or_default();
let route_data = RouteData {
params: ArcMemo::new({
let params = params.clone();
move |_| params.get()
}),
outlet,
};
let view = view.choose(route_data);
Self {
id,
owner,
params,
matched,
outlets,
view,
ty: PhantomData,
}
@ -244,27 +248,301 @@ where
id: RouteMatchId,
owner: Owner,
params: ArcRwSignal<Params>,
matched: ArcRwSignal<String>,
view: <Matcher::View as Render<Rndr>>::State,
//child: Option<Box<NestedRouteState<Matcher::Child, Rndr>>>,
outlets: VecDeque<Outlet<Rndr>>,
}
// TODO: also build a Vec<(RouteMatchId, ArcRwSignal<Params>)>
// when we rebuild, at each level,
// if the route IDs don't match, then replace with new
// if they do match, then just update params
fn build_nested<Match, R>(route_match: Match) -> Match::View
fn get_inner_view<Match, R>(
outlets: &mut VecDeque<Outlet<R>>,
parent: &Owner,
route_match: Match,
) -> Outlet<R>
where
Match: MatchInterface<R> + MatchParams,
R: Renderer + 'static,
{
let owner = parent.child();
let id = route_match.as_id();
let params =
ArcRwSignal::new(route_match.to_params().into_iter().collect());
let state = Rc::new(RefCell::new(None));
let (view, child) = route_match.into_view_and_child();
let outlet = child
.map(|child| get_inner_view(outlets, &owner, child))
.unwrap_or_default();
let fun = Rc::new(RefCell::new(Some({
let params = params.clone();
Box::new(move || {
view.choose(RouteData {
params: ArcMemo::new(move |_| params.get()),
outlet,
})
.into_any()
}) as Box<dyn FnOnce() -> AnyView<R>>
})));
let outlet = Outlet {
id,
owner,
params,
state,
fun,
};
outlets.push_back(outlet.clone());
outlet
}
pub struct Outlet<R>
where
R: Renderer + 'static,
{
id: RouteMatchId,
owner: Owner,
params: ArcRwSignal<Params>,
state: Rc<RefCell<Option<OutletStateInner<R>>>>,
fun: Rc<RefCell<Option<Box<dyn FnOnce() -> AnyView<R>>>>>,
}
impl<R> Clone for Outlet<R>
where
R: Renderer + 'static,
{
fn clone(&self) -> Self {
Self {
id: self.id,
owner: self.owner.clone(),
params: self.params.clone(),
state: Rc::clone(&self.state),
fun: Rc::clone(&self.fun),
}
}
}
impl<R> Default for Outlet<R>
where
R: Renderer + 'static,
{
fn default() -> Self {
Self {
id: RouteMatchId(0),
owner: Owner::current().unwrap(),
params: ArcRwSignal::new(Params::new()),
state: Default::default(),
fun: Rc::new(RefCell::new(Some(Box::new(|| ().into_any())))),
}
}
}
impl<R> Render<R> for Outlet<R>
where
R: Renderer + 'static,
{
type State = OutletState<R>;
type FallibleState = ();
fn build(self) -> Self::State {
let Outlet {
id,
owner,
state,
params,
fun,
} = self;
let fun = fun
.borrow_mut()
.take()
.expect("Outlet function taken before being built");
let this = fun();
*state.borrow_mut() = Some(OutletStateInner {
state: this.build(),
});
OutletState {
id,
owner,
state,
params,
}
}
fn rebuild(self, state: &mut Self::State) {
todo!()
}
fn try_build(self) -> tachys::error::Result<Self::FallibleState> {
todo!()
}
fn try_rebuild(
self,
state: &mut Self::FallibleState,
) -> tachys::error::Result<()> {
todo!()
}
}
impl<R> RenderHtml<R> for Outlet<R>
where
R: Renderer + 'static,
{
const MIN_LENGTH: usize = 0; // TODO
fn to_html_with_buf(self, buf: &mut String, position: &mut Position) {
todo!()
}
fn hydrate<const FROM_SERVER: bool>(
self,
cursor: &Cursor<R>,
position: &PositionState,
) -> Self::State {
todo!()
}
}
pub struct OutletState<R>
where
R: Renderer + 'static,
{
id: RouteMatchId,
owner: Owner,
params: ArcRwSignal<Params>,
state: Rc<RefCell<Option<OutletStateInner<R>>>>,
}
pub struct OutletStateInner<R>
where
R: Renderer + 'static,
{
state: AnyViewState<R>,
}
impl<R> Mountable<R> for OutletState<R>
where
R: Renderer + 'static,
{
fn unmount(&mut self) {
self.state
.borrow_mut()
.as_mut()
.expect("tried to access OutletState before it was built")
.state
.unmount();
}
fn mount(
&mut self,
parent: &<R as Renderer>::Element,
marker: Option<&<R as Renderer>::Node>,
) {
self.state
.borrow_mut()
.as_mut()
.expect("tried to access OutletState before it was built")
.state
.mount(parent, marker);
}
fn insert_before_this(
&self,
parent: &<R as Renderer>::Element,
child: &mut dyn Mountable<R>,
) -> bool {
self.state
.borrow_mut()
.as_mut()
.expect("tried to access OutletState before it was built")
.state
.insert_before_this(parent, child)
}
}
struct OutletData<R>
where
Match: MatchInterface<R>,
R: Renderer,
{
let (view, child) = route_match.into_view_and_child();
let outlet = move || child.map(|child| build_nested(child)).into_any();
let data = RouteData {
params: { ArcMemo::new(move |_| Params::new()) },
outlet: Box::new(outlet),
};
view.choose(data)
trigger: ArcRwSignal<()>,
child: Rc<RefCell<Option<Box<dyn Any>>>>,
fun: Box<dyn FnOnce() -> AnyView<R>>,
}
fn rebuild_nested<Match, R>(
outer_owner: &Owner,
prev: &mut NestedRouteState<Match, R>,
new_match: Match,
) where
Match: MatchInterface<R> + MatchParams,
R: Renderer + 'static,
{
let mut items = 1;
let NestedRouteState {
id,
owner,
params,
view,
outlets,
} = prev;
if new_match.as_id() == *id {
params.set(new_match.to_params().into_iter().collect::<Params>());
let (_, child) = new_match.into_view_and_child();
if let Some(child) = child {
rebuild_inner(&mut items, outlets, child);
} else {
outlets.truncate(items);
}
} else {
let new = NestedRouteView::new(outer_owner, new_match);
new.rebuild(prev);
}
}
fn rebuild_inner<Match, R>(
items: &mut usize,
outlets: &mut VecDeque<Outlet<R>>,
route_match: Match,
) where
Match: MatchInterface<R> + MatchParams,
R: Renderer + 'static,
{
*items += 1;
match outlets.pop_front() {
None => todo!(),
Some(prev) => {
let prev_id = prev.id;
let new_id = route_match.as_id();
if new_id == prev_id {
// this is the same route, but the params may have changed
// so we'll just update that params value
prev.params.set(
route_match.to_params().into_iter().collect::<Params>(),
);
outlets.push_front(prev);
let (_, child) = route_match.into_view_and_child();
if let Some(child) = child {
// we still recurse to the children, because they may also have changed
rebuild_inner(items, outlets, child);
} else {
outlets.truncate(*items);
}
} else {
// if different routes are matched here, it means the rest of the tree is no longer
// matched either
outlets.truncate(*items);
// we'll build a fresh tree instead
// TODO check parent logic here...
let new_outlet =
get_inner_view(outlets, &prev.owner, route_match);
let fun = new_outlet.fun.borrow_mut().take().unwrap();
let new_view = fun();
new_view.rebuild(
&mut prev.state.borrow_mut().as_mut().unwrap().state,
);
}
}
}
}
impl<Matcher, R> Render<R> for NestedRouteView<Matcher, R>
@ -277,17 +555,37 @@ where
type FallibleState = ();
fn build(self) -> Self::State {
let NestedRouteView {
id,
owner,
params,
outlets,
view,
ty,
} = self;
NestedRouteState {
id: self.id,
owner: self.owner,
params: self.params,
matched: self.matched,
view: self.view.build(), //child: None,
id,
owner,
outlets,
params,
view: view.build(),
}
}
fn rebuild(self, state: &mut Self::State) {
todo!()
let NestedRouteView {
id,
owner,
params,
outlets,
view,
ty,
} = self;
state.id = id;
state.owner = owner;
state.params = params;
state.outlets = outlets;
view.rebuild(&mut state.view);
}
fn try_build(self) -> tachys::error::Result<Self::FallibleState> {
@ -495,6 +793,8 @@ where
View::State: 'static,*/
Fallback::State: 'static,
Rndr: Renderer + 'static,
Children::Match: std::fmt::Debug,
<Children::Match as MatchInterface<Rndr>>::Child: std::fmt::Debug,
{
// TODO probably pick a max length here
const MIN_LENGTH: usize = Fallback::MIN_LENGTH;