mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-12-26 13:49:38 +00:00
Add:Manage genres #1163
This commit is contained in:
parent
106ddc9541
commit
4971787482
6 changed files with 260 additions and 6 deletions
169
client/pages/config/item-metadata-utils/genres.vue
Normal file
169
client/pages/config/item-metadata-utils/genres.vue
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
<template>
|
||||
<div class="bg-bg rounded-md shadow-lg border border-white border-opacity-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 hover:bg-opacity-10 text-center">
|
||||
<span class="material-icons text-2xl">arrow_back</span>
|
||||
</nuxt-link>
|
||||
|
||||
<h1 class="text-xl mx-2">Manage Genres</h1>
|
||||
</div>
|
||||
|
||||
<p v-if="!genres.length && !loading" class="text-center py-8 text-lg">No Genres</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="flex-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="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 flex items-center justify-center">
|
||||
<ui-loading-indicator />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
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 = `Are you sure you want to rename genre "${this.editingGenre}" to "${this.newGenreName}" for all items?`
|
||||
if (genreNameExists) {
|
||||
message += '<br><span class="text-sm">Note: This genre already exists so the two genres will be merged.</span>'
|
||||
} else if (genreNameExistsOfDifferentCase) {
|
||||
message += `<br><span class="text-warning text-sm">Warning! A similar genre with a different casing already exists "${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(`${res.numItemsUpdated} Items Updated`)
|
||||
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('Failed to rename genre')
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
removeGenre(genre) {
|
||||
this.loading = true
|
||||
|
||||
this.$axios
|
||||
.$delete(`/api/genres/${this.$encode(genre)}`)
|
||||
.then((res) => {
|
||||
this.$toast.success(`${res.numItemsUpdated} Items Updated`)
|
||||
this.genres = this.genres.filter((g) => g !== genre)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to remove genre', error)
|
||||
this.$toast.error('Failed to remove genre')
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
init() {
|
||||
this.loading = true
|
||||
this.$axios
|
||||
.$get('/api/genres')
|
||||
.then((data) => {
|
||||
this.genres = data.genres || []
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to load genres', error)
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.init()
|
||||
},
|
||||
beforeDestroy() {}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -7,12 +7,12 @@
|
|||
<span class="material-icons">arrow_forward</span>
|
||||
</div>
|
||||
</nuxt-link>
|
||||
<!-- <nuxt-link to="/config/item-metadata-utils/tags" class="block w-full rounded bg-primary/40 hover:bg-primary/60 text-gray-300 hover:text-white p-4 my-2">
|
||||
<nuxt-link to="/config/item-metadata-utils/genres" class="block w-full rounded bg-primary/40 hover:bg-primary/60 text-gray-300 hover:text-white p-4 my-2">
|
||||
<div class="flex justify-between">
|
||||
<p>Manage Genres</p>
|
||||
<span class="material-icons">arrow_forward</span>
|
||||
</div>
|
||||
</nuxt-link> -->
|
||||
</nuxt-link>
|
||||
</app-settings-content>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -76,8 +76,6 @@ export default {
|
|||
|
||||
const tagNameExists = this.tags.find((t) => t !== this.editingTag && t === this.newTagName)
|
||||
const tagNameExistsOfDifferentCase = !tagNameExists ? this.tags.find((t) => t !== this.editingTag && t.toLowerCase() === this.newTagName.toLowerCase()) : null
|
||||
console.log('Tag name', this.newTagName, 'ExistS?', tagNameExists, tagNameExistsOfDifferentCase)
|
||||
console.log('Saving tag', this.editingTag, 'with name', this.newTagName)
|
||||
|
||||
let message = `Are you sure you want to rename tag "${this.editingTag}" to "${this.newTagName}" for all items?`
|
||||
if (tagNameExists) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue