⎇ 58e72c3d0837a5f1515b274ccbd87be7755ee7e7
crates/modules/barra/barra-web/src/lib.rs
Historia
Quién tocó qué
crudo
refactor(monorepo): reorganización lógica + renames + SDDs + split CHANGELOG
1//! Barra-web — binding DOM de la taskbar. Re-exporta `Task` desde
2//! `barra-core` y delega el render-to-html al core. Aquí sólo viven el
3//! mount sobre un `<ul>`, el listener de click delegado y los lookups
4//! de posición (bounding rects) que son intrínsecos al DOM.
5//!
6//! Contrato HTML mínimo:
7//! ```html
8//! <ul id="my-tasks" class="taskbar-list" role="presentation"></ul>
9//! ```
10//!
11//! Convenciones de clase generadas:
12//! - `.taskbar-item` — cada cajita
13//! - `.taskbar-item.active` — la cajita visible/foreground
14//! - `.taskbar-item-dot` — punto decorativo
15//! - `data-task="<id>"` — identificador único usable por CSS para theming
16
17use std::cell::RefCell;
18use std::rc::Rc;
19
20pub use barra_core::Task;
21use barra_core::{render_html, sanitize_attr};
22use wasm_bindgen::prelude::*;
23use wasm_bindgen::JsCast;
24use web_sys::{Element, HtmlElement, MouseEvent};
25
26#[derive(Clone)]
27pub struct TaskList {
28 list: HtmlElement,
29 on_click: Rc<RefCell<Option<Box<dyn FnMut(&str, f64, f64)>>>>,
30}
31
32impl TaskList {
33 pub fn mount(list: HtmlElement) -> Result<Self, JsValue> {
34 let on_click: Rc<RefCell<Option<Box<dyn FnMut(&str, f64, f64)>>>> =
35 Rc::new(RefCell::new(None));
36 let on_click2 = on_click.clone();
37 let cb = Closure::<dyn FnMut(MouseEvent)>::new(move |e: MouseEvent| {
38 let Some(target) = e.target() else { return };
39 let Ok(target_el): Result<Element, _> = target.dyn_into() else { return };
40 let Ok(Some(item)) = target_el.closest(".taskbar-item") else { return };
41 let Some(id) = item.get_attribute("data-task") else { return };
42 let rect = item.get_bounding_client_rect();
43 let cx = rect.left() + rect.width() / 2.0;
44 let cy = rect.top() + rect.height() / 2.0;
45 if let Some(cb) = on_click2.borrow_mut().as_mut() {
46 cb(&id, cx, cy);
47 }
48 });
49 list.add_event_listener_with_callback("click", cb.as_ref().unchecked_ref())?;
50 cb.forget();
51 Ok(Self { list, on_click })
52 }
53
54 pub fn set_tasks(&self, tasks: &[Task]) {
55 self.list.set_inner_html(&render_html(tasks));
56 }
57
58 pub fn on_click<F: FnMut(&str, f64, f64) + 'static>(&self, cb: F) {
59 *self.on_click.borrow_mut() = Some(Box::new(cb));
60 }
61
62 pub fn task_center(&self, id: &str) -> Option<(f64, f64)> {
63 let sel = format!(".taskbar-item[data-task=\"{}\"]", sanitize_attr(id));
64 let el = self.list.query_selector(&sel).ok().flatten()?;
65 let rect = el.get_bounding_client_rect();
66 Some((rect.left() + rect.width() / 2.0, rect.top() + rect.height() / 2.0))
67 }
68
69 pub fn list_el(&self) -> &HtmlElement {
70 &self.list
71 }
72}