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.
|
|
|
|
|
2021-03-11 17:27:01 +00:00
|
|
|
use dioxus::prelude::Properties;
|
2021-02-24 07:22:05 +00:00
|
|
|
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};
|
|
|
|
|
2021-02-13 07:49:10 +00:00
|
|
|
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
|
|
|
|
///
|
2021-02-13 08:19:35 +00:00
|
|
|
pub struct WebsysRenderer {
|
|
|
|
internal_dom: VirtualDom,
|
2021-01-16 06:30:48 +00:00
|
|
|
}
|
|
|
|
|
2021-02-13 08:19:35 +00:00
|
|
|
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-01-16 06:30:48 +00:00
|
|
|
///
|
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-01-16 06:30:48 +00:00
|
|
|
}
|
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-03-11 17:27:01 +00:00
|
|
|
pub fn new_with_props<T: Properties + '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-01-16 06:30:48 +00:00
|
|
|
}
|
|
|
|
|
2021-02-12 21:11:33 +00:00
|
|
|
/// Create a new text renderer from an existing Virtual DOM.
|
2021-02-13 08:19:35 +00:00
|
|
|
pub fn from_vdom(dom: VirtualDom) -> Self {
|
2021-02-24 15:12:26 +00:00
|
|
|
// todo: initialize the event registry properly
|
2021-02-25 23:44:00 +00:00
|
|
|
Self { internal_dom: dom }
|
2021-01-16 06:30:48 +00:00
|
|
|
}
|
|
|
|
|
2021-02-24 06:31:19 +00:00
|
|
|
pub async fn run(&mut self) -> dioxus_core::error::Result<()> {
|
2021-03-11 17:27:01 +00:00
|
|
|
let (sender, mut receiver) = mpsc::unbounded::<EventTrigger>();
|
2021-02-24 06:31:19 +00:00
|
|
|
|
2021-02-24 15:12:26 +00:00
|
|
|
let body_element = prepare_websys_dom();
|
2021-02-28 08:08:08 +00:00
|
|
|
|
|
|
|
let mut patch_machine = interpreter::PatchMachine::new(body_element.clone(), move |ev| {
|
|
|
|
log::debug!("Event trigger! {:#?}", ev);
|
|
|
|
let mut c = sender.clone();
|
|
|
|
wasm_bindgen_futures::spawn_local(async move {
|
|
|
|
c.send(ev).await.unwrap();
|
|
|
|
});
|
|
|
|
});
|
2021-02-24 15:12:26 +00:00
|
|
|
let root_node = body_element.first_child().unwrap();
|
2021-02-28 08:08:08 +00:00
|
|
|
patch_machine.stack.push(root_node.clone());
|
2021-01-16 06:30:48 +00:00
|
|
|
|
2021-02-25 23:44:00 +00:00
|
|
|
// todo: initialize the event registry properly on the root
|
2021-02-24 15:12:26 +00:00
|
|
|
|
2021-03-11 17:27:01 +00:00
|
|
|
let edits = self.internal_dom.rebuild()?;
|
|
|
|
log::debug!("Received edits: {:#?}", edits);
|
|
|
|
edits.iter().for_each(|edit| {
|
|
|
|
log::debug!("patching with {:?}", edit);
|
2021-02-26 17:58:03 +00:00
|
|
|
patch_machine.handle_edit(edit);
|
|
|
|
});
|
2021-03-04 17:03:22 +00:00
|
|
|
// log::debug!("patch stack size {:?}", patch_machine.stack);
|
2021-02-24 08:51:26 +00:00
|
|
|
|
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-03-04 17:03:22 +00:00
|
|
|
// log::debug!("patch stack size before {:#?}", patch_machine.stack);
|
2021-02-28 08:08:08 +00:00
|
|
|
// patch_machine.reset();
|
|
|
|
// patch_machine.stack.push(root_node.clone());
|
2021-02-24 08:51:26 +00:00
|
|
|
self.internal_dom
|
|
|
|
.progress_with_event(event)?
|
2021-02-24 15:12:26 +00:00
|
|
|
.iter()
|
2021-02-24 08:51:26 +00:00
|
|
|
.for_each(|edit| {
|
2021-03-11 17:27:01 +00:00
|
|
|
log::debug!("edit stream {:?}", edit);
|
2021-02-24 15:12:26 +00:00
|
|
|
patch_machine.handle_edit(edit);
|
2021-02-24 08:51:26 +00:00
|
|
|
});
|
2021-02-28 08:08:08 +00:00
|
|
|
|
2021-03-04 17:03:22 +00:00
|
|
|
// log::debug!("patch stack size after {:#?}", patch_machine.stack);
|
2021-02-28 08:08:08 +00:00
|
|
|
patch_machine.reset();
|
|
|
|
// our root node reference gets invalidated
|
|
|
|
// not sure why
|
|
|
|
// for now, just select the first child again.
|
|
|
|
// eventually, we'll just make our own root element instead of using body
|
|
|
|
// or just use body directly IDEK
|
|
|
|
let root_node = body_element.first_child().unwrap();
|
|
|
|
patch_machine.stack.push(root_node.clone());
|
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::*;
|
2021-02-27 01:42:55 +00:00
|
|
|
use dioxus::prelude::bumpalo;
|
|
|
|
use dioxus::prelude::format_args_f;
|
2021-02-17 15:53:55 +00:00
|
|
|
use dioxus_core as dioxus;
|
|
|
|
use dioxus_core::prelude::html;
|
|
|
|
|
|
|
|
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, _| {
|
2021-02-28 08:08:08 +00:00
|
|
|
todo!()
|
2021-03-01 02:21:17 +00:00
|
|
|
// ctx.render(html! {
|
2021-02-28 08:08:08 +00:00
|
|
|
// <div>
|
|
|
|
// "Hello world"
|
|
|
|
// <button onclick={move |_| log::info!("button1 clicked!")}> "click me" </button>
|
|
|
|
// <button onclick={move |_| log::info!("button2 clicked!")}> "click me" </button>
|
|
|
|
// </div>
|
|
|
|
// })
|
2021-02-24 07:22:05 +00:00
|
|
|
}))
|
2021-02-17 15:53:55 +00:00
|
|
|
}
|
2021-01-16 06:30:48 +00:00
|
|
|
}
|