diff --git a/Cargo.toml b/Cargo.toml index 3a0d7db9e..8c83a7269 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,14 +3,15 @@ members = [ # Built-in "packages/dioxus", "packages/core", - "packages/recoil", - "packages/redux", "packages/core-macro", "packages/router", "packages/ssr", "packages/webview", + "packages/web", "packages/livehost", "packages/vscode-ext", + # "packages/recoil", + # "packages/redux", # "packages/macro", # TODO @Jon, share the validation code # "packages/web", diff --git a/packages/recoil/Cargo.toml b/old/recoil/Cargo.toml similarity index 100% rename from packages/recoil/Cargo.toml rename to old/recoil/Cargo.toml diff --git a/packages/recoil/src/lib.rs b/old/recoil/src/lib.rs similarity index 100% rename from packages/recoil/src/lib.rs rename to old/recoil/src/lib.rs diff --git a/packages/redux/Cargo.toml b/old/redux/Cargo.toml similarity index 100% rename from packages/redux/Cargo.toml rename to old/redux/Cargo.toml diff --git a/packages/redux/src/lib.rs b/old/redux/src/lib.rs similarity index 100% rename from packages/redux/src/lib.rs rename to old/redux/src/lib.rs diff --git a/packages/virtual-dom-rs/CHANGELOG.md b/old/virtual-dom-rs/CHANGELOG.md similarity index 100% rename from packages/virtual-dom-rs/CHANGELOG.md rename to old/virtual-dom-rs/CHANGELOG.md diff --git a/packages/virtual-dom-rs/Cargo.toml b/old/virtual-dom-rs/Cargo.toml similarity index 100% rename from packages/virtual-dom-rs/Cargo.toml rename to old/virtual-dom-rs/Cargo.toml diff --git a/packages/virtual-dom-rs/README.md b/old/virtual-dom-rs/README.md similarity index 100% rename from packages/virtual-dom-rs/README.md rename to old/virtual-dom-rs/README.md diff --git a/packages/virtual-dom-rs/src/diff/diff_test_case.rs b/old/virtual-dom-rs/src/diff/diff_test_case.rs similarity index 100% rename from packages/virtual-dom-rs/src/diff/diff_test_case.rs rename to old/virtual-dom-rs/src/diff/diff_test_case.rs diff --git a/packages/virtual-dom-rs/src/diff/mod.rs b/old/virtual-dom-rs/src/diff/mod.rs similarity index 100% rename from packages/virtual-dom-rs/src/diff/mod.rs rename to old/virtual-dom-rs/src/diff/mod.rs diff --git a/packages/virtual-dom-rs/src/dom_updater.rs b/old/virtual-dom-rs/src/dom_updater.rs similarity index 100% rename from packages/virtual-dom-rs/src/dom_updater.rs rename to old/virtual-dom-rs/src/dom_updater.rs diff --git a/packages/virtual-dom-rs/src/lib.rs b/old/virtual-dom-rs/src/lib.rs similarity index 100% rename from packages/virtual-dom-rs/src/lib.rs rename to old/virtual-dom-rs/src/lib.rs diff --git a/packages/virtual-dom-rs/src/patch/apply_patches.rs b/old/virtual-dom-rs/src/patch/apply_patches.rs similarity index 100% rename from packages/virtual-dom-rs/src/patch/apply_patches.rs rename to old/virtual-dom-rs/src/patch/apply_patches.rs diff --git a/packages/virtual-dom-rs/src/patch/mod.rs b/old/virtual-dom-rs/src/patch/mod.rs similarity index 100% rename from packages/virtual-dom-rs/src/patch/mod.rs rename to old/virtual-dom-rs/src/patch/mod.rs diff --git a/packages/virtual-dom-rs/tests/closures.rs b/old/virtual-dom-rs/tests/closures.rs similarity index 100% rename from packages/virtual-dom-rs/tests/closures.rs rename to old/virtual-dom-rs/tests/closures.rs diff --git a/packages/virtual-dom-rs/tests/create_element.rs b/old/virtual-dom-rs/tests/create_element.rs similarity index 100% rename from packages/virtual-dom-rs/tests/create_element.rs rename to old/virtual-dom-rs/tests/create_element.rs diff --git a/packages/virtual-dom-rs/tests/diff_patch.rs b/old/virtual-dom-rs/tests/diff_patch.rs similarity index 100% rename from packages/virtual-dom-rs/tests/diff_patch.rs rename to old/virtual-dom-rs/tests/diff_patch.rs diff --git a/packages/virtual-dom-rs/tests/diff_patch_test_case/mod.rs b/old/virtual-dom-rs/tests/diff_patch_test_case/mod.rs similarity index 100% rename from packages/virtual-dom-rs/tests/diff_patch_test_case/mod.rs rename to old/virtual-dom-rs/tests/diff_patch_test_case/mod.rs diff --git a/packages/virtual-dom-rs/tests/dom_updater.rs b/old/virtual-dom-rs/tests/dom_updater.rs similarity index 100% rename from packages/virtual-dom-rs/tests/dom_updater.rs rename to old/virtual-dom-rs/tests/dom_updater.rs diff --git a/packages/virtual-dom-rs/tests/events.rs b/old/virtual-dom-rs/tests/events.rs similarity index 100% rename from packages/virtual-dom-rs/tests/events.rs rename to old/virtual-dom-rs/tests/events.rs diff --git a/packages/core/.vscode/settings.json b/packages/core/.vscode/settings.json index 2f418701b..80f51cff8 100644 --- a/packages/core/.vscode/settings.json +++ b/packages/core/.vscode/settings.json @@ -1,3 +1,3 @@ { - "rust-analyzer.inlayHints.enable": true + "rust-analyzer.inlayHints.enable": false } \ No newline at end of file diff --git a/packages/core/src/component.rs b/packages/core/src/component.rs index 9ade516e7..a464b5fbd 100644 --- a/packages/core/src/component.rs +++ b/packages/core/src/component.rs @@ -2,7 +2,7 @@ //! for components to be used within Nodes. //! -use crate::inner::*; +use crate::innerlude::*; /// The `Component` trait refers to any struct or funciton that can be used as a component /// We automatically implement Component for FC diff --git a/packages/core/src/context.rs b/packages/core/src/context.rs index 0e720f88d..fc78a165f 100644 --- a/packages/core/src/context.rs +++ b/packages/core/src/context.rs @@ -1,5 +1,5 @@ use crate::prelude::*; -use crate::{inner::Scope, nodes::VNode}; +use crate::{innerlude::Scope, nodes::VNode}; use bumpalo::Bump; use hooks::Hook; use std::{ diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 1f6f95762..1dcf2348c 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -1,15 +1,4 @@ -use crate::prelude::VNode; - -// use crate::{ -// cached_set::{CacheId, CachedSet}, -// change_list::ChangeListBuilder, -// events::EventsRegistry, -// node::{Attribute, ElementNode, Listener, Node, NodeKind, TextNode}, -// }; -// use fxhash::{FxHashMap, FxHashSet}; -// use std::cmp::Ordering; -// use std::u32; -// use wasm_bindgen::UnwrapThrowExt; +use crate::innerlude::{VNode, VText}; // Diff the `old` node with the `new` node. Emits instructions to modify a // physical DOM node that reflects `old` into something that reflects `new`. @@ -28,6 +17,38 @@ pub(crate) fn diff( new: &VNode, // cached_roots: &mut FxHashSet, ) { + match (old, new) { + // This case occurs when we generate two text nodes that are the sa + (VNode::Text(VText { text: old_text }), VNode::Text(VText { text: new_text })) => { + // + if old_text != new_text {} + } + + // compare elements + // if different, schedule different types of update + (VNode::Element(_), VNode::Element(_)) => {} + (VNode::Element(_), VNode::Text(_)) => {} + (VNode::Element(_), VNode::Component(_)) => {} + (VNode::Text(_), VNode::Element(_)) => {} + (VNode::Text(_), VNode::Component(_)) => {} + + (VNode::Component(_), VNode::Element(_)) => {} + (VNode::Component(_), VNode::Text(_)) => {} + + // No immediate change to dom. If props changed though, queue a "props changed" update + // However, mark these for a + (VNode::Component(_), VNode::Component(_)) => {} + // + (VNode::Suspended, _) | (_, VNode::Suspended) => { + todo!("Suspended components not currently available") + } // (VNode::Element(_), VNode::Suspended) => {} + // (VNode::Text(_), VNode::Suspended) => {} + // (VNode::Component(_), VNode::Suspended) => {} + // (VNode::Suspended, VNode::Element(_)) => {} + // (VNode::Suspended, VNode::Text(_)) => {} + // (VNode::Suspended, VNode::Suspended) => {} + // (VNode::Suspended, VNode::Component(_)) => {} + } todo!() // match (&new.kind, &old.kind) { // ( diff --git a/packages/core/src/error.rs b/packages/core/src/error.rs index e32c473d8..71e6d4adf 100644 --- a/packages/core/src/error.rs +++ b/packages/core/src/error.rs @@ -7,38 +7,37 @@ pub enum Error { #[error("No event to progress")] NoEvent, - #[error("Out of compute credits")] - OutOfCredits, + // #[error("Out of compute credits")] + // OutOfCredits, - /// Used when errors need to propogate but are too unique to be typed - #[error("{0}")] - Unique(String), + // /// Used when errors need to propogate but are too unique to be typed + // #[error("{0}")] + // Unique(String), - #[error("GraphQL error: {0}")] - GraphQL(String), + // #[error("GraphQL error: {0}")] + // GraphQL(String), - // TODO(haze): Remove, or make a better error. This is pretty much useless - #[error("GraphQL response mismatch. Got {found} but expected {expected}")] - GraphQLMisMatch { - expected: &'static str, - found: String, - }, + // // TODO(haze): Remove, or make a better error. This is pretty much useless + // #[error("GraphQL response mismatch. Got {found} but expected {expected}")] + // GraphQLMisMatch { + // expected: &'static str, + // found: String, + // }, - #[error("Difference detected in SystemTime! {0}")] - SystemTime(#[from] std::time::SystemTimeError), + // #[error("Difference detected in SystemTime! {0}")] + // SystemTime(#[from] std::time::SystemTimeError), - #[error("Failed to parse Integer")] - ParseInt(#[from] std::num::ParseIntError), + // #[error("Failed to parse Integer")] + // ParseInt(#[from] std::num::ParseIntError), - #[error("")] - MissingAuthentication, + // #[error("")] + // MissingAuthentication, - #[error("Failed to create experiment run: {0}")] - FailedToCreateExperimentRun(String), - - #[error("Could not find shared behavior with ID {0}")] - MissingSharedBehavior(String), + // #[error("Failed to create experiment run: {0}")] + // FailedToCreateExperimentRun(String), + // #[error("Could not find shared behavior with ID {0}")] + // MissingSharedBehavior(String), #[error("I/O Error: {0}")] IO(#[from] std::io::Error), } diff --git a/packages/core/src/events.rs b/packages/core/src/events.rs index f3348ef6c..52f4b2c58 100644 --- a/packages/core/src/events.rs +++ b/packages/core/src/events.rs @@ -4,6 +4,19 @@ //! 3rd party renderers are responsible for forming this virtual events from events //! //! The goal here is to provide a consistent event interface across all renderer types +use generational_arena::Index; + +pub struct EventTrigger { + pub component_id: Index, + pub listener_id: u32, + pub event: VirtualEvent, +} + +impl EventTrigger { + pub fn new() -> Self { + todo!() + } +} pub enum VirtualEvent { ClipboardEvent, diff --git a/packages/core/src/hooks.rs b/packages/core/src/hooks.rs index af9a7911c..ab7e53b24 100644 --- a/packages/core/src/hooks.rs +++ b/packages/core/src/hooks.rs @@ -9,7 +9,7 @@ pub use use_ref_def::use_ref; pub use use_state_def::use_state; mod use_state_def { - use crate::inner::*; + use crate::innerlude::*; use std::{cell::RefCell, ops::DerefMut, rc::Rc}; struct UseState { @@ -80,7 +80,7 @@ mod use_state_def { } mod use_ref_def { - use crate::inner::*; + use crate::innerlude::*; use std::{cell::RefCell, ops::DerefMut}; pub struct UseRef { diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index ead8242c2..92285048e 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -83,7 +83,7 @@ pub mod builder { } // types used internally that are important -pub(crate) mod inner { +pub(crate) mod innerlude { pub use crate::component::{Component, Properties}; use crate::context::hooks::Hook; pub use crate::context::Context; @@ -121,7 +121,7 @@ pub mod prelude { // pub use nodes::iterables::IterableNodes; /// This type alias is an internal way of abstracting over the static functions that represent components. - pub use crate::inner::FC; + pub use crate::innerlude::FC; // TODO @Jon, fix this // hack the VNode type until VirtualNode is fixed in the macro crate diff --git a/packages/core/src/nodebuilder.rs b/packages/core/src/nodebuilder.rs index c8318438d..7798375d6 100644 --- a/packages/core/src/nodebuilder.rs +++ b/packages/core/src/nodebuilder.rs @@ -1071,11 +1071,9 @@ pub fn attr<'a>(name: &'a str, value: &'a str) -> Attribute<'a> { /// ``` pub fn on<'a, 'b, F: 'static>( bump: &'a Bump, - event: &'a str, - callback: impl Fn(()) + 'static, -) -> Listener<'a> -// F: Fn(()) + 'b, -{ + event: &'static str, + callback: impl Fn(()) + 'a, +) -> Listener<'a> { Listener { event, callback: bump.alloc(callback), diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 845ae9f0b..571c8a5c0 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -174,11 +174,11 @@ mod velement { } /// An event listener. - pub struct Listener<'a> { + pub struct Listener<'bump> { /// The type of event to listen for. - pub(crate) event: &'a str, + pub(crate) event: &'static str, /// The callback to invoke when the event happens. - pub(crate) callback: &'a (dyn Fn(())), + pub(crate) callback: &'bump (dyn Fn(())), } /// The key for keyed children. @@ -226,8 +226,8 @@ mod velement { mod vtext { #[derive(PartialEq)] - pub struct VText<'a> { - pub text: &'a str, + pub struct VText<'bump> { + pub text: &'bump str, } impl<'a> VText<'a> { @@ -245,7 +245,7 @@ mod vtext { /// Virtual Components for custom user-defined components /// Only supports the functional syntax mod vcomponent { - use crate::inner::{Properties, FC}; + use crate::innerlude::{Properties, FC}; use std::{any::TypeId, fmt, future::Future, marker::PhantomData}; use super::VNode; diff --git a/packages/core/src/scope.rs b/packages/core/src/scope.rs index 1101e5a6c..ba71d5f96 100644 --- a/packages/core/src/scope.rs +++ b/packages/core/src/scope.rs @@ -1,4 +1,4 @@ -use crate::inner::*; +use crate::innerlude::*; use crate::nodes::VNode; use crate::{context::hooks::Hook, diff::diff}; use bumpalo::Bump; @@ -19,26 +19,26 @@ pub struct Scope { // These hooks are actually references into the hook arena // These two could be combined with "OwningRef" to remove unsafe usage // could also use ourborous - hooks: RefCell>, - hook_arena: typed_arena::Arena, + pub hooks: RefCell>, + pub hook_arena: typed_arena::Arena, // Map to the parent - parent: Option, + pub parent: Option, // todo, do better with the active frame stuff - frames: [Bump; 2], + pub frames: [Bump; 2], // somehow build this vnode with a lifetime tied to self - cur_node: *mut VNode<'static>, + pub cur_node: *mut VNode<'static>, - active_frame: u8, + pub active_frame: u8, // IE Which listeners need to be woken up? - listeners: Vec>, + pub listeners: Vec>, // - props_type: TypeId, - caller: *const i32, + pub props_type: TypeId, + pub caller: *const i32, } impl Scope { @@ -76,7 +76,7 @@ impl Scope { /// Create a new context and run the component with references from the Virtual Dom /// This function downcasts the function pointer based on the stored props_type /// - /// Props is ?Sized because we borrow the props + /// Props is ?Sized because we borrow the props and don't need to know the size. P (sized) is used as a marker (unsized) pub(crate) fn run<'a, P: Properties + ?Sized>(&self, props: &'a P) { let bump = &self.frames[0]; @@ -96,7 +96,7 @@ impl Scope { We hide the generic bound on the function item by casting it to raw pointer. When the function is actually called, we transmute the function back using the props as reference. - we could do a better check to make sure that the TypeID is correct + we could do a better check to make sure that the TypeID is correct before casting -- This is safe because we check that the generic type matches before casting. */ @@ -104,7 +104,11 @@ impl Scope { let new_nodes = caller(ctx, props); let old_nodes: &mut VNode<'static> = unsafe { &mut *self.cur_node }; - // perform the diff, dumping into the change list + // TODO: Iterate through the new nodes + // move any listeners into ourself + + // perform the diff, dumping into the mutable change list + // this doesnt perform any "diff compression" where an event and a re-render crate::diff::diff(old_nodes, &new_nodes); } } diff --git a/packages/core/src/validation.rs b/packages/core/src/validation.rs index f7dd66994..64d9b88b3 100644 --- a/packages/core/src/validation.rs +++ b/packages/core/src/validation.rs @@ -13,6 +13,9 @@ mod validation { // look them up by their unique id. // When the DomUpdater sees that the element no longer exists it will drop all of it's // Rc'd Closures for those events. + // It doesn't quite make sense to keep this here, perhaps just in the html crate? + // Dioxus itself shouldn't be concerned with the attribute names + // a ftk! static SELF_CLOSING_TAGS: Lazy> = Lazy::new(|| { [ "area", "base", "br", "col", "hr", "img", "input", "link", "meta", "param", "command", diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 1a08e35e7..aad8ca429 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -1,29 +1,5 @@ -/* -The Dioxus Virtual Dom integrates an event system and virtual nodes to create reactive user interfaces. - -The Dioxus VDom uses the same underlying mechanics as Dodrio (double buffering, bump dom, etc). -Instead of making the allocator very obvious, we choose to parametrize over the DomTree trait. For our purposes, -the DomTree trait is simply an abstraction over a lazy dom builder, much like the iterator trait. - -This means we can accept DomTree anywhere as well as return it. All components therefore look like this: -```ignore -function Component(ctx: Context<()>) -> VNode { - ctx.view(html! {
"hello world"
}) -} -``` -It's not quite as sexy as statics, but there's only so much you can do. The goal is to get statics working with the FC macro, -so types don't get in the way of you and your component writing. Fortunately, this is all generic enough to be split out -into its own lib (IE, lazy loading wasm chunks by function (exciting stuff!)) - -```ignore -#[fc] // gets translated into a function. -static Component: FC = |ctx| { - ctx.view(html! {
"hello world"
}) -} -``` -*/ -use crate::inner::*; use crate::nodes::VNode; +use crate::{events::EventTrigger, innerlude::*}; use any::Any; use bumpalo::Bump; use generational_arena::{Arena, Index}; @@ -46,13 +22,12 @@ pub struct VirtualDom { base_scope: Index, /// Components generate lifecycle events - event_queue: Vec, + event_queue: Vec, // wrap in a lock or something so // mark the root props with P, even though they're held by the root component _p: PhantomData

, } -/// Implement VirtualDom with no props for components that initialize their state internal to the VDom rather than externally. impl VirtualDom<()> { /// Create a new instance of the Dioxus Virtual Dom with no properties for the root component. /// @@ -63,7 +38,6 @@ impl VirtualDom<()> { } } -/// Implement the VirtualDom for any Properties impl VirtualDom

{ /// Start a new VirtualDom instance with a dependent props. /// Later, the props can be updated by calling "update" with a new set of props, causing a set of re-renders. @@ -97,15 +71,69 @@ impl VirtualDom

{ } } + /// With access to the virtual dom, schedule an update to the Root component's props + pub fn update_props(&mut self, new_props: P) { + self.event_queue.push(LifecycleEvent { + event_type: LifecycleType::PropsChanged { + props: Box::new(new_props), + }, + index: self.base_scope, + }); + } + /// Pop an event off the event queue and process it + /// Update the root props, and progress + /// Takes a bump arena to allocate into, making the diff phase as fast as possible + /// pub fn progress(&mut self) -> Result<()> { let event = self.event_queue.pop().ok_or(Error::NoEvent)?; - process_event(self, event) } - /// Update the root props, causing a full event cycle - pub fn update_props(&mut self, new_props: P) {} + /// This method is the most sophisticated way of updating the virtual dom after an external event has been triggered. + /// + /// Given a synthetic event, the component that triggered the event, and the index of the callback, this runs the virtual + /// dom to completion, tagging components that need updates, compressing events together, and finally emitting a single + /// change list. + /// + /// If implementing an external renderer, this is the perfect method to combine with an async event loop that waits on + /// listeners. + /// + /// ```ignore + /// + /// + /// + /// + /// ``` + pub async fn progress_with_event(&mut self, evt: EventTrigger) -> Result<()> { + let EventTrigger { + component_id, + listener_id, + event, + } = evt; + + let component = self + .components + .get(component_id) + // todo: update this with a dedicated error type so implementors know what went wrong + .expect("Component should exist if an event was triggered"); + + let listener = component + .listeners + .get(listener_id as usize) + .expect("Listener should exist if it was triggered") + .as_ref(); + + // Run the callback + // Theoretically, this should + listener(); + + Ok(()) + } + + pub async fn progress_completely(&mut self) -> Result<()> { + Ok(()) + } } /// Using mutable access to the Virtual Dom, progress a given lifecycle event @@ -159,6 +187,9 @@ fn process_event( // Event from renderer was fired with a given listener ID // LifecycleType::Callback { listener_id } => {} + + // Run any post-render callbacks on a component + LifecycleType::Rendered => {} } Ok(()) @@ -182,6 +213,7 @@ pub enum LifecycleType { PropsChanged { props: Box, }, + Rendered, Mounted, Removed, Messaged, diff --git a/packages/web/Cargo.toml b/packages/web/Cargo.toml index 6ec34715d..7959cffb5 100644 --- a/packages/web/Cargo.toml +++ b/packages/web/Cargo.toml @@ -9,9 +9,11 @@ edition = "2018" [dependencies] dioxus-core = { path = "../core" } js-sys = "0.3" -wasm-bindgen = "0.2.69" -html-validation = { path = "../html-validation", version = "0.1.1" } +wasm-bindgen = "0.2.70" lazy_static = "1.4.0" +wasm-bindgen-futures = "0.4.20" +futures = "0.3.12" +# html-validation = { path = "../html-validation", version = "0.1.1" } [dependencies.web-sys] version = "0.3" diff --git a/packages/web/examples/hello.rs b/packages/web/examples/hello.rs new file mode 100644 index 000000000..eec9c1fe8 --- /dev/null +++ b/packages/web/examples/hello.rs @@ -0,0 +1,21 @@ +use dioxus_core::prelude::*; +use dioxus_web::WebsysRenderer; + +fn main() { + // todo: set this up so the websys render can spawn itself rather than having to wrap it + // almost like bundling an executor with the wasm version + wasm_bindgen_futures::spawn_local(async { + WebsysRenderer::new(Example) + .run() + .await + .expect("Dioxus Failed! This should *not* happen!") + }); +} + +static Example: FC<()> = |ctx, props| { + ctx.view(html! { +

+ "Hello world!" +
+ }) +}; diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs index 39a857565..34fddfd7a 100644 --- a/packages/web/src/lib.rs +++ b/packages/web/src/lib.rs @@ -1,211 +1,113 @@ -use std::{collections::HashMap, fmt, rc::Rc}; -use web_sys::{self, Element, EventTarget, Node, Text}; +//! Dioxus WebSys +//! +//! This crate implements a renderer of the Dioxus Virtual DOM for the web browser. +//! +//! While `VNode` supports "to_string" directly, it renders child components as the RSX! macro tokens. For custom components, +//! an external renderer is needed to progress the component lifecycles. The `WebsysRenderer` shows how to use the Virtual DOM +//! API to progress these lifecycle events to generate a fully-mounted Virtual DOM instance which can be renderer in the +//! `render` method. +//! +//! ```ignore +//! fn main() { +//! let renderer = WebsysRenderer::<()>::new(|_| html! {
"Hello world"
}); +//! let output = renderer.render(); +//! assert_eq!(output, "
Hello World
"); +//! } +//! ``` +//! +//! The `WebsysRenderer` is particularly useful when needing to cache a Virtual DOM in between requests +//! -use dioxus_core::prelude::{VElement, VNode, VText, VirtualNode}; -use std::ops::Deref; -use std::sync::Mutex; -use wasm_bindgen::JsCast; -use wasm_bindgen::JsValue; +use dioxus_core::{ + events::EventTrigger, + prelude::{Properties, VNode, VirtualDom, FC}, +}; +use futures::{channel::mpsc, future, SinkExt, StreamExt}; +use mpsc::UnboundedSender; -pub struct DomRenderer {} +/// 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 -// Used to uniquely identify elements that contain closures so that the DomUpdater can -// look them up by their unique id. -// When the DomUpdater sees that the element no longer exists it will drop all of it's -// Rc'd Closures for those events. -use lazy_static::lazy_static; -lazy_static! { - static ref ELEM_UNIQUE_ID: Mutex = Mutex::new(0); +/// +pub struct WebsysRenderer { + internal_dom: VirtualDom, } -fn create_unique_identifier() -> u32 { - let mut elem_unique_id = ELEM_UNIQUE_ID.lock().unwrap(); - *elem_unique_id += 1; - *elem_unique_id -} - -/// A node along with all of the closures that were created for that -/// node's events and all of it's child node's events. -pub struct CreatedNode { - /// A `Node` or `Element` that was created from a `VirtualNode` - pub node: T, - /// A map of a node's unique identifier along with all of the Closures for that node. +/// Implement VirtualDom with no props for components that initialize their state internal to the VDom rather than externally. +impl WebsysRenderer<()> { + /// Create a new instance of the Dioxus Virtual Dom with no properties for the root component. /// - /// The DomUpdater uses this to look up nodes and see if they're still in the page. If not - /// the reference that we maintain to their closure will be dropped, thus freeing the Closure's - /// memory. - pub closures: HashMap>, -} - -impl CreatedNode { - pub fn without_closures>(node: N) -> Self { - CreatedNode { - node: node.into(), - closures: HashMap::with_capacity(0), - } + /// 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, ()) } } -impl Deref for CreatedNode { - type Target = T; - fn deref(&self) -> &Self::Target { - &self.node +impl WebsysRenderer { + /// 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` + pub fn new_with_props(root: FC, root_props: T) -> Self { + Self::from_vdom(VirtualDom::new_with_props(root, root_props)) } -} -impl From> for CreatedNode { - fn from(other: CreatedNode) -> CreatedNode { - CreatedNode { - node: other.node.into(), - closures: other.closures, - } + /// 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 { + Self { internal_dom: dom } } -} -//---------------------------------- -// Create nodes for the VNode types -// --------------------------------- + /// Run the renderer, progressing any events that crop up + /// Yield on event handlers + /// If the dom errors out, self is consumed and the dom is torn down + pub async fn run(self) -> dioxus_core::error::Result<()> { + let WebsysRenderer { mut internal_dom } = self; -/// Return a `Text` element from a `VirtualNode`, typically right before adding it -/// into the DOM. -pub fn create_text_node(text_node: &VText) -> Text { - let document = web_sys::window().unwrap().document().unwrap(); - document.create_text_node(&text_node.text) -} + // Progress the mount of the root component + internal_dom + .progress() + .expect("Progressing the root failed :("); -/// Build a DOM element by recursively creating DOM nodes for this element and it's -/// children, it's children's children, etc. -pub fn create_element_node(velement: &VElement) -> CreatedNode { - let document = web_sys::window().unwrap().document().unwrap(); + // set up the channels to connect listeners to the event loop + let (sender, mut receiver) = mpsc::unbounded::(); - let element = if html_validation::is_svg_namespace(&velement.tag) { - document - .create_element_ns(Some("http://www.w3.org/2000/svg"), &velement.tag) - .unwrap() - } else { - document.create_element(&velement.tag).unwrap() - }; - - let mut closures = HashMap::new(); - - velement.attrs.iter().for_each(|(name, value)| { - if name == "unsafe_inner_html" { - element.set_inner_html(value); - - return; + // Iterate through the nodes, attaching the closure and sender to the listener + { + let mut remote_sender = sender.clone(); + let f = move || { + let event = EventTrigger::new(); + wasm_bindgen_futures::spawn_local(async move { + remote_sender + .send(event) + .await + .expect("Updating receiver failed"); + }) + }; } - element - .set_attribute(name, value) - .expect("Set element attribute in create element"); - }); + // 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 { + // event is triggered + // relevant listeners are ran + // internal state is modified, components are tagged for changes - todo!("Support events properly in web "); - // if velement.events.0.len() > 0 { - // let unique_id = create_unique_identifier(); - - // element - // .set_attribute("data-vdom-id".into(), &unique_id.to_string()) - // .expect("Could not set attribute on element"); - - // closures.insert(unique_id, vec![]); - - // velement.events.0.iter().for_each(|(onevent, callback)| { - // // onclick -> click - // let event = &onevent[2..]; - - // let current_elem: &EventTarget = element.dyn_ref().unwrap(); - - // current_elem - // .add_event_listener_with_callback(event, callback.as_ref().as_ref().unchecked_ref()) - // .unwrap(); - - // closures - // .get_mut(&unique_id) - // .unwrap() - // .push(Rc::clone(callback)); - // }); - // } - - let mut previous_node_was_text = false; - - velement.children.iter().for_each(|child| { - match child { - VNode::Text(text_node) => { - let current_node = element.as_ref() as &web_sys::Node; - - // We ensure that the text siblings are patched by preventing the browser from merging - // neighboring text nodes. Originally inspired by some of React's work from 2016. - // -> https://reactjs.org/blog/2016/04/07/react-v15.html#major-changes - // -> https://github.com/facebook/react/pull/5753 - // - // `ptns` = Percy text node separator - if previous_node_was_text { - let separator = document.create_comment("ptns"); - current_node - .append_child(separator.as_ref() as &web_sys::Node) - .unwrap(); - } - - current_node - .append_child(&create_text_node(&text_node)) - // .append_child(&text_node.create_text_node()) - .unwrap(); - - previous_node_was_text = true; - } - VNode::Element(element_node) => { - previous_node_was_text = false; - - let child = create_element_node(&element_node); - // let child = element_node.create_element_node(); - let child_elem: Element = child.node; - - closures.extend(child.closures); - - element.append_child(&child_elem).unwrap(); - } - - VNode::Component(component) => { - // - todo!("Support components in the web properly"); + match internal_dom.progress_with_event(event).await { + Err(_) => {} + Ok(_) => render_diffs(), } + // waiting for next event to arrive from the external triggers } - }); - todo!("Support events properly in web "); - // if let Some(on_create_elem) = velement.events.0.get("on_create_elem") { - // let on_create_elem: &js_sys::Function = on_create_elem.as_ref().as_ref().unchecked_ref(); - // on_create_elem - // .call1(&wasm_bindgen::JsValue::NULL, &element) - // .unwrap(); - // } - - CreatedNode { - node: element, - closures, + Ok(()) } } -/// Box>> is our js_sys::Closure. Stored this way to allow us to store -/// any Closure regardless of the arguments. -pub type DynClosure = Rc>; +/// For any listeners in the tree, attach the sender closure. +/// When a event is triggered, we convert it into the synthetic event type and dump it back in the Virtual Dom's queu +fn attach_listeners(sender: &UnboundedSender, dom: &VirtualDom

) {} -/// We need a custom implementation of fmt::Debug since JsValue doesn't -/// implement debug. -pub struct Events(pub HashMap); - -impl PartialEq for Events { - // TODO: What should happen here..? And why? - fn eq(&self, _rhs: &Self) -> bool { - true - } -} - -impl fmt::Debug for Events { - // Print out all of the event names for this VirtualNode - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let events: String = self.0.keys().map(|key| " ".to_string() + key).collect(); - write!(f, "{}", events) - } -} +fn render_diffs() {} diff --git a/packages/web/src/old/lib.rs b/packages/web/src/old/lib.rs new file mode 100644 index 000000000..39a857565 --- /dev/null +++ b/packages/web/src/old/lib.rs @@ -0,0 +1,211 @@ +use std::{collections::HashMap, fmt, rc::Rc}; +use web_sys::{self, Element, EventTarget, Node, Text}; + +use dioxus_core::prelude::{VElement, VNode, VText, VirtualNode}; +use std::ops::Deref; +use std::sync::Mutex; +use wasm_bindgen::JsCast; +use wasm_bindgen::JsValue; + +pub struct DomRenderer {} + +// Used to uniquely identify elements that contain closures so that the DomUpdater can +// look them up by their unique id. +// When the DomUpdater sees that the element no longer exists it will drop all of it's +// Rc'd Closures for those events. +use lazy_static::lazy_static; +lazy_static! { + static ref ELEM_UNIQUE_ID: Mutex = Mutex::new(0); +} + +fn create_unique_identifier() -> u32 { + let mut elem_unique_id = ELEM_UNIQUE_ID.lock().unwrap(); + *elem_unique_id += 1; + *elem_unique_id +} + +/// A node along with all of the closures that were created for that +/// node's events and all of it's child node's events. +pub struct CreatedNode { + /// A `Node` or `Element` that was created from a `VirtualNode` + pub node: T, + /// A map of a node's unique identifier along with all of the Closures for that node. + /// + /// The DomUpdater uses this to look up nodes and see if they're still in the page. If not + /// the reference that we maintain to their closure will be dropped, thus freeing the Closure's + /// memory. + pub closures: HashMap>, +} + +impl CreatedNode { + pub fn without_closures>(node: N) -> Self { + CreatedNode { + node: node.into(), + closures: HashMap::with_capacity(0), + } + } +} + +impl Deref for CreatedNode { + type Target = T; + fn deref(&self) -> &Self::Target { + &self.node + } +} + +impl From> for CreatedNode { + fn from(other: CreatedNode) -> CreatedNode { + CreatedNode { + node: other.node.into(), + closures: other.closures, + } + } +} + +//---------------------------------- +// Create nodes for the VNode types +// --------------------------------- + +/// Return a `Text` element from a `VirtualNode`, typically right before adding it +/// into the DOM. +pub fn create_text_node(text_node: &VText) -> Text { + let document = web_sys::window().unwrap().document().unwrap(); + document.create_text_node(&text_node.text) +} + +/// Build a DOM element by recursively creating DOM nodes for this element and it's +/// children, it's children's children, etc. +pub fn create_element_node(velement: &VElement) -> CreatedNode { + let document = web_sys::window().unwrap().document().unwrap(); + + let element = if html_validation::is_svg_namespace(&velement.tag) { + document + .create_element_ns(Some("http://www.w3.org/2000/svg"), &velement.tag) + .unwrap() + } else { + document.create_element(&velement.tag).unwrap() + }; + + let mut closures = HashMap::new(); + + velement.attrs.iter().for_each(|(name, value)| { + if name == "unsafe_inner_html" { + element.set_inner_html(value); + + return; + } + + element + .set_attribute(name, value) + .expect("Set element attribute in create element"); + }); + + todo!("Support events properly in web "); + // if velement.events.0.len() > 0 { + // let unique_id = create_unique_identifier(); + + // element + // .set_attribute("data-vdom-id".into(), &unique_id.to_string()) + // .expect("Could not set attribute on element"); + + // closures.insert(unique_id, vec![]); + + // velement.events.0.iter().for_each(|(onevent, callback)| { + // // onclick -> click + // let event = &onevent[2..]; + + // let current_elem: &EventTarget = element.dyn_ref().unwrap(); + + // current_elem + // .add_event_listener_with_callback(event, callback.as_ref().as_ref().unchecked_ref()) + // .unwrap(); + + // closures + // .get_mut(&unique_id) + // .unwrap() + // .push(Rc::clone(callback)); + // }); + // } + + let mut previous_node_was_text = false; + + velement.children.iter().for_each(|child| { + match child { + VNode::Text(text_node) => { + let current_node = element.as_ref() as &web_sys::Node; + + // We ensure that the text siblings are patched by preventing the browser from merging + // neighboring text nodes. Originally inspired by some of React's work from 2016. + // -> https://reactjs.org/blog/2016/04/07/react-v15.html#major-changes + // -> https://github.com/facebook/react/pull/5753 + // + // `ptns` = Percy text node separator + if previous_node_was_text { + let separator = document.create_comment("ptns"); + current_node + .append_child(separator.as_ref() as &web_sys::Node) + .unwrap(); + } + + current_node + .append_child(&create_text_node(&text_node)) + // .append_child(&text_node.create_text_node()) + .unwrap(); + + previous_node_was_text = true; + } + VNode::Element(element_node) => { + previous_node_was_text = false; + + let child = create_element_node(&element_node); + // let child = element_node.create_element_node(); + let child_elem: Element = child.node; + + closures.extend(child.closures); + + element.append_child(&child_elem).unwrap(); + } + + VNode::Component(component) => { + // + todo!("Support components in the web properly"); + } + } + }); + + todo!("Support events properly in web "); + // if let Some(on_create_elem) = velement.events.0.get("on_create_elem") { + // let on_create_elem: &js_sys::Function = on_create_elem.as_ref().as_ref().unchecked_ref(); + // on_create_elem + // .call1(&wasm_bindgen::JsValue::NULL, &element) + // .unwrap(); + // } + + CreatedNode { + node: element, + closures, + } +} + +/// Box>> is our js_sys::Closure. Stored this way to allow us to store +/// any Closure regardless of the arguments. +pub type DynClosure = Rc>; + +/// We need a custom implementation of fmt::Debug since JsValue doesn't +/// implement debug. +pub struct Events(pub HashMap); + +impl PartialEq for Events { + // TODO: What should happen here..? And why? + fn eq(&self, _rhs: &Self) -> bool { + true + } +} + +impl fmt::Debug for Events { + // Print out all of the event names for this VirtualNode + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let events: String = self.0.keys().map(|key| " ".to_string() + key).collect(); + write!(f, "{}", events) + } +} diff --git a/packages/web/src/virtual_node_test_utils.rs b/packages/web/src/old/virtual_node_test_utils.rs similarity index 100% rename from packages/web/src/virtual_node_test_utils.rs rename to packages/web/src/old/virtual_node_test_utils.rs diff --git a/packages/web/src/util.rs b/packages/web/src/util.rs new file mode 100644 index 000000000..638cf3dc2 --- /dev/null +++ b/packages/web/src/util.rs @@ -0,0 +1 @@ +//! Utilities specific to websys