74 lines
2.6 KiB
TypeScript
74 lines
2.6 KiB
TypeScript
// 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<FavoritesScope, string> = {
|
|
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<FavoritesScope, string> = {
|
|
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;
|