From d6f2c7d4c41d0bdc425a2bc0ccf826281d5c054f Mon Sep 17 00:00:00 2001 From: Eliot Berriot <contact@eliotberriot.com> Date: Mon, 19 Mar 2018 17:39:03 +0100 Subject: [PATCH] Form, modal and player icon to add track to playlist --- front/src/components/audio/Player.vue | 11 ++- front/src/components/playlists/Form.vue | 90 ++++++++++++++++++ .../components/playlists/PlaylistModal.vue | 91 +++++++++++++++++++ .../playlists/TrackPlaylistIcon.vue | 40 ++++++++ 4 files changed, 231 insertions(+), 1 deletion(-) create mode 100644 front/src/components/playlists/Form.vue create mode 100644 front/src/components/playlists/PlaylistModal.vue create mode 100644 front/src/components/playlists/TrackPlaylistIcon.vue diff --git a/front/src/components/audio/Player.vue b/front/src/components/audio/Player.vue index 0c5ed44b6f..75a01c52e0 100644 --- a/front/src/components/audio/Player.vue +++ b/front/src/components/audio/Player.vue @@ -30,7 +30,12 @@ </router-link> </div> <div class="description"> - <track-favorite-icon :track="currentTrack"></track-favorite-icon> + <track-favorite-icon + v-if="$store.state.auth.authenticated" + :track="currentTrack"></track-favorite-icon> + <track-playlist-icon + v-if="$store.state.auth.authenticated" + :track="currentTrack"></track-playlist-icon> </div> </div> </div> @@ -140,11 +145,13 @@ import ColorThief from '@/vendor/color-thief' import Track from '@/audio/track' import AudioTrack from '@/components/audio/Track' import TrackFavoriteIcon from '@/components/favorites/TrackFavoriteIcon' +import TrackPlaylistIcon from '@/components/playlists/TrackPlaylistIcon' export default { name: 'player', components: { TrackFavoriteIcon, + TrackPlaylistIcon, GlobalEvents, AudioTrack }, @@ -281,6 +288,7 @@ export default { cursor: pointer } .track-area { + margin-top: 0; .header, .meta, .artist, .album { color: white !important; } @@ -384,4 +392,5 @@ export default { .ui.feed.icon { margin: 0; } + </style> diff --git a/front/src/components/playlists/Form.vue b/front/src/components/playlists/Form.vue new file mode 100644 index 0000000000..8b39c6f6c8 --- /dev/null +++ b/front/src/components/playlists/Form.vue @@ -0,0 +1,90 @@ +<template> + <form class="ui form" @submit.prevent="submit()"> + <h4 class="ui header">Create a new playlist</h4> + <div v-if="success" class="ui positive message"> + <div class="header">Playlist created</div> + </div> + <div v-if="errors.length > 0" class="ui negative message"> + <div class="header">We cannot create the playlist</div> + <ul class="list"> + <li v-for="error in errors">{{ error }}</li> + </ul> + </div> + <div class="fields"> + <div class="field"> + <label>Playlist name</label> + <input v-model="name" required type="text" placeholder="My awesome playlist" /> + </div> + <div class="field"> + <label>Playlist visibility</label> + <select class="ui dropdown" v-model="privacyLevel"> + <option :value="c.value" v-for="c in privacyLevelChoices">{{ c.label }}</option> + </select> + </div> + </div> + <button :class="['ui', {'loading': isLoading}, 'button']" type="submit">Create playlist</button> + </form> +</template> + +<script> +import $ from 'jquery' +import axios from 'axios' + +import logger from '@/logging' + +export default { + mounted () { + $(this.$el).find('.dropdown').dropdown() + }, + data () { + return { + privacyLevel: this.$store.state.auth.profile.privacy_level, + name: '', + errors: [], + success: false, + isLoading: false, + privacyLevelChoices: [ + { + value: 'me', + label: 'Nobody except me' + }, + { + value: 'instance', + label: 'Everyone on this instance' + }, + { + value: 'everyone', + label: 'Everyone' + } + ] + } + }, + methods: { + submit () { + this.isLoading = true + this.success = false + this.errors = [] + let self = this + let payload = { + name: this.name, + privacy_level: this.privacyLevel + } + let url = `playlists/` + return axios.post(url, payload).then(response => { + logger.default.info('Successfully created playlist') + self.success = true + self.isLoading = false + self.$store.dispatch('playlists/fetchOwn') + }, error => { + logger.default.error('Error while creating playlist') + self.isLoading = false + self.errors = error.backendErrors + }) + } + } +} +</script> + +<!-- Add "scoped" attribute to limit CSS to this component only --> +<style scoped> +</style> diff --git a/front/src/components/playlists/PlaylistModal.vue b/front/src/components/playlists/PlaylistModal.vue new file mode 100644 index 0000000000..2e16270014 --- /dev/null +++ b/front/src/components/playlists/PlaylistModal.vue @@ -0,0 +1,91 @@ +<template> + <modal @update:show="update" :show="show"> + <div class="header"> + Add track "{{ track.title }}" by {{ track.artist.name }} to playlist + </div> + <div class="content"> + <div class="description"> + <playlist-form></playlist-form> + <div class="ui divider"></div> + <div v-if="errors.length > 0" class="ui negative message"> + <div class="header">We cannot add the track to a playlist</div> + <ul class="list"> + <li v-for="error in errors">{{ error }}</li> + </ul> + </div> + <div class="ui items"> + <div class="item" v-for="playlist in sortedPlaylists"> + <div class="content"> + <div class="header">{{ playlist.name }}</div> + <div class="meta"> + <span class="tracks">45 tracks</span> + </div> + <div class="extra"> + <div class="ui basic green button" @click="addToPlaylist(playlist.id)"> + Add to this playlist + </div> + </div> + </div> + </div> + </div> + </div> + </div> + </modal> +</template> + +<script> +import axios from 'axios' +import {mapState} from 'vuex' + +import logger from '@/logging' +import Modal from '@/components/semantic/Modal' +import PlaylistForm from '@/components/playlists/Form' + +export default { + components: { + Modal, + PlaylistForm + }, + props: { + track: {type: Object}, + show: {type: Boolean} + }, + data () { + return { + errors: [] + } + }, + methods: { + update (v) { + this.$emit('update:show', v) + }, + addToPlaylist (playlistId) { + let self = this + let payload = { + track: this.track.id, + playlist: playlistId + } + return axios.post('playlist-tracks/', payload).then(response => { + logger.default.info('Successfully added track to playlist') + self.$emit('update:show', false) + self.$store.dispatch('playlists/fetchOwn') + }, error => { + logger.default.error('Error while adding track to playlist') + self.errors = error.backendErrors + }) + } + }, + computed: { + ...mapState({ + playlists: state => state.playlists.playlists + }), + sortedPlaylists () { + return this.playlists + } + } +} +</script> + +<!-- Add "scoped" attribute to limit CSS to this component only --> +<style scoped> +</style> diff --git a/front/src/components/playlists/TrackPlaylistIcon.vue b/front/src/components/playlists/TrackPlaylistIcon.vue new file mode 100644 index 0000000000..2684f7cb63 --- /dev/null +++ b/front/src/components/playlists/TrackPlaylistIcon.vue @@ -0,0 +1,40 @@ +<template> + <button + @click="showModal = true" + v-if="button" + :class="['ui', 'button']"> + <i class="list icon"></i> + Add to playlist... + <playlist-modal :track="track" :show.sync="showModal"></playlist-modal> + </button> + <i + v-else + @click="showModal = true" + :class="['favorite-icon', 'list', 'link', 'icon']" + title="Add to playlist..."> + <playlist-modal :track="track" :show.sync="showModal"></playlist-modal> + </i> +</template> + +<script> +import PlaylistModal from '@/components/playlists/PlaylistModal' + +export default { + components: { + PlaylistModal + }, + props: { + track: {type: Object}, + button: {type: Boolean, default: false} + }, + data () { + return { + showModal: false + } + } +} +</script> + +<!-- Add "scoped" attribute to limit CSS to this component only --> +<style scoped> +</style> -- GitLab