Add: Experimental collections edit, book list, collection cover #151

This commit is contained in:
advplyr 2021-11-06 20:31:46 -05:00
parent 28ccd4e568
commit 465e6869c0
31 changed files with 555 additions and 60 deletions

View file

@ -1,6 +1,6 @@
<template>
<div class="flex h-full px-1 overflow-hidden">
<cards-book-cover :audiobook="audiobook" :width="50" />
<covers-book-cover :audiobook="audiobook" :width="50" />
<div class="flex-grow px-2 audiobookSearchCardContent">
<p v-if="matchKey !== 'title'" class="truncate text-sm">{{ title }}</p>
<p v-else class="truncate text-sm" v-html="matchHtml" />

View file

@ -11,7 +11,7 @@
<div class="rounded-sm h-full overflow-hidden relative" :style="{ padding: `${paddingY}px ${paddingX}px` }" @click.stop>
<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">
<cards-book-cover :audiobook="audiobook" :author-override="authorFormat" :width="width" />
<covers-book-cover :audiobook="audiobook" :author-override="authorFormat" :width="width" />
<!-- Hidden SM and DOWN -->
<div v-show="isHovering || isSelectionMode" class="absolute top-0 left-0 w-full h-full bg-black rounded hidden md:block" :class="overlayWrapperClasslist">

View file

@ -1,139 +0,0 @@
<template>
<div class="relative rounded-sm overflow-hidden" :style="{ height: width * 1.6 + 'px', width: width + 'px', maxWidth: width + 'px', minWidth: width + 'px' }">
<div class="w-full h-full relative">
<div v-if="showCoverBg" class="bg-primary absolute top-0 left-0 w-full h-full">
<div class="w-full h-full z-0" ref="coverBg" />
</div>
<img ref="cover" :src="fullCoverUrl" @error="imageError" @load="imageLoaded" class="w-full h-full absolute top-0 left-0" :class="showCoverBg ? 'object-contain' : 'object-cover'" />
</div>
<div v-if="imageFailed" class="absolute top-0 left-0 right-0 bottom-0 w-full h-full bg-red-100" :style="{ padding: placeholderCoverPadding + 'rem' }">
<div class="w-full h-full border-2 border-error flex flex-col items-center justify-center">
<img src="/Logo.png" class="mb-2" :style="{ height: 64 * sizeMultiplier + 'px' }" />
<p class="text-center font-book text-error" :style="{ fontSize: titleFontSize + 'rem' }">Invalid Cover</p>
</div>
</div>
<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' }">
<div>
<p class="text-center font-book" style="color: rgb(247 223 187)" :style="{ fontSize: titleFontSize + 'rem' }">{{ titleCleaned }}</p>
</div>
</div>
<div v-if="!hasCover" class="absolute left-0 right-0 w-full flex items-center justify-center" :style="{ padding: placeholderCoverPadding + 'rem', bottom: authorBottom + 'rem' }">
<p class="text-center font-book" style="color: rgb(247 223 187); opacity: 0.75" :style="{ fontSize: authorFontSize + 'rem' }">{{ authorCleaned }}</p>
</div>
</div>
</template>
<script>
export default {
props: {
audiobook: {
type: Object,
default: () => {}
},
authorOverride: String,
width: {
type: Number,
default: 120
}
},
data() {
return {
imageFailed: false,
showCoverBg: false
}
},
watch: {
cover() {
this.imageFailed = false
}
},
computed: {
book() {
return this.audiobook.book || {}
},
title() {
return this.book.title || 'No Title'
},
titleCleaned() {
if (this.title.length > 60) {
return this.title.slice(0, 57) + '...'
}
return this.title
},
author() {
if (this.authorOverride) return this.authorOverride
return this.book.author || 'Unknown'
},
authorCleaned() {
if (this.author.length > 30) {
return this.author.slice(0, 27) + '...'
}
return this.author
},
placeholderUrl() {
return '/book_placeholder.jpg'
},
fullCoverUrl() {
return this.$store.getters['audiobooks/getBookCoverSrc'](this.audiobook, this.placeholderUrl)
},
cover() {
return this.book.cover || this.placeholderUrl
},
hasCover() {
return !!this.book.cover
},
sizeMultiplier() {
return this.width / 120
},
titleFontSize() {
return 0.75 * this.sizeMultiplier
},
authorFontSize() {
return 0.6 * this.sizeMultiplier
},
placeholderCoverPadding() {
return 0.8 * this.sizeMultiplier
},
authorBottom() {
return 0.75 * this.sizeMultiplier
},
userToken() {
return this.$store.getters['user/getToken']
}
},
methods: {
setCoverBg() {
if (this.$refs.coverBg) {
this.$refs.coverBg.style.backgroundImage = `url("${this.fullCoverUrl}")`
this.$refs.coverBg.style.backgroundSize = 'cover'
this.$refs.coverBg.style.backgroundPosition = 'center'
this.$refs.coverBg.style.opacity = 0.25
this.$refs.coverBg.style.filter = 'blur(1px)'
}
},
hideCoverBg() {},
imageLoaded() {
if (this.$refs.cover && this.cover !== this.placeholderUrl) {
var { naturalWidth, naturalHeight } = this.$refs.cover
var aspectRatio = naturalHeight / naturalWidth
var arDiff = Math.abs(aspectRatio - 1.6)
// If image aspect ratio is <= 1.45 or >= 1.75 then use cover bg, otherwise stretch to fit
if (arDiff > 0.15) {
this.showCoverBg = true
this.$nextTick(this.setCoverBg)
} else {
this.showCoverBg = false
}
}
},
imageError(err) {
console.error('ImgError', err)
this.imageFailed = true
}
},
mounted() {}
}
</script>

View file

@ -0,0 +1,112 @@
<template>
<div class="relative">
<div class="rounded-sm h-full relative" :style="{ padding: `${paddingY}px ${paddingX}px` }" @mouseover="mouseoverCard" @mouseleave="mouseleaveCard" @click="clickCard">
<nuxt-link :to="groupTo" class="cursor-pointer">
<div class="w-full relative" :class="isHovering ? 'bg-black-400' : 'bg-primary'" :style="{ height: coverHeight + 'px', width: coverWidth + 'px' }">
<covers-collection-cover ref="groupcover" :book-items="bookItems" :width="coverWidth" :height="coverHeight" />
<div v-show="isHovering" class="w-full h-full absolute top-0 left-0 z-10 bg-black bg-opacity-40 pointer-events-none">
<!-- <div class="absolute pointer-events-auto" :style="{ top: 0.5 * sizeMultiplier + 'rem', left: 0.5 * sizeMultiplier + 'rem' }" @click.stop.prevent="toggleSelected">
<span class="material-icons text-xl text-white text-opacity-75 hover:text-opacity-100">radio_button_unchecked</span>
</div> -->
<div class="absolute pointer-events-auto" :style="{ top: 0.5 * sizeMultiplier + 'rem', right: 0.5 * sizeMultiplier + 'rem' }" @click.stop.prevent="clickEdit">
<span class="material-icons text-xl text-white text-opacity-75 hover:text-opacity-100">edit</span>
</div>
</div>
</div>
</nuxt-link>
</div>
<div class="categoryPlacard absolute z-30 left-0 right-0 mx-auto bottom-0 h-6 rounded-md font-book text-center" :style="{ width: Math.min(160, coverWidth) + 'px' }">
<div class="w-full h-full shinyBlack flex items-center justify-center rounded-sm border" :style="{ padding: `0rem ${1 * sizeMultiplier}rem` }">
<p class="truncate" :style="{ fontSize: labelFontSize + 'rem' }">{{ collectionName }}</p>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
collection: {
type: Object,
default: () => null
},
width: {
type: Number,
default: 120
},
paddingY: {
type: Number,
default: 24
}
},
data() {
return {
isHovering: false
}
},
watch: {
width(newVal) {
this.$nextTick(() => {
if (this.$refs.groupcover && this.$refs.groupcover.init) {
this.$refs.groupcover.init()
}
})
}
},
computed: {
labelFontSize() {
if (this.coverWidth < 160) return 0.75
return 0.875
},
currentLibraryId() {
return this.$store.state.libraries.currentLibraryId
},
_collection() {
return this.collection || {}
},
groupTo() {
return `/collection/${this._collection.id}`
},
coverWidth() {
return this.width * 2
},
coverHeight() {
return this.width * 1.6
},
sizeMultiplier() {
return this.width / 120
},
paddingX() {
return 16 * this.sizeMultiplier
},
bookItems() {
return this._collection.books || []
},
collectionName() {
return this._collection.name || 'No Name'
},
showExperimentalFeatures() {
return this.$store.state.showExperimentalFeatures
}
},
methods: {
toggleSelected() {
// Selected
},
clickEdit() {
this.$store.commit('globals/setEditCollection', this.collection)
},
mouseoverCard() {
this.isHovering = true
},
mouseleaveCard() {
this.isHovering = false
},
clickCard() {
this.$emit('click', this.collection)
}
}
}
</script>

View file

@ -2,10 +2,10 @@
<div class="relative">
<div class="rounded-sm h-full relative" :style="{ padding: `16px ${paddingX}px` }" @mouseover="mouseoverCard" @mouseleave="mouseleaveCard" @click="clickCard">
<nuxt-link :to="groupTo" class="cursor-pointer">
<div class="w-full relative" :class="isHovering ? 'bg-black-400' : 'bg-primary'" :style="{ height: height + 'px', width: height + 'px' }">
<cards-group-cover ref="groupcover" :name="groupName" :group-to="groupTo" :type="groupType" :book-items="bookItems" :width="height" :height="height" />
<div class="w-full relative" :class="isHovering ? 'bg-black-400' : 'bg-primary'" :style="{ height: coverHeight + 'px', width: coverWidth + 'px' }">
<covers-group-cover ref="groupcover" :name="groupName" :group-to="groupTo" :type="groupType" :book-items="bookItems" :width="coverWidth" :height="coverHeight" />
<div v-if="hasValidCovers && !showExperimentalFeatures && groupType !== 'collection'" class="bg-black bg-opacity-60 absolute top-0 left-0 w-full h-full flex items-center justify-center text-center transition-opacity z-30" :class="isHovering ? '' : 'opacity-0'" :style="{ padding: `${sizeMultiplier}rem` }">
<div v-if="hasValidCovers && !showExperimentalFeatures" class="bg-black bg-opacity-60 absolute top-0 left-0 w-full h-full flex items-center justify-center text-center transition-opacity z-30" :class="isHovering ? '' : 'opacity-0'" :style="{ padding: `${sizeMultiplier}rem` }">
<p class="font-book" :style="{ fontSize: sizeMultiplier + 'rem' }">{{ groupName }}</p>
</div>
@ -66,7 +66,10 @@ export default {
return `/library/${this.currentLibraryId}/bookshelf?filter=tags.${this.groupEncode}`
}
},
height() {
coverWidth() {
return this.coverHeight
},
coverHeight() {
return this.width * 1.6
},
sizeMultiplier() {
@ -90,9 +93,6 @@ export default {
groupName() {
return this._group.name || 'No Name'
},
groupType() {
return this._group.type
},
groupEncode() {
return this.$encode(this.groupName)
},

View file

@ -1,340 +0,0 @@
<template>
<div ref="wrapper" :style="{ height: height + 'px', width: width + 'px' }" class="relative" @mouseover="mouseoverCover" @mouseleave="mouseleaveCover">
<div v-if="noValidCovers" class="absolute top-0 left-0 w-full h-full flex items-center justify-center box-shadow-book" :style="{ padding: `${sizeMultiplier}rem` }">
<p :style="{ fontSize: sizeMultiplier + 'rem' }">{{ name }}</p>
</div>
</div>
</template>
<script>
export default {
props: {
name: String,
bookItems: {
type: Array,
default: () => []
},
width: Number,
height: Number,
groupTo: String,
type: String
},
data() {
return {
noValidCovers: false,
coverDiv: null,
isHovering: false,
coverWrapperEl: null,
coverImageEls: [],
coverWidth: 0,
offsetIncrement: 0,
isFannedOut: false,
isDetached: false,
isAttaching: false,
windowWidth: 0
}
},
watch: {
bookItems: {
immediate: true,
handler(newVal) {
if (newVal) {
// ensure wrapper is initialized
this.$nextTick(this.init)
}
}
}
},
computed: {
sizeMultiplier() {
return this.width / 192
},
showExperimentalFeatures() {
return this.$store.state.showExperimentalFeatures
},
showCoverFan() {
if (this.type === 'collection') return false
return this.showExperimentalFeatures && this.windowWidth > 1024
}
},
methods: {
mouseoverCover() {
if (this.showCoverFan) this.setHover(true)
},
mouseleaveCover() {
if (this.showCoverFan) this.setHover(false)
},
detchCoverWrapper() {
if (!this.coverWrapperEl || !this.$refs.wrapper || this.isDetached) return
this.coverWrapperEl.remove()
this.isDetached = true
document.body.appendChild(this.coverWrapperEl)
this.coverWrapperEl.addEventListener('mouseleave', this.mouseleaveCover)
this.coverWrapperEl.style.position = 'absolute'
this.coverWrapperEl.style.zIndex = 40
this.updatePosition()
},
attachCoverWrapper() {
if (!this.coverWrapperEl || !this.$refs.wrapper || !this.isDetached) return
this.coverWrapperEl.remove()
this.coverWrapperEl.style.position = 'relative'
this.coverWrapperEl.style.left = 'unset'
this.coverWrapperEl.style.top = 'unset'
this.coverWrapperEl.style.width = this.$refs.wrapper.clientWidth + 'px'
this.$refs.wrapper.appendChild(this.coverWrapperEl)
this.isDetached = false
},
updatePosition() {
var rect = this.$refs.wrapper.getBoundingClientRect()
this.coverWrapperEl.style.top = rect.top + window.scrollY + 'px'
this.coverWrapperEl.style.left = rect.left + window.scrollX + 4 + 'px'
this.coverWrapperEl.style.height = rect.height + 'px'
this.coverWrapperEl.style.width = rect.width + 'px'
},
setHover(val) {
if (this.isAttaching) return
if (val && !this.isHovering) {
this.detchCoverWrapper()
this.fanOutCovers()
} else if (!val && this.isHovering) {
this.isAttaching = true
this.reverseFan()
setTimeout(() => {
this.attachCoverWrapper()
this.isAttaching = false
}, 100)
}
this.isHovering = val
},
fanOutCovers() {
if (this.coverImageEls.length < 2 || this.isFannedOut) return
this.isFannedOut = true
var fanCoverWidth = this.coverWidth * 0.75
var maximumWidth = window.innerWidth - 80
var totalFanWidth = (this.coverImageEls.length + 1) * fanCoverWidth
// If Fan width is too large, set new fan cover width
if (totalFanWidth > maximumWidth) {
fanCoverWidth = maximumWidth / (this.coverImageEls.length + 1)
}
var fanWidth = (this.coverImageEls.length - 1) * fanCoverWidth
var offsetLeft = (-1 * fanWidth) / 2
var rect = this.$refs.wrapper.getBoundingClientRect()
// If fan is going off page left or right, make adjustment
var leftEdge = rect.left + offsetLeft
var rightEdge = rect.left + rect.width - offsetLeft
if (leftEdge < 0) {
offsetLeft += leftEdge * -1
}
if (rightEdge + 80 > window.innerWidth) {
var difference = rightEdge + 80 - window.innerWidth
offsetLeft -= difference / 2
}
for (let i = 0; i < this.coverImageEls.length; i++) {
var coverEl = this.coverImageEls[i]
// Series name card pop out further
if (i === this.coverImageEls.length - 1) {
offsetLeft += fanCoverWidth * 0.25
}
coverEl.style.transform = `translateX(${offsetLeft}px)`
offsetLeft += fanCoverWidth
var coverOverlay = document.createElement('div')
coverOverlay.className = 'absolute top-0 left-0 w-full h-full hover:bg-black hover:bg-opacity-40 text-white text-opacity-0 hover:text-opacity-100 flex items-center justify-center cursor-pointer'
if (coverEl.dataset.volumeNumber) {
var pEl = document.createElement('p')
pEl.className = 'text-2xl'
pEl.textContent = `#${coverEl.dataset.volumeNumber}`
coverOverlay.appendChild(pEl)
}
if (coverEl.dataset.audiobookId) {
let audiobookId = coverEl.dataset.audiobookId
coverOverlay.addEventListener('click', (e) => {
this.$router.push(`/audiobook/${audiobookId}`)
e.stopPropagation()
e.preventDefault()
})
} else {
// Is Series
coverOverlay.addEventListener('click', (e) => {
this.$router.push(this.groupTo)
e.stopPropagation()
e.preventDefault()
})
}
coverEl.appendChild(coverOverlay)
}
},
reverseFan() {
if (this.coverImageEls.length < 2 || !this.isFannedOut) return
this.isFannedOut = false
for (let i = 0; i < this.coverImageEls.length; i++) {
var coverEl = this.coverImageEls[i]
coverEl.style.transform = 'translateX(0px)'
if (coverEl.lastChild) coverEl.lastChild.remove() // Remove cover overlay
}
},
getCoverUrl(book) {
return this.$store.getters['audiobooks/getBookCoverSrc'](book, '')
},
async buildCoverImg(coverData, bgCoverWidth, offsetLeft, zIndex, forceCoverBg = false) {
var src = coverData.coverUrl
var showCoverBg =
forceCoverBg ||
(await new Promise((resolve) => {
var image = new Image()
image.onload = () => {
var { naturalWidth, naturalHeight } = image
var aspectRatio = naturalHeight / naturalWidth
var arDiff = Math.abs(aspectRatio - 1.6)
// If image aspect ratio is <= 1.45 or >= 1.75 then use cover bg, otherwise stretch to fit
if (arDiff > 0.15) {
resolve(true)
} else {
resolve(false)
}
}
image.onerror = (err) => {
console.error(err)
resolve(false)
}
image.src = src
}))
var imgdiv = document.createElement('div')
imgdiv.style.height = this.height + 'px'
imgdiv.style.width = bgCoverWidth + 'px'
imgdiv.style.left = offsetLeft + 'px'
imgdiv.style.zIndex = zIndex
imgdiv.dataset.audiobookId = coverData.id
imgdiv.dataset.volumeNumber = coverData.volumeNumber || ''
imgdiv.className = 'absolute top-0 box-shadow-book transition-transform'
imgdiv.style.boxShadow = '4px 0px 4px #11111166'
// imgdiv.style.transform = 'skew(0deg, 15deg)'
if (showCoverBg) {
var coverbgwrapper = document.createElement('div')
coverbgwrapper.className = 'absolute top-0 left-0 w-full h-full bg-primary'
var coverbg = document.createElement('div')
coverbg.className = 'w-full h-full'
coverbg.style.backgroundImage = `url("${src}")`
coverbg.style.backgroundSize = 'cover'
coverbg.style.backgroundPosition = 'center'
coverbg.style.opacity = 0.25
coverbg.style.filter = 'blur(1px)'
coverbgwrapper.appendChild(coverbg)
imgdiv.appendChild(coverbgwrapper)
}
var img = document.createElement('img')
img.src = src
img.className = 'absolute top-0 left-0 w-full h-full'
img.style.objectFit = showCoverBg ? 'contain' : 'cover'
imgdiv.appendChild(img)
return imgdiv
},
createSeriesNameCover(offsetLeft) {
var imgdiv = document.createElement('div')
imgdiv.style.height = this.height + 'px'
imgdiv.style.width = this.height / 1.6 + 'px'
imgdiv.style.left = offsetLeft + 'px'
imgdiv.className = 'absolute top-0 box-shadow-book transition-transform flex items-center justify-center'
imgdiv.style.boxShadow = '4px 0px 4px #11111166'
imgdiv.style.backgroundColor = '#111'
var innerP = document.createElement('p')
innerP.textContent = this.name
innerP.className = 'text-sm font-book text-white'
imgdiv.appendChild(innerP)
return imgdiv
},
async init() {
if (this.coverDiv) {
this.coverDiv.remove()
this.coverDiv = null
}
var validCovers = this.bookItems
.map((bookItem) => {
return {
id: bookItem.id,
volumeNumber: bookItem.book ? bookItem.book.volumeNumber : null,
coverUrl: this.getCoverUrl(bookItem)
}
})
.filter((b) => b.coverUrl !== '')
if (!validCovers.length) {
this.noValidCovers = true
return
}
this.noValidCovers = false
var coverWidth = this.width
var widthPer = this.width
if (validCovers.length > 1) {
coverWidth = this.height / 1.6
widthPer = (this.width - coverWidth) / (validCovers.length - 1)
}
this.coverWidth = coverWidth
this.offsetIncrement = widthPer
var outerdiv = document.createElement('div')
this.coverWrapperEl = outerdiv
outerdiv.className = 'w-full h-full relative'
var coverImageEls = []
var offsetLeft = 0
for (let i = 0; i < validCovers.length; i++) {
offsetLeft = widthPer * i
var zIndex = validCovers.length - i
var img = await this.buildCoverImg(validCovers[i], coverWidth, offsetLeft, zIndex, validCovers.length === 1)
outerdiv.appendChild(img)
coverImageEls.push(img)
}
if (this.showCoverFan) {
var seriesNameCover = this.createSeriesNameCover(offsetLeft)
outerdiv.appendChild(seriesNameCover)
coverImageEls.push(seriesNameCover)
}
this.coverImageEls = coverImageEls
if (this.$refs.wrapper) {
this.coverDiv = outerdiv
this.$refs.wrapper.appendChild(outerdiv)
}
}
},
mounted() {
this.windowWidth = window.innerWidth
},
beforeDestroy() {
if (this.coverWrapperEl) this.coverWrapperEl.remove()
}
}
</script>

View file

@ -1,41 +0,0 @@
<template>
<div ref="container" @mouseover="mouseover" @mouseleave="mouseleave" class="relative">
<cards-book-cover :width="24" :audiobook="audiobook" />
</div>
</template>
<script>
export default {
props: {
audiobook: {
type: Object,
default: () => {}
}
},
data() {
return {
isHovering: false
}
},
computed: {
placeholderUrl() {
return '/book_placeholder.jpg'
},
fullCoverUrl() {
return this.$store.getters['audiobooks/getBookCoverSrc'](this.audiobook, this.placeholderUrl)
},
hasCover() {
return !!this.audiobook.book.cover
}
},
methods: {
mouseover() {
this.isHovering = true
},
mouseleave() {
this.isHovering = false
}
},
mounted() {}
}
</script>

View file

@ -1,93 +0,0 @@
<template>
<div class="relative rounded-sm overflow-hidden" :style="{ height: width * 1.6 + 'px', width: width + 'px', maxWidth: width + 'px', minWidth: width + 'px' }" @mouseover="isHovering = true" @mouseleave="isHovering = false">
<div class="w-full h-full relative">
<div v-if="showCoverBg" class="bg-primary absolute top-0 left-0 w-full h-full">
<div class="w-full h-full z-0" ref="coverBg" />
</div>
<img ref="cover" :src="cover" @error="imageError" @load="imageLoaded" class="w-full h-full absolute top-0 left-0" :class="showCoverBg ? 'object-contain' : 'object-cover'" />
<a v-if="!imageFailed && showOpenNewTab && isHovering" :href="cover" @click.stop target="_blank" class="absolute bg-primary flex items-center justify-center shadow-sm rounded-full hover:scale-110 transform duration-100" :style="{ top: sizeMultiplier * 0.5 + 'rem', right: sizeMultiplier * 0.5 + 'rem', width: 2.5 * sizeMultiplier + 'rem', height: 2.5 * sizeMultiplier + 'rem' }">
<span class="material-icons" :style="{ fontSize: sizeMultiplier * 1.75 + 'rem' }">open_in_new</span>
</a>
</div>
<div v-if="imageFailed" class="absolute top-0 left-0 right-0 bottom-0 w-full h-full bg-red-100" :style="{ padding: placeholderCoverPadding + 'rem' }">
<div class="w-full h-full border-2 border-error flex flex-col items-center justify-center">
<img src="/Logo.png" class="mb-2" :style="{ height: 64 * sizeMultiplier + 'px' }" />
<p class="text-center font-book text-error" :style="{ fontSize: sizeMultiplier + 'rem' }">Invalid Cover</p>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
src: String,
width: {
type: Number,
default: 120
},
showOpenNewTab: Boolean
},
data() {
return {
imageFailed: false,
showCoverBg: false,
isHovering: false,
naturalHeight: 0,
naturalWidth: 0
}
},
watch: {
cover() {
this.imageFailed = false
}
},
computed: {
cover() {
return this.src
},
sizeMultiplier() {
return this.width / 120
},
placeholderCoverPadding() {
return 0.8 * this.sizeMultiplier
}
},
methods: {
setCoverBg() {
if (this.$refs.coverBg) {
this.$refs.coverBg.style.backgroundImage = `url("${this.src}")`
this.$refs.coverBg.style.backgroundSize = 'cover'
this.$refs.coverBg.style.backgroundPosition = 'center'
this.$refs.coverBg.style.opacity = 0.25
this.$refs.coverBg.style.filter = 'blur(1px)'
}
},
imageLoaded() {
if (this.$refs.cover) {
var { naturalWidth, naturalHeight } = this.$refs.cover
this.naturalHeight = naturalHeight
this.naturalWidth = naturalWidth
var aspectRatio = naturalHeight / naturalWidth
var arDiff = Math.abs(aspectRatio - 1.6)
// If image aspect ratio is <= 1.45 or >= 1.75 then use cover bg, otherwise stretch to fit
if (arDiff > 0.15) {
this.showCoverBg = true
this.$nextTick(this.setCoverBg)
} else {
this.showCoverBg = false
}
}
},
imageError(err) {
console.error('ImgError', err)
this.imageFailed = true
}
},
mounted() {}
}
</script>

View file

@ -1,6 +1,6 @@
<template>
<div class="flex h-full px-1 overflow-hidden">
<cards-group-cover :name="series" :book-items="bookItems" :width="60" :height="60" />
<covers-group-cover :name="series" :book-items="bookItems" :width="60" :height="60" />
<div class="flex-grow px-2 seriesSearchCardContent h-full">
<p class="truncate text-sm">{{ series }}</p>
</div>