Files
jellytau/src/lib/components/library/libraryMosaic.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

88 lines
2.9 KiB
TypeScript

// 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<FavoritesScope>(["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;
}