mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	Add collapse series, add filter by series include sequence and sort, show number of episodes on podcast card
This commit is contained in:
		
							parent
							
								
									2a386ca2a9
								
							
						
					
					
						commit
						174dac8fd4
					
				@ -378,8 +378,6 @@ export default {
 | 
				
			|||||||
      let searchParams = new URLSearchParams()
 | 
					      let searchParams = new URLSearchParams()
 | 
				
			||||||
      if (this.page === 'series-books') {
 | 
					      if (this.page === 'series-books') {
 | 
				
			||||||
        searchParams.set('filter', `series.${this.$encode(this.seriesId)}`)
 | 
					        searchParams.set('filter', `series.${this.$encode(this.seriesId)}`)
 | 
				
			||||||
        searchParams.set('sort', 'book.volumeNumber')
 | 
					 | 
				
			||||||
        searchParams.set('desc', 0)
 | 
					 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        if (this.filterBy && this.filterBy !== 'all') {
 | 
					        if (this.filterBy && this.filterBy !== 'all') {
 | 
				
			||||||
          searchParams.set('filter', this.filterBy)
 | 
					          searchParams.set('filter', this.filterBy)
 | 
				
			||||||
 | 
				
			|||||||
@ -35,8 +35,8 @@
 | 
				
			|||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <!-- No progress shown for collapsed series in library -->
 | 
					    <!-- No progress shown for collapsed series in library and podcasts -->
 | 
				
			||||||
    <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>
 | 
					    <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 -->
 | 
					    <!-- 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">
 | 
				
			||||||
@ -78,8 +78,13 @@
 | 
				
			|||||||
    </ui-tooltip>
 | 
					    </ui-tooltip>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <!-- Volume number -->
 | 
					    <!-- 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` }">
 | 
					    <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' }">#{{ volumeNumber }}</p>
 | 
					      <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>
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
@ -100,7 +105,7 @@ export default {
 | 
				
			|||||||
      default: 192
 | 
					      default: 192
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    bookCoverAspectRatio: Number,
 | 
					    bookCoverAspectRatio: Number,
 | 
				
			||||||
    showVolumeNumber: Boolean,
 | 
					    showSequence: Boolean,
 | 
				
			||||||
    bookshelfView: Number,
 | 
					    bookshelfView: Number,
 | 
				
			||||||
    bookMount: {
 | 
					    bookMount: {
 | 
				
			||||||
      // Book can be passed as prop or set with setEntity()
 | 
					      // Book can be passed as prop or set with setEntity()
 | 
				
			||||||
@ -162,8 +167,12 @@ export default {
 | 
				
			|||||||
      return this._libraryItem.id
 | 
					      return this._libraryItem.id
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    series() {
 | 
					    series() {
 | 
				
			||||||
 | 
					      // Only included when filtering by series or collapse series
 | 
				
			||||||
      return this.mediaMetadata.series
 | 
					      return this.mediaMetadata.series
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    seriesSequence() {
 | 
				
			||||||
 | 
					      return this.series ? this.series.sequence : null
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    libraryId() {
 | 
					    libraryId() {
 | 
				
			||||||
      return this._libraryItem.libraryId
 | 
					      return this._libraryItem.libraryId
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
@ -174,12 +183,20 @@ export default {
 | 
				
			|||||||
      if (this.media.tracks) return this.media.tracks.length
 | 
					      if (this.media.tracks) return this.media.tracks.length
 | 
				
			||||||
      return this.media.numTracks || 0 // toJSONMinified
 | 
					      return this.media.numTracks || 0 // toJSONMinified
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    numEpisodes() {
 | 
				
			||||||
 | 
					      if (!this.isPodcast) return 0
 | 
				
			||||||
 | 
					      return this.media.numEpisodes || 0
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    processingBatch() {
 | 
					    processingBatch() {
 | 
				
			||||||
      return this.store.state.processingBatch
 | 
					      return this.store.state.processingBatch
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    collapsedSeries() {
 | 
				
			||||||
 | 
					      // Only added to item object when collapseSeries is enabled
 | 
				
			||||||
 | 
					      return this._libraryItem.collapsedSeries
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    booksInSeries() {
 | 
					    booksInSeries() {
 | 
				
			||||||
      // Only added to item object when collapseSeries is enabled
 | 
					      // Only added to item object when collapseSeries is enabled
 | 
				
			||||||
      return this._libraryItem.booksInSeries
 | 
					      return this.collapsedSeries ? this.collapsedSeries.numBooks : 0
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    hasCover() {
 | 
					    hasCover() {
 | 
				
			||||||
      return !!this.media.coverPath
 | 
					      return !!this.media.coverPath
 | 
				
			||||||
@ -204,9 +221,6 @@ export default {
 | 
				
			|||||||
    authorLF() {
 | 
					    authorLF() {
 | 
				
			||||||
      return this.mediaMetadata.authorNameLF
 | 
					      return this.mediaMetadata.authorNameLF
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    volumeNumber() {
 | 
					 | 
				
			||||||
      return this.mediaMetadata.volumeNumber || null
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    displayTitle() {
 | 
					    displayTitle() {
 | 
				
			||||||
      if (this.orderBy === 'media.metadata.title' && this.sortingIgnorePrefix) {
 | 
					      if (this.orderBy === 'media.metadata.title' && this.sortingIgnorePrefix) {
 | 
				
			||||||
        return this.mediaMetadata.titleIgnorePrefix
 | 
					        return this.mediaMetadata.titleIgnorePrefix
 | 
				
			||||||
@ -392,7 +406,7 @@ export default {
 | 
				
			|||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        var router = this.$router || this.$nuxt.$router
 | 
					        var router = this.$router || this.$nuxt.$router
 | 
				
			||||||
        if (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}`)
 | 
					          else router.push(`/item/${this.libraryItemId}`)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
				
			|||||||
@ -54,7 +54,7 @@ export default {
 | 
				
			|||||||
        bookCoverAspectRatio: this.bookCoverAspectRatio,
 | 
					        bookCoverAspectRatio: this.bookCoverAspectRatio,
 | 
				
			||||||
        bookshelfView: this.bookshelfView
 | 
					        bookshelfView: this.bookshelfView
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      if (this.entityName === 'series-books') props.showVolumeNumber = true
 | 
					      if (this.entityName === 'series-books') props.showSequence = true
 | 
				
			||||||
      if (this.entityName === 'books') {
 | 
					      if (this.entityName === 'books') {
 | 
				
			||||||
        props.filterBy = this.filterBy
 | 
					        props.filterBy = this.filterBy
 | 
				
			||||||
        props.orderBy = this.orderBy
 | 
					        props.orderBy = this.orderBy
 | 
				
			||||||
 | 
				
			|||||||
@ -152,7 +152,11 @@ class LibraryController {
 | 
				
			|||||||
      collapseseries: req.query.collapseseries === '1'
 | 
					      collapseseries: req.query.collapseseries === '1'
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var filterSeries = null
 | 
				
			||||||
    if (payload.filterBy) {
 | 
					    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)
 | 
					      libraryItems = libraryHelpers.getFilteredLibraryItems(libraryItems, payload.filterBy, req.user)
 | 
				
			||||||
      payload.total = libraryItems.length
 | 
					      payload.total = libraryItems.length
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -180,7 +184,21 @@ class LibraryController {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // TODO: Potentially implement collapse series again
 | 
					    // 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) {
 | 
					    if (payload.limit) {
 | 
				
			||||||
      var startIndex = payload.page * payload.limit
 | 
					      var startIndex = payload.page * payload.limit
 | 
				
			||||||
 | 
				
			|||||||
@ -57,7 +57,7 @@ class Podcast {
 | 
				
			|||||||
      metadata: this.metadata.toJSON(),
 | 
					      metadata: this.metadata.toJSON(),
 | 
				
			||||||
      coverPath: this.coverPath,
 | 
					      coverPath: this.coverPath,
 | 
				
			||||||
      tags: [...this.tags],
 | 
					      tags: [...this.tags],
 | 
				
			||||||
      episodes: this.episodes.map(e => e.toJSON()),
 | 
					      numEpisodes: this.episodes.length,
 | 
				
			||||||
      autoDownloadEpisodes: this.autoDownloadEpisodes,
 | 
					      autoDownloadEpisodes: this.autoDownloadEpisodes,
 | 
				
			||||||
      lastEpisodeCheck: this.lastEpisodeCheck,
 | 
					      lastEpisodeCheck: this.lastEpisodeCheck,
 | 
				
			||||||
      size: this.size
 | 
					      size: this.size
 | 
				
			||||||
 | 
				
			|||||||
@ -151,6 +151,12 @@ class BookMetadata {
 | 
				
			|||||||
  hasNarrator(narratorName) {
 | 
					  hasNarrator(narratorName) {
 | 
				
			||||||
    return this.narrators.includes(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) {
 | 
					  getSeriesSequence(seriesId) {
 | 
				
			||||||
    var series = this.series.find(se => se.id == seriesId)
 | 
					    var series = this.series.find(se => se.id == seriesId)
 | 
				
			||||||
    if (!series) return null
 | 
					    if (!series) return null
 | 
				
			||||||
 | 
				
			|||||||
@ -262,4 +262,33 @@ module.exports = {
 | 
				
			|||||||
    })
 | 
					    })
 | 
				
			||||||
    return totalSize
 | 
					    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)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user