feat(plex): expanded metadata filters, clickable info page, image disk cache
Backend (migration 0045_plex_filter_meta): plex_items gains rating (audienceRating ~IMDb), content_rating, studio, originally_available_at, and GIN-indexed genres / directors / cast_names — all mirrored from the cheap section listing (no per-item API calls; they also seed a future watch-habit recommender). /browse gains genre / content- rating / year / rating / duration / added-within / director / actor / studio filters (@> containment, GIN) + sort by year|rating|duration|release; new /facets endpoint returns available genres+ratings (with counts) and the year/rating/duration bounds. A thin on-disk image cache (.plex-img-cache) serves posters/art/cast photos from local disk after first fetch (~7-14x faster repeat loads). Frontend: PlexSidebar grows the full filter set (facet-driven genre/age chips, rating steps, year range inputs, duration buckets, added-within, active people/studio chips, clear-all); filters persist per-account as one JSON blob. PlexInfo metadata (year, genre, director, cast, studio, IMDb score) is clickable → sets the matching filter and returns to the filtered grid (page variant only; the in-player overlay stays read-only so a stray click can't stop playback). i18n en/hu/de.
This commit is contained in:
parent
690e17611c
commit
eefd7e3abd
15 changed files with 842 additions and 122 deletions
|
|
@ -1,12 +1,14 @@
|
|||
import { lazy, Suspense, useCallback, useEffect, useRef, useState } from "react";
|
||||
import { lazy, Suspense, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import {
|
||||
api,
|
||||
EMPTY_PLEX_FILTERS,
|
||||
getActiveAccount,
|
||||
setActiveAccount,
|
||||
setUnauthorizedHandler,
|
||||
type FeedFilters,
|
||||
type PlexFilters,
|
||||
} from "./lib/api";
|
||||
import { setLanguage, isSupported, type LangCode } from "./i18n";
|
||||
import {
|
||||
|
|
@ -247,6 +249,16 @@ export default function App() {
|
|||
const [plexLib, setPlexLib] = useAccountPersistedState(LS.plexLibrary, "");
|
||||
const [plexShowFilter, setPlexShowFilter] = useAccountPersistedState(LS.plexShow, "all");
|
||||
const [plexSort, setPlexSort] = useAccountPersistedState(LS.plexSort, "added");
|
||||
// The expanded Plex filters live as one JSON blob (the string-only account store serializes it).
|
||||
const [plexFiltersRaw, setPlexFiltersRaw] = useAccountPersistedState(LS.plexFilters, "{}");
|
||||
const plexFilters = useMemo<PlexFilters>(() => {
|
||||
try {
|
||||
return { ...EMPTY_PLEX_FILTERS, ...JSON.parse(plexFiltersRaw) };
|
||||
} catch {
|
||||
return EMPTY_PLEX_FILTERS;
|
||||
}
|
||||
}, [plexFiltersRaw]);
|
||||
const setPlexFilters = (f: PlexFilters) => setPlexFiltersRaw(JSON.stringify(f));
|
||||
// Bumped to tell the channel manager to drop a stale column filter when we send the user
|
||||
// there to see a specific set (the header's "without full history" link).
|
||||
const [channelsFilterReset, setChannelsFilterReset] = useState(0);
|
||||
|
|
@ -727,6 +739,8 @@ export default function App() {
|
|||
setShow={setPlexShowFilter}
|
||||
sort={plexSort}
|
||||
setSort={setPlexSort}
|
||||
filters={plexFilters}
|
||||
setFilters={setPlexFilters}
|
||||
collapsed={filterCollapsed}
|
||||
onToggleCollapse={() => setFilterCollapsed(!filterCollapsed)}
|
||||
/>
|
||||
|
|
@ -802,7 +816,14 @@ export default function App() {
|
|||
onOpenWizard={() => setWizardOpen(true)}
|
||||
/>
|
||||
) : page === "plex" && meQuery.data!.plex_enabled ? (
|
||||
<PlexBrowse q={filters.q} library={plexLib} show={plexShowFilter} sort={plexSort} />
|
||||
<PlexBrowse
|
||||
q={filters.q}
|
||||
library={plexLib}
|
||||
show={plexShowFilter}
|
||||
sort={plexSort}
|
||||
filters={plexFilters}
|
||||
setFilters={setPlexFilters}
|
||||
/>
|
||||
) : (
|
||||
<Feed
|
||||
filters={filters}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue