now no need for init_form_editor everywhere

This commit is contained in:
Priec
2025-08-21 21:16:59 +02:00
parent f2b426851b
commit 3dd6808ea2
4 changed files with 176 additions and 118 deletions

View File

@@ -7,8 +7,9 @@ use common::proto::komp_ac::table_structure::TableStructureResponse;
use crate::modes::handlers::mode_manager::AppMode; use crate::modes::handlers::mode_manager::AppMode;
use crate::state::app::search::SearchState; use crate::state::app::search::SearchState;
use crate::ui::handlers::context::DialogPurpose; use crate::ui::handlers::context::DialogPurpose;
use canvas::FormEditor;
use crate::state::pages::form::FormState; use crate::state::pages::form::FormState;
use crate::config::binds::Config;
use canvas::FormEditor;
use std::collections::HashMap; use std::collections::HashMap;
use std::env; use std::env;
use std::sync::Arc; use std::sync::Arc;
@@ -185,6 +186,29 @@ impl AppState {
.get(self.ui.dialog.dialog_active_button_index) .get(self.ui.dialog.dialog_active_button_index)
.map(|s| s.as_str()) .map(|s| s.as_str())
} }
pub fn init_form_editor(&mut self, form_state: FormState, config: &Config) {
let mut editor = FormEditor::new(form_state);
editor.set_keymap(config.build_canvas_keymap()); // inject keymap
self.form_editor = Some(editor);
}
/// Replace the current form state and wrap it in a FormEditor with keymap
pub fn set_form_state(&mut self, form_state: FormState, config: &Config) {
let mut editor = FormEditor::new(form_state);
editor.set_keymap(config.build_canvas_keymap());
self.form_editor = Some(editor);
}
/// Immutable access to the underlying FormState
pub fn form_state(&self) -> Option<&FormState> {
self.form_editor.as_ref().map(|e| e.data_provider())
}
/// Mutable access to the underlying FormState
pub fn form_state_mut(&mut self) -> Option<&mut FormState> {
self.form_editor.as_mut().map(|e| e.data_provider_mut())
}
} }
impl Default for UiState { impl Default for UiState {

View File

@@ -116,26 +116,6 @@ impl FormState {
} }
} }
pub fn render(
&self,
f: &mut Frame,
area: Rect,
theme: &Theme,
is_edit_mode: bool,
) {
// Wrap in FormEditor for new API
let mut editor = FormEditor::new(self.clone());
// Use new canvas rendering
canvas::render_canvas_default(f, area, &editor);
// If autocomplete is active, render suggestions
if self.autocomplete_active && !self.autocomplete_suggestions.is_empty() {
// Note: This will need to be updated when suggestions are integrated
// canvas::render_suggestions_dropdown(f, area, input_rect, &canvas::DefaultCanvasTheme, &editor);
}
}
pub fn reset_to_empty(&mut self) { pub fn reset_to_empty(&mut self) {
self.id = 0; self.id = 0;
self.values.iter_mut().for_each(|v| v.clear()); self.values.iter_mut().for_each(|v| v.clear());

View File

@@ -24,6 +24,7 @@ use crate::state::pages::auth::LoginState;
use crate::state::pages::auth::RegisterState; use crate::state::pages::auth::RegisterState;
use crate::state::pages::form::FormState; use crate::state::pages::form::FormState;
use crate::state::pages::intro::IntroState; use crate::state::pages::intro::IntroState;
use crate::components::render_form;
use ratatui::{ use ratatui::{
layout::{Constraint, Direction, Layout}, layout::{Constraint, Direction, Layout},
Frame, Frame,
@@ -192,12 +193,15 @@ pub fn render_ui(
.split(form_actual_area)[1] .split(form_actual_area)[1]
}; };
// CHANGED: Convert local HighlightState to canvas HighlightState for FormState render_form(
form_state.render(
f, f,
form_render_area, form_render_area,
app_state,
form_state,
app_state.current_view_table_name.as_deref().unwrap_or(""),
theme, theme,
is_event_handler_edit_mode, form_state.total_count,
form_state.current_position,
); );
} }

View File

@@ -102,25 +102,28 @@ pub async fn run_ui() -> Result<()> {
}) })
.collect(); .collect();
let mut form_state = FormState::new( // Replace local form_state with app_state.form_editor
initial_profile.clone(), app_state.set_form_state(
initial_table.clone(), FormState::new(initial_profile.clone(), initial_table.clone(), initial_field_defs),
initial_field_defs, &config,
); );
UiService::fetch_and_set_table_count(&mut grpc_client, &mut form_state) // Fetch initial count using app_state accessor
.await if let Some(form_state) = app_state.form_state_mut() {
.context(format!( UiService::fetch_and_set_table_count(&mut grpc_client, form_state)
"Failed to fetch initial count for table {}.{}", .await
initial_profile, initial_table .context(format!(
))?; "Failed to fetch initial count for table {}.{}",
initial_profile, initial_table
))?;
if form_state.total_count > 0 { if form_state.total_count > 0 {
if let Err(e) = UiService::load_table_data_by_position(&mut grpc_client, &mut form_state).await { if let Err(e) = UiService::load_table_data_by_position(&mut grpc_client, form_state).await {
event_handler.command_message = format!("Error loading initial data: {}", e); event_handler.command_message = format!("Error loading initial data: {}", e);
}
} else {
form_state.reset_to_empty();
} }
} else {
form_state.reset_to_empty();
} }
if auto_logged_in { if auto_logged_in {
@@ -137,7 +140,9 @@ pub async fn run_ui() -> Result<()> {
let mut table_just_switched = false; let mut table_just_switched = false;
loop { loop {
let position_before_event = form_state.current_position; let position_before_event = app_state.form_state()
.map(|fs| fs.current_position)
.unwrap_or(1);
let mut event_processed = false; let mut event_processed = false;
// --- CHANNEL RECEIVERS --- // --- CHANNEL RECEIVERS ---
@@ -162,15 +167,17 @@ pub async fn run_ui() -> Result<()> {
// --- ADDED: For live form autocomplete --- // --- ADDED: For live form autocomplete ---
match event_handler.autocomplete_result_receiver.try_recv() { match event_handler.autocomplete_result_receiver.try_recv() {
Ok(hits) => { Ok(hits) => {
if form_state.autocomplete_active { if let Some(form_state) = app_state.form_state_mut() {
form_state.autocomplete_suggestions = hits; if form_state.autocomplete_active {
form_state.autocomplete_loading = false; form_state.autocomplete_suggestions = hits;
if !form_state.autocomplete_suggestions.is_empty() { form_state.autocomplete_loading = false;
form_state.selected_suggestion_index = Some(0); if !form_state.autocomplete_suggestions.is_empty() {
} else { form_state.selected_suggestion_index = Some(0);
form_state.selected_suggestion_index = None; } else {
form_state.selected_suggestion_index = None;
}
event_handler.command_message = format!("Found {} suggestions.", form_state.autocomplete_suggestions.len());
} }
event_handler.command_message = format!("Found {} suggestions.", form_state.autocomplete_suggestions.len());
} }
needs_redraw = true; needs_redraw = true;
} }
@@ -180,27 +187,38 @@ pub async fn run_ui() -> Result<()> {
} }
} }
if app_state.ui.show_search_palette { if app_state.ui.show_search_palette {
needs_redraw = true; needs_redraw = true;
} }
if crossterm_event::poll(std::time::Duration::from_millis(1))? { if crossterm_event::poll(std::time::Duration::from_millis(1))? {
let event = event_reader.read_event().context("Failed to read terminal event")?; let event = event_reader.read_event().context("Failed to read terminal event")?;
event_processed = true; event_processed = true;
let event_outcome_result = event_handler.handle_event( let event_outcome_result = {
event, // We need to avoid borrowing app_state twice, so we'll need to modify the handle_event call
&config, // For now, let's create a temporary approach
&mut terminal, let mut temp_form_state = app_state.form_state_mut().unwrap().clone();
&mut command_handler, let result = event_handler.handle_event(
&mut form_state, event,
&mut auth_state, &config,
&mut login_state, &mut terminal,
&mut register_state, &mut command_handler,
&mut intro_state, &mut temp_form_state,
&mut admin_state, &mut auth_state,
&mut buffer_state, &mut login_state,
&mut app_state, &mut register_state,
).await; &mut intro_state,
&mut admin_state,
&mut buffer_state,
&mut app_state,
).await;
// Update the app_state with any changes from temp_form_state
if let Some(form_state) = app_state.form_state_mut() {
*form_state = temp_form_state;
}
result
};
let mut should_exit = false; let mut should_exit = false;
match event_outcome_result { match event_outcome_result {
@@ -216,15 +234,21 @@ pub async fn run_ui() -> Result<()> {
} }
EventOutcome::DataSaved(save_outcome, message) => { EventOutcome::DataSaved(save_outcome, message) => {
event_handler.command_message = message; event_handler.command_message = message;
// Clone form_state to avoid double borrow
let mut temp_form_state = app_state.form_state().unwrap().clone();
if let Err(e) = UiService::handle_save_outcome( if let Err(e) = UiService::handle_save_outcome(
save_outcome, save_outcome,
&mut grpc_client, &mut grpc_client,
&mut app_state, &mut app_state,
&mut form_state, &mut temp_form_state,
).await { ).await {
event_handler.command_message = event_handler.command_message =
format!("Error handling save outcome: {}", e); format!("Error handling save outcome: {}", e);
} }
// Update app_state with changes
if let Some(form_state) = app_state.form_state_mut() {
*form_state = temp_form_state;
}
} }
EventOutcome::ButtonSelected { .. } => {} EventOutcome::ButtonSelected { .. } => {}
EventOutcome::TableSelected { path } => { EventOutcome::TableSelected { path } => {
@@ -348,7 +372,7 @@ pub async fn run_ui() -> Result<()> {
// Continue with the rest of the function... // Continue with the rest of the function...
// (The rest remains the same, but now CanvasState trait methods are available) // (The rest remains the same, but now CanvasState trait methods are available)
if app_state.ui.show_form { if app_state.ui.show_form {
let current_view_profile = app_state.current_view_profile_name.clone(); let current_view_profile = app_state.current_view_profile_name.clone();
let current_view_table = app_state.current_view_table_name.clone(); let current_view_table = app_state.current_view_table_name.clone();
@@ -373,39 +397,43 @@ pub async fn run_ui() -> Result<()> {
) )
.await .await
{ {
Ok(mut new_form_state) => { Ok(new_form_state) => {
if let Err(e) = UiService::fetch_and_set_table_count( // Set the new form state and fetch count
&mut grpc_client, app_state.set_form_state(new_form_state, &config);
&mut new_form_state,
) if let Some(form_state) = app_state.form_state_mut() {
.await if let Err(e) = UiService::fetch_and_set_table_count(
{
app_state.update_dialog_content(
&format!("Error fetching count: {}", e),
vec!["OK".to_string()],
DialogPurpose::LoginFailed,
);
} else if new_form_state.total_count > 0 {
if let Err(e) = UiService::load_table_data_by_position(
&mut grpc_client, &mut grpc_client,
&mut new_form_state, form_state,
) )
.await .await
{ {
app_state.update_dialog_content( app_state.update_dialog_content(
&format!("Error loading data: {}", e), &format!("Error fetching count: {}", e),
vec!["OK".to_string()], vec!["OK".to_string()],
DialogPurpose::LoginFailed, DialogPurpose::LoginFailed,
); );
} else if form_state.total_count > 0 {
if let Err(e) = UiService::load_table_data_by_position(
&mut grpc_client,
form_state,
)
.await
{
app_state.update_dialog_content(
&format!("Error loading data: {}", e),
vec!["OK".to_string()],
DialogPurpose::LoginFailed,
);
} else {
app_state.hide_dialog();
}
} else { } else {
form_state.reset_to_empty();
app_state.hide_dialog(); app_state.hide_dialog();
} }
} else {
new_form_state.reset_to_empty();
app_state.hide_dialog();
} }
form_state = new_form_state;
prev_view_profile_name = current_view_profile; prev_view_profile_name = current_view_profile;
prev_view_table_name = current_view_table; prev_view_table_name = current_view_table;
table_just_switched = true; table_just_switched = true;
@@ -429,7 +457,7 @@ pub async fn run_ui() -> Result<()> {
// Continue with the rest of the positioning logic... // Continue with the rest of the positioning logic...
// Now we can use CanvasState methods like get_current_input(), current_field(), etc. // 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 let Some((profile_name, table_name)) = app_state.pending_table_structure_fetch.take() {
if app_state.ui.show_add_logic { if app_state.ui.show_add_logic {
if admin_state.add_logic_state.profile_name == profile_name && if admin_state.add_logic_state.profile_name == profile_name &&
@@ -488,47 +516,53 @@ pub async fn run_ui() -> Result<()> {
} }
} }
let position_changed = form_state.current_position != position_before_event; let current_position = app_state.form_state()
.map(|fs| fs.current_position)
.unwrap_or(1);
let position_changed = current_position != position_before_event;
let mut position_logic_needs_redraw = false; let mut position_logic_needs_redraw = false;
if app_state.ui.show_form && !table_just_switched { if app_state.ui.show_form && !table_just_switched {
if position_changed && !event_handler.is_edit_mode { if position_changed && !event_handler.is_edit_mode {
position_logic_needs_redraw = true; position_logic_needs_redraw = true;
if form_state.current_position > form_state.total_count { if let Some(form_state) = app_state.form_state_mut() {
form_state.reset_to_empty(); if form_state.current_position > form_state.total_count {
event_handler.command_message = format!("New entry for {}.{}", form_state.profile_name, form_state.table_name); form_state.reset_to_empty();
} else { event_handler.command_message = format!("New entry for {}.{}", form_state.profile_name, form_state.table_name);
match UiService::load_table_data_by_position(&mut grpc_client, &mut form_state).await { } else {
Ok(load_message) => { match UiService::load_table_data_by_position(&mut grpc_client, form_state).await {
if event_handler.command_message.is_empty() || !load_message.starts_with("Error") { Ok(load_message) => {
event_handler.command_message = load_message; if event_handler.command_message.is_empty() || !load_message.starts_with("Error") {
event_handler.command_message = load_message;
}
}
Err(e) => {
event_handler.command_message = format!("Error loading data: {}", e);
} }
} }
Err(e) => {
event_handler.command_message = format!("Error loading data: {}", e);
}
} }
let current_input_after_load_str = form_state.get_current_input();
let current_input_len_after_load = current_input_after_load_str.chars().count();
let max_cursor_pos = if current_input_len_after_load > 0 {
current_input_len_after_load.saturating_sub(1)
} else {
0
};
form_state.current_cursor_pos = event_handler.ideal_cursor_column.min(max_cursor_pos);
} }
let current_input_after_load_str = form_state.get_current_input();
let current_input_len_after_load = current_input_after_load_str.chars().count();
let max_cursor_pos = if current_input_len_after_load > 0 {
current_input_len_after_load.saturating_sub(1)
} else {
0
};
form_state.current_cursor_pos = event_handler.ideal_cursor_column.min(max_cursor_pos);
} else if !position_changed && !event_handler.is_edit_mode { } else if !position_changed && !event_handler.is_edit_mode {
let current_input_str = form_state.get_current_input(); if let Some(form_state) = app_state.form_state_mut() {
let current_input_len = current_input_str.chars().count(); let current_input_str = form_state.get_current_input();
let max_cursor_pos = if current_input_len > 0 { let current_input_len = current_input_str.chars().count();
current_input_len.saturating_sub(1) let max_cursor_pos = if current_input_len > 0 {
} else { current_input_len.saturating_sub(1)
0 } else {
}; 0
form_state.current_cursor_pos = event_handler.ideal_cursor_column.min(max_cursor_pos); };
form_state.current_cursor_pos = event_handler.ideal_cursor_column.min(max_cursor_pos);
}
} }
} else if app_state.ui.show_register { } else if app_state.ui.show_register {
if !event_handler.is_edit_mode { if !event_handler.is_edit_mode {
@@ -587,10 +621,23 @@ pub async fn run_ui() -> Result<()> {
AppMode::Command => { terminal.set_cursor_style(SetCursorStyle::SteadyUnderScore)?; terminal.show_cursor().context("Failed to show cursor in Command mode")?; } AppMode::Command => { terminal.set_cursor_style(SetCursorStyle::SteadyUnderScore)?; terminal.show_cursor().context("Failed to show cursor in Command mode")?; }
} }
// Temporarily work around borrow checker by extracting needed values
let current_dir = app_state.current_dir.clone();
// Since we can't borrow app_state both mutably and immutably,
// we'll need to either:
// 1. Modify render_ui to take just app_state and access form_state internally, OR
// 2. Extract the specific fields render_ui needs from app_state
// For now, using approach where we temporarily clone what we need
let form_state_clone = app_state.form_state().unwrap().clone();
terminal.draw(|f| { terminal.draw(|f| {
// Use a mutable clone for rendering
let mut temp_form_state = form_state_clone.clone();
render_ui( render_ui(
f, f,
&mut form_state, &mut temp_form_state,
&mut auth_state, &mut auth_state,
&login_state, &login_state,
&register_state, &register_state,
@@ -603,10 +650,13 @@ pub async fn run_ui() -> Result<()> {
event_handler.command_mode, event_handler.command_mode,
&event_handler.command_message, &event_handler.command_message,
&event_handler.navigation_state, &event_handler.navigation_state,
&app_state.current_dir, &current_dir,
current_fps, current_fps,
&app_state, &app_state,
); );
// If render_ui modified the form_state, we'd need to sync it back
// But typically render functions don't modify state, just read it
}).context("Terminal draw call failed")?; }).context("Terminal draw call failed")?;
needs_redraw = false; needs_redraw = false;
} }