readonly and edit functionality to add table

This commit is contained in:
filipriec
2025-04-17 14:14:36 +02:00
parent 57f789290d
commit c5d7f56399
11 changed files with 967 additions and 254 deletions

View File

@@ -2,3 +2,4 @@
pub mod form_e;
pub mod auth_e;
pub mod add_table_e;

View File

@@ -0,0 +1,343 @@
// src/functions/modes/edit/add_table_e.rs
use crate::state::pages::add_table::AddTableState;
use crate::state::pages::canvas_state::CanvasState; // Use trait
use crossterm::event::{KeyCode, KeyEvent};
use std::error::Error;
#[derive(PartialEq)]
enum CharType {
Whitespace,
Alphanumeric,
Punctuation,
}
fn get_char_type(c: char) -> CharType {
if c.is_whitespace() {
CharType::Whitespace
} else if c.is_alphanumeric() {
CharType::Alphanumeric
} else {
CharType::Punctuation
}
}
fn find_next_word_start(text: &str, current_pos: usize) -> usize {
let chars: Vec<char> = text.chars().collect();
let len = chars.len();
if len == 0 || current_pos >= len {
return len;
}
let mut pos = current_pos;
let initial_type = get_char_type(chars[pos]);
while pos < len && get_char_type(chars[pos]) == initial_type {
pos += 1;
}
while pos < len && get_char_type(chars[pos]) == CharType::Whitespace {
pos += 1;
}
pos
}
fn find_word_end(text: &str, current_pos: usize) -> usize {
let chars: Vec<char> = text.chars().collect();
let len = chars.len();
if len == 0 {
return 0;
}
let mut pos = current_pos.min(len - 1);
if get_char_type(chars[pos]) == CharType::Whitespace {
pos = find_next_word_start(text, pos);
}
if pos >= len {
return len.saturating_sub(1);
}
let word_type = get_char_type(chars[pos]);
while pos < len && get_char_type(chars[pos]) == word_type {
pos += 1;
}
pos.saturating_sub(1).min(len.saturating_sub(1))
}
fn find_prev_word_start(text: &str, current_pos: usize) -> usize {
let chars: Vec<char> = text.chars().collect();
if chars.is_empty() || current_pos == 0 {
return 0;
}
let mut pos = current_pos.saturating_sub(1);
while pos > 0 && get_char_type(chars[pos]) == CharType::Whitespace {
pos -= 1;
}
if pos == 0 && get_char_type(chars[pos]) == CharType::Whitespace {
return 0;
}
let word_type = get_char_type(chars[pos]);
while pos > 0 && get_char_type(chars[pos - 1]) == word_type {
pos -= 1;
}
pos
}
fn find_prev_word_end(text: &str, current_pos: usize) -> usize {
let chars: Vec<char> = text.chars().collect();
let len = chars.len();
if len == 0 || current_pos == 0 {
return 0;
}
let mut pos = current_pos.saturating_sub(1);
while pos > 0 && get_char_type(chars[pos]) == CharType::Whitespace {
pos -= 1;
}
if pos == 0 && get_char_type(chars[pos]) == CharType::Whitespace {
return 0;
}
if pos == 0 && get_char_type(chars[pos]) != CharType::Whitespace {
return 0;
}
let word_type = get_char_type(chars[pos]);
while pos > 0 && get_char_type(chars[pos - 1]) == word_type {
pos -= 1;
}
while pos > 0 && get_char_type(chars[pos - 1]) == CharType::Whitespace {
pos -= 1;
}
if pos > 0 {
pos - 1
} else {
0
}
}
/// Executes edit actions for the AddTable view canvas.
pub async fn execute_edit_action(
action: &str,
key: KeyEvent, // Needed for insert_char
state: &mut AddTableState,
ideal_cursor_column: &mut usize,
// Add other params like grpc_client if needed for future actions (e.g., validation)
) -> Result<String, Box<dyn Error>> {
// Use the CanvasState trait methods implemented for AddTableState
match action {
"insert_char" => {
if let KeyCode::Char(c) = key.code {
let cursor_pos = state.current_cursor_pos();
let field_value = state.get_current_input_mut();
let mut chars: Vec<char> = field_value.chars().collect();
if cursor_pos <= chars.len() {
chars.insert(cursor_pos, c);
*field_value = chars.into_iter().collect();
state.set_current_cursor_pos(cursor_pos + 1);
state.set_has_unsaved_changes(true);
*ideal_cursor_column = state.current_cursor_pos();
}
} else {
return Ok("Error: insert_char called without a char key.".to_string());
}
Ok("".to_string()) // No message needed for char insertion
}
"delete_char_backward" => {
if state.current_cursor_pos() > 0 {
let cursor_pos = state.current_cursor_pos();
let field_value = state.get_current_input_mut();
let mut chars: Vec<char> = field_value.chars().collect();
if cursor_pos <= chars.len() {
chars.remove(cursor_pos - 1);
*field_value = chars.into_iter().collect();
let new_pos = cursor_pos - 1;
state.set_current_cursor_pos(new_pos);
state.set_has_unsaved_changes(true);
*ideal_cursor_column = new_pos;
}
}
Ok("".to_string())
}
"delete_char_forward" => {
let cursor_pos = state.current_cursor_pos();
let field_value = state.get_current_input_mut();
let mut chars: Vec<char> = field_value.chars().collect();
if cursor_pos < chars.len() {
chars.remove(cursor_pos);
*field_value = chars.into_iter().collect();
state.set_has_unsaved_changes(true);
*ideal_cursor_column = cursor_pos;
}
Ok("".to_string())
}
"next_field" => {
let num_fields = AddTableState::INPUT_FIELD_COUNT;
if num_fields > 0 {
let current_field = state.current_field();
let new_field = (current_field + 1) % num_fields;
state.set_current_field(new_field);
let current_input = state.get_current_input();
let max_pos = current_input.len();
state.set_current_cursor_pos((*ideal_cursor_column).min(max_pos));
}
Ok("".to_string())
}
"prev_field" => {
let num_fields = AddTableState::INPUT_FIELD_COUNT;
if num_fields > 0 {
let current_field = state.current_field();
let new_field = if current_field == 0 {
num_fields - 1
} else {
current_field - 1
};
state.set_current_field(new_field);
let current_input = state.get_current_input();
let max_pos = current_input.len();
state.set_current_cursor_pos((*ideal_cursor_column).min(max_pos));
}
Ok("".to_string())
}
"move_left" => {
let new_pos = state.current_cursor_pos().saturating_sub(1);
state.set_current_cursor_pos(new_pos);
*ideal_cursor_column = new_pos;
Ok("".to_string())
}
"move_right" => {
let current_input = state.get_current_input();
let current_pos = state.current_cursor_pos();
if current_pos < current_input.len() {
let new_pos = current_pos + 1;
state.set_current_cursor_pos(new_pos);
*ideal_cursor_column = new_pos;
}
Ok("".to_string())
}
"move_up" => {
let num_fields = AddTableState::INPUT_FIELD_COUNT;
if num_fields > 0 {
let current_field = state.current_field();
if current_field > 0 {
let new_field = current_field - 1;
state.set_current_field(new_field);
let current_input = state.get_current_input();
let max_pos = current_input.len();
state.set_current_cursor_pos((*ideal_cursor_column).min(max_pos));
}
}
Ok("".to_string())
}
"move_down" => {
let num_fields = AddTableState::INPUT_FIELD_COUNT;
if num_fields > 0 {
let current_field = state.current_field();
let last_field_index = num_fields - 1;
if current_field < last_field_index {
let new_field = current_field + 1;
state.set_current_field(new_field);
let current_input = state.get_current_input();
let max_pos = current_input.len();
state.set_current_cursor_pos((*ideal_cursor_column).min(max_pos));
}
}
Ok("".to_string())
}
"move_line_start" => {
state.set_current_cursor_pos(0);
*ideal_cursor_column = 0;
Ok("".to_string())
}
"move_line_end" => {
let current_input = state.get_current_input();
let new_pos = current_input.len();
state.set_current_cursor_pos(new_pos);
*ideal_cursor_column = new_pos;
Ok("".to_string())
}
"move_first_line" => {
if AddTableState::INPUT_FIELD_COUNT > 0 {
state.set_current_field(0);
let current_input = state.get_current_input();
let max_pos = current_input.len();
state.set_current_cursor_pos((*ideal_cursor_column).min(max_pos));
}
Ok("".to_string())
}
"move_last_line" => {
let num_fields = AddTableState::INPUT_FIELD_COUNT;
if num_fields > 0 {
let new_field = num_fields - 1;
state.set_current_field(new_field);
let current_input = state.get_current_input();
let max_pos = current_input.len();
state.set_current_cursor_pos((*ideal_cursor_column).min(max_pos));
}
Ok("".to_string())
}
"move_word_next" => {
let current_input = state.get_current_input();
if !current_input.is_empty() {
let new_pos = find_next_word_start(current_input, state.current_cursor_pos());
let final_pos = new_pos.min(current_input.len());
state.set_current_cursor_pos(final_pos);
*ideal_cursor_column = final_pos;
}
Ok("".to_string())
}
"move_word_end" => {
let current_input = state.get_current_input();
if !current_input.is_empty() {
let current_pos = state.current_cursor_pos();
let new_pos = find_word_end(current_input, current_pos);
let final_pos = if new_pos == current_pos {
find_word_end(current_input, new_pos + 1)
} else {
new_pos
};
let max_valid_index = current_input.len().saturating_sub(1);
let clamped_pos = final_pos.min(max_valid_index);
state.set_current_cursor_pos(clamped_pos);
*ideal_cursor_column = clamped_pos;
}
Ok("".to_string())
}
"move_word_prev" => {
let current_input = state.get_current_input();
if !current_input.is_empty() {
let new_pos = find_prev_word_start(current_input, state.current_cursor_pos());
state.set_current_cursor_pos(new_pos);
*ideal_cursor_column = new_pos;
}
Ok("".to_string())
}
"move_word_end_prev" => {
let current_input = state.get_current_input();
if !current_input.is_empty() {
let new_pos = find_prev_word_end(current_input, state.current_cursor_pos());
state.set_current_cursor_pos(new_pos);
*ideal_cursor_column = new_pos;
}
Ok("".to_string())
}
// Actions handled by main event loop (mode changes, save, revert)
"exit_edit_mode" | "save" | "revert" => {
Ok("Action handled by main loop".to_string())
}
_ => Ok(format!("Unknown or unhandled edit action: {}", action)),
}
}

View File

@@ -5,101 +5,120 @@ use crate::state::{
pages::add_table::{AddTableFocus, AddTableState},
};
use crossterm::event::{KeyEvent};
use ratatui::widgets::TableState; // Import TableState
use ratatui::widgets::TableState;
/// Handles navigation events specifically for the Add Table view.
/// Returns true if the event was handled, false otherwise.
pub fn handle_add_table_navigation(
key: KeyEvent,
config: &Config,
_app_state: &AppState, // Keep for potential future use (e.g., checking permissions)
_app_state: &AppState,
add_table_state: &mut AddTableState,
command_message: &mut String,
) -> bool {
let action = config.get_general_action(key.code, key.modifiers);
let current_focus = add_table_state.current_focus;
let mut handled = true; // Assume handled unless logic determines otherwise
let mut new_focus = current_focus; // Initialize new_focus
// Define focus groups for horizontal navigation
let is_left_pane_focus = matches!(current_focus,
AddTableFocus::ColumnsTable | AddTableFocus::IndexesTable | AddTableFocus::LinksTable
);
let is_right_pane_focus = matches!(current_focus,
AddTableFocus::InputTableName | AddTableFocus::InputColumnName | AddTableFocus::InputColumnType |
AddTableFocus::AddColumnButton | AddTableFocus::SaveButton | AddTableFocus::CancelButton
);
match action.as_deref() {
// --- Vertical Navigation (Up/Down) ---
Some("move_up") => {
let mut new_focus = current_focus; // Start with current focus
match current_focus {
AddTableFocus::InputTableName => new_focus = AddTableFocus::CancelButton, // Wrap top
AddTableFocus::InputTableName => new_focus = AddTableFocus::CancelButton, // Wrap top (right pane)
AddTableFocus::InputColumnName => new_focus = AddTableFocus::InputTableName,
AddTableFocus::InputColumnType => new_focus = AddTableFocus::InputColumnName,
AddTableFocus::AddColumnButton => new_focus = AddTableFocus::InputColumnType,
AddTableFocus::ColumnsTable => {
AddTableFocus::ColumnsTable => { // Left pane navigation
if !navigate_table_up(&mut add_table_state.column_table_state, add_table_state.columns.len()) {
new_focus = AddTableFocus::AddColumnButton; // Move focus up if at table top
// If at top of columns, potentially wrap to bottom of left pane (LinksTable) or stay? Let's stay for now.
// Or maybe move to AddColumnButton? Let's try moving up from right pane instead.
new_focus = AddTableFocus::AddColumnButton; // Tentative: move focus up from right pane
}
// Keep focus on table while navigating within it
}
AddTableFocus::IndexesTable => {
if !navigate_table_up(&mut add_table_state.index_table_state, add_table_state.indexes.len()) {
new_focus = AddTableFocus::ColumnsTable; // Move focus up
new_focus = AddTableFocus::ColumnsTable;
}
}
AddTableFocus::LinksTable => {
if !navigate_table_up(&mut add_table_state.link_table_state, add_table_state.links.len()) {
new_focus = AddTableFocus::IndexesTable; // Move focus up
new_focus = AddTableFocus::IndexesTable;
}
}
AddTableFocus::SaveButton => new_focus = AddTableFocus::LinksTable,
AddTableFocus::SaveButton => new_focus = AddTableFocus::LinksTable, // Move up to left pane bottom
AddTableFocus::CancelButton => new_focus = AddTableFocus::SaveButton,
}
add_table_state.current_focus = new_focus;
*command_message = format!("Focus set to {:?}", add_table_state.current_focus);
}
Some("move_down") => {
let mut new_focus = current_focus; // Start with current focus
match current_focus {
AddTableFocus::InputTableName => new_focus = AddTableFocus::InputColumnName,
AddTableFocus::InputColumnName => new_focus = AddTableFocus::InputColumnType,
AddTableFocus::InputColumnType => new_focus = AddTableFocus::AddColumnButton,
AddTableFocus::AddColumnButton => new_focus = AddTableFocus::ColumnsTable,
AddTableFocus::ColumnsTable => {
AddTableFocus::AddColumnButton => new_focus = AddTableFocus::ColumnsTable, // Move down to left pane top
AddTableFocus::ColumnsTable => { // Left pane navigation
if !navigate_table_down(&mut add_table_state.column_table_state, add_table_state.columns.len()) {
new_focus = AddTableFocus::IndexesTable; // Move focus down if at table bottom
new_focus = AddTableFocus::IndexesTable; // Move to next left pane item
}
// Keep focus on table while navigating within it
}
AddTableFocus::IndexesTable => {
if !navigate_table_down(&mut add_table_state.index_table_state, add_table_state.indexes.len()) {
new_focus = AddTableFocus::LinksTable; // Move focus down
new_focus = AddTableFocus::LinksTable;
}
}
AddTableFocus::LinksTable => {
if !navigate_table_down(&mut add_table_state.link_table_state, add_table_state.links.len()) {
new_focus = AddTableFocus::SaveButton; // Move focus down
new_focus = AddTableFocus::SaveButton; // Move down to right pane bottom
}
}
AddTableFocus::SaveButton => new_focus = AddTableFocus::CancelButton,
AddTableFocus::CancelButton => new_focus = AddTableFocus::InputTableName, // Wrap bottom
AddTableFocus::CancelButton => new_focus = AddTableFocus::InputTableName, // Wrap bottom (right pane)
}
add_table_state.current_focus = new_focus;
*command_message = format!("Focus set to {:?}", add_table_state.current_focus);
}
// --- Horizontal Navigation (Left/Right) ---
Some("next_option") => { // 'l' or Right
add_table_state.current_focus = match current_focus {
AddTableFocus::SaveButton => AddTableFocus::CancelButton,
_ => current_focus, // No change for others yet
};
*command_message = format!("Focus set to {:?}", add_table_state.current_focus);
Some("next_option") => { // 'l' or Right: Move from Left Pane to Right Pane
if is_left_pane_focus {
new_focus = match current_focus {
// Map left pane items to corresponding right pane items (approximate vertical alignment)
AddTableFocus::ColumnsTable => AddTableFocus::InputTableName,
AddTableFocus::IndexesTable => AddTableFocus::InputColumnName, // Or AddColumnButton?
AddTableFocus::LinksTable => AddTableFocus::SaveButton,
_ => current_focus, // Should not happen based on is_left_pane_focus
};
} else if is_right_pane_focus {
// If already in right pane, maybe wrap Save -> Cancel or stay? Let's handle Save->Cancel only.
if current_focus == AddTableFocus::SaveButton {
new_focus = AddTableFocus::CancelButton;
}
}
}
Some("previous_option") => { // 'h' or Left
add_table_state.current_focus = match current_focus {
AddTableFocus::CancelButton => AddTableFocus::SaveButton,
_ => current_focus, // No change for others yet
};
*command_message = format!("Focus set to {:?}", add_table_state.current_focus);
Some("previous_option") => { // 'h' or Left: Move from Right Pane to Left Pane
if is_right_pane_focus {
new_focus = match current_focus {
// Map right pane items back to left pane items (approximate vertical alignment)
AddTableFocus::InputTableName | AddTableFocus::InputColumnName | AddTableFocus::InputColumnType | AddTableFocus::AddColumnButton => AddTableFocus::ColumnsTable, // Go to top of left pane
AddTableFocus::SaveButton | AddTableFocus::CancelButton => AddTableFocus::LinksTable, // Go to bottom of left pane
_ => current_focus, // Should not happen
};
} else if is_left_pane_focus {
// If already in left pane, pressing 'h' could wrap to Cancel button?
new_focus = AddTableFocus::CancelButton; // Wrap left-to-right bottom
}
}
// --- Tab / Shift+Tab Navigation ---
// --- Tab / Shift+Tab Navigation (Keep as vertical cycle) ---
Some("next_field") => { // Tab
add_table_state.current_focus = match current_focus {
new_focus = match current_focus {
AddTableFocus::InputTableName => AddTableFocus::InputColumnName,
AddTableFocus::InputColumnName => AddTableFocus::InputColumnType,
AddTableFocus::InputColumnType => AddTableFocus::AddColumnButton,
@@ -110,10 +129,9 @@ pub fn handle_add_table_navigation(
AddTableFocus::SaveButton => AddTableFocus::CancelButton,
AddTableFocus::CancelButton => AddTableFocus::InputTableName, // Wrap
};
*command_message = format!("Focus set to {:?}", add_table_state.current_focus);
}
Some("prev_field") => { // Shift+Tab
add_table_state.current_focus = match current_focus {
new_focus = match current_focus {
AddTableFocus::InputTableName => AddTableFocus::CancelButton, // Wrap
AddTableFocus::InputColumnName => AddTableFocus::InputTableName,
AddTableFocus::InputColumnType => AddTableFocus::InputColumnName,
@@ -124,7 +142,6 @@ pub fn handle_add_table_navigation(
AddTableFocus::SaveButton => AddTableFocus::LinksTable,
AddTableFocus::CancelButton => AddTableFocus::SaveButton,
};
*command_message = format!("Focus set to {:?}", add_table_state.current_focus);
}
// --- Selection ---
@@ -132,70 +149,54 @@ pub fn handle_add_table_navigation(
match current_focus {
AddTableFocus::AddColumnButton => {
*command_message = "Action: Add Column (Not Implemented)".to_string();
// TODO: Implement logic to add column based on inputs
// Clear input fields, add to columns list, mark unsaved changes
// add_table_state.add_column(); // Example method call
// TODO: Implement logic
}
AddTableFocus::SaveButton => {
*command_message = "Action: Save Table (Not Implemented)".to_string();
// TODO: Implement logic to save table (e.g., call API)
// Mark changes as saved
// add_table_state.save_table(); // Example method call
// TODO: Implement logic
}
AddTableFocus::CancelButton => {
*command_message = "Action: Cancel Add Table".to_string();
// TODO: Implement logic to navigate back (e.g., update AppView history)
// Maybe show a confirmation dialog if there are unsaved changes
// buffer_state.go_back(); // Example call
// TODO: Implement logic
}
// Selecting input fields usually means entering Edit mode (handled elsewhere)
// Selecting tables might mean focusing on them for editing/deletion (TODO)
AddTableFocus::ColumnsTable => {
if let Some(index) = add_table_state.column_table_state.selected() {
*command_message = format!("Selected column index {}", index);
// TODO: Add logic for editing/deleting selected column
} else {
*command_message = "No column selected".to_string();
}
} else { *command_message = "No column selected".to_string(); }
}
AddTableFocus::IndexesTable => {
if let Some(index) = add_table_state.index_table_state.selected() {
*command_message = format!("Selected index index {}", index);
// TODO: Add logic for editing/deleting selected index
} else {
*command_message = "No index selected".to_string();
}
} else { *command_message = "No index selected".to_string(); }
}
AddTableFocus::LinksTable => {
if let Some(index) = add_table_state.link_table_state.selected() {
*command_message = format!("Selected link index {}", index);
// TODO: Add logic for editing/deleting selected link
} else {
*command_message = "No link selected".to_string();
}
} else { *command_message = "No link selected".to_string(); }
}
_ => {
// For InputTableName, InputColumnName, InputColumnType,
// the main event loop should handle 'select' by potentially
// switching to Edit mode if not already in it.
// We don't need specific logic here for that.
_ => { // Input fields
*command_message = format!("Select on {:?}", current_focus);
handled = false; // Let main loop handle edit mode toggle maybe
}
}
// Keep handled = true for select actions unless specifically set to false
}
// --- Other General Keys (Ignore for add_table nav) ---
// --- Other General Keys ---
Some("toggle_sidebar") | Some("toggle_buffer_list") => {
handled = false; // Let global handler manage these
handled = false;
}
// --- No matching action ---
_ => handled = false, // Event not handled by add_table navigation
_ => handled = false,
}
// If focus changed TO a table, select the first row if nothing is selected
if handled && current_focus != add_table_state.current_focus {
// Update focus state if it changed and was handled
if handled && current_focus != new_focus {
add_table_state.current_focus = new_focus;
*command_message = format!("Focus set to {:?}", add_table_state.current_focus);
// Select first item when focusing a table
match add_table_state.current_focus {
AddTableFocus::ColumnsTable if add_table_state.column_table_state.selected().is_none() && !add_table_state.columns.is_empty() => {
add_table_state.column_table_state.select(Some(0));
@@ -206,10 +207,14 @@ pub fn handle_add_table_navigation(
AddTableFocus::LinksTable if add_table_state.link_table_state.selected().is_none() && !add_table_state.links.is_empty() => {
add_table_state.link_table_state.select(Some(0));
}
_ => {} // No action needed for other focus states
_ => {}
}
} else if !handled {
// If not handled by this specific navigation, clear the message potentially set by get_general_action
// command_message.clear(); // Optional: depends if you want default messages
}
handled
}
@@ -217,20 +222,19 @@ pub fn handle_add_table_navigation(
// Helper function for navigating up within a table state
// Returns true if navigation happened within the table, false if it reached the top
fn navigate_table_up(table_state: &mut TableState, item_count: usize) -> bool {
if item_count == 0 { return false; } // Cannot navigate empty table
if item_count == 0 { return false; }
let current_selection = table_state.selected();
match current_selection {
Some(index) => {
if index > 0 {
table_state.select(Some(index - 1));
true // Moved up within table
true
} else {
false // Was at the top
}
}
None => {
// If nothing selected, moving up could select the last item
table_state.select(Some(item_count - 1));
table_state.select(Some(item_count - 1)); // Select last item
true
}
}
@@ -239,20 +243,19 @@ fn navigate_table_up(table_state: &mut TableState, item_count: usize) -> bool {
// Helper function for navigating down within a table state
// Returns true if navigation happened within the table, false if it reached the bottom
fn navigate_table_down(table_state: &mut TableState, item_count: usize) -> bool {
if item_count == 0 { return false; } // Cannot navigate empty table
if item_count == 0 { return false; }
let current_selection = table_state.selected();
match current_selection {
Some(index) => {
if index < item_count - 1 {
table_state.select(Some(index + 1));
true // Moved down within table
true
} else {
false // Was at the bottom
}
}
None => {
// If nothing selected, moving down could select the first item
table_state.select(Some(0));
table_state.select(Some(0)); // Select first item
true
}
}

View File

@@ -2,3 +2,4 @@
pub mod auth_ro;
pub mod form_ro;
pub mod add_table_ro;

View File

@@ -0,0 +1,229 @@
// src/functions/modes/read_only/add_table_ro.rs
use crate::config::binds::key_sequences::KeySequenceTracker;
use crate::state::pages::add_table::AddTableState;
use crate::state::pages::canvas_state::CanvasState; // Use trait for common actions
use crate::state::app::state::AppState;
use std::error::Error;
// Re-use word navigation helpers if they are public or move them to a common module
// For now, duplicating them here for simplicity. Consider refactoring later.
#[derive(PartialEq)]
enum CharType {
Whitespace,
Alphanumeric,
Punctuation,
}
fn get_char_type(c: char) -> CharType {
if c.is_whitespace() { CharType::Whitespace }
else if c.is_alphanumeric() { CharType::Alphanumeric }
else { CharType::Punctuation }
}
fn find_next_word_start(text: &str, current_pos: usize) -> usize {
let chars: Vec<char> = text.chars().collect();
let len = chars.len();
if len == 0 || current_pos >= len { return len; }
let mut pos = current_pos;
let initial_type = get_char_type(chars[pos]);
while pos < len && get_char_type(chars[pos]) == initial_type { pos += 1; }
while pos < len && get_char_type(chars[pos]) == CharType::Whitespace { pos += 1; }
pos
}
fn find_word_end(text: &str, current_pos: usize) -> usize {
let chars: Vec<char> = text.chars().collect();
let len = chars.len();
if len == 0 { return 0; }
let mut pos = current_pos.min(len - 1);
if get_char_type(chars[pos]) == CharType::Whitespace {
pos = find_next_word_start(text, pos);
}
if pos >= len { return len.saturating_sub(1); }
let word_type = get_char_type(chars[pos]);
while pos < len && get_char_type(chars[pos]) == word_type { pos += 1; }
pos.saturating_sub(1).min(len.saturating_sub(1))
}
fn find_prev_word_start(text: &str, current_pos: usize) -> usize {
let chars: Vec<char> = text.chars().collect();
if chars.is_empty() || current_pos == 0 { return 0; }
let mut pos = current_pos.saturating_sub(1);
while pos > 0 && get_char_type(chars[pos]) == CharType::Whitespace { pos -= 1; }
if pos == 0 && get_char_type(chars[pos]) == CharType::Whitespace { return 0; }
let word_type = get_char_type(chars[pos]);
while pos > 0 && get_char_type(chars[pos - 1]) == word_type { pos -= 1; }
pos
}
// Note: find_prev_word_end might need adjustments based on desired behavior.
// This version finds the end of the word *before* the previous word start.
fn find_prev_word_end(text: &str, current_pos: usize) -> usize {
let prev_start = find_prev_word_start(text, current_pos);
if prev_start == 0 { return 0; }
// Find the end of the word that starts at prev_start - 1
find_word_end(text, prev_start.saturating_sub(1))
}
/// Executes read-only actions for the AddTable view canvas.
pub async fn execute_action(
action: &str,
app_state: &mut AppState, // Needed for focus_outside_canvas
state: &mut AddTableState,
ideal_cursor_column: &mut usize,
key_sequence_tracker: &mut KeySequenceTracker,
command_message: &mut String, // Keep for potential messages
) -> Result<String, Box<dyn Error>> {
// Use the CanvasState trait methods implemented for AddTableState
match action {
"move_up" => {
key_sequence_tracker.reset();
let num_fields = AddTableState::INPUT_FIELD_COUNT; // Use the constant
if num_fields == 0 { return Ok("No fields.".to_string()); }
let current_field = state.current_field();
if current_field > 0 {
let new_field = current_field - 1;
state.set_current_field(new_field);
let current_input = state.get_current_input();
let max_cursor_pos = current_input.len(); // Allow cursor at end
let new_pos = (*ideal_cursor_column).min(max_cursor_pos);
state.set_current_cursor_pos(new_pos);
} else {
// Optionally move focus outside canvas when moving up from the first field
// app_state.ui.focus_outside_canvas = true;
// return Ok("Focus moved above canvas".to_string());
}
Ok("".to_string())
}
"move_down" => {
key_sequence_tracker.reset();
let num_fields = AddTableState::INPUT_FIELD_COUNT;
if num_fields == 0 { return Ok("No fields.".to_string()); }
let current_field = state.current_field();
let last_field_index = num_fields - 1;
if current_field < last_field_index {
let new_field = current_field + 1;
state.set_current_field(new_field);
let current_input = state.get_current_input();
let max_cursor_pos = current_input.len(); // Allow cursor at end
let new_pos = (*ideal_cursor_column).min(max_cursor_pos);
state.set_current_cursor_pos(new_pos);
} else {
// Move focus outside canvas when moving down from the last field
app_state.ui.focus_outside_canvas = true;
// Set focus to the first element outside canvas (AddColumnButton)
state.current_focus = crate::state::pages::add_table::AddTableFocus::AddColumnButton;
key_sequence_tracker.reset();
return Ok("Focus moved below canvas".to_string());
}
Ok("".to_string())
}
"move_first_line" => {
key_sequence_tracker.reset();
if AddTableState::INPUT_FIELD_COUNT > 0 {
state.set_current_field(0);
let current_input = state.get_current_input();
let max_cursor_pos = current_input.len();
let new_pos = (*ideal_cursor_column).min(max_cursor_pos);
state.set_current_cursor_pos(new_pos);
*ideal_cursor_column = new_pos; // Update ideal column
}
Ok("".to_string())
}
"move_last_line" => {
key_sequence_tracker.reset();
let num_fields = AddTableState::INPUT_FIELD_COUNT;
if num_fields > 0 {
let last_field_index = num_fields - 1;
state.set_current_field(last_field_index);
let current_input = state.get_current_input();
let max_cursor_pos = current_input.len();
let new_pos = (*ideal_cursor_column).min(max_cursor_pos);
state.set_current_cursor_pos(new_pos);
*ideal_cursor_column = new_pos; // Update ideal column
}
Ok("".to_string())
}
"move_left" => {
let current_pos = state.current_cursor_pos();
let new_pos = current_pos.saturating_sub(1);
state.set_current_cursor_pos(new_pos);
*ideal_cursor_column = new_pos;
Ok("".to_string())
}
"move_right" => {
let current_input = state.get_current_input();
let current_pos = state.current_cursor_pos();
// Allow moving cursor one position past the end
if current_pos < current_input.len() {
let new_pos = current_pos + 1;
state.set_current_cursor_pos(new_pos);
*ideal_cursor_column = new_pos;
}
Ok("".to_string())
}
"move_word_next" => {
let current_input = state.get_current_input();
let new_pos = find_next_word_start(current_input, state.current_cursor_pos());
let final_pos = new_pos.min(current_input.len()); // Allow cursor at end
state.set_current_cursor_pos(final_pos);
*ideal_cursor_column = final_pos;
Ok("".to_string())
}
"move_word_end" => {
let current_input = state.get_current_input();
let current_pos = state.current_cursor_pos();
let new_pos = find_word_end(current_input, current_pos);
// If find_word_end returns current_pos, try starting search from next char
let final_pos = if new_pos == current_pos && current_pos < current_input.len() {
find_word_end(current_input, current_pos + 1)
} else {
new_pos
};
let max_valid_index = current_input.len(); // Allow cursor at end
let clamped_pos = final_pos.min(max_valid_index);
state.set_current_cursor_pos(clamped_pos);
*ideal_cursor_column = clamped_pos;
Ok("".to_string())
}
"move_word_prev" => {
let current_input = state.get_current_input();
let new_pos = find_prev_word_start(current_input, state.current_cursor_pos());
state.set_current_cursor_pos(new_pos);
*ideal_cursor_column = new_pos;
Ok("".to_string())
}
"move_word_end_prev" => {
let current_input = state.get_current_input();
let new_pos = find_prev_word_end(current_input, state.current_cursor_pos());
state.set_current_cursor_pos(new_pos);
*ideal_cursor_column = new_pos;
Ok("".to_string())
}
"move_line_start" => {
state.set_current_cursor_pos(0);
*ideal_cursor_column = 0;
Ok("".to_string())
}
"move_line_end" => {
let current_input = state.get_current_input();
let new_pos = current_input.len(); // Allow cursor at end
state.set_current_cursor_pos(new_pos);
*ideal_cursor_column = new_pos;
Ok("".to_string())
}
// Actions handled by main event loop (mode changes)
"enter_edit_mode_before" | "enter_edit_mode_after" | "enter_command_mode" | "exit_highlight_mode" => {
key_sequence_tracker.reset();
Ok("Mode change handled by main loop".to_string())
}
_ => {
key_sequence_tracker.reset();
command_message.clear(); // Clear message for unhandled actions
Ok(format!("Unknown read-only action: {}", action))
},
}
}

View File

@@ -1,13 +1,17 @@
// src/modes/canvas/edit.rs
use crate::config::binds::config::Config;
use crate::services::grpc_client::GrpcClient;
use crate::state::pages::{auth::{LoginState, RegisterState}, canvas_state::CanvasState};
use crate::state::pages::{
auth::{LoginState, RegisterState},
canvas_state::CanvasState,
};
use crate::state::pages::form::FormState;
use crate::state::pages::add_table::AddTableState; // Added
use crate::modes::handlers::event::EventOutcome;
use crate::functions::modes::edit::{auth_e, form_e};
use crate::functions::modes::edit::add_table_e; // Added
use crate::state::app::state::AppState;
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
// Removed duplicate/unused imports
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EditEventOutcome {
@@ -21,210 +25,319 @@ pub async fn handle_edit_event(
form_state: &mut FormState,
login_state: &mut LoginState,
register_state: &mut RegisterState,
add_table_state: &mut AddTableState, // Added
ideal_cursor_column: &mut usize,
// command_message: &mut String, // Removed as messages are returned
current_position: &mut u64,
total_count: u64,
grpc_client: &mut GrpcClient,
app_state: &AppState,
) -> Result<EditEventOutcome, Box<dyn std::error::Error>> {
// Global command mode check
// Global command mode check (should ideally be handled before calling this function)
if let Some("enter_command_mode") = config.get_action_for_key_in_mode(
&config.keybindings.global,
key.code,
key.modifiers
key.modifiers,
) {
return Ok(EditEventOutcome::Message("Switching to Command Mode...".to_string()));
// This mode change should likely be handled in event.rs
// Returning a message here might prevent the mode switch.
// Consider if this check is necessary here.
return Ok(EditEventOutcome::Message(
"Command mode entry handled globally.".to_string(),
));
}
// Common actions (save/revert)
if let Some(action) = config.get_action_for_key_in_mode(
&config.keybindings.common,
key.code,
key.modifiers
) {
key.modifiers,
).as_deref() {
if matches!(action, "save" | "revert") {
// Ensure all branches result in Result<String, Error> before the final Ok(...) wrap
let message_string: String = if app_state.ui.show_login {
auth_e::execute_common_action(
action,
login_state,
grpc_client,
current_position,
total_count
).await? // Results in String on success
total_count,
)
.await?
} else if app_state.ui.show_register {
// Keeping this block as requested
auth_e::execute_common_action(
action,
register_state,
grpc_client,
current_position,
total_count
).await? // Results in String on success
} else {
total_count,
)
.await? // Results in String on success
} else if app_state.ui.show_add_table {
// Placeholder - common actions for AddTable might be different
format!(
"Action '{}' not fully implemented for Add Table view here.",
action
)
} else { // Assuming FormState otherwise
let outcome = form_e::execute_common_action(
action,
form_state,
grpc_client,
current_position,
total_count
).await?; // This returns EventOutcome on success
// Extract the message string from the EventOutcome
total_count,
)
.await?;
match outcome {
EventOutcome::Ok(msg) => msg,
EventOutcome::DataSaved(_, msg) => msg,
_ => format!("Unexpected outcome from common action: {:?}", outcome),
_ => format!(
"Unexpected outcome from common action: {:?}",
outcome
),
}
};
// Wrap the resulting String message
return Ok(EditEventOutcome::Message(message_string));
}
}
// Edit-specific actions
if let Some(action) = config.get_edit_action_for_key(key.code, key.modifiers) {
if action == "exit" {
if let Some(action) =
config.get_edit_action_for_key(key.code, key.modifiers)
.as_deref() {
if action == "exit_edit_mode" {
// Handle exiting suggestion mode in Register view first
if app_state.ui.show_register && register_state.in_suggestion_mode {
// Call the action, get Result<String, Error>
let msg = auth_e::execute_edit_action(
"exit_suggestion_mode",
"exit_suggestion_mode", // Specific action for suggestion exit
key,
register_state,
ideal_cursor_column,
grpc_client, // Pass necessary args if needed by auth_e
current_position,
total_count,
)
.await?;
return Ok(EditEventOutcome::Message(msg));
} else {
// Signal exit from Edit mode
return Ok(EditEventOutcome::ExitEditMode);
}
}
// Special handling for role field suggestions (Register view only)
if app_state.ui.show_register && register_state.current_field() == 4 {
// Check if Tab was pressed to *enter* suggestion mode
if !register_state.in_suggestion_mode
&& key.code == KeyCode::Tab
&& key.modifiers == KeyModifiers::NONE
{
register_state.update_role_suggestions();
if !register_state.role_suggestions.is_empty() {
register_state.in_suggestion_mode = true;
register_state.selected_suggestion_index = Some(0); // Select first suggestion
return Ok(EditEventOutcome::Message(
"Suggestions shown".to_string(),
));
} else {
return Ok(EditEventOutcome::Message(
"No suggestions available".to_string(),
));
}
}
// Handle suggestion navigation/selection if already in suggestion mode
if register_state.in_suggestion_mode
&& matches!(
action,
"suggestion_down"
| "suggestion_up"
| "select_suggestion"
)
{
let msg = auth_e::execute_edit_action(
action, // Pass the specific suggestion action
key,
register_state,
ideal_cursor_column,
grpc_client,
current_position,
total_count,
).await?; // Results in String on success
// Wrap the String message
)
.await?;
return Ok(EditEventOutcome::Message(msg));
} else {
// Signal exit
return Ok(EditEventOutcome::ExitEditMode);
}
}
// Special handling for role field suggestions
// Execute other edit actions based on the current view
let msg = if app_state.ui.show_login {
auth_e::execute_edit_action(
action,
key,
login_state,
ideal_cursor_column,
grpc_client,
current_position,
total_count,
)
.await?
} else if app_state.ui.show_add_table {
add_table_e::execute_edit_action(
action,
key,
add_table_state,
ideal_cursor_column,
// Pass other necessary params if add_table_e needs them
)
.await?
} else if app_state.ui.show_register {
auth_e::execute_edit_action(
action,
key,
register_state,
ideal_cursor_column,
grpc_client,
current_position,
total_count,
)
.await?
} else {
// Assuming FormState otherwise
form_e::execute_edit_action(
action,
key,
form_state,
ideal_cursor_column,
grpc_client,
current_position,
total_count,
)
.await?
};
return Ok(EditEventOutcome::Message(msg));
}
// --- Character insertion ---
if let KeyCode::Char(c) = key.code {
// Exit suggestion mode in Register view if a character is typed
if app_state.ui.show_register && register_state.in_suggestion_mode {
register_state.in_suggestion_mode = false;
register_state.show_role_suggestions = false;
register_state.selected_suggestion_index = None;
}
// Execute insert_char action based on the current view
let msg = if app_state.ui.show_login {
auth_e::execute_edit_action(
"insert_char",
key, // Pass the key event containing the char
login_state,
ideal_cursor_column,
grpc_client,
current_position,
total_count,
)
.await?
} else if app_state.ui.show_add_table {
add_table_e::execute_edit_action(
"insert_char",
key,
add_table_state,
ideal_cursor_column,
)
.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 {
// Assuming FormState otherwise
form_e::execute_edit_action(
"insert_char",
key,
form_state,
ideal_cursor_column,
grpc_client,
current_position,
total_count,
)
.await?
};
// Update role suggestions after insertion if needed (Register view)
if app_state.ui.show_register && register_state.current_field() == 4 {
if !register_state.in_suggestion_mode && key.code == KeyCode::Tab && key.modifiers == KeyModifiers::NONE {
register_state.update_role_suggestions();
if !register_state.role_suggestions.is_empty() {
register_state.in_suggestion_mode = true;
register_state.selected_suggestion_index = Some(0);
return Ok(EditEventOutcome::Message("Suggestions shown".to_string()));
} else { // Added else here for clarity
return Ok(EditEventOutcome::Message("No suggestions available".to_string()));
}
}
register_state.update_role_suggestions();
}
// Execute other edit actions
let msg = if app_state.ui.show_login {
auth_e::execute_edit_action(
action,
key,
login_state,
ideal_cursor_column,
grpc_client,
current_position,
total_count
).await? // Results in String
} else if app_state.ui.show_register {
auth_e::execute_edit_action(
action,
key,
register_state,
ideal_cursor_column,
grpc_client,
current_position,
total_count
).await? // Results in String
} else {
form_e::execute_edit_action(
action,
key,
form_state,
ideal_cursor_column,
grpc_client,
current_position,
total_count
).await? // Results in String
};
// Wrap the resulting String message
return Ok(EditEventOutcome::Message(msg));
}
// Character insertion
if let KeyCode::Char(_) = key.code {
if app_state.ui.show_register && register_state.in_suggestion_mode {
register_state.in_suggestion_mode = false;
register_state.show_role_suggestions = false;
register_state.selected_suggestion_index = None;
}
// Execute insert_char action
let msg = if app_state.ui.show_login {
auth_e::execute_edit_action(
"insert_char",
key,
login_state,
ideal_cursor_column,
grpc_client,
current_position,
total_count
).await? // Results in String
} 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? // Results in String
} else {
form_e::execute_edit_action(
"insert_char",
key,
form_state,
ideal_cursor_column,
grpc_client,
current_position,
total_count
).await? // Results in String
};
// Wrap the resulting String message
return Ok(EditEventOutcome::Message(msg));
}
// Handle Backspace/Delete
// --- Handle Backspace/Delete ---
if matches!(key.code, KeyCode::Backspace | KeyCode::Delete) {
// Exit suggestion mode in Register view
if app_state.ui.show_register && register_state.in_suggestion_mode {
register_state.in_suggestion_mode = false;
register_state.show_role_suggestions = false;
register_state.selected_suggestion_index = None;
}
let action_str = if key.code == KeyCode::Backspace { "backspace" } else { "delete_char" };
// Ensure both branches result in a String *before* wrapping
let result_msg: String = if app_state.ui.show_register {
auth_e::execute_edit_action(
let action_str = if key.code == KeyCode::Backspace {
"delete_char_backward"
} else {
"delete_char_forward"
};
// Execute delete action based on the current view
let result_msg: String = if app_state.ui.show_login {
auth_e::execute_edit_action(
action_str,
key,
login_state,
ideal_cursor_column,
grpc_client,
current_position,
total_count,
)
.await?
} else if app_state.ui.show_add_table {
add_table_e::execute_edit_action(
action_str,
key,
add_table_state,
ideal_cursor_column,
)
.await?
} else if app_state.ui.show_register {
auth_e::execute_edit_action(
action_str,
key,
register_state,
ideal_cursor_column,
grpc_client,
current_position,
total_count
).await? // Results in String
total_count,
)
.await?
} else {
// Return String directly, not Ok(String)
"Action not applicable here".to_string()
}; // Semicolon here ends the if/else expression
// Assuming FormState otherwise
form_e::execute_edit_action(
action_str,
key,
form_state,
ideal_cursor_column,
grpc_client,
current_position,
total_count
).await?
};
// Update role suggestions after deletion if needed (Register view)
if app_state.ui.show_register && register_state.current_field() == 4 {
register_state.update_role_suggestions();
}
// Wrap the resulting String message
return Ok(EditEventOutcome::Message(result_msg));
}

View File

@@ -6,8 +6,9 @@ use crate::services::grpc_client::GrpcClient;
use crate::state::pages::{canvas_state::CanvasState, auth::RegisterState};
use crate::state::pages::auth::LoginState;
use crate::state::pages::form::FormState;
use crate::state::pages::add_table::AddTableState;
use crate::state::app::state::AppState;
use crate::functions::modes::read_only::{auth_ro, form_ro};
use crate::functions::modes::read_only::{auth_ro, form_ro, add_table_ro};
use crossterm::event::KeyEvent;
pub async fn handle_read_only_event(
@@ -17,6 +18,7 @@ pub async fn handle_read_only_event(
form_state: &mut FormState,
login_state: &mut LoginState,
register_state: &mut RegisterState,
add_table_state: &mut AddTableState,
key_sequence_tracker: &mut KeySequenceTracker,
current_position: &mut u64,
total_count: u64,
@@ -32,34 +34,17 @@ pub async fn handle_read_only_event(
}
if config.is_enter_edit_mode_after(key.code, key.modifiers) {
let (current_input, current_pos) = if app_state.ui.show_login { // Check Login first
(
login_state.get_current_input(),
login_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 {
(
form_state.get_current_input(),
form_state.current_cursor_pos(),
) // Default to Form
};
// Determine target state to adjust cursor
let target_state: &mut dyn CanvasState = if app_state.ui.show_login { login_state }
else if app_state.ui.show_register { register_state }
else if app_state.ui.show_add_table { add_table_state }
else { form_state };
let current_input = target_state.get_current_input();
let current_pos = target_state.current_cursor_pos();
if !current_input.is_empty() && current_pos < current_input.len() {
if app_state.ui.show_login {
login_state.set_current_cursor_pos(current_pos + 1);
*ideal_cursor_column = login_state.current_cursor_pos();
} 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);
*ideal_cursor_column = form_state.current_cursor_pos();
}
target_state.set_current_cursor_pos(current_pos + 1);
*ideal_cursor_column = target_state.current_cursor_pos();
}
*edit_mode_cooldown = true;
*command_message = "Entering Edit mode (after cursor)".to_string();
@@ -83,7 +68,7 @@ pub async fn handle_read_only_event(
key_sequence_tracker.add_key(key.code);
let sequence = key_sequence_tracker.get_sequence();
if let Some(action) = config.matches_key_sequence_generalized(&sequence) {
if let Some(action) = config.matches_key_sequence_generalized(&sequence).as_deref() {
let result = if app_state.ui.show_form && CONTEXT_ACTIONS_FORM.contains(&action) {
crate::tui::functions::form::handle_action(
action,
@@ -96,6 +81,15 @@ pub async fn handle_read_only_event(
.await?
} else if app_state.ui.show_login && CONTEXT_ACTIONS_LOGIN.contains(&action) { // Handle login context actions
crate::tui::functions::login::handle_action(action).await?
} else if app_state.ui.show_add_table {
add_table_ro::execute_action(
action,
app_state,
add_table_state,
ideal_cursor_column,
key_sequence_tracker,
command_message,
).await?
} else if app_state.ui.show_register{
auth_ro::execute_action(
action,
@@ -134,7 +128,7 @@ pub async fn handle_read_only_event(
}
if sequence.len() == 1 && !config.is_key_sequence_prefix(&sequence) {
if let Some(action) = config.get_read_only_action_for_key(key.code, key.modifiers) {
if let Some(action) = config.get_read_only_action_for_key(key.code, key.modifiers).as_deref() {
let result = if app_state.ui.show_form && CONTEXT_ACTIONS_FORM.contains(&action) {
crate::tui::functions::form::handle_action(
action,
@@ -147,6 +141,15 @@ pub async fn handle_read_only_event(
.await?
} else if app_state.ui.show_login && CONTEXT_ACTIONS_LOGIN.contains(&action) { // Handle login context actions
crate::tui::functions::login::handle_action(action).await?
} else if app_state.ui.show_add_table {
add_table_ro::execute_action(
action,
app_state,
add_table_state,
ideal_cursor_column,
key_sequence_tracker,
command_message,
).await?
} else if app_state.ui.show_register /* && CONTEXT_ACTIONS_REGISTER.contains(&action) */ { // Handle register general actions
auth_ro::execute_action(
action,
@@ -184,7 +187,7 @@ pub async fn handle_read_only_event(
} else {
key_sequence_tracker.reset();
if let Some(action) = config.get_read_only_action_for_key(key.code, key.modifiers) {
if let Some(action) = config.get_read_only_action_for_key(key.code, key.modifiers).as_deref() {
let result = if app_state.ui.show_form && CONTEXT_ACTIONS_FORM.contains(&action) {
crate::tui::functions::form::handle_action(
action,
@@ -195,8 +198,17 @@ pub async fn handle_read_only_event(
ideal_cursor_column,
)
.await?
} else if app_state.ui.show_login && CONTEXT_ACTIONS_LOGIN.contains(&action) { // Handle login context actions
} else if app_state.ui.show_login && CONTEXT_ACTIONS_LOGIN.contains(&action) {
crate::tui::functions::login::handle_action(action).await?
} else if app_state.ui.show_add_table {
add_table_ro::execute_action(
action,
app_state,
add_table_state,
ideal_cursor_column,
key_sequence_tracker,
command_message,
).await?
} else if app_state.ui.show_register /* && CONTEXT_ACTIONS_REGISTER.contains(&action) */ { // Handle register general actions
auth_ro::execute_action(
action,

View File

@@ -28,6 +28,7 @@ use crate::state::{
auth::{AuthState, LoginState, RegisterState},
admin::AdminState,
canvas_state::CanvasState,
add_table::AddTableState,
form::FormState,
intro::IntroState,
},
@@ -349,6 +350,7 @@ impl EventHandler {
form_state,
login_state,
register_state,
&mut admin_state.add_table_state,
&mut self.key_sequence_tracker,
current_position,
total_count,
@@ -383,7 +385,7 @@ impl EventHandler {
let (_should_exit, message) = read_only::handle_read_only_event(
app_state, key, config, form_state, login_state,
register_state, &mut self.key_sequence_tracker,
register_state, &mut admin_state.add_table_state, &mut self.key_sequence_tracker,
current_position, total_count, grpc_client,
&mut self.command_message, &mut self.edit_mode_cooldown,
&mut self.ideal_cursor_column,
@@ -431,6 +433,7 @@ impl EventHandler {
form_state,
login_state,
register_state,
&mut admin_state.add_table_state,
&mut self.ideal_cursor_column,
current_position,
total_count,

View File

@@ -2,6 +2,8 @@
use crate::state::app::state::AppState;
use crate::modes::handlers::event::EventHandler;
use crate::state::app::highlight::HighlightState;
use crate::state::pages::add_table::AddTableFocus;
use crate::state::pages::admin::AdminState;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AppMode {
@@ -29,17 +31,20 @@ impl ModeManager {
return AppMode::General;
}
if app_state.ui.show_login || app_state.ui.show_register {
if event_handler.is_edit_mode {
AppMode::Edit
let is_canvas_view = app_state.ui.show_login
|| app_state.ui.show_register
|| app_state.ui.show_form
|| app_state.ui.show_add_table;
if is_canvas_view {
if app_state.ui.focus_outside_canvas {
AppMode::General
} else {
AppMode::ReadOnly
}
} else if app_state.ui.show_form {
if event_handler.is_edit_mode {
AppMode::Edit
} else {
AppMode::ReadOnly
if event_handler.is_edit_mode {
AppMode::Edit
} else {
AppMode::ReadOnly
}
}
} else {
AppMode::General

View File

@@ -6,6 +6,7 @@ use crate::config::binds::key_sequences::KeySequenceTracker;
use crate::services::grpc_client::GrpcClient;
use crate::state::app::state::AppState;
use crate::state::pages::auth::{LoginState, RegisterState};
use crate::state::pages::add_table::AddTableState;
use crate::state::pages::form::FormState;
use crate::modes::handlers::event::EventOutcome;
use crate::modes::read_only; // Import the ReadOnly handler
@@ -21,6 +22,7 @@ pub async fn handle_highlight_event(
form_state: &mut FormState,
login_state: &mut LoginState,
register_state: &mut RegisterState,
add_table_state: &mut AddTableState,
key_sequence_tracker: &mut KeySequenceTracker,
current_position: &mut u64,
total_count: u64,
@@ -38,6 +40,7 @@ pub async fn handle_highlight_event(
form_state,
login_state,
register_state,
add_table_state,
key_sequence_tracker,
current_position,
total_count,

View File

@@ -79,7 +79,7 @@ impl Default for AddTableState {
}
impl AddTableState {
const INPUT_FIELD_COUNT: usize = 3;
pub const INPUT_FIELD_COUNT: usize = 3;
}
// Implement CanvasState for the input fields