// src/pages/admin_panel/add_table/event.rs use anyhow::Result; use crate::config::binds::config::Config; 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::grpc_client::GrpcClient; use crate::state::app::state::AppState; use crate::modes::handlers::event::EventOutcome; use canvas::{AppMode as CanvasMode, DataProvider}; use crossterm::event::KeyEvent; /// Focus traversal order for AddTable (outside canvas) const ADD_TABLE_FOCUS_ORDER: [AddTableFocus; 10] = [ AddTableFocus::InputTableName, AddTableFocus::InputColumnName, AddTableFocus::InputColumnType, AddTableFocus::AddColumnButton, AddTableFocus::ColumnsTable, AddTableFocus::IndexesTable, AddTableFocus::LinksTable, AddTableFocus::SaveButton, AddTableFocus::DeleteSelectedButton, AddTableFocus::CancelButton, ]; /// Handles all AddTable 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_add_table_event( key_event: KeyEvent, movement: Option, config: &Config, app_state: &mut AppState, page: &mut AddTableFormState, mut grpc_client: GrpcClient, save_result_sender: SaveTableResultSender, ) -> Result { // 1) Inside canvas (FormEditor) let inside_canvas_inputs = matches!( page.current_focus(), AddTableFocus::InputTableName | AddTableFocus::InputColumnName | AddTableFocus::InputColumnType ); if inside_canvas_inputs { // Disable global shortcuts while typing app_state.ui.focus_outside_canvas = false; // Only allow leaving the canvas with Down/Next when in ReadOnly mode let in_edit_mode = page.editor.mode() == CanvasMode::Edit; if !in_edit_mode { if let Some(ma) = movement { let last_idx = page.editor.data_provider().field_count().saturating_sub(1); let at_last = page.editor.current_field() >= last_idx; if at_last && matches!(ma, MovementAction::Down | MovementAction::Next) { page.state.last_canvas_field = last_idx; page.set_current_focus(AddTableFocus::AddColumnButton); app_state.ui.focus_outside_canvas = true; return Ok(EventOutcome::Ok("Moved to Add button".to_string())); } } } // Let the FormEditor handle typing 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) Outside canvas if let Some(ma) = movement { // Block outer moves when "inside" any table and handle locally match page.current_focus() { AddTableFocus::InsideColumnsTable => { match ma { MovementAction::Up => { if let Some(i) = page.state.column_table_state.selected() { let next = i.saturating_sub(1); page.state.column_table_state.select(Some(next)); } else if !page.state.columns.is_empty() { page.state.column_table_state.select(Some(0)); } app_state.ui.focus_outside_canvas = true; return Ok(EventOutcome::Ok(String::new())); } MovementAction::Down => { if let Some(i) = page.state.column_table_state.selected() { let last = page.state.columns.len().saturating_sub(1); let next = if i < last { i + 1 } else { i }; page.state.column_table_state.select(Some(next)); } else if !page.state.columns.is_empty() { page.state.column_table_state.select(Some(0)); } app_state.ui.focus_outside_canvas = true; return Ok(EventOutcome::Ok(String::new())); } MovementAction::Select => { if let Some(i) = page.state.column_table_state.selected() { if let Some(col) = page.state.columns.get_mut(i) { col.selected = !col.selected; page.state.has_unsaved_changes = true; } } app_state.ui.focus_outside_canvas = true; return Ok(EventOutcome::Ok(String::new())); } MovementAction::Esc => { page.state.column_table_state.select(None); page.set_current_focus(AddTableFocus::ColumnsTable); app_state.ui.focus_outside_canvas = true; return Ok(EventOutcome::Ok(String::new())); } MovementAction::Next | MovementAction::Previous => { // Block outer movement while inside return Ok(EventOutcome::Ok(String::new())); } _ => {} } } AddTableFocus::InsideIndexesTable => { match ma { MovementAction::Up => { if let Some(i) = page.state.index_table_state.selected() { let next = i.saturating_sub(1); page.state.index_table_state.select(Some(next)); } else if !page.state.indexes.is_empty() { page.state.index_table_state.select(Some(0)); } app_state.ui.focus_outside_canvas = true; return Ok(EventOutcome::Ok(String::new())); } MovementAction::Down => { if let Some(i) = page.state.index_table_state.selected() { let last = page.state.indexes.len().saturating_sub(1); let next = if i < last { i + 1 } else { i }; page.state.index_table_state.select(Some(next)); } else if !page.state.indexes.is_empty() { page.state.index_table_state.select(Some(0)); } app_state.ui.focus_outside_canvas = true; return Ok(EventOutcome::Ok(String::new())); } MovementAction::Select => { if let Some(i) = page.state.index_table_state.selected() { if let Some(ix) = page.state.indexes.get_mut(i) { ix.selected = !ix.selected; page.state.has_unsaved_changes = true; } } app_state.ui.focus_outside_canvas = true; return Ok(EventOutcome::Ok(String::new())); } MovementAction::Esc => { page.state.index_table_state.select(None); page.set_current_focus(AddTableFocus::IndexesTable); app_state.ui.focus_outside_canvas = true; return Ok(EventOutcome::Ok(String::new())); } MovementAction::Next | MovementAction::Previous => { return Ok(EventOutcome::Ok(String::new())); } _ => {} } } AddTableFocus::InsideLinksTable => { match ma { MovementAction::Up => { if let Some(i) = page.state.link_table_state.selected() { let next = i.saturating_sub(1); page.state.link_table_state.select(Some(next)); } else if !page.state.links.is_empty() { page.state.link_table_state.select(Some(0)); } app_state.ui.focus_outside_canvas = true; return Ok(EventOutcome::Ok(String::new())); } MovementAction::Down => { if let Some(i) = page.state.link_table_state.selected() { let last = page.state.links.len().saturating_sub(1); let next = if i < last { i + 1 } else { i }; page.state.link_table_state.select(Some(next)); } else if !page.state.links.is_empty() { page.state.link_table_state.select(Some(0)); } app_state.ui.focus_outside_canvas = true; return Ok(EventOutcome::Ok(String::new())); } MovementAction::Select => { if let Some(i) = page.state.link_table_state.selected() { if let Some(link) = page.state.links.get_mut(i) { link.selected = !link.selected; page.state.has_unsaved_changes = true; } } app_state.ui.focus_outside_canvas = true; return Ok(EventOutcome::Ok(String::new())); } MovementAction::Esc => { page.state.link_table_state.select(None); page.set_current_focus(AddTableFocus::LinksTable); app_state.ui.focus_outside_canvas = true; return Ok(EventOutcome::Ok(String::new())); } MovementAction::Next | MovementAction::Previous => { return Ok(EventOutcome::Ok(String::new())); } _ => {} } } _ => {} } 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) = 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 = 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 = handle_delete_selected_columns(&mut page.state); return Ok(EventOutcome::Ok(msg)); } AddTableFocus::CancelButton => { return Ok(EventOutcome::Ok("Cancelled Add Table".to_string())); } _ => {} }, _ => {} } } Ok(EventOutcome::Ok(String::new())) }