mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			177 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
			
		
		
	
	
			177 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
| <template>
 | |
|   <div class="bg-bg rounded-md shadow-lg border border-white/5 p-4 mb-8 relative" style="min-height: 200px">
 | |
|     <div class="flex items-center mb-4">
 | |
|       <nuxt-link to="/config/item-metadata-utils" class="w-8 h-8 flex items-center justify-center rounded-full cursor-pointer hover:bg-white/10 text-center">
 | |
|         <span class="material-symbols text-2xl">arrow_back</span>
 | |
|       </nuxt-link>
 | |
| 
 | |
|       <h1 class="text-xl mx-2">{{ $strings.HeaderManageGenres }}</h1>
 | |
|     </div>
 | |
| 
 | |
|     <p v-if="!genres.length && !loading" class="text-center py-8 text-lg">{{ $strings.MessageNoGenres }}</p>
 | |
| 
 | |
|     <div class="border border-white/10">
 | |
|       <template v-for="(genre, index) in genres">
 | |
|         <div :key="genre" class="w-full p-2 flex items-center text-gray-400 hover:text-white" :class="{ 'bg-primary/20': index % 2 === 0 }">
 | |
|           <p v-if="editingGenre !== genre" class="text-sm md:text-base text-gray-100">{{ genre }}</p>
 | |
|           <ui-text-input v-else v-model="newGenreName" />
 | |
|           <div class="grow" />
 | |
|           <template v-if="editingGenre !== genre">
 | |
|             <ui-icon-btn v-if="editingGenre !== genre" icon="edit" borderless :size="8" icon-font-size="1.1rem" class="mx-1" @click="editClick(genre)" />
 | |
|             <ui-icon-btn v-if="editingGenre !== genre" icon="delete" borderless :size="8" icon-font-size="1.1rem" @click="removeClick(genre)" />
 | |
|           </template>
 | |
|           <template v-else>
 | |
|             <ui-btn color="bg-success" small class="mx-2" @click.stop="saveClick">{{ $strings.ButtonSave }}</ui-btn>
 | |
|             <ui-btn small @click.stop="cancelEditClick">{{ $strings.ButtonCancel }}</ui-btn>
 | |
|           </template>
 | |
|         </div>
 | |
|       </template>
 | |
|     </div>
 | |
| 
 | |
|     <div v-if="loading" class="absolute top-0 left-0 w-full h-full bg-black/25 rounded-md">
 | |
|       <div class="sticky top-0 left-0 w-full h-full flex items-center justify-center" style="max-height: 80vh">
 | |
|         <ui-loading-indicator />
 | |
|       </div>
 | |
|     </div>
 | |
|   </div>
 | |
| </template>
 | |
| 
 | |
| <script>
 | |
| export default {
 | |
|   asyncData({ store, redirect }) {
 | |
|     if (!store.getters['user/getIsAdminOrUp']) {
 | |
|       redirect('/')
 | |
|     }
 | |
|   },
 | |
|   data() {
 | |
|     return {
 | |
|       loading: false,
 | |
|       genres: [],
 | |
|       editingGenre: null,
 | |
|       newGenreName: ''
 | |
|     }
 | |
|   },
 | |
|   watch: {},
 | |
|   computed: {},
 | |
|   methods: {
 | |
|     cancelEditClick() {
 | |
|       this.newGenreName = ''
 | |
|       this.editingGenre = null
 | |
|     },
 | |
|     removeClick(genre) {
 | |
|       const payload = {
 | |
|         message: `Are you sure you want to remove genre "${genre}" from all items?`,
 | |
|         callback: (confirmed) => {
 | |
|           if (confirmed) {
 | |
|             this.removeGenre(genre)
 | |
|           }
 | |
|         },
 | |
|         type: 'yesNo'
 | |
|       }
 | |
|       this.$store.commit('globals/setConfirmPrompt', payload)
 | |
|     },
 | |
|     editClick(genre) {
 | |
|       this.newGenreName = genre
 | |
|       this.editingGenre = genre
 | |
|     },
 | |
|     saveClick() {
 | |
|       this.newGenreName = this.newGenreName.trim()
 | |
|       if (!this.newGenreName) {
 | |
|         return
 | |
|       }
 | |
| 
 | |
|       if (this.editingGenre === this.newGenreName) {
 | |
|         this.cancelEditClick()
 | |
|         return
 | |
|       }
 | |
| 
 | |
|       const genreNameExists = this.genres.find((g) => g !== this.editingGenre && g === this.newGenreName)
 | |
|       const genreNameExistsOfDifferentCase = !genreNameExists ? this.genres.find((g) => g !== this.editingGenre && g.toLowerCase() === this.newGenreName.toLowerCase()) : null
 | |
| 
 | |
|       let message = this.$getString('MessageConfirmRenameGenre', [this.editingGenre, this.newGenreName])
 | |
|       if (genreNameExists) {
 | |
|         message += `<br><span class="text-sm">${this.$strings.MessageConfirmRenameGenreMergeNote}</span>`
 | |
|       } else if (genreNameExistsOfDifferentCase) {
 | |
|         message += `<br><span class="text-warning text-sm">${this.$getString('MessageConfirmRenameGenreWarning', [genreNameExistsOfDifferentCase])}</span>`
 | |
|       }
 | |
| 
 | |
|       const payload = {
 | |
|         message,
 | |
|         callback: (confirmed) => {
 | |
|           if (confirmed) {
 | |
|             this.renameGenre()
 | |
|           }
 | |
|         },
 | |
|         type: 'yesNo'
 | |
|       }
 | |
|       this.$store.commit('globals/setConfirmPrompt', payload)
 | |
|     },
 | |
|     renameGenre() {
 | |
|       this.loading = true
 | |
|       let _newGenreName = this.newGenreName
 | |
|       let _editingGenre = this.editingGenre
 | |
| 
 | |
|       const payload = {
 | |
|         genre: _editingGenre,
 | |
|         newGenre: _newGenreName
 | |
|       }
 | |
|       this.$axios
 | |
|         .$post('/api/genres/rename', payload)
 | |
|         .then((res) => {
 | |
|           this.$toast.success(this.$getString('MessageItemsUpdated', [res.numItemsUpdated]))
 | |
|           if (res.genreMerged) {
 | |
|             this.genres = this.genres.filter((g) => g !== _newGenreName)
 | |
|           }
 | |
|           this.genres = this.genres.map((g) => {
 | |
|             if (g === _editingGenre) return _newGenreName
 | |
|             return g
 | |
|           })
 | |
|           this.cancelEditClick()
 | |
|         })
 | |
|         .catch((error) => {
 | |
|           console.error('Failed to rename genre', error)
 | |
|           this.$toast.error(this.$strings.ToastRenameFailed)
 | |
|         })
 | |
|         .finally(() => {
 | |
|           this.loading = false
 | |
|         })
 | |
|     },
 | |
|     removeGenre(genre) {
 | |
|       this.loading = true
 | |
| 
 | |
|       this.$axios
 | |
|         .$delete(`/api/genres/${this.$encode(genre)}`)
 | |
|         .then((res) => {
 | |
|           this.$toast.success(this.$getString('MessageItemsUpdated', [res.numItemsUpdated]))
 | |
|           this.genres = this.genres.filter((g) => g !== genre)
 | |
|         })
 | |
|         .catch((error) => {
 | |
|           console.error('Failed to remove genre', error)
 | |
|           this.$toast.error(this.$strings.ToastRemoveFailed)
 | |
|         })
 | |
|         .finally(() => {
 | |
|           this.loading = false
 | |
|         })
 | |
|     },
 | |
|     init() {
 | |
|       this.loading = true
 | |
|       this.$axios
 | |
|         .$get('/api/genres')
 | |
|         .then((data) => {
 | |
|           this.genres = (data.genres || []).sort((a, b) => a.localeCompare(b, undefined, { sensitivity: 'base' }))
 | |
|         })
 | |
|         .catch((error) => {
 | |
|           console.error('Failed to load genres', error)
 | |
|         })
 | |
|         .finally(() => {
 | |
|           this.loading = false
 | |
|         })
 | |
|     }
 | |
|   },
 | |
|   mounted() {
 | |
|     this.init()
 | |
|   },
 | |
|   beforeDestroy() {}
 | |
| }
 | |
| </script>
 |