Implement seeding tab layout

This commit is contained in:
Andreas Tsouchlos 2025-12-30 00:13:35 +02:00
parent e30b22199f
commit 4e46184f37
2 changed files with 183 additions and 84 deletions

View File

@ -10,6 +10,13 @@ pub enum ActivePane {
PendingPublications, PendingPublications,
} }
#[derive(Serialize, Deserialize, Default, PartialEq)]
pub enum ActiveTab {
#[default]
Seeding,
Snowballing,
}
#[derive(Serialize, Deserialize, Default)] #[derive(Serialize, Deserialize, Default)]
pub enum SnowballingStep { pub enum SnowballingStep {
#[default] #[default]
@ -84,6 +91,11 @@ pub struct App {
/// UI state: active pane /// UI state: active pane
pub active_pane: ActivePane, pub active_pane: ActivePane,
/// UI state: active window
pub active_tab: ActiveTab,
pub seeding_input: String,
pub snowballing_iteration: usize, pub snowballing_iteration: usize,
pub snowballing_step: SnowballingStep, pub snowballing_step: SnowballingStep,
@ -100,10 +112,30 @@ pub struct App {
// TODO: Implement export of included papers into zotero (Use RIS format somehow) // TODO: Implement export of included papers into zotero (Use RIS format somehow)
impl App { impl App {
pub fn handle_key(&mut self, key: KeyCode) { pub fn handle_key(&mut self, key: KeyCode) {
match key { if KeyCode::Esc == key {
KeyCode::Char('q') => {
self.should_quit = true; self.should_quit = true;
return;
} }
match self.active_tab {
ActiveTab::Seeding => match key {
KeyCode::Tab => {
self.active_tab = ActiveTab::Snowballing;
}
KeyCode::Enter => {
// TODO: Actually add paper to included list
self.seeding_input.clear();
}
KeyCode::Char(to_insert) => self.seeding_input.push(to_insert),
KeyCode::Backspace => {
if self.seeding_input.len() > 0 {
self.seeding_input
.truncate(self.seeding_input.len() - 1);
}
}
_ => {}
},
ActiveTab::Snowballing => match key {
KeyCode::Enter => match self.active_pane { KeyCode::Enter => match self.active_pane {
ActivePane::IncludedPublications => { ActivePane::IncludedPublications => {
if let Some(idx) = self.included_list_state.selected() { if let Some(idx) = self.included_list_state.selected() {
@ -113,15 +145,23 @@ impl App {
} }
ActivePane::PendingPublications => { ActivePane::PendingPublications => {
if let Some(idx) = self.pending_list_state.selected() { if let Some(idx) = self.pending_list_state.selected() {
open::that(&self.pending_publications[idx].id).unwrap(); open::that(&self.pending_publications[idx].id)
.unwrap();
} }
} }
}, },
KeyCode::Tab => {
self.active_tab = ActiveTab::Seeding;
}
KeyCode::Char('j') => match self.active_pane { KeyCode::Char('j') => match self.active_pane {
ActivePane::IncludedPublications => { ActivePane::IncludedPublications => {
let i = match self.included_list_state.selected() { let i = match self.included_list_state.selected() {
Some(i) => { Some(i) => {
if i >= self.included_publications.len() - 1 { if i >= self
.included_publications
.len()
.wrapping_sub(1)
{
0 0
} else { } else {
i + 1 i + 1
@ -134,7 +174,11 @@ impl App {
ActivePane::PendingPublications => { ActivePane::PendingPublications => {
let i = match self.pending_list_state.selected() { let i = match self.pending_list_state.selected() {
Some(i) => { Some(i) => {
if i >= self.pending_publications.len() - 1 { if i >= self
.pending_publications
.len()
.wrapping_sub(1)
{
0 0
} else { } else {
i + 1 i + 1
@ -150,7 +194,9 @@ impl App {
let i = match self.included_list_state.selected() { let i = match self.included_list_state.selected() {
Some(i) => { Some(i) => {
if i == 0 { if i == 0 {
self.included_publications.len() - 1 self.included_publications
.len()
.wrapping_sub(1)
} else { } else {
i - 1 i - 1
} }
@ -163,7 +209,9 @@ impl App {
let i = match self.pending_list_state.selected() { let i = match self.pending_list_state.selected() {
Some(i) => { Some(i) => {
if i == 0 { if i == 0 {
self.pending_publications.len() - 1 self.pending_publications
.len()
.wrapping_sub(1)
} else { } else {
i - 1 i - 1
} }
@ -188,6 +236,7 @@ impl App {
} }
} }
_ => {} _ => {}
},
} }
} }
} }

View File

@ -1,5 +1,5 @@
use crate::{ use crate::{
app::{ActivePane, App}, app::{ActivePane, ActiveTab, App},
snowballing::Publication, snowballing::Publication,
}; };
use ratatui::{ use ratatui::{
@ -7,10 +7,60 @@ use ratatui::{
layout::{Constraint, Direction, Layout, Rect}, layout::{Constraint, Direction, Layout, Rect},
style::{Color, Modifier, Style, Stylize}, style::{Color, Modifier, Style, Stylize},
text::{Line, Span}, text::{Line, Span},
widgets::{Block, Borders, List, ListItem}, widgets::{Block, Borders, List, ListItem, ListState, Paragraph},
}; };
pub fn draw(f: &mut Frame, app: &mut App) { pub fn draw(f: &mut Frame, app: &mut App) {
match app.active_tab {
ActiveTab::Seeding => draw_seeding_tab(f, app),
ActiveTab::Snowballing => draw_snowballing_tab(f, 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)])
.split(f.area());
// Included publication list
let items = create_publication_item_list(
&app.included_publications,
None,
chunks[0].width.saturating_sub(4) as usize,
false,
);
let list = List::new(items).block(
Block::default()
.title_top(Line::from("Included Publications").centered())
.title_top(
Line::from(Span::styled(
format!("{} entries", app.included_publications.len()),
Style::default().fg(Color::Yellow),
))
.right_aligned(),
)
.borders(Borders::ALL),
);
f.render_stateful_widget(
list,
chunks[0],
&mut ListState::default()
.with_selected(Some(app.included_publications.len() - 1)),
);
// Text entry
let input = Paragraph::new(app.seeding_input.as_str())
.block(Block::bordered().title("Input"));
f.render_widget(input, chunks[1]);
}
fn draw_snowballing_tab(f: &mut Frame, app: &mut App) {
let chunks = Layout::default() let chunks = Layout::default()
.direction(Direction::Horizontal) .direction(Direction::Horizontal)
.constraints([Constraint::Percentage(25), Constraint::Percentage(75)]) .constraints([Constraint::Percentage(25), Constraint::Percentage(75)])