diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index c4870ee..16c414f 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -5,7 +5,7 @@ on: branches: - main paths: - - sdk/** + - packages/** - examples/** pull_request: @@ -13,7 +13,7 @@ on: branches: - main paths: - - sdk/** + - packages/** - examples/** env: @@ -42,5 +42,5 @@ jobs: - run: rustup target add wasm32-unknown-unknown - run: rustup component add clippy - uses: actions/checkout@v3 - - run: cargo clippy --package dioxus-sdk --target wasm32-unknown-unknown --tests --features wasm-testing -- -D warnings - - run: cargo clippy --package dioxus-sdk --tests --features desktop-testing -- -D warnings + - run: cargo clippy --workspace --all-targets --exclude *-example --target wasm32-unknown-unknown --tests --all-features -- -D warnings + - run: cargo clippy --workspace --all-targets --tests --all-features -- -D warnings diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 052e890..0ca5b72 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -6,7 +6,7 @@ on: branches: - main paths: - - sdk/** + - packages/** - examples/** pull_request: @@ -14,7 +14,7 @@ on: branches: - main paths: - - sdk/** + - packages/** - examples/** env: @@ -29,7 +29,7 @@ jobs: - uses: actions/checkout@v3 - run: rustup target add wasm32-unknown-unknown - uses: Swatinem/rust-cache@v2 - - run: cargo build --package dioxus-sdk --verbose --target wasm32-unknown-unknown --no-default-features --features wasm-testing + - run: cargo build --all-targets --workspace --exclude *-example --verbose --target wasm32-unknown-unknown --all-features # need to run tests here desktop: @@ -39,5 +39,6 @@ jobs: steps: - uses: actions/checkout@v3 - uses: Swatinem/rust-cache@v2 - - run: cargo build --package dioxus-sdk --verbose --no-default-features --features desktop-testing - - run: cargo test --package dioxus-sdk --verbose --no-default-features --features desktop-testing + - run: cargo build --all-targets --workspace --verbose --all-features + - run: cargo test --all-targets --workspace --verbose --all-features + - run: cargo test --workspace --verbose --all-features --doc diff --git a/.vscode/settings.json b/.vscode/settings.json index 1b32f8e..872127f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,6 @@ { "rust-analyzer.cargo.features": "all", "rust-analyzer.check.features": "all", + "rust-analyzer.check.allTargets": true, + //"rust-analyzer.cargo.target": "wasm32-unknown-unknown", } \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index d88cbaf..a8fab64 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,55 @@ [workspace] -resolver = "2" -members = ["sdk", "examples/*"] +resolver = "3" +members = ["packages/*", "examples/*"] + +package.authors = [ + "Dioxus Labs", + "Jonathan Kelley", + "DogeDark", + "marc2332", + "ealmloff", +] +package.edition = "2024" +package.license = "MIT OR Apache-2.0" +package.homepage = "https://dioxuslabs.com" +package.repository = "https://github.com/DioxusLabs/sdk/" [workspace.dependencies] -dioxus-sdk = { path = "./sdk" } -dioxus = { version = "0.6.0" } -dioxus-desktop = { version = "0.6.0" } -dioxus-signals = { version = "0.6.0" } +# Workspace +dioxus-sdk = { path = "packages/sdk" } +dioxus-time = { path = "packages/time" } +dioxus-storage = { path = "packages/storage" } +dioxus-geolocation = { path = "packages/geolocation" } +dioxus-notification = { path = "packages/notification" } +dioxus-sync = { path = "packages/sync" } +dioxus-util = { path = "packages/util" } +dioxus-window = { path = "packages/window" } + +# Dioxus +dioxus = "0.6.0" +dioxus-signals = "0.6.0" +dioxus-desktop = "0.6.0" +dioxus-config-macro = "0.6.0" + +# Deps +cfg-if = "1.0.0" +tokio = "1.43.0" +futures = "0.3.31" +futures-util = "0.3.31" + +serde = "1.0.163" +wasm-bindgen = "0.2.100" +web-sys = "0.3.77" +js-sys = "0.3.77" + +[profile] + +[profile.wasm-dev] +inherits = "dev" +opt-level = 1 + +[profile.server-dev] +inherits = "dev" + +[profile.android-dev] +inherits = "dev" diff --git a/README.md b/README.md index dd0d3c3..18bd506 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

🧰 Dioxus Development Kit 🚀

-

A platform agnostic library for supercharging your productivity with Dioxus.

+

Cross-platform crates for supercharging your productivity with Dioxus.

@@ -23,23 +23,27 @@ ----- -

This library is still under development. Expect breaking changes!

+

These crates are still under development. Expect breaking changes!


-`dioxus-sdk` is a development kit for Dioxus that provides abstractions for your Dioxus app. Abstractions included are notifications, clipboard, geolocation and storage with more to come! +`dioxus-sdk` is a development kit for Dioxus that provides cross-platform APIs for your Dioxus app. SDK is organized into many different crates accessible through the `dioxus-sdk` crate with the corresponding feature flags. -**Features** -- [x] Geolocation - (Web, Windows) -- [x] Storage - (Web, Desktop) -- [x] Clipboard - (Desktop) -- [x] Notifications - (Desktop) -- [x] Color Scheme - (Web) -- [x] Utility Hooks - - [x] use_channel - - [x] use_window_size - - [x] use_interval - - [x] use_debounce - - [ ] use_timeout +## Features +- `dioxus-storage` +- `dioxus-geolocation` - Web & Windows +- `dioxus-notifications` - Desktop +- `dioxus-window` + - [x] Theme - (Web, Windows, Mac) + - [x] Window Size +- `dioxus-time` + - [x] Sleep + - [x] Intervals + - [x] Debounce + - [x] Timeouts +- `dioxus-sync` + - [x] Channels +- `dioxus-util` + - [x] `use_root_scroll` - [ ] Camera - [ ] WiFi - [ ] Bluetooth @@ -47,11 +51,14 @@ Geolocation example: ```rust -use dioxus_sdk::geolocation::{ +// dioxus-geolocation= { version = "*" } +use dioxus::prelude::*; +use dioxus_geolocation::{ init_geolocator, use_geolocation, PowerMode }; -fn app() -> Element { +#[component] +fn App() -> Element { let geolocator = init_geolocator(PowerMode::High).unwrap(); let coords = use_geolocation(); @@ -69,22 +76,25 @@ fn app() -> Element { } ``` -## Platform Support -### Clipboard - -On linux you need the x11 library to use the clipboard abstraction: -``` -sudo apt-get install xorg-dev -``` - ## Usage You can add `dioxus-sdk` to your application by adding it to your dependencies. ```toml [dependencies] -dioxus-sdk = { version = "0.6", features = [] } +dioxus-sdk = { version = "0.7", features = [] } ``` +### Dioxus Compatibility +This table represents the compatibility between this crate and Dioxus versions. +The crate version supports a Dioxus version up until the next crate version in the table. + +E.g. if crate version `0.1` supported Dioxus `0.6` and crate version `0.4` supported Dioxus `0.7`, crate versions `0.1`, `0.2`, and `0.3` would support Dioxus `0.6`. + +| Crate Version | Dioxus Version | +| ------------- | -------------- | +| 0.7 | 0.6 | +| 0.5 | 0.5 | + ## License This project is dual licensed under the [MIT](./LICENSE-MIT) and [Apache 2.0](./LICENSE-APACHE) licenses. -Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in `dioxus-sdk` by you, shall be licensed as MIT or Apache 2.0, without any additional terms or conditions. +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in `dioxus-sdk` or any of it's crates, by you, shall be licensed as MIT or Apache 2.0, without any additional terms or conditions. diff --git a/examples/README.md b/examples/README.md deleted file mode 100644 index 6ac7d1c..0000000 --- a/examples/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# Examples - -### [`system_theme`](./system_theme/) -Learn how to use `use_system_theme`. - -### [`geolocation`](./geolocation/) -Learn how to use the `geolocation` abstraction. - -### [`channel`](./channel/) -Learn how to use the `channel` abstraction. - -### [`storage`](./storage/) -Learn how to use the `storage` abstraction. - -### [`clipboard`](./clipboard/) -Learn how to use the `clipboard` abstraction. diff --git a/examples/channel/Cargo.toml b/examples/channel/Cargo.toml index 6a9b7bf..c0b4e79 100644 --- a/examples/channel/Cargo.toml +++ b/examples/channel/Cargo.toml @@ -1,15 +1,13 @@ [package] -name = "channel" +name = "channel-example" version = "0.1.0" edition = "2021" [dependencies] -dioxus-sdk = { workspace = true, features = ["channel"] } -dioxus = { workspace = true, features = ["web"] } +dioxus = { workspace = true } +dioxus-sync = { workspace = true } - -log = "0.4.6" - -# WebAssembly Debug -wasm-logger = "0.2.0" -console_error_panic_hook = "0.1.7" +[features] +default = ["desktop"] +web = ["dioxus/web"] +desktop = ["dioxus/desktop"] \ No newline at end of file diff --git a/examples/channel/Dioxus.toml b/examples/channel/Dioxus.toml deleted file mode 100644 index 84136b0..0000000 --- a/examples/channel/Dioxus.toml +++ /dev/null @@ -1,42 +0,0 @@ -[application] - -# App (Project) Name -name = "channel" - -# Dioxus App Default Platform -# desktop, web, mobile, ssr -default_platform = "web" - -# `build` & `serve` dist path -out_dir = "dist" - -# resource (public) file folder -asset_dir = "public" - -[web.app] - -# HTML title tag content -title = "dioxus | ⛺" - -[web.watcher] - -# when watcher trigger, regenerate the `index.html` -reload_html = true - -# which files or dirs will be watcher monitoring -watch_path = ["src", "public"] - -# include `assets` in web platform -[web.resource] - -# CSS style file -style = [] - -# Javascript code file -script = [] - -[web.resource.dev] - -# Javascript code file -# serve: [dev-server] only -script = [] diff --git a/examples/channel/README.md b/examples/channel/README.md index c427972..4b44b2c 100644 --- a/examples/channel/README.md +++ b/examples/channel/README.md @@ -1,7 +1,7 @@ # use_channel -Learn how to use `use_channel`. +Learn how to use `use_channel` from `dioxus-sync`. Run: -```dioxus serve``` \ No newline at end of file +```dx serve``` \ No newline at end of file diff --git a/examples/channel/public/favicon.ico b/examples/channel/public/favicon.ico deleted file mode 100644 index eed0c09..0000000 Binary files a/examples/channel/public/favicon.ico and /dev/null differ diff --git a/examples/channel/src/main.rs b/examples/channel/src/main.rs index 9be318d..1d5f2fd 100644 --- a/examples/channel/src/main.rs +++ b/examples/channel/src/main.rs @@ -1,21 +1,18 @@ -use dioxus::prelude::*; -use dioxus_sdk::utils::channel::{use_channel, use_listen_channel}; +use dioxus::{logger::tracing::info, prelude::*}; +use dioxus_sync::channel::{use_channel, use_listen_channel}; fn main() { - // init debug tool for WebAssembly - wasm_logger::init(wasm_logger::Config::default()); - console_error_panic_hook::set_once(); - - launch(app); + launch(App); } -fn app() -> Element { +#[component] +fn App() -> Element { let channel = use_channel::(5); use_listen_channel(&channel, |message| async { match message { - Ok(value) => log::info!("Incoming message: {value}"), - Err(err) => log::info!("Error: {err:?}"), + Ok(value) => info!("Incoming message: {value}"), + Err(err) => info!("Error: {err:?}"), } }); diff --git a/examples/clipboard/Cargo.toml b/examples/clipboard/Cargo.toml deleted file mode 100644 index 91530c1..0000000 --- a/examples/clipboard/Cargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[package] -name = "clipboard" -version = "0.1.0" -edition = "2021" - -[dependencies] -dioxus-sdk = { workspace = true, features = ["clipboard"] } -dioxus = { workspace = true, features = ["desktop"] } diff --git a/examples/clipboard/src/main.rs b/examples/clipboard/src/main.rs deleted file mode 100644 index 5e06527..0000000 --- a/examples/clipboard/src/main.rs +++ /dev/null @@ -1,43 +0,0 @@ -use dioxus::prelude::*; -use dioxus_sdk::clipboard::use_clipboard; - -fn main() { - launch(app); -} - -fn app() -> Element { - let mut clipboard = use_clipboard(); - let mut text = use_signal(String::new); - - let oninput = move |e: FormEvent| { - text.set(e.data.value()); - }; - - let oncopy = move |_| match clipboard.set(text.read().clone()) { - Ok(_) => println!("Copied to clipboard: {}", text.read()), - Err(err) => println!("Error on copy: {err:?}"), - }; - - let onpaste = move |_| match clipboard.get() { - Ok(contents) => { - println!("Pasted from clipboard: {contents}"); - text.set(contents); - } - Err(err) => println!("Error on paste: {err:?}"), - }; - - rsx!( - input { - oninput: oninput, - value: "{text}" - } - button { - onclick: oncopy, - "Copy" - } - button { - onclick: onpaste, - "Paste" - } - ) -} diff --git a/examples/geolocation/Cargo.toml b/examples/geolocation/Cargo.toml index 53c9f69..ad06396 100644 --- a/examples/geolocation/Cargo.toml +++ b/examples/geolocation/Cargo.toml @@ -1,10 +1,14 @@ [package] -name = "geolocation" +name = "geolocation-example" version = "0.1.0" edition = "2021" [dependencies] -dioxus-sdk = { workspace = true, features = ["geolocation"] } -# You can change from 'desktop' to 'web' as well -dioxus = { workspace = true, features = ["desktop"] } +dioxus = { workspace = true } +dioxus-geolocation = { workspace = true } + +[features] +default = ["desktop"] +web = ["dioxus/web"] +desktop = ["dioxus/desktop"] diff --git a/examples/geolocation/Dioxus.toml b/examples/geolocation/Dioxus.toml deleted file mode 100644 index 749b197..0000000 --- a/examples/geolocation/Dioxus.toml +++ /dev/null @@ -1,42 +0,0 @@ -[application] - -# App (Project) Name -name = "geolocation" - -# Dioxus App Default Platform -# desktop, web, mobile, ssr -default_platform = "web" - -# `build` & `serve` dist path -out_dir = "dist" - -# resource (public) file folder -asset_dir = "public" - -[web.app] - -# HTML title tag content -title = "dioxus | ⛺" - -[web.watcher] - -# when watcher trigger, regenerate the `index.html` -reload_html = true - -# which files or dirs will be watcher monitoring -watch_path = ["src", "public"] - -# include `assets` in web platform -[web.resource] - -# CSS style file -style = [] - -# Javascript code file -script = [] - -[web.resource.dev] - -# Javascript code file -# serve: [dev-server] only -script = [] diff --git a/examples/geolocation/LICENSE b/examples/geolocation/LICENSE deleted file mode 100644 index bcdd828..0000000 --- a/examples/geolocation/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2022 Dioxus - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/examples/geolocation/index.html b/examples/geolocation/index.html deleted file mode 100644 index f57330c..0000000 --- a/examples/geolocation/index.html +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/examples/geolocation/public/favicon.ico b/examples/geolocation/public/favicon.ico deleted file mode 100644 index eed0c09..0000000 Binary files a/examples/geolocation/public/favicon.ico and /dev/null differ diff --git a/examples/geolocation/src/main.rs b/examples/geolocation/src/main.rs index 8547f2f..be27cc1 100644 --- a/examples/geolocation/src/main.rs +++ b/examples/geolocation/src/main.rs @@ -1,11 +1,12 @@ use dioxus::prelude::*; -use dioxus_sdk::geolocation::{init_geolocator, use_geolocation, PowerMode}; +use dioxus_geolocation::{init_geolocator, use_geolocation, PowerMode}; fn main() { - launch(app); + launch(App); } -fn app() -> Element { +#[component] +fn App() -> Element { let geolocator = init_geolocator(PowerMode::High); let initial_coords = use_resource(move || async move { geolocator diff --git a/examples/scroll/Cargo.toml b/examples/scroll/Cargo.toml index 6d1af22..79d4819 100644 --- a/examples/scroll/Cargo.toml +++ b/examples/scroll/Cargo.toml @@ -1,11 +1,13 @@ [package] -name = "use_scroll" +name = "use-scroll-example" version = "0.1.0" edition = "2021" [dependencies] -dioxus-sdk = { workspace = true, features = ["scroll"] } -dioxus = { workspace = true } +dioxus-util.workspace = true +dioxus.workspace = true [features] +default = ["desktop"] web = ["dioxus/web"] +desktop = ["dioxus/desktop"] \ No newline at end of file diff --git a/examples/scroll/README.md b/examples/scroll/README.md index f68a3f3..4d9bfa6 100644 --- a/examples/scroll/README.md +++ b/examples/scroll/README.md @@ -6,4 +6,9 @@ Learn how to use `use_root_scroll`. ### Run **Web** -```dioxus serve --platform web``` \ No newline at end of file + +```dx serve --platform web``` + +**Desktop** + +```dx serve --platform desktop``` \ No newline at end of file diff --git a/examples/scroll/public/favicon.ico b/examples/scroll/public/favicon.ico deleted file mode 100644 index eed0c09..0000000 Binary files a/examples/scroll/public/favicon.ico and /dev/null differ diff --git a/examples/scroll/src/main.rs b/examples/scroll/src/main.rs index 46c1ba9..3e6b58e 100644 --- a/examples/scroll/src/main.rs +++ b/examples/scroll/src/main.rs @@ -2,7 +2,7 @@ use dioxus::{ logger::tracing::{info, Level}, prelude::*, }; -use dioxus_sdk::utils::scroll::use_root_scroll; +use dioxus_util::scroll::use_root_scroll; fn main() { dioxus::logger::init(Level::TRACE).unwrap(); diff --git a/examples/storage/Cargo.toml b/examples/storage/Cargo.toml index ddd257d..83e8ea6 100644 --- a/examples/storage/Cargo.toml +++ b/examples/storage/Cargo.toml @@ -1,13 +1,14 @@ [package] -name = "storage-desktop" +name = "storage-example" version = "0.1.0" edition = "2021" [dependencies] -dioxus-sdk = { workspace = true, features = ["storage"] } dioxus = { workspace = true, features = ["router"] } +dioxus-storage = { workspace = true } [features] +default = ["desktop"] web = ["dioxus/web"] desktop = ["dioxus/desktop"] fullstack = ["dioxus/fullstack"] diff --git a/examples/storage/src/main.rs b/examples/storage/src/main.rs index 45d9dbe..de1af7a 100644 --- a/examples/storage/src/main.rs +++ b/examples/storage/src/main.rs @@ -1,12 +1,13 @@ use dioxus::prelude::*; -use dioxus_sdk::storage::*; +use dioxus_storage::*; fn main() { - dioxus_sdk::storage::set_dir!(); - launch(app); + dioxus_storage::set_dir!(); + launch(App); } -fn app() -> Element { +#[component] +fn App() -> Element { rsx! { Router:: {} } @@ -32,7 +33,7 @@ fn Footer() -> Element { div { button { onclick: move |_| { - let dom = VirtualDom::new(app); + let dom = VirtualDom::new(App); window.new_window(dom, Default::default()); }, "New Window" diff --git a/examples/system_theme/README.md b/examples/system_theme/README.md deleted file mode 100644 index f22f9e4..0000000 --- a/examples/system_theme/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# color_scheme - -Learn how to use `use_preferred_color_scheme`. - -Run: - -```dioxus serve``` \ No newline at end of file diff --git a/examples/system_theme/public/favicon.ico b/examples/system_theme/public/favicon.ico deleted file mode 100644 index eed0c09..0000000 Binary files a/examples/system_theme/public/favicon.ico and /dev/null differ diff --git a/examples/system_theme/Cargo.toml b/examples/theme/Cargo.toml similarity index 64% rename from examples/system_theme/Cargo.toml rename to examples/theme/Cargo.toml index 4fbb6c7..d61a79d 100644 --- a/examples/system_theme/Cargo.toml +++ b/examples/theme/Cargo.toml @@ -1,12 +1,13 @@ [package] -name = "color_scheme" +name = "theme-example" version = "0.1.0" edition = "2021" [dependencies] -dioxus-sdk = { workspace = true, features = ["system_theme"] } dioxus = { workspace = true } +dioxus-window = { workspace = true } [features] +default = ["desktop"] web = ["dioxus/web"] desktop = ["dioxus/desktop"] \ No newline at end of file diff --git a/examples/theme/README.md b/examples/theme/README.md new file mode 100644 index 0000000..68c5386 --- /dev/null +++ b/examples/theme/README.md @@ -0,0 +1,7 @@ +# color_scheme + +Learn how to use the `use_system_theme` hook. + +Run: + +```dx serve``` \ No newline at end of file diff --git a/examples/system_theme/src/main.rs b/examples/theme/src/main.rs similarity index 95% rename from examples/system_theme/src/main.rs rename to examples/theme/src/main.rs index d86b75a..cc7f856 100644 --- a/examples/system_theme/src/main.rs +++ b/examples/theme/src/main.rs @@ -1,5 +1,5 @@ use dioxus::prelude::*; -use dioxus_sdk::theme::use_system_theme; +use dioxus_window::theme::use_system_theme; fn main() { launch(App); diff --git a/examples/timing/Cargo.toml b/examples/timing/Cargo.toml index edf3d64..fa3f98e 100644 --- a/examples/timing/Cargo.toml +++ b/examples/timing/Cargo.toml @@ -1,13 +1,14 @@ [package] -name = "timing" +name = "timing-example" version = "0.1.0" edition = "2021" [dependencies] -dioxus-sdk = { workspace = true, features = ["timing"] } dioxus = { workspace = true } -dioxus-logger = "0.5.1" +dioxus-time = { workspace = true } +gloo-timers = { version = "0.3", features = ["futures"] } [features] +default = ["desktop"] web = ["dioxus/web"] desktop = ["dioxus/desktop"] diff --git a/examples/timing/Dioxus.toml b/examples/timing/Dioxus.toml deleted file mode 100644 index 5a45f78..0000000 --- a/examples/timing/Dioxus.toml +++ /dev/null @@ -1,42 +0,0 @@ -[application] - -# App (Project) Name -name = "interval" - -# Dioxus App Default Platform -# desktop, web, mobile, ssr -default_platform = "web" - -# `build` & `serve` dist path -out_dir = "dist" - -# resource (public) file folder -asset_dir = "public" - -[web.app] - -# HTML title tag content -title = "dioxus | ⛺" - -[web.watcher] - -# when watcher trigger, regenerate the `index.html` -reload_html = true - -# which files or dirs will be watcher monitoring -watch_path = ["src", "public"] - -# include `assets` in web platform -[web.resource] - -# CSS style file -style = [] - -# Javascript code file -script = [] - -[web.resource.dev] - -# Javascript code file -# serve: [dev-server] only -script = [] diff --git a/examples/timing/README.md b/examples/timing/README.md index 0d2fe46..67bd269 100644 --- a/examples/timing/README.md +++ b/examples/timing/README.md @@ -1,7 +1,7 @@ -# use_interval +# dioxus-time example -Learn how to use `use_interval`. +Learn how to use the `use_interval` and `use_debounce` hooks in `dioxus-time`. Run: -```dioxus serve``` \ No newline at end of file +```dx serve``` \ No newline at end of file diff --git a/examples/timing/public/favicon.ico b/examples/timing/public/favicon.ico deleted file mode 100644 index eed0c09..0000000 Binary files a/examples/timing/public/favicon.ico and /dev/null differ diff --git a/examples/timing/src/main.rs b/examples/timing/src/main.rs index 2b680fa..303e0a0 100644 --- a/examples/timing/src/main.rs +++ b/examples/timing/src/main.rs @@ -1,27 +1,32 @@ -use dioxus::prelude::*; -use dioxus_logger::tracing::{info, Level}; -use dioxus_sdk::utils::timing::{use_debounce, use_interval}; +use dioxus::{logger::tracing::info, prelude::*}; +use dioxus_time::{use_debounce, use_interval, use_timeout}; use std::time::Duration; fn main() { - dioxus_logger::init(Level::INFO).expect("logger failed to init"); - launch(app); + launch(App); } -fn app() -> Element { +#[component] +fn App() -> Element { let mut count = use_signal(|| 0); // using `use_interval`, we increment the count by 1 every second. - use_interval(Duration::from_secs(1), move || { + use_interval(Duration::from_secs(1), move |()| { count += 1; }); // using `use_debounce`, we reset the counter after 2 seconds since the last button click. - let mut debounce = use_debounce(Duration::from_millis(2000), move |text| { + let mut debounce = use_debounce(Duration::from_secs(2), move |text| { info!("{text}"); count.set(0); }); + // using `use_timeout`, we increase a the counter 2 seconds after every trigger. + let mut timeout_count = use_signal(|| 0); + let timeout = use_timeout(Duration::from_secs(2), move |()| { + timeout_count += 1; + }); + rsx! { p { "{count}" }, button { @@ -31,5 +36,9 @@ fn app() -> Element { }, "Reset the counter! (2 second debounce)" } + button { + onclick: move |_| { timeout.action(()); }, + "Trigger Timeout: {timeout_count}", + } } } diff --git a/examples/window_size/Cargo.toml b/examples/window_size/Cargo.toml index fb13980..4ba3a3e 100644 --- a/examples/window_size/Cargo.toml +++ b/examples/window_size/Cargo.toml @@ -1,12 +1,13 @@ [package] -name = "use_window_size" +name = "window-size-example" version = "0.1.0" edition = "2021" [dependencies] -dioxus-sdk = { workspace = true, features = ["window_size"] } dioxus = { workspace = true } +dioxus-window = { workspace = true } [features] +default = ["desktop"] web = ["dioxus/web"] desktop = ["dioxus/desktop"] diff --git a/examples/window_size/README.md b/examples/window_size/README.md index 71a1159..203284b 100644 --- a/examples/window_size/README.md +++ b/examples/window_size/README.md @@ -6,7 +6,7 @@ Learn how to use `use_window_size`. ### Run **Desktop** -```dioxus serve --platform desktop``` +```dx serve --platform desktop``` **Web** -```dioxus serve --platform web``` \ No newline at end of file +```dx serve --platform web``` \ No newline at end of file diff --git a/examples/window_size/public/favicon.ico b/examples/window_size/public/favicon.ico deleted file mode 100644 index eed0c09..0000000 Binary files a/examples/window_size/public/favicon.ico and /dev/null differ diff --git a/examples/window_size/src/main.rs b/examples/window_size/src/main.rs index 76602e8..16854cb 100644 --- a/examples/window_size/src/main.rs +++ b/examples/window_size/src/main.rs @@ -1,5 +1,5 @@ use dioxus::prelude::*; -use dioxus_sdk::utils::window::{get_window_size, use_window_size}; +use dioxus_window::size::{get_window_size, use_window_size}; fn main() { launch(App); @@ -7,8 +7,10 @@ fn main() { #[component] fn App() -> Element { - let initial_size = use_signal(get_window_size); + let initial_size = use_signal(|| get_window_size().unwrap()); + let window_size = use_window_size(); + let window_size = window_size().unwrap(); rsx!( div { @@ -19,8 +21,8 @@ fn App() -> Element { p { "Height: {initial_size().height}" } h3 { "Current Size" } - p { "Width: {window_size().width}" } - p { "Height: {window_size().height}" } + p { "Width: {window_size.width}" } + p { "Height: {window_size.height}" } } ) } diff --git a/packages/geolocation/Cargo.toml b/packages/geolocation/Cargo.toml new file mode 100644 index 0000000..b93b4b0 --- /dev/null +++ b/packages/geolocation/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "dioxus-geolocation" +version = "0.1.0-alpha.1" + +description = "Geolocation utilities and hooks for Dioxus." +readme = "./README.md" +keywords = ["gui", "dioxus", "hooks"] +categories = ["gui", "wasm"] + +edition.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +dioxus = { workspace = true } +cfg-if = { workspace = true } +futures = { workspace = true } +futures-util = { workspace = true } + +[target.'cfg(target_family = "wasm")'.dependencies] +wasm-bindgen = { workspace = true } +js-sys = { workspace = true } +web-sys = { workspace = true, features = [ + "Window", + "Navigator", + "Geolocation", + "PositionOptions", +] } + +[target.'cfg(windows)'.dependencies] +windows = { version = "0.48.0", features = [ + "Foundation", + "Devices_Geolocation", +] } diff --git a/packages/geolocation/README.md b/packages/geolocation/README.md new file mode 100644 index 0000000..34c3d0a --- /dev/null +++ b/packages/geolocation/README.md @@ -0,0 +1,53 @@ +# Dioxus Geolocation +Geolocation utilities and hooks for Dioxus. + +### Supports +- [x] Web +- [x] Windows +- [ ] Mac +- [ ] Linux +- [ ] Android +- [ ] iOs + +## Usage +Add `dioxus-geolocation` to your `Cargo.toml`: +```toml +[dependencies] +dioxus-geolocation = "0.1" +``` + +Example: +```rs +use dioxus::prelude::*; +use dioxus_geolocation::{ + init_geolocator, use_geolocation, PowerMode +}; + +#[component] +fn App() -> Element { + let geolocator = init_geolocator(PowerMode::High).unwrap(); + let coords = use_geolocation(); + + match coords { + Ok(coords) => { + rsx!( p { "Latitude: {coords.latitude} | Longitude: {coords.longitude}" } ) + } + Err(Error::NotInitialized) => { + rsx!( p { "Initializing..." } ) + } + Err(e) => { + rsx!( p { "An error occurred {e}" } ) + } + } +} +``` + +### Dioxus Compatibility +This table represents the compatibility between this crate and Dioxus versions. +The crate version supports a Dioxus version up until the next crate version in the table. + +E.g. if crate version `0.1` supported Dioxus `0.6` and crate version `0.4` supported Dioxus `0.7`, crate versions `0.1`, `0.2`, and `0.3` would support Dioxus `0.6`. + +| Crate Version | Dioxus Version | +| ------------- | -------------- | +| 0.1 | 0.6 | \ No newline at end of file diff --git a/sdk/src/geolocation/core.rs b/packages/geolocation/src/core.rs similarity index 100% rename from sdk/src/geolocation/core.rs rename to packages/geolocation/src/core.rs diff --git a/sdk/src/geolocation/mod.rs b/packages/geolocation/src/lib.rs similarity index 100% rename from sdk/src/geolocation/mod.rs rename to packages/geolocation/src/lib.rs diff --git a/sdk/src/geolocation/platform/mod.rs b/packages/geolocation/src/platform/mod.rs similarity index 100% rename from sdk/src/geolocation/platform/mod.rs rename to packages/geolocation/src/platform/mod.rs diff --git a/sdk/src/geolocation/platform/wasm.rs b/packages/geolocation/src/platform/wasm.rs similarity index 97% rename from sdk/src/geolocation/platform/wasm.rs rename to packages/geolocation/src/platform/wasm.rs index ba2c233..e282ef9 100644 --- a/sdk/src/geolocation/platform/wasm.rs +++ b/packages/geolocation/src/platform/wasm.rs @@ -1,10 +1,10 @@ use futures::channel::mpsc; use futures_util::StreamExt; use std::sync::Arc; -use wasm_bindgen::{prelude::Closure, JsCast, JsValue}; +use wasm_bindgen::{JsCast, JsValue, prelude::Closure}; use web_sys::PositionOptions; -use crate::geolocation::{Error, Event, Geocoordinates, PowerMode}; +use crate::{Error, Event, Geocoordinates, PowerMode}; /// Represents the HAL's geolocator. pub struct Geolocator { diff --git a/sdk/src/geolocation/platform/windows.rs b/packages/geolocation/src/platform/windows.rs similarity index 98% rename from sdk/src/geolocation/platform/windows.rs rename to packages/geolocation/src/platform/windows.rs index a218635..830d2e4 100644 --- a/sdk/src/geolocation/platform/windows.rs +++ b/packages/geolocation/src/platform/windows.rs @@ -8,7 +8,7 @@ use windows::{ Foundation::TypedEventHandler, }; -use crate::geolocation::core::{Error, Event, Geocoordinates, PowerMode, Status}; +use crate::core::{Error, Event, Geocoordinates, PowerMode, Status}; /// Represents the HAL's geolocator. pub struct Geolocator { diff --git a/sdk/src/geolocation/use_geolocation.rs b/packages/geolocation/src/use_geolocation.rs similarity index 93% rename from sdk/src/geolocation/use_geolocation.rs rename to packages/geolocation/src/use_geolocation.rs index 7d48c4e..33f0ab9 100644 --- a/sdk/src/geolocation/use_geolocation.rs +++ b/packages/geolocation/src/use_geolocation.rs @@ -3,8 +3,8 @@ use super::core::{Error, Event, Geocoordinates, Geolocator, PowerMode, Status}; use dioxus::{ prelude::{ - provide_context, try_consume_context, use_coroutine, use_hook, use_signal, ReadOnlySignal, - Signal, UnboundedReceiver, + ReadOnlySignal, Signal, UnboundedReceiver, provide_context, try_consume_context, + use_coroutine, use_hook, use_signal, }, signals::{Readable, Writable}, }; diff --git a/packages/notification/Cargo.toml b/packages/notification/Cargo.toml new file mode 100644 index 0000000..74753b3 --- /dev/null +++ b/packages/notification/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "dioxus-notification" +version = "0.1.0-alpha.1" + +description = "Send notifications from your Dioxus apps." +readme = "./README.md" +keywords = ["gui", "dioxus", "hooks"] +categories = ["gui", "wasm"] + +edition.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +cfg-if.workspace = true + +[target.'cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))'.dependencies] +notify-rust = "4.11.5" diff --git a/packages/notification/README.md b/packages/notification/README.md new file mode 100644 index 0000000..f07f120 --- /dev/null +++ b/packages/notification/README.md @@ -0,0 +1,40 @@ +# Dioxus Notification +Send notifications from your Dioxus apps. + +### Supports +- [x] Windows +- [x] Mac +- [x] Linux +- [ ] Android +- [ ] iOs + +## Usage +Add `dioxus-notification` to your `Cargo.toml`: +```toml +[dependencies] +dioxus-notification = "0.1" +``` + +Example: +```rs +use dioxus_notification::Notification; + +Notification::new() + .app_name("dioxus test".to_string()) + .summary("hi, this is dioxus test".to_string()) + .body("lorem ipsum".to_string()) + .show() + .unwrap(); +``` + + + +### Dioxus Compatibility +This table represents the compatibility between this crate and Dioxus versions. +The crate version supports a Dioxus version up until the next crate version in the table. + +E.g. if crate version `0.1` supported Dioxus `0.6` and crate version `0.4` supported Dioxus `0.7`, crate versions `0.1`, `0.2`, and `0.3` would support Dioxus `0.6`. + +| Crate Version | Dioxus Version | +| ------------- | -------------- | +| 0.1 | 0.6 | \ No newline at end of file diff --git a/packages/notification/src/lib.rs b/packages/notification/src/lib.rs new file mode 100644 index 0000000..84d9156 --- /dev/null +++ b/packages/notification/src/lib.rs @@ -0,0 +1,164 @@ +//! Send desktop notifications. +//! +//! This crate only supports desktop targets (Windows, MacOS, & Linux). +#![deny(missing_docs)] + +use std::{ + error::Error, + fmt::{self, Display}, + path::{Path, PathBuf}, +}; + +/// Provides a builder API and contains relevant notification info. +/// +/// # Examples +/// +/// ``` +/// use dioxus_notification::Notification; +/// +/// Notification::new() +/// .app_name("dioxus test".to_string()) +/// .summary("hi, this is dioxus test".to_string()) +/// .body("lorem ipsum??".to_string()) +/// .show() +/// .unwrap(); +/// +/// ``` +#[derive(Debug, Clone, Default)] +pub struct Notification { + app_name: String, + summary: String, + body: String, + icon_path: PathBuf, + timeout: NotificationTimeout, +} + +/// Represents the notification's timeout. +#[derive(Debug, PartialEq, Clone, Default)] +pub enum NotificationTimeout { + /// Default depends on the target OS. + #[default] + Default, + /// A notification that has to be manually acknowledged. + Never, + /// A notification that times out after a duration. + Duration(std::time::Duration), +} + +cfg_if::cfg_if! { + if #[cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))] { + use notify_rust::Timeout; + impl From for Timeout { + fn from(value: NotificationTimeout) -> Self { + match value { + NotificationTimeout::Default => Timeout::Default, + NotificationTimeout::Never => Timeout::Never, + NotificationTimeout::Duration(dur) => Timeout::Milliseconds(dur.as_millis().try_into().unwrap()), + } + } + } + } +} + +impl Notification { + /// Creates a new notification with empty/default values. + pub fn new() -> Self { + Self::default() + } + + /// Show the final notification. + pub fn show(&self) -> Result<(), NotificationError> { + self.show_inner() + } + + // Unsupported fallback. + #[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))] + fn show_inner(&self) -> Result<(), NotificationError> { + Err(NotificationError::Unsupported) + } + + // notify_rust implementation supporting windows, mac, and linux. + #[cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))] + fn show_inner(&self) -> Result<(), NotificationError> { + let icon_path = + self.icon_path + .as_os_str() + .to_str() + .ok_or(NotificationError::FailedToShow( + "failed to convert icon path into str".into(), + ))?; + + notify_rust::Notification::new() + .appname(&self.app_name) + .summary(&self.summary) + .body(&self.body) + .icon(icon_path) + .timeout(self.timeout.clone()) + .show() + .map_err(|e| NotificationError::FailedToShow(e.into()))?; + + Ok(()) + } + + /// Set the application's name for the notification. + pub fn app_name(&mut self, value: impl ToString) -> &mut Self { + self.app_name = value.to_string(); + self + } + + /// Set the summary content of the notification. + pub fn summary(&mut self, value: impl ToString) -> &mut Self { + self.summary = value.to_string(); + self + } + + /// Set the body content of the notification. + pub fn body(&mut self, value: impl ToString) -> &mut Self { + self.body = value.to_string(); + self + } + + /// Set full path to image. + /// + /// Not supported on MacOS. + pub fn icon_path(&mut self, value: impl AsRef) -> &mut Self { + self.icon_path = value.as_ref().to_path_buf(); + self + } + + /// Set a timeout for when the notification should hide. + pub fn timeout(&mut self, value: NotificationTimeout) -> &mut Self { + self.timeout = value; + self + } +} + +#[test] +#[cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))] +fn test_notification() { + Notification::new() + .app_name("dioxus test".to_string()) + .summary("hi, this is dioxus test".to_string()) + .body("lorem ipsum??".to_string()) + .show() + .unwrap(); +} + +/// Represents errors when utilizing the notification abstraction. +#[derive(Debug)] +pub enum NotificationError { + /// Notification is unsupported on this platform. + Unsupported, + /// Failure to show a notification. + FailedToShow(Box), +} + +impl Error for NotificationError {} +impl Display for NotificationError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Unsupported => write!(f, "notification is not supported on this platform"), + Self::FailedToShow(err) => write!(f, "failed to show notification: {err}"), + } + } +} diff --git a/packages/sdk/Cargo.toml b/packages/sdk/Cargo.toml new file mode 100644 index 0000000..d583a11 --- /dev/null +++ b/packages/sdk/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "dioxus-sdk" +version = "0.7.0-alpha.1" + +description = "A platform agnostic library for supercharging your productivity with Dioxus." +readme = "../../README.md" +keywords = ["gui", "dioxus", "hooks"] +categories = ["gui", "wasm"] + +edition.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +dioxus-geolocation = { workspace = true, optional = true } +dioxus-notification = { workspace = true, optional = true } +dioxus-storage = { workspace = true, optional = true } +dioxus-sync = { workspace = true, optional = true } +dioxus-time = { workspace = true, optional = true } +dioxus-util = { workspace = true, optional = true } +dioxus-window = { workspace = true, optional = true } + +[features] +geolocation = ["dep:dioxus-geolocation"] +notification = ["dep:dioxus-notification"] +storage = ["dep:dioxus-storage"] +sync = ["dep:dioxus-sync"] +time = ["dep:dioxus-time"] +util = ["dep:dioxus-util"] +window = ["dep:dioxus-window"] + +[package.metadata.docs.rs] +all-features = true \ No newline at end of file diff --git a/packages/sdk/src/lib.rs b/packages/sdk/src/lib.rs new file mode 100644 index 0000000..7b35479 --- /dev/null +++ b/packages/sdk/src/lib.rs @@ -0,0 +1,51 @@ +//! # Dioxus SDK +//! The Dioxus SDK is a group of platform agnostic crates for common apis and functionality. +//! +//! This crate, `dioxus-sdk`, acts as an entrypoint to explore the variety of crates in the SDK ecosystem. +//! Individual crates from the SDK ecosystem can be used directly from `crates.io` or you can enable the +//! corresponding feature for a crate here. +//! +//! SDK is growing, and not all functionality supports every platform. Platform support will be documented in +//! each crate, and in the majority of cases a runtime `Err(Unsupported)` will be returned if you target an unsupported platform. +//! +//! ## Available Crates +//! Below is a table of the crates in our ecosystem, a short description, and their corresponding feature flag. +//! +//! | Crate | Description | Feature | +//! | ------------------------- | ------------------------------------- | ----------------- | +//! | [`dioxus-geolocation`] | Access user location services. | `geolocation` | +//! | [`dioxus-storage`] | Store local and persistent data. | `storage` | +//! | [`dioxus-time`] | Common timing utilities. | `time` | +//! | [`dioxus-window`] | Common window utilities. | `window` | +//! | [`dioxus-notification`] | Send notifications. | `notification` | +//! | [`dioxus-sync`] | Synchronization primities for Dioxus. | `sync` | +//! | [`dioxus-util`] | Misc utilities for Dioxus. | `util` | +//! +//! [`dioxus-geolocation`]: https://crates.io/crates/dioxus-geolocation +//! [`dioxus-storage`]: https://crates.io/crates/dioxus-storage +//! [`dioxus-time`]: https://crates.io/crates/dioxus-time +//! [`dioxus-window`]: https://crates.io/crates/dioxus-window +//! [`dioxus-notification`]: https://crates.io/crates/dioxus-notification +//! [`dioxus-sync`]: https://crates.io/crates/dioxus-sync +//! [`dioxus-util`]: https://crates.io/crates/dioxus-util + +#[cfg(feature = "geolocation")] +pub use dioxus_geolocation as geolocation; + +#[cfg(feature = "notification")] +pub use dioxus_notification as notification; + +#[cfg(feature = "storage")] +pub use dioxus_storage as storage; + +#[cfg(feature = "sync")] +pub use dioxus_sync as sync; + +#[cfg(feature = "time")] +pub use dioxus_time as time; + +#[cfg(feature = "util")] +pub use dioxus_util as util; + +#[cfg(feature = "window")] +pub use dioxus_window as window; diff --git a/packages/storage/Cargo.toml b/packages/storage/Cargo.toml new file mode 100644 index 0000000..bc35209 --- /dev/null +++ b/packages/storage/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "dioxus-storage" +version = "0.1.0-alpha.1" + +description = "Local and persistent storage utilities for Dioxus." +readme = "./README.md" +keywords = ["gui", "dioxus", "hooks"] +categories = ["gui", "wasm"] + +edition.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +dioxus = { workspace = true } +tokio = { workspace = true, features = ["sync"] } +futures-util = { workspace = true } +cfg-if = { workspace = true } + +rustc-hash = "1.1.0" +ciborium = "0.2.2" +once_cell = "1.17.0" +dioxus-signals = { workspace = true, features = ["serialize"] } +serde.workspace = true +yazi = "0.1.4" + +[target.'cfg(not(target_family = "wasm"))'.dependencies] +directories = "4.0.1" + +[target.'cfg(target_family = "wasm")'.dependencies] +web-sys = { workspace = true, features = ["Window", "Storage", "StorageEvent"] } +wasm-bindgen = { workspace = true } diff --git a/packages/storage/README.md b/packages/storage/README.md new file mode 100644 index 0000000..57201ee --- /dev/null +++ b/packages/storage/README.md @@ -0,0 +1,47 @@ +# Dioxus Storage +Local and persistent storage utilities for Dioxus. + +### Features: +- [x] Local Storage +- [x] Persistent Storage + +## Usage +Add `dioxus-storage` to your `Cargo.toml`: +```toml +[dependencies] +dioxus-storage = "0.1" +``` + +Example: +```rs +use dioxus_storage::use_persistent; +use dioxus::prelude::*; + +#[component] +fn App() -> Element { + let mut num = use_persistent("count", || 0); + rsx! { + div { + button { + onclick: move |_| { + *num.write() += 1; + }, + "Increment" + } + div { + "{*num.read()}" + } + } + } +} +``` + +### Dioxus Compatibility +This table represents the compatibility between this crate and Dioxus versions. +The crate version supports a Dioxus version up until the next crate version in the table. + +E.g. if crate version `0.1` supported Dioxus `0.6` and crate version `0.4` supported Dioxus `0.7`, crate versions `0.1`, `0.2`, and `0.3` would support Dioxus `0.6`. + +| Crate Version | Dioxus Version | +| ------------- | -------------- | +| 0.1 | 0.6 | \ No newline at end of file diff --git a/sdk/src/storage/client_storage/fs.rs b/packages/storage/src/client_storage/fs.rs similarity index 93% rename from sdk/src/storage/client_storage/fs.rs rename to packages/storage/src/client_storage/fs.rs index 9d2dde5..f7fd110 100644 --- a/sdk/src/storage/client_storage/fs.rs +++ b/packages/storage/src/client_storage/fs.rs @@ -1,12 +1,13 @@ -use crate::storage::{StorageChannelPayload, StorageSubscription}; -use serde::de::DeserializeOwned; +use crate::{StorageChannelPayload, StorageSubscription}; +use dioxus::logger::tracing::trace; use serde::Serialize; +use serde::de::DeserializeOwned; use std::collections::HashMap; use std::io::Write; use std::sync::{OnceLock, RwLock}; -use tokio::sync::watch::{channel, Receiver}; +use tokio::sync::watch::{Receiver, channel}; -use crate::storage::{serde_to_string, try_serde_from_string, StorageBacking, StorageSubscriber}; +use crate::{StorageBacking, StorageSubscriber, serde_to_string, try_serde_from_string}; #[doc(hidden)] /// Sets the directory where the storage files are located. @@ -107,7 +108,7 @@ impl StorageSubscriber for LocalStorage { } fn unsubscribe(key: &::Key) { - tracing::trace!("Unsubscribing from \"{}\"", key); + trace!("Unsubscribing from \"{}\"", key); // Fail silently if unsubscribe is called but the subscriptions map isn't initialized yet. if let Some(subscriptions) = SUBSCRIPTIONS.get() { @@ -115,7 +116,7 @@ impl StorageSubscriber for LocalStorage { // If the subscription exists, remove it from the subscriptions map. if read_binding.contains_key(key) { - tracing::trace!("Found entry for \"{}\"", key); + trace!("Found entry for \"{}\"", key); drop(read_binding); subscriptions.write().unwrap().remove(key); } diff --git a/sdk/src/storage/client_storage/memory.rs b/packages/storage/src/client_storage/memory.rs similarity index 98% rename from sdk/src/storage/client_storage/memory.rs rename to packages/storage/src/client_storage/memory.rs index 195007f..d0433ba 100644 --- a/sdk/src/storage/client_storage/memory.rs +++ b/packages/storage/src/client_storage/memory.rs @@ -5,7 +5,7 @@ use std::ops::{Deref, DerefMut}; use std::rc::Rc; use std::sync::Arc; -use crate::storage::StorageBacking; +use crate::StorageBacking; #[derive(Clone)] pub struct SessionStorage; diff --git a/sdk/src/storage/client_storage/mod.rs b/packages/storage/src/client_storage/mod.rs similarity index 78% rename from sdk/src/storage/client_storage/mod.rs rename to packages/storage/src/client_storage/mod.rs index 32f18e0..3706c9d 100644 --- a/sdk/src/storage/client_storage/mod.rs +++ b/packages/storage/src/client_storage/mod.rs @@ -2,7 +2,7 @@ /// Set the directory where the storage files are located on non-wasm targets. /// /// ```rust -/// use dioxus_sdk::set_dir; +/// use dioxus_storage::set_dir; /// /// fn main(){ /// // set the directory to the default location @@ -10,7 +10,7 @@ /// } /// ``` /// ```rust -/// use dioxus_sdk::set_dir; +/// use dioxus_storage::set_dir; /// /// fn main(){ /// // set the directory to a custom location @@ -21,14 +21,13 @@ macro_rules! set_dir { () => { #[cfg(not(target_family = "wasm"))] - $crate::storage::set_dir_name(env!("CARGO_PKG_NAME")) + $crate::set_dir_name(env!("CARGO_PKG_NAME")) }; ($path: literal) => { #[cfg(not(target_family = "wasm"))] - $crate::storage::set_directory(std::path::PathBuf::from($path)) + $crate::set_directory(std::path::PathBuf::from($path)) }; } -pub use set_dir; cfg_if::cfg_if! { if #[cfg(target_family = "wasm")] { diff --git a/sdk/src/storage/client_storage/web.rs b/packages/storage/src/client_storage/web.rs similarity index 87% rename from sdk/src/storage/client_storage/web.rs rename to packages/storage/src/client_storage/web.rs index b42fec7..b0b224b 100644 --- a/sdk/src/storage/client_storage/web.rs +++ b/packages/storage/src/client_storage/web.rs @@ -3,16 +3,17 @@ use std::{ sync::{Arc, RwLock}, }; +use dioxus::logger::tracing::{error, trace}; use once_cell::sync::Lazy; -use serde::{de::DeserializeOwned, Serialize}; -use tokio::sync::watch::{channel, Receiver}; -use wasm_bindgen::prelude::Closure; +use serde::{Serialize, de::DeserializeOwned}; +use tokio::sync::watch::{Receiver, channel}; use wasm_bindgen::JsCast; -use web_sys::{window, Storage}; +use wasm_bindgen::prelude::Closure; +use web_sys::{Storage, window}; -use crate::storage::{ - serde_to_string, try_serde_from_string, StorageBacking, StorageChannelPayload, - StorageSubscriber, StorageSubscription, +use crate::{ + StorageBacking, StorageChannelPayload, StorageSubscriber, StorageSubscription, serde_to_string, + try_serde_from_string, }; #[derive(Clone)] @@ -65,20 +66,20 @@ impl StorageSubscriber for LocalStorage { static SUBSCRIPTIONS: Lazy>>> = Lazy::new(|| { // Create a closure that will be called when a storage event occurs. let closure = Closure::wrap(Box::new(move |e: web_sys::StorageEvent| { - tracing::trace!("Storage event: {:?}", e); + trace!("Storage event: {:?}", e); let key: String = e.key().unwrap(); let read_binding = SUBSCRIPTIONS.read().unwrap(); if let Some(subscription) = read_binding.get(&key) { if subscription.tx.is_closed() { - tracing::trace!("Channel is closed, removing subscription for \"{}\"", key); + trace!("Channel is closed, removing subscription for \"{}\"", key); drop(read_binding); SUBSCRIPTIONS.write().unwrap().remove(&key); return; } // Call the getter for the given entry and send the value to said entry's channel. match subscription.get_and_send() { - Ok(_) => tracing::trace!("Sent storage event"), - Err(err) => tracing::error!("Error sending storage event: {:?}", err.to_string()), + Ok(_) => trace!("Sent storage event"), + Err(err) => error!("Error sending storage event: {:?}", err.to_string()), } } }) as Box); diff --git a/sdk/src/storage/mod.rs b/packages/storage/src/lib.rs similarity index 97% rename from sdk/src/storage/mod.rs rename to packages/storage/src/lib.rs index f2bb1ea..89881f0 100644 --- a/sdk/src/storage/mod.rs +++ b/packages/storage/src/lib.rs @@ -4,10 +4,11 @@ //! //! ## Usage //! ```rust -//! use dioxus_sdk::storage::use_persistent; +//! use dioxus_storage::use_persistent; //! use dioxus::prelude::*; //! -//! fn app() -> Element { +//! #[component] +//! fn App() -> Element { //! let mut num = use_persistent("count", || 0); //! rsx! { //! div { @@ -29,6 +30,7 @@ mod client_storage; mod persistence; pub use client_storage::{LocalStorage, SessionStorage}; +use dioxus::logger::tracing::trace; use futures_util::stream::StreamExt; pub use persistence::{ new_persistent, new_singleton_persistent, use_persistent, use_singleton_persistent, @@ -37,7 +39,7 @@ use std::cell::RefCell; use std::rc::Rc; use dioxus::prelude::*; -use serde::{de::DeserializeOwned, Serialize}; +use serde::{Serialize, de::DeserializeOwned}; use std::any::Any; use std::fmt::{Debug, Display}; use std::ops::{Deref, DerefMut}; @@ -45,7 +47,6 @@ use std::sync::Arc; use tokio::sync::watch::error::SendError; use tokio::sync::watch::{Receiver, Sender}; -pub use client_storage::set_dir; #[cfg(not(target_family = "wasm"))] pub use client_storage::{set_dir_name, set_directory}; @@ -56,7 +57,7 @@ pub use client_storage::{set_dir_name, set_directory}; /// ## Usage /// /// ```rust -/// use dioxus_sdk::storage::{use_storage, StorageBacking}; +/// use dioxus_storage::{use_storage, StorageBacking}; /// use dioxus::prelude::*; /// use dioxus_signals::Signal; /// @@ -86,6 +87,7 @@ enum StorageMode { impl StorageMode { // Get the active mode + #[allow(unreachable_code)] const fn current() -> Self { server_only! { return StorageMode::Server; @@ -106,7 +108,7 @@ impl StorageMode { /// ## Usage /// /// ```rust -/// use dioxus_sdk::storage::{new_storage, StorageBacking}; +/// use dioxus_storage::{new_storage, StorageBacking}; /// use dioxus::prelude::*; /// use dioxus_signals::Signal; /// @@ -282,7 +284,7 @@ pub trait StorageEntryTrait: let (rc, mut reactive_context) = ReactiveContext::new(); rc.run_in(|| { if old.borrow().as_ref() != Some(&*data.read()) { - tracing::trace!("Saving to storage"); + trace!("Saving to storage"); entry_clone.save(); old.replace(Some(data())); } @@ -586,10 +588,7 @@ pub(crate) fn try_serde_from_string(value: &str) -> Option< } match yazi::decompress(&bytes, yazi::Format::Zlib) { - Ok((decompressed, _)) => match ciborium::from_reader(std::io::Cursor::new(decompressed)) { - Ok(v) => Some(v), - Err(_) => None, - }, + Ok((decompressed, _)) => ciborium::from_reader(std::io::Cursor::new(decompressed)).ok(), Err(_) => None, } } diff --git a/sdk/src/storage/persistence.rs b/packages/storage/src/persistence.rs similarity index 96% rename from sdk/src/storage/persistence.rs rename to packages/storage/src/persistence.rs index db21872..64ee5f2 100644 --- a/sdk/src/storage/persistence.rs +++ b/packages/storage/src/persistence.rs @@ -1,9 +1,9 @@ -use crate::storage::SessionStorage; -use crate::storage::{new_storage_entry, use_hydrate_storage}; +use crate::SessionStorage; +use crate::{new_storage_entry, use_hydrate_storage}; use dioxus::prelude::*; use dioxus_signals::Signal; -use serde::de::DeserializeOwned; use serde::Serialize; +use serde::de::DeserializeOwned; use super::StorageEntryTrait; diff --git a/packages/sync/Cargo.toml b/packages/sync/Cargo.toml new file mode 100644 index 0000000..abbbc55 --- /dev/null +++ b/packages/sync/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "dioxus-sync" +version = "0.1.0-alpha.1" + +description = "Synchronization primitives for your Dioxus app." +readme = "./README.md" +keywords = ["gui", "dioxus", "hooks"] +categories = ["gui", "wasm"] + +edition.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +dioxus = { workspace = true } + +uuid = { version = "1.3.2", features = ["v4"] } +async-broadcast = "0.5.1" + +[target.'cfg(target_family = "wasm")'.dependencies] +uuid = { version = "1.3.2", features = ["v4", "js"] } diff --git a/packages/sync/README.md b/packages/sync/README.md new file mode 100644 index 0000000..1c27954 --- /dev/null +++ b/packages/sync/README.md @@ -0,0 +1,22 @@ +# Dioxus Sync +Synchronization primitives for your Dioxus app. + +### Features: +- [x] `channel` + +## Usage +Add `dioxus-sync` to your `Cargo.toml`: +```toml +[dependencies] +dioxus-sync = "0.1" +``` + +### Dioxus Compatibility +This table represents the compatibility between this crate and Dioxus versions. +The crate version supports a Dioxus version up until the next crate version in the table. + +E.g. if crate version `0.1` supported Dioxus `0.6` and crate version `0.4` supported Dioxus `0.7`, crate versions `0.1`, `0.2`, and `0.3` would support Dioxus `0.6`. + +| Crate Version | Dioxus Version | +| ------------- | -------------- | +| 0.1 | 0.6 | \ No newline at end of file diff --git a/packages/sync/src/channel/mod.rs b/packages/sync/src/channel/mod.rs new file mode 100644 index 0000000..86ffc3e --- /dev/null +++ b/packages/sync/src/channel/mod.rs @@ -0,0 +1,5 @@ +mod use_channel; +mod use_listen_channel; + +pub use use_channel::{UseChannel, use_channel}; +pub use use_listen_channel::{UseListenChannelError, use_listen_channel}; diff --git a/sdk/src/utils/channel/use_channel.rs b/packages/sync/src/channel/use_channel.rs similarity index 94% rename from sdk/src/utils/channel/use_channel.rs rename to packages/sync/src/channel/use_channel.rs index 6b8677b..dc3210f 100644 --- a/sdk/src/utils/channel/use_channel.rs +++ b/packages/sync/src/channel/use_channel.rs @@ -1,4 +1,4 @@ -use async_broadcast::{broadcast, InactiveReceiver, Receiver, SendError, Sender, TrySendError}; +use async_broadcast::{InactiveReceiver, Receiver, SendError, Sender, TrySendError, broadcast}; use dioxus::prelude::*; use uuid::Uuid; diff --git a/sdk/src/utils/channel/use_listen_channel.rs b/packages/sync/src/channel/use_listen_channel.rs similarity index 100% rename from sdk/src/utils/channel/use_listen_channel.rs rename to packages/sync/src/channel/use_listen_channel.rs diff --git a/packages/sync/src/lib.rs b/packages/sync/src/lib.rs new file mode 100644 index 0000000..ff02972 --- /dev/null +++ b/packages/sync/src/lib.rs @@ -0,0 +1 @@ +pub mod channel; diff --git a/packages/time/Cargo.toml b/packages/time/Cargo.toml new file mode 100644 index 0000000..6008097 --- /dev/null +++ b/packages/time/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "dioxus-time" +version = "0.1.0-alpha.1" + +description = "Timing utilities and hooks for Dioxus." +readme = "./README.md" +keywords = ["gui", "dioxus", "hooks"] +categories = ["gui", "wasm"] + +edition.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +dioxus = { workspace = true } +futures = { workspace = true } + +[target.'cfg(not(target_family = "wasm"))'.dependencies] +tokio = { workspace = true, features = ["time"]} + +[target.'cfg(target_family = "wasm")'.dependencies] +gloo-timers = { version = "0.3.0", features = ["futures"] } \ No newline at end of file diff --git a/packages/time/README.md b/packages/time/README.md new file mode 100644 index 0000000..23e71c9 --- /dev/null +++ b/packages/time/README.md @@ -0,0 +1,61 @@ +# Dioxus Time +Timing utilities and hooks for Dioxus. + +### Features: +- [x] Intervals +- [x] Debounces +- [ ] Timeouts + +## Usage +Add `dioxus-time` to your `Cargo.toml`: +```toml +[dependencies] +dioxus-time = "0.1" +``` + +Example: +```rs +use dioxus::{logger::tracing::info, prelude::*}; +use dioxus_time::{use_debounce, use_interval}; +use std::time::Duration; + +fn main() { + launch(App); +} + +#[component] +fn App() -> Element { + let mut count = use_signal(|| 0); + + // Increment count every second. + use_interval(Duration::from_secs(1), move || count += 1); + + // Reset count after 2 seconds of the latest action call. + let mut debounce = use_debounce(Duration::from_millis(2000), move |text| { + info!("{text}"); + count.set(0); + }); + + rsx! { + p { "{count}" }, + button { + // Trigger the debounce. + onclick: move |_| debounce.action("button was clicked"), + "Reset the counter! (2 second debounce)" + } + } +} + +``` + + + +### Dioxus Compatibility +This table represents the compatibility between this crate and Dioxus versions. +The crate version supports a Dioxus version up until the next crate version in the table. + +E.g. if crate version `0.1` supported Dioxus `0.6` and crate version `0.4` supported Dioxus `0.7`, crate versions `0.1`, `0.2`, and `0.3` would support Dioxus `0.6`. + +| Crate Version | Dioxus Version | +| ------------- | -------------- | +| 0.1 | 0.6 | \ No newline at end of file diff --git a/packages/time/src/debounce.rs b/packages/time/src/debounce.rs new file mode 100644 index 0000000..1e2ed66 --- /dev/null +++ b/packages/time/src/debounce.rs @@ -0,0 +1,128 @@ +use crate::{TimeoutHandle, UseTimeout, use_timeout}; +use dioxus::{ + dioxus_core::SpawnIfAsync, + hooks::use_signal, + signals::{Signal, Writable}, +}; +use std::time::Duration; + +/// The interface for calling a debounce. +/// +/// See [`use_debounce`] for more information. +#[derive(Clone, Copy, PartialEq)] +pub struct UseDebounce { + current_handle: Signal>, + timeout: UseTimeout, +} + +impl UseDebounce { + /// Start the debounce countdown, resetting it if already started. + pub fn action(&mut self, args: Args) { + self.cancel(); + self.current_handle.set(Some(self.timeout.action(args))); + } + + /// Cancel the debounce action. + pub fn cancel(&mut self) { + if let Some(handle) = self.current_handle.take() { + handle.cancel(); + } + } +} + +/// A hook for allowing a function to be called only after a provided [`Duration`] has passed. +/// +/// Once the [`UseDebounce::action`] method is called, a timer will start counting down until +/// the callback is ran. If the [`UseDebounce::action`] method is called again, the timer will restart. +/// +/// # Examples +/// +/// Example of using a debounce: +/// ```rust +/// use dioxus::prelude::*; +/// use dioxus_time::use_debounce; +/// use std::time::Duration; +/// +/// #[component] +/// fn App() -> Element { +/// // Create a two second debounce. +/// // This will print "ran" after two seconds since the last action call. +/// let mut debounce = use_debounce(Duration::from_secs(2), |_| println!("ran")); +/// +/// rsx! { +/// button { +/// onclick: move |_| { +/// // Call the debounce. +/// debounce.action(()); +/// }, +/// "Click!" +/// } +/// } +/// } +/// ``` +/// +/// #### Cancelling A Debounce +/// If you need to cancel the currently active debounce, you can call [`UseDebounce::cancel`]: +/// ```rust +/// use dioxus::prelude::*; +/// use dioxus_time::use_debounce; +/// use std::time::Duration; +/// +/// #[component] +/// fn App() -> Element { +/// let mut debounce = use_debounce(Duration::from_secs(5), |_| println!("ran")); +/// +/// rsx! { +/// button { +/// // Start the debounce on click. +/// onclick: move |_| debounce.action(()), +/// "Action!" +/// } +/// button { +/// // Cancel the debounce on click. +/// onclick: move |_| debounce.cancel(), +/// "Cancel!" +/// } +/// } +/// } +/// ``` +/// +/// ### Async Debounce +/// Debounces can accept an async callback: +/// ```rust +/// use dioxus::prelude::*; +/// use dioxus_time::use_debounce; +/// use std::time::Duration; +/// +/// #[component] +/// fn App() -> Element { +/// // Create a two second debounce that uses some async/await. +/// let mut debounce = use_debounce(Duration::from_secs(2), |_| async { +/// println!("debounce called!"); +/// tokio::time::sleep(Duration::from_secs(2)).await; +/// println!("after async"); +/// }); +/// +/// rsx! { +/// button { +/// onclick: move |_| { +/// // Call the debounce. +/// debounce.action(()); +/// }, +/// "Click!" +/// } +/// } +/// } +/// ``` +pub fn use_debounce, Marker>( + duration: Duration, + callback: impl FnMut(Args) -> MaybeAsync + 'static, +) -> UseDebounce { + let timeout = use_timeout(duration, callback); + let current_handle = use_signal(|| None); + + UseDebounce { + timeout, + current_handle, + } +} diff --git a/packages/time/src/interval.rs b/packages/time/src/interval.rs new file mode 100644 index 0000000..fdcc290 --- /dev/null +++ b/packages/time/src/interval.rs @@ -0,0 +1,138 @@ +use dioxus::{ + dioxus_core::SpawnIfAsync, + prelude::{Callback, Task, Writable, spawn, use_hook}, + signals::Signal, +}; +use std::time::Duration; + +/// The interface to a debounce. +/// +/// You can cancel an interval with [`UseInterval::cancel`]. +/// See [`use_interval`] for more information. +#[derive(Clone, PartialEq, Copy)] +pub struct UseInterval { + inner: Signal, +} + +struct InnerUseInterval { + pub(crate) interval: Option, +} + +impl Drop for InnerUseInterval { + fn drop(&mut self) { + if let Some(interval) = self.interval.take() { + interval.cancel(); + } + } +} + +impl UseInterval { + /// Cancel the interval. + pub fn cancel(&mut self) { + if let Some(interval) = self.inner.write().interval.take() { + interval.cancel(); + } + } +} + +/// Repeatedly call a function at a specific interval. +/// +/// Intervals are cancelable with the [`UseInterval::cancel`] method. +/// +/// # Examples +/// +/// Example of using an interval: +/// ```rust +/// use dioxus::prelude::*; +/// use dioxus_time::use_interval; +/// use std::time::Duration; +/// +/// #[component] +/// fn App() -> Element { +/// let mut time_elapsed = use_signal(|| 0); +/// // Create an interval that increases the time elapsed signal by one every second. +/// use_interval(Duration::from_secs(1), move |()| time_elapsed += 1); +/// +/// rsx! { +/// "It has been {time_elapsed} since the app started." +/// } +/// } +/// ``` +/// +/// #### Cancelling Intervals +/// Example of cancelling an interval: +/// ```rust +/// use dioxus::prelude::*; +/// use dioxus_time::use_interval; +/// use std::time::Duration; +/// +/// #[component] +/// fn App() -> Element { +/// let mut time_elapsed = use_signal(|| 0); +/// let mut interval = use_interval(Duration::from_secs(1), move |()| time_elapsed += 1); +/// +/// rsx! { +/// "It has been {time_elapsed} since the app started." +/// button { +/// // Cancel the interval when the button is clicked. +/// onclick: move |_| interval.cancel(), +/// "Cancel Interval" +/// } +/// } +/// } +/// ``` +/// +/// #### Async Intervals +/// Intervals can accept an async callback: +/// ```rust +/// use dioxus::prelude::*; +/// use dioxus_time::use_interval; +/// use std::time::Duration; +/// +/// #[component] +/// fn App() -> Element { +/// let mut time_elapsed = use_signal(|| 0); +/// // Create an interval that increases the time elapsed signal by one every second. +/// use_interval(Duration::from_secs(1), move |()| async move { +/// time_elapsed += 1; +/// // Pretend we're doing some async work. +/// tokio::time::sleep(Duration::from_secs(1)).await; +/// println!("Done!"); +/// }); +/// +/// rsx! { +/// "It has been {time_elapsed} since the app started." +/// } +/// } +/// ``` +pub fn use_interval, Marker>( + period: Duration, + callback: impl FnMut(()) -> MaybeAsync + 'static, +) -> UseInterval { + let inner = use_hook(|| { + let callback = Callback::new(callback); + + let task = spawn(async move { + #[cfg(not(target_family = "wasm"))] + let mut interval = tokio::time::interval(period); + + loop { + #[cfg(not(target_family = "wasm"))] + interval.tick().await; + + #[cfg(target_family = "wasm")] + { + gloo_timers::future::sleep(period).await; + } + + callback.call(()); + } + }); + + Signal::new(InnerUseInterval { + interval: Some(task), + }) + }); + + UseInterval { inner } +} diff --git a/packages/time/src/lib.rs b/packages/time/src/lib.rs new file mode 100644 index 0000000..d900e6c --- /dev/null +++ b/packages/time/src/lib.rs @@ -0,0 +1,52 @@ +//! # Dioxus Time Utilities +//! +//! Cross-platform timing utilities for your Dioxus apps. +//! +//! We currently offer: +//! - [`use_timeout`] +//! - [`use_debounce`] +//! - [`use_interval`] +//! - and [`sleep`] +#![warn(missing_docs)] + +use std::time::Duration; + +mod interval; +pub use interval::{UseInterval, use_interval}; + +mod debounce; +pub use debounce::{UseDebounce, use_debounce}; + +mod timeout; +pub use timeout::{TimeoutHandle, UseTimeout, use_timeout}; + +/// Pause the current task for the specified duration. +/// +/// # Examples +/// ```rust +/// use std::time::Duration; +/// use dioxus::prelude::*; +/// +/// #[component] +/// pub fn App() -> Element { +/// let mut has_slept = use_signal(|| false); +/// +/// use_effect(move || { +/// spawn(async move { +/// dioxus_time::sleep(Duration::from_secs(3)).await; +/// has_slept.set(true); +/// }); +/// }); +/// +/// rsx! { +/// "I have slept: {has_slept}" +/// } +/// } +/// ``` +pub async fn sleep(duration: Duration) { + #[cfg(not(target_family = "wasm"))] + tokio::time::sleep(duration).await; + + #[cfg(target_family = "wasm")] + gloo_timers::future::sleep(duration).await; +} diff --git a/packages/time/src/timeout.rs b/packages/time/src/timeout.rs new file mode 100644 index 0000000..61ca1d2 --- /dev/null +++ b/packages/time/src/timeout.rs @@ -0,0 +1,189 @@ +use dioxus::{ + dioxus_core::SpawnIfAsync, + prelude::{Callback, Task, spawn, use_hook}, + signals::Signal, +}; +use futures::{SinkExt, StreamExt, channel::mpsc}; +use std::time::Duration; + +/// The interface to a timeout. +/// +/// This is used to trigger the timeout with [`UseTimeout::action`]. +/// +/// See [`use_timeout`] for more information. +pub struct UseTimeout { + duration: Duration, + sender: Signal>, +} + +impl UseTimeout { + /// Trigger the timeout. + /// + /// If no arguments are desired, use the [`unit`] type. + /// See [`use_timeout`] for more information. + pub fn action(&self, args: Args) -> TimeoutHandle { + let mut sender = (self.sender)(); + let duration = self.duration; + + let handle = spawn(async move { + #[cfg(not(target_family = "wasm"))] + tokio::time::sleep(duration).await; + + #[cfg(target_family = "wasm")] + gloo_timers::future::sleep(duration).await; + + // If this errors then the timeout was likely dropped. + let _ = sender.send(args).await; + }); + + TimeoutHandle { handle } + } +} + +impl Clone for UseTimeout { + fn clone(&self) -> Self { + *self + } +} +impl Copy for UseTimeout {} +impl PartialEq for UseTimeout { + fn eq(&self, other: &Self) -> bool { + self.duration == other.duration && self.sender == other.sender + } +} + +/// A handle to a pending timeout. +/// +/// A handle to a running timeout triggered with [`UseTimeout::action`]. +/// This handle allows you to cancel the timeout from triggering with [`TimeoutHandle::cancel`] +/// +/// See [`use_timeout`] for more information. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct TimeoutHandle { + handle: Task, +} + +impl TimeoutHandle { + /// Cancel the timeout associated with this handle. + pub fn cancel(self) { + self.handle.cancel(); + } +} + +/// A hook to run a callback after a period of time. +/// +/// Timeouts allow you to trigger a callback that occurs after a period of time. Unlike a debounce, a timeout will not +/// reset it's timer when triggered again. Instead, calling a timeout while it is already running will start another instance +/// to run the callback after the provided period. +/// +/// This hook is similar to the web [setTimeout()](https://developer.mozilla.org/en-US/docs/Web/API/Window/setTimeout) API. +/// +/// # Examples +/// +/// Example of using a timeout: +/// ```rust +/// use dioxus::prelude::*; +/// use dioxus_time::use_timeout; +/// use std::time::Duration; +/// +/// #[component] +/// fn App() -> Element { +/// // Create a timeout for two seconds. +/// // Once triggered, this timeout will print "timeout called" after two seconds. +/// let timeout = use_timeout(Duration::from_secs(2), |()| println!("timeout called")); +/// +/// rsx! { +/// button { +/// onclick: move |_| { +/// // Trigger the timeout. +/// timeout.action(()); +/// }, +/// "Click!" +/// } +/// } +/// } +/// ``` +/// +/// #### Cancelling Timeouts +/// Example of cancelling a timeout. This is the equivalent of a debounce. +/// ```rust +/// use dioxus::prelude::*; +/// use dioxus_time::{use_timeout, TimeoutHandle}; +/// use std::time::Duration; +/// +/// #[component] +/// fn App() -> Element { +/// let mut current_timeout: Signal> = use_signal(|| None); +/// let timeout = use_timeout(Duration::from_secs(2), move |()| { +/// current_timeout.set(None); +/// println!("timeout called"); +/// }); +/// +/// rsx! { +/// button { +/// onclick: move |_| { +/// // Cancel any currently running timeouts. +/// if let Some(handle) = *current_timeout.read() { +/// handle.cancel(); +/// } +/// +/// // Trigger the timeout. +/// let handle = timeout.action(()); +/// current_timeout.set(Some(handle)); +/// }, +/// "Click!" +/// } +/// } +/// } +/// ``` +/// +/// #### Async Timeouts +/// Timeouts can accept an async callback: +/// ```rust +/// use dioxus::prelude::*; +/// use dioxus_time::use_timeout; +/// use std::time::Duration; +/// +/// #[component] +/// fn App() -> Element { +/// // Create a timeout for two seconds. +/// // We use an async sleep to wait an even longer duration after the timeout is called. +/// let timeout = use_timeout(Duration::from_secs(2), |()| async { +/// println!("Timeout after two total seconds."); +/// tokio::time::sleep(Duration::from_secs(2)).await; +/// println!("Timeout after four total seconds."); +/// }); +/// +/// rsx! { +/// button { +/// onclick: move |_| { +/// // Trigger the timeout. +/// timeout.action(()); +/// }, +/// "Click!" +/// } +/// } +/// } +/// ``` +pub fn use_timeout, Marker>( + duration: Duration, + callback: impl FnMut(Args) -> MaybeAsync + 'static, +) -> UseTimeout { + use_hook(|| { + let callback = Callback::new(callback); + let (sender, mut receiver) = mpsc::unbounded(); + + spawn(async move { + loop { + if let Some(args) = receiver.next().await { + callback.call(args); + } + } + }); + + UseTimeout { + duration, + sender: Signal::new(sender), + } + }) +} diff --git a/packages/util/Cargo.toml b/packages/util/Cargo.toml new file mode 100644 index 0000000..ef7d895 --- /dev/null +++ b/packages/util/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "dioxus-util" +version = "0.1.0-alpha.1" + +description = "General utilities for Dioxus apps." +readme = "./README.md" +keywords = ["gui", "dioxus", "hooks"] +categories = ["gui", "wasm"] + +edition.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +dioxus = { workspace = true } +serde.workspace = true diff --git a/packages/util/README.md b/packages/util/README.md new file mode 100644 index 0000000..4ac4e26 --- /dev/null +++ b/packages/util/README.md @@ -0,0 +1,19 @@ +# Dioxus Util +General utilities for Dioxus apps. This crate is a placeholder for now. + +## Usage +Add `dioxus-util` to your `Cargo.toml`: +```toml +[dependencies] +dioxus-util = "0.1" +``` + +### Dioxus Compatibility +This table represents the compatibility between this crate and Dioxus versions. +The crate version supports a Dioxus version up until the next crate version in the table. + +E.g. if crate version `0.1` supported Dioxus `0.6` and crate version `0.4` supported Dioxus `0.7`, crate versions `0.1`, `0.2`, and `0.3` would support Dioxus `0.6`. + +| Crate Version | Dioxus Version | +| ------------- | -------------- | +| 0.1 | 0.6 | \ No newline at end of file diff --git a/packages/util/src/lib.rs b/packages/util/src/lib.rs new file mode 100644 index 0000000..4a879de --- /dev/null +++ b/packages/util/src/lib.rs @@ -0,0 +1,3 @@ +//! Common utilities for Dioxus. + +pub mod scroll; diff --git a/sdk/src/utils/scroll.rs b/packages/util/src/scroll.rs similarity index 96% rename from sdk/src/utils/scroll.rs rename to packages/util/src/scroll.rs index 7c6e30b..b348b16 100644 --- a/sdk/src/utils/scroll.rs +++ b/packages/util/src/scroll.rs @@ -26,12 +26,7 @@ static SCROLL_TRACKER_COUNTER: AtomicUsize = AtomicUsize::new(0); /// Creates a signal that tracks root scrolling. /// ```rust /// use dioxus::{logger::tracing::{info, Level}, prelude::*}; -/// use dioxus_sdk::utils::scroll::use_root_scroll; -/// -/// fn main() { -/// dioxus::logger::init(Level::TRACE).unwrap(); -/// launch(App); -/// } +/// use dioxus_util::scroll::use_root_scroll; /// /// #[component] /// fn App() -> Element { diff --git a/packages/window/Cargo.toml b/packages/window/Cargo.toml new file mode 100644 index 0000000..e318540 --- /dev/null +++ b/packages/window/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "dioxus-window" +version = "0.1.0-alpha.1" + +description = "Window utilities and hooks for Dioxus." +readme = "./README.md" +keywords = ["gui", "dioxus", "hooks"] +categories = ["gui", "wasm"] + +edition.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +dioxus = { workspace = true } +dioxus-config-macro = { workspace = true } + +[target.'cfg(target_family = "wasm")'.dependencies] +web-sys = { workspace = true, features = ["Window", "MediaQueryList"] } +wasm-bindgen = { workspace = true } + +[target.'cfg(not(target_family = "wasm"))'.dependencies] +dioxus-desktop = { workspace = true } diff --git a/packages/window/README.md b/packages/window/README.md new file mode 100644 index 0000000..aaa7c06 --- /dev/null +++ b/packages/window/README.md @@ -0,0 +1,24 @@ +# Dioxus Window +Window utilities for Dioxus. + +### Features: +- [x] Theme +- [x] Window Size + +## Usage +Add `dioxus-window` to your `Cargo.toml`: +```toml +[dependencies] +dioxus-window = "0.1" +``` + + +### Dioxus Compatibility +This table represents the compatibility between this crate and Dioxus versions. +The crate version supports a Dioxus version up until the next crate version in the table. + +E.g. if crate version `0.1` supported Dioxus `0.6` and crate version `0.4` supported Dioxus `0.7`, crate versions `0.1`, `0.2`, and `0.3` would support Dioxus `0.6`. + +| Crate Version | Dioxus Version | +| ------------- | -------------- | +| 0.1 | 0.6 | \ No newline at end of file diff --git a/packages/window/src/lib.rs b/packages/window/src/lib.rs new file mode 100644 index 0000000..0dee610 --- /dev/null +++ b/packages/window/src/lib.rs @@ -0,0 +1,5 @@ +//! Window utilities and hooks for Dioxus. +#![warn(missing_docs)] + +pub mod size; +pub mod theme; diff --git a/packages/window/src/size.rs b/packages/window/src/size.rs new file mode 100644 index 0000000..5b03889 --- /dev/null +++ b/packages/window/src/size.rs @@ -0,0 +1,268 @@ +//! Window size utilities. +//! +//! Acces the window size directly in your Dioxus app. +//! +//! #### Platform Support +//! Window size is available on every platform. +//! +//! # Examples +//! +//! ```rust +//! use dioxus::prelude::*; +//! use dioxus_window::size::use_window_size; +//! +//! fn App() -> Element { +//! let size = use_window_size(); +//! let size = size().unwrap(); +//! +//! rsx! { +//! p { "Width: {size.width}" } +//! p { "Height: {size.height}" } +//! } +//! } +//! ``` +use dioxus::hooks::use_effect; +use dioxus::prelude::{ + ReadOnlySignal, ScopeId, Signal, Writable, provide_root_context, try_use_context, use_hook, + warnings::signal_write_in_component_body, +}; +use dioxus::signals::Readable; +use dioxus::warnings::Warning as _; +use std::error::Error; +use std::fmt::Display; + +/// The width and height of a window. +#[derive(Clone, Copy, Debug, Default)] +pub struct WindowSize { + /// The horizontal size in pixels. + pub width: u32, + /// The vertical size in pixels. + pub height: u32, +} + +/// Possible window size errors. +#[derive(Debug, Clone, PartialEq)] +pub enum WindowSizeError { + /// Window size is not supported on this platform. + /// + /// This error only exists for proper SSR hydration. + Unsupported, + /// Failed to get the window size. + CheckFailed, +} + +impl Error for WindowSizeError {} +impl Display for WindowSizeError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Unsupported => write!(f, "the current platform is not supported"), + Self::CheckFailed => write!(f, "failed to get the current window size"), + } + } +} + +type WindowSizeResult = Result; + +/// A trait for accessing the inner values of [`WindowSize`]. +/// +/// These methods can be convenient if you need access to one of the values but not the other. +/// +/// # Examples +/// +/// ```rust +/// use dioxus::prelude::*; +/// use dioxus_window::size::{use_window_size, ReadableWindowSizeExt}; +/// +/// fn App() -> Element { +/// let size = use_window_size(); +/// +/// let half_of_width = use_memo(move || { +/// let width = size.width().unwrap(); +/// width / 2 +/// }); +/// +/// rsx! { +/// div { +/// style: "width: {half_of_width};", +/// "hi" +/// } +/// } +/// } +/// ``` +pub trait ReadableWindowSizeExt: Readable { + /// Read the width, subscribing to it. + #[track_caller] + fn width(&self) -> Result { + let value = self.read().clone(); + value.map(|x| x.width) + } + + /// Read the height, subscribing to it. + #[track_caller] + fn height(&self) -> Result { + let value = self.read().clone(); + value.map(|x| x.height) + } +} + +impl ReadableWindowSizeExt for R where R: Readable {} + +/// Get a signal to the window size. +/// +/// On first run, the result will be [`WindowSizeError::Unsupported`]. This is to prevent hydration from failing. +/// After the client runs, the window size will be tracked and updated with accurate values. +/// +/// # Examples +/// +/// ```rust +/// use dioxus::prelude::*; +/// use dioxus_window::size::use_window_size; +/// +/// fn App() -> Element { +/// let size = use_window_size(); +/// let size = size().unwrap(); +/// +/// rsx! { +/// p { "Width: {size.width}" } +/// p { "Height: {size.height}" } +/// } +/// } +/// ``` +pub fn use_window_size() -> ReadOnlySignal { + let mut window_size = match try_use_context::>() { + Some(w) => w, + // This should only run once. + None => { + let signal = Signal::new_in_scope(Err(WindowSizeError::Unsupported), ScopeId::ROOT); + provide_root_context(signal) + } + }; + + // Only start the listener on the client. + use_effect(move || { + window_size.set(get_window_size()); + listen(window_size); + }); + + use_hook(|| ReadOnlySignal::new(window_size)) +} + +// Listener for the web implementation. +#[cfg(target_family = "wasm")] +fn listen(mut window_size: Signal) { + use wasm_bindgen::{JsCast, closure::Closure}; + + let window = web_sys::window().expect("no wasm window found; are you in wasm?"); + let window2 = window.clone(); + + let on_resize = Closure::wrap(Box::new(move || { + let width = window2 + .inner_width() + .ok() + .and_then(|x| x.as_f64()) + .ok_or(WindowSizeError::CheckFailed); + + let height = window2 + .inner_height() + .ok() + .and_then(|x| x.as_f64()) + .ok_or(WindowSizeError::CheckFailed); + + let size = (width, height); + let value = match size { + (Ok(width), Ok(height)) => Ok(WindowSize { + width: width as u32, + height: height as u32, + }), + _ => Err(WindowSizeError::CheckFailed), + }; + + signal_write_in_component_body::allow(move || { + window_size.set(value); + }); + }) as Box); + + let on_resize_cb = on_resize.as_ref().clone(); + on_resize.forget(); + window.set_onresize(Some(on_resize_cb.unchecked_ref())); +} + +// Listener for anything but the web implementation. +#[cfg(not(target_family = "wasm"))] +fn listen(mut window_size: Signal) { + use dioxus_desktop::{WindowEvent, tao::event::Event, window}; + + let window = window(); + window.create_wry_event_handler(move |event, _| { + if let Event::WindowEvent { + event: WindowEvent::Resized(size), + .. + } = event + { + signal_write_in_component_body::allow(move || { + window_size.set(Ok(WindowSize { + width: size.width, + height: size.height, + })); + }); + } + }); +} + +/// Get the current window size. +/// +/// **Note** +/// +/// This function will cause hydration to fail if not used inside an effect, task, or event handler. +/// +/// # Examples +/// +/// ```rust +/// use dioxus::prelude::*; +/// use dioxus_window::size::get_window_size; +/// +/// fn App() -> Element { +/// let size = use_signal(get_window_size); +/// let size = size().unwrap(); +/// +/// rsx! { +/// p { "Width: {size.width}" } +/// p { "Height: {size.height}" } +/// } +/// } +/// ``` +pub fn get_window_size() -> WindowSizeResult { + get_size_platform() +} + +// Web implementation of size getter. +#[cfg(target_family = "wasm")] +fn get_size_platform() -> WindowSizeResult { + let window = web_sys::window().ok_or(WindowSizeError::CheckFailed)?; + + // We will fail silently for conversion errors. + let height = window + .inner_height() + .ok() + .and_then(|x| x.as_f64()) + .ok_or(WindowSizeError::CheckFailed)? as u32; + + let width = window + .inner_width() + .ok() + .and_then(|x| x.as_f64()) + .ok_or(WindowSizeError::CheckFailed)? as u32; + + Ok(WindowSize { width, height }) +} + +// Desktop implementation of size getter. +#[cfg(not(target_family = "wasm"))] +fn get_size_platform() -> WindowSizeResult { + let window = dioxus_desktop::window(); + let size = window.inner_size(); + Ok(WindowSize { + width: size.width, + height: size.height, + }) +} diff --git a/packages/window/src/theme.rs b/packages/window/src/theme.rs new file mode 100644 index 0000000..b8cbf13 --- /dev/null +++ b/packages/window/src/theme.rs @@ -0,0 +1,278 @@ +//! Theme utilities. +//! +//! Access the system's theme to use for common tasks such as automatically setting your app styling. +//! +//! Most apps will need to choose a default theme in the event of an error. +//! We recommend using either [`Result::unwrap_or`] or [`Result::unwrap_or_default`] to do this. +//! +//! #### Platform Support +//! Theme is available for Web, Windows, & Mac. Linux is unsupported and Android/iOS has not been tested. +//! +//! # Examples +//! An example of using the theme to determine which class to use. +//! ```rust +//! use dioxus::prelude::*; +//! use dioxus_window::theme::{use_system_theme, Theme}; +//! +//! #[component] +//! fn App() -> Element { +//! let theme = use_system_theme(); +//! +//! // Default to a light theme in the event of an error. +//! let class = match theme().unwrap_or(Theme::Light) { +//! Theme::Light => "bg-light", +//! Theme::Dark => "bg-dark", +//! }; +//! +//! rsx! { +//! div { +//! class: "{class}", +//! "the current theme is: {theme().unwrap_or(Theme::Light)}" +//! } +//! } +//! } +//! ``` +use dioxus::prelude::*; +use std::{error::Error, fmt::Display}; + +/// A color theme. +/// +/// For any themes other than `light` and `dark`, a [`ThemeError::UnknownTheme`] will be returned. +/// We may be able to support custom themes in the future. +#[derive(Debug, Clone, Copy, PartialEq, Default)] +pub enum Theme { + /// A light theme, better in direct sunlight. + #[default] + Light, + /// A dark theme, better for the night owls. + Dark, +} + +impl Display for Theme { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Self::Light => write!(f, "light"), + Self::Dark => write!(f, "dark"), + } + } +} + +/// Possible theme errors. +#[derive(Debug, Clone, PartialEq)] +pub enum ThemeError { + /// Theme is not supported on this platform. + Unsupported, + /// Failed to get the system theme. + CheckFailed, + /// System returned an unknown theme. + UnknownTheme, +} + +impl Error for ThemeError {} +impl Display for ThemeError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Self::Unsupported => write!(f, "the current platform is not supported"), + Self::CheckFailed => write!( + f, + "the system returned an error while checking the color theme" + ), + Self::UnknownTheme => write!( + f, + "the system provided a theme other than `light` or `dark`" + ), + } + } +} + +type ThemeResult = Result; + +/// Get a signal to the system theme. +/// +/// On first run, the result will be [`ThemeError::Unsupported`]. This is to prevent hydration from failing. +/// After the client runs, the theme will be tracked and updated with accurate values. +/// +/// # Examples +/// +/// ```rust +/// use dioxus::prelude::*; +/// use dioxus_window::theme::{use_system_theme, Theme}; +/// +/// #[component] +/// fn App() -> Element { +/// let theme = use_system_theme(); +/// +/// rsx! { +/// p { +/// "the current theme is: {theme().unwrap_or(Theme::Light)}" +/// } +/// } +/// } +/// ``` +pub fn use_system_theme() -> ReadOnlySignal { + let mut system_theme = match try_use_context::>() { + Some(s) => s, + // This should only run once. + None => { + let signal = Signal::new_in_scope(Err(ThemeError::Unsupported), ScopeId::ROOT); + provide_root_context(signal) + } + }; + + // Only start the listener on the client. + use_effect(move || { + system_theme.set(get_theme()); + listen(system_theme); + }); + + use_hook(|| ReadOnlySignal::new(system_theme)) +} + +// The listener implementation for wasm targets. +#[cfg(target_family = "wasm")] +fn listen(mut theme: Signal) { + use wasm_bindgen::{JsCast, closure::Closure}; + use web_sys::MediaQueryList; + + let Some(window) = web_sys::window() else { + theme.set(Err(ThemeError::Unsupported)); + return; + }; + + // Get the media query + let Ok(query) = window.match_media("(prefers-color-scheme: dark)") else { + theme.set(Err(ThemeError::CheckFailed)); + return; + }; + + let Some(query) = query else { + theme.set(Err(ThemeError::UnknownTheme)); + return; + }; + + // Listener that is called when the media query changes. + // https://developer.mozilla.org/en-US/docs/Web/API/MediaQueryList/change_event + let listener = Closure::wrap(Box::new(move |query: MediaQueryList| { + match query.matches() { + true => theme.set(Ok(Theme::Dark)), + false => theme.set(Ok(Theme::Light)), + }; + }) as Box); + + let cb = listener.as_ref().clone(); + listener.forget(); + query.set_onchange(Some(cb.unchecked_ref())); +} + +// The listener implementation for desktop targets. (not linux) +// This should only be called once. +#[cfg(not(target_family = "wasm"))] +fn listen(mut theme: Signal) { + use dioxus_desktop::{ + WindowEvent, + tao::{event::Event, window::Theme as TaoTheme}, + window, + }; + + let window = window(); + + window.create_wry_event_handler(move |event, _| { + if let Event::WindowEvent { + event: WindowEvent::ThemeChanged(new_theme), + .. + } = event + { + match new_theme { + TaoTheme::Dark => theme.set(Ok(Theme::Dark)), + TaoTheme::Light => theme.set(Ok(Theme::Light)), + _ => theme.set(Err(ThemeError::UnknownTheme)), + }; + } + }); +} + +// The listener implementation for unsupported targets. +#[cfg(target_os = "linux")] +fn listen(mut theme: Signal) { + theme.set(Err(ThemeError::Unsupported)); +} + +/// Get the current theme. +/// +/// +/// **Note** +/// +/// This function will cause hydration to fail if not used inside an effect, task, or event handler. +/// +/// # Examples +/// +/// ```rust +/// use dioxus::prelude::*; +/// use dioxus_window::theme::{Theme, get_theme}; +/// +/// #[component] +/// fn App() -> Element { +/// let theme = use_signal(get_theme); +/// +/// let class_name = match theme().unwrap_or(Theme::Light) { +/// Theme::Dark => "dark-theme", +/// Theme::Light => "light-theme", +/// }; +/// +/// rsx! { +/// div { +/// style: "width: 100px; height: 100px;", +/// class: "{class_name}", +/// } +/// } +/// } +/// ``` +pub fn get_theme() -> ThemeResult { + get_theme_platform() +} + +// The wasm implementation to get the system theme. +#[cfg(target_family = "wasm")] +fn get_theme_platform() -> ThemeResult { + let Some(window) = web_sys::window() else { + return Err(ThemeError::Unsupported); + }; + + // Check the color theme with a media query + let Some(query) = window + .match_media("(prefers-color-scheme: dark)") + .or(Err(ThemeError::CheckFailed))? + else { + return Err(ThemeError::UnknownTheme); + }; + + match query.matches() { + true => Ok(Theme::Dark), + false => Ok(Theme::Light), + } +} + +// The desktop (except linux) implementation to get the system theme. +#[cfg(not(target_family = "wasm"))] +fn get_theme_platform() -> ThemeResult { + use dioxus_desktop::DesktopContext; + use dioxus_desktop::tao::window::Theme as TaoTheme; + + // Get window context and theme + let Some(window) = try_consume_context::() else { + return Err(ThemeError::Unsupported); + }; + let theme = window.theme(); + + match theme { + TaoTheme::Light => Ok(Theme::Light), + TaoTheme::Dark => Ok(Theme::Dark), + _ => Err(ThemeError::UnknownTheme), + } +} + +// Implementation for unsupported platforms. +#[cfg(not(any(target_family = "wasm", target_os = "windows", target_os = "macos")))] +fn get_theme_platform() -> ThemeResult { + Err(ThemeError::Unsupported) +} diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml deleted file mode 100644 index f1291f7..0000000 --- a/sdk/Cargo.toml +++ /dev/null @@ -1,203 +0,0 @@ -[package] -name = "dioxus-sdk" -version = "0.6.0" -authors = ["Jonathan Kelley", "Dioxus Labs", "ealmloff", "DogeDark", "marc2332"] -edition = "2021" -description = "Platform agnostic library for supercharging your productivity with Dioxus" -license = "MIT OR Apache-2.0" -readme = "../README.md" -repository = "https://github.com/DioxusLabs/sdk/" -homepage = "https://dioxuslabs.com" -keywords = ["dom", "gui", "dioxus", "standard", "hooks"] -categories = ["multimedia", "os", "wasm"] - - -# # # # # # # -# Features. # -# # # # # # # - -[features] -clipboard = ["dep:copypasta"] -notifications = ["dep:notify-rust"] -geolocation = [ - # Shared - "dep:futures", - "dep:futures-util", - - # Windows - "windows/Foundation", - "windows/Devices_Geolocation", - - # Wasm - "web-sys/Window", - "web-sys/Navigator", - "web-sys/Geolocation", - "web-sys/PositionOptions", - "dep:wasm-bindgen", -] -system_theme = [ - # Shared - "dep:futures", - - # Desktop (not linux) - "dep:dioxus-desktop", - - # Wasm - "web-sys/Window", - "web-sys/MediaQueryList", - "dep:wasm-bindgen", - "dep:wasm-bindgen-futures", -] -scroll = [ - "dep:serde", -] -window_size = [ - # Shared - "dep:futures-util", - - # Desktop - "dep:dioxus-desktop", - - # Wasm - "web-sys/Window", - "dep:wasm-bindgen", -] -channel = ["dep:async-broadcast", "uuid/v4"] -storage = [ - # Shared - "dep:rustc-hash", - "dep:ciborium", - "dep:once_cell", - "dep:dioxus-signals", - "dep:tokio", - "tokio/sync", - "dep:yazi", - "web-sys/Storage", - "web-sys/StorageEvent", - "dep:serde", - "dep:futures-util", - - # WASM - "dep:wasm-bindgen", - - # Not WASM - "dep:directories", -] -timing = [ - # Shared - "dep:futures", - - # Desktop - "dep:tokio", - "tokio/time", - - # Wasm - "dep:gloo-timers", -] - -# CI testing -wasm-testing = [ - "system_theme", - "geolocation", - "channel", - "window_size", - "timing", - "storage", -] -desktop-testing = [ - "system_theme", - "clipboard", - "notifications", - "geolocation", - "channel", - "window_size", - "timing", - "storage", -] - - -# # # # # # # # # # # # # # # -# Non Platform/Shared deps. # -# # # # # # # # # # # # # # # - -[dependencies] -dioxus = { workspace = true } -cfg-if = "1.0.0" -warnings = "0.2.0" - -# Used by: clipboard -copypasta = { version = "0.8.2", optional = true } - -# Used by: notifications -notify-rust = { version = "4.8.0", optional = true } - - -# Used by: channel -uuid = { version = "1.3.2", optional = true } -async-broadcast = { version = "0.5.1", optional = true } - -# Used by: geolocation, storage, timing, window_size, system_theme -futures = { version = "0.3.28", features = ["std"], optional = true } -futures-util = { version = "0.3.28", optional = true } - -# Used by: storage -rustc-hash = { version = "1.1.0", optional = true } -ciborium = { version = "0.2.2", optional = true } -once_cell = { version = "1.17.0", optional = true } -dioxus-signals = { workspace = true, features = [ - "serialize", -], optional = true } -serde = { version = "1.0.163", optional = true } - -yazi = { version = "0.1.4", optional = true } -tracing = "0.1.40" - -# Used by: timing & storage -tokio = { version = "1.33.0", optional = true } - -# # # # # # # # # -# Windows Deps. # -# # # # # # # # # - -[target.'cfg(windows)'.dependencies] - -# Used by: geolocation -windows = { version = "0.48.0", optional = true } - - -# # # # # # # -# WASM Deps # -# # # # # # # - -[target.'cfg(target_family = "wasm")'.dependencies] - -# Used by: color_scheme, geolocation, window_size -web-sys = { version = "0.3.60", optional = true } -wasm-bindgen = { version = "0.2.87", optional = true } -wasm-bindgen-futures = { version = "0.4.35", optional = true } - -# Used by: Geolocation -js-sys = "0.3.62" - -# Used by: channel -uuid = { version = "1.3.2", features = ["js"] } - -# Used by: timing -gloo-timers = { version = "0.3.0", optional = true, features = ["futures"] } - -[target.'cfg(not(target_family = "wasm"))'.dependencies] - -# Used by: storage -directories = { version = "4.0.1", optional = true } - -# Used by: window_size, system_theme -dioxus-desktop = { workspace = true, optional = true } - -# # # # # -# Docs. # -# # # # # - -[package.metadata.docs.rs] -targets = ["x86_64-pc-windows-msvc", "wasm32-unknown-unknown"] -no-default-features = true -features = ["desktop-testing", "wasm-testing"] diff --git a/sdk/src/clipboard/mod.rs b/sdk/src/clipboard/mod.rs deleted file mode 100644 index b5b48fc..0000000 --- a/sdk/src/clipboard/mod.rs +++ /dev/null @@ -1,10 +0,0 @@ -//! Interact with the clipboard. - -cfg_if::cfg_if! { - if #[cfg(not(target_family = "wasm"))] { - mod use_clipboard; - pub use use_clipboard::*; - } else { - compile_error!("the `clipboard` feature is only available on desktop targets"); - } -} diff --git a/sdk/src/clipboard/use_clipboard.rs b/sdk/src/clipboard/use_clipboard.rs deleted file mode 100644 index 81e164c..0000000 --- a/sdk/src/clipboard/use_clipboard.rs +++ /dev/null @@ -1,70 +0,0 @@ -//! Provides a clipboard abstraction to access the target system's clipboard. - -use copypasta::{ClipboardContext, ClipboardProvider}; -use dioxus::prelude::*; - -#[derive(Debug, PartialEq, Clone)] -pub enum ClipboardError { - FailedToRead, - FailedToSet, - NotAvailable, -} - -/// Handle to access the ClipboardContext. -#[derive(Clone, Copy, PartialEq)] -pub struct UseClipboard { - clipboard: Signal>, -} - -impl UseClipboard { - // Read from the clipboard - pub fn get(&mut self) -> Result { - self.clipboard - .write() - .as_mut() - .ok_or(ClipboardError::NotAvailable)? - .get_contents() - .map_err(|_| ClipboardError::FailedToRead) - } - - // Write to the clipboard - pub fn set(&mut self, contents: String) -> Result<(), ClipboardError> { - self.clipboard - .write() - .as_mut() - .ok_or(ClipboardError::NotAvailable)? - .set_contents(contents) - .map_err(|_| ClipboardError::FailedToSet) - } -} - -/// Access the clipboard. -/// -/// # Examples -/// -/// ```rust,ignore -/// use dioxus_sdk::clipboard::use_clipboard; -/// -/// // Get a handle to the clipboard -/// let mut clipboard = use_clipboard(); -/// -/// // Read the clipboard content -/// if let Ok(content) = clipboard.get() { -/// println!("{}", content); -/// } -/// -/// // Write to the clipboard -/// clipboard.set("Hello, Dioxus!".to_string());; -/// -/// ``` -pub fn use_clipboard() -> UseClipboard { - let clipboard = match try_consume_context() { - Some(rt) => rt, - None => { - let clipboard_signal = - Signal::new_in_scope(ClipboardContext::new().ok(), ScopeId::ROOT); - provide_root_context(clipboard_signal) - } - }; - UseClipboard { clipboard } -} diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs deleted file mode 100644 index 3009aa3..0000000 --- a/sdk/src/lib.rs +++ /dev/null @@ -1,37 +0,0 @@ -//#![warn(missing_debug_implementations, missing_docs)] - -cfg_if::cfg_if! { - if #[cfg(feature = "system_theme")] { - pub mod theme; - } -} - -cfg_if::cfg_if! { - if #[cfg(feature = "geolocation")] { - pub mod geolocation; - } -} - -cfg_if::cfg_if! { - if #[cfg(any(feature = "channel", feature = "scroll", feature = "window_size", feature = "timing"))] { - pub mod utils; - } -} - -cfg_if::cfg_if! { - if #[cfg(feature = "clipboard")] { - pub mod clipboard; - } -} - -cfg_if::cfg_if! { - if #[cfg(feature = "storage")] { - pub mod storage; - } -} - -cfg_if::cfg_if! { - if #[cfg(feature = "notifications")] { - pub mod notification; - } -} diff --git a/sdk/src/notification/desktop.rs b/sdk/src/notification/desktop.rs deleted file mode 100644 index f4b92b7..0000000 --- a/sdk/src/notification/desktop.rs +++ /dev/null @@ -1,140 +0,0 @@ -//! Provides a notification abstraction to access the target system's notification feature. - -use notify_rust::Timeout; -use std::fmt; - -/// Provides a builder API and contains relevant notification info. -/// -/// # Examples -/// -/// ``` -/// use dioxus_sdk::notification::Notification; -/// -/// Notification::new() -/// .app_name("dioxus test".to_string()) -/// .summary("hi, this is dioxus test".to_string()) -/// .body("lorem ipsum??".to_string()) -/// .show() -/// .unwrap(); -/// -/// ``` -#[derive(Debug)] -pub struct Notification { - pub app_name: String, - pub summary: String, - pub body: String, - pub icon_path: String, - pub timeout: NotificationTimeout, -} - -/// Represents the notification's timeout. -#[derive(Debug, PartialEq, Clone)] -pub enum NotificationTimeout { - /// Default depends on the target OS. - Default, - Never, - Milliseconds(u32), -} - -impl From for Timeout { - fn from(value: NotificationTimeout) -> Self { - match value { - NotificationTimeout::Default => Timeout::Default, - NotificationTimeout::Never => Timeout::Never, - NotificationTimeout::Milliseconds(ms) => Timeout::Milliseconds(ms), - } - } -} - -impl Notification { - /// Creates a new notification with empty/default values. - pub fn new() -> Self { - Notification { - app_name: "".to_string(), - summary: "".to_string(), - body: "".to_string(), - icon_path: "".to_string(), - timeout: NotificationTimeout::Default, - } - } - - /// Show the final notification. - pub fn show(&self) -> Result<(), NotificationError> { - let result = notify_rust::Notification::new() - .appname(&self.app_name) - .summary(&self.summary) - .body(&self.body) - .icon(&self.icon_path) - .timeout(self.timeout.clone()) - .show(); - - match result { - Ok(_) => Ok(()), - Err(e) => Err(NotificationError::FailedToShowNotification(e.to_string())), - } - } - - // Setters - /// Set the application's name for the notification. - pub fn app_name(&mut self, value: String) -> &mut Self { - self.app_name = value; - self - } - - /// Set the summary content of the notification. - pub fn summary(&mut self, value: String) -> &mut Self { - self.summary = value; - self - } - - /// Set the body content of the notification. - pub fn body(&mut self, value: String) -> &mut Self { - self.body = value; - self - } - - /// Set full path to image. - /// Only works on Linux. - pub fn icon_path(&mut self, value: String) -> &mut Self { - self.icon_path = value; - self - } - - /// Set a timeout for when the notification should hide. - pub fn timeout(&mut self, value: NotificationTimeout) -> &mut Self { - self.timeout = value; - self - } -} - -impl Default for Notification { - fn default() -> Self { - Self::new() - } -} - -#[test] -fn test_notification() { - Notification::new() - .app_name("dioxus test".to_string()) - .summary("hi, this is dioxus test".to_string()) - .body("lorem ipsum??".to_string()) - .show() - .unwrap(); -} - -/// Represents errors when utilizing the notification abstraction. -#[derive(Debug)] -pub enum NotificationError { - /// Failure to show a notification. - FailedToShowNotification(String), -} - -impl std::error::Error for NotificationError {} -impl fmt::Display for NotificationError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - NotificationError::FailedToShowNotification(s) => write!(f, "{}", s), - } - } -} diff --git a/sdk/src/notification/mod.rs b/sdk/src/notification/mod.rs deleted file mode 100644 index 2e6851c..0000000 --- a/sdk/src/notification/mod.rs +++ /dev/null @@ -1,10 +0,0 @@ -//! Send desktop notifications. - -cfg_if::cfg_if! { - if #[cfg(not(target_family = "wasm"))] { - mod desktop; - pub use desktop::*; - } else { - compile_error!("the `notification` feature is only available on desktop targets"); - } -} diff --git a/sdk/src/theme/mod.rs b/sdk/src/theme/mod.rs deleted file mode 100644 index 0a92813..0000000 --- a/sdk/src/theme/mod.rs +++ /dev/null @@ -1,10 +0,0 @@ -//! Interact with the system theme. - -cfg_if::cfg_if! { - if #[cfg(any(target_family = "wasm", target_os = "windows", target_os = "macos"))] { - mod system_theme; - pub use system_theme::*; - } else { - compile_error!("the `color_scheme` feature is only available on wasm, windows, and macos targets"); - } -} diff --git a/sdk/src/theme/system_theme.rs b/sdk/src/theme/system_theme.rs deleted file mode 100644 index 6cae96f..0000000 --- a/sdk/src/theme/system_theme.rs +++ /dev/null @@ -1,221 +0,0 @@ -//! Utilities to get and subscribe to the system theme. - -use dioxus::prelude::*; -use std::{error::Error, fmt::Display}; - -/// Represents the system theme. -/// -/// For any themes other than `light` and `dark`, a [`SystemThemeError::UnknownTheme`] will be returned. -/// We may be able to support custom themes in the future. -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum SystemTheme { - Light, - Dark, -} - -impl Display for SystemTheme { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - match self { - Self::Light => write!(f, "light"), - Self::Dark => write!(f, "dark"), - } - } -} - -/// Represents an error with system theme utilities. -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum SystemThemeError { - /// System theme is not supported on this platform. - NotSupported, - /// Failed to get the system theme. - CheckFailed, - /// System returned an unknown theme. - UnknownTheme, -} - -impl Error for SystemThemeError {} -impl Display for SystemThemeError { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - match self { - Self::NotSupported => write!(f, "the current platform is not supported"), - Self::CheckFailed => write!( - f, - "the system returned an error while checking the color theme" - ), - Self::UnknownTheme => write!( - f, - "the system provided a theme other than `light` or `dark`" - ), - } - } -} - -type SystemThemeResult = Result; - -/// A hook for receiving the system theme. -/// -/// The initial theme will be returned and updated if the system theme changes. -/// -/// # Example -/// -/// ```rust -/// use dioxus::prelude::*; -/// use dioxus_sdk::theme::use_system_theme; -/// -/// fn App() -> Element { -/// let theme = use_system_theme(); -/// -/// rsx! { -/// p { -/// "the current theme is: {theme().unwrap()}" -/// } -/// } -/// } -/// ``` -pub fn use_system_theme() -> ReadOnlySignal { - let system_theme = match try_use_context::>() { - Some(s) => s, - // This should only run once. - None => { - let signal = Signal::new_in_scope(get_system_theme(), ScopeId::ROOT); - let theme = provide_root_context(signal); - listen(theme); - theme - } - }; - - use_hook(|| ReadOnlySignal::new(system_theme)) -} - -/// The listener implementation for wasm targets. -/// This should only be called once. -#[cfg(target_family = "wasm")] -fn listen(mut theme: Signal) { - use wasm_bindgen::{closure::Closure, JsCast}; - use web_sys::MediaQueryList; - - let Some(window) = web_sys::window() else { - theme.set(Err(SystemThemeError::NotSupported)); - return; - }; - - // Get the media query - let Ok(query) = window.match_media("(prefers-color-scheme: dark)") else { - theme.set(Err(SystemThemeError::CheckFailed)); - return; - }; - - let Some(query) = query else { - theme.set(Err(SystemThemeError::UnknownTheme)); - return; - }; - - // Listener that is called when the media query changes. - // https://developer.mozilla.org/en-US/docs/Web/API/MediaQueryList/change_event - let listener = Closure::wrap(Box::new(move |query: MediaQueryList| { - match query.matches() { - true => theme.set(Ok(SystemTheme::Dark)), - false => theme.set(Ok(SystemTheme::Light)), - }; - }) as Box); - - let cb = listener.as_ref().clone(); - listener.forget(); - query.set_onchange(Some(cb.unchecked_ref())); -} - -/// The listener implementation for desktop targets. (not linux) -/// This should only be called once. -#[cfg(not(target_family = "wasm"))] -fn listen(mut theme: Signal) { - use dioxus_desktop::{ - tao::{event::Event, window::Theme}, - window, WindowEvent, - }; - - let window = window(); - - window.create_wry_event_handler(move |event, _| { - if let Event::WindowEvent { - event: WindowEvent::ThemeChanged(new_theme), - .. - } = event - { - match new_theme { - Theme::Dark => theme.set(Ok(SystemTheme::Dark)), - Theme::Light => theme.set(Ok(SystemTheme::Light)), - _ => theme.set(Err(SystemThemeError::UnknownTheme)), - }; - } - }); -} - -/// Get the current system theme. -/// -/// This function will try to get the current system theme. -/// -/// # Example -/// -/// ```rust -/// use dioxus::prelude::*; -/// use dioxus_sdk::theme::{SystemTheme, get_system_theme}; -/// -/// fn App() -> Element { -/// let theme = use_signal(get_system_theme); -/// -/// let class_name = match theme().unwrap() { -/// SystemTheme::Dark => "dark-theme", -/// SystemTheme::Light => "light-theme", -/// }; -/// -/// rsx! { -/// div { -/// style: "width: 100px; height: 100px;", -/// class: "{class_name}", -/// } -/// } -/// } -/// ``` -pub fn get_system_theme() -> SystemThemeResult { - get_system_theme_platform() -} - -/// The wasm implementation to get the system theme. -#[cfg(target_family = "wasm")] -fn get_system_theme_platform() -> SystemThemeResult { - let Some(window) = web_sys::window() else { - return Err(SystemThemeError::NotSupported); - }; - - // Check the color theme with a media query - let Some(query) = window - .match_media("(prefers-color-scheme: dark)") - .or(Err(SystemThemeError::CheckFailed))? - else { - return Err(SystemThemeError::UnknownTheme); - }; - - match query.matches() { - true => Ok(SystemTheme::Dark), - false => Ok(SystemTheme::Light), - } -} - -/// The desktop (except linux) implementation to get the system theme. -#[cfg(not(target_family = "wasm"))] -fn get_system_theme_platform() -> SystemThemeResult { - use dioxus_desktop::tao::window::Theme; - use dioxus_desktop::DesktopContext; - - // Get window context and theme - let Some(window) = try_consume_context::() else { - return Err(SystemThemeError::NotSupported); - }; - let theme = window.theme(); - - match theme { - Theme::Light => Ok(SystemTheme::Light), - Theme::Dark => Ok(SystemTheme::Dark), - _ => Err(SystemThemeError::UnknownTheme), - } -} diff --git a/sdk/src/utils/channel/mod.rs b/sdk/src/utils/channel/mod.rs deleted file mode 100644 index ccbc3bd..0000000 --- a/sdk/src/utils/channel/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -//! Channels for moving data around your app. - -mod use_channel; -mod use_listen_channel; - -pub use use_channel::*; -pub use use_listen_channel::*; diff --git a/sdk/src/utils/mod.rs b/sdk/src/utils/mod.rs deleted file mode 100644 index 41b3ff8..0000000 --- a/sdk/src/utils/mod.rs +++ /dev/null @@ -1,25 +0,0 @@ -//! A variety of utility functions and hooks. - -cfg_if::cfg_if! { - if #[cfg(feature = "channel")] { - pub mod channel; - } -} - -cfg_if::cfg_if! { - if #[cfg(feature = "window_size")] { - pub mod window; - } -} - -cfg_if::cfg_if! { - if #[cfg(feature = "scroll")] { - pub mod scroll; - } -} - -cfg_if::cfg_if! { - if #[cfg(feature = "timing")] { - pub mod timing; - } -} diff --git a/sdk/src/utils/timing/debounce.rs b/sdk/src/utils/timing/debounce.rs deleted file mode 100644 index d6acbaf..0000000 --- a/sdk/src/utils/timing/debounce.rs +++ /dev/null @@ -1,94 +0,0 @@ -use dioxus::prelude::*; -use futures::{ - channel::mpsc::{self, UnboundedSender as Sender}, - StreamExt, -}; -use std::time::Duration; - -/// The interface for calling a debounce. -/// -/// See [`use_debounce`] for more information. -pub struct UseDebounce { - sender: Signal>, -} - -impl UseDebounce { - /// Will start the debounce countdown, resetting it if already started. - pub fn action(&mut self, data: T) { - self.sender.write().unbounded_send(data).ok(); - } -} - -// Manually implement Clone, Copy, and PartialEq as #[derive] thinks that T needs to implement these (it doesn't). - -impl Clone for UseDebounce { - fn clone(&self) -> Self { - *self - } -} - -impl Copy for UseDebounce {} - -impl PartialEq for UseDebounce { - fn eq(&self, other: &Self) -> bool { - self.sender == other.sender - } -} - -/// A hook for allowing a function to be called only after a provided [`Duration`] has passed. -/// -/// Once the [`UseDebounce::action`] method is called, a timer will start counting down until -/// the callback is ran. If the [`UseDebounce::action`] method is called again, the timer will restart. -/// -/// # Example -/// -/// ```rust -/// use dioxus::prelude::*; -/// use dioxus_sdk::utils::timing::use_debounce; -/// use std::time::Duration; -/// -/// fn App() -> Element { -/// let mut debounce = use_debounce(Duration::from_millis(2000), |_| println!("ran")); -/// -/// rsx! { -/// button { -/// onclick: move |_| { -/// debounce.action(()); -/// }, -/// "Click!" -/// } -/// } -/// } -/// ``` -pub fn use_debounce(time: Duration, cb: impl FnOnce(T) + Copy + 'static) -> UseDebounce { - use_hook(|| { - let (sender, mut receiver) = mpsc::unbounded(); - let debouncer = UseDebounce { - sender: Signal::new(sender), - }; - - spawn(async move { - let mut current_task: Option = None; - - loop { - if let Some(data) = receiver.next().await { - if let Some(task) = current_task.take() { - task.cancel(); - } - - current_task = Some(spawn(async move { - #[cfg(not(target_family = "wasm"))] - tokio::time::sleep(time).await; - - #[cfg(target_family = "wasm")] - gloo_timers::future::sleep(time).await; - - cb(data); - })); - } - } - }); - - debouncer - }) -} diff --git a/sdk/src/utils/timing/interval.rs b/sdk/src/utils/timing/interval.rs deleted file mode 100644 index 49fe40c..0000000 --- a/sdk/src/utils/timing/interval.rs +++ /dev/null @@ -1,56 +0,0 @@ -use dioxus::prelude::{use_hook, Callback, Writable}; -use std::time::Duration; - -#[derive(Clone, PartialEq, Copy)] -pub struct UseInterval { - inner: dioxus::prelude::Signal, -} - -struct InnerUseInterval { - pub(crate) interval: Option, -} - -impl Drop for InnerUseInterval { - fn drop(&mut self) { - if let Some(interval) = self.interval.take() { - interval.cancel(); - } - } -} - -impl UseInterval { - /// Cancel the interval - pub fn cancel(&mut self) { - if let Some(interval) = self.inner.write().interval.take() { - interval.cancel(); - } - } -} - -/// Repeatedly calls a function every a certain period. -pub fn use_interval(period: Duration, mut action: impl FnMut() + 'static) -> UseInterval { - let inner = use_hook(|| { - let callback = Callback::new(move |()| { - action(); - }); - - dioxus::prelude::Signal::new(InnerUseInterval { - interval: Some(dioxus::prelude::spawn(async move { - #[cfg(not(target_family = "wasm"))] - let mut interval = tokio::time::interval(period); - - loop { - #[cfg(not(target_family = "wasm"))] - interval.tick().await; - - #[cfg(target_family = "wasm")] - gloo_timers::future::sleep(period).await; - - callback.call(()); - } - })), - }) - }); - - UseInterval { inner } -} diff --git a/sdk/src/utils/timing/mod.rs b/sdk/src/utils/timing/mod.rs deleted file mode 100644 index 113f4a4..0000000 --- a/sdk/src/utils/timing/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -//! Timing utilities. - -mod interval; -pub use interval::*; - -mod debounce; -pub use debounce::*; diff --git a/sdk/src/utils/window.rs b/sdk/src/utils/window.rs deleted file mode 100644 index d37138b..0000000 --- a/sdk/src/utils/window.rs +++ /dev/null @@ -1,166 +0,0 @@ -//! Utilities for the window. - -use dioxus::prelude::{ - provide_root_context, try_use_context, use_hook, warnings::signal_write_in_component_body, - ReadOnlySignal, ScopeId, Signal, Writable, -}; -use std::sync::Once; -use warnings::Warning as _; - -#[allow(dead_code)] -static INIT: Once = Once::new(); - -/// Stores the width and height of a window, screen, or viewport. -#[derive(Clone, Copy, Debug)] -pub struct WindowSize { - /// The horizontal size in pixels. - pub width: u32, - /// The vertical size in pixels. - pub height: u32, -} - -/// A hook for receiving the size of the Window. -/// -/// The initial window size will be returned with this hook and -/// updated continously as the window is resized. -/// -/// # Example -/// -/// ```rust -/// use dioxus::prelude::*; -/// use dioxus_sdk::utils::window::use_window_size; -/// -/// fn App() -> Element { -/// let size = use_window_size(); -/// -/// rsx! { -/// p { "Width: {size().width}" } -/// p { "Height: {size().height}" } -/// } -/// } -/// ``` -pub fn use_window_size() -> ReadOnlySignal { - let window_size = match try_use_context::>() { - Some(w) => w, - // This should only run once. - None => { - let signal = Signal::new_in_scope(get_window_size(), ScopeId::ROOT); - let size = provide_root_context(signal); - listen(size); - - size - } - }; - - use_hook(|| ReadOnlySignal::new(window_size)) -} - -// Listener for the web implementation. -#[cfg(target_family = "wasm")] -fn listen(mut window_size: Signal) { - use wasm_bindgen::{closure::Closure, JsCast, JsValue}; - - INIT.call_once(|| { - let window = web_sys::window().expect("no wasm window found; are you in wasm?"); - let window2 = window.clone(); - - // We will fail silently for conversion errors. - let on_resize = Closure::wrap(Box::new(move || { - let height = window2 - .inner_height() - .unwrap_or(JsValue::from_f64(0.0)) - .as_f64() - .unwrap_or(0.0) as u32; - - let width = window2 - .inner_width() - .unwrap_or(JsValue::from_f64(0.0)) - .as_f64() - .unwrap_or(0.0) as u32; - - signal_write_in_component_body::allow(move || { - window_size.set(WindowSize { width, height }); - }); - }) as Box); - - let on_resize_cb = on_resize.as_ref().clone(); - on_resize.forget(); - window.set_onresize(Some(on_resize_cb.unchecked_ref())); - }); -} - -// Listener for anything but the web implementation. -#[cfg(not(target_family = "wasm"))] -fn listen(mut window_size: Signal) { - use dioxus_desktop::{tao::event::Event, window, WindowEvent}; - - let window = window(); - window.create_wry_event_handler(move |event, _| { - if let Event::WindowEvent { - event: WindowEvent::Resized(size), - .. - } = event - { - signal_write_in_component_body::allow(move || { - window_size.set(WindowSize { - width: size.width, - height: size.height, - }); - }); - } - }); -} - -/// Get the size of the current window. -/// -/// This function will return the current size of the window. -/// -/// ```rust -/// use dioxus::prelude::*; -/// use dioxus_sdk::utils::window::get_window_size; -/// -/// fn App() -> Element { -/// let size = use_signal(get_window_size); -/// -/// rsx! { -/// p { "Width: {size().width}" } -/// p { "Height: {size().height}" } -/// } -/// } -/// ``` -pub fn get_window_size() -> WindowSize { - get_window_size_platform() -} - -// Web implementation of size getter. -#[cfg(target_family = "wasm")] -fn get_window_size_platform() -> WindowSize { - use wasm_bindgen::JsValue; - let window = web_sys::window().expect("no wasm window found; are you in wasm?"); - - // We will fail silently for conversion errors. - let height = window - .inner_height() - .unwrap_or(JsValue::from_f64(0.0)) - .as_f64() - .unwrap_or(0.0) as u32; - - let width = window - .inner_width() - .unwrap_or(JsValue::from_f64(0.0)) - .as_f64() - .unwrap_or(0.0) as u32; - - WindowSize { width, height } -} - -// Desktop implementation of size getter. -#[cfg(not(target_family = "wasm"))] -fn get_window_size_platform() -> WindowSize { - let window = dioxus_desktop::window(); - let size = window.inner_size(); - WindowSize { - width: size.width, - height: size.height, - } -}