mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-10 14:44:12 +00:00
Merge branch 'master' into add-file-data-drag-event
This commit is contained in:
commit
56798b3d1c
407 changed files with 16602 additions and 11298 deletions
5
.cargo/config.toml
Normal file
5
.cargo/config.toml
Normal 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
.github/workflows/cli_release.yml
vendored
2
.github/workflows/cli_release.yml
vendored
|
@ -36,6 +36,8 @@ jobs:
|
|||
toolchain: ${{ matrix.platform.toolchain }}
|
||||
targets: ${{ matrix.platform.target }}
|
||||
|
||||
- uses: ilammy/setup-nasm@v1
|
||||
|
||||
# Setup the Github Actions Cache for the CLI package
|
||||
- name: Setup cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
|
2
.github/workflows/docs stable.yml
vendored
2
.github/workflows/docs stable.yml
vendored
|
@ -33,7 +33,7 @@ jobs:
|
|||
# cd fermi && mdbook build -d ../nightly/fermi && cd ..
|
||||
|
||||
- name: Deploy 🚀
|
||||
uses: JamesIves/github-pages-deploy-action@v4.4.3
|
||||
uses: JamesIves/github-pages-deploy-action@v4.5.0
|
||||
with:
|
||||
branch: gh-pages # The branch the action should deploy to.
|
||||
folder: docs/nightly # The folder the action should deploy.
|
||||
|
|
2
.github/workflows/docs.yml
vendored
2
.github/workflows/docs.yml
vendored
|
@ -39,7 +39,7 @@ jobs:
|
|||
# cd fermi && mdbook build -d ../nightly/fermi && cd ..
|
||||
|
||||
- name: Deploy 🚀
|
||||
uses: JamesIves/github-pages-deploy-action@v4.4.3
|
||||
uses: JamesIves/github-pages-deploy-action@v4.5.0
|
||||
with:
|
||||
branch: gh-pages # The branch the action should deploy to.
|
||||
folder: docs/nightly # The folder the action should deploy.
|
||||
|
|
47
.github/workflows/main.yml
vendored
47
.github/workflows/main.yml
vendored
|
@ -36,9 +36,15 @@ jobs:
|
|||
if: github.event.pull_request.draft == false
|
||||
name: Check
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
CARGO_INCREMENTAL: 0
|
||||
SCCACHE_GHA_ENABLED: "true"
|
||||
RUSTC_WRAPPER: "sccache"
|
||||
steps:
|
||||
- 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 install libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev
|
||||
- uses: actions/checkout@v4
|
||||
|
@ -48,11 +54,17 @@ jobs:
|
|||
if: github.event.pull_request.draft == false
|
||||
name: Test Suite
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
CARGO_INCREMENTAL: 0
|
||||
SCCACHE_GHA_ENABLED: "true"
|
||||
RUSTC_WRAPPER: "sccache"
|
||||
steps:
|
||||
- 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 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: browser-actions/setup-firefox@latest
|
||||
- uses: jetli/wasm-pack-action@v0.4.0
|
||||
|
@ -63,9 +75,15 @@ jobs:
|
|||
if: github.event.pull_request.draft == false
|
||||
name: Rustfmt
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
CARGO_INCREMENTAL: 0
|
||||
SCCACHE_GHA_ENABLED: "true"
|
||||
RUSTC_WRAPPER: "sccache"
|
||||
steps:
|
||||
- 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
|
||||
- uses: actions/checkout@v4
|
||||
- run: cargo fmt --all -- --check
|
||||
|
@ -74,9 +92,15 @@ jobs:
|
|||
if: github.event.pull_request.draft == false
|
||||
name: Clippy
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
CARGO_INCREMENTAL: 0
|
||||
SCCACHE_GHA_ENABLED: "true"
|
||||
RUSTC_WRAPPER: "sccache"
|
||||
steps:
|
||||
- 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 install libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev
|
||||
- run: rustup component add clippy
|
||||
|
@ -86,6 +110,10 @@ jobs:
|
|||
matrix_test:
|
||||
runs-on: ${{ matrix.platform.os }}
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
CARGO_INCREMENTAL: 0
|
||||
SCCACHE_GHA_ENABLED: "true"
|
||||
RUSTC_WRAPPER: "sccache"
|
||||
RUST_CARGO_COMMAND: ${{ matrix.platform.cross == true && 'cross' || 'cargo' }}
|
||||
strategy:
|
||||
matrix:
|
||||
|
@ -125,7 +153,7 @@ jobs:
|
|||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: ilammy/setup-nasm@v1
|
||||
- name: install stable
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
|
@ -136,11 +164,16 @@ jobs:
|
|||
if: ${{ matrix.platform.cross == true }}
|
||||
uses: taiki-e/install-action@cross
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: mozilla-actions/sccache-action@v0.0.3
|
||||
with:
|
||||
workspaces: core -> ../target
|
||||
save-if: ${{ matrix.features.key == 'all' }}
|
||||
|
||||
- name: Install rustfmt
|
||||
run: rustup component add rustfmt
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: test
|
||||
run: |
|
||||
${{ env.RUST_CARGO_COMMAND }} ${{ matrix.platform.command }} ${{ matrix.platform.args }} --target ${{ matrix.platform.target }}
|
||||
|
|
8
.github/workflows/miri.yml
vendored
8
.github/workflows/miri.yml
vendored
|
@ -26,8 +26,8 @@ env:
|
|||
RUST_BACKTRACE: 1
|
||||
# Change to specific Rust release to pin
|
||||
rust_stable: stable
|
||||
rust_nightly: nightly-2022-11-03
|
||||
rust_clippy: 1.65.0
|
||||
rust_nightly: nightly-2023-11-16
|
||||
rust_clippy: 1.70.0
|
||||
# When updating this, also update:
|
||||
# - README.md
|
||||
# - tokio/README.md
|
||||
|
@ -70,6 +70,7 @@ jobs:
|
|||
run: echo "MIRIFLAGS=-Zmiri-tag-gc=1" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ilammy/setup-nasm@v1
|
||||
- name: Install Rust ${{ env.rust_nightly }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
|
@ -86,8 +87,7 @@ jobs:
|
|||
|
||||
# working-directory: tokio
|
||||
env:
|
||||
# todo: disable memory leaks ignore
|
||||
MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-strict-provenance -Zmiri-retag-fields -Zmiri-ignore-leaks
|
||||
MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-strict-provenance -Zmiri-retag-fields
|
||||
PROPTEST_CASES: 10
|
||||
|
||||
# Cache the global cargo directory, but NOT the local `target` directory which
|
||||
|
|
5
.github/workflows/playwright.yml
vendored
5
.github/workflows/playwright.yml
vendored
|
@ -20,7 +20,8 @@ jobs:
|
|||
steps:
|
||||
# Do our best to cache the toolchain and node install steps
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: ilammy/setup-nasm@v1
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 16
|
||||
- name: Install Rust
|
||||
|
@ -43,7 +44,7 @@ jobs:
|
|||
# args: --path packages/cli
|
||||
- name: Run Playwright tests
|
||||
run: npx playwright test
|
||||
- uses: actions/upload-artifact@v3
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -4,6 +4,7 @@
|
|||
/dist
|
||||
Cargo.lock
|
||||
.DS_Store
|
||||
/examples/assets/test_video.mp4
|
||||
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
|
|
171
CHANGELOG.md
171
CHANGELOG.md
|
@ -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>
|
||||
|
18
Cargo.toml
18
Cargo.toml
|
@ -9,6 +9,7 @@ members = [
|
|||
"packages/extension",
|
||||
"packages/router",
|
||||
"packages/html",
|
||||
"packages/html-internal-macro",
|
||||
"packages/hooks",
|
||||
"packages/web",
|
||||
"packages/ssr",
|
||||
|
@ -41,6 +42,7 @@ members = [
|
|||
"examples/tailwind",
|
||||
"examples/PWA-example",
|
||||
"examples/query_segments_demo",
|
||||
"examples/openid_connect_demo",
|
||||
# Playwright tests
|
||||
"playwright-tests/liveview",
|
||||
"playwright-tests/web",
|
||||
|
@ -49,7 +51,7 @@ members = [
|
|||
exclude = ["examples/mobile_demo"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.4.2"
|
||||
version = "0.4.3"
|
||||
|
||||
# dependencies that are shared across packages
|
||||
[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-router = { path = "packages/router", 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-web = { path = "packages/web", 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" }
|
||||
rsx-rosetta = { path = "packages/rsx-rosetta", version = "0.4.0" }
|
||||
dioxus-signals = { path = "packages/signals" }
|
||||
generational-box = { path = "packages/generational-box" }
|
||||
generational-box = { path = "packages/generational-box", version = "0.4.3" }
|
||||
dioxus-hot-reload = { path = "packages/hot-reload", version = "0.4.0" }
|
||||
dioxus-fullstack = { path = "packages/fullstack", version = "0.4.1" }
|
||||
dioxus_server_macro = { path = "packages/server-macro", version = "0.4.1" }
|
||||
|
@ -87,7 +90,7 @@ slab = "0.4.2"
|
|||
futures-channel = "0.3.21"
|
||||
futures-util = { version = "0.3", default-features = false }
|
||||
rustc-hash = "1.1.0"
|
||||
wasm-bindgen = "0.2.87"
|
||||
wasm-bindgen = "0.2.88"
|
||||
html_parser = "0.7.0"
|
||||
thiserror = "1.0.40"
|
||||
prettyplease = { package = "prettier-please", version = "0.2", features = [
|
||||
|
@ -98,7 +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
|
||||
[package]
|
||||
name = "dioxus-examples"
|
||||
version = "0.0.0"
|
||||
version = "0.4.3"
|
||||
authors = ["Jonathan Kelley"]
|
||||
edition = "2021"
|
||||
description = "Top level crate for the Dioxus repository"
|
||||
|
@ -132,3 +135,8 @@ fern = { version = "0.6.0", features = ["colored"] }
|
|||
env_logger = "0.10.0"
|
||||
simple_logger = "4.0.0"
|
||||
thiserror = { workspace = true }
|
||||
|
||||
|
||||
[dependencies]
|
||||
tracing-subscriber = "0.3.17"
|
||||
http-range = "0.1.5"
|
||||
|
|
|
@ -24,12 +24,64 @@ script = [
|
|||
]
|
||||
script_runner = "@duckscript"
|
||||
|
||||
[tasks.format]
|
||||
command = "cargo"
|
||||
args = ["fmt", "--all"]
|
||||
|
||||
[tasks.check]
|
||||
command = "cargo"
|
||||
args = ["check", "--workspace", "--examples", "--tests"]
|
||||
|
||||
[tasks.clippy]
|
||||
command = "cargo"
|
||||
args = [
|
||||
"clippy",
|
||||
"--workspace",
|
||||
"--examples",
|
||||
"--tests",
|
||||
"--",
|
||||
"-D",
|
||||
"warnings",
|
||||
]
|
||||
|
||||
[tasks.tidy]
|
||||
category = "Formatting"
|
||||
dependencies = ["format", "check", "clippy"]
|
||||
description = "Format and Check workspace"
|
||||
|
||||
[tasks.install-miri]
|
||||
toolchain = "nightly"
|
||||
install_crate = { rustup_component_name = "miri", binary = "cargo +nightly miri", test_arg = "--help" }
|
||||
private = true
|
||||
|
||||
[tasks.miri-native]
|
||||
command = "cargo"
|
||||
toolchain = "nightly"
|
||||
dependencies = ["install-miri"]
|
||||
args = [
|
||||
"miri",
|
||||
"test",
|
||||
"--package",
|
||||
"dioxus-native-core",
|
||||
"--test",
|
||||
"miri_native",
|
||||
]
|
||||
|
||||
[tasks.miri-stress]
|
||||
command = "cargo"
|
||||
toolchain = "nightly"
|
||||
dependencies = ["install-miri"]
|
||||
args = ["miri", "test", "--package", "dioxus-core", "--test", "miri_stress"]
|
||||
|
||||
[tasks.miri]
|
||||
dependencies = ["miri-native", "miri-stress"]
|
||||
|
||||
[tasks.tests]
|
||||
category = "Testing"
|
||||
dependencies = ["tests-setup"]
|
||||
description = "Run all tests"
|
||||
env = {CARGO_MAKE_WORKSPACE_SKIP_MEMBERS = ["**/examples/*"]}
|
||||
run_task = {name = ["test-flow", "test-with-browser"], fork = true}
|
||||
env = { CARGO_MAKE_WORKSPACE_SKIP_MEMBERS = ["**/examples/*"] }
|
||||
run_task = { name = ["test-flow", "test-with-browser"], fork = true }
|
||||
|
||||
[tasks.build]
|
||||
command = "cargo"
|
||||
|
@ -42,10 +94,26 @@ private = true
|
|||
[tasks.test]
|
||||
dependencies = ["build"]
|
||||
command = "cargo"
|
||||
args = ["test", "--lib", "--bins", "--tests", "--examples", "--workspace", "--exclude", "dioxus-router", "--exclude", "dioxus-desktop"]
|
||||
args = [
|
||||
"test",
|
||||
"--lib",
|
||||
"--bins",
|
||||
"--tests",
|
||||
"--examples",
|
||||
"--workspace",
|
||||
"--exclude",
|
||||
"dioxus-router",
|
||||
"--exclude",
|
||||
"dioxus-desktop",
|
||||
"--exclude",
|
||||
"dioxus-mobile",
|
||||
]
|
||||
private = true
|
||||
|
||||
[tasks.test-with-browser]
|
||||
env = { CARGO_MAKE_WORKSPACE_INCLUDE_MEMBERS = ["**/packages/router", "**/packages/desktop"] }
|
||||
env = { CARGO_MAKE_WORKSPACE_INCLUDE_MEMBERS = [
|
||||
"**/packages/router",
|
||||
"**/packages/desktop",
|
||||
] }
|
||||
private = true
|
||||
workspace = true
|
||||
|
|
|
@ -159,8 +159,9 @@ So... Dioxus is great, but why won't it work for me?
|
|||
|
||||
|
||||
## Contributing
|
||||
- Check out the website [section on contributing](https://dioxuslabs.com/learn/0.4/contributing).
|
||||
- Report issues on our [issue tracker](https://github.com/dioxuslabs/dioxus/issues).
|
||||
- Join the discord and ask questions!
|
||||
- [Join](https://discord.gg/XgGxMSkvUM) the discord and ask questions!
|
||||
|
||||
|
||||
<a href="https://github.com/dioxuslabs/dioxus/graphs/contributors">
|
||||
|
|
|
@ -139,7 +139,6 @@ Missing Features
|
|||
Missing examples
|
||||
- Shared state
|
||||
- Root-less element groups
|
||||
- Spread props
|
||||
- Custom elements
|
||||
- Component Children: Pass children into child components
|
||||
- Render To string: Render a mounted virtualdom to a string
|
||||
|
|
|
@ -53,8 +53,7 @@ fn app(cx: Scope) -> Element {
|
|||
};
|
||||
|
||||
cx.render(rsx! (
|
||||
div {
|
||||
style: "{CONTAINER_STYLE}",
|
||||
div { style: "{CONTAINER_STYLE}",
|
||||
div {
|
||||
style: "{RECT_STYLE}",
|
||||
// focusing is necessary to catch keyboard events
|
||||
|
@ -62,7 +61,7 @@ fn app(cx: Scope) -> Element {
|
|||
|
||||
onmousemove: move |event| log_event(Event::MouseMove(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)),
|
||||
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"
|
||||
}
|
||||
div {
|
||||
events.read().iter().map(|event| rsx!( div { "{event:?}" } ))
|
||||
},
|
||||
},
|
||||
div { events.read().iter().map(|event| rsx!( div { "{event:?}" } )) }
|
||||
}
|
||||
))
|
||||
}
|
||||
|
|
|
@ -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!"
|
||||
}
|
||||
})
|
||||
}
|
|
@ -6,13 +6,13 @@ This calculator version uses React-style state management. All state is held as
|
|||
use dioxus::events::*;
|
||||
use dioxus::html::input_data::keyboard_types::Key;
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_desktop::{Config, WindowBuilder};
|
||||
use dioxus_desktop::{Config, LogicalSize, WindowBuilder};
|
||||
|
||||
fn main() {
|
||||
let config = Config::new().with_window(
|
||||
WindowBuilder::default()
|
||||
.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);
|
||||
|
@ -62,6 +62,7 @@ fn app(cx: Scope) -> Element {
|
|||
div { id: "wrapper",
|
||||
div { class: "app",
|
||||
div { class: "calculator",
|
||||
tabindex: "0",
|
||||
onkeydown: handle_key_down_event,
|
||||
div { class: "calculator-display", val.to_string() }
|
||||
div { class: "calculator-keypad",
|
||||
|
|
|
@ -10,7 +10,7 @@ fn app(cx: Scope) -> Element {
|
|||
|
||||
use_future!(cx, || async move {
|
||||
loop {
|
||||
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
|
||||
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
|
||||
count += 1;
|
||||
println!("current: {count}");
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
//! This example shows how to create a popup window and send data back to the parent window.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_desktop::use_window;
|
||||
use futures_util::StreamExt;
|
||||
|
||||
fn main() {
|
||||
|
@ -9,7 +8,6 @@ fn main() {
|
|||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let window = use_window(cx);
|
||||
let emails_sent = use_ref(cx, Vec::new);
|
||||
|
||||
let tx = use_coroutine(cx, |mut rx: UnboundedReceiver<String>| {
|
||||
|
@ -27,14 +25,8 @@ fn app(cx: Scope) -> Element {
|
|||
|
||||
button {
|
||||
onclick: move |_| {
|
||||
let dom = VirtualDom::new_with_props(compose, ComposeProps {
|
||||
app_tx: tx.clone()
|
||||
});
|
||||
|
||||
// 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());
|
||||
let dom = VirtualDom::new_with_props(compose, ComposeProps { app_tx: tx.clone() });
|
||||
dioxus_desktop::window().new_window(dom, Default::default());
|
||||
},
|
||||
"Click to compose a new email"
|
||||
}
|
||||
|
@ -57,7 +49,6 @@ struct ComposeProps {
|
|||
|
||||
fn compose(cx: Scope<ComposeProps>) -> Element {
|
||||
let user_input = use_state(cx, String::new);
|
||||
let window = use_window(cx);
|
||||
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
|
@ -66,17 +57,12 @@ fn compose(cx: Scope<ComposeProps>) -> Element {
|
|||
button {
|
||||
onclick: move |_| {
|
||||
cx.props.app_tx.send(user_input.get().clone());
|
||||
window.close();
|
||||
dioxus_desktop::window().close();
|
||||
},
|
||||
"Click to send"
|
||||
}
|
||||
|
||||
input {
|
||||
oninput: move |e| {
|
||||
user_input.set(e.value.clone());
|
||||
},
|
||||
value: "{user_input}"
|
||||
}
|
||||
input { oninput: move |e| user_input.set(e.value()), value: "{user_input}" }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ fn app(cx: Scope) -> Element {
|
|||
input {
|
||||
value: "{counter}",
|
||||
oninput: move |e| {
|
||||
if let Ok(value) = e.value.parse::<usize>() {
|
||||
if let Ok(value) = e.value().parse::<usize>() {
|
||||
counters.make_mut()[i] = value;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,14 +35,16 @@ fn App(cx: Scope) -> Element {
|
|||
rel: "stylesheet",
|
||||
href: "https://unpkg.com/purecss@2.0.6/build/pure-min.css",
|
||||
integrity: "sha384-Uu6IeWbM+gzNVXJcM9XV3SohHtmWE+3VGi496jvgX1jyvDTXfdK+rfZc8C1Aehk5",
|
||||
crossorigin: "anonymous",
|
||||
crossorigin: "anonymous"
|
||||
}
|
||||
|
||||
style { "
|
||||
style {
|
||||
"
|
||||
.red {{
|
||||
background-color: rgb(202, 60, 60) !important;
|
||||
}}
|
||||
" }
|
||||
"
|
||||
}
|
||||
|
||||
h1 { "Dioxus CRM Example" }
|
||||
|
||||
|
@ -57,16 +59,8 @@ fn ClientList(cx: Scope) -> Element {
|
|||
cx.render(rsx! {
|
||||
h2 { "List of Clients" }
|
||||
|
||||
Link {
|
||||
to: Route::ClientAdd {},
|
||||
class: "pure-button pure-button-primary",
|
||||
"Add Client"
|
||||
}
|
||||
Link {
|
||||
to: Route::Settings {},
|
||||
class: "pure-button",
|
||||
"Settings"
|
||||
}
|
||||
Link { to: Route::ClientAdd {}, class: "pure-button pure-button-primary", "Add Client" }
|
||||
Link { to: Route::Settings {}, class: "pure-button", "Settings" }
|
||||
|
||||
clients.read().iter().map(|client| rsx! {
|
||||
div {
|
||||
|
@ -87,8 +81,6 @@ fn ClientAdd(cx: Scope) -> Element {
|
|||
let last_name = use_state(cx, String::new);
|
||||
let description = use_state(cx, String::new);
|
||||
|
||||
let navigator = use_navigator(cx);
|
||||
|
||||
cx.render(rsx! {
|
||||
h2 { "Add new Client" }
|
||||
|
||||
|
@ -96,79 +88,55 @@ fn ClientAdd(cx: Scope) -> Element {
|
|||
class: "pure-form pure-form-aligned",
|
||||
onsubmit: move |_| {
|
||||
let mut clients = clients.write();
|
||||
|
||||
clients.push(Client {
|
||||
first_name: first_name.to_string(),
|
||||
last_name: last_name.to_string(),
|
||||
description: description.to_string(),
|
||||
});
|
||||
|
||||
navigator.push(Route::ClientList {});
|
||||
clients
|
||||
.push(Client {
|
||||
first_name: first_name.to_string(),
|
||||
last_name: last_name.to_string(),
|
||||
description: description.to_string(),
|
||||
});
|
||||
dioxus_router::router().push(Route::ClientList {});
|
||||
},
|
||||
|
||||
fieldset {
|
||||
div {
|
||||
class: "pure-control-group",
|
||||
label {
|
||||
"for": "first_name",
|
||||
"First Name"
|
||||
}
|
||||
div { class: "pure-control-group",
|
||||
label { "for": "first_name", "First Name" }
|
||||
input {
|
||||
id: "first_name",
|
||||
"type": "text",
|
||||
placeholder: "First Name…",
|
||||
required: "",
|
||||
value: "{first_name}",
|
||||
oninput: move |e| first_name.set(e.value.clone())
|
||||
oninput: move |e| first_name.set(e.value())
|
||||
}
|
||||
}
|
||||
|
||||
div {
|
||||
class: "pure-control-group",
|
||||
label {
|
||||
"for": "last_name",
|
||||
"Last Name"
|
||||
}
|
||||
div { class: "pure-control-group",
|
||||
label { "for": "last_name", "Last Name" }
|
||||
input {
|
||||
id: "last_name",
|
||||
"type": "text",
|
||||
placeholder: "Last Name…",
|
||||
required: "",
|
||||
value: "{last_name}",
|
||||
oninput: move |e| last_name.set(e.value.clone())
|
||||
oninput: move |e| last_name.set(e.value())
|
||||
}
|
||||
}
|
||||
|
||||
div {
|
||||
class: "pure-control-group",
|
||||
label {
|
||||
"for": "description",
|
||||
"Description"
|
||||
}
|
||||
div { class: "pure-control-group",
|
||||
label { "for": "description", "Description" }
|
||||
textarea {
|
||||
id: "description",
|
||||
placeholder: "Description…",
|
||||
value: "{description}",
|
||||
oninput: move |e| description.set(e.value.clone())
|
||||
oninput: move |e| description.set(e.value())
|
||||
}
|
||||
}
|
||||
|
||||
div {
|
||||
class: "pure-controls",
|
||||
button {
|
||||
"type": "submit",
|
||||
class: "pure-button pure-button-primary",
|
||||
"Save"
|
||||
}
|
||||
Link {
|
||||
to: Route::ClientList {},
|
||||
class: "pure-button pure-button-primary red",
|
||||
"Cancel"
|
||||
}
|
||||
div { class: "pure-controls",
|
||||
button { "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"
|
||||
}
|
||||
|
||||
Link {
|
||||
to: Route::ClientList {},
|
||||
class: "pure-button",
|
||||
"Go back"
|
||||
}
|
||||
Link { to: Route::ClientList {}, class: "pure-button", "Go back" }
|
||||
})
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ fn app(cx: Scope) -> Element {
|
|||
p {
|
||||
"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
examples/dynamic_asset.rs
Normal file
27
examples/dynamic_asset.rs
Normal 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"
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
use dioxus::prelude::*;
|
||||
use dioxus::{core::CapturedError, prelude::*};
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(App);
|
||||
|
@ -6,30 +6,25 @@ fn main() {
|
|||
|
||||
#[component]
|
||||
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! {
|
||||
h1 { "The parsed value is {num}" }
|
||||
button {
|
||||
onclick: move |_| val.set("invalid"),
|
||||
"Set an invalid number"
|
||||
ErrorBoundary {
|
||||
handle_error: |error: CapturedError| rsx! {"Found error {error}"},
|
||||
DemoC {
|
||||
x: 1
|
||||
}
|
||||
}
|
||||
(0..5).map(|i| rsx! {
|
||||
DemoC { x: i }
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn DemoC(cx: Scope, x: i32) -> Element {
|
||||
let result = Err("Error");
|
||||
|
||||
result.throw()?;
|
||||
|
||||
cx.render(rsx! {
|
||||
h1 {
|
||||
"asdasdasdasd {x}"
|
||||
"{x}"
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -5,26 +5,21 @@ fn main() {
|
|||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let eval_provider = use_eval(cx);
|
||||
|
||||
let future = use_future(cx, (), |_| {
|
||||
to_owned![eval_provider];
|
||||
async move {
|
||||
let eval = eval_provider(
|
||||
r#"
|
||||
let future = use_future(cx, (), |_| async move {
|
||||
let eval = eval(
|
||||
r#"
|
||||
dioxus.send("Hi from JS!");
|
||||
let msg = await dioxus.recv();
|
||||
console.log(msg);
|
||||
return "hello world";
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
eval.send("Hi from Rust!".into()).unwrap();
|
||||
let res = eval.recv().await.unwrap();
|
||||
println!("{:?}", eval.await);
|
||||
res
|
||||
}
|
||||
eval.send("Hi from Rust!".into()).unwrap();
|
||||
let res = eval.recv().await.unwrap();
|
||||
println!("{:?}", eval.await);
|
||||
res
|
||||
});
|
||||
|
||||
match future.value() {
|
||||
|
|
|
@ -18,13 +18,14 @@ fn main() {
|
|||
);
|
||||
}
|
||||
|
||||
const _STYLE: &str = mg!(file("./examples/assets/fileexplorer.css"));
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let files = use_ref(cx, Files::new);
|
||||
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
link { href:"https://fonts.googleapis.com/icon?family=Material+Icons", rel:"stylesheet", }
|
||||
style { include_str!("./assets/fileexplorer.css") }
|
||||
header {
|
||||
i { class: "material-icons icon-menu", "menu" }
|
||||
h1 { "Files: ", files.read().current() }
|
||||
|
|
|
@ -16,7 +16,7 @@ fn App(cx: Scope) -> Element {
|
|||
r#type: "checkbox",
|
||||
checked: "{enable_directory_upload}",
|
||||
oninput: move |evt| {
|
||||
enable_directory_upload.set(evt.value.parse().unwrap());
|
||||
enable_directory_upload.set(evt.value().parse().unwrap());
|
||||
},
|
||||
},
|
||||
"Enable directory upload"
|
||||
|
@ -30,7 +30,7 @@ fn App(cx: Scope) -> Element {
|
|||
onchange: |evt| {
|
||||
to_owned![files_uploaded];
|
||||
async move {
|
||||
if let Some(file_engine) = &evt.files {
|
||||
if let Some(file_engine) = &evt.files() {
|
||||
let files = file_engine.files();
|
||||
for file_name in files {
|
||||
sleep(std::time::Duration::from_secs(1)).await;
|
||||
|
|
|
@ -14,8 +14,8 @@ fn app(cx: Scope) -> Element {
|
|||
div {
|
||||
h1 { "Form" }
|
||||
form {
|
||||
onsubmit: move |ev| println!("Submitted {:?}", ev.values),
|
||||
oninput: move |ev| println!("Input {:?}", ev.values),
|
||||
onsubmit: move |ev| println!("Submitted {:?}", ev.values()),
|
||||
oninput: move |ev| println!("Input {:?}", ev.values()),
|
||||
input { r#type: "text", name: "username" }
|
||||
input { r#type: "text", name: "full-name" }
|
||||
input { r#type: "password", name: "password" }
|
||||
|
|
|
@ -8,33 +8,30 @@ fn main() {
|
|||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let onsubmit = move |evt: FormEvent| {
|
||||
cx.spawn(async move {
|
||||
let resp = reqwest::Client::new()
|
||||
.post("http://localhost:8080/login")
|
||||
.form(&[
|
||||
("username", &evt.values["username"]),
|
||||
("password", &evt.values["password"]),
|
||||
])
|
||||
.send()
|
||||
.await;
|
||||
let onsubmit = move |evt: FormEvent| async move {
|
||||
let resp = reqwest::Client::new()
|
||||
.post("http://localhost:8080/login")
|
||||
.form(&[
|
||||
("username", &evt.values()["username"]),
|
||||
("password", &evt.values()["password"]),
|
||||
])
|
||||
.send()
|
||||
.await;
|
||||
|
||||
match resp {
|
||||
// Parse data from here, such as storing a response token
|
||||
Ok(_data) => println!("Login successful!"),
|
||||
match resp {
|
||||
// Parse data from here, such as storing a response token
|
||||
Ok(_data) => println!("Login successful!"),
|
||||
|
||||
//Handle any errors from the fetch here
|
||||
Err(_err) => {
|
||||
println!("Login failed - you need a login server running on localhost:8080.")
|
||||
}
|
||||
//Handle any errors from the fetch here
|
||||
Err(_err) => {
|
||||
println!("Login failed - you need a login server running on localhost:8080.")
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
cx.render(rsx! {
|
||||
h1 { "Login" }
|
||||
form {
|
||||
onsubmit: onsubmit,
|
||||
form { onsubmit: onsubmit,
|
||||
input { r#type: "text", id: "username", name: "username" }
|
||||
label { "Username" }
|
||||
br {}
|
||||
|
|
2
examples/mobile_demo/.gitignore
vendored
2
examples/mobile_demo/.gitignore
vendored
|
@ -2,7 +2,7 @@
|
|||
target/
|
||||
**/*.rs.bk
|
||||
|
||||
# tauri-mobile
|
||||
# cargo-mobile2
|
||||
.cargo/
|
||||
/gen
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ frameworks = ["WebKit"]
|
|||
[dependencies]
|
||||
anyhow = "1.0.56"
|
||||
log = "0.4.11"
|
||||
wry = "0.28.0"
|
||||
wry = "0.35.0"
|
||||
dioxus = { path = "../../packages/dioxus" }
|
||||
dioxus-desktop = { path = "../../packages/desktop", features = [
|
||||
"tokio_runtime",
|
||||
|
|
|
@ -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.
|
||||
|
||||
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
|
||||
|
||||
|
|
|
@ -5,14 +5,12 @@ fn main() {
|
|||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let window = dioxus_desktop::use_window(cx);
|
||||
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
button {
|
||||
onclick: move |_| {
|
||||
let dom = VirtualDom::new(popup);
|
||||
window.new_window(dom, Default::default());
|
||||
dioxus_desktop::window().new_window(dom, Default::default());
|
||||
},
|
||||
"New Window"
|
||||
}
|
||||
|
|
3
examples/openid_connect_demo/.gitignore
vendored
Normal file
3
examples/openid_connect_demo/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
/target
|
||||
/dist
|
||||
.env
|
25
examples/openid_connect_demo/Cargo.toml
Normal file
25
examples/openid_connect_demo/Cargo.toml
Normal 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
examples/openid_connect_demo/Dioxus.toml
Normal file
47
examples/openid_connect_demo/Dioxus.toml
Normal 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
examples/openid_connect_demo/README.md
Normal file
13
examples/openid_connect_demo/README.md
Normal 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`
|
2
examples/openid_connect_demo/src/constants.rs
Normal file
2
examples/openid_connect_demo/src/constants.rs
Normal file
|
@ -0,0 +1,2 @@
|
|||
pub const DIOXUS_FRONT_AUTH_TOKEN: &str = "auth_token";
|
||||
pub const DIOXUS_FRONT_AUTH_REQUEST: &str = "auth_request";
|
20
examples/openid_connect_demo/src/errors.rs
Normal file
20
examples/openid_connect_demo/src/errors.rs
Normal file
|
@ -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
examples/openid_connect_demo/src/main.rs
Normal file
60
examples/openid_connect_demo/src/main.rs
Normal 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);
|
||||
}
|
1
examples/openid_connect_demo/src/model/mod.rs
Normal file
1
examples/openid_connect_demo/src/model/mod.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub(crate) mod user;
|
7
examples/openid_connect_demo/src/model/user.rs
Normal file
7
examples/openid_connect_demo/src/model/user.rs
Normal file
|
@ -0,0 +1,7 @@
|
|||
use uuid::Uuid;
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub struct User {
|
||||
pub id: Uuid,
|
||||
pub name: String,
|
||||
}
|
125
examples/openid_connect_demo/src/oidc.rs
Normal file
125
examples/openid_connect_demo/src/oidc.rs
Normal 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())
|
||||
}
|
20
examples/openid_connect_demo/src/props/client.rs
Normal file
20
examples/openid_connect_demo/src/props/client.rs
Normal file
|
@ -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 }
|
||||
}
|
||||
}
|
1
examples/openid_connect_demo/src/props/mod.rs
Normal file
1
examples/openid_connect_demo/src/props/mod.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub(crate) mod client;
|
17
examples/openid_connect_demo/src/router.rs
Normal file
17
examples/openid_connect_demo/src/router.rs
Normal file
|
@ -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> },
|
||||
}
|
38
examples/openid_connect_demo/src/storage.rs
Normal file
38
examples/openid_connect_demo/src/storage.rs
Normal file
|
@ -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();
|
||||
}
|
||||
}
|
250
examples/openid_connect_demo/src/views/header.rs
Normal file
250
examples/openid_connect_demo/src/views/header.rs
Normal file
|
@ -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! {{}}
|
||||
}
|
||||
})
|
||||
}
|
5
examples/openid_connect_demo/src/views/home.rs
Normal file
5
examples/openid_connect_demo/src/views/home.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
use dioxus::prelude::*;
|
||||
|
||||
pub fn Home(cx: Scope) -> Element {
|
||||
render! { div { "Hello world" } }
|
||||
}
|
86
examples/openid_connect_demo/src/views/login.rs
Normal file
86
examples/openid_connect_demo/src/views/login.rs
Normal file
|
@ -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! {{}}
|
||||
}
|
||||
})
|
||||
}
|
4
examples/openid_connect_demo/src/views/mod.rs
Normal file
4
examples/openid_connect_demo/src/views/mod.rs
Normal file
|
@ -0,0 +1,4 @@
|
|||
pub(crate) mod header;
|
||||
pub(crate) mod home;
|
||||
pub(crate) mod login;
|
||||
pub(crate) mod not_found;
|
7
examples/openid_connect_demo/src/views/not_found.rs
Normal file
7
examples/openid_connect_demo/src/views/not_found.rs
Normal file
|
@ -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}}}
|
||||
}
|
|
@ -16,8 +16,20 @@ fn app(cx: Scope) -> Element {
|
|||
a: "asd".to_string(),
|
||||
c: "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(),
|
||||
}
|
||||
Button {
|
||||
a: "asd".to_string(),
|
||||
c: "asd".to_string(),
|
||||
d: Some("asd".to_string()),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
use dioxus::prelude::*;
|
||||
use dioxus_desktop::{tao::dpi::PhysicalPosition, use_window, LogicalSize, WindowBuilder};
|
||||
use dioxus_desktop::{tao::dpi::PhysicalPosition, LogicalSize, WindowBuilder};
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch_cfg(app, make_config());
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let window = use_window(cx);
|
||||
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
width: "100%",
|
||||
|
@ -19,7 +17,7 @@ fn app(cx: Scope) -> Element {
|
|||
width: "100%",
|
||||
height: "10px",
|
||||
background_color: "black",
|
||||
onmousedown: move |_| window.drag(),
|
||||
onmousedown: move |_| dioxus_desktop::window().drag(),
|
||||
}
|
||||
|
||||
"This is an overlay!"
|
||||
|
|
|
@ -21,7 +21,7 @@ use dioxus::events::*;
|
|||
use dioxus::html::input_data::keyboard_types::Key;
|
||||
use dioxus::html::MouseEvent;
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_desktop::wry::application::dpi::LogicalSize;
|
||||
use dioxus_desktop::tao::dpi::LogicalSize;
|
||||
use dioxus_desktop::{Config, WindowBuilder};
|
||||
|
||||
fn main() {
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
name = "query_segments_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
|
||||
|
||||
|
|
|
@ -14,29 +14,35 @@ use dioxus_router::prelude::*;
|
|||
#[derive(Routable, Clone)]
|
||||
#[rustfmt::skip]
|
||||
enum Route {
|
||||
// segments that start with ?: are query segments
|
||||
#[route("/blog?:query_params")]
|
||||
// segments that start with ?:.. are query segments that capture the entire query
|
||||
#[route("/blog?:..query_params")]
|
||||
BlogPost {
|
||||
// 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)]
|
||||
struct BlogQuerySegments {
|
||||
struct ManualBlogQuerySegments {
|
||||
name: String,
|
||||
surname: String,
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
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.
|
||||
impl FromQuery for BlogQuerySegments {
|
||||
/// 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 ManualBlogQuerySegments {
|
||||
fn from_query(query: &str) -> Self {
|
||||
let mut name = None;
|
||||
let mut surname = None;
|
||||
|
@ -57,13 +63,21 @@ impl FromQuery for BlogQuerySegments {
|
|||
}
|
||||
|
||||
#[component]
|
||||
fn BlogPost(cx: Scope, query_params: BlogQuerySegments) -> Element {
|
||||
fn BlogPost(cx: Scope, query_params: ManualBlogQuerySegments) -> Element {
|
||||
render! {
|
||||
div{"This is your blogpost with a query segment:"}
|
||||
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]
|
||||
fn App(cx: Scope) -> Element {
|
||||
render! { Router::<Route>{} }
|
||||
|
|
|
@ -53,6 +53,7 @@ fn App(cx: Scope) -> Element {
|
|||
let formatting = "formatting!";
|
||||
let formatting_tuple = ("a", "b");
|
||||
let lazy_fmt = format_args!("lazily formatted text");
|
||||
let asd = 123;
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
// Elements
|
||||
|
@ -80,6 +81,10 @@ fn App(cx: Scope) -> Element {
|
|||
// pass simple rust expressions in
|
||||
class: lazy_fmt,
|
||||
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 {
|
||||
class: {
|
||||
const WORD: &str = "expressions";
|
||||
|
|
|
@ -64,7 +64,7 @@ fn DataEditor(cx: Scope, id: usize) -> Element {
|
|||
fn DataView(cx: Scope, id: usize) -> Element {
|
||||
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 my_data = &cool_data.view(id).unwrap();
|
||||
|
|
|
@ -6,11 +6,17 @@ fn main() {
|
|||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let running = dioxus_signals::use_signal(cx, || true);
|
||||
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 {
|
||||
loop {
|
||||
count += 1;
|
||||
if running.value() {
|
||||
count += 1;
|
||||
}
|
||||
tokio::time::sleep(Duration::from_millis(400)).await;
|
||||
}
|
||||
});
|
||||
|
@ -19,9 +25,25 @@ fn app(cx: Scope) -> Element {
|
|||
h1 { "High-Five counter: {count}" }
|
||||
button { onclick: move |_| count += 1, "Up high!" }
|
||||
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 {
|
||||
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
examples/spread.rs
Normal file
36
examples/spread.rs
Normal 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
examples/streams.rs
Normal file
33
examples/streams.rs
Normal 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
|
||||
})),
|
||||
)
|
||||
}
|
|
@ -18,4 +18,4 @@ dioxus = { path = "../../packages/dioxus" }
|
|||
dioxus-desktop = { path = "../../packages/desktop" }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
dioxus-web = { path = "../../packages/web" }
|
||||
dioxus-web = { path = "../../packages/web" }
|
||||
|
|
|
@ -30,7 +30,7 @@ watch_path = ["src", "public"]
|
|||
[web.resource]
|
||||
|
||||
# CSS style file
|
||||
style = ["/tailwind.css"]
|
||||
style = []
|
||||
|
||||
# Javascript code file
|
||||
script = []
|
||||
|
|
|
@ -7,7 +7,7 @@ This example shows how an app might be styled with TailwindCSS.
|
|||
1. Install the Dioxus CLI:
|
||||
|
||||
```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
|
||||
|
|
1
examples/tailwind/dist/tailwind3531548035813279582.css
vendored
Normal file
1
examples/tailwind/dist/tailwind3531548035813279582.css
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -2,21 +2,23 @@
|
|||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
const _STYLE: &str = mg!(file("./public/tailwind.css"));
|
||||
|
||||
fn main() {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
dioxus_desktop::launch_cfg(
|
||||
app,
|
||||
dioxus_desktop::Config::new()
|
||||
.with_custom_head(r#"<link rel="stylesheet" href="public/tailwind.css">"#.to_string()),
|
||||
);
|
||||
dioxus_desktop::launch(app);
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
dioxus_web::launch(app);
|
||||
}
|
||||
|
||||
pub fn app(cx: Scope) -> Element {
|
||||
let grey_background = true;
|
||||
cx.render(rsx!(
|
||||
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",
|
||||
a { class: "flex title-font font-medium items-center text-white mb-4 md:mb-0",
|
||||
StacksIcon {}
|
||||
|
|
|
@ -17,7 +17,7 @@ fn app(cx: Scope) -> Element {
|
|||
rows: "10",
|
||||
cols: "80",
|
||||
value: "{model}",
|
||||
oninput: move |e| model.set(e.value.clone()),
|
||||
oninput: move |e| model.set(e.value().clone()),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -7,6 +7,8 @@ fn main() {
|
|||
dioxus_desktop::launch(app);
|
||||
}
|
||||
|
||||
const _STYLE: &str = mg!(file("./examples/assets/todomvc.css"));
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy)]
|
||||
pub enum FilterState {
|
||||
All,
|
||||
|
@ -24,8 +26,6 @@ pub struct TodoItem {
|
|||
pub fn app(cx: Scope<()>) -> Element {
|
||||
let todos = use_state(cx, im_rc::HashMap::<u32, TodoItem>::default);
|
||||
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
|
||||
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 selected = |state| {
|
||||
if *filter == state {
|
||||
"selected"
|
||||
} else {
|
||||
"false"
|
||||
}
|
||||
};
|
||||
|
||||
cx.render(rsx! {
|
||||
section { class: "todoapp",
|
||||
style { include_str!("./assets/todomvc.css") }
|
||||
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() {
|
||||
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",
|
||||
TodoHeader { todos: todos }
|
||||
section { class: "main",
|
||||
if !todos.is_empty() {
|
||||
rsx! {
|
||||
input {
|
||||
|
@ -111,43 +76,58 @@ pub fn app(cx: Scope<()>) -> Element {
|
|||
}))
|
||||
}
|
||||
(!todos.is_empty()).then(|| 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 |_| 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"
|
||||
}
|
||||
))
|
||||
ListFooter {
|
||||
active_todo_count: active_todo_count,
|
||||
active_todo_text: active_todo_text,
|
||||
show_clear_completed: show_clear_completed,
|
||||
todos: todos,
|
||||
filter: filter,
|
||||
}
|
||||
))
|
||||
}
|
||||
}
|
||||
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" }}
|
||||
PageFooter {}
|
||||
})
|
||||
}
|
||||
|
||||
#[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 { "" };
|
||||
|
||||
cx.render(rsx!{
|
||||
li {
|
||||
class: "{completed} {editing}",
|
||||
li { class: "{completed} {editing}",
|
||||
div { class: "view",
|
||||
input {
|
||||
class: "toggle",
|
||||
|
@ -176,26 +155,28 @@ pub fn TodoEntry<'a>(cx: Scope<'a, TodoEntryProps<'a>>) -> Element {
|
|||
id: "cbg-{todo.id}",
|
||||
checked: "{todo.checked}",
|
||||
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 {
|
||||
r#for: "cbg-{todo.id}",
|
||||
ondblclick: move |_| is_editing.set(true),
|
||||
ondoubleclick: move |_| is_editing.set(true),
|
||||
prevent_default: "onclick",
|
||||
"{todo.contents}"
|
||||
}
|
||||
button {
|
||||
class: "destroy",
|
||||
onclick: move |_| { cx.props.todos.make_mut().remove(&todo.id); },
|
||||
prevent_default: "onclick",
|
||||
onclick: move |_| {
|
||||
cx.props.todos.make_mut().remove(&todo.id);
|
||||
},
|
||||
prevent_default: "onclick"
|
||||
}
|
||||
}
|
||||
is_editing.then(|| rsx!{
|
||||
input {
|
||||
class: "edit",
|
||||
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",
|
||||
onfocusout: move |_| is_editing.set(false),
|
||||
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
examples/video_stream.rs
Normal file
188
examples/video_stream.rs
Normal 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,7 +1,7 @@
|
|||
use dioxus::prelude::*;
|
||||
use dioxus_desktop::tao::event::Event as WryEvent;
|
||||
use dioxus_desktop::tao::event::WindowEvent;
|
||||
use dioxus_desktop::use_wry_event_handler;
|
||||
use dioxus_desktop::wry::application::event::Event as WryEvent;
|
||||
use dioxus_desktop::{Config, WindowCloseBehaviour};
|
||||
|
||||
fn main() {
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
use dioxus::prelude::*;
|
||||
use dioxus_desktop::use_window;
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(app);
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let window = use_window(cx);
|
||||
let level = use_state(cx, || 1.0);
|
||||
|
||||
cx.render(rsx! {
|
||||
|
@ -14,9 +12,9 @@ fn app(cx: Scope) -> Element {
|
|||
r#type: "number",
|
||||
value: "{level}",
|
||||
oninput: |e| {
|
||||
if let Ok(new_zoom) = e.value.parse::<f64>() {
|
||||
if let Ok(new_zoom) = e.value().parse::<f64>() {
|
||||
level.set(new_zoom);
|
||||
window.webview.zoom(new_zoom);
|
||||
dioxus_desktop::window().webview.zoom(new_zoom);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ fn app(cx: Scope) -> Element {
|
|||
input {
|
||||
value: "{contents}",
|
||||
r#type: "text",
|
||||
oninput: move |e| contents.set(e.value.clone()),
|
||||
oninput: move |e| contents.set(e.value()),
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
247
flake.lock
Normal file
247
flake.lock
Normal 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
flake.nix
Normal file
63
flake.nix
Normal 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";
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
|
@ -8,13 +8,14 @@ use std::fmt::{Result, Write};
|
|||
|
||||
use dioxus_rsx::IfmtInput;
|
||||
|
||||
use crate::write_ifmt;
|
||||
use crate::{indent::IndentOptions, write_ifmt};
|
||||
|
||||
/// The output buffer that tracks indent and string
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Buffer {
|
||||
pub buf: String,
|
||||
pub indent: usize,
|
||||
pub indent_level: usize,
|
||||
pub indent: IndentOptions,
|
||||
}
|
||||
|
||||
impl Buffer {
|
||||
|
@ -31,16 +32,16 @@ impl Buffer {
|
|||
}
|
||||
|
||||
pub fn tab(&mut self) -> Result {
|
||||
self.write_tabs(self.indent)
|
||||
self.write_tabs(self.indent_level)
|
||||
}
|
||||
|
||||
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 {
|
||||
for _ in 0..num {
|
||||
write!(self.buf, " ")?
|
||||
write!(self.buf, "{}", self.indent.indent_str())?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -49,6 +49,7 @@ impl Writer<'_> {
|
|||
attributes,
|
||||
children,
|
||||
brace,
|
||||
..
|
||||
} = el;
|
||||
|
||||
/*
|
||||
|
@ -66,7 +67,7 @@ impl Writer<'_> {
|
|||
|
||||
// check if we have a lot of 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 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 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;
|
||||
} else {
|
||||
opt_level = ShortOptimization::PropsOnTop;
|
||||
|
@ -165,7 +166,7 @@ impl Writer<'_> {
|
|||
|
||||
fn write_attributes(
|
||||
&mut self,
|
||||
attributes: &[ElementAttrNamed],
|
||||
attributes: &[AttributeType],
|
||||
key: &Option<IfmtInput>,
|
||||
sameline: bool,
|
||||
) -> Result {
|
||||
|
@ -185,11 +186,11 @@ impl Writer<'_> {
|
|||
}
|
||||
|
||||
while let Some(attr) = attr_iter.next() {
|
||||
self.out.indent += 1;
|
||||
self.out.indent_level += 1;
|
||||
if !sameline {
|
||||
self.write_comments(attr.attr.start())?;
|
||||
self.write_comments(attr.start())?;
|
||||
}
|
||||
self.out.indent -= 1;
|
||||
self.out.indent_level -= 1;
|
||||
|
||||
if !sameline {
|
||||
self.out.indented_tabbed_line()?;
|
||||
|
@ -209,12 +210,34 @@ impl Writer<'_> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn write_attribute(&mut self, attr: &ElementAttrNamed) -> Result {
|
||||
match &attr.attr {
|
||||
ElementAttr::AttrText { name, value } => {
|
||||
write!(self.out, "{name}: {value}", value = ifmt_to_string(value))?;
|
||||
fn write_attribute_name(&mut self, attr: &ElementAttrName) -> Result {
|
||||
match attr {
|
||||
ElementAttrName::BuiltIn(name) => {
|
||||
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 mut lines = out.split('\n').peekable();
|
||||
let first = lines.next().unwrap();
|
||||
|
@ -222,9 +245,9 @@ impl Writer<'_> {
|
|||
// a one-liner for whatever reason
|
||||
// Does not need a new line
|
||||
if lines.peek().is_none() {
|
||||
write!(self.out, "{name}: {first}")?;
|
||||
write!(self.out, "{first}")?;
|
||||
} else {
|
||||
writeln!(self.out, "{name}: {first}")?;
|
||||
writeln!(self.out, "{first}")?;
|
||||
|
||||
while let Some(line) = lines.next() {
|
||||
self.out.indented_tab()?;
|
||||
|
@ -237,22 +260,7 @@ impl Writer<'_> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 } => {
|
||||
ElementAttrValue::EventTokens(tokens) => {
|
||||
let out = self.retrieve_formatted_expr(tokens).to_string();
|
||||
|
||||
let mut lines = out.split('\n').peekable();
|
||||
|
@ -261,9 +269,9 @@ impl Writer<'_> {
|
|||
// a one-liner for whatever reason
|
||||
// Does not need a new line
|
||||
if lines.peek().is_none() {
|
||||
write!(self.out, "{name}: {first}")?;
|
||||
write!(self.out, "{first}")?;
|
||||
} else {
|
||||
writeln!(self.out, "{name}: {first}")?;
|
||||
writeln!(self.out, "{first}")?;
|
||||
|
||||
while let Some(line) = lines.next() {
|
||||
self.out.indented_tab()?;
|
||||
|
@ -281,6 +289,28 @@ impl Writer<'_> {
|
|||
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.
|
||||
// test by making sure this element is the primary element on this line
|
||||
pub fn current_span_is_primary(&self, location: Span) -> bool {
|
||||
|
@ -398,14 +428,14 @@ impl Writer<'_> {
|
|||
for idx in start.line..end.line {
|
||||
let line = &self.src[idx];
|
||||
if line.trim().starts_with("//") {
|
||||
for _ in 0..self.out.indent + 1 {
|
||||
for _ in 0..self.out.indent_level + 1 {
|
||||
write!(self.out, " ")?
|
||||
}
|
||||
writeln!(self.out, "{}", line.trim()).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
for _ in 0..self.out.indent {
|
||||
for _ in 0..self.out.indent_level {
|
||||
write!(self.out, " ")?
|
||||
}
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ impl Writer<'_> {
|
|||
let first_line = &self.src[start.line - 1];
|
||||
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() {
|
||||
writeln!(self.out)?;
|
||||
|
@ -43,9 +43,9 @@ impl Writer<'_> {
|
|||
};
|
||||
|
||||
// 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 required_indent = self.out.indent + offset;
|
||||
let required_indent = self.out.indent_level + offset;
|
||||
self.out.write_tabs(required_indent)?;
|
||||
|
||||
let line = line.trim_start();
|
||||
|
|
108
packages/autofmt/src/indent.rs
Normal file
108
packages/autofmt/src/indent.rs
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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 crate::writer::*;
|
||||
|
@ -12,8 +16,11 @@ mod collect_macros;
|
|||
mod component;
|
||||
mod element;
|
||||
mod expr;
|
||||
mod indent;
|
||||
mod writer;
|
||||
|
||||
pub use indent::{IndentOptions, IndentType};
|
||||
|
||||
/// 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.
|
||||
|
@ -43,7 +50,7 @@ pub struct FormattedBlock {
|
|||
/// back to the file precisely.
|
||||
///
|
||||
/// 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 parsed = syn::parse_file(contents).unwrap();
|
||||
|
@ -57,6 +64,7 @@ pub fn fmt_file(contents: &str) -> Vec<FormattedBlock> {
|
|||
}
|
||||
|
||||
let mut writer = Writer::new(contents);
|
||||
writer.out.indent = indent;
|
||||
|
||||
// Don't parse nested macros
|
||||
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();
|
||||
|
||||
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);
|
||||
|
||||
|
@ -155,12 +166,13 @@ pub fn fmt_block_from_expr(raw: &str, expr: ExprMacro) -> Option<String> {
|
|||
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 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);
|
||||
|
||||
|
@ -226,14 +238,3 @@ pub(crate) fn write_ifmt(input: &IfmtInput, writable: &mut impl Write) -> std::f
|
|||
let display = DisplayIfmt(input);
|
||||
write!(writable, "{}", display)
|
||||
}
|
||||
|
||||
pub fn leading_whitespaces(input: &str) -> usize {
|
||||
input
|
||||
.chars()
|
||||
.map_while(|c| match c {
|
||||
' ' => Some(1),
|
||||
'\t' => Some(4),
|
||||
_ => None,
|
||||
})
|
||||
.sum()
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use dioxus_rsx::{BodyNode, ElementAttr, ElementAttrNamed, ForLoop};
|
||||
use dioxus_rsx::{AttributeType, BodyNode, ElementAttrValue, ForLoop};
|
||||
use proc_macro2::{LineColumn, Span};
|
||||
use quote::ToTokens;
|
||||
use std::{
|
||||
|
@ -96,11 +96,11 @@ impl<'a> Writer<'a> {
|
|||
|
||||
// Push out the indent level and write each component, line by line
|
||||
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.out.indent -= 1;
|
||||
self.out.indent_level -= 1;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -132,12 +132,45 @@ impl<'a> Writer<'a> {
|
|||
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;
|
||||
|
||||
for attr in attributes {
|
||||
if self.current_span_is_primary(attr.attr.start()) {
|
||||
'line: for line in self.src[..attr.attr.start().start().line - 1].iter().rev() {
|
||||
if self.current_span_is_primary(attr.start()) {
|
||||
'line: for line in self.src[..attr.start().start().line - 1].iter().rev() {
|
||||
match (line.trim().starts_with("//"), line.is_empty()) {
|
||||
(true, _) => return 100000,
|
||||
(_, true) => continue 'line,
|
||||
|
@ -146,40 +179,25 @@ impl<'a> Writer<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
total += match &attr.attr {
|
||||
ElementAttr::AttrText { value, name } => {
|
||||
ifmt_to_string(value).len() + name.span().line_length() + 6
|
||||
}
|
||||
ElementAttr::AttrExpression { name, value } => {
|
||||
value.span().line_length() + name.span().line_length() + 6
|
||||
}
|
||||
ElementAttr::CustomAttrText { value, name } => {
|
||||
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()
|
||||
match attr {
|
||||
AttributeType::Named(attr) => {
|
||||
let name_len = match &attr.attr.name {
|
||||
dioxus_rsx::ElementAttrName::BuiltIn(name) => {
|
||||
let name = name.to_string();
|
||||
name.len()
|
||||
}
|
||||
dioxus_rsx::ElementAttrName::Custom(name) => name.value().len() + 2,
|
||||
};
|
||||
|
||||
len + name.span().line_length() + 6
|
||||
total += name_len;
|
||||
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
|
||||
|
@ -218,7 +236,7 @@ impl<'a> Writer<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
trait SpanLength {
|
||||
pub(crate) trait SpanLength {
|
||||
fn line_length(&self) -> usize;
|
||||
}
|
||||
impl SpanLength for Span {
|
||||
|
|
|
@ -12,7 +12,7 @@ macro_rules! twoway {
|
|||
#[test]
|
||||
fn $name() {
|
||||
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);
|
||||
// normalize line endings
|
||||
let out = out.replace("\r", "");
|
||||
|
|
|
@ -33,7 +33,7 @@ rsx! {
|
|||
}
|
||||
|
||||
// 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
|
||||
div {
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
use dioxus_autofmt::{IndentOptions, IndentType};
|
||||
|
||||
macro_rules! twoway {
|
||||
($val:literal => $name:ident) => {
|
||||
($val:literal => $name:ident ($indent:expr)) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
let src_right = include_str!(concat!("./wrong/", $val, ".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);
|
||||
|
||||
// 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)));
|
||||
|
|
7
packages/autofmt/tests/wrong/comments-tab.rsx
Normal file
7
packages/autofmt/tests/wrong/comments-tab.rsx
Normal file
|
@ -0,0 +1,7 @@
|
|||
rsx! {
|
||||
div {
|
||||
// Comments
|
||||
class: "asdasd",
|
||||
"hello world"
|
||||
}
|
||||
}
|
5
packages/autofmt/tests/wrong/comments-tab.wrong.rsx
Normal file
5
packages/autofmt/tests/wrong/comments-tab.wrong.rsx
Normal file
|
@ -0,0 +1,5 @@
|
|||
rsx! {
|
||||
div {
|
||||
// Comments
|
||||
class: "asdasd", "hello world" }
|
||||
}
|
3
packages/autofmt/tests/wrong/multi-tab.rsx
Normal file
3
packages/autofmt/tests/wrong/multi-tab.rsx
Normal file
|
@ -0,0 +1,3 @@
|
|||
fn app(cx: Scope) -> Element {
|
||||
cx.render(rsx! { div { "hello world" } })
|
||||
}
|
5
packages/autofmt/tests/wrong/multi-tab.wrong.rsx
Normal file
5
packages/autofmt/tests/wrong/multi-tab.wrong.rsx
Normal file
|
@ -0,0 +1,5 @@
|
|||
fn app(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
div {"hello world" }
|
||||
})
|
||||
}
|
8
packages/autofmt/tests/wrong/multiexpr-tab.rsx
Normal file
8
packages/autofmt/tests/wrong/multiexpr-tab.rsx
Normal file
|
@ -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
|
||||
}
|
||||
})
|
||||
}
|
5
packages/autofmt/tests/wrong/multiexpr-tab.wrong.rsx
Normal file
5
packages/autofmt/tests/wrong/multiexpr-tab.wrong.rsx
Normal file
|
@ -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 }
|
||||
})
|
||||
}
|
|
@ -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
|
||||
|
||||
[dependencies]
|
||||
dioxus-rsx = { workspace = true }
|
||||
proc-macro2 = { version = "1.0.6", features = ["span-locations"] }
|
||||
quote = "1.0"
|
||||
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"] }
|
||||
prettyplease = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
indoc = "2.0.3"
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
[![Discord chat][discord-badge]][discord-url]
|
||||
|
||||
[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-url]: https://github.com/dioxuslabs/dioxus/blob/master/LICENSE
|
||||
[actions-badge]: https://github.com/dioxuslabs/dioxus/actions/workflows/main.yml/badge.svg
|
||||
|
@ -16,7 +16,7 @@
|
|||
|
||||
[Website](https://dioxuslabs.com) |
|
||||
[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)
|
||||
|
||||
## Overview
|
||||
|
|
|
@ -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 issues;
|
||||
mod metadata;
|
||||
|
|
31
packages/cli/.github/workflows/build.yml
vendored
31
packages/cli/.github/workflows/build.yml
vendored
|
@ -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
packages/cli/.github/workflows/docs.yml
vendored
34
packages/cli/.github/workflows/docs.yml
vendored
|
@ -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
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue