Merge branch 'master' into jk/form-ma
This commit is contained in:
commit
7c06b3a9a2
113 files changed
+3843
-2205
No files matched your search
@@ -0,0 +1,4 @@
|
|||||||
|
# These are supported funding model platforms
|
||||||
|
|
||||||
|
github: jkelleyrtp # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||||
|
open_collective: dioxus-labs # Replace with a single Open Collective username
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a report to help us improve Dioxus
|
||||||
|
---
|
||||||
|
|
||||||
|
**Problem**
|
||||||
|
|
||||||
|
<!-- A clear and concise description of what the bug is. -->
|
||||||
|
|
||||||
|
**Steps To Reproduce**
|
||||||
|
|
||||||
|
Steps to reproduce the behavior:
|
||||||
|
|
||||||
|
-
|
||||||
|
-
|
||||||
|
-
|
||||||
|
|
||||||
|
**Expected behavior**
|
||||||
|
|
||||||
|
A clear and concise description of what you expected to happen.
|
||||||
|
|
||||||
|
**Screenshots**
|
||||||
|
|
||||||
|
If applicable, add screenshots to help explain your problem.
|
||||||
|
|
||||||
|
**Environment:**
|
||||||
|
- Dioxus version: [e.g. v0.17, `master`]
|
||||||
|
- Rust version: [e.g. 1.43.0, `nightly`]
|
||||||
|
- OS info: [e.g. MacOS]
|
||||||
|
- App platform: [e.g. `web`, `desktop`]
|
||||||
|
|
||||||
|
**Questionnaire**
|
||||||
|
<!-- If you feel up to the challenge, please check one of the boxes below: -->
|
||||||
|
- [ ] I'm interested in fixing this myself but don't know where to start
|
||||||
|
- [ ] I would like to fix and I have a solution
|
||||||
|
- [ ] I don't have time to fix this right now, but maybe later
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
name: Feature Request
|
||||||
|
about: If you have any interesting advice, you can tell us.
|
||||||
|
---
|
||||||
|
|
||||||
|
## Specific Demand
|
||||||
|
|
||||||
|
<!--
|
||||||
|
What feature do you need, please describe it in detail.
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Implement Suggestion
|
||||||
|
|
||||||
|
<!--
|
||||||
|
If you have any suggestion for complete this feature, you can tell us.
|
||||||
|
-->
|
||||||
@@ -19,13 +19,7 @@ jobs:
|
|||||||
max-parallel: 2
|
max-parallel: 2
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
target:
|
target: [x86_64-pc-windows-gnu, x86_64-pc-windows-msvc]
|
||||||
[
|
|
||||||
i686-pc-windows-gnu,
|
|
||||||
i686-pc-windows-msvc,
|
|
||||||
x86_64-pc-windows-gnu,
|
|
||||||
x86_64-pc-windows-msvc,
|
|
||||||
]
|
|
||||||
cfg_release_channel: [stable]
|
cfg_release_channel: [stable]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -47,12 +41,6 @@ jobs:
|
|||||||
rustup target add ${{ matrix.target }}
|
rustup target add ${{ matrix.target }}
|
||||||
shell: powershell
|
shell: powershell
|
||||||
|
|
||||||
- name: Add mingw32 to path for i686-gnu
|
|
||||||
run: |
|
|
||||||
echo "C:\msys64\mingw32\bin" >> $GITHUB_PATH
|
|
||||||
if: matrix.target == 'i686-pc-windows-gnu' && matrix.channel == 'nightly'
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Add mingw64 to path for x86_64-gnu
|
- name: Add mingw64 to path for x86_64-gnu
|
||||||
run: echo "C:\msys64\mingw64\bin" >> $GITHUB_PATH
|
run: echo "C:\msys64\mingw64\bin" >> $GITHUB_PATH
|
||||||
if: matrix.target == 'x86_64-pc-windows-gnu' && matrix.channel == 'nightly'
|
if: matrix.target == 'x86_64-pc-windows-gnu' && matrix.channel == 'nightly'
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
/target
|
/target
|
||||||
|
/dist
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,26 @@ 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/),
|
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).
|
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)
|
## v0.1.7 (2022-01-08)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
+10
-10
@@ -1,26 +1,26 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "dioxus"
|
name = "dioxus"
|
||||||
version = "0.1.7"
|
version = "0.1.8"
|
||||||
authors = ["Jonathan Kelley"]
|
authors = ["Jonathan Kelley"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
description = "Core functionality for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences"
|
description = "Core functionality for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences"
|
||||||
license = "MIT/Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
repository = "https://github.com/DioxusLabs/dioxus/"
|
repository = "https://github.com/DioxusLabs/dioxus/"
|
||||||
homepage = "https://dioxuslabs.com"
|
homepage = "https://dioxuslabs.com"
|
||||||
documentation = "https://dioxuslabs.com"
|
documentation = "https://dioxuslabs.com"
|
||||||
keywords = ["dom", "ui", "gui", "react", "wasm"]
|
keywords = ["dom", "ui", "gui", "react", "wasm"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
dioxus-core = { path = "./packages/core", version = "^0.1.7" }
|
dioxus-core = { path = "./packages/core", version = "^0.1.9" }
|
||||||
dioxus-html = { path = "./packages/html", version = "^0.1.4", optional = true }
|
dioxus-html = { path = "./packages/html", version = "^0.1.6", optional = true }
|
||||||
dioxus-core-macro = { path = "./packages/core-macro", version = "^0.1.6", optional = true }
|
dioxus-core-macro = { path = "./packages/core-macro", version = "^0.1.7", optional = true }
|
||||||
dioxus-hooks = { path = "./packages/hooks", version = "^0.1.6", optional = true }
|
dioxus-hooks = { path = "./packages/hooks", version = "^0.1.7", optional = true }
|
||||||
|
|
||||||
dioxus-web = { path = "./packages/web", version = "^0.0.4", optional = true }
|
dioxus-web = { path = "./packages/web", version = "^0.0.5", optional = true }
|
||||||
dioxus-desktop = { path = "./packages/desktop", version = "^0.1.5", optional = true }
|
dioxus-desktop = { path = "./packages/desktop", version = "^0.1.6", optional = true }
|
||||||
dioxus-ssr = { path = "./packages/ssr", version = "^0.1.2", optional = true }
|
dioxus-ssr = { path = "./packages/ssr", version = "^0.1.3", optional = true }
|
||||||
|
|
||||||
dioxus-router = { path = "./packages/router", version = "^0.1.0", optional = true }
|
dioxus-router = { path = "./packages/router", version = "^0.1.1", optional = true }
|
||||||
dioxus-mobile = { path = "./packages/mobile", version = "^0.0.3", optional = true }
|
dioxus-mobile = { path = "./packages/mobile", version = "^0.0.3", optional = true }
|
||||||
# dioxus-liveview = { path = "./packages/liveview", optional = true }
|
# dioxus-liveview = { path = "./packages/liveview", optional = true }
|
||||||
|
|
||||||
|
|||||||
+176
@@ -0,0 +1,176 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
Permission is hereby granted, free of charge, to any
|
||||||
|
person obtaining a copy of this software and associated
|
||||||
|
documentation files (the "Software"), to deal in the
|
||||||
|
Software without restriction, including without
|
||||||
|
limitation the rights to use, copy, modify, merge,
|
||||||
|
publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software
|
||||||
|
is furnished to do so, subject to the following
|
||||||
|
conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice
|
||||||
|
shall be included in all copies or substantial portions
|
||||||
|
of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||||
|
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||||
|
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||||
|
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||||
|
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||||
|
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
DEALINGS IN THE SOFTWARE.
|
||||||
@@ -35,11 +35,12 @@
|
|||||||
</a>
|
</a>
|
||||||
<!-- Discord -->
|
<!-- Discord -->
|
||||||
<a href="https://discord.gg/XgGxMSkvUM">
|
<a href="https://discord.gg/XgGxMSkvUM">
|
||||||
<img src="https://badgen.net/discord/members/XgGxMSkvUM" alt="Awesome Page" />
|
<img src="https://img.shields.io/discord/899851952891002890.svg?logo=discord&style=flat-square" alt="Discord Link" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<h3>
|
<h3>
|
||||||
<a href="https://dioxuslabs.com"> Website </a>
|
<a href="https://dioxuslabs.com"> Website </a>
|
||||||
@@ -64,12 +65,12 @@ Dioxus is a portable, performant, and ergonomic framework for building cross-pla
|
|||||||
|
|
||||||
```rust
|
```rust
|
||||||
fn app(cx: Scope) -> Element {
|
fn app(cx: Scope) -> Element {
|
||||||
let mut count = use_state(&cx, || 0);
|
let (count, set_count) = use_state(&cx, || 0);
|
||||||
|
|
||||||
cx.render(rsx!(
|
cx.render(rsx!(
|
||||||
h1 { "High-Five counter: {count}" }
|
h1 { "High-Five counter: {count}" }
|
||||||
button { onclick: move |_| count += 1, "Up high!" }
|
button { onclick: move |_| set_count(count + 1), "Up high!" }
|
||||||
button { onclick: move |_| count -= 1, "Down low!" }
|
button { onclick: move |_| set_count(count - 1), "Down low!" }
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -84,7 +85,7 @@ If you know React, then you already know Dioxus.
|
|||||||
- Comprehensive inline documentation - hover and guides for all HTML elements, listeners, and events.
|
- Comprehensive inline documentation - hover and guides for all HTML elements, listeners, and events.
|
||||||
- Extremely memory efficient - 0 global allocations for steady-state components.
|
- Extremely memory efficient - 0 global allocations for steady-state components.
|
||||||
- Multi-channel asynchronous scheduler for first-class async support.
|
- Multi-channel asynchronous scheduler for first-class async support.
|
||||||
- And more! Read the [full release post here](https://dioxuslabs.com/blog/introducing-dioxus/).
|
- And more! Read the [full release post](https://dioxuslabs.com/blog/introducing-dioxus/).
|
||||||
|
|
||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
@@ -141,76 +142,56 @@ By using Rust, we gain:
|
|||||||
|
|
||||||
Specifically, Dioxus provides us many other assurances:
|
Specifically, Dioxus provides us many other assurances:
|
||||||
|
|
||||||
- Proper use of immutable datastructures
|
- Proper use of immutable data structures
|
||||||
- Guaranteed error handling (so you can sleep easy at night not worrying about `cannot read property of undefined`)
|
- Guaranteed error handling (so you can sleep easy at night not worrying about `cannot read property of undefined`)
|
||||||
- Native performance on mobile
|
- Native performance on mobile
|
||||||
- Direct access to system IO
|
- Direct access to system IO
|
||||||
|
|
||||||
And much more. Dioxus makes Rust apps just as fast to write as React apps, but affords more robustness, giving your frontend team greater confidence in making big changes in shorter time.
|
And much more. Dioxus makes Rust apps just as fast to write as React apps, but affords more robustness, giving your frontend team greater confidence in making big changes in shorter time.
|
||||||
|
|
||||||
### Why NOT Dioxus?
|
## Why NOT Dioxus?
|
||||||
You shouldn't use Dioxus if:
|
You shouldn't use Dioxus if:
|
||||||
|
|
||||||
- You don't like the React Hooks approach to frontend
|
- You don't like the React Hooks approach to frontend
|
||||||
- You need a no-std renderer
|
- You need a no-std renderer
|
||||||
- You want to support browsers where Wasm or asm.js are not supported.
|
- You want to support browsers where Wasm or asm.js are not supported.
|
||||||
- You need a Send+Sync UI solution (Dioxus is not currently ThreadSafe)
|
- You need a Send+Sync UI solution (Dioxus is not currently thread-safe)
|
||||||
|
|
||||||
### Comparison with other Rust UI frameworks
|
## Comparison with other Rust UI frameworks
|
||||||
Dioxus primarily emphasizes **developer experience** and **familiarity with React principles**.
|
Dioxus primarily emphasizes **developer experience** and **familiarity with React principles**.
|
||||||
|
|
||||||
- [Yew](https://github.com/yewstack/yew): prefers the elm pattern instead of React-hooks, no borrowed props, supports SSR (no hydration).
|
- [Yew](https://github.com/yewstack/yew): prefers the elm pattern instead of React-hooks, no borrowed props, supports SSR (no hydration).
|
||||||
- [Percy](https://github.com/chinedufn/percy): Supports SSR but less emphasis on state management and event handling.
|
- [Percy](https://github.com/chinedufn/percy): Supports SSR but with less emphasis on state management and event handling.
|
||||||
- [Sycamore](https://github.com/sycamore-rs/sycamore): VDOM-less using fine-grained reactivity, but lacking in ergonomics.
|
- [Sycamore](https://github.com/sycamore-rs/sycamore): VDOM-less using fine-grained reactivity, but lacking in ergonomics.
|
||||||
- [Dominator](https://github.com/Pauan/rust-dominator): Signal-based zero-cost alternative, less emphasis on community and docs.
|
- [Dominator](https://github.com/Pauan/rust-dominator): Signal-based zero-cost alternative, less emphasis on community and docs.
|
||||||
|
- [Azul](https://azul.rs): Fully native HTML/CSS renderer for desktop applications, no support for web/ssr
|
||||||
|
|
||||||
|
|
||||||
# Parity with React
|
## Parity with React & Roadmap
|
||||||
|
|
||||||
Dioxus is heavily inspired by React, but we want your transition to feel like an upgrade. Dioxus is _most_ of the way there, but missing a few key features. This parity table does not necessarily include important ecosystem crates like code blocks, markdown, resizing hooks, etc.
|
Dioxus is heavily inspired by React, but we want your transition to feel like an upgrade. Dioxus is _most_ of the way there, but missing a few key features. These include:
|
||||||
|
|
||||||
|
- Portals
|
||||||
|
- Suspense integration with SSR
|
||||||
|
- Server Components / Bundle Splitting / Lazy
|
||||||
|
|
||||||
| Feature | Dioxus | React | Notes for Dioxus |
|
Dioxus is unique in the Rust ecosystem in that it supports:
|
||||||
| ------------------------- | ------ | ----- | -------------------------------------------------------------------- |
|
|
||||||
| Conditional Rendering | ✅ | ✅ | if/then to hide/show component |
|
|
||||||
| Map, Iterator | ✅ | ✅ | map/filter/reduce to produce rsx! |
|
|
||||||
| Keyed Components | ✅ | ✅ | advanced diffing with keys |
|
|
||||||
| Web | ✅ | ✅ | renderer for web browser |
|
|
||||||
| Desktop (webview) | ✅ | ✅ | renderer for desktop |
|
|
||||||
| Shared State (Context) | ✅ | ✅ | share state through the tree |
|
|
||||||
| Hooks | ✅ | ✅ | memory cells in components |
|
|
||||||
| SSR | ✅ | ✅ | render directly to string |
|
|
||||||
| Component Children | ✅ | ✅ | cx.children() as a list of nodes |
|
|
||||||
| Headless components | ✅ | ✅ | components that don't return real elements |
|
|
||||||
| Fragments | ✅ | ✅ | multiple elements without a real root |
|
|
||||||
| Manual Props | ✅ | ✅ | Manually pass in props with spread syntax |
|
|
||||||
| Controlled Inputs | ✅ | ✅ | stateful wrappers around inputs |
|
|
||||||
| CSS/Inline Styles | ✅ | ✅ | syntax for inline styles/attribute groups |
|
|
||||||
| Custom elements | ✅ | ✅ | Define new element primitives |
|
|
||||||
| Suspense | ✅ | ✅ | schedule future render from future/promise |
|
|
||||||
| Integrated error handling | ✅ | ✅ | Gracefully handle errors with ? syntax |
|
|
||||||
| NodeRef | ✅ | ✅ | gain direct access to nodes |
|
|
||||||
| Re-hydration | ✅ | ✅ | Pre-render to HTML to speed up first contentful paint |
|
|
||||||
| Jank-Free Rendering | ✅ | ✅ | Large diffs are segmented across frames for silky-smooth transitions |
|
|
||||||
| Effects | ✅ | ✅ | Run effects after a component has been committed to render |
|
|
||||||
| Portals | 🛠 | ✅ | Render nodes outside of the traditional tree structure |
|
|
||||||
| Cooperative Scheduling | 🛠 | ✅ | Prioritize important events over non-important events |
|
|
||||||
| Server Components | 🛠 | ✅ | Hybrid components for SPA and Server |
|
|
||||||
| Bundle Splitting | 👀 | ✅ | Efficiently and asynchronously load the app |
|
|
||||||
| Lazy Components | 👀 | ✅ | Dynamically load the new components as the page is loaded |
|
|
||||||
| 1st class global state | ✅ | ✅ | redux/recoil/mobx on top of context |
|
|
||||||
| Runs natively | ✅ | ❓ | runs as a portable binary w/o a runtime (Node) |
|
|
||||||
| Subtree Memoization | ✅ | ❓ | skip diffing static element subtrees |
|
|
||||||
| High-efficiency templates | 🛠 | ❓ | rsx! calls are translated to templates on the DOM's side |
|
|
||||||
| Compile-time correct | ✅ | ❓ | Throw errors on invalid template layouts |
|
|
||||||
| Heuristic Engine | ✅ | ❓ | track component memory usage to minimize future allocations |
|
|
||||||
| Fine-grained reactivity | 👀 | ❓ | Skip diffing for fine-grain updates |
|
|
||||||
|
|
||||||
- ✅ = implemented and working
|
- Components with props that borrow from their parent
|
||||||
- 🛠 = actively being worked on
|
- Server-side-rendering with client-side hydration
|
||||||
- 👀 = not yet implemented or being worked on
|
- Support for desktop applications
|
||||||
- ❓ = not sure if will or can implement
|
|
||||||
|
|
||||||
|
For more information on what features are currently available and the roadmap for the future, be sure to check out [the guide](https://dioxuslabs.com/guide/).
|
||||||
|
|
||||||
|
## Projects in the ecosystem
|
||||||
|
|
||||||
|
Want to jump in and help build the future of Rust frontend? There's plenty of places where your contributions can make a huge difference:
|
||||||
|
|
||||||
|
- [TUI renderer](https://github.com/dioxusLabs/rink)
|
||||||
|
- [CLI Tooling](https://github.com/dioxusLabs/cli)
|
||||||
|
- [Documentation and Example Projects](https://github.com/dioxusLabs/docsite)
|
||||||
|
- LiveView and Web Server
|
||||||
|
- Asset System
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ In general, Dioxus and React share many functional similarities. If this guide i
|
|||||||
|
|
||||||
## Multiplatform
|
## Multiplatform
|
||||||
|
|
||||||
Dioxus is a *portable* toolkit, meaning the Core implementation can run anywhere with no platform-dependent linking. Unlike many other Rust frontend toolkits, Dioxus is not intrinsically linked to Web-Sys. In fact, every element and event listener can be swapped out at compile time. By default, Dioxus ships with the `Html` feature enabled which can be disabled depending on your target renderer.
|
Dioxus is a *portable* toolkit, meaning the Core implementation can run anywhere with no platform-dependent linking. Unlike many other Rust frontend toolkits, Dioxus is not intrinsically linked to Web-Sys. In fact, every element and event listener can be swapped out at compile time. By default, Dioxus ships with the `html` feature enabled, but this can be disabled depending on your target renderer.
|
||||||
|
|
||||||
Right now, we have several 1st-party renderers:
|
Right now, we have several 1st-party renderers:
|
||||||
- WebSys (for WASM)
|
- WebSys (for WASM)
|
||||||
@@ -33,8 +33,7 @@ Right now, we have several 1st-party renderers:
|
|||||||
|
|
||||||
### Web Support
|
### Web Support
|
||||||
---
|
---
|
||||||
|
The Web is the best-supported target platform for Dioxus. To run on the Web, your app must be compiled to WebAssembly and depend on the `dioxus` crate with the `web` feature enabled. Because of the limitations of Wasm not every crate will work with your web-apps, so you'll need to make sure that your crates work without native system calls (timers, IO, etc).
|
||||||
The Web is the most-supported target platform for Dioxus. To run on the Web, your app must be compiled to WebAssembly and depend on the `dioxus` crate with the `web` feature enabled. Because of the Wasm limitation, not every crate will work with your web-apps, so you'll need to make sure that your crates work without native system calls (timers, IO, etc).
|
|
||||||
|
|
||||||
Because the web is a fairly mature platform, we expect there to be very little API churn for web-based features.
|
Because the web is a fairly mature platform, we expect there to be very little API churn for web-based features.
|
||||||
|
|
||||||
@@ -45,6 +44,7 @@ Examples:
|
|||||||
- [ECommerce](https://github.com/DioxusLabs/example-projects/tree/master/ecommerce-site)
|
- [ECommerce](https://github.com/DioxusLabs/example-projects/tree/master/ecommerce-site)
|
||||||
|
|
||||||
[](https://github.com/DioxusLabs/example-projects/blob/master/todomvc)
|
[](https://github.com/DioxusLabs/example-projects/blob/master/todomvc)
|
||||||
|
|
||||||
### SSR Support
|
### SSR Support
|
||||||
---
|
---
|
||||||
Dioxus supports server-side rendering!
|
Dioxus supports server-side rendering!
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Summary
|
# Summary
|
||||||
|
|
||||||
- [Introduction](README.md)
|
- [Introduction](README.md)
|
||||||
- [Getting Setup](setup.md)
|
- [Getting Set Up](setup.md)
|
||||||
- [Hello, World!](hello_world.md)
|
- [Hello, World!](hello_world.md)
|
||||||
- [Describing the UI](elements/index.md)
|
- [Describing the UI](elements/index.md)
|
||||||
- [Intro to Elements](elements/vnodes.md)
|
- [Intro to Elements](elements/vnodes.md)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Core Topics
|
# Core Topics
|
||||||
|
|
||||||
In this chapter, we'll cover some core topics on how Dioxus works and how to best leverage the features to build a beautiful, reactive app.
|
In this chapter, we'll cover some core topics about how Dioxus works and how to best leverage the features to build a beautiful, reactive app.
|
||||||
|
|
||||||
At a very high level, Dioxus is simply a Rust framework for _declaring_ user interfaces and _reacting_ to changes.
|
At a very high level, Dioxus is simply a Rust framework for _declaring_ user interfaces and _reacting_ to changes.
|
||||||
|
|
||||||
|
|||||||
@@ -33,9 +33,9 @@ async fetch_name() -> String {
|
|||||||
|
|
||||||
This component will only schedule its render once the fetch is complete. However, we _don't_ recommend using async/await directly in your components.
|
This component will only schedule its render once the fetch is complete. However, we _don't_ recommend using async/await directly in your components.
|
||||||
|
|
||||||
Async is a notoriously challenging yet rewarding tool for efficient tools. If not careful, locking and unlocking shared aspects of the component's context can lead to data races and panics. If a shared resource is locked while the component is awaiting, then other components can be locked or panic when trying to access the same resource. These rules are especially important when references to shared global state are accessed using the context object's lifetime. If mutable references to data captured immutably by the context are taken, then the component will panic, and cause confusion.
|
Async is a notoriously challenging yet rewarding tool for efficient tools. If not careful, locking and unlocking shared aspects of the component's context can lead to data races and panics. If a shared resource is locked while the component is awaiting, then other components can be locked or panic when trying to access the same resource. These rules are especially important when references to shared global state are accessed using the context object's lifetime. If mutable references to data captured immutably by the context are taken, then the component will panic, causing confusion.
|
||||||
|
|
||||||
Instead, we suggest using hooks and future combinators that can safely utilize the safe guards of the component's Context when interacting with async tasks.
|
Instead, we suggest using hooks and future combinators that can safely utilize the safeguards of the component's Context when interacting with async tasks.
|
||||||
|
|
||||||
As part of our Dioxus hooks crate, we provide a data loader hook which pauses a component until its async dependencies are ready. This caches requests, reruns the fetch if dependencies have changed, and provides the option to render something else while the component is loading.
|
As part of our Dioxus hooks crate, we provide a data loader hook which pauses a component until its async dependencies are ready. This caches requests, reruns the fetch if dependencies have changed, and provides the option to render something else while the component is loading.
|
||||||
|
|
||||||
|
|||||||
@@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
Dioxus differs slightly from other UI virtual doms in some subtle ways due to its memory allocator.
|
Dioxus differs slightly from other UI virtual doms in some subtle ways due to its memory allocator.
|
||||||
|
|
||||||
One important aspect to understand is how props are passed down from parent components to children. All "components" (custom user-made UI elements) are tightly allocated together in an arena. However, because props and hooks are generically typed, they are casted to any and allocated on the heap - not in the arena with the components.
|
One important aspect to understand is how props are passed down from parent components to children. All "components" (custom user-made UI elements) are tightly allocated together in an arena. However, because props and hooks are generically typed, they are casted to `Any` and allocated on the heap - not in the arena with the components.
|
||||||
|
|
||||||
With this system, we try to be more efficient when leaving the component arena and entering the heap. By default, props are memoized between renders using COW and context. This makes props comparisons fast - done via ptr comparisons on the cow pointer. Because memoization is done by default, parent re-renders will _not_ cascade to children if the child's props did not change.
|
With this system, we try to be more efficient when leaving the component arena and entering the heap. By default, props are memoized between renders using COW and context. This makes props comparisons fast - done via ptr comparisons on the cow pointer. Because memoization is done by default, parent re-renders will _not_ cascade to children if the child's props did not change.
|
||||||
|
|
||||||
https://dmitripavlutin.com/use-react-memo-wisely/
|
https://dmitripavlutin.com/use-react-memo-wisely/
|
||||||
|
|
||||||
This behavior is defined as an implicit attribute to user components. When in React land you might wrap a component is `react.memo`, Dioxus components are automatically memoized via an implicit attribute. You can manually configure this behavior on any component with "nomemo" to disable memoization.
|
This behavior is defined as an attribute implicit to user components. When in React land you might wrap a component with `react.memo`, Dioxus components are automatically memoized via an implicit attribute. You can manually configure this behavior on any component with "nomemo" to disable memoization.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
fn test() -> DomTree {
|
fn test() -> DomTree {
|
||||||
@@ -40,7 +40,7 @@ fn test_component(cx: Scope, name: String) -> Element {
|
|||||||
|
|
||||||
"This is different than React, why differ?".
|
"This is different than React, why differ?".
|
||||||
|
|
||||||
Take a component likes this:
|
Take a component like this:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
fn test(cx: Scope) -> DomTree {
|
fn test(cx: Scope) -> DomTree {
|
||||||
@@ -55,4 +55,4 @@ fn test(cx: Scope) -> DomTree {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
While the contents of the destructured bundle might change, not every child component will need to be re-rendered.
|
While the contents of the destructured bundle might change, not every child component will need to be re-rendered every time the context changes.
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# Signals: Skipping the Diff
|
# Signals: Skipping the Diff
|
||||||
|
|
||||||
In most cases, the traditional VirtualDOM diffing pattern is plenty fast. Dioxus will compare trees of VNodes, find the differences, and then update the Renderer's DOM with the updates. However, this can generate a lot of overhead for certain types of components. In apps where reducing visual latency is a top priority, you can opt into the `Signals` api to entirely disable diffing of hot-path components. Dioxus will then automatically construct a state machine for your component, making updates nearly instant.
|
In most cases, the traditional VirtualDOM diffing pattern is plenty fast. Dioxus will compare trees of VNodes, find the differences, and then update the Renderer's DOM with the diffs. However, this can generate a lot of overhead for certain types of components. In apps where reducing visual latency is a top priority, you can opt into the `Signals` api to entirely disable diffing of hot-path components. Dioxus will then automatically construct a state machine for your component, making updates nearly instant.
|
||||||
|
|
||||||
Signals build on the same infrastructure that powers asynchronous rendering where in-tree values can be updated outside of the render phase. In async rendering, a future is used as the signal source. With the raw signal API, any value can be used as a signal source.
|
Signals build on the same infrastructure that powers asynchronous rendering where in-tree values can be updated outside of the render phase. In async rendering, a future is used as the signal source. With the raw signal API, any value can be used as a signal source.
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@ fn Calculator(cx: Scope) -> DomTree {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Do you notice how we can use built-in operations on signals? Under the hood, we actually create a new derived signal that depends on `a` and `b`. Whenever `a` or `b` update, then `c` will update. If we need to create a new derived signal that's more complex that a basic operation (`std::ops`) we can either chain signals together or combine them:
|
Do you notice how we can use built-in operations on signals? Under the hood, we actually create a new derived signal that depends on `a` and `b`. Whenever `a` or `b` update, then `c` will update. If we need to create a new derived signal that's more complex than a basic operation (`std::ops`) we can either chain signals together or combine them:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let mut a = use_signal(&cx, || 0);
|
let mut a = use_signal(&cx, || 0);
|
||||||
@@ -88,7 +88,7 @@ let mut a = use_signal(&cx, || 0);
|
|||||||
let c = *a + *b;
|
let c = *a + *b;
|
||||||
```
|
```
|
||||||
|
|
||||||
Calling `deref` or `derefmut` is actually more complex than it seems. When a value is derefed, you're essentially telling Dioxus that _this_ element _needs_ to be subscribed to the signal. If a signal is derefed outside of an element, the entire component will be subscribed and the advantage of skipping diffing will be lost. Dioxus will throw an error in the console when this happens to tell you that you're using signals wrong, but your component will continue to work.
|
Calling `deref` or `deref_mut` is actually more complex than it seems. When a value is derefed, you're essentially telling Dioxus that _this_ element _needs_ to be subscribed to the signal. If a signal is derefed outside of an element, the entire component will be subscribed and the advantage of skipping diffing will be lost. Dioxus will throw an error in the console when this happens to tell you that you're using signals wrong, but your component will continue to work.
|
||||||
|
|
||||||
## Global Signals
|
## Global Signals
|
||||||
|
|
||||||
@@ -128,7 +128,7 @@ Sometimes you want to use a collection of items. With Signals, you can bypass di
|
|||||||
|
|
||||||
By default, Dioxus is limited when you use iter/map. With the `For` component, you can provide an iterator and a function for the iterator to map to.
|
By default, Dioxus is limited when you use iter/map. With the `For` component, you can provide an iterator and a function for the iterator to map to.
|
||||||
|
|
||||||
Dioxus automatically understands how to use your signals when mixed with iterators through Deref/DerefMut. This lets you efficiently map collections while avoiding the re-rendering of lists. In essence, signals act as a hint to Dioxus on how to avoid un-necessary checks and renders, making your app faster.
|
Dioxus automatically understands how to use your signals when mixed with iterators through `Deref`/`DerefMut`. This lets you efficiently map collections while avoiding the re-rendering of lists. In essence, signals act as a hint to Dioxus on how to avoid un-necessary checks and renders, making your app faster.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
const DICT: AtomFamily<String, String> = |_| {};
|
const DICT: AtomFamily<String, String> = |_| {};
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ For reference, check out the WebSys renderer as a starting point for your custom
|
|||||||
|
|
||||||
## Trait implementation and DomEdits
|
## Trait implementation and DomEdits
|
||||||
|
|
||||||
The current `RealDom` trait lives in `dioxus_core/diff`. A version of it is provided here (but might not be up-to-date):
|
The current `RealDom` trait lives in `dioxus-core/diff`. A version of it is provided here (but might not be up-to-date):
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
pub trait RealDom<'a> {
|
pub trait RealDom<'a> {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ In this chapter, you'll learn about:
|
|||||||
|
|
||||||
## The use case
|
## The use case
|
||||||
|
|
||||||
Let's say you're building a user interface and want to make some part of it clickable to another website. You would normally start with the HTML `<a>` tag, like so:
|
Let's say you're building a user interface and want to make some part of it a clickable link to another website. You would normally start with the HTML `<a>` tag, like so:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
rsx!(
|
rsx!(
|
||||||
@@ -98,7 +98,7 @@ struct ClickableProps<'a> {
|
|||||||
children: Element<'a>
|
children: Element<'a>
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clickable(cx: Scope<ClickableProps>) -> Element {
|
fn Clickable(cx: Scope<ClickableProps>) -> Element {
|
||||||
cx.render(rsx!(
|
cx.render(rsx!(
|
||||||
a {
|
a {
|
||||||
href: "{cx.props.href}",
|
href: "{cx.props.href}",
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ struct PostData {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
If we look at the layout of the component, we notice quite a few buttons and functionality:
|
If we look at the layout of the component, we notice quite a few buttons and pieces of functionality:
|
||||||
|
|
||||||
- Upvote/Downvote
|
- Upvote/Downvote
|
||||||
- View comments
|
- View comments
|
||||||
@@ -53,7 +53,7 @@ If we look at the layout of the component, we notice quite a few buttons and fun
|
|||||||
- View article
|
- View article
|
||||||
- Visit user
|
- Visit user
|
||||||
|
|
||||||
If we included all this functionality in one `rsx!` call, it would be huge! Instead, let's break the post down into some core pieces:
|
If we included all this functionality in one `rsx!` call it would be huge! Instead, let's break the post down into some core pieces:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -133,7 +133,7 @@ fn VoteButton(cx: Scope<VoteButtonProps>) -> Element {
|
|||||||
|
|
||||||
## Borrowing
|
## Borrowing
|
||||||
|
|
||||||
You can avoid clones using borrowed component syntax. For example, let's say we passed the `TitleCard` title as an `&str` instead of `String`. In JavaScript, the string would be copied by reference - none of the contents would be copied, but rather the reference to the string's contents are copied. In Rust, this would be similar to calling `clone` on `Rc<str>`.
|
You can avoid clones by using borrowed component syntax. For example, let's say we passed the `TitleCard` title as an `&str` instead of `String`. In JavaScript, the string would be copied by reference - none of the contents would be copied, but rather the reference to the string's contents are copied. In Rust, this would be similar to calling `clone` on `Rc<str>`.
|
||||||
|
|
||||||
Because we're working in Rust, we can choose to either use `Rc<str>`, clone `Title` on every re-render of `Post`, or simply borrow it. In most cases, you'll just want to let `Title` be cloned.
|
Because we're working in Rust, we can choose to either use `Rc<str>`, clone `Title` on every re-render of `Post`, or simply borrow it. In most cases, you'll just want to let `Title` be cloned.
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ Now that we have a "logged_in" flag accessible in our props, we can render two d
|
|||||||
|
|
||||||
```rust
|
```rust
|
||||||
fn App(cx: Scope<AppProps>) -> Element {
|
fn App(cx: Scope<AppProps>) -> Element {
|
||||||
if props.logged_in {
|
if cx.props.logged_in {
|
||||||
cx.render(rsx!{
|
cx.render(rsx!{
|
||||||
DashboardScreen {}
|
DashboardScreen {}
|
||||||
})
|
})
|
||||||
@@ -39,7 +39,7 @@ fn App(cx: Scope<AppProps>) -> Element {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
When the user is logged in, then this component will return the DashboardScreen. Else, the component will render the LoginScreen.
|
When the user is logged in, then this component will return the DashboardScreen. If they're not logged in, the component will render the LoginScreen.
|
||||||
|
|
||||||
## Using match statements
|
## Using match statements
|
||||||
|
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ fn App(Scope) -> Element {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
If you tried to build this app right now, you'll get an error message saying that `Post is private, trying changing it to public`. This is because we haven't properly exported our component! To fix this, we need to make sure both the Props and Component are declared as "public":
|
If you tried to build this app right now, you'll get an error message saying that `Post is private, try changing it to public`. This is because we haven't properly exported our component! To fix this, we need to make sure both the Props and Component are declared as "public":
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// src/post/mod.rs
|
// src/post/mod.rs
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Core Topics
|
# Core Topics
|
||||||
|
|
||||||
In this chapter, we'll cover some core topics on how Dioxus works and how to best leverage the features to build a beautiful, reactive app.
|
In this chapter, we'll cover some core topics about how Dioxus works and how to best leverage the features to build a beautiful, reactive app.
|
||||||
|
|
||||||
At a very high level, Dioxus is simply a Rust framework for _declaring_ user interfaces and _reacting_ to changes.
|
At a very high level, Dioxus is simply a Rust framework for _declaring_ user interfaces and _reacting_ to changes.
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ $ tree
|
|||||||
|
|
||||||
We are greeted with a pre-initialized git repository, our code folder (`src`) and our project file (`Cargo.toml`).
|
We are greeted with a pre-initialized git repository, our code folder (`src`) and our project file (`Cargo.toml`).
|
||||||
|
|
||||||
Our `src` folder holds our code. Our `main.rs` file holds our `fn main` which will be executed when our app is ran.
|
Our `src` folder holds our code. Our `main.rs` file holds our `fn main` which will be executed when our app is run.
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
$ more src/main.rs
|
$ more src/main.rs
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ Typically, immediate-mode GUIs are simpler to write but can slow down as more fe
|
|||||||
|
|
||||||
Many GUIs today are written in *Retained mode* - your code changes the data of the user interface but the renderer is responsible for actually drawing to the screen. In these cases, our GUI's state sticks around as the UI is rendered. To help accommodate retained mode GUIs, like the web browser, Dioxus provides a mechanism to keep state around.
|
Many GUIs today are written in *Retained mode* - your code changes the data of the user interface but the renderer is responsible for actually drawing to the screen. In these cases, our GUI's state sticks around as the UI is rendered. To help accommodate retained mode GUIs, like the web browser, Dioxus provides a mechanism to keep state around.
|
||||||
|
|
||||||
> Note: Even though hooks are accessible, you should still prefer to one-way data flow and encapsulation. Your UI code should be as predictable as possible. Dioxus is plenty fast, even for the largest apps.
|
> Note: Even though hooks are accessible, you should still prefer one-way data flow and encapsulation. Your UI code should be as predictable as possible. Dioxus is plenty fast, even for the largest apps.
|
||||||
|
|
||||||
## Mechanics of Hooks
|
## Mechanics of Hooks
|
||||||
In order to have state stick around between renders, Dioxus provides the `hook` through the `use_hook` API. This gives us a mutable reference to data returned from the initialization function.
|
In order to have state stick around between renders, Dioxus provides the `hook` through the `use_hook` API. This gives us a mutable reference to data returned from the initialization function.
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
When Dioxus renders your app, it will pass an immutable reference of `PostProps` to your `Post` component. Here, you can pass the state down into children.
|
When Dioxus renders your app, it will pass an immutable reference to `PostProps` into your `Post` component. Here, you can pass the state down into children.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
fn App(cx: Scope<PostProps>) -> Element {
|
fn App(cx: Scope<PostProps>) -> Element {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Overview
|
# Overview
|
||||||
|
|
||||||
In this chapter, we're going to get "set up" with a small desktop application.
|
In this chapter, we're going to get set up with a small desktop application.
|
||||||
|
|
||||||
We'll learn about:
|
We'll learn about:
|
||||||
- Installing the Rust programming language
|
- Installing the Rust programming language
|
||||||
@@ -15,7 +15,6 @@ For platform-specific guides, check out the [Platform Specific Guides](../platfo
|
|||||||
Dioxus requires a few main things to get up and running:
|
Dioxus requires a few main things to get up and running:
|
||||||
|
|
||||||
- The [Rust compiler](https://www.rust-lang.org) and associated build tooling
|
- The [Rust compiler](https://www.rust-lang.org) and associated build tooling
|
||||||
|
|
||||||
- An editor of your choice, ideally configured with the [Rust-Analyzer LSP plugin](https://rust-analyzer.github.io)
|
- An editor of your choice, ideally configured with the [Rust-Analyzer LSP plugin](https://rust-analyzer.github.io)
|
||||||
|
|
||||||
Dioxus integrates very well with the Rust-Analyzer IDE plugin which will provide appropriate syntax highlighting, code navigation, folding, and more.
|
Dioxus integrates very well with the Rust-Analyzer IDE plugin which will provide appropriate syntax highlighting, code navigation, folding, and more.
|
||||||
@@ -24,7 +23,7 @@ Dioxus integrates very well with the Rust-Analyzer IDE plugin which will provide
|
|||||||
|
|
||||||
Head over to [https://rust-lang.org](http://rust-lang.org) and install the Rust compiler.
|
Head over to [https://rust-lang.org](http://rust-lang.org) and install the Rust compiler.
|
||||||
|
|
||||||
Once installed, make sure to install wasm32-unknown-unknown as a target if you're planning on deploying your app to the web.
|
Once installed, make sure to install wasm32-unknown-unknown as a target if you're planning on deploying your app to the web.
|
||||||
|
|
||||||
```
|
```
|
||||||
rustup target add wasm32-unknown-unknown
|
rustup target add wasm32-unknown-unknown
|
||||||
@@ -32,7 +31,7 @@ rustup target add wasm32-unknown-unknown
|
|||||||
|
|
||||||
### Platform-Specific Dependencies
|
### Platform-Specific Dependencies
|
||||||
|
|
||||||
If you are running a modern, mainstream operating system, you should need no additional setup to build WebView-based Desktop apps. However, if you are running an older version of Windows or a flavor of linux with no default web rendering engine, you might need to install some additional dependencies.
|
If you are running a modern, mainstream operating system, you should need no additional setup to build WebView-based Desktop apps. However, if you are running an older version of Windows or a flavor of Linux with no default web rendering engine, you might need to install some additional dependencies.
|
||||||
|
|
||||||
For windows users: download the [bootstrapper for Webview2 from Microsoft](https://developer.microsoft.com/en-us/microsoft-edge/webview2/)
|
For windows users: download the [bootstrapper for Webview2 from Microsoft](https://developer.microsoft.com/en-us/microsoft-edge/webview2/)
|
||||||
|
|
||||||
@@ -49,13 +48,13 @@ When distributing onto older Windows platforms or less-mainstream
|
|||||||
|
|
||||||
### Dioxus-CLI for dev server, bundling, etc.
|
### Dioxus-CLI for dev server, bundling, etc.
|
||||||
|
|
||||||
We also recommend installing the Dioxus CLI. The Dioxus CLI automates building and packaging for various targets and integrates with simulators, development servers, and app deployment. To install the CLI, you'll need cargo (should be automatically installed with Rust):
|
We also recommend installing the Dioxus CLI. The Dioxus CLI automates building and packaging for various targets and integrates with simulators, development servers, and app deployment. To install the CLI, you'll need cargo (which should be automatically installed with Rust):
|
||||||
|
|
||||||
```
|
```
|
||||||
$ cargo install dioxus-cli
|
$ cargo install dioxus-cli
|
||||||
```
|
```
|
||||||
|
|
||||||
You can update the dioxus-cli at any time with:
|
You can update dioxus-cli at any time with:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ cargo install --force dioxus-cli
|
$ cargo install --force dioxus-cli
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 58 KiB |
@@ -0,0 +1,38 @@
|
|||||||
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
dioxus::desktop::launch_cfg(app, |cfg| {
|
||||||
|
cfg.with_window(|w| w.with_title("BorderLess Demo").with_decorations(false))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app(cx: Scope) -> Element {
|
||||||
|
let window = dioxus::desktop::use_window(&cx);
|
||||||
|
|
||||||
|
cx.render(rsx!(
|
||||||
|
link { href:"https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css", rel:"stylesheet" }
|
||||||
|
header {
|
||||||
|
class: "text-gray-400 bg-gray-900 body-font",
|
||||||
|
onmousedown: move |_| window.drag(),
|
||||||
|
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",
|
||||||
|
span { class: "ml-3 text-xl", "Dioxus"}
|
||||||
|
}
|
||||||
|
nav { class: "md:ml-auto flex flex-wrap items-center text-base justify-center" }
|
||||||
|
button {
|
||||||
|
class: "inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0",
|
||||||
|
onmousedown: |evt| evt.cancel_bubble(),
|
||||||
|
onclick: move |_| window.minimize(true),
|
||||||
|
"Minimize"
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
class: "inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0",
|
||||||
|
onmousedown: |evt| evt.cancel_bubble(),
|
||||||
|
onclick: move |_| window.close(),
|
||||||
|
"Close"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
))
|
||||||
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Dioxus manages borrow lifetimes for you. This means any child may borrow from its parent. However, it is not possible
|
Dioxus manages borrow lifetimes for you. This means any child may borrow from its parent. However, it is not possible
|
||||||
to hand out an &mut T to children - all props are consumed by &P, so you'd only get an &&mut T.
|
to hand out an &mut T to children - all props are consumed by &P, so you'd only get an &&mut T.
|
||||||
|
|||||||
+21
-19
@@ -18,17 +18,19 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn app(cx: Scope) -> Element {
|
fn app(cx: Scope) -> Element {
|
||||||
let display_value: UseState<String> = use_state(&cx, || String::from("0"));
|
let (display_value, set_display_value) = use_state(&cx, || String::from("0"));
|
||||||
|
|
||||||
let input_digit = move |num: u8| {
|
let input_digit = move |num: u8| {
|
||||||
if display_value.get() == "0" {
|
if display_value == "0" {
|
||||||
display_value.set(String::new());
|
set_display_value(String::new());
|
||||||
}
|
}
|
||||||
display_value.modify().push_str(num.to_string().as_str());
|
set_display_value
|
||||||
|
.make_mut()
|
||||||
|
.push_str(num.to_string().as_str());
|
||||||
};
|
};
|
||||||
|
|
||||||
let input_operator = move |key: &str| {
|
let input_operator = move |key: &str| {
|
||||||
display_value.modify().push_str(key);
|
set_display_value.make_mut().push_str(key);
|
||||||
};
|
};
|
||||||
|
|
||||||
cx.render(rsx!(
|
cx.render(rsx!(
|
||||||
@@ -53,7 +55,7 @@ fn app(cx: Scope) -> Element {
|
|||||||
KeyCode::Num9 => input_digit(9),
|
KeyCode::Num9 => input_digit(9),
|
||||||
KeyCode::Backspace => {
|
KeyCode::Backspace => {
|
||||||
if !display_value.len() != 0 {
|
if !display_value.len() != 0 {
|
||||||
display_value.modify().pop();
|
set_display_value.make_mut().pop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
@@ -65,21 +67,21 @@ fn app(cx: Scope) -> Element {
|
|||||||
button {
|
button {
|
||||||
class: "calculator-key key-clear",
|
class: "calculator-key key-clear",
|
||||||
onclick: move |_| {
|
onclick: move |_| {
|
||||||
display_value.set(String::new());
|
set_display_value(String::new());
|
||||||
if display_value != "" {
|
if !display_value.is_empty(){
|
||||||
display_value.set("0".into());
|
set_display_value("0".into());
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[if display_value == "" { "C" } else { "AC" }]
|
[if display_value.is_empty() { "C" } else { "AC" }]
|
||||||
}
|
}
|
||||||
button {
|
button {
|
||||||
class: "calculator-key key-sign",
|
class: "calculator-key key-sign",
|
||||||
onclick: move |_| {
|
onclick: move |_| {
|
||||||
let temp = calc_val(display_value.get().clone());
|
let temp = calc_val(display_value.clone());
|
||||||
if temp > 0.0 {
|
if temp > 0.0 {
|
||||||
display_value.set(format!("-{}", temp));
|
set_display_value(format!("-{}", temp));
|
||||||
} else {
|
} else {
|
||||||
display_value.set(format!("{}", temp.abs()));
|
set_display_value(format!("{}", temp.abs()));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"±"
|
"±"
|
||||||
@@ -87,8 +89,8 @@ fn app(cx: Scope) -> Element {
|
|||||||
button {
|
button {
|
||||||
class: "calculator-key key-percent",
|
class: "calculator-key key-percent",
|
||||||
onclick: move |_| {
|
onclick: move |_| {
|
||||||
display_value.set(
|
set_display_value(
|
||||||
format!("{}", calc_val(display_value.get().clone()) / 100.0)
|
format!("{}", calc_val(display_value.clone()) / 100.0)
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
"%"
|
"%"
|
||||||
@@ -98,7 +100,7 @@ fn app(cx: Scope) -> Element {
|
|||||||
button { class: "calculator-key key-0", onclick: move |_| input_digit(0),
|
button { class: "calculator-key key-0", onclick: move |_| input_digit(0),
|
||||||
"0"
|
"0"
|
||||||
}
|
}
|
||||||
button { class: "calculator-key key-dot", onclick: move |_| display_value.modify().push_str("."),
|
button { class: "calculator-key key-dot", onclick: move |_| set_display_value.make_mut().push('.'),
|
||||||
"●"
|
"●"
|
||||||
}
|
}
|
||||||
(1..10).map(|k| rsx!{
|
(1..10).map(|k| rsx!{
|
||||||
@@ -130,7 +132,7 @@ fn app(cx: Scope) -> Element {
|
|||||||
}
|
}
|
||||||
button { class: "calculator-key key-equals",
|
button { class: "calculator-key key-equals",
|
||||||
onclick: move |_| {
|
onclick: move |_| {
|
||||||
display_value.set(format!("{}", calc_val(display_value.get().clone())));
|
set_display_value(format!("{}", calc_val(display_value.clone())));
|
||||||
},
|
},
|
||||||
"="
|
"="
|
||||||
}
|
}
|
||||||
@@ -175,7 +177,7 @@ fn calc_val(val: String) -> f64 {
|
|||||||
|
|
||||||
for c in val[start_index..].chars() {
|
for c in val[start_index..].chars() {
|
||||||
if c == '+' || c == '-' || c == '*' || c == '/' {
|
if c == '+' || c == '-' || c == '*' || c == '/' {
|
||||||
if temp != "" {
|
if !temp.is_empty() {
|
||||||
match &operation as &str {
|
match &operation as &str {
|
||||||
"+" => result += temp.parse::<f64>().unwrap(),
|
"+" => result += temp.parse::<f64>().unwrap(),
|
||||||
"-" => result -= temp.parse::<f64>().unwrap(),
|
"-" => result -= temp.parse::<f64>().unwrap(),
|
||||||
@@ -191,7 +193,7 @@ fn calc_val(val: String) -> f64 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if temp != "" {
|
if !temp.is_empty() {
|
||||||
match &operation as &str {
|
match &operation as &str {
|
||||||
"+" => result += temp.parse::<f64>().unwrap(),
|
"+" => result += temp.parse::<f64>().unwrap(),
|
||||||
"-" => result -= temp.parse::<f64>().unwrap(),
|
"-" => result -= temp.parse::<f64>().unwrap(),
|
||||||
|
|||||||
+16
-16
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
Tiny CRM: A port of the Yew CRM example to Dioxus.
|
Tiny CRM: A port of the Yew CRM example to Dioxus.
|
||||||
*/
|
*/
|
||||||
use dioxus::{events::FormEvent, prelude::*};
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
dioxus::desktop::launch(app);
|
dioxus::desktop::launch(app);
|
||||||
@@ -20,11 +20,11 @@ pub struct Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn app(cx: Scope) -> Element {
|
fn app(cx: Scope) -> Element {
|
||||||
let scene = use_state(&cx, || Scene::ClientsList);
|
|
||||||
let clients = use_ref(&cx, || vec![] as Vec<Client>);
|
let clients = use_ref(&cx, || vec![] as Vec<Client>);
|
||||||
let firstname = use_state(&cx, String::new);
|
let (scene, set_scene) = use_state(&cx, || Scene::ClientsList);
|
||||||
let lastname = use_state(&cx, String::new);
|
let (firstname, set_firstname) = use_state(&cx, String::new);
|
||||||
let description = use_state(&cx, String::new);
|
let (lastname, set_lastname) = use_state(&cx, String::new);
|
||||||
|
let (description, set_description) = use_state(&cx, String::new);
|
||||||
|
|
||||||
cx.render(rsx!(
|
cx.render(rsx!(
|
||||||
body {
|
body {
|
||||||
@@ -38,7 +38,7 @@ fn app(cx: Scope) -> Element {
|
|||||||
|
|
||||||
h1 {"Dioxus CRM Example"}
|
h1 {"Dioxus CRM Example"}
|
||||||
|
|
||||||
match *scene {
|
match scene {
|
||||||
Scene::ClientsList => rsx!(
|
Scene::ClientsList => rsx!(
|
||||||
div { class: "crm",
|
div { class: "crm",
|
||||||
h2 { margin_bottom: "10px", "List of clients" }
|
h2 { margin_bottom: "10px", "List of clients" }
|
||||||
@@ -51,8 +51,8 @@ fn app(cx: Scope) -> Element {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
button { class: "pure-button pure-button-primary", onclick: move |_| scene.set(Scene::NewClientForm), "Add New" }
|
button { class: "pure-button pure-button-primary", onclick: move |_| set_scene(Scene::NewClientForm), "Add New" }
|
||||||
button { class: "pure-button", onclick: move |_| scene.set(Scene::Settings), "Settings" }
|
button { class: "pure-button", onclick: move |_| set_scene(Scene::Settings), "Settings" }
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
Scene::NewClientForm => rsx!(
|
Scene::NewClientForm => rsx!(
|
||||||
@@ -63,19 +63,19 @@ fn app(cx: Scope) -> Element {
|
|||||||
class: "new-client firstname",
|
class: "new-client firstname",
|
||||||
placeholder: "First name",
|
placeholder: "First name",
|
||||||
value: "{firstname}",
|
value: "{firstname}",
|
||||||
oninput: move |e| firstname.set(e.value.clone())
|
oninput: move |e| set_firstname(e.value.clone())
|
||||||
}
|
}
|
||||||
input {
|
input {
|
||||||
class: "new-client lastname",
|
class: "new-client lastname",
|
||||||
placeholder: "Last name",
|
placeholder: "Last name",
|
||||||
value: "{lastname}",
|
value: "{lastname}",
|
||||||
oninput: move |e| lastname.set(e.value.clone())
|
oninput: move |e| set_lastname(e.value.clone())
|
||||||
}
|
}
|
||||||
textarea {
|
textarea {
|
||||||
class: "new-client description",
|
class: "new-client description",
|
||||||
placeholder: "Description",
|
placeholder: "Description",
|
||||||
value: "{description}",
|
value: "{description}",
|
||||||
oninput: move |e| description.set(e.value.clone())
|
oninput: move |e| set_description(e.value.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
button {
|
button {
|
||||||
@@ -86,13 +86,13 @@ fn app(cx: Scope) -> Element {
|
|||||||
first_name: (*firstname).clone(),
|
first_name: (*firstname).clone(),
|
||||||
last_name: (*lastname).clone(),
|
last_name: (*lastname).clone(),
|
||||||
});
|
});
|
||||||
description.set(String::new());
|
set_description(String::new());
|
||||||
firstname.set(String::new());
|
set_firstname(String::new());
|
||||||
lastname.set(String::new());
|
set_lastname(String::new());
|
||||||
},
|
},
|
||||||
"Add New"
|
"Add New"
|
||||||
}
|
}
|
||||||
button { class: "pure-button", onclick: move |_| scene.set(Scene::ClientsList),
|
button { class: "pure-button", onclick: move |_| set_scene(Scene::ClientsList),
|
||||||
"Go Back"
|
"Go Back"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,7 +108,7 @@ fn app(cx: Scope) -> Element {
|
|||||||
}
|
}
|
||||||
button {
|
button {
|
||||||
class: "pure-button pure-button-primary",
|
class: "pure-button pure-button-primary",
|
||||||
onclick: move |_| scene.set(Scene::ClientsList),
|
onclick: move |_| set_scene(Scene::ClientsList),
|
||||||
"Go Back"
|
"Go Back"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
dioxus::desktop::launch(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app(cx: Scope) -> Element {
|
||||||
|
cx.render(rsx! {
|
||||||
|
div {
|
||||||
|
"This should show an image:"
|
||||||
|
img { src: "examples/assets/logo.png", }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -5,12 +5,12 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn app(cx: Scope) -> Element {
|
fn app(cx: Scope) -> Element {
|
||||||
let disabled = use_state(&cx, || false);
|
let (disabled, set_disabled) = use_state(&cx, || false);
|
||||||
|
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
div {
|
div {
|
||||||
button {
|
button {
|
||||||
onclick: move |_| disabled.set(!disabled.get()),
|
onclick: move |_| set_disabled(!disabled),
|
||||||
"click to " [if *disabled {"enable"} else {"disable"} ] " the lower button"
|
"click to " [if *disabled {"enable"} else {"disable"} ] " the lower button"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -18,11 +18,6 @@ fn app(cx: Scope) -> Element {
|
|||||||
disabled: "{disabled}",
|
disabled: "{disabled}",
|
||||||
"lower button"
|
"lower button"
|
||||||
}
|
}
|
||||||
|
|
||||||
input {
|
|
||||||
value: "false",
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
+11
-10
@@ -1,5 +1,6 @@
|
|||||||
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
//! Render a bunch of doggos!
|
//! Render a bunch of doggos!
|
||||||
//!
|
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
@@ -15,7 +16,7 @@ struct ListBreeds {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn app(cx: Scope) -> Element {
|
fn app(cx: Scope) -> Element {
|
||||||
let fut = use_future(&cx, || async move {
|
let breeds = use_future(&cx, || async move {
|
||||||
reqwest::get("https://dog.ceo/api/breeds/list/all")
|
reqwest::get("https://dog.ceo/api/breeds/list/all")
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@@ -23,9 +24,9 @@ fn app(cx: Scope) -> Element {
|
|||||||
.await
|
.await
|
||||||
});
|
});
|
||||||
|
|
||||||
let selected_breed = use_state(&cx, || None);
|
let (breed, set_breed) = use_state(&cx, || None);
|
||||||
|
|
||||||
match fut.value() {
|
match breeds.value() {
|
||||||
Some(Ok(breeds)) => cx.render(rsx! {
|
Some(Ok(breeds)) => cx.render(rsx! {
|
||||||
div {
|
div {
|
||||||
h1 {"Select a dog breed!"}
|
h1 {"Select a dog breed!"}
|
||||||
@@ -35,14 +36,14 @@ fn app(cx: Scope) -> Element {
|
|||||||
breeds.message.keys().map(|breed| rsx!(
|
breeds.message.keys().map(|breed| rsx!(
|
||||||
li {
|
li {
|
||||||
button {
|
button {
|
||||||
onclick: move |_| selected_breed.set(Some(breed.clone())),
|
onclick: move |_| set_breed(Some(breed.clone())),
|
||||||
"{breed}"
|
"{breed}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
div { flex: "50%",
|
div { flex: "50%",
|
||||||
match &*selected_breed {
|
match breed {
|
||||||
Some(breed) => rsx!( Breed { breed: breed.clone() } ),
|
Some(breed) => rsx!( Breed { breed: breed.clone() } ),
|
||||||
None => rsx!("No Breed selected"),
|
None => rsx!("No Breed selected"),
|
||||||
}
|
}
|
||||||
@@ -50,7 +51,7 @@ fn app(cx: Scope) -> Element {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
Some(Err(e)) => cx.render(rsx! {
|
Some(Err(_e)) => cx.render(rsx! {
|
||||||
div { "Error fetching breeds" }
|
div { "Error fetching breeds" }
|
||||||
}),
|
}),
|
||||||
None => cx.render(rsx! {
|
None => cx.render(rsx! {
|
||||||
@@ -72,9 +73,9 @@ fn Breed(cx: Scope, breed: String) -> Element {
|
|||||||
reqwest::get(endpoint).await.unwrap().json::<DogApi>().await
|
reqwest::get(endpoint).await.unwrap().json::<DogApi>().await
|
||||||
});
|
});
|
||||||
|
|
||||||
let breed_name = use_state(&cx, || breed.clone());
|
let (name, set_name) = use_state(&cx, || breed.clone());
|
||||||
if breed_name.get() != breed {
|
if name != breed {
|
||||||
breed_name.set(breed.clone());
|
set_name(breed.clone());
|
||||||
fut.restart();
|
fut.restart();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn app(cx: Scope) -> Element {
|
fn app(cx: Scope) -> Element {
|
||||||
let files = use_ref(&cx, || Files::new());
|
let files = use_ref(&cx, Files::new);
|
||||||
|
|
||||||
rsx!(cx, div {
|
rsx!(cx, div {
|
||||||
link { href:"https://fonts.googleapis.com/icon?family=Material+Icons", rel:"stylesheet", }
|
link { href:"https://fonts.googleapis.com/icon?family=Material+Icons", rel:"stylesheet", }
|
||||||
@@ -28,8 +28,8 @@ fn app(cx: Scope) -> Element {
|
|||||||
}
|
}
|
||||||
main {
|
main {
|
||||||
files.read().path_names.iter().enumerate().map(|(dir_id, path)| {
|
files.read().path_names.iter().enumerate().map(|(dir_id, path)| {
|
||||||
let path_end = path.split('/').last().unwrap_or(path.as_str());
|
let path_end = path.split('/').last().unwrap_or_else(|| path.as_str());
|
||||||
let icon_type = if path_end.contains(".") {
|
let icon_type = if path_end.contains('.') {
|
||||||
"description"
|
"description"
|
||||||
} else {
|
} else {
|
||||||
"folder"
|
"folder"
|
||||||
@@ -56,7 +56,6 @@ fn app(cx: Scope) -> Element {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ use dioxus::prelude::*;
|
|||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
dioxus::desktop::launch_with_props(app, (), |c| {
|
dioxus::desktop::launch_with_props(app, (), |c| {
|
||||||
c.with_file_drop_handler(|w, e| {
|
c.with_file_drop_handler(|_w, e| {
|
||||||
println!("{:?}", e);
|
println!("{:?}", e);
|
||||||
false
|
true
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
|
|
||||||
@@ -15,9 +17,9 @@ impl Label {
|
|||||||
fn new_list(num: usize) -> Vec<Self> {
|
fn new_list(num: usize) -> Vec<Self> {
|
||||||
let mut rng = SmallRng::from_entropy();
|
let mut rng = SmallRng::from_entropy();
|
||||||
let mut labels = Vec::with_capacity(num);
|
let mut labels = Vec::with_capacity(num);
|
||||||
for _ in 0..num {
|
for x in 0..num {
|
||||||
labels.push(Label {
|
labels.push(Label {
|
||||||
key: 0,
|
key: x,
|
||||||
labels: [
|
labels: [
|
||||||
ADJECTIVES.choose(&mut rng).unwrap(),
|
ADJECTIVES.choose(&mut rng).unwrap(),
|
||||||
COLOURS.choose(&mut rng).unwrap(),
|
COLOURS.choose(&mut rng).unwrap(),
|
||||||
@@ -30,8 +32,8 @@ impl Label {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn app(cx: Scope) -> Element {
|
fn app(cx: Scope) -> Element {
|
||||||
let items = use_ref(&cx, || vec![]);
|
let items = use_ref(&cx, Vec::new);
|
||||||
let selected = use_state(&cx, || None);
|
let (selected, set_selected) = use_state(&cx, || None);
|
||||||
|
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
div { class: "container",
|
div { class: "container",
|
||||||
@@ -69,7 +71,7 @@ fn app(cx: Scope) -> Element {
|
|||||||
rsx!(tr { class: "{is_in_danger}",
|
rsx!(tr { class: "{is_in_danger}",
|
||||||
td { class:"col-md-1" }
|
td { class:"col-md-1" }
|
||||||
td { class:"col-md-1", "{item.key}" }
|
td { class:"col-md-1", "{item.key}" }
|
||||||
td { class:"col-md-1", onclick: move |_| selected.set(Some(id)),
|
td { class:"col-md-1", onclick: move |_| set_selected(Some(id)),
|
||||||
a { class: "lbl", item.labels }
|
a { class: "lbl", item.labels }
|
||||||
}
|
}
|
||||||
td { class: "col-md-1",
|
td { class: "col-md-1",
|
||||||
|
|||||||
@@ -20,13 +20,13 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn app(cx: Scope) -> Element {
|
fn app(cx: Scope) -> Element {
|
||||||
let val = use_state(&cx, || 0);
|
let (val, set_val) = use_state(&cx, || 0);
|
||||||
|
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
div {
|
div {
|
||||||
h1 { "hello world. Count: {val}" }
|
h1 { "hello world. Count: {val}" }
|
||||||
button {
|
button {
|
||||||
onclick: move |_| *val.modify() += 1,
|
onclick: move |_| set_val(val + 1),
|
||||||
"click to increment"
|
"click to increment"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
//!
|
//!
|
||||||
//! There is some conversion happening when input types are checkbox/radio/select/textarea etc.
|
//! There is some conversion happening when input types are checkbox/radio/select/textarea etc.
|
||||||
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use dioxus::{events::FormEvent, prelude::*};
|
use dioxus::{events::FormEvent, prelude::*};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
//! Example: Calculator
|
//! Example: Calculator
|
||||||
//! -------------------
|
//! -------------------
|
||||||
//!
|
//!
|
||||||
@@ -30,7 +32,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn app(cx: Scope) -> Element {
|
fn app(cx: Scope) -> Element {
|
||||||
let state = use_ref(&cx, || Calculator::new());
|
let state = use_ref(&cx, Calculator::new);
|
||||||
|
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
style { [include_str!("./assets/calculator.css")] }
|
style { [include_str!("./assets/calculator.css")] }
|
||||||
@@ -171,8 +173,8 @@ impl Calculator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn input_dot(&mut self) {
|
fn input_dot(&mut self) {
|
||||||
if self.display_value.find(".").is_none() {
|
if !self.display_value.contains('.') {
|
||||||
self.display_value.push_str(".");
|
self.display_value.push('.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn perform_operation(&mut self) {
|
fn perform_operation(&mut self) {
|
||||||
@@ -190,8 +192,8 @@ impl Calculator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn toggle_sign(&mut self) {
|
fn toggle_sign(&mut self) {
|
||||||
if self.display_value.starts_with("-") {
|
if self.display_value.starts_with('-') {
|
||||||
self.display_value = self.display_value.trim_start_matches("-").to_string();
|
self.display_value = self.display_value.trim_start_matches('-').to_string();
|
||||||
} else {
|
} else {
|
||||||
self.display_value = format!("-{}", self.display_value);
|
self.display_value = format!("-{}", self.display_value);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,16 +15,16 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn app(cx: Scope) -> Element {
|
fn app(cx: Scope) -> Element {
|
||||||
let state = use_state(&cx, PlayerState::new);
|
let (state, set_state) = use_state(&cx, PlayerState::new);
|
||||||
|
|
||||||
cx.render(rsx!(
|
cx.render(rsx!(
|
||||||
div {
|
div {
|
||||||
h1 {"Select an option"}
|
h1 {"Select an option"}
|
||||||
h3 { "The radio is... " [state.is_playing()] "!" }
|
h3 { "The radio is... " [state.is_playing()] "!" }
|
||||||
button { onclick: move |_| state.modify().reduce(PlayerAction::Pause),
|
button { onclick: move |_| set_state.make_mut().reduce(PlayerAction::Pause),
|
||||||
"Pause"
|
"Pause"
|
||||||
}
|
}
|
||||||
button { onclick: move |_| state.modify().reduce(PlayerAction::Play),
|
button { onclick: move |_| set_state.make_mut().reduce(PlayerAction::Play),
|
||||||
"Play"
|
"Play"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -9,13 +9,13 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn app(cx: Scope) -> Element {
|
fn app(cx: Scope) -> Element {
|
||||||
let mut count = use_state(&cx, || 0);
|
let (count, set_count) = use_state(&cx, || 0);
|
||||||
|
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
div {
|
div {
|
||||||
h1 { "High-Five counter: {count}" }
|
h1 { "High-Five counter: {count}" }
|
||||||
button { onclick: move |_| count += 1, "Up high!" }
|
button { onclick: move |_| set_count(count + 1), "Up high!" }
|
||||||
button { onclick: move |_| count -= 1, "Down low!" }
|
button { onclick: move |_| set_count(count - 1), "Down low!" }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
+10
-2
@@ -19,7 +19,7 @@ fn app(cx: Scope) -> Element {
|
|||||||
Route { to: "/", "Home" }
|
Route { to: "/", "Home" }
|
||||||
Route { to: "users",
|
Route { to: "users",
|
||||||
Route { to: "/", "User list" }
|
Route { to: "/", "User list" }
|
||||||
Route { to: ":name", BlogPost {} }
|
Route { to: ":name", User {} }
|
||||||
}
|
}
|
||||||
Route { to: "blog"
|
Route { to: "blog"
|
||||||
Route { to: "/", "Blog list" }
|
Route { to: "/", "Blog list" }
|
||||||
@@ -48,12 +48,20 @@ struct Query {
|
|||||||
|
|
||||||
fn User(cx: Scope) -> Element {
|
fn User(cx: Scope) -> Element {
|
||||||
let post = dioxus::router::use_route(&cx).last_segment();
|
let post = dioxus::router::use_route(&cx).last_segment();
|
||||||
let query = dioxus::router::use_route(&cx).query::<Query>();
|
let query = dioxus::router::use_route(&cx)
|
||||||
|
.query::<Query>()
|
||||||
|
.unwrap_or(Query { bold: false });
|
||||||
|
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
div {
|
div {
|
||||||
h1 { "Reading blog post: {post}" }
|
h1 { "Reading blog post: {post}" }
|
||||||
p { "example blog post" }
|
p { "example blog post" }
|
||||||
|
|
||||||
|
if query.bold {
|
||||||
|
rsx!{ b { "bold" } }
|
||||||
|
} else {
|
||||||
|
rsx!{ i { "italic" } }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -12,7 +12,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn example(cx: Scope) -> Element {
|
fn example(cx: Scope) -> Element {
|
||||||
let items = use_state(&cx, || {
|
let (items, _set_items) = use_state(&cx, || {
|
||||||
vec![Thing {
|
vec![Thing {
|
||||||
a: "asd".to_string(),
|
a: "asd".to_string(),
|
||||||
b: 10,
|
b: 10,
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
//! Suspense in Dioxus
|
//! Suspense in Dioxus
|
||||||
//!
|
//!
|
||||||
//! Currently, `rsx!` does not accept futures as values. To achieve the functionality
|
//! Currently, `rsx!` does not accept futures as values. To achieve the functionality
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
//! Example: Basic Tailwind usage
|
//! Example: Basic Tailwind usage
|
||||||
//!
|
//!
|
||||||
//! This example shows how an app might be styled with TailwindCSS.
|
//! This example shows how an app might be styled with TailwindCSS.
|
||||||
|
|||||||
+7
-8
@@ -10,16 +10,15 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn app(cx: Scope) -> Element {
|
fn app(cx: Scope) -> Element {
|
||||||
let count = use_state(&cx, || 0);
|
let (count, set_count) = use_state(&cx, || 0);
|
||||||
|
|
||||||
use_future(&cx, move || {
|
use_future(&cx, move || {
|
||||||
let count = UseState::for_async(&count);
|
let set_count = set_count.to_owned();
|
||||||
// for_async![count];
|
|
||||||
async move {
|
async move {
|
||||||
// loop {
|
loop {
|
||||||
// tokio::time::sleep(Duration::from_millis(1000)).await;
|
tokio::time::sleep(Duration::from_millis(1000)).await;
|
||||||
// count += 1;
|
set_count.modify(|f| f + 1);
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -27,7 +26,7 @@ fn app(cx: Scope) -> Element {
|
|||||||
div {
|
div {
|
||||||
h1 { "Current count: {count}" }
|
h1 { "Current count: {count}" }
|
||||||
button {
|
button {
|
||||||
onclick: move |_| count.set(0),
|
onclick: move |_| set_count(0),
|
||||||
"Reset the count"
|
"Reset the count"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+33
-33
@@ -19,15 +19,15 @@ pub struct TodoItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn app(cx: Scope<()>) -> Element {
|
pub fn app(cx: Scope<()>) -> Element {
|
||||||
let todos = use_state(&cx, || im_rc::HashMap::<u32, TodoItem>::default());
|
let (todos, set_todos) = use_state(&cx, im_rc::HashMap::<u32, TodoItem>::default);
|
||||||
let filter = use_state(&cx, || FilterState::All);
|
let (filter, set_filter) = use_state(&cx, || FilterState::All);
|
||||||
let draft = use_state(&cx, || "".to_string());
|
let (draft, set_draft) = use_state(&cx, || "".to_string());
|
||||||
let mut todo_id = use_state(&cx, || 0);
|
let (todo_id, set_todo_id) = use_state(&cx, || 0);
|
||||||
|
|
||||||
// Filter the todos based on the filter state
|
// Filter the todos based on the filter state
|
||||||
let mut filtered_todos = todos
|
let mut filtered_todos = todos
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(_, item)| match *filter {
|
.filter(|(_, item)| match filter {
|
||||||
FilterState::All => true,
|
FilterState::All => true,
|
||||||
FilterState::Active => !item.checked,
|
FilterState::Active => !item.checked,
|
||||||
FilterState::Completed => item.checked,
|
FilterState::Completed => item.checked,
|
||||||
@@ -54,27 +54,25 @@ pub fn app(cx: Scope<()>) -> Element {
|
|||||||
placeholder: "What needs to be done?",
|
placeholder: "What needs to be done?",
|
||||||
value: "{draft}",
|
value: "{draft}",
|
||||||
autofocus: "true",
|
autofocus: "true",
|
||||||
oninput: move |evt| draft.set(evt.value.clone()),
|
oninput: move |evt| set_draft(evt.value.clone()),
|
||||||
onkeydown: move |evt| {
|
onkeydown: move |evt| {
|
||||||
if evt.key == "Enter" {
|
if evt.key == "Enter" && !draft.is_empty() {
|
||||||
if !draft.is_empty() {
|
set_todos.make_mut().insert(
|
||||||
todos.modify().insert(
|
*todo_id,
|
||||||
*todo_id,
|
TodoItem {
|
||||||
TodoItem {
|
id: *todo_id,
|
||||||
id: *todo_id,
|
checked: false,
|
||||||
checked: false,
|
contents: draft.clone(),
|
||||||
contents: draft.get().clone(),
|
},
|
||||||
},
|
);
|
||||||
);
|
set_todo_id(todo_id + 1);
|
||||||
todo_id += 1;
|
set_draft("".to_string());
|
||||||
draft.set("".to_string());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ul { class: "todo-list",
|
ul { class: "todo-list",
|
||||||
filtered_todos.iter().map(|id| rsx!(todo_entry( key: "{id}", id: *id, todos: todos )))
|
filtered_todos.iter().map(|id| rsx!(todo_entry( key: "{id}", id: *id, set_todos: set_todos )))
|
||||||
}
|
}
|
||||||
(!todos.is_empty()).then(|| rsx!(
|
(!todos.is_empty()).then(|| rsx!(
|
||||||
footer { class: "footer",
|
footer { class: "footer",
|
||||||
@@ -83,14 +81,14 @@ pub fn app(cx: Scope<()>) -> Element {
|
|||||||
span {"{item_text} left"}
|
span {"{item_text} left"}
|
||||||
}
|
}
|
||||||
ul { class: "filters",
|
ul { class: "filters",
|
||||||
li { class: "All", a { onclick: move |_| filter.set(FilterState::All), "All" }}
|
li { class: "All", a { onclick: move |_| set_filter(FilterState::All), "All" }}
|
||||||
li { class: "Active", a { onclick: move |_| filter.set(FilterState::Active), "Active" }}
|
li { class: "Active", a { onclick: move |_| set_filter(FilterState::Active), "Active" }}
|
||||||
li { class: "Completed", a { onclick: move |_| filter.set(FilterState::Completed), "Completed" }}
|
li { class: "Completed", a { onclick: move |_| set_filter(FilterState::Completed), "Completed" }}
|
||||||
}
|
}
|
||||||
(show_clear_completed).then(|| rsx!(
|
(show_clear_completed).then(|| rsx!(
|
||||||
button {
|
button {
|
||||||
class: "clear-completed",
|
class: "clear-completed",
|
||||||
onclick: move |_| todos.modify().retain(|_, todo| todo.checked == false),
|
onclick: move |_| set_todos.make_mut().retain(|_, todo| !todo.checked),
|
||||||
"Clear completed"
|
"Clear completed"
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
@@ -108,24 +106,26 @@ pub fn app(cx: Scope<()>) -> Element {
|
|||||||
|
|
||||||
#[derive(Props)]
|
#[derive(Props)]
|
||||||
pub struct TodoEntryProps<'a> {
|
pub struct TodoEntryProps<'a> {
|
||||||
todos: UseState<'a, im_rc::HashMap<u32, TodoItem>>,
|
set_todos: &'a UseState<im_rc::HashMap<u32, TodoItem>>,
|
||||||
id: u32,
|
id: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn todo_entry<'a>(cx: Scope<'a, TodoEntryProps<'a>>) -> Element {
|
pub fn todo_entry<'a>(cx: Scope<'a, TodoEntryProps<'a>>) -> Element {
|
||||||
let todo = &cx.props.todos[&cx.props.id];
|
let (is_editing, set_is_editing) = use_state(&cx, || false);
|
||||||
let is_editing = use_state(&cx, || false);
|
|
||||||
|
let todos = cx.props.set_todos.get();
|
||||||
|
let todo = &todos[&cx.props.id];
|
||||||
let completed = if todo.checked { "completed" } else { "" };
|
let completed = if todo.checked { "completed" } else { "" };
|
||||||
let editing = if *is_editing.get() { "editing" } else { "" };
|
let editing = if *is_editing { "editing" } else { "" };
|
||||||
|
|
||||||
rsx!(cx, li {
|
rsx!(cx, li {
|
||||||
class: "{completed} {editing}",
|
class: "{completed} {editing}",
|
||||||
onclick: move |_| is_editing.set(true),
|
onclick: move |_| set_is_editing(true),
|
||||||
onfocusout: move |_| is_editing.set(false),
|
onfocusout: move |_| set_is_editing(false),
|
||||||
div { class: "view",
|
div { class: "view",
|
||||||
input { class: "toggle", r#type: "checkbox", id: "cbg-{todo.id}", checked: "{todo.checked}",
|
input { class: "toggle", r#type: "checkbox", id: "cbg-{todo.id}", checked: "{todo.checked}",
|
||||||
onchange: move |evt| {
|
onchange: move |evt| {
|
||||||
cx.props.todos.modify()[&cx.props.id].checked = evt.value.parse().unwrap();
|
cx.props.set_todos.make_mut()[&cx.props.id].checked = evt.value.parse().unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
label { r#for: "cbg-{todo.id}", pointer_events: "none", "{todo.contents}" }
|
label { r#for: "cbg-{todo.id}", pointer_events: "none", "{todo.contents}" }
|
||||||
@@ -134,11 +134,11 @@ pub fn todo_entry<'a>(cx: Scope<'a, TodoEntryProps<'a>>) -> Element {
|
|||||||
input {
|
input {
|
||||||
class: "edit",
|
class: "edit",
|
||||||
value: "{todo.contents}",
|
value: "{todo.contents}",
|
||||||
oninput: move |evt| cx.props.todos.modify()[&cx.props.id].contents = evt.value.clone(),
|
oninput: move |evt| cx.props.set_todos.make_mut()[&cx.props.id].contents = evt.value.clone(),
|
||||||
autofocus: "true",
|
autofocus: "true",
|
||||||
onkeydown: move |evt| {
|
onkeydown: move |evt| {
|
||||||
match evt.key.as_str() {
|
match evt.key.as_str() {
|
||||||
"Enter" | "Escape" | "Tab" => is_editing.set(false),
|
"Enter" | "Escape" | "Tab" => set_is_editing(false),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn app(cx: Scope) -> Element {
|
fn app(cx: Scope) -> Element {
|
||||||
let contents = use_state(&cx, || {
|
let (contents, set_contents) = use_state(&cx, || {
|
||||||
String::from("<script>alert(\"hello world\")</script>")
|
String::from("<script>alert(\"hello world\")</script>")
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ fn app(cx: Scope) -> Element {
|
|||||||
input {
|
input {
|
||||||
value: "{contents}",
|
value: "{contents}",
|
||||||
r#type: "text",
|
r#type: "text",
|
||||||
oninput: move |e| contents.set(e.value.clone()),
|
oninput: move |e| set_contents(e.value.clone()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
</a>
|
</a>
|
||||||
<!-- Discord -->
|
<!-- Discord -->
|
||||||
<a href="https://discord.gg/XgGxMSkvUM">
|
<a href="https://discord.gg/XgGxMSkvUM">
|
||||||
<img src="https://badgen.net/discord/members/XgGxMSkvUM" alt="Awesome Page" />
|
<img src="https://img.shields.io/discord/899851952891002890.svg?logo=discord&style=flat-square" alt="Discord Link" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -113,7 +113,7 @@ cargo run --example EXAMPLE
|
|||||||
|
|
||||||
## Dioxus 项目
|
## Dioxus 项目
|
||||||
|
|
||||||
| 文件浏览器 (桌面应用) | WiFi 扫描器 (桌面应用) | Todo管理 (所有平台) | 商城系统 (SSR/liveview) |
|
| 文件浏览器 (桌面应用) | WiFi 扫描器 (桌面应用) | Todo管理 (所有平台) | 商城系统 (SSR/liveview) |
|
||||||
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| [](https://github.com/DioxusLabs/example-projects/blob/master/file-explorer) | [](https://github.com/DioxusLabs/example-projects/blob/master/wifi-scanner) | [](https://github.com/DioxusLabs/example-projects/blob/master/todomvc) | [](https://github.com/DioxusLabs/example-projects/blob/master/ecommerce-site) |
|
| [](https://github.com/DioxusLabs/example-projects/blob/master/file-explorer) | [](https://github.com/DioxusLabs/example-projects/blob/master/wifi-scanner) | [](https://github.com/DioxusLabs/example-projects/blob/master/todomvc) | [](https://github.com/DioxusLabs/example-projects/blob/master/ecommerce-site) |
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,43 @@ 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/),
|
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).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
### New Features
|
||||||
|
|
||||||
|
- <csr-id-d2372717bd01fcff50af0572360e3f763d4c869d/> flatten props attrs
|
||||||
|
- <csr-id-47bc4e4a44a7d08d4f42102d13f0766d9d6bf358/> add "optional" flag for props
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- <csr-id-90abd9c9a08c165384fae9c1f7c3fd098d512c48/> detection of f-string formatting in components
|
||||||
|
|
||||||
|
### Commit Statistics
|
||||||
|
|
||||||
|
<csr-read-only-do-not-edit/>
|
||||||
|
|
||||||
|
- 9 commits contributed to the release over the course of 12 calendar days.
|
||||||
|
- 3 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**
|
||||||
|
- Merge pull request #95 from DioxusLabs/jk/filedragindrop ([`ca0dd4a`](https://github.comgit//DioxusLabs/dioxus/commit/ca0dd4aa7192d483a195d420363f39d771f3e471))
|
||||||
|
- Fix various typos and grammar nits ([`9e4ec43`](https://github.comgit//DioxusLabs/dioxus/commit/9e4ec43b1e78d355c56a38e4c092170b2b01b20d))
|
||||||
|
- flatten props attrs ([`d237271`](https://github.comgit//DioxusLabs/dioxus/commit/d2372717bd01fcff50af0572360e3f763d4c869d))
|
||||||
|
- Merge pull request #108 from DioxusLabs/jk/fstring-component-fields ([`f4132d1`](https://github.comgit//DioxusLabs/dioxus/commit/f4132d1874f7495049fac23ba0a022ac137ad74f))
|
||||||
|
- detection of f-string formatting in components ([`90abd9c`](https://github.comgit//DioxusLabs/dioxus/commit/90abd9c9a08c165384fae9c1f7c3fd098d512c48))
|
||||||
|
- Enable clippy ([`b6903bf`](https://github.comgit//DioxusLabs/dioxus/commit/b6903bf558bc7a3d0fe6794a137c44fca0957d11))
|
||||||
|
- Merge pull request #107 from autarch/autarch/half-assed-router ([`8d3ac3f`](https://github.comgit//DioxusLabs/dioxus/commit/8d3ac3ff148aef9d10a393eda453a11c1e882f58))
|
||||||
|
- Merge pull request #127 from DioxusLabs/jk/handler-tweak ([`5bce294`](https://github.comgit//DioxusLabs/dioxus/commit/5bce294a86941090660b1cfd87809a2b0b76d2ce))
|
||||||
|
- add "optional" flag for props ([`47bc4e4`](https://github.comgit//DioxusLabs/dioxus/commit/47bc4e4a44a7d08d4f42102d13f0766d9d6bf358))
|
||||||
|
</details>
|
||||||
|
|
||||||
## v0.1.6 (2022-01-08)
|
## v0.1.6 (2022-01-08)
|
||||||
|
|
||||||
### Documentation
|
### Documentation
|
||||||
@@ -64,7 +101,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
<csr-read-only-do-not-edit/>
|
<csr-read-only-do-not-edit/>
|
||||||
|
|
||||||
- 138 commits contributed to the release over the course of 352 calendar days.
|
- 139 commits contributed to the release over the course of 352 calendar days.
|
||||||
- 123 commits where understood as [conventional](https://www.conventionalcommits.org).
|
- 123 commits where understood as [conventional](https://www.conventionalcommits.org).
|
||||||
- 0 issues like '(#ID)' where seen in commit messages
|
- 0 issues like '(#ID)' where seen in commit messages
|
||||||
|
|
||||||
@@ -75,6 +112,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
<details><summary>view details</summary>
|
<details><summary>view details</summary>
|
||||||
|
|
||||||
* **Uncategorized**
|
* **Uncategorized**
|
||||||
|
- Release dioxus-core-macro v0.1.6 ([`db0a5bd`](https://github.comgit//DioxusLabs/dioxus/commit/db0a5bd6ec93803cddb3c6fda4172257b9c301c6))
|
||||||
- Release dioxus-core v0.1.7, dioxus-core-macro v0.1.6, dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`40d1f85`](https://github.comgit//DioxusLabs/dioxus/commit/40d1f85d0c3e2c9fd23c08840cca9f459d4e4307))
|
- Release dioxus-core v0.1.7, dioxus-core-macro v0.1.6, dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`40d1f85`](https://github.comgit//DioxusLabs/dioxus/commit/40d1f85d0c3e2c9fd23c08840cca9f459d4e4307))
|
||||||
- component pass thru events ([`c439b0a`](https://github.comgit//DioxusLabs/dioxus/commit/c439b0ac7e09f70a04262b7c29938d8c52197b76))
|
- component pass thru events ([`c439b0a`](https://github.comgit//DioxusLabs/dioxus/commit/c439b0ac7e09f70a04262b7c29938d8c52197b76))
|
||||||
- Merge pull request #74 from mrxiaozhuox/master ([`47056fd`](https://github.comgit//DioxusLabs/dioxus/commit/47056fda4577bcbdaa2a6f63d82eec876e5a5aee))
|
- Merge pull request #74 from mrxiaozhuox/master ([`47056fd`](https://github.comgit//DioxusLabs/dioxus/commit/47056fda4577bcbdaa2a6f63d82eec876e5a5aee))
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "dioxus-core-macro"
|
name = "dioxus-core-macro"
|
||||||
version = "0.1.6"
|
version = "0.1.7"
|
||||||
authors = ["Jonathan Kelley"]
|
authors = ["Jonathan Kelley"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "Core macro for Dioxus Virtual DOM"
|
description = "Core macro for Dioxus Virtual DOM"
|
||||||
|
|||||||
@@ -135,11 +135,14 @@ impl ToTokens for Component {
|
|||||||
None => quote! { None },
|
None => quote! { None },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let fn_name = self.name.segments.last().unwrap().ident.to_string();
|
||||||
|
|
||||||
tokens.append_all(quote! {
|
tokens.append_all(quote! {
|
||||||
__cx.component(
|
__cx.component(
|
||||||
#name,
|
#name,
|
||||||
#builder,
|
#builder,
|
||||||
#key_token,
|
#key_token,
|
||||||
|
#fn_name
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,58 @@ 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/),
|
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).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- <csr-id-036a0ff49a7dade0e04c9c07071a1ff49133ee24/> add comments for the Handler
|
||||||
|
|
||||||
|
### New Features
|
||||||
|
|
||||||
|
- <csr-id-bad4b773b7bbab6a38d4f48d88d8c5a8787927ac/> add "spawn" method
|
||||||
|
- <csr-id-d2bd1751436ef4bec554bb361ba87aea1357036a/> allow providing context to the root component
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- <csr-id-d9a07ddddb45ba00e35645ac0e0ad1a4d2dd996d/> provide_root_context on root scopes
|
||||||
|
- <csr-id-c8d528b3b18e320ca0cee1542ae5fc659c1cca81/> proprogation of root context
|
||||||
|
- <csr-id-e47ead5347ce778935f8f2127fcb14f520eda0ce/> allow eventhandler to derive default
|
||||||
|
|
||||||
|
### Commit Statistics
|
||||||
|
|
||||||
|
<csr-read-only-do-not-edit/>
|
||||||
|
|
||||||
|
- 18 commits contributed to the release over the course of 13 calendar days.
|
||||||
|
- 8 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**
|
||||||
|
- Merge pull request #169 from DioxusLabs/jk/router-userouter ([`3509602`](https://github.comgit//DioxusLabs/dioxus/commit/3509602c0bcd327a33bc8c95896775e24751da1a))
|
||||||
|
- Merge branch 'master' of github.com:DioxusLabs/dioxus ([`8899701`](https://github.comgit//DioxusLabs/dioxus/commit/88997019c5c74658bfd76344d6bb6f3565943b41))
|
||||||
|
- add miri stress test ([`e9792e9`](https://github.comgit//DioxusLabs/dioxus/commit/e9792e9b95521fcf0b0b4e240d7cf0397cd79b8f))
|
||||||
|
- add "spawn" method ([`bad4b77`](https://github.comgit//DioxusLabs/dioxus/commit/bad4b773b7bbab6a38d4f48d88d8c5a8787927ac))
|
||||||
|
- drop hooks before resetting bump arena ([`2e4f765`](https://github.comgit//DioxusLabs/dioxus/commit/2e4f7659327b69b74e20af59b6d5e65c699b6adb))
|
||||||
|
- provide_root_context on root scopes ([`d9a07dd`](https://github.comgit//DioxusLabs/dioxus/commit/d9a07ddddb45ba00e35645ac0e0ad1a4d2dd996d))
|
||||||
|
- Fix various typos and grammar nits ([`9e4ec43`](https://github.comgit//DioxusLabs/dioxus/commit/9e4ec43b1e78d355c56a38e4c092170b2b01b20d))
|
||||||
|
- Merge pull request #121 from DioxusLabs/jk/unify ([`b287a4c`](https://github.comgit//DioxusLabs/dioxus/commit/b287a4cab3eec9e6961a3f010846291f4f105747))
|
||||||
|
- Merge pull request #108 from DioxusLabs/jk/fstring-component-fields ([`f4132d1`](https://github.comgit//DioxusLabs/dioxus/commit/f4132d1874f7495049fac23ba0a022ac137ad74f))
|
||||||
|
- proprogation of root context ([`c8d528b`](https://github.comgit//DioxusLabs/dioxus/commit/c8d528b3b18e320ca0cee1542ae5fc659c1cca81))
|
||||||
|
- allow providing context to the root component ([`d2bd175`](https://github.comgit//DioxusLabs/dioxus/commit/d2bd1751436ef4bec554bb361ba87aea1357036a))
|
||||||
|
- Enable clippy ([`b6903bf`](https://github.comgit//DioxusLabs/dioxus/commit/b6903bf558bc7a3d0fe6794a137c44fca0957d11))
|
||||||
|
- Merge pull request #107 from autarch/autarch/half-assed-router ([`8d3ac3f`](https://github.comgit//DioxusLabs/dioxus/commit/8d3ac3ff148aef9d10a393eda453a11c1e882f58))
|
||||||
|
- Merge pull request #138 from mrxiaozhuox/master ([`8c7473d`](https://github.comgit//DioxusLabs/dioxus/commit/8c7473d1943dd133f388ec36116c9d8295861b97))
|
||||||
|
- Merge pull request #133 from mrxiaozhuox/master ([`887f69d`](https://github.comgit//DioxusLabs/dioxus/commit/887f69d5b47bdcde4fe0eab094c0cd0de23e4f3f))
|
||||||
|
- Don't expect all components to have a scope in ScopeArena.ensure_drop_safety ([`9b282d8`](https://github.comgit//DioxusLabs/dioxus/commit/9b282d877ba0fe2463687cde7ea899b7f8510425))
|
||||||
|
- add comments for the Handler ([`036a0ff`](https://github.comgit//DioxusLabs/dioxus/commit/036a0ff49a7dade0e04c9c07071a1ff49133ee24))
|
||||||
|
- allow eventhandler to derive default ([`e47ead5`](https://github.comgit//DioxusLabs/dioxus/commit/e47ead5347ce778935f8f2127fcb14f520eda0ce))
|
||||||
|
</details>
|
||||||
|
|
||||||
## v0.1.7 (2022-01-08)
|
## v0.1.7 (2022-01-08)
|
||||||
|
|
||||||
### Documentation
|
### Documentation
|
||||||
@@ -138,7 +190,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
<csr-read-only-do-not-edit/>
|
<csr-read-only-do-not-edit/>
|
||||||
|
|
||||||
- 437 commits contributed to the release over the course of 358 calendar days.
|
- 438 commits contributed to the release over the course of 358 calendar days.
|
||||||
- 416 commits where understood as [conventional](https://www.conventionalcommits.org).
|
- 416 commits where understood as [conventional](https://www.conventionalcommits.org).
|
||||||
- 0 issues like '(#ID)' where seen in commit messages
|
- 0 issues like '(#ID)' where seen in commit messages
|
||||||
|
|
||||||
@@ -149,8 +201,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
<details><summary>view details</summary>
|
<details><summary>view details</summary>
|
||||||
|
|
||||||
* **Uncategorized**
|
* **Uncategorized**
|
||||||
- Release dioxus-core v0.1.7, dioxus-core-macro v0.1.6, dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`40d1f85`](https://github.comgit//DioxusLabs/dioxus/commit/40d1f85d0c3e2c9fd23c08840cca9f459d4e4307))
|
- Release dioxus-core v0.1.7 ([`16d73b2`](https://github.comgit//DioxusLabs/dioxus/commit/16d73b240fc446c6f6996c19ccf52bbcda2eca79))
|
||||||
- add title to doc comment ([`d11f322`](https://github.comgit//DioxusLabs/dioxus/commit/d11f322f554e7dbf43b988c9cfda56498cc49872))
|
- add title to doc comment ([`d11f322`](https://github.comgit//DioxusLabs/dioxus/commit/d11f322f554e7dbf43b988c9cfda56498cc49872))
|
||||||
|
- Release dioxus-core v0.1.7, dioxus-core-macro v0.1.6, dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`40d1f85`](https://github.comgit//DioxusLabs/dioxus/commit/40d1f85d0c3e2c9fd23c08840cca9f459d4e4307))
|
||||||
- better document the `EventHandler` type ([`be9f1a5`](https://github.comgit//DioxusLabs/dioxus/commit/be9f1a52ad2b04f101397ae34482ea7394df653b))
|
- better document the `EventHandler` type ([`be9f1a5`](https://github.comgit//DioxusLabs/dioxus/commit/be9f1a52ad2b04f101397ae34482ea7394df653b))
|
||||||
- component pass thru events ([`c439b0a`](https://github.comgit//DioxusLabs/dioxus/commit/c439b0ac7e09f70a04262b7c29938d8c52197b76))
|
- component pass thru events ([`c439b0a`](https://github.comgit//DioxusLabs/dioxus/commit/c439b0ac7e09f70a04262b7c29938d8c52197b76))
|
||||||
- Merge pull request #74 from mrxiaozhuox/master ([`47056fd`](https://github.comgit//DioxusLabs/dioxus/commit/47056fda4577bcbdaa2a6f63d82eec876e5a5aee))
|
- Merge pull request #74 from mrxiaozhuox/master ([`47056fd`](https://github.comgit//DioxusLabs/dioxus/commit/47056fda4577bcbdaa2a6f63d82eec876e5a5aee))
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "dioxus-core"
|
name = "dioxus-core"
|
||||||
version = "0.1.7"
|
version = "0.1.9"
|
||||||
authors = ["Jonathan Kelley"]
|
authors = ["Jonathan Kelley"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
description = "Core functionality for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences"
|
description = "Core functionality for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences"
|
||||||
|
|||||||
@@ -94,5 +94,5 @@ Dioxus deals with arenas, lifetimes, asynchronous tasks, custom allocators, pinn
|
|||||||
|
|
||||||
If you don't want to use a crate that uses unsafe, then this crate is not for you.
|
If you don't want to use a crate that uses unsafe, then this crate is not for you.
|
||||||
|
|
||||||
However, we are always interested in decreasing the scope of the core VirtualDom to make it easier to review. We'd be happy to welcome PRs that can eliminate unsafe code while still upholding the numerous variants required to execute certain features.
|
However, we are always interested in decreasing the scope of the core VirtualDom to make it easier to review. We'd be happy to welcome PRs that can eliminate unsafe code while still upholding the numerous invariants required to execute certain features.
|
||||||
|
|
||||||
+461
-665
File diff suppressed because it is too large.
Load diff
@@ -18,7 +18,7 @@ impl BubbleState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// User Events are events that are shuttled from the renderer into the VirtualDom trhough the scheduler channel.
|
/// User Events are events that are shuttled from the renderer into the VirtualDom through the scheduler channel.
|
||||||
///
|
///
|
||||||
/// These events will be passed to the appropriate Element given by `mounted_dom_id` and then bubbled up through the tree
|
/// These events will be passed to the appropriate Element given by `mounted_dom_id` and then bubbled up through the tree
|
||||||
/// where each listener is checked and fired if the event name matches.
|
/// where each listener is checked and fired if the event name matches.
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ pub(crate) mod util;
|
|||||||
pub(crate) mod virtual_dom;
|
pub(crate) mod virtual_dom;
|
||||||
|
|
||||||
pub(crate) mod innerlude {
|
pub(crate) mod innerlude {
|
||||||
pub(crate) use crate::diff::*;
|
|
||||||
pub use crate::events::*;
|
pub use crate::events::*;
|
||||||
pub use crate::lazynodes::*;
|
pub use crate::lazynodes::*;
|
||||||
pub use crate::mutations::*;
|
pub use crate::mutations::*;
|
||||||
|
|||||||
@@ -143,6 +143,10 @@ impl<'a> Mutations<'a> {
|
|||||||
self.edits.push(InsertBefore { n, root });
|
self.edits.push(InsertBefore { n, root });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn append_children(&mut self, n: u32) {
|
||||||
|
self.edits.push(AppendChildren { many: n });
|
||||||
|
}
|
||||||
|
|
||||||
// Remove Nodes from the dom
|
// Remove Nodes from the dom
|
||||||
pub(crate) fn remove(&mut self, id: u64) {
|
pub(crate) fn remove(&mut self, id: u64) {
|
||||||
self.edits.push(Remove { root: id });
|
self.edits.push(Remove { root: id });
|
||||||
@@ -217,6 +221,10 @@ impl<'a> Mutations<'a> {
|
|||||||
let name = attribute.name;
|
let name = attribute.name;
|
||||||
self.edits.push(RemoveAttribute { name, root });
|
self.edits.push(RemoveAttribute { name, root });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn mark_dirty_scope(&mut self, scope: ScopeId) {
|
||||||
|
self.dirty_scopes.insert(scope);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// refs are only assigned once
|
// refs are only assigned once
|
||||||
|
|||||||
+26
-15
@@ -4,7 +4,7 @@
|
|||||||
//! cheap and *very* fast to construct - building a full tree should be quick.
|
//! cheap and *very* fast to construct - building a full tree should be quick.
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
innerlude::{Element, Properties, Scope, ScopeId, ScopeState},
|
innerlude::{ComponentPtr, Element, Properties, Scope, ScopeId, ScopeState},
|
||||||
lazynodes::LazyNodes,
|
lazynodes::LazyNodes,
|
||||||
AnyEvent, Component,
|
AnyEvent, Component,
|
||||||
};
|
};
|
||||||
@@ -21,7 +21,7 @@ use std::{
|
|||||||
/// - the `rsx!` macro
|
/// - the `rsx!` macro
|
||||||
/// - the [`NodeFactory`] API
|
/// - the [`NodeFactory`] API
|
||||||
pub enum VNode<'src> {
|
pub enum VNode<'src> {
|
||||||
/// Text VNodes simply bump-allocated (or static) string slices
|
/// Text VNodes are simply bump-allocated (or static) string slices
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
@@ -147,13 +147,6 @@ impl<'src> VNode<'src> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn children(&self) -> &[VNode<'src>] {
|
|
||||||
match &self {
|
|
||||||
VNode::Fragment(f) => f.children,
|
|
||||||
_ => &[],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create an "owned" version of the vnode.
|
// Create an "owned" version of the vnode.
|
||||||
pub fn decouple(&self) -> VNode<'src> {
|
pub fn decouple(&self) -> VNode<'src> {
|
||||||
match *self {
|
match *self {
|
||||||
@@ -175,13 +168,21 @@ impl Debug for VNode<'_> {
|
|||||||
.field("key", &el.key)
|
.field("key", &el.key)
|
||||||
.field("attrs", &el.attributes)
|
.field("attrs", &el.attributes)
|
||||||
.field("children", &el.children)
|
.field("children", &el.children)
|
||||||
|
.field("id", &el.id)
|
||||||
.finish(),
|
.finish(),
|
||||||
VNode::Text(t) => write!(s, "VNode::VText {{ text: {} }}", t.text),
|
VNode::Text(t) => write!(s, "VNode::VText {{ text: {} }}", t.text),
|
||||||
VNode::Placeholder(_) => write!(s, "VNode::VPlaceholder"),
|
VNode::Placeholder(t) => write!(s, "VNode::VPlaceholder {{ id: {:?} }}", t.id),
|
||||||
VNode::Fragment(frag) => {
|
VNode::Fragment(frag) => {
|
||||||
write!(s, "VNode::VFragment {{ children: {:?} }}", frag.children)
|
write!(s, "VNode::VFragment {{ children: {:?} }}", frag.children)
|
||||||
}
|
}
|
||||||
VNode::Component(comp) => write!(s, "VNode::VComponent {{ fc: {:?}}}", comp.user_fc),
|
VNode::Component(comp) => s
|
||||||
|
.debug_struct("VNode::VComponent")
|
||||||
|
.field("name", &comp.fn_name)
|
||||||
|
.field("fnptr", &comp.user_fc)
|
||||||
|
.field("key", &comp.key)
|
||||||
|
.field("scope", &comp.scope)
|
||||||
|
.field("originator", &comp.originator)
|
||||||
|
.finish(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -351,11 +352,18 @@ type ExternalListenerCallback<'bump, T> = BumpBox<'bump, dyn FnMut(T) + 'bump>;
|
|||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Default)]
|
|
||||||
pub struct EventHandler<'bump, T = ()> {
|
pub struct EventHandler<'bump, T = ()> {
|
||||||
pub callback: RefCell<Option<ExternalListenerCallback<'bump, T>>>,
|
pub callback: RefCell<Option<ExternalListenerCallback<'bump, T>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a, T> Default for EventHandler<'a, T> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
callback: RefCell::new(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T> EventHandler<'_, T> {
|
impl<T> EventHandler<'_, T> {
|
||||||
/// Call this event handler with the appropriate event type
|
/// Call this event handler with the appropriate event type
|
||||||
pub fn call(&self, event: T) {
|
pub fn call(&self, event: T) {
|
||||||
@@ -377,7 +385,8 @@ pub struct VComponent<'src> {
|
|||||||
pub originator: ScopeId,
|
pub originator: ScopeId,
|
||||||
pub scope: Cell<Option<ScopeId>>,
|
pub scope: Cell<Option<ScopeId>>,
|
||||||
pub can_memoize: bool,
|
pub can_memoize: bool,
|
||||||
pub user_fc: *const (),
|
pub user_fc: ComponentPtr,
|
||||||
|
pub fn_name: &'static str,
|
||||||
pub props: RefCell<Option<Box<dyn AnyProps + 'src>>>,
|
pub props: RefCell<Option<Box<dyn AnyProps + 'src>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -399,7 +408,7 @@ impl<P> AnyProps for VComponentProps<P> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Safety:
|
// Safety:
|
||||||
// this will downcat the other ptr as our swallowed type!
|
// this will downcast the other ptr as our swallowed type!
|
||||||
// you *must* make this check *before* calling this method
|
// you *must* make this check *before* calling this method
|
||||||
// if your functions are not the same, then you will downcast a pointer into a different type (UB)
|
// if your functions are not the same, then you will downcast a pointer into a different type (UB)
|
||||||
unsafe fn memoize(&self, other: &dyn AnyProps) -> bool {
|
unsafe fn memoize(&self, other: &dyn AnyProps) -> bool {
|
||||||
@@ -542,6 +551,7 @@ impl<'a> NodeFactory<'a> {
|
|||||||
component: fn(Scope<'a, P>) -> Element,
|
component: fn(Scope<'a, P>) -> Element,
|
||||||
props: P,
|
props: P,
|
||||||
key: Option<Arguments>,
|
key: Option<Arguments>,
|
||||||
|
fn_name: &'static str,
|
||||||
) -> VNode<'a>
|
) -> VNode<'a>
|
||||||
where
|
where
|
||||||
P: Properties + 'a,
|
P: Properties + 'a,
|
||||||
@@ -550,8 +560,9 @@ impl<'a> NodeFactory<'a> {
|
|||||||
key: key.map(|f| self.raw_text(f).0),
|
key: key.map(|f| self.raw_text(f).0),
|
||||||
scope: Default::default(),
|
scope: Default::default(),
|
||||||
can_memoize: P::IS_STATIC,
|
can_memoize: P::IS_STATIC,
|
||||||
user_fc: component as *const (),
|
user_fc: component as ComponentPtr,
|
||||||
originator: self.scope.scope_id(),
|
originator: self.scope.scope_id(),
|
||||||
|
fn_name,
|
||||||
props: RefCell::new(Some(Box::new(VComponentProps {
|
props: RefCell::new(Some(Box::new(VComponentProps {
|
||||||
// local_props: RefCell::new(Some(props)),
|
// local_props: RefCell::new(Some(props)),
|
||||||
// heap_props: RefCell::new(None),
|
// heap_props: RefCell::new(None),
|
||||||
|
|||||||
+69
-70
@@ -13,7 +13,9 @@ use std::{
|
|||||||
rc::Rc,
|
rc::Rc,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(crate) type FcSlot = *const ();
|
/// for traceability, we use the raw fn pointer to identify the function
|
||||||
|
/// we also get the component name, but that's not necessarily unique in the app
|
||||||
|
pub(crate) type ComponentPtr = *mut std::os::raw::c_void;
|
||||||
|
|
||||||
pub(crate) struct Heuristic {
|
pub(crate) struct Heuristic {
|
||||||
hook_arena_size: usize,
|
hook_arena_size: usize,
|
||||||
@@ -28,7 +30,7 @@ pub(crate) struct ScopeArena {
|
|||||||
pub scope_gen: Cell<usize>,
|
pub scope_gen: Cell<usize>,
|
||||||
pub bump: Bump,
|
pub bump: Bump,
|
||||||
pub scopes: RefCell<FxHashMap<ScopeId, *mut ScopeState>>,
|
pub scopes: RefCell<FxHashMap<ScopeId, *mut ScopeState>>,
|
||||||
pub heuristics: RefCell<FxHashMap<FcSlot, Heuristic>>,
|
pub heuristics: RefCell<FxHashMap<ComponentPtr, Heuristic>>,
|
||||||
pub free_scopes: RefCell<Vec<*mut ScopeState>>,
|
pub free_scopes: RefCell<Vec<*mut ScopeState>>,
|
||||||
pub nodes: RefCell<Slab<*const VNode<'static>>>,
|
pub nodes: RefCell<Slab<*const VNode<'static>>>,
|
||||||
pub tasks: Rc<TaskQueue>,
|
pub tasks: Rc<TaskQueue>,
|
||||||
@@ -70,7 +72,7 @@ impl ScopeArena {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Safety:
|
/// Safety:
|
||||||
/// - Obtaining a mutable refernece to any Scope is unsafe
|
/// - Obtaining a mutable reference to any Scope is unsafe
|
||||||
/// - Scopes use interior mutability when sharing data into components
|
/// - Scopes use interior mutability when sharing data into components
|
||||||
pub(crate) fn get_scope(&self, id: ScopeId) -> Option<&ScopeState> {
|
pub(crate) fn get_scope(&self, id: ScopeId) -> Option<&ScopeState> {
|
||||||
unsafe { self.scopes.borrow().get(&id).map(|f| &**f) }
|
unsafe { self.scopes.borrow().get(&id).map(|f| &**f) }
|
||||||
@@ -82,7 +84,7 @@ impl ScopeArena {
|
|||||||
|
|
||||||
pub(crate) fn new_with_key(
|
pub(crate) fn new_with_key(
|
||||||
&self,
|
&self,
|
||||||
fc_ptr: *const (),
|
fc_ptr: ComponentPtr,
|
||||||
vcomp: Box<dyn AnyProps>,
|
vcomp: Box<dyn AnyProps>,
|
||||||
parent_scope: Option<ScopeId>,
|
parent_scope: Option<ScopeId>,
|
||||||
container: ElementId,
|
container: ElementId,
|
||||||
@@ -94,14 +96,14 @@ impl ScopeArena {
|
|||||||
|
|
||||||
// Get the height of the scope
|
// Get the height of the scope
|
||||||
let height = parent_scope
|
let height = parent_scope
|
||||||
.map(|id| self.get_scope(id).map(|scope| scope.height))
|
.map(|id| self.get_scope(id).map(|scope| scope.height + 1))
|
||||||
.flatten()
|
.flatten()
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
let parent_scope = parent_scope.map(|f| self.get_scope_raw(f)).flatten();
|
let parent_scope = parent_scope.map(|f| self.get_scope_raw(f)).flatten();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
This scopearena aggressively reuse old scopes when possible.
|
This scopearena aggressively reuses old scopes when possible.
|
||||||
We try to minimize the new allocations for props/arenas.
|
We try to minimize the new allocations for props/arenas.
|
||||||
|
|
||||||
However, this will probably lead to some sort of fragmentation.
|
However, this will probably lead to some sort of fragmentation.
|
||||||
@@ -110,31 +112,62 @@ impl ScopeArena {
|
|||||||
if let Some(old_scope) = self.free_scopes.borrow_mut().pop() {
|
if let Some(old_scope) = self.free_scopes.borrow_mut().pop() {
|
||||||
// reuse the old scope
|
// reuse the old scope
|
||||||
let scope = unsafe { &mut *old_scope };
|
let scope = unsafe { &mut *old_scope };
|
||||||
scope.props.get_mut().replace(vcomp);
|
|
||||||
|
scope.container = container;
|
||||||
|
scope.our_arena_idx = new_scope_id;
|
||||||
scope.parent_scope = parent_scope;
|
scope.parent_scope = parent_scope;
|
||||||
scope.height = height;
|
scope.height = height;
|
||||||
|
scope.fnptr = fc_ptr;
|
||||||
|
scope.props.get_mut().replace(vcomp);
|
||||||
scope.subtree.set(subtree);
|
scope.subtree.set(subtree);
|
||||||
scope.our_arena_idx = new_scope_id;
|
scope.frames[0].reset();
|
||||||
scope.container = container;
|
scope.frames[1].reset();
|
||||||
|
scope.shared_contexts.get_mut().clear();
|
||||||
|
scope.items.get_mut().listeners.clear();
|
||||||
|
scope.items.get_mut().borrowed_props.clear();
|
||||||
|
scope.hook_idx.set(0);
|
||||||
|
scope.hook_vals.get_mut().clear();
|
||||||
|
|
||||||
let any_item = self.scopes.borrow_mut().insert(new_scope_id, scope);
|
let any_item = self.scopes.borrow_mut().insert(new_scope_id, scope);
|
||||||
debug_assert!(any_item.is_none());
|
debug_assert!(any_item.is_none());
|
||||||
} else {
|
} else {
|
||||||
// else create a new scope
|
// else create a new scope
|
||||||
|
let (node_capacity, hook_capacity) = self
|
||||||
|
.heuristics
|
||||||
|
.borrow()
|
||||||
|
.get(&fc_ptr)
|
||||||
|
.map(|h| (h.node_arena_size, h.hook_arena_size))
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
self.scopes.borrow_mut().insert(
|
self.scopes.borrow_mut().insert(
|
||||||
new_scope_id,
|
new_scope_id,
|
||||||
self.bump.alloc(ScopeState::new(
|
self.bump.alloc(ScopeState {
|
||||||
height,
|
|
||||||
container,
|
container,
|
||||||
new_scope_id,
|
our_arena_idx: new_scope_id,
|
||||||
parent_scope,
|
parent_scope,
|
||||||
vcomp,
|
height,
|
||||||
self.tasks.clone(),
|
fnptr: fc_ptr,
|
||||||
self.heuristics
|
props: RefCell::new(Some(vcomp)),
|
||||||
.borrow()
|
frames: [BumpFrame::new(node_capacity), BumpFrame::new(node_capacity)],
|
||||||
.get(&fc_ptr)
|
|
||||||
.map(|h| (h.node_arena_size, h.hook_arena_size))
|
// todo: subtrees
|
||||||
.unwrap_or_default(),
|
subtree: Cell::new(0),
|
||||||
)),
|
is_subtree_root: Cell::new(false),
|
||||||
|
|
||||||
|
generation: 0.into(),
|
||||||
|
|
||||||
|
tasks: self.tasks.clone(),
|
||||||
|
shared_contexts: Default::default(),
|
||||||
|
|
||||||
|
items: RefCell::new(SelfReferentialItems {
|
||||||
|
listeners: Default::default(),
|
||||||
|
borrowed_props: Default::default(),
|
||||||
|
}),
|
||||||
|
|
||||||
|
hook_arena: Bump::new(),
|
||||||
|
hook_vals: RefCell::new(Vec::with_capacity(hook_capacity)),
|
||||||
|
hook_idx: Default::default(),
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,8 +222,6 @@ impl ScopeArena {
|
|||||||
/// This also makes sure that drop order is consistent and predictable. All resources that rely on being dropped will
|
/// This also makes sure that drop order is consistent and predictable. All resources that rely on being dropped will
|
||||||
/// be dropped.
|
/// be dropped.
|
||||||
pub(crate) fn ensure_drop_safety(&self, scope_id: ScopeId) {
|
pub(crate) fn ensure_drop_safety(&self, scope_id: ScopeId) {
|
||||||
log::trace!("Ensuring drop safety for scope {:?}", scope_id);
|
|
||||||
|
|
||||||
if let Some(scope) = self.get_scope(scope_id) {
|
if let Some(scope) = self.get_scope(scope_id) {
|
||||||
let mut items = scope.items.borrow_mut();
|
let mut items = scope.items.borrow_mut();
|
||||||
|
|
||||||
@@ -201,7 +232,6 @@ impl ScopeArena {
|
|||||||
if let Some(scope_id) = comp.scope.get() {
|
if let Some(scope_id) = comp.scope.get() {
|
||||||
self.ensure_drop_safety(scope_id);
|
self.ensure_drop_safety(scope_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
drop(comp.props.take());
|
drop(comp.props.take());
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -217,18 +247,18 @@ impl ScopeArena {
|
|||||||
// Cycle to the next frame and then reset it
|
// Cycle to the next frame and then reset it
|
||||||
// This breaks any latent references, invalidating every pointer referencing into it.
|
// This breaks any latent references, invalidating every pointer referencing into it.
|
||||||
// Remove all the outdated listeners
|
// Remove all the outdated listeners
|
||||||
log::trace!("Running scope {:?}", id);
|
|
||||||
self.ensure_drop_safety(id);
|
self.ensure_drop_safety(id);
|
||||||
|
|
||||||
// todo: we *know* that this is aliased by the contents of the scope itself
|
// todo: we *know* that this is aliased by the contents of the scope itself
|
||||||
let scope = unsafe { &mut *self.get_scope_raw(id).expect("could not find scope") };
|
let scope = unsafe { &mut *self.get_scope_raw(id).expect("could not find scope") };
|
||||||
|
|
||||||
|
log::trace!("running scope {:?} symbol: {:?}", id, scope.fnptr);
|
||||||
|
|
||||||
// Safety:
|
// Safety:
|
||||||
// - We dropped the listeners, so no more &mut T can be used while these are held
|
// - We dropped the listeners, so no more &mut T can be used while these are held
|
||||||
// - All children nodes that rely on &mut T are replaced with a new reference
|
// - All children nodes that rely on &mut T are replaced with a new reference
|
||||||
scope.hook_idx.set(0);
|
scope.hook_idx.set(0);
|
||||||
|
|
||||||
// book keeping to ensure safety around the borrowed data
|
|
||||||
{
|
{
|
||||||
// Safety:
|
// Safety:
|
||||||
// - We've dropped all references to the wip bump frame with "ensure_drop_safety"
|
// - We've dropped all references to the wip bump frame with "ensure_drop_safety"
|
||||||
@@ -241,8 +271,6 @@ impl ScopeArena {
|
|||||||
debug_assert!(items.borrowed_props.is_empty());
|
debug_assert!(items.borrowed_props.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
// safety: this is definitely not dropped
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
If the component returns None, then we fill in a placeholder node. This will wipe what was there.
|
If the component returns None, then we fill in a placeholder node. This will wipe what was there.
|
||||||
An alternate approach is to leave the Real Dom the same, but that can lead to safety issues and a lot more checks.
|
An alternate approach is to leave the Real Dom the same, but that can lead to safety issues and a lot more checks.
|
||||||
@@ -284,14 +312,13 @@ impl ScopeArena {
|
|||||||
|
|
||||||
while let Some(id) = cur_el.take() {
|
while let Some(id) = cur_el.take() {
|
||||||
if let Some(el) = nodes.get(id.0) {
|
if let Some(el) = nodes.get(id.0) {
|
||||||
log::trace!("Found valid receiver element");
|
|
||||||
|
|
||||||
let real_el = unsafe { &**el };
|
let real_el = unsafe { &**el };
|
||||||
|
log::debug!("looking for listener on {:?}", real_el);
|
||||||
|
|
||||||
if let VNode::Element(real_el) = real_el {
|
if let VNode::Element(real_el) = real_el {
|
||||||
for listener in real_el.listeners.borrow().iter() {
|
for listener in real_el.listeners.borrow().iter() {
|
||||||
if listener.event == event.name {
|
if listener.event == event.name {
|
||||||
log::trace!("Found valid receiver event");
|
log::debug!("calling listener {:?}", listener.event);
|
||||||
|
|
||||||
if state.canceled.get() {
|
if state.canceled.get() {
|
||||||
// stop bubbling if canceled
|
// stop bubbling if canceled
|
||||||
break;
|
break;
|
||||||
@@ -421,6 +448,7 @@ pub struct ScopeState {
|
|||||||
pub(crate) container: ElementId,
|
pub(crate) container: ElementId,
|
||||||
pub(crate) our_arena_idx: ScopeId,
|
pub(crate) our_arena_idx: ScopeId,
|
||||||
pub(crate) height: u32,
|
pub(crate) height: u32,
|
||||||
|
pub(crate) fnptr: ComponentPtr,
|
||||||
|
|
||||||
// todo: subtrees
|
// todo: subtrees
|
||||||
pub(crate) is_subtree_root: Cell<bool>,
|
pub(crate) is_subtree_root: Cell<bool>,
|
||||||
@@ -449,43 +477,6 @@ pub struct SelfReferentialItems<'a> {
|
|||||||
|
|
||||||
// Public methods exposed to libraries and components
|
// Public methods exposed to libraries and components
|
||||||
impl ScopeState {
|
impl ScopeState {
|
||||||
fn new(
|
|
||||||
height: u32,
|
|
||||||
container: ElementId,
|
|
||||||
our_arena_idx: ScopeId,
|
|
||||||
parent_scope: Option<*mut ScopeState>,
|
|
||||||
vcomp: Box<dyn AnyProps>,
|
|
||||||
tasks: Rc<TaskQueue>,
|
|
||||||
(node_capacity, hook_capacity): (usize, usize),
|
|
||||||
) -> Self {
|
|
||||||
ScopeState {
|
|
||||||
container,
|
|
||||||
our_arena_idx,
|
|
||||||
parent_scope,
|
|
||||||
height,
|
|
||||||
props: RefCell::new(Some(vcomp)),
|
|
||||||
frames: [BumpFrame::new(node_capacity), BumpFrame::new(node_capacity)],
|
|
||||||
|
|
||||||
// todo: subtrees
|
|
||||||
subtree: Cell::new(0),
|
|
||||||
is_subtree_root: Cell::new(false),
|
|
||||||
|
|
||||||
generation: 0.into(),
|
|
||||||
|
|
||||||
tasks,
|
|
||||||
shared_contexts: Default::default(),
|
|
||||||
|
|
||||||
items: RefCell::new(SelfReferentialItems {
|
|
||||||
listeners: Default::default(),
|
|
||||||
borrowed_props: Default::default(),
|
|
||||||
}),
|
|
||||||
|
|
||||||
hook_arena: Bump::new(),
|
|
||||||
hook_vals: RefCell::new(Vec::with_capacity(hook_capacity)),
|
|
||||||
hook_idx: Default::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the subtree ID that this scope belongs to.
|
/// Get the subtree ID that this scope belongs to.
|
||||||
///
|
///
|
||||||
/// Each component has its own subtree ID - the root subtree has an ID of 0. This ID is used by the renderer to route
|
/// Each component has its own subtree ID - the root subtree has an ID of 0. This ID is used by the renderer to route
|
||||||
@@ -695,6 +686,7 @@ impl ScopeState {
|
|||||||
.insert(TypeId::of::<T>(), value.clone())
|
.insert(TypeId::of::<T>(), value.clone())
|
||||||
.map(|f| f.downcast::<T>().ok())
|
.map(|f| f.downcast::<T>().ok())
|
||||||
.flatten();
|
.flatten();
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut search_parent = self.parent_scope;
|
let mut search_parent = self.parent_scope;
|
||||||
@@ -750,6 +742,11 @@ impl ScopeState {
|
|||||||
self.tasks.push_fut(fut)
|
self.tasks.push_fut(fut)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Spawns the future but does not return the TaskId
|
||||||
|
pub fn spawn(&self, fut: impl Future<Output = ()> + 'static) {
|
||||||
|
self.push_future(fut);
|
||||||
|
}
|
||||||
|
|
||||||
// todo: attach some state to the future to know if we should poll it
|
// todo: attach some state to the future to know if we should poll it
|
||||||
pub fn remove_future(&self, id: TaskId) {
|
pub fn remove_future(&self, id: TaskId) {
|
||||||
self.tasks.remove_fut(id);
|
self.tasks.remove_fut(id);
|
||||||
@@ -890,13 +887,15 @@ impl ScopeState {
|
|||||||
self.frames[0].reset();
|
self.frames[0].reset();
|
||||||
self.frames[1].reset();
|
self.frames[1].reset();
|
||||||
|
|
||||||
// Finally, free up the hook values
|
// Free up the hook values
|
||||||
self.hook_arena.reset();
|
|
||||||
self.hook_vals.get_mut().drain(..).for_each(|state| {
|
self.hook_vals.get_mut().drain(..).for_each(|state| {
|
||||||
let as_mut = unsafe { &mut *state };
|
let as_mut = unsafe { &mut *state };
|
||||||
let boxed = unsafe { bumpalo::boxed::Box::from_raw(as_mut) };
|
let boxed = unsafe { bumpalo::boxed::Box::from_raw(as_mut) };
|
||||||
drop(boxed);
|
drop(boxed);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Finally, clear the hook arena
|
||||||
|
self.hook_arena.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use crate::innerlude::*;
|
|||||||
pub struct ElementIdIterator<'a> {
|
pub struct ElementIdIterator<'a> {
|
||||||
vdom: &'a VirtualDom,
|
vdom: &'a VirtualDom,
|
||||||
|
|
||||||
// Heuristcally we should never bleed into 5 completely nested fragments/components
|
// Heuristically we should never bleed into 5 completely nested fragments/components
|
||||||
// Smallvec lets us stack allocate our little stack machine so the vast majority of cases are sane
|
// Smallvec lets us stack allocate our little stack machine so the vast majority of cases are sane
|
||||||
stack: smallvec::SmallVec<[(u16, &'a VNode<'a>); 5]>,
|
stack: smallvec::SmallVec<[(u16, &'a VNode<'a>); 5]>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
//!
|
//!
|
||||||
//! This module provides the primary mechanics to create a hook-based, concurrent VDOM for Rust.
|
//! This module provides the primary mechanics to create a hook-based, concurrent VDOM for Rust.
|
||||||
|
|
||||||
|
use crate::diff::DiffState;
|
||||||
use crate::innerlude::*;
|
use crate::innerlude::*;
|
||||||
use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
|
use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
|
||||||
use futures_util::{future::poll_fn, StreamExt};
|
use futures_util::{future::poll_fn, StreamExt};
|
||||||
@@ -9,7 +10,7 @@ use fxhash::FxHashSet;
|
|||||||
use indexmap::IndexSet;
|
use indexmap::IndexSet;
|
||||||
use std::{collections::VecDeque, iter::FromIterator, task::Poll};
|
use std::{collections::VecDeque, iter::FromIterator, task::Poll};
|
||||||
|
|
||||||
/// A virtual node s ystem that progresses user events and diffs UI trees.
|
/// A virtual node system that progresses user events and diffs UI trees.
|
||||||
///
|
///
|
||||||
///
|
///
|
||||||
/// ## Guide
|
/// ## Guide
|
||||||
@@ -212,7 +213,7 @@ impl VirtualDom {
|
|||||||
let scopes = ScopeArena::new(channel.0.clone());
|
let scopes = ScopeArena::new(channel.0.clone());
|
||||||
|
|
||||||
scopes.new_with_key(
|
scopes.new_with_key(
|
||||||
root as *const _,
|
root as ComponentPtr,
|
||||||
Box::new(VComponentProps {
|
Box::new(VComponentProps {
|
||||||
props: root_props,
|
props: root_props,
|
||||||
memo: |_a, _b| unreachable!("memo on root will neve be run"),
|
memo: |_a, _b| unreachable!("memo on root will neve be run"),
|
||||||
@@ -233,7 +234,7 @@ impl VirtualDom {
|
|||||||
|
|
||||||
/// Get the [`Scope`] for the root component.
|
/// Get the [`Scope`] for the root component.
|
||||||
///
|
///
|
||||||
/// This is useful for traversing the tree from the root for heuristics or alternsative renderers that use Dioxus
|
/// This is useful for traversing the tree from the root for heuristics or alternative renderers that use Dioxus
|
||||||
/// directly.
|
/// directly.
|
||||||
///
|
///
|
||||||
/// This method is equivalent to calling `get_scope(ScopeId(0))`
|
/// This method is equivalent to calling `get_scope(ScopeId(0))`
|
||||||
@@ -448,6 +449,7 @@ impl VirtualDom {
|
|||||||
/// apply_mutations(mutations);
|
/// apply_mutations(mutations);
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
#[allow(unused)]
|
||||||
pub fn work_with_deadline(&mut self, mut deadline: impl FnMut() -> bool) -> Vec<Mutations> {
|
pub fn work_with_deadline(&mut self, mut deadline: impl FnMut() -> bool) -> Vec<Mutations> {
|
||||||
let mut committed_mutations = vec![];
|
let mut committed_mutations = vec![];
|
||||||
|
|
||||||
@@ -467,36 +469,41 @@ impl VirtualDom {
|
|||||||
h1.cmp(&h2).reverse()
|
h1.cmp(&h2).reverse()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
log::debug!("dirty_scopes: {:?}", self.dirty_scopes);
|
||||||
|
|
||||||
if let Some(scopeid) = self.dirty_scopes.pop() {
|
if let Some(scopeid) = self.dirty_scopes.pop() {
|
||||||
if !ran_scopes.contains(&scopeid) {
|
if !ran_scopes.contains(&scopeid) {
|
||||||
ran_scopes.insert(scopeid);
|
ran_scopes.insert(scopeid);
|
||||||
|
|
||||||
self.scopes.run_scope(scopeid);
|
self.scopes.run_scope(scopeid);
|
||||||
|
|
||||||
let (old, new) = (self.scopes.wip_head(scopeid), self.scopes.fin_head(scopeid));
|
diff_state.diff_scope(scopeid);
|
||||||
diff_state.stack.push(DiffInstruction::Diff { new, old });
|
|
||||||
diff_state.stack.scope_stack.push(scopeid);
|
|
||||||
|
|
||||||
let scope = scopes.get_scope(scopeid).unwrap();
|
let DiffState { mutations, .. } = diff_state;
|
||||||
diff_state.stack.element_stack.push(scope.container);
|
|
||||||
|
log::debug!("succesffuly resolved scopes {:?}", mutations.dirty_scopes);
|
||||||
|
for scope in &mutations.dirty_scopes {
|
||||||
|
self.dirty_scopes.remove(scope);
|
||||||
|
}
|
||||||
|
|
||||||
|
committed_mutations.push(mutations);
|
||||||
|
|
||||||
|
// todo: pause the diff machine
|
||||||
|
// if diff_state.work(&mut deadline) {
|
||||||
|
// let DiffState { mutations, .. } = diff_state;
|
||||||
|
// for scope in &mutations.dirty_scopes {
|
||||||
|
// self.dirty_scopes.remove(scope);
|
||||||
|
// }
|
||||||
|
// committed_mutations.push(mutations);
|
||||||
|
// } else {
|
||||||
|
// // leave the work in an incomplete state
|
||||||
|
// //
|
||||||
|
// // todo: we should store the edits and re-apply them later
|
||||||
|
// // for now, we just dump the work completely (threadsafe)
|
||||||
|
// return committed_mutations;
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if diff_state.work(&mut deadline) {
|
|
||||||
let DiffState { mutations, .. } = diff_state;
|
|
||||||
|
|
||||||
for scope in &mutations.dirty_scopes {
|
|
||||||
self.dirty_scopes.remove(scope);
|
|
||||||
}
|
|
||||||
|
|
||||||
committed_mutations.push(mutations);
|
|
||||||
} else {
|
|
||||||
// leave the work in an incomplete state
|
|
||||||
//
|
|
||||||
// todo: we should store the edits and re-apply them later
|
|
||||||
// for now, we just dump the work completely (threadsafe)
|
|
||||||
return committed_mutations;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
committed_mutations
|
committed_mutations
|
||||||
@@ -524,13 +531,15 @@ impl VirtualDom {
|
|||||||
let mut diff_state = DiffState::new(&self.scopes);
|
let mut diff_state = DiffState::new(&self.scopes);
|
||||||
|
|
||||||
self.scopes.run_scope(scope_id);
|
self.scopes.run_scope(scope_id);
|
||||||
diff_state
|
|
||||||
.stack
|
|
||||||
.create_node(self.scopes.fin_head(scope_id), MountType::Append);
|
|
||||||
|
|
||||||
diff_state.stack.element_stack.push(ElementId(0));
|
diff_state.element_stack.push(ElementId(0));
|
||||||
diff_state.stack.scope_stack.push(scope_id);
|
diff_state.scope_stack.push(scope_id);
|
||||||
diff_state.work(|| false);
|
|
||||||
|
let node = self.scopes.fin_head(scope_id);
|
||||||
|
let created = diff_state.create_node(node);
|
||||||
|
|
||||||
|
diff_state.mutations.append_children(created as u32);
|
||||||
|
|
||||||
self.dirty_scopes.clear();
|
self.dirty_scopes.clear();
|
||||||
assert!(self.dirty_scopes.is_empty());
|
assert!(self.dirty_scopes.is_empty());
|
||||||
|
|
||||||
@@ -577,12 +586,11 @@ impl VirtualDom {
|
|||||||
);
|
);
|
||||||
|
|
||||||
diff_machine.force_diff = true;
|
diff_machine.force_diff = true;
|
||||||
diff_machine.stack.push(DiffInstruction::Diff { old, new });
|
diff_machine.scope_stack.push(scope_id);
|
||||||
diff_machine.stack.scope_stack.push(scope_id);
|
|
||||||
|
|
||||||
let scope = diff_machine.scopes.get_scope(scope_id).unwrap();
|
let scope = diff_machine.scopes.get_scope(scope_id).unwrap();
|
||||||
diff_machine.stack.element_stack.push(scope.container);
|
diff_machine.element_stack.push(scope.container);
|
||||||
diff_machine.work(|| false);
|
|
||||||
|
diff_machine.diff_node(old, new);
|
||||||
|
|
||||||
diff_machine.mutations
|
diff_machine.mutations
|
||||||
}
|
}
|
||||||
@@ -621,10 +629,10 @@ impl VirtualDom {
|
|||||||
/// ```
|
/// ```
|
||||||
pub fn diff_vnodes<'a>(&'a self, old: &'a VNode<'a>, new: &'a VNode<'a>) -> Mutations<'a> {
|
pub fn diff_vnodes<'a>(&'a self, old: &'a VNode<'a>, new: &'a VNode<'a>) -> Mutations<'a> {
|
||||||
let mut machine = DiffState::new(&self.scopes);
|
let mut machine = DiffState::new(&self.scopes);
|
||||||
machine.stack.push(DiffInstruction::Diff { new, old });
|
machine.element_stack.push(ElementId(0));
|
||||||
machine.stack.element_stack.push(ElementId(0));
|
machine.scope_stack.push(ScopeId(0));
|
||||||
machine.stack.scope_stack.push(ScopeId(0));
|
machine.diff_node(old, new);
|
||||||
machine.work(|| false);
|
|
||||||
machine.mutations
|
machine.mutations
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -643,11 +651,11 @@ impl VirtualDom {
|
|||||||
/// ```
|
/// ```
|
||||||
pub fn create_vnodes<'a>(&'a self, nodes: LazyNodes<'a, '_>) -> Mutations<'a> {
|
pub fn create_vnodes<'a>(&'a self, nodes: LazyNodes<'a, '_>) -> Mutations<'a> {
|
||||||
let mut machine = DiffState::new(&self.scopes);
|
let mut machine = DiffState::new(&self.scopes);
|
||||||
machine.stack.element_stack.push(ElementId(0));
|
machine.scope_stack.push(ScopeId(0));
|
||||||
machine
|
machine.element_stack.push(ElementId(0));
|
||||||
.stack
|
let node = self.render_vnodes(nodes);
|
||||||
.create_node(self.render_vnodes(nodes), MountType::Append);
|
let created = machine.create_node(node);
|
||||||
machine.work(|| false);
|
machine.mutations.append_children(created as u32);
|
||||||
machine.mutations
|
machine.mutations
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -672,16 +680,15 @@ impl VirtualDom {
|
|||||||
let (old, new) = (self.render_vnodes(left), self.render_vnodes(right));
|
let (old, new) = (self.render_vnodes(left), self.render_vnodes(right));
|
||||||
|
|
||||||
let mut create = DiffState::new(&self.scopes);
|
let mut create = DiffState::new(&self.scopes);
|
||||||
create.stack.scope_stack.push(ScopeId(0));
|
create.scope_stack.push(ScopeId(0));
|
||||||
create.stack.element_stack.push(ElementId(0));
|
create.element_stack.push(ElementId(0));
|
||||||
create.stack.create_node(old, MountType::Append);
|
let created = create.create_node(old);
|
||||||
create.work(|| false);
|
create.mutations.append_children(created as u32);
|
||||||
|
|
||||||
let mut edit = DiffState::new(&self.scopes);
|
let mut edit = DiffState::new(&self.scopes);
|
||||||
edit.stack.scope_stack.push(ScopeId(0));
|
edit.scope_stack.push(ScopeId(0));
|
||||||
edit.stack.element_stack.push(ElementId(0));
|
edit.element_stack.push(ElementId(0));
|
||||||
edit.stack.push(DiffInstruction::Diff { old, new });
|
edit.diff_node(old, new);
|
||||||
edit.work(|| false);
|
|
||||||
|
|
||||||
(create.mutations, edit.mutations)
|
(create.mutations, edit.mutations)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
struct_lit_width = 80
|
||||||
@@ -36,18 +36,9 @@ fn test_original_diff() {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
mutations.edits,
|
mutations.edits,
|
||||||
[
|
[
|
||||||
CreateElement {
|
CreateElement { root: 1, tag: "div" },
|
||||||
root: 1,
|
CreateElement { root: 2, tag: "div" },
|
||||||
tag: "div"
|
CreateTextNode { root: 3, text: "Hello, world!" },
|
||||||
},
|
|
||||||
CreateElement {
|
|
||||||
root: 2,
|
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
CreateTextNode {
|
|
||||||
root: 3,
|
|
||||||
text: "Hello, world!"
|
|
||||||
},
|
|
||||||
AppendChildren { many: 1 },
|
AppendChildren { many: 1 },
|
||||||
AppendChildren { many: 1 },
|
AppendChildren { many: 1 },
|
||||||
AppendChildren { many: 1 },
|
AppendChildren { many: 1 },
|
||||||
@@ -81,34 +72,13 @@ fn create() {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
mutations.edits,
|
mutations.edits,
|
||||||
[
|
[
|
||||||
CreateElement {
|
CreateElement { root: 1, tag: "div" },
|
||||||
root: 1,
|
CreateElement { root: 2, tag: "div" },
|
||||||
tag: "div"
|
CreateTextNode { root: 3, text: "Hello, world!" },
|
||||||
},
|
CreateElement { root: 4, tag: "div" },
|
||||||
CreateElement {
|
CreateElement { root: 5, tag: "div" },
|
||||||
root: 2,
|
CreateTextNode { root: 6, text: "hello" },
|
||||||
tag: "div"
|
CreateTextNode { root: 7, text: "world" },
|
||||||
},
|
|
||||||
CreateTextNode {
|
|
||||||
root: 3,
|
|
||||||
text: "Hello, world!"
|
|
||||||
},
|
|
||||||
CreateElement {
|
|
||||||
root: 4,
|
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
CreateElement {
|
|
||||||
root: 5,
|
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
CreateTextNode {
|
|
||||||
root: 6,
|
|
||||||
text: "hello"
|
|
||||||
},
|
|
||||||
CreateTextNode {
|
|
||||||
root: 7,
|
|
||||||
text: "world"
|
|
||||||
},
|
|
||||||
AppendChildren { many: 2 },
|
AppendChildren { many: 2 },
|
||||||
AppendChildren { many: 1 },
|
AppendChildren { many: 1 },
|
||||||
AppendChildren { many: 2 },
|
AppendChildren { many: 2 },
|
||||||
@@ -135,32 +105,14 @@ fn create_list() {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
mutations.edits,
|
mutations.edits,
|
||||||
[
|
[
|
||||||
CreateElement {
|
CreateElement { root: 1, tag: "div" },
|
||||||
root: 1,
|
CreateTextNode { root: 2, text: "hello" },
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
CreateTextNode {
|
|
||||||
root: 2,
|
|
||||||
text: "hello"
|
|
||||||
},
|
|
||||||
AppendChildren { many: 1 },
|
AppendChildren { many: 1 },
|
||||||
CreateElement {
|
CreateElement { root: 3, tag: "div" },
|
||||||
root: 3,
|
CreateTextNode { root: 4, text: "hello" },
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
CreateTextNode {
|
|
||||||
root: 4,
|
|
||||||
text: "hello"
|
|
||||||
},
|
|
||||||
AppendChildren { many: 1 },
|
AppendChildren { many: 1 },
|
||||||
CreateElement {
|
CreateElement { root: 5, tag: "div" },
|
||||||
root: 5,
|
CreateTextNode { root: 6, text: "hello" },
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
CreateTextNode {
|
|
||||||
root: 6,
|
|
||||||
text: "hello"
|
|
||||||
},
|
|
||||||
AppendChildren { many: 1 },
|
AppendChildren { many: 1 },
|
||||||
AppendChildren { many: 3 },
|
AppendChildren { many: 3 },
|
||||||
]
|
]
|
||||||
@@ -185,22 +137,10 @@ fn create_simple() {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
mutations.edits,
|
mutations.edits,
|
||||||
[
|
[
|
||||||
CreateElement {
|
CreateElement { root: 1, tag: "div" },
|
||||||
root: 1,
|
CreateElement { root: 2, tag: "div" },
|
||||||
tag: "div"
|
CreateElement { root: 3, tag: "div" },
|
||||||
},
|
CreateElement { root: 4, tag: "div" },
|
||||||
CreateElement {
|
|
||||||
root: 2,
|
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
CreateElement {
|
|
||||||
root: 3,
|
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
CreateElement {
|
|
||||||
root: 4,
|
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
AppendChildren { many: 4 },
|
AppendChildren { many: 4 },
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
@@ -235,36 +175,18 @@ fn create_components() {
|
|||||||
mutations.edits,
|
mutations.edits,
|
||||||
[
|
[
|
||||||
CreateElement { root: 1, tag: "h1" },
|
CreateElement { root: 1, tag: "h1" },
|
||||||
CreateElement {
|
CreateElement { root: 2, tag: "div" },
|
||||||
root: 2,
|
CreateTextNode { root: 3, text: "abc1" },
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
CreateTextNode {
|
|
||||||
root: 3,
|
|
||||||
text: "abc1"
|
|
||||||
},
|
|
||||||
AppendChildren { many: 1 },
|
AppendChildren { many: 1 },
|
||||||
CreateElement { root: 4, tag: "p" },
|
CreateElement { root: 4, tag: "p" },
|
||||||
CreateElement { root: 5, tag: "h1" },
|
CreateElement { root: 5, tag: "h1" },
|
||||||
CreateElement {
|
CreateElement { root: 6, tag: "div" },
|
||||||
root: 6,
|
CreateTextNode { root: 7, text: "abc2" },
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
CreateTextNode {
|
|
||||||
root: 7,
|
|
||||||
text: "abc2"
|
|
||||||
},
|
|
||||||
AppendChildren { many: 1 },
|
AppendChildren { many: 1 },
|
||||||
CreateElement { root: 8, tag: "p" },
|
CreateElement { root: 8, tag: "p" },
|
||||||
CreateElement { root: 9, tag: "h1" },
|
CreateElement { root: 9, tag: "h1" },
|
||||||
CreateElement {
|
CreateElement { root: 10, tag: "div" },
|
||||||
root: 10,
|
CreateTextNode { root: 11, text: "abc3" },
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
CreateTextNode {
|
|
||||||
root: 11,
|
|
||||||
text: "abc3"
|
|
||||||
},
|
|
||||||
AppendChildren { many: 1 },
|
AppendChildren { many: 1 },
|
||||||
CreateElement { root: 12, tag: "p" },
|
CreateElement { root: 12, tag: "p" },
|
||||||
AppendChildren { many: 9 },
|
AppendChildren { many: 9 },
|
||||||
@@ -285,14 +207,8 @@ fn anchors() {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
mutations.edits,
|
mutations.edits,
|
||||||
[
|
[
|
||||||
CreateElement {
|
CreateElement { root: 1, tag: "div" },
|
||||||
root: 1,
|
CreateTextNode { root: 2, text: "hello" },
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
CreateTextNode {
|
|
||||||
root: 2,
|
|
||||||
text: "hello"
|
|
||||||
},
|
|
||||||
AppendChildren { many: 1 },
|
AppendChildren { many: 1 },
|
||||||
CreatePlaceholder { root: 3 },
|
CreatePlaceholder { root: 3 },
|
||||||
AppendChildren { many: 2 },
|
AppendChildren { many: 2 },
|
||||||
|
|||||||
+60
-196
@@ -1,4 +1,5 @@
|
|||||||
#![allow(unused, non_upper_case_globals)]
|
#![allow(unused, non_upper_case_globals)]
|
||||||
|
|
||||||
//! Diffing Tests
|
//! Diffing Tests
|
||||||
//!
|
//!
|
||||||
//! These tests only verify that the diffing algorithm works properly for single components.
|
//! These tests only verify that the diffing algorithm works properly for single components.
|
||||||
@@ -31,26 +32,14 @@ fn html_and_rsx_generate_the_same_output() {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
create.edits,
|
create.edits,
|
||||||
[
|
[
|
||||||
CreateElement {
|
CreateElement { root: 1, tag: "div" },
|
||||||
root: 1,
|
CreateTextNode { root: 2, text: "Hello world" },
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
CreateTextNode {
|
|
||||||
root: 2,
|
|
||||||
text: "Hello world"
|
|
||||||
},
|
|
||||||
AppendChildren { many: 1 },
|
AppendChildren { many: 1 },
|
||||||
AppendChildren { many: 1 },
|
AppendChildren { many: 1 },
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(change.edits, [SetText { text: "Goodbye world", root: 2 },]);
|
||||||
change.edits,
|
|
||||||
[SetText {
|
|
||||||
text: "Goodbye world",
|
|
||||||
root: 2
|
|
||||||
},]
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Should result in 3 elements on the stack
|
/// Should result in 3 elements on the stack
|
||||||
@@ -67,32 +56,14 @@ fn fragments_create_properly() {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
create.edits,
|
create.edits,
|
||||||
[
|
[
|
||||||
CreateElement {
|
CreateElement { root: 1, tag: "div" },
|
||||||
root: 1,
|
CreateTextNode { root: 2, text: "Hello a" },
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
CreateTextNode {
|
|
||||||
root: 2,
|
|
||||||
text: "Hello a"
|
|
||||||
},
|
|
||||||
AppendChildren { many: 1 },
|
AppendChildren { many: 1 },
|
||||||
CreateElement {
|
CreateElement { root: 3, tag: "div" },
|
||||||
root: 3,
|
CreateTextNode { root: 4, text: "Hello b" },
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
CreateTextNode {
|
|
||||||
root: 4,
|
|
||||||
text: "Hello b"
|
|
||||||
},
|
|
||||||
AppendChildren { many: 1 },
|
AppendChildren { many: 1 },
|
||||||
CreateElement {
|
CreateElement { root: 5, tag: "div" },
|
||||||
root: 5,
|
CreateTextNode { root: 6, text: "Hello c" },
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
CreateTextNode {
|
|
||||||
root: 6,
|
|
||||||
text: "Hello c"
|
|
||||||
},
|
|
||||||
AppendChildren { many: 1 },
|
AppendChildren { many: 1 },
|
||||||
AppendChildren { many: 3 },
|
AppendChildren { many: 3 },
|
||||||
]
|
]
|
||||||
@@ -116,10 +87,7 @@ fn empty_fragments_create_anchors() {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
change.edits,
|
change.edits,
|
||||||
[
|
[
|
||||||
CreateElement {
|
CreateElement { root: 2, tag: "div" },
|
||||||
root: 2,
|
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
ReplaceWith { m: 1, root: 1 }
|
ReplaceWith { m: 1, root: 1 }
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
@@ -138,29 +106,15 @@ fn empty_fragments_create_many_anchors() {
|
|||||||
create.edits,
|
create.edits,
|
||||||
[CreatePlaceholder { root: 1 }, AppendChildren { many: 1 }]
|
[CreatePlaceholder { root: 1 }, AppendChildren { many: 1 }]
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
change.edits,
|
change.edits,
|
||||||
[
|
[
|
||||||
CreateElement {
|
CreateElement { root: 2, tag: "div" },
|
||||||
root: 2,
|
CreateElement { root: 3, tag: "div" },
|
||||||
tag: "div"
|
CreateElement { root: 4, tag: "div" },
|
||||||
},
|
CreateElement { root: 5, tag: "div" },
|
||||||
CreateElement {
|
CreateElement { root: 6, tag: "div" },
|
||||||
root: 3,
|
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
CreateElement {
|
|
||||||
root: 4,
|
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
CreateElement {
|
|
||||||
root: 5,
|
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
CreateElement {
|
|
||||||
root: 6,
|
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
ReplaceWith { m: 5, root: 1 }
|
ReplaceWith { m: 5, root: 1 }
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
@@ -188,32 +142,14 @@ fn empty_fragments_create_anchors_with_many_children() {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
change.edits,
|
change.edits,
|
||||||
[
|
[
|
||||||
CreateElement {
|
CreateElement { tag: "div", root: 2 },
|
||||||
tag: "div",
|
CreateTextNode { text: "hello: 0", root: 3 },
|
||||||
root: 2,
|
|
||||||
},
|
|
||||||
CreateTextNode {
|
|
||||||
text: "hello: 0",
|
|
||||||
root: 3
|
|
||||||
},
|
|
||||||
AppendChildren { many: 1 },
|
AppendChildren { many: 1 },
|
||||||
CreateElement {
|
CreateElement { tag: "div", root: 4 },
|
||||||
tag: "div",
|
CreateTextNode { text: "hello: 1", root: 5 },
|
||||||
root: 4,
|
|
||||||
},
|
|
||||||
CreateTextNode {
|
|
||||||
text: "hello: 1",
|
|
||||||
root: 5
|
|
||||||
},
|
|
||||||
AppendChildren { many: 1 },
|
AppendChildren { many: 1 },
|
||||||
CreateElement {
|
CreateElement { tag: "div", root: 6 },
|
||||||
tag: "div",
|
CreateTextNode { text: "hello: 2", root: 7 },
|
||||||
root: 6,
|
|
||||||
},
|
|
||||||
CreateTextNode {
|
|
||||||
text: "hello: 2",
|
|
||||||
root: 7
|
|
||||||
},
|
|
||||||
AppendChildren { many: 1 },
|
AppendChildren { many: 1 },
|
||||||
ReplaceWith { root: 1, m: 3 }
|
ReplaceWith { root: 1, m: 3 }
|
||||||
]
|
]
|
||||||
@@ -236,23 +172,11 @@ fn many_items_become_fragment() {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
create.edits,
|
create.edits,
|
||||||
[
|
[
|
||||||
CreateElement {
|
CreateElement { root: 1, tag: "div" },
|
||||||
root: 1,
|
CreateTextNode { text: "hello", root: 2 },
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
CreateTextNode {
|
|
||||||
text: "hello",
|
|
||||||
root: 2
|
|
||||||
},
|
|
||||||
AppendChildren { many: 1 },
|
AppendChildren { many: 1 },
|
||||||
CreateElement {
|
CreateElement { root: 3, tag: "div" },
|
||||||
root: 3,
|
CreateTextNode { text: "hello", root: 4 },
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
CreateTextNode {
|
|
||||||
text: "hello",
|
|
||||||
root: 4
|
|
||||||
},
|
|
||||||
AppendChildren { many: 1 },
|
AppendChildren { many: 1 },
|
||||||
AppendChildren { many: 2 },
|
AppendChildren { many: 2 },
|
||||||
]
|
]
|
||||||
@@ -315,7 +239,7 @@ fn two_fragments_with_differrent_elements_are_differet() {
|
|||||||
// replace the divs with new h1s
|
// replace the divs with new h1s
|
||||||
CreateElement { tag: "h1", root: 7 },
|
CreateElement { tag: "h1", root: 7 },
|
||||||
ReplaceWith { root: 1, m: 1 },
|
ReplaceWith { root: 1, m: 1 },
|
||||||
CreateElement { tag: "h1", root: 8 },
|
CreateElement { tag: "h1", root: 1 }, // notice how 1 gets re-used
|
||||||
ReplaceWith { root: 2, m: 1 },
|
ReplaceWith { root: 2, m: 1 },
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
@@ -339,39 +263,27 @@ fn two_fragments_with_differrent_elements_are_differet_shorter() {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
create.edits,
|
create.edits,
|
||||||
[
|
[
|
||||||
CreateElement {
|
CreateElement { root: 1, tag: "div" },
|
||||||
root: 1,
|
CreateElement { root: 2, tag: "div" },
|
||||||
tag: "div"
|
CreateElement { root: 3, tag: "div" },
|
||||||
},
|
CreateElement { root: 4, tag: "div" },
|
||||||
CreateElement {
|
CreateElement { root: 5, tag: "div" },
|
||||||
root: 2,
|
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
CreateElement {
|
|
||||||
root: 3,
|
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
CreateElement {
|
|
||||||
root: 4,
|
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
CreateElement {
|
|
||||||
root: 5,
|
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
CreateElement { root: 6, tag: "p" },
|
CreateElement { root: 6, tag: "p" },
|
||||||
AppendChildren { many: 6 },
|
AppendChildren { many: 6 },
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// note: key reuse is always the last node that got used
|
||||||
|
// slab maintains a linked list, essentially
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
change.edits,
|
change.edits,
|
||||||
[
|
[
|
||||||
Remove { root: 3 },
|
Remove { root: 3 },
|
||||||
Remove { root: 4 },
|
Remove { root: 4 },
|
||||||
Remove { root: 5 },
|
Remove { root: 5 },
|
||||||
CreateElement { root: 7, tag: "h1" },
|
CreateElement { root: 5, tag: "h1" }, // 3 gets reused
|
||||||
ReplaceWith { root: 1, m: 1 },
|
ReplaceWith { root: 1, m: 1 }, // 1 gets deleted
|
||||||
CreateElement { root: 8, tag: "h1" },
|
CreateElement { root: 1, tag: "h1" }, // 1 gets reused
|
||||||
ReplaceWith { root: 2, m: 1 },
|
ReplaceWith { root: 2, m: 1 },
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
@@ -395,14 +307,8 @@ fn two_fragments_with_same_elements_are_differet() {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
create.edits,
|
create.edits,
|
||||||
[
|
[
|
||||||
CreateElement {
|
CreateElement { root: 1, tag: "div" },
|
||||||
root: 1,
|
CreateElement { root: 2, tag: "div" },
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
CreateElement {
|
|
||||||
root: 2,
|
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
CreateElement { root: 3, tag: "p" },
|
CreateElement { root: 3, tag: "p" },
|
||||||
AppendChildren { many: 3 },
|
AppendChildren { many: 3 },
|
||||||
]
|
]
|
||||||
@@ -410,18 +316,9 @@ fn two_fragments_with_same_elements_are_differet() {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
change.edits,
|
change.edits,
|
||||||
[
|
[
|
||||||
CreateElement {
|
CreateElement { root: 4, tag: "div" },
|
||||||
root: 4,
|
CreateElement { root: 5, tag: "div" },
|
||||||
tag: "div"
|
CreateElement { root: 6, tag: "div" },
|
||||||
},
|
|
||||||
CreateElement {
|
|
||||||
root: 5,
|
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
CreateElement {
|
|
||||||
root: 6,
|
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
InsertAfter { root: 2, n: 3 },
|
InsertAfter { root: 2, n: 3 },
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
@@ -628,14 +525,8 @@ fn keyed_diffing_additions() {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
change.edits,
|
change.edits,
|
||||||
[
|
[
|
||||||
CreateElement {
|
CreateElement { root: 6, tag: "div" },
|
||||||
root: 6,
|
CreateElement { root: 7, tag: "div" },
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
CreateElement {
|
|
||||||
root: 7,
|
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
InsertAfter { n: 2, root: 5 }
|
InsertAfter { n: 2, root: 5 }
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
@@ -663,14 +554,8 @@ fn keyed_diffing_additions_and_moves_on_ends() {
|
|||||||
change.edits,
|
change.edits,
|
||||||
[
|
[
|
||||||
// create 11, 12
|
// create 11, 12
|
||||||
CreateElement {
|
CreateElement { tag: "div", root: 5 },
|
||||||
tag: "div",
|
CreateElement { tag: "div", root: 6 },
|
||||||
root: 5
|
|
||||||
},
|
|
||||||
CreateElement {
|
|
||||||
tag: "div",
|
|
||||||
root: 6
|
|
||||||
},
|
|
||||||
InsertAfter { root: 3, n: 2 },
|
InsertAfter { root: 3, n: 2 },
|
||||||
// move 7 to the front
|
// move 7 to the front
|
||||||
PushRoot { root: 4 },
|
PushRoot { root: 4 },
|
||||||
@@ -684,13 +569,13 @@ fn keyed_diffing_additions_and_moves_in_middle() {
|
|||||||
let dom = new_dom();
|
let dom = new_dom();
|
||||||
|
|
||||||
let left = rsx!({
|
let left = rsx!({
|
||||||
[/**/ 4, 5, 6, 7 /**/].iter().map(|f| {
|
[/**/ 1, 2, 3, 4 /**/].iter().map(|f| {
|
||||||
rsx! { div { key: "{f}" }}
|
rsx! { div { key: "{f}" }}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
let right = rsx!({
|
let right = rsx!({
|
||||||
[/**/ 7, 4, 13, 17, 5, 11, 12, 6 /**/].iter().map(|f| {
|
[/**/ 4, 1, 7, 8, 2, 5, 6, 3 /**/].iter().map(|f| {
|
||||||
rsx! { div { key: "{f}" }}
|
rsx! { div { key: "{f}" }}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
@@ -701,26 +586,14 @@ fn keyed_diffing_additions_and_moves_in_middle() {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
change.edits,
|
change.edits,
|
||||||
[
|
[
|
||||||
// create 13, 17
|
// create 5, 6
|
||||||
CreateElement {
|
CreateElement { tag: "div", root: 5 },
|
||||||
tag: "div",
|
CreateElement { tag: "div", root: 6 },
|
||||||
root: 5
|
|
||||||
},
|
|
||||||
CreateElement {
|
|
||||||
tag: "div",
|
|
||||||
root: 6
|
|
||||||
},
|
|
||||||
InsertBefore { root: 2, n: 2 },
|
|
||||||
// create 11, 12
|
|
||||||
CreateElement {
|
|
||||||
tag: "div",
|
|
||||||
root: 7
|
|
||||||
},
|
|
||||||
CreateElement {
|
|
||||||
tag: "div",
|
|
||||||
root: 8
|
|
||||||
},
|
|
||||||
InsertBefore { root: 3, n: 2 },
|
InsertBefore { root: 3, n: 2 },
|
||||||
|
// create 7, 8
|
||||||
|
CreateElement { tag: "div", root: 7 },
|
||||||
|
CreateElement { tag: "div", root: 8 },
|
||||||
|
InsertBefore { root: 2, n: 2 },
|
||||||
// move 7
|
// move 7
|
||||||
PushRoot { root: 4 },
|
PushRoot { root: 4 },
|
||||||
InsertBefore { root: 1, n: 1 }
|
InsertBefore { root: 1, n: 1 }
|
||||||
@@ -756,16 +629,10 @@ fn controlled_keyed_diffing_out_of_order() {
|
|||||||
// remove 7
|
// remove 7
|
||||||
|
|
||||||
// create 9 and insert before 6
|
// create 9 and insert before 6
|
||||||
CreateElement {
|
CreateElement { root: 5, tag: "div" },
|
||||||
root: 5,
|
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
InsertBefore { n: 1, root: 3 },
|
InsertBefore { n: 1, root: 3 },
|
||||||
// create 0 and insert before 5
|
// create 0 and insert before 5
|
||||||
CreateElement {
|
CreateElement { root: 6, tag: "div" },
|
||||||
root: 6,
|
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
InsertBefore { n: 1, root: 2 },
|
InsertBefore { n: 1, root: 2 },
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
@@ -792,10 +659,7 @@ fn controlled_keyed_diffing_out_of_order_max_test() {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
changes.edits,
|
changes.edits,
|
||||||
[
|
[
|
||||||
CreateElement {
|
CreateElement { root: 6, tag: "div" },
|
||||||
root: 6,
|
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
InsertBefore { n: 1, root: 3 },
|
InsertBefore { n: 1, root: 3 },
|
||||||
PushRoot { root: 4 },
|
PushRoot { root: 4 },
|
||||||
InsertBefore { n: 1, root: 1 },
|
InsertBefore { n: 1, root: 1 },
|
||||||
|
|||||||
@@ -42,14 +42,8 @@ fn test_early_abort() {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
edits.edits,
|
edits.edits,
|
||||||
[
|
[
|
||||||
CreateElement {
|
CreateElement { tag: "div", root: 1 },
|
||||||
tag: "div",
|
CreateTextNode { text: "Hello, world!", root: 2 },
|
||||||
root: 1,
|
|
||||||
},
|
|
||||||
CreateTextNode {
|
|
||||||
text: "Hello, world!",
|
|
||||||
root: 2,
|
|
||||||
},
|
|
||||||
AppendChildren { many: 1 },
|
AppendChildren { many: 1 },
|
||||||
AppendChildren { many: 1 },
|
AppendChildren { many: 1 },
|
||||||
]
|
]
|
||||||
@@ -65,14 +59,8 @@ fn test_early_abort() {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
edits.edits,
|
edits.edits,
|
||||||
[
|
[
|
||||||
CreateElement {
|
CreateElement { tag: "div", root: 1 }, // keys get reused
|
||||||
tag: "div",
|
CreateTextNode { text: "Hello, world!", root: 2 }, // keys get reused
|
||||||
root: 2,
|
|
||||||
},
|
|
||||||
CreateTextNode {
|
|
||||||
text: "Hello, world!",
|
|
||||||
root: 4,
|
|
||||||
},
|
|
||||||
AppendChildren { many: 1 },
|
AppendChildren { many: 1 },
|
||||||
ReplaceWith { root: 3, m: 1 },
|
ReplaceWith { root: 3, m: 1 },
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -27,12 +27,7 @@ fn manual_diffing() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let value = Arc::new(Mutex::new("Hello"));
|
let value = Arc::new(Mutex::new("Hello"));
|
||||||
let mut dom = VirtualDom::new_with_props(
|
let mut dom = VirtualDom::new_with_props(App, AppProps { value: value.clone() });
|
||||||
App,
|
|
||||||
AppProps {
|
|
||||||
value: value.clone(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
let _ = dom.rebuild();
|
let _ = dom.rebuild();
|
||||||
|
|
||||||
@@ -45,7 +40,7 @@ fn manual_diffing() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn events_generate() {
|
fn events_generate() {
|
||||||
static App: Component = |cx| {
|
fn app(cx: Scope) -> Element {
|
||||||
let count = cx.use_hook(|_| 0);
|
let count = cx.use_hook(|_| 0);
|
||||||
|
|
||||||
let inner = match *count {
|
let inner = match *count {
|
||||||
@@ -66,7 +61,7 @@ fn events_generate() {
|
|||||||
cx.render(inner)
|
cx.render(inner)
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut dom = VirtualDom::new(App);
|
let mut dom = VirtualDom::new(app);
|
||||||
let mut channel = dom.get_scheduler_channel();
|
let mut channel = dom.get_scheduler_channel();
|
||||||
assert!(dom.has_work());
|
assert!(dom.has_work());
|
||||||
|
|
||||||
@@ -74,28 +69,12 @@ fn events_generate() {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
edits.edits,
|
edits.edits,
|
||||||
[
|
[
|
||||||
CreateElement {
|
CreateElement { tag: "div", root: 1 },
|
||||||
tag: "div",
|
NewEventListener { event_name: "click", scope: ScopeId(0), root: 1 },
|
||||||
root: 1,
|
CreateElement { tag: "div", root: 2 },
|
||||||
},
|
CreateTextNode { text: "nested", root: 3 },
|
||||||
NewEventListener {
|
|
||||||
event_name: "click",
|
|
||||||
scope: ScopeId(0),
|
|
||||||
root: 1,
|
|
||||||
},
|
|
||||||
CreateElement {
|
|
||||||
tag: "div",
|
|
||||||
root: 2,
|
|
||||||
},
|
|
||||||
CreateTextNode {
|
|
||||||
text: "nested",
|
|
||||||
root: 3,
|
|
||||||
},
|
|
||||||
AppendChildren { many: 1 },
|
AppendChildren { many: 1 },
|
||||||
CreateTextNode {
|
CreateTextNode { text: "Click me!", root: 4 },
|
||||||
text: "Click me!",
|
|
||||||
root: 4,
|
|
||||||
},
|
|
||||||
AppendChildren { many: 2 },
|
AppendChildren { many: 2 },
|
||||||
AppendChildren { many: 1 },
|
AppendChildren { many: 1 },
|
||||||
]
|
]
|
||||||
@@ -104,7 +83,7 @@ fn events_generate() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn components_generate() {
|
fn components_generate() {
|
||||||
static App: Component = |cx| {
|
fn app(cx: Scope) -> Element {
|
||||||
let render_phase = cx.use_hook(|_| 0);
|
let render_phase = cx.use_hook(|_| 0);
|
||||||
*render_phase += 1;
|
*render_phase += 1;
|
||||||
|
|
||||||
@@ -121,106 +100,84 @@ fn components_generate() {
|
|||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
static Child: Component = |cx| {
|
fn Child(cx: Scope) -> Element {
|
||||||
|
log::debug!("Running child");
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
h1 {}
|
h1 {}
|
||||||
})
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
let mut dom = VirtualDom::new(App);
|
let mut dom = VirtualDom::new(app);
|
||||||
let edits = dom.rebuild();
|
let edits = dom.rebuild();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
edits.edits,
|
edits.edits,
|
||||||
[
|
[
|
||||||
CreateTextNode {
|
CreateTextNode { text: "Text0", root: 1 },
|
||||||
text: "Text0",
|
|
||||||
root: 1,
|
|
||||||
},
|
|
||||||
AppendChildren { many: 1 },
|
AppendChildren { many: 1 },
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
let edits = dom.hard_diff(ScopeId(0));
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
edits.edits,
|
dom.hard_diff(ScopeId(0)).edits,
|
||||||
[
|
[
|
||||||
CreateElement {
|
CreateElement { tag: "div", root: 2 },
|
||||||
tag: "div",
|
|
||||||
root: 2,
|
|
||||||
},
|
|
||||||
ReplaceWith { root: 1, m: 1 },
|
ReplaceWith { root: 1, m: 1 },
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
let edits = dom.hard_diff(ScopeId(0));
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
edits.edits,
|
dom.hard_diff(ScopeId(0)).edits,
|
||||||
[
|
[
|
||||||
CreateTextNode {
|
CreateTextNode { text: "Text2", root: 1 },
|
||||||
text: "Text2",
|
|
||||||
root: 3,
|
|
||||||
},
|
|
||||||
ReplaceWith { root: 2, m: 1 },
|
ReplaceWith { root: 2, m: 1 },
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
let edits = dom.hard_diff(ScopeId(0));
|
// child {}
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
edits.edits,
|
dom.hard_diff(ScopeId(0)).edits,
|
||||||
[
|
[
|
||||||
CreateElement { tag: "h1", root: 4 },
|
CreateElement { tag: "h1", root: 2 },
|
||||||
ReplaceWith { root: 3, m: 1 },
|
ReplaceWith { root: 1, m: 1 },
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
let edits = dom.hard_diff(ScopeId(0));
|
// placeholder
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
edits.edits,
|
dom.hard_diff(ScopeId(0)).edits,
|
||||||
[CreatePlaceholder { root: 5 }, ReplaceWith { root: 4, m: 1 },]
|
[CreatePlaceholder { root: 1 }, ReplaceWith { root: 2, m: 1 },]
|
||||||
);
|
);
|
||||||
|
|
||||||
let edits = dom.hard_diff(ScopeId(0));
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
edits.edits,
|
dom.hard_diff(ScopeId(0)).edits,
|
||||||
[
|
[
|
||||||
CreateTextNode {
|
CreateTextNode { text: "text 3", root: 2 },
|
||||||
text: "text 3",
|
ReplaceWith { root: 1, m: 1 },
|
||||||
root: 6,
|
|
||||||
},
|
|
||||||
ReplaceWith { root: 5, m: 1 },
|
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
let edits = dom.hard_diff(ScopeId(0));
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
edits.edits,
|
dom.hard_diff(ScopeId(0)).edits,
|
||||||
[
|
[
|
||||||
CreateTextNode {
|
CreateTextNode { text: "text 0", root: 1 },
|
||||||
text: "text 0",
|
CreateTextNode { text: "text 1", root: 3 },
|
||||||
root: 7,
|
ReplaceWith { root: 2, m: 2 },
|
||||||
},
|
|
||||||
CreateTextNode {
|
|
||||||
text: "text 1",
|
|
||||||
root: 8,
|
|
||||||
},
|
|
||||||
ReplaceWith { root: 6, m: 2 },
|
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
let edits = dom.hard_diff(ScopeId(0));
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
edits.edits,
|
dom.hard_diff(ScopeId(0)).edits,
|
||||||
[
|
[
|
||||||
CreateElement { tag: "h1", root: 9 },
|
CreateElement { tag: "h1", root: 2 },
|
||||||
ReplaceWith { root: 7, m: 1 },
|
ReplaceWith { root: 1, m: 1 },
|
||||||
Remove { root: 8 },
|
Remove { root: 3 },
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn component_swap() {
|
fn component_swap() {
|
||||||
static App: Component = |cx| {
|
fn app(cx: Scope) -> Element {
|
||||||
let render_phase = cx.use_hook(|_| 0);
|
let render_phase = cx.use_hook(|_| 0);
|
||||||
*render_phase += 1;
|
*render_phase += 1;
|
||||||
|
|
||||||
@@ -296,7 +253,7 @@ fn component_swap() {
|
|||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut dom = VirtualDom::new(App);
|
let mut dom = VirtualDom::new(app);
|
||||||
let edits = dom.rebuild();
|
let edits = dom.rebuild();
|
||||||
dbg!(&edits);
|
dbg!(&edits);
|
||||||
|
|
||||||
|
|||||||
@@ -152,12 +152,7 @@ fn free_works_on_root_props() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut dom = new_dom(
|
let mut dom = new_dom(app, Custom { val: String::from("asd") });
|
||||||
app,
|
|
||||||
Custom {
|
|
||||||
val: String::from("asd"),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
dom.rebuild();
|
dom.rebuild();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -313,3 +308,123 @@ fn leak_thru_children() {
|
|||||||
dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
|
dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
|
||||||
dom.work_with_deadline(|| false);
|
dom.work_with_deadline(|| false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pass_thru() {
|
||||||
|
#[inline_props]
|
||||||
|
fn Router<'a>(cx: Scope, children: Element<'a>) -> Element {
|
||||||
|
cx.render(rsx! {
|
||||||
|
div {
|
||||||
|
&cx.props.children
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn NavContainer<'a>(cx: Scope, children: Element<'a>) -> Element {
|
||||||
|
cx.render(rsx! {
|
||||||
|
header {
|
||||||
|
nav {
|
||||||
|
&cx.props.children
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn NavMenu(cx: Scope) -> Element {
|
||||||
|
rsx!(cx,
|
||||||
|
NavBrand {}
|
||||||
|
div {
|
||||||
|
NavStart {}
|
||||||
|
NavEnd {}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn NavBrand(cx: Scope) -> Element {
|
||||||
|
rsx!(cx, div {})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn NavStart(cx: Scope) -> Element {
|
||||||
|
rsx!(cx, div {})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn NavEnd(cx: Scope) -> Element {
|
||||||
|
rsx!(cx, div {})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn MainContainer<'a>(
|
||||||
|
cx: Scope,
|
||||||
|
nav: Element<'a>,
|
||||||
|
body: Element<'a>,
|
||||||
|
footer: Element<'a>,
|
||||||
|
) -> Element {
|
||||||
|
cx.render(rsx! {
|
||||||
|
div {
|
||||||
|
class: "columns is-mobile",
|
||||||
|
div {
|
||||||
|
class: "column is-full",
|
||||||
|
&cx.props.nav,
|
||||||
|
&cx.props.body,
|
||||||
|
&cx.props.footer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app(cx: Scope) -> Element {
|
||||||
|
let nav = cx.render(rsx! {
|
||||||
|
NavContainer {
|
||||||
|
NavMenu {}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let body = cx.render(rsx! {
|
||||||
|
div {}
|
||||||
|
});
|
||||||
|
let footer = cx.render(rsx! {
|
||||||
|
div {}
|
||||||
|
});
|
||||||
|
|
||||||
|
cx.render(rsx! {
|
||||||
|
MainContainer {
|
||||||
|
nav: nav,
|
||||||
|
body: body,
|
||||||
|
footer: footer,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut dom = new_dom(app, ());
|
||||||
|
let _ = dom.rebuild();
|
||||||
|
|
||||||
|
for x in 0..40 {
|
||||||
|
dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
|
||||||
|
dom.work_with_deadline(|| false);
|
||||||
|
dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
|
||||||
|
dom.work_with_deadline(|| false);
|
||||||
|
dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
|
||||||
|
dom.work_with_deadline(|| false);
|
||||||
|
|
||||||
|
dom.handle_message(SchedulerMsg::Immediate(ScopeId(1)));
|
||||||
|
dom.work_with_deadline(|| false);
|
||||||
|
dom.handle_message(SchedulerMsg::Immediate(ScopeId(1)));
|
||||||
|
dom.work_with_deadline(|| false);
|
||||||
|
dom.handle_message(SchedulerMsg::Immediate(ScopeId(1)));
|
||||||
|
dom.work_with_deadline(|| false);
|
||||||
|
|
||||||
|
dom.handle_message(SchedulerMsg::Immediate(ScopeId(2)));
|
||||||
|
dom.work_with_deadline(|| false);
|
||||||
|
dom.handle_message(SchedulerMsg::Immediate(ScopeId(2)));
|
||||||
|
dom.work_with_deadline(|| false);
|
||||||
|
dom.handle_message(SchedulerMsg::Immediate(ScopeId(2)));
|
||||||
|
dom.work_with_deadline(|| false);
|
||||||
|
|
||||||
|
dom.handle_message(SchedulerMsg::Immediate(ScopeId(3)));
|
||||||
|
dom.work_with_deadline(|| false);
|
||||||
|
dom.handle_message(SchedulerMsg::Immediate(ScopeId(3)));
|
||||||
|
dom.work_with_deadline(|| false);
|
||||||
|
dom.handle_message(SchedulerMsg::Immediate(ScopeId(3)));
|
||||||
|
dom.work_with_deadline(|| false);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
#![allow(unused, non_upper_case_globals)]
|
||||||
|
|
||||||
|
//! Diffing Tests
|
||||||
|
//!
|
||||||
|
//! These tests only verify that the diffing algorithm works properly for single components.
|
||||||
|
//!
|
||||||
|
//! It does not validated that component lifecycles work properly. This is done in another test file.
|
||||||
|
|
||||||
|
use dioxus::{prelude::*, DomEdit, ScopeId};
|
||||||
|
use dioxus_core as dioxus;
|
||||||
|
use dioxus_core_macro::*;
|
||||||
|
use dioxus_html as dioxus_elements;
|
||||||
|
|
||||||
|
mod test_logging;
|
||||||
|
|
||||||
|
fn new_dom() -> VirtualDom {
|
||||||
|
const IS_LOGGING_ENABLED: bool = false;
|
||||||
|
test_logging::set_up_logging(IS_LOGGING_ENABLED);
|
||||||
|
VirtualDom::new(|cx| rsx!(cx, "hi"))
|
||||||
|
}
|
||||||
|
|
||||||
|
use DomEdit::*;
|
||||||
|
|
||||||
|
/// Should push the text node onto the stack and modify it
|
||||||
|
#[test]
|
||||||
|
fn nested_passthru_creates() {
|
||||||
|
fn app(cx: Scope) -> Element {
|
||||||
|
cx.render(rsx! {
|
||||||
|
Child {
|
||||||
|
Child {
|
||||||
|
Child {
|
||||||
|
div {
|
||||||
|
"hi"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn Child<'a>(cx: Scope, children: Element<'a>) -> Element {
|
||||||
|
cx.render(rsx! {
|
||||||
|
children
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut dom = VirtualDom::new(app);
|
||||||
|
let mut channel = dom.get_scheduler_channel();
|
||||||
|
assert!(dom.has_work());
|
||||||
|
|
||||||
|
let edits = dom.rebuild();
|
||||||
|
assert_eq!(
|
||||||
|
edits.edits,
|
||||||
|
[
|
||||||
|
CreateElement { tag: "div", root: 1 },
|
||||||
|
CreateTextNode { text: "hi", root: 2 },
|
||||||
|
AppendChildren { many: 1 },
|
||||||
|
AppendChildren { many: 1 },
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Should push the text node onto the stack and modify it
|
||||||
|
#[test]
|
||||||
|
fn nested_passthru_creates_add() {
|
||||||
|
fn app(cx: Scope) -> Element {
|
||||||
|
cx.render(rsx! {
|
||||||
|
Child {
|
||||||
|
"1"
|
||||||
|
Child {
|
||||||
|
"2"
|
||||||
|
Child {
|
||||||
|
"3"
|
||||||
|
div {
|
||||||
|
"hi"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn Child<'a>(cx: Scope, children: Element<'a>) -> Element {
|
||||||
|
cx.render(rsx! {
|
||||||
|
children
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut dom = VirtualDom::new(app);
|
||||||
|
let mut channel = dom.get_scheduler_channel();
|
||||||
|
assert!(dom.has_work());
|
||||||
|
|
||||||
|
let edits = dom.rebuild();
|
||||||
|
assert_eq!(
|
||||||
|
edits.edits,
|
||||||
|
[
|
||||||
|
CreateTextNode { text: "1", root: 1 },
|
||||||
|
CreateTextNode { text: "2", root: 2 },
|
||||||
|
CreateTextNode { text: "3", root: 3 },
|
||||||
|
CreateElement { tag: "div", root: 4 },
|
||||||
|
CreateTextNode { text: "hi", root: 5 },
|
||||||
|
AppendChildren { many: 1 },
|
||||||
|
AppendChildren { many: 4 },
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#![allow(unused, non_upper_case_globals)]
|
#![allow(unused, non_upper_case_globals)]
|
||||||
|
|
||||||
use dioxus::{prelude::*, DomEdit, Mutations};
|
use dioxus::{prelude::*, DomEdit, Mutations, SchedulerMsg, ScopeId};
|
||||||
use dioxus_core as dioxus;
|
use dioxus_core as dioxus;
|
||||||
use dioxus_core_macro::*;
|
use dioxus_core_macro::*;
|
||||||
use dioxus_html as dioxus_elements;
|
use dioxus_html as dioxus_elements;
|
||||||
@@ -29,11 +29,102 @@ fn shared_state_test() {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
edits,
|
edits,
|
||||||
[
|
[
|
||||||
CreateTextNode {
|
CreateTextNode { root: 1, text: "Hello, world!" },
|
||||||
root: 1,
|
|
||||||
text: "Hello, world!"
|
|
||||||
},
|
|
||||||
AppendChildren { many: 1 },
|
AppendChildren { many: 1 },
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn swap_test() {
|
||||||
|
struct MySharedState(&'static str);
|
||||||
|
|
||||||
|
fn app(cx: Scope) -> Element {
|
||||||
|
let val = cx.use_hook(|_| 0);
|
||||||
|
*val += 1;
|
||||||
|
|
||||||
|
cx.provide_context(MySharedState("world!"));
|
||||||
|
|
||||||
|
let child = match *val % 2 {
|
||||||
|
0 => rsx!(
|
||||||
|
Child1 {
|
||||||
|
Child1 { }
|
||||||
|
Child2 { }
|
||||||
|
}
|
||||||
|
),
|
||||||
|
_ => rsx!(
|
||||||
|
Child2 {
|
||||||
|
Child2 { }
|
||||||
|
Child2 { }
|
||||||
|
}
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
cx.render(rsx!(
|
||||||
|
Router {
|
||||||
|
div { child }
|
||||||
|
}
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn Router<'a>(cx: Scope, children: Element<'a>) -> Element<'a> {
|
||||||
|
cx.render(rsx!(div { children }))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn Child1<'a>(cx: Scope, children: Element<'a>) -> Element {
|
||||||
|
let shared = cx.consume_context::<MySharedState>().unwrap();
|
||||||
|
println!("Child1: {}", shared.0);
|
||||||
|
cx.render(rsx! {
|
||||||
|
div {
|
||||||
|
"{shared.0}",
|
||||||
|
children
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn Child2<'a>(cx: Scope, children: Element<'a>) -> Element {
|
||||||
|
let shared = cx.consume_context::<MySharedState>().unwrap();
|
||||||
|
println!("Child2: {}", shared.0);
|
||||||
|
cx.render(rsx! {
|
||||||
|
h1 {
|
||||||
|
"{shared.0}",
|
||||||
|
children
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut dom = VirtualDom::new(app);
|
||||||
|
let Mutations { edits, .. } = dom.rebuild();
|
||||||
|
|
||||||
|
dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
|
||||||
|
dom.work_with_deadline(|| false);
|
||||||
|
dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
|
||||||
|
dom.work_with_deadline(|| false);
|
||||||
|
dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
|
||||||
|
dom.work_with_deadline(|| false);
|
||||||
|
dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
|
||||||
|
dom.work_with_deadline(|| false);
|
||||||
|
dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
|
||||||
|
dom.work_with_deadline(|| false);
|
||||||
|
dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
|
||||||
|
dom.work_with_deadline(|| false);
|
||||||
|
|
||||||
|
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(1)));
|
||||||
|
// dom.work_with_deadline(|| false);
|
||||||
|
|
||||||
|
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(2)));
|
||||||
|
// dom.work_with_deadline(|| false);
|
||||||
|
|
||||||
|
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(3)));
|
||||||
|
// dom.work_with_deadline(|| false);
|
||||||
|
|
||||||
|
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
|
||||||
|
// dom.work_with_deadline(|| false);
|
||||||
|
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
|
||||||
|
// dom.work_with_deadline(|| false);
|
||||||
|
// dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
|
||||||
|
// dom.work_with_deadline(|| false);
|
||||||
|
}
|
||||||
@@ -68,15 +68,9 @@ fn conditional_rendering() {
|
|||||||
mutations.edits,
|
mutations.edits,
|
||||||
[
|
[
|
||||||
CreateElement { root: 1, tag: "h1" },
|
CreateElement { root: 1, tag: "h1" },
|
||||||
CreateTextNode {
|
CreateTextNode { root: 2, text: "hello" },
|
||||||
root: 2,
|
|
||||||
text: "hello"
|
|
||||||
},
|
|
||||||
AppendChildren { many: 1 },
|
AppendChildren { many: 1 },
|
||||||
CreateElement {
|
CreateElement { root: 3, tag: "span" },
|
||||||
root: 3,
|
|
||||||
tag: "span"
|
|
||||||
},
|
|
||||||
CreateTextNode { root: 4, text: "a" },
|
CreateTextNode { root: 4, text: "a" },
|
||||||
AppendChildren { many: 1 },
|
AppendChildren { many: 1 },
|
||||||
CreatePlaceholder { root: 5 },
|
CreatePlaceholder { root: 5 },
|
||||||
|
|||||||
@@ -5,6 +5,76 @@ 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/),
|
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).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- <csr-id-a239d2ba6ac7f1f3d09de16c022ce8ca52cf0f63/> fix web doc example and use &mut for builders everywhere
|
||||||
|
|
||||||
|
### New Features
|
||||||
|
|
||||||
|
- <csr-id-430cde7068d308f2783e33d278fd0c0efa659c1b/> default asset server
|
||||||
|
- <csr-id-95e93ed0bcf6c69990f4cf3c6448b2bf5da96c36/> remove dioxus id on non-event elements
|
||||||
|
- <csr-id-eb138848ec7a8978f0ed7c717374684d2315dc03/> also hide placeholder node
|
||||||
|
- <csr-id-05331ddd8033f6997d4916179b62f4d62f832988/> wire up both desktop and web
|
||||||
|
- <csr-id-8f4aa84f1a4f2443b34d81ee42490564e168de53/> bool attr white list
|
||||||
|
- <csr-id-5bf6c96f9fed04de949403202bafcbeadb5d2030/> setup a typescript build
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- <csr-id-22308eb26a9ea48b14f5f5abb833aa90a4e3fc40/> custom protocol receiver type
|
||||||
|
- <csr-id-6bc45b1c5064a4e2b04d452c52a8167ad179691e/> clippy
|
||||||
|
- <csr-id-bad36162af764291f5a031b6233d151f61d745a4/> wry pathing
|
||||||
|
- <csr-id-be614e6535e6e13e6ff93e9c6a171c1c002e6b01/> cursor jumping and use set instead of lsit
|
||||||
|
- <csr-id-92561612c727e73356d7d36e16af39aacf02a56d/> format code
|
||||||
|
- <csr-id-2073b400df55f0c6d8bed7371b2313be6c064e6e/> check `href` null
|
||||||
|
- <csr-id-21232285d9d84168d9003969ddd254fc22951e4b/> add exclusion list
|
||||||
|
- <csr-id-327f9015481809d8e5b9e69f26202e8d66dd198e/> check `href` null
|
||||||
|
- <csr-id-8089023a6c3a54957af9c9c05c9dee6088b059ef/> prevent `submit` default
|
||||||
|
|
||||||
|
### Commit Statistics
|
||||||
|
|
||||||
|
<csr-read-only-do-not-edit/>
|
||||||
|
|
||||||
|
- 26 commits contributed to the release over the course of 11 calendar days.
|
||||||
|
- 17 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**
|
||||||
|
- custom protocol receiver type ([`22308eb`](https://github.comgit//DioxusLabs/dioxus/commit/22308eb26a9ea48b14f5f5abb833aa90a4e3fc40))
|
||||||
|
- default asset server ([`430cde7`](https://github.comgit//DioxusLabs/dioxus/commit/430cde7068d308f2783e33d278fd0c0efa659c1b))
|
||||||
|
- fix web doc example and use &mut for builders everywhere ([`a239d2b`](https://github.comgit//DioxusLabs/dioxus/commit/a239d2ba6ac7f1f3d09de16c022ce8ca52cf0f63))
|
||||||
|
- Merge pull request #111 from DioxusLabs/jk/props-attrs ([`0369fe7`](https://github.comgit//DioxusLabs/dioxus/commit/0369fe72fb247409da300a54ef11ba9155d0efb3))
|
||||||
|
- clippy ([`6bc45b1`](https://github.comgit//DioxusLabs/dioxus/commit/6bc45b1c5064a4e2b04d452c52a8167ad179691e))
|
||||||
|
- Merge pull request #113 from DioxusLabs/jk/desktop-cursor-jump ([`20a2940`](https://github.comgit//DioxusLabs/dioxus/commit/20a29409b22510b001fdbee349724adb7b44d401))
|
||||||
|
- wry pathing ([`bad3616`](https://github.comgit//DioxusLabs/dioxus/commit/bad36162af764291f5a031b6233d151f61d745a4))
|
||||||
|
- remove dioxus id on non-event elements ([`95e93ed`](https://github.comgit//DioxusLabs/dioxus/commit/95e93ed0bcf6c69990f4cf3c6448b2bf5da96c36))
|
||||||
|
- also hide placeholder node ([`eb13884`](https://github.comgit//DioxusLabs/dioxus/commit/eb138848ec7a8978f0ed7c717374684d2315dc03))
|
||||||
|
- drag and drop support ([`9ae981a`](https://github.comgit//DioxusLabs/dioxus/commit/9ae981a1af4b5474ce16e27e070794d59128c12a))
|
||||||
|
- feat(events:focus): add missing `onfocusin` event ([`007d06d`](https://github.comgit//DioxusLabs/dioxus/commit/007d06d602f1adfaa51c87ec89b2afe90d8cdef9))
|
||||||
|
- cursor jumping and use set instead of lsit ([`be614e6`](https://github.comgit//DioxusLabs/dioxus/commit/be614e6535e6e13e6ff93e9c6a171c1c002e6b01))
|
||||||
|
- Merge pull request #108 from DioxusLabs/jk/fstring-component-fields ([`f4132d1`](https://github.comgit//DioxusLabs/dioxus/commit/f4132d1874f7495049fac23ba0a022ac137ad74f))
|
||||||
|
- feat(example:todomvc): add editing support ([`9849f68`](https://github.comgit//DioxusLabs/dioxus/commit/9849f68f257200fac511c048bfb1a076243b86d3))
|
||||||
|
- Merge pull request #101 from alexkirsz/ci ([`29bf424`](https://github.comgit//DioxusLabs/dioxus/commit/29bf424b0976b95ff645bb128d0e758cf0186614))
|
||||||
|
- Merge pull request #139 from DioxusLabs/jk/provide-context-any ([`70f2ef4`](https://github.comgit//DioxusLabs/dioxus/commit/70f2ef43db5b6737bd9bcbfc1aa21c834ce4b395))
|
||||||
|
- Merge branch 'master' into jk/unify ([`824defa`](https://github.comgit//DioxusLabs/dioxus/commit/824defa2dbcc16d66588b3976699d89b65a8a068))
|
||||||
|
- wire up both desktop and web ([`05331dd`](https://github.comgit//DioxusLabs/dioxus/commit/05331ddd8033f6997d4916179b62f4d62f832988))
|
||||||
|
- format code ([`9256161`](https://github.comgit//DioxusLabs/dioxus/commit/92561612c727e73356d7d36e16af39aacf02a56d))
|
||||||
|
- Enable clippy ([`b6903bf`](https://github.comgit//DioxusLabs/dioxus/commit/b6903bf558bc7a3d0fe6794a137c44fca0957d11))
|
||||||
|
- bool attr white list ([`8f4aa84`](https://github.comgit//DioxusLabs/dioxus/commit/8f4aa84f1a4f2443b34d81ee42490564e168de53))
|
||||||
|
- setup a typescript build ([`5bf6c96`](https://github.comgit//DioxusLabs/dioxus/commit/5bf6c96f9fed04de949403202bafcbeadb5d2030))
|
||||||
|
- check `href` null ([`2073b40`](https://github.comgit//DioxusLabs/dioxus/commit/2073b400df55f0c6d8bed7371b2313be6c064e6e))
|
||||||
|
- add exclusion list ([`2123228`](https://github.comgit//DioxusLabs/dioxus/commit/21232285d9d84168d9003969ddd254fc22951e4b))
|
||||||
|
- check `href` null ([`327f901`](https://github.comgit//DioxusLabs/dioxus/commit/327f9015481809d8e5b9e69f26202e8d66dd198e))
|
||||||
|
- prevent `submit` default ([`8089023`](https://github.comgit//DioxusLabs/dioxus/commit/8089023a6c3a54957af9c9c05c9dee6088b059ef))
|
||||||
|
</details>
|
||||||
|
|
||||||
## v0.1.5 (2022-01-08)
|
## v0.1.5 (2022-01-08)
|
||||||
|
|
||||||
### Documentation
|
### Documentation
|
||||||
@@ -37,6 +107,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- <csr-id-f782e142118fb7acf1b88a0f3fbb03e4a5e3e91e/> omg what a dumb mistake
|
- <csr-id-f782e142118fb7acf1b88a0f3fbb03e4a5e3e91e/> omg what a dumb mistake
|
||||||
- <csr-id-4a0068f09918adbc299150edcf777f342ced0dd3/> bless up, no more segfaults
|
- <csr-id-4a0068f09918adbc299150edcf777f342ced0dd3/> bless up, no more segfaults
|
||||||
- <csr-id-7dfe89c9581f45a445f17f9fe4bb94e61f67e971/> wire up event delegator for webview
|
- <csr-id-7dfe89c9581f45a445f17f9fe4bb94e61f67e971/> wire up event delegator for webview
|
||||||
|
- <csr-id-46fd6ac3450ca5ebf9aecb2d59a5a92b2a68bdd0/> link open in browser
|
||||||
|
- <csr-id-f006f50317f4b75fac353bc988db057a281ba7f8/> move `rpc` to handler
|
||||||
|
- <csr-id-9e04ce5342850d2e0a01dde169807d6f6eb16566/> `open_browser` bool attribute
|
||||||
|
- <csr-id-c737c424b05ad8453e8770a14a0d210fb0c7c2fe/> link open in browser
|
||||||
|
- <csr-id-a0f60152bc7e5866f114ed469809ce8be70d17d4/> link open in browser
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
@@ -48,13 +123,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- <csr-id-a33f7701fcf5f917fea8719253650b5ad92554fd/> tags
|
- <csr-id-a33f7701fcf5f917fea8719253650b5ad92554fd/> tags
|
||||||
- <csr-id-601078f9cf78a58d7502a377676ac94f3cf037bf/> desktop and mobile
|
- <csr-id-601078f9cf78a58d7502a377676ac94f3cf037bf/> desktop and mobile
|
||||||
- <csr-id-ba9e1dbb8fa24048a6c9ccef8a8722688226a845/> messed up how lifetimes worked, need to render once per component
|
- <csr-id-ba9e1dbb8fa24048a6c9ccef8a8722688226a845/> messed up how lifetimes worked, need to render once per component
|
||||||
|
- <csr-id-62b637f8b0eaf616c49461fa23b9251a79abc147/> error pattern
|
||||||
|
- <csr-id-5233ee97d9314f7f0e0bdf05c56d2a9e4201a596/> format code
|
||||||
|
|
||||||
### Commit Statistics
|
### Commit Statistics
|
||||||
|
|
||||||
<csr-read-only-do-not-edit/>
|
<csr-read-only-do-not-edit/>
|
||||||
|
|
||||||
- 91 commits contributed to the release over the course of 151 calendar days.
|
- 98 commits contributed to the release over the course of 151 calendar days.
|
||||||
- 78 commits where understood as [conventional](https://www.conventionalcommits.org).
|
- 84 commits where understood as [conventional](https://www.conventionalcommits.org).
|
||||||
- 0 issues like '(#ID)' where seen in commit messages
|
- 0 issues like '(#ID)' where seen in commit messages
|
||||||
|
|
||||||
### Commit Details
|
### Commit Details
|
||||||
@@ -64,11 +141,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
<details><summary>view details</summary>
|
<details><summary>view details</summary>
|
||||||
|
|
||||||
* **Uncategorized**
|
* **Uncategorized**
|
||||||
- Release dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`a36dab7`](https://github.comgit//DioxusLabs/dioxus/commit/a36dab7f45920acd8535a69b4aa3695f3bb92111))
|
- Release dioxus-desktop v0.1.5 ([`cd0dcac`](https://github.comgit//DioxusLabs/dioxus/commit/cd0dcacaf2862f26d29acb21d98f75d41b940e3f))
|
||||||
- Release dioxus-core v0.1.7, dioxus-core-macro v0.1.6, dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`40d1f85`](https://github.comgit//DioxusLabs/dioxus/commit/40d1f85d0c3e2c9fd23c08840cca9f459d4e4307))
|
|
||||||
- add exclusion list ([`2123228`](https://github.comgit//DioxusLabs/dioxus/commit/21232285d9d84168d9003969ddd254fc22951e4b))
|
|
||||||
- handle bool attrs properly ([`8d685f4`](https://github.comgit//DioxusLabs/dioxus/commit/8d685f40b7e0ef6521c60310d8687291e9b9c48a))
|
- handle bool attrs properly ([`8d685f4`](https://github.comgit//DioxusLabs/dioxus/commit/8d685f40b7e0ef6521c60310d8687291e9b9c48a))
|
||||||
|
- link open in browser ([`46fd6ac`](https://github.comgit//DioxusLabs/dioxus/commit/46fd6ac3450ca5ebf9aecb2d59a5a92b2a68bdd0))
|
||||||
|
- Release dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`a36dab7`](https://github.comgit//DioxusLabs/dioxus/commit/a36dab7f45920acd8535a69b4aa3695f3bb92111))
|
||||||
|
- error pattern ([`62b637f`](https://github.comgit//DioxusLabs/dioxus/commit/62b637f8b0eaf616c49461fa23b9251a79abc147))
|
||||||
|
- move `rpc` to handler ([`f006f50`](https://github.comgit//DioxusLabs/dioxus/commit/f006f50317f4b75fac353bc988db057a281ba7f8))
|
||||||
|
- `open_browser` bool attribute ([`9e04ce5`](https://github.comgit//DioxusLabs/dioxus/commit/9e04ce5342850d2e0a01dde169807d6f6eb16566))
|
||||||
|
- Release dioxus-core v0.1.7, dioxus-core-macro v0.1.6, dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`40d1f85`](https://github.comgit//DioxusLabs/dioxus/commit/40d1f85d0c3e2c9fd23c08840cca9f459d4e4307))
|
||||||
|
- format code ([`5233ee9`](https://github.comgit//DioxusLabs/dioxus/commit/5233ee97d9314f7f0e0bdf05c56d2a9e4201a596))
|
||||||
|
- link open in browser ([`c737c42`](https://github.comgit//DioxusLabs/dioxus/commit/c737c424b05ad8453e8770a14a0d210fb0c7c2fe))
|
||||||
- Merge pull request #89 from DioxusLabs/jk/simplify-example-run ([`8b6aa8b`](https://github.comgit//DioxusLabs/dioxus/commit/8b6aa8b880b6cb5c95e0c0743aad4e4e74388e05))
|
- Merge pull request #89 from DioxusLabs/jk/simplify-example-run ([`8b6aa8b`](https://github.comgit//DioxusLabs/dioxus/commit/8b6aa8b880b6cb5c95e0c0743aad4e4e74388e05))
|
||||||
|
- link open in browser ([`a0f6015`](https://github.comgit//DioxusLabs/dioxus/commit/a0f60152bc7e5866f114ed469809ce8be70d17d4))
|
||||||
- Merge pull request #74 from mrxiaozhuox/master ([`47056fd`](https://github.comgit//DioxusLabs/dioxus/commit/47056fda4577bcbdaa2a6f63d82eec876e5a5aee))
|
- Merge pull request #74 from mrxiaozhuox/master ([`47056fd`](https://github.comgit//DioxusLabs/dioxus/commit/47056fda4577bcbdaa2a6f63d82eec876e5a5aee))
|
||||||
- Merge pull request #80 from DioxusLabs/jk/router2dotoh ([`cdc2d8e`](https://github.comgit//DioxusLabs/dioxus/commit/cdc2d8ec6d123245c2ea5f6d10af02b6a6833994))
|
- Merge pull request #80 from DioxusLabs/jk/router2dotoh ([`cdc2d8e`](https://github.comgit//DioxusLabs/dioxus/commit/cdc2d8ec6d123245c2ea5f6d10af02b6a6833994))
|
||||||
- clear warnigns ([`175a6a1`](https://github.comgit//DioxusLabs/dioxus/commit/175a6a199c6738d8d0c7646ba0ec3fc4406c6535))
|
- clear warnigns ([`175a6a1`](https://github.comgit//DioxusLabs/dioxus/commit/175a6a199c6738d8d0c7646ba0ec3fc4406c6535))
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "dioxus-desktop"
|
name = "dioxus-desktop"
|
||||||
version = "0.1.5"
|
version = "0.1.6"
|
||||||
authors = ["Jonathan Kelley"]
|
authors = ["Jonathan Kelley"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
description = "Dioxus VirtualDOM renderer for a remote webview instance"
|
description = "Dioxus VirtualDOM renderer for a remote webview instance"
|
||||||
@@ -12,7 +12,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
dioxus-core = { path = "../core", version ="^0.1.7", features = ["serialize"] }
|
dioxus-core = { path = "../core", version = "^0.1.9", features = ["serialize"] }
|
||||||
argh = "0.1.4"
|
argh = "0.1.4"
|
||||||
serde = "1.0.120"
|
serde = "1.0.120"
|
||||||
serde_json = "1.0.61"
|
serde_json = "1.0.61"
|
||||||
@@ -27,9 +27,10 @@ tokio = { version = "1.12.0", features = [
|
|||||||
"rt",
|
"rt",
|
||||||
"time",
|
"time",
|
||||||
], optional = true, default-features = false }
|
], optional = true, default-features = false }
|
||||||
dioxus-core-macro = { path = "../core-macro", version ="^0.1.6"}
|
dioxus-core-macro = { path = "../core-macro", version = "^0.1.7" }
|
||||||
dioxus-html = { path = "../html", features = ["serialize"], version ="^0.1.4"}
|
dioxus-html = { path = "../html", features = ["serialize"], version = "^0.1.6" }
|
||||||
webbrowser = "0.5.5"
|
webbrowser = "0.5.5"
|
||||||
|
mime_guess = "2.0.3"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["tokio_runtime"]
|
default = ["tokio_runtime"]
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ impl DesktopConfig {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_custom_protocol<F>(mut self, name: String, handler: F) -> Self
|
pub fn with_custom_protocol<F>(&mut self, name: String, handler: F) -> &mut Self
|
||||||
where
|
where
|
||||||
F: Fn(&HttpRequest) -> WryResult<HttpResponse> + 'static,
|
F: Fn(&HttpRequest) -> WryResult<HttpResponse> + 'static,
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,100 +1,67 @@
|
|||||||
use std::cell::RefCell;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use dioxus::prelude::Scope;
|
use dioxus_core::ScopeState;
|
||||||
use dioxus_core as dioxus;
|
use wry::application::event_loop::EventLoopProxy;
|
||||||
use dioxus_core::{Context, Element, LazyNodes, NodeFactory, Properties};
|
|
||||||
use dioxus_core_macro::Props;
|
|
||||||
|
|
||||||
/*
|
use crate::UserWindowEvent;
|
||||||
This module provides a set of Dioxus components to easily manage windows, tabs, etc.
|
|
||||||
|
|
||||||
Windows can be created anywhere in the tree, making them very flexible for things like modals, etc.
|
type ProxyType = EventLoopProxy<UserWindowEvent>;
|
||||||
|
|
||||||
*/
|
/// Desktop-Window handle api context
|
||||||
pub struct DesktopContext {}
|
///
|
||||||
|
/// you can use this context control some window event
|
||||||
|
///
|
||||||
|
/// you can use `cx.consume_context::<DesktopContext>` to get this context
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// let desktop = cx.consume_context::<DesktopContext>().unwrap();
|
||||||
|
/// ```
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct DesktopContext {
|
||||||
|
proxy: ProxyType,
|
||||||
|
}
|
||||||
|
|
||||||
impl DesktopContext {
|
impl DesktopContext {
|
||||||
fn add_window(&mut self) {
|
pub(crate) fn new(proxy: ProxyType) -> Self {
|
||||||
//
|
Self { proxy }
|
||||||
}
|
}
|
||||||
fn close_window(&mut self) {
|
|
||||||
//
|
/// trigger the drag-window event
|
||||||
|
///
|
||||||
|
/// Moves the window with the left mouse button until the button is released.
|
||||||
|
///
|
||||||
|
/// you need use it in `onmousedown` event:
|
||||||
|
/// ```rust
|
||||||
|
/// onmousedown: move |_| { desktop.drag_window(); }
|
||||||
|
/// ```
|
||||||
|
pub fn drag(&self) {
|
||||||
|
let _ = self.proxy.send_event(UserWindowEvent::DragWindow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// set window minimize state
|
||||||
|
pub fn minimize(&self, minimized: bool) {
|
||||||
|
let _ = self.proxy.send_event(UserWindowEvent::Minimize(minimized));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// set window maximize state
|
||||||
|
pub fn maximize(&self, maximized: bool) {
|
||||||
|
let _ = self.proxy.send_event(UserWindowEvent::Maximize(maximized));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// close window
|
||||||
|
pub fn close(&self) {
|
||||||
|
let _ = self.proxy.send_event(UserWindowEvent::CloseWindow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// set window to focus
|
||||||
|
pub fn focus(&self) {
|
||||||
|
let _ = self.proxy.send_event(UserWindowEvent::FocusWindow);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum WindowHandlers {
|
/// use this function can get the `DesktopContext` context.
|
||||||
Resized(Box<dyn Fn()>),
|
pub fn use_window(cx: &ScopeState) -> &Rc<DesktopContext> {
|
||||||
Moved(Box<dyn Fn()>),
|
cx.use_hook(|_| cx.consume_context::<DesktopContext>())
|
||||||
CloseRequested(Box<dyn Fn()>),
|
.as_ref()
|
||||||
Destroyed(Box<dyn Fn()>),
|
.unwrap()
|
||||||
DroppedFile(Box<dyn Fn()>),
|
|
||||||
HoveredFile(Box<dyn Fn()>),
|
|
||||||
HoverFileCancelled(Box<dyn Fn()>),
|
|
||||||
ReceivedTimeText(Box<dyn Fn()>),
|
|
||||||
Focused(Box<dyn Fn()>),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Props)]
|
|
||||||
pub struct WebviewWindowProps<'a> {
|
|
||||||
onclose: &'a dyn FnMut(()),
|
|
||||||
|
|
||||||
onopen: &'a dyn FnMut(()),
|
|
||||||
|
|
||||||
/// focuse me
|
|
||||||
onfocused: &'a dyn FnMut(()),
|
|
||||||
|
|
||||||
children: Element,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A handle to a
|
|
||||||
///
|
|
||||||
///
|
|
||||||
///
|
|
||||||
///
|
|
||||||
///
|
|
||||||
///
|
|
||||||
///
|
|
||||||
///
|
|
||||||
///
|
|
||||||
pub fn WebviewWindow(cx: Scope<WebviewWindowProps>) -> Element {
|
|
||||||
let dtcx = cx.consume_state::<RefCell<DesktopContext>>()?;
|
|
||||||
|
|
||||||
cx.use_hook(|_| {});
|
|
||||||
|
|
||||||
// render the children directly
|
|
||||||
todo!()
|
|
||||||
// cx.render(LazyNodes::new(move |f: NodeFactory| {
|
|
||||||
// f.fragment_from_iter(cx.children())
|
|
||||||
// }))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct WindowHandle {}
|
|
||||||
|
|
||||||
/// Get a handle to the current window from inside a component
|
|
||||||
pub fn use_current_window(cx: Scope) -> Option<WindowHandle> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn syntax_works() {
|
|
||||||
use dioxus_core as dioxus;
|
|
||||||
use dioxus_core::prelude::*;
|
|
||||||
use dioxus_core_macro::*;
|
|
||||||
use dioxus_hooks::*;
|
|
||||||
use dioxus_html as dioxus_elements;
|
|
||||||
|
|
||||||
static App: Component = |cx| {
|
|
||||||
cx.render(rsx! {
|
|
||||||
// left window
|
|
||||||
WebviewWindow {
|
|
||||||
onclose: move |evt| {}
|
|
||||||
onopen: move |evt| {}
|
|
||||||
onfocused: move |evt| {}
|
|
||||||
|
|
||||||
div {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,550 @@
|
|||||||
|
export function main() {
|
||||||
|
let root = window.document.getElementById("main");
|
||||||
|
if (root != null) {
|
||||||
|
window.interpreter = new Interpreter(root);
|
||||||
|
window.rpc.call("initialize");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class Interpreter {
|
||||||
|
root;
|
||||||
|
stack;
|
||||||
|
listeners;
|
||||||
|
handlers;
|
||||||
|
lastNodeWasText;
|
||||||
|
nodes;
|
||||||
|
constructor(root) {
|
||||||
|
this.root = root;
|
||||||
|
this.stack = [root];
|
||||||
|
this.listeners = {};
|
||||||
|
this.handlers = {};
|
||||||
|
this.lastNodeWasText = false;
|
||||||
|
this.nodes = [root];
|
||||||
|
}
|
||||||
|
top() {
|
||||||
|
return this.stack[this.stack.length - 1];
|
||||||
|
}
|
||||||
|
pop() {
|
||||||
|
return this.stack.pop();
|
||||||
|
}
|
||||||
|
PushRoot(root) {
|
||||||
|
const node = this.nodes[root];
|
||||||
|
this.stack.push(node);
|
||||||
|
}
|
||||||
|
AppendChildren(many) {
|
||||||
|
let root = this.stack[this.stack.length - (1 + many)];
|
||||||
|
let to_add = this.stack.splice(this.stack.length - many);
|
||||||
|
for (let i = 0; i < many; i++) {
|
||||||
|
root.appendChild(to_add[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ReplaceWith(root_id, m) {
|
||||||
|
let root = this.nodes[root_id];
|
||||||
|
let els = this.stack.splice(this.stack.length - m);
|
||||||
|
root.replaceWith(...els);
|
||||||
|
}
|
||||||
|
InsertAfter(root, n) {
|
||||||
|
let old = this.nodes[root];
|
||||||
|
let new_nodes = this.stack.splice(this.stack.length - n);
|
||||||
|
old.after(...new_nodes);
|
||||||
|
}
|
||||||
|
InsertBefore(root, n) {
|
||||||
|
let old = this.nodes[root];
|
||||||
|
let new_nodes = this.stack.splice(this.stack.length - n);
|
||||||
|
old.before(...new_nodes);
|
||||||
|
}
|
||||||
|
Remove(root) {
|
||||||
|
let node = this.nodes[root];
|
||||||
|
if (node !== undefined) {
|
||||||
|
node.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CreateTextNode(text, root) {
|
||||||
|
// todo: make it so the types are okay
|
||||||
|
const node = document.createTextNode(text);
|
||||||
|
this.nodes[root] = node;
|
||||||
|
this.stack.push(node);
|
||||||
|
}
|
||||||
|
CreateElement(tag, root) {
|
||||||
|
const el = document.createElement(tag);
|
||||||
|
// el.setAttribute("data-dioxus-id", `${root}`);
|
||||||
|
this.nodes[root] = el;
|
||||||
|
this.stack.push(el);
|
||||||
|
}
|
||||||
|
CreateElementNs(tag, root, ns) {
|
||||||
|
let el = document.createElementNS(ns, tag);
|
||||||
|
this.stack.push(el);
|
||||||
|
this.nodes[root] = el;
|
||||||
|
}
|
||||||
|
CreatePlaceholder(root) {
|
||||||
|
let el = document.createElement("pre");
|
||||||
|
el.hidden = true;
|
||||||
|
this.stack.push(el);
|
||||||
|
this.nodes[root] = el;
|
||||||
|
}
|
||||||
|
NewEventListener(event_name, root, handler) {
|
||||||
|
const element = this.nodes[root];
|
||||||
|
element.setAttribute("data-dioxus-id", `${root}`);
|
||||||
|
if (this.listeners[event_name] === undefined) {
|
||||||
|
this.listeners[event_name] = 0;
|
||||||
|
this.handlers[event_name] = handler;
|
||||||
|
this.root.addEventListener(event_name, handler);
|
||||||
|
} else {
|
||||||
|
this.listeners[event_name]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RemoveEventListener(root, event_name) {
|
||||||
|
const element = this.nodes[root];
|
||||||
|
element.removeAttribute(`data-dioxus-id`);
|
||||||
|
this.listeners[event_name]--;
|
||||||
|
if (this.listeners[event_name] === 0) {
|
||||||
|
this.root.removeEventListener(event_name, this.handlers[event_name]);
|
||||||
|
delete this.listeners[event_name];
|
||||||
|
delete this.handlers[event_name];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SetText(root, text) {
|
||||||
|
this.nodes[root].textContent = text;
|
||||||
|
}
|
||||||
|
SetAttribute(root, field, value, ns) {
|
||||||
|
const name = field;
|
||||||
|
const node = this.nodes[root];
|
||||||
|
if (ns == "style") {
|
||||||
|
// @ts-ignore
|
||||||
|
node.style[name] = value;
|
||||||
|
} else if (ns != null || ns != undefined) {
|
||||||
|
node.setAttributeNS(ns, name, value);
|
||||||
|
} else {
|
||||||
|
switch (name) {
|
||||||
|
case "value":
|
||||||
|
if (value != node.value) {
|
||||||
|
node.value = value;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "checked":
|
||||||
|
node.checked = value === "true";
|
||||||
|
break;
|
||||||
|
case "selected":
|
||||||
|
node.selected = value === "true";
|
||||||
|
break;
|
||||||
|
case "dangerous_inner_html":
|
||||||
|
node.innerHTML = value;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// https://github.com/facebook/react/blob/8b88ac2592c5f555f315f9440cbb665dd1e7457a/packages/react-dom/src/shared/DOMProperty.js#L352-L364
|
||||||
|
if (value == "false" && bool_attrs.hasOwnProperty(name)) {
|
||||||
|
node.removeAttribute(name);
|
||||||
|
} else {
|
||||||
|
node.setAttribute(name, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RemoveAttribute(root, name) {
|
||||||
|
const node = this.nodes[root];
|
||||||
|
node.removeAttribute(name);
|
||||||
|
if (name === "value") {
|
||||||
|
node.value = "";
|
||||||
|
}
|
||||||
|
if (name === "checked") {
|
||||||
|
node.checked = false;
|
||||||
|
}
|
||||||
|
if (name === "selected") {
|
||||||
|
node.selected = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handleEdits(edits) {
|
||||||
|
this.stack.push(this.root);
|
||||||
|
for (let edit of edits) {
|
||||||
|
this.handleEdit(edit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handleEdit(edit) {
|
||||||
|
switch (edit.type) {
|
||||||
|
case "PushRoot":
|
||||||
|
this.PushRoot(edit.root);
|
||||||
|
break;
|
||||||
|
case "AppendChildren":
|
||||||
|
this.AppendChildren(edit.many);
|
||||||
|
break;
|
||||||
|
case "ReplaceWith":
|
||||||
|
this.ReplaceWith(edit.root, edit.m);
|
||||||
|
break;
|
||||||
|
case "InsertAfter":
|
||||||
|
this.InsertAfter(edit.root, edit.n);
|
||||||
|
break;
|
||||||
|
case "InsertBefore":
|
||||||
|
this.InsertBefore(edit.root, edit.n);
|
||||||
|
break;
|
||||||
|
case "Remove":
|
||||||
|
this.Remove(edit.root);
|
||||||
|
break;
|
||||||
|
case "CreateTextNode":
|
||||||
|
this.CreateTextNode(edit.text, edit.root);
|
||||||
|
break;
|
||||||
|
case "CreateElement":
|
||||||
|
this.CreateElement(edit.tag, edit.root);
|
||||||
|
break;
|
||||||
|
case "CreateElementNs":
|
||||||
|
this.CreateElementNs(edit.tag, edit.root, edit.ns);
|
||||||
|
break;
|
||||||
|
case "CreatePlaceholder":
|
||||||
|
this.CreatePlaceholder(edit.root);
|
||||||
|
break;
|
||||||
|
case "RemoveEventListener":
|
||||||
|
this.RemoveEventListener(edit.root, edit.event_name);
|
||||||
|
break;
|
||||||
|
case "NewEventListener":
|
||||||
|
// this handler is only provided on desktop implementations since this
|
||||||
|
// method is not used by the web implementation
|
||||||
|
let handler = (event) => {
|
||||||
|
let target = event.target;
|
||||||
|
if (target != null) {
|
||||||
|
let realId = target.getAttribute(`data-dioxus-id`);
|
||||||
|
let shouldPreventDefault = target.getAttribute(
|
||||||
|
`dioxus-prevent-default`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (event.type == "click") {
|
||||||
|
event.preventDefault();
|
||||||
|
if (shouldPreventDefault !== `onclick`) {
|
||||||
|
console.log("click", event);
|
||||||
|
console.log("clickeded", event.target);
|
||||||
|
console.log("clickeded", event.target.tagName);
|
||||||
|
if (target.tagName == "A") {
|
||||||
|
const href = target.getAttribute("href");
|
||||||
|
if (href !== "" && href !== null && href !== undefined) {
|
||||||
|
window.rpc.call("browser_open", { href });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// walk the tree to find the real element
|
||||||
|
while (realId == null && target.parentElement != null) {
|
||||||
|
target = target.parentElement;
|
||||||
|
realId = target.getAttribute(`data-dioxus-id`);
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldPreventDefault = target.getAttribute(
|
||||||
|
`dioxus-prevent-default`
|
||||||
|
);
|
||||||
|
|
||||||
|
let contents = serialize_event(event);
|
||||||
|
if (shouldPreventDefault === `on${event.type}`) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
if (event.type == "submit") {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
if (realId == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.rpc.call("user_event", {
|
||||||
|
event: edit.event_name,
|
||||||
|
mounted_dom_id: parseInt(realId),
|
||||||
|
contents: contents,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.NewEventListener(edit.event_name, edit.root, handler);
|
||||||
|
break;
|
||||||
|
case "SetText":
|
||||||
|
this.SetText(edit.root, edit.text);
|
||||||
|
break;
|
||||||
|
case "SetAttribute":
|
||||||
|
this.SetAttribute(edit.root, edit.field, edit.value, edit.ns);
|
||||||
|
break;
|
||||||
|
case "RemoveAttribute":
|
||||||
|
this.RemoveAttribute(edit.root, edit.name);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function serialize_event(event) {
|
||||||
|
switch (event.type) {
|
||||||
|
case "copy":
|
||||||
|
case "cut":
|
||||||
|
case "past": {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
case "compositionend":
|
||||||
|
case "compositionstart":
|
||||||
|
case "compositionupdate": {
|
||||||
|
let { data } = event;
|
||||||
|
return {
|
||||||
|
data,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case "keydown":
|
||||||
|
case "keypress":
|
||||||
|
case "keyup": {
|
||||||
|
let {
|
||||||
|
charCode,
|
||||||
|
key,
|
||||||
|
altKey,
|
||||||
|
ctrlKey,
|
||||||
|
metaKey,
|
||||||
|
keyCode,
|
||||||
|
shiftKey,
|
||||||
|
location,
|
||||||
|
repeat,
|
||||||
|
which,
|
||||||
|
} = event;
|
||||||
|
return {
|
||||||
|
char_code: charCode,
|
||||||
|
key: key,
|
||||||
|
alt_key: altKey,
|
||||||
|
ctrl_key: ctrlKey,
|
||||||
|
meta_key: metaKey,
|
||||||
|
key_code: keyCode,
|
||||||
|
shift_key: shiftKey,
|
||||||
|
location: location,
|
||||||
|
repeat: repeat,
|
||||||
|
which: which,
|
||||||
|
locale: "locale",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case "focus":
|
||||||
|
case "blur": {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
case "change": {
|
||||||
|
let target = event.target;
|
||||||
|
let value;
|
||||||
|
if (target.type === "checkbox" || target.type === "radio") {
|
||||||
|
value = target.checked ? "true" : "false";
|
||||||
|
} else {
|
||||||
|
value = target.value ?? target.textContent;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
value: value,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case "input":
|
||||||
|
case "invalid":
|
||||||
|
case "reset":
|
||||||
|
case "submit": {
|
||||||
|
let target = event.target;
|
||||||
|
let value = target.value ?? target.textContent;
|
||||||
|
if (target.type == "checkbox") {
|
||||||
|
value = target.checked ? "true" : "false";
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
value: value,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case "click":
|
||||||
|
case "contextmenu":
|
||||||
|
case "doubleclick":
|
||||||
|
case "drag":
|
||||||
|
case "dragend":
|
||||||
|
case "dragenter":
|
||||||
|
case "dragexit":
|
||||||
|
case "dragleave":
|
||||||
|
case "dragover":
|
||||||
|
case "dragstart":
|
||||||
|
case "drop":
|
||||||
|
case "mousedown":
|
||||||
|
case "mouseenter":
|
||||||
|
case "mouseleave":
|
||||||
|
case "mousemove":
|
||||||
|
case "mouseout":
|
||||||
|
case "mouseover":
|
||||||
|
case "mouseup": {
|
||||||
|
const {
|
||||||
|
altKey,
|
||||||
|
button,
|
||||||
|
buttons,
|
||||||
|
clientX,
|
||||||
|
clientY,
|
||||||
|
ctrlKey,
|
||||||
|
metaKey,
|
||||||
|
pageX,
|
||||||
|
pageY,
|
||||||
|
screenX,
|
||||||
|
screenY,
|
||||||
|
shiftKey,
|
||||||
|
} = event;
|
||||||
|
return {
|
||||||
|
alt_key: altKey,
|
||||||
|
button: button,
|
||||||
|
buttons: buttons,
|
||||||
|
client_x: clientX,
|
||||||
|
client_y: clientY,
|
||||||
|
ctrl_key: ctrlKey,
|
||||||
|
meta_key: metaKey,
|
||||||
|
page_x: pageX,
|
||||||
|
page_y: pageY,
|
||||||
|
screen_x: screenX,
|
||||||
|
screen_y: screenY,
|
||||||
|
shift_key: shiftKey,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case "pointerdown":
|
||||||
|
case "pointermove":
|
||||||
|
case "pointerup":
|
||||||
|
case "pointercancel":
|
||||||
|
case "gotpointercapture":
|
||||||
|
case "lostpointercapture":
|
||||||
|
case "pointerenter":
|
||||||
|
case "pointerleave":
|
||||||
|
case "pointerover":
|
||||||
|
case "pointerout": {
|
||||||
|
const {
|
||||||
|
altKey,
|
||||||
|
button,
|
||||||
|
buttons,
|
||||||
|
clientX,
|
||||||
|
clientY,
|
||||||
|
ctrlKey,
|
||||||
|
metaKey,
|
||||||
|
pageX,
|
||||||
|
pageY,
|
||||||
|
screenX,
|
||||||
|
screenY,
|
||||||
|
shiftKey,
|
||||||
|
pointerId,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
pressure,
|
||||||
|
tangentialPressure,
|
||||||
|
tiltX,
|
||||||
|
tiltY,
|
||||||
|
twist,
|
||||||
|
pointerType,
|
||||||
|
isPrimary,
|
||||||
|
} = event;
|
||||||
|
return {
|
||||||
|
alt_key: altKey,
|
||||||
|
button: button,
|
||||||
|
buttons: buttons,
|
||||||
|
client_x: clientX,
|
||||||
|
client_y: clientY,
|
||||||
|
ctrl_key: ctrlKey,
|
||||||
|
meta_key: metaKey,
|
||||||
|
page_x: pageX,
|
||||||
|
page_y: pageY,
|
||||||
|
screen_x: screenX,
|
||||||
|
screen_y: screenY,
|
||||||
|
shift_key: shiftKey,
|
||||||
|
pointer_id: pointerId,
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
pressure: pressure,
|
||||||
|
tangential_pressure: tangentialPressure,
|
||||||
|
tilt_x: tiltX,
|
||||||
|
tilt_y: tiltY,
|
||||||
|
twist: twist,
|
||||||
|
pointer_type: pointerType,
|
||||||
|
is_primary: isPrimary,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case "select": {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
case "touchcancel":
|
||||||
|
case "touchend":
|
||||||
|
case "touchmove":
|
||||||
|
case "touchstart": {
|
||||||
|
const { altKey, ctrlKey, metaKey, shiftKey } = event;
|
||||||
|
return {
|
||||||
|
// changed_touches: event.changedTouches,
|
||||||
|
// target_touches: event.targetTouches,
|
||||||
|
// touches: event.touches,
|
||||||
|
alt_key: altKey,
|
||||||
|
ctrl_key: ctrlKey,
|
||||||
|
meta_key: metaKey,
|
||||||
|
shift_key: shiftKey,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case "scroll": {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
case "wheel": {
|
||||||
|
const { deltaX, deltaY, deltaZ, deltaMode } = event;
|
||||||
|
return {
|
||||||
|
delta_x: deltaX,
|
||||||
|
delta_y: deltaY,
|
||||||
|
delta_z: deltaZ,
|
||||||
|
delta_mode: deltaMode,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case "animationstart":
|
||||||
|
case "animationend":
|
||||||
|
case "animationiteration": {
|
||||||
|
const { animationName, elapsedTime, pseudoElement } = event;
|
||||||
|
return {
|
||||||
|
animation_name: animationName,
|
||||||
|
elapsed_time: elapsedTime,
|
||||||
|
pseudo_element: pseudoElement,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case "transitionend": {
|
||||||
|
const { propertyName, elapsedTime, pseudoElement } = event;
|
||||||
|
return {
|
||||||
|
property_name: propertyName,
|
||||||
|
elapsed_time: elapsedTime,
|
||||||
|
pseudo_element: pseudoElement,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case "abort":
|
||||||
|
case "canplay":
|
||||||
|
case "canplaythrough":
|
||||||
|
case "durationchange":
|
||||||
|
case "emptied":
|
||||||
|
case "encrypted":
|
||||||
|
case "ended":
|
||||||
|
case "error":
|
||||||
|
case "loadeddata":
|
||||||
|
case "loadedmetadata":
|
||||||
|
case "loadstart":
|
||||||
|
case "pause":
|
||||||
|
case "play":
|
||||||
|
case "playing":
|
||||||
|
case "progress":
|
||||||
|
case "ratechange":
|
||||||
|
case "seeked":
|
||||||
|
case "seeking":
|
||||||
|
case "stalled":
|
||||||
|
case "suspend":
|
||||||
|
case "timeupdate":
|
||||||
|
case "volumechange":
|
||||||
|
case "waiting": {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
case "toggle": {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const bool_attrs = {
|
||||||
|
allowfullscreen: true,
|
||||||
|
allowpaymentrequest: true,
|
||||||
|
async: true,
|
||||||
|
autofocus: true,
|
||||||
|
autoplay: true,
|
||||||
|
checked: true,
|
||||||
|
controls: true,
|
||||||
|
default: true,
|
||||||
|
defer: true,
|
||||||
|
disabled: true,
|
||||||
|
formnovalidate: true,
|
||||||
|
hidden: true,
|
||||||
|
ismap: true,
|
||||||
|
itemscope: true,
|
||||||
|
loop: true,
|
||||||
|
multiple: true,
|
||||||
|
muted: true,
|
||||||
|
nomodule: true,
|
||||||
|
novalidate: true,
|
||||||
|
open: true,
|
||||||
|
playsinline: true,
|
||||||
|
readonly: true,
|
||||||
|
required: true,
|
||||||
|
reversed: true,
|
||||||
|
selected: true,
|
||||||
|
truespeed: true,
|
||||||
|
};
|
||||||
@@ -51,10 +51,13 @@
|
|||||||
//! Make sure to read the [Dioxus Guide](https://dioxuslabs.com/guide) if you already haven't!
|
//! Make sure to read the [Dioxus Guide](https://dioxuslabs.com/guide) if you already haven't!
|
||||||
|
|
||||||
pub mod cfg;
|
pub mod cfg;
|
||||||
|
pub mod desktop_context;
|
||||||
pub mod escape;
|
pub mod escape;
|
||||||
pub mod events;
|
pub mod events;
|
||||||
|
|
||||||
use cfg::DesktopConfig;
|
use cfg::DesktopConfig;
|
||||||
|
pub use desktop_context::use_window;
|
||||||
|
use desktop_context::DesktopContext;
|
||||||
use dioxus_core::*;
|
use dioxus_core::*;
|
||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, VecDeque},
|
collections::{HashMap, VecDeque},
|
||||||
@@ -149,7 +152,7 @@ pub fn launch_with_props<P: 'static + Send>(
|
|||||||
props: P,
|
props: P,
|
||||||
builder: impl FnOnce(&mut DesktopConfig) -> &mut DesktopConfig,
|
builder: impl FnOnce(&mut DesktopConfig) -> &mut DesktopConfig,
|
||||||
) {
|
) {
|
||||||
let mut cfg = DesktopConfig::new();
|
let mut cfg = DesktopConfig::default();
|
||||||
builder(&mut cfg);
|
builder(&mut cfg);
|
||||||
|
|
||||||
let event_loop = EventLoop::with_user_event();
|
let event_loop = EventLoop::with_user_event();
|
||||||
@@ -188,6 +191,7 @@ pub fn launch_with_props<P: 'static + Send>(
|
|||||||
let _ = proxy.send_event(UserWindowEvent::Update);
|
let _ = proxy.send_event(UserWindowEvent::Update);
|
||||||
}
|
}
|
||||||
"browser_open" => {
|
"browser_open" => {
|
||||||
|
println!("browser_open");
|
||||||
let data = req.params.unwrap();
|
let data = req.params.unwrap();
|
||||||
log::trace!("Open browser: {:?}", data);
|
log::trace!("Open browser: {:?}", data);
|
||||||
if let Some(arr) = data.as_array() {
|
if let Some(arr) = data.as_array() {
|
||||||
@@ -210,18 +214,43 @@ pub fn launch_with_props<P: 'static + Send>(
|
|||||||
// For now, we only serve two pieces of content which get included as bytes into the final binary.
|
// For now, we only serve two pieces of content which get included as bytes into the final binary.
|
||||||
let path = request.uri().replace("dioxus://", "");
|
let path = request.uri().replace("dioxus://", "");
|
||||||
|
|
||||||
if path.trim_end_matches('/') == "index.html" {
|
// all assets shouldbe called from index.html
|
||||||
|
let trimmed = path.trim_start_matches("index.html/");
|
||||||
|
|
||||||
|
if trimmed.is_empty() {
|
||||||
wry::http::ResponseBuilder::new()
|
wry::http::ResponseBuilder::new()
|
||||||
.mimetype("text/html")
|
.mimetype("text/html")
|
||||||
.body(include_bytes!("./index.html").to_vec())
|
.body(include_bytes!("./index.html").to_vec())
|
||||||
} else if path.trim_end_matches('/') == "index.html/index.js" {
|
} else if trimmed == "index.js" {
|
||||||
wry::http::ResponseBuilder::new()
|
wry::http::ResponseBuilder::new()
|
||||||
.mimetype("text/javascript")
|
.mimetype("text/javascript")
|
||||||
.body(include_bytes!("../../jsinterpreter/interpreter.js").to_vec())
|
.body(include_bytes!("./interpreter.js").to_vec())
|
||||||
} else {
|
} else {
|
||||||
wry::http::ResponseBuilder::new()
|
// Read the file content from file path
|
||||||
.status(wry::http::status::StatusCode::NOT_FOUND)
|
use std::fs::read;
|
||||||
.body(format!("Not found: {}", path).as_bytes().to_vec())
|
|
||||||
|
let path_buf = std::path::Path::new(trimmed).canonicalize()?;
|
||||||
|
let cur_path = std::path::Path::new(".").canonicalize()?;
|
||||||
|
|
||||||
|
if !path_buf.starts_with(cur_path) {
|
||||||
|
return wry::http::ResponseBuilder::new()
|
||||||
|
.status(wry::http::status::StatusCode::FORBIDDEN)
|
||||||
|
.body(String::from("Forbidden").into_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
if !path_buf.exists() {
|
||||||
|
return wry::http::ResponseBuilder::new()
|
||||||
|
.status(wry::http::status::StatusCode::NOT_FOUND)
|
||||||
|
.body(String::from("Not Found").into_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mime = mime_guess::from_path(&path_buf).first_or_octet_stream();
|
||||||
|
|
||||||
|
// do not let path searching to go two layers beyond the caller level
|
||||||
|
let data = read(path_buf)?;
|
||||||
|
let meta = format!("{}", mime);
|
||||||
|
|
||||||
|
wry::http::ResponseBuilder::new().mimetype(&meta).body(data)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.with_file_drop_handler(move |window, evet| {
|
.with_file_drop_handler(move |window, evet| {
|
||||||
@@ -257,6 +286,41 @@ pub fn launch_with_props<P: 'static + Send>(
|
|||||||
//
|
//
|
||||||
match _evt {
|
match _evt {
|
||||||
UserWindowEvent::Update => desktop.try_load_ready_webviews(),
|
UserWindowEvent::Update => desktop.try_load_ready_webviews(),
|
||||||
|
UserWindowEvent::DragWindow => {
|
||||||
|
// this loop just run once, because dioxus-desktop is unsupport multi-window.
|
||||||
|
for webview in desktop.webviews.values() {
|
||||||
|
let window = webview.window();
|
||||||
|
// start to drag the window.
|
||||||
|
// if the drag_window have any err. we don't do anything.
|
||||||
|
let _ = window.drag_window();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UserWindowEvent::CloseWindow => {
|
||||||
|
// close window
|
||||||
|
*control_flow = ControlFlow::Exit;
|
||||||
|
}
|
||||||
|
UserWindowEvent::Minimize(state) => {
|
||||||
|
// this loop just run once, because dioxus-desktop is unsupport multi-window.
|
||||||
|
for webview in desktop.webviews.values() {
|
||||||
|
let window = webview.window();
|
||||||
|
// change window minimized state.
|
||||||
|
window.set_minimized(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UserWindowEvent::Maximize(state) => {
|
||||||
|
// this loop just run once, because dioxus-desktop is unsupport multi-window.
|
||||||
|
for webview in desktop.webviews.values() {
|
||||||
|
let window = webview.window();
|
||||||
|
// change window maximized state.
|
||||||
|
window.set_maximized(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UserWindowEvent::FocusWindow => {
|
||||||
|
for webview in desktop.webviews.values() {
|
||||||
|
let window = webview.window();
|
||||||
|
window.set_focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::MainEventsCleared => {}
|
Event::MainEventsCleared => {}
|
||||||
@@ -271,6 +335,11 @@ pub fn launch_with_props<P: 'static + Send>(
|
|||||||
|
|
||||||
pub enum UserWindowEvent {
|
pub enum UserWindowEvent {
|
||||||
Update,
|
Update,
|
||||||
|
DragWindow,
|
||||||
|
CloseWindow,
|
||||||
|
FocusWindow,
|
||||||
|
Minimize(bool),
|
||||||
|
Maximize(bool),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DesktopController {
|
pub struct DesktopController {
|
||||||
@@ -297,6 +366,7 @@ impl DesktopController {
|
|||||||
let return_sender = sender.clone();
|
let return_sender = sender.clone();
|
||||||
let proxy = evt.clone();
|
let proxy = evt.clone();
|
||||||
|
|
||||||
|
let desktop_context_proxy = proxy.clone();
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
// We create the runtime as multithreaded, so you can still "spawn" onto multiple threads
|
// We create the runtime as multithreaded, so you can still "spawn" onto multiple threads
|
||||||
let runtime = tokio::runtime::Builder::new_multi_thread()
|
let runtime = tokio::runtime::Builder::new_multi_thread()
|
||||||
@@ -308,6 +378,10 @@ impl DesktopController {
|
|||||||
let mut dom =
|
let mut dom =
|
||||||
VirtualDom::new_with_props_and_scheduler(root, props, (sender, receiver));
|
VirtualDom::new_with_props_and_scheduler(root, props, (sender, receiver));
|
||||||
|
|
||||||
|
let window_context = DesktopContext::new(desktop_context_proxy);
|
||||||
|
|
||||||
|
dom.base_scope().provide_context(window_context);
|
||||||
|
|
||||||
let edits = dom.rebuild();
|
let edits = dom.rebuild();
|
||||||
|
|
||||||
edit_queue
|
edit_queue
|
||||||
|
|||||||
@@ -5,6 +5,39 @@ 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/),
|
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).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- <csr-id-c092bd43edf1891c427722bc9ca04e11c9359069/> use_state
|
||||||
|
- <csr-id-a8952a9ee8d8831fa911cef4e7035293c3a9f88b/> exampels
|
||||||
|
|
||||||
|
### Commit Statistics
|
||||||
|
|
||||||
|
<csr-read-only-do-not-edit/>
|
||||||
|
|
||||||
|
- 9 commits contributed to the release over the course of 21 calendar days.
|
||||||
|
- 3 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**
|
||||||
|
- Merge pull request #169 from DioxusLabs/jk/router-userouter ([`3509602`](https://github.comgit//DioxusLabs/dioxus/commit/3509602c0bcd327a33bc8c95896775e24751da1a))
|
||||||
|
- Merge pull request #158 from DioxusLabs/jk/router-onchange ([`08988e1`](https://github.comgit//DioxusLabs/dioxus/commit/08988e1bfec35eadb30e50b5d677dbb36af91b9b))
|
||||||
|
- use_state ([`c092bd4`](https://github.comgit//DioxusLabs/dioxus/commit/c092bd43edf1891c427722bc9ca04e11c9359069))
|
||||||
|
- exampels ([`a8952a9`](https://github.comgit//DioxusLabs/dioxus/commit/a8952a9ee8d8831fa911cef4e7035293c3a9f88b))
|
||||||
|
- Merge branch 'master' into jk/update-hooks ([`5c4bd08`](https://github.comgit//DioxusLabs/dioxus/commit/5c4bd0881bc10572440d9272ceb8ca774431c406))
|
||||||
|
- modify usestate to be borrowed ([`58839f4`](https://github.comgit//DioxusLabs/dioxus/commit/58839f47bae3c49cadd4b41da9a5debebb1def99))
|
||||||
|
- Fix various typos and grammar nits ([`9e4ec43`](https://github.comgit//DioxusLabs/dioxus/commit/9e4ec43b1e78d355c56a38e4c092170b2b01b20d))
|
||||||
|
- Merge pull request #108 from DioxusLabs/jk/fstring-component-fields ([`f4132d1`](https://github.comgit//DioxusLabs/dioxus/commit/f4132d1874f7495049fac23ba0a022ac137ad74f))
|
||||||
|
- Enable clippy ([`b6903bf`](https://github.comgit//DioxusLabs/dioxus/commit/b6903bf558bc7a3d0fe6794a137c44fca0957d11))
|
||||||
|
</details>
|
||||||
|
|
||||||
## v0.1.6 (2022-01-08)
|
## v0.1.6 (2022-01-08)
|
||||||
|
|
||||||
### Documentation
|
### Documentation
|
||||||
@@ -52,7 +85,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
<csr-read-only-do-not-edit/>
|
<csr-read-only-do-not-edit/>
|
||||||
|
|
||||||
- 93 commits contributed to the release over the course of 358 calendar days.
|
- 94 commits contributed to the release over the course of 358 calendar days.
|
||||||
- 82 commits where understood as [conventional](https://www.conventionalcommits.org).
|
- 82 commits where understood as [conventional](https://www.conventionalcommits.org).
|
||||||
- 0 issues like '(#ID)' where seen in commit messages
|
- 0 issues like '(#ID)' where seen in commit messages
|
||||||
|
|
||||||
@@ -63,6 +96,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
<details><summary>view details</summary>
|
<details><summary>view details</summary>
|
||||||
|
|
||||||
* **Uncategorized**
|
* **Uncategorized**
|
||||||
|
- Release dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`b804c69`](https://github.comgit//DioxusLabs/dioxus/commit/b804c691d5ade4776390bb3d334cc9cd8efa4a49))
|
||||||
- Release dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`a36dab7`](https://github.comgit//DioxusLabs/dioxus/commit/a36dab7f45920acd8535a69b4aa3695f3bb92111))
|
- Release dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`a36dab7`](https://github.comgit//DioxusLabs/dioxus/commit/a36dab7f45920acd8535a69b4aa3695f3bb92111))
|
||||||
- Release dioxus-core v0.1.7, dioxus-core-macro v0.1.6, dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`40d1f85`](https://github.comgit//DioxusLabs/dioxus/commit/40d1f85d0c3e2c9fd23c08840cca9f459d4e4307))
|
- Release dioxus-core v0.1.7, dioxus-core-macro v0.1.6, dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`40d1f85`](https://github.comgit//DioxusLabs/dioxus/commit/40d1f85d0c3e2c9fd23c08840cca9f459d4e4307))
|
||||||
- component pass thru events ([`c439b0a`](https://github.comgit//DioxusLabs/dioxus/commit/c439b0ac7e09f70a04262b7c29938d8c52197b76))
|
- component pass thru events ([`c439b0a`](https://github.comgit//DioxusLabs/dioxus/commit/c439b0ac7e09f70a04262b7c29938d8c52197b76))
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "dioxus-hooks"
|
name = "dioxus-hooks"
|
||||||
version = "0.1.6"
|
version = "0.1.7"
|
||||||
authors = ["Jonathan Kelley"]
|
authors = ["Jonathan Kelley"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
description = "Dioxus VirtualDOM renderer for a remote webview instance"
|
description = "Dioxus VirtualDOM renderer for a remote webview instance"
|
||||||
@@ -12,4 +12,4 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
dioxus-core = { path = "../../packages/core", version ="^0.1.7"}
|
dioxus-core = { path = "../../packages/core", version = "^0.1.9" }
|
||||||
+22
-42
@@ -16,45 +16,25 @@ pub use usefuture::*;
|
|||||||
mod usesuspense;
|
mod usesuspense;
|
||||||
pub use usesuspense::*;
|
pub use usesuspense::*;
|
||||||
|
|
||||||
// #[macro_export]
|
#[macro_export]
|
||||||
// macro_rules! to_owned {
|
/// A helper macro for using hooks in async environements.
|
||||||
// ($($es:ident),+) => {$(
|
///
|
||||||
// #[allow(unused_mut)]
|
/// # Usage
|
||||||
// let mut $es = $es.to_owned();
|
///
|
||||||
// )*}
|
///
|
||||||
// }
|
/// ```
|
||||||
|
/// let (data) = use_ref(&cx, || {});
|
||||||
// /// Calls `for_async` on the series of paramters.
|
///
|
||||||
// ///
|
/// let handle_thing = move |_| {
|
||||||
// /// If the type is Clone, then it will be cloned. However, if the type is not `clone`
|
/// to_owned![data]
|
||||||
// /// then it must have a `for_async` method for Rust to lower down into.
|
/// cx.spawn(async move {
|
||||||
// ///
|
/// // do stuff
|
||||||
// /// See: how use_state implements `for_async` but *not* through the trait.
|
/// });
|
||||||
// #[macro_export]
|
/// }
|
||||||
// macro_rules! for_async {
|
/// ```
|
||||||
// ($($es:ident),+) => {$(
|
macro_rules! to_owned {
|
||||||
// #[allow(unused_mut)]
|
($($es:ident),+) => {$(
|
||||||
// let mut $es = $es.for_async();
|
#[allow(unused_mut)]
|
||||||
// )*}
|
let mut $es = $es.to_owned();
|
||||||
// }
|
)*}
|
||||||
|
}
|
||||||
// /// This is a marker trait that uses decoherence.
|
|
||||||
// ///
|
|
||||||
// /// It is *not* meant for hooks to actually implement, but rather defer to their
|
|
||||||
// /// underlying implementation if they *don't* implement the trait.
|
|
||||||
// ///
|
|
||||||
// ///
|
|
||||||
// pub trait AsyncHook {
|
|
||||||
// type Output;
|
|
||||||
// fn for_async(self) -> Self::Output;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// impl<T> AsyncHook for T
|
|
||||||
// where
|
|
||||||
// T: ToOwned<Owned = T>,
|
|
||||||
// {
|
|
||||||
// type Output = T;
|
|
||||||
// fn for_async(self) -> Self::Output {
|
|
||||||
// self
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
@@ -33,7 +33,7 @@ impl<T> ProvidedStateInner<T> {
|
|||||||
/// This hook provides some relatively light ergonomics around shared state.
|
/// This hook provides some relatively light ergonomics around shared state.
|
||||||
///
|
///
|
||||||
/// It is not a substitute for a proper state management system, but it is capable enough to provide use_state - type
|
/// It is not a substitute for a proper state management system, but it is capable enough to provide use_state - type
|
||||||
/// ergonimics in a pinch, with zero cost.
|
/// ergonomics in a pinch, with zero cost.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -0,0 +1,346 @@
|
|||||||
|
#![warn(clippy::pedantic)]
|
||||||
|
|
||||||
|
use dioxus_core::prelude::*;
|
||||||
|
use std::{
|
||||||
|
cell::{RefCell, RefMut},
|
||||||
|
fmt::{Debug, Display},
|
||||||
|
rc::Rc,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Store state between component renders.
|
||||||
|
///
|
||||||
|
/// ## Dioxus equivalent of useState, designed for Rust
|
||||||
|
///
|
||||||
|
/// The Dioxus version of `useState` for state management inside components. It allows you to ergonomically store and
|
||||||
|
/// modify state between component renders. When the state is updated, the component will re-render.
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// const Example: Component = |cx| {
|
||||||
|
/// let (count, set_count) = use_state(&cx, || 0);
|
||||||
|
///
|
||||||
|
/// cx.render(rsx! {
|
||||||
|
/// div {
|
||||||
|
/// h1 { "Count: {count}" }
|
||||||
|
/// button { onclick: move |_| set_count(a - 1), "Increment" }
|
||||||
|
/// button { onclick: move |_| set_count(a + 1), "Decrement" }
|
||||||
|
/// }
|
||||||
|
/// ))
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn use_state<'a, T: 'static>(
|
||||||
|
cx: &'a ScopeState,
|
||||||
|
initial_state_fn: impl FnOnce() -> T,
|
||||||
|
) -> (&'a T, &'a UseState<T>) {
|
||||||
|
let hook = cx.use_hook(move |_| {
|
||||||
|
let current_val = Rc::new(initial_state_fn());
|
||||||
|
let update_callback = cx.schedule_update();
|
||||||
|
let slot = Rc::new(RefCell::new(current_val.clone()));
|
||||||
|
let setter = Rc::new({
|
||||||
|
crate::to_owned![update_callback, slot];
|
||||||
|
move |new| {
|
||||||
|
{
|
||||||
|
let mut slot = slot.borrow_mut();
|
||||||
|
|
||||||
|
// if there's only one reference (weak or otherwise), we can just swap the values
|
||||||
|
// Typically happens when the state is set multiple times - we don't want to create a new Rc for each new value
|
||||||
|
if let Some(val) = Rc::get_mut(&mut slot) {
|
||||||
|
*val = new;
|
||||||
|
} else {
|
||||||
|
*slot = Rc::new(new);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
update_callback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
UseState {
|
||||||
|
current_val,
|
||||||
|
update_callback,
|
||||||
|
setter,
|
||||||
|
slot,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
hook.current_val = hook.slot.borrow().clone();
|
||||||
|
|
||||||
|
(hook.current_val.as_ref(), hook)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct UseState<T: 'static> {
|
||||||
|
pub(crate) current_val: Rc<T>,
|
||||||
|
pub(crate) update_callback: Rc<dyn Fn()>,
|
||||||
|
pub(crate) setter: Rc<dyn Fn(T)>,
|
||||||
|
pub(crate) slot: Rc<RefCell<Rc<T>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: 'static> UseState<T> {
|
||||||
|
/// Get the current value of the state by cloning its container Rc.
|
||||||
|
///
|
||||||
|
/// This is useful when you are dealing with state in async contexts but need
|
||||||
|
/// to know the current value. You are not given a reference to the state.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// An async context might need to know the current value:
|
||||||
|
///
|
||||||
|
/// ```rust, ignore
|
||||||
|
/// fn component(cx: Scope) -> Element {
|
||||||
|
/// let (count, set_count) = use_state(&cx, || 0);
|
||||||
|
/// cx.spawn({
|
||||||
|
/// let set_count = set_count.to_owned();
|
||||||
|
/// async move {
|
||||||
|
/// let current = set_count.current();
|
||||||
|
/// }
|
||||||
|
/// })
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[must_use]
|
||||||
|
pub fn current(&self) -> Rc<T> {
|
||||||
|
self.slot.borrow().clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the `setter` function directly without the `UseState` wrapper.
|
||||||
|
///
|
||||||
|
/// This is useful for passing the setter function to other components.
|
||||||
|
///
|
||||||
|
/// However, for most cases, calling `to_owned` o`UseState`te is the
|
||||||
|
/// preferred way to get "anoth`set_state`tate handle.
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// A component might require an `Rc<dyn Fn(T)>` as an input to set a value.
|
||||||
|
///
|
||||||
|
/// ```rust, ignore
|
||||||
|
/// fn component(cx: Scope) -> Element {
|
||||||
|
/// let (value, set_value) = use_state(&cx, || 0);
|
||||||
|
///
|
||||||
|
/// rsx!{
|
||||||
|
/// Component {
|
||||||
|
/// handler: set_val.setter()
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[must_use]
|
||||||
|
pub fn setter(&self) -> Rc<dyn Fn(T)> {
|
||||||
|
self.setter.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the state to a new value, using the current state value as a reference.
|
||||||
|
///
|
||||||
|
/// This is similar to passing a closure to React's `set_value` function.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// Basic usage:
|
||||||
|
/// ```rust
|
||||||
|
/// # use dioxus_core::prelude::*;
|
||||||
|
/// # use dioxus_hooks::*;
|
||||||
|
/// fn component(cx: Scope) -> Element {
|
||||||
|
/// let (value, set_value) = use_state(&cx, || 0);
|
||||||
|
///
|
||||||
|
/// // to increment the value
|
||||||
|
/// set_value.modify(|v| v + 1);
|
||||||
|
///
|
||||||
|
/// // usage in async
|
||||||
|
/// cx.spawn({
|
||||||
|
/// let set_value = set_value.to_owned();
|
||||||
|
/// async move {
|
||||||
|
/// set_value.modify(|v| v + 1);
|
||||||
|
/// }
|
||||||
|
/// });
|
||||||
|
///
|
||||||
|
/// # todo!()
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn modify(&self, f: impl FnOnce(&T) -> T) {
|
||||||
|
let new_val = {
|
||||||
|
let current = self.slot.borrow();
|
||||||
|
f(current.as_ref())
|
||||||
|
};
|
||||||
|
(self.setter)(new_val);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the value of the state when this handle was created.
|
||||||
|
///
|
||||||
|
/// This method is useful when you want an `Rc` around the data to cheaply
|
||||||
|
/// pass it around your app.
|
||||||
|
///
|
||||||
|
/// ## Warning
|
||||||
|
///
|
||||||
|
/// This will return a stale value if used within async contexts.
|
||||||
|
///
|
||||||
|
/// Try `current` to get the real current value of the state.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
///
|
||||||
|
/// ```rust, ignore
|
||||||
|
/// # use dioxus_core::prelude::*;
|
||||||
|
/// # use dioxus_hooks::*;
|
||||||
|
/// fn component(cx: Scope) -> Element {
|
||||||
|
/// let (value, set_value) = use_state(&cx, || 0);
|
||||||
|
///
|
||||||
|
/// let as_rc = set_value.get();
|
||||||
|
/// assert_eq!(as_rc.as_ref(), &0);
|
||||||
|
///
|
||||||
|
/// # todo!()
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[must_use]
|
||||||
|
pub fn get(&self) -> &Rc<T> {
|
||||||
|
&self.current_val
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mark the component that create this [`UseState`] as dirty, forcing it to re-render.
|
||||||
|
///
|
||||||
|
/// ```rust, ignore
|
||||||
|
/// fn component(cx: Scope) -> Element {
|
||||||
|
/// let (count, set_count) = use_state(&cx, || 0);
|
||||||
|
/// cx.spawn({
|
||||||
|
/// let set_count = set_count.to_owned();
|
||||||
|
/// async move {
|
||||||
|
/// // for the component to re-render
|
||||||
|
/// set_count.needs_update();
|
||||||
|
/// }
|
||||||
|
/// })
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn needs_update(&self) {
|
||||||
|
(self.update_callback)();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Clone> UseState<T> {
|
||||||
|
/// Get a mutable handle to the value by calling `ToOwned::to_owned` on the
|
||||||
|
/// current value.
|
||||||
|
///
|
||||||
|
/// This is essentially cloning the underlying value and then setting it,
|
||||||
|
/// giving you a mutable handle in the process. This method is intended for
|
||||||
|
/// types that are cheaply cloneable.
|
||||||
|
///
|
||||||
|
/// If you are comfortable dealing with `RefMut`, then you can use `make_mut` to get
|
||||||
|
/// the underlying slot. However, be careful with `RefMut` since you might panic
|
||||||
|
/// if the `RefCell` is left open.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let (val, set_val) = use_state(&cx, || 0);
|
||||||
|
///
|
||||||
|
/// set_val.with_mut(|v| *v = 1);
|
||||||
|
/// ```
|
||||||
|
pub fn with_mut(&self, apply: impl FnOnce(&mut T)) {
|
||||||
|
let mut slot = self.slot.borrow_mut();
|
||||||
|
let mut inner = slot.as_ref().to_owned();
|
||||||
|
|
||||||
|
apply(&mut inner);
|
||||||
|
|
||||||
|
if let Some(new) = Rc::get_mut(&mut slot) {
|
||||||
|
*new = inner;
|
||||||
|
} else {
|
||||||
|
*slot = Rc::new(inner);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.needs_update();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a mutable handle to the value by calling `ToOwned::to_owned` on the
|
||||||
|
/// current value.
|
||||||
|
///
|
||||||
|
/// This is essentially cloning the underlying value and then setting it,
|
||||||
|
/// giving you a mutable handle in the process. This method is intended for
|
||||||
|
/// types that are cheaply cloneable.
|
||||||
|
///
|
||||||
|
/// # Warning
|
||||||
|
/// Be careful with `RefMut` since you might panic if the `RefCell` is left open!
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let (val, set_val) = use_state(&cx, || 0);
|
||||||
|
///
|
||||||
|
/// set_val.with_mut(|v| *v = 1);
|
||||||
|
/// ```
|
||||||
|
#[must_use]
|
||||||
|
pub fn make_mut(&self) -> RefMut<T> {
|
||||||
|
let mut slot = self.slot.borrow_mut();
|
||||||
|
|
||||||
|
self.needs_update();
|
||||||
|
|
||||||
|
if Rc::strong_count(&*slot) > 0 {
|
||||||
|
*slot = Rc::new(slot.as_ref().to_owned());
|
||||||
|
}
|
||||||
|
|
||||||
|
RefMut::map(slot, |rc| Rc::get_mut(rc).expect("the hard count to be 0"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: 'static> Clone for UseState<T> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
UseState {
|
||||||
|
current_val: self.current_val.clone(),
|
||||||
|
update_callback: self.update_callback.clone(),
|
||||||
|
setter: self.setter.clone(),
|
||||||
|
slot: self.slot.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: 'static + Display> std::fmt::Display for UseState<T> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.current_val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> PartialEq<UseState<T>> for UseState<T> {
|
||||||
|
fn eq(&self, other: &UseState<T>) -> bool {
|
||||||
|
// some level of memoization for UseState
|
||||||
|
Rc::ptr_eq(&self.slot, &other.slot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Debug> Debug for UseState<T> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{:?}", self.current_val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> std::ops::Deref for UseState<T> {
|
||||||
|
type Target = Rc<dyn Fn(T)>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.setter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn api_makes_sense() {
|
||||||
|
#[allow(unused)]
|
||||||
|
fn app(cx: Scope) -> Element {
|
||||||
|
let (val, set_val) = use_state(&cx, || 0);
|
||||||
|
|
||||||
|
set_val(0);
|
||||||
|
set_val.modify(|v| v + 1);
|
||||||
|
let real_current = set_val.current();
|
||||||
|
|
||||||
|
match val {
|
||||||
|
10 => {
|
||||||
|
set_val(20);
|
||||||
|
set_val.modify(|v| v + 1);
|
||||||
|
}
|
||||||
|
20 => {}
|
||||||
|
_ => {
|
||||||
|
println!("{real_current}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cx.spawn({
|
||||||
|
crate::to_owned![set_val];
|
||||||
|
async move {
|
||||||
|
set_val.modify(|f| f + 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cx.render(LazyNodes::new(|f| f.static_text("asd")))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,215 +0,0 @@
|
|||||||
use super::owned::UseStateOwned;
|
|
||||||
use std::{
|
|
||||||
cell::{Ref, RefMut},
|
|
||||||
fmt::{Debug, Display},
|
|
||||||
rc::Rc,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct UseState<'a, T: 'static>(pub(crate) &'a UseStateOwned<T>);
|
|
||||||
|
|
||||||
impl<T> Copy for UseState<'_, T> {}
|
|
||||||
|
|
||||||
impl<'a, T: 'static> Clone for UseState<'a, T> {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
UseState(self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Debug> Debug for UseState<'_, T> {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{:?}", self.0.current_val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, T: 'static> UseState<'a, T> {
|
|
||||||
/// Tell the Dioxus Scheduler that we need to be processed
|
|
||||||
pub fn needs_update(&self) {
|
|
||||||
if !self.0.update_scheuled.get() {
|
|
||||||
self.0.update_scheuled.set(true);
|
|
||||||
(self.0.update_callback)();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set(&self, new_val: T) {
|
|
||||||
*self.0.wip.borrow_mut() = Some(new_val);
|
|
||||||
self.needs_update();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get(&self) -> &'a T {
|
|
||||||
&self.0.current_val
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_rc(&self) -> &'a Rc<T> {
|
|
||||||
&self.0.current_val
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the current status of the work-in-progress data
|
|
||||||
pub fn get_wip(&self) -> Ref<Option<T>> {
|
|
||||||
self.0.wip.borrow()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the current status of the work-in-progress data
|
|
||||||
pub fn get_wip_mut(&self) -> RefMut<Option<T>> {
|
|
||||||
self.0.wip.borrow_mut()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn classic(self) -> (&'a T, Rc<dyn Fn(T)>) {
|
|
||||||
(&self.0.current_val, self.setter())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn setter(&self) -> Rc<dyn Fn(T)> {
|
|
||||||
let slot = self.0.wip.clone();
|
|
||||||
let callback = self.0.update_callback.clone();
|
|
||||||
Rc::new(move |new| {
|
|
||||||
callback();
|
|
||||||
*slot.borrow_mut() = Some(new)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn wtih(&self, f: impl FnOnce(&mut T)) {
|
|
||||||
let mut val = self.0.wip.borrow_mut();
|
|
||||||
|
|
||||||
if let Some(inner) = val.as_mut() {
|
|
||||||
f(inner);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn for_async(&self) -> UseStateOwned<T> {
|
|
||||||
let UseStateOwned {
|
|
||||||
current_val,
|
|
||||||
wip,
|
|
||||||
update_callback,
|
|
||||||
update_scheuled,
|
|
||||||
} = self.0;
|
|
||||||
|
|
||||||
UseStateOwned {
|
|
||||||
current_val: current_val.clone(),
|
|
||||||
wip: wip.clone(),
|
|
||||||
update_callback: update_callback.clone(),
|
|
||||||
update_scheuled: update_scheuled.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, T: 'static + ToOwned<Owned = T>> UseState<'a, T> {
|
|
||||||
/// Gain mutable access to the new value via [`RefMut`].
|
|
||||||
///
|
|
||||||
/// If `modify` is called, then the component will re-render.
|
|
||||||
///
|
|
||||||
/// This method is only available when the value is a `ToOwned` type.
|
|
||||||
///
|
|
||||||
/// Mutable access is derived by calling "ToOwned" (IE cloning) on the current value.
|
|
||||||
///
|
|
||||||
/// To get a reference to the current value, use `.get()`
|
|
||||||
pub fn modify(self) -> RefMut<'a, T> {
|
|
||||||
// make sure we get processed
|
|
||||||
self.needs_update();
|
|
||||||
|
|
||||||
// Bring out the new value, cloning if it we need to
|
|
||||||
// "get_mut" is locked behind "ToOwned" to make it explicit that cloning occurs to use this
|
|
||||||
RefMut::map(self.0.wip.borrow_mut(), |slot| {
|
|
||||||
if slot.is_none() {
|
|
||||||
*slot = Some(self.0.current_val.as_ref().to_owned());
|
|
||||||
}
|
|
||||||
slot.as_mut().unwrap()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn inner(self) -> T {
|
|
||||||
self.0.current_val.as_ref().to_owned()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, T> std::ops::Deref for UseState<'a, T> {
|
|
||||||
type Target = T;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
self.get()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// enable displaty for the handle
|
|
||||||
impl<'a, T: 'static + Display> std::fmt::Display for UseState<'a, T> {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{}", self.0.current_val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'a, V, T: PartialEq<V>> PartialEq<V> for UseState<'a, T> {
|
|
||||||
fn eq(&self, other: &V) -> bool {
|
|
||||||
self.get() == other
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'a, O, T: std::ops::Not<Output = O> + Copy> std::ops::Not for UseState<'a, T> {
|
|
||||||
type Output = O;
|
|
||||||
|
|
||||||
fn not(self) -> Self::Output {
|
|
||||||
!*self.get()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
Convenience methods for UseState.
|
|
||||||
|
|
||||||
Note!
|
|
||||||
|
|
||||||
This is not comprehensive.
|
|
||||||
This is *just* meant to make common operations easier.
|
|
||||||
*/
|
|
||||||
|
|
||||||
use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};
|
|
||||||
|
|
||||||
impl<'a, T: Copy + Add<T, Output = T>> Add<T> for UseState<'a, T> {
|
|
||||||
type Output = T;
|
|
||||||
|
|
||||||
fn add(self, rhs: T) -> Self::Output {
|
|
||||||
self.0.current_val.add(rhs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'a, T: Copy + Add<T, Output = T>> AddAssign<T> for UseState<'a, T> {
|
|
||||||
fn add_assign(&mut self, rhs: T) {
|
|
||||||
self.set(self.0.current_val.add(rhs));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sub
|
|
||||||
impl<'a, T: Copy + Sub<T, Output = T>> Sub<T> for UseState<'a, T> {
|
|
||||||
type Output = T;
|
|
||||||
|
|
||||||
fn sub(self, rhs: T) -> Self::Output {
|
|
||||||
self.0.current_val.sub(rhs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'a, T: Copy + Sub<T, Output = T>> SubAssign<T> for UseState<'a, T> {
|
|
||||||
fn sub_assign(&mut self, rhs: T) {
|
|
||||||
self.set(self.0.current_val.sub(rhs));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// MUL
|
|
||||||
impl<'a, T: Copy + Mul<T, Output = T>> Mul<T> for UseState<'a, T> {
|
|
||||||
type Output = T;
|
|
||||||
|
|
||||||
fn mul(self, rhs: T) -> Self::Output {
|
|
||||||
self.0.current_val.mul(rhs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'a, T: Copy + Mul<T, Output = T>> MulAssign<T> for UseState<'a, T> {
|
|
||||||
fn mul_assign(&mut self, rhs: T) {
|
|
||||||
self.set(self.0.current_val.mul(rhs));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// DIV
|
|
||||||
impl<'a, T: Copy + Div<T, Output = T>> Div<T> for UseState<'a, T> {
|
|
||||||
type Output = T;
|
|
||||||
|
|
||||||
fn div(self, rhs: T) -> Self::Output {
|
|
||||||
self.0.current_val.div(rhs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'a, T: Copy + Div<T, Output = T>> DivAssign<T> for UseState<'a, T> {
|
|
||||||
fn div_assign(&mut self, rhs: T) {
|
|
||||||
self.set(self.0.current_val.div(rhs));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
mod handle;
|
|
||||||
mod owned;
|
|
||||||
pub use handle::*;
|
|
||||||
pub use owned::*;
|
|
||||||
|
|
||||||
use dioxus_core::prelude::*;
|
|
||||||
use std::{
|
|
||||||
cell::{Cell, RefCell},
|
|
||||||
rc::Rc,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Store state between component renders!
|
|
||||||
///
|
|
||||||
/// ## Dioxus equivalent of useState, designed for Rust
|
|
||||||
///
|
|
||||||
/// The Dioxus version of `useState` for state management inside components. It allows you to ergonomically store and
|
|
||||||
/// modify state between component renders. When the state is updated, the component will re-render.
|
|
||||||
///
|
|
||||||
/// Dioxus' use_state basically wraps a RefCell with helper methods and integrates it with the VirtualDOM update system.
|
|
||||||
///
|
|
||||||
/// [`use_state`] exposes a few helper methods to modify the underlying state:
|
|
||||||
/// - `.set(new)` allows you to override the "work in progress" value with a new value
|
|
||||||
/// - `.get_mut()` allows you to modify the WIP value
|
|
||||||
/// - `.get_wip()` allows you to access the WIP value
|
|
||||||
/// - `.deref()` provides the previous value (often done implicitly, though a manual dereference with `*` might be required)
|
|
||||||
///
|
|
||||||
/// Additionally, a ton of std::ops traits are implemented for the `UseState` wrapper, meaning any mutative type operations
|
|
||||||
/// will automatically be called on the WIP value.
|
|
||||||
///
|
|
||||||
/// ## Combinators
|
|
||||||
///
|
|
||||||
/// On top of the methods to set/get state, `use_state` also supports fancy combinators to extend its functionality:
|
|
||||||
/// - `.classic()` and `.split()` convert the hook into the classic React-style hook
|
|
||||||
/// ```rust
|
|
||||||
/// let (state, set_state) = use_state(&cx, || 10).split()
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
///
|
|
||||||
/// Usage:
|
|
||||||
///
|
|
||||||
/// ```ignore
|
|
||||||
/// const Example: Component = |cx| {
|
|
||||||
/// let counter = use_state(&cx, || 0);
|
|
||||||
///
|
|
||||||
/// cx.render(rsx! {
|
|
||||||
/// div {
|
|
||||||
/// h1 { "Counter: {counter}" }
|
|
||||||
/// button { onclick: move |_| counter += 1, "Increment" }
|
|
||||||
/// button { onclick: move |_| counter -= 1, "Decrement" }
|
|
||||||
/// }
|
|
||||||
/// ))
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
pub fn use_state<'a, T: 'static>(
|
|
||||||
cx: &'a ScopeState,
|
|
||||||
initial_state_fn: impl FnOnce() -> T,
|
|
||||||
) -> UseState<'a, T> {
|
|
||||||
let hook = cx.use_hook(move |_| UseStateOwned {
|
|
||||||
current_val: Rc::new(initial_state_fn()),
|
|
||||||
update_callback: cx.schedule_update(),
|
|
||||||
wip: Rc::new(RefCell::new(None)),
|
|
||||||
update_scheuled: Cell::new(false),
|
|
||||||
});
|
|
||||||
|
|
||||||
hook.update_scheuled.set(false);
|
|
||||||
let mut new_val = hook.wip.borrow_mut();
|
|
||||||
|
|
||||||
if new_val.is_some() {
|
|
||||||
// if there's only one reference (weak or otherwise), we can just swap the values
|
|
||||||
if let Some(val) = Rc::get_mut(&mut hook.current_val) {
|
|
||||||
*val = new_val.take().unwrap();
|
|
||||||
} else {
|
|
||||||
hook.current_val = Rc::new(new_val.take().unwrap());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
UseState(hook)
|
|
||||||
}
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
use std::{
|
|
||||||
cell::{Cell, Ref, RefCell, RefMut},
|
|
||||||
fmt::{Debug, Display},
|
|
||||||
rc::Rc,
|
|
||||||
};
|
|
||||||
pub struct UseStateOwned<T: 'static> {
|
|
||||||
// this will always be outdated
|
|
||||||
pub(crate) current_val: Rc<T>,
|
|
||||||
pub(crate) wip: Rc<RefCell<Option<T>>>,
|
|
||||||
pub(crate) update_callback: Rc<dyn Fn()>,
|
|
||||||
pub(crate) update_scheuled: Cell<bool>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> UseStateOwned<T> {
|
|
||||||
pub fn get(&self) -> Ref<Option<T>> {
|
|
||||||
self.wip.borrow()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set(&self, new_val: T) {
|
|
||||||
*self.wip.borrow_mut() = Some(new_val);
|
|
||||||
(self.update_callback)();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn modify(&self) -> RefMut<Option<T>> {
|
|
||||||
(self.update_callback)();
|
|
||||||
self.wip.borrow_mut()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};
|
|
||||||
|
|
||||||
impl<T: Debug> Debug for UseStateOwned<T> {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{:?}", self.current_val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// enable displaty for the handle
|
|
||||||
impl<'a, T: 'static + Display> std::fmt::Display for UseStateOwned<T> {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{}", self.current_val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, T: Copy + Add<T, Output = T>> Add<T> for UseStateOwned<T> {
|
|
||||||
type Output = T;
|
|
||||||
|
|
||||||
fn add(self, rhs: T) -> Self::Output {
|
|
||||||
self.current_val.add(rhs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, T: Copy + Add<T, Output = T>> AddAssign<T> for UseStateOwned<T> {
|
|
||||||
fn add_assign(&mut self, rhs: T) {
|
|
||||||
self.set(self.current_val.add(rhs));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sub
|
|
||||||
impl<'a, T: Copy + Sub<T, Output = T>> Sub<T> for UseStateOwned<T> {
|
|
||||||
type Output = T;
|
|
||||||
|
|
||||||
fn sub(self, rhs: T) -> Self::Output {
|
|
||||||
self.current_val.sub(rhs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'a, T: Copy + Sub<T, Output = T>> SubAssign<T> for UseStateOwned<T> {
|
|
||||||
fn sub_assign(&mut self, rhs: T) {
|
|
||||||
self.set(self.current_val.sub(rhs));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// MUL
|
|
||||||
impl<'a, T: Copy + Mul<T, Output = T>> Mul<T> for UseStateOwned<T> {
|
|
||||||
type Output = T;
|
|
||||||
|
|
||||||
fn mul(self, rhs: T) -> Self::Output {
|
|
||||||
self.current_val.mul(rhs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'a, T: Copy + Mul<T, Output = T>> MulAssign<T> for UseStateOwned<T> {
|
|
||||||
fn mul_assign(&mut self, rhs: T) {
|
|
||||||
self.set(self.current_val.mul(rhs));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// DIV
|
|
||||||
impl<'a, T: Copy + Div<T, Output = T>> Div<T> for UseStateOwned<T> {
|
|
||||||
type Output = T;
|
|
||||||
|
|
||||||
fn div(self, rhs: T) -> Self::Output {
|
|
||||||
self.current_val.div(rhs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'a, T: Copy + Div<T, Output = T>> DivAssign<T> for UseStateOwned<T> {
|
|
||||||
fn div_assign(&mut self, rhs: T) {
|
|
||||||
self.set(self.current_val.div(rhs));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,6 +5,36 @@ 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/),
|
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).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- <csr-id-43e78d56f72e23ada939f5688a6bd2fdd9b8292d/> rustfmt
|
||||||
|
|
||||||
|
### Commit Statistics
|
||||||
|
|
||||||
|
<csr-read-only-do-not-edit/>
|
||||||
|
|
||||||
|
- 7 commits contributed to the release over the course of 19 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**
|
||||||
|
- rustfmt ([`43e78d5`](https://github.comgit//DioxusLabs/dioxus/commit/43e78d56f72e23ada939f5688a6bd2fdd9b8292d))
|
||||||
|
- Add gap and row_gap to style_trait_methods ([`5f4a724`](https://github.comgit//DioxusLabs/dioxus/commit/5f4a72446e63441628e18bb19fd9b2d9f65f9d52))
|
||||||
|
- Merge pull request #111 from DioxusLabs/jk/props-attrs ([`0369fe7`](https://github.comgit//DioxusLabs/dioxus/commit/0369fe72fb247409da300a54ef11ba9155d0efb3))
|
||||||
|
- Fix various typos and grammar nits ([`9e4ec43`](https://github.comgit//DioxusLabs/dioxus/commit/9e4ec43b1e78d355c56a38e4c092170b2b01b20d))
|
||||||
|
- Merge pull request #113 from DioxusLabs/jk/desktop-cursor-jump ([`20a2940`](https://github.comgit//DioxusLabs/dioxus/commit/20a29409b22510b001fdbee349724adb7b44d401))
|
||||||
|
- feat(events:focus): add missing `onfocusin` event ([`007d06d`](https://github.comgit//DioxusLabs/dioxus/commit/007d06d602f1adfaa51c87ec89b2afe90d8cdef9))
|
||||||
|
- feat(example:todomvc): add editing support ([`9849f68`](https://github.comgit//DioxusLabs/dioxus/commit/9849f68f257200fac511c048bfb1a076243b86d3))
|
||||||
|
</details>
|
||||||
|
|
||||||
## v0.1.4 (2022-01-08)
|
## v0.1.4 (2022-01-08)
|
||||||
|
|
||||||
### Documentation
|
### Documentation
|
||||||
@@ -42,7 +72,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
<csr-read-only-do-not-edit/>
|
<csr-read-only-do-not-edit/>
|
||||||
|
|
||||||
- 60 commits contributed to the release over the course of 184 calendar days.
|
- 61 commits contributed to the release over the course of 184 calendar days.
|
||||||
- 52 commits where understood as [conventional](https://www.conventionalcommits.org).
|
- 52 commits where understood as [conventional](https://www.conventionalcommits.org).
|
||||||
- 0 issues like '(#ID)' where seen in commit messages
|
- 0 issues like '(#ID)' where seen in commit messages
|
||||||
|
|
||||||
@@ -53,8 +83,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
<details><summary>view details</summary>
|
<details><summary>view details</summary>
|
||||||
|
|
||||||
* **Uncategorized**
|
* **Uncategorized**
|
||||||
- Release dioxus-core v0.1.7, dioxus-core-macro v0.1.6, dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`40d1f85`](https://github.comgit//DioxusLabs/dioxus/commit/40d1f85d0c3e2c9fd23c08840cca9f459d4e4307))
|
- Release dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`a36dab7`](https://github.comgit//DioxusLabs/dioxus/commit/a36dab7f45920acd8535a69b4aa3695f3bb92111))
|
||||||
- enable prevent_default everywhere ([`9dff700`](https://github.comgit//DioxusLabs/dioxus/commit/9dff700c220dd9e0da2ee028900e82fd24f9d0dd))
|
- enable prevent_default everywhere ([`9dff700`](https://github.comgit//DioxusLabs/dioxus/commit/9dff700c220dd9e0da2ee028900e82fd24f9d0dd))
|
||||||
|
- Release dioxus-core v0.1.7, dioxus-core-macro v0.1.6, dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`40d1f85`](https://github.comgit//DioxusLabs/dioxus/commit/40d1f85d0c3e2c9fd23c08840cca9f459d4e4307))
|
||||||
- component pass thru events ([`c439b0a`](https://github.comgit//DioxusLabs/dioxus/commit/c439b0ac7e09f70a04262b7c29938d8c52197b76))
|
- component pass thru events ([`c439b0a`](https://github.comgit//DioxusLabs/dioxus/commit/c439b0ac7e09f70a04262b7c29938d8c52197b76))
|
||||||
- memoize dom in the prescence of identical components ([`cb2782b`](https://github.comgit//DioxusLabs/dioxus/commit/cb2782b4bb34cdaadfff590bfee930ae3ac6536c))
|
- memoize dom in the prescence of identical components ([`cb2782b`](https://github.comgit//DioxusLabs/dioxus/commit/cb2782b4bb34cdaadfff590bfee930ae3ac6536c))
|
||||||
- new versions of everything ([`4ea5c99`](https://github.comgit//DioxusLabs/dioxus/commit/4ea5c990d72b1645724ab0a88ffea2baf28e2835))
|
- new versions of everything ([`4ea5c99`](https://github.comgit//DioxusLabs/dioxus/commit/4ea5c990d72b1645724ab0a88ffea2baf28e2835))
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "dioxus-html"
|
name = "dioxus-html"
|
||||||
version = "0.1.4"
|
version = "0.1.6"
|
||||||
authors = ["Jonathan Kelley"]
|
authors = ["Jonathan Kelley"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
description = "HTML Element pack for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences"
|
description = "HTML Element pack for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences"
|
||||||
@@ -11,7 +11,7 @@ documentation = "https://docs.rs/dioxus"
|
|||||||
keywords = ["dom", "ui", "gui", "react", "wasm"]
|
keywords = ["dom", "ui", "gui", "react", "wasm"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
dioxus-core = { path = "../core", version ="^0.1.7"}
|
dioxus-core = { path = "../core", version = "^0.1.9" }
|
||||||
serde = { version = "1", features = ["derive"], optional = true }
|
serde = { version = "1", features = ["derive"], optional = true }
|
||||||
serde_repr = { version = "0.1", optional = true }
|
serde_repr = { version = "0.1", optional = true }
|
||||||
|
|
||||||
|
|||||||
@@ -399,6 +399,9 @@ pub trait GlobalAttributes {
|
|||||||
/// Specify the font weight of the text.
|
/// Specify the font weight of the text.
|
||||||
font_weight: "font-weight",
|
font_weight: "font-weight",
|
||||||
|
|
||||||
|
/// Sets gaps (gutters) between rows and columns. Shorthand for row_gap and column_gap.
|
||||||
|
gap: "gap",
|
||||||
|
|
||||||
/// Specify the height of an element.
|
/// Specify the height of an element.
|
||||||
height: "height",
|
height: "height",
|
||||||
|
|
||||||
@@ -531,6 +534,9 @@ pub trait GlobalAttributes {
|
|||||||
/// Specify the location of the right edge of the positioned element.
|
/// Specify the location of the right edge of the positioned element.
|
||||||
right: "right",
|
right: "right",
|
||||||
|
|
||||||
|
/// Specifies the gap between the rows in a multi_column element.
|
||||||
|
row_gap: "row-gap",
|
||||||
|
|
||||||
/// Specifies the length of the tab character.
|
/// Specifies the length of the tab character.
|
||||||
tab_size: "tab-size",
|
tab_size: "tab-size",
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,26 @@ 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/),
|
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).
|
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.0.3 (2022-01-08)
|
## v0.0.3 (2022-01-08)
|
||||||
|
|
||||||
### Documentation
|
### Documentation
|
||||||
@@ -28,7 +48,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
<csr-read-only-do-not-edit/>
|
<csr-read-only-do-not-edit/>
|
||||||
|
|
||||||
- 32 commits contributed to the release over the course of 151 calendar days.
|
- 33 commits contributed to the release over the course of 151 calendar days.
|
||||||
- 21 commits where understood as [conventional](https://www.conventionalcommits.org).
|
- 21 commits where understood as [conventional](https://www.conventionalcommits.org).
|
||||||
- 0 issues like '(#ID)' where seen in commit messages
|
- 0 issues like '(#ID)' where seen in commit messages
|
||||||
|
|
||||||
@@ -39,6 +59,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
<details><summary>view details</summary>
|
<details><summary>view details</summary>
|
||||||
|
|
||||||
* **Uncategorized**
|
* **Uncategorized**
|
||||||
|
- Release dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`b804c69`](https://github.comgit//DioxusLabs/dioxus/commit/b804c691d5ade4776390bb3d334cc9cd8efa4a49))
|
||||||
- Release dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`a36dab7`](https://github.comgit//DioxusLabs/dioxus/commit/a36dab7f45920acd8535a69b4aa3695f3bb92111))
|
- Release dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`a36dab7`](https://github.comgit//DioxusLabs/dioxus/commit/a36dab7f45920acd8535a69b4aa3695f3bb92111))
|
||||||
- Release dioxus-core v0.1.7, dioxus-core-macro v0.1.6, dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`40d1f85`](https://github.comgit//DioxusLabs/dioxus/commit/40d1f85d0c3e2c9fd23c08840cca9f459d4e4307))
|
- Release dioxus-core v0.1.7, dioxus-core-macro v0.1.6, dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`40d1f85`](https://github.comgit//DioxusLabs/dioxus/commit/40d1f85d0c3e2c9fd23c08840cca9f459d4e4307))
|
||||||
- Merge pull request #74 from mrxiaozhuox/master ([`47056fd`](https://github.comgit//DioxusLabs/dioxus/commit/47056fda4577bcbdaa2a6f63d82eec876e5a5aee))
|
- Merge pull request #74 from mrxiaozhuox/master ([`47056fd`](https://github.comgit//DioxusLabs/dioxus/commit/47056fda4577bcbdaa2a6f63d82eec876e5a5aee))
|
||||||
|
|||||||
@@ -12,4 +12,4 @@ license = "MIT/Apache-2.0"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
dioxus-desktop = { path = "../desktop", version ="^0.1.5"}
|
dioxus-desktop = { path = "../desktop", version = "^0.1.6" }
|
||||||
@@ -5,6 +5,54 @@ 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/),
|
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).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
### New Features
|
||||||
|
|
||||||
|
- <csr-id-e24957fc191476184816fe5dee249f691170d4ae/> enable use_router
|
||||||
|
- <csr-id-29ed7ebece26e9d53925af55f2f34a8fd8241405/> connect an onchange listener
|
||||||
|
- <csr-id-d2372717bd01fcff50af0572360e3f763d4c869d/> flatten props attrs
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- <csr-id-5ee9d6c4348a2f51adac827f715fb138918d1dc6/> attach router listener to subscriber list
|
||||||
|
- <csr-id-a21e7d4dd168129da06f535f9dc4b1de724617cb/> use_route should subscribe to changes to the route
|
||||||
|
|
||||||
|
### Commit Statistics
|
||||||
|
|
||||||
|
<csr-read-only-do-not-edit/>
|
||||||
|
|
||||||
|
- 18 commits contributed to the release over the course of 14 calendar days.
|
||||||
|
- 7 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**
|
||||||
|
- Merge pull request #169 from DioxusLabs/jk/router-userouter ([`3509602`](https://github.comgit//DioxusLabs/dioxus/commit/3509602c0bcd327a33bc8c95896775e24751da1a))
|
||||||
|
- enable use_router ([`e24957f`](https://github.comgit//DioxusLabs/dioxus/commit/e24957fc191476184816fe5dee249f691170d4ae))
|
||||||
|
- add docs to router UseRouteListener ([`79e0993`](https://github.comgit//DioxusLabs/dioxus/commit/79e09934aa685d03d6e0b323723bc1cd537d74d9))
|
||||||
|
- Merge pull request #166 from DioxusLabs/jk/default-assets-desktop ([`ccbb955`](https://github.comgit//DioxusLabs/dioxus/commit/ccbb955b7b24bd4e1c5aa40e813a2872ae474a69))
|
||||||
|
- rustfmt ([`9da46eb`](https://github.comgit//DioxusLabs/dioxus/commit/9da46eb7bc207997ca7779c58fcb2a9645dfa9d0))
|
||||||
|
- Make log message in Link component trace level, not debug ([`72c6bb3`](https://github.comgit//DioxusLabs/dioxus/commit/72c6bb3d0b7253f084f7e3bcf55d458cb4adeedb))
|
||||||
|
- attach router listener to subscriber list ([`5ee9d6c`](https://github.comgit//DioxusLabs/dioxus/commit/5ee9d6c4348a2f51adac827f715fb138918d1dc6))
|
||||||
|
- connect an onchange listener ([`29ed7eb`](https://github.comgit//DioxusLabs/dioxus/commit/29ed7ebece26e9d53925af55f2f34a8fd8241405))
|
||||||
|
- use_route should subscribe to changes to the route ([`a21e7d4`](https://github.comgit//DioxusLabs/dioxus/commit/a21e7d4dd168129da06f535f9dc4b1de724617cb))
|
||||||
|
- Merge pull request #95 from DioxusLabs/jk/filedragindrop ([`ca0dd4a`](https://github.comgit//DioxusLabs/dioxus/commit/ca0dd4aa7192d483a195d420363f39d771f3e471))
|
||||||
|
- Fix various typos and grammar nits ([`9e4ec43`](https://github.comgit//DioxusLabs/dioxus/commit/9e4ec43b1e78d355c56a38e4c092170b2b01b20d))
|
||||||
|
- flatten props attrs ([`d237271`](https://github.comgit//DioxusLabs/dioxus/commit/d2372717bd01fcff50af0572360e3f763d4c869d))
|
||||||
|
- Merge pull request #108 from DioxusLabs/jk/fstring-component-fields ([`f4132d1`](https://github.comgit//DioxusLabs/dioxus/commit/f4132d1874f7495049fac23ba0a022ac137ad74f))
|
||||||
|
- Enable clippy ([`b6903bf`](https://github.comgit//DioxusLabs/dioxus/commit/b6903bf558bc7a3d0fe6794a137c44fca0957d11))
|
||||||
|
- Merge pull request #138 from mrxiaozhuox/master ([`8c7473d`](https://github.comgit//DioxusLabs/dioxus/commit/8c7473d1943dd133f388ec36116c9d8295861b97))
|
||||||
|
- Add a warning when Link it called outside of a Router context ([`6408058`](https://github.comgit//DioxusLabs/dioxus/commit/64080588d02c54a1d380116cbecdd17de16d2392))
|
||||||
|
- Merge pull request #133 from mrxiaozhuox/master ([`887f69d`](https://github.comgit//DioxusLabs/dioxus/commit/887f69d5b47bdcde4fe0eab094c0cd0de23e4f3f))
|
||||||
|
- Fix handling of re-renders in the Router ([`81c094e`](https://github.comgit//DioxusLabs/dioxus/commit/81c094ed29fcdc5c6099492dd6ab09a59b79252c))
|
||||||
|
</details>
|
||||||
|
|
||||||
## v0.1.0 (2022-01-08)
|
## v0.1.0 (2022-01-08)
|
||||||
|
|
||||||
### Documentation
|
### Documentation
|
||||||
@@ -33,7 +81,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
<csr-read-only-do-not-edit/>
|
<csr-read-only-do-not-edit/>
|
||||||
|
|
||||||
- 42 commits contributed to the release over the course of 352 calendar days.
|
- 50 commits contributed to the release over the course of 352 calendar days.
|
||||||
- 35 commits where understood as [conventional](https://www.conventionalcommits.org).
|
- 35 commits where understood as [conventional](https://www.conventionalcommits.org).
|
||||||
- 0 issues like '(#ID)' where seen in commit messages
|
- 0 issues like '(#ID)' where seen in commit messages
|
||||||
|
|
||||||
@@ -44,8 +92,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
<details><summary>view details</summary>
|
<details><summary>view details</summary>
|
||||||
|
|
||||||
* **Uncategorized**
|
* **Uncategorized**
|
||||||
|
- Release dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`b804c69`](https://github.comgit//DioxusLabs/dioxus/commit/b804c691d5ade4776390bb3d334cc9cd8efa4a49))
|
||||||
|
- More WIP router implementation ([`e06eac1`](https://github.comgit//DioxusLabs/dioxus/commit/e06eac1ce5c6bb0a6680482574d82b16a141a626))
|
||||||
|
- Implement UseRoute segment method ([`c9408da`](https://github.comgit//DioxusLabs/dioxus/commit/c9408da7310423b1676fab6d41635f9a8000d89e))
|
||||||
- Release dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`a36dab7`](https://github.comgit//DioxusLabs/dioxus/commit/a36dab7f45920acd8535a69b4aa3695f3bb92111))
|
- Release dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`a36dab7`](https://github.comgit//DioxusLabs/dioxus/commit/a36dab7f45920acd8535a69b4aa3695f3bb92111))
|
||||||
|
- Implement router matching for path parameters ([`f8a7e1c`](https://github.comgit//DioxusLabs/dioxus/commit/f8a7e1cd8255fd7d0116384247e0e305bb73bf3d))
|
||||||
|
- Commit WIP on router ([`3c6142f`](https://github.comgit//DioxusLabs/dioxus/commit/3c6142fb9d8b5f715adf0fb30c2f3534b9ecd923))
|
||||||
|
- Add more trace messages to the RouterService code ([`3a5b417`](https://github.comgit//DioxusLabs/dioxus/commit/3a5b417ad1639a31e9aac9f41b05d8b3f074b128))
|
||||||
- Release dioxus-core v0.1.7, dioxus-core-macro v0.1.6, dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`40d1f85`](https://github.comgit//DioxusLabs/dioxus/commit/40d1f85d0c3e2c9fd23c08840cca9f459d4e4307))
|
- Release dioxus-core v0.1.7, dioxus-core-macro v0.1.6, dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`40d1f85`](https://github.comgit//DioxusLabs/dioxus/commit/40d1f85d0c3e2c9fd23c08840cca9f459d4e4307))
|
||||||
|
- Fix typo in RouterService struct's "registered_routes" field name ([`d367e0f`](https://github.comgit//DioxusLabs/dioxus/commit/d367e0f89f1b31484bde42efaf10c3531aed1d64))
|
||||||
|
- Add title prop to Link ([`e22ba5b`](https://github.comgit//DioxusLabs/dioxus/commit/e22ba5b1e52fe00e08e0b6ac0abae29b6068b8f0))
|
||||||
- add prevent default attribute and upgrade router ([`427b126`](https://github.comgit//DioxusLabs/dioxus/commit/427b126bc17336d5d14d56eb7fddb8e07752495f))
|
- add prevent default attribute and upgrade router ([`427b126`](https://github.comgit//DioxusLabs/dioxus/commit/427b126bc17336d5d14d56eb7fddb8e07752495f))
|
||||||
- memoize dom in the prescence of identical components ([`cb2782b`](https://github.comgit//DioxusLabs/dioxus/commit/cb2782b4bb34cdaadfff590bfee930ae3ac6536c))
|
- memoize dom in the prescence of identical components ([`cb2782b`](https://github.comgit//DioxusLabs/dioxus/commit/cb2782b4bb34cdaadfff590bfee930ae3ac6536c))
|
||||||
- bump all versions ([`4f92ba4`](https://github.comgit//DioxusLabs/dioxus/commit/4f92ba41602d706449c1bddabd49829873ee72eb))
|
- bump all versions ([`4f92ba4`](https://github.comgit//DioxusLabs/dioxus/commit/4f92ba41602d706449c1bddabd49829873ee72eb))
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "dioxus-router"
|
name = "dioxus-router"
|
||||||
version = "0.1.0"
|
version = "0.1.1"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
description = "Dioxus VirtualDOM renderer for the web browser using websys"
|
description = "Dioxus VirtualDOM renderer for the web browser using websys"
|
||||||
license = "MIT/Apache-2.0"
|
license = "MIT/Apache-2.0"
|
||||||
@@ -11,9 +11,9 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
dioxus-core = { path = "../core", version ="^0.1.7", default-features = false }
|
dioxus-core = { path = "../core", version = "^0.1.9", default-features = false }
|
||||||
dioxus-html = { path = "../html", version ="^0.1.4", default-features = false }
|
dioxus-html = { path = "../html", version = "^0.1.6", default-features = false }
|
||||||
dioxus-core-macro = { path = "../core-macro", version ="^0.1.6"}
|
dioxus-core-macro = { path = "../core-macro", version = "^0.1.7" }
|
||||||
|
|
||||||
serde = "1"
|
serde = "1"
|
||||||
url = "2.2.2"
|
url = "2.2.2"
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ DioxusRouter adds React-Router style routing to your Dioxus apps. Works in brows
|
|||||||
```rust
|
```rust
|
||||||
fn app() {
|
fn app() {
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
Routes {
|
Router {
|
||||||
Route { to: "/", Component {} },
|
Route { to: "/", Component {} },
|
||||||
Route { to: "/blog", Blog {} },
|
Route { to: "/blog", Blog {} },
|
||||||
Route { to: "/blog/:id", BlogPost {} },
|
Route { to: "/blog/:id", BlogPost {} },
|
||||||
@@ -16,7 +16,7 @@ fn app() {
|
|||||||
|
|
||||||
Then, in your route, you can choose to parse the Route any way you want through `use_route`.
|
Then, in your route, you can choose to parse the Route any way you want through `use_route`.
|
||||||
```rust
|
```rust
|
||||||
let id: usize = use_route(&cx).path("id")?;
|
let id: usize = use_route(&cx).segment("id")?;
|
||||||
|
|
||||||
let state: CustomState = use_route(&cx).parse()?;
|
let state: CustomState = use_route(&cx).parse()?;
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ pub struct LinkProps<'a> {
|
|||||||
|
|
||||||
/// The url that gets pushed to the history stack
|
/// The url that gets pushed to the history stack
|
||||||
///
|
///
|
||||||
/// You can either put it your own inline method or just autoderive the route using `derive(Routable)`
|
/// You can either put in your own inline method or just autoderive the route using `derive(Routable)`
|
||||||
///
|
///
|
||||||
/// ```rust, ignore
|
/// ```rust, ignore
|
||||||
///
|
///
|
||||||
@@ -41,7 +41,7 @@ pub struct LinkProps<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn Link<'a>(cx: Scope<'a, LinkProps<'a>>) -> Element {
|
pub fn Link<'a>(cx: Scope<'a, LinkProps<'a>>) -> Element {
|
||||||
log::debug!("render Link to {}", cx.props.to);
|
// log::trace!("render Link to {}", cx.props.to);
|
||||||
if let Some(service) = cx.consume_context::<RouterService>() {
|
if let Some(service) = cx.consume_context::<RouterService>() {
|
||||||
return cx.render(rsx! {
|
return cx.render(rsx! {
|
||||||
a {
|
a {
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ pub fn Route<'a>(cx: Scope<'a, RouteProps<'a>>) -> Element {
|
|||||||
Some(ctx) => ctx.total_route.to_string(),
|
Some(ctx) => ctx.total_route.to_string(),
|
||||||
None => cx.props.to.to_string(),
|
None => cx.props.to.to_string(),
|
||||||
};
|
};
|
||||||
log::trace!("total route for {} is {}", cx.props.to, total_route);
|
// log::trace!("total route for {} is {}", cx.props.to, total_route);
|
||||||
|
|
||||||
// provide our route context
|
// provide our route context
|
||||||
let route_context = cx.provide_context(RouteContext {
|
let route_context = cx.provide_context(RouteContext {
|
||||||
@@ -48,7 +48,7 @@ pub fn Route<'a>(cx: Scope<'a, RouteProps<'a>>) -> Element {
|
|||||||
Some(RouteInner {})
|
Some(RouteInner {})
|
||||||
});
|
});
|
||||||
|
|
||||||
log::trace!("Checking route {}", cx.props.to);
|
// log::trace!("Checking route {}", cx.props.to);
|
||||||
|
|
||||||
if router_root.should_render(cx.scope_id()) {
|
if router_root.should_render(cx.scope_id()) {
|
||||||
cx.render(rsx!(&cx.props.children))
|
cx.render(rsx!(&cx.props.children))
|
||||||
|
|||||||
@@ -11,17 +11,27 @@ use crate::RouterService;
|
|||||||
pub struct RouterProps<'a> {
|
pub struct RouterProps<'a> {
|
||||||
children: Element<'a>,
|
children: Element<'a>,
|
||||||
|
|
||||||
#[props(default, strip_option)]
|
#[props(default)]
|
||||||
onchange: Option<&'a dyn Fn(&'a str)>,
|
onchange: EventHandler<'a, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
pub fn Router<'a>(cx: Scope<'a, RouterProps<'a>>) -> Element {
|
pub fn Router<'a>(cx: Scope<'a, RouterProps<'a>>) -> Element {
|
||||||
cx.use_hook(|_| {
|
log::debug!("running router {:?}", cx.scope_id());
|
||||||
|
let svc = cx.use_hook(|_| {
|
||||||
let update = cx.schedule_update_any();
|
let update = cx.schedule_update_any();
|
||||||
cx.provide_context(RouterService::new(update, cx.scope_id()))
|
cx.provide_context(RouterService::new(update, cx.scope_id()))
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let any_pending = svc.pending_events.borrow().len() > 0;
|
||||||
|
svc.pending_events.borrow_mut().clear();
|
||||||
|
|
||||||
|
if any_pending {
|
||||||
|
let location = svc.current_location();
|
||||||
|
let path = location.path();
|
||||||
|
cx.props.onchange.call(path.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
cx.render(rsx!(
|
cx.render(rsx!(
|
||||||
div { &cx.props.children }
|
div { &cx.props.children }
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use dioxus_core::ScopeState;
|
use dioxus_core::{ScopeId, ScopeState};
|
||||||
use gloo::history::{HistoryResult, Location};
|
use gloo::history::{HistoryResult, Location};
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
use std::{rc::Rc, str::FromStr};
|
use std::{rc::Rc, str::FromStr};
|
||||||
@@ -8,6 +8,7 @@ use crate::RouterService;
|
|||||||
/// This struct provides is a wrapper around the internal router
|
/// This struct provides is a wrapper around the internal router
|
||||||
/// implementation, with methods for getting information about the current
|
/// implementation, with methods for getting information about the current
|
||||||
/// route.
|
/// route.
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct UseRoute {
|
pub struct UseRoute {
|
||||||
router: Rc<RouterService>,
|
router: Rc<RouterService>,
|
||||||
}
|
}
|
||||||
@@ -74,10 +75,40 @@ impl UseRoute {
|
|||||||
/// This hook provides access to information about the current location in the
|
/// This hook provides access to information about the current location in the
|
||||||
/// context of a [`Router`]. If this function is called outside of a `Router`
|
/// context of a [`Router`]. If this function is called outside of a `Router`
|
||||||
/// component it will panic.
|
/// component it will panic.
|
||||||
pub fn use_route(cx: &ScopeState) -> UseRoute {
|
pub fn use_route(cx: &ScopeState) -> &UseRoute {
|
||||||
let router = cx
|
&cx.use_hook(|_| {
|
||||||
.consume_context::<RouterService>()
|
let router = cx
|
||||||
.expect("Cannot call use_route outside the scope of a Router component")
|
.consume_context::<RouterService>()
|
||||||
.clone();
|
.expect("Cannot call use_route outside the scope of a Router component");
|
||||||
UseRoute { router }
|
|
||||||
|
router.subscribe_onchange(cx.scope_id());
|
||||||
|
|
||||||
|
UseRouteListener {
|
||||||
|
router: UseRoute { router },
|
||||||
|
scope: cx.scope_id(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.router
|
||||||
|
}
|
||||||
|
|
||||||
|
// The entire purpose of this struct is to unubscribe this component when it is unmounted.
|
||||||
|
// The UseRoute can be cloned into async contexts, so we can't rely on its drop to unubscribe.
|
||||||
|
// Instead, we hide the drop implementation on this private type exclusive to the hook,
|
||||||
|
// and reveal our cached version of UseRoute to the component.
|
||||||
|
struct UseRouteListener {
|
||||||
|
router: UseRoute,
|
||||||
|
scope: ScopeId,
|
||||||
|
}
|
||||||
|
impl Drop for UseRouteListener {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.router.router.unsubscribe_onchange(self.scope)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This hook provides access to the `RouterService` for the app.
|
||||||
|
pub fn use_router(cx: &ScopeState) -> &RouterService {
|
||||||
|
cx.use_hook(|_| {
|
||||||
|
cx.consume_context::<RouterService>()
|
||||||
|
.expect("Cannot call use_route outside the scope of a Router component")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
Loaded 100 of 113 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user