feat(playlists): total playtime in header + sticky-header fade fix (E9 S1)
Surface the playlist's total playtime beside the item count in the detail header (12D) — sums every item's duration (formatRuntime "h m", matching the approved mockup), hidden when nothing sums. Data is already client-side; no backend change. Pass fadeTop:false for the playlists page (12B) so the shell's top edge-fade no longer sits over the already-sticky column header (tableHeader.tsx:257), matching the channels/audit pages.
This commit is contained in:
@@ -39,6 +39,7 @@ import { notifyYouTubeActionError } from "../lib/youtubeErrors";
|
|||||||
import { playlistName } from "../lib/playlistName";
|
import { playlistName } from "../lib/playlistName";
|
||||||
import { useUndoable } from "../lib/useUndoable";
|
import { useUndoable } from "../lib/useUndoable";
|
||||||
import { cycleSort, type SortState } from "../lib/columnSort";
|
import { cycleSort, type SortState } from "../lib/columnSort";
|
||||||
|
import { formatRuntime } from "../lib/format";
|
||||||
import { applyView, sameOrder } from "../lib/playlistView";
|
import { applyView, sameOrder } from "../lib/playlistView";
|
||||||
const PlayerModal = lazy(() => import("./PlayerModal"));
|
const PlayerModal = lazy(() => import("./PlayerModal"));
|
||||||
import UndoToolbar from "./UndoToolbar";
|
import UndoToolbar from "./UndoToolbar";
|
||||||
@@ -259,6 +260,13 @@ export default function Playlists() {
|
|||||||
return (v: Video) => m.get(v.id) ?? 0;
|
return (v: Video) => m.get(v.id) ?? 0;
|
||||||
}, [items]);
|
}, [items]);
|
||||||
|
|
||||||
|
// Total playtime of the whole playlist (sums every item, matching the itemCount beside it — not
|
||||||
|
// the filtered `shown` set). Items with an unknown duration count as 0; hidden when nothing sums.
|
||||||
|
const totalSeconds = useMemo(
|
||||||
|
() => items.reduce((acc, v) => acc + (v.duration_seconds ?? 0), 0),
|
||||||
|
[items],
|
||||||
|
);
|
||||||
|
|
||||||
const columns = useMemo(
|
const columns = useMemo(
|
||||||
() => playlistColumns({ t, indexOf: storedIndex, onPlay, onRemove, canEdit }),
|
() => playlistColumns({ t, indexOf: storedIndex, onPlay, onRemove, canEdit }),
|
||||||
[t, storedIndex, onPlay, onRemove, canEdit],
|
[t, storedIndex, onPlay, onRemove, canEdit],
|
||||||
@@ -648,6 +656,7 @@ export default function Playlists() {
|
|||||||
)}
|
)}
|
||||||
<div className="text-xs text-muted mt-0.5">
|
<div className="text-xs text-muted mt-0.5">
|
||||||
{t("playlists.itemCount", { count: items.length })}
|
{t("playlists.itemCount", { count: items.length })}
|
||||||
|
{totalSeconds > 0 && ` · ${formatRuntime(totalSeconds)}`}
|
||||||
{filterActive && ` · ${t("playlists.filteredCount", { count: shown.length })}`}
|
{filterActive && ` · ${t("playlists.filteredCount", { count: shown.length })}`}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-1.5 mt-3 flex-wrap items-center">
|
<div className="flex gap-1.5 mt-3 flex-wrap items-center">
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ export type PageDef = {
|
|||||||
export const PAGE_CONTENT: Record<Page, PageDef> = {
|
export const PAGE_CONTENT: Record<Page, PageDef> = {
|
||||||
feed: { Component: FeedPage },
|
feed: { Component: FeedPage },
|
||||||
channels: { Component: Channels, fadeTop: false, maxWidth: "96rem" },
|
channels: { Component: Channels, fadeTop: false, maxWidth: "96rem" },
|
||||||
playlists: { Component: Playlists, maxWidth: "64rem" },
|
playlists: { Component: Playlists, fadeTop: false, maxWidth: "64rem" },
|
||||||
plex: { Component: PlexBrowse, maxWidth: "1600px" },
|
plex: { Component: PlexBrowse, maxWidth: "1600px" },
|
||||||
notifications: { Component: NotificationsPanel, maxWidth: "48rem" },
|
notifications: { Component: NotificationsPanel, maxWidth: "48rem" },
|
||||||
messages: { Component: MessagesPage },
|
messages: { Component: MessagesPage },
|
||||||
|
|||||||
Reference in New Issue
Block a user