Some small refactoring and moving around to make the code less monolitic, as well as some cargo fmt
This commit is contained in:
parent
5e3e30c30e
commit
26bf2380df
|
@ -68,10 +68,10 @@ impl Config {
|
|||
|
||||
pub fn with_data(mut self, data: HashMap<String, String>) -> Self {
|
||||
self.data = Some(data);
|
||||
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
|
||||
pub fn get_file(&self, file: &str) -> Option<&String> {
|
||||
if let Some(data) = &self.data {
|
||||
data.get(file)
|
||||
|
@ -86,6 +86,7 @@ impl Config {
|
|||
|
||||
pub fn get_main_file(&self) -> &String {
|
||||
let main_filename = &self.get_filename(&self.main_json);
|
||||
self.get_file(&main_filename).expect("Can't get main file {main_filename}")
|
||||
self.get_file(&main_filename)
|
||||
.expect("Can't get main file {main_filename}")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -76,9 +76,7 @@ impl Database {
|
|||
|
||||
pub fn from_config(config: &config::Config) -> Self {
|
||||
if config.data.is_some() {
|
||||
Database::from_str(
|
||||
&config.get_main_file()
|
||||
)
|
||||
Database::from_str(&config.get_main_file())
|
||||
} else {
|
||||
Database::from_file(&config.get_filename(&config.main_json))
|
||||
}
|
||||
|
|
|
@ -63,8 +63,8 @@ impl FlowProfile {
|
|||
}
|
||||
|
||||
pub fn from_str(str: &str, cutoff: Option<f64>) -> Self {
|
||||
let mut brew: Self = serde_json::from_str(&str)
|
||||
.unwrap_or_else(|_| panic!("Cannot deserialize file"));
|
||||
let mut brew: Self =
|
||||
serde_json::from_str(&str).unwrap_or_else(|_| panic!("Cannot deserialize file"));
|
||||
brew.cutoff = cutoff;
|
||||
|
||||
brew
|
||||
|
|
|
@ -34,7 +34,8 @@ pub fn database_plot_entry(brew: &Brew, config: &Config, database: &Database) ->
|
|||
if let Some(file) = &config.get_file(&flow_profile_filename) {
|
||||
flow_profile = FlowProfile::from_str(&file, None).preprocess_json();
|
||||
} else {
|
||||
flow_profile = FlowProfile::from_file(&flow_profile_filename, None).preprocess_json();
|
||||
flow_profile =
|
||||
FlowProfile::from_file(&flow_profile_filename, None).preprocess_json();
|
||||
}
|
||||
|
||||
if let Some(data_collection) = &flow_profile.data_collection {
|
||||
|
|
948
src/ui.rs
948
src/ui.rs
|
@ -2,8 +2,10 @@
|
|||
|
||||
use eframe::egui;
|
||||
|
||||
use egui::{plot::{Legend, Line, Plot}, Align, Layout, ProgressBar, Modifiers, Checkbox, collapsing_header, SelectableLabel};
|
||||
use log::debug;
|
||||
use egui::{
|
||||
plot::{Legend, Line, Plot},
|
||||
Align, Layout, ProgressBar,
|
||||
};
|
||||
use rfd::{AsyncFileDialog, AsyncMessageDialog};
|
||||
|
||||
use crate::{
|
||||
|
@ -14,7 +16,7 @@ use crate::{
|
|||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
io::{Read, Cursor}
|
||||
io::{Cursor, Read},
|
||||
};
|
||||
use std::{
|
||||
env,
|
||||
|
@ -34,10 +36,17 @@ struct LoadingData {
|
|||
|
||||
#[derive(Default)]
|
||||
pub struct Ui {
|
||||
// Program behaviour
|
||||
allowed_to_close: bool,
|
||||
continuous_mode: bool,
|
||||
modal: bool,
|
||||
|
||||
// Program status
|
||||
side_panel_expanded: bool,
|
||||
show_confirmation_dialog: bool,
|
||||
show_loading_screen: bool,
|
||||
|
||||
// Data loading
|
||||
loading_progress: Arc<Mutex<LoadingProgress>>,
|
||||
loading_data: Arc<Mutex<Option<LoadingData>>>,
|
||||
config_file: Arc<Mutex<Option<String>>>,
|
||||
|
@ -45,45 +54,15 @@ pub struct Ui {
|
|||
data_transfered: bool,
|
||||
files_provided: Arc<Mutex<bool>>,
|
||||
|
||||
// Data
|
||||
config: Config,
|
||||
database: Database,
|
||||
|
||||
brew_checkboxes: HashMap<String, bool>,
|
||||
bean_brew_marked: HashMap<String, bool>,
|
||||
|
||||
plot_entries: HashMap<String, PlotEntry>,
|
||||
|
||||
continuous_mode: bool,
|
||||
modal: bool,
|
||||
show_confirmation_dialog: bool,
|
||||
show_loading_screen: bool,
|
||||
}
|
||||
|
||||
impl Ui {
|
||||
pub fn get_selected_brew_uuids(&self) -> Vec<String> {
|
||||
self.brew_checkboxes
|
||||
.iter()
|
||||
.filter_map(|(uuid, checked)| match checked {
|
||||
true => Some(uuid.to_owned()),
|
||||
false => None,
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
}
|
||||
|
||||
pub fn get_selected_brews(&self) -> Vec<&Brew> {
|
||||
self.database
|
||||
.brews_for_uuids(&self.get_selected_brew_uuids())
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
*self = Self::default();
|
||||
}
|
||||
|
||||
pub fn clear_selection(&mut self) {
|
||||
for checkbox in &mut self.brew_checkboxes {
|
||||
*checkbox.1 = false;
|
||||
}
|
||||
}
|
||||
// User inputs
|
||||
brew_checkboxes: HashMap<String, bool>,
|
||||
bean_brew_marked: HashMap<String, bool>,
|
||||
}
|
||||
|
||||
impl eframe::App for Ui {
|
||||
|
@ -93,403 +72,16 @@ impl eframe::App for Ui {
|
|||
}
|
||||
|
||||
self.allowed_to_close = true;
|
||||
egui::TopBottomPanel::bottom("status_bar").show(ctx, |ui| {
|
||||
ui.columns(2, |_columns| {});
|
||||
});
|
||||
|
||||
egui::TopBottomPanel::top("dark_light").show(ctx, |ui| {
|
||||
ui.vertical(|ui| {
|
||||
egui::menu::bar(ui, |ui| {
|
||||
let load_button = ui.button(
|
||||
if !self.data_transfered {
|
||||
"Load Files"
|
||||
} else {
|
||||
"Reload Files"
|
||||
});
|
||||
Self::status_bar(ctx);
|
||||
|
||||
if load_button.hovered() {
|
||||
if self.show_loading_screen {
|
||||
egui::show_tooltip(ctx, egui::Id::new("reload_tooltip"), |ui| {
|
||||
ui.label("Loading is still in progress.\nTo reload, please wait until previous loading has finished!");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if load_button.clicked() {
|
||||
self.reset();
|
||||
|
||||
let config_file_arc = self.config_file.clone();
|
||||
let data_archive_arc = self.data_archive.clone();
|
||||
let files_provided_arc = self.files_provided.clone();
|
||||
|
||||
let file_dialog = async move {
|
||||
let config_message = AsyncMessageDialog::new()
|
||||
.set_title("Select config file")
|
||||
.set_description("Please select the config (.toml) file in the next dialog.\n(Default \"config.toml\")")
|
||||
.set_buttons(rfd::MessageButtons::Ok)
|
||||
.set_level(rfd::MessageLevel::Info)
|
||||
.show().await;
|
||||
|
||||
if !config_message { return }
|
||||
|
||||
let config_file = AsyncFileDialog::new()
|
||||
.add_filter("toml", &["toml"])
|
||||
.set_directory(
|
||||
match &env::current_dir() {
|
||||
Ok(path) => path.to_str(),
|
||||
Err(_) => None,
|
||||
}.unwrap_or_default()
|
||||
)
|
||||
.set_title("Config file [.toml]")
|
||||
.set_file_name("config.toml")
|
||||
.pick_file().await;
|
||||
|
||||
if config_file.is_none() { return }
|
||||
let config_raw = config_file.unwrap().read().await;
|
||||
|
||||
let archive_message = AsyncMessageDialog::new()
|
||||
.set_title("Select data archive")
|
||||
.set_description("Please select the data archive (.zip) in the next dialog.\n(Default \"config.toml\")\n\nWihin the zip archive there should be the main json file (e.g. \"Beanconqueror.json\"), as well as a \"brews\" folder, with exact paths specified in the previously selected config file.")
|
||||
.set_buttons(rfd::MessageButtons::Ok)
|
||||
.set_level(rfd::MessageLevel::Info)
|
||||
.show().await;
|
||||
|
||||
if !archive_message { return }
|
||||
|
||||
let archive_file = AsyncFileDialog::new()
|
||||
.add_filter("zip", &["zip"])
|
||||
.set_directory(
|
||||
match &env::current_dir() {
|
||||
Ok(path) => path.to_str(),
|
||||
Err(_) => None,
|
||||
}.unwrap_or_default()
|
||||
)
|
||||
.set_title("Data archive [.zip]")
|
||||
.set_file_name("beanconqueror.zip")
|
||||
.pick_file().await;
|
||||
|
||||
if archive_file.is_none() { return; }
|
||||
let archive_raw = archive_file.unwrap().read().await;
|
||||
|
||||
let config_content = String::from_utf8(config_raw)
|
||||
.expect("Can't convert provided config file to UTF-8");
|
||||
|
||||
let mut config_file_lock = config_file_arc.lock().unwrap();
|
||||
*config_file_lock = Some(config_content);
|
||||
|
||||
let mut data_archive: Option<HashMap<String, String>> = None;
|
||||
|
||||
if let Ok(mut zip) = zip::ZipArchive::new(Cursor::new(archive_raw)) {
|
||||
let mut unzip: HashMap<String, String> = HashMap::new();
|
||||
|
||||
for i in 0..zip.len() {
|
||||
if let Ok(mut file) = zip.by_index(i) {
|
||||
if file.is_file() {
|
||||
let mut content: String = String::new();
|
||||
if file.read_to_string(&mut content).is_ok() {
|
||||
if let Some(enclosed_name) = &file.enclosed_name() {
|
||||
if let Some(filename) = enclosed_name.to_str() {
|
||||
unzip.insert(filename.to_string(), content);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data_archive = Some(unzip);
|
||||
}
|
||||
|
||||
let mut data_archive_lock = data_archive_arc.lock().unwrap();
|
||||
*data_archive_lock = data_archive;
|
||||
|
||||
let mut files_provided_lock = files_provided_arc.lock().unwrap();
|
||||
*files_provided_lock = true;
|
||||
};
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
task::spawn(file_dialog);
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
wasm_bindgen_futures::spawn_local(file_dialog);
|
||||
}
|
||||
|
||||
if ui.button("Clear Selection").clicked() {
|
||||
self.clear_selection();
|
||||
}
|
||||
|
||||
ui.with_layout(Layout::right_to_left(Align::Min), |ui| {
|
||||
egui::widgets::global_dark_light_mode_buttons(ui);
|
||||
ui.heading("RustyBeans");
|
||||
});
|
||||
});
|
||||
})
|
||||
});
|
||||
self.menu_bar(ctx);
|
||||
|
||||
if self.modal {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
ui.centered_and_justified(|ui| {
|
||||
if self.show_loading_screen {
|
||||
let loading_progress_lock = self.loading_progress.lock().unwrap();
|
||||
|
||||
ui.add(
|
||||
ProgressBar::new(loading_progress_lock.percentage.to_owned()).text(
|
||||
format!(
|
||||
"{:.0}% | Brew [{}/{}]",
|
||||
loading_progress_lock.percentage * 100.0,
|
||||
loading_progress_lock.current,
|
||||
loading_progress_lock.total
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if loading_progress_lock.finished {
|
||||
self.continuous_mode = false;
|
||||
self.modal = false;
|
||||
self.show_loading_screen = false;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
if self.show_confirmation_dialog {
|
||||
// Show confirmation dialog:
|
||||
ui.separator();
|
||||
ui.vertical(|ui| {
|
||||
ui.label("Really quit?");
|
||||
ui.horizontal(|ui| {
|
||||
if ui.button("Yes").clicked() {
|
||||
self.allowed_to_close = true;
|
||||
frame.close();
|
||||
}
|
||||
|
||||
if ui.button("No").clicked() {
|
||||
self.modal = false;
|
||||
self.show_confirmation_dialog = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
self.loading_screen(ctx, frame);
|
||||
} else {
|
||||
egui::SidePanel::left("selection_panel")
|
||||
.resizable(true)
|
||||
.show_animated(ctx, self.side_panel_expanded, |ui| {
|
||||
ui.vertical_centered_justified(|ui| {
|
||||
ui.horizontal_centered(|ui| {
|
||||
ui.vertical(|ui| {
|
||||
if self.data_transfered {
|
||||
ui.heading("Beans with Brews");
|
||||
|
||||
egui::ScrollArea::vertical()
|
||||
.id_source("beans_scroll")
|
||||
.show(ui, |ui| {
|
||||
for bean in &self.database.beans {
|
||||
ui.horizontal(|ui| {
|
||||
if let Some(brew_marked) = self.bean_brew_marked.get_mut(&bean.config.uuid) {
|
||||
let id = bean.name_with_date();
|
||||
|
||||
ui.collapsing(id, |ui| {
|
||||
ui.vertical(|ui| {
|
||||
let mut marked: Option<bool> = None;
|
||||
let mut flip_all: Option<bool> = None;
|
||||
|
||||
let brews = self.database.brews_for_bean(&bean);
|
||||
for brew in &brews {
|
||||
ui.horizontal(|ui| {
|
||||
if let Some(checked) = self
|
||||
.brew_checkboxes
|
||||
.get_mut(&brew.config.uuid)
|
||||
{
|
||||
let response = ui.toggle_value(
|
||||
checked,
|
||||
brew.date_time(&self.database),
|
||||
);
|
||||
|
||||
if response.clicked_by(egui::PointerButton::Primary) {
|
||||
if ctx.input(|i| i.modifiers.command) {
|
||||
flip_all = Some(*checked);
|
||||
*checked = !*checked;
|
||||
}
|
||||
}
|
||||
|
||||
if *checked {
|
||||
marked = Some(true);
|
||||
} else if marked.is_none() {
|
||||
marked = Some(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(value) = flip_all {
|
||||
self.brew_checkboxes.iter_mut().filter(|brew_checkbox| {
|
||||
brews.iter().any(|&brew| &brew.config.uuid == brew_checkbox.0)
|
||||
}).for_each(|brew_checkbox| {
|
||||
*brew_checkbox.1 = value;
|
||||
});
|
||||
|
||||
marked = Some(value);
|
||||
}
|
||||
|
||||
match marked {
|
||||
Some(marked) => *brew_marked = marked,
|
||||
None => *brew_marked = false,
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
ui.with_layout(Layout::left_to_right(Align::Center), |ui| {
|
||||
ui.with_layout(
|
||||
Layout::left_to_right(Align::Center)
|
||||
.with_cross_align(Align::Center)
|
||||
.with_cross_justify(true),
|
||||
|ui| {
|
||||
if ui
|
||||
.button(match self.side_panel_expanded {
|
||||
true => "<",
|
||||
false => ">",
|
||||
})
|
||||
.clicked()
|
||||
{
|
||||
self.side_panel_expanded = !self.side_panel_expanded;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
ui.with_layout(
|
||||
Layout::top_down(Align::Center).with_main_align(Align::Center),
|
||||
|ui| {
|
||||
if *self.files_provided.lock().unwrap() {
|
||||
let loading_finished: bool;
|
||||
{
|
||||
loading_finished = self.loading_progress.lock().unwrap().finished;
|
||||
}
|
||||
if !loading_finished {
|
||||
self.continuous_mode = true;
|
||||
self.modal = true;
|
||||
self.show_loading_screen = true;
|
||||
self.data_transfered = false;
|
||||
|
||||
let config_file_arc = self.config_file.clone();
|
||||
let data_archive_arc = self.data_archive.clone();
|
||||
let loading_data_arc = self.loading_data.clone();
|
||||
let loading_progress_arc = self.loading_progress.clone();
|
||||
let ctx_clone = ctx.clone();
|
||||
|
||||
let data_loader = async move {
|
||||
let ctx = ctx_clone;
|
||||
|
||||
let mut loading_data = LoadingData::default();
|
||||
|
||||
let mut config_file_lock = config_file_arc.lock().unwrap();
|
||||
let mut data_archive_lock = data_archive_arc.lock().unwrap();
|
||||
|
||||
loading_data.config = Config::from_str(&config_file_lock.take().unwrap()).with_data(data_archive_lock.take().unwrap());
|
||||
|
||||
loading_data.database = Database::from_config(&loading_data.config);
|
||||
|
||||
let mut checkboxes = HashMap::new();
|
||||
|
||||
for brew in &loading_data.database.brews {
|
||||
checkboxes.insert(brew.config.uuid.to_owned(), false);
|
||||
}
|
||||
|
||||
loading_data.brew_checkboxes = checkboxes;
|
||||
|
||||
let mut brew_marked = HashMap::new();
|
||||
|
||||
for bean in &loading_data.database.beans {
|
||||
brew_marked.insert(bean.config.uuid.to_owned(), false);
|
||||
}
|
||||
|
||||
loading_data.bean_brew_marked = brew_marked;
|
||||
|
||||
loading_data.plot_entries = plot::database_plot_entries(
|
||||
loading_data
|
||||
.database
|
||||
.brews
|
||||
.iter()
|
||||
.map(|brew| brew)
|
||||
.collect(),
|
||||
&loading_data.config,
|
||||
&loading_data.database,
|
||||
Some(loading_progress_arc),
|
||||
);
|
||||
|
||||
let mut loading_data_lock =
|
||||
loading_data_arc.lock().unwrap();
|
||||
*loading_data_lock = Some(loading_data);
|
||||
|
||||
ctx.request_repaint();
|
||||
};
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
task::spawn(data_loader);
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
wasm_bindgen_futures::spawn_local(data_loader);
|
||||
} else {
|
||||
if !self.data_transfered {
|
||||
self.side_panel_expanded = true;
|
||||
|
||||
// println!("loading_data: {:?}", &self.loading_data);
|
||||
let mut loading_data_lock =
|
||||
self.loading_data.lock().unwrap();
|
||||
// println!("loading_data_lock: {:?}", loading_data_lock);
|
||||
let loading_data = loading_data_lock.take().unwrap();
|
||||
|
||||
self.config = loading_data.config;
|
||||
self.database = loading_data.database;
|
||||
self.brew_checkboxes = loading_data.brew_checkboxes;
|
||||
self.bean_brew_marked = loading_data.bean_brew_marked;
|
||||
self.plot_entries = loading_data.plot_entries;
|
||||
|
||||
self.data_transfered = true;
|
||||
} else {
|
||||
Plot::new("Combined Chart of selected brews")
|
||||
.legend(Legend::default())
|
||||
.label_formatter(|name, value| {
|
||||
let coord = format!("{:.1}g @ {:.1}s", value.y, value.x);
|
||||
if !name.is_empty() {
|
||||
format!("{} [{}]", coord, name)
|
||||
} else {
|
||||
coord
|
||||
}
|
||||
})
|
||||
.show(ui, |plot_ui| {
|
||||
for brew_checkbox in &self.brew_checkboxes {
|
||||
if *brew_checkbox.1 {
|
||||
if let Some(plot_points) =
|
||||
self.plot_entries.get(brew_checkbox.0)
|
||||
{
|
||||
plot_ui.line(
|
||||
Line::new(plot_points_to_owned(
|
||||
&plot_points.1,
|
||||
))
|
||||
.name(plot_points.0.to_owned()),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
self.selection_panel(ctx);
|
||||
self.main_panel(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -500,3 +92,501 @@ impl eframe::App for Ui {
|
|||
self.allowed_to_close
|
||||
}
|
||||
}
|
||||
|
||||
impl Ui {
|
||||
// General methods
|
||||
|
||||
/// Get all UUIDs of all selected Brews
|
||||
pub fn _get_selected_brew_uuids(&self) -> Vec<String> {
|
||||
self.brew_checkboxes
|
||||
.iter()
|
||||
.filter_map(|(uuid, checked)| match checked {
|
||||
true => Some(uuid.to_owned()),
|
||||
false => None,
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
}
|
||||
|
||||
/// Get all selected brews
|
||||
pub fn _get_selected_brews(&self) -> Vec<&Brew> {
|
||||
self.database
|
||||
.brews_for_uuids(&self._get_selected_brew_uuids())
|
||||
}
|
||||
|
||||
/// Reset the whole program
|
||||
pub fn reset(&mut self) {
|
||||
*self = Self::default();
|
||||
}
|
||||
|
||||
/// Uncheck all selected Brews
|
||||
pub fn clear_selection(&mut self) {
|
||||
for checkbox in &mut self.brew_checkboxes {
|
||||
*checkbox.1 = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Ui data metods
|
||||
|
||||
/// Load data from user
|
||||
fn load_data(&mut self, ctx: &egui::Context) {
|
||||
self.continuous_mode = true;
|
||||
self.modal = true;
|
||||
self.show_loading_screen = true;
|
||||
self.data_transfered = false;
|
||||
|
||||
let config_file_arc = self.config_file.clone();
|
||||
let data_archive_arc = self.data_archive.clone();
|
||||
let loading_data_arc = self.loading_data.clone();
|
||||
let loading_progress_arc = self.loading_progress.clone();
|
||||
let ctx_clone = ctx.clone();
|
||||
|
||||
let data_loader = async move {
|
||||
let ctx = ctx_clone;
|
||||
|
||||
let mut loading_data = LoadingData::default();
|
||||
|
||||
let mut config_file_lock = config_file_arc.lock().unwrap();
|
||||
let mut data_archive_lock = data_archive_arc.lock().unwrap();
|
||||
|
||||
loading_data.config = Config::from_str(&config_file_lock.take().unwrap())
|
||||
.with_data(data_archive_lock.take().unwrap());
|
||||
|
||||
loading_data.database = Database::from_config(&loading_data.config);
|
||||
|
||||
let mut checkboxes = HashMap::new();
|
||||
|
||||
for brew in &loading_data.database.brews {
|
||||
checkboxes.insert(brew.config.uuid.to_owned(), false);
|
||||
}
|
||||
|
||||
loading_data.brew_checkboxes = checkboxes;
|
||||
|
||||
let mut brew_marked = HashMap::new();
|
||||
|
||||
for bean in &loading_data.database.beans {
|
||||
brew_marked.insert(bean.config.uuid.to_owned(), false);
|
||||
}
|
||||
|
||||
loading_data.bean_brew_marked = brew_marked;
|
||||
|
||||
loading_data.plot_entries = plot::database_plot_entries(
|
||||
loading_data
|
||||
.database
|
||||
.brews
|
||||
.iter()
|
||||
.map(|brew| brew)
|
||||
.collect(),
|
||||
&loading_data.config,
|
||||
&loading_data.database,
|
||||
Some(loading_progress_arc),
|
||||
);
|
||||
|
||||
let mut loading_data_lock = loading_data_arc.lock().unwrap();
|
||||
*loading_data_lock = Some(loading_data);
|
||||
|
||||
ctx.request_repaint();
|
||||
};
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
task::spawn(data_loader);
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
wasm_bindgen_futures::spawn_local(data_loader);
|
||||
}
|
||||
|
||||
/// Transfer the acquired data into the "native" fields
|
||||
fn transfer_data(&mut self) {
|
||||
self.side_panel_expanded = true;
|
||||
|
||||
// println!("loading_data: {:?}", &self.loading_data);
|
||||
let mut loading_data_lock = self.loading_data.lock().unwrap();
|
||||
// println!("loading_data_lock: {:?}", loading_data_lock);
|
||||
let loading_data = loading_data_lock.take().unwrap();
|
||||
|
||||
self.config = loading_data.config;
|
||||
self.database = loading_data.database;
|
||||
self.brew_checkboxes = loading_data.brew_checkboxes;
|
||||
self.bean_brew_marked = loading_data.bean_brew_marked;
|
||||
self.plot_entries = loading_data.plot_entries;
|
||||
|
||||
self.data_transfered = true;
|
||||
}
|
||||
|
||||
/// Display file dialog that acquires the data and stores it asynchronously
|
||||
async fn load_file_dialog(
|
||||
config_file_arc: Arc<Mutex<Option<String>>>,
|
||||
data_archive_arc: Arc<Mutex<Option<HashMap<String, String>>>>,
|
||||
files_provided_arc: Arc<Mutex<bool>>,
|
||||
) {
|
||||
let config_message = AsyncMessageDialog::new()
|
||||
.set_title("Select config file")
|
||||
.set_description("Please select the config (.toml) file in the next dialog.\n(Default \"config.toml\")")
|
||||
.set_buttons(rfd::MessageButtons::Ok)
|
||||
.set_level(rfd::MessageLevel::Info)
|
||||
.show().await;
|
||||
|
||||
if !config_message {
|
||||
return;
|
||||
}
|
||||
|
||||
let config_file = AsyncFileDialog::new()
|
||||
.add_filter("toml", &["toml"])
|
||||
.set_directory(
|
||||
match &env::current_dir() {
|
||||
Ok(path) => path.to_str(),
|
||||
Err(_) => None,
|
||||
}
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
.set_title("Config file [.toml]")
|
||||
.set_file_name("config.toml")
|
||||
.pick_file()
|
||||
.await;
|
||||
|
||||
if config_file.is_none() {
|
||||
return;
|
||||
}
|
||||
let config_raw = config_file.unwrap().read().await;
|
||||
|
||||
let archive_message = AsyncMessageDialog::new()
|
||||
.set_title("Select data archive")
|
||||
.set_description("Please select the data archive (.zip) in the next dialog.\n\
|
||||
(Default \"config.toml\")\n\n\
|
||||
Wihin the zip archive there should be the main json file (e.g. \"Beanconqueror.json\"),\
|
||||
as well as a \"brews\" folder, with exact paths specified in the previously selected config file.")
|
||||
.set_buttons(rfd::MessageButtons::Ok)
|
||||
.set_level(rfd::MessageLevel::Info)
|
||||
.show().await;
|
||||
|
||||
if !archive_message {
|
||||
return;
|
||||
}
|
||||
|
||||
let archive_file = AsyncFileDialog::new()
|
||||
.add_filter("zip", &["zip"])
|
||||
.set_directory(
|
||||
match &env::current_dir() {
|
||||
Ok(path) => path.to_str(),
|
||||
Err(_) => None,
|
||||
}
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
.set_title("Data archive [.zip]")
|
||||
.set_file_name("beanconqueror.zip")
|
||||
.pick_file()
|
||||
.await;
|
||||
|
||||
if archive_file.is_none() {
|
||||
return;
|
||||
}
|
||||
let archive_raw = archive_file.unwrap().read().await;
|
||||
|
||||
let config_content =
|
||||
String::from_utf8(config_raw).expect("Can't convert provided config file to UTF-8");
|
||||
|
||||
let mut config_file_lock = config_file_arc.lock().unwrap();
|
||||
*config_file_lock = Some(config_content);
|
||||
|
||||
let mut data_archive: Option<HashMap<String, String>> = None;
|
||||
|
||||
if let Ok(mut zip) = zip::ZipArchive::new(Cursor::new(archive_raw)) {
|
||||
let mut unzip: HashMap<String, String> = HashMap::new();
|
||||
|
||||
for i in 0..zip.len() {
|
||||
if let Ok(mut file) = zip.by_index(i) {
|
||||
if file.is_file() {
|
||||
let mut content: String = String::new();
|
||||
if file.read_to_string(&mut content).is_ok() {
|
||||
if let Some(enclosed_name) = &file.enclosed_name() {
|
||||
if let Some(filename) = enclosed_name.to_str() {
|
||||
unzip.insert(filename.to_string(), content);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data_archive = Some(unzip);
|
||||
}
|
||||
|
||||
let mut data_archive_lock = data_archive_arc.lock().unwrap();
|
||||
*data_archive_lock = data_archive;
|
||||
|
||||
let mut files_provided_lock = files_provided_arc.lock().unwrap();
|
||||
*files_provided_lock = true;
|
||||
}
|
||||
|
||||
// Ui component metods
|
||||
fn status_bar(ctx: &egui::Context) {
|
||||
egui::TopBottomPanel::bottom("status_bar").show(ctx, |ui| {
|
||||
ui.columns(2, |_columns| {});
|
||||
});
|
||||
}
|
||||
|
||||
/// Draws a menu bar
|
||||
fn menu_bar(&mut self, ctx: &egui::Context) {
|
||||
egui::TopBottomPanel::top("menu_bar").show(ctx, |ui| {
|
||||
ui.vertical(|ui| {
|
||||
egui::menu::bar(ui, |ui| {
|
||||
// Load button
|
||||
self.load_button(ui, ctx);
|
||||
|
||||
// Clear selection button
|
||||
if ui.button("Clear Selection").clicked() {
|
||||
self.clear_selection();
|
||||
}
|
||||
|
||||
// Program title with dark/light mode switch
|
||||
ui.with_layout(Layout::right_to_left(Align::Min), |ui| {
|
||||
egui::widgets::global_dark_light_mode_buttons(ui);
|
||||
ui.heading("RustyBeans");
|
||||
});
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/// Draws the load button for the data
|
||||
fn load_button(&mut self, ui: &mut egui::Ui, ctx: &egui::Context) {
|
||||
// Display (Re)load files depending on the loading state
|
||||
let load_button = ui.button(if !self.data_transfered {
|
||||
"Load Files"
|
||||
} else {
|
||||
"Reload Files"
|
||||
});
|
||||
|
||||
// If still in progress, show warning on button hover
|
||||
if load_button.hovered() {
|
||||
if self.show_loading_screen {
|
||||
egui::show_tooltip(ctx, egui::Id::new("reload_tooltip"), |ui| {
|
||||
ui.label("Loading is still in progress.\nTo reload, please wait until previous loading has finished!");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Data loading procedure
|
||||
if load_button.clicked() {
|
||||
self.reset();
|
||||
|
||||
let config_file_arc = self.config_file.clone();
|
||||
let data_archive_arc = self.data_archive.clone();
|
||||
let files_provided_arc = self.files_provided.clone();
|
||||
|
||||
// File dialog that handles data acquisition
|
||||
let file_dialog =
|
||||
Self::load_file_dialog(config_file_arc, data_archive_arc, files_provided_arc);
|
||||
|
||||
// On Desktop
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
task::spawn(file_dialog);
|
||||
|
||||
// On web
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
wasm_bindgen_futures::spawn_local(file_dialog);
|
||||
}
|
||||
}
|
||||
|
||||
/// Draws the loading screen with the progress bar
|
||||
fn loading_screen(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
ui.centered_and_justified(|ui| {
|
||||
if self.show_loading_screen {
|
||||
let loading_progress_lock = self.loading_progress.lock().unwrap();
|
||||
|
||||
ui.add(
|
||||
ProgressBar::new(loading_progress_lock.percentage.to_owned()).text(
|
||||
format!(
|
||||
"{:.0}% | Brew [{}/{}]",
|
||||
loading_progress_lock.percentage * 100.0,
|
||||
loading_progress_lock.current,
|
||||
loading_progress_lock.total
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if loading_progress_lock.finished {
|
||||
self.continuous_mode = false;
|
||||
self.modal = false;
|
||||
self.show_loading_screen = false;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
if self.show_confirmation_dialog {
|
||||
// Show confirmation dialog:
|
||||
ui.separator();
|
||||
ui.vertical(|ui| {
|
||||
ui.label("Really quit?");
|
||||
ui.horizontal(|ui| {
|
||||
if ui.button("Yes").clicked() {
|
||||
self.allowed_to_close = true;
|
||||
frame.close();
|
||||
}
|
||||
|
||||
if ui.button("No").clicked() {
|
||||
self.modal = false;
|
||||
self.show_confirmation_dialog = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// Draw Bean -> Brew selection panel
|
||||
fn selection_panel(&mut self, ctx: &egui::Context) {
|
||||
egui::SidePanel::left("selection_panel")
|
||||
.resizable(true)
|
||||
.show_animated(ctx, self.side_panel_expanded, |ui| {
|
||||
ui.vertical_centered_justified(|ui| {
|
||||
ui.horizontal_centered(|ui| {
|
||||
ui.vertical(|ui| {
|
||||
if self.data_transfered {
|
||||
ui.heading("Beans with Brews");
|
||||
|
||||
egui::ScrollArea::vertical()
|
||||
.id_source("beans_scroll")
|
||||
.show(ui, |ui| {
|
||||
for bean in &self.database.beans {
|
||||
ui.horizontal(|ui| {
|
||||
if let Some(brew_marked) = self.bean_brew_marked.get_mut(&bean.config.uuid) {
|
||||
let id = bean.name_with_date();
|
||||
|
||||
ui.collapsing(id, |ui| {
|
||||
ui.vertical(|ui| {
|
||||
let mut marked: Option<bool> = None;
|
||||
let mut flip_all: Option<bool> = None;
|
||||
|
||||
let brews = self.database.brews_for_bean(&bean);
|
||||
for brew in &brews {
|
||||
ui.horizontal(|ui| {
|
||||
if let Some(checked) = self
|
||||
.brew_checkboxes
|
||||
.get_mut(&brew.config.uuid)
|
||||
{
|
||||
let response = ui.toggle_value(
|
||||
checked,
|
||||
brew.date_time(&self.database),
|
||||
);
|
||||
|
||||
if response.clicked_by(egui::PointerButton::Primary) {
|
||||
if ctx.input(|i| i.modifiers.command) {
|
||||
flip_all = Some(*checked);
|
||||
*checked = !*checked;
|
||||
}
|
||||
}
|
||||
|
||||
if *checked {
|
||||
marked = Some(true);
|
||||
} else if marked.is_none() {
|
||||
marked = Some(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(value) = flip_all {
|
||||
self.brew_checkboxes.iter_mut().filter(|brew_checkbox| {
|
||||
brews.iter().any(|&brew| &brew.config.uuid == brew_checkbox.0)
|
||||
}).for_each(|brew_checkbox| {
|
||||
*brew_checkbox.1 = value;
|
||||
});
|
||||
|
||||
marked = Some(value);
|
||||
}
|
||||
|
||||
match marked {
|
||||
Some(marked) => *brew_marked = marked,
|
||||
None => *brew_marked = false,
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// Draw main panel, depending on loading status
|
||||
fn main_panel(&mut self, ctx: &egui::Context) {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
ui.with_layout(Layout::left_to_right(Align::Center), |ui| {
|
||||
self.side_panel(ui);
|
||||
|
||||
ui.with_layout(
|
||||
Layout::top_down(Align::Center).with_main_align(Align::Center),
|
||||
|ui| {
|
||||
if *self.files_provided.lock().unwrap() {
|
||||
let loading_finished: bool;
|
||||
{
|
||||
loading_finished = self.loading_progress.lock().unwrap().finished;
|
||||
}
|
||||
if !loading_finished {
|
||||
self.load_data(ctx);
|
||||
} else {
|
||||
if !self.data_transfered {
|
||||
self.transfer_data();
|
||||
} else {
|
||||
self.plot(ui);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// Draw side panel
|
||||
fn side_panel(&mut self, ui: &mut egui::Ui) {
|
||||
ui.with_layout(
|
||||
Layout::left_to_right(Align::Center)
|
||||
.with_cross_align(Align::Center)
|
||||
.with_cross_justify(true),
|
||||
|ui| {
|
||||
if ui
|
||||
.button(match self.side_panel_expanded {
|
||||
true => "<",
|
||||
false => ">",
|
||||
})
|
||||
.clicked()
|
||||
{
|
||||
self.side_panel_expanded = !self.side_panel_expanded;
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Draw plot
|
||||
fn plot(&self, ui: &mut egui::Ui) {
|
||||
Plot::new("Combined Chart of selected brews")
|
||||
.legend(Legend::default())
|
||||
.label_formatter(|name, value| {
|
||||
let coord = format!("{:.1}g @ {:.1}s", value.y, value.x);
|
||||
if !name.is_empty() {
|
||||
format!("{} [{}]", coord, name)
|
||||
} else {
|
||||
coord
|
||||
}
|
||||
})
|
||||
.show(ui, |plot_ui| {
|
||||
for brew_checkbox in &self.brew_checkboxes {
|
||||
if *brew_checkbox.1 {
|
||||
if let Some(plot_points) = self.plot_entries.get(brew_checkbox.0) {
|
||||
plot_ui.line(
|
||||
Line::new(plot_points_to_owned(&plot_points.1))
|
||||
.name(plot_points.0.to_owned()),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue