feat(plex): S3 toolbar relocation — scope to header, sort above the cards
Move the two "what am I browsing" controls out of the filter sidebar to where they're always in reach, matching the Feed's layout: - Library scope (both/movie/show) → a compact SegmentedToggle in the header next to the search box (new PlexScopeToggle, reads the same provider state). - Sort → a SortControl in the row above the cards (into the PageToolbar band, count on the left, sort on the right), like the Feed. Sidebar cleanup: drop the `scope` and `sort` island cards; the panel badge now counts only the filters still in the panel (expanded filters + watch-state); remove the now-unused sort imports. panelLayout drops both from the default plex order (stale saved-layout keys are auto-filtered, so existing layouts degrade cleanly). usePlexActiveFilters no longer emits a sort chip — sort is an always-visible control now, not a removable narrowing (same as the Feed). FE-only, no backend/migration. typecheck + eslint clean; 171 vitest green; self-E2E verified (header scope drives the grid, sort control reorders, sidebar no longer shows the two cards, no sort chip).
This commit is contained in:
@@ -10,7 +10,8 @@ import {
|
||||
} from "react";
|
||||
import { qk } from "../lib/queryKeys";
|
||||
import { healPlexField } from "../lib/plexSearch";
|
||||
import { healPlexSort } from "../lib/plexSort";
|
||||
import { healPlexSort, plexSortOptions, PLEX_SORT_FALLBACK } from "../lib/plexSort";
|
||||
import SortControl from "./SortControl";
|
||||
import { createPortal } from "react-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
@@ -92,7 +93,7 @@ export default function PlexBrowse() {
|
||||
// search term (q) comes from the shared header box; the rail hands off a playlist to open via the
|
||||
// shared `playlistOpen` slot, which we consume into a history subview then clear.
|
||||
const { q, scope, show, sort, filters, field, playlistOpen: openPlaylist, jumpTo } = usePlex();
|
||||
const { setScope, setFilters, setPlaylistOpen, setJumpTo } = usePlexActions();
|
||||
const { setScope, setSort, setFilters, setPlaylistOpen, setJumpTo } = usePlexActions();
|
||||
const dq = useDebounced(q.trim(), 350);
|
||||
// Plex's active-filter chip row (above the grid), the shared feed idiom: one removable chip per
|
||||
// narrowing, plus a Clear-all (shared with the sidebar) that resets filters + watch-state + sort.
|
||||
@@ -101,6 +102,7 @@ export default function PlexBrowse() {
|
||||
// The provider holds sort scope-agnostically; heal it against THIS scope's options so a movie-only
|
||||
// key (e.g. duration) left over from before a switch to shows doesn't ask for an unavailable sort.
|
||||
const sortState = healPlexSort(sort, scope);
|
||||
const sortOptions = plexSortOptions(scope, (k) => t(`plex.sort.${k}`));
|
||||
// Same idiom for the search scope-pill: a movie-only scope can't hold the Episode field.
|
||||
const fieldState = healPlexField(field, scope);
|
||||
const sub = useHistorySubview<Sub>({ kind: "grid" });
|
||||
@@ -346,7 +348,7 @@ export default function PlexBrowse() {
|
||||
{/* The title count is fixed chrome — it portals into the shell's fixed band so it stays put
|
||||
while only the poster grid scrolls. */}
|
||||
<PageToolbar>
|
||||
<div className="px-4 pt-3 pb-1 max-w-[1600px] mx-auto">
|
||||
<div className="px-4 pt-3 pb-1 max-w-[1600px] mx-auto flex items-center gap-2">
|
||||
<p className="text-xs text-muted">
|
||||
{browseQ.isLoading
|
||||
? " "
|
||||
@@ -354,6 +356,18 @@ export default function PlexBrowse() {
|
||||
? t("plex.searchCount", { count: total })
|
||||
: t("plex.count", { count: total })}
|
||||
</p>
|
||||
{/* Sort moved out of the filter sidebar to the row above the cards (S3), like the Feed. */}
|
||||
<div className="ml-auto flex items-center gap-2">
|
||||
<span className="hidden sm:inline text-xs text-muted">{t("plex.filter.sort")}</span>
|
||||
<SortControl
|
||||
options={sortOptions}
|
||||
sort={sortState}
|
||||
onChange={(next) => setSort(next ?? PLEX_SORT_FALLBACK)}
|
||||
label={t("plex.filter.sort")}
|
||||
ascLabel={t("plex.filter.dir.asc")}
|
||||
descLabel={t("plex.filter.dir.desc")}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</PageToolbar>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user