use crate::CompressionFormat;
use crate::app::pointer::FieldId;
use crate::export::export_modal::{COMPRESSION_OPTIONS, ExportFocus, ExportFormat, ExportModal};
use crate::render::context::RenderContext;
use crate::widgets::ui::{FormLine, FormValue, FormView, HintBar};
use ratatui::layout::Rect;
use ratatui::style::Style;
const LABEL_WIDTH: u16 = 14;
const MAX_WIDTH: u16 = 70;
const HEIGHT: u16 = 6 + 2 + 4;
const NESTED_NOTE: &str = "Lists and structs written as JSON";
const AVRO_NAMES_NOTE: &str = "Column names made valid for Avro";
const FORMAT_NAMES: [&str; ExportFormat::ALL.len()] = {
let mut names = [""; ExportFormat::ALL.len()];
let mut i = 0;
while i < names.len() {
names[i] = ExportFormat::ALL[i].as_str();
i += 1;
}
names
};
const fn compression_name(compression: Option<CompressionFormat>) -> &'static str {
match compression {
None => "None",
Some(CompressionFormat::Gzip) => "Gzip",
Some(CompressionFormat::Zstd) => "Zstd",
Some(CompressionFormat::Bzip2) => "Bzip2",
Some(CompressionFormat::Xz) => "XZ",
}
}
const COMPRESSION_NAMES: [&str; COMPRESSION_OPTIONS.len()] = {
let mut names = [""; COMPRESSION_OPTIONS.len()];
let mut i = 0;
while i < names.len() {
names[i] = compression_name(COMPRESSION_OPTIONS[i]);
i += 1;
}
names
};
pub fn dialog_area(area: Rect) -> Rect {
let width = area.width.saturating_sub(4).min(MAX_WIDTH);
let height = HEIGHT.min(area.height);
crate::render::layout::centered_rect(area, width, height)
}
pub fn render_export_modal(
area: Rect,
buf: &mut ratatui::buffer::Buffer,
modal: &mut ExportModal,
ctx: &RenderContext,
) {
let footer = HintBar::from_ctx(ctx)
.screen(datui_cli::keys::Context::Export)
.group("Form")
.key("Enter")
.weight(3);
let footer = match modal.focus {
ExportFocus::FormatSelector => footer.key_as("← / →", "Format").weight(2),
ExportFocus::CsvIncludeHeader | ExportFocus::SourceFile => {
footer.key_as("Space", "Toggle").weight(2)
}
ExportFocus::Compression => footer.key("← / →").weight(2),
ExportFocus::PathInput | ExportFocus::CsvDelimiter => footer,
};
let footer = footer.key("Tab").weight(1).key("Esc").weight(4);
modal
.path_input
.set_focused(modal.focus == ExportFocus::PathInput);
modal
.csv_delimiter_input
.set_focused(modal.focus == ExportFocus::CsvDelimiter);
let selected = ExportFormat::ALL
.iter()
.position(|f| *f == modal.selected_format)
.unwrap_or(0);
let compression = COMPRESSION_OPTIONS
.iter()
.position(|c| *c == modal.compression())
.unwrap_or(0);
let rows = modal
.focus_order()
.into_iter()
.map(|field| {
let (label, value) = match field {
ExportFocus::FormatSelector => (
"Format:",
FormValue::Options {
items: &FORMAT_NAMES,
selected,
clicks: Some(FieldId::of::<ExportModal>(field)),
},
),
ExportFocus::PathInput => ("Path:", FormValue::Input(&modal.path_input)),
ExportFocus::CsvDelimiter => {
("Delimiter:", FormValue::Input(&modal.csv_delimiter_input))
}
ExportFocus::CsvIncludeHeader => {
("Header:", FormValue::Toggle(modal.csv_include_header))
}
ExportFocus::Compression => (
"Compression:",
FormValue::Options {
items: &COMPRESSION_NAMES,
selected: compression,
clicks: Some(FieldId::of::<ExportModal>(field)),
},
),
ExportFocus::SourceFile => ("Source file:", FormValue::Toggle(modal.source_file)),
};
FormLine::Field(field, label, value)
})
.collect();
let status = match modal.path_error.as_deref() {
Some(message) => (message, ctx.warning),
None if modal.nested_columns && !modal.selected_format.holds_nesting() => {
(NESTED_NOTE, ctx.dimmed)
}
None if modal.avro_renames && modal.selected_format == ExportFormat::Avro => {
(AVRO_NAMES_NOTE, ctx.dimmed)
}
None => ("", ctx.dimmed),
};
FormView {
title: "Export Data",
screen: datui_cli::keys::Context::Export,
footer: Some(footer),
label_width: LABEL_WIDTH,
rows,
focused: Some(modal.focus),
picker: None,
status: Some((status.0.to_string(), Style::default().fg(status.1))),
shields: true,
}
.render::<ExportModal>(area, buf, ctx);
}
#[cfg(test)]
mod tests;