feat(plex): adaptive (faceted) filters — each filter narrows the others

The sidebar's filter chips/bounds now ADAPT to the active filters: /facets takes the
current filters and computes each group's available values with all the OTHER active
filters applied but not its own (standard faceted search). So picking e.g. IMDb rating
>=8 trims the genre/content-rating chips + year bounds to what's still reachable, and
picking a genre narrows them further — while a multi-select group (genres) still shows
options to add. Extracted _meta_filter_conds (shared by browse/unified/facets), and the
frontend passes the live filters to plexFacets (query keyed on them, previous chips kept
during refetch). Search text is intentionally not factored into facets.
This commit is contained in:
npeter83 2026-07-11 00:31:47 +02:00
parent 736db017e4
commit 017be5f8ca
3 changed files with 119 additions and 49 deletions

View file

@ -1210,8 +1210,26 @@ export const api = {
}
return req(`/api/plex/library?${u.toString()}`);
},
plexFacets: (scope: string): Promise<PlexFacets> =>
req(`/api/plex/facets?scope=${encodeURIComponent(scope)}`),
// Facets ADAPT to the active filters (faceted search) — pass them so each group narrows the others.
plexFacets: (scope: string, f?: PlexFilters): Promise<PlexFacets> => {
const u = new URLSearchParams({ scope });
if (f) {
if (f.genres?.length) u.set("genres", f.genres.join(","));
if (f.genreMode === "all") u.set("genre_mode", "all");
if (f.contentRatings?.length) u.set("content_ratings", f.contentRatings.join(","));
if (f.yearMin != null) u.set("year_min", String(f.yearMin));
if (f.yearMax != null) u.set("year_max", String(f.yearMax));
if (f.ratingMin != null) u.set("rating_min", String(f.ratingMin));
if (f.durationMin != null) u.set("duration_min", String(f.durationMin));
if (f.durationMax != null) u.set("duration_max", String(f.durationMax));
if (f.addedWithin) u.set("added_within", f.addedWithin);
if (f.directors?.length) u.set("directors", f.directors.join(","));
if (f.actors?.length) u.set("actors", f.actors.join(","));
if (f.studios?.length) u.set("studios", f.studios.join(","));
if (f.collection) u.set("collection", f.collection);
}
return req(`/api/plex/facets?${u.toString()}`);
},
plexPeople: (library: string, q: string): Promise<{ people: PlexPerson[] }> =>
req(`/api/plex/people?library=${encodeURIComponent(library)}&q=${encodeURIComponent(q)}`),
plexCollections: (library: string, q?: string): Promise<{ collections: PlexCollection[] }> =>