Compare commits

...

7 Commits

Author SHA1 Message Date
filipriec
88a4b2d69c intro is now separated 2025-08-23 21:58:29 +02:00
filipriec
e6072d25c5 register is now separated also 2025-08-23 21:47:18 +02:00
filipriec
fc2b65601e login function moved 2025-08-23 21:05:02 +02:00
filipriec
597bdde7e1 login moved to the pages 2025-08-23 20:58:12 +02:00
filipriec
f56092e86c login page now in a separate dir 2025-08-23 19:48:23 +02:00
filipriec
d5cfe59f47 dialog refactor comment, dialog crate finished for now 2025-08-23 13:36:46 +02:00
filipriec
f281eaa662 dialog is a feature 2025-08-23 13:29:28 +02:00
36 changed files with 540 additions and 489 deletions

View File

@@ -10,7 +10,8 @@ use ratatui::{
widgets::{Block, BorderType, Borders, Paragraph},
Frame,
};
use crate::components::common::{dialog, autocomplete}; // Added autocomplete
use crate::components::common::autocomplete;
use crate::dialog;
use crate::config::binds::config::EditorKeybindingMode;
pub fn render_add_logic(

View File

@@ -10,7 +10,7 @@ use ratatui::{
widgets::{Block, BorderType, Borders, Cell, Paragraph, Row, Table},
Frame,
};
use crate::components::common::dialog;
use crate::dialog;
/// Renders the Add New Table page layout, structuring the display of table information,
/// input fields, and action buttons. Adapts layout based on terminal width.

View File

@@ -1,6 +0,0 @@
// src/components/form.rs
pub mod login;
pub mod register;
pub use login::*;
pub use register::*;

View File

@@ -2,10 +2,8 @@
pub mod text_editor;
pub mod background;
pub mod dialog;
pub mod autocomplete;
pub use text_editor::*;
pub use background::*;
pub use dialog::*;
pub use autocomplete::*;

View File

@@ -1,4 +0,0 @@
// src/components/intro.rs
pub mod intro;
pub use intro::*;

View File

@@ -1,12 +1,9 @@
// src/components/mod.rs
pub mod intro;
pub mod admin;
pub mod common;
pub mod auth;
pub mod utils;
pub use intro::*;
pub use admin::*;
pub use common::*;
pub use auth::*;
pub use utils::*;

View File

@@ -0,0 +1,85 @@
// src/dialog/functions.rs
use crate::dialog::DialogState;
use crate::state::app::state::AppState;
use crate::ui::handlers::context::DialogPurpose;
impl AppState {
pub fn show_dialog(
&mut self,
title: &str,
message: &str,
buttons: Vec<String>,
purpose: DialogPurpose,
) {
self.ui.dialog.dialog_title = title.to_string();
self.ui.dialog.dialog_message = message.to_string();
self.ui.dialog.dialog_buttons = buttons;
self.ui.dialog.dialog_active_button_index = 0;
self.ui.dialog.purpose = Some(purpose);
self.ui.dialog.is_loading = false;
self.ui.dialog.dialog_show = true;
self.ui.focus_outside_canvas = true;
}
pub fn show_loading_dialog(&mut self, title: &str, message: &str) {
self.ui.dialog.dialog_title = title.to_string();
self.ui.dialog.dialog_message = message.to_string();
self.ui.dialog.dialog_buttons.clear();
self.ui.dialog.dialog_active_button_index = 0;
self.ui.dialog.purpose = None;
self.ui.dialog.is_loading = true;
self.ui.dialog.dialog_show = true;
self.ui.focus_outside_canvas = true;
}
pub fn update_dialog_content(
&mut self,
message: &str,
buttons: Vec<String>,
purpose: DialogPurpose,
) {
if self.ui.dialog.dialog_show {
self.ui.dialog.dialog_message = message.to_string();
self.ui.dialog.dialog_buttons = buttons;
self.ui.dialog.dialog_active_button_index = 0;
self.ui.dialog.purpose = Some(purpose);
self.ui.dialog.is_loading = false;
}
}
pub fn hide_dialog(&mut self) {
self.ui.dialog.dialog_show = false;
self.ui.dialog.dialog_title.clear();
self.ui.dialog.dialog_message.clear();
self.ui.dialog.dialog_buttons.clear();
self.ui.dialog.dialog_active_button_index = 0;
self.ui.dialog.purpose = None;
self.ui.focus_outside_canvas = false;
self.ui.dialog.is_loading = false;
}
pub fn next_dialog_button(&mut self) {
if !self.ui.dialog.dialog_buttons.is_empty() {
let next_index = (self.ui.dialog.dialog_active_button_index + 1)
% self.ui.dialog.dialog_buttons.len();
self.ui.dialog.dialog_active_button_index = next_index;
}
}
pub fn previous_dialog_button(&mut self) {
if !self.ui.dialog.dialog_buttons.is_empty() {
let len = self.ui.dialog.dialog_buttons.len();
let prev_index =
(self.ui.dialog.dialog_active_button_index + len - 1) % len;
self.ui.dialog.dialog_active_button_index = prev_index;
}
}
pub fn get_active_dialog_button_label(&self) -> Option<&str> {
self.ui.dialog
.dialog_buttons
.get(self.ui.dialog.dialog_active_button_index)
.map(|s| s.as_str())
}
}

View File

@@ -1,4 +1,18 @@
// src/modes/general/dialog.rs
// src/dialog/logic.rs
// TODO(dialog-refactor):
// Currently this module (`handle_dialog_event`) contains page-specific logic
// (e.g. Login, Register, Admin, SaveTable). This couples the dialog crate
// to application pages and business logic.
//
// Refactor plan:
// 1. Keep dialog generic: only handle navigation (next/prev/select) and return
// a `DialogResult` (Dismissed | Selected { purpose, index }).
// 2. Move all page-specific actions (e.g. login::back_to_main, register::back_to_login,
// handle_delete_selected_columns, buffer_state.update_history) into the
// respective page or event handler (e.g. modes/handlers/event.rs).
// 3. Dialog crate should only provide state, rendering, and generic navigation.
// Pages decide what to do when a dialog button is pressed.
use crossterm::event::{Event, KeyCode};
use crate::config::binds::config::Config;
@@ -7,7 +21,8 @@ use crate::state::app::state::AppState;
use crate::buffer::AppView;
use crate::buffer::state::BufferState;
use crate::modes::handlers::event::EventOutcome;
use crate::tui::functions::common::{login, register};
use crate::pages::register;
use crate::pages::login;
use crate::tui::functions::common::add_table::handle_delete_selected_columns;
use crate::pages::routing::{Router, Page};
use anyhow::Result;

10
client/src/dialog/mod.rs Normal file
View File

@@ -0,0 +1,10 @@
// src/dialog/mod.rs
pub mod ui;
pub mod logic;
pub mod state;
pub mod functions;
pub use ui::render_dialog;
pub use logic::handle_dialog_event;
pub use state::DialogState;

View File

@@ -0,0 +1,26 @@
// src/dialog/state.rs
use crate::ui::handlers::context::DialogPurpose;
pub struct DialogState {
pub dialog_show: bool,
pub dialog_title: String,
pub dialog_message: String,
pub dialog_buttons: Vec<String>,
pub dialog_active_button_index: usize,
pub purpose: Option<DialogPurpose>,
pub is_loading: bool,
}
impl Default for DialogState {
fn default() -> Self {
Self {
dialog_show: false,
dialog_title: String::new(),
dialog_message: String::new(),
dialog_buttons: Vec::new(),
dialog_active_button_index: 0,
purpose: None,
is_loading: false,
}
}
}

View File

@@ -1,3 +1,5 @@
// src/dialog/ui.rs
use crate::config::colors::themes::Theme;
use ratatui::{
layout::{Constraint, Direction, Layout, Margin, Rect},

View File

@@ -10,6 +10,7 @@ pub mod services;
pub mod utils;
pub mod buffer;
pub mod sidebar;
pub mod dialog;
pub mod search;
pub mod bottom_panel;
pub mod pages;

View File

@@ -1,4 +1,3 @@
// src/client/modes/general.rs
pub mod navigation;
pub mod dialog;
pub mod command_navigation;

View File

@@ -13,7 +13,7 @@ use crate::modes::general::command_navigation::{
};
use crate::modes::{
common::{command_mode, commands::CommandHandler},
general::{dialog, navigation},
general::navigation,
handlers::mode_manager::{AppMode, ModeManager},
};
use crate::services::auth::AuthClient;
@@ -25,20 +25,25 @@ use crate::state::{
},
pages::{
admin::AdminState,
auth::{AuthState, LoginState, RegisterState},
intro::IntroState,
auth::AuthState,
},
};
use crate::tui::common::{register, login};
use crate::pages::login::LoginState;
use crate::pages::register::RegisterState;
use crate::pages::intro::IntroState;
use crate::pages::login;
use crate::pages::register;
use crate::pages::intro;
use crate::pages::login::logic::LoginResult;
use crate::pages::register::RegisterResult;
use crate::pages::routing::{Router, Page};
use crate::dialog;
use crate::pages::forms::FormState;
use crate::pages::forms::logic::{save, revert, SaveOutcome};
use crate::search::state::SearchState;
use crate::tui::functions::common::login::LoginResult;
use crate::tui::functions::common::register::RegisterResult;
use crate::tui::{
terminal::core::TerminalCore,
{admin, intro},
admin,
};
use crate::ui::handlers::context::UiContext;
use canvas::KeyEventOutcome;
@@ -807,7 +812,7 @@ impl EventHandler {
match action {
"save" => {
if let Page::Login(login_state) = &mut router.current {
let message = crate::tui::functions::common::login::save(
let message = login::logic::save(
auth_state,
login_state,
&mut self.auth_client,
@@ -844,7 +849,7 @@ impl EventHandler {
}
"save_and_quit" => {
let message = if let Page::Login(login_state) = &mut router.current {
crate::tui::functions::common::login::save(
login::logic::save(
auth_state,
login_state,
&mut self.auth_client,
@@ -873,10 +878,9 @@ impl EventHandler {
}
"revert" => {
let message = if let Page::Login(login_state) = &mut router.current {
crate::tui::functions::common::login::revert(login_state, app_state)
.await
login::logic::revert(login_state, app_state).await
} else if let Page::Register(register_state) = &mut router.current {
crate::tui::functions::common::register::revert(
register::revert(
register_state,
app_state,
)

View File

@@ -0,0 +1,9 @@
// src/pages/intro/mod.rs
pub mod state;
pub mod ui;
pub mod logic;
pub use state::*;
pub use ui::render_intro;
pub use logic::*;

View File

@@ -1,4 +1,4 @@
// src/components/intro/intro.rs
// src/pages/intro/ui.rs
use ratatui::{
layout::{Alignment, Constraint, Direction, Layout, Rect},
style::Style,
@@ -8,7 +8,7 @@ use ratatui::{
Frame,
};
use crate::config::colors::themes::Theme;
use crate::state::pages::intro::IntroState;
use crate::pages::intro::IntroState;
pub fn render_intro(f: &mut Frame, intro_state: &IntroState, area: Rect, theme: &Theme) {
let block = Block::default()

View File

@@ -2,16 +2,17 @@
use crate::services::auth::AuthClient;
use crate::state::pages::auth::AuthState;
use crate::state::pages::auth::LoginState;
use crate::state::app::state::AppState;
use crate::buffer::state::{AppView, BufferState};
use crate::config::storage::storage::{StoredAuthData, save_auth_data};
use crate::ui::handlers::context::DialogPurpose;
use common::proto::komp_ac::auth::LoginResponse;
use crate::pages::login::LoginState;
use anyhow::{Context, Result};
use tokio::spawn;
use tokio::sync::mpsc;
use tracing::{info, error};
use anyhow::anyhow;
#[derive(Debug)]
pub enum LoginResult {
@@ -237,3 +238,15 @@ pub fn handle_login_result(
login_state.current_cursor_pos = 0;
true // Request redraw as dialog content changed
}
pub async fn handle_action(action: &str,) -> Result<String> {
match action {
"previous_entry" => {
Ok("Previous entry at tui/functions/login.rs not implemented".into())
}
"next_entry" => {
Ok("Next entry at tui/functions/login.rs not implemented".into())
}
_ => Err(anyhow!("Unknown login action: {}", action))
}
}

View File

@@ -0,0 +1,9 @@
// src/pages/login/mod.rs
pub mod state;
pub mod ui;
pub mod logic;
pub use state::*;
pub use ui::render_login;
pub use logic::*;

View File

@@ -0,0 +1,121 @@
// src/pages/login/state.rs
use canvas::{AppMode, DataProvider};
#[derive(Debug, Clone)]
pub struct LoginState {
pub username: String,
pub password: String,
pub error_message: Option<String>,
pub current_field: usize,
pub current_cursor_pos: usize,
pub has_unsaved_changes: bool,
pub login_request_pending: bool,
pub app_mode: AppMode,
}
impl Default for LoginState {
fn default() -> Self {
Self {
username: String::new(),
password: String::new(),
error_message: None,
current_field: 0,
current_cursor_pos: 0,
has_unsaved_changes: false,
login_request_pending: false,
app_mode: AppMode::Edit,
}
}
}
impl LoginState {
pub fn new() -> Self {
Self {
app_mode: AppMode::Edit,
..Default::default()
}
}
pub fn current_field(&self) -> usize {
self.current_field
}
pub fn current_cursor_pos(&self) -> usize {
self.current_cursor_pos
}
pub fn set_current_field(&mut self, index: usize) {
if index < 2 {
self.current_field = index;
}
}
pub fn set_current_cursor_pos(&mut self, pos: usize) {
self.current_cursor_pos = pos;
}
pub fn get_current_input(&self) -> &str {
match self.current_field {
0 => &self.username,
1 => &self.password,
_ => "",
}
}
pub fn get_current_input_mut(&mut self) -> &mut String {
match self.current_field {
0 => &mut self.username,
1 => &mut self.password,
_ => panic!("Invalid current_field index in LoginState"),
}
}
pub fn current_mode(&self) -> AppMode {
self.app_mode
}
pub fn has_unsaved_changes(&self) -> bool {
self.has_unsaved_changes
}
pub fn set_has_unsaved_changes(&mut self, changed: bool) {
self.has_unsaved_changes = changed;
}
}
// Implement DataProvider for LoginState
impl DataProvider for LoginState {
fn field_count(&self) -> usize {
2
}
fn field_name(&self, index: usize) -> &str {
match index {
0 => "Username/Email",
1 => "Password",
_ => "",
}
}
fn field_value(&self, index: usize) -> &str {
match index {
0 => &self.username,
1 => &self.password,
_ => "",
}
}
fn set_field_value(&mut self, index: usize, value: String) {
match index {
0 => self.username = value,
1 => self.password = value,
_ => {}
}
self.has_unsaved_changes = true;
}
fn supports_suggestions(&self, _field_index: usize) -> bool {
false // Login form doesn't support suggestions
}
}

View File

@@ -1,9 +1,7 @@
// src/components/auth/login.rs
// src/pages/login/ui.rs
use crate::{
config::colors::themes::Theme,
state::pages::auth::LoginState,
components::common::dialog,
state::app::state::AppState,
};
use ratatui::{
@@ -18,6 +16,8 @@ use canvas::{
render_suggestions_dropdown,
DefaultCanvasTheme,
};
use crate::pages::login::LoginState;
use crate::dialog;
pub fn render_login(
f: &mut Frame,

View File

@@ -1,4 +1,7 @@
// src/pages/mod.rs
pub mod routing;
pub mod intro;
pub mod login;
pub mod register;
pub mod forms;

View File

@@ -1,13 +1,13 @@
// src/tui/functions/common/register.rs
// src/pages/register/logic.rs
use crate::services::auth::AuthClient;
use crate::state::{
pages::auth::RegisterState,
app::state::AppState,
};
use crate::ui::handlers::context::DialogPurpose;
use crate::buffer::state::{AppView, BufferState};
use common::proto::komp_ac::auth::AuthResponse;
use crate::pages::register::RegisterState;
use anyhow::Context;
use tokio::spawn;
use tokio::sync::mpsc;

View File

@@ -0,0 +1,11 @@
// src/pages/register/mod.rs
// pub mod state;
pub mod ui;
pub mod state;
pub mod logic;
// pub use state::*;
pub use ui::render_register;
pub use logic::*;
pub use state::*;

View File

@@ -0,0 +1,184 @@
// src/pages/register/state.rs
use canvas::{DataProvider, AppMode};
use lazy_static::lazy_static;
lazy_static! {
pub static ref AVAILABLE_ROLES: Vec<String> = vec![
"admin".to_string(),
"moderator".to_string(),
"accountant".to_string(),
"viewer".to_string(),
];
}
/// Represents the state of the Registration form UI
#[derive(Debug, Clone)]
pub struct RegisterState {
pub username: String,
pub email: String,
pub password: String,
pub password_confirmation: String,
pub role: String,
pub error_message: Option<String>,
pub current_field: usize,
pub current_cursor_pos: usize,
pub has_unsaved_changes: bool,
pub app_mode: AppMode,
pub role_suggestions: Vec<String>,
pub role_suggestions_active: bool,
}
impl Default for RegisterState {
fn default() -> Self {
Self {
username: String::new(),
email: String::new(),
password: String::new(),
password_confirmation: String::new(),
role: String::new(),
error_message: None,
current_field: 0,
current_cursor_pos: 0,
has_unsaved_changes: false,
app_mode: AppMode::Edit,
role_suggestions: AVAILABLE_ROLES.clone(),
role_suggestions_active: false,
}
}
}
impl RegisterState {
pub fn new() -> Self {
Self {
app_mode: AppMode::Edit,
role_suggestions: AVAILABLE_ROLES.clone(),
role_suggestions_active: false,
..Default::default()
}
}
pub fn current_field(&self) -> usize {
self.current_field
}
pub fn current_cursor_pos(&self) -> usize {
self.current_cursor_pos
}
pub fn set_current_field(&mut self, index: usize) {
if index < 5 {
self.current_field = index;
if index == 4 {
self.activate_role_suggestions();
} else {
self.deactivate_role_suggestions();
}
}
}
pub fn set_current_cursor_pos(&mut self, pos: usize) {
self.current_cursor_pos = pos;
}
pub fn get_current_input(&self) -> &str {
match self.current_field {
0 => &self.username,
1 => &self.email,
2 => &self.password,
3 => &self.password_confirmation,
4 => &self.role,
_ => "",
}
}
pub fn get_current_input_mut(&mut self, index: usize) -> &mut String {
match index {
0 => &mut self.username,
1 => &mut self.email,
2 => &mut self.password,
3 => &mut self.password_confirmation,
4 => &mut self.role,
_ => panic!("Invalid current_field index in RegisterState"),
}
}
pub fn current_mode(&self) -> AppMode {
self.app_mode
}
pub fn activate_role_suggestions(&mut self) {
self.role_suggestions_active = true;
let current_input = self.role.to_lowercase();
self.role_suggestions = AVAILABLE_ROLES
.iter()
.filter(|role| role.to_lowercase().contains(&current_input))
.cloned()
.collect();
}
pub fn deactivate_role_suggestions(&mut self) {
self.role_suggestions_active = false;
}
pub fn is_role_suggestions_active(&self) -> bool {
self.role_suggestions_active
}
pub fn get_role_suggestions(&self) -> &[String] {
&self.role_suggestions
}
pub fn has_unsaved_changes(&self) -> bool {
self.has_unsaved_changes
}
pub fn set_has_unsaved_changes(&mut self, changed: bool) {
self.has_unsaved_changes = changed;
}
}
impl DataProvider for RegisterState {
fn field_count(&self) -> usize {
5
}
fn field_name(&self, index: usize) -> &str {
match index {
0 => "Username",
1 => "Email (Optional)",
2 => "Password (Optional)",
3 => "Confirm Password",
4 => "Role (Optional)",
_ => "",
}
}
fn field_value(&self, index: usize) -> &str {
match index {
0 => &self.username,
1 => &self.email,
2 => &self.password,
3 => &self.password_confirmation,
4 => &self.role,
_ => "",
}
}
fn set_field_value(&mut self, index: usize, value: String) {
match index {
0 => self.username = value,
1 => self.email = value,
2 => self.password = value,
3 => self.password_confirmation = value,
4 => self.role = value,
_ => {}
}
self.has_unsaved_changes = true;
}
fn supports_suggestions(&self, field_index: usize) -> bool {
field_index == 4 // only Role field supports suggestions
}
}

View File

@@ -1,9 +1,7 @@
// src/components/auth/register.rs
// src/pages/register/ui.rs
use crate::{
config::colors::themes::Theme,
state::pages::auth::RegisterState,
components::common::dialog,
state::app::state::AppState,
modes::handlers::mode_manager::AppMode,
};
@@ -13,6 +11,8 @@ use ratatui::{
widgets::{Block, BorderType, Borders, Paragraph},
Frame,
};
use crate::dialog;
use crate::pages::register::RegisterState;
use canvas::{FormEditor, render_canvas, render_suggestions_dropdown, DefaultCanvasTheme};
pub fn render_register(

View File

@@ -1,12 +1,14 @@
// src/pages/routing/router.rs
use crate::state::pages::{
admin::AdminState,
auth::{AuthState, LoginState, RegisterState},
intro::IntroState,
auth::AuthState,
add_logic::AddLogicState,
add_table::AddTableState,
};
use crate::pages::forms::FormState;
use crate::pages::login::LoginState;
use crate::pages::register::RegisterState;
use crate::pages::intro::IntroState;
#[derive(Debug)]
pub enum Page {

View File

@@ -10,23 +10,13 @@ use crate::ui::handlers::context::DialogPurpose;
use crate::config::binds::Config;
use crate::pages::forms::FormState;
use canvas::FormEditor;
use crate::dialog::DialogState;
use std::collections::HashMap;
use std::env;
use std::sync::Arc;
#[cfg(feature = "ui-debug")]
use std::time::Instant;
// --- DialogState and UiState are unchanged ---
pub struct DialogState {
pub dialog_show: bool,
pub dialog_title: String,
pub dialog_message: String,
pub dialog_buttons: Vec<String>,
pub dialog_active_button_index: usize,
pub purpose: Option<DialogPurpose>,
pub is_loading: bool,
}
pub struct UiState {
pub show_sidebar: bool,
pub show_buffer_list: bool,
@@ -109,84 +99,6 @@ impl AppState {
self.current_view_table_name = Some(table_name);
}
pub fn show_dialog(
&mut self,
title: &str,
message: &str,
buttons: Vec<String>,
purpose: DialogPurpose,
) {
self.ui.dialog.dialog_title = title.to_string();
self.ui.dialog.dialog_message = message.to_string();
self.ui.dialog.dialog_buttons = buttons;
self.ui.dialog.dialog_active_button_index = 0;
self.ui.dialog.purpose = Some(purpose);
self.ui.dialog.is_loading = false;
self.ui.dialog.dialog_show = true;
self.ui.focus_outside_canvas = true;
}
pub fn show_loading_dialog(&mut self, title: &str, message: &str) {
self.ui.dialog.dialog_title = title.to_string();
self.ui.dialog.dialog_message = message.to_string();
self.ui.dialog.dialog_buttons.clear();
self.ui.dialog.dialog_active_button_index = 0;
self.ui.dialog.purpose = None;
self.ui.dialog.is_loading = true;
self.ui.dialog.dialog_show = true;
self.ui.focus_outside_canvas = true;
}
pub fn update_dialog_content(
&mut self,
message: &str,
buttons: Vec<String>,
purpose: DialogPurpose,
) {
if self.ui.dialog.dialog_show {
self.ui.dialog.dialog_message = message.to_string();
self.ui.dialog.dialog_buttons = buttons;
self.ui.dialog.dialog_active_button_index = 0;
self.ui.dialog.purpose = Some(purpose);
self.ui.dialog.is_loading = false;
}
}
pub fn hide_dialog(&mut self) {
self.ui.dialog.dialog_show = false;
self.ui.dialog.dialog_title.clear();
self.ui.dialog.dialog_message.clear();
self.ui.dialog.dialog_buttons.clear();
self.ui.dialog.dialog_active_button_index = 0;
self.ui.dialog.purpose = None;
self.ui.focus_outside_canvas = false;
self.ui.dialog.is_loading = false;
}
pub fn next_dialog_button(&mut self) {
if !self.ui.dialog.dialog_buttons.is_empty() {
let next_index = (self.ui.dialog.dialog_active_button_index + 1)
% self.ui.dialog.dialog_buttons.len();
self.ui.dialog.dialog_active_button_index = next_index;
}
}
pub fn previous_dialog_button(&mut self) {
if !self.ui.dialog.dialog_buttons.is_empty() {
let len = self.ui.dialog.dialog_buttons.len();
let prev_index =
(self.ui.dialog.dialog_active_button_index + len - 1) % len;
self.ui.dialog.dialog_active_button_index = prev_index;
}
}
pub fn get_active_dialog_button_label(&self) -> Option<&str> {
self.ui.dialog
.dialog_buttons
.get(self.ui.dialog.dialog_active_button_index)
.map(|s| s.as_str())
}
pub fn init_form_editor(&mut self, form_state: FormState, config: &Config) {
let mut editor = FormEditor::new(form_state);
editor.set_keymap(config.build_canvas_keymap()); // inject keymap
@@ -229,17 +141,3 @@ impl Default for UiState {
}
}
}
impl Default for DialogState {
fn default() -> Self {
Self {
dialog_show: false,
dialog_title: String::new(),
dialog_message: String::new(),
dialog_buttons: Vec::new(),
dialog_active_button_index: 0,
purpose: None,
is_loading: false,
}
}
}

View File

@@ -2,6 +2,5 @@
pub mod auth;
pub mod admin;
pub mod intro;
pub mod add_table;
pub mod add_logic;

View File

@@ -1,15 +1,6 @@
// src/state/pages/auth.rs
use canvas::{DataProvider, AppMode};
use lazy_static::lazy_static;
lazy_static! {
pub static ref AVAILABLE_ROLES: Vec<String> = vec![
"admin".to_string(),
"moderator".to_string(),
"accountant".to_string(),
"viewer".to_string(),
];
}
use canvas::{DataProvider, AppMode};
/// Represents the authenticated session state
#[derive(Default)]
@@ -20,307 +11,8 @@ pub struct AuthState {
pub decoded_username: Option<String>,
}
/// Represents the state of the Login form UI
#[derive(Debug, Clone)]
pub struct LoginState {
pub username: String,
pub password: String,
pub error_message: Option<String>,
pub current_field: usize,
pub current_cursor_pos: usize,
pub has_unsaved_changes: bool,
pub login_request_pending: bool,
pub app_mode: AppMode,
}
impl Default for LoginState {
fn default() -> Self {
Self {
username: String::new(),
password: String::new(),
error_message: None,
current_field: 0,
current_cursor_pos: 0,
has_unsaved_changes: false,
login_request_pending: false,
app_mode: AppMode::Edit,
}
}
}
/// Represents the state of the Registration form UI
#[derive(Debug, Clone)]
pub struct RegisterState {
pub username: String,
pub email: String,
pub password: String,
pub password_confirmation: String,
pub role: String,
pub error_message: Option<String>,
pub current_field: usize,
pub current_cursor_pos: usize,
pub has_unsaved_changes: bool,
pub app_mode: AppMode,
// Keep role suggestions for later integration
pub role_suggestions: Vec<String>,
pub role_suggestions_active: bool,
}
impl Default for RegisterState {
fn default() -> Self {
Self {
username: String::new(),
email: String::new(),
password: String::new(),
password_confirmation: String::new(),
role: String::new(),
error_message: None,
current_field: 0,
current_cursor_pos: 0,
has_unsaved_changes: false,
app_mode: AppMode::Edit,
role_suggestions: AVAILABLE_ROLES.clone(),
role_suggestions_active: false,
}
}
}
impl AuthState {
pub fn new() -> Self {
Self::default()
}
}
impl LoginState {
pub fn new() -> Self {
Self {
app_mode: AppMode::Edit,
..Default::default()
}
}
// Legacy method compatibility
pub fn current_field(&self) -> usize {
self.current_field
}
pub fn current_cursor_pos(&self) -> usize {
self.current_cursor_pos
}
pub fn set_current_field(&mut self, index: usize) {
if index < 2 {
self.current_field = index;
}
}
pub fn set_current_cursor_pos(&mut self, pos: usize) {
self.current_cursor_pos = pos;
}
pub fn get_current_input(&self) -> &str {
match self.current_field {
0 => &self.username,
1 => &self.password,
_ => "",
}
}
pub fn get_current_input_mut(&mut self) -> &mut String {
match self.current_field {
0 => &mut self.username,
1 => &mut self.password,
_ => panic!("Invalid current_field index in LoginState"),
}
}
pub fn current_mode(&self) -> AppMode {
self.app_mode
}
// Add missing methods that used to come from CanvasState trait
pub fn has_unsaved_changes(&self) -> bool {
self.has_unsaved_changes
}
pub fn set_has_unsaved_changes(&mut self, changed: bool) {
self.has_unsaved_changes = changed;
}
}
impl RegisterState {
pub fn new() -> Self {
Self {
app_mode: AppMode::Edit,
role_suggestions: AVAILABLE_ROLES.clone(),
role_suggestions_active: false,
..Default::default()
}
}
// Legacy method compatibility
pub fn current_field(&self) -> usize {
self.current_field
}
pub fn current_cursor_pos(&self) -> usize {
self.current_cursor_pos
}
pub fn set_current_field(&mut self, index: usize) {
if index < 5 {
self.current_field = index;
// Auto-activate role suggestions when moving to role field (index 4)
if index == 4 {
self.activate_role_suggestions();
} else {
self.deactivate_role_suggestions();
}
}
}
pub fn set_current_cursor_pos(&mut self, pos: usize) {
self.current_cursor_pos = pos;
}
pub fn get_current_input(&self) -> &str {
match self.current_field {
0 => &self.username,
1 => &self.email,
2 => &self.password,
3 => &self.password_confirmation,
4 => &self.role,
_ => "",
}
}
pub fn get_current_input_mut(&mut self) -> &mut String {
match self.current_field {
0 => &mut self.username,
1 => &mut self.email,
2 => &mut self.password,
3 => &mut self.password_confirmation,
4 => &mut self.role,
_ => panic!("Invalid current_field index in RegisterState"),
}
}
pub fn current_mode(&self) -> AppMode {
self.app_mode
}
// Role suggestions management
pub fn activate_role_suggestions(&mut self) {
self.role_suggestions_active = true;
// Filter suggestions based on current input
let current_input = self.role.to_lowercase();
self.role_suggestions = AVAILABLE_ROLES
.iter()
.filter(|role| role.to_lowercase().contains(&current_input))
.cloned()
.collect();
}
pub fn deactivate_role_suggestions(&mut self) {
self.role_suggestions_active = false;
}
pub fn is_role_suggestions_active(&self) -> bool {
self.role_suggestions_active
}
pub fn get_role_suggestions(&self) -> &[String] {
&self.role_suggestions
}
// Add missing methods that used to come from CanvasState trait
pub fn has_unsaved_changes(&self) -> bool {
self.has_unsaved_changes
}
pub fn set_has_unsaved_changes(&mut self, changed: bool) {
self.has_unsaved_changes = changed;
}
}
// Step 2: Implement DataProvider for LoginState
impl DataProvider for LoginState {
fn field_count(&self) -> usize {
2
}
fn field_name(&self, index: usize) -> &str {
match index {
0 => "Username/Email",
1 => "Password",
_ => "",
}
}
fn field_value(&self, index: usize) -> &str {
match index {
0 => &self.username,
1 => &self.password,
_ => "",
}
}
fn set_field_value(&mut self, index: usize, value: String) {
match index {
0 => self.username = value,
1 => self.password = value,
_ => {}
}
self.has_unsaved_changes = true;
}
fn supports_suggestions(&self, _field_index: usize) -> bool {
false // Login form doesn't support suggestions
}
}
// Step 3: Implement DataProvider for RegisterState
impl DataProvider for RegisterState {
fn field_count(&self) -> usize {
5
}
fn field_name(&self, index: usize) -> &str {
match index {
0 => "Username",
1 => "Email (Optional)",
2 => "Password (Optional)",
3 => "Confirm Password",
4 => "Role (Optional)",
_ => "",
}
}
fn field_value(&self, index: usize) -> &str {
match index {
0 => &self.username,
1 => &self.email,
2 => &self.password,
3 => &self.password_confirmation,
4 => &self.role,
_ => "",
}
}
fn set_field_value(&mut self, index: usize, value: String) {
match index {
0 => self.username = value,
1 => self.email = value,
2 => self.password = value,
3 => self.password_confirmation = value,
4 => self.role = value,
_ => {}
}
self.has_unsaved_changes = true;
}
fn supports_suggestions(&self, field_index: usize) -> bool {
field_index == 4 // only Role field supports suggestions
}
}

View File

@@ -1,9 +1,6 @@
// src/tui/functions.rs
pub mod admin;
pub mod intro;
pub mod login;
pub mod common;
pub use admin::*;
pub use intro::*;

View File

@@ -1,6 +1,4 @@
// src/tui/functions/common.rs
pub mod login;
pub mod logout;
pub mod register;
pub mod add_table;

View File

@@ -1,15 +0,0 @@
// src/tui/functions/login.rs
use anyhow::{anyhow, Result};
pub async fn handle_action(action: &str,) -> Result<String> {
match action {
"previous_entry" => {
Ok("Previous entry at tui/functions/login.rs not implemented".into())
}
"next_entry" => {
Ok("Next entry at tui/functions/login.rs not implemented".into())
}
_ => Err(anyhow!("Unknown login action: {}", action))
}
}

View File

@@ -3,11 +3,11 @@
use crate::components::{
admin::add_logic::render_add_logic,
admin::render_add_table,
auth::{login::render_login, register::render_register},
common::dialog::render_dialog,
intro::intro::render_intro,
render_background,
};
use crate::pages::login::render_login;
use crate::pages::register::render_register;
use crate::pages::intro::render_intro;
use crate::bottom_panel::{
command_line::render_command_line,
status_line::render_status_line,
@@ -27,6 +27,8 @@ use ratatui::{
Frame,
};
use crate::pages::routing::{Router, Page};
use crate::dialog::render_dialog;
use crate::pages::forms::render_form_page;
pub fn render_ui(
f: &mut Frame,

View File

@@ -9,11 +9,10 @@ use crate::modes::common::commands::CommandHandler;
use crate::modes::handlers::event::{EventHandler, EventOutcome};
use crate::modes::handlers::mode_manager::{AppMode, ModeManager};
use crate::state::pages::auth::AuthState;
use crate::state::pages::auth::LoginState;
use crate::state::pages::auth::RegisterState;
use crate::pages::register::RegisterState;
use crate::state::pages::admin::AdminState;
use crate::state::pages::admin::AdminFocus;
use crate::state::pages::intro::IntroState;
use crate::pages::intro::IntroState;
use crate::pages::forms::{FormState, FieldDefinition};
use crate::pages::routing::{Router, Page};
use crate::buffer::state::BufferState;
@@ -21,11 +20,12 @@ use crate::buffer::state::AppView;
use crate::state::app::state::AppState;
use crate::tui::terminal::{EventReader, TerminalCore};
use crate::ui::handlers::render::render_ui;
use crate::tui::functions::common::login::LoginResult;
use crate::tui::functions::common::register::RegisterResult;
use crate::pages::login;
use crate::pages::register;
use crate::pages::login::LoginResult;
use crate::pages::login::LoginState;
use crate::pages::register::RegisterResult;
use crate::ui::handlers::context::DialogPurpose;
use crate::tui::functions::common::login;
use crate::tui::functions::common::register;
use crate::utils::columns::filter_user_columns;
use canvas::keymap::KeyEventOutcome;
use anyhow::{Context, Result};