// Favourites page presentation helpers โ€” which scopes are offered as tabs, what // they are called, and how a tab is addressed in the URL. // // The *labels and tab order* are presentation and live here. What each scope // MEANS in Jellyfin item types is domain vocabulary and lives in Rust // (`SearchScope::item_types`); this file must never enumerate item types. // // TRACES: UR-067 | DR-117 import type { SearchScope } from "$lib/api/bindings"; /** * Scopes offered as tabs, in display order. A subset of `SearchScope` chosen * for presentation โ€” the backend accepts more than a page needs to show. */ export const FAVORITE_SCOPES = ["all", "movies", "tv", "music"] as const; export type FavoritesScope = (typeof FAVORITE_SCOPES)[number]; export const FAVORITE_SCOPE_LABELS: Record = { all: "All", movies: "Movies", tv: "Shows", music: "Music", }; /** * Resolve the `?scope=` param to a tab, defaulting to All for anything * missing or unrecognised (a hand-edited or stale URL must not blank the page). */ export function resolveFavoritesScope(raw: string | null | undefined): FavoritesScope { if (!raw) return "all"; return (FAVORITE_SCOPES as readonly string[]).includes(raw) ? (raw as FavoritesScope) : "all"; } /** * Narrow a scope the backend supplied (e.g. `Library.favoritesScope`) to one * this page actually offers as a tab, or `null` if it doesn't. * * Unlike `resolveFavoritesScope`, an unrecognised scope is *rejected* rather * than folded into "all": a caller asking "which category is this?" wants no * answer, not the cross-category one. * * TRACES: UR-075 | DR-175 */ export function asFavoritesScope( scope: SearchScope | null | undefined, ): FavoritesScope | null { if (!scope) return null; return (FAVORITE_SCOPES as readonly string[]).includes(scope) ? (scope as FavoritesScope) : null; } /** URL for a tab. The default scope is omitted, keeping the base URL clean. */ export function favoritesRouteUrl(scope: FavoritesScope): string { return scope === "all" ? "/library/favorites" : `/library/favorites?scope=${scope}`; } /** Per-tab empty state copy (ux-flows ยง5C.2). */ export function emptyStateMessage(scope: FavoritesScope): string { const what: Record = { all: "Nothing favourited yet", movies: "No favourite movies yet", tv: "No favourite shows yet", music: "No favourite music yet", }; return `${what[scope]} โ€” tap the heart on anything you like.`; } /** Compile-time guard that every tab is a scope the backend accepts. */ const _scopesAreSearchScopes: readonly SearchScope[] = FAVORITE_SCOPES; void _scopesAreSearchScopes;