webelements/
lib.rs

1pub mod element;
2
3use std::{fmt::Display, ops::Deref};
4
5use wasm_bindgen::{prelude::*, JsCast, JsValue};
6
7pub use element::{elem, Element, WebElement, WebElementBuilder};
8pub use we_derive::{we_builder, WebElement};
9use web_sys::{KeyboardEvent, MessageEvent};
10
11#[non_exhaustive]
12#[derive(Debug)]
13pub enum Error {
14    JsError(JsValue),
15    Cast(&'static str),
16    Window,
17    Document,
18    Body,
19    Value,
20}
21
22impl From<JsValue> for Error {
23    fn from(from: JsValue) -> Self {
24        Error::JsError(from)
25    }
26}
27
28impl From<Error> for JsValue {
29    fn from(e: Error) -> Self {
30        e.as_jsvalue()
31    }
32}
33
34impl Display for Error {
35    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36        match self {
37            Error::JsError(s) => {
38                if let Some(s) = s.as_string() {
39                    write!(f, "{}", s)
40                } else {
41                    Err(std::fmt::Error)
42                }
43            }
44            Error::Cast(t) => writeln!(f, "unable to cast value to type `{}`", t),
45            n => writeln!(f, "{:?}", n),
46        }
47    }
48}
49
50impl Error {
51    pub fn as_jsvalue(&self) -> JsValue {
52        if let Self::JsError(jsvalue) = self {
53            jsvalue.clone()
54        } else {
55            JsValue::from_str(&self.to_string())
56        }
57    }
58
59    pub fn js_str(value: impl AsRef<str>) -> Error {
60        Error::JsError(JsValue::from_str(value.as_ref()))
61    }
62}
63
64impl std::error::Error for Error {}
65
66pub type Result<T> = std::result::Result<T, Error>;
67
68pub struct Window {
69    window: web_sys::Window,
70}
71
72impl Deref for Window {
73    type Target = web_sys::Window;
74
75    fn deref(&self) -> &Self::Target {
76        &self.window
77    }
78}
79
80impl Window {
81    pub fn on_animation(&self, callback: impl FnMut() + 'static) -> Result<()> {
82        let closure = Closure::wrap(Box::new(callback) as Box<dyn FnMut()>);
83        self.request_animation_frame(closure.as_ref().unchecked_ref())
84            .map_err(Error::JsError)?;
85        closure.forget();
86        Ok(())
87    }
88}
89
90pub fn window() -> Result<Window> {
91    Ok(Window {
92        window: web_sys::window().ok_or(Error::Window)?,
93    })
94}
95
96pub struct Document {
97    document: web_sys::Document,
98}
99
100impl Document {
101    pub fn on_key(&self, mut callback: impl FnMut(KeyboardEvent) + 'static) -> Result<()> {
102        let closure =
103            Closure::wrap(Box::new(move |e| callback(e)) as Box<dyn FnMut(KeyboardEvent)>);
104        self.document
105            .add_event_listener_with_callback("keydown", closure.as_ref().unchecked_ref())
106            .map_err(Error::JsError)?;
107        closure.forget();
108        Ok(())
109    }
110
111    pub fn body(&self) -> Result<Element<crate::elem::Base>> {
112        let element = self.document.body().ok_or(Error::Body)?;
113        Ok(Element::from_element(element))
114    }
115}
116
117impl Deref for Document {
118    type Target = web_sys::Document;
119
120    fn deref(&self) -> &Self::Target {
121        &self.document
122    }
123}
124
125pub fn document() -> Result<Document> {
126    Ok(Document {
127        document: window()?.document().ok_or(Error::Document)?,
128    })
129}
130
131pub trait Loggable {
132    fn log(self);
133}
134
135impl<T> Loggable for Result<T> {
136    fn log(self) {
137        if let Err(err) = self {
138            log(format!("{}", err))
139        }
140    }
141}
142
143#[allow(unused_unsafe)]
144pub fn log<S: AsRef<str>>(str: S) {
145    unsafe {
146        web_sys::console::log_1(&JsValue::from_str(str.as_ref()));
147    }
148}
149
150#[derive(Debug, Clone)]
151pub struct Worker {
152    worker: web_sys::Worker,
153}
154
155impl Worker {
156    pub fn new(ctor: impl AsRef<JsValue>) -> Result<Self> {
157        let ctor = ctor
158            .as_ref()
159            .dyn_ref::<js_sys::Function>()
160            .ok_or(Error::Value)?;
161        let worker = ctor
162            .call0(&JsValue::null())?
163            .dyn_into::<web_sys::Worker>()?;
164        Ok(Self { worker })
165    }
166
167    pub fn set_onmessage(&self, mut callback: impl FnMut(JsValue) + 'static) -> Result<()> {
168        let closure = Closure::wrap(Box::new(move |event| {
169            let event: MessageEvent = event;
170            callback(event.data())
171        }) as Box<dyn FnMut(web_sys::MessageEvent)>);
172        self.worker
173            .set_onmessage(Some(closure.into_js_value().unchecked_ref()));
174        Ok(())
175    }
176
177    pub fn post_message(&self, value: impl AsRef<JsValue>) -> Result<()> {
178        self.worker.post_message(value.as_ref())?;
179        Ok(())
180    }
181}
182
183#[derive(Debug, Clone)]
184pub struct Scope {
185    scope: web_sys::DedicatedWorkerGlobalScope,
186}
187
188impl Scope {
189    pub fn new(scope: impl AsRef<JsValue>) -> Result<Self> {
190        Ok(Self {
191            scope: scope.as_ref().clone().dyn_into()?,
192        })
193    }
194    pub fn set_onmessage(&self, mut callback: impl FnMut(JsValue) + 'static) -> Result<()> {
195        let closure = Closure::wrap(Box::new(move |event| {
196            let event: MessageEvent = event;
197            callback(event.data());
198        }) as Box<dyn FnMut(MessageEvent)>);
199        self.scope.set_onmessage(Some(closure.into_js_value().unchecked_ref()));
200        Ok(())
201    }
202
203    pub fn post_message(&self, message: JsValue) -> Result<()> {
204        self.scope.post_message(&message)?;
205        Ok(())
206    }
207}