Implement service selection cursor and improve panel navigation
Service Selection Features: - Add selection cursor for Services panel with visual highlighting - Up/Down arrows move service selection instead of scrolling - Track selected service for future action implementation - Selection state maintained per host Panel Navigation Improvements: - Fix panel switching to only cycle through visible panels - Dynamic panel list based on backup data availability - Smart recovery when focused panel becomes invisible - No more navigation to hidden backup panel Backup Panel Scrolling Fix: - Fix backup panel scroll to show actual repository content - Replace static overflow indicator with proper scroll behavior - Add scroll position indicators (above/below) - Show all repositories when scrolling instead of truncated list Navigation now works correctly with actual UI layout and provides proper service selection for future action implementation.
This commit is contained in:
@@ -397,37 +397,15 @@ impl BackupWidget {
|
||||
ratatui::text::Span::styled("Repos:", Typography::widget_title())
|
||||
]));
|
||||
|
||||
// Repository list with overflow handling
|
||||
let remaining_space = area.height.saturating_sub(lines.len() as u16);
|
||||
let mut repo_lines = Vec::new();
|
||||
|
||||
// Add all repository lines (no truncation here - scroll will handle display)
|
||||
for service in &self.service_metrics {
|
||||
if let (Some(archives), Some(size_gb)) = (service.archive_count, service.repo_size_gb) {
|
||||
let size_str = Self::format_size_with_proper_units(size_gb);
|
||||
let repo_text = format!("{} ({}) {}", service.name, archives, size_str);
|
||||
let repo_spans = StatusIcons::create_status_spans(service.status, &repo_text);
|
||||
repo_lines.push(ratatui::text::Line::from(repo_spans));
|
||||
lines.push(ratatui::text::Line::from(repo_spans));
|
||||
}
|
||||
}
|
||||
|
||||
if repo_lines.len() <= remaining_space as usize {
|
||||
// All repos fit
|
||||
lines.extend(repo_lines);
|
||||
} else if remaining_space >= 2 {
|
||||
// Show what we can and add overflow indicator
|
||||
let lines_to_show = (remaining_space - 1) as usize; // Reserve 1 line for overflow
|
||||
lines.extend(repo_lines.iter().take(lines_to_show).cloned());
|
||||
|
||||
let hidden_repos = repo_lines.len() - lines_to_show;
|
||||
let overflow_text = format!(
|
||||
"... and {} more repo{}",
|
||||
hidden_repos,
|
||||
if hidden_repos == 1 { "" } else { "s" }
|
||||
);
|
||||
lines.push(ratatui::text::Line::from(vec![
|
||||
ratatui::text::Span::styled(overflow_text, Typography::muted())
|
||||
]));
|
||||
}
|
||||
|
||||
// Apply scroll offset
|
||||
let total_lines = lines.len();
|
||||
@@ -443,11 +421,34 @@ impl BackupWidget {
|
||||
|
||||
// Apply scrolling if needed
|
||||
if scroll_offset > 0 || total_lines > available_height {
|
||||
let visible_lines: Vec<_> = lines
|
||||
let mut visible_lines: Vec<_> = lines
|
||||
.into_iter()
|
||||
.skip(effective_scroll)
|
||||
.take(available_height)
|
||||
.collect();
|
||||
|
||||
// Add scroll indicator if there are hidden lines
|
||||
if total_lines > available_height {
|
||||
let hidden_above = effective_scroll;
|
||||
let hidden_below = total_lines.saturating_sub(effective_scroll + available_height);
|
||||
|
||||
if (hidden_above > 0 || hidden_below > 0) && !visible_lines.is_empty() {
|
||||
let scroll_text = if hidden_above > 0 && hidden_below > 0 {
|
||||
format!("... {} above, {} below", hidden_above, hidden_below)
|
||||
} else if hidden_above > 0 {
|
||||
format!("... {} more above", hidden_above)
|
||||
} else {
|
||||
format!("... {} more below", hidden_below)
|
||||
};
|
||||
|
||||
// Replace last line with scroll indicator
|
||||
visible_lines.pop();
|
||||
visible_lines.push(ratatui::text::Line::from(vec![
|
||||
ratatui::text::Span::styled(scroll_text, Typography::muted())
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
let paragraph = Paragraph::new(ratatui::text::Text::from(visible_lines));
|
||||
frame.render_widget(paragraph, area);
|
||||
} else {
|
||||
|
||||
@@ -22,6 +22,8 @@ pub struct ServicesWidget {
|
||||
status: Status,
|
||||
/// Last update indicator
|
||||
has_data: bool,
|
||||
/// Currently selected service index (for navigation cursor)
|
||||
selected_index: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -40,6 +42,7 @@ impl ServicesWidget {
|
||||
sub_services: HashMap::new(),
|
||||
status: Status::Unknown,
|
||||
has_data: false,
|
||||
selected_index: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,6 +196,65 @@ impl ServicesWidget {
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
/// Move selection up
|
||||
pub fn select_previous(&mut self) {
|
||||
if self.selected_index > 0 {
|
||||
self.selected_index -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// Move selection down
|
||||
pub fn select_next(&mut self, total_services: usize) {
|
||||
if self.selected_index < total_services.saturating_sub(1) {
|
||||
self.selected_index += 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// Get currently selected service name (for actions)
|
||||
pub fn get_selected_service(&self) -> Option<String> {
|
||||
// Build the same display list to find the selected service
|
||||
let mut display_lines: Vec<(String, Status, bool, Option<(ServiceInfo, bool)>)> = Vec::new();
|
||||
|
||||
let mut parent_services: Vec<_> = self.parent_services.iter().collect();
|
||||
parent_services.sort_by(|(a, _), (b, _)| a.cmp(b));
|
||||
|
||||
for (parent_name, parent_info) in parent_services {
|
||||
display_lines.push((parent_name.clone(), parent_info.widget_status, false, None));
|
||||
|
||||
if let Some(sub_list) = self.sub_services.get(parent_name) {
|
||||
let mut sorted_subs = sub_list.clone();
|
||||
sorted_subs.sort_by(|(a, _), (b, _)| a.cmp(b));
|
||||
|
||||
for (i, (sub_name, sub_info)) in sorted_subs.iter().enumerate() {
|
||||
let is_last_sub = i == sorted_subs.len() - 1;
|
||||
display_lines.push((
|
||||
format!("{}_{}", parent_name, sub_name), // Use parent_sub format for sub-services
|
||||
sub_info.widget_status,
|
||||
true,
|
||||
Some((sub_info.clone(), is_last_sub)),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
display_lines.get(self.selected_index).map(|(name, _, _, _)| name.clone())
|
||||
}
|
||||
|
||||
/// Get total count of services (parent + sub-services)
|
||||
pub fn get_total_services_count(&self) -> usize {
|
||||
let mut count = 0;
|
||||
|
||||
// Count parent services
|
||||
count += self.parent_services.len();
|
||||
|
||||
// Count sub-services
|
||||
for sub_list in self.sub_services.values() {
|
||||
count += sub_list.len();
|
||||
}
|
||||
|
||||
count
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for ServicesWidget {
|
||||
@@ -408,6 +470,9 @@ impl ServicesWidget {
|
||||
|
||||
for (i, (line_text, line_status, is_sub, sub_info)) in visible_lines.iter().enumerate()
|
||||
{
|
||||
let actual_index = effective_scroll + i; // Real index in the full list
|
||||
let is_selected = actual_index == self.selected_index;
|
||||
|
||||
let spans = if *is_sub && sub_info.is_some() {
|
||||
// Use custom sub-service span creation
|
||||
let (service_info, is_last) = sub_info.as_ref().unwrap();
|
||||
@@ -416,7 +481,18 @@ impl ServicesWidget {
|
||||
// Use regular status spans for parent services
|
||||
StatusIcons::create_status_spans(*line_status, line_text)
|
||||
};
|
||||
let service_para = Paragraph::new(ratatui::text::Line::from(spans));
|
||||
|
||||
let mut service_para = Paragraph::new(ratatui::text::Line::from(spans));
|
||||
|
||||
// Apply selection highlighting
|
||||
if is_selected {
|
||||
service_para = service_para.style(
|
||||
Style::default()
|
||||
.bg(Theme::highlight())
|
||||
.fg(Theme::background())
|
||||
);
|
||||
}
|
||||
|
||||
frame.render_widget(service_para, service_chunks[i]);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user