202 lines
4.6 KiB
Vue
202 lines
4.6 KiB
Vue
<template>
|
|
<div class="card-page">
|
|
<div v-if="loading" class="loading">
|
|
<div class="spinner"></div>
|
|
</div>
|
|
|
|
<Transition name="card-fade" mode="out-in">
|
|
<div v-if="!loading && track" class="card-container" @click="playTrack">
|
|
<Card :track="track" :is-face-up="true" class="card-item" />
|
|
</div>
|
|
|
|
<div v-else-if="error" class="error-message">
|
|
{{ error }}
|
|
</div>
|
|
</Transition>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, onMounted, onBeforeUnmount } from 'vue'
|
|
import { useRoute } from 'vue-router'
|
|
import { usePlayerStore } from '~/store/player'
|
|
import { useCardStore } from '~/store/card'
|
|
import { useDataStore } from '~/store/data'
|
|
import type { Track } from '~~/types'
|
|
|
|
const route = useRoute()
|
|
const playerStore = usePlayerStore()
|
|
const cardStore = useCardStore()
|
|
const dataStore = useDataStore()
|
|
|
|
const track = ref<Track | null>(null)
|
|
const loading = ref(true)
|
|
const error = ref<string | null>(null)
|
|
const hasUserInteracted = ref(false)
|
|
const audioElement = ref<HTMLAudioElement | null>(null)
|
|
|
|
// Récupérer les données de la piste
|
|
const fetchTrack = async () => {
|
|
try {
|
|
loading.value = true
|
|
error.value = null
|
|
|
|
// S'assurer que les données sont chargées
|
|
if (!dataStore.isLoaded) {
|
|
await dataStore.loadData()
|
|
}
|
|
|
|
// Récupérer la piste par son ID
|
|
const trackId = Array.isArray(route.params.id) ? route.params.id[0] : route.params.id || ''
|
|
const foundTrack = dataStore.getTrackById(trackId)
|
|
|
|
if (foundTrack) {
|
|
track.value = foundTrack
|
|
// Marquer la carte comme révélée
|
|
if (foundTrack.id) {
|
|
cardStore.revealCard(Number(foundTrack.id))
|
|
}
|
|
} else {
|
|
error.value = 'Carte non trouvée'
|
|
}
|
|
} catch (err) {
|
|
console.error('Erreur lors du chargement de la piste:', err)
|
|
error.value = 'Une erreur est survenue lors du chargement de la piste'
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
// Gérer la première interaction utilisateur
|
|
const handleFirstInteraction = () => {
|
|
if (!hasUserInteracted.value) {
|
|
hasUserInteracted.value = true
|
|
if (track.value) {
|
|
playerStore.playTrack(track.value)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Configurer les écouteurs d'événements pour la première interaction
|
|
const setupInteractionListeners = () => {
|
|
const events: (keyof WindowEventMap)[] = ['click', 'touchstart', 'keydown']
|
|
const handleInteraction = () => {
|
|
handleFirstInteraction()
|
|
events.forEach(event => {
|
|
window.removeEventListener(event, handleInteraction as EventListener)
|
|
})
|
|
}
|
|
|
|
events.forEach(event => {
|
|
window.addEventListener(event, handleInteraction as EventListener, { once: true } as AddEventListenerOptions)
|
|
})
|
|
|
|
return () => {
|
|
events.forEach(event => {
|
|
window.removeEventListener(event, handleInteraction as EventListener)
|
|
})
|
|
}
|
|
}
|
|
|
|
// Lire la piste
|
|
const playTrack = () => {
|
|
if (track.value) {
|
|
playerStore.playTrack(track.value)
|
|
}
|
|
}
|
|
|
|
// Charger les données au montage du composant
|
|
onMounted(async () => {
|
|
await fetchTrack()
|
|
|
|
// Configurer les écouteurs d'événements pour la première interaction
|
|
const cleanup = setupInteractionListeners()
|
|
|
|
// Nettoyer les écouteurs lors du démontage du composant
|
|
onBeforeUnmount(() => {
|
|
cleanup()
|
|
})
|
|
})
|
|
</script>
|
|
|
|
<style scoped>
|
|
.card-page {
|
|
min-height: 100vh;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 1rem;
|
|
background: linear-gradient(135deg, #1a202c 0%, #2d3748 100%);
|
|
}
|
|
|
|
.loading {
|
|
position: absolute;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.spinner {
|
|
width: 40px;
|
|
height: 40px;
|
|
border: 4px solid rgba(255, 255, 255, 0.1);
|
|
border-radius: 50%;
|
|
border-top-color: #4299e1;
|
|
animation: spin 1s ease-in-out infinite;
|
|
}
|
|
|
|
@keyframes spin {
|
|
to {
|
|
transform: rotate(360deg);
|
|
}
|
|
}
|
|
|
|
.card-container {
|
|
perspective: 1000px;
|
|
cursor: pointer;
|
|
transition: transform 0.3s ease;
|
|
}
|
|
|
|
.card-container:hover {
|
|
transform: scale(1.02);
|
|
}
|
|
|
|
.card-item {
|
|
transform-style: preserve-3d;
|
|
transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
|
|
animation: cardAppear 0.8s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
|
|
opacity: 0;
|
|
transform: translateY(20px) rotateY(10deg);
|
|
}
|
|
|
|
@keyframes cardAppear {
|
|
0% {
|
|
opacity: 0;
|
|
transform: translateY(20px) rotateY(10deg);
|
|
}
|
|
|
|
100% {
|
|
opacity: 1;
|
|
transform: translateY(0) rotateY(0);
|
|
}
|
|
}
|
|
|
|
.error-message {
|
|
color: #feb2b2;
|
|
background-color: #742a2a;
|
|
padding: 1rem 2rem;
|
|
border-radius: 0.5rem;
|
|
font-weight: 500;
|
|
}
|
|
|
|
/* Animation de transition */
|
|
.card-fade-enter-active,
|
|
.card-fade-leave-active {
|
|
transition: opacity 0.3s ease;
|
|
}
|
|
|
|
.card-fade-enter-from,
|
|
.card-fade-leave-to {
|
|
opacity: 0;
|
|
}
|
|
</style> |