mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-10 06:34:20 +00:00
Merge pull request #53 from DioxusLabs/jk/polish
docs: remove all usages of static closure syntax and update readme
This commit is contained in:
commit
d7e967ba5b
66 changed files with 792 additions and 2019 deletions
28
README.md
28
README.md
|
@ -22,19 +22,24 @@
|
|||
alt="docs.rs docs" />
|
||||
</a>
|
||||
<!-- 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"
|
||||
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>
|
||||
</div>
|
||||
|
||||
<div align="center">
|
||||
<h3>
|
||||
<a href="https://dioxuslabs.com/guide"> Guide </a>
|
||||
<span> | </span>
|
||||
<a href="https://dioxuslabs.com"> Website </a>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
|
@ -66,6 +71,15 @@ If you know React, then you already know Dioxus.
|
|||
- Multi-channel asynchronous scheduler for first-class async support.
|
||||
- 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...
|
||||
|
||||
<table style="width:100%" align="center">
|
||||
|
@ -80,8 +94,7 @@ If you know React, then you already know Dioxus.
|
|||
</table>
|
||||
|
||||
|
||||
|
||||
## Examples Projects:
|
||||
## Example Projects:
|
||||
|
||||
| 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.
|
||||
|
||||
## 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?
|
||||
|
||||
|
|
|
@ -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`:
|
||||
|
||||
```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?
|
||||
|
||||
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.
|
||||
|
|
|
@ -115,7 +115,7 @@ fn App(cx: Scope) -> Element {
|
|||
|
||||
Tiny components:
|
||||
```rust
|
||||
static App: Component = |cx, _| rsx!(cx, div {"hello world!"});
|
||||
static App: Component = |cx| rsx!(cx, div {"hello world!"});
|
||||
```
|
||||
|
||||
Borrowed prop contents:
|
||||
|
|
|
@ -17,27 +17,32 @@ fn app(cx: Scope) -> Element {
|
|||
|
||||
let (async_count, dir) = (count.for_async(), *direction);
|
||||
|
||||
let task = use_coroutine(&cx, move || async move {
|
||||
loop {
|
||||
TimeoutFuture::new(250).await;
|
||||
*async_count.modify() += dir;
|
||||
let task = use_coroutine(&cx, move || {
|
||||
//
|
||||
async move {
|
||||
loop {
|
||||
TimeoutFuture::new(250).await;
|
||||
// *async_count.modify() += dir;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
rsx!(cx, div {
|
||||
h1 {"count is {count}"}
|
||||
button { onclick: move |_| task.stop(),
|
||||
"Stop counting"
|
||||
}
|
||||
button { onclick: move |_| task.resume(),
|
||||
"Start counting"
|
||||
}
|
||||
button {
|
||||
onclick: move |_| {
|
||||
*direction.modify() *= -1;
|
||||
task.restart();
|
||||
},
|
||||
"Switch counting direcion"
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
h1 {"count is {count}"}
|
||||
button { onclick: move |_| task.stop(),
|
||||
"Stop counting"
|
||||
}
|
||||
button { onclick: move |_| task.resume(),
|
||||
"Start counting"
|
||||
}
|
||||
button {
|
||||
onclick: move |_| {
|
||||
*direction.modify() *= -1;
|
||||
task.restart();
|
||||
},
|
||||
"Switch counting direcion"
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -17,10 +17,10 @@ and is proven to be safe with MIRI.
|
|||
use dioxus::prelude::*;
|
||||
|
||||
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 first = text.get_mut(0).unwrap();
|
||||
|
|
|
@ -12,7 +12,7 @@ use dioxus::prelude::*;
|
|||
use separator::Separatable;
|
||||
|
||||
fn main() {
|
||||
// dioxus::desktop::launch(app);
|
||||
dioxus::desktop::launch(app);
|
||||
}
|
||||
|
||||
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 {
|
||||
data: std::collections::HashMap<u32, String>,
|
||||
}
|
||||
static AntipatternNoKeys: Component<NoKeysProps> = |cx| {
|
||||
fn AntipatternNoKeys(cx: Scope<NoKeysProps>) -> Element {
|
||||
// WRONG: Make sure to add keys!
|
||||
rsx!(cx, ul {
|
||||
{cx.props.data.iter().map(|(k, v)| rsx!(li { "List item: {v}" }))}
|
||||
cx.render(rsx! {
|
||||
ul {
|
||||
cx.props.data.iter().map(|(k, v)| rsx!(li { "List item: {v}" }))
|
||||
}
|
||||
});
|
||||
// RIGHT: Like this:
|
||||
rsx!(cx, ul {
|
||||
{cx.props.data.iter().map(|(k, v)| rsx!(li { key: "{k}", "List item: {v}" }))}
|
||||
cx.render(rsx! {
|
||||
ul {
|
||||
cx.props.data.iter().map(|(k, v)| rsx!(li { key: "{k}", "List item: {v}" }))
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// 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
|
||||
rsx!(cx,
|
||||
cx.render(rsx!(
|
||||
Fragment {
|
||||
Fragment {
|
||||
Fragment {
|
||||
|
@ -68,8 +72,8 @@ static AntipatternNestedFragments: Component = |cx| {
|
|||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
};
|
||||
))
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// 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.
|
||||
static AntipatternRelyingOnSetState: Component = |cx| {
|
||||
fn AntipatternRelyingOnSetState(cx: Scope) -> Element {
|
||||
let (state, set_state) = use_state(&cx, || "Hello world").classic();
|
||||
set_state("New state");
|
||||
// This will return false! `state` will *still* be "Hello world"
|
||||
assert!(state == &"New state");
|
||||
todo!()
|
||||
};
|
||||
}
|
||||
|
||||
/// Antipattern: Capitalization
|
||||
/// ---------------------------
|
||||
|
@ -120,16 +124,16 @@ static antipattern_component: Component = |cx| todo!();
|
|||
struct MisuedHooksProps {
|
||||
should_render_state: bool,
|
||||
}
|
||||
static AntipatternMisusedHooks: Component<MisuedHooksProps> = |cx| {
|
||||
fn AntipatternMisusedHooks(cx: Scope<MisuedHooksProps>) -> Element {
|
||||
if props.should_render_state {
|
||||
// do not place a hook in the conditional!
|
||||
// prefer to move it out of the conditional
|
||||
let (state, set_state) = use_state(&cx, || "hello world").classic();
|
||||
rsx!(cx, div { "{state}" })
|
||||
cx.render(rsx!(div { "{state}" }))
|
||||
} else {
|
||||
rsx!(cx, div { "Not rendering state" })
|
||||
cx.render(rsx!(div { "Not rendering state" }))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Antipattern: Downcasting refs and panicking
|
||||
/// ------------------------------------------
|
||||
|
@ -173,9 +177,9 @@ static _example: Component = |cx| todo!();
|
|||
/// libraries.
|
||||
static __example: Component = |cx| todo!();
|
||||
|
||||
pub static Example: Component = |cx| {
|
||||
fn Example(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
AntipatternNoKeys { data: std::collections::HashMap::new() }
|
||||
AntipatternNestedFragments {}
|
||||
})
|
||||
};
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
pub static Example: Component = |cx| {
|
||||
pub fn Example(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
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! {
|
||||
div {
|
||||
h1 { "Hello, {cx.props.name}!" }
|
||||
p { "Welcome to the Dioxus framework" }
|
||||
br {}
|
||||
{cx.children()}
|
||||
&cx.props.children
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
pub static Example: Component = |cx| {
|
||||
fn Example(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
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! {
|
||||
div {
|
||||
h1 { "This is a great banner!" }
|
||||
div { class: "content"
|
||||
{cx.children()}
|
||||
}
|
||||
div { class: "content", &cx.props.children }
|
||||
footer { "Wow, what a great footer" }
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
||||
|
|
|
@ -16,15 +16,15 @@ use dioxus::prelude::*;
|
|||
pub struct MyProps {
|
||||
should_show: bool,
|
||||
}
|
||||
pub static Example0: Component<MyProps> = |cx| {
|
||||
pub fn Example0(cx: Scope<MyProps>) -> Element {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
{cx.props.should_show.then(|| rsx!{
|
||||
cx.props.should_show.then(|| rsx!{
|
||||
h1 { "showing the title!" }
|
||||
})}
|
||||
})
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
// 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
|
||||
|
@ -39,31 +39,29 @@ pub static Example0: Component<MyProps> = |cx| {
|
|||
pub struct MyProps1 {
|
||||
should_show: bool,
|
||||
}
|
||||
pub static Example1: Component<MyProps1> = |cx| {
|
||||
pub fn Example1(cx: Scope<MyProps1>) -> Element {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
// With matching
|
||||
{match props.should_show {
|
||||
match props.should_show {
|
||||
true => cx.render(rsx!(div {"it is true!"})),
|
||||
false => rsx!(cx, div {"it is false!"}),
|
||||
}}
|
||||
},
|
||||
|
||||
// or with just regular conditions
|
||||
{if props.should_show {
|
||||
if props.should_show {
|
||||
rsx!(cx, div {"it is true!"})
|
||||
} else {
|
||||
rsx!(cx, div {"it is false!"})
|
||||
}}
|
||||
},
|
||||
|
||||
// or with optional chaining
|
||||
{
|
||||
props.should_show
|
||||
props.should_show
|
||||
.then(|| rsx!(cx, div {"it is true!"}))
|
||||
.unwrap_or_else(|| rsx!(cx, div {"it is false!"}))
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
/// Matching can be expanded
|
||||
|
||||
|
@ -77,19 +75,19 @@ pub enum Color {
|
|||
pub struct MyProps2 {
|
||||
color: Color,
|
||||
}
|
||||
pub static Example2: Component<MyProps2> = |cx| {
|
||||
pub fn Example2(cx: Scope<MyProps2>) -> Element {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
{match props.color {
|
||||
match props.color {
|
||||
Color::Green => rsx!(cx, div {"it is Green!"}),
|
||||
Color::Yellow => rsx!(cx, div {"it is Yellow!"}),
|
||||
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 mut color_index = use_state(&cx, || 0);
|
||||
let color = match *color_index % 2 {
|
||||
|
@ -101,11 +99,11 @@ pub static Example: Component = |cx| {
|
|||
cx.render(rsx! {
|
||||
div {
|
||||
button {
|
||||
onclick: move |_| should_show.set(!*should_show)
|
||||
onclick: move |_| should_show.set(!*should_show),
|
||||
"click to toggle showing the examples"
|
||||
}
|
||||
button {
|
||||
onclick: move |_| color_index += 1
|
||||
onclick: move |_| color_index += 1,
|
||||
"click to for the enxt color"
|
||||
}
|
||||
Example0 { should_show: *should_show }
|
||||
|
@ -113,4 +111,4 @@ pub static Example: Component = |cx| {
|
|||
Example2 { color: color }
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
use dioxus::prelude::*;
|
||||
fn main() {}
|
||||
|
||||
pub static Example: Component = |cx| {
|
||||
pub fn Example(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
// A controlled component:
|
||||
static ControlledSelect: Component = |cx| {
|
||||
pub fn ControlledSelect(cx: Scope) -> Element {
|
||||
let value = use_state(&cx, || String::from("Grapefruit"));
|
||||
cx.render(rsx! {
|
||||
select { value: "{value}", onchange: move |evt| value.set(evt.value()),
|
||||
|
@ -20,10 +20,10 @@ static ControlledSelect: Component = |cx| {
|
|||
option { value: "Mango", "Mango"}
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
// TODO - how do uncontrolled things work?
|
||||
static UncontrolledSelect: Component = |cx| {
|
||||
pub fn UncontrolledSelect(cx: Scope) -> Element {
|
||||
let value = use_state(&cx, || String::new());
|
||||
|
||||
cx.render(rsx! {
|
||||
|
@ -34,4 +34,4 @@ static UncontrolledSelect: Component = |cx| {
|
|||
option { value: "Mango", "Mango"}
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
pub static Example: Component = |cx| {
|
||||
pub fn Example(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
custom_element {
|
||||
|
@ -19,7 +19,7 @@ pub static Example: Component = |cx| {
|
|||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
mod dioxus_elements {
|
||||
use std::fmt::Arguments;
|
||||
|
|
|
@ -5,4 +5,6 @@
|
|||
|
||||
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).
|
||||
/// However, if you get it wrong, the whole app will crash.
|
||||
/// This is pretty flimsy.
|
||||
static App: Component = |cx| {
|
||||
pub fn App(cx: Scope) -> Element {
|
||||
let data = get_data().unwrap();
|
||||
cx.render(rsx!( div { "{data}" } ))
|
||||
};
|
||||
}
|
||||
|
||||
/// 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
|
||||
static App1: Component = |cx| {
|
||||
pub fn App1(cx: Scope) -> Element {
|
||||
let data = match get_data() {
|
||||
Some(data) => data,
|
||||
None => return None,
|
||||
};
|
||||
cx.render(rsx!( div { "{data}" } ))
|
||||
};
|
||||
}
|
||||
|
||||
/// This is an even better form of error handling.
|
||||
/// 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.
|
||||
///
|
||||
/// 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()?;
|
||||
cx.render(rsx!( div { "{data}" } ))
|
||||
};
|
||||
}
|
||||
|
||||
/// This is top-tier error handling since it displays a failure state.
|
||||
///
|
||||
/// However, the error is lacking in context.
|
||||
static App3: Component = |cx| match get_data() {
|
||||
Some(data) => cx.render(rsx!( div { "{data}" } )),
|
||||
None => cx.render(rsx!( div { "Failed to load data :(" } )),
|
||||
};
|
||||
pub fn App3(cx: Scope) -> Element {
|
||||
match get_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
|
||||
/// 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()?;
|
||||
cx.render(rsx!( div { "{data}" } ))
|
||||
};
|
||||
}
|
||||
|
||||
/// 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
|
||||
static App5: Component = |cx| match get_data_err() {
|
||||
Ok(data) => cx.render(rsx!( div { "{data}" } )),
|
||||
Err(c) => cx.render(rsx!( div { "Failed to load data: {c}" } )),
|
||||
};
|
||||
pub fn App5(cx: Scope) -> Element {
|
||||
match get_data_err() {
|
||||
Ok(data) => cx.render(rsx!( div { "{data}" } )),
|
||||
Err(c) => cx.render(rsx!( div { "Failed to load data: {c}" } )),
|
||||
}
|
||||
}
|
||||
|
||||
// this fetching function produces "nothing"
|
||||
fn get_data() -> Option<String> {
|
||||
|
|
|
@ -10,17 +10,25 @@
|
|||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
pub fn Example(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
App1 {}
|
||||
App2 {}
|
||||
App3 {}
|
||||
})
|
||||
}
|
||||
|
||||
// Returning multiple elements with rsx! or html!
|
||||
static App1: Component = |cx| {
|
||||
pub fn App1(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
h1 { }
|
||||
h2 { }
|
||||
h3 { }
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
// Using the Fragment component
|
||||
static App2: Component = |cx| {
|
||||
pub fn App2(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
Fragment {
|
||||
div {}
|
||||
|
@ -28,10 +36,10 @@ static App2: Component = |cx| {
|
|||
"asd"
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
// Using the `fragment` method on the NodeFactory
|
||||
static App3: Component = |cx| {
|
||||
pub fn App3(cx: Scope) -> Element {
|
||||
cx.render(LazyNodes::new(move |fac| {
|
||||
fac.fragment_from_iter([
|
||||
fac.text(format_args!("A")),
|
||||
|
@ -40,12 +48,4 @@ static App3: Component = |cx| {
|
|||
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;}
|
||||
"#;
|
||||
|
||||
pub static Example: Component = |cx| {
|
||||
pub fn Example(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
head { style { "{STYLE}" } }
|
||||
body {
|
||||
|
@ -27,4 +27,4 @@ pub static Example: Component = |cx| {
|
|||
p {"This is a paragraph"}
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
pub static Example: Component = |cx| {
|
||||
pub fn Example(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
head {
|
||||
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 {}
|
||||
// I haven't noticed any name collisions yet, and am tentatively leaving this behavior in..
|
||||
// Don't rely on it.
|
||||
static Example2: Component = |cx| {
|
||||
pub fn Example2(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
div { color: "red"
|
||||
"hello world!"
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
||||
|
|
|
@ -15,25 +15,20 @@ use dioxus::prelude::*;
|
|||
pub static Example: Component = |cx| {
|
||||
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! {
|
||||
h3 {"Selected: {example_data}"}
|
||||
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::*;
|
||||
|
||||
pub static Example: Component = |cx| {
|
||||
pub fn Example(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
ButtonList {}
|
||||
NonUpdatingEvents {}
|
||||
DisablePropagation {}
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
/// 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 names = ["jack", "jill", "john", "jane"]
|
||||
.iter()
|
||||
.map(move |n| rsx!(button { onclick: move |_| name.set(n), "{n}" }));
|
||||
|
||||
cx.render(rsx!(
|
||||
div {
|
||||
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.
|
||||
/// Check the console.
|
||||
static NonUpdatingEvents: Component = |cx| {
|
||||
rsx!(cx, div {
|
||||
button {
|
||||
onclick: move |_| log::info!("Did not cause any updates!")
|
||||
"Click me to log!"
|
||||
pub fn NonUpdatingEvents(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
button {
|
||||
onclick: move |_| log::info!("Did not cause any updates!"),
|
||||
"Click me to log!"
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
static DisablePropagation: Component = |cx| {
|
||||
rsx!(cx,
|
||||
pub fn DisablePropagation(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
onclick: move |_| log::info!("event propagated to the div!")
|
||||
button {
|
||||
onclick: move |evt| {
|
||||
log::info!("Button will allow propagation");
|
||||
}
|
||||
onclick: move |evt| log::info!("Button will allow propagation"),
|
||||
}
|
||||
}
|
||||
)
|
||||
};
|
||||
})
|
||||
}
|
||||
|
|
|
@ -21,11 +21,11 @@ use dioxus::prelude::*;
|
|||
|
||||
// By default, components with no props are always memoized.
|
||||
// A props of () is considered empty.
|
||||
pub static Example: Component = |cx| {
|
||||
pub fn Example(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
div { "100% 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.
|
||||
|
@ -35,11 +35,11 @@ pub struct MyProps1 {
|
|||
name: String,
|
||||
}
|
||||
|
||||
pub static Example1: Component<MyProps1> = |cx| {
|
||||
pub fn Example1(cx: Scope<MyProps1>) -> Element {
|
||||
cx.render(rsx! {
|
||||
div { "100% memoized! {cx.props.name}" }
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
@ -49,11 +49,11 @@ pub struct MyProps2 {
|
|||
name: std::rc::Rc<str>,
|
||||
}
|
||||
|
||||
pub static Example2: Component<MyProps2> = |cx| {
|
||||
pub fn Example2(cx: Scope<MyProps2>) -> Element {
|
||||
cx.render(rsx! {
|
||||
div { "100% memoized! {cx.props.name}" }
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
// These props *do* borrow any content, and therefore cannot be safely memoized!.
|
||||
#[derive(PartialEq, Props)]
|
||||
|
@ -61,7 +61,7 @@ pub struct MyProps3<'a> {
|
|||
name: &'a str,
|
||||
}
|
||||
// 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
|
||||
fn Example3(cx: Scope<'a, MyProps3<'a>>) -> DomTree {
|
||||
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::*;
|
||||
|
||||
pub static Example: Component = |cx| {
|
||||
pub fn Example(cx: Scope) -> Element {
|
||||
let props = MyProps {
|
||||
count: 0,
|
||||
live: true,
|
||||
|
@ -18,22 +18,24 @@ pub static Example: Component = |cx| {
|
|||
cx.render(rsx! {
|
||||
Example1 { ..props, count: 10, div {"child"} }
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Props)]
|
||||
pub struct MyProps {
|
||||
#[derive(Props)]
|
||||
pub struct MyProps<'a> {
|
||||
count: u32,
|
||||
live: bool,
|
||||
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! {
|
||||
div {
|
||||
h1 { "Hello, {name}"}
|
||||
h3 {"Are we alive? {live}"}
|
||||
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";
|
||||
|
||||
pub static Example: Component = |cx| {
|
||||
pub fn Example(cx: Scope) -> Element {
|
||||
let doggo = use_suspense(
|
||||
cx,
|
||||
|| surf::get(ENDPOINT).recv_json::<DogApi>(),
|
||||
|
@ -35,4 +35,4 @@ pub static Example: Component = |cx| {
|
|||
{doggo}
|
||||
}
|
||||
))
|
||||
};
|
||||
}
|
||||
|
|
|
@ -42,19 +42,19 @@ pub static Example: Component = |cx| {
|
|||
div {
|
||||
h1 {"count is {count}"}
|
||||
button {
|
||||
onclick: move |_| task.stop(),
|
||||
"Stop counting"
|
||||
onclick: move |_| task.stop()
|
||||
}
|
||||
button {
|
||||
onclick: move |_| task.resume(),
|
||||
"Start counting"
|
||||
onclick: move |_| task.resume()
|
||||
}
|
||||
button {
|
||||
"Switch counting direcion"
|
||||
onclick: move |_| {
|
||||
direction *= -1;
|
||||
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
|
||||
|
||||
fn main() {
|
||||
dioxus::desktop::launch(App);
|
||||
dioxus::desktop::launch(app);
|
||||
}
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
static App: Component = |cx| {
|
||||
fn app(cx: Scope) -> Element {
|
||||
let p1 = use_state(&cx, || 0);
|
||||
let p2 = use_state(&cx, || 0);
|
||||
|
||||
|
@ -53,7 +53,7 @@ static App: Component = |cx| {
|
|||
Horsey { pos: *p2, "horsey 2" }
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Props)]
|
||||
struct HorseyProps<'a> {
|
||||
|
|
|
@ -20,12 +20,11 @@ pub struct Client {
|
|||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let clients = use_ref(&cx, || vec![] as Vec<Client>);
|
||||
|
||||
let scene = use_state(&cx, || Scene::ClientsList);
|
||||
let firstname = use_state(&cx, String::new);
|
||||
let lastname = use_state(&cx, String::new);
|
||||
let description = use_state(&cx, String::new);
|
||||
let clients = use_ref(&cx, || vec![] as Vec<Client>);
|
||||
|
||||
cx.render(rsx!(
|
||||
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::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::desktop::launch(App);
|
||||
dioxus::desktop::launch(app);
|
||||
}
|
||||
|
||||
#[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 selected = use_state(&cx, || None);
|
||||
|
||||
|
@ -64,7 +64,7 @@ static App: Component = |cx| {
|
|||
}
|
||||
table {
|
||||
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 {""};
|
||||
rsx!(tr { class: "{is_in_danger}",
|
||||
td { class:"col-md-1" }
|
||||
|
@ -79,13 +79,13 @@ static App: Component = |cx| {
|
|||
}
|
||||
td { class: "col-md-6" }
|
||||
})
|
||||
})}
|
||||
})
|
||||
}
|
||||
}
|
||||
span { class: "preloadicon glyphicon glyphicon-remove", aria_hidden: "true" }
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Props)]
|
||||
struct ActionButtonProps<'a> {
|
||||
|
@ -95,9 +95,17 @@ struct ActionButtonProps<'a> {
|
|||
}
|
||||
|
||||
fn ActionButton<'a>(cx: Scope<'a, ActionButtonProps<'a>>) -> Element {
|
||||
rsx!(cx, div { class: "col-sm-6 smallpad",
|
||||
button { class:"btn btn-primary btn-block", r#type: "button", id: "{cx.props.id}", onclick: move |_| (cx.props.onclick)(),
|
||||
"{cx.props.name}"
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
class: "col-sm-6 smallpad",
|
||||
button {
|
||||
class:"btn btn-primary btn-block",
|
||||
r#type: "button",
|
||||
id: "{cx.props.id}",
|
||||
onclick: move |_| (cx.props.onclick)(),
|
||||
|
||||
"{cx.props.name}"
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -13,12 +13,13 @@ use dioxus::prelude::*;
|
|||
use dioxus::ssr;
|
||||
|
||||
fn main() {
|
||||
let vdom = VirtualDom::new(App);
|
||||
let vdom = VirtualDom::new(app);
|
||||
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);
|
||||
|
||||
cx.render(rsx! {
|
||||
|
@ -30,4 +31,4 @@ static App: Component = |cx| {
|
|||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ use std::sync::Arc;
|
|||
use dioxus::{events::FormEvent, prelude::*};
|
||||
|
||||
fn main() {
|
||||
dioxus::desktop::launch(App);
|
||||
dioxus::desktop::launch(app);
|
||||
}
|
||||
|
||||
const FIELDS: &[(&str, &str)] = &[
|
||||
|
@ -37,7 +37,7 @@ const FIELDS: &[(&str, &str)] = &[
|
|||
("week", ""), // degrades to text most of the time
|
||||
];
|
||||
|
||||
static App: Component = |cx| {
|
||||
fn app(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
div { margin_left: "30px",
|
||||
|
||||
|
@ -147,4 +147,4 @@ static App: Component = |cx| {
|
|||
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ fn main() {
|
|||
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));
|
||||
}
|
||||
|
|
|
@ -21,7 +21,6 @@ use dioxus::desktop::wry::application::dpi::LogicalSize;
|
|||
use dioxus::events::*;
|
||||
use dioxus::prelude::*;
|
||||
|
||||
const STYLE: &str = include_str!("./assets/calculator.css");
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
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 formatted = state.read().formatted_display();
|
||||
|
||||
rsx!(cx, div { id: "wrapper",
|
||||
div { class: "app", style { "{STYLE}" }
|
||||
div { class: "calculator", onkeypress: move |evt| state.write().handle_keydown(evt),
|
||||
div { class: "calculator-display", "{formatted}"}
|
||||
div { class: "calculator-keypad",
|
||||
div { class: "input-keys",
|
||||
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(), "%"}
|
||||
cx.render(rsx!{
|
||||
div { id: "wrapper",
|
||||
div {
|
||||
class: "app",
|
||||
style { [include_str!("./assets/calculator.css")] }
|
||||
div { class: "calculator", onkeypress: move |evt| state.write().handle_keydown(evt),
|
||||
div { class: "calculator-display", "{formatted}"}
|
||||
div { class: "calculator-keypad",
|
||||
div { class: "input-keys",
|
||||
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",
|
||||
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: "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(), "=" }
|
||||
}
|
||||
}
|
||||
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::*;
|
||||
fn main() {
|
||||
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);
|
||||
|
||||
cx.render(rsx!(
|
||||
|
@ -26,7 +26,7 @@ pub static App: Component = |cx| {
|
|||
}
|
||||
}
|
||||
))
|
||||
};
|
||||
}
|
||||
|
||||
enum PlayerAction {
|
||||
Pause,
|
||||
|
|
|
@ -5,10 +5,10 @@
|
|||
use dioxus::prelude::*;
|
||||
|
||||
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);
|
||||
|
||||
cx.render(rsx! {
|
||||
|
@ -18,4 +18,4 @@ static App: Component = |cx| {
|
|||
button { onclick: move |_| count -= 1, "Down low!" }
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ pub enum Route {
|
|||
NotFound,
|
||||
}
|
||||
|
||||
static App: Component = |cx| {
|
||||
fn app(cx: Scope) -> Element {
|
||||
let route = use_router(&cx, Route::parse);
|
||||
|
||||
cx.render(rsx! {
|
||||
|
@ -42,7 +42,7 @@ static App: Component = |cx| {
|
|||
}
|
||||
footer {}
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
impl Route {
|
||||
// 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::*;
|
||||
|
||||
fn main() {
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
//! - Allow top-level fragments
|
||||
//!
|
||||
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.
|
||||
|
@ -49,7 +49,7 @@ const NONE_ELEMENT: Option<()> = None;
|
|||
use baller::Baller;
|
||||
use dioxus::prelude::*;
|
||||
|
||||
pub static EXAMPLE: Component = |cx| {
|
||||
fn app(cx: Scope) -> Element {
|
||||
let formatting = "formatting!";
|
||||
let formatting_tuple = ("a", "b");
|
||||
let lazy_fmt = format_args!("lazily formatted text");
|
||||
|
@ -192,10 +192,12 @@ pub static EXAMPLE: Component = |cx| {
|
|||
[helper(&cx, "hello world!")]
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
fn helper<'a>(cx: &'a ScopeState, text: &str) -> Element<'a> {
|
||||
rsx!(cx, p { "{text}" })
|
||||
cx.render(rsx! {
|
||||
p { "{text}" }
|
||||
})
|
||||
}
|
||||
|
||||
mod baller {
|
||||
|
@ -227,5 +229,7 @@ pub fn Taller<'a>(cx: Scope<'a, TallerProps<'a>>) -> Element {
|
|||
|
||||
#[inline_props]
|
||||
fn with_inline<'a>(cx: Scope<'a>, text: &'a str) -> Element {
|
||||
rsx!(cx, p { "{text}" })
|
||||
cx.render(rsx! {
|
||||
p { "{text}" }
|
||||
})
|
||||
}
|
||||
|
|
|
@ -2,16 +2,16 @@ use dioxus::prelude::*;
|
|||
use dioxus::ssr;
|
||||
|
||||
fn main() {
|
||||
let mut vdom = VirtualDom::new(APP);
|
||||
let mut vdom = VirtualDom::new(app);
|
||||
let _ = vdom.rebuild();
|
||||
println!("{}", ssr::render_vdom(&vdom));
|
||||
}
|
||||
|
||||
static APP: Component = |cx| {
|
||||
fn app(cx: Scope) -> Element {
|
||||
cx.render(rsx!(
|
||||
div {
|
||||
h1 { "Title" }
|
||||
p { "Body" }
|
||||
}
|
||||
))
|
||||
};
|
||||
}
|
||||
|
|
|
@ -3,8 +3,9 @@ use dioxus_core as dioxus;
|
|||
use dioxus_core::prelude::*;
|
||||
use dioxus_core_macro::*;
|
||||
use dioxus_html as dioxus_elements;
|
||||
|
||||
fn main() {
|
||||
let mut dom = VirtualDom::new(App);
|
||||
let mut dom = VirtualDom::new(app);
|
||||
dom.rebuild();
|
||||
println!(
|
||||
"{}",
|
||||
|
@ -12,14 +13,14 @@ fn main() {
|
|||
)
|
||||
}
|
||||
|
||||
pub static App: Component = |cx| {
|
||||
fn app(cx: Scope) -> Element {
|
||||
cx.render(rsx!(
|
||||
div {
|
||||
class: "overflow-hidden"
|
||||
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!"
|
||||
}
|
||||
))
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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}"} ))}
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
// };
|
|
@ -24,7 +24,7 @@ fn main() {
|
|||
.unwrap();
|
||||
}
|
||||
|
||||
pub static App: Component = |cx| {
|
||||
pub fn App(cx: Scope) -> Element {
|
||||
cx.render(rsx!(
|
||||
div { class: "overflow-hidden"
|
||||
link { href:"https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel:"stylesheet" }
|
||||
|
@ -37,9 +37,9 @@ pub static App: Component = |cx| {
|
|||
Hero {}
|
||||
}
|
||||
))
|
||||
};
|
||||
}
|
||||
|
||||
pub static Header: Component = |cx| {
|
||||
pub fn Header(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
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! {
|
||||
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! {
|
||||
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!(
|
||||
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"}
|
||||
}
|
||||
))
|
||||
};
|
||||
pub static RightArrowIcon: Component = |cx| {
|
||||
}
|
||||
|
||||
pub fn RightArrowIcon(cx: Scope) -> Element {
|
||||
cx.render(rsx!(
|
||||
svg {
|
||||
fill: "none"
|
||||
|
@ -144,4 +146,4 @@ pub static RightArrowIcon: Component = |cx| {
|
|||
path { d: "M5 12h14M12 5l7 7-7 7"}
|
||||
}
|
||||
))
|
||||
};
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ fn main() {
|
|||
|
||||
const STYLE: &str = "body {overflow:hidden;}";
|
||||
|
||||
pub static App: Component = |cx| {
|
||||
pub fn App(cx: Scope) -> Element {
|
||||
cx.render(rsx!(
|
||||
div { class: "overflow-hidden",
|
||||
style { "{STYLE}" }
|
||||
|
@ -28,9 +28,9 @@ pub static App: Component = |cx| {
|
|||
Hero {}
|
||||
}
|
||||
))
|
||||
};
|
||||
}
|
||||
|
||||
pub static Header: Component = |cx| {
|
||||
pub fn Header(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
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! {
|
||||
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! {
|
||||
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!(
|
||||
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"}
|
||||
}
|
||||
))
|
||||
};
|
||||
pub static RightArrowIcon: Component = |cx| {
|
||||
}
|
||||
pub fn RightArrowIcon(cx: Scope) -> Element {
|
||||
cx.render(rsx!(
|
||||
svg {
|
||||
fill: "none",
|
||||
|
@ -135,4 +135,4 @@ pub static RightArrowIcon: Component = |cx| {
|
|||
path { d: "M5 12h14M12 5l7 7-7 7"}
|
||||
}
|
||||
))
|
||||
};
|
||||
}
|
||||
|
|
|
@ -12,13 +12,14 @@ fn main() {
|
|||
fn app(cx: Scope) -> Element {
|
||||
let count = use_state(&cx, || 0);
|
||||
|
||||
use_future(&cx, || {
|
||||
for_async![count];
|
||||
use_future(&cx, move || {
|
||||
let count = UseState::for_async(&count);
|
||||
// for_async![count];
|
||||
async move {
|
||||
loop {
|
||||
tokio::time::sleep(Duration::from_millis(1000)).await;
|
||||
count += 1;
|
||||
}
|
||||
// loop {
|
||||
// tokio::time::sleep(Duration::from_millis(1000)).await;
|
||||
// count += 1;
|
||||
// }
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ use im_rc::HashMap;
|
|||
use std::rc::Rc;
|
||||
|
||||
fn main() {
|
||||
dioxus::desktop::launch(App);
|
||||
dioxus::desktop::launch(app);
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
|
@ -21,8 +21,7 @@ pub struct TodoItem {
|
|||
pub contents: String,
|
||||
}
|
||||
|
||||
const STYLE: &str = include_str!("./assets/todomvc.css");
|
||||
const App: Component = |cx| {
|
||||
fn app(cx: Scope) -> Element {
|
||||
let draft = use_state(&cx, || "".to_string());
|
||||
let todos = use_state(&cx, || HashMap::<u32, Rc<TodoItem>>::new());
|
||||
let filter = use_state(&cx, || FilterState::All);
|
||||
|
@ -48,40 +47,43 @@ const App: Component = |cx| {
|
|||
_ => "items",
|
||||
};
|
||||
|
||||
rsx!(cx, div { id: "app",
|
||||
style {"{STYLE}"}
|
||||
div {
|
||||
header { class: "header",
|
||||
h1 {"todos"}
|
||||
input {
|
||||
class: "new-todo",
|
||||
placeholder: "What needs to be done?",
|
||||
value: "{draft}",
|
||||
oninput: move |evt| draft.set(evt.value.clone()),
|
||||
cx.render(rsx!(
|
||||
div { id: "app",
|
||||
style { [include_str!("./assets/todomvc.css")] }
|
||||
div {
|
||||
header {
|
||||
class: "header",
|
||||
h1 { "todos" }
|
||||
input {
|
||||
class: "new-todo",
|
||||
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)]
|
||||
pub struct TodoEntryProps {
|
||||
|
@ -93,18 +95,20 @@ pub fn TodoEntry(cx: Scope<TodoEntryProps>) -> Element {
|
|||
let contents = use_state(&cx, || String::from(""));
|
||||
let todo = &cx.props.todo;
|
||||
|
||||
rsx!(cx, li {
|
||||
"{todo.id}"
|
||||
input {
|
||||
class: "toggle",
|
||||
r#type: "checkbox",
|
||||
"{todo.checked}"
|
||||
}
|
||||
{is_editing.then(|| rsx!{
|
||||
cx.render(rsx! {
|
||||
li {
|
||||
"{todo.id}"
|
||||
input {
|
||||
value: "{contents}",
|
||||
oninput: move |evt| contents.set(evt.value.clone())
|
||||
class: "toggle",
|
||||
r#type: "checkbox",
|
||||
"{todo.checked}"
|
||||
}
|
||||
})}
|
||||
is_editing.then(|| rsx!{
|
||||
input {
|
||||
value: "{contents}",
|
||||
oninput: move |evt| contents.set(evt.value.clone())
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -7,22 +7,23 @@
|
|||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::desktop::launch(App);
|
||||
dioxus::desktop::launch(app);
|
||||
}
|
||||
|
||||
const ENDPOINT: &str = "https://api.openweathermap.org/data/2.5/weather";
|
||||
|
||||
static App: Component = |cx| {
|
||||
fn app(cx: Scope) -> Element {
|
||||
//
|
||||
let body = use_suspense(
|
||||
cx,
|
||||
&cx,
|
||||
|| async {
|
||||
let content = reqwest::get(ENDPOINT)
|
||||
.await
|
||||
.unwrap()
|
||||
.json::<serde_json::Value>()
|
||||
.await
|
||||
.unwrap();
|
||||
todo!()
|
||||
// let content = reqwest::get(ENDPOINT)
|
||||
// .await
|
||||
// .unwrap()
|
||||
// .json::<serde_json::Value>()
|
||||
// .await
|
||||
// .unwrap();
|
||||
},
|
||||
|props| {
|
||||
//
|
||||
|
@ -35,13 +36,12 @@ static App: Component = |cx| {
|
|||
{body}
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Props)]
|
||||
struct WeatherProps {}
|
||||
|
||||
static WeatherDisplay: Component<WeatherProps> = |cx| {
|
||||
//
|
||||
fn WeatherDisplay(cx: Scope<WeatherProps>) -> Element {
|
||||
cx.render(rsx!(
|
||||
div { class: "flex items-center justify-center flex-col",
|
||||
div { class: "flex items-center justify-center",
|
||||
|
@ -64,4 +64,4 @@ static WeatherDisplay: Component<WeatherProps> = |cx| {
|
|||
}
|
||||
}
|
||||
))
|
||||
};
|
||||
}
|
||||
|
|
|
@ -13,10 +13,10 @@
|
|||
use dioxus::prelude::*;
|
||||
|
||||
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();
|
||||
|
||||
cx.render(rsx! {
|
||||
|
@ -29,7 +29,7 @@ static App: Component = |cx| {
|
|||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Props)]
|
||||
struct RowProps {
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
//! XSS Safety
|
||||
//!
|
||||
//! This example proves that Dioxus is safe from XSS attacks.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
|
@ -9,19 +13,11 @@ fn app(cx: Scope) -> Element {
|
|||
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
"hello world!"
|
||||
|
||||
h1 { "{contents}" }
|
||||
|
||||
h3 { [contents.as_str()] }
|
||||
|
||||
h3 { "{contents}" }
|
||||
input {
|
||||
value: "{contents}",
|
||||
oninput: move |e| {
|
||||
contents.set(e.value.clone());
|
||||
eprintln!("asd");
|
||||
},
|
||||
"type": "text",
|
||||
r#type: "text",
|
||||
oninput: move |e| contents.set(e.value.clone()),
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -99,8 +99,8 @@ impl ToTokens for InlinePropsBody {
|
|||
};
|
||||
|
||||
out_tokens.append_all(quote! {
|
||||
#[allow(non_camel_case)]
|
||||
#modifiers
|
||||
#[allow(non_camel_case_types)]
|
||||
#vis struct #struct_name #generics {
|
||||
#(#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));
|
||||
}
|
||||
}
|
211
packages/hooks/src/usestate/handle.rs
Normal file
211
packages/hooks/src/usestate/handle.rs
Normal file
|
@ -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));
|
||||
}
|
||||
}
|
78
packages/hooks/src/usestate/mod.rs
Normal file
78
packages/hooks/src/usestate/mod.rs
Normal file
|
@ -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)
|
||||
}
|
102
packages/hooks/src/usestate/owned.rs
Normal file
102
packages/hooks/src/usestate/owned.rs
Normal file
|
@ -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));
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue