diff --git a/.cargo/config.toml b/.cargo/config.toml
new file mode 100644
index 000000000..464189e01
--- /dev/null
+++ b/.cargo/config.toml
@@ -0,0 +1,5 @@
+# All of these variables are used in the `openid_connect_demo` example, they are set here for the CI to work, they are set here because as stated here for now: `https://doc.rust-lang.org/cargo/reference/config.html` the .cargo/config.toml of the inner workspaces are not read when being invoked from the root workspace.
+[env]
+DIOXUS_FRONT_ISSUER_URL = ""
+DIOXUS_FRONT_CLIENT_ID = ""
+DIOXUS_FRONT_URL = ""
diff --git a/.github/workflows/docs stable.yml b/.github/workflows/docs stable.yml
index 642216f0f..523a24067 100644
--- a/.github/workflows/docs stable.yml
+++ b/.github/workflows/docs stable.yml
@@ -33,7 +33,7 @@ jobs:
# cd fermi && mdbook build -d ../nightly/fermi && cd ..
- name: Deploy 🚀
- uses: JamesIves/github-pages-deploy-action@v4.4.3
+ uses: JamesIves/github-pages-deploy-action@v4.5.0
with:
branch: gh-pages # The branch the action should deploy to.
folder: docs/nightly # The folder the action should deploy.
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index 88542aea8..7701209c1 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -39,7 +39,7 @@ jobs:
# cd fermi && mdbook build -d ../nightly/fermi && cd ..
- name: Deploy 🚀
- uses: JamesIves/github-pages-deploy-action@v4.4.3
+ uses: JamesIves/github-pages-deploy-action@v4.5.0
with:
branch: gh-pages # The branch the action should deploy to.
folder: docs/nightly # The folder the action should deploy.
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index b1b1b5c22..8001af650 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -130,7 +130,6 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: ilammy/setup-nasm@v1
-
- name: install stable
uses: dtolnay/rust-toolchain@master
with:
@@ -146,6 +145,11 @@ jobs:
workspaces: core -> ../target
save-if: ${{ matrix.features.key == 'all' }}
+ - name: Install rustfmt
+ run: rustup component add rustfmt
+
+ - uses: actions/checkout@v4
+
- name: test
run: |
${{ env.RUST_CARGO_COMMAND }} ${{ matrix.platform.command }} ${{ matrix.platform.args }} --target ${{ matrix.platform.target }}
diff --git a/.github/workflows/miri.yml b/.github/workflows/miri.yml
index c6974f03f..3864edb32 100644
--- a/.github/workflows/miri.yml
+++ b/.github/workflows/miri.yml
@@ -87,8 +87,7 @@ jobs:
# working-directory: tokio
env:
- # todo: disable memory leaks ignore
- MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-strict-provenance -Zmiri-retag-fields -Zmiri-ignore-leaks
+ MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-strict-provenance -Zmiri-retag-fields
PROPTEST_CASES: 10
# Cache the global cargo directory, but NOT the local `target` directory which
diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml
index 25e26f9a7..49147225c 100644
--- a/.github/workflows/playwright.yml
+++ b/.github/workflows/playwright.yml
@@ -21,7 +21,7 @@ jobs:
# Do our best to cache the toolchain and node install steps
- uses: actions/checkout@v4
- uses: ilammy/setup-nasm@v1
- - uses: actions/setup-node@v3
+ - uses: actions/setup-node@v4
with:
node-version: 16
- name: Install Rust
diff --git a/CHANGELOG.md b/CHANGELOG.md
deleted file mode 100644
index b056a9590..000000000
--- a/CHANGELOG.md
+++ /dev/null
@@ -1,171 +0,0 @@
-# Changelog
-
-All notable changes to this project will be documented in this file.
-
-The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
-and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
-
-## Unreleased
-
-### Commit Statistics
-
-
-
- - 1 commit contributed to the release over the course of 7 calendar days.
- - 0 commits where understood as [conventional](https://www.conventionalcommits.org).
- - 0 issues like '(#ID)' where seen in commit messages
-
-### Commit Details
-
-
-
-view details
-
- * **Uncategorized**
- - Fix various typos and grammar nits ([`9e4ec43`](https://github.comgit//DioxusLabs/dioxus/commit/9e4ec43b1e78d355c56a38e4c092170b2b01b20d))
-
-
-## v0.1.7 (2022-01-08)
-
-### Bug Fixes
-
- - tests
-
-### Commit Statistics
-
-
-
- - 1 commit contributed to the release over the course of 2 calendar days.
- - 1 commit where understood as [conventional](https://www.conventionalcommits.org).
- - 0 issues like '(#ID)' where seen in commit messages
-
-### Commit Details
-
-
-
-view details
-
- * **Uncategorized**
- - tests ([`bd341f5`](https://github.comgit//DioxusLabs/dioxus/commit/bd341f5571580cdf5e495379b49ca988fd9211c3))
-
-
-## v0.1.1 (2021-12-15)
-
-### Documentation
-
- - update local examples and docs to support new syntaxes
- - improve docs
- - big updates to the reference
- - lib.rs docs
- - move around examples
-
-### New Features
-
- - it compiles once more
- - massage lifetimes
- - amazingly awesome error handling
- - svgs working in webview
- - mvoe away from compound context
- - more suspended nodes!
- - add aria
- - enable arbitrary body in rsx! macro
-
-## v0.1.0 (2021-12-15)
-
-### Documentation
-
- - update local examples and docs to support new syntaxes
- - improve docs
- - big updates to the reference
- - lib.rs docs
- - move around examples
-
-### New Features
-
- - it compiles once more
- - massage lifetimes
- - amazingly awesome error handling
- - svgs working in webview
- - mvoe away from compound context
- - more suspended nodes!
- - add aria
- - enable arbitrary body in rsx! macro
-
-## v0.0.1 (2022-01-03)
-
-### Documentation
-
- - improve docs
- - update local examples and docs to support new syntaxes
- - big updates to the reference
- - lib.rs docs
- - move around examples
-
-### New Features
-
- - it compiles once more
- - massage lifetimes
- - amazingly awesome error handling
- - svgs working in webview
- - mvoe away from compound context
- - more suspended nodes!
- - add aria
- - enable arbitrary body in rsx! macro
-
-### Commit Statistics
-
-
-
- - 40 commits contributed to the release over the course of 193 calendar days.
- - 38 commits where understood as [conventional](https://www.conventionalcommits.org).
- - 0 issues like '(#ID)' where seen in commit messages
-
-### Commit Details
-
-
-
-view details
-
- * **Uncategorized**
- - remove runner on hook and then update docs ([`d156045`](https://github.comgit//DioxusLabs/dioxus/commit/d1560450bac55f9566e00e00ea405bd1c70b57e5))
- - polish some more things ([`1496102`](https://github.comgit//DioxusLabs/dioxus/commit/14961023f927b3a8bde83cfc7883aa8bfcca9e85))
- - upgrade hooks ([`b3ac2ee`](https://github.comgit//DioxusLabs/dioxus/commit/b3ac2ee3f76549cd1c7b6f9eee7e3382b07d873c))
- - docs ([`8814977`](https://github.comgit//DioxusLabs/dioxus/commit/8814977eeebe06748a3b9677a8070e42a037ebd7))
- - prepare to change our fragment pattern. Add some more docs ([`2c3a046`](https://github.comgit//DioxusLabs/dioxus/commit/2c3a0464264fa11e8100df025d863931f9606cdb))
- - it compiles once more ([`8acdd2e`](https://github.comgit//DioxusLabs/dioxus/commit/8acdd2ea830b995b608d8bac2ef527db8d40e662))
- - some docs and suspense ([`93d4b8c`](https://github.comgit//DioxusLabs/dioxus/commit/93d4b8ca7c1b133e5dba2a8dc9a310dbe1357001))
- - docs and router ([`a5f05d7`](https://github.comgit//DioxusLabs/dioxus/commit/a5f05d73acc0e47b05cff64a373482519414bc7c))
- - Merge branch 'master' into jk/remove_node_safety ([`db00047`](https://github.comgit//DioxusLabs/dioxus/commit/db0004758c77331cc3b93ea8cf227c060028e12e))
- - improve docs ([`7800744`](https://github.comgit//DioxusLabs/dioxus/commit/78007445f944f259170307d840e0f16242b7b4b6))
- - Various typos/grammar/rewording ([`5747e00`](https://github.comgit//DioxusLabs/dioxus/commit/5747e00b27b1b69c4f9c2820e7e78030feaff71e))
- - bubbling in progress ([`a21020e`](https://github.comgit//DioxusLabs/dioxus/commit/a21020ea575e467ba0d608737269fe1b0792dba7))
- - update local examples and docs to support new syntaxes ([`4de16c4`](https://github.comgit//DioxusLabs/dioxus/commit/4de16c4779648e591b3869b5df31271ae603c812))
- - massage lifetimes ([`9726a06`](https://github.comgit//DioxusLabs/dioxus/commit/9726a065b0d4fb1ede5b53a2ddd58c855e51539f))
- - major cleanups to scheduler ([`2933e4b`](https://github.comgit//DioxusLabs/dioxus/commit/2933e4bc11b3074c2bde8d76ec55364fca841988))
- - threadsafe ([`82953f2`](https://github.comgit//DioxusLabs/dioxus/commit/82953f2ac37913f83a822333acd0c47e20777d31))
- - move macro crate out of core ([`7bdad1e`](https://github.comgit//DioxusLabs/dioxus/commit/7bdad1e2e6f67e74c9f67dde2150140cf8a090e8))
- - amazingly awesome error handling ([`4a72b31`](https://github.comgit//DioxusLabs/dioxus/commit/4a72b3140bd244da602deada1eeecded65ff5848))
- - some ideas ([`05c909f`](https://github.comgit//DioxusLabs/dioxus/commit/05c909f320765aec1bf4c1c55ca59ffd5525a2c7))
- - big updates to the reference ([`583fdfa`](https://github.comgit//DioxusLabs/dioxus/commit/583fdfa5618e11d660985b97e570d4503be2ff49))
- - docs, html! macro, more ([`caf772c`](https://github.comgit//DioxusLabs/dioxus/commit/caf772cf249d2f56c8d0b0fa2737ad48e32c6e82))
- - cleanup workspace ([`8f0bb5d`](https://github.comgit//DioxusLabs/dioxus/commit/8f0bb5dc5bfa3e775af567c4b569622cdd932af1))
- - svgs working in webview ([`3bedcb9`](https://github.comgit//DioxusLabs/dioxus/commit/3bedcb93cacec5bdf134adc38ff02eadbf96c1c6))
- - mvoe away from compound context ([`a2c7d17`](https://github.comgit//DioxusLabs/dioxus/commit/a2c7d17b0595769f60bc1c2bbf7cbe32cec37486))
- - more suspended nodes! ([`de9f61b`](https://github.comgit//DioxusLabs/dioxus/commit/de9f61bcf48c0d6e35e46c337b72a713c9f9f7d2))
- - add aria ([`4091846`](https://github.comgit//DioxusLabs/dioxus/commit/4091846934b4b3b2bc03d3ca8aaf7712aebd4e36))
- - more examples ([`56e7eb8`](https://github.comgit//DioxusLabs/dioxus/commit/56e7eb83a97ebd6d5bcd23464cfb9d718e5ac26d))
- - more refactor for async ([`975fa56`](https://github.comgit//DioxusLabs/dioxus/commit/975fa566f9809f8fa2bb0bdb07fbfc7f855dcaeb))
- - enable arbitrary body in rsx! macro ([`7aec40d`](https://github.comgit//DioxusLabs/dioxus/commit/7aec40d57e78ec13ff3a90ca8149521cbf1d9ff2))
- - move CLI into its own "studio" app ([`fd79335`](https://github.comgit//DioxusLabs/dioxus/commit/fd7933561fe81922e4d5d77f6ac3b6f19efb5a90))
- - move some examples around ([`98a0933`](https://github.comgit//DioxusLabs/dioxus/commit/98a09339fd3190799ea4dd316908f0a53fdf2413))
- - fix issues with lifetimes ([`a38a81e`](https://github.comgit//DioxusLabs/dioxus/commit/a38a81e1290375cae685f7c49d3745e4298fab26))
- - more examples ([`11f89e5`](https://github.comgit//DioxusLabs/dioxus/commit/11f89e5d338d14a7aeece0a6275c24ae65913ce7))
- - lib.rs docs ([`bf21c82`](https://github.comgit//DioxusLabs/dioxus/commit/bf21c82de04e25daee60a06232b9a16b640508f2))
- - rename ctx to cx ([`81382e7`](https://github.comgit//DioxusLabs/dioxus/commit/81382e7044fb3dba61d4abb1e6086b7b29143116))
- - move around examples ([`70cd46d`](https://github.comgit//DioxusLabs/dioxus/commit/70cd46dbb2a689ae2d512e142b8aee9c80798430))
- - start moving events to rc ([`b9ff95f`](https://github.comgit//DioxusLabs/dioxus/commit/b9ff95fa12c46365fe73b64a4926a506d5da2342))
- - rename recoil to atoms ([`36ea39a`](https://github.comgit//DioxusLabs/dioxus/commit/36ea39ae30aa3f1fb2d718c0fdf08850c6bfd3ac))
- - more examples and docs ([`7fbaf69`](https://github.comgit//DioxusLabs/dioxus/commit/7fbaf69cabbdde712bb3fd9e4b2a5dc18b9390e9))
- - docs ([`f5683a2`](https://github.comgit//DioxusLabs/dioxus/commit/f5683a23464992ecace463a61414795b5a2c58c8))
-
-
diff --git a/Cargo.toml b/Cargo.toml
index e6b07eb8c..1772c1f47 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -41,6 +41,7 @@ members = [
"examples/tailwind",
"examples/PWA-example",
"examples/query_segments_demo",
+ "examples/openid_connect_demo",
# Playwright tests
"playwright-tests/liveview",
"playwright-tests/web",
@@ -49,7 +50,7 @@ members = [
exclude = ["examples/mobile_demo"]
[workspace.package]
-version = "0.4.2"
+version = "0.4.3"
# dependencies that are shared across packages
[workspace.dependencies]
@@ -76,7 +77,7 @@ dioxus-native-core = { path = "packages/native-core", version = "0.4.0" }
dioxus-native-core-macro = { path = "packages/native-core-macro", version = "0.4.0" }
rsx-rosetta = { path = "packages/rsx-rosetta", version = "0.4.0" }
dioxus-signals = { path = "packages/signals" }
-generational-box = { path = "packages/generational-box" }
+generational-box = { path = "packages/generational-box", version = "0.4.3" }
dioxus-hot-reload = { path = "packages/hot-reload", version = "0.4.0" }
dioxus-fullstack = { path = "packages/fullstack", version = "0.4.1" }
dioxus_server_macro = { path = "packages/server-macro", version = "0.4.1" }
@@ -87,7 +88,7 @@ slab = "0.4.2"
futures-channel = "0.3.21"
futures-util = { version = "0.3", default-features = false }
rustc-hash = "1.1.0"
-wasm-bindgen = "0.2.87"
+wasm-bindgen = "0.2.88"
html_parser = "0.7.0"
thiserror = "1.0.40"
prettyplease = { package = "prettier-please", version = "0.2", features = [
@@ -98,7 +99,7 @@ prettyplease = { package = "prettier-please", version = "0.2", features = [
# It is not meant to be published, but is used so "cargo run --example XYZ" works properly
[package]
name = "dioxus-examples"
-version = "0.0.0"
+version = "0.4.3"
authors = ["Jonathan Kelley"]
edition = "2021"
description = "Top level crate for the Dioxus repository"
diff --git a/Makefile.toml b/Makefile.toml
index 6f331b98f..1bfb02217 100644
--- a/Makefile.toml
+++ b/Makefile.toml
@@ -24,12 +24,64 @@ script = [
]
script_runner = "@duckscript"
+[tasks.format]
+command = "cargo"
+args = ["fmt", "--all"]
+
+[tasks.check]
+command = "cargo"
+args = ["check", "--workspace", "--examples", "--tests"]
+
+[tasks.clippy]
+command = "cargo"
+args = [
+ "clippy",
+ "--workspace",
+ "--examples",
+ "--tests",
+ "--",
+ "-D",
+ "warnings",
+]
+
+[tasks.tidy]
+category = "Formatting"
+dependencies = ["format", "check", "clippy"]
+description = "Format and Check workspace"
+
+[tasks.install-miri]
+toolchain = "nightly"
+install_crate = { rustup_component_name = "miri", binary = "cargo +nightly miri", test_arg = "--help" }
+private = true
+
+[tasks.miri-native]
+command = "cargo"
+toolchain = "nightly"
+dependencies = ["install-miri"]
+args = [
+ "miri",
+ "test",
+ "--package",
+ "dioxus-native-core",
+ "--test",
+ "miri_native",
+]
+
+[tasks.miri-stress]
+command = "cargo"
+toolchain = "nightly"
+dependencies = ["install-miri"]
+args = ["miri", "test", "--package", "dioxus-core", "--test", "miri_stress"]
+
+[tasks.miri]
+dependencies = ["miri-native", "miri-stress"]
+
[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}
+env = { CARGO_MAKE_WORKSPACE_SKIP_MEMBERS = ["**/examples/*"] }
+run_task = { name = ["test-flow", "test-with-browser"], fork = true }
[tasks.build]
command = "cargo"
@@ -42,10 +94,24 @@ private = true
[tasks.test]
dependencies = ["build"]
command = "cargo"
-args = ["test", "--lib", "--bins", "--tests", "--examples", "--workspace", "--exclude", "dioxus-router", "--exclude", "dioxus-desktop"]
+args = [
+ "test",
+ "--lib",
+ "--bins",
+ "--tests",
+ "--examples",
+ "--workspace",
+ "--exclude",
+ "dioxus-router",
+ "--exclude",
+ "dioxus-desktop",
+]
private = true
[tasks.test-with-browser]
-env = { CARGO_MAKE_WORKSPACE_INCLUDE_MEMBERS = ["**/packages/router", "**/packages/desktop"] }
+env = { CARGO_MAKE_WORKSPACE_INCLUDE_MEMBERS = [
+ "**/packages/router",
+ "**/packages/desktop",
+] }
private = true
workspace = true
diff --git a/README.md b/README.md
index 5b6a99ad3..d1ec98019 100644
--- a/README.md
+++ b/README.md
@@ -159,6 +159,7 @@ So... Dioxus is great, but why won't it work for me?
## Contributing
+- Check out the website [section on contributing](https://dioxuslabs.com/learn/0.4/contributing).
- Report issues on our [issue tracker](https://github.com/dioxuslabs/dioxus/issues).
- Join the discord and ask questions!
diff --git a/examples/calculator.rs b/examples/calculator.rs
index bc7f13459..648ccfbc3 100644
--- a/examples/calculator.rs
+++ b/examples/calculator.rs
@@ -62,6 +62,7 @@ fn app(cx: Scope) -> Element {
div { id: "wrapper",
div { class: "app",
div { class: "calculator",
+ tabindex: "0",
onkeydown: handle_key_down_event,
div { class: "calculator-display", val.to_string() }
div { class: "calculator-keypad",
diff --git a/examples/openid_connect_demo/.gitignore b/examples/openid_connect_demo/.gitignore
new file mode 100644
index 000000000..919c8ee55
--- /dev/null
+++ b/examples/openid_connect_demo/.gitignore
@@ -0,0 +1,3 @@
+/target
+/dist
+.env
diff --git a/examples/openid_connect_demo/Cargo.toml b/examples/openid_connect_demo/Cargo.toml
new file mode 100644
index 000000000..2a1abe173
--- /dev/null
+++ b/examples/openid_connect_demo/Cargo.toml
@@ -0,0 +1,25 @@
+[package]
+name = "openid_auth_demo"
+version = "0.1.0"
+edition = "2021"
+publish = false
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+console_error_panic_hook = "0.1"
+dioxus-logger = "0.4.1"
+dioxus = { path = "../../packages/dioxus", version = "*" }
+dioxus-router = { path = "../../packages/router", version = "*" }
+dioxus-web = { path = "../../packages/web", version = "*" }
+fermi = { path = "../../packages/fermi", version = "*" }
+form_urlencoded = "1.2.0"
+gloo-storage = "0.3.0"
+log = "0.4"
+openidconnect = "3.4.0"
+reqwest = "0.11.20"
+serde = { version = "1.0.188", features = ["derive"] }
+serde_json = "1.0.105"
+thiserror = "1.0.48"
+uuid = "1.4"
+web-sys = { version = "0.3", features = ["Request", "Document"] }
diff --git a/examples/openid_connect_demo/Dioxus.toml b/examples/openid_connect_demo/Dioxus.toml
new file mode 100644
index 000000000..483dfe957
--- /dev/null
+++ b/examples/openid_connect_demo/Dioxus.toml
@@ -0,0 +1,47 @@
+[application]
+
+# dioxus project name
+name = "OpenID Connect authentication demo"
+
+# default platfrom
+# you can also use `dioxus serve/build --platform XXX` to use other platform
+# value: web | desktop
+default_platform = "web"
+
+# Web `build` & `serve` dist path
+out_dir = "dist"
+
+# resource (static) file folder
+asset_dir = "public"
+
+[web.app]
+
+# HTML title tag content
+title = "OpenID Connect authentication demo"
+
+[web.watcher]
+
+index_on_404 = true
+
+watch_path = ["src"]
+
+# include `assets` in web platform
+[web.resource]
+
+# CSS style file
+style = []
+
+# Javascript code file
+script = []
+
+[web.resource.dev]
+
+# Javascript code file
+# serve: [dev-server] only
+script = []
+
+[application.plugins]
+
+available = true
+
+required = []
diff --git a/examples/openid_connect_demo/README.md b/examples/openid_connect_demo/README.md
new file mode 100644
index 000000000..e16467ddf
--- /dev/null
+++ b/examples/openid_connect_demo/README.md
@@ -0,0 +1,13 @@
+# OpenID Connect example to show how to authenticate an user
+
+The environment variables in `.cargo/config.toml` must be set in order for this example to work(if this example is just being compiled from the root workspace, the `.cargo/config.toml` from the root workspace must be set as stated in the [Cargo book](https://doc.rust-lang.org/cargo/reference/config.html)).
+
+Once they are set, you can run `dx serve`
+
+### Environment variables summary
+
+```DIOXUS_FRONT_ISSUER_URL``` The openid-connect's issuer url
+
+```DIOXUS_FRONT_CLIENT_ID``` The openid-connect's client id
+
+```DIOXUS_FRONT_URL``` The url the frontend is supposed to be running on, it could be for example `http://localhost:8080`
\ No newline at end of file
diff --git a/examples/openid_connect_demo/src/constants.rs b/examples/openid_connect_demo/src/constants.rs
new file mode 100644
index 000000000..0a0b4950a
--- /dev/null
+++ b/examples/openid_connect_demo/src/constants.rs
@@ -0,0 +1,2 @@
+pub const DIOXUS_FRONT_AUTH_TOKEN: &str = "auth_token";
+pub const DIOXUS_FRONT_AUTH_REQUEST: &str = "auth_request";
diff --git a/examples/openid_connect_demo/src/errors.rs b/examples/openid_connect_demo/src/errors.rs
new file mode 100644
index 000000000..2b7e3f145
--- /dev/null
+++ b/examples/openid_connect_demo/src/errors.rs
@@ -0,0 +1,20 @@
+use openidconnect::{core::CoreErrorResponseType, url, RequestTokenError, StandardErrorResponse};
+use thiserror::Error;
+
+#[derive(Error, Debug)]
+pub enum Error {
+ #[error("Discovery error: {0}")]
+ OpenIdConnect(
+ #[from] openidconnect::DiscoveryError>,
+ ),
+ #[error("Parsing error: {0}")]
+ Parse(#[from] url::ParseError),
+ #[error("Request token error: {0}")]
+ RequestToken(
+ #[from]
+ RequestTokenError<
+ openidconnect::reqwest::Error,
+ StandardErrorResponse,
+ >,
+ ),
+}
diff --git a/examples/openid_connect_demo/src/main.rs b/examples/openid_connect_demo/src/main.rs
new file mode 100644
index 000000000..7ed8db8a9
--- /dev/null
+++ b/examples/openid_connect_demo/src/main.rs
@@ -0,0 +1,60 @@
+#![allow(non_snake_case)]
+use dioxus::prelude::*;
+use fermi::*;
+use gloo_storage::{LocalStorage, Storage};
+use log::LevelFilter;
+pub(crate) mod constants;
+pub(crate) mod errors;
+pub(crate) mod model;
+pub(crate) mod oidc;
+pub(crate) mod props;
+pub(crate) mod router;
+pub(crate) mod storage;
+pub(crate) mod views;
+use oidc::{AuthRequestState, AuthTokenState};
+use router::Route;
+
+use dioxus_router::prelude::*;
+
+use crate::{
+ constants::{DIOXUS_FRONT_AUTH_REQUEST, DIOXUS_FRONT_AUTH_TOKEN},
+ oidc::ClientState,
+};
+pub static FERMI_CLIENT: fermi::AtomRef = AtomRef(|_| ClientState::default());
+
+// An option is required to prevent the component from being constantly refreshed
+pub static FERMI_AUTH_TOKEN: fermi::AtomRef
");
+ let prop_def_link = format!("{props_def_link}::{arg_name}");
+ let mut arg_doc = format!("- [`{arg_name}`]({prop_def_link}) : `{arg_type}`");
+
+ if let Some(deprecation) = deprecation {
+ arg_doc.push_str("
👎 Deprecated");
+
+ if let Some(since) = deprecation.since {
+ arg_doc.push_str(&format!(" since {since}"));
+ }
+
+ if let Some(note) = deprecation.note {
+ let note = keep_up_to_n_consecutive_chars(¬e, 1, '\n').replace('\n', " ");
+ let note = keep_up_to_n_consecutive_chars(¬e, 1, '\t').replace('\t', " ");
+
+ arg_doc.push_str(&format!(": {note}"));
+ }
+
+ arg_doc.push_str("
");
+
+ if !input_arg_doc.is_empty() {
+ arg_doc.push_str("");
+ }
+ }
+
+ if !input_arg_doc.is_empty() {
+ arg_doc.push_str(&format!("
{input_arg_doc}
"));
+ }
+
+ props_docs.push(parse_quote! {
+ #[doc = #arg_doc]
+ });
+ }
+
+ props_docs
+}
+
+fn get_function(component_body: &ComponentBody) -> ItemFn {
+ let ComponentBody {
+ item_fn,
+ cx_pat_type,
+ ..
+ } = component_body;
+ let ItemFn {
+ attrs: fn_attrs,
+ vis,
+ sig,
+ block: fn_block,
+ } = item_fn;
+ let Signature {
+ inputs,
+ ident: fn_ident,
+ generics,
+ output: fn_output,
+ asyncness,
+ ..
+ } = sig;
+ let Generics { where_clause, .. } = generics;
+
+ let cx_pat = &cx_pat_type.pat;
+ let struct_ident = Ident::new(&format!("{fn_ident}Props"), fn_ident.span());
+
+ // Skip first arg since that's the context
+ let struct_field_names = inputs.iter().skip(1).filter_map(|f| match f {
+ FnArg::Receiver(_) => unreachable!(), // ComponentBody prohibits receiver parameters.
+ FnArg::Typed(pt) => Some(&pt.pat),
+ });
+
+ let first_lifetime = if let Some(GenericParam::Lifetime(lt)) = generics.params.first() {
+ Some(lt)
+ } else {
+ None
+ };
+
+ let (scope_lifetime, fn_generics) = if let Some(lt) = first_lifetime {
+ (quote! { #lt, }, generics.clone())
+ } else {
+ let lifetime: LifetimeParam = parse_quote! { 'a };
+
+ let mut fn_generics = generics.clone();
+ fn_generics
+ .params
+ .insert(0, GenericParam::Lifetime(lifetime.clone()));
+
+ (quote! { #lifetime, }, fn_generics)
+ };
+
+ let generics_no_bounds = {
+ let mut generics = generics.clone();
+ generics.params = generics
+ .params
+ .iter()
+ .map(|it| match it {
+ GenericParam::Type(tp) => {
+ let mut tp = tp.clone();
+ tp.bounds.clear();
+
+ GenericParam::Type(tp)
+ }
+ _ => it.clone(),
+ })
+ .collect();
+
+ generics
+ };
+
+ let props_docs = get_props_docs(fn_ident, inputs.iter().skip(1).collect());
+
+ parse_quote! {
+ #(#fn_attrs)*
+ #(#props_docs)*
+ #asyncness #vis fn #fn_ident #fn_generics (#cx_pat: Scope<#scope_lifetime #struct_ident #generics_no_bounds>) #fn_output
+ #where_clause
+ {
+ let #struct_ident { #(#struct_field_names),* } = cx_pat.props;
+ #fn_block
+ }
+ }
+}
+
+/// Checks if the attribute is a `#[doc]` attribute.
+fn is_attr_doc(attr: &Attribute) -> bool {
+ attr.path() == &parse_quote!(doc)
+}
+
+fn keep_up_to_n_consecutive_chars(
+ input: &str,
+ n_of_consecutive_chars_allowed: usize,
+ target_char: char,
+) -> String {
+ let mut output = String::new();
+ let mut prev_char: Option = None;
+ let mut consecutive_count = 0;
+
+ for c in input.chars() {
+ match prev_char {
+ Some(prev) if c == target_char && prev == target_char => {
+ if consecutive_count < n_of_consecutive_chars_allowed {
+ output.push(c);
+ consecutive_count += 1;
+ }
+ }
+ _ => {
+ output.push(c);
+ prev_char = Some(c);
+ consecutive_count = 1;
+ }
+ }
+ }
+
+ output
+}
diff --git a/packages/core-macro/src/lib.rs b/packages/core-macro/src/lib.rs
index 477c05d76..4686bc797 100644
--- a/packages/core-macro/src/lib.rs
+++ b/packages/core-macro/src/lib.rs
@@ -12,6 +12,7 @@ use syn::{parse_macro_input, Path, Token};
mod component_body;
mod component_body_deserializers;
mod props;
+mod utils;
// mod rsx;
use crate::component_body::ComponentBody;
diff --git a/packages/core-macro/src/props/mod.rs b/packages/core-macro/src/props/mod.rs
index c1931e497..d7a580705 100644
--- a/packages/core-macro/src/props/mod.rs
+++ b/packages/core-macro/src/props/mod.rs
@@ -243,10 +243,6 @@ mod field_info {
}
.into()
}
-
- pub fn type_from_inside_option(&self, check_option_name: bool) -> Option<&syn::Type> {
- type_from_inside_option(self.ty, check_option_name)
- }
}
#[derive(Debug, Default, Clone)]
@@ -551,18 +547,16 @@ mod struct_info {
let generics_with_empty = modify_types_generics_hack(&ty_generics, |args| {
args.insert(0, syn::GenericArgument::Type(empties_tuple.clone().into()));
});
- let phantom_generics = self.generics.params.iter().map(|param| match param {
+ let phantom_generics = self.generics.params.iter().filter_map(|param| match param {
syn::GenericParam::Lifetime(lifetime) => {
let lifetime = &lifetime.lifetime;
- quote!(::core::marker::PhantomData<lifetime ()>)
+ Some(quote!(::core::marker::PhantomData<lifetime ()>))
}
syn::GenericParam::Type(ty) => {
let ty = &ty.ident;
- quote!(::core::marker::PhantomData<#ty>)
- }
- syn::GenericParam::Const(_cnst) => {
- quote!()
+ Some(quote!(::core::marker::PhantomData<#ty>))
}
+ syn::GenericParam::Const(_cnst) => None,
});
let builder_method_doc = match self.builder_attr.builder_method_doc {
Some(ref doc) => quote!(#doc),
@@ -633,7 +627,7 @@ Finally, call `.build()` to create the instance of `{name}`.
Ok(quote! {
impl #impl_generics #name #ty_generics #where_clause {
#[doc = #builder_method_doc]
- #[allow(dead_code)]
+ #[allow(dead_code, clippy::type_complexity)]
#vis fn builder() -> #builder_name #generics_with_empty {
#builder_name {
fields: #empties_tuple,
@@ -701,6 +695,14 @@ Finally, call `.build()` to create the instance of `{name}`.
}
pub fn field_impl(&self, field: &FieldInfo) -> Result {
+ let FieldInfo {
+ name: field_name,
+ ty: field_type,
+ ..
+ } = field;
+ if *field_name == "key" {
+ return Err(Error::new_spanned(field_name, "Naming a prop `key` is not allowed because the name can conflict with the built in key attribute. See https://dioxuslabs.com/learn/0.4/reference/dynamic_rendering#rendering-lists for more information about keys"));
+ }
let StructInfo {
ref builder_name, ..
} = *self;
@@ -715,11 +717,6 @@ Finally, call `.build()` to create the instance of `{name}`.
});
let reconstructing = self.included_fields().map(|f| f.name);
- let FieldInfo {
- name: field_name,
- ty: field_type,
- ..
- } = field;
let mut ty_generics: Vec = self
.generics
.params
@@ -782,31 +779,16 @@ Finally, call `.build()` to create the instance of `{name}`.
None => quote!(),
};
- // NOTE: both auto_into and strip_option affect `arg_type` and `arg_expr`, but the order of
- // nesting is different so we have to do this little dance.
- let arg_type = if field.builder_attr.strip_option {
- field.type_from_inside_option(false).ok_or_else(|| {
- Error::new_spanned(
- field_type,
- "can't `strip_option` - field is not `Option<...>`",
+ let arg_type = field_type;
+ let (arg_type, arg_expr) =
+ if field.builder_attr.auto_into || field.builder_attr.strip_option {
+ (
+ quote!(impl ::core::convert::Into<#arg_type>),
+ quote!(#field_name.into()),
)
- })?
- } else {
- field_type
- };
- let (arg_type, arg_expr) = if field.builder_attr.auto_into {
- (
- quote!(impl ::core::convert::Into<#arg_type>),
- quote!(#field_name.into()),
- )
- } else {
- (quote!(#arg_type), quote!(#field_name))
- };
- let arg_expr = if field.builder_attr.strip_option {
- quote!(Some(#arg_expr))
- } else {
- arg_expr
- };
+ } else {
+ (quote!(#arg_type), quote!(#field_name))
+ };
let repeated_fields_error_type_name = syn::Ident::new(
&format!(
@@ -822,6 +804,7 @@ Finally, call `.build()` to create the instance of `{name}`.
#[allow(dead_code, non_camel_case_types, missing_docs)]
impl #impl_generics #builder_name < #( #ty_generics ),* > #where_clause {
#doc
+ #[allow(clippy::type_complexity)]
pub fn #field_name (self, #field_name: #arg_type) -> #builder_name < #( #target_generics ),* > {
let #field_name = (#arg_expr,);
let ( #(#descructuring,)* ) = self.fields;
@@ -840,6 +823,7 @@ Finally, call `.build()` to create the instance of `{name}`.
#[deprecated(
note = #repeated_fields_error_message
)]
+ #[allow(clippy::type_complexity)]
pub fn #field_name (self, _: #repeated_fields_error_type_name) -> #builder_name < #( #target_generics ),* > {
self
}
diff --git a/packages/core-macro/src/utils.rs b/packages/core-macro/src/utils.rs
new file mode 100644
index 000000000..7e066f773
--- /dev/null
+++ b/packages/core-macro/src/utils.rs
@@ -0,0 +1,129 @@
+use quote::ToTokens;
+use syn::parse::{Parse, ParseStream};
+use syn::spanned::Spanned;
+use syn::{parse_quote, Expr, Lit, Meta, Token, Type};
+
+const FORMATTED_TYPE_START: &str = "static TY_AFTER_HERE:";
+const FORMATTED_TYPE_END: &str = "= todo!();";
+
+/// Attempts to convert the given literal to a string.
+/// Converts ints and floats to their base 10 counterparts.
+///
+/// Returns `None` if the literal is [`Lit::Verbatim`] or if the literal is [`Lit::ByteStr`]
+/// and the byte string could not be converted to UTF-8.
+pub fn lit_to_string(lit: Lit) -> Option {
+ match lit {
+ Lit::Str(l) => Some(l.value()),
+ Lit::ByteStr(l) => String::from_utf8(l.value()).ok(),
+ Lit::Byte(l) => Some(String::from(l.value() as char)),
+ Lit::Char(l) => Some(l.value().to_string()),
+ Lit::Int(l) => Some(l.base10_digits().to_string()),
+ Lit::Float(l) => Some(l.base10_digits().to_string()),
+ Lit::Bool(l) => Some(l.value().to_string()),
+ Lit::Verbatim(_) => None,
+ _ => None,
+ }
+}
+
+pub fn format_type_string(ty: &Type) -> String {
+ let ty_unformatted = ty.into_token_stream().to_string();
+ let ty_unformatted = ty_unformatted.trim();
+
+ // This should always be valid syntax.
+ // Not Rust code, but syntax, which is the only thing that `syn` cares about.
+ let Ok(file_unformatted) = syn::parse_file(&format!(
+ "{FORMATTED_TYPE_START}{ty_unformatted}{FORMATTED_TYPE_END}"
+ )) else {
+ return ty_unformatted.to_string();
+ };
+
+ let file_formatted = prettyplease::unparse(&file_unformatted);
+
+ let file_trimmed = file_formatted.trim();
+ let start_removed = file_trimmed.trim_start_matches(FORMATTED_TYPE_START);
+ let end_removed = start_removed.trim_end_matches(FORMATTED_TYPE_END);
+ let ty_formatted = end_removed.trim();
+
+ ty_formatted.to_string()
+}
+
+/// Represents the `#[deprecated]` attribute.
+///
+/// You can use the [`DeprecatedAttribute::from_meta`] function to try to parse an attribute to this struct.
+#[derive(Default)]
+pub struct DeprecatedAttribute {
+ pub since: Option,
+ pub note: Option,
+}
+
+impl DeprecatedAttribute {
+ /// Returns `None` if the given attribute was not a valid form of the `#[deprecated]` attribute.
+ pub fn from_meta(meta: &Meta) -> syn::Result {
+ if meta.path() != &parse_quote!(deprecated) {
+ return Err(syn::Error::new(
+ meta.span(),
+ "attribute path is not `deprecated`",
+ ));
+ }
+
+ match &meta {
+ Meta::Path(_) => Ok(Self::default()),
+ Meta::NameValue(name_value) => {
+ let Expr::Lit(expr_lit) = &name_value.value else {
+ return Err(syn::Error::new(
+ name_value.span(),
+ "literal in `deprecated` value must be a string",
+ ));
+ };
+
+ Ok(Self {
+ since: None,
+ note: lit_to_string(expr_lit.lit.clone()).map(|s| s.trim().to_string()),
+ })
+ }
+ Meta::List(list) => {
+ let parsed = list.parse_args::()?;
+
+ Ok(Self {
+ since: parsed.since.map(|s| s.trim().to_string()),
+ note: parsed.note.map(|s| s.trim().to_string()),
+ })
+ }
+ }
+ }
+}
+
+mod kw {
+ use syn::custom_keyword;
+ custom_keyword!(since);
+ custom_keyword!(note);
+}
+
+struct DeprecatedAttributeArgsParser {
+ since: Option,
+ note: Option,
+}
+
+impl Parse for DeprecatedAttributeArgsParser {
+ fn parse(input: ParseStream) -> syn::Result {
+ let mut since: Option = None;
+ let mut note: Option = None;
+
+ if input.peek(kw::since) {
+ input.parse::()?;
+ input.parse::()?;
+
+ since = lit_to_string(input.parse()?);
+ }
+
+ if input.peek(Token![,]) && input.peek2(kw::note) {
+ input.parse::()?;
+ input.parse::()?;
+ input.parse::()?;
+
+ note = lit_to_string(input.parse()?);
+ }
+
+ Ok(Self { since, note })
+ }
+}
diff --git a/packages/core/src/arena.rs b/packages/core/src/arena.rs
index b7f768755..785d4b174 100644
--- a/packages/core/src/arena.rs
+++ b/packages/core/src/arena.rs
@@ -164,17 +164,11 @@ impl VirtualDom {
});
// Now that all the references are gone, we can safely drop our own references in our listeners.
- let mut listeners = scope.attributes_to_drop.borrow_mut();
+ let mut listeners = scope.attributes_to_drop_before_render.borrow_mut();
listeners.drain(..).for_each(|listener| {
let listener = unsafe { &*listener };
- match &listener.value {
- AttributeValue::Listener(l) => {
- _ = l.take();
- }
- AttributeValue::Any(a) => {
- _ = a.take();
- }
- _ => (),
+ if let AttributeValue::Listener(l) = &listener.value {
+ _ = l.take();
}
});
}
diff --git a/packages/core/src/bump_frame.rs b/packages/core/src/bump_frame.rs
index 0fe7b3867..6927bd8bc 100644
--- a/packages/core/src/bump_frame.rs
+++ b/packages/core/src/bump_frame.rs
@@ -1,10 +1,16 @@
use crate::nodes::RenderReturn;
+use crate::{Attribute, AttributeValue, VComponent};
use bumpalo::Bump;
+use std::cell::RefCell;
use std::cell::{Cell, UnsafeCell};
pub(crate) struct BumpFrame {
pub bump: UnsafeCell,
pub node: Cell<*const RenderReturn<'static>>,
+
+ // The bump allocator will not call the destructor of the objects it allocated. Attributes and props need to have there destructor called, so we keep a list of them to drop before the bump allocator is reset.
+ pub(crate) attributes_to_drop_before_reset: RefCell>>,
+ pub(crate) props_to_drop_before_reset: RefCell>>,
}
impl BumpFrame {
@@ -13,6 +19,8 @@ impl BumpFrame {
Self {
bump: UnsafeCell::new(bump),
node: Cell::new(std::ptr::null()),
+ attributes_to_drop_before_reset: Default::default(),
+ props_to_drop_before_reset: Default::default(),
}
}
@@ -31,8 +39,38 @@ impl BumpFrame {
unsafe { &*self.bump.get() }
}
- #[allow(clippy::mut_from_ref)]
- pub(crate) unsafe fn bump_mut(&self) -> &mut Bump {
- unsafe { &mut *self.bump.get() }
+ pub(crate) fn add_attribute_to_drop(&self, attribute: *const Attribute<'static>) {
+ self.attributes_to_drop_before_reset
+ .borrow_mut()
+ .push(attribute);
+ }
+
+ /// Reset the bump allocator and drop all the attributes and props that were allocated in it.
+ ///
+ /// # Safety
+ /// The caller must insure that no reference to anything allocated in the bump allocator is available after this function is called.
+ pub(crate) unsafe fn reset(&self) {
+ let mut attributes = self.attributes_to_drop_before_reset.borrow_mut();
+ attributes.drain(..).for_each(|attribute| {
+ let attribute = unsafe { &*attribute };
+ if let AttributeValue::Any(l) = &attribute.value {
+ _ = l.take();
+ }
+ });
+ let mut props = self.props_to_drop_before_reset.borrow_mut();
+ props.drain(..).for_each(|prop| {
+ let prop = unsafe { &*prop };
+ _ = prop.props.borrow_mut().take();
+ });
+ unsafe {
+ let bump = &mut *self.bump.get();
+ bump.reset();
+ }
+ }
+}
+
+impl Drop for BumpFrame {
+ fn drop(&mut self) {
+ unsafe { self.reset() }
}
}
diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs
index 74017db0f..2de862218 100644
--- a/packages/core/src/diff.rs
+++ b/packages/core/src/diff.rs
@@ -560,7 +560,7 @@ impl<'b> VirtualDom {
// If none of the old keys are reused by the new children, then we remove all the remaining old children and
// create the new children afresh.
if shared_keys.is_empty() {
- if old.get(0).is_some() {
+ if old.first().is_some() {
self.remove_nodes(&old[1..]);
self.replace(&old[0], new);
} else {
diff --git a/packages/core/src/events.rs b/packages/core/src/events.rs
index 3b8edb05c..784a8c865 100644
--- a/packages/core/src/events.rs
+++ b/packages/core/src/events.rs
@@ -107,8 +107,6 @@ impl std::fmt::Debug for Event {
}
}
-#[doc(hidden)]
-
/// The callback type generated by the `rsx!` macro when an `on` field is specified for components.
///
/// This makes it possible to pass `move |evt| {}` style closures into components as property fields.
diff --git a/packages/core/src/lazynodes.rs b/packages/core/src/lazynodes.rs
index 811dbb734..3189086b4 100644
--- a/packages/core/src/lazynodes.rs
+++ b/packages/core/src/lazynodes.rs
@@ -23,8 +23,37 @@ use crate::{innerlude::VNode, ScopeState};
///
///
/// ```rust, ignore
-/// LazyNodes::new(|f| f.element("div", [], [], [] None))
+/// LazyNodes::new(|f| {
+/// static TEMPLATE: dioxus::core::Template = dioxus::core::Template {
+/// name: "main.rs:5:5:20", // Source location of the template for hot reloading
+/// roots: &[
+/// dioxus::core::TemplateNode::Element {
+/// tag: dioxus_elements::div::TAG_NAME,
+/// namespace: dioxus_elements::div::NAME_SPACE,
+/// attrs: &[],
+/// children: &[],
+/// },
+/// ],
+/// node_paths: &[],
+/// attr_paths: &[],
+/// };
+/// dioxus::core::VNode {
+/// parent: None,
+/// key: None,
+/// template: std::cell::Cell::new(TEMPLATE),
+/// root_ids: dioxus::core::exports::bumpalo::collections::Vec::with_capacity_in(
+/// 1usize,
+/// f.bump(),
+/// )
+/// .into(),
+/// dynamic_nodes: f.bump().alloc([]),
+/// dynamic_attrs: f.bump().alloc([]),
+/// })
+/// }
/// ```
+///
+/// Find more information about how to construct [`VNode`] at
+
pub struct LazyNodes<'a, 'b> {
#[cfg(not(miri))]
inner: SmallBox VNode<'a> + 'b, S16>,
@@ -61,7 +90,7 @@ impl<'a, 'b> LazyNodes<'a, 'b> {
/// Call the closure with the given factory to produce real [`VNode`].
///
/// ```rust, ignore
- /// let f = LazyNodes::new(move |f| f.element("div", [], [], [] None));
+ /// let f = LazyNodes::new(/* Closure for creating VNodes */);
///
/// let node = f.call(cac);
/// ```
diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs
index ee557af2d..392774767 100644
--- a/packages/core/src/lib.rs
+++ b/packages/core/src/lib.rs
@@ -91,9 +91,9 @@ pub mod prelude {
consume_context, consume_context_from_scope, current_scope_id, fc_to_builder, has_context,
provide_context, provide_context_to_scope, provide_root_context, push_future,
remove_future, schedule_update_any, spawn, spawn_forever, suspend, throw, AnyValue,
- Component, Element, Event, EventHandler, Fragment, IntoAttributeValue, LazyNodes,
- Properties, Runtime, RuntimeGuard, Scope, ScopeId, ScopeState, Scoped, TaskId, Template,
- TemplateAttribute, TemplateNode, Throw, VNode, VirtualDom,
+ Component, Element, Event, EventHandler, Fragment, IntoAttributeValue, IntoDynNode,
+ LazyNodes, Properties, Runtime, RuntimeGuard, Scope, ScopeId, ScopeState, Scoped, TaskId,
+ Template, TemplateAttribute, TemplateNode, Throw, VNode, VirtualDom,
};
}
diff --git a/packages/core/src/mutations.rs b/packages/core/src/mutations.rs
index 27976c157..f33590e2e 100644
--- a/packages/core/src/mutations.rs
+++ b/packages/core/src/mutations.rs
@@ -91,7 +91,7 @@ pub enum Mutation<'a> {
id: ElementId,
},
- /// Create an placeholder int he DOM that we will use later.
+ /// Create a placeholder in the DOM that we will use later.
///
/// Dioxus currently requires the use of placeholders to maintain a re-entrance point for things like list diffing
CreatePlaceholder {
diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs
index c83b1059a..78868d29f 100644
--- a/packages/core/src/nodes.rs
+++ b/packages/core/src/nodes.rs
@@ -707,7 +707,7 @@ impl<'a, 'b> IntoDynNode<'b> for &'a str {
impl IntoDynNode<'_> for String {
fn into_vnode(self, cx: &ScopeState) -> DynamicNode {
DynamicNode::Text(VText {
- value: cx.bump().alloc(self),
+ value: cx.bump().alloc_str(&self),
id: Default::default(),
})
}
@@ -791,6 +791,12 @@ impl<'a> IntoAttributeValue<'a> for &'a str {
}
}
+impl<'a> IntoAttributeValue<'a> for String {
+ fn into_value(self, cx: &'a Bump) -> AttributeValue<'a> {
+ AttributeValue::Text(cx.alloc_str(&self))
+ }
+}
+
impl<'a> IntoAttributeValue<'a> for f64 {
fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
AttributeValue::Float(self)
diff --git a/packages/core/src/scope_arena.rs b/packages/core/src/scope_arena.rs
index 1ed5b816c..397f5328e 100644
--- a/packages/core/src/scope_arena.rs
+++ b/packages/core/src/scope_arena.rs
@@ -35,7 +35,7 @@ impl VirtualDom {
hook_idx: Default::default(),
borrowed_props: Default::default(),
- attributes_to_drop: Default::default(),
+ attributes_to_drop_before_render: Default::default(),
}));
let context =
@@ -54,7 +54,7 @@ impl VirtualDom {
let new_nodes = unsafe {
let scope = &self.scopes[scope_id.0];
- scope.previous_frame().bump_mut().reset();
+ scope.previous_frame().reset();
scope.context().suspended.set(false);
diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs
index 996529a0d..f0bbd776c 100644
--- a/packages/core/src/scopes.rs
+++ b/packages/core/src/scopes.rs
@@ -94,7 +94,7 @@ pub struct ScopeState {
pub(crate) hook_idx: Cell,
pub(crate) borrowed_props: RefCell>>,
- pub(crate) attributes_to_drop: RefCell>>,
+ pub(crate) attributes_to_drop_before_render: RefCell>>,
pub(crate) props: Option>>,
}
@@ -348,25 +348,36 @@ impl<'src> ScopeState {
pub fn render(&'src self, rsx: LazyNodes<'src, '_>) -> Element<'src> {
let element = rsx.call(self);
- let mut listeners = self.attributes_to_drop.borrow_mut();
+ let mut listeners = self.attributes_to_drop_before_render.borrow_mut();
for attr in element.dynamic_attrs {
match attr.value {
- AttributeValue::Any(_) | AttributeValue::Listener(_) => {
+ // We need to drop listeners before the next render because they may borrow data from the borrowed props which will be dropped
+ AttributeValue::Listener(_) => {
let unbounded = unsafe { std::mem::transmute(attr as *const Attribute) };
listeners.push(unbounded);
}
+ // We need to drop any values manually to make sure that their drop implementation is called before the next render
+ AttributeValue::Any(_) => {
+ let unbounded = unsafe { std::mem::transmute(attr as *const Attribute) };
+ self.previous_frame().add_attribute_to_drop(unbounded);
+ }
_ => (),
}
}
let mut props = self.borrowed_props.borrow_mut();
+ let mut drop_props = self
+ .previous_frame()
+ .props_to_drop_before_reset
+ .borrow_mut();
for node in element.dynamic_nodes {
if let DynamicNode::Component(comp) = node {
+ let unbounded = unsafe { std::mem::transmute(comp as *const VComponent) };
if !comp.static_props {
- let unbounded = unsafe { std::mem::transmute(comp as *const VComponent) };
props.push(unbounded);
}
+ drop_props.push(unbounded);
}
}
diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml
index d066347e8..d722ebb22 100644
--- a/packages/desktop/Cargo.toml
+++ b/packages/desktop/Cargo.toml
@@ -60,6 +60,7 @@ devtools = ["wry/devtools"]
tray = ["wry/tray"]
dox = ["wry/dox"]
hot-reload = ["dioxus-hot-reload"]
+gnu = []
[package.metadata.docs.rs]
default-features = false
diff --git a/packages/desktop/build.rs b/packages/desktop/build.rs
new file mode 100644
index 000000000..2061732e8
--- /dev/null
+++ b/packages/desktop/build.rs
@@ -0,0 +1,9 @@
+fn main() {
+ // WARN about wry support on windows gnu targets. GNU windows targets don't work well in wry currently
+ if std::env::var("CARGO_CFG_WINDOWS").is_ok()
+ && std::env::var("CARGO_CFG_TARGET_ENV").unwrap() == "gnu"
+ && !cfg!(feature = "gnu")
+ {
+ println!("cargo:warning=GNU windows targets have some limitations within Wry. Using the MSVC windows toolchain is recommended. If you would like to use continue using GNU, you can read https://github.com/wravery/webview2-rs#cross-compilation and disable this warning by adding the gnu feature to dioxus-desktop in your Cargo.toml")
+ }
+}
diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs
index f19c35e76..029b4cab6 100644
--- a/packages/desktop/src/lib.rs
+++ b/packages/desktop/src/lib.rs
@@ -55,7 +55,7 @@ use wry::{application::window::WindowId, webview::WebContext};
///
/// This function will start a multithreaded Tokio runtime as well the WebView event loop.
///
-/// ```rust, ignore
+/// ```rust, no_run
/// use dioxus::prelude::*;
///
/// fn main() {
@@ -78,11 +78,12 @@ pub fn launch(root: Component) {
///
/// You can configure the WebView window with a configuration closure
///
-/// ```rust, ignore
+/// ```rust, no_run
/// use dioxus::prelude::*;
+/// use dioxus_desktop::*;
///
/// fn main() {
-/// dioxus_desktop::launch_cfg(app, |c| c.with_window(|w| w.with_title("My App")));
+/// dioxus_desktop::launch_cfg(app, Config::default().with_window(WindowBuilder::new().with_title("My App")));
/// }
///
/// fn app(cx: Scope) -> Element {
@@ -101,8 +102,9 @@ pub fn launch_cfg(root: Component, config_builder: Config) {
///
/// You can configure the WebView window with a configuration closure
///
-/// ```rust, ignore
+/// ```rust, no_run
/// use dioxus::prelude::*;
+/// use dioxus_desktop::Config;
///
/// fn main() {
/// dioxus_desktop::launch_with_props(app, AppProps { name: "asd" }, Config::default());
@@ -165,6 +167,7 @@ pub fn launch_with_props(root: Component
, props: P, cfg: Config)
// iOS panics if we create a window before the event loop is started
let props = Rc::new(Cell::new(Some(props)));
let cfg = Rc::new(Cell::new(Some(cfg)));
+ let mut is_visible_before_start = true;
event_loop.run(move |window_event, event_loop, control_flow| {
*control_flow = ControlFlow::Wait;
@@ -214,6 +217,8 @@ pub fn launch_with_props(root: Component
, props: P, cfg: Config)
// Create a dom
let dom = VirtualDom::new_with_props(root, props);
+ is_visible_before_start = cfg.window.window.visible;
+
let handler = create_new_window(
cfg,
event_loop,
@@ -327,6 +332,10 @@ pub fn launch_with_props(root: Component
, props: P, cfg: Config)
EventData::Ipc(msg) if msg.method() == "initialize" => {
let view = webviews.get_mut(&event.1).unwrap();
send_edits(view.dom.rebuild(), &view.desktop_context.webview);
+ view.desktop_context
+ .webview
+ .window()
+ .set_visible(is_visible_before_start);
}
EventData::Ipc(msg) if msg.method() == "browser_open" => {
diff --git a/packages/desktop/src/webview.rs b/packages/desktop/src/webview.rs
index 47ff8e1f5..7f12562ce 100644
--- a/packages/desktop/src/webview.rs
+++ b/packages/desktop/src/webview.rs
@@ -13,7 +13,7 @@ pub fn build(
proxy: EventLoopProxy,
) -> (WebView, WebContext) {
let builder = cfg.window.clone();
- let window = builder.build(event_loop).unwrap();
+ let window = builder.with_visible(false).build(event_loop).unwrap();
let file_handler = cfg.file_drop_handler.take();
let custom_head = cfg.custom_head.clone();
let index_file = cfg.custom_index.clone();
diff --git a/packages/dioxus-tui/examples/colorpicker.rs b/packages/dioxus-tui/examples/colorpicker.rs
index 00f8ef7e0..01cc38cda 100644
--- a/packages/dioxus-tui/examples/colorpicker.rs
+++ b/packages/dioxus-tui/examples/colorpicker.rs
@@ -15,21 +15,21 @@ fn app(cx: Scope) -> Element {
let mapping: DioxusElementToNodeId = cx.consume_context().unwrap();
// disable templates so that every node has an id and can be queried
cx.render(rsx! {
- div{
+ div {
width: "100%",
background_color: "hsl({hue}, 70%, {brightness}%)",
onmousemove: move |evt| {
if let RenderReturn::Ready(node) = cx.root_node() {
- if let Some(id) = node.root_ids.borrow().get(0).cloned() {
+ if let Some(id) = node.root_ids.borrow().first().cloned() {
let node = tui_query.get(mapping.get_node_id(id).unwrap());
- let Size{width, height} = node.size().unwrap();
+ let Size { width, height } = node.size().unwrap();
let pos = evt.inner().element_coordinates();
- hue.set((pos.x as f32/width as f32)*255.0);
- brightness.set((pos.y as f32/height as f32)*100.0);
+ hue.set((pos.x as f32 / width as f32) * 255.0);
+ brightness.set((pos.y as f32 / height as f32) * 100.0);
}
}
},
- "hsl({hue}, 70%, {brightness}%)",
+ "hsl({hue}, 70%, {brightness}%)"
}
})
}
diff --git a/packages/extension/src/lib.rs b/packages/extension/src/lib.rs
index abcdaf598..fb9cca8a9 100644
--- a/packages/extension/src/lib.rs
+++ b/packages/extension/src/lib.rs
@@ -1,17 +1,39 @@
//! This file exports functions into the vscode extension
-use dioxus_autofmt::FormattedBlock;
+use dioxus_autofmt::{FormattedBlock, IndentOptions, IndentType};
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
-pub fn format_rsx(raw: String) -> String {
- let block = dioxus_autofmt::fmt_block(&raw, 0);
+pub fn format_rsx(raw: String, use_tabs: bool, indent_size: usize) -> String {
+ let block = dioxus_autofmt::fmt_block(
+ &raw,
+ 0,
+ IndentOptions::new(
+ if use_tabs {
+ IndentType::Tabs
+ } else {
+ IndentType::Spaces
+ },
+ indent_size,
+ ),
+ );
block.unwrap()
}
#[wasm_bindgen]
-pub fn format_selection(raw: String) -> String {
- let block = dioxus_autofmt::fmt_block(&raw, 0);
+pub fn format_selection(raw: String, use_tabs: bool, indent_size: usize) -> String {
+ let block = dioxus_autofmt::fmt_block(
+ &raw,
+ 0,
+ IndentOptions::new(
+ if use_tabs {
+ IndentType::Tabs
+ } else {
+ IndentType::Spaces
+ },
+ indent_size,
+ ),
+ );
block.unwrap()
}
@@ -35,8 +57,18 @@ impl FormatBlockInstance {
}
#[wasm_bindgen]
-pub fn format_file(contents: String) -> FormatBlockInstance {
- let _edits = dioxus_autofmt::fmt_file(&contents);
+pub fn format_file(contents: String, use_tabs: bool, indent_size: usize) -> FormatBlockInstance {
+ let _edits = dioxus_autofmt::fmt_file(
+ &contents,
+ IndentOptions::new(
+ if use_tabs {
+ IndentType::Tabs
+ } else {
+ IndentType::Spaces
+ },
+ indent_size,
+ ),
+ );
let out = dioxus_autofmt::apply_formats(&contents, _edits.clone());
FormatBlockInstance { new: out, _edits }
}
diff --git a/packages/extension/src/main.ts b/packages/extension/src/main.ts
index fc12457ad..76b7261f1 100644
--- a/packages/extension/src/main.ts
+++ b/packages/extension/src/main.ts
@@ -90,7 +90,13 @@ function fmtDocument(document: vscode.TextDocument) {
if (!editor) return; // Need an editor to apply text edits.
const contents = editor.document.getText();
- const formatted = dioxus.format_file(contents);
+ let tabSize: number;
+ if (typeof editor.options.tabSize === 'number') {
+ tabSize = editor.options.tabSize;
+ } else {
+ tabSize = 4;
+ }
+ const formatted = dioxus.format_file(contents, !editor.options.insertSpaces, tabSize);
// Replace the entire text document
// Yes, this is a bit heavy handed, but the dioxus side doesn't know the line/col scheme that vscode is using
diff --git a/packages/fermi/src/callback.rs b/packages/fermi/src/callback.rs
index a7ca9155f..bb9f61bf2 100644
--- a/packages/fermi/src/callback.rs
+++ b/packages/fermi/src/callback.rs
@@ -28,6 +28,7 @@ impl CallbackApi {
}
}
+#[must_use]
pub fn use_atom_context(cx: &ScopeState) -> &CallbackApi {
todo!()
}
diff --git a/packages/fermi/src/hooks/atom_ref.rs b/packages/fermi/src/hooks/atom_ref.rs
index 74dd9268b..8dbeb2c4d 100644
--- a/packages/fermi/src/hooks/atom_ref.rs
+++ b/packages/fermi/src/hooks/atom_ref.rs
@@ -13,6 +13,7 @@ use std::{
///
///
///
+#[must_use]
pub fn use_atom_ref<'a, T: 'static>(
cx: &'a ScopeState,
atom: &'static AtomRef,
diff --git a/packages/fermi/src/hooks/atom_root.rs b/packages/fermi/src/hooks/atom_root.rs
index 24fd70093..468f5f980 100644
--- a/packages/fermi/src/hooks/atom_root.rs
+++ b/packages/fermi/src/hooks/atom_root.rs
@@ -7,6 +7,6 @@ use dioxus_core::ScopeState;
pub fn use_atom_root(cx: &ScopeState) -> &Rc {
cx.use_hook(|| match cx.consume_context::>() {
Some(root) => root,
- None => panic!("No atom root found in context. Did you forget place an AtomRoot component at the top of your app?"),
+ None => panic!("No atom root found in context. Did you forget to call use_init_atom_root at the top of your app?"),
})
}
diff --git a/packages/fermi/src/hooks/read.rs b/packages/fermi/src/hooks/read.rs
index 57a544e93..c2538c74d 100644
--- a/packages/fermi/src/hooks/read.rs
+++ b/packages/fermi/src/hooks/read.rs
@@ -2,10 +2,12 @@ use crate::{use_atom_root, AtomId, AtomRoot, Readable};
use dioxus_core::{ScopeId, ScopeState};
use std::rc::Rc;
+#[must_use]
pub fn use_read(cx: &ScopeState, f: impl Readable) -> &V {
use_read_rc(cx, f).as_ref()
}
+#[must_use]
pub fn use_read_rc(cx: &ScopeState, f: impl Readable) -> &Rc {
let root = use_atom_root(cx);
diff --git a/packages/fermi/src/hooks/set.rs b/packages/fermi/src/hooks/set.rs
index 5e5141227..89fe48b0f 100644
--- a/packages/fermi/src/hooks/set.rs
+++ b/packages/fermi/src/hooks/set.rs
@@ -2,6 +2,7 @@ use crate::{use_atom_root, Writable};
use dioxus_core::ScopeState;
use std::rc::Rc;
+#[must_use]
pub fn use_set(cx: &ScopeState, f: impl Writable) -> &Rc {
let root = use_atom_root(cx);
cx.use_hook(|| {
diff --git a/packages/fermi/src/hooks/state.rs b/packages/fermi/src/hooks/state.rs
index 071b18059..7d473588a 100644
--- a/packages/fermi/src/hooks/state.rs
+++ b/packages/fermi/src/hooks/state.rs
@@ -30,6 +30,7 @@ use std::{
/// ))
/// }
/// ```
+#[must_use]
pub fn use_atom_state(cx: &ScopeState, f: impl Writable) -> &AtomState {
let root = crate::use_atom_root(cx);
@@ -85,7 +86,9 @@ impl AtomState {
/// ```
#[must_use]
pub fn current(&self) -> Rc {
- self.value.as_ref().unwrap().clone()
+ let atoms = self.root.atoms.borrow();
+ let slot = atoms.get(&self.id).unwrap();
+ slot.value.clone().downcast().unwrap()
}
/// Get the `setter` function directly without the `AtomState` wrapper.
diff --git a/packages/fermi/src/lib.rs b/packages/fermi/src/lib.rs
index 52bf4cd09..dc5ccf010 100644
--- a/packages/fermi/src/lib.rs
+++ b/packages/fermi/src/lib.rs
@@ -22,8 +22,6 @@ mod atoms {
pub use atom::*;
pub use atomfamily::*;
pub use atomref::*;
- pub use selector::*;
- pub use selectorfamily::*;
}
pub mod hooks {
diff --git a/packages/fullstack/Cargo.toml b/packages/fullstack/Cargo.toml
index 9051b3994..2867c1c72 100644
--- a/packages/fullstack/Cargo.toml
+++ b/packages/fullstack/Cargo.toml
@@ -11,7 +11,7 @@ keywords = ["ui", "gui", "react", "ssr", "fullstack"]
[dependencies]
# server functions
-server_fn = { version = "0.4.6", default-features = false }
+server_fn = { version = "0.5.2", default-features = false }
dioxus_server_macro = { workspace = true }
# warp
diff --git a/packages/fullstack/examples/axum-hello-world/src/main.rs b/packages/fullstack/examples/axum-hello-world/src/main.rs
index 8f1d3c8c6..64e5b44a0 100644
--- a/packages/fullstack/examples/axum-hello-world/src/main.rs
+++ b/packages/fullstack/examples/axum-hello-world/src/main.rs
@@ -24,6 +24,7 @@ fn app(cx: Scope) -> Element {
let mut count = use_state(cx, || 0);
let text = use_state(cx, || "...".to_string());
+ let eval = use_eval(cx);
cx.render(rsx! {
div {
diff --git a/packages/fullstack/src/adapters/axum_adapter.rs b/packages/fullstack/src/adapters/axum_adapter.rs
index baa10e3a5..9f9f3b11e 100644
--- a/packages/fullstack/src/adapters/axum_adapter.rs
+++ b/packages/fullstack/src/adapters/axum_adapter.rs
@@ -372,15 +372,65 @@ fn apply_request_parts_to_response(
}
}
-/// SSR renderer handler for Axum
-pub async fn render_handler(
- State((cfg, ssr_state)): State<(ServeConfig
, SSRState)>,
+/// SSR renderer handler for Axum with added context injection.
+///
+/// # Example
+/// ```rust,no_run
+/// #![allow(non_snake_case)]
+/// use std::sync::{Arc, Mutex};
+///
+/// use axum::routing::get;
+/// use dioxus::prelude::*;
+/// use dioxus_fullstack::{axum_adapter::render_handler_with_context, prelude::*};
+///
+/// fn app(cx: Scope) -> Element {
+/// render! {
+/// "hello!"
+/// }
+/// }
+///
+/// #[tokio::main]
+/// async fn main() {
+/// let cfg = ServeConfigBuilder::new(app, ())
+/// .assets_path("dist")
+/// .build();
+/// let ssr_state = SSRState::new(&cfg);
+///
+/// // This could be any state you want to be accessible from your server
+/// // functions using `[DioxusServerContext::get]`.
+/// let state = Arc::new(Mutex::new("state".to_string()));
+///
+/// let addr = std::net::SocketAddr::from(([127, 0, 0, 1], 8080));
+/// axum::Server::bind(&addr)
+/// .serve(
+/// axum::Router::new()
+/// // Register server functions, etc.
+/// // Note you probably want to use `register_server_fns_with_handler`
+/// // to inject the context into server functions running outside
+/// // of an SSR render context.
+/// .fallback(get(render_handler_with_context).with_state((
+/// move |ctx| ctx.insert(state.clone()).unwrap(),
+/// cfg,
+/// ssr_state,
+/// )))
+/// .into_make_service(),
+/// )
+/// .await
+/// .unwrap();
+/// }
+/// ```
+pub async fn render_handler_with_context<
+ P: Clone + serde::Serialize + Send + Sync + 'static,
+ F: FnMut(&mut DioxusServerContext),
+>(
+ State((mut inject_context, cfg, ssr_state)): State<(F, ServeConfig
, SSRState)>,
request: Request
,
) -> impl IntoResponse {
let (parts, _) = request.into_parts();
let url = parts.uri.path_and_query().unwrap().to_string();
let parts: Arc> = Arc::new(RwLock::new(parts.into()));
- let server_context = DioxusServerContext::new(parts.clone());
+ let mut server_context = DioxusServerContext::new(parts.clone());
+ inject_context(&mut server_context);
match ssr_state.render(url, &cfg, &server_context).await {
Ok(rendered) => {
@@ -398,6 +448,14 @@ pub async fn render_handler
}
}
+/// SSR renderer handler for Axum
+pub async fn render_handler(
+ State((cfg, ssr_state)): State<(ServeConfig
, SSRState)>,
+ request: Request
,
+) -> impl IntoResponse {
+ render_handler_with_context(State((|_: &mut _| (), cfg, ssr_state)), request).await
+}
+
fn report_err(e: E) -> Response {
Response::builder()
.status(StatusCode::INTERNAL_SERVER_ERROR)
diff --git a/packages/fullstack/src/adapters/mod.rs b/packages/fullstack/src/adapters/mod.rs
index 98d190919..d368b9c73 100644
--- a/packages/fullstack/src/adapters/mod.rs
+++ b/packages/fullstack/src/adapters/mod.rs
@@ -89,26 +89,26 @@ impl Service for ServerFnHandler {
let parts = Arc::new(RwLock::new(parts));
// Because the future returned by `server_fn_handler` is `Send`, and the future returned by this function must be send, we need to spawn a new runtime
- let (resp_tx, resp_rx) = tokio::sync::oneshot::channel();
let pool = get_local_pool();
- pool.spawn_pinned({
- let function = function.clone();
- let mut server_context = server_context.clone();
- server_context.parts = parts;
- move || async move {
- let data = match function.encoding() {
- Encoding::Url | Encoding::Cbor => &body,
- Encoding::GetJSON | Encoding::GetCBOR => &query,
- };
- let server_function_future = function.call((), data);
- let server_function_future =
- ProvideServerContext::new(server_function_future, server_context.clone());
- let resp = server_function_future.await;
-
- resp_tx.send(resp).unwrap();
- }
- });
- let result = resp_rx.await.unwrap();
+ let result = pool
+ .spawn_pinned({
+ let function = function.clone();
+ let mut server_context = server_context.clone();
+ server_context.parts = parts;
+ move || async move {
+ let data = match function.encoding() {
+ Encoding::Url | Encoding::Cbor => &body,
+ Encoding::GetJSON | Encoding::GetCBOR => &query,
+ };
+ let server_function_future = function.call((), data);
+ let server_function_future = ProvideServerContext::new(
+ server_function_future,
+ server_context.clone(),
+ );
+ server_function_future.await
+ }
+ })
+ .await?;
let mut res = http::Response::builder();
// Set the headers from the server context
diff --git a/packages/fullstack/src/hooks/server_future.rs b/packages/fullstack/src/hooks/server_future.rs
index e4d89480e..f8eaef963 100644
--- a/packages/fullstack/src/hooks/server_future.rs
+++ b/packages/fullstack/src/hooks/server_future.rs
@@ -22,6 +22,7 @@ use std::sync::Arc;
/// will be allowed to continue
///
/// - dependencies: a tuple of references to values that are PartialEq + Clone
+#[must_use = "Consider using `cx.spawn` to run a future without reading its value"]
pub fn use_server_future(
cx: &ScopeState,
dependencies: D,
diff --git a/packages/fullstack/src/launch.rs b/packages/fullstack/src/launch.rs
index c982a1c90..bd145f363 100644
--- a/packages/fullstack/src/launch.rs
+++ b/packages/fullstack/src/launch.rs
@@ -121,8 +121,15 @@ impl BoxedService;
}
@@ -17,7 +19,9 @@ where
}
}
+/// A service is a function that takes a request and returns an async response
pub trait Service {
+ /// Run the service and produce a future that resolves to a response
fn run(
&mut self,
req: http::Request,
@@ -55,6 +59,7 @@ where
}
}
+/// A boxed service is a type-erased service that can be used without knowing the underlying type
pub struct BoxedService(pub Box);
impl tower::Service> for BoxedService {
diff --git a/packages/fullstack/src/lib.rs b/packages/fullstack/src/lib.rs
index 96cd4b17b..8b166757f 100644
--- a/packages/fullstack/src/lib.rs
+++ b/packages/fullstack/src/lib.rs
@@ -41,6 +41,8 @@ pub mod prelude {
#[cfg(not(feature = "ssr"))]
pub use crate::html_storage::deserialize::get_root_props_from_document;
pub use crate::launch::LaunchBuilder;
+ #[cfg(feature = "ssr")]
+ pub use crate::layer::{Layer, Service};
#[cfg(all(feature = "ssr", feature = "router"))]
pub use crate::render::pre_cache_static_routes_with_props;
#[cfg(feature = "ssr")]
@@ -65,3 +67,10 @@ pub mod prelude {
pub use hooks::{server_cached::server_cached, server_future::use_server_future};
}
+
+// Warn users about overlapping features
+#[cfg(all(feature = "ssr", feature = "web"))]
+compile_error!("The `ssr` feature (enabled by `warp`, `axum`, or `salvo`) and `web` feature are overlapping. Please choose one or the other.");
+
+#[cfg(all(feature = "ssr", feature = "desktop"))]
+compile_error!("The `ssr` feature (enabled by `warp`, `axum`, or `salvo`) and `desktop` feature are overlapping. Please choose one or the other.");
diff --git a/packages/fullstack/src/render.rs b/packages/fullstack/src/render.rs
index cda41f82c..886a5bd99 100644
--- a/packages/fullstack/src/render.rs
+++ b/packages/fullstack/src/render.rs
@@ -45,6 +45,8 @@ impl SsrRendererPool {
.expect("couldn't spawn runtime")
.block_on(async move {
let mut vdom = VirtualDom::new_with_props(component, props);
+ // Make sure the evaluator is initialized
+ dioxus_ssr::eval::init_eval(vdom.base_scope());
let mut to = WriteBuffer { buffer: Vec::new() };
// before polling the future, we need to set the context
let prev_context =
diff --git a/packages/fullstack/src/router.rs b/packages/fullstack/src/router.rs
index b556aa000..b8d93f0e0 100644
--- a/packages/fullstack/src/router.rs
+++ b/packages/fullstack/src/router.rs
@@ -53,7 +53,7 @@ fn default_external_navigation_handler() -> fn(Scope) -> Element {
dioxus_router::prelude::FailureExternalNavigation
}
-/// The configeration for the router
+/// The configuration for the router
#[derive(Props, serde::Serialize, serde::Deserialize)]
pub struct FullstackRouterConfig
where
diff --git a/packages/fullstack/src/server_fn.rs b/packages/fullstack/src/server_fn.rs
index 539a0d70a..fd0639372 100644
--- a/packages/fullstack/src/server_fn.rs
+++ b/packages/fullstack/src/server_fn.rs
@@ -125,14 +125,6 @@ impl server_fn::ServerFunctionRegistry<()> for DioxusServerFnRegistry {
}
}
- fn register(
- url: &'static str,
- server_function: ServerFunction,
- encoding: server_fn::Encoding,
- ) -> Result<(), Self::Error> {
- Self::register_explicit("", url, server_function, encoding)
- }
-
/// Returns the server function registered at the given URL, or `None` if no function is registered at that URL.
fn get(url: &str) -> Option> {
REGISTERED_SERVER_FUNCTIONS
diff --git a/packages/generational-box/Cargo.toml b/packages/generational-box/Cargo.toml
index 6c5a5a841..64d36bd13 100644
--- a/packages/generational-box/Cargo.toml
+++ b/packages/generational-box/Cargo.toml
@@ -1,9 +1,12 @@
[package]
name = "generational-box"
authors = ["Evan Almloff"]
-version = "0.0.0"
+version = "0.4.3"
edition = "2018"
-
+description = "A box backed by a generational runtime"
+license = "MIT OR Apache-2.0"
+repository = "https://github.com/DioxusLabs/dioxus/"
+keywords = ["generational", "box", "memory", "allocator"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
@@ -15,3 +18,5 @@ rand = "0.8.5"
[features]
default = ["check_generation"]
check_generation = []
+debug_borrows = []
+debug_ownership = []
diff --git a/packages/generational-box/README.md b/packages/generational-box/README.md
index 63c539e08..95aab8402 100644
--- a/packages/generational-box/README.md
+++ b/packages/generational-box/README.md
@@ -11,6 +11,8 @@ Three main types manage state in Generational Box:
Example:
```rust
+use generational_box::Store;
+
// Create a store for this thread
let store = Store::default();
@@ -31,4 +33,4 @@ let store = Store::default();
## How it works
-Internally
\ No newline at end of file
+Internally, `generational-box` creates an arena of generational RefCell's that are recyled when the owner is dropped. You can think of the cells as something like `&'static RefCell>` with a generational check to make recyling a cell easier to debug. Then GenerationalBox's are `Copy` because the `&'static` pointer is `Copy`
diff --git a/packages/generational-box/src/lib.rs b/packages/generational-box/src/lib.rs
index 60bd41419..f56904ede 100644
--- a/packages/generational-box/src/lib.rs
+++ b/packages/generational-box/src/lib.rs
@@ -2,9 +2,12 @@
#![warn(missing_docs)]
use std::{
+ any::Any,
cell::{Cell, Ref, RefCell, RefMut},
- fmt::Debug,
+ error::Error,
+ fmt::{Debug, Display},
marker::PhantomData,
+ ops::{Deref, DerefMut},
rc::Rc,
};
@@ -29,12 +32,12 @@ fn reused() {
let first_ptr;
{
let owner = store.owner();
- first_ptr = owner.insert(1).raw.data.as_ptr();
+ first_ptr = owner.insert(1).raw.0.data.as_ptr();
drop(owner);
}
{
let owner = store.owner();
- let second_ptr = owner.insert(1234).raw.data.as_ptr();
+ let second_ptr = owner.insert(1234).raw.0.data.as_ptr();
assert_eq!(first_ptr, second_ptr);
drop(owner);
}
@@ -53,7 +56,10 @@ fn leaking_is_ok() {
// don't drop the owner
std::mem::forget(owner);
}
- assert_eq!(key.try_read().as_deref(), Some(&"hello world".to_string()));
+ assert_eq!(
+ key.try_read().as_deref().unwrap(),
+ &"hello world".to_string()
+ );
}
#[test]
@@ -68,7 +74,7 @@ fn drops() {
key = owner.insert(data);
// drop the owner
}
- assert!(key.try_read().is_none());
+ assert!(key.try_read().is_err());
}
#[test]
@@ -129,7 +135,7 @@ fn fuzz() {
println!("{:?}", path);
for key in valid_keys.iter() {
let value = key.read();
- println!("{:?}", value);
+ println!("{:?}", &*value);
assert!(value.starts_with("hello world"));
}
#[cfg(any(debug_assertions, feature = "check_generation"))]
@@ -153,6 +159,8 @@ pub struct GenerationalBox {
raw: MemoryLocation,
#[cfg(any(debug_assertions, feature = "check_generation"))]
generation: u32,
+ #[cfg(any(debug_assertions, feature = "debug_ownership"))]
+ created_at: &'static std::panic::Location<'static>,
_marker: PhantomData,
}
@@ -161,7 +169,7 @@ impl Debug for GenerationalBox {
#[cfg(any(debug_assertions, feature = "check_generation"))]
f.write_fmt(format_args!(
"{:?}@{:?}",
- self.raw.data.as_ptr(),
+ self.raw.0.data.as_ptr(),
self.generation
))?;
#[cfg(not(any(debug_assertions, feature = "check_generation")))]
@@ -175,7 +183,7 @@ impl GenerationalBox {
fn validate(&self) -> bool {
#[cfg(any(debug_assertions, feature = "check_generation"))]
{
- self.raw.generation.get() == self.generation
+ self.raw.0.generation.get() == self.generation
}
#[cfg(not(any(debug_assertions, feature = "check_generation")))]
{
@@ -184,43 +192,51 @@ impl GenerationalBox {
}
/// Try to read the value. Returns None if the value is no longer valid.
- pub fn try_read(&self) -> Option> {
- self.validate()
- .then(|| {
- Ref::filter_map(self.raw.data.borrow(), |any| {
- any.as_ref()?.downcast_ref::()
- })
- .ok()
- })
- .flatten()
+ #[track_caller]
+ pub fn try_read(&self) -> Result, BorrowError> {
+ if !self.validate() {
+ return Err(BorrowError::Dropped(ValueDroppedError {
+ #[cfg(any(debug_assertions, feature = "debug_borrows"))]
+ created_at: self.created_at,
+ }));
+ }
+ self.raw.try_borrow(
+ #[cfg(any(debug_assertions, feature = "debug_borrows"))]
+ self.created_at,
+ )
}
/// Read the value. Panics if the value is no longer valid.
- pub fn read(&self) -> Ref<'_, T> {
+ #[track_caller]
+ pub fn read(&self) -> GenerationalRef {
self.try_read().unwrap()
}
/// Try to write the value. Returns None if the value is no longer valid.
- pub fn try_write(&self) -> Option> {
- self.validate()
- .then(|| {
- RefMut::filter_map(self.raw.data.borrow_mut(), |any| {
- any.as_mut()?.downcast_mut::()
- })
- .ok()
- })
- .flatten()
+ #[track_caller]
+ pub fn try_write(&self) -> Result, BorrowMutError> {
+ if !self.validate() {
+ return Err(BorrowMutError::Dropped(ValueDroppedError {
+ #[cfg(any(debug_assertions, feature = "debug_borrows"))]
+ created_at: self.created_at,
+ }));
+ }
+ self.raw.try_borrow_mut(
+ #[cfg(any(debug_assertions, feature = "debug_borrows"))]
+ self.created_at,
+ )
}
/// Write the value. Panics if the value is no longer valid.
- pub fn write(&self) -> RefMut<'_, T> {
+ #[track_caller]
+ pub fn write(&self) -> GenerationalRefMut {
self.try_write().unwrap()
}
/// Set the value. Panics if the value is no longer valid.
pub fn set(&self, value: T) {
self.validate().then(|| {
- *self.raw.data.borrow_mut() = Some(Box::new(value));
+ *self.raw.0.data.borrow_mut() = Some(Box::new(value));
});
}
@@ -228,7 +244,8 @@ impl GenerationalBox {
pub fn ptr_eq(&self, other: &Self) -> bool {
#[cfg(any(debug_assertions, feature = "check_generation"))]
{
- self.raw.data.as_ptr() == other.raw.data.as_ptr() && self.generation == other.generation
+ self.raw.0.data.as_ptr() == other.raw.0.data.as_ptr()
+ && self.generation == other.generation
}
#[cfg(not(any(debug_assertions, feature = "check_generation")))]
{
@@ -246,26 +263,37 @@ impl Clone for GenerationalBox {
}
#[derive(Clone, Copy)]
-struct MemoryLocation {
- data: &'static RefCell