feat(card): show LIVE/upcoming badge where duration is absent

Live streams and upcoming/premiere videos have no fixed duration, so the
playlist row and video card left a blank where the runtime usually sits.
Show a LIVE (red) or upcoming badge instead, so it's clear the missing
time is by design, not a sync gap.
This commit is contained in:
npeter83 2026-06-16 09:36:51 +02:00
parent c35d4f9184
commit ed941904a6
5 changed files with 29 additions and 4 deletions

View file

@ -110,6 +110,7 @@ function Row({
onPlay: () => void; onPlay: () => void;
onRemove: () => void; onRemove: () => void;
}) { }) {
const { t } = useTranslation();
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = const { attributes, listeners, setNodeRef, transform, transition, isDragging } =
useSortable({ id: video.id, disabled: readOnly }); useSortable({ id: video.id, disabled: readOnly });
const style = { const style = {
@ -151,11 +152,21 @@ function Row({
<div className="text-[13px] text-fg truncate">{video.title}</div> <div className="text-[13px] text-fg truncate">{video.title}</div>
<div className="text-[11px] text-muted truncate">{video.channel_title}</div> <div className="text-[11px] text-muted truncate">{video.channel_title}</div>
</button> </button>
{video.duration_seconds != null && ( {video.duration_seconds != null ? (
<span className="text-[11px] text-muted tabular-nums"> <span className="text-[11px] text-muted tabular-nums">
{formatDuration(video.duration_seconds)} {formatDuration(video.duration_seconds)}
</span> </span>
)} ) : video.live_status === "live" || video.live_status === "upcoming" ? (
<span
className={`text-[10px] font-semibold uppercase tracking-wide px-1.5 py-0.5 rounded ${
video.live_status === "live"
? "bg-red-500/90 text-white"
: "bg-card border border-border text-muted"
}`}
>
{t(`card.${video.live_status}`)}
</span>
) : null}
{!readOnly && ( {!readOnly && (
<button <button
onClick={onRemove} onClick={onRemove}

View file

@ -154,11 +154,19 @@ function Thumb({
) : ( ) : (
<div className="w-full h-full" /> <div className="w-full h-full" />
)} )}
{video.duration_seconds != null && ( {video.duration_seconds != null ? (
<span className="absolute bottom-1.5 right-1.5 bg-black/80 text-white text-[11px] font-medium px-1.5 py-0.5 rounded"> <span className="absolute bottom-1.5 right-1.5 bg-black/80 text-white text-[11px] font-medium px-1.5 py-0.5 rounded">
{formatDuration(video.duration_seconds)} {formatDuration(video.duration_seconds)}
</span> </span>
)} ) : video.live_status === "live" || video.live_status === "upcoming" ? (
<span
className={`absolute bottom-1.5 right-1.5 text-[10px] font-semibold uppercase tracking-wide px-1.5 py-0.5 rounded ${
video.live_status === "live" ? "bg-red-500/90 text-white" : "bg-black/80 text-white"
}`}
>
{t(`card.${video.live_status}`)}
</span>
) : null}
{video.live_status === "was_live" && ( {video.live_status === "was_live" && (
<span className="absolute top-1.5 left-1.5 bg-accent text-accent-fg text-[10px] font-semibold uppercase tracking-wide px-1.5 py-0.5 rounded"> <span className="absolute top-1.5 left-1.5 bg-accent text-accent-fg text-[10px] font-semibold uppercase tracking-wide px-1.5 py-0.5 rounded">
{t("card.stream")} {t("card.stream")}

View file

@ -1,4 +1,6 @@
{ {
"live": "live",
"upcoming": "demnächst",
"watchedUnmark": "Angesehen — zum Aufheben klicken", "watchedUnmark": "Angesehen — zum Aufheben klicken",
"markWatched": "Als angesehen markieren", "markWatched": "Als angesehen markieren",
"savedRemove": "Gespeichert — zum Entfernen klicken", "savedRemove": "Gespeichert — zum Entfernen klicken",

View file

@ -1,4 +1,6 @@
{ {
"live": "live",
"upcoming": "upcoming",
"watchedUnmark": "Watched — click to unmark", "watchedUnmark": "Watched — click to unmark",
"markWatched": "Mark watched", "markWatched": "Mark watched",
"savedRemove": "Saved — click to remove", "savedRemove": "Saved — click to remove",

View file

@ -1,4 +1,6 @@
{ {
"live": "élő",
"upcoming": "hamarosan",
"watchedUnmark": "Megnézve — kattints a visszavonáshoz", "watchedUnmark": "Megnézve — kattints a visszavonáshoz",
"markWatched": "Megnézettnek jelöl", "markWatched": "Megnézettnek jelöl",
"savedRemove": "Mentve — kattints az eltávolításhoz", "savedRemove": "Mentve — kattints az eltávolításhoz",