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>
 |