From e76f12527fc850cbda6f0782d146662d3ddd8012 Mon Sep 17 00:00:00 2001 From: fromost Date: Mon, 15 Dec 2025 02:12:01 +0800 Subject: [PATCH] Add info boxes contents --- db/Cargo.toml | 2 + db/src/lib.rs | 32 +++--- models/Cargo.toml | 1 - db/src/types.rs => models/src/db.rs | 0 models/src/dlsite/category.rs | 3 +- models/src/dlsite/genre.rs | 6 +- models/src/dlsite/maniax.rs | 14 ++- models/src/dlsite/mod.rs | 2 +- models/src/dlsite/translation.rs | 35 +++++-- models/src/lib.rs | 1 + ui/src/app.rs | 7 +- ui/src/cli/sync.rs | 25 ++--- ui/src/lib.rs | 1 - ui/src/models/game_list.rs | 33 ------- ui/src/models/mod.rs | 2 - ui/src/widgets/components/game_info_box.rs | 82 ++++++++++++++++ ui/src/widgets/components/game_list.rs | 108 +++++++++++++++++++++ ui/src/widgets/components/mod.rs | 9 ++ ui/src/widgets/popups/folder.rs | 4 +- ui/src/widgets/views/main_view.rs | 89 +++++------------ 20 files changed, 307 insertions(+), 149 deletions(-) rename db/src/types.rs => models/src/db.rs (100%) delete mode 100755 ui/src/models/game_list.rs delete mode 100755 ui/src/models/mod.rs create mode 100755 ui/src/widgets/components/game_info_box.rs create mode 100755 ui/src/widgets/components/game_list.rs diff --git a/db/Cargo.toml b/db/Cargo.toml index 2a22134..a5574af 100755 --- a/db/Cargo.toml +++ b/db/Cargo.toml @@ -11,3 +11,5 @@ color-eyre.workspace = true serde.workspace = true serde_json.workspace = true directories.workspace = true + +models = { path = "../models" } diff --git a/db/src/lib.rs b/db/src/lib.rs index b6a96b7..c071889 100755 --- a/db/src/lib.rs +++ b/db/src/lib.rs @@ -1,14 +1,13 @@ -pub mod types; - use std::path::PathBuf; -use std::sync::Arc; +use std::sync::{Arc, Mutex}; +use color_eyre::eyre::eyre; use rocksdb::{ColumnFamilyDescriptor, IteratorMode, OptimisticTransactionDB, Options}; use serde::{Serialize}; use serde::de::DeserializeOwned; -use crate::types::{RocksColumn, RocksReference, RocksReferences}; use color_eyre::Result; use directories::BaseDirs; use lazy_static::lazy_static; +use models::db::{RocksColumn, RocksReference, RocksReferences}; const APP_DIR_NAME: &str = "sus_manager"; lazy_static! { @@ -37,39 +36,36 @@ pub struct RocksDBFactory { cfs: Vec, path: PathBuf, db_opts: Options, - cf_opts: Options, - context: Option + cf_opts: Options } impl RocksDBFactory { pub fn new(path: PathBuf, db_opts: Options, cf_opts: Options) -> Result { - let instance = Self { + let mut instance = Self { cfs: vec![], path, db_opts, - cf_opts, - context: None + cf_opts }; + instance.register::(); + instance.register::(); + instance.register::(); if !instance.path.exists() { std::fs::create_dir_all(instance.path.as_path())?; } Ok(instance) } - pub fn register(&mut self) where T: RocksColumn { + fn register(&mut self) where T: RocksColumn { self.cfs.push(T::get_column_name()); } pub fn get_current_context(&mut self) -> Result { - if let Some(context) = &self.context { - return Ok(context.clone()); - } let cfs = self.cfs .iter() .map(|cf| ColumnFamilyDescriptor::new(cf, self.cf_opts.clone())) .collect::>(); let context = RocksDB::new(cfs, self.path.clone(), self.db_opts.clone())?; - self.context = Some(context.clone()); Ok(context) } } @@ -80,11 +76,17 @@ impl Default for RocksDBFactory { } } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct RocksDB { db: Arc, } +impl Drop for RocksDB { + fn drop(&mut self) { + std::mem::drop(self.db.clone()); + } +} + impl RocksDB { pub fn new(cfs: Vec, path: PathBuf, db_opts: Options) -> Result { let db = OptimisticTransactionDB::open_cf_descriptors( diff --git a/models/Cargo.toml b/models/Cargo.toml index 277db78..6c6d2e6 100755 --- a/models/Cargo.toml +++ b/models/Cargo.toml @@ -11,6 +11,5 @@ lazy_static.workspace = true ratatui.workspace = true serde_json.workspace = true dashmap.workspace = true -db = { path = "../db" } language-tags = { version = "0.3.2", features = ["serde"] } sys-locale = "0.3.2" diff --git a/db/src/types.rs b/models/src/db.rs similarity index 100% rename from db/src/types.rs rename to models/src/db.rs diff --git a/models/src/dlsite/category.rs b/models/src/dlsite/category.rs index e3266a3..30875b7 100755 --- a/models/src/dlsite/category.rs +++ b/models/src/dlsite/category.rs @@ -1,7 +1,7 @@ use color_eyre::Report; use serde::{Deserialize, Serialize}; -use db::types::{RocksColumn, RocksReferences}; use crate::config::ApplicationConfig; +use crate::db::{RocksColumn, RocksReferences}; use crate::dlsite::genre::DLSiteGenre; use crate::dlsite::translation::DLSiteTranslation; @@ -10,6 +10,7 @@ pub struct DLSiteCategory { #[serde(skip)] pub id: String, pub genre_ids: Vec, + //TODO: have to become multilingual pub name: DLSiteTranslation } diff --git a/models/src/dlsite/genre.rs b/models/src/dlsite/genre.rs index 1e657d8..81d32fc 100755 --- a/models/src/dlsite/genre.rs +++ b/models/src/dlsite/genre.rs @@ -1,12 +1,12 @@ use serde::{Deserialize, Serialize}; -use db::types::RocksColumn; -use super::translation::DLSiteTranslation; +use crate::db::RocksColumn; +use crate::dlsite::DLSiteTranslations; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct DLSiteGenre { #[serde(skip)] pub id: u16, - pub name: Vec + pub name: DLSiteTranslations } impl RocksColumn for DLSiteGenre { diff --git a/models/src/dlsite/maniax.rs b/models/src/dlsite/maniax.rs index 008f590..e0d34dc 100755 --- a/models/src/dlsite/maniax.rs +++ b/models/src/dlsite/maniax.rs @@ -1,16 +1,16 @@ use std::path::PathBuf; use ratatui::text::Text; use serde::{Deserialize, Serialize}; -use db::types::{RocksColumn, RocksReferences}; +use crate::db::{RocksColumn, RocksReferences}; use super::genre::DLSiteGenre; -use super::translation::DLSiteTranslation; +use super::translation::{DLSiteTranslation, DLSiteTranslations}; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct DLSiteManiax { #[serde(skip)] pub rj_num: String, pub genre_ids: Vec, - pub name: Vec, + pub name: DLSiteTranslations, pub sells_count: u32, pub folder_path: PathBuf, pub version: Option @@ -22,7 +22,7 @@ impl From for DLSiteManiax { Self { rj_num: value.rj_num, genre_ids: value.genre_ids, - name: vec![title], + name: DLSiteTranslations(vec![title]), sells_count: value.sells_count, folder_path: value.folder_path, version: None @@ -56,4 +56,10 @@ impl Into> for &DLSiteManiax { fn into(self) -> Text<'static> { Text::from(self.rj_num.to_string()) } +} + +impl Into> for DLSiteManiax { + fn into(self) -> Text<'static> { + Text::from(self.rj_num.to_string()) + } } \ No newline at end of file diff --git a/models/src/dlsite/mod.rs b/models/src/dlsite/mod.rs index 133399a..2ed2019 100755 --- a/models/src/dlsite/mod.rs +++ b/models/src/dlsite/mod.rs @@ -8,7 +8,7 @@ mod genre; mod maniax; pub mod crawler; -pub use translation::{EN_LOCALE, JP_LOCALE, DLSiteTranslation}; +pub use translation::*; pub use category::DLSiteCategory; pub use genre::DLSiteGenre; pub use maniax::DLSiteManiax; diff --git a/models/src/dlsite/translation.rs b/models/src/dlsite/translation.rs index b3e6bc4..30bd503 100755 --- a/models/src/dlsite/translation.rs +++ b/models/src/dlsite/translation.rs @@ -4,7 +4,7 @@ use language_tags::LanguageTag; use serde::{Deserialize, Serialize}; use lazy_static::lazy_static; use crate::config::ApplicationConfig; -use super::matches_primary_language; +use super::{matches_primary_language, PrimaryLanguage}; lazy_static! { pub static ref EN_LOCALE: LanguageTag = LanguageTag::parse("en").unwrap(); @@ -17,6 +17,30 @@ pub enum DLSiteTranslation { EN(String), JP(String) } +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct DLSiteTranslations(pub Vec); + +impl DLSiteTranslations { + pub fn get_translation(&self, language: LanguageTag) -> color_eyre::Result { + let Self(translations) = self; + let primary_language = PrimaryLanguage::try_from(&language)?; + let translation = match primary_language { + PrimaryLanguage::EN => translations.iter().find(|v| matches!(v, DLSiteTranslation::EN(_))), + PrimaryLanguage::JP => translations.iter().find(|v| matches!(v, DLSiteTranslation::JP(_))), + }; + match translation { + Some(translation) => Ok(translation.to_string()), + None => Err(eyre!("No translation found for {:?}", language)) + } + } +} + +impl DLSiteTranslation { + pub fn to_string(&self) -> String { + self.into() + } +} + impl TryFrom<&str> for DLSiteTranslation { type Error = Report; fn try_from(value: &str) -> color_eyre::Result { @@ -44,13 +68,12 @@ impl TryFrom for DLSiteTranslation { } } -impl TryInto for DLSiteTranslation { - type Error = Report; +impl Into for &DLSiteTranslation { - fn try_into(self) -> Result { + fn into(self) -> String { match self { - DLSiteTranslation::EN(val) => Ok(val), - DLSiteTranslation::JP(val) => Ok(val), + DLSiteTranslation::EN(val) => val.to_string(), + DLSiteTranslation::JP(val) => val.to_string(), } } } \ No newline at end of file diff --git a/models/src/lib.rs b/models/src/lib.rs index fdfa936..3afade0 100755 --- a/models/src/lib.rs +++ b/models/src/lib.rs @@ -7,6 +7,7 @@ use lazy_static::lazy_static; pub mod dlsite; pub mod config; +pub mod db; const APP_DIR_NAME: &str = "sus_manager"; lazy_static! { diff --git a/ui/src/app.rs b/ui/src/app.rs index 039a8be..a0e2ffb 100755 --- a/ui/src/app.rs +++ b/ui/src/app.rs @@ -5,7 +5,7 @@ use crossterm::event::{Event}; use ratatui::{DefaultTerminal, Frame}; use std::time::Duration; use color_eyre::eyre::eyre; -use db::RocksDBFactory; +use db::{RocksDBFactory}; use models::config::ApplicationConfig; use models::dlsite::{DLSiteCategory, DLSiteGenre, DLSiteManiax}; @@ -23,10 +23,7 @@ pub struct AppState { impl App { pub async fn create() -> Result { let config = ApplicationConfig::get_config()?; - let mut db_factory = RocksDBFactory::default(); - db_factory.register::(); - db_factory.register::(); - db_factory.register::(); + let db_factory = RocksDBFactory::default(); let state = AppState { view: Some(AppView::Main(MainView::new(db_factory.clone())?)), }; diff --git a/ui/src/cli/sync.rs b/ui/src/cli/sync.rs index 805c6d3..8987135 100755 --- a/ui/src/cli/sync.rs +++ b/ui/src/cli/sync.rs @@ -8,9 +8,9 @@ use indicatif::{ProgressBar, ProgressStyle}; use itertools::Itertools; use tokio::time::Instant; use crawler::DLSiteCrawler; -use db::RocksDBFactory; +use db::{RocksDBFactory}; use models::config::ApplicationConfig; -use models::dlsite::{DLSiteCategory, DLSiteGenre, DLSiteManiax, DLSiteTranslation}; +use models::dlsite::{DLSiteCategory, DLSiteGenre, DLSiteManiax, DLSiteTranslation, DLSiteTranslations}; use crate::helpers; #[derive(Parser, Debug)] @@ -47,10 +47,7 @@ impl DLSiteSyncCommand { pub async fn handle(&self) -> Result<()> { let now = Instant::now(); let app_conf = ApplicationConfig::get_config()?; - let mut db_factory = RocksDBFactory::default(); - db_factory.register::(); - db_factory.register::(); - db_factory.register::(); + let db_factory = RocksDBFactory::default(); let crawler = DLSiteCrawler::new()?; if self.do_sync_genre { let genre_now = Instant::now(); @@ -95,17 +92,20 @@ impl DLSiteSyncCommand { existing_genres.iter().find(|v| v.id == id); if let Some(existing_genre) = existing_genre { let name = DLSiteTranslation::try_from(genre.name)?; - if existing_genre.name.contains(&name) { + let DLSiteTranslations(existing_translations) = existing_genre.name.clone(); + if existing_translations.contains(&name) { modified_genres.push(existing_genre.clone()); continue; } let mut modified_genre = existing_genre.clone(); - modified_genre.name.push(name); + let DLSiteTranslations(mut modified_translations) = modified_genre.name.clone(); + modified_translations.push(name); + modified_genre.name = DLSiteTranslations(modified_translations); modified_genres.push(modified_genre); } else { modified_genres.push(DLSiteGenre { - id, name: vec![DLSiteTranslation::try_from(genre.name)?] + id, name: DLSiteTranslations(vec![DLSiteTranslation::try_from(genre.name)?]) }); } } @@ -135,12 +135,15 @@ impl DLSiteSyncCommand { .find(|v| v.rj_num == maniax.rj_num); if let Some(existing_maniax) = existing_maniax { let name = DLSiteTranslation::try_from(maniax.title)?; - if existing_maniax.name.contains(&name) { + let DLSiteTranslations(existing_translations) = existing_maniax.name.clone(); + if existing_translations.contains(&name) { modified_maniaxes.push(existing_maniax.clone()); continue; } let mut modified_maniax = existing_maniax.clone(); - modified_maniax.name.push(name); + let DLSiteTranslations(mut modified_translations) = modified_maniax.name.clone(); + modified_translations.push(name); + modified_maniax.name = DLSiteTranslations(modified_translations); modified_maniaxes.push(modified_maniax); } else { let mut value: DLSiteManiax = maniax.into(); diff --git a/ui/src/lib.rs b/ui/src/lib.rs index 6c1326b..4d45880 100755 --- a/ui/src/lib.rs +++ b/ui/src/lib.rs @@ -2,7 +2,6 @@ mod app; mod cli; mod event; mod helpers; -mod models; mod widgets; pub use cli::Cli; \ No newline at end of file diff --git a/ui/src/models/game_list.rs b/ui/src/models/game_list.rs deleted file mode 100755 index 85f9c7d..0000000 --- a/ui/src/models/game_list.rs +++ /dev/null @@ -1,33 +0,0 @@ -use color_eyre::Result; -use ratatui::widgets::ListState; -use serde::de::DeserializeOwned; -use db::types::RocksColumn; - -#[derive(Debug, Clone)] -pub(crate) struct GameList { - pub games: Vec, - pub state: ListState, -} - -impl Default for GameList { - fn default() -> Self { - Self { - games: Vec::new(), - state: ListState::default(), - } - } -} - -impl GameList - where T: DeserializeOwned + RocksColumn -{ - pub fn new(games: Vec) -> Result { - let mut state = ListState::default(); - state.select_first(); - let game_list = GameList { - games, - state - }; - Ok(game_list) - } -} \ No newline at end of file diff --git a/ui/src/models/mod.rs b/ui/src/models/mod.rs deleted file mode 100755 index f3fadf8..0000000 --- a/ui/src/models/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -mod game_list; -pub use game_list::*; \ No newline at end of file diff --git a/ui/src/widgets/components/game_info_box.rs b/ui/src/widgets/components/game_info_box.rs new file mode 100755 index 0000000..73c15db --- /dev/null +++ b/ui/src/widgets/components/game_info_box.rs @@ -0,0 +1,82 @@ +use ratatui::buffer::Buffer; +use ratatui::layout::Rect; +use ratatui::prelude::Line; +use ratatui::style::Style; +use ratatui::widgets::{Block, Borders, Paragraph, StatefulWidget, Widget}; +use db::RocksDBFactory; +use models::config::ApplicationConfig; +use models::db::RocksReferences; +use models::dlsite::{DLSiteGenre, DLSiteManiax, DLSiteTranslation}; +use crate::widgets::components::Component; + +#[derive(Clone)] +pub struct GameInfoBox { + pub state: GameInfoBoxState +} + +#[derive(Clone)] +pub struct GameInfoBoxState { + game_title: String, + genres: Vec, + active: bool +} + +impl Component for GameInfoBoxState { + fn set_active(&mut self, active: bool) { + self.active = active; + } +} + +impl TryFrom for GameInfoBoxState { + type Error = color_eyre::Report; + + fn try_from(value: DLSiteManiax) -> Result { + let db = RocksDBFactory::default().get_current_context()?; + let locale = ApplicationConfig::get_config()?.basic_config.locale; + let game_title = value.name.get_translation(locale.clone())?; + let game_genres = + db.get_reference_values::(&value.get_reference_ids())? + .iter() + .map(|v| v.name.get_translation(locale.clone())) + .filter_map(Result::ok) + .collect::>(); + Ok(Self { game_title, genres: game_genres, active: false }) + } +} + +impl GameInfoBox { + pub fn new(maniax: DLSiteManiax) -> color_eyre::Result { + Ok( + Self { + state: GameInfoBoxState::try_from(maniax)? + } + ) + } + + pub fn set_info(&mut self, maniax: &DLSiteManiax) -> color_eyre::Result<()> { + self.state = GameInfoBoxState::try_from(maniax.clone())?; + Ok(()) + } +} + +impl StatefulWidget for GameInfoBox { + type State = GameInfoBoxState; + + fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) { + let block = Block::new() + .title(Line::raw("Info")) + .borders(Borders::ALL) + .style(Style::default()); + let title_text = Line::styled( + state.game_title.clone(), + Style::default().fg(ratatui::style::Color::Yellow) + ); + let text = Paragraph::new( + vec![ + title_text, + Line::raw(state.genres.join(", ")) + ] + ).block(block.clone()); + text.render(area, buf);; + } +} \ No newline at end of file diff --git a/ui/src/widgets/components/game_list.rs b/ui/src/widgets/components/game_list.rs new file mode 100755 index 0000000..473d9d2 --- /dev/null +++ b/ui/src/widgets/components/game_list.rs @@ -0,0 +1,108 @@ +use std::marker::PhantomData; +use color_eyre::Result; +use crossterm::event::{KeyCode, KeyEvent}; +use ratatui::buffer::Buffer; +use ratatui::layout::Rect; +use ratatui::style::palette::tailwind::SLATE; +use ratatui::style::{Color, Modifier, Style}; +use ratatui::text::{Line}; +use ratatui::widgets::{Block, Borders, HighlightSpacing, List, ListItem, ListState, StatefulWidget}; +use serde::de::DeserializeOwned; +use models::db::RocksColumn; +use crate::widgets::components::Component; + +const SELECTED_STYLE: Style = Style::new() + .bg(SLATE.c800) + .add_modifier(Modifier::BOLD); + +#[derive(Debug, Clone)] +pub struct GameList<'a, T> where T: Into> + DeserializeOwned + RocksColumn + Clone { + pub state: GameListState<'a, T> +} + +#[derive(Debug, Clone)] +pub struct GameListState<'a, T> where T: Into> + DeserializeOwned + RocksColumn + Clone { + games: Vec, + list_state: ListState, + list_page_size: usize, + active: bool, + _phantom: PhantomData<&'a ()> +} + +impl<'a, T> Component for GameList<'a, T> where T: Into> + DeserializeOwned + RocksColumn + Clone { + fn set_active(&mut self, active: bool) { + self.state.active = active; + } +} + +impl<'a, T> Default for GameList<'a, T> where T: Into> + DeserializeOwned + RocksColumn + Clone { + fn default() -> Self { + Self::new(vec![]) + } +} + +impl<'a, T> StatefulWidget for GameList<'a, T> +where + T: Into> + DeserializeOwned + RocksColumn + Clone +{ + type State = GameListState<'a, T>; + + fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) { + let list_block = Block::new() + .title(Line::raw("Games")) + .borders(Borders::ALL); + let list_block = + if state.active { list_block.style(Style::default().fg(Color::Yellow)) } + else { list_block.style(Style::default()) }; + let game_list = List::new(state.games.clone()) + .block(list_block) + .style(Style::default().fg(Color::White)) + .highlight_style(SELECTED_STYLE) + .highlight_symbol(">") + .highlight_spacing(HighlightSpacing::WhenSelected); + state.list_page_size = (area.height - 2) as usize; + game_list.render(area, buf, &mut state.list_state); + } +} + +impl<'a, T> GameList<'a, T> +where + T: Into> + DeserializeOwned + RocksColumn + Clone +{ + pub fn new(games: Vec) -> Self { + let mut state = ListState::default(); + state.select_first(); + let game_list = GameList { + state: GameListState:: { + games, + list_state: state, + _phantom: PhantomData, + list_page_size: 0, + active: false + }, + }; + game_list + } + + pub fn handle_game_list_key(&mut self, event: &KeyEvent) -> Result<()> { + let mut game_list_state = self.state.list_state.clone(); + match event.code { + KeyCode::Down => game_list_state.select_next(), + KeyCode::Up => game_list_state.select_previous(), + KeyCode::PageUp =>game_list_state.scroll_up_by(self.state.list_page_size as u16), + KeyCode::PageDown => game_list_state.scroll_down_by(self.state.list_page_size as u16), + KeyCode::Home => game_list_state.select_first(), + KeyCode::End => game_list_state.select_last(), + _ => {} + } + self.state.list_state = game_list_state; + Ok(()) + } + + pub fn get_selected_value(&self) -> Option<&T> { + let Some(index) = self.state.list_state.selected() else { + return None; + }; + self.state.games.get(index.clamp(0, self.state.games.len() - 1)) + } +} \ No newline at end of file diff --git a/ui/src/widgets/components/mod.rs b/ui/src/widgets/components/mod.rs index 4162e37..cd50edd 100755 --- a/ui/src/widgets/components/mod.rs +++ b/ui/src/widgets/components/mod.rs @@ -1,2 +1,11 @@ mod textarea; +mod game_list; +mod game_info_box; + pub use textarea::*; +pub use game_list::*; +pub use game_info_box::*; + +pub trait Component { + fn set_active(&mut self, active: bool); +} diff --git a/ui/src/widgets/popups/folder.rs b/ui/src/widgets/popups/folder.rs index f60427c..33fd5d2 100755 --- a/ui/src/widgets/popups/folder.rs +++ b/ui/src/widgets/popups/folder.rs @@ -15,8 +15,8 @@ impl AddFolderPopup { let mut textarea = TextArea::new( "Folder Path", "", - |x| { - let path = Path::new(x); + |v| { + let path = Path::new(v); path.exists() && path.is_dir() } ); diff --git a/ui/src/widgets/views/main_view.rs b/ui/src/widgets/views/main_view.rs index 189dc2c..fe3b603 100755 --- a/ui/src/widgets/views/main_view.rs +++ b/ui/src/widgets/views/main_view.rs @@ -1,17 +1,15 @@ use crate::widgets::popups::folder::AddFolderPopup; use crossterm::event::KeyCode::Char; -use crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind}; +use crossterm::event::{Event, KeyCode, KeyEventKind}; use rat_cursor::HasScreenCursor; use ratatui::buffer::Buffer; use ratatui::layout::{Constraint, Direction, Layout, Rect}; use ratatui::prelude::{Color, Line, Span, Style, Text, Widget}; -use ratatui::style::Modifier; -use ratatui::style::palette::tailwind::SLATE; -use ratatui::widgets::{Block, Borders, HighlightSpacing, List, Paragraph, StatefulWidget}; -use db::RocksDBFactory; +use ratatui::widgets::{Block, Borders, Paragraph, StatefulWidget}; +use db::{RocksDBFactory}; use models::config::ApplicationConfig; -use models::dlsite::DLSiteManiax; -use crate::models::GameList; +use models::dlsite::{DLSiteManiax}; +use crate::widgets::components::{Component, GameInfoBox, GameList}; use crate::widgets::popups::AppPopup; use crate::widgets::views::View; @@ -24,8 +22,8 @@ pub struct MainView { #[derive(Clone)] pub struct MainViewState { status: Status, - dl_game_list: GameList, - list_page_size: usize, + dl_game_list: GameList<'static, DLSiteManiax>, + game_info_box: GameInfoBox } #[derive(Clone)] @@ -37,8 +35,11 @@ enum Status { impl MainView { pub fn new(mut db_factory: RocksDBFactory) -> color_eyre::Result { - let db = db_factory.get_current_context()?; - let mut games = db.get_all_values::()?; + let mut games = { + let db = db_factory.get_current_context()?; + let values = db.get_all_values::()?; + values + }; games.sort_by(|a, b| { let left = a.rj_num .chars().skip(2) @@ -52,12 +53,14 @@ impl MainView { .unwrap(); left.cmp(&right) }); - let dl_game_list = GameList::new(games)?; + let first_game = games[0].clone(); + let mut dl_game_list = GameList::new(games); + dl_game_list.set_active(true); let view = Self { state: MainViewState { status: Status::Running, - list_page_size: 0, dl_game_list, + game_info_box: GameInfoBox::new(first_game)? }, db_factory }; @@ -99,29 +102,6 @@ impl MainViewState { } Ok(()) } - - fn handle_game_list_key(&mut self, event: &KeyEvent) -> color_eyre::Result<()> { - let game_list_state = &mut self.dl_game_list.state; - let game_list_len = self.dl_game_list.games.len(); - let selected_value = game_list_state.selected().unwrap_or(0); - match event.code { - KeyCode::Down => game_list_state.select_next(), - KeyCode::Up => game_list_state.select_previous(), - KeyCode::PageUp => { - let selected_index = - if selected_value < self.list_page_size { 0 } - else { selected_value - self.list_page_size }; - game_list_state.select(Some(selected_index)) - }, - KeyCode::PageDown => { - game_list_state.select(Some((selected_value + self.list_page_size).clamp(0, game_list_len))) - }, - KeyCode::Home => game_list_state.select_first(), - KeyCode::End => game_list_state.select_last(), - _ => {} - } - Ok(()) - } } impl View for MainView { @@ -143,7 +123,13 @@ impl View for MainView { Char('a') => state.folder_popup(), _ => {} } - state.handle_game_list_key(key_event)?; + state.dl_game_list.handle_game_list_key(key_event)?; + match state.dl_game_list.get_selected_value() { + Some(value) => { + state.game_info_box.set_info(value)?; + } + None => println!("No game selected") + } } } Ok(()) @@ -169,7 +155,6 @@ impl StatefulWidget for MainView { ]) .split(area); - state.list_page_size = chunks[1].height as usize; Self::render_header(chunks[0], buf); Self::render_game_info(chunks[1], buf, state); Self::render_footer(state, chunks[2], buf); @@ -199,9 +184,6 @@ impl HasScreenCursor for MainView { } impl MainView { - const SELECTED_STYLE: Style = Style::new() - .bg(SLATE.c800) - .add_modifier(Modifier::BOLD); fn render_game_info(area: Rect, buf: &mut Buffer, state: &mut MainViewState) { let chunks = Layout::default() .direction(Direction::Horizontal) @@ -210,29 +192,8 @@ impl MainView { Constraint::Fill(0), ]) .split(area); - Self::render_game_list(chunks[0], buf, state); - Self::render_game_box(chunks[1], buf, state); - } - - fn render_game_list(area: Rect, buf: &mut Buffer, state: &mut MainViewState) { - let list_block = Block::new() - .title(Line::raw("Games")) - .borders(Borders::ALL) - .style(Style::default()); - let game_list = List::new(&state.dl_game_list.games) - .block(list_block) - .highlight_style(Self::SELECTED_STYLE) - .highlight_symbol(">") - .highlight_spacing(HighlightSpacing::WhenSelected); - StatefulWidget::render(game_list, area, buf, &mut state.dl_game_list.state); - } - - fn render_game_box(area: Rect, buf: &mut Buffer, state: &mut MainViewState) { - let block = Block::new() - .title(Line::raw("Info")) - .borders(Borders::ALL) - .style(Style::default()); - block.render(area, buf); + state.dl_game_list.clone().render(chunks[0], buf, &mut state.dl_game_list.state); + state.game_info_box.clone().render(chunks[1], buf, &mut state.game_info_box.state); } fn render_header(area: Rect, buf: &mut Buffer) {