Merge branch 'master' into add-file-data-drag-event

This commit is contained in:
Evan Almloff committed 2024-01-08 14:16:09 -06:00
commit 56798b3d1c
406 files changed
+16592 -11288

No files matched your search

+5
View File
@@ -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 = ""
+2
View File
@@ -36,6 +36,8 @@ jobs:
toolchain: ${{ matrix.platform.toolchain }} toolchain: ${{ matrix.platform.toolchain }}
targets: ${{ matrix.platform.target }} targets: ${{ matrix.platform.target }}
- uses: ilammy/setup-nasm@v1
# Setup the Github Actions Cache for the CLI package # Setup the Github Actions Cache for the CLI package
- name: Setup cache - name: Setup cache
uses: Swatinem/rust-cache@v2 uses: Swatinem/rust-cache@v2
+1 -1
View File
@@ -33,7 +33,7 @@ jobs:
# cd fermi && mdbook build -d ../nightly/fermi && cd .. # cd fermi && mdbook build -d ../nightly/fermi && cd ..
- name: Deploy 🚀 - name: Deploy 🚀
uses: JamesIves/github-pages-deploy-action@v4.4.3 uses: JamesIves/github-pages-deploy-action@v4.5.0
with: with:
branch: gh-pages # The branch the action should deploy to. branch: gh-pages # The branch the action should deploy to.
folder: docs/nightly # The folder the action should deploy. folder: docs/nightly # The folder the action should deploy.
+1 -1
View File
@@ -39,7 +39,7 @@ jobs:
# cd fermi && mdbook build -d ../nightly/fermi && cd .. # cd fermi && mdbook build -d ../nightly/fermi && cd ..
- name: Deploy 🚀 - name: Deploy 🚀
uses: JamesIves/github-pages-deploy-action@v4.4.3 uses: JamesIves/github-pages-deploy-action@v4.5.0
with: with:
branch: gh-pages # The branch the action should deploy to. branch: gh-pages # The branch the action should deploy to.
folder: docs/nightly # The folder the action should deploy. folder: docs/nightly # The folder the action should deploy.
+40 -7
View File
@@ -36,9 +36,15 @@ jobs:
if: github.event.pull_request.draft == false if: github.event.pull_request.draft == false
name: Check name: Check
runs-on: ubuntu-latest runs-on: ubuntu-latest
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0
SCCACHE_GHA_ENABLED: "true"
RUSTC_WRAPPER: "sccache"
steps: steps:
- uses: dtolnay/rust-toolchain@stable - uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2 - uses: mozilla-actions/sccache-action@v0.0.3
- uses: ilammy/setup-nasm@v1
- run: sudo apt-get update - run: sudo apt-get update
- run: sudo apt install libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev - run: sudo apt install libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -48,11 +54,17 @@ jobs:
if: github.event.pull_request.draft == false if: github.event.pull_request.draft == false
name: Test Suite name: Test Suite
runs-on: ubuntu-latest runs-on: ubuntu-latest
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0
SCCACHE_GHA_ENABLED: "true"
RUSTC_WRAPPER: "sccache"
steps: steps:
- uses: dtolnay/rust-toolchain@stable - uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2 - uses: mozilla-actions/sccache-action@v0.0.3
- uses: ilammy/setup-nasm@v1
- run: sudo apt-get update - run: sudo apt-get update
- run: sudo apt install libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev - run: sudo apt install libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev libxdo-dev
- uses: davidB/rust-cargo-make@v1 - uses: davidB/rust-cargo-make@v1
- uses: browser-actions/setup-firefox@latest - uses: browser-actions/setup-firefox@latest
- uses: jetli/wasm-pack-action@v0.4.0 - uses: jetli/wasm-pack-action@v0.4.0
@@ -63,9 +75,15 @@ jobs:
if: github.event.pull_request.draft == false if: github.event.pull_request.draft == false
name: Rustfmt name: Rustfmt
runs-on: ubuntu-latest runs-on: ubuntu-latest
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0
SCCACHE_GHA_ENABLED: "true"
RUSTC_WRAPPER: "sccache"
steps: steps:
- uses: dtolnay/rust-toolchain@stable - uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2 - uses: mozilla-actions/sccache-action@v0.0.3
- uses: ilammy/setup-nasm@v1
- run: rustup component add rustfmt - run: rustup component add rustfmt
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- run: cargo fmt --all -- --check - run: cargo fmt --all -- --check
@@ -74,9 +92,15 @@ jobs:
if: github.event.pull_request.draft == false if: github.event.pull_request.draft == false
name: Clippy name: Clippy
runs-on: ubuntu-latest runs-on: ubuntu-latest
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0
SCCACHE_GHA_ENABLED: "true"
RUSTC_WRAPPER: "sccache"
steps: steps:
- uses: dtolnay/rust-toolchain@stable - uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2 - uses: mozilla-actions/sccache-action@v0.0.3
- uses: ilammy/setup-nasm@v1
- run: sudo apt-get update - run: sudo apt-get update
- run: sudo apt install libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev - run: sudo apt install libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev
- run: rustup component add clippy - run: rustup component add clippy
@@ -86,6 +110,10 @@ jobs:
matrix_test: matrix_test:
runs-on: ${{ matrix.platform.os }} runs-on: ${{ matrix.platform.os }}
env: env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0
SCCACHE_GHA_ENABLED: "true"
RUSTC_WRAPPER: "sccache"
RUST_CARGO_COMMAND: ${{ matrix.platform.cross == true && 'cross' || 'cargo' }} RUST_CARGO_COMMAND: ${{ matrix.platform.cross == true && 'cross' || 'cargo' }}
strategy: strategy:
matrix: matrix:
@@ -125,7 +153,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: ilammy/setup-nasm@v1
- name: install stable - name: install stable
uses: dtolnay/rust-toolchain@master uses: dtolnay/rust-toolchain@master
with: with:
@@ -136,11 +164,16 @@ jobs:
if: ${{ matrix.platform.cross == true }} if: ${{ matrix.platform.cross == true }}
uses: taiki-e/install-action@cross uses: taiki-e/install-action@cross
- uses: Swatinem/rust-cache@v2 - uses: mozilla-actions/sccache-action@v0.0.3
with: with:
workspaces: core -> ../target workspaces: core -> ../target
save-if: ${{ matrix.features.key == 'all' }} save-if: ${{ matrix.features.key == 'all' }}
- name: Install rustfmt
run: rustup component add rustfmt
- uses: actions/checkout@v4
- name: test - name: test
run: | run: |
${{ env.RUST_CARGO_COMMAND }} ${{ matrix.platform.command }} ${{ matrix.platform.args }} --target ${{ matrix.platform.target }} ${{ env.RUST_CARGO_COMMAND }} ${{ matrix.platform.command }} ${{ matrix.platform.args }} --target ${{ matrix.platform.target }}
+4 -4
View File
@@ -26,8 +26,8 @@ env:
RUST_BACKTRACE: 1 RUST_BACKTRACE: 1
# Change to specific Rust release to pin # Change to specific Rust release to pin
rust_stable: stable rust_stable: stable
rust_nightly: nightly-2022-11-03 rust_nightly: nightly-2023-11-16
rust_clippy: 1.65.0 rust_clippy: 1.70.0
# When updating this, also update: # When updating this, also update:
# - README.md # - README.md
# - tokio/README.md # - tokio/README.md
@@ -70,6 +70,7 @@ jobs:
run: echo "MIRIFLAGS=-Zmiri-tag-gc=1" >> $GITHUB_ENV run: echo "MIRIFLAGS=-Zmiri-tag-gc=1" >> $GITHUB_ENV
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: ilammy/setup-nasm@v1
- name: Install Rust ${{ env.rust_nightly }} - name: Install Rust ${{ env.rust_nightly }}
uses: dtolnay/rust-toolchain@master uses: dtolnay/rust-toolchain@master
with: with:
@@ -86,8 +87,7 @@ jobs:
# working-directory: tokio # working-directory: tokio
env: env:
# todo: disable memory leaks ignore MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-strict-provenance -Zmiri-retag-fields
MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-strict-provenance -Zmiri-retag-fields -Zmiri-ignore-leaks
PROPTEST_CASES: 10 PROPTEST_CASES: 10
# Cache the global cargo directory, but NOT the local `target` directory which # Cache the global cargo directory, but NOT the local `target` directory which
+3 -2
View File
@@ -20,7 +20,8 @@ jobs:
steps: steps:
# Do our best to cache the toolchain and node install steps # Do our best to cache the toolchain and node install steps
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-node@v3 - uses: ilammy/setup-nasm@v1
- uses: actions/setup-node@v4
with: with:
node-version: 16 node-version: 16
- name: Install Rust - name: Install Rust
@@ -43,7 +44,7 @@ jobs:
# args: --path packages/cli # args: --path packages/cli
- name: Run Playwright tests - name: Run Playwright tests
run: npx playwright test run: npx playwright test
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v4
if: always() if: always()
with: with:
name: playwright-report name: playwright-report
+1
View File
@@ -4,6 +4,7 @@
/dist /dist
Cargo.lock Cargo.lock
.DS_Store .DS_Store
/examples/assets/test_video.mp4
.vscode/* .vscode/*
!.vscode/settings.json !.vscode/settings.json
-171
View File
@@ -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
<csr-read-only-do-not-edit/>
- 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
<csr-read-only-do-not-edit/>
<details><summary>view details</summary>
* **Uncategorized**
- Fix various typos and grammar nits ([`9e4ec43`](https://github.comgit//DioxusLabs/dioxus/commit/9e4ec43b1e78d355c56a38e4c092170b2b01b20d))
</details>
## v0.1.7 (2022-01-08)
### Bug Fixes
- <csr-id-bd341f5571580cdf5e495379b49ca988fd9211c3/> tests
### Commit Statistics
<csr-read-only-do-not-edit/>
- 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
<csr-read-only-do-not-edit/>
<details><summary>view details</summary>
* **Uncategorized**
- tests ([`bd341f5`](https://github.comgit//DioxusLabs/dioxus/commit/bd341f5571580cdf5e495379b49ca988fd9211c3))
</details>
## v0.1.1 (2021-12-15)
### Documentation
- <csr-id-4de16c4779648e591b3869b5df31271ae603c812/> update local examples and docs to support new syntaxes
- <csr-id-78007445f944f259170307d840e0f16242b7b4b6/> improve docs
- <csr-id-583fdfa5618e11d660985b97e570d4503be2ff49/> big updates to the reference
- <csr-id-bf21c82de04e25daee60a06232b9a16b640508f2/> lib.rs docs
- <csr-id-70cd46dbb2a689ae2d512e142b8aee9c80798430/> move around examples
### New Features
- <csr-id-8acdd2ea830b995b608d8bac2ef527db8d40e662/> it compiles once more
- <csr-id-9726a065b0d4fb1ede5b53a2ddd58c855e51539f/> massage lifetimes
- <csr-id-4a72b3140bd244da602deada1eeecded65ff5848/> amazingly awesome error handling
- <csr-id-3bedcb93cacec5bdf134adc38ff02eadbf96c1c6/> svgs working in webview
- <csr-id-a2c7d17b0595769f60bc1c2bbf7cbe32cec37486/> mvoe away from compound context
- <csr-id-de9f61bcf48c0d6e35e46c337b72a713c9f9f7d2/> more suspended nodes!
- <csr-id-4091846934b4b3b2bc03d3ca8aaf7712aebd4e36/> add aria
- <csr-id-7aec40d57e78ec13ff3a90ca8149521cbf1d9ff2/> enable arbitrary body in rsx! macro
## v0.1.0 (2021-12-15)
### Documentation
- <csr-id-4de16c4779648e591b3869b5df31271ae603c812/> update local examples and docs to support new syntaxes
- <csr-id-78007445f944f259170307d840e0f16242b7b4b6/> improve docs
- <csr-id-583fdfa5618e11d660985b97e570d4503be2ff49/> big updates to the reference
- <csr-id-bf21c82de04e25daee60a06232b9a16b640508f2/> lib.rs docs
- <csr-id-70cd46dbb2a689ae2d512e142b8aee9c80798430/> move around examples
### New Features
- <csr-id-8acdd2ea830b995b608d8bac2ef527db8d40e662/> it compiles once more
- <csr-id-9726a065b0d4fb1ede5b53a2ddd58c855e51539f/> massage lifetimes
- <csr-id-4a72b3140bd244da602deada1eeecded65ff5848/> amazingly awesome error handling
- <csr-id-3bedcb93cacec5bdf134adc38ff02eadbf96c1c6/> svgs working in webview
- <csr-id-a2c7d17b0595769f60bc1c2bbf7cbe32cec37486/> mvoe away from compound context
- <csr-id-de9f61bcf48c0d6e35e46c337b72a713c9f9f7d2/> more suspended nodes!
- <csr-id-4091846934b4b3b2bc03d3ca8aaf7712aebd4e36/> add aria
- <csr-id-7aec40d57e78ec13ff3a90ca8149521cbf1d9ff2/> enable arbitrary body in rsx! macro
## v0.0.1 (2022-01-03)
### Documentation
- <csr-id-78007445f944f259170307d840e0f16242b7b4b6/> improve docs
- <csr-id-4de16c4779648e591b3869b5df31271ae603c812/> update local examples and docs to support new syntaxes
- <csr-id-583fdfa5618e11d660985b97e570d4503be2ff49/> big updates to the reference
- <csr-id-bf21c82de04e25daee60a06232b9a16b640508f2/> lib.rs docs
- <csr-id-70cd46dbb2a689ae2d512e142b8aee9c80798430/> move around examples
### New Features
- <csr-id-8acdd2ea830b995b608d8bac2ef527db8d40e662/> it compiles once more
- <csr-id-9726a065b0d4fb1ede5b53a2ddd58c855e51539f/> massage lifetimes
- <csr-id-4a72b3140bd244da602deada1eeecded65ff5848/> amazingly awesome error handling
- <csr-id-3bedcb93cacec5bdf134adc38ff02eadbf96c1c6/> svgs working in webview
- <csr-id-a2c7d17b0595769f60bc1c2bbf7cbe32cec37486/> mvoe away from compound context
- <csr-id-de9f61bcf48c0d6e35e46c337b72a713c9f9f7d2/> more suspended nodes!
- <csr-id-4091846934b4b3b2bc03d3ca8aaf7712aebd4e36/> add aria
- <csr-id-7aec40d57e78ec13ff3a90ca8149521cbf1d9ff2/> enable arbitrary body in rsx! macro
### Commit Statistics
<csr-read-only-do-not-edit/>
- 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
<csr-read-only-do-not-edit/>
<details><summary>view details</summary>
* **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<event> ([`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))
</details>
+13 -5
View File
@@ -9,6 +9,7 @@ members = [
"packages/extension", "packages/extension",
"packages/router", "packages/router",
"packages/html", "packages/html",
"packages/html-internal-macro",
"packages/hooks", "packages/hooks",
"packages/web", "packages/web",
"packages/ssr", "packages/ssr",
@@ -41,6 +42,7 @@ members = [
"examples/tailwind", "examples/tailwind",
"examples/PWA-example", "examples/PWA-example",
"examples/query_segments_demo", "examples/query_segments_demo",
"examples/openid_connect_demo",
# Playwright tests # Playwright tests
"playwright-tests/liveview", "playwright-tests/liveview",
"playwright-tests/web", "playwright-tests/web",
@@ -49,7 +51,7 @@ members = [
exclude = ["examples/mobile_demo"] exclude = ["examples/mobile_demo"]
[workspace.package] [workspace.package]
version = "0.4.2" version = "0.4.3"
# dependencies that are shared across packages # dependencies that are shared across packages
[workspace.dependencies] [workspace.dependencies]
@@ -58,7 +60,8 @@ dioxus-core = { path = "packages/core", version = "0.4.2" }
dioxus-core-macro = { path = "packages/core-macro", version = "0.4.0" } dioxus-core-macro = { path = "packages/core-macro", version = "0.4.0" }
dioxus-router = { path = "packages/router", version = "0.4.1" } dioxus-router = { path = "packages/router", version = "0.4.1" }
dioxus-router-macro = { path = "packages/router-macro", version = "0.4.1" } dioxus-router-macro = { path = "packages/router-macro", version = "0.4.1" }
dioxus-html = { path = "packages/html", version = "0.4.0" } dioxus-html = { path = "packages/html", default-features = false, version = "0.4.0" }
dioxus-html-internal-macro = { path = "packages/html-internal-macro", version = "0.4.0" }
dioxus-hooks = { path = "packages/hooks", version = "0.4.0" } dioxus-hooks = { path = "packages/hooks", version = "0.4.0" }
dioxus-web = { path = "packages/web", version = "0.4.0" } dioxus-web = { path = "packages/web", version = "0.4.0" }
dioxus-ssr = { path = "packages/ssr", version = "0.4.0" } dioxus-ssr = { path = "packages/ssr", version = "0.4.0" }
@@ -76,7 +79,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" } dioxus-native-core-macro = { path = "packages/native-core-macro", version = "0.4.0" }
rsx-rosetta = { path = "packages/rsx-rosetta", version = "0.4.0" } rsx-rosetta = { path = "packages/rsx-rosetta", version = "0.4.0" }
dioxus-signals = { path = "packages/signals" } 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-hot-reload = { path = "packages/hot-reload", version = "0.4.0" }
dioxus-fullstack = { path = "packages/fullstack", version = "0.4.1" } dioxus-fullstack = { path = "packages/fullstack", version = "0.4.1" }
dioxus_server_macro = { path = "packages/server-macro", version = "0.4.1" } dioxus_server_macro = { path = "packages/server-macro", version = "0.4.1" }
@@ -87,7 +90,7 @@ slab = "0.4.2"
futures-channel = "0.3.21" futures-channel = "0.3.21"
futures-util = { version = "0.3", default-features = false } futures-util = { version = "0.3", default-features = false }
rustc-hash = "1.1.0" rustc-hash = "1.1.0"
wasm-bindgen = "0.2.87" wasm-bindgen = "0.2.88"
html_parser = "0.7.0" html_parser = "0.7.0"
thiserror = "1.0.40" thiserror = "1.0.40"
prettyplease = { package = "prettier-please", version = "0.2", features = [ prettyplease = { package = "prettier-please", version = "0.2", features = [
@@ -98,7 +101,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 # It is not meant to be published, but is used so "cargo run --example XYZ" works properly
[package] [package]
name = "dioxus-examples" name = "dioxus-examples"
version = "0.0.0" version = "0.4.3"
authors = ["Jonathan Kelley"] authors = ["Jonathan Kelley"]
edition = "2021" edition = "2021"
description = "Top level crate for the Dioxus repository" description = "Top level crate for the Dioxus repository"
@@ -132,3 +135,8 @@ fern = { version = "0.6.0", features = ["colored"] }
env_logger = "0.10.0" env_logger = "0.10.0"
simple_logger = "4.0.0" simple_logger = "4.0.0"
thiserror = { workspace = true } thiserror = { workspace = true }
[dependencies]
tracing-subscriber = "0.3.17"
http-range = "0.1.5"
+72 -4
View File
@@ -24,12 +24,64 @@ script = [
] ]
script_runner = "@duckscript" 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] [tasks.tests]
category = "Testing" category = "Testing"
dependencies = ["tests-setup"] dependencies = ["tests-setup"]
description = "Run all tests" description = "Run all tests"
env = {CARGO_MAKE_WORKSPACE_SKIP_MEMBERS = ["**/examples/*"]} env = { CARGO_MAKE_WORKSPACE_SKIP_MEMBERS = ["**/examples/*"] }
run_task = {name = ["test-flow", "test-with-browser"], fork = true} run_task = { name = ["test-flow", "test-with-browser"], fork = true }
[tasks.build] [tasks.build]
command = "cargo" command = "cargo"
@@ -42,10 +94,26 @@ private = true
[tasks.test] [tasks.test]
dependencies = ["build"] dependencies = ["build"]
command = "cargo" 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",
"--exclude",
"dioxus-mobile",
]
private = true private = true
[tasks.test-with-browser] [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 private = true
workspace = true workspace = true
+2 -1
View File
@@ -159,8 +159,9 @@ So... Dioxus is great, but why won't it work for me?
## Contributing ## 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). - Report issues on our [issue tracker](https://github.com/dioxuslabs/dioxus/issues).
- Join the discord and ask questions! - [Join](https://discord.gg/XgGxMSkvUM) the discord and ask questions!
<a href="https://github.com/dioxuslabs/dioxus/graphs/contributors"> <a href="https://github.com/dioxuslabs/dioxus/graphs/contributors">
-1
View File
@@ -139,7 +139,6 @@ Missing Features
Missing examples Missing examples
- Shared state - Shared state
- Root-less element groups - Root-less element groups
- Spread props
- Custom elements - Custom elements
- Component Children: Pass children into child components - Component Children: Pass children into child components
- Render To string: Render a mounted virtualdom to a string - Render To string: Render a mounted virtualdom to a string
+4 -7
View File
@@ -53,8 +53,7 @@ fn app(cx: Scope) -> Element {
}; };
cx.render(rsx! ( cx.render(rsx! (
div { div { style: "{CONTAINER_STYLE}",
style: "{CONTAINER_STYLE}",
div { div {
style: "{RECT_STYLE}", style: "{RECT_STYLE}",
// focusing is necessary to catch keyboard events // focusing is necessary to catch keyboard events
@@ -62,7 +61,7 @@ fn app(cx: Scope) -> Element {
onmousemove: move |event| log_event(Event::MouseMove(event)), onmousemove: move |event| log_event(Event::MouseMove(event)),
onclick: move |event| log_event(Event::MouseClick(event)), onclick: move |event| log_event(Event::MouseClick(event)),
ondblclick: move |event| log_event(Event::MouseDoubleClick(event)), ondoubleclick: move |event| log_event(Event::MouseDoubleClick(event)),
onmousedown: move |event| log_event(Event::MouseDown(event)), onmousedown: move |event| log_event(Event::MouseDown(event)),
onmouseup: move |event| log_event(Event::MouseUp(event)), onmouseup: move |event| log_event(Event::MouseUp(event)),
@@ -77,9 +76,7 @@ fn app(cx: Scope) -> Element {
"Hover, click, type or scroll to see the info down below" "Hover, click, type or scroll to see the info down below"
} }
div { div { events.read().iter().map(|event| rsx!( div { "{event:?}" } )) }
events.read().iter().map(|event| rsx!( div { "{event:?}" } )) }
},
},
)) ))
} }
-18
View File
@@ -1,18 +0,0 @@
use dioxus::prelude::*;
fn main() {
dioxus_desktop::launch(app);
}
fn app(cx: Scope) -> Element {
cx.render(rsx! {
button {
onclick: |_| async move {
println!("hello, desktop!");
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
println!("goodbye, desktop!");
},
"hello, desktop!"
}
})
}
+3 -2
View File
@@ -6,13 +6,13 @@ This calculator version uses React-style state management. All state is held as
use dioxus::events::*; use dioxus::events::*;
use dioxus::html::input_data::keyboard_types::Key; use dioxus::html::input_data::keyboard_types::Key;
use dioxus::prelude::*; use dioxus::prelude::*;
use dioxus_desktop::{Config, WindowBuilder}; use dioxus_desktop::{Config, LogicalSize, WindowBuilder};
fn main() { fn main() {
let config = Config::new().with_window( let config = Config::new().with_window(
WindowBuilder::default() WindowBuilder::default()
.with_title("Calculator") .with_title("Calculator")
.with_inner_size(dioxus_desktop::LogicalSize::new(300.0, 500.0)), .with_inner_size(LogicalSize::new(300.0, 500.0)),
); );
dioxus_desktop::launch_cfg(app, config); dioxus_desktop::launch_cfg(app, config);
@@ -62,6 +62,7 @@ fn app(cx: Scope) -> Element {
div { id: "wrapper", div { id: "wrapper",
div { class: "app", div { class: "app",
div { class: "calculator", div { class: "calculator",
tabindex: "0",
onkeydown: handle_key_down_event, onkeydown: handle_key_down_event,
div { class: "calculator-display", val.to_string() } div { class: "calculator-display", val.to_string() }
div { class: "calculator-keypad", div { class: "calculator-keypad",
+1 -1
View File
@@ -10,7 +10,7 @@ fn app(cx: Scope) -> Element {
use_future!(cx, || async move { use_future!(cx, || async move {
loop { loop {
tokio::time::sleep(std::time::Duration::from_millis(100)).await; tokio::time::sleep(std::time::Duration::from_millis(10)).await;
count += 1; count += 1;
println!("current: {count}"); println!("current: {count}");
} }
+4 -18
View File
@@ -1,7 +1,6 @@
//! This example shows how to create a popup window and send data back to the parent window. //! This example shows how to create a popup window and send data back to the parent window.
use dioxus::prelude::*; use dioxus::prelude::*;
use dioxus_desktop::use_window;
use futures_util::StreamExt; use futures_util::StreamExt;
fn main() { fn main() {
@@ -9,7 +8,6 @@ fn main() {
} }
fn app(cx: Scope) -> Element { fn app(cx: Scope) -> Element {
let window = use_window(cx);
let emails_sent = use_ref(cx, Vec::new); let emails_sent = use_ref(cx, Vec::new);
let tx = use_coroutine(cx, |mut rx: UnboundedReceiver<String>| { let tx = use_coroutine(cx, |mut rx: UnboundedReceiver<String>| {
@@ -27,14 +25,8 @@ fn app(cx: Scope) -> Element {
button { button {
onclick: move |_| { onclick: move |_| {
let dom = VirtualDom::new_with_props(compose, ComposeProps { let dom = VirtualDom::new_with_props(compose, ComposeProps { app_tx: tx.clone() });
app_tx: tx.clone() dioxus_desktop::window().new_window(dom, Default::default());
});
// this returns a weak reference to the other window
// Be careful not to keep a strong reference to the other window or it will never be dropped
// and the window will never close.
window.new_window(dom, Default::default());
}, },
"Click to compose a new email" "Click to compose a new email"
} }
@@ -57,7 +49,6 @@ struct ComposeProps {
fn compose(cx: Scope<ComposeProps>) -> Element { fn compose(cx: Scope<ComposeProps>) -> Element {
let user_input = use_state(cx, String::new); let user_input = use_state(cx, String::new);
let window = use_window(cx);
cx.render(rsx! { cx.render(rsx! {
div { div {
@@ -66,17 +57,12 @@ fn compose(cx: Scope<ComposeProps>) -> Element {
button { button {
onclick: move |_| { onclick: move |_| {
cx.props.app_tx.send(user_input.get().clone()); cx.props.app_tx.send(user_input.get().clone());
window.close(); dioxus_desktop::window().close();
}, },
"Click to send" "Click to send"
} }
input { input { oninput: move |e| user_input.set(e.value()), value: "{user_input}" }
oninput: move |e| {
user_input.set(e.value.clone());
},
value: "{user_input}"
}
} }
}) })
} }
+1 -1
View File
@@ -22,7 +22,7 @@ fn app(cx: Scope) -> Element {
input { input {
value: "{counter}", value: "{counter}",
oninput: move |e| { oninput: move |e| {
if let Ok(value) = e.value.parse::<usize>() { if let Ok(value) = e.value().parse::<usize>() {
counters.make_mut()[i] = value; counters.make_mut()[i] = value;
} }
} }
+27 -63
View File
@@ -35,14 +35,16 @@ fn App(cx: Scope) -> Element {
rel: "stylesheet", rel: "stylesheet",
href: "https://unpkg.com/purecss@2.0.6/build/pure-min.css", href: "https://unpkg.com/purecss@2.0.6/build/pure-min.css",
integrity: "sha384-Uu6IeWbM+gzNVXJcM9XV3SohHtmWE+3VGi496jvgX1jyvDTXfdK+rfZc8C1Aehk5", integrity: "sha384-Uu6IeWbM+gzNVXJcM9XV3SohHtmWE+3VGi496jvgX1jyvDTXfdK+rfZc8C1Aehk5",
crossorigin: "anonymous", crossorigin: "anonymous"
} }
style { " style {
"
.red {{ .red {{
background-color: rgb(202, 60, 60) !important; background-color: rgb(202, 60, 60) !important;
}} }}
" } "
}
h1 { "Dioxus CRM Example" } h1 { "Dioxus CRM Example" }
@@ -57,16 +59,8 @@ fn ClientList(cx: Scope) -> Element {
cx.render(rsx! { cx.render(rsx! {
h2 { "List of Clients" } h2 { "List of Clients" }
Link { Link { to: Route::ClientAdd {}, class: "pure-button pure-button-primary", "Add Client" }
to: Route::ClientAdd {}, Link { to: Route::Settings {}, class: "pure-button", "Settings" }
class: "pure-button pure-button-primary",
"Add Client"
}
Link {
to: Route::Settings {},
class: "pure-button",
"Settings"
}
clients.read().iter().map(|client| rsx! { clients.read().iter().map(|client| rsx! {
div { div {
@@ -87,8 +81,6 @@ fn ClientAdd(cx: Scope) -> Element {
let last_name = use_state(cx, String::new); let last_name = use_state(cx, String::new);
let description = use_state(cx, String::new); let description = use_state(cx, String::new);
let navigator = use_navigator(cx);
cx.render(rsx! { cx.render(rsx! {
h2 { "Add new Client" } h2 { "Add new Client" }
@@ -96,79 +88,55 @@ fn ClientAdd(cx: Scope) -> Element {
class: "pure-form pure-form-aligned", class: "pure-form pure-form-aligned",
onsubmit: move |_| { onsubmit: move |_| {
let mut clients = clients.write(); let mut clients = clients.write();
clients
clients.push(Client { .push(Client {
first_name: first_name.to_string(), first_name: first_name.to_string(),
last_name: last_name.to_string(), last_name: last_name.to_string(),
description: description.to_string(), description: description.to_string(),
}); });
dioxus_router::router().push(Route::ClientList {});
navigator.push(Route::ClientList {});
}, },
fieldset { fieldset {
div { div { class: "pure-control-group",
class: "pure-control-group", label { "for": "first_name", "First Name" }
label {
"for": "first_name",
"First Name"
}
input { input {
id: "first_name", id: "first_name",
"type": "text", "type": "text",
placeholder: "First Name…", placeholder: "First Name…",
required: "", required: "",
value: "{first_name}", value: "{first_name}",
oninput: move |e| first_name.set(e.value.clone()) oninput: move |e| first_name.set(e.value())
} }
} }
div { div { class: "pure-control-group",
class: "pure-control-group", label { "for": "last_name", "Last Name" }
label {
"for": "last_name",
"Last Name"
}
input { input {
id: "last_name", id: "last_name",
"type": "text", "type": "text",
placeholder: "Last Name…", placeholder: "Last Name…",
required: "", required: "",
value: "{last_name}", value: "{last_name}",
oninput: move |e| last_name.set(e.value.clone()) oninput: move |e| last_name.set(e.value())
} }
} }
div { div { class: "pure-control-group",
class: "pure-control-group", label { "for": "description", "Description" }
label {
"for": "description",
"Description"
}
textarea { textarea {
id: "description", id: "description",
placeholder: "Description…", placeholder: "Description…",
value: "{description}", value: "{description}",
oninput: move |e| description.set(e.value.clone()) oninput: move |e| description.set(e.value())
} }
} }
div { div { class: "pure-controls",
class: "pure-controls", button { "type": "submit", class: "pure-button pure-button-primary", "Save" }
button { Link { to: Route::ClientList {}, class: "pure-button pure-button-primary red", "Cancel" }
"type": "submit",
class: "pure-button pure-button-primary",
"Save"
}
Link {
to: Route::ClientList {},
class: "pure-button pure-button-primary red",
"Cancel"
}
} }
} }
} }
}) })
} }
@@ -189,10 +157,6 @@ fn Settings(cx: Scope) -> Element {
"Remove all Clients" "Remove all Clients"
} }
Link { Link { to: Route::ClientList {}, class: "pure-button", "Go back" }
to: Route::ClientList {},
class: "pure-button",
"Go back"
}
}) })
} }
+1 -1
View File
@@ -10,7 +10,7 @@ fn app(cx: Scope) -> Element {
p { p {
"This should show an image:" "This should show an image:"
} }
img { src: "examples/assets/logo.png" } img { src: mg!(image("examples/assets/logo.png").format(ImageType::Avif)).to_string() }
} }
}) })
} }
+27
View File
@@ -0,0 +1,27 @@
use dioxus::prelude::*;
use dioxus_desktop::{use_asset_handler, wry::http::Response};
fn main() {
dioxus_desktop::launch(app);
}
fn app(cx: Scope) -> Element {
use_asset_handler(cx, "logos", |request, response| {
// Note that the "logos" prefix is stripped from the URI
//
// However, the asset is absolute to its "virtual folder" - meaning it starts with a leading slash
if request.uri().path() != "/logo.png" {
return;
}
response.respond(Response::new(include_bytes!("./assets/logo.png").to_vec()));
});
cx.render(rsx! {
div {
img {
src: "/logos/logo.png"
}
}
})
}
+11 -16
View File
@@ -1,4 +1,4 @@
use dioxus::prelude::*; use dioxus::{core::CapturedError, prelude::*};
fn main() { fn main() {
dioxus_desktop::launch(App); dioxus_desktop::launch(App);
@@ -6,30 +6,25 @@ fn main() {
#[component] #[component]
fn App(cx: Scope) -> Element { fn App(cx: Scope) -> Element {
let val = use_state(cx, || "0.0001");
let num = match val.parse::<f32>() {
Err(_) => return cx.render(rsx!("Parsing failed")),
Ok(num) => num,
};
cx.render(rsx! { cx.render(rsx! {
h1 { "The parsed value is {num}" } ErrorBoundary {
button { handle_error: |error: CapturedError| rsx! {"Found error {error}"},
onclick: move |_| val.set("invalid"), DemoC {
"Set an invalid number" x: 1
}
} }
(0..5).map(|i| rsx! {
DemoC { x: i }
})
}) })
} }
#[component] #[component]
fn DemoC(cx: Scope, x: i32) -> Element { fn DemoC(cx: Scope, x: i32) -> Element {
let result = Err("Error");
result.throw()?;
cx.render(rsx! { cx.render(rsx! {
h1 { h1 {
"asdasdasdasd {x}" "{x}"
} }
}) })
} }
+9 -14
View File
@@ -5,26 +5,21 @@ fn main() {
} }
fn app(cx: Scope) -> Element { fn app(cx: Scope) -> Element {
let eval_provider = use_eval(cx); let future = use_future(cx, (), |_| async move {
let eval = eval(
let future = use_future(cx, (), |_| { r#"
to_owned![eval_provider];
async move {
let eval = eval_provider(
r#"
dioxus.send("Hi from JS!"); dioxus.send("Hi from JS!");
let msg = await dioxus.recv(); let msg = await dioxus.recv();
console.log(msg); console.log(msg);
return "hello world"; return "hello world";
"#, "#,
) )
.unwrap(); .unwrap();
eval.send("Hi from Rust!".into()).unwrap(); eval.send("Hi from Rust!".into()).unwrap();
let res = eval.recv().await.unwrap(); let res = eval.recv().await.unwrap();
println!("{:?}", eval.await); println!("{:?}", eval.await);
res res
}
}); });
match future.value() { match future.value() {
+2 -1
View File
@@ -18,13 +18,14 @@ fn main() {
); );
} }
const _STYLE: &str = mg!(file("./examples/assets/fileexplorer.css"));
fn app(cx: Scope) -> Element { fn app(cx: Scope) -> Element {
let files = use_ref(cx, Files::new); let files = use_ref(cx, Files::new);
cx.render(rsx! { cx.render(rsx! {
div { div {
link { href:"https://fonts.googleapis.com/icon?family=Material+Icons", rel:"stylesheet", } link { href:"https://fonts.googleapis.com/icon?family=Material+Icons", rel:"stylesheet", }
style { include_str!("./assets/fileexplorer.css") }
header { header {
i { class: "material-icons icon-menu", "menu" } i { class: "material-icons icon-menu", "menu" }
h1 { "Files: ", files.read().current() } h1 { "Files: ", files.read().current() }
+2 -2
View File
@@ -16,7 +16,7 @@ fn App(cx: Scope) -> Element {
r#type: "checkbox", r#type: "checkbox",
checked: "{enable_directory_upload}", checked: "{enable_directory_upload}",
oninput: move |evt| { oninput: move |evt| {
enable_directory_upload.set(evt.value.parse().unwrap()); enable_directory_upload.set(evt.value().parse().unwrap());
}, },
}, },
"Enable directory upload" "Enable directory upload"
@@ -30,7 +30,7 @@ fn App(cx: Scope) -> Element {
onchange: |evt| { onchange: |evt| {
to_owned![files_uploaded]; to_owned![files_uploaded];
async move { async move {
if let Some(file_engine) = &evt.files { if let Some(file_engine) = &evt.files() {
let files = file_engine.files(); let files = file_engine.files();
for file_name in files { for file_name in files {
sleep(std::time::Duration::from_secs(1)).await; sleep(std::time::Duration::from_secs(1)).await;
+2 -2
View File
@@ -14,8 +14,8 @@ fn app(cx: Scope) -> Element {
div { div {
h1 { "Form" } h1 { "Form" }
form { form {
onsubmit: move |ev| println!("Submitted {:?}", ev.values), onsubmit: move |ev| println!("Submitted {:?}", ev.values()),
oninput: move |ev| println!("Input {:?}", ev.values), oninput: move |ev| println!("Input {:?}", ev.values()),
input { r#type: "text", name: "username" } input { r#type: "text", name: "username" }
input { r#type: "text", name: "full-name" } input { r#type: "text", name: "full-name" }
input { r#type: "password", name: "password" } input { r#type: "password", name: "password" }
+17 -20
View File
@@ -8,33 +8,30 @@ fn main() {
} }
fn app(cx: Scope) -> Element { fn app(cx: Scope) -> Element {
let onsubmit = move |evt: FormEvent| { let onsubmit = move |evt: FormEvent| async move {
cx.spawn(async move { let resp = reqwest::Client::new()
let resp = reqwest::Client::new() .post("http://localhost:8080/login")
.post("http://localhost:8080/login") .form(&[
.form(&[ ("username", &evt.values()["username"]),
("username", &evt.values["username"]), ("password", &evt.values()["password"]),
("password", &evt.values["password"]), ])
]) .send()
.send() .await;
.await;
match resp { match resp {
// Parse data from here, such as storing a response token // Parse data from here, such as storing a response token
Ok(_data) => println!("Login successful!"), Ok(_data) => println!("Login successful!"),
//Handle any errors from the fetch here //Handle any errors from the fetch here
Err(_err) => { Err(_err) => {
println!("Login failed - you need a login server running on localhost:8080.") println!("Login failed - you need a login server running on localhost:8080.")
}
} }
}); }
}; };
cx.render(rsx! { cx.render(rsx! {
h1 { "Login" } h1 { "Login" }
form { form { onsubmit: onsubmit,
onsubmit: onsubmit,
input { r#type: "text", id: "username", name: "username" } input { r#type: "text", id: "username", name: "username" }
label { "Username" } label { "Username" }
br {} br {}
+1 -1
View File
@@ -2,7 +2,7 @@
target/ target/
**/*.rs.bk **/*.rs.bk
# tauri-mobile # cargo-mobile2
.cargo/ .cargo/
/gen /gen
+1 -1
View File
@@ -35,7 +35,7 @@ frameworks = ["WebKit"]
[dependencies] [dependencies]
anyhow = "1.0.56" anyhow = "1.0.56"
log = "0.4.11" log = "0.4.11"
wry = "0.28.0" wry = "0.35.0"
dioxus = { path = "../../packages/dioxus" } dioxus = { path = "../../packages/dioxus" }
dioxus-desktop = { path = "../../packages/desktop", features = [ dioxus-desktop = { path = "../../packages/desktop", features = [
"tokio_runtime", "tokio_runtime",
+1 -1
View File
@@ -4,7 +4,7 @@
Right now, Dioxus supports mobile targets including iOS and Android. However, our tooling is not mature enough to include the build commands directly. Right now, Dioxus supports mobile targets including iOS and Android. However, our tooling is not mature enough to include the build commands directly.
This project was generated using [tauri-mobile](https://github.com/tauri-apps/tauri-mobile). We have yet to integrate this generation into the Dioxus-CLI. The open issue for this is [#1157](https://github.com/DioxusLabs/dioxus/issues/1157). This project was generated using [cargo-mobile2](https://github.com/tauri-apps/cargo-mobile2). We have yet to integrate this generation into the Dioxus-CLI. The open issue for this is [#1157](https://github.com/DioxusLabs/dioxus/issues/1157).
## Running on iOS ## Running on iOS
+1 -3
View File
@@ -5,14 +5,12 @@ fn main() {
} }
fn app(cx: Scope) -> Element { fn app(cx: Scope) -> Element {
let window = dioxus_desktop::use_window(cx);
cx.render(rsx! { cx.render(rsx! {
div { div {
button { button {
onclick: move |_| { onclick: move |_| {
let dom = VirtualDom::new(popup); let dom = VirtualDom::new(popup);
window.new_window(dom, Default::default()); dioxus_desktop::window().new_window(dom, Default::default());
}, },
"New Window" "New Window"
} }
+3
View File
@@ -0,0 +1,3 @@
/target
/dist
.env
+25
View File
@@ -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"] }
+47
View File
@@ -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 = []
+13
View File
@@ -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`
@@ -0,0 +1,2 @@
pub const DIOXUS_FRONT_AUTH_TOKEN: &str = "auth_token";
pub const DIOXUS_FRONT_AUTH_REQUEST: &str = "auth_request";
@@ -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<openidconnect::reqwest::Error<reqwest::Error>>,
),
#[error("Parsing error: {0}")]
Parse(#[from] url::ParseError),
#[error("Request token error: {0}")]
RequestToken(
#[from]
RequestTokenError<
openidconnect::reqwest::Error<reqwest::Error>,
StandardErrorResponse<CoreErrorResponseType>,
>,
),
}
+60
View File
@@ -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<ClientState> = AtomRef(|_| ClientState::default());
// An option is required to prevent the component from being constantly refreshed
pub static FERMI_AUTH_TOKEN: fermi::AtomRef<Option<AuthTokenState>> = AtomRef(|_| None);
pub static FERMI_AUTH_REQUEST: fermi::AtomRef<Option<AuthRequestState>> = AtomRef(|_| None);
pub static DIOXUS_FRONT_ISSUER_URL: &str = env!("DIOXUS_FRONT_ISSUER_URL");
pub static DIOXUS_FRONT_CLIENT_ID: &str = env!("DIOXUS_FRONT_CLIENT_ID");
pub static DIOXUS_FRONT_URL: &str = env!("DIOXUS_FRONT_URL");
fn App(cx: Scope) -> Element {
use_init_atom_root(cx);
// Retrieve the value stored in the browser's storage
let stored_auth_token = LocalStorage::get(DIOXUS_FRONT_AUTH_TOKEN)
.ok()
.unwrap_or(AuthTokenState::default());
let fermi_auth_token = use_atom_ref(cx, &FERMI_AUTH_TOKEN);
if fermi_auth_token.read().is_none() {
*fermi_auth_token.write() = Some(stored_auth_token);
}
let stored_auth_request = LocalStorage::get(DIOXUS_FRONT_AUTH_REQUEST)
.ok()
.unwrap_or(AuthRequestState::default());
let fermi_auth_request = use_atom_ref(cx, &FERMI_AUTH_REQUEST);
if fermi_auth_request.read().is_none() {
*fermi_auth_request.write() = Some(stored_auth_request);
}
render! { Router::<Route> {} }
}
fn main() {
dioxus_logger::init(LevelFilter::Info).expect("failed to init logger");
console_error_panic_hook::set_once();
log::info!("starting app");
dioxus_web::launch(App);
}
@@ -0,0 +1 @@
pub(crate) mod user;
@@ -0,0 +1,7 @@
use uuid::Uuid;
#[derive(PartialEq)]
pub struct User {
pub id: Uuid,
pub name: String,
}
+125
View File
@@ -0,0 +1,125 @@
use openidconnect::{
core::{CoreClient, CoreErrorResponseType, CoreIdToken, CoreResponseType, CoreTokenResponse},
reqwest::async_http_client,
url::Url,
AuthenticationFlow, AuthorizationCode, ClaimsVerificationError, ClientId, CsrfToken, IssuerUrl,
LogoutRequest, Nonce, ProviderMetadataWithLogout, RedirectUrl, RefreshToken, RequestTokenError,
StandardErrorResponse,
};
use serde::{Deserialize, Serialize};
use crate::{props::client::ClientProps, DIOXUS_FRONT_CLIENT_ID};
#[derive(Clone, Debug, Default)]
pub struct ClientState {
pub oidc_client: Option<ClientProps>,
}
/// State that holds the nonce and authorization url and the nonce generated to log in an user
#[derive(Clone, Deserialize, Serialize, Default)]
pub struct AuthRequestState {
pub auth_request: Option<AuthRequest>,
}
#[derive(Clone, Deserialize, Serialize)]
pub struct AuthRequest {
pub nonce: Nonce,
pub authorize_url: String,
}
/// State the tokens returned once the user is authenticated
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
pub struct AuthTokenState {
/// Token used to identify the user
pub id_token: Option<CoreIdToken>,
/// Token used to refresh the tokens if they expire
pub refresh_token: Option<RefreshToken>,
}
pub fn email(
client: CoreClient,
id_token: CoreIdToken,
nonce: Nonce,
) -> Result<String, ClaimsVerificationError> {
match id_token.claims(&client.id_token_verifier(), &nonce) {
Ok(claims) => Ok(claims.clone().email().unwrap().to_string()),
Err(error) => Err(error),
}
}
pub fn authorize_url(client: CoreClient) -> AuthRequest {
let (authorize_url, _csrf_state, nonce) = client
.authorize_url(
AuthenticationFlow::<CoreResponseType>::AuthorizationCode,
CsrfToken::new_random,
Nonce::new_random,
)
.add_scope(openidconnect::Scope::new("email".to_string()))
.add_scope(openidconnect::Scope::new("profile".to_string()))
.url();
AuthRequest {
authorize_url: authorize_url.to_string(),
nonce,
}
}
pub async fn init_provider_metadata() -> Result<ProviderMetadataWithLogout, crate::errors::Error> {
let issuer_url = IssuerUrl::new(crate::DIOXUS_FRONT_ISSUER_URL.to_string())?;
Ok(ProviderMetadataWithLogout::discover_async(issuer_url, async_http_client).await?)
}
pub async fn init_oidc_client() -> Result<(ClientId, CoreClient), crate::errors::Error> {
let client_id = ClientId::new(crate::DIOXUS_FRONT_CLIENT_ID.to_string());
let provider_metadata = init_provider_metadata().await?;
let client_secret = None;
let redirect_url = RedirectUrl::new(format!("{}/login", crate::DIOXUS_FRONT_URL))?;
Ok((
client_id.clone(),
CoreClient::from_provider_metadata(provider_metadata, client_id, client_secret)
.set_redirect_uri(redirect_url),
))
}
///TODO: Add pkce_pacifier
pub async fn token_response(
oidc_client: CoreClient,
code: String,
) -> Result<CoreTokenResponse, crate::errors::Error> {
// let (pkce_challenge, pkce_verifier) = PkceCodeChallenge::new_random_sha256();
Ok(oidc_client
.exchange_code(AuthorizationCode::new(code.clone()))
// .set_pkce_verifier(pkce_verifier)
.request_async(async_http_client)
.await?)
}
pub async fn exchange_refresh_token(
oidc_client: CoreClient,
refresh_token: RefreshToken,
) -> Result<
CoreTokenResponse,
RequestTokenError<
openidconnect::reqwest::Error<reqwest::Error>,
StandardErrorResponse<CoreErrorResponseType>,
>,
> {
oidc_client
.exchange_refresh_token(&refresh_token)
.request_async(async_http_client)
.await
}
pub async fn log_out_url(id_token_hint: CoreIdToken) -> Result<Url, crate::errors::Error> {
let provider_metadata = init_provider_metadata().await?;
let end_session_url = provider_metadata
.additional_metadata()
.clone()
.end_session_endpoint
.unwrap();
let logout_request: LogoutRequest = LogoutRequest::from(end_session_url);
Ok(logout_request
.set_client_id(ClientId::new(DIOXUS_FRONT_CLIENT_ID.to_string()))
.set_id_token_hint(&id_token_hint)
.http_get_url())
}
@@ -0,0 +1,20 @@
use dioxus::prelude::*;
use openidconnect::{core::CoreClient, ClientId};
#[derive(Props, Clone, Debug)]
pub struct ClientProps {
pub client: CoreClient,
pub client_id: ClientId,
}
impl PartialEq for ClientProps {
fn eq(&self, other: &Self) -> bool {
self.client_id == other.client_id
}
}
impl ClientProps {
pub fn new(client_id: ClientId, client: CoreClient) -> Self {
ClientProps { client_id, client }
}
}
@@ -0,0 +1 @@
pub(crate) mod client;
@@ -0,0 +1,17 @@
use crate::views::{header::AuthHeader, home::Home, login::Login, not_found::NotFound};
use dioxus::prelude::*;
use dioxus_router::prelude::*;
#[derive(Routable, Clone)]
pub enum Route {
#[layout(AuthHeader)]
#[route("/")]
Home {},
// https://dioxuslabs.com/learn/0.4/router/reference/routes#query-segments
#[route("/login?:query_string")]
Login { query_string: String },
#[end_layout]
#[route("/:..route")]
NotFound { route: Vec<String> },
}
@@ -0,0 +1,38 @@
use fermi::UseAtomRef;
use gloo_storage::{LocalStorage, Storage};
use serde::{Deserialize, Serialize};
use crate::{
constants::{DIOXUS_FRONT_AUTH_REQUEST, DIOXUS_FRONT_AUTH_TOKEN},
oidc::{AuthRequestState, AuthTokenState},
};
#[derive(Serialize, Deserialize, Clone)]
pub struct StorageEntry<T> {
pub key: String,
pub value: T,
}
pub trait PersistentWrite<T: Serialize + Clone> {
fn persistent_set(atom_ref: &UseAtomRef<Option<T>>, entry: Option<T>);
}
impl PersistentWrite<AuthTokenState> for AuthTokenState {
fn persistent_set(
atom_ref: &UseAtomRef<Option<AuthTokenState>>,
entry: Option<AuthTokenState>,
) {
*atom_ref.write() = entry.clone();
LocalStorage::set(DIOXUS_FRONT_AUTH_TOKEN, entry).unwrap();
}
}
impl PersistentWrite<AuthRequestState> for AuthRequestState {
fn persistent_set(
atom_ref: &UseAtomRef<Option<AuthRequestState>>,
entry: Option<AuthRequestState>,
) {
*atom_ref.write() = entry.clone();
LocalStorage::set(DIOXUS_FRONT_AUTH_REQUEST, entry).unwrap();
}
}
@@ -0,0 +1,250 @@
use crate::{
oidc::{
authorize_url, email, exchange_refresh_token, init_oidc_client, log_out_url,
AuthRequestState, AuthTokenState, ClientState,
},
props::client::ClientProps,
router::Route,
storage::PersistentWrite,
FERMI_AUTH_REQUEST, FERMI_AUTH_TOKEN, FERMI_CLIENT,
};
use dioxus::prelude::*;
use dioxus_router::prelude::{Link, Outlet};
use fermi::*;
use openidconnect::{url::Url, OAuth2TokenResponse, TokenResponse};
#[component]
pub fn LogOut(cx: Scope<ClientProps>) -> Element {
let fermi_auth_token = use_atom_ref(cx, &FERMI_AUTH_TOKEN);
let fermi_auth_token_read = fermi_auth_token.read().clone();
let log_out_url_state = use_state(cx, || None::<Option<Result<Url, crate::errors::Error>>>);
cx.render(match fermi_auth_token_read {
Some(fermi_auth_token_read) => match fermi_auth_token_read.id_token.clone() {
Some(id_token) => match log_out_url_state.get() {
Some(log_out_url_result) => match log_out_url_result {
Some(uri) => match uri {
Ok(uri) => {
rsx! {
Link {
onclick: move |_| {
{
AuthTokenState::persistent_set(
fermi_auth_token,
Some(AuthTokenState::default()),
);
}
},
to: uri.to_string(),
"Log out"
}
}
}
Err(error) => {
rsx! {
div { format!{"Failed to load disconnection url: {:?}", error} }
}
}
},
None => {
rsx! { div { "Loading... Please wait" } }
}
},
None => {
let logout_url_task = move || {
cx.spawn({
let log_out_url_state = log_out_url_state.to_owned();
async move {
let logout_url = log_out_url(id_token).await;
let logout_url_option = Some(logout_url);
log_out_url_state.set(Some(logout_url_option));
}
})
};
logout_url_task();
rsx! { div{"Loading log out url... Please wait"}}
}
},
None => {
rsx! {{}}
}
},
None => {
rsx! {{}}
}
})
}
#[component]
pub fn RefreshToken(cx: Scope<ClientProps>) -> Element {
let fermi_auth_token = use_atom_ref(cx, &FERMI_AUTH_TOKEN);
let fermi_auth_request = use_atom_ref(cx, &FERMI_AUTH_REQUEST);
let fermi_auth_token_read = fermi_auth_token.read().clone();
cx.render(match fermi_auth_token_read {
Some(fermi_auth_client_read) => match fermi_auth_client_read.refresh_token {
Some(refresh_token) => {
let fermi_auth_token = fermi_auth_token.to_owned();
let fermi_auth_request = fermi_auth_request.to_owned();
let client = cx.props.client.clone();
let exchange_refresh_token_spawn = move || {
cx.spawn({
async move {
let exchange_refresh_token =
exchange_refresh_token(client, refresh_token).await;
match exchange_refresh_token {
Ok(response_token) => {
AuthTokenState::persistent_set(
&fermi_auth_token,
Some(AuthTokenState {
id_token: response_token.id_token().cloned(),
refresh_token: response_token.refresh_token().cloned(),
}),
);
}
Err(_error) => {
AuthTokenState::persistent_set(
&fermi_auth_token,
Some(AuthTokenState::default()),
);
AuthRequestState::persistent_set(
&fermi_auth_request,
Some(AuthRequestState::default()),
);
}
}
}
})
};
exchange_refresh_token_spawn();
rsx! { div { "Refreshing session, please wait" } }
}
None => {
rsx! { div { "Id token expired and no refresh token found" } }
}
},
None => {
rsx! {{}}
}
})
}
#[component]
pub fn LoadClient(cx: Scope) -> Element {
let init_client_future = use_future(cx, (), |_| async move { init_oidc_client().await });
let fermi_client: &UseAtomRef<ClientState> = use_atom_ref(cx, &FERMI_CLIENT);
cx.render(match init_client_future.value() {
Some(client_props) => match client_props {
Ok((client_id, client)) => {
*fermi_client.write() = ClientState {
oidc_client: Some(ClientProps::new(client_id.clone(), client.clone())),
};
rsx! {
div { "Client successfully loaded" }
Outlet::<Route> {}
}
}
Err(error) => {
rsx! {
div { format!{"Failed to load client: {:?}", error} }
log::info!{"Failed to load client: {:?}", error},
Outlet::<Route> {}
}
}
},
None => {
rsx! {
div {
div { "Loading client, please wait" }
Outlet::<Route> {}
}
}
}
})
}
#[component]
pub fn AuthHeader(cx: Scope) -> Element {
let auth_token = use_atom_ref(cx, &FERMI_AUTH_TOKEN);
let fermi_auth_request = use_atom_ref(cx, &FERMI_AUTH_REQUEST);
let fermi_client: &UseAtomRef<ClientState> = use_atom_ref(cx, &FERMI_CLIENT);
let client = fermi_client.read().oidc_client.clone();
let auth_request_read = fermi_auth_request.read().clone();
let auth_token_read = auth_token.read().clone();
cx.render(match (client, auth_request_read, auth_token_read) {
// We have everything we need to attempt to authenticate the user
(Some(client_props), Some(auth_request), Some(auth_token)) => {
match auth_request.auth_request {
Some(auth_request) => {
match auth_token.id_token {
Some(id_token) => {
match email(
client_props.client.clone(),
id_token.clone(),
auth_request.nonce.clone(),
) {
Ok(email) => {
rsx! {
div {
div { email }
LogOut { client_id: client_props.client_id, client: client_props.client }
Outlet::<Route> {}
}
}
}
// Id token failed to be decoded
Err(error) => match error {
// Id token failed to be decoded because it expired, we refresh it
openidconnect::ClaimsVerificationError::Expired(_message) => {
log::info!("Token expired");
rsx! {
div {
RefreshToken {client_id: client_props.client_id, client: client_props.client}
Outlet::<Route> {}
}
}
}
// Other issue with token decoding
_ => {
log::info!("Other issue with token");
rsx! {
div {
div { error.to_string() }
Outlet::<Route> {}
}
}
}
},
}
}
// User is not logged in
None => {
rsx! {
div {
Link { to: auth_request.authorize_url.clone(), "Log in" }
Outlet::<Route> {}
}
}
}
}
}
None => {
let auth_request = authorize_url(client_props.client);
AuthRequestState::persistent_set(
fermi_auth_request,
Some(AuthRequestState {
auth_request: Some(auth_request),
}),
);
rsx! { div { "Loading nonce" } }
}
}
}
// Client is not initialized yet, we need it for everything
(None, _, _) => {
rsx! { LoadClient {} }
}
// We need everything loaded before doing anything
(_client, _auth_request, _auth_token) => {
rsx! {{}}
}
})
}
@@ -0,0 +1,5 @@
use dioxus::prelude::*;
pub fn Home(cx: Scope) -> Element {
render! { div { "Hello world" } }
}
@@ -0,0 +1,86 @@
use crate::{
oidc::{token_response, AuthRequestState, AuthTokenState},
router::Route,
storage::PersistentWrite,
DIOXUS_FRONT_URL, FERMI_AUTH_REQUEST, FERMI_AUTH_TOKEN, FERMI_CLIENT,
};
use dioxus::prelude::*;
use dioxus_router::prelude::{Link, NavigationTarget};
use fermi::*;
use openidconnect::{OAuth2TokenResponse, TokenResponse};
#[component]
pub fn Login(cx: Scope, query_string: String) -> Element {
let fermi_client = use_atom_ref(cx, &FERMI_CLIENT);
let fermi_auth_token = use_atom_ref(cx, &FERMI_AUTH_TOKEN);
let home_url: NavigationTarget<Route> = DIOXUS_FRONT_URL.parse().unwrap();
let fermi_auth_request = use_atom_ref(cx, &FERMI_AUTH_REQUEST);
let client = fermi_client.read().oidc_client.clone();
let auth_token_read = fermi_auth_token.read().clone();
cx.render(match (client, auth_token_read) {
(Some(client_props), Some(auth_token_read)) => {
match (auth_token_read.id_token, auth_token_read.refresh_token) {
(Some(_id_token), Some(_refresh_token)) => {
rsx! {
div { "Sign in successful" }
Link { to: home_url, "Go back home" }
}
}
// If the refresh token is set but not the id_token, there was an error, we just go back home and reset their value
(None, Some(_)) | (Some(_), None) => {
rsx! {
div { "Error while attempting to log in" }
Link {
to: home_url,
onclick: move |_| {
AuthTokenState::persistent_set(fermi_auth_token, Some(AuthTokenState::default()));
AuthRequestState::persistent_set(
fermi_auth_request,
Some(AuthRequestState::default()),
);
},
"Go back home"
}
}
}
(None, None) => {
let mut query_pairs = form_urlencoded::parse(query_string.as_bytes());
let code_pair = query_pairs.find(|(key, _value)| key == "code");
match code_pair {
Some((_key, code)) => {
let auth_code = code.to_string();
let token_response_spawn = move ||{
cx.spawn({
let fermi_auth_token = fermi_auth_token.to_owned();
async move {
let token_response_result = token_response(client_props.client, auth_code).await;
match token_response_result{
Ok(token_response) => {
let id_token = token_response.id_token().unwrap();
AuthTokenState::persistent_set(&fermi_auth_token, Some(AuthTokenState {
id_token: Some(id_token.clone()),
refresh_token: token_response.refresh_token().cloned()
}));
}
Err(error) => {
log::warn!{"{error}"};
}
}
}
})
};
token_response_spawn();
rsx!{ div {} }
}
None => {
rsx! { div { "No code provided" } }
}
}
}
}
}
(_, _) => {
rsx! {{}}
}
})
}
@@ -0,0 +1,4 @@
pub(crate) mod header;
pub(crate) mod home;
pub(crate) mod login;
pub(crate) mod not_found;
@@ -0,0 +1,7 @@
use dioxus::prelude::*;
#[component]
pub fn NotFound(cx: Scope, route: Vec<String>) -> Element {
let routes = route.join("");
render! {rsx! {div{routes}}}
}
+12
View File
@@ -16,8 +16,20 @@ fn app(cx: Scope) -> Element {
a: "asd".to_string(), a: "asd".to_string(),
c: "asd".to_string(), c: "asd".to_string(),
d: Some("asd".to_string()), d: Some("asd".to_string()),
e: Some("asd".to_string()),
}
Button {
a: "asd".to_string(),
b: "asd".to_string(),
c: "asd".to_string(),
d: Some("asd".to_string()),
e: "asd".to_string(), e: "asd".to_string(),
} }
Button {
a: "asd".to_string(),
c: "asd".to_string(),
d: Some("asd".to_string()),
}
}) })
} }
+2 -4
View File
@@ -1,13 +1,11 @@
use dioxus::prelude::*; use dioxus::prelude::*;
use dioxus_desktop::{tao::dpi::PhysicalPosition, use_window, LogicalSize, WindowBuilder}; use dioxus_desktop::{tao::dpi::PhysicalPosition, LogicalSize, WindowBuilder};
fn main() { fn main() {
dioxus_desktop::launch_cfg(app, make_config()); dioxus_desktop::launch_cfg(app, make_config());
} }
fn app(cx: Scope) -> Element { fn app(cx: Scope) -> Element {
let window = use_window(cx);
cx.render(rsx! { cx.render(rsx! {
div { div {
width: "100%", width: "100%",
@@ -19,7 +17,7 @@ fn app(cx: Scope) -> Element {
width: "100%", width: "100%",
height: "10px", height: "10px",
background_color: "black", background_color: "black",
onmousedown: move |_| window.drag(), onmousedown: move |_| dioxus_desktop::window().drag(),
} }
"This is an overlay!" "This is an overlay!"
+1 -1
View File
@@ -21,7 +21,7 @@ use dioxus::events::*;
use dioxus::html::input_data::keyboard_types::Key; use dioxus::html::input_data::keyboard_types::Key;
use dioxus::html::MouseEvent; use dioxus::html::MouseEvent;
use dioxus::prelude::*; use dioxus::prelude::*;
use dioxus_desktop::wry::application::dpi::LogicalSize; use dioxus_desktop::tao::dpi::LogicalSize;
use dioxus_desktop::{Config, WindowBuilder}; use dioxus_desktop::{Config, WindowBuilder};
fn main() { fn main() {
+1
View File
@@ -2,6 +2,7 @@
name = "query_segments_demo" name = "query_segments_demo"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
publish = false
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+22 -8
View File
@@ -14,29 +14,35 @@ use dioxus_router::prelude::*;
#[derive(Routable, Clone)] #[derive(Routable, Clone)]
#[rustfmt::skip] #[rustfmt::skip]
enum Route { enum Route {
// segments that start with ?: are query segments // segments that start with ?:.. are query segments that capture the entire query
#[route("/blog?:query_params")] #[route("/blog?:..query_params")]
BlogPost { BlogPost {
// You must include query segments in child variants // You must include query segments in child variants
query_params: BlogQuerySegments, query_params: ManualBlogQuerySegments,
},
// segments that follow the ?:field&:other_field syntax are query segments that follow the standard url query syntax
#[route("/autoblog?:name&:surname")]
AutomaticBlogPost {
name: String,
surname: String,
}, },
} }
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
struct BlogQuerySegments { struct ManualBlogQuerySegments {
name: String, name: String,
surname: String, surname: String,
} }
/// The display impl needs to display the query in a way that can be parsed: /// The display impl needs to display the query in a way that can be parsed:
impl Display for BlogQuerySegments { impl Display for ManualBlogQuerySegments {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "name={}&surname={}", self.name, self.surname) write!(f, "name={}&surname={}", self.name, self.surname)
} }
} }
/// The query segment is anything that implements https://docs.rs/dioxus-router/latest/dioxus_router/routable/trait.FromQuery.html. You can implement that trait for a struct if you want to parse multiple query parameters. /// The query segment is anything that implements <https://docs.rs/dioxus-router/latest/dioxus_router/routable/trait.FromQuery.html>. You can implement that trait for a struct if you want to parse multiple query parameters.
impl FromQuery for BlogQuerySegments { impl FromQuery for ManualBlogQuerySegments {
fn from_query(query: &str) -> Self { fn from_query(query: &str) -> Self {
let mut name = None; let mut name = None;
let mut surname = None; let mut surname = None;
@@ -57,13 +63,21 @@ impl FromQuery for BlogQuerySegments {
} }
#[component] #[component]
fn BlogPost(cx: Scope, query_params: BlogQuerySegments) -> Element { fn BlogPost(cx: Scope, query_params: ManualBlogQuerySegments) -> Element {
render! { render! {
div{"This is your blogpost with a query segment:"} div{"This is your blogpost with a query segment:"}
div{format!("{:?}", query_params)} div{format!("{:?}", query_params)}
} }
} }
#[component]
fn AutomaticBlogPost(cx: Scope, name: String, surname: String) -> Element {
render! {
div{"This is your blogpost with a query segment:"}
div{format!("name={}&surname={}", name, surname)}
}
}
#[component] #[component]
fn App(cx: Scope) -> Element { fn App(cx: Scope) -> Element {
render! { Router::<Route>{} } render! { Router::<Route>{} }
+5
View File
@@ -53,6 +53,7 @@ fn App(cx: Scope) -> Element {
let formatting = "formatting!"; let formatting = "formatting!";
let formatting_tuple = ("a", "b"); let formatting_tuple = ("a", "b");
let lazy_fmt = format_args!("lazily formatted text"); let lazy_fmt = format_args!("lazily formatted text");
let asd = 123;
cx.render(rsx! { cx.render(rsx! {
div { div {
// Elements // Elements
@@ -80,6 +81,10 @@ fn App(cx: Scope) -> Element {
// pass simple rust expressions in // pass simple rust expressions in
class: lazy_fmt, class: lazy_fmt,
id: format_args!("attributes can be passed lazily with std::fmt::Arguments"), id: format_args!("attributes can be passed lazily with std::fmt::Arguments"),
class: "asd",
class: "{asd}",
// if statements can be used to conditionally render attributes
class: if formatting.contains("form") { "{asd}" },
div { div {
class: { class: {
const WORD: &str = "expressions"; const WORD: &str = "expressions";
+1 -1
View File
@@ -64,7 +64,7 @@ fn DataEditor(cx: Scope, id: usize) -> Element {
fn DataView(cx: Scope, id: usize) -> Element { fn DataView(cx: Scope, id: usize) -> Element {
let cool_data = use_shared_state::<CoolData>(cx).unwrap(); let cool_data = use_shared_state::<CoolData>(cx).unwrap();
let oninput = |e: FormEvent| cool_data.write().set(*id, e.value.clone()); let oninput = |e: FormEvent| cool_data.write().set(*id, e.value());
let cool_data = cool_data.read(); let cool_data = cool_data.read();
let my_data = &cool_data.view(id).unwrap(); let my_data = &cool_data.view(id).unwrap();
+23 -1
View File
@@ -6,11 +6,17 @@ fn main() {
} }
fn app(cx: Scope) -> Element { fn app(cx: Scope) -> Element {
let running = dioxus_signals::use_signal(cx, || true);
let mut count = dioxus_signals::use_signal(cx, || 0); let mut count = dioxus_signals::use_signal(cx, || 0);
let saved_values = dioxus_signals::use_signal(cx, || vec![0.to_string()]);
// Signals can be used in async functions without an explicit clone since they're 'static and Copy
// Signals are backed by a runtime that is designed to deeply integrate with Dioxus apps
use_future!(cx, || async move { use_future!(cx, || async move {
loop { loop {
count += 1; if running.value() {
count += 1;
}
tokio::time::sleep(Duration::from_millis(400)).await; tokio::time::sleep(Duration::from_millis(400)).await;
} }
}); });
@@ -19,9 +25,25 @@ fn app(cx: Scope) -> Element {
h1 { "High-Five counter: {count}" } h1 { "High-Five counter: {count}" }
button { onclick: move |_| count += 1, "Up high!" } button { onclick: move |_| count += 1, "Up high!" }
button { onclick: move |_| count -= 1, "Down low!" } button { onclick: move |_| count -= 1, "Down low!" }
button { onclick: move |_| running.toggle(), "Toggle counter" }
button { onclick: move |_| saved_values.push(count.value().to_string()), "Save this value" }
button { onclick: move |_| saved_values.write().clear(), "Clear saved values" }
// We can do boolean operations on the current signal value
if count.value() > 5 { if count.value() > 5 {
rsx!{ h2 { "High five!" } } rsx!{ h2 { "High five!" } }
} }
// We can cleanly map signals with iterators
for value in saved_values.read().iter() {
h3 { "Saved value: {value}" }
}
// We can also use the signal value as a slice
if let [ref first, .., ref last] = saved_values.read().as_slice() {
rsx! { li { "First and last: {first}, {last}" } }
} else {
rsx! { "No saved values" }
}
}) })
} }
+36
View File
@@ -0,0 +1,36 @@
use dioxus::prelude::*;
fn main() {
let mut dom = VirtualDom::new(app);
let _ = dom.rebuild();
let html = dioxus_ssr::render(&dom);
println!("{}", html);
}
fn app(cx: Scope) -> Element {
render! {
Component {
width: "10px",
extra_data: "hello{1}",
extra_data2: "hello{2}",
height: "10px",
left: 1
}
}
}
#[component]
fn Component<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> {
render! {
audio { ..cx.props.attributes, "1: {cx.props.extra_data}\n2: {cx.props.extra_data2}" }
}
}
#[derive(Props)]
struct Props<'a> {
#[props(extends = GlobalAttributes)]
attributes: Vec<Attribute<'a>>,
extra_data: &'a str,
extra_data2: &'a str,
}
+33
View File
@@ -0,0 +1,33 @@
use dioxus::prelude::*;
use dioxus_signals::use_signal;
use futures_util::{future, stream, Stream, StreamExt};
use std::time::Duration;
fn main() {
dioxus_desktop::launch(app);
}
fn app(cx: Scope) -> Element {
let count = use_signal(cx, || 10);
use_future(cx, (), |_| async move {
let mut stream = some_stream();
while let Some(second) = stream.next().await {
count.set(second);
}
});
cx.render(rsx! {
h1 { "High-Five counter: {count}" }
})
}
fn some_stream() -> std::pin::Pin<Box<dyn Stream<Item = i32>>> {
Box::pin(
stream::once(future::ready(0)).chain(stream::iter(1..).then(|second| async move {
tokio::time::sleep(Duration::from_secs(1)).await;
second
})),
)
}
+1 -1
View File
@@ -30,7 +30,7 @@ watch_path = ["src", "public"]
[web.resource] [web.resource]
# CSS style file # CSS style file
style = ["/tailwind.css"] style = []
# Javascript code file # Javascript code file
script = [] script = []
+1 -1
View File
@@ -7,7 +7,7 @@ This example shows how an app might be styled with TailwindCSS.
1. Install the Dioxus CLI: 1. Install the Dioxus CLI:
```bash ```bash
cargo install --git https://github.com/DioxusLabs/cli cargo install dioxus-cli
``` ```
2. Install npm: https://docs.npmjs.com/downloading-and-installing-node-js-and-npm 2. Install npm: https://docs.npmjs.com/downloading-and-installing-node-js-and-npm
@@ -0,0 +1 @@
*,:before,:after{box-sizing:border-box;border:0 solid #e5e7eb}:before,:after{--tw-content:""}html{-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-feature-settings:normal;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;line-height:1.5}body{line-height:inherit;margin:0}hr{color:inherit;border-top-width:1px;height:0}abbr:where([title]){text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button;background-color:#0000;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{margin:0;padding:0;list-style:none}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}[hidden]{display:none}*,:before,:after,::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.container{width:100%}@media (width>=640px){.container{max-width:640px}}@media (width>=768px){.container{max-width:768px}}@media (width>=1024px){.container{max-width:1024px}}@media (width>=1280px){.container{max-width:1280px}}@media (width>=1536px){.container{max-width:1536px}}.mx-auto{margin-left:auto;margin-right:auto}.mb-16{margin-bottom:4rem}.mb-4{margin-bottom:1rem}.mb-8{margin-bottom:2rem}.ml-1{margin-left:.25rem}.ml-3{margin-left:.75rem}.ml-4{margin-left:1rem}.mr-5{margin-right:1.25rem}.mt-4{margin-top:1rem}.flex{display:flex}.inline-flex{display:inline-flex}.hidden{display:none}.h-10{height:2.5rem}.h-4{height:1rem}.w-10{width:2.5rem}.w-4{width:1rem}.w-5\/6{width:83.3333%}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.justify-center{justify-content:center}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.border-0{border-width:0}.bg-gray-800{--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity))}.bg-gray-900{--tw-bg-opacity:1;background-color:rgb(17 24 39/var(--tw-bg-opacity))}.bg-indigo-500{--tw-bg-opacity:1;background-color:rgb(99 102 241/var(--tw-bg-opacity))}.object-cover{-o-object-fit:cover;object-fit:cover}.object-center{-o-object-position:center;object-position:center}.p-2{padding:.5rem}.p-5{padding:1.25rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-24{padding-top:6rem;padding-bottom:6rem}.text-center{text-align:center}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.font-medium{font-weight:500}.leading-relaxed{line-heiLine truncated
+8 -6
View File
@@ -2,21 +2,23 @@
use dioxus::prelude::*; use dioxus::prelude::*;
const _STYLE: &str = mg!(file("./public/tailwind.css"));
fn main() { fn main() {
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
dioxus_desktop::launch_cfg( dioxus_desktop::launch(app);
app,
dioxus_desktop::Config::new()
.with_custom_head(r#"<link rel="stylesheet" href="public/tailwind.css">"#.to_string()),
);
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
dioxus_web::launch(app); dioxus_web::launch(app);
} }
pub fn app(cx: Scope) -> Element { pub fn app(cx: Scope) -> Element {
let grey_background = true;
cx.render(rsx!( cx.render(rsx!(
div { div {
header { class: "text-gray-400 bg-gray-900 body-font", header {
class: "text-gray-400 body-font",
// you can use optional attributes to optionally apply a tailwind class
class: if grey_background { "bg-gray-900" },
div { class: "container mx-auto flex flex-wrap p-5 flex-col md:flex-row items-center", div { class: "container mx-auto flex flex-wrap p-5 flex-col md:flex-row items-center",
a { class: "flex title-font font-medium items-center text-white mb-4 md:mb-0", a { class: "flex title-font font-medium items-center text-white mb-4 md:mb-0",
StacksIcon {} StacksIcon {}
+1 -1
View File
@@ -17,7 +17,7 @@ fn app(cx: Scope) -> Element {
rows: "10", rows: "10",
cols: "80", cols: "80",
value: "{model}", value: "{model}",
oninput: move |e| model.set(e.value.clone()), oninput: move |e| model.set(e.value().clone()),
} }
}) })
} }
+133 -79
View File
@@ -7,6 +7,8 @@ fn main() {
dioxus_desktop::launch(app); dioxus_desktop::launch(app);
} }
const _STYLE: &str = mg!(file("./examples/assets/todomvc.css"));
#[derive(PartialEq, Eq, Clone, Copy)] #[derive(PartialEq, Eq, Clone, Copy)]
pub enum FilterState { pub enum FilterState {
All, All,
@@ -24,8 +26,6 @@ pub struct TodoItem {
pub fn app(cx: Scope<()>) -> Element { pub fn app(cx: Scope<()>) -> Element {
let todos = use_state(cx, im_rc::HashMap::<u32, TodoItem>::default); let todos = use_state(cx, im_rc::HashMap::<u32, TodoItem>::default);
let filter = use_state(cx, || FilterState::All); let filter = use_state(cx, || FilterState::All);
let draft = use_state(cx, || "".to_string());
let todo_id = use_state(cx, || 0);
// Filter the todos based on the filter state // Filter the todos based on the filter state
let mut filtered_todos = todos let mut filtered_todos = todos
@@ -47,45 +47,10 @@ pub fn app(cx: Scope<()>) -> Element {
let show_clear_completed = todos.values().any(|todo| todo.checked); let show_clear_completed = todos.values().any(|todo| todo.checked);
let selected = |state| {
if *filter == state {
"selected"
} else {
"false"
}
};
cx.render(rsx! { cx.render(rsx! {
section { class: "todoapp", section { class: "todoapp",
style { include_str!("./assets/todomvc.css") } TodoHeader { todos: todos }
header { class: "header", section { class: "main",
h1 {"todos"}
input {
class: "new-todo",
placeholder: "What needs to be done?",
value: "{draft}",
autofocus: "true",
oninput: move |evt| {
draft.set(evt.value.clone());
},
onkeydown: move |evt| {
if evt.key() == Key::Enter && !draft.is_empty() {
todos.make_mut().insert(
**todo_id,
TodoItem {
id: **todo_id,
checked: false,
contents: draft.to_string(),
},
);
*todo_id.make_mut() += 1;
draft.set("".to_string());
}
}
}
}
section {
class: "main",
if !todos.is_empty() { if !todos.is_empty() {
rsx! { rsx! {
input { input {
@@ -111,43 +76,58 @@ pub fn app(cx: Scope<()>) -> Element {
})) }))
} }
(!todos.is_empty()).then(|| rsx!( (!todos.is_empty()).then(|| rsx!(
footer { class: "footer", ListFooter {
span { class: "todo-count", active_todo_count: active_todo_count,
strong {"{active_todo_count} "} active_todo_text: active_todo_text,
span {"{active_todo_text} left"} show_clear_completed: show_clear_completed,
} todos: todos,
ul { class: "filters", filter: filter,
for (state, state_text, url) in [
(FilterState::All, "All", "#/"),
(FilterState::Active, "Active", "#/active"),
(FilterState::Completed, "Completed", "#/completed"),
] {
li {
a {
href: url,
class: selected(state),
onclick: move |_| filter.set(state),
prevent_default: "onclick",
state_text
}
}
}
}
show_clear_completed.then(|| rsx!(
button {
class: "clear-completed",
onclick: move |_| todos.make_mut().retain(|_, todo| !todo.checked),
"Clear completed"
}
))
} }
)) ))
} }
} }
footer { class: "info", PageFooter {}
p { "Double-click to edit a todo" } })
p { "Created by ", a { href: "http://github.com/jkelleyrtp/", "jkelleyrtp" }} }
p { "Part of ", a { href: "http://todomvc.com", "TodoMVC" }}
#[derive(Props)]
pub struct TodoHeaderProps<'a> {
todos: &'a UseState<im_rc::HashMap<u32, TodoItem>>,
}
pub fn TodoHeader<'a>(cx: Scope<'a, TodoHeaderProps<'a>>) -> Element {
let draft = use_state(cx, || "".to_string());
let todo_id = use_state(cx, || 0);
cx.render(rsx! {
header { class: "header",
h1 { "todos" }
input {
class: "new-todo",
placeholder: "What needs to be done?",
value: "{draft}",
autofocus: "true",
oninput: move |evt| {
draft.set(evt.value().clone());
},
onkeydown: move |evt| {
if evt.key() == Key::Enter && !draft.is_empty() {
cx.props
.todos
.make_mut()
.insert(
**todo_id,
TodoItem {
id: **todo_id,
checked: false,
contents: draft.to_string(),
},
);
*todo_id.make_mut() += 1;
draft.set("".to_string());
}
}
}
} }
}) })
} }
@@ -167,8 +147,7 @@ pub fn TodoEntry<'a>(cx: Scope<'a, TodoEntryProps<'a>>) -> Element {
let editing = if **is_editing { "editing" } else { "" }; let editing = if **is_editing { "editing" } else { "" };
cx.render(rsx!{ cx.render(rsx!{
li { li { class: "{completed} {editing}",
class: "{completed} {editing}",
div { class: "view", div { class: "view",
input { input {
class: "toggle", class: "toggle",
@@ -176,26 +155,28 @@ pub fn TodoEntry<'a>(cx: Scope<'a, TodoEntryProps<'a>>) -> Element {
id: "cbg-{todo.id}", id: "cbg-{todo.id}",
checked: "{todo.checked}", checked: "{todo.checked}",
oninput: move |evt| { oninput: move |evt| {
cx.props.todos.make_mut()[&cx.props.id].checked = evt.value.parse().unwrap(); cx.props.todos.make_mut()[&cx.props.id].checked = evt.value().parse().unwrap();
} }
} }
label { label {
r#for: "cbg-{todo.id}", r#for: "cbg-{todo.id}",
ondblclick: move |_| is_editing.set(true), ondoubleclick: move |_| is_editing.set(true),
prevent_default: "onclick", prevent_default: "onclick",
"{todo.contents}" "{todo.contents}"
} }
button { button {
class: "destroy", class: "destroy",
onclick: move |_| { cx.props.todos.make_mut().remove(&todo.id); }, onclick: move |_| {
prevent_default: "onclick", cx.props.todos.make_mut().remove(&todo.id);
},
prevent_default: "onclick"
} }
} }
is_editing.then(|| rsx!{ is_editing.then(|| rsx!{
input { input {
class: "edit", class: "edit",
value: "{todo.contents}", value: "{todo.contents}",
oninput: move |evt| cx.props.todos.make_mut()[&cx.props.id].contents = evt.value.clone(), oninput: move |evt| cx.props.todos.make_mut()[&cx.props.id].contents = evt.value(),
autofocus: "true", autofocus: "true",
onfocusout: move |_| is_editing.set(false), onfocusout: move |_| is_editing.set(false),
onkeydown: move |evt| { onkeydown: move |evt| {
@@ -209,3 +190,76 @@ pub fn TodoEntry<'a>(cx: Scope<'a, TodoEntryProps<'a>>) -> Element {
} }
}) })
} }
#[derive(Props)]
pub struct ListFooterProps<'a> {
todos: &'a UseState<im_rc::HashMap<u32, TodoItem>>,
active_todo_count: usize,
active_todo_text: &'a str,
show_clear_completed: bool,
filter: &'a UseState<FilterState>,
}
pub fn ListFooter<'a>(cx: Scope<'a, ListFooterProps<'a>>) -> Element {
let active_todo_count = cx.props.active_todo_count;
let active_todo_text = cx.props.active_todo_text;
let selected = |state| {
if *cx.props.filter == state {
"selected"
} else {
"false"
}
};
cx.render(rsx! {
footer { class: "footer",
span { class: "todo-count",
strong { "{active_todo_count} " }
span { "{active_todo_text} left" }
}
ul { class: "filters",
for (state , state_text , url) in [
(FilterState::All, "All", "#/"),
(FilterState::Active, "Active", "#/active"),
(FilterState::Completed, "Completed", "#/completed"),
] {
li {
a {
href: url,
class: selected(state),
onclick: move |_| cx.props.filter.set(state),
prevent_default: "onclick",
state_text
}
}
}
}
if cx.props.show_clear_completed {
cx.render(rsx! {
button {
class: "clear-completed",
onclick: move |_| cx.props.todos.make_mut().retain(|_, todo| !todo.checked),
"Clear completed"
}
})
}
}
})
}
pub fn PageFooter(cx: Scope) -> Element {
cx.render(rsx! {
footer { class: "info",
p { "Double-click to edit a todo" }
p {
"Created by "
a { href: "http://github.com/jkelleyrtp/", "jkelleyrtp" }
}
p {
"Part of "
a { href: "http://todomvc.com", "TodoMVC" }
}
}
})
}
+188
View File
@@ -0,0 +1,188 @@
use dioxus::prelude::*;
use dioxus_desktop::wry::http;
use dioxus_desktop::wry::http::Response;
use dioxus_desktop::{use_asset_handler, AssetRequest};
use http::{header::*, response::Builder as ResponseBuilder, status::StatusCode};
use std::{io::SeekFrom, path::PathBuf};
use tokio::io::AsyncReadExt;
use tokio::io::AsyncSeekExt;
use tokio::io::AsyncWriteExt;
const VIDEO_PATH: &str = "./examples/assets/test_video.mp4";
fn main() {
let video_file = PathBuf::from(VIDEO_PATH);
if !video_file.exists() {
tokio::runtime::Runtime::new()
.unwrap()
.block_on(async move {
println!("Downloading video file...");
let video_url =
"http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4";
let mut response = reqwest::get(video_url).await.unwrap();
let mut file = tokio::fs::File::create(&video_file).await.unwrap();
while let Some(chunk) = response.chunk().await.unwrap() {
file.write_all(&chunk).await.unwrap();
}
});
}
dioxus_desktop::launch(app);
}
fn app(cx: Scope) -> Element {
use_asset_handler(cx, "videos", move |request, responder| {
// Using dioxus::spawn works, but is slower than a dedicated thread
tokio::task::spawn(async move {
let video_file = PathBuf::from(VIDEO_PATH);
let mut file = tokio::fs::File::open(&video_file).await.unwrap();
match get_stream_response(&mut file, &request).await {
Ok(response) => responder.respond(response),
Err(err) => eprintln!("Error: {}", err),
}
});
});
render! {
div {
video {
src: "/videos/test_video.mp4",
autoplay: true,
controls: true,
width: 640,
height: 480
}
}
}
}
/// This was taken from wry's example
async fn get_stream_response(
asset: &mut (impl tokio::io::AsyncSeek + tokio::io::AsyncRead + Unpin + Send + Sync),
request: &AssetRequest,
) -> Result<Response<Vec<u8>>, Box<dyn std::error::Error>> {
// get stream length
let len = {
let old_pos = asset.stream_position().await?;
let len = asset.seek(SeekFrom::End(0)).await?;
asset.seek(SeekFrom::Start(old_pos)).await?;
len
};
let mut resp = ResponseBuilder::new().header(CONTENT_TYPE, "video/mp4");
// if the webview sent a range header, we need to send a 206 in return
// Actually only macOS and Windows are supported. Linux will ALWAYS return empty headers.
let http_response = if let Some(range_header) = request.headers().get("range") {
let not_satisfiable = || {
ResponseBuilder::new()
.status(StatusCode::RANGE_NOT_SATISFIABLE)
.header(CONTENT_RANGE, format!("bytes */{len}"))
.body(vec![])
};
// parse range header
let ranges = if let Ok(ranges) = http_range::HttpRange::parse(range_header.to_str()?, len) {
ranges
.iter()
// map the output back to spec range <start-end>, example: 0-499
.map(|r| (r.start, r.start + r.length - 1))
.collect::<Vec<_>>()
} else {
return Ok(not_satisfiable()?);
};
/// The Maximum bytes we send in one range
const MAX_LEN: u64 = 1000 * 1024;
if ranges.len() == 1 {
let &(start, mut end) = ranges.first().unwrap();
// check if a range is not satisfiable
//
// this should be already taken care of by HttpRange::parse
// but checking here again for extra assurance
if start >= len || end >= len || end < start {
return Ok(not_satisfiable()?);
}
// adjust end byte for MAX_LEN
end = start + (end - start).min(len - start).min(MAX_LEN - 1);
// calculate number of bytes needed to be read
let bytes_to_read = end + 1 - start;
// allocate a buf with a suitable capacity
let mut buf = Vec::with_capacity(bytes_to_read as usize);
// seek the file to the starting byte
asset.seek(SeekFrom::Start(start)).await?;
// read the needed bytes
asset.take(bytes_to_read).read_to_end(&mut buf).await?;
resp = resp.header(CONTENT_RANGE, format!("bytes {start}-{end}/{len}"));
resp = resp.header(CONTENT_LENGTH, end + 1 - start);
resp = resp.status(StatusCode::PARTIAL_CONTENT);
resp.body(buf)
} else {
let mut buf = Vec::new();
let ranges = ranges
.iter()
.filter_map(|&(start, mut end)| {
// filter out unsatisfiable ranges
//
// this should be already taken care of by HttpRange::parse
// but checking here again for extra assurance
if start >= len || end >= len || end < start {
None
} else {
// adjust end byte for MAX_LEN
end = start + (end - start).min(len - start).min(MAX_LEN - 1);
Some((start, end))
}
})
.collect::<Vec<_>>();
let boundary = format!("{:x}", rand::random::<u64>());
let boundary_sep = format!("\r\n--{boundary}\r\n");
let boundary_closer = format!("\r\n--{boundary}\r\n");
resp = resp.header(
CONTENT_TYPE,
format!("multipart/byteranges; boundary={boundary}"),
);
for (end, start) in ranges {
// a new range is being written, write the range boundary
buf.write_all(boundary_sep.as_bytes()).await?;
// write the needed headers `Content-Type` and `Content-Range`
buf.write_all(format!("{CONTENT_TYPE}: video/mp4\r\n").as_bytes())
.await?;
buf.write_all(format!("{CONTENT_RANGE}: bytes {start}-{end}/{len}\r\n").as_bytes())
.await?;
// write the separator to indicate the start of the range body
buf.write_all("\r\n".as_bytes()).await?;
// calculate number of bytes needed to be read
let bytes_to_read = end + 1 - start;
let mut local_buf = vec![0_u8; bytes_to_read as usize];
asset.seek(SeekFrom::Start(start)).await?;
asset.read_exact(&mut local_buf).await?;
buf.extend_from_slice(&local_buf);
}
// all ranges have been written, write the closing boundary
buf.write_all(boundary_closer.as_bytes()).await?;
resp.body(buf)
}
} else {
resp = resp.header(CONTENT_LENGTH, len);
let mut buf = Vec::with_capacity(len as usize);
asset.read_to_end(&mut buf).await?;
resp.body(buf)
};
http_response.map_err(Into::into)
}
+1 -1
View File
@@ -1,7 +1,7 @@
use dioxus::prelude::*; use dioxus::prelude::*;
use dioxus_desktop::tao::event::Event as WryEvent;
use dioxus_desktop::tao::event::WindowEvent; use dioxus_desktop::tao::event::WindowEvent;
use dioxus_desktop::use_wry_event_handler; use dioxus_desktop::use_wry_event_handler;
use dioxus_desktop::wry::application::event::Event as WryEvent;
use dioxus_desktop::{Config, WindowCloseBehaviour}; use dioxus_desktop::{Config, WindowCloseBehaviour};
fn main() { fn main() {
+2 -4
View File
@@ -1,12 +1,10 @@
use dioxus::prelude::*; use dioxus::prelude::*;
use dioxus_desktop::use_window;
fn main() { fn main() {
dioxus_desktop::launch(app); dioxus_desktop::launch(app);
} }
fn app(cx: Scope) -> Element { fn app(cx: Scope) -> Element {
let window = use_window(cx);
let level = use_state(cx, || 1.0); let level = use_state(cx, || 1.0);
cx.render(rsx! { cx.render(rsx! {
@@ -14,9 +12,9 @@ fn app(cx: Scope) -> Element {
r#type: "number", r#type: "number",
value: "{level}", value: "{level}",
oninput: |e| { oninput: |e| {
if let Ok(new_zoom) = e.value.parse::<f64>() { if let Ok(new_zoom) = e.value().parse::<f64>() {
level.set(new_zoom); level.set(new_zoom);
window.webview.zoom(new_zoom); dioxus_desktop::window().webview.zoom(new_zoom);
} }
} }
} }
+1 -1
View File
@@ -20,7 +20,7 @@ fn app(cx: Scope) -> Element {
input { input {
value: "{contents}", value: "{contents}",
r#type: "text", r#type: "text",
oninput: move |e| contents.set(e.value.clone()), oninput: move |e| contents.set(e.value()),
} }
} }
}) })
Generated
+247
View File
@@ -0,0 +1,247 @@
{
"nodes": {
"crane": {
"inputs": {
"flake-compat": "flake-compat",
"flake-utils": "flake-utils",
"nixpkgs": [
"nixpkgs"
],
"rust-overlay": "rust-overlay"
},
"locked": {
"lastModified": 1696384830,
"narHash": "sha256-j8ZsVqzmj5sOm5MW9cqwQJUZELFFwOislDmqDDEMl6k=",
"owner": "ipetkov",
"repo": "crane",
"rev": "f2143cd27f8bd09ee4f0121336c65015a2a0a19c",
"type": "github"
},
"original": {
"owner": "ipetkov",
"repo": "crane",
"type": "github"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1696267196,
"narHash": "sha256-AAQ/2sD+0D18bb8hKuEEVpHUYD1GmO2Uh/taFamn6XQ=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "4f910c9827911b1ec2bf26b5a062cd09f8d89f85",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-parts": {
"inputs": {
"nixpkgs-lib": "nixpkgs-lib"
},
"locked": {
"lastModified": 1696343447,
"narHash": "sha256-B2xAZKLkkeRFG5XcHHSXXcP7To9Xzr59KXeZiRf4vdQ=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "c9afaba3dfa4085dbd2ccb38dfade5141e33d9d4",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1694529238,
"narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_2": {
"inputs": {
"systems": "systems_2"
},
"locked": {
"lastModified": 1681202837,
"narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "cfacdce06f30d2b68473a46042957675eebb3401",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1697009197,
"narHash": "sha256-viVRhBTFT8fPJTb1N3brQIpFZnttmwo3JVKNuWRVc3s=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "01441e14af5e29c9d27ace398e6dd0b293e25a54",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-lib": {
"locked": {
"dir": "lib",
"lastModified": 1696019113,
"narHash": "sha256-X3+DKYWJm93DRSdC5M6K5hLqzSya9BjibtBsuARoPco=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "f5892ddac112a1e9b3612c39af1b72987ee5783a",
"type": "github"
},
"original": {
"dir": "lib",
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1681358109,
"narHash": "sha256-eKyxW4OohHQx9Urxi7TQlFBTDWII+F+x2hklDOQPB50=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "96ba1c52e54e74c3197f4d43026b3f3d92e83ff9",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"crane": "crane",
"flake-parts": "flake-parts",
"nixpkgs": "nixpkgs",
"rust-overlay": "rust-overlay_2",
"systems": "systems_3"
}
},
"rust-overlay": {
"inputs": {
"flake-utils": [
"crane",
"flake-utils"
],
"nixpkgs": [
"crane",
"nixpkgs"
]
},
"locked": {
"lastModified": 1696299134,
"narHash": "sha256-RS77cAa0N+Sfj5EmKbm5IdncNXaBCE1BSSQvUE8exvo=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "611ccdceed92b4d94ae75328148d84ee4a5b462d",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
},
"rust-overlay_2": {
"inputs": {
"flake-utils": "flake-utils_2",
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1697076655,
"narHash": "sha256-NcCtVUOd0X81srZkrdP8qoA1BMsPdO2tGtlZpsGijeU=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "aa7584f5bbf5947716ad8ec14eccc0334f0d28f0",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_2": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_3": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}
+63
View File
@@ -0,0 +1,63 @@
{
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
flake-parts.url = "github:hercules-ci/flake-parts";
systems.url = "github:nix-systems/default";
rust-overlay.url = "github:oxalica/rust-overlay";
crane.url = "github:ipetkov/crane";
crane.inputs.nixpkgs.follows = "nixpkgs";
};
outputs = inputs:
inputs.flake-parts.lib.mkFlake { inherit inputs; } {
systems = import inputs.systems;
perSystem = { config, self', pkgs, lib, system, ... }:
let
rustToolchain = pkgs.rust-bin.stable.latest.default.override {
extensions = [
"rust-src"
"rust-analyzer"
"clippy"
];
};
rustBuildInputs = [
pkgs.openssl
pkgs.libiconv
pkgs.pkg-config
] ++ lib.optionals pkgs.stdenv.isDarwin (with pkgs.darwin.apple_sdk.frameworks; [
IOKit
Carbon
WebKit
Security
Cocoa
]);
# This is useful when building crates as packages
# Note that it does require a `Cargo.lock` which this repo does not have
# craneLib = (inputs.crane.mkLib pkgs).overrideToolchain rustToolchain;
in
{
_module.args.pkgs = import inputs.nixpkgs {
inherit system;
overlays = [
inputs.rust-overlay.overlays.default
];
};
devShells.default = pkgs.mkShell {
name = "dioxus-dev";
buildInputs = rustBuildInputs;
nativeBuildInputs = [
# Add shell dependencies here
rustToolchain
];
shellHook = ''
# For rust-analyzer 'hover' tooltips to work.
export RUST_SRC_PATH="${rustToolchain}/lib/rustlib/src/rust/library";
'';
};
};
};
}
+6 -5
View File
@@ -8,13 +8,14 @@ use std::fmt::{Result, Write};
use dioxus_rsx::IfmtInput; use dioxus_rsx::IfmtInput;
use crate::write_ifmt; use crate::{indent::IndentOptions, write_ifmt};
/// The output buffer that tracks indent and string /// The output buffer that tracks indent and string
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct Buffer { pub struct Buffer {
pub buf: String, pub buf: String,
pub indent: usize, pub indent_level: usize,
pub indent: IndentOptions,
} }
impl Buffer { impl Buffer {
@@ -31,16 +32,16 @@ impl Buffer {
} }
pub fn tab(&mut self) -> Result { pub fn tab(&mut self) -> Result {
self.write_tabs(self.indent) self.write_tabs(self.indent_level)
} }
pub fn indented_tab(&mut self) -> Result { pub fn indented_tab(&mut self) -> Result {
self.write_tabs(self.indent + 1) self.write_tabs(self.indent_level + 1)
} }
pub fn write_tabs(&mut self, num: usize) -> std::fmt::Result { pub fn write_tabs(&mut self, num: usize) -> std::fmt::Result {
for _ in 0..num { for _ in 0..num {
write!(self.buf, " ")? write!(self.buf, "{}", self.indent.indent_str())?
} }
Ok(()) Ok(())
} }
+63 -33
View File
@@ -49,6 +49,7 @@ impl Writer<'_> {
attributes, attributes,
children, children,
brace, brace,
..
} = el; } = el;
/* /*
@@ -66,7 +67,7 @@ impl Writer<'_> {
// check if we have a lot of attributes // check if we have a lot of attributes
let attr_len = self.is_short_attrs(attributes); let attr_len = self.is_short_attrs(attributes);
let is_short_attr_list = (attr_len + self.out.indent * 4) < 80; let is_short_attr_list = (attr_len + self.out.indent_level * 4) < 80;
let children_len = self.is_short_children(children); let children_len = self.is_short_children(children);
let is_small_children = children_len.is_some(); let is_small_children = children_len.is_some();
@@ -86,7 +87,7 @@ impl Writer<'_> {
// if we have few children and few attributes, make it a one-liner // if we have few children and few attributes, make it a one-liner
if is_short_attr_list && is_small_children { if is_short_attr_list && is_small_children {
if children_len.unwrap() + attr_len + self.out.indent * 4 < 100 { if children_len.unwrap() + attr_len + self.out.indent_level * 4 < 100 {
opt_level = ShortOptimization::Oneliner; opt_level = ShortOptimization::Oneliner;
} else { } else {
opt_level = ShortOptimization::PropsOnTop; opt_level = ShortOptimization::PropsOnTop;
@@ -165,7 +166,7 @@ impl Writer<'_> {
fn write_attributes( fn write_attributes(
&mut self, &mut self,
attributes: &[ElementAttrNamed], attributes: &[AttributeType],
key: &Option<IfmtInput>, key: &Option<IfmtInput>,
sameline: bool, sameline: bool,
) -> Result { ) -> Result {
@@ -185,11 +186,11 @@ impl Writer<'_> {
} }
while let Some(attr) = attr_iter.next() { while let Some(attr) = attr_iter.next() {
self.out.indent += 1; self.out.indent_level += 1;
if !sameline { if !sameline {
self.write_comments(attr.attr.start())?; self.write_comments(attr.start())?;
} }
self.out.indent -= 1; self.out.indent_level -= 1;
if !sameline { if !sameline {
self.out.indented_tabbed_line()?; self.out.indented_tabbed_line()?;
@@ -209,12 +210,34 @@ impl Writer<'_> {
Ok(()) Ok(())
} }
fn write_attribute(&mut self, attr: &ElementAttrNamed) -> Result { fn write_attribute_name(&mut self, attr: &ElementAttrName) -> Result {
match &attr.attr { match attr {
ElementAttr::AttrText { name, value } => { ElementAttrName::BuiltIn(name) => {
write!(self.out, "{name}: {value}", value = ifmt_to_string(value))?; write!(self.out, "{}", name)?;
} }
ElementAttr::AttrExpression { name, value } => { ElementAttrName::Custom(name) => {
write!(self.out, "{}", name.to_token_stream())?;
}
}
Ok(())
}
fn write_attribute_value(&mut self, value: &ElementAttrValue) -> Result {
match value {
ElementAttrValue::AttrOptionalExpr { condition, value } => {
write!(
self.out,
"if {condition} {{ ",
condition = prettyplease::unparse_expr(condition),
)?;
self.write_attribute_value(value)?;
write!(self.out, " }}")?;
}
ElementAttrValue::AttrLiteral(value) => {
write!(self.out, "{value}", value = ifmt_to_string(value))?;
}
ElementAttrValue::AttrExpr(value) => {
let out = prettyplease::unparse_expr(value); let out = prettyplease::unparse_expr(value);
let mut lines = out.split('\n').peekable(); let mut lines = out.split('\n').peekable();
let first = lines.next().unwrap(); let first = lines.next().unwrap();
@@ -222,9 +245,9 @@ impl Writer<'_> {
// a one-liner for whatever reason // a one-liner for whatever reason
// Does not need a new line // Does not need a new line
if lines.peek().is_none() { if lines.peek().is_none() {
write!(self.out, "{name}: {first}")?; write!(self.out, "{first}")?;
} else { } else {
writeln!(self.out, "{name}: {first}")?; writeln!(self.out, "{first}")?;
while let Some(line) = lines.next() { while let Some(line) = lines.next() {
self.out.indented_tab()?; self.out.indented_tab()?;
@@ -237,22 +260,7 @@ impl Writer<'_> {
} }
} }
} }
ElementAttrValue::EventTokens(tokens) => {
ElementAttr::CustomAttrText { name, value } => {
write!(
self.out,
"{name}: {value}",
name = name.to_token_stream(),
value = ifmt_to_string(value)
)?;
}
ElementAttr::CustomAttrExpression { name, value } => {
let out = prettyplease::unparse_expr(value);
write!(self.out, "{}: {}", name.to_token_stream(), out)?;
}
ElementAttr::EventTokens { name, tokens } => {
let out = self.retrieve_formatted_expr(tokens).to_string(); let out = self.retrieve_formatted_expr(tokens).to_string();
let mut lines = out.split('\n').peekable(); let mut lines = out.split('\n').peekable();
@@ -261,9 +269,9 @@ impl Writer<'_> {
// a one-liner for whatever reason // a one-liner for whatever reason
// Does not need a new line // Does not need a new line
if lines.peek().is_none() { if lines.peek().is_none() {
write!(self.out, "{name}: {first}")?; write!(self.out, "{first}")?;
} else { } else {
writeln!(self.out, "{name}: {first}")?; writeln!(self.out, "{first}")?;
while let Some(line) = lines.next() { while let Some(line) = lines.next() {
self.out.indented_tab()?; self.out.indented_tab()?;
@@ -281,6 +289,28 @@ impl Writer<'_> {
Ok(()) Ok(())
} }
fn write_attribute(&mut self, attr: &AttributeType) -> Result {
match attr {
AttributeType::Named(attr) => self.write_named_attribute(attr),
AttributeType::Spread(attr) => self.write_spread_attribute(attr),
}
}
fn write_named_attribute(&mut self, attr: &ElementAttrNamed) -> Result {
self.write_attribute_name(&attr.attr.name)?;
write!(self.out, ": ")?;
self.write_attribute_value(&attr.attr.value)?;
Ok(())
}
fn write_spread_attribute(&mut self, attr: &Expr) -> Result {
write!(self.out, "..")?;
write!(self.out, "{}", prettyplease::unparse_expr(attr))?;
Ok(())
}
// make sure the comments are actually relevant to this element. // make sure the comments are actually relevant to this element.
// test by making sure this element is the primary element on this line // test by making sure this element is the primary element on this line
pub fn current_span_is_primary(&self, location: Span) -> bool { pub fn current_span_is_primary(&self, location: Span) -> bool {
@@ -398,14 +428,14 @@ impl Writer<'_> {
for idx in start.line..end.line { for idx in start.line..end.line {
let line = &self.src[idx]; let line = &self.src[idx];
if line.trim().starts_with("//") { if line.trim().starts_with("//") {
for _ in 0..self.out.indent + 1 { for _ in 0..self.out.indent_level + 1 {
write!(self.out, " ")? write!(self.out, " ")?
} }
writeln!(self.out, "{}", line.trim()).unwrap(); writeln!(self.out, "{}", line.trim()).unwrap();
} }
} }
for _ in 0..self.out.indent { for _ in 0..self.out.indent_level {
write!(self.out, " ")? write!(self.out, " ")?
} }
+3 -3
View File
@@ -29,7 +29,7 @@ impl Writer<'_> {
let first_line = &self.src[start.line - 1]; let first_line = &self.src[start.line - 1];
write!(self.out, "{}", &first_line[start.column - 1..].trim_start())?; write!(self.out, "{}", &first_line[start.column - 1..].trim_start())?;
let prev_block_indent_level = crate::leading_whitespaces(first_line) / 4; let prev_block_indent_level = self.out.indent.count_indents(first_line);
for (id, line) in self.src[start.line..end.line].iter().enumerate() { for (id, line) in self.src[start.line..end.line].iter().enumerate() {
writeln!(self.out)?; writeln!(self.out)?;
@@ -43,9 +43,9 @@ impl Writer<'_> {
}; };
// trim the leading whitespace // trim the leading whitespace
let previous_indent = crate::leading_whitespaces(line) / 4; let previous_indent = self.out.indent.count_indents(line);
let offset = previous_indent.saturating_sub(prev_block_indent_level); let offset = previous_indent.saturating_sub(prev_block_indent_level);
let required_indent = self.out.indent + offset; let required_indent = self.out.indent_level + offset;
self.out.write_tabs(required_indent)?; self.out.write_tabs(required_indent)?;
let line = line.trim_start(); let line = line.trim_start();
+108
View File
@@ -0,0 +1,108 @@
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum IndentType {
Spaces,
Tabs,
}
#[derive(Debug, Clone)]
pub struct IndentOptions {
width: usize,
indent_string: String,
}
impl IndentOptions {
pub fn new(typ: IndentType, width: usize) -> Self {
assert_ne!(width, 0, "Cannot have an indent width of 0");
Self {
width,
indent_string: match typ {
IndentType::Tabs => "\t".into(),
IndentType::Spaces => " ".repeat(width),
},
}
}
/// Gets a string containing one indent worth of whitespace
pub fn indent_str(&self) -> &str {
&self.indent_string
}
/// Computes the line length in characters, counting tabs as the indent width.
pub fn line_length(&self, line: &str) -> usize {
line.chars()
.map(|ch| if ch == '\t' { self.width } else { 1 })
.sum()
}
/// Estimates how many times the line has been indented.
pub fn count_indents(&self, mut line: &str) -> usize {
let mut indent = 0;
while !line.is_empty() {
// Try to count tabs
let num_tabs = line.chars().take_while(|ch| *ch == '\t').count();
if num_tabs > 0 {
indent += num_tabs;
line = &line[num_tabs..];
continue;
}
// Try to count spaces
let num_spaces = line.chars().take_while(|ch| *ch == ' ').count();
if num_spaces >= self.width {
// Intentionally floor here to take only the amount of space that matches an indent
let num_space_indents = num_spaces / self.width;
indent += num_space_indents;
line = &line[num_space_indents * self.width..];
continue;
}
// Line starts with either non-indent characters or an unevent amount of spaces,
// so no more indent remains.
break;
}
indent
}
}
impl Default for IndentOptions {
fn default() -> Self {
Self::new(IndentType::Spaces, 4)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn count_indents() {
assert_eq!(
IndentOptions::new(IndentType::Spaces, 4).count_indents("no indentation here!"),
0
);
assert_eq!(
IndentOptions::new(IndentType::Spaces, 4).count_indents(" v += 2"),
1
);
assert_eq!(
IndentOptions::new(IndentType::Spaces, 4).count_indents(" v += 2"),
2
);
assert_eq!(
IndentOptions::new(IndentType::Spaces, 4).count_indents(" v += 2"),
2
);
assert_eq!(
IndentOptions::new(IndentType::Spaces, 4).count_indents("\t\tv += 2"),
2
);
assert_eq!(
IndentOptions::new(IndentType::Spaces, 4).count_indents("\t\t v += 2"),
2
);
assert_eq!(
IndentOptions::new(IndentType::Spaces, 2).count_indents(" v += 2"),
2
);
}
}
+16 -15
View File
@@ -1,3 +1,7 @@
#![doc = include_str!("../README.md")]
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
use std::fmt::{Display, Write}; use std::fmt::{Display, Write};
use crate::writer::*; use crate::writer::*;
@@ -12,8 +16,11 @@ mod collect_macros;
mod component; mod component;
mod element; mod element;
mod expr; mod expr;
mod indent;
mod writer; mod writer;
pub use indent::{IndentOptions, IndentType};
/// A modification to the original file to be applied by an IDE /// A modification to the original file to be applied by an IDE
/// ///
/// Right now this re-writes entire rsx! blocks at a time, instead of precise line-by-line changes. /// Right now this re-writes entire rsx! blocks at a time, instead of precise line-by-line changes.
@@ -43,7 +50,7 @@ pub struct FormattedBlock {
/// back to the file precisely. /// back to the file precisely.
/// ///
/// Nested blocks of RSX will be handled automatically /// Nested blocks of RSX will be handled automatically
pub fn fmt_file(contents: &str) -> Vec<FormattedBlock> { pub fn fmt_file(contents: &str, indent: IndentOptions) -> Vec<FormattedBlock> {
let mut formatted_blocks = Vec::new(); let mut formatted_blocks = Vec::new();
let parsed = syn::parse_file(contents).unwrap(); let parsed = syn::parse_file(contents).unwrap();
@@ -57,6 +64,7 @@ pub fn fmt_file(contents: &str) -> Vec<FormattedBlock> {
} }
let mut writer = Writer::new(contents); let mut writer = Writer::new(contents);
writer.out.indent = indent;
// Don't parse nested macros // Don't parse nested macros
let mut end_span = LineColumn { column: 0, line: 0 }; let mut end_span = LineColumn { column: 0, line: 0 };
@@ -72,7 +80,10 @@ pub fn fmt_file(contents: &str) -> Vec<FormattedBlock> {
let rsx_start = macro_path.span().start(); let rsx_start = macro_path.span().start();
writer.out.indent = leading_whitespaces(writer.src[rsx_start.line - 1]) / 4; writer.out.indent_level = writer
.out
.indent
.count_indents(writer.src[rsx_start.line - 1]);
write_body(&mut writer, &body); write_body(&mut writer, &body);
@@ -155,12 +166,13 @@ pub fn fmt_block_from_expr(raw: &str, expr: ExprMacro) -> Option<String> {
buf.consume() buf.consume()
} }
pub fn fmt_block(block: &str, indent_level: usize) -> Option<String> { pub fn fmt_block(block: &str, indent_level: usize, indent: IndentOptions) -> Option<String> {
let body = syn::parse_str::<dioxus_rsx::CallBody>(block).unwrap(); let body = syn::parse_str::<dioxus_rsx::CallBody>(block).unwrap();
let mut buf = Writer::new(block); let mut buf = Writer::new(block);
buf.out.indent = indent_level; buf.out.indent = indent;
buf.out.indent_level = indent_level;
write_body(&mut buf, &body); write_body(&mut buf, &body);
@@ -226,14 +238,3 @@ pub(crate) fn write_ifmt(input: &IfmtInput, writable: &mut impl Write) -> std::f
let display = DisplayIfmt(input); let display = DisplayIfmt(input);
write!(writable, "{}", display) write!(writable, "{}", display)
} }
pub fn leading_whitespaces(input: &str) -> usize {
input
.chars()
.map_while(|c| match c {
' ' => Some(1),
'\t' => Some(4),
_ => None,
})
.sum()
}
+56 -38
View File
@@ -1,4 +1,4 @@
use dioxus_rsx::{BodyNode, ElementAttr, ElementAttrNamed, ForLoop}; use dioxus_rsx::{AttributeType, BodyNode, ElementAttrValue, ForLoop};
use proc_macro2::{LineColumn, Span}; use proc_macro2::{LineColumn, Span};
use quote::ToTokens; use quote::ToTokens;
use std::{ use std::{
@@ -96,11 +96,11 @@ impl<'a> Writer<'a> {
// Push out the indent level and write each component, line by line // Push out the indent level and write each component, line by line
pub fn write_body_indented(&mut self, children: &[BodyNode]) -> Result { pub fn write_body_indented(&mut self, children: &[BodyNode]) -> Result {
self.out.indent += 1; self.out.indent_level += 1;
self.write_body_no_indent(children)?; self.write_body_no_indent(children)?;
self.out.indent -= 1; self.out.indent_level -= 1;
Ok(()) Ok(())
} }
@@ -132,12 +132,45 @@ impl<'a> Writer<'a> {
Ok(()) Ok(())
} }
pub(crate) fn is_short_attrs(&mut self, attributes: &[ElementAttrNamed]) -> usize { pub(crate) fn attr_value_len(&mut self, value: &ElementAttrValue) -> usize {
match value {
ElementAttrValue::AttrOptionalExpr { condition, value } => {
let condition_len = self.retrieve_formatted_expr(condition).len();
let value_len = self.attr_value_len(value);
condition_len + value_len + 6
}
ElementAttrValue::AttrLiteral(lit) => ifmt_to_string(lit).len(),
ElementAttrValue::AttrExpr(expr) => expr.span().line_length(),
ElementAttrValue::EventTokens(tokens) => {
let location = Location::new(tokens.span().start());
let len = if let std::collections::hash_map::Entry::Vacant(e) =
self.cached_formats.entry(location)
{
let formatted = prettyplease::unparse_expr(tokens);
let len = if formatted.contains('\n') {
10000
} else {
formatted.len()
};
e.insert(formatted);
len
} else {
self.cached_formats[&location].len()
};
len
}
}
}
pub(crate) fn is_short_attrs(&mut self, attributes: &[AttributeType]) -> usize {
let mut total = 0; let mut total = 0;
for attr in attributes { for attr in attributes {
if self.current_span_is_primary(attr.attr.start()) { if self.current_span_is_primary(attr.start()) {
'line: for line in self.src[..attr.attr.start().start().line - 1].iter().rev() { 'line: for line in self.src[..attr.start().start().line - 1].iter().rev() {
match (line.trim().starts_with("//"), line.is_empty()) { match (line.trim().starts_with("//"), line.is_empty()) {
(true, _) => return 100000, (true, _) => return 100000,
(_, true) => continue 'line, (_, true) => continue 'line,
@@ -146,40 +179,25 @@ impl<'a> Writer<'a> {
} }
} }
total += match &attr.attr { match attr {
ElementAttr::AttrText { value, name } => { AttributeType::Named(attr) => {
ifmt_to_string(value).len() + name.span().line_length() + 6 let name_len = match &attr.attr.name {
} dioxus_rsx::ElementAttrName::BuiltIn(name) => {
ElementAttr::AttrExpression { name, value } => { let name = name.to_string();
value.span().line_length() + name.span().line_length() + 6 name.len()
} }
ElementAttr::CustomAttrText { value, name } => { dioxus_rsx::ElementAttrName::Custom(name) => name.value().len() + 2,
ifmt_to_string(value).len() + name.to_token_stream().to_string().len() + 6
}
ElementAttr::CustomAttrExpression { name, value } => {
name.to_token_stream().to_string().len() + value.span().line_length() + 6
}
ElementAttr::EventTokens { tokens, name } => {
let location = Location::new(tokens.span().start());
let len = if let std::collections::hash_map::Entry::Vacant(e) =
self.cached_formats.entry(location)
{
let formatted = prettyplease::unparse_expr(tokens);
let len = if formatted.contains('\n') {
10000
} else {
formatted.len()
};
e.insert(formatted);
len
} else {
self.cached_formats[&location].len()
}; };
total += name_len;
len + name.span().line_length() + 6 total += self.attr_value_len(&attr.attr.value);
}
AttributeType::Spread(expr) => {
let expr_len = self.retrieve_formatted_expr(expr).len();
total += expr_len + 3;
} }
}; };
total += 6;
} }
total total
@@ -218,7 +236,7 @@ impl<'a> Writer<'a> {
} }
} }
trait SpanLength { pub(crate) trait SpanLength {
fn line_length(&self) -> usize; fn line_length(&self) -> usize;
} }
impl SpanLength for Span { impl SpanLength for Span {
+1 -1
View File
@@ -12,7 +12,7 @@ macro_rules! twoway {
#[test] #[test]
fn $name() { fn $name() {
let src = include_str!(concat!("./samples/", stringify!($name), ".rsx")); let src = include_str!(concat!("./samples/", stringify!($name), ".rsx"));
let formatted = dioxus_autofmt::fmt_file(src); let formatted = dioxus_autofmt::fmt_file(src, Default::default());
let out = dioxus_autofmt::apply_formats(src, formatted); let out = dioxus_autofmt::apply_formats(src, formatted);
// normalize line endings // normalize line endings
let out = out.replace("\r", ""); let out = out.replace("\r", "");
+1 -1
View File
@@ -33,7 +33,7 @@ rsx! {
} }
// No children, minimal props // No children, minimal props
img { class: "mb-6 mx-auto h-24", src: "artemis-assets/images/friends.png", alt: "" } img { class: "mb-6 mx-auto h-24", src: "artemis-assets/images/friends.png" }
// One level compression // One level compression
div { div {
+10 -5
View File
@@ -1,10 +1,12 @@
use dioxus_autofmt::{IndentOptions, IndentType};
macro_rules! twoway { macro_rules! twoway {
($val:literal => $name:ident) => { ($val:literal => $name:ident ($indent:expr)) => {
#[test] #[test]
fn $name() { fn $name() {
let src_right = include_str!(concat!("./wrong/", $val, ".rsx")); let src_right = include_str!(concat!("./wrong/", $val, ".rsx"));
let src_wrong = include_str!(concat!("./wrong/", $val, ".wrong.rsx")); let src_wrong = include_str!(concat!("./wrong/", $val, ".wrong.rsx"));
let formatted = dioxus_autofmt::fmt_file(src_wrong); let formatted = dioxus_autofmt::fmt_file(src_wrong, $indent);
let out = dioxus_autofmt::apply_formats(src_wrong, formatted); let out = dioxus_autofmt::apply_formats(src_wrong, formatted);
// normalize line endings // normalize line endings
@@ -16,8 +18,11 @@ macro_rules! twoway {
}; };
} }
twoway!("comments" => comments); twoway!("comments-4sp" => comments_4sp (IndentOptions::new(IndentType::Spaces, 4)));
twoway!("comments-tab" => comments_tab (IndentOptions::new(IndentType::Tabs, 4)));
twoway!("multi" => multi); twoway!("multi-4sp" => multi_4sp (IndentOptions::new(IndentType::Spaces, 4)));
twoway!("multi-tab" => multi_tab (IndentOptions::new(IndentType::Tabs, 4)));
twoway!("multiexpr" => multiexpr); twoway!("multiexpr-4sp" => multiexpr_4sp (IndentOptions::new(IndentType::Spaces, 4)));
twoway!("multiexpr-tab" => multiexpr_tab (IndentOptions::new(IndentType::Tabs, 4)));
@@ -0,0 +1,7 @@
rsx! {
div {
// Comments
class: "asdasd",
"hello world"
}
}
@@ -0,0 +1,5 @@
rsx! {
div {
// Comments
class: "asdasd", "hello world" }
}
@@ -0,0 +1,3 @@
fn app(cx: Scope) -> Element {
cx.render(rsx! { div { "hello world" } })
}
@@ -0,0 +1,5 @@
fn app(cx: Scope) -> Element {
cx.render(rsx! {
div {"hello world" }
})
}
@@ -0,0 +1,8 @@
fn ItWroks() {
cx.render(rsx! {
div { class: "flex flex-wrap items-center dark:text-white py-16 border-t font-light",
left,
right
}
})
}
@@ -0,0 +1,5 @@
fn ItWroks() {
cx.render(rsx! {
div { class: "flex flex-wrap items-center dark:text-white py-16 border-t font-light", left, right }
})
}
-3
View File
@@ -11,13 +11,10 @@ keywords = ["dom", "ui", "gui", "react"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
dioxus-rsx = { workspace = true }
proc-macro2 = { version = "1.0.6", features = ["span-locations"] } proc-macro2 = { version = "1.0.6", features = ["span-locations"] }
quote = "1.0" quote = "1.0"
syn = { version = "1.0.11", features = ["full", "extra-traits", "visit"] } syn = { version = "1.0.11", features = ["full", "extra-traits", "visit"] }
serde = { version = "1.0.136", features = ["derive"] }
owo-colors = { version = "3.5.0", features = ["supports-colors"] } owo-colors = { version = "3.5.0", features = ["supports-colors"] }
prettyplease = { workspace = true }
[dev-dependencies] [dev-dependencies]
indoc = "2.0.3" indoc = "2.0.3"
+2 -2
View File
@@ -6,7 +6,7 @@
[![Discord chat][discord-badge]][discord-url] [![Discord chat][discord-badge]][discord-url]
[crates-badge]: https://img.shields.io/crates/v/dioxus-autofmt.svg [crates-badge]: https://img.shields.io/crates/v/dioxus-autofmt.svg
[crates-url]: https://crates.io/crates/dioxus-autofmt [crates-url]: https://crates.io/crates/dioxus-check
[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg [mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg
[mit-url]: https://github.com/dioxuslabs/dioxus/blob/master/LICENSE [mit-url]: https://github.com/dioxuslabs/dioxus/blob/master/LICENSE
[actions-badge]: https://github.com/dioxuslabs/dioxus/actions/workflows/main.yml/badge.svg [actions-badge]: https://github.com/dioxuslabs/dioxus/actions/workflows/main.yml/badge.svg
@@ -16,7 +16,7 @@
[Website](https://dioxuslabs.com) | [Website](https://dioxuslabs.com) |
[Guides](https://dioxuslabs.com/learn/0.4/) | [Guides](https://dioxuslabs.com/learn/0.4/) |
[API Docs](https://docs.rs/dioxus-autofmt/latest/dioxus_autofmt) | [API Docs](https://docs.rs/dioxus-check) |
[Chat](https://discord.gg/XgGxMSkvUM) [Chat](https://discord.gg/XgGxMSkvUM)
## Overview ## Overview
+4
View File
@@ -1,3 +1,7 @@
#![doc = include_str!("../README.md")]
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
mod check; mod check;
mod issues; mod issues;
mod metadata; mod metadata;
-31
View File
@@ -1,31 +0,0 @@
# .github/workflows/build.yml
on:
release:
types: [created]
jobs:
release:
name: release ${{ matrix.target }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- target: x86_64-unknown-linux-gnu
archive: tar.gz tar.xz
- target: x86_64-unknown-linux-musl
archive: tar.gz tar.xz
- target: x86_64-apple-darwin
archive: tar.gz tar.xz
- target: x86_64-pc-windows-gnu
archive: zip
steps:
- uses: actions/checkout@master
- name: Compile and release
uses: rust-build/rust-build.action@latest
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RUSTTARGET: ${{ matrix.target }}
ARCHIVE_TYPES: ${{ matrix.archive }}
-34
View File
@@ -1,34 +0,0 @@
name: github pages
on:
push:
paths:
- docs/**
branches:
- master
jobs:
deploy:
runs-on: ubuntu-20.04
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
steps:
- uses: actions/checkout@v2
- name: Setup mdBook
uses: peaceiris/actions-mdbook@v1
with:
mdbook-version: '0.4.10'
# mdbook-version: 'latest'
- run: cd docs && mdbook build
- name: Deploy 🚀
uses: JamesIves/github-pages-deploy-action@v4.2.3
with:
branch: gh-pages # The branch the action should deploy to.
folder: docs/book # The folder the action should deploy.
target-folder: docs/nightly/cli
repository-name: dioxuslabs/docsite
clean: false
token: ${{ secrets.DEPLOY_KEY }} # let's pretend I don't need it for now
-52
View File
@@ -1,52 +0,0 @@
on: [push, pull_request]
name: Rust CI
jobs:
check:
name: Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v1
- run: cargo check
test:
name: Test Suite
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v1
- run: cargo test
fmt:
name: Rustfmt
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v1
- run: rustup component add rustfmt
- run: cargo fmt --all -- --check
# clippy:
# name: Clippy
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v2
# - uses: actions-rs/toolchain@v1
# with:
# profile: minimal
# toolchain: stable
# override: true
# - uses: Swatinem/rust-cache@v1
# - run: rustup component add clippy
# - uses: actions-rs/cargo@v1
# with:
# command: clippy
# args: -- -D warnings
Loaded 100 of 406 files, more files were not shown because too many files have changed in this diff. Show more