ui-grid-core 0.1.7

Rust engine for ui-grid
Documentation
use std::collections::{BTreeMap, BTreeSet};

use serde_json::Value;

use crate::{
    filtering::{clear_grid_filter_reasons, matches_grid_row_filters},
    models::{GridColumnDef, GridOptions, GridRecord, GridRow, SortState},
    sorting::sort_grid_rows,
    utils::get_path_value,
};

pub fn is_tree_enabled(options: &GridOptions) -> bool {
    options.enable_tree_view
}

fn get_tree_children(options: &GridOptions, entity: &GridRecord) -> Vec<GridRecord> {
    if !is_tree_enabled(options) {
        return Vec::new();
    }

    let children_field = options.tree_children_field.as_deref().unwrap_or("children");
    match get_path_value(entity, children_field) {
        Some(Value::Array(values)) => values,
        _ => Vec::new(),
    }
}

fn resolve_row_id(options: &GridOptions, entity: &GridRecord, index: usize) -> String {
    if let Some(field) = &options.row_id_field
        && let Some(Value::String(id)) = get_path_value(entity, field)
    {
        return id;
    }

    format!("{}-{}", options.id, index)
}

struct CreateRowContext<'a> {
    options: &'a GridOptions,
    row_size: usize,
    hidden_row_reasons: &'a BTreeMap<String, Vec<String>>,
    expanded_rows: &'a BTreeMap<String, bool>,
}

fn create_row(
    context: &CreateRowContext<'_>,
    entity: &GridRecord,
    index: usize,
    tree_level: usize,
    parent_id: Option<String>,
    child_count: usize,
) -> GridRow {
    let row_id = resolve_row_id(context.options, entity, index);
    let mut row = GridRow::new(row_id.clone(), entity.clone(), index, context.row_size);
    row.tree_level = tree_level;
    row.parent_id = parent_id;
    row.child_count = child_count;
    row.has_children = child_count > 0;
    row.expanded = context.expanded_rows.get(&row_id).copied().unwrap_or(false);
    row.expanded_row_height = context.options.expandable_row_height.unwrap_or(150);

    if let Some(reasons) = context.hidden_row_reasons.get(&row_id) {
        for reason in reasons {
            row.set_this_row_invisible(reason.clone());
        }
    }

    row
}

pub fn build_grid_rows(
    options: &GridOptions,
    row_size: usize,
    hidden_row_reasons: &BTreeMap<String, Vec<String>>,
    expanded_rows: &BTreeMap<String, bool>,
) -> Vec<GridRow> {
    let mut rows = Vec::new();
    let mut next_index = 0usize;
    let context = CreateRowContext {
        options,
        row_size,
        hidden_row_reasons,
        expanded_rows,
    };

    struct VisitContext<'a> {
        create_row: CreateRowContext<'a>,
    }

    fn visit(
        context: &VisitContext<'_>,
        rows: &mut Vec<GridRow>,
        next_index: &mut usize,
        entities: &[GridRecord],
        tree_level: usize,
        parent_id: Option<String>,
    ) {
        for entity in entities {
            let child_entities = get_tree_children(context.create_row.options, entity);
            let row = create_row(
                &context.create_row,
                entity,
                *next_index,
                tree_level,
                parent_id.clone(),
                child_entities.len(),
            );

            *next_index += 1;
            let parent = row.id.clone();
            rows.push(row);

            if is_tree_enabled(context.create_row.options) && !child_entities.is_empty() {
                visit(
                    context,
                    rows,
                    next_index,
                    &child_entities,
                    tree_level + 1,
                    Some(parent),
                );
            }
        }
    }

    let visit_context = VisitContext {
        create_row: context,
    };

    visit(
        &visit_context,
        &mut rows,
        &mut next_index,
        &options.data,
        0,
        None,
    );
    rows
}

pub fn filter_and_flatten_grid_tree_rows(
    rows: &[GridRow],
    columns: &[GridColumnDef],
    options: &GridOptions,
    active_filters: &BTreeMap<String, String>,
    expanded_tree_rows: &BTreeMap<String, bool>,
    sort_state: &SortState,
) -> Vec<GridRow> {
    let mut rows_by_parent: BTreeMap<Option<String>, Vec<GridRow>> = BTreeMap::new();
    for row in rows {
        rows_by_parent
            .entry(row.parent_id.clone())
            .or_default()
            .push(row.clone());
    }

    let mut included = BTreeSet::new();

    fn visit(
        row: &GridRow,
        rows_by_parent: &BTreeMap<Option<String>, Vec<GridRow>>,
        included: &mut BTreeSet<String>,
        columns: &[GridColumnDef],
        options: &GridOptions,
        active_filters: &BTreeMap<String, String>,
    ) -> bool {
        let manually_hidden = !row.visible
            && row
                .invisible_reasons
                .iter()
                .any(|reason| !reason.starts_with("filter:"));
        if manually_hidden {
            return false;
        }

        let children = rows_by_parent
            .get(&Some(row.id.clone()))
            .cloned()
            .unwrap_or_default();
        let mut child_included = false;
        for child in children {
            child_included = visit(
                &child,
                rows_by_parent,
                included,
                columns,
                options,
                active_filters,
            ) || child_included;
        }

        let mut current = row.clone();
        let self_included =
            matches_grid_row_filters(&mut current, columns, options, active_filters);
        if child_included {
            clear_grid_filter_reasons(&mut current);
        }

        let include = current.visible && (self_included || child_included);
        if include {
            included.insert(current.id);
        }
        include
    }

    for root_row in rows_by_parent.get(&None).cloned().unwrap_or_default() {
        visit(
            &root_row,
            &rows_by_parent,
            &mut included,
            columns,
            options,
            active_filters,
        );
    }

    let mut flattened = Vec::new();

    struct FlattenContext<'a> {
        rows_by_parent: &'a BTreeMap<Option<String>, Vec<GridRow>>,
        included: &'a BTreeSet<String>,
        columns: &'a [GridColumnDef],
        options: &'a GridOptions,
        expanded_tree_rows: &'a BTreeMap<String, bool>,
        sort_state: &'a SortState,
    }

    fn flatten(
        context: &FlattenContext<'_>,
        parent_id: Option<String>,
        flattened: &mut Vec<GridRow>,
    ) {
        let siblings = sort_grid_rows(
            &context
                .rows_by_parent
                .get(&parent_id)
                .cloned()
                .unwrap_or_default()
                .into_iter()
                .filter(|row| context.included.contains(&row.id))
                .collect::<Vec<_>>(),
            context.columns,
            context.options,
            context.sort_state,
        );

        for row in siblings {
            flattened.push(row.clone());
            if row.has_children
                && context
                    .expanded_tree_rows
                    .get(&row.id)
                    .copied()
                    .unwrap_or(false)
            {
                flatten(context, Some(row.id.clone()), flattened);
            }
        }
    }

    let flatten_context = FlattenContext {
        rows_by_parent: &rows_by_parent,
        included: &included,
        columns,
        options,
        expanded_tree_rows,
        sort_state,
    };

    flatten(&flatten_context, None, &mut flattened);
    flattened
}