// What the library overview mosaic is made of, and in what order. // // Pure: takes the libraries, returns the tiles to draw. No DOM, no stores — so // the ordering and the de-duplication rules below are unit-testable rather than // buried in markup. // // Note what is NOT decided here: which favourites category a library belongs to. // That is Jellyfin vocabulary and arrives on the library itself as // `favoritesScope` (Rust: `SearchScope::for_collection_type`). This file only // decides what to *call* it and where to put it. // // TRACES: UR-075, UR-067 | DR-174, DR-175 | UT-167 import type { Library } from "$lib/api/types"; import { FAVORITE_SCOPE_LABELS, asFavoritesScope, favoritesRouteUrl, type FavoritesScope, } from "$lib/utils/favoritesView"; /** Artwork shapes, as the source images generally arrive. A measured image * overrides these (see MosaicGrid); they are the shape assumed until then. */ const SQUARE = 1; const WIDE = 16 / 9; export type LibraryMosaicEntry = { /** Stable identity for the layout and for `{#each}` keying. */ key: string; /** Assumed width / height until the artwork reports its own. */ ratio: number; label: string; } & ( | { kind: "library"; library: Library } | { kind: "favorites"; scope: FavoritesScope; href: string } ); /** * A music library's artwork is a square cover; everything else is a wide * backdrop. Presentation, not taxonomy: this is the shape of a picture, and it * is a starting guess that the decoded image is allowed to overrule. */ export function assumedLibraryRatio(lib: Library): number { return lib.collectionType === "music" ? SQUARE : WIDE; } /** * The mosaic's tiles, in order: the cross-library favourites entry first, then * each library followed by its own favourites tile. * * A category's favourites tile appears **once**, after the first library of that * category — two movie libraries ("Films", "Kids") share one favourites list, so * a tile each would be two tiles going to the same place. */ export function buildLibraryMosaic(libraries: Library[]): LibraryMosaicEntry[] { const entries: LibraryMosaicEntry[] = [ { key: "favorites:all", kind: "favorites", scope: "all", href: favoritesRouteUrl("all"), ratio: WIDE, label: "Favourites", }, ]; const seenScopes = new Set(["all"]); for (const lib of libraries) { const ratio = assumedLibraryRatio(lib); entries.push({ key: `library:${lib.id}`, kind: "library", library: lib, ratio, label: lib.name }); const scope = asFavoritesScope(lib.favoritesScope); if (!scope || seenScopes.has(scope)) continue; seenScopes.add(scope); entries.push({ key: `favorites:${scope}`, kind: "favorites", scope, href: favoritesRouteUrl(scope), ratio, label: `Favourite ${FAVORITE_SCOPE_LABELS[scope]}`, }); } return entries; }