datui_lib/app/keys/
pivot_melt_keys.rs1use crate::app::form::FormKey;
5use crate::app::jobs::{Answer, Job};
6use crate::app::modals::pivot_melt_modal::{
7 PREVIEW_INPUT_ROWS, PivotMeltFocus, PivotMeltTab, PreviewFrame, PreviewInput,
8};
9use crate::{App, AppEvent, Overlay};
10use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
11
12impl App {
13 pub(crate) fn open_pivot_builder(&mut self) {
15 let Some(state) = &self.data_table_state else {
16 return;
17 };
18 let modal = &mut self.pivot_melt_modal;
19 modal.available_columns = state.schema().iter_names().map(|s| s.to_string()).collect();
20 modal.column_dtypes = state
21 .schema()
22 .iter()
23 .map(|(n, d)| (n.to_string(), d.clone()))
24 .collect();
25 let view_rows = state.num_rows_if_valid();
26 let sorted = state.is_sorted();
27 modal.open(self.display.history_limit, &self.theme);
28 modal.preview.view_rows = view_rows;
29 modal.preview.sorted = sorted;
30 self.open_overlay(Overlay::PivotMelt);
31 self.request_reshape_preview();
32 }
33
34 pub(crate) fn pivot_melt_key(&mut self, event: &KeyEvent) -> Option<AppEvent> {
36 let out = self.pivot_melt_form_key(event);
37 if self.overlay == Overlay::PivotMelt {
38 self.request_reshape_preview();
39 }
40 out
41 }
42
43 pub fn request_reshape_preview(&mut self) {
46 let modal = &mut self.pivot_melt_modal;
47 let wanted = modal.staged_spec();
48 let preview = &mut modal.preview;
49 if wanted == preview.wanted {
50 return;
51 }
52 preview.token = preview.token.wrapping_add(1);
53 preview.wanted = wanted;
54 if preview.running.is_none() {
55 self.spawn_reshape_preview();
56 }
57 }
58
59 fn spawn_reshape_preview(&mut self) {
62 let preview = &self.pivot_melt_modal.preview;
63 let Some(spec) = preview.wanted.clone() else {
64 return;
65 };
66 let (epoch, token) = (preview.epoch, preview.token);
67 let input = preview.input.clone();
68 let read = match &input {
69 Some(_) => None,
70 None => {
71 let Some(state) = self.data_table_state.as_ref() else {
72 self.pivot_melt_modal.preview.shown =
74 Some((spec, Err("Nothing to preview".to_string())));
75 return;
76 };
77 let small = state
80 .num_rows_if_valid()
81 .is_some_and(|rows| rows <= PREVIEW_INPUT_ROWS);
82 let sorted = (state.is_sorted() && !small).then(|| state.visible_lf());
83 let lf = if small {
84 state.visible_lf()
85 } else {
86 state.preview_lf()
87 };
88 Some((lf, sorted, state.polars_streaming()))
89 }
90 };
91 self.pivot_melt_modal.preview.running = Some(token);
92 self.spawn_job(Job::ReshapePreview { epoch, token }, None, move |_| {
93 let (input, read) = match (input, read) {
94 (Some(input), _) => (input, None),
95 (None, Some((lf, sorted, streaming))) => {
96 let message = |e: polars::prelude::PolarsError| {
97 crate::error_display::user_message_from_polars(&e)
98 };
99 let head_of = |lf: polars::prelude::LazyFrame| {
101 crate::analysis::statistics::collect_lazy(
102 lf.slice(0, PREVIEW_INPUT_ROWS as u32 + 1),
103 streaming,
104 )
105 };
106 let mut head = head_of(lf).map_err(message)?;
107 if head.height() <= PREVIEW_INPUT_ROWS
109 && let Some(sorted) = sorted
110 {
111 head = head_of(sorted).map_err(message)?;
112 }
113 let input = PreviewInput {
114 whole: head.height() <= PREVIEW_INPUT_ROWS,
115 rows: std::sync::Arc::new(head.head(Some(PREVIEW_INPUT_ROWS))),
116 };
117 (input.clone(), Some(input))
118 }
119 (None, None) => return Err("Nothing to preview".to_string()),
120 };
121 let result = crate::app::modals::pivot_melt_modal::run_preview(&input.rows, &spec);
122 Ok(Answer::ReshapePreviewed {
123 input: read,
124 result,
125 })
126 });
127 }
128
129 pub(crate) fn reshape_preview_ended(
132 &mut self,
133 epoch: u64,
134 token: u64,
135 input: Option<PreviewInput>,
136 result: Result<PreviewFrame, String>,
137 ) {
138 let preview = &mut self.pivot_melt_modal.preview;
139 if self.overlay != Overlay::PivotMelt || preview.epoch != epoch {
140 return;
141 }
142 if preview.running == Some(token) {
143 preview.running = None;
144 }
145 if preview.input.is_none() {
146 preview.input = input;
147 }
148 if token == preview.token {
149 if let Some(spec) = preview.wanted.clone() {
150 preview.shown = Some((spec, result));
151 }
152 } else if preview.running.is_none() && preview.stale() {
153 self.spawn_reshape_preview();
155 }
156 }
157
158 pub fn reshape_preview_pending(&self) -> bool {
160 self.overlay == Overlay::PivotMelt && self.pivot_melt_modal.preview.running.is_some()
161 }
162
163 fn pivot_melt_form_key(&mut self, event: &KeyEvent) -> Option<AppEvent> {
165 if event.code == KeyCode::Esc && self.pivot_computing() {
168 self.cancel_pivot();
169 return None;
170 }
171 if event.code == KeyCode::Char('?') && event.modifiers.contains(KeyModifiers::CONTROL) {
172 self.open_help_overlay();
173 return None;
174 }
175
176 if crate::app::form::picker_form_key(&mut self.pivot_melt_modal, event) {
177 return None;
178 }
179
180 self.pivot_melt_modal.attention = false;
183
184 match crate::app::form::key(&mut self.pivot_melt_modal, event) {
185 FormKey::Cancel => {
186 self.close_overlay();
187 }
188 FormKey::Submit => return self.submit_pivot_melt(),
190 FormKey::Step(PivotMeltFocus::TabBar, _) => self.pivot_melt_modal.switch_tab(),
191 FormKey::Step(row, delta) => {
192 if self.pivot_melt_modal.is_choice_row(row) {
193 self.pivot_melt_modal.step_choice(delta);
194 } else {
195 self.pivot_melt_modal.step_picker_row(delta);
196 }
197 }
198 FormKey::Act(_) => self.pivot_melt_modal.open_picker(),
200 FormKey::Text(_) => {
202 if let Some(input) = self.pivot_melt_modal.focused_text_input_mut() {
203 let _ = input.handle_key(event, None);
204 }
205 }
206 FormKey::Other if event.code == KeyCode::Char('?') => self.open_help_overlay(),
207 FormKey::Moved | FormKey::Other => {}
208 }
209 None
210 }
211
212 fn submit_pivot_melt(&mut self) -> Option<AppEvent> {
215 let modal = &mut self.pivot_melt_modal;
216 match modal.active_tab {
217 PivotMeltTab::Pivot => {
218 if modal.pivot_validation_error().is_some() {
219 modal.attention = true;
220 None
221 } else {
222 modal
223 .build_pivot_spec()
224 .map(|spec| crate::Applied::Pivot(spec).into())
225 }
226 }
227 PivotMeltTab::Melt => {
228 if modal.melt_validation_error().is_some() {
229 modal.attention = true;
230 None
231 } else {
232 modal
233 .build_melt_spec()
234 .map(|spec| crate::Applied::Melt(spec).into())
235 }
236 }
237 }
238 }
239}