diff --git a/.vscode/spellright.dict b/.vscode/spellright.dict index 5c73ac785..c15bc778c 100644 --- a/.vscode/spellright.dict +++ b/.vscode/spellright.dict @@ -64,3 +64,4 @@ Tokio asynchronicity constified SegVec +contentful diff --git a/README.md b/README.md index bb9227165..793df7b5e 100644 --- a/README.md +++ b/README.md @@ -165,15 +165,16 @@ Dioxus is heavily inspired by React, but we want your transition to feel like an | Custom elements | ✅ | ✅ | Define new element primitives | | Suspense | ✅ | ✅ | schedule future render from future/promise | | Integrated error handling | ✅ | ✅ | Gracefully handle errors with ? syntax | -| Effects | 🛠 | ✅ | Run effects after a component has been committed to render | +| Re-hydration | 🛠 | ✅ | Pre-render to HTML to speed up first contentful paint | | Cooperative Scheduling | 🛠 | ✅ | Prioritize important events over non-important events | -| NodeRef | 🛠 | ✅ | gain direct access to nodes [1] | | Runs natively | ✅ | ❓ | runs as a portable binary w/o a runtime (Node) | | 1st class global state | ✅ | ❓ | redux/recoil/mobx on top of context | | Subtree Memoization | ✅ | ❓ | skip diffing static element subtrees | | Compile-time correct | ✅ | ❓ | Throw errors on invalid template layouts | | Heuristic Engine | 🛠 | ❓ | track component memory usage to minimize future allocations | | Fine-grained reactivity | 🛠 | ❓ | Skip diffing for fine-grain updates | +| Effects | 🛠 | ✅ | Run effects after a component has been committed to render | +| NodeRef | 🛠 | ✅ | gain direct access to nodes [1] | - [1] Currently blocked until we figure out a cross-platform way of exposing an imperative Node API. @@ -195,7 +196,7 @@ Dioxus is heavily inspired by React, but we want your transition to feel like an | Feature | Dioxus | React | Notes for Dioxus | | -------------------- | ------ | ----- | ------------------------------------ | | Portal | ❓ | ✅ | cast elements through tree | -| Error/Panic boundary | ❓ | ✅ | catch panics and display custom BSOD | +| Error/Panic boundary | 👀 | ✅ | catch panics and display custom BSOD | | Code-splitting | 👀 | ✅ | Make bundle smaller/lazy | | LiveView | 👀 | ❓ | Example for SSR + WASM apps | diff --git a/examples/reference/errorhandling.rs b/examples/reference/errorhandling.rs new file mode 100644 index 000000000..ba0f21a87 --- /dev/null +++ b/examples/reference/errorhandling.rs @@ -0,0 +1,85 @@ +//! Example: Errror Handling +//! ------------------------ +//! +//! Error handling in Dioxus comes in a few flavors. Because Dioxus is a Rust project, Options and Results are obviously +//! the go-to way of wrapping possibly-errored data. However, if a component fails to "unwrapping," everything will crash, +//! the page will deadlock, and your users will be sad. +//! +//! So, obviously, you need to handle your errors. +//! +//! Fortunately, it's easy to avoid panics, even during quick prototyping. +//! +//! Here's a few strategies: +//! - Leverage the ability to return "None" and propogate None directly +//! - Instead of propogating "None" manually, use the "?" syntax sugar +//! - Covert Results into Options with .ok() +//! - Manually display a separate screen by matching on Options/Results +//! +//! There *are* plans to add helpful screens for when apps completely panic in WASM. However, you should really try to +//! avoid panicking. +use dioxus::prelude::*; +fn main() {} + +/// This is one way to go about error handling (just toss things away with unwrap). +/// However, if you get it wrong, the whole app will crash. +/// This is pretty flimsy. +static App: FC<()> = |cx| { + let data = get_data().unwrap(); + cx.render(rsx!( div { "{data}" } )) +}; + +/// This is a pretty verbose way of error handling +/// However, it's still pretty good since we don't panic, just fail to render anything +static App1: FC<()> = |cx| { + let data = match get_data() { + Some(data) => data, + None => return None, + }; + cx.render(rsx!( div { "{data}" } )) +}; + +/// This is an even better form of error handling. +/// However, it _does_ make the component go blank, which might not be desirable. +/// +/// This type of error handling is good when you have "selectors" that produce Some/None based on some state that's +/// already controlled for higher in the tree. IE displaying a "Username" in a component that should only be shown if +/// a user is logged in. +/// +/// Dioxus will throw an error in the console if the None-path is ever taken. +static App2: FC<()> = |cx| { + let data = get_data()?; + cx.render(rsx!( div { "{data}" } )) +}; + +/// This is top-tier error handling since it displays a failure state. +/// +/// However, the error is lacking in context. +static App3: FC<()> = |cx| match get_data() { + Some(data) => cx.render(rsx!( div { "{data}" } )), + None => cx.render(rsx!( div { "Failed to load data :(" } )), +}; + +/// For errors that return results, it's possible short-circuit the match-based error handling with `.ok()` which converts +/// a Result into an Option and lets you +static App4: FC<()> = |cx| { + let data = get_data_err().ok()?; + cx.render(rsx!( div { "{data}" } )) +}; + +/// This is great error handling since it displays a failure state... with context! +/// +/// Hopefully you never need to disply a screen like this. It's rather bad taste +static App5: FC<()> = |cx| match get_data_err() { + Ok(data) => cx.render(rsx!( div { "{data}" } )), + Err(c) => cx.render(rsx!( div { "Failed to load data: {c}" } )), +}; + +// this fetching function produces "nothing" +fn get_data() -> Option { + None +} + +// this fetching function produces "nothing" +fn get_data_err() -> Result { + Result::Err("Failed!") +} diff --git a/packages/core-macro/Cargo.toml b/packages/core-macro/Cargo.toml index 6c2fc5a4c..f539cd108 100644 --- a/packages/core-macro/Cargo.toml +++ b/packages/core-macro/Cargo.toml @@ -12,7 +12,7 @@ description = "Core macro for Dioxus Virtual DOM" proc-macro = true [dependencies] -once_cell = "1.7.2" +once_cell = "1.8" proc-macro2 = { version = "1.0.6" } quote = "1.0" syn = { version = "1.0.11", features = ["full", "extra-traits"] } diff --git a/packages/core/src/arena.rs b/packages/core/src/arena.rs index f97f45717..6d24afee0 100644 --- a/packages/core/src/arena.rs +++ b/packages/core/src/arena.rs @@ -1,13 +1,13 @@ use std::{cell::UnsafeCell, rc::Rc}; use crate::innerlude::*; -use slotmap::{DefaultKey, SlotMap}; +use slotmap::SlotMap; #[derive(Clone)] pub struct SharedArena { pub components: Rc>, } -pub type ScopeMap = SlotMap; +pub type ScopeMap = SlotMap; enum MutStatus { Immut, diff --git a/packages/core/src/bumpframe.rs b/packages/core/src/bumpframe.rs index ab28508af..62d5035dd 100644 --- a/packages/core/src/bumpframe.rs +++ b/packages/core/src/bumpframe.rs @@ -77,13 +77,14 @@ impl ActiveFrame { } } - pub fn next(&mut self) -> &mut BumpFrame { - *self.generation.borrow_mut() += 1; - - if *self.generation.borrow() % 2 == 0 { - &mut self.frames[0] - } else { - &mut self.frames[1] + pub fn old_frame_mut(&mut self) -> &mut BumpFrame { + match *self.generation.borrow() & 1 == 0 { + true => &mut self.frames[1], + false => &mut self.frames[0], } } + + pub fn cycle_frame(&mut self) { + *self.generation.borrow_mut() += 1; + } } diff --git a/packages/core/src/context.rs b/packages/core/src/context.rs index e2461993f..0c8879eae 100644 --- a/packages/core/src/context.rs +++ b/packages/core/src/context.rs @@ -115,6 +115,11 @@ impl<'src, P> Context<'src, P> { todo!() } + /// Get's this context's ScopeId + pub fn get_scope_id(&self) -> ScopeId { + self.scope.our_arena_idx.clone() + } + /// Take a lazy VNode structure and actually build it with the context of the VDom's efficient VNode allocator. /// /// This function consumes the context and absorb the lifetime, so these VNodes *must* be returned. @@ -200,9 +205,13 @@ Any function prefixed with "use" should not be called conditionally. /// /// /// - pub fn use_create_context(&self, init: impl Fn() -> T) { - let mut cxs = self.scope.shared_contexts.borrow_mut(); + pub fn use_provide_context(self, init: F) -> &'src Rc + where + T: 'static, + F: FnOnce() -> T, + { let ty = TypeId::of::(); + let contains_key = self.scope.shared_contexts.borrow().contains_key(&ty); let is_initialized = self.use_hook( |_| false, @@ -214,18 +223,30 @@ Any function prefixed with "use" should not be called conditionally. |_| {}, ); - match (is_initialized, cxs.contains_key(&ty)) { + match (is_initialized, contains_key) { // Do nothing, already initialized and already exists (true, true) => {} // Needs to be initialized (false, false) => { log::debug!("Initializing context..."); - cxs.insert(ty, Rc::new(init())); + let initialized = Rc::new(init()); + let p = self + .scope + .shared_contexts + .borrow_mut() + .insert(ty, initialized); + log::info!( + "There are now {} shared contexts for scope {:?}", + self.scope.shared_contexts.borrow().len(), + self.scope.our_arena_idx, + ); } _ => debug_assert!(false, "Cannot initialize two contexts of the same type"), - } + }; + + self.use_context::() } /// There are hooks going on here! @@ -234,26 +255,19 @@ Any function prefixed with "use" should not be called conditionally. } /// Uses a context, storing the cached value around - pub fn try_use_context(self) -> Result<&'src Rc> { + /// + /// If a context is not found on the first search, then this call will be "dud", always returning "None" even if a + /// context was added later. This allows using another hook as a fallback + /// + pub fn try_use_context(self) -> Option<&'src Rc> { struct UseContextHook { par: Option>, - we: Option>, } self.use_hook( - move |_| UseContextHook { - par: None as Option>, - we: None as Option>, - }, - move |hook| { + move |_| { let mut scope = Some(self.scope); - - if let Some(we) = &hook.we { - if let Some(re) = we.upgrade() { - hook.par = Some(re); - return Ok(hook.par.as_ref().unwrap()); - } - } + let mut par = None; let ty = TypeId::of::(); while let Some(inner) = scope { @@ -261,35 +275,38 @@ Any function prefixed with "use" should not be called conditionally. "Searching {:#?} for valid shared_context", inner.our_arena_idx ); - let shared_contexts = inner.shared_contexts.borrow(); + let shared_ctx = { + let shared_contexts = inner.shared_contexts.borrow(); - if let Some(shared_cx) = shared_contexts.get(&ty) { + log::debug!( + "This component has {} shared contexts", + shared_contexts.len() + ); + shared_contexts.get(&ty).map(|f| f.clone()) + }; + + if let Some(shared_cx) = shared_ctx { log::debug!("found matching cx"); let rc = shared_cx .clone() .downcast::() .expect("Should not fail, already validated the type from the hashmap"); - hook.we = Some(Rc::downgrade(&rc)); - hook.par = Some(rc); - return Ok(hook.par.as_ref().unwrap()); + par = Some(rc); + break; } else { match inner.parent_idx { Some(parent_id) => { - let parent = inner - .arena_link - .get(parent_id) - .ok_or_else(|| Error::FatalInternal("Failed to find parent"))?; - - scope = Some(parent); + scope = inner.arena_link.get(parent_id); } - None => return Err(Error::MissingSharedContext), + None => break, } } } - - Err(Error::MissingSharedContext) + // + UseContextHook { par } }, + move |hook| hook.par.as_ref(), |_| {}, ) } diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 7247ef5a4..a91a81cdc 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -50,6 +50,7 @@ use crate::{arena::SharedArena, innerlude::*, tasks::TaskQueue}; use fxhash::FxHashSet; +use smallvec::{smallvec, SmallVec}; use std::any::Any; @@ -74,7 +75,7 @@ pub struct DiffMachine<'real, 'bump, Dom: RealDom<'bump>> { pub edits: DomEditor<'real, 'bump>, pub components: &'bump SharedArena, pub task_queue: &'bump TaskQueue, - pub cur_idx: ScopeId, + pub cur_idxs: SmallVec<[ScopeId; 5]>, pub diffed: FxHashSet, pub event_queue: EventQueue, pub seen_nodes: FxHashSet, @@ -96,7 +97,7 @@ where edits: DomEditor::new(edits), components, dom, - cur_idx, + cur_idxs: smallvec![cur_idx], event_queue, task_queue, diffed: FxHashSet::default(), @@ -156,6 +157,7 @@ where log::warn!("diffing components? {:#?}", new.user_fc); if old.user_fc == new.user_fc { // Make sure we're dealing with the same component (by function pointer) + self.cur_idxs.push(old.ass_scope.get().unwrap()); // Make sure the new component vnode is referencing the right scope id let scope_id = old.ass_scope.get(); @@ -181,6 +183,7 @@ where } else { // } + self.cur_idxs.pop(); self.seen_nodes.insert(scope_id.unwrap()); } else { @@ -399,10 +402,10 @@ where log::debug!("Mounting a new component"); let caller = vcomponent.caller.clone(); - let parent_idx = self.cur_idx; + let parent_idx = self.cur_idxs.last().unwrap().clone(); // Insert a new scope into our component list - let idx = self + let new_idx = self .components .with(|components| { components.insert_with_key(|new_idx| { @@ -435,20 +438,22 @@ where // TODO: abstract this unsafe into the arena abstraction let inner: &'bump mut _ = unsafe { &mut *self.components.components.get() }; - let new_component = inner.get_mut(idx).unwrap(); + let new_component = inner.get_mut(new_idx).unwrap(); // Actually initialize the caller's slot with the right address - vcomponent.ass_scope.set(Some(idx)); + vcomponent.ass_scope.set(Some(new_idx)); // Run the scope for one iteration to initialize it new_component.run_scope().unwrap(); // TODO: we need to delete (IE relcaim this node, otherwise the arena will grow infinitely) let nextnode = new_component.next_frame(); + self.cur_idxs.push(new_idx); let meta = self.create(nextnode); + self.cur_idxs.pop(); // Finally, insert this node as a seen node. - self.seen_nodes.insert(idx); + self.seen_nodes.insert(new_idx); CreateMeta::new(vcomponent.is_static, meta.added_to_stack) } diff --git a/packages/core/src/error.rs b/packages/core/src/error.rs index 6d7834213..8e675b026 100644 --- a/packages/core/src/error.rs +++ b/packages/core/src/error.rs @@ -19,6 +19,9 @@ pub enum Error { #[error("Wrong Properties Type")] WrongProps, + #[error("The component failed to return VNodes")] + ComponentFailed, + #[error("Base scope has not been mounted yet")] NotMounted, diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index b9d9f20bc..6a2fab33f 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -10,7 +10,7 @@ //! pub use crate::innerlude::{ - format_args_f, html, rsx, Context, DioxusElement, DomEdit, EventTrigger, LazyNodes, + format_args_f, html, rsx, Context, DioxusElement, DomEdit, DomTree, EventTrigger, LazyNodes, NodeFactory, Properties, RealDom, RealDomNode, ScopeId, VNode, VNodeKind, VirtualDom, VirtualEvent, FC, }; diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index e72ec1b7a..d6db0a637 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -267,6 +267,22 @@ impl<'a> NodeFactory<'a> { } } + pub fn attr_with_alloc_val( + &self, + name: &'static str, + val: &'a str, + namespace: Option<&'static str>, + is_volatile: bool, + ) -> Attribute<'a> { + Attribute { + name, + value: val, + is_static: false, + namespace, + is_volatile, + } + } + pub fn component

( &self, component: FC

, @@ -508,6 +524,25 @@ impl IntoVNode<'_> for Option<()> { cx.fragment_from_iter(None as Option) } } +impl<'a> IntoVNode<'a> for Option> { + fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> { + match self { + Some(n) => n, + None => cx.fragment_from_iter(None as Option), + } + } +} + +impl IntoVNode<'_> for &'static str { + fn into_vnode<'a>(self, cx: NodeFactory<'a>) -> VNode<'a> { + NodeFactory::static_text(self) + } +} +impl IntoVNode<'_> for Arguments<'_> { + fn into_vnode<'a>(self, cx: NodeFactory<'a>) -> VNode<'a> { + cx.text(self) + } +} impl Debug for NodeFactory<'_> { fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { diff --git a/packages/core/src/scope.rs b/packages/core/src/scope.rs index ba21631e5..1c6dd4244 100644 --- a/packages/core/src/scope.rs +++ b/packages/core/src/scope.rs @@ -113,7 +113,8 @@ impl Scope { // Remove all the outdated listeners // This is a very dangerous operation - self.frames.next().bump.reset(); + let next_frame = self.frames.old_frame_mut(); + next_frame.bump.reset(); self.listeners.borrow_mut().clear(); @@ -123,16 +124,19 @@ impl Scope { // Cast the caller ptr from static to one with our own reference let c3: &WrappedCaller = self.caller.as_ref(); - self.frames.cur_frame_mut().head_node = unsafe { self.call_user_component(c3) }; - - Ok(()) - } - - // this is its own function so we can preciesly control how lifetimes flow - unsafe fn call_user_component<'a>(&'a self, caller: &WrappedCaller) -> VNode<'static> { - let new_head: Option> = caller(self); - let new_head = new_head.unwrap_or(errored_fragment()); - std::mem::transmute(new_head) + match c3(self) { + None => { + // the user's component failed. We avoid cycling to the next frame + log::error!("Running your component failed! It will no longer receive events."); + Err(Error::ComponentFailed) + } + Some(new_head) => { + // the user's component succeeded. We can safely cycle to the next frame + self.frames.old_frame_mut().head_node = unsafe { std::mem::transmute(new_head) }; + self.frames.cycle_frame(); + Ok(()) + } + } } // A safe wrapper around calling listeners diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 21eb7bc3b..db7a26a92 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -30,7 +30,9 @@ use std::any::TypeId; use std::cell::RefCell; use std::pin::Pin; -pub type ScopeId = DefaultKey; +slotmap::new_key_type! { + pub struct ScopeId; +} /// An integrated virtual node system that progresses events and diffs UI trees. /// Differences are converted into patches which a renderer can use to draw the UI. @@ -140,7 +142,7 @@ impl VirtualDom { /// let dom = VirtualDom::new(Example); /// ``` pub fn new_with_props(root: FC

, root_props: P) -> Self { - let components = SharedArena::new(SlotMap::new()); + let components = SharedArena::new(SlotMap::::with_key()); let root_props: Pin> = Box::pin(root_props); let props_ptr = root_props.as_ref().downcast_ref::

().unwrap() as *const P; @@ -225,11 +227,12 @@ impl VirtualDom { ); let cur_component = self.components.get_mut(self.base_scope).unwrap(); - cur_component.run_scope()?; - let meta = diff_machine.create(cur_component.next_frame()); - - diff_machine.edits.append_children(meta.added_to_stack); + // We run the component. If it succeeds, then we can diff it and add the changes to the dom. + if cur_component.run_scope().is_ok() { + let meta = diff_machine.create(cur_component.next_frame()); + diff_machine.edits.append_children(meta.added_to_stack); + } Ok(()) } @@ -370,10 +373,10 @@ impl VirtualDom { // We are guaranteeed that this scope is unique because we are tracking which nodes have modified let cur_component = self.components.get_mut(update.idx).unwrap(); - cur_component.run_scope()?; - - let (old, new) = (cur_component.old_frame(), cur_component.next_frame()); - diff_machine.diff_node(old, new); + if cur_component.run_scope().is_ok() { + let (old, new) = (cur_component.old_frame(), cur_component.next_frame()); + diff_machine.diff_node(old, new); + } } } } diff --git a/packages/desktop/src/index.html b/packages/desktop/src/index.html index 44a9f0d19..e67dee623 100644 --- a/packages/desktop/src/index.html +++ b/packages/desktop/src/index.html @@ -51,8 +51,7 @@ const node = self.stack.pop(); node.remove(); } - RemoveAllChildren(self, edit) { - } + RemoveAllChildren(self, edit) {} CreateTextNode(self, edit) { self.stack.push(document.createTextNode(edit.text)); } @@ -62,24 +61,32 @@ self.stack.push(document.createElement(tagName)); } CreateElementNs(self, edit) { + const tagName = edit.tag; + console.log(`creating namespaced element: `, edit); self.stack.push(document.createElementNS(edit.ns, edit.tag)); } CreatePlaceholder(self, edit) { const a = `self.stack.push(document.createElement("pre"))`; self.stack.push(document.createComment("vroot")); } - NewEventListener(self, edit) { - } - RemoveEventListener(self, edit) { - } + NewEventListener(self, edit) {} + RemoveEventListener(self, edit) {} SetText(self, edit) { self.top().textContent = edit.text; } SetAttribute(self, edit) { const name = edit.field; const value = edit.value; + const ns = edit.ns; + const node = self.top(self.stack); - node.setAttribute(name, value); + if (ns == "style") { + node.style[name] = value; + } else if (ns !== undefined) { + node.setAttributeNS(ns, name, value); + } else { + node.setAttribute(name, value); + } if ((name === "value", self)) { node.value = value; @@ -94,6 +101,7 @@ RemoveAttribute(self, edit) { const name = edit.field; const node = self.top(self.stack); + node.removeAttribute(name); if ((name === "value", self)) { diff --git a/packages/hooks/src/usestate.rs b/packages/hooks/src/usestate.rs index 901728dd0..397ed202d 100644 --- a/packages/hooks/src/usestate.rs +++ b/packages/hooks/src/usestate.rs @@ -2,13 +2,13 @@ use dioxus_core::prelude::Context; use std::{ cell::{Cell, Ref, RefCell, RefMut}, fmt::Display, - ops::{Deref, DerefMut}, + ops::{Deref, DerefMut, Not}, rc::Rc, }; /// Store state between component renders! /// -/// ## The "King" of state hooks +/// ## The "Pinnacle" of state hooks /// /// The Dioxus version of `useState` is the "king daddy" of state management. It allows you to ergonomically store and /// modify state between component renders. When the state is updated, the component will re-render. @@ -106,7 +106,7 @@ impl<'a, T: 'static> UseState<'a, T> { *self.inner.wip.borrow_mut() = Some(new_val); } - pub fn get(&self) -> &T { + pub fn get(&self) -> &'a T { &self.inner.current_val } @@ -129,6 +129,10 @@ impl<'a, T: 'static> UseState<'a, T> { wip: self.inner.wip.clone(), } } + + pub fn split_for_async(&'a self) -> (&'a Self, AsyncUseState) { + (self, self.for_async()) + } } impl<'a, T: 'static + ToOwned> UseState<'a, T> { @@ -147,15 +151,15 @@ impl<'a, T: 'static + ToOwned> UseState<'a, T> { } } -impl<'a, T: 'static> std::ops::Deref for UseState<'a, T> { +impl<'a, T> std::ops::Deref for UseState<'a, T> { type Target = T; fn deref(&self) -> &Self::Target { - &self.inner.current_val + self.get() } } -use std::ops::{Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Sub, SubAssign}; +use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign}; impl<'a, T: Copy + Add> Add for UseState<'a, T> { type Output = T; @@ -208,6 +212,18 @@ impl<'a, T: Copy + Div> DivAssign for UseState<'a, T> { self.set(self.inner.current_val.div(rhs)); } } +impl<'a, T: PartialEq> PartialEq for UseState<'a, T> { + fn eq(&self, other: &T) -> bool { + self.get() == other + } +} +impl<'a, O, T: Not + Copy> Not for UseState<'a, T> { + type Output = O; + + fn not(self) -> Self::Output { + !*self.get() + } +} // enable displaty for the handle impl<'a, T: 'static + Display> std::fmt::Display for UseState<'a, T> { @@ -233,4 +249,7 @@ impl AsyncUseState { slot.as_mut().unwrap() }) } + pub fn set(&mut self, val: T) { + *self.wip.borrow_mut() = Some(val); + } } diff --git a/packages/ssr/src/lib.rs b/packages/ssr/src/lib.rs index 68826ae54..dbae758e6 100644 --- a/packages/ssr/src/lib.rs +++ b/packages/ssr/src/lib.rs @@ -12,8 +12,8 @@ use dioxus_core::*; pub fn render_vnode(vnode: &VNode, string: &mut String) {} -pub fn render_vdom(vdom: &VirtualDom) -> String { - format!("{:}", TextRenderer::from_vdom(vdom)) +pub fn render_vdom(dom: &VirtualDom) -> String { + format!("{:}", TextRenderer::from_vdom(dom)) } pub fn render_vdom_scope(vdom: &VirtualDom, scope: ScopeId) -> Option { diff --git a/packages/web/Cargo.toml b/packages/web/Cargo.toml index be2584f4f..9de645a68 100644 --- a/packages/web/Cargo.toml +++ b/packages/web/Cargo.toml @@ -17,12 +17,10 @@ wasm-bindgen-futures = "0.4.20" wasm-logger = "0.2.0" log = "0.4.14" fxhash = "0.2.1" -pretty_env_logger = "0.4.0" console_error_panic_hook = "0.1.6" generational-arena = "0.2.8" wasm-bindgen-test = "0.3.21" -once_cell = "1.7.2" -# atoms = { path = "../atoms" } +once_cell = "1.8" async-channel = "1.6.1" anyhow = "1.0.41" slotmap = "1.0.3" @@ -61,6 +59,8 @@ features = [ "CompositionEvent", "DocumentType", "CharacterData", + "SvgElement", + "SvgAnimatedString", "HtmlOptionElement", ] diff --git a/packages/web/src/new.rs b/packages/web/src/new.rs index fb1a3e5e6..e1138206a 100644 --- a/packages/web/src/new.rs +++ b/packages/web/src/new.rs @@ -294,8 +294,20 @@ impl WebsysDom { fn set_attribute(&mut self, name: &str, value: &str, ns: Option<&str>) { if name == "class" { - if let Some(el) = self.stack.top().dyn_ref::() { - el.set_class_name(value); + match ns { + Some("http://www.w3.org/2000/svg") => { + // + if let Some(el) = self.stack.top().dyn_ref::() { + let r: web_sys::SvgAnimatedString = el.class_name(); + r.set_base_val(value); + // el.set_class_name(value); + } + } + _ => { + if let Some(el) = self.stack.top().dyn_ref::() { + el.set_class_name(value); + } + } } } else { if let Some(el) = self.stack.top().dyn_ref::() { diff --git a/wip.md b/wip.md deleted file mode 100644 index ca6077af7..000000000 --- a/wip.md +++ /dev/null @@ -1,19 +0,0 @@ - -6 oz. button mushrooms -6 oz. shiitake mushrooms -7 oz. smoked or firm tofu (half a 14-oz. package), drained, cut into 1"–2 pieces -1 medium onion, cut into a few large pieces -¼ cup plus 3 Tbsp. vegetable oil -5 garlic cloves, finely chopped -¼ cup gochujang (Korean hot pepper paste) -1 tsp. five-spice powder -½ tsp. ground cumin -3 Tbsp. Shaoxing wine (Chinese rice wine) or medium-dry sherry -2 Tbsp. vegetarian mushroom oyster sauce (such as Lee Kum Kee) -Kosher salt -2 Tbsp. vegetable oil -1 tsp. toasted sesame oil -6 English muffins, split -2 scallions, thinly sliced -½ cup coarsely chopped cilantro -1 Tbsp. toasted sesame seeds