feat(webui): add unavailable condition in series and books filters

Closes: #1580
This commit is contained in:
Gauthier Roebroeck 2025-02-20 17:50:10 +08:00
parent 87d73cc207
commit 1b8fa45ef2
5 changed files with 54 additions and 4 deletions

View file

@ -1,4 +1,5 @@
import {SearchConditionSeries} from '@/types/komga-search'
import {FiltersActive, NameValue} from '@/types/filter'
export function sortOrFilterActive(sortActive: SortActive, sortDefault: SortActive, filters: FiltersActive): boolean {
const sortCustom = sortActive.key !== sortDefault.key || sortActive.order !== sortDefault.order

View file

@ -190,6 +190,14 @@ export class SearchConditionTitleSort implements SearchConditionSeries {
}
}
export class SearchConditionDeleted implements SearchConditionBook, SearchConditionSeries {
deleted: SearchOperatorBoolean
constructor(op: SearchOperatorBoolean) {
this.deleted = op
}
}
export interface AuthorMatch {
name?: string,
role?: string

View file

@ -158,6 +158,7 @@ import {
SearchConditionAllOfBook,
SearchConditionAnyOfBook,
SearchConditionAuthor,
SearchConditionDeleted,
SearchConditionLibraryId,
SearchConditionMediaProfile,
SearchConditionOneShot,
@ -348,6 +349,15 @@ export default Vue.extend({
nValue: new SearchConditionOneShot(new SearchOperatorIsFalse()),
}],
},
deleted: {
values: [
{
name: this.$t('common.unavailable').toString(),
value: new SearchConditionDeleted(new SearchOperatorIsTrue()),
nValue: new SearchConditionDeleted(new SearchOperatorIsFalse()),
},
],
},
} as FiltersOptions
},
filterOptionsPanel(): FiltersOptions {
@ -436,11 +446,12 @@ export default Vue.extend({
// get filter from query params or local storage and validate with available filter values
let activeFilters: any
if (route.query.readStatus || route.query.tag || authorRoles.some(role => role in route.query) || route.query.oneshot) {
if (route.query.readStatus || route.query.tag || authorRoles.some(role => role in route.query) || route.query.oneshot || route.query.deleted) {
activeFilters = {
readStatus: route.query.readStatus || [],
tag: route.query.tag || [],
oneshot: route.query.oneshot || [],
deleted: route.query.deleted || [],
}
authorRoles.forEach((role: string) => {
activeFilters[role] = route.query[role] || []
@ -471,6 +482,7 @@ export default Vue.extend({
readStatus: this.$_.intersectionWith(filters.readStatus, extractFilterOptionsValues(this.filterOptionsList.readStatus.values), objIsEqual) || [],
tag: this.$_.intersectionWith(filters.tag, extractFilterOptionsValues(this.filterOptions.tag), objIsEqual) || [],
oneshot: this.$_.intersectionWith(filters.oneshot, extractFilterOptionsValues(this.filterOptionsList.oneshot.values), objIsEqual) || [],
deleted: this.$_.intersectionWith(filters.deleted, extractFilterOptionsValues(this.filterOptionsList.deleted.values), objIsEqual) || [],
} as any
authorRoles.forEach((role: string) => {
validFilter[role] = filters[role] || []
@ -581,6 +593,7 @@ export default Vue.extend({
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.oneshot && this.filters.oneshot.length > 0) conditions.push(...this.filters.oneshot)
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))
if (this.filters.deleted && this.filters.deleted.length > 0) conditions.push(...this.filters.deleted)
authorRoles.forEach((role: string) => {
if (role in this.filters) {
const authorConditions = this.filters[role].map((name: string) => {

View file

@ -172,6 +172,7 @@ import {
SearchConditionAnyOfSeries,
SearchConditionAuthor,
SearchConditionComplete,
SearchConditionDeleted,
SearchConditionGenre,
SearchConditionLanguage,
SearchConditionLibraryId,
@ -414,6 +415,15 @@ export default Vue.extend({
nValue: new SearchConditionOneShot(new SearchOperatorIsFalse()),
}],
},
deleted: {
values: [
{
name: this.$t('common.unavailable').toString(),
value: new SearchConditionDeleted(new SearchOperatorIsTrue()),
nValue: new SearchConditionDeleted(new SearchOperatorIsFalse()),
},
],
},
} as FiltersOptions
},
filterOptionsPanel(): FiltersOptions {
@ -435,7 +445,7 @@ export default Vue.extend({
},
...this.filterOptions.genre,
],
anyAllSelector: true
anyAllSelector: true,
},
tag: {
name: this.$t('filter.tag').toString(),
@ -610,7 +620,7 @@ export default Vue.extend({
// get filter from query params or local storage and validate with available filter values
let activeFilters: any
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) || route.query.complete || route.query.oneshot || route.query.sharingLabel) {
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) || route.query.complete || route.query.oneshot || route.query.sharingLabel || route.query.deleted) {
activeFilters = {
status: route.query.status || [],
readStatus: route.query.readStatus || [],
@ -623,6 +633,7 @@ export default Vue.extend({
complete: route.query.complete || [],
oneshot: route.query.oneshot || [],
sharingLabel: route.query.sharingLabel || [],
deleted: route.query.deleted || [],
}
authorRoles.forEach((role: string) => {
activeFilters[role] = route.query[role] || []
@ -661,6 +672,7 @@ export default Vue.extend({
complete: this.$_.intersectionWith(filters.complete, extractFilterOptionsValues(this.filterOptionsList.complete.values), objIsEqual) || [],
oneshot: this.$_.intersectionWith(filters.oneshot, extractFilterOptionsValues(this.filterOptionsList.oneshot.values), objIsEqual) || [],
sharingLabel: this.$_.intersectionWith(filters.sharingLabel, extractFilterOptionsValues(this.filterOptions.sharingLabel), objIsEqual) || [],
deleted: this.$_.intersectionWith(filters.deleted, extractFilterOptionsValues(this.filterOptionsList.deleted.values), objIsEqual) || [],
} as any
authorRoles.forEach((role: string) => {
validFilter[role] = filters[role] || []
@ -781,6 +793,7 @@ export default Vue.extend({
if (this.filters.sharingLabel && this.filters.sharingLabel.length > 0) this.filtersMode?.sharingLabel?.allOf ? conditions.push(new SearchConditionAllOfSeries(this.filters.sharingLabel)) : conditions.push(new SearchConditionAnyOfSeries(this.filters.sharingLabel))
if (this.filters.complete && this.filters.complete.length > 0) conditions.push(...this.filters.complete)
if (this.filters.oneshot && this.filters.oneshot.length > 0) conditions.push(...this.filters.oneshot)
if (this.filters.deleted && this.filters.deleted.length > 0) conditions.push(...this.filters.deleted)
authorRoles.forEach((role: string) => {
if (role in this.filters) {
const authorConditions = this.filters[role].map((name: string) => {

View file

@ -555,6 +555,7 @@ import {
SearchConditionAnyOfBook,
SearchConditionAuthor,
SearchConditionBook,
SearchConditionDeleted,
SearchConditionGenre,
SearchConditionLanguage,
SearchConditionMediaProfile,
@ -564,9 +565,11 @@ import {
SearchConditionSeriesStatus,
SearchConditionTag,
SearchOperatorIs,
SearchOperatorIsFalse,
SearchOperatorIsNot,
SearchOperatorIsNotNull,
SearchOperatorIsNull,
SearchOperatorIsTrue,
} from '@/types/komga-search'
import {objIsEqual} from '@/functions/object'
import i18n from '@/i18n'
@ -679,6 +682,15 @@ export default Vue.extend({
},
],
},
deleted: {
values: [
{
name: this.$t('common.unavailable').toString(),
value: new SearchConditionDeleted(new SearchOperatorIsTrue()),
nValue: new SearchConditionDeleted(new SearchOperatorIsFalse()),
},
],
},
} as FiltersOptions
},
filterOptionsPanel(): FiltersOptions {
@ -874,11 +886,12 @@ 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 || route.query.mediaProfile || authorRoles.some(role => role in route.query)) {
if (route.query.readStatus || route.query.tag || route.query.mediaProfile || authorRoles.some(role => role in route.query) || route.query.deleted) {
activeFilters = {
readStatus: route.query.readStatus || [],
tag: route.query.tag || [],
mediaProfile: route.query.mediaProfile || [],
deleted: route.query.deleted || [],
}
authorRoles.forEach((role: string) => {
activeFilters[role] = route.query[role] || []
@ -898,6 +911,7 @@ export default Vue.extend({
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) || [],
deleted: this.$_.intersectionWith(filters.deleted, extractFilterOptionsValues(this.filterOptionsList.deleted.values), objIsEqual) || [],
} as any
authorRoles.forEach((role: string) => {
validFilter[role] = filters[role] || []
@ -1039,6 +1053,7 @@ export default Vue.extend({
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))
if (this.filters.deleted && this.filters.deleted.length > 0) conditions.push(...this.filters.deleted)
authorRoles.forEach((role: string) => {
if (role in this.filters) {
const authorConditions = this.filters[role].map((name: string) => {