Commit cc783ec6 authored by Richard Blechinger's avatar Richard Blechinger
Browse files

Start implementing show repetitions

parent 3b244ed6
This diff is collapsed.
......@@ -9,30 +9,31 @@
},
"dependencies": {
"axios": "^0.18.1",
"bootstrap-vue": "^2.15.0",
"dompurify": "^2.0.14",
"bootstrap-vue": "^2.16.0",
"dompurify": "^2.0.12",
"filesize": "^4.2.1",
"jquery": "^3.5.1",
"moment": "^2.24.0",
"oidc-client": "^1.9.1",
"vue": "^2.6.10",
"vue-full-calendar": "^2.7.0",
"vue-router": "^3.1.3",
"vuejs-logger": "^1.5.3",
"vuex": "^3.1.2"
"jquery": "3.4.1",
"moment": "^2.27.0",
"oidc-client": "^1.10.1",
"vue": "^2.6.12",
"vue-full-calendar": "^2.8.0",
"vue-router": "^3.4.3",
"vuejs-logger": "^1.5.4",
"vuex": "^3.5.1"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^3.11.0",
"@vue/cli-plugin-eslint": "^3.11.0",
"@vue/cli-plugin-babel": "^3.12.1",
"@vue/cli-plugin-eslint": "^3.12.1",
"@vue/cli-service": "^4.5.4",
"@vue/devtools": "^5.1.1",
"babel-eslint": "^10.0.3",
"copy-webpack-plugin": "^6.0.4",
"@vue/devtools": "^5.3.3",
"babel-eslint": "^10.1.0",
"copy-webpack-plugin": "^6.0.3",
"eslint": "^5.16.0",
"eslint-plugin-vue": "^5.2.3",
"tailwindcss": "^1.7.6",
"minimist": "^1.2.5",
"vue-template-compiler": "^2.6.10"
"tailwindcss": "^1.7.6",
"vue-cli-plugin-tailwind": "~1.4.2",
"vue-template-compiler": "^2.6.12"
},
"eslintConfig": {
"root": true,
......@@ -55,6 +56,8 @@
},
"postcss": {
"plugins": {
"tailwindcss": {},
"vue-cli-plugin-tailwind/purgecss": {},
"autoprefixer": {}
}
},
......
......@@ -10,8 +10,10 @@
</template>
<script>
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
import header from './components/Header.vue'
import footer from './components/Footer.vue'
......@@ -54,7 +56,7 @@ export default {
}
}
},
mounted () {
created () {
this.$store.dispatch('auth/oidcInit')
},
methods: {
......
@tailwind components;
@tailwind utilities;
......@@ -465,6 +465,8 @@ export default {
// submit a conflict-resolved schedule to steering
resolveSubmit () {
console.log("before", this.resolveData);
// TODO: check why steering retourns undefined and null values here
if (this.resolveData.schedule.add_business_days_only === undefined) { this.resolveData.schedule.add_business_days_only = false }
if (this.resolveData.schedule.add_days_no === null) { this.resolveData.schedule.add_days_no = 0 }
......@@ -472,6 +474,9 @@ export default {
if (this.resolveData.schedule.fallback_id === null) { this.resolveData.schedule.fallback_id = 0 }
if (this.resolveData.schedule.automation_id === null) { this.resolveData.schedule.automation_id = 0 }
if (this.resolveData.schedule.byweekday === undefined) { this.resolveData.schedule.byweekday = 0 }
console.log("after", this.resolveData);
// create the resolved schedule object including solutions
let resolvedSchedule = {
schedule: this.resolveData.schedule,
......
......@@ -78,7 +78,6 @@ export default {
// This switches the UI elements to reflect another show and fetches all
// relevent data from the tank API.
showHasSwitched () {
this.$log.debug('show has switched to', this.selectedShow.name)
this.$store.dispatch('files/fetchFiles', {
slug: this.selectedShow.slug
})
......
......@@ -25,6 +25,8 @@
<!-- When all show data is loaded, here we display all the rest -->
<div v-else>
<show-schedules />
<!-- include the modals to edit show and timeslot entries from the modal compontents -->
<show-timeslots ref="timeslotsComponent" />
......@@ -44,6 +46,7 @@
<script>
import showJumbotron from './shows/Jumbotron.vue'
import showTimeslots from './shows/Timeslots.vue'
import showSchedules from './shows/Schedules.vue'
import showMetaSimpleTypes from './shows/MetaSimpleTypes.vue'
import showMetaArrays from './shows/MetaArrays.vue'
import showMetaOwners from './shows/MetaOwners.vue'
......@@ -57,6 +60,7 @@ export default {
components: {
'show-jumbotron': showJumbotron,
'show-timeslots': showTimeslots,
'show-schedules': showSchedules,
'show-metaArrays': showMetaArrays,
'show-metaSimpleTypes': showMetaSimpleTypes,
'show-metaOwners': showMetaOwners,
......@@ -90,26 +94,38 @@ export default {
// Right after this component is set up, we want to fetch all available shows
// and the arrays for the show meta info from the AuRa steering module.
created () {
this.$store.dispatch('shows/fetchShows', {
callback: () => {
this.$store.dispatch('playlists/fetch', {slug: this.selectedShow.slug})
this.$refs.showSelector.updateInputSelector()
}
})
this.$store.dispatch('shows/fetchMetaArray', {property: 'types', onlyActive: true})
this.$store.dispatch('shows/fetchMetaArray', {property: 'fundingcategories', onlyActive: true})
this.$store.dispatch('shows/fetchMetaArray', {property: 'categories'})
this.$store.dispatch('shows/fetchMetaArray', {property: 'topics'})
this.$store.dispatch('shows/fetchMetaArray', {property: 'musicfocus'})
this.$store.dispatch('shows/fetchMetaArray', {property: 'languages'})
this.$store.dispatch('shows/fetchMetaArray', {property: 'hosts'})
if (this.isSuperuser) { this.$store.dispatch('auth/fetchUsers') }
if (this.$store.state.auth.user.steeringUser) { this.loadShowInfos() }
else {
this.$store.watch(
(state) => state.auth.user.steeringUser,
() => {
this.loadShowInfos()
}
)
}
},
methods: {
showHasSwitched () {
this.$refs.timeslotsComponent.showHasSwitched()
},
loadShowInfos () {
this.$store.dispatch('shows/fetchShows', {
callback: () => {
this.$store.dispatch('playlists/fetch', {slug: this.selectedShow.slug})
this.$refs.showSelector.updateInputSelector()
}
})
this.$store.dispatch('shows/fetchMetaArray', {property: 'types', onlyActive: true})
this.$store.dispatch('shows/fetchMetaArray', {property: 'fundingcategories', onlyActive: true})
this.$store.dispatch('shows/fetchMetaArray', {property: 'categories'})
this.$store.dispatch('shows/fetchMetaArray', {property: 'topics'})
this.$store.dispatch('shows/fetchMetaArray', {property: 'musicfocus'})
this.$store.dispatch('shows/fetchMetaArray', {property: 'languages'})
this.$store.dispatch('shows/fetchMetaArray', {property: 'hosts'})
if (this.isSuperuser) { this.$store.dispatch('auth/fetchUsers') }
},
}
}
</script>
......
......@@ -98,7 +98,10 @@ export default {
},
computed: {
isSuperuser () { return this.$store.state.auth.user.steeringUser.is_superuser },
isSuperuser () {
const user = this.$store.state.auth.user.steeringUser;
return user && user.is_superuser;
},
loaded () {
return {
shows: this.$store.state.shows.loaded.shows,
......
......@@ -6,7 +6,7 @@
size="lg"
>
<p v-if="loaded.shows">
Editing a timeslot/schedule for show: <b>{{ selectedShow.name }}</b>
Schedule for: <b>{{ selectedShow.name }}</b>
</p>
<div v-if="timeslot && loaded.schedule">
......@@ -66,39 +66,107 @@
</li>
</ul>
</div>
</div>
<div v-else>
<img
src="../../assets/radio.gif"
alt="loading schedule data"
>
</div>
<div v-if="loaded.scheduleTimeslots">
<p>What do you want to do?</p>
<div align="center">
<b-button-group>
<b-button
variant="danger"
size="sm"
@click="deleteFullSchedule(schedule.id)"
>
<span v-if="scheduleTimeslots.length === 1">Delete</span>
<span v-else-if="schedule.rrule === 1">Delete both</span>
<span v-else>Delete schedule + all timeslots</span>
</b-button>
<b-button
v-if="schedule.rrule > 1 && scheduleTimeslots.length > 1"
variant="danger"
size="sm"
@click="deleteSingleTimeslot(schedule.id, timeslot.id)"
>
Delete only this timeslot
</b-button>
<b-button
v-if="schedule.rrule > 1 && scheduleTimeslots.length > 1"
variant="danger"
size="sm"
@click="deleteAllFutureTimeslots(schedule.id, timeslot.id)"
>
Delete this + all future timeslots
</b-button>
</b-button-group>
<hr>
<h4>Add repetition of schedule</h4>
<b-row>
<b-col cols="6">
<label class="tw-leading-loose">
When to repeat?
<b-form-select
v-model="repetitionRule"
:options="repetitionOptions"
>
</b-form-select>
</label>
<b-checkbox v-model="useSameTime">
Use same start and end time
</b-checkbox>
</b-col>
<b-col cols="6" v-if="!useSameTime">
<label class="tw-leading-loose">
Repeat at
<b-form-input type="time" />
</label>
</b-col>
</b-row>
<b-row
v-if="repetitionRule === 3"
class="tw-mt-4"
>
<b-col cols="6">
<label class="tw-leading-loose">
How many days later?
<b-form-input
v-model="addNoOfDays"
type="number"
/>
</label>
<b-checkbox v-model="onlyBusinessDays">
Only count business days
</b-checkbox>
</b-col>
</b-row>
<b-row class="mt-4">
<b-col>
<b-button
variant="primary"
size="sm"
@click="createRepetitionSchedule"
>
Create repetition schedule
</b-button>
</b-col>
</b-row>
<template v-slot:modal-footer>
<div
v-if="loaded.scheduleTimeslots"
class="tw-w-full tw-flex tw-justify-between tw-items-center"
>
<div class="tw-space-x-2">
<b-button
variant="danger"
size="sm"
@click="deleteFullSchedule(schedule.id)"
>
<span v-if="scheduleTimeslots.length === 1">Delete</span>
<span v-else-if="schedule.rrule === 1">Delete both</span>
<span v-else>Delete schedule and all timeslots</span>
</b-button>
<b-button
v-if="schedule.rrule > 1 && scheduleTimeslots.length > 1"
variant="danger"
size="sm"
@click="deleteSingleTimeslot(schedule.id, timeslot.id)"
>
Delete this timeslot
</b-button>
<b-button
v-if="schedule.rrule > 1 && scheduleTimeslots.length > 1"
variant="danger"
size="sm"
@click="deleteAllFutureTimeslots(schedule.id, timeslot.id)"
>
Delete all timeslots
</b-button>
</div>
</div>
<div v-else>
......@@ -107,13 +175,7 @@
alt="loading timeslot data"
>
</div>
</div>
<div v-else>
<img
src="../../assets/radio.gif"
alt="loading schedule data"
>
</div>
</template>
</b-modal>
<b-modal
......@@ -125,7 +187,7 @@
no-close-on-esc
no-close-on-backdrop
>
<div align="center">
<div>
<img
src="../../assets/radio.gif"
alt="loading schedule data"
......@@ -155,7 +217,17 @@ export default {
deletion: {
amount: 0,
count: 0,
}
},
useSameTime: true,
onlyBusinessDays: false,
addNoOfDays: 1,
repetitionRule: 1,
repetitionOptions: [
{ value: 1, text: "On the following day" },
{ value: 2, text: "On the following business day" },
{ value: 3, text: "Number of days later" },
{ value: 4, text: "Custom (WIP)" },
]
}
},
......@@ -176,12 +248,49 @@ export default {
},
methods: {
createRepetitionSchedule() {
const { onlyBusinessDays, addNoOfDays } = this.getRepetitionParameters();
const { dstart, tstart, tend, rrule, until, fallback_id, automation_id, byweekday } = this.schedule;
const newSchedule = {
schedule: {
dstart,
tstart,
tend,
rrule,
until,
fallback_id,
automation_id,
byweekday,
is_repetition: true,
add_business_days_only: onlyBusinessDays,
add_days_no: addNoOfDays,
}
};
console.log(newSchedule);
this.$store.dispatch('shows/submitSchedule', {
showId: this.selectedShow.id,
schedule: newSchedule,
callback: (response) => {
if (response.data.projected === undefined) {
this.$parent.renderView(null)
} else {
this.$log.debug('Timeslot conflict. Switching to resolve mode.');
this.$parent.resolve(response.data)
}
this.$refs.modalEmissionManagerEdit.hide()
},
});
},
deleteFullSchedule (id) {
this.$store.dispatch('shows/deleteSchedule', {
show: this.selectedShow.id,
schedule: id,
callback: () => {
this.$refs.modalEmissionManagerEdit.hide()
this.$refs.modalEmissionManagerEdit.hide();
this.$parent.renderView(null)
}
})
......@@ -257,6 +366,29 @@ export default {
})
},
getRepetitionParameters() {
console.log(typeof this.repetitionRule);
if (this.repetitionRule == 1) {
return {
onlyBusinessDays: false,
addNoOfDays: 1,
}
}
if (this.repetitionRule == 2) {
return {
onlyBusinessDays: true,
addNoOfDays: 1,
}
}
return {
onlyBusinessDays: this.onlyBusinessDays,
addNoOfDays: this.addNoOfDays,
}
},
// initialise a new schedule and open the modal
open (timeslot) {
this.timeslot = timeslot
......
......@@ -31,7 +31,10 @@
<hr>
<p>Playlist entries:</p>
<p class="d-flex justify-content-between">
<span class="font-weight-bold">Playlist entries</span>
<span>Duration: {{ playlistDuration }}</span>
</p>
<!-- If no entries are here (i.e. we add a new playlist), only show
a hint that there's nothing here yet. -->
......@@ -221,6 +224,15 @@ export default {
}
},
playlistDuration() {
const totalDuration = this.playlistEditor.entries.reduce((acc, entry) => {
const file = this.getFileById(entry.file.id)
return acc + file.duration;
}, 0);
return this.prettyNanoseconds(totalDuration)
},
...mapGetters({
selectedShow: 'shows/selectedShow',
getPlaylistById: 'playlists/getPlaylistById',
......
......@@ -68,6 +68,13 @@
</b-button>
</template>
<!-- Column: Duration
This column displays the duration of all playlist entries combined
-->
<template v-slot:cell(duration)="data">
{{ playlistDuration(data) }}
</template>
<!-- Column: Last edit
This column lists the last date this playlist was changed.
-->
......@@ -128,6 +135,7 @@ export default {
{ key: 'id', label: 'Index' },
{ key: 'description', label: 'Description' },
{ key: 'entries', label: 'Entries' },
{ key: 'duration', label: 'Duration' },
{ key: 'updated', label: 'Last edit' },
{ key: 'actions', label: 'Actions', class: 'text-right' },
],
......@@ -179,6 +187,12 @@ export default {
editPlaylist (id) {
this.$refs.editPlaylistsModal.editPlaylist(id)
},
playlistDuration({ item }) {
const totalDuration = item.entries.reduce((acc, entry) => acc + entry.duration, 0)
return this.prettyNanoseconds(totalDuration)
}
},
}
</script>
<template>
<b-modal
ref="modalFallbackSelector"
title="Choose a fallback playlist for this show"
size="lg"
>
<p v-if="loaded">
Currently chosen playlist ID:
<span v-if="selectedShow.fallback_id === null">
<i><small>(none set)</small></i>
</span>
<span v-else>
{{ selectedShow.fallback_id }}
<span v-if="currentPlaylistDescription">
, Description: <b>{{ currentPlaylistDescription }}</b>
</span>
</span>
</p>
<p>Available playlists:</p>
<div v-if="loaded">
<b-table
ref="playlistsTable"
striped
:fields="playlistsTableFields"
:items="playlists"
>
<!-- Column: Entries
This column displays the number of entries of the playlist.
-->
<template v-slot:cell(entries)="data">
{{ data.value.length }} items
<b-button
v-b-tooltip.html="playlistToolTip(data.value)"
variant="outline-success"
size="sm"
>
show entries
</b-button>
</template>
<!-- Column: Actions
This column displays the available buttons for actions the user can
take on this playlist (e.g. editing and deleting).
-->
<template v-slot:cell(actions)="data">
<b-button-group size="sm">
<b-button
v-if="data.item.id !== selectedShow.fallback_id"
variant="info"
@click="choose(data.item.id)"
>
Take it!
</b-button>
<b-button
v-else
variant="danger"
@click="choose(null)"
>
Unset
</b-button>
</b-button-group>
</template>
</b-table>
</div>
<div v-else>
<img