mirror of
https://github.com/gotson/komga.git
synced 2025-12-20 23:45:11 +01:00
feat(webui): search authors in filters
This commit is contained in:
parent
a45a73c8bd
commit
b908ac140b
6 changed files with 145 additions and 59 deletions
|
|
@ -3,7 +3,7 @@
|
|||
<v-expansion-panel
|
||||
v-for="(f, key) in filtersOptions"
|
||||
:key="key"
|
||||
:disabled="f.values.length === 0"
|
||||
:disabled="(f.values && f.values.length === 0) && !f.search"
|
||||
>
|
||||
<v-expansion-panel-header class="text-uppercase">
|
||||
<v-icon
|
||||
|
|
@ -16,7 +16,25 @@
|
|||
{{ f.name }}
|
||||
</v-expansion-panel-header>
|
||||
<v-expansion-panel-content class="no-padding">
|
||||
<v-list dense>
|
||||
<v-autocomplete
|
||||
v-if="f.search"
|
||||
v-model="model[key]"
|
||||
:items="items[key]"
|
||||
:search-input.sync="search[key]"
|
||||
:loading="loading[key]"
|
||||
:hide-no-data="!search[key] || loading[key]"
|
||||
@keydown.esc="search[key] = null"
|
||||
multiple
|
||||
deletable-chips
|
||||
small-chips
|
||||
dense
|
||||
solo
|
||||
/>
|
||||
|
||||
<v-list
|
||||
v-if="f.values"
|
||||
dense
|
||||
>
|
||||
<v-list-item v-for="v in f.values"
|
||||
:key="v.value"
|
||||
@click.stop="click(key, v.value)"
|
||||
|
|
@ -38,37 +56,80 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue'
|
||||
import Vue, {PropType} from 'vue'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'FilterPanels',
|
||||
data: () => {
|
||||
return {
|
||||
search: {} as any,
|
||||
model: {} as any,
|
||||
items: {} as any,
|
||||
loading: {} as any,
|
||||
}
|
||||
},
|
||||
props: {
|
||||
filtersOptions: {
|
||||
type: Object as () => FiltersOptions,
|
||||
type: Object as PropType<FiltersOptions>,
|
||||
required: true,
|
||||
},
|
||||
filtersActive: {
|
||||
type: Object as () => FiltersActive,
|
||||
type: Object as PropType<FiltersActive>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
search: {
|
||||
deep: true,
|
||||
async handler(val: any) {
|
||||
for (const prop in val) {
|
||||
if (val[prop] !== null) {
|
||||
this.loading[prop] = true
|
||||
this.$set(this.items, prop, await (this.filtersOptions[prop] as any).search(val[prop]))
|
||||
this.loading[prop] = false
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
model: {
|
||||
deep: true,
|
||||
async handler(val: any) {
|
||||
for (const prop in val) {
|
||||
if (val[prop] !== null && val[prop] !== this.filtersActive[prop]) {
|
||||
let r = this.$_.cloneDeep(this.filtersActive)
|
||||
r[prop] = this.$_.clone(val[prop])
|
||||
|
||||
this.$emit('update:filtersActive', r)
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
filtersActive: {
|
||||
deep: true,
|
||||
immediate: true,
|
||||
handler(val: any) {
|
||||
for (const prop in val) {
|
||||
if (val[prop].length > 0) {
|
||||
// we need to add existing values to items also, else v-autocomplete won't show it
|
||||
this.$set(this.items, prop, this.$_.union(this.items[prop], val[prop]))
|
||||
this.$set(this.model, prop, val[prop])
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
clear (key: string) {
|
||||
clear(key: string) {
|
||||
let r = this.$_.cloneDeep(this.filtersActive)
|
||||
r[key] = []
|
||||
|
||||
this.$emit('update:filtersActive', r)
|
||||
},
|
||||
groupActive (key: string): boolean {
|
||||
groupActive(key: string): boolean {
|
||||
if (!(key in this.filtersActive)) return false
|
||||
for (let v of this.filtersOptions[key].values) {
|
||||
if (this.filtersActive[key].includes(v.value)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
return this.filtersActive[key].length > 0;
|
||||
},
|
||||
click (key: string, value: string) {
|
||||
click(key: string, value: string) {
|
||||
let r = this.$_.cloneDeep(this.filtersActive)
|
||||
if (!(key in r)) r[key] = []
|
||||
if (r[key].includes(value)) this.$_.pull(r[key], (value))
|
||||
|
|
|
|||
|
|
@ -11,15 +11,16 @@ export default class KomgaReferentialService {
|
|||
this.http = http
|
||||
}
|
||||
|
||||
async getAuthors(search?: string, libraryId?: string, collectionId?: string, seriesId?: string): Promise<AuthorDto[]> {
|
||||
async getAuthors(search?: string, role?: string, libraryId?: string, collectionId?: string, seriesId?: string): Promise<Page<AuthorDto>> {
|
||||
try {
|
||||
const params = {} as any
|
||||
if (search) params.search = search
|
||||
if (role) params.role = role
|
||||
if (libraryId) params.library_id = libraryId
|
||||
if (collectionId) params.collection_id = collectionId
|
||||
if (seriesId) params.series_id = seriesId
|
||||
|
||||
return (await this.http.get('/api/v1/authors', {
|
||||
return (await this.http.get('/api/v2/authors', {
|
||||
params: params,
|
||||
paramsSerializer: params => qs.stringify(params, {indices: false}),
|
||||
})).data
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
interface FiltersOptions {
|
||||
[key: string]: {
|
||||
name?: string,
|
||||
values: NameValue[],
|
||||
values?: NameValue[],
|
||||
search?: (search: string) => Promise<string[]>,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -129,7 +129,6 @@ import EmptyState from '@/components/EmptyState.vue'
|
|||
import {parseQueryParam} 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({
|
||||
|
|
@ -249,8 +248,14 @@ 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, [])}
|
||||
r[role] = {
|
||||
name: this.$t(`author_roles.${role}`).toString(),
|
||||
search: async search => {
|
||||
return (await this.$komgaReferential.getAuthors(search, role, undefined, this.collectionId))
|
||||
.content
|
||||
.map(x => x.name)
|
||||
},
|
||||
}
|
||||
})
|
||||
return r
|
||||
},
|
||||
|
|
@ -276,16 +281,21 @@ export default Vue.extend({
|
|||
name: x.name,
|
||||
value: x.id,
|
||||
})))
|
||||
this.$set(this.filterOptions, 'genre', toNameValue(await this.$komgaReferential.getGenres(undefined, collectionId)))
|
||||
this.$set(this.filterOptions, 'tag', toNameValue(await this.$komgaReferential.getSeriesTags(undefined, collectionId)))
|
||||
this.$set(this.filterOptions, 'publisher', toNameValue(await this.$komgaReferential.getPublishers(undefined, collectionId)))
|
||||
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]) : [])
|
||||
})
|
||||
|
||||
const [genres, tags, publishers, languages, ageRatings, releaseDates] = await Promise.all([
|
||||
this.$komgaReferential.getGenres(undefined, collectionId),
|
||||
this.$komgaReferential.getSeriesTags(undefined, collectionId),
|
||||
this.$komgaReferential.getPublishers(undefined, collectionId),
|
||||
this.$komgaReferential.getLanguages(undefined, collectionId),
|
||||
this.$komgaReferential.getAgeRatings(undefined, collectionId),
|
||||
this.$komgaReferential.getSeriesReleaseDates(undefined, collectionId),
|
||||
])
|
||||
this.$set(this.filterOptions, 'genre', toNameValue(genres))
|
||||
this.$set(this.filterOptions, 'tag', toNameValue(tags))
|
||||
this.$set(this.filterOptions, 'publisher', toNameValue(publishers))
|
||||
this.$set(this.filterOptions, 'language', (languages))
|
||||
this.$set(this.filterOptions, 'ageRating', toNameValue(ageRatings))
|
||||
this.$set(this.filterOptions, 'releaseDate', toNameValue(releaseDates))
|
||||
|
||||
// get filter from query params or local storage and validate with available filter values
|
||||
let activeFilters: any
|
||||
|
|
@ -322,7 +332,7 @@ export default Vue.extend({
|
|||
releaseDate: filters.releaseDate?.filter(x => this.filterOptions.releaseDate.map(n => n.value).includes(x)) || [],
|
||||
} as any
|
||||
authorRoles.forEach((role: string) => {
|
||||
validFilter[role] = filters[role]?.filter(x => ((this.filterOptions as any)[role] as NameValue[]).map(n => n.value).includes(x)) || []
|
||||
validFilter[role] = filters[role] || []
|
||||
})
|
||||
return validFilter
|
||||
},
|
||||
|
|
|
|||
|
|
@ -122,7 +122,6 @@ 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";
|
||||
|
||||
|
|
@ -237,11 +236,13 @@ export default Vue.extend({
|
|||
},
|
||||
filterOptionsList(): FiltersOptions {
|
||||
return {
|
||||
readStatus: {values: [
|
||||
{name: this.$t('filter.unread').toString(), value: ReadStatus.UNREAD_AND_IN_PROGRESS},
|
||||
{name: this.$t('filter.in_progress').toString(), value: ReadStatus.IN_PROGRESS},
|
||||
{name: this.$t('filter.read').toString(), value: ReadStatus.READ},
|
||||
]},
|
||||
readStatus: {
|
||||
values: [
|
||||
{name: this.$t('filter.unread').toString(), value: ReadStatus.UNREAD_AND_IN_PROGRESS},
|
||||
{name: this.$t('filter.in_progress').toString(), value: ReadStatus.IN_PROGRESS},
|
||||
{name: this.$t('filter.read').toString(), value: ReadStatus.READ},
|
||||
],
|
||||
},
|
||||
} as FiltersOptions
|
||||
},
|
||||
filterOptionsPanel(): FiltersOptions {
|
||||
|
|
@ -262,8 +263,14 @@ 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, [])}
|
||||
r[role] = {
|
||||
name: this.$t(`author_roles.${role}`).toString(),
|
||||
search: async search => {
|
||||
return (await this.$komgaReferential.getAuthors(search, role, this.libraryId !== LIBRARIES_ALL ? this.libraryId : undefined))
|
||||
.content
|
||||
.map(x => x.name)
|
||||
},
|
||||
}
|
||||
})
|
||||
return r
|
||||
},
|
||||
|
|
@ -306,16 +313,20 @@ export default Vue.extend({
|
|||
const requestLibraryId = libraryId !== LIBRARIES_ALL ? libraryId : undefined
|
||||
|
||||
// load dynamic filters
|
||||
this.$set(this.filterOptions, 'genre', toNameValue(await this.$komgaReferential.getGenres(requestLibraryId)))
|
||||
this.$set(this.filterOptions, 'tag', toNameValue(await this.$komgaReferential.getSeriesTags(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]) : [])
|
||||
})
|
||||
const [genres, tags, publishers, languages, ageRatings, releaseDates] = await Promise.all([
|
||||
this.$komgaReferential.getGenres(requestLibraryId),
|
||||
this.$komgaReferential.getSeriesTags(requestLibraryId),
|
||||
this.$komgaReferential.getPublishers(requestLibraryId),
|
||||
this.$komgaReferential.getLanguages(requestLibraryId),
|
||||
this.$komgaReferential.getAgeRatings(requestLibraryId),
|
||||
this.$komgaReferential.getSeriesReleaseDates(requestLibraryId),
|
||||
])
|
||||
this.$set(this.filterOptions, 'genre', toNameValue(genres))
|
||||
this.$set(this.filterOptions, 'tag', toNameValue(tags))
|
||||
this.$set(this.filterOptions, 'publisher', toNameValue(publishers ))
|
||||
this.$set(this.filterOptions, 'language', (languages))
|
||||
this.$set(this.filterOptions, 'ageRating', toNameValue(ageRatings))
|
||||
this.$set(this.filterOptions, 'releaseDate', toNameValue(releaseDates))
|
||||
|
||||
// get filter from query params or local storage and validate with available filter values
|
||||
let activeFilters: any
|
||||
|
|
@ -350,7 +361,7 @@ export default Vue.extend({
|
|||
releaseDate: filters.releaseDate?.filter(x => this.filterOptions.releaseDate.map(n => n.value).includes(x)) || [],
|
||||
} as any
|
||||
authorRoles.forEach((role: string) => {
|
||||
validFilter[role] = filters[role]?.filter(x => ((this.filterOptions as any)[role] as NameValue[]).map(n => n.value).includes(x)) || []
|
||||
validFilter[role] = filters[role] || []
|
||||
})
|
||||
return validFilter
|
||||
},
|
||||
|
|
@ -452,7 +463,7 @@ export default Vue.extend({
|
|||
this.totalElements = seriesPage.totalElements
|
||||
this.series = seriesPage.content
|
||||
},
|
||||
getLibraryLazy (libraryId: string): LibraryDto | undefined {
|
||||
getLibraryLazy(libraryId: string): LibraryDto | undefined {
|
||||
if (libraryId !== LIBRARIES_ALL) {
|
||||
return this.$store.getters.getLibraryById(libraryId)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -378,7 +378,7 @@ import ItemBrowser from '@/components/ItemBrowser.vue'
|
|||
import ItemCard from '@/components/ItemCard.vue'
|
||||
import SeriesActionsMenu from '@/components/menus/SeriesActionsMenu.vue'
|
||||
import PageSizeSelect from '@/components/PageSizeSelect.vue'
|
||||
import {parseQueryParamAndFilter, parseQuerySort} from '@/functions/query-params'
|
||||
import {parseQueryParam, parseQueryParamAndFilter, parseQuerySort} from '@/functions/query-params'
|
||||
import {seriesFileUrl, seriesThumbnailUrl} from '@/functions/urls'
|
||||
import {ReadStatus, replaceCompositeReadStatus} from '@/types/enum-books'
|
||||
import {BOOK_CHANGED, LIBRARY_DELETED, READLIST_CHANGED, SERIES_CHANGED} from '@/types/events'
|
||||
|
|
@ -468,8 +468,14 @@ export default Vue.extend({
|
|||
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, [])}
|
||||
r[role] = {
|
||||
name: this.$t(`author_roles.${role}`).toString(),
|
||||
search: async search => {
|
||||
return (await this.$komgaReferential.getAuthors(search, role, undefined, undefined, this.seriesId))
|
||||
.content
|
||||
.map(x => x.name)
|
||||
},
|
||||
}
|
||||
})
|
||||
return r
|
||||
},
|
||||
|
|
@ -592,17 +598,13 @@ export default Vue.extend({
|
|||
|
||||
// load dynamic filters
|
||||
this.$set(this.filterOptions, 'tag', toNameValue(await this.$komgaReferential.getBookTags(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', parseQueryParamAndFilter(this.$route.query.readStatus, Object.keys(ReadStatus)))
|
||||
this.$set(this.filters, 'tag', parseQueryParamAndFilter(this.$route.query.tag, this.filterOptions.tag.map(x => x.value)))
|
||||
authorRoles.forEach((role: string) => {
|
||||
//@ts-ignore
|
||||
this.$set(this.filters, role, parseQueryParamAndFilter(route.query[role], this.filterOptions[role].map((x: NameValue) => x.value)))
|
||||
this.$set(this.filters, role, parseQueryParam(route.query[role]))
|
||||
})
|
||||
},
|
||||
setWatches() {
|
||||
|
|
|
|||
Loading…
Reference in a new issue