1#![allow(unused_braces)]
2#![allow(non_snake_case)]
3#![allow(dead_code)]
4
5use core::fmt::Debug;
6use std::{collections::HashMap, hash::Hash};
7
8use leptos::{ev::MouseEvent, *};
9
10#[derive(Debug, Clone, PartialEq)]
11pub struct SelectionModel<S, T>
12where
13 S: Clone + PartialEq + Eq + Hash + 'static,
14 T: Clone + 'static + Debug + PartialEq,
15{
16 items: HashMap<S, TreeNode<T, S>>,
17 selection: HashMap<S, bool>,
18 multi_select: bool,
19}
20
21impl<S, T> Default for SelectionModel<S, T>
22where
23 S: Clone + PartialEq + Eq + Hash + 'static,
24 T: Clone + 'static + Debug + PartialEq,
25{
26 fn default() -> Self {
27 Self::new()
28 }
29}
30
31impl<S, T> SelectionModel<S, T>
32where
33 S: Clone + PartialEq + Eq + Hash + 'static,
34 T: Clone + 'static + Debug + PartialEq,
35{
36 pub fn new() -> Self {
37 Self {
38 items: HashMap::new(),
39 selection: HashMap::new(),
40 multi_select: false,
41 }
42 }
43
44 pub fn set_multi_select(&mut self, multi_select: bool) {
45 self.multi_select = multi_select
46 }
47
48 pub fn get(&self, key: &S) -> Option<&T> {
49 Some(&self.items.get(key)?.row)
50 }
51
52 pub fn get_mut(&mut self, key: &S) -> Option<&mut T> {
53 Some(&mut self.items.get_mut(key)?.row)
54 }
55
56 pub fn get_node(&self, key: &S) -> Option<&TreeNode<T, S>> {
57 self.items.get(key)
58 }
59
60 pub fn get_node_mut(&mut self, key: &S) -> Option<&mut TreeNode<T, S>> {
61 self.items.get_mut(key)
62 }
63
64 pub fn clear_selection(&mut self) {
65 self.selection.clear();
66 }
67
68 pub fn select(&mut self, key: &S) {
69 if !self.multi_select {
70 self.selection.clear();
71 }
72 self.selection.insert(key.clone(), true);
73 }
74
75 pub fn toggle(&mut self, key: &S) {
76 let current_value = self.is_selected(key);
77 if !self.multi_select {
78 self.selection.clear();
79 }
80
81 self.selection.insert(key.clone(), !current_value);
82 }
83
84 pub fn selection_mut(&mut self) -> Vec<&mut T> {
85 let selected = self.selection.clone();
86 self.items
87 .iter_mut()
88 .filter(|(key, _)| selected.get(*key).cloned().unwrap_or_default())
89 .map(|(_, item)| &mut item.row)
90 .collect()
91 }
92
93 pub fn selection(&self) -> Vec<&T> {
94 self.selection
95 .iter()
96 .filter(|(_, b)| **b)
97 .filter_map(|(k, _)| self.items.get(k).map(|i| &i.row))
98 .collect()
99 }
100
101 pub fn get_selected_keys(&self) -> Vec<&S> {
102 self.selection
103 .iter()
104 .filter(|(k, b)| **b && self.items.contains_key(k))
105 .map(|(key, _)| key)
106 .collect()
107 }
108
109 pub fn get_owned_selected_keys(&self) -> Vec<S> {
110 self.selection
111 .iter()
112 .filter(|(k, b)| **b && self.items.contains_key(k))
113 .map(|(key, _)| key)
114 .cloned()
115 .collect()
116 }
117
118 pub fn remove_item(&mut self, key: &S) -> Option<T> {
119 Some(self.items.remove(key)?.row)
120 }
121
122 pub fn is_selected(&self, key: &S) -> bool {
123 return self.selection.get(key).cloned().unwrap_or_default();
124 }
125
126 pub fn is_empty(&self) -> bool {
127 self.selection.is_empty()
128 }
129}
130
131#[component]
132pub fn TreeViewWidget<T, F, S, FV, IV, EC>(
133 each: F,
134 key: fn(&T) -> S,
135 each_child: EC,
136 view: FV,
137 #[prop(default=create_signal(false).0.into(), into)] show_separator: Signal<bool>,
138 #[prop(default=create_rw_signal(SelectionModel::default()), into)] selection_model: RwSignal<
139 SelectionModel<S, T>,
140 >,
141 #[prop(optional)] on_click: Option<fn(&S, MouseEvent)>,
142) -> impl IntoView
143where
144 T: Debug + Clone + PartialEq + 'static,
145 S: Debug + Clone + PartialEq + Eq + Hash + ToString + 'static,
146 F: Fn() -> Vec<T> + Copy + 'static,
147 FV: Fn(&T) -> IV + Copy + 'static,
148 IV: IntoView,
149 EC: Fn(&T) -> Vec<T> + Copy + 'static,
150{
151 let nodes = create_memo(move |_| each());
152
153 create_isomorphic_effect(move |_| {
154 each().into_iter().for_each(move |c| {
155 let key_val = store_value(key(&c));
156 if selection_model
157 .get_untracked()
158 .get_node(&key_val())
159 .is_none()
160 {
161 let node = TreeNode::<T, S>::new(key, c, 0);
162 selection_model.update(move |s| {
163 s.items.insert(key_val(), node);
164 });
165 }
166 })
167 });
168
169 let each = move || {
170 nodes()
171 .iter()
172 .filter_map(|n| selection_model.get_untracked().get_node(&key(n)).cloned())
173 .collect::<Vec<_>>()
174 };
175
176 view! {
177 <tree-view>
178 <ul>
179 <For
180 each
181 key=move |c| key(&c.row)
182 children=move |item| {
183 view! {
184 <TreeViewRow
185 item=item.row.clone()
186 key
187 selection_model
188 view
189 each_child
190 on_click
191 >
192 {view(&item.row)}
193 </TreeViewRow>
194 <Show when=show_separator fallback=|| ()>
195 <hr />
196 </Show>
197 }
198 }
199 />
200
201 </ul>
202 </tree-view>
203 }
204 .into_view()
205}
206
207#[component]
208fn TreeViewRow<T, S, FV, IV, EC>(
209 children: ChildrenFn,
210 item: T,
211 key: fn(&T) -> S,
212 each_child: EC,
213 view: FV,
214 selection_model: RwSignal<SelectionModel<S, T>>,
215 on_click: Option<fn(&S, MouseEvent)>,
216) -> impl IntoView
217where
218 T: Debug + Clone + PartialEq + 'static,
219 S: Debug + Clone + PartialEq + Eq + Hash + ToString + 'static,
220 FV: Fn(&T) -> IV + Copy + 'static,
221 IV: IntoView,
222 EC: Fn(&T) -> Vec<T> + Copy + 'static,
223{
224 let key_val = store_value(key(&item));
225
226 let node = create_read_slice(selection_model, move |sm| sm.items.get(&key_val()).cloned());
227
228 let (is_expanded, toggle_expand) = create_slice(
229 selection_model,
230 move |model| {
231 model
232 .items
233 .get(&key_val())
234 .map(|n| n.is_expanded)
235 .unwrap_or_default()
236 },
237 move |model, _| {
238 if let Some(node) = model.items.get_mut(&key_val()) {
239 node.toggle_expand()
240 };
241 },
242 );
243
244 let (is_selected, set_selected) = create_slice(
245 selection_model,
246 move |model| model.is_selected(&key_val()),
247 move |model, _| model.select(&key_val()),
248 );
249
250 let caret_class = move || "caret fa-solid fa-caret-right";
251
252 let div_class = move || {
253 let mut class = String::from("selectable row");
254 if is_selected() {
255 class += " selected"
256 }
257
258 class
259 };
260
261 let background = create_memo(move |_| {
262 if is_selected() {
263 "var(--accent, #3584E4)"
264 } else {
265 "none"
266 }
267 });
268
269 let on_row_click = move |_: MouseEvent| set_selected(());
270
271 let on_caret_click = move |ev: MouseEvent| {
272 ev.stop_propagation();
273 toggle_expand(())
274 };
275
276 let depth = move || node().map(|n| n.depth).unwrap_or_default();
277
278 let depth_style = move || {
279 let margin = format!("{}em", 2.0 * depth() as f64);
280 let style = format!("padding-left:{};", margin);
281 style
282 };
283
284 let node_children = create_memo(move |_| each_child(&item));
285
286 create_isomorphic_effect(move |_| {
287 node_children().into_iter().for_each(|c| {
288 let key_val = store_value(key(&c));
289 if selection_model
290 .get_untracked()
291 .get_node(&key_val())
292 .is_none()
293 {
294 let node = TreeNode::<T, S>::new(key, c, depth() + 1);
295 selection_model.update(|s| {
296 s.items.insert(key_val(), node);
297 });
298 }
299 });
300 });
301
302 let children = store_value(children);
303
304 view! {
305 <li style:display="block">
306 <div
307 style=depth_style
308 style:background=background
309 style:display="flex"
310 class=div_class
311 on:click=move |ev| {
312 if let Some(f) = on_click {
313 if let Some(k) = key_val.try_get_value() {
314 f(&k, ev);
315 }
316 } else {
317 on_row_click(ev);
318 }
319 }
320 >
321
322 <Show when=move || {
323 node.try_get_untracked()
324 .flatten()
325 .is_some_and(|c| !each_child(&c.row).is_empty())
326 }>
327 <div
328 class=caret_class
329 style:transform=move || if is_expanded() { "rotate(90deg)" } else { "" }
330 style:cursor="pointer"
331 style:font-size="24px"
332 style:transition="transform 0.24s"
333 on:click=on_caret_click
334 ></div>
335 </Show>
336 {children()}
337 </div>
338 <ul style:display=move || if is_expanded() { "block" } else { "none" }>
339 <For
340 each=node_children
341 key
342 children=move |item| {
343 view! {
344 <TreeViewRow
345 key
346 item=item.clone()
347 selection_model=selection_model
348 each_child=each_child
349 view=view
350 on_click
351 >
352 {view(&item)}
353 </TreeViewRow>
354 }
355 }
356 />
357
358 </ul>
359 </li>
360 }
361}
362
363#[derive(Debug, Clone, PartialEq)]
364pub struct TreeNode<T, S>
365where
366 T: Clone + 'static + Debug + PartialEq,
367 S: Clone + PartialEq + Eq + Hash + 'static,
368{
369 pub key: fn(&T) -> S,
370 pub row: T,
371 pub depth: usize,
372 pub is_expanded: bool,
373}
374
375impl<T, S> TreeNode<T, S>
376where
377 T: Clone + 'static + Debug + PartialEq,
378 S: Clone + PartialEq + Eq + Hash + 'static,
379{
380 pub fn new(key: fn(&T) -> S, item: T, depth: usize) -> Self {
381 Self {
382 key,
383 row: item.clone(),
384 depth,
385 is_expanded: false,
386 }
387 }
388
389 pub fn set_expand(&mut self, do_expand: bool) {
390 self.is_expanded = do_expand
391 }
392
393 pub fn toggle_expand(&mut self) {
394 self.is_expanded = !self.is_expanded
395 }
396}