use silex_core::reactivity::{Effect, SuspenseContext, create_scope, use_suspense_context};
use silex_core::traits::Get;
use silex_dom::attribute::GlobalAttributes;
use silex_dom::view::View;
use silex_html::div;
use web_sys::Node;
pub fn suspense() -> Suspense<()> {
Suspense::new()
}
pub struct Suspense<F = ()> {
resource_factory: F,
}
impl Suspense<()> {
pub fn new() -> Self {
Self {
resource_factory: (),
}
}
pub fn resource<R, F>(self, f: F) -> Suspense<F>
where
F: FnOnce() -> R,
{
Suspense {
resource_factory: f,
}
}
}
impl<F, R> Suspense<F>
where
F: FnOnce() -> R,
{
pub fn children<V, C>(self, child_fn: C) -> V
where
C: FnOnce(R) -> V,
{
SuspenseContext::provide(|| {
let resource = (self.resource_factory)();
child_fn(resource)
})
}
}
#[derive(Clone)]
pub struct SuspenseBoundary<C, F> {
children: C,
fallback: F,
mode: SuspenseMode,
ctx: SuspenseContext,
}
#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)]
pub enum SuspenseMode {
#[default]
KeepAlive,
Unmount,
}
impl SuspenseBoundary<(), ()> {
pub fn new() -> Self {
let ctx = use_suspense_context().expect(
"SuspenseContext not found. Ensure SuspenseBoundary is created inside SuspenseContext::provide closure.",
);
Self {
children: (),
fallback: (),
mode: SuspenseMode::default(),
ctx,
}
}
}
impl<C, F> SuspenseBoundary<C, F> {
pub fn children<NewC>(self, children: NewC) -> SuspenseBoundary<NewC, F> {
SuspenseBoundary {
children,
fallback: self.fallback,
mode: self.mode,
ctx: self.ctx,
}
}
pub fn mode(mut self, mode: SuspenseMode) -> Self {
self.mode = mode;
self
}
pub fn fallback<NewF>(self, fallback: NewF) -> SuspenseBoundary<C, NewF> {
SuspenseBoundary {
children: self.children,
fallback,
mode: self.mode,
ctx: self.ctx,
}
}
}
impl<C, F, VRes, FRes> View for SuspenseBoundary<C, F>
where
C: Fn() -> VRes + 'static,
VRes: View + 'static,
F: Fn() -> FRes + 'static,
FRes: View + 'static,
{
fn mount(self, parent: &Node) {
let children_fn = std::rc::Rc::new(self.children);
let fallback_fn = std::rc::Rc::new(self.fallback);
let mode = self.mode;
let count = self.ctx.count;
let parent_clone = parent.clone();
create_scope(move || {
match mode {
SuspenseMode::KeepAlive => {
let children_fn = children_fn.clone();
let fallback_fn = fallback_fn.clone();
let content_wrapper = div(()).class("suspense-content");
let _ = content_wrapper.clone().style(move || {
if count.get() > 0 {
"display: none"
} else {
"display: block"
}
});
content_wrapper.clone().mount(&parent_clone);
let content_root = content_wrapper.element;
Effect::new(move |_| {
let view = children_fn();
content_root.set_inner_html("");
view.mount(&content_root);
});
let fallback_wrapper = div(()).class("suspense-fallback");
let _ = fallback_wrapper.clone().style(move || {
if count.get() > 0 {
"display: block"
} else {
"display: none"
}
});
fallback_wrapper.clone().mount(&parent_clone);
let fallback_root = fallback_wrapper.element;
Effect::new(move |_| {
let view = fallback_fn();
fallback_root.set_inner_html("");
view.mount(&fallback_root);
});
}
SuspenseMode::Unmount => {
let children_fn = children_fn.clone();
let fallback_fn = fallback_fn.clone();
let content_wrapper = div(()).class("suspense-content");
content_wrapper.clone().mount(&parent_clone);
let content_root = content_wrapper.element;
Effect::new(move |_| {
if count.get() > 0 {
content_root.set_inner_html("");
} else {
let view = children_fn();
content_root.set_inner_html("");
view.mount(&content_root);
}
});
let fallback_wrapper = div(()).class("suspense-fallback");
fallback_wrapper.clone().mount(&parent_clone);
let fallback_root = fallback_wrapper.element;
Effect::new(move |_| {
if count.get() > 0 {
let view = fallback_fn();
fallback_root.set_inner_html("");
view.mount(&fallback_root);
} else {
fallback_root.set_inner_html("");
}
});
}
}
});
}
}