#![deny(missing_docs)]
#![cfg_attr(feature = "cargo-clippy", feature(tool_lints))]
#![cfg_attr(feature = "cargo-clippy", warn(clippy::all))]
#[cfg(test)]
use wasm_bindgen_test::*;
#[cfg(test)]
wasm_bindgen_test_configure!(run_in_browser);
use crate::{
component::{Render, RootParent},
vdom::vcomponent::{ComponentManager, ComponentWrapper},
};
use std::{cell::RefCell, rc::Rc};
use wasm_bindgen::{prelude::*, JsCast};
use web_sys::{window, Element, MessageChannel, MessagePort};
pub mod component;
mod dom;
pub mod vdom;
pub type Markup<RCTX> = vdom::VNode<RCTX>;
pub mod prelude {
pub use crate::component::{Component, Lifecycle, Render};
pub use crate::{App, Markup};
pub use ruukh_codegen::*;
}
pub mod reexports {
pub use fnv::FnvBuildHasher;
pub use indexmap::IndexMap;
}
pub struct App<COMP>
where
COMP: Render<Props = (), Events = ()>,
{
manager: ComponentWrapper<COMP, RootParent>,
}
impl<COMP> App<COMP>
where
COMP: Render<Props = (), Events = ()>,
{
pub fn new() -> App<COMP> {
Default::default()
}
pub fn mount(mut self, element: impl AppMount) {
let parent = element.app_mount();
let (receiver, sender) = app_message_channel();
let root_parent = Rc::new(RefCell::new(()));
self.manager
.render_walk(parent.as_ref(), None, root_parent.clone(), sender.clone())
.unwrap();
receiver.react_on_message(move || {
self.manager
.render_walk(parent.as_ref(), None, root_parent.clone(), sender.clone())
.unwrap();
});
}
}
impl<COMP> Default for App<COMP>
where
COMP: Render<Props = (), Events = ()>,
{
fn default() -> Self {
App {
manager: ComponentWrapper::new((), ()),
}
}
}
fn app_message_channel() -> (MessageReceiver, MessageSender) {
let msg_channel = MessageChannel::new().unwrap();
(
MessageReceiver(msg_channel.port2()),
MessageSender(msg_channel.port1()),
)
}
pub struct MessageReceiver(MessagePort);
impl MessageReceiver {
fn react_on_message(self, mut handler: impl FnMut() + 'static) {
let closure: Closure<dyn FnMut(JsValue)> = Closure::wrap(Box::new(move |_| handler()));
self.0.set_onmessage(Some(closure.as_ref().unchecked_ref()));
closure.forget();
}
}
#[derive(Clone)]
struct MessageSender(MessagePort);
impl MessageSender {
fn do_react(&self) {
self.0
.post_message(&JsValue::null())
.expect("Could not send the message");
}
}
pub type Shared<T> = Rc<RefCell<T>>;
pub trait AppMount {
fn app_mount(self) -> Element;
}
impl<'a> AppMount for &'a str {
fn app_mount(self) -> Element {
window()
.unwrap()
.document()
.unwrap()
.get_element_by_id(self)
.unwrap_or_else(|| {
panic!(
"Could not find element with id `{}` to mount the App.",
self
)
})
}
}
impl AppMount for Element {
fn app_mount(self) -> Element {
self
}
}
impl AppMount for String {
fn app_mount(self) -> Element {
self.as_str().app_mount()
}
}
#[cfg(test)]
fn message_sender() -> MessageSender {
app_message_channel().1
}