Merge branch 'upstream' into simplify-native-core

This commit is contained in:
Evan Almloff 2023-03-14 16:21:39 -05:00
parent 4b81cdbf1b
commit 41bf87e74f
63 changed files with 818 additions and 245 deletions

View file

@ -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

View file

@ -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

View file

@ -40,7 +40,7 @@
<span> | </span>
<a href="https://github.com/DioxusLabs/example-projects"> Examples </a>
<span> | </span>
<a href="https://dioxuslabs.com/guide"> Guide </a>
<a href="https://dioxuslabs.com/docs/0.3/guide/en/"> Guide </a>
<span> | </span>
<a href="https://github.com/DioxusLabs/dioxus/blob/master/notes/README/ZH_CN.md"> 中文 </a>
<span> | </span>

View file

@ -7,7 +7,7 @@ fn main() {}
struct AppSettings {}
// ANCHOR: wrap_context
fn use_settings(cx: &ScopeState) -> UseSharedState<AppSettings> {
fn use_settings(cx: &ScopeState) -> &UseSharedState<AppSettings> {
use_shared_state::<AppSettings>(cx).expect("App settings not provided")
}
// ANCHOR_END: wrap_context

View file

@ -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

View file

@ -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.

View file

@ -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

View file

@ -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

View file

@ -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!

View file

@ -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).

17
examples/shortcut.rs Normal file
View file

@ -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()}"))
}

View file

@ -40,7 +40,7 @@
<span> | </span>
<a href="https://github.com/DioxusLabs/example-projects"> 代码示例 </a>
<span> | </span>
<a href="https://dioxuslabs.com/guide"> 开发指南 </a>
<a href="https://dioxuslabs.com/guide/en"> 开发指南 </a>
<span> | </span>
<a href="https://github.com/DioxusLabs/dioxus/blob/master/README.md"> English </a>
<span> | </span>

View file

@ -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"] }

View file

@ -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]

View file

@ -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)

View file

@ -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;

View file

@ -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"

View file

@ -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,
}
}
}

View file

@ -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 {

View file

@ -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<Item = &'b VNode<'b>>) -> usize {

View file

@ -72,9 +72,9 @@ pub struct LocalTaskHandle {
impl ArcWake for LocalTaskHandle {
fn wake_by_ref(arc_self: &Arc<Self>) {
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));
}
}

View file

@ -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

View file

@ -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);

View file

@ -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

View file

@ -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<RefCell<Vec<*mut objc::runtime::Object>>>,
}
@ -83,6 +90,7 @@ impl DesktopContext {
event_loop: EventLoopWindowTarget<UserWindowEvent>,
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<ShortcutId, ShortcutRegistryError> {
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<objc::runtime::Object>) {

View file

@ -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<P: 'static>(root: Component<P>, 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<P: 'static>(root: Component<P>, 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<P: 'static>(root: Component<P>, 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();

View file

@ -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!

View file

@ -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<RefCell<ShortcutManager>>,
shortcuts: ShortcutMap,
}
type ShortcutMap = Rc<RefCell<HashMap<AcceleratorId, Shortcut>>>;
struct Shortcut {
shortcut: GlobalShortcut,
callbacks: Slab<Box<dyn FnMut()>>,
}
impl Shortcut {
fn insert(&mut self, callback: Box<dyn FnMut()>) -> 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<T>(target: &EventLoopWindowTarget<T>) -> 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<Option<ModifiersState>>,
key: KeyCode,
callback: Box<dyn FnMut()>,
) -> Result<ShortcutId, ShortcutRegistryError> {
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<dyn std::error::Error>),
}
#[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<ShortcutHandle, ShortcutRegistryError> {
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),
}
}
}

View file

@ -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 }

View file

@ -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

View file

@ -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 {

View file

@ -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" }

View file

@ -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)

View file

@ -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<T> = Rc<RefCell<ProvidedStateInner<T>>>;
// Tracks all the subscribers to a shared State
pub struct ProvidedStateInner<T> {
value: Rc<RefCell<T>>,
pub(crate) struct ProvidedStateInner<T> {
value: T,
notify_any: Arc<dyn Fn(ScopeId)>,
consumers: HashSet<ScopeId>,
}
@ -21,14 +21,6 @@ impl<T> ProvidedStateInner<T> {
(self.notify_any)(*consumer);
}
}
pub fn write(&self) -> RefMut<T> {
self.value.borrow_mut()
}
pub fn read(&self) -> Ref<T> {
self.value.borrow()
}
}
/// This hook provides some relatively light ergonomics around shared state.
@ -38,18 +30,57 @@ impl<T> ProvidedStateInner<T> {
///
/// # 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::<Theme>(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::<Theme>(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<T> ProvidedStateInner<T> {
/// 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<T: 'static>(cx: &ScopeState) -> Option<UseSharedState<T>> {
let state = cx.use_hook(|| {
pub fn use_shared_state<T: 'static>(cx: &ScopeState) -> Option<&UseSharedState<T>> {
let state: &Option<UseSharedStateOwner<T>> = &*cx.use_hook(move || {
let scope_id = cx.scope_id();
let root = cx.consume_context::<ProvidedState<T>>();
let root = cx.consume_context::<ProvidedState<T>>()?;
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<T: 'static> {
root: Option<ProvidedState<T>>,
value: Option<Rc<RefCell<T>>>,
/// This wrapper detects when the hook is dropped and will unsubscribe when the component is unmounted
struct UseSharedStateOwner<T> {
state: UseSharedState<T>,
scope_id: ScopeId,
needs_notification: Cell<bool>,
}
impl<T> Drop for SharedStateInner<T> {
impl<T> Drop for UseSharedStateOwner<T> {
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<RefCell<T>>,
pub(crate) root: &'a Rc<RefCell<ProvidedStateInner<T>>>,
pub(crate) needs_notification: &'a Cell<bool>,
/// State that is shared between components through the context system
pub struct UseSharedState<T> {
pub(crate) inner: Rc<RefCell<ProvidedStateInner<T>>>,
}
impl<'a, T: 'static> UseSharedState<'a, T> {
impl<T> UseSharedState<T> {
/// 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<RefCell<ProvidedStateInner<T>>> {
self.root.clone()
RefMut::map(self.inner.borrow_mut(), |inner| &mut inner.value)
}
}
impl<T> Copy for UseSharedState<'_, T> {}
impl<'a, T> Clone for UseSharedState<'a, T>
where
T: 'static,
{
impl<T> Clone for UseSharedState<T> {
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<T: PartialEq> PartialEq for UseSharedState<T> {
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::<Theme>(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<T: 'static>(cx: &ScopeState, f: impl FnOnce() -> T) {
cx.use_hook(|| {
let state: ProvidedState<T> = Rc::new(RefCell::new(ProvidedStateInner {
value: Rc::new(RefCell::new(f())),
value: f(),
notify_any: cx.schedule_update_any(),
consumers: HashSet::new(),
}));

View file

@ -108,6 +108,12 @@ impl<T> Coroutine<T> {
}
}
impl<T> PartialEq for Coroutine<T> {
fn eq(&self, other: &Self) -> bool {
self.task == other.task
}
}
#[cfg(test)]
mod tests {
#![allow(unused)]

View file

@ -329,7 +329,7 @@ impl PartialEq<bool> for &UseState<bool> {
}
}
impl<T: PartialEq> PartialEq<UseState<T>> for UseState<T> {
impl<T> PartialEq<UseState<T>> for UseState<T> {
fn eq(&self, other: &UseState<T>) -> bool {
Rc::ptr_eq(&self.current_val, &other.current_val)
}

View file

@ -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"
ignore = "0.4.19"

View file

@ -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)

View file

@ -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<Ctx: HotReloadingContext + Send + 'static>(cfg: Config<Ctx>) {
} = 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::<Vec<_>>();
let channels = Arc::new(Mutex::new(Vec::new()));
let file_map = Arc::new(Mutex::new(FileMap::<Ctx>::new(crate_dir.clone())));
if let Ok(local_socket_stream) = LocalSocketListener::bind(temp_file.as_path()) {
let FileMapBuildResult {
map: file_map,
errors,
} = FileMap::<Ctx>::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<Ctx: HotReloadingContext + Send + 'static>(cfg: Config<Ctx>) {
// 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<Ctx: HotReloadingContext + Send + 'static>(cfg: Config<Ctx>) {
}
}
let excluded_paths = excluded_paths
.iter()
.map(|path| crate_dir.join(PathBuf::from(path)))
.collect::<Vec<_>>();
let mut rebuild = {
let aborted = aborted.clone();
let channels = channels.clone();
@ -242,7 +260,7 @@ pub fn init<Ctx: HotReloadingContext + Send + 'static>(cfg: Config<Ctx>) {
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<Ctx: HotReloadingContext + Send + 'static>(cfg: Config<Ctx>) {
.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<Ctx: HotReloadingContext + Send + 'static>(cfg: Config<Ctx>) {
}
}
}
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();

View file

@ -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 }

View file

@ -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)

View file

@ -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"

View file

@ -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)

View file

@ -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 })

View file

@ -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)

View file

@ -28,7 +28,7 @@ async fn main() {
r#"
<!DOCTYPE html>
<html>
<head> <title>Dioxus LiveView with Warp</title> </head>
<head> <title>Dioxus LiveView with axum</title> </head>
<body> <div id="main"></div> </body>
{glue}
</html>

View file

@ -41,7 +41,7 @@ fn index(_depot: &mut Depot, res: &mut Response) {
r#"
<!DOCTYPE html>
<html>
<head> <title>Dioxus LiveView with Warp</title> </head>
<head> <title>Dioxus LiveView with Salvo</title> </head>
<body> <div id="main"></div> </body>
{glue}
</html>

View file

@ -37,7 +37,16 @@ impl LiveViewPool {
app: fn(Scope<T>) -> 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<F: FnOnce() -> 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<S> 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<T>(
app: Component<T>,
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();

View file

@ -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!

View file

@ -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)

View file

@ -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"

View file

@ -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)

View file

@ -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"

View file

@ -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)

View file

@ -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<Ctx: HotReloadingContext> {
/// The FileMap that was built
pub map: FileMap<Ctx>,
/// Any errors that occurred while building the FileMap that were not fatal
pub errors: Vec<io::Error>,
}
pub struct FileMap<Ctx: HotReloadingContext> {
pub map: HashMap<PathBuf, (String, Option<Template<'static>>)>,
in_workspace: HashMap<PathBuf, bool>,
phantom: std::marker::PhantomData<Ctx>,
}
impl<Ctx: HotReloadingContext> FileMap<Ctx> {
/// Create a new FileMap from a crate directory
pub fn new(path: PathBuf) -> Self {
pub fn create(path: PathBuf) -> io::Result<FileMapBuildResult<Ctx>> {
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<FileMapBuildResult<Ctx>> {
struct FileMapSearchResult {
map: HashMap<PathBuf, (String, Option<Template<'static>>)>,
errors: Vec<io::Error>,
}
fn find_rs_files(
root: PathBuf,
) -> io::Result<HashMap<PathBuf, (String, Option<Template<'static>>)>> {
filter: &mut impl FnMut(&Path) -> bool,
) -> io::Result<FileMapSearchResult> {
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<UpdateResult> {
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<Ctx: HotReloadingContext> FileMap<Ctx> {
syn::parse2::<CallBody>(old.tokens),
syn::parse2::<CallBody>(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<Ctx: HotReloadingContext> FileMap<Ctx> {
{
// 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<Ctx: HotReloadingContext> FileMap<Ctx> {
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<bool> {
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)
}
}
}

View file

@ -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" }

View file

@ -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

View file

@ -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))
}

View file

@ -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, "<pre><pre/>")?;
write!(buf, "<pre></pre>")?;
}
}
},
@ -138,7 +141,7 @@ fn to_string_works() {
fn app(cx: Scope) -> Element {
let dynamic = 123;
let dyn2 = "</diiiiiiiiv>"; // todo: escape this
let dyn2 = "</diiiiiiiiv>"; // this should be escaped
render! {
div { class: "asdasdasd", class: "asdasdasd", id: "id-{dynamic}",
@ -165,10 +168,10 @@ fn to_string_works() {
vec![
PreRendered("<div class=\"asdasdasd\" class=\"asdasdasd\"".into(),),
Attr(0,),
PreRendered(">Hello world 1 -->".into(),),
PreRendered(">Hello world 1 --&gt;".into(),),
Node(0,),
PreRendered(
"<-- Hello world 2<div>nest 1</div><div></div><div>nest 2</div>".into(),
"&lt;-- Hello world 2<div>nest 1</div><div></div><div>nest 2</div>".into(),
),
Node(1,),
Node(2,),
@ -180,5 +183,5 @@ fn to_string_works() {
use Segment::*;
assert_eq!(out, "<div class=\"asdasdasd\" class=\"asdasdasd\" id=\"id-123\">Hello world 1 -->123<-- Hello world 2<div>nest 1</div><div></div><div>nest 2</div></diiiiiiiiv><div>finalize 0</div><div>finalize 1</div><div>finalize 2</div><div>finalize 3</div><div>finalize 4</div></div>");
assert_eq!(out, "<div class=\"asdasdasd\" class=\"asdasdasd\" id=\"id-123\">Hello world 1 --&gt;123&lt;-- Hello world 2<div>nest 1</div><div></div><div>nest 2</div>&lt;/diiiiiiiiv&gt;<div>finalize 0</div><div>finalize 1</div><div>finalize 2</div><div>finalize 3</div><div>finalize 4</div></div>");
}

View file

@ -38,7 +38,7 @@ fn dynamic() {
dioxus_ssr::render_lazy(rsx! {
div { "Hello world 1 -->" "{dynamic}" "<-- Hello world 2" }
}),
"<div>Hello world 1 -->123<-- Hello world 2</div>"
"<div>Hello world 1 --&gt;123&lt;-- Hello world 2</div>"
);
}

View file

@ -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"

View file

@ -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)

View file

@ -40,7 +40,7 @@
<span> | </span>
<a href="https://github.com/DioxusLabs/example-projects"> Exemplos </a>
<span> | </span>
<a href="https://dioxuslabs.com/guide"> Guia </a>
<a href="https://dioxuslabs.com/guide/pt-br"> Guia </a>
<span> | </span>
<a href="https://github.com/DioxusLabs/dioxus/blob/master/notes/README/ZH_CN.md"> 中文 </a>
</h3>
@ -83,7 +83,7 @@ Se você conhece React, então você já conhece o Dioxus.
<table style="width:100%" align="center">
<tr>
<th><a href="https://dioxuslabs.com/guide/">Tutorial</a></th>
<th><a href="https://dioxuslabs.com/guide/pt-br/">Tutorial</a></th>
<th><a href="https://dioxuslabs.com/reference/web">Web</a></th>
<th><a href="https://dioxuslabs.com/reference/desktop/">Desktop</a></th>
<th><a href="https://dioxuslabs.com/reference/ssr/">SSR</a></th>
@ -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