login waiting dialog works, THIS COMMIT NEEDS TO BE REFACTORED
This commit is contained in:
5
Cargo.lock
generated
5
Cargo.lock
generated
@@ -429,6 +429,7 @@ dependencies = [
|
|||||||
"dirs 6.0.0",
|
"dirs 6.0.0",
|
||||||
"dotenvy",
|
"dotenvy",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
|
"log",
|
||||||
"prost",
|
"prost",
|
||||||
"ratatui",
|
"ratatui",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -1656,9 +1657,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.26"
|
version = "0.4.27"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e"
|
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lru"
|
name = "lru"
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ crossterm = "0.28.1"
|
|||||||
dirs = "6.0.0"
|
dirs = "6.0.0"
|
||||||
dotenvy = "0.15.7"
|
dotenvy = "0.15.7"
|
||||||
lazy_static = "1.5.0"
|
lazy_static = "1.5.0"
|
||||||
|
log = "0.4.27"
|
||||||
prost = "0.13.5"
|
prost = "0.13.5"
|
||||||
ratatui = "0.29.0"
|
ratatui = "0.29.0"
|
||||||
serde = { version = "1.0.218", features = ["derive"] }
|
serde = { version = "1.0.218", features = ["derive"] }
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ pub fn render_add_table(
|
|||||||
f: &mut Frame,
|
f: &mut Frame,
|
||||||
area: Rect,
|
area: Rect,
|
||||||
theme: &Theme,
|
theme: &Theme,
|
||||||
_app_state: &AppState, // Currently unused, might be needed later
|
app_state: &AppState, // Currently unused, might be needed later
|
||||||
add_table_state: &mut AddTableState,
|
add_table_state: &mut AddTableState,
|
||||||
is_edit_mode: bool, // Determines if canvas inputs are in edit mode
|
is_edit_mode: bool, // Determines if canvas inputs are in edit mode
|
||||||
highlight_state: &HighlightState, // For text highlighting in canvas
|
highlight_state: &HighlightState, // For text highlighting in canvas
|
||||||
@@ -486,15 +486,16 @@ pub fn render_add_table(
|
|||||||
|
|
||||||
// --- DIALOG ---
|
// --- DIALOG ---
|
||||||
// Render the dialog overlay if it's active
|
// Render the dialog overlay if it's active
|
||||||
if _app_state.ui.dialog.dialog_show { // Use the passed-in app_state
|
if app_state.ui.dialog.dialog_show { // Use the passed-in app_state
|
||||||
dialog::render_dialog(
|
dialog::render_dialog(
|
||||||
f,
|
f,
|
||||||
f.area(), // Render over the whole frame area
|
f.area(), // Render over the whole frame area
|
||||||
theme,
|
theme,
|
||||||
&_app_state.ui.dialog.dialog_title,
|
&app_state.ui.dialog.dialog_title,
|
||||||
&_app_state.ui.dialog.dialog_message,
|
&app_state.ui.dialog.dialog_message,
|
||||||
&_app_state.ui.dialog.dialog_buttons,
|
&app_state.ui.dialog.dialog_buttons,
|
||||||
_app_state.ui.dialog.dialog_active_button_index,
|
app_state.ui.dialog.dialog_active_button_index,
|
||||||
|
app_state.ui.dialog.is_loading,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -142,7 +142,8 @@ pub fn render_login(
|
|||||||
&app_state.ui.dialog.dialog_title,
|
&app_state.ui.dialog.dialog_title,
|
||||||
&app_state.ui.dialog.dialog_message,
|
&app_state.ui.dialog.dialog_message,
|
||||||
&app_state.ui.dialog.dialog_buttons, // Pass buttons slice
|
&app_state.ui.dialog.dialog_buttons, // Pass buttons slice
|
||||||
app_state.ui.dialog.dialog_active_button_index, // Pass active index
|
app_state.ui.dialog.dialog_active_button_index,
|
||||||
|
app_state.ui.dialog.is_loading,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -168,6 +168,7 @@ pub fn render_register(
|
|||||||
&app_state.ui.dialog.dialog_message,
|
&app_state.ui.dialog.dialog_message,
|
||||||
&app_state.ui.dialog.dialog_buttons,
|
&app_state.ui.dialog.dialog_buttons,
|
||||||
app_state.ui.dialog.dialog_active_button_index,
|
app_state.ui.dialog.dialog_active_button_index,
|
||||||
|
app_state.ui.dialog.is_loading,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ pub fn render_dialog(
|
|||||||
dialog_message: &str,
|
dialog_message: &str,
|
||||||
dialog_buttons: &[String],
|
dialog_buttons: &[String],
|
||||||
dialog_active_button_index: usize,
|
dialog_active_button_index: usize,
|
||||||
|
is_loading: bool,
|
||||||
) {
|
) {
|
||||||
// Calculate required height based on the actual number of lines in the message
|
// Calculate required height based on the actual number of lines in the message
|
||||||
let message_lines: Vec<_> = dialog_message.lines().collect();
|
let message_lines: Vec<_> = dialog_message.lines().collect();
|
||||||
@@ -63,27 +64,36 @@ pub fn render_dialog(
|
|||||||
vertical: 1, // Top/Bottom padding inside border
|
vertical: 1, // Top/Bottom padding inside border
|
||||||
});
|
});
|
||||||
|
|
||||||
// Layout for Message and Buttons based on actual message height
|
if is_loading {
|
||||||
let mut constraints = vec![
|
// --- Loading State ---
|
||||||
// Allocate space for message, ensuring at least 1 line height
|
let loading_text = Paragraph::new(dialog_message) // Use the message passed for loading
|
||||||
Constraint::Length(message_height.max(1)), // Use actual calculated height
|
.style(Style::default().fg(theme.fg).add_modifier(Modifier::ITALIC))
|
||||||
];
|
.alignment(Alignment::Center);
|
||||||
if button_row_height > 0 {
|
// Render loading message centered in the inner area
|
||||||
constraints.push(Constraint::Length(button_row_height));
|
f.render_widget(loading_text, inner_area);
|
||||||
}
|
} else {
|
||||||
|
// --- Normal State (Message + Buttons) ---
|
||||||
|
|
||||||
let chunks = Layout::default()
|
// Layout for Message and Buttons based on actual message height
|
||||||
.direction(Direction::Vertical)
|
let mut constraints = vec![
|
||||||
.constraints(constraints)
|
// Allocate space for message, ensuring at least 1 line height
|
||||||
.split(inner_area);
|
Constraint::Length(message_height.max(1)), // Use actual calculated height
|
||||||
|
];
|
||||||
|
if button_row_height > 0 {
|
||||||
|
constraints.push(Constraint::Length(button_row_height));
|
||||||
|
}
|
||||||
|
|
||||||
// Render Message
|
let chunks = Layout::default()
|
||||||
let available_width = inner_area.width as usize;
|
.direction(Direction::Vertical)
|
||||||
let ellipsis = "...";
|
.constraints(constraints)
|
||||||
let ellipsis_width = UnicodeWidthStr::width(ellipsis);
|
.split(inner_area);
|
||||||
|
|
||||||
let processed_lines: Vec<Line> =
|
// Render Message
|
||||||
message_lines
|
let available_width = inner_area.width as usize;
|
||||||
|
let ellipsis = "...";
|
||||||
|
let ellipsis_width = UnicodeWidthStr::width(ellipsis);
|
||||||
|
|
||||||
|
let processed_lines: Vec<Line> = message_lines
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|line| {
|
.map(|line| {
|
||||||
let line_width = UnicodeWidthStr::width(line);
|
let line_width = UnicodeWidthStr::width(line);
|
||||||
@@ -91,81 +101,83 @@ pub fn render_dialog(
|
|||||||
// Truncate with ellipsis
|
// Truncate with ellipsis
|
||||||
let mut truncated_len = 0;
|
let mut truncated_len = 0;
|
||||||
let mut current_width = 0;
|
let mut current_width = 0;
|
||||||
// Iterate over graphemes to handle multi-byte characters correctly
|
|
||||||
for (idx, grapheme) in line.grapheme_indices(true) {
|
for (idx, grapheme) in line.grapheme_indices(true) {
|
||||||
let grapheme_width = UnicodeWidthStr::width(grapheme);
|
let grapheme_width = UnicodeWidthStr::width(grapheme);
|
||||||
if current_width + grapheme_width > available_width.saturating_sub(ellipsis_width) {
|
if current_width + grapheme_width
|
||||||
break; // Stop before exceeding width needed for text + ellipsis
|
> available_width.saturating_sub(ellipsis_width)
|
||||||
|
{
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
current_width += grapheme_width;
|
current_width += grapheme_width;
|
||||||
truncated_len = idx + grapheme.len(); // Store the byte index of the end of the last fitting grapheme
|
truncated_len = idx + grapheme.len();
|
||||||
}
|
}
|
||||||
let truncated_line = format!("{}{}", &line[..truncated_len], ellipsis);
|
let truncated_line =
|
||||||
Line::from(Span::styled(truncated_line, Style::default().fg(theme.fg)))
|
format!("{}{}", &line[..truncated_len], ellipsis);
|
||||||
|
Line::from(Span::styled(
|
||||||
|
truncated_line,
|
||||||
|
Style::default().fg(theme.fg),
|
||||||
|
))
|
||||||
} else {
|
} else {
|
||||||
// Line fits, use it as is
|
|
||||||
Line::from(Span::styled(line, Style::default().fg(theme.fg)))
|
Line::from(Span::styled(line, Style::default().fg(theme.fg)))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let message_paragraph =
|
let message_paragraph =
|
||||||
Paragraph::new(Text::from(processed_lines)).alignment(Alignment::Center);
|
Paragraph::new(Text::from(processed_lines)).alignment(Alignment::Center);
|
||||||
// Render message in the first chunk
|
f.render_widget(message_paragraph, chunks[0]); // Render message in the first chunk
|
||||||
f.render_widget(message_paragraph, chunks[0]);
|
|
||||||
|
|
||||||
// Render Buttons if they exist and there's a chunk for them
|
// Render Buttons if they exist and there's a chunk for them
|
||||||
if !dialog_buttons.is_empty() && chunks.len() > 1 {
|
if !dialog_buttons.is_empty() && chunks.len() > 1 {
|
||||||
let button_area = chunks[1];
|
let button_area = chunks[1];
|
||||||
let button_count = dialog_buttons.len();
|
let button_count = dialog_buttons.len();
|
||||||
|
|
||||||
// Use Ratio for potentially more even distribution with few buttons
|
let button_constraints = std::iter::repeat(Constraint::Ratio(
|
||||||
let button_constraints = std::iter::repeat(Constraint::Ratio(
|
1,
|
||||||
1,
|
button_count as u32,
|
||||||
button_count as u32,
|
))
|
||||||
))
|
.take(button_count)
|
||||||
.take(button_count)
|
.collect::<Vec<_>>();
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let button_chunks = Layout::default()
|
let button_chunks = Layout::default()
|
||||||
.direction(Direction::Horizontal)
|
.direction(Direction::Horizontal)
|
||||||
.constraints(button_constraints)
|
.constraints(button_constraints)
|
||||||
.horizontal_margin(1) // Add space between buttons
|
.horizontal_margin(1) // Add space between buttons
|
||||||
.split(button_area);
|
.split(button_area);
|
||||||
|
|
||||||
for (i, button_label) in dialog_buttons.iter().enumerate() {
|
for (i, button_label) in dialog_buttons.iter().enumerate() {
|
||||||
// Ensure we don't try to render into a non-existent chunk
|
if i >= button_chunks.len() {
|
||||||
if i >= button_chunks.len() {
|
break;
|
||||||
break;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let is_active = i == dialog_active_button_index;
|
let is_active = i == dialog_active_button_index;
|
||||||
let (button_style, border_style) = if is_active {
|
let (button_style, border_style) = if is_active {
|
||||||
(
|
(
|
||||||
Style::default()
|
Style::default()
|
||||||
.fg(theme.highlight)
|
.fg(theme.highlight)
|
||||||
.add_modifier(Modifier::BOLD),
|
.add_modifier(Modifier::BOLD),
|
||||||
Style::default().fg(theme.accent), // Highlight border
|
Style::default().fg(theme.accent),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
(
|
(
|
||||||
Style::default().fg(theme.fg),
|
Style::default().fg(theme.fg),
|
||||||
Style::default().fg(theme.border), // Normal border
|
Style::default().fg(theme.border),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
let button_block = Block::default()
|
let button_block = Block::default()
|
||||||
.borders(Borders::ALL)
|
.borders(Borders::ALL)
|
||||||
.border_type(BorderType::Plain)
|
.border_type(BorderType::Plain)
|
||||||
.border_style(border_style);
|
.border_style(border_style);
|
||||||
|
|
||||||
f.render_widget(
|
f.render_widget(
|
||||||
Paragraph::new(button_label.as_str())
|
Paragraph::new(button_label.as_str())
|
||||||
.block(button_block)
|
.block(button_block)
|
||||||
.style(button_style)
|
.style(button_style)
|
||||||
.alignment(Alignment::Center),
|
.alignment(Alignment::Center),
|
||||||
button_chunks[i],
|
button_chunks[i],
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use crate::services::auth::AuthClient;
|
|||||||
use crate::config::binds::config::Config;
|
use crate::config::binds::config::Config;
|
||||||
use crate::ui::handlers::rat_state::UiStateHandler;
|
use crate::ui::handlers::rat_state::UiStateHandler;
|
||||||
use crate::ui::handlers::context::UiContext;
|
use crate::ui::handlers::context::UiContext;
|
||||||
|
use crate::ui::handlers::context::DialogPurpose;
|
||||||
use crate::functions::common::buffer;
|
use crate::functions::common::buffer;
|
||||||
use crate::tui::{
|
use crate::tui::{
|
||||||
terminal::core::TerminalCore,
|
terminal::core::TerminalCore,
|
||||||
@@ -226,11 +227,21 @@ impl EventHandler {
|
|||||||
message = format!("Intro Option {} selected", index);
|
message = format!("Intro Option {} selected", index);
|
||||||
}
|
}
|
||||||
UiContext::Login => {
|
UiContext::Login => {
|
||||||
message = match index {
|
let login_action_message = match index {
|
||||||
0 => login::save(auth_state, login_state, &mut self.auth_client, app_state).await?,
|
0 => { // Index 0 corresponds to the "Login" button
|
||||||
|
match login::initiate_login(app_state, login_state).await {
|
||||||
|
Ok(outcome) => return Ok(outcome),
|
||||||
|
Err(e) => {
|
||||||
|
app_state.show_dialog("Error", &format!("Failed to initiate login: {}", e), vec!["OK".to_string()], DialogPurpose::LoginFailed);
|
||||||
|
login_state.login_request_pending = false;
|
||||||
|
"Error initiating login".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
1 => login::back_to_main(login_state, app_state, buffer_state).await,
|
1 => login::back_to_main(login_state, app_state, buffer_state).await,
|
||||||
_ => "Invalid Login Option".to_string(),
|
_ => "Invalid Login Option".to_string(),
|
||||||
};
|
};
|
||||||
|
message = login_action_message;
|
||||||
}
|
}
|
||||||
UiContext::Register => {
|
UiContext::Register => {
|
||||||
message = match index {
|
message = match index {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ pub struct DialogState {
|
|||||||
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 purpose: Option<DialogPurpose>,
|
||||||
|
pub is_loading: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct UiState {
|
pub struct UiState {
|
||||||
@@ -86,10 +87,41 @@ impl AppState {
|
|||||||
self.ui.dialog.dialog_buttons = buttons;
|
self.ui.dialog.dialog_buttons = buttons;
|
||||||
self.ui.dialog.dialog_active_button_index = 0;
|
self.ui.dialog.dialog_active_button_index = 0;
|
||||||
self.ui.dialog.purpose = Some(purpose);
|
self.ui.dialog.purpose = Some(purpose);
|
||||||
|
self.ui.dialog.is_loading = false;
|
||||||
self.ui.dialog.dialog_show = true;
|
self.ui.dialog.dialog_show = true;
|
||||||
self.ui.focus_outside_canvas = true;
|
self.ui.focus_outside_canvas = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shows a dialog specifically for loading states.
|
||||||
|
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(); // No buttons during loading
|
||||||
|
self.ui.dialog.dialog_active_button_index = 0;
|
||||||
|
self.ui.dialog.purpose = None; // Purpose is set when loading finishes
|
||||||
|
self.ui.dialog.is_loading = true;
|
||||||
|
self.ui.dialog.dialog_show = true;
|
||||||
|
self.ui.focus_outside_canvas = true; // Keep focus management consistent
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the content of an existing dialog, typically after loading.
|
||||||
|
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; // Reset focus
|
||||||
|
self.ui.dialog.purpose = Some(purpose);
|
||||||
|
self.ui.dialog.is_loading = false; // Loading finished
|
||||||
|
// Keep dialog_show = true
|
||||||
|
// Keep 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;
|
self.ui.dialog.dialog_show = false;
|
||||||
@@ -156,6 +188,7 @@ impl Default for DialogState {
|
|||||||
dialog_buttons: Vec::new(),
|
dialog_buttons: Vec::new(),
|
||||||
dialog_active_button_index: 0,
|
dialog_active_button_index: 0,
|
||||||
purpose: None,
|
purpose: None,
|
||||||
|
is_loading: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ pub struct LoginState {
|
|||||||
pub current_field: usize,
|
pub current_field: usize,
|
||||||
pub current_cursor_pos: usize,
|
pub current_cursor_pos: usize,
|
||||||
pub has_unsaved_changes: bool,
|
pub has_unsaved_changes: bool,
|
||||||
|
pub login_request_pending: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents the state of the Registration form UI
|
/// Represents the state of the Registration form UI
|
||||||
@@ -71,6 +72,7 @@ impl LoginState {
|
|||||||
current_field: 0,
|
current_field: 0,
|
||||||
current_cursor_pos: 0,
|
current_cursor_pos: 0,
|
||||||
has_unsaved_changes: false,
|
has_unsaved_changes: false,
|
||||||
|
login_request_pending: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,11 @@ use crate::state::app::state::AppState;
|
|||||||
use crate::state::app::buffer::{AppView, BufferState};
|
use crate::state::app::buffer::{AppView, BufferState};
|
||||||
use crate::state::pages::canvas_state::CanvasState;
|
use crate::state::pages::canvas_state::CanvasState;
|
||||||
use crate::ui::handlers::context::DialogPurpose;
|
use crate::ui::handlers::context::DialogPurpose;
|
||||||
|
use crate::modes::handlers::event::EventOutcome; // Make sure this is imported
|
||||||
|
|
||||||
/// 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.
|
||||||
|
/// (This is your existing function - remains unchanged)
|
||||||
pub async fn save(
|
pub async fn save(
|
||||||
auth_state: &mut AuthState,
|
auth_state: &mut AuthState,
|
||||||
login_state: &mut LoginState,
|
login_state: &mut LoginState,
|
||||||
@@ -21,66 +23,74 @@ pub async fn save(
|
|||||||
|
|
||||||
// Clear previous error/dialog state before attempting
|
// Clear previous error/dialog state before attempting
|
||||||
login_state.error_message = None;
|
login_state.error_message = None;
|
||||||
// Use the helper to ensure dialog is hidden and cleared properly
|
app_state.hide_dialog(); // Hide any previous dialog
|
||||||
app_state.hide_dialog();
|
|
||||||
|
|
||||||
// Call the gRPC login method
|
// Call the gRPC login method
|
||||||
match auth_client.login(identifier, password).await {
|
match auth_client.login(identifier, password).await {
|
||||||
Ok(response) => {
|
Ok(response) => {
|
||||||
// Store authentication details on success
|
// Store authentication details using correct field names
|
||||||
auth_state.auth_token = Some(response.access_token.clone());
|
auth_state.auth_token = Some(response.access_token.clone());
|
||||||
auth_state.user_id = Some(response.user_id.clone());
|
auth_state.user_id = Some(response.user_id.clone());
|
||||||
auth_state.role = Some(response.role.clone());
|
auth_state.role = Some(response.role.clone());
|
||||||
auth_state.decoded_username = Some(response.username.clone());
|
auth_state.decoded_username = Some(response.username.clone());
|
||||||
login_state.set_has_unsaved_changes(false);
|
login_state.set_has_unsaved_changes(false);
|
||||||
|
login_state.error_message = None;
|
||||||
|
|
||||||
let success_message = format!(
|
let success_message = "Login Successful!".to_string();
|
||||||
"Login Successful!\n\n\
|
|
||||||
Username: {}\n\
|
|
||||||
User ID: {}\n\
|
|
||||||
Role: {}",
|
|
||||||
response.username,
|
|
||||||
response.user_id,
|
|
||||||
response.role
|
|
||||||
);
|
|
||||||
|
|
||||||
app_state.show_dialog(
|
app_state.show_dialog(
|
||||||
"Login Success",
|
"Login Success",
|
||||||
&success_message,
|
&success_message,
|
||||||
vec!["Menu".to_string(), "Exit".to_string()],
|
vec!["OK".to_string()],
|
||||||
DialogPurpose::LoginSuccess,
|
DialogPurpose::LoginSuccess,
|
||||||
);
|
);
|
||||||
|
login_state.password.clear();
|
||||||
|
login_state.current_cursor_pos = 0;
|
||||||
Ok("Login successful, details shown in dialog.".to_string())
|
Ok("Login successful, details shown in dialog.".to_string())
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let error_message = format!("{}", e);
|
let error_message = format!("{}", e);
|
||||||
|
|
||||||
// Use the helper method to configure and show the dialog
|
|
||||||
app_state.show_dialog(
|
app_state.show_dialog(
|
||||||
"Login Failed",
|
"Login Failed",
|
||||||
&error_message,
|
&error_message,
|
||||||
vec!["OK".to_string()],
|
vec!["OK".to_string()],
|
||||||
DialogPurpose::LoginFailed,
|
DialogPurpose::LoginFailed,
|
||||||
);
|
);
|
||||||
|
login_state.error_message = Some(error_message.clone());
|
||||||
login_state.set_has_unsaved_changes(true);
|
login_state.set_has_unsaved_changes(true);
|
||||||
|
|
||||||
Ok(format!("Login failed: {}", error_message))
|
Ok(format!("Login failed: {}", error_message))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Add this new function ---
|
||||||
|
/// Sets the stage for login: shows loading dialog and sets the pending flag.
|
||||||
|
/// Call this from the event handler when login is triggered.
|
||||||
|
pub async fn initiate_login(
|
||||||
|
app_state: &mut AppState,
|
||||||
|
login_state: &mut LoginState,
|
||||||
|
) -> Result<EventOutcome, Box<dyn std::error::Error>> {
|
||||||
|
// Show the loading dialog immediately
|
||||||
|
app_state.show_loading_dialog("Logging In", "Please wait...");
|
||||||
|
// Set the flag in LoginState to indicate the actual save should run next loop
|
||||||
|
login_state.login_request_pending = true;
|
||||||
|
// Return immediately to allow redraw
|
||||||
|
Ok(EventOutcome::Ok("Login initiated.".to_string()))
|
||||||
|
}
|
||||||
|
// --- End of new function ---
|
||||||
|
|
||||||
|
|
||||||
/// Reverts the login form fields to empty and returns to the previous screen (Intro).
|
/// Reverts the login form fields to empty and returns to the previous screen (Intro).
|
||||||
pub async fn revert(
|
pub async fn revert(
|
||||||
login_state: &mut LoginState,
|
login_state: &mut LoginState,
|
||||||
app_state: &mut AppState,
|
_app_state: &mut AppState, // Keep signature consistent if needed elsewhere
|
||||||
) -> String {
|
) -> String {
|
||||||
// Clear the input fields
|
// Clear the input fields
|
||||||
login_state.username.clear();
|
login_state.username.clear();
|
||||||
login_state.password.clear();
|
login_state.password.clear();
|
||||||
login_state.error_message = None;
|
login_state.error_message = None;
|
||||||
login_state.set_has_unsaved_changes(false);
|
login_state.set_has_unsaved_changes(false);
|
||||||
|
login_state.login_request_pending = false; // Ensure flag is reset on revert
|
||||||
|
|
||||||
"Login reverted".to_string()
|
"Login reverted".to_string()
|
||||||
}
|
}
|
||||||
@@ -95,9 +105,10 @@ pub async fn back_to_main(
|
|||||||
login_state.password.clear();
|
login_state.password.clear();
|
||||||
login_state.error_message = None;
|
login_state.error_message = None;
|
||||||
login_state.set_has_unsaved_changes(false);
|
login_state.set_has_unsaved_changes(false);
|
||||||
|
login_state.login_request_pending = false; // Ensure flag is reset
|
||||||
|
|
||||||
// Ensure dialog is hidden if revert is called
|
// Ensure dialog is hidden if revert is called
|
||||||
app_state.hide_dialog(); // Uncomment if needed
|
app_state.hide_dialog();
|
||||||
|
|
||||||
// Navigation logic
|
// Navigation logic
|
||||||
buffer_state.close_active_buffer();
|
buffer_state.close_active_buffer();
|
||||||
@@ -109,3 +120,4 @@ pub async fn back_to_main(
|
|||||||
|
|
||||||
"Returned to main menu".to_string()
|
"Returned to main menu".to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
use crate::config::binds::config::Config;
|
use crate::config::binds::config::Config;
|
||||||
use crate::config::colors::themes::Theme;
|
use crate::config::colors::themes::Theme;
|
||||||
use crate::services::grpc_client::GrpcClient;
|
use crate::services::grpc_client::GrpcClient;
|
||||||
|
use crate::services::auth::AuthClient; // <-- Add AuthClient import
|
||||||
use crate::services::ui_service::UiService;
|
use crate::services::ui_service::UiService;
|
||||||
use crate::modes::common::commands::CommandHandler;
|
use crate::modes::common::commands::CommandHandler;
|
||||||
use crate::modes::handlers::event::{EventHandler, EventOutcome};
|
use crate::modes::handlers::event::{EventHandler, EventOutcome};
|
||||||
@@ -17,11 +18,15 @@ use crate::state::pages::intro::IntroState;
|
|||||||
use crate::state::app::buffer::BufferState;
|
use crate::state::app::buffer::BufferState;
|
||||||
use crate::state::app::buffer::AppView;
|
use crate::state::app::buffer::AppView;
|
||||||
use crate::state::app::state::AppState;
|
use crate::state::app::state::AppState;
|
||||||
// Import SaveOutcome
|
use crate::ui::handlers::context::DialogPurpose; // <-- Add DialogPurpose import
|
||||||
|
// 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 crate::tui::functions::common::login; // <-- Add login module import
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use crossterm::cursor::SetCursorStyle;
|
use crossterm::cursor::SetCursorStyle;
|
||||||
|
use crossterm::event as crossterm_event;
|
||||||
|
use log; // <-- Add log import
|
||||||
|
|
||||||
pub async fn run_ui() -> Result<(), Box<dyn std::error::Error>> {
|
pub async fn run_ui() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let config = Config::load()?;
|
let config = Config::load()?;
|
||||||
@@ -57,9 +62,6 @@ pub async fn run_ui() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
let mut current_fps = 0.0;
|
let mut current_fps = 0.0;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
// Determine edit mode based on EventHandler state
|
|
||||||
let is_edit_mode = event_handler.is_edit_mode;
|
|
||||||
|
|
||||||
// --- Synchronize UI View from Active Buffer ---
|
// --- Synchronize UI View from Active Buffer ---
|
||||||
if let Some(active_view) = buffer_state.get_active_view() {
|
if let Some(active_view) = buffer_state.get_active_view() {
|
||||||
// Reset all flags first
|
// Reset all flags first
|
||||||
@@ -87,6 +89,10 @@ pub async fn run_ui() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
}
|
}
|
||||||
// --- End Synchronization ---
|
// --- End Synchronization ---
|
||||||
|
|
||||||
|
// --- 3. Draw UI ---
|
||||||
|
// Draw the current state *first*. This ensures the loading dialog
|
||||||
|
// set in the *previous* iteration gets rendered before the pending
|
||||||
|
// action check below.
|
||||||
terminal.draw(|f| {
|
terminal.draw(|f| {
|
||||||
render_ui(
|
render_ui(
|
||||||
f,
|
f,
|
||||||
@@ -98,7 +104,7 @@ pub async fn run_ui() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
&mut admin_state,
|
&mut admin_state,
|
||||||
&buffer_state,
|
&buffer_state,
|
||||||
&theme,
|
&theme,
|
||||||
is_edit_mode,
|
event_handler.is_edit_mode, // Use event_handler's state
|
||||||
&event_handler.highlight_state,
|
&event_handler.highlight_state,
|
||||||
app_state.total_count,
|
app_state.total_count,
|
||||||
app_state.current_position,
|
app_state.current_position,
|
||||||
@@ -112,77 +118,103 @@ pub async fn run_ui() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
// --- Cursor Visibility Logic ---
|
// --- Cursor Visibility Logic ---
|
||||||
|
// (Keep existing cursor logic here - depends on state drawn above)
|
||||||
let current_mode = ModeManager::derive_mode(&app_state, &event_handler);
|
let current_mode = ModeManager::derive_mode(&app_state, &event_handler);
|
||||||
match current_mode {
|
match current_mode {
|
||||||
AppMode::Edit => {
|
AppMode::Edit => { terminal.show_cursor()?; }
|
||||||
terminal.show_cursor()?;
|
AppMode::Highlight => { terminal.set_cursor_style(SetCursorStyle::SteadyBlock)?; terminal.show_cursor()?; }
|
||||||
}
|
|
||||||
AppMode::Highlight => {
|
|
||||||
terminal.set_cursor_style(SetCursorStyle::SteadyBlock)?;
|
|
||||||
terminal.show_cursor()?;
|
|
||||||
}
|
|
||||||
AppMode::ReadOnly => {
|
AppMode::ReadOnly => {
|
||||||
if !app_state.ui.focus_outside_canvas {
|
if !app_state.ui.focus_outside_canvas { terminal.set_cursor_style(SetCursorStyle::SteadyBlock)?; }
|
||||||
terminal.set_cursor_style(SetCursorStyle::SteadyBlock)?;
|
else { terminal.set_cursor_style(SetCursorStyle::SteadyUnderScore)?; }
|
||||||
} else {
|
|
||||||
terminal.set_cursor_style(SetCursorStyle::SteadyUnderScore)?;
|
|
||||||
}
|
|
||||||
terminal.show_cursor()?;
|
terminal.show_cursor()?;
|
||||||
}
|
}
|
||||||
AppMode::General => {
|
AppMode::General => {
|
||||||
if app_state.ui.focus_outside_canvas {
|
if app_state.ui.focus_outside_canvas { terminal.set_cursor_style(SetCursorStyle::SteadyUnderScore)?; terminal.show_cursor()?; }
|
||||||
terminal.set_cursor_style(SetCursorStyle::SteadyUnderScore)?;
|
else { terminal.hide_cursor()?; }
|
||||||
terminal.show_cursor()?;
|
|
||||||
} else {
|
|
||||||
terminal.hide_cursor()?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
AppMode::Command => {
|
|
||||||
terminal.set_cursor_style(SetCursorStyle::SteadyUnderScore)?;
|
|
||||||
terminal.show_cursor()?;
|
|
||||||
}
|
}
|
||||||
|
AppMode::Command => { terminal.set_cursor_style(SetCursorStyle::SteadyUnderScore)?; terminal.show_cursor()?; }
|
||||||
}
|
}
|
||||||
// --- End Cursor Visibility Logic ---
|
// --- End Cursor Visibility Logic ---
|
||||||
|
|
||||||
let total_count = app_state.total_count; // Keep track for save logic
|
// --- 2. Check for Pending Login Action ---
|
||||||
|
// Check *after* drawing, so the loading state was rendered.
|
||||||
|
if login_state.login_request_pending {
|
||||||
|
// Reset the flag *before* calling save
|
||||||
|
login_state.login_request_pending = false;
|
||||||
|
|
||||||
|
// Create AuthClient and call save
|
||||||
|
match AuthClient::new().await {
|
||||||
|
Ok(mut auth_client_instance) => {
|
||||||
|
let save_result = login::save(
|
||||||
|
&mut auth_state,
|
||||||
|
&mut login_state,
|
||||||
|
&mut auth_client_instance,
|
||||||
|
&mut app_state,
|
||||||
|
).await;
|
||||||
|
match save_result {
|
||||||
|
Ok(msg) => log::info!("Login save result: {}", msg),
|
||||||
|
Err(e) => log::error!("Error during login save: {}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
// Handle connection error
|
||||||
|
app_state.show_dialog(
|
||||||
|
"Login Failed",
|
||||||
|
&format!("Connection Error: {}", e),
|
||||||
|
vec!["OK".to_string()],
|
||||||
|
DialogPurpose::LoginFailed,
|
||||||
|
);
|
||||||
|
login_state.error_message = Some(format!("Connection Error: {}", e));
|
||||||
|
log::error!("Failed to create AuthClient: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// After save runs, the state (dialog content, etc.) is updated.
|
||||||
|
// The *next* iteration's draw call will show the final result.
|
||||||
|
} // --- End Pending Login Check ---
|
||||||
|
|
||||||
|
let total_count = app_state.total_count;
|
||||||
let mut current_position = app_state.current_position;
|
let mut current_position = app_state.current_position;
|
||||||
let position_before_event = current_position;
|
let position_before_event = current_position;
|
||||||
|
|
||||||
let event = event_reader.read_event()?;
|
// --- 1. Handle Terminal Events ---
|
||||||
|
let mut event_outcome_result = Ok(EventOutcome::Ok(String::new()));
|
||||||
// Get the outcome from the event handler
|
// Poll for events *after* drawing and checking pending actions
|
||||||
let event_outcome_result = event_handler
|
if crossterm_event::poll(std::time::Duration::from_millis(20))? {
|
||||||
.handle_event(
|
let event = event_reader.read_event()?;
|
||||||
event,
|
event_outcome_result = event_handler
|
||||||
&config,
|
.handle_event(
|
||||||
&mut terminal,
|
event,
|
||||||
&mut grpc_client,
|
&config,
|
||||||
&mut command_handler,
|
&mut terminal,
|
||||||
&mut form_state,
|
&mut grpc_client,
|
||||||
&mut auth_state,
|
&mut command_handler,
|
||||||
&mut login_state,
|
&mut form_state,
|
||||||
&mut register_state,
|
&mut auth_state,
|
||||||
&mut intro_state,
|
&mut login_state,
|
||||||
&mut admin_state,
|
&mut register_state,
|
||||||
&mut buffer_state,
|
&mut intro_state,
|
||||||
&mut app_state,
|
&mut admin_state,
|
||||||
total_count,
|
&mut buffer_state,
|
||||||
&mut current_position,
|
&mut app_state,
|
||||||
)
|
total_count,
|
||||||
.await;
|
&mut current_position,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
// Update position based on handler's modification
|
// Update position based on handler's modification
|
||||||
|
// This happens *after* the event is handled
|
||||||
app_state.current_position = current_position;
|
app_state.current_position = current_position;
|
||||||
|
|
||||||
// --- Centralized Consequence Handling ---
|
// --- Centralized Consequence Handling ---
|
||||||
let mut should_exit = false;
|
let mut should_exit = false;
|
||||||
match event_outcome_result {
|
match event_outcome_result {
|
||||||
// Handle the Result first
|
|
||||||
Ok(outcome) => match outcome {
|
Ok(outcome) => match outcome {
|
||||||
// Handle the Ok variant containing EventOutcome
|
|
||||||
EventOutcome::Ok(message) => {
|
EventOutcome::Ok(message) => {
|
||||||
if !message.is_empty() {
|
if !message.is_empty() {
|
||||||
event_handler.command_message = message;
|
// Update command message only if event handling produced one
|
||||||
|
// Avoid overwriting messages potentially set by pending actions
|
||||||
|
// event_handler.command_message = message;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EventOutcome::Exit(message) => {
|
EventOutcome::Exit(message) => {
|
||||||
@@ -191,8 +223,6 @@ pub async fn run_ui() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
}
|
}
|
||||||
EventOutcome::DataSaved(save_outcome, message) => {
|
EventOutcome::DataSaved(save_outcome, message) => {
|
||||||
event_handler.command_message = message; // Show save status
|
event_handler.command_message = message; // Show save status
|
||||||
|
|
||||||
// *** Delegate outcome handling to UiService ***
|
|
||||||
if let Err(e) = UiService::handle_save_outcome(
|
if let Err(e) = UiService::handle_save_outcome(
|
||||||
save_outcome,
|
save_outcome,
|
||||||
&mut grpc_client,
|
&mut grpc_client,
|
||||||
@@ -201,30 +231,26 @@ pub async fn run_ui() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
// Handle potential errors from the outcome handler itself
|
|
||||||
event_handler.command_message =
|
event_handler.command_message =
|
||||||
format!("Error handling save outcome: {}", e);
|
format!("Error handling save outcome: {}", e);
|
||||||
}
|
}
|
||||||
// No count update needed for UpdatedExisting or NoChange
|
|
||||||
}
|
}
|
||||||
EventOutcome::ButtonSelected { context, index } => {
|
EventOutcome::ButtonSelected { context: _, index: _ } => {
|
||||||
event_handler.command_message = "Internal error: Unexpected button state".to_string();
|
// This case should ideally be fully handled within handle_event
|
||||||
|
// If initiate_login was called, it returned early.
|
||||||
|
// If not, the message was set and returned via Ok(message).
|
||||||
|
// Log if necessary, but likely no action needed here.
|
||||||
|
// log::warn!("ButtonSelected outcome reached main loop unexpectedly.");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
// Handle errors from handle_event, e.g., log or display
|
|
||||||
event_handler.command_message = format!("Error: {}", e);
|
event_handler.command_message = format!("Error: {}", e);
|
||||||
// Decide if the error is fatal, maybe set should_exit = true;
|
|
||||||
}
|
}
|
||||||
}
|
} // --- End Consequence Handling ---
|
||||||
|
|
||||||
// --- Position Change Handling (after outcome processing) ---
|
// --- Position Change Handling (after outcome processing and pending actions) ---
|
||||||
let position_changed =
|
let position_changed = app_state.current_position != position_before_event;
|
||||||
app_state.current_position != position_before_event; // Calculate after potential update
|
|
||||||
// Recalculate total_count *after* potential update
|
|
||||||
let current_total_count = app_state.total_count;
|
let current_total_count = app_state.total_count;
|
||||||
|
|
||||||
// Handle position changes and update form state (Only when form is shown)
|
|
||||||
if app_state.ui.show_form {
|
if app_state.ui.show_form {
|
||||||
if position_changed && !event_handler.is_edit_mode {
|
if position_changed && !event_handler.is_edit_mode {
|
||||||
let current_input = form_state.get_current_input();
|
let current_input = form_state.get_current_input();
|
||||||
@@ -295,7 +321,7 @@ pub async fn run_ui() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
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 {
|
} else if app_state.ui.show_register {
|
||||||
if !event_handler.is_edit_mode {
|
if !event_handler.is_edit_mode {
|
||||||
let current_input = register_state.get_current_input();
|
let current_input = register_state.get_current_input();
|
||||||
let max_cursor_pos = if !current_input.is_empty() {
|
let max_cursor_pos = if !current_input.is_empty() {
|
||||||
current_input.len() - 1
|
current_input.len() - 1
|
||||||
@@ -305,7 +331,7 @@ pub async fn run_ui() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
register_state.current_cursor_pos = event_handler.ideal_cursor_column.min(max_cursor_pos);
|
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 {
|
||||||
if !event_handler.is_edit_mode {
|
if !event_handler.is_edit_mode {
|
||||||
let current_input = login_state.get_current_input();
|
let current_input = login_state.get_current_input();
|
||||||
let max_cursor_pos = if !current_input.is_empty() {
|
let max_cursor_pos = if !current_input.is_empty() {
|
||||||
current_input.len() - 1
|
current_input.len() - 1
|
||||||
@@ -315,10 +341,10 @@ pub async fn run_ui() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
login_state.current_cursor_pos = event_handler.ideal_cursor_column.min(max_cursor_pos);
|
login_state.current_cursor_pos = event_handler.ideal_cursor_column.min(max_cursor_pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// --- End Position Change Handling ---
|
||||||
|
|
||||||
// Check exit condition *after* processing outcome
|
// Check exit condition *after* all processing for the iteration
|
||||||
if should_exit {
|
if should_exit {
|
||||||
// terminal.cleanup()?; // Optional: Drop handles this
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -329,6 +355,6 @@ pub async fn run_ui() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
if frame_duration.as_secs_f64() > 1e-6 {
|
if frame_duration.as_secs_f64() > 1e-6 {
|
||||||
current_fps = 1.0 / frame_duration.as_secs_f64();
|
current_fps = 1.0 / frame_duration.as_secs_f64();
|
||||||
}
|
}
|
||||||
}
|
} // End main loop
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user