Skip to content

Commit

Permalink
feat(webui): more filter criteria
Browse files Browse the repository at this point in the history
filter libraries by: genre, tag, publisher, language
filter series by: tag

closes gotson#283, closes gotson#34
  • Loading branch information
gotson committed Aug 25, 2020
1 parent 940d5d3 commit 4d22d9c
Show file tree
Hide file tree
Showing 10 changed files with 155 additions and 89 deletions.
43 changes: 21 additions & 22 deletions komga-webui/src/components/FilterList.vue
Original file line number Diff line number Diff line change
@@ -1,27 +1,25 @@
<template>
<v-list dense>
<div v-for="(f, key) in filtersOptions"
:key="key"
<v-list dense>
<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.value"
@click.stop="click(key, v.value)"
>
<v-subheader v-if="f.name">{{ f.name }}</v-subheader>
<v-list-item v-for="v in f.values"
:key="v"
@click.stop="click(key, v)"
>
<v-list-item-icon>
<v-icon v-if="filtersActive[key].includes(v)" color="secondary">
mdi-checkbox-marked
</v-icon>
<v-icon v-else>
mdi-checkbox-blank-outline
</v-icon>
</v-list-item-icon>
<v-list-item-title class="text-capitalize">
{{ v.toString().toLowerCase().replace('_', ' ') }}
</v-list-item-title>
</v-list-item>
</div>
</v-list>
<v-list-item-icon>
<v-icon v-if="key in filtersActive && filtersActive[key].includes(v.value)" color="secondary">
mdi-checkbox-marked
</v-icon>
<v-icon v-else>
mdi-checkbox-blank-outline
</v-icon>
</v-list-item-icon>
<v-list-item-title>{{ v.name }}</v-list-item-title>
</v-list-item>
</div>
</v-list>
</template>

<script lang="ts">
Expand All @@ -42,6 +40,7 @@ export default Vue.extend({
methods: {
click (key: string, value: string) {
let r = this.$_.cloneDeep(this.filtersActive)
if (!(key in r)) r[key] = []
if (r[key].includes(value)) this.$_.pull(r[key], (value))
else r[key].push(value)
Expand Down
16 changes: 8 additions & 8 deletions komga-webui/src/components/FilterPanels.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<v-expansion-panels accordion multiple flat tile hover>
<v-expansion-panels accordion flat tile hover>
<v-expansion-panel
v-for="(f, key) in filtersOptions"
:key="key"
Expand All @@ -17,20 +17,18 @@
<v-expansion-panel-content class="no-padding">
<v-list dense>
<v-list-item v-for="v in f.values"
:key="v"
@click.stop="click(key, v)"
:key="v.value"
@click.stop="click(key, v.value)"
>
<v-list-item-icon>
<v-icon v-if="filtersActive[key].includes(v)" color="secondary">
<v-icon v-if="key in filtersActive && filtersActive[key].includes(v.value)" color="secondary">
mdi-checkbox-marked
</v-icon>
<v-icon v-else>
mdi-checkbox-blank-outline
</v-icon>
</v-list-item-icon>
<v-list-item-title class="text-capitalize">
{{ v.toString().toLowerCase().replace('_', ' ') }}
</v-list-item-title>
<v-list-item-title>{{ v.name }}</v-list-item-title>
</v-list-item>
</v-list>
</v-expansion-panel-content>
Expand Down Expand Up @@ -61,15 +59,17 @@ export default Vue.extend({
this.$emit('update:filtersActive', r)
},
groupActive (key: string): boolean {
if (!(key in this.filtersActive)) return false
for (let v of this.filtersOptions[key].values) {
if (this.filtersActive[key].includes(v)) {
if (this.filtersActive[key].includes(v.value)) {
return true
}
}
return false
},
click (key: string, value: string) {
let r = this.$_.cloneDeep(this.filtersActive)
if (!(key in r)) r[key] = []
if (r[key].includes(value)) this.$_.pull(r[key], (value))
else r[key].push(value)
Expand Down
10 changes: 10 additions & 0 deletions komga-webui/src/functions/filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,13 @@ export function sortOrFilterActive (sortActive: SortActive, sortDefault: SortAct
const filterCustom = Object.keys(filters).some(x => filters[x].length !== 0)
return sortCustom || filterCustom
}

export function mergeFilterParams (filter: FiltersActive, query: any) {
for (const f of Object.keys(filter)) {
if (filter[f].length !== 0) query[f] = `${filter[f]}`
}
}

export function toNameValue (list: string[]): NameValue[] {
return list.map(x => ({ name: x, value: x } as NameValue))
}
4 changes: 2 additions & 2 deletions komga-webui/src/functions/query-params.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ export function parseQuerySort (querySort: any, sortOptions: SortOption[]): Sort
return customSort
}

export function parseQueryFilter (queryStatus: any, enumeration: any): string[] {
return queryStatus ? queryStatus.toString().split(',').filter((x: string) => Object.keys(enumeration).includes(x)) : []
export function parseQueryFilter (queryStatus: any, enumeration: string[]): string[] {
return queryStatus ? queryStatus.toString().split(',').filter((x: string) => enumeration.includes(x)) : []
}
34 changes: 34 additions & 0 deletions komga-webui/src/services/komga-referential.service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { AxiosInstance } from 'axios'

const qs = require('qs')
const tags = require('language-tags')

export default class KomgaReferentialService {
private http: AxiosInstance
Expand Down Expand Up @@ -51,4 +52,37 @@ export default class KomgaReferentialService {
throw new Error(msg)
}
}

async getPublishers (): Promise<string[]> {
try {
return (await this.http.get('/api/v1/publishers')).data
} catch (e) {
let msg = 'An error occurred while trying to retrieve publishers'
if (e.response.data.message) {
msg += `: ${e.response.data.message}`
}
throw new Error(msg)
}
}

async getLanguages (): Promise<NameValue[]> {
try {
const data = (await this.http.get('/api/v1/languages')).data
const ret = [] as NameValue[]
for (const code of data) {
const tag = tags(code)
if (tag.valid()) {
const name = tag.language().descriptions()[0] + ` (${code})`
ret.push({ name: name, value: code })
}
}
return ret
} catch (e) {
let msg = 'An error occurred while trying to retrieve publishers'
if (e.response.data.message) {
msg += `: ${e.response.data.message}`
}
throw new Error(msg)
}
}
}
31 changes: 14 additions & 17 deletions komga-webui/src/services/komga-series.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,18 @@ export default class KomgaSeriesService {
this.http = http
}

async getSeries (libraryId?: string, pageRequest?: PageRequest, search?: string, status?: string[], readStatus?: string[]): Promise<Page<SeriesDto>> {
async getSeries (libraryId?: string, pageRequest?: PageRequest, search?: string, status?: string[], readStatus?: string[], genre?: string[], tag?: string[], language?: string[], publisher?: string[]): Promise<Page<SeriesDto>> {
try {
const params = { ...pageRequest } as any
if (libraryId) {
params.library_id = libraryId
}
if (search) {
params.search = search
}
if (status) {
params.status = status
}
if (readStatus) {
params.read_status = readStatus
}
if (libraryId) params.library_id = libraryId
if (search) params.search = search
if (status) params.status = status
if (readStatus) params.read_status = readStatus
if (genre) params.genre = genre
if (tag) params.tag = tag
if (language) params.language = language
if (publisher) params.publisher = publisher

return (await this.http.get(API_SERIES, {
params: params,
paramsSerializer: params => qs.stringify(params, { indices: false }),
Expand Down Expand Up @@ -81,12 +78,12 @@ export default class KomgaSeriesService {
}
}

async getBooks (seriesId: string, pageRequest?: PageRequest, readStatus?: string[]): Promise<Page<BookDto>> {
async getBooks (seriesId: string, pageRequest?: PageRequest, readStatus?: string[], tag?: string[]): Promise<Page<BookDto>> {
try {
const params = { ...pageRequest } as any
if (readStatus) {
params.read_status = readStatus
}
if (readStatus) params.read_status = readStatus
if (tag) params.tag = tag

return (await this.http.get(`${API_SERIES}/${seriesId}/books`, {
params: params,
paramsSerializer: params => qs.stringify(params, { indices: false }),
Expand Down
7 changes: 7 additions & 0 deletions komga-webui/src/types/enum-series.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import { capitalize } from 'lodash'

export enum SeriesStatus {
ENDED = 'ENDED',
ONGOING = 'ONGOING',
ABANDONED = 'ABANDONED',
HIATUS = 'HIATUS'
}

export const SeriesStatusKeyValue = Object.values(SeriesStatus).map(x => ({
name: capitalize(x),
value: x,
} as NameValue))
7 changes: 6 additions & 1 deletion komga-webui/src/types/filter.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
interface FiltersOptions {
[key: string]: {
name?: string,
values: string[],
values: NameValue[],
},
}

interface NameValue {
name: string,
value: string,
}

interface FiltersActive {
[key: string]: string[],
}
47 changes: 26 additions & 21 deletions komga-webui/src/views/BrowseLibraries.vue
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ import LibraryActionsMenu from '@/components/menus/LibraryActionsMenu.vue'
import PageSizeSelect from '@/components/PageSizeSelect.vue'
import { parseQueryFilter, parseQuerySort } from '@/functions/query-params'
import { ReadStatus } from '@/types/enum-books'
import { SeriesStatus } from '@/types/enum-series'
import { SeriesStatus, SeriesStatusKeyValue } from '@/types/enum-series'
import { LIBRARY_CHANGED, LIBRARY_DELETED, SERIES_CHANGED } from '@/types/events'
import Vue from 'vue'
import { Location } from 'vue-router'
Expand All @@ -104,7 +104,7 @@ import FilterDrawer from '@/components/FilterDrawer.vue'
import SortList from '@/components/SortList.vue'
import FilterPanels from '@/components/FilterPanels.vue'
import FilterList from '@/components/FilterList.vue'
import { sortOrFilterActive } from '@/functions/filter'
import { mergeFilterParams, sortOrFilterActive, toNameValue } from '@/functions/filter'
const cookiePageSize = 'pagesize'
Expand Down Expand Up @@ -140,17 +140,16 @@ export default Vue.extend({
sortActive: {} as SortActive,
sortDefault: { key: 'metadata.titleSort', order: 'asc' } as SortActive,
filterOptionsList: {
readStatus: {
values: [ReadStatus.UNREAD],
},
readStatus: { values: [{ name: 'Unread', value: ReadStatus.UNREAD }] },
} as FiltersOptions,
filterOptionsPanel: {
status: {
name: 'STATUS',
values: Object.values(SeriesStatus),
},
status: { name: 'STATUS', values: SeriesStatusKeyValue },
genre: { name: 'GENRE', values: [] },
tag: { name: 'TAG', values: [] },
publisher: { name: 'PUBLISHER', values: [] },
language: { name: 'LANGUAGE', values: [] },
} as FiltersOptions,
filters: { status: [], readStatus: [] } as FiltersActive,
filters: {} as FiltersActive,
sortUnwatch: null as any,
filterUnwatch: null as any,
pageUnwatch: null as any,
Expand Down Expand Up @@ -184,11 +183,16 @@ export default Vue.extend({
this.$eventHub.$off(LIBRARY_DELETED, this.libraryDeleted)
this.$eventHub.$off(LIBRARY_CHANGED, this.reloadLibrary)
},
mounted () {
async mounted () {
if (this.$cookies.isKey(cookiePageSize)) {
this.pageSize = Number(this.$cookies.get(cookiePageSize))
}
this.filterOptionsPanel.genre.values.push(...toNameValue(await this.$komgaReferential.getGenres()))
this.filterOptionsPanel.tag.values.push(...toNameValue(await this.$komgaReferential.getTags()))
this.filterOptionsPanel.publisher.values.push(...toNameValue(await this.$komgaReferential.getPublishers()))
this.filterOptionsPanel.language.values.push(...(await this.$komgaReferential.getLanguages()))
// restore from query param
this.resetParams(this.$route)
if (this.$route.query.page) this.page = Number(this.$route.query.page)
Expand Down Expand Up @@ -249,12 +253,13 @@ export default Vue.extend({
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)
if (route.query.status || route.query.readStatus || route.query.genre || route.query.tag) {
this.filters.status = parseQueryFilter(route.query.status, Object.keys(SeriesStatus))
this.filters.readStatus = parseQueryFilter(route.query.readStatus, Object.keys(ReadStatus))
this.filters.genre = parseQueryFilter(route.query.genre, this.filterOptionsPanel.genre.values.map(x => x.value))
this.filters.tag = parseQueryFilter(route.query.tag, this.filterOptionsPanel.tag.values.map(x => x.value))
} else {
this.filters = this.$cookies.get(this.cookieFilter(route.params.libraryId)) ||
{ status: [], readStatus: [] } as FiltersActive
this.filters = this.$cookies.get(this.cookieFilter(route.params.libraryId)) || {} as FiltersActive
}
},
libraryDeleted (event: EventLibraryDeleted) {
Expand Down Expand Up @@ -316,17 +321,17 @@ export default Vue.extend({
await this.loadPage(libraryId, this.page, this.sortActive)
},
updateRoute () {
this.$router.replace({
const loc = {
name: this.$route.name,
params: { libraryId: this.$route.params.libraryId },
query: {
page: `${this.page}`,
pageSize: `${this.pageSize}`,
sort: `${this.sortActive.key},${this.sortActive.order}`,
status: `${this.filters.status}`,
readStatus: `${this.filters.readStatus}`,
},
} as Location).catch((_: any) => {
} as Location
mergeFilterParams(this.filters, loc.query)
this.$router.replace(loc).catch((_: any) => {
})
},
async loadPage (libraryId: string, page: number, sort: SortActive) {
Expand All @@ -340,7 +345,7 @@ export default Vue.extend({
}
const requestLibraryId = libraryId !== LIBRARIES_ALL ? libraryId : undefined
const seriesPage = await this.$komgaSeries.getSeries(requestLibraryId, pageRequest, undefined, this.filters.status, this.filters.readStatus)
const seriesPage = await this.$komgaSeries.getSeries(requestLibraryId, pageRequest, undefined, this.filters.status, this.filters.readStatus, this.filters.genre, this.filters.tag, this.filters.language, this.filters.publisher)
this.totalPages = seriesPage.totalPages
this.totalElements = seriesPage.totalElements
Expand Down

0 comments on commit 4d22d9c

Please sign in to comment.