chore(front): new layout for artist detail

This commit is contained in:
ArneBo 2025-01-22 13:31:53 +01:00
parent d1c21fa86d
commit ebc92bf0a9
3 changed files with 140 additions and 141 deletions

View File

@ -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>

View File

@ -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>

View File

@ -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"