diff --git a/client/src/components/admin/add_logic.rs b/client/src/components/admin/add_logic.rs index f2f654e..a5c47ed 100644 --- a/client/src/components/admin/add_logic.rs +++ b/client/src/components/admin/add_logic.rs @@ -3,7 +3,7 @@ use crate::config::colors::themes::Theme; use crate::state::app::highlight::HighlightState; use crate::state::app::state::AppState; use crate::state::pages::add_logic::{AddLogicFocus, AddLogicState}; -use canvas::render_canvas_default; +use canvas::{render_canvas_default, render_canvas}; use canvas::canvas::HighlightState as CanvasHighlightState; use ratatui::{ layout::{Alignment, Constraint, Direction, Layout, Rect}, @@ -170,7 +170,7 @@ pub fn render_add_logic( ); let canvas_highlight_state = convert_highlight_state(highlight_state); - let active_field_rect = render_canvas_default( + let active_field_rect = render_canvas( f, canvas_area, add_logic_state, // will later be wrapped in FormEditor diff --git a/client/src/components/admin/add_table.rs b/client/src/components/admin/add_table.rs index 2406eac..2b871a9 100644 --- a/client/src/components/admin/add_table.rs +++ b/client/src/components/admin/add_table.rs @@ -3,7 +3,7 @@ use crate::config::colors::themes::Theme; use crate::state::app::highlight::HighlightState; use crate::state::app::state::AppState; use crate::state::pages::add_table::{AddTableFocus, AddTableState}; -use canvas::render_canvas_default; +use canvas::{render_canvas_default, render_canvas}; use canvas::canvas::HighlightState as CanvasHighlightState; use ratatui::{ layout::{Alignment, Constraint, Direction, Layout, Rect}, @@ -359,7 +359,7 @@ pub fn render_add_table( // --- Canvas Rendering (Column Definition Input) - USING CANVAS LIBRARY --- let canvas_highlight_state = convert_highlight_state(highlight_state); - let _active_field_rect = render_canvas_default( + let _active_field_rect = render_canvas( f, canvas_area, add_table_state, // will later be wrapped in FormEditor diff --git a/client/src/components/auth/login.rs b/client/src/components/auth/login.rs index 6a1c85a..67d02bc 100644 --- a/client/src/components/auth/login.rs +++ b/client/src/components/auth/login.rs @@ -13,7 +13,7 @@ use ratatui::{ Frame, }; use crate::state::app::highlight::HighlightState; -use canvas::{FormEditor, render_canvas_default, render_suggestions_dropdown, DefaultCanvasTheme}; +use canvas::{FormEditor, render_canvas_default, render_canvas, render_suggestions_dropdown, DefaultCanvasTheme}; use canvas::canvas::HighlightState as CanvasHighlightState; // Helper function to convert between HighlightState types @@ -65,7 +65,7 @@ pub fn render_login( // Wrap LoginState in FormEditor let editor = FormEditor::new(login_state.clone()); - let input_rect = render_canvas_default( + let input_rect = render_canvas( f, chunks[0], &editor, diff --git a/client/src/components/auth/register.rs b/client/src/components/auth/register.rs index 9d3594b..1c7f8fe 100644 --- a/client/src/components/auth/register.rs +++ b/client/src/components/auth/register.rs @@ -14,7 +14,7 @@ use ratatui::{ Frame, }; use crate::state::app::highlight::HighlightState; -use canvas::{FormEditor, render_canvas_default, render_suggestions_dropdown, DefaultCanvasTheme}; +use canvas::{FormEditor, render_canvas_default, render_canvas, render_suggestions_dropdown, DefaultCanvasTheme}; use canvas::canvas::HighlightState as CanvasHighlightState; // Helper function to convert between HighlightState types @@ -65,7 +65,7 @@ pub fn render_register( // Wrap RegisterState in FormEditor let editor = FormEditor::new(state.clone()); - let input_rect = render_canvas_default( + let input_rect = render_canvas( f, chunks[0], &editor, diff --git a/client/src/components/form/form.rs b/client/src/components/form/form.rs index 1e4d1b9..8fdf03a 100644 --- a/client/src/components/form/form.rs +++ b/client/src/components/form/form.rs @@ -1,7 +1,5 @@ // src/components/form/form.rs use crate::config::colors::themes::Theme; -use canvas::{FormEditor, render_canvas_default, render_suggestions_dropdown, DefaultCanvasTheme}; -use canvas::canvas::HighlightState; use crate::state::pages::form::FormState; use ratatui::{ layout::{Alignment, Constraint, Direction, Layout, Margin, Rect}, @@ -9,6 +7,8 @@ use ratatui::{ widgets::{Block, Borders, Paragraph}, Frame, }; +use canvas::canvas::HighlightState; +use canvas::{FormEditor, render_canvas_default, render_canvas, render_suggestions_dropdown, DefaultCanvasTheme}; pub fn render_form( f: &mut Frame, @@ -65,7 +65,7 @@ pub fn render_form( // --- FORM RENDERING (Using new canvas API) --- let editor = FormEditor::new(form_state.clone()); - let active_field_rect = render_canvas_default( + let active_field_rect = render_canvas( f, main_layout[1], &editor, diff --git a/client/src/modes/canvas.rs b/client/src/modes/canvas.rs deleted file mode 100644 index aa7c982..0000000 --- a/client/src/modes/canvas.rs +++ /dev/null @@ -1,4 +0,0 @@ -// src/client/modes/canvas.rs -pub mod edit; -pub mod common_mode; -pub mod read_only; diff --git a/client/src/modes/canvas/common_mode.rs b/client/src/modes/canvas/common_mode.rs deleted file mode 100644 index cbdc846..0000000 --- a/client/src/modes/canvas/common_mode.rs +++ /dev/null @@ -1,86 +0,0 @@ -// src/modes/canvas/common_mode.rs - -use crate::tui::terminal::core::TerminalCore; -use crate::state::pages::{form::FormState, auth::LoginState, auth::RegisterState, auth::AuthState}; -use crate::state::app::state::AppState; -use crate::services::grpc_client::GrpcClient; -use crate::services::auth::AuthClient; -use crate::modes::handlers::event::EventOutcome; -use crate::tui::functions::common::form::SaveOutcome; -use anyhow::{Context, Result}; -use crate::tui::functions::common::{ - form::{save as form_save, revert as form_revert}, - login::{save as login_save, revert as login_revert}, - register::{revert as register_revert}, -}; - -pub async fn handle_core_action( - action: &str, - form_state: &mut FormState, - auth_state: &mut AuthState, - login_state: &mut LoginState, - register_state: &mut RegisterState, - grpc_client: &mut GrpcClient, - auth_client: &mut AuthClient, - terminal: &mut TerminalCore, - app_state: &mut AppState, -) -> Result { - match action { - "save" => { - if app_state.ui.show_login { - let message = login_save(auth_state, login_state, auth_client, app_state).await.context("Login save action failed")?; - Ok(EventOutcome::Ok(message)) - } else { - let save_outcome = form_save( - app_state, - form_state, - grpc_client, - ).await.context("Register save action failed")?; - let message = match save_outcome { - SaveOutcome::NoChange => "No changes to save.".to_string(), - SaveOutcome::UpdatedExisting => "Entry updated.".to_string(), - SaveOutcome::CreatedNew(_) => "New entry created.".to_string(), - }; - Ok(EventOutcome::DataSaved(save_outcome, message)) - } - }, - "force_quit" => { - terminal.cleanup()?; - Ok(EventOutcome::Exit("Force exiting without saving.".to_string())) - }, - "save_and_quit" => { - let message = if app_state.ui.show_login { - login_save(auth_state, login_state, auth_client, app_state).await.context("Login save n quit action failed")? - } else { - let save_outcome = form_save( - app_state, - form_state, - grpc_client, - ).await?; - match save_outcome { - SaveOutcome::NoChange => "No changes to save.".to_string(), - SaveOutcome::UpdatedExisting => "Entry updated.".to_string(), - SaveOutcome::CreatedNew(_) => "New entry created.".to_string(), - } - }; - terminal.cleanup()?; - Ok(EventOutcome::Exit(format!("{}. Exiting application.", message))) - }, - "revert" => { - if app_state.ui.show_login { - let message = login_revert(login_state, app_state).await; - Ok(EventOutcome::Ok(message)) - } else if app_state.ui.show_register { - let message = register_revert(register_state, app_state).await; - Ok(EventOutcome::Ok(message)) - } else { - let message = form_revert( - form_state, - grpc_client, - ).await.context("Form revert x action failed")?; - Ok(EventOutcome::Ok(message)) - } - }, - _ => Ok(EventOutcome::Ok(format!("Core action not handled: {}", action))), - } -} diff --git a/client/src/modes/canvas/edit.rs b/client/src/modes/canvas/edit.rs deleted file mode 100644 index dd814d3..0000000 --- a/client/src/modes/canvas/edit.rs +++ /dev/null @@ -1,500 +0,0 @@ -// src/modes/canvas/edit.rs -use crate::config::binds::config::Config; -use crate::modes::handlers::event::EventHandler; -use crate::services::grpc_client::GrpcClient; -use crate::state::app::state::AppState; -use crate::state::pages::admin::AdminState; -use crate::state::pages::{ - auth::{LoginState, RegisterState}, - form::FormState, -}; -use canvas::canvas::CanvasState; -use canvas::{canvas::CanvasAction, dispatcher::ActionDispatcher, canvas::ActionResult}; -use anyhow::Result; -use common::proto::komp_ac::search::search_response::Hit; -use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; -use tokio::sync::mpsc; -use tracing::info; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum EditEventOutcome { - Message(String), - ExitEditMode, -} - -/// Helper function to spawn a non-blocking search task for autocomplete. -async fn trigger_form_autocomplete_search( - form_state: &mut FormState, - grpc_client: &mut GrpcClient, - sender: mpsc::UnboundedSender>, -) { - if let Some(field_def) = form_state.fields.get(form_state.current_field) { - if field_def.is_link { - if let Some(target_table) = &field_def.link_target_table { - // 1. Update state for immediate UI feedback - form_state.autocomplete_loading = true; - form_state.autocomplete_active = true; - form_state.autocomplete_suggestions.clear(); - form_state.selected_suggestion_index = None; - - // 2. Clone everything needed for the background task - let query = form_state.get_current_input().to_string(); - let table_to_search = target_table.clone(); - let mut grpc_client_clone = grpc_client.clone(); - - info!( - "[Autocomplete] Spawning search in '{}' for query: '{}'", - table_to_search, query - ); - - // 3. Spawn the non-blocking task - tokio::spawn(async move { - match grpc_client_clone - .search_table(table_to_search, query) - .await - { - Ok(response) => { - // Send results back through the channel - let _ = sender.send(response.hits); - } - Err(e) => { - tracing::error!( - "[Autocomplete] Search failed: {:?}", - e - ); - // Send an empty vec on error so the UI can stop loading - let _ = sender.send(vec![]); - } - } - }); - } - } - } -} - -pub async fn handle_form_edit_with_canvas( - key_event: KeyEvent, - config: &Config, - form_state: &mut FormState, - ideal_cursor_column: &mut usize, -) -> Result { - // Try canvas action from key first - let canvas_config = canvas::config::CanvasConfig::load(); - if let Some(action_name) = canvas_config.get_edit_action(key_event.code, key_event.modifiers) { - let canvas_action = CanvasAction::from_string(action_name); - match ActionDispatcher::dispatch(canvas_action, form_state, ideal_cursor_column).await { - Ok(ActionResult::Success(msg)) => { - return Ok(msg.unwrap_or_default()); - } - Ok(ActionResult::HandledByFeature(msg)) => { - return Ok(msg); - } - Ok(ActionResult::Error(msg)) => { - return Ok(format!("Error: {}", msg)); - } - Ok(ActionResult::RequiresContext(msg)) => { - return Ok(format!("Context needed: {}", msg)); - } - Err(_) => { - // Fall through to try config mapping - } - } - } - - // Try config-mapped action - if let Some(action_str) = config.get_edit_action_for_key(key_event.code, key_event.modifiers) { - let canvas_action = CanvasAction::from_string(&action_str); - match ActionDispatcher::dispatch(canvas_action, form_state, ideal_cursor_column).await { - Ok(ActionResult::Success(msg)) => { - return Ok(msg.unwrap_or_default()); - } - Ok(ActionResult::HandledByFeature(msg)) => { - return Ok(msg); - } - Ok(ActionResult::Error(msg)) => { - return Ok(format!("Error: {}", msg)); - } - Ok(ActionResult::RequiresContext(msg)) => { - return Ok(format!("Context needed: {}", msg)); - } - Err(e) => { - return Ok(format!("Action failed: {}", e)); - } - } - } - - Ok(String::new()) -} - -/// Helper function to execute a specific action using canvas library -async fn execute_canvas_action( - action: &str, - key: KeyEvent, - form_state: &mut FormState, - ideal_cursor_column: &mut usize, -) -> Result { - let canvas_action = CanvasAction::from_string(action); - match ActionDispatcher::dispatch(canvas_action, form_state, ideal_cursor_column).await { - Ok(ActionResult::Success(msg)) => Ok(msg.unwrap_or_default()), - Ok(ActionResult::HandledByFeature(msg)) => Ok(msg), - Ok(ActionResult::Error(msg)) => Ok(format!("Error: {}", msg)), - Ok(ActionResult::RequiresContext(msg)) => Ok(format!("Context needed: {}", msg)), - Err(e) => Ok(format!("Action failed: {}", e)), - } -} - -/// FIXED: Unified canvas action handler with proper priority order for edit mode -async fn handle_canvas_state_edit( - key: KeyEvent, - config: &Config, - state: &mut S, - ideal_cursor_column: &mut usize, -) -> Result { - // println!("DEBUG: Key pressed: {:?}", key); // DEBUG - - // PRIORITY 1: Character insertion in edit mode comes FIRST - if let KeyCode::Char(c) = key.code { - // Only insert if no modifiers or just shift (for uppercase) - if key.modifiers.is_empty() || key.modifiers == KeyModifiers::SHIFT { - // println!("DEBUG: Using character insertion priority for: {}", c); // DEBUG - let canvas_action = CanvasAction::InsertChar(c); - match ActionDispatcher::dispatch(canvas_action, state, ideal_cursor_column).await { - Ok(ActionResult::Success(msg)) => { - return Ok(msg.unwrap_or_default()); - } - Ok(ActionResult::HandledByFeature(msg)) => { - return Ok(msg); - } - Ok(ActionResult::Error(msg)) => { - return Ok(format!("Error: {}", msg)); - } - Ok(ActionResult::RequiresContext(msg)) => { - return Ok(format!("Context needed: {}", msg)); - } - Err(e) => { - // println!("DEBUG: Character insertion failed: {:?}, trying config", e); - // Fall through to try config mappings - } - } - } - } - - // PRIORITY 2: Check canvas config for special keys/combinations - let canvas_config = canvas::config::CanvasConfig::load(); - if let Some(action_name) = canvas_config.get_edit_action(key.code, key.modifiers) { - // println!("DEBUG: Canvas config mapped to: {}", action_name); // DEBUG - let canvas_action = CanvasAction::from_string(action_name); - - match ActionDispatcher::dispatch(canvas_action, state, ideal_cursor_column).await { - Ok(ActionResult::Success(msg)) => { - return Ok(msg.unwrap_or_default()); - } - Ok(ActionResult::HandledByFeature(msg)) => { - return Ok(msg); - } - Ok(ActionResult::Error(msg)) => { - return Ok(format!("Error: {}", msg)); - } - Ok(ActionResult::RequiresContext(msg)) => { - return Ok(format!("Context needed: {}", msg)); - } - Err(_) => { - // println!("DEBUG: Canvas action failed, trying client config"); // DEBUG - } - } - } else { - // println!("DEBUG: No canvas config mapping found"); // DEBUG - } - - // PRIORITY 3: Check client config ONLY for non-character keys or modified keys - if !matches!(key.code, KeyCode::Char(_)) || !key.modifiers.is_empty() { - if let Some(action_str) = config.get_edit_action_for_key(key.code, key.modifiers) { - // println!("DEBUG: Client config mapped to: {} (for non-char key)", action_str); // DEBUG - let canvas_action = CanvasAction::from_string(&action_str); - match ActionDispatcher::dispatch(canvas_action, state, ideal_cursor_column).await { - Ok(ActionResult::Success(msg)) => { - return Ok(msg.unwrap_or_default()); - } - Ok(ActionResult::HandledByFeature(msg)) => { - return Ok(msg); - } - Ok(ActionResult::Error(msg)) => { - return Ok(format!("Error: {}", msg)); - } - Ok(ActionResult::RequiresContext(msg)) => { - return Ok(format!("Context needed: {}", msg)); - } - Err(e) => { - return Ok(format!("Action failed: {}", e)); - } - } - } else { - // println!("DEBUG: No client config mapping found for non-char key"); // DEBUG - } - } else { - // println!("DEBUG: Skipping client config for character key in edit mode"); // DEBUG - } - - // println!("DEBUG: No action taken for key: {:?}", key); // DEBUG - Ok(String::new()) -} - -#[allow(clippy::too_many_arguments)] -pub async fn handle_edit_event( - key: KeyEvent, - config: &Config, - form_state: &mut FormState, - login_state: &mut LoginState, - register_state: &mut RegisterState, - admin_state: &mut AdminState, - current_position: &mut u64, - total_count: u64, - event_handler: &mut EventHandler, - app_state: &AppState, -) -> Result { - // --- AUTOCOMPLETE-SPECIFIC KEY HANDLING --- - if app_state.ui.show_form && form_state.autocomplete_active { - if let Some(action) = - config.get_edit_action_for_key(key.code, key.modifiers) - { - match action { - "suggestion_down" => { - if !form_state.autocomplete_suggestions.is_empty() { - let current = - form_state.selected_suggestion_index.unwrap_or(0); - let next = (current + 1) - % form_state.autocomplete_suggestions.len(); - form_state.selected_suggestion_index = Some(next); - } - return Ok(EditEventOutcome::Message(String::new())); - } - "suggestion_up" => { - if !form_state.autocomplete_suggestions.is_empty() { - let current = - form_state.selected_suggestion_index.unwrap_or(0); - let prev = if current == 0 { - form_state.autocomplete_suggestions.len() - 1 - } else { - current - 1 - }; - form_state.selected_suggestion_index = Some(prev); - } - return Ok(EditEventOutcome::Message(String::new())); - } - "exit" => { - form_state.deactivate_autocomplete(); - return Ok(EditEventOutcome::Message( - "Autocomplete cancelled".to_string(), - )); - } - "enter_decider" => { - if let Some(selected_idx) = - form_state.selected_suggestion_index - { - if let Some(selection) = form_state - .autocomplete_suggestions - .get(selected_idx) - .cloned() - { - // --- THIS IS THE CORE LOGIC CHANGE --- - - // 1. Get the friendly display name for the UI - let display_name = - form_state.get_display_name_for_hit(&selection); - - // 2. Store the REAL ID in the form's values - let current_input = - form_state.get_current_input_mut(); - *current_input = selection.id.to_string(); - - // 3. Set the persistent display override in the map - form_state.link_display_map.insert( - form_state.current_field, - display_name, - ); - - // 4. Finalize state - form_state.deactivate_autocomplete(); - form_state.set_has_unsaved_changes(true); - return Ok(EditEventOutcome::Message( - "Selection made".to_string(), - )); - } - } - form_state.deactivate_autocomplete(); - // Fall through to default 'enter' behavior - } - _ => {} // Let other keys fall through to the live search logic - } - } - } - - // --- LIVE AUTOCOMPLETE TRIGGER LOGIC --- - let mut trigger_search = false; - - if app_state.ui.show_form { - // Manual trigger - if let Some("trigger_autocomplete") = - config.get_edit_action_for_key(key.code, key.modifiers) - { - if !form_state.autocomplete_active { - trigger_search = true; - } - } - // Live search trigger while typing - else if form_state.autocomplete_active { - if let KeyCode::Char(_) | KeyCode::Backspace = key.code { - let action = if let KeyCode::Backspace = key.code { - "delete_char_backward" - } else { - "insert_char" - }; - // FIXED: Use canvas library instead of form_e::execute_edit_action - execute_canvas_action( - action, - key, - form_state, - &mut event_handler.ideal_cursor_column, - ) - .await?; - trigger_search = true; - } - } - } - - if trigger_search { - trigger_form_autocomplete_search( - form_state, - &mut event_handler.grpc_client, - event_handler.autocomplete_result_sender.clone(), - ) - .await; - return Ok(EditEventOutcome::Message("Searching...".to_string())); - } - - // --- GENERAL EDIT MODE EVENT HANDLING (IF NOT AUTOCOMPLETE) --- - - if let Some(action_str) = - config.get_edit_action_for_key(key.code, key.modifiers) - { - // Handle Enter key (next field) - if action_str == "enter_decider" { - // FIXED: Use canvas library instead of form_e::execute_edit_action - let msg = execute_canvas_action( - "next_field", - key, - form_state, - &mut event_handler.ideal_cursor_column, - ) - .await?; - return Ok(EditEventOutcome::Message(msg)); - } - - // Handle exiting edit mode - if action_str == "exit" { - return Ok(EditEventOutcome::ExitEditMode); - } - - // Handle all other edit actions - NOW USING CANVAS LIBRARY - let msg = if app_state.ui.show_login { - // NEW: Use unified canvas handler instead of auth_e::execute_edit_action - handle_canvas_state_edit( - key, - config, - login_state, - &mut event_handler.ideal_cursor_column, - ) - .await? - } else if app_state.ui.show_add_table { - // NEW: Use unified canvas handler instead of add_table_e::execute_edit_action - handle_canvas_state_edit( - key, - config, - &mut admin_state.add_table_state, - &mut event_handler.ideal_cursor_column, - ) - .await? - } else if app_state.ui.show_add_logic { - // NEW: Use unified canvas handler instead of add_logic_e::execute_edit_action - handle_canvas_state_edit( - key, - config, - &mut admin_state.add_logic_state, - &mut event_handler.ideal_cursor_column, - ) - .await? - } else if app_state.ui.show_register { - // NEW: Use unified canvas handler instead of auth_e::execute_edit_action - handle_canvas_state_edit( - key, - config, - register_state, - &mut event_handler.ideal_cursor_column, - ) - .await? - } else { - // FIXED: Use canvas library instead of form_e::execute_edit_action - execute_canvas_action( - action_str, - key, - form_state, - &mut event_handler.ideal_cursor_column, - ) - .await? - }; - return Ok(EditEventOutcome::Message(msg)); - } - - // --- FALLBACK FOR CHARACTER INSERTION (IF NO OTHER BINDING MATCHED) --- - if let KeyCode::Char(_) = key.code { - let msg = if app_state.ui.show_login { - // NEW: Use unified canvas handler instead of auth_e::execute_edit_action - handle_canvas_state_edit( - key, - config, - login_state, - &mut event_handler.ideal_cursor_column, - ) - .await? - } else if app_state.ui.show_add_table { - // NEW: Use unified canvas handler instead of add_table_e::execute_edit_action - handle_canvas_state_edit( - key, - config, - &mut admin_state.add_table_state, - &mut event_handler.ideal_cursor_column, - ) - .await? - } else if app_state.ui.show_add_logic { - // NEW: Use unified canvas handler instead of add_logic_e::execute_edit_action - handle_canvas_state_edit( - key, - config, - &mut admin_state.add_logic_state, - &mut event_handler.ideal_cursor_column, - ) - .await? - } else if app_state.ui.show_register { - // NEW: Use unified canvas handler instead of auth_e::execute_edit_action - handle_canvas_state_edit( - key, - config, - register_state, - &mut event_handler.ideal_cursor_column, - ) - .await? - } else { - // FIXED: Use canvas library instead of form_e::execute_edit_action - execute_canvas_action( - "insert_char", - key, - form_state, - &mut event_handler.ideal_cursor_column, - ) - .await? - }; - return Ok(EditEventOutcome::Message(msg)); - } - - Ok(EditEventOutcome::Message(String::new())) // No action taken -} diff --git a/client/src/modes/canvas/read_only.rs b/client/src/modes/canvas/read_only.rs deleted file mode 100644 index 93f5a6b..0000000 --- a/client/src/modes/canvas/read_only.rs +++ /dev/null @@ -1,313 +0,0 @@ -// src/modes/canvas/read_only.rs - -use crate::config::binds::config::Config; -use crate::config::binds::key_sequences::KeySequenceTracker; -use crate::services::grpc_client::GrpcClient; -use crate::state::pages::auth::LoginState; -use crate::state::pages::auth::RegisterState; -use crate::state::pages::form::FormState; -use crate::state::pages::add_logic::AddLogicState; -use crate::state::pages::add_table::AddTableState; -use crate::state::app::state::AppState; -use canvas::{canvas::{CanvasAction, CanvasState, ActionResult}, dispatcher::ActionDispatcher}; -use crossterm::event::KeyEvent; -use anyhow::Result; - -/// Helper function to dispatch canvas action for any CanvasState -async fn dispatch_canvas_action( - action: &str, - state: &mut S, - ideal_cursor_column: &mut usize, -) -> String { - let canvas_action = CanvasAction::from_string(action); - match ActionDispatcher::dispatch(canvas_action, state, ideal_cursor_column).await { - Ok(ActionResult::Success(msg)) => msg.unwrap_or_default(), - Ok(ActionResult::HandledByFeature(msg)) => msg, - Ok(ActionResult::Error(msg)) => format!("Error: {}", msg), - Ok(ActionResult::RequiresContext(msg)) => format!("Context needed: {}", msg), - Err(e) => format!("Action failed: {}", e), - } -} - -/// Helper function to dispatch canvas action to the appropriate state based on UI -async fn dispatch_to_active_state( - action: &str, - app_state: &AppState, - form_state: &mut FormState, - login_state: &mut LoginState, - register_state: &mut RegisterState, - add_table_state: &mut AddTableState, - add_logic_state: &mut AddLogicState, - ideal_cursor_column: &mut usize, -) -> String { - if app_state.ui.show_add_table { - dispatch_canvas_action(action, add_table_state, ideal_cursor_column).await - } else if app_state.ui.show_add_logic { - dispatch_canvas_action(action, add_logic_state, ideal_cursor_column).await - } else if app_state.ui.show_register { - dispatch_canvas_action(action, register_state, ideal_cursor_column).await - } else if app_state.ui.show_login { - dispatch_canvas_action(action, login_state, ideal_cursor_column).await - } else { - dispatch_canvas_action(action, form_state, ideal_cursor_column).await - } -} - -/// Helper function to handle context-specific actions that need special treatment -async fn handle_context_action( - action: &str, - app_state: &AppState, - form_state: &mut FormState, - grpc_client: &mut GrpcClient, - ideal_cursor_column: &mut usize, -) -> Result> { - const CONTEXT_ACTIONS_FORM: &[&str] = &[ - "previous_entry", - "next_entry", - ]; - const CONTEXT_ACTIONS_LOGIN: &[&str] = &[ - "previous_entry", - "next_entry", - ]; - - if app_state.ui.show_form && CONTEXT_ACTIONS_FORM.contains(&action) { - Ok(Some(crate::tui::functions::form::handle_action( - action, - form_state, - grpc_client, - ideal_cursor_column, - ).await?)) - } else if app_state.ui.show_login && CONTEXT_ACTIONS_LOGIN.contains(&action) { - Ok(Some(crate::tui::functions::login::handle_action(action).await?)) - } else { - Ok(None) // Not a context action, use regular canvas dispatch - } -} - -pub async fn handle_form_readonly_with_canvas( - key_event: KeyEvent, - config: &Config, - form_state: &mut FormState, - ideal_cursor_column: &mut usize, -) -> Result { - // Try canvas action from key first - let canvas_config = canvas::config::CanvasConfig::load(); - if let Some(action_name) = canvas_config.get_read_only_action(key_event.code, key_event.modifiers) { - let canvas_action = CanvasAction::from_string(action_name); - match ActionDispatcher::dispatch(canvas_action, form_state, ideal_cursor_column).await { - Ok(ActionResult::Success(msg)) => { - return Ok(msg.unwrap_or_default()); - } - Ok(ActionResult::HandledByFeature(msg)) => { - return Ok(msg); - } - Ok(ActionResult::Error(msg)) => { - return Ok(format!("Error: {}", msg)); - } - Ok(ActionResult::RequiresContext(msg)) => { - return Ok(format!("Context needed: {}", msg)); - } - Err(_) => { - // Fall through to try config mapping - } - } - } - - // Try config-mapped action - if let Some(action_str) = config.get_read_only_action_for_key(key_event.code, key_event.modifiers) { - let canvas_action = CanvasAction::from_string(&action_str); - match ActionDispatcher::dispatch(canvas_action, form_state, ideal_cursor_column).await { - Ok(ActionResult::Success(msg)) => { - return Ok(msg.unwrap_or_default()); - } - Ok(ActionResult::HandledByFeature(msg)) => { - return Ok(msg); - } - Ok(ActionResult::Error(msg)) => { - return Ok(format!("Error: {}", msg)); - } - Ok(ActionResult::RequiresContext(msg)) => { - return Ok(format!("Context needed: {}", msg)); - } - Err(e) => { - return Ok(format!("Action failed: {}", e)); - } - } - } - - Ok(String::new()) -} - -pub async fn handle_read_only_event( - app_state: &mut AppState, - key: KeyEvent, - config: &Config, - form_state: &mut FormState, - login_state: &mut LoginState, - register_state: &mut RegisterState, - add_table_state: &mut AddTableState, - add_logic_state: &mut AddLogicState, - key_sequence_tracker: &mut KeySequenceTracker, - grpc_client: &mut GrpcClient, - command_message: &mut String, - edit_mode_cooldown: &mut bool, - ideal_cursor_column: &mut usize, -) -> Result<(bool, String)> { - if config.is_enter_edit_mode_before(key.code, key.modifiers) { - *edit_mode_cooldown = true; - *command_message = "Entering Edit mode".to_string(); - return Ok((false, command_message.clone())); - } - - if config.is_enter_edit_mode_after(key.code, key.modifiers) { - // Determine target state to adjust cursor - all states now use CanvasState trait - if app_state.ui.show_login { - let current_input = login_state.get_current_input(); - let current_pos = login_state.current_cursor_pos(); - if !current_input.is_empty() && current_pos < current_input.len() { - login_state.set_current_cursor_pos(current_pos + 1); - *ideal_cursor_column = login_state.current_cursor_pos(); - } - } else if app_state.ui.show_add_logic { - let current_input = add_logic_state.get_current_input(); - let current_pos = add_logic_state.current_cursor_pos(); - if !current_input.is_empty() && current_pos < current_input.len() { - add_logic_state.set_current_cursor_pos(current_pos + 1); - *ideal_cursor_column = add_logic_state.current_cursor_pos(); - } - } else if app_state.ui.show_register { - let current_input = register_state.get_current_input(); - let current_pos = register_state.current_cursor_pos(); - if !current_input.is_empty() && current_pos < current_input.len() { - register_state.set_current_cursor_pos(current_pos + 1); - *ideal_cursor_column = register_state.current_cursor_pos(); - } - } else if app_state.ui.show_add_table { - let current_input = add_table_state.get_current_input(); - let current_pos = add_table_state.current_cursor_pos(); - if !current_input.is_empty() && current_pos < current_input.len() { - add_table_state.set_current_cursor_pos(current_pos + 1); - *ideal_cursor_column = add_table_state.current_cursor_pos(); - } - } else { - // Handle FormState - let current_input = form_state.get_current_input(); - let current_pos = form_state.current_cursor_pos(); - if !current_input.is_empty() && current_pos < current_input.len() { - form_state.set_current_cursor_pos(current_pos + 1); - *ideal_cursor_column = form_state.current_cursor_pos(); - } - } - - *edit_mode_cooldown = true; - *command_message = "Entering Edit mode (after cursor)".to_string(); - return Ok((false, command_message.clone())); - } - - if key.modifiers.is_empty() { - key_sequence_tracker.add_key(key.code); - let sequence = key_sequence_tracker.get_sequence(); - - if let Some(action) = config.matches_key_sequence_generalized(&sequence).as_deref() { - // Try context-specific actions first, otherwise use canvas dispatch - let result = if let Some(context_result) = handle_context_action( - action, - app_state, - form_state, - grpc_client, - ideal_cursor_column, - ).await? { - context_result - } else { - dispatch_to_active_state( - action, - app_state, - form_state, - login_state, - register_state, - add_table_state, - add_logic_state, - ideal_cursor_column, - ).await - }; - key_sequence_tracker.reset(); - return Ok((false, result)); - } - - if config.is_key_sequence_prefix(&sequence) { - return Ok((false, command_message.clone())); - } - - if sequence.len() == 1 && !config.is_key_sequence_prefix(&sequence) { - if let Some(action) = config.get_read_only_action_for_key(key.code, key.modifiers).as_deref() { - // Try context-specific actions first, otherwise use canvas dispatch - let result = if let Some(context_result) = handle_context_action( - action, - app_state, - form_state, - grpc_client, - ideal_cursor_column, - ).await? { - context_result - } else { - dispatch_to_active_state( - action, - app_state, - form_state, - login_state, - register_state, - add_table_state, - add_logic_state, - ideal_cursor_column, - ).await - }; - key_sequence_tracker.reset(); - return Ok((false, result)); - } - } - key_sequence_tracker.reset(); - } else { - key_sequence_tracker.reset(); - - if let Some(action) = config.get_read_only_action_for_key(key.code, key.modifiers).as_deref() { - // Try context-specific actions first, otherwise use canvas dispatch - let result = if let Some(context_result) = handle_context_action( - action, - app_state, - form_state, - grpc_client, - ideal_cursor_column, - ).await? { - context_result - } else { - dispatch_to_active_state( - action, - app_state, - form_state, - login_state, - register_state, - add_table_state, - add_logic_state, - ideal_cursor_column, - ).await - }; - return Ok((false, result)); - } - } - - if !*edit_mode_cooldown { - let default_key = "i".to_string(); - let edit_key = config - .keybindings - .read_only - .get("enter_edit_mode_before") - .and_then(|keys| keys.first()) - .map(|k| k.to_string()) - .unwrap_or(default_key); - *command_message = format!("Read-only mode - press {} to edit", edit_key); - } - - *edit_mode_cooldown = false; - - Ok((false, command_message.clone())) -} diff --git a/client/src/modes/highlight.rs b/client/src/modes/highlight.rs deleted file mode 100644 index 51de17a..0000000 --- a/client/src/modes/highlight.rs +++ /dev/null @@ -1,2 +0,0 @@ -// src/client/modes/highlight.rs -pub mod highlight; diff --git a/client/src/modes/highlight/highlight.rs b/client/src/modes/highlight/highlight.rs deleted file mode 100644 index 4e07c82..0000000 --- a/client/src/modes/highlight/highlight.rs +++ /dev/null @@ -1,65 +0,0 @@ -// src/modes/highlight/highlight.rs -// (This file is intentionally simple for now, reusing ReadOnly logic) - -use crate::config::binds::config::Config; -use crate::config::binds::key_sequences::KeySequenceTracker; -use crate::services::grpc_client::GrpcClient; -use crate::state::app::state::AppState; -use crate::state::pages::auth::{LoginState, RegisterState}; -use crate::state::pages::admin::AdminState; -use crate::state::pages::form::FormState; -use crate::modes::handlers::event::EventOutcome; -use crate::modes::read_only; -use crossterm::event::KeyEvent; -use anyhow::Result; - -/// Handles events when in Highlight mode. -/// Currently, it mostly delegates to the read_only handler for movement. -/// Exiting highlight mode is handled directly in the main event handler. -pub async fn handle_highlight_event( - app_state: &mut AppState, - key: KeyEvent, - config: &Config, - form_state: &mut FormState, - login_state: &mut LoginState, - register_state: &mut RegisterState, - admin_state: &mut AdminState, - key_sequence_tracker: &mut KeySequenceTracker, - current_position: &mut u64, - total_count: u64, - grpc_client: &mut GrpcClient, - command_message: &mut String, - edit_mode_cooldown: &mut bool, - ideal_cursor_column: &mut usize, -) -> Result { - // Delegate movement and other actions to the read_only handler - // The rendering logic will use the highlight_anchor to draw the selection - let (should_exit, message) = read_only::handle_read_only_event( - app_state, - key, - config, - form_state, - login_state, - register_state, - &mut admin_state.add_table_state, - &mut admin_state.add_logic_state, - key_sequence_tracker, - grpc_client, - command_message, // Pass the message buffer - edit_mode_cooldown, - ideal_cursor_column, - ) - .await?; - - // ReadOnly handler doesn't return EventOutcome directly, adapt if needed - // For now, assume Ok outcome unless ReadOnly signals an exit (which we ignore here) - if should_exit { - // This exit is likely for the whole app, let the main loop handle it - // We just return the message from read_only - Ok(EventOutcome::Ok(message)) - } else { - Ok(EventOutcome::Ok(message)) - } -} - - diff --git a/client/src/modes/mod.rs b/client/src/modes/mod.rs index 87658dc..ecb6a25 100644 --- a/client/src/modes/mod.rs +++ b/client/src/modes/mod.rs @@ -1,11 +1,8 @@ // src/client/modes/mod.rs pub mod handlers; -pub mod canvas; pub mod general; pub mod common; -pub mod highlight; pub use handlers::*; -pub use canvas::*; pub use general::*; pub use common::*;