From f66dff3504fbb43eb6c628e92d50644870772501 Mon Sep 17 00:00:00 2001
From: Eliot Berriot <contact@eliotberriot.com>
Date: Wed, 21 Mar 2018 11:58:53 +0100
Subject: [PATCH] Added playlist list in library

---
 front/src/components/library/Library.vue    |   1 +
 front/src/components/playlists/Card.vue     |  40 +++++
 front/src/components/playlists/CardList.vue |  34 +++++
 front/src/components/playlists/Form.vue     |   5 +-
 front/src/router/index.js                   |  12 ++
 front/src/views/playlists/Detail.vue        |   2 +-
 front/src/views/playlists/List.vue          | 158 ++++++++++++++++++++
 7 files changed, 250 insertions(+), 2 deletions(-)
 create mode 100644 front/src/components/playlists/Card.vue
 create mode 100644 front/src/components/playlists/CardList.vue
 create mode 100644 front/src/views/playlists/List.vue

diff --git a/front/src/components/library/Library.vue b/front/src/components/library/Library.vue
index c209221274..161d4519b2 100644
--- a/front/src/components/library/Library.vue
+++ b/front/src/components/library/Library.vue
@@ -4,6 +4,7 @@
       <router-link class="ui item" to="/library" exact>Browse</router-link>
       <router-link class="ui item" to="/library/artists" exact>Artists</router-link>
       <router-link class="ui item" to="/library/radios" exact>Radios</router-link>
+      <router-link class="ui item" to="/library/playlists" exact>Playlists</router-link>
       <div class="ui secondary right menu">
         <router-link v-if="$store.state.auth.authenticated" class="ui item" to="/library/requests/" exact>
           Requests
diff --git a/front/src/components/playlists/Card.vue b/front/src/components/playlists/Card.vue
new file mode 100644
index 0000000000..6dd1b0a0ce
--- /dev/null
+++ b/front/src/components/playlists/Card.vue
@@ -0,0 +1,40 @@
+<template>
+  <div class="ui card">
+    <div class="content">
+      <div class="header">
+        <router-link class="discrete link" :to="{name: 'library.playlists.detail', params: {id: playlist.id }}">
+          {{ playlist.name }}
+        </router-link>
+      </div>
+      <div class="meta">
+        <i class="user icon"></i> {{ playlist.user.username }}
+      </div>
+      <div class="meta">
+        <i class="clock icon"></i> Updated <human-date :date="playlist.modification_date"></human-date>
+      </div>
+    </div>
+    <div class="extra content">
+      <span>
+        <i class="sound icon"></i>
+        {{ playlist.tracks_count }} tracks
+      </span>
+      <play-button class="mini basic orange right floated" :playlist="playlist">Play all</play-button>
+    </div>
+  </div>
+</template>
+
+<script>
+import PlayButton from '@/components/audio/PlayButton'
+
+export default {
+  props: ['playlist'],
+  components: {
+    PlayButton
+  }
+}
+</script>
+
+<!-- Add "scoped" attribute to limit CSS to this component only -->
+<style scoped>
+
+</style>
diff --git a/front/src/components/playlists/CardList.vue b/front/src/components/playlists/CardList.vue
new file mode 100644
index 0000000000..4d4746090f
--- /dev/null
+++ b/front/src/components/playlists/CardList.vue
@@ -0,0 +1,34 @@
+<template>
+  <div
+    v-if="playlists.length > 0"
+    v-masonry
+    transition-duration="0"
+    item-selector=".column"
+    percent-position="true"
+    stagger="0"
+    class="ui stackable three column doubling grid">
+    <div
+      v-masonry-tile
+      v-for="playlist in playlists"
+      :key="playlist.id"
+      class="column">
+      <playlist-card class="fluid" :playlist="playlist"></playlist-card>
+    </div>
+  </div>
+</template>
+
+<script>
+
+import PlaylistCard from '@/components/playlists/Card'
+
+export default {
+  props: ['playlists'],
+  components: {
+    PlaylistCard
+  }
+}
+</script>
+
+<!-- Add "scoped" attribute to limit CSS to this component only -->
+<style scoped>
+</style>
diff --git a/front/src/components/playlists/Form.vue b/front/src/components/playlists/Form.vue
index 267e2c10e5..21ddfeaec7 100644
--- a/front/src/components/playlists/Form.vue
+++ b/front/src/components/playlists/Form.vue
@@ -21,8 +21,11 @@
           <option :value="c.value" v-for="c in privacyLevelChoices">{{ c.label }}</option>
         </select>
       </div>
+      <div class="field">
+        <label>&nbsp;</label>
+        <button :class="['ui', {'loading': isLoading}, 'button']" type="submit">Create playlist</button>
+      </div>
     </div>
-    <button :class="['ui', {'loading': isLoading}, 'button']" type="submit">Create playlist</button>
   </form>
 </template>
 
diff --git a/front/src/router/index.js b/front/src/router/index.js
index 7cffb6f997..8028444613 100644
--- a/front/src/router/index.js
+++ b/front/src/router/index.js
@@ -22,6 +22,7 @@ import BatchList from '@/components/library/import/BatchList'
 import BatchDetail from '@/components/library/import/BatchDetail'
 import RequestsList from '@/components/requests/RequestsList'
 import PlaylistDetail from '@/views/playlists/Detail'
+import PlaylistList from '@/views/playlists/List'
 import Favorites from '@/components/favorites/List'
 
 Vue.use(Router)
@@ -110,6 +111,17 @@ export default new Router({
         },
         { path: 'radios/build', name: 'library.radios.build', component: RadioBuilder, props: true },
         { path: 'radios/build/:id', name: 'library.radios.edit', component: RadioBuilder, props: true },
+        {
+          path: 'playlists/',
+          name: 'library.playlists.browse',
+          component: PlaylistList,
+          props: (route) => ({
+            defaultOrdering: route.query.ordering,
+            defaultQuery: route.query.query,
+            defaultPaginateBy: route.query.paginateBy,
+            defaultPage: route.query.page
+          })
+        },
         {
           path: 'playlists/:id',
           name: 'library.playlists.detail',
diff --git a/front/src/views/playlists/Detail.vue b/front/src/views/playlists/Detail.vue
index 1f3caa4942..52c38b6181 100644
--- a/front/src/views/playlists/Detail.vue
+++ b/front/src/views/playlists/Detail.vue
@@ -34,7 +34,7 @@
         </dangerous-button>
       </div>
     </div>
-    <div v-if="tracks.length > 0" class="ui vertical stripe segment">
+    <div class="ui vertical stripe segment">
       <template v-if="edit">
         <playlist-editor @tracks-updated="updatePlts" :playlist="playlist" :playlist-tracks="playlistTracks"></playlist-editor>
       </template>
diff --git a/front/src/views/playlists/List.vue b/front/src/views/playlists/List.vue
new file mode 100644
index 0000000000..fc5dcbe54b
--- /dev/null
+++ b/front/src/views/playlists/List.vue
@@ -0,0 +1,158 @@
+<template>
+  <div>
+    <div class="ui vertical stripe segment">
+      <h2 class="ui header">Browsing playlists</h2>
+      <div :class="['ui', {'loading': isLoading}, 'form']">
+        <template v-if="$store.state.auth.authenticated">
+          <button
+            @click="$store.commit('playlists/chooseTrack', null)"
+            class="ui basic green button">Manage your playlists</button>
+          <div class="ui hidden divider"></div>
+        </template>
+        <div class="fields">
+          <div class="field">
+            <label>Search</label>
+            <input type="text" v-model="query" placeholder="Enter an playlist name..."/>
+          </div>
+          <div class="field">
+            <label>Ordering</label>
+            <select class="ui dropdown" v-model="ordering">
+              <option v-for="option in orderingOptions" :value="option[0]">
+                {{ option[1] }}
+              </option>
+            </select>
+          </div>
+          <div class="field">
+            <label>Ordering direction</label>
+            <select class="ui dropdown" v-model="orderingDirection">
+              <option value="">Ascending</option>
+              <option value="-">Descending</option>
+            </select>
+          </div>
+          <div class="field">
+            <label>Results per page</label>
+            <select class="ui dropdown" v-model="paginateBy">
+              <option :value="parseInt(12)">12</option>
+              <option :value="parseInt(25)">25</option>
+              <option :value="parseInt(50)">50</option>
+            </select>
+          </div>
+        </div>
+      </div>
+      <div class="ui hidden divider"></div>
+      <playlist-card-list v-if="result" :playlists="result.results"></playlist-card-list>
+      <div class="ui center aligned basic segment">
+        <pagination
+          v-if="result && result.results.length > 0"
+          @page-changed="selectPage"
+          :current="page"
+          :paginate-by="paginateBy"
+          :total="result.count"
+          ></pagination>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import axios from 'axios'
+import _ from 'lodash'
+import $ from 'jquery'
+
+import OrderingMixin from '@/components/mixins/Ordering'
+import PaginationMixin from '@/components/mixins/Pagination'
+import PlaylistCardList from '@/components/playlists/CardList'
+import Pagination from '@/components/Pagination'
+
+const FETCH_URL = 'playlists/'
+
+export default {
+  mixins: [OrderingMixin, PaginationMixin],
+  props: {
+    defaultQuery: {type: String, required: false, default: ''}
+  },
+  components: {
+    PlaylistCardList,
+    Pagination
+  },
+  data () {
+    let defaultOrdering = this.getOrderingFromString(this.defaultOrdering || '-creation_date')
+    return {
+      isLoading: true,
+      result: null,
+      page: parseInt(this.defaultPage),
+      query: this.defaultQuery,
+      paginateBy: parseInt(this.defaultPaginateBy || 12),
+      orderingDirection: defaultOrdering.direction,
+      ordering: defaultOrdering.field,
+      orderingOptions: [
+        ['creation_date', 'Creation date'],
+        ['modification_date', 'Last modification date'],
+        ['name', 'Name']
+      ]
+    }
+  },
+  created () {
+    this.fetchData()
+  },
+  mounted () {
+    $('.ui.dropdown').dropdown()
+  },
+  methods: {
+    updateQueryString: _.debounce(function () {
+      this.$router.replace({
+        query: {
+          query: this.query,
+          page: this.page,
+          paginateBy: this.paginateBy,
+          ordering: this.getOrderingAsString()
+        }
+      })
+    }, 500),
+    fetchData: _.debounce(function () {
+      var self = this
+      this.isLoading = true
+      let url = FETCH_URL
+      let params = {
+        page: this.page,
+        page_size: this.paginateBy,
+        q: this.query,
+        ordering: this.getOrderingAsString()
+      }
+      axios.get(url, {params: params}).then((response) => {
+        self.result = response.data
+        self.isLoading = false
+      })
+    }, 500),
+    selectPage: function (page) {
+      this.page = page
+    }
+  },
+  watch: {
+    page () {
+      this.updateQueryString()
+      this.fetchData()
+    },
+    paginateBy () {
+      this.updateQueryString()
+      this.fetchData()
+    },
+    ordering () {
+      this.updateQueryString()
+      this.fetchData()
+    },
+    orderingDirection () {
+      this.updateQueryString()
+      this.fetchData()
+    },
+    query () {
+      this.updateQueryString()
+      this.fetchData()
+    }
+  }
+}
+</script>
+
+<!-- Add "scoped" attribute to limit CSS to this component only -->
+<style scoped>
+</style>
-- 
GitLab