making select from the find file to work, not yet working, needs more redesign in how select is working
This commit is contained in:
@@ -18,7 +18,7 @@ pub fn render_buffer_list(
|
|||||||
area: Rect,
|
area: Rect,
|
||||||
theme: &Theme,
|
theme: &Theme,
|
||||||
buffer_state: &BufferState,
|
buffer_state: &BufferState,
|
||||||
app_state: &AppState, // Add this parameter
|
app_state: &AppState,
|
||||||
) {
|
) {
|
||||||
// --- Style Definitions ---
|
// --- Style Definitions ---
|
||||||
let active_style = Style::default()
|
let active_style = Style::default()
|
||||||
@@ -39,8 +39,7 @@ pub fn render_buffer_list(
|
|||||||
let mut spans = Vec::new();
|
let mut spans = Vec::new();
|
||||||
let mut current_width = 0;
|
let mut current_width = 0;
|
||||||
|
|
||||||
// TODO: Replace with actual table name from server response
|
let current_table_name = app_state.current_view_table_name.as_deref();
|
||||||
let current_table_name = Some("2025_customer");
|
|
||||||
|
|
||||||
for (original_index, view) in buffer_state.history.iter().enumerate() {
|
for (original_index, view) in buffer_state.history.iter().enumerate() {
|
||||||
// Filter: Only process views matching the active layer
|
// Filter: Only process views matching the active layer
|
||||||
|
|||||||
@@ -340,12 +340,16 @@ pub async fn handle_command_navigation_event(
|
|||||||
}
|
}
|
||||||
KeyCode::Enter => {
|
KeyCode::Enter => {
|
||||||
if let Some(selected_value) = navigation_state.get_selected_value() {
|
if let Some(selected_value) = navigation_state.get_selected_value() {
|
||||||
let message = match navigation_state.navigation_type {
|
let outcome = match navigation_state.navigation_type {
|
||||||
NavigationType::FindFile => format!("Selected file: {}", selected_value),
|
NavigationType::FindFile => {
|
||||||
NavigationType::TableTree => format!("Selected table: {}", selected_value),
|
EventOutcome::Ok(format!("Selected file: {}", selected_value))
|
||||||
|
}
|
||||||
|
NavigationType::TableTree => {
|
||||||
|
EventOutcome::TableSelected { path: selected_value }
|
||||||
|
}
|
||||||
};
|
};
|
||||||
navigation_state.deactivate();
|
navigation_state.deactivate();
|
||||||
Ok(EventOutcome::Ok(message))
|
Ok(outcome)
|
||||||
} else {
|
} else {
|
||||||
// Enhanced Enter behavior for TableTree: if input is a valid partial path, try to navigate
|
// Enhanced Enter behavior for TableTree: if input is a valid partial path, try to navigate
|
||||||
if navigation_state.navigation_type == NavigationType::TableTree && !navigation_state.input.is_empty() {
|
if navigation_state.navigation_type == NavigationType::TableTree && !navigation_state.input.is_empty() {
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ pub enum EventOutcome {
|
|||||||
Exit(String),
|
Exit(String),
|
||||||
DataSaved(SaveOutcome, String),
|
DataSaved(SaveOutcome, String),
|
||||||
ButtonSelected { context: UiContext, index: usize },
|
ButtonSelected { context: UiContext, index: usize },
|
||||||
|
TableSelected { path: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventOutcome {
|
impl EventOutcome {
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ pub async fn run_ui() -> Result<()> {
|
|||||||
|
|
||||||
// Filter the columns obtained from the service
|
// Filter the columns obtained from the service
|
||||||
let filtered_columns = filter_user_columns(initial_columns_from_service); // Use the correct variable
|
let filtered_columns = filter_user_columns(initial_columns_from_service); // Use the correct variable
|
||||||
|
|
||||||
let mut form_state = FormState::new(
|
let mut form_state = FormState::new(
|
||||||
initial_profile.clone(),
|
initial_profile.clone(),
|
||||||
initial_table.clone(),
|
initial_table.clone(),
|
||||||
@@ -132,208 +132,15 @@ pub async fn run_ui() -> Result<()> {
|
|||||||
let mut prev_view_table_name = app_state.current_view_table_name.clone();
|
let mut prev_view_table_name = app_state.current_view_table_name.clone();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
if let Some(active_view) = buffer_state.get_active_view() {
|
// ===================================================================
|
||||||
app_state.ui.show_intro = false;
|
// PROCESS EVENTS & UPDATE STATE
|
||||||
app_state.ui.show_login = false;
|
// ===================================================================
|
||||||
app_state.ui.show_register = false;
|
|
||||||
app_state.ui.show_admin = false;
|
|
||||||
app_state.ui.show_add_table = false;
|
|
||||||
app_state.ui.show_add_logic = false;
|
|
||||||
app_state.ui.show_form = false;
|
|
||||||
match active_view {
|
|
||||||
AppView::Intro => app_state.ui.show_intro = true,
|
|
||||||
AppView::Login => app_state.ui.show_login = true,
|
|
||||||
AppView::Register => app_state.ui.show_register = true,
|
|
||||||
AppView::Admin => {
|
|
||||||
info!("Active view is Admin, refreshing profile tree...");
|
|
||||||
match grpc_client.get_profile_tree().await {
|
|
||||||
Ok(refreshed_tree) => {
|
|
||||||
app_state.profile_tree = refreshed_tree;
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
error!("Failed to refresh profile tree for Admin panel: {}", e);
|
|
||||||
event_handler.command_message = format!("Error refreshing admin data: {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
app_state.ui.show_admin = true;
|
|
||||||
let profile_names = app_state.profile_tree.profiles.iter()
|
|
||||||
.map(|p| p.name.clone())
|
|
||||||
.collect();
|
|
||||||
admin_state.set_profiles(profile_names);
|
|
||||||
|
|
||||||
if admin_state.current_focus == AdminFocus::default() ||
|
|
||||||
!matches!(admin_state.current_focus,
|
|
||||||
AdminFocus::InsideProfilesList |
|
|
||||||
AdminFocus::Tables | AdminFocus::InsideTablesList |
|
|
||||||
AdminFocus::Button1 | AdminFocus::Button2 | AdminFocus::Button3) {
|
|
||||||
admin_state.current_focus = AdminFocus::ProfilesPane;
|
|
||||||
}
|
|
||||||
if admin_state.profile_list_state.selected().is_none() && !app_state.profile_tree.profiles.is_empty() {
|
|
||||||
admin_state.profile_list_state.select(Some(0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
AppView::AddTable => app_state.ui.show_add_table = true,
|
|
||||||
AppView::AddLogic => app_state.ui.show_add_logic = true,
|
|
||||||
AppView::Form => app_state.ui.show_form = true,
|
|
||||||
AppView::Scratch => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle table change for FormView
|
|
||||||
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();
|
|
||||||
|
|
||||||
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()) {
|
|
||||||
app_state.show_loading_dialog("Loading Table", &format!("Fetching data for {}.{}...", prof_name, tbl_name));
|
|
||||||
needs_redraw = true;
|
|
||||||
|
|
||||||
match grpc_client.get_table_structure(prof_name.clone(), tbl_name.clone()).await {
|
|
||||||
Ok(structure_response) => {
|
|
||||||
let new_columns: Vec<String> = structure_response.columns.iter().map(|c| c.name.clone()).collect();
|
|
||||||
form_state = FormState::new(prof_name.clone(), tbl_name.clone(), new_columns);
|
|
||||||
|
|
||||||
if let Err(e) = UiService::fetch_and_set_table_count(&mut grpc_client, &mut form_state).await {
|
|
||||||
app_state.update_dialog_content(&format!("Error fetching count: {}", e), vec!["OK".to_string()], DialogPurpose::LoginFailed);
|
|
||||||
} else {
|
|
||||||
if form_state.total_count > 0 {
|
|
||||||
if let Err(e) = UiService::load_table_data_by_position(&mut grpc_client, &mut 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 {
|
|
||||||
form_state.reset_to_empty();
|
|
||||||
app_state.hide_dialog();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
app_state.update_dialog_content(&format!("Error fetching table structure: {}", e), vec!["OK".to_string()], DialogPurpose::LoginFailed);
|
|
||||||
app_state.current_view_profile_name = prev_view_profile_name.clone();
|
|
||||||
app_state.current_view_table_name = prev_view_table_name.clone();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
prev_view_profile_name = current_view_profile;
|
|
||||||
prev_view_table_name = current_view_table;
|
|
||||||
needs_redraw = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 &&
|
|
||||||
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,
|
|
||||||
&app_state.profile_tree,
|
|
||||||
).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);
|
|
||||||
} else {
|
|
||||||
event_handler.command_message = fetch_message;
|
|
||||||
}
|
|
||||||
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
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
warn!(
|
|
||||||
"Pending table structure fetch for {}.{} but AddLogic view is not active. Fetch ignored.",
|
|
||||||
profile_name, table_name
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if needs_redraw {
|
|
||||||
terminal.draw(|f| {
|
|
||||||
render_ui(
|
|
||||||
f,
|
|
||||||
&mut form_state,
|
|
||||||
&mut auth_state,
|
|
||||||
&login_state,
|
|
||||||
®ister_state,
|
|
||||||
&intro_state,
|
|
||||||
&mut admin_state,
|
|
||||||
&buffer_state,
|
|
||||||
&theme,
|
|
||||||
event_handler.is_edit_mode,
|
|
||||||
&event_handler.highlight_state,
|
|
||||||
&event_handler.command_input,
|
|
||||||
event_handler.command_mode,
|
|
||||||
&event_handler.command_message,
|
|
||||||
&event_handler.navigation_state,
|
|
||||||
&app_state.current_dir,
|
|
||||||
current_fps,
|
|
||||||
&app_state,
|
|
||||||
);
|
|
||||||
}).context("Terminal draw call failed")?;
|
|
||||||
needs_redraw = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(table_name) = admin_state.add_logic_state.script_editor_awaiting_column_autocomplete.clone() {
|
|
||||||
if app_state.ui.show_add_logic {
|
|
||||||
let profile_name = admin_state.add_logic_state.profile_name.clone();
|
|
||||||
|
|
||||||
info!("Fetching columns for table selection: {}.{}", profile_name, table_name);
|
|
||||||
match UiService::fetch_columns_for_table(&mut grpc_client, &profile_name, &table_name).await {
|
|
||||||
Ok(columns) => {
|
|
||||||
admin_state.add_logic_state.set_columns_for_table_autocomplete(columns.clone());
|
|
||||||
info!("Loaded {} columns for table '{}'", columns.len(), table_name);
|
|
||||||
event_handler.command_message = format!("Columns for '{}' loaded. Select a column.", table_name);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
error!("Failed to fetch columns for {}.{}: {}", profile_name, table_name, e);
|
|
||||||
admin_state.add_logic_state.script_editor_awaiting_column_autocomplete = None;
|
|
||||||
admin_state.add_logic_state.deactivate_script_editor_autocomplete();
|
|
||||||
event_handler.command_message = format!("Error loading columns for '{}': {}", table_name, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
needs_redraw = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let current_mode = ModeManager::derive_mode(&app_state, &event_handler, &admin_state);
|
|
||||||
match current_mode {
|
|
||||||
AppMode::Edit => { terminal.show_cursor()?; }
|
|
||||||
AppMode::Highlight => { terminal.set_cursor_style(SetCursorStyle::SteadyBlock)?; terminal.show_cursor()?; }
|
|
||||||
AppMode::ReadOnly => {
|
|
||||||
if !app_state.ui.focus_outside_canvas { terminal.set_cursor_style(SetCursorStyle::SteadyBlock)?; }
|
|
||||||
else { terminal.set_cursor_style(SetCursorStyle::SteadyUnderScore)?; }
|
|
||||||
terminal.show_cursor().context("Failed to show cursor in ReadOnly mode")?;
|
|
||||||
}
|
|
||||||
AppMode::General => {
|
|
||||||
if app_state.ui.focus_outside_canvas { terminal.set_cursor_style(SetCursorStyle::SteadyUnderScore)?; terminal.show_cursor()?; }
|
|
||||||
else { terminal.hide_cursor()?; }
|
|
||||||
}
|
|
||||||
AppMode::Command => { terminal.set_cursor_style(SetCursorStyle::SteadyUnderScore)?; terminal.show_cursor().context("Failed to show cursor in Command mode")?; }
|
|
||||||
}
|
|
||||||
|
|
||||||
let position_before_event = form_state.current_position;
|
let position_before_event = form_state.current_position;
|
||||||
|
|
||||||
if app_state.ui.dialog.is_loading {
|
|
||||||
needs_redraw = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut event_outcome_result = Ok(EventOutcome::Ok(String::new()));
|
|
||||||
let mut event_processed = false;
|
let mut event_processed = false;
|
||||||
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;
|
||||||
event_outcome_result = event_handler.handle_event(
|
let event_outcome_result = event_handler.handle_event(
|
||||||
event,
|
event,
|
||||||
&config,
|
&config,
|
||||||
&mut terminal,
|
&mut terminal,
|
||||||
@@ -348,12 +155,57 @@ pub async fn run_ui() -> Result<()> {
|
|||||||
&mut buffer_state,
|
&mut buffer_state,
|
||||||
&mut app_state,
|
&mut app_state,
|
||||||
).await;
|
).await;
|
||||||
|
|
||||||
|
// --- Handle the outcome of the event ---
|
||||||
|
let mut should_exit = false;
|
||||||
|
match event_outcome_result {
|
||||||
|
Ok(outcome) => match outcome {
|
||||||
|
EventOutcome::Ok(message) => {
|
||||||
|
if !message.is_empty() {
|
||||||
|
event_handler.command_message = message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EventOutcome::Exit(message) => {
|
||||||
|
event_handler.command_message = message;
|
||||||
|
should_exit = true;
|
||||||
|
}
|
||||||
|
EventOutcome::DataSaved(save_outcome, message) => {
|
||||||
|
event_handler.command_message = message;
|
||||||
|
if let Err(e) = UiService::handle_save_outcome(
|
||||||
|
save_outcome,
|
||||||
|
&mut grpc_client,
|
||||||
|
&mut app_state,
|
||||||
|
&mut form_state,
|
||||||
|
).await {
|
||||||
|
event_handler.command_message =
|
||||||
|
format!("Error handling save outcome: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EventOutcome::ButtonSelected { .. } => {}
|
||||||
|
EventOutcome::TableSelected { path } => {
|
||||||
|
let parts: Vec<&str> = path.split('/').collect();
|
||||||
|
if parts.len() == 2 {
|
||||||
|
let profile_name = parts[0].to_string();
|
||||||
|
let table_name = parts[1].to_string();
|
||||||
|
|
||||||
|
app_state.set_current_view_table(profile_name, table_name);
|
||||||
|
buffer_state.update_history(AppView::Form);
|
||||||
|
event_handler.command_message = format!("Loading table: {}", path);
|
||||||
|
} else {
|
||||||
|
event_handler.command_message = format!("Invalid table path: {}", path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
event_handler.command_message = format!("Error: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if should_exit {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if event_processed {
|
// --- Handle async results from other tasks ---
|
||||||
needs_redraw = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
match login_result_receiver.try_recv() {
|
match login_result_receiver.try_recv() {
|
||||||
Ok(result) => {
|
Ok(result) => {
|
||||||
if login::handle_login_result(result, &mut app_state, &mut auth_state, &mut login_state) {
|
if login::handle_login_result(result, &mut app_state, &mut auth_state, &mut login_state) {
|
||||||
@@ -403,62 +255,182 @@ pub async fn run_ui() -> Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut should_exit = false;
|
// ===================================================================
|
||||||
match event_outcome_result {
|
// STATE-DEPENDENT LOGIC (NOW RUNS AFTER STATE IS UPDATED)
|
||||||
Ok(outcome) => match outcome {
|
// ===================================================================
|
||||||
EventOutcome::Ok(_message) => {}
|
|
||||||
EventOutcome::Exit(message) => {
|
// --- Determine which view is active ---
|
||||||
event_handler.command_message = message;
|
if let Some(active_view) = buffer_state.get_active_view() {
|
||||||
should_exit = true;
|
app_state.ui.show_intro = false;
|
||||||
}
|
app_state.ui.show_login = false;
|
||||||
EventOutcome::DataSaved(save_outcome, message) => {
|
app_state.ui.show_register = false;
|
||||||
event_handler.command_message = message;
|
app_state.ui.show_admin = false;
|
||||||
if let Err(e) = UiService::handle_save_outcome(
|
app_state.ui.show_add_table = false;
|
||||||
save_outcome,
|
app_state.ui.show_add_logic = false;
|
||||||
&mut grpc_client,
|
app_state.ui.show_form = false;
|
||||||
&mut app_state,
|
match active_view {
|
||||||
&mut form_state,
|
AppView::Intro => app_state.ui.show_intro = true,
|
||||||
)
|
AppView::Login => app_state.ui.show_login = true,
|
||||||
.await
|
AppView::Register => app_state.ui.show_register = true,
|
||||||
{
|
AppView::Admin => {
|
||||||
event_handler.command_message =
|
info!("Active view is Admin, refreshing profile tree...");
|
||||||
format!("Error handling save outcome: {}", e);
|
match grpc_client.get_profile_tree().await {
|
||||||
|
Ok(refreshed_tree) => {
|
||||||
|
app_state.profile_tree = refreshed_tree;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to refresh profile tree for Admin panel: {}", e);
|
||||||
|
event_handler.command_message = format!("Error refreshing admin data: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
app_state.ui.show_admin = true;
|
||||||
|
let profile_names = app_state.profile_tree.profiles.iter()
|
||||||
|
.map(|p| p.name.clone())
|
||||||
|
.collect();
|
||||||
|
admin_state.set_profiles(profile_names);
|
||||||
|
|
||||||
|
if admin_state.current_focus == AdminFocus::default() ||
|
||||||
|
!matches!(admin_state.current_focus,
|
||||||
|
AdminFocus::InsideProfilesList |
|
||||||
|
AdminFocus::Tables | AdminFocus::InsideTablesList |
|
||||||
|
AdminFocus::Button1 | AdminFocus::Button2 | AdminFocus::Button3) {
|
||||||
|
admin_state.current_focus = AdminFocus::ProfilesPane;
|
||||||
|
}
|
||||||
|
if admin_state.profile_list_state.selected().is_none() && !app_state.profile_tree.profiles.is_empty() {
|
||||||
|
admin_state.profile_list_state.select(Some(0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EventOutcome::ButtonSelected { context: _, index: _ } => {}
|
AppView::AddTable => app_state.ui.show_add_table = true,
|
||||||
},
|
AppView::AddLogic => app_state.ui.show_add_logic = true,
|
||||||
Err(e) => {
|
AppView::Form => app_state.ui.show_form = true,
|
||||||
event_handler.command_message = format!("Error: {}", e);
|
AppView::Scratch => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- MODIFIED: Position Change Handling (operates on form_state) ---
|
// --- Handle table change for FormView ---
|
||||||
|
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();
|
||||||
|
|
||||||
|
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()) {
|
||||||
|
app_state.show_loading_dialog("Loading Table", &format!("Fetching data for {}.{}...", prof_name, tbl_name));
|
||||||
|
needs_redraw = true;
|
||||||
|
|
||||||
|
match grpc_client.get_table_structure(prof_name.clone(), tbl_name.clone()).await {
|
||||||
|
Ok(structure_response) => {
|
||||||
|
let new_columns: Vec<String> = structure_response.columns.iter().map(|c| c.name.clone()).collect();
|
||||||
|
form_state = FormState::new(prof_name.clone(), tbl_name.clone(), new_columns);
|
||||||
|
|
||||||
|
if let Err(e) = UiService::fetch_and_set_table_count(&mut grpc_client, &mut form_state).await {
|
||||||
|
app_state.update_dialog_content(&format!("Error fetching count: {}", e), vec!["OK".to_string()], DialogPurpose::LoginFailed);
|
||||||
|
} else {
|
||||||
|
if form_state.total_count > 0 {
|
||||||
|
if let Err(e) = UiService::load_table_data_by_position(&mut grpc_client, &mut 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 {
|
||||||
|
form_state.reset_to_empty();
|
||||||
|
app_state.hide_dialog();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
app_state.update_dialog_content(&format!("Error fetching table structure: {}", e), vec!["OK".to_string()], DialogPurpose::LoginFailed);
|
||||||
|
app_state.current_view_profile_name = prev_view_profile_name.clone();
|
||||||
|
app_state.current_view_table_name = prev_view_table_name.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prev_view_profile_name = current_view_profile;
|
||||||
|
prev_view_table_name = current_view_table;
|
||||||
|
needs_redraw = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Handle pending async fetches ---
|
||||||
|
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 &&
|
||||||
|
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,
|
||||||
|
&app_state.profile_tree,
|
||||||
|
).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);
|
||||||
|
} else {
|
||||||
|
event_handler.command_message = fetch_message;
|
||||||
|
}
|
||||||
|
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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
warn!(
|
||||||
|
"Pending table structure fetch for {}.{} but AddLogic view is not active. Fetch ignored.",
|
||||||
|
profile_name, table_name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(table_name) = admin_state.add_logic_state.script_editor_awaiting_column_autocomplete.clone() {
|
||||||
|
if app_state.ui.show_add_logic {
|
||||||
|
let profile_name = admin_state.add_logic_state.profile_name.clone();
|
||||||
|
|
||||||
|
info!("Fetching columns for table selection: {}.{}", profile_name, table_name);
|
||||||
|
match UiService::fetch_columns_for_table(&mut grpc_client, &profile_name, &table_name).await {
|
||||||
|
Ok(columns) => {
|
||||||
|
admin_state.add_logic_state.set_columns_for_table_autocomplete(columns.clone());
|
||||||
|
info!("Loaded {} columns for table '{}'", columns.len(), table_name);
|
||||||
|
event_handler.command_message = format!("Columns for '{}' loaded. Select a column.", table_name);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to fetch columns for {}.{}: {}", profile_name, table_name, e);
|
||||||
|
admin_state.add_logic_state.script_editor_awaiting_column_autocomplete = None;
|
||||||
|
admin_state.add_logic_state.deactivate_script_editor_autocomplete();
|
||||||
|
event_handler.command_message = format!("Error loading columns for '{}': {}", table_name, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
needs_redraw = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Handle cursor updates based on position changes ---
|
||||||
let position_changed = form_state.current_position != position_before_event;
|
let position_changed = form_state.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 { // Only if the form is active
|
if app_state.ui.show_form {
|
||||||
if position_changed && !event_handler.is_edit_mode {
|
if position_changed && !event_handler.is_edit_mode {
|
||||||
// This part is okay: update cursor for the current field BEFORE loading new data
|
|
||||||
let current_input_before_load = form_state.get_current_input();
|
let current_input_before_load = form_state.get_current_input();
|
||||||
let max_cursor_pos_before_load = if !current_input_before_load.is_empty() { current_input_before_load.chars().count() } else { 0 };
|
let max_cursor_pos_before_load = if !current_input_before_load.is_empty() { current_input_before_load.chars().count() } else { 0 };
|
||||||
form_state.current_cursor_pos = event_handler.ideal_cursor_column.min(max_cursor_pos_before_load);
|
form_state.current_cursor_pos = event_handler.ideal_cursor_column.min(max_cursor_pos_before_load);
|
||||||
position_logic_needs_redraw = true;
|
position_logic_needs_redraw = true;
|
||||||
|
|
||||||
// Validate new form_state.current_position
|
|
||||||
if form_state.total_count > 0 && form_state.current_position > form_state.total_count + 1 {
|
if form_state.total_count > 0 && form_state.current_position > form_state.total_count + 1 {
|
||||||
form_state.current_position = form_state.total_count + 1; // Cap at new entry
|
form_state.current_position = form_state.total_count + 1;
|
||||||
} else if form_state.total_count == 0 && form_state.current_position > 1 {
|
} else if form_state.total_count == 0 && form_state.current_position > 1 {
|
||||||
form_state.current_position = 1; // Cap at new entry for empty table
|
form_state.current_position = 1;
|
||||||
}
|
}
|
||||||
if form_state.current_position == 0 && form_state.total_count > 0 {
|
if form_state.current_position == 0 && form_state.total_count > 0 {
|
||||||
form_state.current_position = 1; // Don't allow 0 if there are records
|
form_state.current_position = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Load data for the new position OR reset for new entry
|
|
||||||
if (form_state.total_count > 0 && form_state.current_position <= form_state.total_count && form_state.current_position > 0)
|
if (form_state.total_count > 0 && form_state.current_position <= form_state.total_count && form_state.current_position > 0)
|
||||||
{
|
{
|
||||||
// It's an existing record position
|
|
||||||
match UiService::load_table_data_by_position(&mut grpc_client, &mut form_state).await {
|
match UiService::load_table_data_by_position(&mut grpc_client, &mut form_state).await {
|
||||||
Ok(load_message) => {
|
Ok(load_message) => {
|
||||||
if event_handler.command_message.is_empty() || !load_message.starts_with("Error") {
|
if event_handler.command_message.is_empty() || !load_message.starts_with("Error") {
|
||||||
@@ -467,16 +439,13 @@ pub async fn run_ui() -> Result<()> {
|
|||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
event_handler.command_message = format!("Error loading data: {}", e);
|
event_handler.command_message = format!("Error loading data: {}", e);
|
||||||
// Consider what to do with form_state here - maybe revert position or clear form
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Position indicates a new entry (or table is empty and position is 1)
|
form_state.reset_to_empty();
|
||||||
form_state.reset_to_empty(); // This sets id=0, clears values, and sets current_position correctly
|
|
||||||
event_handler.command_message = format!("New entry for {}.{}", form_state.profile_name, form_state.table_name);
|
event_handler.command_message = format!("New entry for {}.{}", form_state.profile_name, form_state.table_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOW, after data is loaded or form is reset, get the current input string and its length
|
|
||||||
let current_input_after_load_str = form_state.get_current_input();
|
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 current_input_len_after_load = current_input_after_load_str.chars().count();
|
||||||
|
|
||||||
@@ -490,11 +459,9 @@ pub async fn run_ui() -> Result<()> {
|
|||||||
form_state.current_cursor_pos = event_handler.ideal_cursor_column.min(current_input_len_after_load);
|
form_state.current_cursor_pos = event_handler.ideal_cursor_column.min(current_input_len_after_load);
|
||||||
} else {
|
} else {
|
||||||
form_state.current_cursor_pos = event_handler.ideal_cursor_column.min(max_cursor_pos_for_readonly_after_load);
|
form_state.current_cursor_pos = event_handler.ideal_cursor_column.min(max_cursor_pos_for_readonly_after_load);
|
||||||
// The check for empty string is implicitly handled by max_cursor_pos_for_readonly_after_load being 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if !position_changed && !event_handler.is_edit_mode && app_state.ui.show_form {
|
} else if !position_changed && !event_handler.is_edit_mode && app_state.ui.show_form {
|
||||||
// Update cursor if not editing and position didn't change (e.g. arrow keys within field)
|
|
||||||
let current_input_str = form_state.get_current_input();
|
let current_input_str = form_state.get_current_input();
|
||||||
let current_input_len = current_input_str.chars().count();
|
let current_input_len = current_input_str.chars().count();
|
||||||
let max_cursor_pos = if current_input_len > 0 {
|
let max_cursor_pos = if current_input_len > 0 {
|
||||||
@@ -522,10 +489,56 @@ pub async fn run_ui() -> Result<()> {
|
|||||||
needs_redraw = true;
|
needs_redraw = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if should_exit {
|
if app_state.ui.dialog.is_loading {
|
||||||
return Ok(());
|
needs_redraw = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===================================================================
|
||||||
|
// STEP 3: DRAW THE UI
|
||||||
|
// ===================================================================
|
||||||
|
if event_processed || needs_redraw || position_changed {
|
||||||
|
let current_mode = ModeManager::derive_mode(&app_state, &event_handler, &admin_state);
|
||||||
|
match current_mode {
|
||||||
|
AppMode::Edit => { terminal.show_cursor()?; }
|
||||||
|
AppMode::Highlight => { terminal.set_cursor_style(SetCursorStyle::SteadyBlock)?; terminal.show_cursor()?; }
|
||||||
|
AppMode::ReadOnly => {
|
||||||
|
if !app_state.ui.focus_outside_canvas { terminal.set_cursor_style(SetCursorStyle::SteadyBlock)?; }
|
||||||
|
else { terminal.set_cursor_style(SetCursorStyle::SteadyUnderScore)?; }
|
||||||
|
terminal.show_cursor().context("Failed to show cursor in ReadOnly mode")?;
|
||||||
|
}
|
||||||
|
AppMode::General => {
|
||||||
|
if app_state.ui.focus_outside_canvas { terminal.set_cursor_style(SetCursorStyle::SteadyUnderScore)?; terminal.show_cursor()?; }
|
||||||
|
else { terminal.hide_cursor()?; }
|
||||||
|
}
|
||||||
|
AppMode::Command => { terminal.set_cursor_style(SetCursorStyle::SteadyUnderScore)?; terminal.show_cursor().context("Failed to show cursor in Command mode")?; }
|
||||||
|
}
|
||||||
|
|
||||||
|
terminal.draw(|f| {
|
||||||
|
render_ui(
|
||||||
|
f,
|
||||||
|
&mut form_state,
|
||||||
|
&mut auth_state,
|
||||||
|
&login_state,
|
||||||
|
®ister_state,
|
||||||
|
&intro_state,
|
||||||
|
&mut admin_state,
|
||||||
|
&buffer_state,
|
||||||
|
&theme,
|
||||||
|
event_handler.is_edit_mode,
|
||||||
|
&event_handler.highlight_state,
|
||||||
|
&event_handler.command_input,
|
||||||
|
event_handler.command_mode,
|
||||||
|
&event_handler.command_message,
|
||||||
|
&event_handler.navigation_state,
|
||||||
|
&app_state.current_dir,
|
||||||
|
current_fps,
|
||||||
|
&app_state,
|
||||||
|
);
|
||||||
|
}).context("Terminal draw call failed")?;
|
||||||
|
needs_redraw = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- FPS calculation ---
|
||||||
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;
|
||||||
|
|||||||
Reference in New Issue
Block a user