2017-06-23 21:00:42 +00:00
< template >
2018-11-19 22:33:22 +00:00
< main >
2021-12-06 10:35:20 +00:00
< div
v - if = "isLoading"
v - title = "labels.title"
class = "ui vertical segment"
>
< div : class = "['ui', 'centered', 'active', 'inline', 'loader']" / >
2017-06-23 21:00:42 +00:00
< / div >
< template v-if ="track" >
2018-12-04 14:13:37 +00:00
< section
v - title = "track.title"
2021-12-06 10:35:20 +00:00
: class = "['ui', 'head', 'vertical', 'center', 'aligned', 'stripe', 'segment']"
2018-12-04 14:13:37 +00:00
>
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 } }
2021-12-06 10:35:20 +00:00
< div
class = "sub header"
v - html = "subtitle"
/ >
2020-03-26 15:18:37 +00:00
< / 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" >
2021-12-06 10:35:20 +00:00
< play -button
class = "vibrant"
: track = "track"
>
< translate translate -context = " * / Queue / Button.Label / Short , Verb " >
Play
< / translate >
2019-04-17 12:17:59 +00:00
< / p l a y - b u t t o n >
2020-03-26 15:30:23 +00:00
& nbsp ;
2021-12-06 10:35:20 +00:00
< track -favorite -icon
v - if = "$store.state.auth.authenticated"
: border = "true"
: track = "track"
/ >
< track -playlist -icon
v - if = "$store.state.auth.authenticated"
class = "circular"
: border = "true"
: track = "track"
/ >
< a
v - if = "upload"
role = "button"
: aria - label = "labels.download"
: href = "downloadUrl"
target = "_blank"
class = "ui basic circular icon button"
: title = "labels.download"
>
< i class = "download icon" / >
2019-04-17 12:17:59 +00:00
< / a >
2021-12-06 10:35:20 +00:00
< modal
v - if = "isEmbedable"
: show . sync = "showEmbedModal"
>
2020-07-03 12:20:47 +00:00
< h4 class = "header" >
2021-12-06 10:35:20 +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" >
2021-12-06 10:35:20 +00:00
< embed -wizard
: id = "track.id"
type = "track"
/ >
2020-03-26 15:18:37 +00:00
< / 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" >
2021-12-06 10:35:20 +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 >
2021-12-06 10:35:20 +00:00
< button
v - dropdown = "{direction: 'downward'}"
class = "ui floating dropdown circular icon basic button"
: title = "labels.more"
>
< i class = "ellipsis vertical icon" / >
< div
class = "menu"
style = "right: 0; left: auto"
>
2020-08-28 18:41:42 +00:00
< a
v - if = "domain != $store.getters['instance/domain']"
2021-12-06 10:35:20 +00:00
: href = "track.fid"
2020-08-28 18:41:42 +00:00
target = "_blank"
2021-12-06 10:35:20 +00:00
class = "basic item"
>
< i class = "external icon" / >
< translate
: translate - params = "{domain: domain}"
translate - context = "Content/*/Button.Label/Verb"
> View on % { domain } < / translate >
2020-08-28 18:41:42 +00:00
< / a >
2019-04-17 12:17:59 +00:00
< div
2021-09-21 11:12:38 +00:00
v - if = "isEmbedable"
2021-12-06 10:35:20 +00:00
role = "button"
class = "basic item"
2019-04-17 12:17:59 +00:00
@ click = "showEmbedModal = !showEmbedModal"
2021-12-06 10:35:20 +00:00
>
< i class = "code icon" / >
< translate translate -context = " Content / * / Button.Label / Verb " >
Embed
< / translate >
2019-04-17 12:17:59 +00:00
< / div >
2021-12-06 10:35:20 +00:00
< a
: href = "wikipediaUrl"
target = "_blank"
rel = "noreferrer noopener"
class = "basic item"
>
< i class = "wikipedia w icon" / >
2019-04-17 12:17:59 +00:00
< translate translate -context = " Content / * / Button.Label / Verb " > Search on Wikipedia < / translate >
< / a >
2021-12-06 10:35:20 +00:00
< a
v - if = "discogsUrl"
: href = "discogsUrl"
target = "_blank"
rel = "noreferrer noopener"
class = "basic item"
>
< i class = "external icon" / >
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 }}"
2021-12-06 10:35:20 +00:00
class = "basic item"
>
< i class = "edit icon" / >
< translate translate -context = " Content / * / Button.Label / Verb " >
Edit
< / translate >
2019-04-17 12:17:59 +00:00
< / r o u t e r - l i n k >
2020-02-23 14:31:03 +00:00
< dangerous -button
v - if = "artist && $store.state.auth.authenticated && artist.channel && artist.attributed_to.full_username === $store.state.auth.fullUsername"
2021-12-06 10:35:20 +00:00
: class = "['ui', {loading: isLoading}, 'item']"
@ confirm = "remove()"
>
< i class = "ui trash icon" / >
< translate translate -context = " * / * / * / Verb " >
Delete …
< / translate >
< p slot = "modal-header" >
< translate translate -context = " Popup / Channel / Title " >
Delete this track ?
< / translate >
< / p >
2020-02-23 14:31:03 +00:00
< div slot = "modal-content" >
2021-12-06 10:35:20 +00:00
< 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 >
2020-02-23 14:31:03 +00:00
< / div >
2021-12-06 10:35:20 +00:00
< p slot = "modal-confirm" >
< translate translate -context = " * / * / * / Verb " >
Delete
< / translate >
< / p >
2020-02-23 14:31:03 +00:00
< / d a n g e r o u s - b u t t o n >
2021-12-06 10:35:20 +00:00
< div class = "divider" / >
2019-09-09 09:10:25 +00:00
< div
v - for = "obj in getReportableObjs({track})"
: key = "obj.target.type + obj.target.id"
2021-12-06 10:35:20 +00:00
role = "button"
class = "basic item"
@ click . stop . prevent = "$store.dispatch('moderation/report', obj.target)"
>
2019-09-09 09:10:25 +00:00
< i class = "share icon" / > { { obj . label } }
< / div >
2021-12-06 10:35:20 +00:00
< div class = "divider" / >
< router -link
v - if = "$store.state.auth.availablePermissions['library']"
class = "basic item"
: to = "{name: 'manage.library.tracks.detail', params: {id: track.id}}"
>
< i class = "wrench icon" / >
< translate translate -context = " Content / Moderation / Link " >
Open in moderation interface
< / translate >
2019-04-17 12:17:59 +00:00
< / 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}`)"
2021-12-06 10:35:20 +00:00
target = "_blank"
rel = "noopener noreferrer"
>
< i class = "wrench icon" / >
2019-04-17 12:17:59 +00:00
< 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 >
2021-12-06 10:35:20 +00:00
< router -view
v - if = "track"
: key = "$route.fullPath"
: track = "track"
: object = "track"
object - type = "track"
@ libraries - loaded = "libraries = $event"
/ >
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 >
2021-12-06 10:35:20 +00:00
import time from '@/utils/time'
import axios from 'axios'
import url from '@/utils/url'
import { getDomain } from '@/utils'
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'
2021-12-06 10:35:20 +00:00
import EmbedWizard from '@/components/audio/EmbedWizard'
2019-09-09 09:10:25 +00:00
import ReportMixin from '@/components/mixins/Report'
2021-12-06 10:35:20 +00:00
import { momentFormat } from '@/filters'
2017-06-23 21:00:42 +00:00
2021-12-06 10:35:20 +00:00
const FETCH _URL = 'tracks/'
2020-03-26 15:18:37 +00:00
2021-12-06 10:35:20 +00:00
function escapeHtml ( unsafe ) {
2020-03-26 15:18:37 +00:00
return unsafe
2021-12-06 10:35:20 +00:00
. replace ( /&/g , '&' )
. replace ( /</g , '<' )
. replace ( />/g , '>' )
. replace ( /"/g , '"' )
. replace ( /'/g , ''' )
2020-03-26 15:18:37 +00:00
}
2017-06-23 21:00:42 +00:00
export default {
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 ,
2021-12-06 10:35:20 +00:00
EmbedWizard
2017-06-23 21:00:42 +00:00
} ,
2021-12-06 10:35:20 +00:00
mixins : [ ReportMixin ] ,
props : { id : { type : Number , required : true } } ,
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
}
} ,
computed : {
2020-08-28 18:41:42 +00:00
domain ( ) {
if ( this . track ) {
return getDomain ( this . track . fid )
}
2021-12-06 10:35:20 +00:00
return null
2020-08-28 18:41:42 +00:00
} ,
2018-12-19 13:04:49 +00:00
publicLibraries ( ) {
return this . libraries . filter ( l => {
return l . privacy _level === 'everyone'
} )
} ,
2021-09-21 11:12:38 +00:00
isEmbedable ( ) {
2021-12-06 10:35:20 +00:00
const self = this
return ( self . artist && self . artist . channel && self . artist . channel . actor ) || this . publicLibraries . length > 0
} ,
upload ( ) {
2018-10-26 13:21:35 +00:00
if ( this . track . uploads ) {
return this . track . uploads [ 0 ]
}
2021-12-06 10:35:20 +00:00
return null
2018-10-26 13:21:35 +00:00
} ,
2021-12-06 10:35:20 +00:00
labels ( ) {
2019-02-28 08:31:04 +00:00
return {
2021-12-06 10:35:20 +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
}
} ,
2021-12-06 10:35:20 +00:00
wikipediaUrl ( ) {
2018-11-19 22:33:22 +00:00
return (
2021-12-06 10:35:20 +00:00
'https://en.wikipedia.org/w/index.php?search=' +
encodeURI ( this . track . title + ' ' + this . track . artist . name )
2018-11-19 22:33:22 +00:00
)
2017-06-23 21:00:42 +00:00
} ,
2021-12-06 10:35:20 +00:00
discogsUrl ( ) {
2020-02-05 14:06:07 +00:00
if ( this . track . album ) {
return (
2021-12-06 10:35:20 +00:00
'https://discogs.com/search/?type=release&title=' +
encodeURI ( this . track . album . title ) + '&artist=' +
encodeURI ( this . track . artist . name ) + '&track=' +
2020-02-05 14:06:07 +00:00
encodeURI ( this . track . title )
)
}
2021-12-06 10:35:20 +00:00
return null
2019-06-16 15:23:12 +00:00
} ,
2021-12-06 10:35:20 +00:00
downloadUrl ( ) {
let u = this . $store . getters [ 'instance/absoluteUrl' ] (
2018-11-19 22:33:22 +00:00
this . upload . listen _url
)
2019-03-15 14:52:30 +00:00
if ( this . $store . state . auth . authenticated ) {
2021-12-06 10:35:20 +00:00
let param = 'jwt'
2020-05-11 08:06:35 +00:00
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
2021-12-06 10:35:20 +00:00
param = 'token'
2020-05-11 08:06:35 +00:00
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 ( ) {
2021-12-06 10:35:20 +00:00
const route = this . $router . resolve ( {
2020-03-26 15:18:37 +00:00
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 ( ) {
2021-12-06 10:35:20 +00:00
const 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 ( ) {
2021-12-06 10:35:20 +00:00
const 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
} ,
2021-12-06 10:35:20 +00:00
headerStyle ( ) {
2020-08-03 13:47:14 +00:00
if ( ! this . cover || ! this . cover . urls . original ) {
2021-12-06 10:35:20 +00:00
return ''
2017-06-23 21:00:42 +00:00
}
2018-11-19 22:33:22 +00:00
return (
2021-12-06 10:35:20 +00:00
'background-image: url(' +
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 ) ,
2021-12-06 10:35:20 +00:00
prettyDate : escapeHtml ( momentFormat ( this . track . creation _date , 'LL' ) )
2020-03-26 15:18:37 +00:00
} )
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 ) ,
2021-12-06 10:35:20 +00:00
prettyDate : escapeHtml ( momentFormat ( this . track . creation _date , 'LL' ) )
2020-03-26 15:18:37 +00:00
} )
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 : {
2021-12-06 10:35:20 +00:00
id ( ) {
2017-06-23 21:00:42 +00:00
this . fetchData ( )
2021-12-06 10:35:20 +00:00
}
} ,
created ( ) {
this . fetchData ( )
} ,
methods : {
fetchData ( ) {
const self = this
this . isLoading = true
const url = FETCH _URL + this . id + '/'
logger . default . debug ( 'Fetching track "' + this . id + '"' )
axios . get ( url , { params : { refresh : 'true' } } ) . then ( response => {
self . track = response . data
axios . get ( ` artists/ ${ response . data . artist . id } / ` ) . then ( response => {
self . artist = response . data
} )
self . isLoading = false
} )
2018-12-04 14:13:37 +00:00
} ,
2021-12-06 10:35:20 +00:00
remove ( ) {
const 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
}
}
< / script >