<template> <div> <b-modal ref="modalPlaylistSelector" :title="$t('playlistSelector.title')" :cancel-title="$t('cancel')" size="lg" > <p v-if="loaded"> {{ $t('playlistSelector.currentPlaylistLabel') }}: <span v-if="timeslot === null || timeslot.playlist_id === null"> <i ><small>{{ $t('noneSetFeminine') }}</small></i > </span> <span v-else> {{ timeslot.playlist_id }}<br /> <span v-if="currentPlaylistDescription"> {{ $t('showMeta.description') }}: <b>{{ currentPlaylistDescription }}</b></span > </span> </p> <div v-if="loaded"> <div v-if="playlists.length"> <b-table ref="playlistsTable" striped :fields="playlistsTableFields" :items="playlists"> <!-- Column: Entries This column displays the number of entries of the playlist. --> <template #cell(entries)="data"> <span v-b-tooltip.html="playlistToolTip(data.value)" class="tw-underline hover:tw-no-underline tw-cursor-help" > {{ $t('playlistTable.items', { smart_count: data.value.length }) }} </span> </template> <!-- Column: Duration This column displays the number of entries of the playlist. --> <template #cell(duration)="data"> <span :class="{ 'is-mismatched': isMismatchedLength(data) }"> {{ playlistDuration(data.item) }} <abbr v-if="isMismatchedLength(data)" :title="title(data)"> (?) </abbr> </span> </template> <!-- Column: Actions This column displays the available buttons for actions the user can take on this playlist (e.g. editing and deleting). --> <template #cell(actions)="data"> <b-button-group size="sm"> <b-button v-if="data.item.id !== timeslot.playlist_id" variant="info" @click="choose(data)" > {{ $t('playlistTable.assign') }} </b-button> <b-button v-else variant="danger" @click="choose(null)"> {{ $t('playlistTable.unset') }} </b-button> </b-button-group> </template> </b-table> </div> <!-- If no playlists are available --> <div v-else class="tw-mb-4"> {{ $t('playlistSelector.noPlaylistsAvailable') }} </div> </div> <div v-else> <img src="/assets/radio.gif" :alt="$t('loading')" /> </div> <div> <div class="tw-space-x-2"> <b-button :to="'files'"> {{ $t('playlistSelector.goToFiles') }} </b-button> <b-button v-if="!audioUpload" v-model="audioFile" @click="toggleAudioUpload"> {{ $t('playlistSelector.uploadAudio') }} </b-button> </div> <div v-if="audioUpload" class="tw-mt-4 tw-space-x-2 tw-flex"> <b-form-file v-model="audioFile" accept="audio/*" /> <b-button :disabled="!audioFile" @click="upload"> {{ $t('playlistSelector.upload') }} </b-button> </div> </div> </b-modal> </div> </template> <script> import { mapGetters } from 'vuex' import prettyDate from '@/mixins/prettyDate' import playlist from '@/mixins/playlist' export default { mixins: [prettyDate, playlist], data() { return { scheduleId: null, timeslot: null, audioFile: null, audioUpload: false, audioUploadError: '', } }, computed: { loaded() { return this.$store.state.playlists.loaded.playlists }, playlistsTableFields() { return [ { key: 'id', label: this.$t('playlistTable.index') }, { key: 'description', label: this.$t('playlistTable.description') }, { key: 'entries', label: this.$t('playlistTable.entries') }, { key: 'duration', label: this.$t('playlistTable.duration') }, { key: 'actions', label: this.$t('playlistTable.actions'), class: 'text-right' }, ] }, timeslotDurationInNs() { const mm = this.timeslotDuration % 60 const hh = (this.timeslotDuration - mm) / 60 return this.hmsToNanoseconds(`${this.leadingZero(hh)}:${this.leadingZero(mm)}:00`) }, timeslotDuration() { const { start, end } = this.timeslot return parseInt(this.prettyDuration(start, end).minutes, 10) }, currentPlaylistDescription() { let description = false if (this.timeslot && this.timeslot.playlist_id !== null) { const choosenList = this.playlists.find((list) => list.id === this.timeslot.playlist_id) if (choosenList && choosenList.description.length > 0) { description = choosenList.description } } return description }, ...mapGetters({ selectedShow: 'shows/selectedShow', timeslots: 'shows/timeslots', playlists: 'playlists/playlists', getTimeslotById: 'shows/getTimeslotById', }), }, methods: { upload(event) { event.preventDefault() event.stopPropagation() if (!this.audioFile) { this.audioUploadError = this.$t('playlistSelector.missingFile') return } this.audioUpload = false this.$store.dispatch('files/addFile', { show: this.selectedShow.slug, uploadSourceFile: this.audioFile, callback: this.handleUploadedFile, }) }, handleUploadedFile() { this.$store.dispatch('files/fetchFiles', { slug: this.selectedShow.slug, callback: this.createPlaylistForUploadedFile, }) }, createPlaylistForUploadedFile(files) { const file = files.slice(-1)[0] const { slug } = this.selectedShow const playlist = { description: 'Automatisch erstellt durch Dateiupload', entries: [{ file }], } this.$store.dispatch('playlists/add', { slug, playlist, }) }, toggleAudioUpload() { this.audioUpload = !this.audioUpload }, open(scheduleId, timeslotId) { this.audioFile = null this.audioUpload = false this.scheduleId = scheduleId this.timeslot = this.getTimeslotById(timeslotId) this.$refs.modalPlaylistSelector.show() }, choose(data) { const { item } = data || {} const { id } = item || {} let confirmed = true if (data && this.isMismatchedLength(data)) { confirmed = confirm(this.$t('playlistSelector.mismatchedLengthConfirmation')) } if (confirmed) { const ts = { ...this.timeslot } ts.playlist_id = id this.$store.dispatch('shows/updateTimeslot', { show: this.selectedShow.id, schedule: this.scheduleId, timeslot: ts, callback: () => { this.timeslot = this.getTimeslotById(ts.id) }, }) } }, title(data) { if (this.isMismatchedLength(data)) { return this.$t('playlistSelector.mismatchedLength') } return '' }, playlistToolTip(entries) { let text = '<div style="white-space: nowrap;" align="left">' for (const i in entries) { text += i + ': ' + entries[i].uri + '<br>' } text += '</div>' return text }, isMismatchedLength(playlist) { const totalDuration = this.playlistDuration(playlist.item) let delta = 0 const unknowns = (playlist.item.entries ?? []).filter((entry) => !entry.duration) if (unknowns.length === 1) { delta = this.timeslotDurationInNs - totalDuration } return this.timeslotDurationInNs !== totalDuration + delta }, }, } </script>