feat: amazingly awesome error handling

This commit is contained in:
Jonathan Kelley committed 2021-07-18 12:39:32 -04:00
1 parent 05c909f320
commit 4a72b3140b
58 files changed
+248 -1219

No files matched your search

+3 -3
View File
@@ -52,7 +52,7 @@ pub fn html_template(s: TokenStream) -> TokenStream {
/// ```ignore
///
/// #[fc]
/// fn Example(cx: Context, name: &str) -> VNode {
/// fn Example(cx: Context, name: &str) -> DomTree {
/// cx.render(rsx! { h1 {"hello {name}"} })
/// }
/// ```
@@ -220,7 +220,7 @@ pub fn derive_typed_builder(input: proc_macro::TokenStream) -> proc_macro::Token
/// pub struct BallerProps {}
///
/// /// This component totally balls
/// pub fn Baller(cx: Context<()>) -> VNode {
/// pub fn Baller(cx: Context<()>) -> DomTree {
/// todo!()
/// }
/// }
@@ -231,7 +231,7 @@ pub fn derive_typed_builder(input: proc_macro::TokenStream) -> proc_macro::Token
/// }
///
/// /// This component is taller than most :)
/// pub fn Taller(cx: Context<TallerProps>) -> VNode {
/// pub fn Taller(cx: Context<TallerProps>) -> DomTree {
/// let b = true;
/// todo!()
/// }
+1 -1
View File
@@ -1,3 +1,3 @@
{
"rust-analyzer.inlayHints.enable": true
"rust-analyzer.inlayHints.enable": false
}
+21 -16
View File
@@ -1,20 +1,25 @@
fn main() {}
// use dioxus::*;
// use dioxus_core as dioxus;
// use dioxus_core::prelude::*;
use dioxus::*;
use dioxus_core as dioxus;
use dioxus_core::prelude::*;
// pub static Example: FC<()> = |cx| {
// let list = (0..10).map(|f| LazyNodes::new(move |f| todo!()));
pub static Example: FC<()> = |cx| {
let list = (0..10).map(|f| LazyNodes::new(move |f| todo!()));
// cx.render(LazyNodes::new(move |cx| {
// let bump = cx.bump();
// cx.raw_element("div")
// .children([
// cx.text(format_args!("hello")),
// cx.text(format_args!("hello")),
// cx.fragment_from_iter(list),
// ])
// .finish()
// }))
// };
cx.render(LazyNodes::new(move |cx| {
let bump = cx.bump();
cx.raw_element(
"div",
None,
&mut [],
&mut [],
cx.bump().alloc([
cx.text(format_args!("hello")),
cx.text(format_args!("hello")),
cx.fragment_from_iter(list),
]),
None,
)
}))
};
+2 -2
View File
@@ -20,7 +20,7 @@ struct ListItem {
age: u32,
}
fn app(cx: Context<AppProps>) -> VNode {
fn app(cx: Context<AppProps>) -> DomTree {
// let (val, set_val) = use_state_classic(cx, || 0);
cx.render(LazyNodes::new(move |_nodecx| {
@@ -56,7 +56,7 @@ struct ChildProps {
item_handler: Rc<dyn Fn(i32)>,
}
fn ChildItem<'a>(cx: Context<'a, ChildProps>) -> VNode {
fn ChildItem<'a>(cx: Context<'a, ChildProps>) -> DomTree {
cx.render(LazyNodes::new(move |__cx| todo!()))
}
@@ -0,0 +1,48 @@
fn main() {}
use dioxus_core::prelude::*;
fn App(cx: Context<()>) -> DomTree {
//
let vak = cx.use_suspense(
|| async {},
|c, res| {
//
c.render(LazyNodes::new(move |f| f.text(format_args!(""))))
},
);
let d1 = cx.render(LazyNodes::new(move |f| {
f.raw_element(
"div",
None,
&mut [],
&[],
f.bump().alloc([
f.fragment_from_iter(vak),
f.text(format_args!("")),
f.text(format_args!("")),
f.text(format_args!("")),
f.text(format_args!("")),
]),
None,
)
}));
cx.render(LazyNodes::new(move |f| {
f.raw_element(
"div",
None,
&mut [],
&[],
f.bump().alloc([
f.text(format_args!("")),
f.text(format_args!("")),
f.text(format_args!("")),
f.text(format_args!("")),
f.fragment_from_iter(d1),
]),
None,
)
}))
}
+4 -4
View File
@@ -57,7 +57,7 @@ fn main() {}
// }
// }
// fn component<'a>(cx: Context<'a, Props>) -> VNode<'a> {
// fn component<'a>(cx: Context<'a, Props>) -> DomTree<'a> {
// // Write asynchronous rendering code that immediately returns a "suspended" VNode
// // The concurrent API will then progress this component when the future finishes
// // You can suspend the entire component, or just parts of it
@@ -82,7 +82,7 @@ fn main() {}
// })
// }
// fn BuilderComp<'a>(cx: Context<'a, Props>) -> VNode<'a> {
// fn BuilderComp<'a>(cx: Context<'a, Props>) -> DomTree<'a> {
// // VNodes can be constructed via a builder or the html! macro
// // However, both of these are "lazy" - they need to be evaluated (aka, "viewed")
// // We can "view" them with Context for ultimate speed while inside components
@@ -96,7 +96,7 @@ fn main() {}
// }
// #[fc]
// fn EffcComp(cx: Context, name: &str) -> VNode {
// fn EffcComp(cx: Context, name: &str) -> DomTree {
// // VNodes can be constructed via a builder or the html! macro
// // However, both of these are "lazy" - they need to be evaluated (aka, "viewed")
// // We can "view" them with Context for ultimate speed while inside components
@@ -110,7 +110,7 @@ fn main() {}
// })
// }
// fn FullySuspended<'a>(cx: &'a Context<Props>) -> VNode<'a> {
// fn FullySuspended<'a>(cx: &'a Context<Props>) -> DomTree<'a> {
// cx.suspend(async {
// let i: i32 = 0;
+2 -2
View File
@@ -5,7 +5,7 @@
//! if the type suppports PartialEq. The Properties trait is used by the rsx! and html! macros to generate the type-safe builder
//! that ensures compile-time required and optional fields on cx.
use crate::innerlude::{Context, LazyNodes, VNode, FC};
use crate::innerlude::{Context, DomTree, LazyNodes, VNode, FC};
pub trait Properties: Sized {
type Builder;
@@ -51,6 +51,6 @@ pub fn fc_to_builder<T: Properties>(_: FC<T>) -> T::Builder {
/// Fragments are incredibly useful when necessary, but *do* add cost in the diffing phase.
/// Try to avoid nesting fragments if you can. Infinitely nested Fragments *will* cause diffing to crash.
#[allow(non_upper_case_globals, non_snake_case)]
pub fn Fragment<'a>(cx: Context<'a, ()>) -> VNode<'a> {
pub fn Fragment<'a>(cx: Context<'a, ()>) -> DomTree<'a> {
cx.render(LazyNodes::new(move |f| f.fragment_from_iter(cx.children())))
}
+28 -10
View File
@@ -133,13 +133,13 @@ impl<'src, P> Context<'src, P> {
pub fn render<F: FnOnce(NodeFactory<'src>) -> VNode<'src>>(
self,
lazy_nodes: LazyNodes<'src, F>,
) -> VNode<'src> {
) -> DomTree<'src> {
let scope_ref = self.scope;
let listener_id = &scope_ref.listener_idx;
lazy_nodes.into_vnode(NodeFactory {
Some(lazy_nodes.into_vnode(NodeFactory {
scope_ref,
listener_id,
})
}))
}
/// Store a value between renders
@@ -378,7 +378,7 @@ pub(crate) struct SuspenseHook {
pub callback: SuspendedCallback,
pub dom_node_id: Rc<Cell<RealDomNode>>,
}
type SuspendedCallback = Box<dyn for<'a> Fn(Context<'a, ()>) -> VNode<'a>>;
type SuspendedCallback = Box<dyn for<'a> Fn(SuspendedContext<'a>) -> DomTree<'a>>;
impl<'src, P> Context<'src, P> {
/// Asynchronously render new nodes once the given future has completed.
@@ -395,11 +395,11 @@ impl<'src, P> Context<'src, P> {
self,
task_initializer: impl FnOnce() -> Fut,
user_callback: Cb,
) -> VNode<'src>
) -> DomTree<'src>
where
Fut: Future<Output = Out> + 'static,
Out: 'static,
Cb: for<'a> Fn(Context<'a, ()>, &Out) -> VNode<'a> + 'static,
Cb: for<'a> Fn(SuspendedContext<'a>, &Out) -> DomTree<'a> + 'static,
{
self.use_hook(
move |hook_idx| {
@@ -410,7 +410,7 @@ impl<'src, P> Context<'src, P> {
let slot = value.clone();
let callback: SuspendedCallback = Box::new(move |ctx: Context<()>| {
let callback: SuspendedCallback = Box::new(move |ctx: SuspendedContext| {
let v: std::cell::Ref<Option<Box<dyn Any>>> = slot.as_ref().borrow();
match v.as_ref() {
Some(a) => {
@@ -420,13 +420,13 @@ impl<'src, P> Context<'src, P> {
}
None => {
//
VNode {
Some(VNode {
dom_id: RealDomNode::empty_cell(),
key: None,
kind: VNodeKind::Suspended {
node: domnode.clone(),
},
}
})
}
}
});
@@ -459,13 +459,31 @@ impl<'src, P> Context<'src, P> {
scope: &self.scope,
props: &(),
};
(&hook.callback)(cx)
let csx = SuspendedContext { inner: cx };
(&hook.callback)(csx)
},
|_| {},
)
}
}
pub struct SuspendedContext<'a> {
pub(crate) inner: Context<'a, ()>,
}
impl<'src> SuspendedContext<'src> {
pub fn render<F: FnOnce(NodeFactory<'src>) -> VNode<'src>>(
self,
lazy_nodes: LazyNodes<'src, F>,
) -> DomTree<'src> {
let scope_ref = self.inner.scope;
let listener_id = &scope_ref.listener_idx;
Some(lazy_nodes.into_vnode(NodeFactory {
scope_ref,
listener_id,
}))
}
}
#[derive(Clone, Copy)]
pub struct TaskHandle<'src> {
_p: PhantomData<&'src ()>,
+3 -3
View File
@@ -18,8 +18,7 @@ pub use crate::innerlude::{
pub mod prelude {
pub use crate::component::{fc_to_builder, Fragment, Properties};
pub use crate::context::Context;
pub use crate::innerlude::DioxusElement;
pub use crate::innerlude::{LazyNodes, NodeFactory, FC};
pub use crate::innerlude::{DioxusElement, DomTree, LazyNodes, NodeFactory, FC};
pub use crate::nodes::VNode;
pub use crate::VirtualDom;
pub use dioxus_core_macro::{format_args_f, html, rsx, Props};
@@ -43,7 +42,8 @@ pub(crate) mod innerlude {
pub use crate::util::*;
pub use crate::virtual_dom::*;
pub type FC<P> = fn(Context<P>) -> VNode;
pub type DomTree<'a> = Option<VNode<'a>>;
pub type FC<P> = fn(Context<P>) -> DomTree;
pub use dioxus_core_macro::{format_args_f, html, rsx};
}
+8 -5
View File
@@ -5,7 +5,7 @@
//! These VNodes should be *very* cheap and *very* fast to construct - building a full tree should be insanely quick.
use crate::{
events::VirtualEvent,
innerlude::{Context, Properties, RealDomNode, Scope, ScopeId, FC},
innerlude::{Context, DomTree, Properties, RealDomNode, Scope, ScopeId, FC},
};
use std::{
cell::Cell,
@@ -50,6 +50,7 @@ pub struct VText<'src> {
pub struct VFragment<'src> {
pub children: &'src [VNode<'src>],
pub is_static: bool,
pub is_error: bool,
}
pub trait DioxusElement {
@@ -114,7 +115,7 @@ pub struct Listener<'bump> {
pub struct VComponent<'src> {
pub ass_scope: Cell<Option<ScopeId>>,
pub(crate) caller: Rc<dyn Fn(&Scope) -> VNode>,
pub(crate) caller: Rc<dyn Fn(&Scope) -> DomTree>,
pub(crate) children: &'src [VNode<'src>],
@@ -321,9 +322,9 @@ impl<'a> NodeFactory<'a> {
pub fn create_component_caller<'g, P: 'g>(
component: FC<P>,
raw_props: *const (),
) -> Rc<dyn for<'r> Fn(&'r Scope) -> VNode<'r>> {
type Captured<'a> = Rc<dyn for<'r> Fn(&'r Scope) -> VNode<'r> + 'a>;
let caller: Captured = Rc::new(move |scp: &Scope| -> VNode {
) -> Rc<dyn for<'r> Fn(&'r Scope) -> DomTree<'r>> {
type Captured<'a> = Rc<dyn for<'r> Fn(&'r Scope) -> DomTree<'r> + 'a>;
let caller: Captured = Rc::new(move |scp: &Scope| -> DomTree {
// cast back into the right lifetime
let safe_props: &'_ P = unsafe { &*(raw_props as *const P) };
let cx: Context<P> = Context {
@@ -371,6 +372,7 @@ To help you identify where this error is coming from, we've generated a backtrac
kind: VNodeKind::Fragment(VFragment {
children: nodes.into_bump_slice(),
is_static: false,
is_error: false,
}),
}
}
@@ -426,6 +428,7 @@ impl<'a> IntoVNode<'a> for &VNode<'a> {
VNodeKind::Fragment(fragment) => VNodeKind::Fragment(VFragment {
children: fragment.children,
is_static: fragment.is_static,
is_error: false,
}),
VNodeKind::Component(component) => VNodeKind::Component(component),
+15 -2
View File
@@ -53,7 +53,7 @@ pub struct Scope {
type EventChannel = Rc<dyn Fn()>;
// The type of closure that wraps calling components
pub type WrappedCaller = dyn for<'b> Fn(&'b Scope) -> VNode<'b>;
pub type WrappedCaller = dyn for<'b> Fn(&'b Scope) -> DomTree<'b>;
// The type of task that gets sent to the task scheduler
pub type FiberTask = Pin<Box<dyn Future<Output = EventTrigger>>>;
@@ -130,7 +130,8 @@ impl Scope {
// 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: VNode<'a> = caller(self);
let new_head: Option<VNode<'a>> = caller(self);
let new_head = new_head.unwrap_or(errored_fragment());
std::mem::transmute(new_head)
}
@@ -205,3 +206,15 @@ impl Scope {
&self.frames.cur_frame().head_node
}
}
pub fn errored_fragment() -> VNode<'static> {
VNode {
dom_id: RealDomNode::empty_cell(),
key: None,
kind: VNodeKind::Fragment(VFragment {
children: &[],
is_static: false,
is_error: true,
}),
}
}
+4 -2
View File
@@ -101,7 +101,7 @@ impl VirtualDom {
///
/// // or directly from a fn
///
/// fn Example(cx: Context<()>) -> VNode {
/// fn Example(cx: Context<()>) -> DomTree {
/// cx.render(rsx!{ div{"hello world"} })
/// }
///
@@ -318,9 +318,11 @@ impl VirtualDom {
let hook = unsafe { scope.hooks.get_mut::<SuspenseHook>(*hook_idx) }.unwrap();
let cx = Context { scope, props: &() };
let scx = SuspendedContext { inner: cx };
// generate the new node!
let nodes: VNode<'s> = (&hook.callback)(cx);
let nodes: Option<VNode<'s>> = (&hook.callback)(scx);
let nodes = nodes.unwrap_or_else(|| errored_fragment());
let nodes = scope.cur_frame().bump.alloc(nodes);
// push the old node's root onto the stack
+1 -1
View File
@@ -21,7 +21,7 @@ fn main() {
console_error_panic_hook::set_once();
// Run the app
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App));
dioxus_web::launch(App, |c| c)
}
static App: FC<()> = |cx| {
+1 -1
View File
@@ -22,7 +22,7 @@ fn main() {
console_error_panic_hook::set_once();
// Run the app
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App));
dioxus_web::launch(App, |c| c)
}
pub static App: FC<()> = |cx| {
-690
View File
@@ -1,690 +0,0 @@
use std::{borrow::Borrow, convert::TryInto, default, fmt::Debug, sync::Arc};
use dioxus_core::{
events::{EventTrigger, VirtualEvent},
prelude::ScopeIdx,
};
use fxhash::FxHashMap;
use wasm_bindgen::{closure::Closure, JsCast};
use web_sys::{
window, Document, Element, Event, HtmlElement, HtmlInputElement, HtmlOptionElement, Node,
};
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
pub struct CacheId(u32);
#[derive(Clone)]
struct RootCallback(Arc<dyn Fn(EventTrigger)>);
impl std::fmt::Debug for RootCallback {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Ok(())
// a no-op for now
// todo!()
}
}
#[derive(Debug)]
pub(crate) struct PatchMachine {
pub(crate) stack: Stack,
pub(crate) known_roots: FxHashMap<u32, Node>,
pub(crate) root: Element,
pub(crate) temporaries: FxHashMap<u32, Node>,
pub(crate) document: Document,
pub(crate) events: EventDelegater,
pub(crate) current_known: Option<u32>,
// We need to make sure to add comments between text nodes
// We ensure that the text siblings are patched by preventing the browser from merging
// neighboring text nodes. Originally inspired by some of React's work from 2016.
// -> https://reactjs.org/blog/2016/04/07/react-v15.html#major-changes
// -> https://github.com/facebook/react/pull/5753
//
// `ptns` = Percy text node separator
// TODO
pub(crate) last_node_was_text: bool,
}
#[derive(Debug)]
pub(crate) struct EventDelegater {
root: Element,
// every callback gets a monotomically increasing callback ID
callback_id: usize,
// map of listener types to number of those listeners
listeners: FxHashMap<String, (usize, Closure<dyn FnMut(&Event)>)>,
// Map of callback_id to component index and listener id
callback_map: FxHashMap<usize, (usize, usize)>,
trigger: RootCallback,
}
impl EventDelegater {
pub fn new(root: Element, trigger: impl Fn(EventTrigger) + 'static) -> Self {
Self {
trigger: RootCallback(Arc::new(trigger)),
root,
callback_id: 0,
listeners: FxHashMap::default(),
callback_map: FxHashMap::default(),
}
}
pub fn add_listener(&mut self, event: &str, scope: ScopeIdx) {
if let Some(entry) = self.listeners.get_mut(event) {
entry.0 += 1;
} else {
let trigger = self.trigger.clone();
let handler = Closure::wrap(Box::new(move |event: &web_sys::Event| {
log::debug!("Handling event!");
let target = event
.target()
.expect("missing target")
.dyn_into::<Element>()
.expect("not a valid element");
let typ = event.type_();
let gi_id: Option<usize> = target
.get_attribute(&format!("dioxus-giid-{}", typ))
.and_then(|v| v.parse().ok());
let gi_gen: Option<u64> = target
.get_attribute(&format!("dioxus-gigen-{}", typ))
.and_then(|v| v.parse().ok());
let li_idx: Option<usize> = target
.get_attribute(&format!("dioxus-lidx-{}", typ))
.and_then(|v| v.parse().ok());
if let (Some(gi_id), Some(gi_gen), Some(li_idx)) = (gi_id, gi_gen, li_idx) {
// Call the trigger
log::debug!(
"decoded gi_id: {}, gi_gen: {}, li_idx: {}",
gi_id,
gi_gen,
li_idx
);
let triggered_scope = ScopeIdx::from_raw_parts(gi_id, gi_gen);
trigger.0.as_ref()(EventTrigger::new(
virtual_event_from_websys_event(event),
triggered_scope,
// scope,
li_idx,
));
}
}) as Box<dyn FnMut(&Event)>);
self.root
.add_event_listener_with_callback(event, (&handler).as_ref().unchecked_ref())
.unwrap();
// Increment the listeners
self.listeners.insert(event.into(), (1, handler));
}
}
}
// callback: RootCallback,
// callback: Option<Closure<dyn Fn(EventTrigger)>>,
#[derive(Debug, Default)]
pub struct Stack {
list: Vec<Node>,
}
impl Stack {
pub fn with_capacity(cap: usize) -> Self {
Stack {
list: Vec::with_capacity(cap),
}
}
pub fn push(&mut self, node: Node) {
// debug!("stack-push: {:?}", node);
self.list.push(node);
}
pub fn pop(&mut self) -> Node {
let res = self.list.pop().unwrap();
res
}
pub fn clear(&mut self) {
self.list.clear();
}
pub fn top(&self) -> &Node {
match self.list.last() {
Some(a) => a,
None => panic!("Called 'top' of an empty stack, make sure to push the root first"),
}
}
}
impl PatchMachine {
pub fn new(root: Element, event_callback: impl Fn(EventTrigger) + 'static) -> Self {
let document = window()
.expect("must have access to the window")
.document()
.expect("must have access to the Document");
// attach all listeners to the container element
let events = EventDelegater::new(root.clone(), event_callback);
Self {
current_known: None,
known_roots: Default::default(),
root,
events,
stack: Stack::with_capacity(20),
temporaries: Default::default(),
document,
last_node_was_text: false,
}
}
pub fn unmount(&mut self) {
self.stack.clear();
self.temporaries.clear();
}
pub fn start(&mut self) {
if let Some(child) = self.root.first_child() {
self.stack.push(child);
}
}
pub fn reset(&mut self) {
self.stack.clear();
self.temporaries.clear();
}
pub fn handle_edit(&mut self, edit: &Edit) {
match *edit {
// 0
Edit::SetText { text } => {
//
self.stack.top().set_text_content(Some(text))
}
// 1
Edit::RemoveSelfAndNextSiblings {} => {
let node = self.stack.pop();
let mut sibling = node.next_sibling();
while let Some(inner) = sibling {
let temp = inner.next_sibling();
if let Some(sibling) = inner.dyn_ref::<Element>() {
sibling.remove();
}
sibling = temp;
}
if let Some(node) = node.dyn_ref::<Element>() {
node.remove();
}
}
// 2
Edit::ReplaceWith => {
let new_node = self.stack.pop();
let old_node = self.stack.pop();
if old_node.has_type::<Element>() {
old_node
.dyn_ref::<Element>()
.unwrap()
.replace_with_with_node_1(&new_node)
.unwrap();
} else if old_node.has_type::<web_sys::CharacterData>() {
old_node
.dyn_ref::<web_sys::CharacterData>()
.unwrap()
.replace_with_with_node_1(&new_node)
.unwrap();
} else if old_node.has_type::<web_sys::DocumentType>() {
old_node
.dyn_ref::<web_sys::DocumentType>()
.unwrap()
.replace_with_with_node_1(&new_node)
.unwrap();
} else {
panic!("Cannot replace node: {:?}", old_node);
}
// poc to see if this is a valid solution
if let Some(id) = self.current_known {
// update mapping
self.known_roots.insert(id, new_node.clone());
self.current_known = None;
}
self.stack.push(new_node);
}
// 3
Edit::SetAttribute { name, value } => {
let node = self.stack.top();
if let Some(node) = node.dyn_ref::<web_sys::Element>() {
node.set_attribute(name, value).unwrap();
}
if let Some(node) = node.dyn_ref::<HtmlInputElement>() {
log::debug!("el is html input element");
// Some attributes are "volatile" and don't work through `setAttribute`.
if name == "value" {
node.set_value(value);
}
if name == "checked" {
node.set_checked(true);
}
}
if let Some(node) = node.dyn_ref::<HtmlOptionElement>() {
if name == "selected" {
node.set_selected(true);
}
}
}
// 4
Edit::RemoveAttribute { name } => {
let node = self.stack.top();
if let Some(node) = node.dyn_ref::<web_sys::Element>() {
node.remove_attribute(name).unwrap();
}
if let Some(node) = node.dyn_ref::<HtmlInputElement>() {
// Some attributes are "volatile" and don't work through `removeAttribute`.
if name == "value" {
node.set_value("");
}
if name == "checked" {
node.set_checked(false);
}
}
if let Some(node) = node.dyn_ref::<HtmlOptionElement>() {
if name == "selected" {
node.set_selected(true);
}
}
}
// 5
Edit::PushReverseChild { n } => {
let parent = self.stack.top();
let children = parent.child_nodes();
let child = children.get(children.length() - n - 1).unwrap();
self.stack.push(child);
}
// 6
Edit::PopPushChild { n } => {
self.stack.pop();
let parent = self.stack.top();
let children = parent.child_nodes();
let child = children.get(n).unwrap();
self.stack.push(child);
}
// 7
Edit::Pop => {
self.stack.pop();
}
// 8
Edit::AppendChild => {
let child = self.stack.pop();
self.stack.top().append_child(&child).unwrap();
}
// 9
Edit::CreateTextNode { text } => {
//
// We ensure that the text siblings are patched by preventing the browser from merging
// neighboring text nodes. Originally inspired by some of React's work from 2016.
// -> https://reactjs.org/blog/2016/04/07/react-v15.html#major-changes
// -> https://github.com/facebook/react/pull/5753
//
// `ptns` = Percy text node separator
// TODO
if self.last_node_was_text {
let n = self
.document
.create_comment("ptns")
.dyn_into::<Node>()
.unwrap();
}
self.stack.push(
self.document
.create_text_node(text)
.dyn_into::<Node>()
.unwrap(),
);
self.last_node_was_text = true;
}
// 10
Edit::CreateElement { tag_name } => {
self.last_node_was_text = false;
let el = self
.document
.create_element(tag_name)
.unwrap()
.dyn_into::<Node>()
.unwrap();
self.stack.push(el);
}
// 11
Edit::NewListener { event, id, scope } => {
// attach the correct attributes to the element
// these will be used by accessing the event's target
// This ensures we only ever have one handler attached to the root, but decide
// dynamically when we want to call a listener.
let el = self.stack.top();
let el = el
.dyn_ref::<Element>()
.expect(&format!("not an element: {:?}", el));
// el.add_event_listener_with_callback(
// event_type,
// self.callback.as_ref().unwrap().as_ref().unchecked_ref(),
// )
// .unwrap();
// debug!("adding attributes: {}, {}", a, b);
// let CbIdx {
// gi_id,
// gi_gen,
// listener_idx: lidx,
// } = idx;
let (gi_id, gi_gen) = (&scope).into_raw_parts();
el.set_attribute(&format!("dioxus-giid-{}", event), &gi_id.to_string())
.unwrap();
el.set_attribute(&format!("dioxus-gigen-{}", event), &gi_gen.to_string())
.unwrap();
el.set_attribute(&format!("dioxus-lidx-{}", event), &id.to_string())
.unwrap();
self.events.add_listener(event, scope);
}
// 12
Edit::UpdateListener { event, scope, id } => {
// update our internal mapping, and then modify the attribute
if let Some(el) = self.stack.top().dyn_ref::<Element>() {
// el.set_attribute(&format!("dioxus-a-{}", event_type), &a.to_string())
// .unwrap();
// el.set_attribute(&format!("dioxus-b-{}", event_type), &b.to_string())
// .unwrap();
}
}
// 13
Edit::RemoveListener { event: event_type } => {
if let Some(el) = self.stack.top().dyn_ref::<Element>() {
// el.remove_event_listener_with_callback(
// event_type,
// self.callback.as_ref().unwrap().as_ref().unchecked_ref(),
// )
// .unwrap();
}
}
// 14
Edit::CreateElementNs { tag_name, ns } => {
self.last_node_was_text = false;
let el = self
.document
.create_element_ns(Some(ns), tag_name)
.unwrap()
.dyn_into::<Node>()
.unwrap();
log::debug!("Made NS element {} {}", ns, tag_name);
self.stack.push(el);
}
// 15
Edit::SaveChildrenToTemporaries {
mut temp,
start,
end,
} => {
let parent = self.stack.top();
let children = parent.child_nodes();
for i in start..end {
self.temporaries.insert(temp, children.get(i).unwrap());
temp += 1;
}
}
// 16
Edit::PushChild { n } => {
let parent = self.stack.top();
// log::debug!("PushChild {:#?}", parent);
let child = parent.child_nodes().get(n).unwrap();
self.stack.push(child);
}
// 17
Edit::PushTemporary { temp } => {
let t = self.temporaries.get(&temp).unwrap().clone();
self.stack.push(t);
}
// 18
Edit::InsertBefore => {
let before = self.stack.pop();
let after = self.stack.pop();
after
.parent_node()
.unwrap()
.insert_before(&before, Some(&after))
.unwrap();
self.stack.push(before);
}
// 19
Edit::PopPushReverseChild { n } => {
self.stack.pop();
let parent = self.stack.top();
let children = parent.child_nodes();
let child = children.get(children.length() - n - 1).unwrap();
self.stack.push(child);
}
// 20
Edit::RemoveChild { n } => {
let parent = self.stack.top();
if let Some(child) = parent.child_nodes().get(n).unwrap().dyn_ref::<Element>() {
child.remove();
}
}
// 21
Edit::SetClass { class_name } => {
if let Some(el) = self.stack.top().dyn_ref::<Element>() {
el.set_class_name(class_name);
}
}
Edit::MakeKnown { node } => {
let domnode = self.stack.top();
self.known_roots.insert(node, domnode.clone());
}
Edit::TraverseToKnown { node } => {
let domnode = self
.known_roots
.get(&node)
.expect("Failed to pop know root");
self.current_known = Some(node);
self.stack.push(domnode.clone());
}
Edit::RemoveKnown => {}
}
}
}
fn virtual_event_from_websys_event(event: &web_sys::Event) -> VirtualEvent {
use dioxus_core::events::on::*;
match event.type_().as_str() {
"copy" | "cut" | "paste" => {
// let evt: web_sys::ClipboardEvent = event.clone().dyn_into().unwrap();
todo!()
}
"compositionend" | "compositionstart" | "compositionupdate" => {
let evt: web_sys::CompositionEvent = event.clone().dyn_into().unwrap();
todo!()
}
"keydown" | "keypress" | "keyup" => {
let evt: web_sys::KeyboardEvent = event.clone().dyn_into().unwrap();
todo!()
}
"focus" | "blur" => {
let evt: web_sys::FocusEvent = event.clone().dyn_into().unwrap();
todo!()
}
"change" => {
let evt: web_sys::Event = event.clone().dyn_into().expect("wrong error typ");
todo!()
// VirtualEvent::FormEvent(FormEvent {value:})
}
"input" | "invalid" | "reset" | "submit" => {
// is a special react events
let evt: web_sys::InputEvent = event.clone().dyn_into().expect("wrong event type");
let this: web_sys::EventTarget = evt.target().unwrap();
let value = (&this)
.dyn_ref()
.map(|input: &web_sys::HtmlInputElement| input.value())
.or_else(|| {
(&this)
.dyn_ref()
.map(|input: &web_sys::HtmlTextAreaElement| input.value())
})
.or_else(|| {
(&this)
.dyn_ref::<web_sys::HtmlElement>()
.unwrap()
.text_content()
})
.expect("only an InputElement or TextAreaElement or an element with contenteditable=true can have an oninput event listener");
// let p2 = evt.data_transfer();
// let value: Option<String> = (&evt).data();
// let value = val;
// let value = value.unwrap_or_default();
// let value = (&evt).data().expect("No data to unwrap");
// todo - this needs to be a "controlled" event
// these events won't carry the right data with them
todo!()
// VirtualEvent::FormEvent(FormEvent { value })
}
"click" | "contextmenu" | "doubleclick" | "drag" | "dragend" | "dragenter" | "dragexit"
| "dragleave" | "dragover" | "dragstart" | "drop" | "mousedown" | "mouseenter"
| "mouseleave" | "mousemove" | "mouseout" | "mouseover" | "mouseup" => {
let evt: web_sys::MouseEvent = event.clone().dyn_into().unwrap();
// MouseEvent(Box::new(RawMouseEvent {
// alt_key: evt.alt_key(),
// button: evt.button() as i32,
// buttons: evt.buttons() as i32,
// client_x: evt.client_x(),
// client_y: evt.client_y(),
// ctrl_key: evt.ctrl_key(),
// meta_key: evt.meta_key(),
// page_x: evt.page_x(),
// page_y: evt.page_y(),
// screen_x: evt.screen_x(),
// screen_y: evt.screen_y(),
// shift_key: evt.shift_key(),
// get_modifier_state: GetModifierKey(Box::new(|f| {
// // evt.get_modifier_state(f)
// todo!("This is not yet implemented properly, sorry :(");
// })),
// }))
todo!()
// VirtualEvent::MouseEvent()
}
"pointerdown" | "pointermove" | "pointerup" | "pointercancel" | "gotpointercapture"
| "lostpointercapture" | "pointerenter" | "pointerleave" | "pointerover" | "pointerout" => {
let evt: web_sys::PointerEvent = event.clone().dyn_into().unwrap();
todo!()
}
"select" => {
// let evt: web_sys::SelectionEvent = event.clone().dyn_into().unwrap();
// not required to construct anything special beyond standard event stuff
todo!()
}
"touchcancel" | "touchend" | "touchmove" | "touchstart" => {
let evt: web_sys::TouchEvent = event.clone().dyn_into().unwrap();
todo!()
}
"scroll" => {
// let evt: web_sys::UIEvent = event.clone().dyn_into().unwrap();
todo!()
}
"wheel" => {
let evt: web_sys::WheelEvent = event.clone().dyn_into().unwrap();
todo!()
}
"abort" | "canplay" | "canplaythrough" | "durationchange" | "emptied" | "encrypted"
| "ended" | "error" | "loadeddata" | "loadedmetadata" | "loadstart" | "pause" | "play"
| "playing" | "progress" | "ratechange" | "seeked" | "seeking" | "stalled" | "suspend"
| "timeupdate" | "volumechange" | "waiting" => {
// not required to construct anything special beyond standard event stuff
// let evt: web_sys::MediaEvent = event.clone().dyn_into().unwrap();
// let evt: web_sys::MediaEvent = event.clone().dyn_into().unwrap();
todo!()
}
"animationstart" | "animationend" | "animationiteration" => {
let evt: web_sys::AnimationEvent = event.clone().dyn_into().unwrap();
todo!()
}
"transitionend" => {
let evt: web_sys::TransitionEvent = event.clone().dyn_into().unwrap();
todo!()
}
"toggle" => {
// not required to construct anything special beyond standard event stuff (target)
// let evt: web_sys::ToggleEvent = event.clone().dyn_into().unwrap();
todo!()
}
_ => VirtualEvent::OtherEvent,
}
}
-211
View File
@@ -1,211 +0,0 @@
use std::{collections::HashMap, fmt, rc::Rc};
use web_sys::{self, Element, EventTarget, Node, Text};
use dioxus_core::prelude::{VElement, VNode, VText, VirtualNode};
use std::ops::Deref;
use std::sync::Mutex;
use wasm_bindgen::JsCast;
use wasm_bindgen::JsValue;
pub struct DomRenderer {}
// Used to uniquely identify elements that contain closures so that the DomUpdater can
// look them up by their unique id.
// When the DomUpdater sees that the element no longer exists it will drop all of it's
// Rc'd Closures for those events.
use lazy_static::lazy_static;
lazy_static! {
static ref ELEM_UNIQUE_ID: Mutex<u32> = Mutex::new(0);
}
fn create_unique_identifier() -> u32 {
let mut elem_unique_id = ELEM_UNIQUE_ID.lock().unwrap();
*elem_unique_id += 1;
*elem_unique_id
}
/// A node along with all of the closures that were created for that
/// node's events and all of it's child node's events.
pub struct CreatedNode<T> {
/// A `Node` or `Element` that was created from a `VirtualNode`
pub node: T,
/// A map of a node's unique identifier along with all of the Closures for that node.
///
/// The DomUpdater uses this to look up nodes and see if they're still in the page. If not
/// the reference that we maintain to their closure will be dropped, thus freeing the Closure's
/// memory.
pub closures: HashMap<u32, Vec<DynClosure>>,
}
impl<T> CreatedNode<T> {
pub fn without_closures<N: Into<T>>(node: N) -> Self {
CreatedNode {
node: node.into(),
closures: HashMap::with_capacity(0),
}
}
}
impl<T> Deref for CreatedNode<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.node
}
}
impl From<CreatedNode<Element>> for CreatedNode<Node> {
fn from(other: CreatedNode<Element>) -> CreatedNode<Node> {
CreatedNode {
node: other.node.into(),
closures: other.closures,
}
}
}
//----------------------------------
// Create nodes for the VNode types
// ---------------------------------
/// Return a `Text` element from a `VirtualNode`, typically right before adding it
/// into the DOM.
pub fn create_text_node(text_node: &VText) -> Text {
let document = web_sys::window().unwrap().document().unwrap();
document.create_text_node(&text_node.text)
}
/// Build a DOM element by recursively creating DOM nodes for this element and it's
/// children, it's children's children, etc.
pub fn create_element_node(velement: &VElement) -> CreatedNode<Element> {
let document = web_sys::window().unwrap().document().unwrap();
let element = if html_validation::is_svg_namespace(&velement.tag) {
document
.create_element_ns(Some("http://www.w3.org/2000/svg"), &velement.tag)
.unwrap()
} else {
document.create_element(&velement.tag).unwrap()
};
let mut closures = HashMap::new();
velement.attrs.iter().for_each(|(name, value)| {
if name == "unsafe_inner_html" {
element.set_inner_html(value);
return;
}
element
.set_attribute(name, value)
.expect("Set element attribute in create element");
});
todo!("Support events properly in web ");
// if velement.events.0.len() > 0 {
// let unique_id = create_unique_identifier();
// element
// .set_attribute("data-vdom-id".into(), &unique_id.to_string())
// .expect("Could not set attribute on element");
// closures.insert(unique_id, vec![]);
// velement.events.0.iter().for_each(|(onevent, callback)| {
// // onclick -> click
// let event = &onevent[2..];
// let current_elem: &EventTarget = element.dyn_ref().unwrap();
// current_elem
// .add_event_listener_with_callback(event, callback.as_ref().as_ref().unchecked_ref())
// .unwrap();
// closures
// .get_mut(&unique_id)
// .unwrap()
// .push(Rc::clone(callback));
// });
// }
let mut previous_node_was_text = false;
velement.children.iter().for_each(|child| {
match child {
VNode::Text(text_node) => {
let current_node = element.as_ref() as &web_sys::Node;
// We ensure that the text siblings are patched by preventing the browser from merging
// neighboring text nodes. Originally inspired by some of React's work from 2016.
// -> https://reactjs.org/blog/2016/04/07/react-v15.html#major-changes
// -> https://github.com/facebook/react/pull/5753
//
// `ptns` = Percy text node separator
if previous_node_was_text {
let separator = document.create_comment("ptns");
current_node
.append_child(separator.as_ref() as &web_sys::Node)
.unwrap();
}
current_node
.append_child(&create_text_node(&text_node))
// .append_child(&text_node.create_text_node())
.unwrap();
previous_node_was_text = true;
}
VNode::Element(element_node) => {
previous_node_was_text = false;
let child = create_element_node(&element_node);
// let child = element_node.create_element_node();
let child_elem: Element = child.node;
closures.extend(child.closures);
element.append_child(&child_elem).unwrap();
}
VNode::Component(component) => {
//
todo!("Support components in the web properly");
}
}
});
todo!("Support events properly in web ");
// if let Some(on_create_elem) = velement.events.0.get("on_create_elem") {
// let on_create_elem: &js_sys::Function = on_create_elem.as_ref().as_ref().unchecked_ref();
// on_create_elem
// .call1(&wasm_bindgen::JsValue::NULL, &element)
// .unwrap();
// }
CreatedNode {
node: element,
closures,
}
}
/// Box<dyn AsRef<JsValue>>> is our js_sys::Closure. Stored this way to allow us to store
/// any Closure regardless of the arguments.
pub type DynClosure = Rc<dyn AsRef<JsValue>>;
/// We need a custom implementation of fmt::Debug since JsValue doesn't
/// implement debug.
pub struct Events(pub HashMap<String, DynClosure>);
impl PartialEq for Events {
// TODO: What should happen here..? And why?
fn eq(&self, _rhs: &Self) -> bool {
true
}
}
impl fmt::Debug for Events {
// Print out all of the event names for this VirtualNode
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let events: String = self.0.keys().map(|key| " ".to_string() + key).collect();
write!(f, "{}", events)
}
}
@@ -1,160 +0,0 @@
//! A collection of functions that are useful for unit testing your html! views.
use crate::VirtualNode;
impl VirtualNode {
/// Get a vector of all of the VirtualNode children / grandchildren / etc of
/// your virtual_node that have a label that matches your filter.
///
/// # Examples
///
/// ```rust,ignore
/// # #[macro_use] extern crate virtual_dom_rs; fn main() {
///
/// let component = html! {<div>
/// <span label="hello",> {"Hi!"} </span>
/// <em label="world",> {"There!!"} </em>
/// <em label="hello",></em>
/// </div> };
///
/// let hello_nodes = component.filter_label(|label| {
/// label.contains("hello")
/// });
///
/// assert_eq!(hello_nodes.len(), 2);
/// }
/// ```
pub fn filter_label<'a, F>(&'a self, filter: F) -> Vec<&'a VirtualNode>
where
F: Fn(&str) -> bool,
{
// Get descendants recursively
let mut descendants: Vec<&'a VirtualNode> = vec![];
match self {
VirtualNode::Text(_) => { /* nothing to do */ }
VirtualNode::Element(element_node) => {
for child in element_node.children.iter() {
get_descendants(&mut descendants, child);
}
}
}
// Filter descendants
descendants
.into_iter()
.filter(|vn: &&'a VirtualNode| match vn {
VirtualNode::Text(_) => false,
VirtualNode::Element(element_node) => match element_node.attrs.get("label") {
Some(label) => filter(label),
None => false,
},
})
.collect()
}
/// Get a vector of all of the descendants of this VirtualNode
/// that have the provided `filter`.
///
/// # Examples
///
/// ```rust,ignore
/// # #[macro_use] extern crate virtual_dom_rs; fn main() {
///
/// let component = html! {<div>
/// <span label="hello",> {"Hi!"} </span>
/// <em label="world",> {"There!!"} </em>
/// <em label="hello",></em>
/// </div> };
///
/// let hello_nodes = component.filter_label_equals("hello");
///
/// assert_eq!(hello_nodes.len(), 2);
/// }
/// ```
pub fn filter_label_equals<'a>(&'a self, label: &str) -> Vec<&'a VirtualNode> {
self.filter_label(|node_label| node_label == label)
}
}
fn get_descendants<'a>(descendants: &mut Vec<&'a VirtualNode>, node: &'a VirtualNode) {
descendants.push(node);
match node {
VirtualNode::Text(_) => { /* nothing to do */ }
VirtualNode::Element(element_node) => {
for child in element_node.children.iter() {
get_descendants(descendants, child);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::VElement;
use std::collections::HashMap;
// TODO: Move this test somewhere that we can use the `html!` macro
// #[test]
// fn filter_label() {
// let html = html! {
// // Should not pick up labels on the root node
// <div label="hello0",>
// // This node gets picked up
// <span label="hello1",>
// </span>
// // This node gets picked up
// <em label="hello2",>
// { "hello there :)!" }
// </em>
// <div label="world",></div>
// </div>
// };
//
// let hello_nodes = html.filter_label(|label| label.contains("hello"));
//
// assert_eq!(
// hello_nodes.len(),
// 2,
// "2 elements with label containing 'hello'"
// );
// }
#[test]
fn label_equals() {
let span = VirtualNode::element("span");
let mut attrs = HashMap::new();
attrs.insert("label".to_string(), "hello".to_string());
let mut em = VElement::new("em");
em.attrs = attrs;
let mut html = VElement::new("div");
html.children.push(span);
html.children.push(em.into());
let html_node = VirtualNode::Element(html);
let hello_nodes = html_node.filter_label_equals("hello");
assert_eq!(hello_nodes.len(), 1);
}
}
use dioxus_core::prelude::*;
use dioxus_web::WebsysRenderer;
fn main() {
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example));
}
fn Component(cx: Context, props: ()) -> VNode {
let user_data = use_sql_query(&cx, USER_DATA_QUERY);
cx.render(rsx! {
h1 { "Hello, {username}"}
button {
"Delete user"
onclick: move |_| user_data.delete()
}
})
}