hypen_engine/reconcile/
tree.rs1use crate::ir::{Element, NodeId, Value};
2use indexmap::IndexMap;
3use slotmap::SlotMap;
4
5#[derive(Debug, Clone)]
7pub struct InstanceNode {
8 pub id: NodeId,
10
11 pub element_type: String,
13
14 pub props: IndexMap<String, serde_json::Value>,
16
17 pub raw_props: IndexMap<String, Value>,
19
20 pub element_template: Option<Element>,
23
24 pub key: Option<String>,
28
29 pub parent: Option<NodeId>,
31
32 pub children: Vec<NodeId>,
34}
35
36impl InstanceNode {
37 pub fn new(id: NodeId, element: &Element, state: &serde_json::Value) -> Self {
38 let props = resolve_props(&element.props, state);
39
40 Self {
41 id,
42 element_type: element.element_type.clone(),
43 props,
44 raw_props: element.props.clone(),
45 element_template: None,
46 key: element.key.clone(),
47 parent: None,
48 children: Vec::new(),
49 }
50 }
51
52 pub fn update_props(&mut self, state: &serde_json::Value) {
54 self.props = resolve_props(&self.raw_props, state);
55 }
56}
57
58pub struct InstanceTree {
60 nodes: SlotMap<NodeId, InstanceNode>,
62
63 root: Option<NodeId>,
65}
66
67impl InstanceTree {
68 pub fn new() -> Self {
69 Self {
70 nodes: SlotMap::with_key(),
71 root: None,
72 }
73 }
74
75 pub fn clear(&mut self) {
77 self.nodes.clear();
78 self.root = None;
79 }
80
81 pub fn create_node(&mut self, element: &Element, state: &serde_json::Value) -> NodeId {
83 let id = self.nodes.insert_with_key(|id| InstanceNode::new(id, element, state));
84 id
85 }
86
87 pub fn get(&self, id: NodeId) -> Option<&InstanceNode> {
89 self.nodes.get(id)
90 }
91
92 pub fn get_mut(&mut self, id: NodeId) -> Option<&mut InstanceNode> {
94 self.nodes.get_mut(id)
95 }
96
97 pub fn remove(&mut self, id: NodeId) -> Option<InstanceNode> {
99 if let Some(node) = self.nodes.get(id) {
100 let children = node.children.clone();
101 for child_id in children {
103 self.remove(child_id);
104 }
105 }
106 self.nodes.remove(id)
107 }
108
109 pub fn set_root(&mut self, id: NodeId) {
111 self.root = Some(id);
112 }
113
114 pub fn root(&self) -> Option<NodeId> {
116 self.root
117 }
118
119 pub fn add_child(&mut self, parent_id: NodeId, child_id: NodeId, before: Option<NodeId>) {
121 if let Some(parent) = self.nodes.get_mut(parent_id) {
122 if let Some(before_id) = before {
123 if let Some(pos) = parent.children.iter().position(|&id| id == before_id) {
124 parent.children.insert(pos, child_id);
125 } else {
126 parent.children.push(child_id);
127 }
128 } else {
129 parent.children.push(child_id);
130 }
131 }
132
133 if let Some(child) = self.nodes.get_mut(child_id) {
134 child.parent = Some(parent_id);
135 }
136 }
137
138 pub fn remove_child(&mut self, parent_id: NodeId, child_id: NodeId) {
140 if let Some(parent) = self.nodes.get_mut(parent_id) {
141 parent.children.retain(|&id| id != child_id);
142 }
143
144 if let Some(child) = self.nodes.get_mut(child_id) {
145 child.parent = None;
146 }
147 }
148
149 pub fn update_nodes(&mut self, node_ids: &indexmap::IndexSet<NodeId>, state: &serde_json::Value) {
151 for &node_id in node_ids {
152 if let Some(node) = self.nodes.get_mut(node_id) {
153 node.update_props(state);
154 }
155 }
156 }
157
158 pub fn iter(&self) -> impl Iterator<Item = (NodeId, &InstanceNode)> {
160 self.nodes.iter()
161 }
162}
163
164impl Default for InstanceTree {
165 fn default() -> Self {
166 Self::new()
167 }
168}
169
170fn resolve_props(props: &IndexMap<String, Value>, state: &serde_json::Value) -> IndexMap<String, serde_json::Value> {
172 resolve_props_with_item(props, state, None)
173}
174
175fn resolve_props_with_item(
177 props: &IndexMap<String, Value>,
178 state: &serde_json::Value,
179 item: Option<&serde_json::Value>,
180) -> IndexMap<String, serde_json::Value> {
181 let mut resolved = IndexMap::new();
182
183 for (key, value) in props {
184 let resolved_value = match value {
185 Value::Static(v) => v.clone(),
186 Value::Binding(binding) => {
187 if binding.is_item() {
188 if let Some(item_value) = item {
190 evaluate_item_binding(binding, item_value).unwrap_or(serde_json::Value::Null)
191 } else {
192 serde_json::Value::Null
193 }
194 } else {
195 evaluate_binding(binding, state).unwrap_or(serde_json::Value::Null)
197 }
198 }
199 Value::TemplateString { template, .. } => {
200 match crate::reactive::evaluate_template_string(template, state, item) {
203 Ok(result) => serde_json::Value::String(result),
204 Err(_) => serde_json::Value::String(template.clone()),
205 }
206 }
207 Value::Action(action) => {
208 serde_json::Value::String(format!("@{}", action))
210 }
211 };
212 resolved.insert(key.clone(), resolved_value);
213 }
214
215 resolved
216}
217
218fn evaluate_item_binding(binding: &crate::reactive::Binding, item: &serde_json::Value) -> Option<serde_json::Value> {
220 if binding.path.is_empty() {
221 return Some(item.clone());
223 }
224
225 let mut current = item;
226 for segment in &binding.path {
227 current = current.get(segment)?;
228 }
229 Some(current.clone())
230}
231
232fn evaluate_binding(binding: &crate::reactive::Binding, state: &serde_json::Value) -> Option<serde_json::Value> {
234 let mut current = state;
235
236 for segment in &binding.path {
237 current = current.get(segment)?;
238 }
239
240 Some(current.clone())
241}
242
243#[cfg(test)]
244mod tests {
245 use super::*;
246 use serde_json::json;
247
248 #[test]
249 fn test_evaluate_binding() {
250 use crate::reactive::Binding;
251
252 let state = json!({
253 "user": {
254 "name": "Alice",
255 "age": 30
256 }
257 });
258
259 let name_binding = Binding::state(vec!["user".to_string(), "name".to_string()]);
260 let age_binding = Binding::state(vec!["user".to_string(), "age".to_string()]);
261 let email_binding = Binding::state(vec!["user".to_string(), "email".to_string()]);
262
263 assert_eq!(
264 evaluate_binding(&name_binding, &state),
265 Some(json!("Alice"))
266 );
267 assert_eq!(
268 evaluate_binding(&age_binding, &state),
269 Some(json!(30))
270 );
271 assert_eq!(evaluate_binding(&email_binding, &state), None);
272 }
273}