mika 0.0.0

A framework for building wasm front-end web application in Rust
use wasm_bindgen::UnwrapThrowExt;

pub trait Application: Sized + 'static {
    type Message;
    type Context: Context<Self>;
    #[cfg(feature = "sub-apps")]
    type Parent;
    fn init() -> Self;
    fn update(&mut self, m: Self::Message, c: &mut Self::Context);
    // A better name for this?
    // This method is just executed only once when the app start
    fn render_initial_view(&self, main: &WeakMain<Self>) -> crate::dom::Node;
    fn mounted(&mut self, _c: &mut Self::Context) {
        // Default to do nothing
    }
}

pub trait Context<A: Application> {
    #[cfg(not(feature = "sub-apps"))]
    fn init(main: WeakMain<A>) -> Self;

    #[cfg(feature = "sub-apps")]
    fn init(main: WeakMain<A>, parent_main: A::Parent) -> Self;
}

impl<A: Application> Context<A> for () {
    #[cfg(not(feature = "sub-apps"))]
    fn init(_: WeakMain<A>) -> Self { }

    #[cfg(feature = "sub-apps")]
    fn init(_: WeakMain<A>, _: A::Parent) -> Self { }
}

pub struct Main<A: Application> {
    app: A,
    context: Option<A::Context>,
    web_sys_root: web_sys::Element,
    root_node: Option<crate::dom::Node>,
}

impl<A: Application> Main<A> {
    fn update(&mut self, m: A::Message) {
        let context = self
            .context
            .as_mut()
            .expect_throw("self.context.as_mut()");
        self.app.update(m, context);
    }
}

pub struct RcMain<A: Application> {
    rc: std::rc::Rc<std::cell::RefCell<Main<A>>>,
}

impl<A: Application> RcMain<A> {
    fn new(web_sys_root: web_sys::Element) -> Self {
        Self {
            rc: std::rc::Rc::new(std::cell::RefCell::new(Main {
                app: A::init(),
                context: None,
                web_sys_root,
                root_node: None,
            })),
        }
    }

    fn weak(&self) -> WeakMain<A> {
        WeakMain {
            weak: std::rc::Rc::downgrade(&self.rc),
        }
    }

    #[cfg(not(feature = "sub-apps"))]
    pub fn start_in(web_sys_root: web_sys::Element) -> Self {
        //crate::set_panic_hook_once();
        web_sys_root.set_text_content(None);
        let main = RcMain::new(web_sys_root);
        let weak_main = main.weak();
        let mut context = A::Context::init(weak_main.clone());
        match main.rc.try_borrow_mut() {
            Err(e) => log::error!("Error borrowing: {}", e),
            Ok(mut main) => {
                let root_node = main.app.render_initial_view(&weak_main);
                main.root_node = Some(root_node);
                main.root_node
                    .as_ref()
                    .unwrap()
                    .append_to(&main.web_sys_root);
                main.app.mounted(&mut context);
                main.context = Some(context);
            }
        };
        main
    }

    pub fn start_in_body(_: ()) -> Self {
        let web_sys_root = crate::window()
            .document()
            .expect_throw("crate::window().document()")
            .body()
            .expect_throw("body()")
            .into();
        Self::start_in(web_sys_root)
    }
}

pub struct WeakMain<A: Application> {
    weak: std::rc::Weak<std::cell::RefCell<Main<A>>>,
}

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),
            },
        }
    }
}

impl<A: Application> std::clone::Clone for WeakMain<A> {
    fn clone(&self) -> Self {
        Self {
            weak: std::rc::Weak::clone(&self.weak),
        }
    }
}