|
| 1 | +import { computed } from 'vue'; |
| 2 | + |
| 3 | +import type { Ref } from 'vue'; |
| 4 | + |
| 5 | +import type { ListScrollItem, ListScrollSourceItem, OnScroll, OnUpdated } from '~/components/common/list/ListScroll.model'; |
| 6 | +import type { TraktClientPagination } from '~/models/trakt/trakt-client.model'; |
| 7 | + |
| 8 | +export type ListScrollSourceItemWithDate<T extends string> = ListScrollSourceItem & Partial<Record<T, string | number | Date>>; |
| 9 | + |
| 10 | +export const useListScroll = <D extends string, T extends ListScrollSourceItemWithDate<D>>( |
| 11 | + items: Ref<T[]>, |
| 12 | + dateFn?: D | ((item: T) => ListScrollSourceItemWithDate<D>[D]), |
| 13 | +) => |
| 14 | + computed<ListScrollItem[]>(() => { |
| 15 | + const array = items.value; |
| 16 | + if (!array.length) return []; |
| 17 | + return array.map((item, index) => { |
| 18 | + const _item: ListScrollItem = { ...item, index, loading: typeof item.id === 'number' && item.id < 0 }; |
| 19 | + |
| 20 | + if ('movie' in _item) _item.type = 'movie'; |
| 21 | + else if ('episode' in _item) _item.type = 'episode'; |
| 22 | + else if ('season' in _item) _item.type = 'season'; |
| 23 | + else if ('show' in _item) _item.type = 'show'; |
| 24 | + |
| 25 | + if (!_item || !dateFn) return _item; |
| 26 | + const _date = typeof dateFn === 'function' ? dateFn(item) : item[dateFn]; |
| 27 | + if (!_date) return _item; |
| 28 | + |
| 29 | + const date: ListScrollItem['date'] = { current: new Date(_date!) }; |
| 30 | + const previous = typeof dateFn === 'function' ? dateFn(array[index - 1]) : array[index - 1]?.[dateFn]; |
| 31 | + if (index > 0 && previous) date.previous = new Date(previous); |
| 32 | + const next = typeof dateFn === 'function' ? dateFn(array[index + 1]) : array[index + 1]?.[dateFn]; |
| 33 | + if (next) date.next = new Date(next); |
| 34 | + date.sameDayAsPrevious = date.previous?.toLocaleDateString() === date.current?.toLocaleDateString(); |
| 35 | + date.sameDayAsNext = date.next?.toLocaleDateString() === date.current?.toLocaleDateString(); |
| 36 | + |
| 37 | + return { ..._item, date }; |
| 38 | + }); |
| 39 | + }); |
| 40 | + |
| 41 | +export const useListScrollEvents = ( |
| 42 | + callback: (query: { page: number }) => Promise<unknown>, |
| 43 | + { |
| 44 | + data, |
| 45 | + pagination, |
| 46 | + loading, |
| 47 | + belowThreshold, |
| 48 | + }: { data: Ref<ListScrollItem[]>; pagination: Ref<TraktClientPagination | undefined>; loading: Ref<boolean>; belowThreshold: Ref<boolean> }, |
| 49 | +) => { |
| 50 | + const onScroll: OnScroll = async listRef => { |
| 51 | + const key = data.value[data.value.length - 1].id; |
| 52 | + await callback({ |
| 53 | + page: pagination.value?.page ? pagination.value.page + 1 : 0, |
| 54 | + }); |
| 55 | + listRef.value?.scrollTo({ key, debounce: true }); |
| 56 | + }; |
| 57 | + |
| 58 | + const onLoadMore = async () => |
| 59 | + callback({ |
| 60 | + page: pagination.value?.page ? pagination.value.page + 1 : 0, |
| 61 | + }); |
| 62 | + |
| 63 | + /** |
| 64 | + * This is a workaround for the onUpdated lifecycle hook not triggering when wrapped in transition. |
| 65 | + */ |
| 66 | + const onUpdated: OnUpdated = listRef => { |
| 67 | + const { scrollHeight, clientHeight } = listRef.value?.$el?.firstElementChild ?? {}; |
| 68 | + if (scrollHeight !== clientHeight || !belowThreshold.value || loading.value) return; |
| 69 | + |
| 70 | + return onLoadMore(); |
| 71 | + }; |
| 72 | + |
| 73 | + return { onScroll, onUpdated, onLoadMore }; |
| 74 | +}; |
| 75 | + |
| 76 | +export const addLoadMore = ( |
| 77 | + items: Ref<ListScrollItem[]>, |
| 78 | + pagination: Ref<TraktClientPagination | undefined>, |
| 79 | + search: Ref<string>, |
| 80 | +): Ref<ListScrollItem[]> => { |
| 81 | + return computed(() => { |
| 82 | + const array = items.value; |
| 83 | + if (!array.length) return array; |
| 84 | + if (!search.value) return array; |
| 85 | + if (!pagination.value?.page) return array; |
| 86 | + if (!pagination.value?.pageCount) return array; |
| 87 | + if (pagination.value.page === pagination.value.pageCount) return array; |
| 88 | + if (array.length && array[array.length - 1].id === 'load-more') return array; |
| 89 | + const loadMore: ListScrollItem = { id: 'load-more', index: items.value.length }; |
| 90 | + return [...array, loadMore]; |
| 91 | + }); |
| 92 | +}; |
0 commit comments