[EPIC] Audio metadata update - UI / API

This commit is contained in:
Eliot Berriot 2019-02-28 09:31:04 +01:00
parent 1a1c62ab37
commit e0c5ffcb16
59 changed files with 2793 additions and 436 deletions

View file

@ -0,0 +1,209 @@
<template>
<div class="ui fluid card">
<div class="content">
<div class="header">
<router-link :to="detailUrl">
<translate :translate-context="'Content/Library/Card/Short'" :translate-params="{id: obj.uuid.substring(0, 8)}">Modification %{ id }</translate>
</router-link>
</div>
<div class="meta">
<router-link
v-if="obj.target && obj.target.type === 'track'"
:to="{name: 'library.tracks.detail', params: {id: obj.target.id }}">
<i class="music icon"></i>
<translate :translate-context="'Content/Library/Card/Short'" :translate-params="{id: obj.target.id, name: obj.target.repr}">Track #%{ id } - %{ name }</translate>
</router-link>
<br>
<human-date :date="obj.creation_date" :icon="true"></human-date>
<span class="right floated">
<span v-if="obj.is_approved && obj.is_applied">
<i class="green check icon"></i>
<translate :translate-context="'Content/Library/Card/Short'">Approved and applied</translate>
</span>
<span v-else-if="obj.is_approved">
<i class="green check icon"></i>
<translate :translate-context="'Content/Library/Card/Short'">Approved</translate>
</span>
<span v-else-if="obj.is_approved === null">
<i class="yellow hourglass icon"></i>
<translate :translate-context="'Content/Library/Card/Short'">Pending review</translate>
</span>
<span v-else-if="obj.is_approved === false">
<i class="red x icon"></i>
<translate :translate-context="'Content/Library/Card/Short'">Rejected</translate>
</span>
</span>
</div>
</div>
<div v-if="obj.summary" class="content">
{{ obj.summary }}
</div>
<div class="content">
<table v-if="obj.type === 'update'" class="ui celled very basic fixed stacking table">
<thead>
<tr>
<th><translate :translate-context="'Content/Library/Card.Table.Header/Short'">Field</translate></th>
<th><translate :translate-context="'Content/Library/Card.Table.Header/Short'">Old value</translate></th>
<th><translate :translate-context="'Content/Library/Card.Table.Header/Short'">New value</translate></th>
</tr>
</thead>
<tbody>
<tr v-for="field in getUpdatedFields(obj.payload, previousState)" :key="field.id">
<td>{{ field.id }}</td>
<td v-if="field.diff">
<span v-if="!part.added" v-for="part in field.diff" :class="['diff', {removed: part.removed}]">
{{ part.value }}
</span>
</td>
<td v-else>
<translate :translate-context="'*/*/*'">N/A</translate>
</td>
<td v-if="field.diff">
<span v-if="!part.removed" v-for="part in field.diff" :class="['diff', {added: part.added}]">
{{ part.value }}
</span>
</td>
<td v-else>{{ field.new }}</td>
</tr>
</tbody>
</table>
</div>
<div v-if="obj.created_by" class="extra content">
<actor-link :actor="obj.created_by" />
</div>
<div v-if="canDelete || canApprove" class="ui bottom attached buttons">
<button
v-if="canApprove && obj.is_approved !== true"
@click="approve(true)"
:class="['ui', {loading: isLoading}, 'green', 'basic', 'button']">
<translate :translate-context="'Content/Library/Button.Label'">Approve</translate>
</button>
<button
v-if="canApprove && obj.is_approved === null"
@click="approve(false)"
:class="['ui', {loading: isLoading}, 'yellow', 'basic', 'button']">
<translate :translate-context="'Content/Library/Button.Label'">Reject</translate>
</button>
<dangerous-button
v-if="canDelete"
:class="['ui', {loading: isLoading}, 'basic button']"
:action="remove">
<translate :translate-context="'*/*/*/Verb'">Delete</translate>
<p slot="modal-header"><translate :translate-context="'Popup/Library/Title'">Delete this suggestion?</translate></p>
<div slot="modal-content">
<p><translate :translate-context="'Popup/Library/Paragraph'">The suggestion will be completely removed, this action is irreversible.</translate></p>
</div>
<p slot="modal-confirm"><translate :translate-context="'Popup/Library/Button.Label'">Delete</translate></p>
</dangerous-button>
</div>
</div>
</template>
<script>
import axios from 'axios'
import { diffWordsWithSpace } from 'diff'
import edits from '@/edits'
function castValue (value) {
if (value === null || value === undefined) {
return ''
}
return String(value)
}
export default {
props: {
obj: {required: true},
currentState: {required: false}
},
data () {
return {
isLoading: false
}
},
computed: {
canApprove: edits.getCanApprove,
canDelete: edits.getCanDelete,
previousState () {
if (this.obj.is_applied) {
// mutation was applied, we use the previous state that is stored
// on the mutation itself
return this.obj.previous_state
}
// mutation is not applied yet, so we use the current state that was
// passed to the component, if any
return this.currentState
},
detailUrl () {
if (!this.obj.target) {
return ''
}
let namespace
let id = this.obj.target.id
if (this.obj.target.type === 'track') {
namespace = 'library.tracks.edit.detail'
}
if (this.obj.target.type === 'album') {
namespace = 'library.albums.edit.detail'
}
if (this.obj.target.type === 'artist') {
namespace = 'library.artists.edit.detail'
}
return this.$router.resolve({name: namespace, params: {id, editId: this.obj.uuid}}).href
}
},
methods: {
remove () {
let self = this
this.isLoading = true
axios.delete(`mutations/${this.obj.uuid}/`).then((response) => {
self.$emit('deleted')
self.isLoading = false
}, error => {
self.isLoading = false
})
},
approve (approved) {
let url
if (approved) {
url = `mutations/${this.obj.uuid}/approve/`
} else {
url = `mutations/${this.obj.uuid}/reject/`
}
let self = this
this.isLoading = true
axios.post(url).then((response) => {
self.$emit('approved', approved)
self.isLoading = false
self.$store.commit('ui/incrementNotifications', {count: -1, type: 'pendingReviewEdits'})
}, error => {
self.isLoading = false
})
},
getUpdatedFields (payload, previousState) {
let fields = Object.keys(payload)
return fields.map((f) => {
let d = {
id: f,
}
if (previousState && previousState[f]) {
d.old = previousState[f]
}
d.new = payload[f]
if (d.old) {
// we compute the diffs between the old and new values
let oldValue = castValue(d.old.value)
let newValue = castValue(d.new)
d.diff = diffWordsWithSpace(oldValue, newValue)
}
return d
})
}
}
}
</script>

View file

@ -0,0 +1,52 @@
<template>
<section :class="['ui', 'vertical', 'stripe', {loading: isLoading}, 'segment']">
<div class="ui text container">
<edit-card v-if="obj" :obj="obj" :current-state="currentState" />
</div>
</section>
</template>
<script>
import axios from "axios"
import edits from '@/edits'
import EditCard from '@/components/library/EditCard'
export default {
props: ["object", "objectType", "editId"],
components: {
EditCard
},
data () {
return {
isLoading: true,
obj: null,
}
},
created () {
this.fetchData()
},
computed: {
configs: edits.getConfigs,
config: edits.getConfig,
currentState: edits.getCurrentState,
currentState () {
let self = this
let s = {}
this.config.fields.forEach(f => {
s[f.id] = {value: f.getValue(self.object)}
})
return s
}
},
methods: {
fetchData () {
var self = this
this.isLoading = true
axios.get(`mutations/${this.editId}/`).then(response => {
self.obj = response.data
self.isLoading = false
})
}
}
}
</script>

View file

@ -0,0 +1,192 @@
<template>
<div v-if="submittedMutation">
<div class="ui positive message">
<div class="header"><translate :translate-context="'Content/Library/Paragraph'">Your edit was successfully submitted.</translate></div>
</div>
<edit-card :obj="submittedMutation" :current-state="currentState" />
<button class="ui button" @click.prevent="submittedMutation = null">
<translate :translate-context="'Content/Library/Button.Label'">
Submit another edit
</translate>
</button>
</div>
<div v-else>
<edit-list :filters="editListFilters" :url="mutationsUrl" :obj="object" :currentState="currentState">
<div slot="title">
<template v-if="showPendingReview">
<translate :translate-context="'Content/Library/Paragraph'">
Recent edits awaiting review
</translate>
<button class="ui tiny basic right floated button" @click.prevent="showPendingReview = false">
<translate :translate-context="'Content/Library/Button.Label'">
Show all edits
</translate>
</button>
</template>
<template v-else>
<translate :translate-context="'Content/Library/Paragraph'">
Recent edits
</translate>
<button class="ui tiny basic right floated button" @click.prevent="showPendingReview = true">
<translate :translate-context="'Content/Library/Button.Label'">
Retrict to unreviewed edits
</translate>
</button>
</template>
</div>
<empty-state slot="empty-state">
<translate :translate-context="'Content/Library/Paragraph'">
Suggest a change using the form below.
</translate>
</empty-state>
</edit-list>
<form class="ui form" @submit.prevent="submit()">
<div class="ui hidden divider"></div>
<div v-if="errors.length > 0" class="ui negative message">
<div class="header"><translate :translate-context="'Content/Library/Error message.Title'">Error while submitting edit</translate></div>
<ul class="list">
<li v-for="error in errors">{{ error }}</li>
</ul>
</div>
<div v-if="!canEdit" class="ui message">
<translate :translate-context="'Content/Library/Paragraph'">
You don't have the permission to edit this object, but you can suggest changes. Once submitted, suggestions will be reviewed before approval.
</translate>
</div>
<div v-if="values" v-for="fieldConfig in config.fields" :key="fieldConfig.id" class="ui field">
<template v-if="fieldConfig.type === 'text'">
<label :for="fieldConfig.id">{{ fieldConfig.label }}</label>
<input :type="fieldConfig.inputType || 'text'" v-model="values[fieldConfig.id]" :required="fieldConfig.required" :name="fieldConfig.id" :id="fieldConfig.id">
</template>
<div v-if="values[fieldConfig.id] != initialValues[fieldConfig.id]">
<button class="ui tiny basic right floated reset button" form="noop" @click.prevent="values[fieldConfig.id] = initialValues[fieldConfig.id]">
<i class="undo icon"></i>
<translate :translate-context="'Content/Library/Button.Label'" :translate-params="{value: initialValues[fieldConfig.id]}">Reset to initial value: %{ value }</translate>
</button>
</div>
</div>
<div class="field">
<label for="summary"><translate :translate-context="'*/*/*'">Summary (optional)</translate></label>
<textarea name="change-summary" v-model="summary" id="change-summary" rows="3" :placeholder="labels.summaryPlaceholder"></textarea>
</div>
<router-link
class="ui left floated button"
v-if="objectType === 'track'"
:to="{name: 'library.tracks.detail', params: {id: object.id }}"
>
<translate :translate-context="'Content/*/Button.Label'">Cancel</translate>
</router-link>
<button :class="['ui', {'loading': isLoading}, 'right', 'floated', 'green', 'button']" type="submit" :disabled="isLoading || !mutationPayload">
<translate v-if="canEdit" key="1" :translate-context="'Content/Library/Button.Label/Verb'">Submit and apply edit</translate>
<translate v-else key="2" :translate-context="'Content/Library/Button.Label/Verb'">Submit suggestion</translate>
</button>
</form>
</div>
</div>
</template>
<script>
import _ from '@/lodash'
import axios from "axios"
import EditList from '@/components/library/EditList'
import EditCard from '@/components/library/EditCard'
import edits from '@/edits'
export default {
props: ["objectType", "object"],
components: {
EditList,
EditCard
},
data() {
return {
isLoading: false,
errors: [],
values: {},
initialValues: {},
summary: '',
submittedMutation: null,
showPendingReview: true,
}
},
created () {
this.setValues()
},
computed: {
configs: edits.getConfigs,
config: edits.getConfig,
currentState: edits.getCurrentState,
canEdit: edits.getCanEdit,
labels () {
return {
summaryPlaceholder: this.$pgettext('*/*/Placeholder', 'A short summary describing your changes.'),
}
},
mutationsUrl () {
if (this.objectType === 'track') {
return `tracks/${this.object.id}/mutations/`
}
},
mutationPayload () {
let self = this
let changedFields = this.config.fields.filter(f => {
return self.values[f.id] != self.initialValues[f.id]
})
if (changedFields.length === 0) {
return null
}
let payload = {
type: 'update',
payload: {},
summary: this.summary,
}
changedFields.forEach((f) => {
payload.payload[f.id] = self.values[f.id]
})
return payload
},
editListFilters () {
if (this.showPendingReview) {
return {is_approved: 'null'}
} else {
return {}
}
},
},
methods: {
setValues () {
let self = this
this.config.fields.forEach(f => {
self.$set(self.values, f.id, f.getValue(self.object))
self.$set(self.initialValues, f.id, self.values[f.id])
})
},
submit() {
let self = this
self.isLoading = true
self.errors = []
let payload = _.clone(this.mutationPayload || {})
if (this.canEdit) {
payload.is_approved = true
}
return axios.post(this.mutationsUrl, payload).then(
response => {
self.isLoading = false
self.submittedMutation = response.data
},
error => {
self.errors = error.backendErrors
self.isLoading = false
}
)
}
}
}
</script>
<style>
.reset.button {
margin-top: 0.5em;
}
</style>

View file

@ -0,0 +1,74 @@
<template>
<div class="wrapper">
<h3 class="ui header">
<slot name="title"></slot>
</h3>
<slot v-if="!isLoading && objects.length === 0" name="empty-state"></slot>
<button v-if="nextPage || previousPage" :disabled="!previousPage" @click="fetchData(previousPage)" :class="['ui', {disabled: !previousPage}, 'circular', 'icon', 'basic', 'button']"><i :class="['ui', 'angle left', 'icon']"></i></button>
<button v-if="nextPage || previousPage" :disabled="!nextPage" @click="fetchData(nextPage)" :class="['ui', {disabled: !nextPage}, 'circular', 'icon', 'basic', 'button']"><i :class="['ui', 'angle right', 'icon']"></i></button>
<div class="ui hidden divider"></div>
<div v-if="isLoading" class="ui inverted active dimmer">
<div class="ui loader"></div>
</div>
<edit-card @updated="fetchData(url)" @deleted="fetchData(url)" v-for="obj in objects" :key="obj.uuid" :obj="obj" :current-state="currentState" />
</div>
</template>
<script>
import _ from '@/lodash'
import axios from 'axios'
import EditCard from '@/components/library/EditCard'
export default {
props: {
url: {type: String, required: true},
filters: {type: Object, required: false, default: () => {return {}}},
currentState: {required: false},
},
components: {
EditCard
},
data () {
return {
objects: [],
limit: 5,
isLoading: false,
errors: null,
previousPage: null,
nextPage: null
}
},
created () {
this.fetchData(this.url)
},
methods: {
fetchData (url) {
if (!url) {
return
}
this.isLoading = true
let self = this
let params = _.clone(this.filters)
params.page_size = this.limit
axios.get(url, {params: params}).then((response) => {
self.previousPage = response.data.previous
self.nextPage = response.data.next
self.isLoading = false
self.objects = response.data.results
}, error => {
self.isLoading = false
self.errors = error.backendErrors
})
},
},
watch: {
filters: {
handler () {
this.fetchData(this.url)
},
deep: true
}
}
}
</script>

View file

@ -64,99 +64,15 @@
</div>
</modal>
</template>
<router-link
:to="{name: 'library.tracks.edit', params: {id: track.id }}"
class="ui icon labeled button">
<i class="edit icon"></i>
<translate :translate-context="'Content/Track/Button.Label/Verb'">Edit</translate>
</router-link>
</div>
</section>
<section class="ui vertical stripe center aligned segment">
<h2 class="ui header">
<translate :translate-context="'Content/Track/Title/Noun'">Track information</translate>
</h2>
<table class="ui very basic collapsing celled center aligned table">
<tbody>
<tr>
<td>
<translate :translate-context="'Content/Track/Table.Label/Noun'">Copyright</translate>
</td>
<td v-if="track.copyright" :title="track.copyright">{{ track.copyright|truncate(50) }}</td>
<td v-else>
<translate :translate-context="'Content/Track/Table.Paragraph'">No copyright information available for this track</translate>
</td>
</tr>
<tr>
<td>
<translate :translate-context="'Content/Track/Table.Label/Noun'">License</translate>
</td>
<td v-if="license">
<a :href="license.url" target="_blank" rel="noopener noreferrer">{{ license.name }}</a>
</td>
<td v-else>
<translate :translate-context="'Content/Track/Table.Paragraph'">No licensing information for this track</translate>
</td>
</tr>
<tr>
<td>
<translate :translate-context="'Content/Track/Table.Label'">Duration</translate>
</td>
<td v-if="upload && upload.duration">{{ time.parse(upload.duration) }}</td>
<td v-else>
<translate :translate-context="'*/*/*'">N/A</translate>
</td>
</tr>
<tr>
<td>
<translate :translate-context="'Content/Track/Table.Label'">Size</translate>
</td>
<td v-if="upload && upload.size">{{ upload.size | humanSize }}</td>
<td v-else>
<translate :translate-context="'*/*/*'">N/A</translate>
</td>
</tr>
<tr>
<td>
<translate :translate-context="'Content/Track/Table.Label'">Bitrate</translate>
</td>
<td v-if="upload && upload.bitrate">{{ upload.bitrate | humanSize }}/s</td>
<td v-else>
<translate :translate-context="'*/*/*'">N/A</translate>
</td>
</tr>
<tr>
<td>
<translate :translate-context="'Content/Track/Table.Label/Noun'">Type</translate>
</td>
<td v-if="upload && upload.extension">{{ upload.extension }}</td>
<td v-else>
<translate :translate-context="'*/*/*'">N/A</translate>
</td>
</tr>
</tbody>
</table>
</section>
<section class="ui vertical stripe center aligned segment">
<h2>
<translate :translate-context="'Content/Track/Title'">Lyrics</translate>
</h2>
<div v-if="isLoadingLyrics" class="ui vertical segment">
<div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div>
</div>
<div v-if="lyrics" v-html="lyrics.content_rendered"></div>
<template v-if="!isLoadingLyrics & !lyrics">
<p>
<translate :translate-context="'Content/Track/Paragraph'">No lyrics available for this track.</translate>
</p>
<a class="ui button" target="_blank" :href="lyricsSearchUrl">
<i class="search icon"></i>
<translate :translate-context="'Content/Track/Link/Verb'">Search on lyrics.wikia.com</translate>
</a>
</template>
</section>
<section class="ui vertical stripe segment">
<h2>
<translate :translate-context="'Content/Track/Title'">User libraries</translate>
</h2>
<library-widget @loaded="libraries = $event" :url="'tracks/' + id + '/libraries/'">
<translate :translate-context="'Content/Track/Paragraph'" slot="subtitle">This track is present in the following libraries:</translate>
</library-widget>
</section>
<router-view v-if="track" @libraries-loaded="libraries = $event" :track="track" :object="track" object-type="track" :key="$route.fullPath"></router-view>
</template>
</main>
</template>
@ -169,7 +85,6 @@ import logger from "@/logging"
import PlayButton from "@/components/audio/PlayButton"
import TrackFavoriteIcon from "@/components/favorites/TrackFavoriteIcon"
import TrackPlaylistIcon from "@/components/playlists/TrackPlaylistIcon"
import LibraryWidget from "@/components/federation/LibraryWidget"
import Modal from '@/components/semantic/Modal'
import EmbedWizard from "@/components/audio/EmbedWizard"
@ -181,7 +96,6 @@ export default {
PlayButton,
TrackPlaylistIcon,
TrackFavoriteIcon,
LibraryWidget,
Modal,
EmbedWizard
},
@ -189,17 +103,13 @@ export default {
return {
time,
isLoadingTrack: true,
isLoadingLyrics: true,
track: null,
lyrics: null,
licenseData: null,
libraries: [],
showEmbedModal: false
showEmbedModal: false,
libraries: []
}
},
created() {
this.fetchData()
this.fetchLyrics()
},
methods: {
fetchData() {
@ -212,29 +122,6 @@ export default {
self.isLoadingTrack = false
})
},
fetchLicenseData(licenseId) {
var self = this
let url = `licenses/${licenseId}/`
axios.get(url).then(response => {
self.licenseData = response.data
})
},
fetchLyrics() {
var self = this
this.isLoadingLyrics = true
let url = FETCH_URL + this.id + "/lyrics/"
logger.default.debug('Fetching lyrics for track "' + this.id + '"')
axios.get(url).then(
response => {
self.lyrics = response.data
self.isLoadingLyrics = false
},
response => {
console.error("No lyrics available")
self.isLoadingLyrics = false
}
)
}
},
computed: {
publicLibraries () {
@ -242,16 +129,16 @@ export default {
return l.privacy_level === 'everyone'
})
},
labels() {
return {
title: this.$pgettext('Head/Track/Title', "Track")
}
},
upload() {
if (this.track.uploads) {
return this.track.uploads[0]
}
},
labels() {
return {
title: this.$pgettext('Head/Track/Title', "Track")
}
},
wikipediaUrl() {
return (
"https://en.wikipedia.org/w/index.php?search=" +
@ -276,11 +163,6 @@ export default {
}
return u
},
lyricsSearchUrl() {
let base = "http://lyrics.wikia.com/wiki/Special:Search?query="
let query = this.track.artist.name + ":" + this.track.title
return base + encodeURI(query)
},
cover() {
return null
},
@ -302,30 +184,11 @@ export default {
")"
)
},
license() {
if (!this.track || !this.track.license) {
return null
}
return this.licenseData
}
},
watch: {
id() {
this.fetchData()
},
track (v) {
if (v && v.license) {
this.fetchLicenseData(v.license)
}
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
.table.center.aligned {
margin-left: auto;
margin-right: auto;
}
</style>

View file

@ -0,0 +1,191 @@
<template>
<div v-if="track">
<section class="ui vertical stripe center aligned segment">
<h2 class="ui header">
<translate :translate-context="'Content/Track/Title/Noun'">Track information</translate>
</h2>
<table class="ui very basic collapsing celled center aligned table">
<tbody>
<tr>
<td>
<translate :translate-context="'Content/Track/Table.Label/Noun'">Copyright</translate>
</td>
<td v-if="track.copyright" :title="track.copyright">{{ track.copyright|truncate(50) }}</td>
<td v-else>
<translate :translate-context="'Content/Track/Table.Paragraph'">No copyright information available for this track</translate>
</td>
</tr>
<tr>
<td>
<translate :translate-context="'Content/Track/Table.Label/Noun'">License</translate>
</td>
<td v-if="license">
<a :href="license.url" target="_blank" rel="noopener noreferrer">{{ license.name }}</a>
</td>
<td v-else>
<translate :translate-context="'Content/Track/Table.Paragraph'">No licensing information for this track</translate>
</td>
</tr>
<tr>
<td>
<translate :translate-context="'Content/Track/Table.Label'">Duration</translate>
</td>
<td v-if="upload && upload.duration">{{ time.parse(upload.duration) }}</td>
<td v-else>
<translate :translate-context="'*/*/*'">N/A</translate>
</td>
</tr>
<tr>
<td>
<translate :translate-context="'Content/Track/Table.Label'">Size</translate>
</td>
<td v-if="upload && upload.size">{{ upload.size | humanSize }}</td>
<td v-else>
<translate :translate-context="'*/*/*'">N/A</translate>
</td>
</tr>
<tr>
<td>
<translate :translate-context="'Content/Track/Table.Label'">Bitrate</translate>
</td>
<td v-if="upload && upload.bitrate">{{ upload.bitrate | humanSize }}/s</td>
<td v-else>
<translate :translate-context="'*/*/*'">N/A</translate>
</td>
</tr>
<tr>
<td>
<translate :translate-context="'Content/Track/Table.Label/Noun'">Type</translate>
</td>
<td v-if="upload && upload.extension">{{ upload.extension }}</td>
<td v-else>
<translate :translate-context="'*/*/*'">N/A</translate>
</td>
</tr>
</tbody>
</table>
</section>
<section class="ui vertical stripe center aligned segment">
<h2>
<translate :translate-context="'Content/Track/Title'">Lyrics</translate>
</h2>
<div v-if="isLoadingLyrics" class="ui vertical segment">
<div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div>
</div>
<div v-if="lyrics" v-html="lyrics.content_rendered"></div>
<template v-if="!isLoadingLyrics & !lyrics">
<p>
<translate :translate-context="'Content/Track/Paragraph'">No lyrics available for this track.</translate>
</p>
<a class="ui button" target="_blank" :href="lyricsSearchUrl">
<i class="search icon"></i>
<translate :translate-context="'Content/Track/Link/Verb'">Search on lyrics.wikia.com</translate>
</a>
</template>
</section>
<section class="ui vertical stripe segment">
<h2>
<translate :translate-context="'Content/Track/Title'">User libraries</translate>
</h2>
<library-widget @loaded="$emit('libraries-loaded', $event)" :url="'tracks/' + id + '/libraries/'">
<translate :translate-context="'Content/Track/Paragraph'" slot="subtitle">This track is present in the following libraries:</translate>
</library-widget>
</section>
</div>
</template>
<script>
import time from "@/utils/time"
import axios from "axios"
import url from "@/utils/url"
import logger from "@/logging"
import LibraryWidget from "@/components/federation/LibraryWidget"
const FETCH_URL = "tracks/"
export default {
props: ["track", "libraries"],
components: {
LibraryWidget,
},
data() {
return {
time,
id: this.track.id,
isLoadingLyrics: true,
lyrics: null,
licenseData: null
}
},
created() {
this.fetchLyrics()
if (this.track && this.track.license) {
this.fetchLicenseData(this.track.license)
}
},
methods: {
fetchLicenseData(licenseId) {
var self = this
let url = `licenses/${licenseId}/`
axios.get(url).then(response => {
self.licenseData = response.data
})
},
fetchLyrics() {
var self = this
this.isLoadingLyrics = true
let url = FETCH_URL + this.id + "/lyrics/"
logger.default.debug('Fetching lyrics for track "' + this.id + '"')
axios.get(url).then(
response => {
self.lyrics = response.data
self.isLoadingLyrics = false
},
response => {
console.error("No lyrics available")
self.isLoadingLyrics = false
}
)
}
},
computed: {
labels() {
return {
title: this.$pgettext('Head/Track/Title', "Track")
}
},
upload() {
if (this.track.uploads) {
return this.track.uploads[0]
}
},
lyricsSearchUrl() {
let base = "http://lyrics.wikia.com/wiki/Special:Search?query="
let query = this.track.artist.name + ":" + this.track.title
return base + encodeURI(query)
},
license() {
if (!this.track || !this.track.license) {
return null
}
return this.licenseData
}
},
watch: {
track (v) {
if (v && v.license) {
this.fetchLicenseData(v.license)
}
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
.table.center.aligned {
margin-left: auto;
margin-right: auto;
}
</style>

View file

@ -0,0 +1,34 @@
<template>
<section class="ui vertical stripe segment">
<div class="ui text container">
<h2>
<translate v-if="canEdit" key="1" :translate-context="'Content/*/Title'">Edit this track</translate>
<translate v-else key="2" :translate-context="'Content/*/Title'">Suggest an edit on this track</translate>
</h2>
<edit-form :object-type="objectType" :object="object" :can-edit="canEdit"></edit-form>
</div>
</section>
</template>
<script>
import axios from "axios"
import EditForm from '@/components/library/EditForm'
export default {
props: ["objectType", "object", "libraries"],
data() {
return {
id: this.object.id
}
},
components: {
EditForm
},
computed: {
canEdit () {
return true
}
}
}
</script>