continuing on nested routes

This commit is contained in:
Greg Johnston 2024-04-20 15:47:25 -04:00
parent acec3bb313
commit ebdd31cd9f
12 changed files with 421 additions and 158 deletions

View file

@ -10,10 +10,13 @@ use leptos::{
view, IntoView,
};
use log::{debug, info};
use routing::{
components::{ParentRoute, Route, Router},
Outlet,
};
use routing::{
location::{BrowserUrl, Location},
MatchNestedRoutes, NestedRoute, ParamSegment, RouteData, Router, Routes,
StaticSegment,
MatchNestedRoutes, NestedRoute, ParamSegment, StaticSegment,
};
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
@ -26,8 +29,7 @@ pub fn RouterExample() -> impl IntoView {
// contexts are passed down through the route tree
provide_context(ExampleContext(0));
let router = Router::new(
BrowserUrl::new().unwrap(),
/*let router = Router::new(
Routes::new((
NestedRoute::new(StaticSegment("contacts"), ContactList).child((
NestedRoute::new(StaticSegment(""), |_| "Select a contact."),
@ -40,7 +42,7 @@ pub fn RouterExample() -> impl IntoView {
NestedRoute::new(StaticSegment("about"), About),
)),
|| "This page could not be found.",
);
);*/
view! {
<nav>
@ -48,58 +50,26 @@ pub fn RouterExample() -> impl IntoView {
// using <A> has two effects:
// 1) ensuring that relative routing works properly for nested routes
// 2) setting the `aria-current` attribute on the current link,
// for a11y and styling purposes
/*
<A exact=true href="/">"Contacts"</A>
<A href="about">"About"</A>
<A href="settings">"Settings"</A>
<A href="redirect-home">"Redirect to Home"</A>
*/
// for a11y and styling purposes
<a href="/contacts">"Contacts"</a>
<a href="/about">"About"</a>
<a href="/settings">"Settings"</a>
<a href="/redirect-home">"Redirect to Home"</a>
</nav>
{router}
/*<Router>
<nav>
// ordinary <a> elements can be used for client-side navigation
// using <A> has two effects:
// 1) ensuring that relative routing works properly for nested routes
// 2) setting the `aria-current` attribute on the current link,
// for a11y and styling purposes
<A exact=true href="/">"Contacts"</A>
<A href="about">"About"</A>
<A href="settings">"Settings"</A>
<A href="redirect-home">"Redirect to Home"</A>
</nav>
<main>
<AnimatedRoutes
outro="slideOut"
intro="slideIn"
outro_back="slideOutBack"
intro_back="slideInBack"
>
<ContactRoutes/>
<Route
path="about"
view=|| view! { <About/> }
/>
<Route
path="settings"
view=|| view! { <Settings/> }
/>
<Route
path="redirect-home"
view=|| view! { <Redirect path="/"/> }
/>
</AnimatedRoutes>
</main>
</Router>*/
<Router fallback=|| "This page could not be found.">
<ParentRoute path=StaticSegment("contacts") view=ContactList>
<Route path=StaticSegment("") view=|| "Select a contact."/>
<Route path=ParamSegment(":id") view=Contact/>
</ParentRoute>
<Route path=StaticSegment("settings") view=Settings/>
<Route path=StaticSegment("about") view=About/>
</Router>
}
}
pub fn ContactList(route_data: RouteData<Dom>) -> impl IntoView {
#[component]
pub fn ContactList() -> impl IntoView {
info!("rendering <ContactList/>");
// contexts are passed down through the route tree
@ -112,10 +82,16 @@ pub fn ContactList(route_data: RouteData<Dom>) -> impl IntoView {
view! {
<div class="contact-list">
<h1>"Contacts"</h1>
<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}
<li>
<a href="/contacts/1">1</a>
</li>
<li>
<a href="/contacts/2">2</a>
</li>
<li>
<a href="/contacts/3">3</a>
</li>
<Outlet/>
</div>
}
@ -156,8 +132,7 @@ pub struct ContactParams {
id: Option<usize>,
}*/
pub fn Contact(route_data: RouteData<Dom>) -> impl IntoView {
let params = route_data.params;
pub fn Contact() -> impl IntoView {
info!("rendering <Contact/>");
info!(
@ -172,12 +147,12 @@ pub fn Contact(route_data: RouteData<Dom>) -> impl IntoView {
view! {
<div class="contact">
<h2>"Contact"</h2>
{move || format!("{:#?}", params.get())}
// {move || format!("{:#?}", params.get())}
</div>
}
/* let params = use_params::<ContactParams>();
let contact = create_resource(
//let params = use_params::<ContactParams>();
/*let contact = create_resource(
move || {
params
.get()
@ -191,7 +166,7 @@ pub fn Contact(route_data: RouteData<Dom>) -> impl IntoView {
get_contact,
);
create_effect(move |_| {
Effect::new(move |_| {
info!("params = {:#?}", params.get());
});
@ -208,7 +183,7 @@ pub fn Contact(route_data: RouteData<Dom>) -> impl IntoView {
view! {
<section class="card">
<h1>{contact.first_name} " " {contact.last_name}</h1>
<p>{contact.address_1}<br/>{contact.address_2}</p>
<p>{contact.address_1} <br/> {contact.address_2}</p>
</section>
}
.into_any(),
@ -217,14 +192,15 @@ pub fn Contact(route_data: RouteData<Dom>) -> impl IntoView {
view! {
<div class="contact">
<Transition fallback=move || view! { <p>"Loading..."</p> }>
{contact_display}
</Transition>
<Transition fallback=move || {
view! { <p>"Loading..."</p> }
}>{contact_display}</Transition>
</div>
}*/
}
pub fn About(route_data: RouteData<Dom>) -> impl IntoView {
#[component]
pub fn About() -> impl IntoView {
info!("rendering <About/>");
Owner::on_cleanup(|| {
@ -241,19 +217,15 @@ pub fn About(route_data: RouteData<Dom>) -> impl IntoView {
// let navigate = use_navigate();
view! {
// note: this is just an illustration of how to use `use_navigate`
// <button on:click> to navigate is an *anti-pattern*
// you should ordinarily use a link instead,
// both semantically and so your link will work before WASM loads
/*<button on:click=move |_| navigate("/", Default::default())>
"Home"
</button>*/
<h1>"About"</h1>
<p>"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."</p>
<p>
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
</p>
}
}
pub fn Settings(route_data: RouteData<Dom>) -> impl IntoView {
#[component]
pub fn Settings() -> impl IntoView {
info!("rendering <Settings/>");
Owner::on_cleanup(|| {

View file

@ -4,13 +4,13 @@ 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();*/
console_error_panic_hook::set_once();
mount_to_body(RouterExample);
}

View file

@ -1,5 +1,6 @@
mod arc_read;
mod arc_rw;
mod arc_trigger;
mod arc_write;
pub mod guards;
mod read;
@ -9,6 +10,7 @@ mod write;
pub use arc_read::*;
pub use arc_rw::*;
pub use arc_trigger::*;
pub use arc_write::*;
pub use read::*;
pub use rw::*;

View file

@ -0,0 +1,86 @@
use super::subscriber_traits::AsSubscriberSet;
use crate::{
graph::{ReactiveNode, Source, SubscriberSet},
traits::{DefinedAt, IsDisposed, Track, Trigger},
};
use std::{
fmt::{Debug, Formatter, Result},
panic::Location,
sync::{Arc, RwLock},
};
pub struct ArcTrigger {
#[cfg(debug_assertions)]
pub(crate) defined_at: &'static Location<'static>,
pub(crate) inner: Arc<RwLock<SubscriberSet>>,
}
impl ArcTrigger {
#[track_caller]
pub fn new() -> Self {
Self {
#[cfg(debug_assertions)]
defined_at: Location::caller(),
inner: Default::default(),
}
}
}
impl Default for ArcTrigger {
fn default() -> Self {
Self::new()
}
}
impl Clone for ArcTrigger {
#[track_caller]
fn clone(&self) -> Self {
Self {
#[cfg(debug_assertions)]
defined_at: self.defined_at,
inner: Arc::clone(&self.inner),
}
}
}
impl Debug for ArcTrigger {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
f.debug_struct("ArcTrigger").finish()
}
}
impl IsDisposed for ArcTrigger {
#[inline(always)]
fn is_disposed(&self) -> bool {
false
}
}
impl AsSubscriberSet for ArcTrigger {
type Output = Arc<RwLock<SubscriberSet>>;
#[inline(always)]
fn as_subscriber_set(&self) -> Option<Self::Output> {
Some(Arc::clone(&self.inner))
}
}
impl DefinedAt for ArcTrigger {
#[inline(always)]
fn defined_at(&self) -> Option<&'static Location<'static>> {
#[cfg(debug_assertions)]
{
Some(self.defined_at)
}
#[cfg(not(debug_assertions))]
{
None
}
}
}
impl Trigger for ArcTrigger {
fn trigger(&self) {
self.inner.mark_dirty();
}
}

View file

@ -1,9 +1,10 @@
use crate::{
location::BrowserUrl, matching, router, FlatRouter, NestedRoute, RouteData,
Router, Routes,
location::{BrowserUrl, Location},
MatchNestedRoutes, NestedRoute, NestedRoutesView, Routes,
};
use leptos::{children::ToChildren, component};
use std::borrow::Cow;
use leptos::{children::ToChildren, component, IntoView};
use reactive_graph::{computed::ArcMemo, owner::Owner, traits::Read};
use std::{borrow::Cow, marker::PhantomData};
use tachys::renderer::dom::Dom;
#[derive(Debug)]
@ -23,7 +24,7 @@ where
RouteChildren(f())
}
}
/*
#[component]
pub fn FlatRouter<Children, FallbackFn, Fallback>(
#[prop(optional, into)] base: Option<Cow<'static, str>>,
@ -39,22 +40,43 @@ where
} else {
FlatRouter::new(children, fallback)
}
}
}*/
#[component]
pub fn Router<Children, FallbackFn, Fallback>(
pub fn Router<Defs, FallbackFn, Fallback>(
#[prop(optional, into)] base: Option<Cow<'static, str>>,
fallback: FallbackFn,
children: RouteChildren<Children>,
) -> Router<Dom, BrowserUrl, Children, FallbackFn>
children: RouteChildren<Defs>,
) -> impl IntoView
where
FallbackFn: Fn() -> Fallback,
Defs: MatchNestedRoutes<Dom> + Clone + Send + 'static,
FallbackFn: Fn() -> Fallback + Send + 'static,
Fallback: IntoView + 'static,
{
let children = Routes::new(children.into_inner());
if let Some(base) = base {
Router::new_with_base(base, children, fallback)
} else {
Router::new(children, fallback)
let routes = Routes::new(children.into_inner());
let location =
BrowserUrl::new().expect("could not access browser navigation"); // TODO options here
location.init(base.clone());
let url = location.as_url().clone();
let path = ArcMemo::new({
let url = url.clone();
move |_| url.read().path().to_string()
});
let search_params = ArcMemo::new({
let url = url.clone();
move |_| url.read().search_params().clone()
});
let outer_owner =
Owner::current().expect("creating Router, but no Owner was found");
move || NestedRoutesView {
routes: routes.clone(),
outer_owner: outer_owner.clone(),
url: url.clone(),
path: path.clone(),
search_params: search_params.clone(),
base: base.clone(), // TODO is this necessary?
fallback: fallback(),
rndr: PhantomData,
}
}
@ -64,7 +86,20 @@ pub fn Route<Segments, View, ViewFn>(
view: ViewFn,
) -> NestedRoute<Segments, (), (), ViewFn, Dom>
where
ViewFn: Fn(RouteData<Dom>) -> View,
ViewFn: Fn() -> View,
{
NestedRoute::new(path, view)
}
#[component]
pub fn ParentRoute<Segments, View, Children, ViewFn>(
path: Segments,
view: ViewFn,
children: RouteChildren<Children>,
) -> NestedRoute<Segments, Children, (), ViewFn, Dom>
where
ViewFn: Fn() -> View,
{
let children = children.into_inner();
NestedRoute::new(path, view).child(children)
}

View file

@ -1,4 +1,4 @@
//pub mod components;
pub mod components;
mod generate_route_list;
pub mod location;
mod matching;

View file

@ -1,4 +1,3 @@
use crate::RouteData;
use either_of::*;
use tachys::{renderer::Renderer, view::Render};
@ -9,19 +8,19 @@ where
{
type Output: Render<R> + Send;
fn choose(self, route_data: RouteData<R>) -> Self::Output;
fn choose(self) -> Self::Output;
}
impl<F, View, R> ChooseView<R> for F
where
F: Fn(RouteData<R>) -> View + Send + 'static,
F: Fn() -> View + Send + 'static,
View: Render<R> + Send,
R: Renderer + 'static,
{
type Output = View;
fn choose(self, route_data: RouteData<R>) -> Self::Output {
self(route_data)
fn choose(self) -> Self::Output {
self()
}
}
@ -31,7 +30,7 @@ where
{
type Output = ();
fn choose(self, _route_data: RouteData<R>) -> Self::Output {}
fn choose(self) -> Self::Output {}
}
impl<A, B, Rndr> ChooseView<Rndr> for Either<A, B>
@ -42,10 +41,10 @@ where
{
type Output = Either<A::Output, B::Output>;
fn choose(self, route_data: RouteData<Rndr>) -> Self::Output {
fn choose(self) -> Self::Output {
match self {
Either::Left(f) => Either::Left(f.choose(route_data)),
Either::Right(f) => Either::Right(f.choose(route_data)),
Either::Left(f) => Either::Left(f.choose()),
Either::Right(f) => Either::Right(f.choose()),
}
}
}
@ -59,9 +58,9 @@ macro_rules! tuples {
{
type Output = $either<$($ty::Output,)*>;
fn choose(self, route_data: RouteData<Rndr>) -> Self::Output {
fn choose(self ) -> Self::Output {
match self {
$($either::$ty(f) => $either::$ty(f.choose(route_data)),)*
$($either::$ty(f) => $either::$ty(f.choose()),)*
}
}
}

View file

@ -21,6 +21,19 @@ pub struct Routes<Children, Rndr> {
ty: PhantomData<Rndr>,
}
impl<Children, Rndr> Clone for Routes<Children, Rndr>
where
Children: Clone,
{
fn clone(&self) -> Self {
Self {
base: self.base.clone(),
children: self.children.clone(),
ty: PhantomData,
}
}
}
impl<Children, Rndr> Routes<Children, Rndr> {
pub fn new(children: Children) -> Self {
Self {

View file

@ -2,9 +2,9 @@ use super::{
MatchInterface, MatchNestedRoutes, PartialPathMatch, PathSegment,
PossibleRouteMatch, RouteMatchId,
};
use crate::{ChooseView, MatchParams, RouteData};
use crate::{ChooseView, MatchParams};
use core::{fmt, iter};
use std::{borrow::Cow, marker::PhantomData, any::Any, sync::atomic::{AtomicU16, Ordering}};
use std::{borrow::Cow, marker::PhantomData, sync::atomic::{AtomicU16, Ordering}};
use either_of::Either;
use tachys::{
renderer::Renderer,
@ -15,7 +15,7 @@ mod tuples;
static ROUTE_ID: AtomicU16 = AtomicU16::new(1);
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[derive(Debug, Copy, PartialEq, Eq)]
pub struct NestedRoute<Segments, Children, Data, ViewFn, R> {
id: u16,
pub segments: Segments,
@ -25,10 +25,18 @@ pub struct NestedRoute<Segments, Children, Data, ViewFn, R> {
pub rndr: PhantomData<R>,
}
impl<Segments, Children, Data, ViewFn, R> Clone for NestedRoute<Segments, Children, Data, ViewFn, R> where Segments: Clone, Children: Clone, Data: Clone, ViewFn: Clone{
fn clone(&self) -> Self {
Self {
id: self.id,segments: self.segments.clone(),children: self.children.clone(),data: self.data.clone(), view: self.view.clone(), rndr: PhantomData
}
}
}
impl<Segments, ViewFn, R> NestedRoute<Segments, (), (), ViewFn, R> {
pub fn new<View>(path: Segments, view: ViewFn) -> Self
where
ViewFn: Fn(RouteData<R>) -> View,
ViewFn: Fn() -> View,
R: Renderer + 'static,
{
Self {
@ -111,7 +119,7 @@ impl<ParamsIter, Child, ViewFn, View, Rndr> MatchInterface<Rndr>
where
Rndr: Renderer + 'static,
Child: MatchInterface<Rndr> + MatchParams + 'static,
ViewFn: Fn(RouteData<Rndr>) -> View + Send + 'static,
ViewFn: Fn() -> View + Send + 'static,
View: Render<Rndr> + RenderHtml<Rndr> + Send + 'static,
{
type Child = Child;
@ -147,7 +155,7 @@ where
Children::Match: MatchParams,
Children: 'static,
<Children::Match as MatchParams>::Params: Clone,
ViewFn: Fn(RouteData<Rndr>) -> View + Send + Clone + 'static,
ViewFn: Fn() -> View + Send + Clone + 'static,
View: Render<Rndr> + RenderHtml<Rndr> + Send + 'static,
{
type Data = Data;

View file

@ -5,11 +5,13 @@ use crate::{
RouteMatchId,
};
use either_of::Either;
use leptos::{component, IntoView};
use or_poisoned::OrPoisoned;
use reactive_graph::{
computed::ArcMemo,
owner::{provide_context, Owner},
owner::{provide_context, use_context, Owner},
signal::{ArcRwSignal, ArcTrigger},
traits::{Read, Trigger},
traits::{Read, Set, Track, Trigger},
};
use std::{
borrow::Cow,
@ -27,50 +29,40 @@ use tachys::{
view::{
any_view::{AnyView, AnyViewState, IntoAny},
either::EitherState,
Mountable, Render,
Mountable, Render, RenderHtml,
},
};
pub struct RouteData<R = Dom>
where
R: Renderer + 'static,
{
pub params: ArcMemo<Params>,
pub outlet: Outlet<R>,
}
pub struct Outlet<R> {
rndr: PhantomData<R>,
}
pub struct NestedRoutesView<Loc, Defs, Fal, R> {
routes: Routes<Defs, R>,
outer_owner: Owner,
url: ArcRwSignal<Url>,
path: ArcMemo<String>,
search_params: ArcMemo<Params>,
base: Option<Cow<'static, str>>,
fallback: Fal,
loc: PhantomData<Loc>,
rndr: PhantomData<R>,
pub(crate) struct NestedRoutesView<Defs, Fal, R> {
pub routes: Routes<Defs, R>,
pub outer_owner: Owner,
pub url: ArcRwSignal<Url>,
pub path: ArcMemo<String>,
pub search_params: ArcMemo<Params>,
pub base: Option<Cow<'static, str>>,
pub fallback: Fal,
pub rndr: PhantomData<R>,
}
pub struct NestedRouteViewState<Fal, R>
where
Fal: Render<R>,
R: Renderer,
R: Renderer + 'static,
{
outer_owner: Owner,
url: ArcRwSignal<Url>,
path: ArcMemo<String>,
search_params: ArcMemo<Params>,
outlets: VecDeque<OutletContext<R>>,
view: EitherState<Fal::State, (), R>,
view: EitherState<Fal::State, AnyViewState<R>, R>,
}
impl<Loc, Defs, Fal, R> Render<R> for NestedRoutesView<Loc, Defs, Fal, R>
impl<Defs, Fal, R> Render<R> for NestedRoutesView<Defs, Fal, R>
where
Loc: Location,
Defs: MatchNestedRoutes<R>,
Fal: Render<R>,
R: Renderer + 'static,
@ -95,7 +87,8 @@ where
None => Either::Left(fallback),
Some(route) => {
route.build_nested_route(&mut outlets, &outer_owner);
Either::Right(())
provide_context(outlets[1].clone());
Either::Right(Outlet(OutletProps::builder().build()).into_any())
}
}
.build();
@ -113,20 +106,59 @@ where
fn rebuild(self, state: &mut Self::State) {
let new_match = self.routes.match_route(&self.path.read());
// TODO handle fallback => real view, fallback => fallback
match new_match {
None => {
Either::<Fal, ()>::Left(self.fallback).rebuild(&mut state.view);
Either::<Fal, AnyView<R>>::Left(self.fallback)
.rebuild(&mut state.view);
state.outlets.clear();
}
Some(route) => {
todo!()
route.rebuild_nested_route(
&mut 0,
&mut state.outlets,
&self.outer_owner,
);
}
}
}
}
impl<Defs, Fal, R> RenderHtml<R> for NestedRoutesView<Defs, Fal, R>
where
Defs: MatchNestedRoutes<R> + Send,
Fal: RenderHtml<R>,
R: Renderer + 'static,
{
type AsyncOutput = Self;
const MIN_LENGTH: usize = 0; // TODO
async fn resolve(self) -> Self::AsyncOutput {
self
}
fn to_html_with_buf(
self,
buf: &mut String,
position: &mut tachys::view::Position,
) {
todo!()
}
fn hydrate<const FROM_SERVER: bool>(
self,
cursor: &tachys::hydration::Cursor<R>,
position: &tachys::view::PositionState,
) -> Self::State {
todo!()
}
}
type OutletViewFn<R> = Box<dyn FnOnce() -> AnyView<R> + Send>;
#[derive(Debug)]
pub struct OutletContext<R>
where
R: Renderer,
@ -164,6 +196,13 @@ where
outlets: &mut VecDeque<OutletContext<R>>,
parent: &Owner,
);
fn rebuild_nested_route(
self,
items: &mut usize,
outlets: &mut VecDeque<OutletContext<R>>,
parent: &Owner,
);
}
impl<Match, R> AddNestedRoute<R> for Match
@ -179,6 +218,7 @@ where
let owner = parent.child();
let id = self.as_id();
let params = ArcRwSignal::new(self.to_params().into_iter().collect());
let current_child = outlets.len();
let (tx, rx) = std::sync::mpsc::channel();
@ -186,15 +226,12 @@ where
id,
trigger: ArcTrigger::new(),
params,
owner: owner.clone(),
owner: parent.clone(),
tx: tx.clone(),
rx: Arc::new(Mutex::new(Some(rx))),
};
owner.with(|| provide_context(outlet.clone()));
let (view, child) = self.into_view_and_child();
outlet.trigger.trigger();
tx.send(Box::new(move || view.choose(todo!()).into_any()));
// recursively continue building the tree
// this is important because to build the view, we need access to the outlet
@ -203,8 +240,89 @@ where
child.build_nested_route(outlets, &owner);
}
outlet.trigger.trigger();
tx.send(Box::new({
let owner = owner.clone();
let outlet = outlets.get(current_child + 0).cloned();
let parent = parent.clone();
move || {
leptos::logging::log!("here");
parent.with(|| {
if let Some(outlet) = outlet {
leptos::logging::log!(
"providing context on {:?}",
parent.debug_id()
);
provide_context(outlet);
} else {
leptos::logging::log!("nothing found");
}
});
owner.with(|| view.choose().into_any())
}
}));
outlets.push_back(outlet);
}
fn rebuild_nested_route(
self,
items: &mut usize,
outlets: &mut VecDeque<OutletContext<R>>,
parent: &Owner,
) {
let current = outlets.get_mut(*items);
match current {
// if there's nothing currently in the routes at this point, build from here
None => {
self.build_nested_route(outlets, parent);
}
Some(current) => {
let id = self.as_id();
// we always need to update the params, so go ahead and do that
current
.params
.set(self.to_params().into_iter().collect::<Params>());
let (view, child) = self.into_view_and_child();
// if the IDs don't match, everything below in the tree needs to be swapped
// 1) replace this outlet with the next view
// 2) remove other outlets
// 3) build down the chain
if id != current.id {
let owner = current.owner.clone();
current.tx.send({
let owner = owner.clone();
Box::new(move || {
leptos::logging::log!(
"running Outlet view in {:?}",
owner.debug_id()
);
owner.with(|| view.choose().into_any())
})
});
current.trigger.trigger();
current.id = id;
// TODO check this offset
outlets.truncate(*items + 1);
if let Some(child) = child {
child.build_nested_route(outlets, &owner);
}
return;
}
// otherwise, just keep rebuilding recursively
if let Some(child) = child {
let current = current.clone();
*items += 1;
child.rebuild_nested_route(items, outlets, &current.owner);
}
}
}
}
}
impl<Fal, R> Mountable<R> for NestedRouteViewState<Fal, R>
@ -228,3 +346,33 @@ where
self.view.insert_before_this(parent, child)
}
}
#[component]
pub fn Outlet<R>(#[prop(optional)] rndr: PhantomData<R>) -> impl RenderHtml<R>
where
R: Renderer + 'static,
{
_ = rndr;
let owner = Owner::current().unwrap();
leptos::logging::log!("Outlet owner is {:?}", owner.debug_id());
let ctx = use_context::<OutletContext<R>>()
.expect("<Outlet/> used without OutletContext being provided.");
let OutletContext {
id,
trigger,
params,
owner,
tx,
rx,
} = ctx;
let rx = rx.lock().or_poisoned().take().expect(
"Tried to render <Outlet/> but could not find the view receiver. Are \
you using the same <Outlet/> twice?",
);
move || {
trigger.track();
let x = rx.try_recv().map(|view| view()).unwrap();
x
}
}

View file

@ -96,7 +96,7 @@ where
}
}
pub struct RouteData<R = Dom>
pub struct
where
R: Renderer + 'static,
{
@ -394,14 +394,14 @@ where
.map(|child| get_inner_view(&mut outlets, &owner, child))
.unwrap_or_default();
let route_data = RouteData {
let = RouteData {
params: ArcMemo::new({
let params = params.clone();
move |_| params.get()
}),
outlet,
};
let view = owner.with(|| view.choose(route_data));
let view = owner.with(|| view.choose());
Self {
id,
@ -441,14 +441,14 @@ where
})
.unwrap_or_default();
let route_data = RouteData {
let = RouteData {
params: ArcMemo::new({
let params = params.clone();
move |_| params.get()
}),
outlet,
};
let view = owner.with(|| view.choose(route_data));
let view = owner.with(|| view.choose());
Self {
id,
@ -1170,14 +1170,14 @@ where
);
}
let route_data = RouteData {
let = RouteData {
params: ArcMemo::new({
let params = params.clone();
move |_| params.get()
}),
outlet: Default::default(),
};
let view = outer_owner.with(|| view.choose(route_data));
let view = outer_owner.with(|| view.choose());
Either::Left::<_, Fallback>(view).rebuild(&mut prev);
} else {
Either::<<Children::Match as MatchInterface<Rndr>>::View, _>::Right((self.fallback)()).rebuild(&mut prev);
@ -1199,14 +1199,14 @@ where
);
}
let route_data = RouteData {
let = RouteData {
params: ArcMemo::new({
let params = params.clone();
move |_| params.get()
}),
outlet: Default::default(),
};
let view = outer_owner.with(|| view.choose(route_data));
let view = outer_owner.with(|| view.choose());
Either::Left(view)
}
_ => Either::Right((self.fallback)()),
@ -1307,14 +1307,14 @@ where
);
}
let route_data = RouteData {
let = RouteData {
params: ArcMemo::new({
let params = params.clone();
move |_| params.get()
}),
outlet: Default::default(),
};
let view = outer_owner.with(|| view.choose(route_data));
let view = outer_owner.with(|| view.choose());
Either::Left(view)
}
None => Either::Right((self.fallback)()),
@ -1352,14 +1352,14 @@ where
);
}
let route_data = RouteData {
let = RouteData {
params: ArcMemo::new({
let params = params.clone();
move |_| params.get()
}),
outlet: Default::default(),
};
let view = outer_owner.with(|| view.choose(route_data));
let view = outer_owner.with(|| view.choose());
Either::Left(view)
}
None => Either::Right((self.fallback)()),
@ -1407,14 +1407,14 @@ where
);
}
let route_data = RouteData {
let = RouteData {
params: ArcMemo::new({
let params = params.clone();
move |_| params.get()
}),
outlet: Default::default(),
};
let view = outer_owner.with(|| view.choose(route_data));
let view = outer_owner.with(|| view.choose());
Either::Left::<_, Fallback>(view).rebuild(&mut prev);
} else {
Either::<<Children::Match as MatchInterface<Rndr>>::View, _>::Right((self.fallback)()).rebuild(&mut prev);
@ -1436,14 +1436,14 @@ where
);
}
let route_data = RouteData {
let = RouteData {
params: ArcMemo::new({
let params = params.clone();
move |_| params.get()
}),
outlet: Default::default(),
};
let view = outer_owner.with(|| view.choose(route_data));
let view = outer_owner.with(|| view.choose());
Either::Left(view)
}
_ => Either::Right((self.fallback)()),