From e4982f871f67ed05a67834ca6596f0e219a1a01e Mon Sep 17 00:00:00 2001 From: Priec Date: Wed, 30 Jul 2025 18:02:59 +0200 Subject: [PATCH] add_logic is now using canvas library --- client/src/components/admin/add_logic.rs | 45 ++-- .../src/functions/modes/edit/add_logic_e.rs | 2 +- .../functions/modes/read_only/add_logic_ro.rs | 2 +- client/src/modes/canvas/edit.rs | 12 +- client/src/state/pages/add_logic.rs | 226 +++++++++++++----- 5 files changed, 194 insertions(+), 93 deletions(-) diff --git a/client/src/components/admin/add_logic.rs b/client/src/components/admin/add_logic.rs index 12ba015..b3619d7 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 crate::state::pages::canvas_state::CanvasState; +use canvas::canvas::{render_canvas, CanvasState, HighlightState as CanvasHighlightState}; // Use canvas library use ratatui::{ layout::{Alignment, Constraint, Direction, Layout, Rect}, style::{Modifier, Style}, @@ -11,10 +11,18 @@ use ratatui::{ widgets::{Block, BorderType, Borders, Paragraph}, Frame, }; -use crate::components::handlers::canvas::render_canvas; use crate::components::common::{dialog, autocomplete}; // Added autocomplete use crate::config::binds::config::EditorKeybindingMode; +// Helper function to convert between HighlightState types +fn convert_highlight_state(local: &HighlightState) -> CanvasHighlightState { + match local { + HighlightState::Off => CanvasHighlightState::Off, + HighlightState::Characterwise { anchor } => CanvasHighlightState::Characterwise { anchor: *anchor }, + HighlightState::Linewise { anchor_line } => CanvasHighlightState::Linewise { anchor_line: *anchor_line }, + } +} + pub fn render_add_logic( f: &mut Frame, area: Rect, @@ -77,18 +85,18 @@ pub fn render_add_logic( let editor_borrow = add_logic_state.script_content_editor.borrow(); editor_borrow.cursor() // Returns (row, col) as (usize, usize) }; - + let (cursor_line, cursor_col) = current_cursor; - + // Account for TextArea's block borders (1 for each side) let block_offset_x = 1; let block_offset_y = 1; - + // Position autocomplete at current cursor position // Add 1 to column to position dropdown right after the cursor let autocomplete_x = cursor_col + 1; let autocomplete_y = cursor_line; - + let input_rect = Rect { x: (inner_area.x + block_offset_x + autocomplete_x as u16).min(inner_area.right().saturating_sub(20)), y: (inner_area.y + block_offset_y + autocomplete_y as u16).min(inner_area.bottom().saturating_sub(5)), @@ -152,40 +160,37 @@ pub fn render_add_logic( ); f.render_widget(profile_text, top_info_area); - // Canvas + // Canvas - USING CANVAS LIBRARY let focus_on_canvas_inputs = matches!( add_logic_state.current_focus, AddLogicFocus::InputLogicName | AddLogicFocus::InputTargetColumn | AddLogicFocus::InputDescription ); - // Call render_canvas and get the active_field_rect + + let canvas_highlight_state = convert_highlight_state(highlight_state); let active_field_rect = render_canvas( f, canvas_area, - add_logic_state, // Pass the whole state as it impl CanvasState - &add_logic_state.fields(), - &add_logic_state.current_field(), - &add_logic_state.inputs(), - theme, - is_edit_mode && focus_on_canvas_inputs, // is_edit_mode for canvas fields - highlight_state, + add_logic_state, // AddLogicState implements CanvasState + theme, // Theme implements CanvasTheme + is_edit_mode && focus_on_canvas_inputs, + &canvas_highlight_state, ); // --- Render Autocomplete for Target Column --- // `is_edit_mode` here refers to the general edit mode of the EventHandler if is_edit_mode && add_logic_state.current_field() == 1 { // Target Column field - if let Some(suggestions) = add_logic_state.get_suggestions() { // Uses CanvasState impl - let selected = add_logic_state.get_selected_suggestion_index(); - if !suggestions.is_empty() { // Only render if there are suggestions to show + if add_logic_state.in_target_column_suggestion_mode && add_logic_state.show_target_column_suggestions { + if !add_logic_state.target_column_suggestions.is_empty() { if let Some(input_rect) = active_field_rect { autocomplete::render_autocomplete_dropdown( f, input_rect, f.area(), // Full frame area for clamping theme, - suggestions, - selected, + &add_logic_state.target_column_suggestions, + add_logic_state.selected_target_column_suggestion_index, ); } } diff --git a/client/src/functions/modes/edit/add_logic_e.rs b/client/src/functions/modes/edit/add_logic_e.rs index 90ffd9d..98c006b 100644 --- a/client/src/functions/modes/edit/add_logic_e.rs +++ b/client/src/functions/modes/edit/add_logic_e.rs @@ -1,6 +1,6 @@ // src/functions/modes/edit/add_logic_e.rs use crate::state::pages::add_logic::AddLogicState; -use crate::state::pages::canvas_state::CanvasState; +use canvas::canvas::CanvasState; use anyhow::Result; use crossterm::event::{KeyCode, KeyEvent}; diff --git a/client/src/functions/modes/read_only/add_logic_ro.rs b/client/src/functions/modes/read_only/add_logic_ro.rs index 842ea19..2617df4 100644 --- a/client/src/functions/modes/read_only/add_logic_ro.rs +++ b/client/src/functions/modes/read_only/add_logic_ro.rs @@ -1,8 +1,8 @@ // src/functions/modes/read_only/add_logic_ro.rs use crate::config::binds::key_sequences::KeySequenceTracker; use crate::state::pages::add_logic::AddLogicState; // Changed -use crate::state::pages::canvas_state::CanvasState; use crate::state::app::state::AppState; +use canvas::canvas::CanvasState; use anyhow::Result; // Word navigation helpers (get_char_type, find_next_word_start, etc.) diff --git a/client/src/modes/canvas/edit.rs b/client/src/modes/canvas/edit.rs index 4358e03..ab14c60 100644 --- a/client/src/modes/canvas/edit.rs +++ b/client/src/modes/canvas/edit.rs @@ -357,10 +357,10 @@ pub async fn handle_edit_event( ) .await? } else if app_state.ui.show_add_logic { - // FIX: Pass &mut event_handler.ideal_cursor_column - add_logic_e::execute_edit_action( - action_str, + // 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, ) @@ -408,10 +408,10 @@ pub async fn handle_edit_event( ) .await? } else if app_state.ui.show_add_logic { - // FIX: Pass &mut event_handler.ideal_cursor_column - add_logic_e::execute_edit_action( - "insert_char", + // 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, ) diff --git a/client/src/state/pages/add_logic.rs b/client/src/state/pages/add_logic.rs index 980f7a0..ebff0fb 100644 --- a/client/src/state/pages/add_logic.rs +++ b/client/src/state/pages/add_logic.rs @@ -1,6 +1,6 @@ // src/state/pages/add_logic.rs use crate::config::binds::config::{EditorConfig, EditorKeybindingMode}; -use crate::state::pages::canvas_state::CanvasState; +use canvas::canvas::{CanvasState, ActionContext, CanvasAction}; // External library use crate::components::common::text_editor::{TextEditor, VimState}; use std::cell::RefCell; use std::rc::Rc; @@ -50,7 +50,7 @@ pub struct AddLogicState { pub script_editor_trigger_position: Option<(usize, usize)>, // (line, column) pub all_table_names: Vec, pub script_editor_filter_text: String, - + // New fields for same-profile table names and column autocomplete pub same_profile_table_names: Vec, // Tables from same profile only pub script_editor_awaiting_column_autocomplete: Option, // Table name waiting for column fetch @@ -88,7 +88,7 @@ impl AddLogicState { script_editor_trigger_position: None, all_table_names: Vec::new(), script_editor_filter_text: String::new(), - + same_profile_table_names: Vec::new(), script_editor_awaiting_column_autocomplete: None, } @@ -181,7 +181,7 @@ impl AddLogicState { } self.same_profile_table_names.contains(&suggestion.to_string()) } - + /// Sets table columns for autocomplete suggestions pub fn set_table_columns(&mut self, columns: Vec) { self.table_columns_for_suggestions = columns.clone(); @@ -225,6 +225,46 @@ impl AddLogicState { self.script_editor_trigger_position = None; self.script_editor_filter_text.clear(); } + + /// Helper method to validate and save logic + pub fn save_logic(&mut self) -> Option { + if self.logic_name_input.trim().is_empty() { + return Some("Logic name is required".to_string()); + } + + if self.target_column_input.trim().is_empty() { + return Some("Target column is required".to_string()); + } + + let script_content = { + let editor_borrow = self.script_content_editor.borrow(); + editor_borrow.lines().join("\n") + }; + + if script_content.trim().is_empty() { + return Some("Script content is required".to_string()); + } + + // Here you would typically save to database/storage + // For now, just clear the form and mark as saved + self.has_unsaved_changes = false; + Some(format!("Logic '{}' saved successfully", self.logic_name_input.trim())) + } + + /// Helper method to clear the form + pub fn clear_form(&mut self) -> Option { + let profile = self.profile_name.clone(); + let table_id = self.selected_table_id; + let table_name = self.selected_table_name.clone(); + let editor_config = EditorConfig::default(); // You might want to preserve the actual config + + *self = Self::new(&editor_config); + self.profile_name = profile; + self.selected_table_id = table_id; + self.selected_table_name = table_name; + + Some("Form cleared".to_string()) + } } impl Default for AddLogicState { @@ -233,59 +273,18 @@ impl Default for AddLogicState { } } +// Implement external library's CanvasState for AddLogicState impl CanvasState for AddLogicState { fn current_field(&self) -> usize { match self.current_focus { AddLogicFocus::InputLogicName => 0, AddLogicFocus::InputTargetColumn => 1, AddLogicFocus::InputDescription => 2, + // If focus is elsewhere, return the last canvas field used _ => self.last_canvas_field, } } - fn current_cursor_pos(&self) -> usize { - match self.current_focus { - AddLogicFocus::InputLogicName => self.logic_name_cursor_pos, - AddLogicFocus::InputTargetColumn => self.target_column_cursor_pos, - AddLogicFocus::InputDescription => self.description_cursor_pos, - _ => 0, - } - } - - fn has_unsaved_changes(&self) -> bool { - self.has_unsaved_changes - } - - fn inputs(&self) -> Vec<&String> { - vec![ - &self.logic_name_input, - &self.target_column_input, - &self.description_input, - ] - } - - fn get_current_input(&self) -> &str { - match self.current_focus { - AddLogicFocus::InputLogicName => &self.logic_name_input, - AddLogicFocus::InputTargetColumn => &self.target_column_input, - AddLogicFocus::InputDescription => &self.description_input, - _ => "", - } - } - - fn get_current_input_mut(&mut self) -> &mut String { - match self.current_focus { - AddLogicFocus::InputLogicName => &mut self.logic_name_input, - AddLogicFocus::InputTargetColumn => &mut self.target_column_input, - AddLogicFocus::InputDescription => &mut self.description_input, - _ => &mut self.logic_name_input, - } - } - - fn fields(&self) -> Vec<&str> { - vec!["Logic Name", "Target Column", "Description"] - } - fn set_current_field(&mut self, index: usize) { let new_focus = match index { 0 => AddLogicFocus::InputLogicName, @@ -303,6 +302,15 @@ impl CanvasState for AddLogicState { } } + fn current_cursor_pos(&self) -> usize { + match self.current_focus { + AddLogicFocus::InputLogicName => self.logic_name_cursor_pos, + AddLogicFocus::InputTargetColumn => self.target_column_cursor_pos, + AddLogicFocus::InputDescription => self.description_cursor_pos, + _ => 0, + } + } + fn set_current_cursor_pos(&mut self, pos: usize) { match self.current_focus { AddLogicFocus::InputLogicName => { @@ -318,29 +326,117 @@ impl CanvasState for AddLogicState { } } + fn get_current_input(&self) -> &str { + match self.current_focus { + AddLogicFocus::InputLogicName => &self.logic_name_input, + AddLogicFocus::InputTargetColumn => &self.target_column_input, + AddLogicFocus::InputDescription => &self.description_input, + _ => "", // Should not happen if called correctly + } + } + + fn get_current_input_mut(&mut self) -> &mut String { + match self.current_focus { + AddLogicFocus::InputLogicName => &mut self.logic_name_input, + AddLogicFocus::InputTargetColumn => &mut self.target_column_input, + AddLogicFocus::InputDescription => &mut self.description_input, + _ => &mut self.logic_name_input, // Fallback + } + } + + fn inputs(&self) -> Vec<&String> { + vec![ + &self.logic_name_input, + &self.target_column_input, + &self.description_input, + ] + } + + fn fields(&self) -> Vec<&str> { + vec!["Logic Name", "Target Column", "Description"] + } + + fn has_unsaved_changes(&self) -> bool { + self.has_unsaved_changes + } + fn set_has_unsaved_changes(&mut self, changed: bool) { self.has_unsaved_changes = changed; } - fn get_suggestions(&self) -> Option<&[String]> { - if self.current_field() == 1 - && self.in_target_column_suggestion_mode - && self.show_target_column_suggestions - { - Some(&self.target_column_suggestions) - } else { - None - } - } + fn handle_feature_action(&mut self, action: &CanvasAction, _context: &ActionContext) -> Option { + match action { + // Handle saving logic script + CanvasAction::Custom(action_str) if action_str == "save_logic" => { + self.save_logic() + } - fn get_selected_suggestion_index(&self) -> Option { - if self.current_field() == 1 - && self.in_target_column_suggestion_mode - && self.show_target_column_suggestions - { - self.selected_target_column_suggestion_index - } else { - None + // Handle clearing the form + CanvasAction::Custom(action_str) if action_str == "clear_form" => { + self.clear_form() + } + + // Handle target column autocomplete activation + CanvasAction::Custom(action_str) if action_str == "activate_autocomplete" => { + if self.current_field() == 1 { // Target Column field + self.in_target_column_suggestion_mode = true; + self.update_target_column_suggestions(); + Some("Autocomplete activated".to_string()) + } else { + None + } + } + + // Handle target column suggestion selection + CanvasAction::Custom(action_str) if action_str == "select_suggestion" => { + if self.current_field() == 1 && self.in_target_column_suggestion_mode { + if let Some(selected_idx) = self.selected_target_column_suggestion_index { + if let Some(suggestion) = self.target_column_suggestions.get(selected_idx) { + self.target_column_input = suggestion.clone(); + self.target_column_cursor_pos = suggestion.len(); + self.in_target_column_suggestion_mode = false; + self.show_target_column_suggestions = false; + self.has_unsaved_changes = true; + return Some(format!("Selected: {}", suggestion)); + } + } + } + None + } + + // Custom validation when moving between fields + CanvasAction::NextField => { + match self.current_field() { + 0 => { // Logic Name field + if self.logic_name_input.trim().is_empty() { + Some("Logic name cannot be empty".to_string()) + } else { + None // Let canvas library handle the normal field movement + } + } + 1 => { // Target Column field + // Update suggestions when entering target column field + self.update_target_column_suggestions(); + None + } + _ => None, + } + } + + // Handle character insertion with validation + CanvasAction::InsertChar(c) => { + if self.current_field() == 1 { // Target Column field + // Update suggestions after character insertion + // Note: Canvas library will handle the actual insertion + // This is just for triggering suggestion updates + None // Let canvas handle insertion, then we'll update suggestions + } else { + None // Let canvas handle normally + } + } + + // Let canvas library handle everything else + _ => None, } } }