mirror of
https://github.com/gotson/komga.git
synced 2026-02-14 19:33:02 +01:00
feat(webui): sort/filter settings are persisted per library
stored in cookies and restored if no query parameters are specified closes #190
This commit is contained in:
parent
d89533ded6
commit
bf737de910
4 changed files with 83 additions and 45 deletions
|
|
@ -8,16 +8,16 @@
|
|||
</v-btn>
|
||||
</template>
|
||||
<v-list>
|
||||
<div v-for="(f, i) in filtersOptions"
|
||||
:key="i"
|
||||
<div v-for="(f, key) in filtersOptions"
|
||||
:key="key"
|
||||
>
|
||||
<v-subheader v-if="f.name">{{ f.name }}</v-subheader>
|
||||
<v-list-item v-for="v in f.values"
|
||||
:key="v"
|
||||
@click.stop="click(i, v)"
|
||||
@click.stop="click(key, v)"
|
||||
>
|
||||
<v-list-item-icon>
|
||||
<v-icon v-if="filtersActive[i].includes(v)" color="secondary">
|
||||
<v-icon v-if="filtersActive[key].includes(v)" color="secondary">
|
||||
mdi-checkbox-marked
|
||||
</v-icon>
|
||||
<v-icon v-else>
|
||||
|
|
@ -33,7 +33,7 @@
|
|||
<template v-if="filterCustom">
|
||||
<v-divider/>
|
||||
|
||||
<v-list-item @click="clearAll()" dense>
|
||||
<v-list-item @click="clearAll" dense>
|
||||
<v-list-item-icon>
|
||||
<v-icon>mdi-close</v-icon>
|
||||
</v-list-item-icon>
|
||||
|
|
@ -50,39 +50,37 @@ import Vue from 'vue'
|
|||
export default Vue.extend({
|
||||
name: 'FilterMenuButton',
|
||||
props: {
|
||||
// array of object: name, values[]
|
||||
filtersOptions: {
|
||||
type: Array,
|
||||
type: Object as () => FiltersOptions,
|
||||
required: true,
|
||||
},
|
||||
// array of arrays containing the selection, use with sync
|
||||
filtersActive: {
|
||||
type: Array,
|
||||
type: Object as () => FiltersActive,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
filterCustom (): boolean {
|
||||
let r = false
|
||||
this.filtersActive.forEach(x => {
|
||||
if (!this.$_.isEmpty(x)) r = true
|
||||
})
|
||||
for (const [key, value] of Object.entries(this.filtersActive)) {
|
||||
if (!this.$_.isEmpty(value)) r = true
|
||||
}
|
||||
return r
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
clearAll () {
|
||||
let r = [] as any[]
|
||||
this.$_.times(this.filtersActive.length, x => {
|
||||
r.push([])
|
||||
})
|
||||
let r = this.$_.cloneDeep(this.filtersActive)
|
||||
for (const key of Object.keys(r)) {
|
||||
r[key] = []
|
||||
}
|
||||
|
||||
this.$emit('update:filtersActive', r)
|
||||
},
|
||||
click (index: number, value: string) {
|
||||
let r = this.$_.cloneDeep(this.filtersActive) as any[]
|
||||
if (r[index].includes(value)) this.$_.pull(r[index], (value))
|
||||
else r[index].push(value)
|
||||
click (key: string, value: string) {
|
||||
let r = this.$_.cloneDeep(this.filtersActive)
|
||||
if (r[key].includes(value)) this.$_.pull(r[key], (value))
|
||||
else r[key].push(value)
|
||||
|
||||
this.$emit('update:filtersActive', r)
|
||||
},
|
||||
|
|
|
|||
10
komga-webui/src/types/filter.ts
Normal file
10
komga-webui/src/types/filter.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
interface FiltersOptions {
|
||||
[key: string]: {
|
||||
name?: string,
|
||||
values: string[],
|
||||
},
|
||||
}
|
||||
|
||||
interface FiltersActive {
|
||||
[key: string]: string[],
|
||||
}
|
||||
|
|
@ -70,20 +70,20 @@
|
|||
|
||||
<script lang="ts">
|
||||
import Badge from '@/components/Badge.vue'
|
||||
import SeriesMultiSelectBar from '@/components/bars/SeriesMultiSelectBar.vue'
|
||||
import ToolbarSticky from '@/components/bars/ToolbarSticky.vue'
|
||||
import EmptyState from '@/components/EmptyState.vue'
|
||||
import FilterMenuButton from '@/components/FilterMenuButton.vue'
|
||||
import ItemBrowser from '@/components/ItemBrowser.vue'
|
||||
import LibraryActionsMenu from '@/components/menus/LibraryActionsMenu.vue'
|
||||
import LibraryNavigation from '@/components/LibraryNavigation.vue'
|
||||
import LibraryActionsMenu from '@/components/menus/LibraryActionsMenu.vue'
|
||||
import PageSizeSelect from '@/components/PageSizeSelect.vue'
|
||||
import SortMenuButton from '@/components/SortMenuButton.vue'
|
||||
import ToolbarSticky from '@/components/bars/ToolbarSticky.vue'
|
||||
import { parseQueryFilter, parseQuerySort } from '@/functions/query-params'
|
||||
import { ReadStatus } from '@/types/enum-books'
|
||||
import { SeriesStatus } from '@/types/enum-series'
|
||||
import { COLLECTION_CHANGED, LIBRARY_DELETED, SERIES_CHANGED } from '@/types/events'
|
||||
import Vue from 'vue'
|
||||
import SeriesMultiSelectBar from '@/components/bars/SeriesMultiSelectBar.vue'
|
||||
|
||||
const cookiePageSize = 'pagesize'
|
||||
|
||||
|
|
@ -117,8 +117,16 @@ export default Vue.extend({
|
|||
] as SortOption[],
|
||||
sortActive: {} as SortActive,
|
||||
sortDefault: { key: 'metadata.titleSort', order: 'asc' } as SortActive,
|
||||
filterOptions: [{ values: [ReadStatus.UNREAD] }, { name: 'STATUS', values: SeriesStatus }],
|
||||
filters: [[], []] as any[],
|
||||
filterOptions: {
|
||||
readStatus: {
|
||||
values: [ReadStatus.UNREAD],
|
||||
},
|
||||
status: {
|
||||
name: 'STATUS',
|
||||
values: Object.values(SeriesStatus),
|
||||
},
|
||||
} as FiltersOptions,
|
||||
filters: { status: [], readStatus: [] } as FiltersActive,
|
||||
sortUnwatch: null as any,
|
||||
filterUnwatch: null as any,
|
||||
pageUnwatch: null as any,
|
||||
|
|
@ -158,9 +166,7 @@ export default Vue.extend({
|
|||
}
|
||||
|
||||
// restore from query param
|
||||
this.sortActive = this.parseQuerySortOrDefault(this.$route.query.sort)
|
||||
this.filters.splice(1, 1, parseQueryFilter(this.$route.query.status, SeriesStatus))
|
||||
this.filters.splice(0, 1, parseQueryFilter(this.$route.query.readStatus, ReadStatus))
|
||||
this.resetParams(this.$route)
|
||||
if (this.$route.query.page) this.page = Number(this.$route.query.page)
|
||||
if (this.$route.query.pageSize) this.pageSize = Number(this.$route.query.pageSize)
|
||||
|
||||
|
|
@ -173,9 +179,7 @@ export default Vue.extend({
|
|||
this.unsetWatches()
|
||||
|
||||
// reset
|
||||
this.sortActive = this.parseQuerySortOrDefault(to.query.sort)
|
||||
this.filters.splice(1, 1, parseQueryFilter(to.query.status, SeriesStatus))
|
||||
this.filters.splice(0, 1, parseQueryFilter(to.query.readStatus, ReadStatus))
|
||||
this.resetParams(to)
|
||||
this.page = 1
|
||||
this.totalPages = 1
|
||||
this.totalElements = null
|
||||
|
|
@ -208,6 +212,25 @@ export default Vue.extend({
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
cookieSort (libraryId: number): string {
|
||||
return `library.sort.${libraryId}`
|
||||
},
|
||||
cookieFilter (libraryId: number): string {
|
||||
return `library.filter.${libraryId}`
|
||||
},
|
||||
resetParams (route: any) {
|
||||
this.sortActive = parseQuerySort(route.query.sort, this.sortOptions) ||
|
||||
this.$cookies.get(this.cookieSort(route.params.libraryId)) ||
|
||||
this.$_.clone(this.sortDefault)
|
||||
|
||||
if (route.query.status || route.query.readStatus) {
|
||||
this.filters.status = parseQueryFilter(route.query.status, SeriesStatus)
|
||||
this.filters.readStatus = parseQueryFilter(route.query.readStatus, ReadStatus)
|
||||
} else {
|
||||
this.filters = this.$cookies.get(this.cookieFilter(route.params.libraryId)) ||
|
||||
{ status: [], readStatus: [] } as FiltersActive
|
||||
}
|
||||
},
|
||||
libraryDeleted (event: EventLibraryDeleted) {
|
||||
if (event.id === this.libraryId) {
|
||||
this.$router.push({ name: 'home' })
|
||||
|
|
@ -216,8 +239,14 @@ export default Vue.extend({
|
|||
}
|
||||
},
|
||||
setWatches () {
|
||||
this.sortUnwatch = this.$watch('sortActive', this.updateRouteAndReload)
|
||||
this.filterUnwatch = this.$watch('filters', this.updateRouteAndReload)
|
||||
this.sortUnwatch = this.$watch('sortActive', (val) => {
|
||||
this.$cookies.set(this.cookieSort(this.libraryId), val, Infinity)
|
||||
this.updateRouteAndReload()
|
||||
})
|
||||
this.filterUnwatch = this.$watch('filters', (val) => {
|
||||
this.$cookies.set(this.cookieFilter(this.libraryId), val, Infinity)
|
||||
this.updateRouteAndReload()
|
||||
})
|
||||
this.pageSizeUnwatch = this.$watch('pageSize', (val) => {
|
||||
this.$cookies.set(cookiePageSize, val, Infinity)
|
||||
this.updateRouteAndReload()
|
||||
|
|
@ -261,9 +290,6 @@ export default Vue.extend({
|
|||
|
||||
await this.loadPage(libraryId, this.page, this.sortActive)
|
||||
},
|
||||
parseQuerySortOrDefault (querySort: any): SortActive {
|
||||
return parseQuerySort(querySort, this.sortOptions) || this.$_.clone(this.sortDefault)
|
||||
},
|
||||
updateRoute () {
|
||||
this.$router.replace({
|
||||
name: this.$route.name,
|
||||
|
|
@ -272,8 +298,8 @@ export default Vue.extend({
|
|||
page: `${this.page}`,
|
||||
pageSize: `${this.pageSize}`,
|
||||
sort: `${this.sortActive.key},${this.sortActive.order}`,
|
||||
status: `${this.filters[1]}`,
|
||||
readStatus: `${this.filters[0]}`,
|
||||
status: `${this.filters.status}`,
|
||||
readStatus: `${this.filters.readStatus}`,
|
||||
},
|
||||
}).catch(_ => {
|
||||
})
|
||||
|
|
@ -289,7 +315,7 @@ export default Vue.extend({
|
|||
}
|
||||
|
||||
const requestLibraryId = libraryId !== 0 ? libraryId : undefined
|
||||
const seriesPage = await this.$komgaSeries.getSeries(requestLibraryId, pageRequest, undefined, this.filters[1], this.filters[0])
|
||||
const seriesPage = await this.$komgaSeries.getSeries(requestLibraryId, pageRequest, undefined, this.filters.status, this.filters.readStatus)
|
||||
|
||||
this.totalPages = seriesPage.totalPages
|
||||
this.totalElements = seriesPage.totalElements
|
||||
|
|
|
|||
|
|
@ -168,8 +168,12 @@ export default Vue.extend({
|
|||
}] as SortOption[],
|
||||
sortActive: {} as SortActive,
|
||||
sortDefault: { key: 'metadata.numberSort', order: 'asc' } as SortActive,
|
||||
filterOptions: [{ values: [ReadStatus.UNREAD] }],
|
||||
filters: [[]] as any[],
|
||||
filterOptions: {
|
||||
readStatus: {
|
||||
values: [ReadStatus.UNREAD],
|
||||
},
|
||||
} as FiltersOptions,
|
||||
filters: { readStatus: [] } as FiltersActive,
|
||||
sortUnwatch: null as any,
|
||||
filterUnwatch: null as any,
|
||||
pageUnwatch: null as any,
|
||||
|
|
@ -228,7 +232,7 @@ export default Vue.extend({
|
|||
|
||||
// restore from query param
|
||||
this.sortActive = this.parseQuerySortOrDefault(this.$route.query.sort)
|
||||
this.filters.splice(0, 1, parseQueryFilter(this.$route.query.readStatus, ReadStatus))
|
||||
this.filters.readStatus = parseQueryFilter(this.$route.query.readStatus, ReadStatus)
|
||||
if (this.$route.query.page) this.page = Number(this.$route.query.page)
|
||||
if (this.$route.query.pageSize) this.pageSize = Number(this.$route.query.pageSize)
|
||||
|
||||
|
|
@ -242,7 +246,7 @@ export default Vue.extend({
|
|||
|
||||
// reset
|
||||
this.sortActive = this.parseQuerySortOrDefault(to.query.sort)
|
||||
this.filters.splice(0, 1, parseQueryFilter(to.query.readStatus, ReadStatus))
|
||||
this.filters.readStatus = parseQueryFilter(to.query.readStatus, ReadStatus)
|
||||
this.page = 1
|
||||
this.totalPages = 1
|
||||
this.totalElements = null
|
||||
|
|
@ -317,7 +321,7 @@ export default Vue.extend({
|
|||
page: `${this.page}`,
|
||||
pageSize: `${this.pageSize}`,
|
||||
sort: `${this.sortActive.key},${this.sortActive.order}`,
|
||||
readStatus: `${this.filters[0]}`,
|
||||
readStatus: `${this.filters.readStatus}`,
|
||||
},
|
||||
}).catch(_ => {
|
||||
})
|
||||
|
|
@ -331,7 +335,7 @@ export default Vue.extend({
|
|||
if (sort) {
|
||||
pageRequest.sort = [`${sort.key},${sort.order}`]
|
||||
}
|
||||
const booksPage = await this.$komgaSeries.getBooks(seriesId, pageRequest, this.filters[0])
|
||||
const booksPage = await this.$komgaSeries.getBooks(seriesId, pageRequest, this.filters.readStatus)
|
||||
|
||||
this.totalPages = booksPage.totalPages
|
||||
this.totalElements = booksPage.totalElements
|
||||
|
|
|
|||
Loading…
Reference in a new issue