use std::cell::RefCell;
use std::rc::Rc;
use wasm_bindgen::JsValue;
use crate::component::Component;
use crate::node::{mount::mount_to_dom, patch::patch_node, VNode};
pub struct App {
root_fn: Box<dyn Fn() -> VNode>,
selector: String,
mount_element: Option<web_sys::Element>,
old_vnode: Option<VNode>,
component_ctrl: Option<Rc<RefCell<Box<dyn Component>>>>,
}
impl App {
pub fn new<F: Fn() -> VNode + 'static>(selector: &str, root_fn: F) -> Self {
App {
root_fn: Box::new(root_fn),
selector: selector.to_string(),
mount_element: None,
old_vnode: None,
component_ctrl: None,
}
}
pub fn from_component<T: Component>(selector: &str, mut component: T) -> Self {
component.init();
let comp: Rc<RefCell<Box<dyn Component>>> =
Rc::new(RefCell::new(Box::new(component)));
let comp_clone = comp.clone();
let root_fn = Box::new(move || -> VNode {
comp_clone.borrow().render()
});
App {
root_fn,
selector: selector.to_string(),
mount_element: None,
old_vnode: None,
component_ctrl: Some(comp),
}
}
pub fn mount(&mut self) -> Result<(), JsValue> {
let window = web_sys::window().ok_or("no window")?;
let document = window.document().ok_or("no document")?;
let mount_point = document
.query_selector(&self.selector)
.ok()
.flatten()
.ok_or("mount point not found")?;
mount_point.set_inner_html("");
let vnode = (self.root_fn)();
let parent: web_sys::Node = mount_point.clone().into();
let _ = mount_to_dom(&vnode, &parent, None);
self.mount_element = Some(mount_point);
self.old_vnode = Some(vnode);
if let Some(ref comp) = self.component_ctrl {
let borrowed = comp.borrow();
borrowed.mounted();
}
Ok(())
}
pub fn update(&mut self) -> Result<(), JsValue> {
if let Some(ref comp) = self.component_ctrl {
let borrowed = comp.borrow();
if !borrowed.should_update() {
return Ok(());
}
}
let mount_point = self
.mount_element
.as_ref()
.ok_or_else(|| JsValue::from_str("App not mounted"))?;
let new_vnode = (self.root_fn)();
if let Some(ref old_vnode) = self.old_vnode {
let parent: web_sys::Node = mount_point.clone().into();
patch_node(old_vnode, &new_vnode, &parent, None);
} else {
let parent: web_sys::Node = mount_point.clone().into();
mount_to_dom(&new_vnode, &parent, None);
}
self.old_vnode = Some(new_vnode);
Ok(())
}
pub fn mount_element(&self) -> Option<&web_sys::Element> {
self.mount_element.as_ref()
}
}
pub fn mount<F: Fn() -> VNode + 'static>(selector: &str, root_fn: F) -> App {
let mut app = App::new(selector, root_fn);
let _ = app.mount();
app
}