import { Getter } from '@prefecthq/prefect-design' import { SubscriptionOptions, UseSubscription, useSubscriptionWithDependencies } from '@prefecthq/vue-compositions' import merge from 'lodash.merge' import { ComputedRef, MaybeRef, Ref, computed, onScopeDispose, reactive, ref, toRef, watch } from 'vue' import { GLOBAL_API_LIMIT } from '@/compositions/useFilterPagination' import { UseSubscriptions, useSubscriptions } from '@/compositions/useSubscriptions' import { repeat } from '@/utilities/arrays' import { isDefined } from '@/utilities/variables' type PaginationFilter = { limit?: number, offset?: number, } // typescript only lets you use any here // eslint-disable-next-line @typescript-eslint/no-explicit-any type PaginationParameters = [filter?: PaginationFilter, ...any[]] type PaginationFetchAction = (...parameters: PaginationParameters) => Promise type PaginationCountAction = (...parameters: PaginationParameters) => Promise export type PaginationOptions = SubscriptionOptions & { mode?: 'page' | 'infinite', page?: MaybeRef, } export type UsePaginationParameters< TFetch extends PaginationFetchAction, TFetchParameters extends Getter | null>, TCount extends PaginationCountAction, TCountParameters extends Getter | null> > = { fetchMethod: TFetch, fetchParameters: TFetchParameters, countMethod: TCount, countParameters: TCountParameters, options?: PaginationOptions, } export type UsePaginationEntity< TFetch extends PaginationFetchAction, TCount extends PaginationCountAction, TProperty extends string > = Omit, 'results'> & { [ P in TProperty ]: ComputedRef>> } export type UsePagination< TFetch extends PaginationFetchAction, TCount extends PaginationCountAction > = { subscriptions: UseSubscriptions undefined)>['subscriptions'], results: ComputedRef>>, total: ComputedRef, pages: ComputedRef, page: Ref, next: () => void, previous: () => void, } export function usePagination< TFetch extends PaginationFetchAction, TFetchParameters extends Getter | null>, TCount extends PaginationCountAction, TCountParameters extends Getter | null> >({ fetchMethod, fetchParameters: fetchParametersGetter, countMethod, countParameters: countParametersGetter, options, }: UsePaginationParameters): UsePagination { type TFetchFilter = Parameters[0] const mode = getMode() const page = getPageRef() const pages = computed(() => Math.ceil(total.value / getLimit())) const countSubscriptionParameters = computed(() => { if (isDefined(page.value)) { const parameters = countParametersGetter() if (parameters) { return merge([], parameters) } return parameters } return null }) const countSubscription = useSubscriptionWithDependencies(countMethod, countSubscriptionParameters, options) const total = computed(() => countSubscription.response ?? 0) const fetchSubscriptions: UseSubscription[] = reactive([]) const results = computed(() => fetchSubscriptions.flatMap(subscription => subscription.response ?? []) as unknown as Awaited>) watch([total, page, fetchParametersGetter], ([total, page, parameters]) => { if (total === 0 || page === 0 || parameters === null) { fetchSubscriptions.forEach(subscription => subscription.unsubscribe()) fetchSubscriptions.splice(0) return } const newSubscriptions = getPagesToFetch(page).map(page => { const parameters = getFetchParametersForPage(page) return useSubscriptionWithDependencies(fetchMethod, parameters, options) }) fetchSubscriptions.forEach(subscription => subscription.unsubscribe()) fetchSubscriptions.splice(0, Infinity, ...newSubscriptions) }, { immediate: true, deep: true }) const { subscriptions } = useSubscriptions(() => [ countSubscription, ...fetchSubscriptions, ]) function next(): void { if (mode === 'page') { page.value++ return } const shouldLoadNextPage = page.value * getLimit() <= results.value.length if (shouldLoadNextPage) { page.value++ } } function previous(): void { page.value-- } function getPagesToFetch(page: number): number[] { if (mode === 'page') { return [page] } return repeat(page, index => index + 1) } function getFetchParametersForPage(page: number): Ref | null> { return toRef(() => { const parameters = fetchParametersGetter() if (parameters === null) { return null } const [filter, ...rest] = parameters const pageFilter = getFetchFilterForPage(page, filter) return [pageFilter, ...rest] }) as Ref | null> } function getFetchFilterForPage(page: number, filter?: TFetchFilter): TFetchFilter { const limit = getLimit() const offset = getPageOffset(page) return { ...filter, offset, limit, } } function getLimit(): number { const [filter] = fetchParametersGetter() ?? [] const limit = filter?.limit ?? GLOBAL_API_LIMIT return limit } function getPageOffset(page: number): number { const limit = getLimit() return (page - 1) * limit } function getPageRef(): Ref { if (options?.page) { return ref(options.page) } return ref(mode === 'page' ? 1 : 0) } function getMode(): Exclude { return options?.mode ?? 'page' } watch(fetchParametersGetter, () => { page.value = 1 }, { deep: true }) onScopeDispose(() => { subscriptions.unsubscribe() }) return { subscriptions, results, total, page, pages, next, previous, } }