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