terrazzo 0.2.8

The Terrazzo library to build dynamic web pages in Rust
Documentation
use std::any::type_name;
use std::sync::Arc;
use std::sync::Mutex;

use autoclone::autoclone;
use terrazzo_client::prelude::OrElseLog as _;
use terrazzo_client::prelude::UiThreadSafe;
use wasm_bindgen::JsCast;
use wasm_bindgen::JsValue;
use web_sys::Element;

pub struct ElementCapture<T: AsRef<JsValue>>(Arc<Mutex<Option<UiThreadSafe<T>>>>);

impl<T: AsRef<JsValue> + Clone + JsCast + 'static> ElementCapture<T> {
    #[autoclone]
    pub fn capture(&self) -> impl Fn(&Element) + 'static {
        let this = self.clone();
        let on_drop = scopeguard::guard((), move |()| {
            autoclone!(this);
            *this.lock() = None;
        });
        move |element| {
            let _ = &on_drop;
            let element: &T = element
                .dyn_ref::<T>()
                .or_else_throw(|()| format!("'{element:?}' is not an '{}'", type_name::<T>()));
            this.try_set(element.clone())
                .or_throw("Element already set");
        }
    }
}

impl<T: AsRef<JsValue>> ElementCapture<T> {
    pub fn try_with<R>(&self, f: impl FnOnce(&T) -> R) -> Option<R> {
        self.lock().as_ref().map(|value| f(value))
    }

    pub fn with<R>(&self, f: impl FnOnce(&T) -> R) -> R {
        self.try_with(f).or_throw("Element was not set")
    }

    pub fn get(&self) -> T
    where
        T: Clone,
    {
        self.try_with(|element| element.clone())
            .or_throw("Element was not set")
    }

    fn try_set(&self, element: T) -> Result<(), ()> {
        let mut lock = self.lock();
        if lock.is_some() {
            return Err(());
        }
        *lock = Some(element.into());
        Ok(())
    }

    fn lock(&self) -> std::sync::MutexGuard<'_, Option<UiThreadSafe<T>>> {
        self.0.lock().or_throw("lock ElementCapture")
    }
}

impl<T: AsRef<JsValue>> std::fmt::Debug for ElementCapture<T> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let maybe_element = self.try_with(|value| format!("{:?}", value.as_ref()));
        f.debug_tuple("ElementCapture")
            .field(&maybe_element)
            .finish()
    }
}

impl<T: AsRef<JsValue>> Clone for ElementCapture<T> {
    fn clone(&self) -> Self {
        Self(self.0.clone())
    }
}

impl<T: AsRef<JsValue>> Default for ElementCapture<T> {
    fn default() -> Self {
        Self(Default::default())
    }
}