feat(tui): tabbed browsing — browse several users at once
Replace the single browse view with BrowseTabs (one tab per user), mirroring the chat-room tabs. Browsing a new user (from search results or a chat room's member list) opens or focuses their tab; Tab/Shift-Tab switch between open users, 'w' closes the active tab, Esc hides the popup while keeping the tabs. A tab bar shows the open users when more than one is open, and each tab polls/loads independently. Re-browsing a timed-out user retries. BrowseTabs is unit-tested.
This commit is contained in:
@@ -1,20 +1,62 @@
|
||||
use crate::models::{BrowseState, BrowseStatus};
|
||||
use crate::models::{BrowseState, BrowseStatus, BrowseTabs};
|
||||
use crate::ui::{
|
||||
HIGHLIGHT_SYMBOL, border_style, border_type, error_style, format_bytes,
|
||||
get_spinner_char, highlight_style, primary_style,
|
||||
HIGHLIGHT_SYMBOL, border_style, border_type, dimmed_style, error_style,
|
||||
format_bytes, get_spinner_char, highlight_style, primary_style,
|
||||
};
|
||||
use ratatui::{
|
||||
Frame,
|
||||
layout::{Constraint, Rect},
|
||||
text::Line,
|
||||
layout::{Constraint, Layout, Rect},
|
||||
style::Modifier,
|
||||
text::{Line, Span},
|
||||
widgets::{
|
||||
Block, Borders, Cell, HighlightSpacing, Paragraph, Row, Table,
|
||||
TableState,
|
||||
},
|
||||
};
|
||||
|
||||
/// Render the browse popup: a collapsible tree of a peer's shared files.
|
||||
/// Render the browse popup: a tab bar of browsed users (when more than one is
|
||||
/// open) above the active user's collapsible shared-file tree.
|
||||
pub fn render_browse_pane(
|
||||
frame: &mut Frame,
|
||||
area: Rect,
|
||||
tabs: &BrowseTabs,
|
||||
table_state: &mut TableState,
|
||||
spinner_state: usize,
|
||||
) {
|
||||
let Some(active) = tabs.active_tab() else {
|
||||
return;
|
||||
};
|
||||
|
||||
// With multiple users open, reserve the top row for a tab bar.
|
||||
let tree_area = if tabs.tabs.len() > 1 {
|
||||
let chunks =
|
||||
Layout::vertical([Constraint::Length(1), Constraint::Fill(1)])
|
||||
.split(area);
|
||||
render_browse_tabs(frame, chunks[0], tabs);
|
||||
chunks[1]
|
||||
} else {
|
||||
area
|
||||
};
|
||||
|
||||
render_browse_one(frame, tree_area, active, table_state, spinner_state);
|
||||
}
|
||||
|
||||
fn render_browse_tabs(frame: &mut Frame, area: Rect, tabs: &BrowseTabs) {
|
||||
let mut spans: Vec<Span> = Vec::new();
|
||||
for (i, tab) in tabs.tabs.iter().enumerate() {
|
||||
let style = if i == tabs.active {
|
||||
highlight_style().add_modifier(Modifier::BOLD)
|
||||
} else {
|
||||
dimmed_style()
|
||||
};
|
||||
spans.push(Span::styled(format!(" {} ", tab.username), style));
|
||||
spans.push(Span::raw(" "));
|
||||
}
|
||||
frame.render_widget(Paragraph::new(Line::from(spans)), area);
|
||||
}
|
||||
|
||||
/// Render one browsed user's shared-file tree.
|
||||
fn render_browse_one(
|
||||
frame: &mut Frame,
|
||||
area: Rect,
|
||||
browse: &BrowseState,
|
||||
@@ -23,7 +65,7 @@ pub fn render_browse_pane(
|
||||
) {
|
||||
let title = match browse.status {
|
||||
BrowseStatus::Loaded => format!(
|
||||
" Browse {} — {} files, {} folders (Enter/d: download, Esc: close) ",
|
||||
" Browse {} — {} files, {} folders (Enter/d: download, Tab: user, w: close, Esc: hide) ",
|
||||
browse.username, browse.file_count, browse.folder_count
|
||||
),
|
||||
_ => format!(" Browse {} ", browse.username),
|
||||
|
||||
Reference in New Issue
Block a user