funkwhale/front/src/components/playlists/PlaylistDropdown.vue

201 lines
5.4 KiB
Vue

<script setup lang="ts">
import type { Playlist } from '~/types'
import { computed, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { useStore } from '~/store'
import { useRouter } from 'vue-router'
import axios from 'axios'
import useErrorHandler from '~/composables/useErrorHandler'
import OptionsButton from '~/components/ui/button/Options.vue'
import EmbedWizard from '~/components/audio/EmbedWizard.vue'
import Modal from '~/components/ui/Modal.vue'
import Alert from '~/components/ui/Alert.vue'
import Button from '~/components/ui/Button.vue'
import Popover from '~/components/ui/Popover.vue'
import PopoverItem from '~/components/ui/popover/PopoverItem.vue'
interface Events {
(e: 'import'): void
(e: 'export'): void
}
interface Props {
playlist: Playlist
}
const emit = defineEmits<Events>()
const props = defineProps<Props>()
const store = useStore()
const currentDate = new Date()
const formattedDate = currentDate.toISOString().split('T')[0]
const { t } = useI18n()
const labels = computed(() => ({
import: t('components.playlists.PlaylistDropdown.button.import.header'),
export: t('components.playlists.PlaylistDropdown.button.export.header'),
more: t('components.playlists.PlaylistDropdown.more')
}))
const exportUrl = computed(() => store.getters['instance/absoluteUrl'](`/api/v2/playlists/${props.playlist.id}`))
const exportPlaylist = async () => {
const url = exportUrl.value
const authToken = store.state.auth.oauth.accessToken
const headers = {
'Content-Type': 'application/octet-stream',
Accept: 'application/octet-stream',
Authorization: `Bearer ${authToken}`
}
const response = await axios.get(url, { headers })
const blob = new Blob([response.data])
const downloadUrl = window.URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = downloadUrl
a.download = `${props.playlist.name}_${formattedDate}.xspf`
a.click()
a.remove()
}
const fileInputRef = ref<HTMLInputElement | null>(null)
const router = useRouter()
const deletePlaylist = async () => {
try {
await axios.delete(`playlists/${props.playlist.id}/`)
store.dispatch('playlists/fetchOwn')
return router.push({ path: '/library' })
} catch (error) {
useErrorHandler(error as Error)
}
}
const patchPlaylist = async () => {
const url = exportUrl.value
if (!fileInputRef.value || !fileInputRef.value.files || fileInputRef.value.files.length === 0) {
return
}
const file = fileInputRef.value.files[0]
const headers: Record<string, string> = {
'Content-Type': 'application/octet-stream'
}
await axios.patch(url, file, { headers })
emit('import')
}
// Function to trigger file input when clicking import
const triggerFileInput = () => {
fileInputRef.value?.click()
}
const open = ref(false)
const showEmbedModal = ref(false)
const showDeleteModal = ref(false)
</script>
<template>
<span>
<Popover v-model="open">
<template #default="{ toggleOpen }">
<OptionsButton
is-square-small
@click="toggleOpen"
/>
</template>
<template #items>
<PopoverItem
v-if="playlist.privacy_level === 'everyone' && playlist.is_playable"
icon="bi-code-slash"
@click="showEmbedModal = !showEmbedModal"
>
{{ t('views.playlists.Detail.button.embed') }}
</PopoverItem>
<PopoverItem
v-if="store.state.auth.profile && playlist.actor.full_username === store.state.auth.fullUsername"
destructive
icon="bi-trash"
:action="deletePlaylist"
@click="showDeleteModal = !showDeleteModal"
>
{{ t('views.playlists.Detail.button.delete') }}
</PopoverItem>
<PopoverItem
icon="bi-download"
@click="exportPlaylist"
>
{{ labels.export }}
</PopoverItem>
<PopoverItem
v-if="store.state.auth.authenticated && playlist.actor.full_username === store.state.auth.fullUsername"
icon="bi-upload"
@click="triggerFileInput"
>
{{ labels.import }}
</PopoverItem>
</template>
</Popover>
<!-- Hidden file input, triggered by the button click -->
<input
ref="fileInputRef"
type="file"
style="display: none"
@change="patchPlaylist"
>
</span>
<Modal
v-if="playlist.privacy_level === 'everyone' && playlist.is_playable"
v-model="showEmbedModal"
title="t('views.playlists.Detail.modal.embed.header')"
>
<div class="scrolling content">
<div class="description">
<embed-wizard
:id="playlist.id"
type="playlist"
/>
</div>
</div>
<template #actions>
<Button variant="outline">
{{ t('views.playlists.Detail.button.cancel') }}
</Button>
</template>
</Modal>
<Modal
v-model="showDeleteModal"
destructive
:title="t('views.playlists.Detail.modal.delete.header')"
>
<template #alert>
<Alert red>
<p>
{{ t('views.playlists.Detail.modal.delete.content.warning') }}
</p>
</Alert>
</template>
<template #actions>
<Button @click="showDeleteModal = false">
{{ t('views.playlists.Detail.button.cancel') }}
</Button>
<Button
destructive
@click="deletePlaylist"
>
{{ t('views.playlists.Detail.button.confirm') }}
</Button>
</template>
</Modal>
</template>