feat(plex): 'last watched' browse sort + auto-apply in the in-progress view (S3)
Add a 'lastwatched' sort key to the unified Plex browse. Movies rank by coalesce(progress_updated_at, watched_at); shows by max(coalesce(...)) across their episodes (rollup.resumed_at, now broadened from progress-only so watched episodes count too). Never-watched titles sink via nullslast. Frontend auto-applies lastwatched:desc when entering the in-progress watch-state view and restores the pre-entry sort on exit; a manual sort change while there still wins. i18n HU+EN. Backend tests cover both union legs + mixed nullslast.
This commit is contained in:
@@ -11,6 +11,7 @@ import { User, X } from "lucide-react";
|
||||
import { qk } from "../lib/queryKeys";
|
||||
import { healPlexField } from "../lib/plexSearch";
|
||||
import { healPlexSort, plexSortOptions, PLEX_SORT_FALLBACK } from "../lib/plexSort";
|
||||
import type { SortState } from "../lib/columnSort";
|
||||
import {
|
||||
api,
|
||||
EMPTY_PLEX_FILTERS,
|
||||
@@ -52,6 +53,13 @@ const PlexCollectionView = lazy(() => import("./PlexCollectionView"));
|
||||
|
||||
const PAGE = 40;
|
||||
|
||||
// D3: the "last watched" sort auto-applied when you enter the in-progress watch-state view (most
|
||||
// recently watched first). Frozen for the same reason as PLEX_SORT_FALLBACK (handed out by reference).
|
||||
const LASTWATCHED_SORT: NonNullable<SortState> = Object.freeze({
|
||||
key: "lastwatched",
|
||||
dir: "desc" as const,
|
||||
});
|
||||
|
||||
// Watch-state options for the toolbar pill (moved out of the filter rail — it's a top-level "what am
|
||||
// I looking at" control now, like the feed's Watch-state pill, not a rail widget).
|
||||
const SHOW_OPTS = ["all", "unwatched", "in_progress", "watched"] as const;
|
||||
@@ -185,6 +193,25 @@ export default function PlexBrowse() {
|
||||
return () => window.removeEventListener("keydown", onKey);
|
||||
}, [sub]);
|
||||
|
||||
// D3: entering the in-progress watch-state view auto-applies the "last watched" sort (most recently
|
||||
// watched first); leaving restores the sort that was active before entering. Only the ENTER edge
|
||||
// forces the sort, so a manual sort change while in the in-progress view still wins — it's just not
|
||||
// carried back out (exit returns to the pre-entry preference). We stash the raw provider `sort`, not
|
||||
// the scope-healed one, so the user's real preference round-trips.
|
||||
const prevShow = useRef(show);
|
||||
const preInProgressSort = useRef<SortState | null>(null);
|
||||
useEffect(() => {
|
||||
const was = prevShow.current;
|
||||
if (was !== "in_progress" && show === "in_progress") {
|
||||
preInProgressSort.current = sort;
|
||||
setSort(LASTWATCHED_SORT);
|
||||
} else if (was === "in_progress" && show !== "in_progress") {
|
||||
if (preInProgressSort.current) setSort(preInProgressSort.current);
|
||||
preInProgressSort.current = null;
|
||||
}
|
||||
prevShow.current = show;
|
||||
}, [show, sort, setSort]);
|
||||
|
||||
const browseQ = useInfiniteQuery({
|
||||
queryKey: qk.plexLibrary(scope, dq, sortState, show, filters, fieldState, lang),
|
||||
// Gate on committedLang so a language switch fires ONE fetch (after the genre-clear effect
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
},
|
||||
"sort": {
|
||||
"added": "Recently added",
|
||||
"lastwatched": "Last watched",
|
||||
"title": "Title",
|
||||
"year": "Year",
|
||||
"rating": "IMDb rating",
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
},
|
||||
"sort": {
|
||||
"added": "Nemrég hozzáadott",
|
||||
"lastwatched": "Utoljára nézve",
|
||||
"title": "Cím",
|
||||
"year": "Év",
|
||||
"rating": "IMDb pont",
|
||||
|
||||
@@ -4,8 +4,16 @@ import { healSort, type SortOption, type SortState } from "./columnSort";
|
||||
// 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;
|
||||
const MOVIE_SORTS = [
|
||||
"added",
|
||||
"lastwatched",
|
||||
"release",
|
||||
"year",
|
||||
"rating",
|
||||
"duration",
|
||||
"title",
|
||||
] as const;
|
||||
const SHOW_SORTS = ["added", "lastwatched", "release", "year", "rating", "title"] as const;
|
||||
|
||||
/** The sort keys a scope offers — movie libraries add `duration`; shows/both don't. */
|
||||
function plexSortKeys(scope: string): string[] {
|
||||
|
||||
Reference in New Issue
Block a user