mirror of
https://github.com/leptos-rs/leptos
synced 2024-11-10 06:44:17 +00:00
navigation between nested routes
This commit is contained in:
parent
13cccced06
commit
16bd2942db
11 changed files with 450 additions and 187 deletions
|
@ -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"]
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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>
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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>()?),
|
||||
))
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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,)*>;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue