use super::{Reform, VDiff, VNode};
use callback::Callback;
use html::{Component, ComponentUpdate, NodeCell, Renderable, Scope};
use std::any::{Any, TypeId};
use std::cell::RefCell;
use std::marker::PhantomData;
use std::rc::Rc;
use wasm_bindgen::JsCast;
use web_sys::{window, Element, Node};
use Hidden;
type AnyProps = (TypeId, *mut Hidden);
type Generator = dyn FnMut(Element, Node, AnyProps);
type LazyActivator<COMP> = Rc<RefCell<Option<Scope<COMP>>>>;
pub struct VComp<COMP: Component> {
type_id: TypeId,
cell: NodeCell,
props: Option<(TypeId, *mut Hidden)>,
blind_sender: Box<dyn FnMut(AnyProps)>,
generator: Box<Generator>,
activators: Vec<LazyActivator<COMP>>,
destroyer: Box<dyn Fn()>,
_parent: PhantomData<COMP>,
}
impl<COMP: Component> VComp<COMP> {
pub fn lazy<CHILD>() -> (CHILD::Properties, Self)
where
CHILD: Component + Renderable<CHILD>,
{
let cell: NodeCell = Rc::new(RefCell::new(None));
let lazy_activator = Rc::new(RefCell::new(None));
let occupied = cell.clone();
let generator = {
let lazy_activator = lazy_activator.clone();
move |element, ancestor: Node, (type_id, raw): AnyProps| {
if type_id != TypeId::of::<CHILD>() {
panic!("tried to unpack properties of the other component");
}
let props = unsafe {
let raw: *mut CHILD::Properties = ::std::mem::transmute(raw);
*Box::from_raw(raw)
};
let scope: Scope<CHILD> = Scope::new();
let env = scope.clone();
*lazy_activator.borrow_mut() = Some(env);
scope.mount_in_place(
element,
Some(VNode::VRef(ancestor)),
Some(occupied.clone()),
Some(props),
);
}
};
let blind_sender = {
let mut previous_props = None;
let lazy_activator = lazy_activator.clone();
move |(type_id, raw): AnyProps| {
if type_id != TypeId::of::<CHILD>() {
panic!("tried to send properties of the other component");
}
let props = unsafe {
let raw: *mut CHILD::Properties = ::std::mem::transmute(raw);
*Box::from_raw(raw)
};
if !is_unit(&props) {
let new_props = Some(props);
if previous_props != new_props {
let props = new_props.as_ref().unwrap().clone();
lazy_activator
.borrow_mut()
.as_mut()
.expect("activator for child scope was not set (blind sender)")
.send(ComponentUpdate::Properties(props));
previous_props = new_props;
}
}
}
};
let destroyer = {
let lazy_activator = lazy_activator;
move || {
lazy_activator
.borrow_mut()
.as_mut()
.expect("activator for child scope was not set (destroyer)")
.send(ComponentUpdate::Destroy);
}
};
let properties = Default::default();
let comp = VComp {
type_id: TypeId::of::<CHILD>(),
cell,
props: None,
blind_sender: Box::new(blind_sender),
generator: Box::new(generator),
activators: Vec::new(),
destroyer: Box::new(destroyer),
_parent: PhantomData,
};
(properties, comp)
}
pub fn set_props<T>(&mut self, props: T) {
let boxed = Box::into_raw(Box::new(props));
let data = unsafe { ::std::mem::transmute(boxed) };
self.props = Some((self.type_id, data));
}
fn activate_props(&mut self, sender: &Scope<COMP>) -> AnyProps {
for activator in &self.activators {
*activator.borrow_mut() = Some(sender.clone());
}
self.props
.take()
.expect("tried to activate properties twice")
}
pub(crate) fn grab_sender_of(&mut self, other: Self) {
assert_eq!(self.type_id, other.type_id);
self.cell = other.cell;
self.blind_sender = other.blind_sender;
self.destroyer = other.destroyer;
}
}
fn is_unit<T: ?Sized + Any>(_t: &T) -> bool {
TypeId::of::<T>() == TypeId::of::<()>()
}
pub trait Transformer<COMP: Component, FROM, TO> {
fn transform(&mut self, from: FROM) -> TO;
}
impl<COMP, T> Transformer<COMP, T, T> for VComp<COMP>
where
COMP: Component,
{
fn transform(&mut self, from: T) -> T {
from
}
}
impl<'a, COMP, T> Transformer<COMP, &'a T, T> for VComp<COMP>
where
COMP: Component,
T: Clone,
{
fn transform(&mut self, from: &'a T) -> T {
from.clone()
}
}
impl<'a, COMP> Transformer<COMP, &'a str, String> for VComp<COMP>
where
COMP: Component,
{
fn transform(&mut self, from: &'a str) -> String {
from.to_owned()
}
}
impl<'a, COMP, F, IN> Transformer<COMP, F, Option<Callback<IN>>> for VComp<COMP>
where
COMP: Component + Renderable<COMP>,
F: Fn(IN) -> COMP::Message + 'static,
{
fn transform(&mut self, from: F) -> Option<Callback<IN>> {
let cell = Rc::new(RefCell::new(None));
self.activators.push(cell.clone());
let callback = move |arg| {
let msg = from(arg);
if let Some(ref mut sender) = *cell.borrow_mut() {
sender.send(ComponentUpdate::Message(msg));
} else {
panic!("unactivated callback, parent component have to activate it");
}
};
Some(callback.into())
}
}
impl<COMP> VComp<COMP>
where
COMP: Component + 'static,
{
fn mount(
&mut self,
parent: &Node,
ancestor: Node, props: AnyProps,
) {
let element: Element = parent
.to_owned()
.dyn_into()
.expect("element expected to mount VComp");
(self.generator)(element, ancestor, props);
}
fn send_props(&mut self, props: AnyProps) {
(self.blind_sender)(props);
}
}
impl<COMP> VDiff for VComp<COMP>
where
COMP: Component + 'static,
{
type Component = COMP;
fn detach(&mut self, parent: &Node) -> Option<Node> {
(self.destroyer)(); self.cell.borrow_mut().take().and_then(|node| {
let sibling = node.next_sibling();
parent
.remove_child(&node)
.expect("can't remove the component");
sibling
})
}
fn apply(
&mut self,
parent: &Node,
precursor: Option<&Node>,
ancestor: Option<VNode<Self::Component>>,
env: &Scope<Self::Component>,
) -> Option<Node> {
let reform = {
match ancestor {
Some(VNode::VComp(mut vcomp)) => {
if self.type_id == vcomp.type_id {
self.grab_sender_of(vcomp);
Reform::Keep
} else {
let node = vcomp.detach(parent);
Reform::Before(node)
}
}
Some(mut vnode) => {
let node = vnode.detach(parent);
Reform::Before(node)
}
None => Reform::Before(None),
}
};
let any_props = self.activate_props(&env);
match reform {
Reform::Keep => {
self.send_props(any_props);
}
Reform::Before(before) => {
let element = window()
.expect("need window in context")
.document()
.expect("window should have document")
.create_text_node("");
if let Some(sibling) = before {
parent
.insert_before(&element, Some(&sibling))
.expect("can't insert dummy element for a component");
} else {
let precursor = precursor.and_then(|before| before.next_sibling());
if let Some(precursor) = precursor {
parent
.insert_before(&element, Some(&precursor))
.expect("can't insert dummy element before precursor");
} else {
parent
.append_child(&element)
.expect("could not append child to element");
}
}
self.mount(parent, element.into(), any_props);
}
}
self.cell.borrow().as_ref().map(|node| node.to_owned())
}
}
impl<COMP: Component> PartialEq for VComp<COMP> {
fn eq(&self, other: &VComp<COMP>) -> bool {
self.type_id == other.type_id
}
}