mirror of
https://github.com/koel/koel
synced 2024-11-24 21:23:06 +00:00
feat: split and rename Apple Music button component
This commit is contained in:
parent
4ddb6c6a44
commit
e51d8de337
7 changed files with 83 additions and 63 deletions
|
@ -3,24 +3,24 @@
|
|||
<h1 class="name">
|
||||
<span>{{ album.name }}</span>
|
||||
<button :title="`Shuffle all songs in ${album.name}`" class="shuffle control" @click.prevent="shuffleAll">
|
||||
<i class="fa fa-random" />
|
||||
<i class="fa fa-random"/>
|
||||
</button>
|
||||
</h1>
|
||||
|
||||
<main>
|
||||
<AlbumThumbnail :entity="album" />
|
||||
<AlbumThumbnail :entity="album"/>
|
||||
|
||||
<template v-if="album.info">
|
||||
<div v-if="album.info?.wiki?.summary" class="wiki">
|
||||
<div v-if="showSummary" class="summary" v-html="album.info?.wiki?.summary" />
|
||||
<div v-if="showFull" class="full" v-html="album.info?.wiki?.full" />
|
||||
<div v-if="showSummary" class="summary" v-html="album.info?.wiki?.summary"/>
|
||||
<div v-if="showFull" class="full" v-html="album.info?.wiki?.full"/>
|
||||
|
||||
<button v-if="showSummary" class="more" data-testid="more-btn" @click.prevent="showingFullWiki = true">
|
||||
Full Wiki
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<TrackList v-if="album.info?.tracks?.length" :album="album" data-testid="album-info-tracks" />
|
||||
<TrackList v-if="album.info?.tracks?.length" :album="album" data-testid="album-info-tracks"/>
|
||||
|
||||
<footer>Data © <a :href="album.info?.url" rel="noopener" target="_blank">Last.fm</a></footer>
|
||||
</template>
|
||||
|
@ -35,9 +35,9 @@ import { computed, defineAsyncComponent, ref, toRefs, watch } from 'vue'
|
|||
const TrackList = defineAsyncComponent(() => import('./AlbumTrackList.vue'))
|
||||
const AlbumThumbnail = defineAsyncComponent(() => import('@/components/ui/AlbumArtistThumbnail.vue'))
|
||||
|
||||
type DisplayMode = 'sidebar' | 'full'
|
||||
type DisplayMode = 'aside' | 'full'
|
||||
|
||||
const props = withDefaults(defineProps<{ album: Album, mode?: DisplayMode }>(), { mode: 'sidebar' })
|
||||
const props = withDefaults(defineProps<{ album: Album, mode?: DisplayMode }>(), { mode: 'aside' })
|
||||
const { album, mode } = toRefs(props)
|
||||
|
||||
const showingFullWiki = ref(false)
|
||||
|
|
|
@ -1,29 +1,23 @@
|
|||
<template>
|
||||
<li :class="{ available: song }" :title="tooltip" tabindex="0" @click="play">
|
||||
<span class="title">{{ track.title }}</span>
|
||||
<a
|
||||
v-if="useiTunes && !song"
|
||||
:href="iTunesUrl"
|
||||
class="view-on-itunes"
|
||||
target="_blank"
|
||||
title="View on iTunes"
|
||||
>
|
||||
iTunes
|
||||
</a>
|
||||
<AppleMusicButton v-if="useAppleMusic && !song" :url="iTunesUrl"/>
|
||||
<span class="length">{{ track.fmtLength }}</span>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, toRefs } from 'vue'
|
||||
import { computed, defineAsyncComponent, toRefs } from 'vue'
|
||||
import { queueStore, songStore } from '@/stores'
|
||||
import { authService, playbackService } from '@/services'
|
||||
import { useThirdPartyServices } from '@/composables'
|
||||
|
||||
const AppleMusicButton = defineAsyncComponent(() => import('@/components/ui/AppleMusicButton.vue'))
|
||||
|
||||
const props = defineProps<{ album: Album, track: AlbumTrack }>()
|
||||
const { album, track } = toRefs(props)
|
||||
|
||||
const { useiTunes } = useThirdPartyServices()
|
||||
const { useAppleMusic } = useThirdPartyServices()
|
||||
|
||||
const song = computed(() => songStore.guess(track.value.title, album.value))
|
||||
const tooltip = computed(() => song.value ? 'Click to play' : '')
|
||||
|
@ -42,31 +36,14 @@ const play = () => {
|
|||
|
||||
<style lang="scss" scoped>
|
||||
li {
|
||||
span.title {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
span.title {
|
||||
color: var(--color-highlight);
|
||||
}
|
||||
}
|
||||
|
||||
a.view-on-itunes {
|
||||
display: inline-block;
|
||||
border-radius: 3px;
|
||||
font-size: .8rem;
|
||||
padding: 0 5px;
|
||||
color: var(--color-text-primary);
|
||||
background: rgba(255, 255, 255, .1);
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
margin-left: 4px;
|
||||
|
||||
&:hover, &:focus {
|
||||
background: linear-gradient(27deg, #fe5c52 0%, #c74bd5 50%, #2daaff 100%);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
&:active {
|
||||
box-shadow: inset 0px 5px 5px -5px #000;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
<template>
|
||||
<article class="artist-info" :class="mode" data-testid="artist-info">
|
||||
<article :class="mode" class="artist-info" data-testid="artist-info">
|
||||
<h1 class="name">
|
||||
<span>{{ artist.name }}</span>
|
||||
<button :title="`Shuffle all songs by ${artist.name}`" class="shuffle control" @click.prevent="shuffleAll">
|
||||
<i class="fa fa-random" />
|
||||
<i class="fa fa-random"/>
|
||||
</button>
|
||||
</h1>
|
||||
|
||||
<main v-if="artist.info">
|
||||
<ArtistThumbnail :entity="artist" />
|
||||
<ArtistThumbnail :entity="artist"/>
|
||||
|
||||
<template v-if="artist.info">
|
||||
<div v-if="artist.info?.bio?.summary" class="bio">
|
||||
<div v-if="showSummary" class="summary" v-html="artist.info?.bio?.summary" />
|
||||
<div v-if="showFull" class="full" v-html="artist.info?.bio?.full" />
|
||||
<div v-if="showSummary" class="summary" v-html="artist.info?.bio?.summary"/>
|
||||
<div v-if="showFull" class="full" v-html="artist.info?.bio?.full"/>
|
||||
|
||||
<button v-show="showSummary" class="more" data-testid="more-btn" @click.prevent="showingFullBio = true">
|
||||
Full Bio
|
||||
|
@ -33,11 +33,11 @@
|
|||
import { computed, defineAsyncComponent, ref, toRefs, watch } from 'vue'
|
||||
import { playbackService } from '@/services'
|
||||
|
||||
type DisplayMode = 'sidebar' | 'full'
|
||||
type DisplayMode = 'aside' | 'full'
|
||||
|
||||
const ArtistThumbnail = defineAsyncComponent(() => import('@/components/ui/AlbumArtistThumbnail.vue'))
|
||||
|
||||
const props = withDefaults(defineProps<{ artist: Artist, mode?: DisplayMode }>(), { mode: 'sidebar' })
|
||||
const props = withDefaults(defineProps<{ artist: Artist, mode?: DisplayMode }>(), { mode: 'aside' })
|
||||
const { artist, mode } = toRefs(props)
|
||||
|
||||
const showingFullBio = ref(false)
|
||||
|
|
|
@ -50,7 +50,7 @@
|
|||
role="tabpanel"
|
||||
tabindex="0"
|
||||
>
|
||||
<lyrics-pane :song="song"/>
|
||||
<LyricsPane :song="song"/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
|
@ -60,7 +60,7 @@
|
|||
role="tabpanel"
|
||||
tabindex="0"
|
||||
>
|
||||
<ArtistInfo v-if="artist" :artist="artist" mode="sidebar"/>
|
||||
<ArtistInfo v-if="artist" :artist="artist" mode="aside"/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
|
@ -70,7 +70,7 @@
|
|||
role="tabpanel"
|
||||
tabindex="0"
|
||||
>
|
||||
<AlbumInfo v-if="album" :album="album" mode="sidebar"/>
|
||||
<AlbumInfo v-if="album" :album="album" mode="aside"/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
|
@ -91,7 +91,7 @@
|
|||
import isMobile from 'ismobilejs'
|
||||
import { computed, defineAsyncComponent, ref, toRef, watch } from 'vue'
|
||||
import { $, eventBus } from '@/utils'
|
||||
import { preferenceStore as preferences, songStore } from '@/stores'
|
||||
import { preferenceStore as preferences } from '@/stores'
|
||||
import { songInfo } from '@/services'
|
||||
import { useThirdPartyServices } from '@/composables'
|
||||
|
||||
|
@ -120,11 +120,6 @@ watch(showing, (showingExtraPanel) => {
|
|||
}
|
||||
})
|
||||
|
||||
const resetState = () => {
|
||||
currentTab.value = defaultTab
|
||||
song.value = songStore.stub
|
||||
}
|
||||
|
||||
const fetchSongInfo = async (_song: Song) => {
|
||||
try {
|
||||
song.value = await songInfo.fetch(_song)
|
||||
|
|
48
resources/assets/js/components/ui/AppleMusicButton.vue
Normal file
48
resources/assets/js/components/ui/AppleMusicButton.vue
Normal file
|
@ -0,0 +1,48 @@
|
|||
<template>
|
||||
<a :href="url" target="_blank" title="Preview and buy this song on Apple Music">
|
||||
<svg
|
||||
class="web-navigation__logo-vector"
|
||||
height="10"
|
||||
role="presentation"
|
||||
viewBox="0 0 83 20"
|
||||
width="41" xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M34.752 19.746V6.243h-.088l-5.433 13.503h-2.074L21.711 6.243h-.087v13.503h-2.548V1.399h3.235l5.833 14.621h.1L34.064 1.4h3.248v18.347h-2.56zm16.649 0h-2.586v-2.263h-.062c-.725 1.602-2.061 2.504-4.072 2.504-2.86 0-4.61-1.894-4.61-4.958V6.37h2.698v8.125c0 2.034.95 3.127 2.81 3.127 1.95 0 3.124-1.373 3.124-3.458V6.37H51.4v13.376zm7.394-13.618c3.06 0 5.046 1.73 5.134 4.196h-2.536c-.15-1.296-1.087-2.11-2.598-2.11-1.462 0-2.436.724-2.436 1.793 0 .839.6 1.41 2.023 1.741l2.136.496c2.686.636 3.71 1.704 3.71 3.636 0 2.442-2.236 4.12-5.333 4.12-3.285 0-5.26-1.64-5.509-4.183h2.673c.25 1.398 1.187 2.085 2.836 2.085 1.623 0 2.623-.687 2.623-1.78 0-.865-.487-1.373-1.924-1.704l-2.136-.508c-2.498-.585-3.735-1.806-3.735-3.75 0-2.391 2.049-4.032 5.072-4.032zM66.1 2.836c0-.878.7-1.577 1.561-1.577.862 0 1.55.7 1.55 1.577 0 .864-.688 1.576-1.55 1.576a1.573 1.573 0 0 1-1.56-1.576zm.212 3.534h2.698v13.376h-2.698V6.37zm14.089 4.603c-.275-1.424-1.324-2.556-3.085-2.556-2.086 0-3.46 1.767-3.46 4.64 0 2.938 1.386 4.642 3.485 4.642 1.66 0 2.748-.928 3.06-2.48H83C82.713 18.067 80.477 20 77.317 20c-3.76 0-6.208-2.62-6.208-6.942 0-4.247 2.448-6.93 6.183-6.93 3.385 0 5.446 2.213 5.683 4.845h-2.573zM10.824 3.189c-.698.834-1.805 1.496-2.913 1.398-.145-1.128.41-2.33 1.036-3.065C9.644.662 10.848.05 11.835 0c.121 1.178-.336 2.33-1.01 3.19zm.999 1.619c.624.049 2.425.244 3.578 1.98-.096.074-2.137 1.272-2.113 3.79.024 3.01 2.593 4.012 2.617 4.037-.024.074-.407 1.419-1.344 2.812-.817 1.224-1.657 2.422-3.002 2.447-1.297.024-1.73-.783-3.218-.783-1.489 0-1.97.758-3.194.807-1.297.048-2.28-1.297-3.097-2.52C.368 14.908-.904 10.408.825 7.375c.84-1.516 2.377-2.47 4.034-2.495 1.273-.023 2.45.857 3.218.857.769 0 2.137-1.027 3.746-.93z"
|
||||
fill-rule="nonzero"
|
||||
stroke="none"
|
||||
stroke-width="1"
|
||||
></path>
|
||||
</svg>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { toRefs } from 'vue'
|
||||
|
||||
const props = defineProps({ url: String })
|
||||
const { url } = toRefs(props)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
a {
|
||||
display: inline-block;
|
||||
border-radius: 3px;
|
||||
padding: 0 5px;
|
||||
background: rgba(255, 255, 255, .1);
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
|
||||
svg {
|
||||
fill: var(--color-text-primary)
|
||||
}
|
||||
|
||||
&:hover, &:focus {
|
||||
background: linear-gradient(27deg, #fe5c52 0%, #c74bd5 50%, #2daaff 100%);
|
||||
}
|
||||
|
||||
&:active {
|
||||
box-shadow: inset 0px 5px 5px -5px #000;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -4,11 +4,11 @@ import { commonStore } from '@/stores'
|
|||
export const useThirdPartyServices = () => {
|
||||
const useLastfm = toRef(commonStore.state, 'useLastfm')
|
||||
const useYouTube = toRef(commonStore.state, 'useYouTube')
|
||||
const useiTunes = toRef(commonStore.state, 'useiTunes')
|
||||
const useAppleMusic = toRef(commonStore.state, 'useiTunes')
|
||||
|
||||
return {
|
||||
useLastfm,
|
||||
useYouTube,
|
||||
useiTunes
|
||||
useAppleMusic
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,16 +3,16 @@ import { reactive } from 'vue'
|
|||
|
||||
import { httpService } from '@/services'
|
||||
import {
|
||||
userStore,
|
||||
preferenceStore,
|
||||
artistStore,
|
||||
albumStore,
|
||||
songStore,
|
||||
artistStore,
|
||||
playlistStore,
|
||||
recentlyPlayedStore,
|
||||
preferenceStore,
|
||||
queueStore,
|
||||
recentlyPlayedStore,
|
||||
settingStore,
|
||||
themeStore
|
||||
songStore,
|
||||
themeStore,
|
||||
userStore
|
||||
} from '.'
|
||||
|
||||
interface CommonStoreState {
|
||||
|
|
Loading…
Reference in a new issue