mirror of
https://github.com/gotson/komga.git
synced 2025-12-31 21:03:38 +01:00
perf(webui): reduce amount of API requests when matching cbl
This commit is contained in:
parent
2461c835ad
commit
e3d9cb7fd5
3 changed files with 106 additions and 101 deletions
|
|
@ -1,32 +1,32 @@
|
|||
<template>
|
||||
<tr v-if="match">
|
||||
<tr v-if="request">
|
||||
<slot/>
|
||||
<td>
|
||||
<div v-for="s in match.request.series" :key="s">{{ s }}</div>
|
||||
<div v-for="s in request.series" :key="s">{{ s }}</div>
|
||||
</td>
|
||||
<td>{{ match.request.number }}</td>
|
||||
<td>{{ request.number }}</td>
|
||||
|
||||
<!-- Series picker -->
|
||||
<td @click="modalSeriesPicker = true" style="cursor: pointer">
|
||||
<template v-if="selectedSeries">{{ selectedSeries.metadata.title }}</template>
|
||||
<template v-if="series">{{ series.title }}</template>
|
||||
<template v-else>
|
||||
<div style="height: 2em" class="missing"></div>
|
||||
</template>
|
||||
<series-picker-dialog v-model="modalSeriesPicker" :series.sync="selectedSeries"></series-picker-dialog>
|
||||
<series-picker-dialog v-model="modalSeriesPicker" @update:series="pickedSeries"></series-picker-dialog>
|
||||
</td>
|
||||
|
||||
<!-- Book picker -->
|
||||
<td @click="selectedSeries ? modalBookPicker = true : undefined" :style="selectedSeries ? 'cursor: pointer': ''">
|
||||
<template v-if="selectedBook">
|
||||
{{ selectedBook.metadata.number }} - {{ selectedBook.metadata.title }}
|
||||
<td @click="series ? openBookPicker() : undefined" :style="series ? 'cursor: pointer': ''">
|
||||
<template v-if="book">
|
||||
{{ book.number }} - {{ book.title }}
|
||||
</template>
|
||||
<template v-else-if="selectedSeries">
|
||||
<template v-else-if="series">
|
||||
<div style="height: 2em" class="missing"></div>
|
||||
</template>
|
||||
<book-picker-dialog
|
||||
v-model="modalBookPicker"
|
||||
:books="seriesBooks"
|
||||
:book.sync="selectedBook"
|
||||
@update:book="pickedBook"
|
||||
></book-picker-dialog>
|
||||
</td>
|
||||
|
||||
|
|
@ -57,10 +57,11 @@ import Vue, {PropType} from 'vue'
|
|||
import {SeriesDto} from '@/types/komga-series'
|
||||
import {BookDto} from '@/types/komga-books'
|
||||
import SeriesPickerDialog from '@/components/dialogs/SeriesPickerDialog.vue'
|
||||
import TransientBookDetailsDialog from '@/components/dialogs/TransientBookDetailsDialog.vue'
|
||||
import TransientBookViewerDialog from '@/components/dialogs/TransientBookViewerDialog.vue'
|
||||
import FileNameChooserDialog from '@/components/dialogs/FileNameChooserDialog.vue'
|
||||
import {ReadListRequestBookMatchesDto} from '@/types/komga-readlists'
|
||||
import {
|
||||
ReadListRequestBookDto,
|
||||
ReadListRequestBookMatchBookDto,
|
||||
ReadListRequestBookMatchSeriesDto,
|
||||
} from '@/types/komga-readlists'
|
||||
import BookPickerDialog from '@/components/dialogs/BookPickerDialog.vue'
|
||||
|
||||
export default Vue.extend({
|
||||
|
|
@ -70,82 +71,72 @@ export default Vue.extend({
|
|||
SeriesPickerDialog,
|
||||
},
|
||||
props: {
|
||||
match: {
|
||||
type: Object as PropType<ReadListRequestBookMatchesDto>,
|
||||
request: {
|
||||
type: Object as PropType<ReadListRequestBookDto>,
|
||||
required: true,
|
||||
},
|
||||
series: {
|
||||
type: Object as PropType<ReadListRequestBookMatchSeriesDto>,
|
||||
required: false,
|
||||
},
|
||||
book: {
|
||||
type: Object as PropType<ReadListRequestBookMatchBookDto>,
|
||||
required: false,
|
||||
},
|
||||
duplicate: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
bookId: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
match: {
|
||||
handler(val) {
|
||||
this.processMatch(val)
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
selectedSeries: {
|
||||
handler(val, old) {
|
||||
if (!old) {
|
||||
this.getSeriesBooks(val)
|
||||
} else if (val?.id !== old?.id) {
|
||||
this.selectedBook = undefined
|
||||
this.getSeriesBooks(val)
|
||||
series: {
|
||||
handler(val: ReadListRequestBookMatchSeriesDto, old: ReadListRequestBookMatchSeriesDto) {
|
||||
if(val?.seriesId !== old?.seriesId) {
|
||||
this.seriesBooksCached = false
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
selectedBook: {
|
||||
handler(val) {
|
||||
this.$emit('update:bookId', val?.id)
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
data: () => ({
|
||||
innerSelect: false,
|
||||
selectedSeries: undefined as SeriesDto | undefined,
|
||||
selectedBook: undefined as BookDto | undefined,
|
||||
seriesBooks: [] as BookDto[],
|
||||
modalSeriesPicker: false,
|
||||
modalBookPicker: false,
|
||||
seriesBooks: [] as BookDto[],
|
||||
seriesBooksCached: false,
|
||||
}),
|
||||
computed: {
|
||||
existingFileNames(): string[] {
|
||||
return this.seriesBooks.map(x => x.name)
|
||||
},
|
||||
error(): string {
|
||||
if (!this.selectedSeries) return this.$t('book_import.row.error_choose_series').toString()
|
||||
if (!this.selectedBook) return this.$t('readlist_import.row.error_choose_book').toString()
|
||||
if (!this.series) return this.$t('book_import.row.error_choose_series').toString()
|
||||
if (!this.book) return this.$t('readlist_import.row.error_choose_book').toString()
|
||||
return ''
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async processMatch(match: ReadListRequestBookMatchesDto) {
|
||||
let seriesId: string | undefined
|
||||
if (match.matches.length === 1) {
|
||||
seriesId = match.matches[0].seriesId
|
||||
} else if (match.matches.length > 1) {
|
||||
seriesId = match.matches.find((m) => m.bookIds.length > 0)?.seriesId
|
||||
}
|
||||
if (seriesId) {
|
||||
this.selectedSeries = await this.$komgaSeries.getOneSeries(seriesId)
|
||||
const bookId = match.matches.find((m) => m.seriesId === seriesId)?.bookIds.find(Boolean)
|
||||
if (bookId) {
|
||||
this.selectedBook = await this.$komgaBooks.getBook(bookId)
|
||||
}
|
||||
openBookPicker() {
|
||||
if(!this.seriesBooksCached) {
|
||||
this.$komgaSeries.getBooks(this.series?.seriesId, {unpaged: true})
|
||||
.then(r => {
|
||||
this.seriesBooks = r.content
|
||||
this.seriesBooksCached = true
|
||||
})
|
||||
}
|
||||
this.modalBookPicker = true
|
||||
},
|
||||
async getSeriesBooks(series: SeriesDto) {
|
||||
if (series) {
|
||||
this.seriesBooks = (await this.$komgaSeries.getBooks(series.id, {unpaged: true})).content
|
||||
}
|
||||
pickedSeries(series: SeriesDto) {
|
||||
this.$emit('update:series', {
|
||||
seriesId: series.id,
|
||||
title: series.metadata.title,
|
||||
} as ReadListRequestBookMatchSeriesDto)
|
||||
},
|
||||
pickedBook(book: BookDto) {
|
||||
this.$emit('update:book', {
|
||||
bookId: book.id,
|
||||
number: book.metadata.number,
|
||||
title: book.metadata.title,
|
||||
} as ReadListRequestBookMatchBookDto)
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
|
|||
|
|
@ -23,24 +23,7 @@ export interface ReadListUpdateDto {
|
|||
bookIds?: string[]
|
||||
}
|
||||
|
||||
export interface ReadListRequestResultDto {
|
||||
readList?: ReadListDto,
|
||||
unmatchedBooks: ReadListRequestResultBookDto[],
|
||||
errorCode: string,
|
||||
requestName: string,
|
||||
}
|
||||
|
||||
export interface ReadListRequestResultBookDto {
|
||||
book: ReadListRequestBookDto,
|
||||
errorCode: string,
|
||||
}
|
||||
|
||||
export interface ReadListRequestBookDto {
|
||||
series: string,
|
||||
number: string,
|
||||
}
|
||||
|
||||
export interface ReadListRequestBookV2Dto {
|
||||
series: string[],
|
||||
number: string,
|
||||
}
|
||||
|
|
@ -54,7 +37,7 @@ export interface ReadListThumbnailDto {
|
|||
|
||||
export interface ReadListRequestMatchDto {
|
||||
readListMatch: ReadListMatchDto,
|
||||
matches: ReadListRequestBookMatchesDto[],
|
||||
requests: ReadListRequestBookMatchesDto[],
|
||||
errorCode: string,
|
||||
}
|
||||
|
||||
|
|
@ -64,11 +47,22 @@ export interface ReadListMatchDto {
|
|||
}
|
||||
|
||||
export interface ReadListRequestBookMatchesDto {
|
||||
request: ReadListRequestBookV2Dto,
|
||||
request: ReadListRequestBookDto,
|
||||
matches: ReadListRequestBookMatchDto[],
|
||||
}
|
||||
|
||||
export interface ReadListRequestBookMatchDto {
|
||||
seriesId: string,
|
||||
bookIds: string[],
|
||||
series: ReadListRequestBookMatchSeriesDto,
|
||||
books: ReadListRequestBookMatchBookDto[],
|
||||
}
|
||||
|
||||
export interface ReadListRequestBookMatchSeriesDto {
|
||||
seriesId: string,
|
||||
title: string,
|
||||
}
|
||||
|
||||
export interface ReadListRequestBookMatchBookDto {
|
||||
bookId: string,
|
||||
number: string,
|
||||
title: string,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,8 @@
|
|||
<v-col cols="auto">
|
||||
<v-btn
|
||||
color="primary"
|
||||
:disabled="!validMatch"
|
||||
:disabled="!validMatch || matching"
|
||||
:loading="matching"
|
||||
@click="matchFile"
|
||||
>{{ $t('data_import.button_match') }}
|
||||
</v-btn>
|
||||
|
|
@ -43,11 +44,14 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody
|
||||
v-for="(match, i) in result.matches"
|
||||
v-for="(match, i) in result.requests"
|
||||
:key="i"
|
||||
>
|
||||
<read-list-match-row :match="match" :book-id.sync="form.bookIds[i]"
|
||||
:duplicate="isDuplicateBook(form.bookIds[i])">
|
||||
<read-list-match-row :request="match.request"
|
||||
:series.sync="series[i]"
|
||||
:book.sync="form.books[i]"
|
||||
:duplicate="isDuplicateBook(form.books[i])"
|
||||
>
|
||||
<td>{{ i + 1 }}</td>
|
||||
</read-list-match-row>
|
||||
</tbody>
|
||||
|
|
@ -98,13 +102,18 @@
|
|||
<script lang="ts">
|
||||
import Vue from 'vue'
|
||||
import {convertErrorCodes} from '@/functions/error-codes'
|
||||
import {ReadListCreationDto, ReadListDto, ReadListRequestMatchDto} from '@/types/komga-readlists'
|
||||
import {
|
||||
ReadListCreationDto,
|
||||
ReadListDto,
|
||||
ReadListRequestBookMatchBookDto,
|
||||
ReadListRequestMatchDto, ReadListRequestBookMatchSeriesDto,
|
||||
} from '@/types/komga-readlists'
|
||||
import ReadListMatchRow from '@/components/ReadListMatchRow.vue'
|
||||
import {ERROR, NOTIFICATION, NotificationEvent} from '@/types/events'
|
||||
import {helpers, required} from 'vuelidate/lib/validators'
|
||||
|
||||
function validBookIds(this: any, value: string[]) {
|
||||
return value.filter(Boolean).length === this.result.matches.length && value.filter(Boolean).length === [...new Set(value)].length
|
||||
function validBookIds(this: any, value: any[]) {
|
||||
return value.filter(Boolean).length === this.result.requests.length && value.filter(Boolean).length === [...new Set(value)].length
|
||||
}
|
||||
|
||||
function validName(this: any, value: string) {
|
||||
|
|
@ -115,26 +124,27 @@ export default Vue.extend({
|
|||
name: 'ImportReadLists',
|
||||
components: {ReadListMatchRow},
|
||||
data: () => ({
|
||||
convertErrorCodes,
|
||||
file: undefined,
|
||||
result: undefined as unknown as ReadListRequestMatchDto,
|
||||
result: undefined as unknown as ReadListRequestMatchDto | undefined,
|
||||
validMatch: true,
|
||||
validCreate: true,
|
||||
series: [] as (ReadListRequestBookMatchSeriesDto | undefined)[],
|
||||
form: {
|
||||
name: '',
|
||||
ordered: true,
|
||||
summary: '',
|
||||
bookIds: [] as string[],
|
||||
books: [] as (ReadListRequestBookMatchBookDto | undefined)[],
|
||||
},
|
||||
readLists: [] as ReadListDto[],
|
||||
creationFinished: false,
|
||||
matching: false,
|
||||
}),
|
||||
validations: {
|
||||
form: {
|
||||
name: {required, validName},
|
||||
ordered: {},
|
||||
summary: {},
|
||||
bookIds: {validBookIds},
|
||||
books: {validBookIds},
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -156,27 +166,37 @@ export default Vue.extend({
|
|||
async mounted() {
|
||||
this.readLists = (await this.$komgaReadLists.getReadLists(undefined, {unpaged: true} as PageRequest)).content
|
||||
},
|
||||
watch: {},
|
||||
methods: {
|
||||
isDuplicateBook(bookId: string): boolean {
|
||||
return this.form.bookIds.filter((b) => b === bookId).length > 1
|
||||
isDuplicateBook(book: ReadListRequestBookMatchBookDto): boolean {
|
||||
return this.form.books.filter((b) => b?.bookId === book?.bookId).length > 1
|
||||
},
|
||||
async matchFile() {
|
||||
this.matching = true
|
||||
this.result = undefined
|
||||
this.form.summary = ''
|
||||
this.form.ordered = true
|
||||
this.form.books = []
|
||||
this.series = []
|
||||
this.creationFinished = false
|
||||
try {
|
||||
this.result = await this.$komgaReadLists.postReadListMatch(this.file)
|
||||
this.form.name = this.result.readListMatch.name
|
||||
this.result.requests.forEach((request, index) => {
|
||||
const match = request.matches.find(Boolean)
|
||||
this.$set(this.series, index, match?.series)
|
||||
this.$set(this.form.books, index, match?.books.find(Boolean))
|
||||
})
|
||||
} catch (e) {
|
||||
this.$eventHub.$emit(ERROR, {message: convertErrorCodes(e.message)} as ErrorEvent)
|
||||
}
|
||||
this.form.summary = ''
|
||||
this.form.ordered = true
|
||||
this.form.bookIds = []
|
||||
this.creationFinished = false
|
||||
this.matching = false
|
||||
},
|
||||
validateForm(): ReadListCreationDto | undefined {
|
||||
if (this.$v.$invalid) return undefined
|
||||
return {
|
||||
name: this.form.name,
|
||||
bookIds: this.form.bookIds,
|
||||
bookIds: this.form.books.map(b => b?.bookId).filter(Boolean) as string[],
|
||||
ordered: this.form.ordered,
|
||||
summary: this.form.summary,
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue