feat(pwa-offline): Pinia store + IndexedDB; contexto para cache/eliminación; toasts; compatibilidad PWA offline
All checks were successful
build-and-deploy / build (push) Successful in 40s
build-and-deploy / deploy (push) Successful in 4s

- 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:
2025-08-10 02:51:38 -06:00
parent ba70e0d280
commit 81330de97e
14 changed files with 613 additions and 39 deletions

View File

@@ -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>