mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-23 20:53:06 +00:00
Merge pull request #14 from jkelleyrtp/jk/none-handling
feat: allow return none
This commit is contained in:
commit
308414f5ef
19 changed files with 289 additions and 114 deletions
1
.vscode/spellright.dict
vendored
1
.vscode/spellright.dict
vendored
|
@ -64,3 +64,4 @@ Tokio
|
||||||
asynchronicity
|
asynchronicity
|
||||||
constified
|
constified
|
||||||
SegVec
|
SegVec
|
||||||
|
contentful
|
||||||
|
|
|
@ -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 |
|
| Custom elements | ✅ | ✅ | Define new element primitives |
|
||||||
| Suspense | ✅ | ✅ | schedule future render from future/promise |
|
| Suspense | ✅ | ✅ | schedule future render from future/promise |
|
||||||
| Integrated error handling | ✅ | ✅ | Gracefully handle errors with ? syntax |
|
| 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 |
|
| 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) |
|
| Runs natively | ✅ | ❓ | runs as a portable binary w/o a runtime (Node) |
|
||||||
| 1st class global state | ✅ | ❓ | redux/recoil/mobx on top of context |
|
| 1st class global state | ✅ | ❓ | redux/recoil/mobx on top of context |
|
||||||
| Subtree Memoization | ✅ | ❓ | skip diffing static element subtrees |
|
| Subtree Memoization | ✅ | ❓ | skip diffing static element subtrees |
|
||||||
| Compile-time correct | ✅ | ❓ | Throw errors on invalid template layouts |
|
| Compile-time correct | ✅ | ❓ | Throw errors on invalid template layouts |
|
||||||
| Heuristic Engine | 🛠 | ❓ | track component memory usage to minimize future allocations |
|
| Heuristic Engine | 🛠 | ❓ | track component memory usage to minimize future allocations |
|
||||||
| Fine-grained reactivity | 🛠 | ❓ | Skip diffing for fine-grain updates |
|
| 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.
|
- [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 |
|
| Feature | Dioxus | React | Notes for Dioxus |
|
||||||
| -------------------- | ------ | ----- | ------------------------------------ |
|
| -------------------- | ------ | ----- | ------------------------------------ |
|
||||||
| Portal | ❓ | ✅ | cast elements through tree |
|
| 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 |
|
| Code-splitting | 👀 | ✅ | Make bundle smaller/lazy |
|
||||||
| LiveView | 👀 | ❓ | Example for SSR + WASM apps |
|
| LiveView | 👀 | ❓ | Example for SSR + WASM apps |
|
||||||
|
|
||||||
|
|
85
examples/reference/errorhandling.rs
Normal file
85
examples/reference/errorhandling.rs
Normal file
|
@ -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<T, V> into an Option<T> 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<String> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
// this fetching function produces "nothing"
|
||||||
|
fn get_data_err() -> Result<String, &'static str> {
|
||||||
|
Result::Err("Failed!")
|
||||||
|
}
|
|
@ -12,7 +12,7 @@ description = "Core macro for Dioxus Virtual DOM"
|
||||||
proc-macro = true
|
proc-macro = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
once_cell = "1.7.2"
|
once_cell = "1.8"
|
||||||
proc-macro2 = { version = "1.0.6" }
|
proc-macro2 = { version = "1.0.6" }
|
||||||
quote = "1.0"
|
quote = "1.0"
|
||||||
syn = { version = "1.0.11", features = ["full", "extra-traits"] }
|
syn = { version = "1.0.11", features = ["full", "extra-traits"] }
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
use std::{cell::UnsafeCell, rc::Rc};
|
use std::{cell::UnsafeCell, rc::Rc};
|
||||||
|
|
||||||
use crate::innerlude::*;
|
use crate::innerlude::*;
|
||||||
use slotmap::{DefaultKey, SlotMap};
|
use slotmap::SlotMap;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SharedArena {
|
pub struct SharedArena {
|
||||||
pub components: Rc<UnsafeCell<ScopeMap>>,
|
pub components: Rc<UnsafeCell<ScopeMap>>,
|
||||||
}
|
}
|
||||||
pub type ScopeMap = SlotMap<DefaultKey, Scope>;
|
pub type ScopeMap = SlotMap<ScopeId, Scope>;
|
||||||
|
|
||||||
enum MutStatus {
|
enum MutStatus {
|
||||||
Immut,
|
Immut,
|
||||||
|
|
|
@ -77,13 +77,14 @@ impl ActiveFrame {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn next(&mut self) -> &mut BumpFrame {
|
pub fn old_frame_mut(&mut self) -> &mut BumpFrame {
|
||||||
*self.generation.borrow_mut() += 1;
|
match *self.generation.borrow() & 1 == 0 {
|
||||||
|
true => &mut self.frames[1],
|
||||||
if *self.generation.borrow() % 2 == 0 {
|
false => &mut self.frames[0],
|
||||||
&mut self.frames[0]
|
|
||||||
} else {
|
|
||||||
&mut self.frames[1]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn cycle_frame(&mut self) {
|
||||||
|
*self.generation.borrow_mut() += 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -115,6 +115,11 @@ impl<'src, P> Context<'src, P> {
|
||||||
todo!()
|
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.
|
/// 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.
|
/// 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<T: 'static>(&self, init: impl Fn() -> T) {
|
pub fn use_provide_context<T, F>(self, init: F) -> &'src Rc<T>
|
||||||
let mut cxs = self.scope.shared_contexts.borrow_mut();
|
where
|
||||||
|
T: 'static,
|
||||||
|
F: FnOnce() -> T,
|
||||||
|
{
|
||||||
let ty = TypeId::of::<T>();
|
let ty = TypeId::of::<T>();
|
||||||
|
let contains_key = self.scope.shared_contexts.borrow().contains_key(&ty);
|
||||||
|
|
||||||
let is_initialized = self.use_hook(
|
let is_initialized = self.use_hook(
|
||||||
|_| false,
|
|_| 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
|
// Do nothing, already initialized and already exists
|
||||||
(true, true) => {}
|
(true, true) => {}
|
||||||
|
|
||||||
// Needs to be initialized
|
// Needs to be initialized
|
||||||
(false, false) => {
|
(false, false) => {
|
||||||
log::debug!("Initializing context...");
|
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"),
|
_ => debug_assert!(false, "Cannot initialize two contexts of the same type"),
|
||||||
}
|
};
|
||||||
|
|
||||||
|
self.use_context::<T>()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// There are hooks going on here!
|
/// 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
|
/// Uses a context, storing the cached value around
|
||||||
pub fn try_use_context<T: 'static>(self) -> Result<&'src Rc<T>> {
|
///
|
||||||
|
/// 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<T: 'static>(self) -> Option<&'src Rc<T>> {
|
||||||
struct UseContextHook<C> {
|
struct UseContextHook<C> {
|
||||||
par: Option<Rc<C>>,
|
par: Option<Rc<C>>,
|
||||||
we: Option<Weak<C>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.use_hook(
|
self.use_hook(
|
||||||
move |_| UseContextHook {
|
move |_| {
|
||||||
par: None as Option<Rc<T>>,
|
|
||||||
we: None as Option<Weak<T>>,
|
|
||||||
},
|
|
||||||
move |hook| {
|
|
||||||
let mut scope = Some(self.scope);
|
let mut scope = Some(self.scope);
|
||||||
|
let mut par = None;
|
||||||
if let Some(we) = &hook.we {
|
|
||||||
if let Some(re) = we.upgrade() {
|
|
||||||
hook.par = Some(re);
|
|
||||||
return Ok(hook.par.as_ref().unwrap());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let ty = TypeId::of::<T>();
|
let ty = TypeId::of::<T>();
|
||||||
while let Some(inner) = scope {
|
while let Some(inner) = scope {
|
||||||
|
@ -261,35 +275,38 @@ Any function prefixed with "use" should not be called conditionally.
|
||||||
"Searching {:#?} for valid shared_context",
|
"Searching {:#?} for valid shared_context",
|
||||||
inner.our_arena_idx
|
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");
|
log::debug!("found matching cx");
|
||||||
let rc = shared_cx
|
let rc = shared_cx
|
||||||
.clone()
|
.clone()
|
||||||
.downcast::<T>()
|
.downcast::<T>()
|
||||||
.expect("Should not fail, already validated the type from the hashmap");
|
.expect("Should not fail, already validated the type from the hashmap");
|
||||||
|
|
||||||
hook.we = Some(Rc::downgrade(&rc));
|
par = Some(rc);
|
||||||
hook.par = Some(rc);
|
break;
|
||||||
return Ok(hook.par.as_ref().unwrap());
|
|
||||||
} else {
|
} else {
|
||||||
match inner.parent_idx {
|
match inner.parent_idx {
|
||||||
Some(parent_id) => {
|
Some(parent_id) => {
|
||||||
let parent = inner
|
scope = inner.arena_link.get(parent_id);
|
||||||
.arena_link
|
|
||||||
.get(parent_id)
|
|
||||||
.ok_or_else(|| Error::FatalInternal("Failed to find parent"))?;
|
|
||||||
|
|
||||||
scope = Some(parent);
|
|
||||||
}
|
}
|
||||||
None => return Err(Error::MissingSharedContext),
|
None => break,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//
|
||||||
Err(Error::MissingSharedContext)
|
UseContextHook { par }
|
||||||
},
|
},
|
||||||
|
move |hook| hook.par.as_ref(),
|
||||||
|_| {},
|
|_| {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,6 +50,7 @@
|
||||||
|
|
||||||
use crate::{arena::SharedArena, innerlude::*, tasks::TaskQueue};
|
use crate::{arena::SharedArena, innerlude::*, tasks::TaskQueue};
|
||||||
use fxhash::FxHashSet;
|
use fxhash::FxHashSet;
|
||||||
|
use smallvec::{smallvec, SmallVec};
|
||||||
|
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
|
|
||||||
|
@ -74,7 +75,7 @@ pub struct DiffMachine<'real, 'bump, Dom: RealDom<'bump>> {
|
||||||
pub edits: DomEditor<'real, 'bump>,
|
pub edits: DomEditor<'real, 'bump>,
|
||||||
pub components: &'bump SharedArena,
|
pub components: &'bump SharedArena,
|
||||||
pub task_queue: &'bump TaskQueue,
|
pub task_queue: &'bump TaskQueue,
|
||||||
pub cur_idx: ScopeId,
|
pub cur_idxs: SmallVec<[ScopeId; 5]>,
|
||||||
pub diffed: FxHashSet<ScopeId>,
|
pub diffed: FxHashSet<ScopeId>,
|
||||||
pub event_queue: EventQueue,
|
pub event_queue: EventQueue,
|
||||||
pub seen_nodes: FxHashSet<ScopeId>,
|
pub seen_nodes: FxHashSet<ScopeId>,
|
||||||
|
@ -96,7 +97,7 @@ where
|
||||||
edits: DomEditor::new(edits),
|
edits: DomEditor::new(edits),
|
||||||
components,
|
components,
|
||||||
dom,
|
dom,
|
||||||
cur_idx,
|
cur_idxs: smallvec![cur_idx],
|
||||||
event_queue,
|
event_queue,
|
||||||
task_queue,
|
task_queue,
|
||||||
diffed: FxHashSet::default(),
|
diffed: FxHashSet::default(),
|
||||||
|
@ -156,6 +157,7 @@ where
|
||||||
log::warn!("diffing components? {:#?}", new.user_fc);
|
log::warn!("diffing components? {:#?}", new.user_fc);
|
||||||
if old.user_fc == new.user_fc {
|
if old.user_fc == new.user_fc {
|
||||||
// Make sure we're dealing with the same component (by function pointer)
|
// 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
|
// Make sure the new component vnode is referencing the right scope id
|
||||||
let scope_id = old.ass_scope.get();
|
let scope_id = old.ass_scope.get();
|
||||||
|
@ -181,6 +183,7 @@ where
|
||||||
} else {
|
} else {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
self.cur_idxs.pop();
|
||||||
|
|
||||||
self.seen_nodes.insert(scope_id.unwrap());
|
self.seen_nodes.insert(scope_id.unwrap());
|
||||||
} else {
|
} else {
|
||||||
|
@ -399,10 +402,10 @@ where
|
||||||
log::debug!("Mounting a new component");
|
log::debug!("Mounting a new component");
|
||||||
let caller = vcomponent.caller.clone();
|
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
|
// Insert a new scope into our component list
|
||||||
let idx = self
|
let new_idx = self
|
||||||
.components
|
.components
|
||||||
.with(|components| {
|
.with(|components| {
|
||||||
components.insert_with_key(|new_idx| {
|
components.insert_with_key(|new_idx| {
|
||||||
|
@ -435,20 +438,22 @@ where
|
||||||
|
|
||||||
// TODO: abstract this unsafe into the arena abstraction
|
// TODO: abstract this unsafe into the arena abstraction
|
||||||
let inner: &'bump mut _ = unsafe { &mut *self.components.components.get() };
|
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
|
// 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
|
// Run the scope for one iteration to initialize it
|
||||||
new_component.run_scope().unwrap();
|
new_component.run_scope().unwrap();
|
||||||
|
|
||||||
// TODO: we need to delete (IE relcaim this node, otherwise the arena will grow infinitely)
|
// TODO: we need to delete (IE relcaim this node, otherwise the arena will grow infinitely)
|
||||||
let nextnode = new_component.next_frame();
|
let nextnode = new_component.next_frame();
|
||||||
|
self.cur_idxs.push(new_idx);
|
||||||
let meta = self.create(nextnode);
|
let meta = self.create(nextnode);
|
||||||
|
self.cur_idxs.pop();
|
||||||
|
|
||||||
// Finally, insert this node as a seen node.
|
// 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)
|
CreateMeta::new(vcomponent.is_static, meta.added_to_stack)
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,9 @@ pub enum Error {
|
||||||
#[error("Wrong Properties Type")]
|
#[error("Wrong Properties Type")]
|
||||||
WrongProps,
|
WrongProps,
|
||||||
|
|
||||||
|
#[error("The component failed to return VNodes")]
|
||||||
|
ComponentFailed,
|
||||||
|
|
||||||
#[error("Base scope has not been mounted yet")]
|
#[error("Base scope has not been mounted yet")]
|
||||||
NotMounted,
|
NotMounted,
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
//!
|
//!
|
||||||
|
|
||||||
pub use crate::innerlude::{
|
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,
|
NodeFactory, Properties, RealDom, RealDomNode, ScopeId, VNode, VNodeKind, VirtualDom,
|
||||||
VirtualEvent, FC,
|
VirtualEvent, FC,
|
||||||
};
|
};
|
||||||
|
|
|
@ -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<P>(
|
pub fn component<P>(
|
||||||
&self,
|
&self,
|
||||||
component: FC<P>,
|
component: FC<P>,
|
||||||
|
@ -508,6 +524,25 @@ impl IntoVNode<'_> for Option<()> {
|
||||||
cx.fragment_from_iter(None as Option<VNode>)
|
cx.fragment_from_iter(None as Option<VNode>)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl<'a> IntoVNode<'a> for Option<VNode<'a>> {
|
||||||
|
fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> {
|
||||||
|
match self {
|
||||||
|
Some(n) => n,
|
||||||
|
None => cx.fragment_from_iter(None as Option<VNode>),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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<'_> {
|
impl Debug for NodeFactory<'_> {
|
||||||
fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
|
|
@ -113,7 +113,8 @@ impl Scope {
|
||||||
// Remove all the outdated listeners
|
// Remove all the outdated listeners
|
||||||
|
|
||||||
// This is a very dangerous operation
|
// 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();
|
self.listeners.borrow_mut().clear();
|
||||||
|
|
||||||
|
@ -123,16 +124,19 @@ impl Scope {
|
||||||
// Cast the caller ptr from static to one with our own reference
|
// Cast the caller ptr from static to one with our own reference
|
||||||
let c3: &WrappedCaller = self.caller.as_ref();
|
let c3: &WrappedCaller = self.caller.as_ref();
|
||||||
|
|
||||||
self.frames.cur_frame_mut().head_node = unsafe { self.call_user_component(c3) };
|
match c3(self) {
|
||||||
|
None => {
|
||||||
Ok(())
|
// 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)
|
||||||
// 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> {
|
Some(new_head) => {
|
||||||
let new_head: Option<VNode<'a>> = caller(self);
|
// the user's component succeeded. We can safely cycle to the next frame
|
||||||
let new_head = new_head.unwrap_or(errored_fragment());
|
self.frames.old_frame_mut().head_node = unsafe { std::mem::transmute(new_head) };
|
||||||
std::mem::transmute(new_head)
|
self.frames.cycle_frame();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// A safe wrapper around calling listeners
|
// A safe wrapper around calling listeners
|
||||||
|
|
|
@ -30,7 +30,9 @@ use std::any::TypeId;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::pin::Pin;
|
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.
|
/// 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.
|
/// 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);
|
/// let dom = VirtualDom::new(Example);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn new_with_props<P: Properties + 'static>(root: FC<P>, root_props: P) -> Self {
|
pub fn new_with_props<P: Properties + 'static>(root: FC<P>, root_props: P) -> Self {
|
||||||
let components = SharedArena::new(SlotMap::new());
|
let components = SharedArena::new(SlotMap::<ScopeId, Scope>::with_key());
|
||||||
|
|
||||||
let root_props: Pin<Box<dyn Any>> = Box::pin(root_props);
|
let root_props: Pin<Box<dyn Any>> = Box::pin(root_props);
|
||||||
let props_ptr = root_props.as_ref().downcast_ref::<P>().unwrap() as *const P;
|
let props_ptr = root_props.as_ref().downcast_ref::<P>().unwrap() as *const P;
|
||||||
|
@ -225,11 +227,12 @@ impl VirtualDom {
|
||||||
);
|
);
|
||||||
|
|
||||||
let cur_component = self.components.get_mut(self.base_scope).unwrap();
|
let cur_component = self.components.get_mut(self.base_scope).unwrap();
|
||||||
cur_component.run_scope()?;
|
|
||||||
|
|
||||||
let meta = diff_machine.create(cur_component.next_frame());
|
// 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() {
|
||||||
diff_machine.edits.append_children(meta.added_to_stack);
|
let meta = diff_machine.create(cur_component.next_frame());
|
||||||
|
diff_machine.edits.append_children(meta.added_to_stack);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -370,10 +373,10 @@ impl VirtualDom {
|
||||||
// We are guaranteeed that this scope is unique because we are tracking which nodes have modified
|
// 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();
|
let cur_component = self.components.get_mut(update.idx).unwrap();
|
||||||
|
|
||||||
cur_component.run_scope()?;
|
if cur_component.run_scope().is_ok() {
|
||||||
|
let (old, new) = (cur_component.old_frame(), cur_component.next_frame());
|
||||||
let (old, new) = (cur_component.old_frame(), cur_component.next_frame());
|
diff_machine.diff_node(old, new);
|
||||||
diff_machine.diff_node(old, new);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,8 +51,7 @@
|
||||||
const node = self.stack.pop();
|
const node = self.stack.pop();
|
||||||
node.remove();
|
node.remove();
|
||||||
}
|
}
|
||||||
RemoveAllChildren(self, edit) {
|
RemoveAllChildren(self, edit) {}
|
||||||
}
|
|
||||||
CreateTextNode(self, edit) {
|
CreateTextNode(self, edit) {
|
||||||
self.stack.push(document.createTextNode(edit.text));
|
self.stack.push(document.createTextNode(edit.text));
|
||||||
}
|
}
|
||||||
|
@ -62,24 +61,32 @@
|
||||||
self.stack.push(document.createElement(tagName));
|
self.stack.push(document.createElement(tagName));
|
||||||
}
|
}
|
||||||
CreateElementNs(self, edit) {
|
CreateElementNs(self, edit) {
|
||||||
|
const tagName = edit.tag;
|
||||||
|
console.log(`creating namespaced element: `, edit);
|
||||||
self.stack.push(document.createElementNS(edit.ns, edit.tag));
|
self.stack.push(document.createElementNS(edit.ns, edit.tag));
|
||||||
}
|
}
|
||||||
CreatePlaceholder(self, edit) {
|
CreatePlaceholder(self, edit) {
|
||||||
const a = `self.stack.push(document.createElement("pre"))`;
|
const a = `self.stack.push(document.createElement("pre"))`;
|
||||||
self.stack.push(document.createComment("vroot"));
|
self.stack.push(document.createComment("vroot"));
|
||||||
}
|
}
|
||||||
NewEventListener(self, edit) {
|
NewEventListener(self, edit) {}
|
||||||
}
|
RemoveEventListener(self, edit) {}
|
||||||
RemoveEventListener(self, edit) {
|
|
||||||
}
|
|
||||||
SetText(self, edit) {
|
SetText(self, edit) {
|
||||||
self.top().textContent = edit.text;
|
self.top().textContent = edit.text;
|
||||||
}
|
}
|
||||||
SetAttribute(self, edit) {
|
SetAttribute(self, edit) {
|
||||||
const name = edit.field;
|
const name = edit.field;
|
||||||
const value = edit.value;
|
const value = edit.value;
|
||||||
|
const ns = edit.ns;
|
||||||
|
|
||||||
const node = self.top(self.stack);
|
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)) {
|
if ((name === "value", self)) {
|
||||||
node.value = value;
|
node.value = value;
|
||||||
|
@ -94,6 +101,7 @@
|
||||||
RemoveAttribute(self, edit) {
|
RemoveAttribute(self, edit) {
|
||||||
const name = edit.field;
|
const name = edit.field;
|
||||||
const node = self.top(self.stack);
|
const node = self.top(self.stack);
|
||||||
|
|
||||||
node.removeAttribute(name);
|
node.removeAttribute(name);
|
||||||
|
|
||||||
if ((name === "value", self)) {
|
if ((name === "value", self)) {
|
||||||
|
|
|
@ -2,13 +2,13 @@ use dioxus_core::prelude::Context;
|
||||||
use std::{
|
use std::{
|
||||||
cell::{Cell, Ref, RefCell, RefMut},
|
cell::{Cell, Ref, RefCell, RefMut},
|
||||||
fmt::Display,
|
fmt::Display,
|
||||||
ops::{Deref, DerefMut},
|
ops::{Deref, DerefMut, Not},
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Store state between component renders!
|
/// 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
|
/// 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.
|
/// 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);
|
*self.inner.wip.borrow_mut() = Some(new_val);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get(&self) -> &T {
|
pub fn get(&self) -> &'a T {
|
||||||
&self.inner.current_val
|
&self.inner.current_val
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,6 +129,10 @@ impl<'a, T: 'static> UseState<'a, T> {
|
||||||
wip: self.inner.wip.clone(),
|
wip: self.inner.wip.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn split_for_async(&'a self) -> (&'a Self, AsyncUseState<T>) {
|
||||||
|
(self, self.for_async())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T: 'static + ToOwned<Owned = T>> UseState<'a, T> {
|
impl<'a, T: 'static + ToOwned<Owned = T>> UseState<'a, T> {
|
||||||
|
@ -147,15 +151,15 @@ impl<'a, T: 'static + ToOwned<Owned = T>> 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;
|
type Target = T;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
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<T, Output = T>> Add<T> for UseState<'a, T> {
|
impl<'a, T: Copy + Add<T, Output = T>> Add<T> for UseState<'a, T> {
|
||||||
type Output = T;
|
type Output = T;
|
||||||
|
@ -208,6 +212,18 @@ impl<'a, T: Copy + Div<T, Output = T>> DivAssign<T> for UseState<'a, T> {
|
||||||
self.set(self.inner.current_val.div(rhs));
|
self.set(self.inner.current_val.div(rhs));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl<'a, T: PartialEq<T>> PartialEq<T> for UseState<'a, T> {
|
||||||
|
fn eq(&self, other: &T) -> bool {
|
||||||
|
self.get() == other
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a, O, T: Not<Output = O> + Copy> Not for UseState<'a, T> {
|
||||||
|
type Output = O;
|
||||||
|
|
||||||
|
fn not(self) -> Self::Output {
|
||||||
|
!*self.get()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// enable displaty for the handle
|
// enable displaty for the handle
|
||||||
impl<'a, T: 'static + Display> std::fmt::Display for UseState<'a, T> {
|
impl<'a, T: 'static + Display> std::fmt::Display for UseState<'a, T> {
|
||||||
|
@ -233,4 +249,7 @@ impl<T: ToOwned> AsyncUseState<T> {
|
||||||
slot.as_mut().unwrap()
|
slot.as_mut().unwrap()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
pub fn set(&mut self, val: T) {
|
||||||
|
*self.wip.borrow_mut() = Some(val);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,8 +12,8 @@ use dioxus_core::*;
|
||||||
|
|
||||||
pub fn render_vnode(vnode: &VNode, string: &mut String) {}
|
pub fn render_vnode(vnode: &VNode, string: &mut String) {}
|
||||||
|
|
||||||
pub fn render_vdom(vdom: &VirtualDom) -> String {
|
pub fn render_vdom(dom: &VirtualDom) -> String {
|
||||||
format!("{:}", TextRenderer::from_vdom(vdom))
|
format!("{:}", TextRenderer::from_vdom(dom))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_vdom_scope(vdom: &VirtualDom, scope: ScopeId) -> Option<String> {
|
pub fn render_vdom_scope(vdom: &VirtualDom, scope: ScopeId) -> Option<String> {
|
||||||
|
|
|
@ -17,12 +17,10 @@ wasm-bindgen-futures = "0.4.20"
|
||||||
wasm-logger = "0.2.0"
|
wasm-logger = "0.2.0"
|
||||||
log = "0.4.14"
|
log = "0.4.14"
|
||||||
fxhash = "0.2.1"
|
fxhash = "0.2.1"
|
||||||
pretty_env_logger = "0.4.0"
|
|
||||||
console_error_panic_hook = "0.1.6"
|
console_error_panic_hook = "0.1.6"
|
||||||
generational-arena = "0.2.8"
|
generational-arena = "0.2.8"
|
||||||
wasm-bindgen-test = "0.3.21"
|
wasm-bindgen-test = "0.3.21"
|
||||||
once_cell = "1.7.2"
|
once_cell = "1.8"
|
||||||
# atoms = { path = "../atoms" }
|
|
||||||
async-channel = "1.6.1"
|
async-channel = "1.6.1"
|
||||||
anyhow = "1.0.41"
|
anyhow = "1.0.41"
|
||||||
slotmap = "1.0.3"
|
slotmap = "1.0.3"
|
||||||
|
@ -61,6 +59,8 @@ features = [
|
||||||
"CompositionEvent",
|
"CompositionEvent",
|
||||||
"DocumentType",
|
"DocumentType",
|
||||||
"CharacterData",
|
"CharacterData",
|
||||||
|
"SvgElement",
|
||||||
|
"SvgAnimatedString",
|
||||||
"HtmlOptionElement",
|
"HtmlOptionElement",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -294,8 +294,20 @@ impl WebsysDom {
|
||||||
|
|
||||||
fn set_attribute(&mut self, name: &str, value: &str, ns: Option<&str>) {
|
fn set_attribute(&mut self, name: &str, value: &str, ns: Option<&str>) {
|
||||||
if name == "class" {
|
if name == "class" {
|
||||||
if let Some(el) = self.stack.top().dyn_ref::<Element>() {
|
match ns {
|
||||||
el.set_class_name(value);
|
Some("http://www.w3.org/2000/svg") => {
|
||||||
|
//
|
||||||
|
if let Some(el) = self.stack.top().dyn_ref::<web_sys::SvgElement>() {
|
||||||
|
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::<Element>() {
|
||||||
|
el.set_class_name(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if let Some(el) = self.stack.top().dyn_ref::<Element>() {
|
if let Some(el) = self.stack.top().dyn_ref::<Element>() {
|
||||||
|
|
19
wip.md
19
wip.md
|
@ -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
|
|
Loading…
Reference in a new issue