mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-01-23 03:19:38 +00:00
New data model removing media entity for books
This commit is contained in:
parent
920ca683b9
commit
3150822117
44 changed files with 733 additions and 798 deletions
|
|
@ -100,8 +100,8 @@ export default {
|
|||
selectedLibraryItems() {
|
||||
return this.$store.state.selectedLibraryItems
|
||||
},
|
||||
userItemProgress() {
|
||||
return this.$store.state.user.user.libraryItemProgress || []
|
||||
userMediaProgress() {
|
||||
return this.$store.state.user.user.mediaProgress || []
|
||||
},
|
||||
userCanUpdate() {
|
||||
return this.$store.getters['user/getUserCanUpdate']
|
||||
|
|
@ -115,7 +115,7 @@ export default {
|
|||
selectedIsFinished() {
|
||||
// Find an item that is not finished, if none then all items finished
|
||||
return !this.selectedLibraryItems.find((libraryItemId) => {
|
||||
var itemProgress = this.userItemProgress.find((lip) => lip.id === libraryItemId)
|
||||
var itemProgress = this.userMediaProgress.find((lip) => lip.id === libraryItemId)
|
||||
return !itemProgress || !itemProgress.isFinished
|
||||
})
|
||||
},
|
||||
|
|
|
|||
|
|
@ -82,9 +82,6 @@ export default {
|
|||
shelfHeight() {
|
||||
return this.bookCoverHeight + 48
|
||||
},
|
||||
userAudiobooks() {
|
||||
return this.$store.state.user.user ? this.$store.state.user.user.audiobooks || {} : {}
|
||||
},
|
||||
paddingLeft() {
|
||||
if (window.innerWidth < 768) return 1
|
||||
return 2.5
|
||||
|
|
|
|||
|
|
@ -93,12 +93,12 @@ export default {
|
|||
user() {
|
||||
return this.$store.state.user.user
|
||||
},
|
||||
userLibraryItemProgress() {
|
||||
userMediaProgress() {
|
||||
if (!this.libraryItemId) return
|
||||
return this.$store.getters['user/getUserLibraryItemProgress'](this.libraryItemId)
|
||||
return this.$store.getters['user/getUserMediaProgress'](this.libraryItemId)
|
||||
},
|
||||
userItemCurrentTime() {
|
||||
return this.userLibraryItemProgress ? this.userLibraryItemProgress.currentTime || 0 : 0
|
||||
return this.userMediaProgress ? this.userMediaProgress.currentTime || 0 : 0
|
||||
},
|
||||
bookmarks() {
|
||||
if (!this.libraryItemId) return []
|
||||
|
|
|
|||
|
|
@ -17,11 +17,12 @@
|
|||
<div v-if="booksInSeries" class="absolute z-20 top-1.5 right-1.5 rounded-md leading-3 text-sm p-1 font-semibold text-white flex items-center justify-center" style="background-color: #cd9d49dd">{{ booksInSeries }}</div>
|
||||
|
||||
<div class="w-full h-full absolute top-0 left-0 rounded overflow-hidden z-10">
|
||||
<div v-show="audiobook && !imageReady" class="absolute top-0 left-0 w-full h-full flex items-center justify-center" :style="{ padding: sizeMultiplier * 0.5 + 'rem' }">
|
||||
<div v-show="libraryItem && !imageReady" class="absolute top-0 left-0 w-full h-full flex items-center justify-center" :style="{ padding: sizeMultiplier * 0.5 + 'rem' }">
|
||||
<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 transition-opacity duration-300" :class="showCoverBg ? 'object-contain' : 'object-fill'" @load="imageLoaded" :style="{ opacity: imageReady ? 1 : 0 }" />
|
||||
<!-- Cover Image -->
|
||||
<img v-show="libraryItem" 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' }">
|
||||
|
|
@ -38,7 +39,7 @@
|
|||
<div v-if="!booksInSeries" class="absolute bottom-0 left-0 h-1 shadow-sm max-w-full z-10 rounded-b" :class="itemIsFinished ? 'bg-success' : 'bg-yellow-400'" :style="{ width: width * userProgressPercent + 'px' }"></div>
|
||||
|
||||
<!-- Overlay is not shown if collapsing series in library -->
|
||||
<div v-show="!booksInSeries && audiobook && (isHovering || isSelectionMode || isMoreMenuOpen)" class="w-full h-full absolute top-0 left-0 z-10 bg-black rounded hidden md:block" :class="overlayWrapperClasslist">
|
||||
<div v-show="!booksInSeries && libraryItem && (isHovering || isSelectionMode || isMoreMenuOpen)" class="w-full h-full absolute top-0 left-0 z-10 bg-black rounded hidden md:block" :class="overlayWrapperClasslist">
|
||||
<div v-show="showPlayButton" class="h-full flex items-center justify-center pointer-events-none">
|
||||
<div class="hover:text-white text-gray-200 hover:scale-110 transform duration-200 pointer-events-auto" @click.stop.prevent="play">
|
||||
<span class="material-icons" :style="{ fontSize: playIconFontSize + 'rem' }">play_circle_filled</span>
|
||||
|
|
@ -65,7 +66,7 @@
|
|||
</div>
|
||||
|
||||
<!-- Series name overlay -->
|
||||
<div v-if="booksInSeries && audiobook && isHovering" class="w-full h-full absolute top-0 left-0 z-10 bg-black bg-opacity-60 rounded flex items-center justify-center" :style="{ padding: sizeMultiplier + 'rem' }">
|
||||
<div v-if="booksInSeries && libraryItem && isHovering" class="w-full h-full absolute top-0 left-0 z-10 bg-black bg-opacity-60 rounded flex items-center justify-center" :style="{ padding: sizeMultiplier + 'rem' }">
|
||||
<p class="text-gray-200 text-center" :style="{ fontSize: 1.1 * sizeMultiplier + 'rem' }">{{ series }}</p>
|
||||
</div>
|
||||
|
||||
|
|
@ -115,7 +116,7 @@ export default {
|
|||
isHovering: false,
|
||||
isMoreMenuOpen: false,
|
||||
isProcessingReadUpdate: false,
|
||||
audiobook: null,
|
||||
libraryItem: null,
|
||||
imageReady: false,
|
||||
rescanning: false,
|
||||
selected: false,
|
||||
|
|
@ -127,7 +128,7 @@ export default {
|
|||
bookMount: {
|
||||
handler(newVal) {
|
||||
if (newVal) {
|
||||
this.audiobook = newVal
|
||||
this.libraryItem = newVal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -137,7 +138,7 @@ export default {
|
|||
return this.store.state.showExperimentalFeatures
|
||||
},
|
||||
_libraryItem() {
|
||||
return this.audiobook || {}
|
||||
return this.libraryItem || {}
|
||||
},
|
||||
media() {
|
||||
return this._libraryItem.media || {}
|
||||
|
|
@ -161,18 +162,17 @@ export default {
|
|||
return this._libraryItem.libraryId
|
||||
},
|
||||
hasEbook() {
|
||||
if (!this.media.ebooks) return 0
|
||||
return this.media.ebooks.length
|
||||
return this.media.ebookFile
|
||||
},
|
||||
hasAudiobook() {
|
||||
if (!this.media.audiobooks) return 0
|
||||
return this.media.audiobooks.length
|
||||
numTracks() {
|
||||
if (this.media.tracks) return this.media.tracks.length
|
||||
return this.media.numTracks || 0 // toJSONMinified
|
||||
},
|
||||
processingBatch() {
|
||||
return this.store.state.processingBatch
|
||||
},
|
||||
booksInSeries() {
|
||||
// Only added to audiobook object when collapseSeries is enabled
|
||||
// Only added to item object when collapseSeries is enabled
|
||||
return this._libraryItem.booksInSeries
|
||||
},
|
||||
hasCover() {
|
||||
|
|
@ -228,7 +228,7 @@ export default {
|
|||
return null
|
||||
},
|
||||
userProgress() {
|
||||
return this.store.getters['user/getUserLibraryItemProgress'](this.libraryItemId)
|
||||
return this.store.getters['user/getUserMediaProgress'](this.libraryItemId)
|
||||
},
|
||||
userProgressPercent() {
|
||||
return this.userProgress ? this.userProgress.progress || 0 : 0
|
||||
|
|
@ -246,7 +246,7 @@ export default {
|
|||
return !this.isSelectionMode && this.showExperimentalFeatures && !this.showPlayButton && this.hasEbook
|
||||
},
|
||||
showPlayButton() {
|
||||
return !this.isSelectionMode && !this.isMissing && !this.isInvalid && this.hasAudiobook && !this.isStreaming
|
||||
return !this.isSelectionMode && !this.isMissing && !this.isInvalid && this.numTracks && !this.isStreaming
|
||||
},
|
||||
showSmallEBookIcon() {
|
||||
return !this.isSelectionMode && this.showExperimentalFeatures && this.hasEbook
|
||||
|
|
@ -264,8 +264,8 @@ export default {
|
|||
return this._libraryItem.hasInvalidParts
|
||||
},
|
||||
errorText() {
|
||||
if (this.isMissing) return 'Audiobook directory is missing!'
|
||||
else if (this.isInvalid) return 'Audiobook has no audio tracks & ebook'
|
||||
if (this.isMissing) return 'Item directory is missing!'
|
||||
else if (this.isInvalid) return 'Item has no audio tracks & ebook'
|
||||
var txt = ''
|
||||
if (this.hasMissingParts) {
|
||||
txt = `${this.hasMissingParts} missing parts.`
|
||||
|
|
@ -312,7 +312,7 @@ export default {
|
|||
}
|
||||
]
|
||||
if (this.userCanUpdate) {
|
||||
if (this.hasAudiobook) {
|
||||
if (this.numTracks) {
|
||||
items.push({
|
||||
func: 'showEditModalTracks',
|
||||
text: 'Tracks'
|
||||
|
|
@ -382,7 +382,7 @@ export default {
|
|||
if (!val) this.selected = false
|
||||
},
|
||||
setEntity(libraryItem) {
|
||||
this.audiobook = libraryItem
|
||||
this.libraryItem = libraryItem
|
||||
},
|
||||
clickCard(e) {
|
||||
if (this.isSelectionMode) {
|
||||
|
|
@ -398,7 +398,7 @@ export default {
|
|||
}
|
||||
},
|
||||
editClick() {
|
||||
this.$emit('edit', this.audiobook)
|
||||
this.$emit('edit', this.libraryItem)
|
||||
},
|
||||
toggleFinished() {
|
||||
var updatePayload = {
|
||||
|
|
@ -444,18 +444,18 @@ export default {
|
|||
},
|
||||
showEditModalTracks() {
|
||||
// More menu func
|
||||
this.store.commit('showEditModalOnTab', { libraryItem: this.audiobook, tab: 'tracks' })
|
||||
this.store.commit('showEditModalOnTab', { libraryItem: this.libraryItem, tab: 'tracks' })
|
||||
},
|
||||
showEditModalMatch() {
|
||||
// More menu func
|
||||
this.store.commit('showEditModalOnTab', { libraryItem: this.audiobook, tab: 'match' })
|
||||
this.store.commit('showEditModalOnTab', { libraryItem: this.libraryItem, tab: 'match' })
|
||||
},
|
||||
showEditModalDownload() {
|
||||
// More menu func
|
||||
this.store.commit('showEditModalOnTab', { libraryItem: this.audiobook, tab: 'download' })
|
||||
this.store.commit('showEditModalOnTab', { libraryItem: this.libraryItem, tab: 'download' })
|
||||
},
|
||||
openCollections() {
|
||||
this.store.commit('setSelectedLibraryItem', this.audiobook)
|
||||
this.store.commit('setSelectedLibraryItem', this.libraryItem)
|
||||
this.store.commit('globals/setShowUserCollectionsModal', true)
|
||||
},
|
||||
createMoreMenu() {
|
||||
|
|
@ -509,12 +509,12 @@ export default {
|
|||
this.createMoreMenu()
|
||||
},
|
||||
clickReadEBook() {
|
||||
this.store.commit('showEReader', this.audiobook)
|
||||
this.store.commit('showEReader', this.media.ebookFile)
|
||||
},
|
||||
selectBtnClick() {
|
||||
if (this.processingBatch) return
|
||||
this.selected = !this.selected
|
||||
this.$emit('select', this.audiobook)
|
||||
this.$emit('select', this.libraryItem)
|
||||
},
|
||||
play() {
|
||||
var eventBus = this.$eventBus || this.$nuxt.$eventBus
|
||||
|
|
|
|||
|
|
@ -138,6 +138,7 @@ export default {
|
|||
this.$nextTick(() => {
|
||||
this.imageReady = true
|
||||
})
|
||||
|
||||
if (this.$refs.cover && this.cover !== this.placeholderUrl) {
|
||||
var { naturalWidth, naturalHeight } = this.$refs.cover
|
||||
var aspectRatio = naturalHeight / naturalWidth
|
||||
|
|
|
|||
|
|
@ -1,36 +1,33 @@
|
|||
<template>
|
||||
<div class="w-full h-full overflow-y-auto overflow-x-hidden px-4 py-6">
|
||||
<div v-if="!audiobooks.length" class="text-center py-8 text-lg">No Audiobooks</div>
|
||||
<template v-for="audiobook in audiobooks">
|
||||
<div :key="audiobook.id" class="w-full mb-4">
|
||||
<div class="w-full p-4 bg-primary">
|
||||
<p>Audiobook Chapters ({{ audiobook.name }})</p>
|
||||
</div>
|
||||
<div v-if="!audiobook.chapters.length" class="flex my-4 text-center justify-center text-xl">No Chapters</div>
|
||||
<table v-else class="text-sm tracksTable">
|
||||
<tr class="font-book">
|
||||
<th class="text-left w-16"><span class="px-4">Id</span></th>
|
||||
<th class="text-left">Title</th>
|
||||
<th class="text-center">Start</th>
|
||||
<th class="text-center">End</th>
|
||||
</tr>
|
||||
<tr v-for="chapter in audiobook.chapters" :key="chapter.id">
|
||||
<td class="text-left">
|
||||
<p class="px-4">{{ chapter.id }}</p>
|
||||
</td>
|
||||
<td class="font-book">
|
||||
{{ chapter.title }}
|
||||
</td>
|
||||
<td class="font-mono text-center">
|
||||
{{ $secondsToTimestamp(chapter.start) }}
|
||||
</td>
|
||||
<td class="font-mono text-center">
|
||||
{{ $secondsToTimestamp(chapter.end) }}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="w-full mb-4">
|
||||
<div class="w-full p-4 bg-primary">
|
||||
<p>Audiobook Chapters</p>
|
||||
</div>
|
||||
</template>
|
||||
<div v-if="!chapters.length" class="flex my-4 text-center justify-center text-xl">No Chapters</div>
|
||||
<table v-else class="text-sm tracksTable">
|
||||
<tr class="font-book">
|
||||
<th class="text-left w-16"><span class="px-4">Id</span></th>
|
||||
<th class="text-left">Title</th>
|
||||
<th class="text-center">Start</th>
|
||||
<th class="text-center">End</th>
|
||||
</tr>
|
||||
<tr v-for="chapter in chapters" :key="chapter.id">
|
||||
<td class="text-left">
|
||||
<p class="px-4">{{ chapter.id }}</p>
|
||||
</td>
|
||||
<td class="font-book">
|
||||
{{ chapter.title }}
|
||||
</td>
|
||||
<td class="font-mono text-center">
|
||||
{{ $secondsToTimestamp(chapter.start) }}
|
||||
</td>
|
||||
<td class="font-mono text-center">
|
||||
{{ $secondsToTimestamp(chapter.end) }}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -49,8 +46,8 @@ export default {
|
|||
media() {
|
||||
return this.libraryItem ? this.libraryItem.media || {} : {}
|
||||
},
|
||||
audiobooks() {
|
||||
return this.media.audiobooks || []
|
||||
chapters() {
|
||||
return this.media.chapters || []
|
||||
}
|
||||
},
|
||||
methods: {}
|
||||
|
|
|
|||
|
|
@ -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" @mousedown.prevent>
|
||||
<nuxt-link v-if="userCanUpdate" :to="`/audiobook/${libraryItemId}/edit`" class="mr-2 md:mr-4" @mousedown.prevent>
|
||||
<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' : ''">
|
||||
|
|
@ -38,7 +38,7 @@
|
|||
{{ $secondsToTimestamp(track.duration) }}
|
||||
</td>
|
||||
<td v-if="userCanDownload" class="text-center">
|
||||
<a :href="`/s/item/${audiobookId}${$encodeUriPath(track.metadata.relPath)}?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>
|
||||
|
|
@ -59,7 +59,7 @@ export default {
|
|||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
audiobookId: String
|
||||
libraryItemId: String
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -82,18 +82,17 @@ export default {
|
|||
mediaMetadata() {
|
||||
return this.media.metadata || {}
|
||||
},
|
||||
tracks() {
|
||||
return this.media.tracks || []
|
||||
},
|
||||
bookTitle() {
|
||||
return this.mediaMetadata.title || ''
|
||||
},
|
||||
bookAuthor() {
|
||||
return (this.mediaMetadata.authors || []).map((au) => au.name).join(', ')
|
||||
},
|
||||
defaultAudiobook() {
|
||||
if (!this.media.audiobooks.length) return null
|
||||
return this.media.audiobooks[0]
|
||||
},
|
||||
bookDuration() {
|
||||
return this.$secondsToTimestamp(this.defaultAudiobook.duration)
|
||||
return this.$secondsToTimestamp(this.media.duration)
|
||||
},
|
||||
isMissing() {
|
||||
return this.book.isMissing
|
||||
|
|
@ -105,10 +104,10 @@ export default {
|
|||
return this.$store.getters['getLibraryItemIdStreaming'] === this.book.id
|
||||
},
|
||||
showPlayBtn() {
|
||||
return !this.isMissing && !this.isInvalid && !this.isStreaming && this.defaultAudiobook
|
||||
return !this.isMissing && !this.isInvalid && !this.isStreaming && this.tracks.length
|
||||
},
|
||||
itemProgress() {
|
||||
return this.$store.getters['user/getUserLibraryItemProgress'](this.book.id)
|
||||
return this.$store.getters['user/getUserMediaProgress'](this.book.id)
|
||||
},
|
||||
userIsFinished() {
|
||||
return this.itemProgress ? !!this.itemProgress.isFinished : false
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<textarea v-model="inputValue" :rows="rows" :readonly="readonly" :disabled="disabled" :placeholder="placeholder" class="py-2 px-3 rounded bg-primary text-gray-200 focus:border-gray-500 focus:outline-none" :class="transparent ? '' : 'border border-gray-600'" @change="change" />
|
||||
<textarea ref="input" v-model="inputValue" :rows="rows" :readonly="readonly" :disabled="disabled" :placeholder="placeholder" class="py-2 px-3 rounded bg-primary text-gray-200 focus:border-gray-500 focus:outline-none" :class="transparent ? '' : 'border border-gray-600'" @change="change" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
|
@ -31,6 +31,11 @@ export default {
|
|||
methods: {
|
||||
change(e) {
|
||||
this.$emit('change', e.target.value)
|
||||
},
|
||||
blur() {
|
||||
if (this.$refs.input && this.$refs.input.blur) {
|
||||
this.$refs.input.blur()
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div class="w-full">
|
||||
<p class="px-1 text-sm font-semibold" :class="disabled ? 'text-gray-400' : ''">{{ label }}</p>
|
||||
<ui-textarea-input v-model="inputValue" :disabled="disabled" :rows="rows" class="w-full" />
|
||||
<ui-textarea-input ref="input" v-model="inputValue" :disabled="disabled" :rows="rows" class="w-full" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -29,7 +29,13 @@ export default {
|
|||
}
|
||||
}
|
||||
},
|
||||
methods: {},
|
||||
methods: {
|
||||
blur() {
|
||||
if (this.$refs.input && this.$refs.input.blur) {
|
||||
this.$refs.input.blur()
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -16,14 +16,15 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<tables-tracks-table :key="audiobook.id" :title="`Audiobook Tracks (${audiobook.name})`" :tracks="audiobook.tracks" :audiobook-id="audiobook.id" class="mt-6" />
|
||||
<tables-tracks-table :title="`Audiobook Tracks`" :tracks="media.tracks" :library-item-id="libraryItemId" class="mt-6" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
audiobook: {
|
||||
libraryItemId: String,
|
||||
media: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
}
|
||||
|
|
@ -64,10 +65,10 @@ export default {
|
|||
return chunks
|
||||
},
|
||||
missingParts() {
|
||||
return this.audiobook.missingParts || []
|
||||
return this.media.missingParts || []
|
||||
},
|
||||
invalidParts() {
|
||||
return this.audiobook.invalidParts || []
|
||||
return this.media.invalidParts || []
|
||||
}
|
||||
},
|
||||
methods: {},
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@
|
|||
<div id="formWrapper" class="px-4 py-6 details-form-wrapper w-full overflow-hidden overflow-y-auto">
|
||||
<div class="flex -mx-1">
|
||||
<div class="w-1/2 px-1">
|
||||
<ui-text-input-with-label v-model="details.title" label="Title" />
|
||||
<ui-text-input-with-label ref="titleInput" v-model="details.title" label="Title" />
|
||||
</div>
|
||||
<div class="flex-grow px-1">
|
||||
<ui-text-input-with-label v-model="details.subtitle" label="Subtitle" />
|
||||
<ui-text-input-with-label ref="subtitleInput" v-model="details.subtitle" label="Subtitle" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
<ui-multi-select-query-input ref="authorsSelect" v-model="details.authors" label="Authors" endpoint="authors/search" />
|
||||
</div>
|
||||
<div class="flex-grow px-1">
|
||||
<ui-text-input-with-label v-model="details.publishedYear" type="number" label="Publish Year" />
|
||||
<ui-text-input-with-label ref="publishYearInput" v-model="details.publishedYear" type="number" label="Publish Year" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -27,7 +27,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<ui-textarea-with-label v-model="details.description" :rows="3" label="Description" class="mt-2" />
|
||||
<ui-textarea-with-label ref="descriptionInput" v-model="details.description" :rows="3" label="Description" class="mt-2" />
|
||||
|
||||
<div class="flex mt-2 -mx-1">
|
||||
<div class="w-1/2 px-1">
|
||||
|
|
@ -43,19 +43,19 @@
|
|||
<ui-multi-select ref="narratorsSelect" v-model="details.narrators" label="Narrators" :items="narrators" />
|
||||
</div>
|
||||
<div class="w-1/4 px-1">
|
||||
<ui-text-input-with-label v-model="details.isbn" label="ISBN" />
|
||||
<ui-text-input-with-label ref="isbnInput" v-model="details.isbn" label="ISBN" />
|
||||
</div>
|
||||
<div class="w-1/4 px-1">
|
||||
<ui-text-input-with-label v-model="details.asin" label="ASIN" />
|
||||
<ui-text-input-with-label ref="asinInput" v-model="details.asin" label="ASIN" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex mt-2 -mx-1">
|
||||
<div class="w-1/2 px-1">
|
||||
<ui-text-input-with-label v-model="details.publisher" label="Publisher" />
|
||||
<ui-text-input-with-label ref="publisherInput" v-model="details.publisher" label="Publisher" />
|
||||
</div>
|
||||
<div class="w-1/4 px-1">
|
||||
<ui-text-input-with-label v-model="details.language" label="Language" />
|
||||
<ui-text-input-with-label ref="languageInput" v-model="details.language" label="Language" />
|
||||
</div>
|
||||
<div class="flex-grow px-1 pt-6">
|
||||
<div class="flex justify-center">
|
||||
|
|
@ -194,6 +194,15 @@ export default {
|
|||
}
|
||||
},
|
||||
forceBlur() {
|
||||
if (this.$refs.titleInput) this.$refs.titleInput.blur()
|
||||
if (this.$refs.subtitleInput) this.$refs.subtitleInput.blur()
|
||||
if (this.$refs.publishYearInput) this.$refs.publishYearInput.blur()
|
||||
if (this.$refs.descriptionInput) this.$refs.descriptionInput.blur()
|
||||
if (this.$refs.isbnInput) this.$refs.isbnInput.blur()
|
||||
if (this.$refs.asinInput) this.$refs.asinInput.blur()
|
||||
if (this.$refs.publisherInput) this.$refs.publisherInput.blur()
|
||||
if (this.$refs.languageInput) this.$refs.languageInput.blur()
|
||||
|
||||
if (this.$refs.authorsSelect && this.$refs.authorsSelect.isFocused) {
|
||||
this.$refs.authorsSelect.forceBlur()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -258,8 +258,8 @@ export default {
|
|||
userStreamUpdate(user) {
|
||||
this.$store.commit('users/updateUser', user)
|
||||
},
|
||||
userItemProgressUpdate(payload) {
|
||||
this.$store.commit('user/updateItemProgress', payload)
|
||||
userMediaProgressUpdate(payload) {
|
||||
this.$store.commit('user/updateMediaProgress', payload)
|
||||
},
|
||||
collectionAdded(collection) {
|
||||
this.$store.commit('user/addUpdateCollection', collection)
|
||||
|
|
@ -384,7 +384,7 @@ export default {
|
|||
this.socket.on('user_online', this.userOnline)
|
||||
this.socket.on('user_offline', this.userOffline)
|
||||
this.socket.on('user_stream_update', this.userStreamUpdate)
|
||||
this.socket.on('user_item_progress_updated', this.userItemProgressUpdate)
|
||||
this.socket.on('user_item_progress_updated', this.userMediaProgressUpdate)
|
||||
|
||||
// User Collection Listeners
|
||||
this.socket.on('collection_added', this.collectionAdded)
|
||||
|
|
|
|||
|
|
@ -95,19 +95,21 @@ export default {
|
|||
if (!store.getters['user/getUserCanUpdate']) {
|
||||
return redirect('/?error=unauthorized')
|
||||
}
|
||||
var payload = await app.$axios.$get(`/api/entities/${params.id}/item?expanded=1`).catch((error) => {
|
||||
var libraryItem = await app.$axios.$get(`/api/items/${params.id}?expanded=1`).catch((error) => {
|
||||
console.error('Failed', error)
|
||||
return false
|
||||
})
|
||||
if (!payload) {
|
||||
if (!libraryItem) {
|
||||
console.error('Not found...', params.id)
|
||||
return redirect('/')
|
||||
}
|
||||
const audiobook = payload.mediaEntity
|
||||
if (libraryItem.mediaType != 'book') {
|
||||
console.error('Invalid media type')
|
||||
return redirect('/')
|
||||
}
|
||||
return {
|
||||
audiobook,
|
||||
libraryItem: payload.libraryItem,
|
||||
files: audiobook.audioFiles ? audiobook.audioFiles.map((af) => ({ ...af, include: !af.exclude })) : []
|
||||
libraryItem,
|
||||
files: libraryItem.media.audioFiles ? libraryItem.media.audioFiles.map((af) => ({ ...af, include: !af.exclude })) : []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
|
|
@ -130,7 +132,7 @@ export default {
|
|||
return this.media.metadata || []
|
||||
},
|
||||
audioFiles() {
|
||||
return this.audiobook.audioFiles || []
|
||||
return this.media.audioFiles || []
|
||||
},
|
||||
numExcluded() {
|
||||
var count = 0
|
||||
|
|
@ -140,7 +142,7 @@ export default {
|
|||
return count
|
||||
},
|
||||
missingParts() {
|
||||
return this.audiobook.missingParts || []
|
||||
return this.media.missingParts || []
|
||||
},
|
||||
libraryItemId() {
|
||||
return this.libraryItem.id
|
||||
|
|
@ -152,7 +154,7 @@ export default {
|
|||
return this.mediaMetadata.authorName || 'Unknown'
|
||||
},
|
||||
tracks() {
|
||||
return this.audiobook.tracks
|
||||
return this.media.tracks
|
||||
},
|
||||
streamLibraryItem() {
|
||||
return this.$store.state.streamLibraryItem
|
||||
|
|
@ -218,7 +220,7 @@ export default {
|
|||
|
||||
this.saving = true
|
||||
this.$axios
|
||||
.$patch(`/api/entities/${this.audiobook.id}/tracks`, { orderedFileData })
|
||||
.$patch(`/api/items/${this.libraryItem.id}/tracks`, { orderedFileData })
|
||||
.then((data) => {
|
||||
console.log('Finished patching files', data)
|
||||
this.saving = false
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ export default {
|
|||
},
|
||||
playableBooks() {
|
||||
return this.bookItems.filter((book) => {
|
||||
return !book.isMissing && !book.isInvalid && book.media.audiobooks.length
|
||||
return !book.isMissing && !book.isInvalid && book.media.tracks.length
|
||||
})
|
||||
},
|
||||
streaming() {
|
||||
|
|
@ -116,7 +116,7 @@ export default {
|
|||
},
|
||||
clickPlay() {
|
||||
var nextBookNotRead = this.playableBooks.find((pb) => {
|
||||
var prog = this.$store.getters['user/getUserLibraryItemProgress'](pb.id)
|
||||
var prog = this.$store.getters['user/getUserMediaProgress'](pb.id)
|
||||
return !prog || !prog.isFinished
|
||||
})
|
||||
if (nextBookNotRead) {
|
||||
|
|
|
|||
|
|
@ -76,11 +76,11 @@ export default {
|
|||
currentLibraryId() {
|
||||
return this.$store.state.libraries.currentLibraryId
|
||||
},
|
||||
userItemProgress() {
|
||||
return this.user.libraryItemProgress || []
|
||||
userMediaProgress() {
|
||||
return this.user.mediaProgress || []
|
||||
},
|
||||
userItemsFinished() {
|
||||
return this.userItemProgress.filter((lip) => !!lip.isFinished)
|
||||
return this.userMediaProgress.filter((lip) => !!lip.isFinished)
|
||||
},
|
||||
mostRecentListeningSessions() {
|
||||
if (!this.listeningStats) return []
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@
|
|||
<div class="w-full h-px bg-white bg-opacity-10 my-2" />
|
||||
<div class="py-2">
|
||||
<h1 class="text-lg mb-2 text-white text-opacity-90 px-2 sm:px-0">Item Progress</h1>
|
||||
<table v-if="libraryItemProgress.length" class="userAudiobooksTable">
|
||||
<table v-if="mediaProgress.length" class="userAudiobooksTable">
|
||||
<tr class="bg-primary bg-opacity-40">
|
||||
<th class="w-16 text-left">Item</th>
|
||||
<th class="text-left"></th>
|
||||
|
|
@ -47,7 +47,7 @@
|
|||
<th class="w-40 hidden sm:table-cell">Started At</th>
|
||||
<th class="w-40 hidden sm:table-cell">Last Update</th>
|
||||
</tr>
|
||||
<tr v-for="item in libraryItemProgress" :key="item.id" :class="!item.isFinished ? '' : 'isFinished'">
|
||||
<tr v-for="item in mediaProgress" :key="item.id" :class="!item.isFinished ? '' : 'isFinished'">
|
||||
<td>
|
||||
<covers-book-cover :width="50" :library-item="item" :book-cover-aspect-ratio="bookCoverAspectRatio" />
|
||||
</td>
|
||||
|
|
@ -111,8 +111,8 @@ export default {
|
|||
userOnline() {
|
||||
return this.$store.getters['users/getIsUserOnline'](this.user.id)
|
||||
},
|
||||
libraryItemProgress() {
|
||||
return this.user.libraryItemProgress.sort((a, b) => b.lastUpdate - a.lastUpdate)
|
||||
mediaProgress() {
|
||||
return this.user.mediaProgress.sort((a, b) => b.lastUpdate - a.lastUpdate)
|
||||
},
|
||||
totalListeningTime() {
|
||||
return this.listeningStats.totalTime || 0
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@
|
|||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="audiobooks.length" class="flex py-0.5">
|
||||
<div v-if="tracks.length" class="flex py-0.5">
|
||||
<div class="w-32">
|
||||
<span class="text-white text-opacity-60 uppercase text-sm">Duration</span>
|
||||
</div>
|
||||
|
|
@ -77,7 +77,7 @@
|
|||
{{ durationPretty }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="audiobooks.length" class="flex py-0.5">
|
||||
<div class="flex py-0.5">
|
||||
<div class="w-32">
|
||||
<span class="text-white text-opacity-60 uppercase text-sm">Size</span>
|
||||
</div>
|
||||
|
|
@ -143,9 +143,8 @@
|
|||
<p class="text-base text-gray-100 whitespace-pre-line">{{ description }}</p>
|
||||
</div>
|
||||
|
||||
<template v-for="audiobook in audiobooks">
|
||||
<widgets-audiobook-data :key="audiobook.id" :audiobook="audiobook" />
|
||||
</template>
|
||||
<widgets-audiobook-data v-if="tracks.length" :library-item-id="libraryItemId" :media="media" />
|
||||
|
||||
<tables-library-files-table v-if="libraryFiles.length" :is-missing="isMissing" :library-item-id="libraryItemId" :files="libraryFiles" class="mt-6" />
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -205,7 +204,7 @@ export default {
|
|||
showPlayButton() {
|
||||
if (this.isMissing || this.isInvalid) return false
|
||||
if (this.isPodcast) return this.podcastEpisodes.length
|
||||
return this.audiobooks.length
|
||||
return this.tracks.length
|
||||
},
|
||||
libraryId() {
|
||||
return this.libraryItem.libraryId
|
||||
|
|
@ -222,13 +221,12 @@ export default {
|
|||
mediaMetadata() {
|
||||
return this.media.metadata || {}
|
||||
},
|
||||
tracks() {
|
||||
return this.media.tracks || []
|
||||
},
|
||||
podcastEpisodes() {
|
||||
return this.media.episodes || []
|
||||
},
|
||||
defaultAudiobook() {
|
||||
if (!this.audiobooks.length) return null
|
||||
return this.audiobooks[0]
|
||||
},
|
||||
title() {
|
||||
return this.mediaMetadata.title || 'No Title'
|
||||
},
|
||||
|
|
@ -271,51 +269,47 @@ export default {
|
|||
})
|
||||
},
|
||||
durationPretty() {
|
||||
if (!this.defaultAudiobook) return 'N/A'
|
||||
return this.$elapsedPretty(this.defaultAudiobook.duration)
|
||||
if (!this.tracks.length) return 'N/A'
|
||||
return this.$elapsedPretty(this.media.duration)
|
||||
},
|
||||
duration() {
|
||||
if (!this.defaultAudiobook) return 0
|
||||
return this.defaultAudiobook.duration
|
||||
if (!this.tracks.length) return 0
|
||||
return this.media.duration
|
||||
},
|
||||
sizePretty() {
|
||||
if (!this.defaultAudiobook) return 'N/A'
|
||||
return this.$bytesPretty(this.defaultAudiobook.size)
|
||||
return this.$bytesPretty(this.media.size)
|
||||
},
|
||||
libraryFiles() {
|
||||
return this.libraryItem.libraryFiles || []
|
||||
},
|
||||
audiobooks() {
|
||||
return this.media.audiobooks || []
|
||||
},
|
||||
ebooks() {
|
||||
return this.media.ebooks || []
|
||||
},
|
||||
showExperimentalReadAlert() {
|
||||
return !this.audiobooks.length && this.ebooks.length && !this.showExperimentalFeatures
|
||||
return !this.tracks.length && this.ebooks.length && !this.showExperimentalFeatures
|
||||
},
|
||||
description() {
|
||||
return this.mediaMetadata.description || ''
|
||||
},
|
||||
userItemProgress() {
|
||||
return this.$store.getters['user/getUserLibraryItemProgress'](this.libraryItemId)
|
||||
userMediaProgress() {
|
||||
return this.$store.getters['user/getUserMediaProgress'](this.libraryItemId)
|
||||
},
|
||||
userIsFinished() {
|
||||
return this.userItemProgress ? !!this.userItemProgress.isFinished : false
|
||||
return this.userMediaProgress ? !!this.userMediaProgress.isFinished : false
|
||||
},
|
||||
userTimeRemaining() {
|
||||
if (!this.userItemProgress) return 0
|
||||
var duration = this.userItemProgress.duration || this.duration
|
||||
return duration - this.userItemProgress.currentTime
|
||||
if (!this.userMediaProgress) return 0
|
||||
var duration = this.userMediaProgress.duration || this.duration
|
||||
return duration - this.userMediaProgress.currentTime
|
||||
},
|
||||
progressPercent() {
|
||||
return this.userItemProgress ? Math.max(Math.min(1, this.userItemProgress.progress), 0) : 0
|
||||
return this.userMediaProgress ? Math.max(Math.min(1, this.userMediaProgress.progress), 0) : 0
|
||||
},
|
||||
userProgressStartedAt() {
|
||||
return this.userItemProgress ? this.userItemProgress.startedAt : 0
|
||||
return this.userMediaProgress ? this.userMediaProgress.startedAt : 0
|
||||
},
|
||||
userProgressFinishedAt() {
|
||||
return this.userItemProgress ? this.userItemProgress.finishedAt : 0
|
||||
return this.userMediaProgress ? this.userMediaProgress.finishedAt : 0
|
||||
},
|
||||
streamLibraryItem() {
|
||||
return this.$store.state.streamLibraryItem
|
||||
|
|
@ -365,17 +359,11 @@ export default {
|
|||
this.$store.commit('setBookshelfBookIds', [])
|
||||
this.$store.commit('showEditModal', this.libraryItem)
|
||||
},
|
||||
audiobookUpdated() {
|
||||
// console.log('Audiobook Updated - Fetch full audiobook')
|
||||
// this.$axios
|
||||
// .$get(`/api/books/${this.libraryItemId}`)
|
||||
// .then((audiobook) => {
|
||||
// console.log('Updated audiobook', audiobook)
|
||||
// this.libraryItem = audiobook
|
||||
// })
|
||||
// .catch((error) => {
|
||||
// console.error('Failed', error)
|
||||
// })
|
||||
libraryItemUpdated(libraryItem) {
|
||||
if (libraryItem.id === this.libraryItemId) {
|
||||
console.log('Item was updated', libraryItem)
|
||||
this.libraryItem = libraryItem
|
||||
}
|
||||
},
|
||||
clearProgressClick() {
|
||||
if (confirm(`Are you sure you want to reset your progress?`)) {
|
||||
|
|
@ -402,11 +390,14 @@ export default {
|
|||
}
|
||||
},
|
||||
mounted() {
|
||||
// use this audiobooks library id as the current
|
||||
// use this items library id as the current
|
||||
if (this.libraryId) {
|
||||
this.$store.commit('libraries/setCurrentLibrary', this.libraryId)
|
||||
}
|
||||
this.$root.socket.on('item_updated', this.libraryItemUpdated)
|
||||
},
|
||||
beforeDestroy() {}
|
||||
beforeDestroy() {
|
||||
this.$root.socket.off('item_updated', this.libraryItemUpdated)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ export default class PlayerHandler {
|
|||
this.playerState = 'IDLE'
|
||||
this.isHlsTranscode = false
|
||||
this.currentSessionId = null
|
||||
this.mediaEntityId = null
|
||||
this.startTime = 0
|
||||
|
||||
this.lastSyncTime = 0
|
||||
|
|
@ -150,7 +149,6 @@ export default class PlayerHandler {
|
|||
prepareSession(session) {
|
||||
this.startTime = session.currentTime
|
||||
this.currentSessionId = session.id
|
||||
this.mediaEntityId = session.mediaEntityId
|
||||
|
||||
console.log('[PlayerHandler] Preparing Session', session)
|
||||
var audioTracks = session.audioTracks.map(at => new AudioTrack(at, this.userToken))
|
||||
|
|
@ -210,7 +208,6 @@ export default class PlayerHandler {
|
|||
syncData = {
|
||||
timeListened: listeningTimeToAdd,
|
||||
duration: this.getDuration(),
|
||||
mediaEntityId: this.mediaEntityId,
|
||||
currentTime: this.getCurrentTime()
|
||||
}
|
||||
}
|
||||
|
|
@ -229,7 +226,6 @@ export default class PlayerHandler {
|
|||
var syncData = {
|
||||
timeListened: listeningTimeToAdd,
|
||||
duration: this.getDuration(),
|
||||
mediaEntityId: this.mediaEntityId,
|
||||
currentTime
|
||||
}
|
||||
this.listeningTimeSinceSync = 0
|
||||
|
|
|
|||
|
|
@ -22,9 +22,9 @@ export const getters = {
|
|||
getToken: (state) => {
|
||||
return state.user ? state.user.token : null
|
||||
},
|
||||
getUserLibraryItemProgress: (state) => (libraryItemId) => {
|
||||
if (!state.user.libraryItemProgress) return null
|
||||
return state.user.libraryItemProgress.find(li => li.id == libraryItemId)
|
||||
getUserMediaProgress: (state) => (libraryItemId) => {
|
||||
if (!state.user.mediaProgress) return null
|
||||
return state.user.mediaProgress.find(li => li.id == libraryItemId)
|
||||
},
|
||||
getUserBookmarksForItem: (state) => (libraryItemId) => {
|
||||
if (!state.user.bookmarks) return []
|
||||
|
|
@ -107,16 +107,16 @@ export const mutations = {
|
|||
localStorage.removeItem('token')
|
||||
}
|
||||
},
|
||||
updateItemProgress(state, { id, data }) {
|
||||
updateMediaProgress(state, { id, data }) {
|
||||
if (!state.user) return
|
||||
if (!data) {
|
||||
state.user.libraryItemProgress = state.user.libraryItemProgress.filter(lip => lip.id != id)
|
||||
state.user.mediaProgress = state.user.mediaProgress.filter(lip => lip.id != id)
|
||||
} else {
|
||||
var indexOf = state.user.libraryItemProgress.findIndex(lip => lip.id == id)
|
||||
var indexOf = state.user.mediaProgress.findIndex(lip => lip.id == id)
|
||||
if (indexOf >= 0) {
|
||||
state.user.libraryItemProgress.splice(indexOf, 1, data)
|
||||
state.user.mediaProgress.splice(indexOf, 1, data)
|
||||
} else {
|
||||
state.user.libraryItemProgress.push(data)
|
||||
state.user.mediaProgress.push(data)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue