Files
jellytau/src/lib/utils/favoritesView.ts
T
dtourolle 3363ff7f08 Merge branch 'master' into worktree-mosaic-library
# Conflicts:
#	scripts/extract-traces.test.ts
2026-08-16 00:51:46 +02:00

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;