mirror of https://github.com/yewstack/yew
Rip out stdweb (#1697)
* feat: Remove usage of stdweb * Cleanup Cargo.toml * yew-services fixes * fix doc test Co-authored-by: Justin Starry <justin.starry@icloud.com>
This commit is contained in:
parent
5adb142be5
commit
5fc4387dc5
|
@ -1,2 +1,2 @@
|
|||
[target.'cfg(all(target_arch = "wasm32", not(cargo_web)))']
|
||||
[target.'cfg(target_arch = "wasm32")']
|
||||
runner = 'wasm-bindgen-test-runner'
|
||||
|
|
|
@ -27,8 +27,7 @@ If applicable, add screenshots to help explain your problem.
|
|||
- Yew version: [e.g. v0.17, `master`]
|
||||
- Rust version: [e.g. 1.43.0, `nightly`]
|
||||
- Target, if relevant: [e.g. `wasm32-unknown-emscripten`]
|
||||
- Build tool, if relevant: [e.g. `wasm-pack`, `cargo-web`]
|
||||
- Web library: [`stdweb` OR `web-sys`]
|
||||
- Build tool, if relevant: [e.g. `wasm-pack`, `trunk`]
|
||||
- OS, if relevant: [e.g. MacOS]
|
||||
- Browser and version, if relevant: [e.g. Chrome v83]
|
||||
|
||||
|
|
|
@ -24,7 +24,6 @@ jobs:
|
|||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
target
|
||||
yew-stdweb/target
|
||||
key: cargo-${{ runner.os }}-${{ hashFiles('**/Cargo.toml') }}
|
||||
restore-keys: |
|
||||
cargo-${{ runner.os }}-
|
||||
|
@ -48,12 +47,6 @@ jobs:
|
|||
cd packages/yew
|
||||
cargo clippy --all-targets --features "cbor msgpack toml yaml" -- -D warnings
|
||||
|
||||
- name: Run clippy - yew-stdweb with all features
|
||||
if: always()
|
||||
run: |
|
||||
cd packages/yew-stdweb
|
||||
cargo clippy --all-targets --features "cbor msgpack toml yaml" -- -D warnings
|
||||
|
||||
check_examples:
|
||||
name: Check Examples
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -119,15 +112,6 @@ jobs:
|
|||
cd packages/yew
|
||||
cargo test --doc --features "doc_test wasm_test yaml msgpack cbor toml"
|
||||
|
||||
- name: Run doctest - yew-stdweb with features
|
||||
run: |
|
||||
# Sadly we can't run the tests on yew-stdweb as the snippets use `yew`, not `yew_stdweb` so the imports wouldn't work.
|
||||
# To fix this we just run them on yew but with yew-stdweb's default features enabled.
|
||||
cd packages/yew
|
||||
cargo test --doc \
|
||||
--no-default-features --features "agent std_web" \
|
||||
--features "doc_test wasm_test yaml msgpack cbor toml"
|
||||
|
||||
integration_tests:
|
||||
name: Integration Tests on ${{ matrix.toolchain }}
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -165,7 +149,6 @@ jobs:
|
|||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
target
|
||||
yew-stdweb/target
|
||||
key: cargo-${{ runner.os }}-${{ hashFiles('**/Cargo.toml') }}
|
||||
restore-keys: |
|
||||
cargo-${{ runner.os }}-
|
||||
|
@ -175,12 +158,6 @@ jobs:
|
|||
cd packages/yew
|
||||
wasm-pack test --chrome --firefox --headless -- --features "wasm_test"
|
||||
|
||||
- name: Run tests - yew-stdweb
|
||||
if: matrix.toolchain != 'stable'
|
||||
run: |
|
||||
cd packages/yew-stdweb
|
||||
wasm-pack test --chrome --firefox --headless -- --features "wasm_test"
|
||||
|
||||
- name: Run tests - yew-functional
|
||||
run: |
|
||||
cd packages/yew-functional
|
||||
|
|
|
@ -22,14 +22,6 @@ cargo make --list-all-steps
|
|||
|
||||
The most important tasks are outlined below.
|
||||
|
||||
#### stdweb
|
||||
|
||||
To run the examples in `./yew-stdweb`, you may wish to install [cargo-web](https://github.com/koute/cargo-web):
|
||||
|
||||
```bash
|
||||
cargo install cargo-web
|
||||
```
|
||||
|
||||
## Tests
|
||||
|
||||
To run all tests, use the following command:
|
||||
|
|
|
@ -29,7 +29,7 @@ run_task = { name = ["lint", "tests"], fork = true }
|
|||
[tasks.lint]
|
||||
category = "Checks"
|
||||
description = "Check formatting and run Clippy"
|
||||
run_task = { name = ["lint-flow", "lint-stdweb"], fork = true }
|
||||
run_task = { name = ["lint-flow"], fork = true }
|
||||
|
||||
[tasks.tests]
|
||||
category = "Testing"
|
||||
|
@ -38,7 +38,6 @@ dependencies = ["tests-setup"]
|
|||
env = { CARGO_MAKE_WORKSPACE_SKIP_MEMBERS = ["**/examples/*"] }
|
||||
run_task = { name = [
|
||||
"test-flow",
|
||||
"tests-stdweb",
|
||||
"doc-test-flow",
|
||||
], fork = true, cleanup_task = "tests-cleanup" }
|
||||
|
||||
|
@ -53,19 +52,6 @@ private = true
|
|||
workspace = true
|
||||
dependencies = ["core::check-format-flow", "core::clippy-flow"]
|
||||
|
||||
[tasks.lint-stdweb]
|
||||
private = true
|
||||
cwd = "packages/yew-stdweb"
|
||||
command = "cargo"
|
||||
args = [
|
||||
"make",
|
||||
"--loglevel",
|
||||
"${CARGO_MAKE_LOG_LEVEL}",
|
||||
"--profile",
|
||||
"${CARGO_MAKE_PROFILE}",
|
||||
"lint-flow",
|
||||
]
|
||||
|
||||
[tasks.tests-setup]
|
||||
private = true
|
||||
script_runner = "@duckscript"
|
||||
|
|
|
@ -7,10 +7,8 @@
|
|||
- [Project Setup](getting-started/project-setup.md)
|
||||
- [Using trunk](getting-started/project-setup/using-trunk.md)
|
||||
- [Using wasm-pack](getting-started/project-setup/using-wasm-pack.md)
|
||||
- [Using cargo-web](getting-started/project-setup/using-cargo-web.md)
|
||||
- [Starter Templates](getting-started/starter-templates.md)
|
||||
- [Build a Sample App](getting-started/build-a-sample-app.md)
|
||||
- [Choose web-sys or stdweb](getting-started/choose-web-library.md)
|
||||
- [Learn through examples](getting-started/examples.md)
|
||||
|
||||
## Core Concepts <a id="concepts"></a>
|
||||
|
|
|
@ -74,8 +74,7 @@ is also a parameter called `first_render` which can be used to determine whether
|
|||
being called on the first render, or instead a subsequent one.
|
||||
|
||||
```rust
|
||||
use stdweb::web::html_element::InputElement;
|
||||
use stdweb::web::IHtmlElement;
|
||||
use web_sys::HtmlInputElement;
|
||||
use yew::prelude::*;
|
||||
|
||||
pub struct MyComponent {
|
||||
|
@ -93,7 +92,7 @@ impl Component for MyComponent {
|
|||
|
||||
fn rendered(&mut self, first_render: bool) {
|
||||
if first_render {
|
||||
if let Some(input) = self.node_ref.cast::<InputElement>() {
|
||||
if let Some(input) = self.node_ref.cast::<HtmlInputElement>() {
|
||||
input.focus();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -148,108 +148,106 @@ impl Component for MyComponent {
|
|||
|
||||
## Event Types
|
||||
|
||||
In the following table `web-sys`'s event types should only be used if you're using `yew` with `web-sys`
|
||||
(this is enabled by default). Use `stdweb`'s event types if you're using the `yew-stdweb` crate. See
|
||||
[the documentation page about whether to choose `web-sys` or `stdweb`](https://yew.rs/docs/getting-started/choose-web-library) for more information.
|
||||
|
||||
:::tip
|
||||
All the event types mentioned in the following table are re-exported under `yew::events`.
|
||||
Using the types from `yew::events` makes it easier to ensure version compatibility than if you were to manually include `web-sys` or `stdweb` as dependencies in your crate because you won't end up using a version which conflicts with the version Yew specifies.
|
||||
Using the types from `yew::events` makes it easier to ensure version compatibility than
|
||||
if you were to manually include `web-sys` as a dependency in your crate because you won't
|
||||
end up using a version which conflicts with the version that Yew specifies.
|
||||
:::
|
||||
|
||||
| Event name | `web_sys` Event Type | `stdweb` Event Type |
|
||||
| --------------------------- | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
|
||||
| `onabort` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | [ResourceAbortEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.ResourceAbortEvent.html) |
|
||||
| `onauxclick` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) | [AuxClickEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.AuxClickEvent.html) |
|
||||
| `onblur` | [FocusEvent](https://docs.rs/web-sys/latest/web_sys/struct.FocusEvent.html) | [BlurEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.BlurEvent.html) |
|
||||
| `oncancel` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
|
||||
| `oncanplay` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
|
||||
| `oncanplaythrough` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
|
||||
| `onchange` | [ChangeData](https://docs.rs/yew/latest/yew/events/enum.ChangeData.html) | [ChangeData](https://docs.rs/yew-stdweb/latest/yew_stdweb/events/enum.ChangeData.html) |
|
||||
| `onclick` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) | [ClickEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.ClickEvent.html) |
|
||||
| `onclose` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
|
||||
| `oncontextmenu` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) | [ContextMenuEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.ContextMenuEvent.html) |
|
||||
| `oncuechange` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
|
||||
| `ondblclick` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) | [DoubleClickEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.DoubleClickEvent.html) |
|
||||
| `ondrag` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.html) | [DragEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.DragEvent.html) |
|
||||
| `ondragend` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.html) | [DragEndEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.DragEndEvent.html) |
|
||||
| `ondragenter` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.html) | [DragEnterEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.DragEnterEvent.html) |
|
||||
| `ondragexit` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.html) | [DragExitEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.DragExitEvent.html) |
|
||||
| `ondragleave` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.htmk) | [DragLeaveEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.DragLeaveEvent.html) |
|
||||
| `ondragover` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.html) | [DragOverEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.DragOverEvent.html) |
|
||||
| `ondragstart` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.html) | [DragStartEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.DragStartEvent.html) |
|
||||
| `ondrop` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.html) | [DragDropEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.DragDropEvent.html) |
|
||||
| `ondurationchange` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
|
||||
| `onemptied` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
|
||||
| `onended` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
|
||||
| `onerror` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | [ResourceErrorEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.ResourceErrorEvent.html) |
|
||||
| `onfocus` | [FocusEvent](https://docs.rs/web-sys/latest/web_sys/struct.FocusEvent.html) | [FocusEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.FocusEvent.html) |
|
||||
| `onformdata` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
|
||||
| `oninput` | [InputData](https://docs.rs/yew/latest/yew/events/struct.InputData.html) | [InputData](https://docs.rs/yew-stdweb/latest/yew_stdweb/events/struct.InputData.html) |
|
||||
| `oninvalid` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
|
||||
| `onkeydown` | [KeyboardEvent](https://docs.rs/web-sys/latest/web_sys/struct.KeyboardEvent.html) | [KeyDownEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.KeyDownEvent.html) |
|
||||
| `onkeypress` | [KeyboardEvent](https://docs.rs/web-sys/latest/web_sys/struct.KeyboardEvent.html) | [KeyPressEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.KeyPressEvent.html) |
|
||||
| `onkeyup` | [KeyboardEvent](https://docs.rs/web-sys/latest/web_sys/struct.KeyboardEvent.html) | [KeyUpEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.KeyUpEvent.html) |
|
||||
| `onload` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | [ResourceLoadEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.ResourceLoadEvent.html) |
|
||||
| `onloadeddata` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
|
||||
| `onloadedmetadata` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
|
||||
| `onloadstart` | [ProgressEvent](https://docs.rs/web-sys/latest/web_sys/struct.ProgressEvent.html) | [LoadStartEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.LoadStartEvent.html) |
|
||||
| `onmousedown` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) | [MouseDownEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.MouseDownEvent.html) |
|
||||
| `onmouseenter` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) | [MouseEnterEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.MouseEnterEvent.html) |
|
||||
| `onmouseleave` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) | [MouseLeaveEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.MouseLeaveEvent.html) |
|
||||
| `onmousemove` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) | [MouseMoveEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.MouseMoveEvent.html) |
|
||||
| `onmouseout` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) | [MouseOutEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.MouseOutEvent.html) |
|
||||
| `onmouseover` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) | [MouseOverEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.MouseOverEvent.html) |
|
||||
| `onmouseup` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) | [MouseUpEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.MouseUpEvent.html) |
|
||||
| `onpause` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
|
||||
| `onplay` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
|
||||
| `onplaying` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
|
||||
| `onprogress` | [ProgressEvent](https://docs.rs/web-sys/latest/web_sys/struct.ProgressEvent.html) | [ProgressEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.ProgressEvent.html) |
|
||||
| `onratechange` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
|
||||
| `onreset` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
|
||||
| `onresize` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | [ResizeEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.ResizeEvent.html) |
|
||||
| `onscroll` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | [ScrollEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.ScrollEvent.html) |
|
||||
| `onsecuritypolicyviolation` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
|
||||
| `onseeked` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
|
||||
| `onseeking` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
|
||||
| `onselect` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
|
||||
| `onslotchange` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | [SlotChangeEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.SlotChangeEvent.html) |
|
||||
| `onstalled` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
|
||||
| `onsubmit` | [FocusEvent](https://docs.rs/web-sys/latest/web_sys/struct.FocusEvent.html) | [SubmitEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.SubmitEvent.html) |
|
||||
| `onsuspend` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
|
||||
| `ontimeupdate` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
|
||||
| `ontoggle` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
|
||||
| `onvolumechange` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
|
||||
| `onwaiting` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
|
||||
| `onwheel` | [WheelEvent](https://docs.rs/web-sys/latest/web_sys/struct.WheelEvent.html) | [MouseWheelEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.MouseWheelEvent.html) |
|
||||
| `oncopy` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
|
||||
| `oncut` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
|
||||
| `onpaste` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
|
||||
| `onanimationcancel` | [AnimationEvent](https://docs.rs/web-sys/latest/web_sys/struct.AnimationEvent.html) | Unsupported |
|
||||
| `onanimationend` | [AnimationEvent](https://docs.rs/web-sys/latest/web_sys/struct.AnimationEvent.html) | Unsupported |
|
||||
| `onanimationiteration` | [AnimationEvent](https://docs.rs/web-sys/latest/web_sys/struct.AnimationEvent.html) | Unsupported |
|
||||
| `onanimationstart` | [AnimationEvent](https://docs.rs/web-sys/latest/web_sys/struct.AnimationEvent.html) | Unsupported |
|
||||
| `ongotpointercapture` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) | [GotPointerCaptureEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.GotPointerCaptureEvent.html) |
|
||||
| `onloadend` | [ProgressEvent](https://docs.rs/web-sys/latest/web_sys/struct.ProgressEvent.html) | [LoadEndEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.LoadEndEvent.html) |
|
||||
| `onlostpointercapture` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) | [LostPointerCaptureEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.LostPointerCaptureEvent.html) |
|
||||
| `onpointercancel` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) | [PointerCancelEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.PointerCancelEvent.html) |
|
||||
| `onpointerdown` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) | [PointerDownEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.PointerDownEvent.html) |
|
||||
| `onpointerenter` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) | [PointerEnterEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.PointerEnterEvent.html) |
|
||||
| `onpointerleave` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) | [PointerLeaveEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.PointerLeaveEvent.html) |
|
||||
| `onpointerlockchange` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | [PointerLockChangeEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.PointerLockChangeEvent.html) |
|
||||
| `onpointerlockerror` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | [PointerLockErrorEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.PointerLockErrorEvent.html) |
|
||||
| `onpointermove` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) | [PointerMoveEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.PointerMoveEvent.html) |
|
||||
| `onpointerout` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) | [PointerOutEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.PointerOutEvent.html) |
|
||||
| `onpointerover` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) | [PointerOverEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.PointerOverEvent.html) |
|
||||
| `onpointerup` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) | [PointerUpEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.PointerUpEvent.html) |
|
||||
| `onselectionchange` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | [SelectionChangeEvent](https://docs.rs/stdweb/latest/stdweb/web/event/struct.SelectionChangeEvent.html) |
|
||||
| `onselectstart` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
|
||||
| `onshow` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | Unsupported |
|
||||
| `ontouchcancel` | [TouchEvent](https://docs.rs/web-sys/latest/web_sys/struct.TouchEvent.html) | [TouchCancel](https://docs.rs/stdweb/latest/stdweb/web/event/struct.TouchCancel.html) |
|
||||
| `ontouchend` | [TouchEvent](https://docs.rs/web-sys/latest/web_sys/struct.TouchEvent.html) | [TouchEnd](https://docs.rs/stdweb/latest/stdweb/web/event/struct.TouchEnd.html) |
|
||||
| `ontouchmove` | [TouchEvent](https://docs.rs/web-sys/latest/web_sys/struct.TouchEvent.html) | [TouchMove](https://docs.rs/stdweb/latest/stdweb/web/event/struct.TouchMove.html) |
|
||||
| `ontouchstart` | [TouchEvent](https://docs.rs/web-sys/latest/web_sys/struct.TouchEvent.html) | [TouchStart](https://docs.rs/stdweb/latest/stdweb/web/event/struct.TouchStart.html) |
|
||||
| `ontransitioncancel` | [TransitionEvent](https://docs.rs/web-sys/latest/web_sys/struct.TransitionEvent.html) | Unsupported |
|
||||
| `ontransitionend` | [TransitionEvent](https://docs.rs/web-sys/latest/web_sys/struct.TransitionEvent.html) | Unsupported |
|
||||
| `ontransitionrun` | [TransitionEvent](https://docs.rs/web-sys/latest/web_sys/struct.TransitionEvent.html) | Unsupported |
|
||||
| `ontransitionstart` | [TransitionEvent](https://docs.rs/web-sys/latest/web_sys/struct.TransitionEvent.html) | Unsupported |
|
||||
| Event name | `web_sys` Event Type |
|
||||
| --------------------------- | ------------------------------------------------------------------------------------- |
|
||||
| `onabort` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onauxclick` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) |
|
||||
| `onblur` | [FocusEvent](https://docs.rs/web-sys/latest/web_sys/struct.FocusEvent.html) |
|
||||
| `oncancel` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `oncanplay` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `oncanplaythrough` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onchange` | [ChangeData](https://docs.rs/yew/latest/yew/events/enum.ChangeData.html) |
|
||||
| `onclick` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) |
|
||||
| `onclose` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `oncontextmenu` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) |
|
||||
| `oncuechange` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `ondblclick` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) |
|
||||
| `ondrag` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.html) |
|
||||
| `ondragend` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.html) |
|
||||
| `ondragenter` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.html) |
|
||||
| `ondragexit` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.html) |
|
||||
| `ondragleave` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.htmk) |
|
||||
| `ondragover` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.html) |
|
||||
| `ondragstart` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.html) |
|
||||
| `ondrop` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.html) |
|
||||
| `ondurationchange` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onemptied` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onended` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onerror` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onfocus` | [FocusEvent](https://docs.rs/web-sys/latest/web_sys/struct.FocusEvent.html) |
|
||||
| `onformdata` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `oninput` | [InputData](https://docs.rs/yew/latest/yew/events/struct.InputData.html) |
|
||||
| `oninvalid` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onkeydown` | [KeyboardEvent](https://docs.rs/web-sys/latest/web_sys/struct.KeyboardEvent.html) |
|
||||
| `onkeypress` | [KeyboardEvent](https://docs.rs/web-sys/latest/web_sys/struct.KeyboardEvent.html) |
|
||||
| `onkeyup` | [KeyboardEvent](https://docs.rs/web-sys/latest/web_sys/struct.KeyboardEvent.html) |
|
||||
| `onload` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onloadeddata` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onloadedmetadata` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onloadstart` | [ProgressEvent](https://docs.rs/web-sys/latest/web_sys/struct.ProgressEvent.html) |
|
||||
| `onmousedown` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) |
|
||||
| `onmouseenter` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) |
|
||||
| `onmouseleave` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) |
|
||||
| `onmousemove` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) |
|
||||
| `onmouseout` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) |
|
||||
| `onmouseover` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) |
|
||||
| `onmouseup` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) |
|
||||
| `onpause` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onplay` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onplaying` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onprogress` | [ProgressEvent](https://docs.rs/web-sys/latest/web_sys/struct.ProgressEvent.html) |
|
||||
| `onratechange` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onreset` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onresize` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onscroll` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onsecuritypolicyviolation` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onseeked` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onseeking` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onselect` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onslotchange` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onstalled` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onsubmit` | [FocusEvent](https://docs.rs/web-sys/latest/web_sys/struct.FocusEvent.html) |
|
||||
| `onsuspend` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `ontimeupdate` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `ontoggle` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onvolumechange` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onwaiting` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onwheel` | [WheelEvent](https://docs.rs/web-sys/latest/web_sys/struct.WheelEvent.html) |
|
||||
| `oncopy` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `oncut` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onpaste` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onanimationcancel` | [AnimationEvent](https://docs.rs/web-sys/latest/web_sys/struct.AnimationEvent.html) |
|
||||
| `onanimationend` | [AnimationEvent](https://docs.rs/web-sys/latest/web_sys/struct.AnimationEvent.html) |
|
||||
| `onanimationiteration` | [AnimationEvent](https://docs.rs/web-sys/latest/web_sys/struct.AnimationEvent.html) |
|
||||
| `onanimationstart` | [AnimationEvent](https://docs.rs/web-sys/latest/web_sys/struct.AnimationEvent.html) |
|
||||
| `ongotpointercapture` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) |
|
||||
| `onloadend` | [ProgressEvent](https://docs.rs/web-sys/latest/web_sys/struct.ProgressEvent.html) |
|
||||
| `onlostpointercapture` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) |
|
||||
| `onpointercancel` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) |
|
||||
| `onpointerdown` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) |
|
||||
| `onpointerenter` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) |
|
||||
| `onpointerleave` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) |
|
||||
| `onpointerlockchange` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onpointerlockerror` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onpointermove` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) |
|
||||
| `onpointerout` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) |
|
||||
| `onpointerover` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) |
|
||||
| `onpointerup` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) |
|
||||
| `onselectionchange` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onselectstart` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `onshow` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) |
|
||||
| `ontouchcancel` | [TouchEvent](https://docs.rs/web-sys/latest/web_sys/struct.TouchEvent.html) |
|
||||
| `ontouchend` | [TouchEvent](https://docs.rs/web-sys/latest/web_sys/struct.TouchEvent.html) |
|
||||
| `ontouchmove` | [TouchEvent](https://docs.rs/web-sys/latest/web_sys/struct.TouchEvent.html) |
|
||||
| `ontouchstart` | [TouchEvent](https://docs.rs/web-sys/latest/web_sys/struct.TouchEvent.html) |
|
||||
| `ontransitioncancel` | [TransitionEvent](https://docs.rs/web-sys/latest/web_sys/struct.TransitionEvent.html) |
|
||||
| `ontransitionend` | [TransitionEvent](https://docs.rs/web-sys/latest/web_sys/struct.TransitionEvent.html) |
|
||||
| `ontransitionrun` | [TransitionEvent](https://docs.rs/web-sys/latest/web_sys/struct.TransitionEvent.html) |
|
||||
| `ontransitionstart` | [TransitionEvent](https://docs.rs/web-sys/latest/web_sys/struct.TransitionEvent.html) |
|
||||
|
|
|
@ -1,126 +0,0 @@
|
|||
---
|
||||
title: Choosing a web library
|
||||
---
|
||||
|
||||
## Introduction
|
||||
|
||||
Yew apps can be built using either [`web-sys`](https://docs.rs/web-sys) or [`stdweb`](https://docs.rs/stdweb).
|
||||
These two crates provide the bindings between Rust and Web APIs. You'll need to choose one or the other when adding
|
||||
`yew` to your cargo dependencies:
|
||||
|
||||
```toml
|
||||
# Choose `web-sys`
|
||||
yew = "0.17"
|
||||
|
||||
# Choose `stdweb`
|
||||
yew = { version = "0.17", package = "yew-stdweb" }
|
||||
```
|
||||
|
||||
We recommend using `web-sys` due to its support from the [Rust / Wasm Working Group](https://rustwasm.github.io/).
|
||||
|
||||
:::warning
|
||||
Yew will freeze support for `stdweb` at v0.18.
|
||||
It will still receive patch fixes, but no new features will be added.
|
||||
See [#1569](https://github.com/yewstack/yew/issues/1569)
|
||||
:::
|
||||
|
||||
## Example Usage
|
||||
|
||||
This example illustrates the difference in how the two libraries are used.
|
||||
You don't need to run this yourself.
|
||||
|
||||
```rust
|
||||
// web-sys
|
||||
let window: web_sys::Window = web_sys::window().expect("window not available");
|
||||
window.alert_with_message("hello from wasm!").expect("alert failed");
|
||||
|
||||
// stdweb
|
||||
let window: stdweb::web::Window = stdweb::web::window();
|
||||
window.alert("hello from wasm!");
|
||||
|
||||
// stdweb with js! macro
|
||||
use stdweb::js;
|
||||
use stdweb::unstable::TryFrom;
|
||||
use stdweb::web::Window;
|
||||
|
||||
let window_val: stdweb::Value = js!{ return window; }; // <- JS syntax inside!
|
||||
let window = Window::try_from(window_val).expect("conversion to window failed");
|
||||
window.alert("hello from wasm!");
|
||||
```
|
||||
|
||||
The APIs for the two crates differ slightly but they serve roughly the same purpose.
|
||||
|
||||
## Choosing One
|
||||
|
||||
There are a few different angles to consider when choosing between using `web-sys` and `stdweb` for your app.
|
||||
Note that it's possible to use both in one app, but to minimize the binary size of your compiled crate it's best to use only one of the two.
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="text-align:left"></th>
|
||||
<th style="text-align:left"><code>web-sys</code>
|
||||
</th>
|
||||
<th style="text-align:left"><code>stdweb</code>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="text-align:left">Project Status</td>
|
||||
<td style="text-align:left">Actively maintained by the <a href="https://rustwasm.github.io/">Rust / Wasm Working Group</a>
|
||||
</td>
|
||||
<td style="text-align:left">No Github activity for over 8 months</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="text-align:left">Web API Coverage</td>
|
||||
<td style="text-align:left">Rust APIs are generated from the Web IDL spec</td>
|
||||
<td style="text-align:left">Browser APIs are added as needed by the community</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="text-align:left">Rust API Design</td>
|
||||
<td style="text-align:left">Takes conservative approach by returning <code>Result</code> for most API
|
||||
calls</td>
|
||||
<td style="text-align:left">Often avoids <code>Result</code> in favor of panics. For instance, <code>stdweb::web::window()</code> will
|
||||
panic when called in a worker</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="text-align:left">Supported Build Tools</td>
|
||||
<td style="text-align:left">
|
||||
<p></p>
|
||||
<ul>
|
||||
<li><code>trunk</code>
|
||||
</li>
|
||||
<li><code>wasm-pack</code>
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
<td style="text-align:left">
|
||||
<p></p>
|
||||
<ul>
|
||||
<li><code>cargo-web</code>
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="text-align:left">Supported Targets</td>
|
||||
<td style="text-align:left">
|
||||
<ul>
|
||||
<li><code>wasm32-unknown-unknown</code>
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
<td style="text-align:left">
|
||||
<ul>
|
||||
<li><code>wasm32-unknown-unknown</code>
|
||||
</li>
|
||||
<li><code>wasm32-unknown-emscripten</code>
|
||||
</li>
|
||||
<li><code>asmjs-unknown-emscripten</code>
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
|
@ -43,23 +43,15 @@ Because of this, it can only build libraries and doesn't provide useful tools li
|
|||
|
||||
[Get started with `wasm-pack`](project-setup/using-wasm-pack.md)
|
||||
|
||||
### [**`cargo-web`**](https://github.com/koute/cargo-web)
|
||||
|
||||
This was the best preferred tool to use before the creation of `wasm-bindgen`.
|
||||
|
||||
[Getting started with `cargo web`](project-setup/using-cargo-web.md)
|
||||
|
||||
### Comparison
|
||||
|
||||
| | `trunk` | `wasm-pack` | `cargo-web` |
|
||||
| ----------------------------- | ---------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| Project Status | Actively maintained | Actively maintained by the [Rust / Wasm Working Group](https://rustwasm.github.io) | No Github activity for over 6 months |
|
||||
| Dev Experience | Just works! Batteries included, no external dependencies needed. | Bare-bones. You'll need to write some scripts to streamline the experience or use the webpack plugin. | Works great for code but needs separate asset pipeline. |
|
||||
| Local Server | Supported | Only with webpack plugin | Supported |
|
||||
| Auto rebuild on local changes | Supported | Only with webpack plugin | Supported |
|
||||
| Asset handling | Supported | Only with webpack plugin | Static assets only |
|
||||
| Headless Browser Testing | [In Progress](https://github.com/thedodd/trunk/issues/20) | [Supported](https://rustwasm.github.io/wasm-pack/book/commands/test.html) | [Supported](https://github.com/koute/cargo-web#features) |
|
||||
| Supported Targets | <ul><li><code>wasm32-unknown-unknown</code></li></ul> | <ul><li><code>wasm32-unknown-unknown</code></li></ul> | <ul> <li><code>wasm32-unknown-unknown</code></li> <li><code>wasm32-unknown-emscripten</code></li> <li><code>asmjs-unknown-emscripten</code></li> </ul> |
|
||||
| `web-sys` | Compatible | Compatible | Incompatible |
|
||||
| `stdweb` | Incompatible | Compatible | Compatible |
|
||||
| Example Usage | [Sample app](./build-a-sample-app.md) | [Starter template](https://github.com/yewstack/yew-wasm-pack-minimal) | [Build script](https://www.github.com/yewstack/yew/tree/master/packages/yew-stdweb/examples) for `yew-stdweb` examples |
|
||||
| | `trunk` | `wasm-pack` |
|
||||
| ----------------------------- | ---------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
|
||||
| Project Status | Actively maintained | Actively maintained by the [Rust / Wasm Working Group](https://rustwasm.github.io) |
|
||||
| Dev Experience | Just works! Batteries included, no external dependencies needed. | Bare-bones. You'll need to write some scripts to streamline the experience or use the webpack plugin. |
|
||||
| Local Server | Supported | Only with webpack plugin |
|
||||
| Auto rebuild on local changes | Supported | Only with webpack plugin |
|
||||
| Asset handling | Supported | Only with webpack plugin |
|
||||
| Headless Browser Testing | [In Progress](https://github.com/thedodd/trunk/issues/20) | [Supported](https://rustwasm.github.io/wasm-pack/book/commands/test.html) |
|
||||
| Supported Targets | <ul><li><code>wasm32-unknown-unknown</code></li></ul> | <ul><li><code>wasm32-unknown-unknown</code></li></ul> |
|
||||
| Example Usage | [Sample app](./build-a-sample-app.md) | [Starter template](https://github.com/yewstack/yew-wasm-pack-minimal) |
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
---
|
||||
title: Using cargo-web
|
||||
---
|
||||
|
||||
Cargo web is a cargo subcommand for building client web apps. It makes building and deploying web
|
||||
applications incredibly easy. It is also the only toolchain that supports Emscripten targets. Read
|
||||
more [here](https://github.com/koute/cargo-web).
|
||||
|
||||
**Install**
|
||||
|
||||
```bash
|
||||
cargo install cargo-web
|
||||
```
|
||||
|
||||
## Build
|
||||
|
||||
```bash
|
||||
cargo web build
|
||||
```
|
||||
|
||||
## Run
|
||||
|
||||
```bash
|
||||
cargo web start
|
||||
```
|
||||
|
||||
## Supported Targets
|
||||
|
||||
* `wasm32-unknown-unknown`
|
||||
* `wasm32-unknown-emscripten`
|
||||
* `asmjs-unknown-emscripten`
|
||||
|
||||
:::note
|
||||
For `*-emscripten` targets, you'll need to install the Emscripten SDK
|
||||
:::
|
|
@ -32,10 +32,7 @@ edition = "2018"
|
|||
crate-type = ["rlib", "cdylib"]
|
||||
|
||||
[dependencies]
|
||||
# for web_sys
|
||||
yew = "0.17"
|
||||
# or for stdweb
|
||||
# yew = { version = "0.17", package = "yew-stdweb" }
|
||||
wasm-bindgen = "0.2"
|
||||
```
|
||||
|
||||
|
|
|
@ -4,14 +4,9 @@ title: Debugging
|
|||
|
||||
## Panics
|
||||
|
||||
We **strongly recommend** the [`console_error_panic`](https://github.com/rustwasm/console_error_panic_hook)
|
||||
which catches `panic!`s and outputs them to the console. Unfortunately this is not compatible with
|
||||
apps built using `cargo-web`. **You probably don't need to enable this manually.** If you mount your
|
||||
application using `yew::start_app()`, Yew will automatically catch `panic!`s and log them to your
|
||||
browser's console. In some situations you might not be able to use `yew::start_app()` to mount your
|
||||
application, in which case you can call `yew::initialize()` before starting your application to
|
||||
configure this. Under the hood `yew::start_app()` calls `yew::initialize()` (which will enable the
|
||||
`panic!` hook).
|
||||
The [`console_error_panic`](https://github.com/rustwasm/console_error_panic_hook) crate catches
|
||||
`panic!`s and outputs them to the console. Yew will automatically catch `panic!`s and log them to
|
||||
your browser's console.
|
||||
|
||||
## Console Logging
|
||||
|
||||
|
|
|
@ -61,7 +61,6 @@ fn create_canvas(document: &Document) -> HtmlCanvasElement {
|
|||
}
|
||||
|
||||
fn main() {
|
||||
yew::initialize();
|
||||
let document = yew::utils::document();
|
||||
let body = document.query_selector("body").unwrap().unwrap();
|
||||
|
||||
|
@ -76,7 +75,4 @@ fn main() {
|
|||
body.append_child(&mount_point).unwrap();
|
||||
|
||||
yew::App::<Model>::new().mount(mount_point);
|
||||
|
||||
// only required for stdweb
|
||||
yew::run_loop();
|
||||
}
|
||||
|
|
|
@ -81,15 +81,10 @@ fn mount_app(selector: &'static str, app: App<Model>) -> ComponentLink<Model> {
|
|||
}
|
||||
|
||||
fn main() {
|
||||
yew::initialize();
|
||||
|
||||
let first_app = App::new();
|
||||
let second_app = App::new();
|
||||
let to_first = mount_app(".first-app", first_app);
|
||||
let to_second = mount_app(".second-app", second_app);
|
||||
to_first.send_message(Msg::SetOpposite(to_second.clone()));
|
||||
to_second.send_message(Msg::SetOpposite(to_first));
|
||||
|
||||
// this is only important for stdweb
|
||||
yew::run_loop();
|
||||
}
|
||||
|
|
|
@ -31,4 +31,3 @@ yew = { path = "../yew" }
|
|||
|
||||
[features]
|
||||
doc_test = []
|
||||
std_web = []
|
||||
|
|
|
@ -35,21 +35,6 @@ impl Parse for ElementProps {
|
|||
|
||||
let listeners =
|
||||
props.drain_filter(|prop| LISTENER_SET.contains(prop.label.to_string().as_str()));
|
||||
#[cfg(feature = "std_web")]
|
||||
listeners.check_all(|prop| {
|
||||
let label = &prop.label;
|
||||
if UNSUPPORTED_LISTENER_SET.contains(label.to_string().as_str()) {
|
||||
Err(syn::Error::new_spanned(
|
||||
&label,
|
||||
format!(
|
||||
"the listener `{}` is only available when using web-sys",
|
||||
&label
|
||||
),
|
||||
))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
})?;
|
||||
|
||||
// Multiple listener attributes are allowed, but no others
|
||||
props.check_no_duplicates()?;
|
||||
|
@ -225,53 +210,3 @@ lazy_static! {
|
|||
.collect()
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(feature = "std_web")]
|
||||
lazy_static! {
|
||||
static ref UNSUPPORTED_LISTENER_SET: HashSet<&'static str> = {
|
||||
vec![
|
||||
"oncancel",
|
||||
"oncanplay",
|
||||
"oncanplaythrough",
|
||||
"onclose",
|
||||
"oncuechange",
|
||||
"ondurationchange",
|
||||
"onemptied",
|
||||
"onended",
|
||||
"onformdata",
|
||||
"oninvalid",
|
||||
"onloadeddata",
|
||||
"onloadedmetadata",
|
||||
"onpause",
|
||||
"onplay",
|
||||
"onplaying",
|
||||
"onratechange",
|
||||
"onreset",
|
||||
"onsecuritypolicyviolation",
|
||||
"onseeked",
|
||||
"onseeking",
|
||||
"onselect",
|
||||
"onstalled",
|
||||
"onsuspend",
|
||||
"ontimeupdate",
|
||||
"ontoggle",
|
||||
"onvolumechange",
|
||||
"onwaiting",
|
||||
"oncopy",
|
||||
"oncut",
|
||||
"onpaste",
|
||||
"onanimationcancel",
|
||||
"onanimationend",
|
||||
"onanimationiteration",
|
||||
"onanimationstart",
|
||||
"onselectstart",
|
||||
"onshow",
|
||||
"ontransitioncancel",
|
||||
"ontransitionend",
|
||||
"ontransitionrun",
|
||||
"ontransitionstart",
|
||||
]
|
||||
.into_iter()
|
||||
.collect()
|
||||
};
|
||||
}
|
||||
|
|
|
@ -34,9 +34,9 @@ error[E0277]: `()` doesn't implement `std::fmt::Display`
|
|||
15 | <>{ for (0..3).map(|_| not_tree()) }</>
|
||||
| ^^^^^^ `()` cannot be formatted with the default formatter
|
||||
|
|
||||
::: $WORKSPACE/packages/yew/src/utils.rs
|
||||
::: $WORKSPACE/packages/yew/src/utils.rs:51:8
|
||||
|
|
||||
| T: Into<R>,
|
||||
51 | T: Into<R>,
|
||||
| ------- required by this bound in `yew::utils::into_node_iter`
|
||||
|
|
||||
= help: the trait `std::fmt::Display` is not implemented for `()`
|
||||
|
|
|
@ -71,9 +71,9 @@ error[E0277]: `()` is not an iterator
|
|||
18 | { for () }
|
||||
| ^^ `()` is not an iterator
|
||||
|
|
||||
::: $WORKSPACE/packages/yew/src/utils.rs
|
||||
::: $WORKSPACE/packages/yew/src/utils.rs:50:9
|
||||
|
|
||||
| IT: IntoIterator<Item = T>,
|
||||
50 | IT: IntoIterator<Item = T>,
|
||||
| ---------------------- required by this bound in `yew::utils::into_node_iter`
|
||||
|
|
||||
= help: the trait `std::iter::Iterator` is not implemented for `()`
|
||||
|
|
|
@ -11,44 +11,29 @@ description = "A router implementation for the Yew framework"
|
|||
repository = "https://github.com/yewstack/yew"
|
||||
|
||||
[features]
|
||||
default = ["web_sys", "core", "unit_alias"]
|
||||
default = ["core", "unit_alias"]
|
||||
core = ["router", "components"] # Most everything
|
||||
unit_alias = [] # TODO remove this
|
||||
router = ["agent"] # The Router component
|
||||
components = ["agent" ] # The button and anchor
|
||||
agent = ["service"] # The RouteAgent
|
||||
service = ["yew"] # The RouteService
|
||||
std_web = [
|
||||
"yew/std_web",
|
||||
"stdweb"
|
||||
]
|
||||
web_sys = [
|
||||
"yew/web_sys",
|
||||
"gloo",
|
||||
"js-sys",
|
||||
"web-sys",
|
||||
"wasm-bindgen"
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
yew = { version = "0.17.0", path = "../yew", features = ["agent"], default-features= false, optional = true }
|
||||
yew-router-macro = { version = "0.14.0", path = "../yew-router-macro" }
|
||||
yew-router-route-parser = { version = "0.14.0", path = "../yew-router-route-parser" }
|
||||
|
||||
cfg-if = "1.0.0"
|
||||
cfg-match = "0.2"
|
||||
gloo = { version = "0.2.0", optional = true }
|
||||
js-sys = { version = "0.3.35", optional = true }
|
||||
gloo = "0.2.0"
|
||||
js-sys = "0.3.35"
|
||||
log = "0.4.8"
|
||||
nom = "5.1.1"
|
||||
serde = { version = "1.0.104", features = ["derive"] }
|
||||
serde_json = "1.0.48"
|
||||
stdweb = { version = "0.4.20", optional = true }
|
||||
wasm-bindgen = { version = "0.2.58", optional = true }
|
||||
wasm-bindgen = "0.2.58"
|
||||
|
||||
[dependencies.web-sys]
|
||||
version = "0.3"
|
||||
optional = true
|
||||
features = [
|
||||
'History',
|
||||
'HtmlLinkElement',
|
||||
|
@ -58,9 +43,5 @@ features = [
|
|||
'Window',
|
||||
]
|
||||
|
||||
# Compat with building yew with wasm-pack support.
|
||||
[target.'cfg(all(target_arch = "wasm32", not(target_os="wasi"), not(cargo_web)))'.dependencies]
|
||||
wasm-bindgen = "0.2.58"
|
||||
|
||||
[dev-dependencies]
|
||||
uuid = "0.8.1"
|
||||
|
|
|
@ -47,12 +47,6 @@ impl<SW: Switch + Clone + 'static, STATE: RouterState> Component for RouterButto
|
|||
}
|
||||
|
||||
fn view(&self) -> VNode {
|
||||
#[cfg(feature = "std_web")]
|
||||
let cb = self.link.callback(|event: ClickEvent| {
|
||||
event.prevent_default();
|
||||
Msg::Clicked
|
||||
});
|
||||
#[cfg(feature = "web_sys")]
|
||||
let cb = self.link.callback(|event: MouseEvent| {
|
||||
event.prevent_default();
|
||||
Msg::Clicked
|
||||
|
|
|
@ -53,18 +53,9 @@ impl<SW: Switch + Clone + 'static, STATE: RouterState> Component for RouterAncho
|
|||
}
|
||||
|
||||
fn view(&self) -> VNode {
|
||||
#[cfg(feature = "std_web")]
|
||||
use stdweb::web::event::IEvent;
|
||||
|
||||
let route: Route<STATE> = Route::from(self.props.route.clone());
|
||||
let target: &str = route.as_str();
|
||||
|
||||
#[cfg(feature = "std_web")]
|
||||
let cb = self.link.callback(|event: ClickEvent| {
|
||||
event.prevent_default();
|
||||
Msg::Clicked
|
||||
});
|
||||
#[cfg(feature = "web_sys")]
|
||||
let cb = self.link.callback(|event: MouseEvent| {
|
||||
event.prevent_default();
|
||||
Msg::Clicked
|
||||
|
|
|
@ -1,14 +1,7 @@
|
|||
//! Wrapper around route url string, and associated history state.
|
||||
use cfg_if::cfg_if;
|
||||
#[cfg(feature = "service")]
|
||||
use serde::de::DeserializeOwned;
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "std_web")] {
|
||||
use stdweb::{unstable::TryFrom, Value};
|
||||
}
|
||||
}
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
fmt::{self, Debug},
|
||||
|
@ -16,21 +9,7 @@ use std::{
|
|||
};
|
||||
|
||||
/// Any state that can be used in the router agent must meet the criteria of this trait.
|
||||
#[cfg(all(feature = "service", feature = "std_web"))]
|
||||
pub trait RouteState:
|
||||
Serialize + DeserializeOwned + Debug + Clone + Default + TryFrom<Value> + 'static
|
||||
{
|
||||
}
|
||||
#[cfg(all(feature = "service", feature = "std_web"))]
|
||||
impl<T> RouteState for T where
|
||||
T: Serialize + DeserializeOwned + Debug + Clone + Default + TryFrom<Value> + 'static
|
||||
{
|
||||
}
|
||||
|
||||
/// Any state that can be used in the router agent must meet the criteria of this trait.
|
||||
#[cfg(all(feature = "service", feature = "web_sys"))]
|
||||
pub trait RouteState: Serialize + DeserializeOwned + Debug + Clone + Default + 'static {}
|
||||
#[cfg(all(feature = "service", feature = "web_sys"))]
|
||||
impl<T> RouteState for T where T: Serialize + DeserializeOwned + Debug + Clone + Default + 'static {}
|
||||
|
||||
/// The representation of a route, segmented into different sections for easy access.
|
||||
|
|
|
@ -3,24 +3,11 @@
|
|||
use yew::callback::Callback;
|
||||
|
||||
use crate::route::{Route, RouteState};
|
||||
use cfg_if::cfg_if;
|
||||
use cfg_match::cfg_match;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "std_web")] {
|
||||
use stdweb::{
|
||||
js,
|
||||
unstable::{TryFrom, TryInto},
|
||||
web::{event::PopStateEvent, window, EventListenerHandle, History, IEventTarget, Location},
|
||||
Value,
|
||||
};
|
||||
} else if #[cfg(feature = "web_sys")] {
|
||||
use web_sys::{History, Location, PopStateEvent};
|
||||
use gloo::events::EventListener;
|
||||
use wasm_bindgen::{JsValue as Value, JsCast};
|
||||
}
|
||||
}
|
||||
use gloo::events::EventListener;
|
||||
use wasm_bindgen::{JsCast, JsValue as Value};
|
||||
use web_sys::{History, Location, PopStateEvent};
|
||||
|
||||
/// A service that facilitates manipulation of the browser's URL bar and responding to browser events
|
||||
/// when users press 'forward' or 'back'.
|
||||
|
@ -30,9 +17,6 @@ cfg_if! {
|
|||
pub struct RouteService<STATE = ()> {
|
||||
history: History,
|
||||
location: Location,
|
||||
#[cfg(feature = "std_web")]
|
||||
event_listener: Option<EventListenerHandle>,
|
||||
#[cfg(feature = "web_sys")]
|
||||
event_listener: Option<EventListener>,
|
||||
phantom_data: PhantomData<STATE>,
|
||||
}
|
||||
|
@ -49,20 +33,14 @@ where
|
|||
impl<T> RouteService<T> {
|
||||
/// Creates the route service.
|
||||
pub fn new() -> RouteService<T> {
|
||||
let (history, location) = cfg_match! {
|
||||
feature = "std_web" => ({
|
||||
(
|
||||
window().history(),
|
||||
window().location().expect("browser does not support location API")
|
||||
)
|
||||
}),
|
||||
feature = "web_sys" => ({
|
||||
let window = web_sys::window().unwrap();
|
||||
(
|
||||
window.history().expect("browser does not support history API"),
|
||||
window.location()
|
||||
)
|
||||
}),
|
||||
let (history, location) = {
|
||||
let window = web_sys::window().unwrap();
|
||||
(
|
||||
window
|
||||
.history()
|
||||
.expect("browser does not support history API"),
|
||||
window.location(),
|
||||
)
|
||||
};
|
||||
|
||||
RouteService {
|
||||
|
@ -107,10 +85,7 @@ where
|
|||
pub fn register_callback(&mut self, callback: Callback<Route<STATE>>) {
|
||||
let cb = move |event: PopStateEvent| {
|
||||
let state_value: Value = event.state();
|
||||
let state_string: String = cfg_match! {
|
||||
feature = "std_web" => String::try_from(state_value).unwrap_or_default(),
|
||||
feature = "web_sys" => state_value.as_string().unwrap_or_default(),
|
||||
};
|
||||
let state_string: String = state_value.as_string().unwrap_or_default();
|
||||
let state: STATE = serde_json::from_str(&state_string).unwrap_or_else(|_| {
|
||||
log::error!("Could not deserialize state string");
|
||||
STATE::default()
|
||||
|
@ -118,27 +93,20 @@ where
|
|||
|
||||
// Can't use the existing location, because this is a callback, and can't move it in
|
||||
// here.
|
||||
let location: Location = cfg_match! {
|
||||
feature = "std_web" => window().location().unwrap(),
|
||||
feature = "web_sys" => web_sys::window().unwrap().location(),
|
||||
};
|
||||
let location: Location = web_sys::window().unwrap().location();
|
||||
let route: String = Self::get_route_from_location(&location);
|
||||
|
||||
callback.emit(Route { route, state })
|
||||
};
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "std_web")] {
|
||||
self.event_listener = Some(window().add_event_listener(move |event: PopStateEvent| {
|
||||
cb(event)
|
||||
}));
|
||||
} else if #[cfg(feature = "web_sys")] {
|
||||
self.event_listener = Some(EventListener::new(web_sys::window().unwrap().as_ref(), "popstate", move |event| {
|
||||
let event: PopStateEvent = event.clone().dyn_into().unwrap();
|
||||
cb(event)
|
||||
}));
|
||||
}
|
||||
};
|
||||
self.event_listener = Some(EventListener::new(
|
||||
web_sys::window().unwrap().as_ref(),
|
||||
"popstate",
|
||||
move |event| {
|
||||
let event: PopStateEvent = event.clone().dyn_into().unwrap();
|
||||
cb(event)
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
/// Sets the browser's url bar to contain the provided route,
|
||||
|
@ -150,14 +118,9 @@ where
|
|||
log::error!("Could not serialize state string");
|
||||
"".to_string()
|
||||
});
|
||||
cfg_match! {
|
||||
feature = "std_web" => ({
|
||||
self.history.push_state(state_string, "", Some(route));
|
||||
}),
|
||||
feature = "web_sys" => ({
|
||||
let _ = self.history.push_state_with_url(&Value::from_str(&state_string), "", Some(route));
|
||||
}),
|
||||
};
|
||||
let _ = self
|
||||
.history
|
||||
.push_state_with_url(&Value::from_str(&state_string), "", Some(route));
|
||||
}
|
||||
|
||||
/// Replaces the route with another one removing the most recent history event and
|
||||
|
@ -167,14 +130,9 @@ where
|
|||
log::error!("Could not serialize state string");
|
||||
"".to_string()
|
||||
});
|
||||
cfg_match! {
|
||||
feature = "std_web" => ({
|
||||
let _ = self.history.replace_state(state_string, "", Some(route));
|
||||
}),
|
||||
feature = "web_sys" => ({
|
||||
let _ = self.history.replace_state_with_url(&Value::from_str(&state_string), "", Some(route));
|
||||
}),
|
||||
};
|
||||
let _ =
|
||||
self.history
|
||||
.replace_state_with_url(&Value::from_str(&state_string), "", Some(route));
|
||||
}
|
||||
|
||||
/// Gets the concatenated path, query, and fragment.
|
||||
|
@ -216,17 +174,9 @@ pub(crate) fn format_route_string(path: &str, query: &str, fragment: &str) -> St
|
|||
}
|
||||
|
||||
fn get_state(history: &History) -> Value {
|
||||
cfg_match! {
|
||||
feature = "std_web" => js!(
|
||||
return @{history}.state;
|
||||
),
|
||||
feature = "web_sys" => history.state().unwrap(),
|
||||
}
|
||||
history.state().unwrap()
|
||||
}
|
||||
|
||||
fn get_state_string(history: &History) -> Option<String> {
|
||||
cfg_match! {
|
||||
feature = "std_web" => get_state(history).try_into().ok(),
|
||||
feature = "web_sys" => get_state(history).as_string(),
|
||||
}
|
||||
get_state(history).as_string()
|
||||
}
|
||||
|
|
|
@ -5,24 +5,19 @@ authors = ["Hamza <muhammadhamza1311@gmail.com>"]
|
|||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
|
||||
anyhow = "1"
|
||||
cfg-if = "1.0"
|
||||
cfg-match = "0.2"
|
||||
gloo = { version = "0.2.1", optional = true }
|
||||
gloo = "0.2.1"
|
||||
http = "0.2"
|
||||
js-sys = { version = "0.3", optional = true }
|
||||
js-sys = "0.3"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
stdweb = { version = "0.4.20", optional = true }
|
||||
thiserror = "1"
|
||||
yew = { path = "../yew" }
|
||||
wasm-bindgen = { version = "0.2.60" }
|
||||
wasm-bindgen-futures = { version = "0.4", optional = true }
|
||||
wasm-bindgen = "0.2.60"
|
||||
wasm-bindgen-futures = "0.4"
|
||||
|
||||
[dependencies.web-sys]
|
||||
version = "0.3"
|
||||
optional = true
|
||||
features = [
|
||||
"AbortController",
|
||||
"AbortSignal",
|
||||
|
@ -50,21 +45,12 @@ features = [
|
|||
"WorkerOptions",
|
||||
]
|
||||
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = "0.3.4"
|
||||
base64 = "0.13.0"
|
||||
ssri = "6.0.0"
|
||||
|
||||
[features]
|
||||
default = ["web_sys"]
|
||||
web_sys = [
|
||||
"gloo",
|
||||
"js-sys",
|
||||
"web-sys",
|
||||
"wasm-bindgen-futures"
|
||||
]
|
||||
std_web = ["stdweb"]
|
||||
wasm_test = []
|
||||
httpbin_test = []
|
||||
echo_server_test = []
|
||||
|
|
|
@ -1,147 +1,68 @@
|
|||
#![cfg(test)]
|
||||
|
||||
#[cfg(feature = "web_sys")]
|
||||
pub use self::web_sys::*;
|
||||
use std::cell::RefCell;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::rc::Rc;
|
||||
use std::task::{Context, Poll, Waker};
|
||||
use yew::callback::*;
|
||||
|
||||
#[cfg(feature = "std_web")]
|
||||
pub use self::std_web::*;
|
||||
struct CallbackHandle<T> {
|
||||
waker: Option<Waker>,
|
||||
output: Option<T>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "web_sys")]
|
||||
mod web_sys {
|
||||
use std::cell::RefCell;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::rc::Rc;
|
||||
use std::task::{Context, Poll, Waker};
|
||||
use yew::callback::*;
|
||||
|
||||
struct CallbackHandle<T> {
|
||||
waker: Option<Waker>,
|
||||
output: Option<T>,
|
||||
}
|
||||
|
||||
impl<T> Default for CallbackHandle<T> {
|
||||
fn default() -> Self {
|
||||
CallbackHandle {
|
||||
waker: None,
|
||||
output: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CallbackFuture<T>(Rc<RefCell<CallbackHandle<T>>>);
|
||||
|
||||
impl<T> Clone for CallbackFuture<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Default for CallbackFuture<T> {
|
||||
fn default() -> Self {
|
||||
Self(Rc::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> Into<Callback<T>> for CallbackFuture<T> {
|
||||
fn into(self) -> Callback<T> {
|
||||
Callback::from(move |r| self.finish(r))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Future for CallbackFuture<T> {
|
||||
type Output = T;
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
if let Some(output) = self.ready() {
|
||||
Poll::Ready(output)
|
||||
} else {
|
||||
let handle = &self.0;
|
||||
handle.borrow_mut().waker = Some(cx.waker().clone());
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> CallbackFuture<T> {
|
||||
pub fn ready(&self) -> Option<T> {
|
||||
self.0.borrow_mut().output.take()
|
||||
}
|
||||
|
||||
fn finish(&self, output: T) {
|
||||
self.0.borrow_mut().output = Some(output);
|
||||
if let Some(waker) = self.0.borrow_mut().waker.take() {
|
||||
waker.wake();
|
||||
}
|
||||
impl<T> Default for CallbackHandle<T> {
|
||||
fn default() -> Self {
|
||||
CallbackHandle {
|
||||
waker: None,
|
||||
output: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std_web")]
|
||||
mod std_web {
|
||||
use std::cell::RefCell;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::rc::Rc;
|
||||
use std::task::{Context, Poll, Waker};
|
||||
use yew::callback::*;
|
||||
pub struct CallbackFuture<T>(Rc<RefCell<CallbackHandle<T>>>);
|
||||
|
||||
struct CallbackHandle<T> {
|
||||
waker: Option<Waker>,
|
||||
output: Option<T>,
|
||||
impl<T> Clone for CallbackFuture<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Default for CallbackHandle<T> {
|
||||
fn default() -> Self {
|
||||
CallbackHandle {
|
||||
waker: None,
|
||||
output: None,
|
||||
}
|
||||
}
|
||||
impl<T> Default for CallbackFuture<T> {
|
||||
fn default() -> Self {
|
||||
Self(Rc::default())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CallbackFuture<T>(Rc<RefCell<CallbackHandle<T>>>);
|
||||
|
||||
impl<T> Clone for CallbackFuture<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0.clone())
|
||||
}
|
||||
impl<T: 'static> Into<Callback<T>> for CallbackFuture<T> {
|
||||
fn into(self) -> Callback<T> {
|
||||
Callback::from(move |r| self.finish(r))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Default for CallbackFuture<T> {
|
||||
fn default() -> Self {
|
||||
Self(Rc::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> Into<Callback<T>> for CallbackFuture<T> {
|
||||
fn into(self) -> Callback<T> {
|
||||
Callback::from(move |r| self.finish(r))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Future for CallbackFuture<T> {
|
||||
type Output = T;
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
if let Some(output) = self.ready() {
|
||||
Poll::Ready(output)
|
||||
} else {
|
||||
let handle = &self.0;
|
||||
handle.borrow_mut().waker = Some(cx.waker().clone());
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> CallbackFuture<T> {
|
||||
pub fn ready(&self) -> Option<T> {
|
||||
self.0.borrow_mut().output.take()
|
||||
}
|
||||
|
||||
fn finish(&self, output: T) {
|
||||
self.0.borrow_mut().output = Some(output);
|
||||
if let Some(waker) = self.0.borrow_mut().waker.take() {
|
||||
waker.wake();
|
||||
}
|
||||
impl<T> Future for CallbackFuture<T> {
|
||||
type Output = T;
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
if let Some(output) = self.ready() {
|
||||
Poll::Ready(output)
|
||||
} else {
|
||||
let handle = &self.0;
|
||||
handle.borrow_mut().waker = Some(cx.waker().clone());
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> CallbackFuture<T> {
|
||||
pub fn ready(&self) -> Option<T> {
|
||||
self.0.borrow_mut().output.take()
|
||||
}
|
||||
|
||||
fn finish(&self, output: T) {
|
||||
self.0.borrow_mut().output = Some(output);
|
||||
if let Some(waker) = self.0.borrow_mut().waker.take() {
|
||||
waker.wake();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,7 @@
|
|||
//! This module contains a service implementation to use browser's console.
|
||||
|
||||
use cfg_if::cfg_if;
|
||||
use cfg_match::cfg_match;
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "std_web")] {
|
||||
#[allow(unused_imports)]
|
||||
use stdweb::{_js_impl, js};
|
||||
} else if #[cfg(feature = "web_sys")] {
|
||||
use wasm_bindgen::JsValue;
|
||||
use web_sys::console;
|
||||
}
|
||||
}
|
||||
use wasm_bindgen::JsValue;
|
||||
use web_sys::console;
|
||||
|
||||
/// A service to use methods of a
|
||||
/// [Console](https://developer.mozilla.org/en-US/docs/Web/API/Console).
|
||||
|
@ -22,156 +13,105 @@ impl ConsoleService {
|
|||
/// method implementation.
|
||||
/// This method outputs the provided message to the console.
|
||||
pub fn log(message: &str) {
|
||||
cfg_match! {
|
||||
feature = "std_web" => js! { @(no_return) console.log(@{message}); },
|
||||
feature = "web_sys" => console::log_1(&JsValue::from_str(message)),
|
||||
};
|
||||
console::log_1(&JsValue::from_str(message))
|
||||
}
|
||||
|
||||
/// [console.warn](https://developer.mozilla.org/en-US/docs/Web/API/Console/warn)
|
||||
/// method implementation.
|
||||
/// This method outputs the provided message to the console as a warning.
|
||||
pub fn warn(message: &str) {
|
||||
cfg_match! {
|
||||
feature = "std_web" => js! { @(no_return) console.warn(@{message}); },
|
||||
feature = "web_sys" => console::warn_1(&JsValue::from_str(message)),
|
||||
};
|
||||
console::warn_1(&JsValue::from_str(message))
|
||||
}
|
||||
|
||||
/// [console.info](https://developer.mozilla.org/en-US/docs/Web/API/Console/info)
|
||||
/// method implementation.
|
||||
/// This method outputs the provided message to the console as information.
|
||||
pub fn info(message: &str) {
|
||||
cfg_match! {
|
||||
feature = "std_web" => js! { @(no_return) console.info(@{message}); },
|
||||
feature = "web_sys" => console::info_1(&JsValue::from_str(message)),
|
||||
};
|
||||
console::info_1(&JsValue::from_str(message))
|
||||
}
|
||||
|
||||
/// [console.error](https://developer.mozilla.org/en-US/docs/Web/API/Console/error)
|
||||
/// method implementation.
|
||||
/// This method outputs the provided message to the console as an error.
|
||||
pub fn error(message: &str) {
|
||||
cfg_match! {
|
||||
feature = "std_web" => js! { @(no_return) console.error(@{message}); },
|
||||
feature = "web_sys" => console::error_1(&JsValue::from_str(message)),
|
||||
};
|
||||
console::error_1(&JsValue::from_str(message))
|
||||
}
|
||||
|
||||
/// [console.debug](https://developer.mozilla.org/en-US/docs/Web/API/Console/debug)
|
||||
/// method implementation.
|
||||
pub fn debug(message: &str) {
|
||||
cfg_match! {
|
||||
feature = "std_web" => js! { @(no_return) console.debug(@{message}); },
|
||||
feature = "web_sys" => console::debug_1(&JsValue::from_str(message)),
|
||||
};
|
||||
console::debug_1(&JsValue::from_str(message))
|
||||
}
|
||||
|
||||
/// [console.count_named](https://developer.mozilla.org/en-US/docs/Web/API/Console/count_named)
|
||||
/// method implementation.
|
||||
pub fn count_named(name: &str) {
|
||||
cfg_match! {
|
||||
feature = "std_web" => js! { @(no_return) console.count(@{name}); },
|
||||
feature = "web_sys" => console::count_with_label(name),
|
||||
};
|
||||
console::count_with_label(name)
|
||||
}
|
||||
|
||||
/// [console.count](https://developer.mozilla.org/en-US/docs/Web/API/Console/count)
|
||||
/// method implementation.
|
||||
pub fn count() {
|
||||
cfg_match! {
|
||||
feature = "std_web" => js! { @(no_return) console.count(); },
|
||||
feature = "web_sys" => console::count(),
|
||||
};
|
||||
console::count()
|
||||
}
|
||||
|
||||
/// [console.time_named](https://developer.mozilla.org/en-US/docs/Web/API/Console/time_named)
|
||||
/// method implementation.
|
||||
pub fn time_named(name: &str) {
|
||||
cfg_match! {
|
||||
feature = "std_web" => js! { @(no_return) console.time(@{name}); },
|
||||
feature = "web_sys" => console::time_with_label(name),
|
||||
};
|
||||
console::time_with_label(name)
|
||||
}
|
||||
|
||||
/// [console.time_named_end](https://developer.mozilla.org/en-US/docs/Web/API/Console/time_named_end)
|
||||
/// method implementation.
|
||||
pub fn time_named_end(name: &str) {
|
||||
cfg_match! {
|
||||
feature = "std_web" => js! { @(no_return) console.timeEnd(@{name}); },
|
||||
feature = "web_sys" => console::time_end_with_label(name),
|
||||
};
|
||||
console::time_end_with_label(name)
|
||||
}
|
||||
|
||||
/// [console.time](https://developer.mozilla.org/en-US/docs/Web/API/Console/time)
|
||||
/// method implementation.
|
||||
pub fn time() {
|
||||
cfg_match! {
|
||||
feature = "std_web" => js! { @(no_return) console.time(); },
|
||||
feature = "web_sys" => console::time(),
|
||||
};
|
||||
console::time()
|
||||
}
|
||||
/// [console.time_end](https://developer.mozilla.org/en-US/docs/Web/API/Console/time_end)
|
||||
/// method implementation.
|
||||
pub fn time_end() {
|
||||
cfg_match! {
|
||||
feature = "std_web" => js! { @(no_return) console.timeEnd(); },
|
||||
feature = "web_sys" => console::time_end(),
|
||||
};
|
||||
console::time_end()
|
||||
}
|
||||
|
||||
/// [console.clear](https://developer.mozilla.org/en-US/docs/Web/API/Console/clear)
|
||||
/// method implementation.
|
||||
pub fn clear() {
|
||||
cfg_match! {
|
||||
feature = "std_web" => js! { @(no_return) console.clear(); },
|
||||
feature = "web_sys" => console::clear(),
|
||||
};
|
||||
console::clear()
|
||||
}
|
||||
|
||||
/// [console.group](https://developer.mozilla.org/en-US/docs/Web/API/Console/group)
|
||||
/// method implementation.
|
||||
pub fn group() {
|
||||
cfg_match! {
|
||||
feature = "std_web" => js! { @(no_return) console.group(); },
|
||||
feature = "web_sys" => console::group_0(),
|
||||
};
|
||||
console::group_0()
|
||||
}
|
||||
|
||||
/// [console.group_collapsed](https://developer.mozilla.org/en-US/docs/Web/API/Console/group_collapsed)
|
||||
/// method implementation.
|
||||
pub fn group_collapsed() {
|
||||
cfg_match! {
|
||||
feature = "std_web" => js! { @(no_return) console.groupCollapsed(); },
|
||||
feature = "web_sys" => console::group_collapsed_0(),
|
||||
};
|
||||
console::group_collapsed_0()
|
||||
}
|
||||
|
||||
/// [console.group_end](https://developer.mozilla.org/en-US/docs/Web/API/Console/group_end)
|
||||
/// method implementation.
|
||||
pub fn group_end() {
|
||||
cfg_match! {
|
||||
feature = "std_web" => js! { @(no_return) console.groupEnd(); },
|
||||
feature = "web_sys" => console::group_end(),
|
||||
};
|
||||
console::group_end()
|
||||
}
|
||||
|
||||
/// [console.trace](https://developer.mozilla.org/en-US/docs/Web/API/Console/trace)
|
||||
/// method implementation.
|
||||
/// This method outputs the current stack trace to the console.
|
||||
pub fn trace() {
|
||||
cfg_match! {
|
||||
feature = "std_web" => js! { @(no_return) console.trace(); },
|
||||
feature = "web_sys" => console::trace_0(),
|
||||
};
|
||||
console::trace_0()
|
||||
}
|
||||
|
||||
/// [console.assert](https://developer.mozilla.org/en-US/docs/Web/API/Console/assert)
|
||||
/// method implementation.
|
||||
pub fn assert(condition: bool, message: &str) {
|
||||
cfg_match! {
|
||||
feature = "std_web" => js! { @(no_return) console.assert(@{condition}, @{message}); },
|
||||
feature = "web_sys" => console::assert_with_condition_and_data_1(condition, &String::from(message).into()),
|
||||
};
|
||||
console::assert_with_condition_and_data_1(condition, &String::from(message).into())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,17 +4,7 @@
|
|||
//! If you call these methods repeatably browsers tend to disable these options to give users
|
||||
//! a better experience.
|
||||
|
||||
use cfg_if::cfg_if;
|
||||
use cfg_match::cfg_match;
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "std_web")] {
|
||||
use stdweb::Value;
|
||||
#[allow(unused_imports)]
|
||||
use stdweb::{_js_impl, js};
|
||||
} else if #[cfg(feature = "web_sys")] {
|
||||
use yew::utils;
|
||||
}
|
||||
}
|
||||
use yew::utils;
|
||||
|
||||
/// A dialog service.
|
||||
#[derive(Default, Debug)]
|
||||
|
@ -24,33 +14,18 @@ impl DialogService {
|
|||
/// Calls [alert](https://developer.mozilla.org/en-US/docs/Web/API/Window/alert)
|
||||
/// function.
|
||||
pub fn alert(message: &str) {
|
||||
cfg_match! {
|
||||
feature = "std_web" => js! { @(no_return) alert(@{message}); },
|
||||
feature = "web_sys" => utils::window().alert_with_message(message).unwrap(),
|
||||
};
|
||||
utils::window().alert_with_message(message).unwrap()
|
||||
}
|
||||
|
||||
/// Calls [confirm](https://developer.mozilla.org/en-US/docs/Web/API/Window/confirm)
|
||||
/// function.
|
||||
pub fn confirm(message: &str) -> bool {
|
||||
cfg_match! {
|
||||
feature = "std_web" => ({
|
||||
let value: Value = js! { return confirm(@{message}); };
|
||||
match value {
|
||||
Value::Bool(result) => result,
|
||||
_ => false,
|
||||
}
|
||||
}),
|
||||
feature = "web_sys" => utils::window().confirm_with_message(message).unwrap(),
|
||||
}
|
||||
utils::window().confirm_with_message(message).unwrap()
|
||||
}
|
||||
|
||||
/// Prompts the user to input a message. In most browsers this will open an alert box with
|
||||
/// an input field where the user can input a message.
|
||||
#[cfg_attr(
|
||||
feature = "web_sys",
|
||||
doc = "A default value can be supplied which will be returned if the user doesn't input anything."
|
||||
)]
|
||||
#[doc = "A default value can be supplied which will be returned if the user doesn't input anything."]
|
||||
///
|
||||
/// [MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/Window/prompt)
|
||||
///
|
||||
|
@ -61,40 +36,18 @@ impl DialogService {
|
|||
/// the user inputs their message which means that the page will appear to have 'frozen'
|
||||
/// while the user types in their message.
|
||||
///
|
||||
#[cfg_attr(
|
||||
feature = "web_sys",
|
||||
doc = "This function will return `None` if the value of `default` is `None` and the user \
|
||||
#[doc = "This function will return `None` if the value of `default` is `None` and the user \
|
||||
cancels the operation. (normally a 'cancel' button will be displayed to the user, \
|
||||
clicking which cancels the operation)."
|
||||
)]
|
||||
#[cfg_attr(
|
||||
feature = "std_web",
|
||||
doc = "This function will return `None` if the user cancels the operation (normally a \
|
||||
'cancel' button will be displayed to the user, clicking which cancels the operation)."
|
||||
)]
|
||||
pub fn prompt(
|
||||
message: &str,
|
||||
#[cfg(feature = "web_sys")] default: Option<&str>,
|
||||
) -> Option<String> {
|
||||
cfg_if! {
|
||||
if #[cfg(feature="web_sys")] {
|
||||
if let Some(default) = default {
|
||||
utils::window()
|
||||
.prompt_with_message_and_default(message, default)
|
||||
.expect("Couldn't read input.")
|
||||
}
|
||||
else {
|
||||
utils::window()
|
||||
.prompt_with_message(message)
|
||||
.expect("Couldn't read input.")
|
||||
}
|
||||
} else if #[cfg(feature="std_web")] {
|
||||
let value: Value = js! { return prompt(@{message}); };
|
||||
match value {
|
||||
Value::String(result) => Some(result),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
clicking which cancels the operation)."]
|
||||
pub fn prompt(message: &str, default: Option<&str>) -> Option<String> {
|
||||
if let Some(default) = default {
|
||||
utils::window()
|
||||
.prompt_with_message_and_default(message, default)
|
||||
.expect("Couldn't read input.")
|
||||
} else {
|
||||
utils::window()
|
||||
.prompt_with_message(message)
|
||||
.expect("Couldn't read input.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,571 @@
|
|||
//! Service to send HTTP-request to a server.
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(feature = "std_web")] {
|
||||
mod std_web;
|
||||
pub use std_web::*;
|
||||
} else if #[cfg(feature = "web_sys")] {
|
||||
mod web_sys;
|
||||
pub use self::web_sys::*;
|
||||
use crate::Task;
|
||||
use anyhow::{anyhow, Error};
|
||||
use http::request::Parts;
|
||||
use js_sys::{Array, Promise, Uint8Array};
|
||||
use std::cell::RefCell;
|
||||
use std::fmt;
|
||||
use std::iter::FromIterator;
|
||||
use std::marker::PhantomData;
|
||||
use std::rc::Rc;
|
||||
use thiserror::Error as ThisError;
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
use wasm_bindgen::{JsCast, JsValue};
|
||||
use wasm_bindgen_futures::{spawn_local, JsFuture};
|
||||
use web_sys::{
|
||||
AbortController, Headers, ReferrerPolicy, Request as WebRequest, RequestInit,
|
||||
Response as WebResponse,
|
||||
};
|
||||
use yew::callback::Callback;
|
||||
use yew::format::{Binary, Format, Text};
|
||||
|
||||
#[doc(no_inline)]
|
||||
pub use web_sys::{
|
||||
RequestCache as Cache, RequestCredentials as Credentials, RequestMode as Mode,
|
||||
RequestRedirect as Redirect, Window, WorkerGlobalScope,
|
||||
};
|
||||
|
||||
#[doc(no_inline)]
|
||||
pub use http::{HeaderMap, Method, Request, Response, StatusCode, Uri};
|
||||
|
||||
trait JsInterop: Sized {
|
||||
fn from_js(js_value: JsValue) -> Result<Self, FetchError>;
|
||||
fn to_js(self) -> JsValue;
|
||||
}
|
||||
|
||||
impl JsInterop for Vec<u8> {
|
||||
fn from_js(js_value: JsValue) -> Result<Self, FetchError> {
|
||||
Ok(Uint8Array::new(&js_value).to_vec())
|
||||
}
|
||||
|
||||
fn to_js(self) -> JsValue {
|
||||
Uint8Array::from(self.as_slice()).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl JsInterop for String {
|
||||
fn from_js(js_value: JsValue) -> Result<Self, FetchError> {
|
||||
js_value.as_string().ok_or(FetchError::InternalError)
|
||||
}
|
||||
|
||||
fn to_js(self) -> JsValue {
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Init options for `fetch()` function call.
|
||||
/// https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch
|
||||
#[derive(Default, Debug)]
|
||||
pub struct FetchOptions {
|
||||
/// Cache of a fetch request.
|
||||
pub cache: Option<Cache>,
|
||||
/// Credentials of a fetch request.
|
||||
pub credentials: Option<Credentials>,
|
||||
/// Redirect behaviour of a fetch request.
|
||||
pub redirect: Option<Redirect>,
|
||||
/// Request mode of a fetch request.
|
||||
pub mode: Option<Mode>,
|
||||
/// Referrer of a fetch request.
|
||||
pub referrer: Option<Referrer>,
|
||||
/// Referrer policy of a fetch request.
|
||||
pub referrer_policy: Option<ReferrerPolicy>,
|
||||
/// Integrity of a fetch request.
|
||||
pub integrity: Option<String>,
|
||||
}
|
||||
|
||||
impl Into<RequestInit> for FetchOptions {
|
||||
fn into(self) -> RequestInit {
|
||||
let mut init = RequestInit::new();
|
||||
|
||||
if let Some(cache) = self.cache {
|
||||
init.cache(cache);
|
||||
}
|
||||
|
||||
if let Some(credentials) = self.credentials {
|
||||
init.credentials(credentials);
|
||||
}
|
||||
|
||||
if let Some(redirect) = self.redirect {
|
||||
init.redirect(redirect);
|
||||
}
|
||||
|
||||
if let Some(mode) = self.mode {
|
||||
init.mode(mode);
|
||||
}
|
||||
|
||||
if let Some(referrer) = self.referrer {
|
||||
match referrer {
|
||||
Referrer::SameOriginUrl(referrer) => init.referrer(&referrer),
|
||||
Referrer::AboutClient => init.referrer("about:client"),
|
||||
Referrer::Empty => init.referrer(""),
|
||||
};
|
||||
}
|
||||
|
||||
if let Some(referrer_policy) = self.referrer_policy {
|
||||
init.referrer_policy(referrer_policy);
|
||||
}
|
||||
|
||||
if let Some(integrity) = self.integrity {
|
||||
init.integrity(&integrity);
|
||||
}
|
||||
|
||||
init
|
||||
}
|
||||
}
|
||||
|
||||
// convert `headers` to `Iterator<Item = (String, String)>`
|
||||
fn header_iter(headers: Headers) -> impl Iterator<Item = (String, String)> {
|
||||
js_sys::try_iter(&headers)
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.map(Result::unwrap)
|
||||
.map(|entry| {
|
||||
let entry = Array::from(&entry);
|
||||
let key = entry.get(0);
|
||||
let value = entry.get(1);
|
||||
(key.as_string().unwrap(), value.as_string().unwrap())
|
||||
})
|
||||
}
|
||||
|
||||
/// Represents errors of a fetch service.
|
||||
#[derive(Debug, ThisError)]
|
||||
enum FetchError {
|
||||
#[error("canceled")]
|
||||
Canceled,
|
||||
#[error("{0}")]
|
||||
FetchFailed(String),
|
||||
#[error("invalid response")]
|
||||
InvalidResponse,
|
||||
#[error("unexpected error, please report")]
|
||||
InternalError,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Handle {
|
||||
active: Rc<RefCell<bool>>,
|
||||
abort_controller: Option<AbortController>,
|
||||
}
|
||||
|
||||
/// A handle to control sent requests.
|
||||
#[must_use = "the request will be cancelled when the task is dropped"]
|
||||
pub struct FetchTask(Handle);
|
||||
|
||||
impl fmt::Debug for FetchTask {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str("FetchTask")
|
||||
}
|
||||
}
|
||||
|
||||
/// A service to fetch resources.
|
||||
#[derive(Default, Debug)]
|
||||
pub struct FetchService {}
|
||||
|
||||
impl FetchService {
|
||||
/// Sends a request to a remote server given a Request object and a callback
|
||||
/// function to convert a Response object into a loop's message.
|
||||
///
|
||||
/// You may use a Request builder to build your request declaratively as on the
|
||||
/// following examples:
|
||||
///
|
||||
/// ```
|
||||
///# use serde_json::json;
|
||||
///# use yew::format::{Nothing, Json};
|
||||
///# use yew_services::fetch::Request;
|
||||
/// let post_request = Request::post("https://my.api/v1/resource")
|
||||
/// .header("Content-Type", "application/json")
|
||||
/// .body(Json(&json!({"foo": "bar"})))
|
||||
/// .expect("Failed to build request.");
|
||||
///
|
||||
/// let get_request = Request::get("https://my.api/v1/resource")
|
||||
/// .body(Nothing)
|
||||
/// .expect("Failed to build request.");
|
||||
/// ```
|
||||
///
|
||||
/// The callback function can build a loop message by passing or analyzing the
|
||||
/// response body and metadata.
|
||||
///
|
||||
/// ```
|
||||
///# use anyhow::Error;
|
||||
///# use yew::{Component, ComponentLink, Html};
|
||||
///# use yew_services::FetchService;
|
||||
///# use yew_services::fetch::{Response, Request};
|
||||
///# struct Comp;
|
||||
///# impl Component for Comp {
|
||||
///# type Message = Msg;type Properties = ();
|
||||
///# fn create(props: Self::Properties,link: ComponentLink<Self>) -> Self {unimplemented!()}
|
||||
///# fn update(&mut self,msg: Self::Message) -> bool {unimplemented!()}
|
||||
///# fn change(&mut self, _: Self::Properties) -> bool {unimplemented!()}
|
||||
///# fn view(&self) -> Html {unimplemented!()}
|
||||
///# }
|
||||
///# enum Msg {
|
||||
///# Noop,
|
||||
///# Error
|
||||
///# }
|
||||
///# fn dont_execute() {
|
||||
///# let link: ComponentLink<Comp> = unimplemented!();
|
||||
///# let post_request: Request<Result<String, Error>> = unimplemented!();
|
||||
/// let task = FetchService::fetch(
|
||||
/// post_request,
|
||||
/// link.callback(|response: Response<Result<String, Error>>| {
|
||||
/// if response.status().is_success() {
|
||||
/// Msg::Noop
|
||||
/// } else {
|
||||
/// Msg::Error
|
||||
/// }
|
||||
/// }),
|
||||
/// );
|
||||
///# }
|
||||
/// ```
|
||||
///
|
||||
/// For a full example, you can specify that the response must be in the JSON format,
|
||||
/// and be a specific serialized data type. If the message isn't JSON, or isn't the specified
|
||||
/// data type, then you will get an error message.
|
||||
///
|
||||
/// ```
|
||||
///# use anyhow::Error;
|
||||
///# use http::Request;
|
||||
///# use serde::Deserialize;
|
||||
///# use yew::{Component, ComponentLink, Html};
|
||||
///# use yew::format::{Json, Nothing, Format};
|
||||
///# use yew_services::fetch::Response;
|
||||
///# use yew_services::FetchService;
|
||||
///# struct Comp;
|
||||
///# impl Component for Comp {
|
||||
///# type Message = Msg;type Properties = ();
|
||||
///# fn create(props: Self::Properties,link: ComponentLink<Self>) -> Self {unimplemented!()}
|
||||
///# fn update(&mut self,msg: Self::Message) -> bool {unimplemented!()}
|
||||
///# fn change(&mut self, _: Self::Properties) -> bool {unimplemented!()}
|
||||
///# fn view(&self) -> Html {unimplemented!()}
|
||||
///# }
|
||||
///# enum Msg {
|
||||
///# FetchResourceComplete(Data),
|
||||
///# FetchResourceFailed
|
||||
///# }
|
||||
/// #[derive(Deserialize)]
|
||||
/// struct Data {
|
||||
/// value: String
|
||||
/// }
|
||||
///
|
||||
///# fn dont_execute() {
|
||||
///# let link: ComponentLink<Comp> = unimplemented!();
|
||||
/// let get_request = Request::get("/thing").body(Nothing).unwrap();
|
||||
/// let callback = link.callback(|response: Response<Json<Result<Data, Error>>>| {
|
||||
/// if let (meta, Json(Ok(body))) = response.into_parts() {
|
||||
/// if meta.status.is_success() {
|
||||
/// return Msg::FetchResourceComplete(body);
|
||||
/// }
|
||||
/// }
|
||||
/// Msg::FetchResourceFailed
|
||||
/// });
|
||||
///
|
||||
/// let task = FetchService::fetch(get_request, callback);
|
||||
///# }
|
||||
/// ```
|
||||
///
|
||||
pub fn fetch<IN, OUT: 'static>(
|
||||
request: Request<IN>,
|
||||
callback: Callback<Response<OUT>>,
|
||||
) -> Result<FetchTask, Error>
|
||||
where
|
||||
IN: Into<Text>,
|
||||
OUT: From<Text>,
|
||||
{
|
||||
fetch_impl::<IN, OUT, String>(false, request, None, callback)
|
||||
}
|
||||
|
||||
/// `fetch` with provided `FetchOptions` object.
|
||||
/// Use it if you need to send cookies with a request:
|
||||
/// ```
|
||||
///# use anyhow::Error;
|
||||
///# use http::Response;
|
||||
///# use yew::format::Nothing;
|
||||
///# use yew::{Html, Component, ComponentLink};
|
||||
///# use yew_services::fetch::{self, FetchOptions, FetchService, Credentials};
|
||||
///# struct Comp;
|
||||
///# impl Component for Comp {
|
||||
///# type Message = Msg;
|
||||
///# type Properties = ();
|
||||
///# fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {unimplemented!()}
|
||||
///# fn update(&mut self, msg: Self::Message) -> bool {unimplemented!()}
|
||||
///# fn change(&mut self, _: Self::Properties) -> bool {unimplemented!()}
|
||||
///# fn view(&self) -> Html {unimplemented!()}
|
||||
///# }
|
||||
///# pub enum Msg {}
|
||||
///# fn dont_execute() {
|
||||
///# let link: ComponentLink<Comp> = unimplemented!();
|
||||
///# let callback = link.callback(|response: Response<Result<String, Error>>| -> Msg { unimplemented!() });
|
||||
/// let request = fetch::Request::get("/path/")
|
||||
/// .body(Nothing)
|
||||
/// .unwrap();
|
||||
/// let options = FetchOptions {
|
||||
/// credentials: Some(Credentials::SameOrigin),
|
||||
/// ..FetchOptions::default()
|
||||
/// };
|
||||
/// let task = FetchService::fetch_with_options(request, options, callback);
|
||||
///# }
|
||||
/// ```
|
||||
pub fn fetch_with_options<IN, OUT: 'static>(
|
||||
request: Request<IN>,
|
||||
options: FetchOptions,
|
||||
callback: Callback<Response<OUT>>,
|
||||
) -> Result<FetchTask, Error>
|
||||
where
|
||||
IN: Into<Text>,
|
||||
OUT: From<Text>,
|
||||
{
|
||||
fetch_impl::<IN, OUT, String>(false, request, Some(options), callback)
|
||||
}
|
||||
|
||||
/// Fetch the data in binary format.
|
||||
pub fn fetch_binary<IN, OUT: 'static>(
|
||||
request: Request<IN>,
|
||||
callback: Callback<Response<OUT>>,
|
||||
) -> Result<FetchTask, Error>
|
||||
where
|
||||
IN: Into<Binary>,
|
||||
OUT: From<Binary>,
|
||||
{
|
||||
fetch_impl::<IN, OUT, Vec<u8>>(true, request, None, callback)
|
||||
}
|
||||
|
||||
/// Fetch the data in binary format with the provided request options.
|
||||
pub fn fetch_binary_with_options<IN, OUT: 'static>(
|
||||
request: Request<IN>,
|
||||
options: FetchOptions,
|
||||
callback: Callback<Response<OUT>>,
|
||||
) -> Result<FetchTask, Error>
|
||||
where
|
||||
IN: Into<Binary>,
|
||||
OUT: From<Binary>,
|
||||
{
|
||||
fetch_impl::<IN, OUT, Vec<u8>>(true, request, Some(options), callback)
|
||||
}
|
||||
}
|
||||
|
||||
fn fetch_impl<IN, OUT: 'static, DATA: 'static>(
|
||||
binary: bool,
|
||||
request: Request<IN>,
|
||||
options: Option<FetchOptions>,
|
||||
callback: Callback<Response<OUT>>,
|
||||
) -> Result<FetchTask, Error>
|
||||
where
|
||||
DATA: JsInterop,
|
||||
IN: Into<Format<DATA>>,
|
||||
OUT: From<Format<DATA>>,
|
||||
{
|
||||
// Transform http::Request into WebRequest.
|
||||
let (parts, body) = request.into_parts();
|
||||
let body = match body.into() {
|
||||
Ok(b) => b.to_js(),
|
||||
Err(_) => JsValue::NULL,
|
||||
};
|
||||
let request = build_request(parts, &body)?;
|
||||
|
||||
// Transform FetchOptions into RequestInit.
|
||||
let abort_controller = AbortController::new().ok();
|
||||
let mut init = options.map_or_else(RequestInit::new, Into::into);
|
||||
if let Some(abort_controller) = &abort_controller {
|
||||
init.signal(Some(&abort_controller.signal()));
|
||||
}
|
||||
|
||||
// Start fetch
|
||||
let promise = GLOBAL.with(|global| global.fetch_with_request_and_init(&request, &init));
|
||||
|
||||
// Spawn future to resolve fetch
|
||||
let active = Rc::new(RefCell::new(true));
|
||||
let data_fetcher = DataFetcher::new(binary, callback, active.clone());
|
||||
spawn_local(DataFetcher::fetch_data(data_fetcher, promise));
|
||||
|
||||
Ok(FetchTask(Handle {
|
||||
active,
|
||||
abort_controller,
|
||||
}))
|
||||
}
|
||||
|
||||
struct DataFetcher<OUT: 'static, DATA>
|
||||
where
|
||||
DATA: JsInterop,
|
||||
OUT: From<Format<DATA>>,
|
||||
{
|
||||
binary: bool,
|
||||
active: Rc<RefCell<bool>>,
|
||||
callback: Callback<Response<OUT>>,
|
||||
_marker: PhantomData<DATA>,
|
||||
}
|
||||
|
||||
impl<OUT: 'static, DATA> DataFetcher<OUT, DATA>
|
||||
where
|
||||
DATA: JsInterop,
|
||||
OUT: From<Format<DATA>>,
|
||||
{
|
||||
fn new(binary: bool, callback: Callback<Response<OUT>>, active: Rc<RefCell<bool>>) -> Self {
|
||||
Self {
|
||||
binary,
|
||||
callback,
|
||||
active,
|
||||
_marker: PhantomData::default(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn fetch_data(self, promise: Promise) {
|
||||
let result = self.fetch_data_impl(promise).await;
|
||||
let (data, status, headers) = match result {
|
||||
Ok((data, response)) => (Ok(data), response.status(), Some(response.headers())),
|
||||
Err(err) => (Err(err), 408, None),
|
||||
};
|
||||
self.callback(data, status, headers);
|
||||
}
|
||||
|
||||
async fn fetch_data_impl(&self, promise: Promise) -> Result<(DATA, WebResponse), Error> {
|
||||
let response = self.get_response(promise).await?;
|
||||
let data = self.get_data(&response).await?;
|
||||
Ok((data, response))
|
||||
}
|
||||
|
||||
// Prepare the response callback.
|
||||
// Notice that the callback signature must match the call from the javascript
|
||||
// side. There is no static check at this point.
|
||||
fn callback(&self, data: Result<DATA, Error>, status: u16, headers: Option<Headers>) {
|
||||
let mut response_builder = Response::builder();
|
||||
if let Ok(status) = StatusCode::from_u16(status) {
|
||||
response_builder = response_builder.status(status);
|
||||
}
|
||||
|
||||
if let Some(headers) = headers {
|
||||
for (key, value) in header_iter(headers) {
|
||||
response_builder = response_builder.header(key.as_str(), value.as_str());
|
||||
}
|
||||
}
|
||||
|
||||
// Deserialize and wrap response data into a Text or Binary object.
|
||||
let response = response_builder
|
||||
.body(OUT::from(data))
|
||||
.expect("failed to build response, please report");
|
||||
*self.active.borrow_mut() = false;
|
||||
self.callback.emit(response);
|
||||
}
|
||||
|
||||
async fn get_response(&self, fetch_promise: Promise) -> Result<WebResponse, FetchError> {
|
||||
let response = JsFuture::from(fetch_promise)
|
||||
.await
|
||||
.map_err(|err| err.unchecked_into::<js_sys::Error>())
|
||||
.map_err(|err| FetchError::FetchFailed(err.to_string().as_string().unwrap()))?;
|
||||
if *self.active.borrow() {
|
||||
Ok(WebResponse::from(response))
|
||||
} else {
|
||||
Err(FetchError::Canceled)
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_data(&self, response: &WebResponse) -> Result<DATA, FetchError> {
|
||||
let data_promise = if self.binary {
|
||||
response.array_buffer()
|
||||
} else {
|
||||
response.text()
|
||||
}
|
||||
.map_err(|_| FetchError::InvalidResponse)?;
|
||||
|
||||
let data_result = JsFuture::from(data_promise).await;
|
||||
if *self.active.borrow() {
|
||||
data_result
|
||||
.map_err(|_| FetchError::InvalidResponse)
|
||||
.and_then(DATA::from_js)
|
||||
} else {
|
||||
Err(FetchError::Canceled)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn build_request(parts: Parts, body: &JsValue) -> Result<WebRequest, Error> {
|
||||
// Map headers into a Js `Header` type.
|
||||
let header_list = parts
|
||||
.headers
|
||||
.iter()
|
||||
.map(|(k, v)| {
|
||||
Ok(Array::from_iter(&[
|
||||
JsValue::from_str(k.as_str()),
|
||||
JsValue::from_str(
|
||||
v.to_str()
|
||||
.map_err(|_| anyhow!("Unparsable request header"))?,
|
||||
),
|
||||
]))
|
||||
})
|
||||
.collect::<Result<Array, Error>>()?;
|
||||
|
||||
let header_map = Headers::new_with_str_sequence_sequence(&header_list)
|
||||
.map_err(|_| anyhow!("couldn't build headers"))?;
|
||||
|
||||
// Formats URI.
|
||||
let uri = parts.uri.to_string();
|
||||
let method = parts.method.as_str();
|
||||
let mut init = RequestInit::new();
|
||||
init.method(method).body(Some(body)).headers(&header_map);
|
||||
WebRequest::new_with_str_and_init(&uri, &init).map_err(|_| anyhow!("failed to build request"))
|
||||
}
|
||||
|
||||
impl Task for FetchTask {
|
||||
fn is_active(&self) -> bool {
|
||||
*self.0.active.borrow()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for FetchTask {
|
||||
fn drop(&mut self) {
|
||||
if self.is_active() {
|
||||
// Fetch API doesn't support request cancelling in all browsers
|
||||
// and we should use this workaround with a flag.
|
||||
// In that case, request not canceled, but callback won't be called.
|
||||
*self.0.active.borrow_mut() = false;
|
||||
if let Some(abort_controller) = &self.0.abort_controller {
|
||||
abort_controller.abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static GLOBAL: WindowOrWorker = WindowOrWorker::new();
|
||||
}
|
||||
|
||||
enum WindowOrWorker {
|
||||
Window(Window),
|
||||
Worker(WorkerGlobalScope),
|
||||
}
|
||||
|
||||
impl WindowOrWorker {
|
||||
fn new() -> Self {
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
type Global;
|
||||
|
||||
#[wasm_bindgen(method, getter, js_name = Window)]
|
||||
fn window(this: &Global) -> JsValue;
|
||||
|
||||
#[wasm_bindgen(method, getter, js_name = WorkerGlobalScope)]
|
||||
fn worker(this: &Global) -> JsValue;
|
||||
}
|
||||
|
||||
let global: Global = js_sys::global().unchecked_into();
|
||||
|
||||
if !global.window().is_undefined() {
|
||||
Self::Window(global.unchecked_into())
|
||||
} else if !global.worker().is_undefined() {
|
||||
Self::Worker(global.unchecked_into())
|
||||
} else {
|
||||
panic!(
|
||||
"Yew's `FetchService` only works when a `window` or `worker` object is available."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WindowOrWorker {
|
||||
fn fetch_with_request_and_init(&self, input: &WebRequest, init: &RequestInit) -> Promise {
|
||||
match self {
|
||||
Self::Window(window) => window.fetch_with_request_and_init(input, init),
|
||||
Self::Worker(worker) => worker.fetch_with_request_and_init(input, init),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -26,12 +585,11 @@ pub enum Referrer {
|
|||
mod tests {
|
||||
use super::*;
|
||||
use crate::callback_test_util::CallbackFuture;
|
||||
#[cfg(feature = "web_sys")]
|
||||
use ::web_sys::ReferrerPolicy;
|
||||
use serde::Deserialize;
|
||||
use ssri::Integrity;
|
||||
use std::collections::HashMap;
|
||||
use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure};
|
||||
use web_sys::ReferrerPolicy;
|
||||
use yew::callback::Callback;
|
||||
use yew::format::{Json, Nothing};
|
||||
use yew::utils;
|
||||
|
@ -260,9 +818,6 @@ mod tests {
|
|||
let callback: Callback<_> = cb_future.clone().into();
|
||||
let _task = FetchService::fetch(request, callback);
|
||||
let resp = cb_future.await;
|
||||
#[cfg(feature = "std_web")]
|
||||
assert!(resp.body().is_err());
|
||||
#[cfg(feature = "web_sys")]
|
||||
assert!(resp
|
||||
.body()
|
||||
.as_ref()
|
||||
|
|
|
@ -1,503 +0,0 @@
|
|||
//! `stdweb` implementation for the fetch service.
|
||||
|
||||
use super::Referrer;
|
||||
use crate::Task;
|
||||
use serde::Serialize;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
use stdweb::serde::Serde;
|
||||
use stdweb::unstable::{TryFrom, TryInto};
|
||||
use stdweb::web::error::Error;
|
||||
use stdweb::web::ArrayBuffer;
|
||||
use stdweb::{JsSerialize, Value};
|
||||
#[allow(unused_imports)]
|
||||
use stdweb::{_js_impl, js};
|
||||
use thiserror::Error;
|
||||
use yew::callback::Callback;
|
||||
use yew::format::{Binary, Format, Text};
|
||||
|
||||
#[doc(no_inline)]
|
||||
pub use http::{HeaderMap, Method, Request, Response, StatusCode, Uri};
|
||||
|
||||
/// Type to set cache for fetch.
|
||||
#[derive(Serialize, Debug)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum Cache {
|
||||
/// `default` value of cache.
|
||||
#[serde(rename = "default")]
|
||||
DefaultCache,
|
||||
/// `no-store` value of cache.
|
||||
NoStore,
|
||||
/// `reload` value of cache.
|
||||
Reload,
|
||||
/// `no-cache` value of cache.
|
||||
NoCache,
|
||||
/// `force-cache` value of cache
|
||||
ForceCache,
|
||||
/// `only-if-cached` value of cache
|
||||
OnlyIfCached,
|
||||
}
|
||||
|
||||
/// Type to set credentials for fetch.
|
||||
#[derive(Serialize, Debug)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum Credentials {
|
||||
/// `omit` value of credentials.
|
||||
Omit,
|
||||
/// `include` value of credentials.
|
||||
Include,
|
||||
/// `same-origin` value of credentials.
|
||||
SameOrigin,
|
||||
}
|
||||
|
||||
/// Type to set mode for fetch.
|
||||
#[derive(Serialize, Debug)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum Mode {
|
||||
/// `same-origin` value of mode.
|
||||
SameOrigin,
|
||||
/// `no-cors` value of mode.
|
||||
NoCors,
|
||||
/// `cors` value of mode.
|
||||
Cors,
|
||||
}
|
||||
|
||||
/// Type to set redirect behaviour for fetch.
|
||||
#[derive(Serialize, Debug)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum Redirect {
|
||||
/// `follow` value of redirect.
|
||||
Follow,
|
||||
/// `error` value of redirect.
|
||||
Error,
|
||||
/// `manual` value of redirect.
|
||||
Manual,
|
||||
}
|
||||
|
||||
impl Serialize for Referrer {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
match *self {
|
||||
Referrer::SameOriginUrl(ref s) => serializer.serialize_str(s),
|
||||
Referrer::AboutClient => {
|
||||
serializer.serialize_unit_variant("Referrer", 0, "about:client")
|
||||
}
|
||||
Referrer::Empty => serializer.serialize_unit_variant("Referrer", 1, ""),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Type to set referrer policy for fetch.
|
||||
#[derive(Serialize, Debug)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum ReferrerPolicy {
|
||||
/// `no-referrer` value of referrerPolicy.
|
||||
NoReferrer,
|
||||
/// `no-referrer-when-downgrade` value of referrerPolicy.
|
||||
NoReferrerWhenDowngrade,
|
||||
/// `same-origin` value of referrerPolicy.
|
||||
SameOrigin,
|
||||
/// `origin` value of referrerPolicy.
|
||||
Origin,
|
||||
/// `strict-origin` value of referrerPolicy.
|
||||
StrictOrigin,
|
||||
/// `origin-when-cross-origin` value of referrerPolicy.
|
||||
OriginWhenCrossOrigin,
|
||||
/// `strict-origin-when-cross-origin` value of referrerPolicy.
|
||||
StrictOriginWhenCrossOrigin,
|
||||
/// `unsafe-url` value of referrerPolicy.
|
||||
UnsafeUrl,
|
||||
}
|
||||
|
||||
/// Init options for `fetch()` function call.
|
||||
/// https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch
|
||||
#[derive(Serialize, Default, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FetchOptions {
|
||||
/// Cache of a fetch request.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub cache: Option<Cache>,
|
||||
/// Credentials of a fetch request.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub credentials: Option<Credentials>,
|
||||
/// Redirect behaviour of a fetch request.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub redirect: Option<Redirect>,
|
||||
/// Request mode of a fetch request.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub mode: Option<Mode>,
|
||||
/// Referrer of a fetch request.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub referrer: Option<Referrer>,
|
||||
/// Referrer policy of a fetch request.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub referrer_policy: Option<ReferrerPolicy>,
|
||||
/// Integrity of a fetch request.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub integrity: Option<String>,
|
||||
}
|
||||
|
||||
/// Represents errors of a fetch service.
|
||||
#[derive(Debug, Error)]
|
||||
enum FetchError {
|
||||
#[error("failed response")]
|
||||
FailedResponse,
|
||||
}
|
||||
|
||||
/// A handle to control sent requests. Can be canceled with a `Task::cancel` call.
|
||||
#[must_use = "the request will be cancelled when the task is dropped"]
|
||||
pub struct FetchTask(Option<Value>);
|
||||
|
||||
impl fmt::Debug for FetchTask {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str("FetchTask")
|
||||
}
|
||||
}
|
||||
|
||||
/// A service to fetch resources.
|
||||
#[derive(Default, Debug)]
|
||||
pub struct FetchService {}
|
||||
|
||||
impl FetchService {
|
||||
/// Sends a request to a remote server given a Request object and a callback
|
||||
/// function to convert a Response object into a loop's message.
|
||||
///
|
||||
/// You may use a Request builder to build your request declaratively as on the
|
||||
/// following examples:
|
||||
///
|
||||
/// ```
|
||||
///# use yew::format::{Nothing, Json};
|
||||
///# use yew_services::fetch::Request;
|
||||
///# use serde_json::json;
|
||||
/// let post_request = Request::post("https://my.api/v1/resource")
|
||||
/// .header("Content-Type", "application/json")
|
||||
/// .body(Json(&json!({"foo": "bar"})))
|
||||
/// .expect("Failed to build request.");
|
||||
///
|
||||
/// let get_request = Request::get("https://my.api/v1/resource")
|
||||
/// .body(Nothing)
|
||||
/// .expect("Failed to build request.");
|
||||
/// ```
|
||||
///
|
||||
/// The callback function can build a loop message by passing or analyzing the
|
||||
/// response body and metadata.
|
||||
///
|
||||
/// ```
|
||||
///# use yew::{Component, ComponentLink, Html};
|
||||
///# use yew_services::FetchService;
|
||||
///# use yew_services::fetch::{Response, Request};
|
||||
///# struct Comp;
|
||||
///# impl Component for Comp {
|
||||
///# type Message = Msg;type Properties = ();
|
||||
///# fn create(props: Self::Properties,link: ComponentLink<Self>) -> Self {unimplemented!()}
|
||||
///# fn update(&mut self,msg: Self::Message) -> bool {unimplemented!()}
|
||||
///# fn change(&mut self, _: Self::Properties) -> bool {unimplemented!()}
|
||||
///# fn view(&self) -> Html {unimplemented!()}
|
||||
///# }
|
||||
///# enum Msg {
|
||||
///# Noop,
|
||||
///# Error
|
||||
///# }
|
||||
///# fn dont_execute() {
|
||||
///# let link: ComponentLink<Comp> = unimplemented!();
|
||||
///# let post_request: Request<Result<String, anyhow::Error>> = unimplemented!();
|
||||
/// let task = FetchService::fetch(
|
||||
/// post_request,
|
||||
/// link.callback(|response: Response<Result<String, anyhow::Error>>| {
|
||||
/// if response.status().is_success() {
|
||||
/// Msg::Noop
|
||||
/// } else {
|
||||
/// Msg::Error
|
||||
/// }
|
||||
/// }),
|
||||
/// );
|
||||
///# }
|
||||
/// ```
|
||||
///
|
||||
/// For a full example, you can specify that the response must be in the JSON format,
|
||||
/// and be a specific serialized data type. If the mesage isn't Json, or isn't the specified
|
||||
/// data type, then you will get a message indicating failure.
|
||||
///
|
||||
/// ```
|
||||
///# use yew::format::{Json, Nothing, Format};
|
||||
///# use yew_services::FetchService;
|
||||
///# use http::Request;
|
||||
///# use yew_services::fetch::Response;
|
||||
///# use yew::{Component, ComponentLink, Html};
|
||||
///# use serde_derive::Deserialize;
|
||||
///# struct Comp;
|
||||
///# impl Component for Comp {
|
||||
///# type Message = Msg;type Properties = ();
|
||||
///# fn create(props: Self::Properties,link: ComponentLink<Self>) -> Self {unimplemented!()}
|
||||
///# fn update(&mut self,msg: Self::Message) -> bool {unimplemented!()}
|
||||
///# fn change(&mut self, _: Self::Properties) -> bool {unimplemented!()}
|
||||
///# fn view(&self) -> Html {unimplemented!()}
|
||||
///# }
|
||||
///# enum Msg {
|
||||
///# FetchResourceComplete(Data),
|
||||
///# FetchResourceFailed
|
||||
///# }
|
||||
/// #[derive(Deserialize)]
|
||||
/// struct Data {
|
||||
/// value: String
|
||||
/// }
|
||||
///
|
||||
///# fn dont_execute() {
|
||||
///# let link: ComponentLink<Comp> = unimplemented!();
|
||||
/// let get_request = Request::get("/thing").body(Nothing).unwrap();
|
||||
/// let callback = link.callback(|response: Response<Json<Result<Data, anyhow::Error>>>| {
|
||||
/// if let (meta, Json(Ok(body))) = response.into_parts() {
|
||||
/// if meta.status.is_success() {
|
||||
/// return Msg::FetchResourceComplete(body);
|
||||
/// }
|
||||
/// }
|
||||
/// Msg::FetchResourceFailed
|
||||
/// });
|
||||
///
|
||||
/// let task = FetchService::fetch(get_request, callback);
|
||||
///# }
|
||||
/// ```
|
||||
///
|
||||
pub fn fetch<IN, OUT: 'static>(
|
||||
request: Request<IN>,
|
||||
callback: Callback<Response<OUT>>,
|
||||
) -> Result<FetchTask, &'static str>
|
||||
where
|
||||
IN: Into<Text>,
|
||||
OUT: From<Text>,
|
||||
{
|
||||
fetch_impl::<IN, OUT, String, String>(false, request, None, callback)
|
||||
}
|
||||
|
||||
/// `fetch` with provided `FetchOptions` object.
|
||||
/// Use it if you need to send cookies with a request:
|
||||
/// ```
|
||||
///# use yew::format::Nothing;
|
||||
///# use yew_services::fetch::{self, FetchOptions, Credentials};
|
||||
///# use yew::{Html, Component, ComponentLink};
|
||||
///# use yew_services::FetchService;
|
||||
///# use http::Response;
|
||||
///# struct Comp;
|
||||
///# impl Component for Comp {
|
||||
///# type Message = Msg;
|
||||
///# type Properties = ();
|
||||
///# fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {unimplemented!()}
|
||||
///# fn update(&mut self, msg: Self::Message) -> bool {unimplemented!()}
|
||||
///# fn change(&mut self, _: Self::Properties) -> bool {unimplemented!()}
|
||||
///# fn view(&self) -> Html {unimplemented!()}
|
||||
///# }
|
||||
///# pub enum Msg { }
|
||||
///# fn dont_execute() {
|
||||
///# let link: ComponentLink<Comp> = unimplemented!();
|
||||
///# let callback = link.callback(|response: Response<Result<String, anyhow::Error>>| -> Msg { unimplemented!() });
|
||||
/// let request = fetch::Request::get("/path/")
|
||||
/// .body(Nothing)
|
||||
/// .unwrap();
|
||||
/// let options = FetchOptions {
|
||||
/// credentials: Some(Credentials::SameOrigin),
|
||||
/// ..FetchOptions::default()
|
||||
/// };
|
||||
/// let task = FetchService::fetch_with_options(request, options, callback);
|
||||
///# }
|
||||
/// ```
|
||||
pub fn fetch_with_options<IN, OUT: 'static>(
|
||||
request: Request<IN>,
|
||||
options: FetchOptions,
|
||||
callback: Callback<Response<OUT>>,
|
||||
) -> Result<FetchTask, &'static str>
|
||||
where
|
||||
IN: Into<Text>,
|
||||
OUT: From<Text>,
|
||||
{
|
||||
fetch_impl::<IN, OUT, String, String>(false, request, Some(options), callback)
|
||||
}
|
||||
|
||||
/// Fetch the data in binary format.
|
||||
pub fn fetch_binary<IN, OUT: 'static>(
|
||||
request: Request<IN>,
|
||||
callback: Callback<Response<OUT>>,
|
||||
) -> Result<FetchTask, &'static str>
|
||||
where
|
||||
IN: Into<Binary>,
|
||||
OUT: From<Binary>,
|
||||
{
|
||||
fetch_impl::<IN, OUT, Vec<u8>, ArrayBuffer>(true, request, None, callback)
|
||||
}
|
||||
|
||||
/// Fetch the data in binary format using the provided request options.
|
||||
pub fn fetch_binary_with_options<IN, OUT: 'static>(
|
||||
request: Request<IN>,
|
||||
options: FetchOptions,
|
||||
callback: Callback<Response<OUT>>,
|
||||
) -> Result<FetchTask, &'static str>
|
||||
where
|
||||
IN: Into<Binary>,
|
||||
OUT: From<Binary>,
|
||||
{
|
||||
fetch_impl::<IN, OUT, Vec<u8>, ArrayBuffer>(true, request, Some(options), callback)
|
||||
}
|
||||
}
|
||||
|
||||
fn fetch_impl<IN, OUT: 'static, T, X>(
|
||||
binary: bool,
|
||||
request: Request<IN>,
|
||||
options: Option<FetchOptions>,
|
||||
callback: Callback<Response<OUT>>,
|
||||
) -> Result<FetchTask, &'static str>
|
||||
where
|
||||
IN: Into<Format<T>>,
|
||||
OUT: From<Format<T>>,
|
||||
T: JsSerialize,
|
||||
X: TryFrom<Value> + Into<T>,
|
||||
{
|
||||
// Consume request as parts and body.
|
||||
let (parts, body) = request.into_parts();
|
||||
|
||||
// Map headers into a Js `Header` to make sure it's supported.
|
||||
let header_list = parts
|
||||
.headers
|
||||
.iter()
|
||||
.map(|(k, v)| {
|
||||
Ok((
|
||||
k.as_str(),
|
||||
v.to_str().map_err(|_| "Unparsable request header")?,
|
||||
))
|
||||
})
|
||||
.collect::<Result<HashMap<_, _>, _>>()?;
|
||||
let header_map = js! {
|
||||
try {
|
||||
return new Headers(@{header_list});
|
||||
} catch(error) {
|
||||
return error;
|
||||
}
|
||||
};
|
||||
if Error::try_from(js!( return @{header_map.as_ref()}; )).is_ok() {
|
||||
return Err("couldn't build headers");
|
||||
}
|
||||
|
||||
// Formats URI.
|
||||
let uri = parts.uri.to_string();
|
||||
let method = parts.method.as_str();
|
||||
let body = body.into().ok();
|
||||
|
||||
// Prepare the response callback.
|
||||
// Notice that the callback signature must match the call from the JavaScript
|
||||
// side. There is no static check at this point.
|
||||
let callback = move |success: bool, status: u16, headers: HashMap<String, String>, data: X| {
|
||||
let mut response_builder = Response::builder();
|
||||
|
||||
if let Ok(status) = StatusCode::from_u16(status) {
|
||||
response_builder = response_builder.status(status);
|
||||
}
|
||||
|
||||
for (key, values) in headers {
|
||||
response_builder = response_builder.header(key.as_str(), values.as_str());
|
||||
}
|
||||
|
||||
// Deserialize and wrap response data into a Text object.
|
||||
let data = if success {
|
||||
Ok(data.into())
|
||||
} else {
|
||||
Err(FetchError::FailedResponse.into())
|
||||
};
|
||||
let out = OUT::from(data);
|
||||
let response = response_builder.body(out).unwrap();
|
||||
callback.emit(response);
|
||||
};
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
let handle = js! {
|
||||
var body = @{body};
|
||||
if (@{binary} && body != null) {
|
||||
body = Uint8Array.from(body);
|
||||
}
|
||||
var callback = @{callback};
|
||||
var abortController = AbortController ? new AbortController() : null;
|
||||
var handle = {
|
||||
active: true,
|
||||
callback,
|
||||
abortController,
|
||||
};
|
||||
var init = {
|
||||
method: @{method},
|
||||
body: body,
|
||||
headers: @{header_map},
|
||||
};
|
||||
var opts = @{Serde(options)} || {};
|
||||
for (var attrname in opts) {
|
||||
init[attrname] = opts[attrname];
|
||||
}
|
||||
if (abortController && !("signal" in init)) {
|
||||
init.signal = abortController.signal;
|
||||
}
|
||||
fetch(@{uri}, init).then(function(response) {
|
||||
var promise = (@{binary}) ? response.arrayBuffer() : response.text();
|
||||
var status = response.status;
|
||||
var headers = {};
|
||||
response.headers.forEach(function(value, key) {
|
||||
headers[key] = value;
|
||||
});
|
||||
promise.then(function(data) {
|
||||
if (handle.active == true) {
|
||||
handle.active = false;
|
||||
callback(true, status, headers, data);
|
||||
callback.drop();
|
||||
}
|
||||
}).catch(function(err) {
|
||||
if (handle.active == true) {
|
||||
handle.active = false;
|
||||
callback(false, status, headers, data);
|
||||
callback.drop();
|
||||
}
|
||||
});
|
||||
}).catch(function(e) {
|
||||
if (handle.active == true) {
|
||||
var data = (@{binary}) ? new ArrayBuffer() : "";
|
||||
handle.active = false;
|
||||
callback(false, 408, {}, data);
|
||||
callback.drop();
|
||||
}
|
||||
});
|
||||
return handle;
|
||||
};
|
||||
Ok(FetchTask(Some(handle)))
|
||||
}
|
||||
|
||||
impl Task for FetchTask {
|
||||
fn is_active(&self) -> bool {
|
||||
if let Some(ref task) = self.0 {
|
||||
let result = js! {
|
||||
var the_task = @{task};
|
||||
return the_task.active &&
|
||||
(!the_task.abortController || !the_task.abortController.signal.aborted);
|
||||
};
|
||||
result.try_into().unwrap_or(false)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for FetchTask {
|
||||
fn drop(&mut self) {
|
||||
if self.is_active() {
|
||||
// Fetch API doesn't support request cancelling in all browsers
|
||||
// and we should use this workaround with a flag.
|
||||
// In that case, request not canceled, but callback won't be called.
|
||||
let handle = self
|
||||
.0
|
||||
.take()
|
||||
.expect("tried to cancel request fetching twice");
|
||||
js! { @(no_return)
|
||||
var handle = @{handle};
|
||||
handle.active = false;
|
||||
handle.callback.drop();
|
||||
if (handle.abortController) {
|
||||
handle.abortController.abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,572 +0,0 @@
|
|||
//! `web-sys` implementation for the fetch service.
|
||||
|
||||
use super::Referrer;
|
||||
use crate::Task;
|
||||
use anyhow::{anyhow, Error};
|
||||
use http::request::Parts;
|
||||
use js_sys::{Array, Promise, Uint8Array};
|
||||
use std::cell::RefCell;
|
||||
use std::fmt;
|
||||
use std::iter::FromIterator;
|
||||
use std::marker::PhantomData;
|
||||
use std::rc::Rc;
|
||||
use thiserror::Error as ThisError;
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
use wasm_bindgen::{JsCast, JsValue};
|
||||
use wasm_bindgen_futures::{spawn_local, JsFuture};
|
||||
use web_sys::{
|
||||
AbortController, Headers, ReferrerPolicy, Request as WebRequest, RequestInit,
|
||||
Response as WebResponse,
|
||||
};
|
||||
use yew::callback::Callback;
|
||||
use yew::format::{Binary, Format, Text};
|
||||
|
||||
#[doc(no_inline)]
|
||||
pub use web_sys::{
|
||||
RequestCache as Cache, RequestCredentials as Credentials, RequestMode as Mode,
|
||||
RequestRedirect as Redirect, Window, WorkerGlobalScope,
|
||||
};
|
||||
|
||||
#[doc(no_inline)]
|
||||
pub use http::{HeaderMap, Method, Request, Response, StatusCode, Uri};
|
||||
|
||||
trait JsInterop: Sized {
|
||||
fn from_js(js_value: JsValue) -> Result<Self, FetchError>;
|
||||
fn to_js(self) -> JsValue;
|
||||
}
|
||||
|
||||
impl JsInterop for Vec<u8> {
|
||||
fn from_js(js_value: JsValue) -> Result<Self, FetchError> {
|
||||
Ok(Uint8Array::new(&js_value).to_vec())
|
||||
}
|
||||
|
||||
fn to_js(self) -> JsValue {
|
||||
Uint8Array::from(self.as_slice()).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl JsInterop for String {
|
||||
fn from_js(js_value: JsValue) -> Result<Self, FetchError> {
|
||||
js_value.as_string().ok_or(FetchError::InternalError)
|
||||
}
|
||||
|
||||
fn to_js(self) -> JsValue {
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Init options for `fetch()` function call.
|
||||
/// https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch
|
||||
#[derive(Default, Debug)]
|
||||
pub struct FetchOptions {
|
||||
/// Cache of a fetch request.
|
||||
pub cache: Option<Cache>,
|
||||
/// Credentials of a fetch request.
|
||||
pub credentials: Option<Credentials>,
|
||||
/// Redirect behaviour of a fetch request.
|
||||
pub redirect: Option<Redirect>,
|
||||
/// Request mode of a fetch request.
|
||||
pub mode: Option<Mode>,
|
||||
/// Referrer of a fetch request.
|
||||
pub referrer: Option<Referrer>,
|
||||
/// Referrer policy of a fetch request.
|
||||
pub referrer_policy: Option<ReferrerPolicy>,
|
||||
/// Integrity of a fetch request.
|
||||
pub integrity: Option<String>,
|
||||
}
|
||||
|
||||
impl Into<RequestInit> for FetchOptions {
|
||||
fn into(self) -> RequestInit {
|
||||
let mut init = RequestInit::new();
|
||||
|
||||
if let Some(cache) = self.cache {
|
||||
init.cache(cache);
|
||||
}
|
||||
|
||||
if let Some(credentials) = self.credentials {
|
||||
init.credentials(credentials);
|
||||
}
|
||||
|
||||
if let Some(redirect) = self.redirect {
|
||||
init.redirect(redirect);
|
||||
}
|
||||
|
||||
if let Some(mode) = self.mode {
|
||||
init.mode(mode);
|
||||
}
|
||||
|
||||
if let Some(referrer) = self.referrer {
|
||||
match referrer {
|
||||
Referrer::SameOriginUrl(referrer) => init.referrer(&referrer),
|
||||
Referrer::AboutClient => init.referrer("about:client"),
|
||||
Referrer::Empty => init.referrer(""),
|
||||
};
|
||||
}
|
||||
|
||||
if let Some(referrer_policy) = self.referrer_policy {
|
||||
init.referrer_policy(referrer_policy);
|
||||
}
|
||||
|
||||
if let Some(integrity) = self.integrity {
|
||||
init.integrity(&integrity);
|
||||
}
|
||||
|
||||
init
|
||||
}
|
||||
}
|
||||
|
||||
// convert `headers` to `Iterator<Item = (String, String)>`
|
||||
fn header_iter(headers: Headers) -> impl Iterator<Item = (String, String)> {
|
||||
js_sys::try_iter(&headers)
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.map(Result::unwrap)
|
||||
.map(|entry| {
|
||||
let entry = Array::from(&entry);
|
||||
let key = entry.get(0);
|
||||
let value = entry.get(1);
|
||||
(key.as_string().unwrap(), value.as_string().unwrap())
|
||||
})
|
||||
}
|
||||
|
||||
/// Represents errors of a fetch service.
|
||||
#[derive(Debug, ThisError)]
|
||||
enum FetchError {
|
||||
#[error("canceled")]
|
||||
Canceled,
|
||||
#[error("{0}")]
|
||||
FetchFailed(String),
|
||||
#[error("invalid response")]
|
||||
InvalidResponse,
|
||||
#[error("unexpected error, please report")]
|
||||
InternalError,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Handle {
|
||||
active: Rc<RefCell<bool>>,
|
||||
abort_controller: Option<AbortController>,
|
||||
}
|
||||
|
||||
/// A handle to control sent requests.
|
||||
#[must_use = "the request will be cancelled when the task is dropped"]
|
||||
pub struct FetchTask(Handle);
|
||||
|
||||
impl fmt::Debug for FetchTask {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str("FetchTask")
|
||||
}
|
||||
}
|
||||
|
||||
/// A service to fetch resources.
|
||||
#[derive(Default, Debug)]
|
||||
pub struct FetchService {}
|
||||
|
||||
impl FetchService {
|
||||
/// Sends a request to a remote server given a Request object and a callback
|
||||
/// function to convert a Response object into a loop's message.
|
||||
///
|
||||
/// You may use a Request builder to build your request declaratively as on the
|
||||
/// following examples:
|
||||
///
|
||||
/// ```
|
||||
///# use yew::format::{Nothing, Json};
|
||||
///# use yew_services::fetch::Request;
|
||||
///# use serde_json::json;
|
||||
/// let post_request = Request::post("https://my.api/v1/resource")
|
||||
/// .header("Content-Type", "application/json")
|
||||
/// .body(Json(&json!({"foo": "bar"})))
|
||||
/// .expect("Failed to build request.");
|
||||
///
|
||||
/// let get_request = Request::get("https://my.api/v1/resource")
|
||||
/// .body(Nothing)
|
||||
/// .expect("Failed to build request.");
|
||||
/// ```
|
||||
///
|
||||
/// The callback function can build a loop message by passing or analyzing the
|
||||
/// response body and metadata.
|
||||
///
|
||||
/// ```
|
||||
///# use yew::{Component, ComponentLink, Html};
|
||||
///# use yew_services::FetchService;
|
||||
///# use yew_services::fetch::{Response, Request};
|
||||
///# use anyhow::Error;
|
||||
///# struct Comp;
|
||||
///# impl Component for Comp {
|
||||
///# type Message = Msg;type Properties = ();
|
||||
///# fn create(props: Self::Properties,link: ComponentLink<Self>) -> Self {unimplemented!()}
|
||||
///# fn update(&mut self,msg: Self::Message) -> bool {unimplemented!()}
|
||||
///# fn change(&mut self, _: Self::Properties) -> bool {unimplemented!()}
|
||||
///# fn view(&self) -> Html {unimplemented!()}
|
||||
///# }
|
||||
///# enum Msg {
|
||||
///# Noop,
|
||||
///# Error
|
||||
///# }
|
||||
///# fn dont_execute() {
|
||||
///# let link: ComponentLink<Comp> = unimplemented!();
|
||||
///# let post_request: Request<Result<String, Error>> = unimplemented!();
|
||||
/// let task = FetchService::fetch(
|
||||
/// post_request,
|
||||
/// link.callback(|response: Response<Result<String, Error>>| {
|
||||
/// if response.status().is_success() {
|
||||
/// Msg::Noop
|
||||
/// } else {
|
||||
/// Msg::Error
|
||||
/// }
|
||||
/// }),
|
||||
/// );
|
||||
///# }
|
||||
/// ```
|
||||
///
|
||||
/// For a full example, you can specify that the response must be in the JSON format,
|
||||
/// and be a specific serialized data type. If the message isn't JSON, or isn't the specified
|
||||
/// data type, then you will get an error message.
|
||||
///
|
||||
/// ```
|
||||
///# use yew::format::{Json, Nothing, Format};
|
||||
///# use yew_services::FetchService;
|
||||
///# use http::Request;
|
||||
///# use yew_services::fetch::Response;
|
||||
///# use yew::{Component, ComponentLink, Html};
|
||||
///# use serde::Deserialize;
|
||||
///# use anyhow::Error;
|
||||
///# struct Comp;
|
||||
///# impl Component for Comp {
|
||||
///# type Message = Msg;type Properties = ();
|
||||
///# fn create(props: Self::Properties,link: ComponentLink<Self>) -> Self {unimplemented!()}
|
||||
///# fn update(&mut self,msg: Self::Message) -> bool {unimplemented!()}
|
||||
///# fn change(&mut self, _: Self::Properties) -> bool {unimplemented!()}
|
||||
///# fn view(&self) -> Html {unimplemented!()}
|
||||
///# }
|
||||
///# enum Msg {
|
||||
///# FetchResourceComplete(Data),
|
||||
///# FetchResourceFailed
|
||||
///# }
|
||||
/// #[derive(Deserialize)]
|
||||
/// struct Data {
|
||||
/// value: String
|
||||
/// }
|
||||
///
|
||||
///# fn dont_execute() {
|
||||
///# let link: ComponentLink<Comp> = unimplemented!();
|
||||
/// let get_request = Request::get("/thing").body(Nothing).unwrap();
|
||||
/// let callback = link.callback(|response: Response<Json<Result<Data, Error>>>| {
|
||||
/// if let (meta, Json(Ok(body))) = response.into_parts() {
|
||||
/// if meta.status.is_success() {
|
||||
/// return Msg::FetchResourceComplete(body);
|
||||
/// }
|
||||
/// }
|
||||
/// Msg::FetchResourceFailed
|
||||
/// });
|
||||
///
|
||||
/// let task = FetchService::fetch(get_request, callback);
|
||||
///# }
|
||||
/// ```
|
||||
///
|
||||
pub fn fetch<IN, OUT: 'static>(
|
||||
request: Request<IN>,
|
||||
callback: Callback<Response<OUT>>,
|
||||
) -> Result<FetchTask, Error>
|
||||
where
|
||||
IN: Into<Text>,
|
||||
OUT: From<Text>,
|
||||
{
|
||||
fetch_impl::<IN, OUT, String>(false, request, None, callback)
|
||||
}
|
||||
|
||||
/// `fetch` with provided `FetchOptions` object.
|
||||
/// Use it if you need to send cookies with a request:
|
||||
/// ```
|
||||
///# use yew::format::Nothing;
|
||||
///# use yew_services::fetch::{self, FetchOptions, Credentials};
|
||||
///# use yew::{Html, Component, ComponentLink};
|
||||
///# use yew_services::FetchService;
|
||||
///# use http::Response;
|
||||
///# use anyhow::Error;
|
||||
///# struct Comp;
|
||||
///# impl Component for Comp {
|
||||
///# type Message = Msg;
|
||||
///# type Properties = ();
|
||||
///# fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {unimplemented!()}
|
||||
///# fn update(&mut self, msg: Self::Message) -> bool {unimplemented!()}
|
||||
///# fn change(&mut self, _: Self::Properties) -> bool {unimplemented!()}
|
||||
///# fn view(&self) -> Html {unimplemented!()}
|
||||
///# }
|
||||
///# pub enum Msg {}
|
||||
///# fn dont_execute() {
|
||||
///# let link: ComponentLink<Comp> = unimplemented!();
|
||||
///# let callback = link.callback(|response: Response<Result<String, Error>>| -> Msg { unimplemented!() });
|
||||
/// let request = fetch::Request::get("/path/")
|
||||
/// .body(Nothing)
|
||||
/// .unwrap();
|
||||
/// let options = FetchOptions {
|
||||
/// credentials: Some(Credentials::SameOrigin),
|
||||
/// ..FetchOptions::default()
|
||||
/// };
|
||||
/// let task = FetchService::fetch_with_options(request, options, callback);
|
||||
///# }
|
||||
/// ```
|
||||
pub fn fetch_with_options<IN, OUT: 'static>(
|
||||
request: Request<IN>,
|
||||
options: FetchOptions,
|
||||
callback: Callback<Response<OUT>>,
|
||||
) -> Result<FetchTask, Error>
|
||||
where
|
||||
IN: Into<Text>,
|
||||
OUT: From<Text>,
|
||||
{
|
||||
fetch_impl::<IN, OUT, String>(false, request, Some(options), callback)
|
||||
}
|
||||
|
||||
/// Fetch the data in binary format.
|
||||
pub fn fetch_binary<IN, OUT: 'static>(
|
||||
request: Request<IN>,
|
||||
callback: Callback<Response<OUT>>,
|
||||
) -> Result<FetchTask, Error>
|
||||
where
|
||||
IN: Into<Binary>,
|
||||
OUT: From<Binary>,
|
||||
{
|
||||
fetch_impl::<IN, OUT, Vec<u8>>(true, request, None, callback)
|
||||
}
|
||||
|
||||
/// Fetch the data in binary format with the provided request options.
|
||||
pub fn fetch_binary_with_options<IN, OUT: 'static>(
|
||||
request: Request<IN>,
|
||||
options: FetchOptions,
|
||||
callback: Callback<Response<OUT>>,
|
||||
) -> Result<FetchTask, Error>
|
||||
where
|
||||
IN: Into<Binary>,
|
||||
OUT: From<Binary>,
|
||||
{
|
||||
fetch_impl::<IN, OUT, Vec<u8>>(true, request, Some(options), callback)
|
||||
}
|
||||
}
|
||||
|
||||
fn fetch_impl<IN, OUT: 'static, DATA: 'static>(
|
||||
binary: bool,
|
||||
request: Request<IN>,
|
||||
options: Option<FetchOptions>,
|
||||
callback: Callback<Response<OUT>>,
|
||||
) -> Result<FetchTask, Error>
|
||||
where
|
||||
DATA: JsInterop,
|
||||
IN: Into<Format<DATA>>,
|
||||
OUT: From<Format<DATA>>,
|
||||
{
|
||||
// Transform http::Request into WebRequest.
|
||||
let (parts, body) = request.into_parts();
|
||||
let body = match body.into() {
|
||||
Ok(b) => b.to_js(),
|
||||
Err(_) => JsValue::NULL,
|
||||
};
|
||||
let request = build_request(parts, &body)?;
|
||||
|
||||
// Transform FetchOptions into RequestInit.
|
||||
let abort_controller = AbortController::new().ok();
|
||||
let mut init = options.map_or_else(RequestInit::new, Into::into);
|
||||
if let Some(abort_controller) = &abort_controller {
|
||||
init.signal(Some(&abort_controller.signal()));
|
||||
}
|
||||
|
||||
// Start fetch
|
||||
let promise = GLOBAL.with(|global| global.fetch_with_request_and_init(&request, &init));
|
||||
|
||||
// Spawn future to resolve fetch
|
||||
let active = Rc::new(RefCell::new(true));
|
||||
let data_fetcher = DataFetcher::new(binary, callback, active.clone());
|
||||
spawn_local(DataFetcher::fetch_data(data_fetcher, promise));
|
||||
|
||||
Ok(FetchTask(Handle {
|
||||
active,
|
||||
abort_controller,
|
||||
}))
|
||||
}
|
||||
|
||||
struct DataFetcher<OUT: 'static, DATA>
|
||||
where
|
||||
DATA: JsInterop,
|
||||
OUT: From<Format<DATA>>,
|
||||
{
|
||||
binary: bool,
|
||||
active: Rc<RefCell<bool>>,
|
||||
callback: Callback<Response<OUT>>,
|
||||
_marker: PhantomData<DATA>,
|
||||
}
|
||||
|
||||
impl<OUT: 'static, DATA> DataFetcher<OUT, DATA>
|
||||
where
|
||||
DATA: JsInterop,
|
||||
OUT: From<Format<DATA>>,
|
||||
{
|
||||
fn new(binary: bool, callback: Callback<Response<OUT>>, active: Rc<RefCell<bool>>) -> Self {
|
||||
Self {
|
||||
binary,
|
||||
callback,
|
||||
active,
|
||||
_marker: PhantomData::default(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn fetch_data(self, promise: Promise) {
|
||||
let result = self.fetch_data_impl(promise).await;
|
||||
let (data, status, headers) = match result {
|
||||
Ok((data, response)) => (Ok(data), response.status(), Some(response.headers())),
|
||||
Err(err) => (Err(err), 408, None),
|
||||
};
|
||||
self.callback(data, status, headers);
|
||||
}
|
||||
|
||||
async fn fetch_data_impl(&self, promise: Promise) -> Result<(DATA, WebResponse), Error> {
|
||||
let response = self.get_response(promise).await?;
|
||||
let data = self.get_data(&response).await?;
|
||||
Ok((data, response))
|
||||
}
|
||||
|
||||
// Prepare the response callback.
|
||||
// Notice that the callback signature must match the call from the javascript
|
||||
// side. There is no static check at this point.
|
||||
fn callback(&self, data: Result<DATA, Error>, status: u16, headers: Option<Headers>) {
|
||||
let mut response_builder = Response::builder();
|
||||
if let Ok(status) = StatusCode::from_u16(status) {
|
||||
response_builder = response_builder.status(status);
|
||||
}
|
||||
|
||||
if let Some(headers) = headers {
|
||||
for (key, value) in header_iter(headers) {
|
||||
response_builder = response_builder.header(key.as_str(), value.as_str());
|
||||
}
|
||||
}
|
||||
|
||||
// Deserialize and wrap response data into a Text or Binary object.
|
||||
let response = response_builder
|
||||
.body(OUT::from(data))
|
||||
.expect("failed to build response, please report");
|
||||
*self.active.borrow_mut() = false;
|
||||
self.callback.emit(response);
|
||||
}
|
||||
|
||||
async fn get_response(&self, fetch_promise: Promise) -> Result<WebResponse, FetchError> {
|
||||
let response = JsFuture::from(fetch_promise)
|
||||
.await
|
||||
.map_err(|err| err.unchecked_into::<js_sys::Error>())
|
||||
.map_err(|err| FetchError::FetchFailed(err.to_string().as_string().unwrap()))?;
|
||||
if *self.active.borrow() {
|
||||
Ok(WebResponse::from(response))
|
||||
} else {
|
||||
Err(FetchError::Canceled)
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_data(&self, response: &WebResponse) -> Result<DATA, FetchError> {
|
||||
let data_promise = if self.binary {
|
||||
response.array_buffer()
|
||||
} else {
|
||||
response.text()
|
||||
}
|
||||
.map_err(|_| FetchError::InvalidResponse)?;
|
||||
|
||||
let data_result = JsFuture::from(data_promise).await;
|
||||
if *self.active.borrow() {
|
||||
data_result
|
||||
.map_err(|_| FetchError::InvalidResponse)
|
||||
.and_then(DATA::from_js)
|
||||
} else {
|
||||
Err(FetchError::Canceled)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn build_request(parts: Parts, body: &JsValue) -> Result<WebRequest, Error> {
|
||||
// Map headers into a Js `Header` type.
|
||||
let header_list = parts
|
||||
.headers
|
||||
.iter()
|
||||
.map(|(k, v)| {
|
||||
Ok(Array::from_iter(&[
|
||||
JsValue::from_str(k.as_str()),
|
||||
JsValue::from_str(
|
||||
v.to_str()
|
||||
.map_err(|_| anyhow!("Unparsable request header"))?,
|
||||
),
|
||||
]))
|
||||
})
|
||||
.collect::<Result<Array, Error>>()?;
|
||||
|
||||
let header_map = Headers::new_with_str_sequence_sequence(&header_list)
|
||||
.map_err(|_| anyhow!("couldn't build headers"))?;
|
||||
|
||||
// Formats URI.
|
||||
let uri = parts.uri.to_string();
|
||||
let method = parts.method.as_str();
|
||||
let mut init = RequestInit::new();
|
||||
init.method(method).body(Some(body)).headers(&header_map);
|
||||
WebRequest::new_with_str_and_init(&uri, &init).map_err(|_| anyhow!("failed to build request"))
|
||||
}
|
||||
|
||||
impl Task for FetchTask {
|
||||
fn is_active(&self) -> bool {
|
||||
*self.0.active.borrow()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for FetchTask {
|
||||
fn drop(&mut self) {
|
||||
if self.is_active() {
|
||||
// Fetch API doesn't support request cancelling in all browsers
|
||||
// and we should use this workaround with a flag.
|
||||
// In that case, request not canceled, but callback won't be called.
|
||||
*self.0.active.borrow_mut() = false;
|
||||
if let Some(abort_controller) = &self.0.abort_controller {
|
||||
abort_controller.abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static GLOBAL: WindowOrWorker = WindowOrWorker::new();
|
||||
}
|
||||
|
||||
enum WindowOrWorker {
|
||||
Window(Window),
|
||||
Worker(WorkerGlobalScope),
|
||||
}
|
||||
|
||||
impl WindowOrWorker {
|
||||
fn new() -> Self {
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
type Global;
|
||||
|
||||
#[wasm_bindgen(method, getter, js_name = Window)]
|
||||
fn window(this: &Global) -> JsValue;
|
||||
|
||||
#[wasm_bindgen(method, getter, js_name = WorkerGlobalScope)]
|
||||
fn worker(this: &Global) -> JsValue;
|
||||
}
|
||||
|
||||
let global: Global = js_sys::global().unchecked_into();
|
||||
|
||||
if !global.window().is_undefined() {
|
||||
Self::Window(global.unchecked_into())
|
||||
} else if !global.worker().is_undefined() {
|
||||
Self::Worker(global.unchecked_into())
|
||||
} else {
|
||||
panic!(
|
||||
"Yew's `FetchService` only works when a `window` or `worker` object is available."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WindowOrWorker {
|
||||
fn fetch_with_request_and_init(&self, input: &WebRequest, init: &RequestInit) -> Promise {
|
||||
match self {
|
||||
Self::Window(window) => window.fetch_with_request_and_init(input, init),
|
||||
Self::Worker(worker) => worker.fetch_with_request_and_init(input, init),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,29 +2,16 @@
|
|||
//! periodic sending messages to a loop.
|
||||
|
||||
use super::Task;
|
||||
use cfg_if::cfg_if;
|
||||
use cfg_match::cfg_match;
|
||||
use gloo::timers::callback::Interval;
|
||||
use std::convert::TryInto;
|
||||
use std::fmt;
|
||||
use std::time::Duration;
|
||||
use yew::callback::Callback;
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "std_web")] {
|
||||
use stdweb::Value;
|
||||
#[allow(unused_imports)]
|
||||
use stdweb::{_js_impl, js};
|
||||
} else if #[cfg(feature = "web_sys")] {
|
||||
use gloo::timers::callback::Interval;
|
||||
}
|
||||
}
|
||||
|
||||
/// A handle which helps to cancel interval. Uses
|
||||
/// [clearInterval](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/clearInterval).
|
||||
#[must_use = "the interval is only active until the handle is dropped"]
|
||||
pub struct IntervalTask(
|
||||
#[cfg(feature = "std_web")] Value,
|
||||
#[cfg(feature = "web_sys")] Interval,
|
||||
);
|
||||
pub struct IntervalTask(Interval);
|
||||
|
||||
impl fmt::Debug for IntervalTask {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
|
@ -51,20 +38,7 @@ impl IntervalService {
|
|||
.as_millis()
|
||||
.try_into()
|
||||
.expect("duration doesn't fit in u32");
|
||||
let handle = cfg_match! {
|
||||
feature = "std_web" => js! {
|
||||
var callback = @{callback};
|
||||
var action = function() {
|
||||
callback();
|
||||
};
|
||||
var delay = @{ms};
|
||||
return {
|
||||
interval_id: setInterval(action, delay),
|
||||
callback: callback,
|
||||
};
|
||||
},
|
||||
feature = "web_sys" => Interval::new(ms, callback),
|
||||
};
|
||||
let handle = Interval::new(ms, callback);
|
||||
IntervalTask(handle)
|
||||
}
|
||||
}
|
||||
|
@ -74,19 +48,3 @@ impl Task for IntervalTask {
|
|||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for IntervalTask {
|
||||
fn drop(&mut self) {
|
||||
#[cfg(feature = "std_web")]
|
||||
{
|
||||
if self.is_active() {
|
||||
let handle = &self.0;
|
||||
js! { @(no_return)
|
||||
var handle = @{handle};
|
||||
clearInterval(handle.interval_id);
|
||||
handle.callback.drop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,19 +1,10 @@
|
|||
//! Service to register key press event listeners on elements.
|
||||
|
||||
use cfg_if::cfg_if;
|
||||
use cfg_match::cfg_match;
|
||||
use gloo::events::{EventListener, EventListenerOptions};
|
||||
use std::fmt;
|
||||
use wasm_bindgen::JsCast;
|
||||
use web_sys::{Event, EventTarget, KeyboardEvent};
|
||||
use yew::callback::Callback;
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "std_web")] {
|
||||
use stdweb::web::event::{ConcreteEvent, KeyDownEvent, KeyPressEvent, KeyUpEvent};
|
||||
use stdweb::web::{EventListenerHandle, IEventTarget};
|
||||
} else if #[cfg(feature = "web_sys")] {
|
||||
use gloo::events::{EventListener, EventListenerOptions};
|
||||
use wasm_bindgen::JsCast;
|
||||
use web_sys::{Event, EventTarget, KeyboardEvent};
|
||||
}
|
||||
}
|
||||
|
||||
/// Service for registering callbacks on elements to get keystrokes from the user.
|
||||
///
|
||||
|
@ -31,10 +22,7 @@ pub struct KeyboardService {}
|
|||
///
|
||||
/// When the handle goes out of scope, the listener will be removed from the element.
|
||||
#[must_use = "the listener is only active until the handle is dropped"]
|
||||
pub struct KeyListenerHandle(
|
||||
#[cfg(feature = "std_web")] Option<EventListenerHandle>,
|
||||
#[cfg(feature = "web_sys")] EventListener,
|
||||
);
|
||||
pub struct KeyListenerHandle(EventListener);
|
||||
|
||||
impl fmt::Debug for KeyListenerHandle {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
|
@ -51,18 +39,11 @@ impl KeyboardService {
|
|||
/// # Warning
|
||||
/// This API has been deprecated in the HTML standard and it is not recommended for use in new projects.
|
||||
/// Consult the browser compatibility chart in the linked MDN documentation.
|
||||
pub fn register_key_press<
|
||||
#[cfg(feature = "std_web")] T: IEventTarget,
|
||||
#[cfg(feature = "web_sys")] T: AsRef<EventTarget>,
|
||||
>(
|
||||
pub fn register_key_press<T: AsRef<EventTarget>>(
|
||||
element: &T,
|
||||
#[cfg(feature = "std_web")] callback: Callback<KeyPressEvent>,
|
||||
#[cfg(feature = "web_sys")] callback: Callback<KeyboardEvent>,
|
||||
callback: Callback<KeyboardEvent>,
|
||||
) -> KeyListenerHandle {
|
||||
cfg_match! {
|
||||
feature = "std_web" => register_key_impl(element, callback),
|
||||
feature = "web_sys" => register_key_impl(element, callback, "keypress"),
|
||||
}
|
||||
register_key_impl(element, callback, "keypress")
|
||||
}
|
||||
|
||||
/// Registers a callback which listens to KeyDownEvents on a provided element.
|
||||
|
@ -74,18 +55,11 @@ impl KeyboardService {
|
|||
/// This browser feature is relatively new and is set to replace the `keypress` event.
|
||||
/// It may not be fully supported in all browsers.
|
||||
/// Consult the browser compatibility chart in the linked MDN documentation.
|
||||
pub fn register_key_down<
|
||||
#[cfg(feature = "std_web")] T: IEventTarget,
|
||||
#[cfg(feature = "web_sys")] T: AsRef<EventTarget>,
|
||||
>(
|
||||
pub fn register_key_down<T: AsRef<EventTarget>>(
|
||||
element: &T,
|
||||
#[cfg(feature = "std_web")] callback: Callback<KeyDownEvent>,
|
||||
#[cfg(feature = "web_sys")] callback: Callback<KeyboardEvent>,
|
||||
callback: Callback<KeyboardEvent>,
|
||||
) -> KeyListenerHandle {
|
||||
cfg_match! {
|
||||
feature = "std_web" => register_key_impl(element, callback),
|
||||
feature = "web_sys" => register_key_impl(element, callback, "keydown"),
|
||||
}
|
||||
register_key_impl(element, callback, "keydown")
|
||||
}
|
||||
|
||||
/// Registers a callback that listens to KeyUpEvents on a provided element.
|
||||
|
@ -97,36 +71,14 @@ impl KeyboardService {
|
|||
/// This browser feature is relatively new and is set to replace keypress events.
|
||||
/// It may not be fully supported in all browsers.
|
||||
/// Consult the browser compatibility chart in the linked MDN documentation.
|
||||
pub fn register_key_up<
|
||||
#[cfg(feature = "std_web")] T: IEventTarget,
|
||||
#[cfg(feature = "web_sys")] T: AsRef<EventTarget>,
|
||||
>(
|
||||
pub fn register_key_up<T: AsRef<EventTarget>>(
|
||||
element: &T,
|
||||
#[cfg(feature = "std_web")] callback: Callback<KeyUpEvent>,
|
||||
#[cfg(feature = "web_sys")] callback: Callback<KeyboardEvent>,
|
||||
callback: Callback<KeyboardEvent>,
|
||||
) -> KeyListenerHandle {
|
||||
cfg_match! {
|
||||
feature = "std_web" => register_key_impl(element, callback),
|
||||
feature = "web_sys" => register_key_impl(element, callback, "keyup"),
|
||||
}
|
||||
register_key_impl(element, callback, "keyup")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std_web")]
|
||||
fn register_key_impl<T: IEventTarget, E: 'static + ConcreteEvent>(
|
||||
element: &T,
|
||||
callback: Callback<E>,
|
||||
) -> KeyListenerHandle {
|
||||
let handle = element.add_event_listener(move |event: E| {
|
||||
callback.emit(event);
|
||||
});
|
||||
cfg_match! {
|
||||
feature = "std_web" => KeyListenerHandle(Some(handle)),
|
||||
feature = "web_sys" => KeyListenerHandle(handle),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "web_sys")]
|
||||
fn register_key_impl<T: AsRef<EventTarget>>(
|
||||
element: &T,
|
||||
callback: Callback<KeyboardEvent>,
|
||||
|
@ -147,14 +99,3 @@ fn register_key_impl<T: AsRef<EventTarget>>(
|
|||
listener,
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(feature = "std_web")]
|
||||
impl Drop for KeyListenerHandle {
|
||||
fn drop(&mut self) {
|
||||
if let Some(handle) = self.0.take() {
|
||||
handle.remove()
|
||||
} else {
|
||||
panic!("Tried to drop KeyListenerHandle twice")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
//! Service to load files using `FileReader`.
|
||||
|
||||
use crate::Task;
|
||||
use anyhow::{anyhow, Result};
|
||||
use gloo::events::EventListener;
|
||||
use js_sys::Uint8Array;
|
||||
use std::cmp;
|
||||
use std::fmt;
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(feature = "std_web")] {
|
||||
mod std_web;
|
||||
pub use std_web::*;
|
||||
} else if #[cfg(feature = "web_sys")] {
|
||||
mod web_sys;
|
||||
pub use self::web_sys::*;
|
||||
}
|
||||
}
|
||||
#[doc(no_inline)]
|
||||
pub use web_sys::{Blob, File};
|
||||
use web_sys::{Event, FileReader};
|
||||
use yew::callback::Callback;
|
||||
|
||||
/// Struct that represents data of a file.
|
||||
#[derive(Clone, Debug)]
|
||||
|
@ -44,6 +43,95 @@ pub enum FileChunk {
|
|||
#[derive(Default, Debug)]
|
||||
pub struct ReaderService {}
|
||||
|
||||
impl ReaderService {
|
||||
/// Reads all bytes from a file and returns them with a callback.
|
||||
pub fn read_file(file: File, callback: Callback<FileData>) -> Result<ReaderTask> {
|
||||
let file_reader = FileReader::new().map_err(|_| anyhow!("couldn't acquire file reader"))?;
|
||||
let reader = file_reader.clone();
|
||||
let name = file.name();
|
||||
let callback = move |_event: &Event| {
|
||||
if let Ok(result) = reader.result() {
|
||||
let array = Uint8Array::new(&result);
|
||||
let data = FileData {
|
||||
name,
|
||||
content: array.to_vec(),
|
||||
};
|
||||
callback.emit(data);
|
||||
}
|
||||
};
|
||||
let listener = EventListener::once(&file_reader, "loadend", callback);
|
||||
file_reader.read_as_array_buffer(&file).unwrap();
|
||||
Ok(ReaderTask {
|
||||
file_reader,
|
||||
listener,
|
||||
})
|
||||
}
|
||||
|
||||
/// Reads data chunks from a file and returns them with a callback.
|
||||
pub fn read_file_by_chunks(
|
||||
file: File,
|
||||
callback: Callback<Option<FileChunk>>,
|
||||
chunk_size: usize,
|
||||
) -> Result<ReaderTask> {
|
||||
let file_reader = FileReader::new().map_err(|_| anyhow!("couldn't aquire file reader"))?;
|
||||
let name = file.name();
|
||||
let mut position = 0;
|
||||
let total_size = file.size() as usize;
|
||||
let reader = file_reader.clone();
|
||||
let callback = move |_event: &Event| {
|
||||
if let Ok(result) = reader.result() {
|
||||
if result.is_string() {
|
||||
let started = FileChunk::Started { name: name.clone() };
|
||||
callback.emit(Some(started));
|
||||
} else {
|
||||
let array = Uint8Array::new_with_byte_offset(&result, 0);
|
||||
let chunk = FileChunk::DataChunk {
|
||||
data: array.to_vec(),
|
||||
progress: position as f32 / total_size as f32,
|
||||
};
|
||||
callback.emit(Some(chunk));
|
||||
};
|
||||
// Read the next chunk
|
||||
if position < total_size {
|
||||
let from = position;
|
||||
let to = cmp::min(position + chunk_size, total_size);
|
||||
position = to;
|
||||
let blob = file.slice_with_i32_and_i32(from as _, to as _).unwrap();
|
||||
if let Err(..) = reader.read_as_array_buffer(&blob) {
|
||||
callback.emit(None);
|
||||
}
|
||||
} else {
|
||||
let finished = FileChunk::Finished;
|
||||
callback.emit(Some(finished));
|
||||
}
|
||||
} else {
|
||||
callback.emit(None);
|
||||
}
|
||||
};
|
||||
let listener = EventListener::new(&file_reader, "loadend", callback);
|
||||
let blob = Blob::new().map_err(|_| anyhow!("Blob constructor is not supported"))?;
|
||||
file_reader.read_as_text(&blob).unwrap();
|
||||
Ok(ReaderTask {
|
||||
file_reader,
|
||||
listener,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A handle to control reading.
|
||||
#[must_use = "the reader will abort when the task is dropped"]
|
||||
pub struct ReaderTask {
|
||||
pub(super) file_reader: FileReader,
|
||||
#[allow(dead_code)]
|
||||
listener: EventListener,
|
||||
}
|
||||
|
||||
impl Task for ReaderTask {
|
||||
fn is_active(&self) -> bool {
|
||||
self.file_reader.ready_state() == FileReader::LOADING
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for ReaderTask {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str("ReaderTask")
|
||||
|
|
|
@ -1,122 +0,0 @@
|
|||
//! `stdweb` implementation for the reader service.
|
||||
|
||||
use super::*;
|
||||
use crate::Task;
|
||||
use std::cmp;
|
||||
use stdweb::unstable::{TryFrom, TryInto};
|
||||
use stdweb::web::event::LoadEndEvent;
|
||||
#[doc(no_inline)]
|
||||
pub use stdweb::web::{Blob, File, IBlob};
|
||||
use stdweb::web::{FileReader, FileReaderReadyState, FileReaderResult, IEventTarget, TypedArray};
|
||||
#[allow(unused_imports)]
|
||||
use stdweb::{_js_impl, js};
|
||||
use yew::callback::Callback;
|
||||
|
||||
fn new_file_reader() -> Result<FileReader, &'static str> {
|
||||
let file_reader = js! {
|
||||
try {
|
||||
return new FileReader;
|
||||
} catch(error) {
|
||||
return error;
|
||||
}
|
||||
};
|
||||
FileReader::try_from(js!( return @{file_reader.as_ref()}; ))
|
||||
.map_err(|_| "couldn't aquire file reader")
|
||||
}
|
||||
|
||||
impl ReaderService {
|
||||
/// Reads all bytes from a file and returns them with a callback.
|
||||
pub fn read_file(file: File, callback: Callback<FileData>) -> Result<ReaderTask, &'static str> {
|
||||
let file_reader = new_file_reader()?;
|
||||
let reader = file_reader.clone();
|
||||
let name = file.name();
|
||||
file_reader.add_event_listener(move |_event: LoadEndEvent| match reader.result() {
|
||||
Some(FileReaderResult::String(_)) => {
|
||||
unreachable!();
|
||||
}
|
||||
Some(FileReaderResult::ArrayBuffer(buffer)) => {
|
||||
let array: TypedArray<u8> = buffer.into();
|
||||
let data = FileData {
|
||||
name: name.clone(),
|
||||
content: array.to_vec(),
|
||||
};
|
||||
callback.emit(data);
|
||||
}
|
||||
None => {}
|
||||
});
|
||||
file_reader.read_as_array_buffer(&file).unwrap();
|
||||
Ok(ReaderTask { file_reader })
|
||||
}
|
||||
|
||||
/// Reads data chunks from a file and returns them with a callback.
|
||||
pub fn read_file_by_chunks(
|
||||
file: File,
|
||||
callback: Callback<Option<FileChunk>>,
|
||||
chunk_size: usize,
|
||||
) -> Result<ReaderTask, &'static str> {
|
||||
let file_reader = new_file_reader()?;
|
||||
let name = file.name();
|
||||
let mut position = 0;
|
||||
let total_size = file.len() as usize;
|
||||
let reader = file_reader.clone();
|
||||
file_reader.add_event_listener(move |_event: LoadEndEvent| {
|
||||
if let Some(result) = reader.result() {
|
||||
match result {
|
||||
// This branch is used to start reading
|
||||
FileReaderResult::String(_) => {
|
||||
let started = FileChunk::Started { name: name.clone() };
|
||||
callback.emit(Some(started));
|
||||
}
|
||||
// This branch is used to send a chunk value
|
||||
FileReaderResult::ArrayBuffer(buffer) => {
|
||||
let array: TypedArray<u8> = buffer.into();
|
||||
let chunk = FileChunk::DataChunk {
|
||||
data: array.to_vec(),
|
||||
progress: position as f32 / total_size as f32,
|
||||
};
|
||||
callback.emit(Some(chunk));
|
||||
}
|
||||
}
|
||||
|
||||
// Read the next chunk
|
||||
if position < total_size {
|
||||
let file = &file;
|
||||
let from = position;
|
||||
let to = cmp::min(position + chunk_size, total_size);
|
||||
position = to;
|
||||
// TODO(#942): Implement `slice` method in `stdweb`
|
||||
let blob: Blob = (js! {
|
||||
return @{file}.slice(@{from as u32}, @{to as u32});
|
||||
})
|
||||
.try_into()
|
||||
.unwrap();
|
||||
reader.read_as_array_buffer(&blob).unwrap();
|
||||
} else {
|
||||
let finished = FileChunk::Finished;
|
||||
callback.emit(Some(finished));
|
||||
}
|
||||
} else {
|
||||
callback.emit(None);
|
||||
}
|
||||
});
|
||||
let blob: Blob = (js! {
|
||||
return (new Blob());
|
||||
})
|
||||
.try_into()
|
||||
.unwrap();
|
||||
file_reader.read_as_text(&blob).unwrap();
|
||||
Ok(ReaderTask { file_reader })
|
||||
}
|
||||
}
|
||||
|
||||
/// A handle to control reading.
|
||||
#[must_use = "the reader will abort when the task is dropped"]
|
||||
pub struct ReaderTask {
|
||||
pub(super) file_reader: FileReader,
|
||||
}
|
||||
|
||||
impl Task for ReaderTask {
|
||||
fn is_active(&self) -> bool {
|
||||
self.file_reader.ready_state() == FileReaderReadyState::Loading
|
||||
}
|
||||
}
|
|
@ -1,101 +0,0 @@
|
|||
//! `web-sys` implementation for the reader service.
|
||||
|
||||
use super::*;
|
||||
use crate::Task;
|
||||
#[doc(no_inline)]
|
||||
pub use ::web_sys::{Blob, File};
|
||||
use ::web_sys::{Event, FileReader};
|
||||
use anyhow::{anyhow, Result};
|
||||
use gloo::events::EventListener;
|
||||
use js_sys::Uint8Array;
|
||||
use std::cmp;
|
||||
use yew::callback::Callback;
|
||||
|
||||
impl ReaderService {
|
||||
/// Reads all bytes from a file and returns them with a callback.
|
||||
pub fn read_file(file: File, callback: Callback<FileData>) -> Result<ReaderTask> {
|
||||
let file_reader = FileReader::new().map_err(|_| anyhow!("couldn't acquire file reader"))?;
|
||||
let reader = file_reader.clone();
|
||||
let name = file.name();
|
||||
let callback = move |_event: &Event| {
|
||||
if let Ok(result) = reader.result() {
|
||||
let array = Uint8Array::new(&result);
|
||||
let data = FileData {
|
||||
name,
|
||||
content: array.to_vec(),
|
||||
};
|
||||
callback.emit(data);
|
||||
}
|
||||
};
|
||||
let listener = EventListener::once(&file_reader, "loadend", callback);
|
||||
file_reader.read_as_array_buffer(&file).unwrap();
|
||||
Ok(ReaderTask {
|
||||
file_reader,
|
||||
listener,
|
||||
})
|
||||
}
|
||||
|
||||
/// Reads data chunks from a file and returns them with a callback.
|
||||
pub fn read_file_by_chunks(
|
||||
file: File,
|
||||
callback: Callback<Option<FileChunk>>,
|
||||
chunk_size: usize,
|
||||
) -> Result<ReaderTask> {
|
||||
let file_reader = FileReader::new().map_err(|_| anyhow!("couldn't aquire file reader"))?;
|
||||
let name = file.name();
|
||||
let mut position = 0;
|
||||
let total_size = file.size() as usize;
|
||||
let reader = file_reader.clone();
|
||||
let callback = move |_event: &Event| {
|
||||
if let Ok(result) = reader.result() {
|
||||
if result.is_string() {
|
||||
let started = FileChunk::Started { name: name.clone() };
|
||||
callback.emit(Some(started));
|
||||
} else {
|
||||
let array = Uint8Array::new_with_byte_offset(&result, 0);
|
||||
let chunk = FileChunk::DataChunk {
|
||||
data: array.to_vec(),
|
||||
progress: position as f32 / total_size as f32,
|
||||
};
|
||||
callback.emit(Some(chunk));
|
||||
};
|
||||
// Read the next chunk
|
||||
if position < total_size {
|
||||
let from = position;
|
||||
let to = cmp::min(position + chunk_size, total_size);
|
||||
position = to;
|
||||
let blob = file.slice_with_i32_and_i32(from as _, to as _).unwrap();
|
||||
if let Err(..) = reader.read_as_array_buffer(&blob) {
|
||||
callback.emit(None);
|
||||
}
|
||||
} else {
|
||||
let finished = FileChunk::Finished;
|
||||
callback.emit(Some(finished));
|
||||
}
|
||||
} else {
|
||||
callback.emit(None);
|
||||
}
|
||||
};
|
||||
let listener = EventListener::new(&file_reader, "loadend", callback);
|
||||
let blob = Blob::new().map_err(|_| anyhow!("Blob constructor is not supported"))?;
|
||||
file_reader.read_as_text(&blob).unwrap();
|
||||
Ok(ReaderTask {
|
||||
file_reader,
|
||||
listener,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A handle to control reading.
|
||||
#[must_use = "the reader will abort when the task is dropped"]
|
||||
pub struct ReaderTask {
|
||||
pub(super) file_reader: FileReader,
|
||||
#[allow(dead_code)]
|
||||
listener: EventListener,
|
||||
}
|
||||
|
||||
impl Task for ReaderTask {
|
||||
fn is_active(&self) -> bool {
|
||||
self.file_reader.ready_state() == FileReader::LOADING
|
||||
}
|
||||
}
|
|
@ -2,31 +2,16 @@
|
|||
//! request frame rendering
|
||||
|
||||
use crate::Task;
|
||||
use cfg_if::cfg_if;
|
||||
use cfg_match::cfg_match;
|
||||
use std::fmt;
|
||||
use wasm_bindgen::closure::Closure;
|
||||
use wasm_bindgen::{JsCast, JsValue};
|
||||
use yew::callback::Callback;
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "std_web")] {
|
||||
#[allow(unused_imports)]
|
||||
use stdweb::{_js_impl, js};
|
||||
use stdweb::unstable::TryInto;
|
||||
use stdweb::Value;
|
||||
} else if #[cfg(feature = "web_sys")] {
|
||||
use yew::utils;
|
||||
use wasm_bindgen::closure::Closure;
|
||||
use wasm_bindgen::{JsCast, JsValue};
|
||||
}
|
||||
}
|
||||
use yew::utils;
|
||||
|
||||
/// A handle to cancel a render task.
|
||||
#[must_use = "the task will be cancelled when the task is dropped"]
|
||||
pub struct RenderTask(
|
||||
#[cfg(feature = "std_web")] Value,
|
||||
#[cfg(feature = "web_sys")] RenderTaskInner,
|
||||
);
|
||||
pub struct RenderTask(RenderTaskInner);
|
||||
|
||||
#[cfg(feature = "web_sys")]
|
||||
struct RenderTaskInner {
|
||||
render_id: i32,
|
||||
#[allow(dead_code)]
|
||||
|
@ -46,39 +31,19 @@ pub struct RenderService {}
|
|||
impl RenderService {
|
||||
/// Request animation frame. Callback will be notified when frame should be rendered.
|
||||
pub fn request_animation_frame(callback: Callback<f64>) -> RenderTask {
|
||||
let callback = move |#[cfg(feature = "std_web")] v,
|
||||
#[cfg(feature = "web_sys")] v: JsValue| {
|
||||
let time: f64 = cfg_match! {
|
||||
feature = "std_web" => ({
|
||||
match v {
|
||||
Value::Number(n) => n.try_into().unwrap(),
|
||||
_ => 0.0,
|
||||
}
|
||||
}),
|
||||
feature = "web_sys" => v.as_f64().unwrap_or(0.),
|
||||
};
|
||||
let callback = move |v: JsValue| {
|
||||
let time: f64 = v.as_f64().unwrap_or(0.);
|
||||
callback.emit(time);
|
||||
};
|
||||
let handle = cfg_match! {
|
||||
feature = "std_web" => js! {
|
||||
var callback = @{callback};
|
||||
var action = function(time) {
|
||||
callback(time);
|
||||
callback.drop();
|
||||
};
|
||||
return {
|
||||
render_id: requestAnimationFrame(action),
|
||||
callback: callback,
|
||||
};
|
||||
},
|
||||
feature = "web_sys" => ({
|
||||
let callback = Closure::wrap(Box::new(callback) as Box<dyn Fn(JsValue)>);
|
||||
let render_id = utils::window().request_animation_frame(callback.as_ref().unchecked_ref()).unwrap();
|
||||
RenderTaskInner {
|
||||
render_id,
|
||||
callback,
|
||||
}
|
||||
}),
|
||||
let handle = {
|
||||
let callback = Closure::wrap(Box::new(callback) as Box<dyn Fn(JsValue)>);
|
||||
let render_id = utils::window()
|
||||
.request_animation_frame(callback.as_ref().unchecked_ref())
|
||||
.unwrap();
|
||||
RenderTaskInner {
|
||||
render_id,
|
||||
callback,
|
||||
}
|
||||
};
|
||||
RenderTask(handle)
|
||||
}
|
||||
|
@ -93,17 +58,9 @@ impl Task for RenderTask {
|
|||
impl Drop for RenderTask {
|
||||
fn drop(&mut self) {
|
||||
if self.is_active() {
|
||||
cfg_match! {
|
||||
feature = "std_web" => ({
|
||||
let handle = &self.0;
|
||||
js! { @(no_return)
|
||||
var handle = @{handle};
|
||||
cancelAnimationFrame(handle.render_id);
|
||||
handle.callback.drop();
|
||||
}
|
||||
}),
|
||||
feature = "web_sys" => utils::window().cancel_animation_frame(self.0.render_id).unwrap(),
|
||||
}
|
||||
utils::window()
|
||||
.cancel_animation_frame(self.0.render_id)
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,19 +1,9 @@
|
|||
//! This module contains Yew's implementation of a service which listens for browser window resize events.
|
||||
use crate::Task;
|
||||
use cfg_if::cfg_if;
|
||||
use cfg_match::cfg_match;
|
||||
use gloo::events::EventListener;
|
||||
use std::fmt;
|
||||
use web_sys::{Event, Window};
|
||||
use yew::callback::Callback;
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "std_web")] {
|
||||
use stdweb::js;
|
||||
use stdweb::web::{event::ResizeEvent, window, Window};
|
||||
use stdweb::Value;
|
||||
} else if #[cfg(feature = "web_sys")] {
|
||||
use gloo::events::EventListener;
|
||||
use web_sys::{Event, Window};
|
||||
}
|
||||
}
|
||||
|
||||
/// A service which fires events when the browser window is resized.
|
||||
#[derive(Default, Debug)]
|
||||
|
@ -21,10 +11,7 @@ pub struct ResizeService {}
|
|||
|
||||
/// A handle for the event listener listening for resize events.
|
||||
#[must_use = "the listener is only active until the task is dropped"]
|
||||
pub struct ResizeTask(
|
||||
#[cfg(feature = "std_web")] Value,
|
||||
#[cfg(feature = "web_sys")] EventListener,
|
||||
);
|
||||
pub struct ResizeTask(EventListener);
|
||||
|
||||
impl fmt::Debug for ResizeTask {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
|
@ -46,7 +33,6 @@ impl WindowDimensions {
|
|||
pub fn get_dimensions(window: &Window) -> Self {
|
||||
let width = window.inner_width();
|
||||
let height = window.inner_height();
|
||||
#[cfg(feature = "web_sys")]
|
||||
let (width, height) = {
|
||||
(
|
||||
width.unwrap().as_f64().unwrap() as _,
|
||||
|
@ -60,24 +46,12 @@ impl WindowDimensions {
|
|||
impl ResizeService {
|
||||
/// Register a callback that will be called when the browser window resizes.
|
||||
pub fn register(callback: Callback<WindowDimensions>) -> ResizeTask {
|
||||
let callback =
|
||||
move |#[cfg(feature = "web_sys")] _event: &Event,
|
||||
#[cfg(feature = "std_web")] _event: ResizeEvent| {
|
||||
let window = cfg_match! {
|
||||
feature = "std_web" => window(),
|
||||
feature = "web_sys" => web_sys::window().unwrap(),
|
||||
};
|
||||
let dimensions = WindowDimensions::get_dimensions(&window);
|
||||
callback.emit(dimensions);
|
||||
};
|
||||
let handle = cfg_match! {
|
||||
feature = "std_web" => js! {
|
||||
var handle = @{callback};
|
||||
window.addEventListener("resize", handle);
|
||||
return handle;
|
||||
},
|
||||
feature = "web_sys" => EventListener::new(&web_sys::window().unwrap(), "resize", callback),
|
||||
let callback = move |_event: &Event| {
|
||||
let window = web_sys::window().unwrap();
|
||||
let dimensions = WindowDimensions::get_dimensions(&window);
|
||||
callback.emit(dimensions);
|
||||
};
|
||||
let handle = EventListener::new(&web_sys::window().unwrap(), "resize", callback);
|
||||
ResizeTask(handle)
|
||||
}
|
||||
}
|
||||
|
@ -87,17 +61,3 @@ impl Task for ResizeTask {
|
|||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ResizeTask {
|
||||
fn drop(&mut self) {
|
||||
#[cfg(feature = "std_web")]
|
||||
{
|
||||
let handle = &self.0;
|
||||
js! {
|
||||
@(no_return)
|
||||
var handle = @{handle};
|
||||
window.removeEventListener("resize", handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,22 +1,11 @@
|
|||
//! This module contains Yew's implementation of a service to
|
||||
//! use local and session storage of a browser.
|
||||
|
||||
use cfg_if::cfg_if;
|
||||
use cfg_match::cfg_match;
|
||||
use std::fmt;
|
||||
use thiserror::Error;
|
||||
use web_sys::Storage;
|
||||
use yew::format::Text;
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "std_web")] {
|
||||
#[allow(unused_imports)]
|
||||
use stdweb::{_js_impl, js};
|
||||
use stdweb::unstable::TryFrom;
|
||||
use stdweb::web::{Storage};
|
||||
} else if #[cfg(feature = "web_sys")] {
|
||||
use yew::utils;
|
||||
use web_sys::Storage;
|
||||
}
|
||||
}
|
||||
use yew::utils;
|
||||
|
||||
/// Represents errors of a storage.
|
||||
#[derive(Debug, Error)]
|
||||
|
@ -48,30 +37,14 @@ impl fmt::Debug for StorageService {
|
|||
impl StorageService {
|
||||
/// Creates a new storage service instance with specified storage area.
|
||||
pub fn new(area: Area) -> Result<Self, &'static str> {
|
||||
let storage = cfg_match! {
|
||||
feature = "std_web" => ({
|
||||
let storage_name = match area {
|
||||
Area::Local => "localStorage",
|
||||
Area::Session => "sessionStorage",
|
||||
};
|
||||
let storage = js! {
|
||||
try {
|
||||
return window[@{storage_name}];
|
||||
} catch(error) {
|
||||
return error;
|
||||
}
|
||||
};
|
||||
Storage::try_from(js!( return @{storage.as_ref()}; ))
|
||||
}),
|
||||
feature = "web_sys" => ({
|
||||
let storage = {
|
||||
match area {
|
||||
Area::Local => utils::window().local_storage(),
|
||||
Area::Session => utils::window().session_storage(),
|
||||
}
|
||||
};
|
||||
storage.map(Option::unwrap)
|
||||
}),
|
||||
let storage = {
|
||||
let storage = {
|
||||
match area {
|
||||
Area::Local => utils::window().local_storage(),
|
||||
Area::Session => utils::window().session_storage(),
|
||||
}
|
||||
};
|
||||
storage.map(Option::unwrap)
|
||||
};
|
||||
|
||||
storage
|
||||
|
@ -85,10 +58,7 @@ impl StorageService {
|
|||
T: Into<Text>,
|
||||
{
|
||||
if let Ok(data) = value.into() {
|
||||
let result = cfg_match! {
|
||||
feature = "std_web" => self.storage.insert(key, &data),
|
||||
feature = "web_sys" => self.storage.set_item(key, &data),
|
||||
};
|
||||
let result = self.storage.set_item(key, &data);
|
||||
result.expect("can't insert value to a storage");
|
||||
}
|
||||
}
|
||||
|
@ -98,19 +68,13 @@ impl StorageService {
|
|||
where
|
||||
T: From<Text>,
|
||||
{
|
||||
let data = cfg_match! {
|
||||
feature = "std_web" => self.storage.get(key),
|
||||
feature = "web_sys" => self.storage.get_item(key).unwrap(),
|
||||
};
|
||||
let data = self.storage.get_item(key).unwrap();
|
||||
let data = data.ok_or_else(|| StorageError::CantRestore.into());
|
||||
T::from(data)
|
||||
}
|
||||
|
||||
/// Removes value from the storage.
|
||||
pub fn remove(&mut self, key: &str) {
|
||||
cfg_match! {
|
||||
feature = "std_web" => self.storage.remove(key),
|
||||
feature = "web_sys" => self.storage.remove_item(key).unwrap(),
|
||||
};
|
||||
self.storage.remove_item(key).unwrap();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,28 +2,15 @@
|
|||
//! send messages when a timeout has elapsed.
|
||||
|
||||
use super::Task;
|
||||
use cfg_if::cfg_if;
|
||||
use cfg_match::cfg_match;
|
||||
use gloo::timers::callback::Timeout;
|
||||
use std::convert::TryInto;
|
||||
use std::fmt;
|
||||
use std::time::Duration;
|
||||
use yew::callback::Callback;
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "std_web")] {
|
||||
use stdweb::Value;
|
||||
#[allow(unused_imports)]
|
||||
use stdweb::{_js_impl, js};
|
||||
} else if #[cfg(feature = "web_sys")] {
|
||||
use gloo::timers::callback::Timeout;
|
||||
}
|
||||
}
|
||||
|
||||
/// A handle to cancel a timeout task.
|
||||
#[must_use = "the timeout will be cleared when the task is dropped"]
|
||||
pub struct TimeoutTask(
|
||||
#[cfg(feature = "std_web")] Option<Value>,
|
||||
#[cfg(feature = "web_sys")] Option<Timeout>,
|
||||
);
|
||||
pub struct TimeoutTask(Option<Timeout>);
|
||||
|
||||
impl fmt::Debug for TimeoutTask {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
|
@ -49,21 +36,7 @@ impl TimeoutService {
|
|||
.as_millis()
|
||||
.try_into()
|
||||
.expect("duration doesn't fit in u32");
|
||||
let handle = cfg_match! {
|
||||
feature = "std_web" => js! {
|
||||
var callback = @{callback};
|
||||
var action = function() {
|
||||
callback();
|
||||
callback.drop();
|
||||
};
|
||||
var delay = @{ms};
|
||||
return {
|
||||
timeout_id: setTimeout(action, delay),
|
||||
callback: callback,
|
||||
};
|
||||
},
|
||||
feature = "web_sys" => Timeout::new(ms, callback),
|
||||
};
|
||||
let handle = Timeout::new(ms, callback);
|
||||
TimeoutTask(Some(handle))
|
||||
}
|
||||
}
|
||||
|
@ -73,19 +46,3 @@ impl Task for TimeoutTask {
|
|||
self.0.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TimeoutTask {
|
||||
fn drop(&mut self) {
|
||||
#[cfg(feature = "std_web")]
|
||||
{
|
||||
if self.is_active() {
|
||||
let handle = &self.0;
|
||||
js! { @(no_return)
|
||||
var handle = @{handle};
|
||||
clearTimeout(handle.timeout_id);
|
||||
handle.callback.drop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,23 +2,13 @@
|
|||
//! [`WebSocket` Protocol](https://tools.ietf.org/html/rfc6455).
|
||||
|
||||
use super::Task;
|
||||
use cfg_if::cfg_if;
|
||||
use cfg_match::cfg_match;
|
||||
use gloo::events::EventListener;
|
||||
use js_sys::Uint8Array;
|
||||
use std::fmt;
|
||||
use wasm_bindgen::JsCast;
|
||||
use web_sys::{BinaryType, Event, MessageEvent, WebSocket};
|
||||
use yew::callback::Callback;
|
||||
use yew::format::{Binary, FormatError, Text};
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "std_web")] {
|
||||
use stdweb::traits::IMessageEvent;
|
||||
use stdweb::web::event::{SocketCloseEvent, SocketErrorEvent, SocketMessageEvent, SocketOpenEvent};
|
||||
use stdweb::web::{IEventTarget, SocketBinaryType, SocketReadyState, WebSocket};
|
||||
} else if #[cfg(feature = "web_sys")] {
|
||||
use gloo::events::EventListener;
|
||||
use js_sys::Uint8Array;
|
||||
use wasm_bindgen::JsCast;
|
||||
use web_sys::{BinaryType, Event, MessageEvent, WebSocket};
|
||||
}
|
||||
}
|
||||
|
||||
/// The status of a WebSocket connection. Used for status notifications.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
|
@ -44,12 +34,10 @@ pub enum WebSocketError {
|
|||
pub struct WebSocketTask {
|
||||
ws: WebSocket,
|
||||
notification: Callback<WebSocketStatus>,
|
||||
#[cfg(feature = "web_sys")]
|
||||
#[allow(dead_code)]
|
||||
listeners: [EventListener; 4],
|
||||
}
|
||||
|
||||
#[cfg(feature = "web_sys")]
|
||||
impl WebSocketTask {
|
||||
fn new(
|
||||
ws: WebSocket,
|
||||
|
@ -87,23 +75,12 @@ impl WebSocketService {
|
|||
where
|
||||
OUT: From<Text> + From<Binary>,
|
||||
{
|
||||
cfg_match! {
|
||||
feature = "std_web" => ({
|
||||
let ws = Self::connect_common(url, ¬ification)?.0;
|
||||
ws.add_event_listener(move |event: SocketMessageEvent| {
|
||||
process_both(&event, &callback);
|
||||
});
|
||||
Ok(WebSocketTask { ws, notification })
|
||||
}),
|
||||
feature = "web_sys" => ({
|
||||
let ConnectCommon(ws, listeners) = Self::connect_common(url, ¬ification)?;
|
||||
let listener = EventListener::new(&ws, "message", move |event: &Event| {
|
||||
let event = event.dyn_ref::<MessageEvent>().unwrap();
|
||||
process_both(&event, &callback);
|
||||
});
|
||||
WebSocketTask::new(ws, notification, listener, listeners)
|
||||
}),
|
||||
}
|
||||
let ConnectCommon(ws, listeners) = Self::connect_common(url, ¬ification)?;
|
||||
let listener = EventListener::new(&ws, "message", move |event: &Event| {
|
||||
let event = event.dyn_ref::<MessageEvent>().unwrap();
|
||||
process_both(&event, &callback);
|
||||
});
|
||||
WebSocketTask::new(ws, notification, listener, listeners)
|
||||
}
|
||||
|
||||
/// Connects to a server through a WebSocket connection, like connect,
|
||||
|
@ -118,23 +95,12 @@ impl WebSocketService {
|
|||
where
|
||||
OUT: From<Binary>,
|
||||
{
|
||||
cfg_match! {
|
||||
feature = "std_web" => ({
|
||||
let ws = Self::connect_common(url, ¬ification)?.0;
|
||||
ws.add_event_listener(move |event: SocketMessageEvent| {
|
||||
process_binary(&event, &callback);
|
||||
});
|
||||
Ok(WebSocketTask { ws, notification })
|
||||
}),
|
||||
feature = "web_sys" => ({
|
||||
let ConnectCommon(ws, listeners) = Self::connect_common(url, ¬ification)?;
|
||||
let listener = EventListener::new(&ws, "message", move |event: &Event| {
|
||||
let event = event.dyn_ref::<MessageEvent>().unwrap();
|
||||
process_binary(&event, &callback);
|
||||
});
|
||||
WebSocketTask::new(ws, notification, listener, listeners)
|
||||
}),
|
||||
}
|
||||
let ConnectCommon(ws, listeners) = Self::connect_common(url, ¬ification)?;
|
||||
let listener = EventListener::new(&ws, "message", move |event: &Event| {
|
||||
let event = event.dyn_ref::<MessageEvent>().unwrap();
|
||||
process_binary(&event, &callback);
|
||||
});
|
||||
WebSocketTask::new(ws, notification, listener, listeners)
|
||||
}
|
||||
|
||||
/// Connects to a server through a WebSocket connection, like connect,
|
||||
|
@ -149,23 +115,12 @@ impl WebSocketService {
|
|||
where
|
||||
OUT: From<Text>,
|
||||
{
|
||||
cfg_match! {
|
||||
feature = "std_web" => ({
|
||||
let ws = Self::connect_common(url, ¬ification)?.0;
|
||||
ws.add_event_listener(move |event: SocketMessageEvent| {
|
||||
process_text(&event, &callback);
|
||||
});
|
||||
Ok(WebSocketTask { ws, notification })
|
||||
}),
|
||||
feature = "web_sys" => ({
|
||||
let ConnectCommon(ws, listeners) = Self::connect_common(url, ¬ification)?;
|
||||
let listener = EventListener::new(&ws, "message", move |event: &Event| {
|
||||
let event = event.dyn_ref::<MessageEvent>().unwrap();
|
||||
process_text(&event, &callback);
|
||||
});
|
||||
WebSocketTask::new(ws, notification, listener, listeners)
|
||||
}),
|
||||
}
|
||||
let ConnectCommon(ws, listeners) = Self::connect_common(url, ¬ification)?;
|
||||
let listener = EventListener::new(&ws, "message", move |event: &Event| {
|
||||
let event = event.dyn_ref::<MessageEvent>().unwrap();
|
||||
process_text(&event, &callback);
|
||||
});
|
||||
WebSocketTask::new(ws, notification, listener, listeners)
|
||||
}
|
||||
|
||||
fn connect_common(
|
||||
|
@ -174,79 +129,44 @@ impl WebSocketService {
|
|||
) -> Result<ConnectCommon, WebSocketError> {
|
||||
let ws = WebSocket::new(url);
|
||||
|
||||
let ws = ws.map_err(
|
||||
#[cfg(feature = "std_web")]
|
||||
|_| WebSocketError::CreationError("Error opening a WebSocket connection.".to_string()),
|
||||
#[cfg(feature = "web_sys")]
|
||||
|ws_error| {
|
||||
WebSocketError::CreationError(
|
||||
ws_error
|
||||
.unchecked_into::<js_sys::Error>()
|
||||
.to_string()
|
||||
.as_string()
|
||||
.unwrap(),
|
||||
)
|
||||
},
|
||||
)?;
|
||||
let ws = ws.map_err(|ws_error| {
|
||||
WebSocketError::CreationError(
|
||||
ws_error
|
||||
.unchecked_into::<js_sys::Error>()
|
||||
.to_string()
|
||||
.as_string()
|
||||
.unwrap(),
|
||||
)
|
||||
})?;
|
||||
|
||||
cfg_match! {
|
||||
feature = "std_web" => ws.set_binary_type(SocketBinaryType::ArrayBuffer),
|
||||
feature = "web_sys" => ws.set_binary_type(BinaryType::Arraybuffer),
|
||||
ws.set_binary_type(BinaryType::Arraybuffer);
|
||||
let notify = notification.clone();
|
||||
let listener_open = move |_: &Event| {
|
||||
notify.emit(WebSocketStatus::Opened);
|
||||
};
|
||||
let notify = notification.clone();
|
||||
let listener_open =
|
||||
move |#[cfg(feature = "std_web")] _: SocketOpenEvent,
|
||||
#[cfg(feature = "web_sys")] _: &Event| {
|
||||
notify.emit(WebSocketStatus::Opened);
|
||||
};
|
||||
let listener_close = move |_: &Event| {
|
||||
notify.emit(WebSocketStatus::Closed);
|
||||
};
|
||||
let notify = notification.clone();
|
||||
let listener_close =
|
||||
move |#[cfg(feature = "std_web")] _: SocketCloseEvent,
|
||||
#[cfg(feature = "web_sys")] _: &Event| {
|
||||
notify.emit(WebSocketStatus::Closed);
|
||||
};
|
||||
let notify = notification.clone();
|
||||
let listener_error =
|
||||
move |#[cfg(feature = "std_web")] _: SocketErrorEvent,
|
||||
#[cfg(feature = "web_sys")] _: &Event| {
|
||||
notify.emit(WebSocketStatus::Error);
|
||||
};
|
||||
#[cfg_attr(feature = "std_web", allow(clippy::let_unit_value, unused_variables))]
|
||||
{
|
||||
let listeners = cfg_match! {
|
||||
feature = "std_web" => ({
|
||||
ws.add_event_listener(listener_open);
|
||||
ws.add_event_listener(listener_close);
|
||||
ws.add_event_listener(listener_error);
|
||||
}),
|
||||
feature = "web_sys" => [
|
||||
EventListener::new(&ws, "open", listener_open),
|
||||
EventListener::new(&ws, "close", listener_close),
|
||||
EventListener::new(&ws, "error", listener_error),
|
||||
],
|
||||
};
|
||||
Ok(ConnectCommon(
|
||||
ws,
|
||||
#[cfg(feature = "web_sys")]
|
||||
listeners,
|
||||
))
|
||||
}
|
||||
let listener_error = move |_: &Event| {
|
||||
notify.emit(WebSocketStatus::Error);
|
||||
};
|
||||
let listeners = [
|
||||
EventListener::new(&ws, "open", listener_open),
|
||||
EventListener::new(&ws, "close", listener_close),
|
||||
EventListener::new(&ws, "error", listener_error),
|
||||
];
|
||||
Ok(ConnectCommon(ws, listeners))
|
||||
}
|
||||
}
|
||||
|
||||
struct ConnectCommon(WebSocket, #[cfg(feature = "web_sys")] [EventListener; 3]);
|
||||
struct ConnectCommon(WebSocket, [EventListener; 3]);
|
||||
|
||||
fn process_binary<OUT: 'static>(
|
||||
#[cfg(feature = "std_web")] event: &SocketMessageEvent,
|
||||
#[cfg(feature = "web_sys")] event: &MessageEvent,
|
||||
callback: &Callback<OUT>,
|
||||
) where
|
||||
fn process_binary<OUT: 'static>(event: &MessageEvent, callback: &Callback<OUT>)
|
||||
where
|
||||
OUT: From<Binary>,
|
||||
{
|
||||
#[cfg(feature = "std_web")]
|
||||
let bytes = event.data().into_array_buffer();
|
||||
|
||||
#[cfg(feature = "web_sys")]
|
||||
let bytes = if !event.data().is_string() {
|
||||
Some(event.data())
|
||||
} else {
|
||||
|
@ -254,10 +174,7 @@ fn process_binary<OUT: 'static>(
|
|||
};
|
||||
|
||||
let data = if let Some(bytes) = bytes {
|
||||
let bytes: Vec<u8> = cfg_match! {
|
||||
feature = "std_web" => bytes.into(),
|
||||
feature = "web_sys" => Uint8Array::new(&bytes).to_vec(),
|
||||
};
|
||||
let bytes: Vec<u8> = Uint8Array::new(&bytes).to_vec();
|
||||
Ok(bytes)
|
||||
} else {
|
||||
Err(FormatError::ReceivedTextForBinary.into())
|
||||
|
@ -267,17 +184,11 @@ fn process_binary<OUT: 'static>(
|
|||
callback.emit(out);
|
||||
}
|
||||
|
||||
fn process_text<OUT: 'static>(
|
||||
#[cfg(feature = "std_web")] event: &SocketMessageEvent,
|
||||
#[cfg(feature = "web_sys")] event: &MessageEvent,
|
||||
callback: &Callback<OUT>,
|
||||
) where
|
||||
fn process_text<OUT: 'static>(event: &MessageEvent, callback: &Callback<OUT>)
|
||||
where
|
||||
OUT: From<Text>,
|
||||
{
|
||||
let text = cfg_match! {
|
||||
feature = "std_web" => event.data().into_text(),
|
||||
feature = "web_sys" => event.data().as_string(),
|
||||
};
|
||||
let text = event.data().as_string();
|
||||
|
||||
let data = if let Some(text) = text {
|
||||
Ok(text)
|
||||
|
@ -289,17 +200,10 @@ fn process_text<OUT: 'static>(
|
|||
callback.emit(out);
|
||||
}
|
||||
|
||||
fn process_both<OUT: 'static>(
|
||||
#[cfg(feature = "std_web")] event: &SocketMessageEvent,
|
||||
#[cfg(feature = "web_sys")] event: &MessageEvent,
|
||||
callback: &Callback<OUT>,
|
||||
) where
|
||||
fn process_both<OUT: 'static>(event: &MessageEvent, callback: &Callback<OUT>)
|
||||
where
|
||||
OUT: From<Text> + From<Binary>,
|
||||
{
|
||||
#[cfg(feature = "std_web")]
|
||||
let is_text = event.data().into_text().is_some();
|
||||
|
||||
#[cfg(feature = "web_sys")]
|
||||
let is_text = event.data().is_string();
|
||||
|
||||
if is_text {
|
||||
|
@ -316,10 +220,7 @@ impl WebSocketTask {
|
|||
IN: Into<Text>,
|
||||
{
|
||||
if let Ok(body) = data.into() {
|
||||
let result = cfg_match! {
|
||||
feature = "std_web" => self.ws.send_text(&body),
|
||||
feature = "web_sys" => self.ws.send_with_str(&body),
|
||||
};
|
||||
let result = self.ws.send_with_str(&body);
|
||||
|
||||
if result.is_err() {
|
||||
self.notification.emit(WebSocketStatus::Error);
|
||||
|
@ -333,10 +234,7 @@ impl WebSocketTask {
|
|||
IN: Into<Binary>,
|
||||
{
|
||||
if let Ok(body) = data.into() {
|
||||
let result = cfg_match! {
|
||||
feature = "std_web" => self.ws.send_bytes(&body),
|
||||
feature = "web_sys" => self.ws.send_with_u8_array(&body),
|
||||
};
|
||||
let result = self.ws.send_with_u8_array(&body);
|
||||
|
||||
if result.is_err() {
|
||||
self.notification.emit(WebSocketStatus::Error);
|
||||
|
@ -347,20 +245,14 @@ impl WebSocketTask {
|
|||
|
||||
impl Task for WebSocketTask {
|
||||
fn is_active(&self) -> bool {
|
||||
cfg_match! {
|
||||
feature = "std_web" => self.ws.ready_state() == SocketReadyState::Open,
|
||||
feature = "web_sys" => self.ws.ready_state() == WebSocket::OPEN,
|
||||
}
|
||||
self.ws.ready_state() == WebSocket::OPEN
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for WebSocketTask {
|
||||
fn drop(&mut self) {
|
||||
if self.is_active() {
|
||||
cfg_match! {
|
||||
feature = "std_web" => self.ws.close(),
|
||||
feature = "web_sys" => self.ws.close().ok(),
|
||||
};
|
||||
self.ws.close().ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -428,7 +320,6 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "web_sys")]
|
||||
async fn test_invalid_url_error() {
|
||||
let url = "syntactically-invalid";
|
||||
let cb_future = CallbackFuture::<Json<Result<Message, anyhow::Error>>>::default();
|
||||
|
|
|
@ -1,83 +0,0 @@
|
|||
[package]
|
||||
name = "yew-stdweb"
|
||||
version = "0.17.4"
|
||||
edition = "2018"
|
||||
authors = [
|
||||
"Denis Kolodin <deniskolodin@gmail.com>",
|
||||
"Justin Starry <justin@yew.rs>",
|
||||
]
|
||||
repository = "https://github.com/yewstack/yew"
|
||||
homepage = "https://github.com/yewstack/yew"
|
||||
documentation = "https://docs.rs/yew-stdweb/"
|
||||
license = "MIT/Apache-2.0"
|
||||
readme = "../README.md"
|
||||
keywords = ["web", "asmjs", "webasm", "javascript"]
|
||||
categories = ["gui", "web-programming"]
|
||||
description = "A framework for making client-side single-page apps"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
anymap = "0.12"
|
||||
bincode = { version = "1", optional = true }
|
||||
cfg-if = "0.1"
|
||||
cfg-match = "0.2"
|
||||
console_error_panic_hook = { version = "0.1", optional = true }
|
||||
fixedbitset = "0.3.0"
|
||||
gloo = { version = "0.2.1", optional = true }
|
||||
http = "0.2"
|
||||
indexmap = "1.0.2"
|
||||
js-sys = { version = "0.3", optional = true }
|
||||
log = "0.4"
|
||||
rmp-serde = { version = "0.14.0", optional = true }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_cbor = { version = "0.11.1", optional = true }
|
||||
serde_json = "1.0"
|
||||
serde_yaml = { version = "0.8.3", optional = true }
|
||||
slab = "0.4"
|
||||
stdweb = { version = "0.4.20", optional = true }
|
||||
thiserror = "1"
|
||||
toml = { version = "0.5", optional = true }
|
||||
wasm-bindgen = { version = "0.2.60", optional = true }
|
||||
wasm-bindgen-futures = { version = "0.4", optional = true }
|
||||
yew-macro = { version = "0.17.0", path = "../yew-macro", features = [
|
||||
"std_web"
|
||||
] }
|
||||
|
||||
# Changes here must be reflected in `build.rs`
|
||||
[target.'cfg(all(target_arch = "wasm32", not(target_os="wasi"), not(cargo_web)))'.dependencies]
|
||||
wasm-bindgen = "0.2.60"
|
||||
|
||||
# Changes here must be reflected in `build.rs`
|
||||
[target.'cfg(all(target_arch = "wasm32", not(target_os="wasi"), not(cargo_web)))'.dev-dependencies]
|
||||
wasm-bindgen-test = "0.3.4"
|
||||
base64 = "0.12.0"
|
||||
ssri = "5.0.0"
|
||||
|
||||
[target.'cfg(target_os = "emscripten")'.dependencies]
|
||||
ryu = "1.0.2" # 1.0.1 breaks emscripten
|
||||
|
||||
[dev-dependencies]
|
||||
serde_derive = "1"
|
||||
trybuild = "1.0"
|
||||
rustversion = "1.0"
|
||||
rmp-serde = "0.14.0"
|
||||
bincode = "1"
|
||||
|
||||
[features]
|
||||
default = ["agent", "std_web"]
|
||||
std_web = ["stdweb"]
|
||||
doc_test = []
|
||||
wasm_test = []
|
||||
agent = ["bincode"]
|
||||
yaml = ["serde_yaml"]
|
||||
msgpack = ["rmp-serde"]
|
||||
cbor = ["serde_cbor"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["yaml", "cbor", "toml", "msgpack", "doc_test"]
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"examples/counter",
|
||||
"examples/todomvc",
|
||||
]
|
|
@ -1,19 +0,0 @@
|
|||
# Yew Examples
|
||||
|
||||
### Running the examples
|
||||
|
||||
To start an example, enter its directory and start it with [cargo-web]:
|
||||
|
||||
```bash
|
||||
cargo web start
|
||||
```
|
||||
|
||||
To run an optimized build instead of a debug build use:
|
||||
|
||||
```bash
|
||||
cargo web start --release
|
||||
```
|
||||
|
||||
The `wasm32-unknown-unknown` target will be used by default, which is Rust's native WebAssembly target. The Emscripten-based `wasm32-unknown-emscripten` and `asmjs-unknown-emscripten` targets are also supported if you tell the `cargo-web` to build for them using the `--target` parameter.
|
||||
|
||||
[cargo-web]: https://github.com/koute/cargo-web
|
|
@ -1,13 +0,0 @@
|
|||
[package]
|
||||
name = "counter"
|
||||
version = "0.1.1"
|
||||
authors = ["Denis Kolodin <deniskolodin@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
stdweb = "0.4.20"
|
||||
yew = { path = "../..", package = "yew-stdweb" }
|
||||
yew-services = { path = "../../../yew-services" }
|
||||
|
||||
[target.'cfg(all(target_arch = "wasm32", not(target_os="wasi"), not(cargo_web)))'.dependencies]
|
||||
wasm-bindgen = "0.2.60"
|
|
@ -1,62 +0,0 @@
|
|||
#![recursion_limit = "256"]
|
||||
|
||||
use stdweb::web::Date;
|
||||
use yew::{html, Component, ComponentLink, Html, ShouldRender};
|
||||
use yew_services::ConsoleService;
|
||||
|
||||
pub struct Model {
|
||||
link: ComponentLink<Self>,
|
||||
value: i64,
|
||||
}
|
||||
|
||||
pub enum Msg {
|
||||
Increment,
|
||||
Decrement,
|
||||
}
|
||||
|
||||
impl Component for Model {
|
||||
type Message = Msg;
|
||||
type Properties = ();
|
||||
|
||||
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
|
||||
Model { link, value: 0 }
|
||||
}
|
||||
|
||||
fn update(&mut self, msg: Self::Message) -> ShouldRender {
|
||||
match msg {
|
||||
Msg::Increment => {
|
||||
self.value += 1;
|
||||
ConsoleService::log("plus one");
|
||||
}
|
||||
Msg::Decrement => {
|
||||
self.value -= 1;
|
||||
ConsoleService::log("minus one");
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn change(&mut self, _: Self::Properties) -> ShouldRender {
|
||||
false
|
||||
}
|
||||
|
||||
fn view(&self) -> Html {
|
||||
html! {
|
||||
<div>
|
||||
<nav class="menu">
|
||||
<button onclick=self.link.callback(|_| Msg::Increment)>
|
||||
{ "Increment" }
|
||||
</button>
|
||||
<button onclick=self.link.callback(|_| Msg::Decrement)>
|
||||
{ "Decrement" }
|
||||
</button>
|
||||
<button onclick=self.link.batch_callback(|_| vec![Msg::Increment, Msg::Increment])>
|
||||
{ "Increment Twice" }
|
||||
</button>
|
||||
</nav>
|
||||
<p>{ self.value }</p>
|
||||
<p>{ Date::new().to_string() }</p>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
fn main() {
|
||||
yew::start_app::<counter::Model>();
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
[package]
|
||||
name = "todomvc"
|
||||
version = "0.1.0"
|
||||
authors = ["Denis Kolodin <deniskolodin@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
strum = "0.13"
|
||||
strum_macros = "0.13"
|
||||
serde = "1"
|
||||
serde_derive = "1"
|
||||
yew = { path = "../..", package = "yew-stdweb" }
|
||||
yew-services = { path = "../../../yew-services" }
|
||||
|
||||
[target.'cfg(all(target_arch = "wasm32", not(target_os="wasi"), not(cargo_web)))'.dependencies]
|
||||
wasm-bindgen = "0.2.60"
|
|
@ -1,6 +0,0 @@
|
|||
## Yew TodoMVC Demo
|
||||
|
||||
This it an implementationt of [TodoMVC](http://todomvc.com/) app.
|
||||
|
||||
Unlike other implementations, this stores the full state of the model,
|
||||
including: all entries, entered text and chosen filter.
|
|
@ -1,368 +0,0 @@
|
|||
#![recursion_limit = "512"]
|
||||
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::borrow::Cow;
|
||||
use strum::IntoEnumIterator;
|
||||
use strum_macros::{EnumIter, ToString};
|
||||
use yew::events::IKeyboardEvent;
|
||||
use yew::format::Json;
|
||||
use yew::{html, Component, ComponentLink, Html, InputData, KeyPressEvent, ShouldRender};
|
||||
use yew_services::storage::{Area, StorageService};
|
||||
|
||||
const KEY: &str = "yew.todomvc.self";
|
||||
|
||||
pub struct Model {
|
||||
link: ComponentLink<Self>,
|
||||
storage: StorageService,
|
||||
state: State,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct State {
|
||||
entries: Vec<Entry>,
|
||||
filter: Filter,
|
||||
value: String,
|
||||
edit_value: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct Entry {
|
||||
description: String,
|
||||
completed: bool,
|
||||
editing: bool,
|
||||
}
|
||||
|
||||
pub enum Msg {
|
||||
Add,
|
||||
Edit(usize),
|
||||
Update(String),
|
||||
UpdateEdit(String),
|
||||
Remove(usize),
|
||||
SetFilter(Filter),
|
||||
ToggleAll,
|
||||
ToggleEdit(usize),
|
||||
Toggle(usize),
|
||||
ClearCompleted,
|
||||
Nope,
|
||||
}
|
||||
|
||||
impl Component for Model {
|
||||
type Message = Msg;
|
||||
type Properties = ();
|
||||
|
||||
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
|
||||
let storage = StorageService::new(Area::Local).expect("storage was disabled by the user");
|
||||
let entries = {
|
||||
if let Json(Ok(restored_model)) = storage.restore(KEY) {
|
||||
restored_model
|
||||
} else {
|
||||
Vec::new()
|
||||
}
|
||||
};
|
||||
let state = State {
|
||||
entries,
|
||||
filter: Filter::All,
|
||||
value: "".into(),
|
||||
edit_value: "".into(),
|
||||
};
|
||||
Model {
|
||||
link,
|
||||
storage,
|
||||
state,
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, msg: Self::Message) -> ShouldRender {
|
||||
match msg {
|
||||
Msg::Add => {
|
||||
let entry = Entry {
|
||||
description: self.state.value.clone(),
|
||||
completed: false,
|
||||
editing: false,
|
||||
};
|
||||
self.state.entries.push(entry);
|
||||
self.state.value = "".to_string();
|
||||
}
|
||||
Msg::Edit(idx) => {
|
||||
let edit_value = self.state.edit_value.clone();
|
||||
self.state.complete_edit(idx, edit_value);
|
||||
self.state.edit_value = "".to_string();
|
||||
}
|
||||
Msg::Update(val) => {
|
||||
println!("Input: {}", val);
|
||||
self.state.value = val;
|
||||
}
|
||||
Msg::UpdateEdit(val) => {
|
||||
println!("Input: {}", val);
|
||||
self.state.edit_value = val;
|
||||
}
|
||||
Msg::Remove(idx) => {
|
||||
self.state.remove(idx);
|
||||
}
|
||||
Msg::SetFilter(filter) => {
|
||||
self.state.filter = filter;
|
||||
}
|
||||
Msg::ToggleEdit(idx) => {
|
||||
self.state.edit_value = self.state.entries[idx].description.clone();
|
||||
self.state.toggle_edit(idx);
|
||||
}
|
||||
Msg::ToggleAll => {
|
||||
let status = !self.state.is_all_completed();
|
||||
self.state.toggle_all(status);
|
||||
}
|
||||
Msg::Toggle(idx) => {
|
||||
self.state.toggle(idx);
|
||||
}
|
||||
Msg::ClearCompleted => {
|
||||
self.state.clear_completed();
|
||||
}
|
||||
Msg::Nope => {}
|
||||
}
|
||||
self.storage.store(KEY, Json(&self.state.entries));
|
||||
true
|
||||
}
|
||||
|
||||
fn change(&mut self, _: Self::Properties) -> ShouldRender {
|
||||
false
|
||||
}
|
||||
|
||||
fn view(&self) -> Html {
|
||||
html! {
|
||||
<div class="todomvc-wrapper">
|
||||
<section class="todoapp">
|
||||
<header class="header">
|
||||
<h1>{ "todos" }</h1>
|
||||
{ self.view_input() }
|
||||
</header>
|
||||
<section class="main">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="toggle-all"
|
||||
checked=self.state.is_all_completed()
|
||||
onclick=self.link.callback(|_| Msg::ToggleAll) />
|
||||
<ul class="todo-list">
|
||||
{ for self.state.entries.iter().filter(|e| self.state.filter.fit(e)).enumerate().map(|e| self.view_entry(e)) }
|
||||
</ul>
|
||||
</section>
|
||||
<footer class="footer">
|
||||
<span class="todo-count">
|
||||
<strong>{ self.state.total() }</strong>
|
||||
{ " item(s) left" }
|
||||
</span>
|
||||
<ul class="filters">
|
||||
{ for Filter::iter().map(|flt| self.view_filter(flt)) }
|
||||
</ul>
|
||||
<button class="clear-completed" onclick=self.link.callback(|_| Msg::ClearCompleted)>
|
||||
{ format!("Clear completed ({})", self.state.total_completed()) }
|
||||
</button>
|
||||
</footer>
|
||||
</section>
|
||||
<footer class="info">
|
||||
<p>{ "Double-click to edit a todo" }</p>
|
||||
<p>{ "Written by " }<a href="https://github.com/DenisKolodin/" target="_blank">{ "Denis Kolodin" }</a></p>
|
||||
<p>{ "Part of " }<a href="http://todomvc.com/" target="_blank">{ "TodoMVC" }</a></p>
|
||||
</footer>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Model {
|
||||
fn view_filter(&self, filter: Filter) -> Html {
|
||||
let cls = if self.state.filter == filter {
|
||||
"selected"
|
||||
} else {
|
||||
"not-selected"
|
||||
};
|
||||
let flt = filter.clone();
|
||||
html! {
|
||||
<li>
|
||||
<a class=cls
|
||||
href=flt
|
||||
onclick=self.link.callback(move |_| Msg::SetFilter(flt.clone()))>
|
||||
{ filter }
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
}
|
||||
|
||||
fn view_input(&self) -> Html {
|
||||
html! {
|
||||
// You can use standard Rust comments. One line:
|
||||
// <li></li>
|
||||
<input class="new-todo"
|
||||
placeholder="What needs to be done?"
|
||||
value=&self.state.value
|
||||
oninput=self.link.callback(|e: InputData| Msg::Update(e.value))
|
||||
onkeypress=self.link.callback(|e: KeyPressEvent| {
|
||||
if e.key() == "Enter" { Msg::Add } else { Msg::Nope }
|
||||
}) />
|
||||
/* Or multiline:
|
||||
<ul>
|
||||
<li></li>
|
||||
</ul>
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
fn view_entry(&self, (idx, entry): (usize, &Entry)) -> Html {
|
||||
let mut class = "todo".to_string();
|
||||
if entry.editing {
|
||||
class.push_str(" editing");
|
||||
}
|
||||
if entry.completed {
|
||||
class.push_str(" completed");
|
||||
}
|
||||
html! {
|
||||
<li class=class>
|
||||
<div class="view">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="toggle"
|
||||
checked=entry.completed
|
||||
onclick=self.link.callback(move |_| Msg::Toggle(idx)) />
|
||||
<label ondblclick=self.link.callback(move |_| Msg::ToggleEdit(idx))>{ &entry.description }</label>
|
||||
<button class="destroy" onclick=self.link.callback(move |_| Msg::Remove(idx)) />
|
||||
</div>
|
||||
{ self.view_entry_edit_input((idx, &entry)) }
|
||||
</li>
|
||||
}
|
||||
}
|
||||
|
||||
fn view_entry_edit_input(&self, (idx, entry): (usize, &Entry)) -> Html {
|
||||
if entry.editing {
|
||||
html! {
|
||||
<input class="edit"
|
||||
type="text"
|
||||
value=&entry.description
|
||||
oninput=self.link.callback(|e: InputData| Msg::UpdateEdit(e.value))
|
||||
onblur=self.link.callback(move |_| Msg::Edit(idx))
|
||||
onkeypress=self.link.callback(move |e: KeyPressEvent| {
|
||||
if e.key() == "Enter" { Msg::Edit(idx) } else { Msg::Nope }
|
||||
}) />
|
||||
}
|
||||
} else {
|
||||
html! { <input type="hidden" /> }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(EnumIter, ToString, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub enum Filter {
|
||||
All,
|
||||
Active,
|
||||
Completed,
|
||||
}
|
||||
|
||||
impl<'a> Into<Cow<'static, str>> for &'a Filter {
|
||||
fn into(self) -> Cow<'static, str> {
|
||||
match *self {
|
||||
Filter::All => "#/".into(),
|
||||
Filter::Active => "#/active".into(),
|
||||
Filter::Completed => "#/completed".into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Filter {
|
||||
fn fit(&self, entry: &Entry) -> bool {
|
||||
match *self {
|
||||
Filter::All => true,
|
||||
Filter::Active => !entry.completed,
|
||||
Filter::Completed => entry.completed,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn total(&self) -> usize {
|
||||
self.entries.len()
|
||||
}
|
||||
|
||||
fn total_completed(&self) -> usize {
|
||||
self.entries
|
||||
.iter()
|
||||
.filter(|e| Filter::Completed.fit(e))
|
||||
.count()
|
||||
}
|
||||
|
||||
fn is_all_completed(&self) -> bool {
|
||||
let mut filtered_iter = self
|
||||
.entries
|
||||
.iter()
|
||||
.filter(|e| self.filter.fit(e))
|
||||
.peekable();
|
||||
|
||||
if filtered_iter.peek().is_none() {
|
||||
return false;
|
||||
}
|
||||
|
||||
filtered_iter.all(|e| e.completed)
|
||||
}
|
||||
|
||||
fn toggle_all(&mut self, value: bool) {
|
||||
for entry in self.entries.iter_mut() {
|
||||
if self.filter.fit(entry) {
|
||||
entry.completed = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn clear_completed(&mut self) {
|
||||
let entries = self
|
||||
.entries
|
||||
.drain(..)
|
||||
.filter(|e| Filter::Active.fit(e))
|
||||
.collect();
|
||||
self.entries = entries;
|
||||
}
|
||||
|
||||
fn toggle(&mut self, idx: usize) {
|
||||
let filter = self.filter.clone();
|
||||
let mut entries = self
|
||||
.entries
|
||||
.iter_mut()
|
||||
.filter(|e| filter.fit(e))
|
||||
.collect::<Vec<_>>();
|
||||
let entry = entries.get_mut(idx).unwrap();
|
||||
entry.completed = !entry.completed;
|
||||
}
|
||||
|
||||
fn toggle_edit(&mut self, idx: usize) {
|
||||
let filter = self.filter.clone();
|
||||
let mut entries = self
|
||||
.entries
|
||||
.iter_mut()
|
||||
.filter(|e| filter.fit(e))
|
||||
.collect::<Vec<_>>();
|
||||
let entry = entries.get_mut(idx).unwrap();
|
||||
entry.editing = !entry.editing;
|
||||
}
|
||||
|
||||
fn complete_edit(&mut self, idx: usize, val: String) {
|
||||
let filter = self.filter.clone();
|
||||
let mut entries = self
|
||||
.entries
|
||||
.iter_mut()
|
||||
.filter(|e| filter.fit(e))
|
||||
.collect::<Vec<_>>();
|
||||
let entry = entries.get_mut(idx).unwrap();
|
||||
entry.description = val;
|
||||
entry.editing = !entry.editing;
|
||||
}
|
||||
|
||||
fn remove(&mut self, idx: usize) {
|
||||
let idx = {
|
||||
let filter = self.filter.clone();
|
||||
let entries = self
|
||||
.entries
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|&(_, e)| filter.fit(e))
|
||||
.collect::<Vec<_>>();
|
||||
let &(idx, _) = entries.get(idx).unwrap();
|
||||
idx
|
||||
};
|
||||
self.entries.remove(idx);
|
||||
}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
fn main() {
|
||||
yew::start_app::<todomvc::Model>();
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Yew • TodoMVC</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/todomvc-common@1.0.5/base.css"/ >
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/todomvc-app-css@2.1.2/index.css" />
|
||||
</head>
|
||||
<body>
|
||||
<script src="/todomvc.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -1 +0,0 @@
|
|||
../yew/src
|
|
@ -11,38 +11,36 @@ homepage = "https://github.com/yewstack/yew"
|
|||
documentation = "https://docs.rs/yew/"
|
||||
license = "MIT/Apache-2.0"
|
||||
readme = "../README.md"
|
||||
keywords = ["web", "asmjs", "webasm", "javascript"]
|
||||
keywords = ["web", "webasm", "javascript"]
|
||||
categories = ["gui", "wasm", "web-programming"]
|
||||
description = "A framework for making client-side single-page apps"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
anymap = "0.12"
|
||||
bincode = { version = "1", optional = true }
|
||||
cfg-if = "1.0"
|
||||
cfg-match = "0.2"
|
||||
console_error_panic_hook = { version = "0.1", optional = true }
|
||||
gloo = { version = "0.2.1", optional = true }
|
||||
console_error_panic_hook = "0.1"
|
||||
gloo = "0.2.1"
|
||||
http = "0.2"
|
||||
indexmap = { version = "1.5", features = ["std"] }
|
||||
js-sys = { version = "0.3", optional = true }
|
||||
js-sys = "0.3"
|
||||
log = "0.4"
|
||||
rmp-serde = { version = "0.15.0", optional = true }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_cbor = { version = "0.11.1", optional = true }
|
||||
serde_json = "1.0"
|
||||
serde_yaml = { version = "0.8.3", optional = true }
|
||||
slab = "0.4"
|
||||
stdweb = { version = "0.4.20", optional = true }
|
||||
thiserror = "1"
|
||||
toml = { version = "0.5", optional = true }
|
||||
wasm-bindgen = { version = "0.2.60", optional = true }
|
||||
wasm-bindgen-futures = { version = "0.4", optional = true }
|
||||
wasm-bindgen = "0.2.60"
|
||||
wasm-bindgen-futures = "0.4"
|
||||
yew-macro = { version = "0.17.0", path = "../yew-macro" }
|
||||
|
||||
# optional encodings
|
||||
bincode = { version = "1", optional = true }
|
||||
rmp-serde = { version = "0.15.0", optional = true }
|
||||
serde_cbor = { version = "0.11.1", optional = true }
|
||||
serde_yaml = { version = "0.8.3", optional = true }
|
||||
toml = { version = "0.5", optional = true }
|
||||
|
||||
[dependencies.web-sys]
|
||||
version = "0.3"
|
||||
optional = true
|
||||
features = [
|
||||
"AbortController",
|
||||
"AbortSignal",
|
||||
|
@ -100,38 +98,19 @@ features = [
|
|||
"WorkerOptions",
|
||||
]
|
||||
|
||||
# Changes here must be reflected in `build.rs`
|
||||
[target.'cfg(all(target_arch = "wasm32", not(target_os="wasi"), not(cargo_web)))'.dependencies]
|
||||
wasm-bindgen = "0.2.60"
|
||||
|
||||
# Changes here must be reflected in `build.rs`
|
||||
[target.'cfg(all(target_arch = "wasm32", not(target_os="wasi"), not(cargo_web)))'.dev-dependencies]
|
||||
wasm-bindgen-test = "0.3.4"
|
||||
base64 = "0.13.0"
|
||||
ssri = "6.0.0"
|
||||
easybench-wasm = "0.2.1"
|
||||
|
||||
[target.'cfg(target_os = "emscripten")'.dependencies]
|
||||
ryu = "1.0.2" # 1.0.1 breaks emscripten
|
||||
|
||||
[dev-dependencies]
|
||||
serde_derive = "1"
|
||||
trybuild = "1.0"
|
||||
rustversion = "1.0"
|
||||
rmp-serde = "0.15.0"
|
||||
base64 = "0.13.0"
|
||||
bincode = "1"
|
||||
easybench-wasm = "0.2.1"
|
||||
rmp-serde = "0.15.0"
|
||||
rustversion = "1.0"
|
||||
serde_derive = "1"
|
||||
ssri = "6.0.0"
|
||||
trybuild = "1.0"
|
||||
wasm-bindgen-test = "0.3.4"
|
||||
|
||||
[features]
|
||||
default = ["agent", "web_sys"]
|
||||
std_web = ["stdweb", "yew-macro/std_web"]
|
||||
web_sys = [
|
||||
"console_error_panic_hook",
|
||||
"gloo",
|
||||
"js-sys",
|
||||
"web-sys",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures"
|
||||
]
|
||||
default = ["agent"]
|
||||
doc_test = []
|
||||
wasm_test = []
|
||||
wasm_bench = []
|
||||
|
|
|
@ -14,7 +14,7 @@ args = [
|
|||
|
||||
[tasks.doc-test]
|
||||
clear = true
|
||||
run_task = { name = ["doc-test-normal", "doc-test-stdweb"], fork = true }
|
||||
run_task = { name = ["doc-test-normal"], fork = true }
|
||||
|
||||
[tasks.doc-test-normal]
|
||||
command = "cargo"
|
||||
|
@ -25,16 +25,6 @@ args = [
|
|||
"doc_test,wasm_test,yaml,msgpack,cbor,toml",
|
||||
]
|
||||
|
||||
[tasks.doc-test-stdweb]
|
||||
command = "cargo"
|
||||
args = [
|
||||
"test",
|
||||
"--doc",
|
||||
"--features",
|
||||
"doc_test,wasm_test,yaml,msgpack,cbor,toml,std_web,agent",
|
||||
"--no-default-features",
|
||||
]
|
||||
|
||||
[tasks.bench]
|
||||
extend = "core::wasm-pack-base"
|
||||
command = "wasm-pack"
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
use std::env;
|
||||
|
||||
pub fn main() {
|
||||
let using_web_sys = cfg!(feature = "web_sys");
|
||||
let using_std_web = cfg!(feature = "std_web");
|
||||
if using_web_sys && using_std_web {
|
||||
panic!("Yew does not allow the `web_sys` and `std_web` cargo features to be used simultaneously");
|
||||
} else if !using_web_sys && !using_std_web {
|
||||
panic!("Yew requires selecting either the `web_sys` or `std_web` cargo feature");
|
||||
}
|
||||
|
||||
let using_cargo_web = env::var("COMPILING_UNDER_CARGO_WEB").is_ok();
|
||||
if using_cargo_web && using_web_sys {
|
||||
panic!("cargo-web is not compatible with web-sys");
|
||||
}
|
||||
}
|
|
@ -6,21 +6,13 @@ pub use private::Private;
|
|||
pub use public::Public;
|
||||
|
||||
use super::*;
|
||||
use cfg_if::cfg_if;
|
||||
use cfg_match::cfg_match;
|
||||
use crate::utils;
|
||||
use js_sys::{Array, Reflect, Uint8Array};
|
||||
use serde::{Deserialize, Serialize};
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "std_web")] {
|
||||
use stdweb::Value;
|
||||
#[allow(unused_imports)]
|
||||
use stdweb::{_js_impl, js};
|
||||
} else if #[cfg(feature = "web_sys")] {
|
||||
use crate::utils;
|
||||
use js_sys::{Array, Reflect, Uint8Array};
|
||||
use wasm_bindgen::{closure::Closure, JsCast, JsValue};
|
||||
use web_sys::{Blob, BlobPropertyBag, DedicatedWorkerGlobalScope, MessageEvent, Url, Worker, WorkerOptions};
|
||||
}
|
||||
}
|
||||
use wasm_bindgen::{closure::Closure, JsCast, JsValue};
|
||||
use web_sys::{
|
||||
Blob, BlobPropertyBag, DedicatedWorkerGlobalScope, MessageEvent, Url, Worker, WorkerOptions,
|
||||
};
|
||||
|
||||
/// Implements rules to register a worker in a separate thread.
|
||||
pub trait Threaded {
|
||||
|
@ -69,27 +61,16 @@ enum FromWorker<T> {
|
|||
ProcessOutput(HandlerId, T),
|
||||
}
|
||||
|
||||
fn send_to_remote<AGN>(
|
||||
#[cfg(feature = "std_web")] worker: &Value,
|
||||
#[cfg(feature = "web_sys")] worker: &Worker,
|
||||
msg: ToWorker<AGN::Input>,
|
||||
) where
|
||||
fn send_to_remote<AGN>(worker: &Worker, msg: ToWorker<AGN::Input>)
|
||||
where
|
||||
AGN: Agent,
|
||||
<AGN as Agent>::Input: Serialize + for<'de> Deserialize<'de>,
|
||||
<AGN as Agent>::Output: Serialize + for<'de> Deserialize<'de>,
|
||||
{
|
||||
let msg = msg.pack();
|
||||
cfg_match! {
|
||||
feature = "std_web" => js! {
|
||||
var worker = @{worker};
|
||||
var bytes = @{msg};
|
||||
worker.postMessage(bytes);
|
||||
},
|
||||
feature = "web_sys" => worker.post_message_vec(msg),
|
||||
};
|
||||
worker.post_message_vec(msg);
|
||||
}
|
||||
|
||||
#[cfg(feature = "web_sys")]
|
||||
fn worker_new(name_of_resource: &str, is_module: bool) -> Worker {
|
||||
let origin = utils::origin().unwrap();
|
||||
let script_url = format!("{}/{}", origin, name_of_resource);
|
||||
|
@ -123,19 +104,16 @@ fn worker_new(name_of_resource: &str, is_module: bool) -> Worker {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "web_sys")]
|
||||
fn worker_self() -> DedicatedWorkerGlobalScope {
|
||||
JsValue::from(js_sys::global()).into()
|
||||
}
|
||||
|
||||
#[cfg(feature = "web_sys")]
|
||||
trait WorkerExt {
|
||||
fn set_onmessage_closure(&self, handler: impl 'static + Fn(Vec<u8>));
|
||||
|
||||
fn post_message_vec(&self, data: Vec<u8>);
|
||||
}
|
||||
|
||||
#[cfg(feature = "web_sys")]
|
||||
macro_rules! worker_ext_impl {
|
||||
($($type:ident),+) => {$(
|
||||
impl WorkerExt for $type {
|
||||
|
@ -157,7 +135,6 @@ macro_rules! worker_ext_impl {
|
|||
)+};
|
||||
}
|
||||
|
||||
#[cfg(feature = "web_sys")]
|
||||
worker_ext_impl! {
|
||||
Worker, DedicatedWorkerGlobalScope
|
||||
}
|
||||
|
|
|
@ -1,20 +1,10 @@
|
|||
use super::*;
|
||||
use crate::callback::Callback;
|
||||
use cfg_if::cfg_if;
|
||||
use cfg_match::cfg_match;
|
||||
use queue::Queue;
|
||||
use std::fmt;
|
||||
use std::marker::PhantomData;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "std_web")] {
|
||||
use stdweb::Value;
|
||||
#[allow(unused_imports)]
|
||||
use stdweb::{_js_impl, js};
|
||||
} else if #[cfg(feature = "web_sys")] {
|
||||
use web_sys::{Worker};
|
||||
}
|
||||
}
|
||||
use web_sys::Worker;
|
||||
|
||||
thread_local! {
|
||||
static QUEUE: Queue<usize> = Queue::new();
|
||||
|
@ -40,9 +30,7 @@ where
|
|||
fn spawn_or_join(callback: Option<Callback<AGN::Output>>) -> Box<dyn Bridge<AGN>> {
|
||||
let id = PRIVATE_ID_COUNTER.fetch_add(1, Ordering::Relaxed);
|
||||
let callback = callback.expect("Callback required for Private agents");
|
||||
let handler = move |data: Vec<u8>,
|
||||
#[cfg(feature = "std_web")] worker: Value,
|
||||
#[cfg(feature = "web_sys")] worker: &Worker| {
|
||||
let handler = move |data: Vec<u8>, worker: &Worker| {
|
||||
let msg = FromWorker::<AGN::Output>::unpack(&data);
|
||||
match msg {
|
||||
FromWorker::WorkerLoaded => {
|
||||
|
@ -51,13 +39,7 @@ where
|
|||
|
||||
if let Some(msgs) = queue.remove_msg_queue(&id) {
|
||||
for msg in msgs {
|
||||
cfg_match! {
|
||||
feature = "std_web" => ({
|
||||
let worker = &worker;
|
||||
js! {@{worker}.postMessage(@{msg});};
|
||||
}),
|
||||
feature = "web_sys" => worker.post_message_vec(msg),
|
||||
}
|
||||
worker.post_message_vec(msg)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -71,21 +53,11 @@ where
|
|||
|
||||
// TODO(#947): Drop handler when bridge is dropped
|
||||
let name_of_resource = AGN::name_of_resource();
|
||||
let worker = cfg_match! {
|
||||
feature = "std_web" => js! {
|
||||
var worker = new Worker(@{name_of_resource});
|
||||
var handler = @{handler};
|
||||
worker.onmessage = function(event) {
|
||||
handler(event.data, worker);
|
||||
};
|
||||
return worker;
|
||||
},
|
||||
feature = "web_sys" => ({
|
||||
let worker = worker_new(name_of_resource, AGN::is_module());
|
||||
let worker_clone = worker.clone();
|
||||
worker.set_onmessage_closure(move |data: Vec<u8>| handler(data, &worker_clone));
|
||||
worker
|
||||
}),
|
||||
let worker = {
|
||||
let worker = worker_new(name_of_resource, AGN::is_module());
|
||||
let worker_clone = worker.clone();
|
||||
worker.set_onmessage_closure(move |data: Vec<u8>| handler(data, &worker_clone));
|
||||
worker
|
||||
};
|
||||
let bridge = PrivateBridge {
|
||||
worker,
|
||||
|
@ -104,9 +76,6 @@ where
|
|||
<AGN as Agent>::Input: Serialize + for<'de> Deserialize<'de>,
|
||||
<AGN as Agent>::Output: Serialize + for<'de> Deserialize<'de>,
|
||||
{
|
||||
#[cfg(feature = "std_web")]
|
||||
worker: Value,
|
||||
#[cfg(feature = "web_sys")]
|
||||
worker: Worker,
|
||||
_agent: PhantomData<AGN>,
|
||||
id: usize,
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
use super::WorkerExt;
|
||||
use super::*;
|
||||
use crate::callback::Callback;
|
||||
use crate::scheduler::Shared;
|
||||
use anymap::{self, AnyMap};
|
||||
use cfg_if::cfg_if;
|
||||
use cfg_match::cfg_match;
|
||||
use queue::Queue;
|
||||
use slab::Slab;
|
||||
use std::any::TypeId;
|
||||
|
@ -11,16 +10,7 @@ use std::cell::RefCell;
|
|||
use std::fmt;
|
||||
use std::marker::PhantomData;
|
||||
use std::rc::Rc;
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "std_web")] {
|
||||
use stdweb::Value;
|
||||
#[allow(unused_imports)]
|
||||
use stdweb::{_js_impl, js};
|
||||
} else if #[cfg(feature = "web_sys")] {
|
||||
use super::WorkerExt;
|
||||
use web_sys::{Worker};
|
||||
}
|
||||
}
|
||||
use web_sys::Worker;
|
||||
|
||||
thread_local! {
|
||||
static REMOTE_AGENTS_POOL: RefCell<AnyMap> = RefCell::new(AnyMap::new());
|
||||
|
@ -51,24 +41,18 @@ where
|
|||
Rc::new(RefCell::new(Slab::new()));
|
||||
let handler = {
|
||||
let slab = slab.clone();
|
||||
move |data: Vec<u8>,
|
||||
#[cfg(feature = "std_web")] worker: Value,
|
||||
#[cfg(feature = "web_sys")] worker: &Worker| {
|
||||
move |data: Vec<u8>, worker: &Worker| {
|
||||
let msg = FromWorker::<AGN::Output>::unpack(&data);
|
||||
match msg {
|
||||
FromWorker::WorkerLoaded => {
|
||||
QUEUE.with(|queue| {
|
||||
queue.insert_loaded_agent(TypeId::of::<AGN>());
|
||||
|
||||
if let Some(msgs) = queue.remove_msg_queue(&TypeId::of::<AGN>()) {
|
||||
if let Some(msgs) =
|
||||
queue.remove_msg_queue(&TypeId::of::<AGN>())
|
||||
{
|
||||
for msg in msgs {
|
||||
cfg_match! {
|
||||
feature = "std_web" => ({
|
||||
let worker = &worker;
|
||||
js! {@{worker}.postMessage(@{msg});};
|
||||
}),
|
||||
feature = "web_sys" => worker.post_message_vec(msg),
|
||||
}
|
||||
worker.post_message_vec(msg)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -80,23 +64,13 @@ where
|
|||
}
|
||||
};
|
||||
let name_of_resource = AGN::name_of_resource();
|
||||
let worker = cfg_match! {
|
||||
feature = "std_web" => js! {
|
||||
var worker = new Worker(@{name_of_resource});
|
||||
var handler = @{handler};
|
||||
worker.onmessage = function(event) {
|
||||
handler(event.data, worker);
|
||||
};
|
||||
return worker;
|
||||
},
|
||||
feature = "web_sys" => ({
|
||||
let worker = worker_new(name_of_resource, AGN::is_module());
|
||||
let worker_clone = worker.clone();
|
||||
worker.set_onmessage_closure(move |data: Vec<u8>| {
|
||||
handler(data, &worker_clone);
|
||||
});
|
||||
worker
|
||||
}),
|
||||
let worker = {
|
||||
let worker = worker_new(name_of_resource, AGN::is_module());
|
||||
let worker_clone = worker.clone();
|
||||
worker.set_onmessage_closure(move |data: Vec<u8>| {
|
||||
handler(data, &worker_clone);
|
||||
});
|
||||
worker
|
||||
};
|
||||
let launched = RemoteAgent::new(worker, slab);
|
||||
entry.insert(launched).create_bridge(callback)
|
||||
|
@ -122,9 +96,6 @@ where
|
|||
<AGN as Agent>::Input: Serialize + for<'de> Deserialize<'de>,
|
||||
<AGN as Agent>::Output: Serialize + for<'de> Deserialize<'de>,
|
||||
{
|
||||
#[cfg(feature = "std_web")]
|
||||
worker: Value,
|
||||
#[cfg(feature = "web_sys")]
|
||||
worker: Worker,
|
||||
id: HandlerId,
|
||||
_agent: PhantomData<AGN>,
|
||||
|
@ -220,13 +191,7 @@ where
|
|||
fn respond(&self, id: HandlerId, output: AGN::Output) {
|
||||
let msg = FromWorker::ProcessOutput(id, output);
|
||||
let data = msg.pack();
|
||||
cfg_match! {
|
||||
feature = "std_web" => js! {
|
||||
var data = @{data};
|
||||
self.postMessage(data);
|
||||
},
|
||||
feature = "web_sys" => worker_self().post_message_vec(data),
|
||||
};
|
||||
worker_self().post_message_vec(data);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -261,29 +226,15 @@ where
|
|||
let upd = AgentLifecycleEvent::Destroy;
|
||||
scope.send(upd);
|
||||
// Terminates web worker
|
||||
cfg_match! {
|
||||
feature = "std_web" => js! { self.close(); },
|
||||
feature = "web_sys" => worker_self().close(),
|
||||
};
|
||||
worker_self().close();
|
||||
}
|
||||
}
|
||||
};
|
||||
let loaded: FromWorker<AGN::Output> = FromWorker::WorkerLoaded;
|
||||
let loaded = loaded.pack();
|
||||
cfg_match! {
|
||||
feature = "std_web" => js! {
|
||||
var handler = @{handler};
|
||||
self.onmessage = function(event) {
|
||||
handler(event.data);
|
||||
};
|
||||
self.postMessage(@{loaded});
|
||||
},
|
||||
feature = "web_sys" => ({
|
||||
let worker = worker_self();
|
||||
worker.set_onmessage_closure(handler);
|
||||
worker.post_message_vec(loaded);
|
||||
}),
|
||||
};
|
||||
let worker = worker_self();
|
||||
worker.set_onmessage_closure(handler);
|
||||
worker.post_message_vec(loaded);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -293,9 +244,6 @@ where
|
|||
<AGN as Agent>::Input: Serialize + for<'de> Deserialize<'de>,
|
||||
<AGN as Agent>::Output: Serialize + for<'de> Deserialize<'de>,
|
||||
{
|
||||
#[cfg(feature = "std_web")]
|
||||
worker: Value,
|
||||
#[cfg(feature = "web_sys")]
|
||||
worker: Worker,
|
||||
slab: SharedOutputSlab<AGN>,
|
||||
}
|
||||
|
@ -306,11 +254,7 @@ where
|
|||
<AGN as Agent>::Input: Serialize + for<'de> Deserialize<'de>,
|
||||
<AGN as Agent>::Output: Serialize + for<'de> Deserialize<'de>,
|
||||
{
|
||||
pub fn new(
|
||||
#[cfg(feature = "std_web")] worker: Value,
|
||||
#[cfg(feature = "web_sys")] worker: Worker,
|
||||
slab: SharedOutputSlab<AGN>,
|
||||
) -> Self {
|
||||
pub fn new(worker: Worker, slab: SharedOutputSlab<AGN>) -> Self {
|
||||
RemoteAgent { worker, slab }
|
||||
}
|
||||
|
||||
|
|
|
@ -3,14 +3,7 @@
|
|||
|
||||
use crate::html::{Component, ComponentLink, NodeRef, Scope};
|
||||
use crate::utils::document;
|
||||
use cfg_if::cfg_if;
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "std_web")] {
|
||||
use stdweb::web::{Element, INode, IParentNode};
|
||||
} else if #[cfg(feature = "web_sys")] {
|
||||
use web_sys::Element;
|
||||
}
|
||||
}
|
||||
use web_sys::Element;
|
||||
|
||||
/// An instance of an application.
|
||||
#[derive(Debug)]
|
||||
|
@ -89,6 +82,7 @@ where
|
|||
{
|
||||
/// Creates a new `App` with a component in a context.
|
||||
pub fn new() -> Self {
|
||||
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
|
||||
let scope = Scope::new(None);
|
||||
App { scope }
|
||||
}
|
||||
|
|
|
@ -4,14 +4,7 @@ use super::{Component, Scope};
|
|||
use crate::scheduler::{scheduler, ComponentRunnableType, Runnable, Shared};
|
||||
use crate::virtual_dom::{VDiff, VNode};
|
||||
use crate::NodeRef;
|
||||
use cfg_if::cfg_if;
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "std_web")] {
|
||||
use stdweb::web::Element;
|
||||
} else if #[cfg(feature = "web_sys")] {
|
||||
use web_sys::Element;
|
||||
}
|
||||
}
|
||||
use web_sys::Element;
|
||||
|
||||
pub(crate) struct ComponentState<COMP: Component> {
|
||||
pub(crate) component: Box<COMP>,
|
||||
|
|
|
@ -11,19 +11,12 @@ use crate::html::NodeRef;
|
|||
use crate::scheduler::{scheduler, Shared};
|
||||
use crate::utils::document;
|
||||
use crate::virtual_dom::{insert_node, VNode};
|
||||
use cfg_if::cfg_if;
|
||||
use std::any::{Any, TypeId};
|
||||
use std::cell::{Ref, RefCell};
|
||||
use std::fmt;
|
||||
use std::ops::Deref;
|
||||
use std::rc::Rc;
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "std_web")] {
|
||||
use stdweb::web::{Element, Node};
|
||||
} else if #[cfg(feature = "web_sys")] {
|
||||
use web_sys::{Element, Node};
|
||||
}
|
||||
}
|
||||
use web_sys::{Element, Node};
|
||||
|
||||
/// Untyped scope used for accessing parent scope
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
|
@ -1,101 +0,0 @@
|
|||
// Inspired by: http://package.elm-lang.org/packages/elm-lang/html/2.0.0/Html-Events
|
||||
// Yew maintains a list of event listeners not supported by stdweb in the yew-macro crate (in the tag_attribute.rs file).
|
||||
// Be sure to update that list when you change anything here.
|
||||
impl_action! {
|
||||
onabort(event: ResourceAbortEvent) -> ResourceAbortEvent => |_, event| { event }
|
||||
onauxclick(event: AuxClickEvent) -> AuxClickEvent => |_, event| { event }
|
||||
onblur(event: BlurEvent) -> BlurEvent => |_, event| { event }
|
||||
// oncancel not supported
|
||||
// oncanplay not supported
|
||||
// oncanplaythrough not supported
|
||||
onchange(event: ChangeEvent) -> ChangeData => |this: &Element, _| { onchange_handler(this) }
|
||||
onclick(event: ClickEvent) -> ClickEvent => |_, event| { event }
|
||||
// onclose not supported
|
||||
oncontextmenu(event: ContextMenuEvent) -> ContextMenuEvent => |_, event| { event }
|
||||
// oncuechange not supported
|
||||
ondblclick(event: DoubleClickEvent) -> DoubleClickEvent => |_, event| { event }
|
||||
ondrag(event: DragEvent) -> DragEvent => |_, event| { event }
|
||||
ondragend(event: DragEndEvent) -> DragEndEvent => |_, event| { event }
|
||||
ondragenter(event: DragEnterEvent) -> DragEnterEvent => |_, event| { event }
|
||||
ondragexit(event: DragExitEvent) -> DragExitEvent => |_, event| { event }
|
||||
ondragleave(event: DragLeaveEvent) -> DragLeaveEvent => |_, event| { event }
|
||||
ondragover(event: DragOverEvent) -> DragOverEvent => |_, event| { event }
|
||||
ondragstart(event: DragStartEvent) -> DragStartEvent => |_, event| { event }
|
||||
ondrop(event: DragDropEvent) -> DragDropEvent => |_, event| { event }
|
||||
// ondurationchange not supported
|
||||
// onemptied not supported
|
||||
// onended not supported
|
||||
onerror(event: ResourceErrorEvent) -> ResourceErrorEvent => |_, event| { event }
|
||||
onfocus(event: FocusEvent) -> FocusEvent => |_, event| { event }
|
||||
// onformdata not supported
|
||||
oninput(event: InputEvent) -> InputData => |this: &Element, event| { oninput_handler(this, event) }
|
||||
// oninvalid not supported
|
||||
onkeydown(event: KeyDownEvent) -> KeyDownEvent => |_, event| { event }
|
||||
onkeypress(event: KeyPressEvent) -> KeyPressEvent => |_, event| { event }
|
||||
onkeyup(event: KeyUpEvent) -> KeyUpEvent => |_, event| { event }
|
||||
onload(event: ResourceLoadEvent) -> ResourceLoadEvent => |_, event| { event }
|
||||
// onloadeddata not supported
|
||||
// onloadedmetadata not supported
|
||||
onloadstart(event: LoadStartEvent) -> LoadStartEvent => |_, event| { event }
|
||||
onmousedown(event: MouseDownEvent) -> MouseDownEvent => |_, event| { event }
|
||||
onmouseenter(event: MouseEnterEvent) -> MouseEnterEvent => |_, event| { event }
|
||||
onmouseleave(event: MouseLeaveEvent) -> MouseLeaveEvent => |_, event| { event }
|
||||
onmousemove(event: MouseMoveEvent) -> MouseMoveEvent => |_, event| { event }
|
||||
onmouseout(event: MouseOutEvent) -> MouseOutEvent => |_, event| { event }
|
||||
onmouseover(event: MouseOverEvent) -> MouseOverEvent => |_, event| { event }
|
||||
onmouseup(event: MouseUpEvent) -> MouseUpEvent => |_, event| { event }
|
||||
// onpause not supported
|
||||
// onplay not supported
|
||||
// onplaying not supported
|
||||
onprogress(event: ProgressEvent) -> ProgressEvent => |_, event| { event }
|
||||
// onratechange not supported
|
||||
// onreset not supported
|
||||
onresize(event: ResizeEvent) -> ResizeEvent => |_, event| { event }
|
||||
onscroll(event: ScrollEvent) -> ScrollEvent => |_, event| { event }
|
||||
// onsecuritypolicyviolation not supported
|
||||
// onseeked not supported
|
||||
// onseeking not supported
|
||||
// onselect not supported
|
||||
onslotchange(event: SlotChangeEvent) -> SlotChangeEvent => |_, event| { event }
|
||||
// onstalled not supported
|
||||
onsubmit(event: SubmitEvent) -> SubmitEvent => |_, event| { event }
|
||||
// onsuspend not supported
|
||||
// ontimeupdate not supported
|
||||
// ontoggle not supported
|
||||
// onvolumechange not supported
|
||||
// onwaiting not supported
|
||||
onwheel(event: MouseWheelEvent) -> MouseWheelEvent => |_, event| { event }
|
||||
|
||||
// oncopy not supported
|
||||
// oncut not supported
|
||||
// onpaste not supported
|
||||
|
||||
// onanimationcancel not supported
|
||||
// onanimationend not supported
|
||||
// onanimationiteration not supported
|
||||
// onanimationstart not supported
|
||||
ongotpointercapture(event: GotPointerCaptureEvent) -> GotPointerCaptureEvent => |_, event| { event }
|
||||
onloadend(event: LoadEndEvent) -> LoadEndEvent => |_, event| { event }
|
||||
onlostpointercapture(event: LostPointerCaptureEvent) -> LostPointerCaptureEvent => |_, event| { event }
|
||||
onpointercancel(event: PointerCancelEvent) -> PointerCancelEvent => |_, event| { event }
|
||||
onpointerdown(event: PointerDownEvent) -> PointerDownEvent => |_, event| { event }
|
||||
onpointerenter(event: PointerEnterEvent) -> PointerEnterEvent => |_, event| { event }
|
||||
onpointerleave(event: PointerLeaveEvent) -> PointerLeaveEvent => |_, event| { event }
|
||||
onpointerlockchange(event: PointerLockChangeEvent) -> PointerLockChangeEvent => |_, event| { event }
|
||||
onpointerlockerror(event: PointerLockErrorEvent) -> PointerLockErrorEvent => |_, event| { event }
|
||||
onpointermove(event: PointerMoveEvent) -> PointerMoveEvent => |_, event| { event }
|
||||
onpointerout(event: PointerOutEvent) -> PointerOutEvent => |_, event| { event }
|
||||
onpointerover(event: PointerOverEvent) -> PointerOverEvent => |_, event| { event }
|
||||
onpointerup(event: PointerUpEvent) -> PointerUpEvent => |_, event| { event }
|
||||
onselectionchange(event: SelectionChangeEvent) -> SelectionChangeEvent => |_, event| { event }
|
||||
// onselectstart not supported
|
||||
// onshow not supported
|
||||
ontouchcancel(event: TouchCancel) -> TouchCancel => |_, event| { event }
|
||||
ontouchend(event: TouchEnd) -> TouchEnd => |_, event| { event }
|
||||
ontouchmove(event: TouchMove) -> TouchMove => |_, event| { event }
|
||||
ontouchstart(event: TouchStart) -> TouchStart => |_, event| { event }
|
||||
// ontransitioncancel not supported
|
||||
// ontransitionend not supported
|
||||
// ontransitionrun not supported
|
||||
// ontransitionstart not supported
|
||||
}
|
|
@ -1,28 +1,16 @@
|
|||
#[macro_use]
|
||||
macro_rules! impl_action {
|
||||
($($action:ident(event: $type:ident) -> $ret:ty => $convert:expr)*) => {$(
|
||||
impl_action!($action(name: "", event: $type) -> $ret => $convert);
|
||||
)*};
|
||||
($($action:ident(name: $name:literal, event: $type:ident) -> $ret:ty => $convert:expr)*) => {$(
|
||||
/// An abstract implementation of a listener.
|
||||
#[doc(hidden)]
|
||||
pub mod $action {
|
||||
use cfg_if::cfg_if;
|
||||
use cfg_match::cfg_match;
|
||||
use crate::callback::Callback;
|
||||
#[allow(unused_imports)]
|
||||
use crate::html::listener::*;
|
||||
use crate::virtual_dom::Listener;
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "std_web")] {
|
||||
use stdweb::web::event::$type;
|
||||
use stdweb::web::{Element, IEventTarget};
|
||||
} else if #[cfg(feature = "web_sys")] {
|
||||
use gloo::events::{EventListener, EventListenerOptions};
|
||||
use wasm_bindgen::JsValue;
|
||||
use web_sys::{$type as WebSysType, Element, EventTarget};
|
||||
}
|
||||
}
|
||||
use gloo::events::{EventListener, EventListenerOptions};
|
||||
use wasm_bindgen::JsValue;
|
||||
use web_sys::{$type as WebSysType, Element, EventTarget};
|
||||
|
||||
/// A wrapper for a callback which attaches event listeners to elements.
|
||||
#[derive(Clone, Debug)]
|
||||
|
@ -49,25 +37,18 @@ macro_rules! impl_action {
|
|||
let this = element.clone();
|
||||
let callback = self.callback.clone();
|
||||
let listener = move |
|
||||
#[cfg(feature = "std_web")] event: $type,
|
||||
#[cfg(feature = "web_sys")] event: &web_sys::Event
|
||||
event: &web_sys::Event
|
||||
| {
|
||||
#[cfg(feature = "web_sys")]
|
||||
let event: WebSysType = JsValue::from(event).into();
|
||||
callback.emit($convert(&this, event));
|
||||
};
|
||||
cfg_match! {
|
||||
feature = "std_web" => EventListener(Some(element.add_event_listener(listener))),
|
||||
feature = "web_sys" => ({
|
||||
// We should only set passive event listeners for `touchstart` and `touchmove`.
|
||||
// See here: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Improving_scrolling_performance_with_passive_listeners
|
||||
if $name == "touchstart" || $name == "touchmove" {
|
||||
EventListener::new(&EventTarget::from(element.clone()), $name, listener)
|
||||
} else {
|
||||
let options = EventListenerOptions::enable_prevent_default();
|
||||
EventListener::new_with_options(&EventTarget::from(element.clone()), $name, options, listener)
|
||||
}
|
||||
}),
|
||||
// We should only set passive event listeners for `touchstart` and `touchmove`.
|
||||
// See here: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Improving_scrolling_performance_with_passive_listeners
|
||||
if $name == "touchstart" || $name == "touchmove" {
|
||||
EventListener::new(&EventTarget::from(element.clone()), $name, listener)
|
||||
} else {
|
||||
let options = EventListenerOptions::enable_prevent_default();
|
||||
EventListener::new_with_options(&EventTarget::from(element.clone()), $name, options, listener)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,33 +1,14 @@
|
|||
#[macro_use]
|
||||
mod macros;
|
||||
mod events;
|
||||
|
||||
use cfg_if::cfg_if;
|
||||
use cfg_match::cfg_match;
|
||||
use wasm_bindgen::JsCast;
|
||||
use web_sys::{
|
||||
Element, FileList, HtmlInputElement as InputElement, HtmlSelectElement as SelectElement,
|
||||
HtmlTextAreaElement as TextAreaElement, InputEvent,
|
||||
};
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "std_web")] {
|
||||
mod listener_stdweb;
|
||||
|
||||
use stdweb::js;
|
||||
use stdweb::unstable::{TryFrom, TryInto};
|
||||
use stdweb::web::html_element::{InputElement, SelectElement, TextAreaElement};
|
||||
use stdweb::web::{Element, EventListenerHandle, FileList, IElement, INode};
|
||||
use stdweb::web::event::InputEvent;
|
||||
|
||||
pub use listener_stdweb::*;
|
||||
} else if #[cfg(feature = "web_sys")] {
|
||||
mod listener_web_sys;
|
||||
|
||||
use wasm_bindgen::JsCast;
|
||||
use web_sys::{
|
||||
Element, FileList, HtmlInputElement as InputElement, HtmlSelectElement as SelectElement,
|
||||
HtmlTextAreaElement as TextAreaElement,
|
||||
InputEvent
|
||||
};
|
||||
|
||||
pub use listener_web_sys::*;
|
||||
}
|
||||
}
|
||||
pub use events::*;
|
||||
|
||||
/// A type representing data from `oninput` event.
|
||||
#[derive(Debug)]
|
||||
|
@ -64,26 +45,10 @@ fn oninput_handler(this: &Element, event: InputEvent) -> InputData {
|
|||
// practice though any element with `contenteditable=true` may generate such events,
|
||||
// therefore here we fall back to just returning the text content of the node.
|
||||
// See https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/input_event.
|
||||
let (v1, v2) = cfg_match! {
|
||||
feature = "std_web" => ({
|
||||
(
|
||||
this.clone()
|
||||
.try_into()
|
||||
.map(|input: InputElement| input.raw_value())
|
||||
.ok(),
|
||||
this.clone()
|
||||
.try_into()
|
||||
.map(|input: TextAreaElement| input.value())
|
||||
.ok(),
|
||||
)
|
||||
}),
|
||||
feature = "web_sys" => ({
|
||||
(
|
||||
this.dyn_ref().map(|input: &InputElement| input.value()),
|
||||
this.dyn_ref().map(|input: &TextAreaElement| input.value()),
|
||||
)
|
||||
}),
|
||||
};
|
||||
let (v1, v2) = (
|
||||
this.dyn_ref().map(|input: &InputElement| input.value()),
|
||||
this.dyn_ref().map(|input: &TextAreaElement| input.value()),
|
||||
);
|
||||
let v3 = this.text_content();
|
||||
let value = v1.or(v2).or(v3)
|
||||
.expect("only an InputElement or TextAreaElement or an element with contenteditable=true can have an oninput event listener");
|
||||
|
@ -93,39 +58,24 @@ fn oninput_handler(this: &Element, event: InputEvent) -> InputData {
|
|||
fn onchange_handler(this: &Element) -> ChangeData {
|
||||
match this.node_name().as_ref() {
|
||||
"INPUT" => {
|
||||
let input = cfg_match! {
|
||||
feature = "std_web" => InputElement::try_from(this.clone()).unwrap(),
|
||||
feature = "web_sys" => this.dyn_ref::<InputElement>().unwrap(),
|
||||
};
|
||||
let input = this.dyn_ref::<InputElement>().unwrap();
|
||||
let is_file = input
|
||||
.get_attribute("type")
|
||||
.map(|value| value.eq_ignore_ascii_case("file"))
|
||||
.unwrap_or(false);
|
||||
if is_file {
|
||||
let files: FileList = cfg_match! {
|
||||
feature = "std_web" => js!( return @{input}.files; ).try_into().unwrap(),
|
||||
feature = "web_sys" => input.files().unwrap(),
|
||||
};
|
||||
let files: FileList = input.files().unwrap();
|
||||
ChangeData::Files(files)
|
||||
} else {
|
||||
cfg_match! {
|
||||
feature = "std_web" => ChangeData::Value(input.raw_value()),
|
||||
feature = "web_sys" => ChangeData::Value(input.value()),
|
||||
}
|
||||
ChangeData::Value(input.value())
|
||||
}
|
||||
}
|
||||
"TEXTAREA" => {
|
||||
let tae = cfg_match! {
|
||||
feature = "std_web" => TextAreaElement::try_from(this.clone()).unwrap(),
|
||||
feature = "web_sys" => this.dyn_ref::<TextAreaElement>().unwrap(),
|
||||
};
|
||||
let tae = this.dyn_ref::<TextAreaElement>().unwrap();
|
||||
ChangeData::Value(tae.value())
|
||||
}
|
||||
"SELECT" => {
|
||||
let se = cfg_match! {
|
||||
feature = "std_web" => SelectElement::try_from(this.clone()).unwrap(),
|
||||
feature = "web_sys" => this.dyn_ref::<SelectElement>().unwrap().clone(),
|
||||
};
|
||||
let se = this.dyn_ref::<SelectElement>().unwrap().clone();
|
||||
ChangeData::Select(se)
|
||||
}
|
||||
_ => {
|
||||
|
@ -133,17 +83,3 @@ fn onchange_handler(this: &Element) -> ChangeData {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handler to an event listener, only use is to cancel the event.
|
||||
#[cfg(feature = "std_web")]
|
||||
#[derive(Debug)]
|
||||
pub struct EventListener(Option<EventListenerHandle>);
|
||||
|
||||
#[cfg(feature = "std_web")]
|
||||
impl Drop for EventListener {
|
||||
fn drop(&mut self) {
|
||||
if let Some(event) = self.0.take() {
|
||||
event.remove()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,19 +9,10 @@ pub use component::*;
|
|||
pub use listener::*;
|
||||
|
||||
use crate::virtual_dom::VNode;
|
||||
use cfg_if::cfg_if;
|
||||
use cfg_match::cfg_match;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "std_web")] {
|
||||
use stdweb::unstable::TryFrom;
|
||||
use stdweb::web::Node;
|
||||
} else if #[cfg(feature = "web_sys")] {
|
||||
use wasm_bindgen::JsValue;
|
||||
use web_sys::Node;
|
||||
}
|
||||
}
|
||||
use wasm_bindgen::JsValue;
|
||||
use web_sys::Node;
|
||||
|
||||
/// A type which expected as a result of `view` function implementation.
|
||||
pub type Html = VNode;
|
||||
|
@ -31,10 +22,7 @@ pub type Html = VNode;
|
|||
/// # Example
|
||||
/// Focus an `<input>` element on mount.
|
||||
/// ```
|
||||
/// #[cfg(feature = "std_web")]
|
||||
/// use stdweb::web::{html_element::InputElement, IHtmlElement};
|
||||
/// #[cfg(feature = "web_sys")]
|
||||
/// use web_sys::HtmlInputElement as InputElement;
|
||||
/// use web_sys::HtmlInputElement;
|
||||
///# use yew::prelude::*;
|
||||
///
|
||||
/// pub struct Input {
|
||||
|
@ -53,7 +41,7 @@ pub type Html = VNode;
|
|||
///
|
||||
/// fn rendered(&mut self, first_render: bool) {
|
||||
/// if first_render {
|
||||
/// if let Some(input) = self.node_ref.cast::<InputElement>() {
|
||||
/// if let Some(input) = self.node_ref.cast::<HtmlInputElement>() {
|
||||
/// input.focus();
|
||||
/// }
|
||||
/// }
|
||||
|
@ -99,17 +87,9 @@ impl NodeRef {
|
|||
}
|
||||
|
||||
/// Try converting the node reference into another form
|
||||
pub fn cast<
|
||||
#[cfg(feature = "std_web")] INTO: TryFrom<Node>,
|
||||
#[cfg(feature = "web_sys")] INTO: AsRef<Node> + From<JsValue>,
|
||||
>(
|
||||
&self,
|
||||
) -> Option<INTO> {
|
||||
pub fn cast<INTO: AsRef<Node> + From<JsValue>>(&self) -> Option<INTO> {
|
||||
let node = self.get();
|
||||
cfg_match! {
|
||||
feature = "std_web" => node.and_then(|node| INTO::try_from(node).ok()),
|
||||
feature = "web_sys" => node.map(Into::into).map(INTO::from),
|
||||
}
|
||||
node.map(Into::into).map(INTO::from)
|
||||
}
|
||||
|
||||
/// Wrap an existing `Node` in a `NodeRef`
|
||||
|
|
|
@ -11,12 +11,6 @@
|
|||
//!
|
||||
//! ### Supported Targets
|
||||
//! - `wasm32-unknown-unknown`
|
||||
#![cfg_attr(
|
||||
feature = "std_web",
|
||||
doc = "\
|
||||
- `wasm32-unknown-emscripten`
|
||||
- `asmjs-unknown-emscripten`"
|
||||
)]
|
||||
//!
|
||||
//! ### Important Notes
|
||||
//! - Yew is not (yet) production ready but is great for side projects and internal tools
|
||||
|
@ -24,12 +18,6 @@
|
|||
feature = "web_sys",
|
||||
doc = " - If your app is built with `stdweb`, we recommend using [`yew-stdweb`](https://docs.rs/yew-stdweb) instead."
|
||||
)]
|
||||
# instead."
|
||||
)]
|
||||
//!
|
||||
//! ## Example
|
||||
//!
|
||||
|
@ -291,63 +279,26 @@ pub mod virtual_dom;
|
|||
#[cfg(feature = "agent")]
|
||||
pub mod agent;
|
||||
|
||||
#[cfg(feature = "web_sys")]
|
||||
pub use web_sys;
|
||||
|
||||
/// The module that contains all events available in the framework.
|
||||
pub mod events {
|
||||
use cfg_if::cfg_if;
|
||||
|
||||
pub use crate::html::{ChangeData, InputData};
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "std_web")] {
|
||||
#[doc(no_inline)]
|
||||
pub use stdweb::web::event::{
|
||||
BlurEvent, ClickEvent, ContextMenuEvent, DoubleClickEvent, DragDropEvent, DragEndEvent,
|
||||
DragEnterEvent, DragEvent, DragExitEvent, DragLeaveEvent, DragOverEvent, DragStartEvent,
|
||||
FocusEvent, GotPointerCaptureEvent, IKeyboardEvent, IMouseEvent, IPointerEvent,
|
||||
KeyDownEvent, KeyPressEvent, KeyUpEvent, LostPointerCaptureEvent, MouseDownEvent,
|
||||
MouseEnterEvent, MouseLeaveEvent, MouseMoveEvent, MouseOutEvent, MouseOverEvent,
|
||||
MouseUpEvent, MouseWheelEvent, PointerCancelEvent, PointerDownEvent, PointerEnterEvent,
|
||||
PointerLeaveEvent, PointerMoveEvent, PointerOutEvent, PointerOverEvent, PointerUpEvent,
|
||||
ScrollEvent, SubmitEvent, TouchCancel, TouchEnd, TouchEnter, TouchMove, TouchStart,
|
||||
};
|
||||
} else if #[cfg(feature = "web_sys")] {
|
||||
#[doc(no_inline)]
|
||||
pub use web_sys::{
|
||||
AnimationEvent, DragEvent, ErrorEvent, Event, FocusEvent, InputEvent, KeyboardEvent,
|
||||
MouseEvent, PointerEvent, ProgressEvent, TouchEvent, TransitionEvent, UiEvent, WheelEvent,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
use cfg_match::cfg_match;
|
||||
|
||||
/// Initializes yew framework. It should be called first.
|
||||
pub fn initialize() {
|
||||
cfg_match! {
|
||||
feature = "std_web" => stdweb::initialize(),
|
||||
feature = "web_sys" => std::panic::set_hook(Box::new(console_error_panic_hook::hook)),
|
||||
#[doc(no_inline)]
|
||||
pub use web_sys::{
|
||||
AnimationEvent, DragEvent, ErrorEvent, Event, FocusEvent, InputEvent, KeyboardEvent,
|
||||
MouseEvent, PointerEvent, ProgressEvent, TouchEvent, TransitionEvent, UiEvent, WheelEvent,
|
||||
};
|
||||
}
|
||||
|
||||
/// Starts event loop.
|
||||
pub fn run_loop() {
|
||||
#[cfg(feature = "std_web")]
|
||||
stdweb::event_loop();
|
||||
}
|
||||
|
||||
/// Starts an app mounted to a body of the document.
|
||||
pub fn start_app<COMP>()
|
||||
where
|
||||
COMP: Component,
|
||||
COMP::Properties: Default,
|
||||
{
|
||||
initialize();
|
||||
App::<COMP>::new().mount_to_body();
|
||||
run_loop();
|
||||
}
|
||||
|
||||
/// Starts an app mounted to a body of the document.
|
||||
|
@ -355,9 +306,7 @@ pub fn start_app_with_props<COMP>(props: COMP::Properties)
|
|||
where
|
||||
COMP: Component,
|
||||
{
|
||||
initialize();
|
||||
App::<COMP>::new().mount_to_body_with_props(props);
|
||||
run_loop();
|
||||
}
|
||||
|
||||
/// The Yew Prelude
|
||||
|
|
|
@ -1,32 +1,18 @@
|
|||
//! This module contains useful utilities to get information about the current document.
|
||||
|
||||
use anyhow::{anyhow, Error};
|
||||
use cfg_if::cfg_if;
|
||||
use cfg_match::cfg_match;
|
||||
use std::marker::PhantomData;
|
||||
use web_sys::{Document, Window};
|
||||
use yew::html::ChildrenRenderer;
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "std_web")] {
|
||||
use stdweb::web::{Document, Window};
|
||||
} else if #[cfg(feature = "web_sys")] {
|
||||
use web_sys::{Document, Window};
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the current window. This function will panic if there is no available window.
|
||||
pub fn window() -> Window {
|
||||
cfg_match! {
|
||||
feature = "std_web" => stdweb::web::window(),
|
||||
feature = "web_sys" => web_sys::window().expect("no window available"),
|
||||
}
|
||||
web_sys::window().expect("no window available")
|
||||
}
|
||||
|
||||
/// Returns the current document.
|
||||
pub fn document() -> Document {
|
||||
cfg_match! {
|
||||
feature = "std_web" => stdweb::web::document(),
|
||||
feature = "web_sys" => window().document().unwrap(),
|
||||
}
|
||||
window().document().unwrap()
|
||||
}
|
||||
|
||||
/// Returns the `host` for the current document. Useful for connecting to the server which serves
|
||||
|
@ -36,10 +22,6 @@ pub fn host() -> Result<String, Error> {
|
|||
.location()
|
||||
.ok_or_else(|| anyhow!("can't get location"))?;
|
||||
|
||||
#[cfg(feature = "std_web")]
|
||||
let host = location.host().map_err(Error::from)?;
|
||||
|
||||
#[cfg(feature = "web_sys")]
|
||||
let host = location.host().map_err(|e| {
|
||||
anyhow!(e
|
||||
.as_string()
|
||||
|
@ -53,13 +35,6 @@ pub fn host() -> Result<String, Error> {
|
|||
pub fn origin() -> Result<String, Error> {
|
||||
let location = window().location();
|
||||
|
||||
#[cfg(feature = "std_web")]
|
||||
let location = location.ok_or_else(|| anyhow!("can't get location"))?;
|
||||
|
||||
#[cfg(feature = "std_web")]
|
||||
let origin = location.origin().map_err(Error::from)?;
|
||||
|
||||
#[cfg(feature = "web_sys")]
|
||||
let origin = location.origin().map_err(|e| {
|
||||
anyhow!(e
|
||||
.as_string()
|
||||
|
|
|
@ -14,18 +14,10 @@ pub mod vtag;
|
|||
pub mod vtext;
|
||||
|
||||
use crate::html::{AnyScope, NodeRef};
|
||||
use cfg_if::cfg_if;
|
||||
use gloo::events::EventListener;
|
||||
use indexmap::IndexMap;
|
||||
use std::{borrow::Cow, collections::HashMap, fmt, hint::unreachable_unchecked, iter, mem, rc::Rc};
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "std_web")] {
|
||||
use crate::html::EventListener;
|
||||
use stdweb::web::{Element, INode, Node};
|
||||
} else if #[cfg(feature = "web_sys")] {
|
||||
use gloo::events::EventListener;
|
||||
use web_sys::{Element, Node};
|
||||
}
|
||||
}
|
||||
use web_sys::{Element, Node};
|
||||
|
||||
#[doc(inline)]
|
||||
pub use self::key::Key;
|
||||
|
@ -361,7 +353,6 @@ pub(crate) trait VDiff {
|
|||
) -> NodeRef;
|
||||
}
|
||||
|
||||
#[cfg(feature = "web_sys")]
|
||||
pub(crate) fn insert_node(node: &Node, parent: &Element, next_sibling: Option<Node>) {
|
||||
match next_sibling {
|
||||
Some(next_sibling) => parent
|
||||
|
@ -371,25 +362,13 @@ pub(crate) fn insert_node(node: &Node, parent: &Element, next_sibling: Option<No
|
|||
};
|
||||
}
|
||||
|
||||
#[cfg(feature = "std_web")]
|
||||
pub(crate) fn insert_node(node: &impl INode, parent: &impl INode, next_sibling: Option<Node>) {
|
||||
if let Some(next_sibling) = next_sibling {
|
||||
parent
|
||||
.insert_before(node, &next_sibling)
|
||||
.expect("failed to insert tag before next sibling");
|
||||
} else {
|
||||
parent.append_child(node);
|
||||
}
|
||||
}
|
||||
|
||||
/// Transform properties to the expected type.
|
||||
pub trait Transformer<FROM, TO> {
|
||||
/// Transforms one type to another.
|
||||
fn transform(from: FROM) -> TO;
|
||||
}
|
||||
|
||||
// stdweb lacks the `inner_html` method
|
||||
#[cfg(all(test, feature = "web_sys"))]
|
||||
#[cfg(test)]
|
||||
mod layout_tests {
|
||||
use super::*;
|
||||
use crate::html::{AnyScope, Scope};
|
||||
|
@ -530,7 +509,7 @@ mod layout_tests {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, feature = "web_sys", feature = "wasm_bench"))]
|
||||
#[cfg(all(test, feature = "wasm_bench"))]
|
||||
mod benchmarks {
|
||||
use super::{Attributes, PositionalAttr};
|
||||
use std::borrow::Cow;
|
||||
|
|
|
@ -2,18 +2,11 @@
|
|||
|
||||
use super::{Key, Transformer, VDiff, VNode};
|
||||
use crate::html::{AnyScope, Component, NodeRef, Scope, Scoped};
|
||||
use cfg_if::cfg_if;
|
||||
use std::any::TypeId;
|
||||
use std::borrow::Borrow;
|
||||
use std::fmt;
|
||||
use std::ops::Deref;
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "std_web")] {
|
||||
use stdweb::web::Element;
|
||||
} else if #[cfg(feature = "web_sys")] {
|
||||
use web_sys::Element;
|
||||
}
|
||||
}
|
||||
use web_sys::Element;
|
||||
|
||||
/// A virtual component.
|
||||
pub struct VComp {
|
||||
|
@ -274,15 +267,7 @@ mod tests {
|
|||
html, utils::document, Children, Component, ComponentLink, Html, NodeRef, Properties,
|
||||
ShouldRender,
|
||||
};
|
||||
use cfg_match::cfg_match;
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "std_web")] {
|
||||
use stdweb::web::{INode, Node};
|
||||
} else if #[cfg(feature = "web_sys")] {
|
||||
use web_sys::Node;
|
||||
}
|
||||
}
|
||||
use web_sys::Node;
|
||||
|
||||
#[cfg(feature = "wasm_test")]
|
||||
use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure};
|
||||
|
@ -471,10 +456,8 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "web_sys")]
|
||||
use super::{AnyScope, Element};
|
||||
|
||||
#[cfg(feature = "web_sys")]
|
||||
fn setup_parent() -> (AnyScope, Element) {
|
||||
let scope = AnyScope {
|
||||
type_id: std::any::TypeId::of::<()>(),
|
||||
|
@ -488,7 +471,6 @@ mod tests {
|
|||
(scope, parent)
|
||||
}
|
||||
|
||||
#[cfg(feature = "web_sys")]
|
||||
fn get_html(mut node: Html, scope: &AnyScope, parent: &Element) -> String {
|
||||
// clear parent
|
||||
parent.set_inner_html("");
|
||||
|
@ -498,7 +480,6 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "web_sys")]
|
||||
fn all_ways_of_passing_children_work() {
|
||||
let (scope, parent) = setup_parent();
|
||||
|
||||
|
@ -553,25 +534,19 @@ mod tests {
|
|||
};
|
||||
let parent = document().create_element("div").unwrap();
|
||||
|
||||
#[cfg(feature = "std_web")]
|
||||
document().body().unwrap().append_child(&parent);
|
||||
#[cfg(feature = "web_sys")]
|
||||
document().body().unwrap().append_child(&parent).unwrap();
|
||||
|
||||
let node_ref = NodeRef::default();
|
||||
let mut elem: VNode = html! { <Comp ref=node_ref.clone()></Comp> };
|
||||
elem.apply(&scope, &parent, NodeRef::default(), None);
|
||||
let parent_node = cfg_match! {
|
||||
feature = "std_web" => parent.as_node(),
|
||||
feature = "web_sys" => parent.deref(),
|
||||
};
|
||||
let parent_node = parent.deref();
|
||||
assert_eq!(node_ref.get(), parent_node.first_child());
|
||||
elem.detach(&parent);
|
||||
assert!(node_ref.get().is_none());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, feature = "web_sys"))]
|
||||
#[cfg(test)]
|
||||
mod layout_tests {
|
||||
extern crate self as yew;
|
||||
|
||||
|
|
|
@ -1,16 +1,9 @@
|
|||
//! This module contains fragments implementation.
|
||||
use super::{Key, VDiff, VNode, VText};
|
||||
use crate::html::{AnyScope, NodeRef};
|
||||
use cfg_if::cfg_if;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::ops::{Deref, DerefMut};
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "std_web")] {
|
||||
use stdweb::web::Element;
|
||||
} else if #[cfg(feature = "web_sys")] {
|
||||
use web_sys::Element;
|
||||
}
|
||||
}
|
||||
use web_sys::Element;
|
||||
|
||||
/// This struct represents a fragment of the Virtual DOM tree.
|
||||
#[derive(Clone, Debug, PartialEq, Default)]
|
||||
|
@ -235,7 +228,7 @@ impl VDiff for VList {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, feature = "web_sys"))]
|
||||
#[cfg(test)]
|
||||
mod layout_tests {
|
||||
extern crate self as yew;
|
||||
|
||||
|
@ -313,7 +306,7 @@ mod layout_tests {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, feature = "web_sys"))]
|
||||
#[cfg(test)]
|
||||
mod layout_tests_keys {
|
||||
extern crate self as yew;
|
||||
|
||||
|
|
|
@ -2,20 +2,12 @@
|
|||
|
||||
use super::{Key, VChild, VComp, VDiff, VList, VTag, VText};
|
||||
use crate::html::{AnyScope, Component, NodeRef};
|
||||
use cfg_if::cfg_if;
|
||||
use cfg_match::cfg_match;
|
||||
use log::warn;
|
||||
use std::cmp::PartialEq;
|
||||
use std::fmt;
|
||||
use std::iter::FromIterator;
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "std_web")] {
|
||||
use stdweb::web::{Element, INode, Node};
|
||||
} else if #[cfg(feature = "web_sys")] {
|
||||
use web_sys::{Element, Node};
|
||||
}
|
||||
}
|
||||
use web_sys::{Element, Node};
|
||||
|
||||
/// Bind virtual element to a DOM reference.
|
||||
#[derive(Clone)]
|
||||
|
@ -54,10 +46,7 @@ impl VNode {
|
|||
.into(),
|
||||
VNode::VText(vtext) => {
|
||||
let text_node = vtext.reference.as_ref().expect("VText is not mounted");
|
||||
cfg_match! {
|
||||
feature = "std_web" => text_node.as_node().clone(),
|
||||
feature = "web_sys" => text_node.clone().into(),
|
||||
}
|
||||
text_node.clone().into()
|
||||
}
|
||||
VNode::VComp(vcomp) => vcomp.node_ref.get().expect("VComp is not mounted"),
|
||||
VNode::VList(vlist) => vlist
|
||||
|
@ -218,7 +207,7 @@ impl PartialEq for VNode {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, feature = "web_sys"))]
|
||||
#[cfg(test)]
|
||||
mod layout_tests {
|
||||
use super::*;
|
||||
use crate::virtual_dom::layout_tests::{diff_layouts, TestLayout};
|
||||
|
|
|
@ -5,29 +5,17 @@ use super::{
|
|||
};
|
||||
use crate::html::{AnyScope, NodeRef};
|
||||
use crate::utils::document;
|
||||
use cfg_if::cfg_if;
|
||||
use cfg_match::cfg_match;
|
||||
use gloo::events::EventListener;
|
||||
use log::warn;
|
||||
use std::borrow::Cow;
|
||||
use std::cmp::PartialEq;
|
||||
use std::ops::Deref;
|
||||
use std::rc::Rc;
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "std_web")] {
|
||||
use crate::html::EventListener;
|
||||
#[allow(unused_imports)]
|
||||
use stdweb::{_js_impl, js};
|
||||
use stdweb::unstable::TryFrom;
|
||||
use stdweb::web::html_element::{InputElement, TextAreaElement};
|
||||
use stdweb::web::{Element, IElement, INode};
|
||||
} else if #[cfg(feature = "web_sys")] {
|
||||
use gloo::events::EventListener;
|
||||
use std::ops::Deref;
|
||||
use wasm_bindgen::JsCast;
|
||||
use web_sys::{
|
||||
Element, HtmlInputElement as InputElement, HtmlTextAreaElement as TextAreaElement, HtmlButtonElement
|
||||
};
|
||||
}
|
||||
}
|
||||
use wasm_bindgen::JsCast;
|
||||
use web_sys::{
|
||||
Element, HtmlButtonElement, HtmlInputElement as InputElement,
|
||||
HtmlTextAreaElement as TextAreaElement,
|
||||
};
|
||||
|
||||
/// SVG namespace string used for creating svg elements
|
||||
pub const SVG_NAMESPACE: &str = "http://www.w3.org/2000/svg";
|
||||
|
@ -249,22 +237,13 @@ impl VTag {
|
|||
|
||||
if let Some(element) = self.reference.as_ref() {
|
||||
if self.element_type == ElementType::Input {
|
||||
let input_el = cfg_match! {
|
||||
feature = "std_web" => InputElement::try_from(element.clone()).ok(),
|
||||
feature = "web_sys" => element.dyn_ref::<InputElement>(),
|
||||
};
|
||||
let input_el = element.dyn_ref::<InputElement>();
|
||||
if let Some(input) = input_el {
|
||||
let current_value = cfg_match! {
|
||||
feature = "std_web" => input.raw_value(),
|
||||
feature = "web_sys" => input.value(),
|
||||
};
|
||||
let current_value = input.value();
|
||||
self.set_value(¤t_value)
|
||||
}
|
||||
} else if self.element_type == ElementType::Textarea {
|
||||
let textarea_el = cfg_match! {
|
||||
feature = "std_web" => TextAreaElement::try_from(element.clone()).ok(),
|
||||
feature = "web_sys" => element.dyn_ref::<TextAreaElement>(),
|
||||
};
|
||||
let textarea_el = element.dyn_ref::<TextAreaElement>();
|
||||
if let Some(tae) = textarea_el {
|
||||
let current_value = &tae.value();
|
||||
self.set_value(¤t_value)
|
||||
|
@ -331,28 +310,21 @@ impl VTag {
|
|||
.expect("invalid attribute key");
|
||||
}
|
||||
Patch::Remove(key) => {
|
||||
cfg_match! {
|
||||
feature = "std_web" => element.remove_attribute(&key),
|
||||
feature = "web_sys" => element.remove_attribute(&key)
|
||||
.expect("could not remove attribute"),
|
||||
};
|
||||
element
|
||||
.remove_attribute(&key)
|
||||
.expect("could not remove attribute");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: add std_web after https://github.com/koute/stdweb/issues/395 will be approved
|
||||
// Check this out: https://github.com/yewstack/yew/pull/1033/commits/4b4e958bb1ccac0524eb20f63f06ae394c20553d
|
||||
#[cfg(feature = "web_sys")]
|
||||
{
|
||||
if self.element_type == ElementType::Button {
|
||||
if let Some(button) = element.dyn_ref::<HtmlButtonElement>() {
|
||||
if let Some(change) = self.diff_kind(ancestor) {
|
||||
let kind = match change {
|
||||
Patch::Add(kind, _) | Patch::Replace(kind, _) => kind,
|
||||
Patch::Remove(_) => "",
|
||||
};
|
||||
button.set_type(kind);
|
||||
}
|
||||
if self.element_type == ElementType::Button {
|
||||
if let Some(button) = element.dyn_ref::<HtmlButtonElement>() {
|
||||
if let Some(change) = self.diff_kind(ancestor) {
|
||||
let kind = match change {
|
||||
Patch::Add(kind, _) | Patch::Replace(kind, _) => kind,
|
||||
Patch::Remove(_) => "",
|
||||
};
|
||||
button.set_type(kind);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -362,28 +334,13 @@ impl VTag {
|
|||
// and useful in templates. For example I interpret `checked`
|
||||
// attribute as `checked` parameter, not `defaultChecked` as browsers do
|
||||
if self.element_type == ElementType::Input {
|
||||
if let Some(input) = {
|
||||
cfg_match! {
|
||||
feature = "std_web" => InputElement::try_from(element.clone()).ok(),
|
||||
feature = "web_sys" => element.dyn_ref::<InputElement>(),
|
||||
}
|
||||
} {
|
||||
if let Some(input) = element.dyn_ref::<InputElement>() {
|
||||
if let Some(change) = self.diff_kind(ancestor) {
|
||||
let kind = match change {
|
||||
Patch::Add(kind, _) | Patch::Replace(kind, _) => kind,
|
||||
Patch::Remove(_) => "",
|
||||
};
|
||||
cfg_match! {
|
||||
feature = "std_web" => ({
|
||||
//https://github.com/koute/stdweb/commit/3b85c941db00b8e3c942624afd50c5929085fb08
|
||||
//input.set_kind(&kind);
|
||||
let input = &input;
|
||||
js! { @(no_return)
|
||||
@{input}.type = @{kind};
|
||||
}
|
||||
}),
|
||||
feature = "web_sys" => input.set_type(kind),
|
||||
}
|
||||
input.set_type(kind)
|
||||
}
|
||||
|
||||
if let Some(change) = self.diff_value(ancestor) {
|
||||
|
@ -391,10 +348,7 @@ impl VTag {
|
|||
Patch::Add(kind, _) | Patch::Replace(kind, _) => kind,
|
||||
Patch::Remove(_) => "",
|
||||
};
|
||||
cfg_match! {
|
||||
feature = "std_web" => input.set_raw_value(raw_value),
|
||||
feature = "web_sys" => input.set_value(raw_value),
|
||||
};
|
||||
input.set_value(raw_value)
|
||||
}
|
||||
|
||||
// IMPORTANT! This parameter has to be set every time
|
||||
|
@ -402,12 +356,7 @@ impl VTag {
|
|||
set_checked(&input, self.checked);
|
||||
}
|
||||
} else if self.element_type == ElementType::Textarea {
|
||||
if let Some(tae) = {
|
||||
cfg_match! {
|
||||
feature = "std_web" => TextAreaElement::try_from(element.clone()).ok(),
|
||||
feature = "web_sys" => element.dyn_ref::<TextAreaElement>(),
|
||||
}
|
||||
} {
|
||||
if let Some(tae) = { element.dyn_ref::<TextAreaElement>() } {
|
||||
if let Some(change) = self.diff_value(ancestor) {
|
||||
let value = match change {
|
||||
Patch::Add(kind, _) | Patch::Replace(kind, _) => kind,
|
||||
|
@ -426,10 +375,7 @@ impl VTag {
|
|||
.namespace_uri()
|
||||
.map_or(false, |ns| ns == SVG_NAMESPACE)
|
||||
{
|
||||
let namespace = cfg_match! {
|
||||
feature = "std_web" => SVG_NAMESPACE,
|
||||
feature = "web_sys" => Some(SVG_NAMESPACE),
|
||||
};
|
||||
let namespace = Some(SVG_NAMESPACE);
|
||||
document()
|
||||
.create_element_ns(namespace, tag)
|
||||
.expect("can't create namespaced element for vtag")
|
||||
|
@ -509,10 +455,7 @@ impl VDiff for VTag {
|
|||
ancestor_tag.children.detach(element);
|
||||
}
|
||||
|
||||
let node = cfg_match! {
|
||||
feature = "std_web" => element.as_node(),
|
||||
feature = "web_sys" => element.deref(),
|
||||
};
|
||||
let node = element.deref();
|
||||
self.node_ref.set(Some(node.clone()));
|
||||
self.node_ref.clone()
|
||||
}
|
||||
|
@ -520,10 +463,7 @@ impl VDiff for VTag {
|
|||
|
||||
/// Set `checked` value for the `InputElement`.
|
||||
fn set_checked(input: &InputElement, value: bool) {
|
||||
cfg_match! {
|
||||
feature = "std_web" => js!( @(no_return) @{input}.checked = @{value}; ),
|
||||
feature = "web_sys" => input.set_checked(value),
|
||||
};
|
||||
input.set_checked(value);
|
||||
}
|
||||
|
||||
impl PartialEq for VTag {
|
||||
|
@ -563,8 +503,6 @@ mod tests {
|
|||
use super::*;
|
||||
use crate::html;
|
||||
use std::any::TypeId;
|
||||
#[cfg(feature = "std_web")]
|
||||
use stdweb::web::{document, IElement};
|
||||
#[cfg(feature = "wasm_test")]
|
||||
use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure};
|
||||
|
||||
|
@ -696,15 +634,11 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn supports_svg() {
|
||||
#[cfg(feature = "std_web")]
|
||||
let document = document();
|
||||
#[cfg(feature = "web_sys")]
|
||||
let document = web_sys::window().unwrap().document().unwrap();
|
||||
|
||||
let scope = test_scope();
|
||||
let div_el = document.create_element("div").unwrap();
|
||||
let namespace = SVG_NAMESPACE;
|
||||
#[cfg(feature = "web_sys")]
|
||||
let namespace = Some(namespace);
|
||||
let svg_el = document.create_element_ns(namespace, "svg").unwrap();
|
||||
|
||||
|
@ -838,9 +772,6 @@ mod tests {
|
|||
let scope = test_scope();
|
||||
let parent = document().create_element("div").unwrap();
|
||||
|
||||
#[cfg(feature = "std_web")]
|
||||
document().body().unwrap().append_child(&parent);
|
||||
#[cfg(feature = "web_sys")]
|
||||
document().body().unwrap().append_child(&parent).unwrap();
|
||||
|
||||
let mut elem = html! { <div></div> };
|
||||
|
@ -855,9 +786,6 @@ mod tests {
|
|||
let scope = test_scope();
|
||||
let parent = document().create_element("div").unwrap();
|
||||
|
||||
#[cfg(feature = "std_web")]
|
||||
document().body().unwrap().append_child(&parent);
|
||||
#[cfg(feature = "web_sys")]
|
||||
document().body().unwrap().append_child(&parent).unwrap();
|
||||
|
||||
let mut elem = html! { <div class="ferris the crab"></div> };
|
||||
|
@ -872,9 +800,6 @@ mod tests {
|
|||
let scope = test_scope();
|
||||
let parent = document().create_element("div").unwrap();
|
||||
|
||||
#[cfg(feature = "std_web")]
|
||||
document().body().unwrap().append_child(&parent);
|
||||
#[cfg(feature = "web_sys")]
|
||||
document().body().unwrap().append_child(&parent).unwrap();
|
||||
|
||||
let expected = "not_changed_value";
|
||||
|
@ -890,14 +815,8 @@ mod tests {
|
|||
|
||||
// User input
|
||||
let input_ref = vtag.reference.as_ref().unwrap();
|
||||
let input = cfg_match! {
|
||||
feature = "std_web" => InputElement::try_from(input_ref.clone()).ok(),
|
||||
feature = "web_sys" => input_ref.dyn_ref::<InputElement>(),
|
||||
};
|
||||
cfg_match! {
|
||||
feature = "std_web" => input.unwrap().set_raw_value("User input"),
|
||||
feature = "web_sys" => input.unwrap().set_value("User input"),
|
||||
};
|
||||
let input = input_ref.dyn_ref::<InputElement>();
|
||||
input.unwrap().set_value("User input");
|
||||
|
||||
let ancestor = vtag;
|
||||
let mut elem = html! { <input value=expected /> };
|
||||
|
@ -913,16 +832,9 @@ mod tests {
|
|||
|
||||
// Get new current value of the input element
|
||||
let input_ref = vtag.reference.as_ref().unwrap();
|
||||
let input = cfg_match! {
|
||||
feature = "std_web" => InputElement::try_from(input_ref.clone()).ok(),
|
||||
feature = "web_sys" => input_ref.dyn_ref::<InputElement>(),
|
||||
}
|
||||
.unwrap();
|
||||
let input = input_ref.dyn_ref::<InputElement>().unwrap();
|
||||
|
||||
let current_value = cfg_match! {
|
||||
feature = "std_web" => input.raw_value(),
|
||||
feature = "web_sys" => input.value(),
|
||||
};
|
||||
let current_value = input.value();
|
||||
|
||||
// check whether not changed virtual dom value has been set to the input element
|
||||
assert_eq!(current_value, expected);
|
||||
|
@ -933,9 +845,6 @@ mod tests {
|
|||
let scope = test_scope();
|
||||
let parent = document().create_element("div").unwrap();
|
||||
|
||||
#[cfg(feature = "std_web")]
|
||||
document().body().unwrap().append_child(&parent);
|
||||
#[cfg(feature = "web_sys")]
|
||||
document().body().unwrap().append_child(&parent).unwrap();
|
||||
|
||||
// Initial state
|
||||
|
@ -949,14 +858,8 @@ mod tests {
|
|||
|
||||
// User input
|
||||
let input_ref = vtag.reference.as_ref().unwrap();
|
||||
let input = cfg_match! {
|
||||
feature = "std_web" => InputElement::try_from(input_ref.clone()).ok(),
|
||||
feature = "web_sys" => input_ref.dyn_ref::<InputElement>(),
|
||||
};
|
||||
cfg_match! {
|
||||
feature = "std_web" => input.unwrap().set_raw_value("User input"),
|
||||
feature = "web_sys" => input.unwrap().set_value("User input"),
|
||||
};
|
||||
let input = input_ref.dyn_ref::<InputElement>();
|
||||
input.unwrap().set_value("User input");
|
||||
|
||||
let ancestor = vtag;
|
||||
let mut elem = html! { <input /> };
|
||||
|
@ -972,16 +875,9 @@ mod tests {
|
|||
|
||||
// Get user value of the input element
|
||||
let input_ref = vtag.reference.as_ref().unwrap();
|
||||
let input = cfg_match! {
|
||||
feature = "std_web" => InputElement::try_from(input_ref.clone()).ok(),
|
||||
feature = "web_sys" => input_ref.dyn_ref::<InputElement>(),
|
||||
}
|
||||
.unwrap();
|
||||
let input = input_ref.dyn_ref::<InputElement>().unwrap();
|
||||
|
||||
let current_value = cfg_match! {
|
||||
feature = "std_web" => input.raw_value(),
|
||||
feature = "web_sys" => input.value(),
|
||||
};
|
||||
let current_value = input.value();
|
||||
|
||||
// check whether not changed virtual dom value has been set to the input element
|
||||
assert_eq!(current_value, "User input");
|
||||
|
@ -992,9 +888,6 @@ mod tests {
|
|||
let scope = test_scope();
|
||||
let parent = document().create_element("div").unwrap();
|
||||
|
||||
#[cfg(feature = "std_web")]
|
||||
document().body().unwrap().append_child(&parent);
|
||||
#[cfg(feature = "web_sys")]
|
||||
document().body().unwrap().append_child(&parent).unwrap();
|
||||
|
||||
let mut elem = html! { <@{
|
||||
|
@ -1008,7 +901,6 @@ mod tests {
|
|||
// make sure the new tag name is used internally
|
||||
assert_eq!(vtag.tag(), "a");
|
||||
|
||||
#[cfg(feature = "web_sys")]
|
||||
// Element.tagName is always in the canonical upper-case form.
|
||||
assert_eq!(vtag.reference.as_ref().unwrap().tag_name(), "A");
|
||||
}
|
||||
|
@ -1049,19 +941,13 @@ mod tests {
|
|||
let scope = test_scope();
|
||||
let parent = document().create_element("div").unwrap();
|
||||
|
||||
#[cfg(feature = "std_web")]
|
||||
document().body().unwrap().append_child(&parent);
|
||||
#[cfg(feature = "web_sys")]
|
||||
document().body().unwrap().append_child(&parent).unwrap();
|
||||
|
||||
let node_ref = NodeRef::default();
|
||||
let mut elem: VNode = html! { <div ref=node_ref.clone()></div> };
|
||||
assert_vtag(&mut elem);
|
||||
elem.apply(&scope, &parent, NodeRef::default(), None);
|
||||
let parent_node = cfg_match! {
|
||||
feature = "std_web" => parent.as_node(),
|
||||
feature = "web_sys" => parent.deref(),
|
||||
};
|
||||
let parent_node = parent.deref();
|
||||
assert_eq!(node_ref.get(), parent_node.first_child());
|
||||
elem.detach(&parent);
|
||||
assert!(node_ref.get().is_none());
|
||||
|
@ -1094,7 +980,7 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, feature = "web_sys"))]
|
||||
#[cfg(test)]
|
||||
mod layout_tests {
|
||||
extern crate self as yew;
|
||||
|
||||
|
|
|
@ -3,17 +3,10 @@
|
|||
use super::{VDiff, VNode};
|
||||
use crate::html::{AnyScope, NodeRef};
|
||||
use crate::utils::document;
|
||||
use cfg_if::cfg_if;
|
||||
use log::warn;
|
||||
use std::borrow::Cow;
|
||||
use std::cmp::PartialEq;
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "std_web")] {
|
||||
use stdweb::web::{Element, INode, TextNode};
|
||||
} else if #[cfg(feature = "web_sys")] {
|
||||
use web_sys::{Element, Text as TextNode};
|
||||
}
|
||||
}
|
||||
use web_sys::{Element, Text as TextNode};
|
||||
|
||||
/// A type for a virtual
|
||||
/// [`TextNode`](https://developer.mozilla.org/en-US/docs/Web/API/Document/createTextNode)
|
||||
|
@ -110,7 +103,7 @@ mod test {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, feature = "web_sys"))]
|
||||
#[cfg(test)]
|
||||
mod layout_tests {
|
||||
extern crate self as yew;
|
||||
|
||||
|
|
|
@ -9,8 +9,8 @@ repository = "https://github.com/yewstack/yew"
|
|||
readme = "README.md"
|
||||
|
||||
[features]
|
||||
default = ["web_sys", "stable"] # Only stable is included by default.
|
||||
all = ["web_sys", "stable", "experimental"]
|
||||
default = ["stable"] # Only stable is included by default.
|
||||
all = ["stable", "experimental"]
|
||||
|
||||
# Broad features
|
||||
## All features MUST be stable or experimental or soft_depricated
|
||||
|
@ -35,14 +35,6 @@ store = []
|
|||
lrc = []
|
||||
mrc_irc = []
|
||||
|
||||
std_web = [
|
||||
"yew/std_web",
|
||||
]
|
||||
web_sys = [
|
||||
"yew/web_sys",
|
||||
"web-sys"
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
log = "0.4.8"
|
||||
serde = {version= "1.0.102", optional = true}
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
//! Feature to enable fetching using web_sys-based fetch requests.
|
||||
//!
|
||||
//! This feature makes use of JSON for the request and response bodies.
|
||||
//!
|
||||
//! # Note
|
||||
//! Because this makes use of futures, enabling this feature will require the use of a
|
||||
//! web_sys compatible build environment and will prevent you from using `cargo web`.
|
||||
|
||||
use crate::NeqAssign; // requires "neq" feature.
|
||||
|
||||
|
|
|
@ -21,6 +21,9 @@
|
|||
"concepts/components/callbacks": {
|
||||
"title": "Callbacks"
|
||||
},
|
||||
"concepts/components/children": {
|
||||
"title": "Children"
|
||||
},
|
||||
"concepts/components/properties": {
|
||||
"title": "Properties"
|
||||
},
|
||||
|
@ -35,10 +38,10 @@
|
|||
"title": "#[function_component]"
|
||||
},
|
||||
"concepts/function-components/custom-hooks": {
|
||||
"title": "Custom hooks"
|
||||
"title": "Custom Hooks"
|
||||
},
|
||||
"concepts/function-components/pre-defined-hooks": {
|
||||
"title": "Pre-defined hooks"
|
||||
"title": "Pre-defined Hooks"
|
||||
},
|
||||
"concepts/html": {
|
||||
"title": "HTML",
|
||||
|
@ -75,9 +78,6 @@
|
|||
"getting-started/build-a-sample-app": {
|
||||
"title": "Build a sample app"
|
||||
},
|
||||
"getting-started/choose-web-library": {
|
||||
"title": "Choosing a web library"
|
||||
},
|
||||
"getting-started/examples": {
|
||||
"title": "Examples"
|
||||
},
|
||||
|
@ -85,9 +85,6 @@
|
|||
"title": "Project Setup",
|
||||
"sidebar_label": "Introduction"
|
||||
},
|
||||
"getting-started/project-setup/using-cargo-web": {
|
||||
"title": "Using cargo-web"
|
||||
},
|
||||
"getting-started/project-setup/using-trunk": {
|
||||
"title": "Using trunk"
|
||||
},
|
||||
|
|
|
@ -8,12 +8,10 @@
|
|||
"ids": [
|
||||
"getting-started/project-setup",
|
||||
"getting-started/project-setup/using-trunk",
|
||||
"getting-started/project-setup/using-wasm-pack",
|
||||
"getting-started/project-setup/using-cargo-web"
|
||||
"getting-started/project-setup/using-wasm-pack"
|
||||
]
|
||||
},
|
||||
"getting-started/build-a-sample-app",
|
||||
"getting-started/choose-web-library",
|
||||
"getting-started/examples",
|
||||
"getting-started/starter-templates"
|
||||
],
|
||||
|
|
Loading…
Reference in New Issue