use silex_core::reactivity::Effect;
use silex_core::traits::Get;
use silex_dom::View;
use std::cell::RefCell;
use std::rc::Rc;
use web_sys::Node;
pub trait ShowContent {
type View: View;
fn render(&self) -> Self::View;
}
impl<F, V> ShowContent for F
where
F: Fn() -> V,
V: View,
{
type View = V;
fn render(&self) -> Self::View {
(self)()
}
}
#[derive(Clone)]
pub struct Show<Cond, ViewFn, FalsyViewFn> {
condition: Cond,
view: ViewFn,
fallback: FalsyViewFn,
}
impl<Cond, ViewFn> Show<Cond, ViewFn, fn() -> ()> {
pub fn new<V>(condition: Cond, view: ViewFn) -> Self
where
Cond: Get<Value = bool> + 'static,
ViewFn: Fn() -> V + 'static,
V: View,
{
Self {
condition,
view,
fallback: || (),
}
}
}
impl<Cond, ViewFn, FalsyViewFn> Show<Cond, ViewFn, FalsyViewFn>
where
Cond: Get<Value = bool> + 'static,
ViewFn: ShowContent + 'static,
FalsyViewFn: ShowContent + 'static,
{
pub fn fallback<NewFalsyFn>(self, fallback: NewFalsyFn) -> Show<Cond, ViewFn, NewFalsyFn>
where
NewFalsyFn: ShowContent + 'static,
{
Show {
condition: self.condition,
view: self.view,
fallback,
}
}
}
impl<Cond, ViewFn, FalsyViewFn> View for Show<Cond, ViewFn, FalsyViewFn>
where
Cond: Get<Value = bool> + 'static,
ViewFn: ShowContent + Clone + 'static,
FalsyViewFn: ShowContent + Clone + 'static,
{
fn mount(self, parent: &Node) {
let document = silex_dom::document();
let start_marker = document.create_comment("show-start");
let start_node: Node = start_marker.into();
if let Err(e) = parent
.append_child(&start_node)
.map_err(crate::SilexError::from)
{
silex_core::error::handle_error(e);
return;
}
let end_marker = document.create_comment("show-end");
let end_node: Node = end_marker.into();
if let Err(e) = parent
.append_child(&end_node)
.map_err(crate::SilexError::from)
{
silex_core::error::handle_error(e);
return;
}
let cond = self.condition;
let view_fn = self.view;
let fallback_fn = self.fallback;
let prev_state = Rc::new(RefCell::new(None::<bool>));
Effect::new(move |_| {
let val = cond.get();
let mut state = prev_state.borrow_mut();
if *state == Some(val) {
return;
}
if let Some(parent) = start_node.parent_node() {
while let Some(sibling) = start_node.next_sibling() {
if sibling == end_node {
break;
}
let _ = parent.remove_child(&sibling);
}
}
let fragment = document.create_document_fragment();
let fragment_node: Node = fragment.clone().into();
if val {
view_fn.render().mount(&fragment_node);
} else {
fallback_fn.render().mount(&fragment_node);
}
if let Some(parent) = end_node.parent_node() {
let _ = parent.insert_before(&fragment_node, Some(&end_node));
}
*state = Some(val);
});
}
}
use silex_core::traits::IntoSignal;
pub trait SignalShowExt {
type Cond: Get<Value = bool> + 'static;
fn when<V, F>(self, view: F) -> Show<Self::Cond, F, fn() -> ()>
where
V: View,
F: Fn() -> V + 'static;
}
impl<S> SignalShowExt for S
where
S: IntoSignal<Value = bool>,
S::Signal: Clone + 'static,
{
type Cond = S::Signal;
fn when<V, F>(self, view: F) -> Show<Self::Cond, F, fn() -> ()>
where
V: View,
F: Fn() -> V + 'static,
{
Show::new(self.into_signal(), view)
}
}