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, {
 | 
			
		||||
| 
						 | 
				
			
			@ -55,7 +55,7 @@ onMounted(async () => {
 | 
			
		|||
		}
 | 
			
		||||
	})
 | 
			
		||||
	thumbUpdate()
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	setupEntranceAnimations()
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -115,13 +115,13 @@ function setupEntranceAnimations() {
 | 
			
		|||
	if (controllerRef.value) {
 | 
			
		||||
		gsap.fromTo(controllerRef.value.children,
 | 
			
		||||
			{ opacity: 0, y: 30, scale: 0.95 },
 | 
			
		||||
			{ 
 | 
			
		||||
			{
 | 
			
		||||
				opacity: 1, y: 0, scale: 1,
 | 
			
		||||
				duration: 0.6, ease: "power2.out", stagger: 0.1
 | 
			
		||||
			}
 | 
			
		||||
		)
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	if (lyricsSection.value) {
 | 
			
		||||
		gsap.fromTo(lyricsSection.value,
 | 
			
		||||
			{ opacity: 0, x: 50 },
 | 
			
		||||
| 
						 | 
				
			
			@ -147,7 +147,7 @@ function handlePlayPause() {
 | 
			
		|||
function toggleShuffle() {
 | 
			
		||||
	playQueueStore.playMode.shuffle = !playQueueStore.playMode.shuffle
 | 
			
		||||
	playQueueStore.shuffleCurrent = false
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	nextTick(() => {
 | 
			
		||||
		const shuffleBtn = playQueueDialog.value?.querySelector('.flex-1:first-child') as HTMLElement
 | 
			
		||||
		if (shuffleBtn) {
 | 
			
		||||
| 
						 | 
				
			
			@ -167,7 +167,7 @@ function toggleRepeat() {
 | 
			
		|||
			})
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	switch (playQueueStore.playMode.repeat) {
 | 
			
		||||
		case 'off': playQueueStore.playMode.repeat = 'all'; break
 | 
			
		||||
		case 'all': playQueueStore.playMode.repeat = 'single'; break
 | 
			
		||||
| 
						 | 
				
			
			@ -177,17 +177,17 @@ function toggleRepeat() {
 | 
			
		|||
 | 
			
		||||
function makePlayQueueListPresent() {
 | 
			
		||||
	presentQueueListDialog.value = true
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	nextTick(() => {
 | 
			
		||||
		if (!playQueueDialogContainer.value || !playQueueDialog.value) return
 | 
			
		||||
		
 | 
			
		||||
 | 
			
		||||
		const tl = gsap.timeline()
 | 
			
		||||
		tl.to(playQueueDialogContainer.value, {
 | 
			
		||||
			backgroundColor: '#17171780', duration: 0.3, ease: 'power2.out'
 | 
			
		||||
		}).to(playQueueDialog.value, {
 | 
			
		||||
			x: 0, duration: 0.4, ease: 'power3.out'
 | 
			
		||||
		}, '<0.1')
 | 
			
		||||
		
 | 
			
		||||
 | 
			
		||||
		if (playQueueDialog.value.children.length > 0) {
 | 
			
		||||
			tl.fromTo(playQueueDialog.value.children,
 | 
			
		||||
				{ opacity: 0, x: -20 },
 | 
			
		||||
| 
						 | 
				
			
			@ -201,7 +201,7 @@ function makePlayQueueListDismiss() {
 | 
			
		|||
		presentQueueListDialog.value = false
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	const tl = gsap.timeline({
 | 
			
		||||
		onComplete: () => {
 | 
			
		||||
			presentQueueListDialog.value = false
 | 
			
		||||
| 
						 | 
				
			
			@ -213,19 +213,19 @@ function makePlayQueueListDismiss() {
 | 
			
		|||
			}
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	if (playQueueDialog.value.children.length > 0) {
 | 
			
		||||
		tl.to(playQueueDialog.value.children, {
 | 
			
		||||
			opacity: 0, x: -20, duration: 0.2, ease: 'power2.in', stagger: 0.03
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	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(() => {
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -247,7 +311,7 @@ watch(() => playQueueStore.currentIndex, () => {
 | 
			
		|||
			ease: "power2.inOut", yoyo: true, repeat: 1
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	if (songInfo.value) {
 | 
			
		||||
		gsap.fromTo(songInfo.value,
 | 
			
		||||
			{ opacity: 0, y: 10 },
 | 
			
		||||
| 
						 | 
				
			
			@ -267,15 +331,15 @@ watch(() => playQueueStore.currentIndex, () => {
 | 
			
		|||
	<!-- Main content area - new centered flex layout -->
 | 
			
		||||
	<div class="absolute top-0 left-0 flex justify-center h-screen w-screen overflow-y-auto z-10 select-none">
 | 
			
		||||
		<div class="flex items-center justify-center gap-16 h-fit my-auto" ref="mainContainer">
 | 
			
		||||
			
 | 
			
		||||
 | 
			
		||||
			<!-- Controller area -->
 | 
			
		||||
			<div class="flex flex-col w-96 gap-4" ref="controllerRef">
 | 
			
		||||
				<!-- Album cover with enhanced hover effect -->
 | 
			
		||||
				<div ref="albumCover" class="relative">
 | 
			
		||||
					<img :src="getCurrentTrack().album?.coverUrl" 
 | 
			
		||||
					<img :src="getCurrentTrack().album?.coverUrl"
 | 
			
		||||
						class="rounded-2xl shadow-2xl border border-white/20 w-96 h-96 transition-transform duration-300 hover:scale-105" />
 | 
			
		||||
				</div>
 | 
			
		||||
				
 | 
			
		||||
 | 
			
		||||
				<!-- Song info with enhanced styling -->
 | 
			
		||||
				<div class="flex justify-between items-center gap-2" ref="songInfo">
 | 
			
		||||
					<div class="relative flex-auto w-0">
 | 
			
		||||
| 
						 | 
				
			
			@ -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