refactor: extract ListNav component, unify list key handling across views
This commit is contained in:
@@ -3,6 +3,7 @@ pub mod dialog;
|
|||||||
pub mod form_list;
|
pub mod form_list;
|
||||||
pub mod input;
|
pub mod input;
|
||||||
pub mod layout;
|
pub mod layout;
|
||||||
|
pub mod list_nav;
|
||||||
pub mod panel;
|
pub mod panel;
|
||||||
pub mod toast;
|
pub mod toast;
|
||||||
|
|
||||||
@@ -11,5 +12,6 @@ pub use dialog::*;
|
|||||||
pub use form_list::{FormRow, draw_form_list};
|
pub use form_list::{FormRow, draw_form_list};
|
||||||
pub use input::draw_input;
|
pub use input::draw_input;
|
||||||
pub use layout::*;
|
pub use layout::*;
|
||||||
|
pub use list_nav::{ListAction, handle_list_nav};
|
||||||
pub use panel::*;
|
pub use panel::*;
|
||||||
pub use toast::draw_toast;
|
pub use toast::draw_toast;
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
use crossterm::event::{KeyCode, KeyEvent};
|
||||||
|
|
||||||
|
pub enum ListAction {
|
||||||
|
None,
|
||||||
|
Cancel,
|
||||||
|
Select,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_list_nav(cursor: &mut usize, len: usize, key: KeyEvent) -> ListAction {
|
||||||
|
match key.code {
|
||||||
|
KeyCode::Down | KeyCode::Char('j') if len > 0 => {
|
||||||
|
*cursor = (*cursor + 1) % len;
|
||||||
|
ListAction::None
|
||||||
|
}
|
||||||
|
KeyCode::Up | KeyCode::Char('k') if len > 0 => {
|
||||||
|
*cursor = (*cursor + len - 1) % len;
|
||||||
|
ListAction::None
|
||||||
|
}
|
||||||
|
KeyCode::Esc => ListAction::Cancel,
|
||||||
|
KeyCode::Enter => ListAction::Select,
|
||||||
|
_ => ListAction::None,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
use crate::app::{App, Mode};
|
use crate::app::{App, Mode};
|
||||||
|
use crate::ui::component::{ListAction, handle_list_nav};
|
||||||
use crate::ui::{ACCENT, MUTED, PANEL, SELECTED_BG, TEXT};
|
use crate::ui::{ACCENT, MUTED, PANEL, SELECTED_BG, TEXT};
|
||||||
|
|
||||||
use super::View;
|
use super::View;
|
||||||
@@ -82,20 +83,15 @@ impl View for ActionMenuView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handle_key(&self, app: &mut App, key: KeyEvent) -> Result<()> {
|
fn handle_key(&self, app: &mut App, key: KeyEvent) -> Result<()> {
|
||||||
let len = ACTIONS.len();
|
if key.code == KeyCode::Char(':') {
|
||||||
match key.code {
|
app.session.mode = Mode::Home;
|
||||||
KeyCode::Char(':') | KeyCode::Esc => {
|
return Ok(());
|
||||||
|
}
|
||||||
|
match handle_list_nav(&mut app.session.action_menu.cursor, ACTIONS.len(), key) {
|
||||||
|
ListAction::Cancel => {
|
||||||
app.session.mode = Mode::Home;
|
app.session.mode = Mode::Home;
|
||||||
}
|
}
|
||||||
KeyCode::Down | KeyCode::Char('j') => {
|
ListAction::Select => {
|
||||||
app.session.action_menu.cursor =
|
|
||||||
(app.session.action_menu.cursor + 1) % len;
|
|
||||||
}
|
|
||||||
KeyCode::Up | KeyCode::Char('k') => {
|
|
||||||
app.session.action_menu.cursor =
|
|
||||||
(app.session.action_menu.cursor + len - 1) % len;
|
|
||||||
}
|
|
||||||
KeyCode::Enter => {
|
|
||||||
app.session.mode = Mode::Home;
|
app.session.mode = Mode::Home;
|
||||||
match app.session.action_menu.cursor {
|
match app.session.action_menu.cursor {
|
||||||
0 => app.enter_combined_import()?,
|
0 => app.enter_combined_import()?,
|
||||||
|
|||||||
+12
-29
@@ -1,6 +1,6 @@
|
|||||||
use crate::app::{App, Mode};
|
use crate::app::{App, Mode};
|
||||||
use crate::config::CredentialEntry;
|
use crate::config::CredentialEntry;
|
||||||
use crate::ui::component::panel;
|
use crate::ui::component::{ListAction, handle_list_nav, panel};
|
||||||
use crate::ui::{BLUE, GREEN, MUTED, RED, SELECTED_BG, TEXT};
|
use crate::ui::{BLUE, GREEN, MUTED, RED, SELECTED_BG, TEXT};
|
||||||
|
|
||||||
use super::{View, scroll_rows};
|
use super::{View, scroll_rows};
|
||||||
@@ -118,35 +118,18 @@ fn draw_credentials(frame: &mut Frame<'_>, app: &App, area: Rect) {
|
|||||||
// ── Key handling ───────────────────────────────────────────────
|
// ── Key handling ───────────────────────────────────────────────
|
||||||
|
|
||||||
fn handle_credentials(app: &mut App, key: KeyEvent) -> Result<()> {
|
fn handle_credentials(app: &mut App, key: KeyEvent) -> Result<()> {
|
||||||
match key.code {
|
let len = app.config.credentials.entries.len();
|
||||||
KeyCode::Esc => app.session.mode = Mode::Home,
|
match handle_list_nav(&mut app.session.credentials.selected, len, key) {
|
||||||
KeyCode::Down | KeyCode::Char('j') => {
|
ListAction::Cancel => app.session.mode = Mode::Home,
|
||||||
let len = app.config.credentials.entries.len();
|
ListAction::Select => app.edit_cred_form(),
|
||||||
if len > 0 {
|
ListAction::None => match key.code {
|
||||||
app.session.credentials.selected = (app.session.credentials.selected as isize + 1)
|
KeyCode::Char('a') => app.new_cred_form(),
|
||||||
.rem_euclid(len as isize)
|
KeyCode::Char('d') => match app.delete_cred() {
|
||||||
as usize;
|
Ok(()) => app.toast("deleted", true),
|
||||||
}
|
Err(err) => app.toast(err.to_string(), false),
|
||||||
}
|
},
|
||||||
KeyCode::Up | KeyCode::Char('k') => {
|
_ => {}
|
||||||
let len = app.config.credentials.entries.len();
|
|
||||||
if len > 0 {
|
|
||||||
app.session.credentials.selected = (app.session.credentials.selected as isize - 1)
|
|
||||||
.rem_euclid(len as isize)
|
|
||||||
as usize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KeyCode::Enter => {
|
|
||||||
app.edit_cred_form();
|
|
||||||
}
|
|
||||||
KeyCode::Char('a') => {
|
|
||||||
app.new_cred_form();
|
|
||||||
}
|
|
||||||
KeyCode::Char('d') => match app.delete_cred() {
|
|
||||||
Ok(()) => app.toast("deleted", true),
|
|
||||||
Err(err) => app.toast(err.to_string(), false),
|
|
||||||
},
|
},
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
+41
-44
@@ -1,5 +1,5 @@
|
|||||||
use crate::app::App;
|
use crate::app::App;
|
||||||
use crate::ui::component::{panel, panel_with_subtitle};
|
use crate::ui::component::{ListAction, handle_list_nav, panel, panel_with_subtitle};
|
||||||
use crate::ui::{BLUE, GREEN, MUTED, SELECTED_BG, TEXT};
|
use crate::ui::{BLUE, GREEN, MUTED, SELECTED_BG, TEXT};
|
||||||
|
|
||||||
use super::View;
|
use super::View;
|
||||||
@@ -189,48 +189,9 @@ fn handle_import(app: &mut App, key: KeyEvent) -> Result<()> {
|
|||||||
let shell_len = app.session.import.shell_candidates.len();
|
let shell_len = app.session.import.shell_candidates.len();
|
||||||
let total = shell_len + app.session.import.candidates.len();
|
let total = shell_len + app.session.import.candidates.len();
|
||||||
|
|
||||||
match key.code {
|
match handle_list_nav(&mut app.session.import.cursor, total, key) {
|
||||||
KeyCode::Esc => app.session.mode = crate::app::Mode::Home,
|
ListAction::Cancel => app.session.mode = crate::app::Mode::Home,
|
||||||
KeyCode::Down | KeyCode::Char('j') if total > 0 => {
|
ListAction::Select => {
|
||||||
app.session.import.cursor = (app.session.import.cursor + 1) % total;
|
|
||||||
}
|
|
||||||
KeyCode::Up | KeyCode::Char('k') if total > 0 => {
|
|
||||||
app.session.import.cursor = (app.session.import.cursor + total - 1) % total;
|
|
||||||
}
|
|
||||||
KeyCode::Char(' ') => {
|
|
||||||
if app.session.import.cursor < shell_len {
|
|
||||||
let can_toggle = app
|
|
||||||
.session
|
|
||||||
.import
|
|
||||||
.shell_candidates
|
|
||||||
.get(app.session.import.cursor)
|
|
||||||
.is_some_and(|c| c.conflict.is_none());
|
|
||||||
if can_toggle
|
|
||||||
&& let Some(v) = app.session.import.shell_selected.get_mut(app.session.import.cursor)
|
|
||||||
{
|
|
||||||
*v = !*v;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let ssh_idx = app.session.import.cursor - shell_len;
|
|
||||||
if let Some(v) = app.session.import.selected.get_mut(ssh_idx) {
|
|
||||||
*v = !*v;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KeyCode::Char('a') => {
|
|
||||||
app.session
|
|
||||||
.import
|
|
||||||
.shell_selected
|
|
||||||
.iter_mut()
|
|
||||||
.zip(&app.session.import.shell_candidates)
|
|
||||||
.for_each(|(sel, c)| *sel = c.conflict.is_none());
|
|
||||||
app.session.import.selected.fill(true);
|
|
||||||
}
|
|
||||||
KeyCode::Char('A') => {
|
|
||||||
app.session.import.shell_selected.fill(false);
|
|
||||||
app.session.import.selected.fill(false);
|
|
||||||
}
|
|
||||||
KeyCode::Enter => {
|
|
||||||
let mut imported = 0;
|
let mut imported = 0;
|
||||||
let picked_shells: Vec<_> = app
|
let picked_shells: Vec<_> = app
|
||||||
.session
|
.session
|
||||||
@@ -272,7 +233,43 @@ fn handle_import(app: &mut App, key: KeyEvent) -> Result<()> {
|
|||||||
}
|
}
|
||||||
app.session.mode = crate::app::Mode::Home;
|
app.session.mode = crate::app::Mode::Home;
|
||||||
}
|
}
|
||||||
_ => {}
|
ListAction::None => match key.code {
|
||||||
|
KeyCode::Char(' ') => {
|
||||||
|
if app.session.import.cursor < shell_len {
|
||||||
|
let can_toggle = app
|
||||||
|
.session
|
||||||
|
.import
|
||||||
|
.shell_candidates
|
||||||
|
.get(app.session.import.cursor)
|
||||||
|
.is_some_and(|c| c.conflict.is_none());
|
||||||
|
if can_toggle
|
||||||
|
&& let Some(v) =
|
||||||
|
app.session.import.shell_selected.get_mut(app.session.import.cursor)
|
||||||
|
{
|
||||||
|
*v = !*v;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let ssh_idx = app.session.import.cursor - shell_len;
|
||||||
|
if let Some(v) = app.session.import.selected.get_mut(ssh_idx) {
|
||||||
|
*v = !*v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyCode::Char('a') => {
|
||||||
|
app.session
|
||||||
|
.import
|
||||||
|
.shell_selected
|
||||||
|
.iter_mut()
|
||||||
|
.zip(&app.session.import.shell_candidates)
|
||||||
|
.for_each(|(sel, c)| *sel = c.conflict.is_none());
|
||||||
|
app.session.import.selected.fill(true);
|
||||||
|
}
|
||||||
|
KeyCode::Char('A') => {
|
||||||
|
app.session.import.shell_selected.fill(false);
|
||||||
|
app.session.import.selected.fill(false);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user