Explorar el Código

Feat: some updates, pull in the vnode enums

Jonathan Kelley hace 4 años
padre
commit
152bced

+ 1 - 0
packages/core/Cargo.toml

@@ -12,5 +12,6 @@ typed-arena = "2.0.1"
 virtual-dom-rs = { path = "../virtual-dom-rs" }
 virtual-node = { path = "../virtual-node" }
 html-macro = { path = "../html-macro" }
+once_cell = "1.5.2"
 # web-sys = "0.3.46"
 # js-sys = "0.3.46"

+ 10 - 8
packages/core/examples/simple.rs

@@ -3,13 +3,17 @@ use std::future::Future;
 use dioxus_core::{component::AnyContext, prelude::*};
 use virtual_dom_rs::Closure;
 
+// Stop-gap while developing
+// Need to update the macro
+type VirtualNode = VNode;
+
 pub fn main() {
     let dom = VirtualDom::new(root);
     let mut renderer = TextRenderer::new(dom);
     let output = renderer.render();
 }
 
-fn root(ctx: &mut AnyContext) -> VirtualNode {
+fn root(ctx: &mut AnyContext) -> VNode {
     // the regular html syntax
 
     // html! {
@@ -22,10 +26,10 @@ fn root(ctx: &mut AnyContext) -> VirtualNode {
 
     // or a manually crated vnode
     {
-        let mut node_0 = VirtualNode::element("div");
+        let mut node_0 = VNode::element("div");
         let mut node_1: IterableNodes = ("Hello world!").into();
         node_1.first().insert_space_before_text();
-        let mut node_2 = VirtualNode::element("button");
+        let mut node_2 = VNode::element("button");
         {
             // let closure = Closure::wrap(Box::new(|_| {}) as Box<FnMut(_)>);
             // let closure_rc = std::rc::Rc::new(closure);
@@ -44,13 +48,11 @@ fn root(ctx: &mut AnyContext) -> VirtualNode {
             element_node.children.extend(node_2.into_iter());
         }
 
-        VirtualNode
-
         node_0
     }
 }
 
-fn Head(ctx: &mut AnyContext) -> VirtualNode {
+fn Head(ctx: &mut AnyContext) -> VNode {
     html! {
         <head>
             {"Head Section"}
@@ -58,7 +60,7 @@ fn Head(ctx: &mut AnyContext) -> VirtualNode {
     }
 }
 
-fn Body(ctx: &mut AnyContext) -> VirtualNode {
+fn Body(ctx: &mut AnyContext) -> VNode {
     html! {
         <body>
             {"Footer Section"}
@@ -66,7 +68,7 @@ fn Body(ctx: &mut AnyContext) -> VirtualNode {
     }
 }
 
-fn Footer(ctx: &mut AnyContext) -> VirtualNode {
+fn Footer(ctx: &mut AnyContext) -> VNode {
     html! {
         <div>
             {"Footer Section"}

+ 529 - 11
packages/core/src/lib.rs

@@ -1,19 +1,40 @@
-use html_macro::html;
-use virtual_node::{IterableNodes, VElement, VirtualNode};
+//! Dioxus: a concurrent, functional, virtual dom for any renderer in Rust
+//!
+//!
+//!
+//!
+//!
+//!
+//!
+//!
+//!
+//!
+//!
+//!
+//!
+//!
 
-/// A re-export of everything to get macros working smoothly
+/// Re-export common types for ease of development use.
+/// Essential when working with the html! macro
 pub mod prelude {
     pub use crate::component::Context;
+    use crate::nodes;
     pub use crate::renderer::TextRenderer;
     pub use crate::types::FC;
     pub use crate::virtual_dom::VirtualDom;
+    pub use nodes::iterables::IterableNodes;
+    pub use nodes::*;
+
+    // Re-export from the macro crate
     pub use html_macro::html;
-    pub use virtual_node::{IterableNodes, VirtualNode};
 }
 
+/// The Dioxus Virtual Dom integrates an event system and virtual nodes to create reactive user interfaces.
 pub mod virtual_dom {
     use super::*;
 
+    /// An integrated virtual node system that progresses events and diffs UI trees.
+    /// Differences are converted into patches which a renderer can use to draw the UI.
     pub struct VirtualDom {}
 
     impl VirtualDom {
@@ -25,12 +46,472 @@ pub mod virtual_dom {
 
 /// Virtual Node Support
 pub mod nodes {
-    pub type VNode = virtual_node::VirtualNode;
-    // pub enum VNode {
-    //     VText,
-    //     VElement,
-    //     VComponent,
-    // }
+    pub use vcomponent::VComponent;
+    pub use velement::VElement;
+    pub use vnode::VNode;
+    pub use vtext::VText;
+
+    /// Tools for the base unit of the virtual dom - the VNode
+    /// VNodes are intended to be quickly-allocated, lightweight enum values.
+    ///
+    /// Components will be generating a lot of these very quickly, so we want to
+    /// limit the amount of heap allocations / overly large enum sizes.
+    mod vnode {
+        use super::*;
+
+        #[derive(PartialEq)]
+        pub enum VNode {
+            /// An element node (node type `ELEMENT_NODE`).
+            Element(VElement),
+            /// A text node (node type `TEXT_NODE`).
+            ///
+            /// Note: This wraps a `VText` instead of a plain `String` in
+            /// order to enable custom methods like `create_text_node()` on the
+            /// wrapped type.
+            Text(VText),
+
+            /// A User-defined componen node (node type COMPONENT_NODE)
+            Component(VComponent),
+        }
+
+        impl VNode {
+            /// Create a new virtual element node with a given tag.
+            ///
+            /// These get patched into the DOM using `document.createElement`
+            ///
+            /// ```ignore
+            /// let div = VNode::element("div");
+            /// ```
+            pub fn element<S>(tag: S) -> Self
+            where
+                S: Into<String>,
+            {
+                VNode::Element(VElement::new(tag))
+            }
+
+            /// Create a new virtual text node with the given text.
+            ///
+            /// These get patched into the DOM using `document.createTextNode`
+            ///
+            /// ```ignore
+            /// let div = VNode::text("div");
+            /// ```
+            pub fn text<S>(text: S) -> Self
+            where
+                S: Into<String>,
+            {
+                VNode::Text(VText::new(text.into()))
+            }
+
+            /// Return a [`VElement`] reference, if this is an [`Element`] variant.
+            ///
+            /// [`VElement`]: struct.VElement.html
+            /// [`Element`]: enum.VNode.html#variant.Element
+            pub fn as_velement_ref(&self) -> Option<&VElement> {
+                match self {
+                    VNode::Element(ref element_node) => Some(element_node),
+                    _ => None,
+                }
+            }
+
+            /// Return a mutable [`VElement`] reference, if this is an [`Element`] variant.
+            ///
+            /// [`VElement`]: struct.VElement.html
+            /// [`Element`]: enum.VNode.html#variant.Element
+            pub fn as_velement_mut(&mut self) -> Option<&mut VElement> {
+                match self {
+                    VNode::Element(ref mut element_node) => Some(element_node),
+                    _ => None,
+                }
+            }
+
+            /// Return a [`VText`] reference, if this is an [`Text`] variant.
+            ///
+            /// [`VText`]: struct.VText.html
+            /// [`Text`]: enum.VNode.html#variant.Text
+            pub fn as_vtext_ref(&self) -> Option<&VText> {
+                match self {
+                    VNode::Text(ref text_node) => Some(text_node),
+                    _ => None,
+                }
+            }
+
+            /// Return a mutable [`VText`] reference, if this is an [`Text`] variant.
+            ///
+            /// [`VText`]: struct.VText.html
+            /// [`Text`]: enum.VNode.html#variant.Text
+            pub fn as_vtext_mut(&mut self) -> Option<&mut VText> {
+                match self {
+                    VNode::Text(ref mut text_node) => Some(text_node),
+                    _ => None,
+                }
+            }
+
+            /// Used by html-macro to insert space before text that is inside of a block that came after
+            /// an open tag.
+            ///
+            /// html! { <div> {world}</div> }
+            ///
+            /// So that we end up with <div> world</div> when we're finished parsing.
+            pub fn insert_space_before_text(&mut self) {
+                match self {
+                    VNode::Text(text_node) => {
+                        text_node.text = " ".to_string() + &text_node.text;
+                    }
+                    _ => {}
+                }
+            }
+
+            /// Used by html-macro to insert space after braced text if we know that the next block is
+            /// another block or a closing tag.
+            ///
+            /// html! { <div>{Hello} {world}</div> } -> <div>Hello world</div>
+            /// html! { <div>{Hello} </div> } -> <div>Hello </div>
+            ///
+            /// So that we end up with <div>Hello world</div> when we're finished parsing.
+            pub fn insert_space_after_text(&mut self) {
+                match self {
+                    VNode::Text(text_node) => {
+                        text_node.text += " ";
+                    }
+                    _ => {}
+                }
+            }
+        }
+
+        // -----------------------------------------------
+        //  Convert from DOM elements to the primary enum
+        // -----------------------------------------------
+        impl From<VText> for VNode {
+            fn from(other: VText) -> Self {
+                VNode::Text(other)
+            }
+        }
+
+        impl From<VElement> for VNode {
+            fn from(other: VElement) -> Self {
+                VNode::Element(other)
+            }
+        }
+
+        impl From<&str> for VNode {
+            fn from(other: &str) -> Self {
+                VNode::text(other)
+            }
+        }
+
+        impl From<String> for VNode {
+            fn from(other: String) -> Self {
+                VNode::text(other.as_str())
+            }
+        }
+
+        // -----------------------------------------------
+        //  Allow VNodes to be iterated for map-based UI
+        // -----------------------------------------------
+        impl IntoIterator for VNode {
+            type Item = VNode;
+            // TODO: Is this possible with an array [VNode] instead of a vec?
+            type IntoIter = ::std::vec::IntoIter<VNode>;
+
+            fn into_iter(self) -> Self::IntoIter {
+                vec![self].into_iter()
+            }
+        }
+
+        impl Into<::std::vec::IntoIter<VNode>> for VNode {
+            fn into(self) -> ::std::vec::IntoIter<VNode> {
+                self.into_iter()
+            }
+        }
+
+        // -----------------------------------------------
+        //  Allow debug/display adherent to the HTML spec
+        // -----------------------------------------------
+        use std::fmt;
+        impl fmt::Debug for VNode {
+            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+                match self {
+                    VNode::Element(e) => write!(f, "Node::{:?}", e),
+                    VNode::Text(t) => write!(f, "Node::{:?}", t),
+                    VNode::Component(c) => write!(f, "Node::{:?}", c),
+                }
+            }
+        }
+
+        // Turn a VNode into an HTML string (delegate impl to variants)
+        impl fmt::Display for VNode {
+            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+                match self {
+                    VNode::Element(element) => write!(f, "{}", element),
+                    VNode::Text(text) => write!(f, "{}", text),
+                    VNode::Component(c) => write!(f, "{}", c),
+                }
+            }
+        }
+    }
+
+    mod velement {
+        use super::*;
+        use std::collections::HashMap;
+
+        #[derive(PartialEq)]
+        pub struct VElement {
+            /// The HTML tag, such as "div"
+            pub tag: String,
+
+            /// HTML attributes such as id, class, style, etc
+            pub attrs: HashMap<String, String>,
+
+            // TODO @Jon, re-enable "events"
+            //
+            // /// Events that will get added to your real DOM element via `.addEventListener`
+            // pub events: Events,
+            pub events: HashMap<String, ()>,
+
+            /// The children of this `VNode`. So a <div> <em></em> </div> structure would
+            /// have a parent div and one child, em.
+            pub children: Vec<VNode>,
+        }
+
+        impl VElement {
+            pub fn new<S>(tag: S) -> Self
+            where
+                S: Into<String>,
+            {
+                VElement {
+                    tag: tag.into(),
+                    attrs: HashMap::new(),
+                    events: HashMap::new(),
+                    // events: Events(HashMap::new()),
+                    children: vec![],
+                }
+            }
+        }
+
+        // -----------------------------------------------
+        //  Allow debug/display adherent to the HTML spec
+        // -----------------------------------------------
+        use std::fmt;
+        impl fmt::Debug for VElement {
+            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+                write!(
+                    f,
+                    "Element(<{}>, attrs: {:?}, children: {:?})",
+                    self.tag, self.attrs, self.children,
+                )
+            }
+        }
+
+        impl fmt::Display for VElement {
+            // Turn a VElement and all of it's children (recursively) into an HTML string
+            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+                write!(f, "<{}", self.tag).unwrap();
+
+                for (attr, value) in self.attrs.iter() {
+                    write!(f, r#" {}="{}""#, attr, value)?;
+                }
+
+                write!(f, ">")?;
+
+                for child in self.children.iter() {
+                    write!(f, "{}", child.to_string())?;
+                }
+
+                if !crate::validation::is_self_closing(&self.tag) {
+                    write!(f, "</{}>", self.tag)?;
+                }
+
+                Ok(())
+            }
+        }
+    }
+
+    mod vtext {
+        #[derive(PartialEq)]
+        pub struct VText {
+            pub text: String,
+        }
+
+        impl VText {
+            /// Create an new `VText` instance with the specified text.
+            pub fn new<S>(text: S) -> Self
+            where
+                S: Into<String>,
+            {
+                VText { text: text.into() }
+            }
+        }
+
+        // -----------------------------------------------
+        //  Convert from primitives directly into VText
+        // -----------------------------------------------
+        impl From<&str> for VText {
+            fn from(text: &str) -> Self {
+                VText {
+                    text: text.to_string(),
+                }
+            }
+        }
+
+        impl From<String> for VText {
+            fn from(text: String) -> Self {
+                VText { text }
+            }
+        }
+
+        // -----------------------------------------------
+        //  Allow debug/display adherent to the HTML spec
+        // -----------------------------------------------
+        use std::fmt;
+        impl fmt::Debug for VText {
+            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+                write!(f, "Text({})", self.text)
+            }
+        }
+
+        // Turn a VText into an HTML string
+        impl fmt::Display for VText {
+            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+                write!(f, "{}", self.text)
+            }
+        }
+    }
+
+    /// Virtual Components for custom user-defined components
+    /// Only supports the functional syntax
+    mod vcomponent {
+        #[derive(PartialEq)]
+        pub struct VComponent {}
+
+        // -----------------------------------------------
+        //  Allow debug/display adherent to the HTML spec
+        // -----------------------------------------------
+        use std::fmt;
+        impl fmt::Debug for VComponent {
+            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+                // TODO: @JON Implement how components should be formatted when spit out to html
+                // It probably can't be as straightforward as renderinng their VNodes
+                // It _could_ be, but we can't really implement that directly
+                // Instead, we should drop a vnode labeled with the component id/key
+
+                // write!(
+                //     f,
+                //     "Element(<{}>, attrs: {:?}, children: {:?})",
+                //     self.tag, self.attrs, self.children,
+                // )
+                Ok(())
+            }
+        }
+
+        impl fmt::Display for VComponent {
+            // Turn a VElement and all of it's children (recursively) into an HTML string
+            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+                // write!(f, "<{}", self.tag).unwrap();
+
+                // for (attr, value) in self.attrs.iter() {
+                //     write!(f, r#" {}="{}""#, attr, value)?;
+                // }
+
+                // write!(f, ">")?;
+
+                // for child in self.children.iter() {
+                //     write!(f, "{}", child.to_string())?;
+                // }
+
+                // if !crate::validation::is_self_closing(&self.tag) {
+                //     write!(f, "</{}>", self.tag)?;
+                // }
+
+                Ok(())
+            }
+        }
+    }
+
+    pub mod iterables {
+        use super::*;
+
+        /// Used by the html! macro for all braced child nodes so that we can use any type
+        /// that implements Into<IterableNodes>
+        ///
+        /// html! { <div> { nodes } </div> }
+        ///
+        /// nodes can be a String .. VNode .. Vec<VNode> ... etc
+        pub struct IterableNodes(Vec<VNode>);
+
+        impl IterableNodes {
+            /// Retrieve the first node mutably
+            pub fn first(&mut self) -> &mut VNode {
+                self.0.first_mut().unwrap()
+            }
+
+            /// Retrieve the last node mutably
+            pub fn last(&mut self) -> &mut VNode {
+                self.0.last_mut().unwrap()
+            }
+        }
+
+        impl IntoIterator for IterableNodes {
+            type Item = VNode;
+            // TODO: Is this possible with an array [VNode] instead of a vec?
+            type IntoIter = ::std::vec::IntoIter<VNode>;
+
+            fn into_iter(self) -> Self::IntoIter {
+                self.0.into_iter()
+            }
+        }
+
+        impl From<VNode> for IterableNodes {
+            fn from(other: VNode) -> Self {
+                IterableNodes(vec![other])
+            }
+        }
+
+        impl From<&str> for IterableNodes {
+            fn from(other: &str) -> Self {
+                IterableNodes(vec![VNode::text(other)])
+            }
+        }
+
+        impl From<String> for IterableNodes {
+            fn from(other: String) -> Self {
+                IterableNodes(vec![VNode::text(other.as_str())])
+            }
+        }
+
+        impl From<Vec<VNode>> for IterableNodes {
+            fn from(other: Vec<VNode>) -> Self {
+                IterableNodes(other)
+            }
+        }
+
+        // TODO @Jon
+        // Set this up so instead of the view trait, we can just take functions
+        // Functions with no context should just be rendered
+        // But functions with a context should be treated as regular components
+
+        // impl<V: View> From<Vec<V>> for IterableNodes {
+        //     fn from(other: Vec<V>) -> Self {
+        //         IterableNodes(other.into_iter().map(|it| it.render()).collect())
+        //     }
+        // }
+
+        // impl<V: View> From<&Vec<V>> for IterableNodes {
+        //     fn from(other: &Vec<V>) -> Self {
+        //         IterableNodes(other.iter().map(|it| it.render()).collect())
+        //     }
+        // }
+
+        // impl<V: View> From<&[V]> for IterableNodes {
+        //     fn from(other: &[V]) -> Self {
+        //         IterableNodes(other.iter().map(|it| it.render()).collect())
+        //     }
+        // }
+    }
+}
+
+pub mod diff {
+    pub enum Patch {}
 }
 
 /// Example on how to craft a renderer that interacts with the VirtualDom
@@ -78,9 +559,46 @@ pub mod types {
     pub type FC = fn(&mut AnyContext) -> VNode;
 }
 
-// #[cg(test)]
+/// TODO @Jon
+/// Figure out if validation should be its own crate, or embedded directly into dioxus
+/// Should we even be bothered with validation?
+mod validation {
+    use once_cell::sync::Lazy;
+    use std::collections::HashSet;
+
+    // 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.
+    static SELF_CLOSING_TAGS: Lazy<HashSet<&'static str>> = Lazy::new(|| {
+        [
+            "area", "base", "br", "col", "hr", "img", "input", "link", "meta", "param", "command",
+            "keygen", "source",
+        ]
+        .iter()
+        .cloned()
+        .collect()
+    });
+
+    /// Whether or not this tag is self closing
+    ///
+    /// ```
+    /// use html_validation::is_self_closing;
+    ///
+    /// assert_eq!(is_self_closing("br"), true);
+    ///
+    /// assert_eq!(is_self_closing("div"), false);
+    /// ```
+    pub fn is_self_closing(tag: &str) -> bool {
+        SELF_CLOSING_TAGS.contains(tag)
+        // SELF_CLOSING_TAGS.contains(tag) || is_self_closing_svg_tag(tag)
+    }
+}
+
+#[cfg(test)]
 mod integration_tests {
     use crate::prelude::*;
+    type VirtualNode = VNode;
 
     /// Test a basic usage of a virtual dom + text renderer combo
     #[test]

+ 0 - 0
packages/core/src/virtual_node.rs


+ 7 - 12
packages/virtual-node/src/lib.rs

@@ -57,9 +57,8 @@ pub enum VirtualNode {
     /// order to enable custom methods like `create_text_node()` on the
     /// wrapped type.
     Text(VText),
-
-    /// A User-defined componen node (node type COMPONENT_NODE)
-    Component(VComponent),
+    // /// A User-defined componen node (node type COMPONENT_NODE)
+    // Component(VComponent),
 }
 
 #[derive(PartialEq)]
@@ -83,9 +82,6 @@ pub struct VText {
     pub text: String,
 }
 
-#[derive(PartialEq)]
-pub struct VComponent {}
-
 impl VirtualNode {
     /// Create a new virtual element node with a given tag.
     ///
@@ -171,7 +167,7 @@ impl VirtualNode {
                 CreatedNode::without_closures(text_node.create_text_node())
             }
             VirtualNode::Element(element_node) => element_node.create_element_node().into(),
-            VirtualNode::Component(_) => todo!("WIP on Component Syntax"),
+            // VirtualNode::Component(_) => todo!("WIP on Component Syntax"),
         }
     }
 
@@ -311,10 +307,9 @@ impl VElement {
                     closures.extend(child.closures);
 
                     element.append_child(&child_elem).unwrap();
-                }
-                VirtualNode::Component(_) => {
-                    todo!("WIP on Component Syntax")
-                }
+                } // VirtualNode::Component(_) => {
+                  //     todo!("WIP on Component Syntax")
+                  // }
             }
         });
 
@@ -542,7 +537,7 @@ impl fmt::Debug for VirtualNode {
         match self {
             VirtualNode::Element(e) => write!(f, "Node::{:?}", e),
             VirtualNode::Text(t) => write!(f, "Node::{:?}", t),
-            VirtualNode::Component(c) => write!(f, "Node::{:?}", c),
+            // VirtualNode::Component(c) => write!(f, "Node::{:?}", c),
         }
     }
 }