dioxus/packages/web/src/lib.rs

161 lines
5.3 KiB
Rust
Raw Normal View History

2021-02-12 21:11:33 +00:00
//! Dioxus WebSys
2021-02-17 15:53:55 +00:00
//! --------------
2021-02-24 07:22:05 +00:00
//! This crate implements a renderer of the Dioxus Virtual DOM for the web browser using Websys.
use fxhash::FxHashMap;
2021-02-17 15:53:55 +00:00
use web_sys::{window, Document, Element, Event, Node};
2021-02-12 21:11:33 +00:00
2021-02-15 04:39:46 +00:00
pub use dioxus_core as dioxus;
2021-02-12 21:11:33 +00:00
use dioxus_core::{
events::EventTrigger,
2021-02-24 08:51:26 +00:00
prelude::{VirtualDom, FC},
2021-02-24 06:31:19 +00:00
};
2021-02-24 08:51:26 +00:00
use futures::{channel::mpsc, SinkExt, StreamExt};
pub mod interpreter;
2021-02-24 07:22:05 +00:00
2021-02-12 21:11:33 +00:00
/// The `WebsysRenderer` provides a way of rendering a Dioxus Virtual DOM to the browser's DOM.
/// Under the hood, we leverage WebSys and interact directly with the DOM
///
pub struct WebsysRenderer {
internal_dom: VirtualDom,
2021-02-24 07:22:05 +00:00
// this should be a component index
2021-02-24 08:51:26 +00:00
_event_map: FxHashMap<(u32, u32), u32>,
}
impl WebsysRenderer {
2021-02-24 09:03:52 +00:00
/// This method is the primary entrypoint for Websys Dioxus apps. Will panic if an error occurs while rendering.
/// See DioxusErrors for more information on how these errors could occour.
///
/// ```ignore
/// fn main() {
/// wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example));
/// }
/// ```
///
2021-02-24 07:22:05 +00:00
/// Run the app to completion, panicing if any error occurs while rendering.
/// Pairs well with the wasm_bindgen async handler
pub async fn start(root: FC<()>) {
Self::new(root).run().await.expect("Virtual DOM failed");
}
2021-02-12 21:11:33 +00:00
/// Create a new instance of the Dioxus Virtual Dom with no properties for the root component.
///
2021-02-12 21:11:33 +00:00
/// This means that the root component must either consumes its own context, or statics are used to generate the page.
/// The root component can access things like routing in its context.
pub fn new(root: FC<()>) -> Self {
Self::new_with_props(root, ())
}
2021-02-12 21:11:33 +00:00
/// Create a new text-renderer instance from a functional component root.
/// Automatically progresses the creation of the VNode tree to completion.
///
/// A VDom is automatically created. If you want more granular control of the VDom, use `from_vdom`
2021-02-21 02:59:16 +00:00
pub fn new_with_props<T: 'static>(root: FC<T>, root_props: T) -> Self {
2021-02-12 21:11:33 +00:00
Self::from_vdom(VirtualDom::new_with_props(root, root_props))
}
2021-02-12 21:11:33 +00:00
/// Create a new text renderer from an existing Virtual DOM.
/// This will progress the existing VDom's events to completion.
pub fn from_vdom(dom: VirtualDom) -> Self {
2021-02-24 07:22:05 +00:00
Self {
internal_dom: dom,
2021-02-24 08:51:26 +00:00
_event_map: FxHashMap::default(),
2021-02-24 07:22:05 +00:00
}
}
2021-02-24 06:31:19 +00:00
pub async fn run(&mut self) -> dioxus_core::error::Result<()> {
2021-02-24 08:51:26 +00:00
let (sender, mut receiver) = mpsc::unbounded::<EventTrigger>();
2021-02-24 06:31:19 +00:00
2021-02-24 07:22:05 +00:00
let body = prepare_websys_dom();
2021-02-24 08:51:26 +00:00
let mut patch_machine = interpreter::PatchMachine::new(body.clone());
2021-02-24 07:22:05 +00:00
let root_node = body.first_child().unwrap();
patch_machine.stack.push(root_node);
2021-02-24 08:51:26 +00:00
self.internal_dom
.rebuild()?
.into_iter()
.for_each(|edit| patch_machine.handle_edit(&edit));
2021-02-12 21:11:33 +00:00
// Event loop waits for the receiver to finish up
// TODO! Connect the sender to the virtual dom's suspense system
// Suspense is basically an external event that can force renders to specific nodes
while let Some(event) = receiver.next().await {
2021-02-24 08:51:26 +00:00
self.internal_dom
.progress_with_event(event)?
.into_iter()
.for_each(|edit| {
patch_machine.handle_edit(&edit);
});
2021-02-15 04:39:46 +00:00
}
2021-02-24 08:51:26 +00:00
Ok(()) // should actually never return from this, should be an error, rustc just cant see it
2021-02-15 04:39:46 +00:00
}
}
2021-02-24 07:22:05 +00:00
fn prepare_websys_dom() -> Element {
2021-02-24 06:31:19 +00:00
// Initialize the container on the dom
// Hook up the body as the root component to render tinto
let window = web_sys::window().expect("should have access to the Window");
let document = window
.document()
.expect("should have access to the Document");
let body = document.body().unwrap();
// Build a dummy div
let container: &Element = body.as_ref();
container.set_inner_html("");
container
.append_child(
document
.create_element("div")
.expect("should create element OK")
.as_ref(),
)
.expect("should append child OK");
2021-02-24 07:22:05 +00:00
container.clone()
2021-02-24 06:31:19 +00:00
}
// Progress the mount of the root component
// Iterate through the nodes, attaching the closure and sender to the listener
// {
// let mut remote_sender = sender.clone();
// let listener = move || {
// let event = EventTrigger::new();
// wasm_bindgen_futures::spawn_local(async move {
// remote_sender
// .send(event)
// .await
// .expect("Updating receiver failed");
// })
// };
// }
2021-02-17 15:53:55 +00:00
#[cfg(test)]
mod tests {
use std::env;
use super::*;
use dioxus_core as dioxus;
use dioxus_core::prelude::html;
#[test]
fn simple_patch() {
env::set_var("RUST_LOG", "trace");
pretty_env_logger::init();
log::info!("Hello!");
2021-02-15 04:39:46 +00:00
2021-02-24 07:22:05 +00:00
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(|ctx, _| {
ctx.view(html! {
2021-02-17 15:53:55 +00:00
<div>
"Hello world"
2021-02-24 07:22:05 +00:00
<button onclick={move |_| log::info!("button1 clicked!")}> "click me" </button>
<button onclick={move |_| log::info!("button2 clicked!")}> "click me" </button>
2021-02-17 15:53:55 +00:00
</div>
2021-02-24 07:22:05 +00:00
})
}))
2021-02-17 15:53:55 +00:00
}
}