Podcast home page shelves for currently listening episodes, newest episodes. Podcast episode card

This commit is contained in:
advplyr 2022-04-22 19:31:11 -05:00
parent 84fb7ce8b3
commit 5d12cc3f23
10 changed files with 164 additions and 100 deletions

View File

@ -7,6 +7,11 @@
<cards-lazy-book-card :key="entity.id" :ref="`shelf-book-${entity.id}`" :index="index" :width="bookCoverWidth" :height="bookCoverHeight" :book-cover-aspect-ratio="bookCoverAspectRatio" :book-mount="entity" class="relative mx-2" @hook:updated="updatedBookCard" @select="selectItem" @edit="editBook" /> <cards-lazy-book-card :key="entity.id" :ref="`shelf-book-${entity.id}`" :index="index" :width="bookCoverWidth" :height="bookCoverHeight" :book-cover-aspect-ratio="bookCoverAspectRatio" :book-mount="entity" class="relative mx-2" @hook:updated="updatedBookCard" @select="selectItem" @edit="editBook" />
</template> </template>
</div> </div>
<div v-if="shelf.type === 'episode'" class="flex items-center">
<template v-for="(entity, index) in shelf.entities">
<cards-lazy-book-card :key="entity.recentEpisode.id" :ref="`shelf-episode-${entity.recentEpisode.id}`" :index="index" :width="bookCoverWidth" :height="bookCoverHeight" :book-cover-aspect-ratio="bookCoverAspectRatio" :book-mount="entity" class="relative mx-2" @hook:updated="updatedBookCard" @select="selectItem" @edit="editEpisode" />
</template>
</div>
<div v-if="shelf.type === 'series'" class="flex items-center"> <div v-if="shelf.type === 'series'" class="flex items-center">
<template v-for="entity in shelf.entities"> <template v-for="entity in shelf.entities">
<cards-lazy-series-card :key="entity.name" :series-mount="entity" :height="bookCoverHeight" :width="bookCoverWidth * 2" :book-cover-aspect-ratio="bookCoverAspectRatio" class="relative mx-2" @hook:updated="updatedBookCard" /> <cards-lazy-series-card :key="entity.name" :series-mount="entity" :height="bookCoverHeight" :width="bookCoverWidth * 2" :book-cover-aspect-ratio="bookCoverAspectRatio" class="relative mx-2" @hook:updated="updatedBookCard" />
@ -70,11 +75,6 @@ export default {
selectedAuthor: null selectedAuthor: null
} }
}, },
watch: {
isSelectionMode(newVal) {
this.updateSelectionMode(newVal)
}
},
computed: { computed: {
bookCoverHeight() { bookCoverHeight() {
return this.bookCoverWidth * this.bookCoverAspectRatio return this.bookCoverWidth * this.bookCoverAspectRatio
@ -103,9 +103,14 @@ export default {
this.$store.commit('setBookshelfBookIds', bookIds) this.$store.commit('setBookshelfBookIds', bookIds)
this.$store.commit('showEditModal', audiobook) this.$store.commit('showEditModal', audiobook)
}, },
editEpisode({ libraryItem, episode }) {
this.$store.commit('setSelectedLibraryItem', libraryItem)
this.$store.commit('globals/setSelectedEpisode', episode)
this.$store.commit('globals/setShowEditPodcastEpisodeModal', true)
},
updateSelectionMode(val) { updateSelectionMode(val) {
var selectedLibraryItems = this.$store.state.selectedLibraryItems var selectedLibraryItems = this.$store.state.selectedLibraryItems
if (this.shelf.type === 'book') { if (this.shelf.type === 'book' || this.shelf.type === 'podcast') {
this.shelf.entities.forEach((ent) => { this.shelf.entities.forEach((ent) => {
var component = this.$refs[`shelf-book-${ent.id}`] var component = this.$refs[`shelf-book-${ent.id}`]
if (!component || !component.length) return if (!component || !component.length) return
@ -113,10 +118,24 @@ export default {
component.setSelectionMode(val) component.setSelectionMode(val)
component.selected = selectedLibraryItems.includes(ent.id) component.selected = selectedLibraryItems.includes(ent.id)
}) })
} else if (this.shelf.type === 'episode') {
this.shelf.entities.forEach((ent) => {
var component = this.$refs[`shelf-episode-${ent.recentEpisode.id}`]
if (!component || !component.length) return
component = component[0]
component.setSelectionMode(val)
component.selected = selectedLibraryItems.includes(ent.id)
})
} }
}, },
selectItem(libraryItem) { selectItem(libraryItem) {
this.$store.commit('toggleLibraryItemSelected', libraryItem.id) this.$store.commit('toggleLibraryItemSelected', libraryItem.id)
this.$nextTick(() => {
this.$eventBus.$emit('item-selected', libraryItem)
})
},
itemSelectedEvt() {
this.updateSelectionMode(this.isSelectionMode)
}, },
scrolled() { scrolled() {
clearTimeout(this.scrollTimer) clearTimeout(this.scrollTimer)
@ -160,6 +179,12 @@ export default {
this.canScrollLeft = false this.canScrollLeft = false
} }
} }
},
mounted() {
this.$eventBus.$on('item-selected', this.itemSelectedEvt)
},
beforeDestroy() {
this.$eventBus.$off('item-selected', this.itemSelectedEvt)
} }
} }
</script> </script>

View File

@ -35,8 +35,8 @@
</div> </div>
</div> </div>
<!-- No progress shown for collapsed series in library and podcasts --> <!-- No progress shown for collapsed series in library and podcasts (unless showing podcast episode) -->
<div v-if="!booksInSeries && !isPodcast" 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>
<!-- 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)" 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)" class="w-full h-full absolute top-0 left-0 z-10 bg-black rounded hidden md:block" :class="overlayWrapperClasslist">
@ -60,7 +60,7 @@
<span class="material-icons" :class="selected ? 'text-yellow-400' : ''" :style="{ fontSize: 1.25 * sizeMultiplier + 'rem' }">{{ selected ? 'radio_button_checked' : 'radio_button_unchecked' }}</span> <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>
<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"> <div ref="moreIcon" v-show="!isSelectionMode && !recentEpisode" 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> <span class="material-icons" :style="{ fontSize: 1.2 * sizeMultiplier + 'rem' }">more_vert</span>
</div> </div>
</div> </div>
@ -77,13 +77,18 @@
</div> </div>
</ui-tooltip> </ui-tooltip>
<!-- Volume number --> <!-- Series sequence -->
<div v-if="seriesSequence && showSequence && !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 && showSequence && !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 # -->
<div v-if="recentEpisodeNumber && !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' }">Episode #{{ recentEpisodeNumber }}</p>
</div>
<!-- Podcast Num Episodes --> <!-- Podcast Num Episodes -->
<div v-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>
@ -190,6 +195,17 @@ export default {
processingBatch() { processingBatch() {
return this.store.state.processingBatch return this.store.state.processingBatch
}, },
recentEpisode() {
// Only added to item when getting currently listening podcasts
return this._libraryItem.recentEpisode
},
recentEpisodeNumber() {
if (!this.recentEpisode) return null
if (this.recentEpisode.episode) {
return this.recentEpisode.episode.replace(/^#/, '')
}
return this.recentEpisode.index
},
collapsedSeries() { collapsedSeries() {
// Only added to item object when collapseSeries is enabled // Only added to item object when collapseSeries is enabled
return this._libraryItem.collapsedSeries return this._libraryItem.collapsedSeries
@ -240,7 +256,13 @@ export default {
if (this.orderBy === 'size') return 'Size: ' + this.$bytesPretty(this._libraryItem.size) if (this.orderBy === 'size') return 'Size: ' + this.$bytesPretty(this._libraryItem.size)
return null return null
}, },
episodeProgress() {
// Only used on home page currently listening podcast shelf
if (!this.recentEpisode) return null
return this.store.getters['user/getUserMediaProgress'](this.libraryItemId, this.recentEpisode.id)
},
userProgress() { userProgress() {
if (this.episodeProgress) return this.episodeProgress
return this.store.getters['user/getUserMediaProgress'](this.libraryItemId) return this.store.getters['user/getUserMediaProgress'](this.libraryItemId)
}, },
userProgressPercent() { userProgressPercent() {
@ -259,7 +281,7 @@ export default {
return !this.isSelectionMode && this.showExperimentalFeatures && !this.showPlayButton && this.hasEbook return !this.isSelectionMode && this.showExperimentalFeatures && !this.showPlayButton && this.hasEbook
}, },
showPlayButton() { showPlayButton() {
return !this.isSelectionMode && !this.isMissing && !this.isInvalid && this.numTracks && !this.isStreaming return !this.isSelectionMode && !this.isMissing && !this.isInvalid && !this.isStreaming && (this.numTracks || this.recentEpisode)
}, },
showSmallEBookIcon() { showSmallEBookIcon() {
return !this.isSelectionMode && this.showExperimentalFeatures && this.hasEbook return !this.isSelectionMode && this.showExperimentalFeatures && this.hasEbook
@ -406,6 +428,9 @@ export default {
} }
}, },
editClick() { editClick() {
if (this.recentEpisode) {
return this.$emit('edit', { libraryItem: this.libraryItem, episode: this.recentEpisode })
}
this.$emit('edit', this.libraryItem) this.$emit('edit', this.libraryItem)
}, },
toggleFinished() { toggleFinished() {
@ -529,7 +554,8 @@ export default {
play() { play() {
var eventBus = this.$eventBus || this.$nuxt.$eventBus var eventBus = this.$eventBus || this.$nuxt.$eventBus
eventBus.$emit('play-item', { eventBus.$emit('play-item', {
libraryItemId: this.libraryItemId libraryItemId: this.libraryItemId,
episodeId: this.recentEpisode ? this.recentEpisode.id : null
}) })
}, },
mouseover() { mouseover() {

View File

@ -35,17 +35,6 @@
<script> <script>
export default { export default {
props: {
value: Boolean,
libraryItem: {
type: Object,
default: () => {}
},
episode: {
type: Object,
default: () => {}
}
},
data() { data() {
return { return {
processing: false, processing: false,
@ -72,12 +61,18 @@ export default {
computed: { computed: {
show: { show: {
get() { get() {
return this.value return this.$store.state.globals.showEditPodcastEpisode
}, },
set(val) { set(val) {
this.$emit('input', val) this.$store.commit('globals/setShowEditPodcastEpisodeModal', val)
} }
}, },
libraryItem() {
return this.$store.state.selectedLibraryItem
},
episode() {
return this.$store.state.globals.selectedEpisode
},
episodeId() { episodeId() {
return this.episode ? this.episode.id : null return this.episode ? this.episode.id : null
}, },

View File

@ -16,8 +16,6 @@
</template> </template>
</transition-group> </transition-group>
</draggable> </draggable>
<modals-podcast-edit-episode v-model="showEditEpisodeModal" :library-item="libraryItem" :episode="selectedEpisode" />
</div> </div>
</template> </template>
@ -40,8 +38,6 @@ export default {
sortDesc: true, sortDesc: true,
drag: false, drag: false,
episodesCopy: [], episodesCopy: [],
selectedEpisode: null,
showEditEpisodeModal: false,
orderChanged: false, orderChanged: false,
savingOrder: false savingOrder: false
} }
@ -97,8 +93,9 @@ export default {
return false return false
}, },
editEpisode(episode) { editEpisode(episode) {
this.selectedEpisode = episode this.$store.commit('setSelectedLibraryItem', this.libraryItem)
this.showEditEpisodeModal = true this.$store.commit('globals/setSelectedEpisode', episode)
this.$store.commit('globals/setShowEditPodcastEpisodeModal', true)
}, },
draggableUpdate() { draggableUpdate() {
this.orderChanged = this.checkHasOrderChanged() this.orderChanged = this.checkHasOrderChanged()

View File

@ -10,6 +10,7 @@
<modals-user-collections-modal /> <modals-user-collections-modal />
<modals-edit-collection-modal /> <modals-edit-collection-modal />
<modals-bookshelf-texture-modal /> <modals-bookshelf-texture-modal />
<modals-podcast-edit-episode />
<readers-reader /> <readers-reader />
</div> </div>
</template> </template>

View File

@ -5,6 +5,8 @@ export const state = () => ({
showBatchUserCollectionModal: false, showBatchUserCollectionModal: false,
showUserCollectionsModal: false, showUserCollectionsModal: false,
showEditCollectionModal: false, showEditCollectionModal: false,
showEditPodcastEpisode: false,
selectedEpisode: null,
selectedCollection: null, selectedCollection: null,
showBookshelfTextureModal: false, showBookshelfTextureModal: false,
isCasting: false, // Actively casting isCasting: false, // Actively casting
@ -46,10 +48,16 @@ export const mutations = {
setShowEditCollectionModal(state, val) { setShowEditCollectionModal(state, val) {
state.showEditCollectionModal = val state.showEditCollectionModal = val
}, },
setShowEditPodcastEpisodeModal(state, val) {
state.showEditPodcastEpisode = val
},
setEditCollection(state, collection) { setEditCollection(state, collection) {
state.selectedCollection = collection state.selectedCollection = collection
state.showEditCollectionModal = true state.showEditCollectionModal = true
}, },
setSelectedEpisode(state, episode) {
state.selectedEpisode = episode
},
setShowBookshelfTextureModal(state, val) { setShowBookshelfTextureModal(state, val) {
state.showBookshelfTextureModal = val state.showBookshelfTextureModal = val
}, },

View File

@ -300,12 +300,12 @@ class LibraryController {
var limitPerShelf = req.query.limit && !isNaN(req.query.limit) ? Number(req.query.limit) : 12 var limitPerShelf = req.query.limit && !isNaN(req.query.limit) ? Number(req.query.limit) : 12
var minified = req.query.minified === '1' var minified = req.query.minified === '1'
var itemsWithUserProgress = libraryHelpers.getItemsWithUserProgress(req.user, libraryItems) var itemsWithUserProgress = libraryHelpers.getMediaProgressWithItems(req.user, libraryItems)
var categories = [ var categories = [
{ {
id: 'continue-listening', id: 'continue-listening',
label: 'Continue Listening', label: 'Continue Listening',
type: req.library.mediaType, type: isPodcastLibrary ? 'episode' : req.library.mediaType,
entities: libraryHelpers.getItemsMostRecentlyListened(itemsWithUserProgress, limitPerShelf, minified) entities: libraryHelpers.getItemsMostRecentlyListened(itemsWithUserProgress, limitPerShelf, minified)
}, },
{ {
@ -317,7 +317,7 @@ class LibraryController {
{ {
id: 'listen-again', id: 'listen-again',
label: 'Listen Again', label: 'Listen Again',
type: req.library.mediaType, type: isPodcastLibrary ? 'episode' : req.library.mediaType,
entities: libraryHelpers.getItemsMostRecentlyFinished(itemsWithUserProgress, limitPerShelf, minified) entities: libraryHelpers.getItemsMostRecentlyFinished(itemsWithUserProgress, limitPerShelf, minified)
} }
].filter(cats => { // Remove categories with no items ].filter(cats => { // Remove categories with no items
@ -372,57 +372,17 @@ class LibraryController {
entities: authors entities: authors
}) })
} }
} } else {
var episodesRecentlyAdded = libraryHelpers.getEpisodesRecentlyAdded(libraryItems, limitPerShelf, minified)
res.json(categories) if (episodesRecentlyAdded.length) {
} categories.splice(1, 0, {
id: 'episodes-recently-added',
// LEGACY label: 'Newest Episodes',
// api/libraries/:id/books/categories type: 'episode',
async getLibraryCategories(req, res) { entities: episodesRecentlyAdded
var library = req.library })
var books = this.db.audiobooks.filter(ab => ab.libraryId === library.id)
var limitPerShelf = req.query.limit && !isNaN(req.query.limit) ? Number(req.query.limit) : 12
var minified = req.query.minified === '1'
var booksWithUserAb = libraryHelpers.getItemsWithUserProgress(req.user, books)
var series = libraryHelpers.getSeriesFromBooks(books, minified)
var seriesWithUserAb = libraryHelpers.getSeriesWithProgressFromBooks(req.user, books)
var categories = [
{
id: 'continue-reading',
label: 'Continue Reading',
type: 'books',
entities: libraryHelpers.getBooksMostRecentlyRead(booksWithUserAb, limitPerShelf, minified)
},
{
id: 'continue-series',
label: 'Continue Series',
type: 'books',
entities: libraryHelpers.getBooksNextInSeries(seriesWithUserAb, limitPerShelf, minified)
},
{
id: 'recently-added',
label: 'Recently Added',
type: 'books',
entities: libraryHelpers.getBooksMostRecentlyAdded(books, limitPerShelf, minified)
},
{
id: 'read-again',
label: 'Read Again',
type: 'books',
entities: libraryHelpers.getBooksMostRecentlyFinished(booksWithUserAb, limitPerShelf, minified)
},
{
id: 'recent-series',
label: 'Recent Series',
type: 'series',
entities: libraryHelpers.getSeriesMostRecentlyAdded(series, limitPerShelf)
} }
].filter(cats => { // Remove categories with no items }
return cats.entities.length
})
res.json(categories) res.json(categories)
} }

View File

@ -257,5 +257,9 @@ class Podcast {
if (!episode) return 0 if (!episode) return 0
return episode.duration return episode.duration
} }
getEpisode(episodeId) {
return this.episodes.find(ep => ep.id == episodeId)
}
} }
module.exports = Podcast module.exports = Podcast

View File

@ -251,6 +251,11 @@ class User {
}) })
} }
getAllMediaProgressForLibraryItem(libraryItemId) {
if (!this.mediaProgress) return []
return this.mediaProgress.filter(li => li.libraryItemId === libraryItemId)
}
createUpdateMediaProgress(libraryItem, updatePayload, episodeId = null) { createUpdateMediaProgress(libraryItem, updatePayload, episodeId = null) {
var itemProgress = this.mediaProgress.find(li => { var itemProgress = this.mediaProgress.find(li => {
if (episodeId && li.episodeId !== episodeId) return false if (episodeId && li.episodeId !== episodeId) return false

View File

@ -1,4 +1,5 @@
const { sort, createNewSortInstance } = require('fast-sort') const { sort, createNewSortInstance } = require('fast-sort')
const Logger = require('../Logger')
const naturalSort = createNewSortInstance({ const naturalSort = createNewSortInstance({
comparer: new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' }).compare comparer: new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' }).compare
}) })
@ -172,14 +173,28 @@ module.exports = {
}) })
}, },
getItemsWithUserProgress(user, libraryItems) { getMediaProgressWithItems(user, libraryItems) {
return libraryItems.map(li => { var mediaProgress = []
var itemProgress = user.getMediaProgress(li.id) libraryItems.forEach(li => {
return { var itemProgress = user.getAllMediaProgressForLibraryItem(li.id).map(mp => {
userProgress: itemProgress ? itemProgress.toJSON() : null, var episode = null
libraryItem: li if (mp.episodeId) {
} episode = li.media.getEpisode(mp.episodeId)
}).filter(b => !!b.userProgress) if (!episode) {
// Episode not found for library item
return null
}
}
return {
userProgress: mp.toJSON(),
libraryItem: li,
episode
}
}).filter(mp => !!mp)
mediaProgress = mediaProgress.concat(itemProgress)
})
return mediaProgress
}, },
getItemsMostRecentlyListened(itemsWithUserProgress, limit, minified = false) { getItemsMostRecentlyListened(itemsWithUserProgress, limit, minified = false) {
@ -187,7 +202,13 @@ module.exports = {
itemsInProgress.sort((a, b) => { itemsInProgress.sort((a, b) => {
return b.userProgress.lastUpdate - a.userProgress.lastUpdate return b.userProgress.lastUpdate - a.userProgress.lastUpdate
}) })
return itemsInProgress.map(b => minified ? b.libraryItem.toJSONMinified() : b.libraryItem.toJSONExpanded()).slice(0, limit) return itemsInProgress.map(b => {
var libjson = minified ? b.libraryItem.toJSONMinified() : b.libraryItem.toJSONExpanded()
if (b.episode) {
libjson.recentEpisode = b.episode
}
return libjson
}).slice(0, limit)
}, },
getBooksNextInSeries(seriesWithUserAb, limit, minified = false) { getBooksNextInSeries(seriesWithUserAb, limit, minified = false) {
@ -202,17 +223,39 @@ module.exports = {
return booksNextInSeries.sort((a, b) => { return b.DateLastReadSeries - a.DateLastReadSeries }).map(b => minified ? b.book.toJSONMinified() : b.book.toJSONExpanded()).slice(0, limit) return booksNextInSeries.sort((a, b) => { return b.DateLastReadSeries - a.DateLastReadSeries }).map(b => minified ? b.book.toJSONMinified() : b.book.toJSONExpanded()).slice(0, limit)
}, },
getItemsMostRecentlyAdded(libraryItems, limit, minified = false) {
var itemsSortedByAddedAt = sort(libraryItems).desc(li => li.addedAt)
return itemsSortedByAddedAt.map(b => minified ? b.toJSONMinified() : b.toJSONExpanded()).slice(0, limit)
},
getItemsMostRecentlyFinished(itemsWithUserProgress, limit, minified = false) { getItemsMostRecentlyFinished(itemsWithUserProgress, limit, minified = false) {
var itemsFinished = itemsWithUserProgress.filter((data) => data.userProgress && data.userProgress.isFinished) var itemsFinished = itemsWithUserProgress.filter((data) => data.userProgress && data.userProgress.isFinished)
itemsFinished.sort((a, b) => { itemsFinished.sort((a, b) => {
return b.userProgress.finishedAt - a.userProgress.finishedAt return b.userProgress.finishedAt - a.userProgress.finishedAt
}) })
return itemsFinished.map(i => minified ? i.libraryItem.toJSONMinified() : i.libraryItem.toJSONExpanded()).slice(0, limit) return itemsFinished.map(i => {
var libjson = minified ? i.libraryItem.toJSONMinified() : i.libraryItem.toJSONExpanded()
if (i.episode) {
libjson.recentEpisode = i.episode
}
return libjson
}).slice(0, limit)
},
getItemsMostRecentlyAdded(libraryItems, limit, minified = false) {
var itemsSortedByAddedAt = sort(libraryItems).desc(li => li.addedAt)
return itemsSortedByAddedAt.map(b => minified ? b.toJSONMinified() : b.toJSONExpanded()).slice(0, limit)
},
getEpisodesRecentlyAdded(libraryItems, limit, minified = false) {
var libraryItemsWithEpisode = []
libraryItems.forEach((li) => {
if (li.mediaType !== 'podcast' || !li.media.hasMediaEntities) return
var libjson = minified ? li.toJSONMinified() : li.toJSONExpanded()
var episodes = sort(li.media.episodes || []).desc(ep => ep.addedAt)
episodes.forEach((ep) => {
var lie = { ...libjson }
lie.recentEpisode = ep
libraryItemsWithEpisode.push(lie)
})
})
libraryItemsWithEpisode = sort(libraryItemsWithEpisode).desc(lie => lie.recentEpisode.addedAt)
return libraryItemsWithEpisode.slice(0, limit)
}, },
getSeriesMostRecentlyAdded(series, limit) { getSeriesMostRecentlyAdded(series, limit) {