terrazzo-client 0.2.8

A simple macro to clone variables before passing them into a `move` closure or async block.
Documentation
use std::cell::LazyCell;

use web_sys::CssStyleDeclaration;
use web_sys::Element;

use self::diagnostics::debug;
use self::diagnostics::trace;
use self::diagnostics::trace_span;
use self::diagnostics::warn;
use super::XATTRIBUTE;
use super::XAttribute;
use super::builder::AttributeValueDiff;
use super::diff_store::AttributeValueDiffStore;
use super::id::XAttributeId;
use super::name::XAttributeKind;
use super::name::XAttributeName;
use super::template::XAttributeTemplate;
use super::template::XDynamicAttribute;
use super::value::XAttributeValue;
use crate::debug_correlation_id::DebugCorrelationId;
use crate::element::template::LiveElement;
use crate::prelude::diagnostics;
use crate::signal::depth::Depth;
use crate::signal::reactive_closure::reactive_closure_builder::Consumers;
use crate::string::XString;

impl XAttribute {
    pub fn merge(
        &mut self,
        depth: Depth,
        element: &LiveElement,
        backend: &mut impl AttributeValueDiffStore,
        old_attribute_value: Option<XAttributeValue>,
    ) {
        let _span = trace_span!("MergeAttr", attribute = %self.id.name.name).entered();
        let new_attribute = self;
        match &new_attribute.value {
            XAttributeValue::Null => {
                merge_static_atttribute(backend, &new_attribute.id, None, old_attribute_value);
            }
            XAttributeValue::Static(new_attribute_value) => {
                merge_static_atttribute(
                    backend,
                    &new_attribute.id,
                    Some(new_attribute_value),
                    old_attribute_value,
                );
            }
            XAttributeValue::Dynamic(XDynamicAttribute(new_attribute_value)) => {
                new_attribute.value = merge_dynamic_atttribute(
                    depth,
                    element,
                    backend,
                    &new_attribute.id,
                    new_attribute_value,
                    old_attribute_value,
                );
            }
            XAttributeValue::Generated { .. } => {
                warn!("Illegal {XATTRIBUTE} state");
                unreachable!()
            }
        }
    }
}

fn merge_static_atttribute(
    backend: &mut impl AttributeValueDiffStore,
    attribute_id: &XAttributeId,
    new_value: Option<&XString>,
    old_value: Option<XAttributeValue>,
) {
    if let Some((XAttributeValue::Static(old_attribute_value), new_value)) =
        old_value.as_ref().zip(new_value)
        && new_value == old_attribute_value
    {
        trace!("Attribute '{attribute_id}' is still '{new_value}'");
        backend.set(attribute_id, AttributeValueDiff::Same(new_value.to_owned()));
    } else {
        drop(old_value);
        trace!("Attribute '{attribute_id}' is set to '{new_value:?}'");
        backend.set(
            attribute_id,
            if let Some(new_value) = new_value {
                AttributeValueDiff::Value(new_value.to_owned())
            } else {
                AttributeValueDiff::Null
            },
        );
    }
}

fn merge_dynamic_atttribute(
    depth: Depth,
    element: &LiveElement,
    backend: &mut impl AttributeValueDiffStore,
    attribute_id: &XAttributeId,
    new_attribute_value: &dyn Fn(XAttributeTemplate) -> Consumers,
    old_attribute_value: Option<XAttributeValue>,
) -> XAttributeValue {
    trace!("Dynamic Attribute '{attribute_id}' is initialized");
    backend.set(attribute_id, AttributeValueDiff::Undefined);
    let new_template = if let Some(XAttributeValue::Generated {
        template: old_template,
        consumers: old_consumers,
    }) = old_attribute_value
    {
        trace!("Reuse exising attribute template {attribute_id}");
        drop(old_consumers);
        old_template
    } else {
        trace!("Create a new attribute template {attribute_id}");
        XAttributeTemplate::new(
            element.clone(),
            attribute_id.clone(),
            DebugCorrelationId::new(|| format!("attribute_template:{attribute_id}")),
            depth.next(),
        )
    };
    XAttributeValue::Generated {
        template: new_template.clone(),
        consumers: new_attribute_value(new_template),
    }
}

pub fn set_attribute(
    element: &Element,
    css_style: &LazyCell<CssStyleDeclaration, impl FnOnce() -> CssStyleDeclaration>,
    attribute_name: &XAttributeName,
    value: Option<&str>,
) {
    let XAttributeName { name, kind } = attribute_name;
    if let Some(value) = value {
        match kind {
            XAttributeKind::Attribute => match element.set_attribute(name, value) {
                Ok(()) => debug!("Set attribute '{name}' to '{value}'"),
                Err(error) => warn!("Set attribute '{name}' to '{value}' failed: {error:?}"),
            },
            XAttributeKind::Style => match css_style.set_property(name, value) {
                Ok(()) => debug!("Set style {name}: {value}"),
                Err(error) => warn!("Set style {name}: {value} failed: {error:?}"),
            },
        }
    } else {
        match kind {
            XAttributeKind::Attribute => match element.remove_attribute(name) {
                Ok(()) => debug!("Removed attribute {name}"),
                Err(error) => warn!("Removed attribute {name} failed: {error:?}"),
            },
            XAttributeKind::Style => match css_style.remove_property(name) {
                Ok(old_value) => debug!("Removed style {name}: {old_value}"),
                Err(error) => warn!("Removed style {name} failed: {error:?}"),
            },
        }
        return;
    }
}