Skip to main content

ui_grid_core/
models.rs

1use std::collections::BTreeMap;
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5
6use crate::constants::{FilterCondition, SortDirection};
7
8pub type GridRecord = Value;
9
10#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
11#[serde(rename_all = "camelCase")]
12pub enum GridColumnType {
13    #[default]
14    String,
15    Number,
16    Boolean,
17    Date,
18    Object,
19}
20
21#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
22#[serde(rename_all = "camelCase")]
23pub struct GridFilterFlags {
24    #[serde(default)]
25    pub case_sensitive: bool,
26    #[serde(default)]
27    pub date: bool,
28}
29
30#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
31#[serde(rename_all = "camelCase")]
32pub struct GridFilterDescriptor {
33    #[serde(default)]
34    pub term: Option<Value>,
35    #[serde(default)]
36    pub condition: Option<FilterCondition>,
37    #[serde(default)]
38    pub flags: GridFilterFlags,
39    #[serde(default)]
40    pub raw_term: bool,
41    #[serde(default)]
42    pub no_term: bool,
43}
44
45#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
46#[serde(rename_all = "camelCase")]
47pub struct GridSortDescriptor {
48    #[serde(default)]
49    pub direction: Option<SortDirection>,
50    #[serde(default)]
51    pub priority: Option<u32>,
52    #[serde(default)]
53    pub ignore_sort: bool,
54}
55
56#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
57#[serde(rename_all = "camelCase")]
58pub struct GridColumnDef {
59    pub name: String,
60    #[serde(default)]
61    pub display_name: Option<String>,
62    #[serde(default)]
63    pub field: Option<String>,
64    #[serde(default)]
65    pub r#type: GridColumnType,
66    #[serde(default = "default_true")]
67    pub visible: bool,
68    #[serde(default = "default_true")]
69    pub sortable: bool,
70    #[serde(default = "default_true")]
71    pub filterable: bool,
72    #[serde(default = "default_true")]
73    pub enable_sorting: bool,
74    #[serde(default = "default_true")]
75    pub enable_filtering: bool,
76    #[serde(default = "default_true")]
77    pub enable_grouping: bool,
78    #[serde(default)]
79    pub enable_cell_edit: bool,
80    #[serde(default)]
81    pub enable_cell_edit_on_focus: bool,
82    #[serde(default)]
83    pub pinned_left: bool,
84    #[serde(default)]
85    pub pinned_right: bool,
86    #[serde(default = "default_true")]
87    pub enable_pinning: bool,
88    #[serde(default)]
89    pub width: Option<String>,
90    #[serde(default)]
91    pub align: Option<String>,
92    #[serde(default)]
93    pub sort: Option<GridSortDescriptor>,
94    #[serde(default)]
95    pub filter: Option<GridFilterDescriptor>,
96}
97
98impl Default for GridColumnDef {
99    fn default() -> Self {
100        Self {
101            name: String::new(),
102            display_name: None,
103            field: None,
104            r#type: GridColumnType::default(),
105            visible: true,
106            sortable: true,
107            filterable: true,
108            enable_sorting: true,
109            enable_filtering: true,
110            enable_grouping: true,
111            enable_cell_edit: false,
112            enable_cell_edit_on_focus: false,
113            pinned_left: false,
114            pinned_right: false,
115            enable_pinning: true,
116            width: None,
117            align: None,
118            sort: None,
119            filter: None,
120        }
121    }
122}
123
124fn default_true() -> bool {
125    true
126}
127
128#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
129#[serde(rename_all = "camelCase")]
130pub struct GridGroupingOptions {
131    #[serde(default)]
132    pub group_by: Vec<String>,
133    #[serde(default)]
134    pub start_collapsed: bool,
135}
136
137#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
138#[serde(rename_all = "camelCase")]
139pub enum GridIcon {
140    Grip,
141    Sort,
142    SortAsc,
143    SortDesc,
144    Group,
145    Ungroup,
146    ChevronLeft,
147    ChevronRight,
148    ChevronDown,
149    PinLeft,
150    PinRight,
151    Unpin,
152}
153
154#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
155#[serde(rename_all = "camelCase")]
156pub struct GridIcons {
157    pub sort_default: GridIcon,
158    pub sort_asc: GridIcon,
159    pub sort_desc: GridIcon,
160    pub group_column: GridIcon,
161    pub ungroup_column: GridIcon,
162    pub group_collapse: GridIcon,
163    pub group_expand: GridIcon,
164    pub tree_collapse: GridIcon,
165    pub tree_expand: GridIcon,
166    pub expand_detail: GridIcon,
167    pub collapse_detail: GridIcon,
168    pub drag_handle: GridIcon,
169    pub move_left: GridIcon,
170    pub move_right: GridIcon,
171    pub pin_left: GridIcon,
172    pub pin_right: GridIcon,
173    pub unpin: GridIcon,
174}
175
176impl Default for GridIcons {
177    fn default() -> Self {
178        Self {
179            sort_default: GridIcon::Sort,
180            sort_asc: GridIcon::SortAsc,
181            sort_desc: GridIcon::SortDesc,
182            group_column: GridIcon::Group,
183            ungroup_column: GridIcon::Ungroup,
184            group_collapse: GridIcon::ChevronDown,
185            group_expand: GridIcon::ChevronRight,
186            tree_collapse: GridIcon::ChevronDown,
187            tree_expand: GridIcon::ChevronRight,
188            expand_detail: GridIcon::ChevronDown,
189            collapse_detail: GridIcon::ChevronDown,
190            drag_handle: GridIcon::Grip,
191            move_left: GridIcon::ChevronLeft,
192            move_right: GridIcon::ChevronRight,
193            pin_left: GridIcon::PinLeft,
194            pin_right: GridIcon::PinRight,
195            unpin: GridIcon::Unpin,
196        }
197    }
198}
199
200#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
201#[serde(rename_all = "camelCase")]
202pub struct GridLabels {
203    pub sort_default: String,
204    pub sort_asc: String,
205    pub sort_desc: String,
206    pub group_column: String,
207    pub ungroup_column: String,
208    pub group_collapse: String,
209    pub group_expand: String,
210    pub tree_collapse: String,
211    pub tree_expand: String,
212    pub expand_detail: String,
213    pub collapse_detail: String,
214    pub filter_placeholder: String,
215    pub filter_disabled: String,
216    pub filter_column: String,
217    pub pagination_previous: String,
218    pub pagination_next: String,
219    pub pagination_page: String,
220    pub pagination_of: String,
221    pub pagination_rows: String,
222    pub empty_heading: String,
223    pub empty_description: String,
224    pub toolbar_of: String,
225    pub toolbar_rows: String,
226    pub stats_visible_rows: String,
227    pub group_rows_suffix: String,
228    pub pin_column: String,
229    pub pin_left: String,
230    pub pin_right: String,
231    pub unpin: String,
232}
233
234impl Default for GridLabels {
235    fn default() -> Self {
236        Self {
237            sort_default: "Sort".to_string(),
238            sort_asc: "Sort ascending".to_string(),
239            sort_desc: "Sort descending".to_string(),
240            group_column: "Group by this column".to_string(),
241            ungroup_column: "Remove grouping".to_string(),
242            group_collapse: "Collapse group".to_string(),
243            group_expand: "Expand group".to_string(),
244            tree_collapse: "Collapse row".to_string(),
245            tree_expand: "Expand row".to_string(),
246            expand_detail: "Expand details".to_string(),
247            collapse_detail: "Collapse details".to_string(),
248            filter_placeholder: "Filter…".to_string(),
249            filter_disabled: "Filter disabled".to_string(),
250            filter_column: "Filter".to_string(),
251            pagination_previous: "Previous page".to_string(),
252            pagination_next: "Next page".to_string(),
253            pagination_page: "Page".to_string(),
254            pagination_of: "of".to_string(),
255            pagination_rows: "Rows per page".to_string(),
256            empty_heading: "No matching rows".to_string(),
257            empty_description: "Adjust the active filters, grouping, or sort order.".to_string(),
258            toolbar_of: "of".to_string(),
259            toolbar_rows: "rows".to_string(),
260            stats_visible_rows: "visible rows".to_string(),
261            group_rows_suffix: "rows".to_string(),
262            pin_column: "Pin column".to_string(),
263            pin_left: "Pin left".to_string(),
264            pin_right: "Pin right".to_string(),
265            unpin: "Unpin".to_string(),
266        }
267    }
268}
269
270#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
271#[serde(rename_all = "camelCase")]
272pub struct GridInfiniteScrollState {
273    #[serde(default)]
274    pub scroll_up: bool,
275    #[serde(default)]
276    pub scroll_down: bool,
277    #[serde(default)]
278    pub data_loading: bool,
279    #[serde(default)]
280    pub previous_visible_rows: usize,
281}
282
283#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
284#[serde(rename_all = "camelCase")]
285pub struct GridOptions {
286    pub id: String,
287    #[serde(default)]
288    pub title: Option<String>,
289    #[serde(default)]
290    pub data: Vec<GridRecord>,
291    #[serde(default)]
292    pub column_defs: Vec<GridColumnDef>,
293    #[serde(default)]
294    pub labels: GridLabels,
295    #[serde(default)]
296    pub icons: GridIcons,
297    #[serde(default = "default_true")]
298    pub enable_sorting: bool,
299    #[serde(default = "default_true")]
300    pub enable_filtering: bool,
301    #[serde(default)]
302    pub enable_grouping: bool,
303    #[serde(default)]
304    pub enable_column_moving: bool,
305    #[serde(default)]
306    pub enable_cell_edit: bool,
307    #[serde(default)]
308    pub enable_cell_edit_on_focus: bool,
309    #[serde(default = "default_true")]
310    pub enable_virtualization: bool,
311    #[serde(default)]
312    pub enable_pagination: bool,
313    #[serde(default = "default_true")]
314    pub enable_pagination_controls: bool,
315    #[serde(default)]
316    pub use_external_pagination: bool,
317    #[serde(default)]
318    pub pagination_page_sizes: Vec<usize>,
319    #[serde(default)]
320    pub pagination_page_size: Option<usize>,
321    #[serde(default)]
322    pub pagination_current_page: Option<usize>,
323    #[serde(default)]
324    pub total_items: Option<usize>,
325    #[serde(default)]
326    pub enable_expandable: bool,
327    #[serde(default)]
328    pub expandable_row_height: Option<usize>,
329    #[serde(default)]
330    pub expandable_row_header_width: Option<usize>,
331    #[serde(default)]
332    pub enable_tree_view: bool,
333    #[serde(default)]
334    pub tree_children_field: Option<String>,
335    #[serde(default)]
336    pub tree_indent: Option<usize>,
337    #[serde(default = "default_true")]
338    pub show_tree_expand_no_children: bool,
339    #[serde(default)]
340    pub tree_row_header_always_visible: bool,
341    #[serde(default)]
342    pub enable_auto_resize: bool,
343    #[serde(default)]
344    pub infinite_scroll_rows_from_end: Option<usize>,
345    #[serde(default)]
346    pub infinite_scroll_up: bool,
347    #[serde(default)]
348    pub infinite_scroll_down: Option<bool>,
349    #[serde(default)]
350    pub virtualization_threshold: Option<usize>,
351    #[serde(default)]
352    pub viewport_height: Option<usize>,
353    #[serde(default)]
354    pub grouping: Option<GridGroupingOptions>,
355    #[serde(default)]
356    pub enable_pinning: bool,
357    #[serde(default)]
358    pub row_id_field: Option<String>,
359}
360
361impl Default for GridOptions {
362    fn default() -> Self {
363        Self {
364            id: String::new(),
365            title: None,
366            data: Vec::new(),
367            column_defs: Vec::new(),
368            labels: GridLabels::default(),
369            icons: GridIcons::default(),
370            enable_sorting: true,
371            enable_filtering: false,
372            enable_grouping: false,
373            enable_column_moving: false,
374            enable_cell_edit: false,
375            enable_cell_edit_on_focus: false,
376            enable_virtualization: true,
377            enable_pagination: false,
378            enable_pagination_controls: false,
379            use_external_pagination: false,
380            pagination_page_sizes: Vec::new(),
381            pagination_page_size: None,
382            pagination_current_page: None,
383            total_items: None,
384            enable_expandable: false,
385            expandable_row_height: None,
386            expandable_row_header_width: None,
387            enable_tree_view: false,
388            tree_children_field: None,
389            tree_indent: None,
390            show_tree_expand_no_children: false,
391            tree_row_header_always_visible: false,
392            enable_auto_resize: false,
393            infinite_scroll_rows_from_end: None,
394            infinite_scroll_up: false,
395            infinite_scroll_down: None,
396            virtualization_threshold: None,
397            viewport_height: None,
398            grouping: None,
399            enable_pinning: false,
400            row_id_field: Some("id".to_string()),
401        }
402    }
403}
404
405#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
406#[serde(rename_all = "camelCase")]
407pub struct GridCellPosition {
408    pub row_id: String,
409    pub column_name: String,
410}
411
412#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
413#[serde(rename_all = "camelCase")]
414pub struct SortState {
415    #[serde(default)]
416    pub column_name: Option<String>,
417    #[serde(default)]
418    pub direction: SortDirection,
419}
420
421#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
422#[serde(rename_all = "camelCase")]
423pub struct GridSavedPaginationState {
424    pub pagination_current_page: usize,
425    pub pagination_page_size: usize,
426}
427
428#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
429#[serde(rename_all = "camelCase")]
430pub struct GridSavedState {
431    #[serde(default)]
432    pub column_order: Vec<String>,
433    #[serde(default)]
434    pub filters: BTreeMap<String, String>,
435    #[serde(default)]
436    pub sort: Option<SortState>,
437    #[serde(default)]
438    pub grouping: Vec<String>,
439    #[serde(default)]
440    pub pagination: Option<GridSavedPaginationState>,
441    #[serde(default)]
442    pub expandable: BTreeMap<String, bool>,
443    #[serde(default)]
444    pub tree_view: BTreeMap<String, bool>,
445    #[serde(default)]
446    pub pinning: BTreeMap<String, String>,
447}
448
449#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
450#[serde(rename_all = "camelCase")]
451pub struct GridRow {
452    pub id: String,
453    pub entity: GridRecord,
454    pub index: usize,
455    pub height: usize,
456    #[serde(default)]
457    pub invisible_reasons: Vec<String>,
458    #[serde(default = "default_true")]
459    pub visible: bool,
460    #[serde(default)]
461    pub is_selected: bool,
462    #[serde(default)]
463    pub tree_level: usize,
464    #[serde(default)]
465    pub parent_id: Option<String>,
466    #[serde(default)]
467    pub has_children: bool,
468    #[serde(default)]
469    pub child_count: usize,
470    #[serde(default)]
471    pub expanded: bool,
472    #[serde(default)]
473    pub expanded_row_height: usize,
474}
475
476impl GridRow {
477    pub fn new(id: String, entity: GridRecord, index: usize, height: usize) -> Self {
478        Self {
479            id,
480            entity,
481            index,
482            height,
483            invisible_reasons: Vec::new(),
484            visible: true,
485            is_selected: false,
486            tree_level: 0,
487            parent_id: None,
488            has_children: false,
489            child_count: 0,
490            expanded: false,
491            expanded_row_height: 0,
492        }
493    }
494
495    pub fn set_this_row_invisible(&mut self, reason: impl Into<String>) {
496        let reason = reason.into();
497        if !self
498            .invisible_reasons
499            .iter()
500            .any(|existing| existing == &reason)
501        {
502            self.invisible_reasons.push(reason);
503        }
504        self.visible = false;
505    }
506
507    pub fn clear_this_row_invisible(&mut self, reason: &str) {
508        self.invisible_reasons.retain(|existing| existing != reason);
509        self.visible = self.invisible_reasons.is_empty();
510    }
511}
512
513#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
514#[serde(rename_all = "camelCase")]
515pub struct GroupItem {
516    pub id: String,
517    pub depth: usize,
518    pub field: String,
519    pub label: String,
520    pub count: usize,
521    pub collapsed: bool,
522}
523
524#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
525#[serde(rename_all = "camelCase")]
526pub struct RowItem {
527    pub id: String,
528    pub row: GridRow,
529    pub visible_index: usize,
530}
531
532#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
533#[serde(rename_all = "camelCase")]
534pub struct ExpandableItem {
535    pub id: String,
536    pub row: GridRow,
537}
538
539#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
540#[serde(tag = "kind", rename_all = "camelCase")]
541pub enum DisplayItem {
542    Group(GroupItem),
543    Row(RowItem),
544    Expandable(ExpandableItem),
545}
546
547#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
548#[serde(rename_all = "camelCase")]
549pub struct BuildGridPipelineContext {
550    pub options: GridOptions,
551    #[serde(default)]
552    pub columns: Vec<GridColumnDef>,
553    #[serde(default)]
554    pub active_filters: BTreeMap<String, String>,
555    #[serde(default)]
556    pub sort_state: SortState,
557    #[serde(default)]
558    pub group_by_columns: Vec<String>,
559    #[serde(default)]
560    pub collapsed_groups: BTreeMap<String, bool>,
561    #[serde(default)]
562    pub hidden_row_reasons: BTreeMap<String, Vec<String>>,
563    #[serde(default)]
564    pub expanded_rows: BTreeMap<String, bool>,
565    #[serde(default)]
566    pub expanded_tree_rows: BTreeMap<String, bool>,
567    #[serde(default = "default_current_page")]
568    pub current_page: usize,
569    #[serde(default)]
570    pub page_size: usize,
571    #[serde(default = "default_row_size")]
572    pub row_size: usize,
573}
574
575fn default_current_page() -> usize {
576    1
577}
578
579fn default_row_size() -> usize {
580    44
581}
582
583#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
584#[serde(rename_all = "camelCase")]
585pub struct PipelineResult {
586    #[serde(default)]
587    pub visible_rows: Vec<GridRow>,
588    #[serde(default)]
589    pub display_items: Vec<DisplayItem>,
590    #[serde(default)]
591    pub virtualization_enabled: bool,
592    #[serde(default)]
593    pub pipeline_ms: f64,
594    #[serde(default)]
595    pub total_items: usize,
596}