feat(Playroom): add lyrics display functionality with animations and preferences management
This commit is contained in:
parent
95d00616d0
commit
588182e888
|
@ -41,7 +41,7 @@ const songInfo = useTemplateRef('songInfo')
|
|||
const playButton = useTemplateRef('playButton')
|
||||
|
||||
const presentQueueListDialog = ref(false)
|
||||
// const presentLyrics = ref(false)
|
||||
const presentLyrics = ref(false)
|
||||
|
||||
onMounted(async () => {
|
||||
Draggable.create(progressBarThumb.value, {
|
||||
|
@ -223,9 +223,9 @@ function makePlayQueueListDismiss() {
|
|||
tl.to(playQueueDialog.value, {
|
||||
x: -384, duration: 0.3, ease: 'power2.in'
|
||||
}, playQueueDialog.value.children.length > 0 ? '<0.1' : '0')
|
||||
.to(playQueueDialogContainer.value, {
|
||||
backgroundColor: 'transparent', duration: 0.2, ease: 'power2.in'
|
||||
}, '<')
|
||||
.to(playQueueDialogContainer.value, {
|
||||
backgroundColor: 'transparent', duration: 0.2, ease: 'power2.in'
|
||||
}, '<')
|
||||
}
|
||||
|
||||
function getCurrentTrack() {
|
||||
|
@ -236,6 +236,70 @@ function getCurrentTrack() {
|
|||
}
|
||||
}
|
||||
|
||||
watch(() => [preferences.presentLyrics, getCurrentTrack().song.lyricUrl], (newValue, oldValue) => {
|
||||
const [showLyrics, hasLyricUrl] = newValue
|
||||
const [prevShowLyrics, _prevHasLyricUrl] = oldValue || [false, null]
|
||||
|
||||
// Show lyrics when both conditions are met
|
||||
if (showLyrics && hasLyricUrl) {
|
||||
presentLyrics.value = true
|
||||
nextTick(() => {
|
||||
const tl = gsap.timeline()
|
||||
tl.from(controllerRef.value, {
|
||||
marginRight: '-40rem',
|
||||
}).fromTo(lyricsSection.value,
|
||||
{ opacity: 0, x: 50, scale: 0.95 },
|
||||
{ opacity: 1, x: 0, scale: 1, duration: 0.5, ease: "power2.out" },
|
||||
"-=0.3"
|
||||
)
|
||||
})
|
||||
}
|
||||
// Hide lyrics with different animations based on reason
|
||||
else if (presentLyrics.value) {
|
||||
let animationConfig
|
||||
|
||||
// If lyrics were toggled off
|
||||
if (prevShowLyrics && !showLyrics) {
|
||||
animationConfig = {
|
||||
opacity: 0, x: -50, scale: 0.95,
|
||||
duration: 0.3, ease: "power2.in"
|
||||
}
|
||||
}
|
||||
// If no lyrics available (song changed)
|
||||
else if (!hasLyricUrl) {
|
||||
animationConfig = {
|
||||
opacity: 0, y: -20, scale: 0.98,
|
||||
duration: 0.3, ease: "power1.in"
|
||||
}
|
||||
}
|
||||
// Default animation
|
||||
else {
|
||||
animationConfig = {
|
||||
opacity: 0, x: -50,
|
||||
duration: 0.3, ease: "power2.in"
|
||||
}
|
||||
}
|
||||
|
||||
const tl = gsap.timeline({
|
||||
onComplete: () => {
|
||||
presentLyrics.value = false
|
||||
}
|
||||
})
|
||||
|
||||
tl.to(controllerRef.value, {
|
||||
marginLeft: '44rem',
|
||||
duration: 0.3, ease: "power2.out"
|
||||
})
|
||||
.to(lyricsSection.value, animationConfig, '<')
|
||||
.set(lyricsSection.value, {
|
||||
opacity: 1, x: 0, y: 0, scale: 1 // Reset for next time
|
||||
})
|
||||
.set(controllerRef.value, {
|
||||
marginLeft: '0rem' // Reset for next time
|
||||
})
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
onUnmounted(() => {
|
||||
})
|
||||
|
||||
|
@ -301,7 +365,8 @@ watch(() => playQueueStore.currentIndex, () => {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<button class="h-10 w-10 flex justify-center items-center rounded-full bg-black/10 backdrop-blur-3xl transition-all duration-200 hover:bg-black/20 hover:scale-110"
|
||||
<button
|
||||
class="h-10 w-10 flex justify-center items-center rounded-full bg-black/10 backdrop-blur-3xl transition-all duration-200 hover:bg-black/20 hover:scale-110"
|
||||
ref="favoriteButton">
|
||||
<span class="text-white">
|
||||
<StarEmptyIcon :size="6" />
|
||||
|
@ -312,7 +377,8 @@ watch(() => playQueueStore.currentIndex, () => {
|
|||
<!-- Progress section -->
|
||||
<div class="flex flex-col gap-1" ref="progressSection">
|
||||
<!-- ...existing progress bar code... -->
|
||||
<div class="w-full p-[0.125rem] bg-white/20 shadow-[0_.125rem_1rem_0_#00000010] rounded-full backdrop-blur-3xl">
|
||||
<div
|
||||
class="w-full p-[0.125rem] bg-white/20 shadow-[0_.125rem_1rem_0_#00000010] rounded-full backdrop-blur-3xl">
|
||||
<div class="w-full" ref="progressBarContainer">
|
||||
<div class="w-2 h-2 bg-white rounded-full shadow-md" ref="progressBarThumb" />
|
||||
</div>
|
||||
|
@ -321,7 +387,8 @@ watch(() => playQueueStore.currentIndex, () => {
|
|||
<div class="w-full flex justify-between">
|
||||
<!-- ...existing time display code... -->
|
||||
<div class="font-medium flex-1 text-left relative">
|
||||
<span class="text-black blur-lg absolute top-0">{{ timeFormatter(Math.floor(playQueueStore.currentTime)) }}</span>
|
||||
<span
|
||||
class="text-black blur-lg absolute top-0">{{ timeFormatter(Math.floor(playQueueStore.currentTime)) }}</span>
|
||||
<span class="text-white/90">{{ timeFormatter(Math.floor(playQueueStore.currentTime)) }}</span>
|
||||
</div>
|
||||
<div class="text-xs text-center relative">
|
||||
|
@ -330,9 +397,11 @@ watch(() => playQueueStore.currentIndex, () => {
|
|||
</div>
|
||||
<div class="flex flex-1">
|
||||
<div class="flex-1" />
|
||||
<button class="text-white/90 font-medium text-right relative transition-colors duration-200 hover:text-white"
|
||||
<button
|
||||
class="text-white/90 font-medium text-right relative transition-colors duration-200 hover:text-white"
|
||||
@click="preferences.displayTimeLeft = !preferences.displayTimeLeft">
|
||||
<span class="text-black blur-lg absolute top-0">{{ `${preferences.displayTimeLeft ? '-' : ''}${timeFormatter(preferences.displayTimeLeft ? Math.floor(playQueueStore.duration) - Math.floor(playQueueStore.currentTime) : playQueueStore.duration)}` }}</span>
|
||||
<span
|
||||
class="text-black blur-lg absolute top-0">{{ `${preferences.displayTimeLeft ? '-' : ''}${timeFormatter(preferences.displayTimeLeft ? Math.floor(playQueueStore.duration) - Math.floor(playQueueStore.currentTime) : playQueueStore.duration)}` }}</span>
|
||||
<span>{{ `${preferences.displayTimeLeft ? '-' : ''}${timeFormatter(preferences.displayTimeLeft ? Math.floor(playQueueStore.duration) - Math.floor(playQueueStore.currentTime) : playQueueStore.duration)}` }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
@ -342,7 +411,8 @@ watch(() => playQueueStore.currentIndex, () => {
|
|||
<!-- Control buttons -->
|
||||
<div class="w-full flex justify-between items-center" ref="controlButtons">
|
||||
<div class="flex-1 text-left flex gap-1">
|
||||
<button class="h-8 w-8 flex justify-center items-center rounded-full hover:bg-white/25 transition-all duration-200 hover:scale-110"
|
||||
<button
|
||||
class="h-8 w-8 flex justify-center items-center rounded-full hover:bg-white/25 transition-all duration-200 hover:scale-110"
|
||||
ref="volumeButton">
|
||||
<div class="w-6 h-6 relative">
|
||||
<span class="text-black blur-md absolute top-0 left-0">
|
||||
|
@ -353,7 +423,8 @@ watch(() => playQueueStore.currentIndex, () => {
|
|||
</span>
|
||||
</div>
|
||||
</button>
|
||||
<button class="text-white h-8 w-8 flex justify-center items-center rounded-full hover:bg-white/25 transition-all duration-200 hover:scale-110"
|
||||
<button
|
||||
class="text-white h-8 w-8 flex justify-center items-center rounded-full hover:bg-white/25 transition-all duration-200 hover:scale-110"
|
||||
@click="makePlayQueueListPresent" ref="queueButton">
|
||||
<div class="w-6 h-6 relative">
|
||||
<span class="text-black blur-md absolute top-0 left-0">
|
||||
|
@ -367,7 +438,8 @@ watch(() => playQueueStore.currentIndex, () => {
|
|||
</div>
|
||||
|
||||
<div class="flex-2 text-center align-center justify-center gap-2 flex">
|
||||
<button class="text-white flex-1 h-10 flex justify-center items-center rounded-lg hover:bg-white/25 transition-all duration-200 hover:scale-105"
|
||||
<button
|
||||
class="text-white flex-1 h-10 flex justify-center items-center rounded-lg hover:bg-white/25 transition-all duration-200 hover:scale-105"
|
||||
@click="playPrevious" ref="prevButton">
|
||||
<div class="w-8 h-8 relative">
|
||||
<span class="text-black/80 blur-lg absolute top-0 left-0">
|
||||
|
@ -379,7 +451,8 @@ watch(() => playQueueStore.currentIndex, () => {
|
|||
</div>
|
||||
</button>
|
||||
|
||||
<button class="text-white flex-1 h-10 flex justify-center items-center rounded-lg hover:bg-white/25 transition-all duration-200"
|
||||
<button
|
||||
class="text-white flex-1 h-10 flex justify-center items-center rounded-lg hover:bg-white/25 transition-all duration-200"
|
||||
@click="handlePlayPause" ref="playButton">
|
||||
<!-- ...existing play/pause icon code... -->
|
||||
<div v-if="playQueueStore.isPlaying">
|
||||
|
@ -412,7 +485,8 @@ watch(() => playQueueStore.currentIndex, () => {
|
|||
</div>
|
||||
</button>
|
||||
|
||||
<button class="text-white flex-1 h-10 flex justify-center items-center rounded-lg hover:bg-white/25 transition-all duration-200 hover:scale-105"
|
||||
<button
|
||||
class="text-white flex-1 h-10 flex justify-center items-center rounded-lg hover:bg-white/25 transition-all duration-200 hover:scale-105"
|
||||
@click="playNext" ref="nextButton">
|
||||
<div class="w-8 h-8 relative">
|
||||
<span class="text-black/80 blur-lg absolute top-0 left-0">
|
||||
|
@ -427,8 +501,9 @@ watch(() => playQueueStore.currentIndex, () => {
|
|||
|
||||
<div class="flex-1 text-right flex gap-1">
|
||||
<div class="flex-1" />
|
||||
<button class="text-white h-8 w-8 flex justify-center items-center rounded-full hover:bg-white/25 transition-all duration-200 hover:scale-110"
|
||||
ref="lyricsButton">
|
||||
<button
|
||||
class="text-white h-8 w-8 flex justify-center items-center rounded-full hover:bg-white/25 transition-all duration-200 hover:scale-110"
|
||||
ref="lyricsButton" @click="preferences.presentLyrics = !preferences.presentLyrics">
|
||||
<div class="w-6 h-6 relative">
|
||||
<span class="text-black blur-md absolute top-0 left-0">
|
||||
<ChatBubbleQuoteIcon :size="6" />
|
||||
|
@ -438,7 +513,8 @@ watch(() => playQueueStore.currentIndex, () => {
|
|||
</span>
|
||||
</div>
|
||||
</button>
|
||||
<button class="text-white h-8 w-8 flex justify-center items-center rounded-full hover:bg-white/25 transition-all duration-200 hover:scale-110"
|
||||
<button
|
||||
class="text-white h-8 w-8 flex justify-center items-center rounded-full hover:bg-white/25 transition-all duration-200 hover:scale-110"
|
||||
ref="moreButton">
|
||||
<div class="w-6 h-6 relative">
|
||||
<span class="text-black blur-sm absolute top-0 left-0">
|
||||
|
@ -454,9 +530,8 @@ watch(() => playQueueStore.currentIndex, () => {
|
|||
</div>
|
||||
|
||||
<!-- Lyrics section - full screen height -->
|
||||
<div class="w-[40rem] h-screen" ref="lyricsSection">
|
||||
<ScrollingLyrics :lrcSrc="getCurrentTrack().song.lyricUrl ?? undefined"
|
||||
class="h-full" ref="scrollingLyrics" />
|
||||
<div class="w-[40rem] h-screen" ref="lyricsSection" v-if="presentLyrics">
|
||||
<ScrollingLyrics :lrcSrc="getCurrentTrack().song.lyricUrl ?? undefined" class="h-full" ref="scrollingLyrics" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -464,23 +539,27 @@ watch(() => playQueueStore.currentIndex, () => {
|
|||
<!-- Queue list dialog with enhanced animations -->
|
||||
<dialog :open="presentQueueListDialog" class="z-20 w-screen h-screen" @click="makePlayQueueListDismiss"
|
||||
ref="playQueueDialogContainer" style="background-color: transparent;">
|
||||
<div class="w-96 h-screen bg-neutral-900/80 shadow-[0_0_16px_0_rgba(0,0,0,0.5)] backdrop-blur-2xl pt-8 flex flex-col transform -translate-x-96"
|
||||
<div
|
||||
class="w-96 h-screen bg-neutral-900/80 shadow-[0_0_16px_0_rgba(0,0,0,0.5)] backdrop-blur-2xl pt-8 flex flex-col transform -translate-x-96"
|
||||
@click.stop ref="playQueueDialog">
|
||||
<div class="flex justify-between mx-8 mb-4">
|
||||
<div class="text-white font-medium text-2xl">播放队列</div>
|
||||
<button class="text-white w-9 h-9 bg-neutral-800/80 border border-[#ffffff39] rounded-full text-center backdrop-blur-3xl flex justify-center items-center transition-all duration-200 hover:bg-neutral-700/80 hover:scale-110"
|
||||
<button
|
||||
class="text-white w-9 h-9 bg-neutral-800/80 border border-[#ffffff39] rounded-full text-center backdrop-blur-3xl flex justify-center items-center transition-all duration-200 hover:bg-neutral-700/80 hover:scale-110"
|
||||
@click="makePlayQueueListDismiss">
|
||||
<XIcon :size="4" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 mx-8 mb-4">
|
||||
<button class="flex-1 h-9 border border-[#ffffff39] rounded-full text-center backdrop-blur-3xl flex justify-center items-center transition-all duration-200 hover:scale-105"
|
||||
<button
|
||||
class="flex-1 h-9 border border-[#ffffff39] rounded-full text-center backdrop-blur-3xl flex justify-center items-center transition-all duration-200 hover:scale-105"
|
||||
:class="playQueueStore.playMode.shuffle ? 'bg-[#ffffffaa] text-neutral-700' : 'text-white bg-neutral-800/80'"
|
||||
@click="toggleShuffle">
|
||||
<ShuffleIcon :size="4" />
|
||||
</button>
|
||||
<button class="flex-1 h-9 border border-[#ffffff39] rounded-full text-center backdrop-blur-3xl flex justify-center items-center transition-all duration-200 hover:scale-105"
|
||||
<button
|
||||
class="flex-1 h-9 border border-[#ffffff39] rounded-full text-center backdrop-blur-3xl flex justify-center items-center transition-all duration-200 hover:scale-105"
|
||||
:class="playQueueStore.playMode.repeat === 'off' ? 'text-white bg-neutral-800/80' : 'bg-[#ffffffaa] text-neutral-700'"
|
||||
@click="toggleRepeat">
|
||||
<CycleTwoArrowsIcon :size="4" v-if="playQueueStore.playMode.repeat !== 'single'" />
|
||||
|
|
|
@ -10,6 +10,8 @@ declare global {
|
|||
|
||||
export const usePreferences = defineStore('preferences', () => {
|
||||
const displayTimeLeft = ref<boolean>(false)
|
||||
const presentLyrics = ref<boolean>(false)
|
||||
|
||||
const isLoaded = ref(false)
|
||||
const storageType = ref<'chrome' | 'localStorage' | 'memory'>('chrome')
|
||||
const debugInfo = ref<string[]>([])
|
||||
|
@ -153,10 +155,11 @@ export const usePreferences = defineStore('preferences', () => {
|
|||
addDebugInfo('开始初始化偏好设置...')
|
||||
|
||||
try {
|
||||
const value = await getStoredValue('displayTimeLeft', false)
|
||||
displayTimeLeft.value = value as boolean
|
||||
const displayTimeLeftValue = await getStoredValue('displayTimeLeft', false)
|
||||
displayTimeLeft.value = displayTimeLeftValue as boolean
|
||||
const presentLyricsValue = await getStoredValue('presentLyrics', false)
|
||||
presentLyrics.value = presentLyricsValue as boolean
|
||||
isLoaded.value = true
|
||||
addDebugInfo(`✅ 偏好设置初始化完成: displayTimeLeft = ${value}`)
|
||||
} catch (error) {
|
||||
addDebugInfo(`❌ 初始化失败: ${error}`)
|
||||
displayTimeLeft.value = false
|
||||
|
@ -174,6 +177,15 @@ export const usePreferences = defineStore('preferences', () => {
|
|||
}
|
||||
}
|
||||
})
|
||||
watch(presentLyrics, async (val) => {
|
||||
if (isLoaded.value) {
|
||||
try {
|
||||
await setStoredValue('presentLyrics', val)
|
||||
} catch (error) {
|
||||
addDebugInfo(`❌ 监听器保存失败: ${error}`)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 手动保存函数(用于调试)
|
||||
const manualSave = async () => {
|
||||
|
@ -204,6 +216,7 @@ export const usePreferences = defineStore('preferences', () => {
|
|||
|
||||
return {
|
||||
displayTimeLeft,
|
||||
presentLyrics,
|
||||
isLoaded,
|
||||
storageType,
|
||||
debugInfo,
|
||||
|
|
Loading…
Reference in New Issue
Block a user