mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-08-03 17:54:54 +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
|
@ -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>
|
Loading…
Add table
Add a link
Reference in a new issue