Commit 30a9c80d authored by jackie / Andrea Ida Malkah Klaura's avatar jackie / Andrea Ida Malkah Klaura
Browse files

move all playlist related actions to store

parent 772b16bd
This diff is collapsed.
<template>
<b-modal
id="modal-edit-playlist"
size="lg"
:title="playlistEditor.mode === 'edit' ? 'Edit playlist' : 'Add new playlist'"
@ok="storePlaylist"
>
<b-row
v-if="playlistEditor.mode === 'edit'"
style="padding-bottom: 1em;"
>
<b-col
cols="3"
>
Playlist index:
</b-col>
<b-col>{{ playlistEditor.id }}</b-col>
</b-row>
<b-row>
<b-col cols="3">
Description:
</b-col>
<b-col>
<b-form-input
v-model="playlistEditor.description"
type="text"
/>
</b-col>
</b-row>
<hr>
<p>Playlist entries:</p>
<!-- If no entries are here (i.e. we add a new playlist), only show
a hint that there's nothing here yet. -->
<div
v-if="playlistEditor.entries.length === 0"
align="center"
>
No entries yet. Add some.
</div>
<!-- As soon as we have at least one entry in our temporary playlist
we can display a table with all the info and action buttons -->
<div v-else>
<b-table
ref="playlistEditTable"
striped
:items="playlistEditor.entries"
:fields="playlistEditTableFields"
>
<!-- Column: Index
Here we just use the array index, because the playlist entries
are ordered as an array, without the need for an extra id field
-->
<template v-slot:cell(id)="data">
{{ data.index + 1 }}.
</template>
<!-- Column: Type
Based on the entry content (either file or uri), we display
a small badge indicating which type of source this is
-->
<template v-slot:cell(type)="data">
<b-badge
v-if="data.item.file"
variant="success"
>
File
</b-badge>
<b-badge
v-else-if="data.item.uri.startsWith('line://')"
variant="info"
>
Line-in
</b-badge>
<b-badge
v-else-if="data.item.uri.startsWith('http://') || data.item.uri.startsWith('https://')"
variant="light"
>
Stream
</b-badge>
<b-badge
v-else
variant="dark"
>
Other
</b-badge>
</template>
<!-- Column: Source
Here we display where this playlist entry is coming from
-->
<template v-slot:cell(source)="data">
<span v-if="data.item.file">
{{ getFileTitleForPlaylist(data.item.file.show, data.item.file.id) }}
<small><i>( file://{{ data.item.file.show }}/{{ data.item.file.id }} )</i></small>
</span>
<span v-else>
{{ data.item.uri }}
</span>
</template>
<!-- Column: Actions
Finally some buttons to reorder or delete playlist entries
-->
<template v-slot:cell(actions)="data">
<b-button-group size="sm">
<b-button
:disabled="data.index === 0"
@click="movePlaylistItemUp(data.index)"
>
<b class="upDownArrows">&uarr;</b>
</b-button>
<b-button
:disabled="data.index === playlistEditor.entries.length - 1"
@click="movePlaylistItemDown(data.index)"
>
<b class="upDownArrows">&darr;</b>
</b-button>
<b-button
variant="danger"
@click="deletePlaylistItem(data.index)"
>
Delete
</b-button>
</b-button-group>
</template>
</b-table>
</div>
<hr>
<!-- Below the table with the playlists entry we display buttons to
add new entries to the table - these can either be files from our
uploaded/imported files, or one of the preconfigured inputs, or a
stream.
TODO: should we disable choosing files that are still being imported?
TODO: make the inputs configurable
-->
<div>
<b-modal
id="modal-edit-playlist-add-stream"
title="Add stream to the playlist"
@ok="addPlaylistItemStream('save')"
>
<b-input
v-model="playlistEditor.newStreamURL"
type="url"
>
...
</b-input>
</b-modal>
Add:
<b-button-group>
<b-dropdown text="File">
<b-dropdown-item
v-for="(file, index) in files"
:key="index"
@click="addPlaylistItemFile(file.show, file.id)"
>
{{ file.id }}: {{ file.metadata.title ? file.metadata.title : "" }} ({{ prettyNanoseconds(file.duration) }}, {{ prettyFileSize(file.size) }}, {{ file.source.uri }})
</b-dropdown-item>
</b-dropdown>
<b-dropdown text="Line-in">
<b-dropdown-item @click="addPlaylistItemLine('0')">
Studio 1
</b-dropdown-item>
<b-dropdown-item @click="addPlaylistItemLine('1')">
Preprod
</b-dropdown-item>
<b-dropdown-item @click="addPlaylistItemLine('2')">
Line 2
</b-dropdown-item>
</b-dropdown>
<b-button
@click="addPlaylistItemStream('openModal')"
>
Stream
</b-button>
</b-button-group>
</div>
</b-modal>
</template>
<script>
import { mapGetters } from 'vuex'
import prettyDate from '../../mixins/prettyDate'
import filesize from 'filesize'
export default {
mixins: [ prettyDate ],
// include the modal for displaying file import logs
data () {
return {
// for adding and editing the playlists we need some temporary storage
playlistEditor: {
id: null,
mode: 'add', // should be either 'add' or 'edit'
description: '',
entries: [],
newStreamURL: null
},
// configuration of the playlists entries table
playlistEditTableFields: [
{ key: 'id', label: 'Index' },
{ key: 'type', label: 'Type' },
{ key: 'source', label: 'Source' },
{ key: 'actions', label: 'Actions', class: 'text-right' },
]
}
},
computed: {
loaded () {
return {
playlists: this.$store.state.playlists.loaded.playlists,
}
},
...mapGetters({
selectedShow: 'shows/selectedShow',
getPlaylistById: 'playlists/getPlaylistById',
files: 'files/files',
getFileById: 'files/getFileById',
})
},
methods: {
// We want to have a nice format for the file size, given a size in Bytes.
// For that we're useing the filesize npm module.
prettyFileSize: function (s) {
return filesize(s)
},
// return a string representing a file entry for the playlist editor
getFileTitleForPlaylist (show, id) {
// TODO: change structure of files array, so we can access all shows
// the user has access to.
let file = this.getFileById(id)
if (file && file.metadata.title) {
return show + ": " + file.metadata.title
} else {
return ""
}
},
movePlaylistItemUp (index) {
if (index > 0 && index < this.playlistEditor.entries.length) {
let temp = this.playlistEditor.entries[index - 1]
this.playlistEditor.entries[index - 1] = this.playlistEditor.entries[index]
this.playlistEditor.entries[index] = temp
this.$refs.playlistEditTable.refresh()
}
},
movePlaylistItemDown (index) {
if (index < this.playlistEditor.entries.length - 1 && index >= 0) {
let temp = this.playlistEditor.entries[index + 1]
this.playlistEditor.entries[index + 1] = this.playlistEditor.entries[index]
this.playlistEditor.entries[index] = temp
this.$refs.playlistEditTable.refresh()
}
},
deletePlaylistItem (index) {
if (index >= 0 && index < this.playlistEditor.entries.length) {
this.playlistEditor.entries.splice(index, 1)
this.$refs.playlistEditTable.refresh()
}
},
// add a file from the file manager to the playlist that is being edited
addPlaylistItemFile (show, id) {
let item = {}
item.file = {show: show, id: id}
this.playlistEditor.entries.push(item)
},
// add a line input to the playlist that is being edited
addPlaylistItemLine (line) {
this.playlistEditor.entries.push({uri: 'line://' + line})
},
// controls sub-modal to add a new URI to the playlist editor
addPlaylistItemStream (action) {
if (action === 'openModal') {
// the function gets called with the action 'openModal' when the users
// clicks on the add new stream button. then we clear all temp data and
// open the modal
this.playlistEditor.newStreamURL = ''
this.$bvModal.show('modal-edit-playlist-add-stream')
} else {
// when the user hits ok to add the new uri, this function gets called
// with the action 'save' and the modal closes automagically. so we
// just have to push a new entry to the playlist in the editor
if (this.playlistEditor.newStreamURL.trim().length > 0) {
this.playlistEditor.entries.push({uri: this.playlistEditor.newStreamURL})
}
// if an empty string was provided, we just do nothing
}
},
addPlaylist () {
this.playlistEditor.mode = 'add'
this.playlistEditor.id = null
this.playlistEditor.description = ''
this.playlistEditor.entries = []
this.$bvModal.show('modal-edit-playlist')
},
editPlaylist (id) {
this.playlistEditor.mode = 'edit'
this.playlistEditor.entries = []
this.playlistEditor.id = id
let playlist = this.getPlaylistById(id)
this.playlistEditor.description = playlist.description
for (let i in playlist.entries) {
let entry = {}
if (playlist.entries[i].file) {
entry.file = {}
entry.file.show = playlist.entries[i].file.show
entry.file.id = playlist.entries[i].file.id
} else {
entry.uri = playlist.entries[i].uri
}
this.playlistEditor.entries.push(entry)
}
this.$bvModal.show('modal-edit-playlist')
},
storePlaylist () {
// TODO: add some spinner or other visualisation while the playlist is added/updated
let playlist = {
description: this.playlistEditor.description,
entries: this.playlistEditor.entries
}
if (this.playlistEditor.mode === 'add') {
this.$store.dispatch('playlists/add', {
slug: this.selectedShow.slug,
playlist: playlist
})
} else if (this.playlistEditor.mode === 'edit') {
this.$store.dispatch('playlists/update', {
slug: this.selectedShow.slug,
id: this.playlistEditor.id,
playlist: playlist,
callback: () => {
this.$root.$emit('bv::refresh::table', 'playlistsTable')
}
})
}
},
},
}
</script>
<template>
<div>
<edit-playlists-modal ref="editPlaylistsModal" />
<!-- Only display a spinner if the playlists are not loaded yet -->
<div v-if="!loaded.playlists">
<b-row>
<b-col align="center">
<img
src="../../assets/radio.gif"
alt="loading data"
>
</b-col>
</b-row>
</div>
<!-- If all playlist data is loaded we can present a create button and
a table of playlists (if there are already any)-->
<div v-else>
<!-- If no playlists are available, only show an alert and a create button -->
<div
v-if="playlists.length === 0"
align="center"
>
<b-alert
show
variant="warning"
>
There are no playlists for this show yet.
</b-alert>
<b-button
variant="success"
@click="addPlaylist"
>
Create a playlist
</b-button>
</div>
<!-- In case there are playlists, show the button and then the table -->
<div v-else>
<div
align="center"
style="padding-bottom: 1.5em;"
>
<b-button
variant="success"
@click="addPlaylist"
>
Create a playlist
</b-button>
</div>
<b-table
id="playlistsTable"
striped
:fields="playlistsTableFields"
:items="playlists"
>
<!-- Column: Entries
This column displays the number of entries of the playlist.
-->
<template v-slot:cell(entries)="data">
{{ data.value.length }} items
<b-button
v-b-tooltip.html="playlistToolTip(data.value)"
variant="outline-success"
size="sm"
>
show entries
</b-button>
</template>
<!-- Column: Last edit
This column lists the last date this playlist was changed.
-->
<template v-slot:cell(updated)="data">
{{ prettyDateTime(data.value) }}
</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 @click="editPlaylist(data.item.id)">
Edit
</b-button>
<b-button
variant="danger"
@click="deletePlaylist(data.item.id)"
>
Delete
</b-button>
</b-button-group>
</template>
</b-table>
</div>
<!-- End of playlists table -->
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import prettyDate from '../../mixins/prettyDate'
import editPlaylistsModal from './EditPlaylistModal.vue'
export default {
components: {
'edit-playlists-modal': editPlaylistsModal,
},
mixins: [ prettyDate ],
// include the modal for displaying file import logs
data () {
return {
// for adding and editing the playlists we need some temporary stuff
playlistEditor: {
id: null,
mode: 'add', // should be either 'add' or 'edit'
description: '',
entries: [],
newStreamURL: null
},
// configuration of the playlists table, which will use the playlists array as data
playlistsTableFields: [
{ key: 'id', label: 'Index' },
{ key: 'description', label: 'Description' },
{ key: 'entries', label: 'Entries' },
{ key: 'updated', label: 'Last edit' },
{ key: 'actions', label: 'Actions', class: 'text-right' },
],
playlistEditTableFields: [
{ key: 'id', label: 'Index' },
{ key: 'type', label: 'Type' },
{ key: 'source', label: 'Source' },
{ key: 'actions', label: 'Actions', class: 'text-right' },
]
}
},
computed: {
loaded () {
return {
playlists: this.$store.state.playlists.loaded.playlists,
}
},
...mapGetters({
selectedShow: 'shows/selectedShow',
playlists: 'playlists/playlists',
})
},
methods: {
playlistToolTip (entries) {
let text = '<div style="white-space: nowrap;" align="left">'
for (var i in entries) {
text += i + ': ' + entries[i].uri + '<br>'
}
text += '</div>'
return text
},
deletePlaylist (id) {
// TODO: add mechanism to indicate the running delete request in the files table
this.$store.dispatch('playlists/delete', {
slug: this.selectedShow.slug,
id: id
})
},
addPlaylist () {
this.$refs.editPlaylistsModal.addPlaylist()
},
editPlaylist (id) {
this.$refs.editPlaylistsModal.editPlaylist(id)
},
},
}
</script>
import axios from 'axios'
import handleApiError from '../handleApiError'
const state = {
playlists: [],
loaded: {
playlists: false,
}
}
const mutations = {
loading(state, item) { state.loaded[item] = false },
finishLoading(state, item) { state.loaded[item] = true },
setPlaylists (state, lists) { state.playlists = lists },
addPlaylist (state, list) { state.playlists.push(list) },
updatePlaylist (state, data) {
let i = state.playlists.findIndex(p => p.id === data.id)
if (i >= 0) { state.playlists[i] = data.playlist }
else { console.log('[ERROR] updatePlaylist: trying to update a non-existing list') }
},
deletePlaylist (state, id) {
let i = state.playlists.findIndex(p => p.id === id)
if (i >= 0) { state.playlists.splice(i, 1) }
else { console.log('[ERROR] deletePlaylist: trying to delete a non-existing list') }
}
}
const getters = {
playlists: state => state.playlists,
playlistCount: state => state.playlists.length,
getPlaylistById: state => id => {
let list
if (id !== undefined) {
list = state.playlists.find(l => l.id === id)
if (list === undefined) {
console.log('[ERROR] getPlaylistById: ID not found in store!')
}
}
return list
}
}
const actions = {
}
fetch (ctx, data) {
ctx.commit('loading', 'playlists')
let uri = process.env.VUE_APP_API_TANK + 'shows/' + data.slug + '/playlists'
axios.get(uri, {
withCredentials: true,
headers: { 'Authorization': 'Bearer ' + ctx.rootState.auth.user.access_token }
}).then(response => {
// we don't have to check separately, if there are playlists, because tank
// always provides an empty array if there are no playlists (or even if there is no corresponding show)
ctx.commit('setPlaylists', response.data.results)
ctx.commit('finishLoading', 'playlists')
if (data && typeof(data.callback) === 'function