Merge pull request #51 from DioxusLabs/jk/arbitrary_expressions
Jk/arbitrary expressions
This commit is contained in:
65 files changed
+1028
-1422
No files matched your search
+12
-10
@@ -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"
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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"
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
}
|
||||||
@@ -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
@@ -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}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
))
|
))
|
||||||
};
|
}
|
||||||
@@ -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}"}))
|
||||||
))
|
))
|
||||||
};
|
};
|
||||||
@@ -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 +0,0 @@
|
|||||||
fn main() {}
|
|
||||||
@@ -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
@@ -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) {
|
||||||
|
|||||||
@@ -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}"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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!" }
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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 {
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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" }
|
||||||
|
|||||||
@@ -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!" }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
}
|
||||||
@@ -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",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -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"] }
|
||||||
|
|||||||
@@ -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),*
|
||||||
|
|||||||
@@ -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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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>()?)
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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"
|
||||||
|
|||||||
@@ -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" })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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! {
|
||||||
|
|||||||
@@ -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! {
|
||||||
|
|||||||
@@ -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>;
|
||||||
|
}
|
||||||
@@ -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}"})
|
||||||
/// };
|
/// };
|
||||||
/// ```
|
/// ```
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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! {
|
||||||
|
|||||||
@@ -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())
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -37,4 +37,3 @@ tokio_runtime = ["tokio"]
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
dioxus-hooks = { path = "../hooks" }
|
dioxus-hooks = { path = "../hooks" }
|
||||||
simple_logger = "1.13.0"
|
|
||||||
@@ -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,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
|
|
||||||
|
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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"]
|
||||||
@@ -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" }
|
||||||
|
|||||||
@@ -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"
|
|
||||||
@@ -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();
|
||||||
|
|||||||
@@ -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}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -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
@@ -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",
|
|
||||||
# ] }
|
|
||||||
@@ -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!" }
|
|
||||||
})
|
|
||||||
};
|
|
||||||
@@ -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",
|
|
||||||
];
|
|
||||||
@@ -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}
|
|
||||||
})
|
|
||||||
};
|
|
||||||
@@ -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
@@ -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")]
|
||||||
|
|||||||
Reference in new issue
Block a user