1use crate::render::context::RenderContext;
9use crate::sample_modal::{SampleField, SampleForm};
10use crate::widgets::ui::{FormRow, FormValue, Surface};
11use ratatui::buffer::Buffer;
12use ratatui::layout::Rect;
13use ratatui::style::Style;
14use ratatui::text::{Line, Span};
15use ratatui::widgets::{Paragraph, Widget, Wrap};
16
17pub const FILES_SHOWN: usize = 6;
19
20const LABEL_WIDTH: u16 = 15;
22
23pub fn render(form: &SampleForm, focused: bool, area: Rect, buf: &mut Buffer, ctx: &RenderContext) {
26 let dimmed = Style::default().fg(ctx.dimmed);
27 let g = crate::glyphs::get();
28 enum Item {
31 Row(SampleField),
32 Context(Line<'static>),
33 }
34 let mut items = Vec::new();
35 for field in form.fields() {
36 items.push(Item::Row(field));
37 match field {
38 SampleField::PartitionValues => {
39 let known = form.partition_values_known();
40 if !known.is_empty() {
41 let shown = if known.len() > 4 {
42 format!(
43 "{}, {}, {} {}",
44 known[0],
45 known[1],
46 g.ellipsis,
47 known[known.len() - 1]
48 )
49 } else {
50 known.join(", ")
51 };
52 items.push(Item::Context(Line::styled(
53 format!("Holds {shown}"),
54 dimmed,
55 )));
56 }
57 let (list, range) = match known.as_slice() {
59 [first, .., last] if known.len() >= 3 => {
60 (format!("{first},{last}"), format!("{}..{last}", known[1]))
61 }
62 _ => ("2019,2021".to_string(), "2020..2022".to_string()),
63 };
64 items.push(Item::Context(Line::styled(
65 format!("One, a list {list}, or a range {range}"),
66 dimmed,
67 )));
68 }
69 SampleField::Files => {
70 let chosen = form.files_chosen();
71 let files = &form.context.files;
72 for (index, name) in files
73 .iter()
74 .enumerate()
75 .skip(form.file_offset)
76 .take(FILES_SHOWN)
77 {
78 let mark = if chosen.contains(&(index + 1)) {
79 g.checkbox_on
80 } else {
81 g.checkbox_off
82 };
83 items.push(Item::Context(Line::from(vec![
84 Span::styled(format!("{mark} {:>3} ", index + 1), dimmed),
85 Span::styled(short_path(name), Style::default().fg(ctx.text_primary)),
86 ])));
87 }
88 let rest = files.len().saturating_sub(form.file_offset + FILES_SHOWN);
89 if rest > 0 {
90 items.push(Item::Context(Line::styled(
91 format!("{rest} more {}", g.ellipsis),
92 dimmed,
93 )));
94 }
95 }
96 SampleField::RangeTo => {
97 if let Some(rows) = form.context.view_rows {
98 items.push(Item::Context(Line::styled(
99 format!("Table: {} rows", crate::numfmt::group_chrome(rows)),
100 dimmed,
101 )));
102 }
103 }
104 SampleField::TimeBefore => {
105 items.push(Item::Context(Line::styled(
106 format!("Dates as 2024-01-31 {} Before excluded", g.middot),
107 dimmed,
108 )));
109 }
110 SampleField::Size => {
111 let per = if matches!(
112 form.draft.method,
113 crate::sampling::SampleMethod::PerPartition { .. }
114 ) {
115 "Rows per value"
116 } else {
117 "Rows"
118 };
119 let said = match (form.rows_expected(), form.estimate()) {
122 (Some(rows), Some(bytes)) => format!(
123 "{} rows {} ~{}",
124 crate::numfmt::group_chrome(rows),
125 g.middot,
126 crate::widgets::info::format_bytes(bytes)
127 ),
128 _ => format!("{per}, like 50000, 50k or 2m"),
129 };
130 items.push(Item::Context(Line::styled(said, dimmed)));
131 }
132 _ => {}
133 }
134 }
135
136 let error_height = u16::from(form.error.is_some()) * 3;
137 let height = items.len() as u16 + error_height + 2;
138 let width = area.width.saturating_sub(4).clamp(40, 72).min(area.width);
139 let frame = Rect {
140 x: area.x + area.width.saturating_sub(width) / 2,
141 y: area.y + area.height.saturating_sub(height) / 2,
142 width,
143 height: height.min(area.height),
144 };
145 let inner = Surface::new("Sample").render(frame, buf, ctx);
146 let bottom = inner.y + inner.height;
147 let mut y = inner.y;
148 for item in &items {
149 if y >= bottom {
150 return;
151 }
152 let line = Rect {
153 y,
154 height: 1,
155 ..inner
156 };
157 match item {
158 Item::Row(field) => {
159 let choice = form.choice(*field);
160 let value = match form.input(*field) {
161 Some(input) => FormValue::Input(input),
162 None => FormValue::Choice(&choice),
163 };
164 FormRow {
165 label: field.label(),
166 value,
167 focused: focused && form.field == *field,
168 label_width: LABEL_WIDTH,
169 }
170 .render(line, buf, ctx);
171 crate::pointer::record_field::<SampleForm>(line, *field);
172 }
173 Item::Context(text) => {
174 let indent = LABEL_WIDTH + 1;
176 Paragraph::new(text.clone()).render(
177 Rect {
178 x: line.x + indent,
179 width: line.width.saturating_sub(indent),
180 ..line
181 },
182 buf,
183 );
184 }
185 }
186 y += 1;
187 }
188 if let Some(error) = &form.error
189 && y + 1 < bottom
190 {
191 Paragraph::new(error.as_str())
192 .wrap(Wrap { trim: true })
193 .style(Style::default().fg(ctx.warning))
194 .render(
195 Rect {
196 y: y + 1,
197 height: bottom - y - 1,
198 ..inner
199 },
200 buf,
201 );
202 }
203}
204
205fn short_path(path: &str) -> String {
208 let parts: Vec<&str> = path.rsplit(['/', '\\']).take(2).collect();
209 parts.into_iter().rev().collect::<Vec<_>>().join("/")
210}