Skip to main content

ui_grid_core/
tree.rs

1use std::collections::{BTreeMap, BTreeSet};
2
3use serde_json::Value;
4
5use crate::{
6    filtering::{clear_grid_filter_reasons, matches_grid_row_filters},
7    models::{GridColumnDef, GridOptions, GridRecord, GridRow, SortState},
8    sorting::sort_grid_rows,
9    utils::get_path_value,
10};
11
12pub fn is_tree_enabled(options: &GridOptions) -> bool {
13    options.enable_tree_view
14}
15
16fn get_tree_children(options: &GridOptions, entity: &GridRecord) -> Vec<GridRecord> {
17    if !is_tree_enabled(options) {
18        return Vec::new();
19    }
20
21    let children_field = options.tree_children_field.as_deref().unwrap_or("children");
22    match get_path_value(entity, children_field) {
23        Some(Value::Array(values)) => values,
24        _ => Vec::new(),
25    }
26}
27
28fn resolve_row_id(options: &GridOptions, entity: &GridRecord, index: usize) -> String {
29    if let Some(field) = &options.row_id_field
30        && let Some(Value::String(id)) = get_path_value(entity, field)
31    {
32        return id;
33    }
34
35    format!("{}-{}", options.id, index)
36}
37
38struct CreateRowContext<'a> {
39    options: &'a GridOptions,
40    row_size: usize,
41    hidden_row_reasons: &'a BTreeMap<String, Vec<String>>,
42    expanded_rows: &'a BTreeMap<String, bool>,
43}
44
45fn create_row(
46    context: &CreateRowContext<'_>,
47    entity: &GridRecord,
48    index: usize,
49    tree_level: usize,
50    parent_id: Option<String>,
51    child_count: usize,
52) -> GridRow {
53    let row_id = resolve_row_id(context.options, entity, index);
54    let mut row = GridRow::new(row_id.clone(), entity.clone(), index, context.row_size);
55    row.tree_level = tree_level;
56    row.parent_id = parent_id;
57    row.child_count = child_count;
58    row.has_children = child_count > 0;
59    row.expanded = context.expanded_rows.get(&row_id).copied().unwrap_or(false);
60    row.expanded_row_height = context.options.expandable_row_height.unwrap_or(150);
61
62    if let Some(reasons) = context.hidden_row_reasons.get(&row_id) {
63        for reason in reasons {
64            row.set_this_row_invisible(reason.clone());
65        }
66    }
67
68    row
69}
70
71pub fn build_grid_rows(
72    options: &GridOptions,
73    row_size: usize,
74    hidden_row_reasons: &BTreeMap<String, Vec<String>>,
75    expanded_rows: &BTreeMap<String, bool>,
76) -> Vec<GridRow> {
77    let mut rows = Vec::new();
78    let mut next_index = 0usize;
79    let context = CreateRowContext {
80        options,
81        row_size,
82        hidden_row_reasons,
83        expanded_rows,
84    };
85
86    struct VisitContext<'a> {
87        create_row: CreateRowContext<'a>,
88    }
89
90    fn visit(
91        context: &VisitContext<'_>,
92        rows: &mut Vec<GridRow>,
93        next_index: &mut usize,
94        entities: &[GridRecord],
95        tree_level: usize,
96        parent_id: Option<String>,
97    ) {
98        for entity in entities {
99            let child_entities = get_tree_children(context.create_row.options, entity);
100            let row = create_row(
101                &context.create_row,
102                entity,
103                *next_index,
104                tree_level,
105                parent_id.clone(),
106                child_entities.len(),
107            );
108
109            *next_index += 1;
110            let parent = row.id.clone();
111            rows.push(row);
112
113            if is_tree_enabled(context.create_row.options) && !child_entities.is_empty() {
114                visit(
115                    context,
116                    rows,
117                    next_index,
118                    &child_entities,
119                    tree_level + 1,
120                    Some(parent),
121                );
122            }
123        }
124    }
125
126    let visit_context = VisitContext {
127        create_row: context,
128    };
129
130    visit(
131        &visit_context,
132        &mut rows,
133        &mut next_index,
134        &options.data,
135        0,
136        None,
137    );
138    rows
139}
140
141pub fn filter_and_flatten_grid_tree_rows(
142    rows: &[GridRow],
143    columns: &[GridColumnDef],
144    options: &GridOptions,
145    active_filters: &BTreeMap<String, String>,
146    expanded_tree_rows: &BTreeMap<String, bool>,
147    sort_state: &SortState,
148) -> Vec<GridRow> {
149    let mut rows_by_parent: BTreeMap<Option<String>, Vec<GridRow>> = BTreeMap::new();
150    for row in rows {
151        rows_by_parent
152            .entry(row.parent_id.clone())
153            .or_default()
154            .push(row.clone());
155    }
156
157    let mut included = BTreeSet::new();
158
159    fn visit(
160        row: &GridRow,
161        rows_by_parent: &BTreeMap<Option<String>, Vec<GridRow>>,
162        included: &mut BTreeSet<String>,
163        columns: &[GridColumnDef],
164        options: &GridOptions,
165        active_filters: &BTreeMap<String, String>,
166    ) -> bool {
167        let manually_hidden = !row.visible
168            && row
169                .invisible_reasons
170                .iter()
171                .any(|reason| !reason.starts_with("filter:"));
172        if manually_hidden {
173            return false;
174        }
175
176        let children = rows_by_parent
177            .get(&Some(row.id.clone()))
178            .cloned()
179            .unwrap_or_default();
180        let mut child_included = false;
181        for child in children {
182            child_included = visit(
183                &child,
184                rows_by_parent,
185                included,
186                columns,
187                options,
188                active_filters,
189            ) || child_included;
190        }
191
192        let mut current = row.clone();
193        let self_included =
194            matches_grid_row_filters(&mut current, columns, options, active_filters);
195        if child_included {
196            clear_grid_filter_reasons(&mut current);
197        }
198
199        let include = current.visible && (self_included || child_included);
200        if include {
201            included.insert(current.id);
202        }
203        include
204    }
205
206    for root_row in rows_by_parent.get(&None).cloned().unwrap_or_default() {
207        visit(
208            &root_row,
209            &rows_by_parent,
210            &mut included,
211            columns,
212            options,
213            active_filters,
214        );
215    }
216
217    let mut flattened = Vec::new();
218
219    struct FlattenContext<'a> {
220        rows_by_parent: &'a BTreeMap<Option<String>, Vec<GridRow>>,
221        included: &'a BTreeSet<String>,
222        columns: &'a [GridColumnDef],
223        options: &'a GridOptions,
224        expanded_tree_rows: &'a BTreeMap<String, bool>,
225        sort_state: &'a SortState,
226    }
227
228    fn flatten(
229        context: &FlattenContext<'_>,
230        parent_id: Option<String>,
231        flattened: &mut Vec<GridRow>,
232    ) {
233        let siblings = sort_grid_rows(
234            &context
235                .rows_by_parent
236                .get(&parent_id)
237                .cloned()
238                .unwrap_or_default()
239                .into_iter()
240                .filter(|row| context.included.contains(&row.id))
241                .collect::<Vec<_>>(),
242            context.columns,
243            context.options,
244            context.sort_state,
245        );
246
247        for row in siblings {
248            flattened.push(row.clone());
249            if row.has_children
250                && context
251                    .expanded_tree_rows
252                    .get(&row.id)
253                    .copied()
254                    .unwrap_or(false)
255            {
256                flatten(context, Some(row.id.clone()), flattened);
257            }
258        }
259    }
260
261    let flatten_context = FlattenContext {
262        rows_by_parent: &rows_by_parent,
263        included: &included,
264        columns,
265        options,
266        expanded_tree_rows,
267        sort_state,
268    };
269
270    flatten(&flatten_context, None, &mut flattened);
271    flattened
272}