chore: slotify album/artist cards (#1545)

This commit is contained in:
Phan An 2022-10-23 19:52:07 +02:00 committed by GitHub
parent e527eccf03
commit c8dbdf9053
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 236 additions and 197 deletions

View file

@ -1,47 +1,43 @@
<template>
<article
<ArtistAlbumCard
v-if="showing"
:class="layout"
:entity="album"
:title="`${album.name} by ${album.artist_name}`"
class="item"
data-testid="album-card"
draggable="true"
tabindex="0"
:layout="layout"
@contextmenu="requestContextMenu"
@dblclick="shuffle"
@dragstart="onDragStart"
@contextmenu.prevent="requestContextMenu"
>
<AlbumThumbnail :entity="album"/>
<footer>
<a :href="`#/album/${album.id}`" class="name" data-testid="name">{{ album.name }}</a>
<a v-if="isStandardArtist" :href="`#/artist/${album.artist_id}`" class="artist">{{ album.artist_name }}</a>
<template #name>
<a :href="`#/album/${album.id}`" class="text-normal" data-testid="name">{{ album.name }}</a>
<a v-if="isStandardArtist" :href="`#/artist/${album.artist_id}`">{{ album.artist_name }}</a>
<span v-else class="text-secondary">{{ album.artist_name }}</span>
<p class="meta">
<a
:title="`Shuffle all songs in the album ${album.name}`"
class="shuffle-album"
data-testid="shuffle-album"
href
role="button"
@click.prevent="shuffle"
>
Shuffle
</a>
<a
v-if="allowDownload"
:title="`Download all songs in the album ${album.name}`"
class="download-album"
data-testid="download-album"
href
role="button"
@click.prevent="download"
>
Download
</a>
</p>
</footer>
</article>
</template>
<template #meta>
<a
:title="`Shuffle all songs in the album ${album.name}`"
class="shuffle-album"
data-testid="shuffle-album"
href
role="button"
@click.prevent="shuffle"
>
Shuffle
</a>
<a
v-if="allowDownload"
:title="`Download all songs in the album ${album.name}`"
class="download-album"
data-testid="download-album"
href
role="button"
@click.prevent="download"
>
Download
</a>
</template>
</ArtistAlbumCard>
</template>
<script lang="ts" setup>
@ -52,7 +48,7 @@ import { downloadService, playbackService } from '@/services'
import { useDraggable } from '@/composables'
import { RouterKey } from '@/symbols'
import AlbumThumbnail from '@/components/ui/AlbumArtistThumbnail.vue'
import ArtistAlbumCard from '@/components/ui/ArtistAlbumCard.vue'
const router = requireInjection(RouterKey)
@ -76,7 +72,3 @@ const download = () => downloadService.fromAlbum(album.value)
const onDragStart = (event: DragEvent) => startDragging(event, album.value)
const requestContextMenu = (event: MouseEvent) => eventBus.emit('ALBUM_CONTEXT_MENU_REQUESTED', event, album.value)
</script>
<style lang="scss" scoped>
@include artist-album-card();
</style>

View file

@ -1,9 +1,10 @@
// Vitest Snapshot v1
exports[`renders 1`] = `
<article class="full item" title="IV by Led Zeppelin" data-testid="album-card" draggable="true" tabindex="0" data-v-b204153b=""><span class="cover" data-testid="album-artist-thumbnail" data-v-e37470a2="" data-v-b204153b=""><a class="control control-play" href="" role="button" data-v-e37470a2=""><span class="hidden" data-v-e37470a2="">Play all songs in the album IV</span><span class="icon" data-v-e37470a2=""></span></a></span>
<footer data-v-b204153b=""><a href="#/album/42" class="name" data-testid="name" data-v-b204153b="">IV</a><a href="#/artist/17" class="artist" data-v-b204153b="">Led Zeppelin</a>
<p class="meta" data-v-b204153b=""><a title="Shuffle all songs in the album IV" class="shuffle-album" data-testid="shuffle-album" href="" role="button" data-v-b204153b=""> Shuffle </a><a title="Download all songs in the album IV" class="download-album" data-testid="download-album" href="" role="button" data-v-b204153b=""> Download </a></p>
<article class="item full" draggable="true" tabindex="0" title="IV by Led Zeppelin" data-v-f01bdc56=""><span class="cover" data-testid="album-artist-thumbnail" data-v-e37470a2="" data-v-f01bdc56=""><a class="control control-play" href="" role="button" data-v-e37470a2=""><span class="hidden" data-v-e37470a2="">Play all songs in the album IV</span><span class="icon" data-v-e37470a2=""></span></a></span>
<footer data-v-f01bdc56="">
<div class="name" data-v-f01bdc56=""><a href="#/album/42" class="text-normal" data-testid="name">IV</a><a href="#/artist/17">Led Zeppelin</a></div>
<p class="meta" data-v-f01bdc56=""><a title="Shuffle all songs in the album IV" class="shuffle-album" data-testid="shuffle-album" href="" role="button"> Shuffle </a><a title="Download all songs in the album IV" class="download-album" data-testid="download-album" href="" role="button"> Download </a></p>
</footer>
</article>
`;

View file

@ -1,47 +1,40 @@
<template>
<article
<ArtistAlbumCard
v-if="showing"
:class="layout"
:entity="artist"
:layout="layout"
:title="artist.name"
class="item"
data-testid="artist-card"
draggable="true"
tabindex="0"
@contextmenu="requestContextMenu"
@dblclick="shuffle"
@dragstart="onDragStart"
@contextmenu.prevent="requestContextMenu"
>
<ArtistThumbnail :entity="artist"/>
<footer>
<div class="info">
<a :href="`#/artist/${artist.id}`" class="name" data-testid="name">{{ artist.name }}</a>
</div>
<p class="meta">
<a
:title="`Shuffle all songs by ${artist.name}`"
class="shuffle-artist"
data-testid="shuffle-artist"
href
role="button"
@click.prevent="shuffle"
>
Shuffle
</a>
<a
v-if="allowDownload"
:title="`Download all songs by ${artist.name}`"
class="download-artist"
data-testid="download-artist"
href
role="button"
@click.prevent="download"
>
Download
</a>
</p>
</footer>
</article>
<template #name>
<a :href="`#/artist/${artist.id}`" class="text-normal" data-testid="name">{{ artist.name }}</a>
</template>
<template #meta>
<a
:title="`Shuffle all songs by ${artist.name}`"
class="shuffle-artist"
data-testid="shuffle-artist"
href
role="button"
@click.prevent="shuffle"
>
Shuffle
</a>
<a
v-if="allowDownload"
:title="`Download all songs by ${artist.name}`"
class="download-artist"
data-testid="download-artist"
href
role="button"
@click.prevent="download"
>
Download
</a>
</template>
</ArtistAlbumCard>
</template>
<script lang="ts" setup>
@ -52,7 +45,7 @@ import { downloadService, playbackService } from '@/services'
import { useDraggable } from '@/composables'
import { RouterKey } from '@/symbols'
import ArtistThumbnail from '@/components/ui/AlbumArtistThumbnail.vue'
import ArtistAlbumCard from '@/components/ui/ArtistAlbumCard.vue'
const router = requireInjection(RouterKey)
@ -74,7 +67,3 @@ const download = () => downloadService.fromArtist(artist.value)
const onDragStart = (event: DragEvent) => startDragging(event, artist.value)
const requestContextMenu = (event: MouseEvent) => eventBus.emit('ARTIST_CONTEXT_MENU_REQUESTED', event, artist.value)
</script>
<style lang="scss" scoped>
@include artist-album-card();
</style>

View file

@ -1,10 +1,10 @@
// Vitest Snapshot v1
exports[`renders 1`] = `
<article class="full item" title="Led Zeppelin" data-testid="artist-card" draggable="true" tabindex="0" data-v-85d5de45=""><span class="cover" data-testid="album-artist-thumbnail" data-v-e37470a2="" data-v-85d5de45=""><a class="control control-play" href="" role="button" data-v-e37470a2=""><span class="hidden" data-v-e37470a2="">Play all songs by Led Zeppelin</span><span class="icon" data-v-e37470a2=""></span></a></span>
<footer data-v-85d5de45="">
<div class="info" data-v-85d5de45=""><a href="#/artist/42" class="name" data-testid="name" data-v-85d5de45="">Led Zeppelin</a></div>
<p class="meta" data-v-85d5de45=""><a title="Shuffle all songs by Led Zeppelin" class="shuffle-artist" data-testid="shuffle-artist" href="" role="button" data-v-85d5de45=""> Shuffle </a><a title="Download all songs by Led Zeppelin" class="download-artist" data-testid="download-artist" href="" role="button" data-v-85d5de45=""> Download </a></p>
<article class="item full" draggable="true" tabindex="0" title="Led Zeppelin" data-v-f01bdc56=""><span class="cover" data-testid="album-artist-thumbnail" data-v-e37470a2="" data-v-f01bdc56=""><a class="control control-play" href="" role="button" data-v-e37470a2=""><span class="hidden" data-v-e37470a2="">Play all songs by Led Zeppelin</span><span class="icon" data-v-e37470a2=""></span></a></span>
<footer data-v-f01bdc56="">
<div class="name" data-v-f01bdc56=""><a href="#/artist/42" class="text-normal" data-testid="name">Led Zeppelin</a></div>
<p class="meta" data-v-f01bdc56=""><a title="Shuffle all songs by Led Zeppelin" class="shuffle-artist" data-testid="shuffle-artist" href="" role="button"> Shuffle </a><a title="Download all songs by Led Zeppelin" class="download-artist" data-testid="download-artist" href="" role="button"> Download </a></p>
</footer>
</article>
`;

View file

@ -12,7 +12,15 @@ new class extends UnitTestCase {
private async renderComponent () {
albumStore.state.albums = factory<Album>('album', 9)
const rendered = this.render(AlbumListScreen)
const rendered = this.render(AlbumListScreen, {
global: {
stubs: {
AlbumCard: this.stub('album-card')
}
}
})
await this.router.activateRoute({ path: 'albums', screen: 'Albums' })
return rendered
}

View file

@ -12,7 +12,15 @@ new class extends UnitTestCase {
private async renderComponent () {
artistStore.state.artists = factory<Artist>('artist', 9)
const rendered = this.render(ArtistListScreen)
const rendered = this.render(ArtistListScreen, {
global: {
stubs: {
ArtistCard: this.stub('artist-card')
}
}
})
await this.router.activateRoute({ path: 'artists', screen: 'Artists' })
return rendered
}

View file

@ -8,7 +8,13 @@ new class extends UnitTestCase {
protected test () {
it('displays the albums', () => {
overviewStore.state.mostPlayedAlbums = factory<Album>('album', 6)
expect(this.render(MostPlayedAlbums).getAllByTestId('album-card')).toHaveLength(6)
expect(this.render(MostPlayedAlbums, {
global: {
stubs: {
AlbumCard: this.stub('album-card')
}
}
}).getAllByTestId('album-card')).toHaveLength(6)
})
}
}

View file

@ -8,7 +8,13 @@ new class extends UnitTestCase {
protected test () {
it('displays the artists', () => {
overviewStore.state.mostPlayedArtists = factory<Artist>('artist', 6)
expect(this.render(MostPlayedArtists).getAllByTestId('artist-card')).toHaveLength(6)
expect(this.render(MostPlayedArtists, {
global: {
stubs: {
ArtistCard: this.stub('artist-card')
}
}
}).getAllByTestId('artist-card')).toHaveLength(6)
})
}
}

View file

@ -8,7 +8,13 @@ new class extends UnitTestCase {
protected test () {
it('displays the albums', () => {
overviewStore.state.recentlyAddedAlbums = factory<Album>('album', 6)
expect(this.render(RecentlyAddedAlbums).getAllByTestId('album-card')).toHaveLength(6)
expect(this.render(RecentlyAddedAlbums, {
global: {
stubs: {
AlbumCard: this.stub('album-card')
}
}
}).getAllByTestId('album-card')).toHaveLength(6)
})
}
}

View file

@ -0,0 +1,115 @@
<template>
<article
class="item"
:class="layout"
draggable="true"
tabindex="0"
@dblclick="onDblClick"
@dragstart="onDragStart"
@contextmenu.prevent="onContextMenu"
>
<AlbumArtistThumbnail :entity="entity"/>
<footer>
<div class="name">
<slot name="name"/>
</div>
<p class="meta">
<slot name="meta"/>
</p>
</footer>
</article>
</template>
<script lang="ts" setup>
import AlbumArtistThumbnail from '@/components/ui/AlbumArtistThumbnail.vue'
const props = withDefaults(
defineProps<{ layout?: ArtistAlbumCardLayout, entity: Artist | Album }>(),
{ layout: 'full' }
)
const emit = defineEmits(['dblclick', 'contextmenu', 'dragstart'])
const onDblClick = () => emit('dblclick')
const onDragStart = (e: DragEvent) => emit('dragstart', e)
const onContextMenu = (e: MouseEvent) => emit('contextmenu', e)
</script>
<style lang="scss" scoped>
.item {
position: relative;
max-width: 256px;
background: var(--color-bg-secondary);
border: 1px solid var(--color-bg-secondary);
padding: 16px;
border-radius: 8px;
display: flex;
flex-direction: column;
gap: 1.5rem;
.name {
display: flex;
flex-direction: column;
gap: 0.5rem;
white-space: nowrap;
::v-deep(a:link) {
color: var(--color-text-primary);
overflow: hidden;
text-overflow: ellipsis;
}
}
&:focus, &:focus-within {
box-shadow: 0 0 1px 1px var(--color-accent);
}
&.compact {
gap: 1rem;
flex-direction: row;
align-items: center;
max-width: 100%;
padding: 10px;
border-radius: 5px;
.cover {
width: 80px;
border-radius: 5px;
}
}
footer {
flex: 1;
overflow: hidden;
display: flex;
flex-direction: column;
gap: .4rem;
}
.meta {
font-size: .9rem;
display: flex;
gap: .3rem;
opacity: .7;
::v-deep(a) {
& + a {
&::before {
content: '•';
margin-right: .2rem;
color: var(--color-text-secondary);
font-weight: unset;
}
}
}
&:hover {
opacity: 1;
}
}
@media only screen and (max-width: 768px) {
max-width: 100%;
}
}
</style>

View file

@ -24,106 +24,6 @@
}
}
@mixin artist-album-card() {
.item {
position: relative;
max-width: 256px;
background: var(--color-bg-secondary);
border: 1px solid var(--color-bg-secondary);
padding: 16px;
border-radius: 8px;
display: flex;
flex-direction: column;
gap: 1.5rem;
@media only screen and (max-width: 768px) {
max-width: 100%;
}
&:focus, &:focus-within {
box-shadow: 0 0 1px 1px var(--color-accent);
}
&:hover .right, &:focus-within .right {
display: flex !important;
}
&.compact {
gap: 1rem;
flex-direction: row;
align-items: center;
max-width: 100%;
padding: 10px;
border-radius: 5px;
.cover {
width: 80px;
border-radius: 5px;
}
}
footer {
flex: 1;
overflow: hidden;
display: flex;
flex-direction: column;
gap: .4rem;
}
.name {
font-weight: var(--font-weight-normal);
}
.meta {
color: var(--color-text-secondary);
font-size: .9rem;
display: flex;
gap: .3rem;
opacity: .7;
a {
border-radius: 3px;
& + a {
&::before {
content: '';
margin-right: .2rem;
color: var(--color-text-secondary);
font-weight: unset;
}
}
}
&:hover {
opacity: 1;
}
}
a.name, a.artist {
display: block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
&:link, &:visited {
color: var(--color-text-primary);
}
&:focus, &:hover {
color: var(--color-accent);
}
}
.info {
.compact & {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
}
@mixin artist-album-info-wrapper() {
.loading {
@include vertical-center();

View file

@ -241,6 +241,14 @@ label {
font-weight: var(--font-weight-thin) !important;
}
&normal {
font-weight: var(--font-weight-normal) !important;
}
&light {
font-weight: var(--font-weight-light) !important;
}
&bold {
font-weight: var(--font-weight-bold) !important;
}