New data model removing media entity for books

This commit is contained in:
advplyr 2022-03-26 11:59:34 -05:00
parent 920ca683b9
commit 3150822117
44 changed files with 733 additions and 798 deletions

View file

@ -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
})
},

View file

@ -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

View file

@ -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 []

View file

@ -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

View file

@ -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

View file

@ -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: {}

View file

@ -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 {

View file

@ -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

View file

@ -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() {}

View file

@ -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>

View file

@ -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: {},

View file

@ -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()
}