diff --git a/src/database.rs b/src/database.rs index 3f6eebc..d73b93f 100644 --- a/src/database.rs +++ b/src/database.rs @@ -49,9 +49,9 @@ impl Database { .map(|_| ()) } - pub async fn update_sheet_path(&self, sheet: Sheet) -> sqlx::Result<()> { + pub async fn update_sheet_path(&self, sheet: &Sheet) -> sqlx::Result<()> { sqlx::query("UPDATE sheets SET path = $1 WHERE id = $2") - .bind(sheet.path) + .bind(sheet.path.clone()) .bind(sheet.id) .execute(&self.connection) .await diff --git a/src/main.rs b/src/main.rs index 328e440..3f8f19d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -39,16 +39,16 @@ async fn main() { debug!("Validating sheets from database..."); let validation_result = validate_sheet_files(sheets, &cli.directory); debug!("{}", validation_result.get_stats()); - for updated in validation_result.updated_sheets { + for updated in validation_result.updated_sheets.iter() { database.update_sheet_path(updated).await.unwrap(); } let app = RelmApp::new("de.frajul.sheet-organizer"); // Pass empty command line args to allow my own parsing - app.with_args(Vec::new()).run::(cli.directory); + app.with_args(Vec::new()).run::(validation_result); } -struct FileValidationResult { +pub struct FileValidationResult { validated_sheets: Vec, invalidated_sheets: Vec, updated_sheets: Vec, diff --git a/src/sheet_listing.rs b/src/sheet_listing.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/sheet_listing.rs @@ -0,0 +1 @@ + diff --git a/src/ui/app.rs b/src/ui/app.rs index 7ac072d..a137148 100644 --- a/src/ui/app.rs +++ b/src/ui/app.rs @@ -1,9 +1,12 @@ use std::path::PathBuf; use gtk::prelude::*; -use relm4::prelude::*; +use relm4::{component::Connector, prelude::*}; -use crate::ui::{mcdu::McduOutput, sheet_listing::SheetPressedMessage}; +use crate::{ + ui::{mcdu::McduOutput, sheet_model::SheetModelType}, + FileValidationResult, +}; use super::{ mcdu::McduModel, @@ -12,7 +15,8 @@ use super::{ pub struct AppModel { mcdu: Controller, - sheet_listing: Controller, + sheets_and_files_listing: Connector, + new_files_listing: Connector, } #[derive(Debug)] @@ -25,7 +29,7 @@ pub enum AppInput { impl SimpleComponent for AppModel { type Input = AppInput; type Output = (); - type Init = PathBuf; + type Init = FileValidationResult; view! { #[root] @@ -39,11 +43,23 @@ impl SimpleComponent for AppModel { gtk::Box { set_orientation: gtk::Orientation::Vertical, set_hexpand: true, - gtk::ScrolledWindow { - model.sheet_listing.widget(), - set_vexpand: true, - set_hexpand: true, - // set_hscrollbar_policy: PolicyType::Never, + #[name = "stack_switcher"] + gtk::StackSwitcher { + set_stack: Some(&stack), + set_margin_all: 10, + }, + #[name = "stack"] + gtk::Stack { + add_titled[None, "Sheets & Files"]= >k::ScrolledWindow { + model.sheets_and_files_listing.widget(), + set_vexpand: true, + set_hexpand: true, + }, + add_titled[None, "New Files"]= >k::ScrolledWindow { + model.new_files_listing.widget(), + set_vexpand: true, + set_hexpand: true, + }, }, }, model.mcdu.widget(), @@ -52,7 +68,7 @@ impl SimpleComponent for AppModel { } fn init( - path: Self::Init, + file_validation_result: Self::Init, window: &Self::Root, sender: ComponentSender, ) -> relm4::ComponentParts { @@ -64,16 +80,37 @@ impl SimpleComponent for AppModel { McduOutput::SearchStarted(query) => AppInput::SearchStarted(query), }); - let sheet_listing = - SheetListingModel::builder() - .launch(path) - .forward(sender.input_sender(), |response| match response { - SheetPressedMessage::SheetPressed(path) => AppInput::SheetPressed(path), - }); + let new_files: Vec = file_validation_result + .unassigned_files + .iter() + .map(|file| SheetModelType::Pdf { + path: file.to_path_buf(), + }) + .collect(); + let new_files_clone_iter = file_validation_result + .unassigned_files + .into_iter() + .map(|file| SheetModelType::Pdf { path: file }); + + let sheets_and_files: Vec = file_validation_result + .validated_sheets + .into_iter() + .chain(file_validation_result.updated_sheets.into_iter()) + .map(|sheet| SheetModelType::Sheet { sheet }) + .chain(new_files_clone_iter) + .collect(); + + let sheets_and_files_listing = SheetListingModel::builder().launch(sheets_and_files); + let new_files_listing = SheetListingModel::builder().launch(new_files); + // .forward(sender.input_sender(), |_| {}); + // .forward(sender.input_sender(), |response| match response { + // SheetPressedMessage::SheetPressed(path) => AppInput::SheetPressed(path), + // }); let model = AppModel { mcdu, - sheet_listing, + sheets_and_files_listing, + new_files_listing, }; let widgets = view_output!(); @@ -84,7 +121,7 @@ impl SimpleComponent for AppModel { fn update(&mut self, message: Self::Input, _sender: ComponentSender) { match message { AppInput::SheetPressed(sheet) => opener::open(sheet).unwrap(), - AppInput::SearchStarted(query) => self.sheet_listing.emit(SheetListingInput { query }), + AppInput::SearchStarted(query) => {} //self.sheet_listing.emit(SheetListingInput { query }), } } } diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 3bb5055..5f3a11e 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,3 +1,4 @@ pub mod app; pub mod mcdu; pub mod sheet_listing; +pub mod sheet_model; diff --git a/src/ui/sheet_listing.rs b/src/ui/sheet_listing.rs index 47724b0..d1ec6a6 100644 --- a/src/ui/sheet_listing.rs +++ b/src/ui/sheet_listing.rs @@ -1,83 +1,18 @@ use std::path::PathBuf; use gtk::prelude::*; +use relm4::factory::FactoryVecDeque; use relm4::prelude::*; use relm4::{ gtk, Component, ComponentController, ComponentParts, ComponentSender, SimpleComponent, }; use walkdir::WalkDir; -pub struct SheetModel { - path: PathBuf, - visible: bool, -} - -#[derive(Debug)] -pub enum SheetModelInput { - OnClicked, - SearchChanged(String), -} - -#[derive(Debug)] -pub enum SheetPressedMessage { - SheetPressed(PathBuf), -} - -#[relm4::component(pub)] -impl SimpleComponent for SheetModel { - type Init = PathBuf; - type Input = SheetModelInput; - type Output = SheetPressedMessage; - - view! { - #[root] - gtk::Box { - #[watch] - set_visible: model.visible, - set_orientation: gtk::Orientation::Vertical, - append = >k::Button { - set_label: &format!("{}", model.path.file_name().unwrap().to_string_lossy()), - set_halign: gtk::Align::Start, - set_margin_all: 10, - connect_clicked[sender] => move |_| sender.input(SheetModelInput::OnClicked), - } - } - } - - fn init( - path: Self::Init, - root: &Self::Root, - sender: ComponentSender, - ) -> ComponentParts { - let model = SheetModel { - path, - visible: true, - }; - let widgets = view_output!(); - ComponentParts { model, widgets } - } - - fn update(&mut self, message: Self::Input, sender: ComponentSender) { - match message { - SheetModelInput::OnClicked => sender - .output(SheetPressedMessage::SheetPressed(self.path.clone())) - .unwrap(), - SheetModelInput::SearchChanged(query) => { - self.visible = self - .path - .file_name() - .unwrap() - .to_string_lossy() - .to_lowercase() - .contains(&query.to_lowercase()); - } - } - } -} +use super::sheet_model::{SheetModel, SheetModelType}; pub struct SheetListingModel { query: String, - sheet_models: Vec>, + sheets: FactoryVecDeque, } #[derive(Debug)] @@ -87,45 +22,35 @@ pub struct SheetListingInput { #[relm4::component(pub)] impl SimpleComponent for SheetListingModel { - type Init = PathBuf; + type Init = Vec; type Input = SheetListingInput; - type Output = SheetPressedMessage; + type Output = (); view! { #[root] gtk::Box { - set_orientation: gtk::Orientation::Vertical, + // set_orientation: gtk::Orientation::Vertical, + + model.sheets.widget() -> >k::Box { + set_orientation: gtk::Orientation::Vertical, + set_spacing: 5, + }, } } fn init( - dir: Self::Init, + init: Self::Init, root: &Self::Root, sender: ComponentSender, ) -> ComponentParts { - let mut sheet_models = Vec::new(); - - for entry in WalkDir::new(dir) - .into_iter() - .filter_map(|e| e.ok()) - .filter(|file| file.file_type().is_file()) - .map(|file| file.into_path()) - .filter(|path| { - path.extension() - .map(|s| s.to_string_lossy().to_ascii_lowercase() == "pdf") - .unwrap_or(false) - }) - { - let sheet_model = SheetModel::builder() - .launch(entry) - .forward(sender.output_sender(), |m| m); - root.append(sheet_model.widget()); - sheet_models.push(sheet_model); + let mut sheets = FactoryVecDeque::new(gtk::Box::default(), sender.input_sender()); + for sheet_model_type in init { + sheets.guard().push_back(sheet_model_type); } let model = SheetListingModel { query: String::new(), - sheet_models, + sheets, }; let widgets = view_output!(); ComponentParts { model, widgets } @@ -133,8 +58,5 @@ impl SimpleComponent for SheetListingModel { fn update(&mut self, message: Self::Input, _sender: ComponentSender) { self.query = message.query; - for model in self.sheet_models.iter() { - model.emit(SheetModelInput::SearchChanged(self.query.clone())); - } } } diff --git a/src/ui/sheet_model.rs b/src/ui/sheet_model.rs new file mode 100644 index 0000000..7c868a2 --- /dev/null +++ b/src/ui/sheet_model.rs @@ -0,0 +1,48 @@ +use std::path::PathBuf; + +use gtk::prelude::*; +use relm4::prelude::*; + +use crate::sheet::Sheet; + +use super::sheet_listing::SheetListingInput; + +pub enum SheetModelType { + Sheet { sheet: Sheet }, + Pdf { path: PathBuf }, +} + +pub struct SheetModel { + label: String, +} + +#[relm4::factory(pub)] +impl FactoryComponent for SheetModel { + type Init = SheetModelType; + type ParentWidget = gtk::Box; + type CommandOutput = (); + type ParentInput = SheetListingInput; + type Input = (); + type Output = (); + + view! { + #[root] + gtk::Box { + set_orientation: gtk::Orientation::Vertical, + append = >k::Label { + set_label: &self.label, + set_halign: gtk::Align::Start, + set_margin_all: 10, + } + } + } + + fn init_model(value: Self::Init, _index: &DynamicIndex, _sender: FactorySender) -> Self { + let label = match value { + SheetModelType::Sheet { sheet } => sheet.name, + SheetModelType::Pdf { path } => path.file_name().unwrap().to_str().unwrap().to_string(), + }; + + SheetModel { label } + } +}