mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-05-31 04:05:40 -04:00
Show book sequences for collapsed series when filtering by series
This commit is contained in:
parent
b322d0207b
commit
c1035d97e8
@ -1,12 +1,15 @@
|
|||||||
<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" :id="`shelf-${shelf - 1}`" class="w-full px-4 sm:px-8 relative" :class="{ bookshelfRow: !isAlternativeBookshelfView }" :style="{ height: shelfHeight + 'px' }">
|
<div :key="shelf" :id="`shelf-${shelf - 1}`" class="w-full px-4 sm:px-8 relative"
|
||||||
<div v-if="!isAlternativeBookshelfView" class="bookshelfDivider w-full absolute bottom-0 left-0 right-0 z-20" :class="`h-${shelfDividerHeightIndex}`" />
|
:class="{ bookshelfRow: !isAlternativeBookshelfView }" :style="{ height: shelfHeight + 'px' }">
|
||||||
|
<div v-if="!isAlternativeBookshelfView" class="bookshelfDivider w-full absolute bottom-0 left-0 right-0 z-20"
|
||||||
|
:class="`h-${shelfDividerHeightIndex}`" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div v-if="initialized && !totalShelves && !hasFilter && entityName === 'books'" class="w-full flex flex-col items-center justify-center py-12">
|
<div v-if="initialized && !totalShelves && !hasFilter && entityName === 'books'"
|
||||||
|
class="w-full flex flex-col items-center justify-center py-12">
|
||||||
<p class="text-center text-2xl font-book mb-4 py-4">{{ libraryName }} Library is empty!</p>
|
<p class="text-center text-2xl font-book mb-4 py-4">{{ libraryName }} Library is empty!</p>
|
||||||
<div v-if="userIsAdminOrUp" class="flex">
|
<div v-if="userIsAdminOrUp" class="flex">
|
||||||
<ui-btn to="/config" color="primary" class="w-52 mr-2">Configure Scanner</ui-btn>
|
<ui-btn to="/config" color="primary" class="w-52 mr-2">Configure Scanner</ui-btn>
|
||||||
@ -319,7 +322,6 @@ export default {
|
|||||||
this.totalEntities = payload.total
|
this.totalEntities = payload.total
|
||||||
this.totalShelves = Math.ceil(this.totalEntities / this.entitiesPerShelf)
|
this.totalShelves = Math.ceil(this.totalEntities / this.entitiesPerShelf)
|
||||||
this.entities = new Array(this.totalEntities)
|
this.entities = new Array(this.totalEntities)
|
||||||
this.$eventBus.$emit('bookshelf-total-entities', this.totalEntities)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < payload.results.length; i++) {
|
for (let i = 0; i < payload.results.length; i++) {
|
||||||
@ -329,8 +331,21 @@ export default {
|
|||||||
this.entityComponentRefs[index].setEntity(this.entities[index])
|
this.entityComponentRefs[index].setEntity(this.entities[index])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.$eventBus.$emit('bookshelf-total-entities', this.getEntitiesCount())
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
getEntitiesCount() {
|
||||||
|
let uniqueEntities = new Set()
|
||||||
|
this.entities.forEach(entity => {
|
||||||
|
if (entity.collapsedSeries) {
|
||||||
|
entity.collapsedSeries.libraryItemIds.forEach(uniqueEntities.add, uniqueEntities)
|
||||||
|
} else {
|
||||||
|
uniqueEntities.add(entity.id)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return uniqueEntities.size
|
||||||
|
},
|
||||||
loadPage(page) {
|
loadPage(page) {
|
||||||
this.pagesLoaded[page] = true
|
this.pagesLoaded[page] = true
|
||||||
this.fetchEntites(page)
|
this.fetchEntites(page)
|
||||||
@ -513,7 +528,7 @@ export default {
|
|||||||
if (indexOf >= 0) {
|
if (indexOf >= 0) {
|
||||||
this.entities = this.entities.filter((ent) => ent.id !== libraryItem.id)
|
this.entities = this.entities.filter((ent) => ent.id !== libraryItem.id)
|
||||||
this.totalEntities = this.entities.length
|
this.totalEntities = this.entities.length
|
||||||
this.$eventBus.$emit('bookshelf-total-entities', this.totalEntities)
|
this.$eventBus.$emit('bookshelf-total-entities', this.getEntitiesCount())
|
||||||
this.executeRebuild()
|
this.executeRebuild()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -551,7 +566,7 @@ export default {
|
|||||||
if (indexOf >= 0) {
|
if (indexOf >= 0) {
|
||||||
this.entities = this.entities.filter((ent) => ent.id !== collection.id)
|
this.entities = this.entities.filter((ent) => ent.id !== collection.id)
|
||||||
this.totalEntities = this.entities.length
|
this.totalEntities = this.entities.length
|
||||||
this.$eventBus.$emit('bookshelf-total-entities', this.totalEntities)
|
this.$eventBus.$emit('bookshelf-total-entities', this.getEntitiesCount())
|
||||||
this.executeRebuild()
|
this.executeRebuild()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -715,6 +730,7 @@ export default {
|
|||||||
.bookshelfRow {
|
.bookshelfRow {
|
||||||
background-image: var(--bookshelf-texture-img);
|
background-image: var(--bookshelf-texture-img);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bookshelfDivider {
|
.bookshelfDivider {
|
||||||
background: rgb(149, 119, 90);
|
background: rgb(149, 119, 90);
|
||||||
background: var(--bookshelf-divider-bg);
|
background: var(--bookshelf-divider-bg);
|
||||||
|
@ -1,106 +1,160 @@
|
|||||||
<template>
|
<template>
|
||||||
<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">
|
<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" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Alternative bookshelf title/author/sort -->
|
<!-- Alternative bookshelf title/author/sort -->
|
||||||
<div v-if="isAlternativeBookshelfView || isAuthorBookshelfView" class="absolute left-0 z-50 w-full" :style="{ bottom: `-${titleDisplayBottomOffset}rem` }">
|
<div v-if="isAlternativeBookshelfView || isAuthorBookshelfView" class="absolute left-0 z-50 w-full"
|
||||||
|
:style="{ bottom: `-${titleDisplayBottomOffset}rem` }">
|
||||||
<p class="truncate" :style="{ fontSize: 0.9 * sizeMultiplier + 'rem' }">
|
<p class="truncate" :style="{ fontSize: 0.9 * sizeMultiplier + 'rem' }">
|
||||||
{{ displayTitle }}
|
{{ displayTitle }}
|
||||||
</p>
|
</p>
|
||||||
<p class="truncate text-gray-400" :style="{ fontSize: 0.8 * sizeMultiplier + 'rem' }">{{ displayLineTwo || ' ' }}</p>
|
<p class="truncate text-gray-400" :style="{ fontSize: 0.8 * sizeMultiplier + 'rem' }">{{ displayLineTwo ||
|
||||||
<p v-if="displaySortLine" class="truncate text-gray-400" :style="{ fontSize: 0.8 * sizeMultiplier + 'rem' }">{{ displaySortLine }}</p>
|
' '
|
||||||
|
}}</p>
|
||||||
|
<p v-if="displaySortLine" class="truncate text-gray-400" :style="{ fontSize: 0.8 * sizeMultiplier + 'rem' }">{{
|
||||||
|
displaySortLine
|
||||||
|
}}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="booksInSeries" class="absolute z-20 top-1.5 right-1.5 rounded-md leading-3 text-sm p-1 font-semibold text-white flex items-center justify-center" style="background-color: #cd9d49dd">{{ booksInSeries }}</div>
|
<div v-if="seriesSequenceList"
|
||||||
|
class="absolute z-20 top-1.5 right-1.5 rounded-md leading-3 text-sm p-1 font-semibold text-white flex items-center justify-center bg-black bg-opacity-90 box-shadow-md"
|
||||||
|
style="background-color: #78350f">#{{ seriesSequenceList }}</div>
|
||||||
|
<div v-else-if="booksInSeries"
|
||||||
|
class="absolute z-20 top-1.5 right-1.5 rounded-md leading-3 text-sm p-1 font-semibold text-white flex items-center justify-center"
|
||||||
|
style="background-color: #cd9d49dd">{{ booksInSeries }}</div>
|
||||||
|
|
||||||
<div class="w-full h-full absolute top-0 left-0 rounded overflow-hidden z-10">
|
<div class="w-full h-full absolute top-0 left-0 rounded overflow-hidden z-10">
|
||||||
<div v-show="libraryItem && !imageReady" class="absolute top-0 left-0 w-full h-full flex items-center justify-center" :style="{ padding: sizeMultiplier * 0.5 + 'rem' }">
|
<div v-show="libraryItem && !imageReady"
|
||||||
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }" class="font-book text-gray-300 text-center">{{ title }}</p>
|
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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Cover Image -->
|
<!-- Cover Image -->
|
||||||
<img v-show="libraryItem" ref="cover" :src="bookCoverSrc" class="w-full h-full transition-opacity duration-300" :class="showCoverBg ? 'object-contain' : 'object-fill'" @load="imageLoaded" :style="{ opacity: imageReady ? 1 : 0 }" />
|
<img v-show="libraryItem" ref="cover" :src="bookCoverSrc" class="w-full h-full transition-opacity duration-300"
|
||||||
|
:class="showCoverBg ? 'object-contain' : 'object-fill'" @load="imageLoaded"
|
||||||
|
:style="{ opacity: imageReady ? 1 : 0 }" />
|
||||||
|
|
||||||
<!-- Placeholder Cover Title & Author -->
|
<!-- Placeholder Cover Title & Author -->
|
||||||
<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 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>
|
<div>
|
||||||
<p class="text-center font-book" style="color: rgb(247 223 187)" :style="{ fontSize: titleFontSize + 'rem' }">{{ titleCleaned }}</p>
|
<p class="text-center font-book" style="color: rgb(247 223 187)" :style="{ fontSize: titleFontSize + 'rem' }">
|
||||||
|
{{ titleCleaned }}</p>
|
||||||
</div>
|
</div>
|
||||||
</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' }">
|
<div v-if="!hasCover" class="absolute left-0 right-0 w-full flex items-center justify-center"
|
||||||
<p class="text-center font-book" style="color: rgb(247 223 187); opacity: 0.75" :style="{ fontSize: authorFontSize + 'rem' }">{{ authorCleaned }}</p>
|
: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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- No progress shown for collapsed series in library and podcasts (unless showing podcast episode) -->
|
<!-- No progress shown for collapsed series in library and podcasts (unless showing podcast episode) -->
|
||||||
<div v-if="!booksInSeries && (!isPodcast || episodeProgress)" class="absolute bottom-0 left-0 h-1 shadow-sm max-w-full z-10 rounded-b" :class="itemIsFinished ? 'bg-success' : 'bg-yellow-400'" :style="{ width: width * userProgressPercent + 'px' }"></div>
|
<div v-if="!booksInSeries && (!isPodcast || episodeProgress)"
|
||||||
|
class="absolute bottom-0 left-0 h-1 shadow-sm max-w-full z-10 rounded-b"
|
||||||
|
:class="itemIsFinished ? 'bg-success' : 'bg-yellow-400'" :style="{ width: width * userProgressPercent + 'px' }">
|
||||||
|
</div>
|
||||||
<!-- Finished progress bar for collapsed series -->
|
<!-- Finished progress bar for collapsed series -->
|
||||||
<div v-else-if="booksInSeries && seriesIsFinished" class="absolute bottom-0 left-0 h-1 shadow-sm max-w-full z-10 rounded-b bg-success" :style="{ width: width * userProgressPercent + 'px' }"></div>
|
<div v-else-if="booksInSeries && seriesIsFinished"
|
||||||
|
class="absolute bottom-0 left-0 h-1 shadow-sm max-w-full z-10 rounded-b bg-success"
|
||||||
|
:style="{ width: width * userProgressPercent + 'px' }"></div>
|
||||||
|
|
||||||
<!-- Overlay is not shown if collapsing series in library -->
|
<!-- Overlay is not shown if collapsing series in library -->
|
||||||
<div v-show="!booksInSeries && libraryItem && (isHovering || isSelectionMode || isMoreMenuOpen) && !processing" class="w-full h-full absolute top-0 left-0 z-10 bg-black rounded hidden md:block" :class="overlayWrapperClasslist">
|
<div v-show="!booksInSeries && libraryItem && (isHovering || isSelectionMode || isMoreMenuOpen) && !processing"
|
||||||
|
class="w-full h-full absolute top-0 left-0 z-10 bg-black rounded hidden md:block"
|
||||||
|
:class="overlayWrapperClasslist">
|
||||||
<div v-show="showPlayButton" class="h-full flex items-center justify-center pointer-events-none">
|
<div v-show="showPlayButton" class="h-full flex items-center justify-center pointer-events-none">
|
||||||
<div class="hover:text-white text-gray-200 hover:scale-110 transform duration-200 pointer-events-auto" @click.stop.prevent="play">
|
<div class="hover:text-white text-gray-200 hover:scale-110 transform duration-200 pointer-events-auto"
|
||||||
|
@click.stop.prevent="play">
|
||||||
<span class="material-icons" :style="{ fontSize: playIconFontSize + 'rem' }">play_circle_filled</span>
|
<span class="material-icons" :style="{ fontSize: playIconFontSize + 'rem' }">play_circle_filled</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-show="showReadButton" class="h-full flex items-center justify-center pointer-events-none">
|
<div v-show="showReadButton" class="h-full flex items-center justify-center pointer-events-none">
|
||||||
<div class="hover:text-white text-gray-200 hover:scale-110 transform duration-200 pointer-events-auto" @click.stop.prevent="clickReadEBook">
|
<div class="hover:text-white text-gray-200 hover:scale-110 transform duration-200 pointer-events-auto"
|
||||||
|
@click.stop.prevent="clickReadEBook">
|
||||||
<span class="material-icons" :style="{ fontSize: playIconFontSize + 'rem' }">auto_stories</span>
|
<span class="material-icons" :style="{ fontSize: playIconFontSize + 'rem' }">auto_stories</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="userCanUpdate" v-show="!isSelectionMode" class="absolute cursor-pointer hover:text-yellow-300 hover:scale-125 transform duration-150 top-0 right-0" :style="{ padding: 0.375 * sizeMultiplier + 'rem' }" @click.stop.prevent="editClick">
|
<div v-if="userCanUpdate" v-show="!isSelectionMode"
|
||||||
|
class="absolute cursor-pointer hover:text-yellow-300 hover:scale-125 transform duration-150 top-0 right-0"
|
||||||
|
:style="{ padding: 0.375 * sizeMultiplier + 'rem' }" @click.stop.prevent="editClick">
|
||||||
<span class="material-icons" :style="{ fontSize: sizeMultiplier + 'rem' }">edit</span>
|
<span class="material-icons" :style="{ fontSize: sizeMultiplier + 'rem' }">edit</span>
|
||||||
</div>
|
</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">
|
<div class="absolute cursor-pointer hover:text-yellow-300 hover:scale-125 transform duration-100"
|
||||||
<span class="material-icons" :class="selected ? 'text-yellow-400' : ''" :style="{ fontSize: 1.25 * sizeMultiplier + 'rem' }">{{ selected ? 'radio_button_checked' : 'radio_button_unchecked' }}</span>
|
: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>
|
||||||
|
|
||||||
<!-- More Menu Icon -->
|
<!-- More Menu Icon -->
|
||||||
<div ref="moreIcon" v-show="!isSelectionMode" class="hidden md:block absolute cursor-pointer hover:text-yellow-300 300 hover:scale-125 transform duration-150" :style="{ bottom: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem' }" @click.stop.prevent="clickShowMore">
|
<div ref="moreIcon" v-show="!isSelectionMode"
|
||||||
|
class="hidden md:block absolute cursor-pointer hover:text-yellow-300 300 hover:scale-125 transform duration-150"
|
||||||
|
: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>
|
<span class="material-icons" :style="{ fontSize: 1.2 * sizeMultiplier + 'rem' }">more_vert</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Processing/loading spinner overlay -->
|
<!-- Processing/loading spinner overlay -->
|
||||||
<div v-if="processing" class="w-full h-full absolute top-0 left-0 z-10 bg-black bg-opacity-40 rounded flex items-center justify-center">
|
<div v-if="processing"
|
||||||
|
class="w-full h-full absolute top-0 left-0 z-10 bg-black bg-opacity-40 rounded flex items-center justify-center">
|
||||||
<widgets-loading-spinner size="la-lg" />
|
<widgets-loading-spinner size="la-lg" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Series name overlay -->
|
<!-- Series name overlay -->
|
||||||
<div v-if="booksInSeries && libraryItem && isHovering" class="w-full h-full absolute top-0 left-0 z-10 bg-black bg-opacity-60 rounded flex items-center justify-center" :style="{ padding: sizeMultiplier + 'rem' }">
|
<div v-if="booksInSeries && libraryItem && isHovering"
|
||||||
|
class="w-full h-full absolute top-0 left-0 z-10 bg-black bg-opacity-60 rounded flex items-center justify-center"
|
||||||
|
:style="{ padding: sizeMultiplier + 'rem' }">
|
||||||
<p class="text-gray-200 text-center" :style="{ fontSize: 1.1 * sizeMultiplier + 'rem' }">{{ series }}</p>
|
<p class="text-gray-200 text-center" :style="{ fontSize: 1.1 * sizeMultiplier + 'rem' }">{{ series }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Error widget -->
|
<!-- Error widget -->
|
||||||
<ui-tooltip v-if="showError" :text="errorText" class="absolute bottom-4 left-0 z-10">
|
<ui-tooltip v-if="showError" :text="errorText" class="absolute bottom-4 left-0 z-10">
|
||||||
<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">
|
<div :style="{ height: 1.5 * sizeMultiplier + 'rem', width: 2.5 * sizeMultiplier + 'rem' }"
|
||||||
<span class="material-icons text-red-100 pr-1" :style="{ fontSize: 0.875 * sizeMultiplier + 'rem' }">priority_high</span>
|
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>
|
</div>
|
||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
|
|
||||||
<div v-if="rssFeed && !isSelectionMode && !isHovering" class="absolute text-success top-0 left-0 z-10" :style="{ padding: 0.375 * sizeMultiplier + 'rem' }">
|
<div v-if="rssFeed && !isSelectionMode && !isHovering" class="absolute text-success top-0 left-0 z-10"
|
||||||
|
:style="{ padding: 0.375 * sizeMultiplier + 'rem' }">
|
||||||
<span class="material-icons" :style="{ fontSize: sizeMultiplier * 1.5 + 'rem' }">rss_feed</span>
|
<span class="material-icons" :style="{ fontSize: sizeMultiplier * 1.5 + 'rem' }">rss_feed</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Series sequence -->
|
<!-- Series sequence -->
|
||||||
<div v-if="seriesSequence && !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` }">
|
<div v-if="seriesSequence && !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' }">#{{ seriesSequence }}</p>
|
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">#{{ seriesSequence }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Podcast Episode # -->
|
<!-- Podcast Episode # -->
|
||||||
<div v-if="recentEpisodeNumber && !isHovering && !isSelectionMode && !processing" 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` }">
|
<div v-if="recentEpisodeNumber && !isHovering && !isSelectionMode && !processing"
|
||||||
|
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' }">Episode #{{ recentEpisodeNumber }}</p>
|
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">Episode #{{ recentEpisodeNumber }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Podcast Num Episodes -->
|
<!-- Podcast Num Episodes -->
|
||||||
<div v-else-if="numEpisodes && !isHovering && !isSelectionMode" class="absolute rounded-full bg-black bg-opacity-90 box-shadow-md z-10 flex items-center justify-center" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', width: 1.25 * sizeMultiplier + 'rem', height: 1.25 * sizeMultiplier + 'rem' }">
|
<div v-else-if="numEpisodes && !isHovering && !isSelectionMode"
|
||||||
|
class="absolute rounded-full bg-black bg-opacity-90 box-shadow-md z-10 flex items-center justify-center"
|
||||||
|
:style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', width: 1.25 * sizeMultiplier + 'rem', height: 1.25 * sizeMultiplier + 'rem' }">
|
||||||
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">{{ numEpisodes }}</p>
|
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">{{ numEpisodes }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -236,6 +290,9 @@ export default {
|
|||||||
// Only added to item object when collapseSeries is enabled
|
// Only added to item object when collapseSeries is enabled
|
||||||
return this.collapsedSeries ? this.collapsedSeries.numBooks : 0
|
return this.collapsedSeries ? this.collapsedSeries.numBooks : 0
|
||||||
},
|
},
|
||||||
|
seriesSequenceList() {
|
||||||
|
return this.collapsedSeries ? this.collapsedSeries.seriesSequenceList : null
|
||||||
|
},
|
||||||
libraryItemIdsInSeries() {
|
libraryItemIdsInSeries() {
|
||||||
// Only added to item object when collapseSeries is enabled
|
// Only added to item object when collapseSeries is enabled
|
||||||
return this.collapsedSeries ? this.collapsedSeries.libraryItemIds || [] : []
|
return this.collapsedSeries ? this.collapsedSeries.libraryItemIds || [] : []
|
||||||
@ -515,7 +572,7 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
var mediaMetadata = libraryItem.media.metadata
|
var mediaMetadata = libraryItem.media.metadata
|
||||||
if (mediaMetadata.series) {
|
if (mediaMetadata.series && Array.isArray(mediaMetadata.series)) {
|
||||||
var newSeries = mediaMetadata.series.find((se) => se.id === this.series.id)
|
var newSeries = mediaMetadata.series.find((se) => se.id === this.series.id)
|
||||||
if (newSeries) {
|
if (newSeries) {
|
||||||
// update selected series
|
// update selected series
|
||||||
|
@ -162,32 +162,40 @@ class LibraryController {
|
|||||||
}
|
}
|
||||||
var mediaIsBook = payload.mediaType === 'book'
|
var mediaIsBook = payload.mediaType === 'book'
|
||||||
|
|
||||||
|
// Step 1 - Filter the retrieved library items
|
||||||
var filterSeries = null
|
var filterSeries = null
|
||||||
if (payload.filterBy) {
|
if (payload.filterBy) {
|
||||||
// If filtering by series, will include seriesName and seriesSequence on media metadata
|
|
||||||
filterSeries = (mediaIsBook && payload.filterBy.startsWith('series.')) ? libraryHelpers.decode(payload.filterBy.replace('series.', '')) : null
|
|
||||||
if (filterSeries === 'No Series') filterSeries = null
|
|
||||||
|
|
||||||
libraryItems = libraryHelpers.getFilteredLibraryItems(libraryItems, payload.filterBy, req.user, this.rssFeedManager.feedsArray)
|
libraryItems = libraryHelpers.getFilteredLibraryItems(libraryItems, payload.filterBy, req.user, this.rssFeedManager.feedsArray)
|
||||||
payload.total = libraryItems.length
|
payload.total = libraryItems.length
|
||||||
|
|
||||||
|
// Determining if we are filtering titles by a series, and if so, which series
|
||||||
|
filterSeries = (mediaIsBook && payload.filterBy.startsWith('series.')) ? libraryHelpers.decode(payload.filterBy.replace('series.', '')) : null
|
||||||
|
if (filterSeries === 'No Series') filterSeries = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Step 2 - If selected, collapse library items by the series they belong to.
|
||||||
|
// If also filtering by series, will not collapse the filtered series as this would lead
|
||||||
|
// to series having a collapsed series that is just that series.
|
||||||
if (payload.collapseseries) {
|
if (payload.collapseseries) {
|
||||||
libraryItems = libraryHelpers.collapseBookSeries(libraryItems, this.db.series, filterSeries)
|
let collapsedItems = libraryHelpers.collapseBookSeries(libraryItems, this.db.series, filterSeries)
|
||||||
payload.total = libraryItems.length
|
|
||||||
|
if (!(collapsedItems.length == 1 && collapsedItems[0].collapsedSeries)) {
|
||||||
|
libraryItems = collapsedItems
|
||||||
|
payload.total = collapsedItems.length
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Step 3 - Sort the retrieved library items.
|
||||||
var sortArray = []
|
var sortArray = []
|
||||||
if (filterSeries) {
|
|
||||||
// Book media when filtering series will sort by the series sequence
|
// When on the series page, sort by sequence only
|
||||||
|
if (filterSeries && !payload.sortBy) {
|
||||||
sortArray.push({ asc: (li) => li.media.metadata.getSeries(filterSeries).sequence })
|
sortArray.push({ asc: (li) => li.media.metadata.getSeries(filterSeries).sequence })
|
||||||
}
|
}
|
||||||
|
|
||||||
if (payload.sortBy) {
|
if (payload.sortBy) {
|
||||||
var sortKey = payload.sortBy
|
|
||||||
|
|
||||||
// old sort key TODO: should be mutated in dbMigration
|
// old sort key TODO: should be mutated in dbMigration
|
||||||
|
var sortKey = payload.sortBy
|
||||||
if (sortKey.startsWith('book.')) {
|
if (sortKey.startsWith('book.')) {
|
||||||
sortKey = sortKey.replace('book.', 'media.metadata.')
|
sortKey = sortKey.replace('book.', 'media.metadata.')
|
||||||
}
|
}
|
||||||
@ -241,11 +249,11 @@ class LibraryController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort the items
|
|
||||||
if (sortArray.length) {
|
if (sortArray.length) {
|
||||||
libraryItems = naturalSort(libraryItems).by(sortArray)
|
libraryItems = naturalSort(libraryItems).by(sortArray)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Step 4 - Transform the items to pass to the client side
|
||||||
payload.results = libraryItems.map(li => {
|
payload.results = libraryItems.map(li => {
|
||||||
let json = payload.minified ? li.toJSONMinified() : li.toJSON()
|
let json = payload.minified ? li.toJSONMinified() : li.toJSON()
|
||||||
|
|
||||||
@ -257,7 +265,31 @@ class LibraryController {
|
|||||||
libraryItemIds: li.collapsedSeries.books.map(b => b.id),
|
libraryItemIds: li.collapsedSeries.books.map(b => b.id),
|
||||||
numBooks: li.collapsedSeries.books.length
|
numBooks: li.collapsedSeries.books.length
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If collapsing by series and filtering by a series, generate the list of sequences the collapsed
|
||||||
|
// series represents in the filtered series
|
||||||
|
if (filterSeries) {
|
||||||
|
json.collapsedSeries.seriesSequenceList = li.collapsedSeries.books
|
||||||
|
.map(b => parseFloat(b.filterSeriesSequence))
|
||||||
|
.filter(s => s)
|
||||||
|
.sort((a, b) => a - b)
|
||||||
|
.reduce((ranges, currentSequence) => {
|
||||||
|
let lastRange = ranges.at(-1)
|
||||||
|
|
||||||
|
if (lastRange && ((lastRange.end + 1) == currentSequence)) {
|
||||||
|
lastRange.end = currentSequence
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ranges.push({ start: currentSequence, end: currentSequence })
|
||||||
|
}
|
||||||
|
|
||||||
|
return ranges
|
||||||
|
}, [])
|
||||||
|
.map(r => r.start == r.end ? r.start : `${r.start}-${r.end}`)
|
||||||
|
.join(', ')
|
||||||
|
}
|
||||||
} else if (filterSeries) {
|
} else if (filterSeries) {
|
||||||
|
// If filtering by series, make sure to include the series metadata
|
||||||
json.media.metadata.series = li.media.metadata.getSeries(filterSeries)
|
json.media.metadata.series = li.media.metadata.getSeries(filterSeries)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -297,7 +329,7 @@ class LibraryController {
|
|||||||
minified: req.query.minified === '1'
|
minified: req.query.minified === '1'
|
||||||
}
|
}
|
||||||
|
|
||||||
var series = libraryHelpers.getSeriesFromBooks(libraryItems, this.db.series, payload.filterBy, req.user, payload.minified)
|
var series = libraryHelpers.getSeriesFromBooks(libraryItems, this.db.series, null, payload.filterBy, req.user, payload.minified)
|
||||||
|
|
||||||
const direction = payload.sortDesc ? 'desc' : 'asc'
|
const direction = payload.sortDesc ? 'desc' : 'asc'
|
||||||
series = naturalSort(series).by([
|
series = naturalSort(series).by([
|
||||||
|
@ -153,7 +153,7 @@ module.exports = {
|
|||||||
return data
|
return data
|
||||||
},
|
},
|
||||||
|
|
||||||
getSeriesFromBooks(books, allSeries, filterBy, user, minified = false) {
|
getSeriesFromBooks(books, allSeries, filterSeries, filterBy, user, minified = false) {
|
||||||
const _series = {}
|
const _series = {}
|
||||||
const seriesToFilterOut = {}
|
const seriesToFilterOut = {}
|
||||||
books.forEach((libraryItem) => {
|
books.forEach((libraryItem) => {
|
||||||
@ -179,6 +179,9 @@ module.exports = {
|
|||||||
|
|
||||||
const abJson = minified ? libraryItem.toJSONMinified() : libraryItem.toJSONExpanded()
|
const abJson = minified ? libraryItem.toJSONMinified() : libraryItem.toJSONExpanded()
|
||||||
abJson.sequence = bookSeriesObj.sequence
|
abJson.sequence = bookSeriesObj.sequence
|
||||||
|
if (filterSeries) {
|
||||||
|
abJson.filterSeriesSequence = libraryItem.media.metadata.getSeries(filterSeries).sequence
|
||||||
|
}
|
||||||
if (!_series[bookSeriesObj.id]) {
|
if (!_series[bookSeriesObj.id]) {
|
||||||
_series[bookSeriesObj.id] = {
|
_series[bookSeriesObj.id] = {
|
||||||
id: bookSeriesObj.id,
|
id: bookSeriesObj.id,
|
||||||
@ -285,7 +288,7 @@ module.exports = {
|
|||||||
// Get series from the library items. If this list is being collapsed after filtering for a series,
|
// Get series from the library items. If this list is being collapsed after filtering for a series,
|
||||||
// don't collapse that series, only books that are in other series.
|
// don't collapse that series, only books that are in other series.
|
||||||
var seriesObjects = this
|
var seriesObjects = this
|
||||||
.getSeriesFromBooks(libraryItems, series, null, null, true)
|
.getSeriesFromBooks(libraryItems, series, filterSeries, null, null, true)
|
||||||
.filter(s => s.id != filterSeries)
|
.filter(s => s.id != filterSeries)
|
||||||
|
|
||||||
var filteredLibraryItems = []
|
var filteredLibraryItems = []
|
||||||
|
Loading…
x
Reference in New Issue
Block a user