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}