mirror of
https://github.com/leptos-rs/leptos
synced 2024-11-10 14:54:16 +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]
|
#[component]
|
||||||
pub fn App() -> impl IntoView {
|
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();
|
let guard = value.read();
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
<h1>"Error Handling"</h1>
|
<h1>"Error Handling"</h1>
|
||||||
<label>
|
<label>
|
||||||
"Type a number (or something that's not a number!)"
|
"Type a number (or something that's not a number!)"
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value=move || value.get().map(|n| n.to_string()).unwrap_or_default()// TODO guard support here
|
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
|
// when input changes, try to parse a number from the input
|
||||||
on:input:target=move |ev| set_value.set(ev.target().value().parse::<i32>())
|
on:input:target=move |ev| set_value.set(ev.target().value().parse::<i32>())
|
||||||
/>
|
/>
|
||||||
|
|
||||||
// If an `Err(_) had been rendered inside the <ErrorBoundary/>,
|
// If an `Err(_) had been rendered inside the <ErrorBoundary/>,
|
||||||
// the fallback will be displayed. Otherwise, the children of the
|
// the fallback will be displayed. Otherwise, the children of the
|
||||||
// <ErrorBoundary/> will be displayed.
|
// <ErrorBoundary/> will be displayed.
|
||||||
/* <ErrorBoundary
|
<ErrorBoundary
|
||||||
// the fallback receives a signal containing current errors
|
// the fallback receives a signal containing current errors
|
||||||
fallback=|errors| view! {
|
fallback=|errors| {
|
||||||
|
let errors = errors.clone();
|
||||||
|
view! {
|
||||||
<div class="error">
|
<div class="error">
|
||||||
<p>"Not a number! Errors: "</p>
|
<p>"Not a number! Errors: "</p>
|
||||||
// we can render a list of errors
|
// we can render a list of errors
|
||||||
|
@ -30,31 +32,23 @@ pub fn App() -> impl IntoView {
|
||||||
{move || errors.get()
|
{move || errors.get()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(_, e)| view! { <li>{e.to_string()}</li>})
|
.map(|(_, e)| view! { <li>{e.to_string()}</li>})
|
||||||
.collect_view()
|
.collect::<Vec<_>>()
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>*/
|
}
|
||||||
{move || view! {
|
>
|
||||||
<p>
|
<p>
|
||||||
"You entered "
|
"You entered "
|
||||||
// because `value` is `Result<i32, _>`,
|
// because `value` is `Result<i32, _>`,
|
||||||
// it will render the `i32` if it is `Ok`,
|
// it will render the `i32` if it is `Ok`,
|
||||||
// and render nothing and trigger the error boundary
|
// and render nothing and trigger the error boundary
|
||||||
// if it is `Err`. It's a signal, so this will dynamically
|
// if it is `Err`. It's a signal, so this will dynamically
|
||||||
// update when `value` changes
|
// update when `value` changes
|
||||||
{move || value.get()}
|
<strong>{move || value.get()}</strong> // TODO render signal directly
|
||||||
//<strong>{move || value.get()}</strong>
|
</p>
|
||||||
</p>}
|
</ErrorBoundary>
|
||||||
.catch(|e| view! {
|
</label>
|
||||||
<p class="error">{e.to_string()}</p>
|
}
|
||||||
})
|
|
||||||
}
|
|
||||||
//</ErrorBoundary>
|
|
||||||
</label>
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[component]
|
|
||||||
pub fn ErrorBoundary() -> impl IntoView {}
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ use leptos::{
|
||||||
computed::AsyncDerived,
|
computed::AsyncDerived,
|
||||||
signal::{signal, ArcRwSignal},
|
signal::{signal, ArcRwSignal},
|
||||||
},
|
},
|
||||||
view, ErrorBoundary, Errors, IntoView, Transition,
|
view, ErrorBoundary, Errors, IntoView, Suspense, Transition,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
@ -25,6 +25,7 @@ type CatCount = usize;
|
||||||
|
|
||||||
async fn fetch_cats(count: CatCount) -> Result<Vec<String>> {
|
async fn fetch_cats(count: CatCount) -> Result<Vec<String>> {
|
||||||
if count > 0 {
|
if count > 0 {
|
||||||
|
gloo_timers::future::TimeoutFuture::new(1000).await;
|
||||||
// make the request
|
// make the request
|
||||||
let res = reqwasm::http::Request::get(&format!(
|
let res = reqwasm::http::Request::get(&format!(
|
||||||
"https://api.thecatapi.com/v1/images/search?limit={count}",
|
"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
|
// 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
|
// suspense, because Transition expects a Future as its children, and ErrorBoundary isn't a
|
||||||
// future
|
// future
|
||||||
let cats_view = move || async move {
|
/*let cats_view = move || {
|
||||||
cats.await.map(|cats| {
|
async move {
|
||||||
cats.into_iter()
|
cats.await.map(|cats| {
|
||||||
.map(|s| view! { <p><img src={s}/></p> })
|
cats.into_iter()
|
||||||
.collect::<Vec<_>>()
|
.map(|s| view! { <p><img src={s}/></p> })
|
||||||
})
|
.collect::<Vec<_>>()
|
||||||
//.catch(|e| view! { <p class="error">{e.to_string()}</p> })
|
})
|
||||||
};
|
//.catch(|e| view! { <p class="error">{e.to_string()}</p> })
|
||||||
|
}
|
||||||
|
.suspend()
|
||||||
|
};*/
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
<div>
|
<div>
|
||||||
|
@ -98,11 +102,22 @@ pub fn fetch_example() -> impl IntoView {
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<ErrorBoundary fallback>
|
<ErrorBoundary fallback>
|
||||||
<Transition fallback=|| view! { <div>"Loading..."</div> }>
|
<Suspense fallback=|| view! { <div>"Loading..."</div> }>
|
||||||
{cats_view()}
|
<ul>
|
||||||
</Transition>
|
{
|
||||||
</ErrorBoundary>
|
async move {
|
||||||
|
cats.await.map(|cats| {
|
||||||
|
cats.into_iter()
|
||||||
|
.map(|s| view! { <li><img src={s}/></li> })
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
.suspend()
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
</Suspense>
|
||||||
|
</ErrorBoundary>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -222,3 +222,30 @@ where
|
||||||
TypedChildrenMut(Box::new(move || f().into_view()))
|
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 State = ErrorBoundaryViewState<Chil, Fal, Rndr>;
|
||||||
type FallibleState = ();
|
type FallibleState = ();
|
||||||
|
type AsyncOutput = ErrorBoundaryView<Chil, FalFn, Fal, Rndr>;
|
||||||
|
|
||||||
fn build(self) -> Self::State {
|
fn build(self) -> Self::State {
|
||||||
let Self {
|
let Self {
|
||||||
|
@ -169,6 +170,10 @@ where
|
||||||
) -> any_error::Result<()> {
|
) -> any_error::Result<()> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn resolve(self) -> Self::AsyncOutput {
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Chil, FalFn, Fal, Rndr> RenderHtml<Rndr>
|
impl<Chil, FalFn, Fal, Rndr> RenderHtml<Rndr>
|
||||||
|
@ -227,7 +232,7 @@ where
|
||||||
Either::Left(chil) => chil.unmount(),
|
Either::Left(chil) => chil.unmount(),
|
||||||
Either::Right((fal, _)) => fal.unmount(),
|
Either::Right((fal, _)) => fal.unmount(),
|
||||||
});
|
});
|
||||||
self.placeholder.unmount();
|
//self.placeholder.unmount();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mount(
|
fn mount(
|
||||||
|
|
|
@ -38,6 +38,7 @@ where
|
||||||
impl<T: Render<Dom>> Render<Dom> for View<T> {
|
impl<T: Render<Dom>> Render<Dom> for View<T> {
|
||||||
type State = T::State;
|
type State = T::State;
|
||||||
type FallibleState = T::FallibleState;
|
type FallibleState = T::FallibleState;
|
||||||
|
type AsyncOutput = T::AsyncOutput;
|
||||||
|
|
||||||
fn build(self) -> Self::State {
|
fn build(self) -> Self::State {
|
||||||
self.0.build()
|
self.0.build()
|
||||||
|
@ -57,6 +58,10 @@ impl<T: Render<Dom>> Render<Dom> for View<T> {
|
||||||
) -> any_error::Result<()> {
|
) -> any_error::Result<()> {
|
||||||
self.0.try_rebuild(state)
|
self.0.try_rebuild(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn resolve(self) -> Self::AsyncOutput {
|
||||||
|
self.0.resolve().await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: RenderHtml<Dom>> RenderHtml<Dom> for View<T> {
|
impl<T: RenderHtml<Dom>> RenderHtml<Dom> for View<T> {
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
children::{ToChildren, ViewFn},
|
children::{ToChildren, TypedChildrenFn, TypedChildrenMut, ViewFn},
|
||||||
IntoView,
|
IntoView,
|
||||||
};
|
};
|
||||||
use leptos_macro::component;
|
use leptos_macro::component;
|
||||||
|
use leptos_reactive::untrack;
|
||||||
use std::{future::Future, sync::Arc};
|
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
|
/// An async, typed equivalent to [`Children`], which takes a generic but preserves
|
||||||
/// type information to allow the compiler to optimize the view more effectively.
|
/// type information to allow the compiler to optimize the view more effectively.
|
||||||
|
@ -35,21 +36,24 @@ where
|
||||||
|
|
||||||
/// TODO docs!
|
/// TODO docs!
|
||||||
#[component]
|
#[component]
|
||||||
pub fn Suspense<Chil, ChilFn, ChilFut>(
|
pub fn Suspense<Chil>(
|
||||||
#[prop(optional, into)] fallback: ViewFn,
|
#[prop(optional, into)] fallback: ViewFn,
|
||||||
children: AsyncChildren<Chil, ChilFn, ChilFut>,
|
children: TypedChildrenFn<Chil>,
|
||||||
) -> impl IntoView
|
) -> impl IntoView
|
||||||
where
|
where
|
||||||
Chil: IntoView + 'static,
|
Chil: IntoView + 'static,
|
||||||
ChilFn: Fn() -> ChilFut + Send + Clone + 'static,
|
|
||||||
ChilFut: Future<Output = Chil> + Send + 'static,
|
|
||||||
{
|
{
|
||||||
let children = children.into_inner();
|
let children = children.into_inner();
|
||||||
|
let fallback = move || fallback.clone().run();
|
||||||
// TODO check this against islands
|
// TODO check this against islands
|
||||||
move || {
|
move || {
|
||||||
(children.clone())()
|
crate::logging::log!("running innner thing");
|
||||||
.suspend()
|
untrack(|| {
|
||||||
.with_fallback(fallback.run())
|
SuspenseBoundary::<false, _, _>::new(
|
||||||
.track()
|
fallback.clone(),
|
||||||
|
(children.clone())(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
// TODO track
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ async fn arc_async_derived_calculates_eagerly() {
|
||||||
42
|
42
|
||||||
});
|
});
|
||||||
|
|
||||||
assert_eq!(*value.clone().await, 42);
|
assert_eq!(value.clone().await, 42);
|
||||||
std::mem::forget(value);
|
std::mem::forget(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,13 +34,13 @@ async fn arc_async_derived_tracks_signal_change() {
|
||||||
signal.get()
|
signal.get()
|
||||||
});
|
});
|
||||||
|
|
||||||
assert_eq!(*value.clone().await, 10);
|
assert_eq!(value.clone().await, 10);
|
||||||
signal.set(30);
|
signal.set(30);
|
||||||
sleep(Duration::from_millis(5)).await;
|
sleep(Duration::from_millis(5)).await;
|
||||||
assert_eq!(*value.clone().await, 30);
|
assert_eq!(value.clone().await, 30);
|
||||||
signal.set(50);
|
signal.set(50);
|
||||||
sleep(Duration::from_millis(5)).await;
|
sleep(Duration::from_millis(5)).await;
|
||||||
assert_eq!(*value.clone().await, 50);
|
assert_eq!(value.clone().await, 50);
|
||||||
std::mem::forget(value);
|
std::mem::forget(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,7 +56,7 @@ async fn async_derived_calculates_eagerly() {
|
||||||
42
|
42
|
||||||
});
|
});
|
||||||
|
|
||||||
assert_eq!(*value.await, 42);
|
assert_eq!(value.await, 42);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
|
|
@ -11,7 +11,139 @@ use any_spawner::Executor;
|
||||||
use either_of::Either;
|
use either_of::Either;
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
use parking_lot::RwLock;
|
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 {
|
pub trait FutureViewExt: Sized {
|
||||||
fn suspend(self) -> Suspend<false, (), Self>
|
fn suspend(self) -> Suspend<false, (), Self>
|
||||||
|
@ -71,6 +203,7 @@ where
|
||||||
>;
|
>;
|
||||||
// TODO fallible state/error
|
// TODO fallible state/error
|
||||||
type FallibleState = Self::State;
|
type FallibleState = Self::State;
|
||||||
|
type AsyncOutput = Fut::Output;
|
||||||
|
|
||||||
fn build(self) -> Self::State {
|
fn build(self) -> Self::State {
|
||||||
// poll the future once immediately
|
// poll the future once immediately
|
||||||
|
@ -134,6 +267,10 @@ where
|
||||||
) -> any_error::Result<()> {
|
) -> any_error::Result<()> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn resolve(self) -> Self::AsyncOutput {
|
||||||
|
self.fut.await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const TRANSITION: bool, Fal, Fut, Rndr> RenderHtml<Rndr>
|
impl<const TRANSITION: bool, Fal, Fut, Rndr> RenderHtml<Rndr>
|
||||||
|
@ -267,3 +404,31 @@ 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -167,6 +167,7 @@ where
|
||||||
{
|
{
|
||||||
type State = ElementState<At::State, Ch::State, Rndr>;
|
type State = ElementState<At::State, Ch::State, Rndr>;
|
||||||
type FallibleState = ElementState<At::State, Ch::FallibleState, Rndr>;
|
type FallibleState = ElementState<At::State, Ch::FallibleState, Rndr>;
|
||||||
|
type AsyncOutput = HtmlElement<E, At, Ch::AsyncOutput, Rndr>;
|
||||||
|
|
||||||
fn rebuild(self, state: &mut Self::State) {
|
fn rebuild(self, state: &mut Self::State) {
|
||||||
let ElementState {
|
let ElementState {
|
||||||
|
@ -213,6 +214,16 @@ where
|
||||||
self.children.try_rebuild(children)?;
|
self.children.try_rebuild(children)?;
|
||||||
Ok(())
|
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>
|
impl<E, At, Ch, Rndr> RenderHtml<Rndr> for HtmlElement<E, At, Ch, Rndr>
|
||||||
|
|
|
@ -49,6 +49,7 @@ where
|
||||||
{
|
{
|
||||||
type State = View::State;
|
type State = View::State;
|
||||||
type FallibleState = View::FallibleState;
|
type FallibleState = View::FallibleState;
|
||||||
|
type AsyncOutput = View::AsyncOutput;
|
||||||
|
|
||||||
fn build(self) -> Self::State {
|
fn build(self) -> Self::State {
|
||||||
self.view.build()
|
self.view.build()
|
||||||
|
@ -68,6 +69,10 @@ where
|
||||||
) -> any_error::Result<()> {
|
) -> any_error::Result<()> {
|
||||||
self.view.try_rebuild(state)
|
self.view.try_rebuild(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn resolve(self) -> Self::AsyncOutput {
|
||||||
|
self.view.resolve().await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Rndr, View> RenderHtml<Rndr> for Island<Rndr, View>
|
impl<Rndr, View> RenderHtml<Rndr> for Island<Rndr, View>
|
||||||
|
@ -152,6 +157,7 @@ where
|
||||||
{
|
{
|
||||||
type State = ();
|
type State = ();
|
||||||
type FallibleState = Self::State;
|
type FallibleState = Self::State;
|
||||||
|
type AsyncOutput = View::AsyncOutput;
|
||||||
|
|
||||||
fn build(self) -> Self::State {}
|
fn build(self) -> Self::State {}
|
||||||
|
|
||||||
|
@ -167,6 +173,11 @@ where
|
||||||
) -> any_error::Result<()> {
|
) -> any_error::Result<()> {
|
||||||
Ok(())
|
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>
|
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> {
|
impl<R: Renderer> Render<R> for Doctype<R> {
|
||||||
type State = ();
|
type State = ();
|
||||||
type FallibleState = Self::State;
|
type FallibleState = Self::State;
|
||||||
|
type AsyncOutput = Self;
|
||||||
|
|
||||||
fn build(self) -> Self::State {}
|
fn build(self) -> Self::State {}
|
||||||
|
|
||||||
|
@ -43,6 +44,10 @@ impl<R: Renderer> Render<R> for Doctype<R> {
|
||||||
) -> any_error::Result<()> {
|
) -> any_error::Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn resolve(self) -> Self::AsyncOutput {
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R> RenderHtml<R> for Doctype<R>
|
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> {
|
impl<R: Renderer> Render<R> for Oco<'static, str> {
|
||||||
type State = OcoStrState<R>;
|
type State = OcoStrState<R>;
|
||||||
type FallibleState = Self::State;
|
type FallibleState = Self::State;
|
||||||
|
type AsyncOutput = Self;
|
||||||
|
|
||||||
fn build(self) -> Self::State {
|
fn build(self) -> Self::State {
|
||||||
let node = R::create_text_node(&self);
|
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);
|
<Self as Render<R>>::rebuild(self, state);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn resolve(self) -> Self::AsyncOutput {
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R> RenderHtml<R> for Oco<'static, str>
|
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>
|
where G: Deref<Target = $child_type>
|
||||||
{
|
{
|
||||||
type State = [<ReadGuard $child_type:camel State>]<R>;
|
type State = [<ReadGuard $child_type:camel State>]<R>;
|
||||||
type FallibleState = Self::State;
|
type FallibleState = Self::State;
|
||||||
|
type AsyncOutput = Self;
|
||||||
|
|
||||||
fn build(self) -> Self::State {
|
fn build(self) -> Self::State {
|
||||||
let node = R::create_text_node(&self.to_string());
|
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<()> {
|
fn try_rebuild(self, state: &mut Self::FallibleState) -> any_error::Result<()> {
|
||||||
self.rebuild(state);
|
self.rebuild(state);
|
||||||
Ok(())
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn resolve(self) -> Self::AsyncOutput {
|
||||||
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -206,6 +210,7 @@ where
|
||||||
{
|
{
|
||||||
type State = ReadGuardStringState<R>;
|
type State = ReadGuardStringState<R>;
|
||||||
type FallibleState = Self::State;
|
type FallibleState = Self::State;
|
||||||
|
type AsyncOutput = Self;
|
||||||
|
|
||||||
fn build(self) -> Self::State {
|
fn build(self) -> Self::State {
|
||||||
let node = R::create_text_node(&self);
|
let node = R::create_text_node(&self);
|
||||||
|
@ -235,6 +240,10 @@ where
|
||||||
self.rebuild(state);
|
self.rebuild(state);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn resolve(self) -> Self::AsyncOutput {
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<G, R> RenderHtml<R> for ReadGuard<String, G>
|
impl<G, R> RenderHtml<R> for ReadGuard<String, G>
|
||||||
|
|
|
@ -57,6 +57,8 @@ where
|
||||||
type State = RenderEffectState<V::State>;
|
type State = RenderEffectState<V::State>;
|
||||||
type FallibleState =
|
type FallibleState =
|
||||||
RenderEffectState<Result<V::FallibleState, Option<AnyError>>>;
|
RenderEffectState<Result<V::FallibleState, Option<AnyError>>>;
|
||||||
|
// TODO how this should be handled?
|
||||||
|
type AsyncOutput = Self;
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn build(mut self) -> Self::State {
|
fn build(mut self) -> Self::State {
|
||||||
|
@ -136,7 +138,6 @@ where
|
||||||
self,
|
self,
|
||||||
state: &mut Self::FallibleState,
|
state: &mut Self::FallibleState,
|
||||||
) -> any_error::Result<()> {
|
) -> any_error::Result<()> {
|
||||||
crate::log("RenderEffect::try_rebuild");
|
|
||||||
if let Some(inner) = &mut state.0 {
|
if let Some(inner) = &mut state.0 {
|
||||||
inner
|
inner
|
||||||
.with_value_mut(|value| match value {
|
.with_value_mut(|value| match value {
|
||||||
|
@ -148,8 +149,11 @@ where
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
async fn resolve(self) -> Self::AsyncOutput {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
pub struct RenderEffectState<T: 'static>(Option<RenderEffect<T>>);
|
pub struct RenderEffectState<T: 'static>(Option<RenderEffect<T>>);
|
||||||
|
|
||||||
impl<T> From<RenderEffect<T>> for RenderEffectState<T> {
|
impl<T> From<RenderEffect<T>> for RenderEffectState<T> {
|
||||||
|
|
|
@ -69,6 +69,7 @@ where
|
||||||
{
|
{
|
||||||
type State = OwnedViewState<T::State, R>;
|
type State = OwnedViewState<T::State, R>;
|
||||||
type FallibleState = OwnedViewState<T::FallibleState, R>;
|
type FallibleState = OwnedViewState<T::FallibleState, R>;
|
||||||
|
type AsyncOutput = OwnedView<T::AsyncOutput, R>;
|
||||||
|
|
||||||
fn build(self) -> Self::State {
|
fn build(self) -> Self::State {
|
||||||
let state = self.owner.with(|| self.view.build());
|
let state = self.owner.with(|| self.view.build());
|
||||||
|
@ -91,6 +92,10 @@ where
|
||||||
) -> any_error::Result<()> {
|
) -> any_error::Result<()> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn resolve(self) -> Self::AsyncOutput {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, R> RenderHtml<R> for OwnedView<T, R>
|
impl<T, R> RenderHtml<R> for OwnedView<T, R>
|
||||||
|
|
|
@ -261,6 +261,7 @@ where
|
||||||
{
|
{
|
||||||
type State = AnyViewState<R>;
|
type State = AnyViewState<R>;
|
||||||
type FallibleState = Self::State;
|
type FallibleState = Self::State;
|
||||||
|
type AsyncOutput = Self;
|
||||||
|
|
||||||
fn build(self) -> Self::State {
|
fn build(self) -> Self::State {
|
||||||
(self.build)(self.value)
|
(self.build)(self.value)
|
||||||
|
@ -280,6 +281,11 @@ where
|
||||||
) -> any_error::Result<()> {
|
) -> any_error::Result<()> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn resolve(self) -> Self::AsyncOutput {
|
||||||
|
// we probably do need a function for this
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R> RenderHtml<R> for AnyView<R>
|
impl<R> RenderHtml<R> for AnyView<R>
|
||||||
|
|
|
@ -17,7 +17,7 @@ where
|
||||||
Rndr: Renderer,
|
Rndr: Renderer,
|
||||||
{
|
{
|
||||||
pub state: Either<A, B>,
|
pub state: Either<A, B>,
|
||||||
marker: Rndr::Placeholder,
|
pub marker: Rndr::Placeholder,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A, B, Rndr> Render<Rndr> for Either<A, B>
|
impl<A, B, Rndr> Render<Rndr> for Either<A, B>
|
||||||
|
@ -28,6 +28,7 @@ where
|
||||||
{
|
{
|
||||||
type State = EitherState<A::State, B::State, Rndr>;
|
type State = EitherState<A::State, B::State, Rndr>;
|
||||||
type FallibleState = EitherState<A::FallibleState, B::FallibleState, Rndr>;
|
type FallibleState = EitherState<A::FallibleState, B::FallibleState, Rndr>;
|
||||||
|
type AsyncOutput = Either<A::AsyncOutput, B::AsyncOutput>;
|
||||||
|
|
||||||
fn build(self) -> Self::State {
|
fn build(self) -> Self::State {
|
||||||
let marker = Rndr::create_placeholder();
|
let marker = Rndr::create_placeholder();
|
||||||
|
@ -73,6 +74,13 @@ where
|
||||||
) -> any_error::Result<()> {
|
) -> any_error::Result<()> {
|
||||||
todo!()
|
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>
|
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 State = [<EitherOf $num State>]<$($ty,)* Rndr>;
|
||||||
type FallibleState = [<EitherOf $num State>]<$($ty,)* Rndr>;
|
type FallibleState = [<EitherOf $num State>]<$($ty,)* Rndr>;
|
||||||
|
type AsyncOutput = [<EitherOf $num>]<$($ty::AsyncOutput,)*>;
|
||||||
|
|
||||||
fn build(self) -> Self::State {
|
fn build(self) -> Self::State {
|
||||||
let marker = Rndr::create_placeholder();
|
let marker = Rndr::create_placeholder();
|
||||||
|
@ -290,6 +298,12 @@ macro_rules! tuples {
|
||||||
) -> any_error::Result<()> {
|
) -> any_error::Result<()> {
|
||||||
todo!()
|
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,)*>
|
impl<Rndr, $($ty,)*> RenderHtml<Rndr> for [<EitherOf $num>]<$($ty,)*>
|
||||||
|
|
|
@ -16,6 +16,7 @@ where
|
||||||
{
|
{
|
||||||
type State = ResultState<T::State, R>;
|
type State = ResultState<T::State, R>;
|
||||||
type FallibleState = T::State;
|
type FallibleState = T::State;
|
||||||
|
type AsyncOutput = Result<T::AsyncOutput, E>;
|
||||||
|
|
||||||
fn build(self) -> Self::State {
|
fn build(self) -> Self::State {
|
||||||
let placeholder = R::create_placeholder();
|
let placeholder = R::create_placeholder();
|
||||||
|
@ -61,6 +62,13 @@ where
|
||||||
) -> any_error::Result<()> {
|
) -> any_error::Result<()> {
|
||||||
todo!()
|
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.
|
/// View state for a `Result<_, _>` view.
|
||||||
|
@ -234,6 +242,7 @@ where
|
||||||
{
|
{
|
||||||
type State = TryState<T, Fal, Rndr>;
|
type State = TryState<T, Fal, Rndr>;
|
||||||
type FallibleState = Self::State;
|
type FallibleState = Self::State;
|
||||||
|
type AsyncOutput = Try<T::AsyncOutput, Fal, FalFn, Rndr>;
|
||||||
|
|
||||||
fn build(mut self) -> Self::State {
|
fn build(mut self) -> Self::State {
|
||||||
let inner = match self.child.try_build() {
|
let inner = match self.child.try_build() {
|
||||||
|
@ -309,6 +318,10 @@ where
|
||||||
self.rebuild(state);
|
self.rebuild(state);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn resolve(self) -> Self::AsyncOutput {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO RenderHtml implementation for ErrorBoundary
|
// TODO RenderHtml implementation for ErrorBoundary
|
||||||
|
|
|
@ -13,6 +13,7 @@ where
|
||||||
{
|
{
|
||||||
type State = OptionState<T::State, R>;
|
type State = OptionState<T::State, R>;
|
||||||
type FallibleState = OptionState<T::FallibleState, R>;
|
type FallibleState = OptionState<T::FallibleState, R>;
|
||||||
|
type AsyncOutput = Option<T::AsyncOutput>;
|
||||||
|
|
||||||
fn build(self) -> Self::State {
|
fn build(self) -> Self::State {
|
||||||
let placeholder = R::create_placeholder();
|
let placeholder = R::create_placeholder();
|
||||||
|
@ -71,6 +72,13 @@ where
|
||||||
) -> any_error::Result<()> {
|
) -> any_error::Result<()> {
|
||||||
todo!()
|
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>
|
impl<T, R> RenderHtml<R> for Option<T>
|
||||||
|
@ -189,6 +197,7 @@ where
|
||||||
{
|
{
|
||||||
type State = VecState<T::State, R>;
|
type State = VecState<T::State, R>;
|
||||||
type FallibleState = VecState<T::FallibleState, R>;
|
type FallibleState = VecState<T::FallibleState, R>;
|
||||||
|
type AsyncOutput = Vec<T::AsyncOutput>;
|
||||||
|
|
||||||
fn build(self) -> Self::State {
|
fn build(self) -> Self::State {
|
||||||
VecState {
|
VecState {
|
||||||
|
@ -264,6 +273,13 @@ where
|
||||||
) -> any_error::Result<()> {
|
) -> any_error::Result<()> {
|
||||||
todo!()
|
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>
|
pub struct VecState<T, R>
|
||||||
|
|
|
@ -74,6 +74,7 @@ where
|
||||||
type State = KeyedState<K, V, Rndr>;
|
type State = KeyedState<K, V, Rndr>;
|
||||||
// TODO fallible state and try_build()/try_rebuild() here
|
// TODO fallible state and try_build()/try_rebuild() here
|
||||||
type FallibleState = Self::State;
|
type FallibleState = Self::State;
|
||||||
|
type AsyncOutput = Self;
|
||||||
|
|
||||||
fn build(self) -> Self::State {
|
fn build(self) -> Self::State {
|
||||||
let items = self.items.into_iter();
|
let items = self.items.into_iter();
|
||||||
|
@ -138,6 +139,10 @@ where
|
||||||
) -> any_error::Result<()> {
|
) -> any_error::Result<()> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn resolve(self) -> Self::AsyncOutput {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, I, K, KF, VF, V, Rndr> RenderHtml<Rndr>
|
impl<T, I, K, KF, VF, V, Rndr> RenderHtml<Rndr>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
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::sync::Arc;
|
use std::{future::Future, sync::Arc};
|
||||||
|
|
||||||
pub mod add_attr;
|
pub mod add_attr;
|
||||||
pub mod any_view;
|
pub mod any_view;
|
||||||
|
@ -26,6 +26,7 @@ pub trait Render<R: Renderer>: Sized {
|
||||||
/// and the previous string, to allow for diffing between updates.
|
/// and the previous string, to allow for diffing between updates.
|
||||||
type State: Mountable<R>;
|
type State: Mountable<R>;
|
||||||
type FallibleState: Mountable<R>;
|
type FallibleState: Mountable<R>;
|
||||||
|
type AsyncOutput: Render<R>;
|
||||||
|
|
||||||
/// Creates the view for the first time, without hydrating from existing HTML.
|
/// Creates the view for the first time, without hydrating from existing HTML.
|
||||||
fn build(self) -> Self::State;
|
fn build(self) -> Self::State;
|
||||||
|
@ -39,6 +40,8 @@ pub trait Render<R: Renderer>: Sized {
|
||||||
self,
|
self,
|
||||||
state: &mut Self::FallibleState,
|
state: &mut Self::FallibleState,
|
||||||
) -> any_error::Result<()>;
|
) -> any_error::Result<()>;
|
||||||
|
|
||||||
|
fn resolve(self) -> impl Future<Output = Self::AsyncOutput>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
|
|
@ -21,7 +21,7 @@ macro_rules! render_primitive {
|
||||||
paste::paste! {
|
paste::paste! {
|
||||||
pub struct [<$child_type:camel State>]<R>(R::Text, $child_type) where R: Renderer;
|
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) {
|
fn unmount(&mut self) {
|
||||||
self.0.unmount()
|
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 State = [<$child_type:camel State>]<R>;
|
||||||
type FallibleState = Self::State;
|
type FallibleState = Self::State;
|
||||||
|
type AsyncOutput = Self;
|
||||||
|
|
||||||
fn build(self) -> Self::State {
|
fn build(self) -> Self::State {
|
||||||
let node = R::create_text_node(&self.to_string());
|
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<()> {
|
fn try_rebuild(self, state: &mut Self::FallibleState) -> any_error::Result<()> {
|
||||||
self.rebuild(state);
|
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
|
where
|
||||||
R: Renderer,
|
R: Renderer,
|
||||||
|
|
||||||
|
|
|
@ -130,6 +130,7 @@ where
|
||||||
{
|
{
|
||||||
type State = Option<R::Text>;
|
type State = Option<R::Text>;
|
||||||
type FallibleState = Self::State;
|
type FallibleState = Self::State;
|
||||||
|
type AsyncOutput = Self;
|
||||||
|
|
||||||
fn build(self) -> Self::State {
|
fn build(self) -> Self::State {
|
||||||
// a view state has to be returned so it can be mounted
|
// a view state has to be returned so it can be mounted
|
||||||
|
@ -150,6 +151,10 @@ where
|
||||||
Render::<R>::rebuild(self, state);
|
Render::<R>::rebuild(self, state);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn resolve(self) -> futures::future::Ready<Self::AsyncOutput> {
|
||||||
|
futures::future::ready(self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const V: &'static str, R> RenderHtml<R> for Static<V>
|
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 {
|
impl<'a, R: Renderer> Render<R> for &'a str {
|
||||||
type State = StrState<'a, R>;
|
type State = StrState<'a, R>;
|
||||||
type FallibleState = Self::State;
|
type FallibleState = Self::State;
|
||||||
|
type AsyncOutput = Self;
|
||||||
|
|
||||||
fn build(self) -> Self::State {
|
fn build(self) -> Self::State {
|
||||||
let node = R::create_text_node(self);
|
let node = R::create_text_node(self);
|
||||||
|
@ -40,6 +41,10 @@ impl<'a, R: Renderer> Render<R> for &'a str {
|
||||||
self.rebuild(state);
|
self.rebuild(state);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn resolve(self) -> Self::AsyncOutput {
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, R> RenderHtml<R> for &'a str
|
impl<'a, R> RenderHtml<R> for &'a str
|
||||||
|
@ -142,6 +147,7 @@ pub struct StringState<R: Renderer> {
|
||||||
impl<R: Renderer> Render<R> for String {
|
impl<R: Renderer> Render<R> for String {
|
||||||
type State = StringState<R>;
|
type State = StringState<R>;
|
||||||
type FallibleState = Self::State;
|
type FallibleState = Self::State;
|
||||||
|
type AsyncOutput = Self;
|
||||||
|
|
||||||
fn build(self) -> Self::State {
|
fn build(self) -> Self::State {
|
||||||
let node = R::create_text_node(&self);
|
let node = R::create_text_node(&self);
|
||||||
|
@ -167,6 +173,10 @@ impl<R: Renderer> Render<R> for String {
|
||||||
self.rebuild(state);
|
self.rebuild(state);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn resolve(self) -> Self::AsyncOutput {
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R> RenderHtml<R> for String
|
impl<R> RenderHtml<R> for String
|
||||||
|
@ -241,6 +251,7 @@ pub struct RcStrState<R: Renderer> {
|
||||||
impl<R: Renderer> Render<R> for Rc<str> {
|
impl<R: Renderer> Render<R> for Rc<str> {
|
||||||
type State = RcStrState<R>;
|
type State = RcStrState<R>;
|
||||||
type FallibleState = Self::State;
|
type FallibleState = Self::State;
|
||||||
|
type AsyncOutput = Self;
|
||||||
|
|
||||||
fn build(self) -> Self::State {
|
fn build(self) -> Self::State {
|
||||||
let node = R::create_text_node(&self);
|
let node = R::create_text_node(&self);
|
||||||
|
@ -266,6 +277,10 @@ impl<R: Renderer> Render<R> for Rc<str> {
|
||||||
self.rebuild(state);
|
self.rebuild(state);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn resolve(self) -> Self::AsyncOutput {
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R> RenderHtml<R> for Rc<str>
|
impl<R> RenderHtml<R> for Rc<str>
|
||||||
|
@ -341,6 +356,7 @@ pub struct ArcStrState<R: Renderer> {
|
||||||
impl<R: Renderer> Render<R> for Arc<str> {
|
impl<R: Renderer> Render<R> for Arc<str> {
|
||||||
type State = ArcStrState<R>;
|
type State = ArcStrState<R>;
|
||||||
type FallibleState = Self::State;
|
type FallibleState = Self::State;
|
||||||
|
type AsyncOutput = Self;
|
||||||
|
|
||||||
fn build(self) -> Self::State {
|
fn build(self) -> Self::State {
|
||||||
let node = R::create_text_node(&self);
|
let node = R::create_text_node(&self);
|
||||||
|
@ -366,6 +382,10 @@ impl<R: Renderer> Render<R> for Arc<str> {
|
||||||
self.rebuild(state);
|
self.rebuild(state);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn resolve(self) -> Self::AsyncOutput {
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R> RenderHtml<R> for Arc<str>
|
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> {
|
impl<'a, R: Renderer> Render<R> for Cow<'a, str> {
|
||||||
type State = CowStrState<'a, R>;
|
type State = CowStrState<'a, R>;
|
||||||
type FallibleState = Self::State;
|
type FallibleState = Self::State;
|
||||||
|
type AsyncOutput = Self;
|
||||||
|
|
||||||
fn build(self) -> Self::State {
|
fn build(self) -> Self::State {
|
||||||
let node = R::create_text_node(&self);
|
let node = R::create_text_node(&self);
|
||||||
|
@ -466,6 +487,10 @@ impl<'a, R: Renderer> Render<R> for Cow<'a, str> {
|
||||||
self.rebuild(state);
|
self.rebuild(state);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn resolve(self) -> Self::AsyncOutput {
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, R> RenderHtml<R> for Cow<'a, str>
|
impl<'a, R> RenderHtml<R> for Cow<'a, str>
|
||||||
|
|
|
@ -1,16 +1,8 @@
|
||||||
use super::{
|
use super::{
|
||||||
Mountable, Position, PositionState, Render, RenderHtml, ToTemplate,
|
Mountable, Position, PositionState, Render, RenderHtml, ToTemplate,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{hydration::Cursor, renderer::DomRenderer};
|
||||||
dom::document,
|
use std::marker::PhantomData;
|
||||||
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;
|
|
||||||
|
|
||||||
pub struct ViewTemplate<V, R>
|
pub struct ViewTemplate<V, R>
|
||||||
where
|
where
|
||||||
|
@ -46,6 +38,7 @@ where
|
||||||
{
|
{
|
||||||
type State = V::State;
|
type State = V::State;
|
||||||
type FallibleState = V::FallibleState;
|
type FallibleState = V::FallibleState;
|
||||||
|
type AsyncOutput = Self;
|
||||||
|
|
||||||
// TODO try_build/try_rebuild()
|
// TODO try_build/try_rebuild()
|
||||||
|
|
||||||
|
@ -70,6 +63,10 @@ where
|
||||||
) -> any_error::Result<()> {
|
) -> any_error::Result<()> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn resolve(self) -> Self::AsyncOutput {
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V, R> RenderHtml<R> for ViewTemplate<V, R>
|
impl<V, R> RenderHtml<R> for ViewTemplate<V, R>
|
||||||
|
|
|
@ -14,6 +14,7 @@ use const_str_slice_concat::{
|
||||||
impl<R: Renderer> Render<R> for () {
|
impl<R: Renderer> Render<R> for () {
|
||||||
type State = ();
|
type State = ();
|
||||||
type FallibleState = Self::State;
|
type FallibleState = Self::State;
|
||||||
|
type AsyncOutput = ();
|
||||||
|
|
||||||
fn build(self) -> Self::State {}
|
fn build(self) -> Self::State {}
|
||||||
|
|
||||||
|
@ -29,6 +30,8 @@ impl<R: Renderer> Render<R> for () {
|
||||||
) -> any_error::Result<()> {
|
) -> any_error::Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn resolve(self) -> Self::AsyncOutput {}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R> RenderHtml<R> for ()
|
impl<R> RenderHtml<R> for ()
|
||||||
|
@ -102,6 +105,7 @@ impl ToTemplate for () {
|
||||||
impl<A: Render<R>, R: Renderer> Render<R> for (A,) {
|
impl<A: Render<R>, R: Renderer> Render<R> for (A,) {
|
||||||
type State = A::State;
|
type State = A::State;
|
||||||
type FallibleState = A::FallibleState;
|
type FallibleState = A::FallibleState;
|
||||||
|
type AsyncOutput = (A::AsyncOutput,);
|
||||||
|
|
||||||
fn build(self) -> Self::State {
|
fn build(self) -> Self::State {
|
||||||
self.0.build()
|
self.0.build()
|
||||||
|
@ -121,6 +125,10 @@ impl<A: Render<R>, R: Renderer> Render<R> for (A,) {
|
||||||
) -> any_error::Result<()> {
|
) -> any_error::Result<()> {
|
||||||
self.0.try_rebuild(state)
|
self.0.try_rebuild(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn resolve(self) -> Self::AsyncOutput {
|
||||||
|
(self.0.resolve().await,)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A, R> RenderHtml<R> for (A,)
|
impl<A, R> RenderHtml<R> for (A,)
|
||||||
|
@ -210,8 +218,8 @@ macro_rules! impl_view_for_tuples {
|
||||||
Rndr: Renderer
|
Rndr: Renderer
|
||||||
{
|
{
|
||||||
type State = ($first::State, $($ty::State,)*);
|
type State = ($first::State, $($ty::State,)*);
|
||||||
|
|
||||||
type FallibleState = ($first::FallibleState, $($ty::FallibleState,)*);
|
type FallibleState = ($first::FallibleState, $($ty::FallibleState,)*);
|
||||||
|
type AsyncOutput = ($first::AsyncOutput, $($ty::AsyncOutput,)*);
|
||||||
|
|
||||||
fn build(self) -> Self::State {
|
fn build(self) -> Self::State {
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
|
@ -249,6 +257,15 @@ macro_rules! impl_view_for_tuples {
|
||||||
}
|
}
|
||||||
Ok(())
|
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,)*)
|
impl<$first, $($ty),*, Rndr> RenderHtml<Rndr> for ($first, $($ty,)*)
|
||||||
|
|
Loading…
Reference in a new issue