funkwhale/front/src/components/audio/PlayButton.vue

233 wiersze
7.6 KiB
Vue

<script setup lang="ts">
import type { Track, Artist, Album, Playlist, Library, Channel, Actor } from '~/types'
import type { PlayOptionsProps } from '~/composables/audio/usePlayOptions'
import { ref, computed, onMounted } from 'vue'
import { useGettext } from 'vue3-gettext'
import usePlayOptions from '~/composables/audio/usePlayOptions'
import useReport from '~/composables/moderation/useReport'
import { useCurrentElement } from '@vueuse/core'
import { setupDropdown } from '~/utils/fomantic'
interface Props extends PlayOptionsProps {
dropdownIconClasses?: string[]
playIconClass?: string
buttonClasses?: string[]
discrete?: boolean
dropdownOnly?: boolean
iconOnly?: boolean
playing?: boolean
paused?: boolean
// TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged
isPlayable?: boolean
tracks?: Track[]
track?: Track | null
artist?: Artist | null
album?: Album | null
playlist?: Playlist | null
library?: Library | null
channel?: Channel | null
account?: Actor | null
}
const props = withDefaults(defineProps<Props>(), {
tracks: () => [],
track: null,
artist: null,
playlist: null,
album: null,
library: null,
channel: null,
account: null,
dropdownIconClasses: () => ['dropdown'],
playIconClass: () => 'play icon',
buttonClasses: () => ['button'],
discrete: () => false,
dropdownOnly: () => false,
iconOnly: () => false,
isPlayable: () => false,
playing: () => false,
paused: () => false
})
const {
playable,
filterableArtist,
filterArtist,
enqueue,
enqueueNext,
replacePlay,
isLoading
} = usePlayOptions(props)
const { report, getReportableObjects } = useReport()
const { $pgettext } = useGettext()
const labels = computed(() => ({
playNow: $pgettext('*/Queue/Dropdown/Button/Title', 'Play now'),
addToQueue: $pgettext('*/Queue/Dropdown/Button/Title', 'Add to current queue'),
playNext: $pgettext('*/Queue/Dropdown/Button/Title', 'Play next'),
startRadio: $pgettext('*/Queue/Dropdown/Button/Title', 'Play similar songs'),
report: $pgettext('*/Moderation/*/Button/Label,Verb', 'Report…'),
addToPlaylist: $pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Add to playlist…'),
hideArtist: $pgettext('*/Queue/Dropdown/Button/Label/Short', 'Hide content from this artist'),
replacePlay: props.track
? $pgettext('*/Queue/Dropdown/Button/Title', 'Play track')
: props.album
? $pgettext('*/Queue/Dropdown/Button/Title', 'Play album')
: props.artist
? $pgettext('*/Queue/Dropdown/Button/Title', 'Play artist')
: props.playlist
? $pgettext('*/Queue/Dropdown/Button/Title', 'Play playlist')
: $pgettext('*/Queue/Dropdown/Button/Title', 'Play tracks')
}))
const title = computed(() => {
if (playable.value) {
return $pgettext('*/*/Button.Label/Noun', 'More…')
}
if (props.track) {
return $pgettext('*/Queue/Button/Title', 'This track is not available in any library you have access to')
}
return ''
})
const el = useCurrentElement()
const dropdown = ref()
onMounted(() => {
dropdown.value = setupDropdown('.ui.dropdown', el.value)
})
const openMenu = () => {
// little magic to ensure the menu is always visible in the viewport
// By default, try to display it on the right if there is enough room
const menu = dropdown.value.find('.menu')
const viewportOffset = menu.get(0)?.getBoundingClientRect() ?? { right: 0, left: 0 }
const viewportWidth = document.documentElement.clientWidth
const rightOverflow = viewportOffset.right - viewportWidth
const leftOverflow = -viewportOffset.left
if (rightOverflow > 0) {
menu.css({ cssText: `left: ${-rightOverflow - 5}px !important;` })
} else if (leftOverflow > 0) {
menu.css({ cssText: `right: -${leftOverflow + 5}px !important;` })
}
}
</script>
<template>
<span
:title="title"
:class="['ui', {'tiny': discrete, 'icon': !discrete, 'buttons': !dropdownOnly && !iconOnly}, 'play-button component-play-button']"
>
<button
v-if="!dropdownOnly"
:disabled="!playable"
:aria-label="labels.replacePlay"
:class="[...buttonClasses, 'ui', {loading: isLoading, 'mini': discrete, disabled: !playable}]"
@click.stop.prevent="replacePlay()"
>
<i
v-if="playing"
class="pause icon"
/>
<i
v-else
:class="[playIconClass, 'icon']"
/>
<template v-if="!discrete && !iconOnly">&nbsp;<slot><translate translate-context="*/Queue/Button.Label/Short, Verb">Play</translate></slot></template>
</button>
<button
v-if="!discrete && !iconOnly"
:class="['ui', {disabled: !playable && !filterableArtist}, 'floating', 'dropdown', {'icon': !dropdownOnly}, {'button': !dropdownOnly}]"
@click.stop.prevent="openMenu"
>
<i
:class="dropdownIconClasses.concat(['icon'])"
:title="title"
/>
<div class="menu">
<button
class="item basic"
:disabled="!playable"
:title="labels.addToQueue"
@click.stop.prevent="enqueue"
>
<i class="plus icon" /><translate translate-context="*/Queue/Dropdown/Button/Label/Short">Add to queue</translate>
</button>
<button
class="item basic"
:disabled="!playable"
:title="labels.playNext"
@click.stop.prevent="enqueueNext()"
>
<i class="step forward icon" />{{ labels.playNext }}
</button>
<button
class="item basic"
:disabled="!playable"
:title="labels.playNow"
@click.stop.prevent="enqueueNext(true)"
>
<i class="play icon" />{{ labels.playNow }}
</button>
<button
v-if="track"
class="item basic"
:disabled="!playable"
:title="labels.startRadio"
@click.stop.prevent="$store.dispatch('radios/start', {type: 'similar', objectId: track?.id})"
>
<i class="feed icon" /><translate translate-context="*/Queue/Button.Label/Short, Verb">Play radio</translate>
</button>
<button
v-if="track"
class="item basic"
:disabled="!playable"
@click.stop="$store.commit('playlists/chooseTrack', track)"
>
<i class="list icon" />
<translate translate-context="Sidebar/Player/Icon.Tooltip/Verb">Add to playlist</translate>
</button>
<button
v-if="track && $route.name !== 'library.tracks.detail'"
class="item basic"
@click.stop.prevent="$router.push(`/library/tracks/${track?.id}/`)"
>
<i class="info icon" />
<translate
v-if="track.artist?.content_category === 'podcast'"
translate-context="*/Queue/Dropdown/Button/Label/Short"
>Episode details</translate>
<translate
v-else
translate-context="*/Queue/Dropdown/Button/Label/Short"
>Track details</translate>
</button>
<div class="divider" />
<button
v-if="filterableArtist"
class="item basic"
:disabled="!filterableArtist"
:title="labels.hideArtist"
@click.stop.prevent="filterArtist"
>
<i class="eye slash outline icon" />
{{ labels.hideArtist }}
</button>
<button
v-for="obj in getReportableObjects({track, album, artist, playlist, account, channel})"
:key="obj.target.type + obj.target.id"
class="item basic"
@click.stop.prevent="report(obj)"
>
<i class="share icon" /> {{ obj.label }}
</button>
</div>
</button>
</span>
</template>