datui_lib/widgets/
export.rs1use crate::CompressionFormat;
6use crate::app::pointer::FieldId;
7use crate::export::export_modal::{COMPRESSION_OPTIONS, ExportFocus, ExportFormat, ExportModal};
8use crate::render::context::RenderContext;
9use crate::widgets::ui::{FormLine, FormValue, FormView, HintBar};
10use ratatui::layout::Rect;
11use ratatui::style::Style;
12
13const LABEL_WIDTH: u16 = 14;
16
17const MAX_WIDTH: u16 = 70;
19
20const HEIGHT: u16 = 6 + 2 + 4;
24
25const NESTED_NOTE: &str = "Lists and structs written as JSON";
27
28const AVRO_NAMES_NOTE: &str = "Column names made valid for Avro";
30
31const FORMAT_NAMES: [&str; ExportFormat::ALL.len()] = {
33 let mut names = [""; ExportFormat::ALL.len()];
34 let mut i = 0;
35 while i < names.len() {
36 names[i] = ExportFormat::ALL[i].as_str();
37 i += 1;
38 }
39 names
40};
41
42const fn compression_name(compression: Option<CompressionFormat>) -> &'static str {
43 match compression {
44 None => "None",
45 Some(CompressionFormat::Gzip) => "Gzip",
46 Some(CompressionFormat::Zstd) => "Zstd",
47 Some(CompressionFormat::Bzip2) => "Bzip2",
48 Some(CompressionFormat::Xz) => "XZ",
49 }
50}
51
52const COMPRESSION_NAMES: [&str; COMPRESSION_OPTIONS.len()] = {
55 let mut names = [""; COMPRESSION_OPTIONS.len()];
56 let mut i = 0;
57 while i < names.len() {
58 names[i] = compression_name(COMPRESSION_OPTIONS[i]);
59 i += 1;
60 }
61 names
62};
63
64pub fn dialog_area(area: Rect) -> Rect {
67 let width = area.width.saturating_sub(4).min(MAX_WIDTH);
68 let height = HEIGHT.min(area.height);
69 crate::render::layout::centered_rect(area, width, height)
70}
71
72pub fn render_export_modal(
73 area: Rect,
74 buf: &mut ratatui::buffer::Buffer,
75 modal: &mut ExportModal,
76 ctx: &RenderContext,
77) {
78 let footer = HintBar::from_ctx(ctx)
82 .screen(datui_cli::keys::Context::Export)
83 .group("Form")
84 .key("Enter")
85 .weight(3);
86 let footer = match modal.focus {
87 ExportFocus::FormatSelector => footer.key_as("← / →", "Format").weight(2),
88 ExportFocus::CsvIncludeHeader | ExportFocus::SourceFile => {
89 footer.key_as("Space", "Toggle").weight(2)
90 }
91 ExportFocus::Compression => footer.key("← / →").weight(2),
92 ExportFocus::PathInput | ExportFocus::CsvDelimiter => footer,
93 };
94 let footer = footer.key("Tab").weight(1).key("Esc").weight(4);
95
96 modal
97 .path_input
98 .set_focused(modal.focus == ExportFocus::PathInput);
99 modal
100 .csv_delimiter_input
101 .set_focused(modal.focus == ExportFocus::CsvDelimiter);
102
103 let selected = ExportFormat::ALL
107 .iter()
108 .position(|f| *f == modal.selected_format)
109 .unwrap_or(0);
110 let compression = COMPRESSION_OPTIONS
111 .iter()
112 .position(|c| *c == modal.compression())
113 .unwrap_or(0);
114 let rows = modal
115 .focus_order()
116 .into_iter()
117 .map(|field| {
118 let (label, value) = match field {
119 ExportFocus::FormatSelector => (
120 "Format:",
121 FormValue::Options {
122 items: &FORMAT_NAMES,
123 selected,
124 clicks: Some(FieldId::of::<ExportModal>(field)),
125 },
126 ),
127 ExportFocus::PathInput => ("Path:", FormValue::Input(&modal.path_input)),
128 ExportFocus::CsvDelimiter => {
129 ("Delimiter:", FormValue::Input(&modal.csv_delimiter_input))
130 }
131 ExportFocus::CsvIncludeHeader => {
132 ("Header:", FormValue::Toggle(modal.csv_include_header))
133 }
134 ExportFocus::Compression => (
135 "Compression:",
136 FormValue::Options {
137 items: &COMPRESSION_NAMES,
138 selected: compression,
139 clicks: Some(FieldId::of::<ExportModal>(field)),
140 },
141 ),
142 ExportFocus::SourceFile => ("Source file:", FormValue::Toggle(modal.source_file)),
143 };
144 FormLine::Field(field, label, value)
145 })
146 .collect();
147
148 let status = match modal.path_error.as_deref() {
152 Some(message) => (message, ctx.warning),
153 None if modal.nested_columns && !modal.selected_format.holds_nesting() => {
154 (NESTED_NOTE, ctx.dimmed)
155 }
156 None if modal.avro_renames && modal.selected_format == ExportFormat::Avro => {
157 (AVRO_NAMES_NOTE, ctx.dimmed)
158 }
159 None => ("", ctx.dimmed),
160 };
161 FormView {
162 title: "Export Data",
163 screen: datui_cli::keys::Context::Export,
164 footer: Some(footer),
165 label_width: LABEL_WIDTH,
166 rows,
167 focused: Some(modal.focus),
168 picker: None,
169 status: Some((status.0.to_string(), Style::default().fg(status.1))),
170 shields: true,
171 }
172 .render::<ExportModal>(area, buf, ctx);
173}
174
175#[cfg(test)]
176mod tests;