From e9e466bcb5c1d089401ef852a59ec77c068e110f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ciar=C3=A1n=20Ainsworth?= <ciaranainsworth@posteo.net> Date: Thu, 17 Oct 2019 14:15:33 +0200 Subject: [PATCH] Added placeholders across the application --- changes/changelog.d/750.enhancement | 1 + front/src/components/audio/album/Widget.vue | 9 +- front/src/components/audio/track/Widget.vue | 13 +- front/src/components/favorites/List.vue | 15 +- front/src/components/library/Albums.vue | 17 + front/src/components/library/Artists.vue | 17 + front/src/components/library/Radios.vue | 19 +- .../manage/moderation/DomainsTable.vue | 8 +- .../playlists/PlaceholderWidget.vue | 18 - .../components/playlists/PlaylistModal.vue | 12 + front/src/components/playlists/Widget.vue | 25 +- .../views/content/libraries/FilesTable.vue | 313 +++++++++++------- front/src/views/playlists/Detail.vue | 14 +- front/src/views/playlists/List.vue | 19 +- front/src/views/radios/Detail.vue | 17 +- 15 files changed, 366 insertions(+), 151 deletions(-) create mode 100644 changes/changelog.d/750.enhancement delete mode 100644 front/src/components/playlists/PlaceholderWidget.vue diff --git a/changes/changelog.d/750.enhancement b/changes/changelog.d/750.enhancement new file mode 100644 index 000000000..5c66ad162 --- /dev/null +++ b/changes/changelog.d/750.enhancement @@ -0,0 +1 @@ +Placeholders will now be shown if no content is available across the application (#750) \ No newline at end of file diff --git a/front/src/components/audio/album/Widget.vue b/front/src/components/audio/album/Widget.vue index c9e395f3c..e5ee7f742 100644 --- a/front/src/components/audio/album/Widget.vue +++ b/front/src/components/audio/album/Widget.vue @@ -35,7 +35,14 @@ </div> </div> </div> - <div v-if="!isLoading && albums.length === 0">No results matching your query.</div> + <template v-if="!isLoading && albums.length === 0"> + <div class="ui placeholder segment"> + <div class="ui icon header"> + <i class="compact disc icon"></i> + No results matching your query + </div> + </div> + </template> </div> </template> diff --git a/front/src/components/audio/track/Widget.vue b/front/src/components/audio/track/Widget.vue index 788d279d0..235659564 100644 --- a/front/src/components/audio/track/Widget.vue +++ b/front/src/components/audio/track/Widget.vue @@ -7,7 +7,7 @@ <button :disabled="!previousPage" @click="fetchData(previousPage)" :class="['ui', {disabled: !previousPage}, 'circular', 'icon', 'basic', 'button']"><i :class="['ui', 'angle up', 'icon']"></i></button> <button :disabled="!nextPage" @click="fetchData(nextPage)" :class="['ui', {disabled: !nextPage}, 'circular', 'icon', 'basic', 'button']"><i :class="['ui', 'angle down', 'icon']"></i></button> <button @click="fetchData(url)" :class="['ui', 'circular', 'icon', 'basic', 'button']"><i :class="['ui', 'refresh', 'icon']"></i></button> - <div class="ui divided unstackable items"> + <div v-if="count > 0" class="ui divided unstackable items"> <div :class="['item', itemClasses]" v-for="object in objects" :key="object.id"> <div class="ui tiny image"> <img v-if="object.track.album.cover.original" v-lazy="$store.getters['instance/absoluteUrl'](object.track.album.cover.medium_square_crop)"> @@ -51,6 +51,17 @@ <div class="ui loader"></div> </div> </div> + <div v-else class="ui placeholder segment"> + <div class="ui icon header"> + <i class="music icon"></i> + <translate translate-context="Content/Home/Placeholder"> + Nothing found + </translate> + </div> + <div v-if="isLoading" class="ui inverted active dimmer"> + <div class="ui loader"></div> + </div> + </div> </div> </template> diff --git a/front/src/components/favorites/List.vue b/front/src/components/favorites/List.vue index 6402d417f..f7d37369c 100644 --- a/front/src/components/favorites/List.vue +++ b/front/src/components/favorites/List.vue @@ -18,7 +18,7 @@ </h2> <radio-button v-if="hasFavorites" type="favorites"></radio-button> </section> - <section class="ui vertical stripe segment"> + <section v-if="hasFavorites" class="ui vertical stripe segment"> <div :class="['ui', {'loading': isLoading}, 'form']"> <div class="fields"> <div class="field"> @@ -46,7 +46,6 @@ </div> </div> </div> - <track-table v-if="results" :tracks="results.results"></track-table> <div class="ui center aligned basic segment"> <pagination @@ -58,6 +57,18 @@ ></pagination> </div> </section> + <div v-else class="ui placeholder segment"> + <div class="ui icon header"> + <i class="broken heart icon"></i> + <translate + translate-context="Content/Home/Placeholder" + >No tracks have been added to your favorites yet</translate> + </div> + <router-link :to="'/library'" class="ui green labeled icon button"> + <i class="headphones icon"></i> + <translate translate-context="Content/*/Verb">Browse the library</translate> + </router-link> + </div> </main> </template> diff --git a/front/src/components/library/Albums.vue b/front/src/components/library/Albums.vue index 7aad836f0..07e20b22e 100644 --- a/front/src/components/library/Albums.vue +++ b/front/src/components/library/Albums.vue @@ -59,6 +59,23 @@ :key="album.id" :album="album"></album-card> </div> + <div v-else class="ui placeholder segment sixteen wide column" style="text-align: center; display: flex; align-items: center"> + <div class="ui icon header"> + <i class="compact disc icon"></i> + <translate translate-context="Content/Albums/Placeholder"> + No results matching your query + </translate> + </div> + <router-link + v-if="$store.state.auth.authenticated" + :to="{name: 'content.index'}" + class="ui green button labeled icon"> + <i class="upload icon"></i> + <translate translate-context="Content/*/Verb"> + Add some music + </translate> + </router-link> + </div> </div> <div class="ui center aligned basic segment"> <pagination diff --git a/front/src/components/library/Artists.vue b/front/src/components/library/Artists.vue index 2ac23ac8e..a56abac14 100644 --- a/front/src/components/library/Artists.vue +++ b/front/src/components/library/Artists.vue @@ -48,6 +48,23 @@ </div> <artist-card :artist="artist" v-for="artist in result.results" :key="artist.id"></artist-card> </div> + <div v-else-if="!isLoading" class="ui placeholder segment sixteen wide column" style="text-align: center; display: flex; align-items: center"> + <div class="ui icon header"> + <i class="compact disc icon"></i> + <translate translate-context="Content/Artists/Placeholder"> + No results matching your query + </translate> + </div> + <router-link + v-if="$store.state.auth.authenticated" + :to="{name: 'content.index'}" + class="ui green button labeled icon"> + <i class="upload icon"></i> + <translate translate-context="Content/*/Verb"> + Add some music + </translate> + </router-link> + </div> <div class="ui center aligned basic segment"> <pagination v-if="result && result.count > paginateBy" diff --git a/front/src/components/library/Radios.vue b/front/src/components/library/Radios.vue index 2d68d9072..a646321bc 100644 --- a/front/src/components/library/Radios.vue +++ b/front/src/components/library/Radios.vue @@ -60,6 +60,23 @@ </div> </div> <div class="ui hidden divider"></div> + <div v-if="result && !result.results.length > 0" class="ui placeholder segment"> + <div class="ui icon header"> + <i class="feed icon"></i> + <translate translate-context="Content/Radios/Placeholder"> + No results matching your query + </translate> + </div> + <router-link + v-if="$store.state.auth.authenticated" + :to="{name: 'library.radios.build'}" + class="ui green button labeled icon"> + <i class="rss icon"></i> + <translate translate-context="Content/*/Verb"> + Create a radio + </translate> + </router-link> + </div> <div v-if="result" v-masonry @@ -76,7 +93,7 @@ v-for="radio in result.results" :key="radio.id" :custom-radio="radio"></radio-card> - </div> + </div> </div> <div class="ui center aligned basic segment"> <pagination diff --git a/front/src/components/manage/moderation/DomainsTable.vue b/front/src/components/manage/moderation/DomainsTable.vue index 4b7d20739..ef83fb916 100644 --- a/front/src/components/manage/moderation/DomainsTable.vue +++ b/front/src/components/manage/moderation/DomainsTable.vue @@ -1,5 +1,5 @@ <template> - <div> + <div v-if="result.count > 0"> <div class="ui inline form"> <div class="fields"> <div class="ui field"> @@ -90,6 +90,12 @@ </span> </div> </div> + <div v-else class="ui placeholder segment"> + <div class="ui icon header"> + <i class="server icon"></i> + <translate translate-context="Content/Home/Placeholder">No interactions with other pods yet</translate> + </div> + </div> </template> <script> diff --git a/front/src/components/playlists/PlaceholderWidget.vue b/front/src/components/playlists/PlaceholderWidget.vue deleted file mode 100644 index 772fd6c9d..000000000 --- a/front/src/components/playlists/PlaceholderWidget.vue +++ /dev/null @@ -1,18 +0,0 @@ -<template> - <div class="ui placeholder segment"> - <div class="ui icon header"> - <i class="list icon"></i> - <translate translate-context="Content/Home/Placeholder"> - No playlists have been created yet - </translate> - </div> - <button - @click="$store.commit('playlists/chooseTrack', null)" - class="ui primary button" - > - <translate translate-context="Content/Home/CreatePlaylist"> - Create Playlist - </translate> - </button> - </div> -</template> diff --git a/front/src/components/playlists/PlaylistModal.vue b/front/src/components/playlists/PlaylistModal.vue index 9fb77d875..714d9feba 100644 --- a/front/src/components/playlists/PlaylistModal.vue +++ b/front/src/components/playlists/PlaylistModal.vue @@ -38,6 +38,7 @@ </ul> </div> </div> + <div v-if="playlists.length > 0"> <h4 class="ui header"><translate translate-context="Popup/Playlist/Title">Available playlists</translate></h4> <table class="ui unstackable very basic table"> <thead> @@ -72,6 +73,17 @@ </tr> </tbody> </table> + </div> + <template v-else> + <div class="ui placeholder segment"> + <div class="ui icon header"> + <i class="list icon"></i> + <translate translate-context="Content/Home/Placeholder"> + No playlists have been created yet + </translate> + </div> + </div> + </template> </div> </div> <div class="actions"> diff --git a/front/src/components/playlists/Widget.vue b/front/src/components/playlists/Widget.vue index 9cd1c1d16..c9094d537 100644 --- a/front/src/components/playlists/Widget.vue +++ b/front/src/components/playlists/Widget.vue @@ -12,9 +12,24 @@ <template v-if="playlistsExist"> <playlist-card v-for="playlist in objects" :key="playlist.id" :playlist="playlist"></playlist-card> </template> - <template v-else> - <placeholder-widget></placeholder-widget> - </template> + <div v-else class="ui placeholder segment"> + <div class="ui icon header"> + <i class="list icon"></i> + <translate translate-context="Content/Home/Placeholder"> + No playlists have been created yet + </translate> + </div> + <button + v-if="$store.state.auth.authenticated" + @click="$store.commit('playlists/chooseTrack', null)" + class="ui green icon labeled button" + > + <i class="list icon"></i> + <translate translate-context="Content/Home/CreatePlaylist"> + Create Playlist + </translate> + </button> + </div> </div> </template> @@ -22,7 +37,6 @@ import _ from '@/lodash' import axios from 'axios' import PlaylistCard from '@/components/playlists/Card' -import PlaceholderWidget from '@/components/playlists/PlaceholderWidget' export default { props: { @@ -30,8 +44,7 @@ export default { url: {type: String, required: true} }, components: { - PlaylistCard, - PlaceholderWidget + PlaylistCard }, data () { return { diff --git a/front/src/views/content/libraries/FilesTable.vue b/front/src/views/content/libraries/FilesTable.vue index 4e67eb180..fd0348a6e 100644 --- a/front/src/views/content/libraries/FilesTable.vue +++ b/front/src/views/content/libraries/FilesTable.vue @@ -3,34 +3,67 @@ <div class="ui inline form"> <div class="fields"> <div class="ui six wide field"> - <label><translate translate-context="Content/Search/Input.Label/Noun">Search</translate></label> + <label> + <translate translate-context="Content/Search/Input.Label/Noun">Search</translate> + </label> <form @submit.prevent="search.query = $refs.search.value"> - <input name="search" ref="search" type="text" :value="search.query" :placeholder="labels.searchPlaceholder" /> + <input + name="search" + ref="search" + type="text" + :value="search.query" + :placeholder="labels.searchPlaceholder" + /> </form> </div> <div class="field"> - <label><translate translate-context="Content/*/*/Noun">Import status</translate></label> - <select class="ui dropdown" @change="addSearchToken('status', $event.target.value)" :value="getTokenValue('status', '')"> - <option value=""><translate translate-context="Content/*/Dropdown">All</translate></option> - <option value="pending"><translate translate-context="Content/Library/*/Short">Pending</translate></option> - <option value="skipped"><translate translate-context="Content/Library/*">Skipped</translate></option> - <option value="errored"><translate translate-context="Content/Library/Dropdown">Failed</translate></option> - <option value="finished"><translate translate-context="Content/Library/*">Finished</translate></option> + <label> + <translate translate-context="Content/*/*/Noun">Import status</translate> + </label> + <select + class="ui dropdown" + @change="addSearchToken('status', $event.target.value)" + :value="getTokenValue('status', '')" + > + <option value> + <translate translate-context="Content/*/Dropdown">All</translate> + </option> + <option value="pending"> + <translate translate-context="Content/Library/*/Short">Pending</translate> + </option> + <option value="skipped"> + <translate translate-context="Content/Library/*">Skipped</translate> + </option> + <option value="errored"> + <translate translate-context="Content/Library/Dropdown">Failed</translate> + </option> + <option value="finished"> + <translate translate-context="Content/Library/*">Finished</translate> + </option> </select> </div> <div class="field"> - <label><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate></label> + <label> + <translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate> + </label> <select class="ui dropdown" v-model="ordering"> - <option v-for="option in orderingOptions" :value="option[0]"> - {{ sharedLabels.filters[option[1]] }} - </option> + <option + v-for="option in orderingOptions" + :value="option[0]" + >{{ sharedLabels.filters[option[1]] }}</option> </select> </div> <div class="field"> - <label><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering direction</translate></label> + <label> + <translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering direction</translate> + </label> <select class="ui dropdown" v-model="orderingDirection"> - <option value="+"><translate translate-context="Content/Search/Dropdown">Ascending</translate></option> - <option value="-"><translate translate-context="Content/Search/Dropdown">Descending</translate></option> + <option value="+"> + <translate translate-context="Content/Search/Dropdown">Ascending</translate> + </option> + <option value="-"> + <translate translate-context="Content/Search/Dropdown">Descending</translate> + </option> </select> </div> </div> @@ -38,10 +71,18 @@ <import-status-modal :upload="detailedUpload" :show.sync="showUploadDetailModal" /> <div class="dimmable"> <div v-if="isLoading" class="ui active inverted dimmer"> - <div class="ui loader"></div> + <div class="ui loader"></div> + </div> + <div v-else-if="!result && result.results.length === 0 && !needsRefresh" class="ui placeholder segment"> + <div class="ui icon header"> + <i class="upload icon"></i> + <translate + translate-context="Content/Home/Placeholder" + >No tracks have been added to this library yet</translate> + </div> </div> <action-table - v-if="result" + v-else @action-launched="fetchData" :id-field="'uuid'" :objects-data="result" @@ -51,15 +92,30 @@ :needs-refresh="needsRefresh" :action-url="'uploads/action/'" @refresh="fetchData" - :filters="actionFilters"> + :filters="actionFilters" + > <template slot="header-cells"> - <th><translate translate-context="*/*/*/Noun">Title</translate></th> - <th><translate translate-context="*/*/*/Noun">Artist</translate></th> - <th><translate translate-context="*/*/*">Album</translate></th> - <th><translate translate-context="*/*/*/Noun">Upload date</translate></th> - <th><translate translate-context="Content/*/*/Noun">Import status</translate></th> - <th><translate translate-context="Content/*/*">Duration</translate></th> - <th><translate translate-context="Content/*/*/Noun">Size</translate></th> + <th> + <translate translate-context="*/*/*/Noun">Title</translate> + </th> + <th> + <translate translate-context="*/*/*/Noun">Artist</translate> + </th> + <th> + <translate translate-context="*/*/*">Album</translate> + </th> + <th> + <translate translate-context="*/*/*/Noun">Upload date</translate> + </th> + <th> + <translate translate-context="Content/*/*/Noun">Import status</translate> + </th> + <th> + <translate translate-context="Content/*/*">Duration</translate> + </th> + <th> + <translate translate-context="Content/*/*/Noun">Size</translate> + </th> </template> <template slot="row-cells" slot-scope="scope"> <template v-if="scope.obj.track"> @@ -67,10 +123,18 @@ <span :title="scope.obj.track.title">{{ scope.obj.track.title|truncate(25) }}</span> </td> <td> - <span class="discrete link" @click="addSearchToken('artist', scope.obj.track.artist.name)" :title="scope.obj.track.artist.name">{{ scope.obj.track.artist.name|truncate(20) }}</span> + <span + class="discrete link" + @click="addSearchToken('artist', scope.obj.track.artist.name)" + :title="scope.obj.track.artist.name" + >{{ scope.obj.track.artist.name|truncate(20) }}</span> </td> <td> - <span class="discrete link" @click="addSearchToken('album', scope.obj.track.album.title)" :title="scope.obj.track.album.title">{{ scope.obj.track.album.title|truncate(20) }}</span> + <span + class="discrete link" + @click="addSearchToken('album', scope.obj.track.album.title)" + :title="scope.obj.track.album.title" + >{{ scope.obj.track.album.title|truncate(20) }}</span> </td> </template> <template v-else> @@ -82,22 +146,24 @@ <human-date :date="scope.obj.creation_date"></human-date> </td> <td> - <span class="discrete link" @click="addSearchToken('status', scope.obj.import_status)" :title="sharedLabels.fields.import_status.choices[scope.obj.import_status].help"> - {{ sharedLabels.fields.import_status.choices[scope.obj.import_status].label }} - </span> - <button class="ui tiny basic icon button" :title="sharedLabels.fields.import_status.detailTitle" @click="detailedUpload = scope.obj; showUploadDetailModal = true"> + <span + class="discrete link" + @click="addSearchToken('status', scope.obj.import_status)" + :title="sharedLabels.fields.import_status.choices[scope.obj.import_status].help" + >{{ sharedLabels.fields.import_status.choices[scope.obj.import_status].label }}</span> + <button + class="ui tiny basic icon button" + :title="sharedLabels.fields.import_status.detailTitle" + @click="detailedUpload = scope.obj; showUploadDetailModal = true" + > <i class="question circle outline icon"></i> </button> </td> - <td v-if="scope.obj.duration"> - {{ time.parse(scope.obj.duration) }} - </td> + <td v-if="scope.obj.duration">{{ time.parse(scope.obj.duration) }}</td> <td v-else> <translate translate-context="*/*/*">N/A</translate> </td> - <td v-if="scope.obj.size"> - {{ scope.obj.size | humanSize }} - </td> + <td v-if="scope.obj.size">{{ scope.obj.size | humanSize }}</td> <td v-else> <translate translate-context="*/*/*">N/A</translate> </td> @@ -112,44 +178,50 @@ :current="page" :paginate-by="paginateBy" :total="result.count" - ></pagination> + ></pagination> <span v-if="result && result.results.length > 0"> - <translate translate-context="Content/*/Paragraph" - :translate-params="{start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}"> - Showing results %{ start }-%{ end } on %{ total } - </translate> + <translate + translate-context="Content/*/Paragraph" + :translate-params="{start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}" + >Showing results %{ start }-%{ end } on %{ total }</translate> </span> </div> </div> </template> <script> -import axios from 'axios' -import _ from '@/lodash' -import time from '@/utils/time' -import {normalizeQuery, parseTokens} from '@/search' +import axios from "axios"; +import _ from "@/lodash"; +import time from "@/utils/time"; +import { normalizeQuery, parseTokens } from "@/search"; -import Pagination from '@/components/Pagination' -import ActionTable from '@/components/common/ActionTable' -import OrderingMixin from '@/components/mixins/Ordering' -import TranslationsMixin from '@/components/mixins/Translations' -import SmartSearchMixin from '@/components/mixins/SmartSearch' -import ImportStatusModal from '@/components/library/ImportStatusModal' +import Pagination from "@/components/Pagination"; +import ActionTable from "@/components/common/ActionTable"; +import OrderingMixin from "@/components/mixins/Ordering"; +import TranslationsMixin from "@/components/mixins/Translations"; +import SmartSearchMixin from "@/components/mixins/SmartSearch"; +import ImportStatusModal from "@/components/library/ImportStatusModal"; export default { mixins: [OrderingMixin, TranslationsMixin, SmartSearchMixin], props: { - filters: {type: Object, required: false}, - needsRefresh: {type: Boolean, required: false, default: false}, - customObjects: {type: Array, required: false, default: () => { return [] }} + filters: { type: Object, required: false }, + needsRefresh: { type: Boolean, required: false, default: false }, + customObjects: { + type: Array, + required: false, + default: () => { + return []; + } + } }, components: { Pagination, ActionTable, ImportStatusModal }, - data () { + data() { return { time, detailedUpload: null, @@ -162,100 +234,109 @@ export default { query: this.defaultQuery, tokens: parseTokens(normalizeQuery(this.defaultQuery)) }, - orderingDirection: '-', - ordering: 'creation_date', + orderingDirection: "-", + ordering: "creation_date", orderingOptions: [ - ['creation_date', 'creation_date'], - ['title', 'track_title'], - ['size', 'size'], - ['duration', 'duration'], - ['bitrate', 'bitrate'], - ['album_title', 'album_title'], - ['artist_name', 'artist_name'] + ["creation_date", "creation_date"], + ["title", "track_title"], + ["size", "size"], + ["duration", "duration"], + ["bitrate", "bitrate"], + ["album_title", "album_title"], + ["artist_name", "artist_name"] ] - } + }; }, - created () { - this.fetchData() + created() { + this.fetchData(); }, methods: { - fetchData () { - this.$emit('fetch-start') - let params = _.merge({ - 'page': this.page, - 'page_size': this.paginateBy, - 'ordering': this.getOrderingAsString(), - 'q': this.search.query - }, this.filters || {}) - let self = this - self.isLoading = true - self.checked = [] - axios.get('/uploads/', {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 + fetchData() { + this.$emit("fetch-start"); + let params = _.merge( + { + page: this.page, + page_size: this.paginateBy, + ordering: this.getOrderingAsString(), + q: this.search.query + }, + this.filters || {} + ); + let self = this; + self.isLoading = true; + self.checked = []; + axios.get("/uploads/", { params: params }).then( + response => { + self.result = response.data; + self.isLoading = false; + }, + error => { + self.isLoading = false; + self.errors = error.backendErrors; + } + ); }, }, computed: { - labels () { + labels() { return { - searchPlaceholder: this.$pgettext('Content/Library/Input.Placeholder', 'Search by title, artist, album…'), - } + searchPlaceholder: this.$pgettext( + "Content/Library/Input.Placeholder", + "Search by title, artist, album…" + ) + }; }, - actionFilters () { + actionFilters() { var currentFilters = { q: this.search.query - } + }; if (this.filters) { - return _.merge(currentFilters, this.filters) + return _.merge(currentFilters, this.filters); } else { - return currentFilters + return currentFilters; } }, - actions () { - let deleteMsg = this.$pgettext('*/*/*/Verb', 'Delete') - let relaunchMsg = this.$pgettext('Content/Library/Dropdown/Verb', 'Restart import') + actions() { + let deleteMsg = this.$pgettext("*/*/*/Verb", "Delete"); + let relaunchMsg = this.$pgettext( + "Content/Library/Dropdown/Verb", + "Restart import" + ); return [ { - name: 'delete', + name: "delete", label: deleteMsg, isDangerous: true, allowAll: true }, { - name: 'relaunch_import', + name: "relaunch_import", label: relaunchMsg, isDangerous: true, allowAll: true, filterCheckable: f => { - return f.import_status != 'finished' + return f.import_status != "finished"; } } - ] + ]; } }, watch: { - orderingDirection: function () { - this.page = 1 - this.fetchData() + orderingDirection: function() { + this.page = 1; + this.fetchData(); }, - page: function () { - this.fetchData() + page: function() { + this.fetchData(); }, - ordering: function () { - this.page = 1 - this.fetchData() + ordering: function() { + this.page = 1; + this.fetchData(); }, - search (newValue) { - this.page = 1 - this.fetchData() + search(newValue) { + this.page = 1; + this.fetchData(); } } -} +}; </script> diff --git a/front/src/views/playlists/Detail.vue b/front/src/views/playlists/Detail.vue index 3b6b9000b..5dc99e78b 100644 --- a/front/src/views/playlists/Detail.vue +++ b/front/src/views/playlists/Detail.vue @@ -55,7 +55,6 @@ <div class="content"> <div class="description"> <embed-wizard type="playlist" :id="playlist.id" /> - </div> </div> <div class="actions"> @@ -64,7 +63,6 @@ </div> </div> </modal> - </section> <section class="ui vertical stripe segment"> <template v-if="edit"> @@ -73,10 +71,20 @@ @tracks-updated="updatePlts" :playlist="playlist" :playlist-tracks="playlistTracks"></playlist-editor> </template> - <template v-else> + <template v-else-if="tracks.length > 0"> <h2><translate translate-context="*/*/*">Tracks</translate></h2> <track-table :display-position="true" :tracks="tracks"></track-table> </template> + <div v-else class="ui placeholder segment"> + <div class="ui icon header"> + <i class="list icon"></i> + <translate translate-context="Content/Home/Placeholder">There are no tracks in this playlist yet</translate> + </div> + <button @click="edit = !edit" class="ui green icon labeled button"> + <i class="pencil icon"></i> + <translate translate-context="Content/Home/CreatePlaylist">Edit</translate> + </button> + </div> </section> </main> </template> diff --git a/front/src/views/playlists/List.vue b/front/src/views/playlists/List.vue index 1ff56b5d5..9fb53fd66 100644 --- a/front/src/views/playlists/List.vue +++ b/front/src/views/playlists/List.vue @@ -40,7 +40,24 @@ </div> </div> <div class="ui hidden divider"></div> - <playlist-card-list v-if="result" :playlists="result.results"></playlist-card-list> + <playlist-card-list v-if="result && result.results.length > 0" :playlists="result.results"></playlist-card-list> + <div v-else-if="result && !result.results.length > 0" class="ui placeholder segment sixteen wide column" style="text-align: center; display: flex; align-items: center"> + <div class="ui icon header"> + <i class="list icon"></i> + <translate translate-context="Content/Playlists/Placeholder"> + No results matching your query + </translate> + </div> + <button + v-if="$store.state.auth.authenticated" + @click="$store.commit('playlists/chooseTrack', null)" + class="ui green button labeled icon"> + <i class="list icon"></i> + <translate translate-context="Content/*/Verb"> + Create a playlist + </translate> + </button> + </div> <div class="ui center aligned basic segment"> <pagination v-if="result && result.results.length > 0" diff --git a/front/src/views/radios/Detail.vue b/front/src/views/radios/Detail.vue index 355ed8e84..619ac754b 100644 --- a/front/src/views/radios/Detail.vue +++ b/front/src/views/radios/Detail.vue @@ -31,7 +31,7 @@ </template> </div> </section> - <section class="ui vertical stripe segment"> + <section v-if="totalTracks > 0" class="ui vertical stripe segment"> <h2><translate translate-context="*/*/*">Tracks</translate></h2> <track-table :tracks="tracks"></track-table> <div class="ui center aligned basic segment"> @@ -44,6 +44,21 @@ ></pagination> </div> </section> + <div v-else-if="!isLoading && !totalTracks > 0" class="ui placeholder segment"> + <div class="ui icon header"> + <i class="rss icon"></i> + <translate + translate-context="Content/Radios/Placeholder" + >No tracks have been added to this radio yet</translate> + </div> + <router-link + v-if="$store.state.auth.username === radio.user.username" + class="ui green icon labeled button" + :to="{name: 'library.radios.edit', params: {id: radio.id}}" exact> + <i class="pencil icon"></i> + Edit… + </router-link> + </div> </main> </template> -- GitLab