use crate::{
dom::{
events::on_component_mount, program::MountProcedure, Application, Cmd, Component, DomAttr,
DomAttrValue, DomNode, Program,
},
vdom::{Attribute, AttributeName, Leaf, Node},
};
use std::{any::TypeId, cell::RefCell, fmt, rc::Rc};
pub trait StatefulComponent {
fn attribute_changed(&mut self, attr: DomAttr);
fn remove_attribute(&mut self, _attr_name: AttributeName) {}
fn child_container(&self) -> Option<DomNode>;
fn append_children(&mut self, _children: Vec<DomNode>) {}
fn remove_child(&mut self, _index: usize) {}
fn connected_callback(&mut self) {}
fn disconnected_callback(&mut self) {}
fn adopted_callback(&mut self) {}
}
pub struct StatefulModel<MSG> {
pub comp: Rc<RefCell<dyn StatefulComponent>>,
pub type_id: TypeId,
pub attrs: Vec<Attribute<MSG>>,
pub children: Vec<Node<MSG>>,
}
impl<MSG> fmt::Debug for StatefulModel<MSG> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "StatefulModel")
}
}
impl<MSG> StatefulModel<MSG> {
pub fn map_msg<F, MSG2>(self, cb: F) -> StatefulModel<MSG2>
where
F: Fn(MSG) -> MSG2 + Clone + 'static,
MSG2: 'static,
MSG: 'static,
{
StatefulModel {
type_id: self.type_id,
comp: self.comp,
attrs: self
.attrs
.into_iter()
.map(|a| a.map_msg(cb.clone()))
.collect(),
children: self
.children
.into_iter()
.map(|c| c.map_msg(cb.clone()))
.collect(),
}
}
}
impl<MSG> Clone for StatefulModel<MSG> {
fn clone(&self) -> Self {
Self {
comp: Rc::clone(&self.comp),
type_id: self.type_id,
attrs: self.attrs.clone(),
children: self.children.clone(),
}
}
}
impl<MSG> PartialEq for StatefulModel<MSG> {
fn eq(&self, other: &Self) -> bool {
Rc::ptr_eq(&self.comp, &other.comp)
&& self.type_id == other.type_id
&& self.attrs == other.attrs
&& self.children == other.children
}
}
impl<COMP> Application for COMP
where
COMP: Component<XMSG = ()> + StatefulComponent + 'static,
{
type MSG = COMP::MSG;
fn init(&mut self) -> Cmd<Self::MSG> {
Cmd::from(<Self as Component>::init(self))
}
fn update(&mut self, msg: COMP::MSG) -> Cmd<Self::MSG> {
let effects = <Self as Component>::update(self, msg);
Cmd::from(effects)
}
fn view(&self) -> Node<Self::MSG> {
Component::view(self)
}
fn stylesheet() -> Vec<String> {
<Self as Component>::stylesheet()
}
fn style(&self) -> Vec<String> {
<Self as Component>::style(self)
}
}
pub fn stateful_component<COMP, MSG, MSG2>(
app: COMP,
attrs: impl IntoIterator<Item = Attribute<MSG>>,
children: impl IntoIterator<Item = Node<MSG>>,
) -> Node<MSG>
where
COMP: Component<MSG = MSG2, XMSG = ()> + StatefulComponent + Application<MSG = MSG2> + 'static,
MSG: 'static,
MSG2: 'static,
{
let type_id = TypeId::of::<COMP>();
let attrs = attrs.into_iter().collect::<Vec<_>>();
let app = Rc::new(RefCell::new(app));
let mut program = Program::from_rc_app(Rc::clone(&app));
let children: Vec<Node<MSG>> = children.into_iter().collect();
let mount_event = on_component_mount(move |me| {
program.mount(
&me.target_node.as_node(),
MountProcedure::append(),
);
let stylesheet = <COMP as Component>::stylesheet().join("\n");
log::info!("stylesheet: {}", stylesheet);
program.inject_style_to_mount(&stylesheet);
program.inject_style_to_mount(&program.app_context.dynamic_style());
program.update_dom().expect("update dom");
});
Node::Leaf(Leaf::StatefulComponent(StatefulModel {
comp: app,
type_id,
attrs: attrs.into_iter().chain([mount_event]).collect(),
children: children.into_iter().collect(),
}))
}
#[cfg(feature = "with-dom")]
impl From<wasm_bindgen::JsValue> for DomAttrValue {
fn from(val: wasm_bindgen::JsValue) -> Self {
if val.is_null() {
DomAttrValue::Empty
} else if let Some(v) = val.as_bool() {
DomAttrValue::Simple(v.into())
} else if let Some(v) = val.as_f64() {
DomAttrValue::Simple(v.into())
} else if let Some(v) = val.as_string() {
DomAttrValue::Simple(v.into())
} else {
todo!("for: {:?}", val)
}
}
}