chore(front): new layout for artist detail
This commit is contained in:
parent
d1c21fa86d
commit
ebc92bf0a9
|
@ -4,6 +4,7 @@ import type { Track, Album, Artist, Library } from '~/types'
|
||||||
import { computed, ref, watch } from 'vue'
|
import { computed, ref, watch } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useRouter, useRoute } from 'vue-router'
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
|
import { sum } from 'lodash-es'
|
||||||
import { getDomain } from '~/utils'
|
import { getDomain } from '~/utils'
|
||||||
import { useStore } from '~/store'
|
import { useStore } from '~/store'
|
||||||
|
|
||||||
|
@ -11,8 +12,9 @@ import axios from 'axios'
|
||||||
import useReport from '~/composables/moderation/useReport'
|
import useReport from '~/composables/moderation/useReport'
|
||||||
import useLogger from '~/composables/useLogger'
|
import useLogger from '~/composables/useLogger'
|
||||||
|
|
||||||
|
import HumanDuration from '~/components/common/HumanDuration.vue'
|
||||||
import EmbedWizard from '~/components/audio/EmbedWizard.vue'
|
import EmbedWizard from '~/components/audio/EmbedWizard.vue'
|
||||||
import SemanticModal from '~/components/semantic/Modal.vue'
|
import TrackFavoriteIcon from '~/components/favorites/TrackFavoriteIcon.vue'
|
||||||
import Loader from '~/components/ui/Loader.vue'
|
import Loader from '~/components/ui/Loader.vue'
|
||||||
import Button from '~/components/ui/Button.vue'
|
import Button from '~/components/ui/Button.vue'
|
||||||
import OptionsButton from '~/components/ui/button/Options.vue'
|
import OptionsButton from '~/components/ui/button/Options.vue'
|
||||||
|
@ -21,6 +23,9 @@ import RadioButton from '~/components/radios/Button.vue'
|
||||||
import Popover from '~/components/ui/Popover.vue'
|
import Popover from '~/components/ui/Popover.vue'
|
||||||
import PopoverItem from '~/components/ui/popover/PopoverItem.vue'
|
import PopoverItem from '~/components/ui/popover/PopoverItem.vue'
|
||||||
import TagsList from '~/components/tags/List.vue'
|
import TagsList from '~/components/tags/List.vue'
|
||||||
|
import Layout from '~/components/ui/Layout.vue'
|
||||||
|
import Modal from '~/components/ui/Modal.vue'
|
||||||
|
import Spacer from '~/components/ui/Spacer.vue'
|
||||||
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
@ -56,9 +61,10 @@ const cover = computed(() => object.value?.cover?.urls.original
|
||||||
? object.value.cover
|
? object.value.cover
|
||||||
: object.value?.albums.find(album => album.cover?.urls.original)?.cover
|
: object.value?.albums.find(album => album.cover?.urls.original)?.cover
|
||||||
)
|
)
|
||||||
const headerStyle = computed(() => cover.value?.urls.original
|
const imageUrl = computed(() => cover.value?.urls.original
|
||||||
? { backgroundImage: `url(${store.getters['instance/absoluteUrl'](cover.value.urls.original)})` }
|
? store.getters['instance/absoluteUrl']
|
||||||
: ''
|
(cover.value.urls.original)
|
||||||
|
: `${import.meta.env.BASE_URL}embed-default-cover.jpeg`
|
||||||
)
|
)
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
@ -95,75 +101,58 @@ const fetchData = async () => {
|
||||||
isLoading.value = false
|
isLoading.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const totalDuration = computed(() => sum((tracks.value ?? []).map(track => track.uploads[0]?.duration ?? 0)))
|
||||||
|
|
||||||
watch(() => props.id, fetchData, { immediate: true })
|
watch(() => props.id, fetchData, { immediate: true })
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main v-title="labels.title">
|
<Layout stack main>
|
||||||
<Loader v-if="isLoading" />
|
<Loader v-if="isLoading" />
|
||||||
<template v-if="object && !isLoading">
|
<template v-if="object && !isLoading">
|
||||||
<section
|
<Layout flex>
|
||||||
v-title="object.name"
|
<img
|
||||||
:class="['ui', 'head', {'with-background': cover}, 'vertical', 'center', 'aligned']"
|
v-lazy="imageUrl"
|
||||||
:style="headerStyle"
|
:alt="object.name"
|
||||||
|
class="channel-image"
|
||||||
>
|
>
|
||||||
<div class="segment-content">
|
<Layout stack style="flex: 1; gap: 8px;">
|
||||||
<h2 class="ui center aligned icon header">
|
<h1>{{ object.name }}</h1>
|
||||||
<i class="circular inverted users violet icon" />
|
<Layout flex class="meta" style="gap: 0;">
|
||||||
<div class="content">
|
|
||||||
{{ object.name }}
|
|
||||||
<div
|
<div
|
||||||
v-if="albums"
|
v-if="albums"
|
||||||
class="sub header"
|
|
||||||
>
|
>
|
||||||
{{ t('components.library.ArtistBase.meta.tracks', totalTracks) }}
|
{{ t('components.library.ArtistBase.meta.tracks', totalTracks) }}
|
||||||
{{ t('components.library.ArtistBase.meta.albums', totalAlbums) }}
|
{{ t('components.library.ArtistBase.meta.albums', totalAlbums) }}
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="totalDuration > 0">
|
||||||
|
<i class="bi bi-dot" />
|
||||||
|
<human-duration
|
||||||
|
v-if="totalDuration > 0"
|
||||||
|
:duration="totalDuration"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</h2>
|
|
||||||
<TagsList
|
<TagsList
|
||||||
v-if="object.tags && object.tags.length > 0"
|
v-if="object.tags && object.tags.length > 0"
|
||||||
:tags="object.tags"
|
:tags="object.tags"
|
||||||
/>
|
/>
|
||||||
<div class="ui hidden divider" />
|
</Layout>
|
||||||
<div class="header-buttons">
|
<Spacer />
|
||||||
<div class="ui buttons">
|
<Layout flex>
|
||||||
<radio-button
|
|
||||||
type="artist"
|
|
||||||
:object-id="object.id"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="ui buttons">
|
|
||||||
<PlayButton
|
<PlayButton
|
||||||
:is-playable="isPlayable"
|
:is-playable="isPlayable"
|
||||||
|
split
|
||||||
class="vibrant"
|
class="vibrant"
|
||||||
:artist="object"
|
:artist="object"
|
||||||
>
|
>
|
||||||
{{ t('components.library.ArtistBase.button.play') }}
|
{{ t('components.library.ArtistBase.button.play') }}
|
||||||
</PlayButton>
|
</PlayButton>
|
||||||
</div>
|
<radio-button
|
||||||
|
|
||||||
<semantic-modal
|
|
||||||
v-if="publicLibraries.length > 0"
|
|
||||||
v-model:show="showEmbedModal"
|
|
||||||
>
|
|
||||||
<h4 class="header">
|
|
||||||
{{ t('components.library.ArtistBase.modal.embed.header') }}
|
|
||||||
</h4>
|
|
||||||
<div class="scrolling content">
|
|
||||||
<div class="description">
|
|
||||||
<embed-wizard
|
|
||||||
:id="object.id"
|
|
||||||
type="artist"
|
type="artist"
|
||||||
|
:object-id="object.id"
|
||||||
/>
|
/>
|
||||||
</div>
|
<!-- TODO: Add artist to (Track)FavoriteIcon -->
|
||||||
</div>
|
<TrackFavoriteIcon v-if="store.state.auth.authenticated" :artist="object" />
|
||||||
<div class="actions">
|
|
||||||
<Button color="secondary">
|
|
||||||
{{ t('components.library.ArtistBase.button.cancel') }}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</semantic-modal>
|
|
||||||
<Popover>
|
<Popover>
|
||||||
<template #default="{ toggleOpen }">
|
<template #default="{ toggleOpen }">
|
||||||
<OptionsButton
|
<OptionsButton
|
||||||
|
@ -184,7 +173,7 @@ watch(() => props.id, fetchData, { immediate: true })
|
||||||
|
|
||||||
<PopoverItem
|
<PopoverItem
|
||||||
v-if="publicLibraries.length > 0"
|
v-if="publicLibraries.length > 0"
|
||||||
@click="showEmbedModal = !showEmbedModal"
|
@click="showEmbedModal = true"
|
||||||
>
|
>
|
||||||
<i class="bi bi-code-square" />
|
<i class="bi bi-code-square" />
|
||||||
{{ t('components.library.ArtistBase.button.embed') }}
|
{{ t('components.library.ArtistBase.button.embed') }}
|
||||||
|
@ -257,9 +246,26 @@ watch(() => props.id, fetchData, { immediate: true })
|
||||||
</PopoverItem>
|
</PopoverItem>
|
||||||
</template>
|
</template>
|
||||||
</Popover>
|
</Popover>
|
||||||
</div>
|
</Layout>
|
||||||
</div>
|
</Layout>
|
||||||
</section>
|
</Layout>
|
||||||
|
|
||||||
|
<Modal
|
||||||
|
v-if="publicLibraries.length > 0"
|
||||||
|
v-model="showEmbedModal"
|
||||||
|
:title="t('components.library.ArtistBase.modal.embed.header')"
|
||||||
|
>
|
||||||
|
<embed-wizard
|
||||||
|
:id="object.id"
|
||||||
|
type="artist"
|
||||||
|
/>
|
||||||
|
<template #actions>
|
||||||
|
<Button secondary>
|
||||||
|
{{ t('components.library.ArtistBase.button.cancel') }}
|
||||||
|
</Button>
|
||||||
|
</template>
|
||||||
|
</Modal>
|
||||||
|
<hr>
|
||||||
<router-view
|
<router-view
|
||||||
:key="route.fullPath"
|
:key="route.fullPath"
|
||||||
:tracks="tracks"
|
:tracks="tracks"
|
||||||
|
@ -272,5 +278,5 @@ watch(() => props.id, fetchData, { immediate: true })
|
||||||
@libraries-loaded="libraries = $event"
|
@libraries-loaded="libraries = $event"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</main>
|
</Layout>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -11,8 +11,16 @@ import axios from 'axios'
|
||||||
|
|
||||||
import LibraryWidget from '~/components/federation/LibraryWidget.vue'
|
import LibraryWidget from '~/components/federation/LibraryWidget.vue'
|
||||||
import TrackTable from '~/components/audio/track/Table.vue'
|
import TrackTable from '~/components/audio/track/Table.vue'
|
||||||
|
import TagsList from '~/components/tags/List.vue'
|
||||||
import AlbumCard from '~/components/album/Card.vue'
|
import AlbumCard from '~/components/album/Card.vue'
|
||||||
|
import Layout from '~/components/ui/Layout.vue'
|
||||||
|
import Section from '~/components/ui/Section.vue'
|
||||||
|
import Heading from "~/components/ui/Heading.vue";
|
||||||
|
import Loader from '~/components/ui/Loader.vue'
|
||||||
|
import Link from '~/components/ui/Link.vue'
|
||||||
|
import Spacer from '~/components/ui/Spacer.vue'
|
||||||
import Button from '~/components/ui/Button.vue'
|
import Button from '~/components/ui/Button.vue'
|
||||||
|
import Alert from '~/components/ui/Alert.vue'
|
||||||
|
|
||||||
import useErrorHandler from '~/composables/useErrorHandler'
|
import useErrorHandler from '~/composables/useErrorHandler'
|
||||||
|
|
||||||
|
@ -62,87 +70,71 @@ const loadMoreAlbums = async () => {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-if="object">
|
<Layout stack v-if="object">
|
||||||
<div
|
<TagsList
|
||||||
|
v-if="object.tags && object.tags.length > 0"
|
||||||
|
:tags="object.tags"
|
||||||
|
/>
|
||||||
|
<Alert blue
|
||||||
v-if="contentFilter"
|
v-if="contentFilter"
|
||||||
class="ui small text container"
|
|
||||||
>
|
>
|
||||||
<div class="ui hidden divider" />
|
|
||||||
<div class="ui message">
|
|
||||||
<p>
|
<p>
|
||||||
{{ t('components.library.ArtistDetail.message.filter') }}
|
{{ t('components.library.ArtistDetail.message.filter') }}
|
||||||
</p>
|
</p>
|
||||||
<router-link
|
<Link
|
||||||
class="right floated"
|
class="right floated"
|
||||||
:to="{name: 'settings'}"
|
:to="{name: 'settings'}"
|
||||||
>
|
>
|
||||||
{{ t('components.library.ArtistDetail.link.filter') }}
|
{{ t('components.library.ArtistDetail.link.filter') }}
|
||||||
</router-link>
|
</Link>
|
||||||
<Button
|
<Button
|
||||||
class="tiny"
|
class="tiny"
|
||||||
@click="store.dispatch('moderation/deleteContentFilter', contentFilter.uuid)"
|
@click="store.dispatch('moderation/deleteContentFilter', contentFilter.uuid)"
|
||||||
>
|
>
|
||||||
{{ t('components.library.ArtistDetail.button.filter') }}
|
{{ t('components.library.ArtistDetail.button.filter') }}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</Alert>
|
||||||
</div>
|
<Loader v-if="isLoadingAlbums" />
|
||||||
<section
|
<template v-else-if="albums && albums.length > 0">
|
||||||
v-if="isLoadingAlbums"
|
<Heading h2 section-heading>
|
||||||
class="ui vertical stripe segment"
|
|
||||||
>
|
|
||||||
<div :class="['ui', 'centered', 'active', 'inline', 'loader']" />
|
|
||||||
</section>
|
|
||||||
<section
|
|
||||||
v-else-if="albums && albums.length > 0"
|
|
||||||
class="ui vertical stripe segment"
|
|
||||||
>
|
|
||||||
<h2>
|
|
||||||
{{ t('components.library.ArtistDetail.header.album') }}
|
{{ t('components.library.ArtistDetail.header.album') }}
|
||||||
</h2>
|
</Heading>
|
||||||
<div class="ui cards app-cards">
|
<Layout flex>
|
||||||
<album-card
|
<album-card
|
||||||
v-for="album in allAlbums"
|
v-for="album in allAlbums"
|
||||||
:key="album.id"
|
:key="album.id"
|
||||||
:album="album"
|
:album="album"
|
||||||
/>
|
/>
|
||||||
</div>
|
<Spacer h grow />
|
||||||
<div class="ui hidden divider" />
|
|
||||||
<Button
|
<Button
|
||||||
v-if="loadMoreAlbumsUrl !== null"
|
v-if="loadMoreAlbumsUrl !== null"
|
||||||
|
primary
|
||||||
:is-loading="isLoadingMoreAlbums"
|
:is-loading="isLoadingMoreAlbums"
|
||||||
@click="loadMoreAlbums()"
|
@click="loadMoreAlbums()"
|
||||||
>
|
>
|
||||||
{{ t('components.library.ArtistDetail.button.more') }}
|
{{ t('components.library.ArtistDetail.button.more') }}
|
||||||
</Button>
|
</Button>
|
||||||
</section>
|
</Layout>
|
||||||
<section
|
</template>
|
||||||
v-if="tracks.length > 0"
|
<template v-if="tracks.length > 0">
|
||||||
class="ui vertical stripe segment"
|
<Heading h2 section-heading>
|
||||||
>
|
{{ t('components.library.ArtistDetail.header.track') }}
|
||||||
<track-table
|
</Heading>
|
||||||
|
<TrackTable
|
||||||
:is-artist="true"
|
:is-artist="true"
|
||||||
:show-position="false"
|
:show-position="false"
|
||||||
:track-only="true"
|
:track-only="true"
|
||||||
:tracks="tracks.slice(0,5)"
|
:tracks="tracks.slice(0,5)"
|
||||||
>
|
/>
|
||||||
<template #header>
|
|
||||||
<h2>
|
|
||||||
{{ t('components.library.ArtistDetail.header.track') }}
|
|
||||||
</h2>
|
|
||||||
<div class="ui hidden divider" />
|
|
||||||
</template>
|
</template>
|
||||||
</track-table>
|
<Heading h2 section-heading>
|
||||||
</section>
|
|
||||||
<section class="ui vertical stripe segment">
|
|
||||||
<h2>
|
|
||||||
{{ t('components.library.ArtistDetail.header.library') }}
|
{{ t('components.library.ArtistDetail.header.library') }}
|
||||||
</h2>
|
</Heading>
|
||||||
<library-widget
|
<LibraryWidget
|
||||||
:url="'artists/' + object.id + '/libraries/'"
|
:url="'artists/' + object.id + '/libraries/'"
|
||||||
@loaded="emit('libraries-loaded', $event)"
|
@loaded="emit('libraries-loaded', $event)"
|
||||||
>
|
>
|
||||||
{{ t('components.library.ArtistDetail.description.library') }}
|
{{ t('components.library.ArtistDetail.description.library') }}
|
||||||
</library-widget>
|
</LibraryWidget>
|
||||||
</section>
|
</Layout>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import ArtistCard from '~/components/artist/Card.vue'
|
|
||||||
import type { OrderingProps } from '~/composables/navigation/useOrdering'
|
import type { OrderingProps } from '~/composables/navigation/useOrdering'
|
||||||
import type { Artist, BackendResponse } from '~/types'
|
import type { Artist, BackendResponse } from '~/types'
|
||||||
import type { RouteRecordName } from 'vue-router'
|
import type { RouteRecordName } from 'vue-router'
|
||||||
|
@ -14,7 +13,9 @@ import { useStore } from '~/store'
|
||||||
|
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
|
|
||||||
|
import ArtistCard from '~/components/artist/Card.vue'
|
||||||
import Pagination from '~/components/ui/Pagination.vue'
|
import Pagination from '~/components/ui/Pagination.vue'
|
||||||
|
import Card from '~/components/ui/Card.vue'
|
||||||
import Layout from '~/components/ui/Layout.vue'
|
import Layout from '~/components/ui/Layout.vue'
|
||||||
import Input from '~/components/ui/Input.vue'
|
import Input from '~/components/ui/Input.vue'
|
||||||
import Toggle from '~/components/ui/Toggle.vue'
|
import Toggle from '~/components/ui/Toggle.vue'
|
||||||
|
@ -199,11 +200,11 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value]
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
/>
|
/>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
<Loader v-if="isLoading"/>
|
||||||
<Layout grid
|
<Layout grid
|
||||||
v-if="result && result.results.length > 0"
|
v-if="result && result.results.length > 0"
|
||||||
style="display:flex; flex-wrap:wrap; gap: 32px; margin-top:32px;"
|
style="display:flex; flex-wrap:wrap; gap: 32px; margin-top:32px;"
|
||||||
>
|
>
|
||||||
<Loader v-if="isLoading"/>
|
|
||||||
<ArtistCard
|
<ArtistCard
|
||||||
v-for="artist in result.results"
|
v-for="artist in result.results"
|
||||||
:key="artist.id"
|
:key="artist.id"
|
||||||
|
|
Loading…
Reference in New Issue