Add info boxes contents
This commit is contained in:
@@ -11,3 +11,5 @@ color-eyre.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
directories.workspace = true
|
||||
|
||||
models = { path = "../models" }
|
||||
|
||||
@@ -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<String>,
|
||||
path: PathBuf,
|
||||
db_opts: Options,
|
||||
cf_opts: Options,
|
||||
context: Option<RocksDB>
|
||||
cf_opts: Options
|
||||
}
|
||||
|
||||
impl RocksDBFactory {
|
||||
pub fn new(path: PathBuf, db_opts: Options, cf_opts: Options) -> Result<Self> {
|
||||
let instance = Self {
|
||||
let mut instance = Self {
|
||||
cfs: vec![],
|
||||
path,
|
||||
db_opts,
|
||||
cf_opts,
|
||||
context: None
|
||||
cf_opts
|
||||
};
|
||||
instance.register::<models::dlsite::DLSiteManiax>();
|
||||
instance.register::<models::dlsite::DLSiteCategory>();
|
||||
instance.register::<models::dlsite::DLSiteGenre>();
|
||||
if !instance.path.exists() {
|
||||
std::fs::create_dir_all(instance.path.as_path())?;
|
||||
}
|
||||
Ok(instance)
|
||||
}
|
||||
|
||||
pub fn register<T>(&mut self) where T: RocksColumn {
|
||||
fn register<T>(&mut self) where T: RocksColumn {
|
||||
self.cfs.push(T::get_column_name());
|
||||
}
|
||||
|
||||
pub fn get_current_context(&mut self) -> Result<RocksDB> {
|
||||
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::<Vec<_>>();
|
||||
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<OptimisticTransactionDB>,
|
||||
}
|
||||
|
||||
impl Drop for RocksDB {
|
||||
fn drop(&mut self) {
|
||||
std::mem::drop(self.db.clone());
|
||||
}
|
||||
}
|
||||
|
||||
impl RocksDB {
|
||||
pub fn new(cfs: Vec<ColumnFamilyDescriptor>, path: PathBuf, db_opts: Options) -> Result<Self> {
|
||||
let db = OptimisticTransactionDB::open_cf_descriptors(
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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<u16>,
|
||||
//TODO: have to become multilingual
|
||||
pub name: DLSiteTranslation
|
||||
}
|
||||
|
||||
|
||||
@@ -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<DLSiteTranslation>
|
||||
pub name: DLSiteTranslations
|
||||
}
|
||||
|
||||
impl RocksColumn for DLSiteGenre {
|
||||
|
||||
@@ -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<u16>,
|
||||
pub name: Vec<DLSiteTranslation>,
|
||||
pub name: DLSiteTranslations,
|
||||
pub sells_count: u32,
|
||||
pub folder_path: PathBuf,
|
||||
pub version: Option<String>
|
||||
@@ -22,7 +22,7 @@ impl From<super::crawler::DLSiteManiax> 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
|
||||
@@ -57,3 +57,9 @@ impl Into<Text<'_>> for &DLSiteManiax {
|
||||
Text::from(self.rj_num.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Text<'_>> for DLSiteManiax {
|
||||
fn into(self) -> Text<'static> {
|
||||
Text::from(self.rj_num.to_string())
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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<DLSiteTranslation>);
|
||||
|
||||
impl DLSiteTranslations {
|
||||
pub fn get_translation(&self, language: LanguageTag) -> color_eyre::Result<String> {
|
||||
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<Self> {
|
||||
@@ -44,13 +68,12 @@ impl TryFrom<String> for DLSiteTranslation {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryInto<String> for DLSiteTranslation {
|
||||
type Error = Report;
|
||||
impl Into<String> for &DLSiteTranslation {
|
||||
|
||||
fn try_into(self) -> Result<String, Self::Error> {
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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! {
|
||||
|
||||
@@ -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<Self> {
|
||||
let config = ApplicationConfig::get_config()?;
|
||||
let mut db_factory = RocksDBFactory::default();
|
||||
db_factory.register::<DLSiteManiax>();
|
||||
db_factory.register::<DLSiteGenre>();
|
||||
db_factory.register::<DLSiteCategory>();
|
||||
let db_factory = RocksDBFactory::default();
|
||||
let state = AppState {
|
||||
view: Some(AppView::Main(MainView::new(db_factory.clone())?)),
|
||||
};
|
||||
|
||||
@@ -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::<DLSiteManiax>();
|
||||
db_factory.register::<DLSiteGenre>();
|
||||
db_factory.register::<DLSiteCategory>();
|
||||
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();
|
||||
|
||||
@@ -2,7 +2,6 @@ mod app;
|
||||
mod cli;
|
||||
mod event;
|
||||
mod helpers;
|
||||
mod models;
|
||||
mod widgets;
|
||||
|
||||
pub use cli::Cli;
|
||||
@@ -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<T> {
|
||||
pub games: Vec<T>,
|
||||
pub state: ListState,
|
||||
}
|
||||
|
||||
impl<T> Default for GameList<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
games: Vec::new(),
|
||||
state: ListState::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> GameList<T>
|
||||
where T: DeserializeOwned + RocksColumn
|
||||
{
|
||||
pub fn new(games: Vec<T>) -> Result<Self> {
|
||||
let mut state = ListState::default();
|
||||
state.select_first();
|
||||
let game_list = GameList {
|
||||
games,
|
||||
state
|
||||
};
|
||||
Ok(game_list)
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
mod game_list;
|
||||
pub use game_list::*;
|
||||
82
ui/src/widgets/components/game_info_box.rs
Executable file
82
ui/src/widgets/components/game_info_box.rs
Executable file
@@ -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<String>,
|
||||
active: bool
|
||||
}
|
||||
|
||||
impl Component for GameInfoBoxState {
|
||||
fn set_active(&mut self, active: bool) {
|
||||
self.active = active;
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<DLSiteManiax> for GameInfoBoxState {
|
||||
type Error = color_eyre::Report;
|
||||
|
||||
fn try_from(value: DLSiteManiax) -> Result<Self, Self::Error> {
|
||||
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::<DLSiteGenre, DLSiteManiax>(&value.get_reference_ids())?
|
||||
.iter()
|
||||
.map(|v| v.name.get_translation(locale.clone()))
|
||||
.filter_map(Result::ok)
|
||||
.collect::<Vec<_>>();
|
||||
Ok(Self { game_title, genres: game_genres, active: false })
|
||||
}
|
||||
}
|
||||
|
||||
impl GameInfoBox {
|
||||
pub fn new(maniax: DLSiteManiax) -> color_eyre::Result<Self> {
|
||||
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);;
|
||||
}
|
||||
}
|
||||
108
ui/src/widgets/components/game_list.rs
Executable file
108
ui/src/widgets/components/game_list.rs
Executable file
@@ -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<ListItem<'a>> + DeserializeOwned + RocksColumn + Clone {
|
||||
pub state: GameListState<'a, T>
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GameListState<'a, T> where T: Into<ListItem<'a>> + DeserializeOwned + RocksColumn + Clone {
|
||||
games: Vec<T>,
|
||||
list_state: ListState,
|
||||
list_page_size: usize,
|
||||
active: bool,
|
||||
_phantom: PhantomData<&'a ()>
|
||||
}
|
||||
|
||||
impl<'a, T> Component for GameList<'a, T> where T: Into<ListItem<'a>> + 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<ListItem<'a>> + DeserializeOwned + RocksColumn + Clone {
|
||||
fn default() -> Self {
|
||||
Self::new(vec![])
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> StatefulWidget for GameList<'a, T>
|
||||
where
|
||||
T: Into<ListItem<'a>> + 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<ListItem<'a>> + DeserializeOwned + RocksColumn + Clone
|
||||
{
|
||||
pub fn new(games: Vec<T>) -> Self {
|
||||
let mut state = ListState::default();
|
||||
state.select_first();
|
||||
let game_list = GameList {
|
||||
state: GameListState::<T> {
|
||||
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))
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
);
|
||||
|
||||
@@ -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<DLSiteManiax>,
|
||||
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<Self> {
|
||||
let mut games = {
|
||||
let db = db_factory.get_current_context()?;
|
||||
let mut games = db.get_all_values::<DLSiteManiax>()?;
|
||||
let values = db.get_all_values::<DLSiteManiax>()?;
|
||||
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) {
|
||||
|
||||
Reference in New Issue
Block a user