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 { invoke } from '@tauri-apps/api/core';
|
||||||
import { X, Shuffle, SkipBack, Play, Pause, SkipForward, Repeat, Heart, List, Maximize2, MoreHorizontal, Cast } from 'lucide-svelte';
|
import { X, Shuffle, SkipBack, Play, Pause, SkipForward, Repeat, Heart, List, Maximize2, MoreHorizontal, Cast } from 'lucide-svelte';
|
||||||
import QualityBadge from './QualityBadge.svelte';
|
import QualityBadge from './QualityBadge.svelte';
|
||||||
|
import StackIcon from './StackIcon.svelte';
|
||||||
import LyricsLines from './lyrics/LyricsLines.svelte';
|
import LyricsLines from './lyrics/LyricsLines.svelte';
|
||||||
import { startActiveLineUpdates } from '$lib/stores/lyricsStore';
|
import { startActiveLineUpdates } from '$lib/stores/lyricsStore';
|
||||||
|
|
||||||
@@ -244,7 +245,10 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="track-info">
|
<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>
|
<h2 class="artist">{artist}</h2>
|
||||||
<h3 class="album">{album}</h3>
|
<h3 class="album">{album}</h3>
|
||||||
<div class="quality-info">
|
<div class="quality-info">
|
||||||
@@ -508,14 +512,27 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.title-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-row :global(.stack-icon) {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
font-size: 22px;
|
font-size: 22px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: white;
|
color: white;
|
||||||
margin: 0 0 6px;
|
margin: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.artist {
|
.artist {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { SkipBack, Play, Pause, SkipForward, ChevronDown } from 'lucide-svelte';
|
import { SkipBack, Play, Pause, SkipForward, ChevronDown } from 'lucide-svelte';
|
||||||
|
import StackIcon from './StackIcon.svelte';
|
||||||
import LyricsLines from './lyrics/LyricsLines.svelte';
|
import LyricsLines from './lyrics/LyricsLines.svelte';
|
||||||
import { startActiveLineUpdates, stopActiveLineUpdates } from '$lib/stores/lyricsStore';
|
import { startActiveLineUpdates, stopActiveLineUpdates } from '$lib/stores/lyricsStore';
|
||||||
import { t } from '$lib/i18n';
|
import { t } from '$lib/i18n';
|
||||||
@@ -184,7 +185,10 @@
|
|||||||
<div class="track-info">
|
<div class="track-info">
|
||||||
<img src={artwork} alt="" class="mini-artwork" />
|
<img src={artwork} alt="" class="mini-artwork" />
|
||||||
<div class="track-meta">
|
<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 class="track-artist">{artist}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -464,6 +468,18 @@
|
|||||||
flex: 1;
|
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 {
|
.track-title {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
@@ -471,6 +487,8 @@
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.track-artist {
|
.track-artist {
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
} from 'lucide-svelte';
|
} from 'lucide-svelte';
|
||||||
import QualityBadge from './QualityBadge.svelte';
|
import QualityBadge from './QualityBadge.svelte';
|
||||||
import AudioOutputBadges from './AudioOutputBadges.svelte';
|
import AudioOutputBadges from './AudioOutputBadges.svelte';
|
||||||
|
import StackIcon from './StackIcon.svelte';
|
||||||
import { t } from '$lib/i18n';
|
import { t } from '$lib/i18n';
|
||||||
import {
|
import {
|
||||||
subscribe as subscribeOffline,
|
subscribe as subscribeOffline,
|
||||||
@@ -294,7 +295,10 @@
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="song-info">
|
<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">
|
<div class="song-meta">
|
||||||
{#if artist}
|
{#if artist}
|
||||||
<button class="meta-link" onclick={onArtistClick} title={$t('actions.goToArtist')}>
|
<button class="meta-link" onclick={onArtistClick} title={$t('actions.goToArtist')}>
|
||||||
@@ -676,6 +680,20 @@
|
|||||||
align-self: center;
|
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 {
|
.song-title {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
@@ -683,6 +701,8 @@
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.song-meta {
|
.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,
|
Maximize2,
|
||||||
ListMusic
|
ListMusic
|
||||||
} from 'lucide-svelte';
|
} from 'lucide-svelte';
|
||||||
|
import StackIcon from '$lib/components/StackIcon.svelte';
|
||||||
import {
|
import {
|
||||||
subscribe as subscribePlayer,
|
subscribe as subscribePlayer,
|
||||||
getPlayerState,
|
getPlayerState,
|
||||||
@@ -230,7 +231,10 @@
|
|||||||
<Maximize2 size={14} />
|
<Maximize2 size={14} />
|
||||||
</button>
|
</button>
|
||||||
<div class="track-info">
|
<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">
|
<div class="artist-album">
|
||||||
{playerState.currentTrack?.artist ?? '—'}
|
{playerState.currentTrack?.artist ?? '—'}
|
||||||
{#if playerState.currentTrack?.album}
|
{#if playerState.currentTrack?.album}
|
||||||
@@ -408,6 +412,18 @@
|
|||||||
padding-right: 30px;
|
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 {
|
.title {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
@@ -416,6 +432,8 @@
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.artist-album {
|
.artist-album {
|
||||||
|
|||||||
Reference in new issue
Block a user