feat(style): consistent look and use of artist credit label on detail pages

This commit is contained in:
ArneBo 2025-02-23 00:26:33 +01:00
parent 75d4ac5467
commit 966181e480
5 changed files with 366 additions and 415 deletions

View File

@ -2,7 +2,7 @@
import type { ArtistCredit } from '~/types' import type { ArtistCredit } from '~/types'
import { useStore } from '~/store' import { useStore } from '~/store'
import Link from '~/components/ui/Link.vue' import Pill from '~/components/ui/Pill.vue'
const store = useStore() const store = useStore()
@ -23,31 +23,28 @@ const getRoute = (ac: ArtistCredit) => {
</script> </script>
<template> <template>
<div class="artist-label ui image label"> <template
<template v-for="ac in props.artistCredit"
v-for="ac in props.artistCredit" :key="ac.artist.id"
:key="ac.artist.id" >
<router-link
:to="{name: 'library.artists.detail', params: {id: ac.artist.id }}"
@click.stop.prevent=""
> >
<Link <Pill>
solid <template #image>
secondary <img
round v-if="ac.artist.cover && ac.artist.cover.urls.original"
min-content v-lazy="store.getters['instance/absoluteUrl'](ac.artist.cover.urls.small_square_crop)"
:to="getRoute(ac)" :alt="ac.artist.name"
> />
<img <i
v-if="ac.index === 0 && ac.artist.cover && ac.artist.cover.urls.original" v-else
v-lazy="store.getters['instance/absoluteUrl'](ac.artist.cover.urls.medium_square_crop)" class="bi bi-person-circle"
alt="" />
:class="[{circular: ac.artist.content_category != 'podcast'}]" </template>
>
<i
v-else-if="ac.index === 0"
:class="[ac.artist.content_category != 'podcast' ? 'circular' : 'bordered', 'inverted violet bi bi-users icon']"
/>
{{ ac.credit }} {{ ac.credit }}
</Link> </Pill>
<span>{{ ac.joinphrase }}</span> </router-link>
</template> </template>
</div>
</template> </template>

View File

@ -69,7 +69,7 @@ const labels = computed(() => ({
const { const {
isShuffled, isShuffled,
shuffle shuffle,
} = useQueue() } = useQueue()
const isLoading = ref(false) const isLoading = ref(false)
@ -150,10 +150,7 @@ const remove = async () => {
</script> </script>
<template> <template>
<Layout <Layout stack main>
stack
main
>
<Loader <Loader
v-if="isLoading" v-if="isLoading"
v-title="labels.title" v-title="labels.title"
@ -165,129 +162,105 @@ const remove = async () => {
v-lazy="store.getters['instance/absoluteUrl'](object.cover.urls.large_square_crop)" v-lazy="store.getters['instance/absoluteUrl'](object.cover.urls.large_square_crop)"
:alt="object.title" :alt="object.title"
class="channel-image" class="channel-image"
> />
<img
v-else-if="object.artist_credit && object.artist_credit[0] && object.artist_credit[0].artist.cover"
v-lazy="object.artist_credit[0].artist.cover.urls.large_square_crop"
:alt="object.artist_credit[0].artist.name"
class="channel-image"
>
<img <img
v-else v-else
alt="" alt=""
class="channel-image" class="channel-image"
src="../../assets/audio/default-cover.png" src="../../assets/audio/default-cover.png"
> />
<!-- ({target}) => target --> <Layout stack style="flex: 1; gap: 8px;">
<!-- Header (TODO: Put into Header component) Hint: Header is heavier fontweight than h1! --> <h1 style="margin-top: 64px; margin-bottom: 8px;">{{ object.title }}</h1>
<Layout <artist-credit-label
stack v-if="artistCredit"
style="flex: 1; gap: 8px;"
>
<h1>{{ object.title }}</h1>
<!-- <Header :h1="object.title" /> -->
<artist-credit-label
v-if="artistCredit"
:artist-credit="artistCredit"
/>
<!-- Metadata: -->
<div class="meta">
<template v-if="object.release_date">
{{ momentFormat(new Date(object.release_date ?? '1970-01-01'), 'Y') }}
<i class="bi bi-dot" />
</template>
<template v-if="totalTracks > 0">
<span v-if="isSerie">
{{ t('components.library.AlbumBase.meta.episodes', totalTracks) }}
</span>
<span v-else>
{{ t('components.library.AlbumBase.meta.tracks', totalTracks) }}
</span>
</template>
<i
v-if="totalDuration > 0"
class="bi bi-dot"
/>
<human-duration
v-if="totalDuration > 0"
:duration="totalDuration"
/>
<!--TODO: License -->
</div>
<Layout flex>
<rendered-description
v-if="object.description"
:content="object.description"
:can-update="true"
/>
</Layout>
<Layout flex>
<PlayButton
split
:tracks="object.tracks"
:is-playable="object.is_playable"
/>
<Button
v-if="object.tracks.length > 2"
primary
icon="bi-shuffle"
:aria-label="labels.shuffle"
@click.prevent.stop="shuffle()"
>
{{ labels.shuffle }}
</Button>
<DangerousButton
v-if="artistCredit[0] &&
store.state.auth.authenticated &&
artistCredit[0].artist.channel &&
artistCredit[0].artist.attributed_to?.full_username === store.state.auth.fullUsername"
:is-loading="isLoading"
icon="bi-trash"
@confirm="remove()"
>
{{ t('components.library.AlbumDropdown.button.delete') }}
</DangerousButton>
<Spacer
h
grow
/>
<TrackFavoriteIcon
v-if="store.state.auth.authenticated"
:album="object"
/>
<TrackPlaylistIcon
v-if="store.state.auth.authenticated"
:album="object"
/>
<!-- TODO: Share Button -->
<album-dropdown
:object="object"
:public-libraries="publicLibraries"
:is-loading="isLoading"
:is-album="isAlbum"
:is-serie="isSerie"
:is-channel="isChannel"
:artist-credit="artistCredit" :artist-credit="artistCredit"
@remove="remove"
/> />
<!-- Metadata: -->
<div class="meta">
<template v-if="object.release_date">
{{ momentFormat(new Date(object.release_date ?? '1970-01-01'), 'Y') }}
<i class="bi bi-dot" />
</template>
<template v-if="totalTracks > 0">
<span v-if="isSerie">
{{ t('components.library.AlbumBase.meta.episodes', totalTracks) }}
</span>
<span v-else>
{{ t('components.library.AlbumBase.meta.tracks', totalTracks) }}
</span>
</template>
<i v-if="totalDuration > 0" class="bi bi-dot" />
<human-duration
v-if="totalDuration > 0"
:duration="totalDuration"
/>
<!--TODO: License -->
</div>
<Layout flex>
<rendered-description
v-if="object.description"
:content="object.description"
:can-update="true"
/>
</Layout>
<Layout flex>
<PlayButton
split
:tracks="object.tracks"
:is-playable="object.is_playable"
/>
<Button
v-if="object.tracks.length > 2"
primary
icon="bi-shuffle"
:aria-label="labels.shuffle"
@click.prevent.stop="shuffle()"
>
{{ labels.shuffle }}
</Button>
<DangerousButton
v-if="artistCredit[0] &&
store.state.auth.authenticated &&
artistCredit[0].artist.channel &&
artistCredit[0].artist.attributed_to?.full_username === store.state.auth.fullUsername"
:is-loading="isLoading"
@confirm="remove()"
icon="bi-trash"
>
{{ t('components.library.AlbumDropdown.button.delete') }}
</DangerousButton>
<Spacer h grow />
<TrackFavoriteIcon v-if="store.state.auth.authenticated" :album="object" />
<TrackPlaylistIcon v-if="store.state.auth.authenticated" :album="object" />
<!-- TODO: Share Button -->
<album-dropdown
:object="object"
:public-libraries="publicLibraries"
:is-loading="isLoading"
:is-album="isAlbum"
:is-serie="isSerie"
:is-channel="isChannel"
:artist-credit="artistCredit"
@remove="remove"
/>
</Layout>
</Layout> </Layout>
</Layout>
</Layout> </Layout>
<div style="flex 1;"> <div style="flex 1;">
<router-view <router-view
v-if="object" v-if="object"
:key="route.fullPath" :key="route.fullPath"
:paginate-by="paginateBy" :paginate-by="paginateBy"
:total-tracks="totalTracks" :total-tracks="totalTracks"
:is-serie="isSerie" :is-serie="isSerie"
:artist-credit="artistCredit" :artist-credit="artistCredit"
:object="object" :object="object"
:is-loading-tracks="isLoadingTracks" :is-loading-tracks="isLoadingTracks"
object-type="album" object-type="album"
@libraries-loaded="libraries = $event" @libraries-loaded="libraries = $event"
/> />
</div> </div>
</template> </template>
</Layout> </Layout>
</template> </template>

View File

@ -25,6 +25,7 @@ import Layout from '~/components/ui/Layout.vue'
import Modal from '~/components/ui/Modal.vue' import Modal from '~/components/ui/Modal.vue'
import Spacer from '~/components/ui/Spacer.vue' import Spacer from '~/components/ui/Spacer.vue'
interface Props { interface Props {
id: number id: number
} }
@ -55,7 +56,8 @@ const musicbrainzUrl = computed(() => object.value?.mbid ? `https://musicbrainz.
const discogsUrl = computed(() => `https://discogs.com/search/?type=artist&title=${encodeURI(object.value?.name ?? '')}`) const discogsUrl = computed(() => `https://discogs.com/search/?type=artist&title=${encodeURI(object.value?.name ?? '')}`)
const publicLibraries = computed(() => libraries.value?.filter(library => library.privacy_level === 'everyone') ?? []) const publicLibraries = computed(() => libraries.value?.filter(library => library.privacy_level === 'everyone') ?? [])
const cover = computed(() => {
const cover = computed(() => {
const artistCover: Cover | undefined = object.value?.cover const artistCover: Cover | undefined = object.value?.cover
const albumCover: Cover | undefined = object.value?.albums const albumCover: Cover | undefined = object.value?.albums
@ -66,12 +68,12 @@ const cover = computed(() => {
)?.cover )?.cover
const fallback : Cover = { const fallback : Cover = {
uuid: '', uuid: '',
urls: { urls: {
original: `${import.meta.env.BASE_URL}embed-default-cover.jpeg`, original: `${import.meta.env.BASE_URL}embed-default-cover.jpeg`,
medium_square_crop: `${import.meta.env.BASE_URL}embed-default-cover.jpeg`, medium_square_crop: `${import.meta.env.BASE_URL}embed-default-cover.jpeg`,
large_square_crop: `${import.meta.env.BASE_URL}embed-default-cover.jpeg` large_square_crop: `${import.meta.env.BASE_URL}embed-default-cover.jpeg`
} }
} }
return artistCover return artistCover
@ -120,11 +122,7 @@ watch(() => props.id, fetchData, { immediate: true })
</script> </script>
<template> <template>
<Layout <Layout stack main v-title="labels.title">
v-title="labels.title"
stack
main
>
<Loader v-if="isLoading" /> <Loader v-if="isLoading" />
<template v-if="object && !isLoading"> <template v-if="object && !isLoading">
<Layout flex> <Layout flex>
@ -133,16 +131,9 @@ watch(() => props.id, fetchData, { immediate: true })
:alt="object.name" :alt="object.name"
class="channel-image" class="channel-image"
> >
<Layout <Layout stack style="flex: 1; gap: 8px;">
stack <h1 style="margin-top: 64px; margin-bottom: 8px;">{{ object.name }}</h1>
style="flex: 1; gap: 8px;" <Layout flex class="meta" style="gap: 0;">
>
<h1>{{ object.name }}</h1>
<Layout
flex
class="meta"
style="gap: 0;"
>
<div <div
v-if="albums" v-if="albums"
> >
@ -190,8 +181,8 @@ watch(() => props.id, fetchData, { immediate: true })
<PopoverItem <PopoverItem
v-if="publicLibraries.length > 0" v-if="publicLibraries.length > 0"
icon="bi-code-square"
@click="showEmbedModal = true" @click="showEmbedModal = true"
icon="bi-code-square"
> >
{{ t('components.library.ArtistBase.button.embed') }} {{ t('components.library.ArtistBase.button.embed') }}
</PopoverItem> </PopoverItem>

View File

@ -11,6 +11,7 @@ import { useStore } from '~/store'
import axios from 'axios' import axios from 'axios'
import ActorLink from '~/components/common/ActorLink.vue' import ActorLink from '~/components/common/ActorLink.vue'
import ArtistCreditLabel from '~/components/audio/ArtistCreditLabel.vue'
import TrackFavoriteIcon from '~/components/favorites/TrackFavoriteIcon.vue' import TrackFavoriteIcon from '~/components/favorites/TrackFavoriteIcon.vue'
import TrackPlaylistIcon from '~/components/playlists/TrackPlaylistIcon.vue' import TrackPlaylistIcon from '~/components/playlists/TrackPlaylistIcon.vue'
import EmbedWizard from '~/components/audio/EmbedWizard.vue' import EmbedWizard from '~/components/audio/EmbedWizard.vue'
@ -76,6 +77,8 @@ const attributedToUrl = computed(() => router.resolve({
} }
})?.href) })?.href)
const artistCredit = track.value?.artist_credit
const totalDuration = computed(() => track.value?.uploads[0]?.duration ?? 0) const totalDuration = computed(() => track.value?.uploads[0]?.duration ?? 0)
const { t } = useI18n() const { t } = useI18n()
@ -126,271 +129,253 @@ watch(showDeleteModal, (newValue) => {
</script> </script>
<template> <template>
<Layout <Layout stack main>
stack <Loader
main v-if="isLoading"
> v-title="labels.title"
<Loader />
v-if="isLoading" <template v-if="track">
v-title="labels.title" <Layout flex>
/> <img
<template v-if="track"> v-if="track.cover"
<Layout flex> v-lazy="store.getters['instance/absoluteUrl'](track.cover.urls.large_square_crop)"
<img alt=""
v-if="track.cover" class="channel-image"
v-lazy="store.getters['instance/absoluteUrl'](track.cover.urls.large_square_crop)" >
alt="" <img
class="channel-image" v-if="track.album && track.album.cover"
> v-lazy="store.getters['instance/absoluteUrl'](track.album.cover.urls.large_square_crop)"
<img alt=""
v-if="track.album && track.album.cover" class="channel-image"
v-lazy="store.getters['instance/absoluteUrl'](track.album.cover.urls.large_square_crop)" >
alt="" <img
class="channel-image" v-else
> alt=""
<img class="channel-image"
v-else src="../../assets/audio/default-cover.png"
alt="" >
class="channel-image"
src="../../assets/audio/default-cover.png"
>
<Layout <Layout stack style="flex: 1; gap: 8px;">
stack <Layout flex no-gap style="align-items: baseline;">
style="flex: 1; gap: 8px;" <h1 style="margin-top: 64px; margin-bottom: 8px;">{{ track.title }}</h1>
> <Spacer grow />
<Layout <Button
flex v-if="upload"
no-gap :aria-label="labels.download"
style="align-items: baseline; margin-bottom: 24px;" :to="downloadUrl"
target="_blank"
primary
icon="bi-download"
:title="labels.download"
> >
<h1>{{ track.title }}</h1> {{ labels.download }}
<Spacer grow /> </Button>
<Button </Layout>
v-if="upload" <artist-credit-label
:aria-label="labels.download" :artist-credit="track.artist_credit"
:to="downloadUrl" />
target="_blank" <div class="meta">
primary
icon="bi-download"
:title="labels.download"
>
{{ labels.download }}
</Button>
</Layout>
<div class="meta">
<span>{{ t('components.library.TrackBase.title') }}</span> <span>{{ t('components.library.TrackBase.title') }}</span>
<i class="bi bi-dot" /> <i class="bi bi-dot" />
<span>{{ track.album.title }}</span> <span>{{ track.album.title }}</span>
<i <i v-if="totalDuration > 0" class="bi bi-dot" />
v-if="totalDuration > 0"
class="bi bi-dot"
/>
<human-duration <human-duration
v-if="totalDuration > 0" v-if="totalDuration > 0"
:duration="totalDuration" :duration="totalDuration"
/> />
</div> </div>
<Layout flex> <Layout flex>
<PlayButton <PlayButton
:is-playable="track.is_playable" :is-playable="track.is_playable"
class="vibrant" class="vibrant"
split split
:track="track" :track="track"
/>
<Spacer
h
grow
/>
<TrackFavoriteIcon
v-if="store.state.auth.authenticated"
:track="track"
/>
<TrackPlaylistIcon
v-if="store.state.auth.authenticated"
:track="track"
/>
<Popover v-model:open="open">
<template #default="{ toggleOpen }">
<OptionsButton @click="toggleOpen" />
</template>
<template #items>
<PopoverItem
v-if="domain != store.getters['instance/domain']"
:to="track.fid"
target="_blank"
icon="bi-box-arrow-up-right"
>
{{ t('components.library.TrackBase.link.domain', { domain }) }}
</PopoverItem>
<PopoverItem
v-if="isEmbedable"
icon="bi-code-slash"
@click="showEmbedModal = !showEmbedModal"
>
{{ t('components.library.TrackBase.button.embed') }}
</PopoverItem>
<PopoverItem
:to="wikipediaUrl"
target="_blank"
rel="noreferrer noopener"
icon="bi-wikipedia"
>
{{ t('components.library.TrackBase.link.wikipedia') }}
</PopoverItem>
<PopoverItem
v-if="discogsUrl"
:to="discogsUrl"
target="_blank"
rel="noreferrer noopener"
icon="bi-box-arrow-up-right"
>
{{ t('components.library.TrackBase.link.discogs') }}
</PopoverItem>
<PopoverItem
v-if="track.is_local"
icon="bi-pencil-fill"
:to="{ name: 'library.tracks.edit', params: { id: track.id } }"
>
{{ t('components.library.TrackBase.button.edit') }}
</PopoverItem>
<PopoverItem
v-if="artist &&
store.state.auth.authenticated &&
artist.channel &&
artist.attributed_to.full_username === store.state.auth.fullUsername"
icon="bi-trash"
@click="showDeleteModal = true"
>
{{ t('components.library.TrackBase.button.delete') }}
</PopoverItem>
<hr>
<PopoverItem
v-for="obj in getReportableObjects({ track })"
:key="obj.target.type + obj.target.id"
icon="bi-flag"
@click="report(obj)"
>
{{ obj.label }}
</PopoverItem>
<hr>
<PopoverItem
v-if="store.state.auth.availablePermissions['library']"
:to="{
name: 'manage.library.tracks.detail',
params: { id: track.id }
}"
icon="bi-wrench"
>
{{ t('components.library.TrackBase.link.moderation') }}
</PopoverItem>
<PopoverItem
v-if="store.state.auth.profile?.is_superuser"
:to="store.getters['instance/absoluteUrl'](`/api/admin/music/track/${track.id}`)"
target="_blank"
rel="noopener noreferrer"
icon="bi-wrench"
>
{{ t('components.library.TrackBase.link.django') }}
</PopoverItem>
</template>
</Popover>
</Layout>
</Layout>
</Layout>
<hr>
<Layout flex>
<div>
<span v-if="track.attributed_to">
{{ t('components.library.TrackBase.subtitle.with-uploader') }}
</span>
<span v-else>
{{ t('components.library.TrackBase.subtitle.without-uploader') }}
</span>
<ActorLink
v-if="track.attributed_to"
:actor="track.attributed_to"
:avatar="false"
/> />
<time <Spacer h grow />
:title="track.creation_date"
:datetime="track.creation_date"
>
{{ momentFormat(new Date(track.creation_date), 'LL') }}
</time>
</div>
</Layout>
<Modal <TrackFavoriteIcon v-if="store.state.auth.authenticated" :track="track" />
v-if="isEmbedable" <TrackPlaylistIcon v-if="store.state.auth.authenticated" :track="track" />
v-model="showEmbedModal" <Popover v-model:open="open">
:title="t('components.library.TrackBase.modal.embed.header')" <template #default="{ toggleOpen }">
> <OptionsButton @click="toggleOpen" />
<embed-wizard </template>
:id="track.id" <template #items>
type="track" <PopoverItem
v-if="domain != store.getters['instance/domain']"
:to="track.fid"
target="_blank"
icon="bi-box-arrow-up-right"
>
{{ t('components.library.TrackBase.link.domain', { domain }) }}
</PopoverItem>
<PopoverItem
v-if="isEmbedable"
@click="showEmbedModal = !showEmbedModal"
icon="bi-code-slash"
>
{{ t('components.library.TrackBase.button.embed') }}
</PopoverItem>
<PopoverItem
:to="wikipediaUrl"
target="_blank"
rel="noreferrer noopener"
icon="bi-wikipedia"
>
{{ t('components.library.TrackBase.link.wikipedia') }}
</PopoverItem>
<PopoverItem
v-if="discogsUrl"
:to="discogsUrl"
target="_blank"
rel="noreferrer noopener"
icon="bi-box-arrow-up-right"
>
{{ t('components.library.TrackBase.link.discogs') }}
</PopoverItem>
<PopoverItem
v-if="track.is_local"
icon="bi-pencil-fill"
:to="{ name: 'library.tracks.edit', params: { id: track.id } }"
>
{{ t('components.library.TrackBase.button.edit') }}
</PopoverItem>
<PopoverItem
v-if="artist &&
store.state.auth.authenticated &&
artist.channel &&
artist.attributed_to.full_username === store.state.auth.fullUsername"
@click="showDeleteModal = true"
icon="bi-trash"
>
{{ t('components.library.TrackBase.button.delete') }}
</PopoverItem>
<hr>
<PopoverItem
v-for="obj in getReportableObjects({ track })"
:key="obj.target.type + obj.target.id"
@click="report(obj)"
icon="bi-flag"
>
{{ obj.label }}
</PopoverItem>
<hr>
<PopoverItem
v-if="store.state.auth.availablePermissions['library']"
:to="{
name: 'manage.library.tracks.detail',
params: { id: track.id }
}"
icon="bi-wrench"
>
{{ t('components.library.TrackBase.link.moderation') }}
</PopoverItem>
<PopoverItem
v-if="store.state.auth.profile?.is_superuser"
:to="store.getters['instance/absoluteUrl'](`/api/admin/music/track/${track.id}`)"
target="_blank"
rel="noopener noreferrer"
icon="bi-wrench"
>
{{ t('components.library.TrackBase.link.django') }}
</PopoverItem>
</template>
</Popover>
</Layout>
</Layout>
</Layout>
<hr>
<Layout flex>
<div>
<span v-if="track.attributed_to">
{{ t('components.library.TrackBase.subtitle.with-uploader') }}
</span>
<span v-else>
{{ t('components.library.TrackBase.subtitle.without-uploader') }}
</span>
<ActorLink
v-if="track.attributed_to"
:actor="track.attributed_to"
:avatar="false"
/> />
<template #actions> <time
<Button :title="track.creation_date"
secondary :datetime="track.creation_date"
@click="showEmbedModal = false" >
> {{ momentFormat(new Date(track.creation_date), 'LL') }}
{{ t('components.library.TrackBase.button.cancel') }} </time>
</Button> </div>
</template> </Layout>
</Modal>
<Modal
v-model="showDeleteModal"
:title="t('components.library.TrackBase.modal.delete.header')"
destructive
>
<template #alert>
<Alert red>
{{ t('components.library.TrackBase.modal.delete.content.warning') }}
</Alert>
</template>
<template #actions> <Modal
<Button v-if="isEmbedable"
secondary v-model="showEmbedModal"
@click="showDeleteModal = false" :title="t('components.library.TrackBase.modal.embed.header')"
> >
{{ t('components.library.TrackBase.button.cancel') }} <embed-wizard
</Button> :id="track.id"
<Button type="track"
destructive
:is-loading="isLoading"
@click="remove()"
>
{{ t('components.library.TrackBase.button.delete') }}
</Button>
</template>
</Modal>
<router-view
v-if="track"
:key="route.fullPath"
:track="track"
:object="track"
object-type="track"
@libraries-loaded="libraries = $event"
/> />
</template>
<template #actions>
<Button
secondary
@click="showEmbedModal = false"
>
{{ t('components.library.TrackBase.button.cancel') }}
</Button>
</template>
</Modal>
<Modal
v-model="showDeleteModal"
:title="t('components.library.TrackBase.modal.delete.header')"
destructive
>
<template #alert>
<Alert red>
{{ t('components.library.TrackBase.modal.delete.content.warning') }}
</Alert>
</template>
<template #actions>
<Button
secondary
@click="showDeleteModal = false"
>
{{ t('components.library.TrackBase.button.cancel') }}
</Button>
<Button
destructive
:is-loading="isLoading"
@click="remove()"
>
{{ t('components.library.TrackBase.button.delete') }}
</Button>
</template>
</Modal>
<router-view
v-if="track"
:key="route.fullPath"
:track="track"
:object="track"
object-type="track"
@libraries-loaded="libraries = $event"
/>
</template>
</Layout> </Layout>
</template> </template>

View File

@ -35,6 +35,7 @@
overflow: hidden; overflow: hidden;
height: calc(2em - 4px); height: calc(2em - 4px);
margin: 2px; margin: 2px;
align-content: center;
+ .pill-content { + .pill-content {
padding-left: 0.25em; padding-left: 0.25em;
@ -45,6 +46,10 @@
width: 100%; width: 100%;
} }
> i.bi {
font-size: 18px;
}
> img { > img {
object-fit: cover; object-fit: cover;
} }