mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	Series order by volume number, show volume number, keyword filter, fix overflow bug
This commit is contained in:
		
							parent
							
								
									9935bd2ffa
								
							
						
					
					
						commit
						06554811e2
					
				| @ -108,5 +108,5 @@ | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .box-shadow-side { | .box-shadow-side { | ||||||
|   box-shadow: 4px 0px 4px #11111166; |   box-shadow: 5px 0px 5px #11111166; | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| <template> | <template> | ||||||
|   <div id="bookshelf" ref="wrapper" class="w-full h-full overflow-y-auto relative"> |   <div id="bookshelf" ref="wrapper" class="w-full h-full overflow-y-scroll relative"> | ||||||
|     <!-- Cover size widget --> |     <!-- Cover size widget --> | ||||||
|     <div v-show="!isSelectionMode" class="fixed bottom-2 right-4 z-20"> |     <div v-show="!isSelectionMode" class="fixed bottom-2 right-4 z-20"> | ||||||
|       <div class="rounded-full py-1 bg-primary px-2 border border-black-100 text-center flex items-center box-shadow-md" @mousedown.prevent @mouseup.prevent> |       <div class="rounded-full py-1 bg-primary px-2 border border-black-100 text-center flex items-center box-shadow-md" @mousedown.prevent @mouseup.prevent> | ||||||
| @ -23,7 +23,7 @@ | |||||||
|             <template v-for="entity in shelf"> |             <template v-for="entity in shelf"> | ||||||
|               <cards-group-card v-if="showGroups" :key="entity.id" :width="bookCoverWidth" :group="entity" @click="clickGroup" /> |               <cards-group-card v-if="showGroups" :key="entity.id" :width="bookCoverWidth" :group="entity" @click="clickGroup" /> | ||||||
|               <!-- <cards-book-3d :key="entity.id" v-else :width="100" :src="$store.getters['audiobooks/getBookCoverSrc'](entity.book)" /> --> |               <!-- <cards-book-3d :key="entity.id" v-else :width="100" :src="$store.getters['audiobooks/getBookCoverSrc'](entity.book)" /> --> | ||||||
|               <cards-book-card v-else :key="entity.id" :width="bookCoverWidth" :user-progress="userAudiobooks[entity.id]" :audiobook="entity" /> |               <cards-book-card v-else :key="entity.id" :show-volume-number="selectedSeries" :width="bookCoverWidth" :user-progress="userAudiobooks[entity.id]" :audiobook="entity" /> | ||||||
|             </template> |             </template> | ||||||
|           </div> |           </div> | ||||||
|           <div class="bookshelfDivider h-4 w-full absolute bottom-0 left-0 right-0 z-10" /> |           <div class="bookshelfDivider h-4 w-full absolute bottom-0 left-0 right-0 z-10" /> | ||||||
| @ -58,7 +58,8 @@ export default { | |||||||
|       selectedSizeIndex: 3, |       selectedSizeIndex: 3, | ||||||
|       rowPaddingX: 40, |       rowPaddingX: 40, | ||||||
|       keywordFilterTimeout: null, |       keywordFilterTimeout: null, | ||||||
|       scannerParseSubtitle: false |       scannerParseSubtitle: false, | ||||||
|  |       wrapperClientWidth: 0 | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   watch: { |   watch: { | ||||||
| @ -159,7 +160,8 @@ export default { | |||||||
|       this.$store.dispatch('user/updateUserSettings', { bookshelfCoverSize: this.bookCoverWidth }) |       this.$store.dispatch('user/updateUserSettings', { bookshelfCoverSize: this.bookCoverWidth }) | ||||||
|     }, |     }, | ||||||
|     setBookshelfEntities() { |     setBookshelfEntities() { | ||||||
|       var width = Math.max(0, this.$refs.wrapper.clientWidth - this.rowPaddingX * 2) |       this.wrapperClientWidth = this.$refs.wrapper.clientWidth | ||||||
|  |       var width = Math.max(0, this.wrapperClientWidth - this.rowPaddingX * 2) | ||||||
|       var booksPerRow = Math.floor(width / this.bookWidth) |       var booksPerRow = Math.floor(width / this.bookWidth) | ||||||
| 
 | 
 | ||||||
|       var entities = this.entities |       var entities = this.entities | ||||||
| @ -182,6 +184,8 @@ export default { | |||||||
|       this.shelves = groups |       this.shelves = groups | ||||||
|     }, |     }, | ||||||
|     async init() { |     async init() { | ||||||
|  |       this.wrapperClientWidth = this.$refs.wrapper ? this.$refs.wrapper.clientWidth : 0 | ||||||
|  | 
 | ||||||
|       var bookshelfCoverSize = this.$store.getters['user/getUserSetting']('bookshelfCoverSize') |       var bookshelfCoverSize = this.$store.getters['user/getUserSetting']('bookshelfCoverSize') | ||||||
|       var sizeIndex = this.availableSizes.findIndex((s) => s === bookshelfCoverSize) |       var sizeIndex = this.availableSizes.findIndex((s) => s === bookshelfCoverSize) | ||||||
|       if (!isNaN(sizeIndex)) this.selectedSizeIndex = sizeIndex |       if (!isNaN(sizeIndex)) this.selectedSizeIndex = sizeIndex | ||||||
| @ -192,9 +196,7 @@ export default { | |||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     resize() { |     resize() { | ||||||
|       this.$nextTick(() => { |       this.$nextTick(this.setBookshelfEntities) | ||||||
|         this.setBookshelfEntities() |  | ||||||
|       }) |  | ||||||
|     }, |     }, | ||||||
|     audiobooksUpdated() { |     audiobooksUpdated() { | ||||||
|       console.log('[AudioBookshelf] Audiobooks Updated') |       console.log('[AudioBookshelf] Audiobooks Updated') | ||||||
| @ -216,10 +218,18 @@ export default { | |||||||
|       this.$root.socket.emit('scan') |       this.$root.socket.emit('scan') | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|  |   updated() { | ||||||
|  |     if (this.$refs.wrapper) { | ||||||
|  |       if (this.wrapperClientWidth !== this.$refs.wrapper.clientWidth) { | ||||||
|  |         this.$nextTick(this.setBookshelfEntities) | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|   mounted() { |   mounted() { | ||||||
|     window.addEventListener('resize', this.resize) |     window.addEventListener('resize', this.resize) | ||||||
|     this.$store.commit('audiobooks/addListener', { id: 'bookshelf', meth: this.audiobooksUpdated }) |     this.$store.commit('audiobooks/addListener', { id: 'bookshelf', meth: this.audiobooksUpdated }) | ||||||
|     this.$store.commit('user/addSettingsListener', { id: 'bookshelf', meth: this.settingsUpdated }) |     this.$store.commit('user/addSettingsListener', { id: 'bookshelf', meth: this.settingsUpdated }) | ||||||
|  | 
 | ||||||
|     this.init() |     this.init() | ||||||
|   }, |   }, | ||||||
|   beforeDestroy() { |   beforeDestroy() { | ||||||
|  | |||||||
| @ -14,7 +14,7 @@ | |||||||
|         </div> |         </div> | ||||||
|         <div class="flex-grow" /> |         <div class="flex-grow" /> | ||||||
| 
 | 
 | ||||||
|         <ui-text-input v-show="showSortFilters" v-model="_keywordFilter" placeholder="Keyword Filter" :padding-y="1.5" class="text-xs w-40" /> |         <ui-text-input v-show="!selectedSeries" v-model="_keywordFilter" placeholder="Keyword Filter" :padding-y="1.5" class="text-xs w-40" /> | ||||||
|         <controls-filter-select v-show="showSortFilters" v-model="settings.filterBy" class="w-48 h-7.5 ml-4" @change="updateFilter" /> |         <controls-filter-select v-show="showSortFilters" v-model="settings.filterBy" class="w-48 h-7.5 ml-4" @change="updateFilter" /> | ||||||
|         <controls-order-select v-show="showSortFilters" v-model="settings.orderBy" :descending.sync="settings.orderDesc" class="w-48 h-7.5 ml-4" @change="updateOrder" /> |         <controls-order-select v-show="showSortFilters" v-model="settings.orderBy" :descending.sync="settings.orderDesc" class="w-48 h-7.5 ml-4" @change="updateOrder" /> | ||||||
|       </template> |       </template> | ||||||
| @ -121,6 +121,6 @@ export default { | |||||||
| 
 | 
 | ||||||
| <style> | <style> | ||||||
| #toolbar { | #toolbar { | ||||||
|   box-shadow: 0px 8px 8px #111111aa; |   box-shadow: 0px 8px 6px #111111aa; | ||||||
| } | } | ||||||
| </style> | </style> | ||||||
| @ -1,5 +1,6 @@ | |||||||
| <template> | <template> | ||||||
|   <div class="w-20 border-r border-primary bg-bg h-full relative box-shadow-side z-40" style="min-width: 80px"> |   <div class="w-20 bg-bg h-full relative box-shadow-side z-20" style="min-width: 80px"> | ||||||
|  |     <div class="absolute top-0 -right-4 w-4 bg-bg h-10 pointer-events-none" /> | ||||||
|     <nuxt-link to="/library" class="w-full h-20 flex flex-col items-center justify-center text-white border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="paramId === '' ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'"> |     <nuxt-link to="/library" class="w-full h-20 flex flex-col items-center justify-center text-white border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="paramId === '' ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'"> | ||||||
|       <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"> |       <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | ||||||
|         <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" /> |         <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" /> | ||||||
|  | |||||||
| @ -29,6 +29,10 @@ | |||||||
|             </div> |             </div> | ||||||
|           </div> |           </div> | ||||||
| 
 | 
 | ||||||
|  |           <div v-if="volumeNumber && showVolumeNumber && !isHovering" class="absolute rounded-lg bg-black bg-opacity-90 box-shadow-md" :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> | ||||||
|  | 
 | ||||||
|           <div v-show="!isSelectionMode" class="absolute bottom-0 left-0 h-1 shadow-sm max-w-full" :class="userIsRead ? 'bg-success' : 'bg-yellow-400'" :style="{ width: width * userProgressPercent + 'px' }"></div> |           <div v-show="!isSelectionMode" class="absolute bottom-0 left-0 h-1 shadow-sm max-w-full" :class="userIsRead ? 'bg-success' : 'bg-yellow-400'" :style="{ width: width * userProgressPercent + 'px' }"></div> | ||||||
| 
 | 
 | ||||||
|           <ui-tooltip v-if="showError" :text="errorText" class="absolute bottom-4 left-0"> |           <ui-tooltip v-if="showError" :text="errorText" class="absolute bottom-4 left-0"> | ||||||
| @ -56,7 +60,8 @@ export default { | |||||||
|     width: { |     width: { | ||||||
|       type: Number, |       type: Number, | ||||||
|       default: 120 |       default: 120 | ||||||
|     } |     }, | ||||||
|  |     showVolumeNumber: Boolean | ||||||
|   }, |   }, | ||||||
|   data() { |   data() { | ||||||
|     return { |     return { | ||||||
|  | |||||||
| @ -5,13 +5,16 @@ | |||||||
|         <div class="w-full relative" :class="isHovering ? 'bg-black-400' : 'bg-primary'" :style="{ height: height + 'px', width: height + 'px' }"> |         <div class="w-full relative" :class="isHovering ? 'bg-black-400' : 'bg-primary'" :style="{ height: height + 'px', width: height + 'px' }"> | ||||||
|           <cards-group-cover ref="groupcover" :name="groupName" :book-items="bookItems" :width="height" :height="height" /> |           <cards-group-cover ref="groupcover" :name="groupName" :book-items="bookItems" :width="height" :height="height" /> | ||||||
| 
 | 
 | ||||||
|           <div v-if="hasValidCovers" class="bg-black bg-opacity-60 absolute top-0 left-0 w-full h-full flex items-center justify-center text-center transition-opacity" :class="isHovering ? '' : 'opacity-0'"> |           <div v-if="hasValidCovers" class="bg-black bg-opacity-60 absolute top-0 left-0 w-full h-full flex items-center justify-center text-center transition-opacity" :class="isHovering ? '' : 'opacity-0'" :style="{ padding: `${sizeMultiplier}rem` }"> | ||||||
|             <p class="truncate font-book" :style="{ fontSize: sizeMultiplier + 'rem' }">{{ groupName }}</p> |             <p class="font-book" :style="{ fontSize: sizeMultiplier + 'rem' }">{{ groupName }}</p> | ||||||
|           </div> |           </div> | ||||||
| 
 | 
 | ||||||
|           <div class="absolute top-2 right-2 w-7 h-7 rounded-lg bg-black bg-opacity-90 text-gray-300 box-shadow-book flex items-center justify-center border border-white border-opacity-25 pointer-events-none"> |           <div class="absolute top-2 right-2 w-7 h-7 rounded-lg bg-black bg-opacity-90 text-gray-300 box-shadow-book flex items-center justify-center border border-white border-opacity-25 pointer-events-none"> | ||||||
|             <p class="font-book text-xl">{{ bookItems.length }}</p> |             <p class="font-book text-xl">{{ bookItems.length }}</p> | ||||||
|           </div> |           </div> | ||||||
|  |           <div class="absolute bottom-0 left-0 w-full h-1 flex flex-nowrap"> | ||||||
|  |             <div v-for="userProgress in userProgressItems" :key="userProgress.audiobookId" class="h-full w-full" :class="userProgress.isRead ? 'bg-success' : userProgress.progress > 0 ? 'bg-yellow-400' : ''" /> | ||||||
|  |           </div> | ||||||
|         </div> |         </div> | ||||||
|       </nuxt-link> |       </nuxt-link> | ||||||
|     </div> |     </div> | ||||||
| @ -60,6 +63,15 @@ export default { | |||||||
|     bookItems() { |     bookItems() { | ||||||
|       return this._group.books || [] |       return this._group.books || [] | ||||||
|     }, |     }, | ||||||
|  |     userAudiobooks() { | ||||||
|  |       return Object.values(this.$store.state.user.user ? this.$store.state.user.user.audiobooks || {} : {}) | ||||||
|  |     }, | ||||||
|  |     userProgressItems() { | ||||||
|  |       return this.bookItems.map((item) => { | ||||||
|  |         var userAudiobook = this.userAudiobooks.find((ab) => ab.audiobookId === item.id) | ||||||
|  |         return userAudiobook || {} | ||||||
|  |       }) | ||||||
|  |     }, | ||||||
|     groupName() { |     groupName() { | ||||||
|       return this._group.name || 'No Name' |       return this._group.name || 'No Name' | ||||||
|     }, |     }, | ||||||
| @ -81,7 +93,6 @@ export default { | |||||||
|     clickCard() { |     clickCard() { | ||||||
|       this.$emit('click', this.group) |       this.$emit('click', this.group) | ||||||
|     } |     } | ||||||
|   }, |   } | ||||||
|   mounted() {} |  | ||||||
| } | } | ||||||
| </script> | </script> | ||||||
| @ -1,6 +1,6 @@ | |||||||
| <template> | <template> | ||||||
|   <div ref="wrapper" :style="{ height: height + 'px', width: width + 'px' }" class="relative"> |   <div ref="wrapper" :style="{ height: height + 'px', width: width + 'px' }" class="relative"> | ||||||
|     <div v-if="noValidCovers" class="absolute top-0 left-0 w-full h-full flex items-center justify-center box-shadow-book"> |     <div v-if="noValidCovers" class="absolute top-0 left-0 w-full h-full flex items-center justify-center box-shadow-book" :style="{ padding: `${sizeMultiplier}rem` }"> | ||||||
|       <p :style="{ fontSize: sizeMultiplier + 'rem' }">{{ name }}</p> |       <p :style="{ fontSize: sizeMultiplier + 'rem' }">{{ name }}</p> | ||||||
|     </div> |     </div> | ||||||
|   </div> |   </div> | ||||||
|  | |||||||
| @ -26,6 +26,9 @@ export default { | |||||||
|       if (this.$store.state.selectedAudiobooks) { |       if (this.$store.state.selectedAudiobooks) { | ||||||
|         this.$store.commit('setSelectedAudiobooks', []) |         this.$store.commit('setSelectedAudiobooks', []) | ||||||
|       } |       } | ||||||
|  |       if (this.$store.state.audiobooks.keywordFilter) { | ||||||
|  |         this.$store.commit('audiobooks/setKeywordFilter', '') | ||||||
|  |       } | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   computed: { |   computed: { | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
|   "name": "audiobookshelf-client", |   "name": "audiobookshelf-client", | ||||||
|   "version": "1.2.3", |   "version": "1.2.4", | ||||||
|   "description": "Audiobook manager and player", |   "description": "Audiobook manager and player", | ||||||
|   "main": "index.js", |   "main": "index.js", | ||||||
|   "scripts": { |   "scripts": { | ||||||
|  | |||||||
| @ -73,13 +73,23 @@ export const getters = { | |||||||
|         } else { |         } else { | ||||||
|           series[audiobook.book.series] = { |           series[audiobook.book.series] = { | ||||||
|             type: 'series', |             type: 'series', | ||||||
|             name: audiobook.book.series, |             name: audiobook.book.series || '', | ||||||
|             books: [audiobook] |             books: [audiobook] | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|     }) |     }) | ||||||
|     return Object.values(series) |     var seriesArray = Object.values(series).map((_series) => { | ||||||
|  |       _series.books = sort(_series.books)['asc']((ab) => { | ||||||
|  |         return ab.book && ab.book.volumeNumber && !isNaN(ab.book.volumeNumber) ? Number(ab.book.volumeNumber) : null | ||||||
|  |       }) | ||||||
|  |       return _series | ||||||
|  |     }) | ||||||
|  |     if (state.keywordFilter) { | ||||||
|  |       const keywordFilter = state.keywordFilter.toLowerCase() | ||||||
|  |       return seriesArray.filter((_series) => _series.name.toLowerCase().includes(keywordFilter)) | ||||||
|  |     } | ||||||
|  |     return seriesArray | ||||||
|   }, |   }, | ||||||
|   getUniqueAuthors: (state) => { |   getUniqueAuthors: (state) => { | ||||||
|     var _authors = state.audiobooks.filter(ab => !!(ab.book && ab.book.author)).map(ab => ab.book.author) |     var _authors = state.audiobooks.filter(ab => !!(ab.book && ab.book.author)).map(ab => ab.book.author) | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
|   "name": "audiobookshelf", |   "name": "audiobookshelf", | ||||||
|   "version": "1.2.3", |   "version": "1.2.4", | ||||||
|   "description": "Self-hosted audiobook server for managing and playing audiobooks", |   "description": "Self-hosted audiobook server for managing and playing audiobooks", | ||||||
|   "main": "index.js", |   "main": "index.js", | ||||||
|   "scripts": { |   "scripts": { | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user