Implement sheet listings with relm4 factory
This commit is contained in:
parent
6a6e1d03df
commit
9069138255
@ -49,9 +49,9 @@ impl Database {
|
|||||||
.map(|_| ())
|
.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")
|
sqlx::query("UPDATE sheets SET path = $1 WHERE id = $2")
|
||||||
.bind(sheet.path)
|
.bind(sheet.path.clone())
|
||||||
.bind(sheet.id)
|
.bind(sheet.id)
|
||||||
.execute(&self.connection)
|
.execute(&self.connection)
|
||||||
.await
|
.await
|
||||||
|
@ -39,16 +39,16 @@ async fn main() {
|
|||||||
debug!("Validating sheets from database...");
|
debug!("Validating sheets from database...");
|
||||||
let validation_result = validate_sheet_files(sheets, &cli.directory);
|
let validation_result = validate_sheet_files(sheets, &cli.directory);
|
||||||
debug!("{}", validation_result.get_stats());
|
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();
|
database.update_sheet_path(updated).await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
let app = RelmApp::new("de.frajul.sheet-organizer");
|
let app = RelmApp::new("de.frajul.sheet-organizer");
|
||||||
// Pass empty command line args to allow my own parsing
|
// 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>,
|
validated_sheets: Vec<Sheet>,
|
||||||
invalidated_sheets: Vec<Sheet>,
|
invalidated_sheets: Vec<Sheet>,
|
||||||
updated_sheets: Vec<Sheet>,
|
updated_sheets: Vec<Sheet>,
|
||||||
|
1
src/sheet_listing.rs
Normal file
1
src/sheet_listing.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
@ -1,9 +1,12 @@
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use gtk::prelude::*;
|
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::{
|
use super::{
|
||||||
mcdu::McduModel,
|
mcdu::McduModel,
|
||||||
@ -12,7 +15,8 @@ use super::{
|
|||||||
|
|
||||||
pub struct AppModel {
|
pub struct AppModel {
|
||||||
mcdu: Controller<McduModel>,
|
mcdu: Controller<McduModel>,
|
||||||
sheet_listing: Controller<SheetListingModel>,
|
sheets_and_files_listing: Connector<SheetListingModel>,
|
||||||
|
new_files_listing: Connector<SheetListingModel>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -25,7 +29,7 @@ pub enum AppInput {
|
|||||||
impl SimpleComponent for AppModel {
|
impl SimpleComponent for AppModel {
|
||||||
type Input = AppInput;
|
type Input = AppInput;
|
||||||
type Output = ();
|
type Output = ();
|
||||||
type Init = PathBuf;
|
type Init = FileValidationResult;
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
#[root]
|
#[root]
|
||||||
@ -39,11 +43,23 @@ impl SimpleComponent for AppModel {
|
|||||||
gtk::Box {
|
gtk::Box {
|
||||||
set_orientation: gtk::Orientation::Vertical,
|
set_orientation: gtk::Orientation::Vertical,
|
||||||
set_hexpand: true,
|
set_hexpand: true,
|
||||||
gtk::ScrolledWindow {
|
#[name = "stack_switcher"]
|
||||||
model.sheet_listing.widget(),
|
gtk::StackSwitcher {
|
||||||
set_vexpand: true,
|
set_stack: Some(&stack),
|
||||||
set_hexpand: true,
|
set_margin_all: 10,
|
||||||
// set_hscrollbar_policy: PolicyType::Never,
|
},
|
||||||
|
#[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(),
|
model.mcdu.widget(),
|
||||||
@ -52,7 +68,7 @@ impl SimpleComponent for AppModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn init(
|
fn init(
|
||||||
path: Self::Init,
|
file_validation_result: Self::Init,
|
||||||
window: &Self::Root,
|
window: &Self::Root,
|
||||||
sender: ComponentSender<Self>,
|
sender: ComponentSender<Self>,
|
||||||
) -> relm4::ComponentParts<Self> {
|
) -> relm4::ComponentParts<Self> {
|
||||||
@ -64,16 +80,37 @@ impl SimpleComponent for AppModel {
|
|||||||
McduOutput::SearchStarted(query) => AppInput::SearchStarted(query),
|
McduOutput::SearchStarted(query) => AppInput::SearchStarted(query),
|
||||||
});
|
});
|
||||||
|
|
||||||
let sheet_listing =
|
let new_files: Vec<SheetModelType> = file_validation_result
|
||||||
SheetListingModel::builder()
|
.unassigned_files
|
||||||
.launch(path)
|
.iter()
|
||||||
.forward(sender.input_sender(), |response| match response {
|
.map(|file| SheetModelType::Pdf {
|
||||||
SheetPressedMessage::SheetPressed(path) => AppInput::SheetPressed(path),
|
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 {
|
let model = AppModel {
|
||||||
mcdu,
|
mcdu,
|
||||||
sheet_listing,
|
sheets_and_files_listing,
|
||||||
|
new_files_listing,
|
||||||
};
|
};
|
||||||
|
|
||||||
let widgets = view_output!();
|
let widgets = view_output!();
|
||||||
@ -84,7 +121,7 @@ impl SimpleComponent for AppModel {
|
|||||||
fn update(&mut self, message: Self::Input, _sender: ComponentSender<Self>) {
|
fn update(&mut self, message: Self::Input, _sender: ComponentSender<Self>) {
|
||||||
match message {
|
match message {
|
||||||
AppInput::SheetPressed(sheet) => opener::open(sheet).unwrap(),
|
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 }),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
pub mod app;
|
pub mod app;
|
||||||
pub mod mcdu;
|
pub mod mcdu;
|
||||||
pub mod sheet_listing;
|
pub mod sheet_listing;
|
||||||
|
pub mod sheet_model;
|
||||||
|
@ -1,83 +1,18 @@
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
|
use relm4::factory::FactoryVecDeque;
|
||||||
use relm4::prelude::*;
|
use relm4::prelude::*;
|
||||||
use relm4::{
|
use relm4::{
|
||||||
gtk, Component, ComponentController, ComponentParts, ComponentSender, SimpleComponent,
|
gtk, Component, ComponentController, ComponentParts, ComponentSender, SimpleComponent,
|
||||||
};
|
};
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
pub struct SheetModel {
|
use super::sheet_model::{SheetModel, SheetModelType};
|
||||||
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<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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct SheetListingModel {
|
pub struct SheetListingModel {
|
||||||
query: String,
|
query: String,
|
||||||
sheet_models: Vec<Controller<SheetModel>>,
|
sheets: FactoryVecDeque<SheetModel>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -87,45 +22,35 @@ pub struct SheetListingInput {
|
|||||||
|
|
||||||
#[relm4::component(pub)]
|
#[relm4::component(pub)]
|
||||||
impl SimpleComponent for SheetListingModel {
|
impl SimpleComponent for SheetListingModel {
|
||||||
type Init = PathBuf;
|
type Init = Vec<SheetModelType>;
|
||||||
type Input = SheetListingInput;
|
type Input = SheetListingInput;
|
||||||
type Output = SheetPressedMessage;
|
type Output = ();
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
#[root]
|
#[root]
|
||||||
gtk::Box {
|
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(
|
fn init(
|
||||||
dir: Self::Init,
|
init: Self::Init,
|
||||||
root: &Self::Root,
|
root: &Self::Root,
|
||||||
sender: ComponentSender<Self>,
|
sender: ComponentSender<Self>,
|
||||||
) -> ComponentParts<Self> {
|
) -> ComponentParts<Self> {
|
||||||
let mut sheet_models = Vec::new();
|
let mut sheets = FactoryVecDeque::new(gtk::Box::default(), sender.input_sender());
|
||||||
|
for sheet_model_type in init {
|
||||||
for entry in WalkDir::new(dir)
|
sheets.guard().push_back(sheet_model_type);
|
||||||
.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 model = SheetListingModel {
|
let model = SheetListingModel {
|
||||||
query: String::new(),
|
query: String::new(),
|
||||||
sheet_models,
|
sheets,
|
||||||
};
|
};
|
||||||
let widgets = view_output!();
|
let widgets = view_output!();
|
||||||
ComponentParts { model, widgets }
|
ComponentParts { model, widgets }
|
||||||
@ -133,8 +58,5 @@ impl SimpleComponent for SheetListingModel {
|
|||||||
|
|
||||||
fn update(&mut self, message: Self::Input, _sender: ComponentSender<Self>) {
|
fn update(&mut self, message: Self::Input, _sender: ComponentSender<Self>) {
|
||||||
self.query = message.query;
|
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
48
src/ui/sheet_model.rs
Normal 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 = >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>) -> Self {
|
||||||
|
let label = match value {
|
||||||
|
SheetModelType::Sheet { sheet } => sheet.name,
|
||||||
|
SheetModelType::Pdf { path } => path.file_name().unwrap().to_str().unwrap().to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
SheetModel { label }
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user