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 }
|
or_poisoned = { workspace = true }
|
||||||
reactive_graph = { workspace = true }
|
reactive_graph = { workspace = true }
|
||||||
tachys = { workspace = true, features = ["reactive_graph"] }
|
tachys = { workspace = true, features = ["reactive_graph"] }
|
||||||
|
futures = "0.3"
|
||||||
url = "2"
|
url = "2"
|
||||||
js-sys = { version = "0.3" }
|
js-sys = { version = "0.3" }
|
||||||
wasm-bindgen = { version = "0.2" }
|
wasm-bindgen = { version = "0.2" }
|
||||||
|
|
|
@ -79,6 +79,7 @@ where
|
||||||
let location =
|
let location =
|
||||||
BrowserUrl::new().expect("could not access browser navigation"); // TODO options here
|
BrowserUrl::new().expect("could not access browser navigation"); // TODO options here
|
||||||
location.init(base.clone());
|
location.init(base.clone());
|
||||||
|
provide_context(location.clone());
|
||||||
location.as_url().clone()
|
location.as_url().clone()
|
||||||
};
|
};
|
||||||
// provide router context
|
// provide router context
|
||||||
|
@ -199,6 +200,7 @@ where
|
||||||
FallbackFn: Fn() -> Fallback + Send + 'static,
|
FallbackFn: Fn() -> Fallback + Send + 'static,
|
||||||
Fallback: IntoView + 'static,
|
Fallback: IntoView + 'static,
|
||||||
{
|
{
|
||||||
|
let location = use_context::<BrowserUrl>();
|
||||||
let RouterContext {
|
let RouterContext {
|
||||||
current_url, base, ..
|
current_url, base, ..
|
||||||
} = use_context()
|
} = use_context()
|
||||||
|
@ -220,6 +222,7 @@ where
|
||||||
let outer_owner =
|
let outer_owner =
|
||||||
Owner::current().expect("creating Routes, but no Owner was found");
|
Owner::current().expect("creating Routes, but no Owner was found");
|
||||||
move || NestedRoutesView {
|
move || NestedRoutesView {
|
||||||
|
location: location.clone(),
|
||||||
routes: routes.clone(),
|
routes: routes.clone(),
|
||||||
outer_owner: outer_owner.clone(),
|
outer_owner: outer_owner.clone(),
|
||||||
url: current_url.clone(),
|
url: current_url.clone(),
|
||||||
|
@ -241,8 +244,7 @@ where
|
||||||
FallbackFn: Fn() -> Fallback + Send + 'static,
|
FallbackFn: Fn() -> Fallback + Send + 'static,
|
||||||
Fallback: IntoView + 'static,
|
Fallback: IntoView + 'static,
|
||||||
{
|
{
|
||||||
use either_of::Either;
|
let location = use_context::<BrowserUrl>();
|
||||||
|
|
||||||
let RouterContext {
|
let RouterContext {
|
||||||
current_url, base, ..
|
current_url, base, ..
|
||||||
} = use_context()
|
} = use_context()
|
||||||
|
@ -264,12 +266,16 @@ where
|
||||||
let outer_owner =
|
let outer_owner =
|
||||||
Owner::current().expect("creating Router, but no Owner was found");
|
Owner::current().expect("creating Router, but no Owner was found");
|
||||||
let params = ArcRwSignal::new(ParamsMap::new());
|
let params = ArcRwSignal::new(ParamsMap::new());
|
||||||
move || FlatRoutesView {
|
move || {
|
||||||
routes: routes.clone(),
|
path.track();
|
||||||
path: path.clone(),
|
FlatRoutesView {
|
||||||
fallback: fallback(),
|
location: location.clone(),
|
||||||
outer_owner: outer_owner.clone(),
|
routes: routes.clone(),
|
||||||
params: params.clone(),
|
path: path.clone(),
|
||||||
|
fallback: fallback(),
|
||||||
|
outer_owner: outer_owner.clone(),
|
||||||
|
params: params.clone(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,25 +1,28 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
location::{Location, RequestUrl, Url},
|
location::{Location, LocationProvider, RequestUrl, Url},
|
||||||
matching::Routes,
|
matching::Routes,
|
||||||
params::ParamsMap,
|
params::ParamsMap,
|
||||||
resolve_path::resolve_path,
|
resolve_path::resolve_path,
|
||||||
ChooseView, MatchInterface, MatchNestedRoutes, MatchParams, Method,
|
ChooseView, MatchInterface, MatchNestedRoutes, MatchParams, Method,
|
||||||
PathSegment, RouteList, RouteListing, RouteMatchId,
|
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 leptos::{component, oco::Oco, IntoView};
|
||||||
use or_poisoned::OrPoisoned;
|
use or_poisoned::OrPoisoned;
|
||||||
use reactive_graph::{
|
use reactive_graph::{
|
||||||
computed::{ArcMemo, Memo},
|
computed::{ArcMemo, Memo, ScopedFuture},
|
||||||
owner::{provide_context, use_context, Owner},
|
owner::{provide_context, use_context, Owner},
|
||||||
signal::{ArcRwSignal, ArcTrigger},
|
signal::{ArcRwSignal, ArcTrigger},
|
||||||
traits::{Get, Read, ReadUntracked, Set, Track, Trigger},
|
traits::{Get, GetUntracked, Read, ReadUntracked, Set, Track, Trigger},
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
|
cell::RefCell,
|
||||||
iter,
|
iter,
|
||||||
marker::PhantomData,
|
marker::PhantomData,
|
||||||
mem,
|
mem,
|
||||||
|
rc::Rc,
|
||||||
sync::{
|
sync::{
|
||||||
mpsc::{self, Receiver, Sender},
|
mpsc::{self, Receiver, Sender},
|
||||||
Arc, Mutex,
|
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 routes: Routes<Defs, R>,
|
||||||
pub path: ArcMemo<String>,
|
pub path: ArcMemo<String>,
|
||||||
pub fallback: Fal,
|
pub fallback: Fal,
|
||||||
|
@ -45,13 +49,14 @@ pub(crate) struct FlatRoutesView<Defs, Fal, R> {
|
||||||
pub params: ArcRwSignal<ParamsMap>,
|
pub params: ArcRwSignal<ParamsMap>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Defs, Fal, R> FlatRoutesView<Defs, Fal, R>
|
impl<Loc, Defs, Fal, R> FlatRoutesView<Loc, Defs, Fal, R>
|
||||||
where
|
where
|
||||||
|
Loc: LocationProvider,
|
||||||
Defs: MatchNestedRoutes<R>,
|
Defs: MatchNestedRoutes<R>,
|
||||||
Fal: Render<R>,
|
Fal: Render<R>,
|
||||||
R: Renderer + 'static,
|
R: Renderer + 'static,
|
||||||
{
|
{
|
||||||
pub fn choose(
|
pub async fn choose(
|
||||||
self,
|
self,
|
||||||
) -> Either<Fal, <Defs::Match as MatchInterface<R>>::View> {
|
) -> Either<Fal, <Defs::Match as MatchInterface<R>>::View> {
|
||||||
let FlatRoutesView {
|
let FlatRoutesView {
|
||||||
|
@ -60,60 +65,121 @@ where
|
||||||
fallback,
|
fallback,
|
||||||
outer_owner,
|
outer_owner,
|
||||||
params,
|
params,
|
||||||
|
..
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
outer_owner.with(|| {
|
outer_owner
|
||||||
provide_context(params.clone().read_only());
|
.with(|| {
|
||||||
let new_match = routes.match_route(&path.read());
|
provide_context(params.clone().read_only());
|
||||||
match new_match {
|
let new_match = routes.match_route(&path.read());
|
||||||
None => Either::Left(fallback),
|
match new_match {
|
||||||
Some(matched) => {
|
None => EitherFuture::Left {
|
||||||
let new_params =
|
inner: async move { fallback },
|
||||||
matched.to_params().into_iter().collect::<ParamsMap>();
|
},
|
||||||
params.set(new_params);
|
Some(matched) => {
|
||||||
let (view, child) = matched.into_view_and_child();
|
let new_params = matched
|
||||||
|
.to_params()
|
||||||
|
.into_iter()
|
||||||
|
.collect::<ParamsMap>();
|
||||||
|
params.set(new_params);
|
||||||
|
let (view, child) = matched.into_view_and_child();
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
if child.is_some() {
|
if child.is_some() {
|
||||||
panic!(
|
panic!(
|
||||||
"<FlatRoutes> should not be used with nested \
|
"<FlatRoutes> should not be used with nested \
|
||||||
routes."
|
routes."
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
EitherFuture::Right {
|
||||||
|
inner: ScopedFuture::new(view.choose()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let view = view.choose();
|
|
||||||
Either::Right(view)
|
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
})
|
.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
|
where
|
||||||
Defs: MatchNestedRoutes<R>,
|
Loc: LocationProvider,
|
||||||
Fal: Render<R>,
|
Defs: MatchNestedRoutes<R> + 'static,
|
||||||
|
Fal: Render<R> + 'static,
|
||||||
R: Renderer + '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 {
|
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) {
|
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
|
where
|
||||||
Defs: MatchNestedRoutes<R> + Send,
|
Loc: LocationProvider + Send,
|
||||||
Fal: RenderHtml<R>,
|
Defs: MatchNestedRoutes<R> + Send + 'static,
|
||||||
|
Fal: RenderHtml<R> + 'static,
|
||||||
R: Renderer + 'static,
|
R: Renderer + 'static,
|
||||||
{
|
{
|
||||||
type Output<SomeNewAttr: leptos::attr::Attribute<R>> =
|
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>>(
|
fn add_any_attr<NewAttr: leptos::attr::Attribute<R>>(
|
||||||
self,
|
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
|
where
|
||||||
Defs: MatchNestedRoutes<R> + Send,
|
Loc: LocationProvider + Send,
|
||||||
Fal: RenderHtml<R>,
|
Defs: MatchNestedRoutes<R> + Send + 'static,
|
||||||
|
Fal: RenderHtml<R> + 'static,
|
||||||
R: Renderer + 'static,
|
R: Renderer + 'static,
|
||||||
{
|
{
|
||||||
type AsyncOutput = Self;
|
type AsyncOutput = Self;
|
||||||
|
@ -190,7 +257,8 @@ where
|
||||||
|
|
||||||
RouteList::register(RouteList::from(routes));
|
RouteList::register(RouteList::from(routes));
|
||||||
} else {
|
} else {
|
||||||
self.choose().to_html_with_buf(buf, position);
|
todo!()
|
||||||
|
// self.choose().to_html_with_buf(buf, position);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,8 +269,9 @@ where
|
||||||
) where
|
) where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
self.choose()
|
todo!()
|
||||||
.to_html_async_with_buf::<OUT_OF_ORDER>(buf, position);
|
// self.choose()
|
||||||
|
// .to_html_async_with_buf::<OUT_OF_ORDER>(buf, position);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hydrate<const FROM_SERVER: bool>(
|
fn hydrate<const FROM_SERVER: bool>(
|
||||||
|
@ -210,6 +279,7 @@ where
|
||||||
cursor: &Cursor<R>,
|
cursor: &Cursor<R>,
|
||||||
position: &PositionState,
|
position: &PositionState,
|
||||||
) -> Self::State {
|
) -> 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 crate::{navigate::UseNavigate, params::ParamsMap};
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
|
use futures::channel::oneshot;
|
||||||
use js_sys::{try_iter, Array, JsString, Reflect};
|
use js_sys::{try_iter, Array, JsString, Reflect};
|
||||||
|
use or_poisoned::OrPoisoned;
|
||||||
use reactive_graph::{signal::ArcRwSignal, traits::Set};
|
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 tachys::dom::{document, window};
|
||||||
use wasm_bindgen::{closure::Closure, JsCast, JsValue};
|
use wasm_bindgen::{closure::Closure, JsCast, JsValue};
|
||||||
use web_sys::{Event, HtmlAnchorElement, MouseEvent, UrlSearchParams};
|
use web_sys::{Event, HtmlAnchorElement, MouseEvent, UrlSearchParams};
|
||||||
|
@ -13,6 +22,7 @@ use web_sys::{Event, HtmlAnchorElement, MouseEvent, UrlSearchParams};
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct BrowserUrl {
|
pub struct BrowserUrl {
|
||||||
url: ArcRwSignal<Url>,
|
url: ArcRwSignal<Url>,
|
||||||
|
pending_navigation: Arc<Mutex<Option<oneshot::Sender<()>>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for BrowserUrl {
|
impl fmt::Debug for BrowserUrl {
|
||||||
|
@ -49,7 +59,11 @@ impl LocationProvider for BrowserUrl {
|
||||||
|
|
||||||
fn new() -> Result<Self, JsValue> {
|
fn new() -> Result<Self, JsValue> {
|
||||||
let url = ArcRwSignal::new(Self::current()?);
|
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> {
|
fn as_url(&self) -> &ArcRwSignal<Url> {
|
||||||
|
@ -94,10 +108,17 @@ impl LocationProvider for BrowserUrl {
|
||||||
let window = window();
|
let window = window();
|
||||||
let navigate = {
|
let navigate = {
|
||||||
let url = self.url.clone();
|
let url = self.url.clone();
|
||||||
|
let pending = Arc::clone(&self.pending_navigation);
|
||||||
move |new_url, loc| {
|
move |new_url, loc| {
|
||||||
|
let (tx, rx) = oneshot::channel::<()>();
|
||||||
|
*pending.lock().or_poisoned() = Some(tx);
|
||||||
url.set(new_url);
|
url.set(new_url);
|
||||||
async move {
|
async move {
|
||||||
Self::complete_navigation(&loc);
|
// 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -146,6 +167,12 @@ impl LocationProvider for BrowserUrl {
|
||||||
.expect("couldn't add `popstate` listener to `window`");
|
.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) {
|
fn complete_navigation(loc: &LocationChange) {
|
||||||
let history = window().history().unwrap();
|
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;
|
type Error: Debug;
|
||||||
|
|
||||||
fn new() -> Result<Self, Self::Error>;
|
fn new() -> Result<Self, Self::Error>;
|
||||||
|
@ -126,6 +126,10 @@ pub trait LocationProvider: Sized {
|
||||||
/// Sets up any global event listeners or other initialization needed.
|
/// Sets up any global event listeners or other initialization needed.
|
||||||
fn init(&self, base: Option<Cow<'static, str>>);
|
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.
|
/// Update the browser's history to reflect a new location.
|
||||||
fn complete_navigation(loc: &LocationChange);
|
fn complete_navigation(loc: &LocationChange);
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use either_of::*;
|
use either_of::*;
|
||||||
|
use std::future::Future;
|
||||||
use tachys::{renderer::Renderer, view::Render};
|
use tachys::{renderer::Renderer, view::Render};
|
||||||
|
|
||||||
pub trait ChooseView<R>
|
pub trait ChooseView<R>
|
||||||
|
@ -6,21 +7,22 @@ where
|
||||||
Self: Send + 'static,
|
Self: Send + 'static,
|
||||||
R: Renderer + '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
|
where
|
||||||
F: Fn() -> View + Send + 'static,
|
F: Fn() -> ViewFut + Send + 'static,
|
||||||
View: Render<R> + Send,
|
ViewFut: Future,
|
||||||
|
ViewFut::Output: Render<R> + Send,
|
||||||
R: Renderer + 'static,
|
R: Renderer + 'static,
|
||||||
{
|
{
|
||||||
type Output = View;
|
type Output = ViewFut::Output;
|
||||||
|
|
||||||
fn choose(self) -> Self::Output {
|
async fn choose(self) -> Self::Output {
|
||||||
self()
|
self().await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,7 +32,7 @@ where
|
||||||
{
|
{
|
||||||
type Output = ();
|
type Output = ();
|
||||||
|
|
||||||
fn choose(self) -> Self::Output {}
|
async fn choose(self) -> Self::Output {}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A, B, Rndr> ChooseView<Rndr> for Either<A, B>
|
impl<A, B, Rndr> ChooseView<Rndr> for Either<A, B>
|
||||||
|
@ -41,10 +43,10 @@ where
|
||||||
{
|
{
|
||||||
type Output = Either<A::Output, B::Output>;
|
type Output = Either<A::Output, B::Output>;
|
||||||
|
|
||||||
fn choose(self) -> Self::Output {
|
async fn choose(self) -> Self::Output {
|
||||||
match self {
|
match self {
|
||||||
Either::Left(f) => Either::Left(f.choose()),
|
Either::Left(f) => Either::Left(f.choose().await),
|
||||||
Either::Right(f) => Either::Right(f.choose()),
|
Either::Right(f) => Either::Right(f.choose().await),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,9 +60,9 @@ macro_rules! tuples {
|
||||||
{
|
{
|
||||||
type Output = $either<$($ty::Output,)*>;
|
type Output = $either<$($ty::Output,)*>;
|
||||||
|
|
||||||
fn choose(self ) -> Self::Output {
|
async fn choose(self ) -> Self::Output {
|
||||||
match self {
|
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 crate::{ChooseView, MatchParams, SsrMode, GeneratedRouteData};
|
||||||
use core::{fmt, iter};
|
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 either_of::Either;
|
||||||
use tachys::{
|
use tachys::{
|
||||||
renderer::Renderer,
|
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>
|
for NestedMatch<ParamsIter, Child, ViewFn>
|
||||||
where
|
where
|
||||||
Rndr: Renderer + 'static,
|
Rndr: Renderer + 'static,
|
||||||
Child: MatchInterface<Rndr> + MatchParams + 'static,
|
Child: MatchInterface<Rndr> + MatchParams + 'static,
|
||||||
ViewFn: Fn() -> View + Send + 'static,
|
ViewFn: Fn() -> ViewFut + Send + 'static,
|
||||||
View: Render<Rndr> + RenderHtml<Rndr> + Send + 'static,
|
ViewFut: Future,
|
||||||
|
ViewFut::Output: Render<Rndr> + RenderHtml<Rndr> + Send + 'static,
|
||||||
{
|
{
|
||||||
type Child = Child;
|
type Child = Child;
|
||||||
type View = ViewFn::Output;
|
type View = ViewFut::Output;
|
||||||
|
|
||||||
fn as_id(&self) -> RouteMatchId {
|
fn as_id(&self) -> RouteMatchId {
|
||||||
self.id
|
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>
|
for NestedRoute<Segments, Children, Data, ViewFn, Rndr>
|
||||||
where
|
where
|
||||||
Self: 'static,
|
Self: 'static,
|
||||||
|
@ -159,11 +160,12 @@ where
|
||||||
Children::Match: MatchParams,
|
Children::Match: MatchParams,
|
||||||
Children: 'static,
|
Children: 'static,
|
||||||
<Children::Match as MatchParams>::Params: Clone,
|
<Children::Match as MatchParams>::Params: Clone,
|
||||||
ViewFn: Fn() -> View + Send + Clone + 'static,
|
ViewFn: Fn() -> ViewFut + Send + Clone + 'static,
|
||||||
View: Render<Rndr> + RenderHtml<Rndr> + Send + 'static,
|
ViewFut: Future,
|
||||||
|
ViewFut::Output: Render<Rndr> + RenderHtml<Rndr> + Send + 'static,
|
||||||
{
|
{
|
||||||
type Data = Data;
|
type Data = Data;
|
||||||
type View = View;
|
type View = ViewFut::Output;
|
||||||
type Match = NestedMatch<iter::Chain<
|
type Match = NestedMatch<iter::Chain<
|
||||||
<Segments::ParamsIter as IntoIterator>::IntoIter,
|
<Segments::ParamsIter as IntoIterator>::IntoIter,
|
||||||
Either<iter::Empty::<
|
Either<iter::Empty::<
|
||||||
|
|
|
@ -1,25 +1,30 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
location::{Location, RequestUrl, Url},
|
location::{Location, LocationProvider, RequestUrl, Url},
|
||||||
matching::Routes,
|
matching::Routes,
|
||||||
params::ParamsMap,
|
params::ParamsMap,
|
||||||
resolve_path::resolve_path,
|
resolve_path::resolve_path,
|
||||||
ChooseView, MatchInterface, MatchNestedRoutes, MatchParams, Method,
|
ChooseView, MatchInterface, MatchNestedRoutes, MatchParams, Method,
|
||||||
PathSegment, RouteList, RouteListing, RouteMatchId,
|
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 leptos::{component, oco::Oco, IntoView};
|
||||||
use or_poisoned::OrPoisoned;
|
use or_poisoned::OrPoisoned;
|
||||||
use reactive_graph::{
|
use reactive_graph::{
|
||||||
computed::{ArcMemo, Memo},
|
computed::{ArcMemo, Memo, ScopedFuture},
|
||||||
owner::{provide_context, use_context, Owner},
|
owner::{provide_context, use_context, Owner},
|
||||||
signal::{ArcRwSignal, ArcTrigger},
|
signal::{ArcRwSignal, ArcTrigger},
|
||||||
traits::{Get, Read, ReadUntracked, Set, Track, Trigger},
|
traits::{Get, Read, ReadUntracked, Set, Track, Trigger},
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
cell::RefCell,
|
||||||
|
future::Future,
|
||||||
iter,
|
iter,
|
||||||
marker::PhantomData,
|
marker::PhantomData,
|
||||||
mem,
|
mem,
|
||||||
|
pin::Pin,
|
||||||
|
rc::Rc,
|
||||||
sync::{
|
sync::{
|
||||||
mpsc::{self, Receiver, Sender},
|
mpsc::{self, Receiver, Sender},
|
||||||
Arc, Mutex,
|
Arc, Mutex,
|
||||||
|
@ -32,7 +37,7 @@ use tachys::{
|
||||||
view::{
|
view::{
|
||||||
add_attr::AddAnyAttr,
|
add_attr::AddAnyAttr,
|
||||||
any_view::{AnyView, AnyViewState, IntoAny},
|
any_view::{AnyView, AnyViewState, IntoAny},
|
||||||
either::EitherState,
|
either::{EitherOf3State, EitherState},
|
||||||
Mountable, Position, PositionState, Render, RenderHtml,
|
Mountable, Position, PositionState, Render, RenderHtml,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -41,7 +46,8 @@ pub struct Outlet<R> {
|
||||||
rndr: PhantomData<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 routes: Routes<Defs, R>,
|
||||||
pub outer_owner: Owner,
|
pub outer_owner: Owner,
|
||||||
pub url: ArcRwSignal<Url>,
|
pub url: ArcRwSignal<Url>,
|
||||||
|
@ -62,15 +68,18 @@ where
|
||||||
path: ArcMemo<String>,
|
path: ArcMemo<String>,
|
||||||
search_params: ArcMemo<ParamsMap>,
|
search_params: ArcMemo<ParamsMap>,
|
||||||
outlets: Vec<RouteContext<R>>,
|
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
|
where
|
||||||
|
Loc: LocationProvider,
|
||||||
Defs: MatchNestedRoutes<R>,
|
Defs: MatchNestedRoutes<R>,
|
||||||
Fal: Render<R>,
|
Fal: Render<R> + 'static,
|
||||||
R: Renderer + 'static,
|
R: Renderer + 'static,
|
||||||
{
|
{
|
||||||
|
// TODO support fallback while loading
|
||||||
type State = NestedRouteViewState<Fal, R>;
|
type State = NestedRouteViewState<Fal, R>;
|
||||||
|
|
||||||
fn build(self) -> Self::State {
|
fn build(self) -> Self::State {
|
||||||
|
@ -85,20 +94,41 @@ where
|
||||||
..
|
..
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
|
let mut loaders = Vec::new();
|
||||||
let mut outlets = Vec::new();
|
let mut outlets = Vec::new();
|
||||||
let new_match = routes.match_route(&path.read());
|
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) => {
|
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(|| {
|
outer_owner.with(|| {
|
||||||
Either::Right(
|
EitherOf3::C(
|
||||||
Outlet(OutletProps::builder().build()).into_any(),
|
Outlet(OutletProps::builder().build()).into_any(),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
.build();
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
matched_view.rebuild(&mut *view.borrow_mut());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
NestedRouteViewState {
|
NestedRouteViewState {
|
||||||
outlets,
|
outlets,
|
||||||
|
@ -115,25 +145,40 @@ where
|
||||||
|
|
||||||
match new_match {
|
match new_match {
|
||||||
None => {
|
None => {
|
||||||
Either::<Fal, AnyView<R>>::Left(self.fallback)
|
EitherOf3::<(), Fal, AnyView<R>>::B(self.fallback)
|
||||||
.rebuild(&mut state.view);
|
.rebuild(&mut state.view.borrow_mut());
|
||||||
state.outlets.clear();
|
state.outlets.clear();
|
||||||
}
|
}
|
||||||
Some(route) => {
|
Some(route) => {
|
||||||
|
let mut loaders = Vec::new();
|
||||||
route.rebuild_nested_route(
|
route.rebuild_nested_route(
|
||||||
self.base,
|
self.base,
|
||||||
&mut 0,
|
&mut 0,
|
||||||
|
&mut loaders,
|
||||||
&mut state.outlets,
|
&mut state.outlets,
|
||||||
&self.outer_owner,
|
&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 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(|| {
|
self.outer_owner.with(|| {
|
||||||
Either::<Fal, AnyView<R>>::Right(
|
EitherOf3::<(), Fal, AnyView<R>>::C(
|
||||||
Outlet(OutletProps::builder().build()).into_any(),
|
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
|
where
|
||||||
|
Loc: LocationProvider + Send,
|
||||||
Defs: MatchNestedRoutes<R> + Send,
|
Defs: MatchNestedRoutes<R> + Send,
|
||||||
Fal: RenderHtml<R>,
|
Fal: RenderHtml<R> + 'static,
|
||||||
R: Renderer + 'static,
|
R: Renderer + 'static,
|
||||||
{
|
{
|
||||||
type Output<SomeNewAttr: leptos::attr::Attribute<R>> =
|
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>>(
|
fn add_any_attr<NewAttr: leptos::attr::Attribute<R>>(
|
||||||
self,
|
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
|
where
|
||||||
|
Loc: LocationProvider + Send,
|
||||||
Defs: MatchNestedRoutes<R> + Send,
|
Defs: MatchNestedRoutes<R> + Send,
|
||||||
Fal: RenderHtml<R>,
|
Fal: RenderHtml<R> + 'static,
|
||||||
R: Renderer + 'static,
|
R: Renderer + 'static,
|
||||||
{
|
{
|
||||||
type AsyncOutput = Self;
|
type AsyncOutput = Self;
|
||||||
|
@ -238,7 +285,13 @@ where
|
||||||
let view = match new_match {
|
let view = match new_match {
|
||||||
None => Either::Left(fallback),
|
None => Either::Left(fallback),
|
||||||
Some(route) => {
|
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(|| {
|
outer_owner.with(|| {
|
||||||
Either::Right(
|
Either::Right(
|
||||||
Outlet(OutletProps::builder().build()).into_any(),
|
Outlet(OutletProps::builder().build()).into_any(),
|
||||||
|
@ -273,7 +326,13 @@ where
|
||||||
let view = match new_match {
|
let view = match new_match {
|
||||||
None => Either::Left(fallback),
|
None => Either::Left(fallback),
|
||||||
Some(route) => {
|
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(|| {
|
outer_owner.with(|| {
|
||||||
Either::Right(
|
Either::Right(
|
||||||
Outlet(OutletProps::builder().build()).into_any(),
|
Outlet(OutletProps::builder().build()).into_any(),
|
||||||
|
@ -302,18 +361,26 @@ where
|
||||||
|
|
||||||
let mut outlets = Vec::new();
|
let mut outlets = Vec::new();
|
||||||
let new_match = routes.match_route(&path.read());
|
let new_match = routes.match_route(&path.read());
|
||||||
let view = match new_match {
|
let view = Rc::new(RefCell::new(
|
||||||
None => Either::Left(fallback),
|
match new_match {
|
||||||
Some(route) => {
|
None => EitherOf3::B(fallback),
|
||||||
route.build_nested_route(base, &mut outlets, &outer_owner);
|
Some(route) => {
|
||||||
outer_owner.with(|| {
|
route.build_nested_route(
|
||||||
Either::Right(
|
base,
|
||||||
Outlet(OutletProps::builder().build()).into_any(),
|
// TODO loaders in hydration
|
||||||
)
|
&mut Vec::new(),
|
||||||
})
|
&mut outlets,
|
||||||
|
&outer_owner,
|
||||||
|
);
|
||||||
|
outer_owner.with(|| {
|
||||||
|
EitherOf3::C(
|
||||||
|
Outlet(OutletProps::builder().build()).into_any(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
.hydrate::<FROM_SERVER>(cursor, position),
|
||||||
.hydrate::<FROM_SERVER>(cursor, position);
|
));
|
||||||
|
|
||||||
NestedRouteViewState {
|
NestedRouteViewState {
|
||||||
outlets,
|
outlets,
|
||||||
|
@ -378,6 +445,7 @@ where
|
||||||
fn build_nested_route(
|
fn build_nested_route(
|
||||||
self,
|
self,
|
||||||
base: Option<Oco<'static, str>>,
|
base: Option<Oco<'static, str>>,
|
||||||
|
loaders: &mut Vec<Pin<Box<dyn Future<Output = ArcTrigger>>>>,
|
||||||
outlets: &mut Vec<RouteContext<R>>,
|
outlets: &mut Vec<RouteContext<R>>,
|
||||||
parent: &Owner,
|
parent: &Owner,
|
||||||
);
|
);
|
||||||
|
@ -386,6 +454,7 @@ where
|
||||||
self,
|
self,
|
||||||
base: Option<Oco<'static, str>>,
|
base: Option<Oco<'static, str>>,
|
||||||
items: &mut usize,
|
items: &mut usize,
|
||||||
|
loaders: &mut Vec<Pin<Box<dyn Future<Output = ArcTrigger>>>>,
|
||||||
outlets: &mut Vec<RouteContext<R>>,
|
outlets: &mut Vec<RouteContext<R>>,
|
||||||
parent: &Owner,
|
parent: &Owner,
|
||||||
);
|
);
|
||||||
|
@ -399,6 +468,7 @@ where
|
||||||
fn build_nested_route(
|
fn build_nested_route(
|
||||||
self,
|
self,
|
||||||
base: Option<Oco<'static, str>>,
|
base: Option<Oco<'static, str>>,
|
||||||
|
loaders: &mut Vec<Pin<Box<dyn Future<Output = ArcTrigger>>>>,
|
||||||
outlets: &mut Vec<RouteContext<R>>,
|
outlets: &mut Vec<RouteContext<R>>,
|
||||||
parent: &Owner,
|
parent: &Owner,
|
||||||
) {
|
) {
|
||||||
|
@ -428,7 +498,7 @@ where
|
||||||
// add this outlet to the end of the outlet stack used for diffing
|
// add this outlet to the end of the outlet stack used for diffing
|
||||||
let outlet = RouteContext {
|
let outlet = RouteContext {
|
||||||
id: self.as_id(),
|
id: self.as_id(),
|
||||||
trigger,
|
trigger: trigger.clone(),
|
||||||
params,
|
params,
|
||||||
owner: owner.clone(),
|
owner: owner.clone(),
|
||||||
matched: ArcRwSignal::new(self.as_matched().to_string()),
|
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
|
// send the initial view through the channel, and recurse through the children
|
||||||
let (view, child) = self.into_view_and_child();
|
let (view, child) = self.into_view_and_child();
|
||||||
|
|
||||||
tx.send(Box::new({
|
loaders.push(Box::pin({
|
||||||
let owner = outlet.owner.clone();
|
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
|
// 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
|
// this is important because to build the view, we need access to the outlet
|
||||||
// and the outlet will be returned from building this child
|
// and the outlet will be returned from building this child
|
||||||
if let Some(child) = 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,
|
self,
|
||||||
base: Option<Oco<'static, str>>,
|
base: Option<Oco<'static, str>>,
|
||||||
items: &mut usize,
|
items: &mut usize,
|
||||||
|
loaders: &mut Vec<Pin<Box<dyn Future<Output = ArcTrigger>>>>,
|
||||||
outlets: &mut Vec<RouteContext<R>>,
|
outlets: &mut Vec<RouteContext<R>>,
|
||||||
parent: &Owner,
|
parent: &Owner,
|
||||||
) {
|
) {
|
||||||
|
@ -470,7 +546,7 @@ where
|
||||||
match current {
|
match current {
|
||||||
// if there's nothing currently in the routes at this point, build from here
|
// if there's nothing currently in the routes at this point, build from here
|
||||||
None => {
|
None => {
|
||||||
self.build_nested_route(base, outlets, parent);
|
self.build_nested_route(base, loaders, outlets, parent);
|
||||||
}
|
}
|
||||||
Some(current) => {
|
Some(current) => {
|
||||||
// a unique ID for each route, which allows us to compare when we get new matches
|
// 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,
|
// 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
|
// and notify the trigger so that the reactive view inside the Outlet tracking
|
||||||
// the trigger runs again
|
// the trigger runs again
|
||||||
current.tx.send({
|
loaders.push(Box::pin({
|
||||||
let owner = owner.clone();
|
let owner = owner.clone();
|
||||||
Box::new(move || {
|
let trigger = current.trigger.clone();
|
||||||
owner.with(|| view.choose().into_any())
|
let tx = current.tx.clone();
|
||||||
})
|
async move {
|
||||||
});
|
let view = owner
|
||||||
current.trigger.trigger();
|
.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
|
// remove all the items lower in the tree
|
||||||
// if this match is different, all its children will also be different
|
// if this match is different, all its children will also be different
|
||||||
|
@ -531,6 +615,7 @@ where
|
||||||
let mut new_outlets = Vec::new();
|
let mut new_outlets = Vec::new();
|
||||||
child.build_nested_route(
|
child.build_nested_route(
|
||||||
base,
|
base,
|
||||||
|
loaders,
|
||||||
&mut new_outlets,
|
&mut new_outlets,
|
||||||
&owner,
|
&owner,
|
||||||
);
|
);
|
||||||
|
@ -545,7 +630,9 @@ where
|
||||||
if let Some(child) = child {
|
if let Some(child) = child {
|
||||||
let owner = current.owner.clone();
|
let owner = current.owner.clone();
|
||||||
*items += 1;
|
*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 || {
|
move || {
|
||||||
trigger.track();
|
trigger.track();
|
||||||
|
rx.try_recv().map(|view| view())
|
||||||
rx.try_recv().map(|view| view()).unwrap()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -305,31 +305,3 @@ where
|
||||||
self.write().insert_before_this(parent, child)
|
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>,)*
|
$($ty: Render<Rndr>,)*
|
||||||
Rndr: Renderer
|
Rndr: Renderer
|
||||||
{
|
{
|
||||||
state: [<EitherOf $num>]<$($ty::State,)*>,
|
pub state: [<EitherOf $num>]<$($ty::State,)*>,
|
||||||
marker: Rndr::Placeholder,
|
pub marker: Rndr::Placeholder,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<$($ty,)* Rndr> Mountable<Rndr> for [<EitherOf $num State>]<$($ty,)* Rndr>
|
impl<$($ty,)* Rndr> Mountable<Rndr> for [<EitherOf $num State>]<$($ty,)* Rndr>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use self::add_attr::AddAnyAttr;
|
use self::add_attr::AddAnyAttr;
|
||||||
use crate::{hydration::Cursor, renderer::Renderer, ssr::StreamBuilder};
|
use crate::{hydration::Cursor, renderer::Renderer, ssr::StreamBuilder};
|
||||||
use parking_lot::RwLock;
|
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 add_attr;
|
||||||
pub mod any_view;
|
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.
|
/// Allows data to be added to a static template.
|
||||||
pub trait ToTemplate {
|
pub trait ToTemplate {
|
||||||
const TEMPLATE: &'static str = "";
|
const TEMPLATE: &'static str = "";
|
||||||
|
|
Loading…
Reference in a new issue