ui-grid-core 1.0.6

Rust engine for ui-grid
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};

use crate::models::GridOptions;

type GridLabelOverrides = Map<String, Value>;

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GridMenuItem {
    pub title: String,
    pub order: usize,
    pub shown: bool,
    pub action_id: String,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GridExporterMenuContext {
    pub has_selection: bool,
    pub include_pdf: bool,
    pub include_excel: bool,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GridRowEditMenuPredicates {
    pub has_dirty_rows: bool,
    pub has_error_rows: bool,
}

fn label(labels: &GridLabelOverrides, key: &str) -> String {
    labels
        .get(key)
        .and_then(Value::as_str)
        .unwrap_or("")
        .to_string()
}

pub fn build_grid_exporter_menu_items(
    options: &GridOptions,
    labels: &GridLabelOverrides,
    context: &GridExporterMenuContext,
) -> Vec<GridMenuItem> {
    let base_order = options.exporter_menu_item_order.unwrap_or(200);
    let menu_csv = options.exporter_menu_csv.unwrap_or(true);
    let menu_pdf = options.exporter_menu_pdf.unwrap_or(true);
    let menu_excel = options.exporter_menu_excel.unwrap_or(true);
    let menu_all_data = options.exporter_menu_all_data.unwrap_or(true);
    let menu_visible = options.exporter_menu_visible_data.unwrap_or(true);
    let menu_selected = options.exporter_menu_selected_data.unwrap_or(true);

    let mut items = vec![
        GridMenuItem {
            title: label(labels, "exporterAllAsCsv"),
            order: base_order,
            shown: menu_csv && menu_all_data,
            action_id: "csv:all:all".to_string(),
        },
        GridMenuItem {
            title: label(labels, "exporterVisibleAsCsv"),
            order: base_order + 1,
            shown: menu_csv && menu_visible,
            action_id: "csv:visible:visible".to_string(),
        },
        GridMenuItem {
            title: label(labels, "exporterSelectedAsCsv"),
            order: base_order + 2,
            shown: menu_csv && menu_selected && context.has_selection,
            action_id: "csv:selected:visible".to_string(),
        },
    ];

    if context.include_pdf {
        items.extend([
            GridMenuItem {
                title: label(labels, "exporterAllAsPdf"),
                order: base_order + 3,
                shown: menu_pdf && menu_all_data,
                action_id: "pdf:all:all".to_string(),
            },
            GridMenuItem {
                title: label(labels, "exporterVisibleAsPdf"),
                order: base_order + 4,
                shown: menu_pdf && menu_visible,
                action_id: "pdf:visible:visible".to_string(),
            },
            GridMenuItem {
                title: label(labels, "exporterSelectedAsPdf"),
                order: base_order + 5,
                shown: menu_pdf && menu_selected && context.has_selection,
                action_id: "pdf:selected:visible".to_string(),
            },
        ]);
    }

    if context.include_excel {
        items.extend([
            GridMenuItem {
                title: label(labels, "exporterAllAsExcel"),
                order: base_order + 6,
                shown: menu_excel && menu_all_data,
                action_id: "excel:all:all".to_string(),
            },
            GridMenuItem {
                title: label(labels, "exporterVisibleAsExcel"),
                order: base_order + 7,
                shown: menu_excel && menu_visible,
                action_id: "excel:visible:visible".to_string(),
            },
            GridMenuItem {
                title: label(labels, "exporterSelectedAsExcel"),
                order: base_order + 8,
                shown: menu_excel && menu_selected && context.has_selection,
                action_id: "excel:selected:visible".to_string(),
            },
        ]);
    }

    items
}

pub fn build_grid_importer_menu_items(
    options: &GridOptions,
    labels: &GridLabelOverrides,
) -> Vec<GridMenuItem> {
    if options.enable_importer != Some(true) {
        return Vec::new();
    }
    if options.importer_show_menu == Some(false) {
        return Vec::new();
    }
    vec![GridMenuItem {
        title: label(labels, "importerTitle"),
        order: options.importer_menu_item_order.unwrap_or(400),
        shown: true,
        action_id: "import:file".to_string(),
    }]
}

pub fn build_grid_row_edit_menu_items(
    options: &GridOptions,
    labels: &GridLabelOverrides,
    predicates: &GridRowEditMenuPredicates,
) -> Vec<GridMenuItem> {
    let base_order = options.row_edit_menu_item_order.unwrap_or(300);
    let menu_flush = options.row_edit_menu_flush_dirty_rows.unwrap_or(true);
    let menu_cancel = options.row_edit_menu_cancel_dirty_rows.unwrap_or(true);

    let mut items = Vec::new();
    if menu_flush {
        items.push(GridMenuItem {
            title: label(labels, "rowEditFlushAll"),
            order: base_order,
            shown: predicates.has_dirty_rows,
            action_id: "row-edit:flush-dirty".to_string(),
        });
    }
    if menu_cancel {
        items.push(GridMenuItem {
            title: label(labels, "rowEditRetryErrors"),
            order: base_order + 1,
            shown: predicates.has_error_rows,
            action_id: "row-edit:retry-errors".to_string(),
        });
    }
    items
}

#[cfg(test)]
mod tests {
    use serde_json::json;

    use super::*;

    #[test]
    fn exporter_menu_respects_selection_and_optional_formats() {
        let items = build_grid_exporter_menu_items(
            &GridOptions::default(),
            &serde_json::from_value(json!({ "exporterAllAsCsv": "csv" })).unwrap(),
            &GridExporterMenuContext {
                has_selection: false,
                include_pdf: false,
                include_excel: false,
            },
        );
        assert_eq!(items.len(), 3);
        assert!(!items[2].shown);
    }

    #[test]
    fn importer_menu_is_empty_when_disabled() {
        assert!(build_grid_importer_menu_items(&GridOptions::default(), &Map::new()).is_empty());
    }
}