mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-08-04 02:05:06 +02:00
New api routes, updating web client pages, audiobooks to libraryItem migration
This commit is contained in:
parent
b97ed953f7
commit
2a30cc428f
51 changed files with 1225 additions and 654 deletions
|
@ -85,8 +85,9 @@ export default {
|
|||
},
|
||||
async fetchCategories() {
|
||||
var categories = await this.$axios
|
||||
.$get(`/api/libraries/${this.currentLibraryId}/categories?minified=1`)
|
||||
.$get(`/api/libraries/${this.currentLibraryId}/personalized?minified=1`)
|
||||
.then((data) => {
|
||||
console.log('Personalized data', data)
|
||||
return data
|
||||
})
|
||||
.catch((error) => {
|
||||
|
|
|
@ -239,7 +239,7 @@ export default {
|
|||
this.currentSFQueryString = this.buildSearchParams()
|
||||
}
|
||||
|
||||
var entityPath = this.entityName === 'books' || this.entityName === 'series-books' ? `books/all` : this.entityName
|
||||
var entityPath = this.entityName === 'books' || this.entityName === 'series-books' ? `items` : this.entityName
|
||||
var sfQueryString = this.currentSFQueryString ? this.currentSFQueryString + '&' : ''
|
||||
var fullQueryString = `?${sfQueryString}limit=${this.booksPerFetch}&page=${page}&minified=1`
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<!-- Alternative bookshelf title/author/sort -->
|
||||
<div v-if="isAlternativeBookshelfView" class="absolute left-0 z-50 w-full" :style="{ bottom: `-${titleDisplayBottomOffset}rem` }">
|
||||
<p class="truncate" :style="{ fontSize: 0.9 * sizeMultiplier + 'rem' }">
|
||||
<span v-if="volumeNumber">#{{ volumeNumber }} </span>{{ displayTitle }}
|
||||
{{ displayTitle }}
|
||||
</p>
|
||||
<p class="truncate text-gray-400" :style="{ fontSize: 0.8 * sizeMultiplier + 'rem' }">{{ displayAuthor }}</p>
|
||||
<p v-if="displaySortLine" class="truncate text-gray-400" :style="{ fontSize: 0.8 * sizeMultiplier + 'rem' }">{{ displaySortLine }}</p>
|
||||
|
@ -21,7 +21,7 @@
|
|||
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }" class="font-book text-gray-300 text-center">{{ title }}</p>
|
||||
</div>
|
||||
|
||||
<img v-show="audiobook" ref="cover" :src="bookCoverSrc" class="w-full h-full object-contain transition-opacity duration-300" :class="showCoverBg ? 'object-contain' : 'object-fill'" @load="imageLoaded" :style="{ opacity: imageReady ? 1 : 0 }" />
|
||||
<img v-show="audiobook" ref="cover" :src="bookCoverSrc" class="w-full h-full transition-opacity duration-300" :class="showCoverBg ? 'object-contain' : 'object-fill'" @load="imageLoaded" :style="{ opacity: imageReady ? 1 : 0 }" />
|
||||
|
||||
<!-- Placeholder Cover Title & Author -->
|
||||
<div v-if="!hasCover" class="absolute top-0 left-0 right-0 bottom-0 w-full h-full flex items-center justify-center" :style="{ padding: placeholderCoverPadding + 'rem' }">
|
||||
|
@ -136,42 +136,45 @@ export default {
|
|||
showExperimentalFeatures() {
|
||||
return this.store.state.showExperimentalFeatures
|
||||
},
|
||||
_audiobook() {
|
||||
_libraryItem() {
|
||||
return this.audiobook || {}
|
||||
},
|
||||
book() {
|
||||
return this._audiobook.book || {}
|
||||
media() {
|
||||
return this._libraryItem.media || {}
|
||||
},
|
||||
mediaMetadata() {
|
||||
return this.media.metadata || {}
|
||||
},
|
||||
placeholderUrl() {
|
||||
return '/book_placeholder.jpg'
|
||||
},
|
||||
bookCoverSrc() {
|
||||
return this.store.getters['audiobooks/getBookCoverSrc'](this._audiobook, this.placeholderUrl)
|
||||
return this.store.getters['audiobooks/getLibraryItemCoverSrc'](this._libraryItem, this.placeholderUrl)
|
||||
},
|
||||
audiobookId() {
|
||||
return this._audiobook.id
|
||||
libraryItemId() {
|
||||
return this._libraryItem.id
|
||||
},
|
||||
series() {
|
||||
return this.book.series
|
||||
return this.mediaMetadata.series
|
||||
},
|
||||
libraryId() {
|
||||
return this._audiobook.libraryId
|
||||
return this._libraryItem.libraryId
|
||||
},
|
||||
hasEbook() {
|
||||
return this._audiobook.numEbooks
|
||||
return this.media.numEbooks
|
||||
},
|
||||
hasTracks() {
|
||||
return this._audiobook.numTracks
|
||||
return this.media.numTracks
|
||||
},
|
||||
processingBatch() {
|
||||
return this.store.state.processingBatch
|
||||
},
|
||||
booksInSeries() {
|
||||
// Only added to audiobook object when collapseSeries is enabled
|
||||
return this._audiobook.booksInSeries
|
||||
return this._libraryItem.booksInSeries
|
||||
},
|
||||
hasCover() {
|
||||
return !!this.book.cover
|
||||
return !!this.media.coverPath
|
||||
},
|
||||
squareAspectRatio() {
|
||||
return this.bookCoverAspectRatio === 1
|
||||
|
@ -181,43 +184,49 @@ export default {
|
|||
return this.width / baseSize
|
||||
},
|
||||
title() {
|
||||
return this.book.title || ''
|
||||
return this.mediaMetadata.title || ''
|
||||
},
|
||||
playIconFontSize() {
|
||||
return Math.max(2, 3 * this.sizeMultiplier)
|
||||
},
|
||||
author() {
|
||||
return this.book.author
|
||||
authors() {
|
||||
return this.mediaMetadata.authors || []
|
||||
},
|
||||
authorFL() {
|
||||
return this.book.authorFL || this.author
|
||||
author() {
|
||||
return this.authors.map((au) => au.name).join(', ')
|
||||
},
|
||||
authorLF() {
|
||||
return this.book.authorLF || this.author
|
||||
return this.authors
|
||||
.map((au) => {
|
||||
var parts = au.name.split(' ')
|
||||
if (parts.length === 1) return parts[0]
|
||||
return `${parts[1]}, ${parts[0]}`
|
||||
})
|
||||
.join(', ')
|
||||
},
|
||||
volumeNumber() {
|
||||
return this.book.volumeNumber || null
|
||||
return this.mediaMetadata.volumeNumber || null
|
||||
},
|
||||
displayTitle() {
|
||||
if (this.orderBy === 'book.title' && this.sortingIgnorePrefix && this.title.toLowerCase().startsWith('the ')) {
|
||||
if (this.orderBy === 'media.metadata.title' && this.sortingIgnorePrefix && this.title.toLowerCase().startsWith('the ')) {
|
||||
return this.title.substr(4) + ', The'
|
||||
}
|
||||
return this.title
|
||||
},
|
||||
displayAuthor() {
|
||||
if (this.orderBy === 'book.authorLF') return this.authorLF
|
||||
return this.authorFL
|
||||
if (this.orderBy === 'media.metadata.authorLF') return this.authorLF
|
||||
return this.author
|
||||
},
|
||||
displaySortLine() {
|
||||
if (this.orderBy === 'mtimeMs') return 'Modified ' + this.$formatDate(this._audiobook.mtimeMs)
|
||||
if (this.orderBy === 'birthtimeMs') return 'Born ' + this.$formatDate(this._audiobook.birthtimeMs)
|
||||
if (this.orderBy === 'addedAt') return 'Added ' + this.$formatDate(this._audiobook.addedAt)
|
||||
if (this.orderBy === 'duration') return 'Duration: ' + this.$elapsedPrettyExtended(this._audiobook.duration, false)
|
||||
if (this.orderBy === 'size') return 'Size: ' + this.$bytesPretty(this._audiobook.size)
|
||||
if (this.orderBy === 'mtimeMs') return 'Modified ' + this.$formatDate(this._libraryItem.mtimeMs)
|
||||
if (this.orderBy === 'birthtimeMs') return 'Born ' + this.$formatDate(this._libraryItem.birthtimeMs)
|
||||
if (this.orderBy === 'addedAt') return 'Added ' + this.$formatDate(this._libraryItem.addedAt)
|
||||
if (this.orderBy === 'duration') return 'Duration: ' + this.$elapsedPrettyExtended(this.media.duration, false)
|
||||
if (this.orderBy === 'size') return 'Size: ' + this.$bytesPretty(this._libraryItem.size)
|
||||
return null
|
||||
},
|
||||
userProgress() {
|
||||
return this.store.getters['user/getUserAudiobook'](this.audiobookId)
|
||||
return this.store.getters['user/getUserAudiobook'](this.libraryItemId)
|
||||
},
|
||||
userProgressPercent() {
|
||||
return this.userProgress ? this.userProgress.progress || 0 : 0
|
||||
|
@ -229,7 +238,7 @@ export default {
|
|||
return this.hasMissingParts || this.hasInvalidParts || this.isMissing || this.isInvalid
|
||||
},
|
||||
isStreaming() {
|
||||
return this.store.getters['getAudiobookIdStreaming'] === this.audiobookId
|
||||
return this.store.getters['getlibraryItemIdStreaming'] === this.libraryItemId
|
||||
},
|
||||
showReadButton() {
|
||||
return !this.isSelectionMode && this.showExperimentalFeatures && !this.showPlayButton && this.hasEbook
|
||||
|
@ -241,16 +250,16 @@ export default {
|
|||
return !this.isSelectionMode && this.showExperimentalFeatures && this.hasEbook
|
||||
},
|
||||
isMissing() {
|
||||
return this._audiobook.isMissing
|
||||
return this._libraryItem.isMissing
|
||||
},
|
||||
isInvalid() {
|
||||
return this._audiobook.isInvalid
|
||||
return this._libraryItem.isInvalid
|
||||
},
|
||||
hasMissingParts() {
|
||||
return this._audiobook.hasMissingParts
|
||||
return this._libraryItem.hasMissingParts
|
||||
},
|
||||
hasInvalidParts() {
|
||||
return this._audiobook.hasInvalidParts
|
||||
return this._libraryItem.hasInvalidParts
|
||||
},
|
||||
errorText() {
|
||||
if (this.isMissing) return 'Audiobook directory is missing!'
|
||||
|
@ -349,11 +358,11 @@ export default {
|
|||
return this.title
|
||||
},
|
||||
authorCleaned() {
|
||||
if (!this.authorFL) return ''
|
||||
if (this.authorFL.length > 30) {
|
||||
return this.authorFL.slice(0, 27) + '...'
|
||||
if (!this.author) return ''
|
||||
if (this.author.length > 30) {
|
||||
return this.author.slice(0, 27) + '...'
|
||||
}
|
||||
return this.authorFL
|
||||
return this.author
|
||||
},
|
||||
isAlternativeBookshelfView() {
|
||||
var constants = this.$constants || this.$nuxt.$constants
|
||||
|
@ -382,7 +391,7 @@ export default {
|
|||
var router = this.$router || this.$nuxt.$router
|
||||
if (router) {
|
||||
if (this.booksInSeries) router.push(`/library/${this.libraryId}/series/${this.$encode(this.series)}`)
|
||||
else router.push(`/audiobook/${this.audiobookId}`)
|
||||
else router.push(`/item/${this.libraryItemId}`)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -398,7 +407,7 @@ export default {
|
|||
var toast = this.$toast || this.$nuxt.$toast
|
||||
var axios = this.$axios || this.$nuxt.$axios
|
||||
axios
|
||||
.$patch(`/api/me/audiobook/${this.audiobookId}`, updatePayload)
|
||||
.$patch(`/api/me/audiobook/${this.libraryItemId}`, updatePayload)
|
||||
.then(() => {
|
||||
this.isProcessingReadUpdate = false
|
||||
toast.success(`"${this.title}" Marked as ${updatePayload.isRead ? 'Read' : 'Not Read'}`)
|
||||
|
@ -425,7 +434,7 @@ export default {
|
|||
rescan() {
|
||||
this.rescanning = true
|
||||
this._socket.once('audiobook_scan_complete', this.audiobookScanComplete)
|
||||
this._socket.emit('scan_audiobook', this.audiobookId)
|
||||
this._socket.emit('scan_libraryItem', this.libraryItemId)
|
||||
},
|
||||
showEditModalTracks() {
|
||||
// More menu func
|
||||
|
@ -503,7 +512,7 @@ export default {
|
|||
},
|
||||
play() {
|
||||
var eventBus = this.$eventBus || this.$nuxt.$eventBus
|
||||
eventBus.$emit('play-audiobook', this.audiobookId)
|
||||
eventBus.$emit('play-audiobook', this.libraryItemId)
|
||||
},
|
||||
mouseover() {
|
||||
this.isHovering = true
|
||||
|
|
|
@ -34,27 +34,23 @@ export default {
|
|||
items: [
|
||||
{
|
||||
text: 'Title',
|
||||
value: 'book.title'
|
||||
value: 'media.metadata.title'
|
||||
},
|
||||
{
|
||||
text: 'Author (First Last)',
|
||||
value: 'book.authorFL'
|
||||
value: 'media.metadata.author'
|
||||
},
|
||||
{
|
||||
text: 'Author (Last, First)',
|
||||
value: 'book.authorLF'
|
||||
value: 'media.metadata.authorLF'
|
||||
},
|
||||
{
|
||||
text: 'Added At',
|
||||
value: 'addedAt'
|
||||
},
|
||||
{
|
||||
text: 'Volume #',
|
||||
value: 'book.volumeNumber'
|
||||
},
|
||||
{
|
||||
text: 'Duration',
|
||||
value: 'duration'
|
||||
value: 'media.duration'
|
||||
},
|
||||
{
|
||||
text: 'Size',
|
||||
|
@ -89,7 +85,8 @@ export default {
|
|||
}
|
||||
},
|
||||
selectedText() {
|
||||
var _selected = this.selected === 'book.author' ? 'book.authorFL' : this.selected
|
||||
var _selected = this.selected
|
||||
if (this.selected.startsWith('book.')) _selected = _selected.replace('book.', 'media.metadata.')
|
||||
var _sel = this.items.find((i) => i.value === _selected)
|
||||
if (!_sel) return ''
|
||||
return _sel.text
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
<div class="absolute cover-bg" ref="coverBg" />
|
||||
</div>
|
||||
|
||||
<img v-if="audiobook" ref="cover" :src="fullCoverUrl" loading="lazy" @error="imageError" @load="imageLoaded" class="w-full h-full absolute top-0 left-0 z-10 duration-300 transition-opacity" :style="{ opacity: imageReady ? '1' : '0' }" :class="showCoverBg ? 'object-contain' : 'object-fill'" />
|
||||
<div v-show="loading && audiobook" class="absolute top-0 left-0 h-full w-full flex items-center justify-center">
|
||||
<img v-if="libraryItem" ref="cover" :src="fullCoverUrl" loading="lazy" @error="imageError" @load="imageLoaded" class="w-full h-full absolute top-0 left-0 z-10 duration-300 transition-opacity" :style="{ opacity: imageReady ? '1' : '0' }" :class="showCoverBg ? 'object-contain' : 'object-fill'" />
|
||||
<div v-show="loading && libraryItem" class="absolute top-0 left-0 h-full w-full flex items-center justify-center">
|
||||
<p class="font-book text-center" :style="{ fontSize: 0.75 * sizeMultiplier + 'rem' }">{{ title }}</p>
|
||||
<div class="absolute top-2 right-2">
|
||||
<div class="la-ball-spin-clockwise la-sm">
|
||||
|
@ -44,11 +44,10 @@
|
|||
<script>
|
||||
export default {
|
||||
props: {
|
||||
audiobook: {
|
||||
libraryItem: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
authorOverride: String,
|
||||
width: {
|
||||
type: Number,
|
||||
default: 120
|
||||
|
@ -75,12 +74,15 @@ export default {
|
|||
height() {
|
||||
return this.width * this.bookCoverAspectRatio
|
||||
},
|
||||
book() {
|
||||
if (!this.audiobook) return {}
|
||||
return this.audiobook.book || {}
|
||||
media() {
|
||||
if (!this.libraryItem) return {}
|
||||
return this.libraryItem.media || {}
|
||||
},
|
||||
mediaMetadata() {
|
||||
return this.media.metadata || {}
|
||||
},
|
||||
title() {
|
||||
return this.book.title || 'No Title'
|
||||
return this.mediaMetadata.title || 'No Title'
|
||||
},
|
||||
titleCleaned() {
|
||||
if (this.title.length > 60) {
|
||||
|
@ -88,9 +90,11 @@ export default {
|
|||
}
|
||||
return this.title
|
||||
},
|
||||
authors() {
|
||||
return this.mediaMetadata.authors || []
|
||||
},
|
||||
author() {
|
||||
if (this.authorOverride) return this.authorOverride
|
||||
return this.book.author || 'Unknown'
|
||||
return this.authors.map((au) => au.name).join(', ')
|
||||
},
|
||||
authorCleaned() {
|
||||
if (this.author.length > 30) {
|
||||
|
@ -102,15 +106,15 @@ export default {
|
|||
return '/book_placeholder.jpg'
|
||||
},
|
||||
fullCoverUrl() {
|
||||
if (!this.audiobook) return null
|
||||
if (!this.libraryItem) return null
|
||||
var store = this.$store || this.$nuxt.$store
|
||||
return store.getters['audiobooks/getBookCoverSrc'](this.audiobook, this.placeholderUrl)
|
||||
return store.getters['audiobooks/getLibraryItemCoverSrc'](this.libraryItem, this.placeholderUrl)
|
||||
},
|
||||
cover() {
|
||||
return this.book.cover || this.placeholderUrl
|
||||
return this.media.coverPath || this.placeholderUrl
|
||||
},
|
||||
hasCover() {
|
||||
return !!this.book.cover
|
||||
return !!this.media.coverPath
|
||||
},
|
||||
sizeMultiplier() {
|
||||
var baseSize = this.squareAspectRatio ? 192 : 120
|
||||
|
@ -138,7 +142,6 @@ export default {
|
|||
this.$refs.coverBg.style.backgroundImage = `url("${this.fullCoverUrl}")`
|
||||
}
|
||||
},
|
||||
hideCoverBg() {},
|
||||
imageLoaded() {
|
||||
this.loading = false
|
||||
this.$nextTick(() => {
|
||||
|
|
|
@ -63,7 +63,7 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
getCoverUrl(book) {
|
||||
return this.store.getters['audiobooks/getBookCoverSrc'](book, '')
|
||||
return this.store.getters['audiobooks/getLibraryItemCoverSrc'](book, '')
|
||||
},
|
||||
async buildCoverImg(coverData, bgCoverWidth, offsetLeft, zIndex, forceCoverBg = false) {
|
||||
var src = coverData.coverUrl
|
||||
|
|
|
@ -22,7 +22,7 @@ export default {
|
|||
return '/book_placeholder.jpg'
|
||||
},
|
||||
fullCoverUrl() {
|
||||
return this.$store.getters['audiobooks/getBookCoverSrc'](this.audiobook, this.placeholderUrl)
|
||||
return this.$store.getters['audiobooks/getLibraryItemCoverSrc'](this.audiobook, this.placeholderUrl)
|
||||
},
|
||||
hasCover() {
|
||||
return !!this.audiobook.book.cover
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
</div>
|
||||
|
||||
<div class="w-full h-full text-sm rounded-b-lg rounded-tr-lg bg-bg shadow-lg border border-black-300">
|
||||
<component v-if="audiobook && show" :is="tabName" :audiobook="audiobook" :processing.sync="processing" @close="show = false" @selectTab="selectTab" />
|
||||
<component v-if="libraryItem && show" :is="tabName" :library-item="libraryItem" :processing.sync="processing" @close="show = false" @selectTab="selectTab" />
|
||||
</div>
|
||||
</modals-modal>
|
||||
</template>
|
||||
|
@ -29,7 +29,7 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
processing: false,
|
||||
audiobook: null,
|
||||
libraryItem: null,
|
||||
fetchOnShow: false,
|
||||
tabs: [
|
||||
{
|
||||
|
@ -42,11 +42,6 @@ export default {
|
|||
title: 'Cover',
|
||||
component: 'modals-edit-tabs-cover'
|
||||
},
|
||||
// {
|
||||
// id: 'tracks',
|
||||
// title: 'Tracks',
|
||||
// component: 'modals-edit-tabs-tracks'
|
||||
// },
|
||||
{
|
||||
id: 'chapters',
|
||||
title: 'Chapters',
|
||||
|
@ -89,12 +84,12 @@ export default {
|
|||
this.selectedTab = availableTabIds[0]
|
||||
}
|
||||
|
||||
if (this.audiobook && this.audiobook.id === this.selectedAudiobookId) {
|
||||
if (this.libraryItem && this.libraryItem.id === this.selectedLibraryItemId) {
|
||||
if (this.fetchOnShow) this.fetchFull()
|
||||
return
|
||||
}
|
||||
this.fetchOnShow = false
|
||||
this.audiobook = null
|
||||
this.libraryItem = null
|
||||
this.init()
|
||||
this.registerListeners()
|
||||
} else {
|
||||
|
@ -148,26 +143,29 @@ export default {
|
|||
return _tab ? _tab.component : ''
|
||||
},
|
||||
isMissing() {
|
||||
return this.selectedAudiobook.isMissing
|
||||
return this.selectedLibraryItem.isMissing
|
||||
},
|
||||
selectedAudiobook() {
|
||||
return this.$store.state.selectedAudiobook || {}
|
||||
selectedLibraryItem() {
|
||||
return this.$store.state.selectedLibraryItem || {}
|
||||
},
|
||||
selectedAudiobookId() {
|
||||
return this.selectedAudiobook.id
|
||||
selectedLibraryItemId() {
|
||||
return this.selectedLibraryItem.id
|
||||
},
|
||||
book() {
|
||||
return this.audiobook ? this.audiobook.book || {} : {}
|
||||
media() {
|
||||
return this.libraryItem ? this.libraryItem.media || {} : {}
|
||||
},
|
||||
mediaMetadata() {
|
||||
return this.media.metadata || {}
|
||||
},
|
||||
title() {
|
||||
return this.book.title || 'No Title'
|
||||
return this.mediaMetadata.title || 'No Title'
|
||||
},
|
||||
bookshelfBookIds() {
|
||||
return this.$store.state.bookshelfBookIds || []
|
||||
},
|
||||
currentBookshelfIndex() {
|
||||
if (!this.bookshelfBookIds.length) return 0
|
||||
return this.bookshelfBookIds.findIndex((bid) => bid === this.selectedAudiobookId)
|
||||
return this.bookshelfBookIds.findIndex((bid) => bid === this.selectedLibraryItemId)
|
||||
},
|
||||
canGoPrev() {
|
||||
return this.bookshelfBookIds.length && this.currentBookshelfIndex > 0
|
||||
|
@ -188,7 +186,7 @@ export default {
|
|||
})
|
||||
this.processing = false
|
||||
if (prevBook) {
|
||||
this.$store.commit('showEditModalOnTab', { audiobook: prevBook, tab: this.selectedTab })
|
||||
this.$store.commit('showEditModalOnTab', { libraryItem: prevBook, tab: this.selectedTab })
|
||||
this.$nextTick(this.init)
|
||||
} else {
|
||||
console.error('Book not found', prevBookId)
|
||||
|
@ -205,7 +203,7 @@ export default {
|
|||
})
|
||||
this.processing = false
|
||||
if (nextBook) {
|
||||
this.$store.commit('showEditModalOnTab', { audiobook: nextBook, tab: this.selectedTab })
|
||||
this.$store.commit('showEditModalOnTab', { libraryItem: nextBook, tab: this.selectedTab })
|
||||
this.$nextTick(this.init)
|
||||
} else {
|
||||
console.error('Book not found', nextBookId)
|
||||
|
@ -223,16 +221,16 @@ export default {
|
|||
}
|
||||
},
|
||||
init() {
|
||||
this.$store.commit('audiobooks/addListener', { meth: this.audiobookUpdated, id: 'edit-modal', audiobookId: this.selectedAudiobookId })
|
||||
this.$store.commit('audiobooks/addListener', { meth: this.audiobookUpdated, id: 'edit-modal', audiobookId: this.selectedLibraryItemId })
|
||||
this.fetchFull()
|
||||
},
|
||||
async fetchFull() {
|
||||
try {
|
||||
this.processing = true
|
||||
this.audiobook = await this.$axios.$get(`/api/books/${this.selectedAudiobookId}`)
|
||||
this.libraryItem = await this.$axios.$get(`/api/items/${this.selectedLibraryItemId}?expanded=1`)
|
||||
this.processing = false
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch audiobook', this.selectedAudiobookId, error)
|
||||
console.error('Failed to fetch audiobook', this.selectedLibraryItemId, error)
|
||||
this.processing = false
|
||||
this.show = false
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
<script>
|
||||
export default {
|
||||
props: {
|
||||
audiobook: {
|
||||
libraryItem: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ export default {
|
|||
}
|
||||
},
|
||||
watch: {
|
||||
audiobook: {
|
||||
libraryItem: {
|
||||
immediate: true,
|
||||
handler(newVal) {
|
||||
if (newVal) this.init()
|
||||
|
@ -52,7 +52,7 @@ export default {
|
|||
computed: {},
|
||||
methods: {
|
||||
init() {
|
||||
this.chapters = this.audiobook.chapters || []
|
||||
this.chapters = this.libraryItem.chapters || []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
<div class="w-full h-full overflow-hidden overflow-y-auto px-4 py-6 relative">
|
||||
<div class="flex">
|
||||
<div class="relative">
|
||||
<covers-book-cover :audiobook="audiobook" :book-cover-aspect-ratio="bookCoverAspectRatio" />
|
||||
<covers-book-cover :library-item="libraryItem" :book-cover-aspect-ratio="bookCoverAspectRatio" />
|
||||
<!-- 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 v-if="media.coverPath" 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>
|
||||
|
@ -82,7 +82,7 @@
|
|||
export default {
|
||||
props: {
|
||||
processing: Boolean,
|
||||
audiobook: {
|
||||
libraryItem: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
}
|
||||
|
@ -102,7 +102,7 @@ export default {
|
|||
}
|
||||
},
|
||||
watch: {
|
||||
audiobook: {
|
||||
libraryItem: {
|
||||
immediate: true,
|
||||
handler(newVal) {
|
||||
if (newVal) {
|
||||
|
@ -134,17 +134,17 @@ export default {
|
|||
bookCoverAspectRatio() {
|
||||
return this.coverAspectRatio === this.$constants.BookCoverAspectRatio.SQUARE ? 1 : 1.6
|
||||
},
|
||||
audiobookId() {
|
||||
return this.audiobook ? this.audiobook.id : null
|
||||
libraryItemId() {
|
||||
return this.libraryItem ? this.libraryItem.id : null
|
||||
},
|
||||
book() {
|
||||
return this.audiobook ? this.audiobook.book || {} : {}
|
||||
media() {
|
||||
return this.libraryItem ? this.libraryItem.media || {} : {}
|
||||
},
|
||||
audiobookPath() {
|
||||
return this.audiobook ? this.audiobook.path : null
|
||||
mediaMetadata() {
|
||||
return this.media.metadata || {}
|
||||
},
|
||||
otherFiles() {
|
||||
return this.audiobook ? this.audiobook.otherFiles || [] : []
|
||||
libraryFiles() {
|
||||
return this.libraryItem ? this.libraryItem.libraryFiles || [] : []
|
||||
},
|
||||
userCanUpload() {
|
||||
return this.$store.getters['user/getUserCanUpload']
|
||||
|
@ -153,12 +153,11 @@ export default {
|
|||
return this.$store.getters['user/getToken']
|
||||
},
|
||||
localCovers() {
|
||||
return this.otherFiles
|
||||
.filter((f) => f.filetype === 'image')
|
||||
return this.libraryFiles
|
||||
.filter((f) => f.fileType === 'image')
|
||||
.map((file) => {
|
||||
var _file = { ...file }
|
||||
var imgRelPath = _file.path.replace(this.audiobookPath, '')
|
||||
_file.localPath = `/s/book/${this.audiobookId}/${imgRelPath}`
|
||||
_file.localPath = `/s/item/${this.libraryItemId}/${this.$encodeUriPath(file.metadata.relPath)}`
|
||||
return _file
|
||||
})
|
||||
}
|
||||
|
@ -170,7 +169,7 @@ export default {
|
|||
form.set('cover', this.selectedFile)
|
||||
|
||||
this.$axios
|
||||
.$post(`/api/books/${this.audiobook.id}/cover`, form)
|
||||
.$post(`/api/books/${this.libraryItemId}/cover`, form)
|
||||
.then((data) => {
|
||||
if (data.error) {
|
||||
this.$toast.error(data.error)
|
||||
|
@ -203,17 +202,17 @@ export default {
|
|||
},
|
||||
init() {
|
||||
this.showLocalCovers = false
|
||||
if (this.coversFound.length && (this.searchTitle !== this.book.title || this.searchAuthor !== this.book.authorFL)) {
|
||||
if (this.coversFound.length && (this.searchTitle !== this.mediaMetadata.title || this.searchAuthor !== this.mediaMetadata.authorName)) {
|
||||
this.coversFound = []
|
||||
this.hasSearched = false
|
||||
}
|
||||
this.imageUrl = this.book.cover || ''
|
||||
this.searchTitle = this.book.title || ''
|
||||
this.searchAuthor = this.book.authorFL || ''
|
||||
this.imageUrl = this.media.coverPath || ''
|
||||
this.searchTitle = this.mediaMetadata.title || ''
|
||||
this.searchAuthor = this.mediaMetadata.authorName || ''
|
||||
this.provider = localStorage.getItem('book-provider') || 'openlibrary'
|
||||
},
|
||||
removeCover() {
|
||||
if (!this.book.cover) {
|
||||
if (!this.media.coverPath) {
|
||||
this.imageUrl = ''
|
||||
return
|
||||
}
|
||||
|
@ -223,7 +222,7 @@ export default {
|
|||
this.updateCover(this.imageUrl)
|
||||
},
|
||||
async updateCover(cover) {
|
||||
if (cover === this.book.cover) {
|
||||
if (cover === this.media.coverPath) {
|
||||
console.warn('Cover has not changed..', cover)
|
||||
return
|
||||
}
|
||||
|
@ -233,7 +232,7 @@ export default {
|
|||
|
||||
// Download cover from url and use
|
||||
if (cover.startsWith('http:') || cover.startsWith('https:')) {
|
||||
success = await this.$axios.$post(`/api/books/${this.audiobook.id}/cover`, { url: cover }).catch((error) => {
|
||||
success = await this.$axios.$post(`/api/items/${this.libraryItemId}/cover`, { url: cover }).catch((error) => {
|
||||
console.error('Failed to download cover from url', error)
|
||||
if (error.response && error.response.data) {
|
||||
this.$toast.error(error.response.data)
|
||||
|
@ -247,7 +246,7 @@ export default {
|
|||
cover: cover
|
||||
}
|
||||
}
|
||||
success = await this.$axios.$patch(`/api/books/${this.audiobook.id}`, updatePayload).catch((error) => {
|
||||
success = await this.$axios.$patch(`/api/books/${this.libraryItemId}`, updatePayload).catch((error) => {
|
||||
console.error('Failed to update', error)
|
||||
if (error.response && error.response.data) {
|
||||
this.$toast.error(error.response.data)
|
||||
|
@ -259,7 +258,7 @@ export default {
|
|||
this.$toast.success('Update Successful')
|
||||
this.$emit('close')
|
||||
} else {
|
||||
this.imageUrl = this.book.cover || ''
|
||||
this.imageUrl = this.media.coverPath || ''
|
||||
}
|
||||
this.isProcessing = false
|
||||
},
|
||||
|
@ -292,7 +291,7 @@ export default {
|
|||
setCover(coverFile) {
|
||||
this.isProcessing = true
|
||||
this.$axios
|
||||
.$patch(`/api/books/${this.audiobook.id}/coverfile`, coverFile)
|
||||
.$patch(`/api/books/${this.libraryItemId}/coverfile`, coverFile)
|
||||
.then((data) => {
|
||||
console.log('response data', data)
|
||||
if (data && typeof data === 'string') {
|
||||
|
|
|
@ -13,7 +13,8 @@
|
|||
|
||||
<div class="flex mt-2 -mx-1">
|
||||
<div class="w-3/4 px-1">
|
||||
<ui-text-input-with-label v-model="details.author" label="Author" />
|
||||
<!-- <ui-text-input-with-label v-model="details.authors" label="Author" /> -->
|
||||
<p>Authors placeholder</p>
|
||||
</div>
|
||||
<div class="flex-grow px-1">
|
||||
<ui-text-input-with-label v-model="details.publishYear" type="number" label="Publish Year" />
|
||||
|
@ -22,10 +23,11 @@
|
|||
|
||||
<div class="flex mt-2 -mx-1">
|
||||
<div class="w-3/4 px-1">
|
||||
<ui-input-dropdown ref="seriesDropdown" v-model="details.series" label="Series" :items="series" />
|
||||
<p>Series placeholder</p>
|
||||
<!-- <ui-input-dropdown ref="seriesDropdown" v-model="details.series" label="Series" :items="series" /> -->
|
||||
</div>
|
||||
<div class="flex-grow px-1">
|
||||
<ui-text-input-with-label v-model="details.volumeNumber" label="Volume #" />
|
||||
<!-- <ui-text-input-with-label v-model="details.volumeNumber" label="Volume #" /> -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -64,7 +66,7 @@
|
|||
|
||||
<div class="absolute bottom-0 left-0 w-full py-4 bg-bg" :class="isScrollable ? 'box-shadow-md-up' : 'box-shadow-sm-up border-t border-primary border-opacity-50'">
|
||||
<div class="flex items-center px-4">
|
||||
<ui-btn v-if="userCanDelete" color="error" type="button" class="h-8" :padding-x="3" small @click.stop.prevent="deleteAudiobook">Remove</ui-btn>
|
||||
<ui-btn v-if="userCanDelete" color="error" type="button" class="h-8" :padding-x="3" small @click.stop.prevent="removeItem">Remove</ui-btn>
|
||||
|
||||
<div class="flex-grow" />
|
||||
|
||||
|
@ -91,7 +93,7 @@
|
|||
export default {
|
||||
props: {
|
||||
processing: Boolean,
|
||||
audiobook: {
|
||||
libraryItem: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
}
|
||||
|
@ -105,7 +107,6 @@ export default {
|
|||
author: null,
|
||||
narrator: null,
|
||||
series: null,
|
||||
volumeNumber: null,
|
||||
publishYear: null,
|
||||
publisher: null,
|
||||
language: null,
|
||||
|
@ -122,7 +123,7 @@ export default {
|
|||
}
|
||||
},
|
||||
watch: {
|
||||
audiobook: {
|
||||
libraryItem: {
|
||||
immediate: true,
|
||||
handler(newVal) {
|
||||
if (newVal) this.init()
|
||||
|
@ -142,13 +143,16 @@ export default {
|
|||
return this.$store.getters['user/getIsRoot']
|
||||
},
|
||||
isMissing() {
|
||||
return !!this.audiobook && !!this.audiobook.isMissing
|
||||
return !!this.libraryItem && !!this.libraryItem.isMissing
|
||||
},
|
||||
audiobookId() {
|
||||
return this.audiobook ? this.audiobook.id : null
|
||||
libraryItemId() {
|
||||
return this.libraryItem ? this.libraryItem.id : null
|
||||
},
|
||||
book() {
|
||||
return this.audiobook ? this.audiobook.book || {} : {}
|
||||
media() {
|
||||
return this.libraryItem ? this.libraryItem.media || {} : {}
|
||||
},
|
||||
mediaMetadata() {
|
||||
return this.media.metadata || {}
|
||||
},
|
||||
userCanDelete() {
|
||||
return this.$store.getters['user/getUserCanDelete']
|
||||
|
@ -166,7 +170,7 @@ export default {
|
|||
return this.$store.state.libraries.filterData || {}
|
||||
},
|
||||
libraryId() {
|
||||
return this.audiobook ? this.audiobook.libraryId : null
|
||||
return this.libraryItem ? this.libraryItem.libraryId : null
|
||||
},
|
||||
libraryProvider() {
|
||||
return this.$store.getters['libraries/getLibraryProvider'](this.libraryId) || 'google'
|
||||
|
@ -185,13 +189,13 @@ export default {
|
|||
author: this.details.author !== this.book.author ? this.details.author : null
|
||||
}
|
||||
this.$axios
|
||||
.$post(`/api/books/${this.audiobookId}/match`, matchOptions)
|
||||
.$post(`/api/books/${this.libraryItemId}/match`, matchOptions)
|
||||
.then((res) => {
|
||||
this.quickMatching = false
|
||||
if (res.warning) {
|
||||
this.$toast.warning(res.warning)
|
||||
} else if (res.updated) {
|
||||
this.$toast.success('Audiobook details updated')
|
||||
this.$toast.success('Item details updated')
|
||||
} else {
|
||||
this.$toast.info('No updates were made')
|
||||
}
|
||||
|
@ -236,7 +240,7 @@ export default {
|
|||
saveMetadata() {
|
||||
this.savingMetadata = true
|
||||
this.$root.socket.once('save_metadata_complete', this.saveMetadataComplete)
|
||||
this.$root.socket.emit('save_metadata', this.audiobookId)
|
||||
this.$root.socket.emit('save_metadata', this.libraryItemId)
|
||||
},
|
||||
submitForm() {
|
||||
if (this.isProcessing) {
|
||||
|
@ -260,7 +264,7 @@ export default {
|
|||
tags: this.newTags
|
||||
}
|
||||
|
||||
var updatedAudiobook = await this.$axios.$patch(`/api/books/${this.audiobook.id}`, updatePayload).catch((error) => {
|
||||
var updatedAudiobook = await this.$axios.$patch(`/api/books/${this.libraryItemId}`, updatePayload).catch((error) => {
|
||||
console.error('Failed to update', error)
|
||||
return false
|
||||
})
|
||||
|
@ -271,35 +275,34 @@ export default {
|
|||
}
|
||||
},
|
||||
init() {
|
||||
this.details.title = this.book.title
|
||||
this.details.subtitle = this.book.subtitle
|
||||
this.details.description = this.book.description
|
||||
this.details.author = this.book.author
|
||||
this.details.narrator = this.book.narrator
|
||||
this.details.genres = this.book.genres || []
|
||||
this.details.series = this.book.series
|
||||
this.details.volumeNumber = this.book.volumeNumber
|
||||
this.details.publishYear = this.book.publishYear
|
||||
this.details.publisher = this.book.publisher || null
|
||||
this.details.language = this.book.language || null
|
||||
this.details.isbn = this.book.isbn || null
|
||||
this.details.asin = this.book.asin || null
|
||||
this.details.title = this.mediaMetadata.title
|
||||
this.details.subtitle = this.mediaMetadata.subtitle
|
||||
this.details.description = this.mediaMetadata.description
|
||||
this.details.authors = this.mediaMetadata.authors
|
||||
this.details.narrator = this.mediaMetadata.narrator
|
||||
this.details.genres = this.mediaMetadata.genres || []
|
||||
this.details.series = this.mediaMetadata.series
|
||||
this.details.publishYear = this.mediaMetadata.publishYear
|
||||
this.details.publisher = this.mediaMetadata.publisher || null
|
||||
this.details.language = this.mediaMetadata.language || null
|
||||
this.details.isbn = this.mediaMetadata.isbn || null
|
||||
this.details.asin = this.mediaMetadata.asin || null
|
||||
|
||||
this.newTags = this.audiobook.tags || []
|
||||
this.newTags = this.media.tags || []
|
||||
},
|
||||
deleteAudiobook() {
|
||||
if (confirm(`Are you sure you want to remove this audiobook?\n\n*Does not delete your files, only removes the audiobook from AudioBookshelf`)) {
|
||||
removeItem() {
|
||||
if (confirm(`Are you sure you want to remove this item?\n\n*Does not delete your files, only removes the item from audiobookshelf`)) {
|
||||
this.isProcessing = true
|
||||
this.$axios
|
||||
.$delete(`/api/books/${this.audiobookId}`)
|
||||
.$delete(`/api/books/${this.libraryItemId}`)
|
||||
.then(() => {
|
||||
console.log('Audiobook removed')
|
||||
this.$toast.success('Audiobook Removed')
|
||||
console.log('Item removed')
|
||||
this.$toast.success('Item Removed')
|
||||
this.$emit('close')
|
||||
this.isProcessing = false
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Remove Audiobook failed', error)
|
||||
console.error('Remove item failed', error)
|
||||
this.isProcessing = false
|
||||
})
|
||||
}
|
||||
|
|
|
@ -65,7 +65,7 @@
|
|||
export default {
|
||||
props: {
|
||||
processing: Boolean,
|
||||
audiobook: {
|
||||
libraryItem: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
}
|
||||
|
@ -86,14 +86,14 @@ export default {
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
audiobookId() {
|
||||
return this.audiobook ? this.audiobook.id : null
|
||||
libraryItemId() {
|
||||
return this.libraryItem ? this.libraryItem.id : null
|
||||
},
|
||||
_audiobook() {
|
||||
return this.audiobook || {}
|
||||
media() {
|
||||
return this.libraryItem ? this.libraryItem.media || {} : {}
|
||||
},
|
||||
downloads() {
|
||||
return this.$store.getters['downloads/getDownloads'](this.audiobookId)
|
||||
return this.$store.getters['downloads/getDownloads'](this.libraryItemId)
|
||||
},
|
||||
singleAudioDownload() {
|
||||
return this.downloads.find((d) => d.type === 'singleAudio')
|
||||
|
@ -108,24 +108,21 @@ export default {
|
|||
return this.zipDownload ? this.zipDownload.status : false
|
||||
},
|
||||
isSingleTrack() {
|
||||
if (!this.audiobook.tracks) return false
|
||||
return this.audiobook.tracks.length === 1
|
||||
if (!this.libraryItem.tracks) return false
|
||||
return this.libraryItem.tracks.length === 1
|
||||
},
|
||||
singleTrackPath() {
|
||||
if (!this.isSingleTrack) return null
|
||||
return this.audiobook.tracks[0].path
|
||||
},
|
||||
audioFiles() {
|
||||
return this.audiobook ? this.audiobook.audioFiles || [] : []
|
||||
},
|
||||
otherFiles() {
|
||||
return this.audiobook ? this.audiobook.otherFiles || [] : []
|
||||
libraryFiles() {
|
||||
return this.libraryItem.libraryFiles
|
||||
},
|
||||
totalFiles() {
|
||||
return this.audioFiles.length + this.otherFiles.length
|
||||
return this.libraryFiles.length
|
||||
},
|
||||
showM4bDownload() {
|
||||
return !this._audiobook.isMissing && !this._audiobook.isInvalid && this._audiobook.tracks.length
|
||||
return !this.libraryItem.isMissing && this.media.tracks.length
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
</div>
|
||||
<div class="flex-grow" />
|
||||
<ui-btn small :color="showFullPath ? 'gray-600' : 'primary'" class="mr-2 hidden md:block" @click.stop="showFullPath = !showFullPath">Full Path</ui-btn>
|
||||
<nuxt-link v-if="userCanUpdate" :to="`/audiobook/${audiobook.id}/edit`">
|
||||
<nuxt-link v-if="userCanUpdate" :to="`/item/${libraryItem.id}/edit`">
|
||||
<ui-btn small color="primary">Manage Tracks</ui-btn>
|
||||
</nuxt-link>
|
||||
</div>
|
||||
|
@ -21,20 +21,20 @@
|
|||
<th class="text-left">Duration</th>
|
||||
<th v-if="showDownload" class="text-center">Download</th>
|
||||
</tr>
|
||||
<template v-for="track in tracksCleaned">
|
||||
<template v-for="track in tracks">
|
||||
<tr :key="track.index">
|
||||
<td class="text-center">
|
||||
<p>{{ track.index }}</p>
|
||||
</td>
|
||||
<td class="font-sans">{{ showFullPath ? track.fullPath : track.filename }}</td>
|
||||
<td class="font-sans">{{ showFullPath ? track.path : track.filename }}</td>
|
||||
<td class="font-mono">
|
||||
{{ $bytesPretty(track.size) }}
|
||||
{{ $bytesPretty(track.metadata.size) }}
|
||||
</td>
|
||||
<td class="font-mono">
|
||||
{{ $secondsToTimestamp(track.duration) }}
|
||||
</td>
|
||||
<td v-if="showDownload" class="font-mono text-center">
|
||||
<a :href="`/s/book/${audiobook.id}/${track.relativePath}?token=${userToken}`" download><span class="material-icons icon-text">download</span></a>
|
||||
<a :href="`/s/item/${libraryItem.id}/${track.metadata.relPath}?token=${userToken}`" download><span class="material-icons icon-text">download</span></a>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
@ -43,26 +43,26 @@
|
|||
<div v-else class="flex my-4 text-center justify-center text-xl">No Audio Tracks</div>
|
||||
</div>
|
||||
|
||||
<tables-all-files-table :audiobook="audiobook" />
|
||||
<tables-library-files-table :files="libraryFiles" :library-item-id="libraryItem.id" :is-missing="isMissing" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
audiobook: {
|
||||
libraryItem: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tracks: null,
|
||||
tracks: [],
|
||||
showFullPath: false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
audiobook: {
|
||||
libraryItem: {
|
||||
immediate: true,
|
||||
handler(newVal) {
|
||||
if (newVal) this.init()
|
||||
|
@ -70,22 +70,11 @@ export default {
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
audiobookPath() {
|
||||
return this.audiobook.path
|
||||
media() {
|
||||
return this.libraryItem.media || {}
|
||||
},
|
||||
tracksCleaned() {
|
||||
return this.tracks.map((track) => {
|
||||
var trackPath = track.path.replace(/\\/g, '/')
|
||||
var audiobookPath = this.audiobookPath.replace(/\\/g, '/')
|
||||
|
||||
return {
|
||||
...track,
|
||||
relativePath: trackPath
|
||||
.replace(audiobookPath + '/', '')
|
||||
.replace(/%/g, '%25')
|
||||
.replace(/#/g, '%23')
|
||||
}
|
||||
})
|
||||
libraryFiles() {
|
||||
return this.libraryItem.libraryFiles || []
|
||||
},
|
||||
userToken() {
|
||||
return this.$store.getters['user/getToken']
|
||||
|
@ -97,18 +86,18 @@ export default {
|
|||
return this.$store.getters['user/getUserCanDownload']
|
||||
},
|
||||
isMissing() {
|
||||
return this.audiobook.isMissing
|
||||
return this.libraryItem.isMissing
|
||||
},
|
||||
showDownload() {
|
||||
return this.userCanDownload && !this.isMissing
|
||||
},
|
||||
hasTracks() {
|
||||
return this.audiobook.tracks.length
|
||||
return this.tracks.length
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
this.tracks = this.audiobook.tracks
|
||||
this.tracks = this.media.tracks || []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -94,14 +94,14 @@
|
|||
export default {
|
||||
props: {
|
||||
processing: Boolean,
|
||||
audiobook: {
|
||||
libraryItem: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
audiobookId: null,
|
||||
libraryItemId: null,
|
||||
searchTitle: null,
|
||||
searchAuthor: null,
|
||||
lastSearch: null,
|
||||
|
@ -126,7 +126,7 @@ export default {
|
|||
}
|
||||
},
|
||||
watch: {
|
||||
audiobook: {
|
||||
libraryItem: {
|
||||
immediate: true,
|
||||
handler(newVal) {
|
||||
if (newVal) this.init()
|
||||
|
@ -209,19 +209,19 @@ export default {
|
|||
isbn: true
|
||||
}
|
||||
|
||||
if (this.audiobook.id !== this.audiobookId) {
|
||||
if (this.libraryItem.id !== this.libraryItemId) {
|
||||
this.searchResults = []
|
||||
this.hasSearched = false
|
||||
this.audiobookId = this.audiobook.id
|
||||
this.libraryItemId = this.libraryItem.id
|
||||
}
|
||||
|
||||
if (!this.audiobook.book || !this.audiobook.book.title) {
|
||||
if (!this.libraryItem.media || !this.libraryItem.media.metadata.title) {
|
||||
this.searchTitle = null
|
||||
this.searchAuthor = null
|
||||
return
|
||||
}
|
||||
this.searchTitle = this.audiobook.book.title
|
||||
this.searchAuthor = this.audiobook.book.authorFL || ''
|
||||
this.searchTitle = this.libraryItem.media.metadata.title
|
||||
this.searchAuthor = this.libraryItem.media.metadata.authorName || ''
|
||||
this.provider = localStorage.getItem('book-provider') || 'google'
|
||||
},
|
||||
selectMatch(match) {
|
||||
|
@ -247,7 +247,7 @@ export default {
|
|||
var coverPayload = {
|
||||
url: updatePayload.cover
|
||||
}
|
||||
var success = await this.$axios.$post(`/api/books/${this.audiobook.id}/cover`, coverPayload).catch((error) => {
|
||||
var success = await this.$axios.$post(`/api/books/${this.libraryItemId}/cover`, coverPayload).catch((error) => {
|
||||
console.error('Failed to update', error)
|
||||
return false
|
||||
})
|
||||
|
@ -264,7 +264,7 @@ export default {
|
|||
var bookUpdatePayload = {
|
||||
book: updatePayload
|
||||
}
|
||||
var success = await this.$axios.$patch(`/api/books/${this.audiobook.id}`, bookUpdatePayload).catch((error) => {
|
||||
var success = await this.$axios.$patch(`/api/books/${this.libraryItemId}`, bookUpdatePayload).catch((error) => {
|
||||
console.error('Failed to update', error)
|
||||
return false
|
||||
})
|
||||
|
|
|
@ -1,110 +0,0 @@
|
|||
<template>
|
||||
<div class="w-full h-full overflow-y-auto overflow-x-hidden px-4 py-6">
|
||||
<template v-if="hasTracks">
|
||||
<div class="w-full bg-primary px-4 py-2 flex items-center">
|
||||
<div class="h-7 w-7 rounded-full bg-white bg-opacity-10 flex items-center justify-center">
|
||||
<span class="text-sm font-mono">{{ tracks.length }}</span>
|
||||
</div>
|
||||
<div class="flex-grow" />
|
||||
<ui-btn small :color="showFullPath ? 'gray-600' : 'primary'" class="mr-2 hidden md:block" @click.stop="showFullPath = !showFullPath">Full Path</ui-btn>
|
||||
<nuxt-link v-if="userCanUpdate" :to="`/audiobook/${audiobook.id}/edit`" class="mr-4">
|
||||
<ui-btn small color="primary">Manage Tracks</ui-btn>
|
||||
</nuxt-link>
|
||||
</div>
|
||||
<table class="text-sm tracksTable">
|
||||
<tr class="font-book">
|
||||
<th>#</th>
|
||||
<th class="text-left">Filename</th>
|
||||
<th class="text-left">Size</th>
|
||||
<th class="text-left">Duration</th>
|
||||
<th v-if="showDownload" class="text-center">Download</th>
|
||||
</tr>
|
||||
<template v-for="track in tracksCleaned">
|
||||
<tr :key="track.index">
|
||||
<td class="text-center">
|
||||
<p>{{ track.index }}</p>
|
||||
</td>
|
||||
<td class="font-sans">{{ showFullPath ? track.fullPath : track.filename }}</td>
|
||||
<td class="font-mono">
|
||||
{{ $bytesPretty(track.size) }}
|
||||
</td>
|
||||
<td class="font-mono">
|
||||
{{ $secondsToTimestamp(track.duration) }}
|
||||
</td>
|
||||
<td v-if="showDownload" class="font-mono text-center">
|
||||
<a :href="`/s/book/${audiobook.id}/${track.relativePath}?token=${userToken}`" download><span class="material-icons icon-text">download</span></a>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</table>
|
||||
</template>
|
||||
<div v-else class="flex my-4 text-center justify-center text-xl">No Audio Tracks</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
audiobook: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tracks: null,
|
||||
showFullPath: false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
audiobook: {
|
||||
immediate: true,
|
||||
handler(newVal) {
|
||||
if (newVal) this.init()
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
audiobookPath() {
|
||||
return this.audiobook.path
|
||||
},
|
||||
tracksCleaned() {
|
||||
return this.tracks.map((track) => {
|
||||
var trackPath = track.path.replace(/\\/g, '/')
|
||||
var audiobookPath = this.audiobookPath.replace(/\\/g, '/')
|
||||
|
||||
return {
|
||||
...track,
|
||||
relativePath: trackPath
|
||||
.replace(audiobookPath + '/', '')
|
||||
.replace(/%/g, '%25')
|
||||
.replace(/#/g, '%23')
|
||||
}
|
||||
})
|
||||
},
|
||||
userToken() {
|
||||
return this.$store.getters['user/getToken']
|
||||
},
|
||||
userCanUpdate() {
|
||||
return this.$store.getters['user/getUserCanUpdate']
|
||||
},
|
||||
userCanDownload() {
|
||||
return this.$store.getters['user/getUserCanDownload']
|
||||
},
|
||||
isMissing() {
|
||||
return this.audiobook.isMissing
|
||||
},
|
||||
showDownload() {
|
||||
return this.userCanDownload && !this.isMissing
|
||||
},
|
||||
hasTracks() {
|
||||
return this.audiobook.tracks.length
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
this.tracks = this.audiobook.tracks
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -1,14 +1,11 @@
|
|||
<template>
|
||||
<div class="w-full my-2">
|
||||
<div class="w-full bg-primary px-4 md:px-6 py-2 flex items-center cursor-pointer" @click.stop="clickBar">
|
||||
<p class="pr-2 md:pr-4">Other Files</p>
|
||||
<p class="pr-2 md:pr-4">Library Files</p>
|
||||
<div class="h-5 md:h-7 w-5 md:w-7 rounded-full bg-white bg-opacity-10 flex items-center justify-center">
|
||||
<span class="text-sm font-mono">{{ files.length }}</span>
|
||||
</div>
|
||||
<div class="flex-grow" />
|
||||
<!-- <nuxt-link :to="`/audiobook/${audiobookId}/edit`" class="mr-4">
|
||||
<ui-btn small color="primary">Manage Tracks</ui-btn>
|
||||
</nuxt-link> -->
|
||||
<ui-btn small :color="showFullPath ? 'gray-600' : 'primary'" class="mr-2 hidden md:block" @click.stop="showFullPath = !showFullPath">Full Path</ui-btn>
|
||||
<div class="cursor-pointer h-10 w-10 rounded-full hover:bg-black-400 flex justify-center items-center duration-500" :class="showFiles ? 'transform rotate-180' : ''">
|
||||
<span class="material-icons text-4xl">expand_more</span>
|
||||
|
@ -22,19 +19,19 @@
|
|||
<th class="text-left px-4 w-24">Filetype</th>
|
||||
<th v-if="userCanDownload && !isMissing" class="text-center w-20">Download</th>
|
||||
</tr>
|
||||
<template v-for="file in otherFilesCleaned">
|
||||
<template v-for="file in files">
|
||||
<tr :key="file.path">
|
||||
<td class="font-book pl-2">
|
||||
{{ showFullPath ? file.fullPath : file.path }}
|
||||
{{ showFullPath ? file.metadata.path : file.metadata.relPath }}
|
||||
</td>
|
||||
<td class="text-xs">
|
||||
<div class="flex items-center">
|
||||
<span v-if="file.filetype === 'ebook'" class="material-icons text-base mr-1 cursor-pointer text-white text-opacity-60 hover:text-opacity-100" @click="readEbookClick(file)">auto_stories </span>
|
||||
<p>{{ file.filetype }}</p>
|
||||
<p>{{ file.metadata.ext }}</p>
|
||||
</div>
|
||||
</td>
|
||||
<td v-if="userCanDownload && !isMissing" class="text-center">
|
||||
<a :href="`/s/book/${audiobookId}/${file.relativePath}?token=${userToken}`" download><span class="material-icons icon-text">download</span></a>
|
||||
<a :href="`/s/item/${libraryItemId}${$encodeUriPath(file.metadata.relPath)}?token=${userToken}`" download><span class="material-icons icon-text">download</span></a>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
@ -51,10 +48,8 @@ export default {
|
|||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
audiobook: {
|
||||
type: Object,
|
||||
default: () => null
|
||||
}
|
||||
libraryItemId: String,
|
||||
isMissing: Boolean
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -63,40 +58,19 @@ export default {
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
audiobookId() {
|
||||
return this.audiobook.id
|
||||
},
|
||||
audiobookPath() {
|
||||
return this.audiobook.path
|
||||
},
|
||||
otherFilesCleaned() {
|
||||
return this.files.map((file) => {
|
||||
return {
|
||||
...file,
|
||||
relativePath: this.getRelativePath(file.path)
|
||||
}
|
||||
})
|
||||
},
|
||||
userToken() {
|
||||
return this.$store.getters['user/getToken']
|
||||
},
|
||||
isMissing() {
|
||||
return this.audiobook.isMissing
|
||||
},
|
||||
userCanDownload() {
|
||||
return this.$store.getters['user/getUserCanDownload']
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
readEbookClick(file) {
|
||||
this.$store.commit('showEReaderForFile', { audiobook: this.audiobook, file })
|
||||
// this.$store.commit('showEReaderForFile', { audiobook: this.audiobook, file })
|
||||
},
|
||||
clickBar() {
|
||||
this.showFiles = !this.showFiles
|
||||
},
|
||||
getRelativePath(path) {
|
||||
var relativePath = path.replace(/\\/g, '/').replace(this.audiobookPath.replace(/\\/g, '/') + '/', '')
|
||||
return this.$encodeUriPath(relativePath)
|
||||
}
|
||||
},
|
||||
mounted() {}
|
|
@ -8,7 +8,7 @@
|
|||
<!-- <span class="bg-black-400 rounded-xl py-1 px-2 text-sm font-mono">{{ tracks.length }}</span> -->
|
||||
<div class="flex-grow" />
|
||||
<ui-btn small :color="showFullPath ? 'gray-600' : 'primary'" class="mr-2 hidden md:block" @click.stop="showFullPath = !showFullPath">Full Path</ui-btn>
|
||||
<nuxt-link v-if="userCanUpdate" :to="`/audiobook/${audiobookId}/edit`" class="mr-2 md:mr-4">
|
||||
<nuxt-link v-if="userCanUpdate" :to="`/audiobook/${libraryItemId}/edit`" class="mr-2 md:mr-4">
|
||||
<ui-btn small color="primary">Manage Tracks</ui-btn>
|
||||
</nuxt-link>
|
||||
<div class="cursor-pointer h-10 w-10 rounded-full hover:bg-black-400 flex justify-center items-center duration-500" :class="showTracks ? 'transform rotate-180' : ''">
|
||||
|
@ -25,20 +25,20 @@
|
|||
<th class="text-left w-20">Duration</th>
|
||||
<th v-if="userCanDownload" class="text-center w-20">Download</th>
|
||||
</tr>
|
||||
<template v-for="track in tracksCleaned">
|
||||
<template v-for="track in tracks">
|
||||
<tr :key="track.index">
|
||||
<td class="text-center">
|
||||
<p>{{ track.index }}</p>
|
||||
</td>
|
||||
<td class="font-sans">{{ showFullPath ? track.fullPath : track.filename }}</td>
|
||||
<td class="font-sans">{{ showFullPath ? track.metadata.path : track.metadata.filename }}</td>
|
||||
<td class="font-mono">
|
||||
{{ $bytesPretty(track.size) }}
|
||||
{{ $bytesPretty(track.metadata.size) }}
|
||||
</td>
|
||||
<td class="font-mono">
|
||||
{{ $secondsToTimestamp(track.duration) }}
|
||||
</td>
|
||||
<td v-if="userCanDownload" class="text-center">
|
||||
<a :href="`/s/book/${audiobook.id}/${track.relativePath}?token=${userToken}`" download><span class="material-icons icon-text">download</span></a>
|
||||
<a :href="`/s/item/${libraryItemId}${$encodeUriPath(track.metadata.relPath)}?token=${userToken}`" download><span class="material-icons icon-text">download</span></a>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
@ -55,10 +55,7 @@ export default {
|
|||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
audiobook: {
|
||||
type: Object,
|
||||
default: () => null
|
||||
}
|
||||
libraryItemId: String
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -67,20 +64,6 @@ export default {
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
audiobookId() {
|
||||
return this.audiobook.id
|
||||
},
|
||||
audiobookPath() {
|
||||
return this.audiobook.path
|
||||
},
|
||||
tracksCleaned() {
|
||||
return this.tracks.map((track) => {
|
||||
return {
|
||||
...track,
|
||||
relativePath: this.getRelativePath(track.path)
|
||||
}
|
||||
})
|
||||
},
|
||||
userToken() {
|
||||
return this.$store.getters['user/getToken']
|
||||
},
|
||||
|
@ -94,10 +77,6 @@ export default {
|
|||
methods: {
|
||||
clickBar() {
|
||||
this.showTracks = !this.showTracks
|
||||
},
|
||||
getRelativePath(path) {
|
||||
var relativePath = path.replace(/\\/g, '/').replace(this.audiobookPath.replace(/\\/g, '/') + '/', '')
|
||||
return this.$encodeUriPath(relativePath)
|
||||
}
|
||||
},
|
||||
mounted() {}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue