254 lines
6.8 KiB
Vue
254 lines
6.8 KiB
Vue
<script setup>
|
|
import PostComponent from '@/components/PostComponent.vue'
|
|
import { usePostsStore } from '@/stores/posts.js'
|
|
import { nextTick, onMounted, ref } from 'vue'
|
|
import WelcomeComponent from '@/components/WelcomeComponent.vue'
|
|
import { useRoute, useRouter } from 'vue-router'
|
|
|
|
const postsStore = usePostsStore()
|
|
const route = useRoute()
|
|
const router = useRouter()
|
|
|
|
const menu = ref(null)
|
|
const menuItems = ref([])
|
|
|
|
const fullpage = ref(null)
|
|
let fpMouseInScrollableElement = false
|
|
let fpIsScrolling = false
|
|
let fpCurrentlyInView = 0
|
|
let fpTouchScroll = null
|
|
let fpTouchScrollX = 0
|
|
let fpTouchScrollY = 0
|
|
let fpResizeTimeout = false
|
|
|
|
onMounted(async () => {
|
|
await postsStore.fetchPosts()
|
|
await nextTick()
|
|
fullpageScrollToAnchor(window.location.hash.substring(1))
|
|
})
|
|
|
|
window.addEventListener('popstate', (event) => {
|
|
fullpageScrollToAnchor(window.location.hash.substring(1))
|
|
})
|
|
|
|
window.addEventListener('resize', (event) => {
|
|
clearTimeout(fpResizeTimeout)
|
|
fpResizeTimeout = setTimeout(fullpageScrollToAnchor(window.location.hash.substring(1)), 500)
|
|
})
|
|
|
|
function onFullpageWheel(event) {
|
|
if (fpMouseInScrollableElement) return
|
|
if (fpIsScrolling) return
|
|
|
|
// prevent scroll
|
|
event.preventDefault()
|
|
event.stopPropagation()
|
|
|
|
fullpageScrollDir(event.deltaY > 0 ? 1 : -1)
|
|
}
|
|
|
|
document.addEventListener('keydown', (event) => {
|
|
if (event.key === 'ArrowUp' || event.key === 'PageUp') fullpageScrollDir(-1)
|
|
else if (event.key === 'ArrowDown' || event.key === ' ' || event.key === 'PageDown')
|
|
fullpageScrollDir(1)
|
|
else return
|
|
|
|
event.preventDefault()
|
|
event.stopPropagation()
|
|
})
|
|
|
|
// touch screen support
|
|
function onFullpageTouchStart(event) {
|
|
// more than one finger, not a scroll
|
|
if (fpTouchScroll) {
|
|
fpTouchScroll = null
|
|
return
|
|
}
|
|
|
|
const touch = event.changedTouches.item(0)
|
|
fpTouchScrollX = touch.clientX
|
|
fpTouchScrollY = touch.clientY
|
|
fpTouchScroll = touch
|
|
}
|
|
|
|
function onFullpageTouchMove(event) {
|
|
if (fpMouseInScrollableElement) {
|
|
return
|
|
}
|
|
event.preventDefault()
|
|
event.stopPropagation()
|
|
}
|
|
|
|
function onFullpageTouchEnd(event) {
|
|
if (fpMouseInScrollableElement) {
|
|
fpMouseInScrollableElement = false
|
|
return
|
|
}
|
|
if (fpIsScrolling) return
|
|
|
|
const touch = event.changedTouches.item(0)
|
|
const endTouchX = touch.clientX
|
|
const endTouchY = touch.clientY
|
|
|
|
const diffX = endTouchX - fpTouchScrollX
|
|
const diffY = endTouchY - fpTouchScrollY
|
|
|
|
if (Math.abs(diffY) > Math.abs(diffX)) {
|
|
// small diff, not worth scroll
|
|
if (Math.abs(diffY) < 10) {
|
|
fpTouchScroll = null
|
|
return
|
|
}
|
|
if (diffY > 0) fullpageScrollDir(-1)
|
|
else fullpageScrollDir(1)
|
|
}
|
|
|
|
fpTouchScroll = null
|
|
}
|
|
|
|
// actual scrolling
|
|
function fullpageScrollDir(direction) {
|
|
if (direction < 0 && fpCurrentlyInView <= 0) return
|
|
if (direction > 0 && fpCurrentlyInView >= fullpage.value.children.length - 1) return
|
|
|
|
fullpageScrollToIndex(fpCurrentlyInView + direction)
|
|
}
|
|
|
|
function fullpageScrollToAnchor(target) {
|
|
for (let i = 0; i < fullpage.value.children.length; i++) {
|
|
const slide = fullpage.value.children[i]
|
|
if (slide.dataset.anchor === target) {
|
|
fullpageScrollToIndex(i)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
const postComponents = ref([])
|
|
function fullpageScrollToIndex(target) {
|
|
fpIsScrolling = true
|
|
if (fpCurrentlyInView !== 0) {
|
|
const from = postComponents.value[fpCurrentlyInView - 1]
|
|
if (Object.hasOwn(from, 'onInvisible')) from.onInvisible()
|
|
}
|
|
if (target !== 0) {
|
|
const to = postComponents.value[target - 1]
|
|
if (Object.hasOwn(to, 'onVisible')) to.onVisible()
|
|
}
|
|
const to = fullpage.value.children[target]
|
|
onLeave(null, to.dataset.anchor)
|
|
fullpage.value.scroll({
|
|
top: window.innerHeight * target,
|
|
left: 0,
|
|
behavior: 'smooth'
|
|
})
|
|
router.push({ name: route.name, hash: '#' + to.dataset.anchor })
|
|
fpCurrentlyInView = target
|
|
for (let i = 0; i < menu.value.children.length; i++) {
|
|
const menuItem = menu.value.children[i]
|
|
if (menuItem.dataset.menuanchor === to.dataset.anchor) {
|
|
menuItem.classList.add('active')
|
|
} else {
|
|
menuItem.classList.remove('active')
|
|
}
|
|
}
|
|
setTimeout(() => {
|
|
fpIsScrolling = false
|
|
}, 500)
|
|
}
|
|
|
|
function onFullpageScrollEnd() {
|
|
fpIsScrolling = false
|
|
}
|
|
|
|
function fullpageOnScrollableElementEnter() {
|
|
fpMouseInScrollableElement = true
|
|
}
|
|
function fullpageOnScrollableElementLeave() {
|
|
fpMouseInScrollableElement = false
|
|
}
|
|
|
|
// eslint-disable-next-line no-unused-vars
|
|
function onLeave(origin, destination) {
|
|
if (destination === 'welcome-section') {
|
|
menu.value.classList.add('opacity-0')
|
|
menu.value.classList.remove('scrollable-element')
|
|
} else {
|
|
menu.value.classList.remove('opacity-0')
|
|
menu.value.classList.add('scrollable-element')
|
|
|
|
// Disgusting way to make the menu automatically scroll when changing slide.
|
|
// For some reason this doesn't work on Chrome, I love web dev
|
|
for (const menuItem of menuItems.value) {
|
|
if (menuItem.dataset.menuanchor === destination) {
|
|
menuItem.scrollIntoView({
|
|
behavior: 'smooth',
|
|
inline: 'center'
|
|
})
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div
|
|
id="menu-grid"
|
|
class="fixed z-50 h-dvh w-full grid grid-cols-5 grid-rows-3 place-items-stretch pointer-events-none"
|
|
>
|
|
<ul
|
|
id="menu"
|
|
class="col-start-1 col-span-full row-start-1 max-h-32 lg:max-h-52 h-fit mt-28 sm:mt-16 lg:m-0 lg:col-start-5 lg:col-span-1 lg:row-start-3 lg:row-span-1 pointer-events-auto overflow-y-scroll overscroll-y-contain transition-opacity duration-1000 opacity-0"
|
|
ref="menu"
|
|
@mouseenter="fullpageOnScrollableElementEnter"
|
|
@touchstart="fullpageOnScrollableElementEnter"
|
|
@mouseleave="fullpageOnScrollableElementLeave"
|
|
>
|
|
<li
|
|
:data-menuanchor="'post-' + post.id"
|
|
v-for="post in postsStore.posts"
|
|
:key="post.id"
|
|
class="m-2 backdrop-blur-sm rounded-lg bg-black/10 hover:bg-gray-500/10 transition-colors duration-200"
|
|
ref="menuItems"
|
|
>
|
|
<a
|
|
:href="'#post-' + post.id"
|
|
class="block text-right px-4 py-3 transition-all duration-300"
|
|
>{{ post.formatedDate }}</a
|
|
>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
<div
|
|
class="fixed w-screen h-dvh overflow-y-scroll overscroll-y-contain"
|
|
ref="fullpage"
|
|
@wheel="onFullpageWheel"
|
|
@touchstart="onFullpageTouchStart"
|
|
@touchend="onFullpageTouchEnd"
|
|
@scrollend="onFullpageScrollEnd"
|
|
@touchmove="onFullpageTouchMove"
|
|
>
|
|
<WelcomeComponent class="w-screen h-dvh" />
|
|
<PostComponent
|
|
v-for="post in postsStore.posts"
|
|
:key="post.id"
|
|
:post="post"
|
|
@scrollableelemententer="fullpageOnScrollableElementEnter"
|
|
@scrollableelementleave="fullpageOnScrollableElementLeave"
|
|
ref="postComponents"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
#menu li.active,
|
|
#menu li.active:hover {
|
|
background-color: rgba(255, 255, 255, 0.1);
|
|
}
|
|
|
|
#menu li.active a,
|
|
#menu li.active:hover a:hover {
|
|
font-weight: bold;
|
|
}
|
|
</style>
|