post doesnt work, but refactored code displays the autocomplete at least, needs fix
This commit is contained in:
@@ -1,30 +1,18 @@
|
|||||||
// src/components/common/autocomplete.rs
|
// src/components/common/autocomplete.rs
|
||||||
|
|
||||||
use common::proto::multieko2::search::search_response::Hit;
|
|
||||||
use crate::config::colors::themes::Theme;
|
use crate::config::colors::themes::Theme;
|
||||||
|
use crate::state::pages::form::FormState;
|
||||||
|
use common::proto::multieko2::search::search_response::Hit;
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
layout::Rect,
|
layout::Rect,
|
||||||
style::{Color, Modifier, Style},
|
style::{Color, Modifier, Style},
|
||||||
widgets::{Block, List, ListItem, ListState},
|
widgets::{Block, List, ListItem, ListState},
|
||||||
Frame,
|
Frame,
|
||||||
};
|
};
|
||||||
use std::collections::HashMap;
|
|
||||||
use unicode_width::UnicodeWidthStr;
|
use unicode_width::UnicodeWidthStr;
|
||||||
|
|
||||||
/// Converts a serde_json::Value into a displayable String.
|
|
||||||
/// Handles String, Number, and Bool variants. Returns an empty string for Null and others.
|
|
||||||
fn json_value_to_string(value: &serde_json::Value) -> String {
|
|
||||||
match value {
|
|
||||||
serde_json::Value::String(s) => s.clone(),
|
|
||||||
serde_json::Value::Number(n) => n.to_string(),
|
|
||||||
serde_json::Value::Bool(b) => b.to_string(),
|
|
||||||
// Return an empty string for Null, Array, or Object so we can filter them out.
|
|
||||||
_ => String::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Renders an opaque dropdown list for simple string-based suggestions.
|
/// Renders an opaque dropdown list for simple string-based suggestions.
|
||||||
/// This function remains unchanged.
|
/// THIS IS THE RESTORED FUNCTION.
|
||||||
pub fn render_autocomplete_dropdown(
|
pub fn render_autocomplete_dropdown(
|
||||||
f: &mut Frame,
|
f: &mut Frame,
|
||||||
input_rect: Rect,
|
input_rect: Rect,
|
||||||
@@ -84,22 +72,22 @@ pub fn render_autocomplete_dropdown(
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let list = List::new(items);
|
let list = List::new(items);
|
||||||
let mut profile_list_state = ListState::default();
|
let mut list_state = ListState::default();
|
||||||
profile_list_state.select(selected_index);
|
list_state.select(selected_index);
|
||||||
|
|
||||||
f.render_stateful_widget(list, dropdown_area, &mut profile_list_state);
|
f.render_stateful_widget(list, dropdown_area, &mut list_state);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- MODIFIED FUNCTION FOR RICH SUGGESTIONS ---
|
|
||||||
/// Renders an opaque dropdown list for rich `Hit`-based suggestions.
|
/// Renders an opaque dropdown list for rich `Hit`-based suggestions.
|
||||||
/// Displays the value of the first meaningful column, followed by the Hit ID.
|
/// RENAMED from render_rich_autocomplete_dropdown
|
||||||
pub fn render_rich_autocomplete_dropdown(
|
pub fn render_hit_autocomplete_dropdown(
|
||||||
f: &mut Frame,
|
f: &mut Frame,
|
||||||
input_rect: Rect,
|
input_rect: Rect,
|
||||||
frame_area: Rect,
|
frame_area: Rect,
|
||||||
theme: &Theme,
|
theme: &Theme,
|
||||||
suggestions: &[Hit],
|
suggestions: &[Hit],
|
||||||
selected_index: Option<usize>,
|
selected_index: Option<usize>,
|
||||||
|
form_state: &FormState,
|
||||||
) {
|
) {
|
||||||
if suggestions.is_empty() {
|
if suggestions.is_empty() {
|
||||||
return;
|
return;
|
||||||
@@ -107,50 +95,9 @@ pub fn render_rich_autocomplete_dropdown(
|
|||||||
|
|
||||||
let display_names: Vec<String> = suggestions
|
let display_names: Vec<String> = suggestions
|
||||||
.iter()
|
.iter()
|
||||||
.map(|hit| {
|
.map(|hit| form_state.get_display_name_for_hit(hit))
|
||||||
// Use serde_json::Value to handle mixed types (string, null, etc.)
|
|
||||||
if let Ok(content_map) =
|
|
||||||
serde_json::from_str::<HashMap<String, serde_json::Value>>(
|
|
||||||
&hit.content_json,
|
|
||||||
)
|
|
||||||
{
|
|
||||||
// Define keys to ignore for a cleaner display
|
|
||||||
const IGNORED_KEYS: &[&str] = &["id", "deleted", "created_at"];
|
|
||||||
|
|
||||||
// Get keys, filter out ignored ones, and sort for consistency
|
|
||||||
let mut keys: Vec<_> = content_map
|
|
||||||
.keys()
|
|
||||||
.filter(|k| !IGNORED_KEYS.contains(&k.as_str()))
|
|
||||||
.cloned()
|
|
||||||
.collect();
|
|
||||||
keys.sort();
|
|
||||||
|
|
||||||
// Get only the first non-empty value from the sorted keys
|
|
||||||
let values: Vec<_> = keys
|
|
||||||
.iter()
|
|
||||||
.map(|key| {
|
|
||||||
content_map
|
|
||||||
.get(key)
|
|
||||||
.map(json_value_to_string)
|
|
||||||
.unwrap_or_default()
|
|
||||||
})
|
|
||||||
.filter(|s| !s.is_empty()) // Filter out null/empty values
|
|
||||||
.take(1) // Changed from take(2) to take(1)
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let display_part = values.first().cloned().unwrap_or_default(); // Get the first value
|
|
||||||
if display_part.is_empty() {
|
|
||||||
format!("{}", hit.id)
|
|
||||||
} else {
|
|
||||||
format!("{} | {}", display_part, hit.id) // ID at the end
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
format!("{} (parse error)", hit.id)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// --- Calculate Dropdown Size & Position ---
|
|
||||||
let max_suggestion_width =
|
let max_suggestion_width =
|
||||||
display_names.iter().map(|s| s.width()).max().unwrap_or(0) as u16;
|
display_names.iter().map(|s| s.width()).max().unwrap_or(0) as u16;
|
||||||
let horizontal_padding: u16 = 2;
|
let horizontal_padding: u16 = 2;
|
||||||
@@ -164,7 +111,6 @@ pub fn render_rich_autocomplete_dropdown(
|
|||||||
height: dropdown_height,
|
height: dropdown_height,
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- Clamping Logic ---
|
|
||||||
if dropdown_area.bottom() > frame_area.height {
|
if dropdown_area.bottom() > frame_area.height {
|
||||||
dropdown_area.y = input_rect.y.saturating_sub(dropdown_height);
|
dropdown_area.y = input_rect.y.saturating_sub(dropdown_height);
|
||||||
}
|
}
|
||||||
@@ -174,7 +120,6 @@ pub fn render_rich_autocomplete_dropdown(
|
|||||||
dropdown_area.x = dropdown_area.x.max(0);
|
dropdown_area.x = dropdown_area.x.max(0);
|
||||||
dropdown_area.y = dropdown_area.y.max(0);
|
dropdown_area.y = dropdown_area.y.max(0);
|
||||||
|
|
||||||
// --- Rendering Logic ---
|
|
||||||
let background_block =
|
let background_block =
|
||||||
Block::default().style(Style::default().bg(Color::DarkGray));
|
Block::default().style(Style::default().bg(Color::DarkGray));
|
||||||
f.render_widget(background_block, dropdown_area);
|
f.render_widget(background_block, dropdown_area);
|
||||||
|
|||||||
@@ -78,25 +78,25 @@ pub fn render_form(
|
|||||||
|
|
||||||
// --- NEW: RENDER AUTOCOMPLETE ---
|
// --- NEW: RENDER AUTOCOMPLETE ---
|
||||||
if form_state.autocomplete_active {
|
if form_state.autocomplete_active {
|
||||||
// Use the Rect of the active field that render_canvas found for us.
|
|
||||||
if let Some(active_rect) = active_field_rect {
|
if let Some(active_rect) = active_field_rect {
|
||||||
let selected_index = form_state.get_selected_suggestion_index();
|
let selected_index = form_state.get_selected_suggestion_index();
|
||||||
|
|
||||||
// THE DECIDER LOGIC:
|
|
||||||
// 1. Check for rich suggestions first.
|
|
||||||
if let Some(rich_suggestions) = form_state.get_rich_suggestions() {
|
if let Some(rich_suggestions) = form_state.get_rich_suggestions() {
|
||||||
if !rich_suggestions.is_empty() {
|
if !rich_suggestions.is_empty() {
|
||||||
autocomplete::render_rich_autocomplete_dropdown(
|
// CHANGE THIS to call the renamed function
|
||||||
|
autocomplete::render_hit_autocomplete_dropdown(
|
||||||
f,
|
f,
|
||||||
active_rect,
|
active_rect,
|
||||||
f.area(), // Use f.area() for clamping, not f.size()
|
f.area(),
|
||||||
theme,
|
theme,
|
||||||
rich_suggestions,
|
rich_suggestions,
|
||||||
selected_index,
|
selected_index,
|
||||||
|
form_state,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 2. Fallback to simple suggestions if rich ones aren't available.
|
// The fallback to simple suggestions is now correctly handled
|
||||||
|
// because the original render_autocomplete_dropdown exists again.
|
||||||
else if let Some(simple_suggestions) = form_state.get_suggestions() {
|
else if let Some(simple_suggestions) = form_state.get_suggestions() {
|
||||||
if !simple_suggestions.is_empty() {
|
if !simple_suggestions.is_empty() {
|
||||||
autocomplete::render_autocomplete_dropdown(
|
autocomplete::render_autocomplete_dropdown(
|
||||||
@@ -112,3 +112,4 @@ pub fn render_form(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,100 @@
|
|||||||
// src/services/ui_service.rs
|
// src/services/ui_service.rs
|
||||||
|
|
||||||
use crate::services::grpc_client::GrpcClient;
|
use crate::services::grpc_client::GrpcClient;
|
||||||
use crate::state::pages::form::FormState;
|
|
||||||
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 crate::state::pages::add_logic::AddLogicState;
|
||||||
|
use crate::state::pages::form::{FieldDefinition, FormState};
|
||||||
|
use crate::tui::functions::common::form::SaveOutcome;
|
||||||
use crate::utils::columns::filter_user_columns;
|
use crate::utils::columns::filter_user_columns;
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub struct UiService;
|
pub struct UiService;
|
||||||
|
|
||||||
impl UiService {
|
impl UiService {
|
||||||
|
pub async fn load_table_view(
|
||||||
|
grpc_client: &mut GrpcClient,
|
||||||
|
app_state: &mut AppState,
|
||||||
|
profile_name: &str,
|
||||||
|
table_name: &str,
|
||||||
|
) -> Result<FormState> {
|
||||||
|
// 1. & 2. Fetch and Cache Schema - UNCHANGED
|
||||||
|
let table_structure = grpc_client
|
||||||
|
.get_table_structure(profile_name.to_string(), table_name.to_string())
|
||||||
|
.await
|
||||||
|
.context(format!(
|
||||||
|
"Failed to get table structure for {}.{}",
|
||||||
|
profile_name, table_name
|
||||||
|
))?;
|
||||||
|
let cache_key = format!("{}.{}", profile_name, table_name);
|
||||||
|
app_state
|
||||||
|
.schema_cache
|
||||||
|
.insert(cache_key, Arc::new(table_structure.clone()));
|
||||||
|
tracing::info!("Schema for '{}.{}' cached.", profile_name, table_name);
|
||||||
|
|
||||||
|
// --- START: FINAL, SIMPLIFIED, CORRECT LOGIC ---
|
||||||
|
|
||||||
|
// 3a. Create definitions for REGULAR fields first.
|
||||||
|
let mut fields: Vec<FieldDefinition> = table_structure
|
||||||
|
.columns
|
||||||
|
.iter()
|
||||||
|
.filter(|col| {
|
||||||
|
!col.is_primary_key
|
||||||
|
&& col.name != "deleted"
|
||||||
|
&& col.name != "created_at"
|
||||||
|
&& !col.name.ends_with("_id") // Filter out ALL potential links
|
||||||
|
})
|
||||||
|
.map(|col| FieldDefinition {
|
||||||
|
display_name: col.name.clone(),
|
||||||
|
data_key: col.name.clone(),
|
||||||
|
is_link: false,
|
||||||
|
link_target_table: None,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// 3b. Now, find and APPEND definitions for LINK fields based on the `_id` convention.
|
||||||
|
let link_fields: Vec<FieldDefinition> = table_structure
|
||||||
|
.columns
|
||||||
|
.iter()
|
||||||
|
.filter(|col| col.name.ends_with("_id")) // Find all foreign key columns
|
||||||
|
.map(|col| {
|
||||||
|
// The table we link to is derived from the column name.
|
||||||
|
// e.g., "test_diacritics_id" -> "test_diacritics"
|
||||||
|
let target_table_base = col
|
||||||
|
.name
|
||||||
|
.strip_suffix("_id")
|
||||||
|
.unwrap_or(&col.name);
|
||||||
|
|
||||||
|
// Find the full table name from the profile tree for display.
|
||||||
|
// e.g., "test_diacritics" -> "2025_test_diacritics"
|
||||||
|
let full_target_table_name = app_state
|
||||||
|
.profile_tree
|
||||||
|
.profiles
|
||||||
|
.iter()
|
||||||
|
.find(|p| p.name == profile_name)
|
||||||
|
.and_then(|p| p.tables.iter().find(|t| t.name.ends_with(target_table_base)))
|
||||||
|
.map_or(target_table_base.to_string(), |t| t.name.clone());
|
||||||
|
|
||||||
|
FieldDefinition {
|
||||||
|
display_name: full_target_table_name.clone(),
|
||||||
|
data_key: col.name.clone(), // The actual FK column name
|
||||||
|
is_link: true,
|
||||||
|
link_target_table: Some(full_target_table_name),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
fields.extend(link_fields); // Append the link fields to the end
|
||||||
|
|
||||||
|
// --- END: FINAL, SIMPLIFIED, CORRECT LOGIC ---
|
||||||
|
|
||||||
|
Ok(FormState::new(
|
||||||
|
profile_name.to_string(),
|
||||||
|
table_name.to_string(),
|
||||||
|
fields,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn initialize_add_logic_table_data(
|
pub async fn initialize_add_logic_table_data(
|
||||||
grpc_client: &mut GrpcClient,
|
grpc_client: &mut GrpcClient,
|
||||||
add_logic_state: &mut AddLogicState,
|
add_logic_state: &mut AddLogicState,
|
||||||
@@ -93,6 +176,7 @@ impl UiService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// REFACTOR THIS FUNCTION
|
||||||
pub async fn initialize_app_state_and_form(
|
pub async fn initialize_app_state_and_form(
|
||||||
grpc_client: &mut GrpcClient,
|
grpc_client: &mut GrpcClient,
|
||||||
app_state: &mut AppState,
|
app_state: &mut AppState,
|
||||||
@@ -122,35 +206,19 @@ impl UiService {
|
|||||||
initial_table_name.clone(),
|
initial_table_name.clone(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let table_structure = grpc_client
|
// NOW, just call our new central function. This avoids code duplication.
|
||||||
.get_table_structure(
|
let form_state = Self::load_table_view(
|
||||||
initial_profile_name.clone(),
|
grpc_client,
|
||||||
initial_table_name.clone(),
|
app_state,
|
||||||
)
|
&initial_profile_name,
|
||||||
.await
|
&initial_table_name,
|
||||||
.context(format!(
|
)
|
||||||
"Failed to get initial table structure for {}.{}",
|
.await?;
|
||||||
initial_profile_name, initial_table_name
|
|
||||||
))?;
|
|
||||||
|
|
||||||
// NEW: Populate the "Rulebook" cache
|
// The field names for the UI are derived from the new form_state
|
||||||
let cache_key = format!(
|
let field_names = form_state.fields.iter().map(|f| f.display_name.clone()).collect();
|
||||||
"{}.{}",
|
|
||||||
initial_profile_name, initial_table_name
|
|
||||||
);
|
|
||||||
app_state
|
|
||||||
.schema_cache
|
|
||||||
.insert(cache_key, Arc::new(table_structure.clone()));
|
|
||||||
|
|
||||||
let column_names: Vec<String> = table_structure
|
Ok((initial_profile_name, initial_table_name, field_names))
|
||||||
.columns
|
|
||||||
.iter()
|
|
||||||
.map(|col| col.name.clone())
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let filtered_columns = filter_user_columns(column_names);
|
|
||||||
|
|
||||||
Ok((initial_profile_name, initial_table_name, filtered_columns))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn fetch_and_set_table_count(
|
pub async fn fetch_and_set_table_count(
|
||||||
|
|||||||
@@ -350,123 +350,91 @@ pub async fn run_ui() -> Result<()> {
|
|||||||
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();
|
||||||
|
|
||||||
|
// This condition correctly detects a table switch.
|
||||||
if prev_view_profile_name != current_view_profile
|
if prev_view_profile_name != current_view_profile
|
||||||
|| prev_view_table_name != current_view_table
|
|| prev_view_table_name != current_view_table
|
||||||
{
|
{
|
||||||
if let (Some(prof_name), Some(tbl_name)) =
|
if let (Some(prof_name), Some(tbl_name)) =
|
||||||
(current_view_profile.as_ref(), current_view_table.as_ref())
|
(current_view_profile.as_ref(), current_view_table.as_ref())
|
||||||
{
|
{
|
||||||
|
// --- START OF REFACTORED LOGIC ---
|
||||||
app_state.show_loading_dialog(
|
app_state.show_loading_dialog(
|
||||||
"Loading Table",
|
"Loading Table",
|
||||||
&format!("Fetching data for {}.{}...", prof_name, tbl_name),
|
&format!("Fetching data for {}.{}...", prof_name, tbl_name),
|
||||||
);
|
);
|
||||||
needs_redraw = true;
|
needs_redraw = true;
|
||||||
|
|
||||||
match grpc_client
|
// 1. Call our new, central function. It handles fetching AND caching.
|
||||||
.get_table_structure(prof_name.clone(), tbl_name.clone())
|
match UiService::load_table_view(
|
||||||
.await
|
&mut grpc_client,
|
||||||
|
&mut app_state,
|
||||||
|
prof_name,
|
||||||
|
tbl_name,
|
||||||
|
)
|
||||||
|
.await
|
||||||
{
|
{
|
||||||
Ok(structure_response) => {
|
Ok(mut new_form_state) => {
|
||||||
// --- START OF MODIFIED LOGIC ---
|
// 2. The function succeeded, we have a new FormState.
|
||||||
let all_columns: Vec<String> = structure_response
|
// Now, fetch its data.
|
||||||
.columns
|
|
||||||
.iter()
|
|
||||||
.map(|c| c.name.clone())
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let mut field_definitions: Vec<FieldDefinition> =
|
|
||||||
filter_user_columns(all_columns)
|
|
||||||
.into_iter()
|
|
||||||
.filter(|col_name| !col_name.ends_with("_id"))
|
|
||||||
.map(|col_name| FieldDefinition {
|
|
||||||
display_name: col_name.clone(),
|
|
||||||
data_key: col_name,
|
|
||||||
is_link: false,
|
|
||||||
link_target_table: None, // Regular fields have no target
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let linked_tables: Vec<String> = app_state
|
|
||||||
.profile_tree
|
|
||||||
.profiles
|
|
||||||
.iter()
|
|
||||||
.find(|p| p.name == *prof_name)
|
|
||||||
.and_then(|profile| {
|
|
||||||
profile.tables.iter().find(|t| t.name == *tbl_name)
|
|
||||||
})
|
|
||||||
.map_or(vec![], |table| table.depends_on.clone());
|
|
||||||
|
|
||||||
for linked_table_name in linked_tables {
|
|
||||||
let base_name = linked_table_name
|
|
||||||
.split_once('_')
|
|
||||||
.map_or(linked_table_name.as_str(), |(_, rest)| rest);
|
|
||||||
let data_key = format!("{}_id", base_name);
|
|
||||||
let display_name = linked_table_name.clone(); // Clone for use below
|
|
||||||
|
|
||||||
field_definitions.push(FieldDefinition {
|
|
||||||
display_name,
|
|
||||||
data_key,
|
|
||||||
is_link: true,
|
|
||||||
// --- POPULATE THE NEW FIELD ---
|
|
||||||
link_target_table: Some(linked_table_name),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// --- END OF MODIFIED LOGIC ---
|
|
||||||
|
|
||||||
form_state = FormState::new(
|
|
||||||
prof_name.clone(),
|
|
||||||
tbl_name.clone(),
|
|
||||||
field_definitions, // This now contains the complete definitions
|
|
||||||
);
|
|
||||||
|
|
||||||
if let Err(e) = UiService::fetch_and_set_table_count(
|
if let Err(e) = UiService::fetch_and_set_table_count(
|
||||||
&mut grpc_client,
|
&mut grpc_client,
|
||||||
&mut form_state,
|
&mut new_form_state,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
|
// Handle count fetching error
|
||||||
app_state.update_dialog_content(
|
app_state.update_dialog_content(
|
||||||
&format!("Error fetching count: {}", e),
|
&format!("Error fetching count: {}", e),
|
||||||
vec!["OK".to_string()],
|
vec!["OK".to_string()],
|
||||||
DialogPurpose::LoginFailed,
|
DialogPurpose::LoginFailed, // Or a more appropriate purpose
|
||||||
);
|
);
|
||||||
} else if form_state.total_count > 0 {
|
} 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(
|
if let Err(e) = UiService::load_table_data_by_position(
|
||||||
&mut grpc_client,
|
&mut grpc_client,
|
||||||
&mut form_state,
|
&mut new_form_state,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
|
// Handle data loading error
|
||||||
app_state.update_dialog_content(
|
app_state.update_dialog_content(
|
||||||
&format!("Error loading data: {}", e),
|
&format!("Error loading data: {}", e),
|
||||||
vec!["OK".to_string()],
|
vec!["OK".to_string()],
|
||||||
DialogPurpose::LoginFailed,
|
DialogPurpose::LoginFailed, // Or a more appropriate purpose
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
// Success! Hide the loading dialog.
|
||||||
app_state.hide_dialog();
|
app_state.hide_dialog();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
form_state.reset_to_empty();
|
// No records, so just reset to an empty form.
|
||||||
|
new_form_state.reset_to_empty();
|
||||||
app_state.hide_dialog();
|
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_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;
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
// This handles errors from load_table_view (e.g., schema fetch failed)
|
||||||
app_state.update_dialog_content(
|
app_state.update_dialog_content(
|
||||||
&format!("Error fetching table structure: {}", e),
|
&format!("Error loading table: {}", e),
|
||||||
vec!["OK".to_string()],
|
vec!["OK".to_string()],
|
||||||
DialogPurpose::LoginFailed,
|
DialogPurpose::LoginFailed, // Or a more appropriate purpose
|
||||||
);
|
);
|
||||||
|
// Revert the view change in app_state to avoid a loop
|
||||||
app_state.current_view_profile_name =
|
app_state.current_view_profile_name =
|
||||||
prev_view_profile_name.clone();
|
prev_view_profile_name.clone();
|
||||||
app_state.current_view_table_name =
|
app_state.current_view_table_name =
|
||||||
prev_view_table_name.clone();
|
prev_view_table_name.clone();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// --- END OF REFACTORED LOGIC ---
|
||||||
}
|
}
|
||||||
needs_redraw = true;
|
needs_redraw = true;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user