{"version":3,"file":"MediaGallery-BNbTo93S.js","sources":["../../../app/javascript/src/utils/mediaGallery.js","../../../app/javascript/src/modules/MediaGallery/MediaGalleryFlex.vue","../../../app/javascript/src/modules/MediaGallery/MediaGalleryGrid.vue","../../../app/javascript/src/modules/MediaGallery/MediaGallery.vue"],"sourcesContent":["import { divTag } from \"@/helpers/documentHelpers.js\";\n\nexport function waitObject(object, maxRetry = 10, intervalTime = 100) {\n return new Promise((resolve, reject) => {\n if (object) {\n return resolve(object);\n }\n\n let retry = 0;\n const interval = window.setInterval(() => {\n if (object) {\n window.clearInterval(interval);\n return resolve(object);\n }\n\n retry += 1;\n if (retry >= maxRetry) {\n window.clearInterval(interval);\n return reject(new Error('Max retries reached. Object is still not available.'));\n }\n }, intervalTime);\n });\n}\n\nexport function generateCaptionHtml(image) {\n let captionHtml = divTag([image.caption ?? '', image.description ?? ''].filter(Boolean).join('<br><br>'));\n\n const author = image.author_name ?? image.authorName;\n const authorUrl = image.author_url ?? image.authorUrl;\n const platform = image.platform;\n const platformUrl = image.platform_url ?? image.platformUrl;\n\n captionHtml += authorUrl && author ?\n `<span> Photo by <a class=\"lightbox-image-caption\" href=\"${authorUrl}\">${author}</a>` : `<span>`\n\n captionHtml += platformUrl && platform ?\n ` on <a class=\"lightbox-image-caption\" href=\"${platformUrl}\">${platform}</a></span>` : `</span>`\n\n return captionHtml\n}\n\nexport function preloadImage(url) {\n const img = new Image();\n img.src = url;\n}","<template>\n <div class=\"media-gallery-flex\" :class=\"layout\" v-show=\"featureImages.length > 0\">\n <a\n @click.stop\n v-for=\"(image, index) in filtered\"\n :key=\"image.url\"\n :data-fslightbox=\"gallery\"\n data-type=\"image\"\n :data-caption=\"generateCaptionHtml(image)\"\n class=\"image-anchor\"\n :href=\"image.url\"\n >\n <img :src=\"image.thumbnail || image.url\" class=\"image\" :alt=\"image.title\" />\n <div\n v-if=\"overlay && index === filtered.length - 1 && remainCount\"\n class=\"image-overlay\"\n :data-more-images=\"`${remainCount}+`\"\n />\n </a>\n <a\n v-for=\"(image) in remainImages\" class=\"is-hidden\"\n :alt=\"image.title\"\n :href=\"image.url\"\n :key=\"`fslightbox-${image.url}`\"\n :data-fslightbox=\"gallery\"\n data-type=\"image\"\n :data-caption=\"generateCaptionHtml(image)\"/>\n </div>\n</template>\n\n<script setup>\nimport { randomKey } from '@/utils/stringUtils.js';\nimport { computed, onMounted } from \"vue\";\nimport { generateCaptionHtml } from \"@/utils/mediaGallery.js\";\n\nconst { featureImages, layout } = defineProps({\n featureImages: Array,\n layout: {\n type: String,\n default: 'row',\n },\n overlay: {\n type: Boolean,\n default: true,\n },\n});\n\nconst images = { single: 1, row: 4, hotels: 5 };\nconst gallery = randomKey();\nconst filtered = computed(() => featureImages.slice(0, images[layout]));\nconst remainImages = computed(() => featureImages.slice(images[layout]));\nconst remainCount = computed(() => featureImages.length - filtered.value.length);\n\nonMounted(() => {\n if (featureImages.length > 0) {\n refreshFsLightbox(); // eslint-disable-line no-undef\n }\n});\n</script>\n\n<style lang=\"scss\" scoped>\n.media-gallery-flex {\n gap: 12px;\n\n .image {\n position: relative;\n width: 100%;\n aspect-ratio: 1;\n border-radius: 15px;\n object-fit: cover;\n }\n\n .image-anchor {\n display: flex;\n overflow: hidden;\n position: relative;\n }\n\n .image-overlay {\n font-size: $text-base;\n }\n}\n\n.media-gallery-flex.row {\n display: flex;\n\n .image-anchor {\n width: 83px;\n\n @media(max-width: $breakpoint-m) {\n width: 48px;\n }\n }\n}\n\n.media-gallery-flex.hotels {\n display: grid;\n grid-template-columns: repeat(4, 1fr);\n\n .image-anchor:first-child {\n aspect-ratio: auto;\n grid-column: 1/-1;\n }\n}\n</style>\n","<template>\n <div class=\"wrapper\" v-show=\"hasMedia\">\n <div class=\"media-gallery-grid\" :style=\"gridStyle\">\n <div v-if=\"videoUrl\"\n class=\"media-gallery-grid__video\"\n :class=\"{ enlarge: displayCount > (desktop ? 2 : 4) }\">\n <a data-fslightbox=\"feature-gallery-grid\" data-type=\"video\" data-caption=\"Feature video\" :href=\"videoUrl\">\n <video :src=\"videoUrl\" autoplay muted loop playsinline controls/>\n </a>\n </div>\n <a\n v-for=\"(image, index) in filtered\"\n :key=\"image.url\"\n data-fslightbox=\"feature-gallery-grid\"\n data-type=\"image\"\n :data-caption=\"generateCaptionHtml(image)\"\n class=\"media-gallery-grid__image-anchor\"\n :class=\"{ enlarge: !videoUrl && index === 0 && displayCount > (desktop ? 2 : 4) }\"\n :href=\"image.url\"\n >\n <img :src=\"image.thumbnail || image.url\" class=\"media-gallery-grid__image\" :alt=\"image.title\" />\n <div\n v-if=\"index === filtered.length - 1 && remainCount\"\n class=\"itinerary-gallery__image-overlay\"\n :data-more-images=\"`${remainCount}+`\"\n />\n </a>\n <a\n v-for=\"(image) in remainImages\" class=\"is-hidden\"\n :alt=\"image.title\"\n :href=\"image.url\"\n :key=\"`fslightbox-${image.url}`\"\n data-fslightbox=\"feature-gallery-grid\"\n data-type=\"image\"\n :data-caption=\"generateCaptionHtml(image)\"/>\n </div>\n </div>\n</template>\n\n<script setup>\nimport {computed, onMounted} from \"vue\";\nimport {waitObject, preloadImage} from \"@/utils/mediaGallery.js\";\nimport {once} from \"lodash\";\nimport {useShared} from \"@/helpers/vueHelpers.js\";\nimport {generateCaptionHtml} from \"@/utils/mediaGallery.js\";\n\nconst { mobile, desktop } = useShared();\nconst props = defineProps({\n featureImages: Array,\n videoUrl: String,\n desktop: Number,\n mobile: Number\n});\nconst { featureImages, videoUrl } = props;\nconst limit = computed(() => mobile.value ? props.mobile : props.desktop);\nconst mediaCount = computed(() => featureImages.length + (videoUrl ? 1 : 0));\nconst displayCount = computed(() => Math.min(mediaCount.value, limit.value));\nconst displayImageCount = computed(() => displayCount.value - (videoUrl ? 1 : 0));\nconst filtered = computed(() => featureImages.slice(0, displayImageCount.value));\nconst remainImages = computed(() => featureImages.slice(displayImageCount.value));\nconst remainCount = computed(() => featureImages.length - filtered.value.length);\nconst hasMedia = computed(() => featureImages.length > 0 || videoUrl);\n\nconst gridStyle = computed(() => {\n const total = displayCount.value;\n let rows = 1;\n let columns = 1;\n\n if (desktop.value) {\n rows = total > 2 ? 2 : 1;\n columns = Math.min(total, 4);\n } else if (mobile.value && total > 4) {\n rows = 3;\n columns = 3;\n } else if (mobile.value && total > 2) {\n rows = 2\n columns = 2;\n } else if (mobile.value && total > 1) {\n rows = 2;\n }\n return {\n gridTemplateRows: `repeat(${rows}, 1fr)`,\n gridTemplateColumns: `repeat(${columns}, 1fr)`\n };\n});\n\nonMounted(async () => {\n if (hasMedia.value) {\n refreshFsLightbox(); // eslint-disable-line no-undef\n const fsInstance = await waitObject(fsLightboxInstances['feature-gallery-grid']); // eslint-disable-line no-undef\n if (fsInstance) {\n fsInstance.props.onOpen = once(preloadImages);\n }\n }\n});\n\nfunction preloadImages() {\n featureImages.forEach(img => preloadImage(img.url));\n}\n</script>\n\n<style lang=\"scss\" scoped>\n.wrapper {\n position: relative;\n padding-bottom: 49.125%; /* Set ratio here */\n height: 0;\n\n @include mobile {\n padding-bottom: 100%;\n }\n}\n\n.media-gallery-grid {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n\n display: grid;\n grid-template-columns: repeat(4, 1fr);\n grid-template-rows: repeat(2, 1fr);\n gap: 24px;\n\n @include mobile {\n grid-template-columns: repeat(3, 1fr);\n grid-template-rows: repeat(3, 1fr);\n gap: 8px;\n }\n\n .media-gallery-grid__image {\n position: relative;\n width: 100%;\n height: 100%;\n border-radius: 15px;\n object-fit: cover;\n }\n\n .media-gallery-grid__image-anchor {\n width: 100%;\n height: 100%;\n display: block;\n overflow: hidden;\n position: relative;\n }\n\n .enlarge {\n grid-row: span 2;\n grid-column: span 2;\n }\n\n .media-gallery-grid__video {\n display: flex;\n background: black;\n align-items: center;\n justify-content: center;\n border-radius: 15px;\n overflow: hidden;\n\n & > a {\n display: flex;\n max-width: 100%;\n max-height: 100%;\n }\n }\n\n video {\n width: 100%;\n max-height: 100%;\n object-fit: cover;\n }\n}\n</style>\n","<template>\n <div class=\"media-gallery\">\n <MediaGalleryGrid v-bind=\"$attrs\" v-if=\"layout === 'grid'\" :featureImages=\"featureImages\" :videoUrl=\"videoUrl\" :desktop :mobile />\n <MediaGalleryFlex v-bind=\"$attrs\" v-else :layout=\"layout\" :featureImages=\"featureImages\"/>\n </div>\n</template>\n\n<script setup>\nimport \"@/plugins/fslightbox.js\";\nimport MediaGalleryFlex from './MediaGalleryFlex.vue';\nimport MediaGalleryGrid from './MediaGalleryGrid.vue';\nimport { useBodyClass } from \"@/helpers/vueHelpers.js\";\n\ndefineProps({\n featureImages: Array,\n videoUrl: String,\n layout: {\n type: String,\n default: 'grid'\n },\n desktop: {\n type: Number,\n default: 5,\n },\n mobile: {\n type: Number,\n default: 6,\n },\n});\n\nuseBodyClass('has-media-gallery');\n</script>\n\n<style lang=\"scss\" scoped>\n:deep(.image-overlay),\n:deep(.itinerary-gallery__image-overlay) {\n font-size: 30px;\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n color: $black;\n font-weight: $font-semibold;\n background-color: rgba($white, 0.55);\n\n &::after {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n content: attr(data-more-images);\n }\n\n @include mobile {\n font-size: 20px;\n }\n}\n</style>\n\n<style lang=\"scss\">\n.lightbox-image-caption {\n color: $white\n}\n\n.lightbox-image-caption:hover {\n color: $white;\n text-decoration: underline\n}\n</style>\n\n<style lang=\"scss\">\n.has-media-gallery img.fslightboxs {\n border-radius: 15px;\n}\n</style>\n"],"names":["waitObject","object","maxRetry","intervalTime","resolve","reject","retry","interval","generateCaptionHtml","image","captionHtml","divTag","author","authorUrl","platform","platformUrl","preloadImage","url","img","images","gallery","randomKey","filtered","computed","__props","remainImages","remainCount","onMounted","mobile","desktop","useShared","props","featureImages","videoUrl","limit","mediaCount","displayCount","displayImageCount","hasMedia","gridStyle","total","rows","columns","fsInstance","once","preloadImages","useBodyClass"],"mappings":"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"}