refactor(plex): unify Plex sort onto SortControl + a single SortState (R13 S1)
Plex sort was a bespoke chip-pill row plus a separate asc/desc chip pair, with
the key in PlexState.sort and the direction hidden in PlexFilters.sortDir. Now:
- sort is one SortState {key,dir}; sortDir is dropped from PlexFilters (and from
the facet-key destructure). The wire call still splits it into sort + sort_dir
at the server boundary, so the backend is untouched.
- The chip sort-row is replaced by the shared SortControl (select + arrow) — the
same idiom the feed and channel manager use. New lib/plexSort.ts owns the
scope-dependent vocabulary (movies add duration) + the title/asc fallback.
- PlexProvider migrates the old storage in place: a legacy bare-key plexSort
string plus the old filters.sortDir become the {key,dir} state, so an existing
sort preference survives the upgrade.
- PlexBrowse and PlexSidebar heal the stored sort against the current scope's
options, so a movie-only key (duration) left over after switching to shows
can't ask the server for an unavailable sort.
Gate green: tsc clean, eslint 0 errors, prettier clean, vitest 93.
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
// two-way watch-sync link. Self-contained — its types reference only each other, and its methods
|
||||
// touch only the shared `req`/`beacon` from core plus the local `appendPlexFilters` helper.
|
||||
import { req, beacon } from "./core";
|
||||
import { type SortState } from "../columnSort";
|
||||
|
||||
// Admin: result of testing the Plex server connection (also drives the library-picker).
|
||||
interface PlexSection {
|
||||
@@ -196,7 +197,6 @@ export interface PlexFilters {
|
||||
studios: string[]; // OR — from any selected studio
|
||||
collection?: string | null; // a single Plex collection (rating_key)
|
||||
collectionTitle?: string | null; // its title, for the active-filter chip
|
||||
sortDir?: "asc" | "desc"; // sort direction (default desc)
|
||||
}
|
||||
export const EMPTY_PLEX_FILTERS: PlexFilters = {
|
||||
genres: [],
|
||||
@@ -275,7 +275,7 @@ export const plexApi = {
|
||||
plexLibrary: (p: {
|
||||
scope: string; // movie | show | both
|
||||
q?: string;
|
||||
sort?: string;
|
||||
sort?: SortState;
|
||||
show?: string;
|
||||
offset?: number;
|
||||
limit?: number;
|
||||
@@ -283,15 +283,14 @@ export const plexApi = {
|
||||
}): Promise<PlexUnifiedResult> => {
|
||||
const u = new URLSearchParams({ scope: p.scope });
|
||||
if (p.q) u.set("q", p.q);
|
||||
if (p.sort) u.set("sort", p.sort);
|
||||
if (p.sort) {
|
||||
u.set("sort", p.sort.key);
|
||||
u.set("sort_dir", p.sort.dir);
|
||||
}
|
||||
if (p.show && p.show !== "all") u.set("show", p.show);
|
||||
if (p.offset) u.set("offset", String(p.offset));
|
||||
if (p.limit) u.set("limit", String(p.limit));
|
||||
const f = p.filters;
|
||||
if (f) {
|
||||
appendPlexFilters(u, f);
|
||||
u.set("sort_dir", f.sortDir ?? "asc"); // default ascending
|
||||
}
|
||||
if (p.filters) appendPlexFilters(u, p.filters);
|
||||
return req(`/api/plex/library?${u.toString()}`);
|
||||
},
|
||||
// Facets ADAPT to the active filters + watch-state (faceted search) — pass them so each group
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { type SortOption, type SortState } from "./columnSort";
|
||||
|
||||
// Plex's sort vocabulary, source-agnostic like the feed's — a `SortOption[]` fed to the shared
|
||||
// SortControl. Direction lives in the SortState (no more sibling `sortDir` on PlexFilters); the wire
|
||||
// call (`plexLibrary`) still splits it into `sort` + `sort_dir` at the server boundary.
|
||||
|
||||
const MOVIE_SORTS = ["added", "release", "year", "rating", "duration", "title"] as const;
|
||||
const SHOW_SORTS = ["added", "release", "year", "rating", "title"] as const;
|
||||
|
||||
/** The sort keys a scope offers — movie libraries add `duration`; shows/both don't. */
|
||||
export function plexSortKeys(scope: string): string[] {
|
||||
return [...(scope === "movie" ? MOVIE_SORTS : SHOW_SORTS)];
|
||||
}
|
||||
|
||||
/** The historical default: `sort=title`, `sort_dir=asc`. */
|
||||
export const PLEX_SORT_FALLBACK: SortState = { key: "title", dir: "asc" };
|
||||
|
||||
/** Labelled, alpha-ordered options for the SortControl. `label` resolves each key (i18n
|
||||
* `plex.sort.<key>`); pass a no-op label when only the key set matters (healing). */
|
||||
export function plexSortOptions(scope: string, label: (key: string) => string): SortOption[] {
|
||||
return plexSortKeys(scope)
|
||||
.map((key) => ({ key, label: label(key) }))
|
||||
.sort((a, b) => a.label.localeCompare(b.label));
|
||||
}
|
||||
Reference in New Issue
Block a user