mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	Add:Podcast episode filters and default to filter out completed episodes #940
This commit is contained in:
		
							parent
							
								
									05b4124761
								
							
						
					
					
						commit
						40384dd442
					
				
							
								
								
									
										100
									
								
								client/components/controls/EpisodeFilterSelect.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								client/components/controls/EpisodeFilterSelect.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,100 @@ | ||||
| <template> | ||||
|   <div ref="wrapper" class="relative" v-click-outside="clickOutside"> | ||||
|     <button type="button" class="relative w-full h-full border border-gray-500 hover:border-gray-400 rounded shadow-sm pl-3 pr-3 py-0 text-left focus:outline-none sm:text-sm cursor-pointer" aria-haspopup="listbox" aria-expanded="true" aria-labelledby="listbox-label" @click.prevent="showMenu = !showMenu"> | ||||
|       <span class="flex items-center justify-between"> | ||||
|         <span class="block truncate text-xs">{{ selectedText }}</span> | ||||
|       </span> | ||||
|       <span v-if="selected === 'all'" class="ml-3 absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none"> | ||||
|         <svg class="h-5 w-5 text-gray-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"> | ||||
|           <path fill-rule="evenodd" d="M10 3a1 1 0 01.707.293l3 3a1 1 0 01-1.414 1.414L10 5.414 7.707 7.707a1 1 0 01-1.414-1.414l3-3A1 1 0 0110 3zm-3.707 9.293a1 1 0 011.414 0L10 14.586l2.293-2.293a1 1 0 011.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z" clip-rule="evenodd" /> | ||||
|         </svg> | ||||
|       </span> | ||||
|       <div v-else class="ml-3 absolute inset-y-0 right-0 flex items-center pr-2 cursor-pointer text-gray-400 hover:text-gray-300" @mousedown.stop @mouseup.stop @click.stop.prevent="clearSelected"> | ||||
|         <span class="material-icons" style="font-size: 1.1rem">close</span> | ||||
|       </div> | ||||
|     </button> | ||||
| 
 | ||||
|     <div v-show="showMenu" class="absolute z-10 mt-1 w-full bg-bg border border-black-200 shadow-lg max-h-96 rounded-md py-1 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm"> | ||||
|       <ul class="h-full w-full" role="listbox" aria-labelledby="listbox-label"> | ||||
|         <template v-for="item in selectItems"> | ||||
|           <li :key="item.value" class="text-gray-50 select-none relative py-2 pr-9 cursor-pointer hover:bg-black-400" :class="item.value === selected ? 'bg-primary bg-opacity-50' : ''" role="option" @click="clickedOption(item)"> | ||||
|             <div class="flex items-center justify-between"> | ||||
|               <span class="font-normal ml-3 block truncate text-sm md:text-base">{{ item.text }}</span> | ||||
|             </div> | ||||
|           </li> | ||||
|         </template> | ||||
|       </ul> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
| 
 | ||||
| <script> | ||||
| export default { | ||||
|   props: { | ||||
|     value: String | ||||
|   }, | ||||
|   data() { | ||||
|     return { | ||||
|       showMenu: false | ||||
|     } | ||||
|   }, | ||||
|   computed: { | ||||
|     selected: { | ||||
|       get() { | ||||
|         return this.value | ||||
|       }, | ||||
|       set(val) { | ||||
|         this.$emit('input', val) | ||||
|       } | ||||
|     }, | ||||
|     selectItems() { | ||||
|       return [ | ||||
|         { | ||||
|           value: 'all', | ||||
|           text: 'Show All' | ||||
|         }, | ||||
|         { | ||||
|           value: 'incomplete', | ||||
|           text: 'Incomplete' | ||||
|         }, | ||||
|         { | ||||
|           value: 'complete', | ||||
|           text: 'Complete' | ||||
|         }, | ||||
|         { | ||||
|           value: 'in_progress', | ||||
|           text: 'In Progress' | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     selectedText() { | ||||
|       if (!this.selected) return '' | ||||
|       const filter = this.selectItems.find((i) => i.value === this.selected) | ||||
|       return filter ? filter.text : '' | ||||
|     }, | ||||
|     filterData() { | ||||
|       return this.$store.state.libraries.filterData || {} | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
|     clearSelected() { | ||||
|       this.selected = 'all' | ||||
|       this.showMenu = false | ||||
|       this.$nextTick(() => this.$emit('change', 'all')) | ||||
|     }, | ||||
|     clickOutside() { | ||||
|       this.showMenu = false | ||||
|     }, | ||||
|     clickedOption(option) { | ||||
|       var val = option.value | ||||
|       if (this.selected === val) { | ||||
|         this.showMenu = false | ||||
|         return | ||||
|       } | ||||
|       this.selected = val | ||||
|       this.showMenu = false | ||||
|       this.$nextTick(() => this.$emit('change', val)) | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
| @ -1,6 +1,6 @@ | ||||
| <template> | ||||
|   <div ref="wrapper" class="relative" v-click-outside="clickOutside"> | ||||
|     <button type="button" class="relative w-full h-full bg-fg border border-gray-500 hover:border-gray-400 rounded shadow-sm pl-3 pr-3 py-0 text-left focus:outline-none sm:text-sm cursor-pointer" aria-haspopup="listbox" aria-expanded="true" aria-labelledby="listbox-label" @click.prevent="showMenu = !showMenu"> | ||||
|     <button type="button" class="relative w-full h-full border border-gray-500 hover:border-gray-400 rounded shadow-sm pl-3 pr-3 py-0 text-left focus:outline-none sm:text-sm cursor-pointer" aria-haspopup="listbox" aria-expanded="true" aria-labelledby="listbox-label" @click.prevent="showMenu = !showMenu"> | ||||
|       <span class="flex items-center justify-between"> | ||||
|         <span class="block truncate text-xs" :class="!selectedText ? 'text-gray-300' : ''">{{ selectedText }}</span> | ||||
|         <span class="material-icons text-lg text-yellow-400">{{ descending ? 'expand_more' : 'expand_less' }}</span> | ||||
|  | ||||
| @ -10,7 +10,10 @@ | ||||
|         <ui-btn color="error" :disabled="processing" small class="h-9" @click="removeSelectedEpisodes">Remove {{ selectedEpisodes.length }} episode{{ selectedEpisodes.length > 1 ? 's' : '' }}</ui-btn> | ||||
|         <ui-btn :disabled="processing" small class="ml-2 h-9" @click="clearSelected">Cancel</ui-btn> | ||||
|       </template> | ||||
|       <controls-episode-sort-select v-else v-model="sortKey" :descending.sync="sortDesc" class="w-36 sm:w-44 md:w-48 h-9 ml-1 sm:ml-4" /> | ||||
|       <template v-else> | ||||
|         <controls-episode-filter-select v-model="filterKey" class="w-36 md:w-36 h-9 ml-1 sm:ml-4" /> | ||||
|         <controls-episode-sort-select v-model="sortKey" :descending.sync="sortDesc" class="w-36 sm:w-44 md:w-48 h-9 ml-1 sm:ml-4" /> | ||||
|       </template> | ||||
|     </div> | ||||
|     <p v-if="!episodes.length" class="py-4 text-center text-lg">No Episodes</p> | ||||
|     <template v-for="episode in episodesSorted"> | ||||
| @ -32,6 +35,7 @@ export default { | ||||
|   data() { | ||||
|     return { | ||||
|       episodesCopy: [], | ||||
|       filterKey: 'incomplete', | ||||
|       sortKey: 'publishedAt', | ||||
|       sortDesc: true, | ||||
|       selectedEpisode: null, | ||||
| @ -63,12 +67,20 @@ export default { | ||||
|       return this.media.episodes || [] | ||||
|     }, | ||||
|     episodesSorted() { | ||||
|       return this.episodesCopy.sort((a, b) => { | ||||
|         if (this.sortDesc) { | ||||
|           return String(b[this.sortKey]).localeCompare(String(a[this.sortKey]), undefined, { numeric: true, sensitivity: 'base' }) | ||||
|         } | ||||
|         return String(a[this.sortKey]).localeCompare(String(b[this.sortKey]), undefined, { numeric: true, sensitivity: 'base' }) | ||||
|       }) | ||||
|       return this.episodesCopy | ||||
|         .filter((ep) => { | ||||
|           if (this.filterKey === 'all') return true | ||||
|           const episodeProgress = this.$store.getters['user/getUserMediaProgress'](this.libraryItem.id, ep.id) | ||||
|           if (this.filterKey === 'incomplete') return !episodeProgress || !episodeProgress.isFinished | ||||
|           if (this.filterKey === 'complete') return episodeProgress && episodeProgress.isFinished | ||||
|           return episodeProgress && !episodeProgress.isFinished | ||||
|         }) | ||||
|         .sort((a, b) => { | ||||
|           if (this.sortDesc) { | ||||
|             return String(b[this.sortKey]).localeCompare(String(a[this.sortKey]), undefined, { numeric: true, sensitivity: 'base' }) | ||||
|           } | ||||
|           return String(a[this.sortKey]).localeCompare(String(b[this.sortKey]), undefined, { numeric: true, sensitivity: 'base' }) | ||||
|         }) | ||||
|     }, | ||||
|     selectedIsFinished() { | ||||
|       // Find an item that is not finished, if none then all items finished | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user