diff --git a/web-player/src/lib/albums.ts b/web-player/src/lib/albums.ts deleted file mode 100644 index 9d85015..0000000 --- a/web-player/src/lib/albums.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { toQueueItems, type ApiTrackLike, type QueueItem } from '$lib/player/player.svelte'; - -export async function fetchAlbumQueueItems(albumId: string): Promise { - const res = await fetch(`/api/albums/${albumId}/tracks`); - if (!res.ok) return []; - const data = (await res.json()) as { items: ApiTrackLike[] }; - return toQueueItems(data.items ?? []); -} diff --git a/web-player/src/lib/collections.ts b/web-player/src/lib/collections.ts deleted file mode 100644 index 4a6fd65..0000000 --- a/web-player/src/lib/collections.ts +++ /dev/null @@ -1,24 +0,0 @@ -export function shuffle(items: T[]): T[] { - const copy = [...items]; - for (let i = copy.length - 1; i > 0; i--) { - const j = Math.floor(Math.random() * (i + 1)); - [copy[i], copy[j]] = [copy[j], copy[i]]; - } - return copy; -} - -export function appendUnique( - current: T[], - incoming: T[], - keyOf: (item: T) => string | number -): T[] { - const seen = new Set(current.map(keyOf)); - const added: T[] = []; - for (const item of incoming) { - const key = keyOf(item); - if (seen.has(key)) continue; - seen.add(key); - added.push(item); - } - return added.length > 0 ? [...current, ...added] : current; -} diff --git a/web-player/src/lib/genres.ts b/web-player/src/lib/genres.ts deleted file mode 100644 index 8dbc9f2..0000000 --- a/web-player/src/lib/genres.ts +++ /dev/null @@ -1,22 +0,0 @@ -export const GENRE_INFO: Record = { - pop: { label: 'Pop', tagline: 'Éxitos que suenan en todas partes' }, - rock: { label: 'Rock', tagline: 'Guitarras, actitud y ruido' }, - reggaeton: { label: 'Reggaetón', tagline: 'El ritmo que no para' }, - 'lo-fi': { label: 'Lo-fi', tagline: 'Para concentrarte o relajarte' }, - indie: { label: 'Indie', tagline: 'Voces fuera del radar' }, - salsa: { label: 'Salsa', tagline: 'Para mover el cuerpo' }, - 'k-pop': { label: 'K-pop', tagline: 'Coreografías pegajosas' }, - electrónica: { label: 'Electrónica', tagline: 'Beats para perderte' }, - jazz: { label: 'Jazz', tagline: 'Improvisación y elegancia' }, - baladas: { label: 'Baladas', tagline: 'Para sentir con calma' }, - trap: { label: 'Trap', tagline: 'Autotune y bajos pesados' }, - cumbia: { label: 'Cumbia', tagline: 'El sabor de siempre' }, - 'hip hop': { label: 'Hip hop', tagline: 'Rimas con actitud' }, - 'música clásica': { label: 'Clásica', tagline: 'Siglos de composición' } -}; - -export const genreTiles = Object.entries(GENRE_INFO).map(([query, info], i, all) => ({ - query, - hue: Math.round((i * 360) / all.length), - ...info -})); diff --git a/web-player/src/lib/menus.svelte.ts b/web-player/src/lib/menus.svelte.ts deleted file mode 100644 index 864dcec..0000000 --- a/web-player/src/lib/menus.svelte.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { contextMenuPosition, type MenuPosition } from '$lib/components/ui/ContextMenu.svelte'; -import { player, toQueueItem, type ApiTrackLike } from './player/player.svelte'; - -export type TrackMenuState = { track: ApiTrackLike } & MenuPosition; - -export function createTrackMenu() { - let state = $state(null); - - return { - get state() { - return state; - }, - open(event: MouseEvent, track: ApiTrackLike) { - state = { track, ...contextMenuPosition(event) }; - }, - close() { - state = null; - }, - playNext() { - if (!state) return; - player.playNextItem(toQueueItem(state.track)); - state = null; - } - }; -} diff --git a/web-player/src/lib/mixes.ts b/web-player/src/lib/mixes.ts deleted file mode 100644 index e08ab79..0000000 --- a/web-player/src/lib/mixes.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { components } from '$lib/api/schema'; -import type { ApiTrackLike } from '$lib/player/player.svelte'; - -export type Mix = components['schemas']['MixApplicationResponse']; -export type MixItem = components['schemas']['MixItemApplicationResponse']; - -export function mixItemKey(item: MixItem): string { - return `mf:${item.trackId}`; -} - -export function mixItemTrack(item: MixItem): ApiTrackLike { - return { - id: item.trackId, - title: item.title, - artist: item.artist, - listensCount: item.listensCount - }; -} diff --git a/web-player/src/lib/navigation.svelte.ts b/web-player/src/lib/navigation.svelte.ts deleted file mode 100644 index d5bb42d..0000000 --- a/web-player/src/lib/navigation.svelte.ts +++ /dev/null @@ -1,101 +0,0 @@ -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(); -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 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 } - ]; -} diff --git a/web-player/src/lib/playlists.svelte.ts b/web-player/src/lib/playlists.svelte.ts deleted file mode 100644 index ef78ebd..0000000 --- a/web-player/src/lib/playlists.svelte.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { createToggle } from './toggle.svelte'; - -export const createPlaylistModal = createToggle(); diff --git a/web-player/src/lib/recentlyPlayed.ts b/web-player/src/lib/recentlyPlayed.ts deleted file mode 100644 index 9b110d6..0000000 --- a/web-player/src/lib/recentlyPlayed.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { player, toQueueItems, type ApiTrackLike, type QueueItem } from '$lib/player/player.svelte'; - -export function mergeRecentlyPlayed(history: ApiTrackLike[], limit: number): QueueItem[] { - const seen = new Set(); - const merged: QueueItem[] = []; - - for (const t of player.recentlyPlayed) { - const id = String(t.id); - if (seen.has(id)) continue; - seen.add(id); - merged.push({ ...t, id }); - } - - for (const item of toQueueItems(history)) { - const id = String(item.id); - if (seen.has(id)) continue; - seen.add(id); - merged.push({ ...item, id }); - } - - return merged.slice(0, limit); -} diff --git a/web-player/src/lib/search.ts b/web-player/src/lib/search.ts deleted file mode 100644 index fda4ac8..0000000 --- a/web-player/src/lib/search.ts +++ /dev/null @@ -1,74 +0,0 @@ -export const SEARCH_FILTERS = ['Todo', 'Canciones', 'Álbumes', 'Playlists', 'Usuarios'] as const; -export type SearchFilter = (typeof SEARCH_FILTERS)[number]; -export const SEARCH_GROUP_PREVIEW = 4; - -export function showGroup(filter: SearchFilter, label: Exclude) { - return filter === 'Todo' || filter === label; -} - -export function capped(filter: SearchFilter, list: T[]) { - return filter === 'Todo' ? list.slice(0, SEARCH_GROUP_PREVIEW) : list; -} - -export function matchPlaylists(playlists: T[], query: string): T[] { - return query ? playlists.filter((p) => p.name.toLowerCase().includes(query.toLowerCase())) : []; -} - -export function searchCounts(counts: { - tracks: number; - albums: number; - playlists: number; - users: number; -}) { - return { - Canciones: counts.tracks, - Álbumes: counts.albums, - Playlists: counts.playlists, - Usuarios: counts.users - }; -} - -export function searchChips(counts: ReturnType) { - const total = counts.Canciones + counts.Álbumes + counts.Playlists + counts.Usuarios; - return SEARCH_FILTERS.map((label) => ({ - label, - count: label === 'Todo' ? total : counts[label] - })); -} - -export type TopResult = - | { kind: 'track'; track: Track } - | { kind: 'album'; album: Album } - | { kind: 'playlist'; playlist: Playlist } - | { kind: 'user'; user: User }; - -export function findTopResult< - Track extends { title: string }, - Album extends { title: string }, - Playlist extends { name: string }, - User extends { name: string } ->(params: { - query: string; - tracks: Track[]; - albums: Album[]; - playlists: Playlist[]; - users: User[]; -}): TopResult | null { - const q = params.query.trim().toLowerCase(); - const startsWithQuery = (value: string) => value.toLowerCase().startsWith(q); - - const userHit = params.users.find((u) => startsWithQuery(u.name)); - if (userHit) return { kind: 'user', user: userHit }; - const trackHit = params.tracks.find((track) => startsWithQuery(track.title)); - if (trackHit) return { kind: 'track', track: trackHit }; - const albumHit = params.albums.find((album) => startsWithQuery(album.title)); - if (albumHit) return { kind: 'album', album: albumHit }; - const playlistHit = params.playlists.find((p) => startsWithQuery(p.name)); - if (playlistHit) return { kind: 'playlist', playlist: playlistHit }; - - if (params.users.length) return { kind: 'user', user: params.users[0] }; - if (params.tracks.length) return { kind: 'track', track: params.tracks[0] }; - if (params.albums.length) return { kind: 'album', album: params.albums[0] }; - if (params.playlists.length) return { kind: 'playlist', playlist: params.playlists[0] }; - return null; -} diff --git a/web-player/src/lib/toggle.svelte.ts b/web-player/src/lib/toggle.svelte.ts deleted file mode 100644 index bee2e96..0000000 --- a/web-player/src/lib/toggle.svelte.ts +++ /dev/null @@ -1,18 +0,0 @@ -export function createToggle(initial = false) { - let open = $state(initial); - - return { - get open() { - return open; - }, - show() { - open = true; - }, - close() { - open = false; - }, - toggle() { - open = !open; - } - }; -}