From 7df9112d551a77776a281755a73abc1c1f44fbc4 Mon Sep 17 00:00:00 2001 From: Eliot Berriot <contact@eliotberriot.com> Date: Tue, 29 May 2018 00:07:38 +0200 Subject: [PATCH] See #223: front-end to browse/manage library files --- front/src/components/Sidebar.vue | 6 + .../components/manage/library/FilesTable.vue | 205 ++++++++++++++++++ front/src/router/index.js | 13 ++ front/src/views/admin/library/Base.vue | 28 +++ front/src/views/admin/library/FilesList.vue | 23 ++ 5 files changed, 275 insertions(+) create mode 100644 front/src/components/manage/library/FilesTable.vue create mode 100644 front/src/views/admin/library/Base.vue create mode 100644 front/src/views/admin/library/FilesList.vue diff --git a/front/src/components/Sidebar.vue b/front/src/components/Sidebar.vue index e8f330c3..72c55847 100644 --- a/front/src/components/Sidebar.vue +++ b/front/src/components/Sidebar.vue @@ -68,6 +68,12 @@ :title="$t('Pending import requests')"> {{ notifications.importRequests }}</div> </router-link> + <router-link + class="item" + v-if="$store.state.auth.availablePermissions['library']" + :to="{name: 'manage.library.files'}"> + <i class="book icon"></i>{{ $t('Library') }} + </router-link> <router-link class="item" v-else-if="$store.state.auth.availablePermissions['upload']" diff --git a/front/src/components/manage/library/FilesTable.vue b/front/src/components/manage/library/FilesTable.vue new file mode 100644 index 00000000..7d230cc8 --- /dev/null +++ b/front/src/components/manage/library/FilesTable.vue @@ -0,0 +1,205 @@ +<template> + <div> + <div class="ui inline form"> + <div class="fields"> + <div class="ui field"> + <label>{{ $t('Search') }}</label> + <input type="text" v-model="search" placeholder="Search by title, artist, domain..." /> + </div> + <div class="field"> + <i18next tag="label" path="Ordering"/> + <select class="ui dropdown" v-model="ordering"> + <option v-for="option in orderingOptions" :value="option[0]"> + {{ option[1] }} + </option> + </select> + </div> + <div class="field"> + <i18next tag="label" path="Ordering direction"/> + <select class="ui dropdown" v-model="orderingDirection"> + <option value="+">Ascending</option> + <option value="-">Descending</option> + </select> + </div> + </div> + </div> + <div class="dimmable"> + <div v-if="isLoading" class="ui active inverted dimmer"> + <div class="ui loader"></div> + </div> + <action-table + v-if="result" + @action-launched="fetchData" + :objects-data="result" + :actions="actions" + :action-url="'manage/library/track-files/action/'" + :filters="actionFilters"> + <template slot="header-cells"> + <th>{{ $t('Title') }}</th> + <th>{{ $t('Artist') }}</th> + <th>{{ $t('Album') }}</th> + <th>{{ $t('Import date') }}</th> + <th>{{ $t('Type') }}</th> + <th>{{ $t('Bitrate') }}</th> + <th>{{ $t('Duration') }}</th> + <th>{{ $t('Size') }}</th> + </template> + <template slot="row-cells" slot-scope="scope"> + <td> + <span :title="scope.obj.track.title">{{ scope.obj.track.title|truncate(30) }}</span> + </td> + <td> + <span :title="scope.obj.track.artist.name">{{ scope.obj.track.artist.name|truncate(30) }}</span> + </td> + <td> + <span :title="scope.obj.track.album.title">{{ scope.obj.track.album.title|truncate(20) }}</span> + </td> + <td> + <human-date :date="scope.obj.creation_date"></human-date> + </td> + <td v-if="scope.obj.audio_mimetype"> + {{ scope.obj.audio_mimetype }} + </td> + <td v-else> + {{ $t('N/A') }} + </td> + <td v-if="scope.obj.bitrate"> + {{ scope.obj.bitrate | humanSize }}/s + </td> + <td v-else> + {{ $t('N/A') }} + </td> + <td v-if="scope.obj.duration"> + {{ time.parse(scope.obj.duration) }} + </td> + <td v-else> + {{ $t('N/A') }} + </td> + <td v-if="scope.obj.size"> + {{ scope.obj.size | humanSize }} + </td> + <td v-else> + {{ $t('N/A') }} + </td> + </template> + </action-table> + </div> + <div> + <pagination + v-if="result && result.results.length > 0" + @page-changed="selectPage" + :compact="true" + :current="page" + :paginate-by="paginateBy" + :total="result.count" + ></pagination> + + <span v-if="result && result.results.length > 0"> + {{ $t('Showing results {%start%}-{%end%} on {%total%}', {start: ((page-1) * paginateBy) + 1 , end: ((page-1) * paginateBy) + result.results.length, total: result.count})}} + </span> + </div> + </div> +</template> + +<script> +import axios from 'axios' +import _ from 'lodash' +import time from '@/utils/time' +import Pagination from '@/components/Pagination' +import ActionTable from '@/components/common/ActionTable' +import OrderingMixin from '@/components/mixins/Ordering' + +export default { + mixins: [OrderingMixin], + props: { + filters: {type: Object, required: false} + }, + components: { + Pagination, + ActionTable + }, + data () { + let defaultOrdering = this.getOrderingFromString(this.defaultOrdering || '-creation_date') + return { + time, + isLoading: false, + result: null, + page: 1, + paginateBy: 25, + search: '', + orderingDirection: defaultOrdering.direction || '+', + ordering: defaultOrdering.field, + orderingOptions: [ + ['creation_date', 'Creation date'], + ['accessed_date', 'Accessed date'], + ['modification_date', 'Modification date'], + ['size', 'Size'], + ['bitrate', 'Bitrate'], + ['duration', 'Duration'] + ] + + } + }, + created () { + this.fetchData() + }, + methods: { + fetchData () { + let params = _.merge({ + 'page': this.page, + 'page_size': this.paginateBy, + 'q': this.search, + 'ordering': this.getOrderingAsString() + }, this.filters) + let self = this + self.isLoading = true + self.checked = [] + axios.get('/manage/library/track-files/', {params: params}).then((response) => { + self.result = response.data + self.isLoading = false + }, error => { + self.isLoading = false + self.errors = error.backendErrors + }) + }, + selectPage: function (page) { + this.page = page + } + }, + computed: { + actionFilters () { + var currentFilters = { + q: this.search + } + if (this.filters) { + return _.merge(currentFilters, this.filters) + } else { + return currentFilters + } + }, + actions () { + return [ + { + name: 'delete', + label: this.$t('Delete') + } + ] + } + }, + watch: { + search (newValue) { + this.page = 1 + this.fetchData() + }, + page () { + this.fetchData() + }, + ordering () { + this.fetchData() + }, + orderingDirection () { + this.fetchData() + } + } +} +</script> diff --git a/front/src/router/index.js b/front/src/router/index.js index f71dab7f..a52070e3 100644 --- a/front/src/router/index.js +++ b/front/src/router/index.js @@ -29,6 +29,8 @@ import PlaylistDetail from '@/views/playlists/Detail' import PlaylistList from '@/views/playlists/List' import Favorites from '@/components/favorites/List' import AdminSettings from '@/views/admin/Settings' +import AdminLibraryBase from '@/views/admin/library/Base' +import AdminLibraryFilesList from '@/views/admin/library/FilesList' import FederationBase from '@/views/federation/Base' import FederationScan from '@/views/federation/Scan' import FederationLibraryDetail from '@/views/federation/LibraryDetail' @@ -167,6 +169,17 @@ export default new Router({ { path: 'libraries/:id', name: 'federation.libraries.detail', component: FederationLibraryDetail, props: true } ] }, + { + path: '/manage/library', + component: AdminLibraryBase, + children: [ + { + path: 'files', + name: 'manage.library.files', + component: AdminLibraryFilesList + } + ] + }, { path: '/library', component: Library, diff --git a/front/src/views/admin/library/Base.vue b/front/src/views/admin/library/Base.vue new file mode 100644 index 00000000..834fca92 --- /dev/null +++ b/front/src/views/admin/library/Base.vue @@ -0,0 +1,28 @@ +<template> + <div class="main pusher" v-title="'Manage library'"> + <div class="ui secondary pointing menu"> + <router-link + class="ui item" + :to="{name: 'manage.library.files'}">{{ $t('Files') }}</router-link> + </div> + <router-view :key="$route.fullPath"></router-view> + </div> +</template> + +<script> +export default {} +</script> + +<style lang="scss"> +@import '../../../style/vendor/media'; + +.main.pusher > .ui.secondary.menu { + @include media(">tablet") { + margin: 0 2.5rem; + } + .item { + padding-top: 1.5em; + padding-bottom: 1.5em; + } +} +</style> diff --git a/front/src/views/admin/library/FilesList.vue b/front/src/views/admin/library/FilesList.vue new file mode 100644 index 00000000..9c52de57 --- /dev/null +++ b/front/src/views/admin/library/FilesList.vue @@ -0,0 +1,23 @@ +<template> + <div v-title="'Files'"> + <div class="ui vertical stripe segment"> + <h2 class="ui header">{{ $t('Library files') }}</h2> + <div class="ui hidden divider"></div> + <library-files-table :show-library="true"></library-files-table> + </div> + </div> +</template> + +<script> +import LibraryFilesTable from '@/components/manage/library/FilesTable' + +export default { + components: { + LibraryFilesTable + } +} +</script> + +<!-- Add "scoped" attribute to limit CSS to this component only --> +<style scoped> +</style> -- GitLab