diff --git a/web-player/docs/frontend-refactor-plan.md b/web-player/docs/frontend-refactor-plan.md index 0a4bc25..c000bff 100644 --- a/web-player/docs/frontend-refactor-plan.md +++ b/web-player/docs/frontend-refactor-plan.md @@ -783,7 +783,7 @@ Tareas: | P8 | ✅ | (pendiente) | Decisión tomada (recomendada por el propio plan, sin bloquear): se elimina la columna "Álbum" que siempre mostraba "—". TrackList unifica TrackTable+TrackRow+TrackIndexCell+TrackMeta; AlbumTrackTable/PlaylistTrackTable/LibraryPicker se inlinean en sus páginas y se borran. Sin QA visual autenticada (Docker no disponible) | | P9 | ✅ | (pendiente) | MediaCard gana hover-zoom del arte y scrim+PlayButton condicionados a `onPlay` (antes exclusivos de MixTile); ahora se aplicarían a cualquier card futura con onPlay, consistencia intencional. Sin QA visual autenticada (Docker no disponible) | | P10 | ✅ | (pendiente) | Decisión tomada (recomendada, sin bloquear): iconos en los botones de cabecera de playlist (antes solo álbum tenía). PlaylistHeader pierde su prop `id` sin usar tras inlinearlo. Explorar: la cabecera de "Resultados para…" ahora usa el `mb-7/8` de PageHeader en vez de un `mt-5` ajustado a mano antes de los chips de filtro — gap ligeramente mayor entre el resumen y los chips (Δ menor, documentado). Sin QA visual autenticada (Docker no disponible) | -| P11 | ⬜ | | | +| P11 | ✅ | (pendiente) | CoverForm añade snippet `extra` (no está en la spec literal, solo `fields`) para el contenido a ancho completo bajo la fila portada+campos (descripción de álbum, selector de visibilidad de playlist) que no cabía en una única columna. Upload usa ImageDropzone con `onselect` (prop nueva) y se envuelve en `{#key}` para poder resetear tras publicar — Δ: el contenedor de portada pasa de 144/176px (móvil/desktop) a 144px fijo (size-cover-hero, sin variante desktop mayor), y el icono de posición vacía de 64px a 36px (icon-xl), tal como recomienda el plan. Sin QA visual autenticada (Docker no disponible) | | P12 | ⬜ | | | | P13 | ⬜ | | | | P14 | ⬜ | | | diff --git a/web-player/src/lib/components/ui/AlbumForm.svelte b/web-player/src/lib/components/ui/AlbumForm.svelte index ba9280d..79f7691 100644 --- a/web-player/src/lib/components/ui/AlbumForm.svelte +++ b/web-player/src/lib/components/ui/AlbumForm.svelte @@ -1,13 +1,10 @@ -
{ - submitting = true; - return async ({ update, result }) => { - await update({ reset: false }); - submitting = false; - if (result.type === 'success') onSuccess?.(); - }; - }} - class="mt-5 w-full max-w-2xl space-y-5" + {coverFallbackUrl} + {requireCover} + coverIcon={Disc3} + {formMessage} + {submitLabel} + {submittingLabel} + canSubmit={title.trim() !== ''} + {onCancel} + {onSuccess} > -
- - + -
- - - - - - - -
-
- - - + class="min-h-0 field-control resize-none px-4 {klass}"> diff --git a/web-player/src/routes/(app)/explore/+page.svelte b/web-player/src/routes/(app)/explore/+page.svelte index adc4dfd..7cc6de5 100644 --- a/web-player/src/routes/(app)/explore/+page.svelte +++ b/web-player/src/routes/(app)/explore/+page.svelte @@ -13,6 +13,7 @@ import PlayButton from '$lib/components/ui/PlayButton.svelte'; import EqBars from '$lib/components/ui/EqBars.svelte'; import ListRow from '$lib/components/ui/ListRow.svelte'; + import Chip from '$lib/components/ui/Chip.svelte'; import SectionHeading from '$lib/components/ui/SectionHeading.svelte'; import { EXPLORE_PAGE_SIZE } from '$lib/config'; import { searchHref } from '$lib/navigation.svelte'; @@ -262,19 +263,13 @@
{#each searchChips as chip (chip.label)} - + {chip.label} + {/each}
@@ -361,12 +356,7 @@

{#each genreTiles.slice(0, 4) as genre (genre.query)} - - {genre.label} - + {genre.label} {/each}
diff --git a/web-player/src/routes/(app)/upload/+page.svelte b/web-player/src/routes/(app)/upload/+page.svelte index 3041a26..795cd8a 100644 --- a/web-player/src/routes/(app)/upload/+page.svelte +++ b/web-player/src/routes/(app)/upload/+page.svelte @@ -7,6 +7,7 @@ import Page from '$lib/components/ui/Page.svelte'; import PageHeader from '$lib/components/ui/PageHeader.svelte'; import Surface from '$lib/components/ui/Surface.svelte'; + import ImageDropzone from '$lib/components/ui/ImageDropzone.svelte'; import Alert from '$lib/components/ui/Alert.svelte'; import Field from '$lib/components/ui/Field.svelte'; import Input from '$lib/components/ui/Input.svelte'; @@ -21,7 +22,8 @@ let title = $state(''); let audioName = $state(''); let audioSize = $state(0); - let coverPreview = $state(''); + let hasCover = $state(false); + let coverResetToken = $state(0); let dragging = $state(false); let status = $state('idle'); let acceptedTerms = $state(false); @@ -38,7 +40,7 @@ const busy = $derived(status === 'uploading'); const canPublish = $derived( - title.trim() !== '' && audioName !== '' && coverPreview !== '' && acceptedTerms && !busy + title.trim() !== '' && audioName !== '' && hasCover && acceptedTerms && !busy ); const stepStates = $derived.by(() => { @@ -79,16 +81,12 @@ pickAudio(file); } - function onCoverInput(event: Event) { - const file = (event.currentTarget as HTMLInputElement).files?.[0]; - if (file) coverPreview = URL.createObjectURL(file); - } - function reset() { title = ''; audioName = ''; audioSize = 0; - coverPreview = ''; + hasCover = false; + coverResetToken += 1; status = 'idle'; acceptedTerms = false; errorMsg = ''; @@ -213,24 +211,15 @@
- + {/key} diff --git a/web-player/src/routes/layout.css b/web-player/src/routes/layout.css index 7dc4d29..d3fbc89 100644 --- a/web-player/src/routes/layout.css +++ b/web-player/src/routes/layout.css @@ -203,6 +203,9 @@ oklch(var(--mf-tile-shade-l) var(--mf-tile-shade-c) var(--tile-hue)) ); } +@utility field-control { + @apply w-full rounded-control bg-surface py-3 text-sm text-fg transition placeholder:text-muted focus:bg-surface-2 focus:outline-none; +} @layer base { svg.lucide {