1use serde_json::Value;
15
16use crate::types::UiNode;
17
18pub const READ_ONLY_FIELDS: &[&str] = crate::layout_tree::FORM_FIELDS;
20
21pub const READ_ONLY_WRITE_PREFIXES: &[&str] = &["records.", "actions."];
23
24pub fn call_writes(name: &str) -> bool {
26 READ_ONLY_WRITE_PREFIXES.iter().any(|p| name.starts_with(p))
27}
28
29pub fn action_writes(action: &Value) -> bool {
31 action.get("functionCall").and_then(|f| f.get("call")).and_then(Value::as_str).is_some_and(call_writes)
32}
33
34pub fn disables(component: &str, on: Option<&indexmap::IndexMap<String, Value>>) -> bool {
36 match on {
37 Some(on) if !on.is_empty() => on.values().any(action_writes),
38 _ => READ_ONLY_FIELDS.contains(&component),
39 }
40}
41
42fn already_disabled(node: &UiNode) -> bool {
43 node.props.get("disabled") == Some(&Value::Bool(true))
44}
45
46pub fn read_only_tree(node: &UiNode) -> UiNode {
50 let mut out = node.clone();
51 apply(&mut out);
52 out
53}
54
55fn apply(node: &mut UiNode) {
56 if disables(&node.component, node.on.as_ref()) && !already_disabled(node) {
57 node.props.insert("disabled".into(), Value::Bool(true));
58 }
59 if let Some(slots) = node.slots.as_mut() {
60 slots.values_mut().for_each(apply);
61 }
62 node.children.iter_mut().for_each(apply);
63}
64
65pub fn disabled_ids(node: &UiNode) -> Vec<String> {
68 let mut out = Vec::new();
69 collect(node, &mut out);
70 out
71}
72
73fn collect(node: &UiNode, out: &mut Vec<String>) {
74 if disables(&node.component, node.on.as_ref()) && !already_disabled(node) {
75 out.push(node.id.clone());
76 }
77 for s in node.slots.iter().flat_map(|s| s.values()) {
78 collect(s, out);
79 }
80 for c in &node.children {
81 collect(c, out);
82 }
83}