1use crate::app::jobs::{Answer, Job, Progress};
6use crate::app::modals::filter_modal::FilterStatement;
7use crate::app::modals::pivot_melt_modal::{MeltSpec, PivotSpec};
8use crate::chart::chart_export::ChartExportRequest;
9use crate::config::QueryMode;
10use crate::export::ExportRequest;
11use crate::{App, AppEvent, ExportProgress, Overlay, app};
12use std::path::PathBuf;
13
14pub enum Applied {
16 GoToLine(usize),
18 QQuery(String),
19 SqlQuery(String),
20 Filter(Vec<FilterStatement>),
21 Sort(Vec<String>, Vec<bool>),
23 ColumnOrder(Vec<String>, usize),
25 ApplyView(
28 Vec<String>,
29 usize,
30 Vec<FilterStatement>,
31 Vec<String>,
32 Vec<bool>,
33 ),
34 Pivot(PivotSpec),
35 Melt(MeltSpec),
36 QualityReportExport(
38 PathBuf,
39 crate::analysis::quality_export::ReportFormat,
40 crate::Overwrite,
41 ),
42 Export(ExportRequest),
44 DoExport(ExportRequest),
46 CopyTable {
48 format: crate::clipboard::CopyFormat,
49 header: bool,
50 },
51 ChartExport(ChartExportRequest),
53 DoChartExport(ChartExportRequest),
55 OpenLink(String),
57}
58
59impl From<Applied> for AppEvent {
60 fn from(applied: Applied) -> Self {
61 AppEvent::Applied(applied)
62 }
63}
64
65impl App {
66 pub(crate) fn apply(&mut self, applied: Applied) -> Option<AppEvent> {
68 match applied {
69 Applied::GoToLine(n) => {
70 if let Some(state) = self.data_table_state.as_ref()
72 && state.indexing().is_some()
73 && (n >= state.num_rows()
74 || state.changes_rows()
75 || !state.view_sort_columns().is_empty()
76 || !state.view_sort_ascending())
77 {
78 self.counting.goto_when_indexed = Some((self.dataset_generation, n));
79 self.status_message = Some(Self::INDEXING_FOR_ROW.to_string());
80 self.busy = false;
81 return None;
82 }
83 self.handle_scroll(|s| s.scroll_to_row_centered(n))
84 }
85 Applied::QQuery(query) => {
86 self.run_query(QueryMode::Q, &query, "Applying query...");
87 None
88 }
89 Applied::SqlQuery(sql) => {
90 self.run_query(QueryMode::Sql, &sql, "Applying SQL query...");
91 None
92 }
93 Applied::Filter(statements) => {
94 if let Some(state) = &mut self.data_table_state {
95 state.deferred(|s| s.filter(statements.clone()));
96 }
97 self.spawn_async_collect("Filtering...");
98 None
99 }
100 Applied::Sort(columns, descending) => {
101 if let Some(state) = &mut self.data_table_state {
102 state.deferred(|s| s.sort_by(columns.clone(), descending.clone()));
103 }
104 self.spawn_async_collect("Sorting...");
105 None
106 }
107 Applied::ApplyView(order, locked, filters, columns, descending) => {
108 if let Some(state) = &mut self.data_table_state {
109 let change = state
110 .deferred(|s| s.apply_view(order, locked, filters, columns, descending));
111 self.spawn_async_collect(match change {
112 crate::table::ViewChange { sort: true, .. } => "Sorting...",
113 crate::table::ViewChange { filters: true, .. } => "Filtering...",
114 _ => Self::LOADING_BUFFER,
115 });
116 }
117 None
118 }
119 Applied::ColumnOrder(order, locked_count) => {
120 if let Some(state) = &mut self.data_table_state {
121 state.deferred(|s| {
122 s.set_column_order(order);
123 s.set_locked_columns(locked_count);
124 });
125 self.spawn_async_collect(Self::LOADING_BUFFER);
126 }
127 None
128 }
129 Applied::Pivot(spec) => {
130 let job = self.data_table_state.as_ref()?.plan_pivot(&spec);
133 self.spawn_job(Job::Pivot, Some(Self::COMPUTING_PIVOT), move |_| {
134 let pivoted = job
135 .run()
136 .map_err(|e| crate::error_display::user_message_from_report(&e, None))?;
137 Ok(Answer::Pivoted { spec, pivoted })
138 });
139 None
140 }
141 Applied::Melt(spec) => {
142 self.busy = true;
143 if let Some(state) = &mut self.data_table_state {
144 let result = state.deferred(|s| s.melt(&spec));
145 match result {
146 Ok(()) => {
147 self.close_overlay();
148 self.spawn_async_collect("Computing melt...");
149 None
150 }
151 Err(e) => {
152 self.busy = false;
153 self.error_modal
154 .show(crate::error_display::user_message_from_report(&e, None));
155 None
156 }
157 }
158 } else {
159 self.busy = false;
160 None
161 }
162 }
163 Applied::QualityReportExport(path, format, overwrite) => {
164 let results = self.analysis_modal.quality.results.clone()?;
167 let plan = self.analysis_modal.quality_result_plan().clone();
168 self.spawn_job(
169 Job::QualityReport,
170 Some("Writing the report..."),
171 move |_| {
172 crate::analysis::quality_export::write(
173 &path, &results, &plan, format, overwrite,
174 )
175 .map_err(|error| Self::format_export_error(&error))?;
176 Ok(Answer::QualityReportWritten(path))
177 },
178 );
179 None
180 }
181 Applied::Export(request) => {
182 if self.data_table_state.is_some() {
183 self.busy = true;
184 self.export_progress =
185 Some(ExportProgress::new(&request.path, "Preparing export"));
186 Some(Applied::DoExport(request).into())
188 } else {
189 None
190 }
191 }
192 Applied::DoExport(request) => {
193 let Some(state) = &self.data_table_state else {
194 self.export_progress = None;
195 self.busy = false;
196 return None;
197 };
198 let frame = match self.export_modal.counts.clone() {
200 Some(counts) => {
201 crate::table::ExportFrame::of(polars::prelude::IntoLazy::lazy(counts))
202 }
203 None => state.export_frame(request.options.source_file),
204 };
205 let streaming = state.polars_streaming();
206 let phase = match request.route(streaming) {
209 crate::export::Route::Streamed => Self::export_write_phase(&request),
210 crate::export::Route::Collected => "Collecting data",
211 };
212 self.export_progress = Some(ExportProgress::new(&request.path, phase));
213 let writing = Self::export_write_phase(&request);
214 self.spawn_job(Job::Export, Some("Exporting..."), move |worker| {
215 let report = worker.reporter();
216 let written = move |bytes| {
217 report(Progress::ExportWriting {
218 phase: writing,
219 bytes,
220 })
221 };
222 frame
223 .into_lazy()
224 .map_err(color_eyre::eyre::Report::from)
225 .and_then(|lf| crate::export::run(lf, &request, streaming, written))
226 .map_err(|e| Self::format_export_error(&e))?;
227 Ok(Answer::Exported(request.path))
229 });
230 None
231 }
232 Applied::OpenLink(url) => {
233 if app::link_open::open(&url).is_err() {
235 self.flash_note("Couldn't open the link; y copies it".to_string());
236 }
237 None
238 }
239 Applied::CopyTable { format, header } => {
240 let accepts = match self.copy_destination() {
241 Ok(destination) => destination.accepts(),
242 Err(e) => {
243 self.busy = false;
244 self.error_modal.show(e);
245 return None;
246 }
247 };
248 if let Some(state) = &self.data_table_state {
249 let lf = state.visible_lf();
250 let streaming = state.polars_streaming();
251 self.spawn_job(Job::Copy, Some("Collecting data for copy..."), move |_| {
252 let (payload, rows) = match accepts.base64_limit {
255 Some(limit) => crate::clipboard::bounded_table_text(
256 lf, format, header, limit,
257 )
258 .map(|(text, rows)| (crate::clipboard::Payload::text(text), rows)),
259 None => crate::analysis::statistics::collect_lazy(lf, streaming)
260 .map_err(|e| crate::error_display::user_message_from_polars(&e))
261 .and_then(|df| {
262 crate::clipboard::tabular_payload(
263 &df,
264 format,
265 header,
266 accepts.html,
267 )
268 .map(|payload| (payload, df.height()))
269 }),
270 }
271 .map_err(|message| format!("Copy failed: {message}"))?;
272 Ok(Answer::Copied {
274 payload,
275 message: format!(
276 "Copied {} rows as {}",
277 app::modals::copy_modal::thousands(rows),
278 format.as_str()
279 ),
280 })
281 });
282 } else {
283 self.busy = false;
284 }
285 None
286 }
287 Applied::ChartExport(request) => {
288 self.busy = true;
289 self.export_progress = Some(ExportProgress::new(&request.path, "Exporting chart"));
290 Some(Applied::DoChartExport(request).into())
291 }
292 Applied::DoChartExport(request) => {
293 if !self.overlay.shows(&Overlay::Chart) {
296 self.export_progress = None;
297 self.status_message = None;
298 self.busy = false;
299 return None;
300 }
301 self.start_chart_export(request);
302 None
303 }
304 }
305 }
306}