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">
|
2022-11-27 21:11:17 +01:00
|
|
|
<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">
|
2024-07-09 00:00:16 +02:00
|
|
|
<span v-show="!streaming" class="material-symbols fill text-2xl -ml-2 pr-1 text-white">play_arrow</span>
|
2022-11-27 00:58:52 +01:00
|
|
|
{{ streaming ? $strings.ButtonPlaying : $strings.ButtonPlay }}
|
|
|
|
</ui-btn>
|
|
|
|
|
2024-05-26 19:22:21 +02:00
|
|
|
<ui-icon-btn icon="edit" class="mx-0.5" @click="editClick" />
|
2022-11-27 00:58:52 +01:00
|
|
|
|
2024-05-26 19:22:21 +02:00
|
|
|
<ui-icon-btn icon="delete" class="mx-0.5" @click="removeClick" />
|
2022-11-27 00:58:52 +01:00
|
|
|
</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>
|