107 lines
2.8 KiB
Vue
107 lines
2.8 KiB
Vue
<template>
|
|
<div class="infinite-reader" ref="containerRef">
|
|
<div v-for="(page, index) in pages" :key="index" class="page-wrapper">
|
|
<div v-if="page?.loading" class="loading">
|
|
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-primary"></div>
|
|
</div>
|
|
<div v-else-if="page?.error" class="error">
|
|
{{ page.error }}
|
|
</div>
|
|
<ReaderPage v-else-if="page?.base64Content" :page-data="page" :page-number="index + 1" :zoom="zoom" />
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, onMounted, onUnmounted, watch } from 'vue';
|
|
import { nextTick } from 'vue';
|
|
import ReaderPage from './ReaderPage.vue';
|
|
|
|
const props = defineProps({
|
|
pages: {
|
|
type: Array,
|
|
required: true
|
|
},
|
|
zoom: {
|
|
type: Number,
|
|
required: true
|
|
}
|
|
});
|
|
|
|
const emit = defineEmits(['pageVisible']);
|
|
|
|
const containerRef = ref(null);
|
|
const observer = ref(null);
|
|
|
|
const observeIntersection = entries => {
|
|
entries.forEach(entry => {
|
|
if (entry.isIntersecting) {
|
|
const pageIndex = parseInt(entry.target.getAttribute('data-page-index'));
|
|
emit('pageVisible', pageIndex);
|
|
}
|
|
});
|
|
};
|
|
|
|
const setupIntersectionObserver = () => {
|
|
if (observer.value) {
|
|
observer.value.disconnect();
|
|
}
|
|
|
|
observer.value = new IntersectionObserver(observeIntersection, {
|
|
root: null,
|
|
threshold: 0.5
|
|
});
|
|
|
|
nextTick(() => {
|
|
const pageElements = containerRef.value?.querySelectorAll('.page-wrapper');
|
|
if (pageElements) {
|
|
pageElements.forEach((element, index) => {
|
|
element.setAttribute('data-page-index', index);
|
|
observer.value.observe(element);
|
|
});
|
|
}
|
|
});
|
|
};
|
|
|
|
watch(
|
|
() => props.pages,
|
|
() => {
|
|
setupIntersectionObserver();
|
|
},
|
|
{ immediate: true }
|
|
);
|
|
|
|
onMounted(() => {
|
|
setupIntersectionObserver();
|
|
});
|
|
|
|
onUnmounted(() => {
|
|
if (observer.value) {
|
|
observer.value.disconnect();
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<style lang="postcss" scoped>
|
|
.infinite-reader {
|
|
@apply flex-1 flex flex-col items-center overflow-y-auto py-8;
|
|
height: calc(100vh - 8rem);
|
|
scroll-behavior: smooth;
|
|
}
|
|
|
|
.page-wrapper {
|
|
@apply w-full flex justify-center min-h-[200px] mb-4;
|
|
}
|
|
|
|
.loading,
|
|
.error {
|
|
@apply flex items-center justify-center;
|
|
width: 70vw;
|
|
min-height: 400px;
|
|
}
|
|
|
|
.error {
|
|
@apply text-red-500 text-xl bg-red-500/10 rounded-lg;
|
|
}
|
|
</style>
|