info almost done

This commit is contained in:
2026-02-14 22:37:00 -06:00
parent 2893387372
commit 437053395b
2 changed files with 148 additions and 9 deletions

View File

@@ -24,7 +24,7 @@ use crossterm::{
use ratatui::{
layout::{Constraint, Layout, Rect, Spacing},
symbols::merge::MergeStrategy,
widgets::{Block, List, ListItem, ListState, Paragraph},
widgets::{Block, List, ListItem, ListState, Paragraph, Wrap},
style::{Modifier, Style},
Frame, Terminal,
};
@@ -976,6 +976,119 @@ fn ensure_loaded_to_end(app: &mut App) {
}
}
fn ticks_to_runtime_str(ticks: Option<i64>) -> Option<String> {
// Jellyfin ticks are 10,000,000 per second
let t = ticks?;
if t <= 0 { return None; }
let total_seconds = (t as u64 / 10_000_000) as u64;
let minutes = total_seconds / 60;
let seconds = total_seconds % 60;
if minutes >= 60 {
let h = minutes / 60;
let m = minutes % 60;
Some(format!("{h}h {m}m"))
} else {
Some(format!("{minutes}m {seconds:02}s"))
}
}
fn join_genres(genres: &[String]) -> String {
if genres.is_empty() { "-".into() } else { genres.join(", ") }
}
fn join_studios(studios: &[core::jellyfin::JfStudio]) -> String {
let names: Vec<String> = studios
.iter()
.map(|s| s.name.clone())
.filter(|s| !s.trim().is_empty())
.collect();
if names.is_empty() { "-".into() } else { names.join(", ") }
}
fn year_str(y: Option<i32>) -> String {
y.map(|v| v.to_string()).unwrap_or_else(|| "-".into())
}
fn overview_str(o: &Option<String>) -> String {
o.as_deref()
.map(|s| s.trim())
.filter(|s| !s.is_empty())
.unwrap_or("-")
.to_string()
}
fn format_details(it: &core::jellyfin::JfItem) -> String {
let ty = it.item_type.as_deref().unwrap_or("");
match ty {
"Series" => format!(
"Name: {}\nYear: {}\nGenres: {}\nStudio: {}\n\nOverview:\n{}",
it.name,
year_str(it.production_year),
join_genres(&it.genres),
join_studios(&it.studios),
overview_str(&it.overview),
),
"Movie" => format!(
"Name: {}\nYear: {}\nRuntime: {}\nGenres: {}\nStudio: {}\n\nOverview:\n{}",
it.name,
year_str(it.production_year),
ticks_to_runtime_str(it.run_time_ticks).unwrap_or_else(|| "-".into()),
join_genres(&it.genres),
join_studios(&it.studios),
overview_str(&it.overview),
),
// Episodes: show SeriesName if present
"Episode" => {
let series = it.series_name.clone().unwrap_or_else(|| "-".into());
let se = match (it.parent_index_number, it.index_number) {
(Some(s), Some(e)) => format!("{:02}x{:02}", s, e),
_ => "-".into(),
};
format!(
"Series: {}\nEpisode: {} {}\nYear: {}\n\nOverview:\n{}",
series,
se,
it.name,
year_str(it.production_year),
overview_str(&it.overview),
)
}
// Music: you asked only Name + Runtime
"Audio" => format!(
"Name: {}\nRuntime: {}",
it.name,
ticks_to_runtime_str(it.run_time_ticks).unwrap_or_else(|| "-".into()),
),
// fallback
_ => format!(
"Name: {}\nId: {}\nType: {:?}\nYear: {:?}",
it.name, it.id, it.item_type, it.production_year
),
}
}
fn ui(frame: &mut Frame, app: &App) {
let area = frame.area();
let panes = layout(area);
@@ -1214,16 +1327,14 @@ fn render_library(
let detail = if len > 0 {
let it = &items[selected];
format!(
"Name: {}\nId: {}\nType: {:?}\nYear: {:?}",
it.name, it.id, it.item_type, it.production_year
)
format_details(it)
} else {
"No items returned".to_string()
};
frame.render_widget(
Paragraph::new(detail)
.wrap(Wrap { trim: false })
.block(Block::bordered().title("Details").merge_borders(MergeStrategy::Exact)),
right,
);