admin panel fixed completely

This commit is contained in:
filipriec
2025-05-25 11:26:19 +02:00
parent 82e96f7b86
commit 2e1d7fdf2b
4 changed files with 397 additions and 390 deletions

View File

@@ -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]);

View File

@@ -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,313 @@ 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") => { // move_down acts as next_option
admin_state.current_focus = AdminFocus::Tables;
*command_message = "Focus: Tables Pane".to_string();
handled = true;
}
Some("previous_option") | Some("move_up") => { // move_up acts as previous_option
admin_state.current_focus = AdminFocus::Button3; // Cycle to last button
*command_message = "Focus: Button 3".to_string();
handled = true;
}
// If move_up/move_down were not explicitly for pane nav,
// they would be caught here if not handled above.
// But since we want them for pane nav, this is fine.
_ => handled = false,
} }
} }
Some("move_down") => {
match current_focus { AdminFocus::InsideProfilesList => {
AdminFocus::Profiles => { match action.as_deref() {
Some("move_up") => { // Only item navigation here
if profile_count > 0 { if profile_count > 0 {
admin_state.next_profile(profile_count); list_select_previous(&mut admin_state.profile_list_state, profile_count);
*command_message = "Navigated profiles list".to_string(); *command_message = "".to_string();
handled = true;
} }
} }
AdminFocus::Tables => { Some("move_down") => { // Only item navigation here
*command_message = "Press Enter to select and scroll tables".to_string(); if profile_count > 0 {
} list_select_next(&mut admin_state.profile_list_state, profile_count);
AdminFocus::InsideTablesList => { *command_message = "".to_string();
if let Some(p_idx) = admin_state.profile_list_state.selected().or(admin_state.selected_profile_index) { handled = true;
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 => {} Some("select") => {
} admin_state.selected_profile_index = admin_state.profile_list_state.selected();
} admin_state.selected_table_index = None;
// --- Horizontal Navigation (Focus Change) --- if let Some(profile_idx) = admin_state.selected_profile_index {
Some("next_option") | Some("previous_option") => { if let Some(profile) = app_state.profile_tree.profiles.get(profile_idx) {
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() { if !profile.tables.is_empty() {
admin_state.table_list_state.select(Some(0)); admin_state.table_list_state.select(Some(0));
} } else {
*command_message = format!("Selected profile: {}", app_state.profile_tree.profiles[nav_idx].name); admin_state.table_list_state.select(None);
}
} 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 { } else {
*command_message = "No profile selected ([*]), associating Logic with 'None (Global)'.".to_string(); admin_state.table_list_state.select(None);
// Keep logic_state_profile_name as "None (Global)"
} }
*command_message = format!(
// Create AddLogicState with the loaded config "Profile '{}' set as active.",
admin_state.add_logic_state = AddLogicState { admin_state.get_selected_profile_name().unwrap_or(&"N/A".to_string())
profile_name: logic_state_profile_name.clone(), );
editor_keybinding_mode: config.editor.keybinding_mode.clone(), // Add this line handled = true;
..AddLogicState::default()
};
buffer_state.update_history(AppView::AddLogic);
app_state.ui.focus_outside_canvas = false;
// Rest of the code remains the same...
} }
AdminFocus::Button2 => { Some("exit_table_scroll") => { // 'esc'
if let Some(p_idx) = admin_state.selected_profile_index { admin_state.current_focus = AdminFocus::ProfilesPane;
if let Some(profile) = app_state.profile_tree.profiles.get(p_idx) { *command_message = "Focus: Profiles Pane".to_string();
let selected_profile_name = profile.name.clone(); handled = true;
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();
} }
// next_option & previous_option are NOT handled here to enforce 'esc' first
_ => handled = false,
} }
} }
Some("exit_table_scroll") => {
match current_focus { AdminFocus::Tables => {
AdminFocus::InsideTablesList => { match action.as_deref() {
new_focus = AdminFocus::Tables; Some("select") => {
admin_state.table_list_state.select(None); admin_state.current_focus = AdminFocus::InsideTablesList;
*command_message = "Exited Tables List".to_string(); 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 no tables, or no profile, don't enter InsideTablesList or revert
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; // Stay on Tables pane
}
handled = true;
}
Some("previous_option") | Some("move_up") => { // move_up acts as previous_option
admin_state.current_focus = AdminFocus::ProfilesPane;
*command_message = "Focus: Profiles Pane".to_string();
handled = true;
}
Some("next_option") | Some("move_down") => { // move_down acts as next_option
admin_state.current_focus = AdminFocus::Button1;
*command_message = "Focus: Add Logic Button".to_string();
handled = true;
} }
_ => 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::InsideTablesList => {
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") => { // Only item navigation
*command_message = format!("Focus set to {:?}", admin_state.current_focus); 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") => { // Only item navigation
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") => { // 'esc'
admin_state.current_focus = AdminFocus::Tables;
*command_message = "Focus: Tables Pane".to_string();
handled = true;
}
// next_option & previous_option are NOT handled here
_ => handled = false,
}
}
// --- Button Navigation ---
// move_up/move_down on buttons will cycle through them or to adjacent panes (Tables/Profiles)
AdminFocus::Button1 => {
match action.as_deref() {
Some("select") => { /* ... existing select logic ... */
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") => { // move_up from Button1 goes to Tables Pane
admin_state.current_focus = AdminFocus::Tables;
*command_message = "Focus: Tables Pane".to_string();
handled = true;
}
Some("next_option") | Some("move_down") => { // move_down from Button1 goes to Button2
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") => { /* ... existing select logic ... */
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") => { // move_up from Button2 goes to Button1
admin_state.current_focus = AdminFocus::Button1;
*command_message = "Focus: Add Logic Button".to_string();
handled = true;
}
Some("next_option") | Some("move_down") => { // move_down from Button2 goes to Button3
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") => { // move_up from Button3 goes to Button2
admin_state.current_focus = AdminFocus::Button2;
*command_message = "Focus: Add Table Button".to_string();
handled = true;
}
Some("next_option") | Some("move_down") => { // move_down from Button3 goes to Profiles Pane
admin_state.current_focus = AdminFocus::ProfilesPane;
*command_message = "Focus: Profiles Pane".to_string();
handled = true;
}
_ => handled = false,
}
} }
} }
handled handled
} }

View File

@@ -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,

View File

@@ -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,