Working GUI with async load and progressbar
This commit is contained in:
parent
1fd5833d19
commit
710c9400a6
|
@ -2265,6 +2265,15 @@ dependencies = [
|
|||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "poll-promise"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcf2a02372dfae23c9c01267fb296b8a3413bb4e45fbd589c3ac73c6dcfbb305"
|
||||
dependencies = [
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "polling"
|
||||
version = "2.5.2"
|
||||
|
@ -2472,6 +2481,7 @@ dependencies = [
|
|||
"notify-rust",
|
||||
"palette",
|
||||
"plotly",
|
||||
"poll-promise",
|
||||
"rfd",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
|
|
@ -3,7 +3,7 @@ use serde::Deserialize;
|
|||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[derive(Deserialize, Debug, Default)]
|
||||
pub struct Shot {
|
||||
pub filename: Option<String>,
|
||||
pub json: Option<String>,
|
||||
|
@ -12,7 +12,7 @@ pub struct Shot {
|
|||
pub disable: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[derive(Deserialize, Debug, Default)]
|
||||
pub struct Chart {
|
||||
pub title: String,
|
||||
pub shots: Vec<u64>,
|
||||
|
@ -22,7 +22,7 @@ pub struct Chart {
|
|||
pub max_flow: u64,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[derive(Deserialize, Debug, Default)]
|
||||
pub struct Config {
|
||||
pub shots: HashMap<String, Shot>,
|
||||
pub charts: HashMap<String, Chart>,
|
||||
|
|
|
@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};
|
|||
use std::fs;
|
||||
|
||||
use crate::config;
|
||||
use crate::time::{unix_to_human_date, unix_to_human_time};
|
||||
use crate::time::{unix_to_human_date, unix_to_human_date_time, unix_to_human_time};
|
||||
|
||||
/// Structs for handling the Beanconqueror database "Beanconqueror.json" export data
|
||||
|
||||
|
@ -84,20 +84,30 @@ impl Database {
|
|||
pub fn bean_names_with_uuids(&self) -> Vec<(String, String)> {
|
||||
self.beans
|
||||
.iter()
|
||||
.map(|bean| (bean.name_with_date(), bean.config.uuid.clone()))
|
||||
.map(|bean| (bean.name_with_date(), bean.config.uuid.to_owned()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn bean_for_uuid(&self, uuid: &str) -> Option<&Bean> {
|
||||
self.beans
|
||||
.iter()
|
||||
.find(|bean| bean.config.uuid == uuid.to_string())
|
||||
.find(|bean| bean.config.uuid == uuid.to_owned())
|
||||
}
|
||||
|
||||
pub fn brew_for_uuid(&self, uuid: &str) -> Option<&Brew> {
|
||||
self.brews
|
||||
.iter()
|
||||
.find(|brew| brew.config.uuid == uuid.to_string())
|
||||
.find(|brew| brew.config.uuid == uuid.to_owned())
|
||||
}
|
||||
|
||||
pub fn brews_for_uuids(&self, uuids: &Vec<String>) -> Vec<&Brew> {
|
||||
self.brews
|
||||
.iter()
|
||||
.filter_map(|brew| match uuids.contains(&brew.config.uuid) {
|
||||
true => Some(brew),
|
||||
false => None,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn bean_for_brew(&self, brew: &Brew) -> Option<&Bean> {
|
||||
|
@ -120,14 +130,34 @@ impl Bean {
|
|||
pub fn name_with_date(&self) -> String {
|
||||
format!(
|
||||
"{} from {}",
|
||||
self.name.clone(),
|
||||
unix_to_human_date(self.config.unix_timestamp.clone())
|
||||
self.name.to_owned(),
|
||||
unix_to_human_date(self.config.unix_timestamp.to_owned())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Brew {
|
||||
pub fn name_with_time(&self) -> String {
|
||||
unix_to_human_time(self.config.unix_timestamp.clone())
|
||||
pub fn time(&self) -> String {
|
||||
unix_to_human_time(self.config.unix_timestamp.to_owned())
|
||||
}
|
||||
|
||||
pub fn date_time(&self, database: &Database) -> String {
|
||||
if let Some(bean) = &database.bean_for_uuid(&self.bean) {
|
||||
unix_to_human_date_time(self.config.unix_timestamp.to_owned())
|
||||
} else {
|
||||
String::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn date_time_with_bean(&self, database: &Database) -> String {
|
||||
if let Some(bean) = &database.bean_for_uuid(&self.bean) {
|
||||
format!(
|
||||
"{} ({})",
|
||||
unix_to_human_date_time(self.config.unix_timestamp.to_owned()),
|
||||
bean.name_with_date()
|
||||
)
|
||||
} else {
|
||||
String::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -99,10 +99,10 @@ impl FlowProfile {
|
|||
ref actual_weight,
|
||||
..
|
||||
}| {
|
||||
let deltatime = deltatime(str_to_naivetime(timestamp.as_str()), reference_time);
|
||||
let deltatime = deltatime(str_to_naivetime(×tamp), reference_time);
|
||||
let std_duration = deltatime.to_std().unwrap();
|
||||
|
||||
(std_duration.as_secs_f64(), actual_weight.clone())
|
||||
(std_duration.as_secs_f64(), actual_weight.to_owned())
|
||||
},
|
||||
)
|
||||
.collect();
|
||||
|
@ -116,10 +116,10 @@ impl FlowProfile {
|
|||
ref value,
|
||||
..
|
||||
}| {
|
||||
let deltatime = deltatime(str_to_naivetime(timestamp.as_str()), reference_time);
|
||||
let deltatime = deltatime(str_to_naivetime(×tamp), reference_time);
|
||||
let std_duration = deltatime.to_std().unwrap();
|
||||
|
||||
(std_duration.as_secs_f64(), value.clone())
|
||||
(std_duration.as_secs_f64(), value.to_owned())
|
||||
},
|
||||
)
|
||||
.collect();
|
||||
|
@ -133,10 +133,10 @@ impl FlowProfile {
|
|||
ref flow_value,
|
||||
..
|
||||
}| {
|
||||
let deltatime = deltatime(str_to_naivetime(timestamp.as_str()), reference_time);
|
||||
let deltatime = deltatime(str_to_naivetime(×tamp), reference_time);
|
||||
let std_duration = deltatime.to_std().unwrap();
|
||||
|
||||
(std_duration.as_secs_f64(), flow_value.clone())
|
||||
(std_duration.as_secs_f64(), flow_value.to_owned())
|
||||
},
|
||||
)
|
||||
.collect();
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
mod config;
|
||||
mod ui;
|
||||
mod database;
|
||||
mod flow_profile;
|
||||
mod plot;
|
||||
mod sheets;
|
||||
mod time;
|
||||
mod ui;
|
||||
|
||||
use ui::Ui;
|
||||
|
||||
|
@ -16,7 +16,7 @@ use std::panic;
|
|||
|
||||
use eframe::egui;
|
||||
|
||||
fn main() -> Result<(), eframe::Error>{
|
||||
fn main() -> Result<(), eframe::Error> {
|
||||
panic::set_hook(Box::new(console_error_panic_hook::hook));
|
||||
|
||||
// generate_plots();
|
||||
|
|
87
src/plot.rs
87
src/plot.rs
|
@ -1,11 +1,12 @@
|
|||
use crate::config::Config;
|
||||
use crate::database::Database;
|
||||
use crate::database::{Brew, Database};
|
||||
use crate::flow_profile::FlowProfile;
|
||||
use crate::sheets::load_data;
|
||||
use crate::time::{unix_to_human_date, unix_to_human_date_time, unix_to_machine_date};
|
||||
|
||||
use dialoguer::{theme::ColorfulTheme, MultiSelect};
|
||||
|
||||
use egui::plot::{PlotPoint, PlotPoints};
|
||||
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
|
||||
|
||||
use notify_rust::Notification;
|
||||
|
@ -15,7 +16,20 @@ use plotly::{
|
|||
Layout, Plot, Scatter,
|
||||
};
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
pub type RawPlotPoints = Vec<PlotPoint>;
|
||||
pub type PlotEntry = (String, RawPlotPoints);
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct LoadingProgress {
|
||||
pub curr_name: String,
|
||||
pub current: usize,
|
||||
pub total: usize,
|
||||
pub percentage: f32,
|
||||
}
|
||||
|
||||
pub fn generate_plots() -> Vec<(String, Plot)> {
|
||||
let config_file = fs::read_to_string("config.toml").expect("Can't read config.toml");
|
||||
|
@ -83,6 +97,61 @@ pub fn generate_plots() -> Vec<(String, Plot)> {
|
|||
result
|
||||
}
|
||||
|
||||
pub fn plot_points_to_owned(plot_points: &RawPlotPoints) -> PlotPoints {
|
||||
PlotPoints::Owned(plot_points.to_owned())
|
||||
}
|
||||
|
||||
pub fn database_plot_entry(brew: &Brew, config: &Config, database: &Database) -> Option<PlotEntry> {
|
||||
let mut plot_points: Option<(String, RawPlotPoints)> = None;
|
||||
if let Some(flow_profile) = &brew.flow_profile {
|
||||
if !&flow_profile.is_empty() {
|
||||
let brew_title = brew.date_time_with_bean(&database);
|
||||
|
||||
let flow_profile =
|
||||
FlowProfile::from_file(&format!("{}/{}", &config.data_dir, &flow_profile), None)
|
||||
.preprocess_json();
|
||||
|
||||
if let Some(data_collection) = &flow_profile.data_collection {
|
||||
let point_vec: Vec<[f64; 2]> = data_collection
|
||||
.weight
|
||||
.iter()
|
||||
.map(|(x, y)| [x.to_owned(), y.to_owned()])
|
||||
.collect();
|
||||
|
||||
plot_points = Some((brew_title, PlotPoints::from(point_vec).points().to_vec()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
plot_points
|
||||
}
|
||||
|
||||
pub fn database_plot_entries(
|
||||
brews: Vec<&Brew>,
|
||||
config: &Config,
|
||||
database: &Database,
|
||||
progress: Option<Arc<Mutex<LoadingProgress>>>,
|
||||
) -> HashMap<String, PlotEntry> {
|
||||
let mut plot_entries: HashMap<String, PlotEntry> = HashMap::new();
|
||||
let step_size = 1.0 / brews.len() as f32;
|
||||
|
||||
for (i, brew) in brews.iter().enumerate() {
|
||||
if let Some(progress) = &progress {
|
||||
let mut progress_lock = progress.lock().unwrap();
|
||||
(*progress_lock).curr_name = brew.date_time_with_bean(&database);
|
||||
(*progress_lock).current = i;
|
||||
(*progress_lock).total = brews.len();
|
||||
(*progress_lock).percentage += step_size;
|
||||
}
|
||||
|
||||
if let Some(plot_entry) = database_plot_entry(&brew, &config, &database) {
|
||||
plot_entries.insert(brew.config.uuid.to_owned(), plot_entry);
|
||||
}
|
||||
}
|
||||
|
||||
plot_entries
|
||||
}
|
||||
|
||||
pub fn database_plot_selected(uuids: Vec<String>, config: &Config) -> Vec<(String, Plot)> {
|
||||
let mut result: Vec<(String, Plot)> = Vec::with_capacity(config.charts.len());
|
||||
|
||||
|
@ -93,7 +162,7 @@ pub fn database_plot_selected(uuids: Vec<String>, config: &Config) -> Vec<(Strin
|
|||
if !uuids.is_empty() {
|
||||
for single_selection in uuids {
|
||||
if let Some(bean) = &database.bean_for_uuid(&single_selection) {
|
||||
let bean_timestamp = bean.config.unix_timestamp.clone();
|
||||
let bean_timestamp = bean.config.unix_timestamp.to_owned();
|
||||
|
||||
let title = format!("{} from {}", &bean.name, unix_to_human_date(bean_timestamp));
|
||||
let filename = format!(
|
||||
|
@ -111,7 +180,7 @@ pub fn database_plot_selected(uuids: Vec<String>, config: &Config) -> Vec<(Strin
|
|||
if let Some(flow_profile) = &brew.flow_profile {
|
||||
if !&flow_profile.is_empty() {
|
||||
let brew_title =
|
||||
unix_to_human_date_time(brew.config.unix_timestamp.clone());
|
||||
unix_to_human_date_time(brew.config.unix_timestamp.to_owned());
|
||||
|
||||
let brew = FlowProfile::from_file(
|
||||
&format!("{}/{}", &config.data_dir, &flow_profile),
|
||||
|
@ -145,7 +214,7 @@ pub fn database_plot_selected(uuids: Vec<String>, config: &Config) -> Vec<(Strin
|
|||
plot.use_local_plotly();
|
||||
plot.write_html(filename);
|
||||
|
||||
result.push((title.to_string(), plot));
|
||||
result.push((title.to_owned(), plot));
|
||||
}
|
||||
|
||||
// progress_beans.inc(..);
|
||||
|
@ -187,14 +256,14 @@ pub fn database_plot_selected_tui() -> Vec<(String, Plot)> {
|
|||
.expect("Can't generate progress bar style");
|
||||
let progress_beans = multi_progress.add(
|
||||
ProgressBar::new(selection.len() as u64)
|
||||
.with_style(progress_style.clone())
|
||||
.with_style(progress_style.to_owned())
|
||||
.with_message("Beans"),
|
||||
);
|
||||
let mut progress_brews: ProgressBar = ProgressBar::new(0);
|
||||
|
||||
for single_selection in selection {
|
||||
if let Some(bean) = &database.beans.get(single_selection) {
|
||||
let bean_timestamp = bean.config.unix_timestamp.clone();
|
||||
let bean_timestamp = bean.config.unix_timestamp.to_owned();
|
||||
|
||||
let title = format!("{} from {}", &bean.name, unix_to_human_date(bean_timestamp));
|
||||
let filename = format!(
|
||||
|
@ -211,7 +280,7 @@ pub fn database_plot_selected_tui() -> Vec<(String, Plot)> {
|
|||
progress_brews = multi_progress.insert_after(
|
||||
&progress_beans,
|
||||
ProgressBar::new(brews.len() as u64)
|
||||
.with_style(progress_style.clone())
|
||||
.with_style(progress_style.to_owned())
|
||||
.with_message("Brews"),
|
||||
);
|
||||
|
||||
|
@ -219,7 +288,7 @@ pub fn database_plot_selected_tui() -> Vec<(String, Plot)> {
|
|||
if let Some(flow_profile) = &brew.flow_profile {
|
||||
if !&flow_profile.is_empty() {
|
||||
let brew_title =
|
||||
unix_to_human_date_time(brew.config.unix_timestamp.clone());
|
||||
unix_to_human_date_time(brew.config.unix_timestamp.to_owned());
|
||||
|
||||
let brew = FlowProfile::from_file(
|
||||
&format!("{}/{}", &config.data_dir, &flow_profile),
|
||||
|
@ -253,7 +322,7 @@ pub fn database_plot_selected_tui() -> Vec<(String, Plot)> {
|
|||
plot.use_local_plotly();
|
||||
plot.write_html(filename);
|
||||
|
||||
result.push((title.to_string(), plot));
|
||||
result.push((title.to_owned(), plot));
|
||||
}
|
||||
|
||||
progress_beans.inc(1);
|
||||
|
|
|
@ -70,7 +70,7 @@ impl Sandbox for Selector {
|
|||
.items
|
||||
.iter()
|
||||
.filter(|item| item.checked)
|
||||
.map(|item| item.uuid.clone())
|
||||
.map(|item| item.uuid.to_owned())
|
||||
.collect(),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ pub fn unix_to_human_date_time(unix_timestamp: i64) -> String {
|
|||
if let Some(date_time) = NaiveDateTime::from_timestamp_opt(unix_timestamp, 0) {
|
||||
date_time.format("%b %d, %Y %I:%M %P").to_string()
|
||||
} else {
|
||||
"Unknown date & time".to_string()
|
||||
"Unknown date & time".to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -36,7 +36,7 @@ pub fn unix_to_human_date(unix_timestamp: i64) -> String {
|
|||
if let Some(date_time) = NaiveDateTime::from_timestamp_opt(unix_timestamp, 0) {
|
||||
date_time.format("%b %d, %Y").to_string()
|
||||
} else {
|
||||
"Unknown date".to_string()
|
||||
"Unknown date".to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -44,7 +44,7 @@ pub fn unix_to_human_time(unix_timestamp: i64) -> String {
|
|||
if let Some(date_time) = NaiveDateTime::from_timestamp_opt(unix_timestamp, 0) {
|
||||
date_time.format("%I:%M %P").to_string()
|
||||
} else {
|
||||
"Unknown time".to_string()
|
||||
"Unknown time".to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -52,6 +52,6 @@ pub fn unix_to_machine_date(unix_timestamp: i64) -> String {
|
|||
if let Some(date_time) = NaiveDateTime::from_timestamp_opt(unix_timestamp, 0) {
|
||||
date_time.format("%F").to_string()
|
||||
} else {
|
||||
"unknown".to_string()
|
||||
"unknown".to_owned()
|
||||
}
|
||||
}
|
||||
|
|
372
src/ui.rs
372
src/ui.rs
|
@ -2,15 +2,35 @@
|
|||
|
||||
use eframe::egui;
|
||||
|
||||
use egui::{Align, Layout};
|
||||
use egui::{
|
||||
plot::{Legend, Line, Plot},
|
||||
Align, Layout, ProgressBar,
|
||||
};
|
||||
use rfd::FileDialog;
|
||||
|
||||
use crate::{
|
||||
config::Config,
|
||||
database::{Bean, Database},
|
||||
database::{Brew, Database},
|
||||
plot::{self, plot_points_to_owned, LoadingProgress, PlotEntry},
|
||||
};
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
thread::{self, JoinHandle},
|
||||
};
|
||||
use std::{
|
||||
env,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
struct LoadingData {
|
||||
config: Config,
|
||||
database: Database,
|
||||
brew_checkboxes: HashMap<String, bool>,
|
||||
selected_bean: String,
|
||||
plot_entries: HashMap<String, PlotEntry>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Ui {
|
||||
|
@ -19,15 +39,54 @@ pub struct Ui {
|
|||
|
||||
side_panel_expanded: bool,
|
||||
|
||||
database_loaded: bool,
|
||||
config: Option<Config>,
|
||||
database: Option<Database>,
|
||||
continuous_mode: bool,
|
||||
loading_progress: Arc<Mutex<LoadingProgress>>,
|
||||
loader_thread: Option<JoinHandle<()>>,
|
||||
loading_data: Arc<Mutex<Option<LoadingData>>>,
|
||||
data_loaded: bool,
|
||||
|
||||
selected_bean:
|
||||
bean_checkboxes: Option<HashMap<String, bool>>,
|
||||
brew_checkboxes: Option<HashMap<String, bool>>,
|
||||
config: Config,
|
||||
database: Database,
|
||||
|
||||
selected_bean: String,
|
||||
brew_checkboxes: HashMap<String, bool>,
|
||||
|
||||
select_all: bool,
|
||||
clear_all: bool,
|
||||
|
||||
picked_path: Option<String>,
|
||||
|
||||
plot_entries: HashMap<String, PlotEntry>,
|
||||
}
|
||||
|
||||
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 reload(&mut self, ctx: &egui::Context) {
|
||||
if let Some(loader_thread) = &self.loader_thread {
|
||||
if loader_thread.is_finished() {
|
||||
self.data_loaded = false;
|
||||
self.loader_thread = None;
|
||||
|
||||
let loading_progress_arc = self.loading_progress.clone();
|
||||
let mut loading_progress_lock = loading_progress_arc.lock().unwrap();
|
||||
*loading_progress_lock = LoadingProgress::default();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl eframe::App for Ui {
|
||||
|
@ -37,19 +96,51 @@ impl eframe::App for Ui {
|
|||
}
|
||||
|
||||
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
||||
if self.continuous_mode {
|
||||
ctx.request_repaint();
|
||||
}
|
||||
|
||||
self.allowed_to_close = true;
|
||||
|
||||
egui::TopBottomPanel::bottom("status_bar").show(ctx, |ui| {
|
||||
ui.columns(2, |columns| {});
|
||||
ui.columns(2, |_columns| {});
|
||||
});
|
||||
|
||||
egui::TopBottomPanel::top("dark_light").show(ctx, |ui| {
|
||||
ui.columns(2, |columns| {
|
||||
columns[0].heading("RustyBeans");
|
||||
columns[1].with_layout(Layout::right_to_left(Align::Min), |ui| {
|
||||
egui::widgets::global_dark_light_mode_buttons(ui);
|
||||
ui.vertical(|ui| {
|
||||
egui::menu::bar(ui, |ui| {
|
||||
if ui
|
||||
.button(match self.side_panel_expanded {
|
||||
true => "<",
|
||||
false => ">",
|
||||
})
|
||||
.clicked()
|
||||
{
|
||||
self.side_panel_expanded = !self.side_panel_expanded;
|
||||
}
|
||||
|
||||
let reload_button = ui.button("Reload");
|
||||
|
||||
if reload_button.clicked() {
|
||||
self.reload(ctx);
|
||||
}
|
||||
|
||||
if reload_button.hovered() {
|
||||
if let Some(loader_thread) = &self.loader_thread {
|
||||
if !loader_thread.is_finished() {
|
||||
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!");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ui.with_layout(Layout::right_to_left(Align::Min), |ui| {
|
||||
egui::widgets::global_dark_light_mode_buttons(ui);
|
||||
ui.heading("RustyBeans");
|
||||
});
|
||||
});
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
egui::SidePanel::left("selection_panel")
|
||||
|
@ -60,52 +151,64 @@ impl eframe::App for Ui {
|
|||
|
||||
ui.horizontal_centered(|ui| {
|
||||
ui.vertical(|ui| {
|
||||
if let Some(bean_checkboxes) = self.bean_checkboxes.as_mut() {
|
||||
if let Some(database) = &self.database {
|
||||
ui.heading("Beans");
|
||||
if self.data_loaded {
|
||||
ui.heading("Beans");
|
||||
|
||||
egui::ScrollArea::vertical()
|
||||
.id_source("beans_scroll")
|
||||
.max_height(total_height / 2.0)
|
||||
.show(
|
||||
ui,
|
||||
|ui| {
|
||||
for bean in &database.beans {
|
||||
ui.horizontal(|ui| {
|
||||
if let Some(checked) = bean_checkboxes
|
||||
.get_mut(&bean.config.uuid)
|
||||
{
|
||||
ui.checkbox(
|
||||
checked,
|
||||
bean.name_with_date(),
|
||||
);
|
||||
}
|
||||
});
|
||||
egui::ScrollArea::vertical()
|
||||
.id_source("beans_scroll")
|
||||
.max_height(total_height / 2.0)
|
||||
.show(ui, |ui| {
|
||||
for bean in &self.database.beans {
|
||||
ui.horizontal(|ui| {
|
||||
if ui
|
||||
.radio_value(
|
||||
&mut self.selected_bean,
|
||||
bean.config.uuid.to_owned(),
|
||||
bean.name_with_date(),
|
||||
)
|
||||
.clicked_by(egui::PointerButton::Primary)
|
||||
{
|
||||
if ctx.input(|i| i.modifiers.ctrl) {
|
||||
self.select_all = true;
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
ui.vertical(|ui| {
|
||||
if let Some(brew_checkboxes) = self.brew_checkboxes.as_mut() {
|
||||
if let Some(database) = &self.database {
|
||||
if self.data_loaded {
|
||||
if let Some(bean) =
|
||||
&self.database.bean_for_uuid(&self.selected_bean)
|
||||
{
|
||||
let brews = self.database.brews_for_bean(&bean);
|
||||
ui.heading("Brews");
|
||||
|
||||
egui::ScrollArea::vertical()
|
||||
.id_source("brews_scroll")
|
||||
.max_height(total_height / 2.0)
|
||||
.show(ui, |ui| {
|
||||
for brew in &database.brews {
|
||||
for brew in &brews {
|
||||
ui.horizontal(|ui| {
|
||||
if let Some(checked) =
|
||||
brew_checkboxes.get_mut(&brew.config.uuid)
|
||||
if let Some(checked) = self
|
||||
.brew_checkboxes
|
||||
.get_mut(&brew.config.uuid)
|
||||
{
|
||||
ui.checkbox(checked, brew.name_with_time());
|
||||
if self.select_all {
|
||||
*checked = !*checked;
|
||||
}
|
||||
|
||||
ui.checkbox(
|
||||
checked,
|
||||
brew.date_time(&self.database),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
self.select_all = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -114,25 +217,49 @@ impl eframe::App for Ui {
|
|||
|
||||
ui.horizontal_centered(|ui| {
|
||||
ui.vertical(|ui| {
|
||||
if let Some(brew_checkboxes) = self.brew_checkboxes.as_mut() {
|
||||
if let Some(database) = &self.database {
|
||||
ui.heading("Selected Brews");
|
||||
if self.data_loaded {
|
||||
ui.heading("Selected Brews");
|
||||
|
||||
egui::ScrollArea::vertical()
|
||||
.id_source("selected_brews_scroll")
|
||||
.max_height(total_height / 2.0)
|
||||
.show(ui, |ui| {
|
||||
for brew in &database.brews {
|
||||
ui.horizontal(|ui| {
|
||||
if let Some(checked) =
|
||||
brew_checkboxes.get_mut(&brew.config.uuid)
|
||||
{
|
||||
ui.checkbox(checked, brew.name_with_time());
|
||||
egui::ScrollArea::vertical()
|
||||
.id_source("selected_brews_scroll")
|
||||
.max_height(total_height / 2.0)
|
||||
.show(ui, |ui| {
|
||||
if self.clear_all {
|
||||
self.brew_checkboxes.iter_mut().for_each(
|
||||
|brew_checkbox| {
|
||||
*brew_checkbox.1 = false;
|
||||
},
|
||||
);
|
||||
|
||||
self.clear_all = false;
|
||||
} else {
|
||||
for brew in &self.database.brews {
|
||||
if let Some(checked) =
|
||||
self.brew_checkboxes.get_mut(&brew.config.uuid)
|
||||
{
|
||||
if *checked {
|
||||
ui.horizontal(|ui| {
|
||||
if ui
|
||||
.checkbox(
|
||||
checked,
|
||||
brew.date_time_with_bean(
|
||||
&self.database,
|
||||
),
|
||||
)
|
||||
.clicked_by(
|
||||
egui::PointerButton::Primary,
|
||||
)
|
||||
{
|
||||
if ctx.input(|i| i.modifiers.ctrl) {
|
||||
self.clear_all = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -162,31 +289,118 @@ impl eframe::App for Ui {
|
|||
Layout::top_down(Align::Center).with_main_align(egui::Align::Center),
|
||||
|ui| {
|
||||
if let Some(picked_path) = &self.picked_path {
|
||||
if !self.database_loaded {
|
||||
self.config = Some(Config::from_file(&picked_path));
|
||||
let thread_handle = self.loader_thread.get_or_insert_with(|| {
|
||||
self.continuous_mode = true;
|
||||
|
||||
if let Some(config) = &self.config {
|
||||
self.database = Some(Database::from_config(&config));
|
||||
let loading_data_arc = self.loading_data.clone();
|
||||
let loading_progress_arc = self.loading_progress.clone();
|
||||
let picked_path_owned = picked_path.to_owned();
|
||||
let ctx_clone = ctx.clone();
|
||||
|
||||
if let Some(database) = &self.database {
|
||||
let mut checkboxes = HashMap::new();
|
||||
thread::spawn(move || {
|
||||
let ctx = ctx_clone;
|
||||
|
||||
for bean in &database.beans {
|
||||
checkboxes.insert(bean.config.uuid.clone(), false);
|
||||
}
|
||||
let mut loading_data = LoadingData::default();
|
||||
|
||||
self.bean_checkboxes = Some(checkboxes);
|
||||
loading_data.config = Config::from_file(&picked_path_owned);
|
||||
|
||||
checkboxes = HashMap::new();
|
||||
loading_data.database =
|
||||
Database::from_config(&loading_data.config);
|
||||
|
||||
for brew in &database.brews {
|
||||
checkboxes.insert(brew.config.uuid.clone(), false);
|
||||
}
|
||||
let mut checkboxes = HashMap::new();
|
||||
|
||||
self.brew_checkboxes = Some(checkboxes);
|
||||
|
||||
self.database_loaded = true;
|
||||
for brew in &loading_data.database.brews {
|
||||
checkboxes.insert(brew.config.uuid.to_owned(), false);
|
||||
}
|
||||
|
||||
loading_data.brew_checkboxes = checkboxes;
|
||||
|
||||
if !&loading_data.database.beans.is_empty() {
|
||||
loading_data.selected_bean =
|
||||
loading_data.database.beans[0].config.uuid.to_owned();
|
||||
}
|
||||
|
||||
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();
|
||||
})
|
||||
});
|
||||
|
||||
if !thread_handle.is_finished() {
|
||||
egui::Window::new("Loading resources")
|
||||
.collapsible(false)
|
||||
.resizable(false)
|
||||
.show(ctx, |ui| {
|
||||
let loading_progress_arc = self.loading_progress.clone();
|
||||
let loading_progress_lock =
|
||||
loading_progress_arc.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
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
} else {
|
||||
if self.continuous_mode {
|
||||
self.continuous_mode = false;
|
||||
}
|
||||
|
||||
if !self.data_loaded {
|
||||
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.selected_bean = loading_data.selected_bean;
|
||||
self.plot_entries = loading_data.plot_entries;
|
||||
|
||||
self.data_loaded = true;
|
||||
} else {
|
||||
Plot::new("Combined Chart of selected brews")
|
||||
.legend(Legend::default())
|
||||
.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()),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -207,6 +421,10 @@ impl eframe::App for Ui {
|
|||
self.picked_path = Some(path.display().to_string());
|
||||
}
|
||||
}
|
||||
|
||||
if ui.button("Open default config.toml").clicked() {
|
||||
self.picked_path = Some(String::from("config.toml"));
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
|
50
src/web.rs
50
src/web.rs
|
@ -1,50 +0,0 @@
|
|||
use yew::prelude::*;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::database::Database;
|
||||
use crate::plot::database_plot_selected;
|
||||
use crate::plot::database_plot_selected_tui;
|
||||
|
||||
use std::process::Command;
|
||||
|
||||
#[function_component(App)]
|
||||
pub fn plot_component() -> Html {
|
||||
let config = Config::from_default();
|
||||
let database = Database::from_config(&config);
|
||||
|
||||
let plots = database_plot_selected(
|
||||
database
|
||||
.brews
|
||||
.iter()
|
||||
.map(|brew| brew.config.uuid.clone())
|
||||
.collect(),
|
||||
true,
|
||||
);
|
||||
let plot_names: Vec<String> = plots.clone().into_iter().map(|plot| plot.0.clone()).collect();
|
||||
|
||||
let p = yew_hooks::use_async::<_, _, ()>({
|
||||
async move {
|
||||
for plot in &plots {
|
||||
plotly::bindings::new_plot(&plot.0, &plot.1).await;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
});
|
||||
|
||||
use_effect_with_deps(
|
||||
move |_| {
|
||||
p.run();
|
||||
|| ()
|
||||
},
|
||||
(),
|
||||
);
|
||||
|
||||
html! {
|
||||
{
|
||||
for plot_names.into_iter().map(|name| {
|
||||
html!{<div id={name}></div>}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue