mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2024-11-07 08:34:10 +01:00
Add:Server option to use square cover images #210
This commit is contained in:
parent
0d6f83e9d9
commit
9a6ba3d0de
@ -16,7 +16,7 @@
|
||||
</div>
|
||||
<div v-else class="w-full flex flex-col items-center">
|
||||
<template v-for="(shelf, index) in shelves">
|
||||
<app-book-shelf-row :key="index" :index="index" :shelf="shelf" :size-multiplier="sizeMultiplier" :book-cover-width="bookCoverWidth" />
|
||||
<app-book-shelf-row :key="index" :index="index" :shelf="shelf" :size-multiplier="sizeMultiplier" :book-cover-width="bookCoverWidth" :book-cover-aspect-ratio="bookCoverAspectRatio" />
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
@ -51,7 +51,18 @@ export default {
|
||||
return this.$store.state.libraries.currentLibraryId
|
||||
},
|
||||
bookCoverWidth() {
|
||||
return this.$store.getters['user/getUserSetting']('bookshelfCoverSize')
|
||||
var coverSize = this.$store.getters['user/getUserSetting']('bookshelfCoverSize')
|
||||
if (this.isCoverSquareAspectRatio) return coverSize * 1.6
|
||||
return coverSize
|
||||
},
|
||||
coverAspectRatio() {
|
||||
return this.$store.getters['getServerSetting']('coverAspectRatio')
|
||||
},
|
||||
isCoverSquareAspectRatio() {
|
||||
return this.coverAspectRatio === this.$constants.BookCoverAspectRatio.SQUARE
|
||||
},
|
||||
bookCoverAspectRatio() {
|
||||
return this.isCoverSquareAspectRatio ? 1 : 1.6
|
||||
},
|
||||
sizeMultiplier() {
|
||||
return this.bookCoverWidth / 120
|
||||
|
@ -4,30 +4,30 @@
|
||||
<div class="w-full h-full" :style="{ marginTop: sizeMultiplier + 'rem' }">
|
||||
<div v-if="shelf.type === 'books'" class="flex items-center -mb-2">
|
||||
<template v-for="entity in shelf.entities">
|
||||
<cards-book-card :key="entity.id" :width="bookCoverWidth" :user-progress="userAudiobooks[entity.id]" :audiobook="entity" @hook:updated="updatedBookCard" :padding-y="24" @edit="editBook" />
|
||||
<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" />
|
||||
</template>
|
||||
</div>
|
||||
<div v-if="shelf.type === 'series'" class="flex items-center -mb-2">
|
||||
<template v-for="entity in shelf.entities">
|
||||
<cards-group-card :key="entity.name" is-categorized :width="bookCoverWidth" :group="entity" @click="$emit('clickSeries', entity)" />
|
||||
<cards-group-card :key="entity.name" is-categorized :width="bookCoverWidth" :group="entity" :book-cover-aspect-ratio="bookCoverAspectRatio" @hook:updated="updatedBookCard" @click="$emit('clickSeries', entity)" />
|
||||
</template>
|
||||
</div>
|
||||
<div v-if="shelf.type === 'tags'" class="flex items-center -mb-2">
|
||||
<template v-for="entity in shelf.entities">
|
||||
<nuxt-link :key="entity.name" :to="`/library/${currentLibraryId}/bookshelf?filter=tags.${$encode(entity.name)}`">
|
||||
<cards-group-card is-categorized :width="bookCoverWidth" :group="entity" />
|
||||
<cards-group-card is-categorized :width="bookCoverWidth" :group="entity" :book-cover-aspect-ratio="bookCoverAspectRatio" @hook:updated="updatedBookCard" />
|
||||
</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" @click="$emit('clickSeries', entity)" />
|
||||
<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" />
|
||||
<cards-group-card is-categorized :width="bookCoverWidth" :group="entity" :book-cover-aspect-ratio="bookCoverAspectRatio" @hook:updated="updatedBookCard" />
|
||||
</nuxt-link>
|
||||
</template>
|
||||
</div>
|
||||
@ -60,7 +60,8 @@ export default {
|
||||
default: () => {}
|
||||
},
|
||||
sizeMultiplier: Number,
|
||||
bookCoverWidth: Number
|
||||
bookCoverWidth: Number,
|
||||
bookCoverAspectRatio: Number
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -99,28 +99,48 @@ export default {
|
||||
filterBy() {
|
||||
return this.$store.getters['user/getUserSetting']('filterBy')
|
||||
},
|
||||
coverAspectRatio() {
|
||||
return this.$store.getters['getServerSetting']('coverAspectRatio')
|
||||
},
|
||||
isCoverSquareAspectRatio() {
|
||||
return this.coverAspectRatio === this.$constants.BookCoverAspectRatio.SQUARE
|
||||
},
|
||||
bookCoverAspectRatio() {
|
||||
return this.isCoverSquareAspectRatio ? 1 : 1.6
|
||||
},
|
||||
hasFilter() {
|
||||
return this.filterBy && this.filterBy !== 'all'
|
||||
},
|
||||
currentLibraryId() {
|
||||
return this.$store.state.libraries.currentLibraryId
|
||||
},
|
||||
isEntityBook() {
|
||||
return this.entityName === 'series-books' || this.entityName === 'books'
|
||||
},
|
||||
bookWidth() {
|
||||
return this.$store.getters['user/getUserSetting']('bookshelfCoverSize')
|
||||
var coverSize = this.$store.getters['user/getUserSetting']('bookshelfCoverSize')
|
||||
if (this.isCoverSquareAspectRatio) return coverSize * 1.6
|
||||
return coverSize
|
||||
},
|
||||
bookHeight() {
|
||||
if (this.isCoverSquareAspectRatio) return this.bookWidth
|
||||
return this.bookWidth * 1.6
|
||||
},
|
||||
entityWidth() {
|
||||
if (this.entityName === 'series') return this.bookWidth * 1.6
|
||||
if (this.entityName === 'series') return this.bookWidth * 2
|
||||
if (this.entityName === 'collections') return this.bookWidth * 2
|
||||
return this.bookWidth
|
||||
},
|
||||
bookHeight() {
|
||||
return this.bookWidth * 1.6
|
||||
entityHeight() {
|
||||
if (this.entityName === 'series') return this.bookHeight
|
||||
if (this.entityName === 'collections') return this.bookHeight
|
||||
return this.bookHeight
|
||||
},
|
||||
shelfDividerHeightIndex() {
|
||||
return 6
|
||||
},
|
||||
shelfHeight() {
|
||||
return this.bookHeight + 40
|
||||
return this.entityHeight + 40
|
||||
},
|
||||
totalEntityCardWidth() {
|
||||
// Includes margin
|
||||
|
@ -104,7 +104,7 @@ export default {
|
||||
return this.$route.name === 'library-library'
|
||||
},
|
||||
isSeriesPage() {
|
||||
return this.$route.name === 'library-library-series-id'
|
||||
return this.$route.name === 'library-library-series-id' || this.paramId === 'series'
|
||||
},
|
||||
libraryBookshelfPage() {
|
||||
return this.$route.name === 'library-library-bookshelf-id'
|
||||
|
@ -19,7 +19,7 @@
|
||||
<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" />
|
||||
<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">
|
||||
@ -48,14 +48,14 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="volumeNumber && showVolumeNumber && !isHovering && !isSelectionMode" class="absolute rounded-lg bg-black bg-opacity-90 box-shadow-md" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', padding: `${0.1 * sizeMultiplier}rem ${0.25 * sizeMultiplier}rem` }">
|
||||
<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"
|
||||
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"
|
||||
>
|
||||
@ -65,7 +65,7 @@
|
||||
|
||||
<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">
|
||||
<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>
|
||||
@ -100,7 +100,8 @@ export default {
|
||||
default: 16
|
||||
},
|
||||
isBookshelfBook: Boolean,
|
||||
showVolumeNumber: Boolean
|
||||
showVolumeNumber: Boolean,
|
||||
bookCoverAspectRatio: Number
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@ -146,11 +147,15 @@ export default {
|
||||
book() {
|
||||
return this.audiobook.book || {}
|
||||
},
|
||||
squareAspectRatio() {
|
||||
return this.bookCoverAspectRatio === 1
|
||||
},
|
||||
height() {
|
||||
return this.width * 1.6
|
||||
return this.width * this.bookCoverAspectRatio
|
||||
},
|
||||
sizeMultiplier() {
|
||||
return this.width / 120
|
||||
var baseSize = this.squareAspectRatio ? 192 : 120
|
||||
return this.width / baseSize
|
||||
},
|
||||
paddingX() {
|
||||
return 16 * this.sizeMultiplier
|
||||
|
@ -3,9 +3,9 @@
|
||||
<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 h-full relative" :class="isHovering ? 'bg-black-400' : 'bg-primary'" :style="{ height: coverHeight + 'px', width: coverWidth + 'px' }">
|
||||
<covers-group-cover ref="groupcover" :name="groupName" :is-categorized="isCategorized" :group-to="groupTo" :type="groupType" :book-items="bookItems" :width="coverWidth" :height="coverHeight" />
|
||||
<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" />
|
||||
|
||||
<div v-if="hasValidCovers && (!showExperimentalFeatures || isCategorized)" 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" 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>
|
||||
|
||||
@ -42,7 +42,8 @@ export default {
|
||||
type: Number,
|
||||
default: 24
|
||||
},
|
||||
isCategorized: Boolean
|
||||
isCategorized: Boolean,
|
||||
bookCoverAspectRatio: Number
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@ -59,6 +60,9 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
seriesId() {
|
||||
return this.groupEncode
|
||||
},
|
||||
labelFontSize() {
|
||||
if (this.coverWidth < 160) return 0.75
|
||||
return 0.875
|
||||
@ -81,14 +85,18 @@ export default {
|
||||
return `/library/${this.currentLibraryId}/bookshelf?filter=tags.${this.groupEncode}`
|
||||
}
|
||||
},
|
||||
squareAspectRatio() {
|
||||
return this.bookCoverAspectRatio === 1
|
||||
},
|
||||
coverWidth() {
|
||||
return this.coverHeight
|
||||
return this.width * 2
|
||||
},
|
||||
coverHeight() {
|
||||
return this.width * 1.6
|
||||
return this.width * this.bookCoverAspectRatio
|
||||
},
|
||||
sizeMultiplier() {
|
||||
return this.width / 120
|
||||
var baseSize = this.squareAspectRatio ? 192 : 120
|
||||
return this.width / baseSize
|
||||
},
|
||||
paddingX() {
|
||||
return 16 * this.sizeMultiplier
|
||||
|
@ -1,6 +1,8 @@
|
||||
<template>
|
||||
<div ref="card" :id="`book-card-${index}`" :style="{ width: width + 'px', height: bookHeight + '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="{ 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">
|
||||
<!-- When cover image does not fill -->
|
||||
<div v-show="showCoverBg" class="w-full h-full absolute top-0 left-0 z-0" ref="coverBg" />
|
||||
|
||||
<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' }">
|
||||
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }" class="font-book text-gray-300 text-center">{{ title }}</p>
|
||||
@ -55,50 +57,6 @@
|
||||
<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>
|
||||
<!-- <div ref="overlay-wrapper" class="w-full h-full relative box-shadow-book cursor-pointer" @click="clickCard" @mouseover="mouseover" @mouseleave="isHovering = false">
|
||||
<covers-book-cover :audiobook="audiobook" :width="width" />
|
||||
<div v-if="false" ref="overlay">
|
||||
<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>
|
||||
<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" :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>
|
||||
<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"
|
||||
: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"
|
||||
>
|
||||
<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" :class="userIsRead ? 'bg-success' : 'bg-yellow-400'" :style="{ width: width * userProgressPercent + 'px' }"></div>
|
||||
|
||||
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -113,6 +71,11 @@ export default {
|
||||
type: Number,
|
||||
default: 120
|
||||
},
|
||||
height: {
|
||||
type: Number,
|
||||
default: 192
|
||||
},
|
||||
bookCoverAspectRatio: Number,
|
||||
showVolumeNumber: Boolean
|
||||
},
|
||||
data() {
|
||||
@ -159,11 +122,12 @@ export default {
|
||||
hasCover() {
|
||||
return !!this.book.cover
|
||||
},
|
||||
bookHeight() {
|
||||
return this.width * 1.6
|
||||
squareAspectRatio() {
|
||||
return this.bookCoverAspectRatio === 1
|
||||
},
|
||||
sizeMultiplier() {
|
||||
return this.width / 120
|
||||
var baseSize = this.squareAspectRatio ? 192 : 120
|
||||
return this.width / baseSize
|
||||
},
|
||||
title() {
|
||||
return this.book.title || ''
|
||||
@ -492,7 +456,7 @@ export default {
|
||||
if (this.$refs.cover && this.bookCoverSrc !== this.placeholderUrl) {
|
||||
var { naturalWidth, naturalHeight } = this.$refs.cover
|
||||
var aspectRatio = naturalHeight / naturalWidth
|
||||
var arDiff = Math.abs(aspectRatio - 1.6)
|
||||
var arDiff = Math.abs(aspectRatio - this.bookCoverAspectRatio)
|
||||
|
||||
// If image aspect ratio is <= 1.45 or >= 1.75 then use cover bg, otherwise stretch to fit
|
||||
if (arDiff > 0.15) {
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div ref="card" :id="`collection-card-${index}`" :style="{ width: width + 'px', height: cardHeight + 'px' }" class="absolute top-0 left-0 rounded-sm z-10 cursor-pointer box-shadow-book" @mousedown.prevent @mouseup.prevent @mousemove.prevent @mouseover="mouseover" @mouseleave="mouseleave" @click="clickCard">
|
||||
<div ref="card" :id="`collection-card-${index}`" :style="{ width: width + 'px', height: height + 'px' }" class="absolute top-0 left-0 rounded-sm z-10 cursor-pointer box-shadow-book" @mousedown.prevent @mouseup.prevent @mousemove.prevent @mouseover="mouseover" @mouseleave="mouseleave" @click="clickCard">
|
||||
<div class="w-full h-full bg-primary relative rounded overflow-hidden">
|
||||
<covers-collection-cover ref="cover" :book-items="books" :width="width" :height="width" />
|
||||
<covers-collection-cover ref="cover" :book-items="books" :width="width" :height="height" :book-cover-aspect-ratio="bookCoverAspectRatio" />
|
||||
</div>
|
||||
<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">
|
||||
@ -14,7 +14,7 @@
|
||||
<!-- <div v-if="isHovering || isSelectionMode" class="absolute top-0 left-0 w-full h-full bg-black bg-opacity-40">
|
||||
</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 class="w-full h-full shinyBlack flex items-center justify-center rounded-sm border" :style="{ padding: `0rem ${1 * 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>
|
||||
</div>
|
||||
</div>
|
||||
@ -25,7 +25,9 @@
|
||||
export default {
|
||||
props: {
|
||||
index: Number,
|
||||
width: Number
|
||||
width: Number,
|
||||
height: Number,
|
||||
bookCoverAspectRatio: Number
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@ -40,11 +42,9 @@ export default {
|
||||
if (this.width < 160) return 0.75
|
||||
return 0.875
|
||||
},
|
||||
cardHeight() {
|
||||
return (this.width / 2) * 1.6
|
||||
},
|
||||
sizeMultiplier() {
|
||||
return this.width / 120
|
||||
if (this.bookCoverAspectRatio === 1) return this.width / (120 * 1.6 * 2)
|
||||
return this.width / 240
|
||||
},
|
||||
title() {
|
||||
return this.collection ? this.collection.name : ''
|
||||
|
@ -1,13 +1,16 @@
|
||||
<template>
|
||||
<div ref="card" :id="`series-card-${index}`" :style="{ width: width + 'px', height: cardHeight + 'px' }" class="absolute top-0 left-0 rounded-sm z-10 cursor-pointer box-shadow-book" @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="absolute top-0 left-0 rounded-sm z-10 cursor-pointer box-shadow-book" @mousedown.prevent @mouseup.prevent @mousemove.prevent @mouseover="mouseover" @mouseleave="mouseleave" @click="clickCard">
|
||||
<div class="w-full h-full bg-primary relative rounded overflow-hidden">
|
||||
<covers-group-cover ref="cover" :name="title" :book-items="books" :width="width" :height="width" :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" />
|
||||
</div>
|
||||
|
||||
<div v-if="hasValidCovers" 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: 1.2 * sizeMultiplier + 'rem' }">{{ title }}</p>
|
||||
</div>
|
||||
<!-- <div v-if="isHovering || isSelectionMode" class="absolute top-0 left-0 w-full h-full bg-black bg-opacity-40">
|
||||
</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 class="w-full h-full shinyBlack flex items-center justify-center rounded-sm border" :style="{ padding: `0rem ${1 * 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>
|
||||
</div>
|
||||
</div>
|
||||
@ -18,7 +21,9 @@
|
||||
export default {
|
||||
props: {
|
||||
index: Number,
|
||||
width: Number
|
||||
width: Number,
|
||||
height: Number,
|
||||
bookCoverAspectRatio: Number
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@ -34,11 +39,9 @@ export default {
|
||||
if (this.width < 160) return 0.75
|
||||
return 0.875
|
||||
},
|
||||
cardHeight() {
|
||||
return this.width
|
||||
},
|
||||
sizeMultiplier() {
|
||||
return this.width / 120
|
||||
if (this.bookCoverAspectRatio === 1) return this.width / (120 * 1.6 * 2)
|
||||
return this.width / 240
|
||||
},
|
||||
title() {
|
||||
return this.series ? this.series.name : ''
|
||||
@ -54,6 +57,13 @@ export default {
|
||||
},
|
||||
seriesBooksRoute() {
|
||||
return `/library/${this.currentLibraryId}/series/${this.$encode(this.title)}`
|
||||
},
|
||||
seriesId() {
|
||||
return this.series ? this.$encode(this.series.id) : null
|
||||
},
|
||||
hasValidCovers() {
|
||||
var validCovers = this.books.map((bookItem) => bookItem.book.cover)
|
||||
return !!validCovers.length
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@ -72,7 +82,7 @@ export default {
|
||||
clickCard() {
|
||||
if (!this.series) return
|
||||
var router = this.$router || this.$nuxt.$router
|
||||
router.push(`/library/${this.currentLibraryId}/bookshelf/series?series=${this.$encode(this.series.id)}`)
|
||||
router.push(`/library/${this.currentLibraryId}/bookshelf/series?series=${this.seriesId}`)
|
||||
},
|
||||
imageLoaded() {
|
||||
this.imageReady = true
|
||||
@ -89,6 +99,7 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {}
|
||||
mounted() {},
|
||||
beforeDestroy() {}
|
||||
}
|
||||
</script>
|
@ -1,5 +1,5 @@
|
||||
<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="relative rounded-sm overflow-hidden" :style="{ height: height + 'px', width: width + 'px', maxWidth: width + 'px', minWidth: width + 'px' }">
|
||||
<div class="w-full h-full relative bg-bg">
|
||||
<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" />
|
||||
@ -51,7 +51,8 @@ export default {
|
||||
width: {
|
||||
type: Number,
|
||||
default: 120
|
||||
}
|
||||
},
|
||||
bookCoverAspectRatio: Number
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@ -67,6 +68,12 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
squareAspectRatio() {
|
||||
return this.bookCoverAspectRatio === 1
|
||||
},
|
||||
height() {
|
||||
return this.width * this.bookCoverAspectRatio
|
||||
},
|
||||
book() {
|
||||
if (!this.audiobook) return {}
|
||||
return this.audiobook.book || {}
|
||||
@ -105,7 +112,8 @@ export default {
|
||||
return !!this.book.cover
|
||||
},
|
||||
sizeMultiplier() {
|
||||
return this.width / 120
|
||||
var baseSize = this.squareAspectRatio ? 192 : 120
|
||||
return this.width / baseSize
|
||||
},
|
||||
titleFontSize() {
|
||||
return 0.75 * this.sizeMultiplier
|
||||
@ -142,7 +150,7 @@ export default {
|
||||
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)
|
||||
var arDiff = Math.abs(aspectRatio - this.bookCoverAspectRatio)
|
||||
|
||||
// If image aspect ratio is <= 1.45 or >= 1.75 then use cover bg, otherwise stretch to fit
|
||||
if (arDiff > 0.15) {
|
||||
|
@ -13,8 +13,8 @@
|
||||
<div v-else-if="books.length" class="flex justify-center h-full relative bg-primary bg-opacity-95 rounded-sm">
|
||||
<div class="absolute top-0 left-0 w-full h-full bg-gray-400 bg-opacity-5" />
|
||||
|
||||
<covers-book-cover :audiobook="books[0]" :width="width / 2" />
|
||||
<covers-book-cover v-if="books.length > 1" :audiobook="books[1]" :width="width / 2" />
|
||||
<covers-book-cover :audiobook="books[0]" :width="width / 2" :book-cover-aspect-ratio="bookCoverAspectRatio" />
|
||||
<covers-book-cover v-if="books.length > 1" :audiobook="books[1]" :width="width / 2" :book-cover-aspect-ratio="bookCoverAspectRatio" />
|
||||
</div>
|
||||
<div v-else class="relative w-full h-full flex items-center justify-center p-2 bg-primary rounded-sm">
|
||||
<div class="absolute top-0 left-0 w-full h-full bg-gray-400 bg-opacity-5" />
|
||||
@ -32,7 +32,8 @@ export default {
|
||||
default: () => []
|
||||
},
|
||||
width: Number,
|
||||
height: Number
|
||||
height: Number,
|
||||
bookCoverAspectRatio: Number
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@ -42,7 +43,8 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
sizeMultiplier() {
|
||||
return this.width / 120
|
||||
if (this.bookCoverAspectRatio === 1) return this.width / (120 * 1.6 * 2)
|
||||
return this.width / 240
|
||||
},
|
||||
hasOwnCover() {
|
||||
return false
|
||||
|
@ -9,6 +9,7 @@
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
id: String,
|
||||
name: String,
|
||||
bookItems: {
|
||||
type: Array,
|
||||
@ -17,7 +18,8 @@ export default {
|
||||
width: Number,
|
||||
height: Number,
|
||||
groupTo: String,
|
||||
isCategorized: Boolean
|
||||
isCategorized: Boolean,
|
||||
bookCoverAspectRatio: Number
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@ -31,7 +33,8 @@ export default {
|
||||
isFannedOut: false,
|
||||
isDetached: false,
|
||||
isAttaching: false,
|
||||
windowWidth: 0
|
||||
windowWidth: 0,
|
||||
isInit: false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@ -47,13 +50,15 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
sizeMultiplier() {
|
||||
return this.width / 192
|
||||
if (this.bookCoverAspectRatio === 1) return this.width / (120 * 1.6 * 2)
|
||||
return this.width / 240
|
||||
},
|
||||
showExperimentalFeatures() {
|
||||
return this.store.state.showExperimentalFeatures
|
||||
},
|
||||
showCoverFan() {
|
||||
return this.showExperimentalFeatures && this.windowWidth > 1024 && !this.isCategorized
|
||||
// return this.showExperimentalFeatures && this.windowWidth > 1024 && !this.isCategorized
|
||||
return false
|
||||
},
|
||||
store() {
|
||||
return this.$store || this.$nuxt.$store
|
||||
@ -112,11 +117,11 @@ export default {
|
||||
this.fanOutCovers()
|
||||
} else if (!val && this.isHovering) {
|
||||
this.isAttaching = true
|
||||
this.reverseFan()
|
||||
setTimeout(() => {
|
||||
this.attachCoverWrapper()
|
||||
this.isAttaching = false
|
||||
}, 100)
|
||||
// this.reverseFan()
|
||||
// setTimeout(() => {
|
||||
// this.attachCoverWrapper()
|
||||
// this.isAttaching = false
|
||||
// }, 100)
|
||||
}
|
||||
this.isHovering = val
|
||||
},
|
||||
@ -211,7 +216,7 @@ export default {
|
||||
image.onload = () => {
|
||||
var { naturalWidth, naturalHeight } = image
|
||||
var aspectRatio = naturalHeight / naturalWidth
|
||||
var arDiff = Math.abs(aspectRatio - 1.6)
|
||||
var arDiff = Math.abs(aspectRatio - this.bookCoverAspectRatio)
|
||||
|
||||
// If image aspect ratio is <= 1.45 or >= 1.75 then use cover bg, otherwise stretch to fit
|
||||
if (arDiff > 0.15) {
|
||||
@ -265,7 +270,7 @@ export default {
|
||||
createSeriesNameCover(offsetLeft) {
|
||||
var imgdiv = document.createElement('div')
|
||||
imgdiv.style.height = this.height + 'px'
|
||||
imgdiv.style.width = this.height / 1.6 + 'px'
|
||||
imgdiv.style.width = this.height / this.bookCoverAspectRatio + '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'
|
||||
@ -279,6 +284,9 @@ export default {
|
||||
return imgdiv
|
||||
},
|
||||
async init() {
|
||||
if (this.isInit) return
|
||||
this.isInit = true
|
||||
|
||||
if (this.coverDiv) {
|
||||
this.coverDiv.remove()
|
||||
this.coverDiv = null
|
||||
@ -301,15 +309,16 @@ export default {
|
||||
var coverWidth = this.width
|
||||
var widthPer = this.width
|
||||
if (validCovers.length > 1) {
|
||||
coverWidth = this.height / 1.6
|
||||
coverWidth = this.height / this.bookCoverAspectRatio
|
||||
widthPer = (this.width - coverWidth) / (validCovers.length - 1)
|
||||
}
|
||||
this.coverWidth = coverWidth
|
||||
this.offsetIncrement = widthPer
|
||||
|
||||
var outerdiv = document.createElement('div')
|
||||
outerdiv.id = `group-cover-${this.id}`
|
||||
this.coverWrapperEl = outerdiv
|
||||
outerdiv.className = 'w-full h-full relative'
|
||||
outerdiv.className = 'w-full h-full relative box-shadow-book'
|
||||
|
||||
var coverImageEls = []
|
||||
var offsetLeft = 0
|
||||
@ -340,6 +349,10 @@ export default {
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.coverWrapperEl) this.coverWrapperEl.remove()
|
||||
if (this.coverImageEls && this.coverImageEls.length) {
|
||||
this.coverImageEls.forEach((el) => el.remove())
|
||||
}
|
||||
if (this.coverDiv) this.coverDiv.remove()
|
||||
}
|
||||
}
|
||||
</script>
|
@ -47,13 +47,17 @@ export default {
|
||||
|
||||
var ComponentClass = this.getComponentClass()
|
||||
|
||||
var props = {
|
||||
index,
|
||||
width: this.entityWidth,
|
||||
height: this.entityHeight,
|
||||
bookCoverAspectRatio: this.bookCoverAspectRatio
|
||||
}
|
||||
if (this.entityName === 'series-books') props.showVolumeNumber = true
|
||||
|
||||
var _this = this
|
||||
var instance = new ComponentClass({
|
||||
propsData: {
|
||||
index: index,
|
||||
width: this.entityWidth,
|
||||
showVolumeNumber: this.entityName === 'series-books'
|
||||
},
|
||||
propsData: props,
|
||||
created() {
|
||||
this.$on('edit', (entity) => {
|
||||
if (_this.editEntity) _this.editEntity(entity)
|
||||
|
@ -41,6 +41,13 @@
|
||||
<p class="pl-4 text-lg">Store covers with audiobook <span class="material-icons icon-text">info_outlined</span></p>
|
||||
</ui-tooltip>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center py-2">
|
||||
<ui-toggle-switch v-model="useSquareBookCovers" :disabled="updatingServerSettings" @input="updateBookCoverAspectRatio" />
|
||||
<ui-tooltip :text="coverAspectRatioTooltip">
|
||||
<p class="pl-4 text-lg">Use square book covers <span class="material-icons icon-text">info_outlined</span></p>
|
||||
</ui-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="h-0.5 bg-primary bg-opacity-30 w-full" />
|
||||
@ -92,6 +99,7 @@ export default {
|
||||
isResettingAudiobooks: false,
|
||||
storeCoversInAudiobookDir: false,
|
||||
updatingServerSettings: false,
|
||||
useSquareBookCovers: false,
|
||||
newServerSettings: {}
|
||||
}
|
||||
},
|
||||
@ -128,6 +136,9 @@ export default {
|
||||
scannerFindCoversTooltip() {
|
||||
return 'If your audiobook does not have an embedded cover or a cover image inside the folder, the scanner will attempt to find a cover.<br>Note: This will extend scan time'
|
||||
},
|
||||
coverAspectRatioTooltip() {
|
||||
return 'Prefer to use square covers over standard 1.6:1 book covers'
|
||||
},
|
||||
showExperimentalFeatures: {
|
||||
get() {
|
||||
return this.$store.state.showExperimentalFeatures
|
||||
@ -164,6 +175,11 @@ export default {
|
||||
scannerPreferOpfMetadata: !!val
|
||||
})
|
||||
},
|
||||
updateBookCoverAspectRatio(val) {
|
||||
this.updateServerSettings({
|
||||
coverAspectRatio: val ? this.$constants.BookCoverAspectRatio.SQUARE : this.$constants.BookCoverAspectRatio.STANDARD
|
||||
})
|
||||
},
|
||||
updateServerSettings(payload) {
|
||||
this.updatingServerSettings = true
|
||||
this.$store
|
||||
@ -181,6 +197,8 @@ export default {
|
||||
this.newServerSettings = this.serverSettings ? { ...this.serverSettings } : {}
|
||||
|
||||
this.storeCoversInAudiobookDir = this.newServerSettings.coverDestination === this.$constants.CoverDestination.AUDIOBOOK
|
||||
|
||||
this.useSquareBookCovers = this.newServerSettings.coverAspectRatio === this.$constants.BookCoverAspectRatio.SQUARE
|
||||
},
|
||||
resetAudiobooks() {
|
||||
if (confirm('WARNING! This action will remove all audiobooks from the database including any updates or matches you have made. This does not do anything to your actual files. Shall we continue?')) {
|
||||
|
@ -10,9 +10,15 @@ const CoverDestination = {
|
||||
AUDIOBOOK: 1
|
||||
}
|
||||
|
||||
const BookCoverAspectRatio = {
|
||||
STANDARD: 0,
|
||||
SQUARE: 1
|
||||
}
|
||||
|
||||
const Constants = {
|
||||
DownloadStatus,
|
||||
CoverDestination
|
||||
CoverDestination,
|
||||
BookCoverAspectRatio
|
||||
}
|
||||
|
||||
const KeyNames = {
|
||||
|
@ -28,6 +28,10 @@ export const getters = {
|
||||
getIsAudiobookSelected: state => audiobookId => {
|
||||
return !!state.selectedAudiobooks.includes(audiobookId)
|
||||
},
|
||||
getServerSetting: state => key => {
|
||||
if (!state.serverSettings) return null
|
||||
return state.serverSettings[key]
|
||||
},
|
||||
getNumAudiobooksSelected: state => state.selectedAudiobooks.length,
|
||||
getAudiobookIdStreaming: state => {
|
||||
return state.streamAudiobook ? state.streamAudiobook.id : null
|
||||
|
@ -1,4 +1,4 @@
|
||||
const { CoverDestination } = require('../utils/constants')
|
||||
const { CoverDestination, BookCoverAspectRatio } = require('../utils/constants')
|
||||
const Logger = require('../Logger')
|
||||
|
||||
class ServerSettings {
|
||||
@ -33,6 +33,9 @@ class ServerSettings {
|
||||
this.loggerDailyLogsToKeep = 7
|
||||
this.loggerScannerLogsToKeep = 2
|
||||
|
||||
// Cover
|
||||
this.coverAspectRatio = BookCoverAspectRatio.STANDARD
|
||||
|
||||
this.logLevel = Logger.logLevel
|
||||
this.version = null
|
||||
|
||||
@ -61,6 +64,8 @@ class ServerSettings {
|
||||
this.loggerDailyLogsToKeep = settings.loggerDailyLogsToKeep || 7
|
||||
this.loggerScannerLogsToKeep = settings.loggerScannerLogsToKeep || 2
|
||||
|
||||
this.coverAspectRatio = settings.coverAspectRatio || BookCoverAspectRatio.STANDARD
|
||||
|
||||
this.logLevel = settings.logLevel || Logger.logLevel
|
||||
this.version = settings.version || null
|
||||
|
||||
@ -87,6 +92,7 @@ class ServerSettings {
|
||||
backupMetadataCovers: this.backupMetadataCovers,
|
||||
loggerDailyLogsToKeep: this.loggerDailyLogsToKeep,
|
||||
loggerScannerLogsToKeep: this.loggerScannerLogsToKeep,
|
||||
coverAspectRatio: this.coverAspectRatio,
|
||||
logLevel: this.logLevel,
|
||||
version: this.version
|
||||
}
|
||||
|
@ -11,6 +11,11 @@ module.exports.CoverDestination = {
|
||||
AUDIOBOOK: 1
|
||||
}
|
||||
|
||||
module.exports.BookCoverAspectRatio = {
|
||||
STANDARD: 0, // 1.6:1
|
||||
SQUARE: 1
|
||||
}
|
||||
|
||||
module.exports.LogLevel = {
|
||||
TRACE: 0,
|
||||
DEBUG: 1,
|
||||
|
Loading…
Reference in New Issue
Block a user