177 lines
5.2 KiB
Vue
Raw Normal View History

2026-05-31 13:21:13 +02:00
<script setup lang="ts">
import { useEventListener } from '@vueuse/core'
import { computed, ref, watchEffect } from 'vue'
import { createFixedClicks } from '../composables/useClicks'
import { useNav } from '../composables/useNav'
import { CLICKS_MAX } from '../constants'
import { configs, pathPrefix } from '../env'
import { currentOverviewPage, overviewRowCount } from '../logic/overview'
import { breakpoints, showOverview, windowSize } from '../state'
import DrawingPreview from './DrawingPreview.vue'
import IconButton from './IconButton.vue'
import SlideContainer from './SlideContainer.vue'
import SlideWrapper from './SlideWrapper.vue'
const { currentSlideNo, go: goSlide, slides } = useNav()
function close() {
showOverview.value = false
}
function go(page: number) {
goSlide(page)
close()
}
function focus(page: number) {
if (page === currentOverviewPage.value)
return true
return false
}
const xs = breakpoints.smaller('xs')
const sm = breakpoints.smaller('sm')
const padding = 4 * 16 * 2
const gap = 2 * 16
const cardWidth = computed(() => {
if (xs.value)
return windowSize.width.value - padding
else if (sm.value)
return (windowSize.width.value - padding - gap) / 2
return 300
})
const rowCount = computed(() => {
return Math.floor((windowSize.width.value - padding) / (cardWidth.value + gap))
})
const keyboardBuffer = ref<string>('')
useEventListener('keypress', (e) => {
if (!showOverview.value) {
keyboardBuffer.value = ''
return
}
if (e.key === 'Enter') {
e.preventDefault()
if (keyboardBuffer.value) {
go(+keyboardBuffer.value)
keyboardBuffer.value = ''
}
else {
go(currentOverviewPage.value)
}
return
}
const num = Number.parseInt(e.key.replace(/\D/g, ''))
if (Number.isNaN(num)) {
keyboardBuffer.value = ''
return
}
if (!keyboardBuffer.value && num === 0)
return
keyboardBuffer.value += String(num)
// beyond the number of slides, reset
if (+keyboardBuffer.value >= slides.value.length) {
keyboardBuffer.value = ''
return
}
const extactMatch = slides.value.findIndex(i => `/${i.no}` === keyboardBuffer.value)
if (extactMatch !== -1)
currentOverviewPage.value = extactMatch + 1
// When the input number is the largest at the number of digits, we go to that page directly.
if (+keyboardBuffer.value * 10 > slides.value.length) {
go(+keyboardBuffer.value)
keyboardBuffer.value = ''
}
})
watchEffect(() => {
// Watch currentPage, make sure every time we open overview,
// we focus on the right page.
currentOverviewPage.value = currentSlideNo.value
// Watch rowCount, make sure up and down shortcut work correctly.
overviewRowCount.value = rowCount.value
})
</script>
<template>
<Transition
enter-active-class="duration-150 ease-out"
enter-from-class="opacity-0 scale-102 !backdrop-blur-0px"
leave-active-class="duration-200 ease-in"
leave-to-class="opacity-0 scale-102 !backdrop-blur-0px"
>
<div
v-if="showOverview"
class="fixed left-0 right-0 top-0 h-[calc(var(--vh,1vh)*100)] z-modal bg-main !bg-opacity-75 p-16 py-20 overflow-y-auto backdrop-blur-5px select-none"
@click="close"
>
<div
class="grid gap-y-4 gap-x-8 w-full"
:style="`grid-template-columns: repeat(auto-fit,minmax(${cardWidth}px,1fr))`"
>
<div
v-for="(route, idx) of slides"
:key="route.no"
class="relative"
>
<div
class="inline-block border rounded overflow-hidden bg-main hover:border-primary transition"
:class="(focus(idx + 1) || currentOverviewPage === idx + 1) ? 'border-primary' : 'border-main'"
@click="go(route.no)"
>
<SlideContainer
:key="route.no"
:no="route.no"
:use-snapshot="configs.overviewSnapshots"
:width="cardWidth"
class="pointer-events-none"
>
<SlideWrapper
:clicks-context="createFixedClicks(route, CLICKS_MAX)"
:route="route"
render-context="overview"
/>
<DrawingPreview :page="route.no" />
</SlideContainer>
</div>
<div
class="absolute top-0"
:style="`left: ${cardWidth + 5}px`"
>
<template v-if="keyboardBuffer && String(idx + 1).startsWith(keyboardBuffer)">
<span class="text-green font-bold">{{ keyboardBuffer }}</span>
<span class="opacity-50">{{ String(idx + 1).slice(keyboardBuffer.length) }}</span>
</template>
<span v-else class="opacity-50">
{{ idx + 1 }}
</span>
</div>
</div>
</div>
</div>
</Transition>
<div v-if="showOverview" class="fixed top-4 right-4 z-modal text-gray-400 flex flex-col items-center gap-2">
<IconButton title="Close" class="text-2xl" @click="close">
<div class="i-carbon:close" />
</IconButton>
<IconButton
v-if="__SLIDEV_FEATURE_PRESENTER__"
as="a"
title="Slides Overview"
target="_blank"
:href="`${pathPrefix}overview`"
tab-index="-1"
class="text-2xl"
>
<div class="i-carbon:list-boxes" />
</IconButton>
</div>
</template>