ShowManager.vue 37.9 KB
Newer Older
1
<template>
2
  <b-container>
3
4
    <!-- 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) -->
5
    <b-row>
6
7
8
      <b-col>
        <h3>Sendungen verwalten</h3>
      </b-col>
9
      <b-col align="right">
10
11
12
        <b-button
          v-if="$parent.user.steeringUser.is_superuser"
          v-b-popover.hover.top="'Add a new show'"
13
          variant="info"
14
15
16
17
18
          @click="$refs.appModalSuperuser.showModalAddShow()"
        >
          +
        </b-button>
        &nbsp;
19
20
21
        <b-dropdown
          id="ddshows"
          text="Sendereihe auswählen"
22
          variant="outline-info"
23
24
        >
          <b-dropdown-item
25
            v-for="(show, index) in shows"
26
27
28
29
30
            :key="show.id"
            @click="switchShow(index)"
          >
            {{ show.name }}
          </b-dropdown-item>
31
32
33
        </b-dropdown>
      </b-col>
    </b-row>
34
    <hr>
35

36
37
    <!-- The jumbotron is used to display the name and description of the
    currently selected show -->
38
    <show-jumbotron />
39

40
    <!-- If the shows are not fully loaded yet, we just put the loading sign -->
41
    <div v-if="!loaded.shows">
42
43
      <b-row>
        <b-col align="center">
44
45
46
47
          <img
            src="../assets/radio.gif"
            alt="loading data"
          >
48
49
        </b-col>
      </b-row>
50
    </div>
51
52

    <!-- When all show data is loaded, here we display all the rest -->
53
    <div v-else>
54
      <!-- include the modals to edit show and timeslot entries from the modal compontents -->
55
56
57
58
59
60
61
62
63
      <app-modalShow
        ref="appModalShow"
        :show="shows[currentShow]"
      />
      <app-modalNotes
        ref="appModalNotes"
        :show="shows[currentShow]"
        :show-aggregate="current"
      />
64
65
66
      <app-modalSuperuser
        ref="appModalSuperuser"
      />
67
68
69
      <app-modalPlaylist
        ref="appModalPlaylist"
      />
70

71
      <!-- here are the filter settings for our timeslots table -->
72
      <b-card>
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
        <b-row>
          <b-col>
            <b-btn v-b-toggle.timeslotFilterCollapse>
              Toggle timeslot filters
            </b-btn>
          </b-col>
          <b-col align="right">
            <b-button
              v-if="$parent.user.steeringUser.is_superuser"
              variant="info"
              @click="$router.push({path: 'emissions', query: { show: currentShow }})"
            >
              Switch to Emission Manager
            </b-button>
          </b-col>
        </b-row>

90
        <b-collapse id="timeslotFilterCollapse">
91
          <br>
92
          <!-- How many slots to show per table page -->
93
          <b-row>
94
95
96
97
98
99
100
101
102
103
            <b-col sm="3">
              <label for="inputNumSlots">Number of slots to show:</label>
            </b-col>
            <b-col sm="9">
              <b-form-input
                id="inputNumSlots"
                v-model="numSlots"
                type="number"
              />
            </b-col>
104
          </b-row>
105
          <!-- The start date to display timeslots from (defaults to today) -->
106
          <b-row>
107
108
109
110
111
112
113
114
115
116
            <b-col sm="3">
              <label for="inputDateStart">From:</label>
            </b-col>
            <b-col sm="9">
              <b-form-input
                id="inputDateStart"
                v-model="dateStart"
                type="date"
              />
            </b-col>
117
          </b-row>
118
          <!-- The end date until to wich to display timeslots -->
119
          <b-row>
120
121
122
123
124
125
126
127
128
129
            <b-col sm="3">
              <label for="inputNumSlots">Until (exclusive):</label>
            </b-col>
            <b-col sm="9">
              <b-form-input
                id="inputDateEnd"
                v-model="dateEnd"
                type="date"
              />
            </b-col>
130
          </b-row>
131
          <br>
132
          <!-- And finally two buttons, one to reset and one to apply the filter -->
133
134
135
136
137
138
139
140
141
142
143
          <b-container
            fluid
            class="text-right"
          >
            <b-btn
              variant="outline-danger"
              @click="resetFilter()"
            >
              Reset filter
            </b-btn> &nbsp;
            <b-btn
144
              variant="outline-success"
145
146
147
148
              @click="applyFilter()"
            >
              Apply filter
            </b-btn>
149
150
151
152
          </b-container>
        </b-collapse>
      </b-card>

153
      <br>
154

155
156
      <!-- here we show our table of timeslots, if the timeslots are already
      loaded (otherwise we just show the loading symbol) -->
157
      <div v-if="loaded.timeslots">
158
159
160
161
162
163
164
        <b-table
          striped
          hover
          outlined
          :fields="notesTableArrayFields"
          :items="notesTableArray"
        >
165
          <!-- Title of the timeslot (if already set) -->
166
          <template v-slot:cell(title)="data">
167
168
169
            <span v-if="data.value">{{ data.value }}</span>
            <span v-else><small><i>(none set)</i></small></span>
          </template>
170
          <!-- Date and time when this timeslot starts -->
171
          <template v-slot:cell(starts)="data">
172
173
            {{ data.value }}
          </template>
174
          <!-- The duration of this timeslot -->
175
          <template v-slot:cell(duration)="data">
176
177
            {{ data.value }}
          </template>
178
179
          <!-- And here all the buttons for editing and doing other things
          with the displayed timeslot -->
180
          <template v-slot:cell(options)="data">
181
182
183
184
185
186
187
188
189
190
            <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"
191
              @click="editTimeslotPlaylist(shows[currentShow], data.item.options.schedule, data.item.options.id)"
192
193
            ><img
              src="../assets/16x16/media-eject.png"
194
195
              alt="Edit playlist"
              title="Edit playlist"
196
197
            ></span>
            <span
198
              v-if="data.item.options.play"
199
200
201
202
              class="timeslotEditLink"
              @click="notYetImplemented()"
            ><img
              src="../assets/16x16/media-playback-start.png"
203
204
              alt="Open player"
              title="Open player"
205
            ></span>
206
207
208
209
210
          </template>

          <template v-slot:cell(playlist)="data">
            <span v-if="data.value">{{ data.value }}</span>
            <span v-else><small><i>(none set)</i></small></span>
211
212
          </template>
        </b-table>
213
214
215
216
217
218
219
220
        <b-pagination
          v-model="current.timeslotmeta.page"
          align="center"
          :total-rows="current.timeslotmeta.count"
          :per-page="current.timeslotmeta.perpage"
          @change="timeslotsPage"
        />
      </div>
221
222
223
224
225
226
227
228
229
      <!-- If the timeslot data is not loaded, we just show the spinner instead
      of the table itself -->
      <div v-else>
        <div style="text-align: center;">
          <img
            src="../assets/radio.gif"
            alt="loading data"
          ><br>
        </div>
230
231
      </div>

232
      <hr>
233

234
      <h2>Allgemeine Einstellungen zur Sendereihe:</h2>
235
      <show-metaSimpleTypes />
236

237
238
239
240
241
242
243
244
245
246
247
      <hr v-if="$parent.user.steeringUser.is_superuser">
      <b-row v-if="$parent.user.steeringUser.is_superuser">
        <b-col lg="2">
          <b-badge
            variant="info"
            style="width:80%;"
          >
            Owners:
          </b-badge> <img
            src="../assets/16x16/emblem-system.png"
            alt="edit"
248
            @click="$refs.appModalSuperuser.showModalOwners(shows[currentShow])"
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
          >
        </b-col>
        <b-col lg="10">
          <div v-if="loaded.owners">
            <p v-if="shows[currentShow].owners.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="owner in current.owners"
                  :key="owner.id"
                >
                  {{ owner.first_name }} {{ owner.last_name }}
                  <b-badge variant="light">
                    username:
                  </b-badge> <small>{{ owner.username }}</small>
                  <span v-if="owner.email.length > 0">
                    <b-badge variant="light">
                      email:
                    </b-badge> <small>{{ owner.email }}</small>
                  </span>
                </li>
              </ul>
            </p>
          </div>
          <div v-else>
            <img
              src="../assets/radio.gif"
              height="24px"
              alt="loading data"
            ><br>
          </div>
        </b-col>
      </b-row>
      <hr v-if="$parent.user.steeringUser.is_superuser">

287
288
      <b-row>
        <b-col lg="2">
289
290
291
292
293
294
295
          <b-badge style="width:80%;">
            Categories:
          </b-badge> <img
            src="../assets/16x16/emblem-system.png"
            alt="edit"
            @click="$refs.appModalShow.showCategories()"
          >
296
297
298
299
300
301
302
303
        </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>
304
305
306
307
308
309
                <li
                  v-for="cat in current.categories"
                  :key="cat.id"
                >
                  {{ cat.category }}
                </li>
310
311
312
              </ul>
            </p>
          </div>
313
314
315
316
317
318
319
          <div v-else>
            <img
              src="../assets/radio.gif"
              height="24px"
              alt="loading data"
            ><br>
          </div>
320
        </b-col>
321

322
        <b-col lg="2">
323
324
325
326
327
328
329
          <b-badge style="width:80%;">
            Topics:
          </b-badge> <img
            src="../assets/16x16/emblem-system.png"
            alt="edit"
            @click="$refs.appModalShow.showTopics()"
          >
330
331
332
333
334
335
336
337
        </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>
338
339
340
341
342
343
                <li
                  v-for="topic in current.topics"
                  :key="topic.id"
                >
                  {{ topic.topic }}
                </li>
344
345
346
              </ul>
            </p>
          </div>
347
348
349
350
351
352
353
          <div v-else>
            <img
              src="../assets/radio.gif"
              height="24px"
              alt="loading data"
            ><br>
          </div>
354
        </b-col>
355

356
        <b-col lg="2">
357
358
359
360
361
362
363
          <b-badge style="width:80%;">
            Musicfocus:
          </b-badge> <img
            src="../assets/16x16/emblem-system.png"
            alt="edit"
            @click="$refs.appModalShow.showMusicFocus()"
          >
364
365
366
367
368
369
370
371
        </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>
372
373
374
375
376
377
                <li
                  v-for="focus in current.musicfocus"
                  :key="focus.id"
                >
                  {{ focus.focus }}
                </li>
378
379
380
              </ul>
            </p>
          </div>
381
382
383
384
385
386
387
          <div v-else>
            <img
              src="../assets/radio.gif"
              height="24px"
              alt="loading data"
            ><br>
          </div>
388
        </b-col>
389

390
        <b-col lg="2">
391
392
393
394
395
396
397
          <b-badge style="width:80%;">
            Languages:
          </b-badge> <img
            src="../assets/16x16/emblem-system.png"
            alt="edit"
            @click="$refs.appModalShow.showLanguages()"
          >
398
399
400
401
402
403
404
405
        </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>
406
407
408
409
410
411
                <li
                  v-for="lang in current.languages"
                  :key="lang.id"
                >
                  {{ lang.name }}
                </li>
412
413
414
              </ul>
            </p>
          </div>
415
416
417
418
419
420
421
          <div v-else>
            <img
              src="../assets/radio.gif"
              height="24px"
              alt="loading data"
            ><br>
          </div>
422
        </b-col>
423

424
        <b-col lg="2">
425
426
427
428
429
430
431
          <b-badge style="width:80%;">
            Hosts:
          </b-badge> <img
            src="../assets/16x16/emblem-system.png"
            alt="edit"
            @click="$refs.appModalShow.showHosts()"
          >
432
433
434
435
436
437
438
439
440
        </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>
441
442
443
444
445
446
                <li
                  v-for="host in current.hosts"
                  :key="host.id"
                >
                  {{ host.name }}
                </li>
447
448
449
              </ul>
            </p>
          </div>
450
451
452
453
454
455
456
          <div v-else>
            <img
              src="../assets/radio.gif"
              height="24px"
              alt="loading data"
            ><br>
          </div>
457
458
        </b-col>
      </b-row>
459
460
461

      <b-row>
        <b-col lg="2">
462
463
464
465
466
467
468
          <b-badge style="width:80%;">
            Logo:
          </b-badge> <img
            src="../assets/16x16/emblem-system.png"
            alt="edit"
            @click="$refs.appModalShow.showLogo()"
          >
469
470
        </b-col>
        <b-col lg="4">
471
          <div v-if="current.logo.length === 0">
472
473
474
            <small><i>(none set)</i></small>
          </div>
          <div v-else>
475
476
477
478
479
480
481
            <br>
            <b-img
              thumbnail
              :src="current.logo"
              fluid
              @click="$refs.appModalShow.showLogo()"
            />
482
483
484
485
          </div>
        </b-col>

        <b-col lg="2">
486
487
488
489
490
491
492
          <b-badge style="width:80%;">
            Image:
          </b-badge> <img
            src="../assets/16x16/emblem-system.png"
            alt="edit"
            @click="$refs.appModalShow.showImage()"
          >
493
494
        </b-col>
        <b-col lg="4">
495
          <div v-if="current.image.length === 0">
496
497
498
            <small><i>(none set)</i></small>
          </div>
          <div v-else>
499
500
501
502
503
504
505
            <br>
            <b-img
              thumbnail
              :src="current.image"
              fluid
              @click="$refs.appModalShow.showImage()"
            />
506
507
508
509
          </div>
        </b-col>
      </b-row>

510
      <hr>
511
    </div>
512
  </b-container>
513
514
515
</template>

<script>
516
import showJumbotron from './ShowJumbotron.vue'
517
import showMetaSimpleTypes from './ShowMetaSimpleTypes.vue'
518
import modalNotes from './ShowManagerModalNotes.vue'
519
import modalShow from './ShowManagerModalShow.vue'
520
import modalSuperuser from './ShowManagerModalSuperuser.vue'
521
import modalPlaylist from './ShowManagerModalPlaylist.vue'
522
import timeslotSort from '../mixins/timeslotSort'
523
import prettyDate from '../mixins/prettyDate'
524
import axios from 'axios'
525
import DOMPurify from 'dompurify'
526
527

export default {
528
529
  // all modals to edit a show and its timeslots/notes, are importet as separate
  // components, to make it a tiny lickle bit less messy here
530
  components: {
531
    'app-modalNotes': modalNotes,
532
533
    'app-modalShow': modalShow,
    'app-modalSuperuser': modalSuperuser,
534
    'app-modalPlaylist': modalPlaylist,
535
    'show-jumbotron': showJumbotron,
536
    'show-metaSimpleTypes': showMetaSimpleTypes,
537
  },
538
539

  // generic functions that we want to use from our mixins folder
540
  mixins: [ timeslotSort, prettyDate ],
541
542
543

  // this component will be handling a lot of data - probably the component can
  // be refactored to get rid of some redundancy here
544
545
  data () {
    return {
546
      shows: [],      // an array of objects describing our shows (empty at load, will be populated on created())
547
      numSlots: process.env.VUE_APP_TIMESLOT_FILTER_DEFAULT_NUMSLOTS, // all form input values are provided as strings
548
      dateStart: this.apiDate(new Date()),
549
      dateEnd: this.apiDate(new Date(new Date().getTime() + process.env.VUE_APP_TIMESLOT_FILTER_DEFAULT_DAYS * 86400000)),
550
551
552

      // the loaded object holds flags for the different things we will fetch
      // from the AuRa steering module
553
554
555
556
557
558
559
560
561
      loaded: {
        shows: false,
        timeslots: false,
        notes: false,
        categories: false,
        hosts: false,
        languages: false,
        topics: false,
        musicfocus: false,
562
        fundingcategory: false,
563
564
        type: false,
        owners: false,
565
        playlists: false,
566
      },
567
568
569

      // the current object is used to hold all the necessary data to describe
      // the show which is currently selected by the user in the frontend
570
571
572
573
574
575
      current: {
        categories: [],
        hosts: [],
        languages: [],
        topics: [],
        musicfocus: [],
576
        fundingcategory: [],
577
        type: [],
578
        playlists: [],
579
580
581
582
        timeslots: [],
        timeslotmeta: {    // meta info when pagination is used
          count: 0,
          next: null,
583
584
585
          previous: null,
          page: 1,   // page indexes start at 1 for <b-pagination> components
          perpage: 10
586
587
        },
        note: {},
588
589
        notes: [],
        image: '',
590
591
        logo: '',
        owners: [],
592
      },
593
594

      // this is used to configure the table with all the filtered timeslots
595
596
597
598
      notesTableArrayFields: [
        { key: 'title', label: 'Title of emission' },
        { key: 'starts', label: 'Emission start' },
        { key: 'duration', label: 'Duration' },
599
600
        { key: 'options', label: 'Edit' },
        { key: 'playlist', label: 'Playlist' },
601
      ]
602
603
    }
  },
604

jackie / Andrea Ida Malkah Klaura's avatar
jackie / Andrea Ida Malkah Klaura committed
605
  // Some of the info we need in the template are not easily and directly
606
  // retrievable, so we are computing them on the fly, when they are needed
607
  computed: {
608

609
610
611
    currentShow () { return this.$store.state.shows.selected.index },
    currentShowID () { return this.$store.state.shows.selected.id },

612
613
614
615
    // As the show description should allow to be html-formatted, we have to
    // make sure no malicous code can be inserted into the DOM. For that the
    // DOMPurify library (https://github.com/cure53/DOMPurify) does us a much
    // better service than trying to sanitize it with some RegExp.
616
617
618
    sanitizedShowDescription: function () {
      return DOMPurify.sanitize(this.shows[this.currentShow].description)
    },
619
620
621

    // As we do not have a single object which holds all info we need to display
    // in the table with our timeslots, we use this computed array to do that
622
    notesTableArray: function () {
623
      let rows = []
624
      for (var i in this.current.timeslots) {
625
        let note = this.getNoteByTimeslotID(this.current.timeslots[i].id)
626
        if (note !== null) { note = note.title }
627
628
629
630

        let playlistTitle = ''
        if (this.current.timeslots[i].playlist_id !== null) {
          let playlist = this.current.playlists.find(list => list.id === this.current.timeslots[i].playlist_id)
631
632
633
634
635
636
          if (playlist) {
            if (playlist.description.length > 0) {
              playlistTitle = playlist.description
            } else {
              playlistTitle = playlist.id
            }
637
638
639
640
          }
        }

        rows.push({
641
642
          title: note,
          starts: this.prettyDateTime(this.current.timeslots[i].start),
643
          duration: this.prettyDuration(this.current.timeslots[i].start, this.current.timeslots[i].end),
644
645
          options: {
            id: this.current.timeslots[i].id,
646
647
648
649
            schedule: this.current.timeslots[i].schedule,
            play: this.current.timeslots[i].playlist_id !== null
          },
          playlist: playlistTitle
650
651
        })
      }
652
      return rows
653
654
    }
  },
655
656
657

  // Right after this component is set up, we want to fetch all available shows
  // from the AuRa steering module.
658
  created () {
659
660
661
    // As we don't know any shows yet, we use null as id parameter, so the
    // first show in the returned show array will be displayed
    this.loadAndSwitch(null)
662
  },
663
664
665

  // Now for our hotchpotch of methods, mostly used for fetching data from the
  // AuRa steering API (updateing will be done in the imported modal components)
666
  methods: {
667
668

    // Apply the newly set filter parameters for our timeslot table
669
670
671
672
    applyFilter: function () {
      this.current.timeslotmeta.page = 1
      this.getTimeslots(this.dateStart, this.dateEnd, this.numSlots)
    },
673
674

    // Reset the filter parameters for our timeslot table to config defaults
675
    resetFilter: function () {
676
      this.numSlots = process.env.VUE_APP_TIMESLOT_FILTER_DEFAULT_NUMSLOTS
677
      this.dateStart = this.apiDate(new Date())
678
      this.dateEnd = this.apiDate(new Date(new Date().getTime() + process.env.VUE_APP_TIMESLOT_FILTER_DEFAULT_DAYS * 86400000))
679
680
681
      this.current.timeslotmeta.page = 1
      this.getTimeslots(this.dateStart, this.dateEnd, this.numSlots)
    },
682
683

    // Load a different page of timeslots for the timeslots table
684
685
686
687
688
689
    timeslotsPage: function (page) {
      if (this.current.timeslotmeta.page !== page) {
        this.current.timeslotmeta.page = page
        this.getTimeslots(this.dateStart, this.dateEnd, this.numSlots, (page - 1) * this.numSlots)
      }
    },
690
691
692
693
694
695
696
697

    // Every time the user switches to another show, we will load all related
    // data (as e.g. categories, hosts, etc.) from the AuRa steering API, in case
    // something changed.
    // TODO/discuss: maybe this is too inefficient and we could only load this
    // data just in the beginning after shows are loaded and assume they do not
    // change throughout a session or put it into the user's responsibility to
    // reload the show manager page after relevant changes.
698
    switchShow: function (index) {
699
700
701
      // 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
702
703
704
705
706
707
708
709
      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
710
      this.loaded.fundingcategory = false
711
      this.loaded.owners = false
712
      // set the current show and its ID to whatever we want to switch to now
713
      this.$store.commit('shows/switchShow', index)
714
715
      // and check if images are available and set image strings, because we
      // cannot use them directly inside the b-img if they are null
716
717
718
719
      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 }
720
721
722
723
724
725
      // before we load timeslots and notes, we want to fetch the general settings first
      this.getCategories()
      this.getHosts()
      this.getLanguages()
      this.getTopics()
      this.getMusicfocus()
726
      this.getFundingCategory()
727
      this.getType()
728
729
730
      if (this.$parent.user.steeringUser.is_superuser) {
        this.getOwners()
      }
731
      this.getTimeslots(this.dateStart, this.dateEnd, this.numSlots)
732
      this.loadPlaylists()
733
    },
734

735
736
737
    // (Re)Load all shows from server and switch to a show with a specific ID.
    // If the id argument is null, the first show in the show array will be used
    loadAndSwitch: function (id) {
738
      this.$store.dispatch('shows/fetchShows')
739
740
      this.$store.dispatch('shows/fetchTypes')
      this.$store.dispatch('shows/fetchFundingCategories')
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
      this.loaded.shows = false
      var uri = process.env.VUE_APP_API_STEERING_SHOWS
      // normal users should only see their own shows, only superusers see all shows
      if (!this.$parent.user.steeringUser.is_superuser) {
        uri += '?owner=' + this.$parent.user.steeringUser.id
      }
      axios.get(uri, {
        withCredentials: true,
        headers: { 'Authorization': 'Bearer ' + this.$parent.user.access_token }
      }).then(response => {
        // if now shows are found, we'll just print a short info message and leave
        if (response.data.length === 0) {
          this.$log.info('The returned show set has 0 length:')
          this.$log.info(response)
          alert('Info: There are no shows connected to your account. See console for details.')
          this.loaded.shows = true
          return
        }
        // now set the new show array and find the index of the show ID
        this.shows = response.data
        let index = 0
        if (id !== null) {
          index = this.shows.findIndex(show => show.id === id)
          // if no show with the given ID was found, we use the first item in the show array
          if (index === -1) { index = 0 }
        }
767
        this.$store.commit('shows/switchShow', index)
768
769
770
771
772
773
774
775
776
        this.loaded.shows = true
        this.switchShow(this.currentShow)
      }).catch(error => {
        this.$log.error(error.response.status + ' ' + error.response.statusText)
        this.$log.error(error.response)
        alert('Error: could not fetch show data. See console for details.')
      })
    },

777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
    loadPlaylists () {
      this.loaded.playlists = false
      let uri = process.env.VUE_APP_API_TANK + 'shows/' + this.shows[this.currentShow].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.current.playlists = response.data.results
        this.loaded.playlists = true
      }).catch(error => {
        //this.$log.error(error.response.status + ' ' + error.response.statusText)
        //this.$log.error(error.response)
        this.$log.error(error)
        alert('Error: could not fetch playlists from tank. See console for details.')
      })
    },

796
    // Fetch timeslots for the current show and use filter variables if provided
797
    getTimeslots: function (start, end, limit, offset) {
798
      var dateRegex = new RegExp('^\\d{4}-\\d{2}-\\d{2}$')
799
      var uri = process.env.VUE_APP_API_STEERING_SHOWS + this.currentShowID + '/timeslots/?'
800
801
802
803
      if (dateRegex.test(start)) { uri += 'start=' + start + '&' }
      if (dateRegex.test(end)) { uri += 'end=' + end + '&' }
      if (!isNaN(parseInt(limit))) { uri += 'limit=' + parseInt(limit) + '&' }
      if (!isNaN(parseInt(offset))) { uri += 'offset=' + parseInt(offset) }
804
805
      this.loaded.timeslots = false
      this.loaded.notes = false
806
807
808
809
      axios.get(uri, {
        withCredentials: true,
        headers: { 'Authorization': 'Bearer ' + this.$parent.user.access_token }
      }).then(response => {
810
811
        // if we use the limit argument results are paginated and look different
        // than without the limit argument
812
        if (!isNaN(parseInt(limit))) {
813
814
815
816
          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
817
          this.current.timeslotmeta.perpage = parseInt(limit)
818
        } else {
819
820
821
822
          this.current.timeslots = response.data
          this.current.timeslotmeta.count = response.data.length
          this.current.timeslotmeta.next = null
          this.current.timeslotmeta.previous = null
823
          this.current.timeslotmeta.perpage = response.data.length
824
        }
825
        this.loaded.timeslots = true
826
        // now that we have the timeslots we can fetch notes for all those timeslots
827
        uri = process.env.VUE_APP_API_STEERING_SHOWS + this.currentShowID + '/notes/?ids='
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
        // 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 = []
        }
855
      }).catch(error => {
856
857
858
        this.$log.error(error.response.status + ' ' + error.response.statusText)
        this.$log.error(error.response)
        alert('Error: could not fetch timeslots. See console for details.')
859
      })
860
861
      // done fetching timeslots
    },
862
863

    // Open the modal to edit a timeslot's note, given its ID and schedule ID
864
865
    editTimeslotNote: function (timeslotID, scheduleID) {
      this.current.note = null
866
867
868
      for (var i in this.current.notes) {
        if (this.current.notes[i].timeslot === timeslotID) {
          this.current.note = this.current.notes[i]
869
870
871
          break
        }
      }
872
      this.$refs.appModalNotes.showModal(this.current.note, timeslotID, scheduleID)
873
    },
874

875
    editTimeslotPlaylist: function (show, schedule, timeslot) {
876
      this.$refs.appModalPlaylist.open(show, schedule, timeslot, this.current.playlists)
877
878
    },

879
    // For a given timeslot ID return the corresponding note, if there is one
880
881
882
883
    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]
884
885
886
887
        }
      }
      return null
    },
888
889

    // For a given timeslot ID, check if there is a note and return it
890
    prettyTimeslotNote: function (timeslotID) {
891
      var note = this.getTimeslotNoteTitle(timeslotID)
892
893
894
895
896
897
      if (note !== null) {
        return this.prettyTitle(note)
      } else {
        return ''
      }
    },
898
899

    // Limiting display of strings up to 25 characters plus "..."
900
    prettyTitle: function (title) {
901
902
903
      if (title === '') { return '...' }
      else if (title.length > 25) { return title.slice(0, 25) + '...' }
      else { return title }
904
    },
905
906
907
908

    // TODO: all those getSomething functions could be probably merged into one
    // generic getItem function. Maybe this.current should be implemented
    // in a dictionary kind of way then?
909
910
911
912
913
914
915
    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) {
916
          axios.get(process.env.VUE_APP_API_STEERING + 'categories/' + this.shows[this.currentShow].category[i] + '/', {
917
918
919
            withCredentials: true,
            headers: { 'Authorization': 'Bearer ' + this.$parent.user.access_token }
          }).then(response => {
920
921
922
923
924
925
            this.current.categories.push(response.data)
          }).catch(error => {
            loadingError = true
            alert('There was an error fetching categories from the server: ' + error)
          })
        }
926
        if (!loadingError) { this.loaded.categories = true }
927
928
      }
    },
929

930
931
932
933
934
935
936
    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) {
937
          axios.get(process.env.VUE_APP_API_STEERING + 'hosts/' + this.shows[this.currentShow].hosts[i] + '/', {
938
939
940
            withCredentials: true,
            headers: { 'Authorization': 'Bearer ' + this.$parent.user.access_token }
          }).then(response => {
941
942
943
944
945
946
            this.current.hosts.push(response.data)
          }).catch(error => {
            loadingError = true
            alert('There was an error fetching hosts from the server: ' + error)
          })
        }
947
        if (!loadingError) { this.loaded.hosts = true }
948
949
      }
    },
950

951
952
953
954
    getLanguages: function () {
      this.current.languages = []
      var loadingError = false
      if (this.shows[this.currentShow].language.length === 0) {
955
        this.loaded.languages = true
956
957
      } else {
        for (var i in this.shows[this.currentShow].language) {
958
          axios.get(process.env.VUE_APP_API_STEERING + 'languages/' + this.shows[this.currentShow].language[i] + '/', {
959
960
961
            withCredentials: true,
            headers: { 'Authorization': 'Bearer ' + this.$parent.user.access_token }
          }).then(response => {
962
963
964
965
966
967
            this.current.languages.push(response.data)
          }).catch(error => {
            loadingError = true
            alert('There was an error fetching languages from the server: ' + error)
          })
        }
968
        if (!loadingError) { this.loaded.languages = true }
969
970
      }
    },
971

972
973
974
975
976
977
978
    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) {
979
          axios.get(process.env.VUE_APP_API_STEERING + 'topics/' + this.shows[this.currentShow].topic[i] + '/', {
980
981
982
            withCredentials: true,
            headers: { 'Authorization': 'Bearer ' + this.$parent.user.access_token }
          }).then(response => {
983
984
985
986
987
988
            this.current.topics.push(response.data)
          }).catch(error => {
            loadingError = true
            alert('There was an error fetching topics from the server: ' + error)
          })
        }
989
        if (!loadingError) { this.loaded.topics = true }
990
991
      }
    },
992

993
994
995
996
997
998
999
    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) {
1000
          axios.get(process.env.VUE_APP_API_STEERING + 'musicfocus/' + this.shows[this.currentShow].musicfocus[i] + '/', {