feat(album): add thumbnails and AlbumViewer.vue

This commit is contained in:
2025-05-26 15:11:57 -04:00
parent ea0241dd8d
commit a08b384495
11 changed files with 847 additions and 68 deletions

View File

@@ -10,9 +10,9 @@
<!-- 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()"
:title="t('edit')"
>
<v-icon large>mdi-pencil</v-icon>
</button>
@@ -20,10 +20,10 @@
<!-- 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()"
:title="t('save')"
:disabled="!canSave"
>
<v-icon large>mdi-check</v-icon>
</button>
@@ -31,9 +31,9 @@
<!-- 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"
:title="t('cancel')"
>
<v-icon large>mdi-close</v-icon>
</button>
@@ -56,15 +56,15 @@
</div>
<v-textarea v-if="isEditMode"
v-model="editableDescription"
class="w-full p-2 py-6"
:label="t('creator.sections.about.description')"
:error-messages="descriptionError"
: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>
</div>
@@ -88,11 +88,11 @@
<div v-if="isEditMode">
<v-text-field
v-model="editableVideoUrl"
class="w-full p-2"
:error-messages="videoUrlError"
:label="t('creator.fields.videoUrl')"
class="w-full p-2"
type="text"
variant="outlined"
:error-messages="videoUrlError"
/>
</div>
</div>
@@ -101,18 +101,23 @@
<div>
<!-- Use AlbumView for display mode -->
<AlbumView v-if="!isEditMode && hasImages"
:images="imageUrls"
:class="['content-section', {
'rounded-b-xl': videoUrl && !isEditMode,
'rounded-xl': !videoUrl && !isEditMode
}]"/>
}]"
:images="thumbnailUrls"
@photo-click="handlePhotoClick"/>
<AlbumViewer v-model="showAlbumViewer"
:images="originalUrls"
:start-index="selectedPhotoIndex"/>
<!-- Use AlbumEditor for edit mode -->
<AlbumEditor v-if="isEditMode"
:images="imageUrls"
:images="thumbnailUrls"
@update:images="updateImages"/>
</div>
<!-- Contact Information Section -->
<div v-if="phoneNumber || email" class="contact-info mt-6">
<!-- Phone Number -->
@@ -136,10 +141,11 @@ import {useClient} from "@/plugins/api.js";
import {useBrandingStore} from "@/stores/brandingStore.js";
import {useCreatorProfileStore} from "@/stores/creatorProfileStore.js";
import {useI18n} from 'vue-i18n';
import Album from './Album.vue';
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();
const creatorProfileStore = useCreatorProfileStore();
@@ -156,9 +162,12 @@ const description = ref("");
const videoUrl = ref("");
const phoneNumber = ref("");
const email = ref("");
const imageUrls = ref([]);
const thumbnailUrls = ref([]);
const albumId = ref(null);
const originalPhotos = ref([]);
// Add these refs with your other refs
const showAlbumViewer = ref(false);
const selectedPhotoIndex = ref(0);
// Editable fields
const editableDescription = ref("");
@@ -189,7 +198,7 @@ const canSave = computed(() => {
// 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 imageUrls.value.length > 0 && imageUrls.value.some(img => img && img.trim() !== "");
return thumbnailUrls.value.length > 0 && thumbnailUrls.value.some(img => img && img.trim() !== "");
});
// Computed property for YouTube embed URL
@@ -244,17 +253,17 @@ async function fetchAlbumData() {
// Store original photos for comparison
originalPhotos.value = response.data.photos;
// Extract photo URLs from the album photos
imageUrls.value = response.data.photos.map(photo => photo.photoUrl);
thumbnailUrls.value = response.data.photos.map(photo => photo.thumbnailUrl);
} else {
// Initialize with empty array instead of empty slots
imageUrls.value = [];
thumbnailUrls.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
imageUrls.value = [];
thumbnailUrls.value = [];
originalPhotos.value = [];
}
}
@@ -274,7 +283,7 @@ onMounted(async () => {
// Update images from Album component
function updateImages(newImages) {
imageUrls.value = newImages;
thumbnailUrls.value = newImages;
}
async function saveChanges() {
@@ -317,7 +326,7 @@ async function saveChanges() {
videoUrl.value = extractVideoId(editableVideoUrl.value) || "";
// Save album photos if they've changed
if (imageUrls.value.length > 0) {
if (thumbnailUrls.value.length > 0) {
// Create or update the album
const albumId = brandingStore.value.id;
@@ -336,7 +345,7 @@ async function saveChanges() {
// 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 !imageUrls.value.includes(originalPhoto.photoUrl);
return !thumbnailUrls.value.includes(originalPhoto.thumbnailUrl);
});
// Delete removed photos
@@ -349,8 +358,8 @@ async function saveChanges() {
}
// Now add or update photos
for (let i = 0; i < imageUrls.value.length; i++) {
const imageUrl = imageUrls.value[i];
for (let i = 0; i < thumbnailUrls.value.length; i++) {
const imageUrl = thumbnailUrls.value[i];
if (imageUrl && imageUrl.startsWith('data:')) {
// This is a new image that needs to be uploaded
const photoId = crypto.randomUUID();
@@ -396,6 +405,16 @@ function cancelEdit() {
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;
showAlbumViewer.value = true;
}
</script>
<style scoped>
@@ -547,4 +566,4 @@ function cancelEdit() {
}
}
}
</i18n>
</i18n>