Search previously mixed audiobooks into movie/series results with no way to separate them beyond single-type filters. Backend: accept a new "video" group media scope (movies + series) anywhere a media_scope is valid, expanded centrally via MediaScopeItemTypes into the search item-type list, browse comma-list Type filter, and a type = ANY(...) condition in the query executor. Register a user-scoped search.media_scope setting (all|video|audiobook, default video). Frontend: Media / Audiobooks / All chips on the search results page that filter results and persist the choice as the user's default; the global search typeahead follows the same preference. An explicit URL ?type= always wins (with type=all as an unscoped sentinel), and the filter-bar dropdown gains a Movies & Series option. The API surface is additive, so Android/Apple clients are unaffected until they adopt the new scope. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
40 lines
1.5 KiB
TypeScript
40 lines
1.5 KiB
TypeScript
import { useCallback } from "react";
|
|
|
|
import { useSetSetting, useSetting } from "@/hooks/queries/settings";
|
|
|
|
export const SEARCH_MEDIA_SCOPE_SETTING_KEY = "search.media_scope";
|
|
|
|
/** Coarse search scope: "video" = movies & series, "audiobook" = books. */
|
|
export type SearchMediaScope = "all" | "video" | "audiobook";
|
|
|
|
export const DEFAULT_SEARCH_MEDIA_SCOPE: SearchMediaScope = "video";
|
|
|
|
export function parseSearchMediaScope(value: string | null | undefined): SearchMediaScope | null {
|
|
return value === "all" || value === "video" || value === "audiobook" ? value : null;
|
|
}
|
|
|
|
/**
|
|
* Server-persisted preference for the default search scope. Search entry
|
|
* points (global search, the catalog search page) apply this scope when the
|
|
* URL doesn't carry an explicit `type` param, and the scope chips write back
|
|
* to it so the choice sticks across sessions and devices.
|
|
*/
|
|
export function useSearchMediaScope() {
|
|
const settingQuery = useSetting(SEARCH_MEDIA_SCOPE_SETTING_KEY);
|
|
const setSetting = useSetSetting();
|
|
|
|
const scope = parseSearchMediaScope(settingQuery.data) ?? DEFAULT_SEARCH_MEDIA_SCOPE;
|
|
|
|
const setScope = useCallback(
|
|
(next: SearchMediaScope) => {
|
|
setSetting.mutate({ key: SEARCH_MEDIA_SCOPE_SETTING_KEY, value: next });
|
|
},
|
|
[setSetting],
|
|
);
|
|
|
|
// While the setting loads, scope falls back to the default ("video") so
|
|
// consumers can fetch immediately; a differing stored preference simply
|
|
// refetches once it arrives.
|
|
return { scope, setScope };
|
|
}
|