From 98baeabce2235201f1c6a15da887e1b0221edb10 Mon Sep 17 00:00:00 2001 From: filipriec Date: Tue, 18 Feb 2025 22:44:20 +0100 Subject: [PATCH] redesign fixing errors --- src/client/ui/handlers.rs | 5 + src/client/ui/handlers/event.rs | 239 ++++++++++++++++++++++ src/client/ui/handlers/form.rs | 70 +++++++ src/client/ui/handlers/render.rs | 53 +++++ src/client/ui/handlers/state.rs | 32 +++ src/client/ui/handlers/ui.rs | 330 ++++--------------------------- 6 files changed, 433 insertions(+), 296 deletions(-) create mode 100644 src/client/ui/handlers/event.rs create mode 100644 src/client/ui/handlers/form.rs create mode 100644 src/client/ui/handlers/render.rs create mode 100644 src/client/ui/handlers/state.rs diff --git a/src/client/ui/handlers.rs b/src/client/ui/handlers.rs index f08d37e..4a0b921 100644 --- a/src/client/ui/handlers.rs +++ b/src/client/ui/handlers.rs @@ -1,4 +1,9 @@ // src/client/ui/handlers.rs + pub mod ui; +pub mod form; +pub mod event; +pub mod render; +pub mod state; pub use ui::run_ui; diff --git a/src/client/ui/handlers/event.rs b/src/client/ui/handlers/event.rs new file mode 100644 index 0000000..c66ea2b --- /dev/null +++ b/src/client/ui/handlers/event.rs @@ -0,0 +1,239 @@ +// src/client/ui/handlers/event.rs + +use crossterm::event::{Event, KeyCode, KeyModifiers}; +use crate::client::terminal::AppTerminal; +use crate::client::config::Config; +use crate::proto::multieko2::PostAdresarRequest; + +pub struct EventHandler { + pub command_mode: bool, + pub command_input: String, + pub command_message: String, + pub is_edit_mode: bool, + pub edit_mode_cooldown: bool, +} + +impl EventHandler { + pub fn new() -> Self { + EventHandler { + command_mode: false, + command_input: String::new(), + command_message: String::new(), + is_edit_mode: false, + edit_mode_cooldown: false, + } + } + + pub async fn handle_event( + &mut self, + event: Event, + config: &Config, + app_terminal: &mut AppTerminal, + form_state: &mut FormState, + is_saved: &mut bool, + total_count: usize, + current_position: &mut usize, + ) -> Result<(bool, String), Box> { + if let Event::Key(key) = event { + if !self.is_edit_mode && config.is_enter_edit_mode(key.code, key.modifiers) { + self.is_edit_mode = true; + self.edit_mode_cooldown = true; + self.command_message = "Edit mode".to_string(); + return Ok((false, self.command_message.clone())); + } else if self.is_edit_mode && config.is_exit_edit_mode(key.code, key.modifiers) { + self.is_edit_mode = false; + self.edit_mode_cooldown = true; + self.command_message = "Read-only mode".to_string(); + return Ok((false, self.command_message.clone())); + } + + if !self.is_edit_mode { + // Read-only mode handling + match key.code { + KeyCode::Char(':') => { + self.command_mode = true; + self.command_input.clear(); + self.command_message.clear(); + } + KeyCode::Esc => { + self.command_mode = false; + self.command_input.clear(); + self.command_message.clear(); + } + KeyCode::Tab | KeyCode::BackTab | KeyCode::Down | KeyCode::Up => { + if key.modifiers.contains(KeyModifiers::SHIFT) { + form_state.current_field = form_state.current_field.saturating_sub(1); + } else { + form_state.current_field = (form_state.current_field + 1) % form_state.fields.len(); + } + } + _ => { + if !self.edit_mode_cooldown { + let default_key = "i".to_string(); + let edit_key = config.keybindings.get("enter_edit_mode") + .and_then(|keys| keys.first()) + .unwrap_or(&default_key); + self.command_message = format!("Read-only mode - press {} to edit", edit_key); + } + } + } + } else { + // Existing edit mode handling + if self.command_mode { + match key.code { + KeyCode::Enter => { + let form_data = PostAdresarRequest { + firma: form_state.firma.clone(), + kz: form_state.kz.clone(), + drc: form_state.drc.clone(), + ulica: form_state.ulica.clone(), + psc: form_state.psc.clone(), + mesto: form_state.mesto.clone(), + stat: form_state.stat.clone(), + banka: form_state.banka.clone(), + ucet: form_state.ucet.clone(), + skladm: form_state.skladm.clone(), + ico: form_state.ico.clone(), + kontakt: form_state.kontakt.clone(), + telefon: form_state.telefon.clone(), + skladu: form_state.skladu.clone(), + fax: form_state.fax.clone(), + }; + + let command = self.command_input.trim(); + if command.is_empty() { + self.command_message = "Empty command".to_string(); + return Ok((false, self.command_message.clone())); + } + + let action = config.get_action_for_command(command) + .unwrap_or("unknown"); + + let (should_exit, message) = app_terminal + .handle_command(action, is_saved, &form_data) + .await?; + + self.command_message = message; + self.command_mode = false; + self.command_input.clear(); + + if action == "save" && *is_saved { + *current_position = total_count + 1; + } + + return Ok((should_exit, self.command_message.clone())); + } + KeyCode::Char(c) => self.command_input.push(c), + KeyCode::Backspace => { + self.command_input.pop(); + } + KeyCode::Esc => { + self.command_mode = false; + self.command_input.clear(); + self.command_message.clear(); + } + _ => {} + } + } else { + // Check for keybindings + if let Some(action) = config.get_action_for_key(key.code, key.modifiers) { + let form_data = PostAdresarRequest { + firma: form_state.firma.clone(), + kz: form_state.kz.clone(), + drc: form_state.drc.clone(), + ulica: form_state.ulica.clone(), + psc: form_state.psc.clone(), + mesto: form_state.mesto.clone(), + stat: form_state.stat.clone(), + banka: form_state.banka.clone(), + ucet: form_state.ucet.clone(), + skladm: form_state.skladm.clone(), + ico: form_state.ico.clone(), + kontakt: form_state.kontakt.clone(), + telefon: form_state.telefon.clone(), + skladu: form_state.skladu.clone(), + fax: form_state.fax.clone(), + }; + + let (should_exit, message) = app_terminal + .handle_command(action, is_saved, &form_data) + .await?; + self.command_message = message; + return Ok((should_exit, self.command_message.clone())); + } else { + match key.code { + KeyCode::Char(':') => { + self.command_mode = true; + self.command_input.clear(); + self.command_message.clear(); + } + KeyCode::Tab => { + if key.modifiers.contains(KeyModifiers::SHIFT) { + form_state.current_field = form_state.current_field.saturating_sub(1); + } else { + form_state.current_field = (form_state.current_field + 1) % form_state.fields.len(); + } + } + KeyCode::Esc => { + if config.is_exit_edit_mode(key.code, key.modifiers) { + self.is_edit_mode = false; + self.edit_mode_cooldown = true; + self.command_message = "Read-only mode".to_string(); + return Ok((false, self.command_message.clone())); + } + } + KeyCode::BackTab => form_state.current_field = form_state.current_field.saturating_sub(1), + KeyCode::Down => form_state.current_field = (form_state.current_field + 1) % form_state.fields.len(), + KeyCode::Up => form_state.current_field = form_state.current_field.saturating_sub(1), + KeyCode::Enter => form_state.current_field = (form_state.current_field + 1) % form_state.fields.len(), + KeyCode::Char(c) => { + match form_state.current_field { + 0 => form_state.firma.push(c), + 1 => form_state.kz.push(c), + 2 => form_state.drc.push(c), + 3 => form_state.ulica.push(c), + 4 => form_state.psc.push(c), + 5 => form_state.mesto.push(c), + 6 => form_state.stat.push(c), + 7 => form_state.banka.push(c), + 8 => form_state.ucet.push(c), + 9 => form_state.skladm.push(c), + 10 => form_state.ico.push(c), + 11 => form_state.kontakt.push(c), + 12 => form_state.telefon.push(c), + 13 => form_state.skladu.push(c), + 14 => form_state.fax.push(c), + _ => (), + } + } + KeyCode::Backspace => { + match form_state.current_field { + 0 => form_state.firma.pop(), + 1 => form_state.kz.pop(), + 2 => form_state.drc.pop(), + 3 => form_state.ulica.pop(), + 4 => form_state.psc.pop(), + 5 => form_state.mesto.pop(), + 6 => form_state.stat.pop(), + 7 => form_state.banka.pop(), + 8 => form_state.ucet.pop(), + 9 => form_state.skladm.pop(), + 10 => form_state.ico.pop(), + 11 => form_state.kontakt.pop(), + 12 => form_state.telefon.pop(), + 13 => form_state.skladu.pop(), + 14 => form_state.fax.pop(), + _ => None, + }; + } + _ => {} + } + } + } + } + } + + self.edit_mode_cooldown = false; + Ok((false, self.command_message.clone())) + } +} diff --git a/src/client/ui/handlers/form.rs b/src/client/ui/handlers/form.rs new file mode 100644 index 0000000..bf0b7d1 --- /dev/null +++ b/src/client/ui/handlers/form.rs @@ -0,0 +1,70 @@ +// src/client/ui/handlers/form.rs + +use crate::client::components1::render_form; +use crate::client::colors::Theme; +use ratatui::layout::Rect; +use ratatui::Frame; + +pub struct FormState { + pub firma: String, + pub kz: String, + pub drc: String, + pub ulica: String, + pub psc: String, + pub mesto: String, + pub stat: String, + pub banka: String, + pub ucet: String, + pub skladm: String, + pub ico: String, + pub kontakt: String, + pub telefon: String, + pub skladu: String, + pub fax: String, + pub current_field: usize, + pub fields: Vec<&'static str>, +} + +impl FormState { + pub fn new() -> Self { + FormState { + firma: String::new(), + kz: String::new(), + drc: String::new(), + ulica: String::new(), + psc: String::new(), + mesto: String::new(), + stat: String::new(), + banka: String::new(), + ucet: String::new(), + skladm: String::new(), + ico: String::new(), + kontakt: String::new(), + telefon: String::new(), + skladu: String::new(), + fax: String::new(), + current_field: 0, + fields: vec![ + "Firma", "KZ", "DRC", "Ulica", "PSC", "Mesto", "Stat", "Banka", + "Ucet", "Skladm", "ICO", "Kontakt", "Telefon", "Skladu", "Fax", + ], + } + } + + pub fn render(&mut self, f: &mut Frame, area: Rect, theme: &Theme, is_edit_mode: bool, total_count: usize, current_position: usize) { + render_form( + f, + area, + &self.fields, + &mut self.current_field, + &[ + &self.firma, &self.kz, &self.drc, &self.ulica, &self.psc, &self.mesto, &self.stat, &self.banka, + &self.ucet, &self.skladm, &self.ico, &self.kontakt, &self.telefon, &self.skladu, &self.fax, + ], + &theme, + is_edit_mode, + total_count, + current_position, + ); + } +} diff --git a/src/client/ui/handlers/render.rs b/src/client/ui/handlers/render.rs new file mode 100644 index 0000000..0061c3b --- /dev/null +++ b/src/client/ui/handlers/render.rs @@ -0,0 +1,53 @@ +// src/client/ui/handlers/render.rs + +use crate::client::components1::{render_command_line, render_form, render_preview_card, render_status_line}; +use crate::client::colors::Theme; +use ratatui::layout::{Constraint, Direction, Layout, Rect}; +use ratatui::Frame; + +pub fn render_ui( + f: &mut Frame, + form_state: &mut FormState, + theme: &Theme, + is_edit_mode: bool, + total_count: usize, + current_position: usize, + current_dir: &str, + command_input: &str, + command_mode: bool, + command_message: &str, +) { + let root = Layout::default() + .direction(Direction::Vertical) + .constraints([ + Constraint::Min(10), // Main content area + Constraint::Length(1), // Status line + Constraint::Length(1), // Command line + ]) + .split(f.area()); + + // Main content area + let main_chunks = Layout::default() + .direction(Direction::Horizontal) + .constraints([Constraint::Percentage(60), Constraint::Percentage(40)]) + .split(root[0]); + + // Left panel - Form + form_state.render(f, main_chunks[0], theme, is_edit_mode, total_count, current_position); + + // Right panel - Preview Card + render_preview_card( + f, + main_chunks[1], + &[ + &form_state.firma, &form_state.ulica, &form_state.mesto, &form_state.psc, &form_state.ico, &form_state.kontakt, &form_state.telefon, + ], + &theme, + ); + + // Status line + render_status_line(f, root[1], current_dir, theme, is_edit_mode); + + // Command line + render_command_line(f, root[2], command_input, command_mode, theme, command_message); +} diff --git a/src/client/ui/handlers/state.rs b/src/client/ui/handlers/state.rs new file mode 100644 index 0000000..2bbf14b --- /dev/null +++ b/src/client/ui/handlers/state.rs @@ -0,0 +1,32 @@ +// src/client/ui/handlers/state.rs + +use std::env; + +pub struct AppState { + pub is_saved: bool, + pub current_dir: String, + pub total_count: usize, + pub current_position: usize, +} + +impl AppState { + pub fn new() -> Result> { + let current_dir = env::current_dir()? + .to_string_lossy() + .to_string(); + Ok(AppState { + is_saved: false, + current_dir, + total_count: 0, + current_position: 0, + }) + } + + pub fn update_total_count(&mut self, total_count: usize) { + self.total_count = total_count; + } + + pub fn update_current_position(&mut self, current_position: usize) { + self.current_position = current_position; + } +} diff --git a/src/client/ui/handlers/ui.rs b/src/client/ui/handlers/ui.rs index d5b344b..8fad0cc 100644 --- a/src/client/ui/handlers/ui.rs +++ b/src/client/ui/handlers/ui.rs @@ -1,4 +1,4 @@ -// src/client/ui.rs +// src/client/ui/handlers/ui.rs use crossterm::event::{Event, KeyCode, KeyModifiers}; use crate::client::terminal::AppTerminal; @@ -9,321 +9,59 @@ use ratatui::layout::{Constraint, Direction, Layout}; use std::env; use crate::proto::multieko2::PostAdresarRequest; +use super::form::FormState; +use super::event::EventHandler; +use super::render::render_ui; +use super::state::AppState; + pub async fn run_ui() -> Result<(), Box> { let config = Config::load()?; let mut app_terminal = AppTerminal::new().await?; - let mut command_mode = false; - let mut command_input = String::new(); let theme = Theme::dark(); - // Initialize form fields - let mut firma = String::new(); - let mut kz = String::new(); - let mut drc = String::new(); - let mut ulica = String::new(); - let mut psc = String::new(); - let mut mesto = String::new(); - let mut stat = String::new(); - let mut banka = String::new(); - let mut ucet = String::new(); - let mut skladm = String::new(); - let mut ico = String::new(); - let mut kontakt = String::new(); - let mut telefon = String::new(); - let mut skladu = String::new(); - let mut fax = String::new(); - - let mut current_field: usize = 0; - let fields = vec![ - "Firma", "KZ", "DRC", "Ulica", "PSC", "Mesto", "Stat", "Banka", - "Ucet", "Skladm", "ICO", "Kontakt", "Telefon", "Skladu", "Fax", - ]; - - // Get the current directory - let current_dir = env::current_dir()? - .to_string_lossy() - .to_string(); - - // Track whether the state has been saved - let mut is_saved = false; - - // Track the current message to display in the command line - let mut command_message = String::new(); - let mut is_edit_mode = false; - let mut edit_mode_cooldown = false; + let mut form_state = FormState::new(); + let mut event_handler = EventHandler::new(); + let mut app_state = AppState::new()?; // Fetch the total count of Adresar entries let total_count = app_terminal.get_adresar_count().await?; - - // Track the current position in the database sequence - let mut current_position = total_count + 1; // Start at the next position for new entries + app_state.update_total_count(total_count); + app_state.update_current_position(total_count + 1); loop { // Fetch fresh total count on each iteration let total_count = app_terminal.get_adresar_count().await?; + app_state.update_total_count(total_count); app_terminal.draw(|f| { - let root = Layout::default() - .direction(Direction::Vertical) - .constraints([ - Constraint::Min(10), // Main content area - Constraint::Length(1), // Status line - Constraint::Length(1), // Command line - ]) - .split(f.area()); - - // Main content area - let main_chunks = Layout::default() - .direction(Direction::Horizontal) - .constraints([Constraint::Percentage(60), Constraint::Percentage(40)]) - .split(root[0]); - - // Left panel - Form - render_form( + render_ui( f, - main_chunks[0], - &fields, - &mut current_field, - &[ - &firma, &kz, &drc, &ulica, &psc, &mesto, &stat, &banka, - &ucet, &skladm, &ico, &kontakt, &telefon, &skladu, &fax, - ], + &mut form_state, &theme, - is_edit_mode, - total_count, // Pass total count - current_position, // Pass current position + event_handler.is_edit_mode, + app_state.total_count, + app_state.current_position, + &app_state.current_dir, + &event_handler.command_input, + event_handler.command_mode, + &event_handler.command_message, ); - - // Right panel - Preview Card - render_preview_card( - f, - main_chunks[1], - &[ - &firma, &ulica, &mesto, &psc, &ico, &kontakt, &telefon, - ], - &theme, - ); - - // Status line - render_status_line(f, root[1], ¤t_dir, &theme, is_edit_mode); - - // Command line - render_command_line(f, root[2], &command_input, command_mode, &theme, &command_message); })?; - // Event handling - if let Event::Key(key) = app_terminal.read_event()? { - // Handle enter/edit mode keys - if !is_edit_mode && config.is_enter_edit_mode(key.code, key.modifiers) { - is_edit_mode = true; - edit_mode_cooldown = true; - command_message = "Edit mode".to_string(); - continue; - } else if is_edit_mode && config.is_exit_edit_mode(key.code, key.modifiers) { - is_edit_mode = false; - edit_mode_cooldown = true; - command_message = "Read-only mode".to_string(); - continue; - } - - if !is_edit_mode { - // Read-only mode handling - match key.code { - KeyCode::Char(':') => { - command_mode = true; - command_input.clear(); - command_message.clear(); - } - KeyCode::Esc => { - command_mode = false; - command_input.clear(); - command_message.clear(); - } - // Allow navigation but prevent editing - KeyCode::Tab | KeyCode::BackTab | KeyCode::Down | KeyCode::Up => { - if key.modifiers.contains(KeyModifiers::SHIFT) { - current_field = current_field.saturating_sub(1); - } else { - current_field = (current_field + 1) % fields.len(); - } - } - _ => { - // Block all other inputs - if !edit_mode_cooldown { - let default_key = "i".to_string(); - let edit_key = config.keybindings.get("enter_edit_mode") - .and_then(|keys| keys.first()) - .unwrap_or(&default_key); - command_message = format!("Read-only mode - press {} to edit", edit_key); - } - } - } - } else { - // Existing edit mode handling - if command_mode { - match key.code { - KeyCode::Enter => { - // Create PostAdresarRequest from form data - let form_data = PostAdresarRequest { - firma: firma.clone(), - kz: kz.clone(), - drc: drc.clone(), - ulica: ulica.clone(), - psc: psc.clone(), - mesto: mesto.clone(), - stat: stat.clone(), - banka: banka.clone(), - ucet: ucet.clone(), - skladm: skladm.clone(), - ico: ico.clone(), - kontakt: kontakt.clone(), - telefon: telefon.clone(), - skladu: skladu.clone(), - fax: fax.clone(), - }; - - // Validate command format - let command = command_input.trim(); - if command.is_empty() { - command_message = "Empty command".to_string(); - continue; - } - - // Look up the action for the command string (e.g., "w") - let action = config.get_action_for_command(command) - .unwrap_or("unknown"); - - // Pass the resolved action to handle_command - let (should_exit, message) = app_terminal - .handle_command(action, &mut is_saved, &form_data) - .await?; - - command_message = message; - command_mode = false; - command_input.clear(); - - // Update current position after saving - if action == "save" && is_saved { - current_position = total_count + 1; - } - - if should_exit { - return Ok(()); - } - } - KeyCode::Char(c) => command_input.push(c), - KeyCode::Backspace => { - command_input.pop(); - } - KeyCode::Esc => { - command_mode = false; - command_input.clear(); - command_message.clear(); - } - _ => {} - } - } else { - // Check for keybindings - if let Some(action) = config.get_action_for_key(key.code, key.modifiers) { - let form_data = PostAdresarRequest { - firma: firma.clone(), - kz: kz.clone(), - drc: drc.clone(), - ulica: ulica.clone(), - psc: psc.clone(), - mesto: mesto.clone(), - stat: stat.clone(), - banka: banka.clone(), - ucet: ucet.clone(), - skladm: skladm.clone(), - ico: ico.clone(), - kontakt: kontakt.clone(), - telefon: telefon.clone(), - skladu: skladu.clone(), - fax: fax.clone(), - }; - - let (should_exit, message) = app_terminal - .handle_command(action, &mut is_saved, &form_data) - .await?; - command_message = message; - if should_exit { - return Ok(()); - } - } else { - match key.code { - KeyCode::Char(':') => { - command_mode = true; - command_input.clear(); - command_message.clear(); - } - KeyCode::Tab => { - if key.modifiers.contains(KeyModifiers::SHIFT) { - current_field = current_field.saturating_sub(1); - } else { - current_field = (current_field + 1) % fields.len(); - } - } - KeyCode::Esc => { - // Explicitly handle Esc even if not in command mode - if config.is_exit_edit_mode(key.code, key.modifiers) { - is_edit_mode = false; - edit_mode_cooldown = true; - command_message = "Read-only mode".to_string(); - continue; - } - } - KeyCode::BackTab => current_field = current_field.saturating_sub(1), - KeyCode::Down => current_field = (current_field + 1) % fields.len(), - KeyCode::Up => current_field = current_field.saturating_sub(1), - KeyCode::Enter => current_field = (current_field + 1) % fields.len(), - KeyCode::Char(c) => { - match current_field { - 0 => firma.push(c), - 1 => kz.push(c), - 2 => drc.push(c), - 3 => ulica.push(c), - 4 => psc.push(c), - 5 => mesto.push(c), - 6 => stat.push(c), - 7 => banka.push(c), - 8 => ucet.push(c), - 9 => skladm.push(c), - 10 => ico.push(c), - 11 => kontakt.push(c), - 12 => telefon.push(c), - 13 => skladu.push(c), - 14 => fax.push(c), - _ => (), - } - } - KeyCode::Backspace => { - match current_field { - 0 => firma.pop(), - 1 => kz.pop(), - 2 => drc.pop(), - 3 => ulica.pop(), - 4 => psc.pop(), - 5 => mesto.pop(), - 6 => stat.pop(), - 7 => banka.pop(), - 8 => ucet.pop(), - 9 => skladm.pop(), - 10 => ico.pop(), - 11 => kontakt.pop(), - 12 => telefon.pop(), - 13 => skladu.pop(), - 14 => fax.pop(), - _ => None, - }; - } - _ => {} - } - } - } + if let Some(event) = app_terminal.read_event()? { + let (should_exit, message) = event_handler.handle_event( + event, + &config, + &mut app_terminal, + &mut form_state, + &mut app_state.is_saved, + app_state.total_count, + &mut app_state.current_position, + ).await?; + event_handler.command_message = message; + if should_exit { + return Ok(()); } } - - edit_mode_cooldown = false; } }