feat(ui): show playing status in search and artist tracks
cx
This commit is contained in:
1 parent
c6f86a9bcc
commit
d39180bd6c
3 files changed
+314
-15
No files matched your search
@@ -1,10 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { ArrowLeft, User, ChevronDown, ChevronUp, Play, Music, Heart, Search, X, ChevronLeft, ChevronRight } from 'lucide-svelte';
|
||||
import { ArrowLeft, User, ChevronDown, ChevronUp, Play, Pause, Music, Heart, Search, X, ChevronLeft, ChevronRight } from 'lucide-svelte';
|
||||
import type { ArtistDetail, QobuzArtist } from '$lib/types';
|
||||
import AlbumCard from '../AlbumCard.svelte';
|
||||
import TrackMenu from '../TrackMenu.svelte';
|
||||
import { setPlaybackContext } from '$lib/stores/playbackContextStore';
|
||||
import { togglePlay } from '$lib/stores/playerStore';
|
||||
|
||||
interface Track {
|
||||
id: number;
|
||||
@@ -69,6 +70,8 @@
|
||||
onTrackShareSonglink?: (track: Track) => void;
|
||||
onTrackGoToAlbum?: (albumId: string) => void;
|
||||
onTrackGoToArtist?: (artistId: number) => void;
|
||||
activeTrackId?: number | null;
|
||||
isPlaybackActive?: boolean;
|
||||
}
|
||||
|
||||
let {
|
||||
@@ -96,7 +99,9 @@
|
||||
onTrackShareQobuz,
|
||||
onTrackShareSonglink,
|
||||
onTrackGoToAlbum,
|
||||
onTrackGoToArtist
|
||||
onTrackGoToArtist,
|
||||
activeTrackId = null,
|
||||
isPlaybackActive = false
|
||||
}: Props = $props();
|
||||
|
||||
let bioExpanded = $state(false);
|
||||
@@ -298,6 +303,11 @@
|
||||
return `${mins}:${secs.toString().padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
function handlePausePlayback(event: MouseEvent) {
|
||||
event.stopPropagation();
|
||||
void togglePlay();
|
||||
}
|
||||
|
||||
function handleTrackPlay(track: Track, trackIndex?: number) {
|
||||
// Create artist top tracks context
|
||||
if (topTracks.length > 0) {
|
||||
@@ -763,8 +773,10 @@
|
||||
{:else}
|
||||
<div class="tracks-list">
|
||||
{#each topTracks as track, index}
|
||||
{@const isActiveTrack = isPlaybackActive && activeTrackId === track.id}
|
||||
<div
|
||||
class="track-row"
|
||||
class:playing={isActiveTrack}
|
||||
role="button"
|
||||
tabindex="0"
|
||||
onclick={() => handleTrackPlay(track, index)}
|
||||
@@ -779,6 +791,29 @@
|
||||
<Music size={16} />
|
||||
</div>
|
||||
{/if}
|
||||
<button
|
||||
class="track-play-overlay"
|
||||
class:is-playing={isActiveTrack}
|
||||
onclick={(event) => {
|
||||
if (isActiveTrack) {
|
||||
handlePausePlayback(event);
|
||||
} else {
|
||||
event.stopPropagation();
|
||||
handleTrackPlay(track, index);
|
||||
}
|
||||
}}
|
||||
aria-label={isActiveTrack ? 'Pause track' : 'Play track'}
|
||||
>
|
||||
<span class="play-icon" aria-hidden="true">
|
||||
<Play size={18} />
|
||||
</span>
|
||||
<div class="playing-indicator" aria-hidden="true">
|
||||
<div class="bar"></div>
|
||||
<div class="bar"></div>
|
||||
<div class="bar"></div>
|
||||
</div>
|
||||
<Pause size={18} class="pause-icon" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="track-info">
|
||||
<div class="track-title">{track.title}</div>
|
||||
@@ -1671,6 +1706,7 @@
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.track-artwork img {
|
||||
@@ -1689,6 +1725,90 @@
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.track-play-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: background 150ms ease;
|
||||
}
|
||||
|
||||
.track-row:hover .track-play-overlay {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.track-row.playing .track-play-overlay {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.track-play-overlay:hover {
|
||||
background: rgba(0, 0, 0, 0.75);
|
||||
}
|
||||
|
||||
.track-play-overlay .playing-indicator,
|
||||
.track-play-overlay .pause-icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.track-row.playing .track-play-overlay .play-icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.track-row.playing .track-play-overlay .playing-indicator {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.track-row.playing:hover .track-play-overlay .playing-indicator {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.track-row.playing:hover .track-play-overlay .pause-icon {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.playing-indicator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.playing-indicator .bar {
|
||||
width: 3px;
|
||||
background-color: var(--accent-primary);
|
||||
border-radius: 9999px;
|
||||
transform-origin: bottom;
|
||||
animation: artist-equalize 1s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.playing-indicator .bar:nth-child(1) {
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
.playing-indicator .bar:nth-child(2) {
|
||||
height: 14px;
|
||||
animation-delay: 0.15s;
|
||||
}
|
||||
|
||||
.playing-indicator .bar:nth-child(3) {
|
||||
height: 8px;
|
||||
animation-delay: 0.3s;
|
||||
}
|
||||
|
||||
@keyframes artist-equalize {
|
||||
0%, 100% {
|
||||
transform: scaleY(0.5);
|
||||
opacity: 0.7;
|
||||
}
|
||||
50% {
|
||||
transform: scaleY(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.track-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
Reference in new issue
Block a user