Adds PhotoAlbum, CreatorHome, AboutCreator.
This commit is contained in:
262
frontend/src/views/creators/AlbumEditor.vue
Normal file
262
frontend/src/views/creators/AlbumEditor.vue
Normal file
@@ -0,0 +1,262 @@
|
||||
<template>
|
||||
<div class="album-editor">
|
||||
<h2 class="text-xl font-semibold mb-4">{{ t('creator.sections.album.title') }}</h2>
|
||||
|
||||
<div class="image-grid">
|
||||
<!-- Upload button -->
|
||||
<div class="image-wrapper upload-wrapper" @click="triggerFileInput">
|
||||
<input
|
||||
type="file"
|
||||
ref="fileInput"
|
||||
@change="handleFileUpload"
|
||||
accept="image/*"
|
||||
multiple
|
||||
class="hidden"
|
||||
/>
|
||||
<div class="upload-content">
|
||||
<v-icon size="large">mdi-plus</v-icon>
|
||||
<span class="text-sm mt-2">{{ t('upload') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Draggable images -->
|
||||
<draggable
|
||||
v-model="localImages"
|
||||
class="image-grid"
|
||||
item-key="id"
|
||||
@end="handleReorder"
|
||||
>
|
||||
<template #item="{ element, index }">
|
||||
<div class="image-wrapper">
|
||||
<img :src="element.url" :alt="'Image ' + (index + 1)" />
|
||||
<div class="image-actions">
|
||||
<button @click="deleteImage(index)" class="action-btn delete-btn" :title="t('delete')">
|
||||
<v-icon>mdi-delete</v-icon>
|
||||
</button>
|
||||
<button @click="moveImage(index, 'up')"
|
||||
class="action-btn move-btn"
|
||||
:disabled="index === 0"
|
||||
:title="t('moveUp')">
|
||||
<v-icon>mdi-arrow-up</v-icon>
|
||||
</button>
|
||||
<button @click="moveImage(index, 'down')"
|
||||
class="action-btn move-btn"
|
||||
:disabled="index === localImages.length - 1"
|
||||
:title="t('moveDown')">
|
||||
<v-icon>mdi-arrow-down</v-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } 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);
|
||||
|
||||
// Local copy of images with IDs for drag and drop
|
||||
const localImages = ref([]);
|
||||
|
||||
onMounted(() => {
|
||||
// Initialize local images with IDs
|
||||
localImages.value = props.images.map((url, index) => ({
|
||||
id: index,
|
||||
url: url
|
||||
}));
|
||||
});
|
||||
|
||||
// Trigger file input click
|
||||
function triggerFileInput() {
|
||||
fileInput.value.click();
|
||||
}
|
||||
|
||||
// Handle file upload
|
||||
async function handleFileUpload(event) {
|
||||
const files = Array.from(event.target.files);
|
||||
|
||||
for (const file of files) {
|
||||
if (file.type.startsWith('image/')) {
|
||||
try {
|
||||
// Create a data URL for preview
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
const newImage = {
|
||||
id: Date.now() + Math.random(), // Unique ID
|
||||
url: e.target.result
|
||||
};
|
||||
localImages.value.push(newImage);
|
||||
emit('update:images', localImages.value.map(img => img.url));
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
} catch (error) {
|
||||
console.error('Error uploading image:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reset file input
|
||||
event.target.value = '';
|
||||
}
|
||||
|
||||
// Delete an image
|
||||
function deleteImage(index) {
|
||||
localImages.value.splice(index, 1);
|
||||
emit('update:images', localImages.value.map(img => img.url));
|
||||
}
|
||||
|
||||
// Move image up or down
|
||||
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;
|
||||
emit('update:images', localImages.value.map(img => img.url));
|
||||
}
|
||||
}
|
||||
|
||||
// Handle reorder after drag and drop
|
||||
function handleReorder() {
|
||||
emit('update:images', localImages.value.map(img => img.url));
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.album-editor {
|
||||
@apply w-full;
|
||||
}
|
||||
|
||||
.image-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 0.5rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.image-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
aspect-ratio: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.upload-wrapper {
|
||||
border: 2px dashed #ccc;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.upload-wrapper:hover {
|
||||
border-color: #666;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.upload-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.image-actions {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
gap: 0.25rem;
|
||||
padding: 0.25rem;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.image-wrapper:hover .image-actions {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
padding: 0.25rem;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.action-btn:hover {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.action-btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Responsive adjustments */
|
||||
@media (min-width: 768px) {
|
||||
.image-grid {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.image-grid {
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.image-grid {
|
||||
gap: 0.25rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<i18n>
|
||||
{
|
||||
"en": {
|
||||
"upload": "Upload Photos",
|
||||
"delete": "Delete",
|
||||
"moveUp": "Move Up",
|
||||
"moveDown": "Move Down"
|
||||
},
|
||||
"fr": {
|
||||
"upload": "Télécharger des photos",
|
||||
"delete": "Supprimer",
|
||||
"moveUp": "Déplacer vers le haut",
|
||||
"moveDown": "Déplacer vers le bas"
|
||||
},
|
||||
"es": {
|
||||
"upload": "Subir fotos",
|
||||
"delete": "Eliminar",
|
||||
"moveUp": "Mover arriba",
|
||||
"moveDown": "Mover abajo"
|
||||
}
|
||||
}
|
||||
</i18n>
|
||||
Reference in New Issue
Block a user