2017-06-23 21:00:42 +00:00
< template >
2018-11-19 22:33:22 +00:00
< main >
2020-02-23 14:31:03 +00:00
< div v-if ="isLoading" class="ui vertical segment" v-title="labels.title" >
2017-06-23 21:00:42 +00:00
< div : class = "['ui', 'centered', 'active', 'inline', 'loader']" > < / div >
< / div >
< template v-if ="track" >
2018-12-04 14:13:37 +00:00
< section
2020-03-26 15:18:37 +00:00
: class = "['ui', 'head', 'vertical', 'center', 'aligned', 'stripe', 'segment']"
2018-12-04 14:13:37 +00:00
v - title = "track.title"
>
2020-03-26 15:18:37 +00:00
< div class = "ui basic padded segment" >
< div class = "ui stackable grid row container" >
< div class = "eight wide left aligned column" >
< h1 class = "ui header" >
{ { track . title } }
< div class = "sub header" v-html ="subtitle" > < / div >
< / h1 >
2017-06-23 21:00:42 +00:00
< / div >
2020-03-26 15:18:37 +00:00
< div class = "eight wide right aligned column button-group" >
2020-05-15 12:12:36 +00:00
< play -button class = "vibrant" :track ="track" >
2019-04-17 12:17:59 +00:00
< translate translate -context = " * / Queue / Button.Label / Short , Verb " > Play < / translate >
< / p l a y - b u t t o n >
2020-03-26 15:30:23 +00:00
& nbsp ;
2020-03-26 15:18:37 +00:00
< track -favorite -icon v-if ="$store.state.auth.authenticated" :border="true" :track ="track" > < / track -favorite -icon >
< track -playlist -icon class = "circular" v-if ="$store.state.auth.authenticated" :border="true" :track ="track" > < / track -playlist -icon >
2020-08-01 09:11:51 +00:00
< a role = "button" :aria-label ="labels.download" v-if ="upload" :href="downloadUrl" target="_blank" class="ui basic circular icon button" :title="labels.download" >
2019-04-17 12:17:59 +00:00
< i class = "download icon" > < / i >
< / a >
2020-03-26 15:18:37 +00:00
< modal v-if ="publicLibraries.length > 0" :show.sync="showEmbedModal" >
2020-07-03 12:20:47 +00:00
< h4 class = "header" >
2020-03-26 15:18:37 +00:00
< translate translate -context = " Popup / Track / Title " > Embed this track on your website < / translate >
2020-07-03 12:20:47 +00:00
< / h4 >
2020-07-21 08:19:14 +00:00
< div class = "scrolling content" >
2020-03-26 15:18:37 +00:00
< div class = "description" >
< embed -wizard type = "track" :id ="track.id" / >
< / div >
2018-12-19 13:04:49 +00:00
< / div >
2020-03-26 15:18:37 +00:00
< div class = "actions" >
2020-08-04 11:22:31 +00:00
< button class = "ui basic deny button" >
2020-03-26 15:18:37 +00:00
< translate translate -context = " * / * / Button.Label / Verb " > Cancel < / translate >
2020-08-04 11:22:31 +00:00
< / button >
2020-03-26 15:18:37 +00:00
< / div >
< / modal >
2020-08-04 11:22:31 +00:00
< button class = "ui floating dropdown circular icon basic button" :title ="labels.more" v-dropdown ="{direction: 'downward'}" >
2020-03-26 15:18:37 +00:00
< i class = "ellipsis vertical icon" > < / i >
< div class = "menu" style = "right: 0; left: auto" >
2019-04-17 12:17:59 +00:00
< div
role = "button"
v - if = "publicLibraries.length > 0"
@ click = "showEmbedModal = !showEmbedModal"
class = "basic item" >
< i class = "code icon" > < / i >
< translate translate -context = " Content / * / Button.Label / Verb " > Embed < / translate >
< / div >
< a :href ="wikipediaUrl" target = "_blank" rel = "noreferrer noopener" class = "basic item" >
< i class = "wikipedia w icon" > < / i >
< translate translate -context = " Content / * / Button.Label / Verb " > Search on Wikipedia < / translate >
< / a >
2020-05-07 16:16:30 +00:00
< a v-if ="discogsUrl" :href="discogsUrl" target="_blank" rel="noreferrer noopener" class="basic item" >
2019-04-17 12:17:59 +00:00
< i class = "external icon" > < / i >
2020-03-26 15:18:37 +00:00
< translate translate -context = " Content / * / Button.Label / Verb " > Search on Discogs < / translate >
2019-04-17 12:17:59 +00:00
< / a >
< router -link
v - if = "track.is_local"
: to = "{name: 'library.tracks.edit', params: {id: track.id }}"
class = "basic item" >
< i class = "edit icon" > < / i >
< translate translate -context = " Content / * / Button.Label / Verb " > Edit < / translate >
< / r o u t e r - l i n k >
2020-02-23 14:31:03 +00:00
< dangerous -button
: class = "['ui', {loading: isLoading}, 'item']"
v - if = "artist && $store.state.auth.authenticated && artist.channel && artist.attributed_to.full_username === $store.state.auth.fullUsername"
@ confirm = "remove()" >
< i class = "ui trash icon" > < / i >
< translate translate -context = " * / * / * / Verb " > Delete … < / translate >
< p slot = "modal-header" > < translate translate -context = " Popup / Channel / Title " > Delete this track ? < / translate > < / p >
< div slot = "modal-content" >
< p > < translate translate -context = " Content / Moderation / Paragraph " > The track will be deleted , as well as any related files and data . This action is irreversible . < / translate > < / p >
< / div >
< p slot = "modal-confirm" > < translate translate -context = " * / * / * / Verb " > Delete < / translate > < / p >
< / d a n g e r o u s - b u t t o n >
2019-04-17 12:17:59 +00:00
< div class = "divider" > < / div >
2019-09-09 09:10:25 +00:00
< div
role = "button"
class = "basic item"
v - for = "obj in getReportableObjs({track})"
: key = "obj.target.type + obj.target.id"
@ click . stop . prevent = "$store.dispatch('moderation/report', obj.target)" >
< i class = "share icon" / > { { obj . label } }
< / div >
< div class = "divider" > < / div >
2019-04-17 12:17:59 +00:00
< router -link class = "basic item" v-if ="$store.state.auth.availablePermissions['library']" :to="{name: 'manage.library.tracks.detail', params: {id: track.id}}" >
< i class = "wrench icon" > < / i >
< translate translate -context = " Content / Moderation / Link " > Open in moderation interface < / translate >
< / r o u t e r - l i n k >
< a
2019-04-29 13:26:54 +00:00
v - if = "$store.state.auth.profile && $store.state.auth.profile.is_superuser"
2019-04-17 12:17:59 +00:00
class = "basic item"
: href = "$store.getters['instance/absoluteUrl'](`/api/admin/music/track/${track.id}`)"
target = "_blank" rel = "noopener noreferrer" >
< i class = "wrench icon" > < / i >
< translate translate -context = " Content / Moderation / Link / Verb " > View in Django ' s admin < / translate > & nbsp ;
< / a >
< / div >
2020-08-04 11:22:31 +00:00
< / button >
2019-04-17 12:17:59 +00:00
< / div >
< / div >
2017-06-23 21:00:42 +00:00
< / div >
2018-11-19 22:33:22 +00:00
< / section >
2019-02-28 08:31:04 +00:00
< router -view v-if ="track" @libraries-loaded="libraries = $event" :track="track" :object="track" object-type="track" :key="$route.fullPath" > < / router -view >
2017-06-23 21:00:42 +00:00
< / template >
2018-11-19 22:33:22 +00:00
< / main >
2017-06-23 21:00:42 +00:00
< / template >
< script >
2018-11-19 22:33:22 +00:00
import time from "@/utils/time"
import axios from "axios"
import url from "@/utils/url"
import logger from "@/logging"
import PlayButton from "@/components/audio/PlayButton"
import TrackFavoriteIcon from "@/components/favorites/TrackFavoriteIcon"
import TrackPlaylistIcon from "@/components/playlists/TrackPlaylistIcon"
2018-12-04 14:13:37 +00:00
import Modal from '@/components/semantic/Modal'
2018-12-19 13:04:49 +00:00
import EmbedWizard from "@/components/audio/EmbedWizard"
2019-09-09 09:10:25 +00:00
import ReportMixin from '@/components/mixins/Report'
2020-03-26 15:18:37 +00:00
import { momentFormat } from '@/filters'
2018-05-15 20:24:20 +00:00
2018-11-19 22:33:22 +00:00
const FETCH _URL = "tracks/"
2017-06-23 21:00:42 +00:00
2020-03-26 15:18:37 +00:00
function escapeHtml ( unsafe ) {
return unsafe
. replace ( /&/g , "&" )
. replace ( /</g , "<" )
. replace ( />/g , ">" )
. replace ( /"/g , """ )
. replace ( /'/g , "'" ) ;
}
2017-06-23 21:00:42 +00:00
export default {
2018-11-19 22:33:22 +00:00
props : [ "id" ] ,
2019-09-09 09:10:25 +00:00
mixins : [ ReportMixin ] ,
2017-06-23 21:00:42 +00:00
components : {
PlayButton ,
2018-03-20 22:41:31 +00:00
TrackPlaylistIcon ,
2018-10-02 17:30:13 +00:00
TrackFavoriteIcon ,
2018-12-19 13:04:49 +00:00
Modal ,
2019-07-16 12:04:19 +00:00
EmbedWizard ,
2017-06-23 21:00:42 +00:00
} ,
2018-11-19 22:33:22 +00:00
data ( ) {
2017-06-23 21:00:42 +00:00
return {
2018-05-15 20:24:20 +00:00
time ,
2020-02-23 14:31:03 +00:00
isLoading : true ,
2017-06-23 21:00:42 +00:00
track : null ,
2020-02-23 14:31:03 +00:00
artist : null ,
2019-02-28 08:31:04 +00:00
showEmbedModal : false ,
libraries : [ ]
2017-06-23 21:00:42 +00:00
}
} ,
2018-11-19 22:33:22 +00:00
created ( ) {
2017-06-23 21:00:42 +00:00
this . fetchData ( )
} ,
methods : {
2018-11-19 22:33:22 +00:00
fetchData ( ) {
2017-06-23 21:00:42 +00:00
var self = this
2020-02-23 14:31:03 +00:00
this . isLoading = true
2018-11-19 22:33:22 +00:00
let url = FETCH _URL + this . id + "/"
2017-06-23 21:00:42 +00:00
logger . default . debug ( 'Fetching track "' + this . id + '"' )
2019-07-22 10:12:57 +00:00
axios . get ( url , { params : { refresh : 'true' } } ) . then ( response => {
2017-06-23 21:00:42 +00:00
self . track = response . data
2020-02-23 14:31:03 +00:00
axios . get ( ` artists/ ${ response . data . artist . id } / ` ) . then ( response => {
self . artist = response . data
} )
self . isLoading = false
2017-06-23 21:00:42 +00:00
} )
} ,
2020-02-23 14:31:03 +00:00
remove ( ) {
let self = this
self . isLoading = true
axios . delete ( ` tracks/ ${ this . track . id } ` ) . then ( ( response ) => {
self . isLoading = false
self . $emit ( 'deleted' )
self . $router . push ( { name : 'library.artists.detail' , params : { id : this . artist . id } } )
} , error => {
self . isLoading = false
self . errors = error . backendErrors
} )
}
2017-06-23 21:00:42 +00:00
} ,
computed : {
2018-12-19 13:04:49 +00:00
publicLibraries ( ) {
return this . libraries . filter ( l => {
return l . privacy _level === 'everyone'
} )
} ,
2018-11-19 22:33:22 +00:00
upload ( ) {
2018-10-26 13:21:35 +00:00
if ( this . track . uploads ) {
return this . track . uploads [ 0 ]
}
} ,
2019-02-28 08:31:04 +00:00
labels ( ) {
return {
2020-03-26 15:18:37 +00:00
title : this . $pgettext ( '*/*/*/Noun' , "Track" ) ,
download : this . $pgettext ( 'Content/Track/Link/Verb' , "Download" ) ,
more : this . $pgettext ( '*/*/Button.Label/Noun' , "More…" ) ,
2019-02-28 08:31:04 +00:00
}
} ,
2018-11-19 22:33:22 +00:00
wikipediaUrl ( ) {
return (
"https://en.wikipedia.org/w/index.php?search=" +
encodeURI ( this . track . title + " " + this . track . artist . name )
)
2017-06-23 21:00:42 +00:00
} ,
2019-06-16 15:23:12 +00:00
discogsUrl ( ) {
2020-02-05 14:06:07 +00:00
if ( this . track . album ) {
return (
"https://discogs.com/search/?type=release&title=" +
encodeURI ( this . track . album . title ) + "&artist=" +
encodeURI ( this . track . artist . name ) + "&track=" +
encodeURI ( this . track . title )
)
}
2019-06-16 15:23:12 +00:00
} ,
2018-11-19 22:33:22 +00:00
downloadUrl ( ) {
let u = this . $store . getters [ "instance/absoluteUrl" ] (
this . upload . listen _url
)
2019-03-15 14:52:30 +00:00
if ( this . $store . state . auth . authenticated ) {
2020-05-11 08:06:35 +00:00
let param = "jwt"
let value = this . $store . state . auth . token
if ( this . $store . state . auth . scopedTokens && this . $store . state . auth . scopedTokens . listen ) {
// used scoped tokens instead of JWT to reduce the attack surface if the token
// is leaked
param = "token"
value = this . $store . state . auth . scopedTokens . listen
}
2019-03-15 14:52:30 +00:00
u = url . updateQueryString (
u ,
2020-05-11 08:06:35 +00:00
param ,
encodeURI ( value )
2019-03-15 14:52:30 +00:00
)
}
2018-09-22 12:29:30 +00:00
return u
2018-05-15 20:24:20 +00:00
} ,
2020-03-26 15:18:37 +00:00
attributedToUrl ( ) {
let route = this . $router . resolve ( {
name : 'profile.full.overview' ,
params : {
username : this . track . attributed _to . preferred _username ,
domain : this . track . attributed _to . domain
}
} )
return route . href
2017-06-23 21:00:42 +00:00
} ,
2019-02-25 13:28:25 +00:00
albumUrl ( ) {
let route = this . $router . resolve ( { name : 'library.albums.detail' , params : { id : this . track . album . id } } )
2019-06-28 09:57:32 +00:00
return route . href
2019-02-25 13:28:25 +00:00
} ,
artistUrl ( ) {
let route = this . $router . resolve ( { name : 'library.artists.detail' , params : { id : this . track . artist . id } } )
2019-06-28 09:57:32 +00:00
return route . href
2019-02-25 13:28:25 +00:00
} ,
2018-11-19 22:33:22 +00:00
headerStyle ( ) {
2020-08-03 13:47:14 +00:00
if ( ! this . cover || ! this . cover . urls . original ) {
2018-11-19 22:33:22 +00:00
return ""
2017-06-23 21:00:42 +00:00
}
2018-11-19 22:33:22 +00:00
return (
"background-image: url(" +
2020-08-03 13:47:14 +00:00
this . $store . getters [ "instance/absoluteUrl" ] ( this . cover . urls . original ) +
2018-11-19 22:33:22 +00:00
")"
)
2018-12-04 14:13:37 +00:00
} ,
2019-06-11 12:21:59 +00:00
subtitle ( ) {
2020-02-05 14:06:07 +00:00
let msg
2020-03-26 15:18:37 +00:00
if ( this . track . attributed _to ) {
msg = this . $pgettext ( 'Content/Track/Paragraph' , 'Uploaded by <a class="internal" href="%{ uploaderUrl }">%{ uploader }</a> on <time title="%{ date }" datetime="%{ date }">%{ prettyDate }</time>' )
return this . $gettextInterpolate ( msg , {
uploaderUrl : this . attributedToUrl ,
uploader : escapeHtml ( ` @ ${ this . track . attributed _to . full _username } ` ) ,
date : escapeHtml ( this . track . creation _date ) ,
prettyDate : escapeHtml ( momentFormat ( this . track . creation _date , 'LL' ) ) ,
} )
2020-02-05 14:06:07 +00:00
} else {
2020-04-14 15:21:39 +00:00
msg = this . $pgettext ( 'Content/Track/Paragraph' , 'Uploaded on <time title="%{ date }" datetime="%{ date }">%{ prettyDate }</time>' )
2020-03-26 15:18:37 +00:00
return this . $gettextInterpolate ( msg , {
date : escapeHtml ( this . track . creation _date ) ,
prettyDate : escapeHtml ( momentFormat ( this . track . creation _date , 'LL' ) ) ,
} )
2020-02-05 14:06:07 +00:00
}
2019-06-11 12:21:59 +00:00
}
2017-06-23 21:00:42 +00:00
} ,
watch : {
2018-11-19 22:33:22 +00:00
id ( ) {
2017-06-23 21:00:42 +00:00
this . fetchData ( )
2018-12-04 14:13:37 +00:00
} ,
2017-06-23 21:00:42 +00:00
}
}
< / script >