feat(pwa-offline): Pinia store + IndexedDB; contexto para cache/eliminación; toasts; compatibilidad PWA offline
- Agrega @pinia/nuxt, idb y store central (stores/music.ts) - Cacheo manual desde menú contextual y borrado (TrackContextMenu) - Ícono verde para canciones cacheadas, sin auto-cache al reproducir - Toasts de feedback (stores/toast.ts, ToastContainer) - Fallback offline de listado a IndexedDB; fix MUSIC_DIR absoluto en preview/prod - Ajustes PWA: navigateFallback '/', devOptions, workbox condicional - Estilos y animación del context menu (tema light/dark, blur fuerte) - Correcciones de sintaxis y posicionamiento exacto al cursor
This commit is contained in:
@@ -60,6 +60,7 @@ definePageMeta({
|
||||
|
||||
import { ref, onMounted, onUnmounted, computed, watch } from 'vue'
|
||||
import { useLocalStorage } from '@vueuse/core'
|
||||
import { useMusicStore } from '~/stores/music'
|
||||
|
||||
// Import components
|
||||
import AuroraBackground from '~/components/AuroraBackground.client.vue'
|
||||
@@ -67,8 +68,11 @@ import MainContainer from '~/components/MainContainer.client.vue'
|
||||
import TrackList from '~/components/TrackList.client.vue'
|
||||
import MusicControls from '~/components/MusicControls.client.vue'
|
||||
|
||||
// Store
|
||||
const musicStore = useMusicStore()
|
||||
|
||||
// Reactive state
|
||||
const tracks = ref([])
|
||||
const tracks = computed(() => musicStore.tracks)
|
||||
const currentTrack = ref(null)
|
||||
const currentTrackIndex = ref(0)
|
||||
const isPlaying = ref(false)
|
||||
@@ -102,16 +106,23 @@ const displayTracks = computed(() => {
|
||||
// Methods
|
||||
const loadTracks = async () => {
|
||||
try {
|
||||
const response = await $fetch('/api/music')
|
||||
tracks.value = response.tracks
|
||||
await musicStore.fetchTracks()
|
||||
if (tracks.value.length > 0) {
|
||||
generateShuffledIndices()
|
||||
loading.value = false
|
||||
return
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load tracks:', error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
console.warn('Failed to load tracks from server, will fallback to cache:', error)
|
||||
}
|
||||
// Fallback to cached tracks when offline or server fails
|
||||
const cached = await musicStore.loadCachedTracks()
|
||||
if (cached.length > 0) {
|
||||
// Use cached list locally by overwriting store tracks for session display
|
||||
musicStore.tracks = cached
|
||||
generateShuffledIndices()
|
||||
}
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
const generateShuffledIndices = () => {
|
||||
@@ -138,23 +149,36 @@ const playTrack = async (track, index) => {
|
||||
}
|
||||
|
||||
try {
|
||||
// Try IndexedDB cached version first
|
||||
const cachedBlob = await musicStore.getCachedBlob(track.name)
|
||||
if (cachedBlob) {
|
||||
const audioUrl = URL.createObjectURL(cachedBlob)
|
||||
audioPlayer.value.currentBlobUrl = audioUrl
|
||||
audioPlayer.value.src = audioUrl
|
||||
audioPlayer.value.load()
|
||||
audioPlayer.value.addEventListener('canplay', () => {
|
||||
loadingTrack.value = null
|
||||
audioPlayer.value.play()
|
||||
}, { once: true })
|
||||
return
|
||||
}
|
||||
|
||||
// Fetch and preload entire song into memory
|
||||
const encodedName = encodeURIComponent(track.name)
|
||||
const response = await fetch(`/api/music/${encodedName}`)
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`)
|
||||
}
|
||||
|
||||
const blob = await response.blob()
|
||||
const audioUrl = URL.createObjectURL(blob)
|
||||
|
||||
|
||||
// No auto-cache: solo reproducir, el usuario decide cachear desde menú
|
||||
// Store reference to revoke later
|
||||
audioPlayer.value.currentBlobUrl = audioUrl
|
||||
audioPlayer.value.src = audioUrl
|
||||
console.log('Preloaded track into memory:', track.name)
|
||||
audioPlayer.value.load()
|
||||
|
||||
|
||||
// Auto-play when ready
|
||||
audioPlayer.value.addEventListener('canplay', () => {
|
||||
loadingTrack.value = null
|
||||
@@ -359,6 +383,8 @@ const handleViewportChange = () => {
|
||||
// Lifecycle
|
||||
onMounted(() => {
|
||||
loadTracks()
|
||||
// Load cached songs metadata
|
||||
musicStore.loadCachedNames()
|
||||
if (import.meta.client) {
|
||||
document.documentElement.setAttribute('data-theme', isDark.value ? 'dark' : 'light')
|
||||
|
||||
@@ -391,4 +417,3 @@ onUnmounted(() => {
|
||||
<style scoped>
|
||||
/* Page-specific styles */
|
||||
</style>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user