mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2024-12-24 19:11:44 +01:00
f0e70ed27b
* Update: `pages/items/_id` toast messages * Update: account modal strings * Update: audio file data modal strings * Update: sleep timer set string * Update: loading indicator string * Update: lazy book card strings * Reorder keys * Fix: syntax error in LazyBookCard * Fix: json ordering * Fix: fix double message definition * Update: login form toast strings * Update: batch delete toast * Update: collection add toast messages * Replace: toasts in BookShelfToolbar * Update: playlist edit toasts * Update: Details tab * Add: title required string * Update: ereader toasts * Update: author toasts, title and name required toasts * Clean up "no updates" strings * Change: slug strings * Update: cover modal toasts * Change: cancel encode toasts * Change: failed to share toasts * Simplify: "renameFail" and "removeFail" toasts * Fix: ordering * Change: chapters remove toast * Update: notification strings * Revert: loading indicator (error in browser) * Update: collectionBooksTable toast * Update: "failed to get" strings * Update: backup strings * Update: custom provider strings * Update: sessions strings * Update: email strings * Update sort display translation strings, update podcast episode queue strings to use translation * Fix loading indicator please wait translation * Consolidate translations and reduce number of toasts --------- Co-authored-by: advplyr <advplyr@protonmail.com>
177 lines
5.9 KiB
Vue
177 lines
5.9 KiB
Vue
<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-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="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 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>
|