201 lines
6.0 KiB
TypeScript
201 lines
6.0 KiB
TypeScript
import type { Track, Artist, Album, Playlist, Library, Channel, Actor } from '~/types'
|
|
import type { ContentFilter } from '~/store/moderation'
|
|
|
|
import { useStore } from '~/store'
|
|
import { useGettext } from 'vue3-gettext'
|
|
import { computed, markRaw, ref } from 'vue'
|
|
import axios from 'axios'
|
|
import usePlayer from '~/composables/audio/usePlayer'
|
|
import useQueue from '~/composables/audio/useQueue'
|
|
import { useCurrentElement } from '@vueuse/core'
|
|
import jQuery from 'jquery'
|
|
|
|
export interface PlayOptionsProps {
|
|
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
|
|
}
|
|
|
|
export default (props: PlayOptionsProps) => {
|
|
const store = useStore()
|
|
const { resume, pause, playing } = usePlayer()
|
|
const { currentTrack } = useQueue()
|
|
|
|
const playable = computed(() => {
|
|
if (props.isPlayable) {
|
|
return true
|
|
}
|
|
|
|
if (props.track) {
|
|
return props.track.uploads?.length > 0
|
|
} else if (props.artist) {
|
|
return props.artist.tracks_count > 0
|
|
|| props.artist?.albums?.some((album) => album.is_playable === true)
|
|
} else if (props.tracks) {
|
|
return props.tracks?.some((track) => (track.uploads?.length ?? 0) > 0)
|
|
}
|
|
|
|
return false
|
|
})
|
|
|
|
const filterableArtist = computed(() => props.track?.artist ?? props.album?.artist ?? props.artist)
|
|
const filterArtist = () => store.dispatch('moderation/hide', { type: 'artist', target: filterableArtist.value })
|
|
|
|
const { $npgettext } = useGettext()
|
|
const addMessage = (tracks: Track[]) => {
|
|
if (!tracks.length) {
|
|
return
|
|
}
|
|
|
|
store.commit('ui/addMessage', {
|
|
content: $npgettext('*/Queue/Message', '%{ count } track was added to your queue', '%{ count } tracks were added to your queue', tracks.length, {
|
|
count: tracks.length.toString()
|
|
}),
|
|
date: new Date()
|
|
})
|
|
}
|
|
|
|
const getTracksPage = async (params: object, page = 1, tracks: Track[] = []): Promise<Track[]> => {
|
|
if (page > 11) {
|
|
// it's 10 * 100 tracks already, let's stop here
|
|
return tracks
|
|
}
|
|
|
|
// when fetching artists/or album tracks, sometimes, we may have to fetch
|
|
// multiple pages
|
|
const response = await axios.get('tracks/', {
|
|
params: {
|
|
...params,
|
|
page_size: 100,
|
|
page,
|
|
hidden: '',
|
|
playable: true
|
|
}
|
|
})
|
|
|
|
tracks.push(...response.data.results)
|
|
if (response.data.next) {
|
|
return getTracksPage(params, page + 1, tracks)
|
|
}
|
|
|
|
return tracks
|
|
}
|
|
|
|
const isLoading = ref(false)
|
|
const getPlayableTracks = async () => {
|
|
isLoading.value = true
|
|
|
|
const tracks: Track[] = []
|
|
|
|
// TODO (wvffle): Why is there no channel?
|
|
if (props.tracks?.length) {
|
|
tracks.push(...props.tracks)
|
|
} else if (props.track) {
|
|
if (props.track.uploads?.length) {
|
|
tracks.push(props.track)
|
|
} else {
|
|
// fetch uploads from api
|
|
const response = await axios.get(`tracks/${props.track.id}/`)
|
|
tracks.push(response.data as Track)
|
|
}
|
|
} else if (props.playlist) {
|
|
const response = await axios.get(`playlists/${props.playlist.id}/tracks/`)
|
|
const playlistTracks = (response.data.results as Array<{ track: Track }>).map(({ track }) => track as Track)
|
|
|
|
const artistIds = store.getters['moderation/artistFilters']().map((filter: ContentFilter) => filter.target.id)
|
|
if (artistIds.length) {
|
|
tracks.push(...playlistTracks.filter((track) => {
|
|
return !((artistIds.includes(track.artist?.id) || track.album) && artistIds.includes(track.album?.artist.id))
|
|
}))
|
|
} else {
|
|
tracks.push(...playlistTracks)
|
|
}
|
|
} else if (props.artist) {
|
|
tracks.push(...await getTracksPage({ artist: props.artist.id, include_channels: 'true', ordering: 'album__release_date,disc_number,position' }))
|
|
} else if (props.album) {
|
|
tracks.push(...await getTracksPage({ album: props.album.id, include_channels: 'true', ordering: 'disc_number,position' }))
|
|
} else if (props.library) {
|
|
tracks.push(...await getTracksPage({ library: props.library.uuid, ordering: '-creation_date' }))
|
|
}
|
|
|
|
isLoading.value = false
|
|
|
|
return tracks.filter(track => track.uploads?.length).map(markRaw)
|
|
}
|
|
|
|
const el = useCurrentElement()
|
|
const enqueue = async () => {
|
|
jQuery(el.value).find('.ui.dropdown').dropdown('hide')
|
|
|
|
const tracks = await getPlayableTracks()
|
|
await store.dispatch('queue/appendMany', { tracks })
|
|
addMessage(tracks)
|
|
}
|
|
|
|
const enqueueNext = async (next = false) => {
|
|
jQuery(el.value).find('.ui.dropdown').dropdown('hide')
|
|
|
|
const tracks = await getPlayableTracks()
|
|
|
|
const wasEmpty = store.state.queue.tracks.length === 0
|
|
await store.dispatch('queue/appendMany', { tracks, index: store.state.queue.currentIndex + 1 })
|
|
|
|
if (next && !wasEmpty) {
|
|
await store.dispatch('queue/next')
|
|
resume()
|
|
}
|
|
|
|
addMessage(tracks)
|
|
}
|
|
|
|
const replacePlay = async () => {
|
|
store.dispatch('queue/clean')
|
|
|
|
jQuery(el.value).find('.ui.dropdown').dropdown('hide')
|
|
|
|
const tracks = await getPlayableTracks()
|
|
await store.dispatch('queue/appendMany', { tracks })
|
|
|
|
if (props.track && props.tracks?.length) {
|
|
// set queue position to selected track
|
|
const trackIndex = props.tracks.findIndex(track => track.id === props.track?.id && track.position === props.track?.position)
|
|
store.dispatch('queue/currentIndex', trackIndex)
|
|
} else {
|
|
store.dispatch('queue/currentIndex', 0)
|
|
}
|
|
|
|
resume()
|
|
addMessage(tracks)
|
|
}
|
|
|
|
const activateTrack = (track: Track, index: number) => {
|
|
// TODO (wvffle): Check if position checking did not break anything
|
|
if (track.id === currentTrack.value?.id && track.position === currentTrack.value?.position) {
|
|
if (playing.value) {
|
|
return pause()
|
|
}
|
|
|
|
return resume()
|
|
}
|
|
|
|
replacePlay()
|
|
}
|
|
|
|
return {
|
|
playable,
|
|
filterableArtist,
|
|
filterArtist,
|
|
enqueue,
|
|
enqueueNext,
|
|
replacePlay,
|
|
activateTrack,
|
|
isLoading
|
|
}
|
|
}
|