Migrate some components
This commit is contained in:
parent
7eca32e006
commit
c1494c8894
|
@ -8,7 +8,7 @@ import { useStore } from '~/store'
|
|||
import AlbumCard from '~/components/audio/album/Card.vue'
|
||||
|
||||
interface Props {
|
||||
filters: Record<string, string>
|
||||
filters: Record<string, string | boolean>
|
||||
showCount?: boolean
|
||||
search?: boolean
|
||||
limit?: number
|
||||
|
|
|
@ -8,7 +8,7 @@ import { useStore } from '~/store'
|
|||
import ArtistCard from '~/components/audio/artist/Card.vue'
|
||||
|
||||
interface Props {
|
||||
filters: Record<string, string>
|
||||
filters: Record<string, string | boolean>
|
||||
search?: boolean
|
||||
header?: boolean
|
||||
limit?: number
|
||||
|
|
|
@ -16,7 +16,7 @@ interface Emits {
|
|||
}
|
||||
|
||||
interface Props {
|
||||
filters: Record<string, string>
|
||||
filters: Record<string, string | boolean>
|
||||
url: string
|
||||
isActivity?: boolean
|
||||
showCount?: boolean
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import type { EditObject, EditObjectType } from '~/composables/moderation/useEditConfigs'
|
||||
import type { ReviewState } from '~/types'
|
||||
|
||||
import axios from 'axios'
|
||||
import useEditConfigs from '~/composables/moderation/useEditConfigs'
|
||||
|
@ -17,7 +18,7 @@ const props = defineProps<Props>()
|
|||
const configs = useEditConfigs()
|
||||
const config = computed(() => configs[props.objectType])
|
||||
|
||||
const currentState = computed(() => config.value.fields.reduce((state: Record<string, unknown>, field) => {
|
||||
const currentState = computed(() => config.value.fields.reduce((state: ReviewState, field) => {
|
||||
state[field.id] = { value: field.getValue(props.object) }
|
||||
return state
|
||||
}, {}))
|
||||
|
|
|
@ -1,3 +1,148 @@
|
|||
<script setup lang="ts">
|
||||
import type { EditObject, EditObjectType } from '~/composables/moderation/useEditConfigs'
|
||||
import type { BackendError, License, ReviewState } from '~/types'
|
||||
|
||||
import { computed, onMounted, reactive, ref, watchEffect } from 'vue'
|
||||
import { isEqual, clone } from 'lodash-es'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useStore } from '~/store'
|
||||
|
||||
import axios from 'axios'
|
||||
import $ from 'jquery'
|
||||
|
||||
import AttachmentInput from '~/components/common/AttachmentInput.vue'
|
||||
import useEditConfigs from '~/composables/moderation/useEditConfigs'
|
||||
import TagsSelector from '~/components/library/TagsSelector.vue'
|
||||
import EditList from '~/components/library/EditList.vue'
|
||||
import EditCard from '~/components/library/EditCard.vue'
|
||||
|
||||
interface Props {
|
||||
objectType: EditObjectType
|
||||
object: EditObject
|
||||
licenses: License[]
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const configs = useEditConfigs()
|
||||
const store = useStore()
|
||||
|
||||
const config = computed(() => configs[props.objectType])
|
||||
const currentState = computed(() => config.value.fields.reduce((state: ReviewState, field) => {
|
||||
state[field.id] = { value: field.getValue(props.object) }
|
||||
return state
|
||||
}, {}))
|
||||
|
||||
const canEdit = computed(() => {
|
||||
if (!store.state.auth.authenticated) return false
|
||||
|
||||
const isOwner = props.object.attributed_to
|
||||
// TODO (wvffle): Is it better to compare ids? Is full_username unique?
|
||||
&& store.state.auth.fullUsername === props.object.attributed_to.full_username
|
||||
|
||||
return isOwner || store.state.auth.availablePermissions.library
|
||||
})
|
||||
|
||||
const labels = computed(() => ({
|
||||
summaryPlaceholder: $pgettext('*/*/Placeholder', 'A short summary describing your changes.')
|
||||
}))
|
||||
|
||||
const mutationsUrl = computed(() => props.objectType === 'track'
|
||||
? `tracks/${props.object.id}/mutations/`
|
||||
: props.objectType === 'album'
|
||||
? `albums/${props.object.id}/mutations/`
|
||||
: props.objectType === 'artist'
|
||||
? `artists/${props.object.id}/mutations/`
|
||||
: ''
|
||||
)
|
||||
|
||||
const mutationPayload = computed(() => {
|
||||
const changedFields = config.value.fields.filter(f => {
|
||||
return !isEqual(values[f.id], initialValues[f.id])
|
||||
})
|
||||
|
||||
if (changedFields.length === 0) {
|
||||
return {}
|
||||
}
|
||||
|
||||
const data = {
|
||||
type: 'update',
|
||||
payload: {} as Record<string, unknown>,
|
||||
summary: summary.value
|
||||
}
|
||||
|
||||
for (const field of changedFields) {
|
||||
data.payload[field.id] = values[field.id]
|
||||
}
|
||||
|
||||
return data
|
||||
})
|
||||
|
||||
const showPendingReview = ref(true)
|
||||
const editListFilters = computed(() => showPendingReview.value
|
||||
? { is_approved: 'null' }
|
||||
: {}
|
||||
)
|
||||
|
||||
const values = reactive({} as Record<string, any>)
|
||||
const initialValues = reactive({} as Record<string, any>)
|
||||
for (const { id, getValue } of config.value.fields) {
|
||||
values[id] = clone(getValue(props.object))
|
||||
initialValues[id] = clone(values[id])
|
||||
}
|
||||
|
||||
const license = ref()
|
||||
watchEffect(() => {
|
||||
if (values.license === null) {
|
||||
$(license.value).dropdown('clear')
|
||||
return
|
||||
}
|
||||
|
||||
$(license.value).dropdown('set selected', values.license)
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
$('.ui.dropdown').dropdown({ fullTextSearch: true })
|
||||
})
|
||||
|
||||
const submittedMutation = ref()
|
||||
const summary = ref('')
|
||||
|
||||
const errors = ref([] as string[])
|
||||
const isLoading = ref(false)
|
||||
const submit = async () => {
|
||||
const url = mutationsUrl.value
|
||||
if (!url) return
|
||||
|
||||
isLoading.value = true
|
||||
errors.value = []
|
||||
|
||||
try {
|
||||
const response = await axios.post(url, {
|
||||
...mutationPayload.value,
|
||||
is_approved: canEdit.value
|
||||
? true
|
||||
: undefined
|
||||
})
|
||||
|
||||
submittedMutation.value = response.data
|
||||
} catch (error) {
|
||||
errors.value = (error as BackendError).backendErrors
|
||||
}
|
||||
|
||||
isLoading.value = false
|
||||
}
|
||||
|
||||
const fieldValuesChanged = (fieldId: string) => {
|
||||
return !isEqual(values[fieldId], initialValues[fieldId])
|
||||
}
|
||||
|
||||
const resetField = (fieldId: string) => {
|
||||
values[fieldId] = clone(initialValues[fieldId])
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="submittedMutation">
|
||||
<div class="ui positive message">
|
||||
|
@ -107,7 +252,7 @@
|
|||
:id="fieldConfig.id"
|
||||
v-model="values[fieldConfig.id]"
|
||||
:type="fieldConfig.inputType || 'text'"
|
||||
:required="fieldConfig.required || null"
|
||||
:required="fieldConfig.required"
|
||||
:name="fieldConfig.id"
|
||||
>
|
||||
</template>
|
||||
|
@ -118,7 +263,7 @@
|
|||
:id="fieldConfig.id"
|
||||
ref="license"
|
||||
v-model="values[fieldConfig.id]"
|
||||
:required="fieldConfig.required || null"
|
||||
:required="fieldConfig.required"
|
||||
class="ui fluid search dropdown"
|
||||
>
|
||||
<option :value="null">
|
||||
|
@ -158,7 +303,7 @@
|
|||
:id="fieldConfig.id"
|
||||
v-model="values[fieldConfig.id]"
|
||||
:initial-value="initialValues[fieldConfig.id]"
|
||||
:required="fieldConfig.required || null"
|
||||
:required="fieldConfig.required"
|
||||
:name="fieldConfig.id"
|
||||
@delete="values[fieldConfig.id] = initialValues[fieldConfig.id]"
|
||||
>
|
||||
|
@ -220,7 +365,7 @@
|
|||
<button
|
||||
:class="['ui', {'loading': isLoading}, 'right', 'floated', 'success', 'button']"
|
||||
type="submit"
|
||||
:disabled="isLoading || !mutationPayload || null"
|
||||
:disabled="isLoading || !mutationPayload"
|
||||
>
|
||||
<translate
|
||||
v-if="canEdit"
|
||||
|
@ -238,153 +383,3 @@
|
|||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import $ from 'jquery'
|
||||
import { isEqual, clone } from 'lodash-es'
|
||||
import axios from 'axios'
|
||||
import AttachmentInput from '~/components/common/AttachmentInput.vue'
|
||||
import EditList from '~/components/library/EditList.vue'
|
||||
import EditCard from '~/components/library/EditCard.vue'
|
||||
import TagsSelector from '~/components/library/TagsSelector.vue'
|
||||
import useEditConfigs from '~/composables/moderation/useEditConfigs'
|
||||
import { computed } from 'vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
EditList,
|
||||
EditCard,
|
||||
TagsSelector,
|
||||
AttachmentInput
|
||||
},
|
||||
props: {
|
||||
objectType: { type: String, required: true },
|
||||
object: { type: Object, required: true },
|
||||
licenses: { type: Array, required: true }
|
||||
},
|
||||
setup (props) {
|
||||
const configs = useEditConfigs()
|
||||
const config = computed(() => configs[props.objectType])
|
||||
const currentState = computed(() => config.value.fields.reduce((state/*: Record<string, unknown> */, field) => {
|
||||
state[field.id] = { value: field.getValue(props.object) }
|
||||
return state
|
||||
}, {}))
|
||||
|
||||
return { config, currentState, configs }
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
isLoading: false,
|
||||
errors: [],
|
||||
values: {},
|
||||
initialValues: {},
|
||||
summary: '',
|
||||
submittedMutation: null,
|
||||
showPendingReview: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
canEdit () {
|
||||
if (!this.$store.state.auth.authenticated) return false
|
||||
|
||||
const isOwner = this.object.attributed_to
|
||||
// TODO (wvffle): Is it better to compare ids? Is full_username unique?
|
||||
&& this.$store.state.auth.fullUsername === this.object.attributed_to.full_username
|
||||
|
||||
return isOwner || this.$store.state.auth.availablePermissions.library
|
||||
},
|
||||
labels () {
|
||||
return {
|
||||
summaryPlaceholder: this.$pgettext('*/*/Placeholder', 'A short summary describing your changes.')
|
||||
}
|
||||
},
|
||||
mutationsUrl () {
|
||||
if (this.objectType === 'track') {
|
||||
return `tracks/${this.object.id}/mutations/`
|
||||
}
|
||||
if (this.objectType === 'album') {
|
||||
return `albums/${this.object.id}/mutations/`
|
||||
}
|
||||
if (this.objectType === 'artist') {
|
||||
return `artists/${this.object.id}/mutations/`
|
||||
}
|
||||
return null
|
||||
},
|
||||
mutationPayload () {
|
||||
const self = this
|
||||
const changedFields = this.config.fields.filter(f => {
|
||||
return !isEqual(self.values[f.id], self.initialValues[f.id])
|
||||
})
|
||||
if (changedFields.length === 0) {
|
||||
return null
|
||||
}
|
||||
const 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 {}
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'values.license' (newValue) {
|
||||
if (newValue === null) {
|
||||
$(this.$refs.license).dropdown('clear')
|
||||
} else {
|
||||
$(this.$refs.license).dropdown('set selected', newValue)
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.setValues()
|
||||
},
|
||||
mounted () {
|
||||
$('.ui.dropdown').dropdown({ fullTextSearch: true })
|
||||
},
|
||||
|
||||
methods: {
|
||||
setValues () {
|
||||
for (const { id, getValue } of this.config.fields) {
|
||||
this.values[id] = clone(getValue(this.object))
|
||||
this.initialValues[id] = clone(this.values[id])
|
||||
}
|
||||
},
|
||||
submit () {
|
||||
const self = this
|
||||
self.isLoading = true
|
||||
self.errors = []
|
||||
const 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
|
||||
}
|
||||
)
|
||||
},
|
||||
fieldValuesChanged (fieldId) {
|
||||
return !isEqual(this.values[fieldId], this.initialValues[fieldId])
|
||||
},
|
||||
resetField (fieldId) {
|
||||
this.values[fieldId] = clone(this.initialValues[fieldId])
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,3 +1,23 @@
|
|||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
|
||||
import ChannelsWidget from '~/components/audio/ChannelsWidget.vue'
|
||||
import TrackWidget from '~/components/audio/track/Widget.vue'
|
||||
import AlbumWidget from '~/components/audio/album/Widget.vue'
|
||||
import ArtistWidget from '~/components/audio/artist/Widget.vue'
|
||||
import RadioButton from '~/components/radios/Button.vue'
|
||||
|
||||
interface Props {
|
||||
id: string
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const labels = computed(() => ({
|
||||
title: `#${props.id}`
|
||||
}))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main v-title="labels.title">
|
||||
<section class="ui vertical stripe segment">
|
||||
|
@ -87,38 +107,3 @@
|
|||
</section>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ChannelsWidget from '~/components/audio/ChannelsWidget.vue'
|
||||
import TrackWidget from '~/components/audio/track/Widget.vue'
|
||||
import AlbumWidget from '~/components/audio/album/Widget.vue'
|
||||
import ArtistWidget from '~/components/audio/artist/Widget.vue'
|
||||
import RadioButton from '~/components/radios/Button.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ArtistWidget,
|
||||
AlbumWidget,
|
||||
TrackWidget,
|
||||
RadioButton,
|
||||
ChannelsWidget
|
||||
},
|
||||
props: {
|
||||
id: { type: String, required: true }
|
||||
},
|
||||
computed: {
|
||||
labels () {
|
||||
const title = `#${this.id}`
|
||||
return {
|
||||
title
|
||||
}
|
||||
},
|
||||
isAuthenticated () {
|
||||
return this.$store.state.auth.authenticated
|
||||
},
|
||||
hasFavorites () {
|
||||
return this.$store.state.favorites.count > 0
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,3 +1,91 @@
|
|||
<script setup lang="ts">
|
||||
import type { Tag } from '~/types'
|
||||
|
||||
import { ref, watch, onMounted, nextTick } from 'vue'
|
||||
import { isEqual } from 'lodash-es'
|
||||
import { useStore } from '~/store'
|
||||
|
||||
import $ from 'jquery'
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:modelValue', tags: Tag[]): void
|
||||
}
|
||||
|
||||
interface Props {
|
||||
modelValue: Tag[]
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>()
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const store = useStore()
|
||||
|
||||
const dropdown = ref()
|
||||
watch(() => props.modelValue, (value) => {
|
||||
const current = $(dropdown.value).dropdown('get value').split(',').sort()
|
||||
|
||||
if (!isEqual([...value].sort(), current)) {
|
||||
$(dropdown.value).dropdown('set exactly', value)
|
||||
}
|
||||
})
|
||||
|
||||
const handleUpdate = () => {
|
||||
const value = $(dropdown.value).dropdown('get value').split(',')
|
||||
emit('update:modelValue', value)
|
||||
return value
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await nextTick()
|
||||
|
||||
$(dropdown.value).dropdown({
|
||||
keys: { delimiter: 32 },
|
||||
forceSelection: false,
|
||||
saveRemoteData: false,
|
||||
filterRemoteData: true,
|
||||
preserveHTML: false,
|
||||
apiSettings: {
|
||||
url: store.getters['instance/absoluteUrl']('/api/v1/tags/?name__startswith={query}&ordering=length&page_size=5'),
|
||||
beforeXHR: function (xhrObject) {
|
||||
if (store.state.auth.oauth.accessToken) {
|
||||
xhrObject.setRequestHeader('Authorization', store.getters['auth/header'])
|
||||
}
|
||||
return xhrObject
|
||||
},
|
||||
onResponse (response) {
|
||||
response = { results: [], ...response }
|
||||
|
||||
// @ts-expect-error Semantic UI
|
||||
const currentSearch: string = $(dropdown.value).dropdown('get query')
|
||||
|
||||
if (currentSearch) {
|
||||
const existingTag = response.results.find((result: Tag) => result.name === currentSearch)
|
||||
|
||||
if (existingTag) {
|
||||
if (response.results.indexOf(existingTag) !== 0) {
|
||||
response.results = [existingTag, ...response.results]
|
||||
response.results.splice(response.results.indexOf(existingTag) + 1, 1)
|
||||
}
|
||||
} else {
|
||||
response.results = [{ name: currentSearch }, ...response.results]
|
||||
}
|
||||
}
|
||||
return response
|
||||
}
|
||||
},
|
||||
fields: { remoteValues: 'results', value: 'name' },
|
||||
allowAdditions: true,
|
||||
minCharacters: 1,
|
||||
onAdd: handleUpdate,
|
||||
onRemove: handleUpdate,
|
||||
onLabelRemove: handleUpdate,
|
||||
onChange: handleUpdate
|
||||
})
|
||||
|
||||
$(dropdown.value).dropdown('set exactly', props.modelValue)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
ref="dropdown"
|
||||
|
@ -17,86 +105,3 @@
|
|||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import $ from 'jquery'
|
||||
|
||||
import { isEqual } from 'lodash-es'
|
||||
export default {
|
||||
props: { modelValue: { type: Array, required: true } },
|
||||
watch: {
|
||||
modelValue: {
|
||||
handler (v) {
|
||||
const current = $(this.$refs.dropdown).dropdown('get value').split(',').sort()
|
||||
if (!isEqual([...v].sort(), current)) {
|
||||
$(this.$refs.dropdown).dropdown('set exactly', v)
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.$nextTick(() => {
|
||||
this.initDropdown()
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
initDropdown () {
|
||||
const self = this
|
||||
const handleUpdate = () => {
|
||||
const value = $(self.$refs.dropdown).dropdown('get value').split(',')
|
||||
self.$emit('update:modelValue', value)
|
||||
return value
|
||||
}
|
||||
const settings = {
|
||||
keys: {
|
||||
delimiter: 32
|
||||
},
|
||||
forceSelection: false,
|
||||
saveRemoteData: false,
|
||||
filterRemoteData: true,
|
||||
preserveHTML: false,
|
||||
apiSettings: {
|
||||
url: this.$store.getters['instance/absoluteUrl']('/api/v1/tags/?name__startswith={query}&ordering=length&page_size=5'),
|
||||
beforeXHR: function (xhrObject) {
|
||||
if (self.$store.state.auth.oauth.accessToken) {
|
||||
xhrObject.setRequestHeader('Authorization', self.$store.getters['auth/header'])
|
||||
}
|
||||
return xhrObject
|
||||
},
|
||||
onResponse (response) {
|
||||
const currentSearch = $(self.$refs.dropdown).dropdown('get query')
|
||||
response = {
|
||||
results: [],
|
||||
...response
|
||||
}
|
||||
if (currentSearch) {
|
||||
const existingTag = response.results.find((result) => result.name === currentSearch)
|
||||
if (existingTag) {
|
||||
if (response.results.indexOf(existingTag) !== 0) {
|
||||
response.results = [existingTag, ...response.results]
|
||||
response.results.splice(response.results.indexOf(existingTag) + 1, 1)
|
||||
}
|
||||
} else {
|
||||
response.results = [{ name: currentSearch }, ...response.results]
|
||||
}
|
||||
}
|
||||
return response
|
||||
}
|
||||
},
|
||||
fields: {
|
||||
remoteValues: 'results',
|
||||
value: 'name'
|
||||
},
|
||||
allowAdditions: true,
|
||||
minCharacters: 1,
|
||||
onAdd: handleUpdate,
|
||||
onRemove: handleUpdate,
|
||||
onLabelRemove: handleUpdate,
|
||||
onChange: handleUpdate
|
||||
}
|
||||
$(this.$refs.dropdown).dropdown(settings)
|
||||
$(this.$refs.dropdown).dropdown('set exactly', this.modelValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,3 +1,49 @@
|
|||
<script setup lang="ts">
|
||||
import type { Track } from '~/types'
|
||||
|
||||
import { humanSize, momentFormat, truncate } from '~/utils/filters'
|
||||
import { computed, ref, watchEffect } from 'vue'
|
||||
|
||||
import time from '~/utils/time'
|
||||
import axios from 'axios'
|
||||
|
||||
import LibraryWidget from '~/components/federation/LibraryWidget.vue'
|
||||
import PlaylistWidget from '~/components/playlists/Widget.vue'
|
||||
import TagsList from '~/components/tags/List.vue'
|
||||
|
||||
interface Props {
|
||||
track: Track
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const musicbrainzUrl = computed(() => props.track.mbid
|
||||
? `https://musicbrainz.org/recording/${props.track.mbid}`
|
||||
: null
|
||||
)
|
||||
|
||||
const upload = computed(() => props.track.uploads?.[0] ?? null)
|
||||
|
||||
const license = ref()
|
||||
const fetchLicense = async (licenseId: string) => {
|
||||
license.value = undefined
|
||||
|
||||
try {
|
||||
const response = await axios.get(`licenses/${licenseId}`)
|
||||
license.value = response.data
|
||||
} catch (error) {
|
||||
// TODO (wvffle): Handle error
|
||||
}
|
||||
}
|
||||
|
||||
watchEffect(() => {
|
||||
if (props.track.license) {
|
||||
// @ts-expect-error For some reason, track.license is id instead of License here
|
||||
fetchLicense(props.track.license)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="track">
|
||||
<section class="ui vertical stripe segment">
|
||||
|
@ -24,7 +70,7 @@
|
|||
>
|
||||
<h3 class="ui header">
|
||||
<translate
|
||||
v-if="track.artist.content_category === 'music'"
|
||||
v-if="track.artist?.content_category === 'music'"
|
||||
translate-context="Content/*/*"
|
||||
>
|
||||
Track Details
|
||||
|
@ -148,8 +194,8 @@
|
|||
</translate>
|
||||
</td>
|
||||
<td class="right aligned">
|
||||
<router-link :to="{name: 'library.artists.detail', params: {id: track.artist.id}}">
|
||||
{{ track.artist.name }}
|
||||
<router-link :to="{name: 'library.artists.detail', params: {id: track.artist?.id}}">
|
||||
{{ track.artist?.name }}
|
||||
</router-link>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -182,7 +228,7 @@
|
|||
</td>
|
||||
<td class="right aligned">
|
||||
<template v-if="track.album && track.album.release_date">
|
||||
{{ momentFormat(track.album.release_date, 'Y') }}
|
||||
{{ momentFormat(new Date(track.album.release_date), 'Y') }}
|
||||
</template>
|
||||
<template v-else>
|
||||
<translate translate-context="*/*/*">
|
||||
|
@ -273,7 +319,7 @@
|
|||
</translate>
|
||||
</h2>
|
||||
<library-widget
|
||||
:url="'tracks/' + id + '/libraries/'"
|
||||
:url="`tracks/${track.id}/libraries/`"
|
||||
@loaded="$emit('libraries-loaded', $event)"
|
||||
>
|
||||
<translate translate-context="Content/Track/Paragraph">
|
||||
|
@ -285,88 +331,3 @@
|
|||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
import LibraryWidget from '~/components/federation/LibraryWidget.vue'
|
||||
import TagsList from '~/components/tags/List.vue'
|
||||
import PlaylistWidget from '~/components/playlists/Widget.vue'
|
||||
import { humanSize, momentFormat, truncate } from '~/utils/filters'
|
||||
import time from '~/utils/time'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
LibraryWidget,
|
||||
TagsList,
|
||||
PlaylistWidget
|
||||
},
|
||||
props: {
|
||||
track: { type: Object, required: true },
|
||||
libraries: { type: Array, default: null }
|
||||
},
|
||||
setup () {
|
||||
return { humanSize, momentFormat, time, truncate }
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
id: this.track.id,
|
||||
licenseData: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
labels () {
|
||||
return {
|
||||
title: this.$pgettext('*/*/*/Noun', 'Track')
|
||||
}
|
||||
},
|
||||
musicbrainzUrl () {
|
||||
if (this.track.mbid) {
|
||||
return 'https://musicbrainz.org/recording/' + this.track.mbid
|
||||
}
|
||||
return null
|
||||
},
|
||||
upload () {
|
||||
if (this.track.uploads) {
|
||||
return this.track.uploads[0]
|
||||
}
|
||||
return null
|
||||
},
|
||||
license () {
|
||||
if (!this.track || !this.track.license) {
|
||||
return null
|
||||
}
|
||||
return this.licenseData
|
||||
},
|
||||
cover () {
|
||||
if (this.track.cover && this.track.cover.urls.original) {
|
||||
return this.track.cover
|
||||
}
|
||||
if (this.track.album && this.track.album.cover) {
|
||||
return this.track.album.cover
|
||||
}
|
||||
return null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
track (v) {
|
||||
if (v && v.license) {
|
||||
this.fetchLicenseData(v.license)
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
if (this.track && this.track.license) {
|
||||
this.fetchLicenseData(this.track.license)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
fetchLicenseData (licenseId) {
|
||||
const self = this
|
||||
const url = `licenses/${licenseId}`
|
||||
axios.get(url).then(response => {
|
||||
self.licenseData = response.data
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import type { Album, Artist, Content, Track } from '~/types'
|
||||
import type { Album, Artist, Content, Track, Actor } from '~/types'
|
||||
|
||||
import { gettext } from '~/init/locale'
|
||||
|
||||
|
@ -16,7 +16,7 @@ export interface EditableConfigField extends ConfigField {
|
|||
id: EditObjectType
|
||||
}
|
||||
|
||||
export type EditObject = Artist | Album | Track
|
||||
export type EditObject = (Partial<Artist> | Partial<Album> | Partial<Track>) & { attributed_to: Actor }
|
||||
export type EditObjectType = 'artist' | 'album' | 'track'
|
||||
type Configs = Record<EditObjectType, { fields: (EditableConfigField|ConfigField)[] }>
|
||||
|
||||
|
|
|
@ -96,6 +96,7 @@ export interface Track {
|
|||
license?: License
|
||||
tags: string[]
|
||||
uploads: Upload[]
|
||||
downloads_count: number
|
||||
|
||||
album?: Album
|
||||
artist?: Artist
|
||||
|
@ -277,6 +278,7 @@ export interface Upload {
|
|||
mimetype: string
|
||||
extension: string
|
||||
listen_url: string
|
||||
bitrate?: number
|
||||
size?: number
|
||||
|
||||
import_status: ImportStatus
|
||||
|
|
Loading…
Reference in New Issue