ShowManager.vue 42.7 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

236
237
238
      <b-row>
        <b-col lg="6">
          <p>
239
240
241
            <b-badge variant="light">
              E-Mail:
            </b-badge>
242
243
            <span v-if="shows[currentShow].email === null"><small><i>(none set)</i></small></span>
            <span v-else>{{ shows[currentShow].email }}</span>
244
245
246
247
248
            <img
              src="../assets/16x16/emblem-system.png"
              alt="edit contact e-mail"
              @click="$refs.appModalShow.showEmail()"
            >
249
250
          </p>
        </b-col>
251

252
253
        <b-col lg="6">
          <p>
254
255
256
            <b-badge variant="light">
              Website:
            </b-badge>
257
258
            <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>
259
260
261
262
263
            <img
              src="../assets/16x16/emblem-system.png"
              alt="edit website"
              @click="$refs.appModalShow.showWebsite()"
            >
264
265
          </p>
        </b-col>
266

267
268
        <b-col lg="6">
          <p>
269
270
271
            <b-badge variant="light">
              Show type:
            </b-badge>
272
273
274
275
            <!-- 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>
276
277
278
279
280
              <img
                src="../assets/16x16/emblem-system.png"
                alt="edit"
                @click="$refs.appModalShow.showType()"
              >
281
            </span>
282
283
284
285
286
            <span v-else><img
              src="../assets/radio.gif"
              height="24px"
              alt="loading data"
            ></span>
287
288
          </p>
        </b-col>
289

290
291
        <b-col lg="6">
          <p>
292
293
294
            <b-badge variant="light">
              Funding category (eg. for RTR):
            </b-badge>
295
            <!-- TODO: discuss: should this be visible to show owners or only to administrators? -->
296
297
298
            <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>
299
300
301
302
303
              <img
                src="../assets/16x16/emblem-system.png"
                alt="edit"
                @click="$refs.appModalShow.showFundingCategory()"
              >
304
            </span>
305
306
307
308
309
            <span v-else><img
              src="../assets/radio.gif"
              height="24px"
              alt="loading data"
            ></span>
310
311
          </p>
        </b-col>
312

313
314
315
        <b-col lg="6">
          <p>
            <!-- TODO: discuss: should this be visible to show owners or only to administrators? -->
316
            <!-- TODO: fetch name for predecessor from steering api -->
317
318
319
            <b-badge variant="light">
              Predecessor:
            </b-badge>
320
            <span v-if="shows[currentShow].predecessor === null"><small><i>This show has no predecessor show.</i></small></span>
321
            <span v-else>{{ predecessorName }}</span>
322
323
324
325
326
            <img
              src="../assets/16x16/emblem-system.png"
              alt="edit"
              @click="$refs.appModalShow.showPredecessor()"
            >
327
328
          </p>
        </b-col>
329

330
331
        <b-col lg="6">
          <p>
332
333
334
            <b-badge variant="light">
              CBA Series ID:
            </b-badge>
335
336
            <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>
337
338
339
340
341
            <img
              src="../assets/16x16/emblem-system.png"
              alt="edit CBA series ID"
              @click="$refs.appModalShow.showCBAid()"
            >
342
343
          </p>
        </b-col>
344

345
346
        <b-col lg="6">
          <p>
347
348
349
            <b-badge variant="light">
              Fallback List/Pool:
            </b-badge>
350
351
            <span v-if="shows[currentShow].fallback_id === ''"><small><i>(none set)</i></small></span>
            <span v-else>ID: {{ shows[currentShow].fallback_id }}</span>
352
353
354
355
356
            <img
              src="../assets/16x16/emblem-system.png"
              alt="edit"
              @click="notYetImplemented"
            >
357
358
359
          </p>
        </b-col>
      </b-row>
360

361
362
363
364
365
366
367
368
369
370
371
      <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"
372
            @click="$refs.appModalSuperuser.showModalOwners(shows[currentShow])"
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
          >
        </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">

411
412
      <b-row>
        <b-col lg="2">
413
414
415
416
417
418
419
          <b-badge style="width:80%;">
            Categories:
          </b-badge> <img
            src="../assets/16x16/emblem-system.png"
            alt="edit"
            @click="$refs.appModalShow.showCategories()"
          >
420
421
422
423
424
425
426
427
        </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>
428
429
430
431
432
433
                <li
                  v-for="cat in current.categories"
                  :key="cat.id"
                >
                  {{ cat.category }}
                </li>
434
435
436
              </ul>
            </p>
          </div>
437
438
439
440
441
442
443
          <div v-else>
            <img
              src="../assets/radio.gif"
              height="24px"
              alt="loading data"
            ><br>
          </div>
444
        </b-col>
445

446
        <b-col lg="2">
447
448
449
450
451
452
453
          <b-badge style="width:80%;">
            Topics:
          </b-badge> <img
            src="../assets/16x16/emblem-system.png"
            alt="edit"
            @click="$refs.appModalShow.showTopics()"
          >
454
455
456
457
458
459
460
461
        </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>
462
463
464
465
466
467
                <li
                  v-for="topic in current.topics"
                  :key="topic.id"
                >
                  {{ topic.topic }}
                </li>
468
469
470
              </ul>
            </p>
          </div>
471
472
473
474
475
476
477
          <div v-else>
            <img
              src="../assets/radio.gif"
              height="24px"
              alt="loading data"
            ><br>
          </div>
478
        </b-col>
479

480
        <b-col lg="2">
481
482
483
484
485
486
487
          <b-badge style="width:80%;">
            Musicfocus:
          </b-badge> <img
            src="../assets/16x16/emblem-system.png"
            alt="edit"
            @click="$refs.appModalShow.showMusicFocus()"
          >
488
489
490
491
492
493
494
495
        </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>
496
497
498
499
500
501
                <li
                  v-for="focus in current.musicfocus"
                  :key="focus.id"
                >
                  {{ focus.focus }}
                </li>
502
503
504
              </ul>
            </p>
          </div>
505
506
507
508
509
510
511
          <div v-else>
            <img
              src="../assets/radio.gif"
              height="24px"
              alt="loading data"
            ><br>
          </div>
512
        </b-col>
513

514
        <b-col lg="2">
515
516
517
518
519
520
521
          <b-badge style="width:80%;">
            Languages:
          </b-badge> <img
            src="../assets/16x16/emblem-system.png"
            alt="edit"
            @click="$refs.appModalShow.showLanguages()"
          >
522
523
524
525
526
527
528
529
        </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>
530
531
532
533
534
535
                <li
                  v-for="lang in current.languages"
                  :key="lang.id"
                >
                  {{ lang.name }}
                </li>
536
537
538
              </ul>
            </p>
          </div>
539
540
541
542
543
544
545
          <div v-else>
            <img
              src="../assets/radio.gif"
              height="24px"
              alt="loading data"
            ><br>
          </div>
546
        </b-col>
547

548
        <b-col lg="2">
549
550
551
552
553
554
555
          <b-badge style="width:80%;">
            Hosts:
          </b-badge> <img
            src="../assets/16x16/emblem-system.png"
            alt="edit"
            @click="$refs.appModalShow.showHosts()"
          >
556
557
558
559
560
561
562
563
564
        </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>
565
566
567
568
569
570
                <li
                  v-for="host in current.hosts"
                  :key="host.id"
                >
                  {{ host.name }}
                </li>
571
572
573
              </ul>
            </p>
          </div>
574
575
576
577
578
579
580
          <div v-else>
            <img
              src="../assets/radio.gif"
              height="24px"
              alt="loading data"
            ><br>
          </div>
581
582
        </b-col>
      </b-row>
583
584
585

      <b-row>
        <b-col lg="2">
586
587
588
589
590
591
592
          <b-badge style="width:80%;">
            Logo:
          </b-badge> <img
            src="../assets/16x16/emblem-system.png"
            alt="edit"
            @click="$refs.appModalShow.showLogo()"
          >
593
594
        </b-col>
        <b-col lg="4">
595
          <div v-if="current.logo.length === 0">
596
597
598
            <small><i>(none set)</i></small>
          </div>
          <div v-else>
599
600
601
602
603
604
605
            <br>
            <b-img
              thumbnail
              :src="current.logo"
              fluid
              @click="$refs.appModalShow.showLogo()"
            />
606
607
608
609
          </div>
        </b-col>

        <b-col lg="2">
610
611
612
613
614
615
616
          <b-badge style="width:80%;">
            Image:
          </b-badge> <img
            src="../assets/16x16/emblem-system.png"
            alt="edit"
            @click="$refs.appModalShow.showImage()"
          >
617
618
        </b-col>
        <b-col lg="4">
619
          <div v-if="current.image.length === 0">
620
621
622
            <small><i>(none set)</i></small>
          </div>
          <div v-else>
623
624
625
626
627
628
629
            <br>
            <b-img
              thumbnail
              :src="current.image"
              fluid
              @click="$refs.appModalShow.showImage()"
            />
630
631
632
633
          </div>
        </b-col>
      </b-row>

634
      <hr>
635
    </div>
636
  </b-container>
637
638
639
</template>

<script>
640
import showJumbotron from './ShowJumbotron.vue'
641
import modalNotes from './ShowManagerModalNotes.vue'
642
import modalShow from './ShowManagerModalShow.vue'
643
import modalSuperuser from './ShowManagerModalSuperuser.vue'
644
import modalPlaylist from './ShowManagerModalPlaylist.vue'
645
import timeslotSort from '../mixins/timeslotSort'
646
import prettyDate from '../mixins/prettyDate'
647
import axios from 'axios'
648
import DOMPurify from 'dompurify'
649
650

export default {
651
652
  // 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
653
  components: {
654
    'app-modalNotes': modalNotes,
655
656
    'app-modalShow': modalShow,
    'app-modalSuperuser': modalSuperuser,
657
    'app-modalPlaylist': modalPlaylist,
658
    'show-jumbotron': showJumbotron,
659
  },
660
661

  // generic functions that we want to use from our mixins folder
662
  mixins: [ timeslotSort, prettyDate ],
663
664
665

  // this component will be handling a lot of data - probably the component can
  // be refactored to get rid of some redundancy here
666
667
  data () {
    return {
668
      shows: [],      // an array of objects describing our shows (empty at load, will be populated on created())
669
      numSlots: process.env.VUE_APP_TIMESLOT_FILTER_DEFAULT_NUMSLOTS, // all form input values are provided as strings
670
      dateStart: this.apiDate(new Date()),
671
      dateEnd: this.apiDate(new Date(new Date().getTime() + process.env.VUE_APP_TIMESLOT_FILTER_DEFAULT_DAYS * 86400000)),
672
673
674

      // the loaded object holds flags for the different things we will fetch
      // from the AuRa steering module
675
676
677
678
679
680
681
682
683
      loaded: {
        shows: false,
        timeslots: false,
        notes: false,
        categories: false,
        hosts: false,
        languages: false,
        topics: false,
        musicfocus: false,
684
        fundingcategory: false,
685
686
        type: false,
        owners: false,
687
        playlists: false,
688
      },
689
690
691

      // 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
692
693
694
695
696
697
      current: {
        categories: [],
        hosts: [],
        languages: [],
        topics: [],
        musicfocus: [],
698
        fundingcategory: [],
699
        type: [],
700
        playlists: [],
701
702
703
704
        timeslots: [],
        timeslotmeta: {    // meta info when pagination is used
          count: 0,
          next: null,
705
706
707
          previous: null,
          page: 1,   // page indexes start at 1 for <b-pagination> components
          perpage: 10
708
709
        },
        note: {},
710
711
        notes: [],
        image: '',
712
713
        logo: '',
        owners: [],
714
      },
715
716

      // this is used to configure the table with all the filtered timeslots
717
718
719
720
      notesTableArrayFields: [
        { key: 'title', label: 'Title of emission' },
        { key: 'starts', label: 'Emission start' },
        { key: 'duration', label: 'Duration' },
721
722
        { key: 'options', label: 'Edit' },
        { key: 'playlist', label: 'Playlist' },
723
      ]
724
725
    }
  },
726

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

731
732
733
    currentShow () { return this.$store.state.shows.selected.index },
    currentShowID () { return this.$store.state.shows.selected.id },

734
735
736
737
    // 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.
738
739
740
    sanitizedShowDescription: function () {
      return DOMPurify.sanitize(this.shows[this.currentShow].description)
    },
741
742
743
744
745
746
747
748

    // In order to not only just show the predecessor of a show as an ID. we
    // have to find it in our shows array to then output the predecessors name.
    // This currently assumes that a user has access to all the predecessors
    // of the shows as well.
    // TODO/discuss: if all predecessor names should be accessible, independent
    // of access rights, then we would need to load all predecessors show after
    // loading our initial shows as well.
749
750
    predecessorName: function () {
      for (var i in this.shows) {
751
        if (this.shows[i].id === this.shows[this.currentShow].predecessor) {
752
753
754
755
756
          return this.shows[i].name
        }
      }
      return 'Name of predecessor show not available'
    },
757
758
759

    // 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
760
    notesTableArray: function () {
761
      let rows = []
762
      for (var i in this.current.timeslots) {
763
        let note = this.getNoteByTimeslotID(this.current.timeslots[i].id)
764
        if (note !== null) { note = note.title }
765
766
767
768

        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)
769
770
771
772
773
774
          if (playlist) {
            if (playlist.description.length > 0) {
              playlistTitle = playlist.description
            } else {
              playlistTitle = playlist.id
            }
775
776
777
778
          }
        }

        rows.push({
779
780
          title: note,
          starts: this.prettyDateTime(this.current.timeslots[i].start),
781
          duration: this.prettyDuration(this.current.timeslots[i].start, this.current.timeslots[i].end),
782
783
          options: {
            id: this.current.timeslots[i].id,
784
785
786
787
            schedule: this.current.timeslots[i].schedule,
            play: this.current.timeslots[i].playlist_id !== null
          },
          playlist: playlistTitle
788
789
        })
      }
790
      return rows
791
792
    }
  },
793
794
795

  // Right after this component is set up, we want to fetch all available shows
  // from the AuRa steering module.
796
  created () {
797
798
799
    // 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)
800
  },
801
802
803

  // 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)
804
  methods: {
805
806

    // Apply the newly set filter parameters for our timeslot table
807
808
809
810
    applyFilter: function () {
      this.current.timeslotmeta.page = 1
      this.getTimeslots(this.dateStart, this.dateEnd, this.numSlots)
    },
811
812

    // Reset the filter parameters for our timeslot table to config defaults
813
    resetFilter: function () {
814
      this.numSlots = process.env.VUE_APP_TIMESLOT_FILTER_DEFAULT_NUMSLOTS
815
      this.dateStart = this.apiDate(new Date())
816
      this.dateEnd = this.apiDate(new Date(new Date().getTime() + process.env.VUE_APP_TIMESLOT_FILTER_DEFAULT_DAYS * 86400000))
817
818
819
      this.current.timeslotmeta.page = 1
      this.getTimeslots(this.dateStart, this.dateEnd, this.numSlots)
    },
820
821

    // Load a different page of timeslots for the timeslots table
822
823
824
825
826
827
    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)
      }
    },
828
829
830
831
832
833
834
835

    // 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.
836
    switchShow: function (index) {
837
838
839
      // 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
840
841
842
843
844
845
846
847
      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
848
      this.loaded.fundingcategory = false
849
      this.loaded.owners = false
850
      // set the current show and its ID to whatever we want to switch to now
851
      this.$store.commit('shows/switchShow', index)
852
853
      // and check if images are available and set image strings, because we
      // cannot use them directly inside the b-img if they are null
854
855
856
857
      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 }
858
859
860
861
862
863
      // before we load timeslots and notes, we want to fetch the general settings first
      this.getCategories()
      this.getHosts()
      this.getLanguages()
      this.getTopics()
      this.getMusicfocus()
864
      this.getFundingCategory()
865
      this.getType()
866
867
868
      if (this.$parent.user.steeringUser.is_superuser) {
        this.getOwners()
      }
869
      this.getTimeslots(this.dateStart, this.dateEnd, this.numSlots)
870
      this.loadPlaylists()
871
    },
872

873
874
875
    // (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) {
876
      this.$store.dispatch('shows/fetchShows')
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
      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 }
        }
903
        this.$store.commit('shows/switchShow', index)
904
905
906
907
908
909
910
911
912
        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.')
      })
    },

913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
    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.')
      })
    },

932
    // Fetch timeslots for the current show and use filter variables if provided
933
    getTimeslots: function (start, end, limit, offset) {
934
      var dateRegex = new RegExp('^\\d{4}-\\d{2}-\\d{2}$')
935
      var uri = process.env.VUE_APP_API_STEERING_SHOWS + this.currentShowID + '/timeslots/?'
936
937
938
939
      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) }
940
941
      this.loaded.timeslots = false
      this.loaded.notes = false
942
943
944
945
      axios.get(uri, {
        withCredentials: true,
        headers: { 'Authorization': 'Bearer ' + this.$parent.user.access_token }
      }).then(response => {
946
947
        // if we use the limit argument results are paginated and look different
        // than without the limit argument
948
        if (!isNaN(parseInt(limit))) {
949
950
951
952
          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
953
          this.current.timeslotmeta.perpage = parseInt(limit)
954
        } else {
955
956
957
958
          this.current.timeslots = response.data
          this.current.timeslotmeta.count = response.data.length
          this.current.timeslotmeta.next = null
          this.current.timeslotmeta.previous = null
959
          this.current.timeslotmeta.perpage = response.data.length
960
        }
961
        this.loaded.timeslots = true
962
        // now that we have the timeslots we can fetch notes for all those timeslots
963
        uri = process.env.VUE_APP_API_STEERING_SHOWS + this.currentShowID + '/notes/?ids='
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
        // 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 = []
        }
991
      }).catch(error => {
992
993
994
        this.$log.error(error.response.status + ' ' + error.response.statusText)
        this.$log.error(error.response)
        alert('Error: could not fetch timeslots. See console for details.')
995
      })
996
997
      // done fetching timeslots
    },
998
999

    // Open the modal to edit a timeslot's note, given its ID and schedule ID
1000
    editTimeslotNote: function (timeslotID, scheduleID) {