mirror of
https://github.com/leptos-rs/leptos
synced 2024-11-10 06:44:17 +00:00
continuing on nested routes
This commit is contained in:
parent
acec3bb313
commit
ebdd31cd9f
12 changed files with 421 additions and 158 deletions
|
@ -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(|| {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Binary file not shown.
|
@ -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::*;
|
||||
|
|
86
reactive_graph/src/signal/arc_trigger.rs
Normal file
86
reactive_graph/src/signal/arc_trigger.rs
Normal 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();
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
//pub mod components;
|
||||
pub mod components;
|
||||
mod generate_route_list;
|
||||
pub mod location;
|
||||
mod matching;
|
||||
|
|
|
@ -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()),)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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, ¤t.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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)()),
|
||||
|
|
Loading…
Reference in a new issue