mirror of
https://github.com/leptos-rs/leptos
synced 2024-11-10 06:44:17 +00:00
initial async routing work (to support bundle splitting)
This commit is contained in:
parent
cfba7a2797
commit
a7b1152910
11 changed files with 355 additions and 163 deletions
|
@ -16,6 +16,7 @@ either_of = { workspace = true }
|
|||
or_poisoned = { workspace = true }
|
||||
reactive_graph = { workspace = true }
|
||||
tachys = { workspace = true, features = ["reactive_graph"] }
|
||||
futures = "0.3"
|
||||
url = "2"
|
||||
js-sys = { version = "0.3" }
|
||||
wasm-bindgen = { version = "0.2" }
|
||||
|
|
|
@ -79,6 +79,7 @@ where
|
|||
let location =
|
||||
BrowserUrl::new().expect("could not access browser navigation"); // TODO options here
|
||||
location.init(base.clone());
|
||||
provide_context(location.clone());
|
||||
location.as_url().clone()
|
||||
};
|
||||
// provide router context
|
||||
|
@ -199,6 +200,7 @@ where
|
|||
FallbackFn: Fn() -> Fallback + Send + 'static,
|
||||
Fallback: IntoView + 'static,
|
||||
{
|
||||
let location = use_context::<BrowserUrl>();
|
||||
let RouterContext {
|
||||
current_url, base, ..
|
||||
} = use_context()
|
||||
|
@ -220,6 +222,7 @@ where
|
|||
let outer_owner =
|
||||
Owner::current().expect("creating Routes, but no Owner was found");
|
||||
move || NestedRoutesView {
|
||||
location: location.clone(),
|
||||
routes: routes.clone(),
|
||||
outer_owner: outer_owner.clone(),
|
||||
url: current_url.clone(),
|
||||
|
@ -241,8 +244,7 @@ where
|
|||
FallbackFn: Fn() -> Fallback + Send + 'static,
|
||||
Fallback: IntoView + 'static,
|
||||
{
|
||||
use either_of::Either;
|
||||
|
||||
let location = use_context::<BrowserUrl>();
|
||||
let RouterContext {
|
||||
current_url, base, ..
|
||||
} = use_context()
|
||||
|
@ -264,13 +266,17 @@ where
|
|||
let outer_owner =
|
||||
Owner::current().expect("creating Router, but no Owner was found");
|
||||
let params = ArcRwSignal::new(ParamsMap::new());
|
||||
move || FlatRoutesView {
|
||||
move || {
|
||||
path.track();
|
||||
FlatRoutesView {
|
||||
location: location.clone(),
|
||||
routes: routes.clone(),
|
||||
path: path.clone(),
|
||||
fallback: fallback(),
|
||||
outer_owner: outer_owner.clone(),
|
||||
params: params.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
|
|
|
@ -1,25 +1,28 @@
|
|||
use crate::{
|
||||
location::{Location, RequestUrl, Url},
|
||||
location::{Location, LocationProvider, RequestUrl, Url},
|
||||
matching::Routes,
|
||||
params::ParamsMap,
|
||||
resolve_path::resolve_path,
|
||||
ChooseView, MatchInterface, MatchNestedRoutes, MatchParams, Method,
|
||||
PathSegment, RouteList, RouteListing, RouteMatchId,
|
||||
};
|
||||
use either_of::Either;
|
||||
use any_spawner::Executor;
|
||||
use either_of::{Either, EitherFuture, EitherOf3};
|
||||
use leptos::{component, oco::Oco, IntoView};
|
||||
use or_poisoned::OrPoisoned;
|
||||
use reactive_graph::{
|
||||
computed::{ArcMemo, Memo},
|
||||
computed::{ArcMemo, Memo, ScopedFuture},
|
||||
owner::{provide_context, use_context, Owner},
|
||||
signal::{ArcRwSignal, ArcTrigger},
|
||||
traits::{Get, Read, ReadUntracked, Set, Track, Trigger},
|
||||
traits::{Get, GetUntracked, Read, ReadUntracked, Set, Track, Trigger},
|
||||
};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
cell::RefCell,
|
||||
iter,
|
||||
marker::PhantomData,
|
||||
mem,
|
||||
rc::Rc,
|
||||
sync::{
|
||||
mpsc::{self, Receiver, Sender},
|
||||
Arc, Mutex,
|
||||
|
@ -37,7 +40,8 @@ use tachys::{
|
|||
},
|
||||
};
|
||||
|
||||
pub(crate) struct FlatRoutesView<Defs, Fal, R> {
|
||||
pub(crate) struct FlatRoutesView<Loc, Defs, Fal, R> {
|
||||
pub location: Option<Loc>,
|
||||
pub routes: Routes<Defs, R>,
|
||||
pub path: ArcMemo<String>,
|
||||
pub fallback: Fal,
|
||||
|
@ -45,13 +49,14 @@ pub(crate) struct FlatRoutesView<Defs, Fal, R> {
|
|||
pub params: ArcRwSignal<ParamsMap>,
|
||||
}
|
||||
|
||||
impl<Defs, Fal, R> FlatRoutesView<Defs, Fal, R>
|
||||
impl<Loc, Defs, Fal, R> FlatRoutesView<Loc, Defs, Fal, R>
|
||||
where
|
||||
Loc: LocationProvider,
|
||||
Defs: MatchNestedRoutes<R>,
|
||||
Fal: Render<R>,
|
||||
R: Renderer + 'static,
|
||||
{
|
||||
pub fn choose(
|
||||
pub async fn choose(
|
||||
self,
|
||||
) -> Either<Fal, <Defs::Match as MatchInterface<R>>::View> {
|
||||
let FlatRoutesView {
|
||||
|
@ -60,16 +65,22 @@ where
|
|||
fallback,
|
||||
outer_owner,
|
||||
params,
|
||||
..
|
||||
} = self;
|
||||
|
||||
outer_owner.with(|| {
|
||||
outer_owner
|
||||
.with(|| {
|
||||
provide_context(params.clone().read_only());
|
||||
let new_match = routes.match_route(&path.read());
|
||||
match new_match {
|
||||
None => Either::Left(fallback),
|
||||
None => EitherFuture::Left {
|
||||
inner: async move { fallback },
|
||||
},
|
||||
Some(matched) => {
|
||||
let new_params =
|
||||
matched.to_params().into_iter().collect::<ParamsMap>();
|
||||
let new_params = matched
|
||||
.to_params()
|
||||
.into_iter()
|
||||
.collect::<ParamsMap>();
|
||||
params.set(new_params);
|
||||
let (view, child) = matched.into_view_and_child();
|
||||
|
||||
|
@ -81,39 +92,94 @@ where
|
|||
);
|
||||
}
|
||||
|
||||
let view = view.choose();
|
||||
Either::Right(view)
|
||||
EitherFuture::Right {
|
||||
inner: ScopedFuture::new(view.choose()),
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
impl<Defs, Fal, R> Render<R> for FlatRoutesView<Defs, Fal, R>
|
||||
impl<Loc, Defs, Fal, R> Render<R> for FlatRoutesView<Loc, Defs, Fal, R>
|
||||
where
|
||||
Defs: MatchNestedRoutes<R>,
|
||||
Fal: Render<R>,
|
||||
Loc: LocationProvider,
|
||||
Defs: MatchNestedRoutes<R> + 'static,
|
||||
Fal: Render<R> + 'static,
|
||||
R: Renderer + 'static,
|
||||
{
|
||||
type State = <Either<Fal, <Defs::Match as MatchInterface<R>>::View> as Render<R>>::State;
|
||||
type State = Rc<
|
||||
RefCell<
|
||||
// TODO loading indicator
|
||||
<EitherOf3<(), Fal, <Defs::Match as MatchInterface<R>>::View> as Render<
|
||||
R,
|
||||
>>::State,
|
||||
>,
|
||||
>;
|
||||
|
||||
fn build(self) -> Self::State {
|
||||
self.choose().build()
|
||||
let state = Rc::new(RefCell::new(EitherOf3::A(()).build()));
|
||||
let spawned_path = self.path.get_untracked();
|
||||
let current_path = self.path.clone();
|
||||
let location = self.location.clone();
|
||||
let route = self.choose();
|
||||
Executor::spawn_local({
|
||||
let state = Rc::clone(&state);
|
||||
async move {
|
||||
let loaded_route = route.await;
|
||||
// only update the route if it's still the current path
|
||||
// i.e., if we've navigated away before this has loaded, do nothing
|
||||
if &spawned_path == &*current_path.read_untracked() {
|
||||
let new_view = match loaded_route {
|
||||
Either::Left(i) => EitherOf3::B(i),
|
||||
Either::Right(i) => EitherOf3::C(i),
|
||||
};
|
||||
new_view.rebuild(&mut state.borrow_mut());
|
||||
if let Some(location) = location {
|
||||
location.ready_to_complete();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
state
|
||||
}
|
||||
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
self.choose().rebuild(state);
|
||||
let spawned_path = self.path.get_untracked();
|
||||
let current_path = self.path.clone();
|
||||
let location = self.location.clone();
|
||||
let route = self.choose();
|
||||
Executor::spawn_local({
|
||||
let state = Rc::clone(&*state);
|
||||
async move {
|
||||
let loaded_route = route.await;
|
||||
// only update the route if it's still the current path
|
||||
// i.e., if we've navigated away before this has loaded, do nothing
|
||||
if &spawned_path == &*current_path.read_untracked() {
|
||||
let new_view = match loaded_route {
|
||||
Either::Left(i) => EitherOf3::B(i),
|
||||
Either::Right(i) => EitherOf3::C(i),
|
||||
};
|
||||
new_view.rebuild(&mut state.borrow_mut());
|
||||
if let Some(location) = location {
|
||||
location.ready_to_complete();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl<Defs, Fal, R> AddAnyAttr<R> for FlatRoutesView<Defs, Fal, R>
|
||||
impl<Loc, Defs, Fal, R> AddAnyAttr<R> for FlatRoutesView<Loc, Defs, Fal, R>
|
||||
where
|
||||
Defs: MatchNestedRoutes<R> + Send,
|
||||
Fal: RenderHtml<R>,
|
||||
Loc: LocationProvider + Send,
|
||||
Defs: MatchNestedRoutes<R> + Send + 'static,
|
||||
Fal: RenderHtml<R> + 'static,
|
||||
R: Renderer + 'static,
|
||||
{
|
||||
type Output<SomeNewAttr: leptos::attr::Attribute<R>> =
|
||||
FlatRoutesView<Defs, Fal, R>;
|
||||
FlatRoutesView<Loc, Defs, Fal, R>;
|
||||
|
||||
fn add_any_attr<NewAttr: leptos::attr::Attribute<R>>(
|
||||
self,
|
||||
|
@ -126,10 +192,11 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<Defs, Fal, R> RenderHtml<R> for FlatRoutesView<Defs, Fal, R>
|
||||
impl<Loc, Defs, Fal, R> RenderHtml<R> for FlatRoutesView<Loc, Defs, Fal, R>
|
||||
where
|
||||
Defs: MatchNestedRoutes<R> + Send,
|
||||
Fal: RenderHtml<R>,
|
||||
Loc: LocationProvider + Send,
|
||||
Defs: MatchNestedRoutes<R> + Send + 'static,
|
||||
Fal: RenderHtml<R> + 'static,
|
||||
R: Renderer + 'static,
|
||||
{
|
||||
type AsyncOutput = Self;
|
||||
|
@ -190,7 +257,8 @@ where
|
|||
|
||||
RouteList::register(RouteList::from(routes));
|
||||
} else {
|
||||
self.choose().to_html_with_buf(buf, position);
|
||||
todo!()
|
||||
// self.choose().to_html_with_buf(buf, position);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -201,8 +269,9 @@ where
|
|||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
self.choose()
|
||||
.to_html_async_with_buf::<OUT_OF_ORDER>(buf, position);
|
||||
todo!()
|
||||
// self.choose()
|
||||
// .to_html_async_with_buf::<OUT_OF_ORDER>(buf, position);
|
||||
}
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
|
@ -210,6 +279,7 @@ where
|
|||
cursor: &Cursor<R>,
|
||||
position: &PositionState,
|
||||
) -> Self::State {
|
||||
self.choose().hydrate::<FROM_SERVER>(cursor, position)
|
||||
todo!()
|
||||
// self.choose().hydrate::<FROM_SERVER>(cursor, position)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,9 +3,18 @@ use super::{
|
|||
};
|
||||
use crate::{navigate::UseNavigate, params::ParamsMap};
|
||||
use core::fmt;
|
||||
use futures::channel::oneshot;
|
||||
use js_sys::{try_iter, Array, JsString, Reflect};
|
||||
use or_poisoned::OrPoisoned;
|
||||
use reactive_graph::{signal::ArcRwSignal, traits::Set};
|
||||
use std::{borrow::Cow, boxed::Box, rc::Rc, string::String};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
boxed::Box,
|
||||
cell::RefCell,
|
||||
rc::Rc,
|
||||
string::String,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
use tachys::dom::{document, window};
|
||||
use wasm_bindgen::{closure::Closure, JsCast, JsValue};
|
||||
use web_sys::{Event, HtmlAnchorElement, MouseEvent, UrlSearchParams};
|
||||
|
@ -13,6 +22,7 @@ use web_sys::{Event, HtmlAnchorElement, MouseEvent, UrlSearchParams};
|
|||
#[derive(Clone)]
|
||||
pub struct BrowserUrl {
|
||||
url: ArcRwSignal<Url>,
|
||||
pending_navigation: Arc<Mutex<Option<oneshot::Sender<()>>>>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for BrowserUrl {
|
||||
|
@ -49,7 +59,11 @@ impl LocationProvider for BrowserUrl {
|
|||
|
||||
fn new() -> Result<Self, JsValue> {
|
||||
let url = ArcRwSignal::new(Self::current()?);
|
||||
Ok(Self { url })
|
||||
let pending_navigation = Default::default();
|
||||
Ok(Self {
|
||||
url,
|
||||
pending_navigation,
|
||||
})
|
||||
}
|
||||
|
||||
fn as_url(&self) -> &ArcRwSignal<Url> {
|
||||
|
@ -94,12 +108,19 @@ impl LocationProvider for BrowserUrl {
|
|||
let window = window();
|
||||
let navigate = {
|
||||
let url = self.url.clone();
|
||||
let pending = Arc::clone(&self.pending_navigation);
|
||||
move |new_url, loc| {
|
||||
let (tx, rx) = oneshot::channel::<()>();
|
||||
*pending.lock().or_poisoned() = Some(tx);
|
||||
url.set(new_url);
|
||||
async move {
|
||||
// if it has been canceled, ignore
|
||||
// otherwise, complete navigation -- i.e., set URL in address bar
|
||||
if rx.await.is_ok() {
|
||||
Self::complete_navigation(&loc);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let handle_anchor_click =
|
||||
|
@ -146,6 +167,12 @@ impl LocationProvider for BrowserUrl {
|
|||
.expect("couldn't add `popstate` listener to `window`");
|
||||
}
|
||||
|
||||
fn ready_to_complete(&self) {
|
||||
if let Some(tx) = self.pending_navigation.lock().or_poisoned().take() {
|
||||
tx.send(());
|
||||
}
|
||||
}
|
||||
|
||||
fn complete_navigation(loc: &LocationChange) {
|
||||
let history = window().history().unwrap();
|
||||
|
||||
|
|
|
@ -114,7 +114,7 @@ impl Default for LocationChange {
|
|||
}
|
||||
}
|
||||
|
||||
pub trait LocationProvider: Sized {
|
||||
pub trait LocationProvider: Clone + 'static {
|
||||
type Error: Debug;
|
||||
|
||||
fn new() -> Result<Self, Self::Error>;
|
||||
|
@ -126,6 +126,10 @@ pub trait LocationProvider: Sized {
|
|||
/// Sets up any global event listeners or other initialization needed.
|
||||
fn init(&self, base: Option<Cow<'static, str>>);
|
||||
|
||||
/// Should be called after a navigation when all route components and data have been loaded and
|
||||
/// the URL can be updated.
|
||||
fn ready_to_complete(&self);
|
||||
|
||||
/// Update the browser's history to reflect a new location.
|
||||
fn complete_navigation(loc: &LocationChange);
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use either_of::*;
|
||||
use std::future::Future;
|
||||
use tachys::{renderer::Renderer, view::Render};
|
||||
|
||||
pub trait ChooseView<R>
|
||||
|
@ -6,21 +7,22 @@ where
|
|||
Self: Send + 'static,
|
||||
R: Renderer + 'static,
|
||||
{
|
||||
type Output: Render<R> + Send;
|
||||
type Output;
|
||||
|
||||
fn choose(self) -> Self::Output;
|
||||
fn choose(self) -> impl Future<Output = Self::Output>;
|
||||
}
|
||||
|
||||
impl<F, View, R> ChooseView<R> for F
|
||||
impl<F, ViewFut, R> ChooseView<R> for F
|
||||
where
|
||||
F: Fn() -> View + Send + 'static,
|
||||
View: Render<R> + Send,
|
||||
F: Fn() -> ViewFut + Send + 'static,
|
||||
ViewFut: Future,
|
||||
ViewFut::Output: Render<R> + Send,
|
||||
R: Renderer + 'static,
|
||||
{
|
||||
type Output = View;
|
||||
type Output = ViewFut::Output;
|
||||
|
||||
fn choose(self) -> Self::Output {
|
||||
self()
|
||||
async fn choose(self) -> Self::Output {
|
||||
self().await
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,7 +32,7 @@ where
|
|||
{
|
||||
type Output = ();
|
||||
|
||||
fn choose(self) -> Self::Output {}
|
||||
async fn choose(self) -> Self::Output {}
|
||||
}
|
||||
|
||||
impl<A, B, Rndr> ChooseView<Rndr> for Either<A, B>
|
||||
|
@ -41,10 +43,10 @@ where
|
|||
{
|
||||
type Output = Either<A::Output, B::Output>;
|
||||
|
||||
fn choose(self) -> Self::Output {
|
||||
async fn choose(self) -> Self::Output {
|
||||
match self {
|
||||
Either::Left(f) => Either::Left(f.choose()),
|
||||
Either::Right(f) => Either::Right(f.choose()),
|
||||
Either::Left(f) => Either::Left(f.choose().await),
|
||||
Either::Right(f) => Either::Right(f.choose().await),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -58,9 +60,9 @@ macro_rules! tuples {
|
|||
{
|
||||
type Output = $either<$($ty::Output,)*>;
|
||||
|
||||
fn choose(self ) -> Self::Output {
|
||||
async fn choose(self ) -> Self::Output {
|
||||
match self {
|
||||
$($either::$ty(f) => $either::$ty(f.choose()),)*
|
||||
$($either::$ty(f) => $either::$ty(f.choose().await),)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ use super::{
|
|||
};
|
||||
use crate::{ChooseView, MatchParams, SsrMode, GeneratedRouteData};
|
||||
use core::{fmt, iter};
|
||||
use std::{borrow::Cow, marker::PhantomData, sync::atomic::{AtomicU16, Ordering}};
|
||||
use std::{borrow::Cow, marker::PhantomData, sync::atomic::{AtomicU16, Ordering}, future::Future};
|
||||
use either_of::Either;
|
||||
use tachys::{
|
||||
renderer::Renderer,
|
||||
|
@ -118,16 +118,17 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<ParamsIter, Child, ViewFn, View, Rndr> MatchInterface<Rndr>
|
||||
impl<ParamsIter, Child, ViewFn, ViewFut, Rndr> MatchInterface<Rndr>
|
||||
for NestedMatch<ParamsIter, Child, ViewFn>
|
||||
where
|
||||
Rndr: Renderer + 'static,
|
||||
Child: MatchInterface<Rndr> + MatchParams + 'static,
|
||||
ViewFn: Fn() -> View + Send + 'static,
|
||||
View: Render<Rndr> + RenderHtml<Rndr> + Send + 'static,
|
||||
ViewFn: Fn() -> ViewFut + Send + 'static,
|
||||
ViewFut: Future,
|
||||
ViewFut::Output: Render<Rndr> + RenderHtml<Rndr> + Send + 'static,
|
||||
{
|
||||
type Child = Child;
|
||||
type View = ViewFn::Output;
|
||||
type View = ViewFut::Output;
|
||||
|
||||
fn as_id(&self) -> RouteMatchId {
|
||||
self.id
|
||||
|
@ -147,7 +148,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<Segments, Children, Data, ViewFn, View, Rndr> MatchNestedRoutes<Rndr>
|
||||
impl<Segments, Children, Data, ViewFn, ViewFut, Rndr> MatchNestedRoutes<Rndr>
|
||||
for NestedRoute<Segments, Children, Data, ViewFn, Rndr>
|
||||
where
|
||||
Self: 'static,
|
||||
|
@ -159,11 +160,12 @@ where
|
|||
Children::Match: MatchParams,
|
||||
Children: 'static,
|
||||
<Children::Match as MatchParams>::Params: Clone,
|
||||
ViewFn: Fn() -> View + Send + Clone + 'static,
|
||||
View: Render<Rndr> + RenderHtml<Rndr> + Send + 'static,
|
||||
ViewFn: Fn() -> ViewFut + Send + Clone + 'static,
|
||||
ViewFut: Future,
|
||||
ViewFut::Output: Render<Rndr> + RenderHtml<Rndr> + Send + 'static,
|
||||
{
|
||||
type Data = Data;
|
||||
type View = View;
|
||||
type View = ViewFut::Output;
|
||||
type Match = NestedMatch<iter::Chain<
|
||||
<Segments::ParamsIter as IntoIterator>::IntoIter,
|
||||
Either<iter::Empty::<
|
||||
|
|
|
@ -1,25 +1,30 @@
|
|||
use crate::{
|
||||
location::{Location, RequestUrl, Url},
|
||||
location::{Location, LocationProvider, RequestUrl, Url},
|
||||
matching::Routes,
|
||||
params::ParamsMap,
|
||||
resolve_path::resolve_path,
|
||||
ChooseView, MatchInterface, MatchNestedRoutes, MatchParams, Method,
|
||||
PathSegment, RouteList, RouteListing, RouteMatchId,
|
||||
};
|
||||
use either_of::Either;
|
||||
use any_spawner::Executor;
|
||||
use either_of::{Either, EitherOf3};
|
||||
use futures::future::join_all;
|
||||
use leptos::{component, oco::Oco, IntoView};
|
||||
use or_poisoned::OrPoisoned;
|
||||
use reactive_graph::{
|
||||
computed::{ArcMemo, Memo},
|
||||
computed::{ArcMemo, Memo, ScopedFuture},
|
||||
owner::{provide_context, use_context, Owner},
|
||||
signal::{ArcRwSignal, ArcTrigger},
|
||||
traits::{Get, Read, ReadUntracked, Set, Track, Trigger},
|
||||
};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
cell::RefCell,
|
||||
future::Future,
|
||||
iter,
|
||||
marker::PhantomData,
|
||||
mem,
|
||||
pin::Pin,
|
||||
rc::Rc,
|
||||
sync::{
|
||||
mpsc::{self, Receiver, Sender},
|
||||
Arc, Mutex,
|
||||
|
@ -32,7 +37,7 @@ use tachys::{
|
|||
view::{
|
||||
add_attr::AddAnyAttr,
|
||||
any_view::{AnyView, AnyViewState, IntoAny},
|
||||
either::EitherState,
|
||||
either::{EitherOf3State, EitherState},
|
||||
Mountable, Position, PositionState, Render, RenderHtml,
|
||||
},
|
||||
};
|
||||
|
@ -41,7 +46,8 @@ pub struct Outlet<R> {
|
|||
rndr: PhantomData<R>,
|
||||
}
|
||||
|
||||
pub(crate) struct NestedRoutesView<Defs, Fal, R> {
|
||||
pub(crate) struct NestedRoutesView<Loc, Defs, Fal, R> {
|
||||
pub location: Option<Loc>,
|
||||
pub routes: Routes<Defs, R>,
|
||||
pub outer_owner: Owner,
|
||||
pub url: ArcRwSignal<Url>,
|
||||
|
@ -62,15 +68,18 @@ where
|
|||
path: ArcMemo<String>,
|
||||
search_params: ArcMemo<ParamsMap>,
|
||||
outlets: Vec<RouteContext<R>>,
|
||||
view: EitherState<Fal::State, AnyViewState<R>, R>,
|
||||
// TODO loading fallback
|
||||
view: Rc<RefCell<EitherOf3State<(), Fal, AnyView<R>, R>>>,
|
||||
}
|
||||
|
||||
impl<Defs, Fal, R> Render<R> for NestedRoutesView<Defs, Fal, R>
|
||||
impl<Loc, Defs, Fal, R> Render<R> for NestedRoutesView<Loc, Defs, Fal, R>
|
||||
where
|
||||
Loc: LocationProvider,
|
||||
Defs: MatchNestedRoutes<R>,
|
||||
Fal: Render<R>,
|
||||
Fal: Render<R> + 'static,
|
||||
R: Renderer + 'static,
|
||||
{
|
||||
// TODO support fallback while loading
|
||||
type State = NestedRouteViewState<Fal, R>;
|
||||
|
||||
fn build(self) -> Self::State {
|
||||
|
@ -85,20 +94,41 @@ where
|
|||
..
|
||||
} = self;
|
||||
|
||||
let mut loaders = Vec::new();
|
||||
let mut outlets = Vec::new();
|
||||
let new_match = routes.match_route(&path.read());
|
||||
let view = match new_match {
|
||||
None => Either::Left(fallback),
|
||||
|
||||
// start with an empty view because we'll be loading routes async
|
||||
let view = EitherOf3::A(()).build();
|
||||
let view = Rc::new(RefCell::new(view));
|
||||
let matched_view = match new_match {
|
||||
None => EitherOf3::B(fallback),
|
||||
Some(route) => {
|
||||
route.build_nested_route(base, &mut outlets, &outer_owner);
|
||||
route.build_nested_route(
|
||||
base,
|
||||
&mut loaders,
|
||||
&mut outlets,
|
||||
&outer_owner,
|
||||
);
|
||||
outer_owner.with(|| {
|
||||
Either::Right(
|
||||
EitherOf3::C(
|
||||
Outlet(OutletProps::builder().build()).into_any(),
|
||||
)
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
Executor::spawn_local({
|
||||
let view = Rc::clone(&view);
|
||||
let loaders = mem::take(&mut loaders);
|
||||
async move {
|
||||
let triggers = join_all(loaders).await;
|
||||
for trigger in triggers {
|
||||
trigger.trigger();
|
||||
}
|
||||
.build();
|
||||
matched_view.rebuild(&mut *view.borrow_mut());
|
||||
}
|
||||
});
|
||||
|
||||
NestedRouteViewState {
|
||||
outlets,
|
||||
|
@ -115,25 +145,40 @@ where
|
|||
|
||||
match new_match {
|
||||
None => {
|
||||
Either::<Fal, AnyView<R>>::Left(self.fallback)
|
||||
.rebuild(&mut state.view);
|
||||
EitherOf3::<(), Fal, AnyView<R>>::B(self.fallback)
|
||||
.rebuild(&mut state.view.borrow_mut());
|
||||
state.outlets.clear();
|
||||
}
|
||||
Some(route) => {
|
||||
let mut loaders = Vec::new();
|
||||
route.rebuild_nested_route(
|
||||
self.base,
|
||||
&mut 0,
|
||||
&mut loaders,
|
||||
&mut state.outlets,
|
||||
&self.outer_owner,
|
||||
);
|
||||
|
||||
// hmm...
|
||||
let location = self.location.clone();
|
||||
Executor::spawn_local(async move {
|
||||
let triggers = join_all(loaders).await;
|
||||
// tell each one of the outlet triggers that it's ready
|
||||
for trigger in triggers {
|
||||
trigger.trigger();
|
||||
}
|
||||
if let Some(loc) = location {
|
||||
loc.ready_to_complete();
|
||||
}
|
||||
});
|
||||
|
||||
// if it was on the fallback, show the view instead
|
||||
if matches!(state.view.state, Either::Left(_)) {
|
||||
if matches!(state.view.borrow().state, EitherOf3::B(_)) {
|
||||
self.outer_owner.with(|| {
|
||||
Either::<Fal, AnyView<R>>::Right(
|
||||
EitherOf3::<(), Fal, AnyView<R>>::C(
|
||||
Outlet(OutletProps::builder().build()).into_any(),
|
||||
)
|
||||
.rebuild(&mut state.view);
|
||||
.rebuild(&mut *state.view.borrow_mut());
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -141,14 +186,15 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<Defs, Fal, R> AddAnyAttr<R> for NestedRoutesView<Defs, Fal, R>
|
||||
impl<Loc, Defs, Fal, R> AddAnyAttr<R> for NestedRoutesView<Loc, Defs, Fal, R>
|
||||
where
|
||||
Loc: LocationProvider + Send,
|
||||
Defs: MatchNestedRoutes<R> + Send,
|
||||
Fal: RenderHtml<R>,
|
||||
Fal: RenderHtml<R> + 'static,
|
||||
R: Renderer + 'static,
|
||||
{
|
||||
type Output<SomeNewAttr: leptos::attr::Attribute<R>> =
|
||||
NestedRoutesView<Defs, Fal, R>;
|
||||
NestedRoutesView<Loc, Defs, Fal, R>;
|
||||
|
||||
fn add_any_attr<NewAttr: leptos::attr::Attribute<R>>(
|
||||
self,
|
||||
|
@ -161,10 +207,11 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<Defs, Fal, R> RenderHtml<R> for NestedRoutesView<Defs, Fal, R>
|
||||
impl<Loc, Defs, Fal, R> RenderHtml<R> for NestedRoutesView<Loc, Defs, Fal, R>
|
||||
where
|
||||
Loc: LocationProvider + Send,
|
||||
Defs: MatchNestedRoutes<R> + Send,
|
||||
Fal: RenderHtml<R>,
|
||||
Fal: RenderHtml<R> + 'static,
|
||||
R: Renderer + 'static,
|
||||
{
|
||||
type AsyncOutput = Self;
|
||||
|
@ -238,7 +285,13 @@ where
|
|||
let view = match new_match {
|
||||
None => Either::Left(fallback),
|
||||
Some(route) => {
|
||||
route.build_nested_route(base, &mut outlets, &outer_owner);
|
||||
route.build_nested_route(
|
||||
base,
|
||||
// TODO loaders here
|
||||
&mut Vec::new(),
|
||||
&mut outlets,
|
||||
&outer_owner,
|
||||
);
|
||||
outer_owner.with(|| {
|
||||
Either::Right(
|
||||
Outlet(OutletProps::builder().build()).into_any(),
|
||||
|
@ -273,7 +326,13 @@ where
|
|||
let view = match new_match {
|
||||
None => Either::Left(fallback),
|
||||
Some(route) => {
|
||||
route.build_nested_route(base, &mut outlets, &outer_owner);
|
||||
route.build_nested_route(
|
||||
base,
|
||||
// TODO loaders
|
||||
&mut Vec::new(),
|
||||
&mut outlets,
|
||||
&outer_owner,
|
||||
);
|
||||
outer_owner.with(|| {
|
||||
Either::Right(
|
||||
Outlet(OutletProps::builder().build()).into_any(),
|
||||
|
@ -302,18 +361,26 @@ where
|
|||
|
||||
let mut outlets = Vec::new();
|
||||
let new_match = routes.match_route(&path.read());
|
||||
let view = match new_match {
|
||||
None => Either::Left(fallback),
|
||||
let view = Rc::new(RefCell::new(
|
||||
match new_match {
|
||||
None => EitherOf3::B(fallback),
|
||||
Some(route) => {
|
||||
route.build_nested_route(base, &mut outlets, &outer_owner);
|
||||
route.build_nested_route(
|
||||
base,
|
||||
// TODO loaders in hydration
|
||||
&mut Vec::new(),
|
||||
&mut outlets,
|
||||
&outer_owner,
|
||||
);
|
||||
outer_owner.with(|| {
|
||||
Either::Right(
|
||||
EitherOf3::C(
|
||||
Outlet(OutletProps::builder().build()).into_any(),
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
.hydrate::<FROM_SERVER>(cursor, position);
|
||||
.hydrate::<FROM_SERVER>(cursor, position),
|
||||
));
|
||||
|
||||
NestedRouteViewState {
|
||||
outlets,
|
||||
|
@ -378,6 +445,7 @@ where
|
|||
fn build_nested_route(
|
||||
self,
|
||||
base: Option<Oco<'static, str>>,
|
||||
loaders: &mut Vec<Pin<Box<dyn Future<Output = ArcTrigger>>>>,
|
||||
outlets: &mut Vec<RouteContext<R>>,
|
||||
parent: &Owner,
|
||||
);
|
||||
|
@ -386,6 +454,7 @@ where
|
|||
self,
|
||||
base: Option<Oco<'static, str>>,
|
||||
items: &mut usize,
|
||||
loaders: &mut Vec<Pin<Box<dyn Future<Output = ArcTrigger>>>>,
|
||||
outlets: &mut Vec<RouteContext<R>>,
|
||||
parent: &Owner,
|
||||
);
|
||||
|
@ -399,6 +468,7 @@ where
|
|||
fn build_nested_route(
|
||||
self,
|
||||
base: Option<Oco<'static, str>>,
|
||||
loaders: &mut Vec<Pin<Box<dyn Future<Output = ArcTrigger>>>>,
|
||||
outlets: &mut Vec<RouteContext<R>>,
|
||||
parent: &Owner,
|
||||
) {
|
||||
|
@ -428,7 +498,7 @@ where
|
|||
// add this outlet to the end of the outlet stack used for diffing
|
||||
let outlet = RouteContext {
|
||||
id: self.as_id(),
|
||||
trigger,
|
||||
trigger: trigger.clone(),
|
||||
params,
|
||||
owner: owner.clone(),
|
||||
matched: ArcRwSignal::new(self.as_matched().to_string()),
|
||||
|
@ -441,9 +511,14 @@ where
|
|||
// send the initial view through the channel, and recurse through the children
|
||||
let (view, child) = self.into_view_and_child();
|
||||
|
||||
tx.send(Box::new({
|
||||
loaders.push(Box::pin({
|
||||
let owner = outlet.owner.clone();
|
||||
move || owner.with(|| view.choose().into_any())
|
||||
async move {
|
||||
let view =
|
||||
owner.with(|| ScopedFuture::new(view.choose())).await;
|
||||
tx.send(Box::new(move || owner.with(|| view.into_any())));
|
||||
trigger
|
||||
}
|
||||
}));
|
||||
|
||||
// and share the outlet with the parent via context
|
||||
|
@ -455,7 +530,7 @@ where
|
|||
// this is important because to build the view, we need access to the outlet
|
||||
// and the outlet will be returned from building this child
|
||||
if let Some(child) = child {
|
||||
child.build_nested_route(base, outlets, &owner);
|
||||
child.build_nested_route(base, loaders, outlets, &owner);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -463,6 +538,7 @@ where
|
|||
self,
|
||||
base: Option<Oco<'static, str>>,
|
||||
items: &mut usize,
|
||||
loaders: &mut Vec<Pin<Box<dyn Future<Output = ArcTrigger>>>>,
|
||||
outlets: &mut Vec<RouteContext<R>>,
|
||||
parent: &Owner,
|
||||
) {
|
||||
|
@ -470,7 +546,7 @@ where
|
|||
match current {
|
||||
// if there's nothing currently in the routes at this point, build from here
|
||||
None => {
|
||||
self.build_nested_route(base, outlets, parent);
|
||||
self.build_nested_route(base, loaders, outlets, parent);
|
||||
}
|
||||
Some(current) => {
|
||||
// a unique ID for each route, which allows us to compare when we get new matches
|
||||
|
@ -514,13 +590,21 @@ where
|
|||
// send the new view, with the new owner, through the channel to the Outlet,
|
||||
// and notify the trigger so that the reactive view inside the Outlet tracking
|
||||
// the trigger runs again
|
||||
current.tx.send({
|
||||
loaders.push(Box::pin({
|
||||
let owner = owner.clone();
|
||||
Box::new(move || {
|
||||
owner.with(|| view.choose().into_any())
|
||||
})
|
||||
});
|
||||
current.trigger.trigger();
|
||||
let trigger = current.trigger.clone();
|
||||
let tx = current.tx.clone();
|
||||
async move {
|
||||
let view = owner
|
||||
.with(|| ScopedFuture::new(view.choose()))
|
||||
.await;
|
||||
tx.send(Box::new(move || {
|
||||
owner.with(|| view.into_any())
|
||||
}));
|
||||
drop(old_owner);
|
||||
trigger
|
||||
}
|
||||
}));
|
||||
|
||||
// remove all the items lower in the tree
|
||||
// if this match is different, all its children will also be different
|
||||
|
@ -531,6 +615,7 @@ where
|
|||
let mut new_outlets = Vec::new();
|
||||
child.build_nested_route(
|
||||
base,
|
||||
loaders,
|
||||
&mut new_outlets,
|
||||
&owner,
|
||||
);
|
||||
|
@ -545,7 +630,9 @@ where
|
|||
if let Some(child) = child {
|
||||
let owner = current.owner.clone();
|
||||
*items += 1;
|
||||
child.rebuild_nested_route(base, items, outlets, &owner);
|
||||
child.rebuild_nested_route(
|
||||
base, items, loaders, outlets, &owner,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -597,7 +684,6 @@ where
|
|||
);
|
||||
move || {
|
||||
trigger.track();
|
||||
|
||||
rx.try_recv().map(|view| view()).unwrap()
|
||||
rx.try_recv().map(|view| view())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -305,31 +305,3 @@ where
|
|||
self.write().insert_before_this(parent, child)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Rndr, Fal, Output> Mountable<Rndr>
|
||||
for Rc<RefCell<EitherState<Fal, Output, Rndr>>>
|
||||
where
|
||||
Fal: Mountable<Rndr>,
|
||||
Output: Mountable<Rndr>,
|
||||
Rndr: Renderer,
|
||||
{
|
||||
fn unmount(&mut self) {
|
||||
self.borrow_mut().unmount();
|
||||
}
|
||||
|
||||
fn mount(
|
||||
&mut self,
|
||||
parent: &<Rndr as Renderer>::Element,
|
||||
marker: Option<&<Rndr as Renderer>::Node>,
|
||||
) {
|
||||
self.borrow_mut().mount(parent, marker);
|
||||
}
|
||||
|
||||
fn insert_before_this(
|
||||
&self,
|
||||
parent: &<Rndr as Renderer>::Element,
|
||||
child: &mut dyn Mountable<Rndr>,
|
||||
) -> bool {
|
||||
self.borrow_mut().insert_before_this(parent, child)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -425,8 +425,8 @@ macro_rules! tuples {
|
|||
$($ty: Render<Rndr>,)*
|
||||
Rndr: Renderer
|
||||
{
|
||||
state: [<EitherOf $num>]<$($ty::State,)*>,
|
||||
marker: Rndr::Placeholder,
|
||||
pub state: [<EitherOf $num>]<$($ty::State,)*>,
|
||||
pub marker: Rndr::Placeholder,
|
||||
}
|
||||
|
||||
impl<$($ty,)* Rndr> Mountable<Rndr> for [<EitherOf $num State>]<$($ty,)* Rndr>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use self::add_attr::AddAnyAttr;
|
||||
use crate::{hydration::Cursor, renderer::Renderer, ssr::StreamBuilder};
|
||||
use parking_lot::RwLock;
|
||||
use std::{future::Future, sync::Arc};
|
||||
use std::{cell::RefCell, future::Future, rc::Rc, sync::Arc};
|
||||
|
||||
pub mod add_attr;
|
||||
pub mod any_view;
|
||||
|
@ -256,6 +256,28 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<T, R> Mountable<R> for Rc<RefCell<T>>
|
||||
where
|
||||
T: Mountable<R>,
|
||||
R: Renderer,
|
||||
{
|
||||
fn unmount(&mut self) {
|
||||
self.borrow_mut().unmount()
|
||||
}
|
||||
|
||||
fn mount(&mut self, parent: &R::Element, marker: Option<&R::Node>) {
|
||||
self.borrow_mut().mount(parent, marker);
|
||||
}
|
||||
|
||||
fn insert_before_this(
|
||||
&self,
|
||||
parent: &<R as Renderer>::Element,
|
||||
child: &mut dyn Mountable<R>,
|
||||
) -> bool {
|
||||
self.borrow().insert_before_this(parent, child)
|
||||
}
|
||||
}
|
||||
|
||||
/// Allows data to be added to a static template.
|
||||
pub trait ToTemplate {
|
||||
const TEMPLATE: &'static str = "";
|
||||
|
|
Loading…
Reference in a new issue