use crate::backend::backend_manager::Communication; use crate::backend::csv_handler::ImportedData; use crate::backend::database_handler::{Tables,TableField}; use egui::{ComboBox, Context, Ui}; use egui_extras::{Column, Size, StripBuilder, TableBuilder}; use std::error::Error; use std::sync::Arc; use tokio::sync::Mutex; use tokio::sync::{mpsc::Sender, oneshot}; pub struct SpreadSheetWindow { sender: Sender, csv_data_handle: Arc>, db_table_data_handle: Arc>, is_file_loaded_receiver: Option>, is_file_loaded: bool, current_table: Option, is_table_described_receiver: Option>, is_table_described: bool, is_table_matched_with_headers: bool, } impl SpreadSheetWindow { pub fn default( sender: Sender, csv_data_handle: Arc>, db_table_data_handle: Arc>, ) -> SpreadSheetWindow { SpreadSheetWindow { sender, is_file_loaded_receiver: None, is_file_loaded: false, is_table_described_receiver: None, is_table_described: false, is_table_matched_with_headers: false, csv_data_handle, db_table_data_handle, current_table: None, } } } impl SpreadSheetWindow { pub fn show(&mut self, ctx: &Context, ui: &mut Ui, is_db_connection_verified: bool) { self.ui(ui, ctx, is_db_connection_verified); } fn ui(&mut self, ui: &mut Ui, ctx: &Context, is_db_connection_verified: bool) { /* Program Menu */ ui.horizontal_top(|ui| { if ui.button("Open File").clicked() { if let Some(path) = rfd::FileDialog::new() .add_filter("Spreadsheets", &["csv"]) .pick_file() { self.open_file(path.display().to_string()); }; } if ui.button("Save File").clicked() { println!("Saving file lol"); } //if ui.button("Current Working Table: ") }); /* if db isn't connected, don't allow imports */ if !is_db_connection_verified { return; }; /* Handle file drops */ ctx.input(|i| { if !i.raw.dropped_files.is_empty() { self.open_file( i.raw .dropped_files .clone() .get(0) .unwrap() .path .as_ref() .unwrap() .display() .to_string(), ); } }); self.check_file(); SpreadSheetWindow::preview_files_being_dropped(ctx); if !self.is_file_loaded { ui.centered_and_justified(|ui| ui.heading("Drag and drop or Open a file...")); } if self.is_file_loaded { ui.group(|ui| { StripBuilder::new(ui) //So the window doesn't grow with the innerts .size(Size::remainder().at_least(100.0)) // for the table .vertical(|mut strip| { strip.cell(|ui| { egui::ScrollArea::horizontal().show(ui, |ui| self.table_options(ui)); }); }); }); }; } fn table_options(&mut self, ui: &mut Ui) { /* Create table select option */ if let Ok(db_table_data) = &mut self.db_table_data_handle.try_lock() { let mut select_table = ComboBox::from_label("Select Table"); if let Some(table_index) = self.current_table { select_table = select_table .selected_text(&db_table_data.tables.get(table_index).unwrap().name); } ui.vertical(|ui| { select_table.show_ui(ui, |ui| { for (table_i, table) in db_table_data.tables.iter().enumerate() { ui.selectable_value( &mut self.current_table, Some(table_i.clone()), &table.name, ); } }); }); /* If a table is selected, try if it's fields are discovered */ if !self.is_table_described{ if let Some(table_i) = self.current_table { if db_table_data.tables.get(table_i).unwrap().fields.is_some() { self.is_table_described = true; } else if self.is_table_described_receiver.is_some(){ match self.is_table_described_receiver.as_mut().unwrap().try_recv(){ Ok(res) => {self.is_table_matched_with_headers = res}, Err(e) => println!("Failed receiving is_table_described, {}",e), } } else{ let (sender, receiver ) = oneshot::channel(); self.is_table_described_receiver = Some(receiver); self.sender .try_send(Communication::GetTableDescription(table_i, sender)) .unwrap_or_else(|e| println!("Failed asking to describe table, {}", e)); } } } } if self.is_table_described { self.table_builder(ui); } } fn table_builder(&mut self, ui: &mut Ui) { if let Ok(csv_data) = &mut self.csv_data_handle.try_lock() { if let Ok(db_table_data) = &mut self.db_table_data_handle.try_lock() { //ref to all fields in curr db table let mut curr_db_table_fields: &mut Vec = db_table_data.tables.get_mut(self.current_table.unwrap()).unwrap().fields.as_mut().unwrap(); let mut table = TableBuilder::new(ui) .striped(true) .cell_layout(egui::Layout::left_to_right(egui::Align::Center)); for _i in 0..csv_data.data.cols() { table = table.column(Column::auto().resizable(true).clip(false)); } /* If cols got prematched by name */ table .column(Column::remainder()) .min_scrolled_height(0.0) .header(20., |mut header| { for i in 0..csv_data.data.cols() { header.col(|ui| { let mut combo_box: ComboBox; if csv_data.are_headers { combo_box = ComboBox::new( i, csv_data.data.get(0, i).unwrap().data.clone(), ); } else { combo_box = ComboBox::new(i, ""); } //if any field is assinged to this combobox, show it's text, else "----" if let Some(selected_field) = curr_db_table_fields .iter() .find(|field| field.mapped_to_col == Some(i)) { combo_box = combo_box .selected_text(selected_field.description.field.clone()); } else{ combo_box = combo_box.selected_text("-----"); } /* When a Field gets attached to Col, */ combo_box.show_ui(ui, |ui| { for field in curr_db_table_fields.iter_mut() { if ui .selectable_value( &mut field.mapped_to_col, Some(i), field.description.field.clone(), ) .clicked() { self.is_table_described = false; match self.sender.try_send(Communication::TryParseCol(i)){ Ok(_) => { for cel in csv_data.data.iter_col_mut(i) { cel.curr_field_description = Some(field.description.clone()); } } Err(e) => println!("failed sending parsecol request, {}",e) } } } }); }); } }) .body(|body| { body.rows(15., csv_data.data.rows() -1, |row_index, mut row| { for curr_cell in csv_data.data.iter_row_mut(row_index+1) { /* If cell is bound to a field, color it's bg according to is_parsed */ row.col(|ui| { let mut err: Option> = None; if curr_cell.curr_field_description.is_some() { match &curr_cell.is_parsed{ Some(parse) => match parse{ Ok(_) => ui.style_mut().visuals.extreme_bg_color = egui::Color32::DARK_GREEN, Err(arc) => { err = Some(arc.clone()); ui.style_mut().visuals.extreme_bg_color = egui::Color32::DARK_RED; } } None => ui.style_mut().visuals.extreme_bg_color = egui::Color32::DARK_GRAY, } } if err.is_some(){ ui.text_edit_singleline(&mut curr_cell.data).on_hover_text(format!("{}", &err.unwrap())); } else{ ui.text_edit_singleline(&mut curr_cell.data); } if curr_cell.curr_field_description.is_some() { ui.reset_style(); } }); } }); }); } } } } impl SpreadSheetWindow { pub fn check_file(&mut self) { if let Some(receiver) = &mut self.is_file_loaded_receiver { match receiver.try_recv() { Ok(boole) => { if boole { self.is_file_loaded_receiver = None; } self.is_file_loaded = boole; } Err(e) => println!("Failed receiving load file callback, {}", e), } } } pub fn open_file(&mut self, path: String) { if !self.is_file_loaded_receiver.is_some() { let (sed, rec) = oneshot::channel(); self.sender .try_send(Communication::LoadImportFilePath(path, sed)) .unwrap_or_else(|err| println!("failed to send loadimportpath, {}", err)); self.is_file_loaded_receiver = Some(rec); } } pub fn preview_files_being_dropped(ctx: &egui::Context) { use egui::*; if !ctx.input(|i| i.raw.hovered_files.is_empty()) { let text = ctx.input(|i| { let text = "Dropping files...".to_owned(); text }); let painter = ctx.layer_painter(LayerId::new(Order::Foreground, Id::new("file_drop_target"))); let screen_rect = ctx.screen_rect(); painter.rect_filled(screen_rect, 0.0, Color32::from_black_alpha(192)); painter.text( screen_rect.center(), Align2::CENTER_CENTER, text, TextStyle::Heading.resolve(&ctx.style()), Color32::WHITE, ); } } }