ShowManager.vue 22.8 KB
Newer Older
1
<template>
2
  <b-container>
3
4
5
6
7
8
9
10
11
12
13
    <b-row>
      <b-col align="right">
        <b-dropdown id="ddshows" text="Sendereihe auswählen" variant="info">
          <b-dropdown-item v-for="(show, index) in this.shows" :key="show.id" v-on:click="switchShow(index)">{{ show.name }}</b-dropdown-item>
        </b-dropdown>
      </b-col>
    </b-row>
    <hr />

    <b-jumbotron>
      <template slot="header">
14
15
        <span v-if="loaded.shows">
          {{ shows[currentShow].name }}
16
          <img src="../assets/16x16/emblem-system.png" alt="edit name of show" v-on:click="$refs.appModalShow.showName()" />
17
        </span>
18
19
20
        <span v-else>Shows are being loaded</span>
      </template>
      <template slot="lead">
21
        <span v-if="loaded.shows">{{ shows[currentShow].short_description }}</span>
22
        <img src="../assets/16x16/emblem-system.png" alt="edit short description" v-on:click="$refs.appModalShow.showShortDescription()" />
23
      </template>
24
      <p v-if="loaded.shows">
25
        <b>Description:</b> <img src="../assets/16x16/emblem-system.png" alt="edit description" v-on:click="$refs.appModalShow.showDescription()" />
26
        <div v-if="loaded.shows">
27
28
29
30
31
          <!-- 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, '') }}
32
33
34
35
36
          <!-- TODO: add image and logo here? -->
        </div>
      </p>
    </b-jumbotron>

37
    <div v-if="!loaded.shows">
38
39
40
      <b-row>
        <b-col align="center">
          <img src="../assets/radio.gif" alt="loading data" />
41
42
        </b-col>
      </b-row>
43
44
    </div>
    <div v-else>
45
      <app-modalShow ref="appModalShow" v-bind:show="shows[currentShow]"></app-modalShow>
46
47
      <app-modalNotes ref="appModalNotes" v-bind:note="current.note"></app-modalNotes>

48
49
50
51
52
53
54
55
56
      <p>
        The next <b>{{ numSlots }}</b> timeslots from <b>{{ prettyDate(dateSlotsStart) }}</b> to <b>{{ prettyDate(dateSlotsEnd) }}</b>:
      </p>
      <div v-if="loaded.timeslots">
        <b-table striped hover outlined :items="notesTableArray"></b-table>
      </div>
      <div v-else style="text-align: center;"><img src="../assets/radio.gif" alt="loading data" /><br /></div>

      <!-- This is the old old-school view for timeslots; remove when all functions are implmented in table above
57
      <div v-if="loaded.timeslots">
58
59
        <b-row>
          <b-col>
60
            <div v-for="timeslot in this.timeslotsFutureShow">
61
62
              <img v-if="timeslot.playlist_id === null" src="../assets/16x16/go-top.png" alt="choose a playlist for this episode" v-on:click="notYetImplemented" />
              <img v-else src="../assets/16x16/media-playback-start.png" alt="play" v-on:click="notYetImplemented" />
63
              {{ prettyDateTime(timeslot.start) }} <small>(Duration: {{ prettyDuration(timeslot.start, timeslot.end) }})</small>
64
              <span v-if="loaded.notes">{{ prettyTimeslotNote(timeslot.id) }}</span>
65
              <span v-else style="background: ../assets/radio.gif"></span>
66
              <img src="../assets/16x16/emblem-system.png" alt="edit note" v-on:click="editNote(timeslot.id)" />
67
            </div>
68
69
          </b-col>
        </b-row>
70
      </div>
71
      <div v-else style="text-align: center;"><img src="../assets/radio.gif" alt="loading data" /><br /></div>
72
      -->
73

74
75
      <hr />

76
      <div v-if="loaded.shows">
77
        <h2>Allgemeine Einstellungen zur Sendereihe:</h2>
78
        <b-row>
79
80

          <b-col lg="6">
81
            <p>
82
              <b-badge variant="light">E-Mail:</b-badge>
83
84
              <span v-if="shows[currentShow].email === null"><small><i>(none set)</i></small></span>
              <span v-else>{{ shows[currentShow].email }}</span>
85
              <img src="../assets/16x16/emblem-system.png" alt="edit contact e-mail" v-on:click="$refs.appModalShow.showEmail()" />
86
87
            </p>
          </b-col>
88
89

          <b-col lg="6">
90
            <p>
91
              <b-badge variant="light">Website:</b-badge>
92
93
              <span v-if="shows[currentShow].website === null"><small><i>(none set)</i></small></span>
              <span v-else><a :href="shows[currentShow].website">{{ shows[currentShow].website }}</a></span>
94
              <img src="../assets/16x16/emblem-system.png" alt="edit website" v-on:click="$refs.appModalShow.showWebsite()" />
95
96
            </p>
          </b-col>
97
98

          <b-col lg="6">
99
            <p>
100
              <b-badge variant="light">Show type:</b-badge>
101
              <!-- TODO: discuss: should this be visible to show owners or only to administrators? -->
102
103
104
              <span v-if="loaded.type">
                <span v-if="current.type.length === 0"><small><i>(none set)</i></small></span>
                <span v-else>{{ current.type[0].type }}</span>
105
                <img src="../assets/16x16/emblem-system.png" alt="edit" v-on:click="notYetImplemented" />
106
107
              </span>
              <span v-else><img src="../assets/radio.gif" height="24px" alt="loading data" /></span>
108
109
            </p>
          </b-col>
110
111

          <b-col lg="6">
112
            <p>
113
              <b-badge variant="light">Funding category (eg. for RTR):</b-badge>
114
              <!-- TODO: discuss: should this be visible to show owners or only to administrators? -->
115
116
117
              <span v-if="loaded.rtrcategory">
                <span v-if="current.rtrcategory.length === 0"><small><i>(none set)</i></small></span>
                <span v-else>{{ current.rtrcategory[0].rtrcategory }}</span>
118
                <img src="../assets/16x16/emblem-system.png" alt="edit" v-on:click="notYetImplemented" />
119
120
              </span>
              <span v-else><img src="../assets/radio.gif" height="24px" alt="loading data" /></span>
121
122
            </p>
          </b-col>
123
124

          <b-col lg="6">
125
126
127
            <p>
              <!-- TODO: discuss: should this be visible to show owners or only to administrators? -->
              <!-- TODO: fetch name for predecessor_id from steering api -->
128
              <b-badge variant="light">Predecessor:</b-badge>
129
130
131
132
133
              <span v-if="shows[currentShow].predecessor_id === null"><small><i>This show has no predecessor show.</i></small></span>
              <span v-else>{{ shows[currentShow].predecessor_id }}</span>
              <img src="../assets/16x16/emblem-system.png" alt="edit" v-on:click="notYetImplemented" />
            </p>
          </b-col>
134
135

          <b-col lg="6">
136
            <p>
137
              <b-badge variant="light">CBA Series ID:</b-badge>
138
139
              <span v-if="shows[currentShow].cba_series_id === null"><small><i>(none set)</i></small></span>
              <span v-else>{{ shows[currentShow].cba_series_id }}</span>
140
              <img src="../assets/16x16/emblem-system.png" alt="edit CBA series ID" v-on:click="$refs.appModalShow.showCBAid()" />
141
142
            </p>
          </b-col>
143
144

          <b-col lg="6">
145
            <p>
146
              <b-badge variant="light">Fallback List/Pool:</b-badge>
147
148
              <span v-if="shows[currentShow].fallback_id === ''"><small><i>(none set)</i></small></span>
              <span v-else>ID: {{ shows[currentShow].fallback_id }}</span>
149
150
151
              <img src="../assets/16x16/emblem-system.png" alt="edit" v-on:click="notYetImplemented" />
            </p>
          </b-col>
152
153
154
155
156
157

        </b-row>

        <b-row>

          <b-col lg="2">
158
            <b-badge style="width:80%;">Categories:</b-badge> <img src="../assets/16x16/emblem-system.png" alt="edit" v-on:click="notYetImplemented" />
159
          </b-col>
160
161
162
163
164
165
166
167
168
169
170
171
          <b-col lg="4">
            <div v-if="loaded.categories">
              <p v-if="shows[currentShow].category.length === 0">
                <small><i>(none set)</i></small>
              </p>
              <p v-else>
                <ul>
                  <li v-for="cat in current.categories">{{ cat.category }}</li>
                </ul>
              </p>
            </div>
            <div v-else><img src="../assets/radio.gif" height="24px" alt="loading data" /><br /></div>
172
          </b-col>
173
174

          <b-col lg="2">
175
            <b-badge style="width:80%;">Topics:</b-badge> <img src="../assets/16x16/emblem-system.png" alt="edit" v-on:click="notYetImplemented" />
176
          </b-col>
177
          <b-col lg="4">
178
179
            <div v-if="loaded.topics">
              <p v-if="shows[currentShow].topic.length === 0">
180
181
182
183
                <small><i>(none set)</i></small>
              </p>
              <p v-else>
                <ul>
184
                  <li v-for="topic in current.topics">{{ topic.topic }}</li>
185
186
187
188
                </ul>
              </p>
            </div>
            <div v-else><img src="../assets/radio.gif" height="24px" alt="loading data" /><br /></div>
189
          </b-col>
190
191

          <b-col lg="2">
192
            <b-badge style="width:80%;">Musicfocus:</b-badge> <img src="../assets/16x16/emblem-system.png" alt="edit" v-on:click="notYetImplemented" />
193
194
          </b-col>
          <b-col lg="4">
195
196
            <div v-if="loaded.musicfocus">
              <p v-if="shows[currentShow].musicfocus.length === 0">
197
198
199
200
                <small><i>(none set)</i></small>
              </p>
              <p v-else>
                <ul>
201
                  <li v-for="focus in current.musicfocus">{{ focus.focus }}</li>
202
203
204
205
                </ul>
              </p>
            </div>
            <div v-else><img src="../assets/radio.gif" height="24px" alt="loading data" /><br /></div>
206
          </b-col>
207
208

          <b-col lg="2">
209
            <b-badge style="width:80%;">Languages:</b-badge> <img src="../assets/16x16/emblem-system.png" alt="edit" v-on:click="notYetImplemented" />
210
211
          </b-col>
          <b-col lg="4">
212
213
            <div v-if="loaded.languages">
              <p v-if="shows[currentShow].language.length === 0">
214
215
216
217
                <small><i>(none set)</i></small>
              </p>
              <p v-else>
                <ul>
218
                  <li v-for="lang in current.languages">{{ lang.name }}</li>
219
220
221
222
223
224
225
                </ul>
              </p>
            </div>
            <div v-else><img src="../assets/radio.gif" height="24px" alt="loading data" /><br /></div>
          </b-col>

          <b-col lg="2">
226
            <b-badge style="width:80%;">Hosts:</b-badge> <img src="../assets/16x16/emblem-system.png" alt="edit" v-on:click="notYetImplemented" />
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
          </b-col>
          <b-col lg="4">
            <div v-if="loaded.hosts">
              <p v-if="shows[currentShow].hosts.length === 0">
                <small><i>(none set)</i></small>
              </p>
              <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>
                </ul>
              </p>
            </div>
            <div v-else><img src="../assets/radio.gif" height="24px" alt="loading data" /><br /></div>
          </b-col>

243
        </b-row>
244
      </div>
245
    </div>
246
  </b-container>
247
248
249
</template>

<script>
250
import modalNotes from './ShowManagerModalNotes.vue'
251
import modalShow from './ShowManagerModalShow.vue'
252
import timeslotSort from '../mixins/timeslotSort'
253
import prettyDate from '../mixins/prettyDate'
254
import axios from 'axios'
255
256

export default {
257
  components: {
258
259
    'app-modalNotes': modalNotes,
    'app-modalShow': modalShow
260
  },
261
262
  data () {
    return {
263
264
      shows: [],      // an array of objects describing our shows (empty at load, will be populated on created())
      timeslots: [],  // same as with shows, only for the related timeslots
265
266
267
268
269
      timeslotmeta: { // meta info when pagination is used
        count: 0,
        next: null,
        previous: null
      },
270
271
272
      notes: [],      // same as with shows, only for the related notes
      currentShow: 0,   // index of the currently selected show in our shows array
      currentShowID: 0, // actual id of the currently selected show
273
      numUpcoming: 8,
274
      numRecent: 8,
275
276
277
      numSlots: 10,
      dateSlotsStart: new Date(),
      dateSlotsEnd: new Date(new Date().getTime() + 60 * 86400000),
278
279
280
281
282
283
284
285
286
      loaded: {
        shows: false,
        timeslots: false,
        notes: false,
        categories: false,
        hosts: false,
        languages: false,
        topics: false,
        musicfocus: false,
287
288
        rtrcategory: false,
        type: false
289
290
291
292
293
294
295
      },
      current: {
        categories: [],
        hosts: [],
        languages: [],
        topics: [],
        musicfocus: [],
296
        rtrcategory: [],
297
298
        type: [],
        note: {}
299
      }
300
301
    }
  },
302
  mixins: [ timeslotSort, prettyDate ],
303
  computed: {
304
305
306
307
308
309
310
311
312
313
314
315
316
    notesTableArray: function () {
      var arr = []
      for (var i in this.timeslots) {
        arr.push({
          title: this.getTimeslotNoteTitle(this.timeslots[i].id),
          starts: this.prettyDateTime(this.timeslots[i].start),
          duration: this.prettyDuration(this.timeslots[i].start, this.timeslots[i].end) + 'min',
          // options: '<img src="../assets/16x16/emblem-system.png" alt="edit note" v-on:click="' + this.editNote(this.timeslots[i].id) + '" />'
          options: '<a href="#" onclick="alert(1)"><span class="oi" data-glyph="wrench" title="wrench" aria-hidden="true">edit</span></a>'
        })
      }
      return arr
    },
317
318
319
    timeslotsFutureShow: function () {
      if (this.numUpcoming === 'all') return this.timeslotsFuture
      else return this.timeslotsFuture.slice(0, this.numUpcoming)
320
    },
321
322
323
324
    timeslotsPastShow: function () {
      // if (this.numRecent === 'all') return this.timeslotsPast
      // else return this.timeslotsPast.slice(0, this.numRecent)
      return this.timeslotsPast
325
326
327
    }
  },
  methods: {
328
    switchShow: function (index) {
329
330
331
      // if we already had some show loaded with timeslots and notes, set these to
      // not loaded, so we don't display old timeslots and notes while already
      // the new show is displayed and new timeslots and notes are still loading
332
333
334
335
336
337
338
339
340
      this.loaded.timeslots = false
      this.loaded.notes = false
      // also for those settings of the show which are only ids or arrays of ids
      // we have to fetch the corresponding names first
      this.loaded.categories = false
      this.loaded.hosts = false
      this.loaded.languages = false
      this.loaded.musicfocus = false
      this.loaded.rtrcategory = false
341
      // set the current show and its ID to whatever we want to switch to now
342
      this.currentShow = index
343
      this.currentShowID = this.shows[this.currentShow].id
344
345
346
347
348
349
350
      // before we load timeslots and notes, we want to fetch the general settings first
      this.getCategories()
      this.getHosts()
      this.getLanguages()
      this.getTopics()
      this.getMusicfocus()
      this.getRTRCategory()
351
      this.getType()
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
      // now fetch the timeslots (including notes) for a given show from PV backend
      this.getTimeslots(this.dateSlotsStart, this.dateSlotsEnd, this.numSlots)
    },
    getTimeslots: function (start, end, limit, offset) {
      var uri = process.env.API_STEERING_SHOWS + this.currentShowID + '/timeslots/?'
      if (typeof start === 'string') uri += 'start=' + start
      if (typeof end === 'string') uri += '&end=' + end
      if (typeof limit === 'number') uri += '&limit=' + limit
      if (typeof offset === 'number') uri += '&offset=' + offset
      console.log(uri)
      axios.get(uri, {withCredentials: true}).then(response => {
        this.timeslots = response.data.results
        this.timeslotmeta.count = response.data.count
        this.timeslotmeta.next = response.data.next
        this.timeslotmeta.previous = response.data.previous
367
        this.loaded.timeslots = true
368
369
370
371
        // now that we have the timeslots we can fetch notes for all those timeslots
        // TODO: curently we are fetching all notes for the show into a single array
        // for bigger data sets it might be preferable to fetch only the notes for those
        // timeslots that are also visible to the user
372
373
        // TODO: discuss: when a timeslot can only have one not, why is the id
        // of this note not stored in the timeslot? would be way more efficient
374
        axios.get(process.env.API_STEERING_SHOWS + this.currentShowID + '/notes/', {withCredentials: true}).then(response => {
375
376
          this.notes = response.data
          this.loaded.notes = true
377
378
379
380
381
382
        }).catch(error => {
          alert('There was an error fetching notes from the server' + error)
        })
        // done fetching notes
      }).catch(error => {
        alert('There was an error fetching timeslots from the server' + error)
383
      })
384
385
      // done fetching timeslots
    },
386
387
388
389
390
391
392
393
394
    editNote: function (timeslotID) {
      for (var n in this.notes) {
        if (this.notes[n].timeslot === timeslotID) {
          this.current.note = this.notes[n]
          break
        }
      }
      this.$refs.appModalNotes.$refs.modalNote.show()
    },
395
    getTimeslotNoteTitle: function (timeslotID) {
396
397
398
399
400
401
      for (var n in this.notes) {
        if (this.notes[n].timeslot === timeslotID && this.notes[n].title !== undefined) {
          return this.notes[n].title
        }
      }
      return null
402
    },
403
    prettyTimeslotNote: function (timeslotID) {
404
      var note = this.getTimeslotNoteTitle(timeslotID)
405
406
407
408
409
410
      if (note !== null) {
        return this.prettyTitle(note)
      } else {
        return ''
      }
    },
411
412
413
414
415
    prettyTitle: function (title) {
      if (title === '') return '...'
      else if (title.length > 25) return title.slice(0, 25) + '...'
      else return title
    },
416
417
418
419
420
421
422
423
424
425
    // TODO: all thos getSomething functions could be probably merged into one
    // generic getItem function. Maybe this.current should be an associative array
    // instead of an object then?
    getCategories: function () {
      this.current.categories = []
      var loadingError = false
      if (this.shows[this.currentShow].category.length === 0) {
        this.loaded.categories = true
      } else {
        for (var i in this.shows[this.currentShow].category) {
426
          axios.get(process.env.API_STEERING + 'categories/' + this.shows[this.currentShow].category[i], {withCredentials: true}).then(response => {
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
            this.current.categories.push(response.data)
          }).catch(error => {
            loadingError = true
            alert('There was an error fetching categories from the server: ' + error)
          })
        }
        if (!loadingError) this.loaded.categories = true
      }
    },
    getHosts: function () {
      this.current.hosts = []
      var loadingError = false
      if (this.shows[this.currentShow].hosts.length === 0) {
        this.loaded.hosts = true
      } else {
        for (var i in this.shows[this.currentShow].hosts) {
443
          axios.get(process.env.API_STEERING + 'hosts/' + this.shows[this.currentShow].hosts[i] + '/', {withCredentials: true}).then(response => {
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
            this.current.hosts.push(response.data)
          }).catch(error => {
            loadingError = true
            alert('There was an error fetching hosts from the server: ' + error)
          })
        }
        if (!loadingError) this.loaded.hosts = true
      }
    },
    getLanguages: function () {
      this.current.languages = []
      var loadingError = false
      if (this.shows[this.currentShow].language.length === 0) {
        this.loaded.categories = true
      } else {
        for (var i in this.shows[this.currentShow].language) {
460
          axios.get(process.env.API_STEERING + 'languages/' + this.shows[this.currentShow].language[i], {withCredentials: true}).then(response => {
461
462
463
464
465
466
            this.current.languages.push(response.data)
          }).catch(error => {
            loadingError = true
            alert('There was an error fetching languages from the server: ' + error)
          })
        }
467
        if (!loadingError) this.loaded.languages = true
468
469
470
471
472
473
474
475
476
      }
    },
    getTopics: function () {
      this.current.topics = []
      var loadingError = false
      if (this.shows[this.currentShow].topic.length === 0) {
        this.loaded.topics = true
      } else {
        for (var i in this.shows[this.currentShow].topic) {
477
          axios.get(process.env.API_STEERING + 'topics/' + this.shows[this.currentShow].topic[i], {withCredentials: true}).then(response => {
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
            this.current.topics.push(response.data)
          }).catch(error => {
            loadingError = true
            alert('There was an error fetching topics from the server: ' + error)
          })
        }
        if (!loadingError) this.loaded.topics = true
      }
    },
    getMusicfocus: function () {
      this.current.musicfocus = []
      var loadingError = false
      if (this.shows[this.currentShow].musicfocus.length === 0) {
        this.loaded.musicfocus = true
      } else {
        for (var i in this.shows[this.currentShow].musicfocus) {
494
          axios.get(process.env.API_STEERING + 'musicfocus/' + this.shows[this.currentShow].musicfocus[i], {withCredentials: true}).then(response => {
495
496
497
498
499
500
501
502
503
504
505
506
            this.current.musicfocus.push(response.data)
          }).catch(error => {
            loadingError = true
            alert('There was an error fetching musicfocus from the server: ' + error)
          })
        }
      }
      if (!loadingError) this.loaded.musicfocus = true
    },
    getRTRCategory: function () {
      this.current.rtrcategory = []
      var loadingError = false
507
      if (typeof this.shows[this.currentShow].rtrcategory !== 'number') {
508
509
        this.loaded.rtrcategory = true
      } else {
510
        axios.get(process.env.API_STEERING + 'rtrcategories/' + this.shows[this.currentShow].rtrcategory, {withCredentials: true}).then(response => {
511
512
513
514
515
516
517
518
519
520
521
522
523
524
          this.current.rtrcategory.push(response.data)
        }).catch(error => {
          loadingError = true
          alert('There was an error fetching RTR category from the server: ' + error)
        })
      }
      if (!loadingError) this.loaded.rtrcategory = true
    },
    getType: function () {
      this.current.type = []
      var loadingError = false
      if (typeof this.shows[this.currentShow].type !== 'number') {
        this.loaded.type = true
      } else {
525
        axios.get(process.env.API_STEERING + 'types/' + this.shows[this.currentShow].type, {withCredentials: true}).then(response => {
526
527
528
529
530
          this.current.type.push(response.data)
        }).catch(error => {
          loadingError = true
          alert('There was an error fetching show type from the server: ' + error)
        })
531
      }
532
      if (!loadingError) this.loaded.type = true
533
    },
534
    notYetImplemented: function () {
535
      alert('By the mighty witchcraftry of the mother of time!\n\nThis feature is not implemented yet.')
536
537
538
    }
  },
  created () {
539
    axios.get(process.env.API_STEERING_SHOWS, {withCredentials: true}).then(response => {
540
541
542
      this.shows = response.data
      this.currentShowID = this.shows[0].id
      this.currentShow = 0
543
      this.loaded.shows = true
544
545
546
      this.switchShow(this.currentShow)
    }).catch(error => {
      alert('There was an error fetching shows from the server: ' + error)
547
548
549
550
551
    })
  }
}
</script>

552
<style>
553
</style>