fixed form state removed, but not won, aint working yet
This commit is contained in:
@@ -50,7 +50,7 @@ move_right = ["l", "Right"]
|
|||||||
move_down = ["j", "Down"]
|
move_down = ["j", "Down"]
|
||||||
# Optional
|
# Optional
|
||||||
move_line_end = ["$"]
|
move_line_end = ["$"]
|
||||||
# move_word_next = ["w"]
|
move_word_next = ["w"]
|
||||||
next_field = ["Tab"]
|
next_field = ["Tab"]
|
||||||
move_word_prev = ["b"]
|
move_word_prev = ["b"]
|
||||||
move_word_end = ["e"]
|
move_word_end = ["e"]
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ pub async fn handle_command_event(
|
|||||||
app_state: &mut AppState,
|
app_state: &mut AppState,
|
||||||
login_state: &LoginState,
|
login_state: &LoginState,
|
||||||
register_state: &RegisterState,
|
register_state: &RegisterState,
|
||||||
form_state: &mut FormState,
|
|
||||||
command_input: &mut String,
|
command_input: &mut String,
|
||||||
command_message: &mut String,
|
command_message: &mut String,
|
||||||
grpc_client: &mut GrpcClient,
|
grpc_client: &mut GrpcClient,
|
||||||
@@ -38,7 +37,6 @@ pub async fn handle_command_event(
|
|||||||
if config.is_command_execute(key.code, key.modifiers) {
|
if config.is_command_execute(key.code, key.modifiers) {
|
||||||
return process_command(
|
return process_command(
|
||||||
config,
|
config,
|
||||||
form_state,
|
|
||||||
app_state,
|
app_state,
|
||||||
login_state,
|
login_state,
|
||||||
register_state,
|
register_state,
|
||||||
@@ -73,7 +71,6 @@ pub async fn handle_command_event(
|
|||||||
|
|
||||||
async fn process_command(
|
async fn process_command(
|
||||||
config: &Config,
|
config: &Config,
|
||||||
form_state: &mut FormState,
|
|
||||||
app_state: &mut AppState,
|
app_state: &mut AppState,
|
||||||
login_state: &LoginState,
|
login_state: &LoginState,
|
||||||
register_state: &RegisterState,
|
register_state: &RegisterState,
|
||||||
@@ -103,7 +100,6 @@ async fn process_command(
|
|||||||
action,
|
action,
|
||||||
terminal,
|
terminal,
|
||||||
app_state,
|
app_state,
|
||||||
form_state,
|
|
||||||
login_state,
|
login_state,
|
||||||
register_state,
|
register_state,
|
||||||
)
|
)
|
||||||
@@ -118,7 +114,6 @@ async fn process_command(
|
|||||||
"save" => {
|
"save" => {
|
||||||
let outcome = save(
|
let outcome = save(
|
||||||
app_state,
|
app_state,
|
||||||
form_state,
|
|
||||||
grpc_client,
|
grpc_client,
|
||||||
).await?;
|
).await?;
|
||||||
let message = match outcome {
|
let message = match outcome {
|
||||||
@@ -131,7 +126,7 @@ async fn process_command(
|
|||||||
},
|
},
|
||||||
"revert" => {
|
"revert" => {
|
||||||
let message = revert(
|
let message = revert(
|
||||||
form_state,
|
app_state,
|
||||||
grpc_client,
|
grpc_client,
|
||||||
).await?;
|
).await?;
|
||||||
command_input.clear();
|
command_input.clear();
|
||||||
|
|||||||
@@ -15,13 +15,12 @@ impl CommandHandler {
|
|||||||
&mut self,
|
&mut self,
|
||||||
action: &str,
|
action: &str,
|
||||||
terminal: &mut TerminalCore,
|
terminal: &mut TerminalCore,
|
||||||
app_state: &AppState,
|
app_state: &mut AppState,
|
||||||
form_state: &FormState,
|
|
||||||
login_state: &LoginState,
|
login_state: &LoginState,
|
||||||
register_state: &RegisterState,
|
register_state: &RegisterState,
|
||||||
) -> Result<(bool, String)> {
|
) -> Result<(bool, String)> {
|
||||||
match action {
|
match action {
|
||||||
"quit" => self.handle_quit(terminal, app_state, form_state, login_state, register_state).await,
|
"quit" => self.handle_quit(terminal, app_state, login_state, register_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))),
|
||||||
@@ -31,8 +30,7 @@ impl CommandHandler {
|
|||||||
async fn handle_quit(
|
async fn handle_quit(
|
||||||
&self,
|
&self,
|
||||||
terminal: &mut TerminalCore,
|
terminal: &mut TerminalCore,
|
||||||
app_state: &AppState,
|
app_state: &mut AppState,
|
||||||
form_state: &FormState,
|
|
||||||
login_state: &LoginState,
|
login_state: &LoginState,
|
||||||
register_state: &RegisterState,
|
register_state: &RegisterState,
|
||||||
) -> Result<(bool, String)> {
|
) -> Result<(bool, String)> {
|
||||||
@@ -41,8 +39,10 @@ impl CommandHandler {
|
|||||||
login_state.has_unsaved_changes()
|
login_state.has_unsaved_changes()
|
||||||
} else if app_state.ui.show_register {
|
} else if app_state.ui.show_register {
|
||||||
register_state.has_unsaved_changes()
|
register_state.has_unsaved_changes()
|
||||||
|
} else if let Some(fs) = app_state.form_state_mut() {
|
||||||
|
fs.has_unsaved_changes
|
||||||
} else {
|
} else {
|
||||||
form_state.has_unsaved_changes
|
false
|
||||||
};
|
};
|
||||||
|
|
||||||
if !has_unsaved {
|
if !has_unsaved {
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ use anyhow::Result;
|
|||||||
pub async fn handle_navigation_event(
|
pub async fn handle_navigation_event(
|
||||||
key: KeyEvent,
|
key: KeyEvent,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
form_state: &mut FormState,
|
|
||||||
app_state: &mut AppState,
|
app_state: &mut AppState,
|
||||||
login_state: &mut LoginState,
|
login_state: &mut LoginState,
|
||||||
register_state: &mut RegisterState,
|
register_state: &mut RegisterState,
|
||||||
@@ -52,11 +51,15 @@ pub async fn handle_navigation_event(
|
|||||||
return Ok(EventOutcome::Ok(String::new()));
|
return Ok(EventOutcome::Ok(String::new()));
|
||||||
}
|
}
|
||||||
"next_field" => {
|
"next_field" => {
|
||||||
next_field(form_state);
|
if let Some(fs) = app_state.form_state_mut() {
|
||||||
|
next_field(fs);
|
||||||
|
}
|
||||||
return Ok(EventOutcome::Ok(String::new()));
|
return Ok(EventOutcome::Ok(String::new()));
|
||||||
}
|
}
|
||||||
"prev_field" => {
|
"prev_field" => {
|
||||||
prev_field(form_state);
|
if let Some(fs) = app_state.form_state_mut() {
|
||||||
|
prev_field(fs);
|
||||||
|
}
|
||||||
return Ok(EventOutcome::Ok(String::new()));
|
return Ok(EventOutcome::Ok(String::new()));
|
||||||
}
|
}
|
||||||
"enter_command_mode" => {
|
"enter_command_mode" => {
|
||||||
|
|||||||
@@ -220,66 +220,90 @@ impl EventHandler {
|
|||||||
async fn handle_search_palette_event(
|
async fn handle_search_palette_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
key_event: KeyEvent,
|
key_event: KeyEvent,
|
||||||
form_state: &mut FormState,
|
|
||||||
app_state: &mut AppState,
|
app_state: &mut AppState,
|
||||||
) -> Result<EventOutcome> {
|
) -> Result<EventOutcome> {
|
||||||
let mut should_close = false;
|
let mut should_close = false;
|
||||||
let mut outcome_message = String::new();
|
let mut outcome_message = String::new();
|
||||||
let mut trigger_search = false;
|
let mut trigger_search = false;
|
||||||
|
|
||||||
if let Some(search_state) = app_state.search_state.as_mut() {
|
// Step 1: Handle search_state logic in a short scope
|
||||||
match key_event.code {
|
let (maybe_data, maybe_id) = {
|
||||||
KeyCode::Esc => {
|
if let Some(search_state) = app_state.search_state.as_mut() {
|
||||||
should_close = true;
|
match key_event.code {
|
||||||
outcome_message = "Search cancelled".to_string();
|
KeyCode::Esc => {
|
||||||
}
|
|
||||||
KeyCode::Enter => {
|
|
||||||
if let Some(selected_hit) =
|
|
||||||
search_state.results.get(search_state.selected_index)
|
|
||||||
{
|
|
||||||
if let Ok(data) = serde_json::from_str::<
|
|
||||||
std::collections::HashMap<String, String>,
|
|
||||||
>(&selected_hit.content_json)
|
|
||||||
{
|
|
||||||
let detached_pos = form_state.total_count + 2;
|
|
||||||
form_state
|
|
||||||
.update_from_response(&data, detached_pos);
|
|
||||||
}
|
|
||||||
should_close = true;
|
should_close = true;
|
||||||
outcome_message =
|
outcome_message = "Search cancelled".to_string();
|
||||||
format!("Loaded record ID {}", selected_hit.id);
|
(None, None)
|
||||||
}
|
}
|
||||||
}
|
KeyCode::Enter => {
|
||||||
KeyCode::Up => search_state.previous_result(),
|
if let Some(selected_hit) =
|
||||||
KeyCode::Down => search_state.next_result(),
|
search_state.results.get(search_state.selected_index)
|
||||||
KeyCode::Char(c) => {
|
{
|
||||||
search_state
|
if let Ok(data) = serde_json::from_str::<
|
||||||
.input
|
std::collections::HashMap<String, String>,
|
||||||
.insert(search_state.cursor_position, c);
|
>(&selected_hit.content_json)
|
||||||
search_state.cursor_position += 1;
|
{
|
||||||
trigger_search = true;
|
(Some(data), Some(selected_hit.id))
|
||||||
}
|
} else {
|
||||||
KeyCode::Backspace => {
|
(None, None)
|
||||||
if search_state.cursor_position > 0 {
|
}
|
||||||
search_state.cursor_position -= 1;
|
} else {
|
||||||
search_state.input.remove(search_state.cursor_position);
|
(None, None)
|
||||||
trigger_search = true;
|
}
|
||||||
}
|
}
|
||||||
}
|
KeyCode::Up => {
|
||||||
KeyCode::Left => {
|
search_state.previous_result();
|
||||||
search_state.cursor_position =
|
(None, None)
|
||||||
search_state.cursor_position.saturating_sub(1);
|
}
|
||||||
}
|
KeyCode::Down => {
|
||||||
KeyCode::Right => {
|
search_state.next_result();
|
||||||
if search_state.cursor_position < search_state.input.len()
|
(None, None)
|
||||||
{
|
}
|
||||||
|
KeyCode::Char(c) => {
|
||||||
|
search_state.input.insert(search_state.cursor_position, c);
|
||||||
search_state.cursor_position += 1;
|
search_state.cursor_position += 1;
|
||||||
|
trigger_search = true;
|
||||||
|
(None, None)
|
||||||
}
|
}
|
||||||
|
KeyCode::Backspace => {
|
||||||
|
if search_state.cursor_position > 0 {
|
||||||
|
search_state.cursor_position -= 1;
|
||||||
|
search_state.input.remove(search_state.cursor_position);
|
||||||
|
trigger_search = true;
|
||||||
|
}
|
||||||
|
(None, None)
|
||||||
|
}
|
||||||
|
KeyCode::Left => {
|
||||||
|
search_state.cursor_position =
|
||||||
|
search_state.cursor_position.saturating_sub(1);
|
||||||
|
(None, None)
|
||||||
|
}
|
||||||
|
KeyCode::Right => {
|
||||||
|
if search_state.cursor_position < search_state.input.len() {
|
||||||
|
search_state.cursor_position += 1;
|
||||||
|
}
|
||||||
|
(None, None)
|
||||||
|
}
|
||||||
|
_ => (None, None),
|
||||||
}
|
}
|
||||||
_ => {}
|
} else {
|
||||||
|
(None, None)
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if trigger_search {
|
// Step 2: Now safe to borrow form_state
|
||||||
|
if let (Some(data), Some(id)) = (maybe_data, maybe_id) {
|
||||||
|
if let Some(fs) = app_state.form_state_mut() {
|
||||||
|
let detached_pos = fs.total_count + 2;
|
||||||
|
fs.update_from_response(&data, detached_pos);
|
||||||
|
}
|
||||||
|
should_close = true;
|
||||||
|
outcome_message = format!("Loaded record ID {}", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3: Trigger async search if needed
|
||||||
|
if trigger_search {
|
||||||
|
if let Some(search_state) = app_state.search_state.as_mut() {
|
||||||
search_state.is_loading = true;
|
search_state.is_loading = true;
|
||||||
search_state.results.clear();
|
search_state.results.clear();
|
||||||
search_state.selected_index = 0;
|
search_state.selected_index = 0;
|
||||||
@@ -289,10 +313,7 @@ impl EventHandler {
|
|||||||
let sender = self.search_result_sender.clone();
|
let sender = self.search_result_sender.clone();
|
||||||
let mut grpc_client = self.grpc_client.clone();
|
let mut grpc_client = self.grpc_client.clone();
|
||||||
|
|
||||||
info!(
|
info!("--- 1. Spawning search task for query: '{}' ---", query);
|
||||||
"--- 1. Spawning search task for query: '{}' ---",
|
|
||||||
query
|
|
||||||
);
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
info!("--- 2. Background task started. ---");
|
info!("--- 2. Background task started. ---");
|
||||||
match grpc_client.search_table(table_name, query).await {
|
match grpc_client.search_table(table_name, query).await {
|
||||||
@@ -328,7 +349,6 @@ impl EventHandler {
|
|||||||
config: &Config,
|
config: &Config,
|
||||||
terminal: &mut TerminalCore,
|
terminal: &mut TerminalCore,
|
||||||
command_handler: &mut CommandHandler,
|
command_handler: &mut CommandHandler,
|
||||||
form_state: &mut FormState,
|
|
||||||
auth_state: &mut AuthState,
|
auth_state: &mut AuthState,
|
||||||
login_state: &mut LoginState,
|
login_state: &mut LoginState,
|
||||||
register_state: &mut RegisterState,
|
register_state: &mut RegisterState,
|
||||||
@@ -339,13 +359,7 @@ impl EventHandler {
|
|||||||
) -> Result<EventOutcome> {
|
) -> Result<EventOutcome> {
|
||||||
if app_state.ui.show_search_palette {
|
if app_state.ui.show_search_palette {
|
||||||
if let Event::Key(key_event) = event {
|
if let Event::Key(key_event) = event {
|
||||||
return self
|
return self.handle_search_palette_event(key_event, app_state).await;
|
||||||
.handle_search_palette_event(
|
|
||||||
key_event,
|
|
||||||
form_state,
|
|
||||||
app_state,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
return Ok(EventOutcome::Ok(String::new()));
|
return Ok(EventOutcome::Ok(String::new()));
|
||||||
}
|
}
|
||||||
@@ -570,7 +584,6 @@ impl EventHandler {
|
|||||||
let nav_outcome = navigation::handle_navigation_event(
|
let nav_outcome = navigation::handle_navigation_event(
|
||||||
key_event,
|
key_event,
|
||||||
config,
|
config,
|
||||||
form_state,
|
|
||||||
app_state,
|
app_state,
|
||||||
login_state,
|
login_state,
|
||||||
register_state,
|
register_state,
|
||||||
@@ -580,9 +593,7 @@ impl EventHandler {
|
|||||||
&mut self.command_input,
|
&mut self.command_input,
|
||||||
&mut self.command_message,
|
&mut self.command_message,
|
||||||
&mut self.navigation_state,
|
&mut self.navigation_state,
|
||||||
)
|
).await;
|
||||||
.await;
|
|
||||||
|
|
||||||
match nav_outcome {
|
match nav_outcome {
|
||||||
Ok(EventOutcome::ButtonSelected { context, index }) => {
|
Ok(EventOutcome::ButtonSelected { context, index }) => {
|
||||||
let message = match context {
|
let message = match context {
|
||||||
@@ -692,7 +703,6 @@ impl EventHandler {
|
|||||||
return self
|
return self
|
||||||
.handle_core_action(
|
.handle_core_action(
|
||||||
action,
|
action,
|
||||||
form_state,
|
|
||||||
auth_state,
|
auth_state,
|
||||||
login_state,
|
login_state,
|
||||||
register_state,
|
register_state,
|
||||||
@@ -739,7 +749,6 @@ impl EventHandler {
|
|||||||
return self
|
return self
|
||||||
.handle_core_action(
|
.handle_core_action(
|
||||||
action,
|
action,
|
||||||
form_state,
|
|
||||||
auth_state,
|
auth_state,
|
||||||
login_state,
|
login_state,
|
||||||
register_state,
|
register_state,
|
||||||
@@ -792,15 +801,18 @@ impl EventHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if config.is_command_execute(key_code, modifiers) {
|
if config.is_command_execute(key_code, modifiers) {
|
||||||
let mut current_position = form_state.current_position;
|
let (mut current_position, total_count) = if let Some(fs) = app_state.form_state() {
|
||||||
let total_count = form_state.total_count;
|
(fs.current_position, fs.total_count)
|
||||||
|
} else {
|
||||||
|
(1, 0)
|
||||||
|
};
|
||||||
|
|
||||||
let outcome = command_mode::handle_command_event(
|
let outcome = command_mode::handle_command_event(
|
||||||
key_event,
|
key_event,
|
||||||
config,
|
config,
|
||||||
app_state,
|
app_state,
|
||||||
login_state,
|
login_state,
|
||||||
register_state,
|
register_state,
|
||||||
form_state,
|
|
||||||
&mut self.command_input,
|
&mut self.command_input,
|
||||||
&mut self.command_message,
|
&mut self.command_message,
|
||||||
&mut self.grpc_client,
|
&mut self.grpc_client,
|
||||||
@@ -808,9 +820,10 @@ impl EventHandler {
|
|||||||
terminal,
|
terminal,
|
||||||
&mut current_position,
|
&mut current_position,
|
||||||
total_count,
|
total_count,
|
||||||
)
|
).await?;
|
||||||
.await?;
|
if let Some(fs) = app_state.form_state_mut() {
|
||||||
form_state.current_position = current_position;
|
fs.current_position = current_position;
|
||||||
|
}
|
||||||
self.command_mode = false;
|
self.command_mode = false;
|
||||||
self.key_sequence_tracker.reset();
|
self.key_sequence_tracker.reset();
|
||||||
let new_mode = ModeManager::derive_mode(
|
let new_mode = ModeManager::derive_mode(
|
||||||
@@ -921,7 +934,6 @@ impl EventHandler {
|
|||||||
async fn handle_core_action(
|
async fn handle_core_action(
|
||||||
&mut self,
|
&mut self,
|
||||||
action: &str,
|
action: &str,
|
||||||
form_state: &mut FormState,
|
|
||||||
auth_state: &mut AuthState,
|
auth_state: &mut AuthState,
|
||||||
login_state: &mut LoginState,
|
login_state: &mut LoginState,
|
||||||
register_state: &mut RegisterState,
|
register_state: &mut RegisterState,
|
||||||
@@ -940,12 +952,15 @@ impl EventHandler {
|
|||||||
.await?;
|
.await?;
|
||||||
Ok(EventOutcome::Ok(message))
|
Ok(EventOutcome::Ok(message))
|
||||||
} else {
|
} else {
|
||||||
let save_outcome = crate::tui::functions::common::form::save(
|
let save_outcome = if let Some(fs) = app_state.form_state_mut() {
|
||||||
app_state,
|
crate::tui::functions::common::form::save(
|
||||||
form_state,
|
app_state,
|
||||||
&mut self.grpc_client,
|
&mut self.grpc_client,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?
|
||||||
|
} else {
|
||||||
|
SaveOutcome::NoChange
|
||||||
|
};
|
||||||
let message = match save_outcome {
|
let message = match save_outcome {
|
||||||
SaveOutcome::NoChange => "No changes to save.".to_string(),
|
SaveOutcome::NoChange => "No changes to save.".to_string(),
|
||||||
SaveOutcome::UpdatedExisting => "Entry updated.".to_string(),
|
SaveOutcome::UpdatedExisting => "Entry updated.".to_string(),
|
||||||
@@ -975,10 +990,8 @@ impl EventHandler {
|
|||||||
} else {
|
} else {
|
||||||
let save_outcome = crate::tui::functions::common::form::save(
|
let save_outcome = crate::tui::functions::common::form::save(
|
||||||
app_state,
|
app_state,
|
||||||
form_state,
|
|
||||||
&mut self.grpc_client,
|
&mut self.grpc_client,
|
||||||
)
|
).await?;
|
||||||
.await?;
|
|
||||||
match save_outcome {
|
match save_outcome {
|
||||||
SaveOutcome::NoChange => "No changes to save.".to_string(),
|
SaveOutcome::NoChange => "No changes to save.".to_string(),
|
||||||
SaveOutcome::UpdatedExisting => "Entry updated.".to_string(),
|
SaveOutcome::UpdatedExisting => "Entry updated.".to_string(),
|
||||||
@@ -1003,13 +1016,17 @@ impl EventHandler {
|
|||||||
register_state,
|
register_state,
|
||||||
app_state,
|
app_state,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
} else {
|
} else {
|
||||||
crate::tui::functions::common::form::revert(
|
if let Some(fs) = app_state.form_state_mut() {
|
||||||
form_state,
|
crate::tui::functions::common::form::revert(
|
||||||
&mut self.grpc_client,
|
app_state,
|
||||||
)
|
&mut self.grpc_client,
|
||||||
.await?
|
)
|
||||||
|
.await?
|
||||||
|
} else {
|
||||||
|
"Nothing to revert".to_string()
|
||||||
|
}
|
||||||
};
|
};
|
||||||
Ok(EventOutcome::Ok(message))
|
Ok(EventOutcome::Ok(message))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
// src/tui/functions/common/form.rs
|
// src/tui/functions/common/form.rs
|
||||||
|
|
||||||
use crate::services::grpc_client::GrpcClient;
|
use crate::services::grpc_client::GrpcClient;
|
||||||
use crate::state::app::state::AppState; // NEW: Import AppState
|
use crate::state::app::state::AppState;
|
||||||
use crate::state::pages::form::FormState;
|
use crate::utils::data_converter;
|
||||||
use crate::utils::data_converter; // NEW: Import our translator
|
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
@@ -14,143 +12,137 @@ pub enum SaveOutcome {
|
|||||||
CreatedNew(i64),
|
CreatedNew(i64),
|
||||||
}
|
}
|
||||||
|
|
||||||
// MODIFIED save function signature and logic
|
|
||||||
pub async fn save(
|
pub async fn save(
|
||||||
app_state: &AppState, // NEW: Pass in AppState
|
app_state: &mut AppState,
|
||||||
form_state: &mut FormState,
|
|
||||||
grpc_client: &mut GrpcClient,
|
grpc_client: &mut GrpcClient,
|
||||||
) -> Result<SaveOutcome> {
|
) -> Result<SaveOutcome> {
|
||||||
if !form_state.has_unsaved_changes {
|
if let Some(fs) = app_state.form_state_mut() {
|
||||||
return Ok(SaveOutcome::NoChange);
|
if !fs.has_unsaved_changes {
|
||||||
}
|
return Ok(SaveOutcome::NoChange);
|
||||||
|
|
||||||
// --- NEW: VALIDATION & CONVERSION STEP ---
|
|
||||||
let cache_key =
|
|
||||||
format!("{}.{}", form_state.profile_name, form_state.table_name);
|
|
||||||
let schema = match app_state.schema_cache.get(&cache_key) {
|
|
||||||
Some(s) => s,
|
|
||||||
None => {
|
|
||||||
return Err(anyhow!(
|
|
||||||
"Schema for table '{}' not found in cache. Cannot save.",
|
|
||||||
form_state.table_name
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
let data_map: HashMap<String, String> = form_state
|
// Copy out what we need before dropping the mutable borrow
|
||||||
.fields
|
let profile_name = fs.profile_name.clone();
|
||||||
.iter()
|
let table_name = fs.table_name.clone();
|
||||||
.zip(form_state.values.iter())
|
let fields = fs.fields.clone();
|
||||||
.map(|(field_def, value)| (field_def.data_key.clone(), value.clone()))
|
let values = fs.values.clone();
|
||||||
.collect();
|
let id = fs.id;
|
||||||
|
let total_count = fs.total_count;
|
||||||
|
let current_position = fs.current_position;
|
||||||
|
|
||||||
// Use our new translator. It returns a user-friendly error on failure.
|
let cache_key = format!("{}.{}", profile_name, table_name);
|
||||||
let converted_data =
|
let schema = app_state
|
||||||
match data_converter::convert_and_validate_data(&data_map, schema) {
|
.schema_cache
|
||||||
Ok(data) => data,
|
.get(&cache_key)
|
||||||
Err(user_error) => return Err(anyhow!(user_error)),
|
.ok_or_else(|| {
|
||||||
|
anyhow!(
|
||||||
|
"Schema for table '{}' not found in cache. Cannot save.",
|
||||||
|
table_name
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let data_map: HashMap<String, String> = fields
|
||||||
|
.iter()
|
||||||
|
.zip(values.iter())
|
||||||
|
.map(|(field_def, value)| (field_def.data_key.clone(), value.clone()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let converted_data =
|
||||||
|
data_converter::convert_and_validate_data(&data_map, schema)
|
||||||
|
.map_err(|user_error| anyhow!(user_error))?;
|
||||||
|
|
||||||
|
let is_new_entry = id == 0
|
||||||
|
|| (total_count > 0 && current_position > total_count)
|
||||||
|
|| (total_count == 0 && current_position == 1);
|
||||||
|
|
||||||
|
let outcome = if is_new_entry {
|
||||||
|
let response = grpc_client
|
||||||
|
.post_table_data(profile_name.clone(), table_name.clone(), converted_data)
|
||||||
|
.await
|
||||||
|
.context("Failed to post new table data")?;
|
||||||
|
|
||||||
|
if response.success {
|
||||||
|
if let Some(fs) = app_state.form_state_mut() {
|
||||||
|
fs.id = response.inserted_id;
|
||||||
|
fs.total_count += 1;
|
||||||
|
fs.current_position = fs.total_count;
|
||||||
|
fs.has_unsaved_changes = false;
|
||||||
|
}
|
||||||
|
SaveOutcome::CreatedNew(response.inserted_id)
|
||||||
|
} else {
|
||||||
|
return Err(anyhow!("Server failed to insert data: {}", response.message));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if id == 0 {
|
||||||
|
return Err(anyhow!(
|
||||||
|
"Cannot update record: ID is 0, but not classified as new entry."
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let response = grpc_client
|
||||||
|
.put_table_data(profile_name.clone(), table_name.clone(), id, converted_data)
|
||||||
|
.await
|
||||||
|
.context("Failed to put (update) table data")?;
|
||||||
|
|
||||||
|
if response.success {
|
||||||
|
if let Some(fs) = app_state.form_state_mut() {
|
||||||
|
fs.has_unsaved_changes = false;
|
||||||
|
}
|
||||||
|
SaveOutcome::UpdatedExisting
|
||||||
|
} else {
|
||||||
|
return Err(anyhow!("Server failed to update data: {}", response.message));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
// --- END OF NEW STEP ---
|
|
||||||
|
|
||||||
let outcome: SaveOutcome;
|
Ok(outcome)
|
||||||
let is_new_entry = form_state.id == 0
|
|
||||||
|| (form_state.total_count > 0
|
|
||||||
&& form_state.current_position > form_state.total_count)
|
|
||||||
|| (form_state.total_count == 0 && form_state.current_position == 1);
|
|
||||||
|
|
||||||
if is_new_entry {
|
|
||||||
let response = grpc_client
|
|
||||||
.post_table_data(
|
|
||||||
form_state.profile_name.clone(),
|
|
||||||
form_state.table_name.clone(),
|
|
||||||
converted_data, // Use the validated & converted data
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.context("Failed to post new table data")?;
|
|
||||||
|
|
||||||
if response.success {
|
|
||||||
form_state.id = response.inserted_id;
|
|
||||||
form_state.total_count += 1;
|
|
||||||
form_state.current_position = form_state.total_count;
|
|
||||||
outcome = SaveOutcome::CreatedNew(response.inserted_id);
|
|
||||||
} else {
|
|
||||||
return Err(anyhow!(
|
|
||||||
"Server failed to insert data: {}",
|
|
||||||
response.message
|
|
||||||
));
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if form_state.id == 0 {
|
Ok(SaveOutcome::NoChange)
|
||||||
return Err(anyhow!(
|
|
||||||
"Cannot update record: ID is 0, but not classified as new entry."
|
|
||||||
));
|
|
||||||
}
|
|
||||||
let response = grpc_client
|
|
||||||
.put_table_data(
|
|
||||||
form_state.profile_name.clone(),
|
|
||||||
form_state.table_name.clone(),
|
|
||||||
form_state.id,
|
|
||||||
converted_data, // Use the validated & converted data
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.context("Failed to put (update) table data")?;
|
|
||||||
|
|
||||||
if response.success {
|
|
||||||
outcome = SaveOutcome::UpdatedExisting;
|
|
||||||
} else {
|
|
||||||
return Err(anyhow!(
|
|
||||||
"Server failed to update data: {}",
|
|
||||||
response.message
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
form_state.has_unsaved_changes = false;
|
|
||||||
Ok(outcome)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn revert(
|
pub async fn revert(
|
||||||
form_state: &mut FormState, // Takes &mut FormState to update it
|
app_state: &mut AppState,
|
||||||
grpc_client: &mut GrpcClient,
|
grpc_client: &mut GrpcClient,
|
||||||
) -> Result<String> {
|
) -> Result<String> {
|
||||||
if form_state.id == 0 || (form_state.total_count > 0 && form_state.current_position > form_state.total_count) || (form_state.total_count == 0 && form_state.current_position == 1) {
|
if let Some(fs) = app_state.form_state_mut() {
|
||||||
let old_total_count = form_state.total_count; // Preserve for correct new position
|
if fs.id == 0
|
||||||
form_state.reset_to_empty(); // reset_to_empty will clear values and set id=0
|
|| (fs.total_count > 0 && fs.current_position > fs.total_count)
|
||||||
form_state.total_count = old_total_count; // Restore total_count
|
|| (fs.total_count == 0 && fs.current_position == 1)
|
||||||
if form_state.total_count > 0 { // Correctly set current_position for new
|
{
|
||||||
form_state.current_position = form_state.total_count + 1;
|
let old_total_count = fs.total_count;
|
||||||
} else {
|
fs.reset_to_empty();
|
||||||
form_state.current_position = 1;
|
fs.total_count = old_total_count;
|
||||||
|
if fs.total_count > 0 {
|
||||||
|
fs.current_position = fs.total_count + 1;
|
||||||
|
} else {
|
||||||
|
fs.current_position = 1;
|
||||||
|
}
|
||||||
|
return Ok("New entry cleared".to_string());
|
||||||
}
|
}
|
||||||
return Ok("New entry cleared".to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
if form_state.current_position == 0 || form_state.current_position > form_state.total_count {
|
if fs.current_position == 0 || fs.current_position > fs.total_count {
|
||||||
if form_state.total_count > 0 {
|
if fs.total_count > 0 {
|
||||||
form_state.current_position = 1;
|
fs.current_position = 1;
|
||||||
} else {
|
} else {
|
||||||
// No records to revert to, effectively a new entry state.
|
fs.reset_to_empty();
|
||||||
form_state.reset_to_empty();
|
return Ok("No saved data to revert to; form cleared.".to_string());
|
||||||
return Ok("No saved data to revert to; form cleared.".to_string());
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let response = grpc_client
|
||||||
|
.get_table_data_by_position(
|
||||||
|
fs.profile_name.clone(),
|
||||||
|
fs.table_name.clone(),
|
||||||
|
fs.current_position as i32,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.context(format!(
|
||||||
|
"Failed to get table data by position {} for table {}.{}",
|
||||||
|
fs.current_position, fs.profile_name, fs.table_name
|
||||||
|
))?;
|
||||||
|
|
||||||
|
fs.update_from_response(&response.data, fs.current_position);
|
||||||
|
Ok("Changes discarded, reloaded last saved version".to_string())
|
||||||
|
} else {
|
||||||
|
Ok("Nothing to revert".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
let response = grpc_client
|
|
||||||
.get_table_data_by_position(
|
|
||||||
form_state.profile_name.clone(),
|
|
||||||
form_state.table_name.clone(),
|
|
||||||
form_state.current_position as i32,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.context(format!(
|
|
||||||
"Failed to get table data by position {} for table {}.{}",
|
|
||||||
form_state.current_position,
|
|
||||||
form_state.profile_name,
|
|
||||||
form_state.table_name
|
|
||||||
))?;
|
|
||||||
|
|
||||||
// FIX: Pass the current position as the second argument
|
|
||||||
form_state.update_from_response(&response.data, form_state.current_position);
|
|
||||||
Ok("Changes discarded, reloaded last saved version".to_string())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ use crate::ui::handlers::context::DialogPurpose;
|
|||||||
use crate::tui::functions::common::login;
|
use crate::tui::functions::common::login;
|
||||||
use crate::tui::functions::common::register;
|
use crate::tui::functions::common::register;
|
||||||
use crate::utils::columns::filter_user_columns;
|
use crate::utils::columns::filter_user_columns;
|
||||||
|
use canvas::keymap::KeyEventOutcome;
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use crossterm::cursor::SetCursorStyle;
|
use crossterm::cursor::SetCursorStyle;
|
||||||
use crossterm::event as crossterm_event;
|
use crossterm::event as crossterm_event;
|
||||||
@@ -193,33 +194,48 @@ pub async fn run_ui() -> Result<()> {
|
|||||||
if crossterm_event::poll(std::time::Duration::from_millis(1))? {
|
if crossterm_event::poll(std::time::Duration::from_millis(1))? {
|
||||||
let event = event_reader.read_event().context("Failed to read terminal event")?;
|
let event = event_reader.read_event().context("Failed to read terminal event")?;
|
||||||
event_processed = true;
|
event_processed = true;
|
||||||
let event_outcome_result = {
|
|
||||||
// We need to avoid borrowing app_state twice, so we'll need to modify the handle_event call
|
|
||||||
// For now, let's create a temporary approach
|
|
||||||
let mut temp_form_state = app_state.form_state_mut().unwrap().clone();
|
|
||||||
let result = event_handler.handle_event(
|
|
||||||
event,
|
|
||||||
&config,
|
|
||||||
&mut terminal,
|
|
||||||
&mut command_handler,
|
|
||||||
&mut temp_form_state,
|
|
||||||
&mut auth_state,
|
|
||||||
&mut login_state,
|
|
||||||
&mut register_state,
|
|
||||||
&mut intro_state,
|
|
||||||
&mut admin_state,
|
|
||||||
&mut buffer_state,
|
|
||||||
&mut app_state,
|
|
||||||
).await;
|
|
||||||
|
|
||||||
// Update the app_state with any changes from temp_form_state
|
if let crossterm_event::Event::Key(key_event) = &event {
|
||||||
if let Some(form_state) = app_state.form_state_mut() {
|
if app_state.ui.show_form {
|
||||||
*form_state = temp_form_state;
|
if let Some(editor) = app_state.form_editor.as_mut() {
|
||||||
|
match editor.handle_key_event(*key_event) {
|
||||||
|
KeyEventOutcome::Consumed(Some(msg)) => {
|
||||||
|
event_handler.command_message = msg;
|
||||||
|
needs_redraw = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
KeyEventOutcome::Consumed(None) => {
|
||||||
|
needs_redraw = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
KeyEventOutcome::Pending => {
|
||||||
|
needs_redraw = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
KeyEventOutcome::NotMatched => {
|
||||||
|
// fall through to client-level handling
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
result
|
// Get form state from app_state and pass to handle_event
|
||||||
};
|
let form_state = app_state.form_state_mut().unwrap();
|
||||||
|
|
||||||
|
let event_outcome_result = event_handler.handle_event(
|
||||||
|
event,
|
||||||
|
&config,
|
||||||
|
&mut terminal,
|
||||||
|
&mut command_handler,
|
||||||
|
&mut auth_state,
|
||||||
|
&mut login_state,
|
||||||
|
&mut register_state,
|
||||||
|
&mut intro_state,
|
||||||
|
&mut admin_state,
|
||||||
|
&mut buffer_state,
|
||||||
|
&mut app_state,
|
||||||
|
).await;
|
||||||
let mut should_exit = false;
|
let mut should_exit = false;
|
||||||
match event_outcome_result {
|
match event_outcome_result {
|
||||||
Ok(outcome) => match outcome {
|
Ok(outcome) => match outcome {
|
||||||
|
|||||||
Reference in New Issue
Block a user