mirror of
https://github.com/gotson/komga.git
synced 2026-05-08 21:00:16 +02:00
add missing series filter
This commit is contained in:
parent
d7b93d51f6
commit
380beec6a4
8 changed files with 162 additions and 10 deletions
5
next-ui/src/components.d.ts
vendored
5
next-ui/src/components.d.ts
vendored
|
|
@ -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']
|
||||
|
|
|
|||
47
next-ui/src/components/filter/IncludeExclude.vue
Normal file
47
next-ui/src/components/filter/IncludeExclude.vue
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
<template>
|
||||
<FilterTriState
|
||||
:model-value="effectiveModel"
|
||||
:label="label"
|
||||
@change="(newValue) => handleChange(newValue)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { type FilterIncludeExclude } from '@/types/filter'
|
||||
import type { IncludeExclude } from '@/components/filter/TriState.vue'
|
||||
|
||||
const model = defineModel<FilterIncludeExclude>({ required: true })
|
||||
|
||||
const { label } = defineProps<{
|
||||
label: string
|
||||
}>()
|
||||
|
||||
const effectiveModel = computed<IncludeExclude>(() => {
|
||||
switch (model.value.i) {
|
||||
case 'i':
|
||||
return 'include'
|
||||
case 'e':
|
||||
return 'exclude'
|
||||
default:
|
||||
return undefined
|
||||
}
|
||||
})
|
||||
|
||||
function handleChange(newVal: IncludeExclude) {
|
||||
switch (newVal) {
|
||||
case 'include':
|
||||
model.value = { i: 'i' }
|
||||
break
|
||||
case 'exclude':
|
||||
model.value = { i: 'e' }
|
||||
break
|
||||
default:
|
||||
model.value = { i: undefined }
|
||||
break
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts"></script>
|
||||
|
||||
<style scoped></style>
|
||||
22
next-ui/src/components/filter/by/Complete.vue
Normal file
22
next-ui/src/components/filter/by/Complete.vue
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<template>
|
||||
<FilterIncludeExclude
|
||||
v-model="model"
|
||||
:label="
|
||||
$formatMessage({
|
||||
description: 'Filter: complete',
|
||||
defaultMessage: 'Complete',
|
||||
id: 'ZEbDUo',
|
||||
})
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { type FilterIncludeExclude } from '@/types/filter'
|
||||
|
||||
const model = defineModel<FilterIncludeExclude>({ required: true })
|
||||
</script>
|
||||
|
||||
<script lang="ts"></script>
|
||||
|
||||
<style scoped></style>
|
||||
22
next-ui/src/components/filter/by/OneShot.vue
Normal file
22
next-ui/src/components/filter/by/OneShot.vue
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<template>
|
||||
<FilterIncludeExclude
|
||||
v-model="model"
|
||||
:label="
|
||||
$formatMessage({
|
||||
description: 'Filter: one-shot',
|
||||
defaultMessage: 'One-shot',
|
||||
id: '4uqafX',
|
||||
})
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { type FilterIncludeExclude } from '@/types/filter'
|
||||
|
||||
const model = defineModel<FilterIncludeExclude>({ required: true })
|
||||
</script>
|
||||
|
||||
<script lang="ts"></script>
|
||||
|
||||
<style scoped></style>
|
||||
22
next-ui/src/components/filter/by/Unavailable.vue
Normal file
22
next-ui/src/components/filter/by/Unavailable.vue
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<template>
|
||||
<FilterIncludeExclude
|
||||
v-model="model"
|
||||
:label="
|
||||
$formatMessage({
|
||||
description: 'Filter: unavailable',
|
||||
defaultMessage: 'Unavailable',
|
||||
id: '+pyj1u',
|
||||
})
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { type FilterIncludeExclude } from '@/types/filter'
|
||||
|
||||
const model = defineModel<FilterIncludeExclude>({ required: true })
|
||||
</script>
|
||||
|
||||
<script lang="ts"></script>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -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<typeof SchemaFilterStrings>,
|
||||
key: string,
|
||||
|
|
|
|||
|
|
@ -59,6 +59,17 @@
|
|||
</div>
|
||||
</v-list-subheader>
|
||||
|
||||
<FilterByReadStatus
|
||||
v-model="filterReadStatus.v"
|
||||
class="py-0"
|
||||
/>
|
||||
|
||||
<v-list class="py-0">
|
||||
<FilterByOneShot v-model="filterOneShot" />
|
||||
<FilterByComplete v-model="filterComplete" />
|
||||
<FilterByUnavailable v-model="filterUnavailable" />
|
||||
</v-list>
|
||||
|
||||
<v-expansion-panels
|
||||
v-model="filterExpansionPanels"
|
||||
variant="accordion"
|
||||
|
|
@ -66,13 +77,6 @@
|
|||
flat
|
||||
tile
|
||||
>
|
||||
<FilterExpansionPanel
|
||||
:title="$formatMessage(commonMessages.filterPanelReadStatus)"
|
||||
:count="filterReadStatus.v.length"
|
||||
@clear="clearFilter(filterReadStatus)"
|
||||
>
|
||||
<FilterByReadStatus v-model="filterReadStatus.v" />
|
||||
</FilterExpansionPanel>
|
||||
<FilterExpansionPanel
|
||||
:title="$formatMessage(commonMessages.filterPanelSeriesStatus)"
|
||||
:count="filterSeriesStatus.v.length"
|
||||
|
|
@ -188,7 +192,6 @@
|
|||
<SortList
|
||||
v-model="sortActive"
|
||||
:items="sortOptions"
|
||||
color="primary"
|
||||
mandatory
|
||||
/>
|
||||
</v-list>
|
||||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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<typeof SchemaFilterSelectRange>
|
||||
|
||||
export type FilterIncludeExclude = v.InferOutput<typeof SchemaIncludeExclude>
|
||||
|
||||
export type FilterType = FilterTypeAnyAll | FilterTypeSimpleList
|
||||
|
|
|
|||
Loading…
Reference in a new issue