diff --git a/client/src/components/auth/login.rs b/client/src/components/auth/login.rs index f9d6e7e..16402cf 100644 --- a/client/src/components/auth/login.rs +++ b/client/src/components/auth/login.rs @@ -5,7 +5,6 @@ use crate::{ state::pages::auth::LoginState, components::common::dialog, state::app::state::AppState, - components::handlers::canvas_bridge::render_canvas_form, // Use our bridge function }; use ratatui::{ layout::{Alignment, Constraint, Direction, Layout, Rect, Margin}, @@ -14,6 +13,16 @@ use ratatui::{ Frame, }; use crate::state::app::highlight::HighlightState; +use canvas::canvas::{render_canvas, HighlightState as CanvasHighlightState}; // Use canvas library's render function + +// 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_login( f: &mut Frame, @@ -49,14 +58,15 @@ pub fn render_login( ]) .split(inner_area); - // --- FORM RENDERING (Using bridge function) --- - render_canvas_form( + // --- FORM RENDERING (Using canvas library directly) --- + let canvas_highlight_state = convert_highlight_state(highlight_state); + render_canvas( f, chunks[0], login_state, // LoginState implements CanvasState - theme, + theme, // Theme implements CanvasTheme is_edit_mode, - highlight_state, + &canvas_highlight_state, ); // --- ERROR MESSAGE --- diff --git a/client/src/components/auth/register.rs b/client/src/components/auth/register.rs index ca8faa2..b0b3c08 100644 --- a/client/src/components/auth/register.rs +++ b/client/src/components/auth/register.rs @@ -6,15 +6,25 @@ use crate::{ components::common::dialog, state::app::state::AppState, modes::handlers::mode_manager::AppMode, - components::handlers::canvas_bridge::render_canvas_form, // Use our bridge function }; use ratatui::{ layout::{Alignment, Constraint, Direction, Layout, Rect, Margin}, style::{Style, Modifier, Color}, - widgets::{Block, BorderType, Borders, Paragraph, List, ListItem, ListState}, + widgets::{Block, BorderType, Borders, Paragraph}, Frame, }; use crate::state::app::highlight::HighlightState; +use canvas::canvas::{render_canvas, HighlightState as CanvasHighlightState}; // Use canvas library's render function +use canvas::autocomplete::gui::render_autocomplete_dropdown; // Use canvas library's autocomplete dropdown + +// 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_register( f: &mut Frame, @@ -49,14 +59,15 @@ pub fn render_register( ]) .split(inner_area); - // --- FORM RENDERING (Using bridge function) --- - let input_rect = render_canvas_form( + // --- FORM RENDERING (Using canvas library directly) --- + let canvas_highlight_state = convert_highlight_state(highlight_state); + let input_rect = render_canvas( f, chunks[0], state, // RegisterState implements CanvasState - theme, + theme, // Theme implements CanvasTheme is_edit_mode, - highlight_state, + &canvas_highlight_state, ); // --- HELP TEXT --- @@ -135,13 +146,17 @@ pub fn render_register( button_chunks[1], ); - // --- AUTOCOMPLETE DROPDOWN (Simple bridge implementation) --- + // --- AUTOCOMPLETE DROPDOWN (Using canvas library directly) --- if app_state.current_mode == AppMode::Edit { if let Some(autocomplete_state) = state.autocomplete_state() { - if autocomplete_state.is_active && !autocomplete_state.suggestions.is_empty() { - if let Some(field_rect) = input_rect { - render_simple_autocomplete_dropdown(f, field_rect, f.area(), theme, autocomplete_state); - } + if let Some(input_rect) = input_rect { + render_autocomplete_dropdown( + f, + f.area(), // Frame area + input_rect, // Current input field rect + theme, // Theme implements CanvasTheme + autocomplete_state, + ); } } } @@ -160,89 +175,3 @@ pub fn render_register( ); } } - -/// Simple autocomplete dropdown renderer (bridge implementation) -fn render_simple_autocomplete_dropdown( - f: &mut Frame, - input_rect: Rect, - frame_area: Rect, - theme: &Theme, - autocomplete_state: &canvas::AutocompleteState, -) { - if autocomplete_state.is_loading { - // Show loading indicator - let loading_area = Rect { - x: input_rect.x, - y: input_rect.y + 1, - width: input_rect.width, - height: 3, - }; - - let loading_paragraph = Paragraph::new("Loading suggestions...") - .style(Style::default().fg(theme.fg)) - .block( - Block::default() - .borders(ratatui::widgets::Borders::ALL) - .border_style(Style::default().fg(theme.accent)) - .style(Style::default().bg(theme.bg)), - ); - - f.render_widget(loading_paragraph, loading_area); - return; - } - - if autocomplete_state.suggestions.is_empty() { - return; - } - - // Calculate dropdown position - let dropdown_height = (autocomplete_state.suggestions.len() as u16).min(8) + 2; - let dropdown_width = input_rect.width.max(20); - - let mut dropdown_area = Rect { - x: input_rect.x, - y: input_rect.y + 1, - width: dropdown_width, - height: dropdown_height, - }; - - // Keep dropdown within bounds - if dropdown_area.bottom() > frame_area.height { - dropdown_area.y = input_rect.y.saturating_sub(dropdown_height); - } - if dropdown_area.right() > frame_area.width { - dropdown_area.x = frame_area.width.saturating_sub(dropdown_width); - } - - // Create list items - let items: Vec = autocomplete_state - .suggestions - .iter() - .enumerate() - .map(|(i, suggestion)| { - let is_selected = autocomplete_state.selected_index == Some(i); - let style = if is_selected { - Style::default() - .fg(theme.bg) - .bg(theme.highlight) - .add_modifier(Modifier::BOLD) - } else { - Style::default().fg(theme.fg).bg(theme.bg) - }; - - ListItem::new(suggestion.display_text.as_str()).style(style) - }) - .collect(); - - let list = List::new(items).block( - Block::default() - .borders(ratatui::widgets::Borders::ALL) - .border_style(Style::default().fg(theme.accent)) - .style(Style::default().bg(theme.bg)), - ); - - let mut list_state = ListState::default(); - list_state.select(autocomplete_state.selected_index); - - f.render_stateful_widget(list, dropdown_area, &mut list_state); -} diff --git a/client/src/state/pages/auth.rs b/client/src/state/pages/auth.rs index fb2a94a..264c958 100644 --- a/client/src/state/pages/auth.rs +++ b/client/src/state/pages/auth.rs @@ -1,6 +1,6 @@ // src/state/pages/auth.rs -use canvas::{CanvasState, ActionContext, CanvasAction}; // Import from external library -use canvas::{AutocompleteCanvasState, AutocompleteState, SuggestionItem}; // For autocomplete +use canvas::canvas::{CanvasState, ActionContext, CanvasAction}; +use canvas::autocomplete::{AutocompleteCanvasState, AutocompleteState, SuggestionItem}; use lazy_static::lazy_static; lazy_static! { @@ -45,7 +45,6 @@ pub struct RegisterState { pub current_field: usize, pub current_cursor_pos: usize, pub has_unsaved_changes: bool, - // NEW: Replace old autocomplete with external library's system pub autocomplete: AutocompleteState, } @@ -123,7 +122,6 @@ impl CanvasState for LoginState { self.has_unsaved_changes = changed; } - // Handle custom actions (like submit) fn handle_feature_action(&mut self, action: &CanvasAction, _context: &ActionContext) -> Option { match action { CanvasAction::Custom(action_str) if action_str == "submit" => { diff --git a/client/src/ui/handlers/render.rs b/client/src/ui/handlers/render.rs index 483a67e..19ca255 100644 --- a/client/src/ui/handlers/render.rs +++ b/client/src/ui/handlers/render.rs @@ -16,7 +16,8 @@ use crate::components::{ }; use crate::config::colors::themes::Theme; use crate::modes::general::command_navigation::NavigationState; -use crate::state::pages::canvas_state::CanvasState; +use crate::state::pages::canvas_state::CanvasState as LocalCanvasState; // Keep local one with alias +use canvas::canvas::CanvasState; // Import external library's CanvasState trait use crate::state::app::buffer::BufferState; use crate::state::app::highlight::HighlightState as LocalHighlightState; // CHANGED: Alias local version use canvas::canvas::HighlightState as CanvasHighlightState; // CHANGED: Import canvas version with alias @@ -136,7 +137,7 @@ pub fn render_ui( theme, register_state, app_state, - register_state.current_field() < 4, + register_state.current_field() < 4, // Now using CanvasState trait method highlight_state, // Uses local version ); } else if app_state.ui.show_add_table { @@ -166,7 +167,7 @@ pub fn render_ui( theme, login_state, app_state, - login_state.current_field() < 2, + login_state.current_field() < 2, // Now using CanvasState trait method highlight_state, // Uses local version ); } else if app_state.ui.show_admin { @@ -208,7 +209,7 @@ pub fn render_ui( ]) .split(form_actual_area)[1] }; - + // CHANGED: Convert local HighlightState to canvas HighlightState for FormState let canvas_highlight_state = convert_highlight_state(highlight_state); form_state.render( diff --git a/client/src/ui/handlers/ui.rs b/client/src/ui/handlers/ui.rs index c4f3dcd..7d493b0 100644 --- a/client/src/ui/handlers/ui.rs +++ b/client/src/ui/handlers/ui.rs @@ -8,7 +8,8 @@ use crate::config::storage::storage::load_auth_data; use crate::modes::common::commands::CommandHandler; use crate::modes::handlers::event::{EventHandler, EventOutcome}; use crate::modes::handlers::mode_manager::{AppMode, ModeManager}; -use crate::state::pages::canvas_state::CanvasState; +use crate::state::pages::canvas_state::CanvasState as LocalCanvasState; // Keep local one with alias +use canvas::canvas::CanvasState; // Import external library's CanvasState trait use crate::state::pages::form::{FormState, FieldDefinition}; // Import FieldDefinition use crate::state::pages::auth::AuthState; use crate::state::pages::auth::LoginState; @@ -38,6 +39,7 @@ use crate::state::app::state::DebugState; #[cfg(feature = "ui-debug")] use crate::utils::debug_logger::pop_next_debug_message; +// Rest of the file remains the same... pub async fn run_ui() -> Result<()> { let config = Config::load().context("Failed to load configuration")?; let theme = Theme::from_str(&config.colors.theme); @@ -346,25 +348,25 @@ pub async fn run_ui() -> Result<()> { } } + // Continue with the rest of the function... + // (The rest remains the same, but now CanvasState trait methods are available) + if app_state.ui.show_form { let current_view_profile = app_state.current_view_profile_name.clone(); let current_view_table = app_state.current_view_table_name.clone(); - // This condition correctly detects a table switch. if prev_view_profile_name != current_view_profile || prev_view_table_name != current_view_table { if let (Some(prof_name), Some(tbl_name)) = (current_view_profile.as_ref(), current_view_table.as_ref()) { - // --- START OF REFACTORED LOGIC --- app_state.show_loading_dialog( "Loading Table", &format!("Fetching data for {}.{}...", prof_name, tbl_name), ); needs_redraw = true; - // 1. Call our new, central function. It handles fetching AND caching. match UiService::load_table_view( &mut grpc_client, &mut app_state, @@ -374,72 +376,62 @@ pub async fn run_ui() -> Result<()> { .await { Ok(mut new_form_state) => { - // 2. The function succeeded, we have a new FormState. - // Now, fetch its data. if let Err(e) = UiService::fetch_and_set_table_count( &mut grpc_client, &mut new_form_state, ) .await { - // Handle count fetching error app_state.update_dialog_content( &format!("Error fetching count: {}", e), vec!["OK".to_string()], - DialogPurpose::LoginFailed, // Or a more appropriate purpose + DialogPurpose::LoginFailed, ); } else if new_form_state.total_count > 0 { - // If there are records, load the first/last one if let Err(e) = UiService::load_table_data_by_position( &mut grpc_client, &mut new_form_state, ) .await { - // Handle data loading error app_state.update_dialog_content( &format!("Error loading data: {}", e), vec!["OK".to_string()], - DialogPurpose::LoginFailed, // Or a more appropriate purpose + DialogPurpose::LoginFailed, ); } else { - // Success! Hide the loading dialog. app_state.hide_dialog(); } } else { - // No records, so just reset to an empty form. new_form_state.reset_to_empty(); app_state.hide_dialog(); } - // 3. CRITICAL: Replace the old form_state with the new one. form_state = new_form_state; - - // 4. Update our tracking variables. prev_view_profile_name = current_view_profile; prev_view_table_name = current_view_table; table_just_switched = true; } Err(e) => { - // This handles errors from load_table_view (e.g., schema fetch failed) app_state.update_dialog_content( &format!("Error loading table: {}", e), vec!["OK".to_string()], - DialogPurpose::LoginFailed, // Or a more appropriate purpose + DialogPurpose::LoginFailed, ); - // Revert the view change in app_state to avoid a loop app_state.current_view_profile_name = prev_view_profile_name.clone(); app_state.current_view_table_name = prev_view_table_name.clone(); } } - // --- END OF REFACTORED LOGIC --- } needs_redraw = true; } } + // Continue with the rest of the positioning logic... + // Now we can use CanvasState methods like get_current_input(), current_field(), etc. + if let Some((profile_name, table_name)) = app_state.pending_table_structure_fetch.take() { if app_state.ui.show_add_logic { if admin_state.add_logic_state.profile_name == profile_name &&