From 420a30e5d432722e9da16311deb6aa60ea46b0cb Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 3 Jan 2022 00:42:17 -0500 Subject: [PATCH] feat: overhaul examples and clean things up --- Cargo.toml | 20 +- examples/assets/fileexplorer.css | 180 +++++++++++++++ examples/assets/todomvc.css | 373 +++++++++++++++++++++++++++++++ examples/async.rs | 48 ---- examples/calculator.rs | 253 ++++++++++----------- examples/coroutine.rs | 73 ------ examples/crm.rs | 2 +- examples/file_explorer.rs | 81 ++++--- examples/pattern_model.rs | 82 +++++-- examples/pattern_reducer.rs | 7 +- examples/ssr.rs | 34 ++- examples/ssr/basic.rs | 26 --- examples/ssr/tofile.rs | 149 ------------ examples/suspense.rs | 72 ++++++ examples/tailwind.rs | 117 ++++------ examples/tasks.rs | 6 +- examples/todomvc.rs | 128 ++++++----- examples/weather_app.rs | 67 ------ examples/web/async_web.rs | 53 ----- examples/web/basic.rs | 89 -------- examples/web/blah.rs | 55 ----- examples/web/btns.rs | 69 ------ examples/web/crm2.rs | 116 ---------- examples/web/demo.rs | 147 ------------ examples/web_tick.rs | 127 ----------- examples/webview_web.rs | 28 --- examples/xss_safety.rs | 5 +- packages/desktop/src/index.html | 6 +- packages/desktop/src/lib.rs | 2 +- packages/hooks/src/usefuture.rs | 83 ++++--- packages/router/src/link.rs | 2 +- packages/ssr/src/lib.rs | 2 +- packages/web/src/dom.rs | 4 +- 33 files changed, 1089 insertions(+), 1417 deletions(-) create mode 100644 examples/assets/fileexplorer.css delete mode 100644 examples/async.rs delete mode 100644 examples/coroutine.rs delete mode 100644 examples/ssr/basic.rs delete mode 100644 examples/ssr/tofile.rs create mode 100644 examples/suspense.rs delete mode 100644 examples/weather_app.rs delete mode 100644 examples/web/async_web.rs delete mode 100644 examples/web/basic.rs delete mode 100644 examples/web/blah.rs delete mode 100644 examples/web/btns.rs delete mode 100644 examples/web/crm2.rs delete mode 100644 examples/web/demo.rs delete mode 100644 examples/web_tick.rs delete mode 100644 examples/webview_web.rs diff --git a/Cargo.toml b/Cargo.toml index 01d8c0370..19c6e7d64 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,24 +60,8 @@ num-format = "0.4.0" separator = "0.4.1" serde = { version = "1.0.131", features = ["derive"] } im-rc = "15.0.0" -fxhash = "0.2.1" anyhow = "1.0.51" serde_json = "1.0.73" - -[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] -argh = "0.1.7" -env_logger = "0.9.0" +rand = { version = "0.8.4", features = ["small_rng"] } tokio = { version = "1.14.0", features = ["full"] } -rand = { version = "0.8.4", features = ["small_rng"] } -gloo-timers = "0.2.2" - -[target.'cfg(target_arch = "wasm32")'.dev-dependencies] -gloo-timers = "0.2.2" -wasm-logger = "0.2.0" -console_error_panic_hook = "0.1.7" -rand = { version = "0.8.4", features = ["small_rng"] } -wasm-bindgen = { version = "0.2.78", features = ["enable-interning"] } - -[dev-dependencies.getrandom] -version = "0.2.3" -features = ["js"] +reqwest = { version = "0.11.8", features = ["json"] } diff --git a/examples/assets/fileexplorer.css b/examples/assets/fileexplorer.css new file mode 100644 index 000000000..8a85dbeaf --- /dev/null +++ b/examples/assets/fileexplorer.css @@ -0,0 +1,180 @@ +* { + margin: 0; + padding: 0; + font-family: 'Roboto', sans-serif; + user-select: none; + transition: .2s all; +} + +body { + padding-top: 77px; +} + + +/* header { + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 10; + padding: 20px; + background-color: #2196F3; + color: white; +} +header h1 { + float: left; + font-size: 20px; + font-weight: 400; +} +header .material-icons { + float: right; + cursor: pointer; +} +header .icon-menu { + float: left; + margin-right: 20px; +} */ + +main { + padding: 20px 50px; +} + +.folder * { + width: 100px; +} + +.folder { + float: left; + width: 100px; + height: 152px; + /* //padding: 20px; */ + margin-right: 50px; + margin-bottom: 70px; + border-radius: 2px; + /* //overflow: hidden; */ + cursor: pointer; +} + +.folder:hover h1 { + display: none; +} + +.folder:hover p.cooltip { + opacity: 1; + top: 0; +} + +.folder * { + text-align: center; +} + +.folder i { + margin: 0; + font-size: 100px; + color: #607D8B; +} + +.folder h1 { + position: relative; + display: block; + top: -37px; + font-size: 20px; + font-weight: 400; +} + +.folder p.cooltip { + position: relative; + top: 5px; + left: -50%; + margin-left: 35px; + background: #212121; + font-size: 15px; + color: white; + border-radius: 4px; + padding: 10px 20px; + padding-right: 30px; + width: 100px; + opacity: 0; +} + +.folder p.cooltip:before { + content: ''; + position: absolute; + display: block; + top: -4px; + left: 50%; + margin-left: -5px; + height: 10px; + width: 10px; + border-radius: 2px; + background-color: #212121; + transform: rotate(45deg); +} + +div.properties { + position: fixed; + top: 0; + right: 0; + bottom: 0; + z-index: 10; + width: 300px; + background-color: white; +} + +div.properties:before { + content: ''; + position: fixed; + top: 0; + left: 0; + right: 300px; + bottom: 0; + background-color: #212121; + opacity: .5; + overflow: hidden; +} + +div.properties img { + position: relative; + top: -1px; + left: -1px; + width: 110%; + height: 200px; + filter: blur(2px); +} + +div.properties h1 { + position: relative; + width: 100%; + text-align: left; + margin-left: 20px; + color: white; +} + +header { + position: fixed; + top: 0; + left: 0; + right: 0; + padding: 20px; + background-color: #2196F3; + color: white; + display: flex; + align-items: center; +} + +header h1 { + font-weight: 400; +} + +header span { + flex: 1; +} + +header i { + margin: 0 10px; + cursor: pointer; +} + +header i:nth-child(1) { + margin: 0 20px; +} \ No newline at end of file diff --git a/examples/assets/todomvc.css b/examples/assets/todomvc.css index e69de29bb..f9076525c 100644 --- a/examples/assets/todomvc.css +++ b/examples/assets/todomvc.css @@ -0,0 +1,373 @@ +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; + } +} \ No newline at end of file diff --git a/examples/async.rs b/examples/async.rs deleted file mode 100644 index dcd3b2757..000000000 --- a/examples/async.rs +++ /dev/null @@ -1,48 +0,0 @@ -/* -This example shows how to use async and loops to implement a coroutine in a component. Coroutines can be controlled via -the `TaskHandle` object. -*/ - -use dioxus::prelude::*; -use gloo_timers::future::TimeoutFuture; - -#[tokio::main] -async fn main() { - dioxus::desktop::launch(app); -} - -fn app(cx: Scope) -> Element { - let count = use_state(&cx, || 0); - let direction = use_state(&cx, || 1); - - 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; - } - } - }); - - 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" - } - } - }) -} diff --git a/examples/calculator.rs b/examples/calculator.rs index e43323a21..a2a2cf605 100644 --- a/examples/calculator.rs +++ b/examples/calculator.rs @@ -1,160 +1,149 @@ -#![allow(non_snake_case)] - /* This example is a simple iOS-style calculator. This particular example can run any platform - Web, Mobile, Desktop. This calculator version uses React-style state management. All state is held as individual use_states. */ -use std::sync::Arc; - use dioxus::events::*; use dioxus::prelude::*; use separator::Separatable; fn main() { - dioxus::desktop::launch(app); + use dioxus::desktop::tao::dpi::LogicalSize; + dioxus::desktop::launch_cfg(app, |cfg| { + cfg.with_window(|w| { + w.with_title("Calculator Demo") + .with_resizable(false) + .with_inner_size(LogicalSize::new(320.0, 530.0)) + }) + }); } fn app(cx: Scope) -> Element { let cur_val = use_state(&cx, || 0.0_f64); - let operator = use_state(&cx, || None as Option<&'static str>); - let display_value = use_state(&cx, || String::from("")); + let operator = use_state(&cx, Option::default); + let display_value = use_state(&cx, String::new); - let toggle_percent = move |_| todo!(); let input_digit = move |num: u8| display_value.modify().push_str(num.to_string().as_str()); cx.render(rsx!( style { [include_str!("./assets/calculator.css")] } - div { - class: "calculator", - onkeydown: move |evt| match evt.key_code { - KeyCode::Add => operator.set(Some("+")), - KeyCode::Subtract => operator.set(Some("-")), - KeyCode::Divide => operator.set(Some("/")), - KeyCode::Multiply => operator.set(Some("*")), - KeyCode::Num0 => input_digit(0), - KeyCode::Num1 => input_digit(1), - KeyCode::Num2 => input_digit(2), - KeyCode::Num3 => input_digit(3), - KeyCode::Num4 => input_digit(4), - KeyCode::Num5 => input_digit(5), - KeyCode::Num6 => input_digit(6), - KeyCode::Num7 => input_digit(7), - KeyCode::Num8 => input_digit(8), - KeyCode::Num9 => input_digit(9), - KeyCode::Backspace => { - if !display_value.as_str().eq("0") { - display_value.modify().pop(); - } - } - _ => {} - }, - div { class: "calculator-display", [cur_val.separated_string()] } - div { class: "input-keys", - div { class: "function-keys", - CalculatorKey { - name: "key-clear", - onclick: move |_| { - display_value.set("0".to_string()); - if display_value != "0" { - operator.set(None); - cur_val.set(0.0); + div { id: "wrapper", + div { class: "app", + div { class: "calculator", + onkeydown: move |evt| match evt.key_code { + KeyCode::Add => operator.set(Some("+")), + KeyCode::Subtract => operator.set(Some("-")), + KeyCode::Divide => operator.set(Some("/")), + KeyCode::Multiply => operator.set(Some("*")), + KeyCode::Num0 => input_digit(0), + KeyCode::Num1 => input_digit(1), + KeyCode::Num2 => input_digit(2), + KeyCode::Num3 => input_digit(3), + KeyCode::Num4 => input_digit(4), + KeyCode::Num5 => input_digit(5), + KeyCode::Num6 => input_digit(6), + KeyCode::Num7 => input_digit(7), + KeyCode::Num8 => input_digit(8), + KeyCode::Num9 => input_digit(9), + KeyCode::Backspace => { + if !display_value.as_str().eq("0") { + display_value.modify().pop(); } - }, - [if display_value == "0" { "C" } else { "AC" }] - } - CalculatorKey { - name: "key-sign", - onclick: move |_| { - if display_value.starts_with("-") { - display_value.set(display_value.trim_start_matches("-").to_string()) - } else { - display_value.set(format!("-{}", *display_value)) - } - }, - "±" - } - CalculatorKey { - onclick: toggle_percent, - name: "key-percent", - "%" - } - } - div { class: "digit-keys", - CalculatorKey { name: "key-0", onclick: move |_| input_digit(0), - "0" - } - CalculatorKey { name: "key-dot", onclick: move |_| display_value.modify().push_str("."), - "●" - } - (1..9).map(|k| rsx!{ - CalculatorKey { - key: "{k}", - name: "key-{k}", - onclick: move |_| input_digit(k), - "{k}" } - }), - } - - div { class: "operator-keys", - CalculatorKey { - name: "key-divide", - onclick: move |_| operator.set(Some("/")), - "÷" - } - CalculatorKey { - name: "key-multiply", - onclick: move |_| operator.set(Some("*")), - "×" - } - CalculatorKey { - name: "key-subtract", - onclick: move |_| operator.set(Some("-")), - "−" - } - CalculatorKey { - name: "key-add", - onclick: move |_| operator.set(Some("+")), - "+" - } - CalculatorKey { - name: "key-equals", - onclick: move |_| { - if let Some(op) = operator.as_ref() { - let rhs = display_value.parse::().unwrap(); - let new_val = match *op { - "+" => *cur_val + rhs, - "-" => *cur_val - rhs, - "*" => *cur_val * rhs, - "/" => *cur_val / rhs, - _ => unreachable!(), - }; - cur_val.set(new_val); - display_value.set(new_val.to_string()); - operator.set(None); + _ => {} + }, + div { class: "calculator-display", [cur_val.separated_string()] } + div { class: "calculator-keypad", + div { class: "input-keys", + div { class: "function-keys", + button { + class: "calculator-key key-clear", + onclick: move |_| { + display_value.set("0".to_string()); + if display_value != "0" { + operator.set(None); + cur_val.set(0.0); + } + }, + [if display_value == "0" { "C" } else { "AC" }] + } + button { + class: "calculator-key key-sign", + onclick: move |_| { + if display_value.starts_with("-") { + display_value.set(display_value.trim_start_matches("-").to_string()) + } else { + display_value.set(format!("-{}", *display_value)) + } + }, + "±" + } + button { + class: "calculator-key key-percent", + onclick: move |_| { + let pct = (display_value.parse::().unwrap() / 100.0).to_string(); + display_value.set(pct); + }, + "%" + } } - }, - "=" + div { class: "digit-keys", + button { class: "calculator-key key-0", onclick: move |_| input_digit(0), + "0" + } + button { class: "calculator-key key-dot", onclick: move |_| display_value.modify().push_str("."), + "●" + } + (1..10).map(|k| rsx!{ + button { + class: "calculator-key {k}", + name: "key-{k}", + onclick: move |_| input_digit(k), + "{k}" + } + }), + } + } + div { class: "operator-keys", + button { class: "calculator-key key-divide", + onclick: move |_| operator.set(Some("/")), + "÷" + } + button { class: "calculator-key key-multiply", + onclick: move |_| operator.set(Some("*")), + "×" + } + button { class: "calculator-key key-subtract", + onclick: move |_| operator.set(Some("-")), + "−" + } + button { class: "calculator-key key-add", + onclick: move |_| operator.set(Some("+")), + "+" + } + button { class: "calculator-key key-equals", + onclick: move |_| { + if let Some(op) = operator.as_ref() { + let rhs = display_value.parse::().unwrap(); + let new_val = match *op { + "+" => *cur_val + rhs, + "-" => *cur_val - rhs, + "*" => *cur_val * rhs, + "/" => *cur_val / rhs, + _ => unreachable!(), + }; + cur_val.set(new_val); + display_value.set(new_val.to_string()); + operator.set(None); + } + }, + "=" + } + } } } } } + )) } - -#[inline_props] -fn CalculatorKey<'a>( - cx: Scope, - name: &'static str, - onclick: &'a dyn Fn(Arc), - children: Element<'a>, -) -> Element { - cx.render(rsx! { - button { - class: "calculator-key {name}", - onclick: onclick, - children - } - }) -} diff --git a/examples/coroutine.rs b/examples/coroutine.rs deleted file mode 100644 index 8ef6c2bfb..000000000 --- a/examples/coroutine.rs +++ /dev/null @@ -1,73 +0,0 @@ -//! Example: Coroutines! -//! -------------------- -//! -//! Coroutines are an awesome way to write concurrent code. Dioxus heavily leverages coroutines to make sense of complex -//! ongoing asynchronous tasks. The async scheduler of Dioxus supports both single-threaded and multi-threaded coroutines, -//! so you can drop in code to run across multiple threads without blocking the main thread. -//! -//! Dioxus cannot simply abstract away the threading model for the web, unfortunately. If you want to use "web threads" -//! you either need to limit support for Chrome, or you need to use a Web Workers and message passing. This is easy enough -//! to do in your own code, and doesn't require 1st-party support from Dioxus itself. -//! -//! UseState and friends work fine with coroutines, but coroutines might be easier to use with the Dirac global state -//! management API. This lets you easily drive global state from a coroutine without having to subscribe to the state. -//! -//! For now, this example shows how to use coroutines used with use_state. -//! -//! -//! ## What is a Coroutine? -//! -//! A coroutine is a function that can be paused and resumed. It can be paused internally through "await" or externally -//! using the `TaskHandle` API. Within a coroutine, you may execute asynchronous code, that modifies values captured when -//! 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); -} - -use dioxus::prelude::*; - -fn app(cx: Scope) -> Element { - let p1 = use_state(&cx, || 0); - let p2 = use_state(&cx, || 0); - - let (mut p1_async, mut p2_async) = (p1.for_async(), p2.for_async()); - let (p1_handle, _) = use_coroutine(cx, || async move { - loop { - *p1_async.get_mut() += 1; - async_std::task::sleep(std::time::Duration::from_millis(75)).await; - } - }); - let (p2_handle, _) = use_coroutine(cx, || async move { - loop { - *p2_async.get_mut() += 1; - async_std::task::sleep(std::time::Duration::from_millis(100)).await; - } - }); - - cx.render(rsx! { - div { - width: "400px", height: "400px", position: "relative", background: "yellow" - button { "reset", onclick: move |_| {} } - Horsey { pos: *p1, "horsey 1" } - Horsey { pos: *p2, "horsey 2" } - } - }) -} - -#[derive(Props)] -struct HorseyProps<'a> { - pos: i32, - children: Element<'a>, -} - -fn Horsey<'a>(cx: Scope<'a, HorseyProps<'a>>) -> Element { - cx.render(rsx! { - div { - button { "pause" } - div { - {&cx.props.children} - } - } - }) -} diff --git a/examples/crm.rs b/examples/crm.rs index b72fe541d..09a2f2a89 100644 --- a/examples/crm.rs +++ b/examples/crm.rs @@ -21,10 +21,10 @@ pub struct Client { fn app(cx: Scope) -> Element { let scene = use_state(&cx, || Scene::ClientsList); + let clients = use_ref(&cx, || vec![] as Vec); 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); cx.render(rsx!( body { diff --git a/examples/file_explorer.rs b/examples/file_explorer.rs index f27e018a9..5d4bf8be8 100644 --- a/examples/file_explorer.rs +++ b/examples/file_explorer.rs @@ -11,37 +11,53 @@ use dioxus::prelude::*; fn main() { - dioxus::desktop::launch_cfg(app, |c| { - c.with_window(|w| { - w.with_resizable(true) - .with_inner_size(dioxus::desktop::tao::dpi::LogicalSize::new(400.0, 800.0)) - }) - }); + dioxus::desktop::launch_cfg(app, |c| c.with_window(|w| w.with_resizable(true))); } fn app(cx: Scope) -> Element { - let files = use_ref(&cx, Files::new); + let files = use_ref(&cx, || Files::new()); - cx.render(rsx!( - h1 { "Files: " } - h3 { "Cur dir: " [files.read().current()] } - button { onclick: move |_| files.write().go_up(), "go up" } - ol { - files.read().path_names.iter().enumerate().map(|(dir_id, path)| rsx!( - li { key: "{path}", - a { href: "#", onclick: move |_| files.write().enter_dir(dir_id), - "{path}", - } - } - )) + rsx!(cx, div { + link { href:"https://fonts.googleapis.com/icon?family=Material+Icons", rel:"stylesheet", } + style { [include_str!("./assets/fileexplorer.css")] } + header { + i { class: "material-icons icon-menu", "menu" } + h1 { "Files: " [files.read().current()] } + span { } + i { class: "material-icons", onclick: move |_| files.write().go_up(), "logout" } } - files.read().err.as_ref().map(|err| rsx!( - div { - code { "{err}" } - button { onclick: move |_| files.write().clear_err(), "x" } - } - )) - )) + main { + files.read().path_names.iter().enumerate().map(|(dir_id, path)| { + let path_end = path.split('/').last().unwrap_or(path.as_str()); + let icon_type = if path_end.contains(".") { + "description" + } else { + "folder" + }; + rsx! ( + div { + class: "folder", + key: "{path}", + i { class: "material-icons", + onclick: move |_| files.write().enter_dir(dir_id), + "{icon_type}" + p { class: "cooltip", "0 folders / 0 files" } + } + h1 { "{path_end}" } + } + ) + }), + files.read().err.as_ref().map(|err| { + rsx! ( + div { + code { "{err}" } + button { onclick: move |_| files.write().clear_err(), "x" } + } + ) + }) + } + + }) } struct Files { @@ -64,21 +80,26 @@ impl Files { } fn reload_path_list(&mut self) { - let paths = match std::fs::read_dir(self.path_stack.last().unwrap()) { + let cur_path = self.path_stack.last().unwrap(); + let paths = match std::fs::read_dir(cur_path) { Ok(e) => e, Err(err) => { - self.err = Some(format!("An error occured: {:?}", err)); + let err = format!("An error occured: {:?}", err); + self.err = Some(err); self.path_stack.pop(); return; } }; + let collected = paths.collect::>(); // clear the current state self.clear_err(); self.path_names.clear(); - self.path_names - .extend(paths.map(|path| path.unwrap().path().display().to_string())); + for path in collected { + self.path_names + .push(path.unwrap().path().display().to_string()); + } } fn go_up(&mut self) { diff --git a/examples/pattern_model.rs b/examples/pattern_model.rs index 0d5f07b81..fc707437d 100644 --- a/examples/pattern_model.rs +++ b/examples/pattern_model.rs @@ -22,7 +22,6 @@ use dioxus::events::*; use dioxus::prelude::*; fn main() { - env_logger::init(); dioxus::desktop::launch_cfg(app, |cfg| { cfg.with_window(|w| { w.with_title("Calculator Demo") @@ -35,38 +34,78 @@ fn main() { fn app(cx: Scope) -> Element { let state = use_ref(&cx, || Calculator::new()); - let clear_display = state.read().display_value.eq("0"); - let clear_text = if clear_display { "C" } else { "AC" }; - let formatted = state.read().formatted_display(); - - cx.render(rsx!{ + cx.render(rsx! { + style { [include_str!("./assets/calculator.css")] } div { id: "wrapper", - div { - class: "app", - style { [include_str!("./assets/calculator.css")] } + div { class: "app", div { class: "calculator", onkeypress: move |evt| state.write().handle_keydown(evt), - div { class: "calculator-display", "{formatted}"} + div { class: "calculator-display", [state.read().formatted_display()]} 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(), "%"} + CalculatorKey { + name: "key-clear", + onclick: move |_| state.write().clear_display(), + [if state.read().display_value == "0" { "C" } else { "AC" }] + } + 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(), "●" } + 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}" } + 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(), "=" } + 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(), + "=" + } } } } @@ -105,7 +144,6 @@ enum Operator { Mul, Div, } - impl Calculator { fn new() -> Self { Calculator { diff --git a/examples/pattern_reducer.rs b/examples/pattern_reducer.rs index d646b5105..82634d771 100644 --- a/examples/pattern_reducer.rs +++ b/examples/pattern_reducer.rs @@ -4,10 +4,13 @@ //! This example shows how to encapsulate state in dioxus components with the reducer pattern. //! This pattern is very useful when a single component can handle many types of input that can //! be represented by an enum. +//! +//! Currently we don't have a reducer pattern hook. If you'd like to add it, +//! feel free to make a PR. use dioxus::prelude::*; + fn main() { - env_logger::init(); dioxus::desktop::launch(app); } @@ -17,7 +20,7 @@ fn app(cx: Scope) -> Element { cx.render(rsx!( div { h1 {"Select an option"} - h3 { "The radio is... " [state.is_playing()], "!" } + h3 { "The radio is... " [state.is_playing()] "!" } button { onclick: move |_| state.modify().reduce(PlayerAction::Pause), "Pause" } diff --git a/examples/ssr.rs b/examples/ssr.rs index b07d0a2c1..43240c99d 100644 --- a/examples/ssr.rs +++ b/examples/ssr.rs @@ -1,10 +1,40 @@ +//! Example: SSR +//! +//! This example shows how we can render the Dioxus Virtualdom using SSR. + +use std::fmt::Write; + use dioxus::prelude::*; -use dioxus::ssr; fn main() { + // We can render VirtualDoms let mut vdom = VirtualDom::new(app); let _ = vdom.rebuild(); - println!("{}", ssr::render_vdom(&vdom)); + println!("{}", dioxus::ssr::render_vdom(&vdom)); + + // Or we can render rsx! calls themselves + println!( + "{}", + dioxus::ssr::render_lazy(rsx! { + div { + h1 { "Hello, world!" } + } + }) + ); + + // We can configure the SSR rendering to add ids for rehydration + println!( + "{}", + dioxus::ssr::render_vdom_cfg(&vdom, |c| c.pre_render(true)) + ); + + // We can even render as a writer + let mut file = String::new(); + let _ = file.write_fmt(format_args!( + "{}", + dioxus::ssr::TextRenderer::from_vdom(&vdom, Default::default()) + )); + println!("{}", file); } fn app(cx: Scope) -> Element { diff --git a/examples/ssr/basic.rs b/examples/ssr/basic.rs deleted file mode 100644 index 2217b5a4a..000000000 --- a/examples/ssr/basic.rs +++ /dev/null @@ -1,26 +0,0 @@ -use dioxus::virtual_dom::VirtualDom; -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); - dom.rebuild(); - println!( - "{}", - dioxus_ssr::render_vdom(&dom, |c| c.newline(true).indent(true)) - ) -} - -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}"}}) - } - "hello world!" - } - )) -} diff --git a/examples/ssr/tofile.rs b/examples/ssr/tofile.rs deleted file mode 100644 index da6e0eeb0..000000000 --- a/examples/ssr/tofile.rs +++ /dev/null @@ -1,149 +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; -use dioxus_html::{GlobalAttributes, SvgAttributes}; -use dioxus_ssr::TextRenderer; - -fn main() { - use std::fs::File; - use std::io::Write; - - let mut file = File::create("example.html").unwrap(); - - let mut dom = VirtualDom::new(App); - - dom.rebuild(); - - file.write_fmt(format_args!( - "{}", - TextRenderer::from_vdom(&dom, Default::default()) - )) - .unwrap(); -} - -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" } - Header {} - Entry {} - Hero {} - Hero {} - Hero {} - Hero {} - Hero {} - } - )) -} - -pub fn Header(cx: Scope) -> Element { - cx.render(rsx! { - div { - header { class: "text-gray-400 bg-gray-900 body-font" - div { class: "container mx-auto flex flex-wrap p-5 flex-col md:flex-row items-center" - a { class: "flex title-font font-medium items-center text-white mb-4 md:mb-0" - StacksIcon {} - span { class: "ml-3 text-xl" "Hello Dioxus!"} - } - nav { class: "md:ml-auto flex flex-wrap items-center text-base justify-center" - a { class: "mr-5 hover:text-white" "First Link"} - a { class: "mr-5 hover:text-white" "Second Link"} - a { class: "mr-5 hover:text-white" "Third Link"} - a { class: "mr-5 hover:text-white" "Fourth Link"} - } - button { - class: "inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0" - "Button" - RightArrowIcon {} - } - } - } - } - }) -} - -pub fn Hero(cx: Scope) -> Element { - // - cx.render(rsx! { - section{ class: "text-gray-400 bg-gray-900 body-font" - div { class: "container mx-auto flex px-5 py-24 md:flex-row flex-col items-center" - div { class: "lg:flex-grow md:w-1/2 lg:pr-24 md:pr-16 flex flex-col md:items-start md:text-left mb-16 md:mb-0 items-center text-center" - h1 { class: "title-font sm:text-4xl text-3xl mb-4 font-medium text-white" - br { class: "hidden lg:inline-block" } - "Dioxus Sneak Peek" - } - p { - class: "mb-8 leading-relaxed" - - "Dioxus is a new UI framework that makes it easy and simple to write cross-platform apps using web - technologies! It is functional, fast, and portable. Dioxus can run on the web, on the desktop, and - on mobile and embedded platforms." - - } - div { class: "flex justify-center" - button { - class: "inline-flex text-white bg-indigo-500 border-0 py-2 px-6 focus:outline-none hover:bg-indigo-600 rounded text-lg" - "Learn more" - } - button { - class: "ml-4 inline-flex text-gray-400 bg-gray-800 border-0 py-2 px-6 focus:outline-none hover:bg-gray-700 hover:text-white rounded text-lg" - "Build an app" - } - } - } - div { class: "lg:max-w-lg lg:w-full md:w-1/2 w-5/6" - img { class: "object-cover object-center rounded" alt: "hero" src: "https://i.imgur.com/oK6BLtw.png" - referrerpolicy:"no-referrer" - } - } - } - } - }) -} - -pub fn Entry(cx: Scope) -> Element { - // - cx.render(rsx! { - section{ class: "text-gray-400 bg-gray-900 body-font" - div { class: "container mx-auto flex px-5 py-24 md:flex-row flex-col items-center" - textarea { - - } - } - } - }) -} - -pub fn StacksIcon(cx: Scope) -> Element { - cx.render(rsx!( - svg { - xmlns: "http://www.w3.org/2000/svg" - fill: "none" - stroke: "currentColor" - stroke_linecap: "round" - stroke_linejoin: "round" - stroke_width: "2" - class: "w-10 h-10 text-white p-2 bg-indigo-500 rounded-full" - view_box: "0 0 24 24" - path { d: "M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"} - } - )) -} - -pub fn RightArrowIcon(cx: Scope) -> Element { - cx.render(rsx!( - svg { - fill: "none" - stroke: "currentColor" - stroke_linecap: "round" - stroke_linejoin: "round" - stroke_width: "2" - class: "w-4 h-4 ml-1" - view_box: "0 0 24 24" - path { d: "M5 12h14M12 5l7 7-7 7"} - } - )) -} diff --git a/examples/suspense.rs b/examples/suspense.rs new file mode 100644 index 000000000..bea7573d1 --- /dev/null +++ b/examples/suspense.rs @@ -0,0 +1,72 @@ +//! Suspense in Dioxus +//! +//! Currently, `rsx!` does not accept futures as values. To achieve the functionality +//! of suspense, we need to make a new component that performs its own suspense +//! handling. +//! +//! In this example, we render the `Doggo` component which starts a future that +//! will cause it to fetch a random dog image from the Dog API. Since the data +//! is not ready immediately, we render some loading text. +//! +//! We can achieve the majoirty of suspense functionality by composing "suspenseful" +//! primitives in our own custom components. + +use dioxus::prelude::*; + +fn main() { + dioxus::desktop::launch(app) +} + +#[derive(serde::Deserialize)] +struct DogApi { + message: String, +} + +fn app(cx: Scope) -> Element { + cx.render(rsx! { + div { + h1 {"Dogs are very important"} + p { + "The dog or domestic dog (Canis familiaris[4][5] or Canis lupus familiaris[5])" + "is a domesticated descendant of the wolf which is characterized by an upturning tail." + "The dog derived from an ancient, extinct wolf,[6][7] and the modern grey wolf is the" + "dog's nearest living relative.[8] The dog was the first species to be domesticated,[9][8]" + "by hunter–gatherers over 15,000 years ago,[7] before the development of agriculture.[1]" + } + + h3 { "Illustrious Dog Photo" } + Doggo { } + } + }) +} + +/// This component will re-render when the future has finished +/// Suspense is achieved my moving the future into only the component that +/// actually renders the data. +fn Doggo(cx: Scope) -> Element { + let fut = use_future(&cx, || async move { + reqwest::get("https://dog.ceo/api/breeds/image/random/") + .await + .unwrap() + .json::() + .await + }); + + cx.render(match fut.value() { + Some(Ok(resp)) => rsx! { + button { + onclick: move |_| fut.restart(), + "Click to fetch another dog" + } + div { + img { + max_width: "500px", + max_height: "500px", + src: "{resp.message}", + } + } + }, + Some(Err(_)) => rsx! { div { "loading dogs failed" } }, + None => rsx! { div { "loading dogs..." } }, + }) +} diff --git a/examples/tailwind.rs b/examples/tailwind.rs index 2d5244f17..5e0632d34 100644 --- a/examples/tailwind.rs +++ b/examples/tailwind.rs @@ -1,8 +1,18 @@ +//! Example: Basic Tailwind usage +//! +//! This example shows how an app might be styled with TailwindCSS. +//! +//! To minify your tailwind bundle, currently you need to use npm. Follow these instructions: +//! +//! https://dev.to/arctic_hen7/how-to-set-up-tailwind-css-with-yew-and-trunk-il9 + + use dioxus::prelude::*; fn main() { - use dioxus::desktop::wry::application::platform::macos::*; - dioxus::desktop::launch_cfg(App, |c| { + dioxus::desktop::launch_cfg(app, |c| { + // we can configure our primary window through the Tauri Config + use dioxus::desktop::tao::platform::macos::*; c.with_window(|w| { w.with_fullsize_content_view(true) .with_titlebar_buttons_hidden(false) @@ -12,26 +22,9 @@ fn main() { }); } -const STYLE: &str = "body {overflow:hidden;}"; - -pub fn App(cx: Scope) -> Element { +pub fn app(cx: Scope) -> Element { cx.render(rsx!( - div { class: "overflow-hidden", - style { "{STYLE}" } - link { href:"https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css", rel:"stylesheet" } - Header {} - Entry {} - Hero {} - Hero {} - Hero {} - Hero {} - Hero {} - } - )) -} - -pub fn Header(cx: Scope) -> Element { - cx.render(rsx! { + link { href:"https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css", rel:"stylesheet" } div { header { class: "text-gray-400 bg-gray-900 body-font", div { class: "container mx-auto flex flex-wrap p-5 flex-col md:flex-row items-center", @@ -51,66 +44,53 @@ pub fn Header(cx: Scope) -> Element { RightArrowIcon {} } } - } - } - }) -} + } -pub fn Hero(cx: Scope) -> Element { - // - cx.render(rsx! { - section{ class: "text-gray-400 bg-gray-900 body-font", - div { class: "container mx-auto flex px-5 py-24 md:flex-row flex-col items-center", - div { class: "lg:flex-grow md:w-1/2 lg:pr-24 md:pr-16 flex flex-col md:items-start md:text-left mb-16 md:mb-0 items-center text-center", - h1 { class: "title-font sm:text-4xl text-3xl mb-4 font-medium text-white", - br { class: "hidden lg:inline-block" } - "Dioxus Sneak Peek" - } - p { - class: "mb-8 leading-relaxed", - - "Dioxus is a new UI framework that makes it easy and simple to write cross-platform apps using web - technologies! It is functional, fast, and portable. Dioxus can run on the web, on the desktop, and - on mobile and embedded platforms." - - } - div { class: "flex justify-center", - button { - class: "inline-flex text-white bg-indigo-500 border-0 py-2 px-6 focus:outline-none hover:bg-indigo-600 rounded text-lg", - "Learn more" + section { class: "text-gray-400 bg-gray-900 body-font", + div { class: "container mx-auto flex px-5 py-24 md:flex-row flex-col items-center", + div { class: "lg:flex-grow md:w-1/2 lg:pr-24 md:pr-16 flex flex-col md:items-start md:text-left mb-16 md:mb-0 items-center text-center", + h1 { class: "title-font sm:text-4xl text-3xl mb-4 font-medium text-white", + br { class: "hidden lg:inline-block" } + "Dioxus Sneak Peek" } - button { - class: "ml-4 inline-flex text-gray-400 bg-gray-800 border-0 py-2 px-6 focus:outline-none hover:bg-gray-700 hover:text-white rounded text-lg", - "Build an app" + p { + class: "mb-8 leading-relaxed", + + "Dioxus is a new UI framework that makes it easy and simple to write cross-platform apps using web + technologies! It is functional, fast, and portable. Dioxus can run on the web, on the desktop, and + on mobile and embedded platforms." + + } + div { class: "flex justify-center", + button { + class: "inline-flex text-white bg-indigo-500 border-0 py-2 px-6 focus:outline-none hover:bg-indigo-600 rounded text-lg", + "Learn more" + } + button { + class: "ml-4 inline-flex text-gray-400 bg-gray-800 border-0 py-2 px-6 focus:outline-none hover:bg-gray-700 hover:text-white rounded text-lg", + "Build an app" + } + } + } + div { class: "lg:max-w-lg lg:w-full md:w-1/2 w-5/6", + img { + class: "object-cover object-center rounded", + src: "https://i.imgur.com/oK6BLtw.png", + referrerpolicy:"no-referrer", + alt: "hero", } } } - div { class: "lg:max-w-lg lg:w-full md:w-1/2 w-5/6", - img { class: "object-cover object-center rounded", alt: "hero", src: "https://i.imgur.com/oK6BLtw.png", - referrerpolicy:"no-referrer" - } - } } } - }) + )) } -pub fn Entry(cx: Scope) -> Element { - // - cx.render(rsx! { - section{ class: "text-gray-400 bg-gray-900 body-font", - div { class: "container mx-auto flex px-5 py-24 md:flex-row flex-col items-center", - textarea { - } - } - } - }) -} + pub fn StacksIcon(cx: Scope) -> Element { cx.render(rsx!( svg { - // xmlns: "http://www.w3.org/2000/svg" fill: "none", stroke: "currentColor", stroke_linecap: "round", @@ -122,6 +102,7 @@ pub fn StacksIcon(cx: Scope) -> Element { } )) } + pub fn RightArrowIcon(cx: Scope) -> Element { cx.render(rsx!( svg { diff --git a/examples/tasks.rs b/examples/tasks.rs index 0a63019d7..a0ab7675c 100644 --- a/examples/tasks.rs +++ b/examples/tasks.rs @@ -25,10 +25,10 @@ fn app(cx: Scope) -> Element { cx.render(rsx! { div { - h1 { "High-Five counter: {count}" } + h1 { "Current count: {count}" } button { - onclick: move |_| *count.modify() += 1, - "Click me!" + onclick: move |_| count.set(0), + "Reset the count" } } }) diff --git a/examples/todomvc.rs b/examples/todomvc.rs index 907779894..d88ec31b4 100644 --- a/examples/todomvc.rs +++ b/examples/todomvc.rs @@ -1,7 +1,5 @@ -#![allow(non_upper_case_globals, non_snake_case)] use dioxus::prelude::*; -use im_rc::HashMap; -use std::rc::Rc; + fn main() { dioxus::desktop::launch(app); @@ -14,99 +12,125 @@ pub enum FilterState { Completed, } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub struct TodoItem { pub id: u32, pub checked: bool, pub contents: String, } -fn app(cx: Scope) -> Element { - let draft = use_state(&cx, || "".to_string()); - let todos = use_state(&cx, || HashMap::>::new()); - let filter = use_state(&cx, || FilterState::All); - let todolist = todos +pub fn app(cx: Scope<()>) -> Element { + let todos = use_state(&cx, || im_rc::HashMap::::default()); + let filter = use_state(&cx, || FilterState::All); + let draft = use_state(&cx, || "".to_string()); + let mut todo_id = use_state(&cx, || 0); + + // Filter the todos based on the filter state + let mut filtered_todos = todos .iter() - .filter(|(_id, item)| match *filter { + .filter(|(_, item)| match *filter { FilterState::All => true, FilterState::Active => !item.checked, FilterState::Completed => item.checked, }) - .map(|(id, todo)| { - rsx!(TodoEntry { - key: "{id}", - todo: todo.clone() - }) - }) + .map(|f| *f.0) .collect::>(); + filtered_todos.sort_unstable(); - let items_left = todolist.len(); + let show_clear_completed = todos.values().any(|todo| todo.checked); + let items_left = filtered_todos.len(); let item_text = match items_left { 1 => "item", _ => "items", }; - cx.render(rsx!( - div { id: "app", + cx.render(rsx!{ + section { class: "todoapp", style { [include_str!("./assets/todomvc.css")] } div { - header { - class: "header", - h1 { "todos" } + 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.clone()), + onkeydown: move |evt| { + if evt.key == "Enter" { + if !draft.is_empty() { + todos.modify().insert( + *todo_id, + TodoItem { + id: *todo_id, + checked: false, + contents: draft.get().clone(), + }, + ); + todo_id += 1; + draft.set("".to_string()); + } + } + } } } - todolist, + ul { class: "todo-list", + filtered_todos.iter().map(|id| rsx!(todo_entry( key: "{id}", id: *id, todos: todos ))) + } (!todos.is_empty()).then(|| rsx!( - footer { - span { - strong {"{items_left}"} - span {"{item_text} left"} + footer { class: "footer", + span { class: "todo-count", + 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" }} + 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 |_| todos.modify().retain(|_, todo| todo.checked == false), + "Clear 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" }} - } } - )) + 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 { - todo: Rc, +#[derive(Props)] +pub struct TodoEntryProps<'a> { + todos: UseState<'a, im_rc::HashMap>, + id: u32, } -pub fn TodoEntry(cx: Scope) -> Element { +pub fn todo_entry<'a>(cx: Scope<'a, TodoEntryProps<'a>>) -> Element { + let todo = &cx.props.todos[&cx.props.id]; let is_editing = use_state(&cx, || false); - let contents = use_state(&cx, || String::from("")); - let todo = &cx.props.todo; + let completed = if todo.checked { "completed" } else { "" }; - cx.render(rsx! { - li { - "{todo.id}" - input { - class: "toggle", - r#type: "checkbox", - "{todo.checked}" + rsx!(cx, li { class: "{completed}", + div { class: "view", + input { class: "toggle", r#type: "checkbox", id: "cbg-{todo.id}", checked: "{todo.checked}", + onchange: move |evt| { + cx.props.todos.modify()[&cx.props.id].checked = evt.value.parse().unwrap(); + } } - is_editing.then(|| rsx!{ + label { r#for: "cbg-{todo.id}", pointer_events: "none", "{todo.contents}" } + is_editing.then(|| rsx!{ input { - value: "{contents}", - oninput: move |evt| contents.set(evt.value.clone()) + value: "{todo.contents}", + oninput: move |evt| cx.props.todos.modify()[&cx.props.id].contents = evt.value.clone(), } }) } diff --git a/examples/weather_app.rs b/examples/weather_app.rs deleted file mode 100644 index e174fd397..000000000 --- a/examples/weather_app.rs +++ /dev/null @@ -1,67 +0,0 @@ -//! Example: Weather App -//! -------------------- -//! -//! -//! - -use dioxus::prelude::*; - -fn main() { - dioxus::desktop::launch(app); -} - -const ENDPOINT: &str = "https://api.openweathermap.org/data/2.5/weather"; - -fn app(cx: Scope) -> Element { - // - let body = use_suspense( - &cx, - || async { - todo!() - // let content = reqwest::get(ENDPOINT) - // .await - // .unwrap() - // .json::() - // .await - // .unwrap(); - }, - |props| { - // - cx.render(rsx!(WeatherDisplay {})) - }, - ); - - cx.render(rsx! { - div { - {body} - } - }) -} - -#[derive(PartialEq, Props)] -struct WeatherProps {} - -fn WeatherDisplay(cx: Scope) -> Element { - cx.render(rsx!( - div { class: "flex items-center justify-center flex-col", - div { class: "flex items-center justify-center", - div { class: "flex flex-col bg-white rounded p-4 w-full max-w-xs", - div{ class: "font-bold text-xl", - "Jon's awesome site!!" - } - div{ class: "text-sm text-gray-500", - "He worked so hard on it :)" - } - div { class: "flex flex-row items-center justify-center mt-6", - div { class: "font-medium text-6xl", - "1337" - } - } - div { class: "flex flex-row justify-between mt-6", - "Legit made my own React" - } - } - } - } - )) -} diff --git a/examples/web/async_web.rs b/examples/web/async_web.rs deleted file mode 100644 index e18b4269d..000000000 --- a/examples/web/async_web.rs +++ /dev/null @@ -1,53 +0,0 @@ -//! Basic example that renders a simple VNode to the browser. -use dioxus_core as dioxus; -use dioxus_core::prelude::*; -use dioxus_core_macro::*; -use dioxus_hooks::*; -use dioxus_html as dioxus_elements; - -fn main() { - console_error_panic_hook::set_once(); - wasm_logger::init(wasm_logger::Config::new(log::Level::Debug)); - dioxus_web::launch(APP) -} - -#[derive(serde::Deserialize)] -struct DogApi { - message: String, -} - -static APP: Component = |(cx, _props)| { - let state = use_state(&cx, || 0); - - const ENDPOINT: &str = "https://dog.ceo/api/breeds/image/random/"; - let doggo = use_suspense( - cx, - || async { reqwest::get(ENDPOINT).await.unwrap().json::().await }, - |cx, res| match res { - Ok(res) => rsx!( - cx, - img { - src: "{res.message}" - } - ), - Err(_err) => rsx!(cx, div { "No doggos for you :(" }), - }, - ); - - rsx!(cx, div { align_items: "center" - section { class: "py-12 px-4 text-center" - div { class: "w-full max-w-2xl mx-auto" - span { class: "text-sm font-semibold" - "count: {state}" - } - div { - button { onclick: move |_| state.set(state + 1), "incr" } - button { onclick: move |_| state.set(state - 1), "decr" } - } - div { - {doggo} - } - } - } - }) -}; diff --git a/examples/web/basic.rs b/examples/web/basic.rs deleted file mode 100644 index 037402613..000000000 --- a/examples/web/basic.rs +++ /dev/null @@ -1,89 +0,0 @@ -//! Basic example that renders a simple VNode to the browser. -use dioxus_core as dioxus; -use dioxus_core::prelude::*; -use dioxus_core_macro::*; -use dioxus_hooks::*; -use dioxus_html as dioxus_elements; - -fn main() { - // Setup logging - wasm_logger::init(wasm_logger::Config::new(log::Level::Debug)); - console_error_panic_hook::set_once(); - - // Run the app - dioxus_web::launch(APP) -} - -static APP: Component = |cx| { - let mut count = use_state(&cx, || 3); - let content = use_state(&cx, || String::from("h1")); - let text_content = use_state(&cx, || String::from("Hello, world!")); - - cx.render(rsx! { - div { - h1 { "content val is {content}" } - - input { - r#type: "text", - value: "{text_content}" - oninput: move |e| text_content.set(e.value.clone()) - } - - {(0..10).map(|_| { - rsx!( - button { - onclick: move |_| count += 1, - "Click to add." - "Current count: {count}" - } - br {} - ) - })} - - select { - name: "cars" - id: "cars" - value: "{content}" - oninput: move |ev| { - content.set(ev.value.clone()); - match ev.value.as_str() { - "h1" => count.set(0), - "h2" => count.set(5), - "h3" => count.set(10), - _ => {} - } - }, - - option { value: "h1", "h1" } - option { value: "h2", "h2" } - option { value: "h3", "h3" } - } - - {render_list(cx, *count)} - - {render_bullets(cx)} - - CHILD {} - } - }) -}; - -fn render_bullets(cx: Scope) -> Element { - rsx!(cx, div { - "bite me" - }) -} - -fn render_list(cx: Scope, count: usize) -> Element { - let items = (0..count).map(|f| { - rsx! { - li { "a - {f}" } - li { "b - {f}" } - li { "c - {f}" } - } - }); - - rsx!(cx, ul { {items} }) -} - -static CHILD: Component = |cx, _| rsx!(cx, div {"hello child"}); diff --git a/examples/web/blah.rs b/examples/web/blah.rs deleted file mode 100644 index 95f3a5875..000000000 --- a/examples/web/blah.rs +++ /dev/null @@ -1,55 +0,0 @@ -//! Basic example that renders a simple VNode to the browser. - -use dioxus::events::on::MouseEvent; -use dioxus_core as dioxus; -use dioxus_core::prelude::*; -use dioxus_core_macro::*; -use dioxus_hooks::*; -use dioxus_html as dioxus_elements; - -use std::future::Future; - -use std::{pin::Pin, time::Duration}; - -use dioxus::prelude::*; - -use dioxus_web::*; - -fn main() { - // Setup logging - wasm_logger::init(wasm_logger::Config::new(log::Level::Debug)); - console_error_panic_hook::set_once(); - - // Run the app - dioxus_web::launch(App) -} - -static App: Component = |cx| { - let mut state = use_state(&cx, || 0); - cx.render(rsx! { - div { - style: { - align_items: "center" - } - section { class: "py-12 px-4 text-center" - div { class: "w-full max-w-2xl mx-auto" - span { class: "text-sm font-semibold" - "static subtree" - } - } - } - section { class: "py-12 px-4 text-center" - div { class: "w-full max-w-2xl mx-auto" - span { class: "text-sm font-semibold" - "dynamic subtree {state}" - } - div { - button { onclick: move |e| state+=1, "incr" } - br {} - button { onclick: move |e| state-=1, "decr" } - } - } - } - } - }) -}; diff --git a/examples/web/btns.rs b/examples/web/btns.rs deleted file mode 100644 index 757627dbc..000000000 --- a/examples/web/btns.rs +++ /dev/null @@ -1,69 +0,0 @@ -//! Example: Webview Renderer -//! ------------------------- -//! -//! This example shows how to use the dioxus_desktop crate to build a basic desktop application. -//! -//! Under the hood, the dioxus_desktop crate bridges a native Dioxus VirtualDom with a custom prebuit application running -//! in the webview runtime. Custom handlers are provided for the webview instance to consume patches and emit user events -//! into the native VDom instance. -//! -//! Currently, NodeRefs won't work properly, but all other event functionality will. - -use dioxus::prelude::*; -use dioxus_core as dioxus; -use dioxus_core_macro::*; -use dioxus_hooks::*; -use dioxus_html as dioxus_elements; - -// #[cfg] -fn main() { - wasm_logger::init(wasm_logger::Config::new(log::Level::Debug)); - dioxus_web::launch(App); - // env_logger::init(); - // dioxus::web::launch(App); -} - -static App: Component = |cx| { - dbg!("rednering parent"); - cx.render(rsx! { - div { - But { - h1 {"he"} - } - // But { - // h1 {"llo"} - // } - // But { - // h1 {"world"} - // } - } - }) -}; - -static But: Component = |cx| { - let mut count = use_state(&cx, || 0); - - // let d = Dropper { name: "asd" }; - // let handler = move |_| { - // dbg!(d.name); - // }; - - cx.render(rsx! { - div { - h1 { "Hifive counter: {count}" } - {cx.children()} - button { onclick: move |_| count += 1, "Up high!" } - button { onclick: move |_| count -= 1, "Down low!" } - // button { onclick: {handler}, "Down low!" } - } - }) -}; - -// struct Dropper { -// name: &'static str, -// } -// impl Drop for Dropper { -// fn drop(&mut self) { -// dbg!("dropped"); -// } -// } diff --git a/examples/web/crm2.rs b/examples/web/crm2.rs deleted file mode 100644 index edbd534af..000000000 --- a/examples/web/crm2.rs +++ /dev/null @@ -1,116 +0,0 @@ -/* -Tiny CRM: A port of the Yew CRM example to Dioxus. -*/ - -use dioxus_core as dioxus; -use dioxus_core::prelude::*; -use dioxus_core_macro::*; -use dioxus_hooks::*; -use dioxus_html as dioxus_elements; -fn main() { - // Setup logging - wasm_logger::init(wasm_logger::Config::new(log::Level::Debug)); - console_error_panic_hook::set_once(); - - dioxus_web::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 scene = use_state(&cx, || Scene::ClientsList); - let clients = use_ref(&cx, || vec![] as Vec); - - let firstname = use_state(&cx, || String::new()); - let lastname = use_state(&cx, || String::new()); - let description = use_state(&cx, || String::new()); - - let scene = match *scene { - Scene::ClientsList => { - rsx!(cx, div { class: "crm" - h2 { "List of clients" margin_bottom: "10px" } - div { class: "clients" margin_left: "10px" - {clients.read().iter().map(|client| rsx!( - div { class: "client" style: "margin-bottom: 50px" - p { "First Name: {client.first_name}" } - p { "Last Name: {client.last_name}" } - p {"Description: {client.description}"} - }) - )} - } - button { class: "pure-button pure-button-primary" onclick: move |_| scene.set(Scene::NewClientForm), "Add New" } - button { class: "pure-button" onclick: move |_| scene.set(Scene::Settings), "Settings" } - }) - } - Scene::NewClientForm => { - let add_new = move |_| { - clients.write().push(Client { - description: (*description).clone(), - first_name: (*firstname).clone(), - last_name: (*lastname).clone(), - }); - description.set(String::new()); - firstname.set(String::new()); - lastname.set(String::new()); - }; - rsx!(cx, div { class: "crm" - h2 {"Add new client" margin_bottom: "10px" } - form { class: "pure-form" - input { class: "new-client firstname" placeholder: "First name" value: "{firstname}" - oninput: move |evt| firstname.set(evt.value.clone()) - } - input { class: "new-client lastname" placeholder: "Last name" value: "{lastname}" - oninput: move |evt| lastname.set(evt.value.clone()) - } - textarea { class: "new-client description" placeholder: "Description" value: "{description}" - oninput: move |evt| description.set(evt.value.clone()) - } - } - button { class: "pure-button pure-button-primary", onclick: {add_new}, "Add New" } - button { class: "pure-button", onclick: move |_| scene.set(Scene::ClientsList), "Go Back" } - }) - } - Scene::Settings => { - rsx!(cx, div { - h2 { "Settings" margin_bottom: "10px" } - button { - background: "rgb(202, 60, 60)" - class: "pure-button pure-button-primary" - onclick: move |_| { - clients.write().clear(); - 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} - }) -}; diff --git a/examples/web/demo.rs b/examples/web/demo.rs deleted file mode 100644 index d3f7e40e6..000000000 --- a/examples/web/demo.rs +++ /dev/null @@ -1,147 +0,0 @@ -//! Basic example that renders a simple VNode to the browser. - -use dioxus::events::on::MouseEvent; -use dioxus_core as dioxus; -use dioxus_core::prelude::*; -use dioxus_core_macro::*; -use dioxus_hooks::*; -use dioxus_html as dioxus_elements; -use std::future::Future; - -use std::{pin::Pin, time::Duration}; - -use dioxus::prelude::*; -use dioxus_html::SvgAttributes; - -use dioxus_web::*; - -fn main() { - // Setup logging - wasm_logger::init(wasm_logger::Config::new(log::Level::Debug)); - console_error_panic_hook::set_once(); - - // Run the app - dioxus_web::launch(App) -} - -pub static App: Component = |cx| { - cx.render(rsx!( - div { class: "overflow-hidden" - link { href:"https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel:"stylesheet" } - Header {} - // Entry {} - Hero {} - Hero {} - Hero {} - Hero {} - Hero {} - } - )) -}; - -pub static Header: Component = |cx| { - cx.render(rsx! { - div { - header { class: "text-gray-400 bg-gray-900 body-font" - div { class: "container mx-auto flex flex-wrap p-5 flex-col md:flex-row items-center" - a { class: "flex title-font font-medium items-center text-white mb-4 md:mb-0" - StacksIcon {} - span { class: "ml-3 text-xl" "Hello Dioxus!"} - } - nav { class: "md:ml-auto flex flex-wrap items-center text-base justify-center" - a { class: "mr-5 hover:text-white" "First Link"} - a { class: "mr-5 hover:text-white" "Second Link"} - a { class: "mr-5 hover:text-white" "Third Link"} - a { class: "mr-5 hover:text-white" "Fourth Link"} - } - button { - class: "inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0" - "Button" - RightArrowIcon {} - } - } - } - } - }) -}; - -pub static Hero: Component = |cx| { - // - cx.render(rsx! { - section{ class: "text-gray-400 bg-gray-900 body-font" - div { class: "container mx-auto flex px-5 py-24 md:flex-row flex-col items-center" - div { class: "lg:flex-grow md:w-1/2 lg:pr-24 md:pr-16 flex flex-col md:items-start md:text-left mb-16 md:mb-0 items-center text-center" - h1 { class: "title-font sm:text-4xl text-3xl mb-4 font-medium text-white" - br { class: "hidden lg:inline-block" } - "Dioxus Sneak Peek" - } - p { - class: "mb-8 leading-relaxed" - - "Dioxus is a new UI framework that makes it easy and simple to write cross-platform apps using web - technologies! It is functional, fast, and portable. Dioxus can run on the web, on the desktop, and - on mobile and embedded platforms." - - } - div { class: "flex justify-center" - button { - class: "inline-flex text-white bg-indigo-500 border-0 py-2 px-6 focus:outline-none hover:bg-indigo-600 rounded text-lg" - "Learn more" - } - button { - class: "ml-4 inline-flex text-gray-400 bg-gray-800 border-0 py-2 px-6 focus:outline-none hover:bg-gray-700 hover:text-white rounded text-lg" - "Build an app" - } - } - } - div { class: "lg:max-w-lg lg:w-full md:w-1/2 w-5/6" - img { class: "object-cover object-center rounded" alt: "hero" src: "https://i.imgur.com/oK6BLtw.png" - referrerpolicy:"no-referrer" - } - } - } - } - }) -}; -pub static Entry: Component = |cx| { - // - cx.render(rsx! { - section{ class: "text-gray-400 bg-gray-900 body-font" - div { class: "container mx-auto flex px-5 py-24 md:flex-row flex-col items-center" - textarea { - - } - } - } - }) -}; - -pub static StacksIcon: Component = |cx| { - cx.render(rsx!( - svg { - // xmlns: "http://www.w3.org/2000/svg" - fill: "none" - stroke: "currentColor" - stroke_linecap: "round" - stroke_linejoin: "round" - stroke_width: "2" - // class: "w-10 h-10 text-white p-2 bg-indigo-500 rounded-full" - viewBox: "0 0 24 24" - path { d: "M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"} - } - )) -}; -pub static RightArrowIcon: Component = |cx| { - cx.render(rsx!( - svg { - fill: "none" - stroke: "currentColor" - stroke_linecap: "round" - stroke_linejoin: "round" - stroke_width: "2" - // class: "w-4 h-4 ml-1" - viewBox: "0 0 24 24" - path { d: "M5 12h14M12 5l7 7-7 7"} - } - )) -}; diff --git a/examples/web_tick.rs b/examples/web_tick.rs deleted file mode 100644 index ae33d769a..000000000 --- a/examples/web_tick.rs +++ /dev/null @@ -1,127 +0,0 @@ -#![allow(non_upper_case_globals, non_snake_case)] -//! Example: Webview Renderer -//! ------------------------- -//! -//! This example shows how to use the dioxus_desktop crate to build a basic desktop application. -//! -//! Under the hood, the dioxus_desktop crate bridges a native Dioxus VirtualDom with a custom prebuilt application running -//! in the webview runtime. Custom handlers are provided for the webview instance to consume patches and emit user events -//! into the native VDom instance. -//! -//! Currently, NodeRefs won't work properly, but all other event functionality will. - -use dioxus::prelude::*; - -fn main() { - dioxus::desktop::launch(app); -} - -fn app(cx: Scope) -> Element { - let mut rng = SmallRng::from_entropy(); - - cx.render(rsx! { - table { - tbody { - (0..1_000).map(|f| { - let label = Label::new(&mut rng); - rsx! (Row { row_id: f, label: label }) - }) - } - } - }) -} - -#[derive(PartialEq, Props)] -struct RowProps { - row_id: usize, - label: Label, -} -fn Row(cx: Scope) -> Element { - let [adj, col, noun] = cx.props.label.0; - 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", "{adj}" "{col}" "{noun}" } - } - td { class: "col-md-1", - a { class: "remove", onclick: move |_| {/* remove */}, - span { class: "glyphicon glyphicon-remove remove", aria_hidden: "true" } - } - } - td { class: "col-md-6" } - } - }) -} -use rand::prelude::*; - -#[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(), - ]) - } -} - -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", -]; - -#[cfg(target_arch = "wasm32")] -fn intern_strings() { - for adj in ADJECTIVES { - wasm_bindgen::intern(adj); - } - for col in COLOURS { - wasm_bindgen::intern(col); - } - for no in NOUNS { - wasm_bindgen::intern(no); - } - wasm_bindgen::intern("col-md-1"); - wasm_bindgen::intern("col-md-6"); - wasm_bindgen::intern("glyphicon glyphicon-remove remove"); - wasm_bindgen::intern("remove"); - wasm_bindgen::intern("dioxus"); - wasm_bindgen::intern("lbl"); - wasm_bindgen::intern("true"); -} diff --git a/examples/webview_web.rs b/examples/webview_web.rs deleted file mode 100644 index aa762cb88..000000000 --- a/examples/webview_web.rs +++ /dev/null @@ -1,28 +0,0 @@ -//! Example: Webview Renderer -//! ------------------------- -//! -//! This example shows how to use the dioxus_desktop crate to build a basic desktop application. -//! -//! Under the hood, the dioxus_desktop crate bridges a native Dioxus VirtualDom with a custom prebuilt application running -//! in the webview runtime. Custom handlers are provided for the webview instance to consume patches and emit user events -//! into the native VDom instance. -//! -//! Currently, NodeRefs won't work properly, but all other event functionality will. - -use dioxus::prelude::*; - -fn main() { - dioxus::desktop::launch(app); -} - -fn app(cx: Scope) -> Element { - let mut count = use_state(&cx, || 0); - - cx.render(rsx! { - div { - h1 { "Hifive counter: {count}" } - button { onclick: move |_| count += 1, "Up high!" } - button { onclick: move |_| count -= 1, "Down low!" } - } - }) -} diff --git a/examples/xss_safety.rs b/examples/xss_safety.rs index c08f31616..36fc81a95 100644 --- a/examples/xss_safety.rs +++ b/examples/xss_safety.rs @@ -9,10 +9,13 @@ fn main() { } fn app(cx: Scope) -> Element { - let contents = use_state(&cx, || String::from("")); + let contents = use_state(&cx, || { + String::from("") + }); cx.render(rsx! { div { + h1 {"Dioxus is XSS-Safe"} h3 { "{contents}" } input { value: "{contents}", diff --git a/packages/desktop/src/index.html b/packages/desktop/src/index.html index e03b993f7..5228818bd 100644 --- a/packages/desktop/src/index.html +++ b/packages/desktop/src/index.html @@ -5,7 +5,6 @@ - @@ -13,6 +12,7 @@
- + - + \ No newline at end of file diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index 523c6fa0d..1b4b80cff 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -191,7 +191,7 @@ impl DesktopController { // create `first_menu` let mut first_menu = MenuBar::new(); - first_menu.add_native_item(MenuItem::About("Todos".to_string())); + first_menu.add_native_item(MenuItem::About("App".to_string())); first_menu.add_native_item(MenuItem::Services); first_menu.add_native_item(MenuItem::Separator); first_menu.add_native_item(MenuItem::Hide); diff --git a/packages/hooks/src/usefuture.rs b/packages/hooks/src/usefuture.rs index f6e026af9..58f350124 100644 --- a/packages/hooks/src/usefuture.rs +++ b/packages/hooks/src/usefuture.rs @@ -3,27 +3,16 @@ use std::{cell::Cell, future::Future, rc::Rc}; pub fn use_future<'a, T: 'static, F: Future + 'static>( cx: &'a ScopeState, - f: impl FnOnce() -> F, -) -> (Option<&T>, FutureHandle<'a, T>) { - let state = cx.use_hook(|_| { + new_fut: impl FnOnce() -> F, +) -> &'a UseFuture { + let state = cx.use_hook(move |_| { // - let fut = f(); - let slot = Rc::new(Cell::new(None)); - let updater = cx.schedule_update(); - - let _slot = slot.clone(); - let new_fut = async move { - let res = fut.await; - _slot.set(Some(res)); - updater(); - }; - let task = cx.push_future(new_fut); - - UseFutureInner { - needs_regen: true, - slot, + UseFuture { + update: cx.schedule_update(), + needs_regen: Cell::new(true), + slot: Rc::new(Cell::new(None)), value: None, - task: Some(task), + task: None, } }); @@ -32,23 +21,55 @@ pub fn use_future<'a, T: 'static, F: Future + 'static>( state.task = None; } - ( - state.value.as_ref(), - FutureHandle { - cx, - value: Cell::new(None), - }, - ) + if state.needs_regen.get() { + // We don't need regen anymore + state.needs_regen.set(false); + + // Create the new future + let fut = new_fut(); + + // Clone in our cells + let slot = state.slot.clone(); + let updater = state.update.clone(); + + state.task = Some(cx.push_future(async move { + let res = fut.await; + slot.set(Some(res)); + updater(); + })); + } + + state } -struct UseFutureInner { - needs_regen: bool, +pub struct UseFuture { + update: Rc, + needs_regen: Cell, value: Option, slot: Rc>>, task: Option, } -pub struct FutureHandle<'a, T> { - cx: &'a ScopeState, - value: Cell>, +impl UseFuture { + pub fn restart(&self) { + self.needs_regen.set(true); + (self.update)(); + } + + // clears the value in the future slot without starting the future over + pub fn clear(&self) -> Option { + (self.update)(); + self.slot.replace(None) + } + + // Manually set the value in the future slot without starting the future over + pub fn set(&self, new_value: T) { + self.slot.set(Some(new_value)); + self.needs_regen.set(true); + (self.update)(); + } + + pub fn value(&self) -> Option<&T> { + self.value.as_ref() + } } diff --git a/packages/router/src/link.rs b/packages/router/src/link.rs index dd8e683d9..16fc707fe 100644 --- a/packages/router/src/link.rs +++ b/packages/router/src/link.rs @@ -42,7 +42,7 @@ pub fn Link<'a, R: Routable>(cx: Scope<'a, LinkProps<'a, R>>) -> Element { href: "#", class: format_args!("{}", cx.props.class.unwrap_or("")), onclick: move |_| service.push_route(cx.props.to.clone()), - {&cx.props.children} + &cx.props.children } }) } diff --git a/packages/ssr/src/lib.rs b/packages/ssr/src/lib.rs index 5902be660..2ac4473b2 100644 --- a/packages/ssr/src/lib.rs +++ b/packages/ssr/src/lib.rs @@ -194,7 +194,7 @@ impl<'a> TextRenderer<'a, '_> { // when the page is loaded, the `querySelectorAll` will be used to collect all the nodes, and then add // them interpreter's stack if let (true, Some(id)) = (self.cfg.pre_render, node.try_mounted_id()) { - write!(f, " dio_el=\"{}\"", id)?; + write!(f, " dioxus-id=\"{}\"", id)?; for _listener in el.listeners { // todo: write the listeners diff --git a/packages/web/src/dom.rs b/packages/web/src/dom.rs index 9ac102fe9..534bb7d7c 100644 --- a/packages/web/src/dom.rs +++ b/packages/web/src/dom.rs @@ -55,13 +55,13 @@ impl WebsysDom { // re-hydrate the page - only supports one virtualdom per page if cfg.hydrate { // Load all the elements into the arena - let node_list: NodeList = document.query_selector_all("dio_el").unwrap(); + let node_list: NodeList = document.query_selector_all("dioxus-id").unwrap(); let len = node_list.length() as usize; for x in 0..len { let node: Node = node_list.get(x as u32).unwrap(); let el: &Element = node.dyn_ref::().unwrap(); - let id: String = el.get_attribute("dio_el").unwrap(); + let id: String = el.get_attribute("dioxus-id").unwrap(); let id = id.parse::().unwrap(); nodes[id] = Some(node); }