detail page: add tile gallery
|
After Width: | Height: | Size: 2.4 MiB |
|
After Width: | Height: | Size: 10 MiB |
|
After Width: | Height: | Size: 4.5 MiB |
BIN
frontend/public/images/gallery/fuu-j-r2nJPbEYuSQ-unsplash.jpg
Normal file
|
After Width: | Height: | Size: 805 KiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 3.6 MiB |
|
After Width: | Height: | Size: 904 KiB |
|
After Width: | Height: | Size: 3.9 MiB |
|
After Width: | Height: | Size: 2.4 MiB |
|
After Width: | Height: | Size: 1.8 MiB |
|
After Width: | Height: | Size: 2.1 MiB |
|
After Width: | Height: | Size: 2.8 MiB |
|
After Width: | Height: | Size: 3 MiB |
|
After Width: | Height: | Size: 91 KiB |
|
After Width: | Height: | Size: 74 KiB |
|
After Width: | Height: | Size: 71 KiB |
BIN
frontend/public/images/thumbs/fuu-j-r2nJPbEYuSQ-unsplash.jpg
Normal file
|
After Width: | Height: | Size: 83 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 93 KiB |
|
After Width: | Height: | Size: 61 KiB |
BIN
frontend/public/images/thumbs/jay-antol-Xbf_4e7YDII-unsplash.jpg
Normal file
|
After Width: | Height: | Size: 121 KiB |
|
After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 94 KiB |
|
After Width: | Height: | Size: 98 KiB |
|
After Width: | Height: | Size: 128 KiB |
|
After Width: | Height: | Size: 119 KiB |
|
|
@ -108,6 +108,40 @@
|
|||
</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 -->
|
||||
<div v-if="entry.audioRecordings && entry.audioRecordings.length > 0" class="audio-section">
|
||||
<h3>Audio Recordings</h3>
|
||||
|
|
@ -243,6 +277,55 @@
|
|||
</q-card-actions>
|
||||
</q-card>
|
||||
</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>
|
||||
</template>
|
||||
|
||||
|
|
@ -269,6 +352,30 @@ export default {
|
|||
const showVideoLightbox = ref(false)
|
||||
const currentVideoIndex = ref(0)
|
||||
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)
|
||||
const sampleEntry = {
|
||||
|
|
@ -331,12 +438,27 @@ export default {
|
|||
|
||||
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
|
||||
const currentVideo = computed(() =>
|
||||
entry.value.videoRecordings?.[currentVideoIndex.value]
|
||||
)
|
||||
|
||||
|
||||
// Methods
|
||||
const formatDate = (dateString) => {
|
||||
const date = new Date(dateString)
|
||||
|
|
@ -400,6 +522,24 @@ export default {
|
|||
const onVideoLoaded = () => {
|
||||
// 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 = () => {
|
||||
// TODO: Navigate to edit page or open edit modal
|
||||
|
|
@ -419,12 +559,17 @@ export default {
|
|||
return {
|
||||
entry,
|
||||
allImages,
|
||||
additionalImagesGallery,
|
||||
favoriteImageIndices, // Added this to fix the ESLint error
|
||||
currentSlide,
|
||||
currentVideoSlide,
|
||||
showVideoLightbox,
|
||||
currentVideoIndex,
|
||||
currentVideo,
|
||||
lightboxVideo,
|
||||
showImageLightbox,
|
||||
currentImageIndex,
|
||||
currentLightboxImage,
|
||||
formatDate,
|
||||
getEmotionalLevelClass,
|
||||
getEmotionalLevelText,
|
||||
|
|
@ -434,6 +579,9 @@ export default {
|
|||
previousVideo,
|
||||
nextVideo,
|
||||
onVideoLoaded,
|
||||
openImageLightbox,
|
||||
previousImage,
|
||||
nextImage,
|
||||
editEntry
|
||||
}
|
||||
}
|
||||
|
|
@ -895,6 +1043,102 @@ export default {
|
|||
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 */
|
||||
@media (max-width: 600px) {
|
||||
.entry-container {
|
||||
|
|
@ -920,5 +1164,35 @@ export default {
|
|||
.person-item {
|
||||
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>
|
||||