feat(ui): add stack icon to all 4 players
- Create StackIcon component with reactive context subscription - Display Layers icon before track title when context exists - Show tooltip with origin info on hover - Add to: NowPlayingBar, ExpandedPlayer, FocusMode, MiniPlayer - Icon hidden when no playback context (single track) - Responsive sizing per player (14px-18px) cp
This commit is contained in:
1 parent
49fe9e6b11
commit
cf5be434e3
5 files changed
+128
-5
No files matched your search
@@ -2,6 +2,7 @@
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { X, Shuffle, SkipBack, Play, Pause, SkipForward, Repeat, Heart, List, Maximize2, MoreHorizontal, Cast } from 'lucide-svelte';
|
||||
import QualityBadge from './QualityBadge.svelte';
|
||||
import StackIcon from './StackIcon.svelte';
|
||||
import LyricsLines from './lyrics/LyricsLines.svelte';
|
||||
import { startActiveLineUpdates } from '$lib/stores/lyricsStore';
|
||||
|
||||
@@ -244,7 +245,10 @@
|
||||
</div>
|
||||
|
||||
<div class="track-info">
|
||||
<h1 class="title">{trackTitle}</h1>
|
||||
<div class="title-row">
|
||||
<StackIcon size={18} class="stack-icon" />
|
||||
<h1 class="title">{trackTitle}</h1>
|
||||
</div>
|
||||
<h2 class="artist">{artist}</h2>
|
||||
<h3 class="album">{album}</h3>
|
||||
<div class="quality-info">
|
||||
@@ -508,14 +512,27 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.title-row :global(.stack-icon) {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
color: white;
|
||||
margin: 0 0 6px;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.artist {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { SkipBack, Play, Pause, SkipForward, ChevronDown } from 'lucide-svelte';
|
||||
import StackIcon from './StackIcon.svelte';
|
||||
import LyricsLines from './lyrics/LyricsLines.svelte';
|
||||
import { startActiveLineUpdates, stopActiveLineUpdates } from '$lib/stores/lyricsStore';
|
||||
import { t } from '$lib/i18n';
|
||||
@@ -184,7 +185,10 @@
|
||||
<div class="track-info">
|
||||
<img src={artwork} alt="" class="mini-artwork" />
|
||||
<div class="track-meta">
|
||||
<div class="track-title">{trackTitle}</div>
|
||||
<div class="track-title-row">
|
||||
<StackIcon size={14} class="stack-icon" />
|
||||
<div class="track-title">{trackTitle}</div>
|
||||
</div>
|
||||
<div class="track-artist">{artist}</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -464,6 +468,18 @@
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.track-title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.track-title-row :global(.stack-icon) {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.track-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
@@ -471,6 +487,8 @@
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.track-artist {
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
} from 'lucide-svelte';
|
||||
import QualityBadge from './QualityBadge.svelte';
|
||||
import AudioOutputBadges from './AudioOutputBadges.svelte';
|
||||
import StackIcon from './StackIcon.svelte';
|
||||
import { t } from '$lib/i18n';
|
||||
import {
|
||||
subscribe as subscribeOffline,
|
||||
@@ -294,7 +295,10 @@
|
||||
</button>
|
||||
|
||||
<div class="song-info">
|
||||
<span class="song-title" title={trackTitle}>{trackTitle}</span>
|
||||
<div class="song-title-row">
|
||||
<StackIcon size={14} class="stack-icon" />
|
||||
<span class="song-title" title={trackTitle}>{trackTitle}</span>
|
||||
</div>
|
||||
<div class="song-meta">
|
||||
{#if artist}
|
||||
<button class="meta-link" onclick={onArtistClick} title={$t('actions.goToArtist')}>
|
||||
@@ -676,6 +680,20 @@
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.song-title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.song-title-row :global(.stack-icon) {
|
||||
flex-shrink: 0;
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
.song-title {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
@@ -683,6 +701,8 @@
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.song-meta {
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
<script lang="ts">
|
||||
import { Layers } from 'lucide-svelte';
|
||||
import { subscribe, getCurrentContext, getContextDisplayInfo } from '$lib/stores/playbackContextStore';
|
||||
import { onMount, onDestroy } from 'svelte';
|
||||
|
||||
interface Props {
|
||||
size?: number;
|
||||
class?: string;
|
||||
}
|
||||
|
||||
let { size = 16, class: className = '' }: Props = $props();
|
||||
|
||||
let context = $state(getCurrentContext());
|
||||
let displayInfo = $state(context ? getContextDisplayInfo() : null);
|
||||
|
||||
let unsubscribe: (() => void) | null = null;
|
||||
|
||||
onMount(() => {
|
||||
unsubscribe = subscribe(() => {
|
||||
context = getCurrentContext();
|
||||
displayInfo = context ? getContextDisplayInfo() : null;
|
||||
});
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
unsubscribe?.();
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if context && displayInfo}
|
||||
<div class="stack-icon-wrapper {className}" title="Playing from: {displayInfo}">
|
||||
<Layers size={size} strokeWidth={2} />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.stack-icon-wrapper {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--text-secondary);
|
||||
opacity: 0.7;
|
||||
transition: opacity 0.2s;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.stack-icon-wrapper:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
||||
@@ -12,6 +12,7 @@
|
||||
Maximize2,
|
||||
ListMusic
|
||||
} from 'lucide-svelte';
|
||||
import StackIcon from '$lib/components/StackIcon.svelte';
|
||||
import {
|
||||
subscribe as subscribePlayer,
|
||||
getPlayerState,
|
||||
@@ -230,7 +231,10 @@
|
||||
<Maximize2 size={14} />
|
||||
</button>
|
||||
<div class="track-info">
|
||||
<div class="title">{playerState.currentTrack?.title ?? 'No track'}</div>
|
||||
<div class="title-row">
|
||||
<StackIcon size={14} class="stack-icon" />
|
||||
<div class="title">{playerState.currentTrack?.title ?? 'No track'}</div>
|
||||
</div>
|
||||
<div class="artist-album">
|
||||
{playerState.currentTrack?.artist ?? '—'}
|
||||
{#if playerState.currentTrack?.album}
|
||||
@@ -408,6 +412,18 @@
|
||||
padding-right: 30px;
|
||||
}
|
||||
|
||||
.title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.title-row :global(.stack-icon) {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
@@ -416,6 +432,8 @@
|
||||
text-overflow: ellipsis;
|
||||
color: #fff;
|
||||
line-height: 1.4;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.artist-album {
|
||||
|
||||
Reference in new issue
Block a user