158 lines
5.2 KiB
Vue
158 lines
5.2 KiB
Vue
<script setup lang="ts">
|
|
import {
|
|
computed,
|
|
onMounted,
|
|
ref,
|
|
useTemplateRef,
|
|
} from 'vue'
|
|
import PrimeVueCarousel from 'primevue/carousel'
|
|
import PrimeVueImage from 'primevue/image'
|
|
import { marked } from 'marked'
|
|
import type {
|
|
GalleryEntry,
|
|
GalleryEntryWithVariants,
|
|
GalleryEntryWithoutVariants,
|
|
GalleryListDefinition,
|
|
ListWithEntries,
|
|
RoutedWindow,
|
|
} from '@goldenwere/mackenzii-types'
|
|
|
|
import { fetchAndParseConfig, fetchConfigByIdFromList } from 'src/utilities/fetch'
|
|
import { getCurrentRoute } from 'src/utilities/vuetils'
|
|
import { useRouteStore } from 'src/routes'
|
|
|
|
import EmbedableContent from 'src/components/shared/embedable-content.vue'
|
|
|
|
declare const window: RoutedWindow
|
|
|
|
const emits = defineEmits<{
|
|
(e: 'loaded'): void
|
|
}>()
|
|
|
|
const imageRef = useTemplateRef<HTMLImageElement>('image-ref' as any)
|
|
const ready = ref(false)
|
|
const resolved = ref({} as GalleryEntry)
|
|
const currentRoute = getCurrentRoute()
|
|
const routeStore = useRouteStore()
|
|
const routeConfig = routeStore._routes[currentRoute.path.substring(0, currentRoute.path.length - 5)] as GalleryListDefinition
|
|
const routeSubConfig = routeStore._routes[currentRoute.path]
|
|
const description = computed(() => marked.parse(resolved.value.description || '') as string)
|
|
const title = computed(() => marked.parse(resolved.value.title || currentRoute.query.id!.toString()) as string)
|
|
const url = computed(() => (resolved.value as GalleryEntryWithoutVariants).url)
|
|
const fields = computed(() => {
|
|
const toReturn = {} as Record<string, any>
|
|
if (!!resolved.value.date) {
|
|
toReturn.date = resolved.value.date
|
|
}
|
|
if (!!resolved.value.warnings) {
|
|
toReturn.warnings = resolved.value.warnings
|
|
}
|
|
return toReturn
|
|
})
|
|
const variants = computed(() => (resolved.value as GalleryEntryWithVariants).variants)
|
|
const currentSelection = defineModel('currentSelection', { type: Number, default: 0 })
|
|
const currentUrl = computed(() => (resolved.value as GalleryEntryWithVariants).variants[currentSelection.value]?.url)
|
|
const currentAlt = computed(() => (resolved.value as GalleryEntryWithVariants).variants[currentSelection.value]?.alternativeText)
|
|
const currentCaption = computed(() => (resolved.value as GalleryEntryWithVariants).variants[currentSelection.value]?.caption)
|
|
const carouselResponsiveOptions = [
|
|
{
|
|
breakpoint: '800px',
|
|
numVisible: 3,
|
|
numScroll: 1,
|
|
},
|
|
]
|
|
|
|
const onVariantSelected = (event: Event, id: number) => {
|
|
if (id === currentSelection.value) {
|
|
return
|
|
}
|
|
|
|
// prevent resizing from image swap by storing the previous width/height
|
|
if (!!imageRef.value) {
|
|
imageRef.value.style.width = `${imageRef.value.clientWidth}px`
|
|
imageRef.value.style.height = `${imageRef.value.clientHeight}px`
|
|
}
|
|
|
|
// remove the image for a frame so that it won't get stuck on the old one while the new one loads
|
|
currentSelection.value = -1
|
|
requestAnimationFrame(() => {
|
|
currentSelection.value = id
|
|
|
|
// allow a second for the image to load before unsetting the width/height
|
|
setTimeout(() => {
|
|
if (!!imageRef.value) {
|
|
imageRef.value.style.width = `unset`
|
|
imageRef.value.style.height = `unset`
|
|
}
|
|
}, 1000)
|
|
})
|
|
}
|
|
|
|
onMounted(async () => {
|
|
let config = await fetchAndParseConfig<ListWithEntries<GalleryEntry>>(routeConfig.config)
|
|
resolved.value = await fetchConfigByIdFromList(config, currentRoute.query.id as string)
|
|
document.title = routeSubConfig.fullTitle?.replace('$ENTRY', resolved.value.title || currentRoute.query.id as string)
|
|
routeStore.setBreadcrumbs(currentRoute, resolved.value.title || currentRoute.query.id as string)
|
|
window.routeConfig = {...routeConfig}
|
|
window.routeSubConfig = {...routeSubConfig}
|
|
window.routeContentConfig = {...resolved.value}
|
|
currentSelection.value = (resolved.value as GalleryEntryWithVariants).defaultVariant || 0
|
|
ready.value = true
|
|
emits('loaded')
|
|
})
|
|
</script>
|
|
|
|
<template lang="pug">
|
|
.template.gallery-view
|
|
Transition
|
|
.view-wrapper(
|
|
v-if='ready'
|
|
)
|
|
.view-outlet
|
|
.image-wrapper(
|
|
ref='image-ref'
|
|
)
|
|
PrimeVueImage(
|
|
:src='currentUrl'
|
|
:title='currentCaption || currentAlt || description'
|
|
:alt='currentAlt || currentCaption || description'
|
|
preview
|
|
)
|
|
PrimeVueCarousel(
|
|
:value='variants'
|
|
:numVisible='5'
|
|
:numScroll='5'
|
|
:responsiveOptions='carouselResponsiveOptions'
|
|
)
|
|
template(
|
|
#item='slotProps'
|
|
)
|
|
img(
|
|
:class='{ active: slotProps.index === currentSelection }'
|
|
:alt='slotProps.data.alternativeText || slotProps.data.caption || description'
|
|
:src='slotProps.data.thumbnailUrl || slotProps.data.url'
|
|
:title='slotProps.data.caption || slotProps.index'
|
|
@click='onVariantSelected($event, slotProps.index)'
|
|
)
|
|
.view-content
|
|
p.title(
|
|
v-html='title'
|
|
)
|
|
dl.info
|
|
.info-entry(
|
|
v-for='(field, key) in fields'
|
|
:class='key'
|
|
)
|
|
dt {{ key }}
|
|
dd {{ field }}
|
|
EmbedableContent(
|
|
:content='description'
|
|
)
|
|
</template>
|
|
|
|
<style lang="sass">
|
|
.p-carousel
|
|
img
|
|
cursor: pointer
|
|
max-width: 100%
|
|
</style>
|