1use async_channel::Sender;
3use bloom_core::{render_loop, Element, ObjectModel};
4use bloom_html::HtmlNode;
5use std::{
6 any::{Any, TypeId},
7 cell::RefCell,
8 collections::HashMap,
9 fmt::Debug,
10 sync::Arc,
11};
12use wasm_bindgen_futures::spawn_local;
13use web_sys::{console, js_sys::Array, window, Node};
14
15use crate::{dom::Dom, interned_str::interned, spawner::WasmSpawner};
16
17#[derive(Default)]
18struct PartialRenderingContext {
19 context: Arc<HashMap<TypeId, Arc<dyn Any + Send + Sync>>>,
20 subscribers: Vec<Sender<()>>,
21}
22
23impl Drop for PartialRenderingContext {
24 fn drop(&mut self) {
25 for subscriber in self.subscribers.drain(..) {
26 subscriber.close();
27 }
28 }
29}
30
31thread_local! {
32 static CONTEXT: RefCell<HashMap<u64, PartialRenderingContext>> = RefCell::new(HashMap::new());
33}
34
35struct PartialDom(Dom, u64);
36
37impl PartialDom {
38 fn hydrate_from(
39 context_id: u64,
40 root: Arc<HtmlNode>,
41 dom_node: Node,
42 start_index: i32,
43 ) -> Self {
44 let mut inner = Dom::hydrate();
45 inner.register(&root, dom_node);
46 inner.set_hydration_index(root, start_index.unsigned_abs());
47 Self(inner, context_id)
48 }
49}
50
51impl ObjectModel for PartialDom {
52 type Node = HtmlNode;
53
54 fn create(
55 &mut self,
56 node: &Arc<Self::Node>,
57 parent: &Arc<Self::Node>,
58 sibling: &Option<Arc<Self::Node>>,
59 ) {
60 self.0.create(node, parent, sibling)
61 }
62
63 fn update(&mut self, node: &Arc<Self::Node>, next: &Arc<Self::Node>) {
64 self.0.update(node, next)
65 }
66
67 fn remove(&mut self, node: &Arc<Self::Node>, parent: &Arc<Self::Node>) {
68 self.0.remove(node, parent)
69 }
70
71 fn finalize(&mut self) -> impl futures_util::Future<Output = ()> + Send {
72 self.0.finalize()
73 }
74
75 fn subscribe(&mut self, signal: Sender<()>) {
76 CONTEXT.with(|context| {
77 let mut context = context.borrow_mut();
78 let context = context
79 .entry(self.1)
80 .or_insert_with(|| PartialRenderingContext::default());
81 context.subscribers.push(signal);
82 });
83 }
84
85 fn get_context(&mut self) -> Arc<HashMap<TypeId, Arc<dyn Any + Send + Sync>>> {
86 CONTEXT.with(|context| {
87 let context = context.borrow();
88 let context = context.get(&self.1).unwrap();
89 Arc::clone(&context.context)
90 })
91 }
92}
93
94pub fn hydrate_partial<E>(partial_id: String, element: Element<HtmlNode, E>)
95where
96 E: Send + 'static + Debug,
97{
98 spawn_local(async {
99 let first_node = if let Some(first_node) = window()
100 .expect("Failed to get Window")
101 .document()
102 .expect("Failed to get Document")
103 .query_selector(&format!("[data-bloom-partial='{}']", partial_id))
104 .expect("Failed to query selector for partial")
105 {
106 first_node
107 } else {
108 console::warn_2(&"Failed to find Partial Element".into(), &partial_id.into());
109 return;
110 };
111
112 let root_dom_node = first_node
113 .parent_element()
114 .expect("Failed to get Parent for Partial Hydration");
115
116 let root: Arc<HtmlNode> = Arc::new(
117 HtmlNode::element(interned(root_dom_node.tag_name().to_lowercase()))
118 .build()
119 .into(),
120 );
121 let start_index = Array::from(&root_dom_node.child_nodes()).index_of(&first_node, 0);
122 let context_id = u64::from_str_radix(
123 &first_node
124 .get_attribute("data-bloom-ctx")
125 .expect("Failed to get attribute"),
126 16,
127 )
128 .expect("Failed to parse context id");
129
130 let dom =
131 PartialDom::hydrate_from(context_id, root.clone(), root_dom_node.into(), start_index);
132
133 if let Err(error) = render_loop(root, element, WasmSpawner, dom).await {
134 let msg = format!("Render loop error: {:?}", error);
135 console::error_1(&msg.into());
136 }
137 })
138}