From 380beec6a479ad9ed92a1e485849331023777b1d Mon Sep 17 00:00:00 2001 From: Gauthier Roebroeck Date: Wed, 25 Mar 2026 14:39:10 +0800 Subject: [PATCH] add missing series filter --- next-ui/src/components.d.ts | 5 ++ .../src/components/filter/IncludeExclude.vue | 47 +++++++++++++++++++ next-ui/src/components/filter/by/Complete.vue | 22 +++++++++ next-ui/src/components/filter/by/OneShot.vue | 22 +++++++++ .../src/components/filter/by/Unavailable.vue | 22 +++++++++ next-ui/src/functions/filter.ts | 10 ++++ next-ui/src/pages/libraries/[id]/series.vue | 40 ++++++++++++---- next-ui/src/types/filter.ts | 4 +- 8 files changed, 162 insertions(+), 10 deletions(-) create mode 100644 next-ui/src/components/filter/IncludeExclude.vue create mode 100644 next-ui/src/components/filter/by/Complete.vue create mode 100644 next-ui/src/components/filter/by/OneShot.vue create mode 100644 next-ui/src/components/filter/by/Unavailable.vue diff --git a/next-ui/src/components.d.ts b/next-ui/src/components.d.ts index 39ff74c2..b3eb1788 100644 --- a/next-ui/src/components.d.ts +++ b/next-ui/src/components.d.ts @@ -37,15 +37,20 @@ declare module 'vue' { FilterAnyAll: typeof import('./components/filter/AnyAll.vue')['default'] FilterByAgeRating: typeof import('./components/filter/by/AgeRating.vue')['default'] FilterByAuthor: typeof import('./components/filter/by/Author.vue')['default'] + FilterByComplete: typeof import('./components/filter/by/Complete.vue')['default'] FilterByGenre: typeof import('./components/filter/by/Genre.vue')['default'] + FilterByIncludeExclude: typeof import('./components/filter/by/IncludeExclude.vue')['default'] FilterByLanguage: typeof import('./components/filter/by/Language.vue')['default'] + FilterByOneShot: typeof import('./components/filter/by/OneShot.vue')['default'] FilterByPublisher: typeof import('./components/filter/by/Publisher.vue')['default'] FilterByReadStatus: typeof import('./components/filter/by/ReadStatus.vue')['default'] FilterByReleaseYear: typeof import('./components/filter/by/ReleaseYear.vue')['default'] FilterBySeriesStatus: typeof import('./components/filter/by/SeriesStatus.vue')['default'] FilterBySharingLabel: typeof import('./components/filter/by/SharingLabel.vue')['default'] FilterByTag: typeof import('./components/filter/by/Tag.vue')['default'] + FilterByUnavailable: typeof import('./components/filter/by/Unavailable.vue')['default'] FilterExpansionPanel: typeof import('./components/filter/ExpansionPanel.vue')['default'] + FilterIncludeExclude: typeof import('./components/filter/IncludeExclude.vue')['default'] FilterList: typeof import('./components/filter/List.vue')['default'] FilterSearchList: typeof import('./components/filter/SearchList.vue')['default'] FilterSelectRange: typeof import('./components/filter/SelectRange.vue')['default'] diff --git a/next-ui/src/components/filter/IncludeExclude.vue b/next-ui/src/components/filter/IncludeExclude.vue new file mode 100644 index 00000000..50f77500 --- /dev/null +++ b/next-ui/src/components/filter/IncludeExclude.vue @@ -0,0 +1,47 @@ + + + + + + + diff --git a/next-ui/src/components/filter/by/Complete.vue b/next-ui/src/components/filter/by/Complete.vue new file mode 100644 index 00000000..4769ae02 --- /dev/null +++ b/next-ui/src/components/filter/by/Complete.vue @@ -0,0 +1,22 @@ + + + + + + + diff --git a/next-ui/src/components/filter/by/OneShot.vue b/next-ui/src/components/filter/by/OneShot.vue new file mode 100644 index 00000000..8fcc50dc --- /dev/null +++ b/next-ui/src/components/filter/by/OneShot.vue @@ -0,0 +1,22 @@ + + + + + + + diff --git a/next-ui/src/components/filter/by/Unavailable.vue b/next-ui/src/components/filter/by/Unavailable.vue new file mode 100644 index 00000000..0bbda471 --- /dev/null +++ b/next-ui/src/components/filter/by/Unavailable.vue @@ -0,0 +1,22 @@ + + + + + + + diff --git a/next-ui/src/functions/filter.ts b/next-ui/src/functions/filter.ts index 69a37a60..1ceaa142 100644 --- a/next-ui/src/functions/filter.ts +++ b/next-ui/src/functions/filter.ts @@ -1,4 +1,5 @@ import { + type FilterIncludeExclude, SchemaAnyNone, SchemaFilterAuthors, SchemaFilterReadStatus, @@ -81,6 +82,15 @@ export function schemaFilterReadStatusToConditions( } } +export function schemaFilterIncludeExcludeToConditions(filter: FilterIncludeExclude, key: string) { + if (!filter.i) return null + return { + [key]: { + operator: filter.i === 'i' ? 'isTrue' : 'isFalse', + }, + } +} + export function schemaFilterStringToConditions( filter: InferOutput, key: string, diff --git a/next-ui/src/pages/libraries/[id]/series.vue b/next-ui/src/pages/libraries/[id]/series.vue index 2111948e..7e28918b 100644 --- a/next-ui/src/pages/libraries/[id]/series.vue +++ b/next-ui/src/pages/libraries/[id]/series.vue @@ -59,6 +59,17 @@ + + + + + + + + - - - @@ -265,7 +268,7 @@ import { useInfiniteQuery, useQuery } from '@pinia/colada' import { seriesListQuery } from '@/colada/series' import type { components } from '@/generated/openapi/komga' -import { PageRequest } from '@/types/PageRequest' +import { PageRequest, sortToString } from '@/types/PageRequest' import { useGetLibrariesById } from '@/composables/libraries' import { useAppStore } from '@/stores/app' import { useItemsPerPage, usePagination } from '@/composables/pagination' @@ -280,15 +283,18 @@ import { schemaFilterReleaseYearToConditions, schemaFilterAgeRatingToConditions, schemaFilterReadStatusToConditions, + schemaFilterIncludeExcludeToConditions, } from '@/functions/filter' import * as v from 'valibot' import { + type FilterIncludeExclude, type FilterType, type FilterTypeSelectRange, SchemaFilterAuthors, SchemaFilterReadStatus, SchemaFilterSeriesStatus, SchemaFilterStrings, + SchemaIncludeExclude, SchemaSeriesAgeRatings, SchemaSeriesReleaseYears, } from '@/types/filter' @@ -356,6 +362,10 @@ function clearFilterSelectRange(filter: FilterTypeSelectRange) { filter.max = undefined } +function clearFilterSolo(filter: FilterIncludeExclude) { + filter.i = undefined +} + function clearFilters() { clearFilter(filterSeriesStatus.value) clearFilter(filterReadStatus.value) @@ -364,6 +374,9 @@ function clearFilters() { clearFilter(filterPublisher.value) clearFilter(filterSharingLabel.value) clearFilter(filterLanguage.value) + clearFilterSolo(filterComplete.value) + clearFilterSolo(filterUnavailable.value) + clearFilterSolo(filterOneShot.value) clearFilterSelectRange(filterReleaseYear.value) clearFilterSelectRange(filterAgeRating.value) Object.entries(filterAuthors).map(([, filter]) => clearFilter(filter.filter)) @@ -371,6 +384,9 @@ function clearFilters() { const filterCount = computed( () => + (!!filterComplete.value.i ? 1 : 0) + + (!!filterUnavailable.value.i ? 1 : 0) + + (!!filterOneShot.value.i ? 1 : 0) + filterReadStatus.value.v.length + filterSeriesStatus.value.v.length + filterGenre.value.v.length + @@ -394,6 +410,9 @@ const { data: filterSharingLabel } = useRouteQuerySchema('sharingLabel', SchemaF const { data: filterLanguage } = useRouteQuerySchema('language', SchemaFilterStrings) const { data: filterReleaseYear } = useRouteQuerySchema('year', SchemaSeriesReleaseYears) const { data: filterAgeRating } = useRouteQuerySchema('age', SchemaSeriesAgeRatings) +const { data: filterComplete } = useRouteQuerySchema('complete', SchemaIncludeExclude) +const { data: filterUnavailable } = useRouteQuerySchema('unavailable', SchemaIncludeExclude) +const { data: filterOneShot } = useRouteQuerySchema('oneshot', SchemaIncludeExclude) const { convertSortOptionDescriptor } = useIntlFormatter() const sortActive = appStore.getSortActive(viewName.value, [ @@ -404,6 +423,9 @@ const sortOptions = sortSeries.map((it) => convertSortOptionDescriptor(it)) const conds = computed(() => ({ allOf: [ librariesCondition.value as components['schemas']['AnyOfSeries'], + schemaFilterIncludeExcludeToConditions(filterComplete.value, 'complete'), + schemaFilterIncludeExcludeToConditions(filterUnavailable.value, 'deleted'), + schemaFilterIncludeExcludeToConditions(filterOneShot.value, 'oneShot'), schemaFilterSeriesStatusToConditions(filterSeriesStatus.value), schemaFilterReadStatusToConditions(filterReadStatus.value), schemaFilterStringToConditions(filterGenre.value, 'genre', true), diff --git a/next-ui/src/types/filter.ts b/next-ui/src/types/filter.ts index b93d946f..ac1cdbc8 100644 --- a/next-ui/src/types/filter.ts +++ b/next-ui/src/types/filter.ts @@ -41,7 +41,7 @@ export const SchemaAnyAll = v.strictObject({ m: v.optional(v.picklist(['anyOf', 'allOf']), 'anyOf'), }) -const SchemaIncludeExclude = v.strictObject({ +export const SchemaIncludeExclude = v.strictObject({ /** * Shorthand for `include`. * @@ -153,4 +153,6 @@ export const SchemaFilterSelectRange = createSchemaFilterSelectRange( ) export type FilterTypeSelectRange = v.InferOutput +export type FilterIncludeExclude = v.InferOutput + export type FilterType = FilterTypeAnyAll | FilterTypeSimpleList