import { unwrap } from '@pipu/core'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { Link, useNavigate } from '@tanstack/react-router'; import { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useSession } from '../app/session'; import { Cover, TrackList } from '../entities/catalog'; import { PlaylistEditor, playlistInput, usePlaylist, usePlaylists, type Playlist } from '../entities/playlists'; import { usePlayer } from '../player/context'; import { useErrorText } from '../shared/lib/errors'; import { formatDuration } from '../shared/lib/format'; import { Button, Dialog, Field, FormError } from '../shared/ui/ui'; import { playIcon, shuffleIcon } from '../shared/ui/icons'; import c from './catalog.module.css'; import s from './page.module.css'; import p from './playlists.module.css'; export function PlaylistsPage() { const { t } = useTranslation(); const list = usePlaylists(); const errorText = useErrorText(); const navigate = useNavigate(); const [create, setCreate] = useState(false); const [search, setSearch] = useState(''); const items = list.data?.items.filter(x => x.name.toLocaleLowerCase().includes(search.toLocaleLowerCase())) ?? []; return

{t('playlists.title')}

{list.error ? errorText(list.error) : null} {list.isPending &&

{t('playlists.loading')}

} {list.isSuccess && !items.length &&

{t(search ? 'playlists.no_results' : 'playlists.empty')}

}
{items.map(item => {item.name}{t('catalog.track_count', { count: item.track_count })} · {formatDuration(item.duration_ms)} {item.description && {item.description}} )}
{create && setCreate(false)} onSaved={item => { setCreate(false); void navigate({ to: '/playlist/$id', params: { id: item.id } }); }} />}
; } export function PlaylistPage({ id }: { id: string }) { const { t } = useTranslation(); const query = usePlaylist(id); const errorText = useErrorText(); return
← {t('playlists.title')} {query.error ? errorText(query.error) : null} {query.isPending &&

{t('playlists.loading')}

} {query.data && }
; } function PlaylistDetail({ playlist }: { playlist: Playlist }) { const { t } = useTranslation(); const session = useSession(); const cache = useQueryClient(); const player = usePlayer(); const navigate = useNavigate(); const errorText = useErrorText(); const [edit, setEdit] = useState(false); const [remove, setRemove] = useState(false); const [manage, setManage] = useState(false); const context = { type: 'collection' as const, id: playlist.id, label: playlist.name }; const save = useMutation({ mutationFn: async (ids: string[]) => unwrap(await session.client.PUT('/api/v1/me/playlists/{id}', { params: { path: { id: playlist.id } }, body: { ...playlistInput(playlist), track_ids: ids }, })), onSuccess: data => { cache.setQueryData(['playlists', playlist.id], data); void cache.invalidateQueries({ queryKey: ['playlists'] }); }, onError: () => { void cache.invalidateQueries({ queryKey: ['playlists'] }); } }); const deletion = useMutation({ mutationFn: async () => unwrap(await session.client.DELETE('/api/v1/me/playlists/{id}', { params: { path: { id: playlist.id } } })), onSuccess: () => { cache.removeQueries({ queryKey: ['playlists', playlist.id] }); void cache.invalidateQueries({ queryKey: ['playlists'] }); void navigate({ to: '/collection/playlists' }); } }); const play = (shuffle: boolean) => { const tracks = playlist.tracks.filter(x => x.available); if (shuffle) for (let i = tracks.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [tracks[i], tracks[j]] = [tracks[j]!, tracks[i]!]; } if (tracks.length) player.playList(tracks, 0, context); }; const move = (index: number, delta: number) => { const ids = playlist.tracks.map(x => x.id); [ids[index], ids[index + delta]] = [ids[index + delta]!, ids[index]!]; save.mutate(ids); }; return <>
{t('playlists.private')}

{playlist.name}

{playlist.description}

{t('catalog.track_count', { count: playlist.track_count })} · {formatDuration(playlist.duration_ms)}
{save.error ? errorText(save.error) : null} {!playlist.tracks.length &&

{t('playlists.empty_tracks')}

{t('playlists.find_tracks')}
} String(i + 1)} actions={manage ? (track, i) => : undefined} /> {edit && setEdit(false)} onSaved={() => setEdit(false)} />} {remove && { if (!deletion.isPending) setRemove(false); }} footer={<> }>

{t('playlists.delete_confirm', { name: playlist.name })}

{deletion.error ? errorText(deletion.error) : null}
} ; }