Implement sheet listings with relm4 factory

This commit is contained in:
Julian Mutter 2024-02-02 21:11:21 +01:00
parent 6a6e1d03df
commit 9069138255
7 changed files with 126 additions and 117 deletions

View File

@ -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

View File

@ -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::<AppModel>(cli.directory);
app.with_args(Vec::new()).run::<AppModel>(validation_result);
}
struct FileValidationResult {
pub struct FileValidationResult {
validated_sheets: Vec<Sheet>,
invalidated_sheets: Vec<Sheet>,
updated_sheets: Vec<Sheet>,

1
src/sheet_listing.rs Normal file
View File

@ -0,0 +1 @@

View File

@ -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<McduModel>,
sheet_listing: Controller<SheetListingModel>,
sheets_and_files_listing: Connector<SheetListingModel>,
new_files_listing: Connector<SheetListingModel>,
}
#[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"]= &gtk::ScrolledWindow {
model.sheets_and_files_listing.widget(),
set_vexpand: true,
set_hexpand: true,
},
add_titled[None, "New Files"]= &gtk::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<Self>,
) -> relm4::ComponentParts<Self> {
@ -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<SheetModelType> = 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<SheetModelType> = 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<Self>) {
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 }),
}
}
}

View File

@ -1,3 +1,4 @@
pub mod app;
pub mod mcdu;
pub mod sheet_listing;
pub mod sheet_model;

View File

@ -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 = &gtk::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<Self>,
) -> ComponentParts<Self> {
let model = SheetModel {
path,
visible: true,
};
let widgets = view_output!();
ComponentParts { model, widgets }
}
fn update(&mut self, message: Self::Input, sender: ComponentSender<Self>) {
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<Controller<SheetModel>>,
sheets: FactoryVecDeque<SheetModel>,
}
#[derive(Debug)]
@ -87,45 +22,35 @@ pub struct SheetListingInput {
#[relm4::component(pub)]
impl SimpleComponent for SheetListingModel {
type Init = PathBuf;
type Init = Vec<SheetModelType>;
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() -> &gtk::Box {
set_orientation: gtk::Orientation::Vertical,
set_spacing: 5,
},
}
}
fn init(
dir: Self::Init,
init: Self::Init,
root: &Self::Root,
sender: ComponentSender<Self>,
) -> ComponentParts<Self> {
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>) {
self.query = message.query;
for model in self.sheet_models.iter() {
model.emit(SheetModelInput::SearchChanged(self.query.clone()));
}
}
}

48
src/ui/sheet_model.rs Normal file
View File

@ -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 = &gtk::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>) -> Self {
let label = match value {
SheetModelType::Sheet { sheet } => sheet.name,
SheetModelType::Pdf { path } => path.file_name().unwrap().to_str().unwrap().to_string(),
};
SheetModel { label }
}
}