mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-01-28 08:49:25 +01:00
Add:Authors landing page #187
This commit is contained in:
parent
f94c706fc8
commit
fbd7ae10d1
@ -42,32 +42,17 @@
|
|||||||
height: 8px;
|
height: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ::-webkit-scrollbar:horizontal { */
|
|
||||||
/* height: 16px; */
|
|
||||||
/* height: 24px;
|
|
||||||
} */
|
|
||||||
/* Track */
|
/* Track */
|
||||||
::-webkit-scrollbar-track {
|
::-webkit-scrollbar-track {
|
||||||
background-color: rgba(0, 0, 0, 0);
|
background-color: rgba(0, 0, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ::-webkit-scrollbar-track:horizontal { */
|
|
||||||
/* 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%); */
|
|
||||||
/* background: linear-gradient(180deg, rgb(117, 88, 60) 0%, rgb(65, 41, 17) 17%, rgb(71, 43, 15) 88%, rgb(3, 2, 1) 100%);
|
|
||||||
box-shadow: 2px 14px 8px #111111aa;
|
|
||||||
} */
|
|
||||||
/* Handle */
|
/* Handle */
|
||||||
::-webkit-scrollbar-thumb {
|
::-webkit-scrollbar-thumb {
|
||||||
background: #855620;
|
background: #855620;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ::-webkit-scrollbar-thumb:horizontal { */
|
|
||||||
/* 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;
|
|
||||||
border-radius: 4px;
|
|
||||||
} */
|
|
||||||
/* Handle on hover */
|
/* Handle on hover */
|
||||||
::-webkit-scrollbar-thumb:hover {
|
::-webkit-scrollbar-thumb:hover {
|
||||||
background: #704922;
|
background: #704922;
|
||||||
@ -78,6 +63,13 @@
|
|||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.no-scroll {
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
/* IE and Edge */
|
||||||
|
scrollbar-width: none;
|
||||||
|
/* Firefox */
|
||||||
|
}
|
||||||
|
|
||||||
/* Chrome, Safari, Edge, Opera */
|
/* Chrome, Safari, Edge, Opera */
|
||||||
.no-spinner::-webkit-outer-spin-button,
|
.no-spinner::-webkit-outer-spin-button,
|
||||||
.no-spinner::-webkit-inner-spin-button {
|
.no-spinner::-webkit-inner-spin-button {
|
||||||
|
@ -199,10 +199,7 @@ export default {
|
|||||||
scroll-behavior: smooth;
|
scroll-behavior: smooth;
|
||||||
width: calc(100vw - 80px);
|
width: calc(100vw - 80px);
|
||||||
|
|
||||||
/* background-color: rgb(214, 116, 36); */
|
|
||||||
background-image: var(--bookshelf-texture-img);
|
background-image: var(--bookshelf-texture-img);
|
||||||
/* background-position: center; */
|
|
||||||
/* background-size: contain; */
|
|
||||||
background-repeat: repeat-x;
|
background-repeat: repeat-x;
|
||||||
}
|
}
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
@ -213,9 +210,7 @@ export default {
|
|||||||
|
|
||||||
.bookshelfDividerCategorized {
|
.bookshelfDividerCategorized {
|
||||||
background: rgb(149, 119, 90);
|
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%); */
|
|
||||||
background: linear-gradient(180deg, rgb(122, 94, 68) 0%, rgb(92, 62, 31) 17%, rgb(82, 54, 26) 88%, rgba(71, 48, 25, 1) 100%);
|
background: linear-gradient(180deg, rgb(122, 94, 68) 0%, rgb(92, 62, 31) 17%, rgb(82, 54, 26) 88%, rgba(71, 48, 25, 1) 100%);
|
||||||
/* background: linear-gradient(180deg, rgb(114, 85, 59) 0%, rgb(73, 48, 22) 17%, rgb(71, 43, 15) 88%, rgb(61, 41, 20) 100%); */
|
|
||||||
box-shadow: 2px 14px 8px #111111aa;
|
box-shadow: 2px 14px 8px #111111aa;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,13 +43,13 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
props: {
|
// props: {
|
||||||
value: Boolean,
|
// value: Boolean,
|
||||||
author: {
|
// author: {
|
||||||
type: Object,
|
// type: Object,
|
||||||
default: () => {}
|
// default: () => {}
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
authorCopy: {
|
authorCopy: {
|
||||||
@ -73,12 +73,15 @@ export default {
|
|||||||
computed: {
|
computed: {
|
||||||
show: {
|
show: {
|
||||||
get() {
|
get() {
|
||||||
return this.value
|
return this.$store.state.globals.showEditAuthorModal
|
||||||
},
|
},
|
||||||
set(val) {
|
set(val) {
|
||||||
this.$emit('input', val)
|
this.$store.commit('globals/setShowEditAuthorModal', val)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
author() {
|
||||||
|
return this.$store.state.globals.selectedAuthor
|
||||||
|
},
|
||||||
authorId() {
|
authorId() {
|
||||||
if (!this.author) return ''
|
if (!this.author) return ''
|
||||||
return this.author.id
|
return this.author.id
|
||||||
|
138
client/components/widgets/ItemSlider.vue
Normal file
138
client/components/widgets/ItemSlider.vue
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
<template>
|
||||||
|
<div class="w-full">
|
||||||
|
<div class="flex items-center py-3">
|
||||||
|
<slot />
|
||||||
|
<div class="flex-grow" />
|
||||||
|
<button v-if="isScrollable" class="w-8 h-8 mx-1 flex items-center justify-center rounded-full" :class="canScrollLeft ? 'hover:bg-white hover:bg-opacity-5 text-gray-300 hover:text-white' : 'text-white text-opacity-40 cursor-text'" @click="scrollLeft">
|
||||||
|
<span class="material-icons text-2xl">chevron_left</span>
|
||||||
|
</button>
|
||||||
|
<button v-if="isScrollable" class="w-8 h-8 mx-1 flex items-center justify-center rounded-full" :class="canScrollRight ? 'hover:bg-white hover:bg-opacity-5 text-gray-300 hover:text-white' : 'text-white text-opacity-40 cursor-text'" @click="scrollRight">
|
||||||
|
<span class="material-icons text-2xl">chevron_right</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div ref="slider" class="w-full overflow-y-hidden overflow-x-auto no-scroll -mx-2" style="scroll-behavior: smooth" @scroll="scrolled">
|
||||||
|
<div class="flex" :style="{ height: height + 'px' }">
|
||||||
|
<template v-for="(item, index) in items">
|
||||||
|
<cards-lazy-book-card :key="item.id" :ref="`slider-item-${item.id}`" :index="index" :book-mount="item" :height="cardHeight" :width="cardWidth" :book-cover-aspect-ratio="bookCoverAspectRatio" :bookshelf-view="$constants.BookshelfView.TITLES" class="relative mx-2" @edit="editItem" @select="selectItem" />
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
items: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
type: Number,
|
||||||
|
default: 192
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isScrollable: false,
|
||||||
|
canScrollLeft: false,
|
||||||
|
canScrollRight: false,
|
||||||
|
clientWidth: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
bookCoverAspectRatio() {
|
||||||
|
return this.$store.getters['getBookCoverAspectRatio']
|
||||||
|
},
|
||||||
|
cardHeight() {
|
||||||
|
return this.height - 40
|
||||||
|
},
|
||||||
|
cardWidth() {
|
||||||
|
return this.cardHeight / this.bookCoverAspectRatio
|
||||||
|
},
|
||||||
|
booksPerPage() {
|
||||||
|
return Math.floor(this.clientWidth / (this.cardWidth + 16))
|
||||||
|
},
|
||||||
|
isSelectionMode() {
|
||||||
|
return this.$store.getters['getNumLibraryItemsSelected'] > 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
clearSelectedEntities() {
|
||||||
|
this.updateSelectionMode(false)
|
||||||
|
},
|
||||||
|
editItem(libraryItem) {
|
||||||
|
var itemIds = this.items.map((e) => e.id)
|
||||||
|
this.$store.commit('setBookshelfBookIds', itemIds)
|
||||||
|
this.$store.commit('showEditModal', libraryItem)
|
||||||
|
},
|
||||||
|
selectItem(libraryItem) {
|
||||||
|
this.$store.commit('toggleLibraryItemSelected', libraryItem.id)
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$eventBus.$emit('item-selected', libraryItem)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
itemSelectedEvt() {
|
||||||
|
this.updateSelectionMode(this.isSelectionMode)
|
||||||
|
},
|
||||||
|
updateSelectionMode(val) {
|
||||||
|
var selectedLibraryItems = this.$store.state.selectedLibraryItems
|
||||||
|
|
||||||
|
this.items.forEach((item) => {
|
||||||
|
var component = this.$refs[`slider-item-${item.id}`]
|
||||||
|
if (!component || !component.length) return
|
||||||
|
component = component[0]
|
||||||
|
component.setSelectionMode(val)
|
||||||
|
component.selected = selectedLibraryItems.includes(item.id)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
scrolled() {
|
||||||
|
this.setScrollVars()
|
||||||
|
},
|
||||||
|
scrollRight() {
|
||||||
|
if (!this.canScrollRight) return
|
||||||
|
const slider = this.$refs.slider
|
||||||
|
if (!slider) return
|
||||||
|
const scrollAmount = this.booksPerPage * this.cardWidth
|
||||||
|
const maxScrollLeft = slider.scrollWidth - slider.clientWidth
|
||||||
|
|
||||||
|
const newScrollLeft = Math.min(maxScrollLeft, slider.scrollLeft + scrollAmount)
|
||||||
|
slider.scrollLeft = newScrollLeft
|
||||||
|
},
|
||||||
|
scrollLeft() {
|
||||||
|
if (!this.canScrollLeft) return
|
||||||
|
const slider = this.$refs.slider
|
||||||
|
if (!slider) return
|
||||||
|
|
||||||
|
const scrollAmount = this.booksPerPage * this.cardWidth
|
||||||
|
|
||||||
|
const newScrollLeft = Math.max(0, slider.scrollLeft - scrollAmount)
|
||||||
|
slider.scrollLeft = newScrollLeft
|
||||||
|
},
|
||||||
|
setScrollVars() {
|
||||||
|
const slider = this.$refs.slider
|
||||||
|
if (!slider) return
|
||||||
|
const { scrollLeft, scrollWidth, clientWidth } = slider
|
||||||
|
const scrollPercent = (scrollLeft + clientWidth) / scrollWidth
|
||||||
|
|
||||||
|
this.clientWidth = clientWidth
|
||||||
|
this.isScrollable = scrollWidth > clientWidth
|
||||||
|
this.canScrollRight = scrollPercent < 1
|
||||||
|
this.canScrollLeft = scrollLeft > 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updated() {
|
||||||
|
this.setScrollVars()
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.setScrollVars()
|
||||||
|
|
||||||
|
this.$eventBus.$on('bookshelf-clear-selection', this.clearSelectedEntities)
|
||||||
|
this.$eventBus.$on('item-selected', this.itemSelectedEvt)
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
this.$eventBus.$off('bookshelf-clear-selection', this.clearSelectedEntities)
|
||||||
|
this.$eventBus.$off('item-selected', this.itemSelectedEvt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
@ -11,6 +11,7 @@
|
|||||||
<modals-edit-collection-modal />
|
<modals-edit-collection-modal />
|
||||||
<modals-bookshelf-texture-modal />
|
<modals-bookshelf-texture-modal />
|
||||||
<modals-podcast-edit-episode />
|
<modals-podcast-edit-episode />
|
||||||
|
<modals-authors-edit-modal />
|
||||||
<readers-reader />
|
<readers-reader />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
105
client/pages/author/_id.vue
Normal file
105
client/pages/author/_id.vue
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
<template>
|
||||||
|
<div id="page-wrapper" class="bg-bg page overflow-y-auto p-8" :class="streamLibraryItem ? 'streaming' : ''">
|
||||||
|
<div class="max-w-6xl mx-auto">
|
||||||
|
<div class="flex mb-6">
|
||||||
|
<div class="w-48 min-w-48">
|
||||||
|
<div class="w-full h-52">
|
||||||
|
<covers-author-image :author="author" rounded="0" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-grow px-8">
|
||||||
|
<div class="flex items-center mb-8">
|
||||||
|
<h1 class="text-2xl">{{ author.name }}</h1>
|
||||||
|
|
||||||
|
<button class="w-8 h-8 rounded-full flex items-center justify-center mx-4 cursor-pointer text-gray-300 hover:text-warning transform hover:scale-125 duration-100" @click="editAuthor">
|
||||||
|
<span class="material-icons text-base">edit</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p v-if="author.description" class="text-white text-opacity-60 uppercase text-xs mb-2">Description</p>
|
||||||
|
<p class="text-white max-w-3xl text-sm leading-5">{{ author.description }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="py-4">
|
||||||
|
<widgets-item-slider :items="libraryItems">
|
||||||
|
<h2 class="text-lg">{{ libraryItems.length }} Books</h2>
|
||||||
|
</widgets-item-slider>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-for="series in authorSeries" :key="series.id" class="py-4">
|
||||||
|
<widgets-item-slider :items="series.items">
|
||||||
|
<h2 class="text-lg">{{ series.name }}</h2>
|
||||||
|
<p class="text-white text-opacity-40 text-base px-2">Series</p>
|
||||||
|
</widgets-item-slider>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
async asyncData({ store, app, params, redirect }) {
|
||||||
|
const author = await app.$axios.$get(`/api/authors/${params.id}?include=items,series`).catch((error) => {
|
||||||
|
console.error('Failed to get author', error)
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!author) {
|
||||||
|
return redirect(`/library/${store.state.libraries.currentLibraryId}/authors`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
author
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
streamLibraryItem() {
|
||||||
|
return this.$store.state.streamLibraryItem
|
||||||
|
},
|
||||||
|
currentLibraryId() {
|
||||||
|
return this.$store.state.libraries.currentLibraryId
|
||||||
|
},
|
||||||
|
libraryItems() {
|
||||||
|
return this.author.libraryItems || []
|
||||||
|
},
|
||||||
|
authorSeries() {
|
||||||
|
return this.author.series || []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
editAuthor() {
|
||||||
|
this.$store.commit('globals/showEditAuthorModal', this.author)
|
||||||
|
},
|
||||||
|
authorUpdated(author) {
|
||||||
|
if (author.id === this.author.id) {
|
||||||
|
console.log('Author was updated', author)
|
||||||
|
this.author = {
|
||||||
|
...author,
|
||||||
|
series: this.authorSeries,
|
||||||
|
libraryItems: this.libraryItems
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
authorRemoved(author) {
|
||||||
|
if (author.id === this.author.id) {
|
||||||
|
console.warn('Author was removed')
|
||||||
|
this.$router.replace(`/library/${this.currentLibraryId}/authors`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
if (!this.author) this.$router.replace('/')
|
||||||
|
|
||||||
|
this.$root.socket.on('author_updated', this.authorUpdated)
|
||||||
|
this.$root.socket.on('author_removed', this.authorRemoved)
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
this.$root.socket.off('author_updated', this.authorUpdated)
|
||||||
|
this.$root.socket.off('author_removed', this.authorRemoved)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
@ -7,7 +7,8 @@
|
|||||||
<div id="bookshelf" class="w-full h-full p-8 overflow-y-auto">
|
<div id="bookshelf" class="w-full h-full p-8 overflow-y-auto">
|
||||||
<div class="flex flex-wrap justify-center">
|
<div class="flex flex-wrap justify-center">
|
||||||
<template v-for="author in authors">
|
<template v-for="author in authors">
|
||||||
<nuxt-link :key="author.id" :to="`/library/${currentLibraryId}/bookshelf?filter=authors.${$encode(author.id)}`">
|
<!-- <nuxt-link :key="author.id" :to="`/library/${currentLibraryId}/bookshelf?filter=authors.${$encode(author.id)}`"> -->
|
||||||
|
<nuxt-link :key="author.id" :to="`/author/${author.id}`">
|
||||||
<cards-author-card :author="author" :width="160" :height="200" class="p-3" @edit="editAuthor" />
|
<cards-author-card :author="author" :width="160" :height="200" class="p-3" @edit="editAuthor" />
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
</template>
|
</template>
|
||||||
@ -15,7 +16,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<modals-authors-edit-modal v-model="showAuthorModal" :author="selectedAuthor" />
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -40,9 +40,7 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
loading: true,
|
loading: true,
|
||||||
authors: [],
|
authors: []
|
||||||
showAuthorModal: false,
|
|
||||||
selectedAuthor: null
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -51,6 +49,9 @@ export default {
|
|||||||
},
|
},
|
||||||
currentLibraryId() {
|
currentLibraryId() {
|
||||||
return this.$store.state.libraries.currentLibraryId
|
return this.$store.state.libraries.currentLibraryId
|
||||||
|
},
|
||||||
|
selectedAuthor() {
|
||||||
|
return this.$store.state.globals.selectedAuthor
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@ -68,7 +69,7 @@ export default {
|
|||||||
},
|
},
|
||||||
authorUpdated(author) {
|
authorUpdated(author) {
|
||||||
if (this.selectedAuthor && this.selectedAuthor.id === author.id) {
|
if (this.selectedAuthor && this.selectedAuthor.id === author.id) {
|
||||||
this.selectedAuthor = author
|
this.$store.commit('globals/setSelectedAuthor', author)
|
||||||
}
|
}
|
||||||
this.authors = this.authors.map((au) => {
|
this.authors = this.authors.map((au) => {
|
||||||
if (au.id === author.id) {
|
if (au.id === author.id) {
|
||||||
@ -81,8 +82,7 @@ export default {
|
|||||||
this.authors = this.authors.filter((au) => au.id !== author.id)
|
this.authors = this.authors.filter((au) => au.id !== author.id)
|
||||||
},
|
},
|
||||||
editAuthor(author) {
|
editAuthor(author) {
|
||||||
this.selectedAuthor = author
|
this.$store.commit('globals/showEditAuthorModal', author)
|
||||||
this.showAuthorModal = true
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
@ -6,8 +6,10 @@ export const state = () => ({
|
|||||||
showUserCollectionsModal: false,
|
showUserCollectionsModal: false,
|
||||||
showEditCollectionModal: false,
|
showEditCollectionModal: false,
|
||||||
showEditPodcastEpisode: false,
|
showEditPodcastEpisode: false,
|
||||||
|
showEditAuthorModal: false,
|
||||||
selectedEpisode: null,
|
selectedEpisode: null,
|
||||||
selectedCollection: null,
|
selectedCollection: null,
|
||||||
|
selectedAuthor: null,
|
||||||
showBookshelfTextureModal: false,
|
showBookshelfTextureModal: false,
|
||||||
isCasting: false, // Actively casting
|
isCasting: false, // Actively casting
|
||||||
isChromecastInitialized: false // Script loaded
|
isChromecastInitialized: false // Script loaded
|
||||||
@ -61,6 +63,16 @@ export const mutations = {
|
|||||||
setShowBookshelfTextureModal(state, val) {
|
setShowBookshelfTextureModal(state, val) {
|
||||||
state.showBookshelfTextureModal = val
|
state.showBookshelfTextureModal = val
|
||||||
},
|
},
|
||||||
|
showEditAuthorModal(state, author) {
|
||||||
|
state.selectedAuthor = author
|
||||||
|
state.showEditAuthorModal = true
|
||||||
|
},
|
||||||
|
setShowEditAuthorModal(state, val) {
|
||||||
|
state.showEditAuthorModal = val
|
||||||
|
},
|
||||||
|
setSelectedAuthor(state, author) {
|
||||||
|
state.selectedAuthor = author
|
||||||
|
},
|
||||||
setChromecastInitialized(state, val) {
|
setChromecastInitialized(state, val) {
|
||||||
state.isChromecastInitialized = val
|
state.isChromecastInitialized = val
|
||||||
},
|
},
|
||||||
|
@ -1,11 +1,59 @@
|
|||||||
const Logger = require('../Logger')
|
const Logger = require('../Logger')
|
||||||
const { reqSupportsWebp } = require('../utils/index')
|
const { reqSupportsWebp } = require('../utils/index')
|
||||||
|
const { createNewSortInstance } = require('fast-sort')
|
||||||
|
|
||||||
|
const naturalSort = createNewSortInstance({
|
||||||
|
comparer: new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' }).compare
|
||||||
|
})
|
||||||
class AuthorController {
|
class AuthorController {
|
||||||
constructor() { }
|
constructor() { }
|
||||||
|
|
||||||
async findOne(req, res) {
|
async findOne(req, res) {
|
||||||
return res.json(req.author)
|
const include = (req.query.include || '').split(',')
|
||||||
|
|
||||||
|
const authorJson = req.author.toJSON()
|
||||||
|
|
||||||
|
// Used on author landing page to include library items and items grouped in series
|
||||||
|
if (include.includes('items')) {
|
||||||
|
authorJson.libraryItems = this.db.libraryItems.filter(li => {
|
||||||
|
return li.media.metadata.hasAuthor && li.media.metadata.hasAuthor(req.author.id)
|
||||||
|
})
|
||||||
|
|
||||||
|
if (include.includes('series')) {
|
||||||
|
const seriesMap = {}
|
||||||
|
// Group items into series
|
||||||
|
authorJson.libraryItems.forEach((li) => {
|
||||||
|
if (li.media.metadata.series) {
|
||||||
|
li.media.metadata.series.forEach((series) => {
|
||||||
|
|
||||||
|
const itemWithSeries = li.toJSONMinified()
|
||||||
|
itemWithSeries.media.metadata.series = series
|
||||||
|
|
||||||
|
if (seriesMap[series.id]) {
|
||||||
|
seriesMap[series.id].items.push(itemWithSeries)
|
||||||
|
} else {
|
||||||
|
seriesMap[series.id] = {
|
||||||
|
id: series.id,
|
||||||
|
name: series.name,
|
||||||
|
items: [itemWithSeries]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// Sort series items
|
||||||
|
for (const key in seriesMap) {
|
||||||
|
seriesMap[key].items = naturalSort(seriesMap[key].items).asc(li => li.media.metadata.series.sequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
authorJson.series = Object.values(seriesMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Minify library items
|
||||||
|
authorJson.libraryItems = authorJson.libraryItems.map(li => li.toJSONMinified())
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.json(authorJson)
|
||||||
}
|
}
|
||||||
|
|
||||||
async update(req, res) {
|
async update(req, res) {
|
||||||
@ -41,6 +89,7 @@ class AuthorController {
|
|||||||
}).length
|
}).length
|
||||||
this.emitter('author_updated', req.author.toJSONExpanded(numBooks))
|
this.emitter('author_updated', req.author.toJSONExpanded(numBooks))
|
||||||
}
|
}
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
author: req.author.toJSON(),
|
author: req.author.toJSON(),
|
||||||
updated: hasUpdated
|
updated: hasUpdated
|
||||||
|
Loading…
Reference in New Issue
Block a user