diff --git a/frontend/src/components/PlaylistsRail.tsx b/frontend/src/components/PlaylistsRail.tsx index e66e6a3b..8fdfdd17 100644 --- a/frontend/src/components/PlaylistsRail.tsx +++ b/frontend/src/components/PlaylistsRail.tsx @@ -2,7 +2,7 @@ import { useEffect, useMemo, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { qk } from "../lib/queryKeys"; -import { ArrowDown, ArrowUp, ListVideo, Pin, Plus, RefreshCw, Youtube } from "lucide-react"; +import { ListVideo, Pin, Plus, RefreshCw, Youtube } from "lucide-react"; import { api, type Playlist } from "../lib/api"; import { formatDuration } from "../lib/format"; import { notify } from "../lib/notifications"; @@ -11,6 +11,8 @@ import { LS, readAccountMerged, writeAccount } from "../lib/storage"; import { defaultLayout, type PanelLayout } from "../lib/panelLayout"; import SidePanel from "./SidePanel"; import PanelGroups, { type PanelItem } from "./PanelGroups"; +import SortControl from "./SortControl"; +import { type SortOption } from "../lib/columnSort"; import { usePlaylists, usePlaylistsActions } from "./PlaylistsProvider"; import { useMe } from "../lib/useMe"; import { playlistName } from "../lib/playlistName"; @@ -45,6 +47,12 @@ export default function PlaylistsRail({ const { t } = useTranslation(); const qc = useQueryClient(); const [plSort, setPlSort] = useState(() => readAccountMerged(LS.plSort, PL_SORT_DEFAULT)); + const plSortOptions: SortOption[] = [ + { key: "custom", label: t("playlists.railSortCustom"), directionless: true }, + { key: "name", label: t("playlists.railSortName"), defaultDir: "asc" }, + { key: "count", label: t("playlists.railSortCount"), defaultDir: "desc" }, + { key: "duration", label: t("playlists.railSortDuration"), defaultDir: "desc" }, + ]; useEffect(() => { writeAccount(LS.plSort, plSort); }, [plSort]); @@ -149,43 +157,26 @@ export default function PlaylistsRail({ {t("playlists.syncYoutube")} {playlists.length > 1 && ( -
- - - -
+ + next && setPlSort({ ...plSort, key: next.key as PlSort["key"], dir: next.dir }) + } + trailing={ + + } + /> )}
{ diff --git a/frontend/src/components/PlexPlaylistView.tsx b/frontend/src/components/PlexPlaylistView.tsx index 60ca7dac..f77e9606 100644 --- a/frontend/src/components/PlexPlaylistView.tsx +++ b/frontend/src/components/PlexPlaylistView.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useState, type ReactNode } from "react"; +import { useEffect, useMemo, useState } from "react"; import { qk } from "../lib/queryKeys"; import { useTranslation } from "react-i18next"; import { useQuery, useQueryClient } from "@tanstack/react-query"; @@ -35,6 +35,7 @@ import { api, type PlexCard } from "../lib/api"; import { getAccountRaw, LS, setAccountRaw } from "../lib/storage"; import { useConfirm } from "./ConfirmProvider"; import { LoadingState, StateMessage } from "./QueryState"; +import ViewSwitcher, { type ViewOption } from "./ViewSwitcher"; // A playlist's items, grouped for readability: movies stay standalone, while a run of episodes from the // same show collapses into a season/show group so a whole series doesn't sprawl into an endless flat @@ -499,18 +500,10 @@ export default function PlexPlaylistView({ onRemove, }; - const LayoutBtn = ({ v, icon, label }: { v: Layout; icon: ReactNode; label: string }) => ( - - ); + const layoutOptions: ViewOption[] = [ + { id: "accordion", label: t("plex.playlist.layoutAccordion"), icon: Rows3 }, + { id: "tree", label: t("plex.playlist.layoutTree"), icon: ListTree }, + ]; return (
@@ -536,19 +529,13 @@ export default function PlexPlaylistView({ ) : (

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

)} - {/* Layout preference: Accordion (A) or Tree (C). */} -
- } - label={t("plex.playlist.layoutAccordion")} - /> - } - label={t("plex.playlist.layoutTree")} - /> -
+ {/* Layout preference: Accordion or Tree — the shared list-view switcher. */} +