Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
944131d5a6 | ||
|
|
eff46ac9bf | ||
|
|
14f9b254b2 | ||
|
|
337d6050ff | ||
|
|
d36348b84f | ||
|
|
dfb6f5b375 | ||
|
|
a9089bc2ff | ||
|
|
5f5d690ff3 | ||
|
|
431882ece9 | ||
|
|
b51e76e366 | ||
|
|
4e01740a61 | ||
|
|
e729ed9df3 | ||
|
|
6b241304fb | ||
|
|
3ed8764087 | ||
|
|
df61b245ab | ||
|
|
7a364d654c | ||
|
|
0d1a0be1a0 | ||
|
|
5da9f5aaf4 | ||
|
|
2bec0f5850 | ||
|
|
5bc28ec38b | ||
|
|
8c7a0a1ec0 |
6
Cargo.lock
generated
6
Cargo.lock
generated
@@ -421,7 +421,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "client"
|
name = "client"
|
||||||
version = "0.3.3"
|
version = "0.3.5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"common",
|
"common",
|
||||||
@@ -458,7 +458,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "common"
|
name = "common"
|
||||||
version = "0.3.3"
|
version = "0.3.5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"prost",
|
"prost",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -2589,7 +2589,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "server"
|
name = "server"
|
||||||
version = "0.3.3"
|
version = "0.3.5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bcrypt",
|
"bcrypt",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ resolver = "2"
|
|||||||
[workspace.package]
|
[workspace.package]
|
||||||
# TODO: idk how to do the name, fix later
|
# TODO: idk how to do the name, fix later
|
||||||
# name = "Multieko2"
|
# name = "Multieko2"
|
||||||
version = "0.3.3"
|
version = "0.3.5"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "GPL-3.0-or-later"
|
license = "GPL-3.0-or-later"
|
||||||
authors = ["Filip Priečinský <filippriec@gmail.com>"]
|
authors = ["Filip Priečinský <filippriec@gmail.com>"]
|
||||||
|
|||||||
@@ -3,3 +3,4 @@ pub mod login;
|
|||||||
pub mod register;
|
pub mod register;
|
||||||
|
|
||||||
pub use login::*;
|
pub use login::*;
|
||||||
|
pub use register::*;
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ use crate::{
|
|||||||
components::common::dialog,
|
components::common::dialog,
|
||||||
state::state::AppState,
|
state::state::AppState,
|
||||||
};
|
};
|
||||||
use crate::modes::handlers::mode_manager::AppMode;
|
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
layout::{Alignment, Constraint, Direction, Layout, Rect, Margin},
|
layout::{Alignment, Constraint, Direction, Layout, Rect, Margin},
|
||||||
style::{Style, Modifier, Color},
|
style::{Style, Modifier, Color},
|
||||||
|
|||||||
@@ -0,0 +1,153 @@
|
|||||||
|
// src/components/auth/register.rs
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
config::colors::themes::Theme,
|
||||||
|
state::pages::auth::RegisterState, // Use RegisterState
|
||||||
|
components::common::dialog,
|
||||||
|
state::state::AppState,
|
||||||
|
};
|
||||||
|
use ratatui::{
|
||||||
|
layout::{Alignment, Constraint, Direction, Layout, Rect, Margin},
|
||||||
|
style::{Style, Modifier, Color},
|
||||||
|
widgets::{Block, BorderType, Borders, Paragraph},
|
||||||
|
Frame,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn render_register(
|
||||||
|
f: &mut Frame,
|
||||||
|
area: Rect,
|
||||||
|
theme: &Theme,
|
||||||
|
state: &RegisterState, // Use RegisterState
|
||||||
|
app_state: &AppState,
|
||||||
|
is_edit_mode: bool,
|
||||||
|
) {
|
||||||
|
let block = Block::default()
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.border_type(BorderType::Plain)
|
||||||
|
.border_style(Style::default().fg(theme.border))
|
||||||
|
.title(" Register ") // Update title
|
||||||
|
.style(Style::default().bg(theme.bg));
|
||||||
|
|
||||||
|
f.render_widget(block, area);
|
||||||
|
|
||||||
|
let inner_area = area.inner(Margin {
|
||||||
|
horizontal: 1,
|
||||||
|
vertical: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Adjust constraints for 4 fields + error + buttons
|
||||||
|
let chunks = Layout::default()
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
.constraints([
|
||||||
|
Constraint::Length(6), // Form (4 fields + padding)
|
||||||
|
Constraint::Length(1), // Error message
|
||||||
|
Constraint::Length(3), // Buttons
|
||||||
|
])
|
||||||
|
.split(inner_area);
|
||||||
|
|
||||||
|
// --- FORM RENDERING ---
|
||||||
|
crate::components::handlers::canvas::render_canvas(
|
||||||
|
f,
|
||||||
|
chunks[0],
|
||||||
|
state, // Pass RegisterState
|
||||||
|
&[ // Update field labels
|
||||||
|
"Username",
|
||||||
|
"Email (Optional)",
|
||||||
|
"Password (Optional)",
|
||||||
|
"Confirm Password",
|
||||||
|
],
|
||||||
|
&state.current_field,
|
||||||
|
&[ // Update values from RegisterState
|
||||||
|
&state.username,
|
||||||
|
&state.email,
|
||||||
|
&state.password,
|
||||||
|
&state.password_confirmation,
|
||||||
|
],
|
||||||
|
theme,
|
||||||
|
is_edit_mode,
|
||||||
|
);
|
||||||
|
|
||||||
|
// --- ERROR MESSAGE ---
|
||||||
|
if let Some(err) = &state.error_message {
|
||||||
|
f.render_widget(
|
||||||
|
Paragraph::new(err.as_str())
|
||||||
|
.style(Style::default().fg(Color::Red))
|
||||||
|
.alignment(Alignment::Center),
|
||||||
|
chunks[1],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- BUTTONS ---
|
||||||
|
let button_chunks = Layout::default()
|
||||||
|
.direction(Direction::Horizontal)
|
||||||
|
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
|
||||||
|
.split(chunks[2]);
|
||||||
|
|
||||||
|
// Register Button
|
||||||
|
let register_button_index = 0;
|
||||||
|
let register_active = if app_state.ui.focus_outside_canvas {
|
||||||
|
app_state.general.selected_item == register_button_index
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
let mut register_style = Style::default().fg(theme.fg);
|
||||||
|
let mut register_border = Style::default().fg(theme.border);
|
||||||
|
if register_active {
|
||||||
|
register_style = register_style.fg(theme.highlight).add_modifier(Modifier::BOLD);
|
||||||
|
register_border = register_border.fg(theme.accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
f.render_widget(
|
||||||
|
Paragraph::new("Register") // Update button text
|
||||||
|
.style(register_style)
|
||||||
|
.alignment(Alignment::Center)
|
||||||
|
.block(
|
||||||
|
Block::default()
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.border_type(BorderType::Plain)
|
||||||
|
.border_style(register_border),
|
||||||
|
),
|
||||||
|
button_chunks[0],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Return Button (logic remains similar)
|
||||||
|
let return_button_index = 1;
|
||||||
|
let return_active = if app_state.ui.focus_outside_canvas {
|
||||||
|
app_state.general.selected_item == return_button_index
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
let mut return_style = Style::default().fg(theme.fg);
|
||||||
|
let mut return_border = Style::default().fg(theme.border);
|
||||||
|
if return_active {
|
||||||
|
return_style = return_style.fg(theme.highlight).add_modifier(Modifier::BOLD);
|
||||||
|
return_border = return_border.fg(theme.accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
f.render_widget(
|
||||||
|
Paragraph::new("Return")
|
||||||
|
.style(return_style)
|
||||||
|
.alignment(Alignment::Center)
|
||||||
|
.block(
|
||||||
|
Block::default()
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.border_type(BorderType::Plain)
|
||||||
|
.border_style(return_border),
|
||||||
|
),
|
||||||
|
button_chunks[1],
|
||||||
|
);
|
||||||
|
|
||||||
|
// --- DIALOG --- (Keep dialog logic)
|
||||||
|
if app_state.ui.dialog.dialog_show {
|
||||||
|
dialog::render_dialog(
|
||||||
|
f,
|
||||||
|
f.area(),
|
||||||
|
theme,
|
||||||
|
&app_state.ui.dialog.dialog_title,
|
||||||
|
&app_state.ui.dialog.dialog_message,
|
||||||
|
&app_state.ui.dialog.dialog_buttons,
|
||||||
|
app_state.ui.dialog.dialog_active_button_index,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ impl IntroState {
|
|||||||
.direction(Direction::Vertical)
|
.direction(Direction::Vertical)
|
||||||
.constraints([
|
.constraints([
|
||||||
Constraint::Percentage(40),
|
Constraint::Percentage(40),
|
||||||
Constraint::Length(5), // Increased to accommodate 3 buttons
|
Constraint::Length(5),
|
||||||
Constraint::Percentage(40),
|
Constraint::Percentage(40),
|
||||||
])
|
])
|
||||||
.split(inner_area);
|
.split(inner_area);
|
||||||
@@ -48,51 +48,44 @@ impl IntroState {
|
|||||||
.alignment(Alignment::Center);
|
.alignment(Alignment::Center);
|
||||||
f.render_widget(title_para, chunks[1]);
|
f.render_widget(title_para, chunks[1]);
|
||||||
|
|
||||||
// Buttons - now with 3 options
|
// Buttons - now with 4 options
|
||||||
let button_area = Layout::default()
|
let button_area = Layout::default()
|
||||||
.direction(Direction::Horizontal)
|
.direction(Direction::Horizontal)
|
||||||
.constraints([
|
.constraints([
|
||||||
Constraint::Percentage(33),
|
Constraint::Percentage(25),
|
||||||
Constraint::Percentage(33),
|
Constraint::Percentage(25),
|
||||||
Constraint::Percentage(33),
|
Constraint::Percentage(25),
|
||||||
|
Constraint::Percentage(25),
|
||||||
])
|
])
|
||||||
.split(chunks[1].inner(Margin {
|
.split(chunks[1].inner(Margin {
|
||||||
horizontal: 1,
|
horizontal: 1,
|
||||||
vertical: 1
|
vertical: 1
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
let buttons = ["Continue", "Admin", "Login", "Register"];
|
||||||
|
for (i, &text) in buttons.iter().enumerate() {
|
||||||
self.render_button(
|
self.render_button(
|
||||||
f,
|
f,
|
||||||
button_area[0],
|
button_area[i],
|
||||||
"Continue",
|
text,
|
||||||
self.selected_option == 0,
|
self.selected_option == i,
|
||||||
theme,
|
|
||||||
);
|
|
||||||
self.render_button(
|
|
||||||
f,
|
|
||||||
button_area[1],
|
|
||||||
"Admin",
|
|
||||||
self.selected_option == 1,
|
|
||||||
theme,
|
|
||||||
);
|
|
||||||
self.render_button(
|
|
||||||
f,
|
|
||||||
button_area[2],
|
|
||||||
"Login",
|
|
||||||
self.selected_option == 2,
|
|
||||||
theme,
|
theme,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn render_button(&self, f: &mut Frame, area: Rect, text: &str, selected: bool, theme: &Theme) {
|
fn render_button(&self, f: &mut Frame, area: Rect, text: &str, selected: bool, theme: &Theme) {
|
||||||
let button_style = if selected {
|
let button_style = Style::default()
|
||||||
Style::default()
|
.fg(if selected { theme.highlight } else { theme.fg })
|
||||||
.fg(theme.highlight)
|
|
||||||
.bg(theme.bg)
|
.bg(theme.bg)
|
||||||
.add_modifier(ratatui::style::Modifier::BOLD)
|
.add_modifier(if selected {
|
||||||
|
ratatui::style::Modifier::BOLD
|
||||||
} else {
|
} else {
|
||||||
Style::default().fg(theme.fg).bg(theme.bg)
|
ratatui::style::Modifier::empty()
|
||||||
};
|
});
|
||||||
|
|
||||||
|
let border_style = Style::default()
|
||||||
|
.fg(if selected { theme.accent } else { theme.border });
|
||||||
|
|
||||||
let button = Paragraph::new(text)
|
let button = Paragraph::new(text)
|
||||||
.style(button_style)
|
.style(button_style)
|
||||||
@@ -101,21 +94,17 @@ impl IntroState {
|
|||||||
Block::default()
|
Block::default()
|
||||||
.borders(Borders::ALL)
|
.borders(Borders::ALL)
|
||||||
.border_type(BorderType::Double)
|
.border_type(BorderType::Double)
|
||||||
.border_style(if selected {
|
.border_style(border_style),
|
||||||
Style::default().fg(theme.accent)
|
|
||||||
} else {
|
|
||||||
Style::default().fg(theme.border)
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
f.render_widget(button, area);
|
f.render_widget(button, area);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn next_option(&mut self) {
|
pub fn next_option(&mut self) {
|
||||||
self.selected_option = (self.selected_option + 1) % 3;
|
self.selected_option = (self.selected_option + 1) % 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn previous_option(&mut self) {
|
pub fn previous_option(&mut self) {
|
||||||
self.selected_option = if self.selected_option == 0 { 2 } else { self.selected_option - 1 };
|
self.selected_option = if self.selected_option == 0 { 3 } else { self.selected_option - 1 };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
use crate::services::grpc_client::GrpcClient;
|
use crate::services::grpc_client::GrpcClient;
|
||||||
use crate::state::canvas_state::CanvasState;
|
use crate::state::canvas_state::CanvasState;
|
||||||
use crate::state::pages::{auth::AuthState, form::FormState};
|
use crate::state::pages::form::FormState;
|
||||||
use crate::tui::functions::common::form::{revert, save};
|
use crate::tui::functions::common::form::{revert, save};
|
||||||
use crossterm::event::{KeyCode, KeyEvent};
|
use crossterm::event::{KeyCode, KeyEvent};
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
|
|||||||
@@ -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::AuthState};
|
use crate::state::pages::{form::FormState, auth::AuthState, auth::RegisterState};
|
||||||
use crate::state::state::AppState;
|
use crate::state::state::AppState;
|
||||||
use crate::services::grpc_client::GrpcClient;
|
use crate::services::grpc_client::GrpcClient;
|
||||||
use crate::services::auth::AuthClient;
|
use crate::services::auth::AuthClient;
|
||||||
@@ -9,13 +9,15 @@ use crate::modes::handlers::event::EventOutcome;
|
|||||||
use crate::tui::functions::common::form::SaveOutcome;
|
use crate::tui::functions::common::form::SaveOutcome;
|
||||||
use crate::tui::functions::common::{
|
use crate::tui::functions::common::{
|
||||||
form::{save as form_save, revert as form_revert},
|
form::{save as form_save, revert as form_revert},
|
||||||
login::{save as login_save, revert as login_revert}
|
login::{save as login_save, revert as login_revert},
|
||||||
|
register::{save as register_save, revert as register_revert},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub async fn handle_core_action(
|
pub async fn handle_core_action(
|
||||||
action: &str,
|
action: &str,
|
||||||
form_state: &mut FormState,
|
form_state: &mut FormState,
|
||||||
auth_state: &mut AuthState,
|
auth_state: &mut AuthState,
|
||||||
|
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,
|
||||||
@@ -28,6 +30,9 @@ pub async fn handle_core_action(
|
|||||||
if app_state.ui.show_login {
|
if app_state.ui.show_login {
|
||||||
let message = login_save(auth_state, auth_client, app_state).await?;
|
let message = login_save(auth_state, auth_client, app_state).await?;
|
||||||
Ok(EventOutcome::Ok(message))
|
Ok(EventOutcome::Ok(message))
|
||||||
|
} else if app_state.ui.show_register {
|
||||||
|
let message = register_save(register_state, auth_client, app_state).await?;
|
||||||
|
Ok(EventOutcome::Ok(message))
|
||||||
} else {
|
} else {
|
||||||
let save_outcome = form_save(
|
let save_outcome = form_save(
|
||||||
form_state,
|
form_state,
|
||||||
@@ -50,6 +55,8 @@ pub async fn handle_core_action(
|
|||||||
"save_and_quit" => {
|
"save_and_quit" => {
|
||||||
let message = if app_state.ui.show_login {
|
let message = if app_state.ui.show_login {
|
||||||
login_save(auth_state, auth_client, app_state).await?
|
login_save(auth_state, auth_client, app_state).await?
|
||||||
|
} else if app_state.ui.show_register {
|
||||||
|
register_save(register_state, auth_client, app_state).await?
|
||||||
} else {
|
} else {
|
||||||
let save_outcome = form_save(
|
let save_outcome = form_save(
|
||||||
form_state,
|
form_state,
|
||||||
@@ -70,6 +77,9 @@ pub async fn handle_core_action(
|
|||||||
if app_state.ui.show_login {
|
if app_state.ui.show_login {
|
||||||
let message = login_revert(auth_state, app_state).await;
|
let message = login_revert(auth_state, app_state).await;
|
||||||
Ok(EventOutcome::Ok(message))
|
Ok(EventOutcome::Ok(message))
|
||||||
|
} else if app_state.ui.show_register {
|
||||||
|
let message = register_revert(register_state, app_state).await;
|
||||||
|
Ok(EventOutcome::Ok(message))
|
||||||
} else {
|
} else {
|
||||||
let message = form_revert(
|
let message = form_revert(
|
||||||
form_state,
|
form_state,
|
||||||
|
|||||||
@@ -2,22 +2,25 @@
|
|||||||
|
|
||||||
use crate::config::binds::config::Config;
|
use crate::config::binds::config::Config;
|
||||||
use crate::services::grpc_client::GrpcClient;
|
use crate::services::grpc_client::GrpcClient;
|
||||||
use crate::state::pages::{auth::AuthState, form::FormState};
|
use crate::state::pages::{auth::{AuthState, RegisterState}};
|
||||||
|
use crate::state::pages::form::FormState;
|
||||||
use crate::functions::modes::edit::{auth_e, form_e};
|
use crate::functions::modes::edit::{auth_e, form_e};
|
||||||
use crate::modes::handlers::event::EventOutcome;
|
use crate::modes::handlers::event::EventOutcome;
|
||||||
|
use crate::state::state::AppState;
|
||||||
use crossterm::event::{KeyCode, KeyEvent};
|
use crossterm::event::{KeyCode, KeyEvent};
|
||||||
|
|
||||||
pub async fn handle_edit_event(
|
pub async fn handle_edit_event(
|
||||||
is_auth_context: bool,
|
|
||||||
key: KeyEvent,
|
key: KeyEvent,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
form_state: &mut FormState,
|
form_state: &mut FormState,
|
||||||
auth_state: &mut AuthState,
|
auth_state: &mut AuthState,
|
||||||
|
register_state: &mut RegisterState,
|
||||||
ideal_cursor_column: &mut usize,
|
ideal_cursor_column: &mut usize,
|
||||||
command_message: &mut String,
|
command_message: &mut String,
|
||||||
current_position: &mut u64,
|
current_position: &mut u64,
|
||||||
total_count: u64,
|
total_count: u64,
|
||||||
grpc_client: &mut GrpcClient,
|
grpc_client: &mut GrpcClient,
|
||||||
|
app_state: &AppState,
|
||||||
) -> Result<String, Box<dyn std::error::Error>> {
|
) -> Result<String, Box<dyn std::error::Error>> {
|
||||||
|
|
||||||
// Global command mode check
|
// Global command mode check
|
||||||
@@ -37,7 +40,7 @@ pub async fn handle_edit_event(
|
|||||||
key.modifiers
|
key.modifiers
|
||||||
) {
|
) {
|
||||||
if matches!(action, "save" | "revert") {
|
if matches!(action, "save" | "revert") {
|
||||||
let message = if is_auth_context {
|
let message = if app_state.ui.show_login {
|
||||||
auth_e::execute_common_action(
|
auth_e::execute_common_action(
|
||||||
action,
|
action,
|
||||||
auth_state, // Concrete AuthState
|
auth_state, // Concrete AuthState
|
||||||
@@ -45,6 +48,14 @@ pub async fn handle_edit_event(
|
|||||||
current_position,
|
current_position,
|
||||||
total_count
|
total_count
|
||||||
).await
|
).await
|
||||||
|
} else if app_state.ui.show_register {
|
||||||
|
auth_e::execute_common_action(
|
||||||
|
action,
|
||||||
|
register_state,
|
||||||
|
grpc_client,
|
||||||
|
current_position,
|
||||||
|
total_count
|
||||||
|
).await
|
||||||
} else {
|
} else {
|
||||||
form_e::execute_common_action(
|
form_e::execute_common_action(
|
||||||
action,
|
action,
|
||||||
@@ -55,9 +66,11 @@ pub async fn handle_edit_event(
|
|||||||
).await
|
).await
|
||||||
.map(|outcome| match outcome {
|
.map(|outcome| match outcome {
|
||||||
EventOutcome::Ok(msg) => msg,
|
EventOutcome::Ok(msg) => msg,
|
||||||
EventOutcome::Exit(msg) => format!("Exit requested: {}", msg), // Or handle differently
|
EventOutcome::Exit(msg) => format!("Exit requested: {}", msg),
|
||||||
EventOutcome::DataSaved(save_outcome, msg) => format!("Data saved ({:?}): {}", save_outcome, msg),
|
EventOutcome::DataSaved(save_outcome, msg) => format!("Data saved ({:?}): {}", save_outcome, msg),
|
||||||
// Add other EventOutcome variants if necessary
|
EventOutcome::ButtonSelected { context, index } => {
|
||||||
|
"Unexpected action in edit mode".to_string()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}?;
|
}?;
|
||||||
return Ok(message);
|
return Ok(message);
|
||||||
@@ -66,11 +79,21 @@ pub async fn handle_edit_event(
|
|||||||
|
|
||||||
// Edit-specific actions
|
// Edit-specific actions
|
||||||
if let Some(action) = config.get_edit_action_for_key(key.code, key.modifiers) {
|
if let Some(action) = config.get_edit_action_for_key(key.code, key.modifiers) {
|
||||||
return if is_auth_context {
|
return if app_state.ui.show_login {
|
||||||
auth_e::execute_edit_action(
|
auth_e::execute_edit_action(
|
||||||
action,
|
action,
|
||||||
key,
|
key,
|
||||||
auth_state, // Full access to AuthState fields
|
auth_state,
|
||||||
|
ideal_cursor_column,
|
||||||
|
grpc_client,
|
||||||
|
current_position,
|
||||||
|
total_count
|
||||||
|
).await
|
||||||
|
} else if app_state.ui.show_register {
|
||||||
|
auth_e::execute_edit_action(
|
||||||
|
action,
|
||||||
|
key,
|
||||||
|
register_state,
|
||||||
ideal_cursor_column,
|
ideal_cursor_column,
|
||||||
grpc_client,
|
grpc_client,
|
||||||
current_position,
|
current_position,
|
||||||
@@ -80,7 +103,7 @@ pub async fn handle_edit_event(
|
|||||||
form_e::execute_edit_action(
|
form_e::execute_edit_action(
|
||||||
action,
|
action,
|
||||||
key,
|
key,
|
||||||
form_state, // Full access to FormState fields
|
form_state,
|
||||||
ideal_cursor_column,
|
ideal_cursor_column,
|
||||||
grpc_client,
|
grpc_client,
|
||||||
current_position,
|
current_position,
|
||||||
@@ -91,7 +114,7 @@ pub async fn handle_edit_event(
|
|||||||
|
|
||||||
// Character insertion
|
// Character insertion
|
||||||
if let KeyCode::Char(_) = key.code {
|
if let KeyCode::Char(_) = key.code {
|
||||||
return if is_auth_context {
|
return if app_state.ui.show_login {
|
||||||
auth_e::execute_edit_action(
|
auth_e::execute_edit_action(
|
||||||
"insert_char",
|
"insert_char",
|
||||||
key,
|
key,
|
||||||
@@ -101,6 +124,16 @@ pub async fn handle_edit_event(
|
|||||||
current_position,
|
current_position,
|
||||||
total_count
|
total_count
|
||||||
).await
|
).await
|
||||||
|
} else if app_state.ui.show_register {
|
||||||
|
auth_e::execute_edit_action(
|
||||||
|
"insert_char",
|
||||||
|
key,
|
||||||
|
register_state,
|
||||||
|
ideal_cursor_column,
|
||||||
|
grpc_client,
|
||||||
|
current_position,
|
||||||
|
total_count
|
||||||
|
).await
|
||||||
} else {
|
} else {
|
||||||
form_e::execute_edit_action(
|
form_e::execute_edit_action(
|
||||||
"insert_char",
|
"insert_char",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
use crate::config::binds::config::Config;
|
use crate::config::binds::config::Config;
|
||||||
use crate::config::binds::key_sequences::KeySequenceTracker;
|
use crate::config::binds::key_sequences::KeySequenceTracker;
|
||||||
use crate::services::grpc_client::GrpcClient;
|
use crate::services::grpc_client::GrpcClient;
|
||||||
use crate::state::canvas_state::CanvasState;
|
use crate::state::{canvas_state::CanvasState, pages::auth::RegisterState};
|
||||||
use crate::state::pages::auth::AuthState;
|
use crate::state::pages::auth::AuthState;
|
||||||
use crate::state::pages::form::FormState;
|
use crate::state::pages::form::FormState;
|
||||||
use crate::state::state::AppState;
|
use crate::state::state::AppState;
|
||||||
@@ -16,6 +16,7 @@ pub async fn handle_read_only_event(
|
|||||||
config: &Config,
|
config: &Config,
|
||||||
form_state: &mut FormState,
|
form_state: &mut FormState,
|
||||||
auth_state: &mut AuthState,
|
auth_state: &mut AuthState,
|
||||||
|
register_state: &mut RegisterState,
|
||||||
key_sequence_tracker: &mut KeySequenceTracker,
|
key_sequence_tracker: &mut KeySequenceTracker,
|
||||||
current_position: &mut u64,
|
current_position: &mut u64,
|
||||||
total_count: u64,
|
total_count: u64,
|
||||||
@@ -31,23 +32,31 @@ pub async fn handle_read_only_event(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if config.is_enter_edit_mode_after(key.code, key.modifiers) {
|
if config.is_enter_edit_mode_after(key.code, key.modifiers) {
|
||||||
let (current_input, current_pos) = if app_state.ui.show_login {
|
let (current_input, current_pos) = if app_state.ui.show_login { // Check Login first
|
||||||
(
|
(
|
||||||
auth_state.get_current_input(),
|
auth_state.get_current_input(),
|
||||||
auth_state.current_cursor_pos(),
|
auth_state.current_cursor_pos(),
|
||||||
)
|
)
|
||||||
|
} else if app_state.ui.show_register { // Then check Register
|
||||||
|
(
|
||||||
|
register_state.get_current_input(),
|
||||||
|
register_state.current_cursor_pos(),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
(
|
(
|
||||||
form_state.get_current_input(),
|
form_state.get_current_input(),
|
||||||
form_state.current_cursor_pos(),
|
form_state.current_cursor_pos(),
|
||||||
)
|
) // Default to Form
|
||||||
};
|
};
|
||||||
|
|
||||||
if !current_input.is_empty() && current_pos < current_input.len() {
|
if !current_input.is_empty() && current_pos < current_input.len() {
|
||||||
if app_state.ui.show_login {
|
if app_state.ui.show_login {
|
||||||
auth_state.set_current_cursor_pos(current_pos + 1);
|
auth_state.set_current_cursor_pos(current_pos + 1);
|
||||||
*ideal_cursor_column = auth_state.current_cursor_pos();
|
*ideal_cursor_column = auth_state.current_cursor_pos();
|
||||||
} else {
|
} else if app_state.ui.show_register {
|
||||||
|
register_state.set_current_cursor_pos(current_pos + 1);
|
||||||
|
*ideal_cursor_column = register_state.current_cursor_pos();
|
||||||
|
} else { // Default to Form
|
||||||
form_state.set_current_cursor_pos(current_pos + 1);
|
form_state.set_current_cursor_pos(current_pos + 1);
|
||||||
*ideal_cursor_column = form_state.current_cursor_pos();
|
*ideal_cursor_column = form_state.current_cursor_pos();
|
||||||
}
|
}
|
||||||
@@ -65,6 +74,10 @@ pub async fn handle_read_only_event(
|
|||||||
"previous_entry",
|
"previous_entry",
|
||||||
"next_entry",
|
"next_entry",
|
||||||
];
|
];
|
||||||
|
// Add context actions specific to register if needed, otherwise reuse login/form ones
|
||||||
|
const CONTEXT_ACTIONS_REGISTER: &[&str] = &[
|
||||||
|
// Add actions like "next_field", "prev_field" if handled differently than general read-only
|
||||||
|
];
|
||||||
|
|
||||||
if key.modifiers.is_empty() {
|
if key.modifiers.is_empty() {
|
||||||
key_sequence_tracker.add_key(key.code);
|
key_sequence_tracker.add_key(key.code);
|
||||||
@@ -81,13 +94,22 @@ pub async fn handle_read_only_event(
|
|||||||
ideal_cursor_column,
|
ideal_cursor_column,
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
} else if app_state.ui.show_login && CONTEXT_ACTIONS_LOGIN.contains(&action) {
|
} else if app_state.ui.show_login && CONTEXT_ACTIONS_LOGIN.contains(&action) { // Handle login context actions
|
||||||
crate::tui::functions::login::handle_action(
|
crate::tui::functions::login::handle_action(
|
||||||
action,
|
action,
|
||||||
auth_state,
|
auth_state,
|
||||||
ideal_cursor_column,
|
ideal_cursor_column,
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
|
} else if app_state.ui.show_register{
|
||||||
|
auth_ro::execute_action(
|
||||||
|
action,
|
||||||
|
app_state,
|
||||||
|
register_state,
|
||||||
|
ideal_cursor_column,
|
||||||
|
key_sequence_tracker,
|
||||||
|
command_message,
|
||||||
|
).await?
|
||||||
} else if app_state.ui.show_login {
|
} else if app_state.ui.show_login {
|
||||||
auth_ro::execute_action(
|
auth_ro::execute_action(
|
||||||
action,
|
action,
|
||||||
@@ -128,14 +150,23 @@ pub async fn handle_read_only_event(
|
|||||||
ideal_cursor_column,
|
ideal_cursor_column,
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
} else if app_state.ui.show_login && CONTEXT_ACTIONS_LOGIN.contains(&action) {
|
} else if app_state.ui.show_login && CONTEXT_ACTIONS_LOGIN.contains(&action) { // Handle login context actions
|
||||||
crate::tui::functions::login::handle_action(
|
crate::tui::functions::login::handle_action(
|
||||||
action,
|
action,
|
||||||
auth_state,
|
auth_state,
|
||||||
ideal_cursor_column,
|
ideal_cursor_column,
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
} else if app_state.ui.show_login {
|
} else if app_state.ui.show_register /* && CONTEXT_ACTIONS_REGISTER.contains(&action) */ { // Handle register general actions
|
||||||
|
auth_ro::execute_action(
|
||||||
|
action,
|
||||||
|
app_state,
|
||||||
|
register_state,
|
||||||
|
ideal_cursor_column,
|
||||||
|
key_sequence_tracker,
|
||||||
|
command_message,
|
||||||
|
).await?
|
||||||
|
} else if app_state.ui.show_login { // Handle login general actions
|
||||||
auth_ro::execute_action(
|
auth_ro::execute_action(
|
||||||
action,
|
action,
|
||||||
app_state,
|
app_state,
|
||||||
@@ -174,14 +205,23 @@ pub async fn handle_read_only_event(
|
|||||||
ideal_cursor_column,
|
ideal_cursor_column,
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
} else if app_state.ui.show_login && CONTEXT_ACTIONS_LOGIN.contains(&action) {
|
} else if app_state.ui.show_login && CONTEXT_ACTIONS_LOGIN.contains(&action) { // Handle login context actions
|
||||||
crate::tui::functions::login::handle_action(
|
crate::tui::functions::login::handle_action(
|
||||||
action,
|
action,
|
||||||
auth_state,
|
auth_state,
|
||||||
ideal_cursor_column,
|
ideal_cursor_column,
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
} else if app_state.ui.show_login {
|
} else if app_state.ui.show_register /* && CONTEXT_ACTIONS_REGISTER.contains(&action) */ { // Handle register general actions
|
||||||
|
auth_ro::execute_action(
|
||||||
|
action,
|
||||||
|
app_state,
|
||||||
|
register_state,
|
||||||
|
ideal_cursor_column,
|
||||||
|
key_sequence_tracker,
|
||||||
|
command_message,
|
||||||
|
).await?
|
||||||
|
} else if app_state.ui.show_login { // Handle login general actions
|
||||||
auth_ro::execute_action(
|
auth_ro::execute_action(
|
||||||
action,
|
action,
|
||||||
app_state,
|
app_state,
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
// src/client/modes/general.rs
|
// src/client/modes/general.rs
|
||||||
pub mod navigation;
|
pub mod navigation;
|
||||||
|
pub mod dialog;
|
||||||
|
|||||||
127
client/src/modes/general/dialog.rs
Normal file
127
client/src/modes/general/dialog.rs
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
// src/modes/general/dialog.rs
|
||||||
|
|
||||||
|
use crossterm::event::{Event, KeyCode};
|
||||||
|
use crate::config::binds::config::Config;
|
||||||
|
use crate::ui::handlers::context::DialogPurpose;
|
||||||
|
use crate::state::state::AppState;
|
||||||
|
use crate::state::pages::auth::AuthState;
|
||||||
|
use crate::state::pages::auth::RegisterState;
|
||||||
|
use crate::services::auth::AuthClient;
|
||||||
|
use crate::modes::handlers::event::EventOutcome;
|
||||||
|
use crate::tui::functions::common::{login, register};
|
||||||
|
|
||||||
|
/// Handles key events specifically when a dialog is active.
|
||||||
|
/// Returns Some(Result<EventOutcome, Error>) if the event was handled (consumed),
|
||||||
|
/// otherwise returns None.
|
||||||
|
pub async fn handle_dialog_event(
|
||||||
|
event: &Event,
|
||||||
|
config: &Config,
|
||||||
|
app_state: &mut AppState,
|
||||||
|
auth_state: &mut AuthState,
|
||||||
|
register_state: &mut RegisterState,
|
||||||
|
auth_client: &mut AuthClient,
|
||||||
|
) -> Option<Result<EventOutcome, Box<dyn std::error::Error>>> {
|
||||||
|
if let Event::Key(key) = event {
|
||||||
|
// Always allow Esc to dismiss
|
||||||
|
if key.code == KeyCode::Esc {
|
||||||
|
app_state.hide_dialog();
|
||||||
|
return Some(Ok(EventOutcome::Ok("Dialog dismissed".to_string())));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check general bindings for dialog actions
|
||||||
|
if let Some(action) = config.get_general_action(key.code, key.modifiers) {
|
||||||
|
match action {
|
||||||
|
"move_down" | "next_option" => {
|
||||||
|
let current_index = app_state.ui.dialog.dialog_active_button_index;
|
||||||
|
let num_buttons = app_state.ui.dialog.dialog_buttons.len();
|
||||||
|
if num_buttons > 0 && current_index < num_buttons - 1 {
|
||||||
|
app_state.ui.dialog.dialog_active_button_index += 1;
|
||||||
|
}
|
||||||
|
return Some(Ok(EventOutcome::Ok(String::new())));
|
||||||
|
}
|
||||||
|
"move_up" | "previous_option" => {
|
||||||
|
let current_index = app_state.ui.dialog.dialog_active_button_index;
|
||||||
|
if current_index > 0 {
|
||||||
|
app_state.ui.dialog.dialog_active_button_index -= 1;
|
||||||
|
}
|
||||||
|
return Some(Ok(EventOutcome::Ok(String::new())));
|
||||||
|
}
|
||||||
|
"select" => {
|
||||||
|
let selected_index = app_state.ui.dialog.dialog_active_button_index;
|
||||||
|
let purpose = match app_state.ui.dialog.purpose {
|
||||||
|
Some(p) => p,
|
||||||
|
None => {
|
||||||
|
app_state.hide_dialog();
|
||||||
|
return Some(Ok(EventOutcome::Ok("Internal Error: Dialog context lost".to_string())));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle Dialog Actions Directly Here
|
||||||
|
match purpose {
|
||||||
|
DialogPurpose::LoginSuccess => {
|
||||||
|
match selected_index {
|
||||||
|
0 => { // "Menu" button selected
|
||||||
|
app_state.hide_dialog();
|
||||||
|
let message = login::back_to_main(auth_state, app_state).await;
|
||||||
|
return Some(Ok(EventOutcome::Ok(message)));
|
||||||
|
}
|
||||||
|
1 => {
|
||||||
|
app_state.hide_dialog();
|
||||||
|
return Some(Ok(EventOutcome::Ok("Exiting dialog".to_string())));
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
app_state.hide_dialog();
|
||||||
|
return Some(Ok(EventOutcome::Ok("Unknown dialog button selected".to_string())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DialogPurpose::LoginFailed => {
|
||||||
|
match selected_index {
|
||||||
|
0 => { // "OK" button selected
|
||||||
|
app_state.hide_dialog();
|
||||||
|
return Some(Ok(EventOutcome::Ok("Login failed dialog dismissed".to_string())));
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
app_state.hide_dialog();
|
||||||
|
return Some(Ok(EventOutcome::Ok("Unknown dialog button selected".to_string())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DialogPurpose::RegisterSuccess => { // Add this arm
|
||||||
|
match selected_index {
|
||||||
|
0 => { // "OK" button for RegisterSuccess
|
||||||
|
app_state.hide_dialog();
|
||||||
|
// Go back to intro after successful registration dialog
|
||||||
|
let message = register::back_to_main(register_state, app_state).await;
|
||||||
|
return Some(Ok(EventOutcome::Ok(message)));
|
||||||
|
}
|
||||||
|
_ => { // Default for RegisterSuccess
|
||||||
|
app_state.hide_dialog();
|
||||||
|
return Some(Ok(EventOutcome::Ok("Unknown dialog button selected".to_string())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DialogPurpose::RegisterFailed => { // Add this arm
|
||||||
|
match selected_index {
|
||||||
|
0 => { // "OK" button for RegisterFailed
|
||||||
|
app_state.hide_dialog(); // Just dismiss
|
||||||
|
return Some(Ok(EventOutcome::Ok("Register failed dialog dismissed".to_string())));
|
||||||
|
}
|
||||||
|
_ => { // Default for RegisterFailed
|
||||||
|
app_state.hide_dialog();
|
||||||
|
return Some(Ok(EventOutcome::Ok("Unknown dialog button selected".to_string())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {} // Ignore other general actions when dialog is shown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If it was a key event but not handled above, consume it
|
||||||
|
Some(Ok(EventOutcome::Ok(String::new())))
|
||||||
|
} else {
|
||||||
|
// If it wasn't a key event, consume it too while dialog is active
|
||||||
|
Some(Ok(EventOutcome::Ok(String::new())))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@ use crate::state::state::AppState;
|
|||||||
use crate::state::pages::form::FormState;
|
use crate::state::pages::form::FormState;
|
||||||
use crate::state::pages::auth::AuthState;
|
use crate::state::pages::auth::AuthState;
|
||||||
use crate::state::canvas_state::CanvasState;
|
use crate::state::canvas_state::CanvasState;
|
||||||
use crate::tui::functions::{intro, admin};
|
use crate::ui::handlers::context::UiContext;
|
||||||
use crate::modes::handlers::event::EventOutcome;
|
use crate::modes::handlers::event::EventOutcome;
|
||||||
|
|
||||||
pub async fn handle_navigation_event(
|
pub async fn handle_navigation_event(
|
||||||
@@ -37,10 +37,6 @@ pub async fn handle_navigation_event(
|
|||||||
previous_option(app_state);
|
previous_option(app_state);
|
||||||
return Ok(EventOutcome::Ok(String::new()));
|
return Ok(EventOutcome::Ok(String::new()));
|
||||||
}
|
}
|
||||||
"select" => {
|
|
||||||
select(app_state);
|
|
||||||
return Ok(EventOutcome::Ok("Selected".to_string()));
|
|
||||||
}
|
|
||||||
"toggle_sidebar" => {
|
"toggle_sidebar" => {
|
||||||
toggle_sidebar(app_state);
|
toggle_sidebar(app_state);
|
||||||
return Ok(EventOutcome::Ok(format!("Sidebar {}",
|
return Ok(EventOutcome::Ok(format!("Sidebar {}",
|
||||||
@@ -59,6 +55,23 @@ pub async fn handle_navigation_event(
|
|||||||
handle_enter_command_mode(command_mode, command_input, command_message);
|
handle_enter_command_mode(command_mode, command_input, command_message);
|
||||||
return Ok(EventOutcome::Ok(String::new()));
|
return Ok(EventOutcome::Ok(String::new()));
|
||||||
}
|
}
|
||||||
|
"select" => {
|
||||||
|
let (context, index) = if app_state.ui.show_intro {
|
||||||
|
(UiContext::Intro, app_state.ui.intro_state.selected_option)
|
||||||
|
} else if app_state.ui.show_login && app_state.ui.focus_outside_canvas {
|
||||||
|
(UiContext::Login, app_state.general.selected_item)
|
||||||
|
} else if app_state.ui.show_register && app_state.ui.focus_outside_canvas {
|
||||||
|
(UiContext::Register, app_state.general.selected_item)
|
||||||
|
} else if app_state.ui.show_admin {
|
||||||
|
(UiContext::Admin, app_state.general.selected_item)
|
||||||
|
} else if app_state.ui.dialog.dialog_show {
|
||||||
|
(UiContext::Dialog, app_state.ui.dialog.dialog_active_button_index)
|
||||||
|
} else {
|
||||||
|
// Handle cases where select is pressed but no button context applies
|
||||||
|
return Ok(EventOutcome::Ok("Select (No Action)".to_string()));
|
||||||
|
};
|
||||||
|
return Ok(EventOutcome::ButtonSelected { context, index });
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -134,14 +147,6 @@ pub fn previous_option(app_state: &mut AppState) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn select(app_state: &mut AppState) {
|
|
||||||
if app_state.ui.show_intro {
|
|
||||||
intro::handle_intro_selection(app_state);
|
|
||||||
} else if app_state.ui.show_admin {
|
|
||||||
admin::handle_admin_selection(app_state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn toggle_sidebar(app_state: &mut AppState) {
|
pub fn toggle_sidebar(app_state: &mut AppState) {
|
||||||
app_state.ui.show_sidebar = !app_state.ui.show_sidebar;
|
app_state.ui.show_sidebar = !app_state.ui.show_sidebar;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,12 +8,16 @@ use crate::modes::common::commands::CommandHandler;
|
|||||||
use crate::config::binds::config::Config;
|
use crate::config::binds::config::Config;
|
||||||
use crate::state::pages::form::FormState;
|
use crate::state::pages::form::FormState;
|
||||||
use crate::state::pages::auth::AuthState;
|
use crate::state::pages::auth::AuthState;
|
||||||
|
use crate::state::pages::auth::RegisterState;
|
||||||
use crate::state::canvas_state::CanvasState;
|
use crate::state::canvas_state::CanvasState;
|
||||||
use crate::ui::handlers::rat_state::UiStateHandler;
|
use crate::ui::handlers::rat_state::UiStateHandler;
|
||||||
|
use crate::ui::handlers::context::UiContext;
|
||||||
|
use crate::tui::functions::{intro, admin};
|
||||||
|
use crate::tui::functions::common::{login, register};
|
||||||
use crate::modes::{
|
use crate::modes::{
|
||||||
common::command_mode,
|
common::command_mode,
|
||||||
canvas::{edit, read_only, common_mode},
|
canvas::{edit, read_only, common_mode},
|
||||||
general::navigation,
|
general::{navigation, dialog},
|
||||||
};
|
};
|
||||||
use crate::config::binds::key_sequences::KeySequenceTracker;
|
use crate::config::binds::key_sequences::KeySequenceTracker;
|
||||||
use crate::modes::handlers::mode_manager::{ModeManager, AppMode};
|
use crate::modes::handlers::mode_manager::{ModeManager, AppMode};
|
||||||
@@ -21,10 +25,10 @@ use crate::tui::functions::common::form::SaveOutcome;
|
|||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum EventOutcome {
|
pub enum EventOutcome {
|
||||||
Ok(String), // Normal operation, display message
|
Ok(String),
|
||||||
Exit(String), // Signal app exit, display message
|
Exit(String),
|
||||||
DataSaved(SaveOutcome, String), // Data save attempted, include outcome and message
|
DataSaved(SaveOutcome, String),
|
||||||
// Add other outcomes like QuitRequested, SaveAndQuitRequested later if needed
|
ButtonSelected { context: UiContext, index: usize },
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct EventHandler {
|
pub struct EventHandler {
|
||||||
@@ -61,6 +65,7 @@ impl EventHandler {
|
|||||||
command_handler: &mut CommandHandler,
|
command_handler: &mut CommandHandler,
|
||||||
form_state: &mut FormState,
|
form_state: &mut FormState,
|
||||||
auth_state: &mut AuthState,
|
auth_state: &mut AuthState,
|
||||||
|
register_state: &mut RegisterState,
|
||||||
app_state: &mut crate::state::state::AppState,
|
app_state: &mut crate::state::state::AppState,
|
||||||
total_count: u64,
|
total_count: u64,
|
||||||
current_position: &mut u64,
|
current_position: &mut u64,
|
||||||
@@ -68,6 +73,17 @@ impl EventHandler {
|
|||||||
let current_mode = ModeManager::derive_mode(app_state, self);
|
let current_mode = ModeManager::derive_mode(app_state, self);
|
||||||
app_state.update_mode(current_mode);
|
app_state.update_mode(current_mode);
|
||||||
|
|
||||||
|
// --- DIALOG MODALITY ---
|
||||||
|
if app_state.ui.dialog.dialog_show {
|
||||||
|
if let Some(dialog_result) = dialog::handle_dialog_event(
|
||||||
|
&event, config, app_state, auth_state, register_state, &mut self.auth_client
|
||||||
|
).await {
|
||||||
|
return dialog_result;
|
||||||
|
}
|
||||||
|
return Ok(EventOutcome::Ok(String::new()));
|
||||||
|
}
|
||||||
|
// --- END DIALOG MODALITY CHECK ---
|
||||||
|
|
||||||
if let Event::Key(key) = event {
|
if let Event::Key(key) = event {
|
||||||
let key_code = key.code;
|
let key_code = key.code;
|
||||||
let modifiers = key.modifiers;
|
let modifiers = key.modifiers;
|
||||||
@@ -81,7 +97,7 @@ impl EventHandler {
|
|||||||
|
|
||||||
match current_mode {
|
match current_mode {
|
||||||
AppMode::General => {
|
AppMode::General => {
|
||||||
return navigation::handle_navigation_event(
|
let nav_outcome = navigation::handle_navigation_event(
|
||||||
key,
|
key,
|
||||||
config,
|
config,
|
||||||
form_state,
|
form_state,
|
||||||
@@ -91,6 +107,41 @@ impl EventHandler {
|
|||||||
&mut self.command_input,
|
&mut self.command_input,
|
||||||
&mut self.command_message,
|
&mut self.command_message,
|
||||||
).await;
|
).await;
|
||||||
|
match nav_outcome {
|
||||||
|
Ok(EventOutcome::ButtonSelected { context, index }) => {
|
||||||
|
let mut message = String::from("Selected"); // Default message
|
||||||
|
match context {
|
||||||
|
UiContext::Intro => {
|
||||||
|
intro::handle_intro_selection(app_state, index); // Pass index
|
||||||
|
message = format!("Intro Option {} selected", index);
|
||||||
|
}
|
||||||
|
UiContext::Login => {
|
||||||
|
message = match index {
|
||||||
|
0 => login::save(auth_state, &mut self.auth_client, app_state).await?,
|
||||||
|
1 => login::back_to_main(auth_state, app_state).await,
|
||||||
|
_ => "Invalid Login Option".to_string(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
UiContext::Register => {
|
||||||
|
message = match index {
|
||||||
|
0 => register::save(register_state, &mut self.auth_client, app_state).await?,
|
||||||
|
1 => register::back_to_main(register_state, app_state).await,
|
||||||
|
_ => "Invalid Login Option".to_string(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
UiContext::Admin => {
|
||||||
|
// Assuming handle_admin_selection uses app_state.general.selected_item
|
||||||
|
admin::handle_admin_selection(app_state);
|
||||||
|
message = format!("Admin Option {} selected", index);
|
||||||
|
}
|
||||||
|
UiContext::Dialog => {
|
||||||
|
message = "Internal error: Unexpected dialog state".to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Ok(EventOutcome::Ok(message)); // Return Ok with message
|
||||||
|
}
|
||||||
|
other => return other, // Pass through Ok, Err, DataSaved directly
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
AppMode::ReadOnly => {
|
AppMode::ReadOnly => {
|
||||||
@@ -105,19 +156,19 @@ impl EventHandler {
|
|||||||
|
|
||||||
if config.is_enter_edit_mode_after(key_code, modifiers) &&
|
if config.is_enter_edit_mode_after(key_code, modifiers) &&
|
||||||
ModeManager::can_enter_edit_mode(current_mode) {
|
ModeManager::can_enter_edit_mode(current_mode) {
|
||||||
let current_input = if app_state.ui.show_login {
|
let current_input = if app_state.ui.show_login || app_state.ui.show_register{
|
||||||
auth_state.get_current_input()
|
auth_state.get_current_input()
|
||||||
} else {
|
} else {
|
||||||
form_state.get_current_input()
|
form_state.get_current_input()
|
||||||
};
|
};
|
||||||
let current_cursor_pos = if app_state.ui.show_login {
|
let current_cursor_pos = if app_state.ui.show_login || app_state.ui.show_register{
|
||||||
auth_state.current_cursor_pos()
|
auth_state.current_cursor_pos()
|
||||||
} else {
|
} else {
|
||||||
form_state.current_cursor_pos()
|
form_state.current_cursor_pos()
|
||||||
};
|
};
|
||||||
|
|
||||||
if !current_input.is_empty() && current_cursor_pos < current_input.len() {
|
if !current_input.is_empty() && current_cursor_pos < current_input.len() {
|
||||||
if app_state.ui.show_login {
|
if app_state.ui.show_login || app_state.ui.show_register{
|
||||||
auth_state.set_current_cursor_pos(current_cursor_pos + 1);
|
auth_state.set_current_cursor_pos(current_cursor_pos + 1);
|
||||||
self.ideal_cursor_column = auth_state.current_cursor_pos();
|
self.ideal_cursor_column = auth_state.current_cursor_pos();
|
||||||
} else {
|
} else {
|
||||||
@@ -152,6 +203,7 @@ impl EventHandler {
|
|||||||
action,
|
action,
|
||||||
form_state,
|
form_state,
|
||||||
auth_state,
|
auth_state,
|
||||||
|
register_state,
|
||||||
grpc_client,
|
grpc_client,
|
||||||
&mut self.auth_client,
|
&mut self.auth_client,
|
||||||
terminal,
|
terminal,
|
||||||
@@ -170,6 +222,7 @@ impl EventHandler {
|
|||||||
config,
|
config,
|
||||||
form_state,
|
form_state,
|
||||||
auth_state,
|
auth_state,
|
||||||
|
register_state,
|
||||||
&mut self.key_sequence_tracker,
|
&mut self.key_sequence_tracker,
|
||||||
current_position,
|
current_position,
|
||||||
total_count,
|
total_count,
|
||||||
@@ -186,7 +239,7 @@ impl EventHandler {
|
|||||||
self.is_edit_mode = false;
|
self.is_edit_mode = false;
|
||||||
self.edit_mode_cooldown = true;
|
self.edit_mode_cooldown = true;
|
||||||
|
|
||||||
let has_changes = if app_state.ui.show_login {
|
let has_changes = if app_state.ui.show_login || app_state.ui.show_register{
|
||||||
auth_state.has_unsaved_changes()
|
auth_state.has_unsaved_changes()
|
||||||
} else {
|
} else {
|
||||||
form_state.has_unsaved_changes()
|
form_state.has_unsaved_changes()
|
||||||
@@ -200,12 +253,12 @@ impl EventHandler {
|
|||||||
|
|
||||||
terminal.set_cursor_style(SetCursorStyle::SteadyBlock)?;
|
terminal.set_cursor_style(SetCursorStyle::SteadyBlock)?;
|
||||||
|
|
||||||
let current_input = if app_state.ui.show_login {
|
let current_input = if app_state.ui.show_login || app_state.ui.show_register{
|
||||||
auth_state.get_current_input()
|
auth_state.get_current_input()
|
||||||
} else {
|
} else {
|
||||||
form_state.get_current_input()
|
form_state.get_current_input()
|
||||||
};
|
};
|
||||||
let current_cursor_pos = if app_state.ui.show_login {
|
let current_cursor_pos = if app_state.ui.show_login || app_state.ui.show_register{
|
||||||
auth_state.current_cursor_pos()
|
auth_state.current_cursor_pos()
|
||||||
} else {
|
} else {
|
||||||
form_state.current_cursor_pos()
|
form_state.current_cursor_pos()
|
||||||
@@ -213,7 +266,7 @@ impl EventHandler {
|
|||||||
|
|
||||||
if !current_input.is_empty() && current_cursor_pos >= current_input.len() {
|
if !current_input.is_empty() && current_cursor_pos >= current_input.len() {
|
||||||
let new_pos = current_input.len() - 1;
|
let new_pos = current_input.len() - 1;
|
||||||
if app_state.ui.show_login {
|
if app_state.ui.show_login || app_state.ui.show_register{
|
||||||
auth_state.set_current_cursor_pos(new_pos);
|
auth_state.set_current_cursor_pos(new_pos);
|
||||||
self.ideal_cursor_column = auth_state.current_cursor_pos();
|
self.ideal_cursor_column = auth_state.current_cursor_pos();
|
||||||
} else {
|
} else {
|
||||||
@@ -235,6 +288,7 @@ impl EventHandler {
|
|||||||
action,
|
action,
|
||||||
form_state,
|
form_state,
|
||||||
auth_state,
|
auth_state,
|
||||||
|
register_state,
|
||||||
grpc_client,
|
grpc_client,
|
||||||
&mut self.auth_client,
|
&mut self.auth_client,
|
||||||
terminal,
|
terminal,
|
||||||
@@ -248,16 +302,17 @@ impl EventHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let message = edit::handle_edit_event(
|
let message = edit::handle_edit_event(
|
||||||
app_state.ui.show_login,
|
|
||||||
key,
|
key,
|
||||||
config,
|
config,
|
||||||
form_state,
|
form_state,
|
||||||
auth_state,
|
auth_state,
|
||||||
|
register_state,
|
||||||
&mut self.ideal_cursor_column,
|
&mut self.ideal_cursor_column,
|
||||||
&mut self.command_message,
|
&mut self.command_message,
|
||||||
current_position,
|
current_position,
|
||||||
total_count,
|
total_count,
|
||||||
grpc_client,
|
grpc_client,
|
||||||
|
app_state,
|
||||||
).await?;
|
).await?;
|
||||||
|
|
||||||
self.key_sequence_tracker.reset();
|
self.key_sequence_tracker.reset();
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ impl ModeManager {
|
|||||||
return AppMode::General;
|
return AppMode::General;
|
||||||
}
|
}
|
||||||
|
|
||||||
if app_state.ui.show_login {
|
if app_state.ui.show_login || app_state.ui.show_register {
|
||||||
if event_handler.is_edit_mode {
|
if event_handler.is_edit_mode {
|
||||||
AppMode::Edit
|
AppMode::Edit
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
use tonic::transport::Channel;
|
use tonic::transport::Channel;
|
||||||
use common::proto::multieko2::auth::{
|
use common::proto::multieko2::auth::{
|
||||||
auth_service_client::AuthServiceClient,
|
auth_service_client::AuthServiceClient,
|
||||||
LoginRequest, LoginResponse
|
LoginRequest, LoginResponse,
|
||||||
|
RegisterRequest, AuthResponse,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct AuthClient {
|
pub struct AuthClient {
|
||||||
@@ -15,9 +16,28 @@ impl AuthClient {
|
|||||||
Ok(Self { client })
|
Ok(Self { client })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Login user via gRPC.
|
||||||
pub async fn login(&mut self, identifier: String, password: String) -> Result<LoginResponse, Box<dyn std::error::Error>> {
|
pub async fn login(&mut self, identifier: String, password: String) -> Result<LoginResponse, Box<dyn std::error::Error>> {
|
||||||
let request = tonic::Request::new(LoginRequest { identifier, password });
|
let request = tonic::Request::new(LoginRequest { identifier, password });
|
||||||
let response = self.client.login(request).await?.into_inner();
|
let response = self.client.login(request).await?.into_inner();
|
||||||
Ok(response)
|
Ok(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Registers a new user via gRPC.
|
||||||
|
pub async fn register(
|
||||||
|
&mut self,
|
||||||
|
username: String,
|
||||||
|
email: String,
|
||||||
|
password: Option<String>, // Use Option for optional fields
|
||||||
|
password_confirmation: Option<String>, // Use Option for optional fields
|
||||||
|
) -> Result<AuthResponse, Box<dyn std::error::Error>> {
|
||||||
|
let request = tonic::Request::new(RegisterRequest {
|
||||||
|
username,
|
||||||
|
email,
|
||||||
|
password: password.unwrap_or_default(), // Send empty string if None
|
||||||
|
password_confirmation: password_confirmation.unwrap_or_default(), // Send empty string if None
|
||||||
|
});
|
||||||
|
let response = self.client.register(request).await?.into_inner();
|
||||||
|
Ok(response)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,18 @@ pub struct AuthState {
|
|||||||
pub has_unsaved_changes: bool,
|
pub has_unsaved_changes: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Clone)] // Add Clone derive
|
||||||
|
pub struct RegisterState {
|
||||||
|
pub username: String,
|
||||||
|
pub email: String,
|
||||||
|
pub password: String,
|
||||||
|
pub password_confirmation: String,
|
||||||
|
pub error_message: Option<String>,
|
||||||
|
pub current_field: usize,
|
||||||
|
pub current_cursor_pos: usize,
|
||||||
|
pub has_unsaved_changes: bool,
|
||||||
|
}
|
||||||
|
|
||||||
impl AuthState {
|
impl AuthState {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
@@ -32,6 +44,21 @@ impl AuthState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl RegisterState {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
username: String::new(),
|
||||||
|
email: String::new(),
|
||||||
|
password: String::new(),
|
||||||
|
password_confirmation: String::new(),
|
||||||
|
error_message: None,
|
||||||
|
current_field: 0,
|
||||||
|
current_cursor_pos: 0,
|
||||||
|
has_unsaved_changes: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl CanvasState for AuthState {
|
impl CanvasState for AuthState {
|
||||||
fn current_field(&self) -> usize {
|
fn current_field(&self) -> usize {
|
||||||
self.current_field
|
self.current_field
|
||||||
@@ -102,3 +129,91 @@ impl CanvasState for AuthState {
|
|||||||
self.has_unsaved_changes = changed;
|
self.has_unsaved_changes = changed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl CanvasState for RegisterState {
|
||||||
|
fn current_field(&self) -> usize {
|
||||||
|
self.current_field
|
||||||
|
}
|
||||||
|
|
||||||
|
fn current_cursor_pos(&self) -> usize {
|
||||||
|
let len = match self.current_field {
|
||||||
|
0 => self.username.len(),
|
||||||
|
1 => self.email.len(),
|
||||||
|
2 => self.password.len(),
|
||||||
|
3 => self.password_confirmation.len(),
|
||||||
|
_ => 0,
|
||||||
|
};
|
||||||
|
self.current_cursor_pos.min(len)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_unsaved_changes(&self) -> bool {
|
||||||
|
self.has_unsaved_changes
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inputs(&self) -> Vec<&String> {
|
||||||
|
vec![
|
||||||
|
&self.username,
|
||||||
|
&self.email,
|
||||||
|
&self.password,
|
||||||
|
&self.password_confirmation,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_current_input(&self) -> &str {
|
||||||
|
match self.current_field {
|
||||||
|
0 => &self.username,
|
||||||
|
1 => &self.email,
|
||||||
|
2 => &self.password,
|
||||||
|
3 => &self.password_confirmation,
|
||||||
|
_ => "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
_ => panic!("Invalid current_field index in RegisterState"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fields(&self) -> Vec<&str> {
|
||||||
|
vec![
|
||||||
|
"Username",
|
||||||
|
"Email (Optional)",
|
||||||
|
"Password (Optional)",
|
||||||
|
"Confirm Password",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_current_field(&mut self, index: usize) {
|
||||||
|
if index < 4 { // RegisterState has 4 fields
|
||||||
|
self.current_field = index;
|
||||||
|
let len = match self.current_field {
|
||||||
|
0 => self.username.len(),
|
||||||
|
1 => self.email.len(),
|
||||||
|
2 => self.password.len(),
|
||||||
|
3 => self.password_confirmation.len(),
|
||||||
|
_ => 0,
|
||||||
|
};
|
||||||
|
self.current_cursor_pos = self.current_cursor_pos.min(len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_current_cursor_pos(&mut self, pos: usize) {
|
||||||
|
let len = match self.current_field {
|
||||||
|
0 => self.username.len(),
|
||||||
|
1 => self.email.len(),
|
||||||
|
2 => self.password.len(),
|
||||||
|
3 => self.password_confirmation.len(),
|
||||||
|
_ => 0,
|
||||||
|
};
|
||||||
|
self.current_cursor_pos = pos.min(len);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_has_unsaved_changes(&mut self, changed: bool) {
|
||||||
|
self.has_unsaved_changes = changed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use std::env;
|
|||||||
use common::proto::multieko2::table_definition::ProfileTreeResponse;
|
use common::proto::multieko2::table_definition::ProfileTreeResponse;
|
||||||
use crate::components::IntroState;
|
use crate::components::IntroState;
|
||||||
use crate::modes::handlers::mode_manager::AppMode;
|
use crate::modes::handlers::mode_manager::AppMode;
|
||||||
|
use crate::ui::handlers::context::DialogPurpose;
|
||||||
|
|
||||||
pub struct DialogState {
|
pub struct DialogState {
|
||||||
pub dialog_show: bool,
|
pub dialog_show: bool,
|
||||||
@@ -11,6 +12,7 @@ pub struct DialogState {
|
|||||||
pub dialog_message: String,
|
pub dialog_message: String,
|
||||||
pub dialog_buttons: Vec<String>,
|
pub dialog_buttons: Vec<String>,
|
||||||
pub dialog_active_button_index: usize,
|
pub dialog_active_button_index: usize,
|
||||||
|
pub purpose: Option<DialogPurpose>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct UiState {
|
pub struct UiState {
|
||||||
@@ -19,9 +21,10 @@ pub struct UiState {
|
|||||||
pub show_admin: bool,
|
pub show_admin: bool,
|
||||||
pub show_form: bool,
|
pub show_form: bool,
|
||||||
pub show_login: bool,
|
pub show_login: bool,
|
||||||
|
pub show_register: bool,
|
||||||
pub intro_state: IntroState,
|
pub intro_state: IntroState,
|
||||||
pub focus_outside_canvas: bool,
|
pub focus_outside_canvas: bool,
|
||||||
pub dialog: DialogState, // Add dialog state here
|
pub dialog: DialogState,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct GeneralState {
|
pub struct GeneralState {
|
||||||
@@ -84,21 +87,26 @@ impl AppState {
|
|||||||
title: &str,
|
title: &str,
|
||||||
message: &str,
|
message: &str,
|
||||||
buttons: Vec<String>,
|
buttons: Vec<String>,
|
||||||
|
purpose: DialogPurpose,
|
||||||
) {
|
) {
|
||||||
self.ui.dialog.dialog_title = title.to_string();
|
self.ui.dialog.dialog_title = title.to_string();
|
||||||
self.ui.dialog.dialog_message = message.to_string();
|
self.ui.dialog.dialog_message = message.to_string();
|
||||||
self.ui.dialog.dialog_buttons = buttons;
|
self.ui.dialog.dialog_buttons = buttons;
|
||||||
self.ui.dialog.dialog_active_button_index = 0; // Default to first button
|
self.ui.dialog.dialog_active_button_index = 0;
|
||||||
self.ui.dialog.dialog_show = true; // Use new name
|
self.ui.dialog.purpose = Some(purpose);
|
||||||
|
self.ui.dialog.dialog_show = true;
|
||||||
|
self.ui.focus_outside_canvas = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Hides the dialog and clears its content.
|
/// Hides the dialog and clears its content.
|
||||||
pub fn hide_dialog(&mut self) {
|
pub fn hide_dialog(&mut self) {
|
||||||
self.ui.dialog.dialog_show = false; // Use new name
|
self.ui.dialog.dialog_show = false;
|
||||||
self.ui.dialog.dialog_title.clear();
|
self.ui.dialog.dialog_title.clear();
|
||||||
self.ui.dialog.dialog_message.clear();
|
self.ui.dialog.dialog_message.clear();
|
||||||
self.ui.dialog.dialog_buttons.clear();
|
self.ui.dialog.dialog_buttons.clear();
|
||||||
self.ui.dialog.dialog_active_button_index = 0;
|
self.ui.dialog.dialog_active_button_index = 0;
|
||||||
|
self.ui.dialog.purpose = None;
|
||||||
|
self.ui.focus_outside_canvas = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the active button index, wrapping around if necessary.
|
/// Sets the active button index, wrapping around if necessary.
|
||||||
@@ -137,6 +145,7 @@ impl Default for UiState {
|
|||||||
show_admin: false,
|
show_admin: false,
|
||||||
show_form: false,
|
show_form: false,
|
||||||
show_login: false,
|
show_login: false,
|
||||||
|
show_register: false,
|
||||||
intro_state: IntroState::new(),
|
intro_state: IntroState::new(),
|
||||||
focus_outside_canvas: false,
|
focus_outside_canvas: false,
|
||||||
dialog: DialogState::default(),
|
dialog: DialogState::default(),
|
||||||
@@ -148,11 +157,12 @@ impl Default for UiState {
|
|||||||
impl Default for DialogState {
|
impl Default for DialogState {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
dialog_show: false, // Use new name
|
dialog_show: false,
|
||||||
dialog_title: String::new(), // Use new name
|
dialog_title: String::new(),
|
||||||
dialog_message: String::new(), // Use new name
|
dialog_message: String::new(),
|
||||||
dialog_buttons: Vec::new(), // Use new name
|
dialog_buttons: Vec::new(),
|
||||||
dialog_active_button_index: 0, // Use new name
|
dialog_active_button_index: 0,
|
||||||
|
purpose: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,4 +2,4 @@
|
|||||||
|
|
||||||
pub mod form;
|
pub mod form;
|
||||||
pub mod login;
|
pub mod login;
|
||||||
|
pub mod register;
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
// src/tui/functions/common/login.rs
|
// src/tui/functions/common/login.rs
|
||||||
|
|
||||||
use crate::services::auth::AuthClient;
|
use crate::services::auth::AuthClient;
|
||||||
use crate::state::pages::auth::AuthState;
|
use crate::state::pages::auth::AuthState;
|
||||||
use crate::state::state::AppState;
|
use crate::state::state::AppState;
|
||||||
use crate::state::canvas_state::CanvasState;
|
use crate::state::canvas_state::CanvasState;
|
||||||
// Remove unused import if CanvasState is not directly used here
|
use crate::ui::handlers::context::DialogPurpose;
|
||||||
// use crate::state::canvas_state::CanvasState;
|
|
||||||
|
|
||||||
/// Attempts to log the user in using the provided credentials via gRPC.
|
/// Attempts to log the user in using the provided credentials via gRPC.
|
||||||
/// Updates AuthState and AppState on success or failure.
|
/// Updates AuthState and AppState on success or failure.
|
||||||
@@ -51,6 +51,7 @@ pub async fn save(
|
|||||||
"Login Success",
|
"Login Success",
|
||||||
&success_message,
|
&success_message,
|
||||||
vec!["Menu".to_string(), "Exit".to_string()],
|
vec!["Menu".to_string(), "Exit".to_string()],
|
||||||
|
DialogPurpose::LoginSuccess,
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok("Login successful, details shown in dialog.".to_string())
|
Ok("Login successful, details shown in dialog.".to_string())
|
||||||
@@ -62,7 +63,8 @@ pub async fn save(
|
|||||||
app_state.show_dialog(
|
app_state.show_dialog(
|
||||||
"Login Failed",
|
"Login Failed",
|
||||||
&error_message,
|
&error_message,
|
||||||
vec!["OK".to_string()], // Pass buttons here
|
vec!["OK".to_string()],
|
||||||
|
DialogPurpose::LoginFailed,
|
||||||
);
|
);
|
||||||
// REMOVE these lines:
|
// REMOVE these lines:
|
||||||
// app_state.ui.dialog.dialog_title = "Login Failed".to_string();
|
// app_state.ui.dialog.dialog_title = "Login Failed".to_string();
|
||||||
@@ -88,13 +90,29 @@ pub async fn revert(
|
|||||||
auth_state.error_message = None;
|
auth_state.error_message = None;
|
||||||
auth_state.set_has_unsaved_changes(false);
|
auth_state.set_has_unsaved_changes(false);
|
||||||
|
|
||||||
// Ensure dialog is hidden if revert is called
|
|
||||||
// _app_state.hide_dialog(); // Uncomment if needed
|
|
||||||
|
|
||||||
// Navigation logic (currently disabled in original code)
|
|
||||||
// _app_state.ui.show_login = false;
|
|
||||||
// _app_state.ui.show_intro = true;
|
|
||||||
|
|
||||||
"Login reverted".to_string()
|
"Login reverted".to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn back_to_main(
|
||||||
|
auth_state: &mut AuthState,
|
||||||
|
app_state: &mut AppState,
|
||||||
|
) -> String {
|
||||||
|
// Clear the input fields
|
||||||
|
auth_state.username.clear();
|
||||||
|
auth_state.password.clear();
|
||||||
|
auth_state.error_message = None;
|
||||||
|
auth_state.set_has_unsaved_changes(false);
|
||||||
|
|
||||||
|
// Ensure dialog is hidden if revert is called
|
||||||
|
app_state.hide_dialog(); // Uncomment if needed
|
||||||
|
|
||||||
|
// Navigation logic (currently disabled in original code)
|
||||||
|
app_state.ui.show_login = false;
|
||||||
|
app_state.ui.show_intro = true;
|
||||||
|
|
||||||
|
// Reset focus state
|
||||||
|
app_state.ui.focus_outside_canvas = false;
|
||||||
|
app_state.general.selected_item = 0;
|
||||||
|
|
||||||
|
"Returned to main menu".to_string()
|
||||||
|
}
|
||||||
|
|||||||
148
client/src/tui/functions/common/register.rs
Normal file
148
client/src/tui/functions/common/register.rs
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
// src/tui/functions/common/register.rs
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
services::auth::AuthClient,
|
||||||
|
state::{
|
||||||
|
pages::auth::RegisterState,
|
||||||
|
state::AppState,
|
||||||
|
canvas_state::CanvasState,
|
||||||
|
},
|
||||||
|
ui::handlers::context::DialogPurpose,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Attempts to register the user using the provided details via gRPC.
|
||||||
|
/// Updates RegisterState and AppState on success or failure.
|
||||||
|
pub async fn save(
|
||||||
|
register_state: &mut RegisterState,
|
||||||
|
auth_client: &mut AuthClient,
|
||||||
|
app_state: &mut AppState,
|
||||||
|
) -> Result<String, Box<dyn std::error::Error>> {
|
||||||
|
let username = register_state.username.clone();
|
||||||
|
let email = register_state.email.clone();
|
||||||
|
// Handle optional passwords: send None if empty, Some(value) otherwise
|
||||||
|
let password = if register_state.password.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(register_state.password.clone())
|
||||||
|
};
|
||||||
|
let password_confirmation = if register_state.password_confirmation.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(register_state.password_confirmation.clone())
|
||||||
|
};
|
||||||
|
|
||||||
|
// Basic client-side validation (example)
|
||||||
|
if username.is_empty() {
|
||||||
|
app_state.show_dialog(
|
||||||
|
"Registration Failed",
|
||||||
|
"Username cannot be empty.",
|
||||||
|
vec!["OK".to_string()],
|
||||||
|
DialogPurpose::RegisterFailed,
|
||||||
|
);
|
||||||
|
register_state.error_message = Some("Username cannot be empty.".to_string());
|
||||||
|
return Ok("Registration failed: Username cannot be empty.".to_string());
|
||||||
|
}
|
||||||
|
if password.is_some() && password != password_confirmation {
|
||||||
|
app_state.show_dialog(
|
||||||
|
"Registration Failed",
|
||||||
|
"Passwords do not match.",
|
||||||
|
vec!["OK".to_string()],
|
||||||
|
DialogPurpose::RegisterFailed,
|
||||||
|
);
|
||||||
|
register_state.error_message = Some("Passwords do not match.".to_string());
|
||||||
|
return Ok("Registration failed: Passwords do not match.".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Clear previous error/dialog state before attempting
|
||||||
|
register_state.error_message = None;
|
||||||
|
app_state.hide_dialog();
|
||||||
|
|
||||||
|
// Call the gRPC register method
|
||||||
|
match auth_client.register(username, email, password, password_confirmation).await {
|
||||||
|
Ok(response) => {
|
||||||
|
// Clear fields on success? Optional, maybe wait for dialog confirmation.
|
||||||
|
// register_state.username.clear();
|
||||||
|
// register_state.email.clear();
|
||||||
|
// register_state.password.clear();
|
||||||
|
// register_state.password_confirmation.clear();
|
||||||
|
register_state.set_has_unsaved_changes(false);
|
||||||
|
|
||||||
|
let success_message = format!(
|
||||||
|
"Registration Successful!\n\n\
|
||||||
|
User ID: {}\n\
|
||||||
|
Username: {}\n\
|
||||||
|
Email: {}\n\
|
||||||
|
Role: {}",
|
||||||
|
response.id,
|
||||||
|
response.username,
|
||||||
|
response.email,
|
||||||
|
response.role
|
||||||
|
);
|
||||||
|
|
||||||
|
// Show success dialog
|
||||||
|
app_state.show_dialog(
|
||||||
|
"Registration Success",
|
||||||
|
&success_message,
|
||||||
|
vec!["OK".to_string()], // Simple OK for now
|
||||||
|
DialogPurpose::RegisterSuccess,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok("Registration successful, details shown in dialog.".to_string())
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
let error_message = format!("{}", e);
|
||||||
|
register_state.error_message = Some(error_message.clone());
|
||||||
|
register_state.set_has_unsaved_changes(true); // Keep changes on error
|
||||||
|
|
||||||
|
// Show error dialog
|
||||||
|
app_state.show_dialog(
|
||||||
|
"Registration Failed",
|
||||||
|
&error_message,
|
||||||
|
vec!["OK".to_string()],
|
||||||
|
DialogPurpose::RegisterFailed,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(format!("Registration failed: {}", error_message))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clears the registration form fields.
|
||||||
|
pub async fn revert(
|
||||||
|
register_state: &mut RegisterState,
|
||||||
|
_app_state: &mut AppState, // Keep signature consistent if needed elsewhere
|
||||||
|
) -> String {
|
||||||
|
register_state.username.clear();
|
||||||
|
register_state.email.clear();
|
||||||
|
register_state.password.clear();
|
||||||
|
register_state.password_confirmation.clear();
|
||||||
|
register_state.error_message = None;
|
||||||
|
register_state.set_has_unsaved_changes(false);
|
||||||
|
register_state.current_field = 0; // Reset focus to first field
|
||||||
|
register_state.current_cursor_pos = 0;
|
||||||
|
"Registration form cleared".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clears the form and returns to the intro screen.
|
||||||
|
pub async fn back_to_main(
|
||||||
|
register_state: &mut RegisterState,
|
||||||
|
app_state: &mut AppState,
|
||||||
|
) -> String {
|
||||||
|
// Clear fields first
|
||||||
|
let _ = revert(register_state, app_state).await;
|
||||||
|
|
||||||
|
// Ensure dialog is hidden
|
||||||
|
app_state.hide_dialog();
|
||||||
|
|
||||||
|
// Navigation logic
|
||||||
|
app_state.ui.show_register = false;
|
||||||
|
app_state.ui.show_intro = true;
|
||||||
|
|
||||||
|
// Reset focus state
|
||||||
|
app_state.ui.focus_outside_canvas = false;
|
||||||
|
app_state.general.selected_item = 0; // Reset intro selection
|
||||||
|
|
||||||
|
"Returned to main menu".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::state::state::AppState;
|
use crate::state::state::AppState;
|
||||||
|
|
||||||
pub fn handle_intro_selection(app_state: &mut AppState) {
|
pub fn handle_intro_selection(app_state: &mut AppState, index: usize) { // Add index parameter
|
||||||
match app_state.ui.intro_state.selected_option {
|
match index { // Use index directly
|
||||||
0 => { // Continue
|
0 => { // Continue
|
||||||
app_state.ui.show_form = true;
|
app_state.ui.show_form = true;
|
||||||
app_state.ui.show_admin = false;
|
app_state.ui.show_admin = false;
|
||||||
@@ -17,7 +17,14 @@ pub fn handle_intro_selection(app_state: &mut AppState) {
|
|||||||
app_state.ui.show_admin = false;
|
app_state.ui.show_admin = false;
|
||||||
app_state.ui.show_login = true;
|
app_state.ui.show_login = true;
|
||||||
}
|
}
|
||||||
|
3 => { // Register
|
||||||
|
app_state.ui.show_intro = false;
|
||||||
|
app_state.ui.show_register = true;
|
||||||
|
app_state.ui.focus_outside_canvas = false;
|
||||||
|
app_state.general.selected_item = 0;
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
app_state.ui.show_intro = false;
|
app_state.ui.show_intro = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
pub mod ui;
|
pub mod ui;
|
||||||
pub mod render;
|
pub mod render;
|
||||||
pub mod rat_state;
|
pub mod rat_state;
|
||||||
|
pub mod context;
|
||||||
|
|
||||||
pub use ui::run_ui;
|
pub use ui::run_ui;
|
||||||
pub use rat_state::*;
|
pub use rat_state::*;
|
||||||
|
pub use context::*;
|
||||||
|
|||||||
21
client/src/ui/handlers/context.rs
Normal file
21
client/src/ui/handlers/context.rs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
// src/ui/handlers/context.rs
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum UiContext {
|
||||||
|
Intro,
|
||||||
|
Login,
|
||||||
|
Register,
|
||||||
|
Admin,
|
||||||
|
Dialog,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum DialogPurpose {
|
||||||
|
LoginSuccess,
|
||||||
|
LoginFailed,
|
||||||
|
RegisterSuccess,
|
||||||
|
RegisterFailed,
|
||||||
|
// TODO in the future:
|
||||||
|
// ConfirmQuit,
|
||||||
|
}
|
||||||
|
|
||||||
@@ -7,19 +7,21 @@ use crate::components::{
|
|||||||
handlers::sidebar::{self, calculate_sidebar_layout},
|
handlers::sidebar::{self, calculate_sidebar_layout},
|
||||||
form::form::render_form,
|
form::form::render_form,
|
||||||
admin::{admin_panel::AdminPanelState},
|
admin::{admin_panel::AdminPanelState},
|
||||||
auth::login::render_login,
|
auth::{login::render_login, register::render_register},
|
||||||
};
|
};
|
||||||
use crate::config::colors::themes::Theme;
|
use crate::config::colors::themes::Theme;
|
||||||
use ratatui::layout::{Constraint, Direction, Layout};
|
use ratatui::layout::{Constraint, Direction, Layout};
|
||||||
use ratatui::Frame;
|
use ratatui::Frame;
|
||||||
use crate::state::pages::form::FormState;
|
use crate::state::pages::form::FormState;
|
||||||
use crate::state::pages::auth::AuthState;
|
use crate::state::pages::auth::AuthState;
|
||||||
|
use crate::state::pages::auth::RegisterState;
|
||||||
use crate::state::state::AppState;
|
use crate::state::state::AppState;
|
||||||
|
|
||||||
pub fn render_ui(
|
pub fn render_ui(
|
||||||
f: &mut Frame,
|
f: &mut Frame,
|
||||||
form_state: &mut FormState,
|
form_state: &mut FormState,
|
||||||
auth_state: &mut AuthState,
|
auth_state: &mut AuthState,
|
||||||
|
register_state: &RegisterState,
|
||||||
theme: &Theme,
|
theme: &Theme,
|
||||||
is_edit_mode: bool,
|
is_edit_mode: bool,
|
||||||
total_count: u64,
|
total_count: u64,
|
||||||
@@ -29,7 +31,6 @@ pub fn render_ui(
|
|||||||
command_mode: bool,
|
command_mode: bool,
|
||||||
command_message: &str,
|
command_message: &str,
|
||||||
app_state: &AppState,
|
app_state: &AppState,
|
||||||
// intro_state parameter removed
|
|
||||||
) {
|
) {
|
||||||
render_background(f, f.area(), theme);
|
render_background(f, f.area(), theme);
|
||||||
|
|
||||||
@@ -44,15 +45,23 @@ pub fn render_ui(
|
|||||||
|
|
||||||
let main_content_area = root[0];
|
let main_content_area = root[0];
|
||||||
if app_state.ui.show_intro {
|
if app_state.ui.show_intro {
|
||||||
// Use app_state's intro_state directly
|
|
||||||
app_state.ui.intro_state.render(f, main_content_area, theme);
|
app_state.ui.intro_state.render(f, main_content_area, theme);
|
||||||
|
} else if app_state.ui.show_register {
|
||||||
|
render_register(
|
||||||
|
f,
|
||||||
|
main_content_area,
|
||||||
|
theme,
|
||||||
|
register_state,
|
||||||
|
app_state,
|
||||||
|
register_state.current_field < 4
|
||||||
|
);
|
||||||
}else if app_state.ui.show_login {
|
}else if app_state.ui.show_login {
|
||||||
render_login(
|
render_login(
|
||||||
f,
|
f,
|
||||||
main_content_area,
|
main_content_area,
|
||||||
theme,
|
theme,
|
||||||
auth_state,
|
auth_state,
|
||||||
app_state, // Add AppState reference here
|
app_state,
|
||||||
auth_state.current_field < 2
|
auth_state.current_field < 2
|
||||||
);
|
);
|
||||||
} else if app_state.ui.show_admin {
|
} else if app_state.ui.show_admin {
|
||||||
|
|||||||
@@ -10,8 +10,9 @@ use crate::services::ui_service::UiService;
|
|||||||
use crate::state::canvas_state::CanvasState;
|
use crate::state::canvas_state::CanvasState;
|
||||||
use crate::state::pages::auth::AuthState;
|
use crate::state::pages::auth::AuthState;
|
||||||
use crate::state::pages::form::FormState;
|
use crate::state::pages::form::FormState;
|
||||||
|
use crate::state::pages::auth::RegisterState;
|
||||||
use crate::state::state::AppState;
|
use crate::state::state::AppState;
|
||||||
use crate::tui::functions::common::form::SaveOutcome; // Import SaveOutcome
|
// Import SaveOutcome
|
||||||
use crate::tui::terminal::{EventReader, TerminalCore};
|
use crate::tui::terminal::{EventReader, TerminalCore};
|
||||||
use crate::ui::handlers::render::render_ui;
|
use crate::ui::handlers::render::render_ui;
|
||||||
use crossterm::cursor::SetCursorStyle;
|
use crossterm::cursor::SetCursorStyle;
|
||||||
@@ -22,7 +23,8 @@ pub async fn run_ui() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
let mut grpc_client = GrpcClient::new().await?;
|
let mut grpc_client = GrpcClient::new().await?;
|
||||||
let mut command_handler = CommandHandler::new();
|
let mut command_handler = CommandHandler::new();
|
||||||
let theme = Theme::from_str(&config.colors.theme);
|
let theme = Theme::from_str(&config.colors.theme);
|
||||||
let mut auth_state = AuthState::default(); // The single source of truth for AuthState
|
let mut auth_state = AuthState::default();
|
||||||
|
let mut register_state = RegisterState::default();
|
||||||
|
|
||||||
// Initialize app_state first
|
// Initialize app_state first
|
||||||
let mut app_state = AppState::new()?;
|
let mut app_state = AppState::new()?;
|
||||||
@@ -52,9 +54,10 @@ pub async fn run_ui() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
render_ui(
|
render_ui(
|
||||||
f,
|
f,
|
||||||
&mut form_state,
|
&mut form_state,
|
||||||
&mut auth_state, // Pass the single AuthState instance
|
&mut auth_state,
|
||||||
|
®ister_state,
|
||||||
&theme,
|
&theme,
|
||||||
is_edit_mode, // Use determined edit mode
|
is_edit_mode,
|
||||||
app_state.total_count,
|
app_state.total_count,
|
||||||
app_state.current_position,
|
app_state.current_position,
|
||||||
&app_state.current_dir,
|
&app_state.current_dir,
|
||||||
@@ -110,6 +113,7 @@ pub async fn run_ui() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
&mut command_handler,
|
&mut command_handler,
|
||||||
&mut form_state,
|
&mut form_state,
|
||||||
&mut auth_state,
|
&mut auth_state,
|
||||||
|
&mut register_state,
|
||||||
&mut app_state,
|
&mut app_state,
|
||||||
total_count, // Pass the count *before* potential save
|
total_count, // Pass the count *before* potential save
|
||||||
&mut current_position,
|
&mut current_position,
|
||||||
@@ -152,6 +156,9 @@ pub async fn run_ui() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
}
|
}
|
||||||
// No count update needed for UpdatedExisting or NoChange
|
// No count update needed for UpdatedExisting or NoChange
|
||||||
}
|
}
|
||||||
|
EventOutcome::ButtonSelected { context, index } => {
|
||||||
|
event_handler.command_message = "Internal error: Unexpected button state".to_string();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
// Handle errors from handle_event, e.g., log or display
|
// Handle errors from handle_event, e.g., log or display
|
||||||
@@ -236,8 +243,17 @@ pub async fn run_ui() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
form_state.current_cursor_pos =
|
form_state.current_cursor_pos =
|
||||||
event_handler.ideal_cursor_column.min(max_cursor_pos);
|
event_handler.ideal_cursor_column.min(max_cursor_pos);
|
||||||
}
|
}
|
||||||
|
} else if app_state.ui.show_register {
|
||||||
|
if !event_handler.is_edit_mode {
|
||||||
|
let current_input = register_state.get_current_input();
|
||||||
|
let max_cursor_pos = if !current_input.is_empty() {
|
||||||
|
current_input.len() - 1
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
register_state.current_cursor_pos = event_handler.ideal_cursor_column.min(max_cursor_pos);
|
||||||
|
}
|
||||||
} else if app_state.ui.show_login {
|
} else if app_state.ui.show_login {
|
||||||
// Handle cursor updates for AuthState if needed, similar to FormState
|
|
||||||
if !event_handler.is_edit_mode {
|
if !event_handler.is_edit_mode {
|
||||||
let current_input = auth_state.get_current_input();
|
let current_input = auth_state.get_current_input();
|
||||||
let max_cursor_pos = if !current_input.is_empty() {
|
let max_cursor_pos = if !current_input.is_empty() {
|
||||||
|
|||||||
Reference in New Issue
Block a user