audiobookshelf/client/components/modals/playlists/AddCreateModal.vue

214 lines
7.4 KiB
Vue
Raw Normal View History

2022-11-26 23:25:14 +01:00
<template>
<modals-modal v-model="show" name="playlists" :processing="processing" :width="500" :height="'unset'">
<template #outer>
<div class="absolute top-0 left-0 p-5 w-2/3 overflow-hidden pointer-events-none">
<p class="font-book text-3xl text-white truncate">{{ title }}</p>
</div>
</template>
<div ref="container" class="w-full rounded-lg bg-primary box-shadow-md overflow-y-auto overflow-x-hidden" style="max-height: 80vh">
<div v-if="show" class="w-full h-full">
<div class="py-4 px-4">
<h1 v-if="!isBatch" class="text-2xl">{{ $strings.LabelAddToPlaylist }}</h1>
<h1 v-else class="text-2xl">{{ $getString('LabelAddToPlaylistBatch', [selectedPlaylistItems.length]) }}</h1>
</div>
<div class="w-full overflow-y-auto overflow-x-hidden max-h-96">
<transition-group name="list-complete" tag="div">
<template v-for="playlist in sortedPlaylists">
<modals-playlists-user-playlist-item :key="playlist.id" :playlist="playlist" :book-cover-aspect-ratio="bookCoverAspectRatio" class="list-complete-item" @add="addToPlaylist" @remove="removeFromPlaylist" @close="show = false" />
</template>
</transition-group>
</div>
<div v-if="!playlists.length" class="flex h-32 items-center justify-center">
<p class="text-xl">{{ $strings.MessageNoUserPlaylists }}</p>
</div>
<div class="w-full h-px bg-white bg-opacity-10" />
<form @submit.prevent="submitCreatePlaylist">
<div class="flex px-4 py-2 items-center text-center border-b border-white border-opacity-10 text-white text-opacity-80">
<div class="flex-grow px-2">
<ui-text-input v-model="newPlaylistName" :placeholder="$strings.PlaceholderNewPlaylist" class="w-full" />
</div>
<ui-btn type="submit" color="success" :padding-x="4" class="h-10">{{ $strings.ButtonCreate }}</ui-btn>
</div>
</form>
</div>
</div>
</modals-modal>
</template>
<script>
export default {
data() {
return {
newPlaylistName: '',
processing: false
}
},
watch: {
show(newVal) {
if (newVal) {
this.loadPlaylists()
this.newPlaylistName = ''
} else {
this.$store.commit('globals/setSelectedPlaylistItems', null)
}
}
},
computed: {
show: {
get() {
return this.$store.state.globals.showPlaylistsModal
},
set(val) {
this.$store.commit('globals/setShowPlaylistsModal', val)
}
},
title() {
if (!this.selectedPlaylistItems.length) return ''
if (this.isBatch) {
return this.$getString('MessageItemsSelected', [this.selectedPlaylistItems.length])
}
const selectedPlaylistItem = this.selectedPlaylistItems[0]
if (selectedPlaylistItem.episode) {
return selectedPlaylistItem.episode.title
}
return selectedPlaylistItem.libraryItem.media.metadata.title || ''
},
playlists() {
return this.$store.state.user.playlists || []
},
bookCoverAspectRatio() {
return this.$store.getters['libraries/getBookCoverAspectRatio']
},
sortedPlaylists() {
return this.playlists
.map((playlist) => {
const includesItem = !this.selectedPlaylistItems.some((item) => {
return !this.checkIsItemInPlaylist(playlist, item)
})
return {
isItemIncluded: includesItem,
...playlist
}
})
.sort((a, b) => (a.isItemIncluded ? -1 : 1))
},
isBatch() {
return this.selectedPlaylistItems.length > 1
},
selectedPlaylistItems() {
return this.$store.state.globals.selectedPlaylistItems || []
},
currentLibraryId() {
return this.$store.state.libraries.currentLibraryId
}
},
methods: {
checkIsItemInPlaylist(playlist, item) {
if (item.episode) {
return playlist.items.some((i) => i.libraryItemId === item.libraryItem.id && i.episodeId === item.episode.id)
}
return playlist.items.some((i) => i.libraryItemId === item.libraryItem.id)
},
loadPlaylists() {
if (!this.playlists.length) {
this.processing = true
this.$axios
.$get(`/api/playlists`)
.then((data) => {
this.$store.commit('user/setPlaylists', data.playlists || [])
})
.catch((error) => {
console.error('Failed to get playlists', error)
this.$toast.error('Failed to load user playlists')
})
.finally(() => {
this.processing = false
})
}
},
removeFromPlaylist(playlist) {
if (!this.selectedPlaylistItems.length) return
this.processing = true
const itemObjects = this.selectedPlaylistItems.map((pi) => ({ libraryItemId: pi.libraryItem.id, episodeId: pi.episode ? pi.episode.id : null }))
this.$axios
.$post(`/api/playlists/${playlist.id}/batch/remove`, { items: itemObjects })
.then((updatedPlaylist) => {
console.log(`Items removed from playlist`, updatedPlaylist)
this.$toast.success('Playlist item(s) added')
this.processing = false
})
.catch((error) => {
console.error('Failed to remove items from playlist', error)
this.$toast.error('Failed to remove playlist item(s)')
this.processing = false
})
},
addToPlaylist(playlist) {
if (!this.selectedPlaylistItems.length) return
this.processing = true
const itemObjects = this.selectedPlaylistItems.map((pi) => ({ libraryItemId: pi.libraryItem.id, episodeId: pi.episode ? pi.episode.id : null }))
this.$axios
.$post(`/api/playlists/${playlist.id}/batch/add`, { items: itemObjects })
.then((updatedPlaylist) => {
console.log(`Items added to playlist`, updatedPlaylist)
this.$toast.success('Items added to playlist')
this.processing = false
})
.catch((error) => {
console.error('Failed to add items to playlist', error)
this.$toast.error('Failed to add items to playlist')
this.processing = false
})
},
submitCreatePlaylist() {
if (!this.newPlaylistName || !this.selectedPlaylistItems.length) {
return
}
this.processing = true
const itemObjects = this.selectedPlaylistItems.map((pi) => ({ libraryItemId: pi.libraryItem.id, episodeId: pi.episode ? pi.episode.id : null }))
const newPlaylist = {
items: itemObjects,
libraryId: this.currentLibraryId,
name: this.newPlaylistName
}
this.$axios
.$post('/api/playlists', newPlaylist)
.then((data) => {
console.log('New playlist created', data)
this.$toast.success(`Playlist "${data.name}" created`)
this.processing = false
this.newPlaylistName = ''
})
.catch((error) => {
console.error('Failed to create playlist', error)
var errMsg = error.response ? error.response.data || '' : ''
this.$toast.error(`Failed to create playlist: ${errMsg}`)
this.processing = false
})
}
},
mounted() {}
}
</script>
<style>
.list-complete-item {
transition: all 0.8s ease;
}
.list-complete-enter-from,
.list-complete-leave-to {
opacity: 0;
transform: translateY(30px);
}
.list-complete-leave-active {
position: absolute;
}
</style>