From 41bf87e74f45e672cd4bb0364b27a4aca9561232 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 14 Mar 2023 16:21:39 -0500 Subject: [PATCH] Merge branch 'upstream' into simplify-native-core --- .github/workflows/docs stable.yml | 4 +- .github/workflows/docs.yml | 4 +- README.md | 2 +- docs/guide/examples/hooks_composed.rs | 2 +- docs/guide/src/en/getting_started/desktop.md | 2 +- .../src/en/interactivity/custom_hooks.md | 2 +- docs/posts/release.md | 4 +- docs/reference/src/README_pt-br.md | 2 +- docs/reference/src/platforms/desktop.md | 4 +- docs/router/src/README.md | 11 +- examples/shortcut.rs | 17 + notes/README/ZH_CN.md | 2 +- packages/autofmt/Cargo.toml | 2 +- packages/core-macro/Cargo.toml | 2 +- packages/core-macro/README.md | 2 +- packages/core-macro/src/inlineprops.rs | 21 +- packages/core/Cargo.toml | 2 +- packages/core/src/arena.rs | 6 +- packages/core/src/create.rs | 23 +- packages/core/src/diff.rs | 4 +- packages/core/src/scheduler/task.rs | 6 +- packages/core/src/scopes.rs | 7 +- packages/core/src/virtual_dom.rs | 2 +- packages/desktop/README.md | 4 +- packages/desktop/src/desktop_context.rs | 36 ++ packages/desktop/src/lib.rs | 9 + packages/desktop/src/readme.md | 4 +- packages/desktop/src/shortcut.rs | 332 ++++++++++++++++++ packages/dioxus/Cargo.toml | 4 +- packages/dioxus/README.md | 5 +- packages/fermi/src/root.rs | 4 +- packages/hooks/Cargo.toml | 1 + packages/hooks/README.md | 2 +- packages/hooks/src/use_shared_state.rs | 227 +++++++----- packages/hooks/src/usecoroutine.rs | 6 + packages/hooks/src/usestate.rs | 2 +- packages/hot-reload/Cargo.toml | 12 +- packages/hot-reload/README.md | 2 +- packages/hot-reload/src/lib.rs | 60 +++- packages/html/Cargo.toml | 4 +- packages/html/README.md | 2 +- packages/interpreter/Cargo.toml | 2 +- packages/interpreter/README.md | 2 +- packages/interpreter/src/interpreter.js | 14 +- packages/liveview/README.md | 2 +- packages/liveview/examples/axum.rs | 2 +- packages/liveview/examples/salvo.rs | 2 +- packages/liveview/src/pool.rs | 22 +- packages/mobile/README.md | 4 +- packages/router/README.md | 4 +- packages/rsx-rosetta/Cargo.toml | 2 +- packages/rsx-rosetta/README.md | 2 +- packages/rsx/Cargo.toml | 5 +- packages/rsx/README.md | 2 +- .../src/hot_reload/hot_reloading_file_map.rs | 104 +++++- packages/ssr/Cargo.toml | 1 + packages/ssr/README.md | 7 +- packages/ssr/src/cache.rs | 8 +- packages/ssr/src/renderer.rs | 17 +- packages/ssr/tests/simple.rs | 2 +- packages/web/Cargo.toml | 2 +- packages/web/README.md | 2 +- translations/pt-br/README.md | 6 +- 63 files changed, 818 insertions(+), 245 deletions(-) create mode 100644 examples/shortcut.rs create mode 100644 packages/desktop/src/shortcut.rs diff --git a/.github/workflows/docs stable.yml b/.github/workflows/docs stable.yml index 895e7e8d0..2790b1d51 100644 --- a/.github/workflows/docs stable.yml +++ b/.github/workflows/docs stable.yml @@ -24,7 +24,9 @@ jobs: - name: Build run: cd docs && cd guide && mdbook build -d ../nightly/guide && cd .. && - cd router && mdbook build -d ../nightly/router && cd .. + cd router && mdbook build -d ../nightly/router && cd .. + # cd reference && mdbook build -d ../nightly/reference && cd .. && + # cd fermi && mdbook build -d ../nightly/fermi && cd .. - name: Deploy 🚀 uses: JamesIves/github-pages-deploy-action@v4.4.1 diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index f69053742..07a8b462b 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -29,7 +29,9 @@ jobs: - name: Build run: cd docs && cd guide && mdbook build -d ../nightly/guide && cd .. && - cd router && mdbook build -d ../nightly/router && cd .. + cd router && mdbook build -d ../nightly/router && cd .. + # cd reference && mdbook build -d ../nightly/reference && cd .. && + # cd fermi && mdbook build -d ../nightly/fermi && cd .. - name: Deploy 🚀 uses: JamesIves/github-pages-deploy-action@v4.4.1 diff --git a/README.md b/README.md index 4ef6acfee..40981401e 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ | Examples | - Guide + Guide | äž­æ–‡ | diff --git a/docs/guide/examples/hooks_composed.rs b/docs/guide/examples/hooks_composed.rs index 5b1f5ebfe..801474aa3 100644 --- a/docs/guide/examples/hooks_composed.rs +++ b/docs/guide/examples/hooks_composed.rs @@ -7,7 +7,7 @@ fn main() {} struct AppSettings {} // ANCHOR: wrap_context -fn use_settings(cx: &ScopeState) -> UseSharedState { +fn use_settings(cx: &ScopeState) -> &UseSharedState { use_shared_state::(cx).expect("App settings not provided") } // ANCHOR_END: wrap_context diff --git a/docs/guide/src/en/getting_started/desktop.md b/docs/guide/src/en/getting_started/desktop.md index c86d7760e..311ad2c9f 100644 --- a/docs/guide/src/en/getting_started/desktop.md +++ b/docs/guide/src/en/getting_started/desktop.md @@ -14,7 +14,7 @@ Examples: The desktop is a powerful target for Dioxus but is currently limited in capability when compared to the Web platform. Currently, desktop apps are rendered with the platform's WebView library, but your Rust code is running natively on a native thread. This means that browser APIs are *not* available, so rendering WebGL, Canvas, etc is not as easy as the Web. However, native system APIs *are* accessible, so streaming, WebSockets, filesystem, etc are all viable APIs. In the future, we plan to move to a custom web renderer-based DOM renderer with WGPU integrations. -Dioxus Desktop is built off [Tauri](https://tauri.app/). Right now there aren't any Dioxus abstractions over keyboard shortcuts, menubar, handling, etc, so you'll want to leverage Tauri – mostly [Wry](http://github.com/tauri-apps/wry/) and [Tao](http://github.com/tauri-apps/tao)) directly. +Dioxus Desktop is built off [Tauri](https://tauri.app/). Right now there aren't any Dioxus abstractions over the menubar, handling, etc, so you'll want to leverage Tauri – mostly [Wry](http://github.com/tauri-apps/wry/) and [Tao](http://github.com/tauri-apps/tao)) directly. # Getting started diff --git a/docs/guide/src/en/interactivity/custom_hooks.md b/docs/guide/src/en/interactivity/custom_hooks.md index 2f0155cb5..ebf4f94db 100644 --- a/docs/guide/src/en/interactivity/custom_hooks.md +++ b/docs/guide/src/en/interactivity/custom_hooks.md @@ -14,7 +14,7 @@ For example, if many components need to access an `AppSettings` struct, you can ## Custom Hook Logic -You can use [`cx.use_hook`](https://docs.rs/dioxus/latest/dioxus/prelude/struct.Scope.html#method.use_hook) to build your own hooks. In fact, this is what all the standard hooks are built on! +You can use [`cx.use_hook`](https://docs.rs/dioxus/latest/dioxus/prelude/struct.ScopeState.html#method.use_hook) to build your own hooks. In fact, this is what all the standard hooks are built on! `use_hook` accepts a single closure for initializing the hook. It will be only run the first time the component is rendered. The return value of that closure will be used as the value of the hook – Dioxus will take it, and store it for as long as the component is alive. On every render (not just the first one!), you will get a reference to this value. diff --git a/docs/posts/release.md b/docs/posts/release.md index b40b451cc..171664dd3 100644 --- a/docs/posts/release.md +++ b/docs/posts/release.md @@ -63,7 +63,7 @@ This very site is built with Dioxus, and the source code is available [here](htt To get started with Dioxus, check out any of the "Getting Started" guides for your platform of choice, or check out the GitHub Repository for more details. -- [Getting Started with Dioxus](https://dioxuslabs.com/guide) +- [Getting Started with Dioxus](https://dioxuslabs.com/guide/en) - [Getting Started with Web](https://dioxuslabs.com/reference/web) - [Getting Started with Desktop](https://dioxuslabs.com/reference/desktop) - [Getting Started with Mobile](https://dioxuslabs.com/reference/mobile) @@ -163,7 +163,7 @@ Today, to publish a Dioxus app, you don't need NPM/WebPack/Parcel/etc. Dioxus si ## Show me more -Here, we'll dive into some features of Dioxus and why it's so fun to use. The [guide](https://dioxuslabs.com/guide/) serves as a deeper and more comprehensive look at what Dioxus can do. +Here, we'll dive into some features of Dioxus and why it's so fun to use. The [guide](https://dioxuslabs.com/guide/en/) serves as a deeper and more comprehensive look at what Dioxus can do. ## Building a new project is simple diff --git a/docs/reference/src/README_pt-br.md b/docs/reference/src/README_pt-br.md index c345da6bd..e76f43516 100644 --- a/docs/reference/src/README_pt-br.md +++ b/docs/reference/src/README_pt-br.md @@ -4,7 +4,7 @@ **Dioxus** Ă© um framework e ecossistema para desenvolver interfaces rĂĄpidas, escalĂĄveis e robustas com a linguagem de Programação Rust. Este guia irĂĄ ajudar vocĂȘ a começar com o Dioxus para Web, Desktop, MĂłvel e mais. -> Este livro Ă© a ReferĂȘncia e Guias Avançados para o framework Dioxus. Para um tutorial em como de fato _usar_ o Dioxus, procure o [guia oficial](https://dioxuslabs.com/guide/). +> Este livro Ă© a ReferĂȘncia e Guias Avançados para o framework Dioxus. Para um tutorial em como de fato _usar_ o Dioxus, procure o [guia oficial](https://dioxuslabs.com/guide/en/). ## Guias e ReferĂȘncia diff --git a/docs/reference/src/platforms/desktop.md b/docs/reference/src/platforms/desktop.md index d444b90e4..dcdcca778 100644 --- a/docs/reference/src/platforms/desktop.md +++ b/docs/reference/src/platforms/desktop.md @@ -2,7 +2,7 @@ One of Dioxus' killer features is the ability to quickly build a native desktop app that looks and feels the same across platforms. Apps built with Dioxus are typically <5mb in size and use existing system resources, so they won't hog extreme amounts of RAM or memory. -Dioxus Desktop is built off Tauri. Right now there aren't any Dioxus abstractions over keyboard shortcuts, menubar, handling, etc, so you'll want to leverage Tauri - mostly [Wry](http://github.com/tauri-apps/wry/) and [Tao](http://github.com/tauri-apps/tao)) directly. The next major release of Dioxus-Desktop will include components and hooks for notifications, global shortcuts, menubar, etc. +Dioxus Desktop is built off Tauri. Right now there aren't any Dioxus abstractions over the menubar, handling, etc, so you'll want to leverage Tauri - mostly [Wry](http://github.com/tauri-apps/wry/) and [Tao](http://github.com/tauri-apps/tao)) directly. The next major release of Dioxus-Desktop will include components and hooks for notifications, global shortcuts, menubar, etc. ## Getting Set up @@ -42,4 +42,4 @@ To configure the webview, menubar, and other important desktop-specific features ## Future Steps -Make sure to read the [Dioxus Guide](https://dioxuslabs.com/guide) if you already haven't! +Make sure to read the [Dioxus Guide](https://dioxuslabs.com/guide/en) if you already haven't! diff --git a/docs/router/src/README.md b/docs/router/src/README.md index fcbd79aef..055c3009b 100644 --- a/docs/router/src/README.md +++ b/docs/router/src/README.md @@ -1,10 +1,11 @@ # Dioxus Router: Introduction + Whether or not you're building a website, desktop app, or mobile app, organizing your app's views into "pages" can be an effective method for organization and maintainability. -Dioxus comes with a router built-in. To start utilizing Dioxus Router, enable the ``router`` feature in your ``Cargo.toml`` file. -```toml -[dependencies] -dioxus = { version = "x.x.x", features = [.., "router"] } -``` +The `dioxus-router` crate contains the Router module. To add it to your project run: + + cargo add dioxus-router + +> **Be sure to include the `web` feature (`--feature web`) for deployment into a browser!** In this book you'll find a short [guide](./guide/index.md) to get up to speed with Dioxus Router, as well as the router's [reference](./reference/index.md). \ No newline at end of file diff --git a/examples/shortcut.rs b/examples/shortcut.rs new file mode 100644 index 000000000..d98a69ad6 --- /dev/null +++ b/examples/shortcut.rs @@ -0,0 +1,17 @@ +use dioxus::prelude::*; +use dioxus_desktop::tao::keyboard::ModifiersState; +use dioxus_desktop::use_global_shortcut; + +fn main() { + dioxus_desktop::launch(app); +} + +fn app(cx: Scope) -> Element { + let toggled = use_state(cx, || false); + use_global_shortcut(cx, KeyCode::S, ModifiersState::CONTROL, { + to_owned![toggled]; + move || toggled.modify(|t| !*t) + }); + + cx.render(rsx!("toggle: {toggled.get()}")) +} diff --git a/notes/README/ZH_CN.md b/notes/README/ZH_CN.md index 8b1e6185b..ca37f7be1 100644 --- a/notes/README/ZH_CN.md +++ b/notes/README/ZH_CN.md @@ -40,7 +40,7 @@ | 代码ç€ș䟋 | - ćŒ€ć‘æŒ‡ć— + ćŒ€ć‘æŒ‡ć— | English | diff --git a/packages/autofmt/Cargo.toml b/packages/autofmt/Cargo.toml index a1a4592d8..e409cfb2f 100644 --- a/packages/autofmt/Cargo.toml +++ b/packages/autofmt/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -dioxus-rsx = { path = "../rsx", version = "0.0.2" } +dioxus-rsx = { path = "../rsx", version = "^0.0.3" } proc-macro2 = { version = "1.0.6", features = ["span-locations"] } quote = "1.0" syn = { version = "1.0.11", features = ["full", "extra-traits"] } diff --git a/packages/core-macro/Cargo.toml b/packages/core-macro/Cargo.toml index 5338a7acc..843470646 100644 --- a/packages/core-macro/Cargo.toml +++ b/packages/core-macro/Cargo.toml @@ -18,7 +18,7 @@ proc-macro = true proc-macro2 = { version = "1.0" } quote = "1.0" syn = { version = "1.0", features = ["full", "extra-traits"] } -dioxus-rsx = { path = "../rsx", version = "0.0.2" } +dioxus-rsx = { path = "../rsx", version = "^0.0.3" } # testing [dev-dependencies] diff --git a/packages/core-macro/README.md b/packages/core-macro/README.md index 60654396e..bcf1572cf 100644 --- a/packages/core-macro/README.md +++ b/packages/core-macro/README.md @@ -19,7 +19,7 @@ [discord-url]: https://discord.gg/XgGxMSkvUM [Website](https://dioxuslabs.com) | -[Guides](https://dioxuslabs.com/guide/) | +[Guides](https://dioxuslabs.com/docs/0.3/guide/en/) | [API Docs](https://docs.rs/dioxus-core-macro/latest/dioxus_core_macro) | [Chat](https://discord.gg/XgGxMSkvUM) diff --git a/packages/core-macro/src/inlineprops.rs b/packages/core-macro/src/inlineprops.rs index 8eaadff32..814724b56 100644 --- a/packages/core-macro/src/inlineprops.rs +++ b/packages/core-macro/src/inlineprops.rs @@ -145,6 +145,25 @@ impl ToTokens for InlinePropsBody { (quote! { #lifetime, }, fn_generics, quote! { #generics }) }; + let generics_no_bounds = { + let mut generics = generics.clone(); + generics.params = generics + .params + .iter() + .map(|it| match it { + GenericParam::Type(tp) => { + let mut tp = tp.clone(); + tp.bounds.clear(); + + GenericParam::Type(tp) + } + _ => it.clone(), + }) + .collect(); + + generics + }; + out_tokens.append_all(quote! { #modifiers #[allow(non_camel_case_types)] @@ -155,7 +174,7 @@ impl ToTokens for InlinePropsBody { } #(#attrs)* - #maybe_async #vis fn #ident #fn_generics (#cx_token: Scope<#scope_lifetime #struct_name #generics>) #output + #maybe_async #vis fn #ident #fn_generics (#cx_token: Scope<#scope_lifetime #struct_name #generics_no_bounds>) #output #where_clause { let #struct_name { #(#field_names),* } = &#cx_token.props; diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index 9f855e18e..b86466692 100644 --- a/packages/core/Cargo.toml +++ b/packages/core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dioxus-core" -version = "0.3.1" +version = "0.3.2" authors = ["Jonathan Kelley"] edition = "2018" description = "Core functionality for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences" diff --git a/packages/core/src/arena.rs b/packages/core/src/arena.rs index 7fbaf0e12..caa863adf 100644 --- a/packages/core/src/arena.rs +++ b/packages/core/src/arena.rs @@ -175,10 +175,10 @@ impl VirtualDom { } impl ElementPath { - pub(crate) fn is_ascendant(&self, big: &&[u8]) -> bool { + pub(crate) fn is_decendant(&self, small: &&[u8]) -> bool { match *self { - ElementPath::Deep(small) => small.len() <= big.len() && small == &big[..small.len()], - ElementPath::Root(r) => big.len() == 1 && big[0] == r as u8, + ElementPath::Deep(big) => small.len() <= big.len() && *small == &big[..small.len()], + ElementPath::Root(r) => small.len() == 1 && small[0] == r as u8, } } } diff --git a/packages/core/src/create.rs b/packages/core/src/create.rs index 12c11d1e9..113fa7ae1 100644 --- a/packages/core/src/create.rs +++ b/packages/core/src/create.rs @@ -25,8 +25,8 @@ fn sort_bfs(paths: &[&'static [u8]]) -> Vec<(usize, &'static [u8])> { } } // The shorter path goes first - (Some(_), None) => return std::cmp::Ordering::Less, - (None, Some(_)) => return std::cmp::Ordering::Greater, + (None, Some(_)) => return std::cmp::Ordering::Less, + (Some(_), None) => return std::cmp::Ordering::Greater, (None, None) => return std::cmp::Ordering::Equal, } } @@ -40,14 +40,25 @@ fn sorting() { (0, &[0, 1]), (1, &[0, 2]), (2, &[1, 0]), - (4, &[1, 1]), - (3, &[1, 2]), + (3, &[1, 0, 1]), + (4, &[1, 2]), ]; assert_eq!( - sort_bfs(&[&[0, 1,], &[0, 2,], &[1, 0,], &[1, 2,], &[1, 1,],]), + sort_bfs(&[&[0, 1,], &[0, 2,], &[1, 0,], &[1, 0, 1,], &[1, 2,],]), + r + ); + let r: [(usize, &[u8]); 6] = [ + (0, &[0]), + (1, &[0, 1]), + (2, &[0, 1, 2]), + (3, &[1]), + (4, &[1, 2]), + (5, &[2]), + ]; + assert_eq!( + sort_bfs(&[&[0], &[0, 1], &[0, 1, 2], &[1], &[1, 2], &[2],]), r ); - assert!(matches!(&[0], &[_, ..])) } impl<'b> VirtualDom { diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 37e9a0428..fdc1a03f1 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -717,7 +717,7 @@ impl<'b> VirtualDom { Fragment(nodes) => nodes .iter() .map(|node| self.push_all_real_nodes(node)) - .count(), + .sum(), Component(comp) => { let scope = comp.scope.get().unwrap(); @@ -729,7 +729,7 @@ impl<'b> VirtualDom { } } }) - .count() + .sum() } fn create_children(&mut self, nodes: impl IntoIterator>) -> usize { diff --git a/packages/core/src/scheduler/task.rs b/packages/core/src/scheduler/task.rs index 997953f8e..fc668b364 100644 --- a/packages/core/src/scheduler/task.rs +++ b/packages/core/src/scheduler/task.rs @@ -72,9 +72,9 @@ pub struct LocalTaskHandle { impl ArcWake for LocalTaskHandle { fn wake_by_ref(arc_self: &Arc) { - arc_self + // This can fail if the scheduler has been dropped while the application is shutting down + let _ = arc_self .tx - .unbounded_send(SchedulerMsg::TaskNotified(arc_self.id)) - .unwrap(); + .unbounded_send(SchedulerMsg::TaskNotified(arc_self.id)); } } diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index fceaefec6..5601d57fe 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -373,17 +373,12 @@ impl<'src> ScopeState { None } - /// Expose state to children further down the [`crate::VirtualDom`] Tree. Does not require `clone` on the context, - /// though we do recommend it. + /// Expose state to children further down the [`crate::VirtualDom`] Tree. Requires `Clone` on the context to allow getting values down the tree. /// /// This is a "fundamental" operation and should only be called during initialization of a hook. /// /// For a hook that provides the same functionality, use `use_provide_context` and `use_context` instead. /// - /// If a state is provided that already exists, the new value will not be inserted. Instead, this method will - /// return the existing value. This behavior is chosen so shared values do not need to be `Clone`. This particular - /// behavior might change in the future. - /// /// # Example /// /// ```rust, ignore diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index a29671f5d..2269815aa 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -396,7 +396,7 @@ impl VirtualDom { // Remove the "on" prefix if it exists, TODO, we should remove this and settle on one if attr.name.trim_start_matches("on") == name - && target_path.is_ascendant(&this_path) + && target_path.is_decendant(&this_path) { listeners.push(&attr.value); diff --git a/packages/desktop/README.md b/packages/desktop/README.md index 9b1e7ad58..964f90495 100644 --- a/packages/desktop/README.md +++ b/packages/desktop/README.md @@ -19,7 +19,7 @@ [discord-url]: https://discord.gg/XgGxMSkvUM [Website](https://dioxuslabs.com) | -[Guides](https://dioxuslabs.com/guide/) | +[Guides](https://dioxuslabs.com/docs/0.3/guide/en/) | [API Docs](https://docs.rs/dioxus-desktop/latest/dioxus_desktop) | [Chat](https://discord.gg/XgGxMSkvUM) @@ -30,7 +30,7 @@ This requires that webview is installed on the target system. WebView is installed by default on macOS and iOS devices, but might not come preinstalled on Windows or Linux devices. To fix these issues, follow the [instructions in the guide](guide-url). -[guide-url]: https://dioxuslabs.com/guide/setup.html#platform-specific-dependencies +[guide-url]: https://dioxuslabs.com/docs/0.3/guide/en/getting_started/desktop.html diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index edddd7887..2ed4bbee9 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/packages/desktop/src/desktop_context.rs @@ -5,6 +5,11 @@ use std::rc::Weak; use crate::create_new_window; use crate::eval::EvalResult; use crate::events::IpcMessage; +use crate::shortcut::IntoKeyCode; +use crate::shortcut::IntoModifersState; +use crate::shortcut::ShortcutId; +use crate::shortcut::ShortcutRegistry; +use crate::shortcut::ShortcutRegistryError; use crate::Config; use crate::WebviewHandler; use dioxus_core::ScopeState; @@ -63,6 +68,8 @@ pub struct DesktopContext { pub(crate) event_handlers: WindowEventHandlers, + pub(crate) shortcut_manager: ShortcutRegistry, + #[cfg(target_os = "ios")] pub(crate) views: Rc>>, } @@ -83,6 +90,7 @@ impl DesktopContext { event_loop: EventLoopWindowTarget, webviews: WebviewQueue, event_handlers: WindowEventHandlers, + shortcut_manager: ShortcutRegistry, ) -> Self { Self { webview, @@ -91,6 +99,7 @@ impl DesktopContext { eval: tokio::sync::broadcast::channel(8).0, pending_windows: webviews, event_handlers, + shortcut_manager, #[cfg(target_os = "ios")] views: Default::default(), } @@ -111,6 +120,7 @@ impl DesktopContext { dom, &self.pending_windows, &self.event_handlers, + self.shortcut_manager.clone(), ); let id = window.webview.window().id(); @@ -240,6 +250,32 @@ impl DesktopContext { self.event_handlers.remove(id) } + /// Create a global shortcut + /// + /// Linux: Only works on x11. See [this issue](https://github.com/tauri-apps/tao/issues/331) for more information. + pub fn create_shortcut( + &self, + key: impl IntoKeyCode, + modifiers: impl IntoModifersState, + callback: impl FnMut() + 'static, + ) -> Result { + self.shortcut_manager.add_shortcut( + modifiers.into_modifiers_state(), + key.into_key_code(), + Box::new(callback), + ) + } + + /// Remove a global shortcut + pub fn remove_shortcut(&self, id: ShortcutId) { + self.shortcut_manager.remove_shortcut(id) + } + + /// Remove all global shortcuts + pub fn remove_all_shortcuts(&self) { + self.shortcut_manager.remove_all() + } + /// Push an objc view to the window #[cfg(target_os = "ios")] pub fn push_view(&self, view: objc_id::ShareId) { diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index 16f562464..75c67dd22 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -9,6 +9,7 @@ mod escape; mod eval; mod events; mod protocol; +mod shortcut; mod waker; mod webview; @@ -21,6 +22,8 @@ use dioxus_core::*; use dioxus_html::HtmlEvent; pub use eval::{use_eval, EvalResult}; use futures_util::{pin_mut, FutureExt}; +use shortcut::ShortcutRegistry; +pub use shortcut::{use_global_shortcut, ShortcutHandle, ShortcutId, ShortcutRegistryError}; use std::collections::HashMap; use std::rc::Rc; use std::task::Waker; @@ -139,6 +142,8 @@ pub fn launch_with_props(root: Component

, props: P, cfg: Config) let queue = WebviewQueue::default(); + let shortcut_manager = ShortcutRegistry::new(&event_loop); + // By default, we'll create a new window when the app starts queue.borrow_mut().push(create_new_window( cfg, @@ -147,6 +152,7 @@ pub fn launch_with_props(root: Component

, props: P, cfg: Config) VirtualDom::new_with_props(root, props), &queue, &event_handlers, + shortcut_manager.clone(), )); event_loop.run(move |window_event, event_loop, control_flow| { @@ -260,6 +266,7 @@ pub fn launch_with_props(root: Component

, props: P, cfg: Config) _ => {} }, + Event::GlobalShortcutEvent(id) => shortcut_manager.call_handlers(id), _ => {} } }) @@ -272,6 +279,7 @@ fn create_new_window( dom: VirtualDom, queue: &WebviewQueue, event_handlers: &WindowEventHandlers, + shortcut_manager: ShortcutRegistry, ) -> WebviewHandler { let webview = webview::build(&mut cfg, event_loop, proxy.clone()); @@ -281,6 +289,7 @@ fn create_new_window( event_loop.clone(), queue.clone(), event_handlers.clone(), + shortcut_manager, )); let id = webview.window().id(); diff --git a/packages/desktop/src/readme.md b/packages/desktop/src/readme.md index 567b05fcf..cd4dbe41a 100644 --- a/packages/desktop/src/readme.md +++ b/packages/desktop/src/readme.md @@ -6,7 +6,7 @@ Render the Dioxus VirtualDom using the platform's native WebView implementation. One of Dioxus' flagship features is the ability to quickly build a native desktop app that looks and feels the same across platforms. Apps built with Dioxus are typically <5mb in size and use existing system resources, so they won't hog extreme amounts of RAM or memory. -Dioxus Desktop is built off Tauri. Right now there aren't any Dioxus abstractions over keyboard shortcuts, menubar, handling, etc, so you'll want to leverage Tauri - mostly [Wry](http://github.com/tauri-apps/wry/) and [Tao](http://github.com/tauri-apps/tao) directly. An upcoming release of Dioxus-Desktop will include components and hooks for notifications, global shortcuts, menubar, etc. +Dioxus Desktop is built off Tauri. Right now there aren't any Dioxus abstractions over the menubar, handling, etc, so you'll want to leverage Tauri - mostly [Wry](http://github.com/tauri-apps/wry/) and [Tao](http://github.com/tauri-apps/tao) directly. An upcoming release of Dioxus-Desktop will include components and hooks for notifications, global shortcuts, menubar, etc. ## Getting Set up @@ -49,4 +49,4 @@ To configure the webview, menubar, and other important desktop-specific features ## Future Steps -Make sure to read the [Dioxus Guide](https://dioxuslabs.com/guide) if you already haven't! +Make sure to read the [Dioxus Guide](https://dioxuslabs.com/docs/0.3/guide/en) if you already haven't! diff --git a/packages/desktop/src/shortcut.rs b/packages/desktop/src/shortcut.rs new file mode 100644 index 000000000..5232bdb8b --- /dev/null +++ b/packages/desktop/src/shortcut.rs @@ -0,0 +1,332 @@ +use std::{cell::RefCell, collections::HashMap, rc::Rc}; + +use dioxus_core::ScopeState; +use dioxus_html::input_data::keyboard_types::Modifiers; +use slab::Slab; +use wry::application::{ + accelerator::{Accelerator, AcceleratorId}, + event_loop::EventLoopWindowTarget, + global_shortcut::{GlobalShortcut, ShortcutManager, ShortcutManagerError}, + keyboard::{KeyCode, ModifiersState}, +}; + +use crate::{use_window, DesktopContext}; + +#[derive(Clone)] +pub(crate) struct ShortcutRegistry { + manager: Rc>, + shortcuts: ShortcutMap, +} + +type ShortcutMap = Rc>>; + +struct Shortcut { + shortcut: GlobalShortcut, + callbacks: Slab>, +} + +impl Shortcut { + fn insert(&mut self, callback: Box) -> usize { + self.callbacks.insert(callback) + } + + fn remove(&mut self, id: usize) { + let _ = self.callbacks.remove(id); + } + + fn is_empty(&self) -> bool { + self.callbacks.is_empty() + } +} + +impl ShortcutRegistry { + pub fn new(target: &EventLoopWindowTarget) -> Self { + let myself = Self { + manager: Rc::new(RefCell::new(ShortcutManager::new(target))), + shortcuts: Rc::new(RefCell::new(HashMap::new())), + }; + // prevent CTRL+R from reloading the page which breaks apps + let _ = myself.add_shortcut( + Some(ModifiersState::CONTROL), + KeyCode::KeyR, + Box::new(|| {}), + ); + myself + } + + pub(crate) fn call_handlers(&self, id: AcceleratorId) { + if let Some(Shortcut { callbacks, .. }) = self.shortcuts.borrow_mut().get_mut(&id) { + for (_, callback) in callbacks.iter_mut() { + (callback)(); + } + } + } + + pub(crate) fn add_shortcut( + &self, + modifiers: impl Into>, + key: KeyCode, + callback: Box, + ) -> Result { + let accelerator = Accelerator::new(modifiers, key); + let accelerator_id = accelerator.clone().id(); + let mut shortcuts = self.shortcuts.borrow_mut(); + Ok( + if let Some(callbacks) = shortcuts.get_mut(&accelerator_id) { + let id = callbacks.insert(callback); + ShortcutId { + id: accelerator_id, + number: id, + } + } else { + match self.manager.borrow_mut().register(accelerator) { + Ok(global_shortcut) => { + let mut slab = Slab::new(); + let id = slab.insert(callback); + let shortcut = Shortcut { + shortcut: global_shortcut, + callbacks: slab, + }; + shortcuts.insert(accelerator_id, shortcut); + ShortcutId { + id: accelerator_id, + number: id, + } + } + Err(ShortcutManagerError::InvalidAccelerator(shortcut)) => { + return Err(ShortcutRegistryError::InvalidShortcut(shortcut)) + } + Err(err) => return Err(ShortcutRegistryError::Other(Box::new(err))), + } + }, + ) + } + + pub(crate) fn remove_shortcut(&self, id: ShortcutId) { + let mut shortcuts = self.shortcuts.borrow_mut(); + if let Some(callbacks) = shortcuts.get_mut(&id.id) { + callbacks.remove(id.number); + if callbacks.is_empty() { + if let Some(shortcut) = shortcuts.remove(&id.id) { + let _ = self.manager.borrow_mut().unregister(shortcut.shortcut); + } + } + } + } + + pub(crate) fn remove_all(&self) { + let mut shortcuts = self.shortcuts.borrow_mut(); + shortcuts.clear(); + let _ = self.manager.borrow_mut().unregister_all(); + // prevent CTRL+R from reloading the page which breaks apps + let _ = self.add_shortcut( + Some(ModifiersState::CONTROL), + KeyCode::KeyR, + Box::new(|| {}), + ); + } +} + +#[non_exhaustive] +#[derive(Debug)] +/// An error that can occur when registering a shortcut. +pub enum ShortcutRegistryError { + /// The shortcut is invalid. + InvalidShortcut(String), + /// An unknown error occurred. + Other(Box), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +/// An global id for a shortcut. +pub struct ShortcutId { + id: AcceleratorId, + number: usize, +} + +/// A global shortcut. This will be automatically removed when it is dropped. +pub struct ShortcutHandle { + desktop: DesktopContext, + /// The id of the shortcut + pub shortcut_id: ShortcutId, +} + +/// Get a closure that executes any JavaScript in the WebView context. +pub fn use_global_shortcut( + cx: &ScopeState, + key: impl IntoKeyCode, + modifiers: impl IntoModifersState, + handler: impl FnMut() + 'static, +) -> &Result { + let desktop = use_window(cx); + cx.use_hook(move || { + let desktop = desktop.clone(); + + let id = desktop.create_shortcut( + key.into_key_code(), + modifiers.into_modifiers_state(), + handler, + ); + + Ok(ShortcutHandle { + desktop, + shortcut_id: id?, + }) + }) +} + +impl ShortcutHandle { + /// Remove the shortcut. + pub fn remove(&self) { + self.desktop.remove_shortcut(self.shortcut_id); + } +} + +impl Drop for ShortcutHandle { + fn drop(&mut self) { + self.remove() + } +} + +pub trait IntoModifersState { + fn into_modifiers_state(self) -> ModifiersState; +} + +impl IntoModifersState for ModifiersState { + fn into_modifiers_state(self) -> ModifiersState { + self + } +} + +impl IntoModifersState for Modifiers { + fn into_modifiers_state(self) -> ModifiersState { + let mut state = ModifiersState::empty(); + if self.contains(Modifiers::SHIFT) { + state |= ModifiersState::SHIFT + } + if self.contains(Modifiers::CONTROL) { + state |= ModifiersState::CONTROL + } + if self.contains(Modifiers::ALT) { + state |= ModifiersState::ALT + } + if self.contains(Modifiers::META) || self.contains(Modifiers::SUPER) { + state |= ModifiersState::SUPER + } + state + } +} + +pub trait IntoKeyCode { + fn into_key_code(self) -> KeyCode; +} + +impl IntoKeyCode for KeyCode { + fn into_key_code(self) -> KeyCode { + self + } +} + +impl IntoKeyCode for dioxus_html::KeyCode { + fn into_key_code(self) -> KeyCode { + match self { + dioxus_html::KeyCode::Backspace => KeyCode::Backspace, + dioxus_html::KeyCode::Tab => KeyCode::Tab, + dioxus_html::KeyCode::Clear => KeyCode::NumpadClear, + dioxus_html::KeyCode::Enter => KeyCode::Enter, + dioxus_html::KeyCode::Shift => KeyCode::ShiftLeft, + dioxus_html::KeyCode::Ctrl => KeyCode::ControlLeft, + dioxus_html::KeyCode::Alt => KeyCode::AltLeft, + dioxus_html::KeyCode::Pause => KeyCode::Pause, + dioxus_html::KeyCode::CapsLock => KeyCode::CapsLock, + dioxus_html::KeyCode::Escape => KeyCode::Escape, + dioxus_html::KeyCode::Space => KeyCode::Space, + dioxus_html::KeyCode::PageUp => KeyCode::PageUp, + dioxus_html::KeyCode::PageDown => KeyCode::PageDown, + dioxus_html::KeyCode::End => KeyCode::End, + dioxus_html::KeyCode::Home => KeyCode::Home, + dioxus_html::KeyCode::LeftArrow => KeyCode::ArrowLeft, + dioxus_html::KeyCode::UpArrow => KeyCode::ArrowUp, + dioxus_html::KeyCode::RightArrow => KeyCode::ArrowRight, + dioxus_html::KeyCode::DownArrow => KeyCode::ArrowDown, + dioxus_html::KeyCode::Insert => KeyCode::Insert, + dioxus_html::KeyCode::Delete => KeyCode::Delete, + dioxus_html::KeyCode::Num0 => KeyCode::Numpad0, + dioxus_html::KeyCode::Num1 => KeyCode::Numpad1, + dioxus_html::KeyCode::Num2 => KeyCode::Numpad2, + dioxus_html::KeyCode::Num3 => KeyCode::Numpad3, + dioxus_html::KeyCode::Num4 => KeyCode::Numpad4, + dioxus_html::KeyCode::Num5 => KeyCode::Numpad5, + dioxus_html::KeyCode::Num6 => KeyCode::Numpad6, + dioxus_html::KeyCode::Num7 => KeyCode::Numpad7, + dioxus_html::KeyCode::Num8 => KeyCode::Numpad8, + dioxus_html::KeyCode::Num9 => KeyCode::Numpad9, + dioxus_html::KeyCode::A => KeyCode::KeyA, + dioxus_html::KeyCode::B => KeyCode::KeyB, + dioxus_html::KeyCode::C => KeyCode::KeyC, + dioxus_html::KeyCode::D => KeyCode::KeyD, + dioxus_html::KeyCode::E => KeyCode::KeyE, + dioxus_html::KeyCode::F => KeyCode::KeyF, + dioxus_html::KeyCode::G => KeyCode::KeyG, + dioxus_html::KeyCode::H => KeyCode::KeyH, + dioxus_html::KeyCode::I => KeyCode::KeyI, + dioxus_html::KeyCode::J => KeyCode::KeyJ, + dioxus_html::KeyCode::K => KeyCode::KeyK, + dioxus_html::KeyCode::L => KeyCode::KeyL, + dioxus_html::KeyCode::M => KeyCode::KeyM, + dioxus_html::KeyCode::N => KeyCode::KeyN, + dioxus_html::KeyCode::O => KeyCode::KeyO, + dioxus_html::KeyCode::P => KeyCode::KeyP, + dioxus_html::KeyCode::Q => KeyCode::KeyQ, + dioxus_html::KeyCode::R => KeyCode::KeyR, + dioxus_html::KeyCode::S => KeyCode::KeyS, + dioxus_html::KeyCode::T => KeyCode::KeyT, + dioxus_html::KeyCode::U => KeyCode::KeyU, + dioxus_html::KeyCode::V => KeyCode::KeyV, + dioxus_html::KeyCode::W => KeyCode::KeyW, + dioxus_html::KeyCode::X => KeyCode::KeyX, + dioxus_html::KeyCode::Y => KeyCode::KeyY, + dioxus_html::KeyCode::Z => KeyCode::KeyZ, + dioxus_html::KeyCode::Numpad0 => KeyCode::Numpad0, + dioxus_html::KeyCode::Numpad1 => KeyCode::Numpad1, + dioxus_html::KeyCode::Numpad2 => KeyCode::Numpad2, + dioxus_html::KeyCode::Numpad3 => KeyCode::Numpad3, + dioxus_html::KeyCode::Numpad4 => KeyCode::Numpad4, + dioxus_html::KeyCode::Numpad5 => KeyCode::Numpad5, + dioxus_html::KeyCode::Numpad6 => KeyCode::Numpad6, + dioxus_html::KeyCode::Numpad7 => KeyCode::Numpad7, + dioxus_html::KeyCode::Numpad8 => KeyCode::Numpad8, + dioxus_html::KeyCode::Numpad9 => KeyCode::Numpad9, + dioxus_html::KeyCode::Multiply => KeyCode::NumpadMultiply, + dioxus_html::KeyCode::Add => KeyCode::NumpadAdd, + dioxus_html::KeyCode::Subtract => KeyCode::NumpadSubtract, + dioxus_html::KeyCode::DecimalPoint => KeyCode::NumpadDecimal, + dioxus_html::KeyCode::Divide => KeyCode::NumpadDivide, + dioxus_html::KeyCode::F1 => KeyCode::F1, + dioxus_html::KeyCode::F2 => KeyCode::F2, + dioxus_html::KeyCode::F3 => KeyCode::F3, + dioxus_html::KeyCode::F4 => KeyCode::F4, + dioxus_html::KeyCode::F5 => KeyCode::F5, + dioxus_html::KeyCode::F6 => KeyCode::F6, + dioxus_html::KeyCode::F7 => KeyCode::F7, + dioxus_html::KeyCode::F8 => KeyCode::F8, + dioxus_html::KeyCode::F9 => KeyCode::F9, + dioxus_html::KeyCode::F10 => KeyCode::F10, + dioxus_html::KeyCode::F11 => KeyCode::F11, + dioxus_html::KeyCode::F12 => KeyCode::F12, + dioxus_html::KeyCode::NumLock => KeyCode::NumLock, + dioxus_html::KeyCode::ScrollLock => KeyCode::ScrollLock, + dioxus_html::KeyCode::Semicolon => KeyCode::Semicolon, + dioxus_html::KeyCode::EqualSign => KeyCode::Equal, + dioxus_html::KeyCode::Comma => KeyCode::Comma, + dioxus_html::KeyCode::Period => KeyCode::Period, + dioxus_html::KeyCode::ForwardSlash => KeyCode::Slash, + dioxus_html::KeyCode::GraveAccent => KeyCode::Backquote, + dioxus_html::KeyCode::OpenBracket => KeyCode::BracketLeft, + dioxus_html::KeyCode::BackSlash => KeyCode::Backslash, + dioxus_html::KeyCode::CloseBraket => KeyCode::BracketRight, + dioxus_html::KeyCode::SingleQuote => KeyCode::Quote, + key => panic!("Failed to convert {:?} to tao::keyboard::KeyCode, try using tao::keyboard::KeyCode directly", key), + } + } +} diff --git a/packages/dioxus/Cargo.toml b/packages/dioxus/Cargo.toml index afe0ea0c3..bc2098285 100644 --- a/packages/dioxus/Cargo.toml +++ b/packages/dioxus/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dioxus" -version = "0.3.1" +version = "0.3.2" authors = ["Jonathan Kelley", "Dioxus Labs", "ealmloff"] edition = "2021" description = "Core functionality for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences" @@ -16,7 +16,7 @@ dioxus-core = { path = "../core", version = "^0.3.0" } dioxus-html = { path = "../html", version = "^0.3.0", optional = true } dioxus-core-macro = { path = "../core-macro", version = "^0.3.0", optional = true } dioxus-hooks = { path = "../hooks", version = "^0.3.0", optional = true } -dioxus-rsx = { path = "../rsx", version = "0.0.2", optional = true } +dioxus-rsx = { path = "../rsx", version = "^0.0.3", optional = true } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] dioxus-hot-reload = { path = "../hot-reload", version = "0.1.0", optional = true } diff --git a/packages/dioxus/README.md b/packages/dioxus/README.md index 4dc007096..31fff88fa 100644 --- a/packages/dioxus/README.md +++ b/packages/dioxus/README.md @@ -9,9 +9,8 @@ This overview provides a brief introduction to Dioxus. For a more in-depth guide, make sure to check out: -- [Getting Started](https://dioxuslabs.com/guide/setup.html) -- [Book](https://dioxuslabs.com/guide/) -- [Reference](https://dioxuslabs.com/reference) +- [Getting Started](https://dioxuslabs.com/docs/0.3/guide/en/getting_started/index.html) +- [Book](https://dioxuslabs.com/docs/0.3/guide/en/) - [Examples](https://github.com/DioxusLabs/example-projects) # Overview and Goals diff --git a/packages/fermi/src/root.rs b/packages/fermi/src/root.rs index 68a5d5d33..99e97bfbb 100644 --- a/packages/fermi/src/root.rs +++ b/packages/fermi/src/root.rs @@ -13,8 +13,8 @@ use crate::Readable; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct AtomId { - pub(crate) ptr: *const (), - pub(crate) type_id: TypeId, + pub ptr: *const (), + pub type_id: TypeId, } pub struct AtomRoot { diff --git a/packages/hooks/Cargo.toml b/packages/hooks/Cargo.toml index c1b31ea65..c0e1ea783 100644 --- a/packages/hooks/Cargo.toml +++ b/packages/hooks/Cargo.toml @@ -20,3 +20,4 @@ log = "0.4" [dev-dependencies] futures-util = { version = "0.3", default-features = false } dioxus-core = { path = "../../packages/core", version = "^0.3.0" } +dioxus = { path = "../../packages/dioxus", version = "^0.3.0" } diff --git a/packages/hooks/README.md b/packages/hooks/README.md index 6d6f56d50..6c77867de 100644 --- a/packages/hooks/README.md +++ b/packages/hooks/README.md @@ -19,7 +19,7 @@ [discord-url]: https://discord.gg/XgGxMSkvUM [Website](https://dioxuslabs.com) | -[Guides](https://dioxuslabs.com/guide/) | +[Guides](https://dioxuslabs.com/docs/0.3/guide/en/) | [API Docs](https://docs.rs/dioxus-hooks/latest/dioxus_hooks) | [Chat](https://discord.gg/XgGxMSkvUM) diff --git a/packages/hooks/src/use_shared_state.rs b/packages/hooks/src/use_shared_state.rs index a355ae42a..eb836a211 100644 --- a/packages/hooks/src/use_shared_state.rs +++ b/packages/hooks/src/use_shared_state.rs @@ -1,6 +1,6 @@ use dioxus_core::{ScopeId, ScopeState}; use std::{ - cell::{Cell, Ref, RefCell, RefMut}, + cell::{Ref, RefCell, RefMut}, collections::HashSet, rc::Rc, sync::Arc, @@ -9,8 +9,8 @@ use std::{ type ProvidedState = Rc>>; // Tracks all the subscribers to a shared State -pub struct ProvidedStateInner { - value: Rc>, +pub(crate) struct ProvidedStateInner { + value: T, notify_any: Arc, consumers: HashSet, } @@ -21,14 +21,6 @@ impl ProvidedStateInner { (self.notify_any)(*consumer); } } - - pub fn write(&self) -> RefMut { - self.value.borrow_mut() - } - - pub fn read(&self) -> Ref { - self.value.borrow() - } } /// This hook provides some relatively light ergonomics around shared state. @@ -38,18 +30,57 @@ impl ProvidedStateInner { /// /// # Example /// -/// ## Provider +/// ```rust +/// # use dioxus::prelude::*; +/// # +/// # fn app(cx: Scope) -> Element { +/// # render! { +/// # Parent{} +/// # } +/// # } /// -/// ```rust, ignore +/// #[derive(Clone, Copy)] +/// enum Theme { +/// Light, +/// Dark, +/// } /// +/// // Provider +/// fn Parent<'a>(cx: Scope<'a>) -> Element<'a> { +/// use_shared_state_provider(cx, || Theme::Dark); +/// let theme = use_shared_state::(cx).unwrap(); /// -/// ``` -/// -/// ## Consumer -/// -/// ```rust, ignore +/// render! { +/// button{ +/// onclick: move |_| { +/// let current_theme = *theme.read(); +/// *theme.write() = match current_theme { +/// Theme::Dark => Theme::Light, +/// Theme::Light => Theme::Dark, +/// }; +/// }, +/// "Change theme" +/// } +/// Child{} +/// } +/// } /// +/// // Consumer +/// fn Child<'a>(cx: Scope<'a>) -> Element<'a> { +/// let theme = use_shared_state::(cx).unwrap(); +/// let current_theme = *theme.read(); /// +/// render! { +/// match &*theme.read() { +/// Theme::Dark => { +/// "Dark mode" +/// } +/// Theme::Light => { +/// "Light mode" +/// } +/// } +/// } +/// } /// ``` /// /// # How it works @@ -57,118 +88,126 @@ impl ProvidedStateInner { /// Any time a component calls `write`, every consumer of the state will be notified - excluding the provider. /// /// Right now, there is not a distinction between read-only and write-only, so every consumer will be notified. -/// -/// -/// -pub fn use_shared_state(cx: &ScopeState) -> Option> { - let state = cx.use_hook(|| { +pub fn use_shared_state(cx: &ScopeState) -> Option<&UseSharedState> { + let state: &Option> = &*cx.use_hook(move || { let scope_id = cx.scope_id(); - let root = cx.consume_context::>(); + let root = cx.consume_context::>()?; - if let Some(root) = root.as_ref() { - root.borrow_mut().consumers.insert(scope_id); - } + root.borrow_mut().consumers.insert(scope_id); - let value = root.as_ref().map(|f| f.borrow().value.clone()); - SharedStateInner { - root, - value, - scope_id, - needs_notification: Cell::new(false), - } + let state = UseSharedState { inner: root }; + let owner = UseSharedStateOwner { state, scope_id }; + Some(owner) }); - - state.needs_notification.set(false); - match (&state.value, &state.root) { - (Some(value), Some(root)) => Some(UseSharedState { - cx, - value, - root, - needs_notification: &state.needs_notification, - }), - _ => None, - } + state.as_ref().map(|s| &s.state) } -struct SharedStateInner { - root: Option>, - value: Option>>, +/// This wrapper detects when the hook is dropped and will unsubscribe when the component is unmounted +struct UseSharedStateOwner { + state: UseSharedState, scope_id: ScopeId, - needs_notification: Cell, } -impl Drop for SharedStateInner { + +impl Drop for UseSharedStateOwner { fn drop(&mut self) { - // we need to unsubscribe when our component is unounted - if let Some(root) = &self.root { - let mut root = root.borrow_mut(); - root.consumers.remove(&self.scope_id); - } + // we need to unsubscribe when our component is unmounted + let mut root = self.state.inner.borrow_mut(); + root.consumers.remove(&self.scope_id); } } -pub struct UseSharedState<'a, T: 'static> { - pub(crate) cx: &'a ScopeState, - pub(crate) value: &'a Rc>, - pub(crate) root: &'a Rc>>, - pub(crate) needs_notification: &'a Cell, +/// State that is shared between components through the context system +pub struct UseSharedState { + pub(crate) inner: Rc>>, } -impl<'a, T: 'static> UseSharedState<'a, T> { +impl UseSharedState { + /// Notify all consumers of the state that it has changed. (This is called automatically when you call "write") + pub fn notify_consumers(&self) { + self.inner.borrow_mut().notify_consumers(); + } + + /// Read the shared value pub fn read(&self) -> Ref<'_, T> { - self.value.borrow() - } - - pub fn notify_consumers(self) { - if !self.needs_notification.get() { - self.root.borrow_mut().notify_consumers(); - self.needs_notification.set(true); - } - } - - pub fn read_write(&self) -> (Ref<'_, T>, &Self) { - (self.read(), self) + Ref::map(self.inner.borrow(), |inner| &inner.value) } /// Calling "write" will force the component to re-render /// /// - /// TODO: We prevent unncessary notifications only in the hook, but we should figure out some more global lock + // TODO: We prevent unncessary notifications only in the hook, but we should figure out some more global lock pub fn write(&self) -> RefMut<'_, T> { - self.cx.needs_update(); - self.notify_consumers(); - self.value.borrow_mut() + let mut value = self.inner.borrow_mut(); + value.notify_consumers(); + RefMut::map(value, |inner| &mut inner.value) } /// Allows the ability to write the value without forcing a re-render pub fn write_silent(&self) -> RefMut<'_, T> { - self.value.borrow_mut() - } - - pub fn inner(&self) -> Rc>> { - self.root.clone() + RefMut::map(self.inner.borrow_mut(), |inner| &mut inner.value) } } -impl Copy for UseSharedState<'_, T> {} -impl<'a, T> Clone for UseSharedState<'a, T> -where - T: 'static, -{ +impl Clone for UseSharedState { fn clone(&self) -> Self { - UseSharedState { - cx: self.cx, - value: self.value, - root: self.root, - needs_notification: self.needs_notification, + Self { + inner: self.inner.clone(), } } } -/// Provide some state for components down the hierarchy to consume without having to drill props. +impl PartialEq for UseSharedState { + fn eq(&self, other: &Self) -> bool { + let first = self.inner.borrow(); + let second = other.inner.borrow(); + first.value == second.value + } +} + +/// Provide some state for components down the hierarchy to consume without having to drill props. See [`use_shared_state`] to consume the state +/// +/// +/// # Example +/// +/// ```rust +/// # use dioxus::prelude::*; +/// # +/// # fn app(cx: Scope) -> Element { +/// # render! { +/// # Parent{} +/// # } +/// # } +/// +/// #[derive(Clone, Copy)] +/// enum Theme { +/// Light, +/// Dark, +/// } +/// +/// // Provider +/// fn Parent<'a>(cx: Scope<'a>) -> Element<'a> { +/// use_shared_state_provider(cx, || Theme::Dark); +/// let theme = use_shared_state::(cx).unwrap(); +/// +/// render! { +/// button{ +/// onclick: move |_| { +/// let current_theme = *theme.read(); +/// *theme.write() = match current_theme { +/// Theme::Dark => Theme::Light, +/// Theme::Light => Theme::Dark, +/// }; +/// }, +/// "Change theme" +/// } +/// // Children components that consume the state... +/// } +/// } +/// ``` pub fn use_shared_state_provider(cx: &ScopeState, f: impl FnOnce() -> T) { cx.use_hook(|| { let state: ProvidedState = Rc::new(RefCell::new(ProvidedStateInner { - value: Rc::new(RefCell::new(f())), + value: f(), notify_any: cx.schedule_update_any(), consumers: HashSet::new(), })); diff --git a/packages/hooks/src/usecoroutine.rs b/packages/hooks/src/usecoroutine.rs index 2f2442f87..bc97735b9 100644 --- a/packages/hooks/src/usecoroutine.rs +++ b/packages/hooks/src/usecoroutine.rs @@ -108,6 +108,12 @@ impl Coroutine { } } +impl PartialEq for Coroutine { + fn eq(&self, other: &Self) -> bool { + self.task == other.task + } +} + #[cfg(test)] mod tests { #![allow(unused)] diff --git a/packages/hooks/src/usestate.rs b/packages/hooks/src/usestate.rs index 2e1aa7295..d2b6db6fc 100644 --- a/packages/hooks/src/usestate.rs +++ b/packages/hooks/src/usestate.rs @@ -329,7 +329,7 @@ impl PartialEq for &UseState { } } -impl PartialEq> for UseState { +impl PartialEq> for UseState { fn eq(&self, other: &UseState) -> bool { Rc::ptr_eq(&self.current_val, &other.current_val) } diff --git a/packages/hot-reload/Cargo.toml b/packages/hot-reload/Cargo.toml index 99d5cb00d..2d88bf029 100644 --- a/packages/hot-reload/Cargo.toml +++ b/packages/hot-reload/Cargo.toml @@ -1,20 +1,20 @@ [package] name = "dioxus-hot-reload" -version = "0.1.0" +version = "0.1.1" edition = "2021" license = "MIT/Apache-2.0" repository = "https://github.com/DioxusLabs/dioxus/" homepage = "https://dioxuslabs.com" description = "Hot reloading utilites for Dioxus" documentation = "https://dioxuslabs.com" -keywords = ["dom", "ui", "gui", "react", "hot-reloading", "watch"] +keywords = ["dom", "ui", "gui", "react", "hot-reloading"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -dioxus-rsx = { path = "../rsx" } -dioxus-core = { path = "../core", features = ["serialize"] } -dioxus-html = { path = "../html", features = ["hot-reload-context"] } +dioxus-rsx = { path = "../rsx" , version = "^0.0.3" } +dioxus-core = { path = "../core", features = ["serialize"], version = "^0.3.2"} +dioxus-html = { path = "../html", features = ["hot-reload-context"], version = "^0.3.0" } interprocess = { version = "1.2.1" } notify = "5.0.0" @@ -23,4 +23,4 @@ serde_json = "1.0.91" serde = { version = "1", features = ["derive"] } execute = "0.2.11" once_cell = "1.17.0" -ignore = "0.4.19" \ No newline at end of file +ignore = "0.4.19" diff --git a/packages/hot-reload/README.md b/packages/hot-reload/README.md index 126c42103..60b7b9c87 100644 --- a/packages/hot-reload/README.md +++ b/packages/hot-reload/README.md @@ -19,7 +19,7 @@ [discord-url]: https://discord.gg/XgGxMSkvUM [Website](https://dioxuslabs.com) | -[Guides](https://dioxuslabs.com/guide/) | +[Guides](https://dioxuslabs.com/docs/0.3/guide/en) | [API Docs](https://docs.rs/dioxus-hot-reload/latest/dioxus_hot_reload) | [Chat](https://discord.gg/XgGxMSkvUM) diff --git a/packages/hot-reload/src/lib.rs b/packages/hot-reload/src/lib.rs index 1ddd32bb1..d144fccfc 100644 --- a/packages/hot-reload/src/lib.rs +++ b/packages/hot-reload/src/lib.rs @@ -7,7 +7,7 @@ use std::{ use dioxus_core::Template; use dioxus_rsx::{ - hot_reload::{FileMap, UpdateResult}, + hot_reload::{FileMap, FileMapBuildResult, UpdateResult}, HotReloadingContext, }; use interprocess::local_socket::{LocalSocketListener, LocalSocketStream}; @@ -129,10 +129,37 @@ pub fn init(cfg: Config) { } = cfg; if let Ok(crate_dir) = PathBuf::from_str(root_path) { - let temp_file = std::env::temp_dir().join("@dioxusin"); + // try to find the gitingore file + let gitignore_file_path = crate_dir.join(".gitignore"); + let (gitignore, _) = ignore::gitignore::Gitignore::new(gitignore_file_path); + + // convert the excluded paths to absolute paths + let excluded_paths = excluded_paths + .iter() + .map(|path| crate_dir.join(PathBuf::from(path))) + .collect::>(); + let channels = Arc::new(Mutex::new(Vec::new())); - let file_map = Arc::new(Mutex::new(FileMap::::new(crate_dir.clone()))); - if let Ok(local_socket_stream) = LocalSocketListener::bind(temp_file.as_path()) { + let FileMapBuildResult { + map: file_map, + errors, + } = FileMap::::create_with_filter(crate_dir.clone(), |path| { + // skip excluded paths + excluded_paths.iter().any(|p| path.starts_with(p)) || + // respect .gitignore + gitignore + .matched_path_or_any_parents(path, path.is_dir()) + .is_ignore() + }) + .unwrap(); + for err in errors { + if log { + println!("hot reloading failed to initialize:\n{err:?}"); + } + } + let file_map = Arc::new(Mutex::new(file_map)); + + if let Ok(local_socket_stream) = LocalSocketListener::bind("@dioxusin") { let aborted = Arc::new(Mutex::new(false)); // listen for connections @@ -177,10 +204,6 @@ pub fn init(cfg: Config) { // watch for changes std::thread::spawn(move || { - // try to find the gitingore file - let gitignore_file_path = crate_dir.join(".gitignore"); - let (gitignore, _) = ignore::gitignore::Gitignore::new(gitignore_file_path); - let mut last_update_time = chrono::Local::now().timestamp(); let (tx, rx) = std::sync::mpsc::channel(); @@ -198,11 +221,6 @@ pub fn init(cfg: Config) { } } - let excluded_paths = excluded_paths - .iter() - .map(|path| crate_dir.join(PathBuf::from(path))) - .collect::>(); - let mut rebuild = { let aborted = aborted.clone(); let channels = channels.clone(); @@ -242,7 +260,7 @@ pub fn init(cfg: Config) { matches!( path.extension().and_then(|p| p.to_str()), Some("rs" | "toml" | "css" | "html" | "js") - )&& + ) && // skip excluded paths !excluded_paths.iter().any(|p| path.starts_with(p)) && // respect .gitignore @@ -271,7 +289,7 @@ pub fn init(cfg: Config) { .unwrap() .update_rsx(path, crate_dir.as_path()) { - UpdateResult::UpdatedRsx(msgs) => { + Ok(UpdateResult::UpdatedRsx(msgs)) => { for msg in msgs { let mut i = 0; while i < channels.len() { @@ -287,13 +305,20 @@ pub fn init(cfg: Config) { } } } - UpdateResult::NeedsRebuild => { + Ok(UpdateResult::NeedsRebuild) => { drop(channels); if rebuild() { return; } break; } + Err(err) => { + if log { + println!( + "hot reloading failed to update rsx:\n{err:?}" + ); + } + } } } } @@ -322,8 +347,7 @@ fn send_msg(msg: HotReloadMsg, channel: &mut impl Write) -> bool { /// Connect to the hot reloading listener. The callback provided will be called every time a template change is detected pub fn connect(mut f: impl FnMut(HotReloadMsg) + Send + 'static) { std::thread::spawn(move || { - let temp_file = std::env::temp_dir().join("@dioxusin"); - if let Ok(socket) = LocalSocketStream::connect(temp_file.as_path()) { + if let Ok(socket) = LocalSocketStream::connect("@dioxusin") { let mut buf_reader = BufReader::new(socket); loop { let mut buf = String::new(); diff --git a/packages/html/Cargo.toml b/packages/html/Cargo.toml index 2159acb7b..e60fcd8e5 100644 --- a/packages/html/Cargo.toml +++ b/packages/html/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dioxus-html" -version = "0.3.0" +version = "0.3.1" authors = ["Jonathan Kelley"] edition = "2018" description = "HTML Element pack for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences" @@ -12,7 +12,7 @@ keywords = ["dom", "ui", "gui", "react"] [dependencies] dioxus-core = { path = "../core", version = "^0.3.0" } -dioxus-rsx = { path = "../rsx", version = "0.0.2", optional = true } +dioxus-rsx = { path = "../rsx", version = "^0.0.3", optional = true } serde = { version = "1", features = ["derive"], optional = true } serde_repr = { version = "0.1", optional = true } wasm-bindgen = { version = "0.2.79", optional = true } diff --git a/packages/html/README.md b/packages/html/README.md index 7bae7e197..59ec2f9ff 100644 --- a/packages/html/README.md +++ b/packages/html/README.md @@ -19,7 +19,7 @@ [discord-url]: https://discord.gg/XgGxMSkvUM [Website](https://dioxuslabs.com) | -[Guides](https://dioxuslabs.com/guide/) | +[Guides](https://dioxuslabs.com/docs/0.3/guide/en/) | [API Docs](https://docs.rs/dioxus-html/latest/dioxus_html) | [Chat](https://discord.gg/XgGxMSkvUM) diff --git a/packages/interpreter/Cargo.toml b/packages/interpreter/Cargo.toml index a10377f34..7c7d79607 100644 --- a/packages/interpreter/Cargo.toml +++ b/packages/interpreter/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dioxus-interpreter-js" -version = "0.3.0" +version = "0.3.1" edition = "2018" authors = ["Jonathan Kelley"] description = "JS Intepreter for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences" diff --git a/packages/interpreter/README.md b/packages/interpreter/README.md index cb01f2cc7..90e390f56 100644 --- a/packages/interpreter/README.md +++ b/packages/interpreter/README.md @@ -19,7 +19,7 @@ [discord-url]: https://discord.gg/XgGxMSkvUM [Website](https://dioxuslabs.com) | -[Guides](https://dioxuslabs.com/guide/) | +[Guides](https://dioxuslabs.com/docs/0.3/guide/en/) | [API Docs](https://docs.rs/dioxus-interpreter-js/latest/dioxus_interpreter_js) | [Chat](https://discord.gg/XgGxMSkvUM) diff --git a/packages/interpreter/src/interpreter.js b/packages/interpreter/src/interpreter.js index 06774e474..b921c7c22 100644 --- a/packages/interpreter/src/interpreter.js +++ b/packages/interpreter/src/interpreter.js @@ -116,9 +116,6 @@ class Interpreter { node.remove(); } } - CreateRawText(text) { - this.stack.push(document.createTextNode(text)); - } CreateTextNode(text, root) { const node = document.createTextNode(text); this.nodes[root] = node; @@ -306,7 +303,7 @@ class Interpreter { this.CreatePlaceholder(edit.id); break; case "CreateTextNode": - this.CreateTextNode(edit.value); + this.CreateTextNode(edit.value, edit.id); break; case "HydrateText": this.HydrateText(edit.path, edit.value, edit.id); @@ -360,10 +357,11 @@ class Interpreter { if (event.type === "click") { // todo call prevent default if it's the right type of event - if (shouldPreventDefault !== `onclick`) { - if (target.tagName === "A") { - event.preventDefault(); - const href = target.getAttribute("href"); + let a_element = target.closest("a"); + if (a_element != null) { + event.preventDefault(); + if (shouldPreventDefault !== `onclick` && a_element.getAttribute(`dioxus-prevent-default`) !== `onclick`) { + const href = a_element.getAttribute("href"); if (href !== "" && href !== null && href !== undefined) { window.ipc.postMessage( serializeIpcMessage("browser_open", { href }) diff --git a/packages/liveview/README.md b/packages/liveview/README.md index 6ab109f8e..0a0e7de3a 100644 --- a/packages/liveview/README.md +++ b/packages/liveview/README.md @@ -19,7 +19,7 @@ [discord-url]: https://discord.gg/XgGxMSkvUM [Website](https://dioxuslabs.com) | -[Guides](https://dioxuslabs.com/guide/) | +[Guides](https://dioxuslabs.com/docs/0.3/guide/en/) | [API Docs](https://docs.rs/dioxus-liveview/latest/dioxus_liveview) | [Chat](https://discord.gg/XgGxMSkvUM) diff --git a/packages/liveview/examples/axum.rs b/packages/liveview/examples/axum.rs index df24de4aa..2e7f10ee7 100644 --- a/packages/liveview/examples/axum.rs +++ b/packages/liveview/examples/axum.rs @@ -28,7 +28,7 @@ async fn main() { r#" - Dioxus LiveView with Warp + Dioxus LiveView with axum

{glue} diff --git a/packages/liveview/examples/salvo.rs b/packages/liveview/examples/salvo.rs index 8cd2c0ca4..015892702 100644 --- a/packages/liveview/examples/salvo.rs +++ b/packages/liveview/examples/salvo.rs @@ -41,7 +41,7 @@ fn index(_depot: &mut Depot, res: &mut Response) { r#" - Dioxus LiveView with Warp + Dioxus LiveView with Salvo
{glue} diff --git a/packages/liveview/src/pool.rs b/packages/liveview/src/pool.rs index 022641d5c..e02c8195b 100644 --- a/packages/liveview/src/pool.rs +++ b/packages/liveview/src/pool.rs @@ -37,7 +37,16 @@ impl LiveViewPool { app: fn(Scope) -> Element, props: T, ) -> Result<(), LiveViewError> { - match self.pool.spawn_pinned(move || run(app, props, ws)).await { + self.launch_virtualdom(ws, move || VirtualDom::new_with_props(app, props)) + .await + } + + pub async fn launch_virtualdom VirtualDom + Send + 'static>( + &self, + ws: impl LiveViewSocket, + make_app: F, + ) -> Result<(), LiveViewError> { + match self.pool.spawn_pinned(move || run(make_app(), ws)).await { Ok(Ok(_)) => Ok(()), Ok(Err(e)) => Err(e), Err(_) => Err(LiveViewError::SendingFailed), @@ -95,14 +104,7 @@ impl LiveViewSocket for S where /// As long as your framework can provide a Sink and Stream of Strings, you can use this function. /// /// You might need to transform the error types of the web backend into the LiveView error type. -pub async fn run( - app: Component, - props: T, - ws: impl LiveViewSocket, -) -> Result<(), LiveViewError> -where - T: Send + 'static, -{ +pub async fn run(mut vdom: VirtualDom, ws: impl LiveViewSocket) -> Result<(), LiveViewError> { #[cfg(all(feature = "hot-reload", debug_assertions))] let mut hot_reload_rx = { let (tx, rx) = tokio::sync::mpsc::unbounded_channel(); @@ -112,8 +114,6 @@ where rx }; - let mut vdom = VirtualDom::new_with_props(app, props); - // todo: use an efficient binary packed format for this let edits = serde_json::to_string(&vdom.rebuild()).unwrap(); diff --git a/packages/mobile/README.md b/packages/mobile/README.md index 7af09e970..46da9061a 100644 --- a/packages/mobile/README.md +++ b/packages/mobile/README.md @@ -18,7 +18,7 @@ [discord-url]: https://discord.gg/XgGxMSkvUM [Website](https://dioxuslabs.com) | -[Guides](https://dioxuslabs.com/guide/) | +[Guides](https://dioxuslabs.com/docs/0.3/guide/en/) | [API Docs](https://docs.rs/dioxus-mobile/latest/dioxus_mobile) | [Chat](https://discord.gg/XgGxMSkvUM) @@ -98,7 +98,7 @@ To configure the web view, menubar, and other important desktop-specific feature ## Future Steps -Make sure to read the [Dioxus Guide](https://dioxuslabs.com/guide) if you already haven't! +Make sure to read the [Dioxus Guide](https://dioxuslabs.com/docs/0.3/guide/en) if you already haven't! diff --git a/packages/router/README.md b/packages/router/README.md index 43bc2df2b..c7be4496d 100644 --- a/packages/router/README.md +++ b/packages/router/README.md @@ -1,4 +1,4 @@ -# Dioxus Native Core +# Dioxus Router [![Crates.io][crates-badge]][crates-url] [![MIT licensed][mit-badge]][mit-url] @@ -18,7 +18,7 @@ [discord-url]: https://discord.gg/XgGxMSkvUM [Website](https://dioxuslabs.com) | -[Guides](https://dioxuslabs.com/guide/) | +[Guides](https://dioxuslabs.com/docs/0.3/guide/en/) | [API Docs](https://docs.rs/dioxus-router/latest/dioxus_router) | [Chat](https://discord.gg/XgGxMSkvUM) diff --git a/packages/rsx-rosetta/Cargo.toml b/packages/rsx-rosetta/Cargo.toml index 2def4e7c8..a0a9763e1 100644 --- a/packages/rsx-rosetta/Cargo.toml +++ b/packages/rsx-rosetta/Cargo.toml @@ -7,7 +7,7 @@ edition = "2018" [dependencies] dioxus-autofmt = { path = "../autofmt" } -dioxus-rsx = { path = "../rsx" } +dioxus-rsx = { path = "../rsx" , version = "^0.0.3" } html_parser = "0.6.3" proc-macro2 = "1.0.49" quote = "1.0.23" diff --git a/packages/rsx-rosetta/README.md b/packages/rsx-rosetta/README.md index bf811a5fe..da3049d3c 100644 --- a/packages/rsx-rosetta/README.md +++ b/packages/rsx-rosetta/README.md @@ -20,7 +20,7 @@ [discord-url]: https://discord.gg/XgGxMSkvUM [Website](https://dioxuslabs.com) | -[Guides](https://dioxuslabs.com/guide/) | +[Guides](https://dioxuslabs.com/docs/0.3/guide/en/) | [API Docs](https://docs.rs/rsx-rosetta/latest/rsx-rosetta) | [Chat](https://discord.gg/XgGxMSkvUM) diff --git a/packages/rsx/Cargo.toml b/packages/rsx/Cargo.toml index 6f98b5f47..055a97132 100644 --- a/packages/rsx/Cargo.toml +++ b/packages/rsx/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dioxus-rsx" -version = "0.0.2" +version = "0.0.3" edition = "2018" license = "MIT/Apache-2.0" description = "Core functionality for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences" @@ -13,8 +13,9 @@ keywords = ["dom", "ui", "gui", "react"] [dependencies] proc-macro2 = { version = "1.0", features = ["span-locations"] } -dioxus-core = { path = "../core", version = "0.3.0"} +dioxus-core = { path = "../core", version = "^0.3.0"} syn = { version = "1.0", features = ["full", "extra-traits"] } quote = { version = "1.0" } serde = { version = "1.0", features = ["derive"] } internment = "0.7.0" +krates = "0.12.6" \ No newline at end of file diff --git a/packages/rsx/README.md b/packages/rsx/README.md index 707416b98..c20737167 100644 --- a/packages/rsx/README.md +++ b/packages/rsx/README.md @@ -18,7 +18,7 @@ [discord-url]: https://discord.gg/XgGxMSkvUM [Website](https://dioxuslabs.com) | -[Guides](https://dioxuslabs.com/guide/) | +[Guides](https://dioxuslabs.com/docs/0.3/guide/en/) | [API Docs](https://docs.rs/dioxus-rsx/latest/dioxus_rsx) | [Chat](https://discord.gg/XgGxMSkvUM) diff --git a/packages/rsx/src/hot_reload/hot_reloading_file_map.rs b/packages/rsx/src/hot_reload/hot_reloading_file_map.rs index be4f5d828..2f0cc5246 100644 --- a/packages/rsx/src/hot_reload/hot_reloading_file_map.rs +++ b/packages/rsx/src/hot_reload/hot_reloading_file_map.rs @@ -1,5 +1,7 @@ use crate::{CallBody, HotReloadingContext}; use dioxus_core::Template; +use krates::cm::MetadataCommand; +use krates::Cmd; pub use proc_macro2::TokenStream; pub use std::collections::HashMap; use std::path::PathBuf; @@ -17,46 +19,82 @@ pub enum UpdateResult { NeedsRebuild, } +/// The result of building a FileMap +pub struct FileMapBuildResult { + /// The FileMap that was built + pub map: FileMap, + /// Any errors that occurred while building the FileMap that were not fatal + pub errors: Vec, +} + pub struct FileMap { pub map: HashMap>)>, + in_workspace: HashMap, phantom: std::marker::PhantomData, } impl FileMap { /// Create a new FileMap from a crate directory - pub fn new(path: PathBuf) -> Self { + pub fn create(path: PathBuf) -> io::Result> { + Self::create_with_filter(path, |_| false) + } + + /// Create a new FileMap from a crate directory + pub fn create_with_filter( + path: PathBuf, + mut filter: impl FnMut(&Path) -> bool, + ) -> io::Result> { + struct FileMapSearchResult { + map: HashMap>)>, + errors: Vec, + } fn find_rs_files( root: PathBuf, - ) -> io::Result>)>> { + filter: &mut impl FnMut(&Path) -> bool, + ) -> io::Result { let mut files = HashMap::new(); + let mut errors = Vec::new(); if root.is_dir() { for entry in (fs::read_dir(root)?).flatten() { let path = entry.path(); - files.extend(find_rs_files(path)?); + if !filter(&path) { + let FileMapSearchResult { + map, + errors: child_errors, + } = find_rs_files(path, filter)?; + errors.extend(child_errors); + files.extend(map); + } } } else if root.extension().and_then(|s| s.to_str()) == Some("rs") { if let Ok(mut file) = File::open(root.clone()) { let mut src = String::new(); - file.read_to_string(&mut src).expect("Unable to read file"); + file.read_to_string(&mut src)?; files.insert(root, (src, None)); } } - Ok(files) + Ok(FileMapSearchResult { map: files, errors }) } + let FileMapSearchResult { map, errors } = find_rs_files(path, &mut filter)?; let result = Self { - map: find_rs_files(path).unwrap(), + map, + in_workspace: HashMap::new(), phantom: std::marker::PhantomData, }; - result + Ok(FileMapBuildResult { + map: result, + errors, + }) } /// Try to update the rsx in a file - pub fn update_rsx(&mut self, file_path: &Path, crate_dir: &Path) -> UpdateResult { - let mut file = File::open(file_path).unwrap(); + pub fn update_rsx(&mut self, file_path: &Path, crate_dir: &Path) -> io::Result { + let mut file = File::open(file_path)?; let mut src = String::new(); - file.read_to_string(&mut src).expect("Unable to read file"); + file.read_to_string(&mut src)?; if let Ok(syntax) = syn::parse_file(&src) { + let in_workspace = self.child_in_workspace(crate_dir)?; if let Some((old_src, template_slot)) = self.map.get_mut(file_path) { if let Ok(old) = syn::parse_file(old_src) { match find_rsx(&syntax, &old) { @@ -72,7 +110,17 @@ impl FileMap { syn::parse2::(old.tokens), syn::parse2::(new), ) { - if let Ok(file) = file_path.strip_prefix(crate_dir) { + // if the file!() macro is invoked in a workspace, the path is relative to the workspace root, otherwise it's relative to the crate root + // we need to check if the file is in a workspace or not and strip the prefix accordingly + let prefix = if in_workspace { + crate_dir.parent().ok_or(io::Error::new( + io::ErrorKind::Other, + "Could not load workspace", + ))? + } else { + crate_dir + }; + if let Ok(file) = file_path.strip_prefix(prefix) { let line = old_start.line; let column = old_start.column + 1; let location = file.display().to_string() @@ -91,7 +139,7 @@ impl FileMap { { // dioxus cannot handle empty templates if template.roots.is_empty() { - return UpdateResult::NeedsRebuild; + return Ok(UpdateResult::NeedsRebuild); } else { // if the template is the same, don't send it if let Some(old_template) = template_slot { @@ -103,20 +151,44 @@ impl FileMap { messages.push(template); } } else { - return UpdateResult::NeedsRebuild; + return Ok(UpdateResult::NeedsRebuild); } } } } - return UpdateResult::UpdatedRsx(messages); + return Ok(UpdateResult::UpdatedRsx(messages)); } } } } else { // if this is a new file, rebuild the project - *self = FileMap::new(crate_dir.to_path_buf()); + let FileMapBuildResult { map, mut errors } = + FileMap::create(crate_dir.to_path_buf())?; + if let Some(err) = errors.pop() { + return Err(err); + } + *self = map; } } - UpdateResult::NeedsRebuild + Ok(UpdateResult::NeedsRebuild) + } + + fn child_in_workspace(&mut self, crate_dir: &Path) -> io::Result { + if let Some(in_workspace) = self.in_workspace.get(crate_dir) { + Ok(*in_workspace) + } else { + let mut cmd = Cmd::new(); + let manafest_path = crate_dir.join("Cargo.toml"); + cmd.manifest_path(&manafest_path); + let cmd: MetadataCommand = cmd.into(); + let metadata = cmd + .exec() + .map_err(|err| io::Error::new(io::ErrorKind::Other, err))?; + + let in_workspace = metadata.workspace_root != crate_dir; + self.in_workspace + .insert(crate_dir.to_path_buf(), in_workspace); + Ok(in_workspace) + } } } diff --git a/packages/ssr/Cargo.toml b/packages/ssr/Cargo.toml index 0ce901b5a..02900ca0e 100644 --- a/packages/ssr/Cargo.toml +++ b/packages/ssr/Cargo.toml @@ -14,6 +14,7 @@ keywords = ["dom", "ui", "gui", "react", "ssr"] [dependencies] dioxus-core = { path = "../core", version = "^0.3.0", features = ["serialize"] } +askama_escape = "0.10.3" [dev-dependencies] dioxus = { path = "../dioxus", version = "0.3.0" } diff --git a/packages/ssr/README.md b/packages/ssr/README.md index ef5931874..c5b91b7b8 100644 --- a/packages/ssr/README.md +++ b/packages/ssr/README.md @@ -8,10 +8,9 @@ ## Resources This crate is a part of the broader Dioxus ecosystem. For more resources about Dioxus, check out: -- [Getting Started](https://dioxuslabs.com/getting-started) -- [Book](https://dioxuslabs.com/book) -- [Reference](https://dioxuslabs.com/reference) -- [Community Examples](https://github.com/DioxusLabs/community-examples) +- [Getting Started](https://dioxuslabs.com/docs/0.3/guide/en/getting_started/index.html) +- [Book](https://dioxuslabs.com/docs/0.3/guide/en/) +- [Examples](https://github.com/DioxusLabs/example-projects) ## Overview diff --git a/packages/ssr/src/cache.rs b/packages/ssr/src/cache.rs index 39bfb8671..8556682f1 100644 --- a/packages/ssr/src/cache.rs +++ b/packages/ssr/src/cache.rs @@ -82,7 +82,13 @@ impl StringCache { } cur_path.pop(); } - TemplateNode::Text { text } => write!(chain, "{text}")?, + TemplateNode::Text { text } => { + write!( + chain, + "{}", + askama_escape::escape(text, askama_escape::Html) + )?; + } TemplateNode::Dynamic { id: idx } | TemplateNode::DynamicText { id: idx } => { chain.segments.push(Segment::Node(*idx)) } diff --git a/packages/ssr/src/renderer.rs b/packages/ssr/src/renderer.rs index 8c828f01c..5b6fa8fb2 100644 --- a/packages/ssr/src/renderer.rs +++ b/packages/ssr/src/renderer.rs @@ -104,8 +104,11 @@ impl Renderer { write!(buf, "")?; } - // todo: escape the text - write!(buf, "{}", text.value)?; + write!( + buf, + "{}", + askama_escape::escape(text.value, askama_escape::Html) + )?; if self.pre_render { write!(buf, "")?; @@ -119,7 +122,7 @@ impl Renderer { DynamicNode::Placeholder(_el) => { if self.pre_render { - write!(buf, "
")?;
+                            write!(buf, "
")?;
                         }
                     }
                 },
@@ -138,7 +141,7 @@ fn to_string_works() {
 
     fn app(cx: Scope) -> Element {
         let dynamic = 123;
-        let dyn2 = ""; // todo: escape this
+        let dyn2 = ""; // this should be escaped
 
         render! {
             div { class: "asdasdasd", class: "asdasdasd", id: "id-{dynamic}",
@@ -165,10 +168,10 @@ fn to_string_works() {
                 vec![
                     PreRendered("
Hello world 1 -->".into(),), + PreRendered(">Hello world 1 -->".into(),), Node(0,), PreRendered( - "<-- Hello world 2
nest 1
nest 2
".into(), + "<-- Hello world 2
nest 1
nest 2
".into(), ), Node(1,), Node(2,), @@ -180,5 +183,5 @@ fn to_string_works() { use Segment::*; - assert_eq!(out, "
Hello world 1 -->123<-- Hello world 2
nest 1
nest 2
finalize 0
finalize 1
finalize 2
finalize 3
finalize 4
"); + assert_eq!(out, "
Hello world 1 -->123<-- Hello world 2
nest 1
nest 2
</diiiiiiiiv>
finalize 0
finalize 1
finalize 2
finalize 3
finalize 4
"); } diff --git a/packages/ssr/tests/simple.rs b/packages/ssr/tests/simple.rs index 63137aeae..395f801e1 100644 --- a/packages/ssr/tests/simple.rs +++ b/packages/ssr/tests/simple.rs @@ -38,7 +38,7 @@ fn dynamic() { dioxus_ssr::render_lazy(rsx! { div { "Hello world 1 -->" "{dynamic}" "<-- Hello world 2" } }), - "
Hello world 1 -->123<-- Hello world 2
" + "
Hello world 1 -->123<-- Hello world 2
" ); } diff --git a/packages/web/Cargo.toml b/packages/web/Cargo.toml index 805cacddd..7adfbf13a 100644 --- a/packages/web/Cargo.toml +++ b/packages/web/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dioxus-web" -version = "0.3.0" +version = "0.3.1" authors = ["Jonathan Kelley"] edition = "2018" description = "Dioxus VirtualDOM renderer for the web browser using websys" diff --git a/packages/web/README.md b/packages/web/README.md index d3f4c1aeb..87265c926 100644 --- a/packages/web/README.md +++ b/packages/web/README.md @@ -18,7 +18,7 @@ [discord-url]: https://discord.gg/XgGxMSkvUM [Website](https://dioxuslabs.com) | -[Guides](https://dioxuslabs.com/guide/) | +[Guides](https://dioxuslabs.com/docs/0.3/guide/en/) | [API Docs](https://docs.rs/dioxus-web/latest/dioxus_web) | [Chat](https://discord.gg/XgGxMSkvUM) diff --git a/translations/pt-br/README.md b/translations/pt-br/README.md index ef14a1af9..a50727662 100644 --- a/translations/pt-br/README.md +++ b/translations/pt-br/README.md @@ -40,7 +40,7 @@ | Exemplos | - Guia + Guia | äž­æ–‡ @@ -83,7 +83,7 @@ Se vocĂȘ conhece React, entĂŁo vocĂȘ jĂĄ conhece o Dioxus. - + @@ -159,7 +159,7 @@ Dioxus Ă© Ășnico no ecossistema do Rust por suportar: - SSR com `hydration` feito pelo Cliente - Suporte Ă  aplicação Desktop -Para mais informaçÔes sobre quais funçÔes estĂŁo atualmente disponĂ­veis e para o progresso futuro, veja [O Guia](https://dioxuslabs.com/guide/). +Para mais informaçÔes sobre quais funçÔes estĂŁo atualmente disponĂ­veis e para o progresso futuro, veja [O Guia](https://dioxuslabs.com/guide/pt-br/). ## Projeto dentro do ecossistema Dioxus
TutorialTutorial Web Desktop SSR