Added filter. Improved UI. Fixed settings window
This commit is contained in:
@@ -193,6 +193,16 @@ fn status_line(label: &str, value: String) -> Line<'static> {
|
||||
])
|
||||
}
|
||||
|
||||
fn bytes_label(bytes: u64) -> String {
|
||||
if bytes >= 1024 * 1024 {
|
||||
format!("{bytes} B ({:.1} MiB)", bytes as f64 / 1024.0 / 1024.0)
|
||||
} else if bytes >= 1024 {
|
||||
format!("{bytes} B ({:.1} KiB)", bytes as f64 / 1024.0)
|
||||
} else {
|
||||
format!("{bytes} B")
|
||||
}
|
||||
}
|
||||
|
||||
fn short_id(id: &str) -> String {
|
||||
id.chars().take(12).collect::<String>() + "…"
|
||||
}
|
||||
@@ -240,6 +250,13 @@ fn draw_status(frame: &mut Frame, area: Rect, state: &AppState) {
|
||||
.map(|count| count.to_string())
|
||||
.unwrap_or_else(|| "unavailable".to_string()),
|
||||
));
|
||||
lines.push(status_line(
|
||||
"Stored DHT bytes",
|
||||
status
|
||||
.stored_dht_bytes
|
||||
.map(bytes_label)
|
||||
.unwrap_or_else(|| "unavailable".to_string()),
|
||||
));
|
||||
lines.push(status_line(
|
||||
"Published items",
|
||||
status.published_items.to_string(),
|
||||
|
||||
+15
-9
@@ -36,10 +36,11 @@ fn error_style() -> Style {
|
||||
}
|
||||
|
||||
fn bordered(frame: &mut Frame, area: Rect, title: String) -> Rect {
|
||||
let block = Block::bordered()
|
||||
.title(title)
|
||||
.title_style(theme::header())
|
||||
.border_style(theme::dim());
|
||||
bordered_line(frame, area, Line::styled(title, theme::header()))
|
||||
}
|
||||
|
||||
fn bordered_line(frame: &mut Frame, area: Rect, title: Line<'static>) -> Rect {
|
||||
let block = Block::bordered().title(title).border_style(theme::dim());
|
||||
let inner = block.inner(area);
|
||||
frame.render_widget(block, area);
|
||||
inner
|
||||
@@ -271,11 +272,16 @@ fn scroll_offset(items: &[PlanItem], cursor_item: Option<usize>, viewport: u16)
|
||||
fn draw_grid(frame: &mut Frame, area: Rect, state: &AppState) {
|
||||
let global = &state.global;
|
||||
let title = if global.total > 0 {
|
||||
format!(" Global — {} artists ", global.total)
|
||||
format!(" Library — {} artists ", global.total)
|
||||
} else {
|
||||
" Global ".to_string()
|
||||
" Library ".to_string()
|
||||
};
|
||||
let inner = bordered(frame, area, title);
|
||||
let mut title_spans = vec![Span::styled(title, theme::tab_active())];
|
||||
if global.filters.is_active() {
|
||||
title_spans.push(Span::raw(" "));
|
||||
title_spans.push(Span::styled(" FILTERED ", theme::tab_active()));
|
||||
}
|
||||
let inner = bordered_line(frame, area, Line::from(title_spans));
|
||||
|
||||
if global.artists.is_empty() {
|
||||
let message = if let Some(error) = &global.error {
|
||||
@@ -373,7 +379,7 @@ fn draw_artist(frame: &mut Frame, area: Rect, state: &AppState, id: i64, cursor:
|
||||
Some(Loadable::Ready(detail)) => detail.name.clone(),
|
||||
_ => "Artist".to_string(),
|
||||
};
|
||||
let inner = bordered(frame, area, format!(" Global ▸ {name} "));
|
||||
let inner = bordered(frame, area, format!(" Library ▸ {name} "));
|
||||
|
||||
let detail = match loadable {
|
||||
Some(Loadable::Ready(detail)) => detail,
|
||||
@@ -640,7 +646,7 @@ fn draw_release(frame: &mut Frame, area: Rect, state: &AppState, id: i64, cursor
|
||||
Some(Loadable::Ready(detail)) => detail.title.clone(),
|
||||
_ => "Release".to_string(),
|
||||
};
|
||||
let inner = bordered(frame, area, format!(" Global ▸ {title} "));
|
||||
let inner = bordered(frame, area, format!(" Library ▸ {title} "));
|
||||
|
||||
let detail = match loadable {
|
||||
Some(Loadable::Ready(detail)) => detail,
|
||||
|
||||
@@ -21,6 +21,7 @@ pub fn draw(frame: &mut Frame, state: &AppState) {
|
||||
..
|
||||
}) => draw_edit(frame, title, fields, *focus, error.as_deref()),
|
||||
Some(Popup::ConfirmDelete { label, .. }) => draw_confirm_delete(frame, label),
|
||||
Some(Popup::LibraryFilters { cursor }) => draw_library_filters(frame, state, *cursor),
|
||||
Some(Popup::TrackInfo {
|
||||
tracks,
|
||||
cursor,
|
||||
@@ -39,6 +40,49 @@ pub fn draw(frame: &mut Frame, state: &AppState) {
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_library_filters(frame: &mut Frame, state: &AppState, cursor: usize) {
|
||||
let area = centered(frame.area(), 46, 6);
|
||||
let block = Block::bordered()
|
||||
.title(" Library filters ")
|
||||
.title_style(theme::header())
|
||||
.border_style(theme::accent());
|
||||
let inner = block.inner(area);
|
||||
frame.render_widget(Clear, area);
|
||||
frame.render_widget(block, area);
|
||||
|
||||
let [list_area, _, footer] = Layout::vertical([
|
||||
Constraint::Length(1),
|
||||
Constraint::Length(1),
|
||||
Constraint::Length(1),
|
||||
])
|
||||
.areas(inner);
|
||||
|
||||
let checked = if state.global.filters.hide_featured_only {
|
||||
"[x]"
|
||||
} else {
|
||||
"[ ]"
|
||||
};
|
||||
let row = Rect {
|
||||
height: 1,
|
||||
..list_area
|
||||
};
|
||||
frame.render_widget(
|
||||
Paragraph::new(Line::from(vec![
|
||||
Span::styled(format!("{checked} "), theme::accent()),
|
||||
Span::raw("Hide featured only"),
|
||||
])),
|
||||
row,
|
||||
);
|
||||
if cursor == 0 {
|
||||
frame.buffer_mut().set_style(row, theme::tab_active());
|
||||
}
|
||||
frame.render_widget(
|
||||
Paragraph::new(Line::styled("space/enter toggle · esc close", theme::dim()))
|
||||
.alignment(Alignment::Center),
|
||||
footer,
|
||||
);
|
||||
}
|
||||
|
||||
/// One-line text entry on the Federation tab (network id / peer ticket).
|
||||
fn draw_fed_input(frame: &mut Frame, title: &str, input: &crate::app::input::LineEdit) {
|
||||
let area = centered(frame.area(), 64, 5);
|
||||
|
||||
Reference in New Issue
Block a user