mirror of
https://github.com/gotson/komga.git
synced 2026-05-09 05:10:19 +02:00
feat(webui): horizontal scroller infinite scroll on dashboard and search results
closes #605
This commit is contained in:
parent
24b564a707
commit
fe78f17e5e
5 changed files with 315 additions and 137 deletions
|
|
@ -22,7 +22,10 @@
|
||||||
@scroll="computeScrollability"
|
@scroll="computeScrollability"
|
||||||
v-resize="computeScrollability"
|
v-resize="computeScrollability"
|
||||||
>
|
>
|
||||||
<slot name="content" class="content"/>
|
<div class="d-inline-flex">
|
||||||
|
<slot name="content" class="content"/>
|
||||||
|
<slot name="content-append"/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -44,20 +47,35 @@ export default Vue.extend({
|
||||||
adjustment: 100,
|
adjustment: 100,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
props: {
|
||||||
|
tick: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.container = this.$refs[this.id] as HTMLElement
|
this.container = this.$refs[this.id] as HTMLElement
|
||||||
this.computeScrollability()
|
this.computeScrollability()
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
tick() {
|
||||||
|
setTimeout(this.computeScrollability, 200)
|
||||||
|
},
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
computeScrollability() {
|
computeScrollability() {
|
||||||
if (this.container !== undefined) {
|
if (this.container) {
|
||||||
|
let scrollPercent: number
|
||||||
if (this.$vuetify.rtl) {
|
if (this.$vuetify.rtl) {
|
||||||
this.canScrollBackward = Math.round(this.container.scrollLeft) < 0
|
this.canScrollBackward = Math.round(this.container.scrollLeft) < 0
|
||||||
this.canScrollForward = (Math.round(this.container.scrollLeft) - this.container.clientWidth) > -this.container.scrollWidth
|
this.canScrollForward = (Math.round(this.container.scrollLeft) - this.container.clientWidth) > -this.container.scrollWidth
|
||||||
|
scrollPercent = (Math.round(this.container.scrollLeft) - this.container.clientWidth) / -this.container.scrollWidth
|
||||||
} else {
|
} else {
|
||||||
this.canScrollBackward = Math.round(this.container.scrollLeft) > 0
|
this.canScrollBackward = Math.round(this.container.scrollLeft) > 0
|
||||||
this.canScrollForward = (Math.round(this.container.scrollLeft) + this.container.clientWidth) < this.container.scrollWidth
|
this.canScrollForward = (Math.round(this.container.scrollLeft) + this.container.clientWidth) < this.container.scrollWidth
|
||||||
|
scrollPercent = (Math.round(this.container.scrollLeft) + this.container.clientWidth) / this.container.scrollWidth
|
||||||
}
|
}
|
||||||
|
this.$emit('scroll-changed', scrollPercent)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
doScroll(direction: string) {
|
doScroll(direction: string) {
|
||||||
|
|
|
||||||
43
komga-webui/src/types/pageLoader.ts
Normal file
43
komga-webui/src/types/pageLoader.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
export class PageLoader<T> {
|
||||||
|
private readonly pageable: PageRequest
|
||||||
|
private readonly loader: (pageRequest: PageRequest) => Promise<Page<T>>
|
||||||
|
|
||||||
|
private currentPage = undefined as unknown as Page<T>
|
||||||
|
private loadedPages: number[] = []
|
||||||
|
public readonly items: T[] = []
|
||||||
|
|
||||||
|
get page() {
|
||||||
|
return this.currentPage?.number || 0
|
||||||
|
}
|
||||||
|
get hasNextPage() {
|
||||||
|
return !this.currentPage ? false : !this.currentPage.last
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(pageable: PageRequest, loader: (pageRequest: PageRequest) => Promise<Page<T>>) {
|
||||||
|
this.pageable = pageable
|
||||||
|
this.loader = loader
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadNext(): Promise<boolean> {
|
||||||
|
// load first page if nothing has been loaded yet
|
||||||
|
if (!this.currentPage) {
|
||||||
|
this.loadedPages.push(this.pageable.page || 0)
|
||||||
|
this.currentPage = await this.loader(this.pageable)
|
||||||
|
this.items.push(...this.currentPage.content)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// if the last page has been loaded, do nothing
|
||||||
|
else if (this.currentPage.last) return false
|
||||||
|
else {
|
||||||
|
const nextPage = this.currentPage.number + 1
|
||||||
|
if (!this.loadedPages.includes(nextPage)) {
|
||||||
|
this.loadedPages.push(nextPage)
|
||||||
|
const pageable = Object.assign({}, this.pageable, {page: nextPage})
|
||||||
|
this.currentPage = await this.loader(pageable)
|
||||||
|
this.items.push(...this.currentPage.content)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -49,12 +49,17 @@
|
||||||
>
|
>
|
||||||
</empty-state>
|
</empty-state>
|
||||||
|
|
||||||
<horizontal-scroller v-if="inProgressBooks.length !== 0" class="mb-4">
|
<horizontal-scroller
|
||||||
|
v-if="loaderInProgressBooks && loaderInProgressBooks.items.length !== 0"
|
||||||
|
class="mb-4"
|
||||||
|
:tick="loaderInProgressBooks.page"
|
||||||
|
@scroll-changed="(percent) => scrollChanged(loaderInProgressBooks, percent)"
|
||||||
|
>
|
||||||
<template v-slot:prepend>
|
<template v-slot:prepend>
|
||||||
<div class="title">{{ $t('dashboard.keep_reading') }}</div>
|
<div class="title">{{ $t('dashboard.keep_reading') }}</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:content>
|
<template v-slot:content>
|
||||||
<item-browser :items="inProgressBooks"
|
<item-browser :items="loaderInProgressBooks.items"
|
||||||
nowrap
|
nowrap
|
||||||
:edit-function="isAdmin ? singleEditBook : undefined"
|
:edit-function="isAdmin ? singleEditBook : undefined"
|
||||||
:selected.sync="selectedBooks"
|
:selected.sync="selectedBooks"
|
||||||
|
|
@ -64,12 +69,17 @@
|
||||||
</template>
|
</template>
|
||||||
</horizontal-scroller>
|
</horizontal-scroller>
|
||||||
|
|
||||||
<horizontal-scroller v-if="onDeckBooks.length !== 0" class="mb-4">
|
<horizontal-scroller
|
||||||
|
v-if="loaderOnDeckBooks && loaderOnDeckBooks.items.length !== 0"
|
||||||
|
class="mb-4"
|
||||||
|
:tick="loaderOnDeckBooks.page"
|
||||||
|
@scroll-changed="(percent) => scrollChanged(loaderOnDeckBooks, percent)"
|
||||||
|
>
|
||||||
<template v-slot:prepend>
|
<template v-slot:prepend>
|
||||||
<div class="title">{{ $t('dashboard.on_deck') }}</div>
|
<div class="title">{{ $t('dashboard.on_deck') }}</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:content>
|
<template v-slot:content>
|
||||||
<item-browser :items="onDeckBooks"
|
<item-browser :items="loaderOnDeckBooks.items"
|
||||||
nowrap
|
nowrap
|
||||||
:edit-function="isAdmin ? singleEditBook : undefined"
|
:edit-function="isAdmin ? singleEditBook : undefined"
|
||||||
:selected.sync="selectedBooks"
|
:selected.sync="selectedBooks"
|
||||||
|
|
@ -79,12 +89,17 @@
|
||||||
</template>
|
</template>
|
||||||
</horizontal-scroller>
|
</horizontal-scroller>
|
||||||
|
|
||||||
<horizontal-scroller v-if="recentlyReleasedBooks.length !== 0" class="mb-4">
|
<horizontal-scroller
|
||||||
|
v-if="loaderRecentlyReleasedBooks && loaderRecentlyReleasedBooks.items.length !== 0"
|
||||||
|
class="mb-4"
|
||||||
|
:tick="loaderRecentlyReleasedBooks.page"
|
||||||
|
@scroll-changed="(percent) => scrollChanged(loaderRecentlyReleasedBooks, percent)"
|
||||||
|
>
|
||||||
<template v-slot:prepend>
|
<template v-slot:prepend>
|
||||||
<div class="title">{{ $t('dashboard.recently_released_books') }}</div>
|
<div class="title">{{ $t('dashboard.recently_released_books') }}</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:content>
|
<template v-slot:content>
|
||||||
<item-browser :items="recentlyReleasedBooks"
|
<item-browser :items="loaderRecentlyReleasedBooks.items"
|
||||||
nowrap
|
nowrap
|
||||||
:edit-function="isAdmin ? singleEditBook : undefined"
|
:edit-function="isAdmin ? singleEditBook : undefined"
|
||||||
:selected.sync="selectedBooks"
|
:selected.sync="selectedBooks"
|
||||||
|
|
@ -94,12 +109,17 @@
|
||||||
</template>
|
</template>
|
||||||
</horizontal-scroller>
|
</horizontal-scroller>
|
||||||
|
|
||||||
<horizontal-scroller v-if="latestBooks.length !== 0" class="mb-4">
|
<horizontal-scroller
|
||||||
|
v-if="loaderLatestBooks && loaderLatestBooks.items.length !== 0"
|
||||||
|
class="mb-4"
|
||||||
|
:tick="loaderLatestBooks.page"
|
||||||
|
@scroll-changed="(percent) => scrollChanged(loaderLatestBooks, percent)"
|
||||||
|
>
|
||||||
<template v-slot:prepend>
|
<template v-slot:prepend>
|
||||||
<div class="title">{{ $t('dashboard.recently_added_books') }}</div>
|
<div class="title">{{ $t('dashboard.recently_added_books') }}</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:content>
|
<template v-slot:content>
|
||||||
<item-browser :items="latestBooks"
|
<item-browser :items="loaderLatestBooks.items"
|
||||||
nowrap
|
nowrap
|
||||||
:edit-function="isAdmin ? singleEditBook : undefined"
|
:edit-function="isAdmin ? singleEditBook : undefined"
|
||||||
:selected.sync="selectedBooks"
|
:selected.sync="selectedBooks"
|
||||||
|
|
@ -109,12 +129,17 @@
|
||||||
</template>
|
</template>
|
||||||
</horizontal-scroller>
|
</horizontal-scroller>
|
||||||
|
|
||||||
<horizontal-scroller v-if="newSeries.length !== 0" class="mb-4">
|
<horizontal-scroller
|
||||||
|
v-if="loaderNewSeries && loaderNewSeries.items.length !== 0"
|
||||||
|
class="mb-4"
|
||||||
|
:tick="loaderNewSeries.page"
|
||||||
|
@scroll-changed="(percent) => scrollChanged(loaderNewSeries, percent)"
|
||||||
|
>
|
||||||
<template v-slot:prepend>
|
<template v-slot:prepend>
|
||||||
<div class="title">{{ $t('dashboard.recently_added_series') }}</div>
|
<div class="title">{{ $t('dashboard.recently_added_series') }}</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:content>
|
<template v-slot:content>
|
||||||
<item-browser :items="newSeries"
|
<item-browser :items="loaderNewSeries.items"
|
||||||
nowrap
|
nowrap
|
||||||
:edit-function="isAdmin ? singleEditSeries : undefined"
|
:edit-function="isAdmin ? singleEditSeries : undefined"
|
||||||
:selected.sync="selectedSeries"
|
:selected.sync="selectedSeries"
|
||||||
|
|
@ -124,12 +149,17 @@
|
||||||
</template>
|
</template>
|
||||||
</horizontal-scroller>
|
</horizontal-scroller>
|
||||||
|
|
||||||
<horizontal-scroller v-if="updatedSeries.length !== 0" class="mb-4">
|
<horizontal-scroller
|
||||||
|
v-if="loaderUpdatedSeries && loaderUpdatedSeries.items.length !== 0"
|
||||||
|
class="mb-4"
|
||||||
|
:tick="loaderUpdatedSeries.page"
|
||||||
|
@scroll-changed="(percent) => scrollChanged(loaderUpdatedSeries, percent)"
|
||||||
|
>
|
||||||
<template v-slot:prepend>
|
<template v-slot:prepend>
|
||||||
<div class="title">{{ $t('dashboard.recently_updated_series') }}</div>
|
<div class="title">{{ $t('dashboard.recently_updated_series') }}</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:content>
|
<template v-slot:content>
|
||||||
<item-browser :items="updatedSeries"
|
<item-browser :items="loaderUpdatedSeries.items"
|
||||||
nowrap
|
nowrap
|
||||||
:edit-function="isAdmin ? singleEditSeries : undefined"
|
:edit-function="isAdmin ? singleEditSeries : undefined"
|
||||||
:selected.sync="selectedSeries"
|
:selected.sync="selectedSeries"
|
||||||
|
|
@ -139,12 +169,17 @@
|
||||||
</template>
|
</template>
|
||||||
</horizontal-scroller>
|
</horizontal-scroller>
|
||||||
|
|
||||||
<horizontal-scroller v-if="recentlyReadBooks.length !== 0" class="mb-4">
|
<horizontal-scroller
|
||||||
|
v-if="loaderRecentlyReadBooks && loaderRecentlyReadBooks.items.length !== 0"
|
||||||
|
class="mb-4"
|
||||||
|
:tick="loaderRecentlyReadBooks.page"
|
||||||
|
@scroll-changed="(percent) => scrollChanged(loaderRecentlyReadBooks, percent)"
|
||||||
|
>
|
||||||
<template v-slot:prepend>
|
<template v-slot:prepend>
|
||||||
<div class="title">{{ $t('dashboard.recently_read_books') }}</div>
|
<div class="title">{{ $t('dashboard.recently_read_books') }}</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:content>
|
<template v-slot:content>
|
||||||
<item-browser :items="recentlyReadBooks"
|
<item-browser :items="loaderRecentlyReadBooks.items"
|
||||||
nowrap
|
nowrap
|
||||||
:edit-function="isAdmin ? singleEditBook : undefined"
|
:edit-function="isAdmin ? singleEditBook : undefined"
|
||||||
:selected.sync="selectedBooks"
|
:selected.sync="selectedBooks"
|
||||||
|
|
@ -184,6 +219,7 @@ import {throttle} from 'lodash'
|
||||||
import {subMonths} from 'date-fns'
|
import {subMonths} from 'date-fns'
|
||||||
import {BookSseDto, ReadProgressSseDto, SeriesSseDto} from '@/types/komga-sse'
|
import {BookSseDto, ReadProgressSseDto, SeriesSseDto} from '@/types/komga-sse'
|
||||||
import {LibraryDto} from '@/types/komga-libraries'
|
import {LibraryDto} from '@/types/komga-libraries'
|
||||||
|
import {PageLoader} from '@/types/pageLoader'
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
name: 'Dashboard',
|
name: 'Dashboard',
|
||||||
|
|
@ -200,13 +236,13 @@ export default Vue.extend({
|
||||||
return {
|
return {
|
||||||
loading: false,
|
loading: false,
|
||||||
library: undefined as LibraryDto | undefined,
|
library: undefined as LibraryDto | undefined,
|
||||||
newSeries: [] as SeriesDto[],
|
loaderNewSeries: undefined as unknown as PageLoader<SeriesDto>,
|
||||||
updatedSeries: [] as SeriesDto[],
|
loaderUpdatedSeries: undefined as unknown as PageLoader<SeriesDto>,
|
||||||
latestBooks: [] as BookDto[],
|
loaderLatestBooks: undefined as unknown as PageLoader<BookDto>,
|
||||||
inProgressBooks: [] as BookDto[],
|
loaderInProgressBooks: undefined as unknown as PageLoader<BookDto>,
|
||||||
onDeckBooks: [] as BookDto[],
|
loaderOnDeckBooks: undefined as unknown as PageLoader<BookDto>,
|
||||||
recentlyReleasedBooks: [] as BookDto[],
|
loaderRecentlyReleasedBooks: undefined as unknown as PageLoader<BookDto>,
|
||||||
recentlyReadBooks: [] as BookDto[],
|
loaderRecentlyReadBooks: undefined as unknown as PageLoader<BookDto>,
|
||||||
selectedSeries: [] as SeriesDto[],
|
selectedSeries: [] as SeriesDto[],
|
||||||
selectedBooks: [] as BookDto[],
|
selectedBooks: [] as BookDto[],
|
||||||
}
|
}
|
||||||
|
|
@ -263,19 +299,22 @@ export default Vue.extend({
|
||||||
return this.$vuetify.breakpoint.name === 'xs' ? 120 : 150
|
return this.$vuetify.breakpoint.name === 'xs' ? 120 : 150
|
||||||
},
|
},
|
||||||
allEmpty(): boolean {
|
allEmpty(): boolean {
|
||||||
return this.newSeries.length === 0 &&
|
return this.loaderNewSeries?.items.length === 0 &&
|
||||||
this.updatedSeries.length === 0 &&
|
this.loaderUpdatedSeries?.items.length === 0 &&
|
||||||
this.latestBooks.length === 0 &&
|
this.loaderLatestBooks?.items.length === 0 &&
|
||||||
this.inProgressBooks.length === 0 &&
|
this.loaderInProgressBooks?.items.length === 0 &&
|
||||||
this.onDeckBooks.length === 0 &&
|
this.loaderOnDeckBooks?.items.length === 0 &&
|
||||||
this.recentlyReleasedBooks.length === 0 &&
|
this.loaderRecentlyReleasedBooks?.items.length === 0 &&
|
||||||
this.recentlyReadBooks.length === 0
|
this.loaderRecentlyReadBooks?.items.length === 0
|
||||||
},
|
},
|
||||||
individualLibrary(): boolean {
|
individualLibrary(): boolean {
|
||||||
return this.libraryId !== LIBRARIES_ALL
|
return this.libraryId !== LIBRARIES_ALL
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
async scrollChanged(loader: PageLoader<any>, percent: number) {
|
||||||
|
if (percent > 0.95) await loader.loadNext()
|
||||||
|
},
|
||||||
getRequestLibraryId(libraryId: string): string | undefined {
|
getRequestLibraryId(libraryId: string): string | undefined {
|
||||||
return libraryId !== LIBRARIES_ALL ? libraryId : undefined
|
return libraryId !== LIBRARIES_ALL ? libraryId : undefined
|
||||||
},
|
},
|
||||||
|
|
@ -290,11 +329,11 @@ export default Vue.extend({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
readProgressChanged(event: ReadProgressSseDto) {
|
readProgressChanged(event: ReadProgressSseDto) {
|
||||||
if (this.inProgressBooks.some(b => b.id === event.bookId)) this.reload()
|
if (this.loaderInProgressBooks?.items.some(b => b.id === event.bookId)) this.reload()
|
||||||
else if (this.latestBooks.some(b => b.id === event.bookId)) this.reload()
|
else if (this.loaderLatestBooks?.items.some(b => b.id === event.bookId)) this.reload()
|
||||||
else if (this.onDeckBooks.some(b => b.id === event.bookId)) this.reload()
|
else if (this.loaderOnDeckBooks?.items.some(b => b.id === event.bookId)) this.reload()
|
||||||
else if (this.recentlyReleasedBooks.some(b => b.id === event.bookId)) this.reload()
|
else if (this.loaderRecentlyReleasedBooks?.items.some(b => b.id === event.bookId)) this.reload()
|
||||||
else if (this.recentlyReadBooks.some(b => b.id === event.bookId)) this.reload()
|
else if (this.loaderRecentlyReadBooks?.items.some(b => b.id === event.bookId)) this.reload()
|
||||||
},
|
},
|
||||||
reload: throttle(function (this: any) {
|
reload: throttle(function (this: any) {
|
||||||
this.loadAll(this.libraryId)
|
this.loadAll(this.libraryId)
|
||||||
|
|
@ -304,55 +343,49 @@ export default Vue.extend({
|
||||||
this.library = this.getLibraryLazy(libraryId)
|
this.library = this.getLibraryLazy(libraryId)
|
||||||
this.selectedSeries = []
|
this.selectedSeries = []
|
||||||
this.selectedBooks = []
|
this.selectedBooks = []
|
||||||
Promise.all([this.loadInProgressBooks(libraryId),
|
|
||||||
this.loadOnDeckBooks(libraryId),
|
this.loaderInProgressBooks = new PageLoader<BookDto>(
|
||||||
this.loadRecentlyReleasedBooks(libraryId),
|
{sort: ['readProgress.lastModified,desc']},
|
||||||
this.loadLatestBooks(libraryId),
|
(pageable: PageRequest) => this.$komgaBooks.getBooks(this.getRequestLibraryId(libraryId), pageable, undefined, undefined, [ReadStatus.IN_PROGRESS]),
|
||||||
this.loadNewSeries(libraryId),
|
)
|
||||||
this.loadUpdatedSeries(libraryId),
|
this.loaderOnDeckBooks = new PageLoader<BookDto>(
|
||||||
this.loadRecentlyReadBooks(libraryId),
|
{},
|
||||||
]).then(x => {
|
() => this.$komgaBooks.getBooksOnDeck(this.getRequestLibraryId(libraryId)),
|
||||||
|
)
|
||||||
|
this.loaderLatestBooks = new PageLoader<BookDto>(
|
||||||
|
{sort: ['metadata.title,desc']},
|
||||||
|
(pageable: PageRequest) => this.$komgaBooks.getBooks(this.getRequestLibraryId(libraryId), pageable),
|
||||||
|
)
|
||||||
|
this.loaderRecentlyReleasedBooks = new PageLoader<BookDto>(
|
||||||
|
{sort: ['metadata.releaseDate,desc']},
|
||||||
|
(pageable: PageRequest) => this.$komgaBooks.getBooks(this.getRequestLibraryId(libraryId), pageable, undefined, undefined, undefined, subMonths(new Date(), 1)),
|
||||||
|
)
|
||||||
|
this.loaderRecentlyReadBooks = new PageLoader<BookDto>(
|
||||||
|
{sort: ['readProgress.lastModified,desc']},
|
||||||
|
(pageable: PageRequest) => this.$komgaBooks.getBooks(this.getRequestLibraryId(libraryId), pageable, undefined, undefined, [ReadStatus.READ]),
|
||||||
|
)
|
||||||
|
|
||||||
|
this.loaderNewSeries = new PageLoader<SeriesDto>(
|
||||||
|
{},
|
||||||
|
() => this.$komgaSeries.getNewSeries(this.getRequestLibraryId(libraryId)),
|
||||||
|
)
|
||||||
|
this.loaderUpdatedSeries = new PageLoader<SeriesDto>(
|
||||||
|
{},
|
||||||
|
() => this.$komgaSeries.getUpdatedSeries(this.getRequestLibraryId(libraryId)),
|
||||||
|
)
|
||||||
|
|
||||||
|
Promise.all([
|
||||||
|
this.loaderInProgressBooks.loadNext(),
|
||||||
|
this.loaderOnDeckBooks.loadNext(),
|
||||||
|
this.loaderRecentlyReleasedBooks.loadNext(),
|
||||||
|
this.loaderLatestBooks.loadNext(),
|
||||||
|
this.loaderNewSeries.loadNext(),
|
||||||
|
this.loaderUpdatedSeries.loadNext(),
|
||||||
|
this.loaderRecentlyReadBooks.loadNext(),
|
||||||
|
]).then(() => {
|
||||||
this.loading = false
|
this.loading = false
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
async loadNewSeries(libraryId: string) {
|
|
||||||
this.newSeries = (await this.$komgaSeries.getNewSeries(this.getRequestLibraryId(libraryId))).content
|
|
||||||
},
|
|
||||||
async loadUpdatedSeries(libraryId: string) {
|
|
||||||
this.updatedSeries = (await this.$komgaSeries.getUpdatedSeries(this.getRequestLibraryId(libraryId))).content
|
|
||||||
},
|
|
||||||
async loadLatestBooks(libraryId: string) {
|
|
||||||
const pageRequest = {
|
|
||||||
sort: ['createdDate,desc'],
|
|
||||||
} as PageRequest
|
|
||||||
|
|
||||||
this.latestBooks = (await this.$komgaBooks.getBooks(this.getRequestLibraryId(libraryId), pageRequest)).content
|
|
||||||
},
|
|
||||||
async loadRecentlyReleasedBooks(libraryId: string) {
|
|
||||||
const pageRequest = {
|
|
||||||
sort: ['metadata.releaseDate,desc'],
|
|
||||||
} as PageRequest
|
|
||||||
|
|
||||||
const releasedAfter = subMonths(new Date(), 1)
|
|
||||||
this.recentlyReleasedBooks = (await this.$komgaBooks.getBooks(this.getRequestLibraryId(libraryId), pageRequest, undefined, undefined, undefined, releasedAfter)).content
|
|
||||||
},
|
|
||||||
async loadRecentlyReadBooks(libraryId: string) {
|
|
||||||
const pageRequest = {
|
|
||||||
sort: ['readProgress.lastModified,desc'],
|
|
||||||
} as PageRequest
|
|
||||||
|
|
||||||
this.recentlyReadBooks = (await this.$komgaBooks.getBooks(this.getRequestLibraryId(libraryId), pageRequest, undefined, undefined, [ReadStatus.READ])).content
|
|
||||||
},
|
|
||||||
async loadInProgressBooks(libraryId: string) {
|
|
||||||
const pageRequest = {
|
|
||||||
sort: ['readProgress.lastModified,desc'],
|
|
||||||
} as PageRequest
|
|
||||||
|
|
||||||
this.inProgressBooks = (await this.$komgaBooks.getBooks(this.getRequestLibraryId(libraryId), pageRequest, undefined, undefined, [ReadStatus.IN_PROGRESS])).content
|
|
||||||
},
|
|
||||||
async loadOnDeckBooks(libraryId: string) {
|
|
||||||
this.onDeckBooks = (await this.$komgaBooks.getBooksOnDeck(this.getRequestLibraryId(libraryId))).content
|
|
||||||
},
|
|
||||||
singleEditSeries(series: SeriesDto) {
|
singleEditSeries(series: SeriesDto) {
|
||||||
this.$store.dispatch('dialogUpdateSeries', series)
|
this.$store.dispatch('dialogUpdateSeries', series)
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -53,12 +53,17 @@
|
||||||
</empty-state>
|
</empty-state>
|
||||||
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<horizontal-scroller v-if="series.length !== 0" class="mb-4">
|
<horizontal-scroller
|
||||||
|
v-if="loaderSeries && loaderSeries.items.length !== 0"
|
||||||
|
class="mb-4"
|
||||||
|
:tick="loaderSeries.page"
|
||||||
|
@scroll-changed="(percent) => scrollChanged(loaderSeries, percent)"
|
||||||
|
>
|
||||||
<template v-slot:prepend>
|
<template v-slot:prepend>
|
||||||
<div class="title">{{ $t('common.series') }}</div>
|
<div class="title">{{ $t('common.series') }}</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:content>
|
<template v-slot:content>
|
||||||
<item-browser :items="series"
|
<item-browser :items="loaderSeries.items"
|
||||||
nowrap
|
nowrap
|
||||||
:edit-function="isAdmin ? singleEditSeries : undefined"
|
:edit-function="isAdmin ? singleEditSeries : undefined"
|
||||||
:selected.sync="selectedSeries"
|
:selected.sync="selectedSeries"
|
||||||
|
|
@ -68,12 +73,17 @@
|
||||||
</template>
|
</template>
|
||||||
</horizontal-scroller>
|
</horizontal-scroller>
|
||||||
|
|
||||||
<horizontal-scroller v-if="books.length !== 0" class="mb-4">
|
<horizontal-scroller
|
||||||
|
v-if="loaderBooks && loaderBooks.items.length !== 0"
|
||||||
|
class="mb-4"
|
||||||
|
:tick="loaderBooks.page"
|
||||||
|
@scroll-changed="(percent) => scrollChanged(loaderBooks, percent)"
|
||||||
|
>
|
||||||
<template v-slot:prepend>
|
<template v-slot:prepend>
|
||||||
<div class="title">{{ $t('common.books') }}</div>
|
<div class="title">{{ $t('common.books') }}</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:content>
|
<template v-slot:content>
|
||||||
<item-browser :items="books"
|
<item-browser :items="loaderBooks.items"
|
||||||
nowrap
|
nowrap
|
||||||
:edit-function="isAdmin ? singleEditBook : undefined"
|
:edit-function="isAdmin ? singleEditBook : undefined"
|
||||||
:selected.sync="selectedBooks"
|
:selected.sync="selectedBooks"
|
||||||
|
|
@ -83,12 +93,17 @@
|
||||||
</template>
|
</template>
|
||||||
</horizontal-scroller>
|
</horizontal-scroller>
|
||||||
|
|
||||||
<horizontal-scroller v-if="collections.length !== 0" class="mb-4">
|
<horizontal-scroller
|
||||||
|
v-if="loaderCollections && loaderCollections.items.length !== 0"
|
||||||
|
class="mb-4"
|
||||||
|
:tick="loaderCollections.page"
|
||||||
|
@scroll-changed="(percent) => scrollChanged(loaderCollections, percent)"
|
||||||
|
>
|
||||||
<template v-slot:prepend>
|
<template v-slot:prepend>
|
||||||
<div class="title">{{ $t('common.collections') }}</div>
|
<div class="title">{{ $t('common.collections') }}</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:content>
|
<template v-slot:content>
|
||||||
<item-browser :items="collections"
|
<item-browser :items="loaderCollections.items"
|
||||||
nowrap
|
nowrap
|
||||||
:edit-function="isAdmin ? singleEditCollection : undefined"
|
:edit-function="isAdmin ? singleEditCollection : undefined"
|
||||||
:selected.sync="selectedCollections"
|
:selected.sync="selectedCollections"
|
||||||
|
|
@ -98,12 +113,17 @@
|
||||||
</template>
|
</template>
|
||||||
</horizontal-scroller>
|
</horizontal-scroller>
|
||||||
|
|
||||||
<horizontal-scroller v-if="readLists.length !== 0" class="mb-4">
|
<horizontal-scroller
|
||||||
|
v-if="loaderReadLists && loaderReadLists.items.length !== 0"
|
||||||
|
class="mb-4"
|
||||||
|
:tick="loaderReadLists.page"
|
||||||
|
@scroll-changed="(percent) => scrollChanged(loaderReadLists, percent)"
|
||||||
|
>
|
||||||
<template v-slot:prepend>
|
<template v-slot:prepend>
|
||||||
<div class="title">{{ $t('common.readlists') }}</div>
|
<div class="title">{{ $t('common.readlists') }}</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:content>
|
<template v-slot:content>
|
||||||
<item-browser :items="readLists"
|
<item-browser :items="loaderReadLists.items"
|
||||||
nowrap
|
nowrap
|
||||||
:edit-function="isAdmin ? singleEditReadList : undefined"
|
:edit-function="isAdmin ? singleEditReadList : undefined"
|
||||||
:selected.sync="selectedReadLists"
|
:selected.sync="selectedReadLists"
|
||||||
|
|
@ -152,6 +172,7 @@ import {
|
||||||
SeriesSseDto,
|
SeriesSseDto,
|
||||||
} from '@/types/komga-sse'
|
} from '@/types/komga-sse'
|
||||||
import {throttle} from 'lodash'
|
import {throttle} from 'lodash'
|
||||||
|
import {PageLoader} from '@/types/pageLoader'
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
name: 'Search',
|
name: 'Search',
|
||||||
|
|
@ -164,11 +185,11 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
data: () => {
|
data: () => {
|
||||||
return {
|
return {
|
||||||
series: [] as SeriesDto[],
|
loaderSeries: undefined as unknown as PageLoader<SeriesDto>,
|
||||||
books: [] as BookDto[],
|
loaderBooks: undefined as unknown as PageLoader<BookDto>,
|
||||||
collections: [] as CollectionDto[],
|
loaderCollections: undefined as unknown as PageLoader<CollectionDto>,
|
||||||
readLists: [] as ReadListDto[],
|
loaderReadLists: undefined as unknown as PageLoader<ReadListDto>,
|
||||||
pageSize: 50,
|
pageSize: 20,
|
||||||
loading: false,
|
loading: false,
|
||||||
selectedSeries: [] as SeriesDto[],
|
selectedSeries: [] as SeriesDto[],
|
||||||
selectedBooks: [] as BookDto[],
|
selectedBooks: [] as BookDto[],
|
||||||
|
|
@ -176,7 +197,7 @@ export default Vue.extend({
|
||||||
selectedReadLists: [] as ReadListDto[],
|
selectedReadLists: [] as ReadListDto[],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created () {
|
created() {
|
||||||
this.$eventHub.$on(LIBRARY_DELETED, this.reloadResults)
|
this.$eventHub.$on(LIBRARY_DELETED, this.reloadResults)
|
||||||
this.$eventHub.$on(SERIES_CHANGED, this.seriesChanged)
|
this.$eventHub.$on(SERIES_CHANGED, this.seriesChanged)
|
||||||
this.$eventHub.$on(SERIES_DELETED, this.seriesChanged)
|
this.$eventHub.$on(SERIES_DELETED, this.seriesChanged)
|
||||||
|
|
@ -191,7 +212,7 @@ export default Vue.extend({
|
||||||
this.$eventHub.$on(READPROGRESS_SERIES_CHANGED, this.readProgressSeriesChanged)
|
this.$eventHub.$on(READPROGRESS_SERIES_CHANGED, this.readProgressSeriesChanged)
|
||||||
this.$eventHub.$on(READPROGRESS_SERIES_DELETED, this.readProgressSeriesChanged)
|
this.$eventHub.$on(READPROGRESS_SERIES_DELETED, this.readProgressSeriesChanged)
|
||||||
},
|
},
|
||||||
beforeDestroy () {
|
beforeDestroy() {
|
||||||
this.$eventHub.$off(LIBRARY_DELETED, this.reloadResults)
|
this.$eventHub.$off(LIBRARY_DELETED, this.reloadResults)
|
||||||
this.$eventHub.$off(SERIES_CHANGED, this.seriesChanged)
|
this.$eventHub.$off(SERIES_CHANGED, this.seriesChanged)
|
||||||
this.$eventHub.$off(SERIES_DELETED, this.seriesChanged)
|
this.$eventHub.$off(SERIES_DELETED, this.seriesChanged)
|
||||||
|
|
@ -223,60 +244,67 @@ export default Vue.extend({
|
||||||
isAdmin(): boolean {
|
isAdmin(): boolean {
|
||||||
return this.$store.getters.meAdmin
|
return this.$store.getters.meAdmin
|
||||||
},
|
},
|
||||||
fixedCardWidth (): number {
|
fixedCardWidth(): number {
|
||||||
return this.$vuetify.breakpoint.name === 'xs' ? 120 : 150
|
return this.$vuetify.breakpoint.name === 'xs' ? 120 : 150
|
||||||
},
|
},
|
||||||
showToolbar (): boolean {
|
showToolbar(): boolean {
|
||||||
return this.selectedSeries.length === 0 && this.selectedBooks.length === 0 && this.selectedCollections.length === 0 && this.selectedReadLists.length === 0
|
return this.selectedSeries.length === 0 && this.selectedBooks.length === 0 && this.selectedCollections.length === 0 && this.selectedReadLists.length === 0
|
||||||
},
|
},
|
||||||
emptyResults (): boolean {
|
emptyResults(): boolean {
|
||||||
return !this.loading && this.series.length === 0 && this.books.length === 0 && this.collections.length === 0 && this.readLists.length === 0
|
return !this.loading &&
|
||||||
|
this.loaderSeries?.items.length === 0 &&
|
||||||
|
this.loaderBooks?.items.length === 0 &&
|
||||||
|
this.loaderCollections?.items.length === 0 &&
|
||||||
|
this.loaderReadLists?.items.length === 0
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
seriesChanged(event: SeriesSseDto){
|
async scrollChanged(loader: PageLoader<any>, percent: number) {
|
||||||
if(this.series.map(x => x.id).includes(event.seriesId)){
|
if (percent > 0.95) await loader.loadNext()
|
||||||
|
},
|
||||||
|
seriesChanged(event: SeriesSseDto) {
|
||||||
|
if (this.loaderSeries?.items.map(x => x.id).includes(event.seriesId)) {
|
||||||
this.reloadResults()
|
this.reloadResults()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
bookChanged(event: BookSseDto){
|
bookChanged(event: BookSseDto) {
|
||||||
if(this.books.map(x => x.id).includes(event.bookId)){
|
if (this.loaderBooks?.items.map(x => x.id).includes(event.bookId)) {
|
||||||
this.reloadResults()
|
this.reloadResults()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
readProgressChanged(event: ReadProgressSseDto){
|
readProgressChanged(event: ReadProgressSseDto) {
|
||||||
if(this.books.map(x => x.id).includes(event.bookId)){
|
if (this.loaderBooks?.items.map(x => x.id).includes(event.bookId)) {
|
||||||
this.reloadResults()
|
this.reloadResults()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
readProgressSeriesChanged(event: ReadProgressSeriesSseDto){
|
readProgressSeriesChanged(event: ReadProgressSeriesSseDto) {
|
||||||
if(this.series.map(x => x.id).includes(event.seriesId)){
|
if (this.loaderSeries?.items.map(x => x.id).includes(event.seriesId)) {
|
||||||
this.reloadResults()
|
this.reloadResults()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
collectionChanged (event: CollectionSseDto) {
|
collectionChanged(event: CollectionSseDto) {
|
||||||
if (this.collections.map(x => x.id).includes(event.collectionId)) {
|
if (this.loaderCollections?.items.map(x => x.id).includes(event.collectionId)) {
|
||||||
this.reloadResults()
|
this.reloadResults()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
readListChanged (event: ReadListSseDto) {
|
readListChanged(event: ReadListSseDto) {
|
||||||
if (this.readLists.map(x => x.id).includes(event.readListId)) {
|
if (this.loaderReadLists?.items.map(x => x.id).includes(event.readListId)) {
|
||||||
this.reloadResults()
|
this.reloadResults()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
singleEditSeries (series: SeriesDto) {
|
singleEditSeries(series: SeriesDto) {
|
||||||
this.$store.dispatch('dialogUpdateSeries', series)
|
this.$store.dispatch('dialogUpdateSeries', series)
|
||||||
},
|
},
|
||||||
singleEditBook (book: BookDto) {
|
singleEditBook(book: BookDto) {
|
||||||
this.$store.dispatch('dialogUpdateBooks', book)
|
this.$store.dispatch('dialogUpdateBooks', book)
|
||||||
},
|
},
|
||||||
singleEditCollection (collection: CollectionDto) {
|
singleEditCollection(collection: CollectionDto) {
|
||||||
this.$store.dispatch('dialogEditCollection', collection)
|
this.$store.dispatch('dialogEditCollection', collection)
|
||||||
},
|
},
|
||||||
singleEditReadList (readList: ReadListDto) {
|
singleEditReadList(readList: ReadListDto) {
|
||||||
this.$store.dispatch('dialogEditReadList', readList)
|
this.$store.dispatch('dialogEditReadList', readList)
|
||||||
},
|
},
|
||||||
async markSelectedSeriesRead () {
|
async markSelectedSeriesRead() {
|
||||||
await Promise.all(this.selectedSeries.map(s =>
|
await Promise.all(this.selectedSeries.map(s =>
|
||||||
this.$komgaSeries.markAsRead(s.id),
|
this.$komgaSeries.markAsRead(s.id),
|
||||||
))
|
))
|
||||||
|
|
@ -284,7 +312,7 @@ export default Vue.extend({
|
||||||
this.$komgaSeries.getOneSeries(s.id),
|
this.$komgaSeries.getOneSeries(s.id),
|
||||||
))
|
))
|
||||||
},
|
},
|
||||||
async markSelectedSeriesUnread () {
|
async markSelectedSeriesUnread() {
|
||||||
await Promise.all(this.selectedSeries.map(s =>
|
await Promise.all(this.selectedSeries.map(s =>
|
||||||
this.$komgaSeries.markAsUnread(s.id),
|
this.$komgaSeries.markAsUnread(s.id),
|
||||||
))
|
))
|
||||||
|
|
@ -292,33 +320,33 @@ export default Vue.extend({
|
||||||
this.$komgaSeries.getOneSeries(s.id),
|
this.$komgaSeries.getOneSeries(s.id),
|
||||||
))
|
))
|
||||||
},
|
},
|
||||||
addToCollection () {
|
addToCollection() {
|
||||||
this.$store.dispatch('dialogAddSeriesToCollection', this.selectedSeries)
|
this.$store.dispatch('dialogAddSeriesToCollection', this.selectedSeries)
|
||||||
},
|
},
|
||||||
addToReadList () {
|
addToReadList() {
|
||||||
this.$store.dispatch('dialogAddBooksToReadList', this.selectedBooks)
|
this.$store.dispatch('dialogAddBooksToReadList', this.selectedBooks)
|
||||||
},
|
},
|
||||||
editMultipleSeries () {
|
editMultipleSeries() {
|
||||||
this.$store.dispatch('dialogUpdateSeries', this.selectedSeries)
|
this.$store.dispatch('dialogUpdateSeries', this.selectedSeries)
|
||||||
},
|
},
|
||||||
editMultipleBooks () {
|
editMultipleBooks() {
|
||||||
this.$store.dispatch('dialogUpdateBooks', this.selectedBooks)
|
this.$store.dispatch('dialogUpdateBooks', this.selectedBooks)
|
||||||
},
|
},
|
||||||
bulkEditMultipleBooks() {
|
bulkEditMultipleBooks() {
|
||||||
this.$store.dispatch('dialogUpdateBulkBooks', this.selectedBooks)
|
this.$store.dispatch('dialogUpdateBulkBooks', this.selectedBooks)
|
||||||
},
|
},
|
||||||
deleteCollections () {
|
deleteCollections() {
|
||||||
this.$store.dispatch('dialogDeleteCollection', this.selectedCollections)
|
this.$store.dispatch('dialogDeleteCollection', this.selectedCollections)
|
||||||
},
|
},
|
||||||
deleteReadLists () {
|
deleteReadLists() {
|
||||||
this.$store.dispatch('dialogDeleteReadList', this.selectedReadLists)
|
this.$store.dispatch('dialogDeleteReadList', this.selectedReadLists)
|
||||||
},
|
},
|
||||||
async markSelectedBooksRead () {
|
async markSelectedBooksRead() {
|
||||||
await Promise.all(this.selectedBooks.map(b =>
|
await Promise.all(this.selectedBooks.map(b =>
|
||||||
this.$komgaBooks.updateReadProgress(b.id, { completed: true }),
|
this.$komgaBooks.updateReadProgress(b.id, {completed: true}),
|
||||||
))
|
))
|
||||||
},
|
},
|
||||||
async markSelectedBooksUnread () {
|
async markSelectedBooksUnread() {
|
||||||
await Promise.all(this.selectedBooks.map(b =>
|
await Promise.all(this.selectedBooks.map(b =>
|
||||||
this.$komgaBooks.deleteReadProgress(b.id),
|
this.$komgaBooks.deleteReadProgress(b.id),
|
||||||
))
|
))
|
||||||
|
|
@ -326,7 +354,7 @@ export default Vue.extend({
|
||||||
reloadResults: throttle(function (this: any) {
|
reloadResults: throttle(function (this: any) {
|
||||||
this.loadResults(this.$route.query.q.toString())
|
this.loadResults(this.$route.query.q.toString())
|
||||||
}, 500),
|
}, 500),
|
||||||
async loadResults (search: string) {
|
async loadResults(search: string) {
|
||||||
this.selectedBooks = []
|
this.selectedBooks = []
|
||||||
this.selectedSeries = []
|
this.selectedSeries = []
|
||||||
this.selectedCollections = []
|
this.selectedCollections = []
|
||||||
|
|
@ -334,17 +362,24 @@ export default Vue.extend({
|
||||||
if (search) {
|
if (search) {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
|
|
||||||
this.series = (await this.$komgaSeries.getSeries(undefined, { size: this.pageSize }, search)).content
|
this.loaderSeries = new PageLoader<SeriesDto>({size: this.pageSize}, (pageable: PageRequest) => this.$komgaSeries.getSeries(undefined, pageable, search))
|
||||||
this.books = (await this.$komgaBooks.getBooks(undefined, { size: this.pageSize }, search)).content
|
this.loaderBooks = new PageLoader<BookDto>({size: this.pageSize}, (pageable: PageRequest) => this.$komgaBooks.getBooks(undefined, pageable, search))
|
||||||
this.collections = (await this.$komgaCollections.getCollections(undefined, { size: this.pageSize }, search)).content
|
this.loaderCollections = new PageLoader<CollectionDto>({size: this.pageSize}, (pageable: PageRequest) => this.$komgaCollections.getCollections(undefined, pageable, search))
|
||||||
this.readLists = (await this.$komgaReadLists.getReadLists(undefined, { size: this.pageSize }, search)).content
|
this.loaderReadLists = new PageLoader<ReadListDto>({size: this.pageSize}, (pageable: PageRequest) => this.$komgaReadLists.getReadLists(undefined, pageable, search))
|
||||||
|
|
||||||
this.loading = false
|
Promise.all([
|
||||||
|
this.loaderSeries.loadNext(),
|
||||||
|
this.loaderBooks.loadNext(),
|
||||||
|
this.loaderCollections.loadNext(),
|
||||||
|
this.loaderReadLists.loadNext(),
|
||||||
|
]).then(() => {
|
||||||
|
this.loading = false
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
this.series = []
|
this.loaderSeries = null as unknown as PageLoader<SeriesDto>
|
||||||
this.books = []
|
this.loaderBooks = null as unknown as PageLoader<BookDto>
|
||||||
this.collections = []
|
this.loaderCollections = null as unknown as PageLoader<CollectionDto>
|
||||||
this.readLists = []
|
this.loaderReadLists = null as unknown as PageLoader<ReadListDto>
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
49
komga-webui/tests/unit/types/pageLoader.spec.ts
Normal file
49
komga-webui/tests/unit/types/pageLoader.spec.ts
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
import {PageLoader} from '@/types/pageLoader'
|
||||||
|
|
||||||
|
describe('PageLoader', () => {
|
||||||
|
const pageRequest = {} as PageRequest
|
||||||
|
const loader = (pageable: PageRequest) => Promise.resolve({
|
||||||
|
content: [1, 2, 3],
|
||||||
|
last: true,
|
||||||
|
number: 0,
|
||||||
|
} as Page<number>)
|
||||||
|
|
||||||
|
test('given page loader when loading next then it should return true', async () => {
|
||||||
|
const pageLoader = new PageLoader(pageRequest, loader)
|
||||||
|
|
||||||
|
expect(await pageLoader.loadNext()).toBeTruthy()
|
||||||
|
expect(pageLoader.items).toStrictEqual([1, 2, 3])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('given page loader on last page when loading next then it should return false', async () => {
|
||||||
|
const pageLoader = new PageLoader(pageRequest, loader)
|
||||||
|
|
||||||
|
expect(await pageLoader.loadNext()).toBeTruthy()
|
||||||
|
expect(pageLoader.items).toStrictEqual([1, 2, 3])
|
||||||
|
expect(await pageLoader.loadNext()).toBeFalsy()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('given page loader when loading next then it should return true until last page is reached', async () => {
|
||||||
|
const loader = (pageable: PageRequest) => Promise.resolve({
|
||||||
|
content: pageable.page === 1 ? [4, 5, 6] : [1, 2, 3],
|
||||||
|
last: pageable.page === 1,
|
||||||
|
number: pageable.page || 0,
|
||||||
|
} as Page<number>)
|
||||||
|
const pageLoader = new PageLoader(pageRequest, loader)
|
||||||
|
|
||||||
|
expect(await pageLoader.loadNext()).toBeTruthy()
|
||||||
|
expect(pageLoader.items).toStrictEqual([1, 2, 3])
|
||||||
|
expect(await pageLoader.loadNext()).toBeTruthy()
|
||||||
|
expect(pageLoader.items).toStrictEqual([1, 2, 3, 4, 5, 6])
|
||||||
|
expect(await pageLoader.loadNext()).toBeFalsy()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('given exception when loading next then exception is rethrown', async () => {
|
||||||
|
const loader = (pageable: PageRequest) => {
|
||||||
|
throw new Error('boom')
|
||||||
|
}
|
||||||
|
const pageLoader = new PageLoader(pageRequest, loader)
|
||||||
|
|
||||||
|
await expect(pageLoader.loadNext()).rejects.toEqual(new Error('boom'))
|
||||||
|
})
|
||||||
|
})
|
||||||
Loading…
Reference in a new issue