Files
silo-server/web/src/hooks/useSearchMediaScope.ts
T
QuickandClaude Fable 5 2fe2c918f4 feat(search): add Media/Audiobooks/All scope with remembered per-user default
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>
2026-06-09 15:46:11 -04:00

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 };
}