Implement seeding tab layout
This commit is contained in:
parent
e30b22199f
commit
4e46184f37
213
src/app.rs
213
src/app.rs
@ -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,94 +112,131 @@ 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;
|
||||||
}
|
}
|
||||||
KeyCode::Enter => match self.active_pane {
|
|
||||||
ActivePane::IncludedPublications => {
|
match self.active_tab {
|
||||||
if let Some(idx) = self.included_list_state.selected() {
|
ActiveTab::Seeding => match key {
|
||||||
open::that(&self.included_publications[idx].id)
|
KeyCode::Tab => {
|
||||||
.unwrap();
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ActivePane::PendingPublications => {
|
_ => {}
|
||||||
if let Some(idx) = self.pending_list_state.selected() {
|
},
|
||||||
open::that(&self.pending_publications[idx].id).unwrap();
|
ActiveTab::Snowballing => match key {
|
||||||
|
KeyCode::Enter => match self.active_pane {
|
||||||
|
ActivePane::IncludedPublications => {
|
||||||
|
if let Some(idx) = self.included_list_state.selected() {
|
||||||
|
open::that(&self.included_publications[idx].id)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ActivePane::PendingPublications => {
|
||||||
|
if let Some(idx) = self.pending_list_state.selected() {
|
||||||
|
open::that(&self.pending_publications[idx].id)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
KeyCode::Tab => {
|
||||||
|
self.active_tab = ActiveTab::Seeding;
|
||||||
|
}
|
||||||
|
KeyCode::Char('j') => match self.active_pane {
|
||||||
|
ActivePane::IncludedPublications => {
|
||||||
|
let i = match self.included_list_state.selected() {
|
||||||
|
Some(i) => {
|
||||||
|
if i >= self
|
||||||
|
.included_publications
|
||||||
|
.len()
|
||||||
|
.wrapping_sub(1)
|
||||||
|
{
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
i + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => 0,
|
||||||
|
};
|
||||||
|
self.included_list_state.select(Some(i));
|
||||||
|
}
|
||||||
|
ActivePane::PendingPublications => {
|
||||||
|
let i = match self.pending_list_state.selected() {
|
||||||
|
Some(i) => {
|
||||||
|
if i >= self
|
||||||
|
.pending_publications
|
||||||
|
.len()
|
||||||
|
.wrapping_sub(1)
|
||||||
|
{
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
i + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => 0,
|
||||||
|
};
|
||||||
|
self.pending_list_state.select(Some(i));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
KeyCode::Char('k') => match self.active_pane {
|
||||||
|
ActivePane::IncludedPublications => {
|
||||||
|
let i = match self.included_list_state.selected() {
|
||||||
|
Some(i) => {
|
||||||
|
if i == 0 {
|
||||||
|
self.included_publications
|
||||||
|
.len()
|
||||||
|
.wrapping_sub(1)
|
||||||
|
} else {
|
||||||
|
i - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => 0,
|
||||||
|
};
|
||||||
|
self.included_list_state.select(Some(i));
|
||||||
|
}
|
||||||
|
ActivePane::PendingPublications => {
|
||||||
|
let i = match self.pending_list_state.selected() {
|
||||||
|
Some(i) => {
|
||||||
|
if i == 0 {
|
||||||
|
self.pending_publications
|
||||||
|
.len()
|
||||||
|
.wrapping_sub(1)
|
||||||
|
} else {
|
||||||
|
i - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => 0,
|
||||||
|
};
|
||||||
|
self.pending_list_state.select(Some(i));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
KeyCode::Char('h') => {
|
||||||
|
self.active_pane = ActivePane::IncludedPublications;
|
||||||
|
|
||||||
|
if let None = self.included_list_state.selected() {
|
||||||
|
self.included_list_state.select(Some(0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
KeyCode::Char('l') => {
|
||||||
KeyCode::Char('j') => match self.active_pane {
|
self.active_pane = ActivePane::PendingPublications;
|
||||||
ActivePane::IncludedPublications => {
|
|
||||||
let i = match self.included_list_state.selected() {
|
|
||||||
Some(i) => {
|
|
||||||
if i >= self.included_publications.len() - 1 {
|
|
||||||
0
|
|
||||||
} else {
|
|
||||||
i + 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => 0,
|
|
||||||
};
|
|
||||||
self.included_list_state.select(Some(i));
|
|
||||||
}
|
|
||||||
ActivePane::PendingPublications => {
|
|
||||||
let i = match self.pending_list_state.selected() {
|
|
||||||
Some(i) => {
|
|
||||||
if i >= self.pending_publications.len() - 1 {
|
|
||||||
0
|
|
||||||
} else {
|
|
||||||
i + 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => 0,
|
|
||||||
};
|
|
||||||
self.pending_list_state.select(Some(i));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
KeyCode::Char('k') => match self.active_pane {
|
|
||||||
ActivePane::IncludedPublications => {
|
|
||||||
let i = match self.included_list_state.selected() {
|
|
||||||
Some(i) => {
|
|
||||||
if i == 0 {
|
|
||||||
self.included_publications.len() - 1
|
|
||||||
} else {
|
|
||||||
i - 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => 0,
|
|
||||||
};
|
|
||||||
self.included_list_state.select(Some(i));
|
|
||||||
}
|
|
||||||
ActivePane::PendingPublications => {
|
|
||||||
let i = match self.pending_list_state.selected() {
|
|
||||||
Some(i) => {
|
|
||||||
if i == 0 {
|
|
||||||
self.pending_publications.len() - 1
|
|
||||||
} else {
|
|
||||||
i - 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => 0,
|
|
||||||
};
|
|
||||||
self.pending_list_state.select(Some(i));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
KeyCode::Char('h') => {
|
|
||||||
self.active_pane = ActivePane::IncludedPublications;
|
|
||||||
|
|
||||||
if let None = self.included_list_state.selected() {
|
if let None = self.pending_list_state.selected() {
|
||||||
self.included_list_state.select(Some(0));
|
self.pending_list_state.select(Some(0));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
_ => {}
|
||||||
KeyCode::Char('l') => {
|
},
|
||||||
self.active_pane = ActivePane::PendingPublications;
|
|
||||||
|
|
||||||
if let None = self.pending_list_state.selected() {
|
|
||||||
self.pending_list_state.select(Some(0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
54
src/ui.rs
54
src/ui.rs
@ -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)])
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user