docs: remove all usages of static closure syntax and update readme
This commit is contained in:
1 parent
d1560450ba
commit
cafb7df736
66 files changed
+792
-2019
No files matched your search
@@ -22,19 +22,24 @@
|
|||||||
alt="docs.rs docs" />
|
alt="docs.rs docs" />
|
||||||
</a>
|
</a>
|
||||||
<!-- CI -->
|
<!-- CI -->
|
||||||
<a href="https://github.com/jkelleyrtp/dioxus/actions">
|
<!-- <a href="https://github.com/jkelleyrtp/dioxus/actions">
|
||||||
<img src="https://github.com/dioxuslabs/dioxus/actions/workflows/main.yml/badge.svg"
|
<img src="https://github.com/dioxuslabs/dioxus/actions/workflows/main.yml/badge.svg"
|
||||||
alt="CI status" />
|
alt="CI status" />
|
||||||
|
</a> -->
|
||||||
|
<!--Awesome -->
|
||||||
|
<a href="https://github.com/dioxuslabs/awesome-dioxus">
|
||||||
|
<img src="https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg"
|
||||||
|
alt="Awesome Page" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<h3>
|
<h3>
|
||||||
<a href="https://dioxuslabs.com/guide"> Guide </a>
|
|
||||||
<span> | </span>
|
|
||||||
<a href="https://dioxuslabs.com"> Website </a>
|
<a href="https://dioxuslabs.com"> Website </a>
|
||||||
<span> | </span>
|
<span> | </span>
|
||||||
<a href="https://github.com/DioxusLabs/awesome-dioxus"> Examples </a>
|
<a href="https://dioxuslabs.com/guide"> Guide </a>
|
||||||
|
<span> | </span>
|
||||||
|
<a href="https://github.com/DioxusLabs/example-projects"> Examples </a>
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -66,6 +71,15 @@ If you know React, then you already know Dioxus.
|
|||||||
- Multi-channel asynchronous scheduler for first-class async support.
|
- Multi-channel asynchronous scheduler for first-class async support.
|
||||||
- And more! Read the [full release post here](https://dioxuslabs.com/).
|
- And more! Read the [full release post here](https://dioxuslabs.com/).
|
||||||
|
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
All examples in this repo are desktop apps. To run an example, simply clone this repo and use cargo with the `desktop` feature enabled. For SSR examples, you might need to enable SSR instead.
|
||||||
|
|
||||||
|
```
|
||||||
|
cargo run --features desktop --example EXAMPLE
|
||||||
|
```
|
||||||
|
|
||||||
## Get Started with...
|
## Get Started with...
|
||||||
|
|
||||||
<table style="width:100%" align="center">
|
<table style="width:100%" align="center">
|
||||||
@@ -80,8 +94,7 @@ If you know React, then you already know Dioxus.
|
|||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
|
||||||
|
## Example Projects:
|
||||||
## Examples Projects:
|
|
||||||
|
|
||||||
| File Navigator (Desktop) | WiFi scanner (Desktop) | TodoMVC (All platforms) | E-commerce w/ Tailwind (Liveview) |
|
| File Navigator (Desktop) | WiFi scanner (Desktop) | TodoMVC (All platforms) | E-commerce w/ Tailwind (Liveview) |
|
||||||
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
@@ -90,9 +103,6 @@ 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?
|
||||||
|
|
||||||
|
|||||||
@@ -81,11 +81,6 @@ fn App(cx: Scope)-> Element {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
This syntax even enables us to write a one-line component:
|
|
||||||
```rust
|
|
||||||
static App: Component = |cx| rsx!(cx, "hello world!");
|
|
||||||
```
|
|
||||||
|
|
||||||
Alternatively, for match statements, we can just return the builder itself and pass it into a final, single call to `cx.render`:
|
Alternatively, for match statements, we can just return the builder itself and pass it into a final, single call to `cx.render`:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
|||||||
@@ -132,12 +132,6 @@ fn App(cx: Scope) -> Element {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Writing `fn App(cx: Scope) -> Element {` might become tedious. Rust will also let you write functions as static closures, but these types of Components cannot have props that borrow data.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
static App: Component = |cx| cx.render(rsx!(div { "Hello, world!" }));
|
|
||||||
```
|
|
||||||
|
|
||||||
### What is this `Scope` object?
|
### What is this `Scope` object?
|
||||||
|
|
||||||
Coming from React, the `Scope` object might be confusing. In React, you'll want to store data between renders with hooks. However, hooks rely on global variables which make them difficult to integrate in multi-tenant systems like server-rendering.
|
Coming from React, the `Scope` object might be confusing. In React, you'll want to store data between renders with hooks. However, hooks rely on global variables which make them difficult to integrate in multi-tenant systems like server-rendering.
|
||||||
|
|||||||
+1
-1
@@ -115,7 +115,7 @@ fn App(cx: Scope) -> Element {
|
|||||||
|
|
||||||
Tiny components:
|
Tiny components:
|
||||||
```rust
|
```rust
|
||||||
static App: Component = |cx, _| rsx!(cx, div {"hello world!"});
|
static App: Component = |cx| rsx!(cx, div {"hello world!"});
|
||||||
```
|
```
|
||||||
|
|
||||||
Borrowed prop contents:
|
Borrowed prop contents:
|
||||||
|
|||||||
+23
-18
@@ -17,27 +17,32 @@ fn app(cx: Scope) -> Element {
|
|||||||
|
|
||||||
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 || {
|
||||||
loop {
|
//
|
||||||
TimeoutFuture::new(250).await;
|
async move {
|
||||||
*async_count.modify() += dir;
|
loop {
|
||||||
|
TimeoutFuture::new(250).await;
|
||||||
|
// *async_count.modify() += dir;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
rsx!(cx, div {
|
cx.render(rsx! {
|
||||||
h1 {"count is {count}"}
|
div {
|
||||||
button { onclick: move |_| task.stop(),
|
h1 {"count is {count}"}
|
||||||
"Stop counting"
|
button { onclick: move |_| task.stop(),
|
||||||
}
|
"Stop counting"
|
||||||
button { onclick: move |_| task.resume(),
|
}
|
||||||
"Start counting"
|
button { onclick: move |_| task.resume(),
|
||||||
}
|
"Start counting"
|
||||||
button {
|
}
|
||||||
onclick: move |_| {
|
button {
|
||||||
*direction.modify() *= -1;
|
onclick: move |_| {
|
||||||
task.restart();
|
*direction.modify() *= -1;
|
||||||
},
|
task.restart();
|
||||||
"Switch counting direcion"
|
},
|
||||||
|
"Switch counting direcion"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -17,10 +17,10 @@ and is proven to be safe with MIRI.
|
|||||||
use dioxus::prelude::*;
|
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 text = cx.use_hook(|_| vec![String::from("abc=def")]);
|
let text = cx.use_hook(|_| vec![String::from("abc=def")]);
|
||||||
|
|
||||||
let first = text.get_mut(0).unwrap();
|
let first = text.get_mut(0).unwrap();
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ use dioxus::prelude::*;
|
|||||||
use separator::Separatable;
|
use separator::Separatable;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// dioxus::desktop::launch(app);
|
dioxus::desktop::launch(app);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn app(cx: Scope) -> Element {
|
fn app(cx: Scope) -> Element {
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
use dioxus_core::prelude::*;
|
|
||||||
use dioxus_core_macro::format_args_f;
|
|
||||||
use dioxus_core_macro::rsx;
|
|
||||||
use dioxus_html as dioxus_elements;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let mut dom = VirtualDom::new(EXAMPLE);
|
|
||||||
dom.rebuild();
|
|
||||||
println!("{}", dom);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub static EXAMPLE: Component = |cx| {
|
|
||||||
let list = (0..10).map(|_f| {
|
|
||||||
rsx! {
|
|
||||||
"{_f}"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
cx.render(Some(LazyNodes::new(move |cx| {
|
|
||||||
cx.raw_element(
|
|
||||||
"div",
|
|
||||||
None,
|
|
||||||
[],
|
|
||||||
[],
|
|
||||||
[
|
|
||||||
cx.text(format_args!("hello")),
|
|
||||||
cx.text(format_args!("hello")),
|
|
||||||
cx.fragment_from_iter(list),
|
|
||||||
],
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
})))
|
|
||||||
};
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
use bumpalo::Bump;
|
|
||||||
|
|
||||||
fn main() {}
|
|
||||||
|
|
||||||
fn build(factory: Factory) {
|
|
||||||
factory.text();
|
|
||||||
div::new(factory)
|
|
||||||
.r#class()
|
|
||||||
.r#tag()
|
|
||||||
.r#type()
|
|
||||||
.add_children()
|
|
||||||
.iter_children()
|
|
||||||
.finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// # The `div` element
|
|
||||||
///
|
|
||||||
///
|
|
||||||
/// The <div> HTML element is the generic container for flow content. It has no effect on the content or layout until
|
|
||||||
/// styled in some way using CSS (e.g. styling is directly applied to it, or some kind of layout model like Flexbox is
|
|
||||||
/// applied to its parent element).
|
|
||||||
///
|
|
||||||
/// As a "pure" container, the <div> element does not inherently represent anything. Instead, it's used to group content
|
|
||||||
/// so it can be easily styled using the class or id attributes, marking a section of a document as being written in a
|
|
||||||
/// different language (using the lang attribute), and so on.
|
|
||||||
///
|
|
||||||
/// ## Usage
|
|
||||||
/// ```
|
|
||||||
/// rsx!{
|
|
||||||
/// div { class: "tall", id: "unique id"
|
|
||||||
/// h1 {}
|
|
||||||
/// p {}
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// ## Specifications
|
|
||||||
/// - Content categories: Flow content, palpable content.
|
|
||||||
/// - Permitted content: Flow content.
|
|
||||||
/// - Permitted parents: Any element that accepts flow content.
|
|
||||||
#[allow(non_camel_case_types)]
|
|
||||||
struct div {}
|
|
||||||
|
|
||||||
struct h1 {}
|
|
||||||
|
|
||||||
struct h2 {}
|
|
||||||
|
|
||||||
trait BasicElement: Sized {
|
|
||||||
const TagName: &'static str;
|
|
||||||
fn get_bump(&self) -> &Bump;
|
|
||||||
fn new(factory: Factory) -> Self;
|
|
||||||
fn add_children(self) -> Self {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
fn iter_children(self) -> Self {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
fn finish(self) -> Self {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BasicElement for div {
|
|
||||||
const TagName: &'static str = "div";
|
|
||||||
|
|
||||||
fn get_bump(&self) -> &Bump {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new(_factory: Factory) -> Self {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl div {
|
|
||||||
fn class(self) -> Self {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
fn tag(self) -> Self {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
fn r#type(self) -> Self {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
struct Factory<'a> {
|
|
||||||
bump: &'a bumpalo::Bump,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Factory<'a> {
|
|
||||||
fn text(&self) -> &str {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn new_el(&'a self, tag: &'static str) -> ElementBuilder<'a> {
|
|
||||||
ElementBuilder {
|
|
||||||
bump: self.bump,
|
|
||||||
tag,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ElementBuilder<'a> {
|
|
||||||
tag: &'static str,
|
|
||||||
bump: &'a bumpalo::Bump,
|
|
||||||
}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
#![allow(non_snake_case)]
|
|
||||||
|
|
||||||
use dioxus::component::Scope;
|
|
||||||
use dioxus::events::on::MouseEvent;
|
|
||||||
use dioxus_core as dioxus;
|
|
||||||
use dioxus_core::prelude::*;
|
|
||||||
use dioxus_core_macro::*;
|
|
||||||
use dioxus_html as dioxus_elements;
|
|
||||||
use rand::prelude::*;
|
|
||||||
use std::fmt::Display;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let mut dom = VirtualDom::new(App);
|
|
||||||
let g = dom.rebuild();
|
|
||||||
assert!(g.edits.len() > 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn App((cx, props): Scope) -> Element {
|
|
||||||
let mut rng = SmallRng::from_entropy();
|
|
||||||
let rows = (0..10_000_usize).map(|f| {
|
|
||||||
let label = Label::new(&mut rng);
|
|
||||||
rsx! {
|
|
||||||
Row {
|
|
||||||
row_id: f,
|
|
||||||
label: label
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
cx.render(rsx! {
|
|
||||||
table {
|
|
||||||
tbody {
|
|
||||||
{rows}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(PartialEq, Props)]
|
|
||||||
struct RowProps {
|
|
||||||
row_id: usize,
|
|
||||||
label: Label,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn Row<'a>((cx, props): Scope<'a, RowProps>) -> Element<'a> {
|
|
||||||
let handler = move |evt: MouseEvent| {
|
|
||||||
let g = evt.button;
|
|
||||||
};
|
|
||||||
cx.render(rsx! {
|
|
||||||
tr {
|
|
||||||
// td { class:"col-md-1", "{cx.props.row_id}" }
|
|
||||||
// td { class:"col-md-1", onclick: move |_| { /* run onselect */ }
|
|
||||||
// a { class: "lbl", "{cx.props.label}" }
|
|
||||||
// }
|
|
||||||
// td { class: "col-md-1"
|
|
||||||
// a { class: "remove", onclick: {handler}
|
|
||||||
// span { class: "glyphicon glyphicon-remove remove" aria_hidden: "true" }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// td { class: "col-md-6" }
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(PartialEq)]
|
|
||||||
struct Label([&'static str; 3]);
|
|
||||||
|
|
||||||
impl Label {
|
|
||||||
fn new(rng: &mut SmallRng) -> Self {
|
|
||||||
Label([
|
|
||||||
ADJECTIVES.choose(rng).unwrap(),
|
|
||||||
COLOURS.choose(rng).unwrap(),
|
|
||||||
NOUNS.choose(rng).unwrap(),
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Display for Label {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{} {} {}", self.0[0], self.0[1], self.0[2])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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",
|
|
||||||
];
|
|
||||||
@@ -32,16 +32,20 @@ use dioxus::prelude::*;
|
|||||||
struct NoKeysProps {
|
struct NoKeysProps {
|
||||||
data: std::collections::HashMap<u32, String>,
|
data: std::collections::HashMap<u32, String>,
|
||||||
}
|
}
|
||||||
static AntipatternNoKeys: Component<NoKeysProps> = |cx| {
|
fn AntipatternNoKeys(cx: Scope<NoKeysProps>) -> Element {
|
||||||
// WRONG: Make sure to add keys!
|
// WRONG: Make sure to add keys!
|
||||||
rsx!(cx, ul {
|
cx.render(rsx! {
|
||||||
{cx.props.data.iter().map(|(k, v)| rsx!(li { "List item: {v}" }))}
|
ul {
|
||||||
|
cx.props.data.iter().map(|(k, v)| rsx!(li { "List item: {v}" }))
|
||||||
|
}
|
||||||
});
|
});
|
||||||
// RIGHT: Like this:
|
// RIGHT: Like this:
|
||||||
rsx!(cx, ul {
|
cx.render(rsx! {
|
||||||
{cx.props.data.iter().map(|(k, v)| rsx!(li { key: "{k}", "List item: {v}" }))}
|
ul {
|
||||||
|
cx.props.data.iter().map(|(k, v)| rsx!(li { key: "{k}", "List item: {v}" }))
|
||||||
|
}
|
||||||
})
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
/// Antipattern: Deeply nested fragments
|
/// Antipattern: Deeply nested fragments
|
||||||
/// ------------------------------------
|
/// ------------------------------------
|
||||||
@@ -54,9 +58,9 @@ static AntipatternNoKeys: Component<NoKeysProps> = |cx| {
|
|||||||
///
|
///
|
||||||
/// Only Component and Fragment nodes are susceptible to this issue. Dioxus mitigates this with components by providing
|
/// Only Component and Fragment nodes are susceptible to this issue. Dioxus mitigates this with components by providing
|
||||||
/// an API for registering shared state without the ContextProvider pattern.
|
/// an API for registering shared state without the ContextProvider pattern.
|
||||||
static AntipatternNestedFragments: Component = |cx| {
|
fn AntipatternNestedFragments(cx: Scope<()>) -> Element {
|
||||||
// Try to avoid heavily nesting fragments
|
// Try to avoid heavily nesting fragments
|
||||||
rsx!(cx,
|
cx.render(rsx!(
|
||||||
Fragment {
|
Fragment {
|
||||||
Fragment {
|
Fragment {
|
||||||
Fragment {
|
Fragment {
|
||||||
@@ -68,8 +72,8 @@ static AntipatternNestedFragments: Component = |cx| {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
))
|
||||||
};
|
}
|
||||||
|
|
||||||
/// Antipattern: Using state after it's been updated
|
/// Antipattern: Using state after it's been updated
|
||||||
/// -----------------------------------------------
|
/// -----------------------------------------------
|
||||||
@@ -82,13 +86,13 @@ static AntipatternNestedFragments: Component = |cx| {
|
|||||||
/// However, calling set_state will *not* update the current version of state in the component. This should be easy to
|
/// However, calling set_state will *not* update the current version of state in the component. This should be easy to
|
||||||
/// recognize from the function signature, but Dioxus will not update the "live" version of state. Calling `set_state`
|
/// recognize from the function signature, but Dioxus will not update the "live" version of state. Calling `set_state`
|
||||||
/// merely places a new value in the queue and schedules the component for a future update.
|
/// merely places a new value in the queue and schedules the component for a future update.
|
||||||
static AntipatternRelyingOnSetState: Component = |cx| {
|
fn AntipatternRelyingOnSetState(cx: Scope) -> Element {
|
||||||
let (state, set_state) = use_state(&cx, || "Hello world").classic();
|
let (state, set_state) = use_state(&cx, || "Hello world").classic();
|
||||||
set_state("New state");
|
set_state("New state");
|
||||||
// This will return false! `state` will *still* be "Hello world"
|
// This will return false! `state` will *still* be "Hello world"
|
||||||
assert!(state == &"New state");
|
assert!(state == &"New state");
|
||||||
todo!()
|
todo!()
|
||||||
};
|
}
|
||||||
|
|
||||||
/// Antipattern: Capitalization
|
/// Antipattern: Capitalization
|
||||||
/// ---------------------------
|
/// ---------------------------
|
||||||
@@ -120,16 +124,16 @@ static antipattern_component: Component = |cx| todo!();
|
|||||||
struct MisuedHooksProps {
|
struct MisuedHooksProps {
|
||||||
should_render_state: bool,
|
should_render_state: bool,
|
||||||
}
|
}
|
||||||
static AntipatternMisusedHooks: Component<MisuedHooksProps> = |cx| {
|
fn AntipatternMisusedHooks(cx: Scope<MisuedHooksProps>) -> Element {
|
||||||
if props.should_render_state {
|
if props.should_render_state {
|
||||||
// do not place a hook in the conditional!
|
// do not place a hook in the conditional!
|
||||||
// prefer to move it out of the conditional
|
// prefer to move it out of the conditional
|
||||||
let (state, set_state) = use_state(&cx, || "hello world").classic();
|
let (state, set_state) = use_state(&cx, || "hello world").classic();
|
||||||
rsx!(cx, div { "{state}" })
|
cx.render(rsx!(div { "{state}" }))
|
||||||
} else {
|
} else {
|
||||||
rsx!(cx, div { "Not rendering state" })
|
cx.render(rsx!(div { "Not rendering state" }))
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
/// Antipattern: Downcasting refs and panicking
|
/// Antipattern: Downcasting refs and panicking
|
||||||
/// ------------------------------------------
|
/// ------------------------------------------
|
||||||
@@ -173,9 +177,9 @@ static _example: Component = |cx| todo!();
|
|||||||
/// libraries.
|
/// libraries.
|
||||||
static __example: Component = |cx| todo!();
|
static __example: Component = |cx| todo!();
|
||||||
|
|
||||||
pub static Example: Component = |cx| {
|
fn Example(cx: Scope) -> Element {
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
AntipatternNoKeys { data: std::collections::HashMap::new() }
|
AntipatternNoKeys { data: std::collections::HashMap::new() }
|
||||||
AntipatternNestedFragments {}
|
AntipatternNestedFragments {}
|
||||||
})
|
})
|
||||||
};
|
}
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
pub static Example: Component = |cx| {
|
pub fn Example(cx: Scope) -> Element {
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
div {
|
div {
|
||||||
Greeting {
|
Greeting {
|
||||||
@@ -18,20 +18,21 @@ pub static Example: Component = |cx| {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(PartialEq, Props)]
|
|
||||||
struct GreetingProps {
|
|
||||||
name: &'static str,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static Greeting: Component<GreetingProps> = |cx| {
|
#[derive(PartialEq, Props)]
|
||||||
|
struct GreetingProps<'a> {
|
||||||
|
name: &'static str,
|
||||||
|
children: Element<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn Greeting<'a>(cx: Scope<'a, GreetingProps<'a>>) -> Element {
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
div {
|
div {
|
||||||
h1 { "Hello, {cx.props.name}!" }
|
h1 { "Hello, {cx.props.name}!" }
|
||||||
p { "Welcome to the Dioxus framework" }
|
p { "Welcome to the Dioxus framework" }
|
||||||
br {}
|
br {}
|
||||||
{cx.children()}
|
&cx.props.children
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
}
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
pub static Example: Component = |cx| {
|
fn Example(cx: Scope) -> Element {
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
div {
|
div {
|
||||||
Banner {
|
Banner {
|
||||||
@@ -29,16 +29,19 @@ pub static Example: Component = |cx| {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
pub static Banner: Component = |cx| {
|
#[derive(Props)]
|
||||||
|
struct BannerProps {
|
||||||
|
children: Element<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn Banner(cx: Scope) -> Element {
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
div {
|
div {
|
||||||
h1 { "This is a great banner!" }
|
h1 { "This is a great banner!" }
|
||||||
div { class: "content"
|
div { class: "content", &cx.props.children }
|
||||||
{cx.children()}
|
|
||||||
}
|
|
||||||
footer { "Wow, what a great footer" }
|
footer { "Wow, what a great footer" }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
}
|
||||||
@@ -16,15 +16,15 @@ use dioxus::prelude::*;
|
|||||||
pub struct MyProps {
|
pub struct MyProps {
|
||||||
should_show: bool,
|
should_show: bool,
|
||||||
}
|
}
|
||||||
pub static Example0: Component<MyProps> = |cx| {
|
pub fn Example0(cx: Scope<MyProps>) -> Element {
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
div {
|
div {
|
||||||
{cx.props.should_show.then(|| rsx!{
|
cx.props.should_show.then(|| rsx!{
|
||||||
h1 { "showing the title!" }
|
h1 { "showing the title!" }
|
||||||
})}
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
// Convert a boolean conditional into an either/or
|
// Convert a boolean conditional into an either/or
|
||||||
// Because rsx! is lazy (produces a closure), we cannot use it in two branch arms. To use it in matching/branching, we
|
// Because rsx! is lazy (produces a closure), we cannot use it in two branch arms. To use it in matching/branching, we
|
||||||
@@ -39,31 +39,29 @@ pub static Example0: Component<MyProps> = |cx| {
|
|||||||
pub struct MyProps1 {
|
pub struct MyProps1 {
|
||||||
should_show: bool,
|
should_show: bool,
|
||||||
}
|
}
|
||||||
pub static Example1: Component<MyProps1> = |cx| {
|
pub fn Example1(cx: Scope<MyProps1>) -> Element {
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
div {
|
div {
|
||||||
// With matching
|
// With matching
|
||||||
{match props.should_show {
|
match props.should_show {
|
||||||
true => cx.render(rsx!(div {"it is true!"})),
|
true => cx.render(rsx!(div {"it is true!"})),
|
||||||
false => rsx!(cx, div {"it is false!"}),
|
false => rsx!(cx, div {"it is false!"}),
|
||||||
}}
|
},
|
||||||
|
|
||||||
// or with just regular conditions
|
// or with just regular conditions
|
||||||
{if props.should_show {
|
if props.should_show {
|
||||||
rsx!(cx, div {"it is true!"})
|
rsx!(cx, div {"it is true!"})
|
||||||
} else {
|
} else {
|
||||||
rsx!(cx, div {"it is false!"})
|
rsx!(cx, div {"it is false!"})
|
||||||
}}
|
},
|
||||||
|
|
||||||
// or with optional chaining
|
// or with optional chaining
|
||||||
{
|
props.should_show
|
||||||
props.should_show
|
|
||||||
.then(|| rsx!(cx, div {"it is true!"}))
|
.then(|| rsx!(cx, div {"it is true!"}))
|
||||||
.unwrap_or_else(|| rsx!(cx, div {"it is false!"}))
|
.unwrap_or_else(|| rsx!(cx, div {"it is false!"}))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
/// Matching can be expanded
|
/// Matching can be expanded
|
||||||
|
|
||||||
@@ -77,19 +75,19 @@ pub enum Color {
|
|||||||
pub struct MyProps2 {
|
pub struct MyProps2 {
|
||||||
color: Color,
|
color: Color,
|
||||||
}
|
}
|
||||||
pub static Example2: Component<MyProps2> = |cx| {
|
pub fn Example2(cx: Scope<MyProps2>) -> Element {
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
div {
|
div {
|
||||||
{match props.color {
|
match props.color {
|
||||||
Color::Green => rsx!(cx, div {"it is Green!"}),
|
Color::Green => rsx!(cx, div {"it is Green!"}),
|
||||||
Color::Yellow => rsx!(cx, div {"it is Yellow!"}),
|
Color::Yellow => rsx!(cx, div {"it is Yellow!"}),
|
||||||
Color::Red => rsx!(cx, div {"it is Red!"}),
|
Color::Red => rsx!(cx, div {"it is Red!"}),
|
||||||
}}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
pub static Example: Component = |cx| {
|
pub fn Example(cx: Scope<()>) -> Element {
|
||||||
let should_show = use_state(&cx, || false);
|
let should_show = use_state(&cx, || false);
|
||||||
let mut color_index = use_state(&cx, || 0);
|
let mut color_index = use_state(&cx, || 0);
|
||||||
let color = match *color_index % 2 {
|
let color = match *color_index % 2 {
|
||||||
@@ -101,11 +99,11 @@ pub static Example: Component = |cx| {
|
|||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
div {
|
div {
|
||||||
button {
|
button {
|
||||||
onclick: move |_| should_show.set(!*should_show)
|
onclick: move |_| should_show.set(!*should_show),
|
||||||
"click to toggle showing the examples"
|
"click to toggle showing the examples"
|
||||||
}
|
}
|
||||||
button {
|
button {
|
||||||
onclick: move |_| color_index += 1
|
onclick: move |_| color_index += 1,
|
||||||
"click to for the enxt color"
|
"click to for the enxt color"
|
||||||
}
|
}
|
||||||
Example0 { should_show: *should_show }
|
Example0 { should_show: *should_show }
|
||||||
@@ -113,4 +111,4 @@ pub static Example: Component = |cx| {
|
|||||||
Example2 { color: color }
|
Example2 { color: color }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
}
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
fn main() {}
|
fn main() {}
|
||||||
|
|
||||||
pub static Example: Component = |cx| {
|
pub fn Example(cx: Scope) -> Element {
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
div {
|
div {
|
||||||
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
// A controlled component:
|
// A controlled component:
|
||||||
static ControlledSelect: Component = |cx| {
|
pub fn ControlledSelect(cx: Scope) -> Element {
|
||||||
let value = use_state(&cx, || String::from("Grapefruit"));
|
let value = use_state(&cx, || String::from("Grapefruit"));
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
select { value: "{value}", onchange: move |evt| value.set(evt.value()),
|
select { value: "{value}", onchange: move |evt| value.set(evt.value()),
|
||||||
@@ -20,10 +20,10 @@ static ControlledSelect: Component = |cx| {
|
|||||||
option { value: "Mango", "Mango"}
|
option { value: "Mango", "Mango"}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
// TODO - how do uncontrolled things work?
|
// TODO - how do uncontrolled things work?
|
||||||
static UncontrolledSelect: Component = |cx| {
|
pub fn UncontrolledSelect(cx: Scope) -> Element {
|
||||||
let value = use_state(&cx, || String::new());
|
let value = use_state(&cx, || String::new());
|
||||||
|
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
@@ -34,4 +34,4 @@ static UncontrolledSelect: Component = |cx| {
|
|||||||
option { value: "Mango", "Mango"}
|
option { value: "Mango", "Mango"}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
}
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
pub static Example: Component = |cx| {
|
pub fn Example(cx: Scope) -> Element {
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
div {
|
div {
|
||||||
custom_element {
|
custom_element {
|
||||||
@@ -19,7 +19,7 @@ pub static Example: Component = |cx| {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
mod dioxus_elements {
|
mod dioxus_elements {
|
||||||
use std::fmt::Arguments;
|
use std::fmt::Arguments;
|
||||||
|
|||||||
@@ -5,4 +5,6 @@
|
|||||||
|
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
pub static Example: Component = |cx| cx.render(rsx! { Fragment {} });
|
pub fn Example(cx: Scope) -> Element {
|
||||||
|
None
|
||||||
|
}
|
||||||
@@ -23,20 +23,20 @@ fn main() {}
|
|||||||
/// This is one way to go about error handling (just toss things away with unwrap).
|
/// This is one way to go about error handling (just toss things away with unwrap).
|
||||||
/// However, if you get it wrong, the whole app will crash.
|
/// However, if you get it wrong, the whole app will crash.
|
||||||
/// This is pretty flimsy.
|
/// This is pretty flimsy.
|
||||||
static App: Component = |cx| {
|
pub fn App(cx: Scope) -> Element {
|
||||||
let data = get_data().unwrap();
|
let data = get_data().unwrap();
|
||||||
cx.render(rsx!( div { "{data}" } ))
|
cx.render(rsx!( div { "{data}" } ))
|
||||||
};
|
}
|
||||||
|
|
||||||
/// This is a pretty verbose way of error handling
|
/// This is a pretty verbose way of error handling
|
||||||
/// However, it's still pretty good since we don't panic, just fail to render anything
|
/// However, it's still pretty good since we don't panic, just fail to render anything
|
||||||
static App1: Component = |cx| {
|
pub fn App1(cx: Scope) -> Element {
|
||||||
let data = match get_data() {
|
let data = match get_data() {
|
||||||
Some(data) => data,
|
Some(data) => data,
|
||||||
None => return None,
|
None => return None,
|
||||||
};
|
};
|
||||||
cx.render(rsx!( div { "{data}" } ))
|
cx.render(rsx!( div { "{data}" } ))
|
||||||
};
|
}
|
||||||
|
|
||||||
/// This is an even better form of error handling.
|
/// This is an even better form of error handling.
|
||||||
/// However, it _does_ make the component go blank, which might not be desirable.
|
/// However, it _does_ make the component go blank, which might not be desirable.
|
||||||
@@ -46,33 +46,37 @@ static App1: Component = |cx| {
|
|||||||
/// a user is logged in.
|
/// a user is logged in.
|
||||||
///
|
///
|
||||||
/// Dioxus will throw an error in the console if the None-path is ever taken.
|
/// Dioxus will throw an error in the console if the None-path is ever taken.
|
||||||
static App2: Component = |cx| {
|
pub fn App2(cx: Scope) -> Element {
|
||||||
let data = get_data()?;
|
let data = get_data()?;
|
||||||
cx.render(rsx!( div { "{data}" } ))
|
cx.render(rsx!( div { "{data}" } ))
|
||||||
};
|
}
|
||||||
|
|
||||||
/// This is top-tier error handling since it displays a failure state.
|
/// This is top-tier error handling since it displays a failure state.
|
||||||
///
|
///
|
||||||
/// However, the error is lacking in context.
|
/// However, the error is lacking in context.
|
||||||
static App3: Component = |cx| match get_data() {
|
pub fn App3(cx: Scope) -> Element {
|
||||||
Some(data) => cx.render(rsx!( div { "{data}" } )),
|
match get_data() {
|
||||||
None => cx.render(rsx!( div { "Failed to load data :(" } )),
|
Some(data) => cx.render(rsx!( div { "{data}" } )),
|
||||||
};
|
None => cx.render(rsx!( div { "Failed to load data :(" } )),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// For errors that return results, it's possible to short-circuit the match-based error handling with `.ok()` which converts
|
/// For errors that return results, it's possible to short-circuit the match-based error handling with `.ok()` which converts
|
||||||
/// a Result<T, V> into an Option<T> and lets you abort rendering by early-returning `None`
|
/// a Result<T, V> into an Option<T> and lets you abort rendering by early-returning `None`
|
||||||
static App4: Component = |cx| {
|
pub fn App4(cx: Scope) -> Element {
|
||||||
let data = get_data_err().ok()?;
|
let data = get_data_err().ok()?;
|
||||||
cx.render(rsx!( div { "{data}" } ))
|
cx.render(rsx!( div { "{data}" } ))
|
||||||
};
|
}
|
||||||
|
|
||||||
/// This is great error handling since it displays a failure state... with context!
|
/// This is great error handling since it displays a failure state... with context!
|
||||||
///
|
///
|
||||||
/// Hopefully you'll never need to display a screen like this. It's rather bad taste
|
/// Hopefully you'll never need to display a screen like this. It's rather bad taste
|
||||||
static App5: Component = |cx| match get_data_err() {
|
pub fn App5(cx: Scope) -> Element {
|
||||||
Ok(data) => cx.render(rsx!( div { "{data}" } )),
|
match get_data_err() {
|
||||||
Err(c) => cx.render(rsx!( div { "Failed to load data: {c}" } )),
|
Ok(data) => cx.render(rsx!( div { "{data}" } )),
|
||||||
};
|
Err(c) => cx.render(rsx!( div { "Failed to load data: {c}" } )),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// this fetching function produces "nothing"
|
// this fetching function produces "nothing"
|
||||||
fn get_data() -> Option<String> {
|
fn get_data() -> Option<String> {
|
||||||
|
|||||||
@@ -10,17 +10,25 @@
|
|||||||
|
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
|
pub fn Example(cx: Scope) -> Element {
|
||||||
|
cx.render(rsx! {
|
||||||
|
App1 {}
|
||||||
|
App2 {}
|
||||||
|
App3 {}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Returning multiple elements with rsx! or html!
|
// Returning multiple elements with rsx! or html!
|
||||||
static App1: Component = |cx| {
|
pub fn App1(cx: Scope) -> Element {
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
h1 { }
|
h1 { }
|
||||||
h2 { }
|
h2 { }
|
||||||
h3 { }
|
h3 { }
|
||||||
})
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
// Using the Fragment component
|
// Using the Fragment component
|
||||||
static App2: Component = |cx| {
|
pub fn App2(cx: Scope) -> Element {
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
Fragment {
|
Fragment {
|
||||||
div {}
|
div {}
|
||||||
@@ -28,10 +36,10 @@ static App2: Component = |cx| {
|
|||||||
"asd"
|
"asd"
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
// Using the `fragment` method on the NodeFactory
|
// Using the `fragment` method on the NodeFactory
|
||||||
static App3: Component = |cx| {
|
pub fn App3(cx: Scope) -> Element {
|
||||||
cx.render(LazyNodes::new(move |fac| {
|
cx.render(LazyNodes::new(move |fac| {
|
||||||
fac.fragment_from_iter([
|
fac.fragment_from_iter([
|
||||||
fac.text(format_args!("A")),
|
fac.text(format_args!("A")),
|
||||||
@@ -40,12 +48,4 @@ static App3: Component = |cx| {
|
|||||||
fac.text(format_args!("B")),
|
fac.text(format_args!("B")),
|
||||||
])
|
])
|
||||||
}))
|
}))
|
||||||
};
|
}
|
||||||
|
|
||||||
pub static Example: Component = |cx| {
|
|
||||||
cx.render(rsx! {
|
|
||||||
App1 {}
|
|
||||||
App2 {}
|
|
||||||
App3 {}
|
|
||||||
})
|
|
||||||
};
|
|
||||||
@@ -19,7 +19,7 @@ h1 {color: blue;}
|
|||||||
p {color: red;}
|
p {color: red;}
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
pub static Example: Component = |cx| {
|
pub fn Example(cx: Scope) -> Element {
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
head { style { "{STYLE}" } }
|
head { style { "{STYLE}" } }
|
||||||
body {
|
body {
|
||||||
@@ -27,4 +27,4 @@ pub static Example: Component = |cx| {
|
|||||||
p {"This is a paragraph"}
|
p {"This is a paragraph"}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
}
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
pub static Example: Component = |cx| {
|
pub fn Example(cx: Scope) -> Element {
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
head {
|
head {
|
||||||
style: { background_color: "powderblue" }
|
style: { background_color: "powderblue" }
|
||||||
@@ -24,15 +24,15 @@ pub static Example: Component = |cx| {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
// .... technically the rsx! macro is slightly broken at the moment and allows styles not wrapped in style {}
|
// .... technically the rsx! macro is slightly broken at the moment and allows styles not wrapped in style {}
|
||||||
// I haven't noticed any name collisions yet, and am tentatively leaving this behavior in..
|
// I haven't noticed any name collisions yet, and am tentatively leaving this behavior in..
|
||||||
// Don't rely on it.
|
// Don't rely on it.
|
||||||
static Example2: Component = |cx| {
|
pub fn Example2(cx: Scope) -> Element {
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
div { color: "red"
|
div { color: "red"
|
||||||
"hello world!"
|
"hello world!"
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
}
|
||||||
@@ -15,25 +15,20 @@ use dioxus::prelude::*;
|
|||||||
pub static Example: Component = |cx| {
|
pub static Example: Component = |cx| {
|
||||||
let example_data = use_state(&cx, || 0);
|
let example_data = use_state(&cx, || 0);
|
||||||
|
|
||||||
let v = (0..10).map(|f| {
|
|
||||||
rsx! {
|
|
||||||
li { onclick: move |_| example_data.set(f)
|
|
||||||
"ID: {f}"
|
|
||||||
ul {
|
|
||||||
(0..10).map(|k| rsx!{
|
|
||||||
li {
|
|
||||||
"Sub iterator: {f}.{k}"
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
h3 {"Selected: {example_data}"}
|
h3 {"Selected: {example_data}"}
|
||||||
ul {
|
ul {
|
||||||
{v}
|
(0..10).map(|f| rsx! {
|
||||||
|
li {
|
||||||
|
onclick: move |_| example_data.set(f),
|
||||||
|
"ID: {f}"
|
||||||
|
ul {
|
||||||
|
(0..10).map(|k| rsx!{
|
||||||
|
li { "Sub iterator: {f}.{k}" }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
@@ -7,50 +7,49 @@
|
|||||||
|
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
pub static Example: Component = |cx| {
|
pub fn Example(cx: Scope) -> Element {
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
ButtonList {}
|
ButtonList {}
|
||||||
NonUpdatingEvents {}
|
NonUpdatingEvents {}
|
||||||
DisablePropagation {}
|
DisablePropagation {}
|
||||||
})
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
/// We can use `set_name` in multiple closures; the closures automatically *copy* the reference to set_name.
|
/// We can use `set_name` in multiple closures; the closures automatically *copy* the reference to set_name.
|
||||||
static ButtonList: Component = |cx| {
|
pub fn ButtonList(cx: Scope) -> Element {
|
||||||
let name = use_state(&cx, || "...?");
|
let name = use_state(&cx, || "...?");
|
||||||
|
|
||||||
let names = ["jack", "jill", "john", "jane"]
|
|
||||||
.iter()
|
|
||||||
.map(move |n| rsx!(button { onclick: move |_| name.set(n), "{n}" }));
|
|
||||||
|
|
||||||
cx.render(rsx!(
|
cx.render(rsx!(
|
||||||
div {
|
div {
|
||||||
h1 { "Hello, {name}" }
|
h1 { "Hello, {name}" }
|
||||||
{names}
|
|
||||||
|
["jack", "jill", "john", "jane"]
|
||||||
|
.iter()
|
||||||
|
.map(move |n| rsx!(button { onclick: move |_| name.set(n), "{n}" }))
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
};
|
}
|
||||||
|
|
||||||
/// This shows how listeners may be without a visible change in the display.
|
/// This shows how listeners may be without a visible change in the display.
|
||||||
/// Check the console.
|
/// Check the console.
|
||||||
static NonUpdatingEvents: Component = |cx| {
|
pub fn NonUpdatingEvents(cx: Scope) -> Element {
|
||||||
rsx!(cx, div {
|
cx.render(rsx! {
|
||||||
button {
|
div {
|
||||||
onclick: move |_| log::info!("Did not cause any updates!")
|
button {
|
||||||
"Click me to log!"
|
onclick: move |_| log::info!("Did not cause any updates!"),
|
||||||
|
"Click me to log!"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
static DisablePropagation: Component = |cx| {
|
pub fn DisablePropagation(cx: Scope) -> Element {
|
||||||
rsx!(cx,
|
cx.render(rsx! {
|
||||||
div {
|
div {
|
||||||
onclick: move |_| log::info!("event propagated to the div!")
|
onclick: move |_| log::info!("event propagated to the div!")
|
||||||
button {
|
button {
|
||||||
onclick: move |evt| {
|
onclick: move |evt| log::info!("Button will allow propagation"),
|
||||||
log::info!("Button will allow propagation");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
})
|
||||||
};
|
}
|
||||||
@@ -21,11 +21,11 @@ use dioxus::prelude::*;
|
|||||||
|
|
||||||
// By default, components with no props are always memoized.
|
// By default, components with no props are always memoized.
|
||||||
// A props of () is considered empty.
|
// A props of () is considered empty.
|
||||||
pub static Example: Component = |cx| {
|
pub fn Example(cx: Scope) -> Element {
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
div { "100% memoized!" }
|
div { "100% memoized!" }
|
||||||
})
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
// These props do not borrow any content, and therefore can be safely memoized.
|
// These props do not borrow any content, and therefore can be safely memoized.
|
||||||
// However, the parent *must* create a new string on every render.
|
// However, the parent *must* create a new string on every render.
|
||||||
@@ -35,11 +35,11 @@ pub struct MyProps1 {
|
|||||||
name: String,
|
name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub static Example1: Component<MyProps1> = |cx| {
|
pub fn Example1(cx: Scope<MyProps1>) -> Element {
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
div { "100% memoized! {cx.props.name}" }
|
div { "100% memoized! {cx.props.name}" }
|
||||||
})
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
// These props do not borrow any content, and therefore can be safely memoized.
|
// These props do not borrow any content, and therefore can be safely memoized.
|
||||||
// In contrast with the `String` example, these props use `Rc<str>` which operates similar to strings in JavaScript.
|
// In contrast with the `String` example, these props use `Rc<str>` which operates similar to strings in JavaScript.
|
||||||
@@ -49,11 +49,11 @@ pub struct MyProps2 {
|
|||||||
name: std::rc::Rc<str>,
|
name: std::rc::Rc<str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub static Example2: Component<MyProps2> = |cx| {
|
pub fn Example2(cx: Scope<MyProps2>) -> Element {
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
div { "100% memoized! {cx.props.name}" }
|
div { "100% memoized! {cx.props.name}" }
|
||||||
})
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
// These props *do* borrow any content, and therefore cannot be safely memoized!.
|
// These props *do* borrow any content, and therefore cannot be safely memoized!.
|
||||||
#[derive(PartialEq, Props)]
|
#[derive(PartialEq, Props)]
|
||||||
@@ -61,7 +61,7 @@ pub struct MyProps3<'a> {
|
|||||||
name: &'a str,
|
name: &'a str,
|
||||||
}
|
}
|
||||||
// We need to manually specify a lifetime that ensures props and scope (the component's state) share the same lifetime.
|
// We need to manually specify a lifetime that ensures props and scope (the component's state) share the same lifetime.
|
||||||
// Using the `pub static Example: Component` pattern _will_ specify a lifetime, but that lifetime will be static which might
|
// Using the `pub fn Example(cx: Scope): Component` pattern _will_ specify a lifetime, but that lifetime will be static which might
|
||||||
// not exactly be what you want
|
// not exactly be what you want
|
||||||
fn Example3(cx: Scope<'a, MyProps3<'a>>) -> DomTree {
|
fn Example3(cx: Scope<'a, MyProps3<'a>>) -> DomTree {
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
use dioxus::prelude::*;
|
|
||||||
fn main() {}
|
|
||||||
|
|
||||||
pub static Example: Component = |cx| {
|
|
||||||
let p = 10;
|
|
||||||
|
|
||||||
cx.render(rsx! {
|
|
||||||
div {
|
|
||||||
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
use dioxus::prelude::*;
|
|
||||||
fn main() {}
|
|
||||||
|
|
||||||
pub static Example: Component = |cx| {
|
|
||||||
cx.render(rsx! {
|
|
||||||
div {
|
|
||||||
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
pub static Example: Component = |cx| {
|
pub fn Example(cx: Scope) -> Element {
|
||||||
let props = MyProps {
|
let props = MyProps {
|
||||||
count: 0,
|
count: 0,
|
||||||
live: true,
|
live: true,
|
||||||
@@ -18,22 +18,24 @@ pub static Example: Component = |cx| {
|
|||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
Example1 { ..props, count: 10, div {"child"} }
|
Example1 { ..props, count: 10, div {"child"} }
|
||||||
})
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Props)]
|
#[derive(Props)]
|
||||||
pub struct MyProps {
|
pub struct MyProps<'a> {
|
||||||
count: u32,
|
count: u32,
|
||||||
live: bool,
|
live: bool,
|
||||||
name: &'static str,
|
name: &'static str,
|
||||||
|
children: Element<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub static Example1: Component<MyProps> = |cx, MyProps { count, live, name }| {
|
pub fn Example1(cx: Scope<MyProps>) -> Element {
|
||||||
|
let MyProps { count, live, name } = cx.props;
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
div {
|
div {
|
||||||
h1 { "Hello, {name}"}
|
h1 { "Hello, {name}"}
|
||||||
h3 {"Are we alive? {live}"}
|
h3 {"Are we alive? {live}"}
|
||||||
p {"Count is {count}"}
|
p {"Count is {count}"}
|
||||||
{ cx.children() }
|
&cx.props.children
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
}
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
use dioxus::prelude::*;
|
|
||||||
fn main() {}
|
|
||||||
|
|
||||||
pub static Example: Component = |cx| {
|
|
||||||
cx.render(rsx! {
|
|
||||||
div {
|
|
||||||
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
|
||||||
@@ -14,7 +14,7 @@ struct DogApi {
|
|||||||
}
|
}
|
||||||
const ENDPOINT: &str = "https://dog.ceo/api/breeds/image/random";
|
const ENDPOINT: &str = "https://dog.ceo/api/breeds/image/random";
|
||||||
|
|
||||||
pub static Example: Component = |cx| {
|
pub fn Example(cx: Scope) -> Element {
|
||||||
let doggo = use_suspense(
|
let doggo = use_suspense(
|
||||||
cx,
|
cx,
|
||||||
|| surf::get(ENDPOINT).recv_json::<DogApi>(),
|
|| surf::get(ENDPOINT).recv_json::<DogApi>(),
|
||||||
@@ -35,4 +35,4 @@ pub static Example: Component = |cx| {
|
|||||||
{doggo}
|
{doggo}
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
};
|
}
|
||||||
@@ -42,19 +42,19 @@ pub static Example: Component = |cx| {
|
|||||||
div {
|
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 *= -1;
|
||||||
task.restart();
|
task.restart();
|
||||||
}
|
}
|
||||||
|
"Switch counting direcion"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
use dioxus::prelude::*;
|
|
||||||
|
|
||||||
pub static Example: Component = |cx| {
|
|
||||||
cx.render(rsx! {
|
|
||||||
div {
|
|
||||||
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
use dioxus::prelude::*;
|
|
||||||
use dioxus::ssr;
|
|
||||||
|
|
||||||
pub static Example: Component = |cx| {
|
|
||||||
let as_string = use_state(&cx, || {
|
|
||||||
// Currently, SSR is only supported for whole VirtualDOMs
|
|
||||||
// This is an easy/low hanging fruit to improve upon
|
|
||||||
let mut dom = VirtualDom::new(SomeApp);
|
|
||||||
dom.rebuild();
|
|
||||||
ssr::render_vdom(&dom)
|
|
||||||
});
|
|
||||||
|
|
||||||
cx.render(rsx! {
|
|
||||||
div { "{as_string}" }
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
static SomeApp: Component = |cx| {
|
|
||||||
cx.render(rsx! {
|
|
||||||
div { style: {background_color: "blue"}
|
|
||||||
h1 {"Some amazing app or component"}
|
|
||||||
p {"Things are great"}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
|
||||||
@@ -22,12 +22,12 @@
|
|||||||
//! the coroutine was initiated. `use_state` always returns the same setter, so you don't need to worry about
|
//! the coroutine was initiated. `use_state` always returns the same setter, so you don't need to worry about
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
dioxus::desktop::launch(App);
|
dioxus::desktop::launch(app);
|
||||||
}
|
}
|
||||||
|
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
static App: Component = |cx| {
|
fn app(cx: Scope) -> Element {
|
||||||
let p1 = use_state(&cx, || 0);
|
let p1 = use_state(&cx, || 0);
|
||||||
let p2 = use_state(&cx, || 0);
|
let p2 = use_state(&cx, || 0);
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ static App: Component = |cx| {
|
|||||||
Horsey { pos: *p2, "horsey 2" }
|
Horsey { pos: *p2, "horsey 2" }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
#[derive(Props)]
|
#[derive(Props)]
|
||||||
struct HorseyProps<'a> {
|
struct HorseyProps<'a> {
|
||||||
|
|||||||
+1
-2
@@ -20,12 +20,11 @@ pub struct Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn app(cx: Scope) -> Element {
|
fn app(cx: Scope) -> Element {
|
||||||
let clients = use_ref(&cx, || vec![] as Vec<Client>);
|
|
||||||
|
|
||||||
let scene = use_state(&cx, || Scene::ClientsList);
|
let scene = use_state(&cx, || Scene::ClientsList);
|
||||||
let firstname = use_state(&cx, String::new);
|
let firstname = use_state(&cx, String::new);
|
||||||
let lastname = use_state(&cx, String::new);
|
let lastname = use_state(&cx, String::new);
|
||||||
let description = use_state(&cx, String::new);
|
let description = use_state(&cx, String::new);
|
||||||
|
let clients = use_ref(&cx, || vec![] as Vec<Client>);
|
||||||
|
|
||||||
cx.render(rsx!(
|
cx.render(rsx!(
|
||||||
body {
|
body {
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
use dioxus_core::prelude::*;
|
|
||||||
use dioxus_core_macro::*;
|
|
||||||
use dioxus_html as dioxus_elements;
|
|
||||||
|
|
||||||
fn main() {}
|
|
||||||
@@ -1,109 +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() {
|
|
||||||
dioxus_desktop::launch(App)
|
|
||||||
}
|
|
||||||
|
|
||||||
enum Scene {
|
|
||||||
ClientsList,
|
|
||||||
NewClientForm,
|
|
||||||
Settings,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
|
||||||
pub struct Client {
|
|
||||||
pub first_name: String,
|
|
||||||
pub last_name: String,
|
|
||||||
pub description: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
static App: Component = |cx| {
|
|
||||||
let mut scene = use_state(&cx, || Scene::ClientsList);
|
|
||||||
let clients = use_ref(&cx, || vec![] as Vec<Client>);
|
|
||||||
|
|
||||||
let mut firstname = use_state(&cx, String::new);
|
|
||||||
let mut lastname = use_state(&cx, String::new);
|
|
||||||
let mut description = use_state(&cx, String::new);
|
|
||||||
|
|
||||||
let scene = match scene.get() {
|
|
||||||
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 |evt| firstname.set(evt.value)
|
|
||||||
}
|
|
||||||
input { class: "new-client lastname" placeholder: "Last name" value: "{lastname}"
|
|
||||||
oninput: move |evt| lastname.set(evt.value)
|
|
||||||
}
|
|
||||||
textarea { class: "new-client description" placeholder: "Description" value: "{description}"
|
|
||||||
oninput: move |evt| description.set(evt.value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
button { class: "pure-button pure-button-primary", onclick: {add_new}, "Add New" }
|
|
||||||
button { class: "pure-button", onclick: move |_| scene.set(Scene::ClientsList), "Go Back" }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Scene::Settings => {
|
|
||||||
rsx!(cx, div {
|
|
||||||
h2 { "Settings" margin_bottom: "10px" }
|
|
||||||
button {
|
|
||||||
background: "rgb(202, 60, 60)"
|
|
||||||
class: "pure-button pure-button-primary"
|
|
||||||
onclick: move |_| {
|
|
||||||
clients.write().clear();
|
|
||||||
scene.set(Scene::ClientsList);
|
|
||||||
},
|
|
||||||
"Remove all clients"
|
|
||||||
}
|
|
||||||
button {
|
|
||||||
class: "pure-button pure-button-primary"
|
|
||||||
onclick: move |_| scene.set(Scene::ClientsList),
|
|
||||||
"Go Back"
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
rsx!(cx, body {
|
|
||||||
link {
|
|
||||||
rel: "stylesheet"
|
|
||||||
href: "https://unpkg.com/purecss@2.0.6/build/pure-min.css"
|
|
||||||
integrity: "sha384-Uu6IeWbM+gzNVXJcM9XV3SohHtmWE+3VGi496jvgX1jyvDTXfdK+rfZc8C1Aehk5"
|
|
||||||
crossorigin: "anonymous"
|
|
||||||
}
|
|
||||||
margin_left: "35%"
|
|
||||||
h1 {"Dioxus CRM Example"}
|
|
||||||
{scene}
|
|
||||||
})
|
|
||||||
};
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
use dioxus_core as dioxus;
|
|
||||||
use dioxus_core::prelude::*;
|
|
||||||
use dioxus_core_macro::*;
|
|
||||||
|
|
||||||
use dioxus_html as dioxus_elements;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
dioxus_desktop::launch(App);
|
|
||||||
}
|
|
||||||
|
|
||||||
static App: Component = |cx| {
|
|
||||||
cx.render(rsx!(
|
|
||||||
div {
|
|
||||||
"hello world!"
|
|
||||||
}
|
|
||||||
(0..10).map(|f| rsx!( div {"abc {f}"}))
|
|
||||||
))
|
|
||||||
};
|
|
||||||
@@ -1,379 +0,0 @@
|
|||||||
html,
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
border: 0;
|
|
||||||
background: none;
|
|
||||||
font-size: 100%;
|
|
||||||
vertical-align: baseline;
|
|
||||||
font-family: inherit;
|
|
||||||
font-weight: inherit;
|
|
||||||
color: inherit;
|
|
||||||
-webkit-appearance: none;
|
|
||||||
appearance: none;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
|
||||||
line-height: 1.4em;
|
|
||||||
background: #f5f5f5;
|
|
||||||
color: #4d4d4d;
|
|
||||||
min-width: 230px;
|
|
||||||
max-width: 550px;
|
|
||||||
margin: 0 auto;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
font-weight: 300;
|
|
||||||
}
|
|
||||||
|
|
||||||
:focus {
|
|
||||||
outline: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hidden {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.todoapp {
|
|
||||||
background: #fff;
|
|
||||||
margin: 130px 0 40px 0;
|
|
||||||
position: relative;
|
|
||||||
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2),
|
|
||||||
0 25px 50px 0 rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.todoapp input::-webkit-input-placeholder {
|
|
||||||
font-style: italic;
|
|
||||||
font-weight: 300;
|
|
||||||
color: #e6e6e6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.todoapp input::-moz-placeholder {
|
|
||||||
font-style: italic;
|
|
||||||
font-weight: 300;
|
|
||||||
color: #e6e6e6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.todoapp input::input-placeholder {
|
|
||||||
font-style: italic;
|
|
||||||
font-weight: 300;
|
|
||||||
color: #e6e6e6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.todoapp h1 {
|
|
||||||
position: absolute;
|
|
||||||
top: -155px;
|
|
||||||
width: 100%;
|
|
||||||
font-size: 100px;
|
|
||||||
font-weight: 100;
|
|
||||||
text-align: center;
|
|
||||||
color: rgba(175, 47, 47, 0.15);
|
|
||||||
-webkit-text-rendering: optimizeLegibility;
|
|
||||||
-moz-text-rendering: optimizeLegibility;
|
|
||||||
text-rendering: optimizeLegibility;
|
|
||||||
}
|
|
||||||
|
|
||||||
.new-todo,
|
|
||||||
.edit {
|
|
||||||
position: relative;
|
|
||||||
margin: 0;
|
|
||||||
width: 100%;
|
|
||||||
font-size: 24px;
|
|
||||||
font-family: inherit;
|
|
||||||
font-weight: inherit;
|
|
||||||
line-height: 1.4em;
|
|
||||||
border: 0;
|
|
||||||
color: inherit;
|
|
||||||
padding: 6px;
|
|
||||||
border: 1px solid #999;
|
|
||||||
box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
|
|
||||||
box-sizing: border-box;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
}
|
|
||||||
|
|
||||||
.new-todo {
|
|
||||||
padding: 16px 16px 16px 60px;
|
|
||||||
border: none;
|
|
||||||
background: rgba(0, 0, 0, 0.003);
|
|
||||||
box-shadow: inset 0 -2px 1px rgba(0, 0, 0, 0.03);
|
|
||||||
}
|
|
||||||
|
|
||||||
.main {
|
|
||||||
position: relative;
|
|
||||||
z-index: 2;
|
|
||||||
border-top: 1px solid #e6e6e6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toggle-all {
|
|
||||||
text-align: center;
|
|
||||||
border: none;
|
|
||||||
/* Mobile Safari */
|
|
||||||
opacity: 0;
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toggle-all+label {
|
|
||||||
width: 60px;
|
|
||||||
height: 34px;
|
|
||||||
font-size: 0;
|
|
||||||
position: absolute;
|
|
||||||
top: -52px;
|
|
||||||
left: -13px;
|
|
||||||
-webkit-transform: rotate(90deg);
|
|
||||||
transform: rotate(90deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.toggle-all+label:before {
|
|
||||||
content: '❯';
|
|
||||||
font-size: 22px;
|
|
||||||
color: #e6e6e6;
|
|
||||||
padding: 10px 27px 10px 27px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toggle-all:checked+label:before {
|
|
||||||
color: #737373;
|
|
||||||
}
|
|
||||||
|
|
||||||
.todo-list {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
list-style: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.todo-list li {
|
|
||||||
position: relative;
|
|
||||||
font-size: 24px;
|
|
||||||
border-bottom: 1px solid #ededed;
|
|
||||||
}
|
|
||||||
|
|
||||||
.todo-list li:last-child {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.todo-list li.editing {
|
|
||||||
border-bottom: none;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.todo-list li.editing .edit {
|
|
||||||
display: block;
|
|
||||||
width: 506px;
|
|
||||||
padding: 12px 16px;
|
|
||||||
margin: 0 0 0 43px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.todo-list li.editing .view {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.todo-list li .toggle {
|
|
||||||
text-align: center;
|
|
||||||
width: 40px;
|
|
||||||
/* auto, since non-WebKit browsers doesn't support input styling */
|
|
||||||
height: auto;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
margin: auto 0;
|
|
||||||
border: none;
|
|
||||||
/* Mobile Safari */
|
|
||||||
-webkit-appearance: none;
|
|
||||||
appearance: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.todo-list li .toggle {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.todo-list li .toggle+label {
|
|
||||||
/*
|
|
||||||
Firefox requires `#` to be escaped - https://bugzilla.mozilla.org/show_bug.cgi?id=922433
|
|
||||||
IE and Edge requires *everything* to be escaped to render, so we do that instead of just the `#` - https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/7157459/
|
|
||||||
*/
|
|
||||||
background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23ededed%22%20stroke-width%3D%223%22/%3E%3C/svg%3E');
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: center left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.todo-list li .toggle:checked+label {
|
|
||||||
background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23bddad5%22%20stroke-width%3D%223%22/%3E%3Cpath%20fill%3D%22%235dc2af%22%20d%3D%22M72%2025L42%2071%2027%2056l-4%204%2020%2020%2034-52z%22/%3E%3C/svg%3E');
|
|
||||||
}
|
|
||||||
|
|
||||||
.todo-list li label {
|
|
||||||
word-break: break-all;
|
|
||||||
padding: 15px 15px 15px 60px;
|
|
||||||
display: block;
|
|
||||||
line-height: 1.2;
|
|
||||||
transition: color 0.4s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.todo-list li.completed label {
|
|
||||||
color: #d9d9d9;
|
|
||||||
text-decoration: line-through;
|
|
||||||
}
|
|
||||||
|
|
||||||
.todo-list li .destroy {
|
|
||||||
display: none;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 10px;
|
|
||||||
bottom: 0;
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
margin: auto 0;
|
|
||||||
font-size: 30px;
|
|
||||||
color: #cc9a9a;
|
|
||||||
margin-bottom: 11px;
|
|
||||||
transition: color 0.2s ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.todo-list li .destroy:hover {
|
|
||||||
color: #af5b5e;
|
|
||||||
}
|
|
||||||
|
|
||||||
.todo-list li .destroy:after {
|
|
||||||
content: '×';
|
|
||||||
}
|
|
||||||
|
|
||||||
.todo-list li:hover .destroy {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.todo-list li .edit {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.todo-list li.editing:last-child {
|
|
||||||
margin-bottom: -1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer {
|
|
||||||
color: #777;
|
|
||||||
padding: 10px 15px;
|
|
||||||
height: 20px;
|
|
||||||
text-align: center;
|
|
||||||
border-top: 1px solid #e6e6e6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer:before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
height: 50px;
|
|
||||||
overflow: hidden;
|
|
||||||
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2),
|
|
||||||
0 8px 0 -3px #f6f6f6,
|
|
||||||
0 9px 1px -3px rgba(0, 0, 0, 0.2),
|
|
||||||
0 16px 0 -6px #f6f6f6,
|
|
||||||
0 17px 2px -6px rgba(0, 0, 0, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.todo-count {
|
|
||||||
float: left;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.todo-count strong {
|
|
||||||
font-weight: 300;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filters {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
list-style: none;
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filters li {
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filters li a {
|
|
||||||
color: inherit;
|
|
||||||
margin: 3px;
|
|
||||||
padding: 3px 7px;
|
|
||||||
text-decoration: none;
|
|
||||||
border: 1px solid transparent;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filters li a:hover {
|
|
||||||
border-color: rgba(175, 47, 47, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.filters li a.selected {
|
|
||||||
border-color: rgba(175, 47, 47, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.clear-completed,
|
|
||||||
html .clear-completed:active {
|
|
||||||
float: right;
|
|
||||||
position: relative;
|
|
||||||
line-height: 20px;
|
|
||||||
text-decoration: none;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.clear-completed:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info {
|
|
||||||
margin: 65px auto 0;
|
|
||||||
color: #bfbfbf;
|
|
||||||
font-size: 10px;
|
|
||||||
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info p {
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info a {
|
|
||||||
color: inherit;
|
|
||||||
text-decoration: none;
|
|
||||||
font-weight: 400;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info a:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Hack to remove background from Mobile Safari.
|
|
||||||
Can't use it globally since it destroys checkboxes in Firefox
|
|
||||||
*/
|
|
||||||
@media screen and (-webkit-min-device-pixel-ratio:0) {
|
|
||||||
|
|
||||||
.toggle-all,
|
|
||||||
.todo-list li .toggle {
|
|
||||||
background: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.todo-list li .toggle {
|
|
||||||
height: 40px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 430px) {
|
|
||||||
.footer {
|
|
||||||
height: 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filters {
|
|
||||||
bottom: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,170 +0,0 @@
|
|||||||
#![allow(non_upper_case_globals, non_snake_case)]
|
|
||||||
|
|
||||||
use dioxus_core as dioxus;
|
|
||||||
use dioxus_core::prelude::*;
|
|
||||||
use dioxus_core_macro::*;
|
|
||||||
use dioxus_hooks::*;
|
|
||||||
use dioxus_html as dioxus_elements;
|
|
||||||
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
dioxus_desktop::launch(App)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(PartialEq)]
|
|
||||||
pub enum FilterState {
|
|
||||||
All,
|
|
||||||
Active,
|
|
||||||
Completed,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub struct TodoItem {
|
|
||||||
pub id: u32,
|
|
||||||
pub checked: bool,
|
|
||||||
pub contents: String,
|
|
||||||
}
|
|
||||||
pub type Todos = HashMap<u32, TodoItem>;
|
|
||||||
|
|
||||||
pub static App: Component = |cx| {
|
|
||||||
// Share our TodoList to the todos themselves
|
|
||||||
use_provide_state(cx, Todos::new);
|
|
||||||
|
|
||||||
// Save state for the draft, filter
|
|
||||||
let draft = use_state(&cx, || "".to_string());
|
|
||||||
let filter = use_state(&cx, || FilterState::All);
|
|
||||||
let mut todo_id = use_state(&cx, || 0);
|
|
||||||
|
|
||||||
// Consume the todos
|
|
||||||
let todos = use_shared_state::<Todos>(cx)?;
|
|
||||||
|
|
||||||
// Filter the todos based on the filter state
|
|
||||||
let mut filtered_todos = todos
|
|
||||||
.read()
|
|
||||||
.iter()
|
|
||||||
.filter(|(_, item)| match *filter {
|
|
||||||
FilterState::All => true,
|
|
||||||
FilterState::Active => !item.checked,
|
|
||||||
FilterState::Completed => item.checked,
|
|
||||||
})
|
|
||||||
.map(|f| *f.0)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
filtered_todos.sort_unstable();
|
|
||||||
|
|
||||||
// Define the actions to manage the todolist
|
|
||||||
let mut submit_todo = move || {
|
|
||||||
if !draft.is_empty() {
|
|
||||||
todos.write().insert(
|
|
||||||
*todo_id,
|
|
||||||
TodoItem {
|
|
||||||
id: *todo_id,
|
|
||||||
checked: false,
|
|
||||||
contents: draft.get().clone(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
todo_id += 1;
|
|
||||||
draft.set("".to_string());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let clear_completed = move || {
|
|
||||||
todos.write().retain(|_, todo| todo.checked == false);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Some assists in actually rendering the content
|
|
||||||
let show_clear_completed = todos.read().values().any(|todo| todo.checked);
|
|
||||||
let items_left = filtered_todos.len();
|
|
||||||
let item_text = match items_left {
|
|
||||||
1 => "item",
|
|
||||||
_ => "items",
|
|
||||||
};
|
|
||||||
|
|
||||||
cx.render(rsx!{
|
|
||||||
section { class: "todoapp"
|
|
||||||
style { {[include_str!("./todomvc.css")]} }
|
|
||||||
div {
|
|
||||||
header { class: "header"
|
|
||||||
h1 {"todos"}
|
|
||||||
input {
|
|
||||||
class: "new-todo"
|
|
||||||
placeholder: "What needs to be done?"
|
|
||||||
value: "{draft}"
|
|
||||||
autofocus: "true"
|
|
||||||
oninput: move |evt| draft.set(evt.value)
|
|
||||||
onkeydown: move |evt| {
|
|
||||||
if evt.key == "Enter" {
|
|
||||||
submit_todo();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ul { class: "todo-list",
|
|
||||||
filtered_todos.iter().map(|id| rsx!(TodoEntry { key: "{id}", id: *id }))
|
|
||||||
}
|
|
||||||
(!todos.read().is_empty()).then(|| rsx!(
|
|
||||||
footer { class: "footer",
|
|
||||||
span { class: "todo-count" strong {"{items_left} "} span {"{item_text} left"} }
|
|
||||||
ul { class: "filters"
|
|
||||||
li { class: "All", a { onclick: move |_| filter.set(FilterState::All), "All" }}
|
|
||||||
li { class: "Active", a { onclick: move |_| filter.set(FilterState::Active), "Active" }}
|
|
||||||
li { class: "Completed", a { onclick: move |_| filter.set(FilterState::Completed), "Completed" }}
|
|
||||||
}
|
|
||||||
show_clear_completed.then(|| rsx!(
|
|
||||||
button { class: "clear-completed", onclick: move |_| clear_completed(),
|
|
||||||
"Clear completed"
|
|
||||||
}
|
|
||||||
))
|
|
||||||
}
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
footer { class: "info"
|
|
||||||
p {"Double-click to edit a todo"}
|
|
||||||
p { "Created by ", a { "jkelleyrtp", href: "http://github.com/jkelleyrtp/" }}
|
|
||||||
p { "Part of ", a { "TodoMVC", href: "http://todomvc.com" }}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(PartialEq, Props)]
|
|
||||||
pub struct TodoEntryProps {
|
|
||||||
id: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn TodoEntry((cx, props): Scope<TodoEntryProps>) -> Element {
|
|
||||||
let todos = use_shared_state::<Todos>(cx)?;
|
|
||||||
|
|
||||||
let _todos = todos.read();
|
|
||||||
let todo = _todos.get(&cx.props.id)?;
|
|
||||||
|
|
||||||
let is_editing = use_state(&cx, || false);
|
|
||||||
let completed = if todo.checked { "completed" } else { "" };
|
|
||||||
|
|
||||||
cx.render(rsx!{
|
|
||||||
li { class: "{completed}"
|
|
||||||
div { class: "view"
|
|
||||||
input { class: "toggle" r#type: "checkbox" id: "cbg-{todo.id}" checked: "{todo.checked}"
|
|
||||||
onchange: move |evt| {
|
|
||||||
if let Some(todo) = todos.write().get_mut(&cx.props.id) {
|
|
||||||
todo.checked = evt.value.parse().unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
label { r#for: "cbg-{todo.id}" pointer_events: "none"
|
|
||||||
"{todo.contents}"
|
|
||||||
}
|
|
||||||
|
|
||||||
{is_editing.then(|| rsx!{
|
|
||||||
input { value: "{todo.contents}"
|
|
||||||
oninput: move |evt| {
|
|
||||||
if let Some(todo) = todos.write().get_mut(&cx.props.id) {
|
|
||||||
todo.contents = evt.value
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
})}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,7 @@ use dioxus::prelude::*;
|
|||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
dioxus::desktop::launch(App);
|
dioxus::desktop::launch(app);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(Clone, PartialEq)]
|
||||||
@@ -29,7 +29,7 @@ impl Label {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static App: Component = |cx| {
|
fn app(cx: Scope) -> Element {
|
||||||
let items = use_ref(&cx, || vec![]);
|
let items = use_ref(&cx, || vec![]);
|
||||||
let selected = use_state(&cx, || None);
|
let selected = use_state(&cx, || None);
|
||||||
|
|
||||||
@@ -64,7 +64,7 @@ static App: Component = |cx| {
|
|||||||
}
|
}
|
||||||
table {
|
table {
|
||||||
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" }
|
||||||
@@ -79,13 +79,13 @@ static App: Component = |cx| {
|
|||||||
}
|
}
|
||||||
td { class: "col-md-6" }
|
td { class: "col-md-6" }
|
||||||
})
|
})
|
||||||
})}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
span { class: "preloadicon glyphicon glyphicon-remove", aria_hidden: "true" }
|
span { class: "preloadicon glyphicon glyphicon-remove", aria_hidden: "true" }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
#[derive(Props)]
|
#[derive(Props)]
|
||||||
struct ActionButtonProps<'a> {
|
struct ActionButtonProps<'a> {
|
||||||
@@ -95,9 +95,17 @@ 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",
|
cx.render(rsx! {
|
||||||
button { class:"btn btn-primary btn-block", r#type: "button", id: "{cx.props.id}", onclick: move |_| (cx.props.onclick)(),
|
div {
|
||||||
"{cx.props.name}"
|
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}"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,12 +13,13 @@ use dioxus::prelude::*;
|
|||||||
use dioxus::ssr;
|
use dioxus::ssr;
|
||||||
|
|
||||||
fn main() {
|
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| {
|
fn app(cx: Scope) -> Element {
|
||||||
let val = use_state(&cx, || 0);
|
let val = use_state(&cx, || 0);
|
||||||
|
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
@@ -30,4 +31,4 @@ static App: Component = |cx| {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
}
|
||||||
+3
-3
@@ -7,7 +7,7 @@ use std::sync::Arc;
|
|||||||
use dioxus::{events::FormEvent, prelude::*};
|
use dioxus::{events::FormEvent, prelude::*};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
dioxus::desktop::launch(App);
|
dioxus::desktop::launch(app);
|
||||||
}
|
}
|
||||||
|
|
||||||
const FIELDS: &[(&str, &str)] = &[
|
const FIELDS: &[(&str, &str)] = &[
|
||||||
@@ -37,7 +37,7 @@ const FIELDS: &[(&str, &str)] = &[
|
|||||||
("week", ""), // degrades to text most of the time
|
("week", ""), // degrades to text most of the time
|
||||||
];
|
];
|
||||||
|
|
||||||
static App: Component = |cx| {
|
fn app(cx: Scope) -> Element {
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
div { margin_left: "30px",
|
div { margin_left: "30px",
|
||||||
|
|
||||||
@@ -147,4 +147,4 @@ static App: Component = |cx| {
|
|||||||
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
}
|
||||||
@@ -33,7 +33,7 @@ fn main() {
|
|||||||
AppendChildren { many: 1 },
|
AppendChildren { many: 1 },
|
||||||
];
|
];
|
||||||
|
|
||||||
let app: Component = |cx| rsx!(cx, div { "some app" });
|
let app: Component = |cx| cx.render(rsx!(div { "some app" }));
|
||||||
|
|
||||||
dioxus_desktop::run(app, (), |c| c.with_edits(edits));
|
dioxus_desktop::run(app, (), |c| c.with_edits(edits));
|
||||||
}
|
}
|
||||||
+27
-24
@@ -21,7 +21,6 @@ use dioxus::desktop::wry::application::dpi::LogicalSize;
|
|||||||
use dioxus::events::*;
|
use dioxus::events::*;
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
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| {
|
||||||
@@ -40,32 +39,36 @@ fn app(cx: Scope) -> Element {
|
|||||||
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",
|
cx.render(rsx!{
|
||||||
div { class: "app", style { "{STYLE}" }
|
div { id: "wrapper",
|
||||||
div { class: "calculator", onkeypress: move |evt| state.write().handle_keydown(evt),
|
div {
|
||||||
div { class: "calculator-display", "{formatted}"}
|
class: "app",
|
||||||
div { class: "calculator-keypad",
|
style { [include_str!("./assets/calculator.css")] }
|
||||||
div { class: "input-keys",
|
div { class: "calculator", onkeypress: move |evt| state.write().handle_keydown(evt),
|
||||||
div { class: "function-keys",
|
div { class: "calculator-display", "{formatted}"}
|
||||||
CalculatorKey { name: "key-clear", onclick: move |_| state.write().clear_display(), "{clear_text}" }
|
div { class: "calculator-keypad",
|
||||||
CalculatorKey { name: "key-sign", onclick: move |_| state.write().toggle_sign(), "±"}
|
div { class: "input-keys",
|
||||||
CalculatorKey { name: "key-percent", onclick: move |_| state.write().toggle_percent(), "%"}
|
div { class: "function-keys",
|
||||||
|
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-percent", onclick: move |_| state.write().toggle_percent(), "%"}
|
||||||
|
}
|
||||||
|
div { class: "digit-keys",
|
||||||
|
CalculatorKey { name: "key-0", onclick: move |_| state.write().input_digit(0), "0" }
|
||||||
|
CalculatorKey { name: "key-dot", onclick: move |_| state.write().input_dot(), "●" }
|
||||||
|
(1..10).map(move |k| rsx!{
|
||||||
|
CalculatorKey { key: "{k}", name: "key-{k}", onclick: move |_| state.write().input_digit(k), "{k}" }
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
div { class: "digit-keys",
|
div { class: "operator-keys",
|
||||||
CalculatorKey { name: "key-0", onclick: move |_| state.write().input_digit(0), "0" }
|
CalculatorKey { name:"key-divide", onclick: move |_| state.write().set_operator(Operator::Div), "÷" }
|
||||||
CalculatorKey { name: "key-dot", onclick: move |_| state.write().input_dot(), "●" }
|
CalculatorKey { name:"key-multiply", onclick: move |_| state.write().set_operator(Operator::Mul), "×" }
|
||||||
(1..10).map(move |k| rsx!{
|
CalculatorKey { name:"key-subtract", onclick: move |_| state.write().set_operator(Operator::Sub), "−" }
|
||||||
CalculatorKey { key: "{k}", name: "key-{k}", onclick: move |_| state.write().input_digit(k), "{k}" }
|
CalculatorKey { name:"key-add", onclick: move |_| state.write().set_operator(Operator::Add), "+" }
|
||||||
})
|
CalculatorKey { name:"key-equals", onclick: move |_| state.write().perform_operation(), "=" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
div { class: "operator-keys",
|
|
||||||
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-subtract", onclick: move |_| state.write().set_operator(Operator::Sub), "−" }
|
|
||||||
CalculatorKey { name:"key-add", onclick: move |_| state.write().set_operator(Operator::Add), "+" }
|
|
||||||
CalculatorKey { name:"key-equals", onclick: move |_| state.write().perform_operation(), "=" }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,10 +8,10 @@
|
|||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
fn main() {
|
fn main() {
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
dioxus::desktop::launch(App);
|
dioxus::desktop::launch(app);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub static App: Component = |cx| {
|
fn app(cx: Scope) -> Element {
|
||||||
let state = use_state(&cx, PlayerState::new);
|
let state = use_state(&cx, PlayerState::new);
|
||||||
|
|
||||||
cx.render(rsx!(
|
cx.render(rsx!(
|
||||||
@@ -26,7 +26,7 @@ pub static App: Component = |cx| {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
};
|
}
|
||||||
|
|
||||||
enum PlayerAction {
|
enum PlayerAction {
|
||||||
Pause,
|
Pause,
|
||||||
|
|||||||
+3
-3
@@ -5,10 +5,10 @@
|
|||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
dioxus::desktop::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! {
|
||||||
@@ -18,4 +18,4 @@ static App: Component = |cx| {
|
|||||||
button { onclick: move |_| count -= 1, "Down low!" }
|
button { onclick: move |_| count -= 1, "Down low!" }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
}
|
||||||
+2
-2
@@ -23,7 +23,7 @@ pub enum Route {
|
|||||||
NotFound,
|
NotFound,
|
||||||
}
|
}
|
||||||
|
|
||||||
static App: Component = |cx| {
|
fn app(cx: Scope) -> Element {
|
||||||
let route = use_router(&cx, Route::parse);
|
let route = use_router(&cx, Route::parse);
|
||||||
|
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
@@ -42,7 +42,7 @@ static App: Component = |cx| {
|
|||||||
}
|
}
|
||||||
footer {}
|
footer {}
|
||||||
})
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
impl Route {
|
impl Route {
|
||||||
// Generate the appropriate route from the "tail" end of the URL
|
// Generate the appropriate route from the "tail" end of the URL
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
//! This example just flexes the ability to use arbitrary expressions within RSX.
|
||||||
|
//! It also proves that lifetimes work properly, especially when used with use_ref
|
||||||
|
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
//! - Allow top-level fragments
|
//! - Allow top-level fragments
|
||||||
//!
|
//!
|
||||||
fn main() {
|
fn main() {
|
||||||
dioxus::desktop::launch(EXAMPLE);
|
dioxus::desktop::launch(app);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// When trying to return "nothing" to Dioxus, you'll need to specify the type parameter or Rust will be sad.
|
/// When trying to return "nothing" to Dioxus, you'll need to specify the type parameter or Rust will be sad.
|
||||||
@@ -49,7 +49,7 @@ const NONE_ELEMENT: Option<()> = None;
|
|||||||
use baller::Baller;
|
use baller::Baller;
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
pub static EXAMPLE: Component = |cx| {
|
fn app(cx: Scope) -> Element {
|
||||||
let formatting = "formatting!";
|
let formatting = "formatting!";
|
||||||
let formatting_tuple = ("a", "b");
|
let formatting_tuple = ("a", "b");
|
||||||
let lazy_fmt = format_args!("lazily formatted text");
|
let lazy_fmt = format_args!("lazily formatted text");
|
||||||
@@ -192,10 +192,12 @@ pub static EXAMPLE: Component = |cx| {
|
|||||||
[helper(&cx, "hello world!")]
|
[helper(&cx, "hello world!")]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
fn helper<'a>(cx: &'a ScopeState, text: &str) -> Element<'a> {
|
fn helper<'a>(cx: &'a ScopeState, text: &str) -> Element<'a> {
|
||||||
rsx!(cx, p { "{text}" })
|
cx.render(rsx! {
|
||||||
|
p { "{text}" }
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
mod baller {
|
mod baller {
|
||||||
@@ -227,5 +229,7 @@ pub fn Taller<'a>(cx: Scope<'a, TallerProps<'a>>) -> Element {
|
|||||||
|
|
||||||
#[inline_props]
|
#[inline_props]
|
||||||
fn with_inline<'a>(cx: Scope<'a>, text: &'a str) -> Element {
|
fn with_inline<'a>(cx: Scope<'a>, text: &'a str) -> Element {
|
||||||
rsx!(cx, p { "{text}" })
|
cx.render(rsx! {
|
||||||
|
p { "{text}" }
|
||||||
|
})
|
||||||
}
|
}
|
||||||
+3
-3
@@ -2,16 +2,16 @@ use dioxus::prelude::*;
|
|||||||
use dioxus::ssr;
|
use dioxus::ssr;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut vdom = VirtualDom::new(APP);
|
let mut vdom = VirtualDom::new(app);
|
||||||
let _ = vdom.rebuild();
|
let _ = vdom.rebuild();
|
||||||
println!("{}", ssr::render_vdom(&vdom));
|
println!("{}", ssr::render_vdom(&vdom));
|
||||||
}
|
}
|
||||||
|
|
||||||
static APP: Component = |cx| {
|
fn app(cx: Scope) -> Element {
|
||||||
cx.render(rsx!(
|
cx.render(rsx!(
|
||||||
div {
|
div {
|
||||||
h1 { "Title" }
|
h1 { "Title" }
|
||||||
p { "Body" }
|
p { "Body" }
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
};
|
}
|
||||||
@@ -3,8 +3,9 @@ use dioxus_core as dioxus;
|
|||||||
use dioxus_core::prelude::*;
|
use dioxus_core::prelude::*;
|
||||||
use dioxus_core_macro::*;
|
use dioxus_core_macro::*;
|
||||||
use dioxus_html as dioxus_elements;
|
use dioxus_html as dioxus_elements;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut dom = VirtualDom::new(App);
|
let mut dom = VirtualDom::new(app);
|
||||||
dom.rebuild();
|
dom.rebuild();
|
||||||
println!(
|
println!(
|
||||||
"{}",
|
"{}",
|
||||||
@@ -12,14 +13,14 @@ fn main() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub static App: Component = |cx| {
|
fn app(cx: Scope) -> Element {
|
||||||
cx.render(rsx!(
|
cx.render(rsx!(
|
||||||
div {
|
div {
|
||||||
class: "overflow-hidden"
|
class: "overflow-hidden"
|
||||||
ul {
|
ul {
|
||||||
{(0..10).map(|i| rsx!{ li { class: "flex flex-col", "entry: {i}"}})}
|
(0..10).map(|i| rsx!{ li { class: "flex flex-col", "entry: {i}"}})
|
||||||
}
|
}
|
||||||
"hello world!"
|
"hello world!"
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
};
|
}
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
//! Example: realworld usage of hydration
|
|
||||||
//!
|
|
||||||
//!
|
|
||||||
use dioxus::virtual_dom::VirtualDom;
|
|
||||||
use dioxus_core as dioxus;
|
|
||||||
use dioxus_core::prelude::*;
|
|
||||||
use dioxus_hooks::use_state;
|
|
||||||
use dioxus_html as dioxus_elements;
|
|
||||||
|
|
||||||
fn main() {}
|
|
||||||
@@ -1,298 +0,0 @@
|
|||||||
<!-- a js-only interpreter for the dioxus patch stream :) -->
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<link href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel="stylesheet" />
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div>hello dioxus</div>
|
|
||||||
</body>
|
|
||||||
<script>
|
|
||||||
class Interpreter {
|
|
||||||
constructor(root) {
|
|
||||||
this.stack = [root];
|
|
||||||
}
|
|
||||||
|
|
||||||
top() {
|
|
||||||
return this.stack[this.stack.length - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
pop() {
|
|
||||||
return this.stack.pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class OPTABLE {
|
|
||||||
// 0
|
|
||||||
SetText(edit, interp) {
|
|
||||||
interp.top(interp.stack).textContent = edit.text;
|
|
||||||
}
|
|
||||||
// 1
|
|
||||||
RemoveSelfAndNextSiblings(edit) {
|
|
||||||
const node = interp.pop();
|
|
||||||
let sibling = node.nextSibling;
|
|
||||||
while (sibling) {
|
|
||||||
const temp = sibling.nextSibling;
|
|
||||||
sibling.remove();
|
|
||||||
sibling = temp;
|
|
||||||
}
|
|
||||||
node.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2
|
|
||||||
ReplaceWith(edit, interp) {
|
|
||||||
const newNode = interp.pop();
|
|
||||||
const oldNode = interp.pop();
|
|
||||||
oldNode.replaceWith(newNode);
|
|
||||||
interp.stack.push(newNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3
|
|
||||||
SetAttribute(edit, interp) {
|
|
||||||
const name = edit.name;
|
|
||||||
const value = edit.value;
|
|
||||||
const node = interp.top(interp.stack);
|
|
||||||
node.setAttribute(name, value);
|
|
||||||
|
|
||||||
// Some attributes are "volatile" and don't work through `setAttribute`.
|
|
||||||
if ((name === "value", interp)) {
|
|
||||||
node.value = value;
|
|
||||||
}
|
|
||||||
if ((name === "checked", interp)) {
|
|
||||||
node.checked = true;
|
|
||||||
}
|
|
||||||
if ((name === "selected", interp)) {
|
|
||||||
node.selected = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4
|
|
||||||
RemoveAttribute(edit, interp) {
|
|
||||||
const name = edit.name;
|
|
||||||
const node = interp.top(interp.stack);
|
|
||||||
node.removeAttribute(name);
|
|
||||||
|
|
||||||
// Some attributes are "volatile" and don't work through `removeAttribute`.
|
|
||||||
if ((name === "value", interp)) {
|
|
||||||
node.value = null;
|
|
||||||
}
|
|
||||||
if ((name === "checked", interp)) {
|
|
||||||
node.checked = false;
|
|
||||||
}
|
|
||||||
if ((name === "selected", interp)) {
|
|
||||||
node.selected = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5
|
|
||||||
PushReverseChild(edit, interp) {
|
|
||||||
const n = edit.n;
|
|
||||||
const parent = interp.top(interp.stack);
|
|
||||||
const children = parent.childNodes;
|
|
||||||
const child = children[children.length - n - 1];
|
|
||||||
interp.stack.push(child);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 6
|
|
||||||
PopPushChild(edit, interp) {
|
|
||||||
const n = edit.n;
|
|
||||||
interp.pop();
|
|
||||||
const parent = interp.top(interp.stack);
|
|
||||||
const children = parent.childNodes;
|
|
||||||
const child = children[n];
|
|
||||||
interp.stack.push(child);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 7
|
|
||||||
Pop(edit, interp) {
|
|
||||||
interp.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 8
|
|
||||||
AppendChild(edit, interp) {
|
|
||||||
console.log(interp.stack);
|
|
||||||
const child = interp.pop();
|
|
||||||
console.log(interp.stack);
|
|
||||||
interp.top().appendChild(child);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 9
|
|
||||||
CreateTextNode(edit, interp) {
|
|
||||||
// interp.stack.push(document.createTextNode("asd"));
|
|
||||||
console.log(interp.stack);
|
|
||||||
|
|
||||||
interp.stack.push(document.createTextNode(edit.text));
|
|
||||||
console.log(interp.stack);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 10
|
|
||||||
CreateElement(edit, interp) {
|
|
||||||
const tagName = edit.tag_name;
|
|
||||||
interp.stack.push(document.createElement(tagName));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 11
|
|
||||||
NewEventListener(edit, interp) {
|
|
||||||
// todo!
|
|
||||||
const eventId = mem32[i++];
|
|
||||||
const eventType = interp.getCachedString(eventId);
|
|
||||||
const a = mem32[i++];
|
|
||||||
const b = mem32[i++];
|
|
||||||
const el = interp.top(interp.stack);
|
|
||||||
el.addEventListener(eventType, interp.eventHandler);
|
|
||||||
el[`dodrio-a-${eventType}`] = a;
|
|
||||||
el[`dodrio-b-${eventType}`] = b;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 12
|
|
||||||
UpdateEventListener(edit, interp) {
|
|
||||||
// todo!
|
|
||||||
const eventId = mem32[i++];
|
|
||||||
const eventType = interp.getCachedString(eventId);
|
|
||||||
const el = interp.top(interp.stack);
|
|
||||||
el[`dodrio-a-${eventType}`] = mem32[i++];
|
|
||||||
el[`dodrio-b-${eventType}`] = mem32[i++];
|
|
||||||
}
|
|
||||||
|
|
||||||
// 13
|
|
||||||
RemoveEventListener(edit, interp) {
|
|
||||||
// todo!
|
|
||||||
const eventId = mem32[i++];
|
|
||||||
const eventType = interp.getCachedString(eventId);
|
|
||||||
const el = interp.top(interp.stack);
|
|
||||||
el.removeEventListener(eventType, interp.eventHandler);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 14
|
|
||||||
AddCachedString(edit, interp) {
|
|
||||||
// todo!
|
|
||||||
const pointer = mem32[i++];
|
|
||||||
const length = mem32[i++];
|
|
||||||
const id = mem32[i++];
|
|
||||||
const str = string(mem8, pointer, length);
|
|
||||||
interp.addCachedString(str, id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 15
|
|
||||||
DropCachedString(edit, interp) {
|
|
||||||
// todo!
|
|
||||||
const id = mem32[i++];
|
|
||||||
interp.dropCachedString(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 16
|
|
||||||
CreateElementNS(edit, interp) {
|
|
||||||
// const tagNameId = mem32[i++];
|
|
||||||
// const tagName = interp.getCachedString(tagNameId);
|
|
||||||
// const nsId = mem32[i++];
|
|
||||||
// const ns = interp.getCachedString(nsId);
|
|
||||||
interp.stack.push(document.createElementNS(edit.ns, edit.tag_name));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 17
|
|
||||||
SaveChildrenToTemporaries(edit, interp) {
|
|
||||||
// let temp = mem32[i++];
|
|
||||||
// const start = mem32[i++];
|
|
||||||
// const end = mem32[i++];
|
|
||||||
let temp = edit.temp;
|
|
||||||
const start = edit.start;
|
|
||||||
const end = edit.end;
|
|
||||||
const parent = interp.top(interp.stack);
|
|
||||||
const children = parent.childNodes;
|
|
||||||
for (let i = start; i < end; i++, interp) {
|
|
||||||
interp.temporaries[temp++] = children[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 18
|
|
||||||
PushChild(edit, interp) {
|
|
||||||
const parent = interp.top(interp.stack);
|
|
||||||
// const n = mem32[i++];
|
|
||||||
const n = edit.n;
|
|
||||||
const child = parent.childNodes[n];
|
|
||||||
interp.stack.push(child);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 19
|
|
||||||
PushTemporary(edit, interp) {
|
|
||||||
// const temp = mem32[i++];
|
|
||||||
const temp = edit.temp;
|
|
||||||
interp.stack.push(interp.temporaries[temp]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 20
|
|
||||||
InsertBefore(edit, interp) {
|
|
||||||
const before = interp.pop();
|
|
||||||
const after = interp.pop();
|
|
||||||
after.parentNode.insertBefore(before, after);
|
|
||||||
interp.stack.push(before);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 21
|
|
||||||
PopPushReverseChild(edit, interp) {
|
|
||||||
// const n = mem32[i++];
|
|
||||||
const n = edit.n;
|
|
||||||
interp.pop();
|
|
||||||
const parent = interp.top(interp.stack);
|
|
||||||
const children = parent.childNodes;
|
|
||||||
const child = children[children.length - n - 1];
|
|
||||||
interp.stack.push(child);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 22
|
|
||||||
RemoveChild(edit, interp) {
|
|
||||||
// const n = mem32[i++];
|
|
||||||
const n = edit.n;
|
|
||||||
const parent = interp.top(interp.stack);
|
|
||||||
const child = parent.childNodes[n];
|
|
||||||
child.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 23
|
|
||||||
SetClass(edit, interp) {
|
|
||||||
// const classId = mem32[i++];
|
|
||||||
const className = edit.class_name;
|
|
||||||
interp.top(interp.stack).className = className;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 24
|
|
||||||
SaveTemplate(edit, interp) {
|
|
||||||
const id = mem32[i++];
|
|
||||||
const template = interp.top(interp.stack);
|
|
||||||
interp.saveTemplate(id, template.cloneNode(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 25
|
|
||||||
PushTemplate(edit, interp) {
|
|
||||||
const id = mem32[i++];
|
|
||||||
const template = interp.getTemplate(id);
|
|
||||||
interp.stack.push(template.cloneNode(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
NewListener(edit, interp) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
const op_table = new OPTABLE();
|
|
||||||
const interpreter = new Interpreter(window.document.body);
|
|
||||||
|
|
||||||
function EditListReceived(rawEditList) {
|
|
||||||
let editList = JSON.parse(rawEditList);
|
|
||||||
editList.forEach(function (edit, index) {
|
|
||||||
op_table[edit.type](edit, interpreter);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let socket = new WebSocket("ws://127.0.0.1:8080/session/itsworkinggg");
|
|
||||||
socket.onmessage = function(event) {
|
|
||||||
console.log(event.data);
|
|
||||||
EditListReceived(event.data);
|
|
||||||
}
|
|
||||||
// external.invoke("initiate");
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
//!
|
|
||||||
//!
|
|
||||||
//!
|
|
||||||
use dioxus::virtual_dom::VirtualDom;
|
|
||||||
use dioxus_core as dioxus;
|
|
||||||
use dioxus_core::prelude::*;
|
|
||||||
use dioxus_core_macro::*;
|
|
||||||
use dioxus_hooks::use_state;
|
|
||||||
use dioxus_html as dioxus_elements;
|
|
||||||
|
|
||||||
fn main() {}
|
|
||||||
// use tide::{Request, Response};
|
|
||||||
|
|
||||||
// #[async_std::main]
|
|
||||||
// async fn main() -> Result<(), std::io::Error> {
|
|
||||||
// let mut app = tide::new();
|
|
||||||
|
|
||||||
// app.at("/:name").get(|req: Request<()>| async move {
|
|
||||||
// let initial_name: String = req
|
|
||||||
// .param("name")
|
|
||||||
// .map(|f| f.parse().unwrap_or("...?".to_string()))
|
|
||||||
// .unwrap_or("...?".to_string());
|
|
||||||
|
|
||||||
// let mut dom = VirtualDom::new_with_props(Example, ExampleProps { initial_name });
|
|
||||||
|
|
||||||
// dom.rebuild();
|
|
||||||
|
|
||||||
// Ok(Response::builder(200)
|
|
||||||
// .body(format!("{}", dioxus_ssr::render_vdom(&dom)))
|
|
||||||
// .content_type(tide::http::mime::HTML)
|
|
||||||
// .build())
|
|
||||||
// });
|
|
||||||
|
|
||||||
// println!("Server available at [http://127.0.0.1:8080/bill]");
|
|
||||||
// app.listen("127.0.0.1:8080").await?;
|
|
||||||
|
|
||||||
// Ok(())
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[derive(PartialEq, Props)]
|
|
||||||
// struct ExampleProps {
|
|
||||||
// initial_name: String,
|
|
||||||
// }
|
|
||||||
|
|
||||||
// static Example: Component<ExampleProps> = |cx| {
|
|
||||||
// let dispaly_name = use_state(&cx, move || props.initial_name.clone());
|
|
||||||
|
|
||||||
// cx.render(rsx! {
|
|
||||||
// div { class: "py-12 px-4 text-center w-full max-w-2xl mx-auto",
|
|
||||||
// span { class: "text-sm font-semibold"
|
|
||||||
// "Dioxus Example: Jack and Jill"
|
|
||||||
// }
|
|
||||||
// h2 { class: "text-5xl mt-2 mb-6 leading-tight font-semibold font-heading"
|
|
||||||
// "Hello, {dispaly_name}"
|
|
||||||
// }
|
|
||||||
// ul {
|
|
||||||
// {(0..10).map(|f| rsx!( li {"Element {f}"} ))}
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// };
|
|
||||||
+14
-12
@@ -24,7 +24,7 @@ fn main() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub static App: Component = |cx| {
|
pub fn App(cx: Scope) -> Element {
|
||||||
cx.render(rsx!(
|
cx.render(rsx!(
|
||||||
div { class: "overflow-hidden"
|
div { class: "overflow-hidden"
|
||||||
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" }
|
||||||
@@ -37,9 +37,9 @@ pub static App: Component = |cx| {
|
|||||||
Hero {}
|
Hero {}
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
};
|
}
|
||||||
|
|
||||||
pub static Header: Component = |cx| {
|
pub fn Header(cx: Scope) -> Element {
|
||||||
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"
|
||||||
@@ -63,9 +63,9 @@ pub static Header: Component = |cx| {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
pub static Hero: Component = |cx| {
|
pub fn Hero(cx: Scope) -> Element {
|
||||||
//
|
//
|
||||||
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"
|
||||||
@@ -102,8 +102,9 @@ pub static Hero: Component = |cx| {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
}
|
||||||
pub static Entry: Component = |cx| {
|
|
||||||
|
pub fn Entry(cx: Scope) -> Element {
|
||||||
//
|
//
|
||||||
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"
|
||||||
@@ -114,9 +115,9 @@ pub static Entry: Component = |cx| {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
pub static StacksIcon: Component = |cx| {
|
pub fn StacksIcon(cx: Scope) -> Element {
|
||||||
cx.render(rsx!(
|
cx.render(rsx!(
|
||||||
svg {
|
svg {
|
||||||
xmlns: "http://www.w3.org/2000/svg"
|
xmlns: "http://www.w3.org/2000/svg"
|
||||||
@@ -130,8 +131,9 @@ pub static StacksIcon: Component = |cx| {
|
|||||||
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"}
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
};
|
}
|
||||||
pub static RightArrowIcon: Component = |cx| {
|
|
||||||
|
pub fn RightArrowIcon(cx: Scope) -> Element {
|
||||||
cx.render(rsx!(
|
cx.render(rsx!(
|
||||||
svg {
|
svg {
|
||||||
fill: "none"
|
fill: "none"
|
||||||
@@ -144,4 +146,4 @@ pub static RightArrowIcon: Component = |cx| {
|
|||||||
path { d: "M5 12h14M12 5l7 7-7 7"}
|
path { d: "M5 12h14M12 5l7 7-7 7"}
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
};
|
}
|
||||||
+12
-12
@@ -14,7 +14,7 @@ fn main() {
|
|||||||
|
|
||||||
const STYLE: &str = "body {overflow:hidden;}";
|
const STYLE: &str = "body {overflow:hidden;}";
|
||||||
|
|
||||||
pub static App: Component = |cx| {
|
pub fn App(cx: Scope) -> Element {
|
||||||
cx.render(rsx!(
|
cx.render(rsx!(
|
||||||
div { class: "overflow-hidden",
|
div { class: "overflow-hidden",
|
||||||
style { "{STYLE}" }
|
style { "{STYLE}" }
|
||||||
@@ -28,9 +28,9 @@ pub static App: Component = |cx| {
|
|||||||
Hero {}
|
Hero {}
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
};
|
}
|
||||||
|
|
||||||
pub static Header: Component = |cx| {
|
pub fn Header(cx: Scope) -> Element {
|
||||||
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",
|
||||||
@@ -54,9 +54,9 @@ pub static Header: Component = |cx| {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
pub static Hero: Component = |cx| {
|
pub fn Hero(cx: Scope) -> Element {
|
||||||
//
|
//
|
||||||
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",
|
||||||
@@ -93,8 +93,8 @@ pub static Hero: Component = |cx| {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
}
|
||||||
pub static Entry: Component = |cx| {
|
pub fn Entry(cx: Scope) -> Element {
|
||||||
//
|
//
|
||||||
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",
|
||||||
@@ -105,9 +105,9 @@ pub static Entry: Component = |cx| {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
pub static StacksIcon: Component = |cx| {
|
pub fn StacksIcon(cx: Scope) -> Element {
|
||||||
cx.render(rsx!(
|
cx.render(rsx!(
|
||||||
svg {
|
svg {
|
||||||
// xmlns: "http://www.w3.org/2000/svg"
|
// xmlns: "http://www.w3.org/2000/svg"
|
||||||
@@ -121,8 +121,8 @@ pub static StacksIcon: Component = |cx| {
|
|||||||
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"}
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
};
|
}
|
||||||
pub static RightArrowIcon: Component = |cx| {
|
pub fn RightArrowIcon(cx: Scope) -> Element {
|
||||||
cx.render(rsx!(
|
cx.render(rsx!(
|
||||||
svg {
|
svg {
|
||||||
fill: "none",
|
fill: "none",
|
||||||
@@ -135,4 +135,4 @@ pub static RightArrowIcon: Component = |cx| {
|
|||||||
path { d: "M5 12h14M12 5l7 7-7 7"}
|
path { d: "M5 12h14M12 5l7 7-7 7"}
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
};
|
}
|
||||||
+7
-6
@@ -12,13 +12,14 @@ fn main() {
|
|||||||
fn app(cx: Scope) -> Element {
|
fn app(cx: Scope) -> Element {
|
||||||
let count = use_state(&cx, || 0);
|
let count = use_state(&cx, || 0);
|
||||||
|
|
||||||
use_future(&cx, || {
|
use_future(&cx, move || {
|
||||||
for_async![count];
|
let count = UseState::for_async(&count);
|
||||||
|
// for_async![count];
|
||||||
async move {
|
async move {
|
||||||
loop {
|
// loop {
|
||||||
tokio::time::sleep(Duration::from_millis(1000)).await;
|
// tokio::time::sleep(Duration::from_millis(1000)).await;
|
||||||
count += 1;
|
// count += 1;
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+49
-45
@@ -4,7 +4,7 @@ use im_rc::HashMap;
|
|||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
dioxus::desktop::launch(App);
|
dioxus::desktop::launch(app);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq)]
|
#[derive(PartialEq)]
|
||||||
@@ -21,8 +21,7 @@ pub struct TodoItem {
|
|||||||
pub contents: String,
|
pub contents: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
const STYLE: &str = include_str!("./assets/todomvc.css");
|
fn app(cx: Scope) -> Element {
|
||||||
const App: Component = |cx| {
|
|
||||||
let draft = use_state(&cx, || "".to_string());
|
let draft = use_state(&cx, || "".to_string());
|
||||||
let todos = use_state(&cx, || HashMap::<u32, Rc<TodoItem>>::new());
|
let todos = use_state(&cx, || HashMap::<u32, Rc<TodoItem>>::new());
|
||||||
let filter = use_state(&cx, || FilterState::All);
|
let filter = use_state(&cx, || FilterState::All);
|
||||||
@@ -48,40 +47,43 @@ const App: Component = |cx| {
|
|||||||
_ => "items",
|
_ => "items",
|
||||||
};
|
};
|
||||||
|
|
||||||
rsx!(cx, div { id: "app",
|
cx.render(rsx!(
|
||||||
style {"{STYLE}"}
|
div { id: "app",
|
||||||
div {
|
style { [include_str!("./assets/todomvc.css")] }
|
||||||
header { class: "header",
|
div {
|
||||||
h1 {"todos"}
|
header {
|
||||||
input {
|
class: "header",
|
||||||
class: "new-todo",
|
h1 { "todos" }
|
||||||
placeholder: "What needs to be done?",
|
input {
|
||||||
value: "{draft}",
|
class: "new-todo",
|
||||||
oninput: move |evt| draft.set(evt.value.clone()),
|
placeholder: "What needs to be done?",
|
||||||
|
value: "{draft}",
|
||||||
|
oninput: move |evt| draft.set(evt.value.clone()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
todolist,
|
||||||
|
(!todos.is_empty()).then(|| rsx!(
|
||||||
|
footer {
|
||||||
|
span {
|
||||||
|
strong {"{items_left}"}
|
||||||
|
span {"{item_text} left"}
|
||||||
|
}
|
||||||
|
ul { class: "filters",
|
||||||
|
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: "Completed", a { href: "completed", onclick: move |_| filter.set(FilterState::Completed), "Completed" }}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
))
|
||||||
|
}
|
||||||
|
footer { class: "info",
|
||||||
|
p {"Double-click to edit a todo"}
|
||||||
|
p { "Created by ", a { href: "http://github.com/jkelleyrtp/", "jkelleyrtp" }}
|
||||||
|
p { "Part of ", a { href: "http://todomvc.com", "TodoMVC" }}
|
||||||
}
|
}
|
||||||
todolist,
|
|
||||||
(!todos.is_empty()).then(|| rsx!(
|
|
||||||
footer {
|
|
||||||
span {
|
|
||||||
strong {"{items_left}"}
|
|
||||||
span {"{item_text} left"}
|
|
||||||
}
|
|
||||||
ul { class: "filters",
|
|
||||||
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: "Completed", a { href: "completed", onclick: move |_| filter.set(FilterState::Completed), "Completed" }}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
footer { class: "info",
|
))
|
||||||
p {"Double-click to edit a todo"}
|
}
|
||||||
p { "Created by ", a { href: "http://github.com/jkelleyrtp/", "jkelleyrtp" }}
|
|
||||||
p { "Part of ", a { href: "http://todomvc.com", "TodoMVC" }}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(PartialEq, Props)]
|
#[derive(PartialEq, Props)]
|
||||||
pub struct TodoEntryProps {
|
pub struct TodoEntryProps {
|
||||||
@@ -93,18 +95,20 @@ pub fn TodoEntry(cx: Scope<TodoEntryProps>) -> Element {
|
|||||||
let contents = use_state(&cx, || String::from(""));
|
let contents = use_state(&cx, || String::from(""));
|
||||||
let todo = &cx.props.todo;
|
let todo = &cx.props.todo;
|
||||||
|
|
||||||
rsx!(cx, li {
|
cx.render(rsx! {
|
||||||
"{todo.id}"
|
li {
|
||||||
input {
|
"{todo.id}"
|
||||||
class: "toggle",
|
|
||||||
r#type: "checkbox",
|
|
||||||
"{todo.checked}"
|
|
||||||
}
|
|
||||||
{is_editing.then(|| rsx!{
|
|
||||||
input {
|
input {
|
||||||
value: "{contents}",
|
class: "toggle",
|
||||||
oninput: move |evt| contents.set(evt.value.clone())
|
r#type: "checkbox",
|
||||||
|
"{todo.checked}"
|
||||||
}
|
}
|
||||||
})}
|
is_editing.then(|| rsx!{
|
||||||
|
input {
|
||||||
|
value: "{contents}",
|
||||||
|
oninput: move |evt| contents.set(evt.value.clone())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
+13
-13
@@ -7,22 +7,23 @@
|
|||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
dioxus::desktop::launch(App);
|
dioxus::desktop::launch(app);
|
||||||
}
|
}
|
||||||
|
|
||||||
const ENDPOINT: &str = "https://api.openweathermap.org/data/2.5/weather";
|
const ENDPOINT: &str = "https://api.openweathermap.org/data/2.5/weather";
|
||||||
|
|
||||||
static App: Component = |cx| {
|
fn app(cx: Scope) -> Element {
|
||||||
//
|
//
|
||||||
let body = use_suspense(
|
let body = use_suspense(
|
||||||
cx,
|
&cx,
|
||||||
|| async {
|
|| async {
|
||||||
let content = reqwest::get(ENDPOINT)
|
todo!()
|
||||||
.await
|
// let content = reqwest::get(ENDPOINT)
|
||||||
.unwrap()
|
// .await
|
||||||
.json::<serde_json::Value>()
|
// .unwrap()
|
||||||
.await
|
// .json::<serde_json::Value>()
|
||||||
.unwrap();
|
// .await
|
||||||
|
// .unwrap();
|
||||||
},
|
},
|
||||||
|props| {
|
|props| {
|
||||||
//
|
//
|
||||||
@@ -35,13 +36,12 @@ static App: Component = |cx| {
|
|||||||
{body}
|
{body}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Props)]
|
#[derive(PartialEq, Props)]
|
||||||
struct WeatherProps {}
|
struct WeatherProps {}
|
||||||
|
|
||||||
static WeatherDisplay: Component<WeatherProps> = |cx| {
|
fn WeatherDisplay(cx: Scope<WeatherProps>) -> Element {
|
||||||
//
|
|
||||||
cx.render(rsx!(
|
cx.render(rsx!(
|
||||||
div { class: "flex items-center justify-center flex-col",
|
div { class: "flex items-center justify-center flex-col",
|
||||||
div { class: "flex items-center justify-center",
|
div { class: "flex items-center justify-center",
|
||||||
@@ -64,4 +64,4 @@ static WeatherDisplay: Component<WeatherProps> = |cx| {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
};
|
}
|
||||||
@@ -13,10 +13,10 @@
|
|||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
dioxus::desktop::launch(App);
|
dioxus::desktop::launch(app);
|
||||||
}
|
}
|
||||||
|
|
||||||
static App: Component = |cx| {
|
fn app(cx: Scope) -> Element {
|
||||||
let mut rng = SmallRng::from_entropy();
|
let mut rng = SmallRng::from_entropy();
|
||||||
|
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
@@ -29,7 +29,7 @@ static App: Component = |cx| {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Props)]
|
#[derive(PartialEq, Props)]
|
||||||
struct RowProps {
|
struct RowProps {
|
||||||
|
|||||||
+7
-11
@@ -1,3 +1,7 @@
|
|||||||
|
//! XSS Safety
|
||||||
|
//!
|
||||||
|
//! This example proves that Dioxus is safe from XSS attacks.
|
||||||
|
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
@@ -9,19 +13,11 @@ fn app(cx: Scope) -> Element {
|
|||||||
|
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
div {
|
div {
|
||||||
"hello world!"
|
h3 { "{contents}" }
|
||||||
|
|
||||||
h1 { "{contents}" }
|
|
||||||
|
|
||||||
h3 { [contents.as_str()] }
|
|
||||||
|
|
||||||
input {
|
input {
|
||||||
value: "{contents}",
|
value: "{contents}",
|
||||||
oninput: move |e| {
|
r#type: "text",
|
||||||
contents.set(e.value.clone());
|
oninput: move |e| contents.set(e.value.clone()),
|
||||||
eprintln!("asd");
|
|
||||||
},
|
|
||||||
"type": "text",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -99,8 +99,8 @@ impl ToTokens for InlinePropsBody {
|
|||||||
};
|
};
|
||||||
|
|
||||||
out_tokens.append_all(quote! {
|
out_tokens.append_all(quote! {
|
||||||
#[allow(non_camel_case)]
|
|
||||||
#modifiers
|
#modifiers
|
||||||
|
#[allow(non_camel_case_types)]
|
||||||
#vis struct #struct_name #generics {
|
#vis struct #struct_name #generics {
|
||||||
#(#fields),*
|
#(#fields),*
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,277 +0,0 @@
|
|||||||
use dioxus_core::prelude::*;
|
|
||||||
use std::{
|
|
||||||
cell::{Cell, Ref, RefCell, RefMut},
|
|
||||||
fmt::{Debug, Display},
|
|
||||||
rc::Rc,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Store state between component renders!
|
|
||||||
///
|
|
||||||
/// ## Dioxus equivalent of useState, designed for Rust
|
|
||||||
///
|
|
||||||
/// The Dioxus version of `useState` for state management inside components. It allows you to ergonomically store and
|
|
||||||
/// modify state between component renders. When the state is updated, the component will re-render.
|
|
||||||
///
|
|
||||||
/// Dioxus' use_state basically wraps a RefCell with helper methods and integrates it with the VirtualDOM update system.
|
|
||||||
///
|
|
||||||
/// [`use_state`] exposes a few helper methods to modify the underlying state:
|
|
||||||
/// - `.set(new)` allows you to override the "work in progress" value with a new value
|
|
||||||
/// - `.get_mut()` allows you to modify the WIP value
|
|
||||||
/// - `.get_wip()` allows you to access the WIP value
|
|
||||||
/// - `.deref()` provides the previous value (often done implicitly, though a manual dereference with `*` might be required)
|
|
||||||
///
|
|
||||||
/// Additionally, a ton of std::ops traits are implemented for the `UseState` wrapper, meaning any mutative type operations
|
|
||||||
/// will automatically be called on the WIP value.
|
|
||||||
///
|
|
||||||
/// ## Combinators
|
|
||||||
///
|
|
||||||
/// On top of the methods to set/get state, `use_state` also supports fancy combinators to extend its functionality:
|
|
||||||
/// - `.classic()` and `.split()` convert the hook into the classic React-style hook
|
|
||||||
/// ```rust
|
|
||||||
/// let (state, set_state) = use_state(&cx, || 10).split()
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
///
|
|
||||||
/// Usage:
|
|
||||||
///
|
|
||||||
/// ```ignore
|
|
||||||
/// const Example: Component = |cx| {
|
|
||||||
/// let counter = use_state(&cx, || 0);
|
|
||||||
///
|
|
||||||
/// cx.render(rsx! {
|
|
||||||
/// div {
|
|
||||||
/// h1 { "Counter: {counter}" }
|
|
||||||
/// button { onclick: move |_| counter += 1, "Increment" }
|
|
||||||
/// button { onclick: move |_| counter -= 1, "Decrement" }
|
|
||||||
/// }
|
|
||||||
/// ))
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
pub fn use_state<'a, T: 'static>(
|
|
||||||
cx: &'a ScopeState,
|
|
||||||
initial_state_fn: impl FnOnce() -> T,
|
|
||||||
) -> UseState<'a, T> {
|
|
||||||
let hook = cx.use_hook(move |_| {
|
|
||||||
let first_val = initial_state_fn();
|
|
||||||
UseStateInner {
|
|
||||||
current_val: Rc::new(first_val),
|
|
||||||
update_callback: cx.schedule_update(),
|
|
||||||
wip: Rc::new(RefCell::new(None)),
|
|
||||||
update_scheuled: Cell::new(false),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
hook.update_scheuled.set(false);
|
|
||||||
let mut new_val = hook.wip.borrow_mut();
|
|
||||||
if new_val.is_some() {
|
|
||||||
// if there's only one reference (weak or otherwise), we can just swap the values
|
|
||||||
if let Some(val) = Rc::get_mut(&mut hook.current_val) {
|
|
||||||
*val = new_val.take().unwrap();
|
|
||||||
} else {
|
|
||||||
hook.current_val = Rc::new(new_val.take().unwrap());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
UseState { inner: &*hook }
|
|
||||||
}
|
|
||||||
struct UseStateInner<T: 'static> {
|
|
||||||
current_val: Rc<T>,
|
|
||||||
update_scheuled: Cell<bool>,
|
|
||||||
update_callback: Rc<dyn Fn()>,
|
|
||||||
wip: Rc<RefCell<Option<T>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct UseState<'a, T: 'static> {
|
|
||||||
inner: &'a UseStateInner<T>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Copy for UseState<'_, T> {}
|
|
||||||
impl<'a, T> Clone for UseState<'a, T>
|
|
||||||
where
|
|
||||||
T: 'static,
|
|
||||||
{
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
UseState { inner: self.inner }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Debug> Debug for UseState<'_, T> {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{:?}", self.inner.current_val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'a, T: 'static> UseState<'a, T> {
|
|
||||||
/// Tell the Dioxus Scheduler that we need to be processed
|
|
||||||
pub fn needs_update(&self) {
|
|
||||||
if !self.inner.update_scheuled.get() {
|
|
||||||
self.inner.update_scheuled.set(true);
|
|
||||||
(self.inner.update_callback)();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set(&self, new_val: T) {
|
|
||||||
*self.inner.wip.borrow_mut() = Some(new_val);
|
|
||||||
self.needs_update();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get(&self) -> &'a T {
|
|
||||||
&self.inner.current_val
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_rc(&self) -> &'a Rc<T> {
|
|
||||||
&self.inner.current_val
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the current status of the work-in-progress data
|
|
||||||
pub fn get_wip(&self) -> Ref<Option<T>> {
|
|
||||||
self.inner.wip.borrow()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the current status of the work-in-progress data
|
|
||||||
pub fn get_wip_mut(&self) -> RefMut<Option<T>> {
|
|
||||||
self.inner.wip.borrow_mut()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn classic(self) -> (&'a T, Rc<dyn Fn(T)>) {
|
|
||||||
(&self.inner.current_val, self.setter())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn setter(&self) -> Rc<dyn Fn(T)> {
|
|
||||||
let slot = self.inner.wip.clone();
|
|
||||||
Rc::new(move |new| {
|
|
||||||
*slot.borrow_mut() = Some(new);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn for_async(self) -> UseState<'static, T> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn wtih(self, f: impl FnOnce(&mut T)) {
|
|
||||||
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> {
|
|
||||||
/// Gain mutable access to the new value via [`RefMut`].
|
|
||||||
///
|
|
||||||
/// If `modify` is called, then the component will re-render.
|
|
||||||
///
|
|
||||||
/// This method is only available when the value is a `ToOwned` type.
|
|
||||||
///
|
|
||||||
/// Mutable access is derived by calling "ToOwned" (IE cloning) on the current value.
|
|
||||||
///
|
|
||||||
/// To get a reference to the current value, use `.get()`
|
|
||||||
pub fn modify(self) -> RefMut<'a, T> {
|
|
||||||
// make sure we get processed
|
|
||||||
self.needs_update();
|
|
||||||
|
|
||||||
// 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.inner.wip.borrow_mut(), |slot| {
|
|
||||||
if slot.is_none() {
|
|
||||||
*slot = Some(self.inner.current_val.as_ref().to_owned());
|
|
||||||
}
|
|
||||||
slot.as_mut().unwrap()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn inner(self) -> T {
|
|
||||||
self.inner.current_val.as_ref().to_owned()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, T> std::ops::Deref for UseState<'a, T> {
|
|
||||||
type Target = T;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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};
|
|
||||||
|
|
||||||
impl<'a, T: Copy + Add<T, Output = T>> Add<T> for UseState<'a, T> {
|
|
||||||
type Output = T;
|
|
||||||
|
|
||||||
fn add(self, rhs: T) -> Self::Output {
|
|
||||||
self.inner.current_val.add(rhs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'a, T: Copy + Add<T, Output = T>> AddAssign<T> for UseState<'a, T> {
|
|
||||||
fn add_assign(&mut self, rhs: T) {
|
|
||||||
self.set(self.inner.current_val.add(rhs));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'a, T: Copy + Sub<T, Output = T>> Sub<T> for UseState<'a, T> {
|
|
||||||
type Output = T;
|
|
||||||
|
|
||||||
fn sub(self, rhs: T) -> Self::Output {
|
|
||||||
self.inner.current_val.sub(rhs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'a, T: Copy + Sub<T, Output = T>> SubAssign<T> for UseState<'a, T> {
|
|
||||||
fn sub_assign(&mut self, rhs: T) {
|
|
||||||
self.set(self.inner.current_val.sub(rhs));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// MUL
|
|
||||||
impl<'a, T: Copy + Mul<T, Output = T>> Mul<T> for UseState<'a, T> {
|
|
||||||
type Output = T;
|
|
||||||
|
|
||||||
fn mul(self, rhs: T) -> Self::Output {
|
|
||||||
self.inner.current_val.mul(rhs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'a, T: Copy + Mul<T, Output = T>> MulAssign<T> for UseState<'a, T> {
|
|
||||||
fn mul_assign(&mut self, rhs: T) {
|
|
||||||
self.set(self.inner.current_val.mul(rhs));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// DIV
|
|
||||||
impl<'a, T: Copy + Div<T, Output = T>> Div<T> for UseState<'a, T> {
|
|
||||||
type Output = T;
|
|
||||||
|
|
||||||
fn div(self, rhs: T) -> Self::Output {
|
|
||||||
self.inner.current_val.div(rhs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'a, T: Copy + Div<T, Output = T>> DivAssign<T> for UseState<'a, T> {
|
|
||||||
fn div_assign(&mut self, rhs: T) {
|
|
||||||
self.set(self.inner.current_val.div(rhs));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,211 @@
|
|||||||
|
use super::owned::UseStateOwned;
|
||||||
|
use std::{
|
||||||
|
cell::{Ref, RefMut},
|
||||||
|
fmt::{Debug, Display},
|
||||||
|
rc::Rc,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct UseState<'a, T: 'static>(pub(crate) &'a UseStateOwned<T>);
|
||||||
|
|
||||||
|
impl<T> Copy for UseState<'_, T> {}
|
||||||
|
|
||||||
|
impl<'a, T: 'static> Clone for UseState<'a, T> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
UseState(self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Debug> Debug for UseState<'_, T> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{:?}", self.0.current_val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: 'static> UseState<'a, T> {
|
||||||
|
/// Tell the Dioxus Scheduler that we need to be processed
|
||||||
|
pub fn needs_update(&self) {
|
||||||
|
if !self.0.update_scheuled.get() {
|
||||||
|
self.0.update_scheuled.set(true);
|
||||||
|
(self.0.update_callback)();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set(&self, new_val: T) {
|
||||||
|
*self.0.wip.borrow_mut() = Some(new_val);
|
||||||
|
self.needs_update();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self) -> &'a T {
|
||||||
|
&self.0.current_val
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_rc(&self) -> &'a Rc<T> {
|
||||||
|
&self.0.current_val
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the current status of the work-in-progress data
|
||||||
|
pub fn get_wip(&self) -> Ref<Option<T>> {
|
||||||
|
self.0.wip.borrow()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the current status of the work-in-progress data
|
||||||
|
pub fn get_wip_mut(&self) -> RefMut<Option<T>> {
|
||||||
|
self.0.wip.borrow_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn classic(self) -> (&'a T, Rc<dyn Fn(T)>) {
|
||||||
|
(&self.0.current_val, self.setter())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setter(&self) -> Rc<dyn Fn(T)> {
|
||||||
|
let slot = self.0.wip.clone();
|
||||||
|
Rc::new(move |new| *slot.borrow_mut() = Some(new))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn wtih(&self, f: impl FnOnce(&mut T)) {
|
||||||
|
let mut val = self.0.wip.borrow_mut();
|
||||||
|
|
||||||
|
if let Some(inner) = val.as_mut() {
|
||||||
|
f(inner);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn for_async(&self) -> UseStateOwned<T> {
|
||||||
|
let UseStateOwned {
|
||||||
|
current_val,
|
||||||
|
wip,
|
||||||
|
update_callback,
|
||||||
|
update_scheuled,
|
||||||
|
} = self.0;
|
||||||
|
|
||||||
|
UseStateOwned {
|
||||||
|
current_val: current_val.clone(),
|
||||||
|
wip: wip.clone(),
|
||||||
|
update_callback: update_callback.clone(),
|
||||||
|
update_scheuled: update_scheuled.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: 'static + ToOwned<Owned = T>> UseState<'a, T> {
|
||||||
|
/// Gain mutable access to the new value via [`RefMut`].
|
||||||
|
///
|
||||||
|
/// If `modify` is called, then the component will re-render.
|
||||||
|
///
|
||||||
|
/// This method is only available when the value is a `ToOwned` type.
|
||||||
|
///
|
||||||
|
/// Mutable access is derived by calling "ToOwned" (IE cloning) on the current value.
|
||||||
|
///
|
||||||
|
/// To get a reference to the current value, use `.get()`
|
||||||
|
pub fn modify(self) -> RefMut<'a, T> {
|
||||||
|
// make sure we get processed
|
||||||
|
self.needs_update();
|
||||||
|
|
||||||
|
// 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.0.wip.borrow_mut(), |slot| {
|
||||||
|
if slot.is_none() {
|
||||||
|
*slot = Some(self.0.current_val.as_ref().to_owned());
|
||||||
|
}
|
||||||
|
slot.as_mut().unwrap()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn inner(self) -> T {
|
||||||
|
self.0.current_val.as_ref().to_owned()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> std::ops::Deref for UseState<'a, T> {
|
||||||
|
type Target = T;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
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.0.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};
|
||||||
|
|
||||||
|
impl<'a, T: Copy + Add<T, Output = T>> Add<T> for UseState<'a, T> {
|
||||||
|
type Output = T;
|
||||||
|
|
||||||
|
fn add(self, rhs: T) -> Self::Output {
|
||||||
|
self.0.current_val.add(rhs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a, T: Copy + Add<T, Output = T>> AddAssign<T> for UseState<'a, T> {
|
||||||
|
fn add_assign(&mut self, rhs: T) {
|
||||||
|
self.set(self.0.current_val.add(rhs));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sub
|
||||||
|
impl<'a, T: Copy + Sub<T, Output = T>> Sub<T> for UseState<'a, T> {
|
||||||
|
type Output = T;
|
||||||
|
|
||||||
|
fn sub(self, rhs: T) -> Self::Output {
|
||||||
|
self.0.current_val.sub(rhs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a, T: Copy + Sub<T, Output = T>> SubAssign<T> for UseState<'a, T> {
|
||||||
|
fn sub_assign(&mut self, rhs: T) {
|
||||||
|
self.set(self.0.current_val.sub(rhs));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// MUL
|
||||||
|
impl<'a, T: Copy + Mul<T, Output = T>> Mul<T> for UseState<'a, T> {
|
||||||
|
type Output = T;
|
||||||
|
|
||||||
|
fn mul(self, rhs: T) -> Self::Output {
|
||||||
|
self.0.current_val.mul(rhs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a, T: Copy + Mul<T, Output = T>> MulAssign<T> for UseState<'a, T> {
|
||||||
|
fn mul_assign(&mut self, rhs: T) {
|
||||||
|
self.set(self.0.current_val.mul(rhs));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// DIV
|
||||||
|
impl<'a, T: Copy + Div<T, Output = T>> Div<T> for UseState<'a, T> {
|
||||||
|
type Output = T;
|
||||||
|
|
||||||
|
fn div(self, rhs: T) -> Self::Output {
|
||||||
|
self.0.current_val.div(rhs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a, T: Copy + Div<T, Output = T>> DivAssign<T> for UseState<'a, T> {
|
||||||
|
fn div_assign(&mut self, rhs: T) {
|
||||||
|
self.set(self.0.current_val.div(rhs));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
mod handle;
|
||||||
|
mod owned;
|
||||||
|
pub use handle::*;
|
||||||
|
pub use owned::*;
|
||||||
|
|
||||||
|
use dioxus_core::prelude::*;
|
||||||
|
use std::{
|
||||||
|
cell::{Cell, RefCell},
|
||||||
|
rc::Rc,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Store state between component renders!
|
||||||
|
///
|
||||||
|
/// ## Dioxus equivalent of useState, designed for Rust
|
||||||
|
///
|
||||||
|
/// The Dioxus version of `useState` for state management inside components. It allows you to ergonomically store and
|
||||||
|
/// modify state between component renders. When the state is updated, the component will re-render.
|
||||||
|
///
|
||||||
|
/// Dioxus' use_state basically wraps a RefCell with helper methods and integrates it with the VirtualDOM update system.
|
||||||
|
///
|
||||||
|
/// [`use_state`] exposes a few helper methods to modify the underlying state:
|
||||||
|
/// - `.set(new)` allows you to override the "work in progress" value with a new value
|
||||||
|
/// - `.get_mut()` allows you to modify the WIP value
|
||||||
|
/// - `.get_wip()` allows you to access the WIP value
|
||||||
|
/// - `.deref()` provides the previous value (often done implicitly, though a manual dereference with `*` might be required)
|
||||||
|
///
|
||||||
|
/// Additionally, a ton of std::ops traits are implemented for the `UseState` wrapper, meaning any mutative type operations
|
||||||
|
/// will automatically be called on the WIP value.
|
||||||
|
///
|
||||||
|
/// ## Combinators
|
||||||
|
///
|
||||||
|
/// On top of the methods to set/get state, `use_state` also supports fancy combinators to extend its functionality:
|
||||||
|
/// - `.classic()` and `.split()` convert the hook into the classic React-style hook
|
||||||
|
/// ```rust
|
||||||
|
/// let (state, set_state) = use_state(&cx, || 10).split()
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// Usage:
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// const Example: Component = |cx| {
|
||||||
|
/// let counter = use_state(&cx, || 0);
|
||||||
|
///
|
||||||
|
/// cx.render(rsx! {
|
||||||
|
/// div {
|
||||||
|
/// h1 { "Counter: {counter}" }
|
||||||
|
/// button { onclick: move |_| counter += 1, "Increment" }
|
||||||
|
/// button { onclick: move |_| counter -= 1, "Decrement" }
|
||||||
|
/// }
|
||||||
|
/// ))
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn use_state<'a, T: 'static>(
|
||||||
|
cx: &'a ScopeState,
|
||||||
|
initial_state_fn: impl FnOnce() -> T,
|
||||||
|
) -> UseState<'a, T> {
|
||||||
|
let hook = cx.use_hook(move |_| UseStateOwned {
|
||||||
|
current_val: Rc::new(initial_state_fn()),
|
||||||
|
update_callback: cx.schedule_update(),
|
||||||
|
wip: Rc::new(RefCell::new(None)),
|
||||||
|
update_scheuled: Cell::new(false),
|
||||||
|
});
|
||||||
|
|
||||||
|
hook.update_scheuled.set(false);
|
||||||
|
let mut new_val = hook.wip.borrow_mut();
|
||||||
|
|
||||||
|
if new_val.is_some() {
|
||||||
|
// if there's only one reference (weak or otherwise), we can just swap the values
|
||||||
|
if let Some(val) = Rc::get_mut(&mut hook.current_val) {
|
||||||
|
*val = new_val.take().unwrap();
|
||||||
|
} else {
|
||||||
|
hook.current_val = Rc::new(new_val.take().unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UseState(hook)
|
||||||
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
use std::{
|
||||||
|
cell::{Cell, Ref, RefCell, RefMut},
|
||||||
|
fmt::{Debug, Display},
|
||||||
|
rc::Rc,
|
||||||
|
};
|
||||||
|
pub struct UseStateOwned<T: 'static> {
|
||||||
|
// this will always be outdated
|
||||||
|
pub(crate) current_val: Rc<T>,
|
||||||
|
pub(crate) wip: Rc<RefCell<Option<T>>>,
|
||||||
|
pub(crate) update_callback: Rc<dyn Fn()>,
|
||||||
|
pub(crate) update_scheuled: Cell<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> UseStateOwned<T> {
|
||||||
|
pub fn get(&self) -> Ref<T> {
|
||||||
|
Ref::map(self.wip.borrow(), |x| x.as_ref().unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set(&self, new_val: T) {
|
||||||
|
*self.wip.borrow_mut() = Some(new_val);
|
||||||
|
(self.update_callback)();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn modify(&self) -> RefMut<T> {
|
||||||
|
RefMut::map(self.wip.borrow_mut(), |x| x.as_mut().unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with(&self, f: impl FnOnce(&mut T)) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};
|
||||||
|
|
||||||
|
impl<T: Debug> Debug for UseStateOwned<T> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{:?}", self.current_val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// enable displaty for the handle
|
||||||
|
impl<'a, T: 'static + Display> std::fmt::Display for UseStateOwned<T> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.current_val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: Copy + Add<T, Output = T>> Add<T> for UseStateOwned<T> {
|
||||||
|
type Output = T;
|
||||||
|
|
||||||
|
fn add(self, rhs: T) -> Self::Output {
|
||||||
|
self.current_val.add(rhs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: Copy + Add<T, Output = T>> AddAssign<T> for UseStateOwned<T> {
|
||||||
|
fn add_assign(&mut self, rhs: T) {
|
||||||
|
self.set(self.current_val.add(rhs));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sub
|
||||||
|
impl<'a, T: Copy + Sub<T, Output = T>> Sub<T> for UseStateOwned<T> {
|
||||||
|
type Output = T;
|
||||||
|
|
||||||
|
fn sub(self, rhs: T) -> Self::Output {
|
||||||
|
self.current_val.sub(rhs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a, T: Copy + Sub<T, Output = T>> SubAssign<T> for UseStateOwned<T> {
|
||||||
|
fn sub_assign(&mut self, rhs: T) {
|
||||||
|
self.set(self.current_val.sub(rhs));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// MUL
|
||||||
|
impl<'a, T: Copy + Mul<T, Output = T>> Mul<T> for UseStateOwned<T> {
|
||||||
|
type Output = T;
|
||||||
|
|
||||||
|
fn mul(self, rhs: T) -> Self::Output {
|
||||||
|
self.current_val.mul(rhs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a, T: Copy + Mul<T, Output = T>> MulAssign<T> for UseStateOwned<T> {
|
||||||
|
fn mul_assign(&mut self, rhs: T) {
|
||||||
|
self.set(self.current_val.mul(rhs));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// DIV
|
||||||
|
impl<'a, T: Copy + Div<T, Output = T>> Div<T> for UseStateOwned<T> {
|
||||||
|
type Output = T;
|
||||||
|
|
||||||
|
fn div(self, rhs: T) -> Self::Output {
|
||||||
|
self.current_val.div(rhs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a, T: Copy + Div<T, Output = T>> DivAssign<T> for UseStateOwned<T> {
|
||||||
|
fn div_assign(&mut self, rhs: T) {
|
||||||
|
self.set(self.current_val.div(rhs));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user