fix(plex): facet + sort refinements
- Facets now respect the watch-state filter too (In progress / Watched / Unwatched), so e.g. the Year bounds match the visible set (was showing the library min, not the filtered min). - Genre chips sorted alphabetically (was by count). - Sort chips ordered alphabetically by their localized label. - Ascending/Descending chips swapped (Ascending first) and the default sort direction is now Ascending. - Default sort is now Title (ascending) instead of Recently added / Descending.
This commit is contained in:
parent
017be5f8ca
commit
fe024ab29d
4 changed files with 65 additions and 34 deletions
|
|
@ -251,7 +251,7 @@ export default function App() {
|
|||
const [plexScopeRaw, setPlexScope] = useAccountPersistedState(LS.plexLibrary, "both");
|
||||
const plexScope = ["movie", "show", "both"].includes(plexScopeRaw) ? plexScopeRaw : "both";
|
||||
const [plexShowFilter, setPlexShowFilter] = useAccountPersistedState(LS.plexShow, "all");
|
||||
const [plexSort, setPlexSort] = useAccountPersistedState(LS.plexSort, "added");
|
||||
const [plexSort, setPlexSort] = useAccountPersistedState(LS.plexSort, "title");
|
||||
const [plexPlaylistOpen, setPlexPlaylistOpen] = useState<number | null>(null); // sidebar → open a playlist
|
||||
// The expanded Plex filters live as one JSON blob (the string-only account store serializes it).
|
||||
const [plexFiltersRaw, setPlexFiltersRaw] = useAccountPersistedState(LS.plexFilters, "{}");
|
||||
|
|
|
|||
|
|
@ -65,8 +65,8 @@ export default function PlexSidebar({
|
|||
// Facet values (genres/ratings/bounds) for the current scope, ADAPTED to the active filters
|
||||
// (faceted search) — so picking one filter narrows what the others offer. Refetches on any change.
|
||||
const facetsQ = useQuery({
|
||||
queryKey: ["plex-facets", scope, filters],
|
||||
queryFn: () => api.plexFacets(scope, filters),
|
||||
queryKey: ["plex-facets", scope, show, filters],
|
||||
queryFn: () => api.plexFacets(scope, filters, show),
|
||||
enabled: !!scope,
|
||||
placeholderData: (prev) => prev, // keep the old chips visible during the refetch (no flicker)
|
||||
});
|
||||
|
|
@ -75,7 +75,7 @@ export default function PlexSidebar({
|
|||
// (Collection filtering: the active-collection chip is set from an item info page's "Browse
|
||||
// collection"; there's no per-library picker in the unified cross-library scope.)
|
||||
const fCount = plexFilterCount(filters);
|
||||
const activeCount = fCount + (show !== "all" ? 1 : 0) + (sort !== "added" ? 1 : 0);
|
||||
const activeCount = fCount + (show !== "all" ? 1 : 0) + (sort !== "title" ? 1 : 0);
|
||||
const anyActive = activeCount > 0;
|
||||
|
||||
const patch = (p: Partial<PlexFilters>) => setFilters({ ...filters, ...p });
|
||||
|
|
@ -84,11 +84,14 @@ export default function PlexSidebar({
|
|||
const clearAll = () => {
|
||||
setFilters(EMPTY_PLEX_FILTERS);
|
||||
setShow("all");
|
||||
setSort("added");
|
||||
setSort("title");
|
||||
};
|
||||
|
||||
// Movie-only scope offers the duration sort; mixed/show scopes drop it (a show has no runtime).
|
||||
const sorts = scope === "movie" ? MOVIE_SORTS : SHOW_SORTS;
|
||||
// Ordered alphabetically by their localized label.
|
||||
const sorts = [...(scope === "movie" ? MOVIE_SORTS : SHOW_SORTS)].sort((a, b) =>
|
||||
t(`plex.sort.${a}`).localeCompare(t(`plex.sort.${b}`)),
|
||||
);
|
||||
const durBucketKey = DURATION_BUCKETS.find(
|
||||
(b) => (filters.durationMin ?? null) === b.min && (filters.durationMax ?? null) === b.max,
|
||||
)?.key;
|
||||
|
|
@ -210,8 +213,8 @@ export default function PlexSidebar({
|
|||
))}
|
||||
</ChipRow>
|
||||
<div className="mt-1.5 flex gap-1">
|
||||
{(["desc", "asc"] as const).map((d) => (
|
||||
<Chip key={d} active={(filters.sortDir ?? "desc") === d} onClick={() => patch({ sortDir: d })}>
|
||||
{(["asc", "desc"] as const).map((d) => (
|
||||
<Chip key={d} active={(filters.sortDir ?? "asc") === d} onClick={() => patch({ sortDir: d })}>
|
||||
{t(`plex.filter.dir.${d}`)}
|
||||
</Chip>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -1206,13 +1206,15 @@ export const api = {
|
|||
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);
|
||||
if (f.sortDir === "asc") u.set("sort_dir", "asc");
|
||||
u.set("sort_dir", f.sortDir ?? "asc"); // default ascending
|
||||
}
|
||||
return req(`/api/plex/library?${u.toString()}`);
|
||||
},
|
||||
// Facets ADAPT to the active filters (faceted search) — pass them so each group narrows the others.
|
||||
plexFacets: (scope: string, f?: PlexFilters): Promise<PlexFacets> => {
|
||||
// Facets ADAPT to the active filters + watch-state (faceted search) — pass them so each group
|
||||
// narrows the others and the bounds match the visible result set.
|
||||
plexFacets: (scope: string, f?: PlexFilters, show?: string): Promise<PlexFacets> => {
|
||||
const u = new URLSearchParams({ scope });
|
||||
if (show && show !== "all") u.set("show", show);
|
||||
if (f) {
|
||||
if (f.genres?.length) u.set("genres", f.genres.join(","));
|
||||
if (f.genreMode === "all") u.set("genre_mode", "all");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue