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 input;
|
||||
pub mod layout;
|
||||
pub mod list_nav;
|
||||
pub mod panel;
|
||||
pub mod toast;
|
||||
|
||||
@@ -11,5 +12,6 @@ pub use dialog::*;
|
||||
pub use form_list::{FormRow, draw_form_list};
|
||||
pub use input::draw_input;
|
||||
pub use layout::*;
|
||||
pub use list_nav::{ListAction, handle_list_nav};
|
||||
pub use panel::*;
|
||||
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::ui::component::{ListAction, handle_list_nav};
|
||||
use crate::ui::{ACCENT, MUTED, PANEL, SELECTED_BG, TEXT};
|
||||
|
||||
use super::View;
|
||||
@@ -82,20 +83,15 @@ impl View for ActionMenuView {
|
||||
}
|
||||
|
||||
fn handle_key(&self, app: &mut App, key: KeyEvent) -> Result<()> {
|
||||
let len = ACTIONS.len();
|
||||
match key.code {
|
||||
KeyCode::Char(':') | KeyCode::Esc => {
|
||||
if key.code == KeyCode::Char(':') {
|
||||
app.session.mode = Mode::Home;
|
||||
return Ok(());
|
||||
}
|
||||
match handle_list_nav(&mut app.session.action_menu.cursor, ACTIONS.len(), key) {
|
||||
ListAction::Cancel => {
|
||||
app.session.mode = Mode::Home;
|
||||
}
|
||||
KeyCode::Down | KeyCode::Char('j') => {
|
||||
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 => {
|
||||
ListAction::Select => {
|
||||
app.session.mode = Mode::Home;
|
||||
match app.session.action_menu.cursor {
|
||||
0 => app.enter_combined_import()?,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::app::{App, Mode};
|
||||
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 super::{View, scroll_rows};
|
||||
@@ -118,35 +118,18 @@ fn draw_credentials(frame: &mut Frame<'_>, app: &App, area: Rect) {
|
||||
// ── Key handling ───────────────────────────────────────────────
|
||||
|
||||
fn handle_credentials(app: &mut App, key: KeyEvent) -> Result<()> {
|
||||
match key.code {
|
||||
KeyCode::Esc => app.session.mode = Mode::Home,
|
||||
KeyCode::Down | KeyCode::Char('j') => {
|
||||
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::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();
|
||||
}
|
||||
match handle_list_nav(&mut app.session.credentials.selected, len, key) {
|
||||
ListAction::Cancel => app.session.mode = Mode::Home,
|
||||
ListAction::Select => app.edit_cred_form(),
|
||||
ListAction::None => match key.code {
|
||||
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(())
|
||||
}
|
||||
|
||||
+40
-43
@@ -1,5 +1,5 @@
|
||||
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 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 total = shell_len + app.session.import.candidates.len();
|
||||
|
||||
match key.code {
|
||||
KeyCode::Esc => app.session.mode = crate::app::Mode::Home,
|
||||
KeyCode::Down | KeyCode::Char('j') if total > 0 => {
|
||||
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 => {
|
||||
match handle_list_nav(&mut app.session.import.cursor, total, key) {
|
||||
ListAction::Cancel => app.session.mode = crate::app::Mode::Home,
|
||||
ListAction::Select => {
|
||||
let mut imported = 0;
|
||||
let picked_shells: Vec<_> = app
|
||||
.session
|
||||
@@ -272,7 +233,43 @@ fn handle_import(app: &mut App, key: KeyEvent) -> Result<()> {
|
||||
}
|
||||
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(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user