we are suggesting properly table column names now
This commit is contained in:
@@ -234,31 +234,34 @@ pub fn handle_admin_navigation(
|
|||||||
admin_state.add_logic_state = AddLogicState {
|
admin_state.add_logic_state = AddLogicState {
|
||||||
profile_name: profile.name.clone(),
|
profile_name: profile.name.clone(),
|
||||||
selected_table_name: Some(table.name.clone()),
|
selected_table_name: Some(table.name.clone()),
|
||||||
// selected_table_id: table.id, // If you have table IDs
|
selected_table_id: Some(table.id), // If you have table IDs
|
||||||
editor_keybinding_mode: config.editor.keybinding_mode.clone(),
|
editor_keybinding_mode: config.editor.keybinding_mode.clone(),
|
||||||
current_focus: AddLogicFocus::default(), // Reset focus for the new screen
|
current_focus: AddLogicFocus::default(),
|
||||||
..AddLogicState::default()
|
..AddLogicState::default()
|
||||||
};
|
};
|
||||||
buffer_state.update_history(AppView::AddLogic); // Switch view
|
|
||||||
app_state.ui.focus_outside_canvas = false; // Ensure canvas focus
|
// Store table info for later fetching
|
||||||
|
app_state.pending_table_structure_fetch = Some((
|
||||||
|
profile.name.clone(),
|
||||||
|
table.name.clone()
|
||||||
|
));
|
||||||
|
|
||||||
|
buffer_state.update_history(AppView::AddLogic);
|
||||||
|
app_state.ui.focus_outside_canvas = false;
|
||||||
*command_message = format!(
|
*command_message = format!(
|
||||||
"Opening Add Logic for table '{}' in profile '{}'...",
|
"Opening Add Logic for table '{}' in profile '{}'...",
|
||||||
table.name, profile.name
|
table.name, profile.name
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// This case should ideally not be reached if indices are managed correctly
|
|
||||||
*command_message = "Error: Selected table data not found.".to_string();
|
*command_message = "Error: Selected table data not found.".to_string();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Profile is selected, but table is not
|
|
||||||
*command_message = "Select a table first!".to_string();
|
*command_message = "Select a table first!".to_string();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// This case should ideally not be reached if p_idx is valid
|
|
||||||
*command_message = "Error: Selected profile data not found.".to_string();
|
*command_message = "Error: Selected profile data not found.".to_string();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Profile is not selected
|
|
||||||
*command_message = "Select a profile first!".to_string();
|
*command_message = "Select a profile first!".to_string();
|
||||||
}
|
}
|
||||||
handled = true;
|
handled = true;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
use crate::services::grpc_client::GrpcClient;
|
use crate::services::grpc_client::GrpcClient;
|
||||||
use crate::state::pages::form::FormState;
|
use crate::state::pages::form::FormState;
|
||||||
use crate::tui::functions::common::form::SaveOutcome;
|
use crate::tui::functions::common::form::SaveOutcome;
|
||||||
|
use crate::state::pages::add_logic::AddLogicState;
|
||||||
use crate::state::app::state::AppState;
|
use crate::state::app::state::AppState;
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
|
|
||||||
@@ -37,6 +38,49 @@ impl UiService {
|
|||||||
Ok(column_names)
|
Ok(column_names)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn initialize_add_logic_table_data(
|
||||||
|
grpc_client: &mut GrpcClient,
|
||||||
|
add_logic_state: &mut AddLogicState,
|
||||||
|
) -> Result<String> {
|
||||||
|
let profile_name_clone_opt = Some(add_logic_state.profile_name.clone());
|
||||||
|
let table_name_opt_clone = add_logic_state.selected_table_name.clone();
|
||||||
|
|
||||||
|
if let (Some(profile_name_clone), Some(table_name_clone)) = (profile_name_clone_opt, table_name_opt_clone) {
|
||||||
|
match grpc_client.get_table_structure(profile_name_clone.clone(), table_name_clone.clone()).await {
|
||||||
|
Ok(response) => {
|
||||||
|
let column_names: Vec<String> = response.columns
|
||||||
|
.into_iter()
|
||||||
|
.map(|col| col.name)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// This mutable borrow is now fine
|
||||||
|
add_logic_state.set_table_columns(column_names.clone());
|
||||||
|
|
||||||
|
Ok(format!(
|
||||||
|
"Loaded {} columns for table '{}' in profile '{}'",
|
||||||
|
column_names.len(),
|
||||||
|
table_name_clone, // Use cloned value
|
||||||
|
profile_name_clone // Use cloned value
|
||||||
|
))
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
tracing::warn!(
|
||||||
|
"Failed to fetch table structure for {}.{}: {}",
|
||||||
|
profile_name_clone, // Use cloned value
|
||||||
|
table_name_clone, // Use cloned value
|
||||||
|
e
|
||||||
|
);
|
||||||
|
Ok(format!(
|
||||||
|
"Warning: Could not load table structure for '{}'. Autocomplete will use basic suggestions.",
|
||||||
|
table_name_clone // Use cloned value
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok("No table selected or profile name missing for Add Logic".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn initialize_adresar_count(
|
pub async fn initialize_adresar_count(
|
||||||
grpc_client: &mut GrpcClient,
|
grpc_client: &mut GrpcClient,
|
||||||
app_state: &mut AppState,
|
app_state: &mut AppState,
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ pub struct AppState {
|
|||||||
pub selected_profile: Option<String>,
|
pub selected_profile: Option<String>,
|
||||||
pub current_mode: AppMode,
|
pub current_mode: AppMode,
|
||||||
pub focused_button_index: usize,
|
pub focused_button_index: usize,
|
||||||
|
pub pending_table_structure_fetch: Option<(String, String)>,
|
||||||
|
|
||||||
// UI preferences
|
// UI preferences
|
||||||
pub ui: UiState,
|
pub ui: UiState,
|
||||||
@@ -57,6 +58,7 @@ impl AppState {
|
|||||||
selected_profile: None,
|
selected_profile: None,
|
||||||
current_mode: AppMode::General,
|
current_mode: AppMode::General,
|
||||||
focused_button_index: 0,
|
focused_button_index: 0,
|
||||||
|
pending_table_structure_fetch: None,
|
||||||
ui: UiState::default(),
|
ui: UiState::default(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -131,22 +131,26 @@ impl AddLogicState {
|
|||||||
|
|
||||||
/// Updates script editor suggestions based on current filter text
|
/// Updates script editor suggestions based on current filter text
|
||||||
pub fn update_script_editor_suggestions(&mut self) {
|
pub fn update_script_editor_suggestions(&mut self) {
|
||||||
let hardcoded_suggestions = vec![
|
let mut suggestions = vec!["sql".to_string()];
|
||||||
"sql".to_string(),
|
|
||||||
"tablename".to_string(),
|
|
||||||
"table column".to_string()
|
|
||||||
];
|
|
||||||
|
|
||||||
|
// Add actual table name if available
|
||||||
|
if let Some(ref table_name) = self.selected_table_name {
|
||||||
|
suggestions.push(table_name.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add column names from the table
|
||||||
|
suggestions.extend(self.table_columns_for_suggestions.clone());
|
||||||
|
|
||||||
if self.script_editor_filter_text.is_empty() {
|
if self.script_editor_filter_text.is_empty() {
|
||||||
self.script_editor_suggestions = hardcoded_suggestions;
|
self.script_editor_suggestions = suggestions;
|
||||||
} else {
|
} else {
|
||||||
let filter_lower = self.script_editor_filter_text.to_lowercase();
|
let filter_lower = self.script_editor_filter_text.to_lowercase();
|
||||||
self.script_editor_suggestions = hardcoded_suggestions
|
self.script_editor_suggestions = suggestions
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|suggestion| suggestion.to_lowercase().contains(&filter_lower))
|
.filter(|suggestion| suggestion.to_lowercase().contains(&filter_lower))
|
||||||
.collect();
|
.collect();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update selection index
|
// Update selection index
|
||||||
if self.script_editor_suggestions.is_empty() {
|
if self.script_editor_suggestions.is_empty() {
|
||||||
self.script_editor_selected_suggestion_index = None;
|
self.script_editor_selected_suggestion_index = None;
|
||||||
@@ -160,6 +164,15 @@ impl AddLogicState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets table columns for autocomplete suggestions
|
||||||
|
pub fn set_table_columns(&mut self, columns: Vec<String>) {
|
||||||
|
self.table_columns_for_suggestions = columns.clone();
|
||||||
|
// Also update target column suggestions for the input field
|
||||||
|
if !columns.is_empty() {
|
||||||
|
self.update_target_column_suggestions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Deactivates script editor autocomplete and clears related state
|
/// Deactivates script editor autocomplete and clears related state
|
||||||
pub fn deactivate_script_editor_autocomplete(&mut self) {
|
pub fn deactivate_script_editor_autocomplete(&mut self) {
|
||||||
self.script_editor_autocomplete_active = false;
|
self.script_editor_autocomplete_active = false;
|
||||||
|
|||||||
@@ -23,16 +23,16 @@ use crate::tui::terminal::{EventReader, TerminalCore};
|
|||||||
use crate::ui::handlers::render::render_ui;
|
use crate::ui::handlers::render::render_ui;
|
||||||
use crate::tui::functions::common::login::LoginResult;
|
use crate::tui::functions::common::login::LoginResult;
|
||||||
use crate::tui::functions::common::register::RegisterResult;
|
use crate::tui::functions::common::register::RegisterResult;
|
||||||
use crate::tui::functions::common::add_table::handle_save_table_action;
|
// Removed: use crate::tui::functions::common::add_table::handle_save_table_action;
|
||||||
use crate::functions::modes::navigation::add_table_nav::SaveTableResultSender;
|
// Removed: use crate::functions::modes::navigation::add_table_nav::SaveTableResultSender;
|
||||||
use crate::ui::handlers::context::{DialogPurpose, UiContext};
|
use crate::ui::handlers::context::DialogPurpose; // UiContext removed if not used directly
|
||||||
use crate::tui::functions::common::login;
|
use crate::tui::functions::common::login;
|
||||||
use crate::tui::functions::common::register;
|
use crate::tui::functions::common::register;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use crossterm::cursor::SetCursorStyle;
|
use crossterm::cursor::SetCursorStyle;
|
||||||
use crossterm::event as crossterm_event;
|
use crossterm::event as crossterm_event;
|
||||||
use tracing::{error, info};
|
use tracing::{error, info, warn}; // Added warn
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@ pub async fn run_ui() -> Result<()> {
|
|||||||
mpsc::channel::<RegisterResult>(1);
|
mpsc::channel::<RegisterResult>(1);
|
||||||
let (save_table_result_sender, mut save_table_result_receiver) =
|
let (save_table_result_sender, mut save_table_result_receiver) =
|
||||||
mpsc::channel::<Result<String>>(1);
|
mpsc::channel::<Result<String>>(1);
|
||||||
let (save_logic_result_sender, mut save_logic_result_receiver) =
|
let (save_logic_result_sender, _save_logic_result_receiver) = // Prefixed and removed mut
|
||||||
mpsc::channel::<Result<String>>(1);
|
mpsc::channel::<Result<String>>(1);
|
||||||
|
|
||||||
let mut event_handler = EventHandler::new(
|
let mut event_handler = EventHandler::new(
|
||||||
@@ -73,8 +73,6 @@ pub async fn run_ui() -> Result<()> {
|
|||||||
let mut auto_logged_in = false;
|
let mut auto_logged_in = false;
|
||||||
match load_auth_data() {
|
match load_auth_data() {
|
||||||
Ok(Some(stored_data)) => {
|
Ok(Some(stored_data)) => {
|
||||||
// TODO: Optionally validate token with server here
|
|
||||||
// For now, assume valid if successfully loaded
|
|
||||||
auth_state.auth_token = Some(stored_data.access_token);
|
auth_state.auth_token = Some(stored_data.access_token);
|
||||||
auth_state.user_id = Some(stored_data.user_id);
|
auth_state.user_id = Some(stored_data.user_id);
|
||||||
auth_state.role = Some(stored_data.role);
|
auth_state.role = Some(stored_data.role);
|
||||||
@@ -91,27 +89,20 @@ pub async fn run_ui() -> Result<()> {
|
|||||||
}
|
}
|
||||||
// --- END DATA ---
|
// --- END DATA ---
|
||||||
|
|
||||||
// Initialize app state with profile tree and table structure
|
|
||||||
let column_names =
|
let column_names =
|
||||||
UiService::initialize_app_state(&mut grpc_client, &mut app_state)
|
UiService::initialize_app_state(&mut grpc_client, &mut app_state)
|
||||||
.await.context("Failed to initialize app state from UI service")?;
|
.await.context("Failed to initialize app state from UI service")?;
|
||||||
let mut form_state = FormState::new(column_names);
|
let mut form_state = FormState::new(column_names);
|
||||||
|
|
||||||
// Fetch the total count of Adresar entries
|
|
||||||
UiService::initialize_adresar_count(&mut grpc_client, &mut app_state).await?;
|
UiService::initialize_adresar_count(&mut grpc_client, &mut app_state).await?;
|
||||||
form_state.reset_to_empty();
|
form_state.reset_to_empty();
|
||||||
|
|
||||||
// --- DATA2: Adjust initial view based on auth status ---
|
|
||||||
if auto_logged_in {
|
if auto_logged_in {
|
||||||
// User is auto-logged in, go to main app view
|
|
||||||
buffer_state.history = vec![AppView::Form];
|
buffer_state.history = vec![AppView::Form];
|
||||||
buffer_state.active_index = 0;
|
buffer_state.active_index = 0;
|
||||||
info!("Initial view set to Form due to auto-login.");
|
info!("Initial view set to Form due to auto-login.");
|
||||||
}
|
}
|
||||||
// If not auto-logged in, BufferState default (Intro) will be used
|
|
||||||
// --- END DATA2 ---
|
|
||||||
|
|
||||||
// --- FPS Calculation State ---
|
|
||||||
let mut last_frame_time = Instant::now();
|
let mut last_frame_time = Instant::now();
|
||||||
let mut current_fps = 0.0;
|
let mut current_fps = 0.0;
|
||||||
let mut needs_redraw = true;
|
let mut needs_redraw = true;
|
||||||
@@ -119,7 +110,6 @@ pub async fn run_ui() -> Result<()> {
|
|||||||
loop {
|
loop {
|
||||||
// --- Synchronize UI View from Active Buffer ---
|
// --- Synchronize UI View from Active Buffer ---
|
||||||
if let Some(active_view) = buffer_state.get_active_view() {
|
if let Some(active_view) = buffer_state.get_active_view() {
|
||||||
// Reset all flags first
|
|
||||||
app_state.ui.show_intro = false;
|
app_state.ui.show_intro = false;
|
||||||
app_state.ui.show_login = false;
|
app_state.ui.show_login = false;
|
||||||
app_state.ui.show_register = false;
|
app_state.ui.show_register = false;
|
||||||
@@ -142,21 +132,19 @@ pub async fn run_ui() -> Result<()> {
|
|||||||
event_handler.command_message = format!("Error refreshing admin data: {}", e);
|
event_handler.command_message = format!("Error refreshing admin data: {}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
app_state.ui.show_admin = true; // <<< RESTORE THIS
|
app_state.ui.show_admin = true;
|
||||||
let profile_names = app_state.profile_tree.profiles.iter() // <<< RESTORE THIS
|
let profile_names = app_state.profile_tree.profiles.iter()
|
||||||
.map(|p| p.name.clone()) // <<< RESTORE THIS
|
.map(|p| p.name.clone())
|
||||||
.collect(); // <<< RESTORE THIS
|
.collect();
|
||||||
admin_state.set_profiles(profile_names);
|
admin_state.set_profiles(profile_names);
|
||||||
|
|
||||||
// Only reset to ProfilesPane if not already in a specific admin sub-focus
|
if admin_state.current_focus == AdminFocus::default() ||
|
||||||
if admin_state.current_focus == AdminFocus::default() ||
|
!matches!(admin_state.current_focus,
|
||||||
!matches!(admin_state.current_focus,
|
|
||||||
AdminFocus::InsideProfilesList |
|
AdminFocus::InsideProfilesList |
|
||||||
AdminFocus::Tables | AdminFocus::InsideTablesList |
|
AdminFocus::Tables | AdminFocus::InsideTablesList |
|
||||||
AdminFocus::Button1 | AdminFocus::Button2 | AdminFocus::Button3) {
|
AdminFocus::Button1 | AdminFocus::Button2 | AdminFocus::Button3) {
|
||||||
admin_state.current_focus = AdminFocus::ProfilesPane;
|
admin_state.current_focus = AdminFocus::ProfilesPane;
|
||||||
}
|
}
|
||||||
// Pre-select first profile item for visual consistency, but '>' won't show until 'select'
|
|
||||||
if admin_state.profile_list_state.selected().is_none() && !app_state.profile_tree.profiles.is_empty() {
|
if admin_state.profile_list_state.selected().is_none() && !app_state.profile_tree.profiles.is_empty() {
|
||||||
admin_state.profile_list_state.select(Some(0));
|
admin_state.profile_list_state.select(Some(0));
|
||||||
}
|
}
|
||||||
@@ -164,16 +152,56 @@ pub async fn run_ui() -> Result<()> {
|
|||||||
AppView::AddTable => app_state.ui.show_add_table = true,
|
AppView::AddTable => app_state.ui.show_add_table = true,
|
||||||
AppView::AddLogic => app_state.ui.show_add_logic = true,
|
AppView::AddLogic => app_state.ui.show_add_logic = true,
|
||||||
AppView::Form => app_state.ui.show_form = true,
|
AppView::Form => app_state.ui.show_form = true,
|
||||||
AppView::Scratch => {} // Or show a scratchpad component
|
AppView::Scratch => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// --- End Synchronization ---
|
// --- End Synchronization ---
|
||||||
|
|
||||||
|
// --- Handle Pending Table Structure Fetches ---
|
||||||
|
if let Some((profile_name, table_name)) = app_state.pending_table_structure_fetch.take() {
|
||||||
|
if app_state.ui.show_add_logic {
|
||||||
|
// Ensure admin_state.add_logic_state matches the pending fetch
|
||||||
|
if admin_state.add_logic_state.profile_name == profile_name &&
|
||||||
|
admin_state.add_logic_state.selected_table_name.as_deref() == Some(table_name.as_str()) {
|
||||||
|
|
||||||
|
info!("Fetching table structure for {}.{}", profile_name, table_name);
|
||||||
|
let fetch_message = UiService::initialize_add_logic_table_data(
|
||||||
|
&mut grpc_client,
|
||||||
|
&mut admin_state.add_logic_state,
|
||||||
|
).await.unwrap_or_else(|e| {
|
||||||
|
error!("Error initializing add_logic_table_data: {}", e);
|
||||||
|
format!("Error fetching table structure: {}", e)
|
||||||
|
});
|
||||||
|
|
||||||
|
if !fetch_message.contains("Error") && !fetch_message.contains("Warning") {
|
||||||
|
info!("{}", fetch_message);
|
||||||
|
// Optionally update command message on success if desired
|
||||||
|
// event_handler.command_message = fetch_message;
|
||||||
|
} else {
|
||||||
|
event_handler.command_message = fetch_message; // Show error/warning to user
|
||||||
|
}
|
||||||
|
needs_redraw = true;
|
||||||
|
} else {
|
||||||
|
error!(
|
||||||
|
"Mismatch in pending_table_structure_fetch: app_state wants {}.{}, but add_logic_state is for {}.{:?}",
|
||||||
|
profile_name, table_name,
|
||||||
|
admin_state.add_logic_state.profile_name,
|
||||||
|
admin_state.add_logic_state.selected_table_name
|
||||||
|
);
|
||||||
|
// Cleared by .take(), no need to set to None explicitly unless re-queueing
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
warn!(
|
||||||
|
"Pending table structure fetch for {}.{} but AddLogic view is not active. Fetch ignored.",
|
||||||
|
profile_name, table_name
|
||||||
|
);
|
||||||
|
// If you need to re-queue:
|
||||||
|
// app_state.pending_table_structure_fetch = Some((profile_name, table_name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- 3. Draw UI ---
|
// --- 3. Draw UI ---
|
||||||
// Draw the current state *first*. This ensures the loading dialog
|
if needs_redraw {
|
||||||
// set in the *previous* iteration gets rendered before the pending
|
|
||||||
// action check below.
|
|
||||||
if needs_redraw {
|
|
||||||
terminal.draw(|f| {
|
terminal.draw(|f| {
|
||||||
render_ui(
|
render_ui(
|
||||||
f,
|
f,
|
||||||
@@ -185,7 +213,7 @@ pub async fn run_ui() -> Result<()> {
|
|||||||
&mut admin_state,
|
&mut admin_state,
|
||||||
&buffer_state,
|
&buffer_state,
|
||||||
&theme,
|
&theme,
|
||||||
event_handler.is_edit_mode, // Use event_handler's state
|
event_handler.is_edit_mode,
|
||||||
&event_handler.highlight_state,
|
&event_handler.highlight_state,
|
||||||
app_state.total_count,
|
app_state.total_count,
|
||||||
app_state.current_position,
|
app_state.current_position,
|
||||||
@@ -201,7 +229,6 @@ pub async fn run_ui() -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// --- Cursor Visibility Logic ---
|
// --- Cursor Visibility Logic ---
|
||||||
// (Keep existing cursor logic here - depends on state drawn above)
|
|
||||||
let current_mode = ModeManager::derive_mode(&app_state, &event_handler, &admin_state);
|
let current_mode = ModeManager::derive_mode(&app_state, &event_handler, &admin_state);
|
||||||
match current_mode {
|
match current_mode {
|
||||||
AppMode::Edit => { terminal.show_cursor()?; }
|
AppMode::Edit => { terminal.show_cursor()?; }
|
||||||
@@ -222,8 +249,6 @@ pub async fn run_ui() -> Result<()> {
|
|||||||
let total_count = app_state.total_count;
|
let total_count = app_state.total_count;
|
||||||
let mut current_position = app_state.current_position;
|
let mut current_position = app_state.current_position;
|
||||||
let position_before_event = current_position;
|
let position_before_event = current_position;
|
||||||
// --- Determine if redraw is needed based on active login ---
|
|
||||||
// Always redraw if the loading dialog is currently showing.
|
|
||||||
if app_state.ui.dialog.is_loading {
|
if app_state.ui.dialog.is_loading {
|
||||||
needs_redraw = true;
|
needs_redraw = true;
|
||||||
}
|
}
|
||||||
@@ -231,10 +256,9 @@ pub async fn run_ui() -> Result<()> {
|
|||||||
// --- 1. Handle Terminal Events ---
|
// --- 1. Handle Terminal Events ---
|
||||||
let mut event_outcome_result = Ok(EventOutcome::Ok(String::new()));
|
let mut event_outcome_result = Ok(EventOutcome::Ok(String::new()));
|
||||||
let mut event_processed = false;
|
let mut event_processed = false;
|
||||||
// Poll for events *after* drawing and checking pending actions
|
|
||||||
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; // Mark that we received and will process an event
|
event_processed = true;
|
||||||
event_outcome_result = event_handler.handle_event(
|
event_outcome_result = event_handler.handle_event(
|
||||||
event,
|
event,
|
||||||
&config,
|
&config,
|
||||||
@@ -257,9 +281,6 @@ pub async fn run_ui() -> Result<()> {
|
|||||||
if event_processed {
|
if event_processed {
|
||||||
needs_redraw = true;
|
needs_redraw = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update position based on handler's modification
|
|
||||||
// This happens *after* the event is handled
|
|
||||||
app_state.current_position = current_position;
|
app_state.current_position = current_position;
|
||||||
|
|
||||||
// --- Check for Login Results from Channel ---
|
// --- Check for Login Results from Channel ---
|
||||||
@@ -272,7 +293,6 @@ pub async fn run_ui() -> Result<()> {
|
|||||||
Err(mpsc::error::TryRecvError::Empty) => { /* No message waiting */ }
|
Err(mpsc::error::TryRecvError::Empty) => { /* No message waiting */ }
|
||||||
Err(mpsc::error::TryRecvError::Disconnected) => {
|
Err(mpsc::error::TryRecvError::Disconnected) => {
|
||||||
error!("Login result channel disconnected unexpectedly.");
|
error!("Login result channel disconnected unexpectedly.");
|
||||||
// Optionally show an error dialog here
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -291,7 +311,7 @@ pub async fn run_ui() -> Result<()> {
|
|||||||
// --- Check for Save Table Results ---
|
// --- Check for Save Table Results ---
|
||||||
match save_table_result_receiver.try_recv() {
|
match save_table_result_receiver.try_recv() {
|
||||||
Ok(result) => {
|
Ok(result) => {
|
||||||
app_state.hide_dialog(); // Hide loading indicator
|
app_state.hide_dialog();
|
||||||
match result {
|
match result {
|
||||||
Ok(ref success_message) => {
|
Ok(ref success_message) => {
|
||||||
app_state.show_dialog(
|
app_state.show_dialog(
|
||||||
@@ -304,12 +324,11 @@ pub async fn run_ui() -> Result<()> {
|
|||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
event_handler.command_message = format!("Save failed: {}", e);
|
event_handler.command_message = format!("Save failed: {}", e);
|
||||||
// Optionally show an error dialog instead of just command message
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
needs_redraw = true;
|
needs_redraw = true;
|
||||||
}
|
}
|
||||||
Err(mpsc::error::TryRecvError::Empty) => {} // No message
|
Err(mpsc::error::TryRecvError::Empty) => {}
|
||||||
Err(mpsc::error::TryRecvError::Disconnected) => {
|
Err(mpsc::error::TryRecvError::Disconnected) => {
|
||||||
error!("Save table result channel disconnected unexpectedly.");
|
error!("Save table result channel disconnected unexpectedly.");
|
||||||
}
|
}
|
||||||
@@ -319,19 +338,15 @@ pub async fn run_ui() -> Result<()> {
|
|||||||
let mut should_exit = false;
|
let mut should_exit = false;
|
||||||
match event_outcome_result {
|
match event_outcome_result {
|
||||||
Ok(outcome) => match outcome {
|
Ok(outcome) => match outcome {
|
||||||
EventOutcome::Ok(message) => {
|
EventOutcome::Ok(_message) => {
|
||||||
if !message.is_empty() {
|
// Message is often set directly in event_handler.command_message
|
||||||
// Update command message only if event handling produced one
|
|
||||||
// Avoid overwriting messages potentially set by pending actions
|
|
||||||
// event_handler.command_message = message;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
EventOutcome::Exit(message) => {
|
EventOutcome::Exit(message) => {
|
||||||
event_handler.command_message = message;
|
event_handler.command_message = message;
|
||||||
should_exit = true;
|
should_exit = true;
|
||||||
}
|
}
|
||||||
EventOutcome::DataSaved(save_outcome, message) => {
|
EventOutcome::DataSaved(save_outcome, message) => {
|
||||||
event_handler.command_message = message; // Show save status
|
event_handler.command_message = message;
|
||||||
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,
|
||||||
@@ -345,119 +360,87 @@ pub async fn run_ui() -> Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
EventOutcome::ButtonSelected { context: _, index: _ } => {
|
EventOutcome::ButtonSelected { context: _, index: _ } => {
|
||||||
// This case should ideally be fully handled within handle_event
|
// Handled within event_handler or specific navigation modules
|
||||||
// If initiate_login was called, it returned early.
|
|
||||||
// If not, the message was set and returned via Ok(message).
|
|
||||||
// Log if necessary, but likely no action needed here.
|
|
||||||
// log::warn!("ButtonSelected outcome reached main loop unexpectedly.");
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
event_handler.command_message = format!("Error: {}", e);
|
event_handler.command_message = format!("Error: {}", e);
|
||||||
}
|
}
|
||||||
} // --- End Consequence Handling ---
|
}
|
||||||
|
// --- End Consequence Handling ---
|
||||||
|
|
||||||
// --- Position Change Handling (after outcome processing and pending actions) ---
|
// --- Position Change Handling ---
|
||||||
let position_changed = app_state.current_position != position_before_event;
|
let position_changed = app_state.current_position != position_before_event;
|
||||||
let current_total_count = app_state.total_count;
|
let current_total_count = app_state.total_count; // Use current total_count
|
||||||
let mut position_logic_needs_redraw = false;
|
let mut position_logic_needs_redraw = false;
|
||||||
|
|
||||||
if app_state.ui.show_form {
|
if app_state.ui.show_form {
|
||||||
if position_changed && !event_handler.is_edit_mode {
|
if position_changed && !event_handler.is_edit_mode {
|
||||||
let current_input = form_state.get_current_input();
|
let current_input = form_state.get_current_input();
|
||||||
let max_cursor_pos = if !current_input.is_empty() {
|
let max_cursor_pos = if !current_input.is_empty() { current_input.len() - 1 } else { 0 };
|
||||||
current_input.len() - 1 // Limit to last character in readonly mode
|
form_state.current_cursor_pos = event_handler.ideal_cursor_column.min(max_cursor_pos);
|
||||||
} else {
|
|
||||||
0
|
|
||||||
};
|
|
||||||
form_state.current_cursor_pos =
|
|
||||||
event_handler.ideal_cursor_column.min(max_cursor_pos);
|
|
||||||
position_logic_needs_redraw = true;
|
position_logic_needs_redraw = true;
|
||||||
|
|
||||||
// Ensure position never exceeds total_count + 1
|
|
||||||
if app_state.current_position > current_total_count + 1 {
|
if app_state.current_position > current_total_count + 1 {
|
||||||
app_state.current_position = current_total_count + 1;
|
app_state.current_position = current_total_count + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if app_state.current_position > current_total_count {
|
if app_state.current_position > current_total_count {
|
||||||
// New entry - reset form
|
|
||||||
form_state.reset_to_empty();
|
form_state.reset_to_empty();
|
||||||
form_state.current_field = 0;
|
form_state.current_field = 0;
|
||||||
} else if app_state.current_position >= 1
|
} else if app_state.current_position >= 1 && app_state.current_position <= current_total_count {
|
||||||
&& app_state.current_position <= current_total_count
|
let current_position_to_load = app_state.current_position;
|
||||||
{
|
|
||||||
// Existing entry - load data
|
|
||||||
let current_position_to_load = app_state.current_position; // Use a copy
|
|
||||||
let load_message = UiService::load_adresar_by_position(
|
let load_message = UiService::load_adresar_by_position(
|
||||||
&mut grpc_client,
|
&mut grpc_client,
|
||||||
&mut app_state, // Pass app_state mutably if needed by the service
|
&mut app_state,
|
||||||
&mut form_state,
|
&mut form_state,
|
||||||
current_position_to_load,
|
current_position_to_load,
|
||||||
)
|
)
|
||||||
.await.with_context(|| format!("Failed to load adresar by position: {}", current_position_to_load))?;
|
.await.with_context(|| format!("Failed to load adresar by position: {}", current_position_to_load))?;
|
||||||
|
|
||||||
let current_input = form_state.get_current_input();
|
let current_input_after_load = form_state.get_current_input();
|
||||||
let max_cursor_pos = if !event_handler.is_edit_mode
|
let max_cursor_pos_after_load = if !event_handler.is_edit_mode && !current_input_after_load.is_empty() {
|
||||||
&& !current_input.is_empty()
|
current_input_after_load.len() - 1
|
||||||
{
|
|
||||||
current_input.len() - 1 // In readonly mode, limit to last character
|
|
||||||
} else {
|
} else {
|
||||||
current_input.len()
|
current_input_after_load.len()
|
||||||
};
|
};
|
||||||
form_state.current_cursor_pos = event_handler
|
form_state.current_cursor_pos = event_handler.ideal_cursor_column.min(max_cursor_pos_after_load);
|
||||||
.ideal_cursor_column
|
|
||||||
.min(max_cursor_pos);
|
if !load_message.starts_with("Loaded entry") || event_handler.command_message.is_empty() {
|
||||||
// Don't overwrite message from handle_event if load_message is simple success
|
|
||||||
if !load_message.starts_with("Loaded entry")
|
|
||||||
|| event_handler.command_message.is_empty()
|
|
||||||
{
|
|
||||||
event_handler.command_message = load_message;
|
event_handler.command_message = load_message;
|
||||||
}
|
}
|
||||||
} else {
|
} else { // current_position is 0 or invalid
|
||||||
// Invalid position (e.g., 0) - reset to first entry or new entry mode
|
app_state.current_position = 1.min(current_total_count + 1);
|
||||||
app_state.current_position =
|
if app_state.current_position > current_total_count { // Handles empty db case
|
||||||
1.min(current_total_count + 1); // Go to 1 or new entry if empty
|
|
||||||
if app_state.current_position > total_count {
|
|
||||||
form_state.reset_to_empty();
|
form_state.reset_to_empty();
|
||||||
form_state.current_field = 0;
|
form_state.current_field = 0;
|
||||||
}
|
}
|
||||||
|
// If db is not empty, this will trigger load in next iteration if position changed to 1
|
||||||
}
|
}
|
||||||
} else if !position_changed && !event_handler.is_edit_mode {
|
} else if !position_changed && !event_handler.is_edit_mode {
|
||||||
// If position didn't change but we are in read-only, just adjust cursor
|
|
||||||
let current_input = form_state.get_current_input();
|
let current_input = form_state.get_current_input();
|
||||||
let max_cursor_pos = if !current_input.is_empty() {
|
let max_cursor_pos = if !current_input.is_empty() { current_input.len() - 1 } else { 0 };
|
||||||
current_input.len() - 1
|
form_state.current_cursor_pos = event_handler.ideal_cursor_column.min(max_cursor_pos);
|
||||||
} else {
|
|
||||||
0
|
|
||||||
};
|
|
||||||
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 {
|
||||||
let current_input = register_state.get_current_input();
|
let current_input = register_state.get_current_input();
|
||||||
let max_cursor_pos = if !current_input.is_empty() {
|
let max_cursor_pos = if !current_input.is_empty() { current_input.len() - 1 } else { 0 };
|
||||||
current_input.len() - 1
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
};
|
|
||||||
register_state.current_cursor_pos = event_handler.ideal_cursor_column.min(max_cursor_pos);
|
register_state.current_cursor_pos = event_handler.ideal_cursor_column.min(max_cursor_pos);
|
||||||
}
|
}
|
||||||
} else if app_state.ui.show_login {
|
} else if app_state.ui.show_login {
|
||||||
if !event_handler.is_edit_mode {
|
if !event_handler.is_edit_mode {
|
||||||
let current_input = login_state.get_current_input();
|
let current_input = login_state.get_current_input();
|
||||||
let max_cursor_pos = if !current_input.is_empty() {
|
let max_cursor_pos = if !current_input.is_empty() { current_input.len() - 1 } else { 0 };
|
||||||
current_input.len() - 1
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
};
|
|
||||||
login_state.current_cursor_pos = event_handler.ideal_cursor_column.min(max_cursor_pos);
|
login_state.current_cursor_pos = event_handler.ideal_cursor_column.min(max_cursor_pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if position_logic_needs_redraw {
|
if position_logic_needs_redraw {
|
||||||
needs_redraw = true;
|
needs_redraw = true;
|
||||||
}
|
}
|
||||||
// --- End Position Change Handling ---
|
// --- End Position Change Handling ---
|
||||||
|
|
||||||
// Check exit condition *after* all processing for the iteration
|
|
||||||
if should_exit {
|
if should_exit {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@@ -466,9 +449,8 @@ pub async fn run_ui() -> Result<()> {
|
|||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
let frame_duration = now.duration_since(last_frame_time);
|
let frame_duration = now.duration_since(last_frame_time);
|
||||||
last_frame_time = now;
|
last_frame_time = now;
|
||||||
if frame_duration.as_secs_f64() > 1e-6 {
|
if frame_duration.as_secs_f64() > 1e-6 { // Avoid division by zero
|
||||||
current_fps = 1.0 / frame_duration.as_secs_f64();
|
current_fps = 1.0 / frame_duration.as_secs_f64();
|
||||||
}
|
}
|
||||||
} // End main loop
|
} // End main loop
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user