Add basic list navigation and display
This commit is contained in:
@@ -20,7 +20,7 @@ impl App {
|
||||
pub async fn create() -> Result<Self> {
|
||||
let config = ApplicationConfig::get_config()?;
|
||||
let state = AppState {
|
||||
view: Some(AppView::Main(MainView::new())),
|
||||
view: Some(AppView::Main(MainView::new()?)),
|
||||
};
|
||||
let app = Self {
|
||||
events: EventHandler::new(Duration::from_millis(config.basic_config.tick_rate)),
|
||||
@@ -69,7 +69,7 @@ impl App {
|
||||
match current_view {
|
||||
AppView::Main(main_view) => {
|
||||
frame.render_stateful_widget(
|
||||
MainView::new(),
|
||||
MainView::new().unwrap(),
|
||||
frame.area(),
|
||||
&mut main_view.state,
|
||||
);
|
||||
|
||||
@@ -48,7 +48,7 @@ impl DLSiteSyncCommand {
|
||||
pub async fn handle(&self) -> Result<()> {
|
||||
let now = Instant::now();
|
||||
let app_conf = ApplicationConfig::get_config()?;
|
||||
let mut db = RocksDB::new(DB_OPTIONS.clone(), DB_CF_OPTIONS.clone())?;
|
||||
let mut db = RocksDB::default();
|
||||
let crawler = DLSiteCrawler::new()?;
|
||||
if self.do_sync_genre {
|
||||
let genre_now = Instant::now();
|
||||
|
||||
@@ -4,17 +4,13 @@ use color_eyre::Result;
|
||||
use std::path::PathBuf;
|
||||
use language_tags::LanguageTag;
|
||||
use ratatui::widgets::ListState;
|
||||
use serde::Deserialize;
|
||||
use serde_json;
|
||||
|
||||
pub mod types;
|
||||
|
||||
const CONFIG_KEY: &str = "app_conf";
|
||||
|
||||
pub(crate) struct GameList<T> {
|
||||
games: Vec<T>,
|
||||
state: ListState,
|
||||
}
|
||||
|
||||
impl ApplicationConfig {
|
||||
pub fn get_config() -> Result<Self> {
|
||||
if CACHE_MAP.contains_key(CONFIG_KEY) &&
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::constants::{APP_DB_DATA_DIR, DB_COLUMNS};
|
||||
use crate::constants::{APP_DB_DATA_DIR, DB_CF_OPTIONS, DB_COLUMNS, DB_OPTIONS};
|
||||
use rocksdb::{ColumnFamilyDescriptor, IteratorMode, OptimisticTransactionDB, Options};
|
||||
use serde::{Serialize};
|
||||
use serde::de::DeserializeOwned;
|
||||
@@ -9,6 +9,12 @@ pub struct RocksDB {
|
||||
db: OptimisticTransactionDB,
|
||||
}
|
||||
|
||||
impl Default for RocksDB {
|
||||
fn default() -> Self {
|
||||
RocksDB::new(DB_OPTIONS.clone(), DB_CF_OPTIONS.clone()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl RocksDB {
|
||||
pub fn new(db_opts: Options, cf_opts: Options) -> Result<Self> {
|
||||
let cfs = DB_COLUMNS.iter()
|
||||
|
||||
@@ -1,12 +1,47 @@
|
||||
use color_eyre::Result;
|
||||
use std::path::PathBuf;
|
||||
use color_eyre::{eyre, Report};
|
||||
use ratatui::prelude::Text;
|
||||
use ratatui::widgets::ListState;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::de::DeserializeOwned;
|
||||
use crate::config::types::ApplicationConfig;
|
||||
use crate::constants::{EN_LOCALE, JP_LOCALE};
|
||||
use crate::crawler::DLSiteGenreCategory;
|
||||
use crate::helpers::db::RocksDB;
|
||||
use crate::helpers::matches_primary_language;
|
||||
use crate::models::{RocksColumn, RocksReferences};
|
||||
|
||||
#[derive(Debug)]
|
||||
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() -> Result<Self> {
|
||||
let db = RocksDB::default();
|
||||
let mut state = ListState::default();
|
||||
state.select_first();
|
||||
let game_list = GameList {
|
||||
games: db.get_all_values::<T>()?,
|
||||
state
|
||||
};
|
||||
Ok(game_list)
|
||||
}
|
||||
}
|
||||
|
||||
//region Maniax
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub(crate) struct DLSiteManiax {
|
||||
@@ -54,6 +89,12 @@ impl RocksReferences<DLSiteGenre> for DLSiteManiax {
|
||||
self.genre_ids.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Text<'_>> for &DLSiteManiax {
|
||||
fn into(self) -> Text<'static> {
|
||||
Text::from(self.rj_num.to_string())
|
||||
}
|
||||
}
|
||||
//endregion
|
||||
|
||||
//region Genre
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
use crate::config::types::ApplicationConfig;
|
||||
use crate::widgets::popups::folder::AddFolderPopup;
|
||||
use crossterm::event::KeyCode::Char;
|
||||
use crossterm::event::{Event, KeyCode, KeyEventKind};
|
||||
use crossterm::event::{Event, KeyCode, KeyEvent, 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::widgets::{Block, Borders, Paragraph, StatefulWidget};
|
||||
use ratatui::style::Modifier;
|
||||
use ratatui::style::palette::tailwind::SLATE;
|
||||
use ratatui::widgets::{Block, Borders, HighlightSpacing, List, Paragraph, StatefulWidget};
|
||||
use crate::models::{DLSiteManiax, GameList};
|
||||
use crate::widgets::popups::AppPopup;
|
||||
use crate::widgets::views::View;
|
||||
|
||||
@@ -17,6 +20,8 @@ pub struct MainView {
|
||||
pub struct MainViewState {
|
||||
popup: Option<AppPopup>,
|
||||
status: Status,
|
||||
dl_game_list: GameList<DLSiteManiax>,
|
||||
list_page_size: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
@@ -27,13 +32,17 @@ enum Status {
|
||||
}
|
||||
|
||||
impl MainView {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
pub fn new() -> color_eyre::Result<Self> {
|
||||
let dl_game_list = GameList::new()?;
|
||||
let view = Self {
|
||||
state: MainViewState {
|
||||
popup: None,
|
||||
status: Status::Running,
|
||||
list_page_size: 0,
|
||||
dl_game_list
|
||||
}
|
||||
}
|
||||
};
|
||||
Ok(view)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +81,29 @@ 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 {
|
||||
@@ -94,6 +126,7 @@ impl View for MainView {
|
||||
Char('a') => state.folder_popup(),
|
||||
_ => {}
|
||||
}
|
||||
state.handle_game_list_key(key_event)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@@ -119,8 +152,9 @@ impl StatefulWidget for MainView {
|
||||
])
|
||||
.split(area);
|
||||
|
||||
state.list_page_size = chunks[1].height as usize;
|
||||
Self::render_header(chunks[0], buf);
|
||||
Self::render_game_list(chunks[1], buf);
|
||||
Self::render_game_info(chunks[1], buf, state);
|
||||
Self::render_footer(state, chunks[2], buf);
|
||||
|
||||
let Some(popup) = state.popup.as_mut() else {
|
||||
@@ -148,12 +182,30 @@ impl HasScreenCursor for MainView {
|
||||
}
|
||||
|
||||
impl MainView {
|
||||
fn render_game_list(area: Rect, buf: &mut Buffer) {
|
||||
let game_list = Block::new()
|
||||
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)
|
||||
.constraints([
|
||||
Constraint::Length(13),
|
||||
])
|
||||
.split(area);
|
||||
Self::render_game_list(chunks[0], 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());
|
||||
game_list.render(area, buf);
|
||||
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_header(area: Rect, buf: &mut Buffer) {
|
||||
|
||||
Reference in New Issue
Block a user