Files
LeDiscord/frontend/src/views/AlbumDetail.vue
2026-01-25 18:08:38 +01:00

862 lines
30 KiB
Vue

<template>
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<!-- Loading state -->
<div v-if="loading" class="text-center py-12">
<LoadingLogo size="large" text="Chargement de l'album..." />
</div>
<!-- Album not found -->
<div v-else-if="!album" class="text-center py-12">
<h1 class="text-2xl font-bold text-gray-900 mb-4">Album non trouvé</h1>
<p class="text-gray-600 mb-6">L'album que vous recherchez n'existe pas ou a été supprimé.</p>
<router-link to="/albums" class="btn-primary">
Retour aux albums
</router-link>
</div>
<!-- Album details -->
<div v-else>
<!-- Header -->
<div class="mb-8">
<div class="flex flex-col sm:flex-row items-start sm:items-center justify-between mb-4 space-y-4 sm:space-y-0">
<router-link to="/albums" class="text-primary-600 hover:text-primary-700 flex items-center">
<ArrowLeft class="w-4 h-4 mr-2" />
Retour aux albums
</router-link>
<div v-if="canEdit" class="flex flex-wrap gap-2 w-full sm:w-auto">
<button
@click="showEditModal = true"
class="flex-1 sm:flex-none btn-secondary justify-center"
>
<Edit class="w-4 h-4 mr-2" />
Modifier
</button>
<button
@click="showUploadModal = true"
class="flex-1 sm:flex-none btn-primary justify-center"
>
<Upload class="w-4 h-4 mr-2" />
Ajouter
</button>
<button
@click="deleteAlbum"
class="flex-1 sm:flex-none btn-secondary text-accent-600 hover:text-accent-700 hover:border-accent-300 justify-center"
>
<Trash2 class="w-4 h-4 mr-2" />
Supprimer
</button>
</div>
</div>
<div class="flex flex-col md:flex-row items-start space-y-6 md:space-y-0 md:space-x-6">
<!-- Cover Image -->
<div class="w-full md:w-64 h-48 bg-gradient-to-br from-primary-400 to-primary-600 rounded-xl flex items-center justify-center flex-shrink-0">
<Image v-if="!album.cover_image" class="w-16 h-16 text-white" />
<img
v-else
:src="getMediaUrl(album.cover_image)"
:alt="album.title"
class="w-full h-full object-cover rounded-xl"
>
</div>
<!-- Album Info -->
<div class="flex-1">
<h1 class="text-4xl font-bold text-gray-900 mb-4">{{ album.title }}</h1>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="space-y-3">
<div v-if="album.description" class="text-gray-700">
{{ album.description }}
</div>
<div class="flex items-center text-gray-600">
<User class="w-5 h-5 mr-3" />
<span>Créé par {{ album.creator_name }}</span>
</div>
<div class="flex items-center text-gray-600">
<Calendar class="w-5 h-5 mr-3" />
<span>{{ formatDate(album.created_at) }}</span>
</div>
<div v-if="album.event_title" class="flex items-center text-primary-600">
<Calendar class="w-5 h-5 mr-3" />
<router-link :to="`/events/${album.event_id}`" class="hover:underline">
{{ album.event_title }}
</router-link>
</div>
</div>
<!-- Stats -->
<div class="bg-gray-50 rounded-lg p-4">
<h3 class="font-semibold text-gray-900 mb-3">Statistiques</h3>
<div class="grid grid-cols-2 gap-4 text-sm">
<div class="text-center">
<div class="text-2xl font-bold text-primary-600">{{ album.media_count || 0 }}</div>
<div class="text-gray-600">Médias</div>
</div>
<div class="text-center">
<div class="text-2xl font-bold text-success-600">{{ formatBytes(totalSize) }}</div>
<div class="text-gray-600">Taille totale</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Top Media Section -->
<div v-if="album.top_media && album.top_media.length > 0" class="card p-6 mb-8">
<h2 class="text-xl font-semibold text-gray-900 mb-4">Top Media</h2>
<p class="text-gray-600 mb-4">Les médias les plus appréciés de cet album</p>
<div class="grid grid-cols-1 md:grid-cols-4 lg:grid-cols-6 gap-3">
<div
v-for="media in album.top_media"
:key="media.id"
class="group relative aspect-square bg-gray-100 rounded-lg overflow-hidden cursor-pointer"
@click="openMediaViewer(media)"
>
<img
v-if="media.media_type === 'image'"
:src="getMediaUrl(media.thumbnail_path) || getMediaUrl(media.file_path)"
:alt="media.caption || 'Image'"
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-200"
>
<video
v-else
:src="getMediaUrl(media.file_path)"
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-200"
/>
<!-- Media Type Badge -->
<div class="absolute top-1 left-1 bg-black bg-opacity-70 text-white text-xs px-1 py-0.5 rounded">
{{ media.media_type === 'image' ? '📷' : '🎥' }}
</div>
<!-- Likes Badge -->
<div class="absolute top-1 right-1 bg-primary-600 text-white text-xs px-1 py-0.5 rounded flex items-center">
<Heart class="w-2 h-2 mr-1" />
{{ media.likes_count }}
</div>
<!-- Caption -->
<div v-if="media.caption" class="absolute bottom-0 left-0 right-0 bg-black bg-opacity-70 text-white text-xs p-1 truncate">
{{ media.caption }}
</div>
</div>
</div>
</div>
<!-- Media Gallery -->
<div class="card p-6 mb-8">
<div class="flex items-center justify-between mb-6">
<h2 class="text-xl font-semibold text-gray-900">Galerie</h2>
<div class="flex items-center space-x-2">
<button
@click="viewMode = 'grid'"
class="p-2 rounded-lg transition-colors"
:class="viewMode === 'grid' ? 'bg-primary-100 text-primary-600' : 'text-gray-400 hover:text-gray-600'"
>
<Grid class="w-5 h-5" />
</button>
<button
@click="viewMode = 'list'"
class="p-2 rounded-lg transition-colors"
:class="viewMode === 'list' ? 'bg-primary-100 text-primary-600' : 'text-gray-400 hover:text-gray-600'"
>
<List class="w-5 h-5" />
</button>
</div>
</div>
<div v-if="album.media.length === 0" class="text-center py-12 text-gray-500">
<Image class="w-16 h-16 mx-auto mb-4 text-gray-300" />
<h3 class="text-lg font-medium mb-2">Aucun média</h3>
<p>Cet album ne contient pas encore de photos ou vidéos</p>
</div>
<!-- Grid View -->
<div v-else-if="viewMode === 'grid'" class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
<div
v-for="media in album.media"
:key="media.id"
class="group relative aspect-square bg-gray-100 rounded-lg overflow-hidden cursor-pointer"
@click="openMediaViewer(media)"
>
<img
v-if="media.media_type === 'image'"
:src="getMediaUrl(media.thumbnail_path) || getMediaUrl(media.file_path)"
:alt="media.caption || 'Image'"
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-200"
>
<video
v-else
:src="getMediaUrl(media.file_path)"
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-200"
/>
<!-- Media Type Badge -->
<div class="absolute top-2 left-2 bg-black bg-opacity-70 text-white text-xs px-2 py-1 rounded">
{{ media.media_type === 'image' ? '📷' : '🎥' }}
</div>
<!-- Caption -->
<div v-if="media.caption" class="absolute bottom-0 left-0 right-0 bg-black bg-opacity-70 text-white text-xs p-2">
{{ media.caption }}
</div>
<!-- Actions -->
<div v-if="canEdit" class="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity">
<button
@click.stop="deleteMedia(media.id)"
class="bg-red-500 text-white rounded-full w-6 h-6 flex items-center justify-center hover:bg-red-600"
>
<X class="w-4 h-4" />
</button>
</div>
<!-- Like Button -->
<div class="absolute bottom-2 right-2 opacity-100 group-hover:opacity-100 transition-opacity">
<button
@click.stop="toggleMediaLike(media)"
class="flex items-center space-x-2 px-3 py-2 rounded-full text-sm transition-colors shadow-lg"
:class="media.is_liked ? 'bg-red-500 text-white hover:bg-red-600' : 'bg-black bg-opacity-80 text-white hover:bg-opacity-90'"
>
<Heart :class="media.is_liked ? 'fill-current' : ''" class="w-4 h-4" />
<span class="font-medium">{{ media.likes_count }}</span>
</button>
</div>
</div>
</div>
<!-- List View -->
<div v-else class="space-y-3">
<div
v-for="media in album.media"
:key="media.id"
class="flex items-center space-x-4 p-3 bg-gray-50 rounded-lg hover:bg-gray-100 transition-colors"
>
<div class="w-16 h-16 bg-gray-200 rounded overflow-hidden">
<img
v-if="media.media_type === 'image'"
:src="getMediaUrl(media.thumbnail_path) || getMediaUrl(media.file_path)"
:alt="media.caption || 'Image'"
class="w-full h-full object-cover"
>
<video
v-else
:src="getMediaUrl(media.file_path)"
class="w-full h-full object-cover"
/>
</div>
<div class="flex-1">
<p class="font-medium text-gray-900">{{ media.caption || 'Sans titre' }}</p>
<p class="text-sm text-gray-600">{{ formatBytes(media.file_size) }} {{ media.media_type === 'image' ? 'Image' : 'Vidéo' }}</p>
<p class="text-xs text-gray-500">{{ formatDate(media.created_at) }}</p>
</div>
<div class="flex items-center space-x-2">
<button
@click="openMediaViewer(media)"
class="p-2 text-gray-400 hover:text-primary-600 transition-colors"
>
<Eye class="w-4 h-4" />
</button>
<button
@click="toggleMediaLike(media)"
class="p-2 text-gray-400 hover:text-red-600 transition-colors"
:class="{ 'text-red-600': media.is_liked }"
>
<Heart :class="media.is_liked ? 'fill-current' : ''" class="w-5 h-5" />
<span class="ml-2 text-sm font-medium">{{ media.likes_count }}</span>
</button>
<button
v-if="canEdit"
@click="deleteMedia(media.id)"
class="p-2 text-gray-400 hover:text-accent-600 transition-colors"
>
<Trash2 class="w-4 h-4" />
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Edit Album Modal -->
<transition
enter-active-class="transition ease-out duration-200"
enter-from-class="opacity-0"
enter-to-class="opacity-100"
leave-active-class="transition ease-in duration-150"
leave-from-class="opacity-100"
leave-to-class="opacity-0"
>
<div
v-if="showEditModal"
class="fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center p-4"
>
<div class="bg-white rounded-xl max-w-md w-full p-6">
<h2 class="text-xl font-semibold mb-4">Modifier l'album</h2>
<form @submit.prevent="updateAlbum" class="space-y-4">
<div>
<label class="label">Titre</label>
<input
v-model="editForm.title"
type="text"
required
class="input"
>
</div>
<div>
<label class="label">Description</label>
<textarea
v-model="editForm.description"
rows="3"
class="input"
/>
</div>
<div class="flex gap-3 pt-4">
<button
type="button"
@click="showEditModal = false"
class="flex-1 btn-secondary"
>
Annuler
</button>
<button
type="submit"
:disabled="updating"
class="flex-1 btn-primary"
>
{{ updating ? 'Mise à jour...' : 'Mettre à jour' }}
</button>
</div>
</form>
</div>
</div>
</transition>
<!-- Upload Media Modal -->
<transition
enter-active-class="transition ease-out duration-200"
enter-from-class="opacity-0"
enter-to-class="opacity-100"
leave-active-class="transition ease-in duration-150"
leave-from-class="opacity-100"
leave-to-class="opacity-0"
>
<div
v-if="showUploadModal"
class="fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center p-4"
>
<div class="bg-white rounded-xl max-w-2xl w-full p-6 max-h-[90vh] overflow-y-auto">
<h2 class="text-xl font-semibold mb-4">Ajouter des médias</h2>
<form @submit.prevent="uploadMedia" class="space-y-4">
<div>
<label class="label">Photos et vidéos</label>
<div class="border-2 border-dashed border-gray-300 rounded-lg p-6 text-center">
<input
ref="mediaInput"
type="file"
accept="image/*,video/*"
multiple
class="hidden"
@change="handleMediaChange"
>
<div v-if="newMedia.length === 0" class="space-y-2">
<Upload class="w-12 h-12 text-gray-400 mx-auto" />
<p class="text-gray-600">Glissez-déposez ou cliquez pour sélectionner</p>
<p class="text-sm text-gray-500">Images et vidéos (max 100MB par fichier)</p>
<button
type="button"
@click="$refs.mediaInput.click()"
class="btn-secondary"
>
Sélectionner des fichiers
</button>
</div>
<div v-else class="space-y-3">
<div class="grid grid-cols-2 md:grid-cols-3 gap-2">
<div
v-for="(media, index) in newMedia"
:key="index"
class="relative aspect-square bg-gray-100 rounded overflow-hidden"
>
<img
v-if="media.type === 'image'"
:src="media.preview"
:alt="media.name"
class="w-full h-full object-cover"
>
<video
v-else
:src="media.preview"
class="w-full h-full object-cover"
/>
<button
@click="removeMedia(index)"
class="absolute top-1 right-1 bg-red-500 text-white rounded-full w-6 h-6 flex items-center justify-center hover:bg-red-600"
>
<X class="w-4 h-4" />
</button>
<div class="absolute bottom-1 left-1 bg-black bg-opacity-70 text-white text-xs px-1 py-0.5 rounded">
{{ media.type === 'image' ? '📷' : '🎥' }}
</div>
</div>
</div>
<button
type="button"
@click="$refs.mediaInput.click()"
class="btn-secondary text-sm"
>
Ajouter plus de fichiers
</button>
</div>
</div>
</div>
<div class="flex gap-3 pt-4">
<button
type="button"
@click="showUploadModal = false"
class="flex-1 btn-secondary"
>
Annuler
</button>
<button
type="submit"
:disabled="uploading || newMedia.length === 0"
class="flex-1 btn-primary"
>
{{ uploading ? 'Upload...' : 'Ajouter les médias' }}
</button>
</div>
</form>
</div>
</div>
</transition>
<!-- Media Viewer Modal -->
<transition
enter-active-class="transition ease-out duration-200"
enter-from-class="opacity-0"
enter-to-class="opacity-100"
leave-active-class="transition ease-in duration-150"
leave-from-class="opacity-100"
leave-to-class="opacity-0"
>
<div
v-if="selectedMedia"
class="fixed inset-0 bg-black bg-opacity-90 z-50 flex items-center justify-center p-4"
>
<div class="relative max-w-7xl max-h-[95vh] w-full h-full">
<!-- Close Button -->
<button
@click="closeMediaViewer"
class="absolute top-6 right-6 z-10 bg-black bg-opacity-70 text-white rounded-full w-12 h-12 flex items-center justify-center hover:bg-opacity-90 transition-colors shadow-lg"
>
<X class="w-6 h-6" />
</button>
<!-- Navigation Buttons -->
<button
v-if="album.media.length > 1"
@click="previousMedia"
class="absolute left-6 top-1/2 transform -translate-y-1/2 z-10 bg-black bg-opacity-70 text-white rounded-full w-12 h-12 flex items-center justify-center hover:bg-opacity-90 transition-colors shadow-lg"
>
<ChevronLeft class="w-6 h-6" />
</button>
<button
v-if="album.media.length > 1"
@click="nextMedia"
class="absolute right-6 top-1/2 transform -translate-y-1/2 z-10 bg-black bg-opacity-70 text-white rounded-full w-12 h-12 flex items-center justify-center hover:bg-opacity-90 transition-colors shadow-lg"
>
<ChevronRight class="w-6 h-6" />
</button>
<!-- Position Indicator -->
<div v-if="album.media.length > 1" class="absolute top-6 left-1/2 transform -translate-x-1/2 z-10 bg-black bg-opacity-70 text-white px-4 py-2 rounded-full text-sm font-medium backdrop-blur-sm">
{{ getCurrentMediaIndex() + 1 }} / {{ album.media.length }}
</div>
<!-- Media Content -->
<div class="flex flex-col items-center h-full">
<!-- Image or Video -->
<div class="w-full h-full flex items-center justify-center">
<img
v-if="selectedMedia.media_type === 'image'"
:src="getMediaUrl(selectedMedia.thumbnail_path) || getMediaUrl(selectedMedia.file_path)"
:alt="selectedMedia.caption || 'Image'"
class="w-auto h-auto max-w-none max-h-none object-contain rounded-lg shadow-2xl"
:style="getOptimalMediaSize()"
>
<video
v-else
:src="getMediaUrl(selectedMedia.file_path)"
controls
class="w-auto h-auto max-w-none max-h-none rounded-lg shadow-2xl"
:style="getOptimalMediaSize()"
/>
</div>
<!-- Media Info -->
<div class="mt-6 text-center text-white bg-black bg-opacity-50 rounded-xl p-6 backdrop-blur-sm">
<h3 v-if="selectedMedia.caption" class="text-xl font-semibold mb-3 text-white">
{{ selectedMedia.caption }}
</h3>
<div class="flex items-center justify-center space-x-6 text-sm text-gray-200 mb-4">
<span class="flex items-center space-x-2">
<span class="w-2 h-2 bg-blue-400 rounded-full"></span>
<span>{{ formatBytes(selectedMedia.file_size) }}</span>
</span>
<span class="flex items-center space-x-2">
<span class="w-2 h-2 bg-green-400 rounded-full"></span>
<span>{{ selectedMedia.media_type === 'image' ? '📷 Image' : '🎥 Vidéo' }}</span>
</span>
<span class="flex items-center space-x-2">
<span class="w-2 h-2 bg-purple-400 rounded-full"></span>
<span>{{ formatDate(selectedMedia.created_at) }}</span>
</span>
</div>
<!-- Like Button in Viewer -->
<div class="flex items-center justify-center">
<button
@click="toggleMediaLikeFromViewer(selectedMedia)"
class="flex items-center space-x-3 px-6 py-3 rounded-full transition-all duration-300 transform hover:scale-105"
:class="selectedMedia.is_liked ? 'bg-red-500 text-white shadow-lg hover:bg-red-600' : 'bg-white bg-opacity-20 text-white hover:bg-opacity-40 hover:shadow-lg'"
>
<Heart :class="selectedMedia.is_liked ? 'fill-current' : ''" class="w-6 h-6" />
<span class="font-medium text-lg">{{ selectedMedia.likes_count }}</span>
</button>
</div>
</div>
</div>
</div>
</div>
</transition>
</div>
</template>
<script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useAuthStore } from '@/stores/auth'
import { useToast } from 'vue-toastification'
import axios from '@/utils/axios'
import { getMediaUrl } from '@/utils/axios'
import { format, formatDistanceToNow } from 'date-fns'
import { fr } from 'date-fns/locale'
import {
ArrowLeft,
Image,
User,
Calendar,
Edit,
Upload,
Trash2,
Grid,
List,
X,
Eye,
Heart,
ChevronLeft,
ChevronRight
} from 'lucide-vue-next'
import LoadingLogo from '@/components/LoadingLogo.vue'
const route = useRoute()
const router = useRouter()
const authStore = useAuthStore()
const toast = useToast()
const album = ref(null)
const loading = ref(true)
const updating = ref(false)
const uploading = ref(false)
const showEditModal = ref(false)
const showUploadModal = ref(false)
const viewMode = ref('grid')
const selectedMedia = ref(null)
const editForm = ref({
title: '',
description: ''
})
const newMedia = ref([])
const canEdit = computed(() =>
album.value && (album.value.creator_id === authStore.user?.id || authStore.user?.is_admin)
)
const totalSize = computed(() =>
album.value?.media?.reduce((sum, media) => sum + (media.file_size || 0), 0) || 0
)
function formatDate(date) {
return format(new Date(date), 'dd MMMM yyyy', { locale: fr })
}
function formatBytes(bytes) {
if (bytes === 0) return '0 B'
const k = 1024
const sizes = ['B', 'KB', 'MB', 'GB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
}
async function fetchAlbum() {
try {
const response = await axios.get(`/api/albums/${route.params.id}`)
album.value = response.data
// Initialize edit form
editForm.value = {
title: album.value.title,
description: album.value.description || ''
}
} catch (error) {
toast.error('Erreur lors du chargement de l\'album')
console.error('Error fetching album:', error)
} finally {
loading.value = false
}
}
async function updateAlbum() {
updating.value = true
try {
const response = await axios.put(`/api/albums/${album.value.id}`, editForm.value)
album.value = response.data
showEditModal.value = false
toast.success('Album mis à jour')
} catch (error) {
toast.error('Erreur lors de la mise à jour')
}
updating.value = false
}
async function deleteAlbum() {
if (!confirm('Êtes-vous sûr de vouloir supprimer cet album ?')) return
try {
await axios.delete(`/api/albums/${album.value.id}`)
toast.success('Album supprimé')
router.push('/albums')
} catch (error) {
toast.error('Erreur lors de la suppression')
}
}
async function handleMediaChange(event) {
const files = Array.from(event.target.files)
for (const file of files) {
if (!file.type.startsWith('image/') && !file.type.startsWith('video/')) {
toast.error(`${file.name} n'est pas un fichier image ou vidéo valide`)
continue
}
if (file.size > 100 * 1024 * 1024) {
toast.error(`${file.name} est trop volumineux (max 100MB)`)
continue
}
const media = {
file: file,
name: file.name,
type: file.type.startsWith('image/') ? 'image' : 'video',
preview: URL.createObjectURL(file)
}
newMedia.value.push(media)
}
event.target.value = ''
}
function removeMedia(index) {
const media = newMedia.value[index]
if (media.preview && media.preview.startsWith('blob:')) {
URL.revokeObjectURL(media.preview)
}
newMedia.value.splice(index, 1)
}
async function uploadMedia() {
if (newMedia.value.length === 0) return
uploading.value = true
try {
const formData = new FormData()
newMedia.value.forEach(media => {
formData.append('files', media.file)
})
await axios.post(`/api/albums/${album.value.id}/media`, formData, {
headers: { 'Content-Type': 'multipart/form-data' }
})
// Refresh album data
await fetchAlbum()
showUploadModal.value = false
newMedia.value.forEach(media => {
if (media.preview && media.preview.startsWith('blob:')) {
URL.revokeObjectURL(media.preview)
}
})
newMedia.value = []
toast.success('Médias ajoutés avec succès')
} catch (error) {
toast.error('Erreur lors de l\'upload')
}
uploading.value = false
}
async function deleteMedia(mediaId) {
if (!confirm('Êtes-vous sûr de vouloir supprimer ce média ?')) return
try {
await axios.delete(`/api/albums/${album.value.id}/media/${mediaId}`)
await fetchAlbum()
toast.success('Média supprimé')
} catch (error) {
toast.error('Erreur lors de la suppression')
}
}
async function toggleMediaLike(media) {
try {
const response = await axios.post(`/api/albums/${album.value.id}/media/${media.id}/like`)
media.is_liked = response.data.is_liked
media.likes_count = response.data.likes_count
toast.success('Like mis à jour')
} catch (error) {
toast.error('Erreur lors de la mise à jour du like')
}
}
async function toggleMediaLikeFromViewer(media) {
try {
const response = await axios.post(`/api/albums/${album.value.id}/media/${media.id}/like`)
media.is_liked = response.data.is_liked
media.likes_count = response.data.likes_count
toast.success('Like mis à jour')
} catch (error) {
toast.error('Erreur lors de la mise à jour du like')
}
}
function openMediaViewer(media) {
selectedMedia.value = media
// Add keyboard event listeners
document.addEventListener('keydown', handleKeyboardNavigation)
}
function closeMediaViewer() {
selectedMedia.value = null
// Remove keyboard event listeners
document.removeEventListener('keydown', handleKeyboardNavigation)
}
function handleKeyboardNavigation(event) {
if (!selectedMedia.value) return
switch (event.key) {
case 'Escape':
closeMediaViewer()
break
case 'ArrowLeft':
if (album.value.media.length > 1) {
previousMedia()
}
break
case 'ArrowRight':
if (album.value.media.length > 1) {
nextMedia()
}
break
}
}
function previousMedia() {
const currentIndex = album.value.media.findIndex(media => media.id === selectedMedia.value.id)
if (currentIndex > 0) {
selectedMedia.value = album.value.media[currentIndex - 1]
}
}
function nextMedia() {
const currentIndex = album.value.media.findIndex(media => media.id === selectedMedia.value.id)
if (currentIndex < album.value.media.length - 1) {
selectedMedia.value = album.value.media[currentIndex + 1]
}
}
function getCurrentMediaIndex() {
if (!selectedMedia.value || !album.value) return 0
return album.value.media.findIndex(media => media.id === selectedMedia.value.id)
}
function getOptimalMediaSize() {
if (!selectedMedia.value) return {}
// Utilisation MAXIMALE de l'espace disponible
if (selectedMedia.value.media_type === 'image') {
// Images : 100% de l'écran avec juste une petite marge
return {
'max-width': '98vw',
'max-height': '96vh',
'width': 'auto',
'height': 'auto',
'object-fit': 'contain'
}
}
// Vidéos : presque plein écran
return {
'max-width': '96vw',
'max-height': '94vh',
'width': 'auto',
'height': 'auto'
}
}
onMounted(() => {
fetchAlbum()
})
onUnmounted(() => {
// Clean up event listeners
document.removeEventListener('keydown', handleKeyboardNavigation)
})
</script>