feat(tui): hierarchical browse tree with file/folder downloads
Wire browsing a user's shared files into the TUI as a modal tree popup: - press 'b' on a highlighted search result to browse that result's owner, or 'b' elsewhere to type a username (new CommandBarMode::Browse); - a new models/browse.rs synthesizes the flat SharedDirectory list into a nested, collapsible tree (intermediate folders reconstructed from the backslash paths; file download paths kept byte-exact from the original names), with expand/collapse state and a flatten-to-rows renderer — fully unit-tested; - the popup polls take_browse_result each tick (with a timeout), navigates with arrows/hjkl, expands/collapses folders, and queues downloads: Enter on a file, 'd' on a file or an entire folder subtree (recursively) — all funneled into the existing Downloads pane; - shortcuts bar advertises browse keys and surfaces our own sharing status.
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
use crate::models::{BrowseState, BrowseStatus};
|
||||
use crate::ui::{
|
||||
HIGHLIGHT_SYMBOL, border_style, border_type, error_style, format_bytes,
|
||||
get_spinner_char, highlight_style, primary_style,
|
||||
};
|
||||
use ratatui::{
|
||||
Frame,
|
||||
layout::{Constraint, Rect},
|
||||
text::Line,
|
||||
widgets::{
|
||||
Block, Borders, Cell, HighlightSpacing, Paragraph, Row, Table,
|
||||
TableState,
|
||||
},
|
||||
};
|
||||
|
||||
/// Render the browse popup: a collapsible tree of a peer's shared files.
|
||||
pub fn render_browse_pane(
|
||||
frame: &mut Frame,
|
||||
area: Rect,
|
||||
browse: &BrowseState,
|
||||
table_state: &mut TableState,
|
||||
spinner_state: usize,
|
||||
) {
|
||||
let title = match browse.status {
|
||||
BrowseStatus::Loaded => format!(
|
||||
" Browse {} — {} files, {} folders (Enter/d: download, Esc: close) ",
|
||||
browse.username, browse.file_count, browse.folder_count
|
||||
),
|
||||
_ => format!(" Browse {} ", browse.username),
|
||||
};
|
||||
let block = Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.border_style(border_style(true))
|
||||
.border_type(border_type(true))
|
||||
.title(title);
|
||||
|
||||
match browse.status {
|
||||
BrowseStatus::Loading => {
|
||||
let text = format!(
|
||||
"{} Requesting shared files from {}…",
|
||||
get_spinner_char(spinner_state),
|
||||
browse.username
|
||||
);
|
||||
frame.render_widget(Paragraph::new(text).block(block), area);
|
||||
}
|
||||
BrowseStatus::Empty => {
|
||||
let text = format!("{} is not sharing any files.", browse.username);
|
||||
frame.render_widget(Paragraph::new(text).block(block), area);
|
||||
}
|
||||
BrowseStatus::TimedOut => {
|
||||
let text = Line::styled(
|
||||
"Timed out waiting for the file list. Press b to retry.",
|
||||
error_style(),
|
||||
);
|
||||
frame.render_widget(Paragraph::new(text).block(block), area);
|
||||
}
|
||||
BrowseStatus::Loaded => {
|
||||
let rows: Vec<Row> = browse
|
||||
.rows()
|
||||
.iter()
|
||||
.map(|row| {
|
||||
let indent = " ".repeat(row.depth);
|
||||
let (label, size) = if row.is_folder {
|
||||
let glyph = if row.expanded { "▾" } else { "▸" };
|
||||
(
|
||||
Cell::from(format!("{indent}{glyph} {}", row.name))
|
||||
.style(primary_style()),
|
||||
Cell::from(String::new()),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
Cell::from(format!("{indent} {}", row.name)),
|
||||
Cell::from(
|
||||
row.size.map(format_bytes).unwrap_or_default(),
|
||||
),
|
||||
)
|
||||
};
|
||||
Row::new(vec![label, size])
|
||||
})
|
||||
.collect();
|
||||
|
||||
let table =
|
||||
Table::new(rows, [Constraint::Fill(1), Constraint::Length(12)])
|
||||
.row_highlight_style(highlight_style())
|
||||
.highlight_symbol(HIGHLIGHT_SYMBOL)
|
||||
.highlight_spacing(HighlightSpacing::Always)
|
||||
.block(block);
|
||||
|
||||
table_state.select(Some(browse.selected_row));
|
||||
frame.render_stateful_widget(table, area, table_state);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
mod browse_pane;
|
||||
mod download_info_pane;
|
||||
mod downloads_pane;
|
||||
mod results_pane;
|
||||
mod searches_pane;
|
||||
|
||||
pub use browse_pane::render_browse_pane;
|
||||
pub use download_info_pane::render_download_info_pane;
|
||||
pub use downloads_pane::render_downloads_pane;
|
||||
pub use results_pane::{ResultsPaneParams, render_results_pane};
|
||||
|
||||
Reference in New Issue
Block a user