perf(webui): readlist/collection expansion panels load data by page

Refs: #817
This commit is contained in:
Gauthier Roebroeck 2023-01-18 17:36:55 +08:00
parent ff70fea71a
commit 0b57dc9c96
3 changed files with 63 additions and 28 deletions

View file

@ -5,14 +5,18 @@
>
<v-expansion-panel-header>{{ $t('collections_expansion_panel.title', {name: c.name}) }}</v-expansion-panel-header>
<v-expansion-panel-content>
<horizontal-scroller>
<horizontal-scroller
:tick="collectionsLoaders[index].tick"
@scroll-changed="(percent) => scrollChanged(collectionsLoaders[index], percent)"
>
<template v-slot:prepend>
<router-link class="text-overline"
:to="{name: 'browse-collection', params: {collectionId: c.id}}"
>{{ $t('collections_expansion_panel.manage_collection') }}</router-link>
>{{ $t('collections_expansion_panel.manage_collection') }}
</router-link>
</template>
<template v-slot:content>
<item-browser :items="collectionsContent[index]"
<item-browser :items="collectionsLoaders[index].items"
nowrap
:selectable="false"
:action-menu="false"
@ -31,6 +35,7 @@ import ItemBrowser from '@/components/ItemBrowser.vue'
import Vue from 'vue'
import {ContextOrigin} from '@/types/context'
import {SeriesDto} from '@/types/komga-series'
import {PageLoader} from '@/types/pageLoader'
export default Vue.extend({
name: 'CollectionsExpansionPanels',
@ -47,27 +52,36 @@ export default Vue.extend({
data: () => {
return {
collectionPanel: undefined as number | undefined,
collectionsContent: [[]] as any[],
collectionsLoaders: [] as PageLoader<SeriesDto>[],
}
},
watch: {
collections: {
handler (val) {
handler(val: CollectionDto[]) {
this.collectionPanel = undefined
this.collectionsContent = [...Array(val.length)].map(elem => new Array(0))
this.collectionsLoaders = val.map(coll => new PageLoader<SeriesDto>(
{},
(pageable: PageRequest) => this.$komgaCollections.getSeries(coll.id, pageable),
(x: SeriesDto) => {
x.context = {origin: ContextOrigin.COLLECTION, id: coll.id}
return x
},
))
},
immediate: true,
},
async collectionPanel (val) {
async collectionPanel(val) {
if (val !== undefined) {
const collId = this.collections[val].id
if (this.$_.isEmpty(this.collectionsContent[val])) {
const content = (await this.$komgaCollections.getSeries(collId, { unpaged: true } as PageRequest)).content
content.forEach((x: SeriesDto) => x.context = { origin: ContextOrigin.COLLECTION, id: collId })
this.collectionsContent.splice(val, 1, content)
if (!this.collectionsLoaders[val].hasLoadedAny) {
this.collectionsLoaders[val].loadNext()
}
}
},
},
methods: {
async scrollChanged(loader: PageLoader<any>, percent: number) {
if (percent > 0.95) await loader.loadNext()
},
},
})
</script>

View file

@ -5,14 +5,18 @@
>
<v-expansion-panel-header>{{ $t('readlists_expansion_panel.title', {name: r.name}) }}</v-expansion-panel-header>
<v-expansion-panel-content>
<horizontal-scroller>
<horizontal-scroller
:tick="readListsLoaders[index].tick"
@scroll-changed="(percent) => scrollChanged(readListsLoaders[index], percent)"
>
<template v-slot:prepend>
<router-link class="text-overline"
:to="{name: 'browse-readlist', params: {readListId: r.id}}"
>{{ $t('readlists_expansion_panel.manage_readlist') }}</router-link>
>{{ $t('readlists_expansion_panel.manage_readlist') }}
</router-link>
</template>
<template v-slot:content>
<item-browser :items="readListsContent[index]"
<item-browser :items="readListsLoaders[index].items"
:item-context="[ItemContext.SHOW_SERIES]"
nowrap
:selectable="false"
@ -33,6 +37,7 @@ import Vue from 'vue'
import {BookDto} from '@/types/komga-books'
import {ContextOrigin} from '@/types/context'
import {ItemContext} from '@/types/items'
import {PageLoader} from '@/types/pageLoader'
export default Vue.extend({
name: 'ReadListsExpansionPanels',
@ -50,27 +55,36 @@ export default Vue.extend({
return {
ItemContext,
readListPanel: undefined as number | undefined,
readListsContent: [[]] as any[],
readListsLoaders: [] as PageLoader<BookDto>[],
}
},
watch: {
readLists: {
handler (val) {
handler(val: ReadListDto[]) {
this.readListPanel = undefined
this.readListsContent = [...Array(val.length)].map(elem => new Array(0))
this.readListsLoaders = val.map(rl => new PageLoader<BookDto>(
{},
(pageable: PageRequest) => this.$komgaReadLists.getBooks(rl.id, pageable),
(x: BookDto) => {
x.context = {origin: ContextOrigin.READLIST, id: rl.id}
return x
},
))
},
immediate: true,
},
async readListPanel (val) {
async readListPanel(val) {
if (val !== undefined) {
const rlId = this.readLists[val].id
if (this.$_.isEmpty(this.readListsContent[val])) {
const content = (await this.$komgaReadLists.getBooks(rlId, { unpaged: true } as PageRequest)).content
content.forEach((x: BookDto) => x.context = { origin: ContextOrigin.READLIST, id: rlId })
this.readListsContent.splice(val, 1, content)
if (!this.readListsLoaders[val].hasLoadedAny) {
this.readListsLoaders[val].loadNext()
}
}
},
},
methods: {
async scrollChanged(loader: PageLoader<any>, percent: number) {
if (percent > 0.95) await loader.loadNext()
},
},
})
</script>

View file

@ -1,6 +1,7 @@
export class PageLoader<T> {
private readonly pageable: PageRequest
private readonly loader: (pageRequest: PageRequest) => Promise<Page<T>>
private readonly postProcessor: (item: T) => T
private currentPage = undefined as unknown as Page<T>
private loadedPages: number[] = []
@ -15,9 +16,15 @@ export class PageLoader<T> {
return this._tick
}
constructor(pageable: PageRequest, loader: (pageRequest: PageRequest) => Promise<Page<T>>) {
// whether anything has been loaded yet
get hasLoadedAny() {
return this.currentPage
}
constructor(pageable: PageRequest, loader: (pageRequest: PageRequest) => Promise<Page<T>>, postProcessor: (item: T) => T = (item) => item) {
this.pageable = pageable
this.loader = loader
this.postProcessor = postProcessor
}
async reload() {
@ -29,7 +36,7 @@ export class PageLoader<T> {
page: 0,
})
const page = await this.loader(pageable)
this.items.splice(0, this.items.length, ...page.content)
this.items.splice(0, this.items.length, ...page.content.map(this.postProcessor))
this._tick++
}
@ -38,7 +45,7 @@ export class PageLoader<T> {
if (!this.currentPage) {
this.loadedPages.push(this.pageable.page || 0)
this.currentPage = await this.loader(this.pageable)
this.items.push(...this.currentPage.content)
this.items.push(...this.currentPage.content.map(this.postProcessor))
this._tick++
return true
}
@ -50,7 +57,7 @@ export class PageLoader<T> {
this.loadedPages.push(nextPage)
const pageable = Object.assign({}, this.pageable, {page: nextPage})
this.currentPage = await this.loader(pageable)
this.items.push(...this.currentPage.content)
this.items.push(...this.currentPage.content.map(this.postProcessor))
this._tick++
return true
}