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>
|
||||||
</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>
|
||||||
|
|
||||||
|
|
@ -269,6 +352,30 @@ export default {
|
||||||
const showVideoLightbox = ref(false)
|
const showVideoLightbox = ref(false)
|
||||||
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 = {
|
||||||
|
|
@ -331,12 +438,27 @@ 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]
|
||||||
)
|
)
|
||||||
|
|
||||||
// Methods
|
// Methods
|
||||||
const formatDate = (dateString) => {
|
const formatDate = (dateString) => {
|
||||||
const date = new Date(dateString)
|
const date = new Date(dateString)
|
||||||
|
|
@ -400,6 +522,24 @@ export default {
|
||||||
const onVideoLoaded = () => {
|
const onVideoLoaded = () => {
|
||||||
// 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
|
||||||
|
|
@ -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>
|
||||||