{"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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"}