From c2c2f58f1a4fc24a46bc9377768d75fa5f73c6ac Mon Sep 17 00:00:00 2001 From: Gauthier Roebroeck Date: Fri, 26 Feb 2021 16:25:12 +0800 Subject: [PATCH] feat(webui): filter series and books by authors closes #339 --- .../src/services/komga-collections.service.ts | 4 +- .../src/services/komga-series.service.ts | 8 +- komga-webui/src/views/BrowseCollection.vue | 31 +++++++- komga-webui/src/views/BrowseLibraries.vue | 75 +++++++++++-------- komga-webui/src/views/BrowseSeries.vue | 31 +++++++- 5 files changed, 105 insertions(+), 44 deletions(-) diff --git a/komga-webui/src/services/komga-collections.service.ts b/komga-webui/src/services/komga-collections.service.ts index 576974241..0fbe1d933 100644 --- a/komga-webui/src/services/komga-collections.service.ts +++ b/komga-webui/src/services/komga-collections.service.ts @@ -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> { + publisher?: string[], ageRating?: string[], releaseDate?: string[], authors?: AuthorDto[]): Promise> { 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, diff --git a/komga-webui/src/services/komga-series.service.ts b/komga-webui/src/services/komga-series.service.ts index 466775fe9..44a11270e 100644 --- a/komga-webui/src/services/komga-series.service.ts +++ b/komga-webui/src/services/komga-series.service.ts @@ -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> { + publisher?: string[], ageRating?: string[], releaseDate?: string[], authors?: AuthorDto[]): Promise> { 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> { + async getBooks (seriesId: string, pageRequest?: PageRequest, readStatus?: string[], tag?: string[], authors?: AuthorDto[]): Promise> { 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, diff --git a/komga-webui/src/views/BrowseCollection.vue b/komga-webui/src/views/BrowseCollection.vue index 34231ac17..b8e546433 100644 --- a/komga-webui/src/views/BrowseCollection.vue +++ b/komga-webui/src/views/BrowseCollection.vue @@ -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 = [] }, diff --git a/komga-webui/src/views/BrowseLibraries.vue b/komga-webui/src/views/BrowseLibraries.vue index dc90ce70f..0faa86a9d 100644 --- a/komga-webui/src/views/BrowseLibraries.vue +++ b/komga-webui/src/views/BrowseLibraries.vue @@ -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 diff --git a/komga-webui/src/views/BrowseSeries.vue b/komga-webui/src/views/BrowseSeries.vue index bf51c17f8..ea21b1488 100644 --- a/komga-webui/src/views/BrowseSeries.vue +++ b/komga-webui/src/views/BrowseSeries.vue @@ -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