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

Merge branch 'feature-showmanager' into develop

parents 31c9a81c 1659c973
......@@ -21,17 +21,13 @@
<span v-if="loaded.shows">{{ shows[currentShow].short_description }}</span>
<img src="../assets/16x16/emblem-system.png" alt="edit short description" v-on:click="$refs.appModalShow.showShortDescription()" />
</template>
<p v-if="loaded.shows">
<div v-if="loaded.shows">
<b>Description:</b> <img src="../assets/16x16/emblem-system.png" alt="edit description" v-on:click="$refs.appModalShow.showDescription()" />
<div v-if="loaded.shows">
<!-- TODO: see if we can make a nice but secure html rendering of the description
This should be already secure, as long as you do not write directly to the DOM.
Only if you do this and render HTML, take care to have it save (no script tags etc.).
This current regex replace is only to have it looking nicely, in case there are html tags. -->
{{ shows[currentShow].description.replace(/<[^>]*>/g, '') }}
<p v-html="sanitizedShowDescription"></p>
<!-- TODO: add image and logo here? -->
</div>
</p>
</div>
</b-jumbotron>
<div v-if="!loaded.shows">
......@@ -74,7 +70,24 @@
<br />
<div v-if="loaded.timeslots">
<b-table striped hover outlined :items="notesTableArray"></b-table>
<b-table striped hover outlined :fields="notesTableArrayFields" :items="notesTableArray">
<template slot="title" slot-scope="data">
<span v-if="data.value">{{ data.value }}</span>
<span v-else><small><i>(none set)</i></small></span>
</template>
<template slot="starts" slot-scope="data">
{{ data.value }}
</template>
<template slot="duration" slot-scope="data">
{{ data.value }}
</template>
<template slot="options" slot-scope="data">
<span class="timeslotEditLink" @click="editTimeslotNote(data.item.options.id, data.item.options.schedule)"><img src="../assets/16x16/emblem-system.png" alt="Edit description" title="Edit description"></span>
<span class="timeslotEditLink" @click=""><img src="../assets/16x16/media-eject.png" alt="Upload audio file / Create playlist" title="Upload audio file / Create playlist"></span>
<span class="timeslotEditLink" @click=""><img src="../assets/16x16/media-playback-start.png" alt="Play audio file / playlist" title="Play audio file / playlist"></span>
<span class="timeslotEditLink" @click="">...</span>
</template>
</b-table>
<b-pagination align="center" :total-rows="current.timeslotmeta.count" :per-page="current.timeslotmeta.perpage" v-model="current.timeslotmeta.page" @change="timeslotsPage"></b-pagination>
</div>
<div v-else style="text-align: center;"><img src="../assets/radio.gif" alt="loading data" /><br /></div>
......@@ -171,7 +184,7 @@
</p>
<p v-else>
<ul>
<li v-for="cat in current.categories">{{ cat.category }}</li>
<li v-for="cat in current.categories" v-bind:key="cat.id">{{ cat.category }}</li>
</ul>
</p>
</div>
......@@ -188,7 +201,7 @@
</p>
<p v-else>
<ul>
<li v-for="topic in current.topics">{{ topic.topic }}</li>
<li v-for="topic in current.topics" v-bind:key="topic.id">{{ topic.topic }}</li>
</ul>
</p>
</div>
......@@ -205,7 +218,7 @@
</p>
<p v-else>
<ul>
<li v-for="focus in current.musicfocus">{{ focus.focus }}</li>
<li v-for="focus in current.musicfocus" v-bind:key="focus.id">{{ focus.focus }}</li>
</ul>
</p>
</div>
......@@ -222,7 +235,7 @@
</p>
<p v-else>
<ul>
<li v-for="lang in current.languages">{{ lang.name }}</li>
<li v-for="lang in current.languages" v-bind:key="lang.id">{{ lang.name }}</li>
</ul>
</p>
</div>
......@@ -240,7 +253,7 @@
<p v-else>
<!-- TODO: make link on name; when user clicks, open modal to edit host -->
<ul>
<li v-for="host in current.hosts">{{ host.name }}</li>
<li v-for="host in current.hosts" v-bind:key="host.id">{{ host.name }}</li>
</ul>
</p>
</div>
......@@ -248,6 +261,37 @@
</b-col>
</b-row>
<b-row>
<b-col lg="2">
<b-badge style="width:80%;">Logo:</b-badge> <img src="../assets/16x16/emblem-system.png" alt="edit" v-on:click="$refs.appModalShow.showLogo()" />
</b-col>
<b-col lg="4">
<div v-if="current.logo.length === 0">
<small><i>(none set)</i></small>
</div>
<div v-else>
<br />
<b-img thumbnail :src="current.logo" fluid v-on:click="$refs.appModalShow.showLogo()" />
</div>
</b-col>
<b-col lg="2">
<b-badge style="width:80%;">Image:</b-badge> <img src="../assets/16x16/emblem-system.png" alt="edit" v-on:click="$refs.appModalShow.showImage()" />
</b-col>
<b-col lg="4">
<div v-if="current.image.length === 0">
<small><i>(none set)</i></small>
</div>
<div v-else>
<br />
<b-img thumbnail :src="current.image" fluid v-on:click="$refs.appModalShow.showImage()" />
</div>
</b-col>
</b-row>
<hr />
</div>
</b-container>
</template>
......@@ -258,6 +302,7 @@ import modalShow from './ShowManagerModalShow.vue'
import timeslotSort from '../mixins/timeslotSort'
import prettyDate from '../mixins/prettyDate'
import axios from 'axios'
import DOMPurify from 'dompurify'
export default {
components: {
......@@ -301,12 +346,24 @@ export default {
perpage: 10
},
note: {},
notes: []
}
notes: [],
image: '',
logo: ''
},
notesTableArrayFields: [
{ key: 'title', label: 'Title of emission' },
{ key: 'starts', label: 'Emission start' },
{ key: 'duration', label: 'Duration' },
{ key: 'options', label: 'Edit' }
]
}
},
mixins: [ timeslotSort, prettyDate ],
computed: {
sanitizedShowDescription: function () {
//return this.shows[this.currentShow].description.replace(/<[^>]*>/g, '')
return DOMPurify.sanitize(this.shows[this.currentShow].description)
},
predecessorName: function () {
for (var i in this.shows) {
if (this.shows[i].id === this.shows[this.currentShow].predecessor) {
......@@ -324,13 +381,10 @@ export default {
title: note,
starts: this.prettyDateTime(this.current.timeslots[i].start),
duration: this.prettyDuration(this.current.timeslots[i].start, this.current.timeslots[i].end),
// TODO: find out how to insert images or iconffont icons into b-table rows
// options: '<img src="../assets/16x16/emblem-system.png" alt="edit note" v-on:click="' + this.editTimeslotNote(this.current.timeslots[i].id) + '" />'
options: '<span class="timeslotEditLink" onclick="' +
'document.getElementById(\'app\').children[1].__vue__.' +
'editTimeslotNote(' + this.current.timeslots[i].id + ', ' + this.current.timeslots[i].schedule + ')">edit</span> ' +
'<span class="timeslotEditLink" onclick="alert(\'notYetImplemented\')">upload</span>' +
'<span class="timeslotEditLink" onclick="alert(\'notYetImplemented\')">...</span>'
options: {
id: this.current.timeslots[i].id,
schedule: this.current.timeslots[i].schedule
}
})
}
return arr
......@@ -370,6 +424,12 @@ export default {
// set the current show and its ID to whatever we want to switch to now
this.currentShow = index
this.currentShowID = this.shows[this.currentShow].id
// and check if images are available and set image strings, because we
// cannot use them directly inside the b-img if they are null
if (this.shows[this.currentShow].logo === null) this.current.logo = ''
else this.current.logo = this.shows[this.currentShow].logo
if (this.shows[this.currentShow].image === null) this.current.image = ''
else this.current.image = this.shows[this.currentShow].image
// before we load timeslots and notes, we want to fetch the general settings first
this.getCategories()
this.getHosts()
......
......@@ -113,7 +113,7 @@ export default {
axios.put(uri, this.note, {
withCredentials: true,
headers: { 'Authorization': 'Bearer ' + this.$parent.$parent.user.access_token }
}).then(response => {
}).then(() => {
// everything was fine, we can close the modal now
this.$refs.modalNote.hide()
}).catch(error => {
......
......@@ -142,6 +142,44 @@
</b-row>
</b-modal>
<b-modal ref="modalLogo" title="Logo of this show" size="lg" @ok="saveLogo">
<b-row>
<b-col lg="4">
<p>Current logo:</p>
</b-col>
<b-col lg="8">
<p v-if="string.length === 0"><small><i>(none set)</i></small></p>
<p v-else>
<b-img thumbnail fluid :src="string" />
</p>
</b-col>
</b-row>
<b-row>
<b-col>
<b-form-file v-model="file" ref="fileinputLogo" accept="image/jpeg, image/png" :state="Boolean(file)" placeholder="Choose a new logo..."></b-form-file>
</b-col>
</b-row>
</b-modal>
<b-modal ref="modalImage" title="Image of this show" size="lg" @ok="saveImage">
<b-row>
<b-col lg="4">
<p>Current image:</p>
</b-col>
<b-col lg="8">
<p v-if="string.length === 0"><small><i>(none set)</i></small></p>
<p v-else>
<b-img thumbnail fluid :src="string" />
</p>
</b-col>
</b-row>
<b-row>
<b-col>
<b-form-file v-model="file" ref="fileinputImage" accept="image/jpeg, image/png" :state="Boolean(file)" placeholder="Choose a new image..."></b-form-file>
</b-col>
</b-row>
</b-modal>
</div>
</template>
......@@ -173,6 +211,7 @@ export default {
backupid: 0,
array: [],
backuparray: [],
file: null,
// we use this when opening modals, that have to fetch options through the API first.
loaded: false,
/*
......@@ -276,7 +315,7 @@ export default {
axios.put(process.env.VUE_APP_API_STEERING_SHOWS + this.show.id + '/', this.show, {
withCredentials: true,
headers: { 'Authorization': 'Bearer ' + this.$parent.$parent.user.access_token }
}).then(response => {
}).then(() => {
this.$refs.modalShowName.hide()
}).catch(error => {
this.show.name = this.backupstring
......@@ -294,7 +333,7 @@ export default {
axios.put(process.env.VUE_APP_API_STEERING_SHOWS + this.show.id + '/', this.show, {
withCredentials: true,
headers: { 'Authorization': 'Bearer ' + this.$parent.$parent.user.access_token }
}).then(response => {
}).then(() => {
this.$refs.modalShowShortDescription.hide()
}).catch(error => {
this.show.short_description = this.backupstring
......@@ -312,7 +351,7 @@ export default {
axios.put(process.env.VUE_APP_API_STEERING_SHOWS + this.show.id + '/', this.show, {
withCredentials: true,
headers: { 'Authorization': 'Bearer ' + this.$parent.$parent.user.access_token }
}).then(response => {
}).then(() => {
this.$refs.modalShowDescription.hide()
}).catch(error => {
this.show.description = this.backupstring
......@@ -330,7 +369,7 @@ export default {
axios.put(process.env.VUE_APP_API_STEERING_SHOWS + this.show.id + '/', this.show, {
withCredentials: true,
headers: { 'Authorization': 'Bearer ' + this.$parent.$parent.user.access_token }
}).then(response => {
}).then(() => {
this.$refs.modalShowEmail.hide()
}).catch(error => {
this.show.email = this.backupstring
......@@ -348,7 +387,7 @@ export default {
axios.put(process.env.VUE_APP_API_STEERING_SHOWS + this.show.id + '/', this.show, {
withCredentials: true,
headers: { 'Authorization': 'Bearer ' + this.$parent.$parent.user.access_token }
}).then(response => {
}).then(() => {
this.$refs.modalShowWebsite.hide()
}).catch(error => {
this.show.website = this.backupstring
......@@ -366,7 +405,7 @@ export default {
axios.put(process.env.VUE_APP_API_STEERING_SHOWS + this.show.id + '/', this.show, {
withCredentials: true,
headers: { 'Authorization': 'Bearer ' + this.$parent.$parent.user.access_token }
}).then(response => {
}).then(() => {
this.$refs.modalShowCBAid.hide()
}).catch(error => {
this.show.cba_series_id = this.backupid
......@@ -384,7 +423,7 @@ export default {
axios.put(process.env.VUE_APP_API_STEERING_SHOWS + this.show.id + '/', this.show, {
withCredentials: true,
headers: { 'Authorization': 'Bearer ' + this.$parent.$parent.user.access_token }
}).then(response => {
}).then(() => {
this.$parent.getType()
this.$refs.modalShowType.hide()
}).catch(error => {
......@@ -404,7 +443,7 @@ export default {
axios.put(process.env.VUE_APP_API_STEERING_SHOWS + this.show.id + '/', this.show, {
withCredentials: true,
headers: { 'Authorization': 'Bearer ' + this.$parent.$parent.user.access_token }
}).then(response => {
}).then(() => {
this.$refs.modalShowPredecessor.hide()
}).catch(error => {
this.show.predecessor = this.backupid
......@@ -422,7 +461,7 @@ export default {
axios.put(process.env.VUE_APP_API_STEERING_SHOWS + this.show.id + '/', this.show, {
withCredentials: true,
headers: { 'Authorization': 'Bearer ' + this.$parent.$parent.user.access_token }
}).then(response => {
}).then(() => {
this.$parent.getFundingCategory()
this.$refs.modalShowType.hide()
}).catch(error => {
......@@ -435,12 +474,13 @@ export default {
},
saveCategories (event) {
if (this.array.length !== this.show.category.length || !this.array.every((value, index) => value === this.show.category[index])) {
event.preventDefault()
this.backuparray = this.show.category
this.show.category = this.array
axios.put(process.env.VUE_APP_API_STEERING_SHOWS + this.show.id + '/', this.show, {
withCredentials: true,
headers: { 'Authorization': 'Bearer ' + this.$parent.$parent.user.access_token }
}).then(response => {
}).then(() => {
this.$parent.getCategories()
this.$refs.modalShowCategories.hide()
}).catch(error => {
......@@ -453,12 +493,13 @@ export default {
},
saveTopics (event) {
if (this.array.length !== this.show.topic.length || !this.array.every((value, index) => value === this.show.topic[index])) {
event.preventDefault()
this.backuparray = this.show.topic
this.show.topic = this.array
axios.put(process.env.VUE_APP_API_STEERING_SHOWS + this.show.id + '/', this.show, {
withCredentials: true,
headers: { 'Authorization': 'Bearer ' + this.$parent.$parent.user.access_token }
}).then(response => {
}).then(() => {
this.$parent.getTopics()
this.$refs.modalShowTopics.hide()
}).catch(error => {
......@@ -471,12 +512,13 @@ export default {
},
saveMusicFocus (event) {
if (this.array.length !== this.show.musicfocus.length || !this.array.every((value, index) => value === this.show.musicfocus[index])) {
event.preventDefault()
this.backuparray = this.show.musicfocus
this.show.musicfocus = this.array
axios.put(process.env.VUE_APP_API_STEERING_SHOWS + this.show.id + '/', this.show, {
withCredentials: true,
headers: { 'Authorization': 'Bearer ' + this.$parent.$parent.user.access_token }
}).then(response => {
}).then(() => {
this.$parent.getMusicfocus()
this.$refs.modalShowMusicFocus.hide()
}).catch(error => {
......@@ -489,12 +531,13 @@ export default {
},
saveLanguages (event) {
if (this.array.length !== this.show.language.length || !this.array.every((value, index) => value === this.show.language[index])) {
event.preventDefault()
this.backuparray = this.show.language
this.show.language = this.array
axios.put(process.env.VUE_APP_API_STEERING_SHOWS + this.show.id + '/', this.show, {
withCredentials: true,
headers: { 'Authorization': 'Bearer ' + this.$parent.$parent.user.access_token }
}).then(response => {
}).then(() => {
this.$parent.getLanguages()
this.$refs.modalShowLanguages.hide()
}).catch(error => {
......@@ -507,12 +550,13 @@ export default {
},
saveHosts (event) {
if (this.array.length !== this.show.hosts.length || !this.array.every((value, index) => value === this.show.hosts[index])) {
event.preventDefault()
this.backuparray = this.show.hosts
this.show.hosts = this.array
axios.put(process.env.VUE_APP_API_STEERING_SHOWS + this.show.id + '/', this.show, {
withCredentials: true,
headers: { 'Authorization': 'Bearer ' + this.$parent.$parent.user.access_token }
}).then(response => {
}).then(() => {
this.$parent.getHosts()
this.$refs.modalShowHosts.hide()
}).catch(error => {
......@@ -523,6 +567,57 @@ export default {
})
}
},
saveLogo (event) {
event.preventDefault()
if (this.file === null) alert('Please provide a file to upload')
else if (this.file.type !== 'image/jpeg' && this.file.type !== 'image/png') alert('Please provide a valid image file (JPEG or PNG)')
else {
let formData = new FormData()
this.backupstring = this.string
// work in progress:
// as it seems we have to use multipart webform data and add all items in order
// but this is still not working
// have to find out what exact format and encoding is needed to upload a show with files included
formData.append('name', this.show.name)
formData.append('slug', this.show.slug)
formData.append('image', this.show.image)
formData.append('logo', this.file, this.file.name);
formData.append('short_description', this.show.short_description)
formData.append('description', this.show.description)
formData.append('email', this.show.email)
formData.append('website', this.show.website)
formData.append('type', this.show.type)
formData.append('fundingcategory', this.show.fundingcategory)
formData.append('predecessor', this.show.predecessor)
formData.append('cba_series_id', this.show.cba_series_id)
formData.append('fallback_id', this.show.fallback_id)
formData.append('category', this.show.category)
formData.append('host', this.show.host)
formData.append('language', this.show.language)
formData.append('topic', this.show.topic)
formData.append('musicfocus', this.show.musicfocus)
axios.put(process.env.VUE_APP_API_STEERING_SHOWS + this.show.id + '/', formData, {
withCredentials: true,
headers: {
'Authorization': 'Bearer ' + this.$parent.$parent.user.access_token,
'Content-Type': 'multipart/form-data'
}
}).then(() => {
//this.$parent.getHosts()
this.$refs.modalLogo.hide()
}).catch(error => {
this.file = null
this.string = this.backupstring
console.log('Error:')
console.log(error)
alert('Error: could not save the new logo')
})
}
},
saveImage (event) {
alert('Not yet implemented')
console.log(event)
},
/*
Functions to activate modals
They are called from the parent component
......@@ -666,6 +761,20 @@ export default {
alert('Error: could not load available hosts')
})
this.$refs.modalShowHosts.show()
},
showLogo () {
if (this.show.logo === null) this.string = ''
else this.string = this.show.logo
this.file = null
this.$refs.fileinputLogo.reset()
this.$refs.modalLogo.show()
},
showImage () {
if (this.show.image === null) this.string = ''
else this.string = this.show.image
this.file = null
this.$refs.fileinputImage.reset()
this.$refs.modalImage.show()
}
}
}
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment