Compare commits
No commits in common. "2e51ae80640f443d75eb0c71b0bf78550c9ec887" and "dd2d19ee13b545dc2ecfdbc0b0e46f7f343bbc08" have entirely different histories.
2e51ae8064
...
dd2d19ee13
141
src/app.rs
141
src/app.rs
@ -33,12 +33,19 @@ impl Default for StatusMessage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Default, PartialEq, Copy, Clone)]
|
||||||
|
pub enum Tab {
|
||||||
|
#[default]
|
||||||
|
Seeding,
|
||||||
|
Snowballing,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum GlobalAction {
|
pub enum GlobalAction {
|
||||||
ShowStatMsg(StatusMessage),
|
ShowStatusMessage(StatusMessage),
|
||||||
ClearStatMsg,
|
ClearStatusMessage,
|
||||||
AddIncludedPub(Publication),
|
AddIncludedPublication(Publication),
|
||||||
FetchPub,
|
SubmitSeedLink,
|
||||||
NextTab,
|
NextTab,
|
||||||
Quit,
|
Quit,
|
||||||
}
|
}
|
||||||
@ -68,13 +75,6 @@ impl From<SeedingAction> for Action {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Default, PartialEq, Copy, Clone)]
|
|
||||||
pub enum Tab {
|
|
||||||
#[default]
|
|
||||||
Seeding,
|
|
||||||
Snowballing,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Default)]
|
#[derive(Serialize, Deserialize, Default)]
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
// UI state
|
// UI state
|
||||||
@ -109,23 +109,21 @@ pub struct App {
|
|||||||
// TODO: Implement export of included papers as csv for keywording with a spreadsheet
|
// TODO: Implement export of included papers as csv for keywording with a spreadsheet
|
||||||
// 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 {
|
||||||
fn quit(&mut self) {
|
// TODO: Have status messages always last the same amount of time
|
||||||
self.should_quit = true;
|
fn handle_global_action(
|
||||||
}
|
&mut self,
|
||||||
|
action: GlobalAction,
|
||||||
fn next_tab(&mut self) {
|
action_tx: &'static mpsc::UnboundedSender<Action>,
|
||||||
|
) -> Result<(), SendError<Action>> {
|
||||||
|
match action {
|
||||||
|
GlobalAction::NextTab => {
|
||||||
self.state.current_tab = match self.state.current_tab {
|
self.state.current_tab = match self.state.current_tab {
|
||||||
Tab::Seeding => Tab::Snowballing,
|
Tab::Seeding => Tab::Snowballing,
|
||||||
Tab::Snowballing => Tab::Seeding,
|
Tab::Snowballing => Tab::Seeding,
|
||||||
};
|
};
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
GlobalAction::ShowStatusMessage(msg) => {
|
||||||
// TODO: Have status messages always last the same amount of time
|
|
||||||
fn show_stat_msg(
|
|
||||||
&mut self,
|
|
||||||
msg: StatusMessage,
|
|
||||||
action_tx: &'static mpsc::UnboundedSender<Action>,
|
|
||||||
) {
|
|
||||||
match &msg {
|
match &msg {
|
||||||
StatusMessage::Error(_) => {
|
StatusMessage::Error(_) => {
|
||||||
error!("Status message: {:?}", msg)
|
error!("Status message: {:?}", msg)
|
||||||
@ -141,35 +139,22 @@ impl App {
|
|||||||
self.state.status_message = msg;
|
self.state.status_message = msg;
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
sleep(Duration::from_millis(4000)).await;
|
sleep(Duration::from_millis(1000)).await;
|
||||||
|
|
||||||
if let Err(err) = action_tx.send(GlobalAction::ClearStatMsg.into())
|
if let Err(err) =
|
||||||
|
action_tx.send(GlobalAction::ClearStatusMessage.into())
|
||||||
{
|
{
|
||||||
error!("{}", err);
|
error!("{}", err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
fn clear_stat_msg(&mut self) {
|
|
||||||
self.state.status_message = StatusMessage::Info("".to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_included_publ(
|
|
||||||
&mut self,
|
|
||||||
publ: Publication,
|
|
||||||
) -> Result<(), SendError<Action>> {
|
|
||||||
self.state
|
|
||||||
.history
|
|
||||||
.current_iteration
|
|
||||||
.included_publications
|
|
||||||
.push(publ.clone());
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
GlobalAction::ClearStatusMessage => {
|
||||||
fn fetch_publication(
|
self.state.status_message = StatusMessage::Info("".to_string());
|
||||||
&self,
|
Ok(())
|
||||||
action_tx: &'static mpsc::UnboundedSender<Action>,
|
}
|
||||||
) -> Result<(), SendError<Action>> {
|
GlobalAction::SubmitSeedLink => {
|
||||||
if !self
|
if !self
|
||||||
.state
|
.state
|
||||||
.seeding
|
.seeding
|
||||||
@ -198,42 +183,47 @@ impl App {
|
|||||||
);
|
);
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let publ =
|
let publ = get_publication_by_id(
|
||||||
get_publication_by_id(&api_link, "an.tsouchlos@gmail.com");
|
&api_link,
|
||||||
|
"an.tsouchlos@gmail.com",
|
||||||
|
);
|
||||||
|
|
||||||
match publ.await {
|
match publ.await {
|
||||||
Ok(publ) => {
|
Ok(publ) => {
|
||||||
let _ = status_info!(
|
let _ = status_info!(
|
||||||
action_tx,
|
action_tx,
|
||||||
"Seed paper obtained successfully: {}",
|
"Seed paper obtained successfully: {}",
|
||||||
publ.get_title()
|
publ.get_title().unwrap_or(
|
||||||
.unwrap_or("[title unavailable]".to_string())
|
"[title unavailable]".to_string()
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
let _ = action_tx
|
let _ = action_tx.send(
|
||||||
.send(GlobalAction::AddIncludedPub(publ).into());
|
GlobalAction::AddIncludedPublication(publ)
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let _ = status_error!(action_tx, "{}", e.to_string());
|
let _ =
|
||||||
|
status_error!(action_tx, "{}", e.to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
self.action_tx.send(SeedingAction::ClearInput.into())
|
self.action_tx.send(SeedingAction::ClearInput.into())
|
||||||
}
|
}
|
||||||
|
GlobalAction::Quit => {
|
||||||
fn handle_global_action(
|
self.should_quit = true;
|
||||||
&mut self,
|
Ok(())
|
||||||
action: GlobalAction,
|
}
|
||||||
tx: &'static mpsc::UnboundedSender<Action>,
|
GlobalAction::AddIncludedPublication(publ) => {
|
||||||
) -> Result<(), SendError<Action>> {
|
self.state
|
||||||
match action {
|
.history
|
||||||
GlobalAction::Quit => Ok(self.quit()),
|
.current_iteration
|
||||||
GlobalAction::NextTab => Ok(self.next_tab()),
|
.included_publications
|
||||||
GlobalAction::ClearStatMsg => Ok(self.clear_stat_msg()),
|
.push(publ.clone());
|
||||||
GlobalAction::ShowStatMsg(msg) => Ok(self.show_stat_msg(msg, tx)),
|
Ok(())
|
||||||
GlobalAction::FetchPub => self.fetch_publication(tx),
|
}
|
||||||
GlobalAction::AddIncludedPub(publ) => self.add_included_publ(publ),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -262,10 +252,10 @@ impl App {
|
|||||||
) -> Result<(), SendError<Action>> {
|
) -> Result<(), SendError<Action>> {
|
||||||
match (self.state.current_tab, key) {
|
match (self.state.current_tab, key) {
|
||||||
(_, KeyCode::Esc) => {
|
(_, KeyCode::Esc) => {
|
||||||
self.action_tx.send(GlobalAction::Quit.into())?
|
self.action_tx.send(GlobalAction::Quit.into())?;
|
||||||
}
|
}
|
||||||
(_, KeyCode::Tab) => {
|
(_, KeyCode::Tab) => {
|
||||||
self.action_tx.send(GlobalAction::NextTab.into())?
|
self.action_tx.send(GlobalAction::NextTab.into())?;
|
||||||
}
|
}
|
||||||
(Tab::Seeding, KeyCode::Char(c)) => {
|
(Tab::Seeding, KeyCode::Char(c)) => {
|
||||||
self.action_tx.send(SeedingAction::EnterChar(c).into())?;
|
self.action_tx.send(SeedingAction::EnterChar(c).into())?;
|
||||||
@ -274,24 +264,7 @@ impl App {
|
|||||||
self.action_tx.send(SeedingAction::EnterBackspace.into())?;
|
self.action_tx.send(SeedingAction::EnterBackspace.into())?;
|
||||||
}
|
}
|
||||||
(Tab::Seeding, KeyCode::Enter) => {
|
(Tab::Seeding, KeyCode::Enter) => {
|
||||||
self.action_tx.send(GlobalAction::FetchPub.into())?;
|
self.action_tx.send(GlobalAction::SubmitSeedLink.into())?;
|
||||||
}
|
|
||||||
(Tab::Snowballing, KeyCode::Enter) => {
|
|
||||||
self.action_tx.send(SnowballingAction::Search.into())?;
|
|
||||||
}
|
|
||||||
(Tab::Snowballing, KeyCode::Char('h')) => {
|
|
||||||
self.action_tx
|
|
||||||
.send(SnowballingAction::SelectLeftPane.into())?;
|
|
||||||
}
|
|
||||||
(Tab::Snowballing, KeyCode::Char('l')) => {
|
|
||||||
self.action_tx
|
|
||||||
.send(SnowballingAction::SelectRightPane.into())?;
|
|
||||||
}
|
|
||||||
(Tab::Snowballing, KeyCode::Char('j')) => {
|
|
||||||
self.action_tx.send(SnowballingAction::NextItem.into())?;
|
|
||||||
}
|
|
||||||
(Tab::Snowballing, KeyCode::Char('k')) => {
|
|
||||||
self.action_tx.send(SnowballingAction::PrevItem.into())?;
|
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,7 @@ use crate::app::Action;
|
|||||||
macro_rules! status_info {
|
macro_rules! status_info {
|
||||||
($action_tx:expr, $text:expr, $($args:expr)*) => {
|
($action_tx:expr, $text:expr, $($args:expr)*) => {
|
||||||
$action_tx.send(
|
$action_tx.send(
|
||||||
GlobalAction::ShowStatMsg(StatusMessage::Info(format!($text, $($args)*)))
|
GlobalAction::ShowStatusMessage(StatusMessage::Info(format!($text, $($args)*)))
|
||||||
.into(),
|
.into(),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
@ -18,7 +18,7 @@ macro_rules! status_info {
|
|||||||
macro_rules! status_warn {
|
macro_rules! status_warn {
|
||||||
($action_tx:expr, $text:expr, $($args:expr)*) => {
|
($action_tx:expr, $text:expr, $($args:expr)*) => {
|
||||||
$action_tx.send(
|
$action_tx.send(
|
||||||
GlobalAction::ShowStatMsg(StatusMessage::Warning(format!($text, $($args)*)))
|
GlobalAction::ShowStatusMessage(StatusMessage::Warning(format!($text, $($args)*)))
|
||||||
.into(),
|
.into(),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
@ -29,7 +29,7 @@ macro_rules! status_warn {
|
|||||||
macro_rules! status_error {
|
macro_rules! status_error {
|
||||||
($action_tx:expr, $text:expr $(, $args:expr)*) => {
|
($action_tx:expr, $text:expr $(, $args:expr)*) => {
|
||||||
$action_tx.send(
|
$action_tx.send(
|
||||||
GlobalAction::ShowStatMsg(StatusMessage::Error(format!($text, $($args)*)))
|
GlobalAction::ShowStatusMessage(StatusMessage::Error(format!($text, $($args)*)))
|
||||||
.into(),
|
.into(),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|||||||
@ -17,22 +17,6 @@ pub struct SeedingComponent {
|
|||||||
pub included_publications: Vec<Publication>,
|
pub included_publications: Vec<Publication>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SeedingComponent {
|
|
||||||
pub fn clear_input(&mut self) {
|
|
||||||
self.input.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn enter_char(&mut self, c: char) {
|
|
||||||
self.input.push(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn enter_backspace(&mut self) {
|
|
||||||
if self.input.len() > 0 {
|
|
||||||
self.input.truncate(self.input.len() - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Component<SeedingAction> for SeedingComponent {
|
impl Component<SeedingAction> for SeedingComponent {
|
||||||
fn handle_action(
|
fn handle_action(
|
||||||
&mut self,
|
&mut self,
|
||||||
@ -40,9 +24,21 @@ impl Component<SeedingAction> for SeedingComponent {
|
|||||||
_: &mpsc::UnboundedSender<Action>,
|
_: &mpsc::UnboundedSender<Action>,
|
||||||
) -> Result<(), SendError<Action>> {
|
) -> Result<(), SendError<Action>> {
|
||||||
match action {
|
match action {
|
||||||
SeedingAction::ClearInput => Ok(self.clear_input()),
|
SeedingAction::ClearInput => {
|
||||||
SeedingAction::EnterChar(c) => Ok(self.enter_char(c)),
|
self.input.clear();
|
||||||
SeedingAction::EnterBackspace => Ok(self.enter_backspace()),
|
Ok(())
|
||||||
|
}
|
||||||
|
SeedingAction::EnterChar(c) => {
|
||||||
|
self.input.push(c);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
SeedingAction::EnterBackspace => {
|
||||||
|
if self.input.len() > 0 {
|
||||||
|
self.input.truncate(self.input.len() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,9 +17,11 @@ pub enum ActivePane {
|
|||||||
pub enum SnowballingAction {
|
pub enum SnowballingAction {
|
||||||
SelectLeftPane,
|
SelectLeftPane,
|
||||||
SelectRightPane,
|
SelectRightPane,
|
||||||
Search,
|
SearchForSelected,
|
||||||
NextItem,
|
NextItem,
|
||||||
PrevItem,
|
PrevItem,
|
||||||
|
ShowIncludedPublication(Publication),
|
||||||
|
ShowPendingPublication(Publication),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
@ -74,7 +76,7 @@ pub struct SnowballingComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SnowballingComponent {
|
impl SnowballingComponent {
|
||||||
fn select_next_item_impl(
|
fn next_list_item(
|
||||||
list_state: &mut ListState,
|
list_state: &mut ListState,
|
||||||
publications: &Vec<Publication>,
|
publications: &Vec<Publication>,
|
||||||
) {
|
) {
|
||||||
@ -91,7 +93,7 @@ impl SnowballingComponent {
|
|||||||
list_state.select(Some(i));
|
list_state.select(Some(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select_prev_item_impl(
|
fn prev_list_item(
|
||||||
list_state: &mut ListState,
|
list_state: &mut ListState,
|
||||||
publications: &Vec<Publication>,
|
publications: &Vec<Publication>,
|
||||||
) {
|
) {
|
||||||
@ -107,71 +109,6 @@ impl SnowballingComponent {
|
|||||||
};
|
};
|
||||||
list_state.select(Some(i));
|
list_state.select(Some(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select_left_pane(&mut self) {
|
|
||||||
self.active_pane = ActivePane::IncludedPublications;
|
|
||||||
|
|
||||||
if let None = self.included_list_state.selected() {
|
|
||||||
self.included_list_state.select(Some(0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn select_right_pane(&mut self) {
|
|
||||||
self.active_pane = ActivePane::PendingPublications;
|
|
||||||
|
|
||||||
if let None = self.pending_list_state.selected() {
|
|
||||||
self.pending_list_state.select(Some(0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn search(&self) {
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next_item(&mut self) {
|
|
||||||
match self.active_pane {
|
|
||||||
ActivePane::IncludedPublications => {
|
|
||||||
Self::select_next_item_impl(
|
|
||||||
&mut self.included_list_state,
|
|
||||||
&self.included_publications,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
ActivePane::PendingPublications => {
|
|
||||||
Self::select_next_item_impl(
|
|
||||||
&mut self.pending_list_state,
|
|
||||||
&self.pending_publications,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn prev_item(&mut self) {
|
|
||||||
match self.active_pane {
|
|
||||||
ActivePane::IncludedPublications => {
|
|
||||||
Self::select_prev_item_impl(
|
|
||||||
&mut self.included_list_state,
|
|
||||||
&self.included_publications,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
ActivePane::PendingPublications => {
|
|
||||||
Self::select_prev_item_impl(
|
|
||||||
&mut self.pending_list_state,
|
|
||||||
&self.pending_publications,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Component<SnowballingAction> for SnowballingComponent {
|
impl Component<SnowballingAction> for SnowballingComponent {
|
||||||
@ -181,11 +118,72 @@ impl Component<SnowballingAction> for SnowballingComponent {
|
|||||||
_: &mpsc::UnboundedSender<Action>,
|
_: &mpsc::UnboundedSender<Action>,
|
||||||
) -> Result<(), SendError<Action>> {
|
) -> Result<(), SendError<Action>> {
|
||||||
match action {
|
match action {
|
||||||
SnowballingAction::SelectLeftPane => Ok(self.select_left_pane()),
|
SnowballingAction::SelectLeftPane => {
|
||||||
SnowballingAction::SelectRightPane => Ok(self.select_right_pane()),
|
self.active_pane = ActivePane::IncludedPublications;
|
||||||
SnowballingAction::Search => Ok(self.search()),
|
|
||||||
SnowballingAction::NextItem => Ok(self.next_item()),
|
if let None = self.included_list_state.selected() {
|
||||||
SnowballingAction::PrevItem => Ok(self.prev_item()),
|
self.included_list_state.select(Some(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
SnowballingAction::SelectRightPane => {
|
||||||
|
self.active_pane = ActivePane::IncludedPublications;
|
||||||
|
|
||||||
|
if let None = self.included_list_state.selected() {
|
||||||
|
self.included_list_state.select(Some(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
SnowballingAction::SearchForSelected => match self.active_pane {
|
||||||
|
ActivePane::IncludedPublications => {
|
||||||
|
if let Some(idx) = self.included_list_state.selected() {
|
||||||
|
open::that(&self.included_publications[idx].id)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
ActivePane::PendingPublications => {
|
||||||
|
if let Some(idx) = self.pending_list_state.selected() {
|
||||||
|
open::that(&self.pending_publications[idx].id).unwrap();
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
SnowballingAction::NextItem => match self.active_pane {
|
||||||
|
ActivePane::IncludedPublications => {
|
||||||
|
Self::next_list_item(
|
||||||
|
&mut self.included_list_state,
|
||||||
|
&self.included_publications,
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
ActivePane::PendingPublications => {
|
||||||
|
Self::next_list_item(
|
||||||
|
&mut self.pending_list_state,
|
||||||
|
&self.pending_publications,
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
SnowballingAction::PrevItem => match self.active_pane {
|
||||||
|
ActivePane::IncludedPublications => {
|
||||||
|
Self::prev_list_item(
|
||||||
|
&mut self.included_list_state,
|
||||||
|
&self.included_publications,
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
ActivePane::PendingPublications => {
|
||||||
|
Self::prev_list_item(
|
||||||
|
&mut self.pending_list_state,
|
||||||
|
&self.pending_publications,
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => Ok(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user