mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	Add:Recommended book home page shelf
This commit is contained in:
		
							parent
							
								
									030c20b12e
								
							
						
					
					
						commit
						49279430fc
					
				@ -820,7 +820,6 @@ export default {
 | 
			
		||||
        return null
 | 
			
		||||
      })
 | 
			
		||||
      if (!libraryItem) return
 | 
			
		||||
      console.log('Got library itemn', libraryItem)
 | 
			
		||||
      this.store.commit('showEReader', libraryItem)
 | 
			
		||||
    },
 | 
			
		||||
    selectBtnClick(evt) {
 | 
			
		||||
 | 
			
		||||
@ -308,6 +308,7 @@
 | 
			
		||||
  "LabelPublishYear": "Jahr",
 | 
			
		||||
  "LabelRecentlyAdded": "Kürzlich hinzugefügt",
 | 
			
		||||
  "LabelRecentSeries": "Aktuelle Serien",
 | 
			
		||||
  "LabelRecommended": "Recommended",
 | 
			
		||||
  "LabelRegion": "Region",
 | 
			
		||||
  "LabelReleaseDate": "Veröffentlichungsdatum",
 | 
			
		||||
  "LabelRemoveCover": "Lösche Titelbild",
 | 
			
		||||
 | 
			
		||||
@ -308,6 +308,7 @@
 | 
			
		||||
  "LabelPublishYear": "Publish Year",
 | 
			
		||||
  "LabelRecentlyAdded": "Recently Added",
 | 
			
		||||
  "LabelRecentSeries": "Recent Series",
 | 
			
		||||
  "LabelRecommended": "Recommended",
 | 
			
		||||
  "LabelRegion": "Region",
 | 
			
		||||
  "LabelReleaseDate": "Release Date",
 | 
			
		||||
  "LabelRemoveCover": "Remove cover",
 | 
			
		||||
 | 
			
		||||
@ -308,6 +308,7 @@
 | 
			
		||||
  "LabelPublishYear": "Publish Year",
 | 
			
		||||
  "LabelRecentlyAdded": "Recently Added",
 | 
			
		||||
  "LabelRecentSeries": "Recent Series",
 | 
			
		||||
  "LabelRecommended": "Recommended",
 | 
			
		||||
  "LabelRegion": "Region",
 | 
			
		||||
  "LabelReleaseDate": "Release Date",
 | 
			
		||||
  "LabelRemoveCover": "Remove cover",
 | 
			
		||||
 | 
			
		||||
@ -308,6 +308,7 @@
 | 
			
		||||
  "LabelPublishYear": "Année d'Edition",
 | 
			
		||||
  "LabelRecentlyAdded": "Derniers Ajouts",
 | 
			
		||||
  "LabelRecentSeries": "Séries Récentes",
 | 
			
		||||
  "LabelRecommended": "Recommended",
 | 
			
		||||
  "LabelRegion": "Région",
 | 
			
		||||
  "LabelReleaseDate": "Date de Parution",
 | 
			
		||||
  "LabelRemoveCover": "Supprimer la Couverture",
 | 
			
		||||
 | 
			
		||||
@ -308,6 +308,7 @@
 | 
			
		||||
  "LabelPublishYear": "Godina izdavanja",
 | 
			
		||||
  "LabelRecentlyAdded": "Nedavno dodano",
 | 
			
		||||
  "LabelRecentSeries": "Nedavne serije",
 | 
			
		||||
  "LabelRecommended": "Recommended",
 | 
			
		||||
  "LabelRegion": "Regija",
 | 
			
		||||
  "LabelReleaseDate": "Datum izlaska",
 | 
			
		||||
  "LabelRemoveCover": "Remove cover",
 | 
			
		||||
 | 
			
		||||
@ -308,6 +308,7 @@
 | 
			
		||||
  "LabelPublishYear": "Anno Pubblicazione",
 | 
			
		||||
  "LabelRecentlyAdded": "Aggiunti Recentemente",
 | 
			
		||||
  "LabelRecentSeries": "Serie Recenti",
 | 
			
		||||
  "LabelRecommended": "Recommended",
 | 
			
		||||
  "LabelRegion": "Regione",
 | 
			
		||||
  "LabelReleaseDate": "Data Release",
 | 
			
		||||
  "LabelRemoveCover": "Remove cover",
 | 
			
		||||
 | 
			
		||||
@ -308,6 +308,7 @@
 | 
			
		||||
  "LabelPublishYear": "Rok publikacji",
 | 
			
		||||
  "LabelRecentlyAdded": "Niedawno dodany",
 | 
			
		||||
  "LabelRecentSeries": "Ostatnie serie",
 | 
			
		||||
  "LabelRecommended": "Recommended",
 | 
			
		||||
  "LabelRegion": "Region",
 | 
			
		||||
  "LabelReleaseDate": "Data wydania",
 | 
			
		||||
  "LabelRemoveCover": "Remove cover",
 | 
			
		||||
 | 
			
		||||
@ -308,6 +308,7 @@
 | 
			
		||||
  "LabelPublishYear": "发布年份",
 | 
			
		||||
  "LabelRecentlyAdded": "最近添加",
 | 
			
		||||
  "LabelRecentSeries": "最近添加系列",
 | 
			
		||||
  "LabelRecommended": "Recommended",
 | 
			
		||||
  "LabelRegion": "区域",
 | 
			
		||||
  "LabelReleaseDate": "发布日期",
 | 
			
		||||
  "LabelRemoveCover": "移除封面",
 | 
			
		||||
 | 
			
		||||
@ -339,6 +339,14 @@ module.exports = {
 | 
			
		||||
        entities: [],
 | 
			
		||||
        category: 'continueSeries'
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        id: 'episodes-recently-added',
 | 
			
		||||
        label: 'Newest Episodes',
 | 
			
		||||
        labelStringKey: 'LabelNewestEpisodes',
 | 
			
		||||
        type: 'episode',
 | 
			
		||||
        entities: [],
 | 
			
		||||
        category: 'newestEpisodes'
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        id: 'recently-added',
 | 
			
		||||
        label: 'Recently Added',
 | 
			
		||||
@ -347,14 +355,6 @@ module.exports = {
 | 
			
		||||
        entities: [],
 | 
			
		||||
        category: 'newestItems'
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        id: 'listen-again',
 | 
			
		||||
        label: 'Listen Again',
 | 
			
		||||
        labelStringKey: 'LabelListenAgain',
 | 
			
		||||
        type: isPodcastLibrary ? 'episode' : mediaType,
 | 
			
		||||
        entities: [],
 | 
			
		||||
        category: 'recentlyFinished'
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        id: 'recent-series',
 | 
			
		||||
        label: 'Recent Series',
 | 
			
		||||
@ -363,6 +363,22 @@ module.exports = {
 | 
			
		||||
        entities: [],
 | 
			
		||||
        category: 'newestSeries'
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        id: 'recommended',
 | 
			
		||||
        label: 'Recommended',
 | 
			
		||||
        labelStringKey: 'LabelRecommended',
 | 
			
		||||
        type: mediaType,
 | 
			
		||||
        entities: [],
 | 
			
		||||
        category: 'recommended'
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        id: 'listen-again',
 | 
			
		||||
        label: 'Listen Again',
 | 
			
		||||
        labelStringKey: 'LabelListenAgain',
 | 
			
		||||
        type: isPodcastLibrary ? 'episode' : mediaType,
 | 
			
		||||
        entities: [],
 | 
			
		||||
        category: 'recentlyFinished'
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        id: 'newest-authors',
 | 
			
		||||
        label: 'Newest Authors',
 | 
			
		||||
@ -370,22 +386,13 @@ module.exports = {
 | 
			
		||||
        type: 'authors',
 | 
			
		||||
        entities: [],
 | 
			
		||||
        category: 'newestAuthors'
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        id: 'episodes-recently-added',
 | 
			
		||||
        label: 'Newest Episodes',
 | 
			
		||||
        labelStringKey: 'LabelNewestEpisodes',
 | 
			
		||||
        type: 'episode',
 | 
			
		||||
        entities: [],
 | 
			
		||||
        category: 'newestEpisodes'
 | 
			
		||||
      }
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    const categories = ['recentlyListened', 'continueSeries', 'newestEpisodes', 'newestItems', 'newestSeries', 'recentlyFinished', 'newestAuthors']
 | 
			
		||||
    const categoryMap = {}
 | 
			
		||||
    categories.forEach((cat) => {
 | 
			
		||||
      categoryMap[cat] = {
 | 
			
		||||
        category: cat,
 | 
			
		||||
    shelves.forEach((shelf) => {
 | 
			
		||||
      categoryMap[shelf.category] = {
 | 
			
		||||
        category: shelf.category,
 | 
			
		||||
        biggest: 0,
 | 
			
		||||
        smallest: 0,
 | 
			
		||||
        items: []
 | 
			
		||||
@ -395,6 +402,12 @@ module.exports = {
 | 
			
		||||
    const seriesMap = {}
 | 
			
		||||
    const authorMap = {}
 | 
			
		||||
 | 
			
		||||
    // For use with recommended
 | 
			
		||||
    const topGenresListened = {}
 | 
			
		||||
    const topAuthorsListened = {}
 | 
			
		||||
    const topTagsListened = {}
 | 
			
		||||
    const notStartedBooks = []
 | 
			
		||||
 | 
			
		||||
    for (const libraryItem of libraryItems) {
 | 
			
		||||
      if (libraryItem.addedAt > categoryMap.newestItems.smallest) {
 | 
			
		||||
 | 
			
		||||
@ -494,10 +507,28 @@ module.exports = {
 | 
			
		||||
      } else if (libraryItem.isBook) {
 | 
			
		||||
        // Book categories
 | 
			
		||||
 | 
			
		||||
        const mediaProgress = allItemProgress.length ? allItemProgress[0] : null
 | 
			
		||||
 | 
			
		||||
        // Used for recommended. Tally up most listened to authors/genres/tags
 | 
			
		||||
        if (mediaProgress && (mediaProgress.inProgress || mediaProgress.isFinished)) {
 | 
			
		||||
          libraryItem.media.metadata.authors.forEach((author) => {
 | 
			
		||||
            topAuthorsListened[author.id] = (topAuthorsListened[author.id] || 0) + 1
 | 
			
		||||
          })
 | 
			
		||||
          libraryItem.media.metadata.genres.forEach((genre) => {
 | 
			
		||||
            topGenresListened[genre] = (topGenresListened[genre] || 0) + 1
 | 
			
		||||
          })
 | 
			
		||||
          libraryItem.media.tags.forEach((tag) => {
 | 
			
		||||
            topTagsListened[tag] = (topTagsListened[tag] || 0) + 1
 | 
			
		||||
          })
 | 
			
		||||
        } else {
 | 
			
		||||
          // Insert in random position to add randomization to equal weighted items
 | 
			
		||||
          notStartedBooks.splice(Math.floor(Math.random() * (notStartedBooks.length + 1)), 0, libraryItem)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Newest series
 | 
			
		||||
        if (libraryItem.media.metadata.series.length) {
 | 
			
		||||
          for (const librarySeries of libraryItem.media.metadata.series) {
 | 
			
		||||
            const mediaProgress = allItemProgress.length ? allItemProgress[0] : null
 | 
			
		||||
 | 
			
		||||
            const bookInProgress = mediaProgress && (mediaProgress.inProgress || mediaProgress.isFinished)
 | 
			
		||||
            const bookActive = mediaProgress && mediaProgress.inProgress && !mediaProgress.isFinished
 | 
			
		||||
            const libraryItemJson = libraryItem.toJSONMinified()
 | 
			
		||||
@ -602,7 +633,6 @@ module.exports = {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Book listening and finished
 | 
			
		||||
        var mediaProgress = allItemProgress.length ? allItemProgress[0] : null
 | 
			
		||||
        if (mediaProgress) {
 | 
			
		||||
          // Handle most recently finished
 | 
			
		||||
          if (mediaProgress.isFinished) {
 | 
			
		||||
@ -612,7 +642,7 @@ module.exports = {
 | 
			
		||||
                finishedAt: mediaProgress.finishedAt
 | 
			
		||||
              }
 | 
			
		||||
 | 
			
		||||
              var indexToPut = categoryMap.recentlyFinished.items.findIndex(i => mediaProgress.finishedAt > i.finishedAt)
 | 
			
		||||
              const indexToPut = categoryMap.recentlyFinished.items.findIndex(i => mediaProgress.finishedAt > i.finishedAt)
 | 
			
		||||
              if (indexToPut >= 0) {
 | 
			
		||||
                categoryMap.recentlyFinished.items.splice(indexToPut, 0, libraryItemObj)
 | 
			
		||||
              } else {
 | 
			
		||||
@ -632,7 +662,7 @@ module.exports = {
 | 
			
		||||
                progressLastUpdate: mediaProgress.lastUpdate
 | 
			
		||||
              }
 | 
			
		||||
 | 
			
		||||
              var indexToPut = categoryMap.recentlyListened.items.findIndex(i => mediaProgress.lastUpdate > i.progressLastUpdate)
 | 
			
		||||
              const indexToPut = categoryMap.recentlyListened.items.findIndex(i => mediaProgress.lastUpdate > i.progressLastUpdate)
 | 
			
		||||
              if (indexToPut >= 0) {
 | 
			
		||||
                categoryMap.recentlyListened.items.splice(indexToPut, 0, libraryItemObj)
 | 
			
		||||
              } else { // Should only happen when array is < max
 | 
			
		||||
@ -652,9 +682,9 @@ module.exports = {
 | 
			
		||||
 | 
			
		||||
    // For Continue Series - Find next book in series for series that are in progress
 | 
			
		||||
    for (const seriesId in seriesMap) {
 | 
			
		||||
      if (seriesMap[seriesId].inProgress && !seriesMap[seriesId].hideFromContinueListening) {
 | 
			
		||||
      seriesMap[seriesId].books = naturalSort(seriesMap[seriesId].books).asc(li => li.seriesSequence)
 | 
			
		||||
 | 
			
		||||
      if (seriesMap[seriesId].inProgress && !seriesMap[seriesId].hideFromContinueListening) {
 | 
			
		||||
        // take the first book unread with the smallest series sequence
 | 
			
		||||
        // unless the user is already listening to a book from this series
 | 
			
		||||
        const hasActiveBook = seriesMap[seriesId].hasActiveBook
 | 
			
		||||
@ -683,6 +713,76 @@ module.exports = {
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // For recommended
 | 
			
		||||
    if (!isPodcastLibrary && notStartedBooks.length) {
 | 
			
		||||
      const genresCount = Object.values(topGenresListened).reduce((a, b) => a + b, 0)
 | 
			
		||||
      const authorsCount = Object.values(topAuthorsListened).reduce((a, b) => a + b, 0)
 | 
			
		||||
      const tagsCount = Object.values(topTagsListened).reduce((a, b) => a + b, 0)
 | 
			
		||||
 | 
			
		||||
      for (const libraryItem of notStartedBooks) {
 | 
			
		||||
        // dont include books in an unfinished series and books that are not first in an unstarted series
 | 
			
		||||
        let shouldContinue = !libraryItem.media.metadata.series.length
 | 
			
		||||
        libraryItem.media.metadata.series.forEach((se) => {
 | 
			
		||||
          if (seriesMap[se.id]) {
 | 
			
		||||
            if (seriesMap[se.id].inProgress) {
 | 
			
		||||
              shouldContinue = false
 | 
			
		||||
              return
 | 
			
		||||
            } else if (seriesMap[se.id].books[0].id === libraryItem.id) {
 | 
			
		||||
              shouldContinue = true
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        })
 | 
			
		||||
        if (!shouldContinue) {
 | 
			
		||||
          continue;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let totalWeight = 0
 | 
			
		||||
 | 
			
		||||
        if (authorsCount > 0) {
 | 
			
		||||
          libraryItem.media.metadata.authors.forEach((author) => {
 | 
			
		||||
            if (topAuthorsListened[author.id]) {
 | 
			
		||||
              totalWeight += topAuthorsListened[author.id] / authorsCount
 | 
			
		||||
            }
 | 
			
		||||
          })
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (genresCount > 0) {
 | 
			
		||||
          libraryItem.media.metadata.genres.forEach((genre) => {
 | 
			
		||||
            if (topGenresListened[genre]) {
 | 
			
		||||
              totalWeight += topGenresListened[genre] / genresCount
 | 
			
		||||
            }
 | 
			
		||||
          })
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (tagsCount > 0) {
 | 
			
		||||
          libraryItem.media.tags.forEach((tag) => {
 | 
			
		||||
            if (topTagsListened[tag]) {
 | 
			
		||||
              totalWeight += topTagsListened[tag] / tagsCount
 | 
			
		||||
            }
 | 
			
		||||
          })
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!categoryMap.recommended.smallest || totalWeight > categoryMap.recommended.smallest) {
 | 
			
		||||
          const libraryItemObj = {
 | 
			
		||||
            ...libraryItem.toJSONMinified(),
 | 
			
		||||
            weight: totalWeight
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          const indexToPut = categoryMap.recommended.items.findIndex(i => totalWeight > i.weight)
 | 
			
		||||
          if (indexToPut >= 0) {
 | 
			
		||||
            categoryMap.recommended.items.splice(indexToPut, 0, libraryItemObj)
 | 
			
		||||
          } else {
 | 
			
		||||
            categoryMap.recommended.items.push(libraryItemObj)
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          if (categoryMap.recommended.items.length > maxEntitiesPerShelf) {
 | 
			
		||||
            categoryMap.recommended.items.pop()
 | 
			
		||||
            categoryMap.recommended.smallest = categoryMap.recommended.items[categoryMap.recommended.items.length - 1].weight
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Sort series books by sequence
 | 
			
		||||
    if (categoryMap.newestSeries.items.length) {
 | 
			
		||||
      for (const seriesItem of categoryMap.newestSeries.items) {
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user