diff --git a/frontend/src/components/PlexPlaylistView.tsx b/frontend/src/components/PlexPlaylistView.tsx index f77e9606..0bb24816 100644 --- a/frontend/src/components/PlexPlaylistView.tsx +++ b/frontend/src/components/PlexPlaylistView.tsx @@ -32,6 +32,7 @@ import { X, } from "lucide-react"; import { api, type PlexCard } from "../lib/api"; +import { formatRuntime } from "../lib/format"; import { getAccountRaw, LS, setAccountRaw } from "../lib/storage"; import { useConfirm } from "./ConfirmProvider"; import { LoadingState, StateMessage } from "./QueryState"; @@ -387,6 +388,12 @@ export default function PlexPlaylistView({ }, [q.data]); const blocks = useMemo(() => buildBlocks(items), [items]); const order = useMemo(() => items.map((i) => i.id), [items]); + // Total playtime of the whole playlist, mirroring the YouTube playlist header (E9 12D): items with + // an unknown duration count as 0, and the runtime is dropped from the meta line when nothing sums. + const totalSeconds = useMemo( + () => items.reduce((acc, it) => acc + (it.duration_seconds ?? 0), 0), + [items], + ); const invalidate = () => { qc.invalidateQueries({ queryKey: qk.plexPlaylist(playlistId) }); @@ -517,18 +524,26 @@ export default function PlexPlaylistView({
- {renaming ? ( - setName(e.target.value)} - onKeyDown={(e) => e.key === "Enter" && rename()} - onBlur={rename} - className="glass-card min-w-0 flex-1 rounded-lg px-3 py-1.5 text-lg font-bold focus:outline-hidden focus:ring-2 focus:ring-accent/40" - /> - ) : ( -

{q.data?.title ?? " "}

- )} +
+ {renaming ? ( + setName(e.target.value)} + onKeyDown={(e) => e.key === "Enter" && rename()} + onBlur={rename} + className="glass-card w-full rounded-lg px-3 py-1.5 text-lg font-bold focus:outline-hidden focus:ring-2 focus:ring-accent/40" + /> + ) : ( +

{q.data?.title ?? " "}

+ )} + {items.length > 0 && ( +
+ {t("plex.browse.items", { count: items.length })} + {totalSeconds > 0 && ` · ${formatRuntime(totalSeconds)}`} +
+ )} +
{/* Layout preference: Accordion or Tree — the shared list-view switcher. */}