diff --git a/client/src/modes/handlers/event.rs b/client/src/modes/handlers/event.rs index dfd7499..ceb597e 100644 --- a/client/src/modes/handlers/event.rs +++ b/client/src/modes/handlers/event.rs @@ -534,17 +534,17 @@ impl EventHandler { if let Page::AddTable(add_table_state) = &mut router.current { let client_clone = self.grpc_client.clone(); let sender_clone = self.save_table_result_sender.clone(); - if crate::pages::admin_panel::add_table::event::handle_add_table_event( + let outcome = crate::pages::admin_panel::add_table::event::handle_add_table_event( key_event, - movement_action, // wrapper handles both movement + nav + movement_action, config, app_state, add_table_state, client_clone, sender_clone, - &mut self.command_message, - ) { - return Ok(EventOutcome::Ok(self.command_message.clone())); + )?; + if !outcome.get_message_if_ok().is_empty() { + return Ok(outcome); } } diff --git a/client/src/pages/admin_panel/add_table/event.rs b/client/src/pages/admin_panel/add_table/event.rs index e09457c..935146d 100644 --- a/client/src/pages/admin_panel/add_table/event.rs +++ b/client/src/pages/admin_panel/add_table/event.rs @@ -1,51 +1,130 @@ // src/pages/admin_panel/add_table/event.rs +use anyhow::Result; use crate::config::binds::config::Config; -use crate::movement::MovementAction; -use crate::pages::admin_panel::add_table::nav; +use crate::movement::{move_focus, MovementAction}; +use crate::pages::admin_panel::add_table::logic::{ + handle_add_column_action, handle_delete_selected_columns, handle_save_table_action, +}; use crate::pages::admin_panel::add_table::nav::SaveTableResultSender; use crate::pages::admin_panel::add_table::state::{AddTableFocus, AddTableFormState}; use crate::services::GrpcClient; use crate::state::app::state::AppState; +use crate::modes::handlers::event::EventOutcome; use crossterm::event::KeyEvent; -/// Handle all AddTable page-specific events in one place. -/// - First try movement actions (Up/Down/Select/Esc) via AddTableState::handle_movement -/// - Then, if not handled, try rich actions via nav::handle_add_table_navigation -/// -/// Returns true if the key was handled (caller should stop propagation). +/// Focus traversal order for AddTable (outside canvas) +const ADD_TABLE_FOCUS_ORDER: [AddTableFocus; 7] = [ + AddTableFocus::InputTableName, + AddTableFocus::InputColumnName, + AddTableFocus::InputColumnType, + AddTableFocus::AddColumnButton, + AddTableFocus::SaveButton, + AddTableFocus::DeleteSelectedButton, + AddTableFocus::CancelButton, +]; + +/// Unified AddTable event handler (like AddLogic) pub fn handle_add_table_event( key_event: KeyEvent, - movement_action: Option, - config: &Config, + movement: Option, + _config: &Config, app_state: &mut AppState, page: &mut AddTableFormState, - grpc_client: GrpcClient, + mut grpc_client: GrpcClient, save_result_sender: SaveTableResultSender, - command_message: &mut String, -) -> bool { - // 1) Try movement first - if let Some(ma) = movement_action { - if page.state.handle_movement(ma) { - let is_canvas_input = matches!( - page.current_focus(), - AddTableFocus::InputTableName - | AddTableFocus::InputColumnName - | AddTableFocus::InputColumnType - ); - app_state.ui.focus_outside_canvas = !is_canvas_input; - return true; +) -> Result { + // 1) Inside canvas (FormEditor) + let inside_canvas_inputs = matches!( + page.current_focus(), + AddTableFocus::InputTableName + | AddTableFocus::InputColumnName + | AddTableFocus::InputColumnType + ); + + if inside_canvas_inputs { + match page.editor.handle_key_event(key_event) { + canvas::keymap::KeyEventOutcome::Consumed(Some(msg)) => { + page.sync_from_editor(); + return Ok(EventOutcome::Ok(msg)); + } + canvas::keymap::KeyEventOutcome::Consumed(None) => { + page.sync_from_editor(); + return Ok(EventOutcome::Ok("Input updated".into())); + } + canvas::keymap::KeyEventOutcome::Pending => { + return Ok(EventOutcome::Ok(String::new())); + } + canvas::keymap::KeyEventOutcome::NotMatched => { + // fall through + } } } - // 2) Rich actions/navigation - nav::handle_add_table_navigation( - key_event, - config, - app_state, - &mut page.state, - grpc_client, - save_result_sender, - command_message, - ) + // 2) Movement outside canvas + if let Some(ma) = movement { + let mut current = page.current_focus(); + if move_focus(&ADD_TABLE_FOCUS_ORDER, &mut current, ma) { + page.set_current_focus(current); + app_state.ui.focus_outside_canvas = !matches!( + page.current_focus(), + AddTableFocus::InputTableName + | AddTableFocus::InputColumnName + | AddTableFocus::InputColumnType + ); + return Ok(EventOutcome::Ok(String::new())); + } + + // 3) Rich actions + match ma { + MovementAction::Select => match page.current_focus() { + AddTableFocus::AddColumnButton => { + if let Some(focus_after_add) = + crate::pages::admin_panel::add_table::logic::handle_add_column_action( + &mut page.state, + &mut String::new(), + ) + { + page.set_current_focus(focus_after_add); + return Ok(EventOutcome::Ok("Column added".into())); + } + } + AddTableFocus::SaveButton => { + if page.state.table_name.is_empty() { + return Ok(EventOutcome::Ok("Cannot save: Table name is empty".into())); + } + if page.state.columns.is_empty() { + return Ok(EventOutcome::Ok("Cannot save: No columns defined".into())); + } + app_state.show_loading_dialog("Saving", "Please wait..."); + let state_clone = page.state.clone(); + let sender_clone = save_result_sender.clone(); + tokio::spawn(async move { + let result = + crate::pages::admin_panel::add_table::logic::handle_save_table_action( + &mut grpc_client, + &state_clone, + ) + .await; + let _ = sender_clone.send(result).await; + }); + return Ok(EventOutcome::Ok("Saving table...".into())); + } + AddTableFocus::DeleteSelectedButton => { + let msg = + crate::pages::admin_panel::add_table::logic::handle_delete_selected_columns( + &mut page.state, + ); + return Ok(EventOutcome::Ok(msg)); + } + AddTableFocus::CancelButton => { + return Ok(EventOutcome::Ok("Cancelled Add Table".into())); + } + _ => {} + }, + _ => {} + } + } + + Ok(EventOutcome::Ok(String::new())) } diff --git a/client/src/pages/admin_panel/add_table/nav.rs b/client/src/pages/admin_panel/add_table/nav.rs index e59f76c..c2e5377 100644 --- a/client/src/pages/admin_panel/add_table/nav.rs +++ b/client/src/pages/admin_panel/add_table/nav.rs @@ -1,206 +1,6 @@ // src/pages/admin_panel/add_table/nav.rs -use crate::config::binds::config::Config; -use crate::state::{ - app::state::AppState, -}; -use crate::pages::admin_panel::add_table::state::{AddTableFocus, AddTableState}; -use crossterm::event::{KeyEvent}; -use ratatui::widgets::TableState; -use crate::pages::admin_panel::add_table::logic::{handle_add_column_action, handle_save_table_action}; -use crate::ui::handlers::context::DialogPurpose; -use crate::services::GrpcClient; -use tokio::sync::mpsc; use anyhow::Result; +use tokio::sync::mpsc; pub type SaveTableResultSender = mpsc::Sender>; - -fn navigate_table_up(table_state: &mut TableState, item_count: usize) -> bool { - if item_count == 0 { return false; } - let current_selection = table_state.selected(); - match current_selection { - Some(index) => { - if index > 0 { table_state.select(Some(index - 1)); true } - else { false } - } - None => { table_state.select(Some(0)); true } - } -} - -fn navigate_table_down(table_state: &mut TableState, item_count: usize) -> bool { - if item_count == 0 { return false; } - let current_selection = table_state.selected(); - match current_selection { - Some(index) => { - if index < item_count - 1 { table_state.select(Some(index + 1)); true } - else { false } - } - None => { table_state.select(Some(0)); true } - } -} - -pub fn handle_add_table_navigation( - key: KeyEvent, - config: &Config, - app_state: &mut AppState, - add_table_state: &mut AddTableState, - grpc_client: GrpcClient, - save_result_sender: SaveTableResultSender, - command_message: &mut String, -) -> bool { - let action = config.get_general_action(key.code, key.modifiers); - let current_focus = add_table_state.current_focus; - let mut handled = true; - let mut new_focus = current_focus; - - if matches!(current_focus, AddTableFocus::InsideColumnsTable | AddTableFocus::InsideIndexesTable | AddTableFocus::InsideLinksTable) { - if matches!(action.as_deref(), Some("next_option") | Some("previous_option")) { - *command_message = "Press Esc to exit table item navigation first.".to_string(); - return true; - } - } - - match action.as_deref() { - Some("exit_table_scroll") => { - match current_focus { - AddTableFocus::InsideColumnsTable => { - add_table_state.column_table_state.select(None); - new_focus = AddTableFocus::ColumnsTable; - // *command_message = "Exited Columns Table".to_string(); // Minimal change: remove message - } - AddTableFocus::InsideIndexesTable => { - add_table_state.index_table_state.select(None); - new_focus = AddTableFocus::IndexesTable; - // *command_message = "Exited Indexes Table".to_string(); - } - AddTableFocus::InsideLinksTable => { - add_table_state.link_table_state.select(None); - new_focus = AddTableFocus::LinksTable; - // *command_message = "Exited Links Table".to_string(); - } - _ => handled = false, - } - } - Some("move_up") => { - match current_focus { - AddTableFocus::InputTableName => { - // MINIMAL CHANGE: Do nothing, new_focus remains current_focus - // *command_message = "At top of form.".to_string(); // Remove message - } - AddTableFocus::InputColumnName => new_focus = AddTableFocus::InputTableName, - AddTableFocus::InputColumnType => new_focus = AddTableFocus::InputColumnName, - AddTableFocus::AddColumnButton => new_focus = AddTableFocus::InputColumnType, - AddTableFocus::ColumnsTable => new_focus = AddTableFocus::AddColumnButton, - AddTableFocus::IndexesTable => new_focus = AddTableFocus::ColumnsTable, - AddTableFocus::LinksTable => new_focus = AddTableFocus::IndexesTable, - AddTableFocus::InsideColumnsTable => { navigate_table_up(&mut add_table_state.column_table_state, add_table_state.columns.len()); } - AddTableFocus::InsideIndexesTable => { navigate_table_up(&mut add_table_state.index_table_state, add_table_state.indexes.len()); } - AddTableFocus::InsideLinksTable => { navigate_table_up(&mut add_table_state.link_table_state, add_table_state.links.len()); } - AddTableFocus::SaveButton => new_focus = AddTableFocus::LinksTable, - AddTableFocus::DeleteSelectedButton => new_focus = AddTableFocus::SaveButton, - AddTableFocus::CancelButton => new_focus = AddTableFocus::DeleteSelectedButton, - } - } - Some("move_down") => { - match current_focus { - AddTableFocus::InputTableName => new_focus = AddTableFocus::InputColumnName, - AddTableFocus::InputColumnName => new_focus = AddTableFocus::InputColumnType, - AddTableFocus::InputColumnType => { - add_table_state.last_canvas_field = 2; - new_focus = AddTableFocus::AddColumnButton; - }, - AddTableFocus::AddColumnButton => new_focus = AddTableFocus::ColumnsTable, - AddTableFocus::ColumnsTable => new_focus = AddTableFocus::IndexesTable, - AddTableFocus::IndexesTable => new_focus = AddTableFocus::LinksTable, - AddTableFocus::LinksTable => new_focus = AddTableFocus::SaveButton, - AddTableFocus::InsideColumnsTable => { navigate_table_down(&mut add_table_state.column_table_state, add_table_state.columns.len()); } - AddTableFocus::InsideIndexesTable => { navigate_table_down(&mut add_table_state.index_table_state, add_table_state.indexes.len()); } - AddTableFocus::InsideLinksTable => { navigate_table_down(&mut add_table_state.link_table_state, add_table_state.links.len()); } - AddTableFocus::SaveButton => new_focus = AddTableFocus::DeleteSelectedButton, - AddTableFocus::DeleteSelectedButton => new_focus = AddTableFocus::CancelButton, - AddTableFocus::CancelButton => { - // MINIMAL CHANGE: Do nothing, new_focus remains current_focus - // *command_message = "At bottom of form.".to_string(); // Remove message - } - } - } - Some("next_option") => { // This logic should already be non-wrapping - match current_focus { - AddTableFocus::InputTableName | AddTableFocus::InputColumnName | AddTableFocus::InputColumnType => - { new_focus = AddTableFocus::AddColumnButton; } - AddTableFocus::AddColumnButton => new_focus = AddTableFocus::ColumnsTable, - AddTableFocus::ColumnsTable => new_focus = AddTableFocus::IndexesTable, - AddTableFocus::IndexesTable => new_focus = AddTableFocus::LinksTable, - AddTableFocus::LinksTable => new_focus = AddTableFocus::SaveButton, - AddTableFocus::SaveButton => new_focus = AddTableFocus::DeleteSelectedButton, - AddTableFocus::DeleteSelectedButton => new_focus = AddTableFocus::CancelButton, - AddTableFocus::CancelButton => { /* *command_message = "At last focusable area.".to_string(); */ } // No change in focus - _ => handled = false, - } - } - Some("previous_option") => { // This logic should already be non-wrapping - match current_focus { - AddTableFocus::InputTableName | AddTableFocus::InputColumnName | AddTableFocus::InputColumnType => - { /* *command_message = "At first focusable area.".to_string(); */ } // No change in focus - AddTableFocus::AddColumnButton => new_focus = AddTableFocus::InputColumnType, - AddTableFocus::ColumnsTable => new_focus = AddTableFocus::AddColumnButton, - AddTableFocus::IndexesTable => new_focus = AddTableFocus::ColumnsTable, - AddTableFocus::LinksTable => new_focus = AddTableFocus::IndexesTable, - AddTableFocus::SaveButton => new_focus = AddTableFocus::LinksTable, - AddTableFocus::DeleteSelectedButton => new_focus = AddTableFocus::SaveButton, - AddTableFocus::CancelButton => new_focus = AddTableFocus::DeleteSelectedButton, - _ => handled = false, - } - } - Some("next_field") => { - new_focus = match current_focus { - AddTableFocus::InputTableName => AddTableFocus::InputColumnName, AddTableFocus::InputColumnName => AddTableFocus::InputColumnType, AddTableFocus::InputColumnType => AddTableFocus::AddColumnButton, AddTableFocus::AddColumnButton => AddTableFocus::ColumnsTable, - AddTableFocus::ColumnsTable | AddTableFocus::InsideColumnsTable => AddTableFocus::IndexesTable, AddTableFocus::IndexesTable | AddTableFocus::InsideIndexesTable => AddTableFocus::LinksTable, AddTableFocus::LinksTable | AddTableFocus::InsideLinksTable => AddTableFocus::SaveButton, - AddTableFocus::SaveButton => AddTableFocus::DeleteSelectedButton, AddTableFocus::DeleteSelectedButton => AddTableFocus::CancelButton, AddTableFocus::CancelButton => AddTableFocus::InputTableName, - }; - } - Some("prev_field") => { - new_focus = match current_focus { - AddTableFocus::InputTableName => AddTableFocus::CancelButton, AddTableFocus::InputColumnName => AddTableFocus::InputTableName, AddTableFocus::InputColumnType => AddTableFocus::InputColumnName, AddTableFocus::AddColumnButton => AddTableFocus::InputColumnType, - AddTableFocus::ColumnsTable | AddTableFocus::InsideColumnsTable => AddTableFocus::AddColumnButton, AddTableFocus::IndexesTable | AddTableFocus::InsideIndexesTable => AddTableFocus::ColumnsTable, AddTableFocus::LinksTable | AddTableFocus::InsideLinksTable => AddTableFocus::IndexesTable, - AddTableFocus::SaveButton => AddTableFocus::LinksTable, AddTableFocus::DeleteSelectedButton => AddTableFocus::SaveButton, AddTableFocus::CancelButton => AddTableFocus::DeleteSelectedButton, - }; - } - Some("select") => { - match current_focus { - AddTableFocus::ColumnsTable => { new_focus = AddTableFocus::InsideColumnsTable; if add_table_state.column_table_state.selected().is_none() && !add_table_state.columns.is_empty() { add_table_state.column_table_state.select(Some(0)); } /* Message removed */ } - AddTableFocus::IndexesTable => { new_focus = AddTableFocus::InsideIndexesTable; if add_table_state.index_table_state.selected().is_none() && !add_table_state.indexes.is_empty() { add_table_state.index_table_state.select(Some(0)); } /* Message removed */ } - AddTableFocus::LinksTable => { new_focus = AddTableFocus::InsideLinksTable; if add_table_state.link_table_state.selected().is_none() && !add_table_state.links.is_empty() { add_table_state.link_table_state.select(Some(0)); } /* Message removed */ } - AddTableFocus::InsideColumnsTable => { if let Some(index) = add_table_state.column_table_state.selected() { if let Some(col) = add_table_state.columns.get_mut(index) { col.selected = !col.selected; add_table_state.has_unsaved_changes = true; /* Message removed */ }} /* else { Message removed } */ } - AddTableFocus::InsideIndexesTable => { if let Some(index) = add_table_state.index_table_state.selected() { if let Some(idx_def) = add_table_state.indexes.get_mut(index) { idx_def.selected = !idx_def.selected; add_table_state.has_unsaved_changes = true; /* Message removed */ }} /* else { Message removed } */ } - AddTableFocus::InsideLinksTable => { if let Some(index) = add_table_state.link_table_state.selected() { if let Some(link) = add_table_state.links.get_mut(index) { link.selected = !link.selected; add_table_state.has_unsaved_changes = true; /* Message removed */ }} /* else { Message removed } */ } - AddTableFocus::AddColumnButton => { if let Some(focus_after_add) = handle_add_column_action(add_table_state, command_message) { new_focus = focus_after_add; } else { /* Message already set by handle_add_column_action */ }} - AddTableFocus::SaveButton => { if add_table_state.table_name.is_empty() { *command_message = "Cannot save: Table name is empty.".to_string(); } else if add_table_state.columns.is_empty() { *command_message = "Cannot save: No columns defined.".to_string(); } else { *command_message = "Saving table...".to_string(); app_state.show_loading_dialog("Saving", "Please wait..."); let mut client_clone = grpc_client.clone(); let state_clone = add_table_state.clone(); let sender_clone = save_result_sender.clone(); tokio::spawn(async move { let result = handle_save_table_action(&mut client_clone, &state_clone).await; let _ = sender_clone.send(result).await; }); }} - AddTableFocus::DeleteSelectedButton => { let columns_to_delete: Vec<(usize, String, String)> = add_table_state.columns.iter().enumerate().filter(|(_, col)| col.selected).map(|(index, col)| (index, col.name.clone(), col.data_type.clone())).collect(); if columns_to_delete.is_empty() { *command_message = "No columns selected for deletion.".to_string(); } else { let column_details: String = columns_to_delete.iter().map(|(index, name, dtype)| format!("{}. {} ({})", index + 1, name, dtype)).collect::>().join("\n"); let message = format!("Delete the following columns?\n\n{}", column_details); app_state.show_dialog("Confirm Deletion", &message, vec!["Confirm".to_string(), "Cancel".to_string()], DialogPurpose::ConfirmDeleteColumns); }} - AddTableFocus::CancelButton => { *command_message = "Action: Cancel Add Table (Not Implemented)".to_string(); } - _ => { handled = false; } - } - } - _ => handled = false, - } - - if handled && current_focus != new_focus { - add_table_state.current_focus = new_focus; - // Minimal change: Command message update logic can be simplified or removed if not desired - // For now, let's keep it minimal and only update if it was truly a focus change, - // and not a boundary message. - if !command_message.starts_with("At ") && current_focus != new_focus { // Avoid overwriting boundary messages - // *command_message = format!("Focus: {:?}", add_table_state.current_focus); // Optional: restore if needed - } - - - let new_is_canvas_input_focus = matches!(new_focus, - AddTableFocus::InputTableName | AddTableFocus::InputColumnName | AddTableFocus::InputColumnType - ); - app_state.ui.focus_outside_canvas = !new_is_canvas_input_focus; - } - // If not handled, command_message remains as it was (e.g., from a deeper function call or previous event) - // or can be cleared if that's the desired default. For minimal change, we leave it. - - handled -}