Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision

Target

Select target project
  • funkwhale/funkwhale
  • Luclu7/funkwhale
  • mbothorel/funkwhale
  • EorlBruder/funkwhale
  • tcit/funkwhale
  • JocelynDelalande/funkwhale
  • eneiluj/funkwhale
  • reg/funkwhale
  • ButterflyOfFire/funkwhale
  • m4sk1n/funkwhale
  • wxcafe/funkwhale
  • andybalaam/funkwhale
  • jcgruenhage/funkwhale
  • pblayo/funkwhale
  • joshuaboniface/funkwhale
  • n3ddy/funkwhale
  • gegeweb/funkwhale
  • tohojo/funkwhale
  • emillumine/funkwhale
  • Te-k/funkwhale
  • asaintgenis/funkwhale
  • anoadragon453/funkwhale
  • Sakada/funkwhale
  • ilianaw/funkwhale
  • l4p1n/funkwhale
  • pnizet/funkwhale
  • dante383/funkwhale
  • interfect/funkwhale
  • akhardya/funkwhale
  • svfusion/funkwhale
  • noplanman/funkwhale
  • nykopol/funkwhale
  • roipoussiere/funkwhale
  • Von/funkwhale
  • aurieh/funkwhale
  • icaria36/funkwhale
  • floreal/funkwhale
  • paulwalko/funkwhale
  • comradekingu/funkwhale
  • FurryJulie/funkwhale
  • Legolars99/funkwhale
  • Vierkantor/funkwhale
  • zachhats/funkwhale
  • heyjake/funkwhale
  • sn0w/funkwhale
  • jvoisin/funkwhale
  • gordon/funkwhale
  • Alexander/funkwhale
  • bignose/funkwhale
  • qasim.ali/funkwhale
  • fakegit/funkwhale
  • Kxze/funkwhale
  • stenstad/funkwhale
  • creak/funkwhale
  • Kaze/funkwhale
  • Tixie/funkwhale
  • IISergII/funkwhale
  • lfuelling/funkwhale
  • nhaddag/funkwhale
  • yoasif/funkwhale
  • ifischer/funkwhale
  • keslerm/funkwhale
  • flupe/funkwhale
  • petitminion/funkwhale
  • ariasuni/funkwhale
  • ollie/funkwhale
  • ngaumont/funkwhale
  • techknowlogick/funkwhale
  • Shleeble/funkwhale
  • theflyingfrog/funkwhale
  • jonatron/funkwhale
  • neobrain/funkwhale
  • eorn/funkwhale
  • KokaKiwi/funkwhale
  • u1-liquid/funkwhale
  • marzzzello/funkwhale
  • sirenwatcher/funkwhale
  • newer027/funkwhale
  • codl/funkwhale
  • Zwordi/funkwhale
  • gisforgabriel/funkwhale
  • iuriatan/funkwhale
  • simon/funkwhale
  • bheesham/funkwhale
  • zeoses/funkwhale
  • accraze/funkwhale
  • meliurwen/funkwhale
  • divadsn/funkwhale
  • Etua/funkwhale
  • sdrik/funkwhale
  • Soran/funkwhale
  • kuba-orlik/funkwhale
  • cristianvogel/funkwhale
  • Forceu/funkwhale
  • jeff/funkwhale
  • der_scheibenhacker/funkwhale
  • owlnical/funkwhale
  • jovuit/funkwhale
  • SilverFox15/funkwhale
  • phw/funkwhale
  • mayhem/funkwhale
  • sridhar/funkwhale
  • stromlin/funkwhale
  • rrrnld/funkwhale
  • nitaibezerra/funkwhale
  • jaller94/funkwhale
  • pcouy/funkwhale
  • eduxstad/funkwhale
  • codingHahn/funkwhale
  • captain/funkwhale
  • polyedre/funkwhale
  • leishenailong/funkwhale
  • ccritter/funkwhale
  • lnceballosz/funkwhale
  • fpiesche/funkwhale
  • Fanyx/funkwhale
  • markusblogde/funkwhale
  • Firobe/funkwhale
  • devilcius/funkwhale
  • freaktechnik/funkwhale
  • blopware/funkwhale
  • cone/funkwhale
  • thanksd/funkwhale
  • vachan-maker/funkwhale
  • bbenti/funkwhale
  • tarator/funkwhale
  • prplecake/funkwhale
  • DMarzal/funkwhale
  • lullis/funkwhale
  • hanacgr/funkwhale
  • albjeremias/funkwhale
  • xeruf/funkwhale
  • llelite/funkwhale
  • RoiArthurB/funkwhale
  • cloo/funkwhale
  • nztvar/funkwhale
  • Keunes/funkwhale
  • petitminion/funkwhale-petitminion
  • m-idler/funkwhale
  • SkyLeite/funkwhale
140 results
Select Git revision
Show changes
Commits on Source (23)
Showing
with 1749 additions and 436 deletions
Made changes to the track table to make it more visibly pleasing.
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
"diff": "^4.0.1", "diff": "^4.0.1",
"django-channels": "1.1.6", "django-channels": "1.1.6",
"focus-trap": "^5.1.0", "focus-trap": "^5.1.0",
"fomantic-ui-css": "^2.8.3", "fomantic-ui-css": "^2.8.7",
"howler": "^2.2.1", "howler": "^2.2.1",
"js-logger": "^1.4.1", "js-logger": "^1.4.1",
"lodash": "^4.17.21", "lodash": "^4.17.21",
......
...@@ -309,6 +309,11 @@ REPLACEMENTS = { ...@@ -309,6 +309,11 @@ REPLACEMENTS = {
("color", "var(--button-basic-hover-color)"), ("color", "var(--button-basic-hover-color)"),
("box-shadow", "var(--button-basic-hover-box-shadow)"), ("box-shadow", "var(--button-basic-hover-box-shadow)"),
], ],
(".ui.basic.button:focus",): [
("background", "var(--button-basic-hover-background)"),
("color", "var(--button-basic-hover-color)"),
("box-shadow", "var(--button-basic-hover-box-shadow)"),
],
}, },
"card": { "card": {
"skip": [ "skip": [
......
...@@ -8,15 +8,13 @@ ...@@ -8,15 +8,13 @@
:class="[{'disabled': current - 1 < 1}, 'item']"><i class="angle left icon"></i></a> :class="[{'disabled': current - 1 < 1}, 'item']"><i class="angle left icon"></i></a>
<template v-if="!compact"> <template v-if="!compact">
<a href <a href
v-if="page !== 'skip'"
v-for="page in pages" v-for="page in pages"
:key="page"
@click.prevent.stop="selectPage(page)" @click.prevent.stop="selectPage(page)"
:class="[{'active': page === current}, 'item']"> :class="[{'active': page === current}, {'disabled': page === 'skip'}, 'item']">
{{ page }} <span v-if="page !== 'skip'">{{ page }}</span>
<span v-else></span>
</a> </a>
<div v-else class="disabled item">
</div>
</template> </template>
<a href <a href
:disabled="current + 1 > maxPage" :disabled="current + 1 > maxPage"
......
<template>
<div class="album-entries">
<div :class="[{active: currentTrack && isPlaying && track.id === currentTrack.id}, 'album-entry']" @click.prevent="replacePlay(tracks, index)" v-for="(track, index) in tracks" :key="track.id">
<div class="actions">
<play-button class="basic circular icon" :button-classes="['circular inverted vibrant icon button']" :discrete="true" :icon-only="true" :track="track" :tracks="tracks"></play-button>
</div>
<div class="position">{{ prettyPosition(track.position) }}</div>
<div class="content ellipsis">
<strong>{{ track.title }}</strong><br>
</div>
<div class="meta">
<template v-if="$store.state.auth.authenticated && $store.getters['favorites/isFavorite'](track.id)">
<track-favorite-icon class="tiny" :track="track"></track-favorite-icon>
</template>
<human-duration v-if="track.uploads[0] && track.uploads[0].duration" :duration="track.uploads[0].duration"></human-duration>
</div>
<div class="actions">
<play-button class="play-button basic icon" :dropdown-only="true" :is-playable="track.is_playable" :dropdown-icon-classes="['ellipsis', 'vertical', 'large really discrete']" :track="track"></play-button>
</div>
</div>
</div>
</template>
<script>
import _ from '@/lodash'
import axios from 'axios'
import ChannelEntryCard from '@/components/audio/ChannelEntryCard'
import PlayButton from '@/components/audio/PlayButton'
import TrackFavoriteIcon from '@/components/favorites/TrackFavoriteIcon'
import { mapGetters } from "vuex"
export default {
props: {
tracks: Array,
},
components: {
PlayButton,
TrackFavoriteIcon
},
computed: {
...mapGetters({
currentTrack: "queue/currentTrack",
}),
isPlaying () {
return this.$store.state.player.playing
},
},
methods: {
prettyPosition (position, size) {
var s = String(position);
while (s.length < (size || 2)) {s = "0" + s;}
return s;
},
replacePlay (tracks, trackIndex) {
this.$store.dispatch('queue/clean')
this.$store.dispatch('queue/appendMany', {tracks: tracks}).then(() => {
this.$store.dispatch('queue/currentIndex', trackIndex)
})
},
}
}
</script>
...@@ -5,18 +5,34 @@ ...@@ -5,18 +5,34 @@
<div v-if="isLoading" class="ui inverted active dimmer"> <div v-if="isLoading" class="ui inverted active dimmer">
<div class="ui loader"></div> <div class="ui loader"></div>
</div> </div>
<channel-entry-card v-for="entry in objects" :default-cover="defaultCover" :entry="entry" :key="entry.id" /> <podcast-table
<template v-if="count > limit"> v-if="isPodcast"
<div class="ui hidden divider"></div> :default-cover="defaultCover"
<div class = "ui center aligned basic segment"> :is-podcast="isPodcast"
<pagination :show-art="true"
:show-position="false"
:tracks="objects"
:show-artist="false"
:show-album="false"
:paginate-results="true"
:total="count"
@page-changed="updatePage" @page-changed="updatePage"
:current="page" :page="page"
:paginate-by="limit" :paginate-by="limit"></podcast-table>
<track-table
v-else
:default-cover="defaultCover"
:is-podcast="isPodcast"
:show-art="true"
:show-position="false"
:tracks="objects"
:show-artist="false"
:show-album="false"
:paginate-results="true"
:total="count" :total="count"
></pagination> @page-changed="updatePage"
</div> :page="page"
</template> :paginate-by="limit"></track-table>
<template v-if="!isLoading && objects.length === 0"> <template v-if="!isLoading && objects.length === 0">
<empty-state @refresh="fetchData('tracks/')" :refresh="true"> <empty-state @refresh="fetchData('tracks/')" :refresh="true">
<p> <p>
...@@ -30,19 +46,19 @@ ...@@ -30,19 +46,19 @@
<script> <script>
import _ from '@/lodash' import _ from '@/lodash'
import axios from 'axios' import axios from 'axios'
import ChannelEntryCard from '@/components/audio/ChannelEntryCard' import PodcastTable from '@/components/audio/podcast/Table'
import Pagination from "@/components/Pagination" import TrackTable from '@/components/audio/track/Table'
import PaginationMixin from "@/components/mixins/Pagination"
export default { export default {
props: { props: {
filters: {type: Object, required: true}, filters: {type: Object, required: true},
limit: {type: Number, default: 10}, limit: {type: Number, default: 10},
defaultCover: {type: Object}, defaultCover: {type: Object},
isPodcast: {type: Boolean, required: true},
}, },
components: { components: {
ChannelEntryCard, PodcastTable,
Pagination TrackTable,
}, },
data () { data () {
return { return {
...@@ -58,7 +74,7 @@ export default { ...@@ -58,7 +74,7 @@ export default {
this.fetchData('tracks/') this.fetchData('tracks/')
}, },
methods: { methods: {
fetchData (url) { async fetchData (url) {
if (!url) { if (!url) {
return return
} }
...@@ -68,16 +84,17 @@ export default { ...@@ -68,16 +84,17 @@ export default {
params.page_size = this.limit params.page_size = this.limit
params.page = this.page params.page = this.page
params.include_channels = true params.include_channels = true
axios.get(url, {params: params}).then((response) => { try {
self.nextPage = response.data.next let channelsPromise = await axios.get(url, {params: params})
self.nextPage = channelsPromise.data.next
self.objects = channelsPromise.data.results
self.count = channelsPromise.data.count
self.$emit('fetched', channelsPromise.data)
self.isLoading = false self.isLoading = false
self.objects = response.data.results } catch(e) {
self.count = response.data.count
self.$emit('fetched', response.data)
}, error => {
self.isLoading = false self.isLoading = false
self.errors = error.backendErrors self.errors = error.backendErrors
}) }
}, },
updatePage: function(page) { updatePage: function(page) {
this.page = page this.page = page
......
...@@ -6,7 +6,8 @@ ...@@ -6,7 +6,8 @@
:disabled="!playable" :disabled="!playable"
:aria-label="labels.replacePlay" :aria-label="labels.replacePlay"
:class="buttonClasses.concat(['ui', {loading: isLoading}, {'mini': discrete}, {disabled: !playable}])"> :class="buttonClasses.concat(['ui', {loading: isLoading}, {'mini': discrete}, {disabled: !playable}])">
<i :class="[playIconClass, 'icon']"></i> <i v-if="playing" class="pause icon"></i>
<i v-else :class="[playIconClass, 'icon']"></i>
<template v-if="!discrete && !iconOnly">&nbsp;<slot><translate translate-context="*/Queue/Button.Label/Short, Verb">Play</translate></slot></template> <template v-if="!discrete && !iconOnly">&nbsp;<slot><translate translate-context="*/Queue/Button.Label/Short, Verb">Play</translate></slot></template>
</button> </button>
<button <button
...@@ -27,8 +28,14 @@ ...@@ -27,8 +28,14 @@
<button v-if="track" class="item basic" :disabled="!playable" @click.stop.prevent="$store.dispatch('radios/start', {type: 'similar', objectId: track.id})" :title="labels.startRadio"> <button v-if="track" class="item basic" :disabled="!playable" @click.stop.prevent="$store.dispatch('radios/start', {type: 'similar', objectId: track.id})" :title="labels.startRadio">
<i class="feed icon"></i><translate translate-context="*/Queue/Button.Label/Short, Verb">Play radio</translate> <i class="feed icon"></i><translate translate-context="*/Queue/Button.Label/Short, Verb">Play radio</translate>
</button> </button>
<button v-if="track" class="item basic" :disabled="!playable" @click.stop="$store.commit('playlists/chooseTrack', track)">
<i class="list icon"></i>
<translate translate-context="Sidebar/Player/Icon.Tooltip/Verb">Add to playlist…</translate>
</button>
<button v-if="track" class="item basic" @click.stop.prevent="$router.push(`/library/tracks/${track.id}/`)"> <button v-if="track" class="item basic" @click.stop.prevent="$router.push(`/library/tracks/${track.id}/`)">
<i class="info icon"></i><translate translate-context="*/Queue/Dropdown/Button/Label/Short">Track details</translate> <i class="info icon"></i>
<translate v-if="track.artist.content_category === 'podcast'" translate-context="*/Queue/Dropdown/Button/Label/Short">Episode details</translate>
<translate v-else translate-context="*/Queue/Dropdown/Button/Label/Short">Track details</translate>
</button> </button>
<div class="divider"></div> <div class="divider"></div>
<button v-if="filterableArtist" ref="filterArtist" data-ref="filterArtist" class="item basic" :disabled="!filterableArtist" @click.stop.prevent="filterArtist" :title="labels.hideArtist"> <button v-if="filterableArtist" ref="filterArtist" data-ref="filterArtist" class="item basic" :disabled="!filterableArtist" @click.stop.prevent="filterArtist" :title="labels.hideArtist">
...@@ -52,9 +59,10 @@ import axios from 'axios' ...@@ -52,9 +59,10 @@ import axios from 'axios'
import jQuery from 'jquery' import jQuery from 'jquery'
import ReportMixin from '@/components/mixins/Report' import ReportMixin from '@/components/mixins/Report'
import PlayOptionsMixin from '@/components/mixins/PlayOptions'
export default { export default {
mixins: [ReportMixin], mixins: [ReportMixin, PlayOptionsMixin],
props: { props: {
// we can either have a single or multiple tracks to play when clicked // we can either have a single or multiple tracks to play when clicked
tracks: {type: Array, required: false}, tracks: {type: Array, required: false},
...@@ -71,7 +79,9 @@ export default { ...@@ -71,7 +79,9 @@ export default {
album: {type: Object, required: false}, album: {type: Object, required: false},
library: {type: Object, required: false}, library: {type: Object, required: false},
channel: {type: Object, required: false}, channel: {type: Object, required: false},
isPlayable: {type: Boolean, required: false, default: null} isPlayable: {type: Boolean, required: false, default: null},
playing: {type: Boolean, required: false, default: false},
paused: {type: Boolean, required: false, default: false}
}, },
data () { data () {
return { return {
...@@ -100,6 +110,7 @@ export default { ...@@ -100,6 +110,7 @@ export default {
playNext: this.$pgettext('*/Queue/Dropdown/Button/Title', 'Play next'), playNext: this.$pgettext('*/Queue/Dropdown/Button/Title', 'Play next'),
startRadio: this.$pgettext('*/Queue/Dropdown/Button/Title', 'Play similar songs'), startRadio: this.$pgettext('*/Queue/Dropdown/Button/Title', 'Play similar songs'),
report: this.$pgettext('*/Moderation/*/Button/Label,Verb', 'Report…'), report: this.$pgettext('*/Moderation/*/Button/Label,Verb', 'Report…'),
addToPlaylist: this.$pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Add to playlist…'),
replacePlay, replacePlay,
} }
}, },
...@@ -112,165 +123,6 @@ export default { ...@@ -112,165 +123,6 @@ export default {
} }
} }
}, },
playable () {
if (this.isPlayable) {
return true
}
if (this.track) {
return this.track.uploads && this.track.uploads.length > 0
} else if (this.artist && this.artist.tracks_count) {
return this.artist.tracks_count > 0
} else if (this.artist && this.artist.albums) {
return this.artist.albums.filter((a) => {
return a.is_playable === true
}).length > 0
} else if (this.album) {
return true
} else if (this.tracks) {
return this.tracks.filter((t) => {
return t.uploads && t.uploads.length > 0
}).length > 0
}
return false
},
filterableArtist () {
if (this.track) {
return this.track.artist
}
if (this.album) {
return this.album.artist
}
if (this.artist) {
return this.artist
}
},
},
methods: {
filterArtist () {
this.$store.dispatch('moderation/hide', {type: 'artist', target: this.filterableArtist})
},
getTracksPage (page, params, resolve, tracks) {
if (page > 10) {
// it's 10 * 100 tracks already, let's stop here
resolve(tracks)
}
// when fetching artists/or album tracks, sometimes, we may have to fetch
// multiple pages
let self = this
params['page_size'] = 100
params['page'] = page
params['hidden'] = ''
params['playable'] = 'true'
tracks = tracks || []
axios.get('tracks/', {params: params}).then((response) => {
response.data.results.forEach(t => {
tracks.push(t)
})
if (response.data.next) {
self.getTracksPage(page + 1, params, resolve, tracks)
} else {
resolve(tracks)
}
})
},
getPlayableTracks () {
let self = this
this.isLoading = true
let getTracks = new Promise((resolve, reject) => {
if (self.tracks) {
resolve(self.tracks)
} else if (self.track) {
if (!self.track.uploads || self.track.uploads.length === 0) {
// fetch uploads from api
axios.get(`tracks/${self.track.id}/`).then((response) => {
resolve([response.data])
})
} else {
resolve([self.track])
}
} else if (self.playlist) {
let url = 'playlists/' + self.playlist.id + '/'
axios.get(url + 'tracks/').then((response) => {
let artistIds = self.$store.getters['moderation/artistFilters']().map((f) => {
return f.target.id
})
let tracks = response.data.results.map(plt => {
return plt.track
})
if (artistIds.length > 0) {
// skip tracks from hidden artists
tracks = tracks.filter((t) => {
let matchArtist = artistIds.indexOf(t.artist.id) > -1
return !(matchArtist || t.album && artistIds.indexOf(t.album.artist.id) > -1)
})
}
resolve(tracks)
})
} else if (self.artist) {
let params = {'artist': self.artist.id, include_channels: 'true', 'ordering': 'album__release_date,disc_number,position'}
self.getTracksPage(1, params, resolve)
} else if (self.album) {
let params = {'album': self.album.id, include_channels: 'true', 'ordering': 'disc_number,position'}
self.getTracksPage(1, params, resolve)
} else if (self.library) {
let params = {'library': self.library.uuid, 'ordering': '-creation_date'}
self.getTracksPage(1, params, resolve)
}
})
return getTracks.then((tracks) => {
setTimeout(e => {
self.isLoading = false
}, 250)
return tracks.filter(e => {
return e.uploads && e.uploads.length > 0
})
})
},
add () {
let self = this
this.getPlayableTracks().then((tracks) => {
self.$store.dispatch('queue/appendMany', {tracks: tracks}).then(() => self.addMessage(tracks))
})
jQuery(self.$el).find('.ui.dropdown').dropdown('hide')
},
replacePlay () {
let self = this
self.$store.dispatch('queue/clean')
this.getPlayableTracks().then((tracks) => {
self.$store.dispatch('queue/appendMany', {tracks: tracks}).then(() => {
if (self.track) {
// set queue position to selected track
const trackIndex = self.tracks.findIndex(track => track.id === self.track.id)
self.$store.dispatch('queue/currentIndex', trackIndex)
}
self.addMessage(tracks)
})
})
jQuery(self.$el).find('.ui.dropdown').dropdown('hide')
},
addNext (next) {
let self = this
let wasEmpty = this.$store.state.queue.tracks.length === 0
this.getPlayableTracks().then((tracks) => {
self.$store.dispatch('queue/appendMany', {tracks: tracks, index: self.$store.state.queue.currentIndex + 1}).then(() => self.addMessage(tracks))
let goNext = next && !wasEmpty
if (goNext) {
self.$store.dispatch('queue/next')
}
})
jQuery(self.$el).find('.ui.dropdown').dropdown('hide')
},
addMessage (tracks) {
if (tracks.length < 1) {
return
}
let msg = this.$npgettext('*/Queue/Message', '%{ count } track was added to your queue', '%{ count } tracks were added to your queue', tracks.length)
this.$store.commit('ui/addMessage', {
content: this.$gettextInterpolate(msg, {count: tracks.length}),
date: new Date()
})
},
}, },
watch: { watch: {
clicked () { clicked () {
......
<template>
<div
:class="[
{ active: currentTrack && track.id === currentTrack.id },
'track-row row mobile',
]"
>
<div
v-if="showArt"
@click.prevent.exact="activateTrack(track, index)"
class="image left floated column"
>
<img
alt=""
class="ui artist-track mini image"
v-if="
track.album && track.album.cover && track.album.cover.urls.original
"
v-lazy="
$store.getters['instance/absoluteUrl'](
track.album.cover.urls.medium_square_crop
)
"
/>
<img
alt=""
class="ui artist-track mini image"
v-else-if="
track.cover
"
v-lazy="
$store.getters['instance/absoluteUrl'](
track.cover.urls.medium_square_crop
)
"
/>
<img
alt=""
class="ui artist-track mini image"
v-else-if="
track.artist.cover
"
v-lazy="
$store.getters['instance/absoluteUrl'](
track.artist.cover.urls.medium_square_crop
)
"
/>
<img
alt=""
class="ui artist-track mini image"
v-else
src="../../../assets/audio/default-cover.png"
/>
</div>
<div
tabindex=0
@click="activateTrack(track, index)"
role="button"
class="content ellipsis left floated column"
>
<p
:class="[
'track-title',
'mobile',
{ 'play-indicator': isPlaying && currentTrack && track.id === currentTrack.id },
]"
>
{{ track.title }}
</p>
<p v-if="track.artist.content_category === 'podcast'" class="track-meta mobile">
<human-date class="really discrete" :date="track.creation_date"></human-date>
<span>&#183;</span>
<human-duration
v-if="track.uploads[0] && track.uploads[0].duration"
:duration="track.uploads[0].duration"
></human-duration>
</p>
<p v-else class="track-meta mobile">
{{ track.artist.name }} <span>&#183;</span>
<human-duration
v-if="track.uploads[0] && track.uploads[0].duration"
:duration="track.uploads[0].duration"
></human-duration>
</p>
</div>
<div
v-if="$store.state.auth.authenticated && this.track.artist.content_category !== 'podcast'"
:class="[
'meta',
'right',
'floated',
'column',
'mobile',
{ 'with-art': showArt },
]"
role="button"
>
<track-favorite-icon
class="tiny"
:border="false"
:track="track"
></track-favorite-icon>
</div>
<div
role="button"
:aria-label="actionsButtonLabel"
@click.prevent.exact="showTrackModal = !showTrackModal"
:class="[
'modal-button',
'right',
'floated',
'column',
'mobile',
{ 'with-art': showArt },
]"
>
<i class="ellipsis large vertical icon" />
</div>
<track-modal
@update:show="showTrackModal = $event;"
:show="showTrackModal"
:track="track"
:index="index"
:is-artist="isArtist"
:is-album="isAlbum"
></track-modal>
</div>
</template>
<script>
import PlayIndicator from "@/components/audio/track/PlayIndicator";
import { mapActions, mapGetters } from "vuex";
import TrackFavoriteIcon from "@/components/favorites/TrackFavoriteIcon";
import TrackModal from "@/components/audio/track/Modal";
import PlayOptionsMixin from "@/components/mixins/PlayOptions"
export default {
mixins: [PlayOptionsMixin],
data() {
return {
showTrackModal: false,
}
},
props: {
tracks: Array,
showAlbum: { type: Boolean, required: false, default: true },
showArtist: { type: Boolean, required: false, default: true },
showPosition: { type: Boolean, required: false, default: false },
showArt: { type: Boolean, required: false, default: true },
search: { type: Boolean, required: false, default: false },
filters: { type: Object, required: false, default: null },
nextUrl: { type: String, required: false, default: null },
displayActions: { type: Boolean, required: false, default: true },
showDuration: { type: Boolean, required: false, default: true },
index: { type: Number, required: true },
track: { type: Object, required: true },
isArtist: {type: Boolean, required: false, default: false},
isAlbum: {type: Boolean, required: false, default: false},
},
components: {
PlayIndicator,
TrackFavoriteIcon,
TrackModal,
},
computed: {
...mapGetters({
currentTrack: "queue/currentTrack",
}),
isPlaying() {
return this.$store.state.player.playing;
},
actionsButtonLabel () {
return this.$pgettext('Content/Track/Icon.Tooltip/Verb', 'Show track actions')
},
},
methods: {
prettyPosition(position, size) {
var s = String(position);
while (s.length < (size || 2)) {
s = "0" + s;
}
return s;
},
...mapActions({
resumePlayback: "player/resumePlayback",
pausePlayback: "player/pausePlayback",
}),
},
};
</script>
<template>
<modal
@update:show="$emit('update:show', $event)"
:show="show"
:scrolling="true"
:additionalClasses="['scrolling-track-options']"
>
<div class="header">
<div class="ui large centered rounded image">
<img
alt=""
class="ui centered image"
v-if="
track.album && track.album.cover && track.album.cover.urls.original
"
v-lazy="
$store.getters['instance/absoluteUrl'](
track.album.cover.urls.medium_square_crop
)
"
/>
<img
alt=""
class="ui centered image"
v-else-if="track.cover"
v-lazy="
$store.getters['instance/absoluteUrl'](
track.cover.urls.medium_square_crop
)
"
/>
<img
alt=""
class="ui centered image"
v-else-if="track.artist.cover"
v-lazy="
$store.getters['instance/absoluteUrl'](
track.artist.cover.urls.medium_square_crop
)
"
/>
<img
alt=""
class="ui centered image"
v-else
src="../../../assets/audio/default-cover.png"
/>
</div>
<h3 class="track-modal-title">{{ track.title }}</h3>
<h4 class="track-modal-subtitle">{{ track.artist.name }}</h4>
</div>
<div class="ui hidden divider"></div>
<div class="content">
<div class="ui one column unstackable grid">
<div
class="row"
v-if="$store.state.auth.authenticated && this.track.artist.content_category !== 'podcast'">
<div
tabindex="0"
class="column"
role="button"
:aria-label="favoriteButton"
@click.stop="$store.dispatch('favorites/toggle', track.id)"
>
<i
:class="[
'heart',
'favorite-icon',
{ favorited: isFavorite },
{ pink: isFavorite },
'icon',
'track-modal',
'list-icon',
]"
/>
<span class="track-modal list-item">{{ favoriteButton }}</span>
</div>
</div>
<div class="row">
<div
class="column"
role="button"
@click.stop.prevent="
add();
closeModal();
"
:aria-label="labels.addToQueue"
>
<i class="plus icon track-modal list-icon" />
<span class="track-modal list-item">{{ labels.addToQueue }}</span>
</div>
</div>
<div class="row">
<div
class="column"
role="button"
@click.stop.prevent="
addNext(true);
closeModal();
"
:aria-label="labels.playNext"
>
<i class="step forward icon track-modal list-icon" />
<span class="track-modal list-item">{{ labels.playNext }}</span>
</div>
</div>
<div class="row">
<div
class="column"
role="button"
@click.stop.prevent="
$store.dispatch('radios/start', {
type: 'similar',
objectId: track.id,
});
closeModal();
"
:aria-label="labels.startRadio"
>
<i class="rss icon track-modal list-icon" />
<span class="track-modal list-item">{{ labels.startRadio }}</span>
</div>
</div>
<div class="row">
<div
class="column"
role="button"
@click.stop="$store.commit('playlists/chooseTrack', track)"
:aria-label="labels.addToPlaylist"
>
<i class="list icon track-modal list-icon" />
<span class="track-modal list-item">{{
labels.addToPlaylist
}}</span>
</div>
</div>
<div class="ui divider"></div>
<div v-if="!isAlbum && track.album" class="row">
<div
class="column"
role="button"
:aria-label="albumDetailsButton"
@click.prevent.exact="
$router.push({
name: 'library.albums.detail',
params: { id: track.album.id },
})
"
>
<i class="compact disc icon track-modal list-icon" />
<span class="track-modal list-item">{{
albumDetailsButton
}}</span>
</div>
</div>
<div v-if="!isArtist" class="row">
<div
class="column"
role="button"
:aria-label="artistDetailsButton"
@click.prevent.exact="
$router.push({
name: 'library.artists.detail',
params: { id: track.artist.id },
})
"
>
<i class="user icon track-modal list-icon" />
<span class="track-modal list-item">{{
artistDetailsButton
}}</span>
</div>
</div>
<div class="row">
<div
class="column"
role="button"
:aria-label="trackDetailsButton"
@click.prevent.exact="
$router.push({
name: 'library.tracks.detail',
params: { id: track.id },
})
"
>
<i class="info icon track-modal list-icon" />
<span class="track-modal list-item">{{
trackDetailsButton
}}</span>
</div>
</div>
<div class="ui divider"></div>
<div
v-for="obj in getReportableObjs({
track,
album,
artist,
})"
:key="obj.target.type + obj.target.id"
class="row"
:ref="`report${obj.target.type}${obj.target.id}`"
:data-ref="`report${obj.target.type}${obj.target.id}`"
@click.stop.prevent="$store.dispatch('moderation/report', obj.target)"
>
<div class="column">
<i class="share icon track-modal list-icon" /><span
class="track-modal list-item"
>{{ obj.label }}</span
>
</div>
</div>
</div>
</div>
</modal>
</template>
<script>
import Modal from "@/components/semantic/Modal";
import TrackFavoriteIcon from "@/components/favorites/TrackFavoriteIcon";
import ReportMixin from '@/components/mixins/Report'
import PlayOptionsMixin from '@/components/mixins/PlayOptions'
export default {
mixins: [ReportMixin, PlayOptionsMixin],
props: {
show: { type: Boolean, required: true, default: false },
track: { type: Object, required: true },
index: { type: Number, required: true },
isArtist: { type: Boolean, required: false, default: false },
isAlbum: { type: Boolean, required: false, default: false },
},
components: {
Modal,
TrackFavoriteIcon,
},
data() {
return {
isShowing: this.show,
tracks: [this.track],
album: this.track.album,
artist: this.track.artist,
};
},
computed: {
isFavorite() {
return this.$store.getters["favorites/isFavorite"](this.track.id);
},
favoriteButton() {
if (this.isFavorite) {
return this.$pgettext(
"Content/Track/Icon.Tooltip/Verb",
"Remove from favorites"
);
} else {
return this.$pgettext("Content/Track/*/Verb", "Add to favorites");
}
},
trackDetailsButton() {
if (this.track.artist.content_category === 'podcast') {
return this.$pgettext("*/Queue/Dropdown/Button/Label/Short", "Episode details")
} else {
return this.$pgettext("*/Queue/Dropdown/Button/Label/Short", "Track details")
}
},
albumDetailsButton() {
if (this.track.artist.content_category === 'podcast') {
return this.$pgettext("*/Queue/Dropdown/Button/Label/Short", "View series")
} else {
return this.$pgettext("*/Queue/Dropdown/Button/Label/Short", "View album")
}
},
artistDetailsButton() {
if (this.track.artist.content_category === 'podcast') {
return this.$pgettext("*/Queue/Dropdown/Button/Label/Short", "View channel")
} else {
return this.$pgettext("*/Queue/Dropdown/Button/Label/Short", "View artist")
}
},
labels() {
return {
startRadio: this.$pgettext(
"*/Queue/Dropdown/Button/Title",
"Play radio"
),
playNow: this.$pgettext("*/Queue/Dropdown/Button/Title", "Play now"),
addToQueue: this.$pgettext(
"*/Queue/Dropdown/Button/Title",
"Add to queue"
),
playNext: this.$pgettext("*/Queue/Dropdown/Button/Title", "Play next"),
addToPlaylist: this.$pgettext(
"Sidebar/Player/Icon.Tooltip/Verb",
"Add to playlist…"
),
};
},
},
methods: {
closeModal() {
this.$emit("update:show", false);
},
},
};
</script>
<template>
<div
:class="[
{ active: currentTrack && track.id === currentTrack.id },
'track-row podcast row',
]"
@mouseover="hover = track.id"
@mouseleave="hover = null"
@dblclick="activateTrack(track, index)"
>
<div
v-if="showArt"
class="image left floated column"
role="button"
@click.prevent.exact="activateTrack(track, index)"
>
<img
alt=""
class="ui artist-track mini image"
v-if="
track.cover && track.cover.urls.original
"
v-lazy="
$store.getters['instance/absoluteUrl'](
track.cover.urls.medium_square_crop
)
"
/>
<img
alt=""
class="ui artist-track mini image"
v-else-if="
defaultCover
"
v-lazy="
$store.getters['instance/absoluteUrl'](
defaultCover.cover.urls.medium_square_crop
)
"
/>
<img
alt=""
class="ui artist-track mini image"
v-else
src="../../../assets/audio/default-cover.png"
/>
</div>
<div tabindex=0 class="content left floated column">
<a
class="podcast-episode-title ellipsis"
@click.prevent.exact="activateTrack(track, index)">{{ track.title }}</a>
<p class="podcast-episode-meta">
An episode description, with all its twists and turns!
This episode focuses on something I'm sure, but nobody really knows what it's focusing on.</p>
</div>
<div v-if="displayActions" class="meta right floated column">
<play-button
id="playmenu"
class="play-button basic icon"
:dropdown-only="true"
:is-playable="track.is_playable"
:dropdown-icon-classes="[
'ellipsis',
'vertical',
'large really discrete',
]"
:track="track"
></play-button>
</div>
</div>
</template>
<script>
import PlayIndicator from "@/components/audio/track/PlayIndicator";
import { mapActions, mapGetters } from "vuex";
import PlayButton from "@/components/audio/PlayButton";
import PlayOptions from "@/components/mixins/PlayOptions";
export default {
mixins: [PlayOptions],
props: {
tracks: Array,
showAlbum: { type: Boolean, required: false, default: true },
showArtist: { type: Boolean, required: false, default: true },
showPosition: { type: Boolean, required: false, default: false },
showArt: { type: Boolean, required: false, default: true },
search: { type: Boolean, required: false, default: false },
filters: { type: Object, required: false, default: null },
nextUrl: { type: String, required: false, default: null },
displayActions: { type: Boolean, required: false, default: true },
showDuration: { type: Boolean, required: false, default: true },
index: { type: Number, required: true },
track: { type: Object, required: true },
defaultCover: { type: Object, required: false },
},
data() {
return {
hover: null,
}
},
components: {
PlayIndicator,
PlayButton,
},
computed: {
...mapGetters({
currentTrack: "queue/currentTrack",
}),
isPlaying() {
return this.$store.state.player.playing;
},
},
methods: {
prettyPosition(position, size) {
var s = String(position);
while (s.length < (size || 2)) {
s = "0" + s;
}
return s;
},
...mapActions({
resumePlayback: "player/resumePlayback",
pausePlayback: "player/pausePlayback",
}),
},
};
</script>
<template>
<div>
<div class="ui hidden divider"></div>
<!-- Add a header if needed -->
<slot name="header"></slot>
<div>
<div
:class="['track-table', 'ui', 'unstackable', 'grid', 'tablet-and-up']"
>
<!-- For each item, build a row -->
<podcast-row
v-for="(track, index) in tracks"
:default-cover="defaultCover"
:track="track"
:key="track.id"
:index="index"
:tracks="tracks"
:display-actions="displayActions"
:show-duration="showDuration"
:is-podcast="isPodcast"
></podcast-row>
</div>
<div v-if="paginateResults" class="ui center aligned basic segment desktop-and-up">
<pagination
:total="total"
:current="page"
:paginate-by="paginateBy"
v-on="$listeners">
</pagination>
</div>
</div>
<div
:class="['track-table', 'ui', 'unstackable', 'grid', 'tablet-and-below']"
>
<div v-if="isLoading" class="ui inverted active dimmer">
<div class="ui loader"></div>
</div>
<!-- For each item, build a row -->
<track-mobile-row
v-for="(track, index) in tracks"
:track="track"
:key="track.id"
:index="index"
:tracks="tracks"
:show-position="showPosition"
:show-art="showArt"
:show-duration="showDuration"
:is-artist="isArtist"
:is-album="isAlbum"
:is-podcast="isPodcast"
></track-mobile-row>
<div v-if="paginateResults" class="ui center aligned basic segment tablet-and-below">
<pagination
v-if="paginateResults"
:total="total"
:current="page"
:compact="true"
v-on="$listeners"></pagination>
</div>
</div>
</div>
</template>
<script>
import _ from "@/lodash";
import TrackRow from "@/components/audio/track/Row";
import PodcastRow from "@/components/audio/podcast/Row";
import TrackMobileRow from "@/components/audio/track/MobileRow";
import Pagination from "@/components/Pagination";
export default {
components: {
TrackRow,
TrackMobileRow,
Pagination,
PodcastRow,
},
props: {
tracks: Array,
showAlbum: { type: Boolean, required: false, default: true },
showArtist: { type: Boolean, required: false, default: true },
showPosition: { type: Boolean, required: false, default: false },
showArt: { type: Boolean, required: false, default: true },
search: { type: Boolean, required: false, default: false },
filters: { type: Object, required: false, default: null },
nextUrl: { type: String, required: false, default: null },
displayActions: { type: Boolean, required: false, default: true },
showDuration: { type: Boolean, required: false, default: true },
isArtist: { type: Boolean, required: false, default: false },
isAlbum: { type: Boolean, required: false, default: false },
paginateResults: { type: Boolean, required: false, default: true},
total: { type: Number, required: false},
page: {type: Number, required: false, default: 1},
paginateBy: {type: Number, required: false, default: 25},
isPodcast: {type: Boolean, required: true},
defaultCover: {type: Object, required: false},
},
data() {
return {
isLoading: false,
};
},
computed: {
labels() {
return {
title: this.$pgettext("*/*/*/Noun", "Title"),
album: this.$pgettext("*/*/*/Noun", "Album"),
artist: this.$pgettext("*/*/*/Noun", "Artist"),
};
},
},
methods: {
updatePage: function(page) {
this.$emit('page-changed', page)
}
},
};
</script>
<template>
<div
:class="[
{ active: currentTrack && track.id === currentTrack.id },
'track-row row mobile',
]"
>
<div
v-if="showArt"
@click.prevent.exact="activateTrack(track, index)"
class="image left floated column"
>
<img
alt=""
class="ui artist-track mini image"
v-if="
track.album && track.album.cover && track.album.cover.urls.original
"
v-lazy="
$store.getters['instance/absoluteUrl'](
track.album.cover.urls.medium_square_crop
)
"
/>
<img
alt=""
class="ui artist-track mini image"
v-else-if="
track.cover
"
v-lazy="
$store.getters['instance/absoluteUrl'](
track.cover.urls.medium_square_crop
)
"
/>
<img
alt=""
class="ui artist-track mini image"
v-else-if="
track.artist.cover
"
v-lazy="
$store.getters['instance/absoluteUrl'](
track.artist.cover.urls.medium_square_crop
)
"
/>
<img
alt=""
class="ui artist-track mini image"
v-else
src="../../../assets/audio/default-cover.png"
/>
</div>
<div
tabindex=0
@click="activateTrack(track, index)"
role="button"
class="content ellipsis left floated column"
>
<p
:class="[
'track-title',
'mobile',
{ 'play-indicator': isPlaying && currentTrack && track.id === currentTrack.id },
]"
>
{{ track.title }}
</p>
<p class="track-meta mobile">
{{ track.artist.name }} <span>&#183;</span>
<human-duration
v-if="track.uploads[0] && track.uploads[0].duration"
:duration="track.uploads[0].duration"
></human-duration>
</p>
</div>
<div
v-if="$store.state.auth.authenticated"
:class="[
'meta',
'right',
'floated',
'column',
'mobile',
{ 'with-art': showArt },
]"
role="button"
>
<track-favorite-icon
class="tiny"
:border="false"
:track="track"
></track-favorite-icon>
</div>
<div
role="button"
:aria-label="actionsButtonLabel"
@click.prevent.exact="showTrackModal = !showTrackModal"
:class="[
'modal-button',
'right',
'floated',
'column',
'mobile',
{ 'with-art': showArt },
]"
>
<i class="ellipsis large vertical icon" />
</div>
<track-modal
@update:show="showTrackModal = $event;"
:show="showTrackModal"
:track="track"
:index="index"
:is-artist="isArtist"
:is-album="isAlbum"
></track-modal>
</div>
</template>
<script>
import PlayIndicator from "@/components/audio/track/PlayIndicator";
import { mapActions, mapGetters } from "vuex";
import TrackFavoriteIcon from "@/components/favorites/TrackFavoriteIcon";
import TrackModal from "@/components/audio/track/Modal";
import PlayOptionsMixin from "@/components/mixins/PlayOptions"
export default {
mixins: [PlayOptionsMixin],
data() {
return {
showTrackModal: false,
}
},
props: {
tracks: Array,
showAlbum: { type: Boolean, required: false, default: true },
showArtist: { type: Boolean, required: false, default: true },
showPosition: { type: Boolean, required: false, default: false },
showArt: { type: Boolean, required: false, default: true },
search: { type: Boolean, required: false, default: false },
filters: { type: Object, required: false, default: null },
nextUrl: { type: String, required: false, default: null },
displayActions: { type: Boolean, required: false, default: true },
showDuration: { type: Boolean, required: false, default: true },
index: { type: Number, required: true },
track: { type: Object, required: true },
isArtist: {type: Boolean, required: false, default: false},
isAlbum: {type: Boolean, required: false, default: false},
},
components: {
PlayIndicator,
TrackFavoriteIcon,
TrackModal,
},
computed: {
...mapGetters({
currentTrack: "queue/currentTrack",
}),
isPlaying() {
return this.$store.state.player.playing;
},
actionsButtonLabel () {
return this.$pgettext('Content/Track/Icon.Tooltip/Verb', 'Show track actions')
},
},
methods: {
prettyPosition(position, size) {
var s = String(position);
while (s.length < (size || 2)) {
s = "0" + s;
}
return s;
},
...mapActions({
resumePlayback: "player/resumePlayback",
pausePlayback: "player/pausePlayback",
}),
},
};
</script>
<template>
<modal
@update:show="$emit('update:show', $event)"
:show="show"
:scrolling="true"
:additionalClasses="['scrolling-track-options']"
>
<div class="header">
<div class="ui large centered rounded image">
<img
alt=""
class="ui centered image"
v-if="
track.album && track.album.cover && track.album.cover.urls.original
"
v-lazy="
$store.getters['instance/absoluteUrl'](
track.album.cover.urls.medium_square_crop
)
"
/>
<img
alt=""
class="ui centered image"
v-else-if="track.cover"
v-lazy="
$store.getters['instance/absoluteUrl'](
track.cover.urls.medium_square_crop
)
"
/>
<img
alt=""
class="ui centered image"
v-else-if="track.artist.cover"
v-lazy="
$store.getters['instance/absoluteUrl'](
track.artist.cover.urls.medium_square_crop
)
"
/>
<img
alt=""
class="ui centered image"
v-else
src="../../../assets/audio/default-cover.png"
/>
</div>
<h3 class="track-modal-title">{{ track.title }}</h3>
<h4 class="track-modal-subtitle">{{ track.artist.name }}</h4>
</div>
<div class="ui hidden divider"></div>
<div class="content">
<div class="ui one column unstackable grid">
<div
class="row"
v-if="$store.state.auth.authenticated && this.track.artist.content_category !== 'podcast'">
<div
tabindex="0"
class="column"
role="button"
:aria-label="favoriteButton"
@click.stop="$store.dispatch('favorites/toggle', track.id)"
>
<i
:class="[
'heart',
'favorite-icon',
{ favorited: isFavorite },
{ pink: isFavorite },
'icon',
'track-modal',
'list-icon',
]"
/>
<span class="track-modal list-item">{{ favoriteButton }}</span>
</div>
</div>
<div class="row">
<div
class="column"
role="button"
@click.stop.prevent="
add();
closeModal();
"
:aria-label="labels.addToQueue"
>
<i class="plus icon track-modal list-icon" />
<span class="track-modal list-item">{{ labels.addToQueue }}</span>
</div>
</div>
<div class="row">
<div
class="column"
role="button"
@click.stop.prevent="
addNext(true);
closeModal();
"
:aria-label="labels.playNext"
>
<i class="step forward icon track-modal list-icon" />
<span class="track-modal list-item">{{ labels.playNext }}</span>
</div>
</div>
<div class="row">
<div
class="column"
role="button"
@click.stop.prevent="
$store.dispatch('radios/start', {
type: 'similar',
objectId: track.id,
});
closeModal();
"
:aria-label="labels.startRadio"
>
<i class="rss icon track-modal list-icon" />
<span class="track-modal list-item">{{ labels.startRadio }}</span>
</div>
</div>
<div class="row">
<div
class="column"
role="button"
@click.stop="$store.commit('playlists/chooseTrack', track)"
:aria-label="labels.addToPlaylist"
>
<i class="list icon track-modal list-icon" />
<span class="track-modal list-item">{{
labels.addToPlaylist
}}</span>
</div>
</div>
<div class="ui divider"></div>
<div v-if="!isAlbum && track.album" class="row">
<div
class="column"
role="button"
:aria-label="albumDetailsButton"
@click.prevent.exact="
$router.push({
name: 'library.albums.detail',
params: { id: track.album.id },
})
"
>
<i class="compact disc icon track-modal list-icon" />
<span class="track-modal list-item">{{
albumDetailsButton
}}</span>
</div>
</div>
<div v-if="!isArtist" class="row">
<div
class="column"
role="button"
:aria-label="artistDetailsButton"
@click.prevent.exact="
$router.push({
name: 'library.artists.detail',
params: { id: track.artist.id },
})
"
>
<i class="user icon track-modal list-icon" />
<span class="track-modal list-item">{{
artistDetailsButton
}}</span>
</div>
</div>
<div class="row">
<div
class="column"
role="button"
:aria-label="trackDetailsButton"
@click.prevent.exact="
$router.push({
name: 'library.tracks.detail',
params: { id: track.id },
})
"
>
<i class="info icon track-modal list-icon" />
<span class="track-modal list-item">{{
trackDetailsButton
}}</span>
</div>
</div>
<div class="ui divider"></div>
<div
v-for="obj in getReportableObjs({
track,
album,
artist,
})"
:key="obj.target.type + obj.target.id"
class="row"
:ref="`report${obj.target.type}${obj.target.id}`"
:data-ref="`report${obj.target.type}${obj.target.id}`"
@click.stop.prevent="$store.dispatch('moderation/report', obj.target)"
>
<div class="column">
<i class="share icon track-modal list-icon" /><span
class="track-modal list-item"
>{{ obj.label }}</span
>
</div>
</div>
</div>
</div>
</modal>
</template>
<script>
import Modal from "@/components/semantic/Modal";
import TrackFavoriteIcon from "@/components/favorites/TrackFavoriteIcon";
import ReportMixin from '@/components/mixins/Report'
import PlayOptionsMixin from '@/components/mixins/PlayOptions'
export default {
mixins: [ReportMixin, PlayOptionsMixin],
props: {
show: { type: Boolean, required: true, default: false },
track: { type: Object, required: true },
index: { type: Number, required: true },
isArtist: { type: Boolean, required: false, default: false },
isAlbum: { type: Boolean, required: false, default: false },
},
components: {
Modal,
TrackFavoriteIcon,
},
data() {
return {
isShowing: this.show,
tracks: [this.track],
album: this.track.album,
artist: this.track.artist,
};
},
computed: {
isFavorite() {
return this.$store.getters["favorites/isFavorite"](this.track.id);
},
favoriteButton() {
if (this.isFavorite) {
return this.$pgettext(
"Content/Track/Icon.Tooltip/Verb",
"Remove from favorites"
);
} else {
return this.$pgettext("Content/Track/*/Verb", "Add to favorites");
}
},
trackDetailsButton() {
if (this.track.artist.content_category === 'podcast') {
return this.$pgettext("*/Queue/Dropdown/Button/Label/Short", "Episode details")
} else {
return this.$pgettext("*/Queue/Dropdown/Button/Label/Short", "Track details")
}
},
albumDetailsButton() {
if (this.track.artist.content_category === 'podcast') {
return this.$pgettext("*/Queue/Dropdown/Button/Label/Short", "View series")
} else {
return this.$pgettext("*/Queue/Dropdown/Button/Label/Short", "View album")
}
},
artistDetailsButton() {
if (this.track.artist.content_category === 'podcast') {
return this.$pgettext("*/Queue/Dropdown/Button/Label/Short", "View channel")
} else {
return this.$pgettext("*/Queue/Dropdown/Button/Label/Short", "View artist")
}
},
labels() {
return {
startRadio: this.$pgettext(
"*/Queue/Dropdown/Button/Title",
"Play radio"
),
playNow: this.$pgettext("*/Queue/Dropdown/Button/Title", "Play now"),
addToQueue: this.$pgettext(
"*/Queue/Dropdown/Button/Title",
"Add to queue"
),
playNext: this.$pgettext("*/Queue/Dropdown/Button/Title", "Play next"),
addToPlaylist: this.$pgettext(
"Sidebar/Player/Icon.Tooltip/Verb",
"Add to playlist…"
),
};
},
},
methods: {
closeModal() {
this.$emit("update:show", false);
},
},
};
</script>
<template>
<div id="audio-bars">
<div class="audio-bar"></div>
<div class="audio-bar"></div>
<div class="audio-bar"></div>
<div class="audio-bar"></div>
</div>
</template>
\ No newline at end of file
<template> <template>
<tr> <div
<td> :class="[
<play-button :class="['basic', {vibrant: currentTrack && isPlaying && track.id === currentTrack.id}, 'icon']" { active: currentTrack && track.id === currentTrack.id },
:discrete="true" 'track-row row',
:is-playable="playable" ]"
:track="track" @mouseover="hover = track.id"
:track-index="trackIndex" @mouseleave="hover = null"
:tracks="tracks"></play-button> @dblclick="activateTrack(track, index)"
</td> >
<td> <div
<img alt="" class="ui mini image" v-if="track.album && track.album.cover && track.album.cover.urls.original" v-lazy="$store.getters['instance/absoluteUrl'](track.album.cover.urls.medium_square_crop)"> class="actions one wide left floated column"
<img alt="" class="ui mini image" v-else src="../../../assets/audio/default-cover.png"> role="button"
</td> @click.prevent.exact="activateTrack(track, index)"
<td colspan="6"> >
<button class="track" @click.stop="playSong()"> <play-indicator
<template v-if="displayPosition && track.position"> v-if="
{{ track.position }}. !$store.state.player.isLoadingAudio &&
</template> currentTrack &&
{{ track.title|truncate(40) }} isPlaying &&
track.id === currentTrack.id &&
!(track.id == hover)
"
>
</play-indicator>
<button
v-else-if="
currentTrack &&
!isPlaying &&
track.id === currentTrack.id &&
!track.id == hover
"
class="ui really tiny basic icon button play-button paused"
>
<i class="pause icon" />
</button>
<button
v-else-if="
currentTrack &&
isPlaying &&
track.id === currentTrack.id &&
track.id == hover
"
class="ui really tiny basic icon button play-button"
>
<i class="pause icon" />
</button> </button>
</td> <button
<td colspan="4"> v-else-if="track.id == hover"
<router-link class="artist discrete link" :to="{name: 'library.artists.detail', params: {id: track.artist.id }}"> class="ui really tiny basic icon button play-button"
{{ track.artist.name|truncate(40) }} >
</router-link> <i class="play icon" />
</td> </button>
<td colspan="4"> <span class="track-position" v-else-if="showPosition">
<router-link v-if="track.album" class="album discrete link" :to="{name: 'library.albums.detail', params: {id: track.album.id }}"> {{ prettyPosition(track.position) }}
{{ track.album.title|truncate(40) }} </span>
</router-link> </div>
</td> <div
<td colspan="4" v-if="track.uploads && track.uploads.length > 0"> v-if="showArt"
<human-duration :duration="track.uploads[0].duration"></human-duration> class="image left floated column"
</td> role="button"
<td colspan="4" v-else> @click.prevent.exact="activateTrack(track, index)"
<translate translate-context="*/*/*">N/A</translate> >
</td> <img
<td colspan="2" v-if="displayActions" class="align right"> alt=""
<track-favorite-icon class="favorite-icon" :track="track"></track-favorite-icon> class="ui artist-track mini image"
<track-playlist-icon v-if="
track.album && track.album.cover && track.album.cover.urls.original
"
v-lazy="
$store.getters['instance/absoluteUrl'](
track.album.cover.urls.medium_square_crop
)
"
/>
<img
alt=""
class="ui artist-track mini image"
v-else-if="
track.cover && track.cover.urls.original
"
v-lazy="
$store.getters['instance/absoluteUrl'](
track.cover.urls.medium_square_crop
)
"
/>
<img
alt=""
class="ui artist-track mini image"
v-else-if="
track.artist && track.artist.cover && track.album.cover.urls.original
"
v-lazy="
$store.getters['instance/absoluteUrl'](
track.cover.urls.medium_square_crop
)
"
/>
<img
alt=""
class="ui artist-track mini image"
v-else
src="../../../assets/audio/default-cover.png"
/>
</div>
<div tabindex=0 class="content ellipsis left floated column">
<a
@click="activateTrack(track, index)"
>
{{ track.title }}
</a>
</div>
<div v-if="showAlbum" class="content ellipsis left floated column">
<router-link
:to="{ name: 'library.albums.detail', params: { id: track.album.id } }"
>{{ track.album.title }}</router-link
>
</div>
<div v-if="showArtist" class="content ellipsis left floated column">
<router-link
class="artist link"
:to="{
name: 'library.artists.detail',
params: { id: track.artist.id },
}"
>{{ track.artist.name }}</router-link
>
</div>
<div
v-if="$store.state.auth.authenticated" v-if="$store.state.auth.authenticated"
:track="track"></track-playlist-icon> class="meta right floated column"
>
<track-favorite-icon
class="tiny"
:border="false"
:track="track"
></track-favorite-icon>
</div>
<div v-if="showDuration" class="meta right floated column">
<human-duration
v-if="track.uploads[0] && track.uploads[0].duration"
:duration="track.uploads[0].duration"
></human-duration>
</div>
<div v-if="displayActions" class="meta right floated column">
<play-button <play-button
id="playmenu"
class="play-button basic icon" class="play-button basic icon"
:dropdown-only="true" :dropdown-only="true"
:is-playable="track.is_playable" :is-playable="track.is_playable"
:dropdown-icon-classes="['ellipsis', 'vertical', 'large really discrete']" :dropdown-icon-classes="[
'ellipsis',
'vertical',
'large really discrete',
]"
:track="track" :track="track"
></play-button> ></play-button>
</td> </div>
</tr> </div>
</template> </template>
<script> <script>
import { mapGetters } from "vuex" import PlayIndicator from "@/components/audio/track/PlayIndicator";
import TrackFavoriteIcon from '@/components/favorites/TrackFavoriteIcon' import { mapActions, mapGetters } from "vuex";
import TrackPlaylistIcon from '@/components/playlists/TrackPlaylistIcon' import TrackFavoriteIcon from "@/components/favorites/TrackFavoriteIcon";
import PlayButton from '@/components/audio/PlayButton' import PlayButton from "@/components/audio/PlayButton";
import PlayOptions from "@/components/mixins/PlayOptions";
export default { export default {
mixins: [PlayOptions],
props: { props: {
tracks: Array,
showAlbum: { type: Boolean, required: false, default: true },
showArtist: { type: Boolean, required: false, default: true },
showPosition: { type: Boolean, required: false, default: false },
showArt: { type: Boolean, required: false, default: true },
search: { type: Boolean, required: false, default: false },
filters: { type: Object, required: false, default: null },
nextUrl: { type: String, required: false, default: null },
displayActions: { type: Boolean, required: false, default: true },
showDuration: { type: Boolean, required: false, default: true },
index: { type: Number, required: true },
track: { type: Object, required: true }, track: { type: Object, required: true },
trackIndex: {type: Number, required: true},
tracks: {type: Array, required: false},
artist: {type: Object, required: false},
displayPosition: {type: Boolean, default: false},
displayActions: {type: Boolean, default: true},
playable: {type: Boolean, required: false, default: false},
}, },
data() {
return {
hover: null,
}
},
components: { components: {
PlayIndicator,
TrackFavoriteIcon, TrackFavoriteIcon,
TrackPlaylistIcon, PlayButton,
PlayButton
}, },
computed: { computed: {
...mapGetters({ ...mapGetters({
currentTrack: "queue/currentTrack", currentTrack: "queue/currentTrack",
}), }),
isPlaying() { isPlaying() {
return this.$store.state.player.playing return this.$store.state.player.playing;
},
albumArtist () {
if (this.artist) {
return this.artist
} else {
return this.track.album.artist
}
}, },
}, },
methods: { methods: {
playSong () {
this.$store.dispatch('queue/clean') prettyPosition(position, size) {
this.$store.dispatch('queue/appendMany', { var s = String(position);
tracks: this.tracks while (s.length < (size || 2)) {
}).then(() => { s = "0" + s;
this.$store.dispatch('queue/currentIndex', this.trackIndex)
})
},
}
} }
return s;
},
...mapActions({
resumePlayback: "player/resumePlayback",
pausePlayback: "player/pausePlayback",
}),
},
};
</script> </script>
<template> <template>
<div class="table-wrapper component-track-table"> <div>
<inline-search-bar v-model="query" v-if="search" @search="additionalTracks = []; loadMore()"></inline-search-bar> <!-- Show the search bar if search is true -->
<inline-search-bar
v-model="query"
v-if="search"
@search="
additionalTracks = [];
fetchData();
"
></inline-search-bar>
<div class="ui hidden divider"></div>
<!-- Add a header if needed -->
<slot name="header"></slot>
<!-- Show a message if no tracks are available -->
<slot v-if="!isLoading && allTracks.length === 0" name="empty-state"> <slot v-if="!isLoading && allTracks.length === 0" name="empty-state">
<empty-state @refresh="fetchData" :refresh="true"></empty-state> <empty-state
@refresh="fetchData('tracks/')"
:refresh="true"
></empty-state>
</slot> </slot>
<table v-else :class="['ui', 'compact', 'very', 'basic', {loading: isLoading}, 'unstackable', 'table']"> <div v-else>
<thead> <div
<tr> :class="['track-table', 'ui', 'unstackable', 'grid', 'tablet-and-up']"
<th><span class="visually-hidden"><translate translate-context="*/*/*/Noun">Play</translate></span></th> >
<th><span class="visually-hidden"><translate translate-context="*/*/*/Noun">Track Art</translate></span></th> <div v-if="isLoading" class="ui inverted active dimmer">
<th colspan="6"><translate translate-context="*/*/*/Noun">Title</translate></th> <div class="ui loader"></div>
<th colspan="4"><translate translate-context="*/*/*/Noun">Artist</translate></th> </div>
<th colspan="4"><translate translate-context="*/*/*">Album</translate></th> <div class="track-table row">
<th colspan="4"><translate translate-context="Content/*/*">Duration</translate></th> <div v-if="showPosition" class="actions left floated column">
<th colspan="2" v-if="displayActions"><span class="visually hidden"><translate translate-context="*/*/*/Noun">Actions</translate></span></th> <i class="hashtag icon"></i>
</tr> </div>
</thead> <div v-else class="actions left floated column"></div>
<tbody> <div v-if="showArt" class="image left floated column"></div>
<div class="content ellipsis left floated column">
<b>{{ labels.title }}</b>
</div>
<div v-if="showAlbum" class="content ellipsisleft floated column">
<b>{{ labels.album }}</b>
</div>
<div v-if="showArtist" class="content ellipsis left floated column">
<b>{{ labels.artist }}</b>
</div>
<div
v-if="$store.state.auth.authenticated"
class="meta right floated column"
></div>
<div v-if="showDuration" class="meta right floated column">
<i class="clock outline icon" style="padding: 0.5rem" />
</div>
<div v-if="displayActions" class="meta right floated column"></div>
</div>
<!-- For each item, build a row -->
<track-row <track-row
:playable="playable" v-for="(track, index) in allTracks"
:display-position="displayPosition" :track="track"
:key="track.id"
:index="index"
:tracks="allTracks"
:show-album="showAlbum"
:show-artist="showArtist"
:show-position="showPosition"
:show-art="showArt"
:display-actions="displayActions" :display-actions="displayActions"
:show-duration="showDuration"
:is-podcast="isPodcast"
></track-row>
</div>
<div v-if="paginateResults" class="ui center aligned basic segment desktop-and-up">
<pagination
:total="total"
:current="page"
:paginate-by="paginateBy"
v-on="$listeners">
</pagination>
</div>
</div>
<div
:class="['track-table', 'ui', 'unstackable', 'grid', 'tablet-and-below']"
>
<div v-if="isLoading" class="ui inverted active dimmer">
<div class="ui loader"></div>
</div>
<!-- For each item, build a row -->
<track-mobile-row
v-for="(track, index) in allTracks"
:track="track" :track="track"
:track-index="index" :key="track.id"
:index="index"
:tracks="allTracks" :tracks="allTracks"
:artist="artist" :show-position="showPosition"
:key="index + '-' + track.id" :show-art="showArt"
v-for="(track, index) in allTracks"></track-row> :show-duration="showDuration"
</tbody> :is-artist="isArtist"
</table> :is-album="isAlbum"
<button :class="['ui', {loading: isLoading}, 'button']" v-if="loadMoreUrl" @click="loadMore(loadMoreUrl)" :disabled="isLoading"> :is-podcast="isPodcast"
<translate translate-context="Content/*/Button.Label">Load more…</translate> ></track-mobile-row>
</button> <div v-if="paginateResults" class="ui center aligned basic segment tablet-and-below">
<pagination
v-if="paginateResults"
:total="total"
:current="page"
:compact="true"
v-on="$listeners"></pagination>
</div>
</div>
</div> </div>
</template> </template>
<script> <script>
import axios from 'axios' import _ from "@/lodash";
import axios from "axios";
import TrackRow from '@/components/audio/track/Row' import TrackRow from "@/components/audio/track/Row";
import Modal from '@/components/semantic/Modal' import TrackMobileRow from "@/components/audio/track/MobileRow";
import Pagination from "@/components/Pagination";
export default { export default {
components: {
TrackRow,
TrackMobileRow,
Pagination,
},
props: { props: {
tracks: {type: Array, required: false}, tracks: Array,
playable: {type: Boolean, required: false, default: false}, showAlbum: { type: Boolean, required: false, default: true },
showArtist: { type: Boolean, required: false, default: true },
showPosition: { type: Boolean, required: false, default: false },
showArt: { type: Boolean, required: false, default: true },
search: { type: Boolean, required: false, default: false }, search: { type: Boolean, required: false, default: false },
filters: { type: Object, required: false, default: null },
nextUrl: { type: String, required: false, default: null }, nextUrl: { type: String, required: false, default: null },
artist: {type: Object, required: false}, displayActions: { type: Boolean, required: false, default: true },
filters: {type: Object, required: false, default: () => { return {}}}, showDuration: { type: Boolean, required: false, default: true },
displayPosition: {type: Boolean, default: false}, isArtist: { type: Boolean, required: false, default: false },
displayActions: {type: Boolean, default: true}, isAlbum: { type: Boolean, required: false, default: false },
}, isPodcast: { type: Boolean, required: false, default: false },
components: { paginateResults: { type: Boolean, required: false, default: true},
Modal, total: { type: Number, required: false},
TrackRow page: {type: Number, required: false, default: 1},
}, paginateBy: {type: Number, required: false, default: 25}
created () {
if (!this.tracks) {
this.loadMore('tracks/')
}
}, },
data() { data() {
return { return {
loadMoreUrl: this.nextUrl, fetchDataUrl: this.nextUrl,
isLoading: false, isLoading: false,
additionalTracks: [], additionalTracks: [],
query: '', query: "",
} };
}, },
computed: { computed: {
allTracks() { allTracks() {
return (this.tracks || []).concat(this.additionalTracks) return (this.tracks || []).concat(this.additionalTracks);
}
}, },
methods: {
loadMore (url) {
url = url || 'tracks/'
let self = this
let params = {q: this.query, ...this.filters}
self.isLoading = true
axios.get(url, {params}).then((response) => {
self.additionalTracks = self.additionalTracks.concat(response.data.results)
self.loadMoreUrl = response.data.next
self.isLoading = false
}, (error) => {
self.isLoading = false
}) labels() {
return {
title: this.$pgettext("*/*/*/Noun", "Title"),
album: this.$pgettext("*/*/*/Noun", "Album"),
artist: this.$pgettext("*/*/*/Noun", "Artist"),
};
},
},
methods: {
async fetchData(url) {
if (!url) {
return;
} }
this.isLoading = true;
let self = this;
let params = _.clone(this.filters);
let tracksPromise = axios.get(url, { params: params })
params.page_size = this.limit;
params.page = this.page;
params.include_channels = true;
try {
await tracksPromise
self.nextPage = tracksPromise.data.next;
self.objects = tracksPromise.data.results;
self.count = tracksPromise.data.count;
self.$emit("fetched", tracksPromise.data);
self.isLoading = false;
} catch(e) {
self.isLoading = false;
self.errors = error.backendErrors;
} }
},
updatePage: function(page) {
this.$emit('page-changed', page)
}
},
created() {
if (!this.tracks) {
this.fetchData("tracks/");
} }
},
};
</script> </script>
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
<div class="field"> <div class="field">
<label for="favorites-ordering"><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate></label> <label for="favorites-ordering"><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate></label>
<select id="favorites-ordering" class="ui dropdown" v-model="ordering"> <select id="favorites-ordering" class="ui dropdown" v-model="ordering">
<option v-for="option in orderingOptions" :value="option[0]"> <option v-for="option in orderingOptions" :value="option[0]" :key="option[0]">
{{ sharedLabels.filters[option[1]] }} {{ sharedLabels.filters[option[1]] }}
</option> </option>
</select> </select>
...@@ -46,7 +46,7 @@ ...@@ -46,7 +46,7 @@
</div> </div>
</div> </div>
</div> </div>
<track-table v-if="results" :tracks="results.results"></track-table> <track-table :show-artist="true" :show-album="true" v-if="results" :tracks="results.results"></track-table>
<div class="ui center aligned basic segment"> <div class="ui center aligned basic segment">
<pagination <pagination
v-if="results && results.count > paginateBy" v-if="results && results.count > paginateBy"
...@@ -76,21 +76,21 @@ ...@@ -76,21 +76,21 @@
import axios from "axios" import axios from "axios"
import $ from "jquery" import $ from "jquery"
import logger from "@/logging" import logger from "@/logging"
import TrackTable from "@/components/audio/track/Table"
import RadioButton from "@/components/radios/Button" import RadioButton from "@/components/radios/Button"
import Pagination from "@/components/Pagination" import Pagination from "@/components/Pagination"
import OrderingMixin from "@/components/mixins/Ordering" import OrderingMixin from "@/components/mixins/Ordering"
import PaginationMixin from "@/components/mixins/Pagination" import PaginationMixin from "@/components/mixins/Pagination"
import TranslationsMixin from "@/components/mixins/Translations" import TranslationsMixin from "@/components/mixins/Translations"
import {checkRedirectToLogin} from '@/utils' import {checkRedirectToLogin} from '@/utils'
import TrackTable from '@/components/audio/track/Table'
const FAVORITES_URL = "tracks/" const FAVORITES_URL = "tracks/"
export default { export default {
mixins: [OrderingMixin, PaginationMixin, TranslationsMixin], mixins: [OrderingMixin, PaginationMixin, TranslationsMixin],
components: { components: {
TrackTable,
RadioButton, RadioButton,
Pagination Pagination,
TrackTable
}, },
data() { data() {
return { return {
......
...@@ -84,7 +84,8 @@ ...@@ -84,7 +84,8 @@
:is-album="isAlbum" :is-album="isAlbum"
:is-serie="isSerie" :is-serie="isSerie"
:is-channel="isChannel" :is-channel="isChannel"
:artist="artist"></album-dropdown> :artist="artist"
></album-dropdown>
<div v-if="(object.tags && object.tags.length > 0) || object.description || $store.state.auth.authenticated && object.is_local"> <div v-if="(object.tags && object.tags.length > 0) || object.description || $store.state.auth.authenticated && object.is_local">
<div class="ui small hidden divider"></div> <div class="ui small hidden divider"></div>
<div class="ui divider"></div> <div class="ui divider"></div>
...@@ -128,7 +129,6 @@ ...@@ -128,7 +129,6 @@
<script> <script>
import axios from "axios" import axios from "axios"
import lodash from "@/lodash" import lodash from "@/lodash"
import backend from "@/audio/backend"
import PlayButton from "@/components/audio/PlayButton" import PlayButton from "@/components/audio/PlayButton"
import TagsList from "@/components/tags/List" import TagsList from "@/components/tags/List"
import ArtistLabel from '@/components/audio/ArtistLabel' import ArtistLabel from '@/components/audio/ArtistLabel'
...@@ -172,7 +172,7 @@ export default { ...@@ -172,7 +172,7 @@ export default {
methods: { methods: {
async fetchData() { async fetchData() {
this.isLoading = true this.isLoading = true
let tracksResponse = axios.get(`tracks/`, {params: {ordering: 'disc_number,position', album: this.id, page_size: this.paginateBy, page:this.page, include_channels: 'true'}}) let tracksResponse = axios.get(`tracks/`, {params: {ordering: 'disc_number,position', album: this.id, page_size: this.paginateBy, page:this.page, include_channels: 'true', playable: 'true'}})
let albumResponse = await axios.get(`albums/${this.id}/`, {params: {refresh: 'true'}}) let albumResponse = await axios.get(`albums/${this.id}/`, {params: {refresh: 'true'}})
let artistResponse = await axios.get(`artists/${albumResponse.data.artist.id}/`) let artistResponse = await axios.get(`artists/${albumResponse.data.artist.id}/`)
this.artist = artistResponse.data this.artist = artistResponse.data
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
<translate key="1" v-if="isSerie" translate-context="Content/Channels/*">Episodes</translate> <translate key="1" v-if="isSerie" translate-context="Content/Channels/*">Episodes</translate>
<translate key="2" v-else translate-context="*/*/*">Tracks</translate> <translate key="2" v-else translate-context="*/*/*">Tracks</translate>
</h2> </h2>
<channel-entries v-if="artist.channel && isSerie" :limit="50" :filters="{channel: artist.channel.uuid, album: object.id, ordering: '-creation_date'}"> <channel-entries v-if="artist.channel && isSerie" :is-podcast="isSerie" :limit="50" :filters="{channel: artist.channel.uuid, album: object.id, ordering: '-creation_date'}">
</channel-entries> </channel-entries>
<template v-else-if="discs && discs.length > 1"> <template v-else-if="discs && discs.length > 1">
<div v-for="tracks in discs" :key="tracks.disc_number"> <div v-for="tracks in discs" :key="tracks.disc_number">
...@@ -15,21 +15,36 @@ ...@@ -15,21 +15,36 @@
:translate-params="{number: tracks[0].disc_number}" :translate-params="{number: tracks[0].disc_number}"
translate-context="Content/Album/" translate-context="Content/Album/"
>Volume %{ number }</translate> >Volume %{ number }</translate>
<album-entries :tracks="tracks"></album-entries> <track-table
:is-album="true"
:tracks="object.tracks"
:show-position="true"
:show-art="false"
:show-album="false"
:show-artist="false"
:paginate-results="true"
:total="totalTracks"
:paginate-by="paginateBy"
:page="page"
@page-changed="updatePage">
</track-table>
</div> </div>
</template> </template>
<template v-else> <template v-else>
<album-entries :tracks="object.tracks"></album-entries> <track-table
</template> :is-album="true"
<div class="ui center aligned basic segment"> :tracks="object.tracks"
<pagination :show-position="true"
v-if="!isSerie && object.tracks && totalTracks > paginateBy" :show-art="false"
@page-changed="updatePage" :show-album="false"
:current="page" :show-artist="false"
:paginate-by="paginateBy" :paginate-results="true"
:total="totalTracks" :total="totalTracks"
></pagination> :paginate-by="paginateBy"
</div> :page="page"
@page-changed="updatePage">
</track-table>
</template>
<template v-if="!artist.channel && !isSerie"> <template v-if="!artist.channel && !isSerie">
<h2> <h2>
<translate translate-context="Content/*/Title/Noun">User libraries</translate> <translate translate-context="Content/*/Title/Noun">User libraries</translate>
...@@ -44,25 +59,17 @@ ...@@ -44,25 +59,17 @@
<script> <script>
import time from "@/utils/time" import time from "@/utils/time"
import axios from "axios"
import url from "@/utils/url"
import logger from "@/logging"
import LibraryWidget from "@/components/federation/LibraryWidget" import LibraryWidget from "@/components/federation/LibraryWidget"
import TrackTable from "@/components/audio/track/Table"
import ChannelEntries from '@/components/audio/ChannelEntries' import ChannelEntries from '@/components/audio/ChannelEntries'
import AlbumEntries from '@/components/audio/AlbumEntries' import TrackTable from '@/components/audio/track/Table'
import Pagination from "@/components/Pagination"
import PaginationMixin from "@/components/mixins/Pagination"
import PlayButton from "@/components/audio/PlayButton" import PlayButton from "@/components/audio/PlayButton"
export default { export default {
props: ["object", "libraries", "discs", "isSerie", "artist", "page", "paginateBy", "totalTracks"], props: ["object", "libraries", "discs", "isSerie", "artist", "page", "paginateBy", "totalTracks"],
components: { components: {
LibraryWidget, LibraryWidget,
AlbumEntries,
ChannelEntries,
TrackTable, TrackTable,
Pagination, ChannelEntries,
PlayButton PlayButton
}, },
data() { data() {
......
...@@ -195,7 +195,7 @@ export default { ...@@ -195,7 +195,7 @@ export default {
if (!self.object) { if (!self.object) {
return return
} }
let trackPromise = axios.get("tracks/", { params: { artist: this.id, hidden: '' } }).then(response => { let trackPromise = axios.get("tracks/", { params: { artist: this.id, hidden: '', ordering: "-creation_date" } }).then(response => {
self.tracks = response.data.results self.tracks = response.data.results
self.nextTracksUrl = response.data.next self.nextTracksUrl = response.data.next
self.totalTracks = response.data.count self.totalTracks = response.data.count
......