use crate::dom::NodeList;
use std::cell::{Cell, RefCell};
use std::rc::{Rc, Weak};
pub type UpdateView = bool;
pub trait AppContext<A: Application> {
fn init(main: WeakMain<A>, parent: A::Parent) -> Self;
}
impl<A: Application> AppContext<A> for () {
fn init(_: WeakMain<A>, _: A::Parent) -> Self {}
}
pub trait Application: Sized + 'static {
type Message;
type Context: AppContext<Self>;
type Parent;
fn init() -> Self;
fn update(&mut self, m: Self::Message, cp: ContextPlus<Self>) -> UpdateView;
fn render(&self, context: RenderContext<Self>);
#[allow(unused_variables)]
fn mounted(&self, cp: ContextPlus<Self>) {}
}
pub trait Component<A: Application> {
fn render(&self, context: RenderContext<A>);
}
pub struct Main<A: Application> {
app: A,
root_list: NodeList,
real_root: web_sys::Element,
context: Option<A::Context>,
weak: Option<WeakMain<A>>,
sub_apps: SubAppCollection,
}
pub struct RcMain<A: Application> {
rc: Rc<RefCell<Main<A>>>,
}
pub struct WeakMain<A: Application> {
weak: Weak<RefCell<Main<A>>>,
}
impl<A: Application> std::clone::Clone for WeakMain<A> {
fn clone(&self) -> Self {
Self {
weak: Weak::clone(&self.weak),
}
}
}
impl<A: Application> Main<A> {
fn update(&mut self, m: A::Message) {
let context = self
.context
.as_mut()
.unwrap_or_else(|| panic!("Update called when the app is not started yet"));
let cp = ContextPlus {
context,
sub_apps: &self.sub_apps,
};
let update_view = self.app.update(m, cp);
if update_view {
self.render()
}
}
fn render(&mut self) {
let Main {
ref app,
ref mut root_list,
real_root,
weak,
ref mut sub_apps,
..
} = self;
sub_apps.map.clear();
let weak = weak.as_ref().map_or_else(
|| panic!("Update called when the app is not started yet"),
std::clone::Clone::clone,
);
let rc = RenderContext::with_sub_apps(root_list, real_root.as_ref(), weak, None, sub_apps);
app.render(rc);
}
fn mounted(&self, context: &mut A::Context) {
let cp = ContextPlus {
context,
sub_apps: &self.sub_apps,
};
self.app.mounted(cp);
}
pub fn get_app_context_mut(&mut self) -> &mut A::Context {
match self.context {
None => panic!("App not started yet"),
Some(ref mut context) => context,
}
}
}
impl<A: Application> RcMain<A> {
pub(crate) fn to_weak(&self) -> WeakMain<A> {
WeakMain {
weak: Rc::downgrade(&self.rc),
}
}
pub fn start_in(real_root: web_sys::Element, parent: A::Parent) -> Self {
crate::set_panic_hook_once();
real_root.set_inner_html("");
let main = Self {
rc: Rc::new(RefCell::new(Main {
app: A::init(),
root_list: NodeList::new(),
real_root,
context: None,
weak: None,
sub_apps: SubAppCollection::new(),
})),
};
let weak = main.to_weak();
let mut context = A::Context::init(weak.clone(), parent);
match main.rc.try_borrow_mut() {
Err(e) => log::error!("Error borrowing: {}", e),
Ok(mut main) => {
main.weak = Some(weak.clone());
main.render();
main.mounted(&mut context);
main.context = Some(context);
}
};
main
}
pub fn start_in_element_id(element_id: &str, parent: A::Parent) -> Self {
let real_root = crate::interop::document()
.get_element_by_id(element_id)
.unwrap_or_else(|| panic!("No element with id = '{}'", element_id));
Self::start_in(real_root, parent)
}
pub fn start_in_body(parent: A::Parent) -> Self {
let real_root = crate::interop::document()
.body()
.expect("document body")
.into();
Self::start_in(real_root, parent)
}
pub fn send_message(&self, m: A::Message) {
match self.rc.try_borrow_mut() {
Err(e) => log::error!("Error borrowing: {}", e),
Ok(mut main) => main.update(m),
}
}
}
impl<A: Application> WeakMain<A> {
pub fn send_message(&self, m: A::Message) {
match self.weak.upgrade() {
None => log::error!("Upgrade WeakMain to Rc"),
Some(main) => match main.try_borrow_mut() {
Err(e) => log::error!("Error borrowing: {}", e),
Ok(mut main) => main.update(m),
},
}
}
pub fn get_inner(&self) -> Weak<RefCell<Main<A>>> {
self.weak.clone()
}
}
pub struct ContextPlus<'a, A: Application> {
pub context: &'a mut A::Context,
pub sub_apps: &'a SubAppCollection,
}
pub struct RenderContext<'a, A: 'a + Application> {
pub root_list: Cell<Option<&'a mut NodeList>>,
pub real_root: &'a web_sys::Node,
pub main: WeakMain<A>,
pub next_sibling: Option<&'a web_sys::Node>,
pub sub_apps: Cell<Option<&'a mut SubAppCollection>>,
}
impl<'a, A: Application> RenderContext<'a, A> {
pub fn new(
root_list: &'a mut NodeList,
real_root: &'a web_sys::Node,
main: WeakMain<A>,
next_sibling: Option<&'a web_sys::Node>,
) -> Self {
Self {
root_list: Cell::new(Some(root_list)),
real_root,
main,
next_sibling,
sub_apps: Cell::new(None),
}
}
fn with_sub_apps(
root_list: &'a mut NodeList,
real_root: &'a web_sys::Node,
main: WeakMain<A>,
next_sibling: Option<&'a web_sys::Node>,
sub_apps: &'a mut SubAppCollection,
) -> Self {
Self {
root_list: Cell::new(Some(root_list)),
real_root,
main,
next_sibling,
sub_apps: Cell::new(Some(sub_apps)),
}
}
pub fn take_root_list(&self) -> &'a mut NodeList {
self.root_list
.replace(None)
.expect("Root list is already taken")
}
pub fn take_sup_apps_collection(&self) -> &'a mut SubAppCollection {
self.sub_apps
.replace(None)
.expect("Should only call this in `application!`")
}
}
pub struct SubAppCollection {
map: std::collections::HashMap<u8, Box<std::any::Any>>,
}
impl SubAppCollection {
fn new() -> Self {
Self {
map: std::collections::HashMap::new(),
}
}
pub(crate) fn set<S: Application>(&mut self, id: u8, weak: WeakMain<S>) {
self.map.insert(id, Box::new(weak));
}
pub fn get<S: Application>(&self, id: u8) -> Option<WeakMain<S>> {
self.map
.get(&id)
.map(|any| any.downcast_ref::<WeakMain<S>>().cloned())
.unwrap_or_else(|| None)
}
}