diff --git a/src/app.rs b/src/app.rs index 82646ee..470bace 100644 --- a/src/app.rs +++ b/src/app.rs @@ -28,6 +28,19 @@ pub enum SnowballingStep { Forward, } +#[derive(Serialize, Deserialize, Clone)] +pub enum StatusMessage { + Info(String), + Warning(String), + Error(String), +} + +impl Default for StatusMessage { + fn default() -> Self { + StatusMessage::Info("".to_string()) + } +} + impl ToString for SnowballingStep { fn to_string(&self) -> String { match self { @@ -104,7 +117,8 @@ pub struct App { pub snowballing_step: SnowballingStep, - pub status_message: String, + #[serde(skip)] + pub status_message: StatusMessage, #[serde(skip)] pub should_quit: bool, @@ -117,7 +131,6 @@ pub struct App { // TODO: Implement export of included papers into zotero (Use RIS format somehow) // TODO: Log everything relevant impl App { - // TODO: Show error somehow pub async fn add_seed_paper(&mut self, api_link: &String) { let publ = get_publication_by_id(api_link, "an.tsouchlos@gmail.com").await; @@ -129,10 +142,18 @@ impl App { "Failed to get publication metadata using OpenAlex API: {}", err ); + self.set_status_message(StatusMessage::Error(format!( + "Failed to get publication metadata using OpenAlex API: {}", + err + ))); } } } + pub fn set_status_message(&mut self, s: StatusMessage) { + self.status_message = s; + } + pub async fn handle_key(&mut self, key: KeyCode) { if KeyCode::Esc == key { self.should_quit = true; @@ -164,7 +185,11 @@ impl App { "The next snowballing step can only be initiated \ after screening all pending publications" ); - // TODO: Show warning/error somehow + self.set_status_message(StatusMessage::Warning( + "The next snowballing step can only be initiated \ + after screening all pending publications" + .to_string(), + )); return; } @@ -173,13 +198,21 @@ impl App { // TODO: Implement } SnowballingStep::Backward => { - for publication in &self.included_publications { + self.set_status_message(StatusMessage::Info( + "Fetching references...".to_string(), + )); + + // TODO: Find a way to not clone the publications + for publication in + self.included_publications.clone() + { + // TODO: In addition to the referenced_works do + // an API call for citations for reference in &publication.referenced_works { let api_link = format!( "https://api.openalex.org/{}", &reference[21..] ); - // https://openalex.org/W2085881930 let publ = get_publication_by_id( &api_link, "an.tsouchlos@gmail.com", @@ -190,7 +223,6 @@ impl App { Ok(publ) => { self.pending_publications.push(publ) } - // TODO: Show error somehow Err(err) => { warn!( "Failed to get publication\ @@ -198,9 +230,20 @@ impl App { {}", err ); + + self.set_status_message( + StatusMessage::Error(format!( + "Failed to get publication\ + metadata using OpenAlex API: \ + {}", + err + )), + ); } } } + + self.set_status_message(StatusMessage::Info("Done".to_string())); } } } diff --git a/src/main.rs b/src/main.rs index 600dd20..ffcc698 100644 --- a/src/main.rs +++ b/src/main.rs @@ -88,12 +88,12 @@ async fn main() { .init(); match run(&args).await { - Ok(()) => info!("Application completed successfully"), Err(e) => { error!("Application error: {}", e); print!("{e:?}"); std::process::exit(1); } + _ => {} } } diff --git a/src/snowballing.rs b/src/snowballing.rs index ca13f11..8774063 100644 --- a/src/snowballing.rs +++ b/src/snowballing.rs @@ -5,14 +5,14 @@ use serde::{Deserialize, Serialize}; use std::collections::HashMap; use unicode_general_category::{GeneralCategory, get_general_category}; -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct Authorship { pub author_position: String, pub raw_author_name: String, } // TODO: Handle duplicates by having vectors of ids -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct Publication { pub id: String, pub display_name: Option, diff --git a/src/ui.rs b/src/ui.rs index d7f3818..241c727 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -1,5 +1,5 @@ use crate::{ - app::{ActivePane, ActiveTab, App}, + app::{ActivePane, ActiveTab, App, StatusMessage}, snowballing::Publication, }; use ratatui::{ @@ -20,7 +20,11 @@ pub fn draw(f: &mut Frame, app: &mut App) { fn draw_seeding_tab(f: &mut Frame, app: &mut App) { let chunks = Layout::default() .direction(Direction::Vertical) - .constraints([Constraint::Min(3), Constraint::Length(3)]) + .constraints([ + Constraint::Min(3), + Constraint::Length(3), + Constraint::Length(1), + ]) .split(f.area()); // Included publication list @@ -62,6 +66,10 @@ fn draw_seeding_tab(f: &mut Frame, app: &mut App) { .block(Block::bordered().title("Input")); f.render_widget(input, chunks[1]); + + // Status line + + draw_status_line(f, app, chunks[2]); } fn draw_snowballing_tab(f: &mut Frame, app: &mut App) { @@ -322,8 +330,16 @@ fn draw_right_pane(frame: &mut Frame, app: &mut App, area: Rect) { } fn draw_status_line(frame: &mut Frame, app: &App, area: Rect) { - let line = Paragraph::new(app.status_message.clone()) - .style(Style::default().bg(Color::Rgb(60, 56, 54))); + let line = Paragraph::new(Line::from(match app.status_message.clone() { + StatusMessage::Info(s) => Span::raw(s), + StatusMessage::Warning(s) => { + Span::styled(s, Style::default().fg(Color::Yellow)) + } + StatusMessage::Error(s) => { + Span::styled(s, Style::default().fg(Color::Red)) + } + })) + .style(Style::default().bg(Color::Rgb(60, 56, 54))); frame.render_widget(line, area); }