From d07eb39181983fe3c3db254401cf6983d1aac76f Mon Sep 17 00:00:00 2001 From: Gauthier Roebroeck Date: Fri, 17 Jan 2025 17:54:38 +0800 Subject: [PATCH] feat(webui): filter books by media profile Refs: #1829 --- komga-webui/src/locales/en.json | 6 ++++++ komga-webui/src/types/enum-books.ts | 6 ++++++ komga-webui/src/types/komga-search.ts | 8 ++++++++ komga-webui/src/views/BrowseSeries.vue | 17 +++++++++++++++-- 4 files changed, 35 insertions(+), 2 deletions(-) diff --git a/komga-webui/src/locales/en.json b/komga-webui/src/locales/en.json index a6d0dddf439..0dc219f2f01 100644 --- a/komga-webui/src/locales/en.json +++ b/komga-webui/src/locales/en.json @@ -688,6 +688,11 @@ "DuplicatePageDeleted": "Duplicate page deleted", "SeriesFolderDeleted": "Series folder deleted" }, + "media_profile": { + "DIVINA": "DIVINA", + "EPUB": "EPUB", + "PDF": "PDF" + }, "media_status": { "ERROR": "Error", "OUTDATED": "Outdated", @@ -804,6 +809,7 @@ "in_progress": "In Progress", "language": "language", "library": "library", + "media_profile": "Media profile", "oneshot": "One-shot", "publisher": "publisher", "read": "Read", diff --git a/komga-webui/src/types/enum-books.ts b/komga-webui/src/types/enum-books.ts index b509c53a1bd..7e280f65319 100644 --- a/komga-webui/src/types/enum-books.ts +++ b/komga-webui/src/types/enum-books.ts @@ -24,3 +24,9 @@ export enum CopyMode { COPY = 'COPY', HARDLINK = 'HARDLINK', } + +export enum MediaProfile { + DIVINA = 'DIVINA', + PDF = 'PDF', + EPUB = 'EPUB', +} diff --git a/komga-webui/src/types/komga-search.ts b/komga-webui/src/types/komga-search.ts index e7dc4a619f0..e923e55e224 100644 --- a/komga-webui/src/types/komga-search.ts +++ b/komga-webui/src/types/komga-search.ts @@ -86,6 +86,14 @@ export class SearchConditionMediaStatus implements SearchConditionBook { } } +export class SearchConditionMediaProfile implements SearchConditionBook { + mediaProfile: SearchOperatorEquality + + constructor(op: SearchOperatorEquality) { + this.mediaProfile = op + } +} + export class SearchConditionGenre implements SearchConditionSeries { genre: SearchOperatorEquality diff --git a/komga-webui/src/views/BrowseSeries.vue b/komga-webui/src/views/BrowseSeries.vue index 4b71b5f05bb..56a24b5fd02 100644 --- a/komga-webui/src/views/BrowseSeries.vue +++ b/komga-webui/src/views/BrowseSeries.vue @@ -488,7 +488,7 @@ import SeriesActionsMenu from '@/components/menus/SeriesActionsMenu.vue' import PageSizeSelect from '@/components/PageSizeSelect.vue' import {parseQuerySort} from '@/functions/query-params' import {seriesFileUrl, seriesThumbnailUrl} from '@/functions/urls' -import {ReadStatus} from '@/types/enum-books' +import {MediaProfile, ReadStatus} from '@/types/enum-books' import { BOOK_ADDED, BOOK_CHANGED, @@ -535,6 +535,7 @@ import { SearchConditionBook, SearchConditionGenre, SearchConditionLanguage, + SearchConditionMediaProfile, SearchConditionPublisher, SearchConditionReadStatus, SearchConditionSeriesId, @@ -544,6 +545,7 @@ import { SearchOperatorIsNot, } from '@/types/komga-search' import {objIsEqual} from '@/functions/object' +import i18n from '@/i18n' const tags = require('language-tags') @@ -596,6 +598,7 @@ export default Vue.extend({ drawer: false, filterOptions: { tag: [] as NameValue[], + mediaProfile: [] as NameValue[], }, } }, @@ -642,6 +645,13 @@ export default Vue.extend({ filterOptionsPanel(): FiltersOptions { const r = { tag: {name: this.$t('filter.tag').toString(), values: this.filterOptions.tag, anyAllSelector: true}, + mediaProfile: { + name: this.$t('filter.media_profile').toString(), values: Object.values(MediaProfile).map(x => ({ + name: i18n.t(`enums.media_profile.${x}`), + value: new SearchConditionMediaProfile(new SearchOperatorIs(x)), + nValue: new SearchConditionMediaProfile(new SearchOperatorIsNot(x)), + } as NameValue)), + }, } as FiltersOptions authorRoles.forEach((role: string) => { r[role] = { @@ -807,10 +817,11 @@ export default Vue.extend({ // get filter from query params and validate with available filter values let activeFilters = {} as FiltersActive - if (route.query.readStatus || route.query.tag || authorRoles.some(role => role in route.query)) { + if (route.query.readStatus || route.query.tag || route.query.mediaProfile || authorRoles.some(role => role in route.query)) { activeFilters = { readStatus: route.query.readStatus || [], tag: route.query.tag || [], + mediaProfile: route.query.mediaProfile || [], } authorRoles.forEach((role: string) => { activeFilters[role] = route.query[role] || [] @@ -829,6 +840,7 @@ export default Vue.extend({ const validFilter = { readStatus: this.$_.intersectionWith(filters.readStatus, extractFilterOptionsValues(this.filterOptionsList.readStatus.values), objIsEqual) || [], tag: this.$_.intersectionWith(filters.tag, extractFilterOptionsValues(this.filterOptions.tag), objIsEqual) || [], + mediaProfile: this.$_.intersectionWith(filters.mediaProfile, extractFilterOptionsValues(this.filterOptionsPanel.mediaProfile.values), objIsEqual) || [], } as any authorRoles.forEach((role: string) => { validFilter[role] = filters[role] || [] @@ -977,6 +989,7 @@ export default Vue.extend({ conditions.push(new SearchConditionSeriesId(new SearchOperatorIs(seriesId))) if (this.filters.readStatus && this.filters.readStatus.length > 0) conditions.push(new SearchConditionAnyOfBook(this.filters.readStatus)) if (this.filters.tag && this.filters.tag.length > 0) this.filtersMode?.tag?.allOf ? conditions.push(new SearchConditionAllOfBook(this.filters.tag)) : conditions.push(new SearchConditionAnyOfBook(this.filters.tag)) + if (this.filters.mediaProfile && this.filters.mediaProfile.length > 0) this.filtersMode?.mediaProfile?.allOf ? conditions.push(new SearchConditionAllOfBook(this.filters.mediaProfile)) : conditions.push(new SearchConditionAnyOfBook(this.filters.mediaProfile)) authorRoles.forEach((role: string) => { if (role in this.filters) { const authorConditions = this.filters[role].map((name: string) => new SearchConditionAuthor(new SearchOperatorIs({