updates
This commit is contained in:
@@ -6,7 +6,7 @@ instant - which is the point of a keyboard-first UI. Ported from the design mock
|
||||
|
||||
import type { Album } from "../api/types";
|
||||
|
||||
export type Filter = "all" | "music" | "book";
|
||||
export type Group = "music" | "audiobooks" | "podcasts";
|
||||
export type Mode = "albums" | "tracks";
|
||||
|
||||
export interface SongHit {
|
||||
@@ -30,21 +30,28 @@ export function normalize(value: string): string {
|
||||
.replace(/[^a-z0-9]/g, "");
|
||||
}
|
||||
|
||||
export function inFilter(album: Album, filter: Filter): boolean {
|
||||
if (filter === "book") return album.kind === "book";
|
||||
if (filter === "music") return album.kind === "music";
|
||||
return true;
|
||||
/** Which of the three shelves an album belongs on. A podcast is exactly a
|
||||
* `Kinderpodcasts`-section album; everything else buckets by `kind`, so a Figuren
|
||||
* album joins whichever of music/audiobooks matches what it actually holds. */
|
||||
export function groupOf(album: Album): Group {
|
||||
if (album.section === "Kinderpodcasts") return "podcasts";
|
||||
return album.kind === "book" ? "audiobooks" : "music";
|
||||
}
|
||||
|
||||
export function pool(albums: Album[], filter: Filter): Album[] {
|
||||
return albums.filter((album) => inFilter(album, filter));
|
||||
export function inGroup(album: Album, group: Group): boolean {
|
||||
return groupOf(album) === group;
|
||||
}
|
||||
|
||||
/** `null` means unrestricted - typing at the root searches every group at once. */
|
||||
export function pool(albums: Album[], group: Group | null): Album[] {
|
||||
return group === null ? albums : albums.filter((album) => inGroup(album, group));
|
||||
}
|
||||
|
||||
export interface BrowseQuery {
|
||||
albums: Album[];
|
||||
search: string;
|
||||
mode: Mode;
|
||||
filter: Filter;
|
||||
group: Group | null;
|
||||
category: string | null;
|
||||
}
|
||||
|
||||
@@ -56,9 +63,11 @@ export function listMode(query: BrowseQuery): "tracks" | "albums" | "categories"
|
||||
}
|
||||
|
||||
export function categoryMatches(query: BrowseQuery): Category[] {
|
||||
if (listMode(query) !== "categories") return [];
|
||||
// `group === null` is the bare root screen, rendered as three shelves instead of a
|
||||
// flat category list - each shelf calls this again with its own group filled in.
|
||||
if (listMode(query) !== "categories" || query.group === null) return [];
|
||||
const map = new Map<string, Category>();
|
||||
for (const album of pool(query.albums, query.filter)) {
|
||||
for (const album of pool(query.albums, query.group)) {
|
||||
const key = album.category;
|
||||
let entry = map.get(key);
|
||||
if (!entry) {
|
||||
@@ -73,7 +82,7 @@ export function categoryMatches(query: BrowseQuery): Category[] {
|
||||
export function albumMatches(query: BrowseQuery): Album[] {
|
||||
if (query.mode === "tracks") return [];
|
||||
const needle = normalize(query.search);
|
||||
let candidates = pool(query.albums, query.filter);
|
||||
let candidates = pool(query.albums, query.group);
|
||||
if (!needle && !query.category) return [];
|
||||
if (query.category) candidates = candidates.filter((a) => a.category === query.category);
|
||||
if (!needle) return candidates;
|
||||
@@ -87,7 +96,7 @@ export function songMatches(query: BrowseQuery): SongHit[] {
|
||||
if (query.mode !== "tracks") return [];
|
||||
const needle = normalize(query.search);
|
||||
const hits: SongHit[] = [];
|
||||
for (const album of pool(query.albums, query.filter)) {
|
||||
for (const album of pool(query.albums, query.group)) {
|
||||
album.tracks.forEach((track, index) => {
|
||||
if (!needle || normalize(track.title).includes(needle)) {
|
||||
hits.push({ album, index, title: track.title, duration: track.duration });
|
||||
|
||||
Reference in New Issue
Block a user