router2, needs bug fixes

This commit is contained in:
Priec
2025-08-22 22:52:20 +02:00
parent 957f5bf9f0
commit 5d97e63f93
7 changed files with 337 additions and 335 deletions

View File

@@ -11,7 +11,8 @@ use tokio::sync::mpsc;
use anyhow::Result; use anyhow::Result;
use crate::components::common::text_editor::TextEditor; use crate::components::common::text_editor::TextEditor;
use crate::services::ui_service::UiService; use crate::services::ui_service::UiService;
use tui_textarea::CursorMove; // Ensure this import is present use tui_textarea::CursorMove;
use crate::pages::routing::{Router, Page};
pub type SaveLogicResultSender = mpsc::Sender<Result<String>>; pub type SaveLogicResultSender = mpsc::Sender<Result<String>>;
@@ -25,6 +26,7 @@ pub fn handle_add_logic_navigation(
grpc_client: GrpcClient, grpc_client: GrpcClient,
_save_logic_sender: SaveLogicResultSender, // Marked as unused _save_logic_sender: SaveLogicResultSender, // Marked as unused
command_message: &mut String, command_message: &mut String,
router: &mut Router,
) -> bool { ) -> bool {
// === FULLSCREEN SCRIPT EDITING - COMPLETE ISOLATION === // === FULLSCREEN SCRIPT EDITING - COMPLETE ISOLATION ===
if add_logic_state.current_focus == AddLogicFocus::InsideScriptContent { if add_logic_state.current_focus == AddLogicFocus::InsideScriptContent {
@@ -380,7 +382,7 @@ pub fn handle_add_logic_navigation(
} }
AddLogicFocus::CancelButton => { AddLogicFocus::CancelButton => {
buffer_state.update_history(AppView::Admin); buffer_state.update_history(AppView::Admin);
app_state.ui.show_add_logic = false; router.navigate(Page::Admin(app_state.admin_state().clone()));
*command_message = "Cancelled Add Logic".to_string(); *command_message = "Cancelled Add Logic".to_string();
*is_edit_mode = false; *is_edit_mode = false;
} }

View File

@@ -1,7 +1,7 @@
// src/modes/canvas/common_mode.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::LoginState, auth::RegisterState, auth::AuthState}; use crate::state::pages::auth::AuthState;
use crate::state::app::state::AppState; use crate::state::app::state::AppState;
use crate::services::grpc_client::GrpcClient; use crate::services::grpc_client::GrpcClient;
use crate::services::auth::AuthClient; use crate::services::auth::AuthClient;
@@ -13,74 +13,83 @@ use crate::tui::functions::common::{
login::{save as login_save, revert as login_revert}, login::{save as login_save, revert as login_revert},
register::{revert as register_revert}, register::{revert as register_revert},
}; };
use crate::pages::routing::{Router, Page};
pub async fn handle_core_action( pub async fn handle_core_action(
action: &str, action: &str,
form_state: &mut FormState,
auth_state: &mut AuthState, auth_state: &mut AuthState,
login_state: &mut LoginState,
register_state: &mut RegisterState,
grpc_client: &mut GrpcClient, grpc_client: &mut GrpcClient,
auth_client: &mut AuthClient, auth_client: &mut AuthClient,
terminal: &mut TerminalCore, terminal: &mut TerminalCore,
app_state: &mut AppState, app_state: &mut AppState,
router: &mut Router,
) -> Result<EventOutcome> { ) -> Result<EventOutcome> {
match action { match action {
"save" => { "save" => {
if app_state.ui.show_login { match &mut router.current {
let message = login_save(auth_state, login_state, auth_client, app_state).await.context("Login save action failed")?; Page::Login(state) => {
Ok(EventOutcome::Ok(message)) let message = login_save(auth_state, state, auth_client, app_state)
} else { .await
let save_outcome = form_save( .context("Login save action failed")?;
app_state, Ok(EventOutcome::Ok(message))
form_state, }
grpc_client, Page::Form(form_state) => {
).await.context("Register save action failed")?; let save_outcome = form_save(app_state, form_state, grpc_client)
let message = match save_outcome { .await
SaveOutcome::NoChange => "No changes to save.".to_string(), .context("Form save action failed")?;
SaveOutcome::UpdatedExisting => "Entry updated.".to_string(), let message = match save_outcome {
SaveOutcome::CreatedNew(_) => "New entry created.".to_string(), SaveOutcome::NoChange => "No changes to save.".to_string(),
}; SaveOutcome::UpdatedExisting => "Entry updated.".to_string(),
Ok(EventOutcome::DataSaved(save_outcome, message)) SaveOutcome::CreatedNew(_) => "New entry created.".to_string(),
};
Ok(EventOutcome::DataSaved(save_outcome, message))
}
_ => Ok(EventOutcome::Ok("Save not applicable".into())),
} }
}, }
"force_quit" => { "force_quit" => {
terminal.cleanup()?; terminal.cleanup()?;
Ok(EventOutcome::Exit("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 = match &mut router.current {
login_save(auth_state, login_state, auth_client, app_state).await.context("Login save n quit action failed")? Page::Login(state) => {
} else { login_save(auth_state, state, auth_client, app_state)
let save_outcome = form_save( .await
app_state, .context("Login save and quit action failed")?
form_state,
grpc_client,
).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(),
} }
Page::Form(form_state) => {
let save_outcome = form_save(app_state, form_state, grpc_client).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(),
}
}
_ => "Save not applicable".to_string(),
}; };
terminal.cleanup()?; terminal.cleanup()?;
Ok(EventOutcome::Exit(format!("{}. Exiting application.", message))) Ok(EventOutcome::Exit(format!("{}. Exiting application.", message)))
}, }
"revert" => { "revert" => {
if app_state.ui.show_login { match &mut router.current {
let message = login_revert(login_state, app_state).await; Page::Login(state) => {
Ok(EventOutcome::Ok(message)) let message = login_revert(state, app_state).await;
} else if app_state.ui.show_register { Ok(EventOutcome::Ok(message))
let message = register_revert(register_state, app_state).await; }
Ok(EventOutcome::Ok(message)) Page::Register(state) => {
} else { let message = register_revert(state, app_state).await;
let message = form_revert( Ok(EventOutcome::Ok(message))
form_state, }
grpc_client, Page::Form(form_state) => {
).await.context("Form revert x action failed")?; let message = form_revert(form_state, grpc_client)
Ok(EventOutcome::Ok(message)) .await
.context("Form revert action failed")?;
Ok(EventOutcome::Ok(message))
}
_ => Ok(EventOutcome::Ok("Revert not applicable".into())),
} }
}, }
_ => Ok(EventOutcome::Ok(format!("Core action not handled: {}", action))), _ => Ok(EventOutcome::Ok(format!("Core action not handled: {}", action))),
} }
} }

View File

@@ -1,7 +1,7 @@
// src/modes/common/commands.rs // src/modes/common/commands.rs
use crate::tui::terminal::core::TerminalCore; use crate::tui::terminal::core::TerminalCore;
use crate::state::app::state::AppState; use crate::state::app::state::AppState;
use crate::state::pages::{auth::LoginState, auth::RegisterState}; use crate::pages::routing::{Router, Page};
use anyhow::Result; use anyhow::Result;
pub struct CommandHandler; pub struct CommandHandler;
@@ -16,11 +16,10 @@ impl CommandHandler {
action: &str, action: &str,
terminal: &mut TerminalCore, terminal: &mut TerminalCore,
app_state: &mut AppState, app_state: &mut AppState,
login_state: &LoginState, router: &Router,
register_state: &RegisterState,
) -> Result<(bool, String)> { ) -> Result<(bool, String)> {
match action { match action {
"quit" => self.handle_quit(terminal, app_state, login_state, register_state).await, "quit" => self.handle_quit(terminal, app_state, router).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,18 +30,14 @@ impl CommandHandler {
&self, &self,
terminal: &mut TerminalCore, terminal: &mut TerminalCore,
app_state: &mut AppState, app_state: &mut AppState,
login_state: &LoginState, router: &Router,
register_state: &RegisterState,
) -> Result<(bool, String)> { ) -> Result<(bool, String)> {
// Use actual unsaved changes state instead of is_saved flag // Use router to check unsaved changes
let has_unsaved = if app_state.ui.show_login { let has_unsaved = match &router.current {
login_state.has_unsaved_changes() Page::Login(state) => state.has_unsaved_changes(),
} else if app_state.ui.show_register { Page::Register(state) => state.has_unsaved_changes(),
register_state.has_unsaved_changes() Page::Form(fs) => fs.has_unsaved_changes,
} else if let Some(fs) = app_state.form_state_mut() { _ => false,
fs.has_unsaved_changes
} else {
false
}; };
if !has_unsaved { if !has_unsaved {

View File

@@ -30,6 +30,7 @@ use crate::state::{
intro::IntroState, intro::IntroState,
}, },
}; };
use crate::pages::routing::{Router, Page};
use crate::search::state::SearchState; use crate::search::state::SearchState;
use crate::tui::functions::common::login::LoginResult; use crate::tui::functions::common::login::LoginResult;
use crate::tui::functions::common::register::RegisterResult; use crate::tui::functions::common::register::RegisterResult;
@@ -127,90 +128,69 @@ impl EventHandler {
// Helper functions - replace the removed event_helper functions // Helper functions - replace the removed event_helper functions
fn get_current_field_for_state( fn get_current_field_for_state(
app_state: &AppState, router: &Router,
login_state: &LoginState,
register_state: &RegisterState,
form_state: &FormState,
) -> usize { ) -> usize {
if app_state.ui.show_login { match &router.current {
login_state.current_field() Page::Login(state) => state.current_field(),
} else if app_state.ui.show_register { Page::Register(state) => state.current_field(),
register_state.current_field() Page::Form(state) => state.current_field(),
} else { _ => 0,
form_state.current_field()
} }
} }
fn get_current_cursor_pos_for_state( fn get_current_cursor_pos_for_state(
app_state: &AppState, router: &Router,
login_state: &LoginState,
register_state: &RegisterState,
form_state: &FormState,
) -> usize { ) -> usize {
if app_state.ui.show_login { match &router.current {
login_state.current_cursor_pos() Page::Login(state) => state.current_cursor_pos(),
} else if app_state.ui.show_register { Page::Register(state) => state.current_cursor_pos(),
register_state.current_cursor_pos() Page::Form(state) => state.current_cursor_pos(),
} else { _ => 0,
form_state.current_cursor_pos()
} }
} }
fn get_has_unsaved_changes_for_state( fn get_has_unsaved_changes_for_state(
app_state: &AppState, router: &Router,
login_state: &LoginState,
register_state: &RegisterState,
form_state: &FormState,
) -> bool { ) -> bool {
if app_state.ui.show_login { match &router.current {
login_state.has_unsaved_changes() Page::Login(state) => state.has_unsaved_changes(),
} else if app_state.ui.show_register { Page::Register(state) => state.has_unsaved_changes(),
register_state.has_unsaved_changes() Page::Form(state) => state.has_unsaved_changes(),
} else { _ => false,
form_state.has_unsaved_changes()
} }
} }
fn get_current_input_for_state<'a>( fn get_current_input_for_state<'a>(
app_state: &AppState, router: &'a Router,
login_state: &'a LoginState,
register_state: &'a RegisterState,
form_state: &'a FormState,
) -> &'a str { ) -> &'a str {
if app_state.ui.show_login { match &router.current {
login_state.get_current_input() Page::Login(state) => state.get_current_input(),
} else if app_state.ui.show_register { Page::Register(state) => state.get_current_input(),
register_state.get_current_input() Page::Form(state) => state.get_current_input(),
} else { _ => "",
form_state.get_current_input()
} }
} }
fn set_current_cursor_pos_for_state( fn set_current_cursor_pos_for_state(
app_state: &AppState, router: &mut Router,
login_state: &mut LoginState,
register_state: &mut RegisterState,
form_state: &mut FormState,
pos: usize, pos: usize,
) { ) {
if app_state.ui.show_login { match &mut router.current {
login_state.set_current_cursor_pos(pos); Page::Login(state) => state.set_current_cursor_pos(pos),
} else if app_state.ui.show_register { Page::Register(state) => state.set_current_cursor_pos(pos),
register_state.set_current_cursor_pos(pos); Page::Form(state) => state.set_current_cursor_pos(pos),
} else { _ => {},
form_state.set_current_cursor_pos(pos);
} }
} }
fn get_cursor_pos_for_mixed_state( fn get_cursor_pos_for_mixed_state(
app_state: &AppState, router: &Router,
login_state: &LoginState,
form_state: &FormState,
) -> usize { ) -> usize {
if app_state.ui.show_login || app_state.ui.show_register { match &router.current {
login_state.current_cursor_pos() Page::Login(state) => state.current_cursor_pos(),
} else { Page::Register(state) => state.current_cursor_pos(),
form_state.current_cursor_pos() Page::Form(state) => state.current_cursor_pos(),
_ => 0,
} }
} }
@@ -222,12 +202,9 @@ impl EventHandler {
terminal: &mut TerminalCore, terminal: &mut TerminalCore,
command_handler: &mut CommandHandler, command_handler: &mut CommandHandler,
auth_state: &mut AuthState, auth_state: &mut AuthState,
login_state: &mut LoginState,
register_state: &mut RegisterState,
intro_state: &mut IntroState,
admin_state: &mut AdminState,
buffer_state: &mut BufferState, buffer_state: &mut BufferState,
app_state: &mut AppState, app_state: &mut AppState,
router: &mut Router,
) -> 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 {
@@ -244,7 +221,7 @@ impl EventHandler {
} }
let mut current_mode = let mut current_mode =
ModeManager::derive_mode(app_state, self, admin_state); ModeManager::derive_mode(app_state, self, router);
if current_mode == AppMode::General && self.navigation_state.active { if current_mode == AppMode::General && self.navigation_state.active {
if let Event::Key(key_event) = event { if let Event::Key(key_event) = event {
@@ -258,7 +235,7 @@ impl EventHandler {
if !self.navigation_state.active { if !self.navigation_state.active {
self.command_message = outcome.get_message_if_ok(); self.command_message = outcome.get_message_if_ok();
current_mode = current_mode =
ModeManager::derive_mode(app_state, self, admin_state); ModeManager::derive_mode(app_state, self, router);
} }
app_state.update_mode(current_mode); app_state.update_mode(current_mode);
return Ok(outcome); return Ok(outcome);
@@ -269,25 +246,14 @@ impl EventHandler {
app_state.update_mode(current_mode); app_state.update_mode(current_mode);
let current_view = { let current_view = match &router.current {
let ui = &app_state.ui; Page::Intro(_) => AppView::Intro,
if ui.show_intro { Page::Login(_) => AppView::Login,
AppView::Intro Page::Register(_) => AppView::Register,
} else if ui.show_login { Page::Admin(_) => AppView::Admin,
AppView::Login Page::AddLogic(_) => AppView::AddLogic,
} else if ui.show_register { Page::AddTable(_) => AppView::AddTable,
AppView::Register Page::Form(_) => AppView::Form,
} else if ui.show_admin {
AppView::Admin
} else if ui.show_add_logic {
AppView::AddLogic
} else if ui.show_add_table {
AppView::AddTable
} else if ui.show_form {
AppView::Form
} else {
AppView::Scratch
}
}; };
buffer_state.update_history(current_view); buffer_state.update_history(current_view);
@@ -297,10 +263,8 @@ impl EventHandler {
&Event::Key(key_event), &Event::Key(key_event),
config, config,
app_state, app_state,
login_state,
register_state,
buffer_state, buffer_state,
admin_state, router,
) )
.await .await
{ {
@@ -386,7 +350,7 @@ impl EventHandler {
config.get_general_action(key_code, modifiers) config.get_general_action(key_code, modifiers)
{ {
if action == "open_search" { if action == "open_search" {
if app_state.ui.show_form { if let Page::Form(_) = &router.current {
if let Some(table_name) = if let Some(table_name) =
app_state.current_view_table_name.clone() app_state.current_view_table_name.clone()
{ {
@@ -405,31 +369,31 @@ impl EventHandler {
match current_mode { match current_mode {
AppMode::General => { AppMode::General => {
if app_state.ui.show_admin if let Page::Admin(admin_state) = &router.current {
&& auth_state.role.as_deref() == Some("admin") if auth_state.role.as_deref() == Some("admin") {
{ if admin_nav::handle_admin_navigation(
if admin_nav::handle_admin_navigation( key_event,
key_event, config,
config, app_state,
app_state, admin_state,
admin_state, buffer_state,
buffer_state, &mut self.command_message,
&mut self.command_message, ) {
) { return Ok(EventOutcome::Ok(
return Ok(EventOutcome::Ok( self.command_message.clone(),
self.command_message.clone(), ));
)); }
} }
} }
if app_state.ui.show_add_logic { if let Page::AddLogic(add_logic_state) = &mut router.current {
let client_clone = self.grpc_client.clone(); let client_clone = self.grpc_client.clone();
let sender_clone = self.save_logic_result_sender.clone(); let sender_clone = self.save_logic_result_sender.clone();
if add_logic_nav::handle_add_logic_navigation( if add_logic_nav::handle_add_logic_navigation(
key_event, key_event,
config, config,
app_state, app_state,
&mut admin_state.add_logic_state, add_logic_state,
&mut self.is_edit_mode, &mut self.is_edit_mode,
buffer_state, buffer_state,
client_clone, client_clone,
@@ -442,14 +406,14 @@ impl EventHandler {
} }
} }
if app_state.ui.show_add_table { if let Page::AddTable(add_table_state) = &mut router.current {
let client_clone = self.grpc_client.clone(); let client_clone = self.grpc_client.clone();
let sender_clone = self.save_table_result_sender.clone(); let sender_clone = self.save_table_result_sender.clone();
if add_table_nav::handle_add_table_navigation( if add_table_nav::handle_add_table_navigation(
key_event, key_event,
config, config,
app_state, app_state,
&mut admin_state.add_table_state, add_table_state,
client_clone, client_clone,
sender_clone, sender_clone,
&mut self.command_message, &mut self.command_message,
@@ -464,10 +428,7 @@ impl EventHandler {
key_event, key_event,
config, config,
app_state, app_state,
login_state, router,
register_state,
intro_state,
admin_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,
@@ -482,53 +443,68 @@ impl EventHandler {
buffer_state, buffer_state,
index, index,
); );
if app_state.ui.show_admin if let Page::Admin(admin_state) = &router.current {
&& !app_state if !app_state
.profile_tree .profile_tree
.profiles .profiles
.is_empty() .is_empty()
{ {
admin_state admin_state
.profile_list_state .profile_list_state
.select(Some(0)); .select(Some(0));
}
} }
format!("Intro Option {} selected", index) format!("Intro Option {} selected", index)
} }
UiContext::Login => match index { UiContext::Login => {
0 => login::initiate_login( if let Page::Login(login_state) = &router.current {
login_state, match index {
app_state, 0 => login::initiate_login(
self.auth_client.clone(), login_state,
self.login_result_sender.clone(), app_state,
), self.auth_client.clone(),
1 => login::back_to_main( self.login_result_sender.clone(),
login_state, ),
app_state, 1 => login::back_to_main(
buffer_state, login_state,
) app_state,
.await, buffer_state,
_ => "Invalid Login Option".to_string(), )
}, .await,
UiContext::Register => match index { _ => "Invalid Login Option".to_string(),
0 => register::initiate_registration( }
register_state, } else {
app_state, "Invalid state".to_string()
self.auth_client.clone(), }
self.register_result_sender.clone(), }
), UiContext::Register => {
1 => register::back_to_login( if let Page::Register(register_state) = &router.current {
register_state, match index {
app_state, 0 => register::initiate_registration(
buffer_state, register_state,
) app_state,
.await, self.auth_client.clone(),
_ => "Invalid Login Option".to_string(), self.register_result_sender.clone(),
}, ),
1 => register::back_to_login(
register_state,
app_state,
buffer_state,
)
.await,
_ => "Invalid Login Option".to_string(),
}
} else {
"Invalid state".to_string()
}
}
UiContext::Admin => { UiContext::Admin => {
admin::handle_admin_selection( if let Page::Admin(admin_state) = &router.current {
app_state, admin::handle_admin_selection(
admin_state, app_state,
); admin_state,
);
}
format!("Admin Option {} selected", index) format!("Admin Option {} selected", index)
} }
UiContext::Dialog => "Internal error: Unexpected dialog state" UiContext::Dialog => "Internal error: Unexpected dialog state"
@@ -542,7 +518,7 @@ impl EventHandler {
AppMode::ReadOnly => { AppMode::ReadOnly => {
// First let the canvas editor try to handle the key // First let the canvas editor try to handle the key
if app_state.ui.show_form { if let Page::Form(_) = &router.current {
if let Some(editor) = &mut app_state.form_editor { if let Some(editor) = &mut app_state.form_editor {
let outcome = editor.handle_key_event(key_event); let outcome = editor.handle_key_event(key_event);
let new_mode = AppMode::from(editor.mode()); let new_mode = AppMode::from(editor.mode());
@@ -588,10 +564,9 @@ impl EventHandler {
.handle_core_action( .handle_core_action(
action, action,
auth_state, auth_state,
login_state,
register_state,
terminal, terminal,
app_state, app_state,
router,
) )
.await; .await;
} }
@@ -603,7 +578,7 @@ impl EventHandler {
} }
AppMode::Highlight => { AppMode::Highlight => {
if app_state.ui.show_form { if let Page::Form(_) = &router.current {
if let Some(editor) = &mut app_state.form_editor { if let Some(editor) = &mut app_state.form_editor {
let outcome = editor.handle_key_event(key_event); let outcome = editor.handle_key_event(key_event);
let new_mode = AppMode::from(editor.mode()); let new_mode = AppMode::from(editor.mode());
@@ -640,10 +615,9 @@ impl EventHandler {
.handle_core_action( .handle_core_action(
action, action,
auth_state, auth_state,
login_state,
register_state,
terminal, terminal,
app_state, app_state,
router,
) )
.await; .await;
} }
@@ -652,7 +626,7 @@ impl EventHandler {
} }
// Let the canvas editor handle edit-mode keys // Let the canvas editor handle edit-mode keys
if app_state.ui.show_form { if let Page::Form(_) = &router.current {
if let Some(editor) = &mut app_state.form_editor { if let Some(editor) = &mut app_state.form_editor {
let outcome = editor.handle_key_event(key_event); let outcome = editor.handle_key_event(key_event);
let new_mode = AppMode::from(editor.mode()); let new_mode = AppMode::from(editor.mode());
@@ -696,7 +670,7 @@ impl EventHandler {
} }
if config.is_command_execute(key_code, modifiers) { if config.is_command_execute(key_code, modifiers) {
let (mut current_position, total_count) = if let Some(fs) = app_state.form_state() { let (mut current_position, total_count) = if let Page::Form(fs) = &router.current {
(fs.current_position, fs.total_count) (fs.current_position, fs.total_count)
} else { } else {
(1, 0) (1, 0)
@@ -706,8 +680,7 @@ impl EventHandler {
key_event, key_event,
config, config,
app_state, app_state,
login_state, router,
register_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,
@@ -716,7 +689,7 @@ impl EventHandler {
&mut current_position, &mut current_position,
total_count, total_count,
).await?; ).await?;
if let Some(fs) = app_state.form_state_mut() { if let Page::Form(fs) = &mut router.current {
fs.current_position = current_position; fs.current_position = current_position;
} }
self.command_mode = false; self.command_mode = false;
@@ -724,7 +697,7 @@ impl EventHandler {
let new_mode = ModeManager::derive_mode( let new_mode = ModeManager::derive_mode(
app_state, app_state,
self, self,
admin_state, router,
); );
app_state.update_mode(new_mode); app_state.update_mode(new_mode);
return Ok(outcome); return Ok(outcome);
@@ -746,9 +719,7 @@ impl EventHandler {
&sequence, &sequence,
) == Some("find_file_palette_toggle") ) == Some("find_file_palette_toggle")
{ {
if app_state.ui.show_form if matches!(&router.current, Page::Form(_) | Page::Intro(_)) {
|| app_state.ui.show_intro
{
let mut all_table_paths: Vec<String> = let mut all_table_paths: Vec<String> =
app_state app_state
.profile_tree .profile_tree
@@ -830,14 +801,13 @@ impl EventHandler {
&mut self, &mut self,
action: &str, action: &str,
auth_state: &mut AuthState, auth_state: &mut AuthState,
login_state: &mut LoginState,
register_state: &mut RegisterState,
terminal: &mut TerminalCore, terminal: &mut TerminalCore,
app_state: &mut AppState, app_state: &mut AppState,
router: &mut Router,
) -> Result<EventOutcome> { ) -> Result<EventOutcome> {
match action { match action {
"save" => { "save" => {
if app_state.ui.show_login { if let Page::Login(login_state) = &router.current {
let message = crate::tui::functions::common::login::save( let message = crate::tui::functions::common::login::save(
auth_state, auth_state,
login_state, login_state,
@@ -847,7 +817,7 @@ impl EventHandler {
.await?; .await?;
Ok(EventOutcome::Ok(message)) Ok(EventOutcome::Ok(message))
} else { } else {
let save_outcome = if let Some(fs) = app_state.form_state_mut() { let save_outcome = if let Page::Form(_) = &router.current {
crate::tui::functions::common::form::save( crate::tui::functions::common::form::save(
app_state, app_state,
&mut self.grpc_client, &mut self.grpc_client,
@@ -874,7 +844,7 @@ impl EventHandler {
)) ))
} }
"save_and_quit" => { "save_and_quit" => {
let message = if app_state.ui.show_login { let message = if let Page::Login(login_state) = &router.current {
crate::tui::functions::common::login::save( crate::tui::functions::common::login::save(
auth_state, auth_state,
login_state, login_state,
@@ -903,17 +873,17 @@ impl EventHandler {
))) )))
} }
"revert" => { "revert" => {
let message = if app_state.ui.show_login { let message = if let Page::Login(login_state) = &router.current {
crate::tui::functions::common::login::revert(login_state, app_state) crate::tui::functions::common::login::revert(login_state, app_state)
.await .await
} else if app_state.ui.show_register { } else if let Page::Register(register_state) = &router.current {
crate::tui::functions::common::register::revert( crate::tui::functions::common::register::revert(
register_state, register_state,
app_state, app_state,
) )
.await .await
} else { } else {
if let Some(fs) = app_state.form_state_mut() { if let Page::Form(_) = &router.current {
crate::tui::functions::common::form::revert( crate::tui::functions::common::form::revert(
app_state, app_state,
&mut self.grpc_client, &mut self.grpc_client,

View File

@@ -2,7 +2,7 @@
use crate::state::app::state::AppState; use crate::state::app::state::AppState;
use crate::modes::handlers::event::EventHandler; use crate::modes::handlers::event::EventHandler;
use crate::state::pages::add_logic::AddLogicFocus; use crate::state::pages::add_logic::AddLogicFocus;
use crate::state::pages::admin::AdminState; use crate::pages::routing::{Router, Page};
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AppMode { pub enum AppMode {
@@ -28,11 +28,11 @@ impl From<canvas::AppMode> for AppMode {
pub struct ModeManager; pub struct ModeManager;
impl ModeManager { impl ModeManager {
/// Determine current mode based on app state /// Determine current mode based on app state + router
pub fn derive_mode( pub fn derive_mode(
app_state: &AppState, app_state: &AppState,
event_handler: &EventHandler, event_handler: &EventHandler,
admin_state: &AdminState, router: &Router,
) -> AppMode { ) -> AppMode {
// Navigation palette always forces General // Navigation palette always forces General
if event_handler.navigation_state.active { if event_handler.navigation_state.active {
@@ -44,16 +44,17 @@ impl ModeManager {
return AppMode::Command; return AppMode::Command;
} }
// Always trust the FormEditor when a form is active match &router.current {
if app_state.ui.show_form && !app_state.ui.focus_outside_canvas { // --- Form view ---
if let Some(editor) = &app_state.form_editor { Page::Form(_) if !app_state.ui.focus_outside_canvas => {
return AppMode::from(editor.mode()); if let Some(editor) = &app_state.form_editor {
return AppMode::from(editor.mode());
}
AppMode::General
} }
}
// --- Non-form views (add_logic, add_table, etc.) --- // --- AddLogic view ---
if app_state.ui.show_add_logic { Page::AddLogic(state) => match state.current_focus {
match admin_state.add_logic_state.current_focus {
AddLogicFocus::InputLogicName AddLogicFocus::InputLogicName
| AddLogicFocus::InputTargetColumn | AddLogicFocus::InputTargetColumn
| AddLogicFocus::InputDescription => { | AddLogicFocus::InputDescription => {
@@ -64,26 +65,30 @@ impl ModeManager {
} }
} }
_ => AppMode::General, _ => AppMode::General,
},
// --- AddTable view ---
Page::AddTable(_) => {
if app_state.ui.focus_outside_canvas {
AppMode::General
} else if event_handler.is_edit_mode {
AppMode::Edit
} else {
AppMode::ReadOnly
}
} }
} else if app_state.ui.show_add_table {
if app_state.ui.focus_outside_canvas { // --- Login/Register views ---
AppMode::General Page::Login(_) | Page::Register(_) => {
} else if event_handler.is_edit_mode { if event_handler.is_edit_mode {
AppMode::Edit AppMode::Edit
} else { } else {
AppMode::ReadOnly AppMode::ReadOnly
}
} }
} else if app_state.ui.show_login
|| app_state.ui.show_register // --- Everything else (Intro, Admin, etc.) ---
{ _ => AppMode::General,
// login/register still use the old flag
if event_handler.is_edit_mode {
AppMode::Edit
} else {
AppMode::ReadOnly
}
} else {
AppMode::General
} }
} }

View File

@@ -8,7 +8,11 @@ use crate::components::{
intro::intro::render_intro, intro::intro::render_intro,
render_background, render_background,
}; };
use crate::bottom_panel::{command_line::render_command_line, status_line::render_status_line, find_file_palette}; use crate::bottom_panel::{
command_line::render_command_line,
status_line::render_status_line,
find_file_palette,
};
use crate::sidebar::{calculate_sidebar_layout, render_sidebar}; use crate::sidebar::{calculate_sidebar_layout, render_sidebar};
use crate::buffer::render_buffer_list; use crate::buffer::render_buffer_list;
use crate::search::render_search_palette; use crate::search::render_search_palette;
@@ -16,12 +20,7 @@ use crate::config::colors::themes::Theme;
use crate::modes::general::command_navigation::NavigationState; use crate::modes::general::command_navigation::NavigationState;
use crate::buffer::state::BufferState; use crate::buffer::state::BufferState;
use crate::state::app::state::AppState; use crate::state::app::state::AppState;
use crate::state::pages::admin::AdminState;
use crate::state::pages::auth::AuthState; use crate::state::pages::auth::AuthState;
use crate::state::pages::auth::LoginState;
use crate::state::pages::auth::RegisterState;
use crate::state::pages::form::FormState;
use crate::state::pages::intro::IntroState;
use crate::bottom_panel::layout::{bottom_panel_constraints, render_bottom_panel}; use crate::bottom_panel::layout::{bottom_panel_constraints, render_bottom_panel};
use crate::components::render_form; use crate::components::render_form;
use ratatui::{ use ratatui::{
@@ -46,6 +45,7 @@ pub fn render_ui(
) { ) {
render_background(f, f.area(), theme); render_background(f, f.area(), theme);
// Layout: optional buffer list + main content + bottom panel
let mut main_layout_constraints = vec![Constraint::Min(1)]; let mut main_layout_constraints = vec![Constraint::Min(1)];
if app_state.ui.show_buffer_list { if app_state.ui.show_buffer_list {
main_layout_constraints.insert(0, Constraint::Length(1)); main_layout_constraints.insert(0, Constraint::Length(1));
@@ -73,7 +73,7 @@ pub fn render_ui(
let main_content_area = root_chunks[chunk_idx]; let main_content_area = root_chunks[chunk_idx];
chunk_idx += 1; chunk_idx += 1;
// ✅ Instead of checking app_state.ui.show_*, just match router.current // Page rendering is now fully router-driven
match &mut router.current { match &mut router.current {
Page::Intro(state) => render_intro(f, state, main_content_area, theme), Page::Intro(state) => render_intro(f, state, main_content_area, theme),
Page::Login(state) => render_login( Page::Login(state) => render_login(
@@ -160,6 +160,7 @@ pub fn render_ui(
} }
} }
// Global overlays (not tied to a page)
if let Some(area) = buffer_list_area { if let Some(area) = buffer_list_area {
render_buffer_list(f, area, theme, buffer_state, app_state); render_buffer_list(f, area, theme, buffer_state, app_state);
} }

View File

@@ -199,7 +199,7 @@ pub async fn run_ui() -> Result<()> {
event_processed = true; event_processed = true;
if let crossterm_event::Event::Key(key_event) = &event { if let crossterm_event::Event::Key(key_event) = &event {
if app_state.ui.show_form { if let Page::Form(_) = &router.current {
if let Some(editor) = app_state.form_editor.as_mut() { if let Some(editor) = app_state.form_editor.as_mut() {
match editor.handle_key_event(*key_event) { match editor.handle_key_event(*key_event) {
KeyEventOutcome::Consumed(Some(msg)) => { KeyEventOutcome::Consumed(Some(msg)) => {
@@ -391,7 +391,7 @@ pub async fn run_ui() -> Result<()> {
} }
} }
if app_state.ui.show_form { if let Page::Form(_) = &router.current {
let current_view_profile = app_state.current_view_profile_name.clone(); let current_view_profile = app_state.current_view_profile_name.clone();
let current_view_table = app_state.current_view_table_name.clone(); let current_view_table = app_state.current_view_table_name.clone();
@@ -477,18 +477,21 @@ pub async fn run_ui() -> Result<()> {
// Now we can use CanvasState methods like get_current_input(), current_field(), etc. // Now we can use CanvasState methods like get_current_input(), current_field(), etc.
if let Some((profile_name, table_name)) = app_state.pending_table_structure_fetch.take() { if let Some((profile_name, table_name)) = app_state.pending_table_structure_fetch.take() {
if app_state.ui.show_add_logic { if let Page::AddLogic(state) = &mut router.current {
if admin_state.add_logic_state.profile_name == profile_name && if state.profile_name == profile_name
admin_state.add_logic_state.selected_table_name.as_deref() == Some(table_name.as_str()) { && state.selected_table_name.as_deref() == Some(table_name.as_str())
{
info!("Fetching table structure for {}.{}", profile_name, table_name); info!("Fetching table structure for {}.{}", profile_name, table_name);
let fetch_message = UiService::initialize_add_logic_table_data( let fetch_message = UiService::initialize_add_logic_table_data(
&mut grpc_client, &mut grpc_client,
&mut admin_state.add_logic_state, state,
&app_state.profile_tree, &app_state.profile_tree,
).await.unwrap_or_else(|e| { )
error!("Error initializing add_logic_table_data: {}", e); .await
format!("Error fetching table structure: {}", e) .unwrap_or_else(|e| {
}); error!("Error initializing add_logic_table_data: {}", e);
format!("Error fetching table structure: {}", e)
});
if !fetch_message.contains("Error") && !fetch_message.contains("Warning") { if !fetch_message.contains("Error") && !fetch_message.contains("Warning") {
info!("{}", fetch_message); info!("{}", fetch_message);
@@ -498,10 +501,11 @@ pub async fn run_ui() -> Result<()> {
needs_redraw = true; needs_redraw = true;
} else { } else {
error!( error!(
"Mismatch in pending_table_structure_fetch: app_state wants {}.{}, but add_logic_state is for {}.{:?}", "Mismatch in pending_table_structure_fetch: app_state wants {}.{}, but AddLogic state is for {}.{:?}",
profile_name, table_name, profile_name,
admin_state.add_logic_state.profile_name, table_name,
admin_state.add_logic_state.selected_table_name state.profile_name,
state.selected_table_name
); );
} }
} else { } else {
@@ -512,21 +516,21 @@ pub async fn run_ui() -> Result<()> {
} }
} }
if let Some(table_name) = admin_state.add_logic_state.script_editor_awaiting_column_autocomplete.clone() { if let Page::AddLogic(state) = &mut router.current {
if app_state.ui.show_add_logic { if let Some(table_name) = state.script_editor_awaiting_column_autocomplete.clone() {
let profile_name = admin_state.add_logic_state.profile_name.clone(); let profile_name = state.profile_name.clone();
info!("Fetching columns for table selection: {}.{}", profile_name, table_name); info!("Fetching columns for table selection: {}.{}", profile_name, table_name);
match UiService::fetch_columns_for_table(&mut grpc_client, &profile_name, &table_name).await { match UiService::fetch_columns_for_table(&mut grpc_client, &profile_name, &table_name).await {
Ok(columns) => { Ok(columns) => {
admin_state.add_logic_state.set_columns_for_table_autocomplete(columns.clone()); state.set_columns_for_table_autocomplete(columns.clone());
info!("Loaded {} columns for table '{}'", columns.len(), table_name); info!("Loaded {} columns for table '{}'", columns.len(), table_name);
event_handler.command_message = format!("Columns for '{}' loaded. Select a column.", table_name); event_handler.command_message = format!("Columns for '{}' loaded. Select a column.", table_name);
} }
Err(e) => { Err(e) => {
error!("Failed to fetch columns for {}.{}: {}", profile_name, table_name, e); error!("Failed to fetch columns for {}.{}: {}", profile_name, table_name, e);
admin_state.add_logic_state.script_editor_awaiting_column_autocomplete = None; state.script_editor_awaiting_column_autocomplete = None;
admin_state.add_logic_state.deactivate_script_editor_autocomplete(); state.deactivate_script_editor_autocomplete();
event_handler.command_message = format!("Error loading columns for '{}': {}", table_name, e); event_handler.command_message = format!("Error loading columns for '{}': {}", table_name, e);
} }
} }
@@ -540,59 +544,75 @@ pub async fn run_ui() -> Result<()> {
let position_changed = current_position != position_before_event; let position_changed = current_position != position_before_event;
let mut position_logic_needs_redraw = false; let mut position_logic_needs_redraw = false;
if app_state.ui.show_form && !table_just_switched { if let Page::Form(form_state) = &mut router.current {
if position_changed && !event_handler.is_edit_mode { if !table_just_switched {
position_logic_needs_redraw = true; if position_changed && !event_handler.is_edit_mode {
position_logic_needs_redraw = true;
if let Some(form_state) = app_state.form_state_mut() { if let Some(form_state) = app_state.form_state_mut() {
if form_state.current_position > form_state.total_count { if form_state.current_position > form_state.total_count {
form_state.reset_to_empty(); form_state.reset_to_empty();
event_handler.command_message = format!("New entry for {}.{}", form_state.profile_name, form_state.table_name); event_handler.command_message = format!(
} else { "New entry for {}.{}",
match UiService::load_table_data_by_position(&mut grpc_client, form_state).await { form_state.profile_name,
Ok(load_message) => { form_state.table_name
if event_handler.command_message.is_empty() || !load_message.starts_with("Error") { );
event_handler.command_message = load_message; } else {
match UiService::load_table_data_by_position(&mut grpc_client, form_state).await {
Ok(load_message) => {
if event_handler.command_message.is_empty()
|| !load_message.starts_with("Error")
{
event_handler.command_message = load_message;
}
}
Err(e) => {
event_handler.command_message =
format!("Error loading data: {}", e);
} }
} }
Err(e) => {
event_handler.command_message = format!("Error loading data: {}", e);
}
} }
}
let current_input_after_load_str = form_state.get_current_input(); let current_input_after_load_str = form_state.get_current_input();
let current_input_len_after_load = current_input_after_load_str.chars().count(); let current_input_len_after_load =
let max_cursor_pos = if current_input_len_after_load > 0 { current_input_after_load_str.chars().count();
current_input_len_after_load.saturating_sub(1) let max_cursor_pos = if current_input_len_after_load > 0 {
} else { current_input_len_after_load.saturating_sub(1)
0 } else {
}; 0
form_state.current_cursor_pos = event_handler.ideal_cursor_column.min(max_cursor_pos); };
} form_state.current_cursor_pos =
} else if !position_changed && !event_handler.is_edit_mode { event_handler.ideal_cursor_column.min(max_cursor_pos);
if let Some(form_state) = app_state.form_state_mut() { }
let current_input_str = form_state.get_current_input(); } else if !position_changed && !event_handler.is_edit_mode {
let current_input_len = current_input_str.chars().count(); if let Some(form_state) = app_state.form_state_mut() {
let max_cursor_pos = if current_input_len > 0 { let current_input_str = form_state.get_current_input();
current_input_len.saturating_sub(1) let current_input_len = current_input_str.chars().count();
} else { let max_cursor_pos = if current_input_len > 0 {
0 current_input_len.saturating_sub(1)
}; } else {
form_state.current_cursor_pos = event_handler.ideal_cursor_column.min(max_cursor_pos); 0
};
form_state.current_cursor_pos =
event_handler.ideal_cursor_column.min(max_cursor_pos);
}
} }
} }
} else if app_state.ui.show_register { } else if let Page::Register(state) = &mut router.current {
if !event_handler.is_edit_mode { if !event_handler.is_edit_mode {
let current_input = register_state.get_current_input(); let current_input = state.get_current_input();
let max_cursor_pos = if !current_input.is_empty() { current_input.len() - 1 } else { 0 }; let max_cursor_pos =
register_state.current_cursor_pos = event_handler.ideal_cursor_column.min(max_cursor_pos); if !current_input.is_empty() { current_input.len() - 1 } else { 0 };
state.current_cursor_pos =
event_handler.ideal_cursor_column.min(max_cursor_pos);
} }
} else if app_state.ui.show_login { } else if let Page::Login(state) = &mut router.current {
if !event_handler.is_edit_mode { if !event_handler.is_edit_mode {
let current_input = login_state.get_current_input(); let current_input = state.get_current_input();
let max_cursor_pos = if !current_input.is_empty() { current_input.len() - 1 } else { 0 }; let max_cursor_pos =
login_state.current_cursor_pos = event_handler.ideal_cursor_column.min(max_cursor_pos); if !current_input.is_empty() { current_input.len() - 1 } else { 0 };
state.current_cursor_pos =
event_handler.ideal_cursor_column.min(max_cursor_pos);
} }
} }
@@ -623,7 +643,7 @@ pub async fn run_ui() -> Result<()> {
} }
if event_processed || needs_redraw || position_changed { if event_processed || needs_redraw || position_changed {
let current_mode = ModeManager::derive_mode(&app_state, &event_handler, &admin_state); let current_mode = ModeManager::derive_mode(&app_state, &event_handler, &router);
match current_mode { match current_mode {
AppMode::Edit => { terminal.show_cursor()?; } AppMode::Edit => { terminal.show_cursor()?; }
AppMode::Highlight => { terminal.set_cursor_style(SetCursorStyle::SteadyBlock)?; terminal.show_cursor()?; } AppMode::Highlight => { terminal.set_cursor_style(SetCursorStyle::SteadyBlock)?; terminal.show_cursor()?; }