Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a8eef8107b | ||
|
|
75cd942f39 | ||
|
|
fc04af148d | ||
|
|
d7d7fd614b | ||
|
|
10e9c3ead0 | ||
|
|
70678432c6 | ||
|
|
bb103fac6c | ||
|
|
0e1fc3f5fa | ||
|
|
7830ebdb3b | ||
|
|
b061dd3395 | ||
|
|
a6f2fa8a88 | ||
|
|
37f12ea6f0 | ||
|
|
e29b576102 |
6
Cargo.lock
generated
6
Cargo.lock
generated
@@ -421,7 +421,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "client"
|
name = "client"
|
||||||
version = "0.3.0"
|
version = "0.3.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"common",
|
"common",
|
||||||
@@ -458,7 +458,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "common"
|
name = "common"
|
||||||
version = "0.3.0"
|
version = "0.3.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"prost",
|
"prost",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -2589,7 +2589,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "server"
|
name = "server"
|
||||||
version = "0.3.0"
|
version = "0.3.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bcrypt",
|
"bcrypt",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ resolver = "2"
|
|||||||
[workspace.package]
|
[workspace.package]
|
||||||
# TODO: idk how to do the name, fix later
|
# TODO: idk how to do the name, fix later
|
||||||
# name = "Multieko2"
|
# name = "Multieko2"
|
||||||
version = "0.3.0"
|
version = "0.3.3"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "GPL-3.0-or-later"
|
license = "GPL-3.0-or-later"
|
||||||
authors = ["Filip Priečinský <filippriec@gmail.com>"]
|
authors = ["Filip Priečinský <filippriec@gmail.com>"]
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use crate::{
|
|||||||
components::common::dialog,
|
components::common::dialog,
|
||||||
state::state::AppState,
|
state::state::AppState,
|
||||||
};
|
};
|
||||||
|
use crate::modes::handlers::mode_manager::AppMode;
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
layout::{Alignment, Constraint, Direction, Layout, Rect, Margin},
|
layout::{Alignment, Constraint, Direction, Layout, Rect, Margin},
|
||||||
style::{Style, Modifier, Color},
|
style::{Style, Modifier, Color},
|
||||||
@@ -75,7 +76,12 @@ pub fn render_login(
|
|||||||
.split(chunks[2]);
|
.split(chunks[2]);
|
||||||
|
|
||||||
// Login Button
|
// 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_style = Style::default().fg(theme.fg);
|
||||||
let mut login_border = Style::default().fg(theme.border);
|
let mut login_border = Style::default().fg(theme.border);
|
||||||
if login_active {
|
if login_active {
|
||||||
@@ -97,7 +103,12 @@ pub fn render_login(
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Return Button
|
// 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_style = Style::default().fg(theme.fg);
|
||||||
let mut return_border = Style::default().fg(theme.border);
|
let mut return_border = Style::default().fg(theme.border);
|
||||||
if return_active {
|
if return_active {
|
||||||
|
|||||||
@@ -3,5 +3,4 @@
|
|||||||
pub mod common;
|
pub mod common;
|
||||||
pub mod modes;
|
pub mod modes;
|
||||||
|
|
||||||
pub use common::*;
|
|
||||||
pub use modes::*;
|
pub use modes::*;
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
// src/functions/modes/edit/auth_e.rs
|
// src/functions/modes/edit/auth_e.rs
|
||||||
|
|
||||||
use crate::config::binds::config::Config;
|
|
||||||
use crate::services::grpc_client::GrpcClient;
|
use crate::services::grpc_client::GrpcClient;
|
||||||
use crate::state::canvas_state::CanvasState;
|
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 crate::tui::functions::common::form::{revert, save};
|
||||||
use crossterm::event::{KeyCode, KeyEvent};
|
use crossterm::event::{KeyCode, KeyEvent};
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
@@ -12,7 +11,6 @@ pub async fn execute_common_action<S: CanvasState + Any>(
|
|||||||
action: &str,
|
action: &str,
|
||||||
state: &mut S,
|
state: &mut S,
|
||||||
grpc_client: &mut GrpcClient,
|
grpc_client: &mut GrpcClient,
|
||||||
is_saved: &mut bool,
|
|
||||||
current_position: &mut u64,
|
current_position: &mut u64,
|
||||||
total_count: u64,
|
total_count: u64,
|
||||||
) -> Result<String, Box<dyn std::error::Error>> {
|
) -> Result<String, Box<dyn std::error::Error>> {
|
||||||
@@ -26,14 +24,15 @@ pub async fn execute_common_action<S: CanvasState + Any>(
|
|||||||
{
|
{
|
||||||
match action {
|
match action {
|
||||||
"save" => {
|
"save" => {
|
||||||
save(
|
let outcome = save(
|
||||||
form_state,
|
form_state,
|
||||||
grpc_client,
|
grpc_client,
|
||||||
is_saved,
|
|
||||||
current_position,
|
current_position,
|
||||||
total_count,
|
total_count,
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
|
let message = format!("Save successful: {:?}", outcome); // Simple message for now
|
||||||
|
Ok(message)
|
||||||
}
|
}
|
||||||
"revert" => {
|
"revert" => {
|
||||||
revert(
|
revert(
|
||||||
@@ -62,10 +61,9 @@ pub async fn execute_edit_action<S: CanvasState>(
|
|||||||
key: KeyEvent,
|
key: KeyEvent,
|
||||||
state: &mut S,
|
state: &mut S,
|
||||||
ideal_cursor_column: &mut usize,
|
ideal_cursor_column: &mut usize,
|
||||||
_grpc_client: &mut GrpcClient,
|
grpc_client: &mut GrpcClient,
|
||||||
_is_saved: &mut bool,
|
current_position: &mut u64,
|
||||||
_current_position: &mut u64,
|
total_count: u64,
|
||||||
_total_count: u64,
|
|
||||||
) -> Result<String, Box<dyn std::error::Error>> {
|
) -> Result<String, Box<dyn std::error::Error>> {
|
||||||
match action {
|
match action {
|
||||||
"insert_char" => {
|
"insert_char" => {
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
// src/functions/modes/edit/form_e.rs
|
// src/functions/modes/edit/form_e.rs
|
||||||
|
|
||||||
use crate::config::binds::config::Config;
|
|
||||||
use crate::services::grpc_client::GrpcClient;
|
use crate::services::grpc_client::GrpcClient;
|
||||||
use crate::state::canvas_state::CanvasState;
|
use crate::state::canvas_state::CanvasState;
|
||||||
use crate::state::pages::form::FormState;
|
use crate::state::pages::form::FormState;
|
||||||
use crate::tui::functions::common::form::{revert, save};
|
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 crossterm::event::{KeyCode, KeyEvent};
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
|
|
||||||
@@ -12,48 +13,61 @@ pub async fn execute_common_action<S: CanvasState + Any>(
|
|||||||
action: &str,
|
action: &str,
|
||||||
state: &mut S,
|
state: &mut S,
|
||||||
grpc_client: &mut GrpcClient,
|
grpc_client: &mut GrpcClient,
|
||||||
is_saved: &mut bool,
|
|
||||||
current_position: &mut u64,
|
current_position: &mut u64,
|
||||||
total_count: u64,
|
total_count: u64,
|
||||||
) -> Result<String, Box<dyn std::error::Error>> {
|
) -> Result<EventOutcome, Box<dyn std::error::Error>> {
|
||||||
match action {
|
match action {
|
||||||
"save" | "revert" => {
|
"save" | "revert" => {
|
||||||
if !state.has_unsaved_changes() {
|
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) =
|
if let Some(form_state) =
|
||||||
(state as &mut dyn Any).downcast_mut::<FormState>()
|
(state as &mut dyn Any).downcast_mut::<FormState>()
|
||||||
{
|
{
|
||||||
match action {
|
match action {
|
||||||
"save" => {
|
"save" => {
|
||||||
save(
|
let save_result = save(
|
||||||
form_state,
|
form_state,
|
||||||
grpc_client,
|
grpc_client,
|
||||||
is_saved,
|
|
||||||
current_position,
|
current_position,
|
||||||
total_count,
|
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" => {
|
||||||
revert(
|
let revert_result = revert(
|
||||||
form_state,
|
form_state,
|
||||||
grpc_client,
|
grpc_client,
|
||||||
current_position,
|
current_position,
|
||||||
total_count,
|
total_count,
|
||||||
)
|
).await;
|
||||||
.await
|
|
||||||
|
match revert_result {
|
||||||
|
Ok(message) => Ok(EventOutcome::Ok(message)),
|
||||||
|
Err(e) => Err(e),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Ok(format!(
|
Ok(EventOutcome::Ok(format!(
|
||||||
"Action '{}' not implemented for this state type.",
|
"Action '{}' not implemented for this state type.",
|
||||||
action
|
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,
|
key: KeyEvent,
|
||||||
state: &mut S,
|
state: &mut S,
|
||||||
ideal_cursor_column: &mut usize,
|
ideal_cursor_column: &mut usize,
|
||||||
_grpc_client: &mut GrpcClient,
|
grpc_client: &mut GrpcClient,
|
||||||
_is_saved: &mut bool,
|
current_position: &mut u64,
|
||||||
_current_position: &mut u64,
|
total_count: u64,
|
||||||
_total_count: u64,
|
|
||||||
) -> Result<String, Box<dyn std::error::Error>> {
|
) -> Result<String, Box<dyn std::error::Error>> {
|
||||||
match action {
|
match action {
|
||||||
"insert_char" => {
|
"insert_char" => {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
use crate::config::binds::key_sequences::KeySequenceTracker;
|
use crate::config::binds::key_sequences::KeySequenceTracker;
|
||||||
use crate::state::canvas_state::CanvasState;
|
use crate::state::canvas_state::CanvasState;
|
||||||
|
use crate::state::state::AppState;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
|
||||||
#[derive(PartialEq)]
|
#[derive(PartialEq)]
|
||||||
@@ -13,6 +14,7 @@ enum CharType {
|
|||||||
|
|
||||||
pub async fn execute_action<S: CanvasState>(
|
pub async fn execute_action<S: CanvasState>(
|
||||||
action: &str,
|
action: &str,
|
||||||
|
app_state: &mut AppState,
|
||||||
state: &mut S,
|
state: &mut S,
|
||||||
ideal_cursor_column: &mut usize,
|
ideal_cursor_column: &mut usize,
|
||||||
key_sequence_tracker: &mut KeySequenceTracker,
|
key_sequence_tracker: &mut KeySequenceTracker,
|
||||||
@@ -53,7 +55,17 @@ pub async fn execute_action<S: CanvasState>(
|
|||||||
return Ok("No fields to navigate.".to_string());
|
return Ok("No fields to navigate.".to_string());
|
||||||
}
|
}
|
||||||
let current_field = state.current_field();
|
let current_field = state.current_field();
|
||||||
let new_field = (state.current_field() + 1).min(num_fields - 1);
|
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 {
|
||||||
|
// Move to the next field within the canvas
|
||||||
|
let new_field = (current_field + 1).min(last_field_index);
|
||||||
state.set_current_field(new_field);
|
state.set_current_field(new_field);
|
||||||
let current_input = state.get_current_input();
|
let current_input = state.get_current_input();
|
||||||
let max_cursor_pos = if current_input.is_empty() {
|
let max_cursor_pos = if current_input.is_empty() {
|
||||||
@@ -63,7 +75,8 @@ pub async fn execute_action<S: CanvasState>(
|
|||||||
};
|
};
|
||||||
let new_pos = (*ideal_cursor_column).min(max_cursor_pos);
|
let new_pos = (*ideal_cursor_column).min(max_cursor_pos);
|
||||||
state.set_current_cursor_pos(new_pos);
|
state.set_current_cursor_pos(new_pos);
|
||||||
Ok("move down from functions/modes/read_only/auth_ro.rs".to_string())
|
Ok("".to_string()) // Clear previous debug message
|
||||||
|
}
|
||||||
}
|
}
|
||||||
"move_first_line" => {
|
"move_first_line" => {
|
||||||
key_sequence_tracker.reset();
|
key_sequence_tracker.reset();
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
// src/modes/canvas/common.rs
|
// src/modes/canvas/common_mode.rs
|
||||||
|
|
||||||
use crate::tui::terminal::core::TerminalCore;
|
use crate::tui::terminal::core::TerminalCore;
|
||||||
use crate::state::pages::{form::FormState, auth::AuthState};
|
use crate::state::pages::{form::FormState, auth::AuthState};
|
||||||
use crate::state::state::AppState;
|
use crate::state::state::AppState;
|
||||||
use crate::services::grpc_client::GrpcClient;
|
use crate::services::grpc_client::GrpcClient;
|
||||||
use crate::services::auth::AuthClient;
|
use crate::services::auth::AuthClient;
|
||||||
|
use crate::modes::handlers::event::EventOutcome;
|
||||||
|
use crate::tui::functions::common::form::SaveOutcome;
|
||||||
use crate::tui::functions::common::{
|
use crate::tui::functions::common::{
|
||||||
form::{save as form_save, revert as form_revert},
|
form::{save as form_save, revert as form_revert},
|
||||||
login::{save as login_save, revert as login_revert}
|
login::{save as login_save, revert as login_revert}
|
||||||
@@ -20,46 +22,54 @@ pub async fn handle_core_action(
|
|||||||
app_state: &mut AppState,
|
app_state: &mut AppState,
|
||||||
current_position: &mut u64,
|
current_position: &mut u64,
|
||||||
total_count: u64,
|
total_count: u64,
|
||||||
) -> Result<(bool, String), Box<dyn std::error::Error>> {
|
) -> Result<EventOutcome, Box<dyn std::error::Error>> {
|
||||||
match action {
|
match action {
|
||||||
"save" => {
|
"save" => {
|
||||||
if app_state.ui.show_login {
|
if app_state.ui.show_login {
|
||||||
let message = login_save(auth_state, auth_client, app_state).await?;
|
let message = login_save(auth_state, auth_client, app_state).await?;
|
||||||
Ok((false, message))
|
Ok(EventOutcome::Ok(message))
|
||||||
} else {
|
} else {
|
||||||
let message = form_save(
|
let save_outcome = form_save(
|
||||||
form_state,
|
form_state,
|
||||||
grpc_client,
|
grpc_client,
|
||||||
&mut app_state.ui.is_saved,
|
|
||||||
current_position,
|
current_position,
|
||||||
total_count,
|
total_count,
|
||||||
).await?;
|
).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" => {
|
"force_quit" => {
|
||||||
terminal.cleanup()?;
|
terminal.cleanup()?;
|
||||||
Ok((true, "Force exiting without saving.".to_string()))
|
Ok(EventOutcome::Exit("Force exiting without saving.".to_string()))
|
||||||
},
|
},
|
||||||
"save_and_quit" => {
|
"save_and_quit" => {
|
||||||
let message = if app_state.ui.show_login {
|
let message = if app_state.ui.show_login {
|
||||||
login_save(auth_state, auth_client, app_state).await?
|
login_save(auth_state, auth_client, app_state).await?
|
||||||
} else {
|
} else {
|
||||||
form_save(
|
let save_outcome = form_save(
|
||||||
form_state,
|
form_state,
|
||||||
grpc_client,
|
grpc_client,
|
||||||
&mut app_state.ui.is_saved,
|
|
||||||
current_position,
|
current_position,
|
||||||
total_count,
|
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()?;
|
terminal.cleanup()?;
|
||||||
Ok((true, format!("{}. Exiting application.", message)))
|
Ok(EventOutcome::Exit(format!("{}. Exiting application.", message)))
|
||||||
},
|
},
|
||||||
"revert" => {
|
"revert" => {
|
||||||
if app_state.ui.show_login {
|
if app_state.ui.show_login {
|
||||||
let message = login_revert(auth_state, app_state).await;
|
let message = login_revert(auth_state, app_state).await;
|
||||||
Ok((false, message))
|
Ok(EventOutcome::Ok(message))
|
||||||
} else {
|
} else {
|
||||||
let message = form_revert(
|
let message = form_revert(
|
||||||
form_state,
|
form_state,
|
||||||
@@ -67,9 +77,9 @@ pub async fn handle_core_action(
|
|||||||
current_position,
|
current_position,
|
||||||
total_count,
|
total_count,
|
||||||
).await?;
|
).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::config::binds::config::Config;
|
||||||
use crate::services::grpc_client::GrpcClient;
|
use crate::services::grpc_client::GrpcClient;
|
||||||
use crate::state::pages::{auth::AuthState, form::FormState};
|
use crate::state::pages::{auth::AuthState, form::FormState};
|
||||||
use crate::state::state::AppState;
|
|
||||||
use crate::functions::modes::edit::{auth_e, form_e};
|
use crate::functions::modes::edit::{auth_e, form_e};
|
||||||
|
use crate::modes::handlers::event::EventOutcome;
|
||||||
use crossterm::event::{KeyCode, KeyEvent};
|
use crossterm::event::{KeyCode, KeyEvent};
|
||||||
|
|
||||||
pub async fn handle_edit_event(
|
pub async fn handle_edit_event(
|
||||||
@@ -15,7 +15,6 @@ pub async fn handle_edit_event(
|
|||||||
auth_state: &mut AuthState,
|
auth_state: &mut AuthState,
|
||||||
ideal_cursor_column: &mut usize,
|
ideal_cursor_column: &mut usize,
|
||||||
command_message: &mut String,
|
command_message: &mut String,
|
||||||
is_saved: &mut bool,
|
|
||||||
current_position: &mut u64,
|
current_position: &mut u64,
|
||||||
total_count: u64,
|
total_count: u64,
|
||||||
grpc_client: &mut GrpcClient,
|
grpc_client: &mut GrpcClient,
|
||||||
@@ -38,12 +37,11 @@ pub async fn handle_edit_event(
|
|||||||
key.modifiers
|
key.modifiers
|
||||||
) {
|
) {
|
||||||
if matches!(action, "save" | "revert") {
|
if matches!(action, "save" | "revert") {
|
||||||
return if is_auth_context {
|
let message = if is_auth_context {
|
||||||
auth_e::execute_common_action(
|
auth_e::execute_common_action(
|
||||||
action,
|
action,
|
||||||
auth_state, // Concrete AuthState
|
auth_state, // Concrete AuthState
|
||||||
grpc_client,
|
grpc_client,
|
||||||
is_saved,
|
|
||||||
current_position,
|
current_position,
|
||||||
total_count
|
total_count
|
||||||
).await
|
).await
|
||||||
@@ -52,11 +50,17 @@ pub async fn handle_edit_event(
|
|||||||
action,
|
action,
|
||||||
form_state, // Concrete FormState
|
form_state, // Concrete FormState
|
||||||
grpc_client,
|
grpc_client,
|
||||||
is_saved,
|
|
||||||
current_position,
|
current_position,
|
||||||
total_count
|
total_count
|
||||||
).await
|
).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),
|
||||||
|
// Add other EventOutcome variants if necessary
|
||||||
|
})
|
||||||
|
}?;
|
||||||
|
return Ok(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,7 +73,6 @@ pub async fn handle_edit_event(
|
|||||||
auth_state, // Full access to AuthState fields
|
auth_state, // Full access to AuthState fields
|
||||||
ideal_cursor_column,
|
ideal_cursor_column,
|
||||||
grpc_client,
|
grpc_client,
|
||||||
is_saved,
|
|
||||||
current_position,
|
current_position,
|
||||||
total_count
|
total_count
|
||||||
).await
|
).await
|
||||||
@@ -80,7 +83,6 @@ pub async fn handle_edit_event(
|
|||||||
form_state, // Full access to FormState fields
|
form_state, // Full access to FormState fields
|
||||||
ideal_cursor_column,
|
ideal_cursor_column,
|
||||||
grpc_client,
|
grpc_client,
|
||||||
is_saved,
|
|
||||||
current_position,
|
current_position,
|
||||||
total_count
|
total_count
|
||||||
).await
|
).await
|
||||||
@@ -96,7 +98,6 @@ pub async fn handle_edit_event(
|
|||||||
auth_state,
|
auth_state,
|
||||||
ideal_cursor_column,
|
ideal_cursor_column,
|
||||||
grpc_client,
|
grpc_client,
|
||||||
is_saved,
|
|
||||||
current_position,
|
current_position,
|
||||||
total_count
|
total_count
|
||||||
).await
|
).await
|
||||||
@@ -107,7 +108,6 @@ pub async fn handle_edit_event(
|
|||||||
form_state,
|
form_state,
|
||||||
ideal_cursor_column,
|
ideal_cursor_column,
|
||||||
grpc_client,
|
grpc_client,
|
||||||
is_saved,
|
|
||||||
current_position,
|
current_position,
|
||||||
total_count
|
total_count
|
||||||
).await
|
).await
|
||||||
|
|||||||
@@ -6,11 +6,12 @@ use crate::services::grpc_client::GrpcClient;
|
|||||||
use crate::state::canvas_state::CanvasState;
|
use crate::state::canvas_state::CanvasState;
|
||||||
use crate::state::pages::auth::AuthState;
|
use crate::state::pages::auth::AuthState;
|
||||||
use crate::state::pages::form::FormState;
|
use crate::state::pages::form::FormState;
|
||||||
|
use crate::state::state::AppState;
|
||||||
use crate::functions::modes::read_only::{auth_ro, form_ro};
|
use crate::functions::modes::read_only::{auth_ro, form_ro};
|
||||||
use crossterm::event::KeyEvent;
|
use crossterm::event::KeyEvent;
|
||||||
|
|
||||||
pub async fn handle_read_only_event(
|
pub async fn handle_read_only_event(
|
||||||
app_state: &crate::state::state::AppState,
|
app_state: &mut AppState,
|
||||||
key: KeyEvent,
|
key: KeyEvent,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
form_state: &mut FormState,
|
form_state: &mut FormState,
|
||||||
@@ -90,6 +91,7 @@ pub async fn handle_read_only_event(
|
|||||||
} else if app_state.ui.show_login {
|
} else if app_state.ui.show_login {
|
||||||
auth_ro::execute_action(
|
auth_ro::execute_action(
|
||||||
action,
|
action,
|
||||||
|
app_state,
|
||||||
auth_state,
|
auth_state,
|
||||||
ideal_cursor_column,
|
ideal_cursor_column,
|
||||||
key_sequence_tracker,
|
key_sequence_tracker,
|
||||||
@@ -136,6 +138,7 @@ pub async fn handle_read_only_event(
|
|||||||
} else if app_state.ui.show_login {
|
} else if app_state.ui.show_login {
|
||||||
auth_ro::execute_action(
|
auth_ro::execute_action(
|
||||||
action,
|
action,
|
||||||
|
app_state,
|
||||||
auth_state,
|
auth_state,
|
||||||
ideal_cursor_column,
|
ideal_cursor_column,
|
||||||
key_sequence_tracker,
|
key_sequence_tracker,
|
||||||
@@ -181,6 +184,7 @@ pub async fn handle_read_only_event(
|
|||||||
} else if app_state.ui.show_login {
|
} else if app_state.ui.show_login {
|
||||||
auth_ro::execute_action(
|
auth_ro::execute_action(
|
||||||
action,
|
action,
|
||||||
|
app_state,
|
||||||
auth_state,
|
auth_state,
|
||||||
ideal_cursor_column,
|
ideal_cursor_column,
|
||||||
key_sequence_tracker,
|
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 crossterm::event::{KeyEvent, KeyCode, KeyModifiers};
|
||||||
use crate::config::binds::config::Config;
|
use crate::config::binds::config::Config;
|
||||||
use crate::services::grpc_client::GrpcClient;
|
use crate::services::grpc_client::GrpcClient;
|
||||||
use crate::state::pages::form::FormState;
|
use crate::state::pages::form::FormState;
|
||||||
|
use crate::state::{state::AppState, pages::auth::AuthState};
|
||||||
use crate::modes::common::commands::CommandHandler;
|
use crate::modes::common::commands::CommandHandler;
|
||||||
use crate::tui::terminal::core::TerminalCore;
|
use crate::tui::terminal::core::TerminalCore;
|
||||||
use crate::tui::functions::common::form::{save, revert};
|
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(
|
pub async fn handle_command_event(
|
||||||
key: KeyEvent,
|
key: KeyEvent,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
|
app_state: &AppState,
|
||||||
|
auth_state: &AuthState,
|
||||||
form_state: &mut FormState,
|
form_state: &mut FormState,
|
||||||
command_input: &mut String,
|
command_input: &mut String,
|
||||||
command_message: &mut String,
|
command_message: &mut String,
|
||||||
@@ -19,15 +25,12 @@ pub async fn handle_command_event(
|
|||||||
terminal: &mut TerminalCore,
|
terminal: &mut TerminalCore,
|
||||||
current_position: &mut u64,
|
current_position: &mut u64,
|
||||||
total_count: u64,
|
total_count: u64,
|
||||||
) -> Result<(bool, String, bool), Box<dyn std::error::Error>> {
|
) -> Result<EventOutcome, Box<dyn Error>> {
|
||||||
|
|
||||||
// Return value: (should_exit, message, should_exit_command_mode)
|
|
||||||
|
|
||||||
// Exit command mode (via configurable keybinding)
|
// Exit command mode (via configurable keybinding)
|
||||||
if config.is_exit_command_mode(key.code, key.modifiers) {
|
if config.is_exit_command_mode(key.code, key.modifiers) {
|
||||||
command_input.clear();
|
command_input.clear();
|
||||||
*command_message = "".to_string();
|
*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)
|
// Execute command (via configurable keybinding, defaults to Enter)
|
||||||
@@ -35,6 +38,8 @@ pub async fn handle_command_event(
|
|||||||
return process_command(
|
return process_command(
|
||||||
config,
|
config,
|
||||||
form_state,
|
form_state,
|
||||||
|
app_state,
|
||||||
|
auth_state,
|
||||||
command_input,
|
command_input,
|
||||||
command_message,
|
command_message,
|
||||||
grpc_client,
|
grpc_client,
|
||||||
@@ -48,7 +53,7 @@ pub async fn handle_command_event(
|
|||||||
// Backspace (via configurable keybinding, defaults to Backspace)
|
// Backspace (via configurable keybinding, defaults to Backspace)
|
||||||
if config.is_command_backspace(key.code, key.modifiers) {
|
if config.is_command_backspace(key.code, key.modifiers) {
|
||||||
command_input.pop();
|
command_input.pop();
|
||||||
return Ok((false, "".to_string(), false));
|
return Ok(EventOutcome::Ok("".to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Regular character input - accept any character in command mode
|
// 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')
|
// Accept regular or shifted characters (e.g., 'a' or 'A')
|
||||||
if key.modifiers.is_empty() || key.modifiers == KeyModifiers::SHIFT {
|
if key.modifiers.is_empty() || key.modifiers == KeyModifiers::SHIFT {
|
||||||
command_input.push(c);
|
command_input.push(c);
|
||||||
return Ok((false, "".to_string(), false));
|
return Ok(EventOutcome::Ok("".to_string()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ignore all other keys
|
// Ignore all other keys
|
||||||
Ok((false, "".to_string(), false))
|
Ok(EventOutcome::Ok("".to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn process_command(
|
async fn process_command(
|
||||||
config: &Config,
|
config: &Config,
|
||||||
form_state: &mut FormState,
|
form_state: &mut FormState,
|
||||||
|
app_state: &AppState,
|
||||||
|
auth_state: &AuthState,
|
||||||
command_input: &mut String,
|
command_input: &mut String,
|
||||||
command_message: &mut String,
|
command_message: &mut String,
|
||||||
grpc_client: &mut GrpcClient,
|
grpc_client: &mut GrpcClient,
|
||||||
@@ -74,12 +81,12 @@ async fn process_command(
|
|||||||
terminal: &mut TerminalCore,
|
terminal: &mut TerminalCore,
|
||||||
current_position: &mut u64,
|
current_position: &mut u64,
|
||||||
total_count: 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
|
// Clone the trimmed command to avoid borrow issues
|
||||||
let command = command_input.trim().to_string();
|
let command = command_input.trim().to_string();
|
||||||
if command.is_empty() {
|
if command.is_empty() {
|
||||||
*command_message = "Empty command".to_string();
|
*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)
|
// Get the action for the command (now checks global and common bindings too)
|
||||||
@@ -89,21 +96,35 @@ async fn process_command(
|
|||||||
match action {
|
match action {
|
||||||
"force_quit" | "save_and_quit" | "quit" => {
|
"force_quit" | "save_and_quit" | "quit" => {
|
||||||
let (should_exit, message) = command_handler
|
let (should_exit, message) = command_handler
|
||||||
.handle_command(action, terminal)
|
.handle_command(
|
||||||
|
action,
|
||||||
|
terminal,
|
||||||
|
app_state,
|
||||||
|
form_state,
|
||||||
|
auth_state,
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
command_input.clear();
|
command_input.clear();
|
||||||
Ok((should_exit, message, true))
|
if should_exit {
|
||||||
|
Ok(EventOutcome::Exit(message))
|
||||||
|
} else {
|
||||||
|
Ok(EventOutcome::Ok(message))
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"save" => {
|
"save" => {
|
||||||
let message = save(
|
let outcome = save(
|
||||||
form_state,
|
form_state,
|
||||||
grpc_client,
|
grpc_client,
|
||||||
&mut command_handler.is_saved,
|
|
||||||
current_position,
|
current_position,
|
||||||
total_count,
|
total_count,
|
||||||
).await?;
|
).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();
|
command_input.clear();
|
||||||
return Ok((false, message, true));
|
Ok(EventOutcome::DataSaved(outcome, message))
|
||||||
},
|
},
|
||||||
"revert" => {
|
"revert" => {
|
||||||
let message = revert(
|
let message = revert(
|
||||||
@@ -113,17 +134,12 @@ async fn process_command(
|
|||||||
total_count,
|
total_count,
|
||||||
).await?;
|
).await?;
|
||||||
command_input.clear();
|
command_input.clear();
|
||||||
return Ok((false, message, true));
|
Ok(EventOutcome::Ok(message))
|
||||||
},
|
|
||||||
"unknown" => {
|
|
||||||
let message = format!("Unknown command: {}", command);
|
|
||||||
command_input.clear();
|
|
||||||
return Ok((false, message, true));
|
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
let message = format!("Unhandled action: {}", action);
|
let message = format!("Unhandled action: {}", action);
|
||||||
command_input.clear();
|
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::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 struct CommandHandler;
|
||||||
pub is_saved: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CommandHandler {
|
impl CommandHandler {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self { is_saved: false }
|
Self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn handle_command(
|
pub async fn handle_command(
|
||||||
&mut self,
|
&mut self,
|
||||||
action: &str,
|
action: &str,
|
||||||
terminal: &mut TerminalCore,
|
terminal: &mut TerminalCore,
|
||||||
|
app_state: &AppState,
|
||||||
|
form_state: &FormState,
|
||||||
|
auth_state: &AuthState,
|
||||||
) -> Result<(bool, String), Box<dyn std::error::Error>> {
|
) -> Result<(bool, String), Box<dyn std::error::Error>> {
|
||||||
match action {
|
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,
|
"force_quit" => self.handle_force_quit(terminal).await,
|
||||||
"save_and_quit" => self.handle_save_quit(terminal).await,
|
"save_and_quit" => self.handle_save_quit(terminal).await,
|
||||||
_ => Ok((false, format!("Unknown command: {}", action))),
|
_ => Ok((false, format!("Unknown command: {}", action))),
|
||||||
@@ -26,8 +30,18 @@ impl CommandHandler {
|
|||||||
async fn handle_quit(
|
async fn handle_quit(
|
||||||
&self,
|
&self,
|
||||||
terminal: &mut TerminalCore,
|
terminal: &mut TerminalCore,
|
||||||
|
app_state: &AppState,
|
||||||
|
form_state: &FormState,
|
||||||
|
auth_state: &AuthState,
|
||||||
) -> Result<(bool, String), Box<dyn std::error::Error>> {
|
) -> 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()?;
|
terminal.cleanup()?;
|
||||||
Ok((true, "Exiting.".into()))
|
Ok((true, "Exiting.".into()))
|
||||||
} else {
|
} else {
|
||||||
@@ -47,7 +61,6 @@ impl CommandHandler {
|
|||||||
&mut self,
|
&mut self,
|
||||||
terminal: &mut TerminalCore,
|
terminal: &mut TerminalCore,
|
||||||
) -> Result<(bool, String), Box<dyn std::error::Error>> {
|
) -> Result<(bool, String), Box<dyn std::error::Error>> {
|
||||||
self.is_saved = true;
|
|
||||||
terminal.cleanup()?;
|
terminal.cleanup()?;
|
||||||
Ok((true, "State saved. Exiting.".into()))
|
Ok((true, "State saved. Exiting.".into()))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,65 +4,77 @@ use crossterm::event::KeyEvent;
|
|||||||
use crate::config::binds::config::Config;
|
use crate::config::binds::config::Config;
|
||||||
use crate::state::state::AppState;
|
use crate::state::state::AppState;
|
||||||
use crate::state::pages::form::FormState;
|
use crate::state::pages::form::FormState;
|
||||||
|
use crate::state::pages::auth::AuthState;
|
||||||
|
use crate::state::canvas_state::CanvasState;
|
||||||
use crate::tui::functions::{intro, admin};
|
use crate::tui::functions::{intro, admin};
|
||||||
|
use crate::modes::handlers::event::EventOutcome;
|
||||||
|
|
||||||
pub async fn handle_navigation_event(
|
pub async fn handle_navigation_event(
|
||||||
key: KeyEvent,
|
key: KeyEvent,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
form_state: &mut FormState,
|
form_state: &mut FormState,
|
||||||
app_state: &mut AppState,
|
app_state: &mut AppState,
|
||||||
|
auth_state: &mut AuthState,
|
||||||
command_mode: &mut bool,
|
command_mode: &mut bool,
|
||||||
command_input: &mut String,
|
command_input: &mut String,
|
||||||
command_message: &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) {
|
if let Some(action) = config.get_general_action(key.code, key.modifiers) {
|
||||||
match action {
|
match action {
|
||||||
"move_up" => {
|
"move_up" => {
|
||||||
move_up(app_state);
|
move_up(app_state, auth_state);
|
||||||
return Ok((false, String::new()));
|
return Ok(EventOutcome::Ok(String::new()));
|
||||||
}
|
}
|
||||||
"move_down" => {
|
"move_down" => {
|
||||||
move_down(app_state);
|
move_down(app_state);
|
||||||
return Ok((false, String::new()));
|
return Ok(EventOutcome::Ok(String::new()));
|
||||||
}
|
}
|
||||||
"next_option" => {
|
"next_option" => {
|
||||||
next_option(app_state); // Intro has 2 options
|
next_option(app_state);
|
||||||
return Ok((false, String::new()));
|
return Ok(EventOutcome::Ok(String::new()));
|
||||||
}
|
}
|
||||||
"previous_option" => {
|
"previous_option" => {
|
||||||
previous_option(app_state);
|
previous_option(app_state);
|
||||||
return Ok((false, String::new()));
|
return Ok(EventOutcome::Ok(String::new()));
|
||||||
}
|
}
|
||||||
"select" => {
|
"select" => {
|
||||||
select(app_state);
|
select(app_state);
|
||||||
return Ok((false, "Selected".to_string()));
|
return Ok(EventOutcome::Ok("Selected".to_string()));
|
||||||
}
|
}
|
||||||
"toggle_sidebar" => {
|
"toggle_sidebar" => {
|
||||||
toggle_sidebar(app_state);
|
toggle_sidebar(app_state);
|
||||||
return Ok((false, format!("Sidebar {}",
|
return Ok(EventOutcome::Ok(format!("Sidebar {}",
|
||||||
if app_state.ui.show_sidebar { "shown" } else { "hidden" }
|
if app_state.ui.show_sidebar { "shown" } else { "hidden" }
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
"next_field" => {
|
"next_field" => {
|
||||||
next_field(form_state);
|
next_field(form_state);
|
||||||
return Ok((false, String::new()));
|
return Ok(EventOutcome::Ok(String::new()));
|
||||||
}
|
}
|
||||||
"prev_field" => {
|
"prev_field" => {
|
||||||
prev_field(form_state);
|
prev_field(form_state);
|
||||||
return Ok((false, String::new()));
|
return Ok(EventOutcome::Ok(String::new()));
|
||||||
}
|
}
|
||||||
"enter_command_mode" => {
|
"enter_command_mode" => {
|
||||||
handle_enter_command_mode(command_mode, command_input, command_message);
|
handle_enter_command_mode(command_mode, command_input, command_message);
|
||||||
return Ok((false, String::new()));
|
return Ok(EventOutcome::Ok(String::new()));
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok((false, String::new()))
|
Ok(EventOutcome::Ok(String::new()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn move_up(app_state: &mut AppState) {
|
pub fn move_up(app_state: &mut AppState, auth_state: &mut AuthState) {
|
||||||
if app_state.ui.show_intro {
|
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();
|
app_state.ui.intro_state.previous_option();
|
||||||
} else if app_state.ui.show_admin {
|
} else if app_state.ui.show_admin {
|
||||||
// Assuming profile_tree.profiles is the list we're navigating
|
// Assuming profile_tree.profiles is the list we're navigating
|
||||||
@@ -81,7 +93,12 @@ pub fn move_up(app_state: &mut AppState) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn move_down(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();
|
app_state.ui.intro_state.next_option();
|
||||||
} else if app_state.ui.show_admin {
|
} else if app_state.ui.show_admin {
|
||||||
// Assuming profile_tree.profiles is the list we're navigating
|
// Assuming profile_tree.profiles is the list we're navigating
|
||||||
|
|||||||
@@ -17,6 +17,15 @@ use crate::modes::{
|
|||||||
};
|
};
|
||||||
use crate::config::binds::key_sequences::KeySequenceTracker;
|
use crate::config::binds::key_sequences::KeySequenceTracker;
|
||||||
use crate::modes::handlers::mode_manager::{ModeManager, AppMode};
|
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), // Normal operation, display message
|
||||||
|
Exit(String), // Signal app exit, display message
|
||||||
|
DataSaved(SaveOutcome, String), // Data save attempted, include outcome and message
|
||||||
|
// Add other outcomes like QuitRequested, SaveAndQuitRequested later if needed
|
||||||
|
}
|
||||||
|
|
||||||
pub struct EventHandler {
|
pub struct EventHandler {
|
||||||
pub command_mode: bool,
|
pub command_mode: bool,
|
||||||
@@ -55,7 +64,7 @@ impl EventHandler {
|
|||||||
app_state: &mut crate::state::state::AppState,
|
app_state: &mut crate::state::state::AppState,
|
||||||
total_count: u64,
|
total_count: u64,
|
||||||
current_position: &mut u64,
|
current_position: &mut u64,
|
||||||
) -> Result<(bool, String), Box<dyn std::error::Error>> {
|
) -> Result<EventOutcome, Box<dyn std::error::Error>> {
|
||||||
let current_mode = ModeManager::derive_mode(app_state, self);
|
let current_mode = ModeManager::derive_mode(app_state, self);
|
||||||
app_state.update_mode(current_mode);
|
app_state.update_mode(current_mode);
|
||||||
|
|
||||||
@@ -64,9 +73,10 @@ impl EventHandler {
|
|||||||
let modifiers = key.modifiers;
|
let modifiers = key.modifiers;
|
||||||
|
|
||||||
if UiStateHandler::toggle_sidebar(&mut app_state.ui, config, key_code, modifiers) {
|
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" }
|
if app_state.ui.show_sidebar { "shown" } else { "hidden" }
|
||||||
)));
|
);
|
||||||
|
return Ok(EventOutcome::Ok(message));
|
||||||
}
|
}
|
||||||
|
|
||||||
match current_mode {
|
match current_mode {
|
||||||
@@ -76,6 +86,7 @@ impl EventHandler {
|
|||||||
config,
|
config,
|
||||||
form_state,
|
form_state,
|
||||||
app_state,
|
app_state,
|
||||||
|
auth_state,
|
||||||
&mut self.command_mode,
|
&mut self.command_mode,
|
||||||
&mut self.command_input,
|
&mut self.command_input,
|
||||||
&mut self.command_message,
|
&mut self.command_message,
|
||||||
@@ -89,7 +100,7 @@ impl EventHandler {
|
|||||||
self.edit_mode_cooldown = true;
|
self.edit_mode_cooldown = true;
|
||||||
self.command_message = "Edit mode".to_string();
|
self.command_message = "Edit mode".to_string();
|
||||||
terminal.set_cursor_style(SetCursorStyle::BlinkingBar)?;
|
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) &&
|
if config.is_enter_edit_mode_after(key_code, modifiers) &&
|
||||||
@@ -118,7 +129,7 @@ impl EventHandler {
|
|||||||
self.edit_mode_cooldown = true;
|
self.edit_mode_cooldown = true;
|
||||||
self.command_message = "Edit mode (after cursor)".to_string();
|
self.command_message = "Edit mode (after cursor)".to_string();
|
||||||
terminal.set_cursor_style(SetCursorStyle::BlinkingBar)?;
|
terminal.set_cursor_style(SetCursorStyle::BlinkingBar)?;
|
||||||
return Ok((false, self.command_message.clone()));
|
return Ok(EventOutcome::Ok(self.command_message.clone()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(action) = config.get_read_only_action_for_key(key_code, modifiers) {
|
if let Some(action) = config.get_read_only_action_for_key(key_code, modifiers) {
|
||||||
@@ -126,7 +137,7 @@ impl EventHandler {
|
|||||||
self.command_mode = true;
|
self.command_mode = true;
|
||||||
self.command_input.clear();
|
self.command_input.clear();
|
||||||
self.command_message.clear();
|
self.command_message.clear();
|
||||||
return Ok((false, String::new()));
|
return Ok(EventOutcome::Ok(String::new()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,8 +164,8 @@ impl EventHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return read_only::handle_read_only_event(
|
let (_should_exit, message) = read_only::handle_read_only_event(
|
||||||
&app_state,
|
app_state,
|
||||||
key,
|
key,
|
||||||
config,
|
config,
|
||||||
form_state,
|
form_state,
|
||||||
@@ -166,7 +177,8 @@ impl EventHandler {
|
|||||||
&mut self.command_message,
|
&mut self.command_message,
|
||||||
&mut self.edit_mode_cooldown,
|
&mut self.edit_mode_cooldown,
|
||||||
&mut self.ideal_cursor_column,
|
&mut self.ideal_cursor_column,
|
||||||
).await;
|
).await?;
|
||||||
|
return Ok(EventOutcome::Ok(message));
|
||||||
},
|
},
|
||||||
|
|
||||||
AppMode::Edit => {
|
AppMode::Edit => {
|
||||||
@@ -209,7 +221,7 @@ impl EventHandler {
|
|||||||
self.ideal_cursor_column = form_state.current_cursor_pos();
|
self.ideal_cursor_column = form_state.current_cursor_pos();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Ok((false, self.command_message.clone()));
|
return Ok(EventOutcome::Ok(self.command_message.clone()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(action) = config.get_action_for_key_in_mode(
|
if let Some(action) = config.get_action_for_key_in_mode(
|
||||||
@@ -235,7 +247,7 @@ impl EventHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = edit::handle_edit_event(
|
let message = edit::handle_edit_event(
|
||||||
app_state.ui.show_login,
|
app_state.ui.show_login,
|
||||||
key,
|
key,
|
||||||
config,
|
config,
|
||||||
@@ -243,20 +255,21 @@ impl EventHandler {
|
|||||||
auth_state,
|
auth_state,
|
||||||
&mut self.ideal_cursor_column,
|
&mut self.ideal_cursor_column,
|
||||||
&mut self.command_message,
|
&mut self.command_message,
|
||||||
&mut app_state.ui.is_saved,
|
|
||||||
current_position,
|
current_position,
|
||||||
total_count,
|
total_count,
|
||||||
grpc_client,
|
grpc_client,
|
||||||
).await?;
|
).await?;
|
||||||
|
|
||||||
self.key_sequence_tracker.reset();
|
self.key_sequence_tracker.reset();
|
||||||
return Ok((false, result));
|
return Ok(EventOutcome::Ok(message));
|
||||||
},
|
},
|
||||||
|
|
||||||
AppMode::Command => {
|
AppMode::Command => {
|
||||||
let (should_exit, message, exit_command_mode) = command_mode::handle_command_event(
|
let outcome = command_mode::handle_command_event(
|
||||||
key,
|
key,
|
||||||
config,
|
config,
|
||||||
|
app_state,
|
||||||
|
auth_state,
|
||||||
form_state,
|
form_state,
|
||||||
&mut self.command_input,
|
&mut self.command_input,
|
||||||
&mut self.command_message,
|
&mut self.command_message,
|
||||||
@@ -267,16 +280,17 @@ impl EventHandler {
|
|||||||
total_count,
|
total_count,
|
||||||
).await?;
|
).await?;
|
||||||
|
|
||||||
if exit_command_mode {
|
if let EventOutcome::Ok(msg) = &outcome {
|
||||||
|
if msg == "Exited command mode" {
|
||||||
self.command_mode = false;
|
self.command_mode = false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return Ok((should_exit, message));
|
return Ok(outcome);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.edit_mode_cooldown = false;
|
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;
|
return AppMode::Command;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if app_state.ui.focus_outside_canvas {
|
||||||
|
return AppMode::General;
|
||||||
|
}
|
||||||
|
|
||||||
if app_state.ui.show_login {
|
if app_state.ui.show_login {
|
||||||
if event_handler.is_edit_mode {
|
if event_handler.is_edit_mode {
|
||||||
AppMode::Edit
|
AppMode::Edit
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
use crate::services::grpc_client::GrpcClient;
|
use crate::services::grpc_client::GrpcClient;
|
||||||
use crate::state::pages::form::FormState;
|
use crate::state::pages::form::FormState;
|
||||||
|
use crate::tui::functions::common::form::SaveOutcome;
|
||||||
use crate::state::state::AppState;
|
use crate::state::state::AppState;
|
||||||
|
|
||||||
pub struct UiService;
|
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
|
// src/state/canvas_state.rs
|
||||||
|
|
||||||
use crate::state::pages::form::FormState;
|
|
||||||
|
|
||||||
pub trait CanvasState {
|
pub trait CanvasState {
|
||||||
fn current_field(&self) -> usize;
|
fn current_field(&self) -> usize;
|
||||||
|
|||||||
@@ -15,12 +15,12 @@ pub struct DialogState {
|
|||||||
|
|
||||||
pub struct UiState {
|
pub struct UiState {
|
||||||
pub show_sidebar: bool,
|
pub show_sidebar: bool,
|
||||||
pub is_saved: bool,
|
|
||||||
pub show_intro: bool,
|
pub show_intro: bool,
|
||||||
pub show_admin: bool,
|
pub show_admin: bool,
|
||||||
pub show_form: bool,
|
pub show_form: bool,
|
||||||
pub show_login: bool,
|
pub show_login: bool,
|
||||||
pub intro_state: IntroState,
|
pub intro_state: IntroState,
|
||||||
|
pub focus_outside_canvas: bool,
|
||||||
pub dialog: DialogState, // Add dialog state here
|
pub dialog: DialogState, // Add dialog state here
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,12 +133,12 @@ impl Default for UiState {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
show_sidebar: true,
|
show_sidebar: true,
|
||||||
is_saved: false,
|
|
||||||
show_intro: true,
|
show_intro: true,
|
||||||
show_admin: false,
|
show_admin: false,
|
||||||
show_form: false,
|
show_form: false,
|
||||||
show_login: false,
|
show_login: false,
|
||||||
intro_state: IntroState::new(),
|
intro_state: IntroState::new(),
|
||||||
|
focus_outside_canvas: false,
|
||||||
dialog: DialogState::default(),
|
dialog: DialogState::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,4 +9,3 @@ pub mod common;
|
|||||||
pub use admin::*;
|
pub use admin::*;
|
||||||
pub use intro::*;
|
pub use intro::*;
|
||||||
pub use form::*;
|
pub use form::*;
|
||||||
pub use common::*;
|
|
||||||
|
|||||||
@@ -3,5 +3,3 @@
|
|||||||
pub mod form;
|
pub mod form;
|
||||||
pub mod login;
|
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 crate::state::pages::form::FormState;
|
||||||
use common::proto::multieko2::adresar::{PostAdresarRequest, PutAdresarRequest};
|
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
|
/// Shared logic for saving the current form state
|
||||||
pub async fn save(
|
pub async fn save(
|
||||||
form_state: &mut FormState,
|
form_state: &mut FormState,
|
||||||
grpc_client: &mut GrpcClient,
|
grpc_client: &mut GrpcClient,
|
||||||
is_saved: &mut bool,
|
|
||||||
current_position: &mut u64,
|
current_position: &mut u64,
|
||||||
total_count: 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 is_new = *current_position == total_count + 1;
|
||||||
|
|
||||||
let message = if is_new {
|
let outcome = if is_new {
|
||||||
let post_request = PostAdresarRequest {
|
let post_request = PostAdresarRequest {
|
||||||
firma: form_state.values[0].clone(),
|
firma: form_state.values[0].clone(),
|
||||||
kz: form_state.values[1].clone(),
|
kz: form_state.values[1].clone(),
|
||||||
@@ -33,10 +42,9 @@ pub async fn save(
|
|||||||
fax: form_state.values[14].clone(),
|
fax: form_state.values[14].clone(),
|
||||||
};
|
};
|
||||||
let response = grpc_client.post_adresar(post_request).await?;
|
let response = grpc_client.post_adresar(post_request).await?;
|
||||||
let new_total = grpc_client.get_adresar_count().await?;
|
let new_id = response.into_inner().id;
|
||||||
*current_position = new_total;
|
form_state.id = new_id;
|
||||||
form_state.id = response.into_inner().id;
|
SaveOutcome::CreatedNew(new_id) // <-- Return CreatedNew with ID
|
||||||
"New entry created".to_string()
|
|
||||||
} else {
|
} else {
|
||||||
let put_request = PutAdresarRequest {
|
let put_request = PutAdresarRequest {
|
||||||
id: form_state.id,
|
id: form_state.id,
|
||||||
@@ -57,12 +65,11 @@ pub async fn save(
|
|||||||
fax: form_state.values[14].clone(),
|
fax: form_state.values[14].clone(),
|
||||||
};
|
};
|
||||||
let _ = grpc_client.put_adresar(put_request).await?;
|
let _ = grpc_client.put_adresar(put_request).await?;
|
||||||
"Entry updated".to_string()
|
SaveOutcome::UpdatedExisting
|
||||||
};
|
};
|
||||||
|
|
||||||
*is_saved = true;
|
|
||||||
form_state.has_unsaved_changes = false;
|
form_state.has_unsaved_changes = false;
|
||||||
Ok(message)
|
Ok(outcome)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Discard changes since last save
|
/// Discard changes since last save
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ pub async fn save(
|
|||||||
/// Reverts the login form fields to empty and returns to the previous screen (Intro).
|
/// Reverts the login form fields to empty and returns to the previous screen (Intro).
|
||||||
pub async fn revert(
|
pub async fn revert(
|
||||||
auth_state: &mut AuthState,
|
auth_state: &mut AuthState,
|
||||||
_app_state: &mut AppState, // Prefix unused variable
|
app_state: &mut AppState,
|
||||||
) -> String {
|
) -> String {
|
||||||
// Clear the input fields
|
// Clear the input fields
|
||||||
auth_state.username.clear();
|
auth_state.username.clear();
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
// src/tui/functions/login.rs
|
// src/tui/functions/login.rs
|
||||||
use crate::state::pages::auth::AuthState;
|
use crate::state::pages::auth::AuthState;
|
||||||
use crate::state::canvas_state::CanvasState;
|
|
||||||
|
|
||||||
pub async fn handle_action(
|
pub async fn handle_action(
|
||||||
action: &str,
|
action: &str,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
use crossterm::{
|
use crossterm::{
|
||||||
execute,
|
execute,
|
||||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
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 ratatui::{backend::CrosstermBackend, Terminal};
|
||||||
use std::io::{self, stdout, Write};
|
use std::io::{self, stdout, Write};
|
||||||
@@ -64,6 +64,22 @@ impl TerminalCore {
|
|||||||
)?;
|
)?;
|
||||||
Ok(())
|
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 {
|
impl Drop for TerminalCore {
|
||||||
|
|||||||
@@ -1,25 +1,25 @@
|
|||||||
// src/ui/handlers/ui.rs
|
// 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::config::binds::config::Config;
|
||||||
use crate::ui::handlers::render::render_ui;
|
use crate::config::colors::themes::Theme;
|
||||||
use crate::state::pages::form::FormState;
|
use crate::modes::common::commands::CommandHandler;
|
||||||
use crate::state::pages::auth::AuthState;
|
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::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::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>> {
|
pub async fn run_ui() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let config = Config::load()?;
|
let config = Config::load()?;
|
||||||
let mut terminal = TerminalCore::new()?;
|
let mut terminal = TerminalCore::new()?;
|
||||||
let mut grpc_client = GrpcClient::new().await?;
|
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 mut command_handler = CommandHandler::new();
|
||||||
let theme = Theme::from_str(&config.colors.theme);
|
let theme = Theme::from_str(&config.colors.theme);
|
||||||
let mut auth_state = AuthState::default(); // The single source of truth for AuthState
|
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()?;
|
let mut app_state = AppState::new()?;
|
||||||
|
|
||||||
// Initialize app state with profile tree and table structure
|
// 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
|
// Initialize FormState with dynamic fields
|
||||||
let mut form_state = FormState::new(column_names);
|
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();
|
let event_reader = EventReader::new();
|
||||||
|
|
||||||
// Fetch the total count of Adresar entries
|
// 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();
|
form_state.reset_to_empty();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
UiService::update_adresar_count(&mut grpc_client, &mut app_state).await?;
|
|
||||||
|
|
||||||
// Determine edit mode based on EventHandler state
|
// Determine edit mode based on EventHandler state
|
||||||
let is_edit_mode = event_handler.is_edit_mode;
|
let is_edit_mode = event_handler.is_edit_mode;
|
||||||
|
|
||||||
@@ -64,28 +65,106 @@ 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 mut current_position = app_state.current_position;
|
||||||
// Store position before event handling to detect navigation
|
|
||||||
let position_before_event = current_position;
|
let position_before_event = current_position;
|
||||||
|
|
||||||
let event = event_reader.read_event()?;
|
let event = event_reader.read_event()?;
|
||||||
let (should_exit, message) = event_handler.handle_event(
|
|
||||||
|
// Get the outcome from the event handler
|
||||||
|
let event_outcome_result = event_handler
|
||||||
|
.handle_event(
|
||||||
event,
|
event,
|
||||||
&config,
|
&config,
|
||||||
&mut terminal,
|
&mut terminal, // Pass terminal mutably
|
||||||
&mut grpc_client,
|
&mut grpc_client,
|
||||||
&mut command_handler,
|
&mut command_handler,
|
||||||
&mut form_state,
|
&mut form_state,
|
||||||
&mut auth_state, // Pass the single AuthState instance here too
|
&mut auth_state,
|
||||||
&mut app_state,
|
&mut app_state,
|
||||||
total_count,
|
total_count, // Pass the count *before* potential save
|
||||||
&mut current_position,
|
&mut current_position,
|
||||||
).await?;
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// Update position based on handler's modification
|
||||||
app_state.current_position = current_position;
|
app_state.current_position = current_position;
|
||||||
|
|
||||||
let position_changed = app_state.current_position != position_before_event;
|
// --- 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
|
||||||
|
}
|
||||||
|
},
|
||||||
|
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)
|
// Handle position changes and update form state (Only when form is shown)
|
||||||
if app_state.ui.show_form {
|
if app_state.ui.show_form {
|
||||||
@@ -96,40 +175,51 @@ pub async fn run_ui() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
} else {
|
} else {
|
||||||
0
|
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
|
// Ensure position never exceeds total_count + 1
|
||||||
if app_state.current_position > total_count + 1 {
|
if app_state.current_position > current_total_count + 1 {
|
||||||
app_state.current_position = 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
|
// New entry - reset form
|
||||||
form_state.reset_to_empty();
|
form_state.reset_to_empty();
|
||||||
form_state.current_field = 0;
|
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
|
// Existing entry - load data
|
||||||
let current_position_to_load = app_state.current_position; // Use a copy
|
let current_position_to_load = app_state.current_position; // Use a copy
|
||||||
let load_message = UiService::load_adresar_by_position(
|
let load_message = UiService::load_adresar_by_position(
|
||||||
&mut grpc_client,
|
&mut grpc_client,
|
||||||
&mut app_state, // Pass app_state mutably if needed by the service
|
&mut app_state, // Pass app_state mutably if needed by the service
|
||||||
&mut form_state,
|
&mut form_state,
|
||||||
current_position_to_load
|
current_position_to_load,
|
||||||
).await?;
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
let current_input = form_state.get_current_input();
|
let current_input = form_state.get_current_input();
|
||||||
let max_cursor_pos = if !event_handler.is_edit_mode && !current_input.is_empty() {
|
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
|
current_input.len() - 1 // In readonly mode, limit to last character
|
||||||
} else {
|
} else {
|
||||||
current_input.len()
|
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
|
// Don't overwrite message from handle_event if load_message is simple success
|
||||||
if !load_message.starts_with("Loaded entry") || message.is_empty() {
|
if !load_message.starts_with("Loaded entry")
|
||||||
|
|| event_handler.command_message.is_empty()
|
||||||
|
{
|
||||||
event_handler.command_message = load_message;
|
event_handler.command_message = load_message;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Invalid position (e.g., 0) - reset to first entry or new entry mode
|
// 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
|
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 {
|
if app_state.current_position > total_count {
|
||||||
form_state.reset_to_empty();
|
form_state.reset_to_empty();
|
||||||
form_state.current_field = 0;
|
form_state.current_field = 0;
|
||||||
@@ -143,8 +233,8 @@ pub async fn run_ui() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
} else {
|
} else {
|
||||||
0
|
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);
|
||||||
}
|
}
|
||||||
} else if app_state.ui.show_login {
|
} else if app_state.ui.show_login {
|
||||||
// Handle cursor updates for AuthState if needed, similar to FormState
|
// Handle cursor updates for AuthState if needed, similar to FormState
|
||||||
@@ -155,17 +245,14 @@ pub async fn run_ui() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
};
|
};
|
||||||
auth_state.current_cursor_pos = event_handler.ideal_cursor_column.min(max_cursor_pos);
|
auth_state.current_cursor_pos =
|
||||||
|
event_handler.ideal_cursor_column.min(max_cursor_pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check exit condition *after* processing outcome
|
||||||
// Only update command message if handle_event provided one
|
|
||||||
if !message.is_empty() {
|
|
||||||
event_handler.command_message = message;
|
|
||||||
}
|
|
||||||
|
|
||||||
if should_exit {
|
if should_exit {
|
||||||
|
// terminal.cleanup()?; // Optional: Drop handles this
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user