router implementation

This commit is contained in:
Priec
2025-08-22 22:19:59 +02:00
parent 6833ac5fad
commit 957f5bf9f0
9 changed files with 225 additions and 180 deletions

View File

@@ -12,6 +12,7 @@ pub mod buffer;
pub mod sidebar; pub mod sidebar;
pub mod search; pub mod search;
pub mod bottom_panel; pub mod bottom_panel;
pub mod pages;
pub use ui::run_ui; pub use ui::run_ui;

View File

@@ -4,10 +4,7 @@ use crossterm::event::KeyEvent;
use crate::config::binds::config::Config; use crate::config::binds::config::Config;
use crate::state::app::state::AppState; use crate::state::app::state::AppState;
use crate::state::pages::form::FormState; use crate::state::pages::form::FormState;
use crate::state::pages::auth::LoginState; use crate::pages::routing::{Router, Page};
use crate::state::pages::auth::RegisterState;
use crate::state::pages::intro::IntroState;
use crate::state::pages::admin::AdminState;
use crate::ui::handlers::context::UiContext; use crate::ui::handlers::context::UiContext;
use crate::modes::handlers::event::EventOutcome; use crate::modes::handlers::event::EventOutcome;
use crate::modes::general::command_navigation::{handle_command_navigation_event, NavigationState}; use crate::modes::general::command_navigation::{handle_command_navigation_event, NavigationState};
@@ -18,10 +15,7 @@ pub async fn handle_navigation_event(
key: KeyEvent, key: KeyEvent,
config: &Config, config: &Config,
app_state: &mut AppState, app_state: &mut AppState,
login_state: &mut LoginState, router: &mut Router,
register_state: &mut RegisterState,
intro_state: &mut IntroState,
admin_state: &mut AdminState,
command_mode: &mut bool, command_mode: &mut bool,
command_input: &mut String, command_input: &mut String,
command_message: &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) { if let Some(action) = config.get_general_action(key.code, key.modifiers) {
match action { match action {
"move_up" => { "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())); return Ok(EventOutcome::Ok(String::new()));
} }
"move_down" => { "move_down" => {
move_down(app_state, intro_state, admin_state); move_down(app_state, router);
return Ok(EventOutcome::Ok(String::new())); return Ok(EventOutcome::Ok(String::new()));
} }
"next_option" => { "next_option" => {
next_option(app_state, intro_state); next_option(app_state, router);
return Ok(EventOutcome::Ok(String::new())); return Ok(EventOutcome::Ok(String::new()));
} }
"previous_option" => { "previous_option" => {
previous_option(app_state, intro_state); previous_option(app_state, router);
return Ok(EventOutcome::Ok(String::new())); return Ok(EventOutcome::Ok(String::new()));
} }
"next_field" => { "next_field" => {
@@ -67,18 +61,21 @@ pub async fn handle_navigation_event(
return Ok(EventOutcome::Ok(String::new())); return Ok(EventOutcome::Ok(String::new()));
} }
"select" => { "select" => {
let (context, index) = if app_state.ui.show_intro { let (context, index) = match &router.current {
(UiContext::Intro, intro_state.selected_option) Page::Intro(state) => (UiContext::Intro, state.selected_option),
} else if app_state.ui.show_login && app_state.ui.focus_outside_canvas { Page::Login(_) if app_state.ui.focus_outside_canvas => {
(UiContext::Login, app_state.focused_button_index) (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) Page::Register(_) if app_state.ui.focus_outside_canvas => {
} else if app_state.ui.show_admin { (UiContext::Register, app_state.focused_button_index)
(UiContext::Admin, admin_state.get_selected_index().unwrap_or(0)) }
} else if app_state.ui.dialog.dialog_show { Page::Admin(state) => {
(UiContext::Dialog, app_state.ui.dialog.dialog_active_button_index) (UiContext::Admin, state.get_selected_index().unwrap_or(0))
} else { }
return Ok(EventOutcome::Ok("Select (No Action)".to_string())); _ 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 }); return Ok(EventOutcome::ButtonSelected { context, index });
} }
@@ -88,60 +85,76 @@ pub async fn handle_navigation_event(
Ok(EventOutcome::Ok(String::new())) 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) { pub fn move_up(app_state: &mut AppState, router: &mut Router) {
if app_state.ui.focus_outside_canvas && app_state.ui.show_login || app_state.ui.show_register{ match &mut router.current {
if app_state.focused_button_index == 0 { Page::Login(state) if app_state.ui.focus_outside_canvas => {
app_state.ui.focus_outside_canvas = false; if app_state.focused_button_index == 0 {
if app_state.ui.show_login { app_state.ui.focus_outside_canvas = false;
let last_field_index = login_state.field_count().saturating_sub(1); let last_field_index = state.field_count().saturating_sub(1);
login_state.set_current_field(last_field_index); state.set_current_field(last_field_index);
} else { } else {
let last_field_index = register_state.field_count().saturating_sub(1); app_state.focused_button_index =
register_state.set_current_field(last_field_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 { Page::Register(state) if app_state.ui.focus_outside_canvas => {
intro_state.previous_option(); if app_state.focused_button_index == 0 {
} else if app_state.ui.show_admin { app_state.ui.focus_outside_canvas = false;
admin_state.previous(); let last_field_index = state.field_count().saturating_sub(1);
} state.set_current_field(last_field_index);
} } else {
app_state.focused_button_index =
pub fn move_down(app_state: &mut AppState, intro_state: &mut IntroState, admin_state: &mut AdminState) { app_state.focused_button_index.saturating_sub(1);
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;
} }
} else if app_state.ui.show_intro { Page::Intro(state) => state.previous_option(),
intro_state.next_option(); Page::Admin(state) => state.previous(),
} else if app_state.ui.show_admin { _ => {}
admin_state.next();
} }
} }
pub fn next_option(app_state: &mut AppState, intro_state: &mut IntroState) { pub fn move_down(app_state: &mut AppState, router: &mut Router) {
if app_state.ui.show_intro { match &mut router.current {
intro_state.next_option(); Page::Login(_) | Page::Register(_) if app_state.ui.focus_outside_canvas => {
} else { let num_general_elements = 2;
// Get option count from state instead of parameter if app_state.focused_button_index < num_general_elements - 1 {
let option_count = app_state.profile_tree.profiles.len(); app_state.focused_button_index += 1;
app_state.focused_button_index = (app_state.focused_button_index + 1) % option_count; }
}
Page::Intro(state) => state.next_option(),
Page::Admin(state) => state.next(),
_ => {}
} }
} }
pub fn previous_option(app_state: &mut AppState, intro_state: &mut IntroState) { pub fn next_option(app_state: &mut AppState, router: &mut Router) {
if app_state.ui.show_intro { match &mut router.current {
intro_state.previous_option(); Page::Intro(state) => state.next_option(),
} else { Page::Admin(_) => {
let option_count = app_state.profile_tree.profiles.len(); let option_count = app_state.profile_tree.profiles.len();
app_state.focused_button_index = if app_state.focused_button_index == 0 { if option_count > 0 {
option_count.saturating_sub(1) app_state.focused_button_index =
} else { (app_state.focused_button_index + 1) % option_count;
app_state.focused_button_index - 1 }
}; }
_ => {}
}
}
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( pub fn handle_enter_command_mode(
command_mode: &mut bool, command_mode: &mut bool,
command_input: &mut String, command_input: &mut String,
command_message: &mut String command_message: &mut String,
) { ) {
*command_mode = true; *command_mode = true;
command_input.clear(); command_input.clear();

3
client/src/pages/mod.rs Normal file
View File

@@ -0,0 +1,3 @@
// src/pages/mod.rs
pub mod routing;

View File

@@ -0,0 +1,5 @@
// src/pages/routing/mod.rs
pub mod router;
pub use router::{Page, Router};

View File

@@ -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;
}
}

View File

@@ -21,7 +21,7 @@ pub struct AuthState {
} }
/// Represents the state of the Login form UI /// Represents the state of the Login form UI
#[derive(Clone)] #[derive(Debug, Clone)]
pub struct LoginState { pub struct LoginState {
pub username: String, pub username: String,
pub password: String, pub password: String,
@@ -49,7 +49,7 @@ impl Default for LoginState {
} }
/// Represents the state of the Registration form UI /// Represents the state of the Registration form UI
#[derive(Clone)] #[derive(Debug, Clone)]
pub struct RegisterState { pub struct RegisterState {
pub username: String, pub username: String,
pub email: String, pub email: String,

View File

@@ -21,7 +21,7 @@ pub struct FieldDefinition {
pub link_target_table: Option<String>, pub link_target_table: Option<String>,
} }
#[derive(Clone)] #[derive(Debug, Clone)]
pub struct FormState { pub struct FormState {
pub id: i64, pub id: i64,
pub profile_name: String, pub profile_name: String,

View File

@@ -28,16 +28,11 @@ use ratatui::{
layout::{Constraint, Direction, Layout}, layout::{Constraint, Direction, Layout},
Frame, Frame,
}; };
use crate::pages::routing::{Router, Page};
#[allow(clippy::too_many_arguments)]
pub fn render_ui( pub fn render_ui(
f: &mut Frame, f: &mut Frame,
form_state: &mut FormState, router: &mut Router,
auth_state: &mut AuthState,
login_state: &LoginState,
register_state: &RegisterState,
intro_state: &IntroState,
admin_state: &mut AdminState,
buffer_state: &BufferState, buffer_state: &BufferState,
theme: &Theme, theme: &Theme,
is_event_handler_edit_mode: bool, is_event_handler_edit_mode: bool,
@@ -78,101 +73,97 @@ 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;
if app_state.ui.show_intro { // ✅ Instead of checking app_state.ui.show_*, just match router.current
render_intro(f, intro_state, main_content_area, theme); match &mut router.current {
} else if app_state.ui.show_register { Page::Intro(state) => render_intro(f, state, main_content_area, theme),
render_register( Page::Login(state) => render_login(
f, f,
main_content_area, main_content_area,
theme, theme,
register_state, state,
app_state, app_state,
register_state.current_field() < 4, // Now using CanvasState trait method state.current_field() < 2,
); ),
} else if app_state.ui.show_add_table { Page::Register(state) => render_register(
render_add_table(
f, f,
main_content_area, main_content_area,
theme, theme,
state,
app_state, app_state,
&mut admin_state.add_table_state, state.current_field() < 4,
is_event_handler_edit_mode, ),
); Page::Admin(state) => crate::components::admin::admin_panel::render_admin_panel(
} 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(
f, f,
app_state, app_state,
auth_state, &mut AuthState::default(), // TODO: later move AuthState into Router
admin_state, state,
main_content_area, main_content_area,
theme, theme,
&app_state.profile_tree, &app_state.profile_tree,
&app_state.selected_profile, &app_state.selected_profile,
); ),
} else if app_state.ui.show_form { Page::AddLogic(state) => render_add_logic(
let (sidebar_area, form_actual_area) = f,
calculate_sidebar_layout(app_state.ui.show_sidebar, main_content_area); main_content_area,
if let Some(sidebar_rect) = sidebar_area { theme,
render_sidebar( 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, f,
sidebar_rect, form_render_area,
app_state,
state,
app_state.current_view_table_name.as_deref().unwrap_or(""),
theme, theme,
&app_state.profile_tree, state.total_count,
&app_state.selected_profile, 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 { 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);
} }
// This block now correctly handles drawing popups over any view.
if app_state.ui.show_search_palette { if app_state.ui.show_search_palette {
if let Some(search_state) = &app_state.search_state { if let Some(search_state) = &app_state.search_state {
render_search_palette(f, f.area(), theme, search_state); render_search_palette(f, f.area(), theme, search_state);

View File

@@ -15,6 +15,7 @@ use crate::state::pages::auth::RegisterState;
use crate::state::pages::admin::AdminState; use crate::state::pages::admin::AdminState;
use crate::state::pages::admin::AdminFocus; use crate::state::pages::admin::AdminFocus;
use crate::state::pages::intro::IntroState; use crate::state::pages::intro::IntroState;
use crate::pages::routing::{Router, Page};
use crate::buffer::state::BufferState; use crate::buffer::state::BufferState;
use crate::buffer::state::AppView; use crate::buffer::state::AppView;
use crate::state::app::state::AppState; use crate::state::app::state::AppState;
@@ -68,6 +69,7 @@ pub async fn run_ui() -> Result<()> {
let mut register_state = RegisterState::default(); let mut register_state = RegisterState::default();
let mut intro_state = IntroState::default(); let mut intro_state = IntroState::default();
let mut admin_state = AdminState::default(); let mut admin_state = AdminState::default();
let mut router = Router::new();
let mut buffer_state = BufferState::default(); let mut buffer_state = BufferState::default();
let mut app_state = AppState::new().context("Failed to create initial app state")?; 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() { 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 { match active_view {
AppView::Intro => app_state.ui.show_intro = true, AppView::Intro => router.navigate(Page::Intro(intro_state.clone())),
AppView::Login => app_state.ui.show_login = true, AppView::Login => router.navigate(Page::Login(login_state.clone())),
AppView::Register => app_state.ui.show_register = true, AppView::Register => router.navigate(Page::Register(register_state.clone())),
AppView::Admin => { AppView::Admin => {
info!("Active view is Admin, refreshing profile tree..."); info!("Active view is Admin, refreshing profile tree...");
match grpc_client.get_profile_tree().await { match grpc_client.get_profile_tree().await {
@@ -360,36 +355,42 @@ pub async fn run_ui() -> Result<()> {
} }
Err(e) => { Err(e) => {
error!("Failed to refresh profile tree for Admin panel: {}", 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() let profile_names = app_state.profile_tree.profiles.iter()
.map(|p| p.name.clone()) .map(|p| p.name.clone())
.collect(); .collect();
admin_state.set_profiles(profile_names); admin_state.set_profiles(profile_names);
if admin_state.current_focus == AdminFocus::default() || if admin_state.current_focus == AdminFocus::default()
!matches!(admin_state.current_focus, || !matches!(admin_state.current_focus,
AdminFocus::InsideProfilesList | AdminFocus::InsideProfilesList |
AdminFocus::Tables | AdminFocus::InsideTablesList | AdminFocus::Tables | AdminFocus::InsideTablesList |
AdminFocus::Button1 | AdminFocus::Button2 | AdminFocus::Button3) { AdminFocus::Button1 | AdminFocus::Button2 | AdminFocus::Button3)
{
admin_state.current_focus = AdminFocus::ProfilesPane; 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)); 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 => {} 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 { if app_state.ui.show_form {
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();
@@ -654,12 +655,7 @@ pub async fn run_ui() -> Result<()> {
let mut temp_form_state = form_state_clone.clone(); let mut temp_form_state = form_state_clone.clone();
render_ui( render_ui(
f, f,
&mut temp_form_state, &mut router,
&mut auth_state,
&login_state,
&register_state,
&intro_state,
&mut admin_state,
&buffer_state, &buffer_state,
&theme, &theme,
event_handler.is_edit_mode, event_handler.is_edit_mode,