diff --git a/VocaDbWeb/Scripts/Components/Shared/Partials/Knockout/ServerSidePaging.tsx b/VocaDbWeb/Scripts/Components/Shared/Partials/Knockout/ServerSidePaging.tsx new file mode 100644 index 0000000000..1061b2214b --- /dev/null +++ b/VocaDbWeb/Scripts/Components/Shared/Partials/Knockout/ServerSidePaging.tsx @@ -0,0 +1,58 @@ +import Pagination from '@Bootstrap/Pagination'; +import ServerSidePagingStore from '@Stores/ServerSidePagingStore'; +import { observer } from 'mobx-react-lite'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; + +interface ServerSidePagingProps { + store: ServerSidePagingStore; +} + +const ServerSidePaging = observer( + ({ store }: ServerSidePagingProps): React.ReactElement => { + const { t } = useTranslation(['VocaDb.Web.Resources.Other']); + + return ( + + + «« {t('VocaDb.Web.Resources.Other:PagedList.First')} + + + « {t('VocaDb.Web.Resources.Other:PagedList.Previous')} + + + {store.showMoreBegin && } + + {store.pages.map((page) => ( + store.setPage(page)} + key={page} + > + {page} + + ))} + + {store.showMoreEnd && } + + + {t('VocaDb.Web.Resources.Other:PagedList.Next')} » + + + {t('VocaDb.Web.Resources.Other:PagedList.Last')} »» + + + ); + }, +); + +export default ServerSidePaging; diff --git a/VocaDbWeb/Scripts/Stores/ServerSidePagingStore.ts b/VocaDbWeb/Scripts/Stores/ServerSidePagingStore.ts new file mode 100644 index 0000000000..e9569268fc --- /dev/null +++ b/VocaDbWeb/Scripts/Stores/ServerSidePagingStore.ts @@ -0,0 +1,86 @@ +import _ from 'lodash'; +import { action, computed, makeObservable, observable } from 'mobx'; + +export default class ServerSidePagingStore { + @observable public page = 1; + @action public setPage = (value: number): void => { + this.page = value; + }; + + @observable public totalItems = 0; + @action public setTotalItems = (value: number): void => { + this.totalItems = value; + }; + + @observable public pageSize = 10; + @action public setPageSize = (value: number): void => { + this.pageSize = value; + }; + + public constructor(pageSize: number = 10) { + makeObservable(this); + + this.pageSize = pageSize; + } + + @computed public get firstItem(): number { + return (this.page - 1) * this.pageSize; + } + + @computed public get totalPages(): number { + return Math.ceil(this.totalItems / this.pageSize); + } + + @computed public get hasMultiplePages(): boolean { + return this.totalPages > 1; + } + + @computed public get isFirstPage(): boolean { + return this.page <= 1; + } + + @computed public get isLastPage(): boolean { + return this.page >= this.totalPages; + } + + @computed public get pages(): number[] { + const start = Math.max(this.page - 4, 1); + const end = Math.min(this.page + 4, this.totalPages); + + return _.range(start, end + 1); + } + + @computed public get showMoreBegin(): boolean { + return this.page > 5; + } + + @computed public get showMoreEnd(): boolean { + return this.page < this.totalPages - 4; + } + + public getPagingProperties = ( + clearResults: boolean = false, + ): { start: number; maxEntries: number; getTotalCount: boolean } => { + return { + start: this.firstItem, + maxEntries: this.pageSize, + getTotalCount: clearResults || this.totalItems === 0, + }; + }; + + @action public goToFirstPage = (): void => { + this.page = 1; + }; + + @action public goToLastPage = (): void => { + this.page = this.totalPages; + }; + + @action public nextPage = (): void => { + if (!this.isLastPage) this.page = this.page + 1; + }; + + @action public previousPage = (): void => { + if (!this.isFirstPage) this.page = this.page - 1; + }; +}