Merge pull request #51 from DioxusLabs/jk/arbitrary_expressions

Jk/arbitrary expressions
This commit is contained in:
Jonathan Kelley authored and GitHub committed 2021-12-30 03:15:10 -05:00
commit 7d41a16208
65 files changed
+1028 -1422

No files matched your search

+12 -10
View File
@@ -11,18 +11,19 @@ documentation = "https://dioxuslabs.com"
keywords = ["dom", "ui", "gui", "react", "wasm"] keywords = ["dom", "ui", "gui", "react", "wasm"]
[dependencies] [dependencies]
dioxus-core = { path = "./packages/core", version = "^0.1.3" } dioxus-core = { path = "./packages/core", version = "^0.1.4" }
dioxus-html = { path = "./packages/html", optional = true } dioxus-html = { path = "./packages/html", version = "^0.1.1", optional = true }
dioxus-core-macro = { path = "./packages/core-macro", optional = true } dioxus-core-macro = { path = "./packages/core-macro", version = "^0.1.3", optional = true }
dioxus-hooks = { path = "./packages/hooks", optional = true } dioxus-hooks = { path = "./packages/hooks", optional = true }
dioxus-ssr = { path = "./packages/ssr", optional = true } dioxus-web = { path = "./packages/web", version = "^0.0.1", optional = true }
dioxus-web = { path = "./packages/web", optional = true }
dioxus-desktop = { path = "./packages/desktop", optional = true } dioxus-desktop = { path = "./packages/desktop", optional = true }
dioxus-ssr = { path = "./packages/ssr", optional = true }
dioxus-router = { path = "./packages/router", optional = true } dioxus-router = { path = "./packages/router", optional = true }
dioxus-mobile = { path = "./packages/mobile", optional = true } # dioxus-mobile = { path = "./packages/mobile", optional = true }
dioxus-liveview = { path = "./packages/liveview", optional = true } # dioxus-liveview = { path = "./packages/liveview", optional = true }
[features] [features]
default = ["macro", "hooks", "html"] default = ["macro", "hooks", "html"]
@@ -31,11 +32,13 @@ macro = ["dioxus-core-macro"]
hooks = ["dioxus-hooks"] hooks = ["dioxus-hooks"]
html = ["dioxus-html"] html = ["dioxus-html"]
router = ["dioxus-router"] router = ["dioxus-router"]
liveview = ["dioxus-liveview"]
ssr = ["dioxus-ssr"] ssr = ["dioxus-ssr"]
web = ["dioxus-web", "dioxus-router/web"] web = ["dioxus-web", "dioxus-router/web"]
desktop = ["dioxus-desktop", "dioxus-router/desktop"] desktop = ["dioxus-desktop", "dioxus-router/desktop"]
mobile = ["dioxus-mobile"]
# mobile = ["dioxus-mobile"]
# liveview = ["dioxus-liveview"]
[workspace] [workspace]
@@ -60,7 +63,6 @@ im-rc = "15.0.0"
fxhash = "0.2.1" fxhash = "0.2.1"
anyhow = "1.0.51" anyhow = "1.0.51"
serde_json = "1.0.73" serde_json = "1.0.73"
simple_logger = "1.16.0"
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
argh = "0.1.7" argh = "0.1.7"
+7 -1
View File
@@ -79,7 +79,9 @@ If you know React, then you already know Dioxus.
<tr> <tr>
</table> </table>
## Examples:
## Examples Projects:
| File Navigator (Desktop) | WiFi scanner (Desktop) | TodoMVC (All platforms) | Ecommerce w/ Tailwind (Liveview) | | File Navigator (Desktop) | WiFi scanner (Desktop) | TodoMVC (All platforms) | Ecommerce w/ Tailwind (Liveview) |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
@@ -88,6 +90,10 @@ If you know React, then you already know Dioxus.
See the awesome-dioxus page for a curated list of content in the Dioxus Ecosystem. See the awesome-dioxus page for a curated list of content in the Dioxus Ecosystem.
## Running examples locally
All local examples are built for the desktop renderer. This means you can simply clone this repo and call `cargo run --example EXAMPLE_NAME`. To run non-desktop examples, checkout the example projects shown above.
## Why Dioxus and why Rust? ## Why Dioxus and why Rust?
TypeScript is a fantastic addition to JavaScript, but it's still fundamentally JavaScript. TS code runs slightly slower, has tons of configuration options, and not every package is properly typed. TypeScript is a fantastic addition to JavaScript, but it's still fundamentally JavaScript. TS code runs slightly slower, has tons of configuration options, and not every package is properly typed.
+16 -24
View File
@@ -42,14 +42,6 @@ These examples are not necessarily meant to be run, but rather serve as a refere
| [Complete rsx reference](./rsx_usage.rs) | A complete reference for all rsx! usage | ✅ | | [Complete rsx reference](./rsx_usage.rs) | A complete reference for all rsx! usage | ✅ |
| [Event Listeners](./listener.rs) | Attach closures to events on elements | ✅ | | [Event Listeners](./listener.rs) | Attach closures to events on elements | ✅ |
These web-specific examples must be run with `dioxus-cli` using `dioxus develop --example XYZ`
| Example | What it does |
| ------- | ------------ |
| asd | this does |
| asd | this does |
## Show me some examples! ## Show me some examples!
@@ -64,7 +56,7 @@ Here's what a few common tasks look like in Dioxus:
Nested components with children and internal state: Nested components with children and internal state:
```rust ```rust
fn App(cx: Scope<()>) -> Element { fn App(cx: Scope) -> Element {
cx.render(rsx!( Toggle { "Toggle me" } )) cx.render(rsx!( Toggle { "Toggle me" } ))
} }
@@ -75,9 +67,9 @@ fn Toggle(cx: Scope<ToggleProps>) -> Element {
let mut toggled = use_state(&cx, || false); let mut toggled = use_state(&cx, || false);
cx.render(rsx!{ cx.render(rsx!{
div { div {
{&cx.props.children} &cx.props.children
button { onclick: move |_| toggled.set(true), button { onclick: move |_| toggled.set(true),
{toggled.and_then(|| "On").or_else(|| "Off")} toggled.and_then(|| "On").or_else(|| "Off")
} }
} }
}) })
@@ -86,7 +78,7 @@ fn Toggle(cx: Scope<ToggleProps>) -> Element {
Controlled inputs: Controlled inputs:
```rust ```rust
fn App(cx: Scope<()>) -> Element { fn App(cx: Scope) -> Element {
let value = use_state(&cx, String::new); let value = use_state(&cx, String::new);
cx.render(rsx!( cx.render(rsx!(
input { input {
@@ -100,7 +92,7 @@ fn App(cx: Scope<()>) -> Element {
Lists and Conditional rendering: Lists and Conditional rendering:
```rust ```rust
fn App(cx: Scope<()>) -> Element { fn App(cx: Scope) -> Element {
let list = (0..10).map(|i| { let list = (0..10).map(|i| {
rsx!(li { key: "{i}", "Value: {i}" }) rsx!(li { key: "{i}", "Value: {i}" })
}); });
@@ -112,8 +104,8 @@ fn App(cx: Scope<()>) -> Element {
if should_show { if should_show {
cx.render(rsx!( cx.render(rsx!(
{title} title,
ul { {list} } ul { list }
)) ))
} else { } else {
None None
@@ -128,7 +120,7 @@ static App: Component = |cx, _| rsx!(cx, div {"hello world!"});
Borrowed prop contents: Borrowed prop contents:
```rust ```rust
fn App(cx: Scope<()>) -> Element { fn App(cx: Scope) -> Element {
let name = use_state(&cx, || String::from("example")); let name = use_state(&cx, || String::from("example"));
rsx!(cx, Child { title: name.as_str() }) rsx!(cx, Child { title: name.as_str() })
} }
@@ -145,12 +137,12 @@ Global State
```rust ```rust
struct GlobalState { name: String } struct GlobalState { name: String }
fn App(cx: Scope<()>) -> Element { fn App(cx: Scope) -> Element {
use_provide_shared_state(cx, || GlobalState { name: String::from("Toby") }) use_provide_shared_state(cx, || GlobalState { name: String::from("Toby") })
rsx!(cx, Leaf {}) rsx!(cx, Leaf {})
} }
fn Leaf(cx: Scope<()>) -> Element { fn Leaf(cx: Scope) -> Element {
let state = use_consume_shared_state::<GlobalState>(cx)?; let state = use_consume_shared_state::<GlobalState>(cx)?;
rsx!(cx, "Hello {state.name}") rsx!(cx, "Hello {state.name}")
} }
@@ -166,20 +158,20 @@ enum Route {
Post(id) Post(id)
} }
fn App(cx: Scope<()>) -> Element { fn App(cx: Scope) -> Element {
let route = use_router(cx, Route::parse); let route = use_router(cx, Route::parse);
cx.render(rsx!(div { cx.render(rsx!(div {
{match route { match route {
Route::Home => rsx!( Home {} ), Route::Home => rsx!( Home {} ),
Route::Post(id) => rsx!( Post { id: id }) Route::Post(id) => rsx!( Post { id: id })
}} }
})) }))
} }
``` ```
Suspense Suspense
```rust ```rust
fn App(cx: Scope<()>) -> Element { fn App(cx: Scope) -> Element {
let doggo = use_suspense(cx, let doggo = use_suspense(cx,
|| async { reqwest::get("https://dog.ceo/api/breeds/image/random").await.unwrap().json::<Response>().await.unwrap() }, || async { reqwest::get("https://dog.ceo/api/breeds/image/random").await.unwrap().json::<Response>().await.unwrap() },
|response| cx.render(rsx!( img { src: "{response.message}" })) |response| cx.render(rsx!( img { src: "{response.message}" }))
@@ -187,8 +179,8 @@ fn App(cx: Scope<()>) -> Element {
cx.render(rsx!{ cx.render(rsx!{
div { div {
"One doggo coming right up:" "One doggo coming right up:",
{doggo} doggo
} }
}) })
} }
+10 -12
View File
@@ -8,38 +8,36 @@ use gloo_timers::future::TimeoutFuture;
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
dioxus::desktop::launch(App); dioxus::desktop::launch(app);
} }
pub static App: Component = |cx| { fn app(cx: Scope) -> Element {
let count = use_state(&cx, || 0); let count = use_state(&cx, || 0);
let mut direction = use_state(&cx, || 1); let direction = use_state(&cx, || 1);
let (async_count, dir) = (count.for_async(), *direction); let (async_count, dir) = (count.for_async(), *direction);
let task = use_coroutine(&cx, move || async move { let task = use_coroutine(&cx, move || async move {
loop { loop {
TimeoutFuture::new(250).await; TimeoutFuture::new(250).await;
*async_count.get_mut() += dir; *async_count.modify() += dir;
} }
}); });
rsx!(cx, div { rsx!(cx, div {
h1 {"count is {count}"} h1 {"count is {count}"}
button { button { onclick: move |_| task.stop(),
"Stop counting" "Stop counting"
onclick: move |_| task.stop()
} }
button { button { onclick: move |_| task.resume(),
"Start counting" "Start counting"
onclick: move |_| task.resume()
} }
button { button {
"Switch counting direcion"
onclick: move |_| { onclick: move |_| {
direction *= -1; *direction.modify() *= -1;
task.restart(); task.restart();
} },
"Switch counting direcion"
} }
}) })
}; }
+2 -2
View File
@@ -20,8 +20,8 @@ fn main() {
dioxus::desktop::launch(App); dioxus::desktop::launch(App);
} }
fn App(cx: Scope<()>) -> Element { fn App(cx: Scope) -> Element {
let text: &mut Vec<String> = cx.use_hook(|_| vec![String::from("abc=def")], |f| f); let text = cx.use_hook(|_| vec![String::from("abc=def")], |f| f);
let first = text.get_mut(0).unwrap(); let first = text.get_mut(0).unwrap();
+125 -94
View File
@@ -1,3 +1,5 @@
#![allow(non_snake_case)]
/* /*
This example is a simple iOS-style calculator. This particular example can run any platform - Web, Mobile, Desktop. This example is a simple iOS-style calculator. This particular example can run any platform - Web, Mobile, Desktop.
This calculator version uses React-style state management. All state is held as individual use_states. This calculator version uses React-style state management. All state is held as individual use_states.
@@ -10,10 +12,10 @@ use dioxus::prelude::*;
use separator::Separatable; use separator::Separatable;
fn main() { fn main() {
dioxus::desktop::launch(APP); dioxus::desktop::launch(app);
} }
static APP: Component = |cx| { fn app(cx: Scope) -> Element {
let cur_val = use_state(&cx, || 0.0_f64); let cur_val = use_state(&cx, || 0.0_f64);
let operator = use_state(&cx, || None as Option<&'static str>); let operator = use_state(&cx, || None as Option<&'static str>);
let display_value = use_state(&cx, || String::from("")); let display_value = use_state(&cx, || String::from(""));
@@ -21,109 +23,138 @@ static APP: Component = |cx| {
let toggle_percent = move |_| todo!(); let toggle_percent = move |_| todo!();
let input_digit = move |num: u8| display_value.modify().push_str(num.to_string().as_str()); let input_digit = move |num: u8| display_value.modify().push_str(num.to_string().as_str());
rsx!(cx, div { cx.render(rsx!(
class: "calculator", style { [include_str!("./assets/calculator.css")] }
onkeydown: move |evt| match evt.key_code { div {
KeyCode::Add => operator.set(Some("+")), class: "calculator",
KeyCode::Subtract => operator.set(Some("-")), onkeydown: move |evt| match evt.key_code {
KeyCode::Divide => operator.set(Some("/")), KeyCode::Add => operator.set(Some("+")),
KeyCode::Multiply => operator.set(Some("*")), KeyCode::Subtract => operator.set(Some("-")),
KeyCode::Num0 => input_digit(0), KeyCode::Divide => operator.set(Some("/")),
KeyCode::Num1 => input_digit(1), KeyCode::Multiply => operator.set(Some("*")),
KeyCode::Num2 => input_digit(2), KeyCode::Num0 => input_digit(0),
KeyCode::Num3 => input_digit(3), KeyCode::Num1 => input_digit(1),
KeyCode::Num4 => input_digit(4), KeyCode::Num2 => input_digit(2),
KeyCode::Num5 => input_digit(5), KeyCode::Num3 => input_digit(3),
KeyCode::Num6 => input_digit(6), KeyCode::Num4 => input_digit(4),
KeyCode::Num7 => input_digit(7), KeyCode::Num5 => input_digit(5),
KeyCode::Num8 => input_digit(8), KeyCode::Num6 => input_digit(6),
KeyCode::Num9 => input_digit(9), KeyCode::Num7 => input_digit(7),
KeyCode::Backspace => { KeyCode::Num8 => input_digit(8),
if !display_value.as_str().eq("0") { KeyCode::Num9 => input_digit(9),
display_value.modify().pop(); KeyCode::Backspace => {
} if !display_value.as_str().eq("0") {
} display_value.modify().pop();
_ => {}
}
div { class: "calculator-display", {[format_args!("{}", cur_val.separated_string())]} }
div { class: "input-keys"
div { class: "function-keys"
CalculatorKey {
{[if display_value == "0" { "C" } else { "AC" }]}
name: "key-clear",
onclick: move |_| {
display_value.set("0".to_string());
if display_value != "0" {
operator.set(None);
cur_val.set(0.0);
}
} }
} }
CalculatorKey { _ => {}
"±" },
name: "key-sign", div { class: "calculator-display", [cur_val.separated_string()] }
onclick: move |_| { div { class: "input-keys",
if display_value.starts_with("-") { div { class: "function-keys",
display_value.set(display_value.trim_start_matches("-").to_string()) CalculatorKey {
} else { name: "key-clear",
display_value.set(format!("-{}", *display_value)) onclick: move |_| {
display_value.set("0".to_string());
if display_value != "0" {
operator.set(None);
cur_val.set(0.0);
}
},
[if display_value == "0" { "C" } else { "AC" }]
}
CalculatorKey {
name: "key-sign",
onclick: move |_| {
if display_value.starts_with("-") {
display_value.set(display_value.trim_start_matches("-").to_string())
} else {
display_value.set(format!("-{}", *display_value))
}
},
"±"
}
CalculatorKey {
onclick: toggle_percent,
name: "key-percent",
"%"
}
}
div { class: "digit-keys",
CalculatorKey { name: "key-0", onclick: move |_| input_digit(0),
"0"
}
CalculatorKey { name: "key-dot", onclick: move |_| display_value.modify().push_str("."),
"●"
}
(1..9).map(|k| rsx!{
CalculatorKey {
key: "{k}",
name: "key-{k}",
onclick: move |_| input_digit(k),
"{k}"
} }
}, }),
} }
CalculatorKey {
"%"
onclick: {toggle_percent}
name: "key-percent",
}
}
div { class: "digit-keys"
CalculatorKey { name: "key-0", onclick: move |_| input_digit(0), "0" }
CalculatorKey { name: "key-dot", onclick: move |_| display_value.modify().push_str("."), "●" }
{(1..9).map(|k| rsx!{ div { class: "operator-keys",
CalculatorKey { key: "{k}", name: "key-{k}", onclick: move |_| input_digit(k), "{k}" } CalculatorKey {
})} name: "key-divide",
} onclick: move |_| operator.set(Some("/")),
div { class: "operator-keys" "÷"
CalculatorKey { name: "key-divide", onclick: move |_| operator.set(Some("/")) "÷" } }
CalculatorKey { name: "key-multiply", onclick: move |_| operator.set(Some("*")) "×" } CalculatorKey {
CalculatorKey { name: "key-subtract", onclick: move |_| operator.set(Some("-")) "−" } name: "key-multiply",
CalculatorKey { name: "key-add", onclick: move |_| operator.set(Some("+")) "+" } onclick: move |_| operator.set(Some("*")),
CalculatorKey { "×"
"=" }
name: "key-equals", CalculatorKey {
onclick: move |_| { name: "key-subtract",
if let Some(op) = operator.as_ref() { onclick: move |_| operator.set(Some("-")),
let rhs = display_value.parse::<f64>().unwrap(); "−"
let new_val = match *op { }
"+" => *cur_val + rhs, CalculatorKey {
"-" => *cur_val - rhs, name: "key-add",
"*" => *cur_val * rhs, onclick: move |_| operator.set(Some("+")),
"/" => *cur_val / rhs, "+"
_ => unreachable!(), }
}; CalculatorKey {
cur_val.set(new_val); name: "key-equals",
display_value.set(new_val.to_string()); onclick: move |_| {
operator.set(None); if let Some(op) = operator.as_ref() {
} let rhs = display_value.parse::<f64>().unwrap();
}, let new_val = match *op {
"+" => *cur_val + rhs,
"-" => *cur_val - rhs,
"*" => *cur_val * rhs,
"/" => *cur_val / rhs,
_ => unreachable!(),
};
cur_val.set(new_val);
display_value.set(new_val.to_string());
operator.set(None);
}
},
"="
}
} }
} }
} }
}) ))
}; }
#[derive(Props)] #[inline_props]
struct CalculatorKeyProps<'a> { fn CalculatorKey<'a>(
cx: Scope,
name: &'static str, name: &'static str,
onclick: &'a dyn Fn(Arc<MouseEvent>), onclick: &'a dyn Fn(Arc<MouseEvent>),
children: Element<'a>, children: Element<'a>,
} ) -> Element {
cx.render(rsx! {
fn CalculatorKey<'a>(cx: Scope<'a, CalculatorKeyProps<'a>>) -> Element { button {
rsx!(cx, button { class: "calculator-key {name}",
class: "calculator-key {cx.props.name}" onclick: onclick,
onclick: {cx.props.onclick} children
{&cx.props.children} }
}) })
} }
+1 -1
View File
@@ -15,7 +15,7 @@ fn main() {
assert!(g.edits.len() > 1); assert!(g.edits.len() > 1);
} }
fn App((cx, props): Scope<()>) -> Element { fn App((cx, props): Scope) -> Element {
let mut rng = SmallRng::from_entropy(); let mut rng = SmallRng::from_entropy();
let rows = (0..10_000_usize).map(|f| { let rows = (0..10_000_usize).map(|f| {
let label = Label::new(&mut rng); let label = Label::new(&mut rng);
+2 -2
View File
@@ -20,11 +20,11 @@ pub static Example: Component = |cx| {
li { onclick: move |_| example_data.set(f) li { onclick: move |_| example_data.set(f)
"ID: {f}" "ID: {f}"
ul { ul {
{(0..10).map(|k| rsx!{ (0..10).map(|k| rsx!{
li { li {
"Sub iterator: {f}.{k}" "Sub iterator: {f}.{k}"
} }
})} })
} }
} }
} }
+95 -82
View File
@@ -4,7 +4,7 @@ Tiny CRM: A port of the Yew CRM example to Dioxus.
use dioxus::prelude::*; use dioxus::prelude::*;
fn main() { fn main() {
dioxus::web::launch(App); dioxus::desktop::launch(app);
} }
enum Scene { enum Scene {
ClientsList, ClientsList,
@@ -19,88 +19,101 @@ pub struct Client {
pub description: String, pub description: String,
} }
static App: Component = |cx| { fn app(cx: Scope) -> Element {
let mut clients = use_ref(&cx, || vec![] as Vec<Client>); let clients = use_ref(&cx, || vec![] as Vec<Client>);
let mut scene = use_state(&cx, || Scene::ClientsList);
let mut firstname = use_state(&cx, || String::new()); let scene = use_state(&cx, || Scene::ClientsList);
let mut lastname = use_state(&cx, || String::new()); let firstname = use_state(&cx, String::new);
let mut description = use_state(&cx, || String::new()); let lastname = use_state(&cx, String::new);
let description = use_state(&cx, String::new);
let scene = 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.clone())
}
input { class: "new-client lastname" placeholder: "Last name" value: "{lastname}"
oninput: move |e| lastname.set(e.value.clone())
}
textarea { class: "new-client description" placeholder: "Description" value: "{description}"
oninput: move |e| description.set(e.value.clone())
}
}
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"
}
})
}
};
cx.render(rsx!( cx.render(rsx!(
body { body { margin_left: "35%",
link { link {
rel: "stylesheet" rel: "stylesheet",
href: "https://unpkg.com/purecss@2.0.6/build/pure-min.css" href: "https://unpkg.com/purecss@2.0.6/build/pure-min.css",
integrity: "sha384-Uu6IeWbM+gzNVXJcM9XV3SohHtmWE+3VGi496jvgX1jyvDTXfdK+rfZc8C1Aehk5" integrity: "sha384-Uu6IeWbM+gzNVXJcM9XV3SohHtmWE+3VGi496jvgX1jyvDTXfdK+rfZc8C1Aehk5",
crossorigin: "anonymous" crossorigin: "anonymous",
} }
margin_left: "35%"
h1 {"Dioxus CRM Example"} h1 {"Dioxus CRM Example"}
{scene}
} match *scene {
Scene::ClientsList => rsx!(
div { class: "crm",
h2 { margin_bottom: "10px", "List of clients" }
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 => rsx!(
div { class: "crm",
h2 { margin_bottom: "10px", "Add new client" }
form { class: "pure-form",
input {
class: "new-client firstname",
placeholder: "First name",
value: "{firstname}",
oninput: move |e| firstname.set(e.value.clone())
}
input {
class: "new-client lastname",
placeholder: "Last name",
value: "{lastname}",
oninput: move |e| lastname.set(e.value.clone())
}
textarea {
class: "new-client description",
placeholder: "Description",
value: "{description}",
oninput: move |e| description.set(e.value.clone())
}
}
button {
class: "pure-button pure-button-primary",
onclick: 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());
},
"Add New"
}
button { class: "pure-button", onclick: move |_| scene.set(Scene::ClientsList),
"Go Back"
}
}
),
Scene::Settings => rsx!(
div {
h2 { margin_bottom: "10px", "Settings" }
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"
}
}
)
}
}
)) ))
}; }
+1 -1
View File
@@ -13,6 +13,6 @@ static App: Component = |cx| {
div { div {
"hello world!" "hello world!"
} }
{(0..10).map(|f| rsx!( div {"abc {f}"}))} (0..10).map(|f| rsx!( div {"abc {f}"}))
)) ))
}; };
-8
View File
@@ -1,8 +0,0 @@
use dioxus_core as dioxus;
use dioxus_core::prelude::*;
use dioxus_core_macro::*;
use dioxus_hooks::*;
use dioxus_html as dioxus_elements;
fn main() {}
-1
View File
@@ -1 +0,0 @@
fn main() {}
+7 -12
View File
@@ -5,15 +5,10 @@ use dioxus_core::prelude::*;
use dioxus_core_macro::*; use dioxus_core_macro::*;
use dioxus_hooks::*; use dioxus_hooks::*;
use dioxus_html as dioxus_elements; use dioxus_html as dioxus_elements;
use simple_logger::SimpleLogger;
use std::collections::HashMap; use std::collections::HashMap;
fn main() { fn main() {
if cfg!(debug_assertions) {
SimpleLogger::new().init().unwrap();
}
dioxus_desktop::launch(App) dioxus_desktop::launch(App)
} }
@@ -103,24 +98,24 @@ pub static App: Component = |cx| {
} }
} }
} }
ul { class: "todo-list" ul { class: "todo-list",
{filtered_todos.iter().map(|id| rsx!(TodoEntry { key: "{id}", id: *id }))} filtered_todos.iter().map(|id| rsx!(TodoEntry { key: "{id}", id: *id }))
} }
{(!todos.read().is_empty()).then(|| rsx!( (!todos.read().is_empty()).then(|| rsx!(
footer { class: "footer" footer { class: "footer",
span { class: "todo-count" strong {"{items_left} "} span {"{item_text} left"} } span { class: "todo-count" strong {"{items_left} "} span {"{item_text} left"} }
ul { class: "filters" ul { class: "filters"
li { class: "All", a { onclick: move |_| filter.set(FilterState::All), "All" }} li { class: "All", a { onclick: move |_| filter.set(FilterState::All), "All" }}
li { class: "Active", a { onclick: move |_| filter.set(FilterState::Active), "Active" }} li { class: "Active", a { onclick: move |_| filter.set(FilterState::Active), "Active" }}
li { class: "Completed", a { onclick: move |_| filter.set(FilterState::Completed), "Completed" }} li { class: "Completed", a { onclick: move |_| filter.set(FilterState::Completed), "Completed" }}
} }
{(show_clear_completed).then(|| rsx!( (show_clear_completed).then(|| rsx!(
button { class: "clear-completed", onclick: move |_| clear_completed(), button { class: "clear-completed", onclick: move |_| clear_completed(),
"Clear completed" "Clear completed"
} }
))} ))
} }
))} ))
} }
} }
footer { class: "info" footer { class: "info"
+30 -44
View File
@@ -4,51 +4,45 @@
//! This is a fun little desktop application that lets you explore the file system. //! This is a fun little desktop application that lets you explore the file system.
//! //!
//! This example is interesting because it's mixing filesystem operations and GUI, which is typically hard for UI to do. //! This example is interesting because it's mixing filesystem operations and GUI, which is typically hard for UI to do.
//!
//! It also uses `use_ref` to maintain a model, rather than `use_state`. That way,
//! we dont need to clutter our code with `read` commands.
use dioxus::prelude::*; use dioxus::prelude::*;
fn main() { fn main() {
// simple_logger::init_with_level(log::Level::Debug); dioxus::desktop::launch_cfg(app, |c| {
dioxus::desktop::launch_cfg(App, |c| {
c.with_window(|w| { c.with_window(|w| {
w.with_resizable(true).with_inner_size( w.with_resizable(true)
dioxus::desktop::wry::application::dpi::LogicalSize::new(400.0, 800.0), .with_inner_size(dioxus::desktop::tao::dpi::LogicalSize::new(400.0, 800.0))
)
}) })
}); });
} }
static App: Component = |cx| { fn app(cx: Scope) -> Element {
let file_manager = use_ref(&cx, Files::new); let files = use_ref(&cx, Files::new);
let files = file_manager.read();
let file_list = files.path_names.iter().enumerate().map(|(dir_id, path)| {
rsx! (
li { a {"{path}", onclick: move |_| file_manager.write().enter_dir(dir_id), href: "#"} }
)
});
let err_disp = files.err.as_ref().map(|err| {
rsx! (
div {
code {"{err}"}
button {"x", onclick: move |_| file_manager.write().clear_err() }
}
)
});
let current_dir = files.current();
cx.render(rsx!( cx.render(rsx!(
div { h1 { "Files: " }
h1 {"Files: "} h3 { "Cur dir: " [files.read().current()] }
h3 {"Cur dir: {current_dir}"} button { onclick: move |_| files.write().go_up(), "go up" }
button { "go up", onclick: move |_| file_manager.write().go_up() } ol {
ol { {file_list} } files.read().path_names.iter().enumerate().map(|(dir_id, path)| rsx!(
{err_disp} li { key: "{path}",
a { href: "#", onclick: move |_| files.write().enter_dir(dir_id),
"{path}",
}
}
))
} }
files.read().err.as_ref().map(|err| rsx!(
div {
code { "{err}" }
button { onclick: move |_| files.write().clear_err(), "x" }
}
))
)) ))
}; }
struct Files { struct Files {
path_stack: Vec<String>, path_stack: Vec<String>,
@@ -70,29 +64,21 @@ impl Files {
} }
fn reload_path_list(&mut self) { fn reload_path_list(&mut self) {
let cur_path = self.path_stack.last().unwrap(); let paths = match std::fs::read_dir(self.path_stack.last().unwrap()) {
log::info!("Reloading path list for {:?}", cur_path);
let paths = match std::fs::read_dir(cur_path) {
Ok(e) => e, Ok(e) => e,
Err(err) => { Err(err) => {
let err = format!("An error occured: {:?}", err); self.err = Some(format!("An error occured: {:?}", err));
self.err = Some(err);
self.path_stack.pop(); self.path_stack.pop();
return; return;
} }
}; };
let collected = paths.collect::<Vec<_>>();
log::info!("Path list reloaded {:#?}", collected);
// clear the current state // clear the current state
self.clear_err(); self.clear_err();
self.path_names.clear(); self.path_names.clear();
for path in collected { self.path_names
self.path_names .extend(paths.map(|path| path.unwrap().path().display().to_string()));
.push(path.unwrap().path().display().to_string());
}
log::info!("path namees are {:#?}", self.path_names);
} }
fn go_up(&mut self) { fn go_up(&mut self) {
+15 -15
View File
@@ -2,8 +2,8 @@ use dioxus::prelude::*;
use rand::prelude::*; use rand::prelude::*;
fn main() { fn main() {
dioxus::web::launch(App); // dioxus::web::launch(App);
// dioxus::desktop::launch(App); dioxus::desktop::launch(App);
} }
#[derive(Clone, PartialEq)] #[derive(Clone, PartialEq)]
@@ -31,16 +31,16 @@ impl Label {
} }
static App: Component = |cx| { static App: Component = |cx| {
let mut items = use_ref(&cx, || vec![]); let items = use_ref(&cx, || vec![]);
let mut selected = use_state(&cx, || None); let selected = use_state(&cx, || None);
cx.render(rsx! { cx.render(rsx! {
div { class: "container" div { class: "container",
div { class: "jumbotron" div { class: "jumbotron",
div { class: "row" div { class: "row",
div { class: "col-md-6", h1 { "Dioxus" } } div { class: "col-md-6", h1 { "Dioxus" } }
div { class: "col-md-6" div { class: "col-md-6",
div { class: "row" div { class: "row",
ActionButton { name: "Create 1,000 rows", id: "run", ActionButton { name: "Create 1,000 rows", id: "run",
onclick: move || items.set(Label::new_list(1_000)), onclick: move || items.set(Label::new_list(1_000)),
} }
@@ -67,15 +67,15 @@ static App: Component = |cx| {
tbody { tbody {
{items.read().iter().enumerate().map(|(id, item)| { {items.read().iter().enumerate().map(|(id, item)| {
let is_in_danger = if (*selected).map(|s| s == id).unwrap_or(false) {"danger"} else {""}; let is_in_danger = if (*selected).map(|s| s == id).unwrap_or(false) {"danger"} else {""};
rsx!(tr { class: "{is_in_danger}" rsx!(tr { class: "{is_in_danger}",
td { class:"col-md-1" } td { class:"col-md-1" }
td { class:"col-md-1", "{item.key}" } td { class:"col-md-1", "{item.key}" }
td { class:"col-md-1", onclick: move |_| selected.set(Some(id)), td { class:"col-md-1", onclick: move |_| selected.set(Some(id)),
a { class: "lbl", {item.labels} } a { class: "lbl", item.labels }
} }
td { class: "col-md-1" td { class: "col-md-1",
a { class: "remove", onclick: move |_| { items.write().remove(id); }, a { class: "remove", onclick: move |_| { items.write().remove(id); },
span { class: "glyphicon glyphicon-remove remove" aria_hidden: "true" } span { class: "glyphicon glyphicon-remove remove", aria_hidden: "true" }
} }
} }
td { class: "col-md-6" } td { class: "col-md-6" }
@@ -83,7 +83,7 @@ static App: Component = |cx| {
})} })}
} }
} }
// span { class: "preloadicon glyphicon glyphicon-remove" aria_hidden: "true" } span { class: "preloadicon glyphicon glyphicon-remove", aria_hidden: "true" }
} }
}) })
}; };
@@ -96,7 +96,7 @@ struct ActionButtonProps<'a> {
} }
fn ActionButton<'a>(cx: Scope<'a, ActionButtonProps<'a>>) -> Element { fn ActionButton<'a>(cx: Scope<'a, ActionButtonProps<'a>>) -> Element {
rsx!(cx, div { class: "col-sm-6 smallpad" rsx!(cx, div { class: "col-sm-6 smallpad",
button { class:"btn btn-primary btn-block", r#type: "button", id: "{cx.props.id}", onclick: move |_| (cx.props.onclick)(), button { class:"btn btn-primary btn-block", r#type: "button", id: "{cx.props.id}", onclick: move |_| (cx.props.onclick)(),
"{cx.props.name}" "{cx.props.name}"
} }
+1 -1
View File
@@ -4,7 +4,7 @@ fn main() {
dioxus::desktop::launch(app); dioxus::desktop::launch(app);
} }
fn app(cx: Scope<()>) -> Element { fn app(cx: Scope) -> Element {
cx.render(rsx! ( cx.render(rsx! (
div { "Hello, world!" } div { "Hello, world!" }
)) ))
+4 -4
View File
@@ -16,18 +16,18 @@ fn main() {
let vdom = VirtualDom::new(App); let vdom = VirtualDom::new(App);
let content = ssr::render_vdom_cfg(&vdom, |f| f.pre_render(true)); let content = ssr::render_vdom_cfg(&vdom, |f| f.pre_render(true));
dioxus::desktop::launch_cfg(App, |c| c.with_prerendered(content)); // dioxus::desktop::launch_cfg(App, |c| c.with_prerendered(content));
} }
static App: Component = |cx| { static App: Component = |cx| {
let mut val = use_state(&cx, || 0); let val = use_state(&cx, || 0);
cx.render(rsx! { cx.render(rsx! {
div { div {
h1 {"hello world. Count: {val}"} h1 { "hello world. Count: {val}" }
button { button {
onclick: move |_| *val.modify() += 1,
"click to increment" "click to increment"
onclick: move |_| val += 1
} }
} }
}) })
+20 -20
View File
@@ -24,42 +24,42 @@ use dioxus::prelude::*;
const STYLE: &str = include_str!("./assets/calculator.css"); const STYLE: &str = include_str!("./assets/calculator.css");
fn main() { fn main() {
env_logger::init(); env_logger::init();
dioxus::desktop::launch_cfg(App, |cfg| { // dioxus::desktop::launch_cfg(App, |cfg| {
cfg.with_window(|w| { // cfg.with_window(|w| {
w.with_title("Calculator Demo") // w.with_title("Calculator Demo")
.with_resizable(false) // .with_resizable(false)
.with_inner_size(LogicalSize::new(320.0, 530.0)) // .with_inner_size(LogicalSize::new(320.0, 530.0))
}) // })
}); // });
} }
static App: Component = |cx| { fn app(cx: Scope) -> Element {
let state = use_ref(&cx, || Calculator::new()); let state = use_ref(&cx, || Calculator::new());
let clear_display = state.read().display_value.eq("0"); let clear_display = state.read().display_value.eq("0");
let clear_text = if clear_display { "C" } else { "AC" }; let clear_text = if clear_display { "C" } else { "AC" };
let formatted = state.read().formatted_display(); let formatted = state.read().formatted_display();
rsx!(cx, div { id: "wrapper" rsx!(cx, div { id: "wrapper",
div { class: "app", style { "{STYLE}" } div { class: "app", style { "{STYLE}" }
div { class: "calculator", onkeypress: move |evt| state.write().handle_keydown(evt), div { class: "calculator", onkeypress: move |evt| state.write().handle_keydown(evt),
div { class: "calculator-display", "{formatted}"} div { class: "calculator-display", "{formatted}"}
div { class: "calculator-keypad" div { class: "calculator-keypad",
div { class: "input-keys" div { class: "input-keys",
div { class: "function-keys" div { class: "function-keys",
CalculatorKey { name: "key-clear", onclick: move |_| state.write().clear_display(), "{clear_text}" } CalculatorKey { name: "key-clear", onclick: move |_| state.write().clear_display(), "{clear_text}" }
CalculatorKey { name: "key-sign", onclick: move |_| state.write().toggle_sign(), "±"} CalculatorKey { name: "key-sign", onclick: move |_| state.write().toggle_sign(), "±"}
CalculatorKey { name: "key-percent", onclick: move |_| state.write().toggle_percent(), "%"} CalculatorKey { name: "key-percent", onclick: move |_| state.write().toggle_percent(), "%"}
} }
div { class: "digit-keys" div { class: "digit-keys",
CalculatorKey { name: "key-0", onclick: move |_| state.write().input_digit(0), "0" } CalculatorKey { name: "key-0", onclick: move |_| state.write().input_digit(0), "0" }
CalculatorKey { name: "key-dot", onclick: move |_| state.write().input_dot(), "●" } CalculatorKey { name: "key-dot", onclick: move |_| state.write().input_dot(), "●" }
{(1..10).map(move |k| rsx!{ (1..10).map(move |k| rsx!{
CalculatorKey { key: "{k}", name: "key-{k}", onclick: move |_| state.write().input_digit(k), "{k}" } CalculatorKey { key: "{k}", name: "key-{k}", onclick: move |_| state.write().input_digit(k), "{k}" }
})} })
} }
} }
div { class: "operator-keys" div { class: "operator-keys",
CalculatorKey { name:"key-divide", onclick: move |_| state.write().set_operator(Operator::Div), "÷" } CalculatorKey { name:"key-divide", onclick: move |_| state.write().set_operator(Operator::Div), "÷" }
CalculatorKey { name:"key-multiply", onclick: move |_| state.write().set_operator(Operator::Mul), "×" } CalculatorKey { name:"key-multiply", onclick: move |_| state.write().set_operator(Operator::Mul), "×" }
CalculatorKey { name:"key-subtract", onclick: move |_| state.write().set_operator(Operator::Sub), "−" } CalculatorKey { name:"key-subtract", onclick: move |_| state.write().set_operator(Operator::Sub), "−" }
@@ -70,7 +70,7 @@ static App: Component = |cx| {
} }
} }
}) })
}; }
#[derive(Props)] #[derive(Props)]
struct CalculatorKeyProps<'a> { struct CalculatorKeyProps<'a> {
@@ -82,9 +82,9 @@ struct CalculatorKeyProps<'a> {
fn CalculatorKey<'a>(cx: Scope<'a, CalculatorKeyProps<'a>>) -> Element { fn CalculatorKey<'a>(cx: Scope<'a, CalculatorKeyProps<'a>>) -> Element {
cx.render(rsx! { cx.render(rsx! {
button { button {
class: "calculator-key {cx.props.name}" class: "calculator-key {cx.props.name}",
onclick: move |e| (cx.props.onclick)(e) onclick: move |e| (cx.props.onclick)(e),
{&cx.props.children} &cx.props.children
} }
}) })
} }
+11 -13
View File
@@ -14,20 +14,18 @@ fn main() {
pub static App: Component = |cx| { pub static App: Component = |cx| {
let state = use_state(&cx, PlayerState::new); let state = use_state(&cx, PlayerState::new);
let is_playing = state.is_playing(); cx.render(rsx!(
div {
rsx!(cx, div { h1 {"Select an option"}
h1 {"Select an option"} h3 { "The radio is... " [state.is_playing()], "!" }
h3 {"The radio is... {is_playing}!"} button { onclick: move |_| state.modify().reduce(PlayerAction::Pause),
button { "Pause"
"Pause" }
onclick: move |_| state.modify().reduce(PlayerAction::Pause) button { onclick: move |_| state.modify().reduce(PlayerAction::Play),
"Play"
}
} }
button { ))
"Play"
onclick: move |_| state.modify().reduce(PlayerAction::Play)
}
})
}; };
enum PlayerAction { enum PlayerAction {
+1
View File
@@ -3,6 +3,7 @@
//! The example from the README.md. //! The example from the README.md.
use dioxus::prelude::*; use dioxus::prelude::*;
fn main() { fn main() {
dioxus::desktop::launch(App); dioxus::desktop::launch(App);
} }
+61
View File
@@ -0,0 +1,61 @@
use dioxus::prelude::*;
fn main() {
let mut vdom = VirtualDom::new(example);
vdom.rebuild();
let out = dioxus::ssr::render_vdom_cfg(&vdom, |c| c.newline(true).indent(true));
println!("{}", out);
}
fn example(cx: Scope) -> Element {
let items = use_state(&cx, || {
vec![Thing {
a: "asd".to_string(),
b: 10,
}]
});
let things = use_ref(&cx, || {
vec![Thing {
a: "asd".to_string(),
b: 10,
}]
});
let things_list = things.read();
let mything = use_ref(&cx, || Some(String::from("asd")));
let mything_read = mything.read();
cx.render(rsx!(
div {
div {
id: "asd",
"your neighborhood spiderman"
items.iter().cycle().take(5).map(|f| rsx!{
div {
"{f.a}"
}
})
things_list.iter().map(|f| rsx!{
div {
"{f.a}"
}
})
mything_read.as_ref().map(|f| rsx!{
div {
"{f}"
}
})
}
}
))
}
struct Thing {
a: String,
b: u32,
}
+54 -29
View File
@@ -60,6 +60,7 @@ pub static EXAMPLE: Component = |cx| {
h1 {"Some text"} h1 {"Some text"}
h1 {"Some text with {formatting}"} h1 {"Some text with {formatting}"}
h1 {"Formatting basic expressions {formatting_tuple.0} and {formatting_tuple.1}"} h1 {"Formatting basic expressions {formatting_tuple.0} and {formatting_tuple.1}"}
h1 {"Formatting without interpolation " [formatting_tuple.0] "and" [formatting_tuple.1] }
h2 { h2 {
"Multiple" "Multiple"
"Text" "Text"
@@ -72,7 +73,7 @@ pub static EXAMPLE: Component = |cx| {
h3 {"elements"} h3 {"elements"}
} }
div { div {
class: "my special div" class: "my special div",
h1 {"Headers and attributes!"} h1 {"Headers and attributes!"}
} }
div { div {
@@ -88,42 +89,51 @@ pub static EXAMPLE: Component = |cx| {
} }
// Expressions can be used in element position too: // Expressions can be used in element position too:
{rsx!(p { "More templating!" })} rsx!(p { "More templating!" }),
// {html!(<p>"Even HTML templating!!"</p>)}
// Iterators // Iterators
{(0..10).map(|i| rsx!(li { "{i}" }))} (0..10).map(|i| rsx!(li { "{i}" })),
{{
// Iterators within expressions
{
let data = std::collections::HashMap::<&'static str, &'static str>::new(); let data = std::collections::HashMap::<&'static str, &'static str>::new();
// Iterators *should* have keys when you can provide them. // Iterators *should* have keys when you can provide them.
// Keys make your app run faster. Make sure your keys are stable, unique, and predictable. // Keys make your app run faster. Make sure your keys are stable, unique, and predictable.
// Using an "ID" associated with your data is a good idea. // Using an "ID" associated with your data is a good idea.
data.into_iter().map(|(k, v)| rsx!(li { key: "{k}" "{v}" })) data.into_iter().map(|(k, v)| rsx!(li { key: "{k}", "{v}" }))
}} }
// Matching // Matching
{match true { match true {
true => rsx!( h1 {"Top text"}), true => rsx!( h1 {"Top text"}),
false => rsx!( h1 {"Bottom text"}) false => rsx!( h1 {"Bottom text"})
}} }
// Conditional rendering // Conditional rendering
// Dioxus conditional rendering is based around None/Some. We have no special syntax for conditionals. // Dioxus conditional rendering is based around None/Some. We have no special syntax for conditionals.
// You can convert a bool condition to rsx! with .then and .or // You can convert a bool condition to rsx! with .then and .or
{true.then(|| rsx!(div {}))} true.then(|| rsx!(div {})),
// True conditions need to be rendered (same reasons as matching) // Alternatively, you can use the "if" syntax - but both branches must be resolve to Element
{if true { if false {
rsx!(cx, h1 {"Top text"}) rsx!(h1 {"Top text"})
} else { } else {
rsx!(cx, h1 {"Bottom text"}) rsx!(h1 {"Bottom text"})
}} }
// returning "None" is a bit noisy... but rare in practice // Using optionals for diverging branches
{None as Option<()>} if true {
Some(rsx!(h1 {"Top text"}))
} else {
None
}
// returning "None" without a diverging branch is a bit noisy... but rare in practice
None as Option<()>,
// Use the Dioxus type-alias for less noise // Use the Dioxus type-alias for less noise
{NONE_ELEMENT} NONE_ELEMENT,
// can also just use empty fragments // can also just use empty fragments
Fragment {} Fragment {}
@@ -137,9 +147,8 @@ pub static EXAMPLE: Component = |cx| {
Fragment { Fragment {
"D" "D"
Fragment { Fragment {
"heavily nested fragments is an antipattern" "E"
"they cause Dioxus to do unnecessary work" "F"
"don't use them carelessly if you can help it"
} }
} }
} }
@@ -158,22 +167,29 @@ pub static EXAMPLE: Component = |cx| {
Taller { a: "asd" } Taller { a: "asd" }
// Can pass in props directly as an expression // Can pass in props directly as an expression
{{ {
let props = TallerProps {a: "hello", children: Default::default()}; let props = TallerProps {a: "hello", children: Default::default()};
rsx!(Taller { ..props }) rsx!(Taller { ..props })
}} }
// Spreading can also be overridden manually // Spreading can also be overridden manually
Taller { Taller {
..TallerProps { a: "ballin!", children: Default::default() } ..TallerProps { a: "ballin!", children: Default::default() },
a: "not ballin!" a: "not ballin!"
} }
// Can take children too! // Can take children too!
Taller { a: "asd", div {"hello world!"} } Taller { a: "asd", div {"hello world!"} }
// Components can be used with the `call` syntax
// This component's props are defined *inline* with the `inline_props` macro
with_inline(
text: "using functionc all syntax"
)
// helper functions // helper functions
{helper(&cx, "hello world!")} // Single values must be wrapped in braces or `Some` to satisfy `IntoIterator`
[helper(&cx, "hello world!")]
} }
}) })
}; };
@@ -187,6 +203,7 @@ mod baller {
#[derive(Props, PartialEq)] #[derive(Props, PartialEq)]
pub struct BallerProps {} pub struct BallerProps {}
#[allow(non_snake_case)]
/// This component totally balls /// This component totally balls
pub fn Baller(_: Scope<BallerProps>) -> Element { pub fn Baller(_: Scope<BallerProps>) -> Element {
todo!() todo!()
@@ -195,12 +212,20 @@ mod baller {
#[derive(Props)] #[derive(Props)]
pub struct TallerProps<'a> { pub struct TallerProps<'a> {
/// Fields are documented and accessible in rsx!
a: &'static str, a: &'static str,
children: Element<'a>, children: Element<'a>,
} }
/// This component is taller than most :) /// Documention for this component is visible within the rsx macro
pub fn Taller<'a>(_: Scope<'a, TallerProps<'a>>) -> Element { #[allow(non_snake_case)]
let b = true; pub fn Taller<'a>(cx: Scope<'a, TallerProps<'a>>) -> Element {
todo!() cx.render(rsx! {
&cx.props.children
})
}
#[inline_props]
fn with_inline<'a>(cx: Scope<'a>, text: &'a str) -> Element {
rsx!(cx, p { "{text}" })
} }
+38 -38
View File
@@ -16,9 +16,9 @@ const STYLE: &str = "body {overflow:hidden;}";
pub static App: Component = |cx| { pub static App: Component = |cx| {
cx.render(rsx!( cx.render(rsx!(
div { class: "overflow-hidden" div { class: "overflow-hidden",
style { "{STYLE}" } style { "{STYLE}" }
link { href:"https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel:"stylesheet" } link { href:"https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css", rel:"stylesheet" }
Header {} Header {}
Entry {} Entry {}
Hero {} Hero {}
@@ -33,20 +33,20 @@ pub static App: Component = |cx| {
pub static Header: Component = |cx| { pub static Header: Component = |cx| {
cx.render(rsx! { cx.render(rsx! {
div { div {
header { class: "text-gray-400 bg-gray-900 body-font" header { class: "text-gray-400 bg-gray-900 body-font",
div { class: "container mx-auto flex flex-wrap p-5 flex-col md:flex-row items-center" div { class: "container mx-auto flex flex-wrap p-5 flex-col md:flex-row items-center",
a { class: "flex title-font font-medium items-center text-white mb-4 md:mb-0" a { class: "flex title-font font-medium items-center text-white mb-4 md:mb-0",
StacksIcon {} StacksIcon {}
span { class: "ml-3 text-xl" "Hello Dioxus!"} span { class: "ml-3 text-xl", "Hello Dioxus!"}
} }
nav { class: "md:ml-auto flex flex-wrap items-center text-base justify-center" nav { class: "md:ml-auto flex flex-wrap items-center text-base justify-center",
a { class: "mr-5 hover:text-white" "First Link"} a { class: "mr-5 hover:text-white", "First Link"}
a { class: "mr-5 hover:text-white" "Second Link"} a { class: "mr-5 hover:text-white", "Second Link"}
a { class: "mr-5 hover:text-white" "Third Link"} a { class: "mr-5 hover:text-white", "Third Link"}
a { class: "mr-5 hover:text-white" "Fourth Link"} a { class: "mr-5 hover:text-white", "Fourth Link"}
} }
button { button {
class: "inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0" class: "inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0",
"Button" "Button"
RightArrowIcon {} RightArrowIcon {}
} }
@@ -59,34 +59,34 @@ pub static Header: Component = |cx| {
pub static Hero: Component = |cx| { pub static Hero: Component = |cx| {
// //
cx.render(rsx! { cx.render(rsx! {
section{ class: "text-gray-400 bg-gray-900 body-font" section{ class: "text-gray-400 bg-gray-900 body-font",
div { class: "container mx-auto flex px-5 py-24 md:flex-row flex-col items-center" div { class: "container mx-auto flex px-5 py-24 md:flex-row flex-col items-center",
div { class: "lg:flex-grow md:w-1/2 lg:pr-24 md:pr-16 flex flex-col md:items-start md:text-left mb-16 md:mb-0 items-center text-center" div { class: "lg:flex-grow md:w-1/2 lg:pr-24 md:pr-16 flex flex-col md:items-start md:text-left mb-16 md:mb-0 items-center text-center",
h1 { class: "title-font sm:text-4xl text-3xl mb-4 font-medium text-white" h1 { class: "title-font sm:text-4xl text-3xl mb-4 font-medium text-white",
br { class: "hidden lg:inline-block" } br { class: "hidden lg:inline-block" }
"Dioxus Sneak Peek" "Dioxus Sneak Peek"
} }
p { p {
class: "mb-8 leading-relaxed" class: "mb-8 leading-relaxed",
"Dioxus is a new UI framework that makes it easy and simple to write cross-platform apps using web "Dioxus is a new UI framework that makes it easy and simple to write cross-platform apps using web
technologies! It is functional, fast, and portable. Dioxus can run on the web, on the desktop, and technologies! It is functional, fast, and portable. Dioxus can run on the web, on the desktop, and
on mobile and embedded platforms." on mobile and embedded platforms."
} }
div { class: "flex justify-center" div { class: "flex justify-center",
button { button {
class: "inline-flex text-white bg-indigo-500 border-0 py-2 px-6 focus:outline-none hover:bg-indigo-600 rounded text-lg" class: "inline-flex text-white bg-indigo-500 border-0 py-2 px-6 focus:outline-none hover:bg-indigo-600 rounded text-lg",
"Learn more" "Learn more"
} }
button { button {
class: "ml-4 inline-flex text-gray-400 bg-gray-800 border-0 py-2 px-6 focus:outline-none hover:bg-gray-700 hover:text-white rounded text-lg" class: "ml-4 inline-flex text-gray-400 bg-gray-800 border-0 py-2 px-6 focus:outline-none hover:bg-gray-700 hover:text-white rounded text-lg",
"Build an app" "Build an app"
} }
} }
} }
div { class: "lg:max-w-lg lg:w-full md:w-1/2 w-5/6" div { class: "lg:max-w-lg lg:w-full md:w-1/2 w-5/6",
img { class: "object-cover object-center rounded" alt: "hero" src: "https://i.imgur.com/oK6BLtw.png" img { class: "object-cover object-center rounded", alt: "hero", src: "https://i.imgur.com/oK6BLtw.png",
referrerpolicy:"no-referrer" referrerpolicy:"no-referrer"
} }
} }
@@ -97,8 +97,8 @@ pub static Hero: Component = |cx| {
pub static Entry: Component = |cx| { pub static Entry: Component = |cx| {
// //
cx.render(rsx! { cx.render(rsx! {
section{ class: "text-gray-400 bg-gray-900 body-font" section{ class: "text-gray-400 bg-gray-900 body-font",
div { class: "container mx-auto flex px-5 py-24 md:flex-row flex-col items-center" div { class: "container mx-auto flex px-5 py-24 md:flex-row flex-col items-center",
textarea { textarea {
} }
@@ -111,13 +111,13 @@ pub static StacksIcon: Component = |cx| {
cx.render(rsx!( cx.render(rsx!(
svg { svg {
// xmlns: "http://www.w3.org/2000/svg" // xmlns: "http://www.w3.org/2000/svg"
fill: "none" fill: "none",
stroke: "currentColor" stroke: "currentColor",
stroke_linecap: "round" stroke_linecap: "round",
stroke_linejoin: "round" stroke_linejoin: "round",
stroke_width: "2" stroke_width: "2",
class: "w-10 h-10 text-white p-2 bg-indigo-500 rounded-full" class: "w-10 h-10 text-white p-2 bg-indigo-500 rounded-full",
view_box: "0 0 24 24" view_box: "0 0 24 24",
path { d: "M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"} path { d: "M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"}
} }
)) ))
@@ -125,13 +125,13 @@ pub static StacksIcon: Component = |cx| {
pub static RightArrowIcon: Component = |cx| { pub static RightArrowIcon: Component = |cx| {
cx.render(rsx!( cx.render(rsx!(
svg { svg {
fill: "none" fill: "none",
stroke: "currentColor" stroke: "currentColor",
stroke_linecap: "round" stroke_linecap: "round",
stroke_linejoin: "round" stroke_linejoin: "round",
stroke_width: "2" stroke_width: "2",
class: "w-4 h-4 ml-1" class: "w-4 h-4 ml-1",
view_box: "0 0 24 24" view_box: "0 0 24 24",
path { d: "M5 12h14M12 5l7 7-7 7"} path { d: "M5 12h14M12 5l7 7-7 7"}
} }
)) ))
+12 -7
View File
@@ -2,26 +2,31 @@
//! //!
//! The example from the README.md. //! The example from the README.md.
use dioxus::prelude::*;
use std::time::Duration; use std::time::Duration;
use dioxus::prelude::*;
fn main() { fn main() {
dioxus::desktop::launch(app); dioxus::desktop::launch(app);
} }
fn app(cx: Scope<()>) -> Element { fn app(cx: Scope) -> Element {
let mut count = use_state(&cx, || 0); let count = use_state(&cx, || 0);
cx.push_future(|| async move { use_future(&cx, || {
tokio::time::sleep(Duration::from_millis(100)).await; for_async![count];
count += 1; async move {
loop {
tokio::time::sleep(Duration::from_millis(1000)).await;
count += 1;
}
}
}); });
cx.render(rsx! { cx.render(rsx! {
div { div {
h1 { "High-Five counter: {count}" } h1 { "High-Five counter: {count}" }
button { button {
onclick: move |_| count +=1 , onclick: move |_| *count.modify() += 1,
"Click me!" "Click me!"
} }
} }
+21 -18
View File
@@ -29,7 +29,7 @@ const App: Component = |cx| {
let todolist = todos let todolist = todos
.iter() .iter()
.filter(|(id, item)| match *filter { .filter(|(_id, item)| match *filter {
FilterState::All => true, FilterState::All => true,
FilterState::Active => !item.checked, FilterState::Active => !item.checked,
FilterState::Completed => item.checked, FilterState::Completed => item.checked,
@@ -48,34 +48,37 @@ const App: Component = |cx| {
_ => "items", _ => "items",
}; };
rsx!(cx, div { id: "app" rsx!(cx, div { id: "app",
style {"{STYLE}"} style {"{STYLE}"}
div { div {
header { class: "header" header { class: "header",
h1 {"todos"} h1 {"todos"}
input { input {
class: "new-todo" class: "new-todo",
placeholder: "What needs to be done?" placeholder: "What needs to be done?",
value: "{draft}" value: "{draft}",
oninput: move |evt| draft.set(evt.value.clone()) oninput: move |evt| draft.set(evt.value.clone()),
} }
} }
{todolist} todolist,
{(!todos.is_empty()).then(|| rsx!( (!todos.is_empty()).then(|| rsx!(
footer { footer {
span { strong {"{items_left}"} span {"{item_text} left"} } span {
ul { class: "filters" strong {"{items_left}"}
span {"{item_text} left"}
}
ul { class: "filters",
li { class: "All", a { href: "", onclick: move |_| filter.set(FilterState::All), "All" }} li { class: "All", a { href: "", onclick: move |_| filter.set(FilterState::All), "All" }}
li { class: "Active", a { href: "active", onclick: move |_| filter.set(FilterState::Active), "Active" }} li { class: "Active", a { href: "active", onclick: move |_| filter.set(FilterState::Active), "Active" }}
li { class: "Completed", a { href: "completed", onclick: move |_| filter.set(FilterState::Completed), "Completed" }} li { class: "Completed", a { href: "completed", onclick: move |_| filter.set(FilterState::Completed), "Completed" }}
} }
} }
))} ))
} }
footer { class: "info" footer { class: "info",
p {"Double-click to edit a todo"} p {"Double-click to edit a todo"}
p { "Created by ", a { "jkelleyrtp", href: "http://github.com/jkelleyrtp/" }} p { "Created by ", a { href: "http://github.com/jkelleyrtp/", "jkelleyrtp" }}
p { "Part of ", a { "TodoMVC", href: "http://todomvc.com" }} p { "Part of ", a { href: "http://todomvc.com", "TodoMVC" }}
} }
}) })
}; };
@@ -93,13 +96,13 @@ pub fn TodoEntry(cx: Scope<TodoEntryProps>) -> Element {
rsx!(cx, li { rsx!(cx, li {
"{todo.id}" "{todo.id}"
input { input {
class: "toggle" class: "toggle",
r#type: "checkbox" r#type: "checkbox",
"{todo.checked}" "{todo.checked}"
} }
{is_editing.then(|| rsx!{ {is_editing.then(|| rsx!{
input { input {
value: "{contents}" value: "{contents}",
oninput: move |evt| contents.set(evt.value.clone()) oninput: move |evt| contents.set(evt.value.clone())
} }
})} })}
+9 -18
View File
@@ -13,28 +13,19 @@
use dioxus::prelude::*; use dioxus::prelude::*;
fn main() { fn main() {
#[cfg(target_arch = "wasm32")] dioxus::desktop::launch(App);
intern_strings();
dioxus::web::launch(App);
} }
static App: Component = |cx| { static App: Component = |cx| {
let mut rng = SmallRng::from_entropy(); let mut rng = SmallRng::from_entropy();
let rows = (0..1_000).map(|f| {
let label = Label::new(&mut rng);
rsx! {
Row {
row_id: f,
label: label
}
}
});
cx.render(rsx! { cx.render(rsx! {
table { table {
tbody { tbody {
{rows} (0..1_000).map(|f| {
let label = Label::new(&mut rng);
rsx! (Row { row_id: f, label: label })
})
} }
} }
}) })
@@ -50,12 +41,12 @@ fn Row(cx: Scope<RowProps>) -> Element {
cx.render(rsx! { cx.render(rsx! {
tr { tr {
td { class:"col-md-1", "{cx.props.row_id}" } td { class:"col-md-1", "{cx.props.row_id}" }
td { class:"col-md-1", onclick: move |_| { /* run onselect */ } td { class:"col-md-1", onclick: move |_| { /* run onselect */ },
a { class: "lbl", "{adj}" "{col}" "{noun}" } a { class: "lbl", "{adj}" "{col}" "{noun}" }
} }
td { class: "col-md-1" td { class: "col-md-1",
a { class: "remove", onclick: move |_| {/* remove */} a { class: "remove", onclick: move |_| {/* remove */},
span { class: "glyphicon glyphicon-remove remove" aria_hidden: "true" } span { class: "glyphicon glyphicon-remove remove", aria_hidden: "true" }
} }
} }
td { class: "col-md-6" } td { class: "col-md-6" }
+3 -4
View File
@@ -1,4 +1,3 @@
#![allow(non_upper_case_globals, non_snake_case)]
//! Example: Webview Renderer //! Example: Webview Renderer
//! ------------------------- //! -------------------------
//! //!
@@ -13,10 +12,10 @@
use dioxus::prelude::*; use dioxus::prelude::*;
fn main() { fn main() {
dioxus::web::launch(App); dioxus::desktop::launch(app);
} }
static App: Component = |cx| { fn app(cx: Scope) -> Element {
let mut count = use_state(&cx, || 0); let mut count = use_state(&cx, || 0);
cx.render(rsx! { cx.render(rsx! {
@@ -26,4 +25,4 @@ static App: Component = |cx| {
button { onclick: move |_| count -= 1, "Down low!" } button { onclick: move |_| count -= 1, "Down low!" }
} }
}) })
}; }
+31
View File
@@ -0,0 +1,31 @@
use dioxus::prelude::*;
fn main() {
dioxus::desktop::launch(app);
}
fn app(cx: Scope) -> Element {
let contents = use_state(&cx, || String::from("<script>alert(123)</script>"));
cx.render(rsx! {
div {
"hello world!"
h1 {
"{contents}"
}
h3 {
[contents.as_str()]
}
input {
value: "{contents}",
oninput: move |e| {
contents.set(e.value.clone());
eprintln!("asd");
},
"type": "text",
}
}
})
}
+2 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "dioxus-core-macro" name = "dioxus-core-macro"
version = "0.1.2" version = "0.1.3"
authors = ["Jonathan Kelley"] authors = ["Jonathan Kelley"]
edition = "2021" edition = "2021"
description = "Core macro for Dioxus Virtual DOM" description = "Core macro for Dioxus Virtual DOM"
@@ -16,6 +16,7 @@ proc-macro = true
[dependencies] [dependencies]
once_cell = "1.8" once_cell = "1.8"
proc-macro-error = "1.0.4"
proc-macro2 = { version = "1.0.6" } proc-macro2 = { version = "1.0.6" }
quote = "1.0" quote = "1.0"
syn = { version = "1.0.11", features = ["full", "extra-traits"] } syn = { version = "1.0.11", features = ["full", "extra-traits"] }
+1
View File
@@ -99,6 +99,7 @@ impl ToTokens for InlinePropsBody {
}; };
out_tokens.append_all(quote! { out_tokens.append_all(quote! {
#[allow(non_camel_case)]
#modifiers #modifiers
#vis struct #struct_name #generics { #vis struct #struct_name #generics {
#(#fields),* #(#fields),*
+4 -3
View File
@@ -161,7 +161,7 @@ pub fn derive_typed_builder(input: proc_macro::TokenStream) -> proc_macro::Token
/// pub struct BallerProps {} /// pub struct BallerProps {}
/// ///
/// /// This component totally balls /// /// This component totally balls
/// pub fn Baller(cx: Scope<()>) -> DomTree { /// pub fn Baller(cx: Scope) -> DomTree {
/// todo!() /// todo!()
/// } /// }
/// } /// }
@@ -177,11 +177,12 @@ pub fn derive_typed_builder(input: proc_macro::TokenStream) -> proc_macro::Token
/// todo!() /// todo!()
/// } /// }
/// ``` /// ```
#[proc_macro_error::proc_macro_error]
#[proc_macro] #[proc_macro]
pub fn rsx(s: TokenStream) -> TokenStream { pub fn rsx(s: TokenStream) -> TokenStream {
match syn::parse::<rsx::CallBody>(s) { match syn::parse::<rsx::CallBody>(s) {
Err(e) => e.to_compile_error().into(), Err(err) => err.to_compile_error().into(),
Ok(s) => s.to_token_stream().into(), Ok(stream) => stream.to_token_stream().into(),
} }
} }
-64
View File
@@ -1,64 +0,0 @@
//! Parse anything that has a pattern of < Ident, Bracket >
//! ========================================================
//!
//! Whenever a `name {}` pattern emerges, we need to parse it into an element, a component, or a fragment.
//! This feature must support:
//! - Namepsaced/pathed components
//! - Differentiating between built-in and custom elements
use super::*;
use proc_macro2::TokenStream as TokenStream2;
use quote::ToTokens;
use syn::{
parse::{Parse, ParseStream},
Error, Ident, Result, Token,
};
#[allow(clippy::large_enum_variant)]
pub enum AmbiguousElement {
Element(Element),
Component(Component),
}
impl Parse for AmbiguousElement {
fn parse(input: ParseStream) -> Result<Self> {
// Try to parse as an absolute path and immediately defer to the componetn
if input.peek(Token![::]) {
return input.parse::<Component>().map(AmbiguousElement::Component);
}
// If not an absolute path, then parse the ident and check if it's a valid tag
if let Ok(pat) = input.fork().parse::<syn::Path>() {
if pat.segments.len() > 1 {
return input.parse::<Component>().map(AmbiguousElement::Component);
}
}
use syn::ext::IdentExt;
if let Ok(name) = input.fork().call(Ident::parse_any) {
let name_str = name.to_string();
let first_char = name_str.chars().next().unwrap();
if first_char.is_ascii_uppercase() {
input.parse::<Component>().map(AmbiguousElement::Component)
} else {
if input.peek2(syn::token::Paren) {
input.parse::<Component>().map(AmbiguousElement::Component)
} else {
input.parse::<Element>().map(AmbiguousElement::Element)
}
}
} else {
Err(Error::new(input.span(), "Not a valid Html tag"))
}
}
}
impl ToTokens for AmbiguousElement {
fn to_tokens(&self, tokens: &mut TokenStream2) {
match self {
AmbiguousElement::Element(el) => el.to_tokens(tokens),
AmbiguousElement::Component(comp) => comp.to_tokens(tokens),
}
}
}
-66
View File
@@ -1,66 +0,0 @@
use proc_macro2::TokenStream as TokenStream2;
use quote::{quote, ToTokens, TokenStreamExt};
use syn::{
parse::{Parse, ParseStream},
Ident, Result, Token,
};
use super::*;
pub struct CallBody {
custom_context: Option<Ident>,
roots: Vec<BodyNode>,
}
/// The custom rusty variant of parsing rsx!
impl Parse for CallBody {
fn parse(input: ParseStream) -> Result<Self> {
let custom_context = try_parse_custom_context(input)?;
let (_, roots, _) = BodyConfig::new_call_body().parse_component_body(input)?;
Ok(Self {
custom_context,
roots,
})
}
}
fn try_parse_custom_context(input: ParseStream) -> Result<Option<Ident>> {
let res = if input.peek(Ident) && input.peek2(Token![,]) {
let name = input.parse::<Ident>()?;
input.parse::<Token![,]>()?;
Some(name)
} else {
None
};
Ok(res)
}
/// Serialize the same way, regardless of flavor
impl ToTokens for CallBody {
fn to_tokens(&self, out_tokens: &mut TokenStream2) {
let inner = if self.roots.len() == 1 {
let inner = &self.roots[0];
quote! {#inner}
} else {
let childs = &self.roots;
quote! { __cx.fragment_root([ #(#childs),* ]) }
};
match &self.custom_context {
// The `in cx` pattern allows directly rendering
Some(ident) => out_tokens.append_all(quote! {
#ident.render(LazyNodes::new_some(move |__cx: NodeFactory| -> VNode {
use dioxus_elements::{GlobalAttributes, SvgAttributes};
#inner
}))
}),
// Otherwise we just build the LazyNode wrapper
None => out_tokens.append_all(quote! {
LazyNodes::new_some(move |__cx: NodeFactory| -> VNode {
use dioxus_elements::{GlobalAttributes, SvgAttributes};
#inner
})
}),
};
}
}
+20 -95
View File
@@ -19,11 +19,10 @@ use quote::{quote, ToTokens, TokenStreamExt};
use syn::{ use syn::{
ext::IdentExt, ext::IdentExt,
parse::{Parse, ParseBuffer, ParseStream}, parse::{Parse, ParseBuffer, ParseStream},
token, Error, Expr, ExprClosure, Ident, Result, Token, token, Expr, ExprClosure, Ident, Result, Token,
}; };
pub struct Component { pub struct Component {
// accept any path-like argument
name: syn::Path, name: syn::Path,
body: Vec<ComponentField>, body: Vec<ComponentField>,
children: Vec<BodyNode>, children: Vec<BodyNode>,
@@ -32,12 +31,8 @@ pub struct Component {
impl Parse for Component { impl Parse for Component {
fn parse(stream: ParseStream) -> Result<Self> { fn parse(stream: ParseStream) -> Result<Self> {
// let name = s.parse::<syn::ExprPath>()?;
// todo: look into somehow getting the crate/super/etc
let name = syn::Path::parse_mod_style(stream)?; let name = syn::Path::parse_mod_style(stream)?;
// parse the guts
let content: ParseBuffer; let content: ParseBuffer;
// if we see a `{` then we have a block // if we see a `{` then we have a block
@@ -48,13 +43,25 @@ impl Parse for Component {
syn::parenthesized!(content in stream); syn::parenthesized!(content in stream);
} }
let cfg: BodyConfig = BodyConfig { let mut body = Vec::new();
allow_children: true, let mut children = Vec::new();
allow_fields: true, let mut manual_props = None;
allow_manual_props: true,
};
let (body, children, manual_props) = cfg.parse_component_body(&content)?; while !content.is_empty() {
// if we splat into a component then we're merging properties
if content.peek(Token![..]) {
content.parse::<Token![..]>()?;
manual_props = Some(content.parse::<Expr>()?);
} else if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
body.push(content.parse::<ComponentField>()?);
} else {
children.push(content.parse::<BodyNode>()?);
}
if content.peek(Token![,]) {
let _ = content.parse::<Token![,]>();
}
}
Ok(Self { Ok(Self {
name, name,
@@ -65,77 +72,6 @@ impl Parse for Component {
} }
} }
pub struct BodyConfig {
pub allow_fields: bool,
pub allow_children: bool,
pub allow_manual_props: bool,
}
impl BodyConfig {
/// The configuration to parse the root
pub fn new_call_body() -> Self {
Self {
allow_children: true,
allow_fields: false,
allow_manual_props: false,
}
}
}
impl BodyConfig {
// todo: unify this body parsing for both elements and components
// both are style rather ad-hoc, though components are currently more configured
pub fn parse_component_body(
&self,
content: &ParseBuffer,
) -> Result<(Vec<ComponentField>, Vec<BodyNode>, Option<Expr>)> {
let mut body = Vec::new();
let mut children = Vec::new();
let mut manual_props = None;
'parsing: loop {
// [1] Break if empty
if content.is_empty() {
break 'parsing;
}
if content.peek(Token![..]) {
if !self.allow_manual_props {
return Err(Error::new(
content.span(),
"Props spread syntax is not allowed in this context. \nMake to only use the elipsis `..` in Components.",
));
}
content.parse::<Token![..]>()?;
manual_props = Some(content.parse::<Expr>()?);
} else if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
if !self.allow_fields {
return Err(Error::new(
content.span(),
"Property fields is not allowed in this context. \nMake to only use fields in Components or Elements.",
));
}
body.push(content.parse::<ComponentField>()?);
} else {
if !self.allow_children {
return Err(Error::new(
content.span(),
"This item is not allowed to accept children.",
));
}
children.push(content.parse::<BodyNode>()?);
}
// consume comma if it exists
// we don't actually care if there *are* commas between attrs
if content.peek(Token![,]) {
let _ = content.parse::<Token![,]>();
}
}
Ok((body, children, manual_props))
}
}
impl ToTokens for Component { impl ToTokens for Component {
fn to_tokens(&self, tokens: &mut TokenStream2) { fn to_tokens(&self, tokens: &mut TokenStream2) {
let name = &self.name; let name = &self.name;
@@ -219,9 +155,7 @@ pub struct ComponentField {
enum ContentField { enum ContentField {
ManExpr(Expr), ManExpr(Expr),
OnHandler(ExprClosure),
// A handler was provided in {} tokens
OnHandlerRaw(Expr), OnHandlerRaw(Expr),
} }
@@ -229,9 +163,6 @@ impl ToTokens for ContentField {
fn to_tokens(&self, tokens: &mut TokenStream2) { fn to_tokens(&self, tokens: &mut TokenStream2) {
match self { match self {
ContentField::ManExpr(e) => e.to_tokens(tokens), ContentField::ManExpr(e) => e.to_tokens(tokens),
ContentField::OnHandler(e) => tokens.append_all(quote! {
__cx.bump().alloc(#e)
}),
ContentField::OnHandlerRaw(e) => tokens.append_all(quote! { ContentField::OnHandlerRaw(e) => tokens.append_all(quote! {
__cx.bump().alloc(#e) __cx.bump().alloc(#e)
}), }),
@@ -246,13 +177,7 @@ impl Parse for ComponentField {
let name_str = name.to_string(); let name_str = name.to_string();
let content = if name_str.starts_with("on") { let content = if name_str.starts_with("on") {
if input.peek(token::Brace) { ContentField::OnHandlerRaw(input.parse()?)
let content;
syn::braced!(content in input);
ContentField::OnHandlerRaw(content.parse()?)
} else {
ContentField::OnHandler(input.parse()?)
}
} else { } else {
ContentField::ManExpr(input.parse::<Expr>()?) ContentField::ManExpr(input.parse::<Expr>()?)
}; };
+116 -63
View File
@@ -4,7 +4,7 @@ use proc_macro2::TokenStream as TokenStream2;
use quote::{quote, ToTokens, TokenStreamExt}; use quote::{quote, ToTokens, TokenStreamExt};
use syn::{ use syn::{
parse::{Parse, ParseBuffer, ParseStream}, parse::{Parse, ParseBuffer, ParseStream},
token, Expr, ExprClosure, Ident, LitStr, Result, Token, Expr, Ident, LitStr, Result, Token,
}; };
// ======================================= // =======================================
@@ -33,45 +33,69 @@ impl Parse for Element {
let mut key = None; let mut key = None;
let mut _el_ref = None; let mut _el_ref = None;
// todo: more descriptive error handling // parse fields with commas
while !content.is_empty() { // break when we don't get this pattern anymore
// start parsing bodynodes
// "def": 456,
// abc: 123,
loop {
// Parse the raw literal fields
if content.peek(LitStr) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
let name = content.parse::<LitStr>()?;
let ident = name.clone();
content.parse::<Token![:]>()?;
if content.peek(LitStr) {
let value = content.parse::<LitStr>()?;
attributes.push(ElementAttrNamed {
el_name: el_name.clone(),
attr: ElementAttr::CustomAttrText { name, value },
});
} else {
let value = content.parse::<Expr>()?;
attributes.push(ElementAttrNamed {
el_name: el_name.clone(),
attr: ElementAttr::CustomAttrExpression { name, value },
});
}
if content.is_empty() {
break;
}
// todo: add a message saying you need to include commas between fields
if content.parse::<Token![,]>().is_err() {
proc_macro_error::emit_error!(
ident,
"This attribute is misisng a trailing comma"
)
}
continue;
}
if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) { if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
let name = content.parse::<Ident>()?; let name = content.parse::<Ident>()?;
let ident = name.clone();
let name_str = name.to_string(); let name_str = name.to_string();
content.parse::<Token![:]>()?; content.parse::<Token![:]>()?;
if name_str.starts_with("on") { if name_str.starts_with("on") {
if content.peek(token::Brace) { listeners.push(ElementAttrNamed {
let mycontent; el_name: el_name.clone(),
syn::braced!(mycontent in content); attr: ElementAttr::EventTokens {
name,
listeners.push(ElementAttrNamed { tokens: content.parse()?,
el_name: el_name.clone(), },
attr: ElementAttr::EventTokens { });
name,
tokens: mycontent.parse()?,
},
});
} else {
listeners.push(ElementAttrNamed {
el_name: el_name.clone(),
attr: ElementAttr::EventClosure {
name,
closure: content.parse()?,
},
});
};
} else { } else {
match name_str.as_str() { match name_str.as_str() {
"key" => { "key" => {
key = Some(content.parse()?); key = Some(content.parse()?);
} }
"classes" => { "classes" => todo!("custom class list not supported yet"),
todo!("custom class list not supported") // "namespace" => todo!("custom namespace not supported yet"),
}
"namespace" => {
todo!("custom namespace not supported")
}
"node_ref" => { "node_ref" => {
_el_ref = Some(content.parse::<Expr>()?); _el_ref = Some(content.parse::<Expr>()?);
} }
@@ -96,27 +120,56 @@ impl Parse for Element {
} }
} }
} }
} else if content.peek(LitStr) && content.peek2(Token![:]) {
let name = content.parse::<LitStr>()?;
content.parse::<Token![:]>()?;
if content.peek(LitStr) { if content.is_empty() {
let value = content.parse::<LitStr>()?; break;
attributes.push(ElementAttrNamed {
el_name: el_name.clone(),
attr: ElementAttr::CustomAttrText { name, value },
});
} else {
let value = content.parse::<Expr>()?;
attributes.push(ElementAttrNamed {
el_name: el_name.clone(),
attr: ElementAttr::CustomAttrExpression { name, value },
});
} }
} else {
children.push(content.parse::<BodyNode>()?); // todo: add a message saying you need to include commas between fields
if content.parse::<Token![,]>().is_err() {
proc_macro_error::emit_error!(
ident,
"This attribute is misisng a trailing comma"
)
}
continue;
} }
break;
}
while !content.is_empty() {
if (content.peek(LitStr) && content.peek2(Token![:])) && !content.peek3(Token![:]) {
let ident = content.parse::<LitStr>().unwrap();
let name = ident.value();
proc_macro_error::emit_error!(
ident, "This attribute `{}` is in the wrong place.", name;
help =
"All attribute fields must be placed above children elements.
div {
attr: \"...\", <---- attribute is above children
div { } <---- children are below attributes
}";
)
}
if (content.peek(Ident) && content.peek2(Token![:])) && !content.peek3(Token![:]) {
let ident = content.parse::<Ident>().unwrap();
let name = ident.to_string();
proc_macro_error::emit_error!(
ident, "This attribute `{}` is in the wrong place.", name;
help =
"All attribute fields must be placed above children elements.
div {
attr: \"...\", <---- attribute is above children
div { } <---- children are below attributes
}";
)
}
children.push(content.parse::<BodyNode>()?);
// consume comma if it exists // consume comma if it exists
// we don't actually care if there *are* commas after elements/text // we don't actually care if there *are* commas after elements/text
if content.peek(Token![,]) { if content.peek(Token![,]) {
@@ -138,7 +191,7 @@ impl Parse for Element {
impl ToTokens for Element { impl ToTokens for Element {
fn to_tokens(&self, tokens: &mut TokenStream2) { fn to_tokens(&self, tokens: &mut TokenStream2) {
let name = &self.name; let name = &self.name;
let childs = &self.children; let children = &self.children;
let listeners = &self.listeners; let listeners = &self.listeners;
let attr = &self.attributes; let attr = &self.attributes;
@@ -153,7 +206,7 @@ impl ToTokens for Element {
dioxus_elements::#name, dioxus_elements::#name,
[ #(#listeners),* ], [ #(#listeners),* ],
[ #(#attr),* ], [ #(#attr),* ],
[ #(#childs),* ], [ #(#children),* ],
#key, #key,
) )
}); });
@@ -173,8 +226,8 @@ enum ElementAttr {
// "attribute": true, // "attribute": true,
CustomAttrExpression { name: LitStr, value: Expr }, CustomAttrExpression { name: LitStr, value: Expr },
// onclick: move |_| {} // // onclick: move |_| {}
EventClosure { name: Ident, closure: ExprClosure }, // EventClosure { name: Ident, closure: ExprClosure },
// onclick: {} // onclick: {}
EventTokens { name: Ident, tokens: Expr }, EventTokens { name: Ident, tokens: Expr },
@@ -189,7 +242,7 @@ impl ToTokens for ElementAttrNamed {
fn to_tokens(&self, tokens: &mut TokenStream2) { fn to_tokens(&self, tokens: &mut TokenStream2) {
let ElementAttrNamed { el_name, attr } = self; let ElementAttrNamed { el_name, attr } = self;
let toks = match attr { tokens.append_all(match attr {
ElementAttr::AttrText { name, value } => { ElementAttr::AttrText { name, value } => {
quote! { quote! {
dioxus_elements::#el_name.#name(__cx, format_args_f!(#value)) dioxus_elements::#el_name.#name(__cx, format_args_f!(#value))
@@ -200,26 +253,26 @@ impl ToTokens for ElementAttrNamed {
dioxus_elements::#el_name.#name(__cx, #value) dioxus_elements::#el_name.#name(__cx, #value)
} }
} }
ElementAttr::CustomAttrText { name, value } => { ElementAttr::CustomAttrText { name, value } => {
quote! { __cx.attr( #name, format_args_f!(#value), None, false ) }
}
ElementAttr::CustomAttrExpression { name, value } => {
quote! { __cx.attr( #name, format_args_f!(#value), None, false ) }
}
ElementAttr::EventClosure { name, closure } => {
quote! { quote! {
dioxus_elements::on::#name(__cx, #closure) __cx.attr( #name, format_args_f!(#value), None, false )
} }
} }
ElementAttr::CustomAttrExpression { name, value } => {
quote! {
__cx.attr( #name, format_args_f!(#value), None, false )
}
}
// ElementAttr::EventClosure { name, closure } => {
// quote! {
// dioxus_elements::on::#name(__cx, #closure)
// }
// }
ElementAttr::EventTokens { name, tokens } => { ElementAttr::EventTokens { name, tokens } => {
quote! { quote! {
dioxus_elements::on::#name(__cx, #tokens) dioxus_elements::on::#name(__cx, #tokens)
} }
} }
}; });
tokens.append_all(toks);
} }
} }
-63
View File
@@ -1,63 +0,0 @@
//! Parse `Fragments` into the Fragment VNode
//! ==========================================
//!
//! This parsing path emerges from [`AmbiguousElement`] which supports validation of the Fragment format.
//! We can be reasonably sure that whatever enters this parsing path is in the right format.
//! This feature must support:
//! - [x] Optional commas
//! - [ ] Children
//! - [ ] Keys
use super::AmbiguousElement;
use syn::parse::ParseBuffer;
use {
proc_macro2::TokenStream as TokenStream2,
quote::{quote, ToTokens, TokenStreamExt},
syn::{
parse::{Parse, ParseStream},
Ident, Result, Token,
},
};
pub struct Fragment {
children: Vec<AmbiguousElement>,
}
impl Parse for Fragment {
fn parse(input: ParseStream) -> Result<Self> {
input.parse::<Ident>()?;
let children = Vec::new();
// parse the guts
let content: ParseBuffer;
syn::braced!(content in input);
while !content.is_empty() {
content.parse::<AmbiguousElement>()?;
if content.peek(Token![,]) {
let _ = content.parse::<Token![,]>();
}
}
Ok(Self { children })
}
}
impl ToTokens for Fragment {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let childs = &self.children;
let children = quote! {
ChildrenList::new(__cx)
#( .add_child(#childs) )*
.finish()
};
tokens.append_all(quote! {
// #key_token,
dioxus::builder::vfragment(
__cx,
None,
#children
)
})
}
}
+74 -6
View File
@@ -11,17 +11,85 @@
//! //!
//! Any errors in using rsx! will likely occur when people start using it, so the first errors must be really helpful. //! Any errors in using rsx! will likely occur when people start using it, so the first errors must be really helpful.
mod ambiguous;
mod body;
mod component; mod component;
mod element; mod element;
mod fragment;
mod node; mod node;
// Re-export the namespaces into each other // Re-export the namespaces into each other
pub use ambiguous::*;
pub use body::*;
pub use component::*; pub use component::*;
pub use element::*; pub use element::*;
pub use fragment::*;
pub use node::*; pub use node::*;
// imports
use proc_macro2::TokenStream as TokenStream2;
use quote::{quote, ToTokens, TokenStreamExt};
use syn::{
parse::{Parse, ParseStream},
Ident, Result, Token,
};
pub struct CallBody {
custom_context: Option<Ident>,
roots: Vec<BodyNode>,
}
impl Parse for CallBody {
fn parse(input: ParseStream) -> Result<Self> {
let custom_context = if input.peek(Ident) && input.peek2(Token![,]) {
let name = input.parse::<Ident>()?;
input.parse::<Token![,]>()?;
Some(name)
} else {
None
};
let mut roots = Vec::new();
while !input.is_empty() {
let node = input.parse::<BodyNode>()?;
if input.peek(Token![,]) {
let _ = input.parse::<Token![,]>();
}
roots.push(node);
}
Ok(Self {
custom_context,
roots,
})
}
}
/// Serialize the same way, regardless of flavor
impl ToTokens for CallBody {
fn to_tokens(&self, out_tokens: &mut TokenStream2) {
let inner = if self.roots.len() == 1 {
let inner = &self.roots[0];
quote! { #inner }
} else {
let childs = &self.roots;
quote! { __cx.fragment_root([ #(#childs),* ]) }
};
match &self.custom_context {
// The `in cx` pattern allows directly rendering
Some(ident) => out_tokens.append_all(quote! {
#ident.render(LazyNodes::new_some(move |__cx: NodeFactory| -> VNode {
use dioxus_elements::{GlobalAttributes, SvgAttributes};
#inner
}))
}),
// Otherwise we just build the LazyNode wrapper
None => out_tokens.append_all(quote! {
LazyNodes::new_some(move |__cx: NodeFactory| -> VNode {
use dioxus_elements::{GlobalAttributes, SvgAttributes};
#inner
})
}),
};
}
}
+54 -39
View File
@@ -4,34 +4,67 @@ use proc_macro2::TokenStream as TokenStream2;
use quote::{quote, ToTokens, TokenStreamExt}; use quote::{quote, ToTokens, TokenStreamExt};
use syn::{ use syn::{
parse::{Parse, ParseStream}, parse::{Parse, ParseStream},
token, Expr, LitStr, Result, token, Expr, LitStr, Result, Token,
}; };
// ============================================== /*
// Parse any div {} as a VElement Parse
// ============================================== -> div {}
-> Component {}
-> component()
-> "text {with_args}"
-> (0..10).map(|f| rsx!("asd")), // <--- notice the comma - must be a complete expr
*/
pub enum BodyNode { pub enum BodyNode {
Element(AmbiguousElement), Element(Element),
Text(TextNode), Component(Component),
Text(LitStr),
RawExpr(Expr), RawExpr(Expr),
} }
impl Parse for BodyNode { impl Parse for BodyNode {
fn parse(stream: ParseStream) -> Result<Self> { fn parse(stream: ParseStream) -> Result<Self> {
// Supposedly this approach is discouraged due to inability to return proper errors
// TODO: Rework this to provide more informative errors
if stream.peek(token::Brace) {
let content;
syn::braced!(content in stream);
return Ok(BodyNode::RawExpr(content.parse::<Expr>()?));
}
if stream.peek(LitStr) { if stream.peek(LitStr) {
return Ok(BodyNode::Text(stream.parse::<TextNode>()?)); return Ok(BodyNode::Text(stream.parse()?));
} }
Ok(BodyNode::Element(stream.parse::<AmbiguousElement>()?)) // div {} -> el
// Div {} -> comp
if stream.peek(syn::Ident) && stream.peek2(token::Brace) {
if stream
.fork()
.parse::<Ident>()?
.to_string()
.chars()
.next()
.unwrap()
.is_ascii_uppercase()
{
return Ok(BodyNode::Component(stream.parse()?));
} else {
return Ok(BodyNode::Element(stream.parse::<Element>()?));
}
}
// component() -> comp
// ::component {} -> comp
// ::component () -> comp
if (stream.peek(syn::Ident) && stream.peek2(token::Paren))
|| (stream.peek(Token![::]))
|| (stream.peek(Token![:]) && stream.peek2(Token![:]))
{
return Ok(BodyNode::Component(stream.parse::<Component>()?));
}
// crate::component{} -> comp
// crate::component() -> comp
if let Ok(pat) = stream.fork().parse::<syn::Path>() {
if pat.segments.len() > 1 {
return Ok(BodyNode::Component(stream.parse::<Component>()?));
}
}
Ok(BodyNode::RawExpr(stream.parse::<Expr>()?))
} }
} }
@@ -39,31 +72,13 @@ impl ToTokens for BodyNode {
fn to_tokens(&self, tokens: &mut TokenStream2) { fn to_tokens(&self, tokens: &mut TokenStream2) {
match &self { match &self {
BodyNode::Element(el) => el.to_tokens(tokens), BodyNode::Element(el) => el.to_tokens(tokens),
BodyNode::Text(txt) => txt.to_tokens(tokens), BodyNode::Component(comp) => comp.to_tokens(tokens),
BodyNode::Text(txt) => tokens.append_all(quote! {
__cx.text(format_args_f!(#txt))
}),
BodyNode::RawExpr(exp) => tokens.append_all(quote! { BodyNode::RawExpr(exp) => tokens.append_all(quote! {
__cx.fragment_from_iter(#exp) __cx.fragment_from_iter(#exp)
}), }),
} }
} }
} }
// =======================================
// Parse just plain text
// =======================================
pub struct TextNode(LitStr);
impl Parse for TextNode {
fn parse(s: ParseStream) -> Result<Self> {
Ok(Self(s.parse()?))
}
}
impl ToTokens for TextNode {
fn to_tokens(&self, tokens: &mut TokenStream2) {
// todo: use heuristics to see if we can promote to &static str
let token_stream = &self.0.to_token_stream();
tokens.append_all(quote! {
__cx.text(format_args_f!(#token_stream))
});
}
}
-1
View File
@@ -50,7 +50,6 @@ anyhow = "1.0.42"
dioxus-html = { path = "../html" } dioxus-html = { path = "../html" }
fern = { version = "0.6.0", features = ["colored"] } fern = { version = "0.6.0", features = ["colored"] }
rand = { version = "0.8.4", features = ["small_rng"] } rand = { version = "0.8.4", features = ["small_rng"] }
simple_logger = "1.13.0"
dioxus-core-macro = { path = "../core-macro", version = "^0.1.2" } dioxus-core-macro = { path = "../core-macro", version = "^0.1.2" }
criterion = "0.3.5" criterion = "0.3.5"
thiserror = "1.0.30" thiserror = "1.0.30"
+1 -1
View File
@@ -6,7 +6,7 @@ To build new apps with Dioxus or to extend the ecosystem with new hooks or compo
```rust ```rust
fn app(cx: Scope<()>) -> Element { fn app(cx: Scope) -> Element {
rsx!(cx, div { "hello world" }) rsx!(cx, div { "hello world" })
} }
+1 -1
View File
@@ -11,7 +11,7 @@ fn main() {
dbg!(edits); dbg!(edits);
} }
fn parent(cx: Scope<()>) -> Element { fn parent(cx: Scope) -> Element {
let value = cx.use_hook(|_| String::new(), |f| f); let value = cx.use_hook(|_| String::new(), |f| f);
cx.render(rsx! { cx.render(rsx! {
+1 -1
View File
@@ -9,7 +9,7 @@ fn main() {
let _ = VirtualDom::new(parent); let _ = VirtualDom::new(parent);
} }
fn parent(cx: Scope<()>) -> Element { fn parent(cx: Scope) -> Element {
let value = cx.use_hook(|_| String::new(), |f| f); let value = cx.use_hook(|_| String::new(), |f| f);
cx.render(rsx! { cx.render(rsx! {
+13 -3
View File
@@ -84,7 +84,7 @@ pub enum VNode<'src> {
/// # Example /// # Example
/// ///
/// ```rust, ignore /// ```rust, ignore
/// fn Example(cx: Scope<()>) -> Element { /// fn Example(cx: Scope) -> Element {
/// ... /// ...
/// } /// }
/// ///
@@ -734,9 +734,15 @@ impl<'a, 'b> IntoVNode<'a> for LazyNodes<'a, 'b> {
} }
} }
impl IntoVNode<'_> for &'static str { impl<'b> IntoVNode<'_> for &'b str {
fn into_vnode(self, cx: NodeFactory) -> VNode { fn into_vnode(self, cx: NodeFactory) -> VNode {
cx.static_text(self) cx.text(format_args!("{}", self))
}
}
impl IntoVNode<'_> for String {
fn into_vnode(self, cx: NodeFactory) -> VNode {
cx.text(format_args!("{}", self))
} }
} }
@@ -758,3 +764,7 @@ impl<'a> IntoVNode<'a> for &VNode<'a> {
self.decouple() self.decouple()
} }
} }
trait IntoAcceptedVnode<'a> {
fn into_accepted_vnode(self, cx: NodeFactory<'a>) -> VNode<'a>;
}
+1 -1
View File
@@ -490,7 +490,7 @@ impl ScopeState {
/// # Example /// # Example
/// ///
/// ```rust, ignore /// ```rust, ignore
/// fn App(cx: Scope<()>) -> Element { /// fn App(cx: Scope) -> Element {
/// rsx!(cx, div { "Subtree {id}"}) /// rsx!(cx, div { "Subtree {id}"})
/// }; /// };
/// ``` /// ```
+9 -9
View File
@@ -80,7 +80,7 @@ use std::{any::Any, collections::VecDeque, iter::FromIterator, pin::Pin, sync::A
/// Putting everything together, you can build an event loop around Dioxus by using the methods outlined above. /// Putting everything together, you can build an event loop around Dioxus by using the methods outlined above.
/// ///
/// ```rust, ignore /// ```rust, ignore
/// fn App(cx: Scope<()>) -> Element { /// fn App(cx: Scope) -> Element {
/// cx.render(rsx!{ /// cx.render(rsx!{
/// div { "Hello World" } /// div { "Hello World" }
/// }) /// })
@@ -127,7 +127,7 @@ impl VirtualDom {
/// ///
/// # Example /// # Example
/// ```rust, ignore /// ```rust, ignore
/// fn Example(cx: Scope<()>) -> Element { /// fn Example(cx: Scope) -> Element {
/// cx.render(rsx!( div { "hello world" } )) /// cx.render(rsx!( div { "hello world" } ))
/// } /// }
/// ///
@@ -388,7 +388,7 @@ impl VirtualDom {
/// # Example /// # Example
/// ///
/// ```rust, ignore /// ```rust, ignore
/// fn App(cx: Scope<()>) -> Element { /// fn App(cx: Scope) -> Element {
/// cx.render(rsx!( div {"hello"} )) /// cx.render(rsx!( div {"hello"} ))
/// } /// }
/// ///
@@ -544,7 +544,7 @@ impl VirtualDom {
/// Useful when needing to render nodes from outside the VirtualDom, such as in a test. /// Useful when needing to render nodes from outside the VirtualDom, such as in a test.
/// ///
/// ```rust /// ```rust
/// fn Base(cx: Scope<()>) -> Element { /// fn Base(cx: Scope) -> Element {
/// rsx!(cx, div {}) /// rsx!(cx, div {})
/// } /// }
/// ///
@@ -564,7 +564,7 @@ impl VirtualDom {
/// Useful when needing to render nodes from outside the VirtualDom, such as in a test. /// Useful when needing to render nodes from outside the VirtualDom, such as in a test.
/// ///
/// ```rust /// ```rust
/// fn Base(cx: Scope<()>) -> Element { /// fn Base(cx: Scope) -> Element {
/// rsx!(cx, div {}) /// rsx!(cx, div {})
/// } /// }
/// ///
@@ -586,7 +586,7 @@ impl VirtualDom {
/// ///
/// ///
/// ```rust /// ```rust
/// fn Base(cx: Scope<()>) -> Element { /// fn Base(cx: Scope) -> Element {
/// rsx!(cx, div {}) /// rsx!(cx, div {})
/// } /// }
/// ///
@@ -609,7 +609,7 @@ impl VirtualDom {
/// ///
/// ///
/// ```rust /// ```rust
/// fn Base(cx: Scope<()>) -> Element { /// fn Base(cx: Scope) -> Element {
/// rsx!(cx, div {}) /// rsx!(cx, div {})
/// } /// }
/// ///
@@ -753,7 +753,7 @@ pub enum SchedulerMsg {
/// ///
/// # Example /// # Example
/// ```rust /// ```rust
/// fn App(cx: Scope<()>) -> Element { /// fn App(cx: Scope) -> Element {
/// rsx!(cx, div { /// rsx!(cx, div {
/// onclick: move |_| println!("Clicked!") /// onclick: move |_| println!("Clicked!")
/// }) /// })
@@ -871,7 +871,7 @@ impl<'a, const A: bool> FragmentBuilder<'a, A> {
/// ## Example /// ## Example
/// ///
/// ```rust, ignore /// ```rust, ignore
/// fn App(cx: Scope<()>) -> Element { /// fn App(cx: Scope) -> Element {
/// cx.render(rsx!{ /// cx.render(rsx!{
/// CustomCard { /// CustomCard {
/// h1 {}2 /// h1 {}2
+1 -1
View File
@@ -10,7 +10,7 @@ fn test_borrowed_state() {
let _ = VirtualDom::new(Parent); let _ = VirtualDom::new(Parent);
} }
fn Parent(cx: Scope<()>) -> Element { fn Parent(cx: Scope) -> Element {
let value = cx.use_hook(|_| String::new(), |f| &*f); let value = cx.use_hook(|_| String::new(), |f| &*f);
cx.render(rsx! { cx.render(rsx! {
+7 -7
View File
@@ -28,7 +28,7 @@ fn new_dom<P: 'static + Send>(app: Component<P>, props: P) -> VirtualDom {
/// In debug, this should also toss a warning. /// In debug, this should also toss a warning.
#[test] #[test]
fn test_memory_leak() { fn test_memory_leak() {
fn app(cx: Scope<()>) -> Element { fn app(cx: Scope) -> Element {
let val = cx.use_hook(|_| 0, |f| f); let val = cx.use_hook(|_| 0, |f| f);
*val += 1; *val += 1;
@@ -68,7 +68,7 @@ fn test_memory_leak() {
}) })
} }
fn child(cx: Scope<()>) -> Element { fn child(cx: Scope) -> Element {
rsx!(cx, div { "goodbye world" }) rsx!(cx, div { "goodbye world" })
} }
@@ -85,7 +85,7 @@ fn test_memory_leak() {
#[test] #[test]
fn memo_works_properly() { fn memo_works_properly() {
fn app(cx: Scope<()>) -> Element { fn app(cx: Scope) -> Element {
let val = cx.use_hook(|_| 0, |f| f); let val = cx.use_hook(|_| 0, |f| f);
*val += 1; *val += 1;
@@ -164,7 +164,7 @@ fn free_works_on_root_props() {
#[test] #[test]
fn free_works_on_borrowed() { fn free_works_on_borrowed() {
fn app(cx: Scope<()>) -> Element { fn app(cx: Scope) -> Element {
cx.render(rsx! { cx.render(rsx! {
child(a: "alpha", b: "asd".to_string()) child(a: "alpha", b: "asd".to_string())
}) })
@@ -203,7 +203,7 @@ fn free_works_on_root_hooks() {
} }
} }
fn app(cx: Scope<()>) -> Element { fn app(cx: Scope) -> Element {
let name = cx.use_hook(|_| Droppable(String::from("asd")), |f| f); let name = cx.use_hook(|_| Droppable(String::from("asd")), |f| f);
rsx!(cx, div { "{name.0}" }) rsx!(cx, div { "{name.0}" })
} }
@@ -214,7 +214,7 @@ fn free_works_on_root_hooks() {
#[test] #[test]
fn old_props_arent_stale() { fn old_props_arent_stale() {
fn app(cx: Scope<()>) -> Element { fn app(cx: Scope) -> Element {
dbg!("rendering parent"); dbg!("rendering parent");
let cnt = cx.use_hook(|_| 0, |f| f); let cnt = cx.use_hook(|_| 0, |f| f);
*cnt += 1; *cnt += 1;
@@ -261,7 +261,7 @@ fn old_props_arent_stale() {
#[test] #[test]
fn basic() { fn basic() {
fn app(cx: Scope<()>) -> Element { fn app(cx: Scope) -> Element {
rsx!(cx, div { rsx!(cx, div {
child(a: "abcdef".to_string()) child(a: "abcdef".to_string())
}) })
+1 -1
View File
@@ -44,7 +44,7 @@ fn lists_work() {
static App: Component = |cx| { static App: Component = |cx| {
cx.render(rsx!( cx.render(rsx!(
h1 {"hello"} h1 {"hello"}
{(0..6).map(|f| rsx!(span{ "{f}" }))} (0..6).map(|f| rsx!(span{ "{f}" }))
)) ))
}; };
let mut vdom = VirtualDom::new(App); let mut vdom = VirtualDom::new(App);
-1
View File
@@ -37,4 +37,3 @@ tokio_runtime = ["tokio"]
[dev-dependencies] [dev-dependencies]
dioxus-hooks = { path = "../hooks" } dioxus-hooks = { path = "../hooks" }
simple_logger = "1.13.0"
+10 -14
View File
@@ -9,22 +9,18 @@ fn main() {
dioxus_desktop::launch(app); dioxus_desktop::launch(app);
} }
fn app(cx: Scope<()>) -> Element { fn app(cx: Scope) -> Element {
let count = use_state(&cx, || 0); let count = use_state(&cx, || 0);
// push the futureo on initialization use_future(&cx, || {
cx.use_hook( let count = count.for_async();
|_| { async move {
cx.push_future({ loop {
let count = count.for_async(); tokio::time::sleep(Duration::from_millis(1000)).await;
async move { *count.modify() += 1;
tokio::time::sleep(Duration::from_millis(1000)).await; }
*count.get_mut() += 1; }
} });
});
},
|_| {},
);
cx.render(rsx! { cx.render(rsx! {
div { div {
+4 -43
View File
@@ -4,58 +4,19 @@ This crate includes some basic useful hooks for dioxus:
- use_state - use_state
- use_ref - use_ref
- use_collection - use_future
- use_task - use_coroutine
- use_signal
## use_state ## use_state
The king daddy of state hooks. The primary mechanism of stored state.
You can always use it "normally" with the `split` method: You can always use it "normally" with the `split` method:
```rust ```rust
// Normal usage: // Rusty-smart-pointer usage:
let value = use_state(&cx, || 10); let value = use_state(&cx, || 10);
// "Classic" usage: // "Classic" usage:
let (value, set_value) = use_state(&cx, || 0).split(); let (value, set_value) = use_state(&cx, || 0).split();
``` ```
## use_ref
## use_rwlock
A multithreaded form of RwLock for use in tasks
```rust
let val = use_rwlock(cx, || 10);
use_task((), || async loop {
*val.write().unwrap() += 1;
async_std::task::delay(Duration::from_ms(1000)).await;
});
use_task((), || async loop {
*val.write().unwrap() -= 1;
async_std::task::delay(Duration::from_ms(500)).await;
});
```
## use_hashmap
Store a memoized collection with similar semantics to use_state. Comes with a bunch of utility methods to make working with collections easier. Is essentially a wrapper over the immutable hashmap in im-rc.
```rust
let todos = use_hashmap(cx, |map| map.insert("bob", "bill"));
cx.render(rsx!(
button { onclick: move |_| todos.insert("bob", "bill")
"add random todo"
}
)
```
## use_task
use_task submits a task to the dioxus task queue to be progressed during Dioxus's async event loop. The task must not return anything
## use_signal
+43 -3
View File
@@ -1,5 +1,5 @@
mod usestate; mod usestate;
pub use usestate::{use_state, AsyncUseState, UseState}; pub use usestate::{use_state, UseState};
mod useref; mod useref;
pub use useref::*; pub use useref::*;
@@ -16,5 +16,45 @@ pub use usefuture::*;
mod usesuspense; mod usesuspense;
pub use usesuspense::*; pub use usesuspense::*;
// mod usemodel; #[macro_export]
// pub use usemodel::*; macro_rules! to_owned {
($($es:ident),+) => {$(
#[allow(unused_mut)]
let mut $es = $es.to_owned();
)*}
}
/// Calls `for_async` on the series of paramters.
///
/// If the type is Clone, then it will be cloned. However, if the type is not `clone`
/// then it must have a `for_async` method for Rust to lower down into.
///
/// See: how use_state implements `for_async` but *not* through the trait.
#[macro_export]
macro_rules! for_async {
($($es:ident),+) => {$(
#[allow(unused_mut)]
let mut $es = $es.for_async();
)*}
}
/// This is a marker trait that uses decoherence.
///
/// It is *not* meant for hooks to actually implement, but rather defer to their
/// underlying implementation if they *don't* implement the trait.
///
///
pub trait AsyncHook {
type Output;
fn for_async(self) -> Self::Output;
}
impl<T> AsyncHook for T
where
T: ToOwned<Owned = T>,
{
type Output = T;
fn for_async(self) -> Self::Output {
self
}
}
+39 -79
View File
@@ -2,20 +2,9 @@ use dioxus_core::prelude::*;
use std::{ use std::{
cell::{Cell, Ref, RefCell, RefMut}, cell::{Cell, Ref, RefCell, RefMut},
fmt::{Debug, Display}, fmt::{Debug, Display},
ops::Not,
rc::Rc, rc::Rc,
}; };
pub trait UseStateA<'a, T> {
fn use_state(&self, initial_state_fn: impl FnOnce() -> T) -> UseState<'a, T>;
}
impl<'a, P, T> UseStateA<'a, T> for Scope<'a, P> {
fn use_state(&self, initial_state_fn: impl FnOnce() -> T) -> UseState<'a, T> {
use_state(self.scope, initial_state_fn)
}
}
/// Store state between component renders! /// Store state between component renders!
/// ///
/// ## Dioxus equivalent of useState, designed for Rust /// ## Dioxus equivalent of useState, designed for Rust
@@ -109,6 +98,7 @@ where
UseState { inner: self.inner } UseState { inner: self.inner }
} }
} }
impl<T: Debug> Debug for UseState<'_, T> { impl<T: Debug> Debug for UseState<'_, T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self.inner.current_val) write!(f, "{:?}", self.inner.current_val)
@@ -157,21 +147,21 @@ impl<'a, T: 'static> UseState<'a, T> {
}) })
} }
pub fn for_async(&self) -> AsyncUseState<T> { pub fn for_async(self) -> UseState<'static, T> {
AsyncUseState { todo!()
re_render: self.inner.update_callback.clone(),
wip: self.inner.wip.clone(),
inner: self.inner.current_val.clone(),
}
} }
pub fn split_for_async(&'a self) -> (&'a Self, AsyncUseState<T>) { pub fn wtih(self, f: impl FnOnce(&mut T)) {
(self, self.for_async()) let mut val = self.inner.wip.borrow_mut();
if let Some(inner) = val.as_mut() {
f(inner);
}
} }
} }
impl<'a, T: 'static + ToOwned<Owned = T>> UseState<'a, T> { impl<'a, T: 'static + ToOwned<Owned = T>> UseState<'a, T> {
/// Gain mutable access to the new value via RefMut. /// Gain mutable access to the new value via [`RefMut`].
/// ///
/// If `modify` is called, then the component will re-render. /// If `modify` is called, then the component will re-render.
/// ///
@@ -207,6 +197,35 @@ impl<'a, T> std::ops::Deref for UseState<'a, T> {
} }
} }
// enable displaty for the handle
impl<'a, T: 'static + Display> std::fmt::Display for UseState<'a, T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.inner.current_val)
}
}
impl<'a, V, T: PartialEq<V>> PartialEq<V> for UseState<'a, T> {
fn eq(&self, other: &V) -> bool {
self.get() == other
}
}
impl<'a, O, T: std::ops::Not<Output = O> + Copy> std::ops::Not for UseState<'a, T> {
type Output = O;
fn not(self) -> Self::Output {
!*self.get()
}
}
/*
Convenience methods for UseState.
Note!
This is not comprehensive.
This is *just* meant to make common operations easier.
*/
use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign}; use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};
impl<'a, T: Copy + Add<T, Output = T>> Add<T> for UseState<'a, T> { impl<'a, T: Copy + Add<T, Output = T>> Add<T> for UseState<'a, T> {
@@ -260,62 +279,3 @@ impl<'a, T: Copy + Div<T, Output = T>> DivAssign<T> for UseState<'a, T> {
self.set(self.inner.current_val.div(rhs)); self.set(self.inner.current_val.div(rhs));
} }
} }
impl<'a, V, T: PartialEq<V>> PartialEq<V> for UseState<'a, T> {
fn eq(&self, other: &V) -> bool {
self.get() == other
}
}
impl<'a, O, T: Not<Output = O> + Copy> Not for UseState<'a, T> {
type Output = O;
fn not(self) -> Self::Output {
!*self.get()
}
}
// enable displaty for the handle
impl<'a, T: 'static + Display> std::fmt::Display for UseState<'a, T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.inner.current_val)
}
}
/// A less ergonmic but still capable form of use_state that's valid for `static lifetime
pub struct AsyncUseState<T: 'static> {
inner: Rc<T>,
re_render: Rc<dyn Fn()>,
wip: Rc<RefCell<Option<T>>>,
}
impl<T: ToOwned<Owned = T>> AsyncUseState<T> {
pub fn get_mut<'a>(&'a self) -> RefMut<'a, T> {
// make sure we get processed
{
let mut wip = self.wip.borrow_mut();
if wip.is_none() {
*wip = Some(self.inner.as_ref().to_owned());
}
(self.re_render)();
}
// Bring out the new value, cloning if it we need to
// "get_mut" is locked behind "ToOwned" to make it explicit that cloning occurs to use this
RefMut::map(self.wip.borrow_mut(), |slot| {
//
slot.as_mut().unwrap()
})
}
}
impl<T> AsyncUseState<T> {
pub fn set(&mut self, val: T) {
(self.re_render)();
*self.wip.borrow_mut() = Some(val);
}
// pub fn get(&self) -> Ref<'_, T> {
// self.wip.borrow
// }
pub fn get_rc(&self) -> &Rc<T> {
&self.inner
}
}
+1 -3
View File
@@ -7,16 +7,14 @@ description = "HTML Element pack for Dioxus - a concurrent renderer-agnostic Vir
license = "MIT/Apache-2.0" license = "MIT/Apache-2.0"
repository = "https://github.com/DioxusLabs/dioxus/" repository = "https://github.com/DioxusLabs/dioxus/"
homepage = "https://dioxuslabs.com" homepage = "https://dioxuslabs.com"
documentation = "https://dioxuslabs.com" documentation = "https://docs.rs/dioxus"
keywords = ["dom", "ui", "gui", "react", "wasm"] keywords = ["dom", "ui", "gui", "react", "wasm"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
dioxus-core = { path = "../core", version = "^0.1.4" } dioxus-core = { path = "../core", version = "^0.1.4" }
serde = { version = "1", features = ["derive"], optional = true } serde = { version = "1", features = ["derive"], optional = true }
serde_repr = { version = "0.1", optional = true } serde_repr = { version = "0.1", optional = true }
[features] [features]
default = [] default = []
serialize = ["serde", "serde_repr"] serialize = ["serde", "serde_repr"]
+1 -1
View File
@@ -39,7 +39,7 @@ async fn order_shoes(mut req: WebsocketRequest) -> Response {
dioxus::liveview::launch(App, stream).await; dioxus::liveview::launch(App, stream).await;
} }
fn App(cx: Scope<()>) -> Element { fn App(cx: Scope) -> Element {
let mut count = use_state(&cx, || 0); let mut count = use_state(&cx, || 0);
cx.render(rsx!( cx.render(rsx!(
button { onclick: move |_| count += 1, "Incr" } button { onclick: move |_| count += 1, "Incr" }
+1 -4
View File
@@ -12,7 +12,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
[dependencies] [dependencies]
anyhow = "1.0" anyhow = "1.0"
# cacao = { git = "https://github.com/ryanmcgrath/cacao" } # cacao = { git = "https://github.com/ryanmcgrath/cacao" }
dioxus-core = { path = "../core", version ="^0.1.3"} dioxus-core = { path = "../core", version = "^0.1.3" }
log = "0.4.14" log = "0.4.14"
serde = "1.0.126" serde = "1.0.126"
serde_json = "1.0.64" serde_json = "1.0.64"
@@ -23,6 +23,3 @@ wry = "0.12.2"
android_logger = "0.9.0" android_logger = "0.9.0"
log = "0.4.11" log = "0.4.11"
ndk-glue = "0.2.1" ndk-glue = "0.2.1"
[target.'cfg(not(target_os = "android"))'.dependencies]
simple_logger = "1.11.0"
-5
View File
@@ -20,10 +20,6 @@ use wry::{
mod dom; mod dom;
use dom::*; use dom::*;
fn init_logging() {
simple_logger::SimpleLogger::new().init().unwrap();
}
static HTML_CONTENT: &'static str = include_str!("../../desktop/src/index.html"); static HTML_CONTENT: &'static str = include_str!("../../desktop/src/index.html");
pub fn launch(root: Component, builder: fn(WindowBuilder) -> WindowBuilder) -> anyhow::Result<()> { pub fn launch(root: Component, builder: fn(WindowBuilder) -> WindowBuilder) -> anyhow::Result<()> {
@@ -187,7 +183,6 @@ impl<T: 'static + Send> WebviewRenderer<T> {
} }
fn main() { fn main() {
init_logging();
let event_loop = EventLoop::new(); let event_loop = EventLoop::new();
let mut weviews = HashMap::new(); let mut weviews = HashMap::new();
+1 -1
View File
@@ -41,8 +41,8 @@ pub fn Link<'a, R: Routable>(cx: Scope<'a, LinkProps<'a, R>>) -> Element {
a { a {
href: "#", href: "#",
class: format_args!("{}", cx.props.class.unwrap_or("")), class: format_args!("{}", cx.props.class.unwrap_or("")),
{&cx.props.children}
onclick: move |_| service.push_route(cx.props.to.clone()), onclick: move |_| service.push_route(cx.props.to.clone()),
{&cx.props.children}
} }
}) })
} }
+7 -8
View File
@@ -309,19 +309,18 @@ mod tests {
static SLIGHTLY_MORE_COMPLEX: Component = |cx| { static SLIGHTLY_MORE_COMPLEX: Component = |cx| {
cx.render(rsx! { cx.render(rsx! {
div { div { title: "About W3Schools",
title: "About W3Schools" (0..20).map(|f| rsx!{
{(0..20).map(|f| rsx!{
div { div {
title: "About W3Schools" title: "About W3Schools",
style: "color:blue;text-align:center" style: "color:blue;text-align:center",
class: "About W3Schools" class: "About W3Schools",
p { p {
title: "About W3Schools" title: "About W3Schools",
"Hello world!: {f}" "Hello world!: {f}"
} }
} }
})} })
} }
}) })
}; };
+17 -29
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "dioxus-web" name = "dioxus-web"
version = "0.0.0" version = "0.0.1"
authors = ["Jonathan Kelley"] authors = ["Jonathan Kelley"]
edition = "2018" edition = "2018"
description = "Dioxus VirtualDOM renderer for the web browser using websys" description = "Dioxus VirtualDOM renderer for the web browser using websys"
@@ -9,20 +9,12 @@ repository = "https://github.com/DioxusLabs/dioxus/"
homepage = "https://dioxuslabs.com" homepage = "https://dioxuslabs.com"
documentation = "https://dioxuslabs.com" documentation = "https://dioxuslabs.com"
keywords = ["dom", "ui", "gui", "react", "wasm"] keywords = ["dom", "ui", "gui", "react", "wasm"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
dioxus-core = { path = "../core", version ="^0.1.3"} dioxus-core = { path = "../core", version = "^0.1.4" }
dioxus-html = { path = "../html" } dioxus-html = { path = "../html", version = "^0.1.1" }
js-sys = "0.3" js-sys = "0.3"
# wasm-bindgen-shared = { path = "../../../Tinkering/wasm-bindgen/crates/shared" }
# wasm-bindgen-macro-support = { path = "../../../Tinkering/wasm-bindgen/crates/macro-support" }
# wasm-bindgen = { features = [
wasm-bindgen = { version = "0.2.78", features = ["enable-interning"] } wasm-bindgen = { version = "0.2.78", features = ["enable-interning"] }
# wasm-bindgen = { version = "0.2.78", features = ["enable-interning"] }
# wasm-bindgen = { version = "0.2.78", features = ["enable-interning"] }
lazy_static = "1.4.0" lazy_static = "1.4.0"
wasm-bindgen-futures = "0.4.20" wasm-bindgen-futures = "0.4.20"
log = { version = "0.4.14", features = ["release_max_level_off"] } log = { version = "0.4.14", features = ["release_max_level_off"] }
@@ -78,23 +70,19 @@ features = [
] ]
[lib] # [lib]
crate-type = ["cdylib", "rlib"] # crate-type = ["cdylib", "rlib"]
[dev-dependencies] # [dev-dependencies]
im-rc = "15.0.0" # im-rc = "15.0.0"
separator = "0.4.1" # separator = "0.4.1"
uuid = { version = "0.8.2", features = ["v4", "wasm-bindgen"] } # uuid = { version = "0.8.2", features = ["v4", "wasm-bindgen"] }
serde = { version = "1.0.126", features = ["derive"] } # serde = { version = "1.0.126", features = ["derive"] }
reqwest = { version = "0.11", features = ["json"] } # reqwest = { version = "0.11", features = ["json"] }
dioxus-hooks = { path = "../hooks" } # dioxus-hooks = { path = "../hooks" }
dioxus-core-macro = { path = "../core-macro" } # dioxus-core-macro = { path = "../core-macro" }
rand = { version = "0.8.4", features = ["small_rng"] } # rand = { version = "0.8.4", features = ["small_rng"] }
[dev-dependencies.getrandom] # [dev-dependencies.getrandom]
version = "0.2" # version = "0.2"
features = ["js"] # features = ["js"]
# surf = { version = "2.3.1", default-features = false, features = [
# "wasm-client",
# ] }
-29
View File
@@ -1,29 +0,0 @@
//! Example: README.md showcase
//!
//! The example from the README.md.
use dioxus::prelude::*;
use dioxus_core as dioxus;
use dioxus_core_macro::*;
use dioxus_hooks::use_state;
use dioxus_html as dioxus_elements;
use dioxus_web;
use gloo_timers::future::TimeoutFuture;
fn main() {
dioxus_web::launch(App);
}
static App: Component = |cx| {
let mut count = use_state(&cx, || 0);
cx.push_future(|| async move {
TimeoutFuture::new(100).await;
count += 1;
});
rsx!(cx, div {
h3 { "High-Five counter: {count}" }
button { onclick: move |_| count.set(0), "Reset!" }
})
};
-221
View File
@@ -1,221 +0,0 @@
use std::cell::Cell;
use dioxus::prelude::*;
use dioxus_core as dioxus;
use dioxus_core_macro::*;
use dioxus_hooks::{use_ref, use_state};
use dioxus_html as dioxus_elements;
use dioxus_web;
use rand::prelude::*;
fn main() {
console_error_panic_hook::set_once();
if cfg!(debug_assertions) {
wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
log::debug!("hello world");
}
for a in ADJECTIVES {
wasm_bindgen::intern(*a);
}
for a in COLOURS {
wasm_bindgen::intern(*a);
}
for a in NOUNS {
wasm_bindgen::intern(*a);
}
for a in [
"container",
"jumbotron",
"row",
"Dioxus",
"col-md-6",
"col-md-1",
"Create 1,000 rows",
"run",
"Create 10,000 rows",
"runlots",
"Append 1,000 rows",
"add",
"Update every 10th row",
"update",
"Clear",
"clear",
"Swap rows",
"swaprows",
"preloadicon glyphicon glyphicon-remove", //
"aria-hidden",
"onclick",
"true",
"false",
"danger",
"type",
"id",
"class",
"glyphicon glyphicon-remove remove",
"dioxus-id",
"dioxus-event-click",
"dioxus",
"click",
"1.10",
"lbl",
"remove",
"dioxus-event",
"col-sm-6 smallpad",
"btn btn-primary btn-block",
"",
" ",
] {
wasm_bindgen::intern(a);
}
for x in 0..100_000 {
wasm_bindgen::intern(&x.to_string());
}
dioxus_web::launch(App);
}
#[derive(Clone, PartialEq, Copy)]
struct Label {
key: usize,
labels: [&'static str; 3],
}
static mut Counter: Cell<usize> = Cell::new(1);
impl Label {
fn new_list(num: usize) -> Vec<Self> {
let mut rng = SmallRng::from_entropy();
let mut labels = Vec::with_capacity(num);
let offset = unsafe { Counter.get() };
unsafe { Counter.set(offset + num) };
for k in offset..(offset + num) {
labels.push(Label {
key: k,
labels: [
ADJECTIVES.choose(&mut rng).unwrap(),
COLOURS.choose(&mut rng).unwrap(),
NOUNS.choose(&mut rng).unwrap(),
],
});
}
labels
}
}
static App: Component = |cx| {
let mut items = use_ref(&cx, || vec![]);
let mut selected = use_state(&cx, || None);
cx.render(rsx! {
div { class: "container"
div { class: "jumbotron"
div { class: "row"
div { class: "col-md-6", h1 { "Dioxus" } }
div { class: "col-md-6"
div { class: "row"
ActionButton { name: "Create 1,000 rows", id: "run",
onclick: move || items.set(Label::new_list(1_000)),
}
ActionButton { name: "Create 10,000 rows", id: "runlots",
onclick: move || items.set(Label::new_list(10_000)),
}
ActionButton { name: "Append 1,000 rows", id: "add",
onclick: move || items.write().extend(Label::new_list(1_000)),
}
ActionButton { name: "Update every 10th row", id: "update",
onclick: move || items.write().iter_mut().step_by(10).for_each(|item| item.labels[2] = "!!!"),
}
ActionButton { name: "Clear", id: "clear",
onclick: move || items.write().clear(),
}
ActionButton { name: "Swap Rows", id: "swaprows",
onclick: move || items.write().swap(0, 998),
}
}
}
}
}
table { class: "table table-hover table-striped test-data"
tbody { id: "tbody"
{items.read().iter().enumerate().map(|(id, item)| {
let [adj, col, noun] = item.labels;
let is_in_danger = if (*selected).map(|s| s == id).unwrap_or(false) {"danger"} else {""};
rsx!(tr {
class: "{is_in_danger}",
key: "{id}",
td { class:"col-md-1" }
td { class:"col-md-1", "{item.key}" }
td { class:"col-md-1", onclick: move |_| selected.set(Some(id)),
a { class: "lbl", "{adj} {col} {noun}" }
}
td { class: "col-md-1"
a { class: "remove", onclick: move |_| { items.write().remove(id); },
span { class: "glyphicon glyphicon-remove remove" aria_hidden: "true" }
}
}
td { class: "col-md-6" }
})
})}
}
}
span { class: "preloadicon glyphicon glyphicon-remove" aria_hidden: "true" }
}
})
};
#[derive(Props)]
struct ActionButtonProps<'a> {
name: &'static str,
id: &'static str,
onclick: &'a dyn Fn(),
}
fn ActionButton<'a>(cx: Scope<'a, ActionButtonProps<'a>>) -> Element {
rsx!(cx, div { class: "col-sm-6 smallpad"
button { class:"btn btn-primary btn-block", r#type: "button", id: "{cx.props.id}", onclick: move |_| (cx.props.onclick)(),
"{cx.props.name}"
}
})
}
static ADJECTIVES: &[&str] = &[
"pretty",
"large",
"big",
"small",
"tall",
"short",
"long",
"handsome",
"plain",
"quaint",
"clean",
"elegant",
"easy",
"angry",
"crazy",
"helpful",
"mushy",
"odd",
"unsightly",
"adorable",
"important",
"inexpensive",
"cheap",
"expensive",
"fancy",
];
static COLOURS: &[&str] = &[
"red", "yellow", "blue", "green", "pink", "brown", "purple", "brown", "white", "black",
"orange",
];
static NOUNS: &[&str] = &[
"table", "chair", "house", "bbq", "desk", "car", "pony", "cookie", "sandwich", "burger",
"pizza", "mouse", "keyboard",
];
-44
View File
@@ -1,44 +0,0 @@
//! Example: README.md showcase
//!
//! The example from the README.md.
use dioxus::prelude::*;
use dioxus_core as dioxus;
use dioxus_core_macro::*;
use dioxus_hooks::use_state;
use dioxus_html as dioxus_elements;
fn main() {
wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
dioxus_web::launch(App);
}
static App: Component = |cx| {
let show = use_state(&cx, || true);
let inner = match *show {
true => {
rsx!( div {
"hello world"
})
}
false => {
rsx!( div {
// h1 {
"bello world"
// }
})
}
};
rsx!(cx, div {
button {
"toggle"
onclick: move |_| {
let cur = *show;
show.set(!cur);
}
}
{inner}
})
};
-41
View File
@@ -1,41 +0,0 @@
#![allow(non_upper_case_globals)]
//! Example: README.md showcase
//!
//! The example from the README.md.
use dioxus::prelude::*;
use dioxus_core as dioxus;
use dioxus_core_macro::*;
use dioxus_html as dioxus_elements;
use dioxus_web;
fn main() {
dioxus_web::launch(App);
}
static App: Component = |cx| {
todo!("suspense is broken")
// let doggo = suspend(|| async move {
// #[derive(serde::Deserialize)]
// struct Doggo {
// message: String,
// }
// let src = reqwest::get("https://dog.ceo/api/breeds/image/random")
// .await
// .expect("Failed to fetch doggo")
// .json::<Doggo>()
// .await
// .expect("Failed to parse doggo")
// .message;
// rsx!(cx, img { src: "{src}" })
// });
// rsx!(cx, div {
// h1 {"One doggo coming right up"}
// button { onclick: move |_| cx.needs_update(), "Get a new doggo" }
// {doggo}
// })
};
+9 -9
View File
@@ -46,7 +46,7 @@
//! dioxus::desktop::launch(app); //! dioxus::desktop::launch(app);
//! } //! }
//! //!
//! fn app(cx: Scope<()>) -> Element { //! fn app(cx: Scope) -> Element {
//! cx.render(rsx!("hello world!")) //! cx.render(rsx!("hello world!"))
//! } //! }
//! ``` //! ```
@@ -57,7 +57,7 @@
//! For components with no explicit properties, we can use the `()` type. In Dioxus, all properties are memoized by default! //! For components with no explicit properties, we can use the `()` type. In Dioxus, all properties are memoized by default!
//! //!
//! ```rust //! ```rust
//! fn App(cx: Scope<()>) -> Element { //! fn App(cx: Scope) -> Element {
//! cx.render(rsx!( //! cx.render(rsx!(
//! Header { //! Header {
//! title: "My App", //! title: "My App",
@@ -91,7 +91,7 @@
//! While components are reusable forms of UI elements, hooks are reusable forms of logic. All hooks start with `use_`. We can use hooks to declare state. //! While components are reusable forms of UI elements, hooks are reusable forms of logic. All hooks start with `use_`. We can use hooks to declare state.
//! //!
//! ```rust //! ```rust
//! fn app(cx: Scope<()>) -> Element { //! fn app(cx: Scope) -> Element {
//! let name = use_state(&cx, || "world"); //! let name = use_state(&cx, || "world");
//! //!
//! rsx!(cx, "hello {name}!") //! rsx!(cx, "hello {name}!")
@@ -137,7 +137,7 @@
//! dioxus::desktop::launch(App); //! dioxus::desktop::launch(App);
//! } //! }
//! //!
//! fn App(cx: Scope<()>) -> Element { //! fn App(cx: Scope) -> Element {
//! let mut count = use_state(&cx, || 0); //! let mut count = use_state(&cx, || 0);
//! //!
//! cx.render(rsx!( //! cx.render(rsx!(
@@ -187,20 +187,20 @@ pub use dioxus_core as core;
#[cfg(feature = "hooks")] #[cfg(feature = "hooks")]
pub use dioxus_hooks as hooks; pub use dioxus_hooks as hooks;
#[cfg(feature = "router")]
pub use dioxus_router as router;
#[cfg(feature = "ssr")] #[cfg(feature = "ssr")]
pub use dioxus_ssr as ssr; pub use dioxus_ssr as ssr;
#[cfg(feature = "web")] #[cfg(feature = "web")]
pub use dioxus_web as web; pub use dioxus_web as web;
#[cfg(feature = "mobile")]
pub use dioxus_mobile as mobile;
#[cfg(feature = "desktop")] #[cfg(feature = "desktop")]
pub use dioxus_desktop as desktop; pub use dioxus_desktop as desktop;
#[cfg(feature = "router")] // #[cfg(feature = "mobile")]
pub use dioxus_router as router; // pub use dioxus_mobile as mobile;
pub mod events { pub mod events {
#[cfg(feature = "html")] #[cfg(feature = "html")]