feat: amazingly awesome error handling
This commit is contained in:
1 parent
05c909f320
commit
4a72b3140b
58 files changed
+248
-1219
No files matched your search
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user