mirror of
https://github.com/gotson/komga.git
synced 2026-05-08 12:35:30 +02:00
fix(webui): replace searchbox for authors in filter panel
This commit is contained in:
parent
61ca80dc72
commit
14e6718252
2 changed files with 122 additions and 60 deletions
|
|
@ -16,20 +16,30 @@
|
||||||
{{ f.name }}
|
{{ f.name }}
|
||||||
</v-expansion-panel-header>
|
</v-expansion-panel-header>
|
||||||
<v-expansion-panel-content class="no-padding">
|
<v-expansion-panel-content class="no-padding">
|
||||||
<v-autocomplete
|
<search-box-base
|
||||||
|
v-if="f.search"
|
||||||
|
:search-function="f.search"
|
||||||
|
@selected="click(key, $event)"
|
||||||
|
>
|
||||||
|
<template v-slot:item="data">
|
||||||
|
<v-list-item-content class="text-body-2">{{ data.item }}</v-list-item-content>
|
||||||
|
</template>
|
||||||
|
</search-box-base>
|
||||||
|
|
||||||
|
<v-list
|
||||||
v-if="f.search"
|
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
|
dense
|
||||||
solo
|
>
|
||||||
/>
|
<v-list-item v-for="(v, i) in filtersActive[key]"
|
||||||
|
:key="i"
|
||||||
|
@click.stop="click(key, v)"
|
||||||
|
>
|
||||||
|
<v-list-item-icon>
|
||||||
|
<v-icon color="secondary">mdi-checkbox-marked</v-icon>
|
||||||
|
</v-list-item-icon>
|
||||||
|
<v-list-item-title>{{ v }}</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
|
||||||
<v-list
|
<v-list
|
||||||
v-if="f.values"
|
v-if="f.values"
|
||||||
|
|
@ -57,17 +67,11 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue, {PropType} from 'vue'
|
import Vue, {PropType} from 'vue'
|
||||||
|
import SearchBoxBase from "@/components/SearchBoxBase.vue";
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
name: 'FilterPanels',
|
name: 'FilterPanels',
|
||||||
data: () => {
|
components: {SearchBoxBase},
|
||||||
return {
|
|
||||||
search: {} as any,
|
|
||||||
model: {} as any,
|
|
||||||
items: {} as any,
|
|
||||||
loading: {} as any,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
props: {
|
props: {
|
||||||
filtersOptions: {
|
filtersOptions: {
|
||||||
type: Object as PropType<FiltersOptions>,
|
type: Object as PropType<FiltersOptions>,
|
||||||
|
|
@ -78,46 +82,6 @@ export default Vue.extend({
|
||||||
required: true,
|
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: {
|
methods: {
|
||||||
clear(key: string) {
|
clear(key: string) {
|
||||||
let r = this.$_.cloneDeep(this.filtersActive)
|
let r = this.$_.cloneDeep(this.filtersActive)
|
||||||
|
|
|
||||||
98
komga-webui/src/components/SearchBoxBase.vue
Normal file
98
komga-webui/src/components/SearchBoxBase.vue
Normal file
|
|
@ -0,0 +1,98 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<v-autocomplete
|
||||||
|
v-model="selectedItem"
|
||||||
|
:placeholder="$t('search.search')"
|
||||||
|
:no-data-text="$t('searchbox.no_results')"
|
||||||
|
:loading="loading"
|
||||||
|
:items="results"
|
||||||
|
:hide-no-data="!showResults"
|
||||||
|
clearable
|
||||||
|
solo
|
||||||
|
hide-details
|
||||||
|
no-filter
|
||||||
|
dense
|
||||||
|
append-icon=""
|
||||||
|
auto-select-first
|
||||||
|
:search-input.sync="search"
|
||||||
|
@keydown.esc="clear"
|
||||||
|
ref="searchbox"
|
||||||
|
>
|
||||||
|
<template v-slot:selection>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-slot:item="data">
|
||||||
|
<slot name="item" v-bind:item="data.item"></slot>
|
||||||
|
</template>
|
||||||
|
</v-autocomplete>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import {debounce} from 'lodash'
|
||||||
|
import Vue, {PropType} from 'vue'
|
||||||
|
import {BookDto} from '@/types/komga-books'
|
||||||
|
import {SeriesDto} from "@/types/komga-series";
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
name: 'SearchBoxBase',
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
selectedItem: null as unknown as any,
|
||||||
|
search: null,
|
||||||
|
showResults: false,
|
||||||
|
loading: false,
|
||||||
|
results: [],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
selectedItem(val, old) {
|
||||||
|
if (val) {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.selectedItem = undefined
|
||||||
|
this.clear()
|
||||||
|
})
|
||||||
|
|
||||||
|
this.$emit('selected', val)
|
||||||
|
|
||||||
|
//@ts-ignore
|
||||||
|
this.$refs.searchbox.blur()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
search(val) {
|
||||||
|
this.searchItems(val)
|
||||||
|
},
|
||||||
|
showResults(val) {
|
||||||
|
!val && this.clear()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
searchFunction: {
|
||||||
|
type: Function as PropType<Function>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getLibraryName(item: BookDto | SeriesDto): string {
|
||||||
|
return this.$store.getters.getLibraryById(item.libraryId).name;
|
||||||
|
},
|
||||||
|
searchItems: debounce(async function (this: any, query: string) {
|
||||||
|
if (query) {
|
||||||
|
this.loading = true
|
||||||
|
|
||||||
|
this.results = await this.searchFunction.apply(this, [query])
|
||||||
|
|
||||||
|
this.showResults = true
|
||||||
|
this.loading = false
|
||||||
|
} else {
|
||||||
|
this.clear()
|
||||||
|
}
|
||||||
|
}, 500),
|
||||||
|
clear() {
|
||||||
|
this.search = null
|
||||||
|
this.results = []
|
||||||
|
this.showResults = false
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
Loading…
Reference in a new issue