search added, but unable to trigger it yet

This commit is contained in:
filipriec
2025-06-11 16:24:42 +02:00
parent afd9228efa
commit 2af79a3ef2
10 changed files with 365 additions and 48 deletions

View File

@@ -17,6 +17,7 @@ toggle_buffer_list = ["ctrl+b"]
next_field = ["Tab"] next_field = ["Tab"]
prev_field = ["Shift+Tab"] prev_field = ["Shift+Tab"]
exit_table_scroll = ["esc"] exit_table_scroll = ["esc"]
open_search = ["ctrl+f"]
[keybindings.common] [keybindings.common]
save = ["ctrl+s"] save = ["ctrl+s"]

View File

@@ -5,6 +5,7 @@ pub mod text_editor;
pub mod background; pub mod background;
pub mod dialog; pub mod dialog;
pub mod autocomplete; pub mod autocomplete;
pub mod search_palette;
pub mod find_file_palette; pub mod find_file_palette;
pub use command_line::*; pub use command_line::*;
@@ -13,4 +14,5 @@ pub use text_editor::*;
pub use background::*; pub use background::*;
pub use dialog::*; pub use dialog::*;
pub use autocomplete::*; pub use autocomplete::*;
pub use search_palette::*;
pub use find_file_palette::*; pub use find_file_palette::*;

View File

@@ -0,0 +1,121 @@
// src/components/common/search_palette.rs
use crate::config::colors::themes::Theme;
use crate::state::app::search::SearchState;
use ratatui::{
layout::{Constraint, Direction, Layout, Rect},
style::{Modifier, Style},
text::{Line, Span},
widgets::{Block, Borders, Clear, List, ListItem, Paragraph},
Frame,
};
/// Renders the search palette dialog over the main UI.
pub fn render_search_palette(
f: &mut Frame,
area: Rect,
theme: &Theme,
state: &SearchState,
) {
// --- Dialog Area Calculation ---
let height = (area.height as f32 * 0.7).min(30.0) as u16;
let width = (area.width as f32 * 0.6).min(100.0) as u16;
let dialog_area = Rect {
x: area.x + (area.width - width) / 2,
y: area.y + (area.height - height) / 4,
width,
height,
};
f.render_widget(Clear, dialog_area); // Clear background
let block = Block::default()
.title(format!(" Search in '{}' ", state.table_name))
.borders(Borders::ALL)
.border_style(Style::default().fg(theme.accent));
f.render_widget(block.clone(), dialog_area);
// --- Inner Layout (Input + Results) ---
let inner_chunks = Layout::default()
.direction(Direction::Vertical)
.margin(1)
.constraints([
Constraint::Length(3), // For input box
Constraint::Min(0), // For results list
])
.split(dialog_area);
// --- Render Input Box ---
let input_block = Block::default()
.title("Query")
.borders(Borders::ALL)
.border_style(Style::default().fg(theme.border));
let input_text = Paragraph::new(state.input.as_str())
.block(input_block)
.style(Style::default().fg(theme.fg));
f.render_widget(input_text, inner_chunks[0]);
// Set cursor position
f.set_cursor(
inner_chunks[0].x + state.cursor_position as u16 + 1,
inner_chunks[0].y + 1,
);
// --- Render Results List ---
if state.is_loading {
let loading_p = Paragraph::new("Searching...")
.style(Style::default().fg(theme.fg).add_modifier(Modifier::ITALIC));
f.render_widget(loading_p, inner_chunks[1]);
} else {
let list_items: Vec<ListItem> = state
.results
.iter()
.map(|hit| {
// Parse the JSON string to make it readable
let content_summary = match serde_json::from_str::<
serde_json::Value,
>(&hit.content_json)
{
Ok(json) => {
if let Some(obj) = json.as_object() {
// Create a summary from the first few non-null string values
obj.values()
.filter_map(|v| v.as_str())
.filter(|s| !s.is_empty())
.take(3)
.collect::<Vec<_>>()
.join(" | ")
} else {
"Non-object JSON".to_string()
}
}
Err(_) => "Invalid JSON content".to_string(),
};
let line = Line::from(vec![
Span::styled(
format!("{:<4.2} ", hit.score),
Style::default().fg(theme.accent),
),
Span::raw(content_summary),
]);
ListItem::new(line)
})
.collect();
let results_list = List::new(list_items)
.block(Block::default().title("Results"))
.highlight_style(
Style::default()
.bg(theme.highlight)
.fg(theme.bg)
.add_modifier(Modifier::BOLD),
)
.highlight_symbol(">> ");
// We need a mutable ListState to render the selection
let mut list_state =
ratatui::widgets::ListState::default().with_selected(Some(state.selected_index));
f.render_stateful_widget(results_list, inner_chunks[1], &mut list_state);
}
}

View File

@@ -21,6 +21,7 @@ use crate::state::{
app::{ app::{
buffer::{AppView, BufferState}, buffer::{AppView, BufferState},
highlight::HighlightState, highlight::HighlightState,
search::SearchState, // Correctly imported
state::AppState, state::AppState,
}, },
pages::{ pages::{
@@ -41,10 +42,12 @@ use crate::tui::{
use crate::ui::handlers::context::UiContext; use crate::ui::handlers::context::UiContext;
use crate::ui::handlers::rat_state::UiStateHandler; use crate::ui::handlers::rat_state::UiStateHandler;
use anyhow::Result; use anyhow::Result;
use common::proto::multieko2::search::search_response::Hit; // Correctly imported
use crossterm::cursor::SetCursorStyle; use crossterm::cursor::SetCursorStyle;
use crossterm::event::KeyCode; use crossterm::event::KeyCode;
use crossterm::event::{Event, KeyEvent}; use crossterm::event::{Event, KeyEvent};
use tokio::sync::mpsc; use tokio::sync::mpsc;
use tokio::sync::mpsc::unbounded_channel;
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum EventOutcome { pub enum EventOutcome {
@@ -79,6 +82,8 @@ pub struct EventHandler {
pub save_table_result_sender: SaveTableResultSender, pub save_table_result_sender: SaveTableResultSender,
pub save_logic_result_sender: SaveLogicResultSender, pub save_logic_result_sender: SaveLogicResultSender,
pub navigation_state: NavigationState, pub navigation_state: NavigationState,
pub search_result_sender: mpsc::UnboundedSender<Vec<Hit>>,
pub search_result_receiver: mpsc::UnboundedReceiver<Vec<Hit>>,
} }
impl EventHandler { impl EventHandler {
@@ -88,6 +93,7 @@ impl EventHandler {
save_table_result_sender: SaveTableResultSender, save_table_result_sender: SaveTableResultSender,
save_logic_result_sender: SaveLogicResultSender, save_logic_result_sender: SaveLogicResultSender,
) -> Result<Self> { ) -> Result<Self> {
let (search_tx, search_rx) = unbounded_channel();
Ok(EventHandler { Ok(EventHandler {
command_mode: false, command_mode: false,
command_input: String::new(), command_input: String::new(),
@@ -103,6 +109,8 @@ impl EventHandler {
save_table_result_sender, save_table_result_sender,
save_logic_result_sender, save_logic_result_sender,
navigation_state: NavigationState::new(), navigation_state: NavigationState::new(),
search_result_sender: search_tx,
search_result_receiver: search_rx,
}) })
} }
@@ -114,6 +122,90 @@ impl EventHandler {
self.navigation_state.activate_find_file(options); self.navigation_state.activate_find_file(options);
} }
// REFACTORED: This function now safely handles state changes.
async fn handle_search_palette_event(
&mut self,
key_event: KeyEvent,
grpc_client: &mut GrpcClient,
form_state: &mut FormState,
app_state: &mut AppState,
) -> Result<EventOutcome> {
let mut should_close = false;
let mut outcome_message = String::new();
let mut trigger_search = false;
if let Some(search_state) = app_state.search_state.as_mut() {
match key_event.code {
KeyCode::Esc => {
should_close = true;
outcome_message = "Search cancelled".to_string();
}
KeyCode::Enter => {
if let Some(selected_hit) = search_state.results.get(search_state.selected_index) {
if let Ok(data) = serde_json::from_str::<std::collections::HashMap<String, String>>(&selected_hit.content_json) {
let detached_pos = form_state.total_count + 2;
form_state.update_from_response(&data, detached_pos);
}
should_close = true;
outcome_message = format!("Loaded record ID {}", selected_hit.id);
}
}
KeyCode::Up => search_state.previous_result(),
KeyCode::Down => search_state.next_result(),
KeyCode::Char(c) => {
search_state.input.insert(search_state.cursor_position, c);
search_state.cursor_position += 1;
trigger_search = true;
}
KeyCode::Backspace => {
if search_state.cursor_position > 0 {
search_state.cursor_position -= 1;
search_state.input.remove(search_state.cursor_position);
trigger_search = true;
}
}
KeyCode::Left => {
search_state.cursor_position = search_state.cursor_position.saturating_sub(1);
}
KeyCode::Right => {
if search_state.cursor_position < search_state.input.len() {
search_state.cursor_position += 1;
}
}
_ => {}
}
if trigger_search {
search_state.is_loading = true;
search_state.results.clear();
search_state.selected_index = 0;
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();
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![]);
}
});
}
}
// The borrow on `app_state.search_state` ends here.
// Now we can safely modify the Option itself.
if should_close {
app_state.search_state = None;
app_state.ui.show_search_palette = false;
app_state.ui.focus_outside_canvas = false;
}
Ok(EventOutcome::Ok(outcome_message))
}
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub async fn handle_event( pub async fn handle_event(
&mut self, &mut self,
@@ -131,6 +223,20 @@ impl EventHandler {
buffer_state: &mut BufferState, buffer_state: &mut BufferState,
app_state: &mut AppState, app_state: &mut AppState,
) -> Result<EventOutcome> { ) -> Result<EventOutcome> {
if let Ok(hits) = self.search_result_receiver.try_recv() {
if let Some(search_state) = app_state.search_state.as_mut() {
search_state.results = hits;
search_state.is_loading = false;
}
}
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 Ok(EventOutcome::Ok(String::new()));
}
let mut current_mode = ModeManager::derive_mode(app_state, self, admin_state); let mut current_mode = ModeManager::derive_mode(app_state, self, admin_state);
if current_mode == AppMode::General && self.navigation_state.active { if current_mode == AppMode::General && self.navigation_state.active {
@@ -212,6 +318,19 @@ impl EventHandler {
_ => {} _ => {}
} }
} }
if let Some(action) = config.get_general_action(key_code, modifiers) {
if action == "open_search" {
if app_state.ui.show_form {
if let Some(table_name) = app_state.current_view_table_name.clone() {
app_state.ui.show_search_palette = true;
app_state.search_state = Some(SearchState::new(table_name));
app_state.ui.focus_outside_canvas = true;
return Ok(EventOutcome::Ok("Search palette opened".to_string()));
}
}
}
}
} }
match current_mode { match current_mode {
@@ -348,7 +467,6 @@ impl EventHandler {
&mut admin_state.add_table_state, &mut admin_state.add_table_state,
&mut admin_state.add_logic_state, &mut admin_state.add_logic_state,
&mut self.key_sequence_tracker, &mut self.key_sequence_tracker,
// No more current_position or total_count arguments
grpc_client, grpc_client,
&mut self.command_message, &mut self.command_message,
&mut self.edit_mode_cooldown, &mut self.edit_mode_cooldown,
@@ -477,7 +595,6 @@ impl EventHandler {
if config.matches_key_sequence_generalized(&sequence) == Some("find_file_palette_toggle") { if config.matches_key_sequence_generalized(&sequence) == Some("find_file_palette_toggle") {
if app_state.ui.show_form || app_state.ui.show_intro { if app_state.ui.show_form || app_state.ui.show_intro {
// --- START FIX ---
let mut all_table_paths: Vec<String> = app_state let mut all_table_paths: Vec<String> = app_state
.profile_tree .profile_tree
.profiles .profiles
@@ -491,7 +608,6 @@ impl EventHandler {
all_table_paths.sort(); all_table_paths.sort();
self.navigation_state.activate_find_file(all_table_paths); self.navigation_state.activate_find_file(all_table_paths);
// --- END FIX ---
self.command_mode = false; self.command_mode = false;
self.command_input.clear(); self.command_input.clear();

View File

@@ -20,6 +20,9 @@ use common::proto::multieko2::tables_data::{
PostTableDataRequest, PostTableDataResponse, PutTableDataRequest, PostTableDataRequest, PostTableDataResponse, PutTableDataRequest,
PutTableDataResponse, PutTableDataResponse,
}; };
use common::proto::multieko2::search::{
searcher_client::SearcherClient, SearchRequest, SearchResponse,
};
use anyhow::{Context, Result}; // Added Context use anyhow::{Context, Result}; // Added Context
use std::collections::HashMap; // NEW use std::collections::HashMap; // NEW
@@ -28,36 +31,32 @@ pub struct GrpcClient {
table_structure_client: TableStructureServiceClient<Channel>, table_structure_client: TableStructureServiceClient<Channel>,
table_definition_client: TableDefinitionClient<Channel>, table_definition_client: TableDefinitionClient<Channel>,
table_script_client: TableScriptClient<Channel>, table_script_client: TableScriptClient<Channel>,
tables_data_client: TablesDataClient<Channel>, // NEW tables_data_client: TablesDataClient<Channel>,
search_client: SearcherClient<Channel>,
} }
impl GrpcClient { impl GrpcClient {
pub async fn new() -> Result<Self> { pub async fn new() -> Result<Self> {
let table_structure_client = TableStructureServiceClient::connect( let channel = Channel::from_static("http://[::1]:50051")
"http://[::1]:50051", .connect()
) .await
.await .context("Failed to create gRPC channel")?;
.context("Failed to connect to TableStructureService")?;
let table_definition_client = TableDefinitionClient::connect( let table_structure_client =
"http://[::1]:50051", TableStructureServiceClient::new(channel.clone());
) let table_definition_client =
.await TableDefinitionClient::new(channel.clone());
.context("Failed to connect to TableDefinitionService")?; let table_script_client = TableScriptClient::new(channel.clone());
let table_script_client = let tables_data_client = TablesDataClient::new(channel.clone());
TableScriptClient::connect("http://[::1]:50051") // NEW: Instantiate the search client
.await let search_client = SearcherClient::new(channel.clone());
.context("Failed to connect to TableScriptService")?;
let tables_data_client =
TablesDataClient::connect("http://[::1]:50051")
.await
.context("Failed to connect to TablesDataService")?; // NEW
Ok(Self { Ok(Self {
// adresar_client, // REMOVE
table_structure_client, table_structure_client,
table_definition_client, table_definition_client,
table_script_client, table_script_client,
tables_data_client, // NEW tables_data_client,
search_client, // NEW
}) })
} }
@@ -197,4 +196,18 @@ impl GrpcClient {
.context("gRPC PutTableData call failed")?; .context("gRPC PutTableData call failed")?;
Ok(response.into_inner()) Ok(response.into_inner())
} }
pub async fn search_table(
&mut self,
table_name: String,
query: String,
) -> Result<SearchResponse> {
let request = tonic::Request::new(SearchRequest { table_name, query });
let response = self
.search_client
.search_table(request)
.await
.context("gRPC SearchTable call failed")?;
Ok(response.into_inner())
}
} }

View File

@@ -2,4 +2,5 @@
pub mod state; pub mod state;
pub mod buffer; pub mod buffer;
pub mod search;
pub mod highlight; pub mod highlight;

View File

@@ -0,0 +1,56 @@
// src/state/app/search.rs
use common::proto::multieko2::search::search_response::Hit;
/// Holds the complete state for the search palette.
pub struct SearchState {
/// The name of the table being searched.
pub table_name: String,
/// The current text entered by the user.
pub input: String,
/// The position of the cursor within the input text.
pub cursor_position: usize,
/// The search results returned from the server.
pub results: Vec<Hit>,
/// The index of the currently selected search result.
pub selected_index: usize,
/// A flag to indicate if a search is currently in progress.
pub is_loading: bool,
}
impl SearchState {
/// Creates a new SearchState for a given table.
pub fn new(table_name: String) -> Self {
Self {
table_name,
input: String::new(),
cursor_position: 0,
results: Vec::new(),
selected_index: 0,
is_loading: false,
}
}
/// Moves the selection to the next item, wrapping around if at the end.
pub fn next_result(&mut self) {
if !self.results.is_empty() {
let next = self.selected_index + 1;
self.selected_index = if next >= self.results.len() {
0 // Wrap to the start
} else {
next
};
}
}
/// Moves the selection to the previous item, wrapping around if at the beginning.
pub fn previous_result(&mut self) {
if !self.results.is_empty() {
self.selected_index = if self.selected_index == 0 {
self.results.len() - 1 // Wrap to the end
} else {
self.selected_index - 1
};
}
}
}

View File

@@ -1,11 +1,13 @@
// src/state/state.rs // src/state/app/state.rs
use std::env; use std::env;
use common::proto::multieko2::table_definition::ProfileTreeResponse; use common::proto::multieko2::table_definition::ProfileTreeResponse;
use crate::modes::handlers::mode_manager::AppMode; use crate::modes::handlers::mode_manager::AppMode;
use crate::ui::handlers::context::DialogPurpose; use crate::ui::handlers::context::DialogPurpose;
use crate::state::app::search::SearchState; // ADDED
use anyhow::Result; use anyhow::Result;
// --- YOUR EXISTING DIALOGSTATE IS UNTOUCHED ---
pub struct DialogState { pub struct DialogState {
pub dialog_show: bool, pub dialog_show: bool,
pub dialog_title: String, pub dialog_title: String,
@@ -26,6 +28,7 @@ pub struct UiState {
pub show_form: bool, pub show_form: bool,
pub show_login: bool, pub show_login: bool,
pub show_register: bool, pub show_register: bool,
pub show_search_palette: bool, // ADDED
pub focus_outside_canvas: bool, pub focus_outside_canvas: bool,
pub dialog: DialogState, pub dialog: DialogState,
} }
@@ -42,6 +45,9 @@ pub struct AppState {
pub focused_button_index: usize, pub focused_button_index: usize,
pub pending_table_structure_fetch: Option<(String, String)>, pub pending_table_structure_fetch: Option<(String, String)>,
// ADDED: State for the search palette
pub search_state: Option<SearchState>,
// UI preferences // UI preferences
pub ui: UiState, pub ui: UiState,
@@ -63,6 +69,7 @@ impl AppState {
current_mode: AppMode::General, current_mode: AppMode::General,
focused_button_index: 0, focused_button_index: 0,
pending_table_structure_fetch: None, pending_table_structure_fetch: None,
search_state: None, // ADDED
ui: UiState::default(), ui: UiState::default(),
#[cfg(feature = "ui-debug")] #[cfg(feature = "ui-debug")]
@@ -70,18 +77,17 @@ impl AppState {
}) })
} }
// --- ALL YOUR EXISTING METHODS ARE UNTOUCHED ---
pub fn update_mode(&mut self, mode: AppMode) { pub fn update_mode(&mut self, mode: AppMode) {
self.current_mode = mode; self.current_mode = mode;
} }
pub fn set_current_view_table(&mut self, profile_name: String, table_name: String) { pub fn set_current_view_table(&mut self, profile_name: String, table_name: String) {
self.current_view_profile_name = Some(profile_name); self.current_view_profile_name = Some(profile_name);
self.current_view_table_name = Some(table_name); self.current_view_table_name = Some(table_name);
} }
// Add dialog helper methods
/// Shows a dialog with the given title, message, and buttons.
/// The first button (index 0) is active by default.
pub fn show_dialog( pub fn show_dialog(
&mut self, &mut self,
title: &str, title: &str,
@@ -99,19 +105,17 @@ impl AppState {
self.ui.focus_outside_canvas = true; self.ui.focus_outside_canvas = true;
} }
/// Shows a dialog specifically for loading states.
pub fn show_loading_dialog(&mut self, title: &str, message: &str) { pub fn show_loading_dialog(&mut self, title: &str, message: &str) {
self.ui.dialog.dialog_title = title.to_string(); self.ui.dialog.dialog_title = title.to_string();
self.ui.dialog.dialog_message = message.to_string(); self.ui.dialog.dialog_message = message.to_string();
self.ui.dialog.dialog_buttons.clear(); // No buttons during loading self.ui.dialog.dialog_buttons.clear();
self.ui.dialog.dialog_active_button_index = 0; self.ui.dialog.dialog_active_button_index = 0;
self.ui.dialog.purpose = None; // Purpose is set when loading finishes self.ui.dialog.purpose = None;
self.ui.dialog.is_loading = true; self.ui.dialog.is_loading = true;
self.ui.dialog.dialog_show = true; self.ui.dialog.dialog_show = true;
self.ui.focus_outside_canvas = true; // Keep focus management consistent self.ui.focus_outside_canvas = true;
} }
/// Updates the content of an existing dialog, typically after loading.
pub fn update_dialog_content( pub fn update_dialog_content(
&mut self, &mut self,
message: &str, message: &str,
@@ -121,16 +125,12 @@ impl AppState {
if self.ui.dialog.dialog_show { if self.ui.dialog.dialog_show {
self.ui.dialog.dialog_message = message.to_string(); self.ui.dialog.dialog_message = message.to_string();
self.ui.dialog.dialog_buttons = buttons; self.ui.dialog.dialog_buttons = buttons;
self.ui.dialog.dialog_active_button_index = 0; // Reset focus self.ui.dialog.dialog_active_button_index = 0;
self.ui.dialog.purpose = Some(purpose); self.ui.dialog.purpose = Some(purpose);
self.ui.dialog.is_loading = false; // Loading finished self.ui.dialog.is_loading = false;
// Keep dialog_show = true
// Keep focus_outside_canvas = true
} }
} }
/// Hides the dialog and clears its content.
pub fn hide_dialog(&mut self) { pub fn hide_dialog(&mut self) {
self.ui.dialog.dialog_show = false; self.ui.dialog.dialog_show = false;
self.ui.dialog.dialog_title.clear(); self.ui.dialog.dialog_title.clear();
@@ -142,30 +142,27 @@ impl AppState {
self.ui.dialog.is_loading = false; self.ui.dialog.is_loading = false;
} }
/// Sets the active button index, wrapping around if necessary.
pub fn next_dialog_button(&mut self) { pub fn next_dialog_button(&mut self) {
if !self.ui.dialog.dialog_buttons.is_empty() { if !self.ui.dialog.dialog_buttons.is_empty() {
let next_index = (self.ui.dialog.dialog_active_button_index + 1) let next_index = (self.ui.dialog.dialog_active_button_index + 1)
% self.ui.dialog.dialog_buttons.len(); % self.ui.dialog.dialog_buttons.len();
self.ui.dialog.dialog_active_button_index = next_index; // Use new name self.ui.dialog.dialog_active_button_index = next_index;
} }
} }
/// Sets the active button index, wrapping around if necessary.
pub fn previous_dialog_button(&mut self) { pub fn previous_dialog_button(&mut self) {
if !self.ui.dialog.dialog_buttons.is_empty() { if !self.ui.dialog.dialog_buttons.is_empty() {
let len = self.ui.dialog.dialog_buttons.len(); let len = self.ui.dialog.dialog_buttons.len();
let prev_index = let prev_index =
(self.ui.dialog.dialog_active_button_index + len - 1) % len; (self.ui.dialog.dialog_active_button_index + len - 1) % len;
self.ui.dialog.dialog_active_button_index = prev_index; // Use new name self.ui.dialog.dialog_active_button_index = prev_index;
} }
} }
/// Gets the label of the currently active button, if any.
pub fn get_active_dialog_button_label(&self) -> Option<&str> { pub fn get_active_dialog_button_label(&self) -> Option<&str> {
self.ui.dialog self.ui.dialog
.dialog_buttons // Use new name .dialog_buttons
.get(self.ui.dialog.dialog_active_button_index) // Use new name .get(self.ui.dialog.dialog_active_button_index)
.map(|s| s.as_str()) .map(|s| s.as_str())
} }
} }
@@ -182,13 +179,13 @@ impl Default for UiState {
show_login: false, show_login: false,
show_register: false, show_register: false,
show_buffer_list: true, show_buffer_list: true,
show_search_palette: false, // ADDED
focus_outside_canvas: false, focus_outside_canvas: false,
dialog: DialogState::default(), dialog: DialogState::default(),
} }
} }
} }
// Update the Default implementation for DialogState itself
impl Default for DialogState { impl Default for DialogState {
fn default() -> Self { fn default() -> Self {
Self { Self {

View File

@@ -6,6 +6,8 @@ use ratatui::layout::Rect;
use ratatui::Frame; use ratatui::Frame;
use crate::state::app::highlight::HighlightState; use crate::state::app::highlight::HighlightState;
use crate::state::pages::canvas_state::CanvasState; 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 struct FormState {
pub id: i64, pub id: i64,
@@ -54,6 +56,7 @@ impl FormState {
theme: &Theme, theme: &Theme,
is_edit_mode: bool, is_edit_mode: bool,
highlight_state: &HighlightState, highlight_state: &HighlightState,
app_state: &AppState,
) { ) {
let fields_str_slice: Vec<&str> = let fields_str_slice: Vec<&str> =
self.fields.iter().map(|s| s.as_str()).collect(); self.fields.iter().map(|s| s.as_str()).collect();
@@ -73,6 +76,12 @@ impl FormState {
self.total_count, self.total_count,
self.current_position, 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. /// Resets the form to a state for creating a new entry.

View File

@@ -12,6 +12,7 @@ use crate::components::{
admin::add_logic::render_add_logic, admin::add_logic::render_add_logic,
auth::{login::render_login, register::render_register}, auth::{login::render_login, register::render_register},
common::find_file_palette, common::find_file_palette,
common::search_palette::render_search_palette
}; };
use crate::config::colors::themes::Theme; use crate::config::colors::themes::Theme;
use ratatui::{ use ratatui::{