audiobookshelf/client/components/covers/SortedCovers.vue

97 lines
3.6 KiB
Vue

// This component sorts and displays book covers based on their aspect ratio.
// It separates covers into primary (preferred aspect ratio) and secondary (opposite aspect ratio) sections.
// Covers are displayed with options to select, view resolution, and open in a new tab.
<template>
<div class="flex flex-col items-center sm:max-h-80 sm:overflow-y-scroll max-w-full">
<!-- Primary Covers Section (based on preferred aspect ratio) -->
<div v-if="primaryCovers.length" class="flex items-center flex-wrap justify-center">
<template v-for="cover in primaryCovers">
<div :key="cover.url" class="m-0.5 mb-5 border-2 border-transparent hover:border-yellow-300 cursor-pointer" :class="cover.url === selectedCover ? 'border-yellow-300' : ''" @click="$emit('select-cover', cover.url)">
<div class="h-24 bg-primary" :style="{ width: 96 / bookCoverAspectRatio + 'px' }">
<covers-display-cover :cover-image="cover" :width="96 / bookCoverAspectRatio" :book-cover-aspect-ratio="bookCoverAspectRatio" :show-resolution="true" :show-open-new-tab="true" />
</div>
</div>
</template>
</div>
<!-- Divider if both sections have covers -->
<div v-if="primaryCovers.length && secondaryCovers.length" class="w-full border-b border-white/10 my-4"></div>
<!-- Secondary Covers Section (opposite aspect ratio) -->
<div v-if="secondaryCovers.length" class="flex items-center flex-wrap justify-center">
<template v-for="cover in secondaryCovers">
<div :key="cover.url" class="m-0.5 mb-5 border-2 border-transparent hover:border-yellow-300 cursor-pointer" :class="cover.url === selectedCover ? 'border-yellow-300' : ''" @click="$emit('select-cover', cover.url)">
<div class="h-24 bg-primary" :style="{ width: 96 / bookCoverAspectRatio + 'px' }">
<covers-display-cover :cover-image="cover" :width="96 / bookCoverAspectRatio" :book-cover-aspect-ratio="bookCoverAspectRatio" :show-resolution="true" :show-open-new-tab="true" />
</div>
</div>
</template>
</div>
</div>
</template>
<script>
import DisplayCover from './DisplayCover.vue'
export default {
name: 'SortedCovers',
components: {
'covers-display-cover': DisplayCover
},
props: {
covers: {
type: Array,
required: true,
default: () => []
},
bookCoverAspectRatio: {
type: Number,
required: true
},
selectedCover: {
type: String,
default: ''
}
},
computed: {
// Sort covers by dimension and size
sortedCovers() {
return [...this.covers].sort((a, b) => {
// Sort by area (width * height)
return a.width * a.height - b.width * b.height
})
},
// Get square covers (width === height)
squareCovers() {
return this.sortedCovers.filter((cover) => cover.width === cover.height)
},
// Get rectangular covers (width !== height)
rectangleCovers() {
return this.sortedCovers.filter((cover) => cover.width !== cover.height)
},
// Determine primary covers based on preferred aspect ratio
primaryCovers() {
return this.bookCoverAspectRatio === 1 ? this.squareCovers : this.rectangleCovers
},
// Determine secondary covers (opposite of primary)
secondaryCovers() {
return this.bookCoverAspectRatio === 1 ? this.rectangleCovers : this.squareCovers
}
}
}
</script>
<style scoped>
/* Ensure proper height distribution */
.cover-grid-container {
min-height: 200px;
height: auto;
}
@media (min-width: 640px) {
.cover-grid-container {
max-height: calc(100vh - 400px);
}
}
</style>