Compare commits

..

8 Commits

Author SHA1 Message Date
Priec
6833ac5fad find palette in the bottom panel 2025-08-22 17:11:52 +02:00
Priec
3dff2ced6c bottom panel moved 2025-08-22 16:48:25 +02:00
Priec
ea7ff3796f search grpc client isolated a bit mode 2025-08-22 16:09:16 +02:00
Priec
310617d62b cargo fix 2025-08-22 15:49:33 +02:00
Priec
1d94e82f4b search 2025-08-22 15:48:30 +02:00
Priec
00dad5d673 fixed buffer logic 2025-08-22 14:26:58 +02:00
Priec
414c6957e7 sidebar as a feature 2025-08-22 14:11:36 +02:00
Priec
f127298e5a buffer as a feature 2025-08-22 13:47:34 +02:00
51 changed files with 404 additions and 341 deletions

10
Cargo.lock generated
View File

@@ -493,7 +493,7 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
[[package]] [[package]]
name = "canvas" name = "canvas"
version = "0.4.2" version = "0.5.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@@ -584,7 +584,7 @@ dependencies = [
[[package]] [[package]]
name = "client" name = "client"
version = "0.4.2" version = "0.5.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@@ -635,7 +635,7 @@ dependencies = [
[[package]] [[package]]
name = "common" name = "common"
version = "0.4.2" version = "0.5.0"
dependencies = [ dependencies = [
"prost", "prost",
"prost-types", "prost-types",
@@ -3022,7 +3022,7 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
[[package]] [[package]]
name = "search" name = "search"
version = "0.4.2" version = "0.5.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"common", "common",
@@ -3121,7 +3121,7 @@ dependencies = [
[[package]] [[package]]
name = "server" name = "server"
version = "0.4.2" version = "0.5.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bcrypt", "bcrypt",

View File

@@ -5,7 +5,7 @@ resolver = "2"
[workspace.package] [workspace.package]
# TODO: idk how to do the name, fix later # TODO: idk how to do the name, fix later
# name = "komp_ac" # name = "komp_ac"
version = "0.4.2" version = "0.5.0"
edition = "2021" edition = "2021"
license = "GPL-3.0-or-later" license = "GPL-3.0-or-later"
authors = ["Filip Priečinský <filippriec@gmail.com>"] authors = ["Filip Priečinský <filippriec@gmail.com>"]

View File

@@ -1,4 +1,4 @@
// src/components/common/find_file_palette.rs // src/bottom_panel/find_file_palette.rs
use crate::config::colors::themes::Theme; use crate::config::colors::themes::Theme;
use crate::modes::general::command_navigation::NavigationState; // Corrected path use crate::modes::general::command_navigation::NavigationState; // Corrected path

View File

@@ -0,0 +1,97 @@
// src/bottom_panel/layout.rs
use ratatui::{layout::Constraint, layout::Rect, Frame};
use crate::bottom_panel::{status_line::render_status_line, command_line::render_command_line};
use crate::bottom_panel::find_file_palette;
use crate::config::colors::themes::Theme;
use crate::modes::general::command_navigation::NavigationState;
use crate::state::app::state::AppState;
/// Calculate the layout constraints for the bottom panel (status line + command line/palette).
pub fn bottom_panel_constraints(
app_state: &AppState,
navigation_state: &NavigationState,
event_handler_command_mode_active: bool,
) -> Vec<Constraint> {
let mut status_line_height = 1;
#[cfg(feature = "ui-debug")]
{
if let Some(debug_state) = &app_state.debug_state {
if debug_state.is_error {
status_line_height = 4;
}
}
}
const PALETTE_OPTIONS_HEIGHT_FOR_LAYOUT: u16 = 15;
let command_palette_area_height = if navigation_state.active {
1 + PALETTE_OPTIONS_HEIGHT_FOR_LAYOUT
} else if event_handler_command_mode_active {
1
} else {
0
};
let mut constraints = vec![Constraint::Length(status_line_height)];
if command_palette_area_height > 0 {
constraints.push(Constraint::Length(command_palette_area_height));
}
constraints
}
/// Render the bottom panel (status line + command line/palette).
pub fn render_bottom_panel(
f: &mut Frame,
root_chunks: &[Rect],
chunk_idx: &mut usize,
current_dir: &str,
theme: &Theme,
is_event_handler_edit_mode: bool,
current_fps: f64,
app_state: &AppState,
navigation_state: &NavigationState,
event_handler_command_input: &str,
event_handler_command_mode_active: bool,
event_handler_command_message: &str,
) {
// --- Status line area ---
let status_line_area = root_chunks[*chunk_idx];
*chunk_idx += 1;
// --- Command line / palette area ---
let command_render_area = if root_chunks.len() > *chunk_idx {
Some(root_chunks[*chunk_idx])
} else {
None
};
if command_render_area.is_some() {
*chunk_idx += 1;
}
// --- Render status line ---
render_status_line(
f,
status_line_area,
current_dir,
theme,
is_event_handler_edit_mode,
current_fps,
app_state,
);
// --- Render command line or palette ---
if let Some(area) = command_render_area {
if navigation_state.active {
find_file_palette::render_find_file_palette(f, area, theme, navigation_state);
} else if event_handler_command_mode_active {
render_command_line(
f,
area,
event_handler_command_input,
true,
theme,
event_handler_command_message,
);
}
}
}

View File

@@ -0,0 +1,6 @@
// src/bottom_panel/mod.rs
pub mod status_line;
pub mod command_line;
pub mod layout;
pub mod find_file_palette;

View File

@@ -5,10 +5,9 @@ use ratatui::{
layout::Rect, layout::Rect,
style::Style, style::Style,
text::{Line, Span, Text}, text::{Line, Span, Text},
widgets::Paragraph, widgets::{Paragraph, Wrap},
Frame, Frame,
}; };
use ratatui::widgets::Wrap;
use std::path::Path; use std::path::Path;
use unicode_width::UnicodeWidthStr; use unicode_width::UnicodeWidthStr;

View File

@@ -1,7 +1,7 @@
// src/functions/common/buffer.rs // src/buffer/functions/buffer.rs
use crate::state::app::buffer::BufferState; use crate::buffer::state::BufferState;
use crate::state::app::buffer::AppView; use crate::buffer::state::AppView;
pub fn get_view_layer(view: &AppView) -> u8 { pub fn get_view_layer(view: &AppView) -> u8 {
match view { match view {

View File

@@ -0,0 +1,20 @@
// src/buffer/logic.rs
use crossterm::event::{KeyCode, KeyModifiers};
use crate::config::binds::config::Config;
use crate::state::app::state::UiState;
/// Toggle the buffer list visibility based on keybindings.
pub fn toggle_buffer_list(
ui_state: &mut UiState,
config: &Config,
key: KeyCode,
modifiers: KeyModifiers,
) -> bool {
if let Some(action) = config.get_common_action(key, modifiers) {
if action == "toggle_buffer_list" {
ui_state.show_buffer_list = !ui_state.show_buffer_list;
return true;
}
}
false
}

11
client/src/buffer/mod.rs Normal file
View File

@@ -0,0 +1,11 @@
// src/buffer/mod.rs
pub mod state;
pub mod functions;
pub mod ui;
pub mod logic;
pub use state::{AppView, BufferState};
pub use functions::*;
pub use ui::render_buffer_list;
pub use logic::toggle_buffer_list;

View File

@@ -1,4 +1,4 @@
// src/state/app/buffer.rs // src/buffer/state/buffer.rs
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum AppView { pub enum AppView {

View File

@@ -1,7 +1,7 @@
// src/components/handlers/buffer_list.rs // src/buffer/ui.rs
use crate::config::colors::themes::Theme; use crate::config::colors::themes::Theme;
use crate::state::app::buffer::BufferState; use crate::buffer::state::BufferState;
use crate::state::app::state::AppState; // Add this import use crate::state::app::state::AppState; // Add this import
use ratatui::{ use ratatui::{
layout::{Alignment, Rect}, layout::{Alignment, Rect},
@@ -11,7 +11,7 @@ use ratatui::{
Frame, Frame,
}; };
use unicode_width::UnicodeWidthStr; use unicode_width::UnicodeWidthStr;
use crate::functions::common::buffer::get_view_layer; use crate::buffer::functions::get_view_layer;
pub fn render_buffer_list( pub fn render_buffer_list(
f: &mut Frame, f: &mut Frame,

View File

@@ -2,7 +2,7 @@
use crate::config::colors::themes::Theme; use crate::config::colors::themes::Theme;
use crate::state::app::state::AppState; use crate::state::app::state::AppState;
use crate::state::pages::add_table::{AddTableFocus, AddTableState}; use crate::state::pages::add_table::{AddTableFocus, AddTableState};
use canvas::{render_canvas_default, render_canvas, FormEditor}; use canvas::{render_canvas, FormEditor};
use ratatui::{ use ratatui::{
layout::{Alignment, Constraint, Direction, Layout, Rect}, layout::{Alignment, Constraint, Direction, Layout, Rect},
style::{Modifier, Style}, style::{Modifier, Style},

View File

@@ -13,7 +13,7 @@ use ratatui::{
widgets::{Block, BorderType, Borders, Paragraph}, widgets::{Block, BorderType, Borders, Paragraph},
Frame, Frame,
}; };
use canvas::{FormEditor, render_canvas_default, render_canvas, render_suggestions_dropdown, DefaultCanvasTheme}; use canvas::{FormEditor, render_canvas, render_suggestions_dropdown, DefaultCanvasTheme};
pub fn render_register( pub fn render_register(
f: &mut Frame, f: &mut Frame,

View File

@@ -1,18 +1,11 @@
// src/components/common.rs // src/components/common.rs
pub mod command_line;
pub mod status_line;
pub mod text_editor; 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 use command_line::*;
pub use status_line::*;
pub use text_editor::*; 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::*;

View File

@@ -8,9 +8,8 @@ use ratatui::{
widgets::{Block, Borders, Paragraph}, widgets::{Block, Borders, Paragraph},
Frame, Frame,
}; };
use canvas::canvas::HighlightState;
use canvas::{ use canvas::{
render_canvas, render_suggestions_dropdown, DefaultCanvasTheme, FormEditor, render_canvas, render_suggestions_dropdown, DefaultCanvasTheme,
}; };
pub fn render_form( pub fn render_form(

View File

@@ -1,6 +0,0 @@
// src/components/handlers.rs
pub mod sidebar;
pub mod buffer_list;
pub use sidebar::*;
pub use buffer_list::*;

View File

@@ -1,5 +1,4 @@
// src/components/mod.rs // src/components/mod.rs
pub mod handlers;
pub mod intro; pub mod intro;
pub mod admin; pub mod admin;
pub mod common; pub mod common;
@@ -7,7 +6,6 @@ pub mod form;
pub mod auth; pub mod auth;
pub mod utils; pub mod utils;
pub use handlers::*;
pub use intro::*; pub use intro::*;
pub use admin::*; pub use admin::*;
pub use common::*; pub use common::*;

View File

@@ -1,5 +0,0 @@
// src/functions/common.rs
pub mod buffer;
pub use buffer::*;

View File

@@ -1,6 +1,5 @@
// src/functions/mod.rs // src/functions/mod.rs
pub mod common;
pub mod modes; pub mod modes;
pub use modes::*; pub use modes::*;

View File

@@ -3,9 +3,8 @@ use crate::config::binds::config::{Config, EditorKeybindingMode};
use crate::state::{ use crate::state::{
app::state::AppState, app::state::AppState,
pages::add_logic::{AddLogicFocus, AddLogicState}, pages::add_logic::{AddLogicFocus, AddLogicState},
app::buffer::AppView,
app::buffer::BufferState,
}; };
use crate::buffer::{AppView, BufferState};
use crossterm::event::{KeyEvent, KeyCode, KeyModifiers}; use crossterm::event::{KeyEvent, KeyCode, KeyModifiers};
use crate::services::GrpcClient; use crate::services::GrpcClient;
use tokio::sync::mpsc; use tokio::sync::mpsc;

View File

@@ -2,7 +2,7 @@
use crate::state::pages::admin::{AdminFocus, AdminState}; use crate::state::pages::admin::{AdminFocus, AdminState};
use crate::state::app::state::AppState; use crate::state::app::state::AppState;
use crate::config::binds::config::Config; use crate::config::binds::config::Config;
use crate::state::app::buffer::{BufferState, AppView}; use crate::buffer::state::{BufferState, AppView};
use crate::state::pages::add_table::{AddTableState, LinkDefinition}; use crate::state::pages::add_table::{AddTableState, LinkDefinition};
use ratatui::widgets::ListState; use ratatui::widgets::ListState;
use crate::state::pages::add_logic::{AddLogicState, AddLogicFocus}; // Added AddLogicFocus import use crate::state::pages::add_logic::{AddLogicState, AddLogicFocus}; // Added AddLogicFocus import

View File

@@ -8,6 +8,10 @@ pub mod modes;
pub mod functions; pub mod functions;
pub mod services; pub mod services;
pub mod utils; pub mod utils;
pub mod buffer;
pub mod sidebar;
pub mod search;
pub mod bottom_panel;
pub use ui::run_ui; pub use ui::run_ui;

View File

@@ -3,7 +3,6 @@
use crossterm::event::{KeyEvent, KeyCode, KeyModifiers}; use crossterm::event::{KeyEvent, KeyCode, KeyModifiers};
use crate::config::binds::config::Config; use crate::config::binds::config::Config;
use crate::services::grpc_client::GrpcClient; use crate::services::grpc_client::GrpcClient;
use crate::state::pages::form::FormState;
use crate::state::{app::state::AppState, pages::auth::LoginState, pages::auth::RegisterState}; use crate::state::{app::state::AppState, pages::auth::LoginState, pages::auth::RegisterState};
use crate::modes::common::commands::CommandHandler; use crate::modes::common::commands::CommandHandler;
use crate::tui::terminal::core::TerminalCore; use crate::tui::terminal::core::TerminalCore;

View File

@@ -1,7 +1,7 @@
// src/modes/common/commands.rs // src/modes/common/commands.rs
use crate::tui::terminal::core::TerminalCore; use crate::tui::terminal::core::TerminalCore;
use crate::state::app::state::AppState; use crate::state::app::state::AppState;
use crate::state::pages::{form::FormState, auth::LoginState, auth::RegisterState}; use crate::state::pages::{auth::LoginState, auth::RegisterState};
use anyhow::Result; use anyhow::Result;
pub struct CommandHandler; pub struct CommandHandler;

View File

@@ -3,8 +3,9 @@
use crossterm::event::{Event, KeyCode}; use crossterm::event::{Event, KeyCode};
use crate::config::binds::config::Config; use crate::config::binds::config::Config;
use crate::ui::handlers::context::DialogPurpose; use crate::ui::handlers::context::DialogPurpose;
use crate::state::app::{state::AppState, buffer::AppView}; use crate::state::app::state::AppState;
use crate::state::app::buffer::BufferState; use crate::buffer::AppView;
use crate::buffer::state::BufferState;
use crate::state::pages::auth::{LoginState, RegisterState}; use crate::state::pages::auth::{LoginState, RegisterState};
use crate::state::pages::admin::AdminState; use crate::state::pages::admin::AdminState;
use crate::modes::handlers::event::EventOutcome; use crate::modes::handlers::event::EventOutcome;

View File

@@ -1,7 +1,9 @@
// src/modes/handlers/event.rs // src/modes/handlers/event.rs
use crate::config::binds::config::Config; use crate::config::binds::config::Config;
use crate::config::binds::key_sequences::KeySequenceTracker; use crate::config::binds::key_sequences::KeySequenceTracker;
use crate::functions::common::buffer; use crate::buffer::{AppView, BufferState, switch_buffer, toggle_buffer_list};
use crate::sidebar::toggle_sidebar;
use crate::search::event::handle_search_palette_event;
use crate::functions::modes::navigation::add_logic_nav; use crate::functions::modes::navigation::add_logic_nav;
use crate::functions::modes::navigation::add_logic_nav::SaveLogicResultSender; use crate::functions::modes::navigation::add_logic_nav::SaveLogicResultSender;
use crate::functions::modes::navigation::add_table_nav::SaveTableResultSender; use crate::functions::modes::navigation::add_table_nav::SaveTableResultSender;
@@ -16,11 +18,9 @@ use crate::modes::{
}; };
use crate::services::auth::AuthClient; use crate::services::auth::AuthClient;
use crate::services::grpc_client::GrpcClient; use crate::services::grpc_client::GrpcClient;
use canvas::{FormEditor, AppMode as CanvasMode}; use canvas::AppMode as CanvasMode;
use crate::state::{ use crate::state::{
app::{ app::{
buffer::{AppView, BufferState},
search::SearchState,
state::AppState, state::AppState,
}, },
pages::{ pages::{
@@ -30,6 +30,7 @@ use crate::state::{
intro::IntroState, intro::IntroState,
}, },
}; };
use crate::search::state::SearchState;
use crate::tui::functions::common::login::LoginResult; use crate::tui::functions::common::login::LoginResult;
use crate::tui::functions::common::register::RegisterResult; use crate::tui::functions::common::register::RegisterResult;
use crate::tui::{ use crate::tui::{
@@ -38,15 +39,12 @@ use crate::tui::{
{admin, intro}, {admin, intro},
}; };
use crate::ui::handlers::context::UiContext; use crate::ui::handlers::context::UiContext;
use crate::ui::handlers::rat_state::UiStateHandler;
use canvas::KeyEventOutcome; use canvas::KeyEventOutcome;
use anyhow::Result; use anyhow::Result;
use common::proto::komp_ac::search::search_response::Hit; use common::proto::komp_ac::search::search_response::Hit;
use crossterm::event::KeyModifiers; use crossterm::event::{Event, KeyCode};
use crossterm::event::{Event, KeyCode, KeyEvent};
use tokio::sync::mpsc; use tokio::sync::mpsc;
use tokio::sync::mpsc::unbounded_channel; use tokio::sync::mpsc::unbounded_channel;
use tracing::{error, info};
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum EventOutcome { pub enum EventOutcome {
@@ -216,132 +214,6 @@ impl EventHandler {
} }
} }
// This function handles state changes.
async fn handle_search_palette_event(
&mut self,
key_event: KeyEvent,
app_state: &mut AppState,
) -> Result<EventOutcome> {
let mut should_close = false;
let mut outcome_message = String::new();
let mut trigger_search = false;
// Step 1: Handle search_state logic in a short scope
let (maybe_data, maybe_id) = {
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();
(None, None)
}
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)
{
(Some(data), Some(selected_hit.id))
} else {
(None, None)
}
} else {
(None, None)
}
}
KeyCode::Up => {
search_state.previous_result();
(None, None)
}
KeyCode::Down => {
search_state.next_result();
(None, None)
}
KeyCode::Char(c) => {
search_state.input.insert(search_state.cursor_position, c);
search_state.cursor_position += 1;
trigger_search = true;
(None, None)
}
KeyCode::Backspace => {
if search_state.cursor_position > 0 {
search_state.cursor_position -= 1;
search_state.input.remove(search_state.cursor_position);
trigger_search = true;
}
(None, None)
}
KeyCode::Left => {
search_state.cursor_position =
search_state.cursor_position.saturating_sub(1);
(None, None)
}
KeyCode::Right => {
if search_state.cursor_position < search_state.input.len() {
search_state.cursor_position += 1;
}
(None, None)
}
_ => (None, None),
}
} else {
(None, None)
}
};
// Step 2: Now safe to borrow form_state
if let (Some(data), Some(id)) = (maybe_data, maybe_id) {
if let Some(fs) = app_state.form_state_mut() {
let detached_pos = fs.total_count + 2;
fs.update_from_response(&data, detached_pos);
}
should_close = true;
outcome_message = format!("Loaded record ID {}", id);
}
// Step 3: Trigger async search if needed
if trigger_search {
if let Some(search_state) = app_state.search_state.as_mut() {
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 sender = self.search_result_sender.clone();
let mut grpc_client = self.grpc_client.clone();
info!("--- 1. Spawning search task for query: '{}' ---", query);
tokio::spawn(async move {
info!("--- 2. Background task started. ---");
match grpc_client.search_table(table_name, query).await {
Ok(response) => {
info!(
"--- 3a. gRPC call successful. Found {} hits. ---",
response.hits.len()
);
let _ = sender.send(response.hits);
}
Err(e) => {
error!("--- 3b. gRPC call failed: {:?} ---", e);
let _ = sender.send(vec![]);
}
}
});
}
}
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,
@@ -359,9 +231,16 @@ impl EventHandler {
) -> Result<EventOutcome> { ) -> Result<EventOutcome> {
if app_state.ui.show_search_palette { if app_state.ui.show_search_palette {
if let Event::Key(key_event) = event { if let Event::Key(key_event) = event {
return self.handle_search_palette_event(key_event, app_state).await; if let Some(message) = handle_search_palette_event(
key_event,
app_state,
&mut self.grpc_client,
self.search_result_sender.clone(),
).await? {
return Ok(EventOutcome::Ok(message));
}
return Ok(EventOutcome::Ok(String::new()));
} }
return Ok(EventOutcome::Ok(String::new()));
} }
let mut current_mode = let mut current_mode =
@@ -436,7 +315,7 @@ impl EventHandler {
let key_code = key_event.code; let key_code = key_event.code;
let modifiers = key_event.modifiers; let modifiers = key_event.modifiers;
if UiStateHandler::toggle_sidebar( if toggle_sidebar(
&mut app_state.ui, &mut app_state.ui,
config, config,
key_code, key_code,
@@ -452,7 +331,7 @@ impl EventHandler {
); );
return Ok(EventOutcome::Ok(message)); return Ok(EventOutcome::Ok(message));
} }
if UiStateHandler::toggle_buffer_list( if toggle_buffer_list(
&mut app_state.ui, &mut app_state.ui,
config, config,
key_code, key_code,
@@ -477,14 +356,14 @@ impl EventHandler {
) { ) {
match action { match action {
"next_buffer" => { "next_buffer" => {
if buffer::switch_buffer(buffer_state, true) { if switch_buffer(buffer_state, true) {
return Ok(EventOutcome::Ok( return Ok(EventOutcome::Ok(
"Switched to next buffer".to_string(), "Switched to next buffer".to_string(),
)); ));
} }
} }
"previous_buffer" => { "previous_buffer" => {
if buffer::switch_buffer(buffer_state, false) { if switch_buffer(buffer_state, false) {
return Ok(EventOutcome::Ok( return Ok(EventOutcome::Ok(
"Switched to previous buffer".to_string(), "Switched to previous buffer".to_string(),
)); ));

View File

@@ -3,7 +3,6 @@ use crate::state::app::state::AppState;
use crate::modes::handlers::event::EventHandler; use crate::modes::handlers::event::EventHandler;
use crate::state::pages::add_logic::AddLogicFocus; use crate::state::pages::add_logic::AddLogicFocus;
use crate::state::pages::admin::AdminState; use crate::state::pages::admin::AdminState;
use canvas::AppMode as CanvasMode;
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AppMode { pub enum AppMode {

View File

@@ -7,4 +7,3 @@ pub mod canvas;
pub use handlers::*; pub use handlers::*;
pub use general::*; pub use general::*;
pub use common::*; pub use common::*;
pub use canvas::*;

106
client/src/search/event.rs Normal file
View File

@@ -0,0 +1,106 @@
// src/search/event.rs
use crate::state::app::state::AppState;
use crate::services::grpc_client::GrpcClient;
use common::proto::komp_ac::search::search_response::Hit;
use crossterm::event::KeyCode;
use tokio::sync::mpsc;
use tracing::{error, info};
use std::collections::HashMap;
use anyhow::Result;
pub async fn handle_search_palette_event(
key_event: crossterm::event::KeyEvent,
app_state: &mut AppState,
grpc_client: &mut GrpcClient,
search_result_sender: mpsc::UnboundedSender<Vec<Hit>>,
) -> Result<Option<String>> {
let mut should_close = false;
let mut outcome_message = None;
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 = Some("Search cancelled".to_string());
}
KeyCode::Enter => {
// Step 1: Extract the data we need while holding the borrow
let maybe_data = search_state
.results
.get(search_state.selected_index)
.map(|hit| (hit.id, hit.content_json.clone()));
// Step 2: Process outside the borrow
if let Some((id, content_json)) = maybe_data {
if let Ok(data) = serde_json::from_str::<HashMap<String, String>>(&content_json) {
if let Some(fs) = app_state.form_state_mut() {
let detached_pos = fs.total_count + 2;
fs.update_from_response(&data, detached_pos);
}
should_close = true;
outcome_message = Some(format!("Loaded record ID {}", 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 {
if let Some(search_state) = app_state.search_state.as_mut() {
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 sender = search_result_sender.clone();
let mut grpc_client = grpc_client.clone();
info!("Spawning search task for query: '{}'", query);
tokio::spawn(async move {
match grpc_client.search_table(table_name, query).await {
Ok(response) => {
let _ = sender.send(response.hits);
}
Err(e) => {
error!("Search failed: {:?}", e);
let _ = sender.send(vec![]);
}
}
});
}
}
if should_close {
app_state.search_state = None;
app_state.ui.show_search_palette = false;
app_state.ui.focus_outside_canvas = false;
}
Ok(outcome_message)
}

31
client/src/search/grpc.rs Normal file
View File

@@ -0,0 +1,31 @@
// src/search/grpc.rs
use common::proto::komp_ac::search::{
searcher_client::SearcherClient, SearchRequest, SearchResponse,
};
use tonic::transport::Channel;
use anyhow::Result;
/// Internal search gRPC wrapper
#[derive(Clone)]
pub struct SearchGrpc {
client: SearcherClient<Channel>,
}
impl SearchGrpc {
pub fn new(channel: Channel) -> Self {
Self {
client: SearcherClient::new(channel),
}
}
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.client.search_table(request).await?;
Ok(response.into_inner())
}
}

9
client/src/search/mod.rs Normal file
View File

@@ -0,0 +1,9 @@
// src/search/mod.rs
pub mod state;
pub mod ui;
pub mod event;
pub mod grpc;
pub use ui::*;
pub use grpc::SearchGrpc;

View File

@@ -1,4 +1,4 @@
// src/state/app/search.rs // src/search/state.rs
use common::proto::komp_ac::search::search_response::Hit; use common::proto::komp_ac::search::search_response::Hit;

View File

@@ -1,7 +1,7 @@
// src/components/common/search_palette.rs // src/search/ui.rs
use crate::config::colors::themes::Theme; use crate::config::colors::themes::Theme;
use crate::state::app::search::SearchState; use crate::search::state::SearchState;
use ratatui::{ use ratatui::{
layout::{Constraint, Direction, Layout, Rect}, layout::{Constraint, Direction, Layout, Rect},
style::{Modifier, Style}, style::{Modifier, Style},

View File

@@ -22,9 +22,8 @@ use common::proto::komp_ac::tables_data::{
PostTableDataRequest, PostTableDataResponse, PutTableDataRequest, PostTableDataRequest, PostTableDataResponse, PutTableDataRequest,
PutTableDataResponse, PutTableDataResponse,
}; };
use common::proto::komp_ac::search::{ use crate::search::SearchGrpc;
searcher_client::SearcherClient, SearchRequest, SearchResponse, use common::proto::komp_ac::search::SearchResponse;
};
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use std::collections::HashMap; use std::collections::HashMap;
use tonic::transport::Channel; use tonic::transport::Channel;
@@ -36,7 +35,7 @@ pub struct GrpcClient {
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>, tables_data_client: TablesDataClient<Channel>,
search_client: SearcherClient<Channel>, search_client: SearchGrpc,
} }
impl GrpcClient { impl GrpcClient {
@@ -52,7 +51,7 @@ impl GrpcClient {
TableDefinitionClient::new(channel.clone()); TableDefinitionClient::new(channel.clone());
let table_script_client = TableScriptClient::new(channel.clone()); let table_script_client = TableScriptClient::new(channel.clone());
let tables_data_client = TablesDataClient::new(channel.clone()); let tables_data_client = TablesDataClient::new(channel.clone());
let search_client = SearcherClient::new(channel.clone()); let search_client = SearchGrpc::new(channel.clone());
Ok(Self { Ok(Self {
table_structure_client, table_structure_client,
@@ -247,11 +246,6 @@ impl GrpcClient {
table_name: String, table_name: String,
query: String, query: String,
) -> Result<SearchResponse> { ) -> Result<SearchResponse> {
let request = tonic::Request::new(SearchRequest { table_name, query }); self.search_client.search_table(table_name, query).await
let response = self
.search_client
.search_table(request)
.await?;
Ok(response.into_inner())
} }
} }

View File

@@ -0,0 +1,21 @@
// src/sidebar/state.rs
use crossterm::event::{KeyCode, KeyModifiers};
use crate::config::binds::config::Config;
use crate::state::app::state::UiState;
pub fn toggle_sidebar(
ui_state: &mut UiState,
config: &Config,
key: KeyCode,
modifiers: KeyModifiers,
) -> bool {
if let Some(action) =
config.get_action_for_key_in_mode(&config.keybindings.common, key, modifiers)
{
if action == "toggle_sidebar" {
ui_state.show_sidebar = !ui_state.show_sidebar;
return true;
}
}
false
}

View File

@@ -0,0 +1,7 @@
// src/sidebar/mod.rs
pub mod ui;
pub mod logic;
pub use ui::{calculate_sidebar_layout, render_sidebar};
pub use logic::toggle_sidebar;

View File

@@ -1,4 +1,4 @@
// src/components/handlers/sidebar.rs // src/sidebar/ui.rs
use ratatui::{ use ratatui::{
widgets::{Block, List, ListItem}, widgets::{Block, List, ListItem},
layout::{Rect, Direction, Layout, Constraint}, layout::{Rect, Direction, Layout, Constraint},

View File

@@ -1,5 +1,3 @@
// src/state/app.rs // src/state/app.rs
pub mod state; pub mod state;
pub mod buffer;
pub mod search;

View File

@@ -5,7 +5,7 @@ use common::proto::komp_ac::table_definition::ProfileTreeResponse;
// NEW: Import the types we need for the cache // NEW: Import the types we need for the cache
use common::proto::komp_ac::table_structure::TableStructureResponse; use common::proto::komp_ac::table_structure::TableStructureResponse;
use crate::modes::handlers::mode_manager::AppMode; use crate::modes::handlers::mode_manager::AppMode;
use crate::state::app::search::SearchState; use crate::search::state::SearchState;
use crate::ui::handlers::context::DialogPurpose; use crate::ui::handlers::context::DialogPurpose;
use crate::state::pages::form::FormState; use crate::state::pages::form::FormState;
use crate::config::binds::Config; use crate::config::binds::Config;

View File

@@ -1,6 +1,6 @@
// src/state/pages/add_table.rs // src/state/pages/add_table.rs
use canvas::{DataProvider, CanvasAction, AppMode}; use canvas::{DataProvider, AppMode};
use ratatui::widgets::TableState; use ratatui::widgets::TableState;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};

View File

@@ -1,5 +1,5 @@
// src/state/pages/auth.rs // src/state/pages/auth.rs
use canvas::{DataProvider, AppMode, SuggestionItem}; use canvas::{DataProvider, AppMode};
use lazy_static::lazy_static; use lazy_static::lazy_static;
lazy_static! { lazy_static! {

View File

@@ -1,10 +1,7 @@
// src/state/pages/form.rs // src/state/pages/form.rs
use crate::config::colors::themes::Theme; use canvas::{DataProvider, AppMode};
use canvas::{DataProvider, AppMode, EditorState, FormEditor};
use common::proto::komp_ac::search::search_response::Hit; use common::proto::komp_ac::search::search_response::Hit;
use ratatui::layout::Rect;
use ratatui::Frame;
use std::collections::HashMap; use std::collections::HashMap;
fn json_value_to_string(value: &serde_json::Value) -> String { fn json_value_to_string(value: &serde_json::Value) -> String {

View File

@@ -4,7 +4,7 @@ use crate::services::auth::AuthClient;
use crate::state::pages::auth::AuthState; use crate::state::pages::auth::AuthState;
use crate::state::pages::auth::LoginState; use crate::state::pages::auth::LoginState;
use crate::state::app::state::AppState; use crate::state::app::state::AppState;
use crate::state::app::buffer::{AppView, BufferState}; use crate::buffer::state::{AppView, BufferState};
use crate::config::storage::storage::{StoredAuthData, save_auth_data}; use crate::config::storage::storage::{StoredAuthData, save_auth_data};
use crate::ui::handlers::context::DialogPurpose; use crate::ui::handlers::context::DialogPurpose;
use common::proto::komp_ac::auth::LoginResponse; use common::proto::komp_ac::auth::LoginResponse;

View File

@@ -2,7 +2,7 @@
use crate::config::storage::delete_auth_data; use crate::config::storage::delete_auth_data;
use crate::state::pages::auth::AuthState; use crate::state::pages::auth::AuthState;
use crate::state::app::state::AppState; use crate::state::app::state::AppState;
use crate::state::app::buffer::{AppView, BufferState}; use crate::buffer::state::{AppView, BufferState};
use crate::ui::handlers::context::DialogPurpose; use crate::ui::handlers::context::DialogPurpose;
use tracing::{error, info}; use tracing::{error, info};

View File

@@ -6,7 +6,7 @@ use crate::state::{
app::state::AppState, app::state::AppState,
}; };
use crate::ui::handlers::context::DialogPurpose; use crate::ui::handlers::context::DialogPurpose;
use crate::state::app::buffer::{AppView, BufferState}; use crate::buffer::state::{AppView, BufferState};
use common::proto::komp_ac::auth::AuthResponse; use common::proto::komp_ac::auth::AuthResponse;
use anyhow::Context; use anyhow::Context;
use tokio::spawn; use tokio::spawn;

View File

@@ -1,6 +1,6 @@
// src/tui/functions/intro.rs // src/tui/functions/intro.rs
use crate::state::app::state::AppState; use crate::state::app::state::AppState;
use crate::state::app::buffer::{AppView, BufferState}; use crate::buffer::state::{AppView, BufferState};
/// Handles intro screen selection by updating view history and managing focus state. /// Handles intro screen selection by updating view history and managing focus state.
/// 0: Continue (restores last form or default) /// 0: Continue (restores last form or default)

View File

@@ -2,9 +2,7 @@
pub mod ui; pub mod ui;
pub mod render; pub mod render;
pub mod rat_state;
pub mod context; pub mod context;
pub use ui::run_ui; pub use ui::run_ui;
pub use rat_state::*;
pub use context::*; pub use context::*;

View File

@@ -1,38 +0,0 @@
// client/src/ui/handlers/rat_state.rs
use crossterm::event::{KeyCode, KeyModifiers};
use crate::config::binds::config::Config;
use crate::state::app::state::UiState;
pub struct UiStateHandler;
impl UiStateHandler {
pub fn toggle_sidebar(
ui_state: &mut UiState,
config: &Config,
key: KeyCode,
modifiers: KeyModifiers,
) -> bool {
if let Some(action) = config.get_action_for_key_in_mode(&config.keybindings.common, key, modifiers) {
if action == "toggle_sidebar" {
ui_state.show_sidebar = !ui_state.show_sidebar;
return true;
}
}
false
}
pub fn toggle_buffer_list(
ui_state: &mut UiState,
config: &Config,
key: KeyCode,
modifiers: KeyModifiers,
) -> bool {
if let Some(action) = config.get_common_action(key, modifiers) {
if action == "toggle_buffer_list" {
ui_state.show_buffer_list = !ui_state.show_buffer_list;
return true;
}
}
false
}
}

View File

@@ -5,18 +5,16 @@ use crate::components::{
admin::render_add_table, admin::render_add_table,
auth::{login::render_login, register::render_register}, auth::{login::render_login, register::render_register},
common::dialog::render_dialog, common::dialog::render_dialog,
common::find_file_palette,
common::search_palette::render_search_palette,
handlers::sidebar::{self, calculate_sidebar_layout},
intro::intro::render_intro, intro::intro::render_intro,
render_background, render_background,
render_buffer_list,
render_command_line,
render_status_line,
}; };
use crate::bottom_panel::{command_line::render_command_line, status_line::render_status_line, find_file_palette};
use crate::sidebar::{calculate_sidebar_layout, render_sidebar};
use crate::buffer::render_buffer_list;
use crate::search::render_search_palette;
use crate::config::colors::themes::Theme; use crate::config::colors::themes::Theme;
use crate::modes::general::command_navigation::NavigationState; use crate::modes::general::command_navigation::NavigationState;
use crate::state::app::buffer::BufferState; use crate::buffer::state::BufferState;
use crate::state::app::state::AppState; use crate::state::app::state::AppState;
use crate::state::pages::admin::AdminState; use crate::state::pages::admin::AdminState;
use crate::state::pages::auth::AuthState; use crate::state::pages::auth::AuthState;
@@ -24,6 +22,7 @@ use crate::state::pages::auth::LoginState;
use crate::state::pages::auth::RegisterState; use crate::state::pages::auth::RegisterState;
use crate::state::pages::form::FormState; use crate::state::pages::form::FormState;
use crate::state::pages::intro::IntroState; use crate::state::pages::intro::IntroState;
use crate::bottom_panel::layout::{bottom_panel_constraints, render_bottom_panel};
use crate::components::render_form; use crate::components::render_form;
use ratatui::{ use ratatui::{
layout::{Constraint, Direction, Layout}, layout::{Constraint, Direction, Layout},
@@ -52,38 +51,15 @@ pub fn render_ui(
) { ) {
render_background(f, f.area(), theme); render_background(f, f.area(), theme);
// --- START DYNAMIC LAYOUT LOGIC ---
let mut status_line_height = 1;
#[cfg(feature = "ui-debug")]
{
if let Some(debug_state) = &app_state.debug_state {
if debug_state.is_error {
status_line_height = 4;
}
}
}
// --- END DYNAMIC LAYOUT LOGIC ---
const PALETTE_OPTIONS_HEIGHT_FOR_LAYOUT: u16 = 15;
let mut bottom_area_constraints: Vec<Constraint> = vec![Constraint::Length(status_line_height)];
let command_palette_area_height = if navigation_state.active {
1 + PALETTE_OPTIONS_HEIGHT_FOR_LAYOUT
} else if event_handler_command_mode_active {
1
} else {
0
};
if command_palette_area_height > 0 {
bottom_area_constraints.push(Constraint::Length(command_palette_area_height));
}
let mut main_layout_constraints = vec![Constraint::Min(1)]; let mut main_layout_constraints = vec![Constraint::Min(1)];
if app_state.ui.show_buffer_list { if app_state.ui.show_buffer_list {
main_layout_constraints.insert(0, Constraint::Length(1)); main_layout_constraints.insert(0, Constraint::Length(1));
} }
main_layout_constraints.extend(bottom_area_constraints); main_layout_constraints.extend(bottom_panel_constraints(
app_state,
navigation_state,
event_handler_command_mode_active,
));
let root_chunks = Layout::default() let root_chunks = Layout::default()
.direction(Direction::Vertical) .direction(Direction::Vertical)
@@ -102,19 +78,6 @@ pub fn render_ui(
let main_content_area = root_chunks[chunk_idx]; let main_content_area = root_chunks[chunk_idx];
chunk_idx += 1; chunk_idx += 1;
let status_line_area = root_chunks[chunk_idx];
chunk_idx += 1;
let command_render_area = if command_palette_area_height > 0 {
if root_chunks.len() > chunk_idx {
Some(root_chunks[chunk_idx])
} else {
None
}
} else {
None
};
if app_state.ui.show_intro { if app_state.ui.show_intro {
render_intro(f, intro_state, main_content_area, theme); render_intro(f, intro_state, main_content_area, theme);
} else if app_state.ui.show_register { } else if app_state.ui.show_register {
@@ -168,7 +131,7 @@ pub fn render_ui(
let (sidebar_area, form_actual_area) = let (sidebar_area, form_actual_area) =
calculate_sidebar_layout(app_state.ui.show_sidebar, main_content_area); calculate_sidebar_layout(app_state.ui.show_sidebar, main_content_area);
if let Some(sidebar_rect) = sidebar_area { if let Some(sidebar_rect) = sidebar_area {
sidebar::render_sidebar( render_sidebar(
f, f,
sidebar_rect, sidebar_rect,
theme, theme,
@@ -209,36 +172,6 @@ pub fn render_ui(
render_buffer_list(f, area, theme, buffer_state, app_state); render_buffer_list(f, area, theme, buffer_state, app_state);
} }
render_status_line(
f,
status_line_area,
current_dir,
theme,
is_event_handler_edit_mode,
current_fps,
app_state,
);
if let Some(palette_or_command_area) = command_render_area {
if navigation_state.active {
find_file_palette::render_find_file_palette(
f,
palette_or_command_area,
theme,
navigation_state,
);
} else if event_handler_command_mode_active {
render_command_line(
f,
palette_or_command_area,
event_handler_command_input,
true,
theme,
event_handler_command_message,
);
}
}
// This block now correctly handles drawing popups over any view. // This block now correctly handles drawing popups over any view.
if app_state.ui.show_search_palette { if app_state.ui.show_search_palette {
if let Some(search_state) = &app_state.search_state { if let Some(search_state) = &app_state.search_state {
@@ -256,4 +189,19 @@ pub fn render_ui(
app_state.ui.dialog.is_loading, app_state.ui.dialog.is_loading,
); );
} }
render_bottom_panel(
f,
&root_chunks,
&mut chunk_idx,
current_dir,
theme,
is_event_handler_edit_mode,
current_fps,
app_state,
navigation_state,
event_handler_command_input,
event_handler_command_mode_active,
event_handler_command_message,
);
} }

View File

@@ -15,8 +15,8 @@ use crate::state::pages::auth::RegisterState;
use crate::state::pages::admin::AdminState; use crate::state::pages::admin::AdminState;
use crate::state::pages::admin::AdminFocus; use crate::state::pages::admin::AdminFocus;
use crate::state::pages::intro::IntroState; use crate::state::pages::intro::IntroState;
use crate::state::app::buffer::BufferState; use crate::buffer::state::BufferState;
use crate::state::app::buffer::AppView; use crate::buffer::state::AppView;
use crate::state::app::state::AppState; use crate::state::app::state::AppState;
use crate::tui::terminal::{EventReader, TerminalCore}; use crate::tui::terminal::{EventReader, TerminalCore};
use crate::ui::handlers::render::render_ui; use crate::ui::handlers::render::render_ui;
@@ -32,7 +32,8 @@ use crossterm::cursor::SetCursorStyle;
use crossterm::event as crossterm_event; use crossterm::event as crossterm_event;
use tracing::{error, info, warn}; use tracing::{error, info, warn};
use tokio::sync::mpsc; use tokio::sync::mpsc;
use std::time::{Instant, Duration}; use std::time::Instant;
use std::time::Duration;
#[cfg(feature = "ui-debug")] #[cfg(feature = "ui-debug")]
use crate::state::app::state::DebugState; use crate::state::app::state::DebugState;
#[cfg(feature = "ui-debug")] #[cfg(feature = "ui-debug")]