diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index 4d320ea7c..9c19720cf 100644 --- a/packages/core/Cargo.toml +++ b/packages/core/Cargo.toml @@ -42,13 +42,13 @@ serde_repr = { version = "0.1.7", optional = true } [dev-dependencies] anyhow = "1.0.42" -async-std = { version = "1.9.0", features = ["attributes"] } -criterion = "0.3.5" dioxus-html = { path = "../html" } fern = { version = "0.6.0", features = ["colored"] } rand = { version = "0.8.4", features = ["small_rng"] } simple_logger = "1.13.0" dioxus-core-macro = { path = "../core-macro", version = "0.1.2" } +async-std = { version = "1.9.0", features = ["attributes"] } +criterion = "0.3.5" [features] default = [] diff --git a/packages/core/examples/async.rs b/packages/core/examples/async.rs index 9a1ff4a2c..24a4f34f7 100644 --- a/packages/core/examples/async.rs +++ b/packages/core/examples/async.rs @@ -7,9 +7,9 @@ fn main() { const App: FC<()> = |(cx, props)| { let id = cx.scope_id(); - cx.submit_task(Box::pin(async move { id })); + // cx.submit_task(Box::pin(async move { id })); - let (handle, contents) = use_task(cx, || async { "hello world".to_string() }); + // let (handle, contents) = use_task(cx, || async { "hello world".to_string() }); todo!() }; diff --git a/packages/core/tests/borrowedstate.rs b/packages/core/tests/borrowedstate.rs index 60cc0d3f2..b7d2e3f31 100644 --- a/packages/core/tests/borrowedstate.rs +++ b/packages/core/tests/borrowedstate.rs @@ -1,9 +1,16 @@ -use dioxus::{nodes::VSuspended, prelude::*, DomEdit, TestDom}; +#![allow(non_snake_case)] + +use dioxus::prelude::*; use dioxus_core as dioxus; use dioxus_core_macro::*; use dioxus_html as dioxus_elements; -static Parent: FC<()> = |(cx, props)| { +#[test] +fn test_borrowed_state() { + let _ = VirtualDom::new(Parent); +} + +fn Parent((cx, _): Component<()>) -> DomTree { let value = cx.use_hook(|_| String::new(), |f| &*f, |_| {}); cx.render(rsx! { @@ -14,11 +21,11 @@ static Parent: FC<()> = |(cx, props)| { Child { name: value } } }) -}; +} #[derive(Props)] struct ChildProps<'a> { - name: &'a String, + name: &'a str, } fn Child<'a>((cx, props): Component<'a, ChildProps>) -> DomTree<'a> { @@ -32,7 +39,7 @@ fn Child<'a>((cx, props): Component<'a, ChildProps>) -> DomTree<'a> { #[derive(Props)] struct Grandchild<'a> { - name: &'a String, + name: &'a str, } fn Child2<'a>((cx, props): Component<'a, Grandchild>) -> DomTree<'a> { @@ -40,6 +47,3 @@ fn Child2<'a>((cx, props): Component<'a, Grandchild>) -> DomTree<'a> { div { "Hello {props.name}!" } }) } - -#[test] -fn test_borrowed_state() {} diff --git a/packages/core/tests/create_dom.rs b/packages/core/tests/create_dom.rs index c26c8bed1..d8a79162d 100644 --- a/packages/core/tests/create_dom.rs +++ b/packages/core/tests/create_dom.rs @@ -1,3 +1,5 @@ +#![allow(unused, non_upper_case_globals)] + //! Prove that the dom works normally through virtualdom methods. //! //! This methods all use "rebuild" which completely bypasses the scheduler. diff --git a/packages/core/tests/diffing.rs b/packages/core/tests/diffing.rs index 7ab0731a6..bfb33b4d6 100644 --- a/packages/core/tests/diffing.rs +++ b/packages/core/tests/diffing.rs @@ -1,3 +1,4 @@ +#![allow(unused, non_upper_case_globals)] //! Diffing Tests //! //! These tests only verify that the diffing algorithm works properly for single components. diff --git a/packages/core/tests/display_vdom.rs b/packages/core/tests/display_vdom.rs index 107a2a1c9..ec81ac52c 100644 --- a/packages/core/tests/display_vdom.rs +++ b/packages/core/tests/display_vdom.rs @@ -1,19 +1,16 @@ +#![allow(unused, non_upper_case_globals)] + //! test that we can display the virtualdom properly //! //! //! -use std::{cell::RefCell, rc::Rc}; - -use anyhow::{Context, Result}; use dioxus::prelude::*; use dioxus_core as dioxus; use dioxus_core_macro::*; use dioxus_html as dioxus_elements; mod test_logging; -const IS_LOGGING_ENABLED: bool = true; - #[test] fn please_work() { static App: FC<()> = |(cx, props)| { diff --git a/packages/core/tests/eventsystem.rs b/packages/core/tests/eventsystem.rs deleted file mode 100644 index 7b0380efe..000000000 --- a/packages/core/tests/eventsystem.rs +++ /dev/null @@ -1,19 +0,0 @@ -use bumpalo::Bump; - -use anyhow::{Context, Result}; -use dioxus::{prelude::*, DomEdit}; -use dioxus_core as dioxus; -use dioxus_core_macro::*; -use dioxus_html as dioxus_elements; - -#[async_std::test] -async fn event_queue_works() { - static App: FC<()> = |(cx, props)| { - cx.render(rsx! { - div { "hello world" } - }) - }; - - let mut dom = VirtualDom::new(App); - let edits = dom.rebuild(); -} diff --git a/packages/core/tests/hooks.rs b/packages/core/tests/hooks.rs deleted file mode 100644 index 664355fdf..000000000 --- a/packages/core/tests/hooks.rs +++ /dev/null @@ -1,26 +0,0 @@ -use std::{cell::RefCell, rc::Rc}; - -use anyhow::{Context, Result}; -use dioxus::prelude::*; -use dioxus_core as dioxus; -use dioxus_html as dioxus_elements; - -type Shared = Rc>; - -#[test] -fn sample_refs() { - - // static App: FC<()> = |(cx, props)|{ - // let div_ref = use_node_ref::(cx); - - // cx.render(rsx! { - // div { - // style: { color: "red" }, - // node_ref: div_ref, - // onmouseover: move |_| { - // div_ref.borrow_mut().focus(); - // }, - // }, - // }) - // }; -} diff --git a/packages/core/tests/lifecycle.rs b/packages/core/tests/lifecycle.rs index 5e3d69a97..3b9c05cca 100644 --- a/packages/core/tests/lifecycle.rs +++ b/packages/core/tests/lifecycle.rs @@ -1,6 +1,6 @@ -//! Tests for the lifecycle of components. +#![allow(unused, non_upper_case_globals)] -use anyhow::{Context, Result}; +//! Tests for the lifecycle of components. use dioxus::prelude::*; use dioxus_core as dioxus; use dioxus_core_macro::*; diff --git a/packages/core/tests/sharedstate.rs b/packages/core/tests/sharedstate.rs index f5b730016..1983ec8c7 100644 --- a/packages/core/tests/sharedstate.rs +++ b/packages/core/tests/sharedstate.rs @@ -1,4 +1,6 @@ -use dioxus::{nodes::VSuspended, prelude::*, DomEdit, TestDom}; +#![allow(unused, non_upper_case_globals)] + +use dioxus::{prelude::*, DomEdit, TestDom}; use dioxus_core as dioxus; use dioxus_core_macro::*; use dioxus_html as dioxus_elements; @@ -7,12 +9,6 @@ use DomEdit::*; mod test_logging; -fn new_dom() -> TestDom { - const IS_LOGGING_ENABLED: bool = false; - test_logging::set_up_logging(IS_LOGGING_ENABLED); - TestDom::new() -} - #[test] fn shared_state_test() { struct MySharedState(&'static str); diff --git a/packages/core/tests/test_logging.rs b/packages/core/tests/test_logging.rs index 8f696be89..54b5982ef 100644 --- a/packages/core/tests/test_logging.rs +++ b/packages/core/tests/test_logging.rs @@ -1,6 +1,5 @@ pub fn set_up_logging(enabled: bool) { use fern::colors::{Color, ColoredLevelConfig}; - if !enabled { return; @@ -19,7 +18,7 @@ pub fn set_up_logging(enabled: bool) { // configure colors for the name of the level. // since almost all of them are the same as the color for the whole line, we // just clone `colors_line` and overwrite our changes - let colors_level = colors_line.clone().info(Color::Green); + let colors_level = colors_line.info(Color::Green); // here we set up our fern Dispatch // when running tests in batch, the logger is re-used, so ignore the logger error diff --git a/packages/core/tests/vdom_rebuild.rs b/packages/core/tests/vdom_rebuild.rs index 12fa26d4f..9ea872a17 100644 --- a/packages/core/tests/vdom_rebuild.rs +++ b/packages/core/tests/vdom_rebuild.rs @@ -1,3 +1,5 @@ +#![allow(unused, non_upper_case_globals)] + //! Rebuilding tests //! ---------------- //! diff --git a/packages/desktop/.vscode/settings.json b/packages/desktop/.vscode/settings.json deleted file mode 100644 index b3bde867c..000000000 --- a/packages/desktop/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "rust-analyzer.cargo.allFeatures": true -} diff --git a/packages/desktop/.vscode/spellright.dict b/packages/desktop/.vscode/spellright.dict deleted file mode 100644 index 59b2711ce..000000000 --- a/packages/desktop/.vscode/spellright.dict +++ /dev/null @@ -1 +0,0 @@ -WebviewWindow diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml index 18b925646..948ffcc0b 100644 --- a/packages/desktop/Cargo.toml +++ b/packages/desktop/Cargo.toml @@ -33,6 +33,7 @@ tokio_runtime = ["tokio"] [dev-dependencies] dioxus-html = { path = "../html" } dioxus-hooks = { path = "../hooks" } +simple_logger = "1.13.0" [build-dependencies] diff --git a/packages/desktop/examples/crm.rs b/packages/desktop/examples/crm.rs index 3971a4ab0..efb7efed2 100644 --- a/packages/desktop/examples/crm.rs +++ b/packages/desktop/examples/crm.rs @@ -22,14 +22,14 @@ pub struct Client { } static App: FC<()> = |(cx, _)| { - let scene = use_state(cx, || Scene::ClientsList); + let mut 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 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 { + let scene = match scene.get() { Scene::ClientsList => { rsx!(cx, div { class: "crm" h2 { "List of clients" margin_bottom: "10px" } @@ -61,13 +61,13 @@ static App: FC<()> = |(cx, _)| { 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()) + oninput: move |evt| firstname.set(evt.value) } input { class: "new-client lastname" placeholder: "Last name" value: "{lastname}" - oninput: move |evt| lastname.set(evt.value.clone()) + oninput: move |evt| lastname.set(evt.value) } textarea { class: "new-client description" placeholder: "Description" value: "{description}" - oninput: move |evt| description.set(evt.value.clone()) + oninput: move |evt| description.set(evt.value) } } button { class: "pure-button pure-button-primary", onclick: {add_new}, "Add New" } diff --git a/packages/desktop/examples/todomvc.css b/packages/desktop/examples/todomvc.css new file mode 100644 index 000000000..ab9dbd7a9 --- /dev/null +++ b/packages/desktop/examples/todomvc.css @@ -0,0 +1,379 @@ +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; + } +} diff --git a/packages/desktop/examples/todomvc.rs b/packages/desktop/examples/todomvc.rs new file mode 100644 index 000000000..fa2eae08e --- /dev/null +++ b/packages/desktop/examples/todomvc.rs @@ -0,0 +1,171 @@ +#![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 simple_logger::SimpleLogger; + +use std::collections::HashMap; + +fn main() { + if cfg!(debug_assertions) { + SimpleLogger::new().init().unwrap(); + } + + dioxus_desktop::launch(App, |c| c) +} + +#[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; + +pub static App: FC<()> = |(cx, _)| { + // Share our TodoList to the todos themselves + use_provide_state(cx, || Todos::new()); + + // Save state for the draft, filter + let mut draft = use_state(cx, || "".to_string()); + let mut filter = use_state(cx, || FilterState::All); + let mut todo_id = use_state(cx, || 0); + + // Consume the todos + let todos = use_shared_state::(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::>(); + 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.clone()) + 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): Component) -> DomTree { + let todos = use_shared_state::(cx)?; + + let _todos = todos.read(); + let todo = _todos.get(&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| { + todos.write().get_mut(&props.id).map(|todo| 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| { + todos.write().get_mut(&props.id).map(|todo| todo.contents = evt.value.clone()); + }, + } + })} + } + } + }) +} diff --git a/packages/desktop/src/events.rs b/packages/desktop/src/events.rs index f1e6d165e..82a698f05 100644 --- a/packages/desktop/src/events.rs +++ b/packages/desktop/src/events.rs @@ -49,7 +49,6 @@ fn make_synthetic_event(name: &str, val: serde_json::Value) -> Box(val).unwrap()) } "keydown" | "keypress" | "keyup" => { - dbg!(&val); let evt = serde_json::from_value::(val).unwrap(); Box::new(evt) } diff --git a/packages/desktop/src/index.js b/packages/desktop/src/index.js index 222509629..2caad4dc2 100644 --- a/packages/desktop/src/index.js +++ b/packages/desktop/src/index.js @@ -39,12 +39,12 @@ class Interpreter { } ReplaceWith(edit) { - console.log(edit); + // console.log(edit); let root = this.nodes[edit.root]; let els = this.stack.splice(this.stack.length - edit.m); - console.log(root); - console.log(els); + // console.log(root); + // console.log(els); root.replaceWith(...els); @@ -108,7 +108,7 @@ class Interpreter { this.listeners[event_name] = "bla"; this.root.addEventListener(event_name, (event) => { - console.log("CLICKED"); + // console.log("CLICKED"); const target = event.target; const val = target.getAttribute(`dioxus-event-${event_name}`); if (val == null) { @@ -119,30 +119,31 @@ class Interpreter { const scope_id = parseInt(fields[0]); const real_id = parseInt(fields[1]); - // console.log(`parsed event with scope_id ${scope_id} and real_id ${real_id}`); + // // console.log(`parsed event with scope_id ${scope_id} and real_id ${real_id}`); - console.log("message fired"); + // console.log("message fired"); let contents = serialize_event(event); - rpc.call('user_event', { + let evt = { event: event_name, scope: scope_id, mounted_dom_id: real_id, contents: contents, - }).then((reply) => { - console.log("reply received"); + }; + // console.log(evt); + rpc.call('user_event', evt).then((reply) => { + // console.log("reply received", reply); - // console.log(reply); this.stack.push(this.root); - let edits = reply.edits; + reply.map((reply) => { + let edits = reply.edits; + for (let x = 0; x < edits.length; x++) { + let edit = edits[x]; + let f = this[edit.type]; + f.call(this, edit); + } + }); - for (let x = 0; x < edits.length; x++) { - let edit = edits[x]; - let f = this[edit.type]; - f.call(this, edit); - } - - // console.log("initiated"); }) }); } @@ -153,25 +154,35 @@ class Interpreter { } SetAttribute(edit) { + // console.log("setting attr", edit); const name = edit.field; const value = edit.value; const ns = edit.ns; const node = this.nodes[edit.root] + if (ns == "style") { node.style[name] = value; - } else if (ns !== undefined) { + } else if ((ns != null) || (ns != undefined)) { node.setAttributeNS(ns, name, value); } else { - node.setAttribute(name, value); - } - if (name === "value") { - node.value = value; - } - if (name === "checked") { - node.checked = true; - } - if (name === "selected") { - node.selected = true; + switch (name) { + case "value": + node.value = value; + break; + case "checked": + // console.log("setting checked"); + node.checked = (value === "true"); + break; + case "selected": + node.selected = (value === "true"); + break; + case "dangerous_inner_html": + node.innerHTML = value; + break; + default: + // console.log("setting attr directly ", name, value); + node.setAttribute(name, value); + } } } RemoveAttribute(edit) { @@ -209,14 +220,14 @@ async function initialize() { } function apply_edits(edits, interpreter) { - console.log(edits); + // console.log(edits); for (let x = 0; x < edits.length; x++) { let edit = edits[x]; let f = interpreter[edit.type]; f.call(interpreter, edit); } - // console.log("stack completed: ", interpreter.stack); + // // console.log("stack completed: ", interpreter.stack); } function serialize_event(event) { @@ -244,8 +255,8 @@ const SerializeMap = { "focus": serialize_focus, "blur": serialize_focus, - "change": serialize_form, - // "change": serialize_change, + // "change": serialize_form, + "change": serialize_change, "input": serialize_form, "invalid": serialize_form, @@ -353,8 +364,19 @@ function serialize_keyboard(event) { function serialize_focus(_event) { return {} } -function serialize_change(_event) { - return {} + +function serialize_change(event) { + let target = event.target; + let value; + if (target.type === "checkbox" || target.type === "radio") { + value = target.checked ? "true" : "false"; + } else { + value = target.value ?? target.textContent; + } + + return { + value: value + } } function serialize_form(event) { let target = event.target; diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index 49fedf515..ce119e9bc 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -95,13 +95,13 @@ pub fn run( match event { Event::NewEvents(StartCause::Init) => { - let window = WindowBuilder::new().build(&event_loop).unwrap(); + let window = WindowBuilder::new().build(event_loop).unwrap(); let window_id = window.id(); let (event_tx, event_rx) = tokio::sync::mpsc::unbounded_channel(); let my_props = props_shared.take().unwrap(); - let sender = launch_vdom_with_tokio(root, my_props, event_tx.clone()); + let sender = launch_vdom_with_tokio(root, my_props, event_tx); let locked_receiver = Rc::new(RefCell::new(event_rx)); @@ -121,10 +121,15 @@ pub fn run( } "user_event" => { let event = events::trigger_from_serialized(req.params.unwrap()); + log::debug!("User event: {:?}", event); + sender.unbounded_send(SchedulerMsg::UiEvent(event)).unwrap(); + if let Some(BridgeEvent::Update(edits)) = rx.blocking_recv() { + log::info!("bridge received message {:?}", edits); Some(RpcResponse::new_result(req.id.take(), Some(edits))) } else { + log::info!("none received message"); None } } @@ -224,13 +229,20 @@ pub(crate) fn launch_vdom_with_tokio( let mut muts = vir.run_with_deadline(|| false); + log::debug!("finished running with deadline"); + + let mut edits = vec![]; + while let Some(edit) = muts.pop() { + log::debug!("sending message on channel with edit {:?}", edit); let edit_string = serde_json::to_value(Evt { edits: edit.edits }) .expect("serializing edits should never fail"); - event_tx - .send(BridgeEvent::Update(edit_string)) - .expect("Sending should not fail"); + edits.push(edit_string); } + + event_tx + .send(BridgeEvent::Update(serde_json::Value::Array(edits))) + .expect("Sending should not fail"); } }) }); diff --git a/packages/hooks/src/usestate.rs b/packages/hooks/src/usestate.rs index 76a72ea75..79402f22d 100644 --- a/packages/hooks/src/usestate.rs +++ b/packages/hooks/src/usestate.rs @@ -102,7 +102,7 @@ impl<'a, T: 'static> UseState<'a, T> { } } - pub fn set(&self, new_val: T) { + pub fn set(&mut self, new_val: T) { *self.inner.wip.borrow_mut() = Some(new_val); self.needs_update(); } diff --git a/packages/html/src/lib.rs b/packages/html/src/lib.rs index 9fe166997..0eac59265 100644 --- a/packages/html/src/lib.rs +++ b/packages/html/src/lib.rs @@ -1076,7 +1076,9 @@ builder_constructors! { /// Build a /// [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/code) /// element. - code {}; + code { + language: String, + }; /// Build a /// [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/data) @@ -1973,8 +1975,8 @@ pub trait SvgAttributes { vert_adv_y: "vert-adv-y", vert_origin_x: "vert-origin-x", vert_origin_y: "vert-origin-y", - viewBox: "viewBox", - viewTarget: "viewTarget", + view_box: "viewBox", + view_target: "viewTarget", visibility: "visibility", width: "width", widths: "widths", @@ -1985,11 +1987,11 @@ pub trait SvgAttributes { x1: "x1", x2: "x2", xmlns: "xmlns", - xChannelSelector: "xChannelSelector", + x_channel_selector: "xChannelSelector", y: "y", y1: "y1", y2: "y2", - yChannelSelector: "yChannelSelector", + y_channel_selector: "yChannelSelector", z: "z", zoomAndPan: "zoomAndPan", }