Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: I20f205d9e06a93a89e8f4433ed6f80576a6a6964
311 lines
8.7 KiB
Rust
311 lines
8.7 KiB
Rust
use ratatui::{
|
|
Frame,
|
|
layout::{Constraint, Direction, Layout, Rect},
|
|
style::{Color, Modifier, Style},
|
|
text::{Line, Span},
|
|
widgets::{Block, Borders, Paragraph},
|
|
};
|
|
|
|
use super::{format_date, format_duration, format_size, media_type_color};
|
|
use crate::app::AppState;
|
|
|
|
pub fn render(f: &mut Frame, state: &AppState, area: Rect) {
|
|
let Some(item) = &state.selected_media else {
|
|
let msg = Paragraph::new("No item selected")
|
|
.block(Block::default().borders(Borders::ALL).title(" Detail "));
|
|
f.render_widget(msg, area);
|
|
return;
|
|
};
|
|
|
|
let chunks = Layout::default()
|
|
.direction(Direction::Vertical)
|
|
.constraints([Constraint::Min(0)])
|
|
.split(area);
|
|
|
|
let label_style = Style::default()
|
|
.fg(Color::Yellow)
|
|
.add_modifier(Modifier::BOLD);
|
|
let value_style = Style::default().fg(Color::White);
|
|
let dim_style = Style::default().fg(Color::DarkGray);
|
|
|
|
let pad = " ";
|
|
let label_width = 14;
|
|
let make_label = |name: &str| -> String { format!("{name:<label_width$}") };
|
|
|
|
let mut lines: Vec<Line> = Vec::new();
|
|
|
|
// Section: File Info
|
|
lines.push(Line::from(Span::styled(
|
|
"--- File Info ---",
|
|
Style::default()
|
|
.fg(Color::Cyan)
|
|
.add_modifier(Modifier::BOLD),
|
|
)));
|
|
|
|
lines.push(Line::from(vec![
|
|
Span::raw(pad),
|
|
Span::styled(make_label("Name"), label_style),
|
|
Span::styled(&item.file_name, value_style),
|
|
]));
|
|
|
|
lines.push(Line::from(vec![
|
|
Span::raw(pad),
|
|
Span::styled(make_label("Path"), label_style),
|
|
Span::styled(&item.path, dim_style),
|
|
]));
|
|
|
|
let type_color = media_type_color(&item.media_type);
|
|
lines.push(Line::from(vec![
|
|
Span::raw(pad),
|
|
Span::styled(make_label("Type"), label_style),
|
|
Span::styled(&item.media_type, Style::default().fg(type_color)),
|
|
]));
|
|
|
|
lines.push(Line::from(vec![
|
|
Span::raw(pad),
|
|
Span::styled(make_label("Size"), label_style),
|
|
Span::styled(format_size(item.file_size), value_style),
|
|
]));
|
|
|
|
lines.push(Line::from(vec![
|
|
Span::raw(pad),
|
|
Span::styled(make_label("Hash"), label_style),
|
|
Span::styled(&item.content_hash, dim_style),
|
|
]));
|
|
|
|
if item.has_thumbnail {
|
|
lines.push(Line::from(vec![
|
|
Span::raw(pad),
|
|
Span::styled(make_label("Thumbnail"), label_style),
|
|
Span::styled("Yes", Style::default().fg(Color::Green)),
|
|
]));
|
|
}
|
|
|
|
lines.push(Line::default()); // blank line
|
|
|
|
// Section: Metadata
|
|
lines.push(Line::from(Span::styled(
|
|
"--- Metadata ---",
|
|
Style::default()
|
|
.fg(Color::Cyan)
|
|
.add_modifier(Modifier::BOLD),
|
|
)));
|
|
|
|
lines.push(Line::from(vec![
|
|
Span::raw(pad),
|
|
Span::styled(make_label("Title"), label_style),
|
|
Span::styled(item.title.as_deref().unwrap_or("-"), value_style),
|
|
]));
|
|
|
|
lines.push(Line::from(vec![
|
|
Span::raw(pad),
|
|
Span::styled(make_label("Artist"), label_style),
|
|
Span::styled(item.artist.as_deref().unwrap_or("-"), value_style),
|
|
]));
|
|
|
|
lines.push(Line::from(vec![
|
|
Span::raw(pad),
|
|
Span::styled(make_label("Album"), label_style),
|
|
Span::styled(item.album.as_deref().unwrap_or("-"), value_style),
|
|
]));
|
|
|
|
lines.push(Line::from(vec![
|
|
Span::raw(pad),
|
|
Span::styled(make_label("Genre"), label_style),
|
|
Span::styled(item.genre.as_deref().unwrap_or("-"), value_style),
|
|
]));
|
|
|
|
lines.push(Line::from(vec![
|
|
Span::raw(pad),
|
|
Span::styled(make_label("Year"), label_style),
|
|
Span::styled(
|
|
item.year.map_or_else(|| "-".to_string(), |y| y.to_string()),
|
|
value_style,
|
|
),
|
|
]));
|
|
|
|
lines.push(Line::from(vec![
|
|
Span::raw(pad),
|
|
Span::styled(make_label("Duration"), label_style),
|
|
Span::styled(
|
|
item
|
|
.duration_secs
|
|
.map_or_else(|| "-".to_string(), format_duration),
|
|
value_style,
|
|
),
|
|
]));
|
|
|
|
// Description
|
|
if let Some(ref desc) = item.description
|
|
&& !desc.is_empty()
|
|
{
|
|
lines.push(Line::default());
|
|
lines.push(Line::from(vec![
|
|
Span::raw(pad),
|
|
Span::styled(make_label("Description"), label_style),
|
|
Span::styled(desc.as_str(), value_style),
|
|
]));
|
|
}
|
|
|
|
// Custom fields
|
|
if !item.custom_fields.is_empty() {
|
|
lines.push(Line::default());
|
|
lines.push(Line::from(Span::styled(
|
|
"--- Custom Fields ---",
|
|
Style::default()
|
|
.fg(Color::Cyan)
|
|
.add_modifier(Modifier::BOLD),
|
|
)));
|
|
let mut fields: Vec<_> = item.custom_fields.iter().collect();
|
|
fields.sort_by_key(|(k, _)| k.as_str());
|
|
for (key, field) in fields {
|
|
lines.push(Line::from(vec![
|
|
Span::raw(pad),
|
|
Span::styled(format!("{key:<label_width$}"), label_style),
|
|
Span::styled(
|
|
format!("{} ({})", field.value, field.field_type),
|
|
value_style,
|
|
),
|
|
]));
|
|
}
|
|
}
|
|
|
|
// Tags section
|
|
if !state.tags.is_empty() {
|
|
lines.push(Line::default());
|
|
lines.push(Line::from(Span::styled(
|
|
"--- Tags ---",
|
|
Style::default()
|
|
.fg(Color::Cyan)
|
|
.add_modifier(Modifier::BOLD),
|
|
)));
|
|
let tag_names: Vec<&str> =
|
|
state.tags.iter().map(|t| t.name.as_str()).collect();
|
|
lines.push(Line::from(vec![
|
|
Span::raw(pad),
|
|
Span::styled(tag_names.join(", "), Style::default().fg(Color::Green)),
|
|
]));
|
|
}
|
|
|
|
// Book metadata section
|
|
if let Some(ref book) = state.book_metadata {
|
|
lines.push(Line::default());
|
|
lines.push(Line::from(Span::styled(
|
|
"--- Book Metadata ---",
|
|
Style::default()
|
|
.fg(Color::Cyan)
|
|
.add_modifier(Modifier::BOLD),
|
|
)));
|
|
if let Some(ref subtitle) = book.subtitle {
|
|
lines.push(Line::from(vec![
|
|
Span::raw(pad),
|
|
Span::styled(make_label("Subtitle"), label_style),
|
|
Span::styled(subtitle.as_str(), value_style),
|
|
]));
|
|
}
|
|
if !book.authors.is_empty() {
|
|
let authors: Vec<&str> =
|
|
book.authors.iter().map(|a| a.name.as_str()).collect();
|
|
lines.push(Line::from(vec![
|
|
Span::raw(pad),
|
|
Span::styled(make_label("Authors"), label_style),
|
|
Span::styled(authors.join(", "), value_style),
|
|
]));
|
|
}
|
|
if let Some(ref publisher) = book.publisher {
|
|
lines.push(Line::from(vec![
|
|
Span::raw(pad),
|
|
Span::styled(make_label("Publisher"), label_style),
|
|
Span::styled(publisher.as_str(), value_style),
|
|
]));
|
|
}
|
|
if let Some(isbn) = book.isbn13.as_ref().or(book.isbn.as_ref()) {
|
|
lines.push(Line::from(vec![
|
|
Span::raw(pad),
|
|
Span::styled(make_label("ISBN"), label_style),
|
|
Span::styled(isbn.as_str(), value_style),
|
|
]));
|
|
}
|
|
if let Some(ref language) = book.language {
|
|
lines.push(Line::from(vec![
|
|
Span::raw(pad),
|
|
Span::styled(make_label("Language"), label_style),
|
|
Span::styled(language.as_str(), value_style),
|
|
]));
|
|
}
|
|
if let Some(pages) = book.page_count {
|
|
lines.push(Line::from(vec![
|
|
Span::raw(pad),
|
|
Span::styled(make_label("Pages"), label_style),
|
|
Span::styled(pages.to_string(), value_style),
|
|
]));
|
|
}
|
|
if let Some(ref series) = book.series {
|
|
let series_display = book
|
|
.series_index
|
|
.map_or_else(|| series.clone(), |idx| format!("{series} #{idx}"));
|
|
lines.push(Line::from(vec![
|
|
Span::raw(pad),
|
|
Span::styled(make_label("Series"), label_style),
|
|
Span::styled(series_display, value_style),
|
|
]));
|
|
}
|
|
}
|
|
|
|
// Reading progress section
|
|
if let Some(ref progress) = state.reading_progress {
|
|
lines.push(Line::default());
|
|
lines.push(Line::from(Span::styled(
|
|
"--- Reading Progress ---",
|
|
Style::default()
|
|
.fg(Color::Cyan)
|
|
.add_modifier(Modifier::BOLD),
|
|
)));
|
|
let page_display = progress.total_pages.map_or_else(
|
|
|| format!("Page {}", progress.current_page),
|
|
|total| format!("Page {} / {total}", progress.current_page),
|
|
);
|
|
lines.push(Line::from(vec![
|
|
Span::raw(pad),
|
|
Span::styled(make_label("Progress"), label_style),
|
|
Span::styled(page_display, value_style),
|
|
]));
|
|
lines.push(Line::from(vec![
|
|
Span::raw(pad),
|
|
Span::styled(make_label("Status"), label_style),
|
|
Span::styled(&progress.status, value_style),
|
|
]));
|
|
}
|
|
|
|
lines.push(Line::default());
|
|
|
|
// Section: Timestamps
|
|
lines.push(Line::from(Span::styled(
|
|
"--- Timestamps ---",
|
|
Style::default()
|
|
.fg(Color::Cyan)
|
|
.add_modifier(Modifier::BOLD),
|
|
)));
|
|
|
|
lines.push(Line::from(vec![
|
|
Span::raw(pad),
|
|
Span::styled(make_label("Created"), label_style),
|
|
Span::styled(format_date(&item.created_at), dim_style),
|
|
]));
|
|
|
|
lines.push(Line::from(vec![
|
|
Span::raw(pad),
|
|
Span::styled(make_label("Updated"), label_style),
|
|
Span::styled(format_date(&item.updated_at), dim_style),
|
|
]));
|
|
|
|
let title = item.title.as_ref().map_or_else(
|
|
|| format!(" Detail: {} ", item.file_name),
|
|
|title_str| format!(" Detail: {title_str} "),
|
|
);
|
|
|
|
let detail = Paragraph::new(lines)
|
|
.block(Block::default().borders(Borders::ALL).title(title));
|
|
|
|
f.render_widget(detail, chunks[0]);
|
|
}
|