<template> <div> <b-modal ref="modalPlaylistSelector" title="Edit playlist for this timeslot" size="lg" > <p v-if="loaded"> Currently chosen playlist ID: <span v-if="timeslot === null || timeslot.playlist_id === null"> <i><small>(none set)</small></i> </span> <span v-else> {{ timeslot.playlist_id }} <span v-if="currentPlaylistDescription"> , Description: <b>{{ currentPlaylistDescription }}</b> </span> </span> </p> <p>Available playlists:</p> <div v-if="loaded"> <b-table ref="playlistsTable" striped :fields="playlistsTableFields" :items="playlists" > <!-- Column: Entries This column displays the number of entries of the playlist. --> <template v-slot:cell(entries)="data"> <span v-b-tooltip.html="playlistToolTip(data.value)" class="tw-underline hover:tw-no-underline tw-cursor-help" > {{ data.value.length }} items </span> </template> <!-- Column: Duration This column displays the number of entries of the playlist. --> <template v-slot:cell(duration)="data"> <span :class="{'is-mismatched': isMismatchedLength(data) }" > {{ playlistDuration(data) }} <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 v-slot:cell(actions)="data"> <b-button-group size="sm"> <b-button v-if="data.item.id !== timeslot.playlist_id" variant="info" @click="choose(data)" > Take it! </b-button> <b-button v-else variant="danger" @click="choose(null)" > Unset </b-button> </b-button-group> </template> </b-table> </div> <div v-else> <img src="/assets/radio.gif" alt="loading playlists" > </div> <div align="center"> <b-button :to="'files'"> Go to FileManager </b-button> </div> </b-modal> </div> </template> <script> import {mapGetters} from 'vuex' import prettyDate from '../../mixins/prettyDate' export default { mixins: [prettyDate], data() { return { scheduleId: null, timeslot: null, playlistsTableFields: [ {key: 'id', label: 'Index'}, {key: 'description', label: 'Description'}, {key: 'entries', label: 'Entries'}, {key: 'duration', label: 'Duration'}, {key: 'actions', label: 'Actions', class: 'text-right'}, ], } }, computed: { loaded() { return this.$store.state.playlists.loaded.playlists }, 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), 10 ) }, currentPlaylistDescription() { let description = false if (this.timeslot && this.timeslot.playlist_id !== null) { let 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', notes: 'shows/notes', playlists: 'playlists/playlists', getTimeslotById: 'shows/getTimeslotById', }) }, methods: { open(scheduleId, timeslotId) { 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("The playlist you have selected has a different length than the timeslot. Proceed?") } if (confirmed) { let 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 "Playlist is not the same length as the timeslot" } return "" }, playlistToolTip(entries) { let text = '<div style="white-space: nowrap;" align="left">' for (let i in entries) { text += i + ': ' + entries[i].uri + '<br>' } text += '</div>' return text }, playlistDuration({item}) { if (!item.entries) { return 0; } let delta = 0; const totalDuration = item.entries.reduce((acc, entry) => { const newDuration = acc + this.durationInSeconds(entry.duration); if (Number.isNaN(newDuration)) { return acc; } return newDuration }, 0) const unknowns = item.entries.filter(entry => !entry.duration); if (unknowns.length === 1) { delta = this.durationInSeconds(this.timeslotDurationInNs) - totalDuration; } const totalDurationInNanoseconds = (totalDuration + delta) * 1000 * 1000 * 1000 return this.prettyNanoseconds(totalDurationInNanoseconds) }, isMismatchedLength(playlist) { const totalDuration = this.playlistDuration(playlist); 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> <style scoped> .is-mismatched { color: var(--orange); } </style>