fix(album): removing last album image was not working

This commit is contained in:
2025-06-02 12:35:44 -04:00
parent a08b384495
commit 6ae6db0c1d
7 changed files with 369 additions and 262 deletions

14
frontend/.prettierrc Normal file
View File

@@ -0,0 +1,14 @@
{
"useTabs": false,
"tabWidth": 4,
"printWidth": 120,
"semi": true,
"singleQuote": true,
"singleAttributePerLine": true,
"trailingComma": "es5",
"bracketSpacing": true,
"arrowParens": "avoid",
"htmlWhitespaceSensitivity": "ignore",
"vueIndentScriptAndStyle": true,
"endOfLine": "lf"
}

View File

@@ -32,10 +32,13 @@
"devDependencies": {
"@types/webpack-env": "^1.18.8",
"@vitejs/plugin-vue": "^5.0.3",
"@vue/eslint-config-prettier": "^10.2.0",
"autoprefixer": "^10.4.21",
"eslint": "^8.57.0",
"eslint-plugin-tailwindcss": "^3.18.0",
"eslint-plugin-vue": "^9.22.0",
"postcss": "^8.5.3",
"prettier": "^3.5.3",
"tailwindcss": "^3.4.17",
"vite": "^6.3.1"
}
@@ -975,6 +978,18 @@
"node": ">=14"
}
},
"node_modules/@pkgr/core": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.4.tgz",
"integrity": "sha512-ROFF39F6ZrnzSUEmQQZUar0Jt4xVoP9WnDRdWwF4NNcXs3xBTLgBUDoOwW141y1jP+S8nahIbdxbFC7IShw9Iw==",
"dev": true,
"engines": {
"node": "^12.20.0 || ^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/pkgr"
}
},
"node_modules/@rollup/pluginutils": {
"version": "5.1.4",
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.4.tgz",
@@ -1554,6 +1569,20 @@
"rfdc": "^1.4.1"
}
},
"node_modules/@vue/eslint-config-prettier": {
"version": "10.2.0",
"resolved": "https://registry.npmjs.org/@vue/eslint-config-prettier/-/eslint-config-prettier-10.2.0.tgz",
"integrity": "sha512-GL3YBLwv/+b86yHcNNfPJxOTtVFJ4Mbc9UU3zR+KVoG7SwGTjPT+32fXamscNumElhcpXW3mT0DgzS9w32S7Bw==",
"dev": true,
"dependencies": {
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-prettier": "^5.2.2"
},
"peerDependencies": {
"eslint": ">= 8.21.0",
"prettier": ">= 3.0.0"
}
},
"node_modules/@vue/reactivity": {
"version": "3.5.14",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.14.tgz",
@@ -2460,6 +2489,67 @@
"url": "https://opencollective.com/eslint"
}
},
"node_modules/eslint-config-prettier": {
"version": "10.1.5",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.5.tgz",
"integrity": "sha512-zc1UmCpNltmVY34vuLRV61r1K27sWuX39E+uyUnY8xS2Bex88VV9cugG+UZbRSRGtGyFboj+D8JODyme1plMpw==",
"dev": true,
"bin": {
"eslint-config-prettier": "bin/cli.js"
},
"funding": {
"url": "https://opencollective.com/eslint-config-prettier"
},
"peerDependencies": {
"eslint": ">=7.0.0"
}
},
"node_modules/eslint-plugin-prettier": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.4.0.tgz",
"integrity": "sha512-BvQOvUhkVQM1i63iMETK9Hjud9QhqBnbtT1Zc642p9ynzBuCe5pybkOnvqZIBypXmMlsGcnU4HZ8sCTPfpAexA==",
"dev": true,
"dependencies": {
"prettier-linter-helpers": "^1.0.0",
"synckit": "^0.11.0"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/eslint-plugin-prettier"
},
"peerDependencies": {
"@types/eslint": ">=8.0.0",
"eslint": ">=8.0.0",
"eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0",
"prettier": ">=3.0.0"
},
"peerDependenciesMeta": {
"@types/eslint": {
"optional": true
},
"eslint-config-prettier": {
"optional": true
}
}
},
"node_modules/eslint-plugin-tailwindcss": {
"version": "3.18.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-tailwindcss/-/eslint-plugin-tailwindcss-3.18.0.tgz",
"integrity": "sha512-PQDU4ZMzFH0eb2DrfHPpbgo87Zgg2EXSMOj1NSfzdZm+aJzpuwGerfowMIaVehSREEa0idbf/eoNYAOHSJoDAQ==",
"dev": true,
"dependencies": {
"fast-glob": "^3.2.5",
"postcss": "^8.4.4"
},
"engines": {
"node": ">=18.12.0"
},
"peerDependencies": {
"tailwindcss": "^3.4.0"
}
},
"node_modules/eslint-plugin-vue": {
"version": "9.33.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.33.0.tgz",
@@ -2617,6 +2707,12 @@
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
},
"node_modules/fast-diff": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz",
"integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==",
"dev": true
},
"node_modules/fast-glob": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
@@ -4090,6 +4186,33 @@
"node": ">= 0.8.0"
}
},
"node_modules/prettier": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz",
"integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==",
"dev": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/prettier-linter-helpers": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
"integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
"dev": true,
"dependencies": {
"fast-diff": "^1.1.2"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/property-expr": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz",
@@ -4626,6 +4749,21 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/synckit": {
"version": "0.11.6",
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.6.tgz",
"integrity": "sha512-2pR2ubZSV64f/vqm9eLPz/KOvR9Dm+Co/5ChLgeHl0yEDRc6h5hXHoxEQH8Y5Ljycozd3p1k5TTSVdzYGkPvLw==",
"dev": true,
"dependencies": {
"@pkgr/core": "^0.2.4"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/synckit"
}
},
"node_modules/tailwindcss": {
"version": "3.4.17",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz",

View File

@@ -33,10 +33,13 @@
"devDependencies": {
"@types/webpack-env": "^1.18.8",
"@vitejs/plugin-vue": "^5.0.3",
"@vue/eslint-config-prettier": "^10.2.0",
"autoprefixer": "^10.4.21",
"eslint": "^8.57.0",
"eslint-plugin-tailwindcss": "^3.18.0",
"eslint-plugin-vue": "^9.22.0",
"postcss": "^8.5.3",
"prettier": "^3.5.3",
"tailwindcss": "^3.4.17",
"vite": "^6.3.1"
}

View File

@@ -1,40 +1,27 @@
<template>
<div class="p-4 relative"
<div class="relative p-4"
@mouseenter="showEditButtons = isLoggedIn && creatorProfileStore.creator?.id === brandingStore.value.id"
@mouseleave="showEditButtons = false">
<!-- Edit buttons with absolute positioning -->
<div v-if="showEditButtons || isEditMode"
class="absolute top-4 right-4 flex gap-2">
<div v-if="showEditButtons || isEditMode" class="absolute right-4 top-4 flex gap-2">
<!-- Edit button with pencil icon -->
<button
v-if="!isEditMode"
:title="t('edit')"
class="w-12 h-12 bg-hutopyPrimary rounded-full flex items-center justify-center shadow-lg"
@click="toggleEditMode()"
>
<button v-if="!isEditMode" :title="t('edit')"
class="flex size-12 items-center justify-center rounded-full bg-hutopyPrimary shadow-lg"
@click="toggleEditMode()">
<v-icon large>mdi-pencil</v-icon>
</button>
<!-- Save button -->
<button
v-if="isEditMode"
:disabled="!canSave"
:title="t('save')"
class="w-12 h-12 bg-hutopyPrimary rounded-full flex items-center justify-center shadow-lg"
@click="saveChanges()"
>
<button v-if="isEditMode" :disabled="!canSave" :title="t('save')"
class="flex size-12 items-center justify-center rounded-full bg-hutopyPrimary shadow-lg" @click="saveChanges()">
<v-icon large>mdi-check</v-icon>
</button>
<!-- Cancel button -->
<button
v-if="isEditMode"
:title="t('cancel')"
class="w-12 h-12 bg-red-500 rounded-full flex items-center justify-center shadow-lg"
@click="cancelEdit"
>
<button v-if="isEditMode" :title="t('cancel')"
class="flex size-12 items-center justify-center rounded-full bg-red-500 shadow-lg" @click="cancelEdit">
<v-icon large>mdi-close</v-icon>
</button>
</div>
@@ -42,7 +29,7 @@
<!-- MainPage -->
<div class="flex flex-col">
<h1 class="flex justify-start text-2xl font-bold text-center mb-4">
<h1 class="mb-4 flex justify-start text-center text-2xl font-bold">
{{ t('creator.sections.about.title') }}
</h1>
@@ -50,72 +37,47 @@
<!-- Description Section -->
<div>
<div v-if="!isEditMode">
<p v-if="description" class="text-lg text-justify mb-6 whitespace-pre-line">
<p v-if="description" class="mb-6 whitespace-pre-line text-justify text-lg">
{{ description }}
</p>
</div>
<v-textarea v-if="isEditMode"
v-model="editableDescription"
:counter="2000"
:error-messages="descriptionError"
:label="t('creator.sections.about.description')"
:rules="[
<v-textarea v-if="isEditMode" v-model="editableDescription" :counter="2000" :error-messages="descriptionError"
:label="t('creator.sections.about.description')" :rules="[
v => !!v || t('creator.validation.descriptionRequired'),
v => v.length <= 2000 || t('creator.validation.descriptionTooLong')
]"
auto-grow
class="w-full p-2 py-6"
rows="5"
variant="outlined"></v-textarea>
]" auto-grow class="w-full p-2 py-6" rows="5" variant="outlined"></v-textarea>
</div>
<!-- Video Section -->
<div v-if="videoUrl || isEditMode"
:class="['content-section', {
<div v-if="videoUrl || isEditMode" :class="['content-section', {
'rounded-t-xl': hasImages && !isEditMode,
'rounded-xl': !hasImages && !isEditMode
}]">
<div v-if="!isEditMode && videoUrl" class="video-container">
<iframe
:src="youtubeEmbedUrl"
<iframe :src="youtubeEmbedUrl"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen
class="video-frame"
title="YouTube video player">
allowfullscreen class="video-frame" title="YouTube video player">
</iframe>
</div>
<div v-if="isEditMode">
<v-text-field
v-model="editableVideoUrl"
:error-messages="videoUrlError"
:label="t('creator.fields.videoUrl')"
class="w-full p-2"
type="text"
variant="outlined"
/>
<v-text-field v-model="editableVideoUrl" :error-messages="videoUrlError"
:label="t('creator.fields.videoUrl')" class="w-full p-2" type="text" variant="outlined" />
</div>
</div>
<!-- Photos Section using Album component -->
<div>
<!-- Use AlbumView for display mode -->
<AlbumView v-if="!isEditMode && hasImages"
:class="['content-section', {
<AlbumView v-if="!isEditMode && hasImages" :class="['content-section', {
'rounded-b-xl': videoUrl && !isEditMode,
'rounded-xl': !videoUrl && !isEditMode
}]"
:images="thumbnailUrls"
@photo-click="handlePhotoClick"/>
}]" :images="thumbnailUrls" @photo-click="handlePhotoClick" />
<AlbumViewer v-model="showAlbumViewer"
:images="originalUrls"
:start-index="selectedPhotoIndex"/>
<AlbumViewer v-model="showAlbumViewer" :images="originalUrls" :start-index="selectedPhotoIndex" />
<!-- Use AlbumEditor for edit mode -->
<AlbumEditor v-if="isEditMode"
:images="thumbnailUrls"
@update:images="updateImages"/>
<AlbumEditor v-if="isEditMode" :images="photos" @update:images="updateImages" />
</div>
<!-- Contact Information Section -->
@@ -144,7 +106,6 @@ import {useI18n} from 'vue-i18n';
import { buildEmbedUrl, isValidYouTubeUrlOrId, extractVideoId } from '@/utils/youtube';
import AlbumEditor from "@/views/creators/AlbumEditor.vue";
import AlbumView from "@/views/creators/AlbumView.vue";
// Add these imports at the top with your other imports
import AlbumViewer from './AlbumViewer.vue';
const { t } = useI18n();
@@ -162,7 +123,7 @@ const description = ref("");
const videoUrl = ref("");
const phoneNumber = ref("");
const email = ref("");
const thumbnailUrls = ref([]);
const photos = ref([]); //before was thumbnailUrls
const albumId = ref(null);
const originalPhotos = ref([]);
// Add these refs with your other refs
@@ -195,10 +156,19 @@ const canSave = computed(() => {
return true;
});
const thumbnailUrls = computed(() => {
return photos.value.map(photo => photo.thumbnailUrl)
})
// Add this computed property to get the original image URLs
const originalUrls = computed(() => {
return photos.value.map(photo => photo.originalUrl);
});
// Computed property to check if there are images
const hasImages = computed(() => {
// Only consider it has images if there are actual image URLs (not empty strings)
return thumbnailUrls.value.length > 0 && thumbnailUrls.value.some(img => img && img.trim() !== "");
return photos.value.length > 0;
});
// Computed property for YouTube embed URL
@@ -253,17 +223,17 @@ async function fetchAlbumData() {
// Store original photos for comparison
originalPhotos.value = response.data.photos;
// Extract photo URLs from the album photos
thumbnailUrls.value = response.data.photos.map(photo => photo.thumbnailUrl);
photos.value = response.data.photos;
} else {
// Initialize with empty array instead of empty slots
thumbnailUrls.value = [];
photos.value = [];
originalPhotos.value = [];
}
} catch (error) {
// Album might not exist yet, which is fine
console.log("Album might not exist yet:", error);
// Initialize with empty array instead of empty slots
thumbnailUrls.value = [];
photos.value = [];
originalPhotos.value = [];
}
}
@@ -283,7 +253,7 @@ onMounted(async () => {
// Update images from Album component
function updateImages(newImages) {
thumbnailUrls.value = newImages;
photos.value = newImages;
}
async function saveChanges() {
@@ -325,8 +295,15 @@ async function saveChanges() {
description.value = editableDescription.value;
videoUrl.value = extractVideoId(editableVideoUrl.value) || "";
// Check for deleted photos
const deletedPhotos = originalPhotos.value.filter(originalPhoto => {
// If the photo URL is not in the current images array, it was deleted
return !photos.value.includes(originalPhoto.thumbnailUrl);
});
// Save album photos if they've changed
if (thumbnailUrls.value.length > 0) {
if (photos.value.length > 0 || deletedPhotos.length > 0) {
// Create or update the album
const albumId = brandingStore.value.id;
@@ -342,12 +319,6 @@ async function saveChanges() {
console.log("Album might already exist:", error);
}
// Check for deleted photos
const deletedPhotos = originalPhotos.value.filter(originalPhoto => {
// If the photo URL is not in the current images array, it was deleted
return !thumbnailUrls.value.includes(originalPhoto.thumbnailUrl);
});
// Delete removed photos
for (const photo of deletedPhotos) {
try {
@@ -358,17 +329,30 @@ async function saveChanges() {
}
// Now add or update photos
for (let i = 0; i < thumbnailUrls.value.length; i++) {
const imageUrl = thumbnailUrls.value[i];
if (imageUrl && imageUrl.startsWith('data:')) {
for (let i = 0; i < photos.value.length; i++) {
const imageData = photos.value[i];
console.log('Image Data to be uploaded:', imageData);
if (imageData && imageData.image && imageData.image.originalUrl.startsWith('data:')) {
// This is a new image that needs to be uploaded
const photoId = crypto.randomUUID();
const formData = new FormData();
// Extract MIME type from data URL
const mimeMatch = imageData.image.originalUrl.match(/^data:(.*?);base64,/);
if (!mimeMatch) {
console.warn(`Invalid data URL at index ${i}`);
continue;
}
const mimeType = mimeMatch[1];
// Determine file extension from MIME type
const extension = mimeType.split('/')[1]; // e.g., "jpeg", "png", "webp", etc.
// Convert data URL to file
const response = await fetch(imageUrl);
const response = await fetch(imageData.image.originalUrl);
const blob = await response.blob();
const file = new File([blob], `photo-${i}.jpg`, {type: 'image/jpeg'});
const file = new File([blob], `photo-${i}.${extension}`, { type: mimeType });
formData.append('file', file);
@@ -404,12 +388,6 @@ function cancelEdit() {
// Désactiver le mode édition
isEditMode.value = false;
}
// Add this computed property to get the original image URLs
const originalUrls = computed(() => {
return originalPhotos.value.map(photo => photo.originalUrl);
});
// Add this function to handle photo clicks
function handlePhotoClick(index) {
selectedPhotoIndex.value = index;
@@ -425,7 +403,8 @@ function handlePhotoClick(index) {
.video-container {
position: relative;
width: 100%;
padding-top: 31.25%; /* Reduced from 56.25% to make it shorter while maintaining aspect ratio */
padding-top: 31.25%;
/* Reduced from 56.25% to make it shorter while maintaining aspect ratio */
max-height: 40vh;
}

View File

@@ -1,73 +1,51 @@
<template>
<div class="album-editor">
<h2 class="text-xl font-semibold mb-4">
<h2 class="mb-4 text-xl font-semibold">
{{ t('title') }}
</h2>
<!-- Drop zone with photos -->
<div class="drop-zone"
@dragover.prevent
@drop.prevent="handleDrop"
@click="triggerFileInput">
<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>
<span class="mt-2 text-sm">{{ t('dropzoneText') }}</span>
</div>
<!-- Hidden file input -->
<input
type="file"
ref="fileInput"
@change="handleFileUpload"
accept="image/*"
multiple
class="hidden"
/>
<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"
>
<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)" />
<img :src="element.image.originalUrl" :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>
<span class="mt-2 text-sm text-white">{{ 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>
<span class="mt-2 text-sm text-white">{{ 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}">
<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')"
<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')"
<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>
@@ -80,8 +58,9 @@
</template>
<script setup>
import { ref, onMounted, onUnmounted } from "vue";
import { ref, onMounted, onUnmounted, watch } from "vue";
import { useI18n } from 'vue-i18n';
import { v7 } from 'uuid';
import draggable from 'vuedraggable';
const props = defineProps({
@@ -100,24 +79,88 @@ const activePhotoIndex = ref(null); // Track which photo is currently active for
onMounted(() => {
// Initialize local images with IDs and states
localImages.value = props.images.map((url, index) => ({
id: index,
url: url,
localImages.value = props.images.map((image) => ({
image: image,
file: null,
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 closeActiveControls() {
activePhotoIndex.value = null;
}
watch(localImages, (newVal) => {
console.log('localImages changed:', newVal);
}, { deep: true });
function handleFiles(files) {
console.log('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 = {
image: {
id: v7(),
originalUrl: '',
},
file: file,
isProcessing: true,
isUploading: false,
};
localImages.value.push(tempImage);
console.log('Processing image:', tempImage);
reader.onload = (e) => {
console.log('Image loaded:', e);
const index = localImages.value.findIndex(local => local.image.id === tempImage.image.id);
if (index !== -1) {
localImages.value[index].image.originalUrl = e.target.result;
localImages.value[index].isProcessing = false;
emit('update:images', localImages.value);
}
};
reader.readAsDataURL(file);
} catch (error) {
console.error('Error processing image:', error);
}
}
}
}
function handleDrop(event) {
console.log('Drop triggered');
const files = Array.from(event.dataTransfer.files);
handleFiles(files);
}
function triggerFileInput() {
console.log('Input triggered');
fileInput.value.click();
}
function handleFileUpload(event) {
console.log('File input triggered');
const files = Array.from(event.target.files);
handleFiles(files);
event.target.value = '';
}
function handleReorder() {
activePhotoIndex.value = null; // Reset active photo
emit('update:images', localImages.value);
}
function toggleMobileControls(index) {
// If clicking the same photo, toggle the controls
if (activePhotoIndex.value === index) {
@@ -128,78 +171,6 @@ function toggleMobileControls(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) {
@@ -207,10 +178,16 @@ function moveImage(index, direction) {
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));
emit('update:images', localImages.value);
}
}
function deleteImage(index) {
localImages.value.splice(index, 1);
activePhotoIndex.value = null; // Reset active photo
emit('update:images', localImages.value);
}
</script>
<style scoped>

View File

@@ -1,4 +1,4 @@
<template>
<template>
<div class="creator-home">
<!-- Content sections container -->
@@ -6,12 +6,9 @@
<!-- Donation Section -->
<div v-if="brandingStore.value?.acceptDonation" class="section sm:hidden">
<DonationButton
:creator-id="brandingStore.value?.id"
:creator-name="brandingStore.value?.name"
<DonationButton :creator-id="brandingStore.value?.id" :creator-name="brandingStore.value?.name"
:on-cancelled-url="baseURL + '/paymentfailed/' + brandingStore.value?.id"
:on-success-url="baseURL + '/paymentcompleted/' + brandingStore.value?.id"
/>
:on-success-url="baseURL + '/paymentcompleted/' + brandingStore.value?.id" />
</div>
<!-- About Creator Section -->
@@ -61,7 +58,6 @@ const baseURL = window.location.origin;
mask-composite: exclude;
pointer-events: none;
}
</style>
<i18n>