From d7968c987ff07134f480e9ab3869d518c1ecd554 Mon Sep 17 00:00:00 2001 From: t1m0t Date: Tue, 1 Feb 2022 20:40:29 +0100 Subject: [PATCH 01/28] wip: Makefile setup --- Makefile.toml | 42 +++++++++++++++++++++ packages/router/Cargo.toml | 56 ++++++++++++++-------------- packages/router/Makefile.toml | 10 +++++ packages/router/tests/route.rs | 68 ++++++++++++++++++++++++++++++++++ 4 files changed, 148 insertions(+), 28 deletions(-) create mode 100644 Makefile.toml create mode 100644 packages/router/Makefile.toml create mode 100644 packages/router/tests/route.rs diff --git a/Makefile.toml b/Makefile.toml new file mode 100644 index 000000000..23eeacea2 --- /dev/null +++ b/Makefile.toml @@ -0,0 +1,42 @@ +[config] +default_to_workspace = false +min_version = "0.32.4" + +[env] +CARGO_MAKE_CLIPPY_ARGS = "-- --deny=warnings" +CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true + +[config.modify_core_tasks] +namespace = "core" +private = true + +[tasks.tests] +category = "Testing" +dependencies = ["tests-setup"] +description = "Run all tests" +env = {CARGO_MAKE_WORKSPACE_SKIP_MEMBERS = ["**/examples/*", "**/packages/changelog"]} +run_task = {name = ["test-flow"], fork = true} + +[tasks.tests-setup] +private = true +script = [ + """ + test_flags = array --headless --firefox + dioxus_test_features = set wasm_test + dioxus_test_flags = array_join ${test_flags} " " + echo "running tests with flags: ${dioxus_test_flags} and features: ${dioxus_test_features}" + set_env DIOXUS_TEST_FLAGS ${dioxus_test_flags} + set_env DIOXUS_TEST_FEATURES ${dioxus_test_features} + """, +] +script_runner = "@duckscript" + +[tasks.test-flow] +dependencies = ["test"] +private = true +workspace = true + +[tasks.test] +args = ["test", "--all-targets"] +command = "cargo" +private = true diff --git a/packages/router/Cargo.toml b/packages/router/Cargo.toml index 1c6ed24d2..3bcb6a348 100644 --- a/packages/router/Cargo.toml +++ b/packages/router/Cargo.toml @@ -1,51 +1,51 @@ [package] -name = "dioxus-router" -version = "0.1.1" -edition = "2018" description = "Dioxus VirtualDOM renderer for the web browser using websys" -license = "MIT/Apache-2.0" -repository = "https://github.com/DioxusLabs/dioxus/" -homepage = "https://dioxuslabs.com" documentation = "https://dioxuslabs.com" +edition = "2018" +homepage = "https://dioxuslabs.com" keywords = ["dom", "ui", "gui", "react", "wasm"] +license = "MIT/Apache-2.0" +name = "dioxus-router" +repository = "https://github.com/DioxusLabs/dioxus/" +version = "0.1.1" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -dioxus-core = { path = "../core", version = "^0.1.9", default-features = false } -dioxus-html = { path = "../html", version = "^0.1.6", default-features = false } -dioxus-core-macro = { path = "../core-macro", version = "^0.1.7" } +dioxus-core = {path = "../core", version = "^0.1.9", default-features = false} +dioxus-core-macro = {path = "../core-macro", version = "^0.1.7"} +dioxus-html = {path = "../html", version = "^0.1.6", default-features = false} serde = "1" -url = "2.2.2" serde_urlencoded = "0.7" +url = "2.2.2" # for wasm -web-sys = { version = "0.3", features = [ - "Attr", - "Document", - "History", - "HtmlBaseElement", - "Event", - "NamedNodeMap", - "Url", - "UrlSearchParams", - "Window", -], optional = true } -wasm-bindgen = { version = "0.2", optional = true } -js-sys = { version = "0.3", optional = true } -gloo = { version = "0.5", optional = true } +gloo = {version = "0.5", optional = true} +js-sys = {version = "0.3", optional = true} log = "0.4.14" - +wasm-bindgen = {version = "0.2", optional = true} +web-sys = {version = "0.3", features = [ + "Attr", + "Document", + "History", + "HtmlBaseElement", + "Event", + "NamedNodeMap", + "Url", + "UrlSearchParams", + "Window", +], optional = true} [features] default = ["derive", "web"] -web = ["web-sys", "gloo", "js-sys", "wasm-bindgen"] +derive = [] desktop = [] mobile = [] -derive = [] +web = ["web-sys", "gloo", "js-sys", "wasm-bindgen"] [dev-dependencies] console_error_panic_hook = "0.1.7" -dioxus-web = { path = "../web" } +dioxus-web = {path = "../web"} log = "0.4.14" +wasm-bindgen-test = "0.3" wasm-logger = "0.2.0" diff --git a/packages/router/Makefile.toml b/packages/router/Makefile.toml new file mode 100644 index 000000000..27a6c9244 --- /dev/null +++ b/packages/router/Makefile.toml @@ -0,0 +1,10 @@ +[tasks.test] +args = [ + "test", + "@@split(DIOXUS_TEST_FLAGS, )", + "--", + "--features", + "${DIOXUS_TEST_FEATURES}", +] +command = "wasm-pack" +extend = "core::wasm-pack-base" diff --git a/packages/router/tests/route.rs b/packages/router/tests/route.rs new file mode 100644 index 000000000..08a5b0aff --- /dev/null +++ b/packages/router/tests/route.rs @@ -0,0 +1,68 @@ +#![cfg(target_arch = "wasm32")] + +use dioxus_core::prelude::*; +use dioxus_core_macro::*; +use dioxus_html as dioxus_elements; +use dioxus_router::*; +use gloo_utils::document; +use serde::{Deserialize, Serialize}; +use wasm_bindgen_test::*; + +wasm_bindgen_test_configure!(run_in_browser); + +#[wasm_bindgen_test] +fn simple_test() { + fn main() { + console_error_panic_hook::set_once(); + wasm_logger::init(wasm_logger::Config::new(log::Level::Debug)); + dioxus_web::launch(APP); + } + + static APP: Component = |cx| { + cx.render(rsx! { + Router { + onchange: move |route| log::info!("route changed to {}", route), + Route { to: "/", Home {} } + Route { to: "blog" + Route { to: "/", BlogList {} } + Route { to: ":id", BlogPost {} } + } + } + }) + }; + + fn Home(cx: Scope) -> Element { + cx.render(rsx! { + div { + h1 { "Home" } + } + }) + } + + fn BlogList(cx: Scope) -> Element { + cx.render(rsx! { + div { + + } + }) + } + + fn BlogPost(cx: Scope) -> Element { + let id = use_route(&cx).segment::("id")?; + + cx.render(rsx! { + div { + id: "test1", + id + } + }) + } + + main(); + + let element = gloo_utils::document() + .get_element_by_id("test1") + .expect("No result found. Most likely, the application crashed") + .inner_html(); + assert!(element, ""); +} From 8ad3f85872f77f3fc2a586dd18f980937a34a25d Mon Sep 17 00:00:00 2001 From: t1m0t Date: Tue, 1 Feb 2022 21:13:18 +0100 Subject: [PATCH 02/28] fix ordering to match original one --- .vscode/settings.json | 1 + packages/router/Cargo.toml | 59 +++++++++++++++++++------------------- 2 files changed, 31 insertions(+), 29 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index ac2195ccd..221164c7c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,4 +4,5 @@ "desktop", "router" ], + "editor.formatOnSave": false } diff --git a/packages/router/Cargo.toml b/packages/router/Cargo.toml index 3bcb6a348..af701d2f2 100644 --- a/packages/router/Cargo.toml +++ b/packages/router/Cargo.toml @@ -1,51 +1,52 @@ [package] -description = "Dioxus VirtualDOM renderer for the web browser using websys" -documentation = "https://dioxuslabs.com" -edition = "2018" -homepage = "https://dioxuslabs.com" -keywords = ["dom", "ui", "gui", "react", "wasm"] -license = "MIT/Apache-2.0" name = "dioxus-router" -repository = "https://github.com/DioxusLabs/dioxus/" version = "0.1.1" +edition = "2018" +description = "Dioxus VirtualDOM renderer for the web browser using websys" +license = "MIT/Apache-2.0" +repository = "https://github.com/DioxusLabs/dioxus/" +homepage = "https://dioxuslabs.com" +documentation = "https://dioxuslabs.com" +keywords = ["dom", "ui", "gui", "react", "wasm"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -dioxus-core = {path = "../core", version = "^0.1.9", default-features = false} -dioxus-core-macro = {path = "../core-macro", version = "^0.1.7"} -dioxus-html = {path = "../html", version = "^0.1.6", default-features = false} +dioxus-core = { path = "../core", version = "^0.1.9", default-features = false } +dioxus-html = { path = "../html", version = "^0.1.6", default-features = false } +dioxus-core-macro = { path = "../core-macro", version = "^0.1.7" } serde = "1" -serde_urlencoded = "0.7" url = "2.2.2" +serde_urlencoded = "0.7" # for wasm -gloo = {version = "0.5", optional = true} -js-sys = {version = "0.3", optional = true} +web-sys = { version = "0.3", features = [ + "Attr", + "Document", + "History", + "HtmlBaseElement", + "Event", + "NamedNodeMap", + "Url", + "UrlSearchParams", + "Window", +], optional = true } +wasm-bindgen = { version = "0.2", optional = true } +js-sys = { version = "0.3", optional = true } +gloo = { version = "0.5", optional = true } log = "0.4.14" -wasm-bindgen = {version = "0.2", optional = true} -web-sys = {version = "0.3", features = [ - "Attr", - "Document", - "History", - "HtmlBaseElement", - "Event", - "NamedNodeMap", - "Url", - "UrlSearchParams", - "Window", -], optional = true} + [features] default = ["derive", "web"] -derive = [] +web = ["web-sys", "gloo", "js-sys", "wasm-bindgen"] desktop = [] mobile = [] -web = ["web-sys", "gloo", "js-sys", "wasm-bindgen"] +derive = [] [dev-dependencies] console_error_panic_hook = "0.1.7" -dioxus-web = {path = "../web"} +dioxus-web = { path = "../web" } log = "0.4.14" -wasm-bindgen-test = "0.3" wasm-logger = "0.2.0" +wasm-bindgen-test = "0.3" From 81ca1a001402a415458e79f2e2a8e6891d85c843 Mon Sep 17 00:00:00 2001 From: t1m0t Date: Tue, 1 Feb 2022 21:34:57 +0100 Subject: [PATCH 03/28] updated workflow to include make tests --- .github/workflows/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b941960b7..ce7ffecbd 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -35,8 +35,8 @@ jobs: - run: sudo apt install libwebkit2gtk-4.0-dev libappindicator3-dev libgtk-3-dev - uses: actions-rs/cargo@v1 with: - command: test - args: --features "desktop, ssr, router" + command: make + args: tests fmt: name: Rustfmt From 3f97cbdaa5347bbcb2e32f68366b32245edd7839 Mon Sep 17 00:00:00 2001 From: t1m0t Date: Tue, 1 Feb 2022 21:44:21 +0100 Subject: [PATCH 04/28] format on save: toml excluded --- .vscode/settings.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 221164c7c..44bcb08e3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,5 +4,8 @@ "desktop", "router" ], - "editor.formatOnSave": false + "editor.formatOnSave": true, + "[toml]": { + "editor.formatOnSave": false + } } From 8568678f501c84d679c2fdf44be2a1423db2f1d2 Mon Sep 17 00:00:00 2001 From: t1m0t Date: Tue, 1 Feb 2022 21:50:55 +0100 Subject: [PATCH 05/28] install cargo make --- .github/workflows/main.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ce7ffecbd..9a6a7e143 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -33,6 +33,11 @@ jobs: - uses: Swatinem/rust-cache@v1 - run: sudo apt-get update - run: sudo apt install libwebkit2gtk-4.0-dev libappindicator3-dev libgtk-3-dev + - name: Install cargo-make + uses: actions-rs/cargo@v1 + with: + command: install + args: --debug cargo-make - uses: actions-rs/cargo@v1 with: command: make From 12cfd79d51a54e2da6aecd437559a563a7233b8b Mon Sep 17 00:00:00 2001 From: t1m0t Date: Tue, 1 Feb 2022 22:04:21 +0100 Subject: [PATCH 06/28] commented out failing example --- packages/desktop/examples/async.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/desktop/examples/async.rs b/packages/desktop/examples/async.rs index 056e7b591..47b17b1b4 100644 --- a/packages/desktop/examples/async.rs +++ b/packages/desktop/examples/async.rs @@ -1,4 +1,4 @@ -use dioxus::prelude::*; +/* use dioxus::prelude::*; use dioxus_core as dioxus; use dioxus_core_macro::*; use dioxus_hooks::*; @@ -32,3 +32,4 @@ fn app(cx: Scope) -> Element { } }) } + */ From 022d6d7850dba2e6f68edab3a89e383c8181321c Mon Sep 17 00:00:00 2001 From: t1m0t Date: Tue, 1 Feb 2022 22:37:34 +0100 Subject: [PATCH 07/28] exclude dioxus-mobile in tests (future feature) --- Makefile.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.toml b/Makefile.toml index 23eeacea2..95abec9de 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -37,6 +37,6 @@ private = true workspace = true [tasks.test] -args = ["test", "--all-targets"] +args = ["test", "--all-targets", "--workspace", "--exclude", "dioxus-mobile"] command = "cargo" private = true From b2b2dae7e611cb9eb0a0f5864759921151919655 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 2 Feb 2022 11:33:02 -0500 Subject: [PATCH 08/28] fix: diffing allows component reuse --- packages/core/src/diff.rs | 37 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 3f3629c10..48163ccf7 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -270,26 +270,23 @@ impl<'b> DiffState<'b> { fn create_component_node(&mut self, vcomponent: &'b VComponent<'b>) -> usize { let parent_idx = self.current_scope().unwrap(); - // ensure this scope doesn't already exist if we're trying to create it - debug_assert!( - vcomponent - .scope - .get() - .and_then(|f| self.scopes.get_scope(f)) - .is_none(), - "component scope already exists" - ); - - // Insert a new scope into our component list - let props: Box = vcomponent.props.borrow_mut().take().unwrap(); - let props: Box = unsafe { std::mem::transmute(props) }; - let new_idx = self.scopes.new_with_key( - vcomponent.user_fc, - props, - Some(parent_idx), - self.element_stack.last().copied().unwrap(), - 0, - ); + // the component might already exist - if it does, we need to reuse it + // this makes figure out when to drop the component more complicated + let new_idx = if let Some(idx) = vcomponent.scope.get() { + assert!(self.scopes.get_scope(idx).is_some()); + idx + } else { + // Insert a new scope into our component list + let props: Box = vcomponent.props.borrow_mut().take().unwrap(); + let props: Box = unsafe { std::mem::transmute(props) }; + self.scopes.new_with_key( + vcomponent.user_fc, + props, + Some(parent_idx), + self.element_stack.last().copied().unwrap(), + 0, + ) + }; // Actually initialize the caller's slot with the right address vcomponent.scope.set(Some(new_idx)); From 24d78682ad7dfe72297844929b7d79a321e73d1e Mon Sep 17 00:00:00 2001 From: t1m0t Date: Wed, 2 Feb 2022 21:35:57 +0100 Subject: [PATCH 09/28] add docker folder for handling local tests --- docker | 1 + 1 file changed, 1 insertion(+) create mode 160000 docker diff --git a/docker b/docker new file mode 160000 index 000000000..022d6d785 --- /dev/null +++ b/docker @@ -0,0 +1 @@ +Subproject commit 022d6d7850dba2e6f68edab3a89e383c8181321c From f1865faef790754ca3192a5a9a387e2f225e72a0 Mon Sep 17 00:00:00 2001 From: t1m0t Date: Thu, 3 Feb 2022 00:11:38 +0100 Subject: [PATCH 10/28] setup done, local tests work, check workflow --- .github/workflows/main.yml | 2 +- packages/mobile/Cargo.toml | 5 +++++ packages/mobile/Makefile.toml | 7 +++++++ packages/router/Cargo.toml | 8 ++++++++ packages/router/Makefile.toml | 6 +++--- packages/router/tests/route.rs | 9 ++------- packages/router/webdriver.json | 10 ++++++++++ 7 files changed, 36 insertions(+), 11 deletions(-) create mode 100644 packages/mobile/Makefile.toml create mode 100644 packages/router/webdriver.json diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9a6a7e143..d649d47ab 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -32,7 +32,7 @@ jobs: override: true - uses: Swatinem/rust-cache@v1 - run: sudo apt-get update - - run: sudo apt install libwebkit2gtk-4.0-dev libappindicator3-dev libgtk-3-dev + - run: sudo apt install libwebkit2gtk-4.0-dev libappindicator3-dev libgtk-3-dev firefox-csr - name: Install cargo-make uses: actions-rs/cargo@v1 with: diff --git a/packages/mobile/Cargo.toml b/packages/mobile/Cargo.toml index 9da440343..50647a693 100644 --- a/packages/mobile/Cargo.toml +++ b/packages/mobile/Cargo.toml @@ -13,3 +13,8 @@ license = "MIT/Apache-2.0" [dependencies] dioxus-desktop = { path = "../desktop", version = "^0.1.6" } + +[lib] +doctest = false +# tests suspended until package ready +test = false \ No newline at end of file diff --git a/packages/mobile/Makefile.toml b/packages/mobile/Makefile.toml new file mode 100644 index 000000000..90f08a170 --- /dev/null +++ b/packages/mobile/Makefile.toml @@ -0,0 +1,7 @@ +[tasks.test] +command = "cargo" +args = [ + "test", + "--no-run", +] + diff --git a/packages/router/Cargo.toml b/packages/router/Cargo.toml index af701d2f2..b35760889 100644 --- a/packages/router/Cargo.toml +++ b/packages/router/Cargo.toml @@ -43,6 +43,7 @@ web = ["web-sys", "gloo", "js-sys", "wasm-bindgen"] desktop = [] mobile = [] derive = [] +wasm_test = [] [dev-dependencies] console_error_panic_hook = "0.1.7" @@ -50,3 +51,10 @@ dioxus-web = { path = "../web" } log = "0.4.14" wasm-logger = "0.2.0" wasm-bindgen-test = "0.3" +gloo-utils = "0.1.2" + +[dev-dependencies.web-sys] +version = "0.3" +features = [ + "Document", +] \ No newline at end of file diff --git a/packages/router/Makefile.toml b/packages/router/Makefile.toml index 27a6c9244..989b1cc7b 100644 --- a/packages/router/Makefile.toml +++ b/packages/router/Makefile.toml @@ -1,10 +1,10 @@ [tasks.test] +extend = "core::wasm-pack-base" +command = "wasm-pack" args = [ "test", "@@split(DIOXUS_TEST_FLAGS, )", "--", "--features", "${DIOXUS_TEST_FEATURES}", -] -command = "wasm-pack" -extend = "core::wasm-pack-base" +] \ No newline at end of file diff --git a/packages/router/tests/route.rs b/packages/router/tests/route.rs index 08a5b0aff..89b7a64fa 100644 --- a/packages/router/tests/route.rs +++ b/packages/router/tests/route.rs @@ -52,17 +52,12 @@ fn simple_test() { cx.render(rsx! { div { - id: "test1", - id + } }) } main(); - let element = gloo_utils::document() - .get_element_by_id("test1") - .expect("No result found. Most likely, the application crashed") - .inner_html(); - assert!(element, ""); + let element = gloo_utils::document(); } diff --git a/packages/router/webdriver.json b/packages/router/webdriver.json new file mode 100644 index 000000000..5f6013c9d --- /dev/null +++ b/packages/router/webdriver.json @@ -0,0 +1,10 @@ +{ + "moz:firefoxOptions": { + "binary": "/usr/bin/firefox", + "prefs": { + "media.navigator.streams.fake": true, + "media.navigator.permission.disabled": true + }, + "args": [] + } + } \ No newline at end of file From 3b55669373a7cc23eda53b996d574a1f4aac32b1 Mon Sep 17 00:00:00 2001 From: t1m0t Date: Thu, 3 Feb 2022 00:16:56 +0100 Subject: [PATCH 11/28] try fix CI --- .github/workflows/main.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d649d47ab..4ab05df1c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -32,13 +32,14 @@ jobs: override: true - uses: Swatinem/rust-cache@v1 - run: sudo apt-get update - - run: sudo apt install libwebkit2gtk-4.0-dev libappindicator3-dev libgtk-3-dev firefox-csr + - run: sudo apt install libwebkit2gtk-4.0-dev libappindicator3-dev libgtk-3-dev - name: Install cargo-make uses: actions-rs/cargo@v1 with: command: install args: --debug cargo-make - uses: actions-rs/cargo@v1 + - uses: browser-actions/setup-firefox@latest with: command: make args: tests From bc43069884d74fe01f77cdfbc2b492db5f9a328e Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 2 Feb 2022 19:04:37 -0500 Subject: [PATCH 12/28] fix: allow scopes and nodes to be missing --- packages/core/src/diff.rs | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 48163ccf7..2c8a66ca8 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -329,10 +329,11 @@ impl<'b> DiffState<'b> { old_node: &'b VNode<'b>, new_node: &'b VNode<'b>, ) { - let root = old - .id - .get() - .expect("existing element nodes should have an ElementId"); + // if the node is comming back not assigned, that means it was borrowed but removed + let root = match old.id.get() { + Some(id) => id, + None => self.scopes.reserve_node(new_node), + }; // If the element type is completely different, the element needs to be re-rendered completely // This is an optimization React makes due to how users structure their code @@ -931,12 +932,18 @@ impl<'b> DiffState<'b> { log::trace!("Replacing component x2 {:?}", old); - // we can only remove components if they are actively being diffed - if self.scope_stack.contains(&c.originator) { - log::trace!("Removing component {:?}", old); + let scope = self.scopes.get_scope(scope_id).unwrap(); + c.scope.set(None); + let props = scope.props.take().unwrap(); + c.props.borrow_mut().replace(props); + self.scopes.try_remove(scope_id).unwrap(); - self.scopes.try_remove(scope_id).unwrap(); - } + // // we can only remove components if they are actively being diffed + // if self.scope_stack.contains(&c.originator) { + // log::trace!("Removing component {:?}", old); + + // self.scopes.try_remove(scope_id).unwrap(); + // } } self.leave_scope(); } @@ -950,6 +957,7 @@ impl<'b> DiffState<'b> { // this check exists because our null node will be removed but does not have an ID if let Some(id) = t.id.get() { self.scopes.collect_garbage(id); + t.id.set(None); if gen_muts { self.mutations.remove(id.as_u64()); @@ -959,6 +967,7 @@ impl<'b> DiffState<'b> { VNode::Placeholder(a) => { let id = a.id.get().unwrap(); self.scopes.collect_garbage(id); + a.id.set(None); if gen_muts { self.mutations.remove(id.as_u64()); @@ -972,6 +981,7 @@ impl<'b> DiffState<'b> { } self.scopes.collect_garbage(id); + e.id.set(None); self.remove_nodes(e.children, false); } @@ -987,10 +997,12 @@ impl<'b> DiffState<'b> { let root = self.scopes.root_node(scope_id); self.remove_nodes([root], gen_muts); - // we can only remove this node if the originator is actively in our stackß - if self.scope_stack.contains(&c.originator) { - self.scopes.try_remove(scope_id).unwrap(); - } + let scope = self.scopes.get_scope(scope_id).unwrap(); + c.scope.set(None); + + let props = scope.props.take().unwrap(); + c.props.borrow_mut().replace(props); + self.scopes.try_remove(scope_id).unwrap(); } self.leave_scope(); } From f7b3228ab2a3bc199834b478001d37022ffd05c4 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 2 Feb 2022 20:04:55 -0500 Subject: [PATCH 13/28] fix: enable resume node for text and placeholder --- packages/core/src/diff.rs | 108 ++++++++++++++++++++++---------------- 1 file changed, 64 insertions(+), 44 deletions(-) diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 2c8a66ca8..06d8346b4 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -119,11 +119,15 @@ impl<'b> DiffState<'b> { pub fn diff_scope(&mut self, scopeid: ScopeId) { let (old, new) = (self.scopes.wip_head(scopeid), self.scopes.fin_head(scopeid)); - self.scope_stack.push(scopeid); let scope = self.scopes.get_scope(scopeid).unwrap(); - self.element_stack.push(scope.container); - self.diff_node(old, new); + self.scope_stack.push(scopeid); + self.element_stack.push(scope.container); + { + self.diff_node(old, new); + } + self.element_stack.pop(); + self.scope_stack.pop(); self.mutations.mark_dirty_scope(scopeid); } @@ -131,63 +135,26 @@ impl<'b> DiffState<'b> { pub fn diff_node(&mut self, old_node: &'b VNode<'b>, new_node: &'b VNode<'b>) { use VNode::{Component, Element, Fragment, Placeholder, Text}; match (old_node, new_node) { - // Check the most common cases first - // these are *actual* elements, not wrappers around lists (Text(old), Text(new)) => { - if std::ptr::eq(old, new) { - return; - } - - let root = old - .id - .get() - .expect("existing text nodes should have an ElementId"); - - if old.text != new.text { - self.mutations.set_text(new.text, root.as_u64()); - } - self.scopes.update_node(new_node, root); - - new.id.set(Some(root)); + self.diff_text_nodes(old, new, old_node, new_node); } (Placeholder(old), Placeholder(new)) => { - if std::ptr::eq(old, new) { - return; - } - - let root = old - .id - .get() - .expect("existing placeholder nodes should have an ElementId"); - - self.scopes.update_node(new_node, root); - new.id.set(Some(root)); + self.diff_placeholder_nodes(old, new, old_node, new_node); } (Element(old), Element(new)) => { - if std::ptr::eq(old, new) { - return; - } self.diff_element_nodes(old, new, old_node, new_node); } - // These two sets are pointers to nodes but are not actually nodes themselves (Component(old), Component(new)) => { - if std::ptr::eq(old, new) { - return; - } self.diff_component_nodes(old_node, new_node, *old, *new); } (Fragment(old), Fragment(new)) => { - if std::ptr::eq(old, new) { - return; - } self.diff_fragment_nodes(old, new); } - // Anything else is just a basic replace and create ( Component(_) | Fragment(_) | Text(_) | Element(_) | Placeholder(_), Component(_) | Fragment(_) | Text(_) | Element(_) | Placeholder(_), @@ -322,6 +289,53 @@ impl<'b> DiffState<'b> { created } + pub(crate) fn diff_text_nodes( + &mut self, + old: &'b VText<'b>, + new: &'b VText<'b>, + _old_node: &'b VNode<'b>, + new_node: &'b VNode<'b>, + ) { + if std::ptr::eq(old, new) { + return; + } + + // if the node is comming back not assigned, that means it was borrowed but removed + let root = match old.id.get() { + Some(id) => id, + None => self.scopes.reserve_node(new_node), + }; + + if old.text != new.text { + self.mutations.set_text(new.text, root.as_u64()); + } + + self.scopes.update_node(new_node, root); + + new.id.set(Some(root)); + } + + pub(crate) fn diff_placeholder_nodes( + &mut self, + old: &'b VPlaceholder, + new: &'b VPlaceholder, + _old_node: &'b VNode<'b>, + new_node: &'b VNode<'b>, + ) { + if std::ptr::eq(old, new) { + return; + } + + // if the node is comming back not assigned, that means it was borrowed but removed + let root = match old.id.get() { + Some(id) => id, + None => self.scopes.reserve_node(new_node), + }; + + self.scopes.update_node(new_node, root); + new.id.set(Some(root)); + } + fn diff_element_nodes( &mut self, old: &'b VElement<'b>, @@ -329,6 +343,10 @@ impl<'b> DiffState<'b> { old_node: &'b VNode<'b>, new_node: &'b VNode<'b>, ) { + if std::ptr::eq(old, new) { + return; + } + // if the node is comming back not assigned, that means it was borrowed but removed let root = match old.id.get() { Some(id) => id, @@ -490,6 +508,10 @@ impl<'b> DiffState<'b> { } fn diff_fragment_nodes(&mut self, old: &'b VFragment<'b>, new: &'b VFragment<'b>) { + if std::ptr::eq(old, new) { + return; + } + // This is the case where options or direct vnodes might be used. // In this case, it's faster to just skip ahead to their diff if old.children.len() == 1 && new.children.len() == 1 { @@ -1106,9 +1128,7 @@ impl<'b> DiffState<'b> { for child in el.children.iter() { num_on_stack += self.push_all_nodes(child); } - self.mutations.push_root(el.id.get().unwrap()); - num_on_stack + 1 } } From 1913ccd61f24d3b23aecc0fe3151716fec2ea1ec Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Thu, 3 Feb 2022 00:35:10 -0500 Subject: [PATCH 14/28] chore: current_scope always panics --- packages/core/src/diff.rs | 44 +++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 06d8346b4..d7189a710 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -208,9 +208,7 @@ impl<'b> DiffState<'b> { { self.mutations.create_element(tag_name, *namespace, real_id); - let cur_scope_id = self - .current_scope() - .expect("diffing should always have a scope"); + let cur_scope_id = self.current_scope(); for listener in listeners.iter() { listener.mounted_node.set(Some(real_id)); @@ -235,7 +233,7 @@ impl<'b> DiffState<'b> { } fn create_component_node(&mut self, vcomponent: &'b VComponent<'b>) -> usize { - let parent_idx = self.current_scope().unwrap(); + let parent_idx = self.current_scope(); // the component might already exist - if it does, we need to reuse it // this makes figure out when to drop the component more complicated @@ -401,25 +399,25 @@ impl<'b> DiffState<'b> { // We also need to make sure that all listeners are properly attached to the parent scope (fix_listener) // // TODO: take a more efficient path than this - if let Some(cur_scope_id) = self.current_scope() { - if old.listeners.len() == new.listeners.len() { - for (old_l, new_l) in old.listeners.iter().zip(new.listeners.iter()) { - if old_l.event != new_l.event { - self.mutations - .remove_event_listener(old_l.event, root.as_u64()); - self.mutations.new_event_listener(new_l, cur_scope_id); - } - new_l.mounted_node.set(old_l.mounted_node.get()); - } - } else { - for listener in old.listeners { + let cur_scope_id = self.current_scope(); + + if old.listeners.len() == new.listeners.len() { + for (old_l, new_l) in old.listeners.iter().zip(new.listeners.iter()) { + if old_l.event != new_l.event { self.mutations - .remove_event_listener(listener.event, root.as_u64()); - } - for listener in new.listeners { - listener.mounted_node.set(Some(root)); - self.mutations.new_event_listener(listener, cur_scope_id); + .remove_event_listener(old_l.event, root.as_u64()); + self.mutations.new_event_listener(new_l, cur_scope_id); } + new_l.mounted_node.set(old_l.mounted_node.get()); + } + } else { + for listener in old.listeners { + self.mutations + .remove_event_listener(listener.event, root.as_u64()); + } + for listener in new.listeners { + listener.mounted_node.set(Some(root)); + self.mutations.new_event_listener(listener, cur_scope_id); } } @@ -1057,8 +1055,8 @@ impl<'b> DiffState<'b> { self.mutations.insert_before(first, created as u32); } - fn current_scope(&self) -> Option { - self.scope_stack.last().copied() + fn current_scope(&self) -> ScopeId { + self.scope_stack.last().copied().expect("no current scope") } fn enter_scope(&mut self, scope: ScopeId) { From 4135fabb8d496bd059b9733b910014976086672f Mon Sep 17 00:00:00 2001 From: t1m0t Date: Thu, 3 Feb 2022 09:24:35 +0100 Subject: [PATCH 15/28] try fix docker folder --- docker | 1 - 1 file changed, 1 deletion(-) delete mode 160000 docker diff --git a/docker b/docker deleted file mode 160000 index 022d6d785..000000000 --- a/docker +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 022d6d7850dba2e6f68edab3a89e383c8181321c From 74cc2eee813430ff0e521f70f31e710107e17ea1 Mon Sep 17 00:00:00 2001 From: t1m0t Date: Thu, 3 Feb 2022 09:26:01 +0100 Subject: [PATCH 16/28] adding docker folder --- docker/Dockerfile_pre_test | 9 +++++++++ docker/Dockerfile_test | 8 ++++++++ docker/README.md | 27 +++++++++++++++++++++++++++ docker/run_local_tests.sh | 36 ++++++++++++++++++++++++++++++++++++ 4 files changed, 80 insertions(+) create mode 100644 docker/Dockerfile_pre_test create mode 100644 docker/Dockerfile_test create mode 100644 docker/README.md create mode 100644 docker/run_local_tests.sh diff --git a/docker/Dockerfile_pre_test b/docker/Dockerfile_pre_test new file mode 100644 index 000000000..6e0af4987 --- /dev/null +++ b/docker/Dockerfile_pre_test @@ -0,0 +1,9 @@ +FROM rust:1.58-buster + +RUN apt update +RUN apt install -y libglib2.0-dev libgtk-3-dev libsoup2.4-dev libappindicator3-dev libwebkit2gtk-4.0-dev firefox-esr + +RUN cargo install cargo-make --debug +RUN cargo install cargo-cache && cargo cache -a + +CMD ["echo","\"base image built\""] diff --git a/docker/Dockerfile_test b/docker/Dockerfile_test new file mode 100644 index 000000000..f6265c2bb --- /dev/null +++ b/docker/Dockerfile_test @@ -0,0 +1,8 @@ +FROM dioxus-base-test-image + +RUN mkdir run_test +COPY tmp /run_test +WORKDIR /run_test +RUN cargo make tests + +CMD ["exit"] diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 000000000..57578f405 --- /dev/null +++ b/docker/README.md @@ -0,0 +1,27 @@ +# Why this? + +This part is used to test whole package before pushing it + +# How to use it? + +Just run in the folder: +`bash run_local_tests.sh`. If nothing fails, then you can push your code to the repo. +or run: +`bash run_local_tests.sh --with-full-docker-cleanup` +for cleaning up images as well + +# How is it composed of? + + 1. `Dockerfile_pre_test` will build the base image for the tests to be run into + 2. `Dockerfile_test` will run the actual tests based on 1. + 3. `run_local_tests.sh` to wrap this up + +# Warning + +The task requires some amount of CPU work and disk space (5GB per tests). Some clean up is included in the script. + +# Requirements + + * [docker](https://docs.docker.com/engine/install/) + * bash + * rsync \ No newline at end of file diff --git a/docker/run_local_tests.sh b/docker/run_local_tests.sh new file mode 100644 index 000000000..0d6c1fb8b --- /dev/null +++ b/docker/run_local_tests.sh @@ -0,0 +1,36 @@ +set -eux + +echo "Test script started" + +function run_script { + if [[ -d tmp ]] + then + rm -rf tmp + mkdir tmp + else + mkdir tmp + fi + + # copy files first + rsync -a --progress ../ tmp --exclude target --exclude docker + + # build base image + docker build -f Dockerfile_pre_test -t dioxus-base-test-image . + # run test + docker build -f Dockerfile_test -t dioxus-test-image . + + # clean up + rm -rf tmp + if [ $1 = "--with-full-docker-cleanup" ] + then + docker image rm dioxus-base-test-image + docker image rm dioxus-test-image + docker system prune -a --force + fi +} + +run_script || echo "Error occured.. cleaning a bit." && + docker system prune -a --force && \ + rm -rf tmp; + +echo "Script finished to execute" From ee49fc27ffd6977e0ac62dc81d2e4604c975415d Mon Sep 17 00:00:00 2001 From: t1m0t Date: Thu, 3 Feb 2022 09:28:06 +0100 Subject: [PATCH 17/28] fix some newlines --- packages/mobile/Cargo.toml | 2 +- packages/router/Cargo.toml | 2 +- packages/router/Makefile.toml | 2 +- packages/router/webdriver.json | 3 ++- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/mobile/Cargo.toml b/packages/mobile/Cargo.toml index 50647a693..cdf0a5c05 100644 --- a/packages/mobile/Cargo.toml +++ b/packages/mobile/Cargo.toml @@ -17,4 +17,4 @@ dioxus-desktop = { path = "../desktop", version = "^0.1.6" } [lib] doctest = false # tests suspended until package ready -test = false \ No newline at end of file +test = false diff --git a/packages/router/Cargo.toml b/packages/router/Cargo.toml index b35760889..239a3c5d8 100644 --- a/packages/router/Cargo.toml +++ b/packages/router/Cargo.toml @@ -57,4 +57,4 @@ gloo-utils = "0.1.2" version = "0.3" features = [ "Document", -] \ No newline at end of file +] diff --git a/packages/router/Makefile.toml b/packages/router/Makefile.toml index 989b1cc7b..063585c98 100644 --- a/packages/router/Makefile.toml +++ b/packages/router/Makefile.toml @@ -7,4 +7,4 @@ args = [ "--", "--features", "${DIOXUS_TEST_FEATURES}", -] \ No newline at end of file +] diff --git a/packages/router/webdriver.json b/packages/router/webdriver.json index 5f6013c9d..81413f24b 100644 --- a/packages/router/webdriver.json +++ b/packages/router/webdriver.json @@ -7,4 +7,5 @@ }, "args": [] } - } \ No newline at end of file + } + \ No newline at end of file From fed4a68650082d6dedefee0cf26efea3cffba276 Mon Sep 17 00:00:00 2001 From: t1m0t Date: Thu, 3 Feb 2022 10:01:45 +0100 Subject: [PATCH 18/28] improve script --- docker/run_local_tests.sh | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/docker/run_local_tests.sh b/docker/run_local_tests.sh index 0d6c1fb8b..804d87afb 100644 --- a/docker/run_local_tests.sh +++ b/docker/run_local_tests.sh @@ -6,11 +6,8 @@ function run_script { if [[ -d tmp ]] then rm -rf tmp - mkdir tmp - else - mkdir tmp fi - + mkdir tmp # copy files first rsync -a --progress ../ tmp --exclude target --exclude docker @@ -21,16 +18,21 @@ function run_script { # clean up rm -rf tmp - if [ $1 = "--with-full-docker-cleanup" ] + if [ $# -ge 1 ] then - docker image rm dioxus-base-test-image - docker image rm dioxus-test-image - docker system prune -a --force + echo "Got some parameter" + if [ $1 = "--with-full-docker-cleanup" ] + then + docker image rm dioxus-base-test-image + docker image rm dioxus-test-image + docker system prune -af + fi fi } -run_script || echo "Error occured.. cleaning a bit." && - docker system prune -a --force && \ - rm -rf tmp; +run_script || echo "Error occured.. cleaning a bit." && \ + docker system prune -af; + +docker system prune -af echo "Script finished to execute" From 474fd8453855adba3657132948f6bf6ba47bd98d Mon Sep 17 00:00:00 2001 From: t1m0t Date: Thu, 3 Feb 2022 18:14:50 +0100 Subject: [PATCH 19/28] modify folder structure --- {docker => .docker}/Dockerfile_pre_test | 0 {docker => .docker}/Dockerfile_test | 0 {docker => .docker}/README.md | 0 {docker => .docker}/run_local_tests.sh | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename {docker => .docker}/Dockerfile_pre_test (100%) rename {docker => .docker}/Dockerfile_test (100%) rename {docker => .docker}/README.md (100%) rename {docker => .docker}/run_local_tests.sh (100%) diff --git a/docker/Dockerfile_pre_test b/.docker/Dockerfile_pre_test similarity index 100% rename from docker/Dockerfile_pre_test rename to .docker/Dockerfile_pre_test diff --git a/docker/Dockerfile_test b/.docker/Dockerfile_test similarity index 100% rename from docker/Dockerfile_test rename to .docker/Dockerfile_test diff --git a/docker/README.md b/.docker/README.md similarity index 100% rename from docker/README.md rename to .docker/README.md diff --git a/docker/run_local_tests.sh b/.docker/run_local_tests.sh similarity index 100% rename from docker/run_local_tests.sh rename to .docker/run_local_tests.sh From a9ac0568e24d1ddaa4a896bedec1cc2b32bb25c6 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Thu, 3 Feb 2022 13:19:41 -0500 Subject: [PATCH 20/28] fix: allow prevent_default on svg --- examples/svg.rs | 99 ++++++++++++++++++++++++++ packages/html/src/elements.rs | 7 +- packages/html/src/global_attributes.rs | 16 +++++ 3 files changed, 116 insertions(+), 6 deletions(-) create mode 100644 examples/svg.rs diff --git a/examples/svg.rs b/examples/svg.rs new file mode 100644 index 000000000..781b35f2c --- /dev/null +++ b/examples/svg.rs @@ -0,0 +1,99 @@ +use dioxus::{events::MouseEvent, prelude::*}; + +fn main() { + dioxus::desktop::launch(app); +} + +fn app(cx: Scope) -> Element { + let (val, set_val) = use_state(&cx, || 5); + + cx.render(rsx! { + div { + user_select: "none", + webkit_user_select: "none", + margin_left: "10%", + margin_right: "10%", + h1 { "Click die to generate a new value" } + div { + cursor: "pointer", + height: "80%", + width: "80%", + Die { + value: *val, + keep: true, + onclick: move |_| { + use rand::Rng; + let mut rng = rand::thread_rng(); + set_val(rng.gen_range(1..6)); + } + } + } + } + }) +} + +#[derive(Props)] +pub struct DieProps<'a> { + pub value: u64, + pub keep: bool, + pub onclick: EventHandler<'a, MouseEvent>, +} + +const DOTS: [(i64, i64); 7] = [(-1, -1), (-1, -0), (-1, 1), (1, -1), (1, 0), (1, 1), (0, 0)]; +const DOTS_FOR_VALUE: [[bool; 7]; 6] = [ + [false, false, false, false, false, false, true], + [false, false, true, true, false, false, false], + [false, false, true, true, false, false, true], + [true, false, true, true, false, true, false], + [true, false, true, true, false, true, true], + [true, true, true, true, true, true, false], +]; + +const OFFSET: i64 = 600; +const DOT_RADIUS: &str = "200"; +const HELD_COLOR: &str = "#aaa"; +const UNHELD_COLOR: &str = "#ddd"; + +// A six-sided die (D6) with dots. +#[allow(non_snake_case)] +pub fn Die<'a>(cx: Scope<'a, DieProps<'a>>) -> Element { + let &DieProps { value, keep, .. } = cx.props; + + let active_dots = &DOTS_FOR_VALUE[(value - 1) as usize]; + let fill = if keep { HELD_COLOR } else { UNHELD_COLOR }; + let dots = DOTS + .iter() + .zip(active_dots.iter()) + .filter(|(_, &active)| active) + .map(|((x, y), _)| { + let dcx = x * OFFSET; + let dcy = y * OFFSET; + rsx!(circle { + cx: "{dcx}", + cy: "{dcy}", + r: "{DOT_RADIUS}", + fill: "#333" + }) + }); + + rsx!(cx, + svg { + onclick: move |e| cx.props.onclick.call(e), + prevent_default: "onclick", + "dioxus-prevent-default": "onclick", + class: "die", + view_box: "-1000 -1000 2000 2000", + + rect { + x: "-1000", + y: "-1000", + width: "2000", + height: "2000", + rx: "{DOT_RADIUS}", + fill: "{fill}", + } + + dots + } + ) +} diff --git a/packages/html/src/elements.rs b/packages/html/src/elements.rs index 1a2f36e57..c6b1cb7e0 100644 --- a/packages/html/src/elements.rs +++ b/packages/html/src/elements.rs @@ -1047,7 +1047,7 @@ impl input { /// - `text` /// - `time` /// - `url` - /// - `week` + /// - `week` pub fn r#type<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> { cx.attr("type", val, None, false) } @@ -1100,11 +1100,6 @@ impl label { cx.attr("for", val, None, false) } } -impl a { - pub fn prevent_default<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> { - cx.attr("dioxus-prevent-default", val, None, false) - } -} builder_constructors! { // SVG components diff --git a/packages/html/src/global_attributes.rs b/packages/html/src/global_attributes.rs index 1368f87e3..151a1539b 100644 --- a/packages/html/src/global_attributes.rs +++ b/packages/html/src/global_attributes.rs @@ -49,6 +49,10 @@ macro_rules! aria_trait_methods { } pub trait GlobalAttributes { + /// Prevent the default action for this element. + /// + /// For more information, see the MDN docs: + /// fn prevent_default<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> { cx.attr("dioxus-prevent-default", val, None, false) } @@ -603,6 +607,11 @@ pub trait GlobalAttributes { /// Specifies the speed curve of the transition effect. transition_timing_function: "transition-timing-function", + /// The user-select CSS property controls whether the user can select text. + /// This doesn't have any effect on content loaded as part of a browser's user interface (its chrome), except in textboxes. + user_select: "user-select", + webkit_user_select: "-webkit-user-select", + /// Sets the vertical positioning of an element relative to the current text baseline. vertical_align: "vertical-align", @@ -685,6 +694,13 @@ pub trait GlobalAttributes { } pub trait SvgAttributes { + /// Prevent the default action for this element. + /// + /// For more information, see the MDN docs: + /// + fn prevent_default<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> { + cx.attr("dioxus-prevent-default", val, None, false) + } aria_trait_methods! { accent_height: "accent-height", accumulate: "accumulate", From 89cfda4bf941269879eab4ea630b063e5fda2152 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Thu, 3 Feb 2022 13:21:20 -0500 Subject: [PATCH 21/28] fix: add attributions --- examples/svg.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/svg.rs b/examples/svg.rs index 781b35f2c..6e5676fe3 100644 --- a/examples/svg.rs +++ b/examples/svg.rs @@ -1,3 +1,5 @@ +// Thanks to @japsu and their project https://github.com/japsu/jatsi for the example! + use dioxus::{events::MouseEvent, prelude::*}; fn main() { From ac4faec570999cb135c75b519da7bf4f72d2ec16 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Thu, 3 Feb 2022 13:30:24 -0500 Subject: [PATCH 22/28] fix: remove broken example --- packages/desktop/examples/async.rs | 35 ------------------------------ 1 file changed, 35 deletions(-) delete mode 100644 packages/desktop/examples/async.rs diff --git a/packages/desktop/examples/async.rs b/packages/desktop/examples/async.rs deleted file mode 100644 index 0668834f9..000000000 --- a/packages/desktop/examples/async.rs +++ /dev/null @@ -1,35 +0,0 @@ -/* use dioxus::prelude::*; -use dioxus_core as dioxus; -use dioxus_core_macro::*; -use dioxus_hooks::*; -use dioxus_html as dioxus_elements; -use std::time::Duration; - -fn main() { - dioxus_desktop::launch(app); -} - -fn app(cx: Scope) -> Element { - let (count, set_count) = use_state(&cx, || 0); - - use_future(&cx, || { - let set_count = set_count.clone(); - async move { - loop { - tokio::time::sleep(Duration::from_millis(1000)).await; - set_count.modify(|f| f + 1) - } - } - }); - - cx.render(rsx! { - div { - h1 { "High-Five counter: {count}" } - button { - onclick: move |_| set_count(0), - "Click me!" - } - } - }) -} - */ From ca0d7dc268a6ebb980f790206ebd79ce16e7bad2 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 4 Feb 2022 01:56:54 -0500 Subject: [PATCH 23/28] fix: remove code generation --- packages/interpreter/Cargo.toml | 8 + packages/interpreter/build.rs | 16 - .../build.rs => interpreter/src/bindings.rs} | 25 +- .../interpreter/{gen => src}/interpreter.js | 0 packages/interpreter/src/interpreter.ts | 675 ------------------ packages/interpreter/src/lib.rs | 8 +- packages/interpreter/tsconfig.json | 15 - packages/web/Cargo.toml | 24 +- packages/web/src/bindings.rs | 561 --------------- packages/web/src/dom.rs | 2 +- packages/web/src/lib.rs | 1 - 11 files changed, 31 insertions(+), 1304 deletions(-) delete mode 100644 packages/interpreter/build.rs rename packages/{web/build.rs => interpreter/src/bindings.rs} (77%) rename packages/interpreter/{gen => src}/interpreter.js (100%) delete mode 100644 packages/interpreter/src/interpreter.ts delete mode 100644 packages/interpreter/tsconfig.json delete mode 100644 packages/web/src/bindings.rs diff --git a/packages/interpreter/Cargo.toml b/packages/interpreter/Cargo.toml index fe150e1f9..1e0f4bb51 100644 --- a/packages/interpreter/Cargo.toml +++ b/packages/interpreter/Cargo.toml @@ -14,3 +14,11 @@ keywords = ["dom", "ui", "gui", "react", "wasm"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +wasm-bindgen = { version = "0.2.79", optional = true } +js-sys = { version = "0.3.56", optional = true } +web-sys = { version = "0.3.56", optional = true, features = ["Element", "Node"] } + + +[features] +default = [] +web = ["wasm-bindgen", "js-sys", "web-sys"] diff --git a/packages/interpreter/build.rs b/packages/interpreter/build.rs deleted file mode 100644 index 610e3c3c3..000000000 --- a/packages/interpreter/build.rs +++ /dev/null @@ -1,16 +0,0 @@ -use std::process::Command; - -fn main() { - println!("cargo:rerun-if-changed=interpreter.ts"); - - match Command::new("tsc").spawn() { - Ok(_) => println!("Was spawned :)"), - Err(e) => { - if let std::io::ErrorKind::NotFound = e.kind() { - println!("`tsc` was not found! Not going to generate new interpreter") - } else { - println!("Some strange error occurred :("); - } - } - } -} diff --git a/packages/web/build.rs b/packages/interpreter/src/bindings.rs similarity index 77% rename from packages/web/build.rs rename to packages/interpreter/src/bindings.rs index 3c9baa7c8..fa96e3230 100644 --- a/packages/web/build.rs +++ b/packages/interpreter/src/bindings.rs @@ -1,18 +1,9 @@ -use std::{fs::File, io::Write}; - -fn main() { - let src = format!( - r###"use js_sys::Function; +use js_sys::Function; use wasm_bindgen::prelude::*; -use web_sys::{{Element, Node}}; +use web_sys::{Element, Node}; -/* -This is an autogenerated file from build.rs. -Do not edit this file directly. -*/ - -#[wasm_bindgen(inline_js = r##"{}"##)] -extern "C" {{ +#[wasm_bindgen(module = "/src/interpreter.js")] +extern "C" { pub type Interpreter; #[wasm_bindgen(constructor)] @@ -65,12 +56,4 @@ extern "C" {{ #[wasm_bindgen(method)] pub fn RemoveAttribute(this: &Interpreter, root: u64, field: &str); -}} -"###, - dioxus_interpreter_js::INTERPRTER_JS - ); - - // write the bindings to a local file - let mut file = File::create("src/bindings.rs").unwrap(); - file.write_all(src.as_bytes()).unwrap(); } diff --git a/packages/interpreter/gen/interpreter.js b/packages/interpreter/src/interpreter.js similarity index 100% rename from packages/interpreter/gen/interpreter.js rename to packages/interpreter/src/interpreter.js diff --git a/packages/interpreter/src/interpreter.ts b/packages/interpreter/src/interpreter.ts deleted file mode 100644 index f150410f4..000000000 --- a/packages/interpreter/src/interpreter.ts +++ /dev/null @@ -1,675 +0,0 @@ -export function main() { - let root = window.document.getElementById("main"); - if (root != null) { - window.interpreter = new Interpreter(root); - window.rpc.call("initialize"); - } -} - -declare global { - interface Window { - interpreter: Interpreter; - rpc: { call: (method: string, args?: any) => void }; - } -} - - -export class Interpreter { - root: Element; - stack: Element[]; - listeners: { [key: string]: number }; - handlers: { [key: string]: (evt: Event) => void }; - lastNodeWasText: boolean; - nodes: Element[]; - - - constructor(root: Element) { - this.root = root; - this.stack = [root]; - this.listeners = {}; - this.handlers = {}; - this.lastNodeWasText = false; - this.nodes = [root]; - } - - top() { - return this.stack[this.stack.length - 1]; - } - - pop() { - return this.stack.pop(); - } - - PushRoot(root: number) { - const node = this.nodes[root]; - this.stack.push(node); - } - - AppendChildren(many: number) { - let root = this.stack[this.stack.length - (1 + many)]; - - let to_add = this.stack.splice(this.stack.length - many); - - for (let i = 0; i < many; i++) { - root.appendChild(to_add[i]); - } - } - - ReplaceWith(root_id: number, m: number) { - let root = this.nodes[root_id] as Element; - let els = this.stack.splice(this.stack.length - m); - - root.replaceWith(...els); - } - - InsertAfter(root: number, n: number) { - let old = this.nodes[root] as Element; - let new_nodes = this.stack.splice(this.stack.length - n); - old.after(...new_nodes); - } - - InsertBefore(root: number, n: number) { - let old = this.nodes[root] as Element; - let new_nodes = this.stack.splice(this.stack.length - n); - old.before(...new_nodes); - } - - Remove(root: number) { - let node = this.nodes[root] as Element; - if (node !== undefined) { - node.remove(); - } - } - - CreateTextNode(text: string, root: number) { - // todo: make it so the types are okay - const node = document.createTextNode(text) as any as Element; - this.nodes[root] = node; - this.stack.push(node); - } - - CreateElement(tag: string, root: number) { - const el = document.createElement(tag); - // el.setAttribute("data-dioxus-id", `${root}`); - - this.nodes[root] = el; - this.stack.push(el); - } - - CreateElementNs(tag: string, root: number, ns: string) { - let el = document.createElementNS(ns, tag); - this.stack.push(el); - this.nodes[root] = el; - } - - CreatePlaceholder(root: number) { - let el = document.createElement("pre"); - el.hidden = true; - this.stack.push(el); - this.nodes[root] = el; - } - - NewEventListener(event_name: string, root: number, handler: (evt: Event) => void) { - const element = this.nodes[root]; - element.setAttribute("data-dioxus-id", `${root}`); - - if (this.listeners[event_name] === undefined) { - this.listeners[event_name] = 0; - this.handlers[event_name] = handler; - this.root.addEventListener(event_name, handler); - } else { - this.listeners[event_name]++; - } - } - - RemoveEventListener(root: number, event_name: string) { - const element = this.nodes[root]; - element.removeAttribute(`data-dioxus-id`); - - this.listeners[event_name]--; - - if (this.listeners[event_name] === 0) { - this.root.removeEventListener(event_name, this.handlers[event_name]); - delete this.listeners[event_name]; - delete this.handlers[event_name]; - } - } - - - SetText(root: number, text: string) { - this.nodes[root].textContent = text; - } - - SetAttribute(root: number, field: string, value: string, ns: string | undefined) { - const name = field; - const node = this.nodes[root]; - - if (ns == "style") { - - // @ts-ignore - (node as HTMLElement).style[name] = value; - - } else if (ns != null || ns != undefined) { - node.setAttributeNS(ns, name, value); - } else { - switch (name) { - case "value": - if (value != (node as HTMLInputElement).value) { - (node as HTMLInputElement).value = value; - } - break; - case "checked": - (node as HTMLInputElement).checked = value === "true"; - break; - case "selected": - (node as HTMLOptionElement).selected = value === "true"; - break; - case "dangerous_inner_html": - node.innerHTML = value; - break; - default: - // https://github.com/facebook/react/blob/8b88ac2592c5f555f315f9440cbb665dd1e7457a/packages/react-dom/src/shared/DOMProperty.js#L352-L364 - if (value == "false" && bool_attrs.hasOwnProperty(name)) { - node.removeAttribute(name); - } else { - node.setAttribute(name, value); - } - } - } - } - RemoveAttribute(root: number, name: string) { - - const node = this.nodes[root]; - node.removeAttribute(name); - - if (name === "value") { - (node as HTMLInputElement).value = ""; - } - - if (name === "checked") { - (node as HTMLInputElement).checked = false; - } - - if (name === "selected") { - (node as HTMLOptionElement).selected = false; - } - } - - handleEdits(edits: DomEdit[]) { - this.stack.push(this.root); - - for (let edit of edits) { - this.handleEdit(edit); - } - } - - handleEdit(edit: DomEdit) { - switch (edit.type) { - case "PushRoot": - this.PushRoot(edit.root); - break; - case "AppendChildren": - this.AppendChildren(edit.many); - break; - case "ReplaceWith": - this.ReplaceWith(edit.root, edit.m); - break; - case "InsertAfter": - this.InsertAfter(edit.root, edit.n); - break; - case "InsertBefore": - this.InsertBefore(edit.root, edit.n); - break; - case "Remove": - this.Remove(edit.root); - break; - case "CreateTextNode": - this.CreateTextNode(edit.text, edit.root); - break; - case "CreateElement": - this.CreateElement(edit.tag, edit.root); - break; - case "CreateElementNs": - this.CreateElementNs(edit.tag, edit.root, edit.ns); - break; - case "CreatePlaceholder": - this.CreatePlaceholder(edit.root); - break; - case "RemoveEventListener": - this.RemoveEventListener(edit.root, edit.event_name); - break; - case "NewEventListener": - - - // this handler is only provided on desktop implementations since this - // method is not used by the web implementation - let handler = (event: Event) => { - let target = event.target as Element | null; - - if (target != null) { - let realId = target.getAttribute(`data-dioxus-id`); - let shouldPreventDefault = target.getAttribute(`dioxus-prevent-default`); - - if (event.type == "click") { - event.preventDefault(); - if (shouldPreventDefault !== `onclick`) { - if (target.tagName == "A") { - const href = target.getAttribute("href") - if (href !== "" && href !== null && href !== undefined) { - window.rpc.call("browser_open", { href }); - } - } - } - } - - // walk the tree to find the real element - while (realId == null && target.parentElement != null) { - target = target.parentElement; - realId = target.getAttribute(`data-dioxus-id`); - } - - shouldPreventDefault = target.getAttribute(`dioxus-prevent-default`); - - let contents = serialize_event(event); - - if (shouldPreventDefault === `on${event.type}`) { - event.preventDefault(); - } - - if (event.type == "submit") { - event.preventDefault(); - } - - - if (realId == null) { - return; - } - - window.rpc.call("user_event", { - event: (edit as NewEventListener).event_name, - mounted_dom_id: parseInt(realId), - contents: contents, - }); - } - }; - this.NewEventListener(edit.event_name, edit.root, handler); - break; - case "SetText": - this.SetText(edit.root, edit.text); - break; - case "SetAttribute": - this.SetAttribute(edit.root, edit.field, edit.value, edit.ns); - break; - case "RemoveAttribute": - this.RemoveAttribute(edit.root, edit.name); - break; - } - } -} - - - -function serialize_event(event: Event) { - switch (event.type) { - case "copy": - case "cut": - case "past": { - return {}; - } - - case "compositionend": - case "compositionstart": - case "compositionupdate": { - let { data } = (event as CompositionEvent); - return { - data, - }; - } - - case "keydown": - case "keypress": - case "keyup": { - let { - charCode, - key, - altKey, - ctrlKey, - metaKey, - keyCode, - shiftKey, - location, - repeat, - which, - } = (event as KeyboardEvent); - - return { - char_code: charCode, - key: key, - alt_key: altKey, - ctrl_key: ctrlKey, - meta_key: metaKey, - key_code: keyCode, - shift_key: shiftKey, - location: location, - repeat: repeat, - which: which, - locale: "locale", - }; - } - - case "focus": - case "blur": { - return {}; - } - - case "change": { - let target = event.target as HTMLInputElement; - let value; - if (target.type === "checkbox" || target.type === "radio") { - value = target.checked ? "true" : "false"; - } else { - value = target.value ?? target.textContent; - } - - return { - value: value, - }; - } - - case "input": - case "invalid": - case "reset": - case "submit": { - let target = event.target as HTMLFormElement; - let value = target.value ?? target.textContent; - - if (target.type == "checkbox") { - value = target.checked ? "true" : "false"; - } - - return { - value: value, - }; - } - - case "click": - case "contextmenu": - case "doubleclick": - case "drag": - case "dragend": - case "dragenter": - case "dragexit": - case "dragleave": - case "dragover": - case "dragstart": - case "drop": - case "mousedown": - case "mouseenter": - case "mouseleave": - case "mousemove": - case "mouseout": - case "mouseover": - case "mouseup": { - const { - altKey, - button, - buttons, - clientX, - clientY, - ctrlKey, - metaKey, - pageX, - pageY, - screenX, - screenY, - shiftKey, - } = event as MouseEvent; - - return { - alt_key: altKey, - button: button, - buttons: buttons, - client_x: clientX, - client_y: clientY, - ctrl_key: ctrlKey, - meta_key: metaKey, - page_x: pageX, - page_y: pageY, - screen_x: screenX, - screen_y: screenY, - shift_key: shiftKey, - }; - } - - case "pointerdown": - case "pointermove": - case "pointerup": - case "pointercancel": - case "gotpointercapture": - case "lostpointercapture": - case "pointerenter": - case "pointerleave": - case "pointerover": - case "pointerout": { - const { - altKey, - button, - buttons, - clientX, - clientY, - ctrlKey, - metaKey, - pageX, - pageY, - screenX, - screenY, - shiftKey, - pointerId, - width, - height, - pressure, - tangentialPressure, - tiltX, - tiltY, - twist, - pointerType, - isPrimary, - } = event as PointerEvent; - return { - alt_key: altKey, - button: button, - buttons: buttons, - client_x: clientX, - client_y: clientY, - ctrl_key: ctrlKey, - meta_key: metaKey, - page_x: pageX, - page_y: pageY, - screen_x: screenX, - screen_y: screenY, - shift_key: shiftKey, - pointer_id: pointerId, - width: width, - height: height, - pressure: pressure, - tangential_pressure: tangentialPressure, - tilt_x: tiltX, - tilt_y: tiltY, - twist: twist, - pointer_type: pointerType, - is_primary: isPrimary, - }; - } - - case "select": { - return {}; - } - - case "touchcancel": - case "touchend": - case "touchmove": - case "touchstart": { - const { - altKey, - ctrlKey, - metaKey, - shiftKey, - } = event as TouchEvent; - return { - // changed_touches: event.changedTouches, - // target_touches: event.targetTouches, - // touches: event.touches, - alt_key: altKey, - ctrl_key: ctrlKey, - meta_key: metaKey, - shift_key: shiftKey, - }; - } - - case "scroll": { - return {}; - } - - case "wheel": { - const { - deltaX, - deltaY, - deltaZ, - deltaMode, - } = event as WheelEvent; - return { - delta_x: deltaX, - delta_y: deltaY, - delta_z: deltaZ, - delta_mode: deltaMode, - }; - } - - case "animationstart": - case "animationend": - case "animationiteration": { - const { - animationName, - elapsedTime, - pseudoElement, - } = event as AnimationEvent; - return { - animation_name: animationName, - elapsed_time: elapsedTime, - pseudo_element: pseudoElement, - }; - } - - case "transitionend": { - const { - propertyName, - elapsedTime, - pseudoElement, - } = event as TransitionEvent; - return { - property_name: propertyName, - elapsed_time: elapsedTime, - pseudo_element: pseudoElement, - }; - } - - case "abort": - case "canplay": - case "canplaythrough": - case "durationchange": - case "emptied": - case "encrypted": - case "ended": - case "error": - case "loadeddata": - case "loadedmetadata": - case "loadstart": - case "pause": - case "play": - case "playing": - case "progress": - case "ratechange": - case "seeked": - case "seeking": - case "stalled": - case "suspend": - case "timeupdate": - case "volumechange": - case "waiting": { - return {}; - } - - case "toggle": { - return {}; - } - - default: { - return {}; - } - } -} - -const bool_attrs = { - allowfullscreen: true, - allowpaymentrequest: true, - async: true, - autofocus: true, - autoplay: true, - checked: true, - controls: true, - default: true, - defer: true, - disabled: true, - formnovalidate: true, - hidden: true, - ismap: true, - itemscope: true, - loop: true, - multiple: true, - muted: true, - nomodule: true, - novalidate: true, - open: true, - playsinline: true, - readonly: true, - required: true, - reversed: true, - selected: true, - truespeed: true, -}; - - - -type PushRoot = { type: "PushRoot", root: number }; -type AppendChildren = { type: "AppendChildren", many: number }; -type ReplaceWith = { type: "ReplaceWith", root: number, m: number }; -type InsertAfter = { type: "InsertAfter", root: number, n: number }; -type InsertBefore = { type: "InsertBefore", root: number, n: number }; -type Remove = { type: "Remove", root: number }; -type CreateTextNode = { type: "CreateTextNode", text: string, root: number }; -type CreateElement = { type: "CreateElement", tag: string, root: number }; -type CreateElementNs = { type: "CreateElementNs", tag: string, root: number, ns: string }; -type CreatePlaceholder = { type: "CreatePlaceholder", root: number }; -type NewEventListener = { type: "NewEventListener", root: number, event_name: string, scope: number }; -type RemoveEventListener = { type: "RemoveEventListener", event_name: string, scope: number, root: number }; -type SetText = { type: "SetText", root: number, text: string }; -type SetAttribute = { type: "SetAttribute", root: number, field: string, value: string, ns: string | undefined }; -type RemoveAttribute = { type: "RemoveAttribute", root: number, name: string }; - - -type DomEdit = - PushRoot | - AppendChildren | - ReplaceWith | - InsertAfter | - InsertBefore | - Remove | - CreateTextNode | - CreateElement | - CreateElementNs | - CreatePlaceholder | - NewEventListener | - RemoveEventListener | - SetText | - SetAttribute | - RemoveAttribute; diff --git a/packages/interpreter/src/lib.rs b/packages/interpreter/src/lib.rs index 7d680ae25..ebca3cbf4 100644 --- a/packages/interpreter/src/lib.rs +++ b/packages/interpreter/src/lib.rs @@ -1 +1,7 @@ -pub static INTERPRTER_JS: &str = include_str!("../gen/interpreter.js"); +pub static INTERPRTER_JS: &str = include_str!("./interpreter.js"); + +#[cfg(feature = "web")] +mod bindings; + +#[cfg(feature = "web")] +pub use bindings::Interpreter; diff --git a/packages/interpreter/tsconfig.json b/packages/interpreter/tsconfig.json deleted file mode 100644 index 1c1ed1ef9..000000000 --- a/packages/interpreter/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "compilerOptions": { - "target": "ESNext", - "module": "ES2015", - "lib": [ - "es2015", - "es5", - "es6", - "dom" - ], - "rootDir": "src", - "strict": true, - "outDir": "gen", - } -} diff --git a/packages/web/Cargo.toml b/packages/web/Cargo.toml index 9ac508a8c..9145fed8b 100644 --- a/packages/web/Cargo.toml +++ b/packages/web/Cargo.toml @@ -13,27 +13,25 @@ keywords = ["dom", "ui", "gui", "react", "wasm"] [dependencies] dioxus-core = { path = "../core", version = "^0.1.9" } dioxus-html = { path = "../html", version = "^0.1.6" } -js-sys = "0.3" -wasm-bindgen = { version = "0.2.78", features = ["enable-interning"] } +js-sys = "0.3.56" +wasm-bindgen = { version = "0.2.79", features = ["enable-interning"] } lazy_static = "1.4.0" -wasm-bindgen-futures = "0.4.20" +wasm-bindgen-futures = "0.4.29" log = { version = "0.4.14", features = ["release_max_level_off"] } fxhash = "0.2.1" wasm-logger = "0.2.0" console_error_panic_hook = { version = "0.1.7", optional = true } -wasm-bindgen-test = "0.3.21" -once_cell = "1.8" +wasm-bindgen-test = "0.3.29" +once_cell = "1.9.0" async-channel = "1.6.1" -anyhow = "1.0" -gloo-timers = { version = "0.2.1", features = ["futures"] } -futures-util = "0.3.15" +anyhow = "1.0.53" +gloo-timers = { version = "0.2.3", features = ["futures"] } +futures-util = "0.3.19" smallstr = "0.2.0" - -[build-dependencies] -dioxus-interpreter-js = { path = "../interpreter", version = "^0.0.0" } +dioxus-interpreter-js = { path = "../interpreter", version = "^0.0.0", features = ["web"] } [dependencies.web-sys] -version = "0.3.51" +version = "0.3.56" features = [ "Comment", "Attr", @@ -78,5 +76,5 @@ panic_hook = ["console_error_panic_hook"] [dev-dependencies] dioxus-core-macro = { path = "../core-macro" } -wasm-bindgen-test = "0.3.28" +wasm-bindgen-test = "0.3.29" dioxus-ssr = { path = "../ssr" } diff --git a/packages/web/src/bindings.rs b/packages/web/src/bindings.rs deleted file mode 100644 index 4834146af..000000000 --- a/packages/web/src/bindings.rs +++ /dev/null @@ -1,561 +0,0 @@ -use js_sys::Function; -use wasm_bindgen::prelude::*; -use web_sys::{Element, Node}; - -/* -This is an autogenerated file from build.rs. -Do not edit this file directly. -*/ - -#[wasm_bindgen(inline_js = r##"export function main() { - let root = window.document.getElementById("main"); - if (root != null) { - window.interpreter = new Interpreter(root); - window.rpc.call("initialize"); - } -} -export class Interpreter { - root; - stack; - listeners; - handlers; - lastNodeWasText; - nodes; - constructor(root) { - this.root = root; - this.stack = [root]; - this.listeners = {}; - this.handlers = {}; - this.lastNodeWasText = false; - this.nodes = [root]; - } - top() { - return this.stack[this.stack.length - 1]; - } - pop() { - return this.stack.pop(); - } - PushRoot(root) { - const node = this.nodes[root]; - this.stack.push(node); - } - AppendChildren(many) { - let root = this.stack[this.stack.length - (1 + many)]; - let to_add = this.stack.splice(this.stack.length - many); - for (let i = 0; i < many; i++) { - root.appendChild(to_add[i]); - } - } - ReplaceWith(root_id, m) { - let root = this.nodes[root_id]; - let els = this.stack.splice(this.stack.length - m); - root.replaceWith(...els); - } - InsertAfter(root, n) { - let old = this.nodes[root]; - let new_nodes = this.stack.splice(this.stack.length - n); - old.after(...new_nodes); - } - InsertBefore(root, n) { - let old = this.nodes[root]; - let new_nodes = this.stack.splice(this.stack.length - n); - old.before(...new_nodes); - } - Remove(root) { - let node = this.nodes[root]; - if (node !== undefined) { - node.remove(); - } - } - CreateTextNode(text, root) { - // todo: make it so the types are okay - const node = document.createTextNode(text); - this.nodes[root] = node; - this.stack.push(node); - } - CreateElement(tag, root) { - const el = document.createElement(tag); - // el.setAttribute("data-dioxus-id", `${root}`); - this.nodes[root] = el; - this.stack.push(el); - } - CreateElementNs(tag, root, ns) { - let el = document.createElementNS(ns, tag); - this.stack.push(el); - this.nodes[root] = el; - } - CreatePlaceholder(root) { - let el = document.createElement("pre"); - el.hidden = true; - this.stack.push(el); - this.nodes[root] = el; - } - NewEventListener(event_name, root, handler) { - const element = this.nodes[root]; - element.setAttribute("data-dioxus-id", `${root}`); - if (this.listeners[event_name] === undefined) { - this.listeners[event_name] = 0; - this.handlers[event_name] = handler; - this.root.addEventListener(event_name, handler); - } - else { - this.listeners[event_name]++; - } - } - RemoveEventListener(root, event_name) { - const element = this.nodes[root]; - element.removeAttribute(`data-dioxus-id`); - this.listeners[event_name]--; - if (this.listeners[event_name] === 0) { - this.root.removeEventListener(event_name, this.handlers[event_name]); - delete this.listeners[event_name]; - delete this.handlers[event_name]; - } - } - SetText(root, text) { - this.nodes[root].textContent = text; - } - SetAttribute(root, field, value, ns) { - const name = field; - const node = this.nodes[root]; - if (ns == "style") { - // @ts-ignore - node.style[name] = value; - } - else if (ns != null || ns != undefined) { - node.setAttributeNS(ns, name, value); - } - else { - switch (name) { - case "value": - if (value != node.value) { - node.value = value; - } - break; - case "checked": - node.checked = value === "true"; - break; - case "selected": - node.selected = value === "true"; - break; - case "dangerous_inner_html": - node.innerHTML = value; - break; - default: - // https://github.com/facebook/react/blob/8b88ac2592c5f555f315f9440cbb665dd1e7457a/packages/react-dom/src/shared/DOMProperty.js#L352-L364 - if (value == "false" && bool_attrs.hasOwnProperty(name)) { - node.removeAttribute(name); - } - else { - node.setAttribute(name, value); - } - } - } - } - RemoveAttribute(root, name) { - const node = this.nodes[root]; - node.removeAttribute(name); - if (name === "value") { - node.value = ""; - } - if (name === "checked") { - node.checked = false; - } - if (name === "selected") { - node.selected = false; - } - } - handleEdits(edits) { - this.stack.push(this.root); - for (let edit of edits) { - this.handleEdit(edit); - } - } - handleEdit(edit) { - switch (edit.type) { - case "PushRoot": - this.PushRoot(edit.root); - break; - case "AppendChildren": - this.AppendChildren(edit.many); - break; - case "ReplaceWith": - this.ReplaceWith(edit.root, edit.m); - break; - case "InsertAfter": - this.InsertAfter(edit.root, edit.n); - break; - case "InsertBefore": - this.InsertBefore(edit.root, edit.n); - break; - case "Remove": - this.Remove(edit.root); - break; - case "CreateTextNode": - this.CreateTextNode(edit.text, edit.root); - break; - case "CreateElement": - this.CreateElement(edit.tag, edit.root); - break; - case "CreateElementNs": - this.CreateElementNs(edit.tag, edit.root, edit.ns); - break; - case "CreatePlaceholder": - this.CreatePlaceholder(edit.root); - break; - case "RemoveEventListener": - this.RemoveEventListener(edit.root, edit.event_name); - break; - case "NewEventListener": - // this handler is only provided on desktop implementations since this - // method is not used by the web implementation - let handler = (event) => { - let target = event.target; - if (target != null) { - let realId = target.getAttribute(`data-dioxus-id`); - let shouldPreventDefault = target.getAttribute(`dioxus-prevent-default`); - if (event.type == "click") { - event.preventDefault(); - if (shouldPreventDefault !== `onclick`) { - if (target.tagName == "A") { - const href = target.getAttribute("href"); - if (href !== "" && href !== null && href !== undefined) { - window.rpc.call("browser_open", { href }); - } - } - } - } - // walk the tree to find the real element - while (realId == null && target.parentElement != null) { - target = target.parentElement; - realId = target.getAttribute(`data-dioxus-id`); - } - shouldPreventDefault = target.getAttribute(`dioxus-prevent-default`); - let contents = serialize_event(event); - if (shouldPreventDefault === `on${event.type}`) { - event.preventDefault(); - } - if (event.type == "submit") { - event.preventDefault(); - } - if (realId == null) { - return; - } - window.rpc.call("user_event", { - event: edit.event_name, - mounted_dom_id: parseInt(realId), - contents: contents, - }); - } - }; - this.NewEventListener(edit.event_name, edit.root, handler); - break; - case "SetText": - this.SetText(edit.root, edit.text); - break; - case "SetAttribute": - this.SetAttribute(edit.root, edit.field, edit.value, edit.ns); - break; - case "RemoveAttribute": - this.RemoveAttribute(edit.root, edit.name); - break; - } - } -} -function serialize_event(event) { - switch (event.type) { - case "copy": - case "cut": - case "past": { - return {}; - } - case "compositionend": - case "compositionstart": - case "compositionupdate": { - let { data } = event; - return { - data, - }; - } - case "keydown": - case "keypress": - case "keyup": { - let { charCode, key, altKey, ctrlKey, metaKey, keyCode, shiftKey, location, repeat, which, } = event; - return { - char_code: charCode, - key: key, - alt_key: altKey, - ctrl_key: ctrlKey, - meta_key: metaKey, - key_code: keyCode, - shift_key: shiftKey, - location: location, - repeat: repeat, - which: which, - locale: "locale", - }; - } - case "focus": - case "blur": { - return {}; - } - case "change": { - 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, - }; - } - case "input": - case "invalid": - case "reset": - case "submit": { - let target = event.target; - let value = target.value ?? target.textContent; - if (target.type == "checkbox") { - value = target.checked ? "true" : "false"; - } - return { - value: value, - }; - } - case "click": - case "contextmenu": - case "doubleclick": - case "drag": - case "dragend": - case "dragenter": - case "dragexit": - case "dragleave": - case "dragover": - case "dragstart": - case "drop": - case "mousedown": - case "mouseenter": - case "mouseleave": - case "mousemove": - case "mouseout": - case "mouseover": - case "mouseup": { - const { altKey, button, buttons, clientX, clientY, ctrlKey, metaKey, pageX, pageY, screenX, screenY, shiftKey, } = event; - return { - alt_key: altKey, - button: button, - buttons: buttons, - client_x: clientX, - client_y: clientY, - ctrl_key: ctrlKey, - meta_key: metaKey, - page_x: pageX, - page_y: pageY, - screen_x: screenX, - screen_y: screenY, - shift_key: shiftKey, - }; - } - case "pointerdown": - case "pointermove": - case "pointerup": - case "pointercancel": - case "gotpointercapture": - case "lostpointercapture": - case "pointerenter": - case "pointerleave": - case "pointerover": - case "pointerout": { - const { altKey, button, buttons, clientX, clientY, ctrlKey, metaKey, pageX, pageY, screenX, screenY, shiftKey, pointerId, width, height, pressure, tangentialPressure, tiltX, tiltY, twist, pointerType, isPrimary, } = event; - return { - alt_key: altKey, - button: button, - buttons: buttons, - client_x: clientX, - client_y: clientY, - ctrl_key: ctrlKey, - meta_key: metaKey, - page_x: pageX, - page_y: pageY, - screen_x: screenX, - screen_y: screenY, - shift_key: shiftKey, - pointer_id: pointerId, - width: width, - height: height, - pressure: pressure, - tangential_pressure: tangentialPressure, - tilt_x: tiltX, - tilt_y: tiltY, - twist: twist, - pointer_type: pointerType, - is_primary: isPrimary, - }; - } - case "select": { - return {}; - } - case "touchcancel": - case "touchend": - case "touchmove": - case "touchstart": { - const { altKey, ctrlKey, metaKey, shiftKey, } = event; - return { - // changed_touches: event.changedTouches, - // target_touches: event.targetTouches, - // touches: event.touches, - alt_key: altKey, - ctrl_key: ctrlKey, - meta_key: metaKey, - shift_key: shiftKey, - }; - } - case "scroll": { - return {}; - } - case "wheel": { - const { deltaX, deltaY, deltaZ, deltaMode, } = event; - return { - delta_x: deltaX, - delta_y: deltaY, - delta_z: deltaZ, - delta_mode: deltaMode, - }; - } - case "animationstart": - case "animationend": - case "animationiteration": { - const { animationName, elapsedTime, pseudoElement, } = event; - return { - animation_name: animationName, - elapsed_time: elapsedTime, - pseudo_element: pseudoElement, - }; - } - case "transitionend": { - const { propertyName, elapsedTime, pseudoElement, } = event; - return { - property_name: propertyName, - elapsed_time: elapsedTime, - pseudo_element: pseudoElement, - }; - } - case "abort": - case "canplay": - case "canplaythrough": - case "durationchange": - case "emptied": - case "encrypted": - case "ended": - case "error": - case "loadeddata": - case "loadedmetadata": - case "loadstart": - case "pause": - case "play": - case "playing": - case "progress": - case "ratechange": - case "seeked": - case "seeking": - case "stalled": - case "suspend": - case "timeupdate": - case "volumechange": - case "waiting": { - return {}; - } - case "toggle": { - return {}; - } - default: { - return {}; - } - } -} -const bool_attrs = { - allowfullscreen: true, - allowpaymentrequest: true, - async: true, - autofocus: true, - autoplay: true, - checked: true, - controls: true, - default: true, - defer: true, - disabled: true, - formnovalidate: true, - hidden: true, - ismap: true, - itemscope: true, - loop: true, - multiple: true, - muted: true, - nomodule: true, - novalidate: true, - open: true, - playsinline: true, - readonly: true, - required: true, - reversed: true, - selected: true, - truespeed: true, -}; -"##)] -extern "C" { - pub type Interpreter; - - #[wasm_bindgen(constructor)] - pub fn new(arg: Element) -> Interpreter; - - #[wasm_bindgen(method)] - pub fn set_node(this: &Interpreter, id: usize, node: Node); - - #[wasm_bindgen(method)] - pub fn PushRoot(this: &Interpreter, root: u64); - - #[wasm_bindgen(method)] - pub fn AppendChildren(this: &Interpreter, many: u32); - - #[wasm_bindgen(method)] - pub fn ReplaceWith(this: &Interpreter, root: u64, m: u32); - - #[wasm_bindgen(method)] - pub fn InsertAfter(this: &Interpreter, root: u64, n: u32); - - #[wasm_bindgen(method)] - pub fn InsertBefore(this: &Interpreter, root: u64, n: u32); - - #[wasm_bindgen(method)] - pub fn Remove(this: &Interpreter, root: u64); - - #[wasm_bindgen(method)] - pub fn CreateTextNode(this: &Interpreter, text: &str, root: u64); - - #[wasm_bindgen(method)] - pub fn CreateElement(this: &Interpreter, tag: &str, root: u64); - - #[wasm_bindgen(method)] - pub fn CreateElementNs(this: &Interpreter, tag: &str, root: u64, ns: &str); - - #[wasm_bindgen(method)] - pub fn CreatePlaceholder(this: &Interpreter, root: u64); - - #[wasm_bindgen(method)] - pub fn NewEventListener(this: &Interpreter, name: &str, root: u64, handler: &Function); - - #[wasm_bindgen(method)] - pub fn RemoveEventListener(this: &Interpreter, root: u64, name: &str); - - #[wasm_bindgen(method)] - pub fn SetText(this: &Interpreter, root: u64, text: &str); - - #[wasm_bindgen(method)] - pub fn SetAttribute(this: &Interpreter, root: u64, field: &str, value: &str, ns: Option<&str>); - - #[wasm_bindgen(method)] - pub fn RemoveAttribute(this: &Interpreter, root: u64, field: &str); -} diff --git a/packages/web/src/dom.rs b/packages/web/src/dom.rs index 733e50e4b..6ca689d28 100644 --- a/packages/web/src/dom.rs +++ b/packages/web/src/dom.rs @@ -7,8 +7,8 @@ //! - tests to ensure dyn_into works for various event types. //! - Partial delegation?> -use crate::bindings::Interpreter; use dioxus_core::{DomEdit, ElementId, SchedulerMsg, UserEvent}; +use dioxus_interpreter_js::Interpreter; use js_sys::Function; use std::{any::Any, rc::Rc, sync::Arc}; use wasm_bindgen::{closure::Closure, JsCast}; diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs index f4266a7c8..cef1bc14c 100644 --- a/packages/web/src/lib.rs +++ b/packages/web/src/lib.rs @@ -61,7 +61,6 @@ pub use dioxus_core as dioxus; use dioxus_core::prelude::Component; use futures_util::FutureExt; -pub(crate) mod bindings; mod cache; mod cfg; mod dom; From b4391a3eaa4292d96072661107fd7328c23293b2 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 4 Feb 2022 02:44:27 -0500 Subject: [PATCH 24/28] fix: tweak the js bindings while we're at it --- examples/todomvc.rs | 9 +- packages/interpreter/src/interpreter.js | 1002 ++++++++++++----------- 2 files changed, 531 insertions(+), 480 deletions(-) diff --git a/examples/todomvc.rs b/examples/todomvc.rs index e5f7d1dfa..937d6992c 100644 --- a/examples/todomvc.rs +++ b/examples/todomvc.rs @@ -120,15 +120,18 @@ pub fn todo_entry<'a>(cx: Scope<'a, TodoEntryProps<'a>>) -> Element { rsx!(cx, li { class: "{completed} {editing}", - onclick: move |_| set_is_editing(true), - onfocusout: move |_| set_is_editing(false), div { class: "view", input { class: "toggle", r#type: "checkbox", id: "cbg-{todo.id}", checked: "{todo.checked}", onchange: move |evt| { cx.props.set_todos.make_mut()[&cx.props.id].checked = evt.value.parse().unwrap(); } } - label { r#for: "cbg-{todo.id}", pointer_events: "none", "{todo.contents}" } + label { + r#for: "cbg-{todo.id}", + onclick: move |_| set_is_editing(true), + onfocusout: move |_| set_is_editing(false), + "{todo.contents}" + } } is_editing.then(|| rsx!{ input { diff --git a/packages/interpreter/src/interpreter.js b/packages/interpreter/src/interpreter.js index 0d89efa85..dbcb73a96 100644 --- a/packages/interpreter/src/interpreter.js +++ b/packages/interpreter/src/interpreter.js @@ -1,497 +1,545 @@ export function main() { - let root = window.document.getElementById("main"); - if (root != null) { - window.interpreter = new Interpreter(root); - window.rpc.call("initialize"); - } + let root = window.document.getElementById("main"); + if (root != null) { + window.interpreter = new Interpreter(root); + window.rpc.call("initialize"); + } } export class Interpreter { - root; - stack; - listeners; - handlers; - lastNodeWasText; - nodes; - constructor(root) { - this.root = root; - this.stack = [root]; - this.listeners = {}; - this.handlers = {}; - this.lastNodeWasText = false; - this.nodes = [root]; + root; + stack; + listeners; + handlers; + lastNodeWasText; + nodes; + constructor(root) { + this.root = root; + this.stack = [root]; + this.listeners = {}; + this.handlers = {}; + this.lastNodeWasText = false; + this.nodes = [root]; + } + top() { + return this.stack[this.stack.length - 1]; + } + pop() { + return this.stack.pop(); + } + PushRoot(root) { + const node = this.nodes[root]; + this.stack.push(node); + } + AppendChildren(many) { + let root = this.stack[this.stack.length - (1 + many)]; + let to_add = this.stack.splice(this.stack.length - many); + for (let i = 0; i < many; i++) { + root.appendChild(to_add[i]); } - top() { - return this.stack[this.stack.length - 1]; + } + ReplaceWith(root_id, m) { + let root = this.nodes[root_id]; + let els = this.stack.splice(this.stack.length - m); + root.replaceWith(...els); + } + InsertAfter(root, n) { + let old = this.nodes[root]; + let new_nodes = this.stack.splice(this.stack.length - n); + old.after(...new_nodes); + } + InsertBefore(root, n) { + let old = this.nodes[root]; + let new_nodes = this.stack.splice(this.stack.length - n); + old.before(...new_nodes); + } + Remove(root) { + let node = this.nodes[root]; + if (node !== undefined) { + node.remove(); } - pop() { - return this.stack.pop(); + } + CreateTextNode(text, root) { + // todo: make it so the types are okay + const node = document.createTextNode(text); + this.nodes[root] = node; + this.stack.push(node); + } + CreateElement(tag, root) { + const el = document.createElement(tag); + // el.setAttribute("data-dioxus-id", `${root}`); + this.nodes[root] = el; + this.stack.push(el); + } + CreateElementNs(tag, root, ns) { + let el = document.createElementNS(ns, tag); + this.stack.push(el); + this.nodes[root] = el; + } + CreatePlaceholder(root) { + let el = document.createElement("pre"); + el.hidden = true; + this.stack.push(el); + this.nodes[root] = el; + } + NewEventListener(event_name, root, handler) { + const element = this.nodes[root]; + element.setAttribute("data-dioxus-id", `${root}`); + if (this.listeners[event_name] === undefined) { + this.listeners[event_name] = 0; + this.handlers[event_name] = handler; + this.root.addEventListener(event_name, handler); + } else { + this.listeners[event_name]++; } - PushRoot(root) { - const node = this.nodes[root]; - this.stack.push(node); + } + RemoveEventListener(root, event_name) { + const element = this.nodes[root]; + element.removeAttribute(`data-dioxus-id`); + this.listeners[event_name]--; + if (this.listeners[event_name] === 0) { + this.root.removeEventListener(event_name, this.handlers[event_name]); + delete this.listeners[event_name]; + delete this.handlers[event_name]; } - AppendChildren(many) { - let root = this.stack[this.stack.length - (1 + many)]; - let to_add = this.stack.splice(this.stack.length - many); - for (let i = 0; i < many; i++) { - root.appendChild(to_add[i]); - } + } + SetText(root, text) { + this.nodes[root].textContent = text; + } + SetAttribute(root, field, value, ns) { + const name = field; + const node = this.nodes[root]; + if (ns == "style") { + // @ts-ignore + node.style[name] = value; + } else if (ns != null || ns != undefined) { + node.setAttributeNS(ns, name, value); + } else { + switch (name) { + case "value": + if (value != node.value) { + node.value = value; + } + break; + case "checked": + node.checked = value === "true"; + break; + case "selected": + node.selected = value === "true"; + break; + case "dangerous_inner_html": + node.innerHTML = value; + break; + default: + // https://github.com/facebook/react/blob/8b88ac2592c5f555f315f9440cbb665dd1e7457a/packages/react-dom/src/shared/DOMProperty.js#L352-L364 + if (value == "false" && bool_attrs.hasOwnProperty(name)) { + node.removeAttribute(name); + } else { + node.setAttribute(name, value); + } + } } - ReplaceWith(root_id, m) { - let root = this.nodes[root_id]; - let els = this.stack.splice(this.stack.length - m); - root.replaceWith(...els); + } + RemoveAttribute(root, name) { + const node = this.nodes[root]; + node.removeAttribute(name); + if (name === "value") { + node.value = ""; } - InsertAfter(root, n) { - let old = this.nodes[root]; - let new_nodes = this.stack.splice(this.stack.length - n); - old.after(...new_nodes); + if (name === "checked") { + node.checked = false; } - InsertBefore(root, n) { - let old = this.nodes[root]; - let new_nodes = this.stack.splice(this.stack.length - n); - old.before(...new_nodes); + if (name === "selected") { + node.selected = false; } - Remove(root) { - let node = this.nodes[root]; - if (node !== undefined) { - node.remove(); - } + } + handleEdits(edits) { + this.stack.push(this.root); + for (let edit of edits) { + this.handleEdit(edit); } - CreateTextNode(text, root) { - // todo: make it so the types are okay - const node = document.createTextNode(text); - this.nodes[root] = node; - this.stack.push(node); - } - CreateElement(tag, root) { - const el = document.createElement(tag); - // el.setAttribute("data-dioxus-id", `${root}`); - this.nodes[root] = el; - this.stack.push(el); - } - CreateElementNs(tag, root, ns) { - let el = document.createElementNS(ns, tag); - this.stack.push(el); - this.nodes[root] = el; - } - CreatePlaceholder(root) { - let el = document.createElement("pre"); - el.hidden = true; - this.stack.push(el); - this.nodes[root] = el; - } - NewEventListener(event_name, root, handler) { - const element = this.nodes[root]; - element.setAttribute("data-dioxus-id", `${root}`); - if (this.listeners[event_name] === undefined) { - this.listeners[event_name] = 0; - this.handlers[event_name] = handler; - this.root.addEventListener(event_name, handler); - } - else { - this.listeners[event_name]++; - } - } - RemoveEventListener(root, event_name) { - const element = this.nodes[root]; - element.removeAttribute(`data-dioxus-id`); - this.listeners[event_name]--; - if (this.listeners[event_name] === 0) { - this.root.removeEventListener(event_name, this.handlers[event_name]); - delete this.listeners[event_name]; - delete this.handlers[event_name]; - } - } - SetText(root, text) { - this.nodes[root].textContent = text; - } - SetAttribute(root, field, value, ns) { - const name = field; - const node = this.nodes[root]; - if (ns == "style") { - // @ts-ignore - node.style[name] = value; - } - else if (ns != null || ns != undefined) { - node.setAttributeNS(ns, name, value); - } - else { - switch (name) { - case "value": - if (value != node.value) { - node.value = value; - } - break; - case "checked": - node.checked = value === "true"; - break; - case "selected": - node.selected = value === "true"; - break; - case "dangerous_inner_html": - node.innerHTML = value; - break; - default: - // https://github.com/facebook/react/blob/8b88ac2592c5f555f315f9440cbb665dd1e7457a/packages/react-dom/src/shared/DOMProperty.js#L352-L364 - if (value == "false" && bool_attrs.hasOwnProperty(name)) { - node.removeAttribute(name); - } - else { - node.setAttribute(name, value); - } + } + handleEdit(edit) { + switch (edit.type) { + case "PushRoot": + this.PushRoot(edit.root); + break; + case "AppendChildren": + this.AppendChildren(edit.many); + break; + case "ReplaceWith": + this.ReplaceWith(edit.root, edit.m); + break; + case "InsertAfter": + this.InsertAfter(edit.root, edit.n); + break; + case "InsertBefore": + this.InsertBefore(edit.root, edit.n); + break; + case "Remove": + this.Remove(edit.root); + break; + case "CreateTextNode": + this.CreateTextNode(edit.text, edit.root); + break; + case "CreateElement": + this.CreateElement(edit.tag, edit.root); + break; + case "CreateElementNs": + this.CreateElementNs(edit.tag, edit.root, edit.ns); + break; + case "CreatePlaceholder": + this.CreatePlaceholder(edit.root); + break; + case "RemoveEventListener": + this.RemoveEventListener(edit.root, edit.event_name); + break; + case "NewEventListener": + // this handler is only provided on desktop implementations since this + // method is not used by the web implementation + let handler = (event) => { + let target = event.target; + if (target != null) { + let realId = target.getAttribute(`data-dioxus-id`); + let shouldPreventDefault = target.getAttribute( + `dioxus-prevent-default` + ); + + if (event.type == "click") { + // todo call prevent default if it's the right type of event + if (shouldPreventDefault !== `onclick`) { + if (target.tagName == "A") { + const href = target.getAttribute("href"); + if (href !== "" && href !== null && href !== undefined) { + window.rpc.call("browser_open", { href }); + } + } + } } - } - } - RemoveAttribute(root, name) { - const node = this.nodes[root]; - node.removeAttribute(name); - if (name === "value") { - node.value = ""; - } - if (name === "checked") { - node.checked = false; - } - if (name === "selected") { - node.selected = false; - } - } - handleEdits(edits) { - this.stack.push(this.root); - for (let edit of edits) { - this.handleEdit(edit); - } - } - handleEdit(edit) { - switch (edit.type) { - case "PushRoot": - this.PushRoot(edit.root); - break; - case "AppendChildren": - this.AppendChildren(edit.many); - break; - case "ReplaceWith": - this.ReplaceWith(edit.root, edit.m); - break; - case "InsertAfter": - this.InsertAfter(edit.root, edit.n); - break; - case "InsertBefore": - this.InsertBefore(edit.root, edit.n); - break; - case "Remove": - this.Remove(edit.root); - break; - case "CreateTextNode": - this.CreateTextNode(edit.text, edit.root); - break; - case "CreateElement": - this.CreateElement(edit.tag, edit.root); - break; - case "CreateElementNs": - this.CreateElementNs(edit.tag, edit.root, edit.ns); - break; - case "CreatePlaceholder": - this.CreatePlaceholder(edit.root); - break; - case "RemoveEventListener": - this.RemoveEventListener(edit.root, edit.event_name); - break; - case "NewEventListener": - // this handler is only provided on desktop implementations since this - // method is not used by the web implementation - let handler = (event) => { - let target = event.target; - if (target != null) { - let realId = target.getAttribute(`data-dioxus-id`); - let shouldPreventDefault = target.getAttribute(`dioxus-prevent-default`); - if (event.type == "click") { - event.preventDefault(); - if (shouldPreventDefault !== `onclick`) { - if (target.tagName == "A") { - const href = target.getAttribute("href"); - if (href !== "" && href !== null && href !== undefined) { - window.rpc.call("browser_open", { href }); - } - } - } - } - // walk the tree to find the real element - while (realId == null && target.parentElement != null) { - target = target.parentElement; - realId = target.getAttribute(`data-dioxus-id`); - } - shouldPreventDefault = target.getAttribute(`dioxus-prevent-default`); - let contents = serialize_event(event); - if (shouldPreventDefault === `on${event.type}`) { - event.preventDefault(); - } - if (event.type == "submit") { - event.preventDefault(); - } - if (realId == null) { - return; - } - window.rpc.call("user_event", { - event: edit.event_name, - mounted_dom_id: parseInt(realId), - contents: contents, - }); - } - }; - this.NewEventListener(edit.event_name, edit.root, handler); - break; - case "SetText": - this.SetText(edit.root, edit.text); - break; - case "SetAttribute": - this.SetAttribute(edit.root, edit.field, edit.value, edit.ns); - break; - case "RemoveAttribute": - this.RemoveAttribute(edit.root, edit.name); - break; - } + // walk the tree to find the real element + while (realId == null && target.parentElement != null) { + target = target.parentElement; + realId = target.getAttribute(`data-dioxus-id`); + } + + shouldPreventDefault = target.getAttribute( + `dioxus-prevent-default` + ); + let contents = serialize_event(event); + if (shouldPreventDefault === `on${event.type}`) { + // event.preventDefault(); + } + if (event.type == "submit") { + // event.preventDefault(); + } + if (realId == null) { + return; + } + window.rpc.call("user_event", { + event: edit.event_name, + mounted_dom_id: parseInt(realId), + contents: contents, + }); + } + }; + this.NewEventListener(edit.event_name, edit.root, handler); + break; + case "SetText": + this.SetText(edit.root, edit.text); + break; + case "SetAttribute": + this.SetAttribute(edit.root, edit.field, edit.value, edit.ns); + break; + case "RemoveAttribute": + this.RemoveAttribute(edit.root, edit.name); + break; } + } } function serialize_event(event) { - switch (event.type) { - case "copy": - case "cut": - case "past": { - return {}; - } - case "compositionend": - case "compositionstart": - case "compositionupdate": { - let { data } = event; - return { - data, - }; - } - case "keydown": - case "keypress": - case "keyup": { - let { charCode, key, altKey, ctrlKey, metaKey, keyCode, shiftKey, location, repeat, which, } = event; - return { - char_code: charCode, - key: key, - alt_key: altKey, - ctrl_key: ctrlKey, - meta_key: metaKey, - key_code: keyCode, - shift_key: shiftKey, - location: location, - repeat: repeat, - which: which, - locale: "locale", - }; - } - case "focus": - case "blur": { - return {}; - } - case "change": { - 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, - }; - } - case "input": - case "invalid": - case "reset": - case "submit": { - let target = event.target; - let value = target.value ?? target.textContent; - if (target.type == "checkbox") { - value = target.checked ? "true" : "false"; - } - return { - value: value, - }; - } - case "click": - case "contextmenu": - case "doubleclick": - case "drag": - case "dragend": - case "dragenter": - case "dragexit": - case "dragleave": - case "dragover": - case "dragstart": - case "drop": - case "mousedown": - case "mouseenter": - case "mouseleave": - case "mousemove": - case "mouseout": - case "mouseover": - case "mouseup": { - const { altKey, button, buttons, clientX, clientY, ctrlKey, metaKey, pageX, pageY, screenX, screenY, shiftKey, } = event; - return { - alt_key: altKey, - button: button, - buttons: buttons, - client_x: clientX, - client_y: clientY, - ctrl_key: ctrlKey, - meta_key: metaKey, - page_x: pageX, - page_y: pageY, - screen_x: screenX, - screen_y: screenY, - shift_key: shiftKey, - }; - } - case "pointerdown": - case "pointermove": - case "pointerup": - case "pointercancel": - case "gotpointercapture": - case "lostpointercapture": - case "pointerenter": - case "pointerleave": - case "pointerover": - case "pointerout": { - const { altKey, button, buttons, clientX, clientY, ctrlKey, metaKey, pageX, pageY, screenX, screenY, shiftKey, pointerId, width, height, pressure, tangentialPressure, tiltX, tiltY, twist, pointerType, isPrimary, } = event; - return { - alt_key: altKey, - button: button, - buttons: buttons, - client_x: clientX, - client_y: clientY, - ctrl_key: ctrlKey, - meta_key: metaKey, - page_x: pageX, - page_y: pageY, - screen_x: screenX, - screen_y: screenY, - shift_key: shiftKey, - pointer_id: pointerId, - width: width, - height: height, - pressure: pressure, - tangential_pressure: tangentialPressure, - tilt_x: tiltX, - tilt_y: tiltY, - twist: twist, - pointer_type: pointerType, - is_primary: isPrimary, - }; - } - case "select": { - return {}; - } - case "touchcancel": - case "touchend": - case "touchmove": - case "touchstart": { - const { altKey, ctrlKey, metaKey, shiftKey, } = event; - return { - // changed_touches: event.changedTouches, - // target_touches: event.targetTouches, - // touches: event.touches, - alt_key: altKey, - ctrl_key: ctrlKey, - meta_key: metaKey, - shift_key: shiftKey, - }; - } - case "scroll": { - return {}; - } - case "wheel": { - const { deltaX, deltaY, deltaZ, deltaMode, } = event; - return { - delta_x: deltaX, - delta_y: deltaY, - delta_z: deltaZ, - delta_mode: deltaMode, - }; - } - case "animationstart": - case "animationend": - case "animationiteration": { - const { animationName, elapsedTime, pseudoElement, } = event; - return { - animation_name: animationName, - elapsed_time: elapsedTime, - pseudo_element: pseudoElement, - }; - } - case "transitionend": { - const { propertyName, elapsedTime, pseudoElement, } = event; - return { - property_name: propertyName, - elapsed_time: elapsedTime, - pseudo_element: pseudoElement, - }; - } - case "abort": - case "canplay": - case "canplaythrough": - case "durationchange": - case "emptied": - case "encrypted": - case "ended": - case "error": - case "loadeddata": - case "loadedmetadata": - case "loadstart": - case "pause": - case "play": - case "playing": - case "progress": - case "ratechange": - case "seeked": - case "seeking": - case "stalled": - case "suspend": - case "timeupdate": - case "volumechange": - case "waiting": { - return {}; - } - case "toggle": { - return {}; - } - default: { - return {}; - } + switch (event.type) { + case "copy": + case "cut": + case "past": { + return {}; } + case "compositionend": + case "compositionstart": + case "compositionupdate": { + let { data } = event; + return { + data, + }; + } + case "keydown": + case "keypress": + case "keyup": { + let { + charCode, + key, + altKey, + ctrlKey, + metaKey, + keyCode, + shiftKey, + location, + repeat, + which, + } = event; + return { + char_code: charCode, + key: key, + alt_key: altKey, + ctrl_key: ctrlKey, + meta_key: metaKey, + key_code: keyCode, + shift_key: shiftKey, + location: location, + repeat: repeat, + which: which, + locale: "locale", + }; + } + case "focus": + case "blur": { + return {}; + } + case "change": { + 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, + }; + } + case "input": + case "invalid": + case "reset": + case "submit": { + let target = event.target; + let value = target.value ?? target.textContent; + if (target.type == "checkbox") { + value = target.checked ? "true" : "false"; + } + return { + value: value, + }; + } + case "click": + case "contextmenu": + case "doubleclick": + case "drag": + case "dragend": + case "dragenter": + case "dragexit": + case "dragleave": + case "dragover": + case "dragstart": + case "drop": + case "mousedown": + case "mouseenter": + case "mouseleave": + case "mousemove": + case "mouseout": + case "mouseover": + case "mouseup": { + const { + altKey, + button, + buttons, + clientX, + clientY, + ctrlKey, + metaKey, + pageX, + pageY, + screenX, + screenY, + shiftKey, + } = event; + return { + alt_key: altKey, + button: button, + buttons: buttons, + client_x: clientX, + client_y: clientY, + ctrl_key: ctrlKey, + meta_key: metaKey, + page_x: pageX, + page_y: pageY, + screen_x: screenX, + screen_y: screenY, + shift_key: shiftKey, + }; + } + case "pointerdown": + case "pointermove": + case "pointerup": + case "pointercancel": + case "gotpointercapture": + case "lostpointercapture": + case "pointerenter": + case "pointerleave": + case "pointerover": + case "pointerout": { + const { + altKey, + button, + buttons, + clientX, + clientY, + ctrlKey, + metaKey, + pageX, + pageY, + screenX, + screenY, + shiftKey, + pointerId, + width, + height, + pressure, + tangentialPressure, + tiltX, + tiltY, + twist, + pointerType, + isPrimary, + } = event; + return { + alt_key: altKey, + button: button, + buttons: buttons, + client_x: clientX, + client_y: clientY, + ctrl_key: ctrlKey, + meta_key: metaKey, + page_x: pageX, + page_y: pageY, + screen_x: screenX, + screen_y: screenY, + shift_key: shiftKey, + pointer_id: pointerId, + width: width, + height: height, + pressure: pressure, + tangential_pressure: tangentialPressure, + tilt_x: tiltX, + tilt_y: tiltY, + twist: twist, + pointer_type: pointerType, + is_primary: isPrimary, + }; + } + case "select": { + return {}; + } + case "touchcancel": + case "touchend": + case "touchmove": + case "touchstart": { + const { altKey, ctrlKey, metaKey, shiftKey } = event; + return { + // changed_touches: event.changedTouches, + // target_touches: event.targetTouches, + // touches: event.touches, + alt_key: altKey, + ctrl_key: ctrlKey, + meta_key: metaKey, + shift_key: shiftKey, + }; + } + case "scroll": { + return {}; + } + case "wheel": { + const { deltaX, deltaY, deltaZ, deltaMode } = event; + return { + delta_x: deltaX, + delta_y: deltaY, + delta_z: deltaZ, + delta_mode: deltaMode, + }; + } + case "animationstart": + case "animationend": + case "animationiteration": { + const { animationName, elapsedTime, pseudoElement } = event; + return { + animation_name: animationName, + elapsed_time: elapsedTime, + pseudo_element: pseudoElement, + }; + } + case "transitionend": { + const { propertyName, elapsedTime, pseudoElement } = event; + return { + property_name: propertyName, + elapsed_time: elapsedTime, + pseudo_element: pseudoElement, + }; + } + case "abort": + case "canplay": + case "canplaythrough": + case "durationchange": + case "emptied": + case "encrypted": + case "ended": + case "error": + case "loadeddata": + case "loadedmetadata": + case "loadstart": + case "pause": + case "play": + case "playing": + case "progress": + case "ratechange": + case "seeked": + case "seeking": + case "stalled": + case "suspend": + case "timeupdate": + case "volumechange": + case "waiting": { + return {}; + } + case "toggle": { + return {}; + } + default: { + return {}; + } + } } const bool_attrs = { - allowfullscreen: true, - allowpaymentrequest: true, - async: true, - autofocus: true, - autoplay: true, - checked: true, - controls: true, - default: true, - defer: true, - disabled: true, - formnovalidate: true, - hidden: true, - ismap: true, - itemscope: true, - loop: true, - multiple: true, - muted: true, - nomodule: true, - novalidate: true, - open: true, - playsinline: true, - readonly: true, - required: true, - reversed: true, - selected: true, - truespeed: true, + allowfullscreen: true, + allowpaymentrequest: true, + async: true, + autofocus: true, + autoplay: true, + checked: true, + controls: true, + default: true, + defer: true, + disabled: true, + formnovalidate: true, + hidden: true, + ismap: true, + itemscope: true, + loop: true, + multiple: true, + muted: true, + nomodule: true, + novalidate: true, + open: true, + playsinline: true, + readonly: true, + required: true, + reversed: true, + selected: true, + truespeed: true, }; From ca9700a84dc01afcfa9e3e17e94143d1f33a335c Mon Sep 17 00:00:00 2001 From: t1m0t Date: Fri, 4 Feb 2022 08:47:09 +0100 Subject: [PATCH 25/28] fix ci test --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4ab05df1c..29319ef30 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -38,8 +38,8 @@ jobs: with: command: install args: --debug cargo-make - - uses: actions-rs/cargo@v1 - uses: browser-actions/setup-firefox@latest + - uses: actions-rs/cargo@v1 with: command: make args: tests From bd565bb65f7cef7aab263cb28994181a7f069730 Mon Sep 17 00:00:00 2001 From: t1m0t Date: Fri, 4 Feb 2022 17:18:31 +0100 Subject: [PATCH 26/28] improve Makefile tests --- .docker/Dockerfile_pre_test | 15 +++++++++++++-- .docker/run_local_tests.sh | 5 ++--- Makefile.toml | 27 ++++++++++++++++++--------- packages/router/Makefile.toml | 4 ++-- 4 files changed, 35 insertions(+), 16 deletions(-) diff --git a/.docker/Dockerfile_pre_test b/.docker/Dockerfile_pre_test index 6e0af4987..380bb5773 100644 --- a/.docker/Dockerfile_pre_test +++ b/.docker/Dockerfile_pre_test @@ -1,9 +1,20 @@ FROM rust:1.58-buster RUN apt update -RUN apt install -y libglib2.0-dev libgtk-3-dev libsoup2.4-dev libappindicator3-dev libwebkit2gtk-4.0-dev firefox-esr +RUN apt install -y \ + libglib2.0-dev \ + libgtk-3-dev \ + libsoup2.4-dev \ + libappindicator3-dev \ + libwebkit2gtk-4.0-dev \ + firefox-esr +# for kcov and Tarpaulin +#liblzma-dev binutils-dev libcurl4-openssl-dev libdw-dev libelf-dev RUN cargo install cargo-make --debug +# for test coverage +#RUN cargo install cargo-tarpaulin +# clean up a bit RUN cargo install cargo-cache && cargo cache -a -CMD ["echo","\"base image built\""] +CMD ["exit"] diff --git a/.docker/run_local_tests.sh b/.docker/run_local_tests.sh index 804d87afb..4684625d4 100644 --- a/.docker/run_local_tests.sh +++ b/.docker/run_local_tests.sh @@ -25,14 +25,13 @@ function run_script { then docker image rm dioxus-base-test-image docker image rm dioxus-test-image - docker system prune -af fi fi } run_script || echo "Error occured.. cleaning a bit." && \ - docker system prune -af; + docker system prune -f; -docker system prune -af +docker system prune -f echo "Script finished to execute" diff --git a/Makefile.toml b/Makefile.toml index 95abec9de..17c32fd15 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -10,13 +10,6 @@ CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true namespace = "core" private = true -[tasks.tests] -category = "Testing" -dependencies = ["tests-setup"] -description = "Run all tests" -env = {CARGO_MAKE_WORKSPACE_SKIP_MEMBERS = ["**/examples/*", "**/packages/changelog"]} -run_task = {name = ["test-flow"], fork = true} - [tasks.tests-setup] private = true script = [ @@ -31,12 +24,28 @@ script = [ ] script_runner = "@duckscript" +[tasks.tests] +category = "Testing" +dependencies = ["tests-setup"] +description = "Run all tests" +env = {CARGO_MAKE_WORKSPACE_SKIP_MEMBERS = ["**/examples/*"]} +run_task = {name = ["test-flow", "test-with-browser"], fork = true} + +[tasks.build] +command = "cargo" +args = ["build"] + [tasks.test-flow] dependencies = ["test"] private = true -workspace = true [tasks.test] -args = ["test", "--all-targets", "--workspace", "--exclude", "dioxus-mobile"] +dependencies = ["build"] command = "cargo" +args = ["test", "--all-targets", "--workspace", "--exclude", "dioxus-router"] private = true + +[tasks.test-with-browser] +env = { CARGO_MAKE_WORKSPACE_INCLUDE_MEMBERS = ["**/packages/router"] } +private = true +workspace = true diff --git a/packages/router/Makefile.toml b/packages/router/Makefile.toml index 063585c98..42b7f5c9a 100644 --- a/packages/router/Makefile.toml +++ b/packages/router/Makefile.toml @@ -1,4 +1,4 @@ -[tasks.test] +[tasks.test-with-browser] extend = "core::wasm-pack-base" command = "wasm-pack" args = [ @@ -7,4 +7,4 @@ args = [ "--", "--features", "${DIOXUS_TEST_FEATURES}", -] +] \ No newline at end of file From c18670c068d9029e123f0daa450f579f0b7e9632 Mon Sep 17 00:00:00 2001 From: t1m0t Date: Fri, 4 Feb 2022 17:52:47 +0100 Subject: [PATCH 27/28] adding cargo-make and wasm-pack to CI worklow --- .github/workflows/main.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 29319ef30..244313ef9 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -33,12 +33,9 @@ jobs: - uses: Swatinem/rust-cache@v1 - run: sudo apt-get update - run: sudo apt install libwebkit2gtk-4.0-dev libappindicator3-dev libgtk-3-dev - - name: Install cargo-make - uses: actions-rs/cargo@v1 - with: - command: install - args: --debug cargo-make + - uses: davidB/rust-cargo-make@v1 - uses: browser-actions/setup-firefox@latest + - uses: jetli/wasm-pack-action@v0.3.0 - uses: actions-rs/cargo@v1 with: command: make From 841c0c40159972614d37a7bce6efba1e3c22928d Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 4 Feb 2022 18:06:36 -0500 Subject: [PATCH 28/28] docs: combine icons --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 52493b119..cc39ddf82 100644 --- a/README.md +++ b/README.md @@ -26,9 +26,7 @@ CI status - -