Add collapse series, add filter by series include sequence and sort, show number of episodes on podcast card

This commit is contained in:
advplyr 2022-04-09 19:44:46 -05:00
parent 2a386ca2a9
commit 174dac8fd4
7 changed files with 80 additions and 15 deletions

View File

@ -378,8 +378,6 @@ export default {
let searchParams = new URLSearchParams()
if (this.page === 'series-books') {
searchParams.set('filter', `series.${this.$encode(this.seriesId)}`)
searchParams.set('sort', 'book.volumeNumber')
searchParams.set('desc', 0)
} else {
if (this.filterBy && this.filterBy !== 'all') {
searchParams.set('filter', this.filterBy)

View File

@ -35,8 +35,8 @@
</div>
</div>
<!-- No progress shown for collapsed series in library -->
<div v-if="!booksInSeries" 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>
<!-- No progress shown for collapsed series in library and podcasts -->
<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>
<!-- 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">
@ -78,8 +78,13 @@
</ui-tooltip>
<!-- Volume number -->
<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 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>
</div>
<!-- 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' }">
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">{{ numEpisodes }}</p>
</div>
</div>
</template>
@ -100,7 +105,7 @@ export default {
default: 192
},
bookCoverAspectRatio: Number,
showVolumeNumber: Boolean,
showSequence: Boolean,
bookshelfView: Number,
bookMount: {
// Book can be passed as prop or set with setEntity()
@ -162,8 +167,12 @@ export default {
return this._libraryItem.id
},
series() {
// Only included when filtering by series or collapse series
return this.mediaMetadata.series
},
seriesSequence() {
return this.series ? this.series.sequence : null
},
libraryId() {
return this._libraryItem.libraryId
},
@ -174,12 +183,20 @@ export default {
if (this.media.tracks) return this.media.tracks.length
return this.media.numTracks || 0 // toJSONMinified
},
numEpisodes() {
if (!this.isPodcast) return 0
return this.media.numEpisodes || 0
},
processingBatch() {
return this.store.state.processingBatch
},
collapsedSeries() {
// Only added to item object when collapseSeries is enabled
return this._libraryItem.collapsedSeries
},
booksInSeries() {
// Only added to item object when collapseSeries is enabled
return this._libraryItem.booksInSeries
return this.collapsedSeries ? this.collapsedSeries.numBooks : 0
},
hasCover() {
return !!this.media.coverPath
@ -204,9 +221,6 @@ export default {
authorLF() {
return this.mediaMetadata.authorNameLF
},
volumeNumber() {
return this.mediaMetadata.volumeNumber || null
},
displayTitle() {
if (this.orderBy === 'media.metadata.title' && this.sortingIgnorePrefix) {
return this.mediaMetadata.titleIgnorePrefix
@ -392,7 +406,7 @@ export default {
} else {
var router = this.$router || this.$nuxt.$router
if (router) {
if (this.booksInSeries) router.push(`/library/${this.libraryId}/series/${this.$encode(this.series)}`)
if (this.collapsedSeries) router.push(`/library/${this.libraryId}/series/${this.collapsedSeries.id}`)
else router.push(`/item/${this.libraryItemId}`)
}
}

View File

@ -54,7 +54,7 @@ export default {
bookCoverAspectRatio: this.bookCoverAspectRatio,
bookshelfView: this.bookshelfView
}
if (this.entityName === 'series-books') props.showVolumeNumber = true
if (this.entityName === 'series-books') props.showSequence = true
if (this.entityName === 'books') {
props.filterBy = this.filterBy
props.orderBy = this.orderBy

View File

@ -152,7 +152,11 @@ class LibraryController {
collapseseries: req.query.collapseseries === '1'
}
var filterSeries = null
if (payload.filterBy) {
// If filtering by series, will include seriesName and seriesSequence on media metadata
filterSeries = (payload.mediaType == 'book' && payload.filterBy.startsWith('series.')) ? libraryHelpers.decode(payload.filterBy.replace('series.', '')) : null
libraryItems = libraryHelpers.getFilteredLibraryItems(libraryItems, payload.filterBy, req.user)
payload.total = libraryItems.length
}
@ -180,7 +184,21 @@ class LibraryController {
}
// TODO: Potentially implement collapse series again
libraryItems = libraryItems.map(ab => payload.minified ? ab.toJSONMinified() : ab.toJSON())
if (payload.collapseseries) {
libraryItems = libraryHelpers.collapseBookSeries(libraryItems)
payload.total = libraryItems.length
} else if (filterSeries) {
// Book media when filtering series will include series object on media metadata
libraryItems = libraryItems.map(li => {
var series = li.media.metadata.getSeries(filterSeries)
var liJson = payload.minified ? li.toJSONMinified() : li.toJSON()
liJson.media.metadata.series = series
return liJson
})
libraryItems = naturalSort(libraryItems).asc(li => li.media.metadata.series.sequence)
} else {
libraryItems = libraryItems.map(li => payload.minified ? li.toJSONMinified() : li.toJSON())
}
if (payload.limit) {
var startIndex = payload.page * payload.limit

View File

@ -57,7 +57,7 @@ class Podcast {
metadata: this.metadata.toJSON(),
coverPath: this.coverPath,
tags: [...this.tags],
episodes: this.episodes.map(e => e.toJSON()),
numEpisodes: this.episodes.length,
autoDownloadEpisodes: this.autoDownloadEpisodes,
lastEpisodeCheck: this.lastEpisodeCheck,
size: this.size

View File

@ -151,6 +151,12 @@ class BookMetadata {
hasNarrator(narratorName) {
return this.narrators.includes(narratorName)
}
getSeries(seriesId) {
return this.series.find(se => se.id == seriesId)
}
getFirstSeries() {
return this.series.length ? this.series[0] : null
}
getSeriesSequence(seriesId) {
var series = this.series.find(se => se.id == seriesId)
if (!series) return null

View File

@ -262,4 +262,33 @@ module.exports = {
})
return totalSize
},
collapseBookSeries(libraryItems) {
var seriesObjects = this.getSeriesFromBooks(libraryItems, true)
var seriesToUse = {}
var libraryItemIdsToHide = []
seriesObjects.forEach((series) => {
series.firstBook = series.books.find(b => !seriesToUse[b.id]) // Find first book not already used
if (series.firstBook) {
seriesToUse[series.firstBook.id] = series
libraryItemIdsToHide = libraryItemIdsToHide.concat(series.books.filter(b => !seriesToUse[b.id]).map(b => b.id))
}
})
return libraryItems.map((li) => {
if (li.mediaType != 'book') return
var libraryItemJson = li.toJSONMinified()
if (libraryItemIdsToHide.includes(li.id)) {
return null
}
if (seriesToUse[li.id]) {
libraryItemJson.collapsedSeries = {
id: seriesToUse[li.id].id,
name: seriesToUse[li.id].name,
numBooks: seriesToUse[li.id].books.length
}
}
return libraryItemJson
}).filter(li => li)
}
}