From 8159a844473607b4b333c7efd64a145731e1fb06 Mon Sep 17 00:00:00 2001 From: filipriec Date: Wed, 16 Apr 2025 23:31:28 +0200 Subject: [PATCH] movement --- client/src/components/admin/add_table.rs | 214 +++++++++++------- client/src/functions/modes/navigation.rs | 1 + .../modes/navigation/add_table_nav.rs | 169 ++++++++++++++ client/src/modes/handlers/event.rs | 13 +- client/src/modes/handlers/mode_manager.rs | 2 +- client/src/state/pages/add_table.rs | 12 +- 6 files changed, 318 insertions(+), 93 deletions(-) create mode 100644 client/src/functions/modes/navigation/add_table_nav.rs diff --git a/client/src/components/admin/add_table.rs b/client/src/components/admin/add_table.rs index cc10715..b4366c6 100644 --- a/client/src/components/admin/add_table.rs +++ b/client/src/components/admin/add_table.rs @@ -5,7 +5,7 @@ use crate::state::{ pages::add_table::{AddTableFocus, AddTableState}, }; use ratatui::{ - layout::{Alignment, Constraint, Direction, Layout, Rect}, + layout::{Alignment, Constraint, Direction, Layout, Rect, Margin}, style::{Modifier, Style, Stylize}, text::{Line, Span}, widgets::{ @@ -14,19 +14,20 @@ use ratatui::{ Frame, }; -/// Renders the detailed page for adding a new table. +/// Renders the detailed page for adding a new table with Title -> Canvas -> Results structure. pub fn render_add_table( f: &mut Frame, area: Rect, theme: &Theme, - app_state: &AppState, - add_table_state: &mut AddTableState, + app_state: &AppState, // Read-only access needed + add_table_state: &mut AddTableState, // Mutable for TableState ) { let focused_style = Style::default().fg(theme.highlight); - let unfocused_style = Style::default().fg(theme.border); + // let unfocused_style = Style::default().fg(theme.border); // Keep if needed elsewhere let base_style = Style::default().fg(theme.fg); - let header_style = Style::default().fg(theme.secondary); + let title_style = Style::default().fg(theme.accent).add_modifier(Modifier::BOLD); let selected_row_style = Style::default().bg(theme.highlight).fg(theme.bg); + let placeholder_style = Style::default().fg(theme.secondary).add_modifier(Modifier::ITALIC); // --- Main Block --- let main_block = Block::default() @@ -35,72 +36,83 @@ pub fn render_add_table( .border_type(BorderType::Rounded) .border_style(Style::default().fg(theme.accent)) .style(Style::default().bg(theme.bg)); - let inner_area = main_block.inner(area); + let inner_area = main_block.inner(area); // Area inside the main border f.render_widget(main_block, area); - // --- Layout --- + // --- Layout (Single Vertical Column) --- let constraints = [ Constraint::Length(1), // Profile line - Constraint::Length(1), // Table Name line + Constraint::Length(1), // Table Name line (Input) Constraint::Length(1), // Spacer - Constraint::Length(1), // Columns Header - Constraint::Min(3), // Columns Table (at least 3 rows visible) + Constraint::Length(1), // Columns Title + Constraint::Length(2), // Columns Canvas (Placeholder - Height 2) + Constraint::Min(3), // Columns Results Table Constraint::Length(1), // Spacer - Constraint::Length(1), // Indexes Header - Constraint::Min(2), // Indexes Table + Constraint::Length(1), // Indexes Title + Constraint::Length(2), // Indexes Canvas (Placeholder - Height 2) + Constraint::Min(2), // Indexes Results Table Constraint::Length(1), // Spacer - Constraint::Length(1), // Links Header - Constraint::Min(3), // Links Table + Constraint::Length(1), // Links Title + Constraint::Length(2), // Links Canvas (Placeholder - Height 2) + Constraint::Min(3), // Links Results Table Constraint::Length(1), // Spacer Constraint::Length(1), // Action Buttons ]; let chunks = Layout::default() .direction(Direction::Vertical) .constraints(constraints) - .margin(1) // Add margin inside the main block + .margin(1) // Margin inside the main block .split(inner_area); // --- Helper: Get Border Style based on Focus --- - let get_border_style = |focus: AddTableFocus| { - if add_table_state.current_focus == focus { - focused_style + // Use this for elements that should show focus, like input placeholders or tables + let get_focus_border_style = |focus_target: AddTableFocus| { + if add_table_state.current_focus == focus_target { + Style::default().fg(theme.highlight) } else { - unfocused_style + Style::default().fg(theme.border) // Keep border for tables/buttons + } + }; + // Helper for placeholder focus (no border, just text style change) + let get_placeholder_style = |focus_target: AddTableFocus| { + if add_table_state.current_focus == focus_target { + placeholder_style.fg(theme.highlight).remove_modifier(Modifier::ITALIC) + } else { + placeholder_style } }; + // --- 1. Profile Line --- - // TODO: Fetch actual selected profile if needed, using app_state.selected_profile or admin_state let profile_text = format!("Profile: {}", add_table_state.profile_name); f.render_widget(Paragraph::new(profile_text).style(base_style), chunks[0]); - // --- 2. Table Name Line --- + // --- 2. Table Name Line (Input) --- let table_name_block = Block::default() - .borders(Borders::BOTTOM) - .border_style(get_border_style(AddTableFocus::TableName)); - // Basic rendering for now, cursor needs event handling logic + .borders(Borders::ALL) // Box around input + .border_type(BorderType::Plain) + .border_style(get_focus_border_style(AddTableFocus::TableName)); let table_name_text = format!("Table Name: [ {} ]", add_table_state.table_name); let table_name_paragraph = Paragraph::new(table_name_text) .style(base_style) .block(table_name_block); f.render_widget(table_name_paragraph, chunks[1]); - // --- 4. Columns Header --- - let columns_header = Paragraph::new(Line::from(vec![ - Span::styled(" Name ", header_style), - Span::raw("| "), - Span::styled("Type ", header_style), - Span::raw("| "), - Span::styled(" <- Add Column (Ctrl+A)", header_style.add_modifier(Modifier::ITALIC)), - ])); - f.render_widget(columns_header, chunks[3]); + // --- 4. Columns Title --- + f.render_widget(Paragraph::new("Columns").style(title_style), chunks[3]); - // --- 5. Columns Table --- - let columns_block = Block::default() - .borders(Borders::TOP | Borders::BOTTOM) // Only top/bottom for visual separation - .border_style(get_border_style(AddTableFocus::Columns)); - let columns_table_area = columns_block.inner(chunks[4]); - f.render_widget(columns_block, chunks[4]); + // --- 5. Columns Canvas (Placeholder - No Border, Height 2) --- + let columns_canvas_text = Paragraph::new("Name: [input] | Type: [input]") + .style(get_placeholder_style(AddTableFocus::ColumnInput)) // Style text based on focus + .alignment(Alignment::Center); + f.render_widget(columns_canvas_text, chunks[4]); // Render directly into the chunk + + // --- 6. Columns Results Table --- + let columns_table_block = Block::default() + .borders(Borders::TOP) // Separator from canvas + .border_style(get_focus_border_style(AddTableFocus::ColumnsTable)); // Focus on table border + let columns_table_area = columns_table_block.inner(chunks[5]); + f.render_widget(columns_table_block, chunks[5]); let column_rows = add_table_state.columns.iter().map(|col| { Row::new(vec![ @@ -111,34 +123,37 @@ pub fn render_add_table( }); let column_widths = [Constraint::Percentage(50), Constraint::Percentage(50)]; let columns_table = Table::new(column_rows, column_widths) + .header(Row::new(vec!["Name", "Type"]).style(Style::default().fg(theme.secondary)).bottom_margin(1)) .highlight_style(selected_row_style) - .highlight_symbol("* "); // Indicate selection + .highlight_symbol("* "); f.render_stateful_widget( columns_table, columns_table_area, &mut add_table_state.column_table_state, ); - // --- 7. Indexes Header --- - let indexes_header = Paragraph::new(Line::from(vec![ - Span::styled(" Column Name ", header_style), - Span::raw("| "), - Span::styled(" <- Add Index (Ctrl+I), Remove (Ctrl+X)", header_style.add_modifier(Modifier::ITALIC)), - ])); - f.render_widget(indexes_header, chunks[6]); + // --- 8. Indexes Title --- + f.render_widget(Paragraph::new("Indexes").style(title_style), chunks[7]); - // --- 8. Indexes Table --- - let indexes_block = Block::default() - .borders(Borders::TOP | Borders::BOTTOM) - .border_style(get_border_style(AddTableFocus::Indexes)); - let indexes_table_area = indexes_block.inner(chunks[7]); - f.render_widget(indexes_block, chunks[7]); + // --- 9. Indexes Canvas (Placeholder - No Border, Height 2) --- + let indexes_canvas_text = Paragraph::new("Column Name: [input]") + .style(get_placeholder_style(AddTableFocus::IndexInput)) + .alignment(Alignment::Center); + f.render_widget(indexes_canvas_text, chunks[8]); + + // --- 10. Indexes Results Table --- + let indexes_table_block = Block::default() + .borders(Borders::TOP) + .border_style(get_focus_border_style(AddTableFocus::IndexesTable)); + let indexes_table_area = indexes_table_block.inner(chunks[9]); + f.render_widget(indexes_table_block, chunks[9]); let index_rows = add_table_state.indexes.iter().map(|idx_col_name| { Row::new(vec![Cell::from(idx_col_name.as_str())]).style(base_style) }); let index_widths = [Constraint::Percentage(100)]; let indexes_table = Table::new(index_rows, index_widths) + .header(Row::new(vec!["Indexed Column"]).style(Style::default().fg(theme.secondary)).bottom_margin(1)) .highlight_style(selected_row_style) .highlight_symbol("* "); f.render_stateful_widget( @@ -147,25 +162,24 @@ pub fn render_add_table( &mut add_table_state.index_table_state, ); - // --- 10. Links Header --- - let links_header = Paragraph::new(Line::from(vec![ - Span::styled(" Linked Table ", header_style), - Span::raw("| "), - Span::styled("Required? ", header_style), - Span::raw("| "), - Span::styled(" <- Toggle Required (Space)", header_style.add_modifier(Modifier::ITALIC)), - ])); - f.render_widget(links_header, chunks[9]); + // --- 12. Links Title --- + f.render_widget(Paragraph::new("Links (Dependencies)").style(title_style), chunks[11]); - // --- 11. Links Table --- - let links_block = Block::default() - .borders(Borders::TOP | Borders::BOTTOM) - .border_style(get_border_style(AddTableFocus::Links)); - let links_table_area = links_block.inner(chunks[10]); - f.render_widget(links_block, chunks[10]); + // --- 13. Links Canvas (Placeholder - No Border, Height 2) --- + let links_canvas_text = Paragraph::new("Linked Table: [input] | Required: [ ]") + .style(get_placeholder_style(AddTableFocus::LinkInput)) + .alignment(Alignment::Center); + f.render_widget(links_canvas_text, chunks[12]); + + // --- 14. Links Results Table --- + let links_table_block = Block::default() + .borders(Borders::TOP) + .border_style(get_focus_border_style(AddTableFocus::LinksTable)); + let links_table_area = links_table_block.inner(chunks[13]); + f.render_widget(links_table_block, chunks[13]); let link_rows = add_table_state.links.iter().map(|link| { - let required_text = if link.is_required { "[X] Yes" } else { "[ ] No " }; // Pad No for alignment + let required_text = if link.is_required { "[X] Yes" } else { "[ ] No " }; Row::new(vec![ Cell::from(link.linked_table_name.as_str()), Cell::from(required_text), @@ -174,6 +188,7 @@ pub fn render_add_table( }); let link_widths = [Constraint::Percentage(70), Constraint::Percentage(30)]; let links_table = Table::new(link_rows, link_widths) + .header(Row::new(vec!["Linked Table", "Required?"]).style(Style::default().fg(theme.secondary)).bottom_margin(1)) .highlight_style(selected_row_style) .highlight_symbol("* "); f.render_stateful_widget( @@ -182,28 +197,51 @@ pub fn render_add_table( &mut add_table_state.link_table_state, ); - // --- 13. Action Buttons --- + // --- 16. Action Buttons --- let button_layout = Layout::default() .direction(Direction::Horizontal) .constraints([Constraint::Percentage(50), Constraint::Percentage(50)]) - .split(chunks[12]); + .split(chunks[15]); // Use the last chunk index - let save_button = Paragraph::new("[ Save Table ]") - .alignment(Alignment::Center) - .style(if add_table_state.current_focus == AddTableFocus::SaveButton { - selected_row_style // Use selected style for focused button - } else { - base_style - }); - let cancel_button = Paragraph::new("[ Cancel ]") - .alignment(Alignment::Center) - .style(if add_table_state.current_focus == AddTableFocus::CancelButton { - selected_row_style - } else { - base_style - }); + // Style buttons similar to login page + let save_active = add_table_state.current_focus == AddTableFocus::SaveButton; + let mut save_style = Style::default().fg(theme.fg); + let mut save_border = Style::default().fg(theme.border); + if save_active { + save_style = save_style.fg(theme.highlight).add_modifier(Modifier::BOLD); + save_border = save_border.fg(theme.accent); + } + f.render_widget( + Paragraph::new("Save Table") + .style(save_style) + .alignment(Alignment::Center) + .block( + Block::default() + .borders(Borders::ALL) + .border_type(BorderType::Plain) + .border_style(save_border), + ), + button_layout[0], + ); - f.render_widget(save_button, button_layout[0]); - f.render_widget(cancel_button, button_layout[1]); + let cancel_active = add_table_state.current_focus == AddTableFocus::CancelButton; + let mut cancel_style = Style::default().fg(theme.fg); + let mut cancel_border = Style::default().fg(theme.border); + if cancel_active { + cancel_style = cancel_style.fg(theme.highlight).add_modifier(Modifier::BOLD); + cancel_border = cancel_border.fg(theme.accent); + } + f.render_widget( + Paragraph::new("Cancel") + .style(cancel_style) + .alignment(Alignment::Center) + .block( + Block::default() + .borders(Borders::ALL) + .border_type(BorderType::Plain) + .border_style(cancel_border), + ), + button_layout[1], + ); } diff --git a/client/src/functions/modes/navigation.rs b/client/src/functions/modes/navigation.rs index 7b6121e..cfa5e47 100644 --- a/client/src/functions/modes/navigation.rs +++ b/client/src/functions/modes/navigation.rs @@ -1,3 +1,4 @@ // src/functions/modes/navigation.rs pub mod admin_nav; +pub mod add_table_nav; diff --git a/client/src/functions/modes/navigation/add_table_nav.rs b/client/src/functions/modes/navigation/add_table_nav.rs new file mode 100644 index 0000000..51c675a --- /dev/null +++ b/client/src/functions/modes/navigation/add_table_nav.rs @@ -0,0 +1,169 @@ +// src/functions/modes/navigation/add_table_nav.rs +use crate::state::pages::add_table::{AddTableFocus, AddTableState}; +use crate::state::app::state::AppState; // If needed for list lengths later + +/// Handles navigation events specifically for the Add Table page. +/// Returns true if the event was handled, false otherwise. +pub fn handle_navigation( + state: &mut AddTableState, + action: &str, + // app_state: &AppState, // Add if needed for list lengths +) -> bool { + let current_focus = state.current_focus; + let mut handled = true; // Assume handled unless proven otherwise + + match action { + "move_up" => { + state.current_focus = match current_focus { + AddTableFocus::TableName => AddTableFocus::CancelButton, // Wrap around top + AddTableFocus::ColumnInput => AddTableFocus::TableName, + AddTableFocus::ColumnsTable => { + // Navigate within table or move focus up + if !navigate_table_up(&mut state.column_table_state, state.columns.len()) { + state.current_focus = AddTableFocus::ColumnInput; + } + AddTableFocus::ColumnsTable // Keep focus here while navigating table + } + AddTableFocus::IndexInput => AddTableFocus::ColumnsTable, + AddTableFocus::IndexesTable => { + if !navigate_table_up(&mut state.index_table_state, state.indexes.len()) { + state.current_focus = AddTableFocus::IndexInput; + } + AddTableFocus::IndexesTable + } + AddTableFocus::LinkInput => AddTableFocus::IndexesTable, + AddTableFocus::LinksTable => { + if !navigate_table_up(&mut state.link_table_state, state.links.len()) { + state.current_focus = AddTableFocus::LinkInput; + } + AddTableFocus::LinksTable + } + AddTableFocus::SaveButton | AddTableFocus::CancelButton => AddTableFocus::LinksTable, + } + } + "move_down" => { + state.current_focus = match current_focus { + AddTableFocus::TableName => AddTableFocus::ColumnInput, + AddTableFocus::ColumnInput => AddTableFocus::ColumnsTable, + AddTableFocus::ColumnsTable => { + if !navigate_table_down(&mut state.column_table_state, state.columns.len()) { + state.current_focus = AddTableFocus::IndexInput; + } + AddTableFocus::ColumnsTable + } + AddTableFocus::IndexInput => AddTableFocus::IndexesTable, + AddTableFocus::IndexesTable => { + if !navigate_table_down(&mut state.index_table_state, state.indexes.len()) { + state.current_focus = AddTableFocus::LinkInput; + } + AddTableFocus::IndexesTable + } + AddTableFocus::LinkInput => AddTableFocus::LinksTable, + AddTableFocus::LinksTable => { + if !navigate_table_down(&mut state.link_table_state, state.links.len()) { + // Move to buttons after table + state.current_focus = AddTableFocus::SaveButton; + } + AddTableFocus::LinksTable + } + AddTableFocus::SaveButton | AddTableFocus::CancelButton => AddTableFocus::TableName, // Wrap around bottom + } + } + "next_option" => { // Typically Tab or Right arrow + state.current_focus = match current_focus { + // Simple vertical flow for now, like move_down + AddTableFocus::TableName => AddTableFocus::ColumnInput, + AddTableFocus::ColumnInput => AddTableFocus::ColumnsTable, + AddTableFocus::ColumnsTable => AddTableFocus::IndexInput, + AddTableFocus::IndexInput => AddTableFocus::IndexesTable, + AddTableFocus::IndexesTable => AddTableFocus::LinkInput, + AddTableFocus::LinkInput => AddTableFocus::LinksTable, + AddTableFocus::LinksTable => AddTableFocus::SaveButton, + AddTableFocus::SaveButton => AddTableFocus::CancelButton, + AddTableFocus::CancelButton => AddTableFocus::TableName, // Wrap + } + } + "previous_option" => { // Typically Shift+Tab or Left arrow + state.current_focus = match current_focus { + // Simple vertical flow upwards + AddTableFocus::TableName => AddTableFocus::CancelButton, // Wrap + AddTableFocus::ColumnInput => AddTableFocus::TableName, + AddTableFocus::ColumnsTable => AddTableFocus::ColumnInput, + AddTableFocus::IndexInput => AddTableFocus::ColumnsTable, + AddTableFocus::IndexesTable => AddTableFocus::IndexInput, + AddTableFocus::LinkInput => AddTableFocus::IndexesTable, + AddTableFocus::LinksTable => AddTableFocus::LinkInput, + AddTableFocus::SaveButton => AddTableFocus::LinksTable, + AddTableFocus::CancelButton => AddTableFocus::SaveButton, + } + } + "select" => { + // TODO: Implement select action based on current_focus + // e.g., Enter edit mode for TableName/Inputs, toggle link required, trigger save/cancel + handled = false; // Mark as not handled for now + } + _ => handled = false, // Action not relevant for this navigation + } + + // If focus changed to a table, select the first row if nothing is selected + if handled && current_focus != state.current_focus { + match state.current_focus { + AddTableFocus::ColumnsTable if state.column_table_state.selected().is_none() && !state.columns.is_empty() => { + state.column_table_state.select(Some(0)); + } + AddTableFocus::IndexesTable if state.index_table_state.selected().is_none() && !state.indexes.is_empty() => { + state.index_table_state.select(Some(0)); + } + AddTableFocus::LinksTable if state.link_table_state.selected().is_none() && !state.links.is_empty() => { + state.link_table_state.select(Some(0)); + } + _ => {} // No action needed for other focus states + } + } + + + handled +} + + +// 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 ratatui::widgets::TableState, item_count: usize) -> bool { + if item_count == 0 { return false; } // Cannot navigate empty table + 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 + } else { + false // Was at the top + } + } + None => { + table_state.select(Some(item_count - 1)); // Select last item if nothing selected + true + } + } +} + +// 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 ratatui::widgets::TableState, item_count: usize) -> bool { + if item_count == 0 { return false; } // Cannot navigate empty table + 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 + } else { + false // Was at the bottom + } + } + None => { + table_state.select(Some(0)); // Select first item if nothing selected + true + } + } +} diff --git a/client/src/modes/handlers/event.rs b/client/src/modes/handlers/event.rs index 38201fc..1e79aaf 100644 --- a/client/src/modes/handlers/event.rs +++ b/client/src/modes/handlers/event.rs @@ -39,7 +39,7 @@ use crate::modes::{ highlight::highlight, general::{navigation, dialog}, }; -use crate::functions::modes::navigation::admin_nav; +use crate::functions::modes::navigation::{admin_nav, add_table_nav}; use crate::config::binds::key_sequences::KeySequenceTracker; #[derive(Debug, Clone, PartialEq, Eq)] @@ -175,6 +175,17 @@ impl EventHandler { return Ok(EventOutcome::Ok(self.command_message.clone())); } } + // --- Add Table Page Navigation --- + if app_state.ui.show_add_table { + if let Some(action) = config.get_general_action(key.code, key.modifiers) { + if add_table_nav::handle_navigation( + &mut admin_state.add_table_state, + action, + ) { + return Ok(EventOutcome::Ok(String::new())); + } + } + } let nav_outcome = navigation::handle_navigation_event( key, diff --git a/client/src/modes/handlers/mode_manager.rs b/client/src/modes/handlers/mode_manager.rs index 4af0f30..7afe049 100644 --- a/client/src/modes/handlers/mode_manager.rs +++ b/client/src/modes/handlers/mode_manager.rs @@ -25,7 +25,7 @@ impl ModeManager { return AppMode::Highlight; } - if app_state.ui.focus_outside_canvas { + if app_state.ui.focus_outside_canvas || app_state.ui.show_add_table{ return AppMode::General; } diff --git a/client/src/state/pages/add_table.rs b/client/src/state/pages/add_table.rs index b1efc53..cca12ce 100644 --- a/client/src/state/pages/add_table.rs +++ b/client/src/state/pages/add_table.rs @@ -17,9 +17,15 @@ pub struct LinkDefinition { pub enum AddTableFocus { #[default] TableName, - Columns, - Indexes, - Links, + // Input areas (placeholders for now) + ColumnInput, + IndexInput, + LinkInput, + // Result Tables + ColumnsTable, + IndexesTable, + LinksTable, + // Buttons SaveButton, CancelButton, }