orbital_base_components/collection/state/
registry.rs1use 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 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 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}