detail page: add tile gallery

This commit is contained in:
Tilman Behrend 2025-09-22 09:55:24 +02:00
parent 5402f98d05
commit 7d2ed57347
27 changed files with 275 additions and 1 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 805 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 904 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

View file

@ -108,6 +108,40 @@
</div> </div>
</div> </div>
<!-- Additional Images Gallery -->
<div v-if="additionalImagesGallery.length > 0" class="additional-images-section">
<div class="additional-images-grid">
<div
v-for="(image, index) in additionalImagesGallery"
:key="index"
class="image-tile"
@click="openImageLightbox(image, index)"
>
<q-img
:src="image.thumbnail"
class="tile-image"
fit="cover"
loading="lazy"
>
<template v-slot:error>
<div class="absolute-full flex flex-center bg-grey-3 text-grey-7">
<q-icon name="broken_image" size="24px" />
</div>
</template>
</q-img>
<!-- Favorite Star Badge -->
<div v-if="image.isFavorite" class="favorite-star-badge">
<q-icon name="star" size="20px" color="amber" />
</div>
<div class="image-tile-overlay">
<q-icon name="zoom_in" size="24px" color="white" />
</div>
</div>
</div>
</div>
<!-- Audio Recordings --> <!-- Audio Recordings -->
<div v-if="entry.audioRecordings && entry.audioRecordings.length > 0" class="audio-section"> <div v-if="entry.audioRecordings && entry.audioRecordings.length > 0" class="audio-section">
<h3>Audio Recordings</h3> <h3>Audio Recordings</h3>
@ -243,6 +277,55 @@
</q-card-actions> </q-card-actions>
</q-card> </q-card>
</q-dialog> </q-dialog>
<!-- Image Lightbox Dialog -->
<q-dialog v-model="showImageLightbox" maximized>
<q-card class="image-lightbox-card">
<q-card-section class="image-lightbox-header">
<q-btn icon="close" flat round dense v-close-popup class="close-btn" />
</q-card-section>
<q-card-section class="image-lightbox-content">
<q-img
v-if="currentLightboxImage"
:src="currentLightboxImage.fullSize"
class="lightbox-image"
fit="contain"
>
<template v-slot:error>
<div class="absolute-full flex flex-center bg-grey-8 text-white">
<div class="text-center">
<q-icon name="broken_image" size="48px" class="q-mb-md" />
<div>Image could not be loaded</div>
</div>
</div>
</template>
</q-img>
</q-card-section>
<q-card-actions v-if="additionalImagesGallery.length > 1" align="center" class="image-lightbox-actions">
<q-btn
icon="chevron_left"
@click="previousImage"
:disable="currentImageIndex === 0"
flat
round
color="white"
/>
<span class="image-counter">
{{ currentImageIndex + 1 }} / {{ additionalImagesGallery.length }}
</span>
<q-btn
icon="chevron_right"
@click="nextImage"
:disable="currentImageIndex === additionalImagesGallery.length - 1"
flat
round
color="white"
/>
</q-card-actions>
</q-card>
</q-dialog>
</q-page> </q-page>
</template> </template>
@ -270,6 +353,30 @@ export default {
const currentVideoIndex = ref(0) const currentVideoIndex = ref(0)
const lightboxVideo = ref(null) const lightboxVideo = ref(null)
// Image lightbox state
const showImageLightbox = ref(false)
const currentImageIndex = ref(0)
// Define available gallery images from thumbs folder
const galleryImageNames = [
'aaron-huber-RLs8LZcONCA-unsplash.jpg',
'andrew-bui-z7rzbFHXym0-unsplash.jpg',
'becca-tapert--A_Sx8GrRWg-unsplash.jpg',
'fuu-j-r2nJPbEYuSQ-unsplash.jpg',
'ian-dooley-hpTH5b6mo2s-unsplash.jpg',
'ishan-seefromthesky-TobZaa8ZwI4-unsplash.jpg',
'javier-allegue-barros-55bVEzGVnzY-unsplash.jpg',
'jay-antol-Xbf_4e7YDII-unsplash.jpg',
'jorgen-hendriksen-8qg-hy6VbYE-unsplash.jpg',
'mohamed-nohassi-odxB5oIG_iA-unsplash.jpg',
'saad-chaudhry-cYpqYxGeqts-unsplash.jpg',
'taryn-kaahanui-J5b23iaAHis-unsplash.jpg',
'tirza-van-dijk-hbwdmqcmP6k-unsplash.jpg',
]
// Mark specific images as favorites (indices 2, 5, and 9)
const favoriteImageIndices = [2, 5, 9]
// Sample entry data (for testing) // Sample entry data (for testing)
const sampleEntry = { const sampleEntry = {
id: 1, id: 1,
@ -332,6 +439,21 @@ export default {
return images return images
}) })
// Additional images gallery - Updated to include favorite status
const additionalImagesGallery = computed(() => {
return galleryImageNames.map((imageName, index) => ({
thumbnail: `/images/thumbs/${imageName}`,
fullSize: `/images/gallery/${imageName}`,
name: imageName,
isFavorite: favoriteImageIndices.includes(index)
}))
})
// Current lightbox image
const currentLightboxImage = computed(() =>
additionalImagesGallery.value[currentImageIndex.value]
)
// Current video for lightbox // Current video for lightbox
const currentVideo = computed(() => const currentVideo = computed(() =>
entry.value.videoRecordings?.[currentVideoIndex.value] entry.value.videoRecordings?.[currentVideoIndex.value]
@ -401,6 +523,24 @@ export default {
// Video metadata loaded, can add additional logic here if needed // Video metadata loaded, can add additional logic here if needed
} }
// Image lightbox methods
const openImageLightbox = (image, index) => {
currentImageIndex.value = index
showImageLightbox.value = true
}
const previousImage = () => {
if (currentImageIndex.value > 0) {
currentImageIndex.value--
}
}
const nextImage = () => {
if (currentImageIndex.value < additionalImagesGallery.value.length - 1) {
currentImageIndex.value++
}
}
const editEntry = () => { const editEntry = () => {
// TODO: Navigate to edit page or open edit modal // TODO: Navigate to edit page or open edit modal
console.log('Edit entry:', entry.value.id) console.log('Edit entry:', entry.value.id)
@ -419,12 +559,17 @@ export default {
return { return {
entry, entry,
allImages, allImages,
additionalImagesGallery,
favoriteImageIndices, // Added this to fix the ESLint error
currentSlide, currentSlide,
currentVideoSlide, currentVideoSlide,
showVideoLightbox, showVideoLightbox,
currentVideoIndex, currentVideoIndex,
currentVideo, currentVideo,
lightboxVideo, lightboxVideo,
showImageLightbox,
currentImageIndex,
currentLightboxImage,
formatDate, formatDate,
getEmotionalLevelClass, getEmotionalLevelClass,
getEmotionalLevelText, getEmotionalLevelText,
@ -434,6 +579,9 @@ export default {
previousVideo, previousVideo,
nextVideo, nextVideo,
onVideoLoaded, onVideoLoaded,
openImageLightbox,
previousImage,
nextImage,
editEntry editEntry
} }
} }
@ -895,6 +1043,102 @@ export default {
box-shadow: 0 6px 20px rgba(52, 152, 219, 0.4); box-shadow: 0 6px 20px rgba(52, 152, 219, 0.4);
} }
/* Additional Images Gallery Styles */
.additional-images-section {
background: white;
border-radius: 16px;
padding: 24px;
margin-bottom: 24px;
}
.additional-images-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 16px;
}
.image-tile {
position: relative;
aspect-ratio: 1;
border-radius: 12px;
overflow: hidden;
cursor: pointer;
transition: all 0.3s ease;
}
.image-tile:hover {
transform: translateY(-4px);
}
.tile-image {
width: 100%;
height: 100%;
border-radius: 12px;
}
.image-tile-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: opacity 0.3s ease;
}
.image-tile:hover .image-tile-overlay {
opacity: 1;
}
/* Image Lightbox Styles */
.image-lightbox-card {
background: #000;
}
.image-lightbox-header {
background: transparent;
color: white;
display: flex;
justify-content: flex-end;
align-items: flex-start;
padding: 16px 24px;
position: absolute;
top: 0;
right: 0;
z-index: 1001;
}
.image-lightbox-content {
background: #000;
display: flex;
align-items: center;
justify-content: center;
min-height: 80vh;
padding: 20px;
}
.lightbox-image {
max-width: 100%;
max-height: 80vh;
border-radius: 8px;
}
.image-lightbox-actions {
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 16px;
}
.image-counter {
color: white;
font-weight: 500;
margin: 0 16px;
}
/* Responsive */ /* Responsive */
@media (max-width: 600px) { @media (max-width: 600px) {
.entry-container { .entry-container {
@ -920,5 +1164,35 @@ export default {
.person-item { .person-item {
min-width: auto; min-width: auto;
} }
.additional-images-grid {
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
gap: 12px;
}
.additional-images-section {
padding: 16px;
}
}
/* Favorite Star Badge */
.favorite-star-badge {
position: absolute;
top: 8px;
right: 8px;
background: rgba(255, 255, 255, 0.9);
border-radius: 50%;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
z-index: 2;
transition: all 0.3s ease;
}
.image-tile:hover .favorite-star-badge {
background: rgba(255, 255, 255, 1);
transform: scale(1.1);
} }
</style> </style>