the search tui is not working yet
This commit is contained in:
@@ -48,6 +48,7 @@ use crossterm::event::KeyCode;
|
||||
use crossterm::event::{Event, KeyEvent};
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::sync::mpsc::unbounded_channel;
|
||||
use tracing::{info, error};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum EventOutcome {
|
||||
@@ -122,11 +123,11 @@ impl EventHandler {
|
||||
self.navigation_state.activate_find_file(options);
|
||||
}
|
||||
|
||||
// REFACTORED: This function now safely handles state changes.
|
||||
// This function handles state changes.
|
||||
async fn handle_search_palette_event(
|
||||
&mut self,
|
||||
key_event: KeyEvent,
|
||||
grpc_client: &mut GrpcClient,
|
||||
mut grpc_client: GrpcClient,
|
||||
form_state: &mut FormState,
|
||||
app_state: &mut AppState,
|
||||
) -> Result<EventOutcome> {
|
||||
@@ -175,6 +176,7 @@ impl EventHandler {
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// --- START CORRECTED LOGIC ---
|
||||
if trigger_search {
|
||||
search_state.is_loading = true;
|
||||
search_state.results.clear();
|
||||
@@ -182,14 +184,19 @@ impl EventHandler {
|
||||
|
||||
let query = search_state.input.clone();
|
||||
let table_name = search_state.table_name.clone();
|
||||
let mut client = grpc_client.clone();
|
||||
let sender = self.search_result_sender.clone();
|
||||
|
||||
// We now move the grpc_client into the task, just like with login.
|
||||
tokio::spawn(async move {
|
||||
if let Ok(response) = client.search_table(table_name, query).await {
|
||||
let _ = sender.send(response.hits);
|
||||
} else {
|
||||
let _ = sender.send(vec![]);
|
||||
match grpc_client.search_table(table_name, query).await {
|
||||
Ok(response) => {
|
||||
info!("Search successful. Received {} hits.", response.hits.len());
|
||||
let _ = sender.send(response.hits);
|
||||
}
|
||||
Err(e) => {
|
||||
error!("gRPC search call failed: {}", e);
|
||||
let _ = sender.send(vec![]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -232,7 +239,7 @@ impl EventHandler {
|
||||
|
||||
if app_state.ui.show_search_palette {
|
||||
if let Event::Key(key_event) = event {
|
||||
return self.handle_search_palette_event(key_event, grpc_client, form_state, app_state).await;
|
||||
return self.handle_search_palette_event(key_event, grpc_client.clone(), form_state, app_state).await;
|
||||
}
|
||||
return Ok(EventOutcome::Ok(String::new()));
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// src/state/canvas_state.rs
|
||||
|
||||
|
||||
pub trait CanvasState {
|
||||
fn current_field(&self) -> usize;
|
||||
fn current_cursor_pos(&self) -> usize;
|
||||
|
||||
@@ -1,23 +1,19 @@
|
||||
// src/state/pages/form.rs
|
||||
|
||||
use std::collections::HashMap; // NEW
|
||||
use std::collections::HashMap;
|
||||
use crate::config::colors::themes::Theme;
|
||||
use ratatui::layout::Rect;
|
||||
use ratatui::Frame;
|
||||
use crate::state::app::highlight::HighlightState;
|
||||
use crate::state::pages::canvas_state::CanvasState;
|
||||
use crate::state::app::state::AppState;
|
||||
use crate::components::common::search_palette::render_search_palette;
|
||||
|
||||
pub struct FormState {
|
||||
pub id: i64,
|
||||
// NEW fields for dynamic table context
|
||||
pub profile_name: String,
|
||||
pub table_name: String,
|
||||
pub total_count: u64,
|
||||
pub current_position: u64, // 1-based index, 0 or total_count + 1 for new entry
|
||||
|
||||
pub fields: Vec<String>, // Already dynamic, which is good
|
||||
pub current_position: u64,
|
||||
pub fields: Vec<String>,
|
||||
pub values: Vec<String>,
|
||||
pub current_field: usize,
|
||||
pub has_unsaved_changes: bool,
|
||||
@@ -25,9 +21,6 @@ pub struct FormState {
|
||||
}
|
||||
|
||||
impl FormState {
|
||||
/// Creates a new, empty FormState for a given table.
|
||||
/// The position defaults to 1, representing either the first record
|
||||
/// or the position for a new entry if the table is empty.
|
||||
pub fn new(
|
||||
profile_name: String,
|
||||
table_name: String,
|
||||
@@ -35,11 +28,10 @@ impl FormState {
|
||||
) -> Self {
|
||||
let values = vec![String::new(); fields.len()];
|
||||
FormState {
|
||||
id: 0, // Default to 0, indicating a new or unloaded record
|
||||
id: 0,
|
||||
profile_name,
|
||||
table_name,
|
||||
total_count: 0, // Will be fetched after initialization
|
||||
// FIX: Default to 1. A position of 0 is an invalid state.
|
||||
total_count: 0,
|
||||
current_position: 1,
|
||||
fields,
|
||||
values,
|
||||
@@ -49,6 +41,7 @@ impl FormState {
|
||||
}
|
||||
}
|
||||
|
||||
// This signature is now correct and only deals with form-related state.
|
||||
pub fn render(
|
||||
&self,
|
||||
f: &mut Frame,
|
||||
@@ -56,7 +49,6 @@ impl FormState {
|
||||
theme: &Theme,
|
||||
is_edit_mode: bool,
|
||||
highlight_state: &HighlightState,
|
||||
app_state: &AppState,
|
||||
) {
|
||||
let fields_str_slice: Vec<&str> =
|
||||
self.fields.iter().map(|s| s.as_str()).collect();
|
||||
@@ -65,7 +57,7 @@ impl FormState {
|
||||
crate::components::form::form::render_form(
|
||||
f,
|
||||
area,
|
||||
self, // Pass self as CanvasState
|
||||
self,
|
||||
&fields_str_slice,
|
||||
&self.current_field,
|
||||
&values_str_slice,
|
||||
@@ -76,27 +68,19 @@ impl FormState {
|
||||
self.total_count,
|
||||
self.current_position,
|
||||
);
|
||||
|
||||
if app_state.ui.show_search_palette {
|
||||
if let Some(search_state) = &app_state.search_state {
|
||||
render_search_palette(f, f.size(), theme, search_state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Resets the form to a state for creating a new entry.
|
||||
/// It clears all values and sets the position to be one after the last record.
|
||||
// ... other methods are unchanged ...
|
||||
pub fn reset_to_empty(&mut self) {
|
||||
self.id = 0;
|
||||
self.values.iter_mut().for_each(|v| v.clear());
|
||||
self.current_field = 0;
|
||||
self.current_cursor_pos = 0;
|
||||
self.has_unsaved_changes = false;
|
||||
// Set the position for a new entry.
|
||||
if self.total_count > 0 {
|
||||
self.current_position = self.total_count + 1;
|
||||
} else {
|
||||
self.current_position = 1; // If table is empty, new record is at position 1
|
||||
self.current_position = 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,26 +97,19 @@ impl FormState {
|
||||
.expect("Invalid current_field index")
|
||||
}
|
||||
|
||||
/// Updates the form's values from a data response and sets its position.
|
||||
/// This is the single source of truth for populating the form after a data fetch.
|
||||
pub fn update_from_response(
|
||||
&mut self,
|
||||
response_data: &HashMap<String, String>,
|
||||
// FIX: Add new_position to make this method authoritative.
|
||||
new_position: u64,
|
||||
) {
|
||||
// Create a new vector for the values, ensuring they are in the correct order.
|
||||
self.values = self.fields.iter().map(|field_from_schema| {
|
||||
// For each field from our schema, find the corresponding key in the
|
||||
// response data by doing a case-insensitive comparison.
|
||||
response_data
|
||||
.iter()
|
||||
.find(|(key_from_data, _)| key_from_data.eq_ignore_ascii_case(field_from_schema))
|
||||
.map(|(_, value)| value.clone()) // If found, clone its value.
|
||||
.unwrap_or_default() // If not found, use an empty string.
|
||||
.map(|(_, value)| value.clone())
|
||||
.unwrap_or_default()
|
||||
}).collect();
|
||||
|
||||
// Now, do the same case-insensitive lookup for the 'id' field.
|
||||
let id_str_opt = response_data
|
||||
.iter()
|
||||
.find(|(k, _)| k.eq_ignore_ascii_case("id"))
|
||||
@@ -149,7 +126,6 @@ impl FormState {
|
||||
self.id = 0;
|
||||
}
|
||||
|
||||
// FIX: Set the position from the provided parameter.
|
||||
self.current_position = new_position;
|
||||
self.has_unsaved_changes = false;
|
||||
self.current_field = 0;
|
||||
@@ -175,12 +151,10 @@ impl CanvasState for FormState {
|
||||
}
|
||||
|
||||
fn get_current_input(&self) -> &str {
|
||||
// Re-use the struct's own method
|
||||
FormState::get_current_input(self)
|
||||
}
|
||||
|
||||
fn get_current_input_mut(&mut self) -> &mut String {
|
||||
// Re-use the struct's own method
|
||||
FormState::get_current_input_mut(self)
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::components::{
|
||||
admin::add_logic::render_add_logic,
|
||||
admin::render_add_table,
|
||||
auth::{login::render_login, register::render_register},
|
||||
common::dialog::render_dialog, // Make sure this is imported
|
||||
common::dialog::render_dialog,
|
||||
common::find_file_palette,
|
||||
common::search_palette::render_search_palette,
|
||||
form::form::render_form,
|
||||
@@ -17,6 +17,7 @@ use crate::components::{
|
||||
};
|
||||
use crate::config::colors::themes::Theme;
|
||||
use crate::modes::general::command_navigation::NavigationState;
|
||||
use crate::state::pages::canvas_state::CanvasState;
|
||||
use crate::state::app::buffer::BufferState;
|
||||
use crate::state::app::highlight::HighlightState;
|
||||
use crate::state::app::state::AppState;
|
||||
@@ -24,7 +25,6 @@ use crate::state::pages::admin::AdminState;
|
||||
use crate::state::pages::auth::AuthState;
|
||||
use crate::state::pages::auth::LoginState;
|
||||
use crate::state::pages::auth::RegisterState;
|
||||
use crate::state::pages::canvas_state::CanvasState;
|
||||
use crate::state::pages::form::FormState;
|
||||
use crate::state::pages::intro::IntroState;
|
||||
use ratatui::{
|
||||
@@ -189,17 +189,13 @@ pub fn render_ui(
|
||||
.split(form_actual_area)[1]
|
||||
};
|
||||
|
||||
// --- FIX START ---
|
||||
// The call to `form_state.render` is now separate from the popup rendering.
|
||||
form_state.render(
|
||||
f,
|
||||
form_render_area,
|
||||
theme,
|
||||
is_event_handler_edit_mode,
|
||||
highlight_state,
|
||||
app_state,
|
||||
);
|
||||
// --- FIX END ---
|
||||
}
|
||||
|
||||
if let Some(area) = buffer_list_area {
|
||||
@@ -236,8 +232,7 @@ pub fn render_ui(
|
||||
}
|
||||
}
|
||||
|
||||
// --- FIX START ---
|
||||
// This block now handles drawing popups over any view. This is the correct place for it.
|
||||
// This block now correctly handles drawing popups over any view.
|
||||
if app_state.ui.show_search_palette {
|
||||
if let Some(search_state) = &app_state.search_state {
|
||||
render_search_palette(f, f.area(), theme, search_state);
|
||||
@@ -254,5 +249,4 @@ pub fn render_ui(
|
||||
app_state.ui.dialog.is_loading,
|
||||
);
|
||||
}
|
||||
// --- FIX END ---
|
||||
}
|
||||
|
||||
@@ -126,6 +126,24 @@ pub async fn run_ui() -> Result<()> {
|
||||
loop {
|
||||
let position_before_event = form_state.current_position;
|
||||
let mut event_processed = false;
|
||||
|
||||
match event_handler.search_result_receiver.try_recv() {
|
||||
Ok(hits) => {
|
||||
if let Some(search_state) = app_state.search_state.as_mut() {
|
||||
search_state.results = hits;
|
||||
search_state.is_loading = false;
|
||||
}
|
||||
needs_redraw = true;
|
||||
}
|
||||
Err(mpsc::error::TryRecvError::Empty) => {
|
||||
}
|
||||
Err(mpsc::error::TryRecvError::Disconnected) => {
|
||||
error!("Search result channel disconnected!");
|
||||
}
|
||||
}
|
||||
if app_state.ui.show_search_palette {
|
||||
needs_redraw = true;
|
||||
}
|
||||
if crossterm_event::poll(std::time::Duration::from_millis(1))? {
|
||||
let event = event_reader.read_event().context("Failed to read terminal event")?;
|
||||
event_processed = true;
|
||||
|
||||
Reference in New Issue
Block a user