Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d0f958a68 | ||
|
|
b82f50b76b | ||
|
|
0ab11a9bf9 | ||
|
|
d28c310704 | ||
|
|
2e1d7fdf2b |
@@ -6,12 +6,11 @@ use crate::state::app::state::AppState;
|
|||||||
use ratatui::{
|
use ratatui::{
|
||||||
layout::{Alignment, Constraint, Direction, Layout, Rect},
|
layout::{Alignment, Constraint, Direction, Layout, Rect},
|
||||||
style::Style,
|
style::Style,
|
||||||
text::{Line, Span, Text}, // Added Text
|
text::{Line, Span, Text},
|
||||||
widgets::{Block, BorderType, Borders, List, ListItem, Paragraph},
|
widgets::{Block, BorderType, Borders, List, ListItem, Paragraph},
|
||||||
Frame,
|
Frame,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Renders the view specific to admin users with a three-pane layout.
|
|
||||||
pub fn render_admin_panel_admin(
|
pub fn render_admin_panel_admin(
|
||||||
f: &mut Frame,
|
f: &mut Frame,
|
||||||
area: Rect,
|
area: Rect,
|
||||||
@@ -19,15 +18,13 @@ pub fn render_admin_panel_admin(
|
|||||||
admin_state: &mut AdminState,
|
admin_state: &mut AdminState,
|
||||||
theme: &Theme,
|
theme: &Theme,
|
||||||
) {
|
) {
|
||||||
// Split vertically: Top for panes, Bottom for buttons
|
|
||||||
let main_chunks = Layout::default()
|
let main_chunks = Layout::default()
|
||||||
.direction(Direction::Vertical)
|
.direction(Direction::Vertical)
|
||||||
.constraints([Constraint::Min(0), Constraint::Length(1)].as_ref()) // 1 line for buttons
|
.constraints([Constraint::Min(0), Constraint::Length(1)].as_ref())
|
||||||
.split(area);
|
.split(area);
|
||||||
let panes_area = main_chunks[0];
|
let panes_area = main_chunks[0];
|
||||||
let buttons_area = main_chunks[1];
|
let buttons_area = main_chunks[1];
|
||||||
|
|
||||||
// Split the top area into three panes: Profiles | Tables | Dependencies
|
|
||||||
let pane_chunks = Layout::default()
|
let pane_chunks = Layout::default()
|
||||||
.direction(Direction::Horizontal)
|
.direction(Direction::Horizontal)
|
||||||
.constraints([
|
.constraints([
|
||||||
@@ -35,207 +32,148 @@ pub fn render_admin_panel_admin(
|
|||||||
Constraint::Percentage(40), // Tables
|
Constraint::Percentage(40), // Tables
|
||||||
Constraint::Percentage(35), // Dependencies
|
Constraint::Percentage(35), // Dependencies
|
||||||
].as_ref())
|
].as_ref())
|
||||||
.split(panes_area); // Use the whole area directly
|
.split(panes_area);
|
||||||
|
|
||||||
let profiles_pane = pane_chunks[0];
|
let profiles_pane = pane_chunks[0];
|
||||||
let tables_pane = pane_chunks[1];
|
let tables_pane = pane_chunks[1];
|
||||||
let deps_pane = pane_chunks[2];
|
let deps_pane = pane_chunks[2];
|
||||||
|
|
||||||
// --- Profiles Pane (Left) ---
|
// --- Profiles Pane (Left) ---
|
||||||
let profile_focus = admin_state.current_focus == AdminFocus::Profiles;
|
let profile_pane_has_focus = matches!(admin_state.current_focus, AdminFocus::ProfilesPane | AdminFocus::InsideProfilesList);
|
||||||
let profile_border_style = if profile_focus {
|
let profile_border_style = if profile_pane_has_focus {
|
||||||
Style::default().fg(theme.highlight)
|
Style::default().fg(theme.highlight)
|
||||||
} else {
|
} else {
|
||||||
Style::default().fg(theme.border)
|
Style::default().fg(theme.border)
|
||||||
};
|
};
|
||||||
|
let profiles_block = Block::default().title(" Profiles ").borders(Borders::ALL).border_type(BorderType::Rounded).border_style(profile_border_style);
|
||||||
// Block for the profiles pane
|
let profiles_inner_area = profiles_block.inner(profiles_pane);
|
||||||
let profiles_block = Block::default()
|
f.render_widget(profiles_block, profiles_pane);
|
||||||
.title(" Profiles ")
|
let profile_list_items: Vec<ListItem> = app_state.profile_tree.profiles.iter().enumerate().map(|(idx, profile)| {
|
||||||
.borders(Borders::ALL)
|
let is_persistently_selected = admin_state.selected_profile_index == Some(idx);
|
||||||
.border_type(BorderType::Rounded)
|
let is_nav_highlighted = admin_state.profile_list_state.selected() == Some(idx) && admin_state.current_focus == AdminFocus::InsideProfilesList;
|
||||||
.border_style(profile_border_style);
|
let prefix = if is_persistently_selected { "[*] " } else { "[ ] " };
|
||||||
let profiles_inner_area = profiles_block.inner(profiles_pane); // Get inner area for list
|
let item_style = if is_nav_highlighted { Style::default().fg(theme.highlight).add_modifier(ratatui::style::Modifier::BOLD) }
|
||||||
f.render_widget(profiles_block, profiles_pane); // Render the block itself
|
else if is_persistently_selected { Style::default().fg(theme.accent) }
|
||||||
|
else { Style::default().fg(theme.fg) };
|
||||||
// Create profile list items
|
ListItem::new(Line::from(vec![Span::styled(prefix, item_style), Span::styled(&profile.name, item_style)]))
|
||||||
let profile_list_items: Vec<ListItem> = app_state
|
}).collect();
|
||||||
.profile_tree
|
|
||||||
.profiles
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.map(|(idx, profile)| {
|
|
||||||
// Check persistent selection for prefix, navigation state for style/highlight
|
|
||||||
let is_selected = admin_state.selected_profile_index == Some(idx);
|
|
||||||
let prefix = if is_selected { "[*] " } else { "[ ] " };
|
|
||||||
let style = if is_selected { // Style based on selection too
|
|
||||||
Style::default().fg(theme.highlight).add_modifier(ratatui::style::Modifier::BOLD)
|
|
||||||
} else {
|
|
||||||
Style::default().fg(theme.fg)
|
|
||||||
};
|
|
||||||
ListItem::new(Line::from(vec![
|
|
||||||
Span::styled(prefix, style),
|
|
||||||
Span::styled(&profile.name, style)
|
|
||||||
]))
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// Build and render profile list inside the block's inner area
|
|
||||||
let profile_list = List::new(profile_list_items)
|
let profile_list = List::new(profile_list_items)
|
||||||
// Highlight style depends only on Profiles focus
|
.highlight_style(if admin_state.current_focus == AdminFocus::InsideProfilesList { Style::default().add_modifier(ratatui::style::Modifier::REVERSED) } else { Style::default() })
|
||||||
.highlight_style(if profile_focus {
|
.highlight_symbol(if admin_state.current_focus == AdminFocus::InsideProfilesList { "> " } else { " " });
|
||||||
Style::default().add_modifier(ratatui::style::Modifier::REVERSED)
|
|
||||||
} else {
|
|
||||||
Style::default()
|
|
||||||
})
|
|
||||||
.highlight_symbol(if profile_focus { "> " } else { " " });
|
|
||||||
|
|
||||||
f.render_stateful_widget(profile_list, profiles_inner_area, &mut admin_state.profile_list_state);
|
f.render_stateful_widget(profile_list, profiles_inner_area, &mut admin_state.profile_list_state);
|
||||||
|
|
||||||
|
|
||||||
// --- Tables Pane (Middle) ---
|
// --- Tables Pane (Middle) ---
|
||||||
let table_pane_has_focus = matches!(admin_state.current_focus, AdminFocus::Tables | AdminFocus::InsideTablesList);
|
let table_pane_has_focus = matches!(admin_state.current_focus, AdminFocus::Tables | AdminFocus::InsideTablesList);
|
||||||
let table_border_style = if table_pane_has_focus {
|
let table_border_style = if table_pane_has_focus { Style::default().fg(theme.highlight) } else { Style::default().fg(theme.border) };
|
||||||
Style::default().fg(theme.highlight)
|
|
||||||
|
let profile_to_display_tables_for_idx: Option<usize>;
|
||||||
|
if admin_state.current_focus == AdminFocus::InsideProfilesList {
|
||||||
|
profile_to_display_tables_for_idx = admin_state.profile_list_state.selected();
|
||||||
} else {
|
} else {
|
||||||
Style::default().fg(theme.border)
|
profile_to_display_tables_for_idx = admin_state.selected_profile_index
|
||||||
};
|
.or_else(|| admin_state.profile_list_state.selected());
|
||||||
|
}
|
||||||
|
let tables_pane_title_profile_name = profile_to_display_tables_for_idx
|
||||||
|
.and_then(|idx| app_state.profile_tree.profiles.get(idx))
|
||||||
|
.map_or("None Selected", |p| p.name.as_str());
|
||||||
|
let tables_block = Block::default().title(format!(" Tables (Profile: {}) ", tables_pane_title_profile_name)).borders(Borders::ALL).border_type(BorderType::Rounded).border_style(table_border_style);
|
||||||
|
let tables_inner_area = tables_block.inner(tables_pane);
|
||||||
|
f.render_widget(tables_block, tables_pane);
|
||||||
|
|
||||||
// Get selected profile information
|
let table_list_items_for_display: Vec<ListItem> =
|
||||||
let navigated_profile_idx = admin_state.profile_list_state.selected(); // Use nav state for display
|
if let Some(profile_data_for_tables) = profile_to_display_tables_for_idx
|
||||||
let selected_profile_name = app_state
|
.and_then(|idx| app_state.profile_tree.profiles.get(idx)) {
|
||||||
.profile_tree
|
profile_data_for_tables.tables.iter().enumerate().map(|(idx, table)| {
|
||||||
.profiles
|
let is_table_persistently_selected = admin_state.selected_table_index == Some(idx) &&
|
||||||
.get(navigated_profile_idx.unwrap_or(usize::MAX)) // Use nav state for title
|
profile_to_display_tables_for_idx == admin_state.selected_profile_index;
|
||||||
.map_or("None", |p| &p.name);
|
let is_table_nav_highlighted = admin_state.table_list_state.selected() == Some(idx) &&
|
||||||
|
admin_state.current_focus == AdminFocus::InsideTablesList;
|
||||||
// Block for the tables pane
|
let prefix = if is_table_persistently_selected { "[*] " } else { "[ ] " };
|
||||||
let tables_block = Block::default()
|
let style = if is_table_nav_highlighted { Style::default().fg(theme.highlight).add_modifier(ratatui::style::Modifier::BOLD) }
|
||||||
.title(format!(" Tables (Profile: {}) ", selected_profile_name))
|
else if is_table_persistently_selected { Style::default().fg(theme.accent) }
|
||||||
.borders(Borders::ALL)
|
else { Style::default().fg(theme.fg) };
|
||||||
.border_type(BorderType::Rounded)
|
ListItem::new(Line::from(vec![Span::styled(prefix, style), Span::styled(&table.name, style)]))
|
||||||
.border_style(table_border_style);
|
}).collect()
|
||||||
let tables_inner_area = tables_block.inner(tables_pane); // Get inner area for list
|
|
||||||
f.render_widget(tables_block, tables_pane); // Render the block itself
|
|
||||||
|
|
||||||
// Create table list items and get dependencies for the selected table
|
|
||||||
let (table_list_items, selected_table_deps): (Vec<ListItem>, Vec<String>) = if let Some(
|
|
||||||
profile, // Get profile based on NAVIGATED profile index
|
|
||||||
) = navigated_profile_idx.and_then(|idx| app_state.profile_tree.profiles.get(idx)) {
|
|
||||||
let items: Vec<ListItem> = profile
|
|
||||||
.tables
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.map(|(idx, table)| { // Renamed i to idx for clarity
|
|
||||||
// Check persistent selection for prefix, navigation state for style/highlight
|
|
||||||
let is_selected = admin_state.selected_table_index == Some(idx); // Use persistent state for [*]
|
|
||||||
let is_navigated = admin_state.table_list_state.selected() == Some(idx); // Use nav state for highlight/>
|
|
||||||
let prefix = if is_selected { "[*] " } else { "[ ] " };
|
|
||||||
let style = if is_navigated { // Style based on navigation highlight
|
|
||||||
Style::default().fg(theme.highlight).add_modifier(ratatui::style::Modifier::BOLD)
|
|
||||||
} else {
|
|
||||||
Style::default().fg(theme.fg)
|
|
||||||
};
|
|
||||||
ListItem::new(Line::from(vec![
|
|
||||||
Span::styled(prefix, style),
|
|
||||||
Span::styled(&table.name, style),
|
|
||||||
]))
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// Get dependencies only for the PERSISTENTLY selected table in the PERSISTENTLY selected profile
|
|
||||||
let chosen_profile_idx = admin_state.selected_profile_index; // Use persistent profile selection
|
|
||||||
let deps = chosen_profile_idx // Start with the chosen profile index
|
|
||||||
.and_then(|p_idx| app_state.profile_tree.profiles.get(p_idx)) // Get the chosen profile
|
|
||||||
.and_then(|p| admin_state.selected_table_index.and_then(|t_idx| p.tables.get(t_idx))) // Get the chosen table using its index
|
|
||||||
.map_or(Vec::new(), |t| t.depends_on.clone()); // If found, clone its depends_on, otherwise return empty Vec
|
|
||||||
|
|
||||||
(items, deps)
|
|
||||||
} else {
|
|
||||||
// Default when no profile is selected
|
|
||||||
(vec![ListItem::new("Select a profile to see tables")], vec![])
|
|
||||||
};
|
|
||||||
|
|
||||||
// Build and render table list inside the block's inner area
|
|
||||||
let table_list = List::new(table_list_items)
|
|
||||||
// Highlight style only applies when focus is *inside* the list
|
|
||||||
.highlight_style(if admin_state.current_focus == AdminFocus::InsideTablesList {
|
|
||||||
Style::default().add_modifier(ratatui::style::Modifier::REVERSED)
|
|
||||||
} else {
|
} else {
|
||||||
Style::default()
|
vec![ListItem::new("Select a profile to see tables")]
|
||||||
})
|
};
|
||||||
.highlight_symbol(if admin_state.current_focus == AdminFocus::InsideTablesList { "> " } else { "" });
|
let table_list = List::new(table_list_items_for_display)
|
||||||
|
.highlight_style(if admin_state.current_focus == AdminFocus::InsideTablesList { Style::default().add_modifier(ratatui::style::Modifier::REVERSED) } else { Style::default() })
|
||||||
|
.highlight_symbol(if admin_state.current_focus == AdminFocus::InsideTablesList { "> " } else { " " });
|
||||||
f.render_stateful_widget(table_list, tables_inner_area, &mut admin_state.table_list_state);
|
f.render_stateful_widget(table_list, tables_inner_area, &mut admin_state.table_list_state);
|
||||||
|
|
||||||
// --- Dependencies Pane (Right) ---
|
|
||||||
// Get name based on PERSISTENT selections
|
|
||||||
let chosen_profile_idx = admin_state.selected_profile_index; // Use persistent profile selection
|
|
||||||
let selected_table_name = chosen_profile_idx
|
|
||||||
.and_then(|p_idx| app_state.profile_tree.profiles.get(p_idx))
|
|
||||||
.and_then(|p| admin_state.selected_table_index.and_then(|t_idx| p.tables.get(t_idx))) // Use persistent table selection
|
|
||||||
.map_or("N/A", |t| &t.name); // Get name of the selected table
|
|
||||||
|
|
||||||
// Block for the dependencies pane
|
// --- Dependencies Pane (Right) ---
|
||||||
|
let mut deps_pane_title_table_name = "N/A".to_string();
|
||||||
|
let dependencies_to_display: Vec<String>;
|
||||||
|
|
||||||
|
if admin_state.current_focus == AdminFocus::InsideTablesList {
|
||||||
|
// If navigating tables, show dependencies for the '>' highlighted table.
|
||||||
|
// The profile context is `profile_to_display_tables_for_idx` (from Tables pane logic).
|
||||||
|
if let Some(p_idx_for_current_tables) = profile_to_display_tables_for_idx {
|
||||||
|
if let Some(current_profile_showing_tables) = app_state.profile_tree.profiles.get(p_idx_for_current_tables) {
|
||||||
|
if let Some(table_nav_idx) = admin_state.table_list_state.selected() { // The '>' highlighted table
|
||||||
|
if let Some(navigated_table) = current_profile_showing_tables.tables.get(table_nav_idx) {
|
||||||
|
deps_pane_title_table_name = navigated_table.name.clone();
|
||||||
|
dependencies_to_display = navigated_table.depends_on.clone();
|
||||||
|
} else {
|
||||||
|
dependencies_to_display = Vec::new(); // Navigated table index out of bounds
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dependencies_to_display = Vec::new(); // No table navigated with '>'
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dependencies_to_display = Vec::new(); // Profile for tables out of bounds
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dependencies_to_display = Vec::new(); // No profile active for table display
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Otherwise, show dependencies for the '[*]' persistently selected table & profile.
|
||||||
|
if let Some(p_idx) = admin_state.selected_profile_index { // Must be a persistently selected profile
|
||||||
|
if let Some(selected_profile) = app_state.profile_tree.profiles.get(p_idx) {
|
||||||
|
if let Some(t_idx) = admin_state.selected_table_index { // Must be a persistently selected table
|
||||||
|
if let Some(selected_table) = selected_profile.tables.get(t_idx) {
|
||||||
|
deps_pane_title_table_name = selected_table.name.clone();
|
||||||
|
dependencies_to_display = selected_table.depends_on.clone();
|
||||||
|
} else { dependencies_to_display = Vec::new(); }
|
||||||
|
} else { dependencies_to_display = Vec::new(); }
|
||||||
|
} else { dependencies_to_display = Vec::new(); }
|
||||||
|
} else { dependencies_to_display = Vec::new(); }
|
||||||
|
}
|
||||||
|
|
||||||
let deps_block = Block::default()
|
let deps_block = Block::default()
|
||||||
.title(format!(" Dependencies (Table: {}) ", selected_table_name))
|
.title(format!(" Dependencies (Table: {}) ", deps_pane_title_table_name))
|
||||||
.borders(Borders::ALL)
|
.borders(Borders::ALL)
|
||||||
.border_type(BorderType::Rounded)
|
.border_type(BorderType::Rounded)
|
||||||
.border_style(Style::default().fg(theme.border)); // No focus highlight for deps pane
|
.border_style(Style::default().fg(theme.border));
|
||||||
let deps_inner_area = deps_block.inner(deps_pane); // Get inner area for content
|
let deps_inner_area = deps_block.inner(deps_pane);
|
||||||
f.render_widget(deps_block, deps_pane); // Render the block itself
|
f.render_widget(deps_block, deps_pane);
|
||||||
|
|
||||||
// Prepare content for the dependencies paragraph
|
|
||||||
let mut deps_content = Text::default();
|
let mut deps_content = Text::default();
|
||||||
deps_content.lines.push(Line::from(Span::styled(
|
deps_content.lines.push(Line::from(Span::styled(
|
||||||
"Depends On:",
|
"Depends On:",
|
||||||
Style::default().fg(theme.accent), // Use accent color for the label
|
Style::default().fg(theme.accent),
|
||||||
)));
|
)));
|
||||||
|
|
||||||
if !selected_table_deps.is_empty() {
|
if !dependencies_to_display.is_empty() {
|
||||||
for dep in selected_table_deps {
|
for dep in dependencies_to_display {
|
||||||
// List each dependency
|
|
||||||
deps_content.lines.push(Line::from(Span::styled(format!("- {}", dep), theme.fg)));
|
deps_content.lines.push(Line::from(Span::styled(format!("- {}", dep), theme.fg)));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Indicate if there are no dependencies
|
|
||||||
deps_content.lines.push(Line::from(Span::styled(" None", theme.secondary)));
|
deps_content.lines.push(Line::from(Span::styled(" None", theme.secondary)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build and render dependencies paragraph inside the block's inner area
|
|
||||||
let deps_paragraph = Paragraph::new(deps_content);
|
let deps_paragraph = Paragraph::new(deps_content);
|
||||||
f.render_widget(deps_paragraph, deps_inner_area);
|
f.render_widget(deps_paragraph, deps_inner_area);
|
||||||
|
|
||||||
// --- Buttons Row ---
|
// --- Buttons Row ---
|
||||||
let button_chunks = Layout::default()
|
let button_chunks = Layout::default().direction(Direction::Horizontal).constraints([Constraint::Percentage(33), Constraint::Percentage(34), Constraint::Percentage(33)].as_ref()).split(buttons_area);
|
||||||
.direction(Direction::Horizontal)
|
|
||||||
.constraints([
|
|
||||||
Constraint::Percentage(33),
|
|
||||||
Constraint::Percentage(34),
|
|
||||||
Constraint::Percentage(33),
|
|
||||||
].as_ref())
|
|
||||||
.split(buttons_area);
|
|
||||||
|
|
||||||
let btn_base_style = Style::default().fg(theme.secondary);
|
let btn_base_style = Style::default().fg(theme.secondary);
|
||||||
|
let get_btn_style = |button_focus: AdminFocus| { if admin_state.current_focus == button_focus { btn_base_style.add_modifier(ratatui::style::Modifier::REVERSED) } else { btn_base_style } };
|
||||||
// Define the helper closure to get style based on focus
|
let btn1 = Paragraph::new("Add Logic").style(get_btn_style(AdminFocus::Button1)).alignment(Alignment::Center);
|
||||||
let get_btn_style = |button_focus: AdminFocus| {
|
let btn2 = Paragraph::new("Add Table").style(get_btn_style(AdminFocus::Button2)).alignment(Alignment::Center);
|
||||||
if admin_state.current_focus == button_focus {
|
let btn3 = Paragraph::new("Change Table").style(get_btn_style(AdminFocus::Button3)).alignment(Alignment::Center);
|
||||||
// Apply highlight style if this button is focused
|
|
||||||
btn_base_style.add_modifier(ratatui::style::Modifier::REVERSED)
|
|
||||||
} else {
|
|
||||||
btn_base_style // Use base style otherwise
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let btn1 = Paragraph::new("Add Logic")
|
|
||||||
.style(get_btn_style(AdminFocus::Button1))
|
|
||||||
.alignment(Alignment::Center);
|
|
||||||
let btn2 = Paragraph::new("Add Table")
|
|
||||||
.style(get_btn_style(AdminFocus::Button2))
|
|
||||||
.alignment(Alignment::Center);
|
|
||||||
let btn3 = Paragraph::new("Change Table")
|
|
||||||
.style(get_btn_style(AdminFocus::Button3))
|
|
||||||
.alignment(Alignment::Center);
|
|
||||||
|
|
||||||
f.render_widget(btn1, button_chunks[0]);
|
f.render_widget(btn1, button_chunks[0]);
|
||||||
f.render_widget(btn2, button_chunks[1]);
|
f.render_widget(btn2, button_chunks[1]);
|
||||||
f.render_widget(btn3, button_chunks[2]);
|
f.render_widget(btn3, button_chunks[2]);
|
||||||
|
|||||||
@@ -12,11 +12,32 @@ use crate::services::GrpcClient;
|
|||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
// Define a type for the save result channel
|
|
||||||
pub type SaveTableResultSender = mpsc::Sender<Result<String>>;
|
pub type SaveTableResultSender = mpsc::Sender<Result<String>>;
|
||||||
|
|
||||||
/// Handles navigation events specifically for the Add Table view.
|
fn navigate_table_up(table_state: &mut TableState, item_count: usize) -> bool {
|
||||||
/// Returns true if the event was handled, false otherwise.
|
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 }
|
||||||
|
else { false }
|
||||||
|
}
|
||||||
|
None => { table_state.select(Some(0)); true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn navigate_table_down(table_state: &mut TableState, item_count: usize) -> bool {
|
||||||
|
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 }
|
||||||
|
else { false }
|
||||||
|
}
|
||||||
|
None => { table_state.select(Some(0)); true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn handle_add_table_navigation(
|
pub fn handle_add_table_navigation(
|
||||||
key: KeyEvent,
|
key: KeyEvent,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
@@ -28,61 +49,52 @@ pub fn handle_add_table_navigation(
|
|||||||
) -> bool {
|
) -> bool {
|
||||||
let action = config.get_general_action(key.code, key.modifiers);
|
let action = config.get_general_action(key.code, key.modifiers);
|
||||||
let current_focus = add_table_state.current_focus;
|
let current_focus = add_table_state.current_focus;
|
||||||
let mut handled = true; // Assume handled unless logic determines otherwise
|
let mut handled = true;
|
||||||
let mut new_focus = current_focus; // Initialize new_focus
|
let mut new_focus = current_focus;
|
||||||
|
|
||||||
|
if matches!(current_focus, AddTableFocus::InsideColumnsTable | AddTableFocus::InsideIndexesTable | AddTableFocus::InsideLinksTable) {
|
||||||
|
if matches!(action.as_deref(), Some("next_option") | Some("previous_option")) {
|
||||||
|
*command_message = "Press Esc to exit table item navigation first.".to_string();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
match action.as_deref() {
|
match action.as_deref() {
|
||||||
// --- Handle Exiting Table Scroll Mode ---
|
|
||||||
Some("exit_table_scroll") => {
|
Some("exit_table_scroll") => {
|
||||||
match current_focus {
|
match current_focus {
|
||||||
AddTableFocus::InsideColumnsTable => {
|
AddTableFocus::InsideColumnsTable => {
|
||||||
add_table_state.column_table_state.select(None);
|
add_table_state.column_table_state.select(None);
|
||||||
new_focus = AddTableFocus::ColumnsTable;
|
new_focus = AddTableFocus::ColumnsTable;
|
||||||
*command_message = "Exited Columns Table".to_string();
|
// *command_message = "Exited Columns Table".to_string(); // Minimal change: remove message
|
||||||
}
|
}
|
||||||
AddTableFocus::InsideIndexesTable => {
|
AddTableFocus::InsideIndexesTable => {
|
||||||
add_table_state.index_table_state.select(None);
|
add_table_state.index_table_state.select(None);
|
||||||
new_focus = AddTableFocus::IndexesTable;
|
new_focus = AddTableFocus::IndexesTable;
|
||||||
*command_message = "Exited Indexes Table".to_string();
|
// *command_message = "Exited Indexes Table".to_string();
|
||||||
}
|
}
|
||||||
AddTableFocus::InsideLinksTable => {
|
AddTableFocus::InsideLinksTable => {
|
||||||
add_table_state.link_table_state.select(None);
|
add_table_state.link_table_state.select(None);
|
||||||
new_focus = AddTableFocus::LinksTable;
|
new_focus = AddTableFocus::LinksTable;
|
||||||
*command_message = "Exited Links Table".to_string();
|
// *command_message = "Exited Links Table".to_string();
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
// Action triggered but not applicable in this focus state
|
|
||||||
handled = false;
|
|
||||||
}
|
}
|
||||||
|
_ => handled = false,
|
||||||
}
|
}
|
||||||
// If handled (i.e., focus changed), handled remains true.
|
|
||||||
// If not handled, handled becomes false.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Vertical Navigation (Up/Down) ---
|
|
||||||
Some("move_up") => {
|
Some("move_up") => {
|
||||||
match current_focus {
|
match current_focus {
|
||||||
AddTableFocus::InputTableName => new_focus = AddTableFocus::CancelButton,
|
AddTableFocus::InputTableName => {
|
||||||
|
// MINIMAL CHANGE: Do nothing, new_focus remains current_focus
|
||||||
|
// *command_message = "At top of form.".to_string(); // Remove message
|
||||||
|
}
|
||||||
AddTableFocus::InputColumnName => new_focus = AddTableFocus::InputTableName,
|
AddTableFocus::InputColumnName => new_focus = AddTableFocus::InputTableName,
|
||||||
AddTableFocus::InputColumnType => new_focus = AddTableFocus::InputColumnName,
|
AddTableFocus::InputColumnType => new_focus = AddTableFocus::InputColumnName,
|
||||||
AddTableFocus::AddColumnButton => new_focus = AddTableFocus::InputColumnType,
|
AddTableFocus::AddColumnButton => new_focus = AddTableFocus::InputColumnType,
|
||||||
// Navigate between blocks when focus is on the table block itself
|
AddTableFocus::ColumnsTable => new_focus = AddTableFocus::AddColumnButton,
|
||||||
AddTableFocus::ColumnsTable => new_focus = AddTableFocus::AddColumnButton, // Move up to right pane
|
|
||||||
AddTableFocus::IndexesTable => new_focus = AddTableFocus::ColumnsTable,
|
AddTableFocus::IndexesTable => new_focus = AddTableFocus::ColumnsTable,
|
||||||
AddTableFocus::LinksTable => new_focus = AddTableFocus::IndexesTable,
|
AddTableFocus::LinksTable => new_focus = AddTableFocus::IndexesTable,
|
||||||
// Scroll inside the table when focus is internal
|
AddTableFocus::InsideColumnsTable => { navigate_table_up(&mut add_table_state.column_table_state, add_table_state.columns.len()); }
|
||||||
AddTableFocus::InsideColumnsTable => {
|
AddTableFocus::InsideIndexesTable => { navigate_table_up(&mut add_table_state.index_table_state, add_table_state.indexes.len()); }
|
||||||
navigate_table_up(&mut add_table_state.column_table_state, add_table_state.columns.len());
|
AddTableFocus::InsideLinksTable => { navigate_table_up(&mut add_table_state.link_table_state, add_table_state.links.len()); }
|
||||||
// Stay inside the table, don't change new_focus
|
|
||||||
}
|
|
||||||
AddTableFocus::InsideIndexesTable => {
|
|
||||||
navigate_table_up(&mut add_table_state.index_table_state, add_table_state.indexes.len());
|
|
||||||
// Stay inside the table
|
|
||||||
}
|
|
||||||
AddTableFocus::InsideLinksTable => {
|
|
||||||
navigate_table_up(&mut add_table_state.link_table_state, add_table_state.links.len());
|
|
||||||
// Stay inside the table
|
|
||||||
}
|
|
||||||
AddTableFocus::SaveButton => new_focus = AddTableFocus::LinksTable,
|
AddTableFocus::SaveButton => new_focus = AddTableFocus::LinksTable,
|
||||||
AddTableFocus::DeleteSelectedButton => new_focus = AddTableFocus::SaveButton,
|
AddTableFocus::DeleteSelectedButton => new_focus = AddTableFocus::SaveButton,
|
||||||
AddTableFocus::CancelButton => new_focus = AddTableFocus::DeleteSelectedButton,
|
AddTableFocus::CancelButton => new_focus = AddTableFocus::DeleteSelectedButton,
|
||||||
@@ -92,306 +104,102 @@ pub fn handle_add_table_navigation(
|
|||||||
match current_focus {
|
match current_focus {
|
||||||
AddTableFocus::InputTableName => new_focus = AddTableFocus::InputColumnName,
|
AddTableFocus::InputTableName => new_focus = AddTableFocus::InputColumnName,
|
||||||
AddTableFocus::InputColumnName => new_focus = AddTableFocus::InputColumnType,
|
AddTableFocus::InputColumnName => new_focus = AddTableFocus::InputColumnType,
|
||||||
AddTableFocus::InputColumnType => new_focus = AddTableFocus::AddColumnButton,
|
AddTableFocus::InputColumnType => {
|
||||||
|
add_table_state.last_canvas_field = 2;
|
||||||
|
new_focus = AddTableFocus::AddColumnButton;
|
||||||
|
},
|
||||||
AddTableFocus::AddColumnButton => new_focus = AddTableFocus::ColumnsTable,
|
AddTableFocus::AddColumnButton => new_focus = AddTableFocus::ColumnsTable,
|
||||||
// Navigate between blocks when focus is on the table block itself
|
|
||||||
AddTableFocus::ColumnsTable => new_focus = AddTableFocus::IndexesTable,
|
AddTableFocus::ColumnsTable => new_focus = AddTableFocus::IndexesTable,
|
||||||
AddTableFocus::IndexesTable => new_focus = AddTableFocus::LinksTable,
|
AddTableFocus::IndexesTable => new_focus = AddTableFocus::LinksTable,
|
||||||
AddTableFocus::LinksTable => new_focus = AddTableFocus::SaveButton, // Move down to right pane
|
AddTableFocus::LinksTable => new_focus = AddTableFocus::SaveButton,
|
||||||
// Scroll inside the table when focus is internal
|
AddTableFocus::InsideColumnsTable => { navigate_table_down(&mut add_table_state.column_table_state, add_table_state.columns.len()); }
|
||||||
AddTableFocus::InsideColumnsTable => {
|
AddTableFocus::InsideIndexesTable => { navigate_table_down(&mut add_table_state.index_table_state, add_table_state.indexes.len()); }
|
||||||
navigate_table_down(&mut add_table_state.column_table_state, add_table_state.columns.len());
|
AddTableFocus::InsideLinksTable => { navigate_table_down(&mut add_table_state.link_table_state, add_table_state.links.len()); }
|
||||||
// Stay inside the table
|
|
||||||
}
|
|
||||||
AddTableFocus::InsideIndexesTable => {
|
|
||||||
navigate_table_down(&mut add_table_state.index_table_state, add_table_state.indexes.len());
|
|
||||||
// Stay inside the table
|
|
||||||
}
|
|
||||||
AddTableFocus::InsideLinksTable => {
|
|
||||||
navigate_table_down(&mut add_table_state.link_table_state, add_table_state.links.len());
|
|
||||||
// Stay inside the table
|
|
||||||
}
|
|
||||||
AddTableFocus::SaveButton => new_focus = AddTableFocus::DeleteSelectedButton,
|
AddTableFocus::SaveButton => new_focus = AddTableFocus::DeleteSelectedButton,
|
||||||
AddTableFocus::DeleteSelectedButton => new_focus = AddTableFocus::CancelButton,
|
AddTableFocus::DeleteSelectedButton => new_focus = AddTableFocus::CancelButton,
|
||||||
AddTableFocus::CancelButton => new_focus = AddTableFocus::InputTableName,
|
AddTableFocus::CancelButton => {
|
||||||
|
// MINIMAL CHANGE: Do nothing, new_focus remains current_focus
|
||||||
|
// *command_message = "At bottom of form.".to_string(); // Remove message
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Some("next_option") => { // This logic should already be non-wrapping
|
||||||
// --- Horizontal Navigation (Left/Right) ---
|
match current_focus {
|
||||||
Some("next_option") => { // 'l' or Right: Move from Left Pane to Right Pane
|
AddTableFocus::InputTableName | AddTableFocus::InputColumnName | AddTableFocus::InputColumnType =>
|
||||||
// Horizontal nav within bottom buttons
|
{ new_focus = AddTableFocus::AddColumnButton; }
|
||||||
if current_focus == AddTableFocus::SaveButton {
|
AddTableFocus::AddColumnButton => new_focus = AddTableFocus::ColumnsTable,
|
||||||
new_focus = AddTableFocus::DeleteSelectedButton;
|
AddTableFocus::ColumnsTable => new_focus = AddTableFocus::IndexesTable,
|
||||||
} else if current_focus == AddTableFocus::DeleteSelectedButton {
|
AddTableFocus::IndexesTable => new_focus = AddTableFocus::LinksTable,
|
||||||
new_focus = AddTableFocus::CancelButton;
|
AddTableFocus::LinksTable => new_focus = AddTableFocus::SaveButton,
|
||||||
}
|
AddTableFocus::SaveButton => new_focus = AddTableFocus::DeleteSelectedButton,
|
||||||
|
AddTableFocus::DeleteSelectedButton => new_focus = AddTableFocus::CancelButton,
|
||||||
|
AddTableFocus::CancelButton => { /* *command_message = "At last focusable area.".to_string(); */ } // No change in focus
|
||||||
|
_ => handled = false,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Some("previous_option") => { // 'h' or Left: Move from Right Pane to Left Pane
|
Some("previous_option") => { // This logic should already be non-wrapping
|
||||||
// Horizontal nav within bottom buttons
|
match current_focus {
|
||||||
if current_focus == AddTableFocus::CancelButton {
|
AddTableFocus::InputTableName | AddTableFocus::InputColumnName | AddTableFocus::InputColumnType =>
|
||||||
new_focus = AddTableFocus::DeleteSelectedButton;
|
{ /* *command_message = "At first focusable area.".to_string(); */ } // No change in focus
|
||||||
} else if current_focus == AddTableFocus::DeleteSelectedButton {
|
AddTableFocus::AddColumnButton => new_focus = AddTableFocus::InputColumnType,
|
||||||
new_focus = AddTableFocus::SaveButton;
|
AddTableFocus::ColumnsTable => new_focus = AddTableFocus::AddColumnButton,
|
||||||
}
|
AddTableFocus::IndexesTable => new_focus = AddTableFocus::ColumnsTable,
|
||||||
|
AddTableFocus::LinksTable => new_focus = AddTableFocus::IndexesTable,
|
||||||
|
AddTableFocus::SaveButton => new_focus = AddTableFocus::LinksTable,
|
||||||
|
AddTableFocus::DeleteSelectedButton => new_focus = AddTableFocus::SaveButton,
|
||||||
|
AddTableFocus::CancelButton => new_focus = AddTableFocus::DeleteSelectedButton,
|
||||||
|
_ => handled = false,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Some("next_field") => {
|
||||||
// --- Tab / Shift+Tab Navigation (Keep as vertical cycle) ---
|
|
||||||
Some("next_field") => { // Tab
|
|
||||||
new_focus = match current_focus {
|
new_focus = match current_focus {
|
||||||
AddTableFocus::InputTableName => AddTableFocus::InputColumnName,
|
AddTableFocus::InputTableName => AddTableFocus::InputColumnName, AddTableFocus::InputColumnName => AddTableFocus::InputColumnType, AddTableFocus::InputColumnType => AddTableFocus::AddColumnButton, AddTableFocus::AddColumnButton => AddTableFocus::ColumnsTable,
|
||||||
AddTableFocus::InputColumnName => AddTableFocus::InputColumnType,
|
AddTableFocus::ColumnsTable | AddTableFocus::InsideColumnsTable => AddTableFocus::IndexesTable, AddTableFocus::IndexesTable | AddTableFocus::InsideIndexesTable => AddTableFocus::LinksTable, AddTableFocus::LinksTable | AddTableFocus::InsideLinksTable => AddTableFocus::SaveButton,
|
||||||
AddTableFocus::InputColumnType => AddTableFocus::AddColumnButton,
|
AddTableFocus::SaveButton => AddTableFocus::DeleteSelectedButton, AddTableFocus::DeleteSelectedButton => AddTableFocus::CancelButton, AddTableFocus::CancelButton => AddTableFocus::InputTableName,
|
||||||
AddTableFocus::AddColumnButton => AddTableFocus::ColumnsTable,
|
|
||||||
// Treat Inside* same as block focus for tabbing out
|
|
||||||
AddTableFocus::ColumnsTable | AddTableFocus::InsideColumnsTable => AddTableFocus::IndexesTable,
|
|
||||||
AddTableFocus::IndexesTable | AddTableFocus::InsideIndexesTable => AddTableFocus::LinksTable,
|
|
||||||
AddTableFocus::LinksTable | AddTableFocus::InsideLinksTable => AddTableFocus::SaveButton,
|
|
||||||
AddTableFocus::SaveButton => AddTableFocus::DeleteSelectedButton,
|
|
||||||
AddTableFocus::DeleteSelectedButton => AddTableFocus::CancelButton,
|
|
||||||
AddTableFocus::CancelButton => AddTableFocus::InputTableName, // Wrap
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
Some("prev_field") => { // Shift+Tab
|
Some("prev_field") => {
|
||||||
new_focus = match current_focus {
|
new_focus = match current_focus {
|
||||||
AddTableFocus::InputTableName => AddTableFocus::CancelButton, // Wrap
|
AddTableFocus::InputTableName => AddTableFocus::CancelButton, AddTableFocus::InputColumnName => AddTableFocus::InputTableName, AddTableFocus::InputColumnType => AddTableFocus::InputColumnName, AddTableFocus::AddColumnButton => AddTableFocus::InputColumnType,
|
||||||
AddTableFocus::InputColumnName => AddTableFocus::InputTableName,
|
AddTableFocus::ColumnsTable | AddTableFocus::InsideColumnsTable => AddTableFocus::AddColumnButton, AddTableFocus::IndexesTable | AddTableFocus::InsideIndexesTable => AddTableFocus::ColumnsTable, AddTableFocus::LinksTable | AddTableFocus::InsideLinksTable => AddTableFocus::IndexesTable,
|
||||||
AddTableFocus::InputColumnType => AddTableFocus::InputColumnName,
|
AddTableFocus::SaveButton => AddTableFocus::LinksTable, AddTableFocus::DeleteSelectedButton => AddTableFocus::SaveButton, AddTableFocus::CancelButton => AddTableFocus::DeleteSelectedButton,
|
||||||
AddTableFocus::AddColumnButton => AddTableFocus::InputColumnType,
|
|
||||||
// Treat Inside* same as block focus for tabbing out
|
|
||||||
AddTableFocus::ColumnsTable | AddTableFocus::InsideColumnsTable => AddTableFocus::AddColumnButton,
|
|
||||||
AddTableFocus::IndexesTable | AddTableFocus::InsideIndexesTable => AddTableFocus::ColumnsTable,
|
|
||||||
AddTableFocus::LinksTable | AddTableFocus::InsideLinksTable => AddTableFocus::IndexesTable,
|
|
||||||
AddTableFocus::SaveButton => AddTableFocus::LinksTable,
|
|
||||||
AddTableFocus::DeleteSelectedButton => AddTableFocus::SaveButton,
|
|
||||||
AddTableFocus::CancelButton => AddTableFocus::DeleteSelectedButton,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Selection ---
|
|
||||||
Some("select") => {
|
Some("select") => {
|
||||||
match current_focus {
|
match current_focus {
|
||||||
// --- Enter/Exit Table Focus ---
|
AddTableFocus::ColumnsTable => { new_focus = AddTableFocus::InsideColumnsTable; 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)); } /* Message removed */ }
|
||||||
AddTableFocus::ColumnsTable => {
|
AddTableFocus::IndexesTable => { new_focus = AddTableFocus::InsideIndexesTable; if add_table_state.index_table_state.selected().is_none() && !add_table_state.indexes.is_empty() { add_table_state.index_table_state.select(Some(0)); } /* Message removed */ }
|
||||||
new_focus = AddTableFocus::InsideColumnsTable;
|
AddTableFocus::LinksTable => { new_focus = AddTableFocus::InsideLinksTable; 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)); } /* Message removed */ }
|
||||||
// Select first item if none selected when entering
|
AddTableFocus::InsideColumnsTable => { if let Some(index) = add_table_state.column_table_state.selected() { if let Some(col) = add_table_state.columns.get_mut(index) { col.selected = !col.selected; add_table_state.has_unsaved_changes = true; /* Message removed */ }} /* else { Message removed } */ }
|
||||||
if add_table_state.column_table_state.selected().is_none() && !add_table_state.columns.is_empty() {
|
AddTableFocus::InsideIndexesTable => { if let Some(index) = add_table_state.index_table_state.selected() { if let Some(idx_def) = add_table_state.indexes.get_mut(index) { idx_def.selected = !idx_def.selected; add_table_state.has_unsaved_changes = true; /* Message removed */ }} /* else { Message removed } */ }
|
||||||
add_table_state.column_table_state.select(Some(0));
|
AddTableFocus::InsideLinksTable => { if let Some(index) = add_table_state.link_table_state.selected() { if let Some(link) = add_table_state.links.get_mut(index) { link.selected = !link.selected; add_table_state.has_unsaved_changes = true; /* Message removed */ }} /* else { Message removed } */ }
|
||||||
}
|
AddTableFocus::AddColumnButton => { if let Some(focus_after_add) = handle_add_column_action(add_table_state, command_message) { new_focus = focus_after_add; } else { /* Message already set by handle_add_column_action */ }}
|
||||||
*command_message = "Entered Columns Table (Scroll with Up/Down, Select to exit)".to_string();
|
AddTableFocus::SaveButton => { if add_table_state.table_name.is_empty() { *command_message = "Cannot save: Table name is empty.".to_string(); } else if add_table_state.columns.is_empty() { *command_message = "Cannot save: No columns defined.".to_string(); } else { *command_message = "Saving table...".to_string(); app_state.show_loading_dialog("Saving", "Please wait..."); let mut client_clone = grpc_client.clone(); let state_clone = add_table_state.clone(); let sender_clone = save_result_sender.clone(); tokio::spawn(async move { let result = handle_save_table_action(&mut client_clone, &state_clone).await; let _ = sender_clone.send(result).await; }); }}
|
||||||
}
|
AddTableFocus::DeleteSelectedButton => { let columns_to_delete: Vec<(usize, String, String)> = add_table_state.columns.iter().enumerate().filter(|(_, col)| col.selected).map(|(index, col)| (index, col.name.clone(), col.data_type.clone())).collect(); if columns_to_delete.is_empty() { *command_message = "No columns selected for deletion.".to_string(); } else { let column_details: String = columns_to_delete.iter().map(|(index, name, dtype)| format!("{}. {} ({})", index + 1, name, dtype)).collect::<Vec<String>>().join("\n"); let message = format!("Delete the following columns?\n\n{}", column_details); app_state.show_dialog("Confirm Deletion", &message, vec!["Confirm".to_string(), "Cancel".to_string()], DialogPurpose::ConfirmDeleteColumns); }}
|
||||||
AddTableFocus::IndexesTable => {
|
AddTableFocus::CancelButton => { *command_message = "Action: Cancel Add Table (Not Implemented)".to_string(); }
|
||||||
new_focus = AddTableFocus::InsideIndexesTable;
|
_ => { handled = false; }
|
||||||
if add_table_state.index_table_state.selected().is_none() && !add_table_state.indexes.is_empty() {
|
|
||||||
add_table_state.index_table_state.select(Some(0));
|
|
||||||
}
|
|
||||||
*command_message = "Entered Indexes Table (Scroll with Up/Down, Select to exit)".to_string();
|
|
||||||
}
|
|
||||||
AddTableFocus::LinksTable => {
|
|
||||||
new_focus = AddTableFocus::InsideLinksTable;
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
*command_message = "Entered Links Table (Scroll with Up/Down, Select to toggle/exit)".to_string();
|
|
||||||
}
|
|
||||||
AddTableFocus::InsideColumnsTable => {
|
|
||||||
// Toggle selection when pressing select *inside* the columns table
|
|
||||||
if let Some(index) = add_table_state.column_table_state.selected() {
|
|
||||||
if let Some(col) = add_table_state.columns.get_mut(index) {
|
|
||||||
col.selected = !col.selected;
|
|
||||||
add_table_state.has_unsaved_changes = true;
|
|
||||||
*command_message = format!(
|
|
||||||
"Toggled selection for column: {} to {}",
|
|
||||||
col.name, col.selected
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
*command_message = "No column highlighted to toggle selection".to_string();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
AddTableFocus::InsideIndexesTable => {
|
|
||||||
// Select does nothing here anymore, only Esc exits.
|
|
||||||
if let Some(index) = add_table_state.index_table_state.selected() {
|
|
||||||
if let Some(idx_def) = add_table_state.indexes.get_mut(index) {
|
|
||||||
idx_def.selected = !idx_def.selected;
|
|
||||||
add_table_state.has_unsaved_changes = true;
|
|
||||||
*command_message = format!(
|
|
||||||
"Toggled selection for index: {} to {}",
|
|
||||||
idx_def.name, idx_def.selected
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
*command_message = "Error: Selected index out of bounds".to_string();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
*command_message = "No index selected (Press Esc to exit scroll mode)".to_string();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
AddTableFocus::InsideLinksTable => {
|
|
||||||
// Toggle selection when pressing select *inside* the links table
|
|
||||||
if let Some(index) = add_table_state.link_table_state.selected() {
|
|
||||||
if let Some(link) = add_table_state.links.get_mut(index) {
|
|
||||||
link.selected = !link.selected; // Toggle the selected state
|
|
||||||
add_table_state.has_unsaved_changes = true; // Mark changes
|
|
||||||
*command_message = format!(
|
|
||||||
"Toggled selection for link: {} to {}",
|
|
||||||
link.linked_table_name, link.selected
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
*command_message = "Error: Selected link index out of bounds".to_string();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
*command_message = "No link selected to toggle".to_string();
|
|
||||||
}
|
|
||||||
// Stay inside the links table after toggling
|
|
||||||
new_focus = AddTableFocus::InsideLinksTable;
|
|
||||||
// Alternative: Exit after toggle:
|
|
||||||
// new_focus = AddTableFocus::LinksTable;
|
|
||||||
// *command_message = format!("{} - Exited Links Table", command_message);
|
|
||||||
}
|
|
||||||
// --- Other Select Actions ---
|
|
||||||
AddTableFocus::AddColumnButton => {
|
|
||||||
if let Some(focus_after_add) = handle_add_column_action(add_table_state, command_message) {
|
|
||||||
new_focus = focus_after_add;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
AddTableFocus::SaveButton => {
|
|
||||||
// --- Initiate Async Save ---
|
|
||||||
if add_table_state.table_name.is_empty() {
|
|
||||||
*command_message = "Cannot save: Table name is empty.".to_string();
|
|
||||||
} else if add_table_state.columns.is_empty() {
|
|
||||||
*command_message = "Cannot save: No columns defined.".to_string();
|
|
||||||
} else {
|
|
||||||
*command_message = "Saving table...".to_string();
|
|
||||||
app_state.show_loading_dialog("Saving", "Please wait...");
|
|
||||||
|
|
||||||
let mut client_clone = grpc_client.clone();
|
|
||||||
let state_clone = add_table_state.clone();
|
|
||||||
let sender_clone = save_result_sender.clone();
|
|
||||||
|
|
||||||
tokio::spawn(async move {
|
|
||||||
let result = handle_save_table_action(&mut client_clone, &state_clone).await;
|
|
||||||
let _ = sender_clone.send(result).await; // Send result back
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// --- End Initiate Async Save ---
|
|
||||||
}
|
|
||||||
AddTableFocus::DeleteSelectedButton => {
|
|
||||||
// --- Show Confirmation Dialog ---
|
|
||||||
// Collect tuples of (index, name, type) for selected columns
|
|
||||||
let columns_to_delete: Vec<(usize, String, String)> = add_table_state
|
|
||||||
.columns
|
|
||||||
.iter()
|
|
||||||
.enumerate() // Get index along with the column
|
|
||||||
.filter(|(_index, col)| col.selected) // Filter based on selection
|
|
||||||
.map(|(index, col)| (index, col.name.clone(), col.data_type.clone())) // Map to (index, name, type)
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
if columns_to_delete.is_empty() {
|
|
||||||
*command_message = "No columns selected for deletion.".to_string();
|
|
||||||
} else {
|
|
||||||
// Format the message to include index, name, and type
|
|
||||||
let column_details: String = columns_to_delete
|
|
||||||
.iter()
|
|
||||||
// Add 1 to index for 1-based numbering for user display
|
|
||||||
.map(|(index, name, dtype)| format!("{}. {} ({})", index + 1, name, dtype))
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
.join("\n");
|
|
||||||
|
|
||||||
// Use the formatted column_details string in the message
|
|
||||||
let message = format!(
|
|
||||||
"Delete the following columns?\n\n{}",
|
|
||||||
column_details
|
|
||||||
);
|
|
||||||
let buttons = vec!["Confirm".to_string(), "Cancel".to_string()];
|
|
||||||
app_state.show_dialog(
|
|
||||||
"Confirm Deletion",
|
|
||||||
&message,
|
|
||||||
buttons,
|
|
||||||
DialogPurpose::ConfirmDeleteColumns,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
AddTableFocus::CancelButton => {
|
|
||||||
*command_message = "Action: Cancel Add Table".to_string();
|
|
||||||
// TODO: Implement logic
|
|
||||||
}
|
|
||||||
_ => { // 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 ---
|
|
||||||
Some("toggle_sidebar") | Some("toggle_buffer_list") => {
|
|
||||||
handled = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- No matching action ---
|
|
||||||
_ => handled = false,
|
_ => handled = false,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update focus state if it changed and was handled
|
|
||||||
if handled && current_focus != new_focus {
|
if handled && current_focus != new_focus {
|
||||||
add_table_state.current_focus = new_focus;
|
add_table_state.current_focus = new_focus;
|
||||||
// Avoid overwriting specific messages set during 'select' handling
|
// Minimal change: Command message update logic can be simplified or removed if not desired
|
||||||
if command_message.is_empty() || command_message.starts_with("Focus set to") {
|
// For now, let's keep it minimal and only update if it was truly a focus change,
|
||||||
*command_message = format!("Focus set to {:?}", add_table_state.current_focus);
|
// and not a boundary message.
|
||||||
|
if !command_message.starts_with("At ") && current_focus != new_focus { // Avoid overwriting boundary messages
|
||||||
|
// *command_message = format!("Focus: {:?}", add_table_state.current_focus); // Optional: restore if needed
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the *new* focus target is one of the canvas input fields
|
|
||||||
let new_is_canvas_input_focus = matches!(new_focus,
|
let new_is_canvas_input_focus = matches!(new_focus,
|
||||||
AddTableFocus::InputTableName | AddTableFocus::InputColumnName | AddTableFocus::InputColumnType
|
AddTableFocus::InputTableName | AddTableFocus::InputColumnName | AddTableFocus::InputColumnType
|
||||||
);
|
);
|
||||||
// Focus is outside canvas if it's not an input field
|
|
||||||
app_state.ui.focus_outside_canvas = !new_is_canvas_input_focus;
|
app_state.ui.focus_outside_canvas = !new_is_canvas_input_focus;
|
||||||
} else if !handled {
|
|
||||||
// command_message.clear(); // Optional: Clear message if not handled here
|
|
||||||
}
|
}
|
||||||
|
// If not handled, command_message remains as it was (e.g., from a deeper function call or previous event)
|
||||||
|
// or can be cleared if that's the desired default. For minimal change, we leave it.
|
||||||
|
|
||||||
handled
|
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 TableState, item_count: usize) -> bool {
|
|
||||||
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 // Navigation happened
|
|
||||||
} else {
|
|
||||||
false // Was at the top
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => { // No item selected, select the last one
|
|
||||||
table_state.select(Some(item_count - 1));
|
|
||||||
true // Navigation happened (selection set)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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; }
|
|
||||||
let current_selection = table_state.selected();
|
|
||||||
match current_selection {
|
|
||||||
Some(index) => {
|
|
||||||
if index < item_count - 1 {
|
|
||||||
table_state.select(Some(index + 1));
|
|
||||||
true // Navigation happened
|
|
||||||
} else {
|
|
||||||
false // Was at the bottom
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => { // No item selected, select the first one
|
|
||||||
table_state.select(Some(0));
|
|
||||||
true // Navigation happened (selection set)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,27 +1,20 @@
|
|||||||
// src/functions/modes/navigation/admin_nav.rs
|
// src/functions/modes/navigation/admin_nav.rs
|
||||||
|
use crate::state::pages::admin::{AdminFocus, AdminState};
|
||||||
|
use crate::state::app::state::AppState;
|
||||||
use crate::config::binds::config::Config;
|
use crate::config::binds::config::Config;
|
||||||
use crate::state::{
|
use crate::state::app::buffer::{BufferState, AppView};
|
||||||
app::state::AppState,
|
|
||||||
pages::admin::{AdminFocus, AdminState},
|
|
||||||
};
|
|
||||||
use crossterm::event::KeyEvent;
|
|
||||||
use crate::state::app::buffer::AppView;
|
|
||||||
use crate::state::app::buffer::BufferState;
|
|
||||||
use crate::state::pages::add_table::{AddTableState, LinkDefinition};
|
use crate::state::pages::add_table::{AddTableState, LinkDefinition};
|
||||||
use crate::state::pages::add_logic::AddLogicState;
|
|
||||||
use ratatui::widgets::ListState;
|
use ratatui::widgets::ListState;
|
||||||
|
use crate::state::pages::add_logic::AddLogicState;
|
||||||
|
|
||||||
// --- Helper functions for ListState navigation (similar to TableState) ---
|
// Helper functions list_select_next and list_select_previous remain the same
|
||||||
fn list_select_next(list_state: &mut ListState, item_count: usize) {
|
fn list_select_next(list_state: &mut ListState, item_count: usize) {
|
||||||
if item_count == 0 {
|
if item_count == 0 {
|
||||||
list_state.select(None);
|
list_state.select(None);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let i = match list_state.selected() {
|
let i = match list_state.selected() {
|
||||||
Some(i) => {
|
Some(i) => if i >= item_count - 1 { 0 } else { i + 1 },
|
||||||
if i >= item_count - 1 { 0 } else { i + 1 }
|
|
||||||
}
|
|
||||||
None => 0,
|
None => 0,
|
||||||
};
|
};
|
||||||
list_state.select(Some(i));
|
list_state.select(Some(i));
|
||||||
@@ -33,252 +26,305 @@ fn list_select_previous(list_state: &mut ListState, item_count: usize) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let i = match list_state.selected() {
|
let i = match list_state.selected() {
|
||||||
Some(i) => {
|
Some(i) => if i == 0 { item_count - 1 } else { i - 1 },
|
||||||
if i == 0 { item_count - 1 } else { i - 1 }
|
None => if item_count > 0 { item_count - 1 } else { 0 },
|
||||||
}
|
|
||||||
None => item_count - 1, // Select last if nothing was selected
|
|
||||||
};
|
};
|
||||||
list_state.select(Some(i));
|
list_state.select(Some(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Handles navigation events specifically for the Admin Panel view.
|
|
||||||
/// Returns true if the event was handled, false otherwise.
|
|
||||||
pub fn handle_admin_navigation(
|
pub fn handle_admin_navigation(
|
||||||
key: KeyEvent,
|
key: crossterm::event::KeyEvent,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
app_state: &mut AppState,
|
app_state: &mut AppState,
|
||||||
admin_state: &mut AdminState,
|
admin_state: &mut AdminState,
|
||||||
buffer_state: &mut BufferState,
|
buffer_state: &mut BufferState,
|
||||||
command_message: &mut String,
|
command_message: &mut String,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let action = config.get_general_action(key.code, key.modifiers).map(String::from); // Clone action string
|
let action = config.get_general_action(key.code, key.modifiers).map(String::from);
|
||||||
let current_focus = admin_state.current_focus;
|
let current_focus = admin_state.current_focus;
|
||||||
let profile_count = app_state.profile_tree.profiles.len();
|
let profile_count = app_state.profile_tree.profiles.len();
|
||||||
let mut new_focus = current_focus; // Start with current focus
|
let mut handled = false;
|
||||||
let mut handled = true; // Assume handled unless logic says otherwise
|
|
||||||
|
|
||||||
match action.as_deref() {
|
match current_focus {
|
||||||
// --- Vertical Navigation (Up/Down) ---
|
AdminFocus::ProfilesPane => {
|
||||||
Some("move_up") => {
|
match action.as_deref() {
|
||||||
match current_focus {
|
Some("select") => {
|
||||||
AdminFocus::Profiles => {
|
admin_state.current_focus = AdminFocus::InsideProfilesList;
|
||||||
if profile_count > 0 {
|
if !app_state.profile_tree.profiles.is_empty() {
|
||||||
admin_state.previous_profile(profile_count);
|
if admin_state.profile_list_state.selected().is_none() {
|
||||||
*command_message = "Navigated profiles list".to_string();
|
admin_state.profile_list_state.select(Some(0));
|
||||||
}
|
|
||||||
}
|
|
||||||
AdminFocus::Tables => {
|
|
||||||
*command_message = "Press Enter to select and scroll tables".to_string();
|
|
||||||
}
|
|
||||||
AdminFocus::InsideTablesList => {
|
|
||||||
if let Some(p_idx) = admin_state.profile_list_state.selected().or(admin_state.selected_profile_index) {
|
|
||||||
if let Some(profile) = app_state.profile_tree.profiles.get(p_idx) {
|
|
||||||
list_select_previous(&mut admin_state.table_list_state, profile.tables.len());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*command_message = "Navigating profiles. Use Up/Down. Esc to exit.".to_string();
|
||||||
|
handled = true;
|
||||||
}
|
}
|
||||||
AdminFocus::Button1 | AdminFocus::Button2 | AdminFocus::Button3 => {}
|
Some("next_option") | Some("move_down") => {
|
||||||
}
|
admin_state.current_focus = AdminFocus::Tables;
|
||||||
}
|
*command_message = "Focus: Tables Pane".to_string();
|
||||||
Some("move_down") => {
|
handled = true;
|
||||||
match current_focus {
|
|
||||||
AdminFocus::Profiles => {
|
|
||||||
if profile_count > 0 {
|
|
||||||
admin_state.next_profile(profile_count);
|
|
||||||
*command_message = "Navigated profiles list".to_string();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
AdminFocus::Tables => {
|
Some("previous_option") | Some("move_up") => {
|
||||||
*command_message = "Press Enter to select and scroll tables".to_string();
|
// No wrap-around: Stay on ProfilesPane if trying to go "before" it
|
||||||
}
|
*command_message = "At first focusable pane.".to_string();
|
||||||
AdminFocus::InsideTablesList => {
|
handled = true;
|
||||||
if let Some(p_idx) = admin_state.profile_list_state.selected().or(admin_state.selected_profile_index) {
|
|
||||||
if let Some(profile) = app_state.profile_tree.profiles.get(p_idx) {
|
|
||||||
list_select_next(&mut admin_state.table_list_state, profile.tables.len());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
AdminFocus::Button1 | AdminFocus::Button2 | AdminFocus::Button3 => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// --- Horizontal Navigation (Focus Change) ---
|
|
||||||
Some("next_option") | Some("previous_option") => {
|
|
||||||
let old_focus = admin_state.current_focus;
|
|
||||||
let is_next = action.as_deref() == Some("next_option");
|
|
||||||
|
|
||||||
admin_state.current_focus = match old_focus {
|
|
||||||
AdminFocus::Profiles => if is_next { AdminFocus::Tables } else { AdminFocus::Button3 },
|
|
||||||
AdminFocus::Tables => if is_next { AdminFocus::Button1 } else { AdminFocus::Profiles },
|
|
||||||
AdminFocus::Button1 => if is_next { AdminFocus::Button2 } else { AdminFocus::Tables },
|
|
||||||
AdminFocus::Button2 => if is_next { AdminFocus::Button3 } else { AdminFocus::Button1 },
|
|
||||||
AdminFocus::Button3 => if is_next { AdminFocus::Profiles } else { AdminFocus::Button2 },
|
|
||||||
AdminFocus::InsideTablesList => old_focus,
|
|
||||||
};
|
|
||||||
|
|
||||||
new_focus = admin_state.current_focus; // Update new_focus after changing admin_state.current_focus
|
|
||||||
*command_message = format!("Focus set to {:?}", new_focus);
|
|
||||||
if old_focus == AdminFocus::Profiles && new_focus == AdminFocus::Tables && is_next {
|
|
||||||
if let Some(profile_idx) = admin_state.profile_list_state.selected() {
|
|
||||||
if let Some(profile) = app_state.profile_tree.profiles.get(profile_idx) {
|
|
||||||
if !profile.tables.is_empty() {
|
|
||||||
admin_state.table_list_state.select(Some(0));
|
|
||||||
} else {
|
|
||||||
admin_state.table_list_state.select(None);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
admin_state.table_list_state.select(None);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
admin_state.table_list_state.select(None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if old_focus == AdminFocus::Tables && new_focus != AdminFocus::Tables && old_focus != AdminFocus::InsideTablesList {
|
|
||||||
admin_state.table_list_state.select(None);
|
|
||||||
}
|
|
||||||
// No change needed for profile_list_state clearing here based on current logic
|
|
||||||
}
|
|
||||||
// --- Selection ---
|
|
||||||
Some("select") => {
|
|
||||||
match current_focus {
|
|
||||||
AdminFocus::Profiles => {
|
|
||||||
if let Some(nav_idx) = admin_state.profile_list_state.selected() {
|
|
||||||
admin_state.selected_profile_index = Some(nav_idx);
|
|
||||||
new_focus = AdminFocus::Tables;
|
|
||||||
admin_state.table_list_state.select(None);
|
|
||||||
admin_state.selected_table_index = None;
|
|
||||||
if let Some(profile) = app_state.profile_tree.profiles.get(nav_idx) {
|
|
||||||
if !profile.tables.is_empty() {
|
|
||||||
admin_state.table_list_state.select(Some(0));
|
|
||||||
}
|
|
||||||
*command_message = format!("Selected profile: {}", app_state.profile_tree.profiles[nav_idx].name);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
*command_message = "No profile selected".to_string();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
AdminFocus::Tables => {
|
|
||||||
new_focus = AdminFocus::InsideTablesList;
|
|
||||||
if let Some(p_idx) = admin_state.profile_list_state.selected().or(admin_state.selected_profile_index) {
|
|
||||||
if let Some(profile) = app_state.profile_tree.profiles.get(p_idx) {
|
|
||||||
if admin_state.table_list_state.selected().is_none() && !profile.tables.is_empty() {
|
|
||||||
admin_state.table_list_state.select(Some(0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*command_message = "Entered Tables List (Select item with Enter, Exit with Esc)".to_string();
|
|
||||||
}
|
|
||||||
AdminFocus::InsideTablesList => {
|
|
||||||
if let Some(nav_idx) = admin_state.table_list_state.selected() {
|
|
||||||
admin_state.selected_table_index = Some(nav_idx);
|
|
||||||
let table_name = admin_state.profile_list_state.selected().or(admin_state.selected_profile_index)
|
|
||||||
.and_then(|p_idx| app_state.profile_tree.profiles.get(p_idx))
|
|
||||||
.and_then(|p| p.tables.get(nav_idx).map(|t| t.name.clone()))
|
|
||||||
.unwrap_or_else(|| "N/A".to_string());
|
|
||||||
*command_message = format!("Selected table: {}", table_name);
|
|
||||||
} else {
|
|
||||||
*command_message = "No table highlighted".to_string();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// In src/functions/modes/navigation/admin_nav.rs
|
|
||||||
AdminFocus::Button1 => { // Add Logic
|
|
||||||
let mut logic_state_profile_name = "None (Global)".to_string();
|
|
||||||
let mut selected_table_id: Option<i64> = None;
|
|
||||||
let mut selected_table_name_for_logic: Option<String> = None;
|
|
||||||
if let Some(p_idx) = admin_state.selected_profile_index {
|
|
||||||
if let Some(profile) = app_state.profile_tree.profiles.get(p_idx) {
|
|
||||||
logic_state_profile_name = profile.name.clone();
|
|
||||||
// Check for persistently selected table within this profile
|
|
||||||
if let Some(t_idx) = admin_state.selected_table_index {
|
|
||||||
if let Some(table) = profile.tables.get(t_idx) {
|
|
||||||
selected_table_id = None;
|
|
||||||
selected_table_name_for_logic = Some(table.name.clone());
|
|
||||||
*command_message = format!("Adding logic for table: {}. CRITICAL: Table ID not found in profile tree response!", table.name);
|
|
||||||
} else {
|
|
||||||
*command_message = format!("Selected table index {} out of bounds for profile '{}'. Logic will not be table-specific.", t_idx, profile.name);
|
|
||||||
}} else {
|
|
||||||
*command_message = format!("No table selected in profile '{}'. Logic will not be table-specific.", profile.name);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
*command_message = "Error: Selected profile index out of bounds, associating with 'None'.".to_string();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
*command_message = "No profile selected ([*]), associating Logic with 'None (Global)'.".to_string();
|
|
||||||
// Keep logic_state_profile_name as "None (Global)"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create AddLogicState with the loaded config
|
|
||||||
admin_state.add_logic_state = AddLogicState {
|
|
||||||
profile_name: logic_state_profile_name.clone(),
|
|
||||||
editor_keybinding_mode: config.editor.keybinding_mode.clone(), // Add this line
|
|
||||||
..AddLogicState::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
buffer_state.update_history(AppView::AddLogic);
|
|
||||||
app_state.ui.focus_outside_canvas = false;
|
|
||||||
// Rest of the code remains the same...
|
|
||||||
}
|
|
||||||
AdminFocus::Button2 => {
|
|
||||||
if let Some(p_idx) = admin_state.selected_profile_index {
|
|
||||||
if let Some(profile) = app_state.profile_tree.profiles.get(p_idx) {
|
|
||||||
let selected_profile_name = profile.name.clone();
|
|
||||||
let available_links: Vec<LinkDefinition> = profile
|
|
||||||
.tables
|
|
||||||
.iter()
|
|
||||||
.map(|table| LinkDefinition {
|
|
||||||
linked_table_name: table.name.clone(),
|
|
||||||
is_required: false,
|
|
||||||
selected: false,
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let new_add_table_state = AddTableState {
|
|
||||||
profile_name: selected_profile_name,
|
|
||||||
links: available_links,
|
|
||||||
..AddTableState::default()
|
|
||||||
};
|
|
||||||
admin_state.add_table_state = new_add_table_state;
|
|
||||||
buffer_state.update_history(AppView::AddTable);
|
|
||||||
app_state.ui.focus_outside_canvas = false;
|
|
||||||
*command_message = format!(
|
|
||||||
"Navigating to Add Table for profile '{}'...",
|
|
||||||
admin_state.add_table_state.profile_name
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
*command_message = "Error: Selected profile index out of bounds.".to_string();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
*command_message = "Please select a profile ([*]) first.".to_string();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
AdminFocus::Button3 => {
|
|
||||||
*command_message = "Action: Change Table (Not Implemented)".to_string();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some("exit_table_scroll") => {
|
|
||||||
match current_focus {
|
|
||||||
AdminFocus::InsideTablesList => {
|
|
||||||
new_focus = AdminFocus::Tables;
|
|
||||||
admin_state.table_list_state.select(None);
|
|
||||||
*command_message = "Exited Tables List".to_string();
|
|
||||||
}
|
}
|
||||||
_ => handled = false,
|
_ => handled = false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some("toggle_sidebar") | Some("toggle_buffer_list") | Some("next_field") | Some("prev_field") => {
|
|
||||||
handled = false;
|
|
||||||
}
|
|
||||||
_ => handled = false,
|
|
||||||
}
|
|
||||||
|
|
||||||
if handled && admin_state.current_focus != new_focus { // Check admin_state.current_focus
|
AdminFocus::InsideProfilesList => {
|
||||||
admin_state.current_focus = new_focus;
|
match action.as_deref() {
|
||||||
if command_message.is_empty() || command_message.starts_with("Focus set to") {
|
Some("move_up") => {
|
||||||
*command_message = format!("Focus set to {:?}", admin_state.current_focus);
|
if profile_count > 0 {
|
||||||
|
list_select_previous(&mut admin_state.profile_list_state, profile_count);
|
||||||
|
*command_message = "".to_string();
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some("move_down") => {
|
||||||
|
if profile_count > 0 {
|
||||||
|
list_select_next(&mut admin_state.profile_list_state, profile_count);
|
||||||
|
*command_message = "".to_string();
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some("select") => {
|
||||||
|
admin_state.selected_profile_index = admin_state.profile_list_state.selected();
|
||||||
|
admin_state.selected_table_index = None;
|
||||||
|
if let Some(profile_idx) = admin_state.selected_profile_index {
|
||||||
|
if let Some(profile) = app_state.profile_tree.profiles.get(profile_idx) {
|
||||||
|
if !profile.tables.is_empty() {
|
||||||
|
admin_state.table_list_state.select(Some(0));
|
||||||
|
} else {
|
||||||
|
admin_state.table_list_state.select(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
admin_state.table_list_state.select(None);
|
||||||
|
}
|
||||||
|
*command_message = format!(
|
||||||
|
"Profile '{}' set as active.",
|
||||||
|
admin_state.get_selected_profile_name().unwrap_or(&"N/A".to_string())
|
||||||
|
);
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
Some("exit_table_scroll") => {
|
||||||
|
admin_state.current_focus = AdminFocus::ProfilesPane;
|
||||||
|
*command_message = "Focus: Profiles Pane".to_string();
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
_ => handled = false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AdminFocus::Tables => {
|
||||||
|
match action.as_deref() {
|
||||||
|
Some("select") => {
|
||||||
|
admin_state.current_focus = AdminFocus::InsideTablesList;
|
||||||
|
let current_profile_idx = admin_state.selected_profile_index
|
||||||
|
.or_else(|| admin_state.profile_list_state.selected());
|
||||||
|
if let Some(profile_idx) = current_profile_idx {
|
||||||
|
if let Some(profile) = app_state.profile_tree.profiles.get(profile_idx) {
|
||||||
|
if !profile.tables.is_empty() {
|
||||||
|
if admin_state.table_list_state.selected().is_none() {
|
||||||
|
admin_state.table_list_state.select(Some(0));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
admin_state.table_list_state.select(None);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
admin_state.table_list_state.select(None);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
admin_state.table_list_state.select(None);
|
||||||
|
*command_message = "Select a profile first to view its tables.".to_string();
|
||||||
|
}
|
||||||
|
if admin_state.current_focus == AdminFocus::InsideTablesList && !admin_state.table_list_state.selected().is_none() {
|
||||||
|
*command_message = "Navigating tables. Use Up/Down. Esc to exit.".to_string();
|
||||||
|
} else if admin_state.table_list_state.selected().is_none() {
|
||||||
|
if current_profile_idx.is_none() {
|
||||||
|
*command_message = "No profile selected to view tables.".to_string();
|
||||||
|
} else {
|
||||||
|
*command_message = "No tables in selected profile.".to_string();
|
||||||
|
}
|
||||||
|
admin_state.current_focus = AdminFocus::Tables;
|
||||||
|
}
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
Some("previous_option") | Some("move_up") => {
|
||||||
|
admin_state.current_focus = AdminFocus::ProfilesPane;
|
||||||
|
*command_message = "Focus: Profiles Pane".to_string();
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
Some("next_option") | Some("move_down") => {
|
||||||
|
admin_state.current_focus = AdminFocus::Button1;
|
||||||
|
*command_message = "Focus: Add Logic Button".to_string();
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
_ => handled = false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AdminFocus::InsideTablesList => {
|
||||||
|
match action.as_deref() {
|
||||||
|
Some("move_up") => {
|
||||||
|
let current_profile_idx = admin_state.selected_profile_index
|
||||||
|
.or_else(|| admin_state.profile_list_state.selected());
|
||||||
|
if let Some(p_idx) = current_profile_idx {
|
||||||
|
if let Some(profile) = app_state.profile_tree.profiles.get(p_idx) {
|
||||||
|
if !profile.tables.is_empty() {
|
||||||
|
list_select_previous(&mut admin_state.table_list_state, profile.tables.len());
|
||||||
|
*command_message = "".to_string();
|
||||||
|
handled = true;
|
||||||
|
} else {
|
||||||
|
*command_message = "No tables to navigate.".to_string();
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
*command_message = "No active profile for tables.".to_string();
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some("move_down") => {
|
||||||
|
let current_profile_idx = admin_state.selected_profile_index
|
||||||
|
.or_else(|| admin_state.profile_list_state.selected());
|
||||||
|
if let Some(p_idx) = current_profile_idx {
|
||||||
|
if let Some(profile) = app_state.profile_tree.profiles.get(p_idx) {
|
||||||
|
if !profile.tables.is_empty() {
|
||||||
|
list_select_next(&mut admin_state.table_list_state, profile.tables.len());
|
||||||
|
*command_message = "".to_string();
|
||||||
|
handled = true;
|
||||||
|
} else {
|
||||||
|
*command_message = "No tables to navigate.".to_string();
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
*command_message = "No active profile for tables.".to_string();
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some("select") => {
|
||||||
|
admin_state.selected_table_index = admin_state.table_list_state.selected();
|
||||||
|
let table_name = admin_state.selected_profile_index
|
||||||
|
.or_else(|| admin_state.profile_list_state.selected())
|
||||||
|
.and_then(|p_idx| app_state.profile_tree.profiles.get(p_idx))
|
||||||
|
.and_then(|p| admin_state.selected_table_index.and_then(|t_idx| p.tables.get(t_idx)))
|
||||||
|
.map_or("N/A", |t| t.name.as_str());
|
||||||
|
*command_message = format!("Table '{}' set as active.", table_name);
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
Some("exit_table_scroll") => {
|
||||||
|
admin_state.current_focus = AdminFocus::Tables;
|
||||||
|
*command_message = "Focus: Tables Pane".to_string();
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
_ => handled = false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AdminFocus::Button1 => {
|
||||||
|
match action.as_deref() {
|
||||||
|
Some("select") => {
|
||||||
|
let mut logic_state_profile_name = "None (Global)".to_string();
|
||||||
|
let mut selected_table_name_for_logic: Option<String> = None;
|
||||||
|
if let Some(p_idx) = admin_state.selected_profile_index {
|
||||||
|
if let Some(profile) = app_state.profile_tree.profiles.get(p_idx) {
|
||||||
|
logic_state_profile_name = profile.name.clone();
|
||||||
|
if let Some(t_idx) = admin_state.selected_table_index {
|
||||||
|
if let Some(table) = profile.tables.get(t_idx) {
|
||||||
|
selected_table_name_for_logic = Some(table.name.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
admin_state.add_logic_state = AddLogicState {
|
||||||
|
profile_name: logic_state_profile_name.clone(),
|
||||||
|
selected_table_name: selected_table_name_for_logic,
|
||||||
|
editor_keybinding_mode: config.editor.keybinding_mode.clone(),
|
||||||
|
..AddLogicState::default()
|
||||||
|
};
|
||||||
|
buffer_state.update_history(AppView::AddLogic);
|
||||||
|
app_state.ui.focus_outside_canvas = false;
|
||||||
|
*command_message = "Opening Add Logic...".to_string();
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
Some("previous_option") | Some("move_up") => {
|
||||||
|
admin_state.current_focus = AdminFocus::Tables;
|
||||||
|
*command_message = "Focus: Tables Pane".to_string();
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
Some("next_option") | Some("move_down") => {
|
||||||
|
admin_state.current_focus = AdminFocus::Button2;
|
||||||
|
*command_message = "Focus: Add Table Button".to_string();
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
_ => handled = false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AdminFocus::Button2 => {
|
||||||
|
match action.as_deref() {
|
||||||
|
Some("select") => {
|
||||||
|
if let Some(p_idx) = admin_state.selected_profile_index {
|
||||||
|
if let Some(profile) = app_state.profile_tree.profiles.get(p_idx) {
|
||||||
|
let selected_profile_name = profile.name.clone();
|
||||||
|
let available_links: Vec<LinkDefinition> = profile.tables.iter()
|
||||||
|
.map(|table| LinkDefinition {
|
||||||
|
linked_table_name: table.name.clone(),
|
||||||
|
is_required: false, selected: false,
|
||||||
|
}).collect();
|
||||||
|
admin_state.add_table_state = AddTableState {
|
||||||
|
profile_name: selected_profile_name, links: available_links,
|
||||||
|
..AddTableState::default()
|
||||||
|
};
|
||||||
|
buffer_state.update_history(AppView::AddTable);
|
||||||
|
app_state.ui.focus_outside_canvas = false;
|
||||||
|
*command_message = format!("Opening Add Table for profile '{}'...", admin_state.add_table_state.profile_name);
|
||||||
|
handled = true;
|
||||||
|
} else {
|
||||||
|
*command_message = "Error: Selected profile index out of bounds.".to_string();
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
*command_message = "Please select a profile ([*]) first to add a table.".to_string();
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some("previous_option") | Some("move_up") => {
|
||||||
|
admin_state.current_focus = AdminFocus::Button1;
|
||||||
|
*command_message = "Focus: Add Logic Button".to_string();
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
Some("next_option") | Some("move_down") => {
|
||||||
|
admin_state.current_focus = AdminFocus::Button3;
|
||||||
|
*command_message = "Focus: Change Table Button".to_string();
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
_ => handled = false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AdminFocus::Button3 => {
|
||||||
|
match action.as_deref() {
|
||||||
|
Some("select") => {
|
||||||
|
*command_message = "Action: Change Table (Not Implemented)".to_string();
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
Some("previous_option") | Some("move_up") => {
|
||||||
|
admin_state.current_focus = AdminFocus::Button2;
|
||||||
|
*command_message = "Focus: Add Table Button".to_string();
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
Some("next_option") | Some("move_down") => {
|
||||||
|
// No wrap-around: Stay on Button3 if trying to go "after" it
|
||||||
|
*command_message = "At last focusable button.".to_string();
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
_ => handled = false,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handled
|
handled
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// src/functions/modes/read_only/add_table_ro.rs
|
// src/functions/modes/read_only/add_table_ro.rs
|
||||||
use crate::config::binds::key_sequences::KeySequenceTracker;
|
use crate::config::binds::key_sequences::KeySequenceTracker;
|
||||||
use crate::state::pages::add_table::AddTableState;
|
use crate::state::pages::add_table::AddTableState;
|
||||||
use crate::state::pages::canvas_state::CanvasState; // Use trait for common actions
|
use crate::state::pages::canvas_state::CanvasState;
|
||||||
use crate::state::app::state::AppState;
|
use crate::state::app::state::AppState;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
@@ -80,41 +80,36 @@ pub async fn execute_action(
|
|||||||
"move_up" => {
|
"move_up" => {
|
||||||
key_sequence_tracker.reset();
|
key_sequence_tracker.reset();
|
||||||
let num_fields = AddTableState::INPUT_FIELD_COUNT;
|
let num_fields = AddTableState::INPUT_FIELD_COUNT;
|
||||||
if num_fields == 0 { return Ok("No fields.".to_string()); }
|
if num_fields == 0 {
|
||||||
|
*command_message = "No fields.".to_string();
|
||||||
|
return Ok(command_message.clone());
|
||||||
|
}
|
||||||
let current_field = state.current_field(); // Gets the index (0, 1, or 2)
|
let current_field = state.current_field(); // Gets the index (0, 1, or 2)
|
||||||
|
|
||||||
if current_field > 0 {
|
if current_field > 0 {
|
||||||
// This handles moving from field 2 -> 1, or 1 -> 0
|
// This handles moving from field 2 -> 1, or 1 -> 0
|
||||||
let new_field = current_field - 1;
|
let new_field = current_field - 1;
|
||||||
state.set_current_field(new_field);
|
state.set_current_field(new_field);
|
||||||
// ... (rest of the logic to set cursor position) ...
|
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);
|
||||||
|
*ideal_cursor_column = new_pos; // Update ideal column as cursor moved
|
||||||
|
*command_message = "".to_string(); // Clear message for successful internal navigation
|
||||||
} else {
|
} else {
|
||||||
// --- THIS IS WHERE THE FIX GOES ---
|
|
||||||
// current_field is 0 (InputTableName), and user pressed Up.
|
// current_field is 0 (InputTableName), and user pressed Up.
|
||||||
// We need to move focus *outside* the canvas.
|
// Forbid moving up. Do not change focus or cursor.
|
||||||
|
*command_message = "At top of form.".to_string();
|
||||||
// Set the flag to indicate focus is leaving the canvas
|
|
||||||
app_state.ui.focus_outside_canvas = true;
|
|
||||||
|
|
||||||
// Decide which element gets focus. Based on your layout and the
|
|
||||||
// downward navigation (CancelButton wraps to InputTableName),
|
|
||||||
// moving up from InputTableName should likely go to CancelButton.
|
|
||||||
state.current_focus = crate::state::pages::add_table::AddTableFocus::CancelButton;
|
|
||||||
|
|
||||||
// Reset the sequence tracker as the action is complete
|
|
||||||
key_sequence_tracker.reset();
|
|
||||||
|
|
||||||
// Return a message indicating the focus change
|
|
||||||
return Ok("Focus moved above canvas".to_string());
|
|
||||||
// --- END FIX ---
|
|
||||||
}
|
}
|
||||||
// If we moved within the canvas (e.g., 1 -> 0), return empty string
|
Ok(command_message.clone())
|
||||||
Ok("".to_string())
|
|
||||||
}
|
}
|
||||||
"move_down" => {
|
"move_down" => {
|
||||||
key_sequence_tracker.reset();
|
key_sequence_tracker.reset();
|
||||||
let num_fields = AddTableState::INPUT_FIELD_COUNT;
|
let num_fields = AddTableState::INPUT_FIELD_COUNT;
|
||||||
if num_fields == 0 { return Ok("No fields.".to_string()); }
|
if num_fields == 0 {
|
||||||
|
*command_message = "No fields.".to_string();
|
||||||
|
return Ok(command_message.clone());
|
||||||
|
}
|
||||||
let current_field = state.current_field();
|
let current_field = state.current_field();
|
||||||
let last_field_index = num_fields - 1;
|
let last_field_index = num_fields - 1;
|
||||||
|
|
||||||
@@ -125,16 +120,19 @@ pub async fn execute_action(
|
|||||||
let max_cursor_pos = current_input.len(); // Allow cursor at end
|
let max_cursor_pos = current_input.len(); // Allow cursor at end
|
||||||
let new_pos = (*ideal_cursor_column).min(max_cursor_pos);
|
let new_pos = (*ideal_cursor_column).min(max_cursor_pos);
|
||||||
state.set_current_cursor_pos(new_pos);
|
state.set_current_cursor_pos(new_pos);
|
||||||
|
*ideal_cursor_column = new_pos; // Update ideal column
|
||||||
|
*command_message = "".to_string();
|
||||||
} else {
|
} else {
|
||||||
// Move focus outside canvas when moving down from the last field
|
// Move focus outside canvas when moving down from the last field
|
||||||
app_state.ui.focus_outside_canvas = true;
|
app_state.ui.focus_outside_canvas = true;
|
||||||
// Set focus to the first element outside canvas (AddColumnButton)
|
// Set focus to the first element outside canvas (AddColumnButton)
|
||||||
state.current_focus = crate::state::pages::add_table::AddTableFocus::AddColumnButton;
|
state.current_focus =
|
||||||
key_sequence_tracker.reset();
|
crate::state::pages::add_table::AddTableFocus::AddColumnButton;
|
||||||
return Ok("Focus moved below canvas".to_string());
|
*command_message = "Focus moved below canvas".to_string();
|
||||||
}
|
}
|
||||||
Ok("".to_string())
|
Ok(command_message.clone())
|
||||||
}
|
}
|
||||||
|
// ... (other actions like "move_first_line", "move_left", etc. remain the same) ...
|
||||||
"move_first_line" => {
|
"move_first_line" => {
|
||||||
key_sequence_tracker.reset();
|
key_sequence_tracker.reset();
|
||||||
if AddTableState::INPUT_FIELD_COUNT > 0 {
|
if AddTableState::INPUT_FIELD_COUNT > 0 {
|
||||||
@@ -145,7 +143,8 @@ pub async fn execute_action(
|
|||||||
state.set_current_cursor_pos(new_pos);
|
state.set_current_cursor_pos(new_pos);
|
||||||
*ideal_cursor_column = new_pos; // Update ideal column
|
*ideal_cursor_column = new_pos; // Update ideal column
|
||||||
}
|
}
|
||||||
Ok("".to_string())
|
*command_message = "".to_string();
|
||||||
|
Ok(command_message.clone())
|
||||||
}
|
}
|
||||||
"move_last_line" => {
|
"move_last_line" => {
|
||||||
key_sequence_tracker.reset();
|
key_sequence_tracker.reset();
|
||||||
@@ -159,14 +158,16 @@ pub async fn execute_action(
|
|||||||
state.set_current_cursor_pos(new_pos);
|
state.set_current_cursor_pos(new_pos);
|
||||||
*ideal_cursor_column = new_pos; // Update ideal column
|
*ideal_cursor_column = new_pos; // Update ideal column
|
||||||
}
|
}
|
||||||
Ok("".to_string())
|
*command_message = "".to_string();
|
||||||
|
Ok(command_message.clone())
|
||||||
}
|
}
|
||||||
"move_left" => {
|
"move_left" => {
|
||||||
let current_pos = state.current_cursor_pos();
|
let current_pos = state.current_cursor_pos();
|
||||||
let new_pos = current_pos.saturating_sub(1);
|
let new_pos = current_pos.saturating_sub(1);
|
||||||
state.set_current_cursor_pos(new_pos);
|
state.set_current_cursor_pos(new_pos);
|
||||||
*ideal_cursor_column = new_pos;
|
*ideal_cursor_column = new_pos;
|
||||||
Ok("".to_string())
|
*command_message = "".to_string();
|
||||||
|
Ok(command_message.clone())
|
||||||
}
|
}
|
||||||
"move_right" => {
|
"move_right" => {
|
||||||
let current_input = state.get_current_input();
|
let current_input = state.get_current_input();
|
||||||
@@ -177,68 +178,90 @@ pub async fn execute_action(
|
|||||||
state.set_current_cursor_pos(new_pos);
|
state.set_current_cursor_pos(new_pos);
|
||||||
*ideal_cursor_column = new_pos;
|
*ideal_cursor_column = new_pos;
|
||||||
}
|
}
|
||||||
Ok("".to_string())
|
*command_message = "".to_string();
|
||||||
|
Ok(command_message.clone())
|
||||||
}
|
}
|
||||||
"move_word_next" => {
|
"move_word_next" => {
|
||||||
let current_input = state.get_current_input();
|
let current_input = state.get_current_input();
|
||||||
let new_pos = find_next_word_start(current_input, state.current_cursor_pos());
|
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
|
let final_pos = new_pos.min(current_input.len()); // Allow cursor at end
|
||||||
state.set_current_cursor_pos(final_pos);
|
state.set_current_cursor_pos(final_pos);
|
||||||
*ideal_cursor_column = final_pos;
|
*ideal_cursor_column = final_pos;
|
||||||
Ok("".to_string())
|
*command_message = "".to_string();
|
||||||
|
Ok(command_message.clone())
|
||||||
}
|
}
|
||||||
"move_word_end" => {
|
"move_word_end" => {
|
||||||
let current_input = state.get_current_input();
|
let current_input = state.get_current_input();
|
||||||
let current_pos = state.current_cursor_pos();
|
let current_pos = state.current_cursor_pos();
|
||||||
let new_pos = find_word_end(current_input, current_pos);
|
let new_pos = find_word_end(current_input, current_pos);
|
||||||
// If find_word_end returns current_pos, try starting search from next char
|
// 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() {
|
let final_pos =
|
||||||
find_word_end(current_input, current_pos + 1)
|
if new_pos == current_pos && current_pos < current_input.len() {
|
||||||
} else {
|
find_word_end(current_input, current_pos + 1)
|
||||||
new_pos
|
} else {
|
||||||
};
|
new_pos
|
||||||
|
};
|
||||||
let max_valid_index = current_input.len(); // Allow cursor at end
|
let max_valid_index = current_input.len(); // Allow cursor at end
|
||||||
let clamped_pos = final_pos.min(max_valid_index);
|
let clamped_pos = final_pos.min(max_valid_index);
|
||||||
state.set_current_cursor_pos(clamped_pos);
|
state.set_current_cursor_pos(clamped_pos);
|
||||||
*ideal_cursor_column = clamped_pos;
|
*ideal_cursor_column = clamped_pos;
|
||||||
Ok("".to_string())
|
*command_message = "".to_string();
|
||||||
|
Ok(command_message.clone())
|
||||||
}
|
}
|
||||||
"move_word_prev" => {
|
"move_word_prev" => {
|
||||||
let current_input = state.get_current_input();
|
let current_input = state.get_current_input();
|
||||||
let new_pos = find_prev_word_start(current_input, state.current_cursor_pos());
|
let new_pos = find_prev_word_start(
|
||||||
|
current_input,
|
||||||
|
state.current_cursor_pos(),
|
||||||
|
);
|
||||||
state.set_current_cursor_pos(new_pos);
|
state.set_current_cursor_pos(new_pos);
|
||||||
*ideal_cursor_column = new_pos;
|
*ideal_cursor_column = new_pos;
|
||||||
Ok("".to_string())
|
*command_message = "".to_string();
|
||||||
|
Ok(command_message.clone())
|
||||||
}
|
}
|
||||||
"move_word_end_prev" => {
|
"move_word_end_prev" => {
|
||||||
let current_input = state.get_current_input();
|
let current_input = state.get_current_input();
|
||||||
let new_pos = find_prev_word_end(current_input, state.current_cursor_pos());
|
let new_pos = find_prev_word_end(
|
||||||
|
current_input,
|
||||||
|
state.current_cursor_pos(),
|
||||||
|
);
|
||||||
state.set_current_cursor_pos(new_pos);
|
state.set_current_cursor_pos(new_pos);
|
||||||
*ideal_cursor_column = new_pos;
|
*ideal_cursor_column = new_pos;
|
||||||
Ok("".to_string())
|
*command_message = "".to_string();
|
||||||
|
Ok(command_message.clone())
|
||||||
}
|
}
|
||||||
"move_line_start" => {
|
"move_line_start" => {
|
||||||
state.set_current_cursor_pos(0);
|
state.set_current_cursor_pos(0);
|
||||||
*ideal_cursor_column = 0;
|
*ideal_cursor_column = 0;
|
||||||
Ok("".to_string())
|
*command_message = "".to_string();
|
||||||
|
Ok(command_message.clone())
|
||||||
}
|
}
|
||||||
"move_line_end" => {
|
"move_line_end" => {
|
||||||
let current_input = state.get_current_input();
|
let current_input = state.get_current_input();
|
||||||
let new_pos = current_input.len(); // Allow cursor at end
|
let new_pos = current_input.len(); // Allow cursor at end
|
||||||
state.set_current_cursor_pos(new_pos);
|
state.set_current_cursor_pos(new_pos);
|
||||||
*ideal_cursor_column = new_pos;
|
*ideal_cursor_column = new_pos;
|
||||||
Ok("".to_string())
|
*command_message = "".to_string();
|
||||||
|
Ok(command_message.clone())
|
||||||
}
|
}
|
||||||
// Actions handled by main event loop (mode changes)
|
// Actions handled by main event loop (mode changes)
|
||||||
"enter_edit_mode_before" | "enter_edit_mode_after" | "enter_command_mode" | "exit_highlight_mode" => {
|
"enter_edit_mode_before" | "enter_edit_mode_after"
|
||||||
key_sequence_tracker.reset();
|
| "enter_command_mode" | "exit_highlight_mode" => {
|
||||||
Ok("Mode change handled by main loop".to_string())
|
key_sequence_tracker.reset();
|
||||||
|
// These actions are primarily mode changes handled by the main event loop.
|
||||||
|
// The message here might be overridden by the main loop's message for mode change.
|
||||||
|
*command_message = "Mode change initiated".to_string();
|
||||||
|
Ok(command_message.clone())
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
key_sequence_tracker.reset();
|
key_sequence_tracker.reset();
|
||||||
command_message.clear(); // Clear message for unhandled actions
|
*command_message =
|
||||||
Ok(format!("Unknown read-only action: {}", action))
|
format!("Unknown read-only action: {}", action);
|
||||||
},
|
Ok(command_message.clone())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ pub struct AddTableState {
|
|||||||
pub indexes: Vec<IndexDefinition>,
|
pub indexes: Vec<IndexDefinition>,
|
||||||
pub links: Vec<LinkDefinition>,
|
pub links: Vec<LinkDefinition>,
|
||||||
pub current_focus: AddTableFocus,
|
pub current_focus: AddTableFocus,
|
||||||
|
pub last_canvas_field: usize,
|
||||||
pub column_table_state: TableState,
|
pub column_table_state: TableState,
|
||||||
pub index_table_state: TableState,
|
pub index_table_state: TableState,
|
||||||
pub link_table_state: TableState,
|
pub link_table_state: TableState,
|
||||||
@@ -77,6 +78,7 @@ impl Default for AddTableState {
|
|||||||
indexes: Vec::new(),
|
indexes: Vec::new(),
|
||||||
links: Vec::new(),
|
links: Vec::new(),
|
||||||
current_focus: AddTableFocus::InputTableName,
|
current_focus: AddTableFocus::InputTableName,
|
||||||
|
last_canvas_field: 2,
|
||||||
column_table_state: TableState::default(),
|
column_table_state: TableState::default(),
|
||||||
index_table_state: TableState::default(),
|
index_table_state: TableState::default(),
|
||||||
link_table_state: TableState::default(),
|
link_table_state: TableState::default(),
|
||||||
@@ -100,7 +102,7 @@ impl CanvasState for AddTableState {
|
|||||||
AddTableFocus::InputColumnName => 1,
|
AddTableFocus::InputColumnName => 1,
|
||||||
AddTableFocus::InputColumnType => 2,
|
AddTableFocus::InputColumnType => 2,
|
||||||
// If focus is elsewhere, default to the first field for canvas rendering logic
|
// If focus is elsewhere, default to the first field for canvas rendering logic
|
||||||
_ => 0,
|
_ => self.last_canvas_field,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,10 +147,20 @@ impl CanvasState for AddTableState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn set_current_field(&mut self, index: usize) {
|
fn set_current_field(&mut self, index: usize) {
|
||||||
|
// Update both current focus and last canvas field
|
||||||
self.current_focus = match index {
|
self.current_focus = match index {
|
||||||
0 => AddTableFocus::InputTableName,
|
0 => {
|
||||||
1 => AddTableFocus::InputColumnName,
|
self.last_canvas_field = 0;
|
||||||
2 => AddTableFocus::InputColumnType,
|
AddTableFocus::InputTableName
|
||||||
|
},
|
||||||
|
1 => {
|
||||||
|
self.last_canvas_field = 1;
|
||||||
|
AddTableFocus::InputColumnName
|
||||||
|
},
|
||||||
|
2 => {
|
||||||
|
self.last_canvas_field = 2;
|
||||||
|
AddTableFocus::InputColumnType
|
||||||
|
},
|
||||||
_ => self.current_focus, // Stay on current focus if index is out of bounds
|
_ => self.current_focus, // Stay on current focus if index is out of bounds
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ use crate::state::pages::add_logic::AddLogicState;
|
|||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||||
pub enum AdminFocus {
|
pub enum AdminFocus {
|
||||||
#[default] // Default focus is on the profiles list
|
#[default] // Default focus is on the profiles list
|
||||||
Profiles,
|
ProfilesPane,
|
||||||
|
InsideProfilesList,
|
||||||
Tables,
|
Tables,
|
||||||
InsideTablesList,
|
InsideTablesList,
|
||||||
Button1,
|
Button1,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ use crate::state::pages::auth::AuthState;
|
|||||||
use crate::state::pages::auth::LoginState;
|
use crate::state::pages::auth::LoginState;
|
||||||
use crate::state::pages::auth::RegisterState;
|
use crate::state::pages::auth::RegisterState;
|
||||||
use crate::state::pages::admin::AdminState;
|
use crate::state::pages::admin::AdminState;
|
||||||
|
use crate::state::pages::admin::AdminFocus;
|
||||||
use crate::state::pages::intro::IntroState;
|
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;
|
||||||
@@ -108,11 +109,24 @@ pub async fn run_ui() -> Result<()> {
|
|||||||
event_handler.command_message = format!("Error refreshing admin data: {}", e);
|
event_handler.command_message = format!("Error refreshing admin data: {}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
app_state.ui.show_admin = true;
|
app_state.ui.show_admin = true; // <<< RESTORE THIS
|
||||||
let profile_names = app_state.profile_tree.profiles.iter()
|
let profile_names = app_state.profile_tree.profiles.iter() // <<< RESTORE THIS
|
||||||
.map(|p| p.name.clone())
|
.map(|p| p.name.clone()) // <<< RESTORE THIS
|
||||||
.collect();
|
.collect(); // <<< RESTORE THIS
|
||||||
admin_state.set_profiles(profile_names);
|
admin_state.set_profiles(profile_names);
|
||||||
|
|
||||||
|
// Only reset to ProfilesPane if not already in a specific admin sub-focus
|
||||||
|
if admin_state.current_focus == AdminFocus::default() ||
|
||||||
|
!matches!(admin_state.current_focus,
|
||||||
|
AdminFocus::InsideProfilesList |
|
||||||
|
AdminFocus::Tables | AdminFocus::InsideTablesList |
|
||||||
|
AdminFocus::Button1 | AdminFocus::Button2 | AdminFocus::Button3) {
|
||||||
|
admin_state.current_focus = AdminFocus::ProfilesPane;
|
||||||
|
}
|
||||||
|
// Pre-select first profile item for visual consistency, but '>' won't show until 'select'
|
||||||
|
if admin_state.profile_list_state.selected().is_none() && !app_state.profile_tree.profiles.is_empty() {
|
||||||
|
admin_state.profile_list_state.select(Some(0));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
AppView::AddTable => app_state.ui.show_add_table = true,
|
AppView::AddTable => app_state.ui.show_add_table = true,
|
||||||
AppView::AddLogic => app_state.ui.show_add_logic = true,
|
AppView::AddLogic => app_state.ui.show_add_logic = true,
|
||||||
|
|||||||
Reference in New Issue
Block a user