diff --git a/client/src/lib.rs b/client/src/lib.rs index 729fcb6..82df1f8 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -12,6 +12,7 @@ pub mod buffer; pub mod sidebar; pub mod search; pub mod bottom_panel; +pub mod pages; pub use ui::run_ui; diff --git a/client/src/modes/general/navigation.rs b/client/src/modes/general/navigation.rs index dbadc01..2be355b 100644 --- a/client/src/modes/general/navigation.rs +++ b/client/src/modes/general/navigation.rs @@ -4,10 +4,7 @@ use crossterm::event::KeyEvent; use crate::config::binds::config::Config; use crate::state::app::state::AppState; use crate::state::pages::form::FormState; -use crate::state::pages::auth::LoginState; -use crate::state::pages::auth::RegisterState; -use crate::state::pages::intro::IntroState; -use crate::state::pages::admin::AdminState; +use crate::pages::routing::{Router, Page}; use crate::ui::handlers::context::UiContext; use crate::modes::handlers::event::EventOutcome; use crate::modes::general::command_navigation::{handle_command_navigation_event, NavigationState}; @@ -18,10 +15,7 @@ pub async fn handle_navigation_event( key: KeyEvent, config: &Config, app_state: &mut AppState, - login_state: &mut LoginState, - register_state: &mut RegisterState, - intro_state: &mut IntroState, - admin_state: &mut AdminState, + router: &mut Router, command_mode: &mut bool, command_input: &mut String, command_message: &mut String, @@ -35,19 +29,19 @@ pub async fn handle_navigation_event( if let Some(action) = config.get_general_action(key.code, key.modifiers) { match action { "move_up" => { - move_up(app_state, login_state, register_state, intro_state, admin_state); + move_up(app_state, router); return Ok(EventOutcome::Ok(String::new())); } "move_down" => { - move_down(app_state, intro_state, admin_state); + move_down(app_state, router); return Ok(EventOutcome::Ok(String::new())); } "next_option" => { - next_option(app_state, intro_state); + next_option(app_state, router); return Ok(EventOutcome::Ok(String::new())); } "previous_option" => { - previous_option(app_state, intro_state); + previous_option(app_state, router); return Ok(EventOutcome::Ok(String::new())); } "next_field" => { @@ -67,18 +61,21 @@ pub async fn handle_navigation_event( return Ok(EventOutcome::Ok(String::new())); } "select" => { - let (context, index) = if app_state.ui.show_intro { - (UiContext::Intro, intro_state.selected_option) - } else if app_state.ui.show_login && app_state.ui.focus_outside_canvas { - (UiContext::Login, app_state.focused_button_index) - } else if app_state.ui.show_register && app_state.ui.focus_outside_canvas { - (UiContext::Register, app_state.focused_button_index) - } else if app_state.ui.show_admin { - (UiContext::Admin, admin_state.get_selected_index().unwrap_or(0)) - } else if app_state.ui.dialog.dialog_show { - (UiContext::Dialog, app_state.ui.dialog.dialog_active_button_index) - } else { - return Ok(EventOutcome::Ok("Select (No Action)".to_string())); + let (context, index) = match &router.current { + Page::Intro(state) => (UiContext::Intro, state.selected_option), + Page::Login(_) if app_state.ui.focus_outside_canvas => { + (UiContext::Login, app_state.focused_button_index) + } + Page::Register(_) if app_state.ui.focus_outside_canvas => { + (UiContext::Register, app_state.focused_button_index) + } + Page::Admin(state) => { + (UiContext::Admin, state.get_selected_index().unwrap_or(0)) + } + _ if app_state.ui.dialog.dialog_show => { + (UiContext::Dialog, app_state.ui.dialog.dialog_active_button_index) + } + _ => return Ok(EventOutcome::Ok("Select (No Action)".to_string())), }; return Ok(EventOutcome::ButtonSelected { context, index }); } @@ -88,60 +85,76 @@ pub async fn handle_navigation_event( Ok(EventOutcome::Ok(String::new())) } -pub fn move_up(app_state: &mut AppState, login_state: &mut LoginState, register_state: &mut RegisterState, intro_state: &mut IntroState, admin_state: &mut AdminState) { - if app_state.ui.focus_outside_canvas && app_state.ui.show_login || app_state.ui.show_register{ - if app_state.focused_button_index == 0 { - app_state.ui.focus_outside_canvas = false; - if app_state.ui.show_login { - let last_field_index = login_state.field_count().saturating_sub(1); - login_state.set_current_field(last_field_index); +pub fn move_up(app_state: &mut AppState, router: &mut Router) { + match &mut router.current { + Page::Login(state) if app_state.ui.focus_outside_canvas => { + if app_state.focused_button_index == 0 { + app_state.ui.focus_outside_canvas = false; + let last_field_index = state.field_count().saturating_sub(1); + state.set_current_field(last_field_index); } else { - let last_field_index = register_state.field_count().saturating_sub(1); - register_state.set_current_field(last_field_index); + app_state.focused_button_index = + app_state.focused_button_index.saturating_sub(1); } - } else { - app_state.focused_button_index = app_state.focused_button_index.saturating_sub(1); } - } else if app_state.ui.show_intro { - intro_state.previous_option(); - } else if app_state.ui.show_admin { - admin_state.previous(); - } -} - -pub fn move_down(app_state: &mut AppState, intro_state: &mut IntroState, admin_state: &mut AdminState) { - if app_state.ui.focus_outside_canvas && app_state.ui.show_login || app_state.ui.show_register { - let num_general_elements = 2; - if app_state.focused_button_index < num_general_elements - 1 { - app_state.focused_button_index += 1; + Page::Register(state) if app_state.ui.focus_outside_canvas => { + if app_state.focused_button_index == 0 { + app_state.ui.focus_outside_canvas = false; + let last_field_index = state.field_count().saturating_sub(1); + state.set_current_field(last_field_index); + } else { + app_state.focused_button_index = + app_state.focused_button_index.saturating_sub(1); + } } - } else if app_state.ui.show_intro { - intro_state.next_option(); - } else if app_state.ui.show_admin { - admin_state.next(); + Page::Intro(state) => state.previous_option(), + Page::Admin(state) => state.previous(), + _ => {} } } -pub fn next_option(app_state: &mut AppState, intro_state: &mut IntroState) { - if app_state.ui.show_intro { - intro_state.next_option(); - } else { - // Get option count from state instead of parameter - let option_count = app_state.profile_tree.profiles.len(); - app_state.focused_button_index = (app_state.focused_button_index + 1) % option_count; +pub fn move_down(app_state: &mut AppState, router: &mut Router) { + match &mut router.current { + Page::Login(_) | Page::Register(_) if app_state.ui.focus_outside_canvas => { + let num_general_elements = 2; + if app_state.focused_button_index < num_general_elements - 1 { + app_state.focused_button_index += 1; + } + } + Page::Intro(state) => state.next_option(), + Page::Admin(state) => state.next(), + _ => {} } } -pub fn previous_option(app_state: &mut AppState, intro_state: &mut IntroState) { - if app_state.ui.show_intro { - intro_state.previous_option(); - } else { - let option_count = app_state.profile_tree.profiles.len(); - app_state.focused_button_index = if app_state.focused_button_index == 0 { - option_count.saturating_sub(1) - } else { - app_state.focused_button_index - 1 - }; +pub fn next_option(app_state: &mut AppState, router: &mut Router) { + match &mut router.current { + Page::Intro(state) => state.next_option(), + Page::Admin(_) => { + let option_count = app_state.profile_tree.profiles.len(); + if option_count > 0 { + app_state.focused_button_index = + (app_state.focused_button_index + 1) % option_count; + } + } + _ => {} + } +} + +pub fn previous_option(app_state: &mut AppState, router: &mut Router) { + match &mut router.current { + Page::Intro(state) => state.previous_option(), + Page::Admin(_) => { + let option_count = app_state.profile_tree.profiles.len(); + if option_count > 0 { + app_state.focused_button_index = if app_state.focused_button_index == 0 { + option_count.saturating_sub(1) + } else { + app_state.focused_button_index - 1 + }; + } + } + _ => {} } } @@ -164,7 +177,7 @@ pub fn prev_field(form_state: &mut FormState) { pub fn handle_enter_command_mode( command_mode: &mut bool, command_input: &mut String, - command_message: &mut String + command_message: &mut String, ) { *command_mode = true; command_input.clear(); diff --git a/client/src/pages/mod.rs b/client/src/pages/mod.rs new file mode 100644 index 0000000..9ac81f6 --- /dev/null +++ b/client/src/pages/mod.rs @@ -0,0 +1,3 @@ +// src/pages/mod.rs + +pub mod routing; diff --git a/client/src/pages/routing/mod.rs b/client/src/pages/routing/mod.rs new file mode 100644 index 0000000..44efd00 --- /dev/null +++ b/client/src/pages/routing/mod.rs @@ -0,0 +1,5 @@ +// src/pages/routing/mod.rs + +pub mod router; + +pub use router::{Page, Router}; diff --git a/client/src/pages/routing/router.rs b/client/src/pages/routing/router.rs new file mode 100644 index 0000000..2d52b8e --- /dev/null +++ b/client/src/pages/routing/router.rs @@ -0,0 +1,36 @@ +// src/pages/routing/router.rs +use crate::state::pages::{ + admin::AdminState, + auth::{AuthState, LoginState, RegisterState}, + form::FormState, + intro::IntroState, + add_logic::AddLogicState, + add_table::AddTableState, +}; + +#[derive(Debug)] +pub enum Page { + Intro(IntroState), + Login(LoginState), + Register(RegisterState), + Admin(AdminState), + AddLogic(AddLogicState), + AddTable(AddTableState), + Form(FormState), +} + +pub struct Router { + pub current: Page, +} + +impl Router { + pub fn new() -> Self { + Self { + current: Page::Intro(IntroState::default()), + } + } + + pub fn navigate(&mut self, page: Page) { + self.current = page; + } +} diff --git a/client/src/state/pages/auth.rs b/client/src/state/pages/auth.rs index fe06f07..ab0c1f9 100644 --- a/client/src/state/pages/auth.rs +++ b/client/src/state/pages/auth.rs @@ -21,7 +21,7 @@ pub struct AuthState { } /// Represents the state of the Login form UI -#[derive(Clone)] +#[derive(Debug, Clone)] pub struct LoginState { pub username: String, pub password: String, @@ -49,7 +49,7 @@ impl Default for LoginState { } /// Represents the state of the Registration form UI -#[derive(Clone)] +#[derive(Debug, Clone)] pub struct RegisterState { pub username: String, pub email: String, diff --git a/client/src/state/pages/form.rs b/client/src/state/pages/form.rs index 50970f9..087251e 100644 --- a/client/src/state/pages/form.rs +++ b/client/src/state/pages/form.rs @@ -21,7 +21,7 @@ pub struct FieldDefinition { pub link_target_table: Option, } -#[derive(Clone)] +#[derive(Debug, Clone)] pub struct FormState { pub id: i64, pub profile_name: String, diff --git a/client/src/ui/handlers/render.rs b/client/src/ui/handlers/render.rs index 8e7fb98..675b96b 100644 --- a/client/src/ui/handlers/render.rs +++ b/client/src/ui/handlers/render.rs @@ -28,16 +28,11 @@ use ratatui::{ layout::{Constraint, Direction, Layout}, Frame, }; +use crate::pages::routing::{Router, Page}; -#[allow(clippy::too_many_arguments)] pub fn render_ui( f: &mut Frame, - form_state: &mut FormState, - auth_state: &mut AuthState, - login_state: &LoginState, - register_state: &RegisterState, - intro_state: &IntroState, - admin_state: &mut AdminState, + router: &mut Router, buffer_state: &BufferState, theme: &Theme, is_event_handler_edit_mode: bool, @@ -78,101 +73,97 @@ pub fn render_ui( let main_content_area = root_chunks[chunk_idx]; chunk_idx += 1; - if app_state.ui.show_intro { - render_intro(f, intro_state, main_content_area, theme); - } else if app_state.ui.show_register { - render_register( + // ✅ Instead of checking app_state.ui.show_*, just match router.current + match &mut router.current { + Page::Intro(state) => render_intro(f, state, main_content_area, theme), + Page::Login(state) => render_login( f, main_content_area, theme, - register_state, + state, app_state, - register_state.current_field() < 4, // Now using CanvasState trait method - ); - } else if app_state.ui.show_add_table { - render_add_table( + state.current_field() < 2, + ), + Page::Register(state) => render_register( f, main_content_area, theme, + state, app_state, - &mut admin_state.add_table_state, - is_event_handler_edit_mode, - ); - } else if app_state.ui.show_add_logic { - render_add_logic( - f, - main_content_area, - theme, - app_state, - &mut admin_state.add_logic_state, - is_event_handler_edit_mode, - ); - } else if app_state.ui.show_login { - render_login( - f, - main_content_area, - theme, - login_state, - app_state, - login_state.current_field() < 2, // Now using CanvasState trait method - ); - } else if app_state.ui.show_admin { - crate::components::admin::admin_panel::render_admin_panel( + state.current_field() < 4, + ), + Page::Admin(state) => crate::components::admin::admin_panel::render_admin_panel( f, app_state, - auth_state, - admin_state, + &mut AuthState::default(), // TODO: later move AuthState into Router + state, main_content_area, theme, &app_state.profile_tree, &app_state.selected_profile, - ); - } else if app_state.ui.show_form { - let (sidebar_area, form_actual_area) = - calculate_sidebar_layout(app_state.ui.show_sidebar, main_content_area); - if let Some(sidebar_rect) = sidebar_area { - render_sidebar( + ), + Page::AddLogic(state) => render_add_logic( + f, + main_content_area, + theme, + app_state, + state, + is_event_handler_edit_mode, + ), + Page::AddTable(state) => render_add_table( + f, + main_content_area, + theme, + app_state, + state, + is_event_handler_edit_mode, + ), + Page::Form(state) => { + let (sidebar_area, form_actual_area) = + calculate_sidebar_layout(app_state.ui.show_sidebar, main_content_area); + if let Some(sidebar_rect) = sidebar_area { + render_sidebar( + f, + sidebar_rect, + theme, + &app_state.profile_tree, + &app_state.selected_profile, + ); + } + let available_width = form_actual_area.width; + let form_render_area = if available_width >= 80 { + Layout::default() + .direction(Direction::Horizontal) + .constraints([Constraint::Min(0), Constraint::Length(80), Constraint::Min(0)]) + .split(form_actual_area)[1] + } else { + Layout::default() + .direction(Direction::Horizontal) + .constraints([ + Constraint::Min(0), + Constraint::Length(available_width), + Constraint::Min(0), + ]) + .split(form_actual_area)[1] + }; + + render_form( f, - sidebar_rect, + form_render_area, + app_state, + state, + app_state.current_view_table_name.as_deref().unwrap_or(""), theme, - &app_state.profile_tree, - &app_state.selected_profile, + state.total_count, + state.current_position, ); } - let available_width = form_actual_area.width; - let form_render_area = if available_width >= 80 { - Layout::default() - .direction(Direction::Horizontal) - .constraints([Constraint::Min(0), Constraint::Length(80), Constraint::Min(0)]) - .split(form_actual_area)[1] - } else { - Layout::default() - .direction(Direction::Horizontal) - .constraints([ - Constraint::Min(0), - Constraint::Length(available_width), - Constraint::Min(0), - ]) - .split(form_actual_area)[1] - }; - - render_form( - f, - form_render_area, - app_state, - form_state, - app_state.current_view_table_name.as_deref().unwrap_or(""), - theme, - form_state.total_count, - form_state.current_position, - ); } if let Some(area) = buffer_list_area { render_buffer_list(f, area, theme, buffer_state, app_state); } - // This block now correctly handles drawing popups over any view. if app_state.ui.show_search_palette { if let Some(search_state) = &app_state.search_state { render_search_palette(f, f.area(), theme, search_state); diff --git a/client/src/ui/handlers/ui.rs b/client/src/ui/handlers/ui.rs index 8300cb5..72c9b7e 100644 --- a/client/src/ui/handlers/ui.rs +++ b/client/src/ui/handlers/ui.rs @@ -15,6 +15,7 @@ use crate::state::pages::auth::RegisterState; use crate::state::pages::admin::AdminState; use crate::state::pages::admin::AdminFocus; use crate::state::pages::intro::IntroState; +use crate::pages::routing::{Router, Page}; use crate::buffer::state::BufferState; use crate::buffer::state::AppView; use crate::state::app::state::AppState; @@ -68,6 +69,7 @@ pub async fn run_ui() -> Result<()> { let mut register_state = RegisterState::default(); let mut intro_state = IntroState::default(); let mut admin_state = AdminState::default(); + let mut router = Router::new(); let mut buffer_state = BufferState::default(); let mut app_state = AppState::new().context("Failed to create initial app state")?; @@ -341,17 +343,10 @@ pub async fn run_ui() -> Result<()> { } if let Some(active_view) = buffer_state.get_active_view() { - app_state.ui.show_intro = false; - app_state.ui.show_login = false; - app_state.ui.show_register = false; - app_state.ui.show_admin = false; - app_state.ui.show_add_table = false; - app_state.ui.show_add_logic = false; - app_state.ui.show_form = false; match active_view { - AppView::Intro => app_state.ui.show_intro = true, - AppView::Login => app_state.ui.show_login = true, - AppView::Register => app_state.ui.show_register = true, + AppView::Intro => router.navigate(Page::Intro(intro_state.clone())), + AppView::Login => router.navigate(Page::Login(login_state.clone())), + AppView::Register => router.navigate(Page::Register(register_state.clone())), AppView::Admin => { info!("Active view is Admin, refreshing profile tree..."); match grpc_client.get_profile_tree().await { @@ -360,36 +355,42 @@ pub async fn run_ui() -> Result<()> { } Err(e) => { error!("Failed to refresh profile tree for Admin panel: {}", e); - event_handler.command_message = format!("Error refreshing admin data: {}", e); + event_handler.command_message = + format!("Error refreshing admin data: {}", e); } } - app_state.ui.show_admin = true; let profile_names = app_state.profile_tree.profiles.iter() .map(|p| p.name.clone()) .collect(); admin_state.set_profiles(profile_names); - if admin_state.current_focus == AdminFocus::default() || - !matches!(admin_state.current_focus, - AdminFocus::InsideProfilesList | - AdminFocus::Tables | AdminFocus::InsideTablesList | - AdminFocus::Button1 | AdminFocus::Button2 | AdminFocus::Button3) { + if admin_state.current_focus == AdminFocus::default() + || !matches!(admin_state.current_focus, + AdminFocus::InsideProfilesList | + AdminFocus::Tables | AdminFocus::InsideTablesList | + AdminFocus::Button1 | AdminFocus::Button2 | AdminFocus::Button3) + { admin_state.current_focus = AdminFocus::ProfilesPane; } - if admin_state.profile_list_state.selected().is_none() && !app_state.profile_tree.profiles.is_empty() { + if admin_state.profile_list_state.selected().is_none() + && !app_state.profile_tree.profiles.is_empty() + { admin_state.profile_list_state.select(Some(0)); } + + router.navigate(Page::Admin(admin_state.clone())); + } + AppView::AddTable => router.navigate(Page::AddTable(admin_state.add_table_state.clone())), + AppView::AddLogic => router.navigate(Page::AddLogic(admin_state.add_logic_state.clone())), + AppView::Form => { + if let Some(form_state) = app_state.form_state().cloned() { + router.navigate(Page::Form(form_state)); + } } - AppView::AddTable => app_state.ui.show_add_table = true, - AppView::AddLogic => app_state.ui.show_add_logic = true, - AppView::Form => app_state.ui.show_form = true, AppView::Scratch => {} } } - // Continue with the rest of the function... - // (The rest remains the same, but now CanvasState trait methods are available) - if app_state.ui.show_form { let current_view_profile = app_state.current_view_profile_name.clone(); let current_view_table = app_state.current_view_table_name.clone(); @@ -654,12 +655,7 @@ pub async fn run_ui() -> Result<()> { let mut temp_form_state = form_state_clone.clone(); render_ui( f, - &mut temp_form_state, - &mut auth_state, - &login_state, - ®ister_state, - &intro_state, - &mut admin_state, + &mut router, &buffer_state, &theme, event_handler.is_edit_mode,