feat(webui): filter series by status

when browsing libraries

linked to #48
This commit is contained in:
Gauthier Roebroeck 2020-01-22 14:53:38 +08:00
parent c96bf19048
commit c540e56c08
3 changed files with 84 additions and 15 deletions

View file

@ -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) {

View file

@ -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 })

View file

@ -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'
}