Commit 9ce24284 authored by Ciarán Ainsworth's avatar Ciarán Ainsworth
Browse files

Merge branch '750-do-not-display-empty-tables-headers' into 'develop'

Added placeholders across the application

Closes #750

See merge request !922
parents 860522a2 11d6c7cf
Placeholders will now be shown if no content is available across the application (#750)
\ No newline at end of file
...@@ -35,7 +35,14 @@ ...@@ -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>
......
...@@ -7,7 +7,7 @@ ...@@ -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 @@ ...@@ -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>
......
...@@ -18,7 +18,7 @@ ...@@ -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 @@ ...@@ -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 @@ ...@@ -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>
......
...@@ -59,6 +59,23 @@ ...@@ -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
......
...@@ -48,6 +48,23 @@ ...@@ -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"
......
...@@ -60,6 +60,23 @@ ...@@ -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 @@ ...@@ -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
......
<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 @@ ...@@ -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>
......
<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>
...@@ -38,6 +38,7 @@ ...@@ -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 @@ ...@@ -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">
......
...@@ -12,9 +12,24 @@ ...@@ -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 @@ ...@@ -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 { ...@@ -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 {
......
...@@ -3,34 +3,67 @@ ...@@ -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', '')"
>
<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> </select>
</div> </div>
<div class="field"> <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"> <select class="ui dropdown" v-model="ordering">
<option v-for="option in orderingOptions" :value="option[0]"> <option
{{ sharedLabels.filters[option[1]] }} v-for="option in orderingOptions"
</option> :value="option[0]"
>{{ sharedLabels.filters[option[1]] }}</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 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 @@ ...@@ -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 @@ ...@@ -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 @@ ...@@ -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 @@ ...@@ -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 @@ ...@@ -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"