From e61f909f313ea043b835c1e655300351d64d2450 Mon Sep 17 00:00:00 2001
From: patak <matias.capeletto@gmail.com>
Date: Wed, 16 Nov 2022 17:11:08 +0100
Subject: [PATCH] feat: common paginator component (#9)

---
 components/account/AccountPaginator.client.vue | 18 +++++++-----------
 components/common/CommonPaginator.vue          | 17 +++++++++++++++++
 .../NotificationPaginator.client.vue           | 18 +++++++-----------
 .../timeline/TimelinePaginator.client.vue      | 18 +++++++-----------
 composables/paginator.ts                       | 18 +++++++++---------
 types/index.ts                                 |  2 ++
 6 files changed, 49 insertions(+), 42 deletions(-)
 create mode 100644 components/common/CommonPaginator.vue

diff --git a/components/account/AccountPaginator.client.vue b/components/account/AccountPaginator.client.vue
index e82fc1ee..91d8b668 100644
--- a/components/account/AccountPaginator.client.vue
+++ b/components/account/AccountPaginator.client.vue
@@ -5,18 +5,14 @@ const { paginator } = defineProps<{
   paginator: Paginator<any, Account[]>
 }>()
 
-const { items: accounts, isLoading, isDone, endAnchor } = usePaginator(paginator)
+const { items: accounts, state, endAnchor } = usePaginator(paginator)
 </script>
 
 <template>
-  <template v-for="account of accounts" :key="account.id">
-    <AccountCard :account="account" border="t border" pt-4 />
-  </template>
-  <div ref="endAnchor" />
-  <div v-if="isLoading">
-    Loading...
-  </div>
-  <div v-if="isDone">
-    End of list
-  </div>
+  <CommonPaginator :state="state">
+    <template v-for="account of accounts" :key="account.id">
+      <AccountCard :account="account" border="t border" pt-4 />
+    </template>
+    <div ref="endAnchor" />
+  </CommonPaginator>
 </template>
diff --git a/components/common/CommonPaginator.vue b/components/common/CommonPaginator.vue
new file mode 100644
index 00000000..96127a8d
--- /dev/null
+++ b/components/common/CommonPaginator.vue
@@ -0,0 +1,17 @@
+<script setup lang="ts">
+import type { PaginatorState } from '~/types'
+
+defineProps<{
+  state: PaginatorState
+}>()
+</script>
+
+<template>
+  <slot />
+  <div v-if="state === 'loading'" p5 color-gray-5>
+    Loading...
+  </div>
+  <div v-if="state === 'done'" p5 color-gray>
+    End of list
+  </div>
+</template>
diff --git a/components/notification/NotificationPaginator.client.vue b/components/notification/NotificationPaginator.client.vue
index 8f7674ea..3dcce353 100644
--- a/components/notification/NotificationPaginator.client.vue
+++ b/components/notification/NotificationPaginator.client.vue
@@ -5,18 +5,14 @@ const { paginator } = defineProps<{
   paginator: Paginator<any, Notification[]>
 }>()
 
-const { items: notifications, isLoading, isDone, endAnchor } = usePaginator(paginator)
+const { items: notifications, state, endAnchor } = usePaginator(paginator)
 </script>
 
 <template>
-  <template v-for="notification of notifications" :key="notification.id">
-    <NotificationCard :notification="notification" border="t border" pt-4 />
-  </template>
-  <div ref="endAnchor" />
-  <div v-if="isLoading">
-    Loading...
-  </div>
-  <div v-if="isDone">
-    End of list
-  </div>
+  <CommonPaginator :state="state">
+    <template v-for="notification of notifications" :key="notification.id">
+      <NotificationCard :notification="notification" border="t border" pt-4 />
+    </template>
+    <div ref="endAnchor" />
+  </CommonPaginator>
 </template>
diff --git a/components/timeline/TimelinePaginator.client.vue b/components/timeline/TimelinePaginator.client.vue
index 61fdaef4..dc50598f 100644
--- a/components/timeline/TimelinePaginator.client.vue
+++ b/components/timeline/TimelinePaginator.client.vue
@@ -5,18 +5,14 @@ const { paginator } = defineProps<{
   paginator: Paginator<any, Status[]>
 }>()
 
-const { items: statuses, isLoading, isDone, endAnchor } = usePaginator(paginator)
+const { items: statuses, state, endAnchor } = usePaginator(paginator)
 </script>
 
 <template>
-  <template v-for="status of statuses" :key="status.id">
-    <StatusCard :status="status" border="t border" pt-4 />
-  </template>
-  <div ref="endAnchor" />
-  <div v-if="isLoading">
-    Loading...
-  </div>
-  <div v-if="isDone">
-    End of list
-  </div>
+  <CommonPaginator :state="state">
+    <template v-for="status of statuses" :key="status.id">
+      <StatusCard :status="status" border="t border" pt-4 />
+    </template>
+    <div ref="endAnchor" />
+  </CommonPaginator>
 </template>
diff --git a/composables/paginator.ts b/composables/paginator.ts
index 82233b22..4c3055ed 100644
--- a/composables/paginator.ts
+++ b/composables/paginator.ts
@@ -1,8 +1,8 @@
 import type { Paginator } from 'masto'
+import type { PaginatorState } from '~/types'
 
 export function usePaginator<T>(paginator: Paginator<any, T[]>) {
-  let isLoading = $ref(false)
-  let isDone = $ref(false)
+  let state = $ref('ready' as PaginatorState)
   const items = $ref<T[]>([])
 
   const endAnchor = ref<HTMLDivElement>()
@@ -10,16 +10,16 @@ export function usePaginator<T>(paginator: Paginator<any, T[]>) {
   const isInScreen = $computed(() => bound.top < window.innerHeight * 2)
 
   async function loadNext() {
-    if (isLoading || isDone)
+    if (state === 'loading' || state === 'done')
       return
 
-    isLoading = true
+    state = 'loading'
     const result = await paginator.next()
-    if (result.done)
-      isDone = true
+    state = result.done ? 'done' : 'ready'
+
     if (result.value?.length)
       items.push(...result.value)
-    isLoading = false
+
     await nextTick()
     bound.update()
   }
@@ -31,11 +31,11 @@ export function usePaginator<T>(paginator: Paginator<any, T[]>) {
   watch(
     () => isInScreen,
     () => {
-      if (isInScreen && !isLoading)
+      if (isInScreen && state !== 'loading')
         loadNext()
     },
     { immediate: true },
   )
 
-  return { items, isLoading, isDone, endAnchor }
+  return { items, state, endAnchor }
 }
diff --git a/types/index.ts b/types/index.ts
index 1a2dc8d2..61485d41 100644
--- a/types/index.ts
+++ b/types/index.ts
@@ -15,3 +15,5 @@ export interface UserLogin {
   token: string
   account?: AccountCredentials
 }
+
+export type PaginatorState = 'ready' | 'loading' | 'done'