Fix listener for audiobook updates in edit modal, add remove cover button

This commit is contained in:
advplyr 2021-08-25 19:15:00 -05:00
parent cb40e063da
commit 1dc01615dd
9 changed files with 96 additions and 51 deletions

View File

@ -25,18 +25,21 @@ export default {
return {
selectedTab: 'details',
processing: false,
audiobook: null
audiobook: null,
fetchOnShow: false
}
},
watch: {
show: {
handler(newVal) {
if (newVal) {
if (this.audiobook && this.audiobook.id === this.selectedAudiobookId) return
if (this.audiobook && this.audiobook.id === this.selectedAudiobookId) {
if (this.fetchOnShow) this.fetchFull()
return
}
this.fetchOnShow = false
this.audiobook = null
this.init()
} else {
this.$store.commit('audiobooks/removeListener', 'edit-modal')
}
}
}
@ -75,7 +78,10 @@ export default {
this.selectedTab = tab
},
audiobookUpdated() {
this.fetchFull()
if (!this.show) this.fetchOnShow = true
else {
this.fetchFull()
}
},
init() {
this.$store.commit('audiobooks/addListener', { meth: this.audiobookUpdated, id: 'edit-modal', audiobookId: this.selectedAudiobookId })

View File

@ -1,7 +1,16 @@
<template>
<div class="w-full h-full overflow-hidden overflow-y-auto px-1">
<div class="flex">
<cards-book-cover :audiobook="audiobook" />
<div class="relative">
<cards-book-cover :audiobook="audiobook" />
<!-- book cover overlay -->
<div v-if="book.cover" class="absolute top-0 left-0 w-full h-full z-10 opacity-0 hover:opacity-100 transition-opacity duration-100">
<div class="absolute top-0 left-0 w-full h-16 bg-gradient-to-b from-black-600 to-transparent" />
<div class="p-1 absolute top-1 right-1 text-red-500 rounded-full w-8 h-8 cursor-pointer hover:text-red-400 shadow-sm" @click="removeCover">
<span class="material-icons">delete</span>
</div>
</div>
</div>
<div class="flex-grow pl-6 pr-2">
<form @submit.prevent="submitForm">
<div class="flex items-center">
@ -19,7 +28,6 @@
<div v-if="showLocalCovers" class="flex items-center justify-center">
<template v-for="cover in localCovers">
<!-- <img :src="`/local/${cover.path}`" :key="cover.path" class="w-20 h-32 object-cover m-0.5" /> -->
<div :key="cover.path" class="m-0.5 border-2 border-transparent hover:border-yellow-300 cursor-pointer" :class="cover.localPath === imageUrl ? 'border-yellow-300' : ''" @click="setCover(cover.localPath)">
<img :src="cover.localPath" class="h-24 object-cover" style="width: 60px" />
</div>
@ -43,9 +51,11 @@
<div v-if="hasSearched" class="flex items-center flex-wrap justify-center max-h-60 overflow-y-scroll mt-2 max-w-full">
<p v-if="!coversFound.length">No Covers Found</p>
<template v-for="cover in coversFound">
<div :key="cover" class="m-0.5 border-2 border-transparent hover:border-yellow-300 cursor-pointer" :class="cover === imageUrl ? 'border-yellow-300' : ''" @click="setCover(cover)">
<img :src="cover" class="h-24 object-cover" style="width: 60px" />
</div>
<ui-tooltip :key="cover" direction="bottom" :text="cover">
<div class="m-0.5 border-2 border-transparent hover:border-yellow-300 cursor-pointer" :class="cover === imageUrl ? 'border-yellow-300' : ''" @click="setCover(cover)">
<img :src="cover" class="h-24 object-cover" style="width: 60px" />
</div>
</ui-tooltip>
</template>
</div>
</div>
@ -78,7 +88,9 @@ export default {
audiobook: {
immediate: true,
handler(newVal) {
if (newVal) this.init()
if (newVal) {
this.init()
}
}
}
},
@ -118,10 +130,22 @@ export default {
this.searchTitle = this.book.title || ''
this.searchAuthor = this.book.author || ''
},
removeCover() {
if (!this.book.cover) {
this.imageUrl = ''
return
}
this.updateCover('')
},
submitForm() {
this.updateCover(this.imageUrl)
},
async updateCover(cover) {
if (cover === this.book.cover) {
console.warn('Cover has not changed..', cover)
return
}
this.isProcessing = true
const updatePayload = {
book: {
@ -134,7 +158,6 @@ export default {
})
this.isProcessing = false
if (updatedAudiobook) {
console.log('Update Successful', updatedAudiobook)
this.$toast.success('Update Successful')
this.$emit('close')
}

View File

@ -130,7 +130,6 @@ export default {
})
this.isProcessing = false
if (updatedAudiobook) {
console.log('Update Successful', updatedAudiobook)
this.$toast.success('Update Successful')
this.$emit('close')
}

View File

@ -66,7 +66,7 @@ export default {
},
methods: {
getSearchQuery() {
var searchQuery = `provider=${this.provider}&title=${this.searchTitle}`
var searchQuery = `provider=${this.provider}&fallbackTitleOnly=1&title=${this.searchTitle}`
if (this.searchAuthor) searchQuery += `&author=${this.searchAuthor}`
return searchQuery
},
@ -129,7 +129,6 @@ export default {
})
this.isProcessing = false
if (updatedAudiobook) {
console.log('Update Successful', updatedAudiobook)
this.$toast.success('Update Successful')
this.$emit('close')
}

View File

@ -1,6 +1,6 @@
{
"name": "audiobookshelf-client",
"version": "0.9.77-beta",
"version": "0.9.78-beta",
"description": "Audiobook manager and player",
"main": "index.js",
"scripts": {

View File

@ -1,6 +1,6 @@
{
"name": "audiobookshelf",
"version": "0.9.77-beta",
"version": "0.9.78-beta",
"description": "Self-hosted audiobook server for managing and playing audiobooks.",
"main": "index.js",
"scripts": {

View File

@ -95,13 +95,21 @@ class Book {
if (data.otherFiles && data.otherFiles.length) {
var imageFile = data.otherFiles.find(f => f.filetype === 'image')
if (imageFile) {
this.cover = Path.join('/local', imageFile.path)
this.cover = Path.normalize(Path.join('/local', imageFile.path))
}
}
}
update(payload) {
var hasUpdates = false
if (payload.cover) {
// If updating to local cover then normalize path
if (!payload.cover.startsWith('http:') && !payload.cover.startsWith('https:')) {
payload.cover = Path.normalize(payload.cover)
}
}
for (const key in payload) {
if (payload[key] === undefined) continue;

View File

@ -12,7 +12,7 @@ class BookFinder {
async findByISBN(isbn) {
var book = await this.openLibrary.isbnLookup(isbn)
if (book.errorCode) {
console.error('Book not found')
Logger.error('Book not found')
}
return book
}
@ -61,73 +61,82 @@ class BookFinder {
return books.map(b => {
b.cleanedTitle = this.cleanTitleForCompares(b.title)
b.titleDistance = levenshteinDistance(b.cleanedTitle, title)
// Total length of search (title or both title & author)
b.totalPossibleDistance = b.title.length
if (author) {
if (!b.author) {
b.authorDistance = author.length
} else {
b.totalPossibleDistance += b.author.length
b.cleanedAuthor = this.cleanAuthorForCompares(b.author)
var cleanedAuthorDistance = levenshteinDistance(b.cleanedAuthor, searchAuthor)
var authorDistance = levenshteinDistance(b.author || '', author)
// Use best distance
if (cleanedAuthorDistance > authorDistance) b.authorDistance = authorDistance
else b.authorDistance = cleanedAuthorDistance
b.authorDistance = Math.min(cleanedAuthorDistance, authorDistance)
// Check book author contains searchAuthor
if (searchAuthor.length > 4 && b.cleanedAuthor.includes(searchAuthor)) b.includesAuthor = searchAuthor
else if (author.length > 4 && b.author.includes(author)) b.includesAuthor = author
}
}
b.totalDistance = b.titleDistance + (b.authorDistance || 0)
b.totalPossibleDistance = b.title.length
if (b.cleanedTitle.includes(searchTitle) && searchTitle.length > 4) {
b.includesSearch = searchTitle
} else if (b.title.includes(searchTitle) && searchTitle.length > 4) {
b.includesSearch = searchTitle
}
if (author && b.author) b.totalPossibleDistance += b.author.length
// Check book title contains the searchTitle
if (searchTitle.length > 4 && b.cleanedTitle.includes(searchTitle)) b.includesTitle = searchTitle
else if (title.length > 4 && b.title.includes(title)) b.includesTitle = title
return b
}).filter(b => {
if (b.includesSearch) { // If search was found in result title exactly then skip over leven distance check
Logger.debug(`Exact search was found inside title ${b.cleanedTitle}/${b.includesSearch}`)
if (b.includesTitle) { // If search title was found in result title then skip over leven distance check
Logger.debug(`Exact title was included in "${b.title}", Search: "${b.includesTitle}"`)
} else if (b.titleDistance > maxTitleDistance) {
Logger.debug(`Filtering out search result title distance = ${b.titleDistance}: "${b.cleanedTitle}"/"${searchTitle}"`)
return false
}
if (author && b.authorDistance > maxAuthorDistance) {
Logger.debug(`Filtering out search result "${b.title}", author distance = ${b.authorDistance}: "${b.author}"/"${author}"`)
return false
if (author) {
if (b.includesAuthor) { // If search author was found in result author then skip over leven distance check
Logger.debug(`Exact author was included in "${b.author}", Search: "${b.includesAuthor}"`)
} else if (b.authorDistance > maxAuthorDistance) {
Logger.debug(`Filtering out search result "${b.author}", author distance = ${b.authorDistance}: "${b.author}"/"${author}"`)
return false
}
}
if (b.totalPossibleDistance < 4 && b.totalDistance > 0) return false
// If book total search length < 5 and was not exact match, then filter out
if (b.totalPossibleDistance < 5 && b.totalDistance > 0) return false
return true
})
}
async getLibGenResults(title, author, maxTitleDistance, maxAuthorDistance) {
var books = await this.libGen.search(title)
Logger.info(`LibGen Book Search Results: ${books.length || 0}`)
Logger.debug(`LibGen Book Search Results: ${books.length || 0}`)
if (books.errorCode) {
Logger.error(`LibGen Search Error ${books.errorCode}`)
return []
}
var booksFiltered = this.filterSearchResults(books, title, author, maxTitleDistance, maxAuthorDistance)
if (!booksFiltered.length && books.length) {
Logger.info(`Search has ${books.length} matches, but no close title matches`)
Logger.debug(`Search has ${books.length} matches, but no close title matches`)
}
return booksFiltered
}
async getOpenLibResults(title, author, maxTitleDistance, maxAuthorDistance) {
var books = await this.openLibrary.searchTitle(title)
Logger.info(`OpenLib Book Search Results: ${books.length || 0}`)
Logger.debug(`OpenLib Book Search Results: ${books.length || 0}`)
if (books.errorCode) {
Logger.error(`OpenLib Search Error ${books.errorCode}`)
return []
}
var booksFiltered = this.filterSearchResults(books, title, author, maxTitleDistance, maxAuthorDistance)
if (!booksFiltered.length && books.length) {
Logger.info(`Search has ${books.length} matches, but no close title matches`)
Logger.debug(`Search has ${books.length} matches, but no close title matches`)
}
return booksFiltered
}
@ -136,7 +145,7 @@ class BookFinder {
var books = []
var maxTitleDistance = !isNaN(options.titleDistance) ? Number(options.titleDistance) : 4
var maxAuthorDistance = !isNaN(options.authorDistance) ? Number(options.authorDistance) : 4
Logger.info(`Book Search, title: "${title}", author: "${author}", provider: ${provider}`)
Logger.debug(`Book Search, title: "${title}", author: "${author}", provider: ${provider}`)
if (provider === 'libgen') {
books = await this.getLibGenResults(title, author, maxTitleDistance, maxAuthorDistance)
@ -147,18 +156,16 @@ class BookFinder {
var olBooks = await this.getOpenLibResults(title, author, maxTitleDistance, maxAuthorDistance)
books = books.concat(lbBooks, olBooks)
} else {
var olBooks = await this.getOpenLibResults(title, author, maxTitleDistance, maxAuthorDistance)
var hasCloseMatch = olBooks.find(b => (b.totalDistance < 4 && b.totalPossibleDistance > 4))
if (hasCloseMatch) {
books = olBooks
} else {
Logger.info(`Book Search, LibGen has no close matches - get openlib results also`)
books = await this.getOpenLibResults(title, author, maxTitleDistance, maxAuthorDistance)
var hasCloseMatch = books.find(b => (b.totalDistance < 2 && b.totalPossibleDistance > 6))
if (!hasCloseMatch) {
Logger.debug(`Book Search, openlib has no super close matches - get libgen results also`)
var lbBooks = await this.getLibGenResults(title, author, maxTitleDistance, maxAuthorDistance)
books = books.concat(lbBooks)
}
if (!books.length && author) {
Logger.info(`Book Search, no matches for title and author.. check title only`)
if (!books.length && author && options.fallbackTitleOnly) {
Logger.debug(`Book Search, no matches for title and author.. check title only`)
return this.search(provider, title, null, options)
}
}
@ -170,7 +177,7 @@ class BookFinder {
async findCovers(provider, title, author, options = {}) {
var searchResults = await this.search(provider, title, author, options)
Logger.info(`[BookFinder] FindCovers search results: ${searchResults.length}`)
Logger.debug(`[BookFinder] FindCovers search results: ${searchResults.length}`)
var covers = []
searchResults.forEach((result) => {

View File

@ -247,7 +247,7 @@ class Scanner {
}
var results = await this.bookFinder.findCovers('openlibrary', audiobook.title, audiobook.author, options)
if (results.length) {
Logger.info(`[Scanner] Found best cover for "${audiobook.title}"`)
Logger.debug(`[Scanner] Found best cover for "${audiobook.title}"`)
audiobook.book.cover = results[0]
await this.db.updateAudiobook(audiobook)
found++
@ -294,7 +294,10 @@ class Scanner {
async findCovers(req, res) {
var query = req.query
var result = await this.bookFinder.findCovers(query.provider, query.title, query.author || null)
var options = {
fallbackTitleOnly: !!query.fallbackTitleOnly
}
var result = await this.bookFinder.findCovers(query.provider, query.title, query.author || null, options)
res.json(result)
}
}