ShowManager.vue 37.6 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
13
14
15
        <b-dropdown
          id="ddshows"
          text="Sendereihe auswählen"
          variant="info"
        >
          <b-dropdown-item
16
            v-for="(show, index) in shows"
17
18
19
20
21
            :key="show.id"
            @click="switchShow(index)"
          >
            {{ show.name }}
          </b-dropdown-item>
22
23
24
        </b-dropdown>
      </b-col>
    </b-row>
25
    <hr>
26

27
28
    <!-- The jumbotron is used to display the name and description of the
    currently selected show -->
29
    <b-jumbotron>
30
      <!-- The show title goes into the jumbotron header -->
31
      <template slot="header">
32
33
        <span v-if="loaded.shows">
          {{ shows[currentShow].name }}
34
35
36
37
38
          <img
            src="../assets/16x16/emblem-system.png"
            alt="edit name of show"
            @click="$refs.appModalShow.showName()"
          >
39
        </span>
40
41
        <span v-else>Shows are being loaded</span>
      </template>
42
      <!-- The short description of the show goes into the jumbotron lead -->
43
      <template slot="lead">
44
        <span v-if="loaded.shows">{{ shows[currentShow].short_description }}</span>
45
46
47
48
49
        <img
          src="../assets/16x16/emblem-system.png"
          alt="edit short description"
          @click="$refs.appModalShow.showShortDescription()"
        >
50
      </template>
51
      <!-- The rest of the jumbotron is filled with the show description -->
52
      <div v-if="loaded.shows">
53
54
55
56
57
        <b>Description:</b> <img
          src="../assets/16x16/emblem-system.png"
          alt="edit description"
          @click="$refs.appModalShow.showDescription()"
        >
58
        <div v-if="loaded.shows">
59
60
61
62
63
64
          <!--
          we are disabling the linter warning for the next line, because, while it
          generally is not advisible to use v-html, in this case we took thorough
          steps to sanitize the date before inserting it into the DOM
          -->
          <!-- eslint-disable-next-line vue/no-v-html -->
65
          <p v-html="sanitizedShowDescription" />
66
67
          <!-- TODO: add image and logo here? -->
        </div>
68
      </div>
69
70
    </b-jumbotron>

71
    <!-- If the shows are not fully loaded yet, we just put the loading sign -->
72
    <div v-if="!loaded.shows">
73
74
      <b-row>
        <b-col align="center">
75
76
77
78
          <img
            src="../assets/radio.gif"
            alt="loading data"
          >
79
80
        </b-col>
      </b-row>
81
    </div>
82
83

    <!-- When all show data is loaded, here we display all the rest -->
84
    <div v-else>
85
      <!-- include the modals to edit show and timeslot entries from the modal compontents -->
86
87
88
89
90
91
92
93
94
      <app-modalShow
        ref="appModalShow"
        :show="shows[currentShow]"
      />
      <app-modalNotes
        ref="appModalNotes"
        :show="shows[currentShow]"
        :show-aggregate="current"
      />
95

96
      <!-- here are the filter settings for our timeslots table -->
97
      <b-card>
98
99
100
        <b-btn v-b-toggle.timeslotFilterCollapse>
          Toggle timeslot filters
        </b-btn>
101
        <b-collapse id="timeslotFilterCollapse">
102
          <br>
103
          <!-- How many slots to show per table page -->
104
          <b-row>
105
106
107
108
109
110
111
112
113
114
            <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>
115
          </b-row>
116
          <!-- The start date to display timeslots from (defaults to today) -->
117
          <b-row>
118
119
120
121
122
123
124
125
126
127
            <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>
128
          </b-row>
129
          <!-- The end date until to wich to display timeslots -->
130
          <b-row>
131
132
133
134
135
136
137
138
139
140
            <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>
141
          </b-row>
142
          <br>
143
          <!-- And finally two buttons, one to reset and one to apply the filter -->
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
          <b-container
            fluid
            class="text-right"
          >
            <b-btn
              variant="outline-danger"
              @click="resetFilter()"
            >
              Reset filter
            </b-btn> &nbsp;
            <b-btn
              variant="info"
              @click="applyFilter()"
            >
              Apply filter
            </b-btn>
160
161
162
163
          </b-container>
        </b-collapse>
      </b-card>

164
      <br>
165

166
167
      <!-- here we show our table of timeslots, if the timeslots are already
      loaded (otherwise we just show the loading symbol) -->
168
      <div v-if="loaded.timeslots">
169
170
171
172
173
174
175
        <b-table
          striped
          hover
          outlined
          :fields="notesTableArrayFields"
          :items="notesTableArray"
        >
176
          <!-- Title of the timeslot (if already set) -->
177
178
179
180
          <template
            slot="title"
            slot-scope="data"
          >
181
182
183
            <span v-if="data.value">{{ data.value }}</span>
            <span v-else><small><i>(none set)</i></small></span>
          </template>
184
          <!-- Date and time when this timeslot starts -->
185
186
187
188
          <template
            slot="starts"
            slot-scope="data"
          >
189
190
            {{ data.value }}
          </template>
191
          <!-- The duration of this timeslot -->
192
193
194
195
          <template
            slot="duration"
            slot-scope="data"
          >
196
197
            {{ data.value }}
          </template>
198
199
          <!-- And here all the buttons for editing and doing other things
          with the displayed timeslot -->
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
          <template
            slot="options"
            slot-scope="data"
          >
            <span
              class="timeslotEditLink"
              @click="editTimeslotNote(data.item.options.id, data.item.options.schedule)"
            ><img
              src="../assets/16x16/emblem-system.png"
              alt="Edit description"
              title="Edit description"
            ></span>
            <span
              class="timeslotEditLink"
              @click="notYetImplemented()"
            ><img
              src="../assets/16x16/media-eject.png"
              alt="Upload audio file / Create playlist"
              title="Upload audio file / Create playlist"
            ></span>
            <span
              class="timeslotEditLink"
              @click="notYetImplemented()"
            ><img
              src="../assets/16x16/media-playback-start.png"
              alt="Play audio file / playlist"
              title="Play audio file / playlist"
            ></span>
            <span
              class="timeslotEditLink"
              @click="notYetImplemented()"
            >...</span>
232
233
          </template>
        </b-table>
234
235
236
237
238
239
240
241
        <b-pagination
          v-model="current.timeslotmeta.page"
          align="center"
          :total-rows="current.timeslotmeta.count"
          :per-page="current.timeslotmeta.perpage"
          @change="timeslotsPage"
        />
      </div>
242
243
244
245
246
247
248
249
250
      <!-- 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>
251
252
      </div>

253
      <hr>
254

255
256
257
258
      <h2>Allgemeine Einstellungen zur Sendereihe:</h2>
      <b-row>
        <b-col lg="6">
          <p>
259
260
261
            <b-badge variant="light">
              E-Mail:
            </b-badge>
262
263
            <span v-if="shows[currentShow].email === null"><small><i>(none set)</i></small></span>
            <span v-else>{{ shows[currentShow].email }}</span>
264
265
266
267
268
            <img
              src="../assets/16x16/emblem-system.png"
              alt="edit contact e-mail"
              @click="$refs.appModalShow.showEmail()"
            >
269
270
          </p>
        </b-col>
271

272
273
        <b-col lg="6">
          <p>
274
275
276
            <b-badge variant="light">
              Website:
            </b-badge>
277
278
            <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>
279
280
281
282
283
            <img
              src="../assets/16x16/emblem-system.png"
              alt="edit website"
              @click="$refs.appModalShow.showWebsite()"
            >
284
285
          </p>
        </b-col>
286

287
288
        <b-col lg="6">
          <p>
289
290
291
            <b-badge variant="light">
              Show type:
            </b-badge>
292
293
294
295
            <!-- 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>
296
297
298
299
300
              <img
                src="../assets/16x16/emblem-system.png"
                alt="edit"
                @click="$refs.appModalShow.showType()"
              >
301
            </span>
302
303
304
305
306
            <span v-else><img
              src="../assets/radio.gif"
              height="24px"
              alt="loading data"
            ></span>
307
308
          </p>
        </b-col>
309

310
311
        <b-col lg="6">
          <p>
312
313
314
            <b-badge variant="light">
              Funding category (eg. for RTR):
            </b-badge>
315
            <!-- TODO: discuss: should this be visible to show owners or only to administrators? -->
316
317
318
            <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>
319
320
321
322
323
              <img
                src="../assets/16x16/emblem-system.png"
                alt="edit"
                @click="$refs.appModalShow.showFundingCategory()"
              >
324
            </span>
325
326
327
328
329
            <span v-else><img
              src="../assets/radio.gif"
              height="24px"
              alt="loading data"
            ></span>
330
331
          </p>
        </b-col>
332

333
334
335
        <b-col lg="6">
          <p>
            <!-- TODO: discuss: should this be visible to show owners or only to administrators? -->
336
            <!-- TODO: fetch name for predecessor from steering api -->
337
338
339
            <b-badge variant="light">
              Predecessor:
            </b-badge>
340
            <span v-if="shows[currentShow].predecessor === null"><small><i>This show has no predecessor show.</i></small></span>
341
            <span v-else>{{ predecessorName }}</span>
342
343
344
345
346
            <img
              src="../assets/16x16/emblem-system.png"
              alt="edit"
              @click="$refs.appModalShow.showPredecessor()"
            >
347
348
          </p>
        </b-col>
349

350
351
        <b-col lg="6">
          <p>
352
353
354
            <b-badge variant="light">
              CBA Series ID:
            </b-badge>
355
356
            <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>
357
358
359
360
361
            <img
              src="../assets/16x16/emblem-system.png"
              alt="edit CBA series ID"
              @click="$refs.appModalShow.showCBAid()"
            >
362
363
          </p>
        </b-col>
364

365
366
        <b-col lg="6">
          <p>
367
368
369
            <b-badge variant="light">
              Fallback List/Pool:
            </b-badge>
370
371
            <span v-if="shows[currentShow].fallback_id === ''"><small><i>(none set)</i></small></span>
            <span v-else>ID: {{ shows[currentShow].fallback_id }}</span>
372
373
374
375
376
            <img
              src="../assets/16x16/emblem-system.png"
              alt="edit"
              @click="notYetImplemented"
            >
377
378
379
          </p>
        </b-col>
      </b-row>
380

381
382
      <b-row>
        <b-col lg="2">
383
384
385
386
387
388
389
          <b-badge style="width:80%;">
            Categories:
          </b-badge> <img
            src="../assets/16x16/emblem-system.png"
            alt="edit"
            @click="$refs.appModalShow.showCategories()"
          >
390
391
392
393
394
395
396
397
        </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>
398
399
400
401
402
403
                <li
                  v-for="cat in current.categories"
                  :key="cat.id"
                >
                  {{ cat.category }}
                </li>
404
405
406
              </ul>
            </p>
          </div>
407
408
409
410
411
412
413
          <div v-else>
            <img
              src="../assets/radio.gif"
              height="24px"
              alt="loading data"
            ><br>
          </div>
414
        </b-col>
415

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

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

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

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

      <b-row>
        <b-col lg="2">
556
557
558
559
560
561
562
          <b-badge style="width:80%;">
            Logo:
          </b-badge> <img
            src="../assets/16x16/emblem-system.png"
            alt="edit"
            @click="$refs.appModalShow.showLogo()"
          >
563
564
        </b-col>
        <b-col lg="4">
565
          <div v-if="current.logo.length === 0">
566
567
568
            <small><i>(none set)</i></small>
          </div>
          <div v-else>
569
570
571
572
573
574
575
            <br>
            <b-img
              thumbnail
              :src="current.logo"
              fluid
              @click="$refs.appModalShow.showLogo()"
            />
576
577
578
579
          </div>
        </b-col>

        <b-col lg="2">
580
581
582
583
584
585
586
          <b-badge style="width:80%;">
            Image:
          </b-badge> <img
            src="../assets/16x16/emblem-system.png"
            alt="edit"
            @click="$refs.appModalShow.showImage()"
          >
587
588
        </b-col>
        <b-col lg="4">
589
          <div v-if="current.image.length === 0">
590
591
592
            <small><i>(none set)</i></small>
          </div>
          <div v-else>
593
594
595
596
597
598
599
            <br>
            <b-img
              thumbnail
              :src="current.image"
              fluid
              @click="$refs.appModalShow.showImage()"
            />
600
601
602
603
          </div>
        </b-col>
      </b-row>

604
      <hr>
605
    </div>
606
  </b-container>
607
608
609
</template>

<script>
610
import modalNotes from './ShowManagerModalNotes.vue'
611
import modalShow from './ShowManagerModalShow.vue'
612
import timeslotSort from '../mixins/timeslotSort'
613
import prettyDate from '../mixins/prettyDate'
614
import axios from 'axios'
615
import DOMPurify from 'dompurify'
616
617

export default {
618
619
  // 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
620
  components: {
621
622
    'app-modalNotes': modalNotes,
    'app-modalShow': modalShow
623
  },
624
625

  // generic functions that we want to use from our mixins folder
626
  mixins: [ timeslotSort, prettyDate ],
627
628
629

  // this component will be handling a lot of data - probably the component can
  // be refactored to get rid of some redundancy here
630
631
  data () {
    return {
632
633
634
      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
635
      numSlots: process.env.VUE_APP_TIMESLOT_FILTER_DEFAULT_NUMSLOTS, // all form input values are provided as strings
636
      dateStart: this.apiDate(new Date()),
637
      dateEnd: this.apiDate(new Date(new Date().getTime() + process.env.VUE_APP_TIMESLOT_FILTER_DEFAULT_DAYS * 86400000)),
638
639
640

      // the loaded object holds flags for the different things we will fetch
      // from the AuRa steering module
641
642
643
644
645
646
647
648
649
      loaded: {
        shows: false,
        timeslots: false,
        notes: false,
        categories: false,
        hosts: false,
        languages: false,
        topics: false,
        musicfocus: false,
650
        fundingcategory: false,
651
        type: false
652
      },
653
654
655

      // 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
656
657
658
659
660
661
      current: {
        categories: [],
        hosts: [],
        languages: [],
        topics: [],
        musicfocus: [],
662
        fundingcategory: [],
663
        type: [],
664
665
666
667
        timeslots: [],
        timeslotmeta: {    // meta info when pagination is used
          count: 0,
          next: null,
668
669
670
          previous: null,
          page: 1,   // page indexes start at 1 for <b-pagination> components
          perpage: 10
671
672
        },
        note: {},
673
674
675
        notes: [],
        image: '',
        logo: ''
676
      },
677
678

      // this is used to configure the table with all the filtered timeslots
679
680
681
682
683
684
      notesTableArrayFields: [
        { key: 'title', label: 'Title of emission' },
        { key: 'starts', label: 'Emission start' },
        { key: 'duration', label: 'Duration' },
        { key: 'options', label: 'Edit' }
      ]
685
686
    }
  },
687
688
689

  // Some of the info we need in the tempalte are not easily and directly
  // retrievable, so we are computing them on the fly, when they are needed
690
  computed: {
691
692
693
694
695

    // 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.
696
697
698
    sanitizedShowDescription: function () {
      return DOMPurify.sanitize(this.shows[this.currentShow].description)
    },
699
700
701
702
703
704
705
706

    // 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.
707
708
    predecessorName: function () {
      for (var i in this.shows) {
709
        if (this.shows[i].id === this.shows[this.currentShow].predecessor) {
710
711
712
713
714
          return this.shows[i].name
        }
      }
      return 'Name of predecessor show not available'
    },
715
716
717

    // 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
718
719
    notesTableArray: function () {
      var arr = []
720
721
      for (var i in this.current.timeslots) {
        var note = this.getNoteByTimeslotID(this.current.timeslots[i].id)
722
        if (note !== null) { note = note.title }
723
        arr.push({
724
725
          title: note,
          starts: this.prettyDateTime(this.current.timeslots[i].start),
726
          duration: this.prettyDuration(this.current.timeslots[i].start, this.current.timeslots[i].end),
727
728
729
730
          options: {
            id: this.current.timeslots[i].id,
            schedule: this.current.timeslots[i].schedule
          }
731
732
733
        })
      }
      return arr
734
735
    }
  },
736
737
738

  // Right after this component is set up, we want to fetch all available shows
  // from the AuRa steering module.
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
  created () {
    var uri = process.env.VUE_APP_API_STEERING_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 (response.data.length === 0) {
        alert('There are now shows associated with your account!')
        return
      }
      this.shows = response.data
      this.currentShowID = this.shows[0].id
      this.currentShow = 0
      this.loaded.shows = true
      this.switchShow(this.currentShow)
    }).catch(error => {
      alert('There was an error fetching shows from the server: ' + error)
    })
  },
761
762
763

  // 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)
764
  methods: {
765
766

    // Apply the newly set filter parameters for our timeslot table
767
768
769
770
    applyFilter: function () {
      this.current.timeslotmeta.page = 1
      this.getTimeslots(this.dateStart, this.dateEnd, this.numSlots)
    },
771
772

    // Reset the filter parameters for our timeslot table to config defaults
773
    resetFilter: function () {
774
      this.numSlots = process.env.VUE_APP_TIMESLOT_FILTER_DEFAULT_NUMSLOTS
775
      this.dateStart = this.apiDate(new Date())
776
      this.dateEnd = this.apiDate(new Date(new Date().getTime() + process.env.VUE_APP_TIMESLOT_FILTER_DEFAULT_DAYS * 86400000))
777
778
779
      this.current.timeslotmeta.page = 1
      this.getTimeslots(this.dateStart, this.dateEnd, this.numSlots)
    },
780
781

    // Load a different page of timeslots for the timeslots table
782
783
784
785
786
787
    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)
      }
    },
788
789
790
791
792
793
794
795

    // 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.
796
    switchShow: function (index) {
797
798
799
      // 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
800
801
802
803
804
805
806
807
      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
808
      this.loaded.fundingcategory = false
809
      // set the current show and its ID to whatever we want to switch to now
810
      this.currentShow = index
811
      this.currentShowID = this.shows[this.currentShow].id
812
813
      // and check if images are available and set image strings, because we
      // cannot use them directly inside the b-img if they are null
814
815
816
817
      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 }
818
819
820
821
822
823
      // before we load timeslots and notes, we want to fetch the general settings first
      this.getCategories()
      this.getHosts()
      this.getLanguages()
      this.getTopics()
      this.getMusicfocus()
824
      this.getFundingCategory()
825
      this.getType()
826
      // now fetch the timeslots (including notes) for a given show from PV backend
827
      this.getTimeslots(this.dateStart, this.dateEnd, this.numSlots)
828
    },
829
830

    // Fetch timeslots for the current show and use filter variables if provided
831
    getTimeslots: function (start, end, limit, offset) {
832
      var dateRegex = new RegExp('^\\d{4}-\\d{2}-\\d{2}$')
833
      var uri = process.env.VUE_APP_API_STEERING_SHOWS + this.currentShowID + '/timeslots/?'
834
835
836
837
      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) }
838
839
      this.loaded.timeslots = false
      this.loaded.notes = false
840
841
842
843
      axios.get(uri, {
        withCredentials: true,
        headers: { 'Authorization': 'Bearer ' + this.$parent.user.access_token }
      }).then(response => {
844
845
        // if we use the limit argument results are paginated and look different
        // than without the limit argument
846
        if (!isNaN(parseInt(limit))) {
847
848
849
850
          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
851
          this.current.timeslotmeta.perpage = parseInt(limit)
852
        } else {
853
854
855
856
          this.current.timeslots = response.data
          this.current.timeslotmeta.count = response.data.length
          this.current.timeslotmeta.next = null
          this.current.timeslotmeta.previous = null
857
          this.current.timeslotmeta.perpage = response.data.length
858
        }
859
        this.loaded.timeslots = true
860
        // now that we have the timeslots we can fetch notes for all those timeslots
861
        uri = process.env.VUE_APP_API_STEERING_SHOWS + this.currentShowID + '/notes/?ids='
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
        // 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 = []
        }
889
      }).catch(error => {
890
891
        console.log(error)
        alert('There was an error fetching timeslots from the server\n' + error)
892
      })
893
894
      // done fetching timeslots
    },
895
896

    // Open the modal to edit a timeslot's note, given its ID and schedule ID
897
898
    editTimeslotNote: function (timeslotID, scheduleID) {
      this.current.note = null
899
900
901
      for (var i in this.current.notes) {
        if (this.current.notes[i].timeslot === timeslotID) {
          this.current.note = this.current.notes[i]
902
903
904
          break
        }
      }
905
      this.$refs.appModalNotes.showModal(this.current.note, timeslotID, scheduleID)
906
    },
907
908

    // For a given timeslot ID return the corresponding note, if there is one
909
910
911
912
    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]
913
914
915
916
        }
      }
      return null
    },
917
918

    // For a given timeslot ID, check if there is a note and return it
919
    prettyTimeslotNote: function (timeslotID) {
920
      var note = this.getTimeslotNoteTitle(timeslotID)
921
922
923
924
925
926
      if (note !== null) {
        return this.prettyTitle(note)
      } else {
        return ''
      }
    },
927
928

    // Limiting display of strings up to 25 characters plus "..."
929
    prettyTitle: function (title) {
930
931
932
      if (title === '') { return '...' }
      else if (title.length > 25) { return title.slice(0, 25) + '...' }
      else { return title }
933
    },
934
935
936
937

    // 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?
938
939
940
941
942
943
944
    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) {
945
          axios.get(process.env.VUE_APP_API_STEERING + 'categories/' + this.shows[this.currentShow].category[i] + '/', {
946
947
948
            withCredentials: true,
            headers: { 'Authorization': 'Bearer ' + this.$parent.user.access_token }
          }).then(response => {
949
950
951
952
953
954
            this.current.categories.push(response.data)
          }).catch(error => {
            loadingError = true
            alert('There was an error fetching categories from the server: ' + error)
          })
        }
955
        if (!loadingError) { this.loaded.categories = true }
956
957
      }
    },
958

959
960
961
962
963
964
965
    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) {
966
          axios.get(process.env.VUE_APP_API_STEERING + 'hosts/' + this.shows[this.currentShow].hosts[i] + '/', {
967
968
969
            withCredentials: true,
            headers: { 'Authorization': 'Bearer ' + this.$parent.user.access_token }
          }).then(response => {
970
971
972
973
974
975
            this.current.hosts.push(response.data)
          }).catch(error => {
            loadingError = true
            alert('There was an error fetching hosts from the server: ' + error)
          })
        }
976
        if (!loadingError) { this.loaded.hosts = true }
977
978
      }
    },
979

980
981
982
983
    getLanguages: function () {
      this.current.languages = []
      var loadingError = false
      if (this.shows[this.currentShow].language.length === 0) {
984
        this.loaded.languages = true
985
986
      } else {
        for (var i in this.shows[this.currentShow].language) {
987
          axios.get(process.env.VUE_APP_API_STEERING + 'languages/' + this.shows[this.currentShow].language[i] + '/', {
988
989
990
            withCredentials: true,
            headers: { 'Authorization': 'Bearer ' + this.$parent.user.access_token }
          }).then(response => {
991
992
993
994
995
996
            this.current.languages.push(response.data)
          }).catch(error => {
            loadingError = true
            alert('There was an error fetching languages from the server: ' + error)
          })
        }
997
        if (!loadingError) { this.loaded.languages = true }
998
999
      }
    },
1000