mirror of
https://github.com/gotson/komga.git
synced 2025-12-21 16:03:03 +01:00
parent
75b72164fe
commit
5867db77f5
8 changed files with 286 additions and 241 deletions
|
|
@ -1,31 +1,24 @@
|
|||
<template>
|
||||
<v-item-group multiple v-model="selectedItems">
|
||||
<v-row justify="start" ref="content" v-resize="onResize" v-if="hasItems">
|
||||
<v-skeleton-loader v-for="(item, index) in items"
|
||||
:key="index"
|
||||
:width="itemWidth"
|
||||
:height="itemHeight"
|
||||
justify-self="start"
|
||||
:loading="item === null"
|
||||
type="card, text"
|
||||
class="ma-3 mx-2"
|
||||
:data-index="index"
|
||||
v-intersect="onElementIntersect"
|
||||
|
||||
<v-item
|
||||
v-for="(item, index) in items"
|
||||
:key="index"
|
||||
class="my-3 mx-2"
|
||||
v-slot:default="{ toggle, active }" :value="$_.get(item, 'id', 0)"
|
||||
>
|
||||
<v-item v-slot:default="{ toggle, active }" :value="$_.get(item, 'id', 0)">
|
||||
<slot name="item" v-bind:data="{ toggle, active, item, index, itemWidth, preselect: shouldPreselect(), editItem }">
|
||||
<item-card
|
||||
:item="item"
|
||||
:width="itemWidth"
|
||||
:selected="active"
|
||||
:preselect="shouldPreselect()"
|
||||
:onEdit="editItem"
|
||||
:onSelected="toggle"
|
||||
></item-card>
|
||||
</slot>
|
||||
</v-item>
|
||||
</v-skeleton-loader>
|
||||
<slot name="item"
|
||||
v-bind:data="{ toggle, active, item, index, itemWidth, preselect: shouldPreselect(), editItem }">
|
||||
<item-card
|
||||
:item="item"
|
||||
:width="itemWidth"
|
||||
:selected="active"
|
||||
:preselect="shouldPreselect()"
|
||||
:onEdit="editItem"
|
||||
:onSelected="toggle"
|
||||
></item-card>
|
||||
</slot>
|
||||
</v-item>
|
||||
</v-row>
|
||||
<v-row v-else justify="center">
|
||||
<slot name="empty"></slot>
|
||||
|
|
@ -34,13 +27,11 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue'
|
||||
import { computeCardWidth } from '@/functions/grid-utilities'
|
||||
import ItemCard from '@/components/ItemCard.vue'
|
||||
import mixins from 'vue-typed-mixins'
|
||||
import VisibleElements from '@/mixins/VisibleElements'
|
||||
import { computeCardWidth } from '@/functions/grid-utilities'
|
||||
import Vue from 'vue'
|
||||
|
||||
export default mixins(VisibleElements).extend({
|
||||
export default Vue.extend({
|
||||
name: 'ItemBrowser',
|
||||
components: { ItemCard },
|
||||
props: {
|
||||
|
|
@ -66,12 +57,6 @@ export default mixins(VisibleElements).extend({
|
|||
}
|
||||
},
|
||||
watch: {
|
||||
series: {
|
||||
handler () {
|
||||
this.visibleElements = []
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
selectedItems: {
|
||||
handler () {
|
||||
this.$emit('update:selected', this.selectedItems)
|
||||
|
|
|
|||
57
komga-webui/src/components/PageSizeSelect.vue
Normal file
57
komga-webui/src/components/PageSizeSelect.vue
Normal 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>
|
||||
|
|
@ -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
|
||||
|
|
@ -5,13 +5,19 @@ import store from './store'
|
|||
|
||||
Vue.use(Router)
|
||||
|
||||
const lStore = store
|
||||
const lStore = store as any
|
||||
|
||||
const adminGuard = (to: any, from: any, next: any) => {
|
||||
if (!lStore.getters.meAdmin) next({ name: 'home' })
|
||||
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({
|
||||
mode: 'history',
|
||||
base: urls.base,
|
||||
|
|
@ -36,6 +42,7 @@ const router = new Router({
|
|||
{
|
||||
path: '/dashboard',
|
||||
name: 'dashboard',
|
||||
beforeEnter: noLibraryGuard,
|
||||
component: () => import(/* webpackChunkName: "dashboard" */ './views/Dashboard.vue'),
|
||||
},
|
||||
{
|
||||
|
|
@ -71,13 +78,14 @@ const router = new Router({
|
|||
component: () => import(/* webpackChunkName: "account" */ './views/AccountSettings.vue'),
|
||||
},
|
||||
{
|
||||
path: '/libraries/:libraryId/:index?',
|
||||
path: '/libraries/:libraryId',
|
||||
name: 'browse-libraries',
|
||||
beforeEnter: noLibraryGuard,
|
||||
component: () => import(/* webpackChunkName: "browse-libraries" */ './views/BrowseLibraries.vue'),
|
||||
props: (route) => ({ libraryId: Number(route.params.libraryId) }),
|
||||
},
|
||||
{
|
||||
path: '/series/:seriesId/:index?',
|
||||
path: '/series/:seriesId',
|
||||
name: 'browse-series',
|
||||
component: () => import(/* webpackChunkName: "browse-series" */ './views/BrowseSeries.vue'),
|
||||
props: (route) => ({ seriesId: Number(route.params.seriesId) }),
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
export enum LoadState {
|
||||
Loaded,
|
||||
NotLoaded,
|
||||
Loading
|
||||
}
|
||||
|
|
@ -42,6 +42,9 @@
|
|||
:sort-options="sortOptions"
|
||||
:sort-active.sync="sortActive"
|
||||
/>
|
||||
|
||||
<page-size-select v-model="pageSize"/>
|
||||
|
||||
</toolbar-sticky>
|
||||
|
||||
<v-scroll-y-transition hide-on-leave>
|
||||
|
|
@ -69,57 +72,72 @@
|
|||
:series.sync="editSeriesSingle"
|
||||
/>
|
||||
|
||||
<item-browser :items="series"
|
||||
:selected.sync="selected"
|
||||
:edit-function="this.singleEdit"
|
||||
class="px-6"
|
||||
@update="updateVisible"
|
||||
>
|
||||
<template #empty v-if="filterStatus.length > 0">
|
||||
<empty-state title="The active filter has no matches"
|
||||
sub-title="Use the menu above to change the active filter"
|
||||
icon="mdi-book-multiple"
|
||||
icon-color="secondary"
|
||||
>
|
||||
<v-btn @click="filterStatus = []">Clear filter</v-btn>
|
||||
</empty-state>
|
||||
<v-container>
|
||||
<empty-state
|
||||
v-if="totalPages === 0"
|
||||
title="The active filter has no matches"
|
||||
sub-title="Use the menu above to change the active filter"
|
||||
icon="mdi-book-multiple"
|
||||
icon-color="secondary"
|
||||
>
|
||||
<v-btn @click="filterStatus = []">Clear filter</v-btn>
|
||||
</empty-state>
|
||||
|
||||
<template v-else>
|
||||
<v-pagination
|
||||
v-model="page"
|
||||
:total-visible="paginationVisible"
|
||||
:length="totalPages"
|
||||
/>
|
||||
|
||||
<item-browser
|
||||
:items="series"
|
||||
:selected.sync="selected"
|
||||
:edit-function="this.singleEdit"
|
||||
class="px-6"
|
||||
/>
|
||||
</template>
|
||||
<template #empty v-else>
|
||||
<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>
|
||||
</v-container>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Badge from '@/components/Badge.vue'
|
||||
import EmptyState from '@/components/EmptyState.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 PageSizeSelect from '@/components/PageSizeSelect.vue'
|
||||
import SortMenuButton from '@/components/SortMenuButton.vue'
|
||||
import ToolbarSticky from '@/components/ToolbarSticky.vue'
|
||||
import { parseQuerySort } from '@/functions/query-params'
|
||||
import { LoadState } from '@/types/common'
|
||||
import { SeriesStatus } from '@/types/enum-series'
|
||||
import ItemBrowser from '@/components/ItemBrowser.vue'
|
||||
import Vue from 'vue'
|
||||
|
||||
const cookiePageSize = 'pagesize'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'BrowseLibraries',
|
||||
components: { LibraryActionsMenu, EmptyState, ToolbarSticky, SortMenuButton, Badge, EditSeriesDialog, ItemBrowser },
|
||||
components: {
|
||||
LibraryActionsMenu,
|
||||
EmptyState,
|
||||
ToolbarSticky,
|
||||
SortMenuButton,
|
||||
Badge,
|
||||
EditSeriesDialog,
|
||||
ItemBrowser,
|
||||
PageSizeSelect,
|
||||
},
|
||||
data: () => {
|
||||
return {
|
||||
library: undefined as LibraryDto | undefined,
|
||||
series: [] as SeriesDto[],
|
||||
selectedSeries: [] as SeriesDto[],
|
||||
editSeriesSingle: {} as SeriesDto,
|
||||
pagesState: [] as LoadState[],
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
totalPages: 1,
|
||||
totalElements: null as number | null,
|
||||
sortOptions: [
|
||||
{ name: 'Name', key: 'metadata.titleSort' },
|
||||
|
|
@ -132,6 +150,8 @@ export default Vue.extend({
|
|||
SeriesStatus,
|
||||
sortUnwatch: null as any,
|
||||
filterUnwatch: null as any,
|
||||
pageUnwatch: null as any,
|
||||
pageSizeUnwatch: null as any,
|
||||
selected: [],
|
||||
dialogEdit: false,
|
||||
dialogEditSingle: false,
|
||||
|
|
@ -163,24 +183,18 @@ export default Vue.extend({
|
|||
}
|
||||
},
|
||||
},
|
||||
async created () {
|
||||
this.library = await this.getLibraryLazy(this.libraryId)
|
||||
},
|
||||
mounted () {
|
||||
// fill series skeletons if an index is provided, so scroll position can be restored
|
||||
if (this.$route.params.index) {
|
||||
this.series = Array(Number(this.$route.params.index)).fill(null)
|
||||
} else { // else fill one page of skeletons
|
||||
this.series = Array(this.pageSize).fill(null)
|
||||
if (this.$cookies.isKey(cookiePageSize)) {
|
||||
this.pageSize = Number(this.$cookies.get(cookiePageSize))
|
||||
}
|
||||
|
||||
// restore sort from query param
|
||||
// restore from query param
|
||||
this.sortActive = this.parseQuerySortOrDefault(this.$route.query.sort)
|
||||
|
||||
// restore filter status from query params
|
||||
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()
|
||||
},
|
||||
|
|
@ -188,16 +202,14 @@ export default Vue.extend({
|
|||
if (to.params.libraryId !== from.params.libraryId) {
|
||||
this.unsetWatches()
|
||||
|
||||
this.library = this.getLibraryLazy(Number(to.params.libraryId))
|
||||
|
||||
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)
|
||||
}
|
||||
// reset
|
||||
this.sortActive = this.parseQuerySortOrDefault(to.query.sort)
|
||||
this.filterStatus = this.parseQueryFilterStatus(to.query.status)
|
||||
this.reloadData(Number(to.params.libraryId), this.series.length)
|
||||
this.page = 1
|
||||
this.totalPages = 1
|
||||
this.totalElements = null
|
||||
this.series = []
|
||||
|
||||
this.loadLibrary(Number(to.params.libraryId))
|
||||
|
||||
this.setWatches()
|
||||
}
|
||||
|
|
@ -208,34 +220,54 @@ export default Vue.extend({
|
|||
isAdmin (): boolean {
|
||||
return this.$store.getters.meAdmin
|
||||
},
|
||||
},
|
||||
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.libraryId))
|
||||
}
|
||||
}
|
||||
|
||||
const max = this.$_.max(val) as number | undefined
|
||||
const index = (max === undefined ? 0 : max).toString()
|
||||
|
||||
if (this.$route.params.index !== index) {
|
||||
this.updateRoute(index)
|
||||
paginationVisible (): number {
|
||||
switch (this.$vuetify.breakpoint.name) {
|
||||
case 'xs':
|
||||
return 5
|
||||
case 'sm':
|
||||
case 'md':
|
||||
return 10
|
||||
case 'lg':
|
||||
case 'xl':
|
||||
default:
|
||||
return 15
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
setWatches () {
|
||||
this.sortUnwatch = this.$watch('sortActive', 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 () {
|
||||
this.sortUnwatch()
|
||||
this.filterUnwatch()
|
||||
this.pageUnwatch()
|
||||
this.pageSizeUnwatch()
|
||||
},
|
||||
updateRouteAndReload () {
|
||||
this.unsetWatches()
|
||||
|
||||
this.selected = []
|
||||
this.page = 1
|
||||
|
||||
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 {
|
||||
return parseQuerySort(querySort, this.sortOptions) || this.$_.clone(this.sortDefault)
|
||||
|
|
@ -243,52 +275,38 @@ export default Vue.extend({
|
|||
parseQueryFilterStatus (queryStatus: any): string[] {
|
||||
return queryStatus ? queryStatus.toString().split(',').filter((x: string) => Object.keys(SeriesStatus).includes(x)) : []
|
||||
},
|
||||
reloadData (libraryId: number, countItem?: number) {
|
||||
this.totalElements = null
|
||||
this.pagesState = []
|
||||
this.series = Array(countItem || this.pageSize).fill(null)
|
||||
this.loadInitialData(libraryId)
|
||||
},
|
||||
updateRoute (index?: string) {
|
||||
updateRoute () {
|
||||
this.$router.replace({
|
||||
name: this.$route.name,
|
||||
params: { libraryId: this.$route.params.libraryId, index: index || this.$route.params.index },
|
||||
params: { libraryId: this.$route.params.libraryId },
|
||||
query: {
|
||||
page: `${this.page}`,
|
||||
pageSize: `${this.pageSize}`,
|
||||
sort: `${this.sortActive.key},${this.sortActive.order}`,
|
||||
status: `${this.filterStatus}`,
|
||||
},
|
||||
}).catch(_ => {
|
||||
})
|
||||
},
|
||||
async loadInitialData (libraryId: number, pageToLoad: number = 0) {
|
||||
this.processPage(await this.loadPage(pageToLoad, libraryId))
|
||||
},
|
||||
async loadPage (page: number, libraryId: number): Promise<Page<SeriesDto>> {
|
||||
this.pagesState[page] = LoadState.Loading
|
||||
async loadPage (libraryId: number, page: number, sort: SortActive) {
|
||||
const pageRequest = {
|
||||
page: page,
|
||||
page: page - 1,
|
||||
size: this.pageSize,
|
||||
} as PageRequest
|
||||
|
||||
if (this.sortActive != null) {
|
||||
pageRequest.sort = [`${this.sortActive.key},${this.sortActive.order}`]
|
||||
if (sort) {
|
||||
pageRequest.sort = [`${sort.key},${sort.order}`]
|
||||
}
|
||||
|
||||
let requestLibraryId
|
||||
if (libraryId !== 0) {
|
||||
requestLibraryId = libraryId
|
||||
}
|
||||
return this.$komgaSeries.getSeries(requestLibraryId, pageRequest, undefined, this.filterStatus)
|
||||
},
|
||||
processPage (page: Page<SeriesDto>) {
|
||||
if (this.totalElements === null) {
|
||||
// initialize page data
|
||||
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
|
||||
const seriesPage = await this.$komgaSeries.getSeries(requestLibraryId, pageRequest, undefined, this.filterStatus)
|
||||
|
||||
this.totalPages = seriesPage.totalPages
|
||||
this.totalElements = seriesPage.totalElements
|
||||
this.series = seriesPage.content
|
||||
},
|
||||
getLibraryLazy (libraryId: any): LibraryDto | undefined {
|
||||
if (libraryId !== 0) {
|
||||
|
|
|
|||
|
|
@ -42,6 +42,9 @@
|
|||
:sort-options="sortOptions"
|
||||
:sort-active.sync="sortActive"
|
||||
/>
|
||||
|
||||
<page-size-select v-model="pageSize"/>
|
||||
|
||||
</toolbar-sticky>
|
||||
|
||||
<v-scroll-y-transition hide-on-leave>
|
||||
|
|
@ -104,11 +107,18 @@
|
|||
</v-row>
|
||||
|
||||
<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>
|
||||
<edit-series-dialog v-model="dialogEdit"
|
||||
:series.sync="series"/>
|
||||
|
||||
<edit-series-dialog v-model="dialogEdit" :series.sync="series"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -117,24 +127,27 @@ import Badge from '@/components/Badge.vue'
|
|||
import EditBooksDialog from '@/components/EditBooksDialog.vue'
|
||||
import EditSeriesDialog from '@/components/EditSeriesDialog.vue'
|
||||
import ItemBrowser from '@/components/ItemBrowser.vue'
|
||||
import PageSizeSelect from '@/components/PageSizeSelect.vue'
|
||||
import SortMenuButton from '@/components/SortMenuButton.vue'
|
||||
import ToolbarSticky from '@/components/ToolbarSticky.vue'
|
||||
import { parseQuerySort } from '@/functions/query-params'
|
||||
import { seriesThumbnailUrl } from '@/functions/urls'
|
||||
import { LoadState } from '@/types/common'
|
||||
import Vue from 'vue'
|
||||
|
||||
const cookiePageSize = 'pagesize'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'BrowseSeries',
|
||||
components: { ToolbarSticky, SortMenuButton, Badge, EditSeriesDialog, EditBooksDialog, ItemBrowser },
|
||||
components: { ToolbarSticky, SortMenuButton, Badge, EditSeriesDialog, EditBooksDialog, ItemBrowser, PageSizeSelect },
|
||||
data: () => {
|
||||
return {
|
||||
series: {} as SeriesDto,
|
||||
books: [] as BookDto[],
|
||||
selectedBooks: [] as BookDto[],
|
||||
editBookSingle: {} as BookDto,
|
||||
pagesState: [] as LoadState[],
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
totalPages: 1,
|
||||
totalElements: null as number | null,
|
||||
sortOptions: [{ name: 'Number', key: 'metadata.numberSort' }, { name: 'Date added', key: 'createdDate' }, {
|
||||
name: 'File size',
|
||||
|
|
@ -144,6 +157,8 @@ export default Vue.extend({
|
|||
sortDefault: { key: 'metadata.numberSort', order: 'asc' } as SortActive,
|
||||
dialogEdit: false,
|
||||
sortUnwatch: null as any,
|
||||
pageUnwatch: null as any,
|
||||
pageSizeUnwatch: null as any,
|
||||
selected: [],
|
||||
dialogEditBooks: false,
|
||||
dialogEditBookSingle: false,
|
||||
|
|
@ -153,12 +168,22 @@ export default Vue.extend({
|
|||
isAdmin (): boolean {
|
||||
return this.$store.getters.meAdmin
|
||||
},
|
||||
sortCustom (): boolean {
|
||||
return this.sortActive.key !== this.sortDefault.key || this.sortActive.order !== this.sortDefault.order
|
||||
},
|
||||
thumbnailUrl (): string {
|
||||
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: {
|
||||
seriesId: {
|
||||
|
|
@ -191,39 +216,32 @@ export default Vue.extend({
|
|||
}
|
||||
},
|
||||
},
|
||||
async created () {
|
||||
this.loadSeries()
|
||||
},
|
||||
mounted () {
|
||||
// fill books skeletons if an index is provided, so scroll position can be restored
|
||||
if (this.$route.params.index) {
|
||||
this.books = Array(Number(this.$route.params.index)).fill(null)
|
||||
} else { // else fill one page of skeletons
|
||||
this.books = Array(this.pageSize).fill(null)
|
||||
if (this.$cookies.isKey(cookiePageSize)) {
|
||||
this.pageSize = Number(this.$cookies.get(cookiePageSize))
|
||||
}
|
||||
|
||||
// restore sort from query param
|
||||
// restore from query param
|
||||
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.loadSeries()
|
||||
},
|
||||
async beforeRouteUpdate (to, from, next) {
|
||||
if (to.params.seriesId !== from.params.seriesId) {
|
||||
this.unsetWatches()
|
||||
|
||||
this.series = await this.$komgaSeries.getOneSeries(Number(to.params.seriesId))
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// reset
|
||||
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()
|
||||
}
|
||||
|
|
@ -231,33 +249,37 @@ export default Vue.extend({
|
|||
next()
|
||||
},
|
||||
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 () {
|
||||
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 () {
|
||||
this.sortUnwatch()
|
||||
this.pageUnwatch()
|
||||
this.pageSizeUnwatch()
|
||||
},
|
||||
updateRouteAndReload () {
|
||||
this.unsetWatches()
|
||||
|
||||
this.selected = []
|
||||
this.page = 1
|
||||
|
||||
this.updateRoute()
|
||||
this.reloadData(this.seriesId)
|
||||
this.loadPage(this.seriesId, this.page, this.sortActive)
|
||||
|
||||
this.setWatches()
|
||||
},
|
||||
async loadSeries () {
|
||||
this.series = await this.$komgaSeries.getOneSeries(this.seriesId)
|
||||
async loadSeries (seriesId: number) {
|
||||
this.series = await this.$komgaSeries.getOneSeries(seriesId)
|
||||
await this.loadPage(seriesId, this.page, this.sortActive)
|
||||
},
|
||||
parseQuerySortOrDefault (querySort: any): SortActive {
|
||||
return parseQuerySort(querySort, this.sortOptions) || this.$_.clone(this.sortDefault)
|
||||
|
|
@ -265,43 +287,29 @@ export default Vue.extend({
|
|||
updateRoute (index?: string) {
|
||||
this.$router.replace({
|
||||
name: this.$route.name,
|
||||
params: { seriesId: this.$route.params.seriesId, index: index || this.$route.params.index },
|
||||
params: { seriesId: this.$route.params.seriesId },
|
||||
query: {
|
||||
page: `${this.page}`,
|
||||
pageSize: `${this.pageSize}`,
|
||||
sort: `${this.sortActive.key},${this.sortActive.order}`,
|
||||
},
|
||||
}).catch(_ => {
|
||||
})
|
||||
},
|
||||
reloadData (seriesId: number, countItem?: number) {
|
||||
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
|
||||
async loadPage (seriesId: number, page: number, sort: SortActive) {
|
||||
const pageRequest = {
|
||||
page: page,
|
||||
page: page - 1,
|
||||
size: this.pageSize,
|
||||
} as PageRequest
|
||||
|
||||
if (this.sortActive != null) {
|
||||
pageRequest.sort = [`${this.sortActive.key},${this.sortActive.order}`]
|
||||
if (sort) {
|
||||
pageRequest.sort = [`${sort.key},${sort.order}`]
|
||||
}
|
||||
return this.$komgaSeries.getBooks(seriesId, pageRequest)
|
||||
},
|
||||
processPage (page: Page<BookDto>) {
|
||||
if (this.totalElements === null) {
|
||||
// initialize page data
|
||||
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
|
||||
const booksPage = await this.$komgaSeries.getBooks(seriesId, pageRequest)
|
||||
|
||||
this.totalPages = booksPage.totalPages
|
||||
this.totalElements = booksPage.totalElements
|
||||
this.books = booksPage.content
|
||||
},
|
||||
analyze () {
|
||||
this.$komgaSeries.analyzeSeries(this.series)
|
||||
|
|
|
|||
|
|
@ -75,11 +75,11 @@
|
|||
</template>
|
||||
|
||||
<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 EditSeriesDialog from '@/components/EditSeriesDialog.vue'
|
||||
import HorizontalScroller from '@/components/HorizontalScroller.vue'
|
||||
import ItemCard from '@/components/ItemCard.vue'
|
||||
import Vue from 'vue'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'Dashboard',
|
||||
|
|
@ -98,13 +98,9 @@ export default Vue.extend({
|
|||
}
|
||||
},
|
||||
mounted () {
|
||||
if (this.$store.state.komgaLibraries.libraries.length === 0) {
|
||||
this.$router.push({ name: 'welcome' })
|
||||
} else {
|
||||
this.loadNewSeries()
|
||||
this.loadUpdatedSeries()
|
||||
this.loadLatestBooks()
|
||||
}
|
||||
this.loadNewSeries()
|
||||
this.loadUpdatedSeries()
|
||||
this.loadLatestBooks()
|
||||
},
|
||||
watch: {
|
||||
editSeriesSingle (val: SeriesDto) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue