mirror of https://github.com/terhechte/postsack
Large Scale UI Integration
- Split UI up into multiple states - Changes throughout the codebase to accomodate that - All still messy but opening a folder and creating a database works now.pull/1/head
parent
17c3326c36
commit
b8cce5deba
Binary file not shown.
Binary file not shown.
@ -0,0 +1,43 @@
|
||||
use eframe::{
|
||||
egui,
|
||||
egui::{Response, Widget},
|
||||
};
|
||||
|
||||
use super::super::widgets;
|
||||
use super::{StateUI, StateUIAction, StateUIVariant};
|
||||
use crate::types::Config;
|
||||
|
||||
pub struct ErrorUI {
|
||||
/// The error to display
|
||||
report: eyre::Report,
|
||||
/// The config that led to this error, in order
|
||||
/// to let the user `go back`.
|
||||
/// As we might not have a config *yet* this is optional
|
||||
config: Option<Config>,
|
||||
}
|
||||
|
||||
impl ErrorUI {
|
||||
pub fn new(report: eyre::Report, config: Option<Config>) -> Self {
|
||||
Self { report, config }
|
||||
}
|
||||
}
|
||||
impl StateUIVariant for ErrorUI {
|
||||
fn update_panel(&mut self, ctx: &egui::CtxRef) -> StateUIAction {
|
||||
egui::CentralPanel::default()
|
||||
.frame(egui::containers::Frame::none())
|
||||
.show(ctx, |ui| {
|
||||
ui.add(|ui: &mut egui::Ui| self.ui(ui));
|
||||
});
|
||||
// If the user tapped the back button, go back to startup
|
||||
StateUIAction::Nothing
|
||||
}
|
||||
}
|
||||
|
||||
impl ErrorUI {
|
||||
fn ui(&mut self, ui: &mut egui::Ui) -> Response {
|
||||
// FIXME: Try again button
|
||||
// that goes back to the `Startup` screen.
|
||||
// somehow it should also fill it out again?
|
||||
ui.add(widgets::ErrorBox(&self.report))
|
||||
}
|
||||
}
|
@ -0,0 +1,134 @@
|
||||
use eframe::egui::{self, Response, Stroke, Widget};
|
||||
use eyre::{Report, Result};
|
||||
|
||||
use super::super::widgets::{self, FilterState, Spinner};
|
||||
use super::{StateUI, StateUIAction, StateUIVariant};
|
||||
use crate::types::Config;
|
||||
|
||||
use crate::model::Engine;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct UIState {
|
||||
pub show_emails: bool,
|
||||
pub show_filters: bool,
|
||||
pub show_export: bool,
|
||||
pub action_close: bool,
|
||||
}
|
||||
|
||||
pub struct MainUI {
|
||||
config: Config,
|
||||
engine: Engine,
|
||||
error: Option<Report>,
|
||||
state: UIState,
|
||||
filter_state: FilterState,
|
||||
platform_custom_setup: bool,
|
||||
}
|
||||
|
||||
impl MainUI {
|
||||
pub fn new(config: Config) -> Result<Self> {
|
||||
let mut engine = Engine::new(&config)?;
|
||||
engine.start()?;
|
||||
Ok(Self {
|
||||
config,
|
||||
engine,
|
||||
error: None,
|
||||
state: UIState::default(),
|
||||
filter_state: FilterState::new(),
|
||||
platform_custom_setup: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl StateUIVariant for MainUI {
|
||||
fn update_panel(&mut self, ctx: &egui::CtxRef) -> super::StateUIAction {
|
||||
// Avoid any processing if there is an unhandled error.
|
||||
if self.error.is_none() {
|
||||
self.error = self.engine.process().err();
|
||||
}
|
||||
|
||||
if !self.platform_custom_setup {
|
||||
self.platform_custom_setup = true;
|
||||
self.error = super::super::platform::initial_update(&ctx).err();
|
||||
|
||||
// Make the UI a bit bigger
|
||||
let pixels = ctx.pixels_per_point();
|
||||
ctx.set_pixels_per_point(pixels * 1.2)
|
||||
}
|
||||
|
||||
let platform_colors = super::super::platform::platform_colors();
|
||||
|
||||
let frame = egui::containers::Frame::none()
|
||||
.fill(platform_colors.window_background_dark)
|
||||
.stroke(Stroke::none());
|
||||
|
||||
egui::TopBottomPanel::top("my_panel")
|
||||
.frame(frame)
|
||||
.show(ctx, |ui| {
|
||||
ui.add(super::super::navigation_bar::NavigationBar::new(
|
||||
&mut self.engine,
|
||||
&mut self.error,
|
||||
&mut self.state,
|
||||
&mut self.filter_state,
|
||||
));
|
||||
});
|
||||
|
||||
if self.state.show_emails {
|
||||
egui::SidePanel::right("my_left_panel")
|
||||
.default_width(500.0)
|
||||
.show(ctx, |ui| {
|
||||
ui.add(super::super::mail_panel::MailPanel::new(
|
||||
&mut self.engine,
|
||||
&mut self.error,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
egui::CentralPanel::default()
|
||||
.frame(egui::containers::Frame::none())
|
||||
.show(ctx, |ui| {
|
||||
if self.engine.segmentations().is_empty() {
|
||||
ui.centered_and_justified(|ui| {
|
||||
ui.add(Spinner::new(egui::vec2(50.0, 50.0)));
|
||||
});
|
||||
} else {
|
||||
let stroke = Stroke::none();
|
||||
let fill = platform_colors.content_background_dark;
|
||||
super::super::widgets::background::color_background(
|
||||
ui,
|
||||
15.0,
|
||||
stroke,
|
||||
fill,
|
||||
|ui| {
|
||||
ui.vertical(|ui: &mut egui::Ui| {
|
||||
ui.add(super::super::segmentation_bar::SegmentationBar::new(
|
||||
&mut self.engine,
|
||||
&mut self.error,
|
||||
));
|
||||
ui.add(super::super::widgets::Rectangles::new(
|
||||
&mut self.engine,
|
||||
&mut self.error,
|
||||
));
|
||||
})
|
||||
.response
|
||||
},
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// If we're waiting for a computation to succeed, we re-render again.
|
||||
if self.engine.is_busy() {
|
||||
ctx.request_repaint();
|
||||
}
|
||||
|
||||
match (self.state.action_close, self.error.take()) {
|
||||
(_, Some(error)) => StateUIAction::Error {
|
||||
report: error,
|
||||
config: self.config.clone(),
|
||||
},
|
||||
(true, _) => StateUIAction::Close {
|
||||
config: self.config.clone(),
|
||||
},
|
||||
_ => StateUIAction::Nothing,
|
||||
}
|
||||
}
|
||||
}
|
@ -1,16 +1,156 @@
|
||||
mod error;
|
||||
mod import;
|
||||
mod main;
|
||||
mod startup;
|
||||
mod visualize;
|
||||
|
||||
pub use import::Import;
|
||||
pub use startup::Startup;
|
||||
pub use visualize::{UIState, Visualize};
|
||||
|
||||
pub fn make_temporary_ui_config() -> crate::types::Config {
|
||||
crate::types::Config::new(
|
||||
"./db6.sql",
|
||||
"",
|
||||
"terhechte@me.com".to_string(),
|
||||
crate::types::FormatType::AppleMail,
|
||||
)
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use eframe::egui::{self, Widget};
|
||||
pub use error::ErrorUI;
|
||||
use eyre::{Report, Result};
|
||||
pub use import::ImporterUI;
|
||||
pub use main::{MainUI, UIState};
|
||||
pub use startup::StartupUI;
|
||||
|
||||
use crate::types::{Config, FormatType};
|
||||
|
||||
pub enum StateUIAction {
|
||||
CreateDatabase {
|
||||
database_path: Option<PathBuf>,
|
||||
emails_folder_path: PathBuf,
|
||||
sender_emails: Vec<String>,
|
||||
format: FormatType,
|
||||
},
|
||||
OpenDatabase {
|
||||
database_path: PathBuf,
|
||||
},
|
||||
ImportDone {
|
||||
config: Config,
|
||||
},
|
||||
Close {
|
||||
config: Config,
|
||||
},
|
||||
Error {
|
||||
report: Report,
|
||||
config: Config,
|
||||
},
|
||||
Nothing,
|
||||
}
|
||||
|
||||
// FIXME: Removve
|
||||
// pub fn make_temporary_ui_config() -> crate::types::Config {
|
||||
// crate::types::Config::new(
|
||||
// "./db6.sql",
|
||||
// "",
|
||||
// vec!["terhechte@me.com".to_string()],
|
||||
// crate::types::FormatType::AppleMail,
|
||||
// )
|
||||
// }
|
||||
|
||||
// pub enum MainApp {
|
||||
// Startup { panel: StartupUI },
|
||||
// Import { panel: ImporterUI },
|
||||
// Main { panel: MainUI },
|
||||
// Error { panel: ErrorUI },
|
||||
// }
|
||||
|
||||
/// This defines the state machine switches between the `MainApp`
|
||||
/// states.
|
||||
/// FIXME: I'm not particularly happy with this abstraction right now.
|
||||
// impl MainApp {
|
||||
// /// An Error state can always happen
|
||||
// pub fn error(report: eyre::Report) -> MainApp {
|
||||
// MainApp::Error {
|
||||
// panel: ErrorUI(report),
|
||||
// }
|
||||
// }
|
||||
// // pub fn import(startup: &) -> MainApp {
|
||||
|
||||
// // }
|
||||
// }
|
||||
pub enum StateUI {
|
||||
Startup(startup::StartupUI),
|
||||
Import(import::ImporterUI),
|
||||
Main(main::MainUI),
|
||||
Error(error::ErrorUI),
|
||||
}
|
||||
|
||||
pub trait StateUIVariant {
|
||||
fn update_panel(&mut self, ctx: &egui::CtxRef) -> StateUIAction;
|
||||
}
|
||||
|
||||
impl StateUI {
|
||||
/// This proxies the `update` call to the individual calls in
|
||||
/// the `app_state` types
|
||||
pub fn update(&mut self, ctx: &egui::CtxRef) {
|
||||
let response = match self {
|
||||
StateUI::Startup(panel) => panel.update_panel(ctx),
|
||||
StateUI::Import(panel) => panel.update_panel(ctx),
|
||||
StateUI::Main(panel) => panel.update_panel(ctx),
|
||||
StateUI::Error(panel) => panel.update_panel(ctx),
|
||||
};
|
||||
match response {
|
||||
StateUIAction::CreateDatabase {
|
||||
database_path,
|
||||
emails_folder_path,
|
||||
sender_emails,
|
||||
format,
|
||||
} => {
|
||||
*self =
|
||||
self.create_database(database_path, emails_folder_path, sender_emails, format)
|
||||
}
|
||||
StateUIAction::OpenDatabase { database_path } => {
|
||||
*self = self.open_database(database_path)
|
||||
}
|
||||
StateUIAction::ImportDone { config } => {
|
||||
*self = match main::MainUI::new(config.clone()) {
|
||||
Ok(n) => StateUI::Main(n),
|
||||
Err(e) => StateUI::Error(ErrorUI::new(e, Some(config.clone()))),
|
||||
};
|
||||
}
|
||||
StateUIAction::Close { config } => {
|
||||
*self = StateUI::Startup(StartupUI::from_config(config));
|
||||
}
|
||||
StateUIAction::Error { report, config } => {
|
||||
*self = StateUI::Error(error::ErrorUI::new(report, Some(config)))
|
||||
}
|
||||
StateUIAction::Nothing => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StateUI {
|
||||
pub fn new() -> StateUI {
|
||||
StateUI::Startup(startup::StartupUI::default())
|
||||
}
|
||||
|
||||
pub fn create_database(
|
||||
&self,
|
||||
database_path: Option<PathBuf>,
|
||||
emails_folder_path: PathBuf,
|
||||
sender_emails: Vec<String>,
|
||||
format: FormatType,
|
||||
) -> StateUI {
|
||||
let config = match Config::new(database_path, emails_folder_path, sender_emails, format) {
|
||||
Ok(n) => n,
|
||||
Err(e) => {
|
||||
return StateUI::Error(error::ErrorUI::new(e, None));
|
||||
}
|
||||
};
|
||||
|
||||
let importer = match import::ImporterUI::new(config.clone()) {
|
||||
Ok(n) => n,
|
||||
Err(e) => {
|
||||
return StateUI::Error(error::ErrorUI::new(e, Some(config.clone())));
|
||||
}
|
||||
};
|
||||
|
||||
return StateUI::Import(importer);
|
||||
}
|
||||
|
||||
pub fn open_database(&mut self, database_path: PathBuf) -> StateUI {
|
||||
// FIXME: the database needs to be opened in order to figure
|
||||
// out whether it is a correct DB, before we can head on
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
@ -1,115 +0,0 @@
|
||||
use eframe::egui::{self, Response, Stroke, Widget};
|
||||
use eyre::Report;
|
||||
|
||||
use super::super::widgets::{self, FilterState, Spinner};
|
||||
|
||||
use crate::model::Engine;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct UIState {
|
||||
pub show_emails: bool,
|
||||
pub show_filters: bool,
|
||||
pub show_export: bool,
|
||||
pub action_close: bool,
|
||||
}
|
||||
|
||||
pub struct Visualize {
|
||||
engine: Engine,
|
||||
error: Option<Report>,
|
||||
state: UIState,
|
||||
filter_state: FilterState,
|
||||
platform_custom_setup: bool,
|
||||
}
|
||||
|
||||
impl Widget for &mut Visualize {
|
||||
fn ui(self, ui: &mut egui::Ui) -> Response {
|
||||
// Avoid any processing if there is an unhandled error.
|
||||
if self.error.is_none() {
|
||||
self.error = self.engine.process().err();
|
||||
}
|
||||
|
||||
if !self.platform_custom_setup {
|
||||
self.platform_custom_setup = true;
|
||||
self.error = super::super::platform::initial_update(&ui.ctx()).err();
|
||||
|
||||
// Make the UI a bit bigger
|
||||
let pixels = ui.ctx().pixels_per_point();
|
||||
ui.ctx().set_pixels_per_point(pixels * 1.2)
|
||||
}
|
||||
|
||||
let platform_colors = super::super::platform::platform_colors();
|
||||
|
||||
let response = if let Some(error) = self.error.as_ref() {
|
||||
dbg!(&error);
|
||||
egui::CentralPanel::default()
|
||||
.show(ui.ctx(), |ui| ui.add(widgets::ErrorBox(error)))
|
||||
.response
|
||||
} else {
|
||||
let frame = egui::containers::Frame::none()
|
||||
.fill(platform_colors.window_background_dark)
|
||||
.stroke(Stroke::none());
|
||||
|
||||
egui::TopBottomPanel::top("my_panel")
|
||||
.frame(frame)
|
||||
.show(ui.ctx(), |ui| {
|
||||
ui.add(super::super::navigation_bar::NavigationBar::new(
|
||||
&mut self.engine,
|
||||
&mut self.error,
|
||||
&mut self.state,
|
||||
&mut self.filter_state,
|
||||
));
|
||||
});
|
||||
|
||||
if self.state.show_emails {
|
||||
egui::SidePanel::right("my_left_panel")
|
||||
.default_width(500.0)
|
||||
.show(ui.ctx(), |ui| {
|
||||
ui.add(super::super::mail_panel::MailPanel::new(
|
||||
&mut self.engine,
|
||||
&mut self.error,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
egui::CentralPanel::default()
|
||||
.frame(egui::containers::Frame::none())
|
||||
.show(ui.ctx(), |ui| {
|
||||
if self.engine.segmentations().is_empty() {
|
||||
ui.centered_and_justified(|ui| {
|
||||
ui.add(Spinner::new(egui::vec2(50.0, 50.0)));
|
||||
});
|
||||
} else {
|
||||
let stroke = Stroke::none();
|
||||
let fill = platform_colors.content_background_dark;
|
||||
super::super::widgets::background::color_background(
|
||||
ui,
|
||||
15.0,
|
||||
stroke,
|
||||
fill,
|
||||
|ui| {
|
||||
ui.vertical(|ui: &mut egui::Ui| {
|
||||
ui.add(super::super::segmentation_bar::SegmentationBar::new(
|
||||
&mut self.engine,
|
||||
&mut self.error,
|
||||
));
|
||||
ui.add(super::super::widgets::Rectangles::new(
|
||||
&mut self.engine,
|
||||
&mut self.error,
|
||||
));
|
||||
})
|
||||
.response
|
||||
},
|
||||
);
|
||||
}
|
||||
})
|
||||
.response
|
||||
};
|
||||
|
||||
// If we're waiting for a computation to succeed, we re-render again.
|
||||
if self.engine.is_busy() {
|
||||
ui.ctx().request_repaint();
|
||||
}
|
||||
|
||||
response
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue