webelements 0.1.1

A macro that transforms html to rust code
Documentation
pub mod element;

use std::{fmt::Display, ops::Deref};

use wasm_bindgen::{prelude::*, JsCast, JsValue};

pub use element::{elem, Element, WebElement, WebElementBuilder};
pub use we_derive::{we_builder, WebElement};
use web_sys::{KeyboardEvent, MessageEvent};

#[non_exhaustive]
#[derive(Debug)]
pub enum Error {
    JsError(JsValue),
    Cast(&'static str),
    Window,
    Document,
    Body,
    Value,
}

impl From<JsValue> for Error {
    fn from(from: JsValue) -> Self {
        Error::JsError(from)
    }
}

impl From<Error> for JsValue {
    fn from(e: Error) -> Self {
        e.as_jsvalue()
    }
}

impl Display for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Error::JsError(s) => {
                if let Some(s) = s.as_string() {
                    write!(f, "{}", s)
                } else {
                    Err(std::fmt::Error)
                }
            }
            Error::Cast(t) => writeln!(f, "unable to cast value to type `{}`", t),
            n => writeln!(f, "{:?}", n),
        }
    }
}

impl Error {
    pub fn as_jsvalue(&self) -> JsValue {
        if let Self::JsError(jsvalue) = self {
            jsvalue.clone()
        } else {
            JsValue::from_str(&self.to_string())
        }
    }

    pub fn js_str(value: impl AsRef<str>) -> Error {
        Error::JsError(JsValue::from_str(value.as_ref()))
    }
}

impl std::error::Error for Error {}

pub type Result<T> = std::result::Result<T, Error>;

pub struct Window {
    window: web_sys::Window,
}

impl Deref for Window {
    type Target = web_sys::Window;

    fn deref(&self) -> &Self::Target {
        &self.window
    }
}

impl Window {
    pub fn on_animation(&self, callback: impl FnMut() + 'static) -> Result<()> {
        let closure = Closure::wrap(Box::new(callback) as Box<dyn FnMut()>);
        self.request_animation_frame(closure.as_ref().unchecked_ref())
            .map_err(Error::JsError)?;
        closure.forget();
        Ok(())
    }
}

pub fn window() -> Result<Window> {
    Ok(Window {
        window: web_sys::window().ok_or(Error::Window)?,
    })
}

pub struct Document {
    document: web_sys::Document,
}

impl Document {
    pub fn on_key(&self, mut callback: impl FnMut(KeyboardEvent) + 'static) -> Result<()> {
        let closure =
            Closure::wrap(Box::new(move |e| callback(e)) as Box<dyn FnMut(KeyboardEvent)>);
        self.document
            .add_event_listener_with_callback("keydown", closure.as_ref().unchecked_ref())
            .map_err(Error::JsError)?;
        closure.forget();
        Ok(())
    }

    pub fn body(&self) -> Result<Element<crate::elem::Base>> {
        let element = self.document.body().ok_or(Error::Body)?;
        Ok(Element::from_element(element))
    }
}

impl Deref for Document {
    type Target = web_sys::Document;

    fn deref(&self) -> &Self::Target {
        &self.document
    }
}

pub fn document() -> Result<Document> {
    Ok(Document {
        document: window()?.document().ok_or(Error::Document)?,
    })
}

pub trait Loggable {
    fn log(self);
}

impl<T> Loggable for Result<T> {
    fn log(self) {
        if let Err(err) = self {
            log(format!("{}", err))
        }
    }
}

#[allow(unused_unsafe)]
pub fn log<S: AsRef<str>>(str: S) {
    unsafe {
        web_sys::console::log_1(&JsValue::from_str(str.as_ref()));
    }
}

#[derive(Debug, Clone)]
pub struct Worker {
    worker: web_sys::Worker,
}

impl Worker {
    pub fn new(ctor: impl AsRef<JsValue>) -> Result<Self> {
        let ctor = ctor
            .as_ref()
            .dyn_ref::<js_sys::Function>()
            .ok_or(Error::Value)?;
        let worker = ctor
            .call0(&JsValue::null())?
            .dyn_into::<web_sys::Worker>()?;
        Ok(Self { worker })
    }

    pub fn set_onmessage(&self, mut callback: impl FnMut(JsValue) + 'static) -> Result<()> {
        let closure = Closure::wrap(Box::new(move |event| {
            let event: MessageEvent = event;
            callback(event.data())
        }) as Box<dyn FnMut(web_sys::MessageEvent)>);
        self.worker
            .set_onmessage(Some(closure.into_js_value().unchecked_ref()));
        Ok(())
    }

    pub fn post_message(&self, value: impl AsRef<JsValue>) -> Result<()> {
        self.worker.post_message(value.as_ref())?;
        Ok(())
    }
}

#[derive(Debug, Clone)]
pub struct Scope {
    scope: web_sys::DedicatedWorkerGlobalScope,
}

impl Scope {
    pub fn new(scope: impl AsRef<JsValue>) -> Result<Self> {
        Ok(Self {
            scope: scope.as_ref().clone().dyn_into()?,
        })
    }
    pub fn set_onmessage(&self, mut callback: impl FnMut(JsValue) + 'static) -> Result<()> {
        let closure = Closure::wrap(Box::new(move |event| {
            let event: MessageEvent = event;
            callback(event.data());
        }) as Box<dyn FnMut(MessageEvent)>);
        self.scope.set_onmessage(Some(closure.into_js_value().unchecked_ref()));
        Ok(())
    }

    pub fn post_message(&self, message: JsValue) -> Result<()> {
        self.scope.post_message(&message)?;
        Ok(())
    }
}