feat(webui): filter series and books by authors

closes #339
This commit is contained in:
Gauthier Roebroeck 2021-02-26 16:25:12 +08:00
parent bd64381a8e
commit c2c2f58f1a
5 changed files with 105 additions and 44 deletions

View File

@ -1,5 +1,6 @@
import {AxiosInstance} from 'axios'
import {SeriesDto} from "@/types/komga-series";
import {AuthorDto} from "@/types/komga-books";
const qs = require('qs')
@ -82,7 +83,7 @@ export default class KomgaCollectionsService {
async getSeries (collectionId: string, pageRequest?: PageRequest,
libraryId?: string[], status?: string[],
readStatus?: string[], genre?: string[], tag?: string[], language?: string[],
publisher?: string[], ageRating?: string[], releaseDate?: string[]): Promise<Page<SeriesDto>> {
publisher?: string[], ageRating?: string[], releaseDate?: string[], authors?: AuthorDto[]): Promise<Page<SeriesDto>> {
try {
const params = { ...pageRequest } as any
if (libraryId) params.library_id = libraryId
@ -94,6 +95,7 @@ export default class KomgaCollectionsService {
if (publisher) params.publisher = publisher
if (ageRating) params.age_rating = ageRating
if (releaseDate) params.release_year = releaseDate
if (authors) params.author = authors.map(a => `${a.name},${a.role}`)
return (await this.http.get(`${API_COLLECTIONS}/${collectionId}/series`, {
params: params,

View File

@ -1,5 +1,5 @@
import {AxiosInstance} from 'axios'
import {BookDto} from '@/types/komga-books'
import {AuthorDto, BookDto} from '@/types/komga-books'
import {SeriesDto, SeriesMetadataUpdateDto} from "@/types/komga-series";
const qs = require('qs')
@ -15,7 +15,7 @@ export default class KomgaSeriesService {
async getSeries (libraryId?: string, pageRequest?: PageRequest, search?: string, status?: string[],
readStatus?: string[], genre?: string[], tag?: string[], language?: string[],
publisher?: string[], ageRating?: string[], releaseDate?: string[]): Promise<Page<SeriesDto>> {
publisher?: string[], ageRating?: string[], releaseDate?: string[], authors?: AuthorDto[]): Promise<Page<SeriesDto>> {
try {
const params = { ...pageRequest } as any
if (libraryId) params.library_id = libraryId
@ -28,6 +28,7 @@ export default class KomgaSeriesService {
if (publisher) params.publisher = publisher
if (ageRating) params.age_rating = ageRating
if (releaseDate) params.release_year = releaseDate
if (authors) params.author = authors.map(a => `${a.name},${a.role}`)
return (await this.http.get(API_SERIES, {
params: params,
@ -84,11 +85,12 @@ export default class KomgaSeriesService {
}
}
async getBooks (seriesId: string, pageRequest?: PageRequest, readStatus?: string[], tag?: string[]): Promise<Page<BookDto>> {
async getBooks (seriesId: string, pageRequest?: PageRequest, readStatus?: string[], tag?: string[], authors?: AuthorDto[]): Promise<Page<BookDto>> {
try {
const params = { ...pageRequest } as any
if (readStatus) params.read_status = readStatus
if (tag) params.tag = tag
if (authors) params.author = authors.map(a => `${a.name},${a.role}`)
return (await this.http.get(`${API_SERIES}/${seriesId}/books`, {
params: params,

View File

@ -123,6 +123,9 @@ import {Location} from 'vue-router'
import EmptyState from '@/components/EmptyState.vue'
import {parseQueryFilter} from '@/functions/query-params'
import {SeriesDto} from "@/types/komga-series";
import {authorRoles} from "@/types/author-roles";
import {groupAuthorsByRole} from "@/functions/authors";
import {AuthorDto} from "@/types/komga-books";
export default Vue.extend({
name: 'BrowseCollection',
@ -217,7 +220,7 @@ export default Vue.extend({
} as FiltersOptions
},
filterOptionsPanel(): FiltersOptions {
return {
const r = {
library: {name: this.$t('filter.library').toString(), values: this.filterOptions.library},
status: {name: this.$t('filter.status').toString(), values: SeriesStatusKeyValue()},
genre: {name: this.$t('filter.genre').toString(), values: this.filterOptions.genre},
@ -234,6 +237,11 @@ export default Vue.extend({
},
releaseDate: {name: this.$t('filter.release_date').toString(), values: this.filterOptions.releaseDate},
} as FiltersOptions
authorRoles.forEach((role: string) => {
//@ts-ignore
r[role] = {name: this.$t(`author_roles.${role}`).toString(), values: this.$_.get(this.filterOptions, role, [])}
})
return r
},
isAdmin(): boolean {
return this.$store.getters.meAdmin
@ -255,17 +263,26 @@ export default Vue.extend({
this.$set(this.filterOptions, 'language', (await this.$komgaReferential.getLanguages(undefined, collectionId)))
this.$set(this.filterOptions, 'ageRating', toNameValue(await this.$komgaReferential.getAgeRatings(undefined, collectionId)))
this.$set(this.filterOptions, 'releaseDate', toNameValue(await this.$komgaReferential.getSeriesReleaseDates(undefined, collectionId)))
const grouped = groupAuthorsByRole(await this.$komgaReferential.getAuthors(undefined, undefined, collectionId))
authorRoles.forEach((role: string) => {
this.$set(this.filterOptions, role, role in grouped ? toNameValue(grouped[role]) : [])
})
// filter query params with available filter values
if (route.query.status || route.query.readStatus || route.query.genre || route.query.tag || route.query.language || route.query.ageRating || route.query.library) {
if (route.query.status || route.query.readStatus || route.query.genre || route.query.tag || route.query.language || route.query.ageRating || route.query.library || route.query.publisher || authorRoles.some(role => role in route.query)) {
this.$set(this.filters, 'status', parseQueryFilter(route.query.status, Object.keys(SeriesStatus)))
this.$set(this.filters, 'readStatus', parseQueryFilter(route.query.readStatus, Object.keys(ReadStatus)))
this.$set(this.filters, 'library', parseQueryFilter(route.query.library, this.filterOptions.library.map(x => x.value)))
this.$set(this.filters, 'genre', parseQueryFilter(route.query.genre, this.filterOptions.genre.map(x => x.value)))
this.$set(this.filters, 'tag', parseQueryFilter(route.query.tag, this.filterOptions.tag.map(x => x.value)))
this.$set(this.filters, 'publisher', parseQueryFilter(route.query.publisher, this.filterOptions.publisher.map(x => x.value)))
this.$set(this.filters, 'language', parseQueryFilter(route.query.language, this.filterOptions.language.map(x => x.value)))
this.$set(this.filters, 'ageRating', parseQueryFilter(route.query.ageRating, this.filterOptions.ageRating.map(x => x.value)))
this.$set(this.filters, 'releaseDate', parseQueryFilter(route.query.releaseDate, this.filterOptions.releaseDate.map(x => x.value)))
authorRoles.forEach((role: string) => {
//@ts-ignore
this.$set(this.filters, role, parseQueryFilter(route.query[role], this.filterOptions[role].map((x: NameValue) => x.value)))
})
} else {
this.filters = this.$cookies.get(this.cookieFilter(route.params.collectionId)) || {} as FiltersActive
}
@ -296,7 +313,15 @@ export default Vue.extend({
this.setWatches()
},
async loadSeries(collectionId: string) {
this.series = (await this.$komgaCollections.getSeries(collectionId, {unpaged: true} as PageRequest, this.filters.library, this.filters.status, this.filters.readStatus, this.filters.genre, this.filters.tag, this.filters.language, this.filters.publisher, this.filters.ageRating, this.filters.releaseDate)).content
let authorsFilter = [] as AuthorDto[]
authorRoles.forEach((role: string) => {
if (role in this.filters) this.filters[role].forEach((name: string) => authorsFilter.push({
name: name,
role: role,
}))
})
this.series = (await this.$komgaCollections.getSeries(collectionId, {unpaged: true} as PageRequest, this.filters.library, this.filters.status, this.filters.readStatus, this.filters.genre, this.filters.tag, this.filters.language, this.filters.publisher, this.filters.ageRating, this.filters.releaseDate, authorsFilter)).content
this.seriesCopy = [...this.series]
this.selectedSeries = []
},

View File

@ -113,6 +113,9 @@ import FilterPanels from '@/components/FilterPanels.vue'
import FilterList from '@/components/FilterList.vue'
import {mergeFilterParams, sortOrFilterActive, toNameValue} from '@/functions/filter'
import {SeriesDto} from "@/types/komga-series";
import {groupAuthorsByRole} from "@/functions/authors";
import {AuthorDto} from "@/types/komga-books";
import {authorRoles} from "@/types/author-roles";
const cookiePageSize = 'pagesize'
@ -142,16 +145,7 @@ export default Vue.extend({
totalElements: null as number | null,
sortActive: {} as SortActive,
sortDefault: {key: 'metadata.titleSort', order: 'asc'} as SortActive,
// we need to define all the possible values, else the properties are not reactive when changed
filters: {
status: [],
readStatus: [],
genre: [],
tag: [],
language: [],
ageRating: [],
releaseDate: [],
} as FiltersActive,
filters: {} as FiltersActive,
sortUnwatch: null as any,
filterUnwatch: null as any,
pageUnwatch: null as any,
@ -217,12 +211,6 @@ export default Vue.extend({
this.totalPages = 1
this.totalElements = null
this.series = []
this.filterOptions.genre = []
this.filterOptions.tag = []
this.filterOptions.publisher = []
this.filterOptions.language = []
this.filterOptions.ageRating = []
this.filterOptions.releaseDate = []
this.loadLibrary(to.params.libraryId)
@ -246,7 +234,7 @@ export default Vue.extend({
} as FiltersOptions
},
filterOptionsPanel(): FiltersOptions {
return {
const r = {
status: {name: this.$t('filter.status').toString(), values: SeriesStatusKeyValue()},
genre: {name: this.$t('filter.genre').toString(), values: this.filterOptions.genre},
tag: {name: this.$t('filter.tag').toString(), values: this.filterOptions.tag},
@ -262,6 +250,11 @@ export default Vue.extend({
},
releaseDate: {name: this.$t('filter.release_date').toString(), values: this.filterOptions.releaseDate},
} as FiltersOptions
authorRoles.forEach((role: string) => {
//@ts-ignore
r[role] = {name: this.$t(`author_roles.${role}`).toString(), values: this.$_.get(this.filterOptions, role, [])}
})
return r
},
isAdmin(): boolean {
return this.$store.getters.meAdmin
@ -298,23 +291,31 @@ export default Vue.extend({
const requestLibraryId = libraryId !== LIBRARIES_ALL ? libraryId : undefined
// load dynamic filters
this.filterOptions.genre = toNameValue(await this.$komgaReferential.getGenres(requestLibraryId))
this.filterOptions.tag = toNameValue(await this.$komgaReferential.getTags(requestLibraryId))
this.filterOptions.publisher = toNameValue(await this.$komgaReferential.getPublishers(requestLibraryId))
this.filterOptions.language = (await this.$komgaReferential.getLanguages(requestLibraryId))
this.filterOptions.ageRating = toNameValue(await this.$komgaReferential.getAgeRatings(requestLibraryId))
this.filterOptions.releaseDate = toNameValue(await this.$komgaReferential.getSeriesReleaseDates(requestLibraryId))
this.$set(this.filterOptions, 'genre', toNameValue(await this.$komgaReferential.getGenres(requestLibraryId)))
this.$set(this.filterOptions, 'tag', toNameValue(await this.$komgaReferential.getTags(requestLibraryId)))
this.$set(this.filterOptions, 'publisher', toNameValue(await this.$komgaReferential.getPublishers(requestLibraryId)))
this.$set(this.filterOptions, 'language', (await this.$komgaReferential.getLanguages(requestLibraryId)))
this.$set(this.filterOptions, 'ageRating', toNameValue(await this.$komgaReferential.getAgeRatings(requestLibraryId)))
this.$set(this.filterOptions, 'releaseDate', toNameValue(await this.$komgaReferential.getSeriesReleaseDates(requestLibraryId)))
const grouped = groupAuthorsByRole(await this.$komgaReferential.getAuthors(undefined, requestLibraryId))
authorRoles.forEach((role: string) => {
this.$set(this.filterOptions, role, role in grouped ? toNameValue(grouped[role]) : [])
})
// filter query params with available filter values
if (route.query.status || route.query.readStatus || route.query.genre || route.query.tag || route.query.language || route.query.ageRating || route.query.publisher) {
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.filterOptions.genre.map(x => x.value))
this.filters.tag = parseQueryFilter(route.query.tag, this.filterOptions.tag.map(x => x.value))
this.filters.publisher = parseQueryFilter(route.query.publisher, this.filterOptions.publisher.map(x => x.value))
this.filters.language = parseQueryFilter(route.query.language, this.filterOptions.language.map(x => x.value))
this.filters.ageRating = parseQueryFilter(route.query.ageRating, this.filterOptions.ageRating.map(x => x.value))
this.filters.releaseDate = parseQueryFilter(route.query.releaseDate, this.filterOptions.releaseDate.map(x => x.value))
if (route.query.status || route.query.readStatus || route.query.genre || route.query.tag || route.query.language || route.query.ageRating || route.query.publisher || authorRoles.some(role => role in route.query)) {
this.$set(this.filters, 'status', parseQueryFilter(route.query.status, Object.keys(SeriesStatus)))
this.$set(this.filters, 'readStatus', parseQueryFilter(route.query.readStatus, Object.keys(ReadStatus)))
this.$set(this.filters, 'genre', parseQueryFilter(route.query.genre, this.filterOptions.genre.map(x => x.value)))
this.$set(this.filters, 'tag', parseQueryFilter(route.query.tag, this.filterOptions.tag.map(x => x.value)))
this.$set(this.filters, 'publisher', parseQueryFilter(route.query.publisher, this.filterOptions.publisher.map(x => x.value)))
this.$set(this.filters, 'language', parseQueryFilter(route.query.language, this.filterOptions.language.map(x => x.value)))
this.$set(this.filters, 'ageRating', parseQueryFilter(route.query.ageRating, this.filterOptions.ageRating.map(x => x.value)))
this.$set(this.filters, 'releaseDate', parseQueryFilter(route.query.releaseDate, this.filterOptions.releaseDate.map(x => x.value)))
authorRoles.forEach((role: string) => {
//@ts-ignore
this.$set(this.filters, role, parseQueryFilter(route.query[role], this.filterOptions[role].map((x: NameValue) => x.value)))
})
} else {
this.filters = this.$cookies.get(this.cookieFilter(route.params.libraryId)) || {} as FiltersActive
}
@ -402,8 +403,16 @@ export default Vue.extend({
pageRequest.sort = [`${sort.key},${sort.order}`]
}
let authorsFilter = [] as AuthorDto[]
authorRoles.forEach((role: string) => {
if (role in this.filters) this.filters[role].forEach((name: string) => authorsFilter.push({
name: name,
role: role,
}))
})
const requestLibraryId = libraryId !== LIBRARIES_ALL ? libraryId : undefined
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.filters.ageRating, this.filters.releaseDate)
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.filters.ageRating, this.filters.releaseDate, authorsFilter)
this.totalPages = seriesPage.totalPages
this.totalElements = seriesPage.totalElements

View File

@ -283,7 +283,7 @@ import {ReadStatus} from '@/types/enum-books'
import {BOOK_CHANGED, LIBRARY_DELETED, READLIST_CHANGED, SERIES_CHANGED} from '@/types/events'
import Vue from 'vue'
import {Location} from 'vue-router'
import {BookDto} from '@/types/komga-books'
import {AuthorDto, BookDto} from '@/types/komga-books'
import {SeriesStatus} from '@/types/enum-series'
import FilterDrawer from '@/components/FilterDrawer.vue'
import FilterList from '@/components/FilterList.vue'
@ -291,8 +291,9 @@ import SortList from '@/components/SortList.vue'
import {mergeFilterParams, sortOrFilterActive, toNameValue} from '@/functions/filter'
import FilterPanels from '@/components/FilterPanels.vue'
import {SeriesDto} from "@/types/komga-series";
import {groupAuthorsByRoleI18n} from "@/functions/authors";
import {groupAuthorsByRole, groupAuthorsByRoleI18n} from "@/functions/authors";
import ReadMore from "@/components/ReadMore.vue";
import {authorRoles} from "@/types/author-roles";
const tags = require('language-tags')
@ -354,9 +355,14 @@ export default Vue.extend({
} as FiltersOptions
},
filterOptionsPanel(): FiltersOptions {
return {
const r = {
tag: {name: this.$t('filter.tag').toString(), values: this.filterOptions.tag},
} as FiltersOptions
authorRoles.forEach((role: string) => {
//@ts-ignore
r[role] = {name: this.$t(`author_roles.${role}`).toString(), values: this.$_.get(this.filterOptions, role, [])}
})
return r
},
isAdmin(): boolean {
return this.$store.getters.meAdmin
@ -462,10 +468,18 @@ export default Vue.extend({
// load dynamic filters
this.$set(this.filterOptions, 'tag', toNameValue(await this.$komgaReferential.getTags(undefined, seriesId)))
const grouped = groupAuthorsByRole(await this.$komgaReferential.getAuthors(undefined, undefined, undefined, seriesId))
authorRoles.forEach((role: string) => {
this.$set(this.filterOptions, role, role in grouped ? toNameValue(grouped[role]) : [])
})
// filter query params with available filter values
this.$set(this.filters, 'readStatus', parseQueryFilter(this.$route.query.readStatus, Object.keys(ReadStatus)))
this.$set(this.filters, 'tag', parseQueryFilter(this.$route.query.tag, this.filterOptions.tag.map(x => x.value)))
authorRoles.forEach((role: string) => {
//@ts-ignore
this.$set(this.filters, role, parseQueryFilter(route.query[role], this.filterOptions[role].map((x: NameValue) => x.value)))
})
},
setWatches() {
this.sortUnwatch = this.$watch('sortActive', this.updateRouteAndReload)
@ -544,7 +558,16 @@ export default Vue.extend({
if (sort) {
pageRequest.sort = [`${sort.key},${sort.order}`]
}
const booksPage = await this.$komgaSeries.getBooks(seriesId, pageRequest, this.filters.readStatus, this.filters.tag)
let authorsFilter = [] as AuthorDto[]
authorRoles.forEach((role: string) => {
if (role in this.filters) this.filters[role].forEach((name: string) => authorsFilter.push({
name: name,
role: role,
}))
})
const booksPage = await this.$komgaSeries.getBooks(seriesId, pageRequest, this.filters.readStatus, this.filters.tag, authorsFilter)
this.totalPages = booksPage.totalPages
this.totalElements = booksPage.totalElements