ShowManager.vue 24.1 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
      <!-- include the modals to edit show and timeslot entries from the modal compontents -->
46
47
      <app-modalShow ref="appModalShow" :show="shows[currentShow]"></app-modalShow>
      <app-modalNotes ref="appModalNotes" :show="shows[currentShow]" :showAggregate="current"></app-modalNotes>
48

49
      <!-- here we show our table of timeslots -->
50
      <p>
51
        The next <b>{{ numSlots }}</b> timeslots between <b>{{ prettyDate(dateSlotsStart) }}</b> and <b>{{ prettyDate(dateSlotsEnd) }}</b>:
52
53
54
55
56
57
      </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>

58
59
      <hr />

60
61
      <h2>Allgemeine Einstellungen zur Sendereihe:</h2>
      <b-row>
62

63
64
65
66
67
68
69
70
        <b-col lg="6">
          <p>
            <b-badge variant="light">E-Mail:</b-badge>
            <span v-if="shows[currentShow].email === null"><small><i>(none set)</i></small></span>
            <span v-else>{{ shows[currentShow].email }}</span>
            <img src="../assets/16x16/emblem-system.png" alt="edit contact e-mail" v-on:click="$refs.appModalShow.showEmail()" />
          </p>
        </b-col>
71

72
73
74
75
76
77
78
79
        <b-col lg="6">
          <p>
            <b-badge variant="light">Website:</b-badge>
            <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>
            <img src="../assets/16x16/emblem-system.png" alt="edit website" v-on:click="$refs.appModalShow.showWebsite()" />
          </p>
        </b-col>
80

81
82
83
84
85
86
87
88
89
90
91
92
        <b-col lg="6">
          <p>
            <b-badge variant="light">Show type:</b-badge>
            <!-- TODO: discuss: should this be visible to show owners or only to administrators? -->
            <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>
              <img src="../assets/16x16/emblem-system.png" alt="edit" v-on:click="notYetImplemented" />
            </span>
            <span v-else><img src="../assets/radio.gif" height="24px" alt="loading data" /></span>
          </p>
        </b-col>
93

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

107
108
109
110
111
112
113
114
115
116
        <b-col lg="6">
          <p>
            <!-- TODO: discuss: should this be visible to show owners or only to administrators? -->
            <!-- TODO: fetch name for predecessor_id from steering api -->
            <b-badge variant="light">Predecessor:</b-badge>
            <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>
117

118
119
120
121
122
123
124
125
        <b-col lg="6">
          <p>
            <b-badge variant="light">CBA Series ID:</b-badge>
            <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>
            <img src="../assets/16x16/emblem-system.png" alt="edit CBA series ID" v-on:click="$refs.appModalShow.showCBAid()" />
          </p>
        </b-col>
126

127
128
129
130
131
132
133
134
        <b-col lg="6">
          <p>
            <b-badge variant="light">Fallback List/Pool:</b-badge>
            <span v-if="shows[currentShow].fallback_id === ''"><small><i>(none set)</i></small></span>
            <span v-else>ID: {{ shows[currentShow].fallback_id }}</span>
            <img src="../assets/16x16/emblem-system.png" alt="edit" v-on:click="notYetImplemented" />
          </p>
        </b-col>
135

136
      </b-row>
137

138
      <b-row>
139

140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
        <b-col lg="2">
          <b-badge style="width:80%;">Categories:</b-badge> <img src="../assets/16x16/emblem-system.png" alt="edit" v-on:click="notYetImplemented" />
        </b-col>
        <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>
        </b-col>
156

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

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

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

208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
        <b-col lg="2">
          <b-badge style="width:80%;">Hosts:</b-badge> <img src="../assets/16x16/emblem-system.png" alt="edit" v-on:click="notYetImplemented" />
        </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>

      </b-row>
227
    </div>
228
  </b-container>
229
230
231
</template>

<script>
232
import modalNotes from './ShowManagerModalNotes.vue'
233
import modalShow from './ShowManagerModalShow.vue'
234
import timeslotSort from '../mixins/timeslotSort'
235
import prettyDate from '../mixins/prettyDate'
236
import axios from 'axios'
237
238

export default {
239
  components: {
240
241
    'app-modalNotes': modalNotes,
    'app-modalShow': modalShow
242
  },
243
244
  data () {
    return {
245
246
247
      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
248
      numUpcoming: 8,
249
      numRecent: 8,
250
251
      numSlots: 10,
      dateSlotsStart: new Date(),
252
      dateSlotsEnd: new Date(new Date().getTime() + 90 * 86400000),
253
254
255
256
257
258
259
260
261
      loaded: {
        shows: false,
        timeslots: false,
        notes: false,
        categories: false,
        hosts: false,
        languages: false,
        topics: false,
        musicfocus: false,
262
        fundingcategory: false,
263
        type: false
264
265
266
267
268
269
270
      },
      current: {
        categories: [],
        hosts: [],
        languages: [],
        topics: [],
        musicfocus: [],
271
        fundingcategory: [],
272
        type: [],
273
274
275
276
277
278
279
280
        timeslots: [],
        timeslotmeta: {    // meta info when pagination is used
          count: 0,
          next: null,
          previous: null
        },
        note: {},
        notes: []
281
      }
282
283
    }
  },
284
  mixins: [ timeslotSort, prettyDate ],
285
  computed: {
286
287
    notesTableArray: function () {
      var arr = []
288
289
290
      for (var i in this.current.timeslots) {
        var note = this.getNoteByTimeslotID(this.current.timeslots[i].id)
        if (note !== null) note = note.title
291
        arr.push({
292
293
294
          title: note,
          starts: this.prettyDateTime(this.current.timeslots[i].start),
          duration: this.prettyDuration(this.current.timeslots[i].start, this.current.timeslots[i].end) + 'min',
295
          // TODO: find out how to insert images or iconffont icons into b-table rows
296
          // options: '<img src="../assets/16x16/emblem-system.png" alt="edit note" v-on:click="' + this.editTimeslotNote(this.current.timeslots[i].id) + '" />'
297
298
          options: '<span class="timeslotEditLink" onclick="' +
            'document.getElementById(\'app\').children[1].__vue__.' +
299
            'editTimeslotNote(' + this.current.timeslots[i].id + ', ' + this.current.timeslots[i].schedule + ')">edit</span> ' +
300
301
            '<span class="timeslotEditLink" onclick="alert(\'notYetImplemented\')">upload</span>' +
            '<span class="timeslotEditLink" onclick="alert(\'notYetImplemented\')">...</span>'
302
303
304
        })
      }
      return arr
305
306
307
    }
  },
  methods: {
308
    switchShow: function (index) {
309
310
311
      // 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
312
313
314
315
316
317
318
319
      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
320
      this.loaded.fundingcategory = false
321
      // set the current show and its ID to whatever we want to switch to now
322
      this.currentShow = index
323
      this.currentShowID = this.shows[this.currentShow].id
324
325
326
327
328
329
330
      // 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()
331
      this.getType()
332
333
334
335
336
      // 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/?'
337
338
      if (typeof start === 'object') uri += 'start=' + this.apiDate(start)
      if (typeof end === 'object') uri += '&end=' + this.apiDate(end)
339
340
      if (typeof limit === 'number') uri += '&limit=' + limit
      if (typeof offset === 'number') uri += '&offset=' + offset
341
342
343
344
      axios.get(uri, {
        withCredentials: true,
        headers: { 'Authorization': 'Bearer ' + this.$parent.user.access_token }
      }).then(response => {
345
346
347
        // if we use the limit argument results are paginated and look different
        // than without the limit argument
        if (typeof limit === 'number') {
348
349
350
351
          this.current.timeslots = response.data.results
          this.current.timeslotmeta.count = response.data.count
          this.current.timeslotmeta.next = response.data.next
          this.current.timeslotmeta.previous = response.data.previous
352
        } else {
353
354
355
356
          this.current.timeslots = response.data
          this.current.timeslotmeta.count = response.data.length
          this.current.timeslotmeta.next = null
          this.current.timeslotmeta.previous = null
357
        }
358
        this.loaded.timeslots = true
359
        // now that we have the timeslots we can fetch notes for all those timeslots
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
        uri = process.env.API_STEERING_SHOWS + this.currentShowID + '/notes/?ids='
        // add all note IDs from the timeslots that have existing notes
        var thereIsANote = false
        for (var i in this.current.timeslots) {
          if (typeof this.current.timeslots[i].note_id === 'number') {
            uri += this.current.timeslots[i].note_id + ','
            thereIsANote = true
          }
        }
        // now remove trailing ',' if at least one note already exists
        // and make the api call to fetch them
        if (thereIsANote) {
          uri = uri.slice(0, -1)
          axios.get(uri, {
            withCredentials: true,
            headers: { 'Authorization': 'Bearer ' + this.$parent.user.access_token }
          }).then(response => {
            this.current.notes = response.data
            this.loaded.notes = true
          }).catch(error => {
            alert('There was an error fetching notes from the server' + error)
          })
          // done fetching notes
        } else {
          // if no notes exist that correspond to our selected timeslots, empty
          // the corresponding array
          this.current.notes = []
        }
388
389
      }).catch(error => {
        alert('There was an error fetching timeslots from the server' + error)
390
      })
391
392
      // done fetching timeslots
    },
393
394
    editTimeslotNote: function (timeslotID, scheduleID) {
      this.current.note = null
395
396
397
      for (var i in this.current.notes) {
        if (this.current.notes[i].timeslot === timeslotID) {
          this.current.note = this.current.notes[i]
398
399
400
          break
        }
      }
401
      this.$refs.appModalNotes.showModal(this.current.note, timeslotID, scheduleID)
402
    },
403
404
405
406
    getNoteByTimeslotID: function (timeslotID) {
      for (var i in this.current.notes) {
        if (this.current.notes[i].timeslot === timeslotID && this.current.notes[i].title !== undefined) {
          return this.current.notes[i]
407
408
409
410
        }
      }
      return null
    },
411
    prettyTimeslotNote: function (timeslotID) {
412
      var note = this.getTimeslotNoteTitle(timeslotID)
413
414
415
416
417
418
      if (note !== null) {
        return this.prettyTitle(note)
      } else {
        return ''
      }
    },
419
420
421
422
423
    prettyTitle: function (title) {
      if (title === '') return '...'
      else if (title.length > 25) return title.slice(0, 25) + '...'
      else return title
    },
424
425
426
427
428
429
430
431
432
433
    // 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) {
434
          axios.get(process.env.API_STEERING + 'categories/' + this.shows[this.currentShow].category[i] + '/', {
435
436
437
            withCredentials: true,
            headers: { 'Authorization': 'Bearer ' + this.$parent.user.access_token }
          }).then(response => {
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
            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) {
454
455
456
457
          axios.get(process.env.API_STEERING + 'hosts/' + this.shows[this.currentShow].hosts[i] + '/', {
            withCredentials: true,
            headers: { 'Authorization': 'Bearer ' + this.$parent.user.access_token }
          }).then(response => {
458
459
460
461
462
463
464
465
466
467
468
469
470
            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) {
471
        this.loaded.languages = true
472
473
      } else {
        for (var i in this.shows[this.currentShow].language) {
474
          axios.get(process.env.API_STEERING + 'languages/' + this.shows[this.currentShow].language[i] + '/', {
475
476
477
            withCredentials: true,
            headers: { 'Authorization': 'Bearer ' + this.$parent.user.access_token }
          }).then(response => {
478
479
480
481
482
483
            this.current.languages.push(response.data)
          }).catch(error => {
            loadingError = true
            alert('There was an error fetching languages from the server: ' + error)
          })
        }
484
        if (!loadingError) this.loaded.languages = true
485
486
487
488
489
490
491
492
493
      }
    },
    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) {
494
          axios.get(process.env.API_STEERING + 'topics/' + this.shows[this.currentShow].topic[i] + '/', {
495
496
497
            withCredentials: true,
            headers: { 'Authorization': 'Bearer ' + this.$parent.user.access_token }
          }).then(response => {
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
            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) {
514
          axios.get(process.env.API_STEERING + 'musicfocus/' + this.shows[this.currentShow].musicfocus[i] + '/', {
515
516
517
            withCredentials: true,
            headers: { 'Authorization': 'Bearer ' + this.$parent.user.access_token }
          }).then(response => {
518
519
520
521
522
523
524
525
526
527
            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 () {
528
      this.current.fundingcategory = []
529
      var loadingError = false
530
531
      if (typeof this.shows[this.currentShow].fundingcategory !== 'number') {
        this.loaded.fundingcategory = true
532
      } else {
533
        axios.get(process.env.API_STEERING + 'fundingcategories/' + this.shows[this.currentShow].fundingcategory + '/', {
534
535
536
          withCredentials: true,
          headers: { 'Authorization': 'Bearer ' + this.$parent.user.access_token }
        }).then(response => {
537
          this.current.fundingcategory.push(response.data)
538
539
540
541
542
        }).catch(error => {
          loadingError = true
          alert('There was an error fetching RTR category from the server: ' + error)
        })
      }
543
      if (!loadingError) this.loaded.fundingcategory = true
544
545
546
547
548
549
550
    },
    getType: function () {
      this.current.type = []
      var loadingError = false
      if (typeof this.shows[this.currentShow].type !== 'number') {
        this.loaded.type = true
      } else {
551
        axios.get(process.env.API_STEERING + 'types/' + this.shows[this.currentShow].type + '/', {
552
553
554
          withCredentials: true,
          headers: { 'Authorization': 'Bearer ' + this.$parent.user.access_token }
        }).then(response => {
555
556
557
558
559
          this.current.type.push(response.data)
        }).catch(error => {
          loadingError = true
          alert('There was an error fetching show type from the server: ' + error)
        })
560
      }
561
      if (!loadingError) this.loaded.type = true
562
    },
563
    notYetImplemented: function () {
564
      alert('By the mighty witchcraftry of the mother of time!\n\nThis feature is not implemented yet.')
565
566
567
    }
  },
  created () {
568
569
570
571
572
    var uri = process.env.API_STEERING_SHOWS
    if (!this.$parent.user.steeringUser.is_superuser) {
      uri += '?owner=' + this.$parent.user.steeringUser.id
    }
    axios.get(uri, {
573
574
575
      withCredentials: true,
      headers: { 'Authorization': 'Bearer ' + this.$parent.user.access_token }
    }).then(response => {
576
577
578
579
      if (response.data.length === 0) {
        alert('There are now shows associated with your account!')
        return
      }
580
581
582
      this.shows = response.data
      this.currentShowID = this.shows[0].id
      this.currentShow = 0
583
      this.loaded.shows = true
584
585
586
      this.switchShow(this.currentShow)
    }).catch(error => {
      alert('There was an error fetching shows from the server: ' + error)
587
588
589
590
591
    })
  }
}
</script>

592
<style>
593
594
595
596
597
598
599
600
601
span.timeslotEditLink {
  color: #8d5f82 !important;
  font-weight: bold;
  font-size: 0.8em;
  padding: 0.25em;
}
span.timeslotEditLink:hover {
  color: #ad7fa8 !important;
}
602
</style>