Skip to main content

orbital_base_components/collection/state/
registry.rs

1use leptos::prelude::*;
2use std::collections::HashMap;
3
4#[derive(Clone, Debug)]
5pub struct CollectionRegistryEntry {
6    pub id: String,
7    pub label: String,
8    pub parent_id: Option<String>,
9    pub is_branch: bool,
10    pub depth: usize,
11    pub order: usize,
12}
13
14#[derive(Clone, Default)]
15pub struct CollectionRegistry {
16    pub entries: RwSignal<HashMap<String, CollectionRegistryEntry>>,
17    pub ordered_ids: RwSignal<Vec<String>>,
18}
19
20impl CollectionRegistry {
21    pub fn new() -> Self {
22        Self::default()
23    }
24
25    fn normalized_parent_id(parent_id: &Option<String>) -> Option<&str> {
26        parent_id.as_deref().filter(|id| !id.is_empty())
27    }
28
29    pub fn register(&self, entry: CollectionRegistryEntry) {
30        let entry = CollectionRegistryEntry {
31            parent_id: Self::normalized_parent_id(&entry.parent_id).map(str::to_string),
32            ..entry
33        };
34        self.entries.update(|entries| {
35            entries.insert(entry.id.clone(), entry.clone());
36        });
37        self.ordered_ids.update(|ordered| {
38            if !ordered.iter().any(|id| id == &entry.id) {
39                ordered.push(entry.id.clone());
40            }
41        });
42    }
43
44    pub fn unregister(&self, item_id: &str) {
45        self.entries.update(|entries| {
46            entries.remove(item_id);
47        });
48        self.ordered_ids.update(|ordered| {
49            ordered.retain(|id| id != item_id);
50        });
51    }
52
53    pub fn update_label(&self, item_id: &str, label: String) {
54        self.entries.update(|entries| {
55            if let Some(entry) = entries.get_mut(item_id) {
56                entry.label = label;
57            }
58        });
59    }
60
61    pub fn get_entry(&self, item_id: &str) -> Option<CollectionRegistryEntry> {
62        self.entries
63            .with_untracked(|entries| entries.get(item_id).cloned())
64    }
65
66    pub fn ordered_children_ids(&self, parent_id: &str) -> Vec<String> {
67        self.entries.with_untracked(|entries| {
68            let mut children: Vec<_> = entries
69                .values()
70                .filter(|entry| Self::normalized_parent_id(&entry.parent_id) == Some(parent_id))
71                .cloned()
72                .collect();
73            children.sort_by_key(|entry| entry.order);
74            children.into_iter().map(|entry| entry.id).collect()
75        })
76    }
77
78    pub fn root_ids(&self) -> Vec<String> {
79        self.entries.with_untracked(|entries| {
80            let mut roots: Vec<_> = entries
81                .values()
82                .filter(|entry| Self::normalized_parent_id(&entry.parent_id).is_none())
83                .cloned()
84                .collect();
85            roots.sort_by_key(|entry| entry.order);
86            roots.into_iter().map(|entry| entry.id).collect()
87        })
88    }
89
90    /// Depth-first visible item ids respecting expansion state.
91    pub fn visible_ordered_ids(&self, is_open: impl Fn(&str) -> bool) -> Vec<String> {
92        let mut result = Vec::new();
93        self.walk_visible(&mut result, None, &is_open);
94        result
95    }
96
97    fn walk_visible(
98        &self,
99        result: &mut Vec<String>,
100        parent_id: Option<&str>,
101        is_open: &impl Fn(&str) -> bool,
102    ) {
103        let children = self.entries.with_untracked(|entries| {
104            let mut children: Vec<_> = entries
105                .values()
106                .filter(|entry| Self::normalized_parent_id(&entry.parent_id) == parent_id)
107                .cloned()
108                .collect();
109            children.sort_by_key(|entry| entry.order);
110            children
111        });
112
113        for child in children {
114            result.push(child.id.clone());
115            if child.is_branch && is_open(&child.id) {
116                self.walk_visible(result, Some(&child.id), is_open);
117            }
118        }
119    }
120
121    pub fn item_tree(&self) -> Vec<(String, Vec<String>)> {
122        self.entries.with_untracked(|entries| {
123            let mut roots: Vec<_> = entries
124                .values()
125                .filter(|entry| Self::normalized_parent_id(&entry.parent_id).is_none())
126                .cloned()
127                .collect();
128            roots.sort_by_key(|entry| entry.order);
129            roots
130                .into_iter()
131                .map(|entry| {
132                    let children = self.ordered_children_ids(&entry.id);
133                    (entry.id, children)
134                })
135                .collect()
136        })
137    }
138
139    pub fn descendant_ids(&self, item_id: &str) -> Vec<String> {
140        let mut result = Vec::new();
141        self.collect_descendants(item_id, &mut result);
142        result
143    }
144
145    fn collect_descendants(&self, item_id: &str, result: &mut Vec<String>) {
146        for child_id in self.ordered_children_ids(item_id) {
147            result.push(child_id.clone());
148            self.collect_descendants(&child_id, result);
149        }
150    }
151
152    pub fn ancestor_ids(&self, item_id: &str) -> Vec<String> {
153        let mut result = Vec::new();
154        let mut current = self.get_entry(item_id).and_then(|e| e.parent_id);
155        while let Some(id) = current {
156            result.push(id.clone());
157            current = self.get_entry(&id).and_then(|e| e.parent_id);
158        }
159        result
160    }
161
162    /// Reorder `source_id` relative to `target_id` among shared siblings. `order` is `0` for before the target and `1` for after.
163    pub fn reorder_siblings(&self, source_id: &str, target_id: &str, order: usize) -> bool {
164        if source_id == target_id {
165            return false;
166        }
167
168        let parent_id = match (self.get_entry(source_id), self.get_entry(target_id)) {
169            (Some(source), Some(target)) if source.parent_id == target.parent_id => {
170                source.parent_id
171            }
172            _ => return false,
173        };
174
175        self.entries.update(|entries| {
176            let mut sorted: Vec<(usize, String)> = entries
177                .values()
178                .filter(|entry| entry.parent_id == parent_id)
179                .map(|entry| (entry.order, entry.id.clone()))
180                .collect();
181            sorted.sort_by(|a, b| a.0.cmp(&b.0).then_with(|| a.1.cmp(&b.1)));
182            let mut ids: Vec<String> = sorted.into_iter().map(|(_, id)| id).collect();
183            ids.retain(|id| id != source_id);
184            let Some(target_index) = ids.iter().position(|id| id == target_id) else {
185                return;
186            };
187            let insert_at = if order == 0 {
188                target_index
189            } else {
190                target_index + 1
191            };
192            ids.insert(insert_at.min(ids.len()), source_id.to_string());
193            for (index, id) in ids.iter().enumerate() {
194                if let Some(entry) = entries.get_mut(id) {
195                    entry.order = index;
196                }
197            }
198        });
199
200        true
201    }
202}