diff --git a/komga-webui/src/views/BrowseCollection.vue b/komga-webui/src/views/BrowseCollection.vue index 4eb586840..474be8a38 100644 --- a/komga-webui/src/views/BrowseCollection.vue +++ b/komga-webui/src/views/BrowseCollection.vue @@ -36,6 +36,8 @@ + + mdi-filter-variant @@ -90,7 +92,7 @@ {{ $t('common.reset_filters') }} - + @@ -138,16 +155,18 @@ import {Location} from 'vue-router' import EmptyState from '@/components/EmptyState.vue' import {SeriesDto} from '@/types/komga-series' import {authorRoles} from '@/types/author-roles' -import {AuthorDto} from '@/types/komga-books' +import {AuthorDto, BookDto} from '@/types/komga-books' import {CollectionSseDto, ReadProgressSeriesSseDto, SeriesSseDto} from '@/types/komga-sse' import {throttle} from 'lodash' import {LibraryDto} from '@/types/komga-libraries' import {parseBooleanFilter} from '@/functions/query-params' import {ContextOrigin} from '@/types/context' +import PageSizeSelect from '@/components/PageSizeSelect.vue' export default Vue.extend({ name: 'BrowseCollection', components: { + PageSizeSelect, ToolbarSticky, ItemBrowser, CollectionActionsMenu, @@ -163,9 +182,16 @@ export default Vue.extend({ series: [] as SeriesDto[], seriesCopy: [] as SeriesDto[], selectedSeries: [] as SeriesDto[], + page: 1, + pageSize: 20, + unpaged: false, + totalPages: 1, + totalElements: null as number | null, editElements: false, filters: {} as FiltersActive, filterUnwatch: null as any, + pageUnwatch: null as any, + pageSizeUnwatch: null as any, drawer: false, filterOptions: { library: [] as NameValue[], @@ -201,7 +227,12 @@ export default Vue.extend({ this.$eventHub.$off(READPROGRESS_SERIES_DELETED, this.readProgressChanged) }, async mounted() { + this.pageSize = this.$store.state.persistedState.browsingPageSize || this.pageSize + + // restore from query param await this.resetParams(this.$route, this.collectionId) + if (this.$route.query.page) this.page = Number(this.$route.query.page) + if (this.$route.query.pageSize) this.pageSize = Number(this.$route.query.pageSize) this.loadCollection(this.collectionId) @@ -213,6 +244,9 @@ export default Vue.extend({ // reset await this.resetParams(this.$route, to.params.collectionId) + this.page = 1 + this.totalPages = 1 + this.totalElements = null this.series = [] this.editElements = false @@ -224,6 +258,19 @@ export default Vue.extend({ next() }, computed: { + paginationVisible(): number { + switch (this.$vuetify.breakpoint.name) { + case 'xs': + return 5 + case 'sm': + case 'md': + return 10 + case 'lg': + case 'xl': + default: + return 15 + } + }, filterOptionsList(): FiltersOptions { return { readStatus: { @@ -352,9 +399,20 @@ export default Vue.extend({ this.$store.commit('setCollectionFilter', {id: this.collectionId, filter: val}) this.updateRouteAndReload() }) + this.pageSizeUnwatch = this.$watch('pageSize', (val) => { + this.$store.commit('setBrowsingPageSize', val) + this.updateRouteAndReload() + }) + + this.pageUnwatch = this.$watch('page', (val) => { + this.updateRoute() + this.loadPage(this.collectionId, val) + }) }, unsetWatches() { this.filterUnwatch() + this.pageUnwatch() + this.pageSizeUnwatch() }, collectionChanged(event: CollectionSseDto) { if (event.collectionId === this.collectionId) { @@ -369,15 +427,25 @@ export default Vue.extend({ updateRouteAndReload() { this.unsetWatches() + this.page = 1 + this.updateRoute() - this.loadSeries(this.collectionId) + this.loadPage(this.collectionId, this.page) this.setWatches() }, - reloadSeries: throttle(function (this: any) { - this.loadSeries(this.collectionId) - }, 1000), - async loadSeries(collectionId: string) { + // reloadSeries: throttle(function (this: any) { + // this.loadSeries(this.collectionId) + // }, 1000), + async loadPage(collectionId: string, page: number) { + this.selectedSeries = [] + + const pageRequest = { + page: page - 1, + size: this.pageSize, + unpaged: this.unpaged, + } as PageRequest + let authorsFilter = [] as AuthorDto[] authorRoles.forEach((role: string) => { if (role in this.filters) this.filters[role].forEach((name: string) => authorsFilter.push({ @@ -387,22 +455,48 @@ export default Vue.extend({ }) const complete = parseBooleanFilter(this.filters.complete) - this.series = (await this.$komgaCollections.getSeries(collectionId, {unpaged: true} as PageRequest, this.filters.library, this.filters.status, replaceCompositeReadStatus(this.filters.readStatus), this.filters.genre, this.filters.tag, this.filters.language, this.filters.publisher, this.filters.ageRating, this.filters.releaseDate, authorsFilter, complete)).content + const seriesPage = await this.$komgaCollections.getSeries(collectionId, pageRequest, this.filters.library, this.filters.status, replaceCompositeReadStatus(this.filters.readStatus), this.filters.genre, this.filters.tag, this.filters.language, this.filters.publisher, this.filters.ageRating, this.filters.releaseDate, authorsFilter, complete) + + this.totalPages = seriesPage.totalPages + this.totalElements = seriesPage.totalElements + this.series = seriesPage.content + this.series.forEach((x: SeriesDto) => x.context = {origin: ContextOrigin.COLLECTION, id: collectionId}) this.seriesCopy = [...this.series] this.selectedSeries = [] }, + reloadPage: throttle(function (this: any) { + this.loadPage(this.collectionId, this.page) + }, 1000), + // async loadSeries(collectionId: string) { + // let authorsFilter = [] as AuthorDto[] + // authorRoles.forEach((role: string) => { + // if (role in this.filters) this.filters[role].forEach((name: string) => authorsFilter.push({ + // name: name, + // role: role, + // })) + // }) + // + // const complete = parseBooleanFilter(this.filters.complete) + // this.series = (await this.$komgaCollections.getSeries(collectionId, {unpaged: true} as PageRequest, this.filters.library, this.filters.status, replaceCompositeReadStatus(this.filters.readStatus), this.filters.genre, this.filters.tag, this.filters.language, this.filters.publisher, this.filters.ageRating, this.filters.releaseDate, authorsFilter, complete)).content + // this.series.forEach((x: SeriesDto) => x.context = {origin: ContextOrigin.COLLECTION, id: collectionId}) + // this.seriesCopy = [...this.series] + // this.selectedSeries = [] + // }, async loadCollection(collectionId: string) { this.$komgaCollections.getOneCollection(collectionId) .then(v => this.collection = v) - await this.loadSeries(collectionId) + await this.loadPage(collectionId, this.page) }, updateRoute() { const loc = { name: this.$route.name, params: {collectionId: this.$route.params.collectionId}, - query: {}, + query: { + page: `${this.page}`, + pageSize: `${this.pageSize}`, + }, } as Location mergeFilterParams(this.filters, loc.query) this.$router.replace(loc).catch((_: any) => { @@ -429,13 +523,17 @@ export default Vue.extend({ addToCollection() { this.$store.dispatch('dialogAddSeriesToCollection', this.selectedSeries) }, - startEditElements() { + async startEditElements() { this.filters = {} + this.unpaged = true + await this.reloadPage() this.editElements = true }, cancelEditElements() { this.editElements = false this.series = [...this.seriesCopy] + this.unpaged = false + this.reloadPage() }, doEditElements() { this.editElements = false @@ -443,15 +541,17 @@ export default Vue.extend({ seriesIds: this.series.map(x => x.id), } as CollectionUpdateDto this.$komgaCollections.patchCollection(this.collectionId, update) + this.unpaged = false + this.reloadPage() }, editCollection() { this.$store.dispatch('dialogEditCollection', this.collection) }, seriesChanged(event: SeriesSseDto) { - if (this.series.some(s => s.id === event.seriesId)) this.reloadSeries() + if (this.series.some(s => s.id === event.seriesId)) this.reloadPage() }, readProgressChanged(event: ReadProgressSeriesSseDto) { - if (this.series.some(b => b.id === event.seriesId)) this.reloadSeries() + if (this.series.some(b => b.id === event.seriesId)) this.reloadPage() }, }, }) diff --git a/komga-webui/src/views/BrowseReadList.vue b/komga-webui/src/views/BrowseReadList.vue index d2ea2a9be..449204430 100644 --- a/komga-webui/src/views/BrowseReadList.vue +++ b/komga-webui/src/views/BrowseReadList.vue @@ -33,6 +33,8 @@ + + mdi-filter-variant @@ -108,14 +110,40 @@ - + + {{ $t('common.reset_filters') }} + + + @@ -151,10 +179,14 @@ import {mergeFilterParams, toNameValue} from '@/functions/filter' import {Location} from 'vue-router' import {readListFileUrl} from '@/functions/urls' import {ItemContext} from '@/types/items' +import PageSizeSelect from '@/components/PageSizeSelect.vue' +import EmptyState from '@/components/EmptyState.vue' export default Vue.extend({ name: 'BrowseReadList', components: { + EmptyState, + PageSizeSelect, ToolbarSticky, ItemBrowser, ReadListActionsMenu, @@ -171,9 +203,16 @@ export default Vue.extend({ books: [] as BookDto[], booksCopy: [] as BookDto[], selectedBooks: [] as BookDto[], + page: 1, + pageSize: 20, + unpaged: false, + totalPages: 1, + totalElements: null as number | null, editElements: false, filters: {} as FiltersActive, filterUnwatch: null as any, + pageUnwatch: null as any, + pageSizeUnwatch: null as any, drawer: false, filterOptions: { library: [] as NameValue[], @@ -204,7 +243,12 @@ export default Vue.extend({ this.$eventHub.$off(READPROGRESS_DELETED, this.readProgressChanged) }, async mounted() { + this.pageSize = this.$store.state.persistedState.browsingPageSize || this.pageSize + + // restore from query param await this.resetParams(this.$route, this.readListId) + if (this.$route.query.page) this.page = Number(this.$route.query.page) + if (this.$route.query.pageSize) this.pageSize = Number(this.$route.query.pageSize) this.loadReadList(this.readListId) @@ -216,6 +260,9 @@ export default Vue.extend({ // reset await this.resetParams(this.$route, this.readListId) + this.page = 1 + this.totalPages = 1 + this.totalElements = null this.books = [] this.editElements = false @@ -227,6 +274,19 @@ export default Vue.extend({ next() }, computed: { + paginationVisible(): number { + switch (this.$vuetify.breakpoint.name) { + case 'xs': + return 5 + case 'sm': + case 'md': + return 10 + case 'lg': + case 'xl': + default: + return 15 + } + }, filterOptionsList(): FiltersOptions { return { readStatus: { @@ -319,15 +379,28 @@ export default Vue.extend({ this.$store.commit('setReadListFilter', {id: this.readListId, filter: val}) this.updateRouteAndReload() }) + this.pageSizeUnwatch = this.$watch('pageSize', (val) => { + this.$store.commit('setBrowsingPageSize', val) + this.updateRouteAndReload() + }) + + this.pageUnwatch = this.$watch('page', (val) => { + this.updateRoute() + this.loadPage(this.readListId, val) + }) }, unsetWatches() { this.filterUnwatch() + this.pageUnwatch() + this.pageSizeUnwatch() }, updateRouteAndReload() { this.unsetWatches() + this.page = 1 + this.updateRoute() - this.loadBooks(this.readListId) + this.loadPage(this.readListId, this.page) this.setWatches() }, @@ -335,7 +408,10 @@ export default Vue.extend({ const loc = { name: this.$route.name, params: {readListId: this.$route.params.readListId}, - query: {}, + query: { + page: `${this.page}`, + pageSize: `${this.pageSize}`, + }, } as Location mergeFilterParams(this.filters, loc.query) this.$router.replace(loc).catch((_: any) => { @@ -354,9 +430,18 @@ export default Vue.extend({ async loadReadList(readListId: string) { this.$komgaReadLists.getOneReadList(readListId) .then(v => this.readList = v) - await this.loadBooks(readListId) + + await this.loadPage(readListId, this.page) }, - async loadBooks(readListId: string) { + async loadPage(readListId: string, page: number) { + this.selectedBooks = [] + + const pageRequest = { + page: page - 1, + size: this.pageSize, + unpaged: this.unpaged, + } as PageRequest + let authorsFilter = [] as AuthorDto[] authorRoles.forEach((role: string) => { if (role in this.filters) this.filters[role].forEach((name: string) => authorsFilter.push({ @@ -365,13 +450,18 @@ export default Vue.extend({ })) }) - this.books = (await this.$komgaReadLists.getBooks(readListId, {unpaged: true} as PageRequest, this.filters.library, replaceCompositeReadStatus(this.filters.readStatus), this.filters.tag, authorsFilter)).content + const booksPage = await this.$komgaReadLists.getBooks(readListId, pageRequest, this.filters.library, replaceCompositeReadStatus(this.filters.readStatus), this.filters.tag, authorsFilter) + + this.totalPages = booksPage.totalPages + this.totalElements = booksPage.totalElements + this.books = booksPage.content + this.books.forEach((x: BookDto) => x.context = {origin: ContextOrigin.READLIST, id: readListId}) this.booksCopy = [...this.books] this.selectedBooks = [] }, - reloadBooks: throttle(function (this: any) { - this.loadBooks(this.readListId) + reloadPage: throttle(function (this: any) { + this.loadPage(this.readListId, this.page) }, 1000), editSingleBook(book: BookDto) { this.$store.dispatch('dialogUpdateBooks', book) @@ -397,13 +487,17 @@ export default Vue.extend({ addToReadList() { this.$store.dispatch('dialogAddBooksToReadList', this.selectedBooks) }, - startEditElements() { + async startEditElements() { this.filters = {} + this.unpaged = true + await this.reloadPage() this.editElements = true }, cancelEditElements() { this.editElements = false this.books = [...this.booksCopy] + this.unpaged = false + this.reloadPage() }, doEditElements() { this.editElements = false @@ -411,15 +505,17 @@ export default Vue.extend({ bookIds: this.books.map(x => x.id), } as ReadListUpdateDto this.$komgaReadLists.patchReadList(this.readListId, update) + this.unpaged = false + this.reloadPage() }, editReadList() { this.$store.dispatch('dialogEditReadList', this.readList) }, bookChanged(event: BookSseDto) { - if (this.books.some(b => b.id === event.bookId)) this.reloadBooks() + if (this.books.some(b => b.id === event.bookId)) this.reloadPage() }, readProgressChanged(event: ReadProgressSseDto) { - if (this.books.some(b => b.id === event.bookId)) this.reloadBooks() + if (this.books.some(b => b.id === event.bookId)) this.reloadPage() }, }, })