Browse Source

feat: a cute crm

Jonathan Kelley 3 năm trước cách đây
mục cha
commit
718fa14b45
6 tập tin đã thay đổi với 246 bổ sung33 xóa
  1. 1 1
      .vscode/settings.json
  2. 1 1
      Cargo.toml
  3. 77 0
      examples/crm.rs
  4. 7 0
      packages/html/src/lib.rs
  5. 115 0
      packages/web/examples/crm2.rs
  6. 45 31
      packages/web/src/dom.rs

+ 1 - 1
.vscode/settings.json

@@ -1,4 +1,4 @@
 {
-  "rust-analyzer.inlayHints.enable": true,
+  "rust-analyzer.inlayHints.enable": false,
   "rust-analyzer.cargo.allFeatures": true
 }

+ 1 - 1
Cargo.toml

@@ -19,7 +19,7 @@ dioxus-mobile = { path = "./packages/mobile", optional = true }
 
 [features]
 # core
-default = ["core", "ssr"]
+default = ["core", "ssr", "web"]
 core = ["macro", "hooks", "html"]
 macro = ["dioxus-core-macro"]
 hooks = ["dioxus-hooks"]

+ 77 - 0
examples/crm.rs

@@ -0,0 +1,77 @@
+/*
+Tiny CRM: A port of the Yew CRM example to Dioxus.
+*/
+use dioxus::prelude::*;
+
+fn main() {
+    dioxus::web::launch(App, |c| c);
+}
+
+enum Scene {
+    ClientsList,
+    NewClientForm,
+    Settings,
+}
+
+#[derive(Clone, Debug, Default)]
+pub struct Client {
+    pub first_name: String,
+    pub last_name: String,
+    pub description: String,
+}
+
+static App: FC<()> = |cx, _| {
+    let scene = use_state(cx, || Scene::ClientsList);
+    let clients = use_ref(cx, || vec![] as Vec<Client>);
+
+    let firstname = use_state(cx, || String::new());
+    let lastname = use_state(cx, || String::new());
+    let description = use_state(cx, || String::new());
+
+    match *scene {
+        Scene::ClientsList => {
+            rsx!(cx, div { class: "crm"
+                h1 { "List of clients" }
+                div { class: "clients" {clients.read().iter().map(|client| rsx!(
+                    div { class: "client" style: "margin-bottom: 50px"
+                        p { "First Name: {client.first_name}" }
+                        p { "Last Name: {client.last_name}" }
+                        p {"Description: {client.description}"}
+                    }))}
+                }
+                button { onclick: move |_| scene.set(Scene::NewClientForm), "Add New" }
+                button { onclick: move |_| scene.set(Scene::Settings), "Settings" }
+            })
+        }
+        Scene::NewClientForm => {
+            rsx!(cx, div { class: "crm"
+                h1 {"Add new client"}
+                div { class: "names"
+                    input { class: "new-client firstname" placeholder: "First name"
+                        onchange: move |e| firstname.set(e.value())
+                    }
+                    input { class: "new-client lastname" placeholder: "Last name"
+                        onchange: move |e| lastname.set(e.value())
+                    }
+                    textarea { class: "new-client description" placeholder: "Description"
+                        onchange: move |e| description.set(e.value())
+                    }
+                }
+                button { disabled: "false", onclick: move |_| clients.write().push(Client {
+                    description: (*description).clone(),
+                    first_name: (*firstname).clone(),
+                    last_name: (*lastname).clone(),
+
+                }), "Add New" }
+                button { onclick: move |_| scene.set(Scene::ClientsList), "Go Back" }
+            })
+        }
+        Scene::Settings => {
+            rsx!(cx, div {
+                h1 {"Settings"}
+                button { onclick: move |_| clients.write().clear() "Remove all clients"  }
+                button { onclick: move |_| scene.set(Scene::ClientsList), "Go Back"  }
+            })
+        }
+    }
+};

+ 7 - 0
packages/html/src/lib.rs

@@ -778,6 +778,7 @@ builder_constructors! {
         sizes: String, // FIXME
         title: String, // FIXME
         r#type: Mime,
+        integrity: String,
     };
 
     /// Build a
@@ -1715,6 +1716,12 @@ impl option {
     }
 }
 
+impl textarea {
+    pub fn value<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
+        cx.attr("value", val, None, true)
+    }
+}
+
 pub trait SvgAttributes {
     aria_trait_methods! {
         accent_height: "accent-height",

+ 115 - 0
packages/web/examples/crm2.rs

@@ -0,0 +1,115 @@
+/*
+Tiny CRM: A port of the Yew CRM example to Dioxus.
+*/
+
+use dioxus_core as dioxus;
+use dioxus_core::prelude::*;
+use dioxus_hooks::*;
+use dioxus_html as dioxus_elements;
+
+fn main() {
+    // Setup logging
+    wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
+    console_error_panic_hook::set_once();
+
+    // Run the app
+    static WrappedApp: FC<()> = |cx, _| {
+        rsx!(cx, body {
+            link {
+                rel: "stylesheet"
+                href: "https://unpkg.com/purecss@2.0.6/build/pure-min.css"
+                integrity: "sha384-Uu6IeWbM+gzNVXJcM9XV3SohHtmWE+3VGi496jvgX1jyvDTXfdK+rfZc8C1Aehk5"
+                crossorigin: "anonymous"
+            }
+            margin_left: "35%"
+            h1 {"Dioxus CRM Example"}
+            App {}
+        })
+    };
+    dioxus_web::launch(WrappedApp, |c| c)
+}
+
+enum Scene {
+    ClientsList,
+    NewClientForm,
+    Settings,
+}
+
+#[derive(Clone, Debug, Default)]
+pub struct Client {
+    pub first_name: String,
+    pub last_name: String,
+    pub description: String,
+}
+
+static App: FC<()> = |cx, _| {
+    let scene = use_state(cx, || Scene::ClientsList);
+    let clients = use_ref(cx, || vec![] as Vec<Client>);
+
+    let firstname = use_state(cx, || String::new());
+    let lastname = use_state(cx, || String::new());
+    let description = use_state(cx, || String::new());
+
+    match *scene {
+        Scene::ClientsList => {
+            rsx!(cx, div { class: "crm"
+                h2 { "List of clients" margin_bottom: "10px" }
+                div { class: "clients" margin_left: "10px"
+                    {clients.read().iter().map(|client| rsx!(
+                        div { class: "client" style: "margin-bottom: 50px"
+                            p { "First Name: {client.first_name}" }
+                            p { "Last Name: {client.last_name}" }
+                            p {"Description: {client.description}"}
+                        })
+                    )}
+                }
+                button { class: "pure-button pure-button-primary" onclick: move |_| scene.set(Scene::NewClientForm), "Add New" }
+                button { class: "pure-button" onclick: move |_| scene.set(Scene::Settings), "Settings" }
+            })
+        }
+        Scene::NewClientForm => {
+            let add_new = move |_| {
+                clients.write().push(Client {
+                    description: (*description).clone(),
+                    first_name: (*firstname).clone(),
+                    last_name: (*lastname).clone(),
+                });
+                description.set(String::new());
+                firstname.set(String::new());
+                lastname.set(String::new());
+            };
+            rsx!(cx, div { class: "crm"
+                h2 {"Add new client" margin_bottom: "10px" }
+                form { class: "pure-form"
+                    input { class: "new-client firstname" placeholder: "First name" value: "{firstname}"
+                        oninput: move |e| firstname.set(e.value())
+                    }
+                    input { class: "new-client lastname" placeholder: "Last name" value: "{lastname}"
+                        oninput: move |e| lastname.set(e.value())
+                    }
+                    textarea { class: "new-client description" placeholder: "Description" value: "{description}"
+                        oninput: move |e| description.set(e.value())
+                    }
+                }
+                button { class: "pure-button pure-button-primary", onclick: {add_new}, "Add New" }
+                button { class: "pure-button", onclick: move |_| scene.set(Scene::ClientsList), "Go Back" }
+            })
+        }
+        Scene::Settings => {
+            rsx!(cx, div {
+                h2 {"Settings" margin_bottom: "10px" }
+                button {
+                    background: "rgb(202, 60, 60)"
+                    class: "pure-button pure-button-primary"
+                    onclick: move |_| clients.write().clear(),
+                    "Remove all clients"
+                }
+                button {
+                    class: "pure-button pure-button-primary"
+                    onclick: move |_| scene.set(Scene::ClientsList),
+                    "Go Back"
+                }
+            })
+        }
+    }
+};

+ 45 - 31
packages/web/src/dom.rs

@@ -10,7 +10,7 @@ use fxhash::FxHashMap;
 use wasm_bindgen::{closure::Closure, JsCast, JsValue};
 use web_sys::{
     window, Attr, CssStyleDeclaration, Document, Element, Event, HtmlElement, HtmlInputElement,
-    HtmlOptionElement, Node, NodeList, UiEvent,
+    HtmlOptionElement, HtmlTextAreaElement, Node, NodeList, UiEvent,
 };
 
 use crate::{nodeslab::NodeSlab, WebConfig};
@@ -302,38 +302,52 @@ impl WebsysDom {
 
     fn set_attribute(&mut self, name: &str, value: &str, ns: Option<&str>) {
         let node = self.stack.top();
-        if let Some(el) = node.dyn_ref::<Element>() {
-            match ns {
-                // inline style support
-                Some("style") => {
-                    let el = el.dyn_ref::<HtmlElement>().unwrap();
-                    let style_dc: CssStyleDeclaration = el.style();
-                    style_dc.set_property(name, value).unwrap();
-                }
-                _ => el.set_attribute(name, value).unwrap(),
+        if ns == Some("style") {
+            if let Some(el) = node.dyn_ref::<Element>() {
+                let el = el.dyn_ref::<HtmlElement>().unwrap();
+                let style_dc: CssStyleDeclaration = el.style();
+                style_dc.set_property(name, value).unwrap();
             }
-        }
-
-        if let Some(input) = node.dyn_ref::<HtmlInputElement>() {
-            if name == "value" {
-                /*
-                if the attribute being set is the same as the value of the input, then don't bother setting it.
-                This is used in controlled components to keep the cursor in the right spot.
-
-                this logic should be moved into the virtualdom since we have the notion of "volatile"
-                */
-                if input.value() != value {
-                    input.set_value(value);
+        } else {
+            let fallback = || {
+                let el = node.dyn_ref::<Element>().unwrap();
+                el.set_attribute(name, value).unwrap()
+            };
+            match name {
+                "value" => {
+                    if let Some(input) = node.dyn_ref::<HtmlInputElement>() {
+                        /*
+                        if the attribute being set is the same as the value of the input, then don't bother setting it.
+                        This is used in controlled components to keep the cursor in the right spot.
+
+                        this logic should be moved into the virtualdom since we have the notion of "volatile"
+                        */
+                        if input.value() != value {
+                            input.set_value(value);
+                        }
+                    } else if let Some(node) = node.dyn_ref::<HtmlTextAreaElement>() {
+                        if name == "value" {
+                            node.set_value(value);
+                        }
+                    } else {
+                        fallback();
+                    }
                 }
-            }
-            if name == "checked" {
-                input.set_checked(true);
-            }
-        }
-
-        if let Some(node) = node.dyn_ref::<HtmlOptionElement>() {
-            if name == "selected" {
-                node.set_selected(true);
+                "checked" => {
+                    if let Some(input) = node.dyn_ref::<HtmlInputElement>() {
+                        input.set_checked(true);
+                    } else {
+                        fallback();
+                    }
+                }
+                "selected" => {
+                    if let Some(node) = node.dyn_ref::<HtmlOptionElement>() {
+                        node.set_selected(true);
+                    } else {
+                        fallback();
+                    }
+                }
+                _ => fallback(),
             }
         }
     }