funkwhale/front/src/components/audio/track/Table.vue

307 lines
7.3 KiB
Vue

<script setup lang="ts">
import type { Track } from '~/types'
import { useElementByPoint, useMouse } from '@vueuse/core'
import { useGettext } from 'vue3-gettext'
import { clone, uniqBy } from 'lodash-es'
import { ref, computed } from 'vue'
import axios from 'axios'
import TrackMobileRow from '~/components/audio/track/MobileRow.vue'
import Pagination from '~/components/vui/Pagination.vue'
import TrackRow from '~/components/audio/track/Row.vue'
import useErrorHandler from '~/composables/useErrorHandler'
interface Events {
(e: 'fetched'): void
(e: 'page-changed', page: number): void
}
interface Props {
tracks?: undefined | Track[]
showAlbum?: boolean
showArtist?: boolean
showPosition?: boolean
showArt?: boolean
showDuration?: boolean
search?: boolean
displayActions?: boolean
isArtist?: boolean
isAlbum?: boolean
isPodcast?: boolean
filters?: object
nextUrl?: string | null
paginateResults?: boolean
total?: number
page?: number
paginateBy?: number,
unique?: boolean
}
const emit = defineEmits<Events>()
const props = withDefaults(defineProps<Props>(), {
tracks: undefined,
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 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) {
useErrorHandler(error as Error)
}
isLoading.value = false
}
const performSearch = () => {
currentPage.value = 1
additionalTracks.value = []
fetchData()
}
if (props.tracks === undefined) {
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 -->
<inline-search-bar
v-if="search"
v-model="query"
@search="performSearch"
/>
<!-- Add a header if needed -->
<slot name="header" />
<!-- Show a message if no tracks are available -->
<slot
v-if="!isLoading && allTracks.length === 0"
name="empty-state"
>
<empty-state
:refresh="true"
@refresh="fetchData()"
/>
</slot>
<div v-else>
<div
:class="['track-table', 'ui', 'unstackable', 'grid', 'tablet-and-up']"
>
<div
v-if="isLoading"
class="ui inverted active dimmer"
>
<div class="ui loader" />
</div>
<div class="track-table row">
<div
v-if="showPosition"
class="actions left floated column"
>
<i class="hashtag icon" />
</div>
<div
v-else
class="actions left floated column"
/>
<div
v-if="showArt"
class="image left floated column"
/>
<div class="content ellipsis left floated column">
<b>{{ labels.title }}</b>
</div>
<div
v-if="showAlbum"
class="content ellipsisleft floated column"
>
<b>{{ labels.album }}</b>
</div>
<div
v-if="showArtist"
class="content ellipsis left floated column"
>
<b>{{ labels.artist }}</b>
</div>
<div
v-if="$store.state.auth.authenticated"
class="meta right floated column"
/>
<div
v-if="showDuration"
class="meta right floated column"
>
<i
class="clock outline icon"
style="padding: 0.5rem"
/>
</div>
<div
v-if="displayActions"
class="meta right floated column"
/>
</div>
<!-- For each item, build a row -->
<track-row
v-for="(track, index) in allTracks"
:key="track.id + (track.position ?? 0)"
:data-track-id="track.id"
:data-track-position="track.position"
:track="track"
:index="index"
:tracks="allTracks"
:show-album="showAlbum"
:show-artist="showArtist"
:show-position="showPosition"
:show-art="showArt"
:display-actions="displayActions"
:show-duration="showDuration"
:is-podcast="isPodcast"
:hover="hover === track"
/>
</div>
<div
v-if="tracks && paginateResults"
class="ui center aligned basic segment desktop-and-up"
>
<pagination
:total="totalTracks"
:current="tracks.length > 0 ? page : currentPage"
:paginate-by="paginateBy"
@update:current="updatePage"
/>
</div>
</div>
<div
:class="['track-table', 'ui', 'unstackable', 'grid', 'tablet-and-below']"
>
<div
v-if="isLoading"
class="ui inverted active dimmer"
>
<div class="ui loader" />
</div>
<!-- For each item, build a row -->
<track-mobile-row
v-for="(track, index) in allTracks"
:key="track.id"
:track="track"
:index="index"
:tracks="allTracks"
:show-position="showPosition"
:show-art="showArt"
:show-duration="showDuration"
:is-artist="isArtist"
:is-album="isAlbum"
:is-podcast="isPodcast"
/>
<div
v-if="tracks && paginateResults && totalTracks > paginateBy"
class="ui center aligned basic segment tablet-and-below"
>
<pagination
v-if="paginateResults && totalTracks > paginateBy"
:paginate-by="paginateBy"
:total="totalTracks"
:current="tracks.length > 0 ? page : currentPage"
:compact="true"
@update:current="updatePage"
/>
</div>
</div>
</div>
</template>