264 lines
7.4 KiB
Vue
264 lines
7.4 KiB
Vue
<script setup lang="ts">
|
|
import type { Track, Artist, Album, Playlist, Library, Channel, Actor } from '~/types'
|
|
import type { components } from '~/generated/types'
|
|
import type { PlayOptionsProps } from '~/composables/audio/usePlayOptions'
|
|
|
|
import { computed, ref } from 'vue'
|
|
import { useI18n } from 'vue-i18n'
|
|
import usePlayOptions from '~/composables/audio/usePlayOptions'
|
|
import useReport from '~/composables/moderation/useReport'
|
|
import { useStore } from '~/store'
|
|
import { useRouter, useRoute } from 'vue-router'
|
|
|
|
import Button from '~/components/ui/Button.vue'
|
|
import OptionsButton from '~/components/ui/button/Options.vue'
|
|
import Popover from '~/components/ui/Popover.vue'
|
|
import PopoverItem from '~/components/ui/popover/PopoverItem.vue'
|
|
|
|
interface Props extends PlayOptionsProps {
|
|
split?: boolean
|
|
dropdownIconClasses?: string[]
|
|
playIconClass?: string
|
|
buttonClasses?: string[]
|
|
discrete?: boolean
|
|
dropdownOnly?: boolean
|
|
iconOnly?: boolean
|
|
playing?: boolean
|
|
paused?: boolean
|
|
lowHeight?: boolean
|
|
|
|
// TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged
|
|
isPlayable?: boolean
|
|
tracks?: Track[]
|
|
track?: Track | null
|
|
artist?: Artist | components["schemas"]["SimpleChannelArtist"] | components['schemas']['ArtistWithAlbums'] | null
|
|
album?: Album | null
|
|
playlist?: Playlist | null
|
|
library?: Library | null
|
|
channel?: Channel | null
|
|
account?: Actor | components['schemas']['APIActor'] | null
|
|
}
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
split: false,
|
|
tracks: () => [],
|
|
track: null,
|
|
artist: null,
|
|
playlist: null,
|
|
album: null,
|
|
library: null,
|
|
channel: null,
|
|
account: null,
|
|
dropdownIconClasses: () => ['bi-caret-down-fill'],
|
|
playIconClass: () => 'bi-play-fill',
|
|
buttonClasses: () => ['button'],
|
|
discrete: () => false,
|
|
dropdownOnly: () => false,
|
|
iconOnly: () => false,
|
|
isPlayable: () => false,
|
|
playing: () => false,
|
|
paused: () => false,
|
|
lowHeight: () => false
|
|
})
|
|
|
|
// (1) Create a PlayButton
|
|
// Some of the props are meant for `usePlayOptions`!
|
|
// UsePlayOptions accepts the props from this component and returns the following things:
|
|
|
|
const {
|
|
playable,
|
|
filterableArtist,
|
|
filterArtist,
|
|
enqueue,
|
|
enqueueNext,
|
|
replacePlay,
|
|
isLoading
|
|
} = usePlayOptions(props)
|
|
|
|
const { report, getReportableObjects } = useReport()
|
|
|
|
const { t } = useI18n()
|
|
const store = useStore()
|
|
const router = useRouter()
|
|
const route = useRoute()
|
|
|
|
const labels = computed(() => ({
|
|
playNow: t('components.audio.PlayButton.button.playNow'),
|
|
addToQueue: t('components.audio.PlayButton.button.addToQueue'),
|
|
playNext: t('components.audio.PlayButton.button.playNext'),
|
|
startRadio: t('components.audio.PlayButton.button.startRadio'),
|
|
report: t('components.audio.PlayButton.button.report'),
|
|
addToPlaylist: t('components.audio.PlayButton.button.addToPlaylist'),
|
|
hideArtist: t('components.audio.PlayButton.button.hideArtist'),
|
|
replacePlay: props.track
|
|
? t('components.audio.PlayButton.button.playTrack')
|
|
: props.album
|
|
? t('components.audio.PlayButton.button.playAlbum')
|
|
: props.artist
|
|
? t('components.audio.PlayButton.button.playArtist')
|
|
: props.playlist
|
|
? t('components.audio.PlayButton.button.playPlaylist')
|
|
: t('components.audio.PlayButton.button.playTracks')
|
|
}))
|
|
|
|
const isOpen = ref(false)
|
|
</script>
|
|
|
|
<template>
|
|
<Popover
|
|
v-if="split || (!iconOnly && dropdownOnly)"
|
|
v-model="isOpen"
|
|
>
|
|
<OptionsButton
|
|
v-if="dropdownOnly"
|
|
v-bind="$attrs"
|
|
:is-ghost="discrete"
|
|
@click="isOpen = !isOpen"
|
|
/>
|
|
<Button
|
|
v-else
|
|
v-bind="{
|
|
disabled: !playable && !filterableArtist,
|
|
primary: playable,
|
|
split: true,
|
|
splitIcon: 'bi-caret-down-fill'
|
|
}"
|
|
:aria-label="labels.replacePlay"
|
|
:class="[...buttonClasses, 'play-button']"
|
|
:isloading="isLoading"
|
|
:dropdown-only="dropdownOnly"
|
|
:low-height="lowHeight || undefined"
|
|
style="align-self: start;"
|
|
@click.stop.prevent="replacePlay()"
|
|
@split-click="isOpen = !isOpen"
|
|
>
|
|
<template #main>
|
|
<i
|
|
v-if="playing"
|
|
class="bi bi-pause-fill"
|
|
/>
|
|
<i
|
|
v-else
|
|
:class="['bi', playIconClass]"
|
|
/>
|
|
<template v-if="!discrete && !iconOnly">
|
|
<slot>{{ t('components.audio.PlayButton.button.discretePlay') }}</slot>
|
|
</template>
|
|
</template>
|
|
</Button>
|
|
|
|
<template #items>
|
|
<PopoverItem
|
|
:disabled="!playable"
|
|
:title="labels.addToQueue"
|
|
icon="bi-plus"
|
|
@click.stop.prevent="enqueue"
|
|
>
|
|
{{ labels.addToQueue }}
|
|
</PopoverItem>
|
|
|
|
<PopoverItem
|
|
:disabled="!playable"
|
|
:title="labels.playNext"
|
|
icon="bi-skip-forward-fill"
|
|
@click.stop.prevent="enqueueNext()"
|
|
>
|
|
{{ labels.playNext }}
|
|
</PopoverItem>
|
|
|
|
<PopoverItem
|
|
:disabled="!playable"
|
|
:title="labels.playNow"
|
|
icon="bi-play-fill"
|
|
@click.stop.prevent="enqueueNext(true)"
|
|
>
|
|
{{ labels.playNow }}
|
|
</PopoverItem>
|
|
|
|
<PopoverItem
|
|
v-if="track"
|
|
:disabled="!playable"
|
|
:title="labels.startRadio"
|
|
icon="bi-broadcast"
|
|
@click.stop.prevent="store.dispatch('radios/start', {type: 'similar', objectId: track?.id})"
|
|
>
|
|
{{ labels.startRadio }}
|
|
</PopoverItem>
|
|
|
|
<PopoverItem
|
|
v-if="track"
|
|
:disabled="!playable"
|
|
icon="bi-list"
|
|
@click.stop="store.commit('playlists/chooseTrack', track)"
|
|
>
|
|
{{ labels.addToPlaylist }}
|
|
</PopoverItem>
|
|
<PopoverItem
|
|
v-if="track && route.name !== 'library.tracks.detail'"
|
|
icon="bi-info-circle"
|
|
@click.stop.prevent="router.push(`/library/tracks/${track?.id}/`)"
|
|
>
|
|
<span v-if="track.artist_credit?.some(ac => ac.artist.content_category === 'podcast')">
|
|
{{ t('components.audio.PlayButton.button.episodeDetails') }}
|
|
</span>
|
|
<span v-else>
|
|
{{ t('components.audio.PlayButton.button.trackDetails') }}
|
|
</span>
|
|
</PopoverItem>
|
|
|
|
<hr v-if="filterableArtist || Object.keys(getReportableObjects({ track, album, artist, playlist, account, channel })).length > 0">
|
|
|
|
<PopoverItem
|
|
v-if="filterableArtist"
|
|
:disabled="!filterableArtist"
|
|
:title="labels.hideArtist"
|
|
icon="bi-eye-slash"
|
|
@click.stop.prevent="filterArtist"
|
|
>
|
|
{{ labels.hideArtist }}
|
|
</PopoverItem>
|
|
|
|
<PopoverItem
|
|
v-for="obj in getReportableObjects({ track, album, artist, playlist, account, channel })"
|
|
:key="obj.target.type + obj.target.id"
|
|
icon="bi-exclamation-triangle-fill"
|
|
@click.stop.prevent="report(obj)"
|
|
>
|
|
{{ obj.label }}
|
|
</PopoverItem>
|
|
</template>
|
|
</Popover>
|
|
<Button
|
|
v-else
|
|
v-bind="{
|
|
disabled: !playable,
|
|
primary: playable,
|
|
}"
|
|
:aria-label="labels.replacePlay"
|
|
:class="[...buttonClasses, 'play-button']"
|
|
:isloading="isLoading"
|
|
:square="iconOnly"
|
|
:icon="!playing ? playIconClass : 'bi-pause-fill'"
|
|
:round="iconOnly"
|
|
:primary="iconOnly && !discrete"
|
|
:ghost="discrete"
|
|
:low-height="lowHeight || undefined"
|
|
@click.stop.prevent="replacePlay()"
|
|
>
|
|
<template v-if="!discrete && !iconOnly">
|
|
<span>
|
|
{{ t('components.audio.PlayButton.button.discretePlay') }}
|
|
</span>
|
|
</template>
|
|
</Button>
|
|
</template>
|
|
|
|
<style lang="scss" scoped>
|
|
.funkwhale.split-button {
|
|
&.button {
|
|
gap: 0px;
|
|
padding: 0px;
|
|
}
|
|
}
|
|
</style>
|