Compare commits

..

19 Commits

Author SHA1 Message Date
filipriec
e36b1817bc working, split config where functions for each page are defined for this page, if the functions are not general for each page. Huge update, works for tui/functions/form and fui/functions/login. Working, time to move to other things 2025-03-30 15:46:49 +02:00
filipriec
13d4db6bdc properly handling up and down in the form and login, login needs logic implementation 2025-03-30 15:11:02 +02:00
filipriec
b5a5ebd7c0 working exactly as i want, now making the login up and down to work properly well 2025-03-30 15:03:12 +02:00
filipriec
6e04c1f267 original form fully working in the tui functions is the logic for the form 2025-03-30 14:24:12 +02:00
filipriec
a0d96cb87a moving to tui/functions/form.rs read_only functions 2025-03-30 14:13:07 +02:00
filipriec
4052a5b81b fixed unused imports 2025-03-30 14:01:08 +02:00
filipriec
ed99ebf541 compiled if statement in the read_only mode 2025-03-30 13:57:22 +02:00
filipriec
0a4f59cf8e reverting back fully 2025-03-30 13:15:51 +02:00
filipriec
fd6a9b73be reverting back 2025-03-30 13:11:04 +02:00
filipriec
dc994f6ee1 login in the functions 2025-03-30 13:04:48 +02:00
filipriec
e234dd1785 down and up now added to work in the original form 2025-03-30 12:09:20 +02:00
filipriec
6e0943f0cc compiled 2025-03-30 00:51:11 +01:00
filipriec
9622e0bd3c moving read_only functions for the form specific 2025-03-29 22:36:03 +01:00
filipriec
ee666e91ed adjusted login not working yet, wrong changes 2025-03-29 00:09:06 +01:00
filipriec
4f8f2f4a40 step4 2025-03-28 20:48:06 +01:00
filipriec
f21953147b step 3 compiled 2025-03-28 14:35:16 +01:00
filipriec
48b2658b55 step2 2025-03-28 14:29:36 +01:00
filipriec
37b08fdd10 implementation of canvas for multiple pages step 1 2025-03-28 14:26:18 +01:00
filipriec
d4efdf4833 login improvements 2025-03-27 12:27:43 +01:00
15 changed files with 518 additions and 168 deletions

5
Cargo.lock generated
View File

@@ -170,9 +170,9 @@ dependencies = [
[[package]] [[package]]
name = "async-trait" name = "async-trait"
version = "0.1.87" version = "0.1.88"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d556ec1359574147ec0c4fc5eb525f3f23263a592b1a9c07e0a75b427de55c97" checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -423,6 +423,7 @@ dependencies = [
name = "client" name = "client"
version = "0.2.5" version = "0.2.5"
dependencies = [ dependencies = [
"async-trait",
"common", "common",
"crossterm", "crossterm",
"dirs 6.0.0", "dirs 6.0.0",

View File

@@ -5,6 +5,7 @@ edition.workspace = true
license.workspace = true license.workspace = true
[dependencies] [dependencies]
async-trait = "0.1.88"
common = { path = "../common" } common = { path = "../common" }
crossterm = "0.28.1" crossterm = "0.28.1"

View File

@@ -1,58 +1,126 @@
// src/components/auth/login.rs // src/components/auth/login.rs
use crate::{
components::form::form::render_generic_form,
config::colors::themes::Theme,
state::pages::auth::AuthState,
};
use ratatui::{ use ratatui::{
layout::{Alignment, Constraint, Direction, Layout, Rect}, layout::{Alignment, Constraint, Direction, Layout, Rect, Margin},
style::Style, style::{Color, Style},
widgets::{Block, BorderType, Borders, Paragraph}, widgets::{Block, BorderType, Borders, Paragraph},
Frame, Frame,
}; };
use crate::{
config::colors::themes::Theme,
state::pages::auth::AuthState
};
pub fn render_login(f: &mut Frame, area: Rect, theme: &Theme, state: &mut AuthState) { pub fn render_login(
f: &mut Frame,
area: Rect,
theme: &Theme,
state: &AuthState,
) {
// Main login block with plain borders (matches main form style)
let block = Block::default() let block = Block::default()
.borders(Borders::ALL) .borders(Borders::ALL)
.border_type(BorderType::Rounded) .border_type(BorderType::Plain) // Matches main form style
.border_style(Style::default().fg(theme.accent)) .border_style(Style::default().fg(theme.border))
.style(Style::default().bg(theme.bg)) .title(" Login ")
.title(" Login "); .style(Style::default().bg(theme.bg));
let inner_area = block.inner(area);
f.render_widget(block, area); f.render_widget(block, area);
let inner_area = area.inner(Margin {
horizontal: 1,
vertical: 1,
});
// Define field names
let fields = &["Username/Email", "Password"];
// Split layout for form and buttons
let chunks = Layout::default() let chunks = Layout::default()
.direction(Direction::Vertical) .direction(Direction::Vertical)
.constraints([ .constraints([
Constraint::Percentage(40), Constraint::Min(3), // Form area
Constraint::Length(3), Constraint::Length(1), // Error message area
Constraint::Percentage(40), Constraint::Length(3), // Buttons area
]) ])
.split(inner_area); .split(inner_area);
let button_style = if state.return_selected { // Render form with plaintext display
render_generic_form(
f,
chunks[0],
"Login",
state,
fields,
theme,
!state.return_selected, // is_edit_mode
);
// Render buttons
let button_chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
.split(chunks[2]);
// Login button
let login_style = if !state.return_selected {
Style::default() Style::default()
.fg(theme.highlight) .fg(theme.highlight)
.add_modifier(ratatui::style::Modifier::BOLD) .add_modifier(ratatui::style::Modifier::BOLD)
} else { } else {
Style::default().fg(theme.fg) Style::default().fg(theme.fg)
}; };
let login_border_style = if !state.return_selected {
Style::default().fg(theme.accent)
} else {
Style::default().fg(theme.border)
};
f.render_widget( f.render_widget(
Paragraph::new("Return to Intro") Paragraph::new("Login")
.style(button_style) .style(login_style)
.alignment(Alignment::Center) .alignment(Alignment::Center)
.block( .block(
Block::default() Block::default()
.borders(Borders::ALL) .borders(Borders::ALL)
.border_type(BorderType::Plain) .border_type(BorderType::Plain)
.border_style(if state.return_selected { .border_style(login_border_style),
Style::default().fg(theme.accent)
} else {
Style::default().fg(theme.border)
}),
), ),
chunks[1] button_chunks[0],
); );
// Return button
let return_style = if state.return_selected {
Style::default()
.fg(theme.highlight)
.add_modifier(ratatui::style::Modifier::BOLD)
} else {
Style::default().fg(theme.fg)
};
let return_border_style = if state.return_selected {
Style::default().fg(theme.accent)
} else {
Style::default().fg(theme.border)
};
f.render_widget(
Paragraph::new("Return")
.style(return_style)
.alignment(Alignment::Center)
.block(
Block::default()
.borders(Borders::ALL)
.border_type(BorderType::Plain)
.border_style(return_border_style),
),
button_chunks[1],
);
// Render error message if present
if let Some(err) = &state.error_message {
let err_block = Paragraph::new(err.as_str())
.style(Style::default().fg(Color::Red))
.alignment(Alignment::Center);
f.render_widget(err_block, chunks[1]);
}
} }

View File

@@ -1,4 +1,4 @@
// src/components/handlers/form.rs // src/components/form/form.rs
use ratatui::{ use ratatui::{
widgets::{Paragraph, Block, Borders}, widgets::{Paragraph, Block, Borders},
layout::{Layout, Constraint, Direction, Rect, Margin, Alignment}, layout::{Layout, Constraint, Direction, Rect, Margin, Alignment},
@@ -6,13 +6,14 @@ use ratatui::{
Frame, Frame,
}; };
use crate::config::colors::themes::Theme; use crate::config::colors::themes::Theme;
use crate::state::pages::form::FormState; use crate::state::canvas_state::CanvasState;
use crate::components::handlers::canvas::render_canvas; use crate::components::handlers::canvas::render_canvas;
// Original form renderer (keep for backward compatibility)
pub fn render_form( pub fn render_form(
f: &mut Frame, f: &mut Frame,
area: Rect, area: Rect,
form_state: &FormState, form_state: &impl CanvasState,
fields: &[&str], fields: &[&str],
current_field: &usize, current_field: &usize,
inputs: &[&String], inputs: &[&String],
@@ -64,3 +65,47 @@ pub fn render_form(
is_edit_mode, is_edit_mode,
); );
} }
// New generic form renderer
pub fn render_generic_form(
f: &mut Frame,
area: Rect,
title: &str,
state: &impl CanvasState,
fields: &[&str],
theme: &Theme,
is_edit_mode: bool,
) {
// Create form card
let form_card = Block::default()
.borders(Borders::ALL)
.border_style(Style::default().fg(theme.border))
.title(format!(" {} ", title))
.style(Style::default().bg(theme.bg).fg(theme.fg));
f.render_widget(form_card, area);
// Define inner area
let inner_area = area.inner(Margin {
horizontal: 1,
vertical: 1,
});
// Create main layout
let main_layout = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Min(1)])
.split(inner_area);
// Delegate to render_canvas
render_canvas(
f,
main_layout[0],
state,
fields,
&state.current_field(),
&state.inputs(),
theme,
is_edit_mode,
);
}

View File

@@ -8,12 +8,12 @@ use ratatui::{
prelude::Alignment, prelude::Alignment,
}; };
use crate::config::colors::themes::Theme; use crate::config::colors::themes::Theme;
use crate::state::pages::form::FormState; use crate::state::canvas_state::CanvasState;
pub fn render_canvas( pub fn render_canvas(
f: &mut Frame, f: &mut Frame,
area: Rect, area: Rect,
form_state: &FormState, form_state: &impl CanvasState,
fields: &[&str], fields: &[&str],
current_field: &usize, current_field: &usize,
inputs: &[&String], inputs: &[&String],
@@ -30,7 +30,7 @@ pub fn render_canvas(
let input_container = Block::default() let input_container = Block::default()
.borders(Borders::ALL) .borders(Borders::ALL)
.border_style(if is_edit_mode { .border_style(if is_edit_mode {
form_state.has_unsaved_changes.then(|| theme.warning).unwrap_or(theme.accent) form_state.has_unsaved_changes().then(|| theme.warning).unwrap_or(theme.accent)
} else { } else {
theme.secondary theme.secondary
}) })
@@ -81,7 +81,7 @@ pub fn render_canvas(
f.render_widget(input_display, input_rows[i]); f.render_widget(input_display, input_rows[i]);
if is_active { if is_active {
let cursor_x = input_rows[i].x + form_state.current_cursor_pos as u16; let cursor_x = input_rows[i].x + form_state.current_cursor_pos() as u16;
let cursor_y = input_rows[i].y; let cursor_y = input_rows[i].y;
f.set_cursor_position((cursor_x, cursor_y)); f.set_cursor_position((cursor_x, cursor_y));
} }

View File

@@ -1,8 +1,10 @@
// src/modes/handlers/read_only.rs
// src/modes/canvas/read_only.rs
use crossterm::event::{KeyEvent}; use crossterm::event::{KeyEvent};
use crate::config::binds::config::Config; use crate::config::binds::config::Config;
use crate::state::pages::form::FormState; use crate::state::pages::form::FormState;
use crate::state::pages::auth::AuthState;
use crate::config::binds::key_sequences::KeySequenceTracker; use crate::config::binds::key_sequences::KeySequenceTracker;
use crate::tui::terminal::grpc_client::GrpcClient; use crate::tui::terminal::grpc_client::GrpcClient;
@@ -14,9 +16,11 @@ enum CharType {
} }
pub async fn handle_read_only_event( pub async fn handle_read_only_event(
app_state: &crate::state::state::AppState,
key: KeyEvent, key: KeyEvent,
config: &Config, config: &Config,
form_state: &mut FormState, form_state: &mut FormState,
auth_state: &mut AuthState,
key_sequence_tracker: &mut KeySequenceTracker, key_sequence_tracker: &mut KeySequenceTracker,
current_position: &mut u64, current_position: &mut u64,
total_count: u64, total_count: u64,
@@ -50,16 +54,34 @@ pub async fn handle_read_only_event(
// Try to match the current sequence against Read-Only mode bindings // Try to match the current sequence against Read-Only mode bindings
if let Some(action) = config.matches_key_sequence_generalized(&sequence) { if let Some(action) = config.matches_key_sequence_generalized(&sequence) {
let result = execute_action( let result = if (action == "previous_entry" || action == "next_entry" ||
action, action == "move_up" || action == "move_down") && app_state.ui.show_form {
form_state, crate::tui::functions::form::handle_action(
ideal_cursor_column, action,
key_sequence_tracker, form_state,
command_message, grpc_client,
current_position, current_position,
total_count, total_count,
grpc_client, ideal_cursor_column,
).await?; ).await?
} else if (action == "move_up" || action == "move_down") && app_state.ui.show_login {
crate::tui::functions::login::handle_action(
action,
auth_state,
ideal_cursor_column,
).await?
} else {
execute_action(
action,
form_state,
ideal_cursor_column,
key_sequence_tracker,
command_message,
current_position,
total_count,
grpc_client,
).await?
};
key_sequence_tracker.reset(); key_sequence_tracker.reset();
return Ok((false, result)); return Ok((false, result));
} }
@@ -72,16 +94,27 @@ pub async fn handle_read_only_event(
// Since it's not part of a multi-key sequence, check for a direct action // Since it's not part of a multi-key sequence, check for a direct action
if sequence.len() == 1 && !config.is_key_sequence_prefix(&sequence) { if sequence.len() == 1 && !config.is_key_sequence_prefix(&sequence) {
if let Some(action) = config.get_read_only_action_for_key(key.code, key.modifiers) { if let Some(action) = config.get_read_only_action_for_key(key.code, key.modifiers) {
let result = execute_action( let result = if action == "previous_entry" && app_state.ui.show_form {
action, crate::tui::functions::form::handle_action(
form_state, action,
ideal_cursor_column, form_state,
key_sequence_tracker, grpc_client,
command_message, current_position,
current_position, total_count,
total_count, ideal_cursor_column,
grpc_client, ).await?
).await?; } else {
execute_action(
action,
form_state,
ideal_cursor_column,
key_sequence_tracker,
command_message,
current_position,
total_count,
grpc_client,
).await?
};
key_sequence_tracker.reset(); key_sequence_tracker.reset();
return Ok((false, result)); return Ok((false, result));
} }
@@ -91,16 +124,27 @@ pub async fn handle_read_only_event(
key_sequence_tracker.reset(); key_sequence_tracker.reset();
if let Some(action) = config.get_read_only_action_for_key(key.code, key.modifiers) { if let Some(action) = config.get_read_only_action_for_key(key.code, key.modifiers) {
let result = execute_action( let result = if action == "previous_entry" && app_state.ui.show_form {
action, crate::tui::functions::form::handle_action(
form_state, action,
ideal_cursor_column, form_state,
key_sequence_tracker, grpc_client,
command_message, current_position,
current_position, total_count,
total_count, ideal_cursor_column,
grpc_client, ).await?
).await?; } else {
execute_action(
action,
form_state,
ideal_cursor_column,
key_sequence_tracker,
command_message,
current_position,
total_count,
grpc_client,
).await?
};
return Ok((false, result)); return Ok((false, result));
} }
} }
@@ -128,72 +172,20 @@ async fn execute_action(
grpc_client: &mut GrpcClient, grpc_client: &mut GrpcClient,
) -> Result<String, Box<dyn std::error::Error>> { ) -> Result<String, Box<dyn std::error::Error>> {
match action { match action {
"previous_entry" => { "previous_entry" | "next_entry" => {
let new_position = current_position.saturating_sub(1); // This will only be called when no component is active
if new_position >= 1 { key_sequence_tracker.reset();
*current_position = new_position; Ok(format!("Navigation prev/next only available in form mode"))
match grpc_client.get_adresar_by_position(*current_position).await {
Ok(response) => {
form_state.id = response.id;
form_state.values = vec![
response.firma, response.kz, response.drc,
response.ulica, response.psc, response.mesto,
response.stat, response.banka, response.ucet,
response.skladm, response.ico, response.kontakt,
response.telefon, response.skladu, response.fax,
];
let current_input = form_state.get_current_input();
let max_cursor_pos = if !current_input.is_empty() {
current_input.len() - 1
} else { 0 };
form_state.current_cursor_pos = std::cmp::min(*ideal_cursor_column, max_cursor_pos);
form_state.has_unsaved_changes = false;
*command_message = format!("Loaded entry {}", *current_position);
}
Err(e) => {
*command_message = format!("Error loading entry: {}", e);
}
}
key_sequence_tracker.reset();
}
Ok(command_message.clone())
} }
"next_entry" => { "move_up" | "move_down" => {
if *current_position <= total_count { // This will only be called when no component is active
*current_position += 1; key_sequence_tracker.reset();
if *current_position <= total_count { Ok(format!("Navigation up/down only available in form mode"))
match grpc_client.get_adresar_by_position(*current_position).await { }
Ok(response) => { "exit_edit_mode" => {
form_state.id = response.id; key_sequence_tracker.reset();
form_state.values = vec![ command_message.clear();
response.firma, response.kz, response.drc, Ok("".to_string())
response.ulica, response.psc, response.mesto,
response.stat, response.banka, response.ucet,
response.skladm, response.ico, response.kontakt,
response.telefon, response.skladu, response.fax,
];
let current_input = form_state.get_current_input();
let max_cursor_pos = if !current_input.is_empty() {
current_input.len() - 1
} else { 0 };
form_state.current_cursor_pos = std::cmp::min(*ideal_cursor_column, max_cursor_pos);
form_state.has_unsaved_changes = false;
*command_message = format!("Loaded entry {}", *current_position);
}
Err(e) => {
*command_message = format!("Error loading entry: {}", e);
}
}
} else {
form_state.reset_to_empty();
form_state.current_field = 0;
form_state.current_cursor_pos = 0;
*ideal_cursor_column = 0;
*command_message = "New entry mode".to_string();
}
key_sequence_tracker.reset();
}
Ok(command_message.clone())
} }
"exit_edit_mode" => { "exit_edit_mode" => {
key_sequence_tracker.reset(); key_sequence_tracker.reset();
@@ -215,38 +207,6 @@ async fn execute_action(
} }
Ok("".to_string()) Ok("".to_string())
} }
"move_up" => {
// Change field first
if form_state.current_field == 0 {
form_state.current_field = form_state.fields.len() - 1;
} else {
form_state.current_field = form_state.current_field.saturating_sub(1);
}
// Get current input AFTER changing field
let current_input = form_state.get_current_input();
let max_cursor_pos = if !current_input.is_empty() {
current_input.len() - 1
} else {
0
};
form_state.current_cursor_pos = (*ideal_cursor_column).min(max_cursor_pos);
Ok("".to_string())
}
"move_down" => {
// Change field first
form_state.current_field = (form_state.current_field + 1) % form_state.fields.len();
// Get current input AFTER changing field
let current_input = form_state.get_current_input();
let max_cursor_pos = if !current_input.is_empty() {
current_input.len() - 1
} else {
0
};
form_state.current_cursor_pos = (*ideal_cursor_column).min(max_cursor_pos);
Ok("".to_string())
}
"move_word_next" => { "move_word_next" => {
let current_input = form_state.get_current_input(); let current_input = form_state.get_current_input();
if !current_input.is_empty() { if !current_input.is_empty() {

View File

@@ -8,6 +8,7 @@ use crate::tui::terminal::{
use crate::tui::controls::commands::CommandHandler; use crate::tui::controls::commands::CommandHandler;
use crate::config::binds::config::Config; use crate::config::binds::config::Config;
use crate::state::pages::form::FormState; use crate::state::pages::form::FormState;
use crate::state::pages::auth::AuthState;
use crate::ui::handlers::rat_state::UiStateHandler; use crate::ui::handlers::rat_state::UiStateHandler;
use crate::modes::{ use crate::modes::{
common::{command_mode}, common::{command_mode},
@@ -25,6 +26,7 @@ pub struct EventHandler {
pub edit_mode_cooldown: bool, pub edit_mode_cooldown: bool,
pub ideal_cursor_column: usize, pub ideal_cursor_column: usize,
pub key_sequence_tracker: KeySequenceTracker, pub key_sequence_tracker: KeySequenceTracker,
pub auth_state: AuthState,
} }
impl EventHandler { impl EventHandler {
@@ -37,6 +39,7 @@ impl EventHandler {
edit_mode_cooldown: false, edit_mode_cooldown: false,
ideal_cursor_column: 0, ideal_cursor_column: 0,
key_sequence_tracker: KeySequenceTracker::new(800), key_sequence_tracker: KeySequenceTracker::new(800),
auth_state: AuthState::new(),
} }
} }
@@ -141,9 +144,11 @@ impl EventHandler {
// Let read_only mode handle its own actions (including navigation from common bindings) // Let read_only mode handle its own actions (including navigation from common bindings)
return read_only::handle_read_only_event( return read_only::handle_read_only_event(
&app_state,
key, key,
config, config,
form_state, form_state,
&mut self.auth_state,
&mut self.key_sequence_tracker, &mut self.key_sequence_tracker,
current_position, current_position,
total_count, total_count,

View File

@@ -15,14 +15,16 @@ pub struct ModeManager;
impl ModeManager { impl ModeManager {
// Determine current mode based on app state // Determine current mode based on app state
pub fn derive_mode(app_state: &AppState, event_handler: &EventHandler) -> AppMode { pub fn derive_mode(app_state: &AppState, event_handler: &EventHandler) -> AppMode {
// Command mode takes precedence if active
if event_handler.command_mode { if event_handler.command_mode {
return AppMode::Command; return AppMode::Command;
} }
// Check UI state flags if app_state.ui.show_login { // NEW: Check auth visibility
if app_state.ui.show_intro || app_state.ui.show_admin { if event_handler.is_edit_mode {
AppMode::General AppMode::Edit
} else {
AppMode::ReadOnly
}
} else if app_state.ui.show_form { } else if app_state.ui.show_form {
if event_handler.is_edit_mode { if event_handler.is_edit_mode {
AppMode::Edit AppMode::Edit
@@ -30,11 +32,10 @@ impl ModeManager {
AppMode::ReadOnly AppMode::ReadOnly
} }
} else { } else {
// Fallback
AppMode::General AppMode::General
} }
} }
// Mode transition rules // Mode transition rules
pub fn can_enter_command_mode(current_mode: AppMode) -> bool { pub fn can_enter_command_mode(current_mode: AppMode) -> bool {
!matches!(current_mode, AppMode::Edit) // Can't enter from Edit mode !matches!(current_mode, AppMode::Edit) // Can't enter from Edit mode

View File

@@ -0,0 +1,44 @@
// src/state/canvas_state.rs
use crate::state::pages::form::FormState;
pub trait CanvasState {
fn current_field(&self) -> usize;
fn current_cursor_pos(&self) -> usize;
fn has_unsaved_changes(&self) -> bool;
fn inputs(&self) -> Vec<&String>;
fn get_current_input(&self) -> &str;
fn get_current_input_mut(&mut self) -> &mut String;
}
// Implement for FormState (keep existing form.rs code and add this)
impl CanvasState for FormState {
fn current_field(&self) -> usize {
self.current_field
}
fn current_cursor_pos(&self) -> usize {
self.current_cursor_pos
}
fn has_unsaved_changes(&self) -> bool {
self.has_unsaved_changes
}
fn inputs(&self) -> Vec<&String> {
self.values.iter().collect()
}
fn get_current_input(&self) -> &str {
self.values
.get(self.current_field)
.map(|s| s.as_str())
.unwrap_or("")
}
fn get_current_input_mut(&mut self) -> &mut String {
self.values
.get_mut(self.current_field)
.expect("Invalid current_field index")
}
}

View File

@@ -1,3 +1,4 @@
// src/state/mod.rs // src/state/mod.rs
pub mod state; pub mod state;
pub mod pages; pub mod pages;
pub mod canvas_state;

View File

@@ -1,4 +1,5 @@
// src/state/pages/auth.rs // src/state/pages/auth.rs
use crate::state::canvas_state::CanvasState;
#[derive(Default)] #[derive(Default)]
pub struct AuthState { pub struct AuthState {
@@ -6,6 +7,8 @@ pub struct AuthState {
pub username: String, pub username: String,
pub password: String, pub password: String,
pub error_message: Option<String>, pub error_message: Option<String>,
pub current_field: usize,
pub current_cursor_pos: usize,
} }
impl AuthState { impl AuthState {
@@ -15,6 +18,43 @@ impl AuthState {
username: String::new(), username: String::new(),
password: String::new(), password: String::new(),
error_message: None, error_message: None,
current_field: 0,
current_cursor_pos: 0,
}
}
}
impl CanvasState for AuthState {
fn current_field(&self) -> usize {
self.current_field
}
fn current_cursor_pos(&self) -> usize {
self.current_cursor_pos
}
fn has_unsaved_changes(&self) -> bool {
// Auth form doesn't need unsaved changes tracking
false
}
fn inputs(&self) -> Vec<&String> {
vec![&self.username, &self.password]
}
fn get_current_input(&self) -> &str {
match self.current_field {
0 => &self.username,
1 => &self.password,
_ => "", // Return empty string for invalid index instead of panicking
}
}
fn get_current_input_mut(&mut self) -> &mut String {
match self.current_field {
0 => &mut self.username,
1 => &mut self.password,
_ => panic!("Invalid current_field index in AuthState"),
} }
} }
} }

View File

@@ -70,4 +70,15 @@ impl FormState {
.get_mut(self.current_field) .get_mut(self.current_field)
.expect("Invalid current_field index") .expect("Invalid current_field index")
} }
pub fn update_from_response(&mut self, response: common::proto::multieko2::adresar::AdresarResponse) {
self.id = response.id;
self.values = vec![
response.firma, response.kz, response.drc,
response.ulica, response.psc, response.mesto,
response.stat, response.banka, response.ucet,
response.skladm, response.ico, response.kontakt,
response.telefon, response.skladu, response.fax,
];
}
} }

View File

@@ -2,6 +2,9 @@
pub mod admin; pub mod admin;
pub mod intro; pub mod intro;
pub mod login;
pub mod form;
pub use admin::*; pub use admin::*;
pub use intro::*; pub use intro::*;
pub use form::*;

View File

@@ -0,0 +1,112 @@
// src/tui/functions/form.rs
use crate::state::pages::form::FormState;
use crate::tui::terminal::GrpcClient;
use common::proto::multieko2::adresar::AdresarResponse;
pub async fn handle_action(
action: &str,
form_state: &mut FormState,
grpc_client: &mut GrpcClient,
current_position: &mut u64,
total_count: u64,
ideal_cursor_column: &mut usize,
) -> Result<String, Box<dyn std::error::Error>> {
match action {
"previous_entry" => {
let new_position = current_position.saturating_sub(1);
if new_position >= 1 {
*current_position = new_position;
let response = grpc_client.get_adresar_by_position(*current_position).await?;
// Direct field assignments
form_state.id = response.id;
form_state.values = vec![
response.firma, response.kz, response.drc,
response.ulica, response.psc, response.mesto,
response.stat, response.banka, response.ucet,
response.skladm, response.ico, response.kontakt,
response.telefon, response.skladu, response.fax,
];
let current_input = form_state.get_current_input();
let max_cursor_pos = if !current_input.is_empty() {
current_input.len() - 1
} else { 0 };
form_state.current_cursor_pos = std::cmp::min(*ideal_cursor_column, max_cursor_pos);
form_state.has_unsaved_changes = false;
Ok(format!("Loaded form entry {}", *current_position))
} else {
Ok("Already at first form entry".into())
}
}
"next_entry" => {
if *current_position <= total_count {
*current_position += 1;
if *current_position <= total_count {
let response = grpc_client.get_adresar_by_position(*current_position).await?;
// Direct field assignments
form_state.id = response.id;
form_state.values = vec![
response.firma, response.kz, response.drc,
response.ulica, response.psc, response.mesto,
response.stat, response.banka, response.ucet,
response.skladm, response.ico, response.kontakt,
response.telefon, response.skladu, response.fax,
];
let current_input = form_state.get_current_input();
let max_cursor_pos = if !current_input.is_empty() {
current_input.len() - 1
} else { 0 };
form_state.current_cursor_pos = std::cmp::min(*ideal_cursor_column, max_cursor_pos);
form_state.has_unsaved_changes = false;
Ok(format!("Loaded form entry {}", *current_position))
} else {
form_state.reset_to_empty();
form_state.current_field = 0;
form_state.current_cursor_pos = 0;
*ideal_cursor_column = 0;
Ok("New form entry mode".into())
}
} else {
Ok("Already at last entry".into())
}
}
"move_up" => {
// Change field first
if form_state.current_field == 0 {
form_state.current_field = form_state.fields.len() - 1;
} else {
form_state.current_field = form_state.current_field.saturating_sub(1);
}
// Get current input AFTER changing field
let current_input = form_state.get_current_input();
let max_cursor_pos = if !current_input.is_empty() {
current_input.len() - 1
} else {
0
};
form_state.current_cursor_pos = (*ideal_cursor_column).min(max_cursor_pos);
Ok("".to_string())
}
"move_down" => {
// Change field first
form_state.current_field = (form_state.current_field + 1) % form_state.fields.len();
// Get current input AFTER changing field
let current_input = form_state.get_current_input();
let max_cursor_pos = if !current_input.is_empty() {
current_input.len() - 1
} else {
0
};
form_state.current_cursor_pos = (*ideal_cursor_column).min(max_cursor_pos);
Ok("".to_string())
}
_ => Err("Unknown form action".into())
}
}

View File

@@ -0,0 +1,58 @@
// src/tui/functions/login.rs
use crate::state::pages::auth::AuthState;
use crate::state::canvas_state::CanvasState;
pub async fn handle_action(
action: &str,
auth_state: &mut AuthState,
ideal_cursor_column: &mut usize,
) -> Result<String, Box<dyn std::error::Error>> {
match action {
"move_up" => {
if auth_state.return_selected {
// Coming from return button to fields
auth_state.return_selected = false;
auth_state.current_field = 1; // Focus on password field
} else if auth_state.current_field == 1 {
// Moving from password to username/email
auth_state.current_field = 0;
} else if auth_state.current_field == 0 {
// Wrap around to buttons
auth_state.return_selected = false; // Select Login button
}
// Update cursor position when in a field
if !auth_state.return_selected {
let current_input = auth_state.get_current_input();
let max_cursor_pos = current_input.len();
auth_state.current_cursor_pos = (*ideal_cursor_column).min(max_cursor_pos);
}
Ok(format!("Navigation 'up' from functions/login"))
},
"move_down" => {
if auth_state.return_selected {
// Coming from return button to fields
auth_state.return_selected = false;
auth_state.current_field = 0; // Focus on username field
} else if auth_state.current_field == 0 {
// Moving from username/email to password
auth_state.current_field = 1;
} else if auth_state.current_field == 1 {
// Moving from password to buttons
auth_state.return_selected = false; // Select Login button
}
// Update cursor position when in a field
if !auth_state.return_selected {
let current_input = auth_state.get_current_input();
let max_cursor_pos = current_input.len();
auth_state.current_cursor_pos = (*ideal_cursor_column).min(max_cursor_pos);
}
Ok(format!("Navigation 'down' from functions/login"))
},
_ => Err("Unknown login action".into())
}
}