diff --git a/examples/error_boundary/src/lib.rs b/examples/error_boundary/src/lib.rs index afdc27a1d..43b289681 100644 --- a/examples/error_boundary/src/lib.rs +++ b/examples/error_boundary/src/lib.rs @@ -1,27 +1,29 @@ -use leptos::{component, create_signal, prelude::*, view, IntoView}; +use leptos::{component, prelude::*, signal, view, ErrorBoundary, IntoView}; #[component] pub fn App() -> impl IntoView { - let (value, set_value) = create_signal(Ok(0)); //"".parse::()); + let (value, set_value) = signal("".parse::()); let guard = value.read(); view! { -

"Error Handling"

- + } } - -#[component] -pub fn ErrorBoundary() -> impl IntoView {} diff --git a/examples/fetch/src/lib.rs b/examples/fetch/src/lib.rs index 013fd0c76..c31c23d89 100644 --- a/examples/fetch/src/lib.rs +++ b/examples/fetch/src/lib.rs @@ -5,7 +5,7 @@ use leptos::{ computed::AsyncDerived, signal::{signal, ArcRwSignal}, }, - view, ErrorBoundary, Errors, IntoView, Transition, + view, ErrorBoundary, Errors, IntoView, Suspense, Transition, }; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -25,6 +25,7 @@ type CatCount = usize; async fn fetch_cats(count: CatCount) -> Result> { if count > 0 { + gloo_timers::future::TimeoutFuture::new(1000).await; // make the request let res = reqwasm::http::Request::get(&format!( "https://api.thecatapi.com/v1/images/search?limit={count}", @@ -76,14 +77,17 @@ pub fn fetch_example() -> impl IntoView { // TODO weaving together Transition and ErrorBoundary is hard with the new async API for // suspense, because Transition expects a Future as its children, and ErrorBoundary isn't a // future - let cats_view = move || async move { - cats.await.map(|cats| { - cats.into_iter() - .map(|s| view! {

}) - .collect::>() - }) - //.catch(|e| view! {

{e.to_string()}

}) - }; + /*let cats_view = move || { + async move { + cats.await.map(|cats| { + cats.into_iter() + .map(|s| view! {

}) + .collect::>() + }) + //.catch(|e| view! {

{e.to_string()}

}) + } + .suspend() + };*/ view! {
@@ -98,11 +102,22 @@ pub fn fetch_example() -> impl IntoView { } /> - - "Loading..."
}> - {cats_view()} - - + + "Loading..." }> +
    + { + async move { + cats.await.map(|cats| { + cats.into_iter() + .map(|s| view! {
  • }) + .collect::>() + }) + } + .suspend() + } +
+
+
} } diff --git a/leptos/src/children.rs b/leptos/src/children.rs index 8de0cde9b..7ec3529e9 100644 --- a/leptos/src/children.rs +++ b/leptos/src/children.rs @@ -222,3 +222,30 @@ where TypedChildrenMut(Box::new(move || f().into_view())) } } + +/// A typed equivalent to [`ChildrenFn`], which takes a generic but preserves type information to +/// allow the compiler to optimize the view more effectively. +pub struct TypedChildrenFn(Arc View + Send + Sync>); + +impl Debug for TypedChildrenFn { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("TypedChildrenFn").finish() + } +} + +impl TypedChildrenFn { + pub fn into_inner(self) -> Arc View + Send + Sync> { + self.0 + } +} + +impl ToChildren for TypedChildrenFn +where + F: Fn() -> C + Send + Sync + 'static, + C: IntoView, +{ + #[inline] + fn to_children(f: F) -> Self { + TypedChildrenFn(Arc::new(move || f().into_view())) + } +} diff --git a/leptos/src/error_boundary.rs b/leptos/src/error_boundary.rs index d1fa51750..2166d8ffb 100644 --- a/leptos/src/error_boundary.rs +++ b/leptos/src/error_boundary.rs @@ -93,6 +93,7 @@ where { type State = ErrorBoundaryViewState; type FallibleState = (); + type AsyncOutput = ErrorBoundaryView; fn build(self) -> Self::State { let Self { @@ -169,6 +170,10 @@ where ) -> any_error::Result<()> { todo!() } + + async fn resolve(self) -> Self::AsyncOutput { + self + } } impl RenderHtml @@ -227,7 +232,7 @@ where Either::Left(chil) => chil.unmount(), Either::Right((fal, _)) => fal.unmount(), }); - self.placeholder.unmount(); + //self.placeholder.unmount(); } fn mount( diff --git a/leptos/src/into_view.rs b/leptos/src/into_view.rs index c16c4b61e..10d224eaf 100644 --- a/leptos/src/into_view.rs +++ b/leptos/src/into_view.rs @@ -38,6 +38,7 @@ where impl> Render for View { type State = T::State; type FallibleState = T::FallibleState; + type AsyncOutput = T::AsyncOutput; fn build(self) -> Self::State { self.0.build() @@ -57,6 +58,10 @@ impl> Render for View { ) -> any_error::Result<()> { self.0.try_rebuild(state) } + + async fn resolve(self) -> Self::AsyncOutput { + self.0.resolve().await + } } impl> RenderHtml for View { diff --git a/leptos/src/suspense_component.rs b/leptos/src/suspense_component.rs index 4a190d115..e2cd20daf 100644 --- a/leptos/src/suspense_component.rs +++ b/leptos/src/suspense_component.rs @@ -1,10 +1,11 @@ use crate::{ - children::{ToChildren, ViewFn}, + children::{ToChildren, TypedChildrenFn, TypedChildrenMut, ViewFn}, IntoView, }; use leptos_macro::component; +use leptos_reactive::untrack; use std::{future::Future, sync::Arc}; -use tachys::prelude::FutureViewExt; +use tachys::{async_views::SuspenseBoundary, prelude::FutureViewExt}; /// An async, typed equivalent to [`Children`], which takes a generic but preserves /// type information to allow the compiler to optimize the view more effectively. @@ -35,21 +36,24 @@ where /// TODO docs! #[component] -pub fn Suspense( +pub fn Suspense( #[prop(optional, into)] fallback: ViewFn, - children: AsyncChildren, + children: TypedChildrenFn, ) -> impl IntoView where Chil: IntoView + 'static, - ChilFn: Fn() -> ChilFut + Send + Clone + 'static, - ChilFut: Future + Send + 'static, { let children = children.into_inner(); + let fallback = move || fallback.clone().run(); // TODO check this against islands move || { - (children.clone())() - .suspend() - .with_fallback(fallback.run()) - .track() + crate::logging::log!("running innner thing"); + untrack(|| { + SuspenseBoundary::::new( + fallback.clone(), + (children.clone())(), + ) + }) + // TODO track } } diff --git a/reactive_graph/tests/async_derived.rs b/reactive_graph/tests/async_derived.rs index 9f474a630..8bbfad3ff 100644 --- a/reactive_graph/tests/async_derived.rs +++ b/reactive_graph/tests/async_derived.rs @@ -17,7 +17,7 @@ async fn arc_async_derived_calculates_eagerly() { 42 }); - assert_eq!(*value.clone().await, 42); + assert_eq!(value.clone().await, 42); std::mem::forget(value); } @@ -34,13 +34,13 @@ async fn arc_async_derived_tracks_signal_change() { signal.get() }); - assert_eq!(*value.clone().await, 10); + assert_eq!(value.clone().await, 10); signal.set(30); sleep(Duration::from_millis(5)).await; - assert_eq!(*value.clone().await, 30); + assert_eq!(value.clone().await, 30); signal.set(50); sleep(Duration::from_millis(5)).await; - assert_eq!(*value.clone().await, 50); + assert_eq!(value.clone().await, 50); std::mem::forget(value); } @@ -56,7 +56,7 @@ async fn async_derived_calculates_eagerly() { 42 }); - assert_eq!(*value.await, 42); + assert_eq!(value.await, 42); } #[tokio::test] diff --git a/tachys/src/async_views/mod.rs b/tachys/src/async_views/mod.rs index 4956f4be7..90ea1fa50 100644 --- a/tachys/src/async_views/mod.rs +++ b/tachys/src/async_views/mod.rs @@ -11,7 +11,139 @@ use any_spawner::Executor; use either_of::Either; use futures::FutureExt; use parking_lot::RwLock; -use std::{fmt::Debug, future::Future, sync::Arc}; +use std::{cell::RefCell, fmt::Debug, future::Future, rc::Rc, sync::Arc}; + +pub struct SuspenseBoundary { + fallback: FalFn, + children: Chil, +} + +impl + SuspenseBoundary +{ + pub fn new(fallback: FalFn, children: Chil) -> Self { + Self { fallback, children } + } +} + +impl Render + for SuspenseBoundary +where + FalFn: Fn() -> Fal, + Fal: Render + 'static, + Chil: Render + 'static, + Chil::State: 'static, + Rndr: Renderer + 'static, +{ + type State = Rc< + RefCell< + EitherState< + Fal::State, + >::State, + Rndr, + >, + >, + >; + type FallibleState = (); + type AsyncOutput = Self; + + fn build(self) -> Self::State { + let fut = self.children.resolve(); + #[cfg(feature = "reactive_graph")] + let fut = reactive_graph::computed::ScopedFuture::new(fut); + + let initial = Either::< + Fal::State, + >::State, + >::Left((self.fallback)().build()); + + // now we can build the initial state + let marker = Rndr::create_placeholder(); + let state = Rc::new(RefCell::new(EitherState { + state: initial, + marker: marker.clone(), + })); + + // if the initial state was pending, spawn a future to wait for it + // spawning immediately means that our now_or_never poll result isn't lost + // if it wasn't pending at first, we don't need to poll the Future again + Executor::spawn_local({ + let state = Rc::clone(&state); + let marker = marker.clone(); + async move { + let mut value = fut.await; + let mut state = state.borrow_mut(); + Either::::Right(value) + .rebuild(&mut *state); + } + }); + + state + } + + fn rebuild(self, state: &mut Self::State) { + if !TRANSITION { + Either::::Left((self.fallback)()) + .rebuild(&mut *state.borrow_mut()); + } + + // spawn the future, and rebuild the state when it resolves + let fut = self.children.resolve(); + #[cfg(feature = "reactive_graph")] + let fut = reactive_graph::computed::ScopedFuture::new(fut); + Executor::spawn_local({ + let state = Rc::clone(state); + async move { + let value = fut.await; + let mut state = state.borrow_mut(); + let fut = Either::::Right(value) + .rebuild(&mut *state); + } + }); + } + + fn try_build(self) -> any_error::Result { + todo!() + } + + fn try_rebuild( + self, + state: &mut Self::FallibleState, + ) -> any_error::Result<()> { + todo!() + } + + // building/rebuild SuspenseBoundary asynchronously just runs the Suspense: + // i.e., if you nest a SuspenseBoundary inside another SuspenseBoundary, the parent will not + // wait for the child to load + async fn resolve(self) -> Self::AsyncOutput { + self + } +} + +impl RenderHtml + for SuspenseBoundary +where + FalFn: Fn() -> Fal, + Fal: RenderHtml + 'static, + Chil: RenderHtml + 'static, + Chil::State: 'static, + Rndr: Renderer + 'static, +{ + const MIN_LENGTH: usize = 0; // TODO + + fn to_html_with_buf(self, buf: &mut String, position: &mut Position) { + todo!() + } + + fn hydrate( + self, + cursor: &Cursor, + position: &PositionState, + ) -> Self::State { + todo!() + } +} pub trait FutureViewExt: Sized { fn suspend(self) -> Suspend @@ -71,6 +203,7 @@ where >; // TODO fallible state/error type FallibleState = Self::State; + type AsyncOutput = Fut::Output; fn build(self) -> Self::State { // poll the future once immediately @@ -134,6 +267,10 @@ where ) -> any_error::Result<()> { todo!() } + + async fn resolve(self) -> Self::AsyncOutput { + self.fut.await + } } impl RenderHtml @@ -267,3 +404,31 @@ where self.write().insert_before_this(parent, child) } } + +impl Mountable + for Rc>> +where + Fal: Mountable, + Output: Mountable, + Rndr: Renderer, +{ + fn unmount(&mut self) { + self.borrow_mut().unmount(); + } + + fn mount( + &mut self, + parent: &::Element, + marker: Option<&::Node>, + ) { + self.borrow_mut().mount(parent, marker); + } + + fn insert_before_this( + &self, + parent: &::Element, + child: &mut dyn Mountable, + ) -> bool { + self.borrow_mut().insert_before_this(parent, child) + } +} diff --git a/tachys/src/html/element/mod.rs b/tachys/src/html/element/mod.rs index 592525dc0..8892ce8e0 100644 --- a/tachys/src/html/element/mod.rs +++ b/tachys/src/html/element/mod.rs @@ -167,6 +167,7 @@ where { type State = ElementState; type FallibleState = ElementState; + type AsyncOutput = HtmlElement; fn rebuild(self, state: &mut Self::State) { let ElementState { @@ -213,6 +214,16 @@ where self.children.try_rebuild(children)?; Ok(()) } + + async fn resolve(self) -> Self::AsyncOutput { + HtmlElement { + tag: self.tag, + // TODO async attributes too + attributes: self.attributes, + children: self.children.resolve().await, + rndr: PhantomData, + } + } } impl RenderHtml for HtmlElement diff --git a/tachys/src/html/islands.rs b/tachys/src/html/islands.rs index e49016e8c..b26d5f3a4 100644 --- a/tachys/src/html/islands.rs +++ b/tachys/src/html/islands.rs @@ -49,6 +49,7 @@ where { type State = View::State; type FallibleState = View::FallibleState; + type AsyncOutput = View::AsyncOutput; fn build(self) -> Self::State { self.view.build() @@ -68,6 +69,10 @@ where ) -> any_error::Result<()> { self.view.try_rebuild(state) } + + async fn resolve(self) -> Self::AsyncOutput { + self.view.resolve().await + } } impl RenderHtml for Island @@ -152,6 +157,7 @@ where { type State = (); type FallibleState = Self::State; + type AsyncOutput = View::AsyncOutput; fn build(self) -> Self::State {} @@ -167,6 +173,11 @@ where ) -> any_error::Result<()> { Ok(()) } + + async fn resolve(self) -> Self::AsyncOutput { + // TODO should this be wrapped? + self.view.resolve().await + } } impl RenderHtml for IslandChildren diff --git a/tachys/src/html/mod.rs b/tachys/src/html/mod.rs index fda9ba744..afccb8a45 100644 --- a/tachys/src/html/mod.rs +++ b/tachys/src/html/mod.rs @@ -28,6 +28,7 @@ pub fn doctype(value: &'static str) -> Doctype { impl Render for Doctype { type State = (); type FallibleState = Self::State; + type AsyncOutput = Self; fn build(self) -> Self::State {} @@ -43,6 +44,10 @@ impl Render for Doctype { ) -> any_error::Result<()> { Ok(()) } + + async fn resolve(self) -> Self::AsyncOutput { + self + } } impl RenderHtml for Doctype diff --git a/tachys/src/oco.rs b/tachys/src/oco.rs index c0e5ba170..3fe8eef3f 100644 --- a/tachys/src/oco.rs +++ b/tachys/src/oco.rs @@ -15,6 +15,7 @@ pub struct OcoStrState { impl Render for Oco<'static, str> { type State = OcoStrState; type FallibleState = Self::State; + type AsyncOutput = Self; fn build(self) -> Self::State { let node = R::create_text_node(&self); @@ -40,6 +41,10 @@ impl Render for Oco<'static, str> { >::rebuild(self, state); Ok(()) } + + async fn resolve(self) -> Self::AsyncOutput { + self + } } impl RenderHtml for Oco<'static, str> diff --git a/tachys/src/reactive_graph/guards.rs b/tachys/src/reactive_graph/guards.rs index 3803ddcb6..8cdb845d4 100644 --- a/tachys/src/reactive_graph/guards.rs +++ b/tachys/src/reactive_graph/guards.rs @@ -52,12 +52,12 @@ macro_rules! render_primitive { } } - impl<'a, G, R: Renderer> Render for ReadGuard<$child_type, G> + impl Render for ReadGuard<$child_type, G> where G: Deref { type State = []; type FallibleState = Self::State; - + type AsyncOutput = Self; fn build(self) -> Self::State { let node = R::create_text_node(&self.to_string()); @@ -79,6 +79,10 @@ macro_rules! render_primitive { fn try_rebuild(self, state: &mut Self::FallibleState) -> any_error::Result<()> { self.rebuild(state); Ok(()) + } + + async fn resolve(self) -> Self::AsyncOutput { + self } } @@ -206,6 +210,7 @@ where { type State = ReadGuardStringState; type FallibleState = Self::State; + type AsyncOutput = Self; fn build(self) -> Self::State { let node = R::create_text_node(&self); @@ -235,6 +240,10 @@ where self.rebuild(state); Ok(()) } + + async fn resolve(self) -> Self::AsyncOutput { + self + } } impl RenderHtml for ReadGuard diff --git a/tachys/src/reactive_graph/mod.rs b/tachys/src/reactive_graph/mod.rs index 92ad5cfff..10ea0d0c0 100644 --- a/tachys/src/reactive_graph/mod.rs +++ b/tachys/src/reactive_graph/mod.rs @@ -57,6 +57,8 @@ where type State = RenderEffectState; type FallibleState = RenderEffectState>>; + // TODO how this should be handled? + type AsyncOutput = Self; #[track_caller] fn build(mut self) -> Self::State { @@ -136,7 +138,6 @@ where self, state: &mut Self::FallibleState, ) -> any_error::Result<()> { - crate::log("RenderEffect::try_rebuild"); if let Some(inner) = &mut state.0 { inner .with_value_mut(|value| match value { @@ -148,8 +149,11 @@ where Ok(()) } } -} + async fn resolve(self) -> Self::AsyncOutput { + self + } +} pub struct RenderEffectState(Option>); impl From> for RenderEffectState { diff --git a/tachys/src/reactive_graph/owned.rs b/tachys/src/reactive_graph/owned.rs index ed1035513..17cdc01f3 100644 --- a/tachys/src/reactive_graph/owned.rs +++ b/tachys/src/reactive_graph/owned.rs @@ -69,6 +69,7 @@ where { type State = OwnedViewState; type FallibleState = OwnedViewState; + type AsyncOutput = OwnedView; fn build(self) -> Self::State { let state = self.owner.with(|| self.view.build()); @@ -91,6 +92,10 @@ where ) -> any_error::Result<()> { todo!() } + + async fn resolve(self) -> Self::AsyncOutput { + todo!() + } } impl RenderHtml for OwnedView diff --git a/tachys/src/view/any_view.rs b/tachys/src/view/any_view.rs index 8e3f1650c..84c19175a 100644 --- a/tachys/src/view/any_view.rs +++ b/tachys/src/view/any_view.rs @@ -261,6 +261,7 @@ where { type State = AnyViewState; type FallibleState = Self::State; + type AsyncOutput = Self; fn build(self) -> Self::State { (self.build)(self.value) @@ -280,6 +281,11 @@ where ) -> any_error::Result<()> { todo!() } + + async fn resolve(self) -> Self::AsyncOutput { + // we probably do need a function for this + todo!() + } } impl RenderHtml for AnyView diff --git a/tachys/src/view/either.rs b/tachys/src/view/either.rs index e2f7afff6..5e85e0864 100644 --- a/tachys/src/view/either.rs +++ b/tachys/src/view/either.rs @@ -17,7 +17,7 @@ where Rndr: Renderer, { pub state: Either, - marker: Rndr::Placeholder, + pub marker: Rndr::Placeholder, } impl Render for Either @@ -28,6 +28,7 @@ where { type State = EitherState; type FallibleState = EitherState; + type AsyncOutput = Either; fn build(self) -> Self::State { let marker = Rndr::create_placeholder(); @@ -73,6 +74,13 @@ where ) -> any_error::Result<()> { todo!() } + + async fn resolve(self) -> Self::AsyncOutput { + match self { + Either::Left(left) => Either::Left(left.resolve().await), + Either::Right(right) => Either::Right(right.resolve().await), + } + } } impl Mountable for EitherState @@ -248,7 +256,7 @@ macro_rules! tuples { { type State = []<$($ty,)* Rndr>; type FallibleState = []<$($ty,)* Rndr>; - + type AsyncOutput = []<$($ty::AsyncOutput,)*>; fn build(self) -> Self::State { let marker = Rndr::create_placeholder(); @@ -290,6 +298,12 @@ macro_rules! tuples { ) -> any_error::Result<()> { todo!() } + + async fn resolve(self) -> Self::AsyncOutput { + match self { + $([]::$ty(this) => []::$ty(this.resolve().await),)* + } + } } impl RenderHtml for []<$($ty,)*> diff --git a/tachys/src/view/error_boundary.rs b/tachys/src/view/error_boundary.rs index 538084879..d5a84b41e 100644 --- a/tachys/src/view/error_boundary.rs +++ b/tachys/src/view/error_boundary.rs @@ -16,6 +16,7 @@ where { type State = ResultState; type FallibleState = T::State; + type AsyncOutput = Result; fn build(self) -> Self::State { let placeholder = R::create_placeholder(); @@ -61,6 +62,13 @@ where ) -> any_error::Result<()> { todo!() } + + async fn resolve(self) -> Self::AsyncOutput { + match self { + Ok(view) => Ok(view.resolve().await), + Err(e) => Err(e), + } + } } /// View state for a `Result<_, _>` view. @@ -234,6 +242,7 @@ where { type State = TryState; type FallibleState = Self::State; + type AsyncOutput = Try; fn build(mut self) -> Self::State { let inner = match self.child.try_build() { @@ -309,6 +318,10 @@ where self.rebuild(state); Ok(()) } + + async fn resolve(self) -> Self::AsyncOutput { + todo!() + } } // TODO RenderHtml implementation for ErrorBoundary diff --git a/tachys/src/view/iterators.rs b/tachys/src/view/iterators.rs index 3bc4835f6..ac37a4897 100644 --- a/tachys/src/view/iterators.rs +++ b/tachys/src/view/iterators.rs @@ -13,6 +13,7 @@ where { type State = OptionState; type FallibleState = OptionState; + type AsyncOutput = Option; fn build(self) -> Self::State { let placeholder = R::create_placeholder(); @@ -71,6 +72,13 @@ where ) -> any_error::Result<()> { todo!() } + + async fn resolve(self) -> Self::AsyncOutput { + match self { + None => None, + Some(value) => Some(value.resolve().await), + } + } } impl RenderHtml for Option @@ -189,6 +197,7 @@ where { type State = VecState; type FallibleState = VecState; + type AsyncOutput = Vec; fn build(self) -> Self::State { VecState { @@ -264,6 +273,13 @@ where ) -> any_error::Result<()> { todo!() } + + async fn resolve(self) -> Self::AsyncOutput { + futures::future::join_all(self.into_iter().map(T::resolve)) + .await + .into_iter() + .collect::>() + } } pub struct VecState diff --git a/tachys/src/view/keyed.rs b/tachys/src/view/keyed.rs index fe7bfe620..a5e20df76 100644 --- a/tachys/src/view/keyed.rs +++ b/tachys/src/view/keyed.rs @@ -74,6 +74,7 @@ where type State = KeyedState; // TODO fallible state and try_build()/try_rebuild() here type FallibleState = Self::State; + type AsyncOutput = Self; fn build(self) -> Self::State { let items = self.items.into_iter(); @@ -138,6 +139,10 @@ where ) -> any_error::Result<()> { todo!() } + + async fn resolve(self) -> Self::AsyncOutput { + todo!() + } } impl RenderHtml diff --git a/tachys/src/view/mod.rs b/tachys/src/view/mod.rs index 3d891a23e..82a327f41 100644 --- a/tachys/src/view/mod.rs +++ b/tachys/src/view/mod.rs @@ -1,6 +1,6 @@ use crate::{hydration::Cursor, renderer::Renderer, ssr::StreamBuilder}; use parking_lot::RwLock; -use std::sync::Arc; +use std::{future::Future, sync::Arc}; pub mod add_attr; pub mod any_view; @@ -26,6 +26,7 @@ pub trait Render: Sized { /// and the previous string, to allow for diffing between updates. type State: Mountable; type FallibleState: Mountable; + type AsyncOutput: Render; /// Creates the view for the first time, without hydrating from existing HTML. fn build(self) -> Self::State; @@ -39,6 +40,8 @@ pub trait Render: Sized { self, state: &mut Self::FallibleState, ) -> any_error::Result<()>; + + fn resolve(self) -> impl Future; } #[derive(Debug, Clone, Copy)] diff --git a/tachys/src/view/primitives.rs b/tachys/src/view/primitives.rs index f76c53814..402dffa33 100644 --- a/tachys/src/view/primitives.rs +++ b/tachys/src/view/primitives.rs @@ -21,7 +21,7 @@ macro_rules! render_primitive { paste::paste! { pub struct [<$child_type:camel State>](R::Text, $child_type) where R: Renderer; - impl<'a, R: Renderer> Mountable for [<$child_type:camel State>] { + impl Mountable for [<$child_type:camel State>] { fn unmount(&mut self) { self.0.unmount() } @@ -44,10 +44,10 @@ macro_rules! render_primitive { } } - impl<'a, R: Renderer> Render for $child_type { + impl Render for $child_type { type State = [<$child_type:camel State>]; type FallibleState = Self::State; - + type AsyncOutput = Self; fn build(self) -> Self::State { let node = R::create_text_node(&self.to_string()); @@ -68,11 +68,15 @@ macro_rules! render_primitive { fn try_rebuild(self, state: &mut Self::FallibleState) -> any_error::Result<()> { self.rebuild(state); -Ok(()) + Ok(()) + } + + async fn resolve(self) -> Self::AsyncOutput { + self } } - impl<'a, R> RenderHtml for $child_type + impl RenderHtml for $child_type where R: Renderer, diff --git a/tachys/src/view/static_types.rs b/tachys/src/view/static_types.rs index 7b9e1b75c..fffd47726 100644 --- a/tachys/src/view/static_types.rs +++ b/tachys/src/view/static_types.rs @@ -130,6 +130,7 @@ where { type State = Option; type FallibleState = Self::State; + type AsyncOutput = Self; fn build(self) -> Self::State { // a view state has to be returned so it can be mounted @@ -150,6 +151,10 @@ where Render::::rebuild(self, state); Ok(()) } + + fn resolve(self) -> futures::future::Ready { + futures::future::ready(self) + } } impl RenderHtml for Static diff --git a/tachys/src/view/strings.rs b/tachys/src/view/strings.rs index 91a09fa68..95dd97705 100644 --- a/tachys/src/view/strings.rs +++ b/tachys/src/view/strings.rs @@ -15,6 +15,7 @@ pub struct StrState<'a, R: Renderer> { impl<'a, R: Renderer> Render for &'a str { type State = StrState<'a, R>; type FallibleState = Self::State; + type AsyncOutput = Self; fn build(self) -> Self::State { let node = R::create_text_node(self); @@ -40,6 +41,10 @@ impl<'a, R: Renderer> Render for &'a str { self.rebuild(state); Ok(()) } + + async fn resolve(self) -> Self::AsyncOutput { + self + } } impl<'a, R> RenderHtml for &'a str @@ -142,6 +147,7 @@ pub struct StringState { impl Render for String { type State = StringState; type FallibleState = Self::State; + type AsyncOutput = Self; fn build(self) -> Self::State { let node = R::create_text_node(&self); @@ -167,6 +173,10 @@ impl Render for String { self.rebuild(state); Ok(()) } + + async fn resolve(self) -> Self::AsyncOutput { + self + } } impl RenderHtml for String @@ -241,6 +251,7 @@ pub struct RcStrState { impl Render for Rc { type State = RcStrState; type FallibleState = Self::State; + type AsyncOutput = Self; fn build(self) -> Self::State { let node = R::create_text_node(&self); @@ -266,6 +277,10 @@ impl Render for Rc { self.rebuild(state); Ok(()) } + + async fn resolve(self) -> Self::AsyncOutput { + self + } } impl RenderHtml for Rc @@ -341,6 +356,7 @@ pub struct ArcStrState { impl Render for Arc { type State = ArcStrState; type FallibleState = Self::State; + type AsyncOutput = Self; fn build(self) -> Self::State { let node = R::create_text_node(&self); @@ -366,6 +382,10 @@ impl Render for Arc { self.rebuild(state); Ok(()) } + + async fn resolve(self) -> Self::AsyncOutput { + self + } } impl RenderHtml for Arc @@ -441,6 +461,7 @@ pub struct CowStrState<'a, R: Renderer> { impl<'a, R: Renderer> Render for Cow<'a, str> { type State = CowStrState<'a, R>; type FallibleState = Self::State; + type AsyncOutput = Self; fn build(self) -> Self::State { let node = R::create_text_node(&self); @@ -466,6 +487,10 @@ impl<'a, R: Renderer> Render for Cow<'a, str> { self.rebuild(state); Ok(()) } + + async fn resolve(self) -> Self::AsyncOutput { + self + } } impl<'a, R> RenderHtml for Cow<'a, str> diff --git a/tachys/src/view/template.rs b/tachys/src/view/template.rs index 23037e5b7..6be53ecc6 100644 --- a/tachys/src/view/template.rs +++ b/tachys/src/view/template.rs @@ -1,16 +1,8 @@ use super::{ Mountable, Position, PositionState, Render, RenderHtml, ToTemplate, }; -use crate::{ - dom::document, - hydration::Cursor, - renderer::{dom::Dom, DomRenderer}, -}; -use linear_map::LinearMap; -use once_cell::unsync::Lazy; -use std::{any::TypeId, cell::RefCell, marker::PhantomData}; -use wasm_bindgen::JsCast; -use web_sys::HtmlTemplateElement; +use crate::{hydration::Cursor, renderer::DomRenderer}; +use std::marker::PhantomData; pub struct ViewTemplate where @@ -46,6 +38,7 @@ where { type State = V::State; type FallibleState = V::FallibleState; + type AsyncOutput = Self; // TODO try_build/try_rebuild() @@ -70,6 +63,10 @@ where ) -> any_error::Result<()> { todo!() } + + async fn resolve(self) -> Self::AsyncOutput { + self + } } impl RenderHtml for ViewTemplate diff --git a/tachys/src/view/tuples.rs b/tachys/src/view/tuples.rs index e2714cb72..2ea3f0269 100644 --- a/tachys/src/view/tuples.rs +++ b/tachys/src/view/tuples.rs @@ -14,6 +14,7 @@ use const_str_slice_concat::{ impl Render for () { type State = (); type FallibleState = Self::State; + type AsyncOutput = (); fn build(self) -> Self::State {} @@ -29,6 +30,8 @@ impl Render for () { ) -> any_error::Result<()> { Ok(()) } + + async fn resolve(self) -> Self::AsyncOutput {} } impl RenderHtml for () @@ -102,6 +105,7 @@ impl ToTemplate for () { impl, R: Renderer> Render for (A,) { type State = A::State; type FallibleState = A::FallibleState; + type AsyncOutput = (A::AsyncOutput,); fn build(self) -> Self::State { self.0.build() @@ -121,6 +125,10 @@ impl, R: Renderer> Render for (A,) { ) -> any_error::Result<()> { self.0.try_rebuild(state) } + + async fn resolve(self) -> Self::AsyncOutput { + (self.0.resolve().await,) + } } impl RenderHtml for (A,) @@ -210,8 +218,8 @@ macro_rules! impl_view_for_tuples { Rndr: Renderer { type State = ($first::State, $($ty::State,)*); - type FallibleState = ($first::FallibleState, $($ty::FallibleState,)*); + type AsyncOutput = ($first::AsyncOutput, $($ty::AsyncOutput,)*); fn build(self) -> Self::State { #[allow(non_snake_case)] @@ -249,6 +257,15 @@ macro_rules! impl_view_for_tuples { } Ok(()) } + + async fn resolve(self) -> Self::AsyncOutput { + #[allow(non_snake_case)] + let ($first, $($ty,)*) = self; + futures::join!( + $first.resolve(), + $($ty.resolve()),* + ) + } } impl<$first, $($ty),*, Rndr> RenderHtml for ($first, $($ty,)*)