1use std::rc::Rc;
12
13use wasm_bindgen::prelude::*;
14
15use crate::elements::{InnerElement, InnerElements};
16use crate::hooks::{signal_updater_callback, signal_updater_callback_full};
17use crate::prelude::*;
18
19const ERROR_MSG: &str = r#"
20The `BrowserRenderer` failed to render an element properly.
21
22This is an internal error and should be fixed.
23Please open an issue at `https://gitlab.com/TobiasBinnewies/rustolio/-/issues`.
24"#;
25
26#[wasm_bindgen(raw_module = "./rustolio-web/utils/main.js")]
27extern "C" {
28 #[wasm_bindgen(catch)]
29 fn apply_tw_classes(
30 elem: &web_sys::Element,
31 classes: &str,
32 ) -> Result<(), wasm_bindgen::JsValue>;
33}
34
35#[wasm_bindgen]
36impl Elements {
37 #[wasm_bindgen]
38 pub fn render(&self, parent: &Node) {
39 BrowserRenderer::render_elements_rec(self, parent);
40 }
41}
42
43#[derive(Clone, Default)]
44pub struct BrowserRenderer {}
45
46impl BrowserRenderer {
47 pub fn new() -> Self {
48 Self {}
49 }
50
51 pub fn run<F>(self, element: F) -> crate::Result<()>
52 where
53 F: Fn() -> Element + 'static,
54 {
55 GlobalWrapper::register_globals();
56
57 let root = document()?
58 .get_element_by_id("root")
59 .expect("No element with id: `root`");
60 let app = Self::render_dynamic(Rc::new(element));
61 root.append_child(&app)?;
62 Ok(())
64 }
65
66 pub fn run_result<F>(self, element: F) -> crate::Result<()>
67 where
68 F: Fn() -> crate::Result<Element> + 'static,
69 {
70 Self::run(self, crate::error::convert_fn_0("BrowserRenderer", element))
71 }
72
73 fn render_elements_rec(elements: &Elements, parent_node: &Node) {
74 match &elements.inner {
75 InnerElements::Element(e) => {
76 let node = Self::render(e.clone());
77 parent_node.append_child(&node).expect(ERROR_MSG);
78 }
79 InnerElements::Dynamic(d) => {
80 let node = Self::render_dynamic(d.clone());
81 parent_node.append_child(&node).expect(ERROR_MSG);
82 }
83 InnerElements::Elements(i) => {
84 for elements in i.iter() {
85 Self::render_elements_rec(elements, parent_node);
86 }
87 }
88 InnerElements::Overlay(o) => {
89 Self::render_overlay(o.clone());
90 }
91 }
92 }
93
94 fn render_dynamic(dynamic: Rc<dyn Fn() -> Element + 'static>) -> Node {
95 signal_updater_callback_full(
96 |current: &Node| {
97 current.is_connected()
99 && current
100 .parent_node()
101 .map(|p| p.is_connected())
102 .unwrap_or(false)
103 },
104 move |current| {
105 let new = Self::render(Rc::new(dynamic()));
106 if let Some(current) = current {
107 current
108 .parent_node()
109 .expect(ERROR_MSG)
110 .replace_child(&new, current)
111 .expect(ERROR_MSG);
112 }
113 new
114 },
115 )
116 }
117
118 fn render_overlay(overlay: Rc<dyn Fn() -> Element + 'static>) {
119 let overlay = signal_updater_callback_full(
120 |_: &Node| true,
121 move |current| {
122 let new = Self::render(Rc::new(overlay()));
123 if let Some(current) = current {
124 current
125 .parent_node()
126 .expect(ERROR_MSG)
127 .replace_child(&new, current)
128 .expect(ERROR_MSG);
129 }
130 new
131 },
132 );
133 body()
134 .expect(ERROR_MSG)
135 .append_child(&overlay)
136 .expect(ERROR_MSG);
137 }
138
139 pub fn render(elem: Rc<Element>) -> Node {
140 fn set_class(element: &web_sys::Element, elem: Rc<Element>) {
141 match elem.attributes.class_dynamic {
142 true => {
143 let c = element.clone();
144 let c1 = element.clone();
145 signal_updater_callback(
146 move || c1.is_connected(),
147 move || {
148 apply_tw_classes(&c, &super::build_multi_attrs(&elem.attributes.class))
149 .expect(ERROR_MSG);
150 },
151 );
152 }
153 false => {
154 if !elem.attributes.class.is_empty() {
155 apply_tw_classes(
156 element,
157 &super::build_multi_attrs(&elem.attributes.class),
158 )
159 .expect(ERROR_MSG);
160 }
161 }
162 };
163 }
164 fn set_style(element: &web_sys::Element, elem: Rc<Element>) {
165 match elem.attributes.style_dynamic {
166 true => {
167 let c = element.clone();
168 let c1 = element.clone();
169 signal_updater_callback(
170 move || c1.is_connected(),
171 move || {
172 let v = super::build_multi_attrs(&elem.attributes.style);
173 c.set_attribute("style", v.as_str()).expect(ERROR_MSG);
174 },
175 )
176 }
177 false => {
178 if !elem.attributes.style.is_empty() {
179 element
180 .set_attribute(
181 "style",
182 &super::build_multi_attrs(&elem.attributes.style),
183 )
184 .expect(ERROR_MSG)
185 }
186 }
187 };
188 }
189 fn set_value(element: &web_sys::Element, attr: &html::AttributeValue) {
190 let value = match attr {
191 html::AttributeValue::Dynamic(f) => {
192 let f = f.clone();
193 let e1 = element.clone();
194 let e2 = element.clone();
195 signal_updater_callback(
196 move || e1.is_connected(),
197 move || {
198 let value = f();
199 e2.unchecked_ref::<web_sys::HtmlInputElement>()
200 .set_value(value.str_value())
201 },
202 );
203 return;
204 }
205 _ => attr.str_value(),
206 };
207 element
208 .unchecked_ref::<web_sys::HtmlInputElement>()
209 .set_value(value);
210 }
211 fn set_attr(
212 element: &web_sys::Element,
213 attr: &html::AttributeValue,
214 set_fn: impl Fn(&web_sys::Element, bool) + 'static,
215 ) {
216 if let html::AttributeValue::Dynamic(f) = attr {
217 let f = f.clone();
218 let e1 = element.clone();
219 let e2 = element.clone();
220 signal_updater_callback(
221 move || e1.is_connected(),
222 move || set_fn(&e2, f().is_true()),
223 );
224 return;
225 }
226 set_fn(element, attr.is_true());
227 }
228 let set_checked = |element, attr| {
229 set_attr(element, attr, |element, checked| {
230 element
231 .unchecked_ref::<web_sys::HtmlInputElement>()
232 .set_checked(checked);
233 })
234 };
235 let set_selected = |element, attr| {
236 set_attr(element, attr, |element, selected| {
237 element
238 .unchecked_ref::<web_sys::HtmlOptionElement>()
239 .set_selected(selected);
240 })
241 };
242 fn set_attrs(element: &web_sys::Element, attributes: &[html::Attribute]) {
243 for attr in attributes {
244 let html::Attribute { name, value } = attr;
245 match value {
246 html::AttributeValue::False => element.remove_attribute(name).expect(ERROR_MSG),
247 html::AttributeValue::True => element.set_attribute(name, "").expect(ERROR_MSG),
248
249 html::AttributeValue::Static(value) => {
250 element.set_attribute(name, value).expect(ERROR_MSG)
251 }
252
253 html::AttributeValue::Dynamic(f) => {
254 let name = *name;
255 let f = f.clone();
256 let c = element.clone();
257 let c1 = element.clone();
258 signal_updater_callback(
259 move || c1.is_connected(),
260 move || match f() {
261 html::AttributeValue::Dynamic(_) => unreachable!(
262 "A dynamic `AttributeValue` cannot return another dynamic `AttributeValue`"
263 ),
264 html::AttributeValue::False => {
265 c.remove_attribute(name).expect(ERROR_MSG)
266 }
267 html::AttributeValue::True => {
268 c.set_attribute(name, "").expect(ERROR_MSG)
269 }
270 html::AttributeValue::Static(value) => {
271 c.set_attribute(name, &value).expect(ERROR_MSG)
272 }
273 },
274 );
275 }
276 }
277 }
278 }
279
280 let document = document().expect(ERROR_MSG);
281
282 let node: Node = match &elem.inner {
283 InnerElement::HtmlTag {
284 tag,
285 children,
286 namespace,
287 } => {
288 let element = match namespace.is_some() {
290 true => document
291 .create_element_ns(*namespace, tag)
292 .expect(ERROR_MSG),
293 false => document.create_element(tag).expect(ERROR_MSG),
294 };
295
296 set_class(&element, elem.clone());
298
299 set_style(&element, elem.clone());
301
302 set_checked(&element, &elem.attributes.checked);
304 set_selected(&element, &elem.attributes.selected);
305
306 set_attrs(&element, &elem.attributes.attributes);
310
311 Self::render_elements_rec(children, &element);
313
314 set_value(&element, &elem.attributes.value);
318
319 element.into()
320 }
321 InnerElement::Raw {
322 raw,
323 supported_type,
324 } => {
325 let parser = web_sys::DomParser::new().expect(ERROR_MSG);
326 let doc = parser
327 .parse_from_string(raw, *supported_type)
328 .expect(ERROR_MSG);
329 let element = doc.document_element().expect(ERROR_MSG);
330 set_class(&element, elem.clone());
331 set_style(&element, elem.clone());
332 set_checked(&element, &elem.attributes.checked);
333 set_selected(&element, &elem.attributes.selected);
334 set_value(&element, &elem.attributes.value);
335 set_attrs(&element, &elem.attributes.attributes);
336
337 element.into()
338 }
339 InnerElement::Text(text) => document.create_text_node(text).into(),
340 InnerElement::None => {
341 document.create_text_node("").into()
344 }
345 };
346
347 for lstr in &elem.attributes.listener {
349 node.add_event_listener(lstr).expect(ERROR_MSG);
350 }
351
352 node
353 }
354}