datui_lib/widgets/
sample_form.rs1use crate::analysis::sample_modal::{SampleField, SampleForm};
9use crate::render::context::RenderContext;
10use crate::widgets::ui::{FormLine, FormValue, FormView};
11use ratatui::buffer::Buffer;
12use ratatui::layout::Rect;
13use ratatui::style::Style;
14use ratatui::text::{Line, Span};
15
16pub const FILES_SHOWN: usize = 6;
18
19const LABEL_WIDTH: u16 = 15;
21
22pub fn render(form: &SampleForm, focused: bool, area: Rect, buf: &mut Buffer, ctx: &RenderContext) {
25 let dimmed = Style::default().fg(ctx.dimmed);
26 let g = crate::glyphs::get();
27 let choices: Vec<(SampleField, String)> = form
30 .fields()
31 .into_iter()
32 .map(|field| (field, form.choice(field)))
33 .collect();
34 let mut items = Vec::new();
35 for (field, choice) in &choices {
36 let field = *field;
37 let value = match form.input(field) {
38 Some(input) => FormValue::Input(input),
39 None => FormValue::Choice(choice),
40 };
41 items.push(FormLine::Field(field, field.label(), value));
42 match field {
43 SampleField::PartitionValues => {
44 let known = form.partition_values_known();
45 if !known.is_empty() {
46 let shown = if known.len() > 4 {
47 format!(
48 "{}, {}, {} {}",
49 known[0],
50 known[1],
51 g.ellipsis,
52 known[known.len() - 1]
53 )
54 } else {
55 known.join(", ")
56 };
57 items.push(FormLine::Note(Line::styled(
58 format!("Holds {shown}"),
59 dimmed,
60 )));
61 }
62 let (list, range) = match known.as_slice() {
64 [first, .., last] if known.len() >= 3 => {
65 (format!("{first},{last}"), format!("{}..{last}", known[1]))
66 }
67 _ => ("2019,2021".to_string(), "2020..2022".to_string()),
68 };
69 items.push(FormLine::Note(Line::styled(
70 format!("One, a list {list}, or a range {range}"),
71 dimmed,
72 )));
73 }
74 SampleField::Files => {
75 let chosen = form.files_chosen();
76 let files = &form.context.files;
77 for (index, name) in files
78 .iter()
79 .enumerate()
80 .skip(form.file_offset)
81 .take(FILES_SHOWN)
82 {
83 let mark = if chosen.contains(&(index + 1)) {
84 g.checkbox_on
85 } else {
86 g.checkbox_off
87 };
88 items.push(FormLine::Note(Line::from(vec![
89 Span::styled(format!("{mark} {:>3} ", index + 1), dimmed),
90 Span::styled(short_path(name), Style::default().fg(ctx.text_primary)),
91 ])));
92 }
93 let rest = files.len().saturating_sub(form.file_offset + FILES_SHOWN);
94 if rest > 0 {
95 items.push(FormLine::Note(Line::styled(
96 format!("{rest} more {}", g.ellipsis),
97 dimmed,
98 )));
99 }
100 }
101 SampleField::RangeTo => {
102 if let Some(rows) = form.context.view_rows {
103 items.push(FormLine::Note(Line::styled(
104 format!("Table: {} rows", crate::numfmt::group_chrome(rows)),
105 dimmed,
106 )));
107 }
108 }
109 SampleField::TimeBefore => {
110 items.push(FormLine::Note(Line::styled(
111 format!("Dates as 2024-01-31 {} Before excluded", g.middot),
112 dimmed,
113 )));
114 }
115 SampleField::Size => {
116 let per = if matches!(
117 form.draft.method,
118 crate::analysis::sampling::SampleMethod::PerPartition { .. }
119 ) {
120 "Rows per value"
121 } else {
122 "Rows"
123 };
124 let said = match (form.rows_expected(), form.estimate()) {
127 (Some(rows), Some(bytes)) => format!(
128 "{} rows {} ~{}",
129 crate::numfmt::group_chrome(rows),
130 g.middot,
131 crate::numfmt::bytes(bytes)
132 ),
133 _ => format!("{per}, like 50000, 50k or 2m"),
134 };
135 items.push(FormLine::Note(Line::styled(said, dimmed)));
136 }
137 _ => {}
138 }
139 }
140
141 let error_height = u16::from(form.error.is_some()) * 3;
143 let height = items.len() as u16 + error_height + 2;
144 let width = area.width.saturating_sub(4).clamp(40, 72).min(area.width);
145 let frame = crate::render::layout::centered_rect(area, width, height);
146 FormView {
147 title: "Sample",
148 screen: datui_cli::keys::Context::Sample,
149 footer: None,
150 label_width: LABEL_WIDTH,
151 rows: items,
152 focused: focused.then_some(form.field),
153 picker: None,
154 status: form
155 .error
156 .clone()
157 .map(|error| (error, Style::default().fg(ctx.warning))),
158 shields: false,
161 }
162 .render::<SampleForm>(frame, buf, ctx);
163}
164
165fn short_path(path: &str) -> String {
168 let parts: Vec<&str> = path.rsplit(['/', '\\']).take(2).collect();
169 parts.into_iter().rev().collect::<Vec<_>>().join("/")
170}