audiobookshelf/client/pages/playlist/_id.vue

188 lines
6.5 KiB
Vue
Raw Normal View History

2022-11-27 00:58:52 +01:00
<template>
<div id="page-wrapper" class="bg-bg page overflow-hidden" :class="streamLibraryItem ? 'streaming' : ''">
<div class="w-full h-full overflow-y-auto px-2 py-6 md:p-8">
<div class="flex flex-col sm:flex-row max-w-6xl mx-auto">
<div class="w-full flex justify-center md:block sm:w-32 md:w-52" style="min-width: 200px">
2022-11-27 00:58:52 +01:00
<div class="relative" style="height: fit-content">
2022-11-27 22:12:55 +01:00
<covers-playlist-cover :items="playlistItems" :width="200" :height="200" />
2022-11-27 00:58:52 +01:00
</div>
</div>
<div class="flex-grow px-2 py-6 md:py-0 md:px-10">
<div class="flex items-end flex-row flex-wrap md:flex-nowrap">
<h1 class="text-2xl md:text-3xl font-sans w-full md:w-fit mb-4 md:mb-0">
{{ playlistName }}
</h1>
<div class="flex-grow" />
<ui-btn v-if="showPlayButton" :disabled="streaming" color="success" :padding-x="4" small class="flex items-center h-9 mr-2" @click="clickPlay">
<span v-show="!streaming" class="material-icons text-2xl -ml-2 pr-1 text-white">play_arrow</span>
{{ streaming ? $strings.ButtonPlaying : $strings.ButtonPlay }}
</ui-btn>
<ui-icon-btn v-if="userCanUpdate" icon="edit" class="mx-0.5" @click="editClick" />
<ui-icon-btn v-if="userCanDelete" icon="delete" class="mx-0.5" @click="removeClick" />
</div>
<div class="my-8 max-w-2xl">
<p class="text-base text-gray-100">{{ description }}</p>
</div>
<tables-playlist-items-table :items="playlistItems" :playlist-id="playlistId" />
</div>
</div>
</div>
<div v-show="processingRemove" class="absolute top-0 left-0 w-full h-full z-10 bg-black bg-opacity-40 flex items-center justify-center">
<ui-loading-indicator />
</div>
</div>
</template>
<script>
export default {
async asyncData({ store, params, app, redirect, route }) {
if (!store.state.user.user) {
return redirect(`/login?redirect=${route.path}`)
}
var playlist = await app.$axios.$get(`/api/playlists/${params.id}`).catch((error) => {
console.error('Failed', error)
return false
})
if (!playlist) {
return redirect('/')
}
2022-11-27 20:34:50 +01:00
// If playlist is a different library then set library as current
if (playlist.libraryId !== store.state.libraries.currentLibraryId) {
await store.dispatch('libraries/fetch', playlist.libraryId)
}
2022-11-27 00:58:52 +01:00
store.commit('libraries/addUpdateUserPlaylist', playlist)
return {
playlistId: playlist.id
}
},
data() {
return {
processingRemove: false
}
},
computed: {
streamLibraryItem() {
return this.$store.state.streamLibraryItem
},
playlistItems() {
return this.playlist.items || []
},
playlistName() {
return this.playlist.name || ''
},
description() {
return this.playlist.description || ''
},
playlist() {
return this.$store.getters['libraries/getPlaylist'](this.playlistId) || {}
},
playableItems() {
return this.playlistItems.filter((item) => {
const libraryItem = item.libraryItem
if (libraryItem.isMissing || libraryItem.isInvalid) return false
if (item.episode) return item.episode.audioFile
return libraryItem.media.tracks.length
})
},
streaming() {
return !!this.playableItems.find((i) => this.$store.getters['getIsMediaStreaming'](i.libraryItemId, i.episodeId))
},
showPlayButton() {
return this.playableItems.length
},
userCanUpdate() {
return this.$store.getters['user/getUserCanUpdate']
},
userCanDelete() {
return this.$store.getters['user/getUserCanDelete']
}
},
methods: {
editClick() {
this.$store.commit('globals/setEditPlaylist', this.playlist)
},
removeClick() {
if (confirm(`Are you sure you want to remove playlist "${this.playlistName}"?`)) {
this.processingRemove = true
var playlistName = this.playlistName
this.$axios
.$delete(`/api/playlists/${this.playlist.id}`)
.then(() => {
this.processingRemove = false
this.$toast.success(`Playlist "${playlistName}" Removed`)
})
.catch((error) => {
console.error('Failed to remove playlist', error)
this.processingRemove = false
this.$toast.error(`Failed to remove playlist`)
})
}
},
clickPlay() {
const queueItems = []
// Playlist queue will start at the first unfinished item
// if all items are finished then entire playlist is queued
const itemsWithProgress = this.playableItems.map((item) => {
return {
...item,
progress: this.$store.getters['user/getUserMediaProgress'](item.libraryItemId, item.episodeId)
}
})
const hasUnfinishedItems = itemsWithProgress.some((i) => !i.progress || !i.progress.isFinished)
if (!hasUnfinishedItems) {
console.warn('All items in playlist are finished - starting at first item')
}
for (let i = 0; i < itemsWithProgress.length; i++) {
const playlistItem = itemsWithProgress[i]
if (!hasUnfinishedItems || !playlistItem.progress || !playlistItem.progress.isFinished) {
const libraryItem = playlistItem.libraryItem
if (playlistItem.episode) {
queueItems.push({
libraryItemId: libraryItem.id,
libraryId: libraryItem.libraryId,
episodeId: playlistItem.episode.id,
title: playlistItem.episode.title,
subtitle: libraryItem.media.metadata.title,
caption: '',
duration: playlistItem.episode.duration || null,
coverPath: libraryItem.media.coverPath || null
})
} else {
queueItems.push({
libraryItemId: libraryItem.id,
libraryId: libraryItem.libraryId,
episodeId: null,
title: libraryItem.media.metadata.title,
subtitle: libraryItem.media.metadata.authors.map((au) => au.name).join(', '),
caption: '',
duration: libraryItem.media.duration || null,
coverPath: libraryItem.media.coverPath || null
})
}
}
}
if (queueItems.length >= 0) {
this.$eventBus.$emit('play-item', {
libraryItemId: queueItems[0].libraryItemId,
episodeId: queueItems[0].episodeId,
queueItems
})
}
}
},
mounted() {},
beforeDestroy() {}
}
</script>