feat: a cute crm

This commit is contained in:
Jonathan Kelley 2021-09-24 01:24:03 -04:00
parent 1a2f91ed91
commit 718fa14b45
6 changed files with 246 additions and 33 deletions

View File

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

View File

@ -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
examples/crm.rs Normal file
View File

@ -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" }
})
}
}
};

View File

@ -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",

View File

@ -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"
}
})
}
}
};

View File

@ -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();
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();
}
} 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();
}
}
_ => el.set_attribute(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);
"checked" => {
if let Some(input) = node.dyn_ref::<HtmlInputElement>() {
input.set_checked(true);
} else {
fallback();
}
}
}
if name == "checked" {
input.set_checked(true);
}
}
if let Some(node) = node.dyn_ref::<HtmlOptionElement>() {
if name == "selected" {
node.set_selected(true);
"selected" => {
if let Some(node) = node.dyn_ref::<HtmlOptionElement>() {
node.set_selected(true);
} else {
fallback();
}
}
_ => fallback(),
}
}
}