use std::collections::HashMap;
use std::sync::Mutex;
use wasm_bindgen::JsCast as _;
use web_sys::Document;
use web_sys::Element;
use web_sys::Node;
use web_sys::Text;
use web_sys::window;
use super::XElement;
use super::XElementValue;
use super::template::XTemplate;
use crate::element::template::LiveElement;
use crate::key::XKey;
use crate::node::XNode;
use crate::node::XText;
use crate::prelude::OrElseLog as _;
use crate::prelude::XAttributeKind;
use crate::prelude::XAttributeName;
use crate::prelude::XAttributeValue;
use crate::prelude::diagnostics::error;
use crate::prelude::diagnostics::trace;
use crate::prelude::diagnostics::warn;
use crate::string::XString;
use crate::utils::Ptr;
pub fn merge(
template: &XTemplate,
new_nodes: &mut [XNode],
old_nodes: &mut [XNode],
element: &Element,
) {
trace! { new_count = new_nodes.len(), old_count = old_nodes.len(), "Children" };
let document: Document = window().or_throw("window").document().or_throw("document");
let mut old_elements_map = {
let mut old_elements_map = HashMap::new();
for old_node in old_nodes {
if let XNode::Element(old_element) = old_node {
old_elements_map.insert(old_element.key.to_owned(), old_element);
}
}
old_elements_map
};
let cur_nodes = {
let mut cur_nodes = vec![];
let cur_nodes_view = element.child_nodes();
for index in 0..cur_nodes_view.length() {
if let Some(cur_node) = cur_nodes_view.item(index) {
cur_nodes.push(cur_node);
}
}
cur_nodes
};
let mut cur_elements = {
let mut cur_elements = HashMap::new();
let mut index = 0;
for cur_node in &cur_nodes {
if let Some(cur_element) = cur_node.dyn_ref::<Element>() {
cur_elements.insert(XKey::of(template, index, cur_element), cur_element);
index += 1;
}
}
cur_elements
};
let mut cur_nodes = cur_nodes.iter();
let mut index = 0;
for new_node in new_nodes {
match new_node {
XNode::Element(new_element) => {
merge_element(
&document,
template,
element,
&mut old_elements_map,
&mut cur_nodes,
&mut cur_elements,
index,
new_element,
);
index += 1;
}
XNode::Text(new_text) => merge_text(&document, element, &mut cur_nodes, new_text),
}
}
detatch_remaining_nodes(element, &mut cur_nodes, None);
}
fn merge_element<'t>(
document: &Document,
template: &XTemplate,
element: &Element,
old_elements_map: &mut HashMap<XKey, &mut XElement>,
cur_nodes: &mut impl Iterator<Item = &'t Node>,
cur_elements: &mut HashMap<XKey, &'t Element>,
index: usize,
new_element: &mut XElement,
) {
let key = &new_element.key;
trace!("Merge element: index={index:?} key={key:?}",);
let mut new_element_zero;
let old_element = match old_elements_map.remove(key) {
Some(old_element) => {
trace!("Old element: Found");
old_element
}
None => {
trace!("Old element: Not found");
new_element_zero = new_element.zero();
&mut new_element_zero
}
};
if let Some(cur_element) = get_cur_element(
template,
element,
cur_nodes,
cur_elements,
index,
new_element,
) {
new_element.merge(
template,
old_element,
Ptr::new(Mutex::new(LiveElement::new(cur_element.to_owned()))),
);
return;
} else {
trace!("Cur element: Not found");
}
if let Some(cur_element) = create_new_element(document, template, element, new_element) {
trace!("Cur element: Created new");
new_element.merge(template, old_element, Ptr::new(Mutex::new(cur_element)));
return;
}
trace!("Cur element: Failed");
}
fn get_cur_element<'t>(
template: &XTemplate,
element: &Element,
cur_nodes: &mut impl Iterator<Item = &'t Node>,
cur_elements: &mut HashMap<XKey, &'t Element>,
index: usize,
new_element: &XElement,
) -> Option<&'t Element> {
let maybe_cur_node = cur_nodes.next();
let maybe_cur_element = maybe_cur_node.and_then(|cur_node| cur_node.dyn_ref::<Element>());
if let Some(cur_element) = maybe_cur_element
&& element_matches(template, index, new_element, cur_element)
{
trace!("Cur element: Found in order");
return Some(cur_element);
}
detatch_remaining_nodes(element, cur_nodes, maybe_cur_node);
let cur_element = cur_elements.remove(&new_element.key)?;
if let Err(error) = element.append_child(cur_element) {
warn!("Failed to append cur_element: {error:?}");
}
trace!("Cur element: Found out of order");
return Some(cur_element);
}
fn element_matches(
template: &XTemplate,
index: usize,
new_element: &XElement,
cur_element: &Element,
) -> bool {
if XKey::of(template, index, cur_element) != new_element.key {
return false;
}
if let XElementValue::Dynamic { .. } = &new_element.value {
return true;
}
let Some(new_tag_name) = new_element.tag_name.as_deref() else {
return true;
};
return cur_element.tag_name().to_lowercase().as_str() == new_tag_name;
}
fn create_new_element(
document: &Document,
template: &XTemplate,
element: &Element,
new_element: &XElement,
) -> Option<LiveElement> {
static XMLNS: XAttributeName = XAttributeName {
name: XString::Str("xmlns"),
kind: XAttributeKind::Attribute,
};
let Some(tag_name) = new_element.tag_name.as_deref() else {
error!("Failed to create new element with undefined tag name");
return None;
};
let html = if let XElementValue::Static { attributes, .. } = &new_element.value
&& let Some(xmlns) = attributes
.iter()
.find(|a| a.id.name == XMLNS)
.and_then(|xmlns| {
let XAttributeValue::Static(xmlns) = &xmlns.value else {
return None;
};
return Some(xmlns.as_str());
}) {
document.create_element_ns(Some(xmlns), tag_name)
} else {
document.create_element(tag_name)
};
let html = html
.inspect_err(|error| warn!("Create new element '{tag_name}' failed: {error:?}'"))
.ok()?;
#[cfg(feature = "diagnostics")]
set_tag_version_id(&html);
let cur_element = LiveElement::new(html);
if let XKey::Named(key) = &new_element.key {
let () = cur_element
.set_key_attribute(template, key)
.inspect_err(|error| warn!("Set element key failed: {error:?}'"))
.ok()?;
}
if let Err(error) = element.append_child(&cur_element.html) {
warn!("Failed to append cur_element: {error:?}");
return None;
}
return Some(cur_element);
}
#[cfg(feature = "diagnostics")]
fn set_tag_version_id(html: &Element) -> Option<()> {
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering::SeqCst;
static NEXT: AtomicUsize = AtomicUsize::new(0);
let () = html
.set_attribute("data-trz-tag-id", &NEXT.fetch_add(1, SeqCst).to_string())
.inspect_err(|error| warn!("Failed to set tag version id: {error:?}"))
.ok()?;
Some(())
}
fn merge_text<'t>(
document: &Document,
element: &Element,
cur_nodes: &mut impl Iterator<Item = &'t Node>,
XText(new_text): &XText,
) {
let new_text_data = new_text.as_str();
trace!("Merge text: {:?}", new_text_data);
if let Some(cur_text) = get_cur_text(element, cur_nodes) {
let cur_text_data = cur_text.data();
if cur_text_data.as_str() != new_text_data {
cur_text.set_data(new_text_data);
trace!("Cur text: Update from {cur_text_data:?} to {new_text_data:?}");
} else {
trace!("Cur text: Still {:?}", cur_text_data);
}
return;
}
let cur_text = document.create_text_node(new_text_data);
if let Err(error) = element.append_child(&cur_text) {
warn!("Failed to append cur_text: {error:?}");
}
trace!("Cur text: Created new");
}
fn get_cur_text<'t>(
element: &Element,
cur_nodes: &mut impl Iterator<Item = &'t Node>,
) -> Option<&'t Text> {
let maybe_cur_node = cur_nodes.next();
let maybe_cur_text = maybe_cur_node.and_then(|cur_node| cur_node.dyn_ref::<Text>());
if let Some(cur_text) = maybe_cur_text {
trace!("Cur text: Found");
return Some(cur_text);
}
trace!("Cur text: Not found");
detatch_remaining_nodes(element, cur_nodes, maybe_cur_node);
return None;
}
fn detatch_remaining_nodes<'t>(
element: &Element,
cur_nodes: &mut impl Iterator<Item = &'t Node>,
maybe_cur_node: Option<&'t Node>,
) {
if let Some(cur_node) = maybe_cur_node
&& let Err(error) = element.remove_child(cur_node)
{
warn!("Failed to remove cur_node: {error:?}");
}
for cur_node in cur_nodes {
if let Err(error) = element.remove_child(cur_node) {
warn!("Failed to remove cur_node: {error:?}");
}
}
}