Allow displaying multiple same tracks in track list

Well, there was some error with `@mouseleave` not firing in some cases for some weird reason, so I decided to handle the `hover` prop in the container
environments/review-front-deve-otr6gc/deployments/13419
wvffle 2022-07-17 13:18:55 +00:00 zatwierdzone przez Georg Krause
rodzic 3e5a772027
commit a37835a9c2
5 zmienionych plików z 180 dodań i 147 usunięć

Wyświetl plik

@ -9,10 +9,9 @@ import PlayButton from '~/components/audio/PlayButton.vue'
import usePlayOptions from '~/composables/audio/usePlayOptions'
import useQueue from '~/composables/audio/useQueue'
import usePlayer from '~/composables/audio/usePlayer'
import { ref } from 'vue'
import { computed } from 'vue'
interface Props extends PlayOptionsProps {
tracks: Track[]
track: Track
index: number
@ -23,7 +22,10 @@ interface Props extends PlayOptionsProps {
showPosition?: boolean
displayActions?: boolean
hover: boolean
// TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged
tracks: Track[]
isPlayable?: boolean
artist?: Artist | null
album?: Album | null
@ -42,22 +44,16 @@ const props = withDefaults(defineProps<Props>(), {
displayActions: true
})
const hover = ref<string | null>(null)
const { playing } = usePlayer()
const { playing, loading } = usePlayer()
const { currentTrack } = useQueue()
const { activateTrack } = usePlayOptions(props)
const active = computed(() => props.track.id === currentTrack.value?.id && props.track.position === currentTrack.value?.position)
</script>
<template>
<div
:class="[
{ active: currentTrack && track.id === currentTrack.id },
'track-row row',
]"
@mouseover="hover = track.id"
@mouseleave="hover = null"
:class="[{ active }, 'track-row row']"
@dblclick="activateTrack(track, index)"
>
<div
@ -67,37 +63,34 @@ const { activateTrack } = usePlayOptions(props)
>
<play-indicator
v-if="
!$store.state.player.isLoadingAudio &&
currentTrack &&
!loading &&
playing &&
track.id === currentTrack.id &&
!(track.id == hover)
active &&
!hover
"
/>
<button
<button
v-else-if="
currentTrack &&
!playing &&
track.id === currentTrack.id &&
track.id !== hover
active &&
!hover
"
class="ui really tiny basic icon button play-button paused"
>
<i class="pause icon" />
<i class="play icon" />
</button>
<button
<button
v-else-if="
currentTrack &&
playing &&
track.id === currentTrack.id &&
track.id == hover
active &&
hover
"
class="ui really tiny basic icon button play-button"
>
<i class="pause icon" />
</button>
<button
v-else-if="track.id == hover"
v-else-if="hover"
class="ui really tiny basic icon button play-button"
>
<i class="play icon" />

Wyświetl plik

@ -1,3 +1,145 @@
<script setup lang="ts">
import type { Track } from '~/types'
import { ref, computed } from 'vue'
import { useGettext } from 'vue3-gettext'
import { clone, uniqBy } from 'lodash-es'
import { useElementByPoint, useMouse } from '@vueuse/core'
import axios from 'axios'
import TrackRow from '~/components/audio/track/Row.vue'
import TrackMobileRow from '~/components/audio/track/MobileRow.vue'
import Pagination from '~/components/vui/Pagination.vue'
interface Props {
tracks?: Track[]
showAlbum?: boolean
showArtist?: boolean
showPosition?: boolean
showArt?: boolean
showDuration?: boolean
search?: boolean
displayActions?: boolean
isArtist?: boolean
isAlbum?: boolean
isPodcast?: boolean
// TODO (wvffle): Find correct type
filters?: object
nextUrl?: string | null
paginateResults?: boolean
total?: number
page?: number
paginateBy?: number,
unique?: boolean
}
const props = withDefaults(defineProps<Props>(), {
tracks: () => [],
showAlbum: true,
showArtist: true,
showPosition: false,
showArt: true,
showDuration: true,
search: false,
displayActions: true,
isArtist: false,
isAlbum: false,
isPodcast: false,
filters: () => ({}),
nextUrl: null,
paginateResults: true,
total: 0,
page: 1,
paginateBy: 25,
unique: true
})
const { x, y } = useMouse({ type: 'client' })
const { element } = useElementByPoint({ x, y })
const hover = computed(() => {
const row = element.value?.closest('.track-row') ?? null
return row && allTracks.value.find(track => {
return `${track.id}` === row.getAttribute('data-track-id') && `${track.position}` === row.getAttribute('data-track-position')
})
})
const currentPage = ref(props.page)
const totalTracks = ref(props.total)
const fetchDataUrl = ref(props.nextUrl)
const additionalTracks = ref([] as Track[])
const query = ref('')
const allTracks = computed(() => {
const tracks = [...props.tracks, ...additionalTracks.value]
return props.unique
? uniqBy(tracks, 'id')
: tracks
})
const { $pgettext } = useGettext()
const labels = computed(() => ({
title: $pgettext('*/*/*/Noun', 'Title'),
album: $pgettext('*/*/*/Noun', 'Album'),
artist: $pgettext('*/*/*/Noun', 'Artist')
}))
const emit = defineEmits(['fetched', 'page-changed'])
const isLoading = ref(false)
const fetchData = async () => {
isLoading.value = true
const params = {
...clone(props.filters),
page_size: props.paginateBy,
page: currentPage.value,
include_channels: true,
q: query.value
}
try {
const response = await axios.get('tracks/', { params })
// TODO (wvffle): Fetch continously?
fetchDataUrl.value = response.data.next
additionalTracks.value = response.data.results
totalTracks.value = response.data.count
emit('fetched')
} catch (error) {
// TODO (wvffle): Handle error
}
isLoading.value = false
}
const performSearch = () => {
currentPage.value = 1
additionalTracks.value = []
fetchData()
}
if (props.tracks.length === 0) {
fetchData()
}
const updatePage = (page: number) => {
if (props.tracks.length === 0) {
currentPage.value = page
fetchData()
} else {
emit('page-changed', page)
}
}
</script>
<template>
<div>
<!-- Show the search bar if search is true -->
@ -19,7 +161,7 @@
>
<empty-state
:refresh="true"
@refresh="fetchData('tracks/')"
@refresh="fetchData()"
/>
</slot>
<div v-else>
@ -85,7 +227,9 @@
<track-row
v-for="(track, index) in allTracks"
:key="track.id"
:data-track-id="track.id"
:data-track-position="track.position"
:key="track.id + track.position"
:track="track"
:index="index"
:tracks="allTracks"
@ -96,6 +240,7 @@
:display-actions="displayActions"
:show-duration="showDuration"
:is-podcast="isPodcast"
:hover="hover === track"
/>
</div>
<div
@ -144,7 +289,7 @@
v-if="paginateResults && totalTracks > paginateBy"
:paginate-by="paginateBy"
:total="totalTracks"
:current="tracks.length > 0 ? page : {currentPage}"
:current="tracks.length > 0 ? page : currentPage"
:compact="true"
@page-changed="updatePage"
/>
@ -152,112 +297,3 @@
</div>
</div>
</template>
<script>
import { clone, uniqBy } from 'lodash-es'
import axios from 'axios'
import TrackRow from '~/components/audio/track/Row.vue'
import TrackMobileRow from '~/components/audio/track/MobileRow.vue'
import Pagination from '~/components/vui/Pagination.vue'
export default {
components: {
TrackRow,
TrackMobileRow,
Pagination
},
props: {
tracks: { type: Array, default: () => { return [] } },
showAlbum: { type: Boolean, required: false, default: true },
showArtist: { type: Boolean, required: false, default: true },
showPosition: { type: Boolean, required: false, default: false },
showArt: { type: Boolean, required: false, default: true },
search: { type: Boolean, required: false, default: false },
filters: { type: Object, required: false, default: () => { return {} } },
nextUrl: { type: String, required: false, default: null },
displayActions: { type: Boolean, required: false, default: true },
showDuration: { type: Boolean, required: false, default: true },
isArtist: { type: Boolean, required: false, default: false },
isAlbum: { type: Boolean, required: false, default: false },
isPodcast: { type: Boolean, required: false, default: false },
paginateResults: { type: Boolean, required: false, default: true },
total: { type: Number, required: false, default: 0 },
page: { type: Number, required: false, default: 1 },
paginateBy: { type: Number, required: false, default: 25 }
},
setup () {
const performSearch = () => {
this.currentPage = 1
this.additionalTracks.length = 0
this.fetchData('tracks/')
}
return { performSearch }
},
data () {
return {
fetchDataUrl: this.nextUrl,
isLoading: false,
additionalTracks: [],
query: '',
totalTracks: this.total,
currentPage: this.page
}
},
computed: {
allTracks () {
const tracks = (this.tracks || []).concat(this.additionalTracks)
return uniqBy(tracks, 'id')
},
labels () {
return {
title: this.$pgettext('*/*/*/Noun', 'Title'),
album: this.$pgettext('*/*/*/Noun', 'Album'),
artist: this.$pgettext('*/*/*/Noun', 'Artist')
}
}
},
created () {
if (this.tracks.length === 0) {
this.fetchData('tracks/')
}
},
methods: {
async fetchData (url) {
if (!url) {
return
}
this.isLoading = true
const self = this
const params = clone(this.filters)
params.page_size = this.paginateBy
params.page = this.currentPage
params.include_channels = true
params.q = this.query
const tracksPromise = await axios.get(url, { params: params })
try {
self.fetchDataUrl = tracksPromise.data.next
self.additionalTracks = tracksPromise.data.results
self.totalTracks = tracksPromise.data.count
self.$emit('fetched', tracksPromise.data)
self.isLoading = false
} catch (e) {
self.isLoading = false
self.errors = e.backendErrors
}
},
updatePage: function (page) {
if (this.tracks.length === 0) {
this.currentPage = page
this.fetchData('tracks/')
} else {
this.$emit('page-changed', page)
}
}
}
}
</script>

Wyświetl plik

@ -4,7 +4,7 @@ import type { BackendError, Playlist, APIErrorResponse } from '~/types'
import { filter, sortBy, flow } from 'lodash-es'
import axios from 'axios'
import { useGettext } from 'vue3-gettext'
import Modal from '~/components/semantic/Modal.vue'
import SemanticModal from '~/components/semantic/Modal.vue'
import PlaylistForm from '~/components/playlists/Form.vue'
import useLogger from '~/composables/useLogger'
import { useStore } from '~/store'
@ -79,7 +79,7 @@ store.dispatch('playlists/fetchOwn')
</script>
<template>
<modal
<semantic-modal
v-model:show="$store.state.playlists.showModal"
>
<h4 class="header">
@ -268,5 +268,5 @@ store.dispatch('playlists/fetchOwn')
</translate>
</button>
</div>
</modal>
</semantic-modal>
</template>

Wyświetl plik

@ -168,7 +168,7 @@ export default (props: PlayOptionsProps) => {
if (props.track && props.tracks?.length) {
// set queue position to selected track
const trackIndex = props.tracks.findIndex(track => track.id === props.track?.id)
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)
@ -179,13 +179,16 @@ export default (props: PlayOptionsProps) => {
}
const activateTrack = (track: Track, index: number) => {
if (playing.value && track.id === currentTrack.value?.id) {
pause()
} else if (!playing.value && track.id === currentTrack.value?.id) {
resume()
} else {
replacePlay()
// 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 {

Wyświetl plik

@ -221,6 +221,7 @@ const deletePlaylist = async () => {
<track-table
:display-position="true"
:tracks="tracks"
:unique="false"
/>
</template>
<div