Skip to content
This repository was archived by the owner on Jul 5, 2025. It is now read-only.

Commit 4a7441c

Browse files
committed
wip: text block encoder
1 parent bf4319e commit 4a7441c

File tree

8 files changed

+290
-63
lines changed

8 files changed

+290
-63
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,9 @@ console_error_panic_hook = "0.1.7"
109109
[workspace.dependencies.web-sys]
110110
version = "0.3.76"
111111
features = [
112+
"Text",
113+
"Node",
114+
"NodeList",
112115
# Blob
113116
'Blob',
114117
'BlobEvent',

crates/sweet_core/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ serde = ["dep:serde", "dep:bincode"]
1818
flume = ["dep:flume"]
1919

2020
[dependencies]
21+
sweet_utils.workspace = true
2122
thiserror.workspace = true
2223
anyhow.workspace = true
2324
flume = { workspace = true, optional = true }
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
use crate::prelude::*;
2+
use wasm_bindgen::JsCast;
3+
use web_sys::window;
4+
use web_sys::Document;
5+
use web_sys::Element;
6+
use web_sys::HtmlDivElement;
7+
use web_sys::HtmlElement;
8+
9+
/// A hydrator for working with the dom
10+
pub struct DomHydrator {
11+
constants: HtmlConstants,
12+
// cache document reference
13+
document: Document,
14+
/// sparse set element array, cached for fast reference
15+
elements: Vec<Option<Element>>,
16+
}
17+
18+
impl Default for DomHydrator {
19+
fn default() -> Self {
20+
Self {
21+
constants: Default::default(),
22+
document: window().unwrap().document().unwrap(),
23+
elements: Default::default(),
24+
}
25+
}
26+
}
27+
28+
impl DomHydrator {
29+
/// we've found a html node with a matching id
30+
#[allow(unused)]
31+
fn apply_rsx(
32+
&self,
33+
el: Element,
34+
rsx: RsxNode,
35+
cx: &RsxContext,
36+
) -> Result<(), HydrationError> {
37+
Ok(())
38+
}
39+
40+
/// try to get cached element or find it in the dom.
41+
/// This also uncollapses the child text nodes
42+
fn get_or_find_element(
43+
&mut self,
44+
rsx_id: usize,
45+
) -> Result<Element, HydrationError> {
46+
if let Some(Some(el)) = self.elements.get(rsx_id) {
47+
return Ok(el.clone());
48+
}
49+
50+
let query =
51+
format!("[{}='{}']", self.constants.id_attribute_key, rsx_id);
52+
53+
if let Some(el) = self.document.query_selector(&query).unwrap() {
54+
self.elements.resize(rsx_id + 1, None);
55+
self.elements[rsx_id] = Some(el.clone());
56+
self.uncollapse_children(&el, rsx_id)?;
57+
Ok(el)
58+
} else {
59+
Err(HydrationError::InvalidContext(format!(
60+
"Could not find node with id: {}",
61+
rsx_id
62+
)))
63+
}
64+
}
65+
66+
fn uncollapse_children(
67+
&self,
68+
el: &Element,
69+
rsx_id: usize,
70+
) -> Result<(), HydrationError> {
71+
if let Some(encoded) =
72+
el.get_attribute(self.constants.block_attribute_key)
73+
{
74+
let positions =
75+
TextBlockEncoder::decode(&encoded).map_err(|err| {
76+
HydrationError::InvalidContext(format!(
77+
"Could not decode block attribute on element: {}\n{:?}",
78+
rsx_id, err
79+
))
80+
})?;
81+
82+
let indices = TextBlockPosition::into_split_positions(positions);
83+
84+
let children = el.child_nodes();
85+
86+
for (child_index, positions) in indices.into_iter().enumerate() {
87+
let child =
88+
children.item(child_index as u32).ok_or_else(|| {
89+
HydrationError::InvalidContext(format!(
90+
"Could not find child at index: {}",
91+
child_index
92+
))
93+
})?;
94+
let child: web_sys::Text = child.dyn_into().map_err(|_| {
95+
HydrationError::InvalidContext(format!(
96+
"Could not convert child to text node"
97+
))
98+
})?;
99+
// let mut cursor = 0;
100+
// let text = child.text
101+
// for position in positions {
102+
// let next_split = position - cursor;
103+
// child.split_text(next_split as u32 - cursor).unwrap();
104+
// cursor = position as u32;
105+
// sweet_utils::log!("child: {:?}", child);
106+
// }
107+
}
108+
}
109+
Ok(())
110+
}
111+
}
112+
113+
114+
impl Hydrator for DomHydrator {
115+
/// returns body inner html
116+
fn render(&self) -> String {
117+
window()
118+
.unwrap()
119+
.document()
120+
.unwrap()
121+
.body()
122+
.unwrap()
123+
.inner_html()
124+
}
125+
126+
fn update_rsx_node(
127+
&mut self,
128+
rsx: RsxNode,
129+
cx: &RsxContext,
130+
) -> Result<(), HydrationError> {
131+
let el = self.get_or_find_element(cx.element_id)?;
132+
match rsx {
133+
RsxNode::Fragment(vec) => todo!(),
134+
RsxNode::Block {
135+
initial,
136+
register_effect,
137+
} => {
138+
sweet_utils::log!("element found! {}", el.tag_name());
139+
}
140+
RsxNode::Doctype => todo!(),
141+
RsxNode::Comment(_) => todo!(),
142+
RsxNode::Text(val) => {
143+
if let Some(encoded) =
144+
el.get_attribute(self.constants.block_attribute_key)
145+
{
146+
let parts =
147+
TextBlockEncoder::decode(&encoded).map_err(|err| {
148+
HydrationError::InvalidContext(format!(
149+
"Could not decode block attribute on element: {}\n{:?}",
150+
cx.element_id,err
151+
))
152+
})?;
153+
} else {
154+
return Err(HydrationError::InvalidContext(format!(
155+
"Could not find block attribute on element: {}",
156+
cx.element_id
157+
)));
158+
};
159+
160+
// let block_encoding = self.document.create_text_node(&val);
161+
}
162+
RsxNode::Element(rsx_element) => todo!(),
163+
}
164+
165+
166+
Ok(())
167+
}
168+
}

crates/sweet_core/src/rsx/rsx_context.rs

Lines changed: 18 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,15 @@ pub struct RsxContext {
88
/// the number of rsx nodes visited
99
rsx_node_count: usize,
1010
/// the rsx id of the parent element
11-
pub html_pos: TreePosition,
11+
// pub html_pos: TreePosition,
1212
/// an id for the current element
1313
pub element_id: usize,
14-
/// the actual index of the html node child.
15-
/// This is post collapse so you may need to
16-
/// use [TextBlockEncoder] to split the node
14+
/// The index of this child *at rsx time*. This will be incorrect
15+
/// post reload because other children may be added or removed
1716
pub element_child_index: usize,
18-
/// text nodes may sit alongside one another
19-
/// in the [HtmlNode] tree so use this value to index
20-
/// before collapse
21-
pub element_child_index_uncollapsed: usize,
22-
23-
/// if this is true and a text node is visited,
24-
/// the child index will not increment as these will collapse
25-
/// in html. The positioning will be encoded by [TextBlockEncoder]
26-
prev_text_node: bool,
17+
/// in rsx the html may move around during a reload but the indices of
18+
/// child blocks is constant so we use that to track the position
19+
pub element_child_block_index: usize,
2720
}
2821

2922
impl Default for RsxContext {
@@ -32,9 +25,7 @@ impl Default for RsxContext {
3225
rsx_node_count: 0,
3326
element_id: 0,
3427
element_child_index: 0,
35-
element_child_index_uncollapsed: 0,
36-
html_pos: TreePosition::root(),
37-
prev_text_node: false,
28+
element_child_block_index: 0,
3829
}
3930
}
4031
}
@@ -51,33 +42,32 @@ impl RsxContext {
5142
match node {
5243
RsxNodeDiscriminants::Fragment => {}
5344
RsxNodeDiscriminants::Block => {}
54-
RsxNodeDiscriminants::Text => self.visit_text_node(),
55-
RsxNodeDiscriminants::Doctype => self.visit_non_text_node(),
56-
RsxNodeDiscriminants::Comment => self.visit_non_text_node(),
45+
RsxNodeDiscriminants::Text
46+
| RsxNodeDiscriminants::Doctype
47+
| RsxNodeDiscriminants::Comment => self.next_rsx_id(),
5748
RsxNodeDiscriminants::Element => {
58-
self.visit_non_text_node();
49+
self.next_rsx_id();
5950
self.element_id = self.rsx_id();
6051
}
6152
}
6253
}
6354
/// call this before visiting the children of an element
6455
pub fn before_element_children(&mut self) {
65-
self.prev_text_node = false;
6656
self.element_child_index = 0;
67-
self.element_child_index_uncollapsed = 0;
68-
self.html_pos.push_child();
57+
self.element_child_block_index = 0;
58+
// self.html_pos.push_child();
6959
}
7060

7161
/// call this after visiting the children of an element
72-
pub fn after_element_children(&mut self) {
73-
self.prev_text_node = false;
74-
self.html_pos.pop_child();
75-
}
62+
pub fn after_element_children(&mut self) {}
7663

7764
pub fn after_visit_next(&mut self, node: &RsxNodeDiscriminants) {
65+
self.element_child_index += 1;
7866
match node {
7967
RsxNodeDiscriminants::Fragment => {}
80-
RsxNodeDiscriminants::Block => {}
68+
RsxNodeDiscriminants::Block => {
69+
self.element_child_block_index += 1;
70+
}
8171
RsxNodeDiscriminants::Text => {}
8272
RsxNodeDiscriminants::Doctype => {}
8373
RsxNodeDiscriminants::Comment => {}
@@ -87,27 +77,6 @@ impl RsxContext {
8777

8878
fn next_rsx_id(&mut self) { self.rsx_node_count += 1; }
8979

90-
/// visit a real html node
91-
fn visit_non_text_node(&mut self) {
92-
self.element_child_index += 1;
93-
self.element_child_index_uncollapsed += 1;
94-
self.prev_text_node = false;
95-
self.html_pos.next_sibling();
96-
self.next_rsx_id();
97-
}
98-
99-
/// visit a text node that may be collapsed
100-
fn visit_text_node(&mut self) {
101-
self.element_child_index_uncollapsed += 1;
102-
if !self.prev_text_node {
103-
// this will create a new child
104-
self.prev_text_node = true;
105-
self.element_child_index += 1;
106-
self.html_pos.next_sibling();
107-
}
108-
// otherwise this node will collapse into prev
109-
self.next_rsx_id();
110-
}
11180
}
11281

11382
impl std::fmt::Display for RsxContext {

0 commit comments

Comments
 (0)