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