login and register are now havving own handlers and loaders, moving logic out of event.rs and ui.rs
This commit is contained in:
@@ -305,94 +305,33 @@ impl EventHandler {
|
||||
|
||||
if !overlay_active {
|
||||
if let Page::Login(login_page) = &mut router.current {
|
||||
use crossterm::event::{KeyCode, KeyModifiers};
|
||||
|
||||
// Inside canvas: at the last field, 'j' or Down moves focus to buttons
|
||||
if !app_state.ui.focus_outside_canvas {
|
||||
let last_idx = login_page
|
||||
.editor
|
||||
.data_provider()
|
||||
.field_count()
|
||||
.saturating_sub(1);
|
||||
let at_last = login_page.editor.current_field() >= last_idx;
|
||||
if at_last
|
||||
&& matches!(
|
||||
(key_code, modifiers),
|
||||
(KeyCode::Char('j'), KeyModifiers::NONE) | (KeyCode::Down, _)
|
||||
)
|
||||
{
|
||||
app_state.ui.focus_outside_canvas = true;
|
||||
app_state.focused_button_index = 0; // focus "Login" button
|
||||
// Ensure canvas mode is ReadOnly when leaving
|
||||
login_page.editor.set_mode(CanvasMode::ReadOnly);
|
||||
return Ok(EventOutcome::Ok("Focus moved to buttons".to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
// Only forward to the canvas while focus is inside it
|
||||
if !app_state.ui.focus_outside_canvas {
|
||||
match login_page.handle_key_event(key_event) {
|
||||
KeyEventOutcome::Consumed(Some(msg)) => {
|
||||
self.command_message = msg;
|
||||
return Ok(EventOutcome::Ok("Login input updated".to_string()));
|
||||
}
|
||||
KeyEventOutcome::Consumed(None) => {
|
||||
return Ok(EventOutcome::Ok("Login input updated".to_string()));
|
||||
}
|
||||
KeyEventOutcome::Pending => {
|
||||
return Ok(EventOutcome::Ok("Waiting for next key...".to_string()));
|
||||
}
|
||||
KeyEventOutcome::NotMatched => {
|
||||
// fall through to other handlers (buttons, etc.)
|
||||
}
|
||||
}
|
||||
let outcome =
|
||||
login::event::handle_login_event(event, app_state, login_page)?;
|
||||
// Only return if the login page actually consumed the key
|
||||
if !outcome.get_message_if_ok().is_empty() {
|
||||
return Ok(outcome);
|
||||
}
|
||||
} else if let Page::Register(register_page) = &mut router.current {
|
||||
use crossterm::event::{KeyCode, KeyModifiers};
|
||||
|
||||
// Inside canvas: at the last field, 'j' or Down moves focus to buttons
|
||||
if !app_state.ui.focus_outside_canvas {
|
||||
let last_idx = register_page.editor.data_provider().field_count().saturating_sub(1);
|
||||
let at_last = register_page.editor.current_field() >= last_idx;
|
||||
if at_last
|
||||
&& matches!(
|
||||
(key_code, modifiers),
|
||||
(KeyCode::Char('j'), KeyModifiers::NONE) | (KeyCode::Down, _)
|
||||
)
|
||||
{
|
||||
app_state.ui.focus_outside_canvas = true;
|
||||
app_state.focused_button_index = 0; // focus "Register" button
|
||||
register_page.editor.set_mode(CanvasMode::ReadOnly);
|
||||
return Ok(EventOutcome::Ok("Focus moved to buttons".to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
// Only forward to the canvas while focus is inside it
|
||||
if !app_state.ui.focus_outside_canvas {
|
||||
match register_page.handle_key_event(key_event) {
|
||||
KeyEventOutcome::Consumed(Some(msg)) => {
|
||||
self.command_message = msg;
|
||||
return Ok(EventOutcome::Ok("Register input updated".to_string()));
|
||||
}
|
||||
KeyEventOutcome::Consumed(None) => {
|
||||
return Ok(EventOutcome::Ok("Register input updated".to_string()));
|
||||
}
|
||||
KeyEventOutcome::Pending => {
|
||||
return Ok(EventOutcome::Ok("Waiting for next key...".to_string()));
|
||||
}
|
||||
KeyEventOutcome::NotMatched => {
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
let outcome = crate::pages::register::event::handle_register_event(
|
||||
event,
|
||||
app_state,
|
||||
register_page,
|
||||
)?;
|
||||
// Only stop if page actually consumed the key; else fall through to global handling
|
||||
if !outcome.get_message_if_ok().is_empty() {
|
||||
return Ok(outcome);
|
||||
}
|
||||
} else if let Page::Form(path) = &router.current {
|
||||
// NEW: Delegate Form event handling
|
||||
return forms::event::handle_form_event(
|
||||
let outcome = forms::event::handle_form_event(
|
||||
event,
|
||||
app_state,
|
||||
path,
|
||||
&mut self.ideal_cursor_column
|
||||
);
|
||||
&mut self.ideal_cursor_column,
|
||||
)?;
|
||||
// Only return if the form page actually consumed the key
|
||||
if !outcome.get_message_if_ok().is_empty() {
|
||||
return Ok(outcome);
|
||||
}
|
||||
}
|
||||
}
|
||||
if toggle_sidebar(
|
||||
@@ -508,19 +447,6 @@ impl EventHandler {
|
||||
match &mut router.current {
|
||||
// LOGIN: From buttons (general) back into the canvas with 'k' (Up),
|
||||
// but ONLY from the left-most "Login" button.
|
||||
Page::Login(page) => {
|
||||
if app_state.ui.focus_outside_canvas {
|
||||
if app_state.focused_button_index == 0
|
||||
&& matches!(ma, crate::movement::MovementAction::Up)
|
||||
{
|
||||
app_state.ui.focus_outside_canvas = false;
|
||||
// Enter canvas in ReadOnly mode (never jump straight to Edit)
|
||||
page.editor.set_mode(CanvasMode::ReadOnly);
|
||||
// Optional: keep current field (usually 0 initially)
|
||||
return Ok(EventOutcome::Ok(String::new()));
|
||||
}
|
||||
}
|
||||
}
|
||||
Page::AddTable(state) => {
|
||||
if state.handle_movement(ma) {
|
||||
// Keep UI focus consistent with inputs vs. outer elements
|
||||
|
||||
60
client/src/pages/admin/admin/event.rs
Normal file
60
client/src/pages/admin/admin/event.rs
Normal file
@@ -0,0 +1,60 @@
|
||||
// src/pages/admin/admin/event.rs
|
||||
use anyhow::Result;
|
||||
use crossterm::event::KeyEvent;
|
||||
|
||||
use crate::buffer::state::BufferState;
|
||||
use crate::config::binds::config::Config;
|
||||
use crate::pages::admin::AdminState;
|
||||
use crate::pages::admin::main::logic::handle_admin_navigation;
|
||||
use crate::state::app::state::AppState;
|
||||
|
||||
/// Handle all Admin page-specific key events (movement + actions).
|
||||
/// Returns true if the key was handled (so the caller should stop propagation).
|
||||
pub fn handle_admin_event(
|
||||
key_event: KeyEvent,
|
||||
config: &Config,
|
||||
app_state: &mut AppState,
|
||||
admin_state: &mut AdminState,
|
||||
buffer_state: &mut BufferState,
|
||||
command_message: &mut String,
|
||||
) -> Result<bool> {
|
||||
// 1) Map general action to MovementAction (same mapping used in event.rs)
|
||||
let movement_action = if let Some(act) =
|
||||
config.get_general_action(key_event.code, key_event.modifiers)
|
||||
{
|
||||
use crate::movement::MovementAction;
|
||||
match act {
|
||||
"up" => Some(MovementAction::Up),
|
||||
"down" => Some(MovementAction::Down),
|
||||
"left" => Some(MovementAction::Left),
|
||||
"right" => Some(MovementAction::Right),
|
||||
"next" => Some(MovementAction::Next),
|
||||
"previous" => Some(MovementAction::Previous),
|
||||
"select" => Some(MovementAction::Select),
|
||||
"esc" => Some(MovementAction::Esc),
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(ma) = movement_action {
|
||||
if admin_state.handle_movement(app_state, ma) {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
|
||||
// 2) Rich Admin navigation (buttons, selections, etc.)
|
||||
if handle_admin_navigation(
|
||||
key_event,
|
||||
config,
|
||||
app_state,
|
||||
admin_state,
|
||||
buffer_state,
|
||||
command_message,
|
||||
) {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
54
client/src/pages/admin/admin/loader.rs
Normal file
54
client/src/pages/admin/admin/loader.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
// src/pages/admin/admin/loader.rs
|
||||
use anyhow::{Context, Result};
|
||||
|
||||
use crate::pages::admin::{AdminFocus, AdminState};
|
||||
use crate::services::grpc_client::GrpcClient;
|
||||
use crate::state::app::state::AppState;
|
||||
|
||||
/// Refresh admin data and ensure focus and selections are valid.
|
||||
pub async fn refresh_admin_state(
|
||||
grpc_client: &mut GrpcClient,
|
||||
app_state: &mut AppState,
|
||||
admin_state: &mut AdminState,
|
||||
) -> Result<()> {
|
||||
// Fetch latest profile tree
|
||||
let refreshed_tree = grpc_client
|
||||
.get_profile_tree()
|
||||
.await
|
||||
.context("Failed to refresh profile tree for Admin panel")?;
|
||||
app_state.profile_tree = refreshed_tree;
|
||||
|
||||
// Populate profile names for AdminState's list
|
||||
let profile_names = app_state
|
||||
.profile_tree
|
||||
.profiles
|
||||
.iter()
|
||||
.map(|p| p.name.clone())
|
||||
.collect::<Vec<_>>();
|
||||
admin_state.set_profiles(profile_names);
|
||||
|
||||
// Ensure a sane focus
|
||||
if admin_state.current_focus == AdminFocus::default()
|
||||
|| !matches!(
|
||||
admin_state.current_focus,
|
||||
AdminFocus::InsideProfilesList
|
||||
| AdminFocus::Tables
|
||||
| AdminFocus::InsideTablesList
|
||||
| AdminFocus::Button1
|
||||
| AdminFocus::Button2
|
||||
| AdminFocus::Button3
|
||||
| AdminFocus::ProfilesPane
|
||||
)
|
||||
{
|
||||
admin_state.current_focus = AdminFocus::ProfilesPane;
|
||||
}
|
||||
|
||||
// Ensure a selection exists when profiles are present
|
||||
if admin_state.profile_list_state.selected().is_none()
|
||||
&& !app_state.profile_tree.profiles.is_empty()
|
||||
{
|
||||
admin_state.profile_list_state.select(Some(0));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -3,5 +3,7 @@
|
||||
pub mod state;
|
||||
pub mod ui;
|
||||
pub mod tui;
|
||||
pub mod event;
|
||||
pub mod loader;
|
||||
|
||||
pub use state::{AdminState, AdminFocus};
|
||||
|
||||
73
client/src/pages/login/event.rs
Normal file
73
client/src/pages/login/event.rs
Normal file
@@ -0,0 +1,73 @@
|
||||
// src/pages/login/event.rs
|
||||
use anyhow::Result;
|
||||
use crossterm::event::{Event, KeyCode, KeyModifiers};
|
||||
use canvas::{keymap::KeyEventOutcome, AppMode as CanvasMode};
|
||||
use crate::{
|
||||
state::app::state::AppState,
|
||||
pages::login::LoginFormState,
|
||||
modes::handlers::event::EventOutcome,
|
||||
};
|
||||
use canvas::DataProvider;
|
||||
|
||||
/// Handles all Login page-specific events
|
||||
pub fn handle_login_event(
|
||||
event: Event,
|
||||
app_state: &mut AppState,
|
||||
login_page: &mut LoginFormState,
|
||||
) -> Result<EventOutcome> {
|
||||
if let Event::Key(key_event) = event {
|
||||
let key_code = key_event.code;
|
||||
let modifiers = key_event.modifiers;
|
||||
|
||||
// From buttons (outside) back into the canvas (ReadOnly) with Up/k from the left-most button
|
||||
if login_page.focus_outside_canvas
|
||||
&& login_page.focused_button_index == 0
|
||||
&& matches!(key_code, KeyCode::Up | KeyCode::Char('k'))
|
||||
&& modifiers.is_empty()
|
||||
{
|
||||
login_page.focus_outside_canvas = false;
|
||||
app_state.ui.focus_outside_canvas = false; // 🔑 keep global in sync
|
||||
login_page.editor.set_mode(CanvasMode::ReadOnly);
|
||||
return Ok(EventOutcome::Ok(String::new()));
|
||||
}
|
||||
|
||||
// Focus handoff: inside canvas → buttons
|
||||
if !login_page.focus_outside_canvas {
|
||||
let last_idx = login_page.editor.data_provider().field_count().saturating_sub(1);
|
||||
let at_last = login_page.editor.current_field() >= last_idx;
|
||||
if at_last
|
||||
&& matches!(
|
||||
(key_code, modifiers),
|
||||
(KeyCode::Char('j'), KeyModifiers::NONE) | (KeyCode::Down, _)
|
||||
)
|
||||
{
|
||||
login_page.focus_outside_canvas = true;
|
||||
login_page.focused_button_index = 0; // focus "Login" button
|
||||
app_state.ui.focus_outside_canvas = true;
|
||||
app_state.focused_button_index = 0;
|
||||
login_page.editor.set_mode(CanvasMode::ReadOnly);
|
||||
return Ok(EventOutcome::Ok("Focus moved to buttons".into()));
|
||||
}
|
||||
}
|
||||
|
||||
// Forward to canvas if focus is inside
|
||||
if !login_page.focus_outside_canvas {
|
||||
match login_page.handle_key_event(key_event) {
|
||||
KeyEventOutcome::Consumed(Some(msg)) => {
|
||||
return Ok(EventOutcome::Ok(msg));
|
||||
}
|
||||
KeyEventOutcome::Consumed(None) => {
|
||||
return Ok(EventOutcome::Ok("Login input updated".into()));
|
||||
}
|
||||
KeyEventOutcome::Pending => {
|
||||
return Ok(EventOutcome::Ok("Waiting for next key...".into()));
|
||||
}
|
||||
KeyEventOutcome::NotMatched => {
|
||||
// fall through to button handling
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(EventOutcome::Ok(String::new()))
|
||||
}
|
||||
@@ -3,7 +3,9 @@
|
||||
pub mod state;
|
||||
pub mod ui;
|
||||
pub mod logic;
|
||||
pub mod event;
|
||||
|
||||
pub use state::*;
|
||||
pub use ui::render_login;
|
||||
pub use logic::*;
|
||||
pub use event::*;
|
||||
|
||||
@@ -127,6 +127,8 @@ impl DataProvider for LoginState {
|
||||
pub struct LoginFormState {
|
||||
pub state: LoginState,
|
||||
pub editor: FormEditor<LoginState>,
|
||||
pub focus_outside_canvas: bool,
|
||||
pub focused_button_index: usize,
|
||||
}
|
||||
|
||||
// manual debug because FormEditor doesnt implement debug
|
||||
@@ -150,7 +152,12 @@ impl LoginFormState {
|
||||
pub fn new() -> Self {
|
||||
let state = LoginState::default();
|
||||
let editor = FormEditor::new(state.clone());
|
||||
Self { state, editor }
|
||||
Self {
|
||||
state,
|
||||
editor,
|
||||
focus_outside_canvas: false,
|
||||
focused_button_index: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// === Delegates to LoginState fields ===
|
||||
|
||||
@@ -80,7 +80,7 @@ pub fn render_login(
|
||||
|
||||
// Login Button
|
||||
let login_button_index = 0;
|
||||
let login_active = if app_state.ui.focus_outside_canvas {
|
||||
let login_active = if login_page.focus_outside_canvas {
|
||||
app_state.focused_button_index == login_button_index
|
||||
} else {
|
||||
false
|
||||
|
||||
76
client/src/pages/register/event.rs
Normal file
76
client/src/pages/register/event.rs
Normal file
@@ -0,0 +1,76 @@
|
||||
// src/pages/register/event.rs
|
||||
use anyhow::Result;
|
||||
use crossterm::event::{Event, KeyCode, KeyModifiers};
|
||||
use canvas::{keymap::KeyEventOutcome, AppMode as CanvasMode};
|
||||
use canvas::DataProvider;
|
||||
use crate::{
|
||||
state::app::state::AppState,
|
||||
pages::register::RegisterFormState,
|
||||
modes::handlers::event::EventOutcome,
|
||||
};
|
||||
|
||||
/// Handles all Register page-specific events.
|
||||
/// Return a non-empty Ok(message) only when the page actually consumed the key,
|
||||
/// otherwise return Ok("") to let global handling proceed.
|
||||
pub fn handle_register_event(
|
||||
event: Event,
|
||||
app_state: &mut AppState,
|
||||
register_page: &mut RegisterFormState,
|
||||
)-> Result<EventOutcome> {
|
||||
if let Event::Key(key_event) = event {
|
||||
let key_code = key_event.code;
|
||||
let modifiers = key_event.modifiers;
|
||||
|
||||
// From buttons (outside) back into the canvas (ReadOnly) with Up/k from the left-most button
|
||||
if register_page.focus_outside_canvas
|
||||
&& register_page.focused_button_index == 0
|
||||
&& matches!(key_code, KeyCode::Up | KeyCode::Char('k'))
|
||||
&& modifiers.is_empty()
|
||||
{
|
||||
register_page.focus_outside_canvas = false;
|
||||
// Keep global in sync for now (cursor styling elsewhere still reads it)
|
||||
app_state.ui.focus_outside_canvas = false;
|
||||
register_page.editor.set_mode(CanvasMode::ReadOnly);
|
||||
return Ok(EventOutcome::Ok(String::new()));
|
||||
}
|
||||
|
||||
// Focus handoff: inside canvas → buttons
|
||||
if !register_page.focus_outside_canvas {
|
||||
let last_idx = register_page.editor.data_provider().field_count().saturating_sub(1);
|
||||
let at_last = register_page.editor.current_field() >= last_idx;
|
||||
if at_last
|
||||
&& matches!(
|
||||
(key_code, modifiers),
|
||||
(KeyCode::Char('j'), KeyModifiers::NONE) | (KeyCode::Down, _)
|
||||
)
|
||||
{
|
||||
register_page.focus_outside_canvas = true;
|
||||
register_page.focused_button_index = 0; // focus "Register" button
|
||||
// Keep global in sync for now
|
||||
app_state.ui.focus_outside_canvas = true;
|
||||
app_state.focused_button_index = 0;
|
||||
register_page.editor.set_mode(CanvasMode::ReadOnly);
|
||||
return Ok(EventOutcome::Ok("Focus moved to buttons".into()));
|
||||
}
|
||||
}
|
||||
|
||||
// Forward to canvas if focus is inside
|
||||
if !register_page.focus_outside_canvas {
|
||||
match register_page.handle_key_event(key_event) {
|
||||
KeyEventOutcome::Consumed(Some(msg)) => {
|
||||
return Ok(EventOutcome::Ok(msg));
|
||||
}
|
||||
KeyEventOutcome::Consumed(None) => {
|
||||
return Ok(EventOutcome::Ok("Register input updated".into()));
|
||||
}
|
||||
KeyEventOutcome::Pending => {
|
||||
return Ok(EventOutcome::Ok("Waiting for next key...".into()));
|
||||
}
|
||||
KeyEventOutcome::NotMatched => {
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(EventOutcome::Ok(String::new()))
|
||||
}
|
||||
@@ -54,6 +54,8 @@ pub async fn back_to_login(
|
||||
buffer_state.update_history(AppView::Login);
|
||||
|
||||
// Reset focus state
|
||||
register_state.focus_outside_canvas = false;
|
||||
register_state.focused_button_index = 0;
|
||||
app_state.ui.focus_outside_canvas = false;
|
||||
app_state.focused_button_index = 0;
|
||||
|
||||
|
||||
@@ -5,8 +5,10 @@ pub mod ui;
|
||||
pub mod state;
|
||||
pub mod logic;
|
||||
pub mod suggestions;
|
||||
pub mod event;
|
||||
|
||||
// pub use state::*;
|
||||
pub use ui::render_register;
|
||||
pub use logic::*;
|
||||
pub use state::*;
|
||||
pub use event::*;
|
||||
|
||||
@@ -146,6 +146,8 @@ impl DataProvider for RegisterState {
|
||||
pub struct RegisterFormState {
|
||||
pub state: RegisterState,
|
||||
pub editor: FormEditor<RegisterState>,
|
||||
pub focus_outside_canvas: bool,
|
||||
pub focused_button_index: usize,
|
||||
}
|
||||
|
||||
impl Default for RegisterFormState {
|
||||
@@ -174,7 +176,12 @@ impl RegisterFormState {
|
||||
pub fn new() -> Self {
|
||||
let state = RegisterState::default();
|
||||
let editor = FormEditor::new(state.clone());
|
||||
Self { state, editor }
|
||||
Self {
|
||||
state,
|
||||
editor,
|
||||
focus_outside_canvas: false,
|
||||
focused_button_index: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// === Delegates to RegisterState ===
|
||||
|
||||
@@ -80,8 +80,9 @@ pub fn render_register(
|
||||
|
||||
// Register Button
|
||||
let register_button_index = 0;
|
||||
let register_active = app_state.ui.focus_outside_canvas
|
||||
&& app_state.focused_button_index == register_button_index;
|
||||
let register_active =
|
||||
register_page.focus_outside_canvas
|
||||
&& register_page.focused_button_index == register_button_index;
|
||||
let mut register_style = Style::default().fg(theme.fg);
|
||||
let mut register_border = Style::default().fg(theme.border);
|
||||
if register_active {
|
||||
@@ -104,8 +105,9 @@ pub fn render_register(
|
||||
|
||||
// Return Button
|
||||
let return_button_index = 1;
|
||||
let return_active = app_state.ui.focus_outside_canvas
|
||||
&& app_state.focused_button_index == return_button_index;
|
||||
let return_active =
|
||||
register_page.focus_outside_canvas
|
||||
&& register_page.focused_button_index == return_button_index;
|
||||
let mut return_style = Style::default().fg(theme.fg);
|
||||
let mut return_border = Style::default().fg(theme.border);
|
||||
if return_active {
|
||||
|
||||
Reference in New Issue
Block a user