mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-05-31 04:05:40 -04:00
Fix:Native audio player set time and play on load #227, Change:Mobile UI updates & cleanup old bookshelf
This commit is contained in:
parent
4592e1f494
commit
6d5f6bc46e
@ -22,7 +22,7 @@
|
|||||||
<controls-volume-control ref="volumeControl" v-model="volume" @input="updateVolume" />
|
<controls-volume-control ref="volumeControl" v-model="volume" @input="updateVolume" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex pb-8 sm:pb-4 md:pb-2">
|
<div class="flex pb-4 md:pb-2">
|
||||||
<div class="flex-grow" />
|
<div class="flex-grow" />
|
||||||
<template v-if="!loading">
|
<template v-if="!loading">
|
||||||
<div class="cursor-pointer flex items-center justify-center text-gray-300 mr-8" @mousedown.prevent @mouseup.prevent @click.stop="restart">
|
<div class="cursor-pointer flex items-center justify-center text-gray-300 mr-8" @mousedown.prevent @mouseup.prevent @click.stop="restart">
|
||||||
@ -82,7 +82,7 @@
|
|||||||
<p class="font-mono text-sm text-gray-100 pointer-events-auto">{{ timeRemainingPretty }}</p>
|
<p class="font-mono text-sm text-gray-100 pointer-events-auto">{{ timeRemainingPretty }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<audio ref="audio" @progress="progress" @timeupdate="timeupdate" @loadedmetadata="audioLoadedMetadata" @loadeddata="audioLoadedData" @play="audioPlayed" @pause="audioPaused" @error="audioError" @ended="audioEnded" @stalled="audioStalled" @suspend="audioSuspended" />
|
<audio ref="audio" @progress="progress" @timeupdate="timeupdate" @loadedmetadata="audioLoadedMetadata" @play="audioPlayed" @pause="audioPaused" @error="audioError" @ended="audioEnded" @stalled="audioStalled" @suspend="audioSuspended" />
|
||||||
|
|
||||||
<modals-chapters-modal v-model="showChaptersModal" :current-chapter="currentChapter" :chapters="chapters" @select="selectChapter" />
|
<modals-chapters-modal v-model="showChaptersModal" :current-chapter="currentChapter" :chapters="chapters" @select="selectChapter" />
|
||||||
</div>
|
</div>
|
||||||
@ -109,6 +109,9 @@ export default {
|
|||||||
return {
|
return {
|
||||||
hlsInstance: null,
|
hlsInstance: null,
|
||||||
staleHlsInstance: null,
|
staleHlsInstance: null,
|
||||||
|
usingNativeAudioPlayer: false,
|
||||||
|
playOnLoad: false,
|
||||||
|
startTime: 0,
|
||||||
volume: 1,
|
volume: 1,
|
||||||
playbackRate: 1,
|
playbackRate: 1,
|
||||||
trackWidth: 0,
|
trackWidth: 0,
|
||||||
@ -194,7 +197,7 @@ export default {
|
|||||||
},
|
},
|
||||||
audioStalled() {
|
audioStalled() {
|
||||||
if (!this.$refs.audio) return
|
if (!this.$refs.audio) return
|
||||||
console.warn('Audio Ended', this.$refs.audio.paused, this.$refs.audio.currentTime)
|
console.warn('Audio Stalled', this.$refs.audio.paused, this.$refs.audio.currentTime)
|
||||||
},
|
},
|
||||||
audioSuspended() {
|
audioSuspended() {
|
||||||
if (!this.$refs.audio) return
|
if (!this.$refs.audio) return
|
||||||
@ -555,11 +558,13 @@ export default {
|
|||||||
this.playedTrackWidth = ptWidth
|
this.playedTrackWidth = ptWidth
|
||||||
},
|
},
|
||||||
audioLoadedMetadata() {
|
audioLoadedMetadata() {
|
||||||
console.log('Audio METADATA Loaded, total duration', this.audioEl.duration)
|
|
||||||
this.totalDuration = this.audioEl.duration
|
this.totalDuration = this.audioEl.duration
|
||||||
this.$emit('loaded', this.totalDuration)
|
this.$emit('loaded', this.totalDuration)
|
||||||
|
if (this.usingNativeAudioPlayer) {
|
||||||
|
this.audioEl.currentTime = this.startTime
|
||||||
|
this.play()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
audioLoadedData() {},
|
|
||||||
set(url, currentTime, playOnLoad = false) {
|
set(url, currentTime, playOnLoad = false) {
|
||||||
if (this.hlsInstance) {
|
if (this.hlsInstance) {
|
||||||
this.terminateStream()
|
this.terminateStream()
|
||||||
@ -570,6 +575,8 @@ export default {
|
|||||||
}
|
}
|
||||||
this.listeningTimeSinceLastUpdate = 0
|
this.listeningTimeSinceLastUpdate = 0
|
||||||
|
|
||||||
|
this.playOnLoad = playOnLoad
|
||||||
|
this.startTime = currentTime
|
||||||
this.url = url
|
this.url = url
|
||||||
if (process.env.NODE_ENV === 'development') {
|
if (process.env.NODE_ENV === 'development') {
|
||||||
url = `${process.env.serverUrl}${url}`
|
url = `${process.env.serverUrl}${url}`
|
||||||
@ -584,6 +591,7 @@ export default {
|
|||||||
// iOS does not support Media Elements but allows for HLS in the native audio player
|
// iOS does not support Media Elements but allows for HLS in the native audio player
|
||||||
if (!Hls.isSupported()) {
|
if (!Hls.isSupported()) {
|
||||||
console.warn('HLS is not supported - fallback to using audio element')
|
console.warn('HLS is not supported - fallback to using audio element')
|
||||||
|
this.usingNativeAudioPlayer = true
|
||||||
audio.src = this.src + '?token=' + this.token
|
audio.src = this.src + '?token=' + this.token
|
||||||
audio.currentTime = currentTime
|
audio.currentTime = currentTime
|
||||||
return
|
return
|
||||||
@ -604,10 +612,10 @@ export default {
|
|||||||
// console.log('[HLS] MEDIA ATTACHED')
|
// console.log('[HLS] MEDIA ATTACHED')
|
||||||
this.hlsInstance.loadSource(url)
|
this.hlsInstance.loadSource(url)
|
||||||
|
|
||||||
this.hlsInstance.on(Hls.Events.MANIFEST_PARSED, function () {
|
this.hlsInstance.on(Hls.Events.MANIFEST_PARSED, () => {
|
||||||
console.log('[HLS] Manifest Parsed')
|
console.log('[HLS] Manifest Parsed')
|
||||||
if (playOnLoad) {
|
if (playOnLoad) {
|
||||||
audio.play()
|
this.play()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -1,449 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div id="bookshelf" class="overflow-hidden relative block max-h-full">
|
|
||||||
<div ref="wrapper" class="h-full w-full relative" :class="isGridMode ? 'overflow-y-scroll' : 'overflow-hidden'">
|
|
||||||
<!-- Cover size widget -->
|
|
||||||
<div v-show="!isSelectionMode && isGridMode" class="fixed bottom-4 right-4 z-30">
|
|
||||||
<div class="rounded-full py-1 bg-primary px-2 border border-black-100 text-center flex items-center box-shadow-md" @mousedown.prevent @mouseup.prevent>
|
|
||||||
<span class="material-icons" :class="selectedSizeIndex === 0 ? 'text-gray-400' : 'hover:text-yellow-300 cursor-pointer'" style="font-size: 0.9rem" @mousedown.prevent @click="decreaseSize">remove</span>
|
|
||||||
<p class="px-2 font-mono">{{ bookCoverWidth }}</p>
|
|
||||||
<span class="material-icons" :class="selectedSizeIndex === availableSizes.length - 1 ? 'text-gray-400' : 'hover:text-yellow-300 cursor-pointer'" style="font-size: 0.9rem" @mousedown.prevent @click="increaseSize">add</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Experimental Bookshelf Texture -->
|
|
||||||
<div v-show="showExperimentalFeatures" class="fixed bottom-4 right-28 z-40">
|
|
||||||
<div class="rounded-full py-1 bg-primary hover:bg-bg cursor-pointer px-2 border border-black-100 text-center flex items-center box-shadow-md" @mousedown.prevent @mouseup.prevent @click="showBookshelfTextureModal"><p class="text-sm py-0.5">Texture</p></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="!audiobooks.length" class="w-full flex flex-col items-center justify-center py-12">
|
|
||||||
<p class="text-center text-2xl font-book mb-4 py-4">Your Audiobookshelf is empty!</p>
|
|
||||||
<div class="flex">
|
|
||||||
<ui-btn to="/config" color="primary" class="w-52 mr-2" @click="scan">Configure Scanner</ui-btn>
|
|
||||||
<ui-btn color="success" class="w-52" @click="scan">Scan Audiobooks</ui-btn>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="page === 'search'" id="bookshelf-categorized" class="w-full flex flex-col items-center">
|
|
||||||
<template v-for="(shelf, index) in categorizedShelves">
|
|
||||||
<app-book-shelf-row :key="index" :index="index" :shelf="shelf" :size-multiplier="sizeMultiplier" :book-cover-width="bookCoverWidth" />
|
|
||||||
</template>
|
|
||||||
<div v-show="!categorizedShelves.length" class="w-full py-16 text-center text-xl">
|
|
||||||
<div class="py-4 mb-6"><p class="text-2xl">No Results</p></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-else class="w-full">
|
|
||||||
<template v-if="viewMode === 'grid'">
|
|
||||||
<div class="w-full flex flex-col items-center">
|
|
||||||
<template v-for="(shelf, index) in shelves">
|
|
||||||
<div :key="index" class="w-full bookshelfRow relative">
|
|
||||||
<div class="flex justify-center items-center">
|
|
||||||
<template v-for="entity in shelf">
|
|
||||||
<cards-collection-card v-if="isCollections" :key="entity.id" :width="bookCoverWidth" :collection="entity" @click="clickGroup" />
|
|
||||||
<cards-group-card v-else-if="showGroups" :key="entity.id" :width="bookCoverWidth" :group="entity" @click="clickGroup" />
|
|
||||||
|
|
||||||
<!-- <cards-book-3d :key="entity.id" v-else :width="100" :src="$store.getters['audiobooks/getBookCoverSrc'](entity.book)" /> -->
|
|
||||||
<cards-book-card v-else :ref="`book-card-${entity.id}`" :key="entity.id" :is-bookshelf-book="!isSeries" :show-volume-number="!!selectedSeries" :width="bookCoverWidth" :user-progress="userAudiobooks[entity.id]" :audiobook="entity" @edit="editBook" @hook:mounted="mountedBookCard(entity)" />
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
<div class="bookshelfDivider w-full absolute bottom-0 left-0 right-0 z-10" :class="isCollections || isSeriesGroups ? 'h-6' : 'h-4'" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<app-book-list :books="entities" />
|
|
||||||
</template>
|
|
||||||
<div v-show="!shelves.length" class="w-full py-16 text-center text-xl">
|
|
||||||
<div v-if="page === 'search'" class="py-4 mb-6"><p class="text-2xl">No Results</p></div>
|
|
||||||
<div v-else class="py-4 capitalize">No {{ showGroups ? page : 'Audiobooks' }}</div>
|
|
||||||
<ui-btn v-if="!showGroups && (filterBy !== 'all' || keywordFilter)" @click="clearFilter">Clear Filter</ui-btn>
|
|
||||||
<ui-btn v-else-if="page === 'search'" to="/library">Back to Library</ui-btn>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
page: String,
|
|
||||||
selectedSeries: String,
|
|
||||||
searchResults: {
|
|
||||||
type: Object,
|
|
||||||
default: () => {}
|
|
||||||
},
|
|
||||||
searchQuery: String,
|
|
||||||
viewMode: String
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
shelves: [],
|
|
||||||
currSearchParams: null,
|
|
||||||
availableSizes: [60, 80, 100, 120, 140, 160, 180, 200, 220],
|
|
||||||
selectedSizeIndex: 3,
|
|
||||||
rowPaddingX: 40,
|
|
||||||
keywordFilterTimeout: null,
|
|
||||||
scannerParseSubtitle: false,
|
|
||||||
wrapperClientWidth: 0,
|
|
||||||
observer: null,
|
|
||||||
booksObserved: [],
|
|
||||||
booksVisible: {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
keywordFilter() {
|
|
||||||
this.checkKeywordFilter()
|
|
||||||
},
|
|
||||||
selectedSeries() {
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.$store.commit('audiobooks/setSelectedSeries', this.selectedSeries)
|
|
||||||
this.setBookshelfEntities()
|
|
||||||
})
|
|
||||||
},
|
|
||||||
searchResults() {
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.setBookshelfEntities()
|
|
||||||
})
|
|
||||||
},
|
|
||||||
'$route.query.filter'() {
|
|
||||||
if (this.$route.query.filter && this.$route.query.filter !== this.filterBy) {
|
|
||||||
this.$store.dispatch('user/updateUserSettings', { filterBy: this.$route.query.filter })
|
|
||||||
} else if (!this.$route.query.filter && this.filterBy) {
|
|
||||||
this.$store.dispatch('user/updateUserSettings', { filterBy: 'all' })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
showExperimentalFeatures() {
|
|
||||||
return this.$store.state.showExperimentalFeatures
|
|
||||||
},
|
|
||||||
isGridMode() {
|
|
||||||
return this.viewMode === 'grid'
|
|
||||||
},
|
|
||||||
keywordFilter() {
|
|
||||||
return this.$store.state.audiobooks.keywordFilter
|
|
||||||
},
|
|
||||||
userAudiobooks() {
|
|
||||||
return this.$store.state.user.user ? this.$store.state.user.user.audiobooks || {} : {}
|
|
||||||
},
|
|
||||||
audiobooks() {
|
|
||||||
return this.$store.state.audiobooks.audiobooks
|
|
||||||
},
|
|
||||||
sizeMultiplier() {
|
|
||||||
return this.bookCoverWidth / 120
|
|
||||||
},
|
|
||||||
bookCoverWidth() {
|
|
||||||
var coverWidth = this.availableSizes[this.selectedSizeIndex]
|
|
||||||
return coverWidth
|
|
||||||
},
|
|
||||||
sizeMultiplier() {
|
|
||||||
return this.bookCoverWidth / 120
|
|
||||||
},
|
|
||||||
paddingX() {
|
|
||||||
return 16 * this.sizeMultiplier
|
|
||||||
},
|
|
||||||
bookWidth() {
|
|
||||||
var coverWidth = this.bookCoverWidth
|
|
||||||
if (this.page === 'collections') coverWidth *= 2
|
|
||||||
var _width = coverWidth + this.paddingX * 2
|
|
||||||
return this.showGroups ? _width * 1.6 : _width
|
|
||||||
},
|
|
||||||
isSelectionMode() {
|
|
||||||
return this.$store.getters['getNumAudiobooksSelected']
|
|
||||||
},
|
|
||||||
filterBy() {
|
|
||||||
return this.$store.getters['user/getUserSetting']('filterBy')
|
|
||||||
},
|
|
||||||
orderBy() {
|
|
||||||
return this.$store.getters['user/getUserSetting']('orderBy')
|
|
||||||
},
|
|
||||||
orderDesc() {
|
|
||||||
return this.$store.getters['user/getUserSetting']('orderDesc')
|
|
||||||
},
|
|
||||||
showGroups() {
|
|
||||||
return this.page !== '' && this.page !== 'search' && !this.selectedSeries
|
|
||||||
},
|
|
||||||
isCollections() {
|
|
||||||
return this.page === 'collections'
|
|
||||||
},
|
|
||||||
isSeries() {
|
|
||||||
return this.page === 'series'
|
|
||||||
},
|
|
||||||
isSeriesGroups() {
|
|
||||||
return this.isSeries && !this.selectedSeries
|
|
||||||
},
|
|
||||||
categorizedShelves() {
|
|
||||||
if (this.page !== 'search') return []
|
|
||||||
var audiobookSearchResults = this.searchResults ? this.searchResults.audiobooks || [] : []
|
|
||||||
const shelves = []
|
|
||||||
|
|
||||||
if (audiobookSearchResults.length) {
|
|
||||||
shelves.push({
|
|
||||||
label: 'Books',
|
|
||||||
books: audiobookSearchResults.map((absr) => absr.audiobook)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.searchResults.series && this.searchResults.series.length) {
|
|
||||||
var seriesGroups = this.searchResults.series.map((seriesResult) => {
|
|
||||||
return {
|
|
||||||
type: 'series',
|
|
||||||
name: seriesResult.series || '',
|
|
||||||
books: seriesResult.audiobooks || []
|
|
||||||
}
|
|
||||||
})
|
|
||||||
shelves.push({
|
|
||||||
label: 'Series',
|
|
||||||
series: seriesGroups
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.searchResults.tags && this.searchResults.tags.length) {
|
|
||||||
var tagGroups = this.searchResults.tags.map((tagResult) => {
|
|
||||||
return {
|
|
||||||
type: 'tags',
|
|
||||||
name: tagResult.tag || '',
|
|
||||||
books: tagResult.audiobooks || []
|
|
||||||
}
|
|
||||||
})
|
|
||||||
shelves.push({
|
|
||||||
label: 'Tags',
|
|
||||||
series: tagGroups
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return shelves
|
|
||||||
},
|
|
||||||
entities() {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
showBookshelfTextureModal() {
|
|
||||||
this.$store.commit('globals/setShowBookshelfTextureModal', true)
|
|
||||||
},
|
|
||||||
editBook(audiobook) {
|
|
||||||
var bookIds = this.entities.map((e) => e.id)
|
|
||||||
this.$store.commit('setBookshelfBookIds', bookIds)
|
|
||||||
this.$store.commit('showEditModal', audiobook)
|
|
||||||
},
|
|
||||||
clickGroup(group) {
|
|
||||||
if (this.page === 'collections') return
|
|
||||||
this.$emit('update:selectedSeries', group.name)
|
|
||||||
},
|
|
||||||
clearFilter() {
|
|
||||||
this.$store.commit('audiobooks/setKeywordFilter', null)
|
|
||||||
if (this.filterBy !== 'all') {
|
|
||||||
this.$store.dispatch('user/updateUserSettings', {
|
|
||||||
filterBy: 'all'
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
this.setBookshelfEntities()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
checkKeywordFilter() {
|
|
||||||
clearTimeout(this.keywordFilterTimeout)
|
|
||||||
this.keywordFilterTimeout = setTimeout(() => {
|
|
||||||
this.setBookshelfEntities()
|
|
||||||
}, 500)
|
|
||||||
},
|
|
||||||
increaseSize() {
|
|
||||||
this.selectedSizeIndex = Math.min(this.availableSizes.length - 1, this.selectedSizeIndex + 1)
|
|
||||||
this.resize()
|
|
||||||
this.$store.dispatch('user/updateUserSettings', { bookshelfCoverSize: this.bookCoverWidth })
|
|
||||||
},
|
|
||||||
decreaseSize() {
|
|
||||||
this.selectedSizeIndex = Math.max(0, this.selectedSizeIndex - 1)
|
|
||||||
this.resize()
|
|
||||||
this.$store.dispatch('user/updateUserSettings', { bookshelfCoverSize: this.bookCoverWidth })
|
|
||||||
},
|
|
||||||
setBookshelfEntities() {
|
|
||||||
this.wrapperClientWidth = this.$refs.wrapper.clientWidth
|
|
||||||
var width = Math.max(0, this.wrapperClientWidth - this.rowPaddingX * 2)
|
|
||||||
|
|
||||||
var booksPerRow = Math.floor(width / this.bookWidth)
|
|
||||||
|
|
||||||
this.currSearchParams = this.buildSearchParams()
|
|
||||||
|
|
||||||
var entities = this.entities
|
|
||||||
|
|
||||||
var groups = []
|
|
||||||
var currentRow = 0
|
|
||||||
var currentGroup = []
|
|
||||||
|
|
||||||
for (let i = 0; i < entities.length; i++) {
|
|
||||||
var row = Math.floor(i / booksPerRow)
|
|
||||||
if (row > currentRow) {
|
|
||||||
groups.push([...currentGroup])
|
|
||||||
currentRow = row
|
|
||||||
currentGroup = []
|
|
||||||
}
|
|
||||||
currentGroup.push(entities[i])
|
|
||||||
}
|
|
||||||
if (currentGroup.length) {
|
|
||||||
groups.push([...currentGroup])
|
|
||||||
}
|
|
||||||
this.shelves = groups
|
|
||||||
},
|
|
||||||
async init() {
|
|
||||||
this.checkUpdateSearchParams()
|
|
||||||
|
|
||||||
this.wrapperClientWidth = this.$refs.wrapper ? this.$refs.wrapper.clientWidth : 0
|
|
||||||
|
|
||||||
var bookshelfCoverSize = this.$store.getters['user/getUserSetting']('bookshelfCoverSize')
|
|
||||||
var sizeIndex = this.availableSizes.findIndex((s) => s === bookshelfCoverSize)
|
|
||||||
if (!isNaN(sizeIndex)) this.selectedSizeIndex = sizeIndex
|
|
||||||
|
|
||||||
// var isLoading = await this.$store.dispatch('audiobooks/load')
|
|
||||||
// if (!isLoading) {
|
|
||||||
// this.setBookshelfEntities()
|
|
||||||
// }
|
|
||||||
},
|
|
||||||
resize() {
|
|
||||||
this.$nextTick(this.setBookshelfEntities)
|
|
||||||
},
|
|
||||||
audiobooksUpdated() {
|
|
||||||
console.log('[Bookshelf] Audiobooks Updated')
|
|
||||||
this.setBookshelfEntities()
|
|
||||||
},
|
|
||||||
collectionsUpdated() {
|
|
||||||
if (!this.isCollections) return
|
|
||||||
console.log('[Bookshelf] Collections Updated')
|
|
||||||
this.setBookshelfEntities()
|
|
||||||
},
|
|
||||||
buildSearchParams() {
|
|
||||||
if (this.page === 'search' || this.page === 'series' || this.page === 'collections') {
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
|
|
||||||
let searchParams = new URLSearchParams()
|
|
||||||
if (this.filterBy && this.filterBy !== 'all') {
|
|
||||||
searchParams.set('filter', this.filterBy)
|
|
||||||
}
|
|
||||||
if (this.orderBy) {
|
|
||||||
searchParams.set('order', this.orderBy)
|
|
||||||
searchParams.set('orderdesc', this.orderDesc ? 1 : 0)
|
|
||||||
}
|
|
||||||
return searchParams.toString()
|
|
||||||
},
|
|
||||||
checkUpdateSearchParams() {
|
|
||||||
var newSearchParams = this.buildSearchParams()
|
|
||||||
var currentQueryString = window.location.search
|
|
||||||
|
|
||||||
if (newSearchParams === '') {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newSearchParams !== this.currSearchParams || newSearchParams !== currentQueryString) {
|
|
||||||
let newurl = window.location.protocol + '//' + window.location.host + window.location.pathname + '?' + newSearchParams
|
|
||||||
window.history.replaceState({ path: newurl }, '', newurl)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
},
|
|
||||||
settingsUpdated(settings) {
|
|
||||||
var wasUpdated = this.checkUpdateSearchParams()
|
|
||||||
if (wasUpdated) this.setBookshelfEntities()
|
|
||||||
|
|
||||||
if (settings.bookshelfCoverSize !== this.bookCoverWidth && settings.bookshelfCoverSize !== undefined) {
|
|
||||||
var index = this.availableSizes.indexOf(settings.bookshelfCoverSize)
|
|
||||||
if (index >= 0) {
|
|
||||||
this.selectedSizeIndex = index
|
|
||||||
this.resize()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
scan() {
|
|
||||||
this.$root.socket.emit('scan', this.$store.state.libraries.currentLibraryId)
|
|
||||||
},
|
|
||||||
mountedBookCard(entity, shouldUnobserve = false) {
|
|
||||||
if (!this.observer) {
|
|
||||||
console.error('Observer not loaded', entity.id)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var el = document.getElementById(`book-card-${entity.id}`)
|
|
||||||
if (el) {
|
|
||||||
if (shouldUnobserve) {
|
|
||||||
console.warn('Unobserving el', el)
|
|
||||||
this.observer.unobserve(el)
|
|
||||||
}
|
|
||||||
this.observer.observe(el)
|
|
||||||
this.booksObserved.push(entity.id)
|
|
||||||
// console.log('Book observed', this.booksObserved.length)
|
|
||||||
} else {
|
|
||||||
console.error('Could not get book card', entity.id)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getBookCard(id) {
|
|
||||||
if (!this.$refs[id] || !this.$refs[id].length) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
return this.$refs[id][0]
|
|
||||||
},
|
|
||||||
observerCallback(entries, observer) {
|
|
||||||
entries.forEach((entry) => {
|
|
||||||
var bookId = entry.target.getAttribute('data-bookId')
|
|
||||||
if (!bookId) {
|
|
||||||
console.error('Invalid observe no book id', entry)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var component = this.getBookCard(entry.target.id)
|
|
||||||
if (component) {
|
|
||||||
if (entry.isIntersecting) {
|
|
||||||
if (!this.booksVisible[bookId]) {
|
|
||||||
this.booksVisible[bookId] = true
|
|
||||||
component.setShowCard(true)
|
|
||||||
}
|
|
||||||
} else if (this.booksVisible[bookId]) {
|
|
||||||
this.booksVisible[bookId] = false
|
|
||||||
component.setShowCard(false)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.error('Could not get book card for id', entry.target.id)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
initIO() {
|
|
||||||
let observerOptions = {
|
|
||||||
rootMargin: '0px',
|
|
||||||
threshold: 0.1
|
|
||||||
}
|
|
||||||
|
|
||||||
this.observer = new IntersectionObserver(this.observerCallback, observerOptions)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
updated() {
|
|
||||||
if (this.$refs.wrapper) {
|
|
||||||
if (this.wrapperClientWidth !== this.$refs.wrapper.clientWidth) {
|
|
||||||
this.$nextTick(this.setBookshelfEntities)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
window.addEventListener('resize', this.resize)
|
|
||||||
this.$store.commit('audiobooks/addListener', { id: 'bookshelf', meth: this.audiobooksUpdated })
|
|
||||||
this.$store.commit('user/addSettingsListener', { id: 'bookshelf', meth: this.settingsUpdated })
|
|
||||||
this.$store.commit('user/addCollectionsListener', { id: 'bookshelf', meth: this.collectionsUpdated })
|
|
||||||
|
|
||||||
this.init()
|
|
||||||
this.initIO()
|
|
||||||
},
|
|
||||||
beforeDestroy() {
|
|
||||||
window.removeEventListener('resize', this.resize)
|
|
||||||
this.$store.commit('audiobooks/removeListener', 'bookshelf')
|
|
||||||
this.$store.commit('user/removeSettingsListener', 'bookshelf')
|
|
||||||
this.$store.commit('user/removeCollectionsListener', 'bookshelf')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.bookshelfRow {
|
|
||||||
background-image: var(--bookshelf-texture-img);
|
|
||||||
}
|
|
||||||
.bookshelfDivider {
|
|
||||||
background: rgb(149, 119, 90);
|
|
||||||
background: linear-gradient(180deg, rgba(149, 119, 90, 1) 0%, rgba(103, 70, 37, 1) 17%, rgba(103, 70, 37, 1) 88%, rgba(71, 48, 25, 1) 100%);
|
|
||||||
box-shadow: 2px 14px 8px #111111aa;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -149,7 +149,6 @@ export default {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
this.shelves = shelves
|
this.shelves = shelves
|
||||||
},
|
},
|
||||||
settingsUpdated(settings) {},
|
settingsUpdated(settings) {},
|
||||||
|
@ -1,40 +1,28 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<div ref="shelf" class="w-full max-w-full categorizedBookshelfRow relative overflow-x-scroll overflow-y-hidden z-10" :style="{ paddingLeft: paddingLeft * sizeMultiplier + 'rem' }" @scroll="scrolled">
|
<div ref="shelf" class="w-full max-w-full categorizedBookshelfRow relative overflow-x-scroll overflow-y-hidden z-10" :style="{ paddingLeft: paddingLeft * sizeMultiplier + 'rem', height: shelfHeight + 'px' }" @scroll="scrolled">
|
||||||
<div class="w-full h-full" :style="{ marginTop: sizeMultiplier + 'rem' }">
|
<div class="w-full h-full pt-6">
|
||||||
<div v-if="shelf.type === 'books'" class="flex items-center -mb-2">
|
<div v-if="shelf.type === 'books'" class="flex items-center">
|
||||||
<template v-for="entity in shelf.entities">
|
<template v-for="(entity, index) in shelf.entities">
|
||||||
<cards-book-card :key="entity.id" :width="bookCoverWidth" :user-progress="userAudiobooks[entity.id]" :audiobook="entity" @hook:updated="updatedBookCard" :padding-y="24" :book-cover-aspect-ratio="bookCoverAspectRatio" @edit="editBook" />
|
<cards-lazy-book-card :key="entity.id" :ref="`shelf-book-${entity.id}`" :index="index" :width="bookCoverWidth" :height="bookCoverHeight" :book-cover-aspect-ratio="bookCoverAspectRatio" :book-mount="entity" class="relative mx-2" @hook:updated="updatedBookCard" @select="selectBook" @edit="editBook" />
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="shelf.type === 'series'" class="flex items-center -mb-2">
|
<div v-if="shelf.type === 'series'" class="flex items-center">
|
||||||
<template v-for="entity in shelf.entities">
|
<template v-for="entity in shelf.entities">
|
||||||
<cards-group-card :key="entity.name" is-categorized :width="bookCoverWidth" :group="entity" :book-cover-aspect-ratio="bookCoverAspectRatio" @hook:updated="updatedBookCard" @click="$emit('clickSeries', entity)" />
|
<cards-lazy-series-card :key="entity.name" :series-mount="entity" :height="bookCoverHeight" :width="bookCoverWidth * 2" :book-cover-aspect-ratio="bookCoverAspectRatio" class="relative mx-2" @hook:updated="updatedBookCard" />
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="shelf.type === 'tags'" class="flex items-center -mb-2">
|
<div v-if="shelf.type === 'tags'" class="flex items-center">
|
||||||
<template v-for="entity in shelf.entities">
|
<template v-for="entity in shelf.entities">
|
||||||
<nuxt-link :key="entity.name" :to="`/library/${currentLibraryId}/bookshelf?filter=tags.${$encode(entity.name)}`">
|
<nuxt-link :key="entity.name" :to="`/library/${currentLibraryId}/bookshelf?filter=tags.${$encode(entity.name)}`">
|
||||||
<cards-group-card is-categorized :width="bookCoverWidth" :group="entity" :book-cover-aspect-ratio="bookCoverAspectRatio" @hook:updated="updatedBookCard" />
|
<cards-group-card is-categorized :width="bookCoverWidth" :group="entity" :book-cover-aspect-ratio="bookCoverAspectRatio" @hook:updated="updatedBookCard" />
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="shelf.type === 'authors'" class="flex items-center -mb-2">
|
<div v-if="shelf.type === 'authors'" class="flex items-center">
|
||||||
<template v-for="entity in shelf.entities">
|
<template v-for="entity in shelf.entities">
|
||||||
<nuxt-link :key="entity.id" :to="`/library/${currentLibraryId}/bookshelf?filter=authors.${$encode(entity.name)}`">
|
<nuxt-link :key="entity.id" :to="`/library/${currentLibraryId}/bookshelf?filter=authors.${$encode(entity.name)}`">
|
||||||
<cards-author-card :width="bookCoverWidth" :height="bookCoverWidth" :author="entity" :size-multiplier="sizeMultiplier" @hook:updated="updatedBookCard" class="pb-6" />
|
<cards-author-card :width="bookCoverWidth" :height="bookCoverWidth" :author="entity" :size-multiplier="sizeMultiplier" @hook:updated="updatedBookCard" class="pb-6 mx-2" />
|
||||||
</nuxt-link>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="shelf.series" class="flex items-center -mb-2">
|
|
||||||
<template v-for="entity in shelf.series">
|
|
||||||
<cards-group-card is-categorized :key="entity.name" :width="bookCoverWidth" :group="entity" :book-cover-aspect-ratio="bookCoverAspectRatio" @hook:updated="updatedBookCard" @click="$emit('clickSeries', entity)" />
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="shelf.tags" class="flex items-center -mb-2">
|
|
||||||
<template v-for="entity in shelf.tags">
|
|
||||||
<nuxt-link :key="entity.name" :to="`/library/${currentLibraryId}/bookshelf?filter=tags.${$encode(entity.name)}`">
|
|
||||||
<cards-group-card is-categorized :width="bookCoverWidth" :group="entity" :book-cover-aspect-ratio="bookCoverAspectRatio" @hook:updated="updatedBookCard" />
|
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
@ -49,10 +37,10 @@
|
|||||||
|
|
||||||
<div class="bookshelfDividerCategorized h-6 w-full absolute bottom-0 left-0 right-0 z-20"></div>
|
<div class="bookshelfDividerCategorized h-6 w-full absolute bottom-0 left-0 right-0 z-20"></div>
|
||||||
|
|
||||||
<div v-show="canScrollLeft && !isScrolling" class="absolute top-0 left-0 w-32 pr-8 bg-black book-shelf-arrow-left flex items-center justify-center cursor-pointer opacity-0 hover:opacity-100 z-30" @click="scrollLeft">
|
<div v-show="canScrollLeft && !isScrolling" class="hidden sm:flex absolute top-0 left-0 w-32 pr-8 bg-black book-shelf-arrow-left items-center justify-center cursor-pointer opacity-0 hover:opacity-100 z-30" @click="scrollLeft">
|
||||||
<span class="material-icons text-6xl text-white">chevron_left</span>
|
<span class="material-icons text-6xl text-white">chevron_left</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-show="canScrollRight && !isScrolling" class="absolute top-0 right-0 w-32 pl-8 bg-black book-shelf-arrow-right flex items-center justify-center cursor-pointer opacity-0 hover:opacity-100 z-30" @click="scrollRight">
|
<div v-show="canScrollRight && !isScrolling" class="hidden sm:flex absolute top-0 right-0 w-32 pl-8 bg-black book-shelf-arrow-right items-center justify-center cursor-pointer opacity-0 hover:opacity-100 z-30" @click="scrollRight">
|
||||||
<span class="material-icons text-6xl text-white">chevron_right</span>
|
<span class="material-icons text-6xl text-white">chevron_right</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -79,7 +67,18 @@ export default {
|
|||||||
updateTimer: null
|
updateTimer: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
isSelectionMode(newVal) {
|
||||||
|
this.updateSelectionMode(newVal)
|
||||||
|
}
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
bookCoverHeight() {
|
||||||
|
return this.bookCoverWidth * this.bookCoverAspectRatio
|
||||||
|
},
|
||||||
|
shelfHeight() {
|
||||||
|
return this.bookCoverHeight + 48
|
||||||
|
},
|
||||||
userAudiobooks() {
|
userAudiobooks() {
|
||||||
return this.$store.state.user.user ? this.$store.state.user.user.audiobooks || {} : {}
|
return this.$store.state.user.user ? this.$store.state.user.user.audiobooks || {} : {}
|
||||||
},
|
},
|
||||||
@ -89,6 +88,9 @@ export default {
|
|||||||
},
|
},
|
||||||
currentLibraryId() {
|
currentLibraryId() {
|
||||||
return this.$store.state.libraries.currentLibraryId
|
return this.$store.state.libraries.currentLibraryId
|
||||||
|
},
|
||||||
|
isSelectionMode() {
|
||||||
|
return this.$store.getters['getNumAudiobooksSelected'] > 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@ -97,6 +99,21 @@ export default {
|
|||||||
this.$store.commit('setBookshelfBookIds', bookIds)
|
this.$store.commit('setBookshelfBookIds', bookIds)
|
||||||
this.$store.commit('showEditModal', audiobook)
|
this.$store.commit('showEditModal', audiobook)
|
||||||
},
|
},
|
||||||
|
updateSelectionMode(val) {
|
||||||
|
var selectedAudiobooks = this.$store.state.selectedAudiobooks
|
||||||
|
if (this.shelf.type === 'books') {
|
||||||
|
this.shelf.entities.forEach((ent) => {
|
||||||
|
var component = this.$refs[`shelf-book-${ent.id}`]
|
||||||
|
if (!component || !component.length) return
|
||||||
|
component = component[0]
|
||||||
|
component.setSelectionMode(val)
|
||||||
|
component.selected = selectedAudiobooks.includes(ent.id)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selectBook(audiobook) {
|
||||||
|
this.$store.commit('toggleAudiobookSelected', audiobook.id)
|
||||||
|
},
|
||||||
scrolled() {
|
scrolled() {
|
||||||
clearTimeout(this.scrollTimer)
|
clearTimeout(this.scrollTimer)
|
||||||
this.scrollTimer = setTimeout(() => {
|
this.scrollTimer = setTimeout(() => {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="bookshelf" class="w-full overflow-y-auto">
|
<div id="bookshelf" class="w-full overflow-y-auto">
|
||||||
<template v-for="shelf in totalShelves">
|
<template v-for="shelf in totalShelves">
|
||||||
<div :key="shelf" class="w-full px-8 bookshelfRow relative" :id="`shelf-${shelf - 1}`" :style="{ height: shelfHeight + 'px' }">
|
<div :key="shelf" class="w-full px-4 sm:px-8 bookshelfRow relative" :id="`shelf-${shelf - 1}`" :style="{ height: shelfHeight + 'px' }">
|
||||||
<!-- <div class="absolute top-0 left-0 bottom-0 p-4 z-10">
|
<!-- <div class="absolute top-0 left-0 bottom-0 p-4 z-10">
|
||||||
<p class="text-white text-2xl">{{ shelf }}</p>
|
<p class="text-white text-2xl">{{ shelf }}</p>
|
||||||
</div> -->
|
</div> -->
|
||||||
@ -128,14 +128,21 @@ export default {
|
|||||||
if (this.isCoverSquareAspectRatio) return this.bookWidth
|
if (this.isCoverSquareAspectRatio) return this.bookWidth
|
||||||
return this.bookWidth * 1.6
|
return this.bookWidth * 1.6
|
||||||
},
|
},
|
||||||
|
shelfPadding() {
|
||||||
|
if (this.bookshelfWidth < 640) return 32
|
||||||
|
return 64
|
||||||
|
},
|
||||||
|
totalPadding() {
|
||||||
|
return this.shelfPadding * 2
|
||||||
|
},
|
||||||
entityWidth() {
|
entityWidth() {
|
||||||
if (this.entityName === 'series') return this.bookWidth * 2
|
if (this.entityName === 'series' || this.entityName === 'collections') {
|
||||||
if (this.entityName === 'collections') return this.bookWidth * 2
|
if (this.bookWidth * 2 > this.bookshelfWidth - this.shelfPadding) return this.bookWidth * 1.6
|
||||||
|
return this.bookWidth * 2
|
||||||
|
}
|
||||||
return this.bookWidth
|
return this.bookWidth
|
||||||
},
|
},
|
||||||
entityHeight() {
|
entityHeight() {
|
||||||
if (this.entityName === 'series') return this.bookHeight
|
|
||||||
if (this.entityName === 'collections') return this.bookHeight
|
|
||||||
return this.bookHeight
|
return this.bookHeight
|
||||||
},
|
},
|
||||||
shelfDividerHeightIndex() {
|
shelfDividerHeightIndex() {
|
||||||
@ -225,7 +232,6 @@ export default {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (payload) {
|
if (payload) {
|
||||||
// console.log('Received payload', payload)
|
|
||||||
if (!this.initialized) {
|
if (!this.initialized) {
|
||||||
this.initialized = true
|
this.initialized = true
|
||||||
this.totalEntities = payload.total
|
this.totalEntities = payload.total
|
||||||
@ -237,7 +243,6 @@ export default {
|
|||||||
for (let i = 0; i < payload.results.length; i++) {
|
for (let i = 0; i < payload.results.length; i++) {
|
||||||
var index = i + startIndex
|
var index = i + startIndex
|
||||||
this.entities[index] = payload.results[i]
|
this.entities[index] = payload.results[i]
|
||||||
|
|
||||||
if (this.entityComponentRefs[index]) {
|
if (this.entityComponentRefs[index]) {
|
||||||
this.entityComponentRefs[index].setEntity(this.entities[index])
|
this.entityComponentRefs[index].setEntity(this.entities[index])
|
||||||
}
|
}
|
||||||
@ -437,7 +442,7 @@ export default {
|
|||||||
this.mountWindowWidth = window.innerWidth
|
this.mountWindowWidth = window.innerWidth
|
||||||
this.bookshelfHeight = clientHeight
|
this.bookshelfHeight = clientHeight
|
||||||
this.bookshelfWidth = clientWidth
|
this.bookshelfWidth = clientWidth
|
||||||
this.entitiesPerShelf = Math.floor((this.bookshelfWidth - 64) / this.totalEntityCardWidth)
|
this.entitiesPerShelf = Math.max(1, Math.floor((this.bookshelfWidth - this.shelfPadding) / this.totalEntityCardWidth))
|
||||||
this.shelvesPerPage = Math.ceil(this.bookshelfHeight / this.shelfHeight) + 2
|
this.shelvesPerPage = Math.ceil(this.bookshelfHeight / this.shelfHeight) + 2
|
||||||
this.bookshelfMarginLeft = (this.bookshelfWidth - this.entitiesPerShelf * this.totalEntityCardWidth) / 2
|
this.bookshelfMarginLeft = (this.bookshelfWidth - this.entitiesPerShelf * this.totalEntityCardWidth) / 2
|
||||||
|
|
||||||
@ -526,10 +531,12 @@ export default {
|
|||||||
this.initListeners()
|
this.initListeners()
|
||||||
},
|
},
|
||||||
updated() {
|
updated() {
|
||||||
if (window.innerWidth > 0 && window.innerWidth !== this.mountWindowWidth) {
|
setTimeout(() => {
|
||||||
console.log('Updated window width', window.innerWidth, 'from', this.mountWindowWidth)
|
if (window.innerWidth > 0 && window.innerWidth !== this.mountWindowWidth) {
|
||||||
this.rebuild()
|
console.log('Updated window width', window.innerWidth, 'from', this.mountWindowWidth)
|
||||||
}
|
this.rebuild()
|
||||||
|
}
|
||||||
|
}, 50)
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
this.destroyEntityComponents()
|
this.destroyEntityComponents()
|
||||||
|
@ -1,433 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div ref="wrapper" class="relative book-card" :data-bookId="audiobookId" :id="`book-card-${audiobookId}`">
|
|
||||||
<template v-if="!showCard">
|
|
||||||
<div class="rounded-sm h-full overflow-hidden relative" :style="{ padding: `${paddingY}px ${paddingX}px` }">
|
|
||||||
<div class="bg-bg flex items-center justify-center p-2" :style="{ height: height + 'px', width: width + 'px' }">
|
|
||||||
<p class="font-book text-center" :style="{ fontSize: 0.75 * sizeMultiplier + 'rem' }">{{ title }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<!-- New Book Flag -->
|
|
||||||
<div v-show="isNew" class="absolute top-4 left-0 w-4 h-10 pr-2 bg-darkgreen box-shadow-xl z-20">
|
|
||||||
<div class="absolute top-0 left-0 w-full h-full transform -rotate-90 flex items-center justify-center">
|
|
||||||
<p class="text-center text-sm">New</p>
|
|
||||||
</div>
|
|
||||||
<div class="absolute -bottom-4 left-0 triangle-right" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="rounded-sm h-full overflow-hidden relative" :style="{ padding: `${paddingY}px ${paddingX}px` }">
|
|
||||||
<nuxt-link :to="isSelectionMode ? '' : `/audiobook/${audiobookId}`" class="cursor-pointer">
|
|
||||||
<div class="w-full relative box-shadow-book" :style="{ height: height + 'px' }" @click="clickCard" @mouseover="isHovering = true" @mouseleave="isHovering = false">
|
|
||||||
<covers-book-cover :audiobook="audiobook" :author-override="authorFormat" :width="width" :book-cover-aspect-ratio="bookCoverAspectRatio" />
|
|
||||||
|
|
||||||
<!-- Hidden SM and DOWN -->
|
|
||||||
<div v-show="isHovering || isSelectionMode || isMoreMenuOpen" class="absolute top-0 left-0 w-full h-full bg-black rounded hidden md:block z-20" :class="overlayWrapperClasslist">
|
|
||||||
<div v-show="showPlayButton" class="h-full flex items-center justify-center">
|
|
||||||
<div class="hover:text-gray-200 hover:scale-110 transform duration-200" @click.stop.prevent="play">
|
|
||||||
<span class="material-icons" :style="{ fontSize: playIconFontSize + 'rem' }">play_circle_filled</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-show="showReadButton" class="h-full flex items-center justify-center">
|
|
||||||
<div class="hover:text-gray-200 hover:scale-110 transform duration-200" @click.stop.prevent="clickReadEBook">
|
|
||||||
<span class="material-icons" :style="{ fontSize: playIconFontSize + 'rem' }">auto_stories</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="userCanUpdate" v-show="!isSelectionMode" class="absolute cursor-pointer hover:text-yellow-300 hover:scale-125 transform duration-50" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem' }" @click.stop.prevent="editClick">
|
|
||||||
<span class="material-icons" :style="{ fontSize: sizeMultiplier + 'rem' }">edit</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="absolute cursor-pointer hover:text-yellow-300 hover:scale-125 transform duration-100" :style="{ top: 0.375 * sizeMultiplier + 'rem', left: 0.375 * sizeMultiplier + 'rem' }" @click.stop.prevent="selectBtnClick">
|
|
||||||
<span class="material-icons" :class="selected ? 'text-yellow-400' : ''" :style="{ fontSize: 1.25 * sizeMultiplier + 'rem' }">{{ selected ? 'radio_button_checked' : 'radio_button_unchecked' }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- More Icon -->
|
|
||||||
<div ref="moreIcon" v-show="!isSelectionMode" class="hidden md:block absolute cursor-pointer hover:text-yellow-300" :style="{ bottom: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem' }" @click.stop.prevent="clickShowMore">
|
|
||||||
<span class="material-icons" :style="{ fontSize: 1.2 * sizeMultiplier + 'rem' }">more_vert</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="volumeNumber && showVolumeNumber && !isHovering && !isSelectionMode" class="absolute rounded-lg bg-black bg-opacity-90 box-shadow-md z-10" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', padding: `${0.1 * sizeMultiplier}rem ${0.25 * sizeMultiplier}rem` }">
|
|
||||||
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">#{{ volumeNumber }}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- EBook Icon -->
|
|
||||||
<div
|
|
||||||
v-if="showSmallEBookIcon"
|
|
||||||
class="absolute rounded-full bg-blue-500 flex items-center justify-center bg-opacity-90 hover:scale-125 transform duration-200 z-10"
|
|
||||||
:style="{ bottom: 0.375 * sizeMultiplier + 'rem', left: 0.375 * sizeMultiplier + 'rem', padding: `${0.1 * sizeMultiplier}rem ${0.25 * sizeMultiplier}rem`, width: 1.5 * sizeMultiplier + 'rem', height: 1.5 * sizeMultiplier + 'rem' }"
|
|
||||||
@click.stop.prevent="clickReadEBook"
|
|
||||||
>
|
|
||||||
<!-- <p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">EBook</p> -->
|
|
||||||
<span class="material-icons text-white" :style="{ fontSize: sizeMultiplier * 1 + 'rem' }">auto_stories</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-show="!isSelectionMode" class="absolute bottom-0 left-0 h-1 shadow-sm max-w-full z-10" :class="userIsRead ? 'bg-success' : 'bg-yellow-400'" :style="{ width: width * userProgressPercent + 'px' }"></div>
|
|
||||||
|
|
||||||
<ui-tooltip v-if="showError" :text="errorText" class="absolute bottom-4 left-0 z-20">
|
|
||||||
<div :style="{ height: 1.5 * sizeMultiplier + 'rem', width: 2.5 * sizeMultiplier + 'rem' }" class="bg-error rounded-r-full shadow-md flex items-center justify-end border-r border-b border-red-300">
|
|
||||||
<span class="material-icons text-red-100 pr-1" :style="{ fontSize: 0.875 * sizeMultiplier + 'rem' }">priority_high</span>
|
|
||||||
</div>
|
|
||||||
</ui-tooltip>
|
|
||||||
</div>
|
|
||||||
</nuxt-link>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import Vue from 'vue'
|
|
||||||
import MoreMenu from '@/components/widgets/MoreMenu'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
audiobook: {
|
|
||||||
type: Object,
|
|
||||||
default: () => null
|
|
||||||
},
|
|
||||||
userProgress: {
|
|
||||||
type: Object,
|
|
||||||
default: () => null
|
|
||||||
},
|
|
||||||
width: {
|
|
||||||
type: Number,
|
|
||||||
default: 120
|
|
||||||
},
|
|
||||||
paddingY: {
|
|
||||||
type: Number,
|
|
||||||
default: 16
|
|
||||||
},
|
|
||||||
isBookshelfBook: Boolean,
|
|
||||||
showVolumeNumber: Boolean,
|
|
||||||
bookCoverAspectRatio: Number
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
showCard: false,
|
|
||||||
isHovering: false,
|
|
||||||
isMoreMenuOpen: false,
|
|
||||||
isProcessingReadUpdate: false,
|
|
||||||
rescanning: false,
|
|
||||||
timesVisible: 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
showExperimentalFeatures() {
|
|
||||||
return this.$store.state.showExperimentalFeatures
|
|
||||||
},
|
|
||||||
isNew() {
|
|
||||||
return this.tags.includes('New')
|
|
||||||
},
|
|
||||||
tags() {
|
|
||||||
return this.audiobook.tags || []
|
|
||||||
},
|
|
||||||
audiobookId() {
|
|
||||||
return this.audiobook.id
|
|
||||||
},
|
|
||||||
hasEbook() {
|
|
||||||
return this.audiobook.numEbooks
|
|
||||||
},
|
|
||||||
hasTracks() {
|
|
||||||
return this.audiobook.numTracks
|
|
||||||
},
|
|
||||||
isSelectionMode() {
|
|
||||||
return !!this.selectedAudiobooks.length
|
|
||||||
},
|
|
||||||
selectedAudiobooks() {
|
|
||||||
return this.$store.state.selectedAudiobooks
|
|
||||||
},
|
|
||||||
selected() {
|
|
||||||
return this.$store.getters['getIsAudiobookSelected'](this.audiobookId)
|
|
||||||
},
|
|
||||||
processingBatch() {
|
|
||||||
return this.$store.state.processingBatch
|
|
||||||
},
|
|
||||||
book() {
|
|
||||||
return this.audiobook.book || {}
|
|
||||||
},
|
|
||||||
squareAspectRatio() {
|
|
||||||
return this.bookCoverAspectRatio === 1
|
|
||||||
},
|
|
||||||
height() {
|
|
||||||
return this.width * this.bookCoverAspectRatio
|
|
||||||
},
|
|
||||||
sizeMultiplier() {
|
|
||||||
var baseSize = this.squareAspectRatio ? 192 : 120
|
|
||||||
return this.width / baseSize
|
|
||||||
},
|
|
||||||
paddingX() {
|
|
||||||
return 16 * this.sizeMultiplier
|
|
||||||
},
|
|
||||||
title() {
|
|
||||||
return this.book.title
|
|
||||||
},
|
|
||||||
playIconFontSize() {
|
|
||||||
return Math.max(2, 3 * this.sizeMultiplier)
|
|
||||||
},
|
|
||||||
author() {
|
|
||||||
return this.book.author
|
|
||||||
},
|
|
||||||
authorFL() {
|
|
||||||
return this.book.authorFL || this.author
|
|
||||||
},
|
|
||||||
authorLF() {
|
|
||||||
return this.book.authorLF || this.author
|
|
||||||
},
|
|
||||||
authorFormat() {
|
|
||||||
if (!this.orderBy || !this.orderBy.startsWith('book.author')) return null
|
|
||||||
return this.orderBy === 'book.authorLF' ? this.authorLF : this.authorFL
|
|
||||||
},
|
|
||||||
volumeNumber() {
|
|
||||||
return this.book.volumeNumber || null
|
|
||||||
},
|
|
||||||
orderBy() {
|
|
||||||
return this.$store.getters['user/getUserSetting']('orderBy')
|
|
||||||
},
|
|
||||||
filterBy() {
|
|
||||||
return this.$store.getters['user/getUserSetting']('filterBy')
|
|
||||||
},
|
|
||||||
userProgressPercent() {
|
|
||||||
return this.userProgress ? this.userProgress.progress || 0 : 0
|
|
||||||
},
|
|
||||||
userIsRead() {
|
|
||||||
return this.userProgress ? !!this.userProgress.isRead : false
|
|
||||||
},
|
|
||||||
showError() {
|
|
||||||
return this.hasMissingParts || this.hasInvalidParts || this.isMissing || this.isInvalid
|
|
||||||
},
|
|
||||||
isStreaming() {
|
|
||||||
return this.$store.getters['getAudiobookIdStreaming'] === this.audiobookId
|
|
||||||
},
|
|
||||||
showReadButton() {
|
|
||||||
return !this.isSelectionMode && this.showExperimentalFeatures && !this.showPlayButton && this.hasEbook
|
|
||||||
},
|
|
||||||
showPlayButton() {
|
|
||||||
return !this.isSelectionMode && !this.isMissing && !this.isInvalid && this.hasTracks && !this.isStreaming
|
|
||||||
},
|
|
||||||
showSmallEBookIcon() {
|
|
||||||
return !this.isSelectionMode && this.showExperimentalFeatures && this.hasEbook
|
|
||||||
},
|
|
||||||
isMissing() {
|
|
||||||
return this.audiobook.isMissing
|
|
||||||
},
|
|
||||||
isInvalid() {
|
|
||||||
return this.audiobook.isInvalid
|
|
||||||
},
|
|
||||||
hasMissingParts() {
|
|
||||||
return this.audiobook.hasMissingParts
|
|
||||||
},
|
|
||||||
hasInvalidParts() {
|
|
||||||
return this.audiobook.hasInvalidParts
|
|
||||||
},
|
|
||||||
errorText() {
|
|
||||||
if (this.isMissing) return 'Audiobook directory is missing!'
|
|
||||||
else if (this.isInvalid) return 'Audiobook has no audio tracks & ebook'
|
|
||||||
var txt = ''
|
|
||||||
if (this.hasMissingParts) {
|
|
||||||
txt = `${this.hasMissingParts} missing parts.`
|
|
||||||
}
|
|
||||||
if (this.hasInvalidParts) {
|
|
||||||
if (this.hasMissingParts) txt += ' '
|
|
||||||
txt += `${this.hasInvalidParts} invalid parts.`
|
|
||||||
}
|
|
||||||
return txt || 'Unknown Error'
|
|
||||||
},
|
|
||||||
overlayWrapperClasslist() {
|
|
||||||
var classes = []
|
|
||||||
if (this.isSelectionMode) classes.push('bg-opacity-60')
|
|
||||||
else classes.push('bg-opacity-40')
|
|
||||||
if (this.selected) {
|
|
||||||
classes.push('border-2 border-yellow-400')
|
|
||||||
}
|
|
||||||
return classes
|
|
||||||
},
|
|
||||||
userCanUpdate() {
|
|
||||||
return this.$store.getters['user/getUserCanUpdate']
|
|
||||||
},
|
|
||||||
userCanDelete() {
|
|
||||||
return this.$store.getters['user/getUserCanDelete']
|
|
||||||
},
|
|
||||||
userCanDownload() {
|
|
||||||
return this.$store.getters['user/getUserCanDownload']
|
|
||||||
},
|
|
||||||
userIsRoot() {
|
|
||||||
return this.$store.getters['user/getIsRoot']
|
|
||||||
},
|
|
||||||
moreMenuItems() {
|
|
||||||
var items = [
|
|
||||||
{
|
|
||||||
func: 'toggleRead',
|
|
||||||
text: `Mark as ${this.userIsRead ? 'Not Read' : 'Read'}`
|
|
||||||
},
|
|
||||||
{
|
|
||||||
func: 'openCollections',
|
|
||||||
text: 'Add to Collection'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
if (this.userCanUpdate) {
|
|
||||||
if (this.hasTracks) {
|
|
||||||
items.push({
|
|
||||||
func: 'showEditModalTracks',
|
|
||||||
text: 'Tracks'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
items.push({
|
|
||||||
func: 'showEditModalMatch',
|
|
||||||
text: 'Match'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (this.userCanDownload) {
|
|
||||||
items.push({
|
|
||||||
func: 'showEditModalDownload',
|
|
||||||
text: 'Download'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (this.userIsRoot) {
|
|
||||||
items.push({
|
|
||||||
func: 'rescan',
|
|
||||||
text: 'Re-Scan'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return items
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
setShowCard(val) {
|
|
||||||
if (val) this.timesVisible++
|
|
||||||
this.showCard = val
|
|
||||||
},
|
|
||||||
selectBtnClick() {
|
|
||||||
if (this.processingBatch) return
|
|
||||||
this.$store.commit('toggleAudiobookSelected', this.audiobookId)
|
|
||||||
},
|
|
||||||
clickError(e) {
|
|
||||||
e.stopPropagation()
|
|
||||||
this.$router.push(`/audiobook/${this.audiobookId}`)
|
|
||||||
},
|
|
||||||
play() {
|
|
||||||
this.$store.commit('setStreamAudiobook', this.audiobook)
|
|
||||||
this.$root.socket.emit('open_stream', this.audiobookId)
|
|
||||||
},
|
|
||||||
editClick() {
|
|
||||||
// this.$store.commit('showEditModal', this.audiobook)
|
|
||||||
this.$emit('edit', this.audiobook)
|
|
||||||
},
|
|
||||||
clickCard(e) {
|
|
||||||
if (this.isSelectionMode) {
|
|
||||||
e.stopPropagation()
|
|
||||||
e.preventDefault()
|
|
||||||
this.selectBtnClick()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
clickReadEBook() {
|
|
||||||
this.$store.commit('showEReader', this.audiobook)
|
|
||||||
},
|
|
||||||
toggleRead() {
|
|
||||||
// More menu func
|
|
||||||
var updatePayload = {
|
|
||||||
isRead: !this.userIsRead
|
|
||||||
}
|
|
||||||
this.isProcessingReadUpdate = true
|
|
||||||
this.$axios
|
|
||||||
.$patch(`/api/me/audiobook/${this.audiobookId}`, updatePayload)
|
|
||||||
.then(() => {
|
|
||||||
this.isProcessingReadUpdate = false
|
|
||||||
this.$toast.success(`"${this.title}" Marked as ${updatePayload.isRead ? 'Read' : 'Not Read'}`)
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error('Failed', error)
|
|
||||||
this.isProcessingReadUpdate = false
|
|
||||||
this.$toast.error(`Failed to mark as ${updatePayload.isRead ? 'Read' : 'Not Read'}`)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
audiobookScanComplete(result) {
|
|
||||||
this.rescanning = false
|
|
||||||
if (!result) {
|
|
||||||
this.$toast.error(`Re-Scan Failed for "${this.title}"`)
|
|
||||||
} else if (result === 'UPDATED') {
|
|
||||||
this.$toast.success(`Re-Scan complete audiobook was updated`)
|
|
||||||
} else if (result === 'UPTODATE') {
|
|
||||||
this.$toast.success(`Re-Scan complete audiobook was up to date`)
|
|
||||||
} else if (result === 'REMOVED') {
|
|
||||||
this.$toast.error(`Re-Scan complete audiobook was removed`)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
rescan() {
|
|
||||||
this.rescanning = true
|
|
||||||
this.$root.socket.once('audiobook_scan_complete', this.audiobookScanComplete)
|
|
||||||
this.$root.socket.emit('scan_audiobook', this.audiobookId)
|
|
||||||
},
|
|
||||||
showEditModalTracks() {
|
|
||||||
// More menu func
|
|
||||||
this.$store.commit('showEditModalOnTab', { audiobook: this.audiobook, tab: 'tracks' })
|
|
||||||
},
|
|
||||||
showEditModalMatch() {
|
|
||||||
// More menu func
|
|
||||||
this.$store.commit('showEditModalOnTab', { audiobook: this.audiobook, tab: 'match' })
|
|
||||||
},
|
|
||||||
showEditModalDownload() {
|
|
||||||
// More menu func
|
|
||||||
this.$store.commit('showEditModalOnTab', { audiobook: this.audiobook, tab: 'download' })
|
|
||||||
},
|
|
||||||
openCollections() {
|
|
||||||
this.$store.commit('setSelectedAudiobook', this.audiobook)
|
|
||||||
this.$store.commit('globals/setShowUserCollectionsModal', true)
|
|
||||||
},
|
|
||||||
createMoreMenu() {
|
|
||||||
if (!this.$refs.moreIcon) return
|
|
||||||
|
|
||||||
var ComponentClass = Vue.extend(MoreMenu)
|
|
||||||
|
|
||||||
var _this = this
|
|
||||||
var instance = new ComponentClass({
|
|
||||||
propsData: {
|
|
||||||
items: this.moreMenuItems
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.$on('action', (func) => {
|
|
||||||
if (_this[func]) _this[func]()
|
|
||||||
})
|
|
||||||
this.$on('close', () => {
|
|
||||||
_this.isMoreMenuOpen = false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
instance.$mount()
|
|
||||||
|
|
||||||
var wrapperBox = this.$refs.moreIcon.getBoundingClientRect()
|
|
||||||
var el = instance.$el
|
|
||||||
|
|
||||||
var elHeight = this.moreMenuItems.length * 28 + 2
|
|
||||||
var elWidth = 130
|
|
||||||
|
|
||||||
var bottomOfIcon = wrapperBox.top + wrapperBox.height
|
|
||||||
var rightOfIcon = wrapperBox.left + wrapperBox.width
|
|
||||||
|
|
||||||
var elTop = bottomOfIcon
|
|
||||||
var elLeft = rightOfIcon
|
|
||||||
if (bottomOfIcon + elHeight > window.innerHeight - 100) {
|
|
||||||
elTop = wrapperBox.top - elHeight
|
|
||||||
elLeft = wrapperBox.left
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rightOfIcon + elWidth > window.innerWidth - 100) {
|
|
||||||
elLeft = rightOfIcon - elWidth
|
|
||||||
}
|
|
||||||
|
|
||||||
el.style.top = elTop + 'px'
|
|
||||||
el.style.left = elLeft + 'px'
|
|
||||||
|
|
||||||
this.isMoreMenuOpen = true
|
|
||||||
document.body.appendChild(el)
|
|
||||||
},
|
|
||||||
clickShowMore() {
|
|
||||||
this.createMoreMenu()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.showCard = !this.isBookshelfBook
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<div class="rounded-sm h-full relative" :style="{ padding: `${paddingY}px ${paddingX}px` }" @mouseover="mouseoverCard" @mouseleave="mouseleaveCard" @click="clickCard">
|
<div class="rounded-sm h-full relative" :style="{ padding: `0px ${paddingX}px` }" @mouseover="mouseoverCard" @mouseleave="mouseleaveCard" @click="clickCard">
|
||||||
<nuxt-link :to="groupTo" class="cursor-pointer">
|
<nuxt-link :to="groupTo" class="cursor-pointer">
|
||||||
<div class="w-full h-full relative" :class="isHovering ? 'bg-black-400' : 'bg-primary'" :style="{ height: coverHeight + 'px', width: coverWidth + 'px' }">
|
<div class="w-full h-full relative" :class="isHovering ? 'bg-black-400' : 'bg-primary'" :style="{ height: coverHeight + 'px', width: coverWidth + 'px' }">
|
||||||
<covers-group-cover ref="groupcover" :id="seriesId" :name="groupName" :is-categorized="isCategorized" :group-to="groupTo" :type="groupType" :book-items="bookItems" :width="coverWidth" :height="coverHeight" :book-cover-aspect-ratio="bookCoverAspectRatio" />
|
<covers-group-cover ref="groupcover" :id="seriesId" :name="groupName" :is-categorized="isCategorized" :group-to="groupTo" :type="groupType" :book-items="bookItems" :width="coverWidth" :height="coverHeight" :book-cover-aspect-ratio="bookCoverAspectRatio" />
|
||||||
@ -38,10 +38,6 @@ export default {
|
|||||||
type: Number,
|
type: Number,
|
||||||
default: 120
|
default: 120
|
||||||
},
|
},
|
||||||
paddingY: {
|
|
||||||
type: Number,
|
|
||||||
default: 24
|
|
||||||
},
|
|
||||||
isCategorized: Boolean,
|
isCategorized: Boolean,
|
||||||
bookCoverAspectRatio: Number
|
bookCoverAspectRatio: Number
|
||||||
},
|
},
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div ref="card" :id="`book-card-${index}`" :style="{ width: width + 'px', height: height + 'px' }" class="absolute top-0 left-0 rounded-sm z-10 bg-primary cursor-pointer box-shadow-book" @mousedown.prevent @mouseup.prevent @mousemove.prevent @mouseover="mouseover" @mouseleave="mouseleave" @click="clickCard">
|
<div ref="card" :id="`book-card-${index}`" :style="{ minWidth: width + 'px', maxWidth: width + 'px', height: height + 'px' }" class="rounded-sm z-10 bg-primary cursor-pointer box-shadow-book" @mousedown.prevent @mouseup.prevent @mousemove.prevent @mouseover="mouseover" @mouseleave="mouseleave" @click="clickCard">
|
||||||
<!-- When cover image does not fill -->
|
<!-- When cover image does not fill -->
|
||||||
<div v-show="showCoverBg" class="absolute top-0 left-0 w-full h-full overflow-hidden rounded-sm bg-primary">
|
<div v-show="showCoverBg" class="absolute top-0 left-0 w-full h-full overflow-hidden rounded-sm bg-primary">
|
||||||
<div class="absolute cover-bg" ref="coverBg" />
|
<div class="absolute cover-bg" ref="coverBg" />
|
||||||
@ -78,7 +78,12 @@ export default {
|
|||||||
default: 192
|
default: 192
|
||||||
},
|
},
|
||||||
bookCoverAspectRatio: Number,
|
bookCoverAspectRatio: Number,
|
||||||
showVolumeNumber: Boolean
|
showVolumeNumber: Boolean,
|
||||||
|
bookMount: {
|
||||||
|
// Book can be passed as prop or set with setEntity()
|
||||||
|
type: Object,
|
||||||
|
default: () => null
|
||||||
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@ -465,6 +470,11 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
if (this.bookMount) {
|
||||||
|
this.setEntity(this.bookMount)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div ref="card" :id="`series-card-${index}`" :style="{ width: width + 'px', height: height + 'px' }" class="absolute top-0 left-0 rounded-sm z-30 cursor-pointer" @mousedown.prevent @mouseup.prevent @mousemove.prevent @mouseover="mouseover" @mouseleave="mouseleave" @click="clickCard">
|
<div ref="card" :id="`series-card-${index}`" :style="{ width: width + 'px', height: height + 'px' }" class="rounded-sm z-30 cursor-pointer" @mousedown.prevent @mouseup.prevent @mousemove.prevent @mouseover="mouseover" @mouseleave="mouseleave" @click="clickCard">
|
||||||
<div class="absolute top-0 left-0 w-full box-shadow-book shadow-height" />
|
<div class="absolute top-0 left-0 w-full box-shadow-book shadow-height" />
|
||||||
<div class="w-full h-full bg-primary relative rounded overflow-hidden">
|
<div class="w-full h-full bg-primary relative rounded overflow-hidden">
|
||||||
<covers-group-cover v-if="series" ref="cover" :id="seriesId" :name="title" :book-items="books" :width="width" :height="height" :book-cover-aspect-ratio="bookCoverAspectRatio" :group-to="seriesBooksRoute" />
|
<covers-group-cover v-if="series" ref="cover" :id="seriesId" :name="title" :book-items="books" :width="width" :height="height" :book-cover-aspect-ratio="bookCoverAspectRatio" :group-to="seriesBooksRoute" />
|
||||||
@ -10,7 +10,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- <div v-if="isHovering || isSelectionMode" class="absolute top-0 left-0 w-full h-full bg-black bg-opacity-40">
|
<!-- <div v-if="isHovering || isSelectionMode" class="absolute top-0 left-0 w-full h-full bg-black bg-opacity-40">
|
||||||
</div> -->
|
</div> -->
|
||||||
<div class="categoryPlacard absolute z-30 left-0 right-0 mx-auto -bottom-6 h-6 rounded-md font-book text-center" :style="{ width: Math.min(160, width) + 'px' }">
|
<div v-if="!isCategorized" class="categoryPlacard absolute z-30 left-0 right-0 mx-auto -bottom-6 h-6 rounded-md font-book text-center" :style="{ width: Math.min(160, width) + 'px' }">
|
||||||
<div class="w-full h-full shinyBlack flex items-center justify-center rounded-sm border" :style="{ padding: `0rem ${0.5 * sizeMultiplier}rem` }">
|
<div class="w-full h-full shinyBlack flex items-center justify-center rounded-sm border" :style="{ padding: `0rem ${0.5 * sizeMultiplier}rem` }">
|
||||||
<p class="truncate" :style="{ fontSize: labelFontSize + 'rem' }">{{ title }}</p>
|
<p class="truncate" :style="{ fontSize: labelFontSize + 'rem' }">{{ title }}</p>
|
||||||
</div>
|
</div>
|
||||||
@ -24,7 +24,12 @@ export default {
|
|||||||
index: Number,
|
index: Number,
|
||||||
width: Number,
|
width: Number,
|
||||||
height: Number,
|
height: Number,
|
||||||
bookCoverAspectRatio: Number
|
bookCoverAspectRatio: Number,
|
||||||
|
isCategorized: Boolean,
|
||||||
|
seriesMount: {
|
||||||
|
type: Object,
|
||||||
|
default: () => null
|
||||||
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@ -60,7 +65,7 @@ export default {
|
|||||||
return `/library/${this.currentLibraryId}/series/${this.$encode(this.title)}`
|
return `/library/${this.currentLibraryId}/series/${this.$encode(this.title)}`
|
||||||
},
|
},
|
||||||
seriesId() {
|
seriesId() {
|
||||||
return this.series ? this.$encode(this.series.id) : null
|
return this.series ? this.$encode(this.title) : null
|
||||||
},
|
},
|
||||||
hasValidCovers() {
|
hasValidCovers() {
|
||||||
var validCovers = this.books.map((bookItem) => bookItem.book.cover)
|
var validCovers = this.books.map((bookItem) => bookItem.book.cover)
|
||||||
@ -69,6 +74,7 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
setEntity(_series) {
|
setEntity(_series) {
|
||||||
|
console.log('setting entity', _series)
|
||||||
this.series = _series
|
this.series = _series
|
||||||
},
|
},
|
||||||
setSelectionMode(val) {
|
setSelectionMode(val) {
|
||||||
@ -100,7 +106,11 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {},
|
mounted() {
|
||||||
|
if (this.seriesMount) {
|
||||||
|
this.setEntity(this.seriesMount)
|
||||||
|
}
|
||||||
|
},
|
||||||
beforeDestroy() {}
|
beforeDestroy() {}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -71,6 +71,7 @@ export default {
|
|||||||
|
|
||||||
instance.$mount()
|
instance.$mount()
|
||||||
instance.$el.style.transform = `translate3d(${shelfOffsetX}px, ${shelfOffsetY}px, 0px)`
|
instance.$el.style.transform = `translate3d(${shelfOffsetX}px, ${shelfOffsetY}px, 0px)`
|
||||||
|
instance.$el.classList.add('absolute', 'top-0', 'left-0')
|
||||||
shelfEl.appendChild(instance.$el)
|
shelfEl.appendChild(instance.$el)
|
||||||
|
|
||||||
if (this.entities[index]) {
|
if (this.entities[index]) {
|
||||||
|
@ -143,5 +143,6 @@ export {
|
|||||||
export default ({ app }, inject) => {
|
export default ({ app }, inject) => {
|
||||||
app.$decode = decode
|
app.$decode = decode
|
||||||
app.$encode = encode
|
app.$encode = encode
|
||||||
app.$isDev = process.env.NODE_ENV !== 'production'
|
// app.$isDev = process.env.NODE_ENV !== 'production'
|
||||||
|
inject('isDev', process.env.NODE_ENV !== 'production')
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user