212 lines
5.6 KiB
Vue
212 lines
5.6 KiB
Vue
<template>
|
|
<div ref="resultSection" class="resultSection">
|
|
<page-header v-bind="{ title, info, shortList }" />
|
|
|
|
<div
|
|
v-if="!loadedPages.includes(1) && loading == false"
|
|
class="button-container"
|
|
>
|
|
<seasoned-button @click="loadLess" class="load-button" :fullWidth="true"
|
|
>load previous</seasoned-button
|
|
>
|
|
</div>
|
|
|
|
<results-list v-bind="{ results, shortList, loading }" />
|
|
<loader v-if="loading" />
|
|
|
|
<div ref="loadMoreButton" class="button-container">
|
|
<seasoned-button
|
|
class="load-button"
|
|
v-if="!loading && !shortList && page != totalPages && results.length"
|
|
@click="loadMore"
|
|
:fullWidth="true"
|
|
>load more</seasoned-button
|
|
>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { defineProps, ref, computed, onMounted } from "vue";
|
|
import { useStore } from "vuex";
|
|
import PageHeader from "@/components/PageHeader.vue";
|
|
import ResultsList from "@/components/ResultsList.vue";
|
|
import SeasonedButton from "@/components/ui/SeasonedButton.vue";
|
|
import Loader from "@/components/ui/Loader.vue";
|
|
import { getTmdbMovieListByName } from "../api";
|
|
import type { Ref } from "vue";
|
|
import type { IList, ListResults } from "../interfaces/IList";
|
|
import type ISection from "../interfaces/ISection";
|
|
|
|
interface Props extends ISection {
|
|
title: string;
|
|
apiFunction: (page: number) => Promise<IList>;
|
|
shortList?: boolean;
|
|
}
|
|
|
|
const store = useStore();
|
|
const props = defineProps<Props>();
|
|
|
|
const results: Ref<ListResults> = ref([]);
|
|
const page: Ref<number> = ref(1);
|
|
const loadedPages: Ref<number[]> = ref([]);
|
|
const totalResults: Ref<number> = ref(0);
|
|
const totalPages: Ref<number> = ref(0);
|
|
const loading: Ref<boolean> = ref(true);
|
|
const autoLoad: Ref<boolean> = ref(false);
|
|
const observer: Ref<any> = ref(null);
|
|
const resultSection = ref(null);
|
|
const loadMoreButton = ref(null);
|
|
|
|
page.value = getPageFromUrl() || page.value;
|
|
if (results.value?.length === 0) getListResults();
|
|
|
|
const info = computed(() => {
|
|
if (results.value.length === 0) return [null, null];
|
|
|
|
const pageCount = pageCountString(page.value, totalPages.value);
|
|
const resultCount = resultCountString(results.value, totalResults.value);
|
|
return [pageCount, resultCount];
|
|
});
|
|
|
|
onMounted(() => {
|
|
if (!props?.shortList) setupAutoloadObserver();
|
|
});
|
|
|
|
function pageCountString(page: Number, totalPages: Number) {
|
|
return `Page ${page} of ${totalPages}`;
|
|
}
|
|
|
|
function resultCountString(results: ListResults, totalResults: number) {
|
|
const loadedResults = results.length;
|
|
const _totalResults = totalResults < 10000 ? totalResults : "∞";
|
|
return `${loadedResults} of ${_totalResults} results`;
|
|
}
|
|
|
|
function getPageFromUrl() {
|
|
const page = new URLSearchParams(window.location.search).get("page");
|
|
if (!page) return null;
|
|
|
|
return Number(page);
|
|
}
|
|
|
|
function getListResults(front = false) {
|
|
props
|
|
.apiFunction(page.value)
|
|
.then(listResponse => {
|
|
if (!front)
|
|
results.value = results.value.concat(...listResponse.results);
|
|
else results.value = listResponse.results.concat(...results.value);
|
|
|
|
page.value = listResponse.page;
|
|
loadedPages.value.push(page.value);
|
|
loadedPages.value = loadedPages.value.sort((a, b) => a - b);
|
|
totalPages.value = listResponse.total_pages;
|
|
totalResults.value = listResponse.total_results;
|
|
})
|
|
.then(updateQueryParams)
|
|
.finally(() => (loading.value = false));
|
|
}
|
|
|
|
function loadMore() {
|
|
if (!autoLoad.value) {
|
|
autoLoad.value = true;
|
|
}
|
|
|
|
loading.value = true;
|
|
let maxPage = [...loadedPages.value].slice(-1)[0];
|
|
|
|
if (maxPage == NaN) return;
|
|
page.value = maxPage + 1;
|
|
getListResults();
|
|
}
|
|
|
|
function loadLess() {
|
|
loading.value = true;
|
|
const minPage = loadedPages.value[0];
|
|
if (minPage === 1) return;
|
|
|
|
page.value = minPage - 1;
|
|
getListResults(true);
|
|
}
|
|
|
|
function updateQueryParams() {
|
|
let params = new URLSearchParams(window.location.search);
|
|
if (params.has("page")) {
|
|
params.set("page", page.value?.toString());
|
|
} else if (page.value > 1) {
|
|
params.append("page", page.value?.toString());
|
|
}
|
|
|
|
window.history.replaceState(
|
|
{},
|
|
"search",
|
|
`${window.location.protocol}//${window.location.hostname}${
|
|
window.location.port ? `:${window.location.port}` : ""
|
|
}${window.location.pathname}${
|
|
params.toString().length ? `?${params}` : ""
|
|
}`
|
|
);
|
|
}
|
|
|
|
function handleButtonIntersection(entries) {
|
|
entries.map(entry =>
|
|
entry.isIntersecting && autoLoad.value ? loadMore() : null
|
|
);
|
|
}
|
|
|
|
function setupAutoloadObserver() {
|
|
observer.value = new IntersectionObserver(handleButtonIntersection, {
|
|
root: resultSection.value.$el,
|
|
rootMargin: "0px",
|
|
threshold: 0
|
|
});
|
|
|
|
observer.value.observe(loadMoreButton.value);
|
|
}
|
|
|
|
// created() {
|
|
// if (!this.shortList) {
|
|
// store.dispatch(
|
|
// "documentTitle/updateTitle",
|
|
// `${this.$router.history.current.name} ${this.title}`
|
|
// );
|
|
// }
|
|
// },
|
|
// beforeDestroy() {
|
|
// this.observer = undefined;
|
|
// }
|
|
// };
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
@import "src/scss/media-queries";
|
|
|
|
.resultSection {
|
|
background-color: var(--background-color);
|
|
}
|
|
|
|
.button-container {
|
|
display: flex;
|
|
justify-content: center;
|
|
display: flex;
|
|
width: 100%;
|
|
}
|
|
|
|
.load-button {
|
|
margin: 2rem 0;
|
|
|
|
@include mobile {
|
|
margin: 1rem 0;
|
|
}
|
|
|
|
&:last-of-type {
|
|
margin-bottom: 4rem;
|
|
|
|
@include mobile {
|
|
margin-bottom: 2rem;
|
|
}
|
|
}
|
|
}
|
|
</style>
|