use crate::{
errors::ClientError,
path::PathMaybeWithLocale,
reactor::Reactor,
state::{AnyFreeze, MakeRx, MakeUnrx, TemplateState, UnreactiveState},
};
use super::{Entity, PreloadInfo, TemplateInner};
use serde::{de::DeserializeOwned, Serialize};
use std::sync::Arc;
use sycamore::{
prelude::{create_child_scope, create_scope, BoundedScope, Scope, ScopeDisposer},
view::View,
web::Html,
};
pub(crate) type CapsuleFn<G, P> = Box<
dyn for<'a> Fn(
Scope<'a>,
PreloadInfo,
TemplateState,
P,
PathMaybeWithLocale, // Widget path
PathMaybeWithLocale, // Caller path
) -> Result<(View<G>, ScopeDisposer<'a>), ClientError>
+ Send
+ Sync,
>;
pub struct Capsule<G: Html, P: Clone + 'static> {
pub(crate) inner: Entity<G>,
capsule_view: CapsuleFn<G, P>,
#[allow(clippy::type_complexity)]
pub(crate) fallback: Option<Arc<dyn Fn(Scope, P) -> View<G> + Send + Sync>>,
}
impl<G: Html, P: Clone + 'static> std::fmt::Debug for Capsule<G, P> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Capsule").finish()
}
}
pub struct CapsuleInner<G: Html, P: Clone + 'static> {
template_inner: TemplateInner<G>,
capsule_view: CapsuleFn<G, P>,
#[allow(clippy::type_complexity)]
pub(crate) fallback: Option<Arc<dyn Fn(Scope, P) -> View<G> + Send + Sync>>,
}
impl<G: Html, P: Clone + 'static> std::fmt::Debug for CapsuleInner<G, P> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CapsuleInner")
.field("template_inner", &self.template_inner)
.finish_non_exhaustive()
}
}
impl<G: Html, P: Clone + 'static> Capsule<G, P> {
pub fn build(mut template_inner: TemplateInner<G>) -> CapsuleInner<G, P> {
template_inner.is_capsule = true;
#[cfg(engine)]
{
assert!(
template_inner.head.is_none(),
"capsules cannot set document metadata"
);
assert!(
template_inner.set_headers.is_none(),
"capsules cannot set headers"
);
}
template_inner.view = Box::new(|_, _, _, _| Ok((View::empty(), create_scope(|_| {}))));
CapsuleInner {
template_inner,
capsule_view: Box::new(|_, _, _, _, _, _| Ok((View::empty(), create_scope(|_| {})))),
fallback: None,
}
}
#[cfg(any(client, doc))]
#[allow(clippy::too_many_arguments)]
pub(crate) fn render_widget_for_template_client(
&self,
path: PathMaybeWithLocale,
caller_path: PathMaybeWithLocale,
props: P,
cx: Scope,
preload_info: PreloadInfo,
) -> Result<View<G>, ClientError> {
let (view, _disposer) = (self.capsule_view)(
cx,
preload_info,
TemplateState::empty(),
props,
path,
caller_path,
)?;
Ok(view)
}
#[cfg(engine)]
pub(crate) fn render_widget_for_template_server(
&self,
path: PathMaybeWithLocale,
state: TemplateState,
props: P,
cx: Scope,
) -> Result<View<G>, ClientError> {
let preload_info = PreloadInfo {};
let (view, _) = (self.capsule_view)(
cx,
preload_info,
state,
props,
path,
PathMaybeWithLocale(String::new()),
)?;
Ok(view)
}
}
impl<G: Html, P: Clone + 'static> CapsuleInner<G, P> {
pub fn fallback(mut self, view: impl Fn(Scope, P) -> View<G> + Send + Sync + 'static) -> Self {
{
self.fallback = Some(Arc::new(view));
}
self
}
pub fn empty_fallback(mut self) -> Self {
{
self.fallback = Some(Arc::new(|cx, _| sycamore::view! { cx, }));
}
self
}
pub fn build(self) -> Capsule<G, P> {
Capsule {
inner: Entity::from(self.template_inner),
capsule_view: self.capsule_view,
fallback: self.fallback,
}
}
pub fn view_with_state<I, F>(mut self, val: F) -> Self
where
F: for<'app, 'child> Fn(BoundedScope<'app, 'child>, &'child I, P) -> View<G>
+ Clone
+ Send
+ Sync
+ 'static,
I: MakeUnrx + AnyFreeze + Clone,
I::Unrx: MakeRx<Rx = I> + Serialize + DeserializeOwned + Send + Sync + Clone + 'static,
{
self.template_inner.view =
Box::new(|_, _, _, _| panic!("attempted to call template rendering logic for widget"));
#[cfg(any(client, doc))]
let entity_name = self.template_inner.get_path();
#[cfg(any(client, doc))]
let fallback_fn = self.fallback.clone(); self.capsule_view = Box::new(
#[allow(unused_variables)]
move |app_cx, preload_info, template_state, props, path, caller_path| {
let reactor = Reactor::<G>::from_cx(app_cx);
reactor.get_widget_view::<I::Unrx, _, P>(
app_cx,
path,
caller_path,
#[cfg(any(client, doc))]
entity_name.clone(),
template_state,
props,
#[cfg(any(client, doc))]
preload_info,
val.clone(),
#[cfg(any(client, doc))]
fallback_fn.as_ref().unwrap(),
)
},
);
self
}
pub fn view_with_unreactive_state<F, S>(mut self, val: F) -> Self
where
F: Fn(Scope, S, P) -> View<G> + Clone + Send + Sync + 'static,
S: MakeRx + Serialize + DeserializeOwned + UnreactiveState + 'static,
<S as MakeRx>::Rx: AnyFreeze + Clone + MakeUnrx<Unrx = S>,
{
self.template_inner.view =
Box::new(|_, _, _, _| panic!("attempted to call template rendering logic for widget"));
#[cfg(any(client, doc))]
let entity_name = self.template_inner.get_path();
#[cfg(any(client, doc))]
let fallback_fn = self.fallback.clone(); self.capsule_view = Box::new(
#[allow(unused_variables)]
move |app_cx, preload_info, template_state, props, path, caller_path| {
let reactor = Reactor::<G>::from_cx(app_cx);
reactor.get_unreactive_widget_view(
app_cx,
path,
caller_path,
#[cfg(any(client, doc))]
entity_name.clone(),
template_state,
props,
#[cfg(any(client, doc))]
preload_info,
val.clone(),
#[cfg(any(client, doc))]
fallback_fn.as_ref().unwrap(),
)
},
);
self
}
pub fn view<F>(mut self, val: F) -> Self
where
F: Fn(Scope, P) -> View<G> + Send + Sync + 'static,
{
self.template_inner.view =
Box::new(|_, _, _, _| panic!("attempted to call template rendering logic for widget"));
self.capsule_view = Box::new(
#[allow(unused_variables)]
move |app_cx, _preload_info, _template_state, props, path, caller_path| {
let reactor = Reactor::<G>::from_cx(app_cx);
reactor.register_no_state(&path, true);
#[cfg(any(client, doc))]
reactor.state_store.declare_dependency(&path, &caller_path);
let mut view = View::empty();
let disposer = create_child_scope(app_cx, |child_cx| {
view = val(child_cx, props);
});
Ok((view, disposer))
},
);
self
}
}