#![allow(clippy::type_complexity)]
#[cfg(feature = "ssr")]
use super::MarkBranch;
use super::{
add_attr::AddAnyAttr, Mountable, Position, PositionState, Render,
RenderHtml,
};
use crate::{
erased::{Erased, ErasedLocal},
html::attribute::{
any_attribute::{AnyAttribute, AnyAttributeState, IntoAnyAttribute},
Attribute,
},
hydration::Cursor,
renderer::Rndr,
ssr::StreamBuilder,
};
use futures::future::{join, join_all};
use std::{any::TypeId, fmt::Debug};
#[cfg(any(feature = "ssr", feature = "hydrate"))]
use std::{future::Future, pin::Pin};
pub struct AnyView {
type_id: TypeId,
value: Erased,
build: fn(Erased) -> AnyViewState,
rebuild: fn(Erased, &mut AnyViewState),
#[cfg(feature = "ssr")]
html_len: usize,
#[cfg(feature = "ssr")]
to_html:
fn(Erased, &mut String, &mut Position, bool, bool, Vec<AnyAttribute>),
#[cfg(feature = "ssr")]
to_html_async: fn(
Erased,
&mut StreamBuilder,
&mut Position,
bool,
bool,
Vec<AnyAttribute>,
),
#[cfg(feature = "ssr")]
to_html_async_ooo: fn(
Erased,
&mut StreamBuilder,
&mut Position,
bool,
bool,
Vec<AnyAttribute>,
),
#[cfg(feature = "ssr")]
#[allow(clippy::type_complexity)]
resolve: fn(Erased) -> Pin<Box<dyn Future<Output = AnyView> + Send>>,
#[cfg(feature = "ssr")]
dry_resolve: fn(&mut Erased),
#[cfg(feature = "hydrate")]
#[allow(clippy::type_complexity)]
hydrate_from_server: fn(Erased, &Cursor, &PositionState) -> AnyViewState,
#[cfg(feature = "hydrate")]
#[allow(clippy::type_complexity)]
hydrate_async: fn(
Erased,
&Cursor,
&PositionState,
) -> Pin<Box<dyn Future<Output = AnyViewState>>>,
}
impl AnyView {
#[doc(hidden)]
pub fn as_type_id(&self) -> TypeId {
self.type_id
}
}
impl Debug for AnyView {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AnyView")
.field("type_id", &self.type_id)
.finish_non_exhaustive()
}
}
pub struct AnyViewState {
type_id: TypeId,
state: ErasedLocal,
unmount: fn(&mut ErasedLocal),
mount: fn(
&mut ErasedLocal,
parent: &crate::renderer::types::Element,
marker: Option<&crate::renderer::types::Node>,
),
insert_before_this: fn(&ErasedLocal, child: &mut dyn Mountable) -> bool,
elements: fn(&ErasedLocal) -> Vec<crate::renderer::types::Element>,
placeholder: Option<crate::renderer::types::Placeholder>,
}
impl Debug for AnyViewState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AnyViewState")
.field("type_id", &self.type_id)
.field("state", &"")
.field("unmount", &self.unmount)
.field("mount", &self.mount)
.field("insert_before_this", &self.insert_before_this)
.finish()
}
}
pub trait IntoAny {
fn into_any(self) -> AnyView;
}
pub trait IntoMaybeErased {
type Output: IntoMaybeErased;
fn into_maybe_erased(self) -> Self::Output;
}
impl<T> IntoMaybeErased for T
where
T: RenderHtml,
{
#[cfg(not(erase_components))]
type Output = Self;
#[cfg(erase_components)]
type Output = AnyView;
fn into_maybe_erased(self) -> Self::Output {
#[cfg(not(erase_components))]
{
self
}
#[cfg(erase_components)]
{
self.into_owned().into_any()
}
}
}
fn mount_any<T>(
state: &mut ErasedLocal,
parent: &crate::renderer::types::Element,
marker: Option<&crate::renderer::types::Node>,
) where
T: Render,
T::State: 'static,
{
state.get_mut::<T::State>().mount(parent, marker)
}
fn unmount_any<T>(state: &mut ErasedLocal)
where
T: Render,
T::State: 'static,
{
state.get_mut::<T::State>().unmount();
}
fn insert_before_this<T>(state: &ErasedLocal, child: &mut dyn Mountable) -> bool
where
T: Render,
T::State: 'static,
{
state.get_ref::<T::State>().insert_before_this(child)
}
fn elements<T>(state: &ErasedLocal) -> Vec<crate::renderer::types::Element>
where
T: Render,
T::State: 'static,
{
state.get_ref::<T::State>().elements()
}
impl<T> IntoAny for T
where
T: Send,
T: RenderHtml,
{
fn into_any(self) -> AnyView {
#[cfg(feature = "ssr")]
fn dry_resolve<T: RenderHtml + 'static>(value: &mut Erased) {
value.get_mut::<T>().dry_resolve();
}
#[cfg(feature = "ssr")]
fn resolve<T: RenderHtml + 'static>(
value: Erased,
) -> Pin<Box<dyn Future<Output = AnyView> + Send>> {
use futures::FutureExt;
async move { value.into_inner::<T>().resolve().await.into_any() }
.boxed()
}
#[cfg(feature = "ssr")]
fn to_html<T: RenderHtml + 'static>(
value: Erased,
buf: &mut String,
position: &mut Position,
escape: bool,
mark_branches: bool,
extra_attrs: Vec<AnyAttribute>,
) {
value.into_inner::<T>().to_html_with_buf(
buf,
position,
escape,
mark_branches,
extra_attrs,
);
if !T::EXISTS {
buf.push_str("<!--<() />-->");
}
}
#[cfg(feature = "ssr")]
fn to_html_async<T: RenderHtml + 'static>(
value: Erased,
buf: &mut StreamBuilder,
position: &mut Position,
escape: bool,
mark_branches: bool,
extra_attrs: Vec<AnyAttribute>,
) {
value.into_inner::<T>().to_html_async_with_buf::<false>(
buf,
position,
escape,
mark_branches,
extra_attrs,
);
if !T::EXISTS {
buf.push_sync("<!--<() />-->");
}
}
#[cfg(feature = "ssr")]
fn to_html_async_ooo<T: RenderHtml + 'static>(
value: Erased,
buf: &mut StreamBuilder,
position: &mut Position,
escape: bool,
mark_branches: bool,
extra_attrs: Vec<AnyAttribute>,
) {
value.into_inner::<T>().to_html_async_with_buf::<true>(
buf,
position,
escape,
mark_branches,
extra_attrs,
);
if !T::EXISTS {
buf.push_sync("<!--<() />-->");
}
}
fn build<T: RenderHtml + 'static>(value: Erased) -> AnyViewState {
let state = ErasedLocal::new(value.into_inner::<T>().build());
let placeholder = (!T::EXISTS).then(Rndr::create_placeholder);
AnyViewState {
type_id: TypeId::of::<T>(),
state,
mount: mount_any::<T>,
unmount: unmount_any::<T>,
insert_before_this: insert_before_this::<T>,
elements: elements::<T>,
placeholder,
}
}
#[cfg(feature = "hydrate")]
fn hydrate_from_server<T: RenderHtml + 'static>(
value: Erased,
cursor: &Cursor,
position: &PositionState,
) -> AnyViewState {
let state = ErasedLocal::new(
value.into_inner::<T>().hydrate::<true>(cursor, position),
);
let placeholder =
(!T::EXISTS).then(|| cursor.next_placeholder(position));
AnyViewState {
type_id: TypeId::of::<T>(),
state,
mount: mount_any::<T>,
unmount: unmount_any::<T>,
insert_before_this: insert_before_this::<T>,
elements: elements::<T>,
placeholder,
}
}
#[cfg(feature = "hydrate")]
fn hydrate_async<T: RenderHtml + 'static>(
value: Erased,
cursor: &Cursor,
position: &PositionState,
) -> Pin<Box<dyn Future<Output = AnyViewState>>> {
let cursor = cursor.clone();
let position = position.clone();
Box::pin(async move {
let state = ErasedLocal::new(
value
.into_inner::<T>()
.hydrate_async(&cursor, &position)
.await,
);
let placeholder =
(!T::EXISTS).then(|| cursor.next_placeholder(&position));
AnyViewState {
type_id: TypeId::of::<T>(),
state,
mount: mount_any::<T>,
unmount: unmount_any::<T>,
insert_before_this: insert_before_this::<T>,
elements: elements::<T>,
placeholder,
}
})
}
fn rebuild<T: RenderHtml + 'static>(
value: Erased,
state: &mut AnyViewState,
) {
let state = state.state.get_mut::<<T as Render>::State>();
value.into_inner::<T>().rebuild(state);
}
let value = self.into_owned();
AnyView {
type_id: TypeId::of::<T::Owned>(),
build: build::<T::Owned>,
rebuild: rebuild::<T::Owned>,
#[cfg(feature = "ssr")]
resolve: resolve::<T::Owned>,
#[cfg(feature = "ssr")]
dry_resolve: dry_resolve::<T::Owned>,
#[cfg(feature = "ssr")]
html_len: value.html_len(),
#[cfg(feature = "ssr")]
to_html: to_html::<T::Owned>,
#[cfg(feature = "ssr")]
to_html_async: to_html_async::<T::Owned>,
#[cfg(feature = "ssr")]
to_html_async_ooo: to_html_async_ooo::<T::Owned>,
#[cfg(feature = "hydrate")]
hydrate_from_server: hydrate_from_server::<T::Owned>,
#[cfg(feature = "hydrate")]
hydrate_async: hydrate_async::<T::Owned>,
value: Erased::new(value),
}
}
}
impl Render for AnyView {
type State = AnyViewState;
fn build(self) -> Self::State {
(self.build)(self.value)
}
fn rebuild(self, state: &mut Self::State) {
if self.type_id == state.type_id {
(self.rebuild)(self.value, state)
} else {
let mut new = self.build();
if let Some(placeholder) = &mut state.placeholder {
placeholder.insert_before_this(&mut new);
placeholder.unmount();
} else {
state.insert_before_this(&mut new);
}
state.unmount();
*state = new;
}
}
}
impl AddAnyAttr for AnyView {
type Output<SomeNewAttr: Attribute> = AnyViewWithAttrs;
#[allow(unused_variables)]
fn add_any_attr<NewAttr: Attribute>(
self,
attr: NewAttr,
) -> Self::Output<NewAttr>
where
Self::Output<NewAttr>: RenderHtml,
{
AnyViewWithAttrs {
view: self,
attrs: vec![attr.into_cloneable_owned().into_any_attr()],
}
}
}
impl RenderHtml for AnyView {
type AsyncOutput = Self;
type Owned = Self;
fn dry_resolve(&mut self) {
#[cfg(feature = "ssr")]
{
(self.dry_resolve)(&mut self.value)
}
#[cfg(not(feature = "ssr"))]
panic!(
"You are rendering AnyView to HTML without the `ssr` feature \
enabled."
);
}
async fn resolve(self) -> Self::AsyncOutput {
#[cfg(feature = "ssr")]
{
(self.resolve)(self.value).await
}
#[cfg(not(feature = "ssr"))]
panic!(
"You are rendering AnyView to HTML without the `ssr` feature \
enabled."
);
}
const MIN_LENGTH: usize = 0;
fn to_html_with_buf(
self,
buf: &mut String,
position: &mut Position,
escape: bool,
mark_branches: bool,
extra_attrs: Vec<AnyAttribute>,
) {
#[cfg(feature = "ssr")]
{
let type_id = if mark_branches && escape {
format!("{:?}", self.type_id)
} else {
Default::default()
};
if mark_branches && escape {
buf.open_branch(&type_id);
}
(self.to_html)(
self.value,
buf,
position,
escape,
mark_branches,
extra_attrs,
);
if mark_branches && escape {
buf.close_branch(&type_id);
if *position == Position::NextChildAfterText {
*position = Position::NextChild;
}
}
}
#[cfg(not(feature = "ssr"))]
{
_ = mark_branches;
_ = buf;
_ = position;
_ = escape;
_ = extra_attrs;
panic!(
"You are rendering AnyView to HTML without the `ssr` feature \
enabled."
);
}
}
fn to_html_async_with_buf<const OUT_OF_ORDER: bool>(
self,
buf: &mut StreamBuilder,
position: &mut Position,
escape: bool,
mark_branches: bool,
extra_attrs: Vec<AnyAttribute>,
) where
Self: Sized,
{
#[cfg(feature = "ssr")]
if OUT_OF_ORDER {
let type_id = if mark_branches && escape {
format!("{:?}", self.type_id)
} else {
Default::default()
};
if mark_branches && escape {
buf.open_branch(&type_id);
}
(self.to_html_async_ooo)(
self.value,
buf,
position,
escape,
mark_branches,
extra_attrs,
);
if mark_branches && escape {
buf.close_branch(&type_id);
if *position == Position::NextChildAfterText {
*position = Position::NextChild;
}
}
} else {
let type_id = if mark_branches && escape {
format!("{:?}", self.type_id)
} else {
Default::default()
};
if mark_branches && escape {
buf.open_branch(&type_id);
}
(self.to_html_async)(
self.value,
buf,
position,
escape,
mark_branches,
extra_attrs,
);
if mark_branches && escape {
buf.close_branch(&type_id);
if *position == Position::NextChildAfterText {
*position = Position::NextChild;
}
}
}
#[cfg(not(feature = "ssr"))]
{
_ = buf;
_ = position;
_ = escape;
_ = mark_branches;
_ = extra_attrs;
panic!(
"You are rendering AnyView to HTML without the `ssr` feature \
enabled."
);
}
}
fn hydrate<const FROM_SERVER: bool>(
self,
cursor: &Cursor,
position: &PositionState,
) -> Self::State {
#[cfg(feature = "hydrate")]
{
if FROM_SERVER {
(self.hydrate_from_server)(self.value, cursor, position)
} else {
panic!(
"hydrating AnyView from inside a ViewTemplate is not \
supported."
);
}
}
#[cfg(not(feature = "hydrate"))]
{
_ = cursor;
_ = position;
panic!(
"You are trying to hydrate AnyView without the `hydrate` \
feature enabled."
);
}
}
async fn hydrate_async(
self,
cursor: &Cursor,
position: &PositionState,
) -> Self::State {
#[cfg(feature = "hydrate")]
{
let state =
(self.hydrate_async)(self.value, cursor, position).await;
state
}
#[cfg(not(feature = "hydrate"))]
{
_ = cursor;
_ = position;
panic!(
"You are trying to hydrate AnyView without the `hydrate` \
feature enabled."
);
}
}
fn html_len(&self) -> usize {
#[cfg(feature = "ssr")]
{
self.html_len
}
#[cfg(not(feature = "ssr"))]
{
0
}
}
fn into_owned(self) -> Self::Owned {
self
}
}
impl Mountable for AnyViewState {
fn unmount(&mut self) {
(self.unmount)(&mut self.state);
if let Some(placeholder) = &mut self.placeholder {
placeholder.unmount();
}
}
fn mount(
&mut self,
parent: &crate::renderer::types::Element,
marker: Option<&crate::renderer::types::Node>,
) {
(self.mount)(&mut self.state, parent, marker);
if let Some(placeholder) = &mut self.placeholder {
placeholder.mount(parent, marker);
}
}
fn insert_before_this(&self, child: &mut dyn Mountable) -> bool {
let before_view = (self.insert_before_this)(&self.state, child);
if before_view {
return true;
}
if let Some(placeholder) = &self.placeholder {
placeholder.insert_before_this(child)
} else {
false
}
}
fn elements(&self) -> Vec<crate::renderer::types::Element> {
(self.elements)(&self.state)
}
}
pub struct AnyViewWithAttrs {
view: AnyView,
attrs: Vec<AnyAttribute>,
}
impl Render for AnyViewWithAttrs {
type State = AnyViewWithAttrsState;
fn build(self) -> Self::State {
let view = self.view.build();
let elements = view.elements();
let mut attrs = Vec::with_capacity(elements.len() * self.attrs.len());
for attr in self.attrs {
for el in &elements {
attrs.push(attr.clone().build(el))
}
}
AnyViewWithAttrsState { view, attrs }
}
fn rebuild(self, state: &mut Self::State) {
self.view.rebuild(&mut state.view);
for element in state.elements() {
self.attrs
.clone()
.rebuild(&mut (element.clone(), Vec::new()));
self.attrs.clone().build(&element);
}
}
}
impl RenderHtml for AnyViewWithAttrs {
type AsyncOutput = Self;
type Owned = Self;
const MIN_LENGTH: usize = 0;
fn dry_resolve(&mut self) {
self.view.dry_resolve();
for attr in &mut self.attrs {
attr.dry_resolve();
}
}
async fn resolve(self) -> Self::AsyncOutput {
let resolve_view = self.view.resolve();
let resolve_attrs =
join_all(self.attrs.into_iter().map(|attr| attr.resolve()));
let (view, attrs) = join(resolve_view, resolve_attrs).await;
Self { view, attrs }
}
fn to_html_with_buf(
self,
buf: &mut String,
position: &mut Position,
escape: bool,
mark_branches: bool,
mut extra_attrs: Vec<AnyAttribute>,
) {
extra_attrs.extend(self.attrs);
self.view.to_html_with_buf(
buf,
position,
escape,
mark_branches,
extra_attrs,
);
}
fn to_html_async_with_buf<const OUT_OF_ORDER: bool>(
self,
buf: &mut StreamBuilder,
position: &mut Position,
escape: bool,
mark_branches: bool,
mut extra_attrs: Vec<AnyAttribute>,
) where
Self: Sized,
{
extra_attrs.extend(self.attrs);
self.view.to_html_async_with_buf::<OUT_OF_ORDER>(
buf,
position,
escape,
mark_branches,
extra_attrs,
);
}
fn hydrate<const FROM_SERVER: bool>(
self,
cursor: &Cursor,
position: &PositionState,
) -> Self::State {
let view = self.view.hydrate::<FROM_SERVER>(cursor, position);
let elements = view.elements();
let mut attrs = Vec::with_capacity(elements.len() * self.attrs.len());
for attr in self.attrs {
for el in &elements {
attrs.push(attr.clone().hydrate::<FROM_SERVER>(el));
}
}
AnyViewWithAttrsState { view, attrs }
}
async fn hydrate_async(
self,
cursor: &Cursor,
position: &PositionState,
) -> Self::State {
let view = self.view.hydrate_async(cursor, position).await;
let elements = view.elements();
let mut attrs = Vec::with_capacity(elements.len() * self.attrs.len());
for attr in self.attrs {
for el in &elements {
attrs.push(attr.clone().hydrate::<true>(el));
}
}
AnyViewWithAttrsState { view, attrs }
}
fn html_len(&self) -> usize {
self.view.html_len()
+ self.attrs.iter().map(|attr| attr.html_len()).sum::<usize>()
}
fn into_owned(self) -> Self::Owned {
self
}
}
impl AddAnyAttr for AnyViewWithAttrs {
type Output<SomeNewAttr: Attribute> = AnyViewWithAttrs;
fn add_any_attr<NewAttr: Attribute>(
mut self,
attr: NewAttr,
) -> Self::Output<NewAttr>
where
Self::Output<NewAttr>: RenderHtml,
{
self.attrs.push(attr.into_cloneable_owned().into_any_attr());
self
}
}
pub struct AnyViewWithAttrsState {
view: AnyViewState,
#[allow(dead_code)] attrs: Vec<AnyAttributeState>,
}
impl Mountable for AnyViewWithAttrsState {
fn unmount(&mut self) {
self.view.unmount();
}
fn mount(
&mut self,
parent: &crate::renderer::types::Element,
marker: Option<&crate::renderer::types::Node>,
) {
self.view.mount(parent, marker)
}
fn insert_before_this(&self, child: &mut dyn Mountable) -> bool {
self.view.insert_before_this(child)
}
fn elements(&self) -> Vec<crate::renderer::types::Element> {
self.view.elements()
}
}