(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.
- Tutorial
+ Tutorial
Web
Desktop
SSR
@@ -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