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
......@@ -17,372 +17,43 @@
<!-- All the UI for creating and editing playlists is only shown if the user
choose to edit playlists in the jumbotron above -->
<div v-if="mode === 'playlists'">
<!-- Modal: Add new file
This is the modal that is used when the user clicks on the
"Upload or add a file" button. When the user hits the OK button,
the addFile method will be called. -->
<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>
<!-- 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
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">
{{ 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>
<playlists />
</div>
</b-container>
</template>
<script>
import { mapGetters } from 'vuex'
import axios from 'axios'
import filesize from 'filesize'
import prettyDate from '../mixins/prettyDate'
import showSelector from './ShowSelector.vue'
import jumbotron from './filemanager/Jumbotron.vue'
import files from './filemanager/Files.vue'
import playlists from './filemanager/Playlists.vue'
export default {
components: {
'show-selector': showSelector,
'jumbotron': jumbotron,
'files': files,
'playlists': playlists,
},
// generic functions that we want to use from our mixins folder
mixins: [ prettyDate ],
// the data this component will be handling: mostly flags and local versions
// of the data fetched from the AuRa tank API
data () {
return {
shows: [], // an array of objects describing our shows (empty at load, will be populated on created())
currentShow: 0, // index of the currently selected show in our shows array
currentShowID: 0, // actual id of the currently selected show
files: [],
playlists: [],
mode: 'files',
uploadInterval: null,
// some flags to signal if API data is already fully loaded
loadedLocal: {
playlists: false
},
// and 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
},
// for formatting the buttons - this way we could customize it later
button: {
files: 'info',
playlists: 'outline-info'
},
// 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 {
shows: this.$store.state.shows.loaded.shows,
files: this.$store.state.files.loaded.files,
playlists: this.loadedLocal.playlists
}
},
...mapGetters({
selectedShow: 'shows/selectedShow',
mode: 'fileManagerMode'
})
},
......@@ -400,203 +71,6 @@ export default {
// posting/updating data to the AuRa tank API
methods: {
// Just a placeholder function we can use in the UI, to signal if something
// is not yet implemented
notYetImplemented: function () {
alert('By the mighty witchcraftry of the mother of time!\n\nThis feature is not implemented yet.')
},
// 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)
},
// Returns a file object from the files array, given a file ID
getFileById: function (id) {
for (var i in this.files) {
if (this.files[i].id === id) {
return this.files[i]
}
}
return null
},
// Returns a playlist object from the playlists array, given a playlist ID.
// If the mode parameter is used and set to 'index', then the playlists
// index in the playlist array is returned instead of the object
getPlaylistById: function (id, mode) {
for (var i in this.playlists) {
if (this.playlists[i].id === id) {
if (mode === 'index') { return i }
else { return this.playlists[i] }
}
}
return null
},
// return a string representing a file entry for the playlist editor
getFileTitleForPlaylist: function (show, id) {
// TODO: change structure of files array, so we can access all shows
// the user has access to.
var file = this.getFileById(id)
if (file && file.metadata.title) {
return show + ": " + file.metadata.title
} else {
return ""
}
},
playlistToolTip: function (entries) {
var text = '<div style="white-space: nowrap;" align="left">'
for (var i in entries) {
text += i + ': ' + entries[i].uri + '<br>'
}
text += '</div>'
return text
},
movePlaylistItemUp: function (index) {
if (index > 0 && index < this.playlistEditor.entries.length) {
var 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: function (index) {
if (index < this.playlistEditor.entries.length - 1 && index >= 0) {
var 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: function (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: function (show, id) {
var item = {}
item.file = {show: show, id: id}
this.playlistEditor.entries.push(item)
},
// add a line input to the playlist that is being edited
addPlaylistItemLine: function (line) {
this.playlistEditor.entries.push({uri: 'line://' + line})
},
// controls sub-modal to add a new URI to the playlist editor
addPlaylistItemStream: function (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
}
},
deletePlaylist: function (id) {
var uri = process.env.VUE_APP_API_TANK + 'shows/' + this.shows[this.currentShow].slug + '/playlists/' + id
// TODO: add mechanism to indicate the running delete request in the files table
axios.delete(uri, {
withCredentials: true,
headers: { 'Authorization': 'Bearer ' + this.$parent.user.access_token },
}).then(
this.fetchPlaylists(this.shows[this.currentShow].slug)
).catch(error => {
this.$log.error(error.response.status + ' ' + error.response.statusText)
this.$log.error(error.response)
alert('Error: could not delete playlist. See console for details.')
})
},
addPlaylist: function () {
this.playlistEditor.mode = 'add'
this.playlistEditor.id = null
this.playlistEditor.description = ''
this.playlistEditor.entries = []
this.$bvModal.show('modal-edit-playlist')
},
editPlaylist: function (id) {
this.playlistEditor.mode = 'edit'
this.playlistEditor.entries = []
this.playlistEditor.id = id
var playlist = this.getPlaylistById(id)
this.playlistEditor.description = playlist.description
for (var i in playlist.entries) {
var 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')
},
// Adds a new or updates an existing playlist by sending a POST/PUT
// request to the AURA tank API
storePlaylist: function () {
var data = {
description: this.playlistEditor.description,
entries: this.playlistEditor.entries
}
var uri = process.env.VUE_APP_API_TANK + 'shows/' + this.shows[this.currentShow].slug + '/playlists'
// TODO: add some spinner or other visualisation while the playlist is added/updated
if (this.playlistEditor.mode === 'add') {
axios.post(uri, data, {
withCredentials: true,
headers: { 'Authorization': 'Bearer ' + this.$parent.user.access_token }
}).then(response => {
this.playlists.push(response.data)
}).catch(error => {
this.$log.error(error.response.status + ' ' + error.response.statusText)
this.$log.error(error.response)
alert('Error: could not add new playlist. See console for details.')
})
// when updating an existing playlist we use a PUT request and add the id
// of the playlist to the uri
} else if (this.playlistEditor.mode === 'edit') {
var id = this.playlistEditor.id
uri += '/' + id
axios.put(uri, data, {
withCredentials: true,
headers: { 'Authorization': 'Bearer ' + this.$parent.user.access_token }
}).then(response => {
this.playlists[this.getPlaylistById(id, 'index')] = response.data
this.$refs.playlistsTable.refresh()
}).catch(error => {
this.$log.error(error.response.status + ' ' + error.response.statusText)
this.$log.error(error.response)
alert('Error: could not update playlist. See console for details.')
})
}
},
// This switches the UI elements to reflect another show and fetches all
// relevent data from the tank API.
showHasSwitched () {
......@@ -604,27 +78,11 @@ export default {
this.$store.dispatch('files/fetchFiles', {
slug: this.selectedShow.slug
})
this.fetchPlaylists(this.selectedShow.slug)
this.$store.dispatch('playlists/fetch', {
slug: this.selectedShow.slug
})
},
// Fetch all playlists for a given show from the AuRa tank API
fetchPlaylists: function (slug) {
this.loadedLocal.playlists = false
var uri = process.env.VUE_APP_API_TANK + 'shows/' + slug + '/playlists'
axios.get(uri, {
withCredentials: true,
headers: { 'Authorization': 'Bearer ' + this.$parent.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)
this.playlists = response.data.results
this.loadedLocal.playlists = true
}).catch(error => {
this.$log.error(error.response.status + ' ' + error.response.statusText)
this.$log.error(error.response)
alert('Error: could not fetch playlists from tank. See console for details.')
})
}
}
}
</script>
......