mackenzii/projects/frontend/src/views/gallery/gallery-view.vue
2025-04-17 22:54:41 -04:00

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>