284 lines
7.1 KiB
Vue
284 lines
7.1 KiB
Vue
<script setup lang="ts">
|
|
import type { Track, Library } from '~/types'
|
|
|
|
import { humanSize, momentFormat } from '~/utils/filters'
|
|
import { computed, ref, watchEffect } from 'vue'
|
|
import { useI18n } from 'vue-i18n'
|
|
import { useStore } from '~/store'
|
|
|
|
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'
|
|
import Layout from '~/components/ui/Layout.vue'
|
|
import Section from '~/components/ui/Section.vue'
|
|
import Link from '~/components/ui/Link.vue'
|
|
import Spacer from '~/components/ui/Spacer.vue'
|
|
|
|
import useErrorHandler from '~/composables/useErrorHandler'
|
|
|
|
interface Events {
|
|
(e: 'libraries-loaded', libraries: Library[]): void
|
|
}
|
|
|
|
interface Props {
|
|
track: Track
|
|
}
|
|
|
|
const { t } = useI18n()
|
|
|
|
const emit = defineEmits<Events>()
|
|
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) {
|
|
useErrorHandler(error as Error)
|
|
}
|
|
}
|
|
|
|
watchEffect(() => {
|
|
if (props.track.license) {
|
|
// @ts-expect-error For some reason, track.license is id instead of License here
|
|
fetchLicense(props.track.license)
|
|
}
|
|
})
|
|
|
|
const release_details: {
|
|
label: string;
|
|
release_value: string;
|
|
link?: { name: string; params: { id: number } };
|
|
}[] = [
|
|
{
|
|
label: t('components.library.TrackDetail.table.release.artist'),
|
|
release_value: props.track.artist_credit.map(ac => ac.credit).join(', '),
|
|
link: props.track.artist_credit.length > 0
|
|
? {
|
|
name: 'library.artists.detail',
|
|
params: { id: props.track.artist_credit[0].artist.id }
|
|
}
|
|
: undefined
|
|
},
|
|
{
|
|
label:
|
|
props.track.album?.artist_credit?.[0].artist.content_category === 'music'
|
|
? t('components.library.TrackDetail.table.release.album')
|
|
: t('components.library.TrackDetail.table.release.series'),
|
|
release_value: props.track.album?.title || t('components.library.TrackDetail.notApplicable'),
|
|
link: props.track.album
|
|
? {
|
|
name: 'library.albums.detail',
|
|
params: { id: props.track.album.id }
|
|
}
|
|
: undefined
|
|
},
|
|
{
|
|
label: t('components.library.TrackDetail.table.release.year'),
|
|
release_value: props.track.album?.release_date
|
|
? momentFormat(new Date(props.track.album.release_date), 'Y')
|
|
: t('components.library.TrackDetail.notApplicable')
|
|
},
|
|
{
|
|
label: t('components.library.TrackDetail.table.release.copyright'),
|
|
release_value: props.track.copyright || t('components.library.TrackDetail.notApplicable')
|
|
},
|
|
{
|
|
label: t('components.library.TrackDetail.table.release.license'),
|
|
release_value: license.value?.name || t('components.library.TrackDetail.notApplicable')
|
|
}
|
|
]
|
|
|
|
const track_details: {
|
|
label: string;
|
|
track_value: string | number;
|
|
link?: { name: string; params: { id: number } };
|
|
}[] = [
|
|
{
|
|
label: t('components.library.TrackDetail.table.track.duration'),
|
|
track_value: upload?.value.duration ? time.parse(upload.value.duration) : t('components.library.TrackDetail.notApplicable')
|
|
},
|
|
{
|
|
label:
|
|
t('components.library.TrackDetail.table.track.size'),
|
|
track_value: upload?.value.size ? humanSize(upload.value.size) : t('components.library.TrackDetail.notApplicable')
|
|
},
|
|
{
|
|
label: t('components.library.TrackDetail.table.track.codec'),
|
|
track_value: upload?.value.extension || t('components.library.TrackDetail.notApplicable')
|
|
},
|
|
{
|
|
label:
|
|
t('components.library.TrackDetail.table.track.bitrate.label'),
|
|
track_value: upload?.value.bitrate
|
|
? t('components.library.TrackDetail.table.track.bitrate.value', { bitrate: humanSize(upload.value.bitrate) })
|
|
: t('components.library.TrackDetail.notApplicable')
|
|
},
|
|
{
|
|
label: t('components.library.TrackDetail.table.track.downloads'),
|
|
track_value: props.track.downloads_count
|
|
}
|
|
]
|
|
</script>
|
|
|
|
<template>
|
|
<Layout
|
|
v-if="track"
|
|
stack
|
|
>
|
|
<TagsList
|
|
v-if="track.tags && track.tags.length > 0"
|
|
style="margin-top: -16px;"
|
|
:tags="track.tags"
|
|
/>
|
|
|
|
<Layout
|
|
flex
|
|
style="gap: 24px;"
|
|
>
|
|
<Layout
|
|
stack
|
|
style="flex: 1; gap: 0;"
|
|
>
|
|
<Section
|
|
align-left
|
|
h2="Release Details"
|
|
:action="{
|
|
text:'View on MusicBrainz',
|
|
to: musicbrainzUrl
|
|
}"
|
|
icon="bi-box-arrow-up-right"
|
|
/>
|
|
<Layout
|
|
v-for="item in release_details"
|
|
key="label"
|
|
flex
|
|
class="details"
|
|
style="min-width: 120px;"
|
|
>
|
|
<span class="label">{{ item.label }}</span>
|
|
<Spacer
|
|
h
|
|
grow
|
|
/>
|
|
<Link
|
|
v-if="item.link"
|
|
class="value"
|
|
:to="item.link"
|
|
>
|
|
{{ item.release_value }}
|
|
</Link>
|
|
<span
|
|
v-else
|
|
class="value"
|
|
>{{ item.release_value }}</span>
|
|
</Layout>
|
|
</Layout>
|
|
|
|
<Layout
|
|
stack
|
|
style="flex: 1; gap: 0;"
|
|
>
|
|
<Section
|
|
align-left
|
|
h2="Track Details"
|
|
/>
|
|
<Layout
|
|
v-for="item in track_details"
|
|
key="label"
|
|
flex
|
|
class="details"
|
|
style="min-width: 120px;"
|
|
>
|
|
<span class="label">{{ item.label }}</span>
|
|
<Spacer
|
|
h
|
|
grow
|
|
/>
|
|
<Link
|
|
v-if="item.link"
|
|
class="value"
|
|
:to="item.link"
|
|
>
|
|
{{ item.track_value }}
|
|
</Link>
|
|
<span
|
|
v-else
|
|
class="value"
|
|
>{{ item.track_value }}</span>
|
|
</Layout>
|
|
</Layout>
|
|
</Layout>
|
|
|
|
<h2>{{ t('components.library.TrackDetail.header.playlists') }}</h2>
|
|
<playlist-widget
|
|
:url="'playlists/'"
|
|
:filters="{track: track.id, playable: true, ordering: '-modification_date'}"
|
|
/>
|
|
|
|
<h2>{{ t('components.library.TrackDetail.header.library') }}</h2>
|
|
<library-widget
|
|
:url="`tracks/${track.id}/libraries/`"
|
|
@loaded="emit('libraries-loaded', $event)"
|
|
>
|
|
{{ t('components.library.TrackDetail.description.library') }}
|
|
</library-widget>
|
|
</Layout>
|
|
</template>
|
|
|
|
<style lang="scss">
|
|
.channel-image {
|
|
width: 300px;
|
|
height: 300px;
|
|
border: none;
|
|
}
|
|
|
|
.details {
|
|
padding: 0 16px;
|
|
height: 72px;
|
|
align-items: center;
|
|
border-top: 1px solid;
|
|
|
|
@include light-theme {
|
|
border-color: var(--fw-gray-300);
|
|
}
|
|
@include dark-theme {
|
|
border-color: var(--fw-gray-800);
|
|
}
|
|
|
|
.label {
|
|
font-weight: 800;
|
|
|
|
@include light-theme {
|
|
color: var(--fw-gray-600);
|
|
}
|
|
|
|
@include dark-theme {
|
|
color: var(--fw-gray-500);
|
|
}
|
|
}
|
|
|
|
a.value {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
&:last-child {
|
|
border-bottom: 1px solid;
|
|
}
|
|
}
|
|
|
|
</style>
|