<template> <v-list height='calc(100vh - 145px)' class='overflow-y-auto' v-scroll.self='scroll'> <v-card class='d-flex'> <v-img :src='playlist.image.full' :lazy-src="playlist.image.thumb" max-height="100%" max-width="35vh" contain ></v-img> <div class='pl-4'> <v-overlay absolute :value="loading" z-index="3" opacity='0.9'> <v-progress-circular indeterminate></v-progress-circular> </v-overlay> <h1>{{playlist.title}}</h1> <h3>{{playlist.user.name}}</h3> <h5>{{playlist.description}}</h5> <div class='mt-2' v-if='!loading'> <span class='text-subtitle-2'>{{playlist.trackCount}} {{$t("tracks")}}</span><br> <span class='text-subtitle-2'>{{$t("Duration")}}: {{$duration(playlist.duration)}}</span><br> <span class='text-subtitle-2'>{{$numberString(playlist.fans)}} {{$t('fans')}}</span><br> </div> <div class='my-2 d-flex'> <v-btn color='primary' class='mr-1' @click='play'> <v-icon left>mdi-play</v-icon> {{$t('Play')}} </v-btn> <v-btn color='red' class='mx-1' @click='library' :loading='libraryLoading' v-if='!isOwn'> <div v-if='!playlist.library'> <v-icon left>mdi-heart</v-icon> {{$t('Library')}} </div> <div v-if='playlist.library'> <v-icon left>mdi-heart-remove</v-icon> {{$t('Remove')}} </div> </v-btn> <v-btn color='green' class='mx-1' @click='download'> <v-icon left>mdi-download</v-icon> {{$t('Download')}} </v-btn> <v-btn color='red' class='mx-1' v-if='isOwn' @click='deleteDialog = true'> <v-icon left>mdi-delete</v-icon> {{$t('Delete')}} </v-btn> <div class='mx-2' dense stlye='max-width: 120px;'> <v-select class='px-2' dense solo :items='sortTypes' @change='sort' :label='$t("Sort by")'> </v-select> </div> <div class='px-2' @click='reverseSort'> <v-btn icon> <v-icon v-if='isReversed'>mdi-sort-reverse-variant</v-icon> <v-icon v-if='!isReversed'>mdi-sort-variant</v-icon> </v-btn> </div> </div> </div> </v-card> <h1 class='my-2 px-2'>Tracks</h1> <v-lazy v-for='(track, index) in playlist.tracks' :key='index.toString() + "-" + track.id' ><TrackTile :track='track' @click='playIndex(index)' :playlistId='playlist.id' @remove='trackRemoved(index)' ></TrackTile> </v-lazy> <div class='text-center' v-if='loadingTracks'> <v-progress-circular indeterminate></v-progress-circular> </div> <DownloadDialog :playlistName='playlist.title' :tracks='playlist.tracks' v-if='downloadDialog' @close='downloadDialog = false'></DownloadDialog> <!-- Delete playlist --> <v-dialog v-model='deleteDialog' max-width='256px'> <v-card> <v-card-title> {{$t("Delete")}} </v-card-title> <v-card-text> {{$t("Are you sure you want to delete this playlist?")}} </v-card-text> <v-card-actions> <v-spacer></v-spacer> <v-btn text @click='deleteDialog = false' class='mx-2'>{{$t("Cancel")}}</v-btn> <v-btn text color='red' @click='deleteDialog = false; deletePlaylist();'>{{$t("Delete")}}</v-btn> </v-card-actions> </v-card> </v-dialog> </v-list> </template> <script> import TrackTile from '@/components/TrackTile.vue'; import DownloadDialog from '@/components/DownloadDialog.vue'; export default { name: 'PlaylistTile', components: { TrackTile, DownloadDialog }, props: { playlistData: Object, }, data() { return { //Props cannot be modified playlist: this.playlistData, //Initial loading loading: false, loadingTracks: false, //Add to library button libraryLoading: false, downloadDialog: false, deleteDialog: false, //Sort sortTypes: [ this.$t('Date Added'), this.$t('Name (A-Z)'), this.$t('Artist (A-Z)'), this.$t('Album (A-Z)') ], isReversed: false } }, methods: { async playIndex(index) { //Load tracks if (this.playlist.tracks.length == 0) await this.loadAllTracks(); this.$root.queue.source = { text: this.playlist.title, source: 'playlist', data: this.playlist.id }; this.$root.replaceQueue(this.playlist.tracks); this.$root.playIndex(index); //Load rest of tracks on background if (this.playlist.tracks.length < this.playlist.trackCount) { this.loadAllTracks().then(() => { this.$root.replaceQueue(this.playlist.tracks); }); } }, play() { this.playIndex(0); }, scroll(event) { let loadOffset = event.target.scrollHeight - event.target.offsetHeight - 100; if (event.target.scrollTop > loadOffset) { if (!this.loadingTracks && !this.loading) this.loadTracks(); } }, //Lazy loading async loadTracks() { if (this.playlist.tracks.length >= this.playlist.trackCount) return; this.loadingTracks = true; let offset = this.playlist.tracks.length; let res = await this.$axios.get(`/playlist/${this.playlist.id}?start=${offset}`); if (res.data && res.data.tracks) { this.playlist.tracks.push(...res.data.tracks); } this.loadingTracks = false; }, //Load all the tracks async loadAllTracks() { this.loadingTracks = true; let data = await this.$axios.get(`/playlist/${this.playlist.id}?full=iguess`); if (data && data.data && data.data.tracks) { this.playlist.tracks.push(...data.data.tracks.slice(this.playlist.tracks.length)); } this.loadingTracks = false; }, async library() { this.libraryLoading = true; if (this.playlist.library) { //Remove await this.$axios.delete('/library/playlist?id=' + this.playlist.id); this.$root.globalSnackbar = this.$t('Removed from library!'); this.playlist.library = false; } else { //Add await this.$axios.put(`/library/playlist?id=${this.playlist.id}`); this.$root.globalSnackbar = this.$t('Added to library!'); this.playlist.library = true; } this.libraryLoading = false; }, async initialLoad() { //Load meta and intial tracks if (this.playlist.tracks.length < this.playlist.trackCount) { this.loading = true; let data = await this.$axios.get(`/playlist/${this.playlist.id}?start=0`); if (data && data.data && data.data.tracks) { //Preserve library state let inLib = this.playlist.library; this.playlist = data.data; this.playlist.library = inLib; } this.loading = false; } }, //On track removed trackRemoved(index) { this.playlist.tracks.splice(index, 1); }, async download() { //Load all tracks if (this.playlist.tracks.length < this.playlist.trackCount) { await this.loadAllTracks(); } this.downloadDialog = true; }, async deletePlaylist() { await this.$axios.delete(`/playlist/${this.playlist.id}`); this.$router.go(-1); }, //Sort changed async sort(type) { let index = this.sortTypes.indexOf(type); //Preload all tracks if (this.playlist.tracks.length < this.playlist.trackCount) await this.loadAllTracks(); //Copy original if (!this.tracksUnsorted) this.tracksUnsorted = JSON.parse(JSON.stringify(this.playlist.tracks)); //Using indexes, so it can be translated later this.isReversed = false; switch (index) { //Default case 0: this.tracks = JSON.parse(JSON.stringify(this.tracksUnsorted)); break; //Name case 1: this.tracks = this.playlist.tracks.sort((a, b) => {return a.title.localeCompare(b.title);}); break; //Artist case 2: this.tracks = this.playlist.tracks.sort((a, b) => {return a.artistString.localeCompare(b.artistString);}); break; //Album case 3: this.tracks = this.playlist.tracks.sort((a, b) => {return a.album.title.localeCompare(b.album.title);}); break; } }, async reverseSort() { //Preload tracks if not sorted yet if (this.playlist.tracks.length < this.playlist.trackCount) await this.sort(0); this.isReversed = !this.isReversed; this.tracks.reverse(); }, }, mounted() { this.initialLoad(); }, computed: { isOwn() { if (this.$root.profile.id == this.playlist.user.id) return true; return false; } }, watch: { //Reload on playlist change from drawer playlistData(n, o) { if (n.id == o.id) return; this.playlist = this.playlistData; this.loading = false; this.initialLoad(); } } } </script>