Added placeholders across the application

This commit is contained in:
Ciarán Ainsworth 2019-10-17 14:15:33 +02:00
parent 860522a291
commit 11d6c7cf1d
15 changed files with 369 additions and 154 deletions

View File

@ -0,0 +1 @@
Placeholders will now be shown if no content is available across the application (#750)

View File

@ -35,7 +35,14 @@
</div> </div>
</div> </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> </div>
</template> </template>

View File

@ -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="!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 :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> <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="['item', itemClasses]" v-for="object in objects" :key="object.id">
<div class="ui tiny image"> <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)"> <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 class="ui loader"></div>
</div> </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> </div>
</template> </template>

View File

@ -18,7 +18,7 @@
</h2> </h2>
<radio-button v-if="hasFavorites" type="favorites"></radio-button> <radio-button v-if="hasFavorites" type="favorites"></radio-button>
</section> </section>
<section class="ui vertical stripe segment"> <section v-if="hasFavorites" class="ui vertical stripe segment">
<div :class="['ui', {'loading': isLoading}, 'form']"> <div :class="['ui', {'loading': isLoading}, 'form']">
<div class="fields"> <div class="fields">
<div class="field"> <div class="field">
@ -46,7 +46,6 @@
</div> </div>
</div> </div>
</div> </div>
<track-table v-if="results" :tracks="results.results"></track-table> <track-table v-if="results" :tracks="results.results"></track-table>
<div class="ui center aligned basic segment"> <div class="ui center aligned basic segment">
<pagination <pagination
@ -58,6 +57,18 @@
></pagination> ></pagination>
</div> </div>
</section> </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> </main>
</template> </template>

View File

@ -59,6 +59,23 @@
:key="album.id" :key="album.id"
:album="album"></album-card> :album="album"></album-card>
</div> </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>
<div class="ui center aligned basic segment"> <div class="ui center aligned basic segment">
<pagination <pagination

View File

@ -48,6 +48,23 @@
</div> </div>
<artist-card :artist="artist" v-for="artist in result.results" :key="artist.id"></artist-card> <artist-card :artist="artist" v-for="artist in result.results" :key="artist.id"></artist-card>
</div> </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"> <div class="ui center aligned basic segment">
<pagination <pagination
v-if="result && result.count > paginateBy" v-if="result && result.count > paginateBy"

View File

@ -60,6 +60,23 @@
</div> </div>
</div> </div>
<div class="ui hidden divider"></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 <div
v-if="result" v-if="result"
v-masonry v-masonry
@ -76,7 +93,7 @@
v-for="radio in result.results" v-for="radio in result.results"
:key="radio.id" :key="radio.id"
:custom-radio="radio"></radio-card> :custom-radio="radio"></radio-card>
</div> </div>
</div> </div>
<div class="ui center aligned basic segment"> <div class="ui center aligned basic segment">
<pagination <pagination

View File

@ -1,5 +1,5 @@
<template> <template>
<div> <div v-if="result.count > 0">
<div class="ui inline form"> <div class="ui inline form">
<div class="fields"> <div class="fields">
<div class="ui field"> <div class="ui field">
@ -90,6 +90,12 @@
</span> </span>
</div> </div>
</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> </template>
<script> <script>

View File

@ -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>

View File

@ -38,6 +38,7 @@
</ul> </ul>
</div> </div>
</div> </div>
<div v-if="playlists.length > 0">
<h4 class="ui header"><translate translate-context="Popup/Playlist/Title">Available playlists</translate></h4> <h4 class="ui header"><translate translate-context="Popup/Playlist/Title">Available playlists</translate></h4>
<table class="ui unstackable very basic table"> <table class="ui unstackable very basic table">
<thead> <thead>
@ -72,6 +73,17 @@
</tr> </tr>
</tbody> </tbody>
</table> </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> </div>
<div class="actions"> <div class="actions">

View File

@ -12,9 +12,24 @@
<template v-if="playlistsExist"> <template v-if="playlistsExist">
<playlist-card v-for="playlist in objects" :key="playlist.id" :playlist="playlist"></playlist-card> <playlist-card v-for="playlist in objects" :key="playlist.id" :playlist="playlist"></playlist-card>
</template> </template>
<template v-else> <div v-else class="ui placeholder segment">
<placeholder-widget></placeholder-widget> <div class="ui icon header">
</template> <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> </div>
</template> </template>
@ -22,7 +37,6 @@
import _ from '@/lodash' import _ from '@/lodash'
import axios from 'axios' import axios from 'axios'
import PlaylistCard from '@/components/playlists/Card' import PlaylistCard from '@/components/playlists/Card'
import PlaceholderWidget from '@/components/playlists/PlaceholderWidget'
export default { export default {
props: { props: {
@ -30,8 +44,7 @@ export default {
url: {type: String, required: true} url: {type: String, required: true}
}, },
components: { components: {
PlaylistCard, PlaylistCard
PlaceholderWidget
}, },
data () { data () {
return { return {

View File

@ -3,34 +3,67 @@
<div class="ui inline form"> <div class="ui inline form">
<div class="fields"> <div class="fields">
<div class="ui six wide field"> <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"> <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> </form>
</div> </div>
<div class="field"> <div class="field">
<label><translate translate-context="Content/*/*/Noun">Import status</translate></label> <label>
<select class="ui dropdown" @change="addSearchToken('status', $event.target.value)" :value="getTokenValue('status', '')"> <translate translate-context="Content/*/*/Noun">Import status</translate>
<option value=""><translate translate-context="Content/*/Dropdown">All</translate></option> </label>
<option value="pending"><translate translate-context="Content/Library/*/Short">Pending</translate></option> <select
<option value="skipped"><translate translate-context="Content/Library/*">Skipped</translate></option> class="ui dropdown"
<option value="errored"><translate translate-context="Content/Library/Dropdown">Failed</translate></option> @change="addSearchToken('status', $event.target.value)"
<option value="finished"><translate translate-context="Content/Library/*">Finished</translate></option> :value="getTokenValue('status', '')"
</select> >
</div> <option value>
<div class="field"> <translate translate-context="Content/*/Dropdown">All</translate>
<label><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate></label> </option>
<select class="ui dropdown" v-model="ordering"> <option value="pending">
<option v-for="option in orderingOptions" :value="option[0]"> <translate translate-context="Content/Library/*/Short">Pending</translate>
{{ sharedLabels.filters[option[1]] }} </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> </option>
</select> </select>
</div> </div>
<div class="field"> <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</translate>
</label>
<select class="ui dropdown" v-model="ordering">
<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>
<select class="ui dropdown" v-model="orderingDirection"> <select class="ui dropdown" v-model="orderingDirection">
<option value="+"><translate translate-context="Content/Search/Dropdown">Ascending</translate></option> <option value="+">
<option value="-"><translate translate-context="Content/Search/Dropdown">Descending</translate></option> <translate translate-context="Content/Search/Dropdown">Ascending</translate>
</option>
<option value="-">
<translate translate-context="Content/Search/Dropdown">Descending</translate>
</option>
</select> </select>
</div> </div>
</div> </div>
@ -38,10 +71,18 @@
<import-status-modal :upload="detailedUpload" :show.sync="showUploadDetailModal" /> <import-status-modal :upload="detailedUpload" :show.sync="showUploadDetailModal" />
<div class="dimmable"> <div class="dimmable">
<div v-if="isLoading" class="ui active inverted dimmer"> <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> </div>
<action-table <action-table
v-if="result" v-else
@action-launched="fetchData" @action-launched="fetchData"
:id-field="'uuid'" :id-field="'uuid'"
:objects-data="result" :objects-data="result"
@ -51,15 +92,30 @@
:needs-refresh="needsRefresh" :needs-refresh="needsRefresh"
:action-url="'uploads/action/'" :action-url="'uploads/action/'"
@refresh="fetchData" @refresh="fetchData"
:filters="actionFilters"> :filters="actionFilters"
>
<template slot="header-cells"> <template slot="header-cells">
<th><translate translate-context="*/*/*/Noun">Title</translate></th> <th>
<th><translate translate-context="*/*/*/Noun">Artist</translate></th> <translate translate-context="*/*/*/Noun">Title</translate>
<th><translate translate-context="*/*/*">Album</translate></th> </th>
<th><translate translate-context="*/*/*/Noun">Upload date</translate></th> <th>
<th><translate translate-context="Content/*/*/Noun">Import status</translate></th> <translate translate-context="*/*/*/Noun">Artist</translate>
<th><translate translate-context="Content/*/*">Duration</translate></th> </th>
<th><translate translate-context="Content/*/*/Noun">Size</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>
<template slot="row-cells" slot-scope="scope"> <template slot="row-cells" slot-scope="scope">
<template v-if="scope.obj.track"> <template v-if="scope.obj.track">
@ -67,10 +123,18 @@
<span :title="scope.obj.track.title">{{ scope.obj.track.title|truncate(25) }}</span> <span :title="scope.obj.track.title">{{ scope.obj.track.title|truncate(25) }}</span>
</td> </td>
<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>
<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> </td>
</template> </template>
<template v-else> <template v-else>
@ -82,22 +146,24 @@
<human-date :date="scope.obj.creation_date"></human-date> <human-date :date="scope.obj.creation_date"></human-date>
</td> </td>
<td> <td>
<span class="discrete link" @click="addSearchToken('status', scope.obj.import_status)" :title="sharedLabels.fields.import_status.choices[scope.obj.import_status].help"> <span
{{ sharedLabels.fields.import_status.choices[scope.obj.import_status].label }} class="discrete link"
</span> @click="addSearchToken('status', scope.obj.import_status)"
<button class="ui tiny basic icon button" :title="sharedLabels.fields.import_status.detailTitle" @click="detailedUpload = scope.obj; showUploadDetailModal = true"> :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> <i class="question circle outline icon"></i>
</button> </button>
</td> </td>
<td v-if="scope.obj.duration"> <td v-if="scope.obj.duration">{{ time.parse(scope.obj.duration) }}</td>
{{ time.parse(scope.obj.duration) }}
</td>
<td v-else> <td v-else>
<translate translate-context="*/*/*">N/A</translate> <translate translate-context="*/*/*">N/A</translate>
</td> </td>
<td v-if="scope.obj.size"> <td v-if="scope.obj.size">{{ scope.obj.size | humanSize }}</td>
{{ scope.obj.size | humanSize }}
</td>
<td v-else> <td v-else>
<translate translate-context="*/*/*">N/A</translate> <translate translate-context="*/*/*">N/A</translate>
</td> </td>
@ -112,44 +178,50 @@
:current="page" :current="page"
:paginate-by="paginateBy" :paginate-by="paginateBy"
:total="result.count" :total="result.count"
></pagination> ></pagination>
<span v-if="result && result.results.length > 0"> <span v-if="result && result.results.length > 0">
<translate translate-context="Content/*/Paragraph" <translate
:translate-params="{start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}"> translate-context="Content/*/Paragraph"
Showing results %{ start }-%{ end } on %{ total } :translate-params="{start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}"
</translate> >Showing results %{ start }-%{ end } on %{ total }</translate>
</span> </span>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import axios from 'axios' import axios from "axios";
import _ from '@/lodash' import _ from "@/lodash";
import time from '@/utils/time' import time from "@/utils/time";
import {normalizeQuery, parseTokens} from '@/search' import { normalizeQuery, parseTokens } from "@/search";
import Pagination from '@/components/Pagination' import Pagination from "@/components/Pagination";
import ActionTable from '@/components/common/ActionTable' import ActionTable from "@/components/common/ActionTable";
import OrderingMixin from '@/components/mixins/Ordering' import OrderingMixin from "@/components/mixins/Ordering";
import TranslationsMixin from '@/components/mixins/Translations' import TranslationsMixin from "@/components/mixins/Translations";
import SmartSearchMixin from '@/components/mixins/SmartSearch' import SmartSearchMixin from "@/components/mixins/SmartSearch";
import ImportStatusModal from '@/components/library/ImportStatusModal' import ImportStatusModal from "@/components/library/ImportStatusModal";
export default { export default {
mixins: [OrderingMixin, TranslationsMixin, SmartSearchMixin], mixins: [OrderingMixin, TranslationsMixin, SmartSearchMixin],
props: { props: {
filters: {type: Object, required: false}, filters: { type: Object, required: false },
needsRefresh: {type: Boolean, required: false, default: false}, needsRefresh: { type: Boolean, required: false, default: false },
customObjects: {type: Array, required: false, default: () => { return [] }} customObjects: {
type: Array,
required: false,
default: () => {
return [];
}
}
}, },
components: { components: {
Pagination, Pagination,
ActionTable, ActionTable,
ImportStatusModal ImportStatusModal
}, },
data () { data() {
return { return {
time, time,
detailedUpload: null, detailedUpload: null,
@ -162,100 +234,109 @@ export default {
query: this.defaultQuery, query: this.defaultQuery,
tokens: parseTokens(normalizeQuery(this.defaultQuery)) tokens: parseTokens(normalizeQuery(this.defaultQuery))
}, },
orderingDirection: '-', orderingDirection: "-",
ordering: 'creation_date', ordering: "creation_date",
orderingOptions: [ orderingOptions: [
['creation_date', 'creation_date'], ["creation_date", "creation_date"],
['title', 'track_title'], ["title", "track_title"],
['size', 'size'], ["size", "size"],
['duration', 'duration'], ["duration", "duration"],
['bitrate', 'bitrate'], ["bitrate", "bitrate"],
['album_title', 'album_title'], ["album_title", "album_title"],
['artist_name', 'artist_name'] ["artist_name", "artist_name"]
] ]
} };
}, },
created () { created() {
this.fetchData() this.fetchData();
}, },
methods: { methods: {
fetchData () { fetchData() {
this.$emit('fetch-start') this.$emit("fetch-start");
let params = _.merge({ let params = _.merge(
'page': this.page, {
'page_size': this.paginateBy, page: this.page,
'ordering': this.getOrderingAsString(), page_size: this.paginateBy,
'q': this.search.query ordering: this.getOrderingAsString(),
}, this.filters || {}) q: this.search.query
let self = this },
self.isLoading = true this.filters || {}
self.checked = [] );
axios.get('/uploads/', {params: params}).then((response) => { let self = this;
self.result = response.data self.isLoading = true;
self.isLoading = false self.checked = [];
}, error => { axios.get("/uploads/", { params: params }).then(
self.isLoading = false response => {
self.errors = error.backendErrors self.result = response.data;
}) self.isLoading = false;
}, },
selectPage: function (page) { error => {
this.page = page self.isLoading = false;
self.errors = error.backendErrors;
}
);
}, },
}, },
computed: { computed: {
labels () { labels() {
return { 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 = { var currentFilters = {
q: this.search.query q: this.search.query
} };
if (this.filters) { if (this.filters) {
return _.merge(currentFilters, this.filters) return _.merge(currentFilters, this.filters);
} else { } else {
return currentFilters return currentFilters;
} }
}, },
actions () { actions() {
let deleteMsg = this.$pgettext('*/*/*/Verb', 'Delete') let deleteMsg = this.$pgettext("*/*/*/Verb", "Delete");
let relaunchMsg = this.$pgettext('Content/Library/Dropdown/Verb', 'Restart import') let relaunchMsg = this.$pgettext(
"Content/Library/Dropdown/Verb",
"Restart import"
);
return [ return [
{ {
name: 'delete', name: "delete",
label: deleteMsg, label: deleteMsg,
isDangerous: true, isDangerous: true,
allowAll: true allowAll: true
}, },
{ {
name: 'relaunch_import', name: "relaunch_import",
label: relaunchMsg, label: relaunchMsg,
isDangerous: true, isDangerous: true,
allowAll: true, allowAll: true,
filterCheckable: f => { filterCheckable: f => {
return f.import_status != 'finished' return f.import_status != "finished";
} }
} }
] ];
} }
}, },
watch: { watch: {
orderingDirection: function () { orderingDirection: function() {
this.page = 1 this.page = 1;
this.fetchData() this.fetchData();
}, },
page: function () { page: function() {
this.fetchData() this.fetchData();
}, },
ordering: function () { ordering: function() {
this.page = 1 this.page = 1;
this.fetchData() this.fetchData();
}, },
search (newValue) { search(newValue) {
this.page = 1 this.page = 1;
this.fetchData() this.fetchData();
} }
} }
} };
</script> </script>

View File

@ -55,7 +55,6 @@
<div class="content"> <div class="content">
<div class="description"> <div class="description">
<embed-wizard type="playlist" :id="playlist.id" /> <embed-wizard type="playlist" :id="playlist.id" />
</div> </div>
</div> </div>
<div class="actions"> <div class="actions">
@ -64,7 +63,6 @@
</div> </div>
</div> </div>
</modal> </modal>
</section> </section>
<section class="ui vertical stripe segment"> <section class="ui vertical stripe segment">
<template v-if="edit"> <template v-if="edit">
@ -73,10 +71,20 @@
@tracks-updated="updatePlts" @tracks-updated="updatePlts"
:playlist="playlist" :playlist-tracks="playlistTracks"></playlist-editor> :playlist="playlist" :playlist-tracks="playlistTracks"></playlist-editor>
</template> </template>
<template v-else> <template v-else-if="tracks.length > 0">
<h2><translate translate-context="*/*/*">Tracks</translate></h2> <h2><translate translate-context="*/*/*">Tracks</translate></h2>
<track-table :display-position="true" :tracks="tracks"></track-table> <track-table :display-position="true" :tracks="tracks"></track-table>
</template> </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> </section>
</main> </main>
</template> </template>

View File

@ -40,7 +40,24 @@
</div> </div>
</div> </div>
<div class="ui hidden divider"></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"> <div class="ui center aligned basic segment">
<pagination <pagination
v-if="result && result.results.length > 0" v-if="result && result.results.length > 0"

View File

@ -31,7 +31,7 @@
</template> </template>
</div> </div>
</section> </section>
<section class="ui vertical stripe segment"> <section v-if="totalTracks > 0" class="ui vertical stripe segment">
<h2><translate translate-context="*/*/*">Tracks</translate></h2> <h2><translate translate-context="*/*/*">Tracks</translate></h2>
<track-table :tracks="tracks"></track-table> <track-table :tracks="tracks"></track-table>
<div class="ui center aligned basic segment"> <div class="ui center aligned basic segment">
@ -44,6 +44,21 @@
></pagination> ></pagination>
</div> </div>
</section> </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> </main>
</template> </template>