Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8c7a0a1ec0 | ||
|
|
a8eef8107b | ||
|
|
75cd942f39 | ||
|
|
fc04af148d | ||
|
|
d7d7fd614b | ||
|
|
10e9c3ead0 | ||
|
|
70678432c6 | ||
|
|
bb103fac6c | ||
|
|
0e1fc3f5fa | ||
|
|
7830ebdb3b | ||
|
|
b061dd3395 | ||
|
|
a6f2fa8a88 | ||
|
|
37f12ea6f0 | ||
|
|
e29b576102 | ||
|
|
b1b3cf6136 | ||
|
|
173c4c98b8 | ||
|
|
5a3067c8e5 | ||
|
|
803c748738 | ||
|
|
5879b40e8c |
6
Cargo.lock
generated
6
Cargo.lock
generated
@@ -421,7 +421,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "client"
|
||||
version = "0.3.0"
|
||||
version = "0.3.3"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"common",
|
||||
@@ -458,7 +458,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "common"
|
||||
version = "0.3.0"
|
||||
version = "0.3.3"
|
||||
dependencies = [
|
||||
"prost",
|
||||
"serde",
|
||||
@@ -2589,7 +2589,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "server"
|
||||
version = "0.3.0"
|
||||
version = "0.3.3"
|
||||
dependencies = [
|
||||
"bcrypt",
|
||||
"chrono",
|
||||
|
||||
@@ -5,7 +5,7 @@ resolver = "2"
|
||||
[workspace.package]
|
||||
# TODO: idk how to do the name, fix later
|
||||
# name = "Multieko2"
|
||||
version = "0.3.0"
|
||||
version = "0.3.3"
|
||||
edition = "2021"
|
||||
license = "GPL-3.0-or-later"
|
||||
authors = ["Filip Priečinský <filippriec@gmail.com>"]
|
||||
|
||||
@@ -6,6 +6,7 @@ use crate::{
|
||||
components::common::dialog,
|
||||
state::state::AppState,
|
||||
};
|
||||
use crate::modes::handlers::mode_manager::AppMode;
|
||||
use ratatui::{
|
||||
layout::{Alignment, Constraint, Direction, Layout, Rect, Margin},
|
||||
style::{Style, Modifier, Color},
|
||||
@@ -75,7 +76,12 @@ pub fn render_login(
|
||||
.split(chunks[2]);
|
||||
|
||||
// Login Button
|
||||
let login_active = !state.return_selected;
|
||||
let login_button_index = 0;
|
||||
let login_active = if app_state.ui.focus_outside_canvas {
|
||||
app_state.general.selected_item == login_button_index
|
||||
} else {
|
||||
false
|
||||
};
|
||||
let mut login_style = Style::default().fg(theme.fg);
|
||||
let mut login_border = Style::default().fg(theme.border);
|
||||
if login_active {
|
||||
@@ -97,7 +103,12 @@ pub fn render_login(
|
||||
);
|
||||
|
||||
// Return Button
|
||||
let return_active = state.return_selected;
|
||||
let return_button_index = 1; // Assuming Return is the second general element
|
||||
let return_active = if app_state.ui.focus_outside_canvas {
|
||||
app_state.general.selected_item == return_button_index
|
||||
} else {
|
||||
false // Not active if focus is in canvas or other modes
|
||||
};
|
||||
let mut return_style = Style::default().fg(theme.fg);
|
||||
let mut return_border = Style::default().fg(theme.border);
|
||||
if return_active {
|
||||
|
||||
@@ -27,13 +27,16 @@ pub fn render_canvas(
|
||||
.split(area);
|
||||
|
||||
// Input container styling
|
||||
let border_style = if form_state.has_unsaved_changes() {
|
||||
Style::default().fg(theme.warning)
|
||||
} else if is_edit_mode {
|
||||
Style::default().fg(theme.accent)
|
||||
} else {
|
||||
Style::default().fg(theme.secondary)
|
||||
};
|
||||
let input_container = Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.border_style(if is_edit_mode {
|
||||
form_state.has_unsaved_changes().then(|| theme.warning).unwrap_or(theme.accent)
|
||||
} else {
|
||||
theme.secondary
|
||||
})
|
||||
.border_style(border_style)
|
||||
.style(Style::default().bg(theme.bg));
|
||||
|
||||
// Input block dimensions
|
||||
|
||||
@@ -3,5 +3,4 @@
|
||||
pub mod common;
|
||||
pub mod modes;
|
||||
|
||||
pub use common::*;
|
||||
pub use modes::*;
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
// src/functions/modes/edit/auth_e.rs
|
||||
|
||||
use crate::config::binds::config::Config;
|
||||
use crate::services::grpc_client::GrpcClient;
|
||||
use crate::state::canvas_state::CanvasState;
|
||||
use crate::state::pages::form::FormState;
|
||||
use crate::state::pages::{auth::AuthState, form::FormState};
|
||||
use crate::tui::functions::common::form::{revert, save};
|
||||
use crossterm::event::{KeyCode, KeyEvent};
|
||||
use std::any::Any;
|
||||
@@ -12,7 +11,6 @@ pub async fn execute_common_action<S: CanvasState + Any>(
|
||||
action: &str,
|
||||
state: &mut S,
|
||||
grpc_client: &mut GrpcClient,
|
||||
is_saved: &mut bool,
|
||||
current_position: &mut u64,
|
||||
total_count: u64,
|
||||
) -> Result<String, Box<dyn std::error::Error>> {
|
||||
@@ -26,14 +24,15 @@ pub async fn execute_common_action<S: CanvasState + Any>(
|
||||
{
|
||||
match action {
|
||||
"save" => {
|
||||
save(
|
||||
let outcome = save(
|
||||
form_state,
|
||||
grpc_client,
|
||||
is_saved,
|
||||
current_position,
|
||||
total_count,
|
||||
)
|
||||
.await
|
||||
.await?;
|
||||
let message = format!("Save successful: {:?}", outcome); // Simple message for now
|
||||
Ok(message)
|
||||
}
|
||||
"revert" => {
|
||||
revert(
|
||||
@@ -62,10 +61,9 @@ pub async fn execute_edit_action<S: CanvasState>(
|
||||
key: KeyEvent,
|
||||
state: &mut S,
|
||||
ideal_cursor_column: &mut usize,
|
||||
_grpc_client: &mut GrpcClient,
|
||||
_is_saved: &mut bool,
|
||||
_current_position: &mut u64,
|
||||
_total_count: u64,
|
||||
grpc_client: &mut GrpcClient,
|
||||
current_position: &mut u64,
|
||||
total_count: u64,
|
||||
) -> Result<String, Box<dyn std::error::Error>> {
|
||||
match action {
|
||||
"insert_char" => {
|
||||
@@ -173,11 +171,7 @@ pub async fn execute_edit_action<S: CanvasState>(
|
||||
let num_fields = state.fields().len();
|
||||
if num_fields > 0 {
|
||||
let current_field = state.current_field();
|
||||
let new_field = if current_field == 0 {
|
||||
num_fields - 1
|
||||
} else {
|
||||
current_field - 1
|
||||
};
|
||||
let new_field = current_field.saturating_sub(1);
|
||||
state.set_current_field(new_field);
|
||||
let current_input = state.get_current_input();
|
||||
let max_pos = current_input.len();
|
||||
@@ -191,7 +185,7 @@ pub async fn execute_edit_action<S: CanvasState>(
|
||||
"move_down" => {
|
||||
let num_fields = state.fields().len();
|
||||
if num_fields > 0 {
|
||||
let new_field = (state.current_field() + 1) % num_fields;
|
||||
let new_field = (state.current_field() + 1).min(num_fields - 1);
|
||||
state.set_current_field(new_field);
|
||||
let current_input = state.get_current_input();
|
||||
let max_pos = current_input.len();
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
// src/functions/modes/edit/form_e.rs
|
||||
|
||||
use crate::config::binds::config::Config;
|
||||
use crate::services::grpc_client::GrpcClient;
|
||||
use crate::state::canvas_state::CanvasState;
|
||||
use crate::state::pages::form::FormState;
|
||||
use crate::tui::functions::common::form::{revert, save};
|
||||
use crate::tui::functions::common::form::SaveOutcome;
|
||||
use crate::modes::handlers::event::EventOutcome;
|
||||
use crossterm::event::{KeyCode, KeyEvent};
|
||||
use std::any::Any;
|
||||
|
||||
@@ -12,48 +13,61 @@ pub async fn execute_common_action<S: CanvasState + Any>(
|
||||
action: &str,
|
||||
state: &mut S,
|
||||
grpc_client: &mut GrpcClient,
|
||||
is_saved: &mut bool,
|
||||
current_position: &mut u64,
|
||||
total_count: u64,
|
||||
) -> Result<String, Box<dyn std::error::Error>> {
|
||||
) -> Result<EventOutcome, Box<dyn std::error::Error>> {
|
||||
match action {
|
||||
"save" | "revert" => {
|
||||
if !state.has_unsaved_changes() {
|
||||
return Ok("No changes to save or revert.".to_string());
|
||||
return Ok(EventOutcome::Ok("No changes to save or revert.".to_string()));
|
||||
}
|
||||
if let Some(form_state) =
|
||||
(state as &mut dyn Any).downcast_mut::<FormState>()
|
||||
{
|
||||
match action {
|
||||
"save" => {
|
||||
save(
|
||||
let save_result = save(
|
||||
form_state,
|
||||
grpc_client,
|
||||
is_saved,
|
||||
current_position,
|
||||
total_count,
|
||||
)
|
||||
.await
|
||||
).await;
|
||||
|
||||
match save_result {
|
||||
Ok(save_outcome) => {
|
||||
let message = match save_outcome {
|
||||
SaveOutcome::NoChange => "No changes to save.".to_string(),
|
||||
SaveOutcome::UpdatedExisting => "Entry updated.".to_string(),
|
||||
SaveOutcome::CreatedNew(_) => "New entry created.".to_string(),
|
||||
};
|
||||
Ok(EventOutcome::DataSaved(save_outcome, message))
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
"revert" => {
|
||||
revert(
|
||||
let revert_result = revert(
|
||||
form_state,
|
||||
grpc_client,
|
||||
current_position,
|
||||
total_count,
|
||||
)
|
||||
.await
|
||||
).await;
|
||||
|
||||
match revert_result {
|
||||
Ok(message) => Ok(EventOutcome::Ok(message)),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
} else {
|
||||
Ok(format!(
|
||||
Ok(EventOutcome::Ok(format!(
|
||||
"Action '{}' not implemented for this state type.",
|
||||
action
|
||||
))
|
||||
)))
|
||||
}
|
||||
}
|
||||
_ => Ok(format!("Common action '{}' not handled here.", action)),
|
||||
_ => Ok(EventOutcome::Ok(format!("Common action '{}' not handled here.", action))),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,10 +76,9 @@ pub async fn execute_edit_action<S: CanvasState>(
|
||||
key: KeyEvent,
|
||||
state: &mut S,
|
||||
ideal_cursor_column: &mut usize,
|
||||
_grpc_client: &mut GrpcClient,
|
||||
_is_saved: &mut bool,
|
||||
_current_position: &mut u64,
|
||||
_total_count: u64,
|
||||
grpc_client: &mut GrpcClient,
|
||||
current_position: &mut u64,
|
||||
total_count: u64,
|
||||
) -> Result<String, Box<dyn std::error::Error>> {
|
||||
match action {
|
||||
"insert_char" => {
|
||||
@@ -173,11 +186,7 @@ pub async fn execute_edit_action<S: CanvasState>(
|
||||
let num_fields = state.fields().len();
|
||||
if num_fields > 0 {
|
||||
let current_field = state.current_field();
|
||||
let new_field = if current_field == 0 {
|
||||
num_fields - 1
|
||||
} else {
|
||||
current_field - 1
|
||||
};
|
||||
let new_field = current_field.saturating_sub(1);
|
||||
state.set_current_field(new_field);
|
||||
let current_input = state.get_current_input();
|
||||
let max_pos = current_input.len();
|
||||
@@ -191,7 +200,7 @@ pub async fn execute_edit_action<S: CanvasState>(
|
||||
"move_down" => {
|
||||
let num_fields = state.fields().len();
|
||||
if num_fields > 0 {
|
||||
let new_field = (state.current_field() + 1) % num_fields;
|
||||
let new_field = (state.current_field() + 1).min(num_fields - 1);
|
||||
state.set_current_field(new_field);
|
||||
let current_input = state.get_current_input();
|
||||
let max_pos = current_input.len();
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
use crate::config::binds::key_sequences::KeySequenceTracker;
|
||||
use crate::state::canvas_state::CanvasState;
|
||||
use crate::state::state::AppState;
|
||||
use std::error::Error;
|
||||
|
||||
#[derive(PartialEq)]
|
||||
@@ -13,6 +14,7 @@ enum CharType {
|
||||
|
||||
pub async fn execute_action<S: CanvasState>(
|
||||
action: &str,
|
||||
app_state: &mut AppState,
|
||||
state: &mut S,
|
||||
ideal_cursor_column: &mut usize,
|
||||
key_sequence_tracker: &mut KeySequenceTracker,
|
||||
@@ -33,11 +35,7 @@ pub async fn execute_action<S: CanvasState>(
|
||||
return Ok("No fields to navigate.".to_string());
|
||||
}
|
||||
let current_field = state.current_field();
|
||||
let new_field = if current_field == 0 {
|
||||
num_fields - 1
|
||||
} else {
|
||||
current_field - 1
|
||||
};
|
||||
let new_field = current_field.saturating_sub(1);
|
||||
state.set_current_field(new_field);
|
||||
let current_input = state.get_current_input();
|
||||
let max_cursor_pos = if current_input.is_empty() {
|
||||
@@ -57,17 +55,28 @@ pub async fn execute_action<S: CanvasState>(
|
||||
return Ok("No fields to navigate.".to_string());
|
||||
}
|
||||
let current_field = state.current_field();
|
||||
let new_field = (current_field + 1) % num_fields;
|
||||
state.set_current_field(new_field);
|
||||
let current_input = state.get_current_input();
|
||||
let max_cursor_pos = if current_input.is_empty() {
|
||||
0
|
||||
let last_field_index = num_fields - 1;
|
||||
|
||||
if current_field == last_field_index {
|
||||
// Already on the last field, move focus outside
|
||||
app_state.ui.focus_outside_canvas = true;
|
||||
app_state.general.selected_item = 0; // Focus first general item (e.g., Login button)
|
||||
key_sequence_tracker.reset();
|
||||
Ok("Focus moved below canvas".to_string())
|
||||
} else {
|
||||
current_input.len().saturating_sub(1)
|
||||
};
|
||||
let new_pos = (*ideal_cursor_column).min(max_cursor_pos);
|
||||
state.set_current_cursor_pos(new_pos);
|
||||
Ok("move down from functions/modes/read_only/auth_ro.rs".to_string())
|
||||
// Move to the next field within the canvas
|
||||
let new_field = (current_field + 1).min(last_field_index);
|
||||
state.set_current_field(new_field);
|
||||
let current_input = state.get_current_input();
|
||||
let max_cursor_pos = if current_input.is_empty() {
|
||||
0
|
||||
} else {
|
||||
current_input.len().saturating_sub(1)
|
||||
};
|
||||
let new_pos = (*ideal_cursor_column).min(max_cursor_pos);
|
||||
state.set_current_cursor_pos(new_pos);
|
||||
Ok("".to_string()) // Clear previous debug message
|
||||
}
|
||||
}
|
||||
"move_first_line" => {
|
||||
key_sequence_tracker.reset();
|
||||
|
||||
@@ -33,11 +33,7 @@ pub async fn execute_action<S: CanvasState>(
|
||||
return Ok("No fields to navigate.".to_string());
|
||||
}
|
||||
let current_field = state.current_field();
|
||||
let new_field = if current_field == 0 {
|
||||
num_fields - 1
|
||||
} else {
|
||||
current_field - 1
|
||||
};
|
||||
let new_field = current_field.saturating_sub(1);
|
||||
state.set_current_field(new_field);
|
||||
let current_input = state.get_current_input();
|
||||
let max_cursor_pos = if current_input.is_empty() {
|
||||
@@ -56,7 +52,7 @@ pub async fn execute_action<S: CanvasState>(
|
||||
return Ok("No fields to navigate.".to_string());
|
||||
}
|
||||
let current_field = state.current_field();
|
||||
let new_field = (current_field + 1) % num_fields;
|
||||
let new_field = (current_field + 1).min(num_fields - 1);
|
||||
state.set_current_field(new_field);
|
||||
let current_input = state.get_current_input();
|
||||
let max_cursor_pos = if current_input.is_empty() {
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
// src/modes/canvas/common.rs
|
||||
// src/modes/canvas/common_mode.rs
|
||||
|
||||
use crate::tui::terminal::core::TerminalCore;
|
||||
use crate::state::pages::{form::FormState, auth::AuthState};
|
||||
use crate::state::state::AppState;
|
||||
use crate::services::grpc_client::GrpcClient;
|
||||
use crate::services::auth::AuthClient;
|
||||
use crate::modes::handlers::event::EventOutcome;
|
||||
use crate::tui::functions::common::form::SaveOutcome;
|
||||
use crate::tui::functions::common::{
|
||||
form::{save as form_save, revert as form_revert},
|
||||
login::{save as login_save, revert as login_revert}
|
||||
@@ -20,46 +22,54 @@ pub async fn handle_core_action(
|
||||
app_state: &mut AppState,
|
||||
current_position: &mut u64,
|
||||
total_count: u64,
|
||||
) -> Result<(bool, String), Box<dyn std::error::Error>> {
|
||||
) -> Result<EventOutcome, Box<dyn std::error::Error>> {
|
||||
match action {
|
||||
"save" => {
|
||||
if app_state.ui.show_login {
|
||||
let message = login_save(auth_state, auth_client, app_state).await?;
|
||||
Ok((false, message))
|
||||
Ok(EventOutcome::Ok(message))
|
||||
} else {
|
||||
let message = form_save(
|
||||
let save_outcome = form_save(
|
||||
form_state,
|
||||
grpc_client,
|
||||
&mut app_state.ui.is_saved,
|
||||
current_position,
|
||||
total_count,
|
||||
).await?;
|
||||
Ok((false, message))
|
||||
let message = match save_outcome {
|
||||
SaveOutcome::NoChange => "No changes to save.".to_string(),
|
||||
SaveOutcome::UpdatedExisting => "Entry updated.".to_string(),
|
||||
SaveOutcome::CreatedNew(_) => "New entry created.".to_string(),
|
||||
};
|
||||
Ok(EventOutcome::DataSaved(save_outcome, message))
|
||||
}
|
||||
},
|
||||
"force_quit" => {
|
||||
terminal.cleanup()?;
|
||||
Ok((true, "Force exiting without saving.".to_string()))
|
||||
Ok(EventOutcome::Exit("Force exiting without saving.".to_string()))
|
||||
},
|
||||
"save_and_quit" => {
|
||||
let message = if app_state.ui.show_login {
|
||||
login_save(auth_state, auth_client, app_state).await?
|
||||
} else {
|
||||
form_save(
|
||||
let save_outcome = form_save(
|
||||
form_state,
|
||||
grpc_client,
|
||||
&mut app_state.ui.is_saved,
|
||||
current_position,
|
||||
total_count,
|
||||
).await?
|
||||
).await?;
|
||||
match save_outcome {
|
||||
SaveOutcome::NoChange => "No changes to save.".to_string(),
|
||||
SaveOutcome::UpdatedExisting => "Entry updated.".to_string(),
|
||||
SaveOutcome::CreatedNew(_) => "New entry created.".to_string(),
|
||||
}
|
||||
};
|
||||
terminal.cleanup()?;
|
||||
Ok((true, format!("{}. Exiting application.", message)))
|
||||
Ok(EventOutcome::Exit(format!("{}. Exiting application.", message)))
|
||||
},
|
||||
"revert" => {
|
||||
if app_state.ui.show_login {
|
||||
let message = login_revert(auth_state, app_state).await;
|
||||
Ok((false, message))
|
||||
Ok(EventOutcome::Ok(message))
|
||||
} else {
|
||||
let message = form_revert(
|
||||
form_state,
|
||||
@@ -67,9 +77,9 @@ pub async fn handle_core_action(
|
||||
current_position,
|
||||
total_count,
|
||||
).await?;
|
||||
Ok((false, message))
|
||||
Ok(EventOutcome::Ok(message))
|
||||
}
|
||||
},
|
||||
_ => Ok((false, format!("Core action not handled: {}", action))),
|
||||
_ => Ok(EventOutcome::Ok(format!("Core action not handled: {}", action))),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
use crate::config::binds::config::Config;
|
||||
use crate::services::grpc_client::GrpcClient;
|
||||
use crate::state::pages::{auth::AuthState, form::FormState};
|
||||
use crate::state::state::AppState;
|
||||
use crate::functions::modes::edit::{auth_e, form_e};
|
||||
use crate::modes::handlers::event::EventOutcome;
|
||||
use crossterm::event::{KeyCode, KeyEvent};
|
||||
|
||||
pub async fn handle_edit_event(
|
||||
@@ -15,7 +15,6 @@ pub async fn handle_edit_event(
|
||||
auth_state: &mut AuthState,
|
||||
ideal_cursor_column: &mut usize,
|
||||
command_message: &mut String,
|
||||
is_saved: &mut bool,
|
||||
current_position: &mut u64,
|
||||
total_count: u64,
|
||||
grpc_client: &mut GrpcClient,
|
||||
@@ -38,12 +37,11 @@ pub async fn handle_edit_event(
|
||||
key.modifiers
|
||||
) {
|
||||
if matches!(action, "save" | "revert") {
|
||||
return if is_auth_context {
|
||||
let message = if is_auth_context {
|
||||
auth_e::execute_common_action(
|
||||
action,
|
||||
auth_state, // Concrete AuthState
|
||||
grpc_client,
|
||||
is_saved,
|
||||
current_position,
|
||||
total_count
|
||||
).await
|
||||
@@ -52,11 +50,19 @@ pub async fn handle_edit_event(
|
||||
action,
|
||||
form_state, // Concrete FormState
|
||||
grpc_client,
|
||||
is_saved,
|
||||
current_position,
|
||||
total_count
|
||||
).await
|
||||
};
|
||||
.map(|outcome| match outcome {
|
||||
EventOutcome::Ok(msg) => msg,
|
||||
EventOutcome::Exit(msg) => format!("Exit requested: {}", msg), // Or handle differently
|
||||
EventOutcome::DataSaved(save_outcome, msg) => format!("Data saved ({:?}): {}", save_outcome, msg),
|
||||
EventOutcome::ButtonSelected { context, index } => {
|
||||
"Unexpected action in edit mode".to_string()
|
||||
}
|
||||
})
|
||||
}?;
|
||||
return Ok(message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +75,6 @@ pub async fn handle_edit_event(
|
||||
auth_state, // Full access to AuthState fields
|
||||
ideal_cursor_column,
|
||||
grpc_client,
|
||||
is_saved,
|
||||
current_position,
|
||||
total_count
|
||||
).await
|
||||
@@ -80,7 +85,6 @@ pub async fn handle_edit_event(
|
||||
form_state, // Full access to FormState fields
|
||||
ideal_cursor_column,
|
||||
grpc_client,
|
||||
is_saved,
|
||||
current_position,
|
||||
total_count
|
||||
).await
|
||||
@@ -96,7 +100,6 @@ pub async fn handle_edit_event(
|
||||
auth_state,
|
||||
ideal_cursor_column,
|
||||
grpc_client,
|
||||
is_saved,
|
||||
current_position,
|
||||
total_count
|
||||
).await
|
||||
@@ -107,7 +110,6 @@ pub async fn handle_edit_event(
|
||||
form_state,
|
||||
ideal_cursor_column,
|
||||
grpc_client,
|
||||
is_saved,
|
||||
current_position,
|
||||
total_count
|
||||
).await
|
||||
|
||||
@@ -6,11 +6,12 @@ use crate::services::grpc_client::GrpcClient;
|
||||
use crate::state::canvas_state::CanvasState;
|
||||
use crate::state::pages::auth::AuthState;
|
||||
use crate::state::pages::form::FormState;
|
||||
use crate::state::state::AppState;
|
||||
use crate::functions::modes::read_only::{auth_ro, form_ro};
|
||||
use crossterm::event::KeyEvent;
|
||||
|
||||
pub async fn handle_read_only_event(
|
||||
app_state: &crate::state::state::AppState,
|
||||
app_state: &mut AppState,
|
||||
key: KeyEvent,
|
||||
config: &Config,
|
||||
form_state: &mut FormState,
|
||||
@@ -90,6 +91,7 @@ pub async fn handle_read_only_event(
|
||||
} else if app_state.ui.show_login {
|
||||
auth_ro::execute_action(
|
||||
action,
|
||||
app_state,
|
||||
auth_state,
|
||||
ideal_cursor_column,
|
||||
key_sequence_tracker,
|
||||
@@ -136,6 +138,7 @@ pub async fn handle_read_only_event(
|
||||
} else if app_state.ui.show_login {
|
||||
auth_ro::execute_action(
|
||||
action,
|
||||
app_state,
|
||||
auth_state,
|
||||
ideal_cursor_column,
|
||||
key_sequence_tracker,
|
||||
@@ -181,6 +184,7 @@ pub async fn handle_read_only_event(
|
||||
} else if app_state.ui.show_login {
|
||||
auth_ro::execute_action(
|
||||
action,
|
||||
app_state,
|
||||
auth_state,
|
||||
ideal_cursor_column,
|
||||
key_sequence_tracker,
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
// src/modes/handlers/command_mode.rs
|
||||
// src/modes/common/command_mode.rs
|
||||
|
||||
use crossterm::event::{KeyEvent, KeyCode, KeyModifiers};
|
||||
use crate::config::binds::config::Config;
|
||||
use crate::services::grpc_client::GrpcClient;
|
||||
use crate::state::pages::form::FormState;
|
||||
use crate::state::{state::AppState, pages::auth::AuthState};
|
||||
use crate::modes::common::commands::CommandHandler;
|
||||
use crate::tui::terminal::core::TerminalCore;
|
||||
use crate::tui::functions::common::form::{save, revert};
|
||||
use crate::modes::handlers::event::EventOutcome;
|
||||
use crate::tui::functions::common::form::SaveOutcome;
|
||||
use std::error::Error;
|
||||
|
||||
pub async fn handle_command_event(
|
||||
key: KeyEvent,
|
||||
config: &Config,
|
||||
app_state: &AppState,
|
||||
auth_state: &AuthState,
|
||||
form_state: &mut FormState,
|
||||
command_input: &mut String,
|
||||
command_message: &mut String,
|
||||
@@ -19,15 +25,12 @@ pub async fn handle_command_event(
|
||||
terminal: &mut TerminalCore,
|
||||
current_position: &mut u64,
|
||||
total_count: u64,
|
||||
) -> Result<(bool, String, bool), Box<dyn std::error::Error>> {
|
||||
|
||||
// Return value: (should_exit, message, should_exit_command_mode)
|
||||
|
||||
) -> Result<EventOutcome, Box<dyn Error>> {
|
||||
// Exit command mode (via configurable keybinding)
|
||||
if config.is_exit_command_mode(key.code, key.modifiers) {
|
||||
command_input.clear();
|
||||
*command_message = "".to_string();
|
||||
return Ok((false, "".to_string(), true));
|
||||
return Ok(EventOutcome::Ok("Exited command mode".to_string()));
|
||||
}
|
||||
|
||||
// Execute command (via configurable keybinding, defaults to Enter)
|
||||
@@ -35,6 +38,8 @@ pub async fn handle_command_event(
|
||||
return process_command(
|
||||
config,
|
||||
form_state,
|
||||
app_state,
|
||||
auth_state,
|
||||
command_input,
|
||||
command_message,
|
||||
grpc_client,
|
||||
@@ -48,7 +53,7 @@ pub async fn handle_command_event(
|
||||
// Backspace (via configurable keybinding, defaults to Backspace)
|
||||
if config.is_command_backspace(key.code, key.modifiers) {
|
||||
command_input.pop();
|
||||
return Ok((false, "".to_string(), false));
|
||||
return Ok(EventOutcome::Ok("".to_string()));
|
||||
}
|
||||
|
||||
// Regular character input - accept any character in command mode
|
||||
@@ -56,17 +61,19 @@ pub async fn handle_command_event(
|
||||
// Accept regular or shifted characters (e.g., 'a' or 'A')
|
||||
if key.modifiers.is_empty() || key.modifiers == KeyModifiers::SHIFT {
|
||||
command_input.push(c);
|
||||
return Ok((false, "".to_string(), false));
|
||||
return Ok(EventOutcome::Ok("".to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
// Ignore all other keys
|
||||
Ok((false, "".to_string(), false))
|
||||
Ok(EventOutcome::Ok("".to_string()))
|
||||
}
|
||||
|
||||
async fn process_command(
|
||||
config: &Config,
|
||||
form_state: &mut FormState,
|
||||
app_state: &AppState,
|
||||
auth_state: &AuthState,
|
||||
command_input: &mut String,
|
||||
command_message: &mut String,
|
||||
grpc_client: &mut GrpcClient,
|
||||
@@ -74,12 +81,12 @@ async fn process_command(
|
||||
terminal: &mut TerminalCore,
|
||||
current_position: &mut u64,
|
||||
total_count: u64,
|
||||
) -> Result<(bool, String, bool), Box<dyn std::error::Error>> {
|
||||
) -> Result<EventOutcome, Box<dyn Error>> {
|
||||
// Clone the trimmed command to avoid borrow issues
|
||||
let command = command_input.trim().to_string();
|
||||
if command.is_empty() {
|
||||
*command_message = "Empty command".to_string();
|
||||
return Ok((false, command_message.clone(), false));
|
||||
return Ok(EventOutcome::Ok(command_message.clone()));
|
||||
}
|
||||
|
||||
// Get the action for the command (now checks global and common bindings too)
|
||||
@@ -89,21 +96,35 @@ async fn process_command(
|
||||
match action {
|
||||
"force_quit" | "save_and_quit" | "quit" => {
|
||||
let (should_exit, message) = command_handler
|
||||
.handle_command(action, terminal)
|
||||
.handle_command(
|
||||
action,
|
||||
terminal,
|
||||
app_state,
|
||||
form_state,
|
||||
auth_state,
|
||||
)
|
||||
.await?;
|
||||
command_input.clear();
|
||||
Ok((should_exit, message, true))
|
||||
if should_exit {
|
||||
Ok(EventOutcome::Exit(message))
|
||||
} else {
|
||||
Ok(EventOutcome::Ok(message))
|
||||
}
|
||||
},
|
||||
"save" => {
|
||||
let message = save(
|
||||
let outcome = save(
|
||||
form_state,
|
||||
grpc_client,
|
||||
&mut command_handler.is_saved,
|
||||
current_position,
|
||||
total_count,
|
||||
).await?;
|
||||
let message = match outcome {
|
||||
SaveOutcome::CreatedNew(_) => "New entry created".to_string(),
|
||||
SaveOutcome::UpdatedExisting => "Entry updated".to_string(),
|
||||
SaveOutcome::NoChange => "No changes to save".to_string(),
|
||||
};
|
||||
command_input.clear();
|
||||
return Ok((false, message, true));
|
||||
Ok(EventOutcome::DataSaved(outcome, message))
|
||||
},
|
||||
"revert" => {
|
||||
let message = revert(
|
||||
@@ -113,17 +134,12 @@ async fn process_command(
|
||||
total_count,
|
||||
).await?;
|
||||
command_input.clear();
|
||||
return Ok((false, message, true));
|
||||
},
|
||||
"unknown" => {
|
||||
let message = format!("Unknown command: {}", command);
|
||||
command_input.clear();
|
||||
return Ok((false, message, true));
|
||||
Ok(EventOutcome::Ok(message))
|
||||
},
|
||||
_ => {
|
||||
let message = format!("Unhandled action: {}", action);
|
||||
command_input.clear();
|
||||
return Ok((false, message, true));
|
||||
Ok(EventOutcome::Ok(message))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,26 @@
|
||||
// src/tui/controls/commands.rs
|
||||
// src/modes/common/commands.rs
|
||||
use crate::tui::terminal::core::TerminalCore;
|
||||
use crate::state::state::AppState;
|
||||
use crate::state::pages::{form::FormState, auth::AuthState};
|
||||
use crate::state::canvas_state::CanvasState;
|
||||
|
||||
pub struct CommandHandler {
|
||||
pub is_saved: bool,
|
||||
}
|
||||
pub struct CommandHandler;
|
||||
|
||||
impl CommandHandler {
|
||||
pub fn new() -> Self {
|
||||
Self { is_saved: false }
|
||||
Self
|
||||
}
|
||||
|
||||
pub async fn handle_command(
|
||||
&mut self,
|
||||
action: &str,
|
||||
terminal: &mut TerminalCore,
|
||||
app_state: &AppState,
|
||||
form_state: &FormState,
|
||||
auth_state: &AuthState,
|
||||
) -> Result<(bool, String), Box<dyn std::error::Error>> {
|
||||
match action {
|
||||
"quit" => self.handle_quit(terminal).await,
|
||||
"quit" => self.handle_quit(terminal, app_state, form_state, auth_state).await,
|
||||
"force_quit" => self.handle_force_quit(terminal).await,
|
||||
"save_and_quit" => self.handle_save_quit(terminal).await,
|
||||
_ => Ok((false, format!("Unknown command: {}", action))),
|
||||
@@ -26,8 +30,18 @@ impl CommandHandler {
|
||||
async fn handle_quit(
|
||||
&self,
|
||||
terminal: &mut TerminalCore,
|
||||
app_state: &AppState,
|
||||
form_state: &FormState,
|
||||
auth_state: &AuthState,
|
||||
) -> Result<(bool, String), Box<dyn std::error::Error>> {
|
||||
if self.is_saved {
|
||||
// Use actual unsaved changes state instead of is_saved flag
|
||||
let has_unsaved = if app_state.ui.show_login {
|
||||
auth_state.has_unsaved_changes()
|
||||
} else {
|
||||
form_state.has_unsaved_changes
|
||||
};
|
||||
|
||||
if !has_unsaved {
|
||||
terminal.cleanup()?;
|
||||
Ok((true, "Exiting.".into()))
|
||||
} else {
|
||||
@@ -47,7 +61,6 @@ impl CommandHandler {
|
||||
&mut self,
|
||||
terminal: &mut TerminalCore,
|
||||
) -> Result<(bool, String), Box<dyn std::error::Error>> {
|
||||
self.is_saved = true;
|
||||
terminal.cleanup()?;
|
||||
Ok((true, "State saved. Exiting.".into()))
|
||||
}
|
||||
|
||||
@@ -4,65 +4,88 @@ use crossterm::event::KeyEvent;
|
||||
use crate::config::binds::config::Config;
|
||||
use crate::state::state::AppState;
|
||||
use crate::state::pages::form::FormState;
|
||||
use crate::tui::functions::{intro, admin};
|
||||
use crate::state::pages::auth::AuthState;
|
||||
use crate::state::canvas_state::CanvasState;
|
||||
use crate::ui::handlers::context::UiContext;
|
||||
use crate::modes::handlers::event::EventOutcome;
|
||||
|
||||
pub async fn handle_navigation_event(
|
||||
key: KeyEvent,
|
||||
config: &Config,
|
||||
form_state: &mut FormState,
|
||||
app_state: &mut AppState,
|
||||
auth_state: &mut AuthState,
|
||||
command_mode: &mut bool,
|
||||
command_input: &mut String,
|
||||
command_message: &mut String,
|
||||
) -> Result<(bool, String), Box<dyn std::error::Error>> {
|
||||
) -> Result<EventOutcome, Box<dyn std::error::Error>> {
|
||||
if let Some(action) = config.get_general_action(key.code, key.modifiers) {
|
||||
match action {
|
||||
"move_up" => {
|
||||
move_up(app_state);
|
||||
return Ok((false, String::new()));
|
||||
move_up(app_state, auth_state);
|
||||
return Ok(EventOutcome::Ok(String::new()));
|
||||
}
|
||||
"move_down" => {
|
||||
move_down(app_state);
|
||||
return Ok((false, String::new()));
|
||||
return Ok(EventOutcome::Ok(String::new()));
|
||||
}
|
||||
"next_option" => {
|
||||
next_option(app_state); // Intro has 2 options
|
||||
return Ok((false, String::new()));
|
||||
next_option(app_state);
|
||||
return Ok(EventOutcome::Ok(String::new()));
|
||||
}
|
||||
"previous_option" => {
|
||||
previous_option(app_state);
|
||||
return Ok((false, String::new()));
|
||||
}
|
||||
"select" => {
|
||||
select(app_state);
|
||||
return Ok((false, "Selected".to_string()));
|
||||
return Ok(EventOutcome::Ok(String::new()));
|
||||
}
|
||||
"toggle_sidebar" => {
|
||||
toggle_sidebar(app_state);
|
||||
return Ok((false, format!("Sidebar {}",
|
||||
return Ok(EventOutcome::Ok(format!("Sidebar {}",
|
||||
if app_state.ui.show_sidebar { "shown" } else { "hidden" }
|
||||
)));
|
||||
}
|
||||
"next_field" => {
|
||||
next_field(form_state);
|
||||
return Ok((false, String::new()));
|
||||
return Ok(EventOutcome::Ok(String::new()));
|
||||
}
|
||||
"prev_field" => {
|
||||
prev_field(form_state);
|
||||
return Ok((false, String::new()));
|
||||
return Ok(EventOutcome::Ok(String::new()));
|
||||
}
|
||||
"enter_command_mode" => {
|
||||
handle_enter_command_mode(command_mode, command_input, command_message);
|
||||
return Ok((false, String::new()));
|
||||
return Ok(EventOutcome::Ok(String::new()));
|
||||
}
|
||||
"select" => {
|
||||
let (context, index) = if app_state.ui.show_intro {
|
||||
(UiContext::Intro, app_state.ui.intro_state.selected_option)
|
||||
} else if app_state.ui.show_login && app_state.ui.focus_outside_canvas {
|
||||
(UiContext::Login, app_state.general.selected_item)
|
||||
} else if app_state.ui.show_admin {
|
||||
(UiContext::Admin, app_state.general.selected_item)
|
||||
} else if app_state.ui.dialog.dialog_show {
|
||||
(UiContext::Dialog, app_state.ui.dialog.dialog_active_button_index)
|
||||
} else {
|
||||
// Handle cases where select is pressed but no button context applies
|
||||
return Ok(EventOutcome::Ok("Select (No Action)".to_string()));
|
||||
};
|
||||
return Ok(EventOutcome::ButtonSelected { context, index });
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok((false, String::new()))
|
||||
Ok(EventOutcome::Ok(String::new()))
|
||||
}
|
||||
|
||||
pub fn move_up(app_state: &mut AppState) {
|
||||
if app_state.ui.show_intro {
|
||||
pub fn move_up(app_state: &mut AppState, auth_state: &mut AuthState) {
|
||||
if app_state.ui.focus_outside_canvas && app_state.ui.show_login {
|
||||
if app_state.general.selected_item == 0 {
|
||||
app_state.ui.focus_outside_canvas = false;
|
||||
let last_field_index = auth_state.fields().len().saturating_sub(1);
|
||||
auth_state.set_current_field(last_field_index);
|
||||
} else {
|
||||
app_state.general.selected_item = app_state.general.selected_item.saturating_sub(1);
|
||||
}
|
||||
} else if app_state.ui.show_intro {
|
||||
app_state.ui.intro_state.previous_option();
|
||||
} else if app_state.ui.show_admin {
|
||||
// Assuming profile_tree.profiles is the list we're navigating
|
||||
@@ -81,7 +104,12 @@ pub fn move_up(app_state: &mut AppState) {
|
||||
}
|
||||
|
||||
pub fn move_down(app_state: &mut AppState) {
|
||||
if app_state.ui.show_intro {
|
||||
if app_state.ui.focus_outside_canvas && app_state.ui.show_login {
|
||||
let num_general_elements = 2;
|
||||
if app_state.general.selected_item < num_general_elements - 1 {
|
||||
app_state.general.selected_item += 1;
|
||||
}
|
||||
} else if app_state.ui.show_intro {
|
||||
app_state.ui.intro_state.next_option();
|
||||
} else if app_state.ui.show_admin {
|
||||
// Assuming profile_tree.profiles is the list we're navigating
|
||||
@@ -117,14 +145,6 @@ pub fn previous_option(app_state: &mut AppState) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn select(app_state: &mut AppState) {
|
||||
if app_state.ui.show_intro {
|
||||
intro::handle_intro_selection(app_state);
|
||||
} else if app_state.ui.show_admin {
|
||||
admin::handle_admin_selection(app_state);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggle_sidebar(app_state: &mut AppState) {
|
||||
app_state.ui.show_sidebar = !app_state.ui.show_sidebar;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
// src/modes/handlers/event.rs
|
||||
use crossterm::event::Event;
|
||||
use crossterm::cursor::SetCursorStyle;
|
||||
use crate::tui::terminal::{
|
||||
core::TerminalCore,
|
||||
};
|
||||
use crate::tui::terminal::core::TerminalCore;
|
||||
use crate::services::grpc_client::GrpcClient;
|
||||
use crate::services::auth::AuthClient;
|
||||
use crate::modes::common::commands::CommandHandler;
|
||||
@@ -12,13 +10,25 @@ use crate::state::pages::form::FormState;
|
||||
use crate::state::pages::auth::AuthState;
|
||||
use crate::state::canvas_state::CanvasState;
|
||||
use crate::ui::handlers::rat_state::UiStateHandler;
|
||||
use crate::ui::handlers::context::UiContext;
|
||||
use crate::tui::functions::{intro, admin};
|
||||
use crate::tui::functions::common::login;
|
||||
use crate::modes::{
|
||||
common::{command_mode},
|
||||
common::command_mode,
|
||||
canvas::{edit, read_only, common_mode},
|
||||
general::navigation,
|
||||
};
|
||||
use crate::config::binds::key_sequences::KeySequenceTracker;
|
||||
use crate::modes::handlers::mode_manager::{ModeManager, AppMode};
|
||||
use crate::tui::functions::common::form::SaveOutcome;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum EventOutcome {
|
||||
Ok(String),
|
||||
Exit(String),
|
||||
DataSaved(SaveOutcome, String),
|
||||
ButtonSelected { context: UiContext, index: usize },
|
||||
}
|
||||
|
||||
pub struct EventHandler {
|
||||
pub command_mode: bool,
|
||||
@@ -28,7 +38,6 @@ pub struct EventHandler {
|
||||
pub edit_mode_cooldown: bool,
|
||||
pub ideal_cursor_column: usize,
|
||||
pub key_sequence_tracker: KeySequenceTracker,
|
||||
// pub auth_state: AuthState, // Removed
|
||||
pub auth_client: AuthClient,
|
||||
}
|
||||
|
||||
@@ -42,7 +51,6 @@ impl EventHandler {
|
||||
edit_mode_cooldown: false,
|
||||
ideal_cursor_column: 0,
|
||||
key_sequence_tracker: KeySequenceTracker::new(800),
|
||||
// auth_state: AuthState::new(), // Removed
|
||||
auth_client: AuthClient::new().await?,
|
||||
})
|
||||
}
|
||||
@@ -55,12 +63,11 @@ impl EventHandler {
|
||||
grpc_client: &mut GrpcClient,
|
||||
command_handler: &mut CommandHandler,
|
||||
form_state: &mut FormState,
|
||||
auth_state: &mut AuthState, // Added
|
||||
auth_state: &mut AuthState,
|
||||
app_state: &mut crate::state::state::AppState,
|
||||
total_count: u64,
|
||||
current_position: &mut u64,
|
||||
) -> Result<(bool, String), Box<dyn std::error::Error>> {
|
||||
// Determine current mode based on app state and event handler state
|
||||
) -> Result<EventOutcome, Box<dyn std::error::Error>> {
|
||||
let current_mode = ModeManager::derive_mode(app_state, self);
|
||||
app_state.update_mode(current_mode);
|
||||
|
||||
@@ -68,36 +75,71 @@ impl EventHandler {
|
||||
let key_code = key.code;
|
||||
let modifiers = key.modifiers;
|
||||
|
||||
// Handle common actions across all modes
|
||||
if UiStateHandler::toggle_sidebar(&mut app_state.ui, config, key_code, modifiers) {
|
||||
return Ok((false, format!("Sidebar {}",
|
||||
let message = format!("Sidebar {}",
|
||||
if app_state.ui.show_sidebar { "shown" } else { "hidden" }
|
||||
)));
|
||||
);
|
||||
return Ok(EventOutcome::Ok(message));
|
||||
}
|
||||
|
||||
// Mode-specific handling
|
||||
match current_mode {
|
||||
AppMode::General => {
|
||||
return navigation::handle_navigation_event(
|
||||
let nav_outcome = navigation::handle_navigation_event(
|
||||
key,
|
||||
config,
|
||||
form_state,
|
||||
app_state,
|
||||
auth_state,
|
||||
&mut self.command_mode,
|
||||
&mut self.command_input,
|
||||
&mut self.command_message,
|
||||
).await;
|
||||
match nav_outcome {
|
||||
Ok(EventOutcome::ButtonSelected { context, index }) => {
|
||||
let mut message = String::from("Selected"); // Default message
|
||||
match context {
|
||||
UiContext::Intro => {
|
||||
intro::handle_intro_selection(app_state, index); // Pass index
|
||||
message = format!("Intro Option {} selected", index);
|
||||
}
|
||||
UiContext::Login => {
|
||||
message = match index {
|
||||
0 => login::save(auth_state, &mut self.auth_client, app_state).await?,
|
||||
1 => {
|
||||
let msg = login::revert(auth_state, app_state).await;
|
||||
// Optional: Add navigation logic here if revert should change screen
|
||||
// app_state.ui.show_login = false;
|
||||
// app_state.ui.show_intro = true;
|
||||
msg
|
||||
}
|
||||
_ => "Invalid Login Option".to_string(),
|
||||
};
|
||||
}
|
||||
UiContext::Admin => {
|
||||
// Assuming handle_admin_selection uses app_state.general.selected_item
|
||||
admin::handle_admin_selection(app_state);
|
||||
message = format!("Admin Option {} selected", index);
|
||||
}
|
||||
UiContext::Dialog => {
|
||||
// Add specific dialog handling logic here
|
||||
message = format!("Dialog Button {} selected", index);
|
||||
app_state.hide_dialog(); // Example action
|
||||
}
|
||||
}
|
||||
return Ok(EventOutcome::Ok(message)); // Return Ok with message
|
||||
}
|
||||
other => return other, // Pass through Ok, Err, DataSaved directly
|
||||
}
|
||||
},
|
||||
|
||||
AppMode::ReadOnly => {
|
||||
// Check for mode transitions first
|
||||
if config.is_enter_edit_mode_before(key_code, modifiers) &&
|
||||
ModeManager::can_enter_edit_mode(current_mode) {
|
||||
self.is_edit_mode = true;
|
||||
self.edit_mode_cooldown = true;
|
||||
self.command_message = "Edit mode".to_string();
|
||||
terminal.set_cursor_style(SetCursorStyle::BlinkingBar)?;
|
||||
return Ok((false, self.command_message.clone()));
|
||||
return Ok(EventOutcome::Ok(self.command_message.clone()));
|
||||
}
|
||||
|
||||
if config.is_enter_edit_mode_after(key_code, modifiers) &&
|
||||
@@ -126,20 +168,18 @@ impl EventHandler {
|
||||
self.edit_mode_cooldown = true;
|
||||
self.command_message = "Edit mode (after cursor)".to_string();
|
||||
terminal.set_cursor_style(SetCursorStyle::BlinkingBar)?;
|
||||
return Ok((false, self.command_message.clone()));
|
||||
return Ok(EventOutcome::Ok(self.command_message.clone()));
|
||||
}
|
||||
|
||||
// Check for entering command mode
|
||||
if let Some(action) = config.get_read_only_action_for_key(key_code, modifiers) {
|
||||
if action == "enter_command_mode" && ModeManager::can_enter_command_mode(current_mode) {
|
||||
self.command_mode = true;
|
||||
self.command_input.clear();
|
||||
self.command_message.clear();
|
||||
return Ok((false, String::new()));
|
||||
return Ok(EventOutcome::Ok(String::new()));
|
||||
}
|
||||
}
|
||||
|
||||
// Check for core application actions (save, quit, etc.)
|
||||
if let Some(action) = config.get_action_for_key_in_mode(
|
||||
&config.keybindings.common,
|
||||
key_code,
|
||||
@@ -150,7 +190,7 @@ impl EventHandler {
|
||||
return common_mode::handle_core_action(
|
||||
action,
|
||||
form_state,
|
||||
auth_state, // Changed
|
||||
auth_state,
|
||||
grpc_client,
|
||||
&mut self.auth_client,
|
||||
terminal,
|
||||
@@ -163,13 +203,12 @@ impl EventHandler {
|
||||
}
|
||||
}
|
||||
|
||||
// Let read_only mode handle its own actions
|
||||
return read_only::handle_read_only_event(
|
||||
&app_state,
|
||||
let (_should_exit, message) = read_only::handle_read_only_event(
|
||||
app_state,
|
||||
key,
|
||||
config,
|
||||
form_state,
|
||||
auth_state, // Changed
|
||||
auth_state,
|
||||
&mut self.key_sequence_tracker,
|
||||
current_position,
|
||||
total_count,
|
||||
@@ -177,25 +216,27 @@ impl EventHandler {
|
||||
&mut self.command_message,
|
||||
&mut self.edit_mode_cooldown,
|
||||
&mut self.ideal_cursor_column,
|
||||
).await;
|
||||
).await?;
|
||||
return Ok(EventOutcome::Ok(message));
|
||||
},
|
||||
|
||||
AppMode::Edit => {
|
||||
// Check for exiting edit mode
|
||||
if config.is_exit_edit_mode(key_code, modifiers) {
|
||||
self.is_edit_mode = false;
|
||||
self.edit_mode_cooldown = true;
|
||||
|
||||
let has_changes = if app_state.ui.show_login {
|
||||
auth_state.has_unsaved_changes()
|
||||
} else {
|
||||
form_state.has_unsaved_changes()
|
||||
};
|
||||
|
||||
if has_changes {
|
||||
self.command_message = "Unsaved changes! Use :w to save or :q! to discard".to_string();
|
||||
return Ok((false, self.command_message.clone()));
|
||||
}
|
||||
self.is_edit_mode = false;
|
||||
self.edit_mode_cooldown = true;
|
||||
self.command_message = "Read-only mode".to_string();
|
||||
self.command_message = if has_changes {
|
||||
"Exited edit mode (unsaved changes remain)".to_string()
|
||||
} else {
|
||||
"Read-only mode".to_string()
|
||||
};
|
||||
|
||||
terminal.set_cursor_style(SetCursorStyle::SteadyBlock)?;
|
||||
|
||||
let current_input = if app_state.ui.show_login {
|
||||
@@ -219,10 +260,9 @@ impl EventHandler {
|
||||
self.ideal_cursor_column = form_state.current_cursor_pos();
|
||||
}
|
||||
}
|
||||
return Ok((false, self.command_message.clone()));
|
||||
return Ok(EventOutcome::Ok(self.command_message.clone()));
|
||||
}
|
||||
|
||||
// Check for core application actions (save, quit, etc.)
|
||||
if let Some(action) = config.get_action_for_key_in_mode(
|
||||
&config.keybindings.common,
|
||||
key_code,
|
||||
@@ -233,7 +273,7 @@ impl EventHandler {
|
||||
return common_mode::handle_core_action(
|
||||
action,
|
||||
form_state,
|
||||
auth_state, // Changed
|
||||
auth_state,
|
||||
grpc_client,
|
||||
&mut self.auth_client,
|
||||
terminal,
|
||||
@@ -246,29 +286,29 @@ impl EventHandler {
|
||||
}
|
||||
}
|
||||
|
||||
// Let edit mode handle its own actions
|
||||
let result = edit::handle_edit_event(
|
||||
let message = edit::handle_edit_event(
|
||||
app_state.ui.show_login,
|
||||
key,
|
||||
config,
|
||||
form_state,
|
||||
auth_state, // Changed
|
||||
auth_state,
|
||||
&mut self.ideal_cursor_column,
|
||||
&mut self.command_message,
|
||||
&mut app_state.ui.is_saved,
|
||||
current_position,
|
||||
total_count,
|
||||
grpc_client,
|
||||
).await?;
|
||||
|
||||
self.key_sequence_tracker.reset();
|
||||
return Ok((false, result));
|
||||
return Ok(EventOutcome::Ok(message));
|
||||
},
|
||||
|
||||
AppMode::Command => {
|
||||
let (should_exit, message, exit_command_mode) = command_mode::handle_command_event(
|
||||
let outcome = command_mode::handle_command_event(
|
||||
key,
|
||||
config,
|
||||
app_state,
|
||||
auth_state,
|
||||
form_state,
|
||||
&mut self.command_input,
|
||||
&mut self.command_message,
|
||||
@@ -278,19 +318,18 @@ impl EventHandler {
|
||||
current_position,
|
||||
total_count,
|
||||
).await?;
|
||||
|
||||
if exit_command_mode {
|
||||
self.command_mode = false;
|
||||
|
||||
if let EventOutcome::Ok(msg) = &outcome {
|
||||
if msg == "Exited command mode" {
|
||||
self.command_mode = false;
|
||||
}
|
||||
}
|
||||
|
||||
return Ok((should_exit, message));
|
||||
return Ok(outcome);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Non-key events or if no specific handler was matched
|
||||
self.edit_mode_cooldown = false;
|
||||
Ok((false, self.command_message.clone()))
|
||||
Ok(EventOutcome::Ok(self.command_message.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,10 @@ impl ModeManager {
|
||||
return AppMode::Command;
|
||||
}
|
||||
|
||||
if app_state.ui.focus_outside_canvas {
|
||||
return AppMode::General;
|
||||
}
|
||||
|
||||
if app_state.ui.show_login {
|
||||
if event_handler.is_edit_mode {
|
||||
AppMode::Edit
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
use crate::services::grpc_client::GrpcClient;
|
||||
use crate::state::pages::form::FormState;
|
||||
use crate::tui::functions::common::form::SaveOutcome;
|
||||
use crate::state::state::AppState;
|
||||
|
||||
pub struct UiService;
|
||||
@@ -85,5 +86,27 @@ impl UiService {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles the consequences of a save operation, like updating counts.
|
||||
pub async fn handle_save_outcome(
|
||||
save_outcome: SaveOutcome,
|
||||
grpc_client: &mut GrpcClient,
|
||||
app_state: &mut AppState,
|
||||
form_state: &mut FormState, // Needed to potentially update position/ID
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
match save_outcome {
|
||||
SaveOutcome::CreatedNew(new_id) => {
|
||||
// A new record was created, update the count!
|
||||
UiService::update_adresar_count(grpc_client, app_state).await?;
|
||||
// Navigate to the new record (now that count is updated)
|
||||
app_state.update_current_position(app_state.total_count);
|
||||
form_state.id = new_id; // Ensure ID is set (might be redundant if save already did it)
|
||||
}
|
||||
SaveOutcome::UpdatedExisting | SaveOutcome::NoChange => {
|
||||
// No count update needed for these outcomes
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// src/state/canvas_state.rs
|
||||
|
||||
use crate::state::pages::form::FormState;
|
||||
|
||||
pub trait CanvasState {
|
||||
fn current_field(&self) -> usize;
|
||||
|
||||
@@ -15,12 +15,12 @@ pub struct DialogState {
|
||||
|
||||
pub struct UiState {
|
||||
pub show_sidebar: bool,
|
||||
pub is_saved: bool,
|
||||
pub show_intro: bool,
|
||||
pub show_admin: bool,
|
||||
pub show_form: bool,
|
||||
pub show_login: bool,
|
||||
pub intro_state: IntroState,
|
||||
pub focus_outside_canvas: bool,
|
||||
pub dialog: DialogState, // Add dialog state here
|
||||
}
|
||||
|
||||
@@ -133,12 +133,12 @@ impl Default for UiState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
show_sidebar: true,
|
||||
is_saved: false,
|
||||
show_intro: true,
|
||||
show_admin: false,
|
||||
show_form: false,
|
||||
show_login: false,
|
||||
intro_state: IntroState::new(),
|
||||
focus_outside_canvas: false,
|
||||
dialog: DialogState::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,4 +9,3 @@ pub mod common;
|
||||
pub use admin::*;
|
||||
pub use intro::*;
|
||||
pub use form::*;
|
||||
pub use common::*;
|
||||
|
||||
@@ -3,5 +3,3 @@
|
||||
pub mod form;
|
||||
pub mod login;
|
||||
|
||||
pub use form::*;
|
||||
pub use login::*;
|
||||
|
||||
@@ -4,17 +4,26 @@ use crate::services::grpc_client::GrpcClient;
|
||||
use crate::state::pages::form::FormState;
|
||||
use common::proto::multieko2::adresar::{PostAdresarRequest, PutAdresarRequest};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum SaveOutcome {
|
||||
NoChange, // Nothing needed saving
|
||||
UpdatedExisting, // An existing record was updated
|
||||
CreatedNew(i64), // A new record was created (include its new ID)
|
||||
}
|
||||
|
||||
/// Shared logic for saving the current form state
|
||||
pub async fn save(
|
||||
form_state: &mut FormState,
|
||||
grpc_client: &mut GrpcClient,
|
||||
is_saved: &mut bool,
|
||||
current_position: &mut u64,
|
||||
total_count: u64,
|
||||
) -> Result<String, Box<dyn std::error::Error>> {
|
||||
) -> Result<SaveOutcome, Box<dyn std::error::Error>> { // <-- Return SaveOutcome
|
||||
if !form_state.has_unsaved_changes {
|
||||
return Ok(SaveOutcome::NoChange); // Early exit if no changes
|
||||
}
|
||||
let is_new = *current_position == total_count + 1;
|
||||
|
||||
let message = if is_new {
|
||||
let outcome = if is_new {
|
||||
let post_request = PostAdresarRequest {
|
||||
firma: form_state.values[0].clone(),
|
||||
kz: form_state.values[1].clone(),
|
||||
@@ -33,10 +42,9 @@ pub async fn save(
|
||||
fax: form_state.values[14].clone(),
|
||||
};
|
||||
let response = grpc_client.post_adresar(post_request).await?;
|
||||
let new_total = grpc_client.get_adresar_count().await?;
|
||||
*current_position = new_total;
|
||||
form_state.id = response.into_inner().id;
|
||||
"New entry created".to_string()
|
||||
let new_id = response.into_inner().id;
|
||||
form_state.id = new_id;
|
||||
SaveOutcome::CreatedNew(new_id) // <-- Return CreatedNew with ID
|
||||
} else {
|
||||
let put_request = PutAdresarRequest {
|
||||
id: form_state.id,
|
||||
@@ -57,12 +65,11 @@ pub async fn save(
|
||||
fax: form_state.values[14].clone(),
|
||||
};
|
||||
let _ = grpc_client.put_adresar(put_request).await?;
|
||||
"Entry updated".to_string()
|
||||
SaveOutcome::UpdatedExisting
|
||||
};
|
||||
|
||||
*is_saved = true;
|
||||
form_state.has_unsaved_changes = false;
|
||||
Ok(message)
|
||||
Ok(outcome)
|
||||
}
|
||||
|
||||
/// Discard changes since last save
|
||||
|
||||
@@ -45,16 +45,13 @@ pub async fn save(
|
||||
);
|
||||
|
||||
// Use the helper method to configure and show the dialog
|
||||
// TODO Implement logic for pressing menu or exit buttons, not imeplementing it now,
|
||||
// need to do other more important stuff now"
|
||||
app_state.show_dialog(
|
||||
"Login Success",
|
||||
&success_message,
|
||||
vec!["OK".to_string()], // Pass buttons here
|
||||
vec!["Menu".to_string(), "Exit".to_string()],
|
||||
);
|
||||
// REMOVE these lines:
|
||||
// app_state.ui.dialog.dialog_title = "Login Success".to_string();
|
||||
// app_state.ui.dialog.dialog_message = success_message;
|
||||
// app_state.ui.dialog.dialog_show = true;
|
||||
// app_state.ui.dialog.dialog_button_active = true;
|
||||
|
||||
Ok("Login successful, details shown in dialog.".to_string())
|
||||
}
|
||||
@@ -83,7 +80,7 @@ pub async fn save(
|
||||
/// Reverts the login form fields to empty and returns to the previous screen (Intro).
|
||||
pub async fn revert(
|
||||
auth_state: &mut AuthState,
|
||||
_app_state: &mut AppState, // Prefix unused variable
|
||||
app_state: &mut AppState,
|
||||
) -> String {
|
||||
// Clear the input fields
|
||||
auth_state.username.clear();
|
||||
|
||||
@@ -11,13 +11,22 @@ pub async fn handle_action(
|
||||
total_count: u64,
|
||||
ideal_cursor_column: &mut usize,
|
||||
) -> Result<String, Box<dyn std::error::Error>> {
|
||||
// TODO store unsaved changes without deleting form state values
|
||||
// First check for unsaved changes in both cases
|
||||
if form_state.has_unsaved_changes() {
|
||||
return Ok(
|
||||
"Unsaved changes. Save (Ctrl+S) or Revert (Ctrl+R) before navigating."
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
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![
|
||||
@@ -27,14 +36,14 @@ pub async fn handle_action(
|
||||
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())
|
||||
@@ -45,7 +54,7 @@ pub async fn handle_action(
|
||||
*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![
|
||||
@@ -55,14 +64,14 @@ pub async fn handle_action(
|
||||
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();
|
||||
@@ -77,4 +86,4 @@ pub async fn handle_action(
|
||||
}
|
||||
_ => Err("Unknown form action".into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::state::state::AppState;
|
||||
|
||||
pub fn handle_intro_selection(app_state: &mut AppState) {
|
||||
match app_state.ui.intro_state.selected_option {
|
||||
pub fn handle_intro_selection(app_state: &mut AppState, index: usize) { // Add index parameter
|
||||
match index { // Use index directly
|
||||
0 => { // Continue
|
||||
app_state.ui.show_form = true;
|
||||
app_state.ui.show_admin = false;
|
||||
@@ -21,3 +21,4 @@ pub fn handle_intro_selection(app_state: &mut AppState) {
|
||||
}
|
||||
app_state.ui.show_intro = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// src/tui/functions/login.rs
|
||||
use crate::state::pages::auth::AuthState;
|
||||
use crate::state::canvas_state::CanvasState;
|
||||
|
||||
pub async fn handle_action(
|
||||
action: &str,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
use crossterm::{
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
cursor::{SetCursorStyle, EnableBlinking, Show, MoveTo},
|
||||
cursor::{SetCursorStyle, EnableBlinking, Show, Hide, MoveTo},
|
||||
};
|
||||
use ratatui::{backend::CrosstermBackend, Terminal};
|
||||
use std::io::{self, stdout, Write};
|
||||
@@ -64,6 +64,22 @@ impl TerminalCore {
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn show_cursor(&mut self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
execute!(
|
||||
self.terminal.backend_mut(),
|
||||
Show
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn hide_cursor(&mut self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
execute!(
|
||||
self.terminal.backend_mut(),
|
||||
Hide
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TerminalCore {
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
pub mod ui;
|
||||
pub mod render;
|
||||
pub mod rat_state;
|
||||
pub mod context;
|
||||
|
||||
pub use ui::run_ui;
|
||||
pub use rat_state::*;
|
||||
pub use context::*;
|
||||
|
||||
8
client/src/ui/handlers/context.rs
Normal file
8
client/src/ui/handlers/context.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum UiContext {
|
||||
Intro,
|
||||
Login,
|
||||
Admin,
|
||||
Dialog,
|
||||
}
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
// src/ui/handlers/ui.rs
|
||||
|
||||
use crate::tui::terminal::TerminalCore;
|
||||
use crate::services::grpc_client::GrpcClient;
|
||||
use crate::services::auth::AuthClient;
|
||||
use crate::services::ui_service::UiService;
|
||||
use crate::tui::terminal::EventReader;
|
||||
use crate::modes::common::commands::CommandHandler;
|
||||
use crate::config::colors::themes::Theme;
|
||||
use crate::config::binds::config::Config;
|
||||
use crate::ui::handlers::render::render_ui;
|
||||
use crate::state::pages::form::FormState;
|
||||
use crate::state::pages::auth::AuthState;
|
||||
use crate::config::colors::themes::Theme;
|
||||
use crate::modes::common::commands::CommandHandler;
|
||||
use crate::modes::handlers::event::{EventHandler, EventOutcome}; // Import EventOutcome
|
||||
use crate::modes::handlers::mode_manager::{AppMode, ModeManager};
|
||||
use crate::services::grpc_client::GrpcClient;
|
||||
use crate::services::ui_service::UiService;
|
||||
use crate::state::canvas_state::CanvasState;
|
||||
use crate::modes::handlers::event::EventHandler;
|
||||
use crate::state::pages::auth::AuthState;
|
||||
use crate::state::pages::form::FormState;
|
||||
use crate::state::state::AppState;
|
||||
use crate::tui::functions::common::form::SaveOutcome; // Import SaveOutcome
|
||||
use crate::tui::terminal::{EventReader, TerminalCore};
|
||||
use crate::ui::handlers::render::render_ui;
|
||||
use crossterm::cursor::SetCursorStyle;
|
||||
|
||||
pub async fn run_ui() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let config = Config::load()?;
|
||||
let mut terminal = TerminalCore::new()?;
|
||||
let mut grpc_client = GrpcClient::new().await?;
|
||||
// let auth_client = AuthClient::new().await?; // AuthClient is now inside EventHandler
|
||||
let mut command_handler = CommandHandler::new();
|
||||
let theme = Theme::from_str(&config.colors.theme);
|
||||
let mut auth_state = AuthState::default(); // The single source of truth for AuthState
|
||||
@@ -28,7 +28,9 @@ pub async fn run_ui() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut app_state = AppState::new()?;
|
||||
|
||||
// Initialize app state with profile tree and table structure
|
||||
let column_names = UiService::initialize_app_state(&mut grpc_client, &mut app_state).await?;
|
||||
let column_names =
|
||||
UiService::initialize_app_state(&mut grpc_client, &mut app_state)
|
||||
.await?;
|
||||
|
||||
// Initialize FormState with dynamic fields
|
||||
let mut form_state = FormState::new(column_names);
|
||||
@@ -38,12 +40,11 @@ pub async fn run_ui() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let event_reader = EventReader::new();
|
||||
|
||||
// Fetch the total count of Adresar entries
|
||||
UiService::initialize_adresar_count(&mut grpc_client, &mut app_state).await?;
|
||||
UiService::initialize_adresar_count(&mut grpc_client, &mut app_state)
|
||||
.await?;
|
||||
form_state.reset_to_empty();
|
||||
|
||||
loop {
|
||||
UiService::update_adresar_count(&mut grpc_client, &mut app_state).await?;
|
||||
|
||||
// Determine edit mode based on EventHandler state
|
||||
let is_edit_mode = event_handler.is_edit_mode;
|
||||
|
||||
@@ -64,94 +65,197 @@ pub async fn run_ui() -> Result<(), Box<dyn std::error::Error>> {
|
||||
);
|
||||
})?;
|
||||
|
||||
let total_count = app_state.total_count;
|
||||
// --- Cursor Visibility Logic ---
|
||||
let current_mode = ModeManager::derive_mode(&app_state, &event_handler);
|
||||
match current_mode {
|
||||
AppMode::Edit => {
|
||||
terminal.show_cursor()?;
|
||||
}
|
||||
AppMode::ReadOnly => {
|
||||
if !app_state.ui.focus_outside_canvas {
|
||||
terminal.set_cursor_style(SetCursorStyle::SteadyBlock)?;
|
||||
} else {
|
||||
terminal.set_cursor_style(SetCursorStyle::SteadyUnderScore)?;
|
||||
}
|
||||
terminal.show_cursor()?;
|
||||
}
|
||||
AppMode::General => {
|
||||
if app_state.ui.focus_outside_canvas {
|
||||
terminal.set_cursor_style(SetCursorStyle::SteadyUnderScore)?;
|
||||
terminal.show_cursor()?;
|
||||
} else {
|
||||
terminal.hide_cursor()?;
|
||||
}
|
||||
}
|
||||
AppMode::Command => {
|
||||
terminal.set_cursor_style(SetCursorStyle::SteadyUnderScore)?;
|
||||
terminal.show_cursor()?;
|
||||
}
|
||||
}
|
||||
// --- End Cursor Visibility Logic ---
|
||||
|
||||
let total_count = app_state.total_count; // Keep track for save logic
|
||||
let mut current_position = app_state.current_position;
|
||||
let position_before_event = current_position;
|
||||
|
||||
let event = event_reader.read_event()?;
|
||||
let (should_exit, message) = event_handler.handle_event(
|
||||
event,
|
||||
&config,
|
||||
&mut terminal,
|
||||
&mut grpc_client,
|
||||
&mut command_handler,
|
||||
&mut form_state,
|
||||
&mut auth_state, // Pass the single AuthState instance here too
|
||||
&mut app_state,
|
||||
total_count,
|
||||
&mut current_position,
|
||||
).await?;
|
||||
|
||||
// Get the outcome from the event handler
|
||||
let event_outcome_result = event_handler
|
||||
.handle_event(
|
||||
event,
|
||||
&config,
|
||||
&mut terminal, // Pass terminal mutably
|
||||
&mut grpc_client,
|
||||
&mut command_handler,
|
||||
&mut form_state,
|
||||
&mut auth_state,
|
||||
&mut app_state,
|
||||
total_count, // Pass the count *before* potential save
|
||||
&mut current_position,
|
||||
)
|
||||
.await;
|
||||
|
||||
// Update position based on handler's modification
|
||||
app_state.current_position = current_position;
|
||||
|
||||
// --- Centralized Consequence Handling ---
|
||||
let mut should_exit = false;
|
||||
match event_outcome_result {
|
||||
// Handle the Result first
|
||||
Ok(outcome) => match outcome {
|
||||
// Handle the Ok variant containing EventOutcome
|
||||
EventOutcome::Ok(message) => {
|
||||
if !message.is_empty() {
|
||||
event_handler.command_message = message;
|
||||
}
|
||||
}
|
||||
EventOutcome::Exit(message) => {
|
||||
event_handler.command_message = message;
|
||||
should_exit = true;
|
||||
}
|
||||
EventOutcome::DataSaved(save_outcome, message) => {
|
||||
event_handler.command_message = message; // Show save status
|
||||
|
||||
// *** Delegate outcome handling to UiService ***
|
||||
if let Err(e) = UiService::handle_save_outcome(
|
||||
save_outcome,
|
||||
&mut grpc_client,
|
||||
&mut app_state,
|
||||
&mut form_state,
|
||||
)
|
||||
.await
|
||||
{
|
||||
// Handle potential errors from the outcome handler itself
|
||||
event_handler.command_message =
|
||||
format!("Error handling save outcome: {}", e);
|
||||
}
|
||||
// No count update needed for UpdatedExisting or NoChange
|
||||
}
|
||||
EventOutcome::ButtonSelected { context, index } => {
|
||||
event_handler.command_message = "Internal error: Unexpected button state".to_string();
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
// Handle errors from handle_event, e.g., log or display
|
||||
event_handler.command_message = format!("Error: {}", e);
|
||||
// Decide if the error is fatal, maybe set should_exit = true;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Position Change Handling (after outcome processing) ---
|
||||
let position_changed =
|
||||
app_state.current_position != position_before_event; // Calculate after potential update
|
||||
// Recalculate total_count *after* potential update
|
||||
let current_total_count = app_state.total_count;
|
||||
|
||||
// Handle position changes and update form state (Only when form is shown)
|
||||
if app_state.ui.show_form { // Added check
|
||||
if !event_handler.is_edit_mode {
|
||||
if app_state.ui.show_form {
|
||||
if position_changed && !event_handler.is_edit_mode {
|
||||
let current_input = form_state.get_current_input();
|
||||
let max_cursor_pos = if !current_input.is_empty() {
|
||||
current_input.len() - 1 // Limit to last character in readonly mode
|
||||
current_input.len() - 1 // Limit to last character in readonly mode
|
||||
} else {
|
||||
0
|
||||
};
|
||||
form_state.current_cursor_pos = event_handler.ideal_cursor_column.min(max_cursor_pos);
|
||||
form_state.current_cursor_pos =
|
||||
event_handler.ideal_cursor_column.min(max_cursor_pos);
|
||||
|
||||
// Ensure position never exceeds total_count + 1
|
||||
if app_state.current_position > total_count + 1 {
|
||||
app_state.current_position = total_count + 1;
|
||||
if app_state.current_position > current_total_count + 1 {
|
||||
app_state.current_position = current_total_count + 1;
|
||||
}
|
||||
if app_state.current_position > total_count {
|
||||
if app_state.current_position > current_total_count {
|
||||
// New entry - reset form
|
||||
form_state.reset_to_empty();
|
||||
form_state.current_field = 0;
|
||||
} else if app_state.current_position >= 1 && app_state.current_position <= total_count {
|
||||
} else if app_state.current_position >= 1
|
||||
&& app_state.current_position <= current_total_count
|
||||
{
|
||||
// Existing entry - load data
|
||||
let current_position_to_load = app_state.current_position; // Use a copy
|
||||
let load_message = UiService::load_adresar_by_position(
|
||||
&mut grpc_client,
|
||||
&mut app_state, // Pass app_state mutably if needed by the service
|
||||
&mut form_state,
|
||||
current_position_to_load
|
||||
).await?;
|
||||
current_position_to_load,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let current_input = form_state.get_current_input();
|
||||
let max_cursor_pos = if !event_handler.is_edit_mode && !current_input.is_empty() {
|
||||
current_input.len() - 1 // In readonly mode, limit to last character
|
||||
let max_cursor_pos = if !event_handler.is_edit_mode
|
||||
&& !current_input.is_empty()
|
||||
{
|
||||
current_input.len() - 1 // In readonly mode, limit to last character
|
||||
} else {
|
||||
current_input.len()
|
||||
};
|
||||
form_state.current_cursor_pos = event_handler.ideal_cursor_column.min(max_cursor_pos);
|
||||
form_state.current_cursor_pos = event_handler
|
||||
.ideal_cursor_column
|
||||
.min(max_cursor_pos);
|
||||
// Don't overwrite message from handle_event if load_message is simple success
|
||||
if !load_message.starts_with("Loaded entry") || message.is_empty() {
|
||||
event_handler.command_message = load_message;
|
||||
if !load_message.starts_with("Loaded entry")
|
||||
|| event_handler.command_message.is_empty()
|
||||
{
|
||||
event_handler.command_message = load_message;
|
||||
}
|
||||
} else {
|
||||
// Invalid position (e.g., 0) - reset to first entry or new entry mode
|
||||
app_state.current_position = 1.min(total_count + 1); // Go to 1 or new entry if empty
|
||||
if app_state.current_position > total_count {
|
||||
form_state.reset_to_empty();
|
||||
form_state.current_field = 0;
|
||||
}
|
||||
app_state.current_position =
|
||||
1.min(current_total_count + 1); // Go to 1 or new entry if empty
|
||||
if app_state.current_position > total_count {
|
||||
form_state.reset_to_empty();
|
||||
form_state.current_field = 0;
|
||||
}
|
||||
}
|
||||
} else if !position_changed && !event_handler.is_edit_mode {
|
||||
// If position didn't change but we are in read-only, just adjust cursor
|
||||
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 =
|
||||
event_handler.ideal_cursor_column.min(max_cursor_pos);
|
||||
}
|
||||
} else if app_state.ui.show_login {
|
||||
// Handle cursor updates for AuthState if needed, similar to FormState
|
||||
if !event_handler.is_edit_mode {
|
||||
let current_input = auth_state.get_current_input();
|
||||
let max_cursor_pos = if !current_input.is_empty() {
|
||||
current_input.len() - 1
|
||||
} else {
|
||||
0
|
||||
};
|
||||
auth_state.current_cursor_pos = event_handler.ideal_cursor_column.min(max_cursor_pos);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Only update command message if handle_event provided one
|
||||
if !message.is_empty() {
|
||||
event_handler.command_message = message;
|
||||
// Handle cursor updates for AuthState if needed, similar to FormState
|
||||
if !event_handler.is_edit_mode {
|
||||
let current_input = auth_state.get_current_input();
|
||||
let max_cursor_pos = if !current_input.is_empty() {
|
||||
current_input.len() - 1
|
||||
} else {
|
||||
0
|
||||
};
|
||||
auth_state.current_cursor_pos =
|
||||
event_handler.ideal_cursor_column.min(max_cursor_pos);
|
||||
}
|
||||
}
|
||||
|
||||
// Check exit condition *after* processing outcome
|
||||
if should_exit {
|
||||
// terminal.cleanup()?; // Optional: Drop handles this
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user