From 960fea2a32590e95e26a4abca4027e6f42c5c493 Mon Sep 17 00:00:00 2001 From: valere Date: Sun, 6 Oct 2024 22:00:35 +0200 Subject: [PATCH] FEAT: infinite scroll --- components/list.vue | 35 +++++++++++++------ composables/useMouse.ts | 19 ---------- composables/useScrollEnd.ts | 27 ++++++++++++++ nuxt.config.ts | 4 ++- server/api/{list.ts => explore.ts} | 5 ++- server/api/explore/[page].ts | 13 +++++++ .../api/search/{[terms].ts => [termspage].ts} | 7 ++-- 7 files changed, 72 insertions(+), 38 deletions(-) delete mode 100644 composables/useMouse.ts create mode 100644 composables/useScrollEnd.ts rename server/api/{list.ts => explore.ts} (72%) create mode 100644 server/api/explore/[page].ts rename server/api/search/{[terms].ts => [termspage].ts} (84%) diff --git a/components/list.vue b/components/list.vue index 712d4a3..cb8f193 100644 --- a/components/list.vue +++ b/components/list.vue @@ -5,11 +5,13 @@ class="w-full flex bg-slate-800 font-semibold rounded-full backdrop-blur sticky top-0 justify-center items-center hover:ring"> - + @keypress.enter="pressEnter()"> + {{ films.length }} + @@ -29,26 +31,39 @@ const config = useRuntimeConfig() const terms = ref('') const films = ref([]) +const page = ref(1) +const loading = ref(false) +const { onScrollEnd } = useScrollEnd() const search = async () => { + loading.value = true if (terms.value !== '') { - films.value = [] - const { data } = await useFetch(`/api/search/${terms.value}`) - films.value = data.value + const { data } = await useFetch(`/api/search/${terms.value}-${page.value}`) + console.log(data) + films.value.push(...data.value) } else { - initList() + const { data } = await useFetch(`/api/explore/${page.value}`) + films.value.push(...data.value) } + loading.value = false } -const initList = async () => { - const { data } = await useFetch(`/api/list`) - films.value = data.value +const pressEnter = () => { + page.value = 1 + films.value = [] + search() } +onScrollEnd(() => { + page.value += 1 + search() +}) + onMounted(() => { nextTick(() => { - initList() + search() }) + }) diff --git a/composables/useMouse.ts b/composables/useMouse.ts deleted file mode 100644 index cfc021a..0000000 --- a/composables/useMouse.ts +++ /dev/null @@ -1,19 +0,0 @@ -export function useMouse() { - // state encapsulated and managed by the composable - const x = ref(0) - const y = ref(0) - - // a composable can update its managed state over time. - function update(event) { - x.value = event.pageX - y.value = event.pageY - } - - // a composable can also hook into its owner component's - // lifecycle to setup and teardown side effects. - onMounted(() => window.addEventListener('mousemove', update)) - onUnmounted(() => window.removeEventListener('mousemove', update)) - - // expose managed state as return value - return { x, y } -} diff --git a/composables/useScrollEnd.ts b/composables/useScrollEnd.ts new file mode 100644 index 0000000..bcc965a --- /dev/null +++ b/composables/useScrollEnd.ts @@ -0,0 +1,27 @@ + +export function useScrollEnd() { + let callback = null + + const handleScroll = () => { + const scrollPosition = window.innerHeight + window.scrollY + const pageHeight = document.documentElement.offsetHeight + + if (scrollPosition >= pageHeight && typeof callback === 'function') { + callback() + } + } + + const onScrollEnd = (cb) => { + callback = cb + } + + onMounted(() => { + window.addEventListener('scroll', handleScroll) + }) + + onUnmounted(() => { + window.removeEventListener('scroll', handleScroll) + }) + + return { onScrollEnd } +} diff --git a/nuxt.config.ts b/nuxt.config.ts index fd14b82..e703db3 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -23,4 +23,6 @@ export default defineNuxtConfig({ IMG_BASE_URL: 'https://media.themoviedb.org/t/p/w220_and_h330_face/', } }, -}) + + compatibilityDate: '2024-10-06', +}) \ No newline at end of file diff --git a/server/api/list.ts b/server/api/explore.ts similarity index 72% rename from server/api/list.ts rename to server/api/explore.ts index ff9dc85..ff6ad08 100644 --- a/server/api/list.ts +++ b/server/api/explore.ts @@ -1,9 +1,8 @@ import fetch from 'node-fetch' export default eventHandler(async (req) => { - const terms = req.context.params?.terms || '' - const url = 'https://api.themoviedb.org/3/discover/movie?include_adult=false&include_video=false&language=en-US&page=1&sort_by=popularity.desc' - + const page = req.context.params?.page || 1 + const url = `https://api.themoviedb.org/3/discover/movie?include_adult=false&include_video=false&language=en-US&page=${page}&sort_by=popularity.desc` const response = await fetch(url, { method: 'get', diff --git a/server/api/explore/[page].ts b/server/api/explore/[page].ts new file mode 100644 index 0000000..ff6ad08 --- /dev/null +++ b/server/api/explore/[page].ts @@ -0,0 +1,13 @@ +import fetch from 'node-fetch' + +export default eventHandler(async (req) => { + const page = req.context.params?.page || 1 + const url = `https://api.themoviedb.org/3/discover/movie?include_adult=false&include_video=false&language=en-US&page=${page}&sort_by=popularity.desc` + + const response = await fetch(url, { + method: 'get', + headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiI2MmVmZmYzYWU2YWYyNWQ0NTY4OGY3OTkxYjgyNmNhOCIsIm5iZiI6MTcyODA1NTIwMy4wNzcyNywic3ViIjoiNjcwMDA2ZjQ5ZWJlYTE5MDA2ZjgxZmJhIiwic2NvcGVzIjpbImFwaV9yZWFkIl0sInZlcnNpb24iOjF9.XuH-_0UggmULCgQQajKc-QsmlRYW2rqSenyhguE6wRU' } + }) + const data = await response.json() + return data.results +}) diff --git a/server/api/search/[terms].ts b/server/api/search/[termspage].ts similarity index 84% rename from server/api/search/[terms].ts rename to server/api/search/[termspage].ts index ceaa577..dae39a9 100644 --- a/server/api/search/[terms].ts +++ b/server/api/search/[termspage].ts @@ -11,12 +11,9 @@ interface Film { let films: Film[] = [] - - - export default eventHandler(async (req) => { - const terms = req.context.params?.terms || '' - const url = `https://api.themoviedb.org/3/search/movie?query=${terms}&include_adult=false&language=en-US&page=1` + const [terms, page] = req.context.params?.termspage.split('-') || '' + const url = `https://api.themoviedb.org/3/search/movie?query=${terms}&page=${page}&include_adult=false&language=en-US&page=1` const response = await fetch(url, { method: 'get',