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:
2026-08-13 23:36:34 +02:00
parent 440bdafa74
commit e4bdc5ea6a
5 changed files with 88 additions and 44 deletions
+24
View File
@@ -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));
}