Refactor code to remove Any
This commit is contained in:
@@ -5,6 +5,7 @@ description = "manager for SuS gamers"
|
|||||||
authors = ["fromost"]
|
authors = ["fromost"]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
rust-version = "1.91"
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
debug = true
|
debug = true
|
||||||
@@ -30,7 +31,6 @@ log = "0.4.28"
|
|||||||
num_cpus = "1.17.0"
|
num_cpus = "1.17.0"
|
||||||
sys-locale = "0.3.2"
|
sys-locale = "0.3.2"
|
||||||
jemallocator = "0.5.4"
|
jemallocator = "0.5.4"
|
||||||
reqwest_cookie_store = { version = "0.9.0", features = ["serde"] }
|
|
||||||
itertools = "0.14.0"
|
itertools = "0.14.0"
|
||||||
dashmap = { version = "6.1.0", features = ["serde"] }
|
dashmap = { version = "6.1.0", features = ["serde"] }
|
||||||
|
|
||||||
|
|||||||
36
src/app.rs
36
src/app.rs
@@ -1,13 +1,11 @@
|
|||||||
use crate::config::types::ApplicationConfig;
|
use crate::config::types::ApplicationConfig;
|
||||||
use crate::event::{AppEvent, EventHandler};
|
use crate::event::{AppEvent, EventHandler};
|
||||||
use crate::widgets::views::MainView;
|
use crate::widgets::views::{AppView, MainView};
|
||||||
use crate::widgets::views::View;
|
use crate::widgets::views::View;
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use crossterm::event::Event as CrosstermEvent;
|
use crossterm::event::{Event};
|
||||||
use crossterm::event::{Event, KeyEvent};
|
|
||||||
use rat_cursor::HasScreenCursor;
|
use rat_cursor::HasScreenCursor;
|
||||||
use ratatui::{DefaultTerminal, Frame};
|
use ratatui::{DefaultTerminal, Frame};
|
||||||
use std::any::Any;
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
pub(crate) struct App {
|
pub(crate) struct App {
|
||||||
@@ -16,14 +14,14 @@ pub(crate) struct App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct AppState {
|
struct AppState {
|
||||||
view: Option<Box<dyn Any>>,
|
view: Option<AppView>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
pub async fn create() -> Result<Self> {
|
pub async fn create() -> Result<Self> {
|
||||||
let config = ApplicationConfig::get_config()?;
|
let config = ApplicationConfig::get_config()?;
|
||||||
let state = AppState {
|
let state = AppState {
|
||||||
view: Some(Box::new(MainView::new())),
|
view: Some(AppView::MainView(MainView::new())),
|
||||||
};
|
};
|
||||||
let app = Self {
|
let app = Self {
|
||||||
events: EventHandler::new(Duration::from_millis(config.basic_config.tick_rate)),
|
events: EventHandler::new(Duration::from_millis(config.basic_config.tick_rate)),
|
||||||
@@ -38,7 +36,7 @@ impl App {
|
|||||||
let event = self.events.next().await?;
|
let event = self.events.next().await?;
|
||||||
self.update(event)?;
|
self.update(event)?;
|
||||||
if let Some(view) = self.state.view.as_mut()
|
if let Some(view) = self.state.view.as_mut()
|
||||||
&& let Some(main_view) = view.downcast_ref::<MainView>()
|
&& let AppView::MainView(main_view) = view
|
||||||
&& !main_view.is_running()
|
&& !main_view.is_running()
|
||||||
{
|
{
|
||||||
break Ok(());
|
break Ok(());
|
||||||
@@ -49,27 +47,17 @@ impl App {
|
|||||||
fn update(&mut self, event: AppEvent) -> Result<()> {
|
fn update(&mut self, event: AppEvent) -> Result<()> {
|
||||||
if let AppEvent::Raw(cross_event) = event {
|
if let AppEvent::Raw(cross_event) = event {
|
||||||
self.handle_event(&cross_event)?;
|
self.handle_event(&cross_event)?;
|
||||||
if let CrosstermEvent::Key(key) = cross_event {
|
|
||||||
self.handle_key_event(&key)?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_key_event(&mut self, key: &KeyEvent) -> Result<()> {
|
|
||||||
if let Some(any) = self.state.view.as_mut() {
|
|
||||||
if let Some(main_view) = any.downcast_mut::<MainView>() {
|
|
||||||
main_view.handle_key_input(key)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_event(&mut self, key: &Event) -> Result<()> {
|
fn handle_event(&mut self, key: &Event) -> Result<()> {
|
||||||
if let Some(any) = self.state.view.as_mut() {
|
if let Some(current_view) = self.state.view.as_mut() {
|
||||||
if let Some(main_view) = any.downcast_mut::<MainView>() {
|
match current_view {
|
||||||
main_view.handle_input(key)?;
|
AppView::MainView(main_view) => {
|
||||||
|
main_view.handle_input(key)?
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -77,7 +65,8 @@ impl App {
|
|||||||
|
|
||||||
fn draw(&mut self, frame: &mut Frame) {
|
fn draw(&mut self, frame: &mut Frame) {
|
||||||
if let Some(view) = self.state.view.as_mut() {
|
if let Some(view) = self.state.view.as_mut() {
|
||||||
if let Some(main_view) = view.downcast_mut::<MainView>() {
|
match view {
|
||||||
|
AppView::MainView(main_view) => {
|
||||||
frame.render_stateful_widget(
|
frame.render_stateful_widget(
|
||||||
MainView::new(),
|
MainView::new(),
|
||||||
frame.area(),
|
frame.area(),
|
||||||
@@ -89,4 +78,5 @@ impl App {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ mod folder;
|
|||||||
mod sync;
|
mod sync;
|
||||||
|
|
||||||
use crate::{app, helpers};
|
use crate::{app, helpers};
|
||||||
use clap::{command, Args, Command, Parser, Subcommand};
|
use clap::{command, Parser};
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use ratatui::crossterm;
|
use ratatui::crossterm;
|
||||||
use crate::cli::folder::FolderCommand;
|
use crate::cli::folder::FolderCommand;
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ use futures::StreamExt;
|
|||||||
use indicatif::{ProgressBar, ProgressStyle};
|
use indicatif::{ProgressBar, ProgressStyle};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use tokio::time::Instant;
|
use tokio::time::Instant;
|
||||||
use crate::models;
|
|
||||||
use crate::models::{DLSiteCategory, DLSiteGenre, DLSiteManiax, DLSiteTranslation};
|
use crate::models::{DLSiteCategory, DLSiteGenre, DLSiteManiax, DLSiteTranslation};
|
||||||
use crate::config::types::ApplicationConfig;
|
use crate::config::types::ApplicationConfig;
|
||||||
use crate::constants::{DB_CF_OPTIONS, DB_OPTIONS};
|
use crate::constants::{DB_CF_OPTIONS, DB_OPTIONS};
|
||||||
@@ -113,7 +112,7 @@ impl DLSiteSyncCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn sync_works(&self, app_conf: &ApplicationConfig, db: &mut RocksDB, crawler: &DLSiteCrawler) -> Result<()> {
|
async fn sync_works(&self, app_conf: &ApplicationConfig, db: &mut RocksDB, crawler: &DLSiteCrawler) -> Result<()> {
|
||||||
let existing_works = db.get_all_values::<models::DLSiteManiax>()?;
|
let existing_works = db.get_all_values::<DLSiteManiax>()?;
|
||||||
|
|
||||||
let work_list = self.get_work_list(&app_conf, &existing_works).await?;
|
let work_list = self.get_work_list(&app_conf, &existing_works).await?;
|
||||||
let rj_nums = work_list.clone().into_keys().collect::<Vec<_>>();
|
let rj_nums = work_list.clone().into_keys().collect::<Vec<_>>();
|
||||||
@@ -151,7 +150,7 @@ impl DLSiteSyncCommand {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_work_list(&self, app_conf: &ApplicationConfig, existing_works: &[models::DLSiteManiax]) -> Result<HashMap<String, PathBuf>> {
|
async fn get_work_list(&self, app_conf: &ApplicationConfig, existing_works: &[DLSiteManiax]) -> Result<HashMap<String, PathBuf>> {
|
||||||
let existing_nums = existing_works.iter()
|
let existing_nums = existing_works.iter()
|
||||||
.map(|x| x.rj_num.clone())
|
.map(|x| x.rj_num.clone())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|||||||
@@ -17,8 +17,10 @@ pub(crate) struct GameList<T> {
|
|||||||
|
|
||||||
impl ApplicationConfig {
|
impl ApplicationConfig {
|
||||||
pub fn get_config() -> Result<Self> {
|
pub fn get_config() -> Result<Self> {
|
||||||
if CACHE_MAP.contains_key(CONFIG_KEY) {
|
if CACHE_MAP.contains_key(CONFIG_KEY) &&
|
||||||
Ok(serde_json::from_value(CACHE_MAP.get(CONFIG_KEY).unwrap().clone())?)
|
let Some(cached_config) = CACHE_MAP.get(CONFIG_KEY)
|
||||||
|
{
|
||||||
|
Ok(serde_json::from_value(cached_config.clone())?)
|
||||||
} else if APP_CONIFG_FILE_PATH.exists() {
|
} else if APP_CONIFG_FILE_PATH.exists() {
|
||||||
ApplicationConfig::from_file(&APP_CONIFG_FILE_PATH)
|
ApplicationConfig::from_file(&APP_CONIFG_FILE_PATH)
|
||||||
} else {
|
} else {
|
||||||
@@ -37,7 +39,6 @@ impl ApplicationConfig {
|
|||||||
let default_locale = sys_locale::get_locale().unwrap_or(String::from("ja-JP"));
|
let default_locale = sys_locale::get_locale().unwrap_or(String::from("ja-JP"));
|
||||||
let conf = Self {
|
let conf = Self {
|
||||||
basic_config: BasicConfig {
|
basic_config: BasicConfig {
|
||||||
db_path: APP_DB_DATA_DIR.to_str().unwrap().to_string(),
|
|
||||||
tick_rate: 250,
|
tick_rate: 250,
|
||||||
locale: LanguageTag::parse(&default_locale)?,
|
locale: LanguageTag::parse(&default_locale)?,
|
||||||
},
|
},
|
||||||
@@ -45,18 +46,19 @@ impl ApplicationConfig {
|
|||||||
dlsite_paths: vec![],
|
dlsite_paths: vec![],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
conf.clone().write_to_file(&APP_CONIFG_FILE_PATH.to_path_buf())?;
|
conf.clone().write_to_file(APP_CONIFG_FILE_PATH.to_path_buf())?;
|
||||||
Ok(conf)
|
Ok(conf)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_to_file(self, path: &PathBuf) -> Result<()> {
|
fn write_to_file(self, path: PathBuf) -> Result<()> {
|
||||||
let writer = std::fs::File::create(path)?;
|
let writer = std::fs::File::create(path)?;
|
||||||
serde_json::to_writer_pretty(writer, &self)?;
|
serde_json::to_writer_pretty(writer, &self)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn save(self) -> Result<()> {
|
pub fn save(self) -> Result<()> {
|
||||||
self.write_to_file(&APP_CONIFG_FILE_PATH.to_path_buf())
|
let current_value = serde_json::to_value(&self)?;
|
||||||
|
CACHE_MAP.alter(CONFIG_KEY, |_, _| current_value);
|
||||||
|
self.write_to_file(APP_CONIFG_FILE_PATH.to_path_buf())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ pub struct ApplicationConfig {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub(crate) struct BasicConfig {
|
pub(crate) struct BasicConfig {
|
||||||
pub db_path: String,
|
|
||||||
pub locale: LanguageTag,
|
pub locale: LanguageTag,
|
||||||
pub tick_rate: u64,
|
pub tick_rate: u64,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,12 +12,10 @@ use lazy_static::lazy_static;
|
|||||||
use scraper::{Element, Html, Selector};
|
use scraper::{Element, Html, Selector};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use crate::config::types::ApplicationConfig;
|
|
||||||
use crate::constants::{APP_DATA_DIR, JP_LOCALE};
|
use crate::constants::{APP_DATA_DIR, JP_LOCALE};
|
||||||
use crate::crawler::Crawler;
|
use crate::crawler::Crawler;
|
||||||
use crate::helpers::matches_primary_language;
|
use crate::helpers::matches_primary_language;
|
||||||
use crate::models;
|
use crate::models::{PrimaryLanguage};
|
||||||
use crate::models::{DLSiteTranslation, PrimaryLanguage};
|
|
||||||
|
|
||||||
//TODO: override locale with user one
|
//TODO: override locale with user one
|
||||||
const DLSITE_URL: &str = "https://www.dlsite.com/";
|
const DLSITE_URL: &str = "https://www.dlsite.com/";
|
||||||
@@ -167,7 +165,6 @@ impl DLSiteCrawler {
|
|||||||
|
|
||||||
let result = html.select(&selector).next().unwrap();
|
let result = html.select(&selector).next().unwrap();
|
||||||
let genre_rows = result.child_elements().collect::<Vec<_>>();
|
let genre_rows = result.child_elements().collect::<Vec<_>>();
|
||||||
let t = genre_rows.iter().filter_map(|v| v.first_element_child().unwrap().text().next()).collect_vec();
|
|
||||||
let genre_row = genre_rows.iter()
|
let genre_row = genre_rows.iter()
|
||||||
.find(|v| v.first_element_child().unwrap().text().next().unwrap() == genre_str)
|
.find(|v| v.first_element_child().unwrap().text().next().unwrap() == genre_str)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
pub mod dlsite;
|
pub mod dlsite;
|
||||||
|
|
||||||
use std::sync::Arc;
|
|
||||||
pub use dlsite::*;
|
pub use dlsite::*;
|
||||||
use color_eyre::eyre::eyre;
|
use color_eyre::eyre::eyre;
|
||||||
use crate::constants::APP_CACHE_PATH;
|
use crate::constants::APP_CACHE_PATH;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use crossterm::event::EventStream;
|
|||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use tokio::sync::mpsc::UnboundedSender;
|
use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
|
||||||
use tokio::task::JoinHandle;
|
use tokio::task::JoinHandle;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@@ -15,7 +15,7 @@ pub(crate) enum AppEvent {
|
|||||||
|
|
||||||
pub(crate) struct EventHandler {
|
pub(crate) struct EventHandler {
|
||||||
_tx: UnboundedSender<AppEvent>,
|
_tx: UnboundedSender<AppEvent>,
|
||||||
rx: tokio::sync::mpsc::UnboundedReceiver<AppEvent>,
|
rx: UnboundedReceiver<AppEvent>,
|
||||||
pub task: JoinHandle<()>,
|
pub task: JoinHandle<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
use std::path::Path;
|
use crate::constants::{APP_DB_DATA_DIR, DB_COLUMNS};
|
||||||
use crate::constants::{DB_COLUMNS};
|
|
||||||
use rocksdb::{ColumnFamilyDescriptor, IteratorMode, OptimisticTransactionDB, Options};
|
use rocksdb::{ColumnFamilyDescriptor, IteratorMode, OptimisticTransactionDB, Options};
|
||||||
use serde::{Serialize};
|
use serde::{Serialize};
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
use crate::models::{RocksColumn, RocksReference, RocksReferences};
|
use crate::models::{RocksColumn, RocksReference, RocksReferences};
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use crate::config::types::ApplicationConfig;
|
|
||||||
|
|
||||||
pub struct RocksDB {
|
pub struct RocksDB {
|
||||||
db: OptimisticTransactionDB,
|
db: OptimisticTransactionDB,
|
||||||
@@ -13,13 +11,12 @@ pub struct RocksDB {
|
|||||||
|
|
||||||
impl RocksDB {
|
impl RocksDB {
|
||||||
pub fn new(db_opts: Options, cf_opts: Options) -> Result<Self> {
|
pub fn new(db_opts: Options, cf_opts: Options) -> Result<Self> {
|
||||||
let app_conf = ApplicationConfig::get_config()?;
|
|
||||||
let cfs = DB_COLUMNS.iter()
|
let cfs = DB_COLUMNS.iter()
|
||||||
.map(|cf| ColumnFamilyDescriptor::new(cf.to_string(), cf_opts.clone()))
|
.map(|cf| ColumnFamilyDescriptor::new(cf.to_string(), cf_opts.clone()))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let db = OptimisticTransactionDB::open_cf_descriptors(
|
let db = OptimisticTransactionDB::open_cf_descriptors(
|
||||||
&db_opts,
|
&db_opts,
|
||||||
Path::new(&app_conf.basic_config.db_path),
|
APP_DB_DATA_DIR.as_path(),
|
||||||
cfs
|
cfs
|
||||||
)?;
|
)?;
|
||||||
let rocks = Self {
|
let rocks = Self {
|
||||||
|
|||||||
@@ -5,13 +5,11 @@ use color_eyre::eyre::eyre;
|
|||||||
use color_eyre::owo_colors::OwoColorize;
|
use color_eyre::owo_colors::OwoColorize;
|
||||||
use language_tags::LanguageTag;
|
use language_tags::LanguageTag;
|
||||||
use tokio::fs;
|
use tokio::fs;
|
||||||
use crate::config::types::ApplicationConfig;
|
use crate::constants::{APP_CONFIG_DIR, APP_DATA_DIR, APP_DB_DATA_DIR};
|
||||||
use crate::constants::{APP_CONFIG_DIR, APP_DATA_DIR, SUPPORTED_LOCALES};
|
|
||||||
use crate::crawler::DLSITE_IMG_FOLDER;
|
use crate::crawler::DLSITE_IMG_FOLDER;
|
||||||
|
|
||||||
|
|
||||||
pub async fn initialize_folders() -> color_eyre::Result<()> {
|
pub async fn initialize_folders() -> color_eyre::Result<()> {
|
||||||
let app_conf = ApplicationConfig::get_config()?;
|
|
||||||
if !APP_CONFIG_DIR.exists() {
|
if !APP_CONFIG_DIR.exists() {
|
||||||
fs::create_dir_all(APP_CONFIG_DIR.as_path()).await?;
|
fs::create_dir_all(APP_CONFIG_DIR.as_path()).await?;
|
||||||
}
|
}
|
||||||
@@ -21,9 +19,8 @@ pub async fn initialize_folders() -> color_eyre::Result<()> {
|
|||||||
if !DLSITE_IMG_FOLDER.exists() {
|
if !DLSITE_IMG_FOLDER.exists() {
|
||||||
fs::create_dir_all(DLSITE_IMG_FOLDER.as_path()).await?;
|
fs::create_dir_all(DLSITE_IMG_FOLDER.as_path()).await?;
|
||||||
}
|
}
|
||||||
let db_path = Path::new(&app_conf.basic_config.db_path);
|
if !APP_DB_DATA_DIR.exists() {
|
||||||
if !db_path.exists() {
|
fs::create_dir_all(APP_DB_DATA_DIR.as_path()).await?;
|
||||||
fs::create_dir_all(db_path).await?;
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1,5 @@
|
|||||||
pub mod folder;
|
pub mod folder;
|
||||||
|
|
||||||
|
pub enum AppPopup {
|
||||||
|
AddFolder(folder::AddFolderPopup)
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,21 +2,20 @@ use crate::config::types::ApplicationConfig;
|
|||||||
use crate::widgets::popups::folder::AddFolderPopup;
|
use crate::widgets::popups::folder::AddFolderPopup;
|
||||||
use crate::widgets::views::View;
|
use crate::widgets::views::View;
|
||||||
use crossterm::event::KeyCode::Char;
|
use crossterm::event::KeyCode::Char;
|
||||||
use crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind};
|
use crossterm::event::{Event, KeyCode, KeyEventKind};
|
||||||
use rat_cursor::HasScreenCursor;
|
use rat_cursor::HasScreenCursor;
|
||||||
use ratatui::buffer::Buffer;
|
use ratatui::buffer::Buffer;
|
||||||
use ratatui::layout::{Constraint, Direction, Layout, Rect};
|
use ratatui::layout::{Constraint, Direction, Layout, Rect};
|
||||||
use ratatui::prelude::{Color, Line, Span, Style, Text, Widget};
|
use ratatui::prelude::{Color, Line, Span, Style, Text, Widget};
|
||||||
use ratatui::widgets::{Block, Borders, Paragraph, StatefulWidget};
|
use ratatui::widgets::{Block, Borders, Paragraph, StatefulWidget};
|
||||||
use std::any::Any;
|
use crate::widgets::popups::AppPopup;
|
||||||
|
|
||||||
pub struct MainView {
|
pub struct MainView {
|
||||||
pub state: MainViewState,
|
pub state: MainViewState,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct MainViewState {
|
pub struct MainViewState {
|
||||||
popup: Option<Box<dyn Any>>,
|
popup: Option<AppPopup>,
|
||||||
status: Status,
|
status: Status,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,44 +45,47 @@ impl MainView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn folder_popup(&mut self) {
|
fn folder_popup(&mut self) {
|
||||||
self.state.popup = Some(Box::new(AddFolderPopup::new()));
|
self.state.popup = Some(AppPopup::AddFolder(AddFolderPopup::new()));
|
||||||
self.state.status = Status::Popup;
|
self.state.status = Status::Popup;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl View for MainView {
|
impl View for MainView {
|
||||||
fn handle_input(&mut self, event: &Event) -> color_eyre::Result<()> {
|
fn handle_input(&mut self, event: &Event) -> color_eyre::Result<()> {
|
||||||
if let Some(any) = self.state.popup.as_mut()
|
if let Some(current_popup) = self.state.popup.as_mut() {
|
||||||
&& let Some(popup) = any.downcast_mut::<AddFolderPopup>()
|
match current_popup {
|
||||||
{
|
AppPopup::AddFolder(folder_popup) => {
|
||||||
popup.textarea.handle_input(event)?;
|
folder_popup.textarea.handle_input(event)?;
|
||||||
}
|
if let Event::Key(key) = event &&
|
||||||
Ok(())
|
key.code.is_enter() &&
|
||||||
}
|
let Some(value) = folder_popup.get_folder_value()
|
||||||
|
|
||||||
fn handle_key_input(&mut self, key: &KeyEvent) -> color_eyre::Result<()> {
|
|
||||||
if matches!(self.state.status, Status::Popup) && matches!(key.code, KeyCode::Esc) {
|
|
||||||
self.state.status = Status::Running;
|
|
||||||
self.state.popup = None;
|
|
||||||
}
|
|
||||||
if let Some(any) = self.state.popup.as_mut() &&
|
|
||||||
let Some(popup) = any.downcast_mut::<AddFolderPopup>() &&
|
|
||||||
let Some(value) = popup.get_folder_value() &&
|
|
||||||
key.code.is_enter()
|
|
||||||
{
|
{
|
||||||
let mut config = ApplicationConfig::get_config()?;
|
let mut config = ApplicationConfig::get_config()?;
|
||||||
config.path_config.dlsite_paths.push(value);
|
config.path_config.dlsite_paths.push(value);
|
||||||
|
|
||||||
popup.textarea.reset_value()?;
|
folder_popup.textarea.reset_value()?;
|
||||||
config.save()?;
|
config.save()?;
|
||||||
}
|
}
|
||||||
if !matches!(self.state.status, Status::Popup) && matches!(key.kind, KeyEventKind::Press) {
|
}
|
||||||
match key.code {
|
}
|
||||||
|
}
|
||||||
|
if let Event::Key(key_event) = event {
|
||||||
|
if matches!(self.state.status, Status::Popup) &&
|
||||||
|
matches!(key_event.code, KeyCode::Esc)
|
||||||
|
{
|
||||||
|
self.state.status = Status::Running;
|
||||||
|
self.state.popup = None;
|
||||||
|
}
|
||||||
|
if !matches!(self.state.status, Status::Popup) &&
|
||||||
|
matches!(key_event.kind, KeyEventKind::Press)
|
||||||
|
{
|
||||||
|
match key_event.code {
|
||||||
Char('q') => self.quit()?,
|
Char('q') => self.quit()?,
|
||||||
Char('a') => self.folder_popup(),
|
Char('a') => self.folder_popup(),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,22 +113,27 @@ impl StatefulWidget for MainView {
|
|||||||
Self::render_game_list(chunks[1], buf);
|
Self::render_game_list(chunks[1], buf);
|
||||||
Self::render_footer(state, chunks[2], buf);
|
Self::render_footer(state, chunks[2], buf);
|
||||||
|
|
||||||
if let Some(boxed) = state.popup.as_mut()
|
let Some(popup) = state.popup.as_mut() else {
|
||||||
&& let Some(popup) = boxed.downcast_mut::<AddFolderPopup>()
|
return;
|
||||||
{
|
};
|
||||||
|
match popup {
|
||||||
|
AppPopup::AddFolder(popup) => {
|
||||||
popup.clone().render(area, buf, popup);
|
popup.clone().render(area, buf, popup);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HasScreenCursor for MainView {
|
impl HasScreenCursor for MainView {
|
||||||
fn screen_cursor(&self) -> Option<(u16, u16)> {
|
fn screen_cursor(&self) -> Option<(u16, u16)> {
|
||||||
if let Some(popup) = &self.state.popup
|
let Some(popup) = &self.state.popup else {
|
||||||
&& let Some(add_folder) = popup.downcast_ref::<AddFolderPopup>()
|
return None;
|
||||||
{
|
};
|
||||||
return add_folder.textarea.screen_cursor();
|
match popup {
|
||||||
|
AppPopup::AddFolder(popup) => {
|
||||||
|
popup.textarea.screen_cursor()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
None
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
mod main_view;
|
mod main_view;
|
||||||
|
|
||||||
use crossterm::event::{Event, KeyEvent};
|
use crossterm::event::{Event};
|
||||||
pub use main_view::MainView;
|
pub use main_view::MainView;
|
||||||
|
|
||||||
pub trait View {
|
pub trait View {
|
||||||
fn handle_input(&mut self, event: &Event) -> color_eyre::Result<()>;
|
fn handle_input(&mut self, event: &Event) -> color_eyre::Result<()>;
|
||||||
fn handle_key_input(&mut self, key: &KeyEvent) -> color_eyre::Result<()>;
|
|
||||||
fn is_running(&self) -> bool;
|
fn is_running(&self) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum AppView {
|
||||||
|
MainView(MainView),
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user