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.
106 lines
3.2 KiB
TypeScript
106 lines
3.2 KiB
TypeScript
import { afterNavigate } from '$app/navigation';
|
|
import type { LucideIcon } from '@lucide/svelte';
|
|
import Home from '@lucide/svelte/icons/house';
|
|
import Compass from '@lucide/svelte/icons/compass';
|
|
import ListMusic from '@lucide/svelte/icons/list-music';
|
|
import Heart from '@lucide/svelte/icons/heart';
|
|
import Disc from '@lucide/svelte/icons/disc-2';
|
|
import Folder from '@lucide/svelte/icons/folder';
|
|
import Upload from '@lucide/svelte/icons/upload';
|
|
import type { SessionUser } from '../types';
|
|
|
|
const HISTORY_INDEX = 'sveltekit:history';
|
|
|
|
const LABELS: [RegExp, string][] = [
|
|
[/^\/$/, 'Volver al inicio'],
|
|
[/^\/explore\/?$/, 'Volver a explorar'],
|
|
[/^\/playlists\/[^/]+/, 'Volver a la lista'],
|
|
[/^\/playlists\/?$/, 'Volver a tus listas'],
|
|
[/^\/albums\/[^/]+/, 'Volver al álbum'],
|
|
[/^\/albums\/?$/, 'Volver a tus álbumes'],
|
|
[/^\/library\/?$/, 'Volver a tus canciones'],
|
|
[/^\/upload\/?$/, 'Volver a subir música'],
|
|
[/^\/mixes\/[^/]+/, 'Volver a la mezcla']
|
|
];
|
|
|
|
export interface PageRef {
|
|
url: string;
|
|
label: string;
|
|
}
|
|
|
|
const visited = new Map<number, PageRef>();
|
|
let index = $state(-1);
|
|
|
|
function labelFor(pathname: string): string | null {
|
|
for (const [pattern, label] of LABELS) {
|
|
if (pattern.test(pathname)) return label;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function historyIndex(): number | null {
|
|
const value = history.state?.[HISTORY_INDEX];
|
|
return typeof value === 'number' ? value : null;
|
|
}
|
|
|
|
export function trackNavigation() {
|
|
afterNavigate((nav) => {
|
|
const at = historyIndex();
|
|
if (at === null) return;
|
|
const url = nav.to?.url;
|
|
const label = url ? labelFor(url.pathname) : null;
|
|
if (url && label) visited.set(at, { url: url.pathname + url.search, label });
|
|
else visited.delete(at);
|
|
index = at;
|
|
});
|
|
}
|
|
|
|
export function previousPage(): PageRef | null {
|
|
return visited.get(index - 1) ?? null;
|
|
}
|
|
|
|
export function isSectionActive(href: string, pathname: string, section?: string | null): boolean {
|
|
if (section !== undefined) return section === href;
|
|
return href === '/' ? pathname === '/' : pathname.startsWith(href);
|
|
}
|
|
|
|
export function searchHref(query: string): string {
|
|
return query ? `/explore?q=${encodeURIComponent(query)}` : '/explore';
|
|
}
|
|
|
|
export function genreHref(genre: string): string {
|
|
return `/explore?genre=${encodeURIComponent(genre)}`;
|
|
}
|
|
|
|
export interface AppNavLink {
|
|
href: string;
|
|
label: string;
|
|
icon: LucideIcon;
|
|
count?: number;
|
|
primary: boolean;
|
|
}
|
|
|
|
export interface AppNavCounts {
|
|
playlists?: number;
|
|
liked?: number;
|
|
}
|
|
|
|
export function appNavLinks(user: SessionUser | null, counts: AppNavCounts = {}): AppNavLink[] {
|
|
if (!user) return [{ href: '/explore', label: 'Descubrir', icon: Compass, primary: true }];
|
|
return [
|
|
{ href: '/', label: 'Inicio', icon: Home, primary: true },
|
|
{ href: '/explore', label: 'Descubrir', icon: Compass, primary: true },
|
|
{
|
|
href: '/playlists',
|
|
label: 'Playlists',
|
|
icon: ListMusic,
|
|
count: counts.playlists,
|
|
primary: true
|
|
},
|
|
{ href: '/liked', label: 'Me gusta', icon: Heart, count: counts.liked, primary: true },
|
|
{ href: '/albums', label: 'Mis álbumes', icon: Disc, primary: false },
|
|
{ href: '/library', label: 'Canciones subidas', icon: Folder, primary: false },
|
|
{ href: '/upload', label: 'Subir música', icon: Upload, primary: false }
|
|
];
|
|
}
|