mirror of
https://github.com/gotson/komga.git
synced 2026-04-18 21:13:24 +02:00
feat(webui): filter series by status
when browsing libraries linked to #48
This commit is contained in:
parent
c96bf19048
commit
c540e56c08
3 changed files with 84 additions and 15 deletions
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { SeriesStatus } from '@/types/common'
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<v-toolbar flat
|
<v-toolbar flat
|
||||||
|
|
@ -12,7 +13,7 @@
|
||||||
<v-toolbar-title>
|
<v-toolbar-title>
|
||||||
<span>{{ library ? library.name : 'All libraries' }}</span>
|
<span>{{ library ? library.name : 'All libraries' }}</span>
|
||||||
<span class="ml-4 badge-count"
|
<span class="ml-4 badge-count"
|
||||||
v-if="totalElements"
|
v-if="totalElements !== null"
|
||||||
>
|
>
|
||||||
{{ totalElements }}
|
{{ totalElements }}
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -20,6 +21,33 @@
|
||||||
|
|
||||||
<v-spacer/>
|
<v-spacer/>
|
||||||
|
|
||||||
|
<!-- Filter menu -->
|
||||||
|
<v-menu offset-y
|
||||||
|
:close-on-content-click="false"
|
||||||
|
>
|
||||||
|
<template v-slot:activator="{on}">
|
||||||
|
<v-btn icon v-on="on">
|
||||||
|
<v-icon :color="$_.isEmpty(filterStatus) ? null : 'secondary'"
|
||||||
|
>mdi-filter-variant
|
||||||
|
</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
<v-list>
|
||||||
|
<v-subheader>STATUS</v-subheader>
|
||||||
|
<v-list-item v-for="s in SeriesStatus"
|
||||||
|
:key="s"
|
||||||
|
>
|
||||||
|
<v-list-item-title class="text-capitalize">
|
||||||
|
<v-checkbox v-model="filterStatus"
|
||||||
|
:label="s.toString().toLowerCase()"
|
||||||
|
color="secondary"
|
||||||
|
class="mt-1 ml-2"
|
||||||
|
:value="s"/>
|
||||||
|
</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-menu>
|
||||||
|
|
||||||
<!-- Sort menu -->
|
<!-- Sort menu -->
|
||||||
<v-menu offset-y>
|
<v-menu offset-y>
|
||||||
<template v-slot:activator="{on}">
|
<template v-slot:activator="{on}">
|
||||||
|
|
@ -49,7 +77,7 @@
|
||||||
</v-toolbar>
|
</v-toolbar>
|
||||||
|
|
||||||
<v-container fluid class="px-6">
|
<v-container fluid class="px-6">
|
||||||
<v-row justify="start" ref="content" v-resize="updateCardWidth">
|
<v-row justify="start" ref="content" v-resize="updateCardWidth" v-if="totalElements !== 0">
|
||||||
|
|
||||||
<v-skeleton-loader v-for="(s, i) in series"
|
<v-skeleton-loader v-for="(s, i) in series"
|
||||||
:key="i"
|
:key="i"
|
||||||
|
|
@ -66,6 +94,20 @@
|
||||||
</v-skeleton-loader>
|
</v-skeleton-loader>
|
||||||
|
|
||||||
</v-row>
|
</v-row>
|
||||||
|
|
||||||
|
<!-- Empty state if filter returns no books -->
|
||||||
|
<v-row justify="center" v-else>
|
||||||
|
<div class="text-center">
|
||||||
|
<v-avatar color="grey lighten-3" size="400">
|
||||||
|
<div>
|
||||||
|
<v-icon color="primary" size="140">mdi-book-multiple</v-icon>
|
||||||
|
<h1 class="headline">The active filter has no matches</h1>
|
||||||
|
<p class="body-1">Use the menu above to change the active filter</p>
|
||||||
|
<v-btn color="primary" @click="filterStatus = []">Clear filter</v-btn>
|
||||||
|
</div>
|
||||||
|
</v-avatar>
|
||||||
|
</div>
|
||||||
|
</v-row>
|
||||||
</v-container>
|
</v-container>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -73,7 +115,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import CardSeries from '@/components/CardSeries.vue'
|
import CardSeries from '@/components/CardSeries.vue'
|
||||||
import LibraryActionsMenu from '@/components/LibraryActionsMenu.vue'
|
import LibraryActionsMenu from '@/components/LibraryActionsMenu.vue'
|
||||||
import { LoadState } from '@/types/common'
|
import { LoadState, SeriesStatus } from '@/types/common'
|
||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
|
|
@ -93,6 +135,8 @@ export default Vue.extend({
|
||||||
}] as SortOption[],
|
}] as SortOption[],
|
||||||
sortActive: {} as SortActive as SortActive,
|
sortActive: {} as SortActive as SortActive,
|
||||||
sortDefault: { key: 'name', order: 'asc' } as SortActive as SortActive,
|
sortDefault: { key: 'name', order: 'asc' } as SortActive as SortActive,
|
||||||
|
filterStatus: [] as string[],
|
||||||
|
SeriesStatus,
|
||||||
cardWidth: 150
|
cardWidth: 150
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -114,6 +158,12 @@ export default Vue.extend({
|
||||||
default: 0
|
default: 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
filterStatus () {
|
||||||
|
this.updateRoute()
|
||||||
|
this.reloadData(this.libraryId)
|
||||||
|
}
|
||||||
|
},
|
||||||
async created () {
|
async created () {
|
||||||
this.library = await this.getLibraryLazy(this.libraryId)
|
this.library = await this.getLibraryLazy(this.libraryId)
|
||||||
},
|
},
|
||||||
|
|
@ -127,11 +177,15 @@ export default Vue.extend({
|
||||||
|
|
||||||
// restore sort from query param
|
// restore sort 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)
|
||||||
},
|
},
|
||||||
beforeRouteUpdate (to, from, next) {
|
beforeRouteUpdate (to, from, next) {
|
||||||
if (to.params.libraryId !== from.params.libraryId) {
|
if (to.params.libraryId !== from.params.libraryId) {
|
||||||
this.library = this.getLibraryLazy(Number(to.params.libraryId))
|
this.library = this.getLibraryLazy(Number(to.params.libraryId))
|
||||||
this.sortActive = this.parseQuerySortOrDefault(to.query.sort)
|
this.sortActive = this.parseQuerySortOrDefault(to.query.sort)
|
||||||
|
this.filterStatus = this.parseQueryFilterStatus(to.query.status)
|
||||||
this.reloadData(Number(to.params.libraryId))
|
this.reloadData(Number(to.params.libraryId))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -165,6 +219,9 @@ export default Vue.extend({
|
||||||
return this.$_.clone(this.sortDefault)
|
return this.$_.clone(this.sortDefault)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
parseQueryFilterStatus (queryStatus: any): string[] {
|
||||||
|
return queryStatus ? queryStatus.toString().split(',').filter((x: string) => Object.keys(SeriesStatus).includes(x)) : []
|
||||||
|
},
|
||||||
async onCardIntersect (entries: any, observer: any, isIntersecting: boolean) {
|
async onCardIntersect (entries: any, observer: any, isIntersecting: boolean) {
|
||||||
const elementIndex = Number(entries[0].target.dataset['index'])
|
const elementIndex = Number(entries[0].target.dataset['index'])
|
||||||
if (isIntersecting) {
|
if (isIntersecting) {
|
||||||
|
|
@ -181,11 +238,7 @@ export default Vue.extend({
|
||||||
const index = (max === undefined ? 0 : max).toString()
|
const index = (max === undefined ? 0 : max).toString()
|
||||||
|
|
||||||
if (this.$route.params.index !== index) {
|
if (this.$route.params.index !== index) {
|
||||||
this.$router.replace({
|
this.updateRoute(index)
|
||||||
name: this.$route.name,
|
|
||||||
params: { libraryId: this.$route.params.libraryId, index: index },
|
|
||||||
query: { sort: `${this.sortActive.key},${this.sortActive.order}` }
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
reloadData (libraryId: number) {
|
reloadData (libraryId: number) {
|
||||||
|
|
@ -195,6 +248,16 @@ export default Vue.extend({
|
||||||
this.series = Array(this.pageSize).fill(null)
|
this.series = Array(this.pageSize).fill(null)
|
||||||
this.loadInitialData(libraryId)
|
this.loadInitialData(libraryId)
|
||||||
},
|
},
|
||||||
|
updateRoute (index?: string) {
|
||||||
|
this.$router.replace({
|
||||||
|
name: this.$route.name,
|
||||||
|
params: { libraryId: this.$route.params.libraryId, index: index || this.$route.params.index },
|
||||||
|
query: {
|
||||||
|
sort: `${this.sortActive.key},${this.sortActive.order}`,
|
||||||
|
status: `${this.filterStatus}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
setSort (sort: SortOption) {
|
setSort (sort: SortOption) {
|
||||||
if (this.sortActive.key === sort.key) {
|
if (this.sortActive.key === sort.key) {
|
||||||
if (this.sortActive.order === 'desc') {
|
if (this.sortActive.order === 'desc') {
|
||||||
|
|
@ -205,11 +268,7 @@ export default Vue.extend({
|
||||||
} else {
|
} else {
|
||||||
this.sortActive = { key: sort.key, order: 'desc' }
|
this.sortActive = { key: sort.key, order: 'desc' }
|
||||||
}
|
}
|
||||||
this.$router.replace({
|
this.updateRoute()
|
||||||
name: this.$route.name,
|
|
||||||
params: { libraryId: this.$route.params.libraryId, index: this.$route.params.index },
|
|
||||||
query: { sort: `${this.sortActive.key},${this.sortActive.order}` }
|
|
||||||
})
|
|
||||||
this.reloadData(this.libraryId)
|
this.reloadData(this.libraryId)
|
||||||
},
|
},
|
||||||
async loadInitialData (libraryId: number, pageToLoad: number = 0) {
|
async loadInitialData (libraryId: number, pageToLoad: number = 0) {
|
||||||
|
|
@ -230,7 +289,7 @@ export default Vue.extend({
|
||||||
if (libraryId !== 0) {
|
if (libraryId !== 0) {
|
||||||
requestLibraryId = libraryId
|
requestLibraryId = libraryId
|
||||||
}
|
}
|
||||||
return this.$komgaSeries.getSeries(requestLibraryId, pageRequest)
|
return this.$komgaSeries.getSeries(requestLibraryId, pageRequest, undefined, this.filterStatus)
|
||||||
},
|
},
|
||||||
processPage (page: Page<SeriesDto>) {
|
processPage (page: Page<SeriesDto>) {
|
||||||
if (this.totalElements === null) {
|
if (this.totalElements === null) {
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ export default class KomgaSeriesService {
|
||||||
this.http = http
|
this.http = http
|
||||||
}
|
}
|
||||||
|
|
||||||
async getSeries (libraryId?: number, pageRequest?: PageRequest, search?: string): Promise<Page<SeriesDto>> {
|
async getSeries (libraryId?: number, pageRequest?: PageRequest, search?: string, status?: string[]): Promise<Page<SeriesDto>> {
|
||||||
try {
|
try {
|
||||||
const params = { ...pageRequest } as any
|
const params = { ...pageRequest } as any
|
||||||
if (libraryId) {
|
if (libraryId) {
|
||||||
|
|
@ -20,6 +20,9 @@ export default class KomgaSeriesService {
|
||||||
if (search) {
|
if (search) {
|
||||||
params.search = search
|
params.search = search
|
||||||
}
|
}
|
||||||
|
if (status) {
|
||||||
|
params.status = status
|
||||||
|
}
|
||||||
return (await this.http.get(API_SERIES, {
|
return (await this.http.get(API_SERIES, {
|
||||||
params: params,
|
params: params,
|
||||||
paramsSerializer: params => qs.stringify(params, { indices: false })
|
paramsSerializer: params => qs.stringify(params, { indices: false })
|
||||||
|
|
|
||||||
|
|
@ -16,3 +16,10 @@ export enum MediaStatus {
|
||||||
Error = 'ERROR',
|
Error = 'ERROR',
|
||||||
Unsupported = 'UNSUPPORTED'
|
Unsupported = 'UNSUPPORTED'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum SeriesStatus {
|
||||||
|
ENDED = 'ENDED',
|
||||||
|
ONGOING = 'ONGOING',
|
||||||
|
ABANDONED = 'ABANDONED',
|
||||||
|
HIATUS = 'HIATUS'
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue