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

improve FileManager structure & comments

parent 975ab7bd
<template> <template>
<b-container> <b-container>
<!-- This first row is so far only used to provide a dropdown for
choosing one of the loaded shows (which the user has access to) -->
<b-row> <b-row>
<b-col> <b-col>
<h3>Dateien und Playlists</h3> <h3>Dateien und Playlists</h3>
...@@ -21,6 +23,10 @@ ...@@ -21,6 +23,10 @@
</b-col> </b-col>
</b-row> </b-row>
<hr> <hr>
<!-- The jumbotron is used to display the name and short description of the
currently selected show and let the user choose between editing files and
playlists -->
<b-jumbotron> <b-jumbotron>
<template slot="header"> <template slot="header">
<span v-if="loaded.shows"> <span v-if="loaded.shows">
...@@ -52,7 +58,10 @@ ...@@ -52,7 +58,10 @@
</div> </div>
</b-jumbotron> </b-jumbotron>
<!-- All the UI for uploading and editing files is only shown if the user
choose to edit files in the jumbotron above -->
<div v-if="mode === 'files'"> <div v-if="mode === 'files'">
<!-- Only display a spinner if the files are not loaded yet -->
<div v-if="!loaded.files"> <div v-if="!loaded.files">
<b-row> <b-row>
<b-col align="center"> <b-col align="center">
...@@ -63,12 +72,22 @@ ...@@ -63,12 +72,22 @@
</b-col> </b-col>
</b-row> </b-row>
</div> </div>
<!-- If all file data is loaded we can present an upload button and
a table of files (if there are already any or if there are any already
being uploaded/imported )-->
<div v-else> <div v-else>
<!-- 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 <b-modal
id="modal-add-file" id="modal-add-file"
title="Add new file" title="Add new file"
@ok="addFile" @ok="addFile"
> >
<!-- If the checkbox below is checked we only need a simple input
for entering an URI, instead of a complete file upload dialogue -->
<div v-if="addNewFileURI"> <div v-if="addNewFileURI">
<b-row> <b-row>
<b-col md="2"> <b-col md="2">
...@@ -83,6 +102,8 @@ ...@@ -83,6 +102,8 @@
</b-col> </b-col>
</b-row> </b-row>
</div> </div>
<!-- If the checkbox is not checked, we provide a file selection
dialogue instead of a simple input for an URI -->
<div v-else> <div v-else>
<b-form-file <b-form-file
v-model="uploadSourceFile" v-model="uploadSourceFile"
...@@ -91,7 +112,11 @@ ...@@ -91,7 +112,11 @@
drop-placeholder="Drop file here..." drop-placeholder="Drop file here..."
/> />
</div> </div>
<hr> <hr>
<!-- This checkbox determines whether there is just a simple input
or a file selection dialogue (see above) -->
<div align="center"> <div align="center">
<b-form-checkbox <b-form-checkbox
v-model="addNewFileURI" v-model="addNewFileURI"
...@@ -102,12 +127,18 @@ ...@@ -102,12 +127,18 @@
</b-form-checkbox> </b-form-checkbox>
</div> </div>
</b-modal> </b-modal>
<!-- Modal: Edit meta information
This modal lets the user change a file's meta information (artist,
album, title). When the user hits OK, the saveFile method is called.
-->
<b-modal <b-modal
id="modal-edit-file" id="modal-edit-file"
title="Edit meta information" title="Edit meta information"
size="lg" size="lg"
@ok="saveFile" @ok="saveFile"
> >
<!-- Input element: Artist -->
<b-row> <b-row>
<b-col md="2"> <b-col md="2">
Artist: Artist:
...@@ -119,6 +150,8 @@ ...@@ -119,6 +150,8 @@
/> />
</b-col> </b-col>
</b-row> </b-row>
<!-- Input element: Album -->
<b-row> <b-row>
<b-col md="2"> <b-col md="2">
Album: Album:
...@@ -130,6 +163,8 @@ ...@@ -130,6 +163,8 @@
/> />
</b-col> </b-col>
</b-row> </b-row>
<!-- Input element: Title -->
<b-row> <b-row>
<b-col md="2"> <b-col md="2">
Title: Title:
...@@ -141,7 +176,13 @@ ...@@ -141,7 +176,13 @@
/> />
</b-col> </b-col>
</b-row> </b-row>
<hr> <hr>
<!-- As additional orientation we provide the source this file was
uploaded/imported from. This might help the user to manage all files
and set the meta information accordingly.
-->
<b-row> <b-row>
<b-col md="2"> <b-col md="2">
Sourced from: Sourced from:
...@@ -149,6 +190,9 @@ ...@@ -149,6 +190,9 @@
<b-col>{{ temp.uri }}</b-col> <b-col>{{ temp.uri }}</b-col>
</b-row> </b-row>
</b-modal> </b-modal>
<!-- Now for the files. If there are none yet, just show an alert and
a button for uploading/importing a new file -->
<div <div
v-if="files.length === 0" v-if="files.length === 0"
align="center" align="center"
...@@ -166,7 +210,11 @@ ...@@ -166,7 +210,11 @@
Upload or add a file Upload or add a file
</b-button> </b-button>
</div> </div>
<!-- If there already are files for this show we'll show the button
for uploading/importing a new file and then a table with all files -->
<div v-else> <div v-else>
<!-- This is the button -->
<div <div
align="center" align="center"
style="padding-bottom: 1.5em;" style="padding-bottom: 1.5em;"
...@@ -178,12 +226,24 @@ ...@@ -178,12 +226,24 @@
Upload or add a file Upload or add a file
</b-button> </b-button>
</div> </div>
<!-- And here comes the table -->
<b-table <b-table
ref="filesTable" ref="filesTable"
striped striped
:fields="filesTableFields" :fields="filesTableFields"
:items="files" :items="files"
> >
<!-- The first two columns in the table (Index & Artist) are filled
in automatically, because we do not use these fields for displaying
upload/import progress information.
-->
<!-- Column: Album
This column displays either the album meta information or, in case
the file is just being uploaded/imported, a spinner visualising an
ongoing import.
-->
<template <template
slot="metadata.album" slot="metadata.album"
slot-scope="data" slot-scope="data"
...@@ -195,6 +255,12 @@ ...@@ -195,6 +255,12 @@
></span> ></span>
<span v-else>{{ data.value }}</span> <span v-else>{{ data.value }}</span>
</template> </template>
<!-- Column: Title
This column displays either the title meta information or, in case
the file is just being uploaded/imported, the current phase
of the ongoing import (fetching or normalizing).
-->
<template <template
slot="metadata.title" slot="metadata.title"
slot-scope="data" slot-scope="data"
...@@ -202,40 +268,49 @@ ...@@ -202,40 +268,49 @@
<span v-if="data.item.source.import.state === 'done'">{{ data.value }}</span> <span v-if="data.item.source.import.state === 'done'">{{ data.value }}</span>
<span v-else-if="data.item.source.import.progress !== undefined">{{ data.item.source.import.progress.step }} :</span> <span v-else-if="data.item.source.import.progress !== undefined">{{ data.item.source.import.progress.step }} :</span>
</template> </template>
<!-- Column: Duration
This column displays either the duration of the audio file, or, if
the file is just being uploaded/imported, the current progress.
-->
<template <template
slot="duration" slot="duration"
slot-scope="data" slot-scope="data"
> >
<!-- In case the import is already done just print a pretty duration -->
<div v-if="data.item.source.import.state === 'done'"> <div v-if="data.item.source.import.state === 'done'">
{{ prettyNanoseconds(data.value) }} {{ prettyNanoseconds(data.value) }}
</div> </div>
<!-- Or print the progress for ongoing imports. We use the variant
prop of the b-progress to display the bar in different colours -
either the info variant for the fetching phase or the success
variant for the normalizing phase -->
<div v-else-if="data.item.source.import.progress !== undefined"> <div v-else-if="data.item.source.import.progress !== undefined">
<div v-if="data.item.source.import.progress.step === 'fetching'"> <b-progress
<b-progress :value="data.item.source.import.progress.progress"
:value="data.item.source.import.progress.progress" :max="1"
:max="1" show-progress
show-progress :variant="data.item.source.import.progress.step === 'fetching' ? 'info' : 'success'"
variant="info" animated
animated />
/>
</div>
<div v-else>
<b-progress
:value="data.item.source.import.progress.progress"
:max="1"
show-progress
variant="success"
animated
/>
</div>
</div> </div>
</template> </template>
<!-- Column: Size
This column displays the size of the audio file, if the file is
already fully imported. Otherwise we'll just leave it empty.
-->
<template <template
slot="size" slot="size"
slot-scope="data" slot-scope="data"
> >
<span v-if="data.item.source.import.state === 'done'">{{ prettyFileSize(data.value) }}</span> <span v-if="data.item.source.import.state === 'done'">{{ prettyFileSize(data.value) }}</span>
</template> </template>
<!-- Column: Actions
This column displays the available button for actions the user can
take on this file (e.g. editing and deleting).
-->
<template <template
slot="actions" slot="actions"
slot-scope="data" slot-scope="data"
...@@ -254,10 +329,14 @@ ...@@ -254,10 +329,14 @@
</template> </template>
</b-table> </b-table>
</div> </div>
<!-- End of files table -->
</div> </div>
</div> </div>
<!-- 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'"> <div v-if="mode === 'playlists'">
<!-- Only display a spinner if the playlists are not loaded yet -->
<div v-if="!loaded.playlists"> <div v-if="!loaded.playlists">
<b-row> <b-row>
<b-col align="center"> <b-col align="center">
...@@ -268,6 +347,9 @@ ...@@ -268,6 +347,9 @@
</b-col> </b-col>
</b-row> </b-row>
</div> </div>
<!-- If all playlist data is loaded we can present an create button and
a table of playlists (if there are already any)-->
<div v-else> <div v-else>
<div <div
v-if="playlists.length === 0" v-if="playlists.length === 0"
...@@ -303,7 +385,11 @@ import filesize from 'filesize' ...@@ -303,7 +385,11 @@ import filesize from 'filesize'
import prettyDate from '../mixins/prettyDate' import prettyDate from '../mixins/prettyDate'
export default { export default {
// generic functions that we want to use from our mixins folder
mixins: [ prettyDate ], mixins: [ prettyDate ],
// the data this component will be handling: mostly flags and local versions
// of the data fetched from the AuRa tank API
data () { data () {
return { return {
shows: [], // an array of objects describing our shows (empty at load, will be populated on created()) shows: [], // an array of objects describing our shows (empty at load, will be populated on created())
...@@ -312,25 +398,35 @@ export default { ...@@ -312,25 +398,35 @@ export default {
files: [], files: [],
playlists: [], playlists: [],
mode: 'files', mode: 'files',
addNewFileURI: false,
uploadSourceURI: '',
uploadSourceFile: null,
uploadInterval: null, uploadInterval: null,
// some flags to signal if API data is already fully loaded
loaded: { loaded: {
shows: false, shows: false,
files: false, files: false,
playlists: false playlists: false
}, },
// variables used by file upload/import modal
addNewFileURI: false,
uploadSourceURI: '',
uploadSourceFile: null,
// we need this for the modal to edit a file's meta information
temp: { temp: {
id: null, id: null,
artist: '', artist: '',
album: '', album: '',
title: '' title: ''
}, },
// for formatting the buttons - this way we could customize it later
button: { button: {
files: 'info', files: 'info',
playlists: 'outline-info' playlists: 'outline-info'
}, },
// configuration of the files table, which will use the files array as data
filesTableFields: [ filesTableFields: [
{ key: 'id', label: 'Index' }, { key: 'id', label: 'Index' },
{ key: 'metadata.artist', label: 'Artist' }, { key: 'metadata.artist', label: 'Artist' },
...@@ -342,6 +438,9 @@ export default { ...@@ -342,6 +438,9 @@ export default {
] ]
} }
}, },
// TODO: if we can use the playlists object itself (similar to the files in
// in the files table, we can get rid of the computed properties)
computed: { computed: {
playlistsTable: function (){ playlistsTable: function (){
var arr = [] var arr = []
...@@ -356,6 +455,11 @@ export default { ...@@ -356,6 +455,11 @@ export default {
return arr return arr
} }
}, },
// Right after this component is set up, we want to fetch all available shows
// from the AuRa tank module. This works quite similar to the ShowManager.
// We also want to load the files and playlists as soon as the shows are
// loaded.
created () { created () {
// when we enter this module, we want to load all shows of the current user // when we enter this module, we want to load all shows of the current user
// before we search for corresponding shows in the tank // before we search for corresponding shows in the tank
...@@ -383,13 +487,24 @@ export default { ...@@ -383,13 +487,24 @@ export default {
alert('There was an error fetching shows from steering: ' + error) alert('There was an error fetching shows from steering: ' + error)
}) })
}, },
// Now for our hotchpotch of methods, mostly for fetching data from and
// posting/updating data to the AuRa tank API
methods: { methods: {
// Just a placeholder function we can use in the UI, to signal if something
// is not yet implemented
notYetImplemented: function () { notYetImplemented: function () {
alert('By the mighty witchcraftry of the mother of time!\n\nThis feature is not implemented yet.') 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) { prettyFileSize: function (s) {
return filesize(s) return filesize(s)
}, },
// Returns a file object from the files array, given a file ID
getFileById: function (id) { getFileById: function (id) {
for (var i in this.files) { for (var i in this.files) {
if (this.files[i].id === id) { if (this.files[i].id === id) {
...@@ -398,6 +513,10 @@ export default { ...@@ -398,6 +513,10 @@ export default {
} }
return null return null
}, },
// To start modifying the meta information for a file we have to set our
// temporary data (which will be used to check if anything changed) and
// then open the editing modal
editFile: function (id) { editFile: function (id) {
var file = this.getFileById(id) var file = this.getFileById(id)
this.temp.id = file.id this.temp.id = file.id
...@@ -407,6 +526,10 @@ export default { ...@@ -407,6 +526,10 @@ export default {
this.temp.uri = file.source.uri this.temp.uri = file.source.uri
this.$bvModal.show('modal-edit-file') this.$bvModal.show('modal-edit-file')
}, },
// Once the OK button is hit in the file editing modal, we have to check
// if anything changed and then send an appropriate metadata obecjt to
// the AuRa tank API to update it
saveFile: function (){ saveFile: function (){
var file = this.getFileById(this.temp.id) var file = this.getFileById(this.temp.id)
// we only want to send a PATCH request if some metadata actually changed // we only want to send a PATCH request if some metadata actually changed
...@@ -432,6 +555,9 @@ export default { ...@@ -432,6 +555,9 @@ export default {
}) })
} }
}, },
// Deletes a file with a specific, calling the AuRa tank API and afterwards
// fetching a fresh list of files from it.
deleteFile: function (id) { deleteFile: function (id) {
var uri = process.env.VUE_APP_API_TANK + 'shows/' + this.shows[this.currentShow].slug + '/files/' + id var uri = process.env.VUE_APP_API_TANK + 'shows/' + this.shows[this.currentShow].slug + '/files/' + id
// TODO: add mechanism to indicate the running delete request in the files table // TODO: add mechanism to indicate the running delete request in the files table
...@@ -446,6 +572,12 @@ export default { ...@@ -446,6 +572,12 @@ export default {
alert('Error: could not delete file. See console log for details.') alert('Error: could not delete file. See console log for details.')
}) })
}, },
// With this function we add a new file in the AuRa tank by calling its API.
// Depending on wheter we add a remote file which tank then imports by itself,
// or if we want to upload a local file, we source-uri has to look different.
// And for uploading a local file this is just the first step. Afterwards the
// actual upload has to be started with the startUpload function.
addFile: function () { addFile: function () {
var uri = process.env.VUE_APP_API_TANK + 'shows/' + this.shows[this.currentShow].slug + '/files' var uri = process.env.VUE_APP_API_TANK + 'shows/' + this.shows[this.currentShow].slug + '/files'
if (this.addNewFileURI) { if (this.addNewFileURI) {
...@@ -483,6 +615,10 @@ export default { ...@@ -483,6 +615,10 @@ export default {
alert('Something is wrong. You have choosen to upload a file, but the corresponding file object does not exist.') alert('Something is wrong. You have choosen to upload a file, but the corresponding file object does not exist.')
} }
}, },
// When a new file was added with the addFile function we can start an upload
// fetching the import endpoint of this file and then call the upload
// function, which atually puts the file onto the server.
startUpload: function (id) { startUpload: function (id) {
var uri = process.env.VUE_APP_API_TANK + 'shows/' + this.shows[this.currentShow].slug + '/files/' + id + '/import' var uri = process.env.VUE_APP_API_TANK + 'shows/' + this.shows[this.currentShow].slug + '/files/' + id + '/import'
axios.get(uri, { axios.get(uri, {
...@@ -497,6 +633,9 @@ export default { ...@@ -497,6 +633,9 @@ export default {
alert('Error: could not start the file upload. See console log for details.') alert('Error: could not start the file upload. See console log for details.')
}) })
}, },
// Upload a file to the AuRa tank API - given it was created with the addFile
// and started with the startUpload methods.
upload: function (id) { upload: function (id) {
/* /*
* NOTE: there is no npm package for flow.js and importing it manually did not * NOTE: there is no npm package for flow.js and importing it manually did not
...@@ -538,12 +677,18 @@ export default { ...@@ -538,12 +677,18 @@ export default {
alert('Error: could not start the file upload. See console log for details.') alert('Error: could not start the file upload. See console log for details.')
}) })
}, },
// This switches the UI elements to reflect another show and fetches all
// relevent data from the tank API.
switchShow: function (index) { switchShow: function (index) {
// set the current show and its ID to whatever we want to switch to now // set the current show and its ID to whatever we want to switch to now
this.currentShow = index this.currentShow = index
this.currentShowID = this.shows[this.currentShow].id this.currentShowID = this.shows[this.currentShow].id
this.fetchShow(this.shows[this.currentShow].slug) this.fetchShow(this.shows[this.currentShow].slug)
}, },