bucket + card sharer
This commit is contained in:
202
app/pages/card/[id].vue
Normal file
202
app/pages/card/[id].vue
Normal file
@@ -0,0 +1,202 @@
|
||||
<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/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>
|
||||
Reference in New Issue
Block a user