Files
social-media/frontend/src/views/creators/AlbumEditor.vue

400 lines
9.8 KiB
Vue

<template>
<div class="album-editor">
<h2 class="text-xl font-semibold mb-4">
{{ t('title') }}
</h2>
<!-- Drop zone with photos -->
<div class="drop-zone"
@dragover.prevent
@drop.prevent="handleDrop"
@click="triggerFileInput">
<!-- Upload prompt -->
<div class="drop-zone-content">
<v-icon size="large">mdi-plus</v-icon>
<span class="text-sm mt-2">{{ t('dropzoneText') }}</span>
</div>
<!-- Hidden file input -->
<input
type="file"
ref="fileInput"
@change="handleFileUpload"
accept="image/*"
multiple
class="hidden"
/>
<!-- Photos grid -->
<draggable
v-model="localImages"
class="photos-grid"
item-key="id"
@end="handleReorder"
>
<template #item="{ element, index }">
<div class="photo-wrapper" @click.stop="toggleMobileControls(index)">
<div class="index-bubble">{{ index + 1 }}</div>
<img :src="element.url" :alt="'Image ' + (index + 1)" />
<!-- Processing spinner overlay -->
<div v-if="element.isProcessing" class="loading-overlay">
<v-progress-circular indeterminate color="primary"></v-progress-circular>
<span class="text-white text-sm mt-2">{{ t('processing') }}</span>
</div>
<!-- Upload spinner overlay -->
<div v-if="element.isUploading" class="loading-overlay uploading">
<v-progress-circular indeterminate color="secondary"></v-progress-circular>
<span class="text-white text-sm mt-2">{{ t('uploading') }}</span>
</div>
<!-- Left arrow -->
<button @click.stop="moveImage(index, 'up')"
class="action-btn left-btn"
:disabled="index === 0"
:title="t('moveLeft')"
:class="{'mobile-active': activePhotoIndex === index}">
<v-icon>mdi-arrow-left</v-icon>
</button>
<!-- Right arrow -->
<button @click.stop="moveImage(index, 'down')"
class="action-btn right-btn"
:disabled="index === localImages.length - 1"
:title="t('moveRight')"
:class="{'mobile-active': activePhotoIndex === index}">
<v-icon>mdi-arrow-right</v-icon>
</button>
<!-- Delete button -->
<button @click.stop="deleteImage(index)"
class="action-btn delete-btn"
:title="t('delete')"
:class="{'mobile-active': activePhotoIndex === index}">
<v-icon>mdi-delete</v-icon>
</button>
</div>
</template>
</draggable>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from "vue";
import { useI18n } from 'vue-i18n';
import draggable from 'vuedraggable';
const props = defineProps({
images: {
type: Array,
required: true
}
});
const emit = defineEmits(['update:images']);
const { t } = useI18n();
const fileInput = ref(null);
const localImages = ref([]);
const activePhotoIndex = ref(null); // Track which photo is currently active for mobile
onMounted(() => {
// Initialize local images with IDs and states
localImages.value = props.images.map((url, index) => ({
id: index,
url: url,
isProcessing: false,
isUploading: false,
file: null // Store the actual file for upload
}));
// Add event listener to close active controls when clicking outside
document.addEventListener('click', closeActiveControls);
});
// Close active controls when component is unmounted
onUnmounted(() => {
document.removeEventListener('click', closeActiveControls);
});
// Function to handle mobile control visibility
function toggleMobileControls(index) {
// If clicking the same photo, toggle the controls
if (activePhotoIndex.value === index) {
activePhotoIndex.value = null;
} else {
// Otherwise, set this photo as active
activePhotoIndex.value = index;
}
}
// Close active controls when clicking outside
function closeActiveControls() {
activePhotoIndex.value = null;
}
// Trigger file input click
function triggerFileInput() {
fileInput.value.click();
}
// Add drop handler
function handleDrop(event) {
const files = Array.from(event.dataTransfer.files);
handleFiles(files);
}
// Extract file handling logic
function handleFiles(files) {
for (const file of files) {
if (file.type.startsWith('image/')) {
try {
const reader = new FileReader();
// Create a temporary image object with processing state
const tempImage = {
id: Date.now() + Math.random(),
url: '',
isProcessing: true,
isUploading: false,
file: file // Store the file for later upload
};
localImages.value.push(tempImage);
reader.onload = (e) => {
const index = localImages.value.findIndex(img => img.id === tempImage.id);
if (index !== -1) {
localImages.value[index] = {
...tempImage,
url: e.target.result,
isProcessing: false
};
emit('update:images', localImages.value.map(img => img.url));
}
};
reader.readAsDataURL(file);
} catch (error) {
console.error('Error processing image:', error);
}
}
}
}
// Update file upload handler to use common function
function handleFileUpload(event) {
const files = Array.from(event.target.files);
handleFiles(files);
event.target.value = '';
}
// Delete an image
function deleteImage(index) {
localImages.value.splice(index, 1);
activePhotoIndex.value = null; // Reset active photo
emit('update:images', localImages.value.map(img => img.url));
}
// Handle reorder after drag and drop
function handleReorder() {
activePhotoIndex.value = null; // Reset active photo
emit('update:images', localImages.value.map(img => img.url));
}
// Add back the moveImage function
function moveImage(index, direction) {
const newIndex = direction === 'up' ? index - 1 : index + 1;
if (newIndex >= 0 && newIndex < localImages.value.length) {
const temp = localImages.value[index];
localImages.value[index] = localImages.value[newIndex];
localImages.value[newIndex] = temp;
activePhotoIndex.value = newIndex; // Keep the moved image active
emit('update:images', localImages.value.map(img => img.url));
}
}
</script>
<style scoped>
.album-editor {
@apply w-full;
}
.drop-zone {
@apply w-full;
@apply min-h-[200px];
@apply border-2;
@apply border-dashed;
@apply border-gray-300 hover:border-gray-500;
@apply rounded-lg;
@apply p-4;
@apply relative;
@apply transition-colors;
@apply duration-200;
@apply overflow-visible;
@apply bg-hSurface;
}
.drop-zone-content {
@apply flex;
@apply flex-col;
@apply items-center;
@apply text-gray-500;
@apply mb-8;
@apply relative;
@apply z-10;
}
.photos-grid {
@apply grid;
@apply grid-cols-2;
@apply sm:grid-cols-3;
@apply md:grid-cols-4;
@apply lg:grid-cols-5;
@apply gap-4;
@apply w-full;
@apply pb-1;
}
.photo-wrapper {
@apply relative;
@apply aspect-square;
@apply rounded-lg;
@apply overflow-hidden;
@apply bg-gray-100;
@apply cursor-pointer;
}
.photo-wrapper img {
@apply w-full;
@apply h-full;
@apply object-cover;
}
.action-btn {
@apply absolute;
@apply bg-black;
@apply bg-opacity-50;
@apply text-white;
@apply rounded-full;
@apply p-1;
@apply flex;
@apply items-center;
@apply justify-center;
@apply transition-all;
@apply duration-200;
@apply opacity-0;
@apply z-10;
}
/* Show buttons on hover for desktop */
@media (hover: hover) {
.photo-wrapper:hover .action-btn {
@apply opacity-100;
}
}
/* For mobile, show buttons when photo is active */
.action-btn.mobile-active {
@apply opacity-100;
}
.action-btn:hover:not(:disabled) {
@apply bg-opacity-75;
@apply scale-110;
}
.action-btn:disabled {
@apply opacity-30;
@apply cursor-not-allowed;
@apply bg-gray-500;
@apply scale-90;
}
.left-btn {
@apply top-1/2;
@apply -translate-y-1/2;
@apply left-2;
}
.right-btn {
@apply top-1/2;
@apply -translate-y-1/2;
@apply right-2;
}
.delete-btn {
@apply top-2;
@apply right-2;
@apply bg-red-500;
@apply bg-opacity-50;
}
.delete-btn:hover {
@apply bg-opacity-75;
}
.index-bubble {
@apply absolute;
@apply top-2;
@apply left-2;
@apply bg-black;
@apply bg-opacity-50;
@apply text-white;
@apply text-xs;
@apply font-medium;
@apply rounded-full;
@apply w-6;
@apply h-6;
@apply flex;
@apply items-center;
@apply justify-center;
@apply z-10;
}
.loading-overlay {
@apply absolute;
@apply inset-0;
@apply flex;
@apply flex-col;
@apply items-center;
@apply justify-center;
@apply bg-black;
@apply bg-opacity-50;
@apply z-20;
}
.loading-overlay.uploading {
@apply bg-opacity-75;
}
</style>
<i18n>
{
"en": {
"title": "Album",
"dropzoneText": "Drop a photo here to add it to your album",
"processing": "Processing...",
"uploading": "Uploading...",
"moveLeft": "Move Left",
"moveRight": "Move Right",
"delete": "Delete"
},
"fr": {
"title": "Album",
"dropzoneText": "Déposez une photo ici pour l'ajouter à l'album",
"processing": "Traitement en cours...",
"uploading": "Téléchargement...",
"moveLeft": "Déplacer à gauche",
"moveRight": "Déplacer à droite",
"delete": "Supprimer"
},
"es": {
"title": "Album",
"dropzoneText": "Suelta una foto aquí para añadirla al álbum",
"processing": "Procesando...",
"uploading": "Subiendo...",
"moveLeft": "Mover a la izquierda",
"moveRight": "Mover a la derecha",
"delete": "Eliminar"
}
}
</i18n>