Make Discover and upload genres dynamic
CI/CD / BackEnd (push) Successful in 1m22s
CI/CD / WebPlayer (push) Successful in 38s
CI/CD / Deploy (push) Skipped
CI/CD / BackEnd (pull_request) Successful in 1m21s
CI/CD / WebPlayer (pull_request) Successful in 40s
CI/CD / Deploy (pull_request) Skipped
CI/CD / BackEnd (push) Successful in 1m22s
CI/CD / WebPlayer (push) Successful in 38s
CI/CD / Deploy (push) Skipped
CI/CD / BackEnd (pull_request) Successful in 1m21s
CI/CD / WebPlayer (pull_request) Successful in 40s
CI/CD / Deploy (pull_request) Skipped
Discover used a hardcoded list of genre tiles that searched track titles, and the upload form kept its own copy of the genre list and compatibility rules. Both now come from the backend. Backend: - GET /genres returns genres with at least one track and their counts. - GET /genres/available returns every assignable genre with the genres it cannot be combined with, so the rules live only in GenreCompatibility. - GET /tracks accepts ?genre= (case-insensitive, 400 on unknown values). web-player: - Discover tiles are built from /genres; each opens /explore?genre=X, a song list filtered by that tag. Unknown genres fall back to a label derived from their name. - The upload form loads /genres/available, disables incompatible genres as you pick, and the server validates before reserving upload URLs. - lib/data/trackGenres.ts is removed; its helpers merge into lib/data/genres.ts. - Regenerate the typed API client.
This commit is contained in:
@@ -16,13 +16,13 @@
|
||||
import Chip from '$lib/components/ui/primitives/Chip.svelte';
|
||||
import SectionHeading from '$lib/components/ui/layout/SectionHeading.svelte';
|
||||
import { EXPLORE_PAGE_SIZE } from '$lib/config';
|
||||
import { searchHref } from '$lib/state/navigation.svelte';
|
||||
import { genreHref } from '$lib/state/navigation.svelte';
|
||||
import { createTrackMenu } from '$lib/state/menus.svelte';
|
||||
import ContextMenu, { contextMenuPosition } from '$lib/components/ui/overlay/ContextMenu.svelte';
|
||||
import ListPlus from '@lucide/svelte/icons/list-plus';
|
||||
import ListEnd from '@lucide/svelte/icons/list-end';
|
||||
import { appendUnique } from '$lib/data/collections';
|
||||
import { genreTiles } from '$lib/data/genres';
|
||||
import { genreInfo, genreTiles } from '$lib/data/genres';
|
||||
import {
|
||||
type SearchFilter,
|
||||
showGroup,
|
||||
@@ -58,7 +58,7 @@
|
||||
$effect(() => {
|
||||
trackMenu.close();
|
||||
albumMenu = null;
|
||||
sfilter = 'Todo';
|
||||
sfilter = data.genre ? 'Canciones' : 'Todo';
|
||||
localItems = data.tracks.items.map((item) => item.track);
|
||||
localPage = Number(data.tracks.pageNumber);
|
||||
hasMore = data.tracks.hasNextPage;
|
||||
@@ -73,6 +73,9 @@
|
||||
|
||||
const items = $derived(songRows.map((row) => row.track));
|
||||
|
||||
const tiles = $derived(genreTiles(data.genres));
|
||||
const activeGenre = $derived(data.genre ? genreInfo(data.genre) : null);
|
||||
|
||||
const albums = $derived(data.albums);
|
||||
const users = $derived(data.users);
|
||||
|
||||
@@ -169,6 +172,7 @@
|
||||
pageSize: String(EXPLORE_PAGE_SIZE)
|
||||
});
|
||||
if (data.query) params.set('name', data.query);
|
||||
if (data.genre) params.set('genre', data.genre);
|
||||
const res = await fetch(`/api/tracks?${params}`);
|
||||
if (!res.ok) throw new Error(String(res.status));
|
||||
const next = (await res.json()) as typeof data.tracks;
|
||||
@@ -188,7 +192,9 @@
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{data.query ? `Buscar «${data.query}»` : 'Descubrir'}</title>
|
||||
<title
|
||||
>{activeGenre ? activeGenre.label : data.query ? `Buscar «${data.query}»` : 'Descubrir'}</title
|
||||
>
|
||||
<meta
|
||||
name="description"
|
||||
content="Busca canciones, álbumes y personas, o descubre por género en Musify."
|
||||
@@ -196,7 +202,9 @@
|
||||
</svelte:head>
|
||||
|
||||
<Page>
|
||||
{#if !data.query}
|
||||
{#if activeGenre}
|
||||
<PageHeader eyebrow="Género" title={activeGenre.label} description={activeGenre.tagline} />
|
||||
{:else if !data.query}
|
||||
<PageHeader
|
||||
title="Descubrir"
|
||||
description="Encuentra tu próxima canción favorita explorando por género."
|
||||
@@ -300,31 +308,34 @@
|
||||
por playlist.
|
||||
</p>
|
||||
<div class="mt-5.5 flex flex-wrap justify-center gap-2">
|
||||
{#each genreTiles.slice(0, 4) as genre (genre.query)}
|
||||
<Chip href={searchHref(genre.query)}>{genre.label}</Chip>
|
||||
{#each tiles.slice(0, 4) as tile (tile.genre)}
|
||||
<Chip href={genreHref(tile.genre)}>{tile.label}</Chip>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if !data.query}
|
||||
{#if !data.query && !data.genre}
|
||||
<div class="grid-wide">
|
||||
{#each genreTiles as genre, i (genre.query)}
|
||||
{#each tiles as tile, i (tile.genre)}
|
||||
<a
|
||||
href={searchHref(genre.query)}
|
||||
href={genreHref(tile.genre)}
|
||||
class="animate-enter group relative flex h-28 flex-col gap-1 overflow-hidden rounded-panel p-4 transition-[filter] genre-tile hover:brightness-110"
|
||||
style="--i:{i}; --tile-hue:{genre.hue}"
|
||||
style="--i:{i}; --tile-hue:{tile.hue}"
|
||||
>
|
||||
<div class="text-on-art">
|
||||
{genre.label}
|
||||
{tile.label}
|
||||
</div>
|
||||
<div class="text-xs text-on-art-2">{genre.tagline}</div>
|
||||
<div class="text-xs text-on-art-2">{tile.tagline}</div>
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
{#if tiles.length === 0}
|
||||
<EmptyState icon={Music} description="Todavía no hay géneros. Sube música para empezar." />
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{#if data.query && !nothingFound}
|
||||
{#if (data.query || data.genre) && !nothingFound}
|
||||
<div class="mt-9 flex flex-col gap-9">
|
||||
{#if showGroup(sfilter, 'Canciones')}
|
||||
<div>
|
||||
@@ -364,7 +375,9 @@
|
||||
{:else}
|
||||
<EmptyState
|
||||
icon={Music}
|
||||
description="No hay canciones que coincidan con «{data.query}»."
|
||||
description={activeGenre
|
||||
? 'Todavía no hay canciones de este género.'
|
||||
: `No hay canciones que coincidan con «${data.query}».`}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user