Remove YouTube integration from web-player; unify Home track rows

Drops every YouTube-backed route, proxy and type (external
albums/tracks, thumbnail proxy, youtube.ts) and simplifies
player/track/mix types to the local-only catalog now that the
backend contract dropped Source/ExternalId. Genre tiles on Explore
and "Populares esta semana" on Home stay as deliberate client-only
placeholders per product decision, just without the YouTube backing.

Also finishes the design-system unification pass: Home's three
hand-rolled track rows (Continuar escuchando, Playlist destacada,
Populares esta semana) now reuse the shared TrackTitleCell component
instead of duplicating cover+title+artist markup, and the orphaned
Rail.svelte (no importers) is removed.
This commit is contained in:
2026-09-15 21:17:14 +02:00
parent 31032bd8e8
commit 5ae9955bd0
41 changed files with 167 additions and 1541 deletions
@@ -2,18 +2,11 @@
const MENU_WIDTH = 240;
const SUBMENU_WIDTH = 256;
export type AlbumMenuState = { x: number; y: number; openLeft: boolean } & (
{ kind: 'local'; albumId: string } | { kind: 'youtube'; albumId: string }
);
export type AlbumMenuState = { x: number; y: number; openLeft: boolean; albumId: string };
export function albumContextMenuStateFor(
event: MouseEvent,
kind: 'local' | 'youtube',
albumId: string
): AlbumMenuState {
export function albumContextMenuStateFor(event: MouseEvent, albumId: string): AlbumMenuState {
event.preventDefault();
return {
kind,
albumId,
x: Math.max(8, Math.min(event.clientX, window.innerWidth - MENU_WIDTH - 8)),
y: Math.max(8, Math.min(event.clientY, window.innerHeight - 60)),
@@ -93,9 +86,7 @@
{#each playlists as playlist (playlist.id)}
<form
method="POST"
action={menu.kind === 'youtube'
? '?/addYoutubeAlbumToPlaylist'
: '?/addAlbumToPlaylist'}
action="?/addAlbumToPlaylist"
use:enhance={() =>
({ update }) => {
onClose();
@@ -78,7 +78,7 @@
{#each playlists as playlist (playlist.id)}
<form
method="POST"
action={menu.kind === 'youtube' ? '?/addYouTubeToPlaylist' : '?/addTrack'}
action="?/addTrack"
use:enhance={() =>
({ update }) => {
onClose();
@@ -86,15 +86,7 @@
}}
>
<input type="hidden" name="playlistId" value={playlist.id} />
{#if menu.kind === 'youtube'}
<input type="hidden" name="videoId" value={menu.song.videoId} />
<input type="hidden" name="title" value={menu.song.title} />
<input type="hidden" name="artist" value={menu.song.artist} />
<input type="hidden" name="durationSeconds" value={menu.song.durationSeconds} />
<input type="hidden" name="thumbnailUrl" value={menu.song.thumbnailUrl} />
{:else}
<input type="hidden" name="trackId" value={menu.track.id} />
{/if}
<input type="hidden" name="trackId" value={menu.track.id} />
<button
type="submit"
class="flex w-full items-center gap-3 rounded-control px-2 py-2.5 text-left text-sm font-medium text-fg transition hover:bg-hover"
@@ -39,7 +39,6 @@
<div class="mb-6 flex items-center gap-3 rounded-xl bg-accent-tint p-2.5">
<Cover
trackId={player.current.id}
src={player.current.coverUrl}
size="small"
alt={player.current.title}
class="h-10 w-10 shrink-0 rounded-[9px]"
@@ -90,7 +89,6 @@
>
<Cover
trackId={track.id}
src={track.coverUrl}
size="small"
alt={track.title}
class="h-9 w-9 shrink-0 rounded-lg opacity-90"
@@ -104,9 +102,7 @@
/>
</div>
{#if track.duration}
<span class="shrink-0 text-xs text-muted tabular-nums"
>{fmtTime(track.duration)}</span
>
<span class="shrink-0 text-xs text-muted tabular-nums">{fmtTime(track.duration)}</span>
{/if}
</div>
{:else}
@@ -20,8 +20,6 @@
id: player.current.id,
title: player.current.title,
artist: player.current.artist,
coverUrl: player.current.coverUrl,
source: player.current.source,
explicit: player.current.explicit,
ownerUserId: player.current.ownerUserId
});
@@ -32,7 +30,6 @@
{#if player.current.id}
<Cover
trackId={player.current.id}
src={player.current.coverUrl}
size="small"
alt={player.current.title}
class="h-11 w-11 shrink-0 rounded-[11px] shadow-cover-xs md:h-12 md:w-12"
@@ -1,6 +1,5 @@
<script lang="ts">
import type { Snippet } from 'svelte';
import { thumbnailSrc } from '$lib/thumbnails';
interface Props {
trackId: string | number;
@@ -24,9 +23,7 @@
let failed = $state(false);
const imageSrc = $derived(
src ? (thumbnailSrc(src, size) ?? src) : `/api/tracks/${trackId}/cover?size=${size}`
);
const imageSrc = $derived(src ?? `/api/tracks/${trackId}/cover?size=${size}`);
$effect(() => {
imageSrc;
+2 -12
View File
@@ -15,21 +15,11 @@
{#if tiles.length >= 4}
<div class="grid grid-cols-2 grid-rows-2 overflow-hidden {klass}">
{#each tiles as item (mixItemKey(item))}
<Cover
trackId={item.trackId ?? item.videoId ?? ''}
src={item.thumbnailUrl}
size="small"
class="h-full w-full"
/>
<Cover trackId={item.trackId} size="small" class="h-full w-full" />
{/each}
</div>
{:else if tiles.length > 0}
<Cover
trackId={tiles[0].trackId ?? tiles[0].videoId ?? ''}
src={tiles[0].thumbnailUrl}
size="medium"
class={klass}
/>
<Cover trackId={tiles[0].trackId} size="medium" class={klass} />
{:else}
<div class="bg-surface {klass}"></div>
{/if}
@@ -1,29 +0,0 @@
<script lang="ts">
import type { Snippet } from 'svelte';
interface Props {
class?: string;
children: Snippet;
}
let { class: klass = '', children }: Props = $props();
let track: HTMLDivElement | undefined = $state();
export function scrollByPage(direction: 1 | -1) {
track?.scrollBy({ left: direction * track.clientWidth * 0.85, behavior: 'smooth' });
}
</script>
<div bind:this={track} class="no-scrollbar -mx-1 flex gap-4 overflow-x-auto px-1 pb-2 {klass}">
{@render children()}
</div>
<style>
.no-scrollbar {
scrollbar-width: none;
}
.no-scrollbar::-webkit-scrollbar {
display: none;
}
</style>