Extract shared data grid, selection bar, file dropzone and genre picker components

Track lists and the upload queue now render through a declarative DataGrid, the album editor reuses AlbumForm with a split layout and a track picker, and genre labels and types are shared helpers.
This commit is contained in:
2026-09-25 01:17:59 +02:00
parent 797bed8e7d
commit 074ea7bf26
26 changed files with 879 additions and 663 deletions
+8 -1
View File
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest';
import { findConflict, genreHue } from './genres';
import { findConflict, genreHue, genreLabels } from './genres';
describe('genreHue', () => {
it('is stable and within 0-359', () => {
@@ -26,3 +26,10 @@ describe('findConflict', () => {
expect(findConflict(['x', 'y'], options)).toBeUndefined();
});
});
describe('genreLabels', () => {
it('joins the labels and falls back to a dash', () => {
expect(genreLabels(['HipHop', 'Rock'])).toBe('Hip hop, Rock');
expect(genreLabels([])).toBe('-');
});
});
+8 -5
View File
@@ -1,3 +1,5 @@
import type { GenreOption } from '$lib/types';
const GENRE_INFO: Record<string, { label: string; tagline: string }> = {
Pop: { label: 'Pop', tagline: 'Éxitos que suenan en todas partes' },
Rock: { label: 'Rock', tagline: 'Guitarras, actitud y ruido' },
@@ -45,6 +47,10 @@ export function genreHue(genre: string): number {
return hash;
}
export function genreLabels(genres: string[]): string {
return genres.map((genre) => genreInfo(genre).label).join(', ') || '-';
}
export function genreTiles(genres: { genre: string }[]) {
return genres.map(({ genre }) => ({
genre,
@@ -53,7 +59,7 @@ export function genreTiles(genres: { genre: string }[]) {
}));
}
export function genreOptions(genres: { genre: string; incompatibleWith: string[] }[]) {
export function genreOptions(genres: GenreOption[]) {
return genres.map(({ genre, incompatibleWith }) => ({
genre,
incompatibleWith,
@@ -61,10 +67,7 @@ export function genreOptions(genres: { genre: string; incompatibleWith: string[]
}));
}
export function findConflict(
tags: string[],
options: { genre: string; incompatibleWith: string[] }[]
): [string, string] | undefined {
export function findConflict(tags: string[], options: GenreOption[]): [string, string] | undefined {
const byGenre = new Map(options.map((option) => [option.genre, option.incompatibleWith]));
for (let i = 0; i < tags.length; i++) {
for (let j = i + 1; j < tags.length; j++) {