fix(webui): use pagination for browsing screens

closes #91
This commit is contained in:
Gauthier Roebroeck 2020-06-03 15:43:03 +08:00
parent 75b72164fe
commit 5867db77f5
8 changed files with 286 additions and 241 deletions

View file

@ -1,31 +1,24 @@
<template> <template>
<v-item-group multiple v-model="selectedItems"> <v-item-group multiple v-model="selectedItems">
<v-row justify="start" ref="content" v-resize="onResize" v-if="hasItems"> <v-row justify="start" ref="content" v-resize="onResize" v-if="hasItems">
<v-skeleton-loader v-for="(item, index) in items" <v-item
:key="index" v-for="(item, index) in items"
:width="itemWidth" :key="index"
:height="itemHeight" class="my-3 mx-2"
justify-self="start" v-slot:default="{ toggle, active }" :value="$_.get(item, 'id', 0)"
:loading="item === null"
type="card, text"
class="ma-3 mx-2"
:data-index="index"
v-intersect="onElementIntersect"
> >
<v-item v-slot:default="{ toggle, active }" :value="$_.get(item, 'id', 0)"> <slot name="item"
<slot name="item" v-bind:data="{ toggle, active, item, index, itemWidth, preselect: shouldPreselect(), editItem }"> v-bind:data="{ toggle, active, item, index, itemWidth, preselect: shouldPreselect(), editItem }">
<item-card <item-card
:item="item" :item="item"
:width="itemWidth" :width="itemWidth"
:selected="active" :selected="active"
:preselect="shouldPreselect()" :preselect="shouldPreselect()"
:onEdit="editItem" :onEdit="editItem"
:onSelected="toggle" :onSelected="toggle"
></item-card> ></item-card>
</slot> </slot>
</v-item> </v-item>
</v-skeleton-loader>
</v-row> </v-row>
<v-row v-else justify="center"> <v-row v-else justify="center">
<slot name="empty"></slot> <slot name="empty"></slot>
@ -34,13 +27,11 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import Vue from 'vue'
import { computeCardWidth } from '@/functions/grid-utilities'
import ItemCard from '@/components/ItemCard.vue' import ItemCard from '@/components/ItemCard.vue'
import mixins from 'vue-typed-mixins' import { computeCardWidth } from '@/functions/grid-utilities'
import VisibleElements from '@/mixins/VisibleElements' import Vue from 'vue'
export default mixins(VisibleElements).extend({ export default Vue.extend({
name: 'ItemBrowser', name: 'ItemBrowser',
components: { ItemCard }, components: { ItemCard },
props: { props: {
@ -66,12 +57,6 @@ export default mixins(VisibleElements).extend({
} }
}, },
watch: { watch: {
series: {
handler () {
this.visibleElements = []
},
immediate: true,
},
selectedItems: { selectedItems: {
handler () { handler () {
this.$emit('update:selected', this.selectedItems) this.$emit('update:selected', this.selectedItems)

View file

@ -0,0 +1,57 @@
<template>
<v-menu offset-y>
<template v-slot:activator="{on}">
<v-btn icon v-on="on">
<v-icon>mdi-view-grid-plus</v-icon>
</v-btn>
</template>
<v-list>
<v-list-item-group v-model="selection">
<v-list-item v-for="(item, index) in items"
:key="index"
@click="setPageSize(item)"
>
<v-list-item-title>{{ item }}</v-list-item-title>
</v-list-item>
</v-list-item-group>
</v-list>
</v-menu>
</template>
<script lang="ts">
import Vue from 'vue'
export default Vue.extend({
name: 'PageSizeSelect',
data: () => {
return {
selection: 0,
}
},
props: {
items: {
type: Array,
default: () => [20, 50, 100, 200, 500],
},
value: {
type: Number,
required: true,
},
},
watch: {
value (val) {
this.selection = this.items.findIndex(x => x === val)
},
},
methods: {
setPageSize (size: number) {
this.$emit('input', size)
},
},
})
</script>
<style scoped>
</style>

View file

@ -1,22 +0,0 @@
import Vue from 'vue'
const visibleElements = Vue.extend({
data: () => {
return {
visibleElements: [] as number[],
}
},
methods: {
async onElementIntersect (entries: any, observer: any, isIntersecting: boolean) {
const elementIndex = Number(entries[0].target.dataset['index'])
if (isIntersecting) {
this.visibleElements.push(elementIndex)
} else {
this.$_.pull(this.visibleElements, elementIndex)
}
this.$emit('update', this.visibleElements)
},
},
})
export default visibleElements

View file

@ -5,13 +5,19 @@ import store from './store'
Vue.use(Router) Vue.use(Router)
const lStore = store const lStore = store as any
const adminGuard = (to: any, from: any, next: any) => { const adminGuard = (to: any, from: any, next: any) => {
if (!lStore.getters.meAdmin) next({ name: 'home' }) if (!lStore.getters.meAdmin) next({ name: 'home' })
else next() else next()
} }
const noLibraryGuard = (to: any, from: any, next: any) => {
if (lStore.state.komgaLibraries.libraries.length === 0) {
next({ name: 'welcome' })
} else next()
}
const router = new Router({ const router = new Router({
mode: 'history', mode: 'history',
base: urls.base, base: urls.base,
@ -36,6 +42,7 @@ const router = new Router({
{ {
path: '/dashboard', path: '/dashboard',
name: 'dashboard', name: 'dashboard',
beforeEnter: noLibraryGuard,
component: () => import(/* webpackChunkName: "dashboard" */ './views/Dashboard.vue'), component: () => import(/* webpackChunkName: "dashboard" */ './views/Dashboard.vue'),
}, },
{ {
@ -71,13 +78,14 @@ const router = new Router({
component: () => import(/* webpackChunkName: "account" */ './views/AccountSettings.vue'), component: () => import(/* webpackChunkName: "account" */ './views/AccountSettings.vue'),
}, },
{ {
path: '/libraries/:libraryId/:index?', path: '/libraries/:libraryId',
name: 'browse-libraries', name: 'browse-libraries',
beforeEnter: noLibraryGuard,
component: () => import(/* webpackChunkName: "browse-libraries" */ './views/BrowseLibraries.vue'), component: () => import(/* webpackChunkName: "browse-libraries" */ './views/BrowseLibraries.vue'),
props: (route) => ({ libraryId: Number(route.params.libraryId) }), props: (route) => ({ libraryId: Number(route.params.libraryId) }),
}, },
{ {
path: '/series/:seriesId/:index?', path: '/series/:seriesId',
name: 'browse-series', name: 'browse-series',
component: () => import(/* webpackChunkName: "browse-series" */ './views/BrowseSeries.vue'), component: () => import(/* webpackChunkName: "browse-series" */ './views/BrowseSeries.vue'),
props: (route) => ({ seriesId: Number(route.params.seriesId) }), props: (route) => ({ seriesId: Number(route.params.seriesId) }),

View file

@ -1,5 +0,0 @@
export enum LoadState {
Loaded,
NotLoaded,
Loading
}

View file

@ -42,6 +42,9 @@
:sort-options="sortOptions" :sort-options="sortOptions"
:sort-active.sync="sortActive" :sort-active.sync="sortActive"
/> />
<page-size-select v-model="pageSize"/>
</toolbar-sticky> </toolbar-sticky>
<v-scroll-y-transition hide-on-leave> <v-scroll-y-transition hide-on-leave>
@ -69,57 +72,72 @@
:series.sync="editSeriesSingle" :series.sync="editSeriesSingle"
/> />
<item-browser :items="series" <v-container>
:selected.sync="selected" <empty-state
:edit-function="this.singleEdit" v-if="totalPages === 0"
class="px-6" title="The active filter has no matches"
@update="updateVisible" sub-title="Use the menu above to change the active filter"
> icon="mdi-book-multiple"
<template #empty v-if="filterStatus.length > 0"> icon-color="secondary"
<empty-state title="The active filter has no matches" >
sub-title="Use the menu above to change the active filter" <v-btn @click="filterStatus = []">Clear filter</v-btn>
icon="mdi-book-multiple" </empty-state>
icon-color="secondary"
> <template v-else>
<v-btn @click="filterStatus = []">Clear filter</v-btn> <v-pagination
</empty-state> v-model="page"
:total-visible="paginationVisible"
:length="totalPages"
/>
<item-browser
:items="series"
:selected.sync="selected"
:edit-function="this.singleEdit"
class="px-6"
/>
</template> </template>
<template #empty v-else> </v-container>
<empty-state title="There are no current libraries"
sub-title="Use the + button on the side bar to add a library"
icon="mdi-book-multiple"
icon-color="secondary"
>
</empty-state>
</template>
</item-browser>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import Badge from '@/components/Badge.vue' import Badge from '@/components/Badge.vue'
import EmptyState from '@/components/EmptyState.vue'
import EditSeriesDialog from '@/components/EditSeriesDialog.vue' import EditSeriesDialog from '@/components/EditSeriesDialog.vue'
import EmptyState from '@/components/EmptyState.vue'
import ItemBrowser from '@/components/ItemBrowser.vue'
import LibraryActionsMenu from '@/components/LibraryActionsMenu.vue' import LibraryActionsMenu from '@/components/LibraryActionsMenu.vue'
import PageSizeSelect from '@/components/PageSizeSelect.vue'
import SortMenuButton from '@/components/SortMenuButton.vue' import SortMenuButton from '@/components/SortMenuButton.vue'
import ToolbarSticky from '@/components/ToolbarSticky.vue' import ToolbarSticky from '@/components/ToolbarSticky.vue'
import { parseQuerySort } from '@/functions/query-params' import { parseQuerySort } from '@/functions/query-params'
import { LoadState } from '@/types/common'
import { SeriesStatus } from '@/types/enum-series' import { SeriesStatus } from '@/types/enum-series'
import ItemBrowser from '@/components/ItemBrowser.vue'
import Vue from 'vue' import Vue from 'vue'
const cookiePageSize = 'pagesize'
export default Vue.extend({ export default Vue.extend({
name: 'BrowseLibraries', name: 'BrowseLibraries',
components: { LibraryActionsMenu, EmptyState, ToolbarSticky, SortMenuButton, Badge, EditSeriesDialog, ItemBrowser }, components: {
LibraryActionsMenu,
EmptyState,
ToolbarSticky,
SortMenuButton,
Badge,
EditSeriesDialog,
ItemBrowser,
PageSizeSelect,
},
data: () => { data: () => {
return { return {
library: undefined as LibraryDto | undefined, library: undefined as LibraryDto | undefined,
series: [] as SeriesDto[], series: [] as SeriesDto[],
selectedSeries: [] as SeriesDto[], selectedSeries: [] as SeriesDto[],
editSeriesSingle: {} as SeriesDto, editSeriesSingle: {} as SeriesDto,
pagesState: [] as LoadState[], page: 1,
pageSize: 20, pageSize: 20,
totalPages: 1,
totalElements: null as number | null, totalElements: null as number | null,
sortOptions: [ sortOptions: [
{ name: 'Name', key: 'metadata.titleSort' }, { name: 'Name', key: 'metadata.titleSort' },
@ -132,6 +150,8 @@ export default Vue.extend({
SeriesStatus, SeriesStatus,
sortUnwatch: null as any, sortUnwatch: null as any,
filterUnwatch: null as any, filterUnwatch: null as any,
pageUnwatch: null as any,
pageSizeUnwatch: null as any,
selected: [], selected: [],
dialogEdit: false, dialogEdit: false,
dialogEditSingle: false, dialogEditSingle: false,
@ -163,24 +183,18 @@ export default Vue.extend({
} }
}, },
}, },
async created () {
this.library = await this.getLibraryLazy(this.libraryId)
},
mounted () { mounted () {
// fill series skeletons if an index is provided, so scroll position can be restored if (this.$cookies.isKey(cookiePageSize)) {
if (this.$route.params.index) { this.pageSize = Number(this.$cookies.get(cookiePageSize))
this.series = Array(Number(this.$route.params.index)).fill(null)
} else { // else fill one page of skeletons
this.series = Array(this.pageSize).fill(null)
} }
// restore sort from query param // restore from query param
this.sortActive = this.parseQuerySortOrDefault(this.$route.query.sort) this.sortActive = this.parseQuerySortOrDefault(this.$route.query.sort)
// restore filter status from query params
this.filterStatus = this.parseQueryFilterStatus(this.$route.query.status) this.filterStatus = this.parseQueryFilterStatus(this.$route.query.status)
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.reloadData(Number(this.$route.params.libraryId), this.series.length) this.loadLibrary(this.libraryId)
this.setWatches() this.setWatches()
}, },
@ -188,16 +202,14 @@ export default Vue.extend({
if (to.params.libraryId !== from.params.libraryId) { if (to.params.libraryId !== from.params.libraryId) {
this.unsetWatches() this.unsetWatches()
this.library = this.getLibraryLazy(Number(to.params.libraryId)) // reset
if (to.params.index) {
this.series = Array(Number(to.params.index)).fill(null)
} else { // else fill one page of skeletons
this.series = Array(this.pageSize).fill(null)
}
this.sortActive = this.parseQuerySortOrDefault(to.query.sort) this.sortActive = this.parseQuerySortOrDefault(to.query.sort)
this.filterStatus = this.parseQueryFilterStatus(to.query.status) this.page = 1
this.reloadData(Number(to.params.libraryId), this.series.length) this.totalPages = 1
this.totalElements = null
this.series = []
this.loadLibrary(Number(to.params.libraryId))
this.setWatches() this.setWatches()
} }
@ -208,34 +220,54 @@ export default Vue.extend({
isAdmin (): boolean { isAdmin (): boolean {
return this.$store.getters.meAdmin return this.$store.getters.meAdmin
}, },
}, paginationVisible (): number {
methods: { switch (this.$vuetify.breakpoint.name) {
async updateVisible (val: []) { case 'xs':
for (const i of val) { return 5
const pageNumber = Math.floor(i / this.pageSize) case 'sm':
if (this.pagesState[pageNumber] === undefined || this.pagesState[pageNumber] === LoadState.NotLoaded) { case 'md':
this.processPage(await this.loadPage(pageNumber, this.libraryId)) return 10
} case 'lg':
} case 'xl':
default:
const max = this.$_.max(val) as number | undefined return 15
const index = (max === undefined ? 0 : max).toString()
if (this.$route.params.index !== index) {
this.updateRoute(index)
} }
}, },
},
methods: {
setWatches () { setWatches () {
this.sortUnwatch = this.$watch('sortActive', this.updateRouteAndReload) this.sortUnwatch = this.$watch('sortActive', this.updateRouteAndReload)
this.filterUnwatch = this.$watch('filterStatus', this.updateRouteAndReload) this.filterUnwatch = this.$watch('filterStatus', this.updateRouteAndReload)
this.pageSizeUnwatch = this.$watch('pageSize', (val) => {
this.$cookies.set(cookiePageSize, val, Infinity)
this.updateRouteAndReload()
})
this.pageUnwatch = this.$watch('page', (val) => {
this.updateRoute()
this.loadPage(this.libraryId, val, this.sortActive)
})
}, },
unsetWatches () { unsetWatches () {
this.sortUnwatch() this.sortUnwatch()
this.filterUnwatch() this.filterUnwatch()
this.pageUnwatch()
this.pageSizeUnwatch()
}, },
updateRouteAndReload () { updateRouteAndReload () {
this.unsetWatches()
this.selected = []
this.page = 1
this.updateRoute() this.updateRoute()
this.reloadData(this.libraryId) this.loadPage(this.libraryId, this.page, this.sortActive)
this.setWatches()
},
async loadLibrary (libraryId: number) {
this.library = this.getLibraryLazy(libraryId)
await this.loadPage(libraryId, this.page, this.sortActive)
}, },
parseQuerySortOrDefault (querySort: any): SortActive { parseQuerySortOrDefault (querySort: any): SortActive {
return parseQuerySort(querySort, this.sortOptions) || this.$_.clone(this.sortDefault) return parseQuerySort(querySort, this.sortOptions) || this.$_.clone(this.sortDefault)
@ -243,52 +275,38 @@ export default Vue.extend({
parseQueryFilterStatus (queryStatus: any): string[] { parseQueryFilterStatus (queryStatus: any): string[] {
return queryStatus ? queryStatus.toString().split(',').filter((x: string) => Object.keys(SeriesStatus).includes(x)) : [] return queryStatus ? queryStatus.toString().split(',').filter((x: string) => Object.keys(SeriesStatus).includes(x)) : []
}, },
reloadData (libraryId: number, countItem?: number) { updateRoute () {
this.totalElements = null
this.pagesState = []
this.series = Array(countItem || this.pageSize).fill(null)
this.loadInitialData(libraryId)
},
updateRoute (index?: string) {
this.$router.replace({ this.$router.replace({
name: this.$route.name, name: this.$route.name,
params: { libraryId: this.$route.params.libraryId, index: index || this.$route.params.index }, params: { libraryId: this.$route.params.libraryId },
query: { query: {
page: `${this.page}`,
pageSize: `${this.pageSize}`,
sort: `${this.sortActive.key},${this.sortActive.order}`, sort: `${this.sortActive.key},${this.sortActive.order}`,
status: `${this.filterStatus}`, status: `${this.filterStatus}`,
}, },
}).catch(_ => { }).catch(_ => {
}) })
}, },
async loadInitialData (libraryId: number, pageToLoad: number = 0) { async loadPage (libraryId: number, page: number, sort: SortActive) {
this.processPage(await this.loadPage(pageToLoad, libraryId))
},
async loadPage (page: number, libraryId: number): Promise<Page<SeriesDto>> {
this.pagesState[page] = LoadState.Loading
const pageRequest = { const pageRequest = {
page: page, page: page - 1,
size: this.pageSize, size: this.pageSize,
} as PageRequest } as PageRequest
if (this.sortActive != null) { if (sort) {
pageRequest.sort = [`${this.sortActive.key},${this.sortActive.order}`] pageRequest.sort = [`${sort.key},${sort.order}`]
} }
let requestLibraryId let requestLibraryId
if (libraryId !== 0) { if (libraryId !== 0) {
requestLibraryId = libraryId requestLibraryId = libraryId
} }
return this.$komgaSeries.getSeries(requestLibraryId, pageRequest, undefined, this.filterStatus) const seriesPage = await this.$komgaSeries.getSeries(requestLibraryId, pageRequest, undefined, this.filterStatus)
},
processPage (page: Page<SeriesDto>) { this.totalPages = seriesPage.totalPages
if (this.totalElements === null) { this.totalElements = seriesPage.totalElements
// initialize page data this.series = seriesPage.content
this.totalElements = page.totalElements
this.series = Array(this.totalElements).fill(null)
this.pagesState = Array(page.totalPages).fill(LoadState.NotLoaded)
}
this.series.splice(page.number * page.size, page.size, ...page.content)
this.pagesState[page.number] = LoadState.Loaded
}, },
getLibraryLazy (libraryId: any): LibraryDto | undefined { getLibraryLazy (libraryId: any): LibraryDto | undefined {
if (libraryId !== 0) { if (libraryId !== 0) {

View file

@ -42,6 +42,9 @@
:sort-options="sortOptions" :sort-options="sortOptions"
:sort-active.sync="sortActive" :sort-active.sync="sortActive"
/> />
<page-size-select v-model="pageSize"/>
</toolbar-sticky> </toolbar-sticky>
<v-scroll-y-transition hide-on-leave> <v-scroll-y-transition hide-on-leave>
@ -104,11 +107,18 @@
</v-row> </v-row>
<v-divider class="my-4"/> <v-divider class="my-4"/>
<item-browser :items="books" :selected.sync="selected" :edit-function="this.singleEdit" class="px-6"
@update="updateVisible"></item-browser> <v-pagination
v-model="page"
:total-visible="paginationVisible"
:length="totalPages"
/>
<item-browser :items="books" :selected.sync="selected" :edit-function="this.singleEdit" class="px-6"/>
</v-container> </v-container>
<edit-series-dialog v-model="dialogEdit"
:series.sync="series"/> <edit-series-dialog v-model="dialogEdit" :series.sync="series"/>
</div> </div>
</template> </template>
@ -117,24 +127,27 @@ import Badge from '@/components/Badge.vue'
import EditBooksDialog from '@/components/EditBooksDialog.vue' import EditBooksDialog from '@/components/EditBooksDialog.vue'
import EditSeriesDialog from '@/components/EditSeriesDialog.vue' import EditSeriesDialog from '@/components/EditSeriesDialog.vue'
import ItemBrowser from '@/components/ItemBrowser.vue' import ItemBrowser from '@/components/ItemBrowser.vue'
import PageSizeSelect from '@/components/PageSizeSelect.vue'
import SortMenuButton from '@/components/SortMenuButton.vue' import SortMenuButton from '@/components/SortMenuButton.vue'
import ToolbarSticky from '@/components/ToolbarSticky.vue' import ToolbarSticky from '@/components/ToolbarSticky.vue'
import { parseQuerySort } from '@/functions/query-params' import { parseQuerySort } from '@/functions/query-params'
import { seriesThumbnailUrl } from '@/functions/urls' import { seriesThumbnailUrl } from '@/functions/urls'
import { LoadState } from '@/types/common'
import Vue from 'vue' import Vue from 'vue'
const cookiePageSize = 'pagesize'
export default Vue.extend({ export default Vue.extend({
name: 'BrowseSeries', name: 'BrowseSeries',
components: { ToolbarSticky, SortMenuButton, Badge, EditSeriesDialog, EditBooksDialog, ItemBrowser }, components: { ToolbarSticky, SortMenuButton, Badge, EditSeriesDialog, EditBooksDialog, ItemBrowser, PageSizeSelect },
data: () => { data: () => {
return { return {
series: {} as SeriesDto, series: {} as SeriesDto,
books: [] as BookDto[], books: [] as BookDto[],
selectedBooks: [] as BookDto[], selectedBooks: [] as BookDto[],
editBookSingle: {} as BookDto, editBookSingle: {} as BookDto,
pagesState: [] as LoadState[], page: 1,
pageSize: 20, pageSize: 20,
totalPages: 1,
totalElements: null as number | null, totalElements: null as number | null,
sortOptions: [{ name: 'Number', key: 'metadata.numberSort' }, { name: 'Date added', key: 'createdDate' }, { sortOptions: [{ name: 'Number', key: 'metadata.numberSort' }, { name: 'Date added', key: 'createdDate' }, {
name: 'File size', name: 'File size',
@ -144,6 +157,8 @@ export default Vue.extend({
sortDefault: { key: 'metadata.numberSort', order: 'asc' } as SortActive, sortDefault: { key: 'metadata.numberSort', order: 'asc' } as SortActive,
dialogEdit: false, dialogEdit: false,
sortUnwatch: null as any, sortUnwatch: null as any,
pageUnwatch: null as any,
pageSizeUnwatch: null as any,
selected: [], selected: [],
dialogEditBooks: false, dialogEditBooks: false,
dialogEditBookSingle: false, dialogEditBookSingle: false,
@ -153,12 +168,22 @@ export default Vue.extend({
isAdmin (): boolean { isAdmin (): boolean {
return this.$store.getters.meAdmin return this.$store.getters.meAdmin
}, },
sortCustom (): boolean {
return this.sortActive.key !== this.sortDefault.key || this.sortActive.order !== this.sortDefault.order
},
thumbnailUrl (): string { thumbnailUrl (): string {
return seriesThumbnailUrl(this.seriesId) return seriesThumbnailUrl(this.seriesId)
}, },
paginationVisible (): number {
switch (this.$vuetify.breakpoint.name) {
case 'xs':
return 5
case 'sm':
case 'md':
return 10
case 'lg':
case 'xl':
default:
return 15
}
},
}, },
props: { props: {
seriesId: { seriesId: {
@ -191,39 +216,32 @@ export default Vue.extend({
} }
}, },
}, },
async created () {
this.loadSeries()
},
mounted () { mounted () {
// fill books skeletons if an index is provided, so scroll position can be restored if (this.$cookies.isKey(cookiePageSize)) {
if (this.$route.params.index) { this.pageSize = Number(this.$cookies.get(cookiePageSize))
this.books = Array(Number(this.$route.params.index)).fill(null)
} else { // else fill one page of skeletons
this.books = Array(this.pageSize).fill(null)
} }
// restore sort from query param // restore from query param
this.sortActive = this.parseQuerySortOrDefault(this.$route.query.sort) this.sortActive = this.parseQuerySortOrDefault(this.$route.query.sort)
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.reloadData(Number(this.$route.params.seriesId), this.books.length) this.loadSeries(this.seriesId)
this.setWatches() this.setWatches()
this.loadSeries()
}, },
async beforeRouteUpdate (to, from, next) { async beforeRouteUpdate (to, from, next) {
if (to.params.seriesId !== from.params.seriesId) { if (to.params.seriesId !== from.params.seriesId) {
this.unsetWatches() this.unsetWatches()
this.series = await this.$komgaSeries.getOneSeries(Number(to.params.seriesId)) // reset
if (to.params.index) {
this.books = Array(Number(to.params.index)).fill(null)
} else { // else fill one page of skeletons
this.books = Array(this.pageSize).fill(null)
}
this.sortActive = this.parseQuerySortOrDefault(to.query.sort) this.sortActive = this.parseQuerySortOrDefault(to.query.sort)
this.reloadData(Number(to.params.seriesId), this.books.length) this.page = 1
this.totalPages = 1
this.totalElements = null
this.books = []
this.loadSeries(Number(to.params.seriesId))
this.setWatches() this.setWatches()
} }
@ -231,33 +249,37 @@ export default Vue.extend({
next() next()
}, },
methods: { methods: {
async updateVisible (val: []) {
for (const i of val) {
const pageNumber = Math.floor(i / this.pageSize)
if (this.pagesState[pageNumber] === undefined || this.pagesState[pageNumber] === LoadState.NotLoaded) {
this.processPage(await this.loadPage(pageNumber, this.seriesId))
}
}
const max = this.$_.max(val) as number | undefined
const index = (max === undefined ? 0 : max).toString()
if (this.$route.params.index !== index) {
this.updateRoute(index)
}
},
setWatches () { setWatches () {
this.sortUnwatch = this.$watch('sortActive', this.updateRouteAndReload) this.sortUnwatch = this.$watch('sortActive', this.updateRouteAndReload)
this.pageSizeUnwatch = this.$watch('pageSize', (val) => {
this.$cookies.set(cookiePageSize, val, Infinity)
this.updateRouteAndReload()
})
this.pageUnwatch = this.$watch('page', (val) => {
this.updateRoute()
this.loadPage(this.seriesId, val, this.sortActive)
})
}, },
unsetWatches () { unsetWatches () {
this.sortUnwatch() this.sortUnwatch()
this.pageUnwatch()
this.pageSizeUnwatch()
}, },
updateRouteAndReload () { updateRouteAndReload () {
this.unsetWatches()
this.selected = []
this.page = 1
this.updateRoute() this.updateRoute()
this.reloadData(this.seriesId) this.loadPage(this.seriesId, this.page, this.sortActive)
this.setWatches()
}, },
async loadSeries () { async loadSeries (seriesId: number) {
this.series = await this.$komgaSeries.getOneSeries(this.seriesId) this.series = await this.$komgaSeries.getOneSeries(seriesId)
await this.loadPage(seriesId, this.page, this.sortActive)
}, },
parseQuerySortOrDefault (querySort: any): SortActive { parseQuerySortOrDefault (querySort: any): SortActive {
return parseQuerySort(querySort, this.sortOptions) || this.$_.clone(this.sortDefault) return parseQuerySort(querySort, this.sortOptions) || this.$_.clone(this.sortDefault)
@ -265,43 +287,29 @@ export default Vue.extend({
updateRoute (index?: string) { updateRoute (index?: string) {
this.$router.replace({ this.$router.replace({
name: this.$route.name, name: this.$route.name,
params: { seriesId: this.$route.params.seriesId, index: index || this.$route.params.index }, params: { seriesId: this.$route.params.seriesId },
query: { query: {
page: `${this.page}`,
pageSize: `${this.pageSize}`,
sort: `${this.sortActive.key},${this.sortActive.order}`, sort: `${this.sortActive.key},${this.sortActive.order}`,
}, },
}).catch(_ => { }).catch(_ => {
}) })
}, },
reloadData (seriesId: number, countItem?: number) { async loadPage (seriesId: number, page: number, sort: SortActive) {
this.totalElements = null
this.pagesState = []
this.books = Array(countItem || this.pageSize).fill(null)
this.loadInitialData(seriesId)
},
async loadInitialData (seriesId: number, pageToLoad: number = 0) {
this.processPage(await this.loadPage(pageToLoad, seriesId))
},
async loadPage (page: number, seriesId: number): Promise<Page<BookDto>> {
this.pagesState[page] = LoadState.Loading
const pageRequest = { const pageRequest = {
page: page, page: page - 1,
size: this.pageSize, size: this.pageSize,
} as PageRequest } as PageRequest
if (this.sortActive != null) { if (sort) {
pageRequest.sort = [`${this.sortActive.key},${this.sortActive.order}`] pageRequest.sort = [`${sort.key},${sort.order}`]
} }
return this.$komgaSeries.getBooks(seriesId, pageRequest) const booksPage = await this.$komgaSeries.getBooks(seriesId, pageRequest)
},
processPage (page: Page<BookDto>) { this.totalPages = booksPage.totalPages
if (this.totalElements === null) { this.totalElements = booksPage.totalElements
// initialize page data this.books = booksPage.content
this.totalElements = page.totalElements
this.books = Array(this.totalElements).fill(null)
this.pagesState = Array(page.totalPages).fill(LoadState.NotLoaded)
}
this.books.splice(page.number * page.size, page.size, ...page.content)
this.pagesState[page.number] = LoadState.Loaded
}, },
analyze () { analyze () {
this.$komgaSeries.analyzeSeries(this.series) this.$komgaSeries.analyzeSeries(this.series)

View file

@ -75,11 +75,11 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import HorizontalScroller from '@/components/HorizontalScroller.vue'
import Vue from 'vue'
import EditSeriesDialog from '@/components/EditSeriesDialog.vue'
import EditBooksDialog from '@/components/EditBooksDialog.vue' import EditBooksDialog from '@/components/EditBooksDialog.vue'
import EditSeriesDialog from '@/components/EditSeriesDialog.vue'
import HorizontalScroller from '@/components/HorizontalScroller.vue'
import ItemCard from '@/components/ItemCard.vue' import ItemCard from '@/components/ItemCard.vue'
import Vue from 'vue'
export default Vue.extend({ export default Vue.extend({
name: 'Dashboard', name: 'Dashboard',
@ -98,13 +98,9 @@ export default Vue.extend({
} }
}, },
mounted () { mounted () {
if (this.$store.state.komgaLibraries.libraries.length === 0) { this.loadNewSeries()
this.$router.push({ name: 'welcome' }) this.loadUpdatedSeries()
} else { this.loadLatestBooks()
this.loadNewSeries()
this.loadUpdatedSeries()
this.loadLatestBooks()
}
}, },
watch: { watch: {
editSeriesSingle (val: SeriesDto) { editSeriesSingle (val: SeriesDto) {