mirror of
https://github.com/leptos-rs/leptos
synced 2024-09-20 14:32:00 +00:00
feat: ErrorBoundary and Suspense
This commit is contained in:
parent
d7c62622ae
commit
c06110128b
26 changed files with 464 additions and 94 deletions
|
@ -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::<i32>());
|
||||
let (value, set_value) = signal("".parse::<i32>());
|
||||
let guard = value.read();
|
||||
|
||||
view! {
|
||||
<h1>"Error Handling"</h1>
|
||||
<label>
|
||||
"Type a number (or something that's not a number!)"
|
||||
<input
|
||||
type="text"
|
||||
value=move || value.get().map(|n| n.to_string()).unwrap_or_default()// TODO guard support here
|
||||
// when input changes, try to parse a number from the input
|
||||
on:input:target=move |ev| set_value.set(ev.target().value().parse::<i32>())
|
||||
/>
|
||||
<h1>"Error Handling"</h1>
|
||||
<label>
|
||||
"Type a number (or something that's not a number!)"
|
||||
<input
|
||||
type="text"
|
||||
value=move || value.get().map(|n| n.to_string()).unwrap_or_default()// TODO guard support here
|
||||
// when input changes, try to parse a number from the input
|
||||
on:input:target=move |ev| set_value.set(ev.target().value().parse::<i32>())
|
||||
/>
|
||||
|
||||
// If an `Err(_) had been rendered inside the <ErrorBoundary/>,
|
||||
// the fallback will be displayed. Otherwise, the children of the
|
||||
// <ErrorBoundary/> will be displayed.
|
||||
/* <ErrorBoundary
|
||||
// the fallback receives a signal containing current errors
|
||||
fallback=|errors| view! {
|
||||
// If an `Err(_) had been rendered inside the <ErrorBoundary/>,
|
||||
// the fallback will be displayed. Otherwise, the children of the
|
||||
// <ErrorBoundary/> will be displayed.
|
||||
<ErrorBoundary
|
||||
// the fallback receives a signal containing current errors
|
||||
fallback=|errors| {
|
||||
let errors = errors.clone();
|
||||
view! {
|
||||
<div class="error">
|
||||
<p>"Not a number! Errors: "</p>
|
||||
// we can render a list of errors
|
||||
|
@ -30,31 +32,23 @@ pub fn App() -> impl IntoView {
|
|||
{move || errors.get()
|
||||
.into_iter()
|
||||
.map(|(_, e)| view! { <li>{e.to_string()}</li>})
|
||||
.collect_view()
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
>*/
|
||||
{move || view! {
|
||||
<p>
|
||||
"You entered "
|
||||
// because `value` is `Result<i32, _>`,
|
||||
// it will render the `i32` if it is `Ok`,
|
||||
// and render nothing and trigger the error boundary
|
||||
// if it is `Err`. It's a signal, so this will dynamically
|
||||
// update when `value` changes
|
||||
{move || value.get()}
|
||||
//<strong>{move || value.get()}</strong>
|
||||
</p>}
|
||||
.catch(|e| view! {
|
||||
<p class="error">{e.to_string()}</p>
|
||||
})
|
||||
}
|
||||
//</ErrorBoundary>
|
||||
</label>
|
||||
}
|
||||
}
|
||||
>
|
||||
<p>
|
||||
"You entered "
|
||||
// because `value` is `Result<i32, _>`,
|
||||
// it will render the `i32` if it is `Ok`,
|
||||
// and render nothing and trigger the error boundary
|
||||
// if it is `Err`. It's a signal, so this will dynamically
|
||||
// update when `value` changes
|
||||
<strong>{move || value.get()}</strong> // TODO render signal directly
|
||||
</p>
|
||||
</ErrorBoundary>
|
||||
</label>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn ErrorBoundary() -> impl IntoView {}
|
||||
|
|
|
@ -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<Vec<String>> {
|
||||
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! { <p><img src={s}/></p> })
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
//.catch(|e| view! { <p class="error">{e.to_string()}</p> })
|
||||
};
|
||||
/*let cats_view = move || {
|
||||
async move {
|
||||
cats.await.map(|cats| {
|
||||
cats.into_iter()
|
||||
.map(|s| view! { <p><img src={s}/></p> })
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
//.catch(|e| view! { <p class="error">{e.to_string()}</p> })
|
||||
}
|
||||
.suspend()
|
||||
};*/
|
||||
|
||||
view! {
|
||||
<div>
|
||||
|
@ -98,11 +102,22 @@ pub fn fetch_example() -> impl IntoView {
|
|||
}
|
||||
/>
|
||||
</label>
|
||||
<ErrorBoundary fallback>
|
||||
<Transition fallback=|| view! { <div>"Loading..."</div> }>
|
||||
{cats_view()}
|
||||
</Transition>
|
||||
</ErrorBoundary>
|
||||
<ErrorBoundary fallback>
|
||||
<Suspense fallback=|| view! { <div>"Loading..."</div> }>
|
||||
<ul>
|
||||
{
|
||||
async move {
|
||||
cats.await.map(|cats| {
|
||||
cats.into_iter()
|
||||
.map(|s| view! { <li><img src={s}/></li> })
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
}
|
||||
.suspend()
|
||||
}
|
||||
</ul>
|
||||
</Suspense>
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<T>(Arc<dyn Fn() -> View<T> + Send + Sync>);
|
||||
|
||||
impl<T> Debug for TypedChildrenFn<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_tuple("TypedChildrenFn").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> TypedChildrenFn<T> {
|
||||
pub fn into_inner(self) -> Arc<dyn Fn() -> View<T> + Send + Sync> {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, C> ToChildren<F> for TypedChildrenFn<C>
|
||||
where
|
||||
F: Fn() -> C + Send + Sync + 'static,
|
||||
C: IntoView,
|
||||
{
|
||||
#[inline]
|
||||
fn to_children(f: F) -> Self {
|
||||
TypedChildrenFn(Arc::new(move || f().into_view()))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -93,6 +93,7 @@ where
|
|||
{
|
||||
type State = ErrorBoundaryViewState<Chil, Fal, Rndr>;
|
||||
type FallibleState = ();
|
||||
type AsyncOutput = ErrorBoundaryView<Chil, FalFn, Fal, Rndr>;
|
||||
|
||||
fn build(self) -> Self::State {
|
||||
let Self {
|
||||
|
@ -169,6 +170,10 @@ where
|
|||
) -> any_error::Result<()> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<Chil, FalFn, Fal, Rndr> RenderHtml<Rndr>
|
||||
|
@ -227,7 +232,7 @@ where
|
|||
Either::Left(chil) => chil.unmount(),
|
||||
Either::Right((fal, _)) => fal.unmount(),
|
||||
});
|
||||
self.placeholder.unmount();
|
||||
//self.placeholder.unmount();
|
||||
}
|
||||
|
||||
fn mount(
|
||||
|
|
|
@ -38,6 +38,7 @@ where
|
|||
impl<T: Render<Dom>> Render<Dom> for View<T> {
|
||||
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<T: Render<Dom>> Render<Dom> for View<T> {
|
|||
) -> any_error::Result<()> {
|
||||
self.0.try_rebuild(state)
|
||||
}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
self.0.resolve().await
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: RenderHtml<Dom>> RenderHtml<Dom> for View<T> {
|
||||
|
|
|
@ -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<Chil, ChilFn, ChilFut>(
|
||||
pub fn Suspense<Chil>(
|
||||
#[prop(optional, into)] fallback: ViewFn,
|
||||
children: AsyncChildren<Chil, ChilFn, ChilFut>,
|
||||
children: TypedChildrenFn<Chil>,
|
||||
) -> impl IntoView
|
||||
where
|
||||
Chil: IntoView + 'static,
|
||||
ChilFn: Fn() -> ChilFut + Send + Clone + 'static,
|
||||
ChilFut: Future<Output = Chil> + 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::<false, _, _>::new(
|
||||
fallback.clone(),
|
||||
(children.clone())(),
|
||||
)
|
||||
})
|
||||
// TODO track
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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<const TRANSITION: bool, FalFn, Chil> {
|
||||
fallback: FalFn,
|
||||
children: Chil,
|
||||
}
|
||||
|
||||
impl<const TRANSITION: bool, FalFn, Chil>
|
||||
SuspenseBoundary<TRANSITION, FalFn, Chil>
|
||||
{
|
||||
pub fn new(fallback: FalFn, children: Chil) -> Self {
|
||||
Self { fallback, children }
|
||||
}
|
||||
}
|
||||
|
||||
impl<const TRANSITION: bool, FalFn, Fal, Chil, Rndr> Render<Rndr>
|
||||
for SuspenseBoundary<TRANSITION, FalFn, Chil>
|
||||
where
|
||||
FalFn: Fn() -> Fal,
|
||||
Fal: Render<Rndr> + 'static,
|
||||
Chil: Render<Rndr> + 'static,
|
||||
Chil::State: 'static,
|
||||
Rndr: Renderer + 'static,
|
||||
{
|
||||
type State = Rc<
|
||||
RefCell<
|
||||
EitherState<
|
||||
Fal::State,
|
||||
<Chil::AsyncOutput as Render<Rndr>>::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,
|
||||
<Chil::AsyncOutput as Render<Rndr>>::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::<Fal, Chil::AsyncOutput>::Right(value)
|
||||
.rebuild(&mut *state);
|
||||
}
|
||||
});
|
||||
|
||||
state
|
||||
}
|
||||
|
||||
fn rebuild(self, state: &mut Self::State) {
|
||||
if !TRANSITION {
|
||||
Either::<Fal, Chil::AsyncOutput>::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::<Fal, Chil::AsyncOutput>::Right(value)
|
||||
.rebuild(&mut *state);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn try_build(self) -> any_error::Result<Self::FallibleState> {
|
||||
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<const TRANSITION: bool, FalFn, Fal, Chil, Rndr> RenderHtml<Rndr>
|
||||
for SuspenseBoundary<TRANSITION, FalFn, Chil>
|
||||
where
|
||||
FalFn: Fn() -> Fal,
|
||||
Fal: RenderHtml<Rndr> + 'static,
|
||||
Chil: RenderHtml<Rndr> + '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<const FROM_SERVER: bool>(
|
||||
self,
|
||||
cursor: &Cursor<Rndr>,
|
||||
position: &PositionState,
|
||||
) -> Self::State {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait FutureViewExt: Sized {
|
||||
fn suspend(self) -> Suspend<false, (), Self>
|
||||
|
@ -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<const TRANSITION: bool, Fal, Fut, Rndr> RenderHtml<Rndr>
|
||||
|
@ -267,3 +404,31 @@ 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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -167,6 +167,7 @@ where
|
|||
{
|
||||
type State = ElementState<At::State, Ch::State, Rndr>;
|
||||
type FallibleState = ElementState<At::State, Ch::FallibleState, Rndr>;
|
||||
type AsyncOutput = HtmlElement<E, At, Ch::AsyncOutput, Rndr>;
|
||||
|
||||
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<E, At, Ch, Rndr> RenderHtml<Rndr> for HtmlElement<E, At, Ch, Rndr>
|
||||
|
|
|
@ -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<Rndr, View> RenderHtml<Rndr> for Island<Rndr, View>
|
||||
|
@ -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<Rndr, View> RenderHtml<Rndr> for IslandChildren<Rndr, View>
|
||||
|
|
|
@ -28,6 +28,7 @@ pub fn doctype<R: Renderer>(value: &'static str) -> Doctype<R> {
|
|||
impl<R: Renderer> Render<R> for Doctype<R> {
|
||||
type State = ();
|
||||
type FallibleState = Self::State;
|
||||
type AsyncOutput = Self;
|
||||
|
||||
fn build(self) -> Self::State {}
|
||||
|
||||
|
@ -43,6 +44,10 @@ impl<R: Renderer> Render<R> for Doctype<R> {
|
|||
) -> any_error::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> RenderHtml<R> for Doctype<R>
|
||||
|
|
|
@ -15,6 +15,7 @@ pub struct OcoStrState<R: Renderer> {
|
|||
impl<R: Renderer> Render<R> for Oco<'static, str> {
|
||||
type State = OcoStrState<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<R: Renderer> Render<R> for Oco<'static, str> {
|
|||
<Self as Render<R>>::rebuild(self, state);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> RenderHtml<R> for Oco<'static, str>
|
||||
|
|
|
@ -52,12 +52,12 @@ macro_rules! render_primitive {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a, G, R: Renderer> Render<R> for ReadGuard<$child_type, G>
|
||||
impl<G, R: Renderer> Render<R> for ReadGuard<$child_type, G>
|
||||
where G: Deref<Target = $child_type>
|
||||
{
|
||||
type State = [<ReadGuard $child_type:camel State>]<R>;
|
||||
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<R>;
|
||||
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<G, R> RenderHtml<R> for ReadGuard<String, G>
|
||||
|
|
|
@ -57,6 +57,8 @@ where
|
|||
type State = RenderEffectState<V::State>;
|
||||
type FallibleState =
|
||||
RenderEffectState<Result<V::FallibleState, Option<AnyError>>>;
|
||||
// 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<T: 'static>(Option<RenderEffect<T>>);
|
||||
|
||||
impl<T> From<RenderEffect<T>> for RenderEffectState<T> {
|
||||
|
|
|
@ -69,6 +69,7 @@ where
|
|||
{
|
||||
type State = OwnedViewState<T::State, R>;
|
||||
type FallibleState = OwnedViewState<T::FallibleState, R>;
|
||||
type AsyncOutput = OwnedView<T::AsyncOutput, R>;
|
||||
|
||||
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<T, R> RenderHtml<R> for OwnedView<T, R>
|
||||
|
|
|
@ -261,6 +261,7 @@ where
|
|||
{
|
||||
type State = AnyViewState<R>;
|
||||
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<R> RenderHtml<R> for AnyView<R>
|
||||
|
|
|
@ -17,7 +17,7 @@ where
|
|||
Rndr: Renderer,
|
||||
{
|
||||
pub state: Either<A, B>,
|
||||
marker: Rndr::Placeholder,
|
||||
pub marker: Rndr::Placeholder,
|
||||
}
|
||||
|
||||
impl<A, B, Rndr> Render<Rndr> for Either<A, B>
|
||||
|
@ -28,6 +28,7 @@ where
|
|||
{
|
||||
type State = EitherState<A::State, B::State, Rndr>;
|
||||
type FallibleState = EitherState<A::FallibleState, B::FallibleState, Rndr>;
|
||||
type AsyncOutput = Either<A::AsyncOutput, B::AsyncOutput>;
|
||||
|
||||
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<A, B, Rndr> Mountable<Rndr> for EitherState<A, B, Rndr>
|
||||
|
@ -248,7 +256,7 @@ macro_rules! tuples {
|
|||
{
|
||||
type State = [<EitherOf $num State>]<$($ty,)* Rndr>;
|
||||
type FallibleState = [<EitherOf $num State>]<$($ty,)* Rndr>;
|
||||
|
||||
type AsyncOutput = [<EitherOf $num>]<$($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 {
|
||||
$([<EitherOf $num>]::$ty(this) => [<EitherOf $num>]::$ty(this.resolve().await),)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Rndr, $($ty,)*> RenderHtml<Rndr> for [<EitherOf $num>]<$($ty,)*>
|
||||
|
|
|
@ -16,6 +16,7 @@ where
|
|||
{
|
||||
type State = ResultState<T::State, R>;
|
||||
type FallibleState = T::State;
|
||||
type AsyncOutput = Result<T::AsyncOutput, E>;
|
||||
|
||||
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<T, Fal, Rndr>;
|
||||
type FallibleState = Self::State;
|
||||
type AsyncOutput = Try<T::AsyncOutput, Fal, FalFn, Rndr>;
|
||||
|
||||
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
|
||||
|
|
|
@ -13,6 +13,7 @@ where
|
|||
{
|
||||
type State = OptionState<T::State, R>;
|
||||
type FallibleState = OptionState<T::FallibleState, R>;
|
||||
type AsyncOutput = Option<T::AsyncOutput>;
|
||||
|
||||
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<T, R> RenderHtml<R> for Option<T>
|
||||
|
@ -189,6 +197,7 @@ where
|
|||
{
|
||||
type State = VecState<T::State, R>;
|
||||
type FallibleState = VecState<T::FallibleState, R>;
|
||||
type AsyncOutput = Vec<T::AsyncOutput>;
|
||||
|
||||
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::<Vec<_>>()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct VecState<T, R>
|
||||
|
|
|
@ -74,6 +74,7 @@ where
|
|||
type State = KeyedState<K, V, Rndr>;
|
||||
// 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<T, I, K, KF, VF, V, Rndr> RenderHtml<Rndr>
|
||||
|
|
|
@ -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<R: Renderer>: Sized {
|
|||
/// and the previous string, to allow for diffing between updates.
|
||||
type State: Mountable<R>;
|
||||
type FallibleState: Mountable<R>;
|
||||
type AsyncOutput: Render<R>;
|
||||
|
||||
/// Creates the view for the first time, without hydrating from existing HTML.
|
||||
fn build(self) -> Self::State;
|
||||
|
@ -39,6 +40,8 @@ pub trait Render<R: Renderer>: Sized {
|
|||
self,
|
||||
state: &mut Self::FallibleState,
|
||||
) -> any_error::Result<()>;
|
||||
|
||||
fn resolve(self) -> impl Future<Output = Self::AsyncOutput>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
|
|
|
@ -21,7 +21,7 @@ macro_rules! render_primitive {
|
|||
paste::paste! {
|
||||
pub struct [<$child_type:camel State>]<R>(R::Text, $child_type) where R: Renderer;
|
||||
|
||||
impl<'a, R: Renderer> Mountable<R> for [<$child_type:camel State>]<R> {
|
||||
impl<R: Renderer> Mountable<R> for [<$child_type:camel State>]<R> {
|
||||
fn unmount(&mut self) {
|
||||
self.0.unmount()
|
||||
}
|
||||
|
@ -44,10 +44,10 @@ macro_rules! render_primitive {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a, R: Renderer> Render<R> for $child_type {
|
||||
impl<R: Renderer> Render<R> for $child_type {
|
||||
type State = [<$child_type:camel State>]<R>;
|
||||
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<R> for $child_type
|
||||
impl<R> RenderHtml<R> for $child_type
|
||||
where
|
||||
R: Renderer,
|
||||
|
||||
|
|
|
@ -130,6 +130,7 @@ where
|
|||
{
|
||||
type State = Option<R::Text>;
|
||||
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::<R>::rebuild(self, state);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn resolve(self) -> futures::future::Ready<Self::AsyncOutput> {
|
||||
futures::future::ready(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const V: &'static str, R> RenderHtml<R> for Static<V>
|
||||
|
|
|
@ -15,6 +15,7 @@ pub struct StrState<'a, R: Renderer> {
|
|||
impl<'a, R: Renderer> Render<R> 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<R> for &'a str {
|
|||
self.rebuild(state);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, R> RenderHtml<R> for &'a str
|
||||
|
@ -142,6 +147,7 @@ pub struct StringState<R: Renderer> {
|
|||
impl<R: Renderer> Render<R> for String {
|
||||
type State = StringState<R>;
|
||||
type FallibleState = Self::State;
|
||||
type AsyncOutput = Self;
|
||||
|
||||
fn build(self) -> Self::State {
|
||||
let node = R::create_text_node(&self);
|
||||
|
@ -167,6 +173,10 @@ impl<R: Renderer> Render<R> for String {
|
|||
self.rebuild(state);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> RenderHtml<R> for String
|
||||
|
@ -241,6 +251,7 @@ pub struct RcStrState<R: Renderer> {
|
|||
impl<R: Renderer> Render<R> for Rc<str> {
|
||||
type State = RcStrState<R>;
|
||||
type FallibleState = Self::State;
|
||||
type AsyncOutput = Self;
|
||||
|
||||
fn build(self) -> Self::State {
|
||||
let node = R::create_text_node(&self);
|
||||
|
@ -266,6 +277,10 @@ impl<R: Renderer> Render<R> for Rc<str> {
|
|||
self.rebuild(state);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> RenderHtml<R> for Rc<str>
|
||||
|
@ -341,6 +356,7 @@ pub struct ArcStrState<R: Renderer> {
|
|||
impl<R: Renderer> Render<R> for Arc<str> {
|
||||
type State = ArcStrState<R>;
|
||||
type FallibleState = Self::State;
|
||||
type AsyncOutput = Self;
|
||||
|
||||
fn build(self) -> Self::State {
|
||||
let node = R::create_text_node(&self);
|
||||
|
@ -366,6 +382,10 @@ impl<R: Renderer> Render<R> for Arc<str> {
|
|||
self.rebuild(state);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> RenderHtml<R> for Arc<str>
|
||||
|
@ -441,6 +461,7 @@ pub struct CowStrState<'a, R: Renderer> {
|
|||
impl<'a, R: Renderer> Render<R> 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<R> for Cow<'a, str> {
|
|||
self.rebuild(state);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, R> RenderHtml<R> for Cow<'a, str>
|
||||
|
|
|
@ -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<V, R>
|
||||
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<V, R> RenderHtml<R> for ViewTemplate<V, R>
|
||||
|
|
|
@ -14,6 +14,7 @@ use const_str_slice_concat::{
|
|||
impl<R: Renderer> Render<R> for () {
|
||||
type State = ();
|
||||
type FallibleState = Self::State;
|
||||
type AsyncOutput = ();
|
||||
|
||||
fn build(self) -> Self::State {}
|
||||
|
||||
|
@ -29,6 +30,8 @@ impl<R: Renderer> Render<R> for () {
|
|||
) -> any_error::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {}
|
||||
}
|
||||
|
||||
impl<R> RenderHtml<R> for ()
|
||||
|
@ -102,6 +105,7 @@ impl ToTemplate for () {
|
|||
impl<A: Render<R>, R: Renderer> Render<R> 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<A: Render<R>, R: Renderer> Render<R> for (A,) {
|
|||
) -> any_error::Result<()> {
|
||||
self.0.try_rebuild(state)
|
||||
}
|
||||
|
||||
async fn resolve(self) -> Self::AsyncOutput {
|
||||
(self.0.resolve().await,)
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, R> RenderHtml<R> 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<Rndr> for ($first, $($ty,)*)
|
||||
|
|
Loading…
Reference in a new issue