feat(web): polish catalog motion, empty states, and mobile now-playing
release / desktop (Linux и Windows в релиз) (push) Successful in 16m22s
release / prune (старые файлы и прогоны) (push) Successful in 6s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 1m23s
release / release (версия и заметки) (push) Successful in 6s
ci / ts / ts (packages + web) (push) Failing after 5m25s
release / android (APK в релиз) (push) Successful in 4m18s
release / desktop (Linux и Windows в релиз) (push) Successful in 16m22s
release / prune (старые файлы и прогоны) (push) Successful in 6s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 1m23s
release / release (версия и заметки) (push) Successful in 6s
ci / ts / ts (packages + web) (push) Failing after 5m25s
release / android (APK в релиз) (push) Successful in 4m18s
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
39aaaed969
commit
72567acd28
31 files changed
+939
-131
No files matched your search
@@ -4,8 +4,12 @@ import { Link } from '@tanstack/react-router';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useSession } from '../app/session';
|
||||
import { ArtistLinks, Cover, TrackList } from '../entities/catalog';
|
||||
import { ArtistLinks, Cover, coverSrc } from '../entities/catalog';
|
||||
import { AddToPlaylist } from '../entities/playlists';
|
||||
import { usePlayer, usePlayerState } from '../player/context';
|
||||
import { formatDuration } from '../shared/lib/format';
|
||||
import { pauseIcon, playIcon } from '../shared/ui/icons';
|
||||
import { Button, PageSkeleton } from '../shared/ui/ui';
|
||||
import { NotFound } from './NotFound';
|
||||
import s from './page.module.css';
|
||||
import c from './catalog.module.css';
|
||||
@@ -14,27 +18,63 @@ import { TrackGenius } from './TrackGenius';
|
||||
export function TrackPage({ id }: { id: string }) {
|
||||
const { t } = useTranslation();
|
||||
const session = useSession();
|
||||
const player = usePlayer();
|
||||
const st = usePlayerState();
|
||||
const track = useQuery({
|
||||
queryKey: ['track', id],
|
||||
queryFn: async () => unwrap(await session.client.GET('/api/v1/tracks/{id}', { params: { path: { id } } })),
|
||||
});
|
||||
if (track.isPending) {
|
||||
return (
|
||||
<div className={s.page}>
|
||||
<PageSkeleton />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (track.isError) return <NotFound error={track.error} />;
|
||||
const tr = track.data;
|
||||
if (!tr) return null;
|
||||
const ctx = { type: 'queue' as const };
|
||||
const current = st.track?.id === tr.id;
|
||||
const playing = current && st.playing;
|
||||
return (
|
||||
<div className={s.page}>
|
||||
<header className={c.hero}>
|
||||
{tr && <Cover album={tr.album} size={200} />}
|
||||
<div className={c.heroBody}>
|
||||
<span className={c.kind}>{t('player.track_page')}</span>
|
||||
<h1 className={c.heroTitle}>{tr?.title ?? '…'}</h1>
|
||||
{tr && <>
|
||||
<span className={c.heroMeta}><ArtistLinks artists={tr.artists} /> · {formatDuration(tr.duration_ms)}</span>
|
||||
<Link to="/album/$id" params={{ id: tr.album.id }}>{tr.album.title}</Link>
|
||||
</>}
|
||||
<div className={c.listen}>
|
||||
<header className={c.hero}>
|
||||
{tr.album.cover_url && (
|
||||
<span className={c.heroBg} aria-hidden>
|
||||
<img src={coverSrc(tr.album.cover_url, 300)} alt="" />
|
||||
</span>
|
||||
)}
|
||||
<Cover album={tr.album} size={280} className={c.heroCover} />
|
||||
<div className={c.heroBody}>
|
||||
<span className={c.kind}>{t('player.track_page')}</span>
|
||||
<h1 className={c.heroTitle}>{tr.title}</h1>
|
||||
<span className={c.heroMeta}>
|
||||
<ArtistLinks artists={tr.artists} /> · {formatDuration(tr.duration_ms)}
|
||||
</span>
|
||||
<Link to="/album/$id" params={{ id: tr.album.id }}>
|
||||
{tr.album.title}
|
||||
</Link>
|
||||
<div className={c.heroActions}>
|
||||
{tr.available && (
|
||||
<Button
|
||||
variant="primary"
|
||||
className={c.pill}
|
||||
icon={playing ? pauseIcon : playIcon}
|
||||
onClick={() => (current ? player.toggle() : player.playList([tr], 0, ctx))}
|
||||
>
|
||||
{t(playing ? 'player.pause' : 'catalog.play')}
|
||||
</Button>
|
||||
)}
|
||||
<AddToPlaylist trackIds={[tr.id]} />
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div className={c.listenLyrics}>
|
||||
<TrackGenius id={id} />
|
||||
</div>
|
||||
</header>
|
||||
{tr && <TrackList tracks={[tr]} context={{ type: 'queue' }} />}
|
||||
{tr && <TrackGenius id={id} />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user