122 lines
4.1 KiB
Rust
122 lines
4.1 KiB
Rust
// 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);
|
|
}
|
|
}
|