{"version":3,"file":"treestudyplan-components.min.js","sources":["../src/treestudyplan-components.js"],"sourcesContent":["/* eslint no-var: \"error\"*/\n/* eslint no-console: \"off\"*/\n/* eslint camelcase: \"off\" */\n/* eslint-disable no-trailing-spaces */\n/* eslint-env es6*/\n// Put this file in path/to/plugin/amd/src\n\nimport {loadStrings} from './util/string-helper';\nimport {formatDate, studyplanDates, studyplanTiming} from './util/date-helper';\nimport FitTextVue from './util/fittext-vue';\nimport {settings} from \"./util/settings\";\n\nexport default {\n\n install(Vue /* ,options */) {\n Vue.use(FitTextVue);\n\n let strings = loadStrings({\n studyplancard: {\n open: \"open\",\n noenddate: \"noenddate\",\n idnumber: \"studyplan_idnumber\",\n description: \"studyplan_description\",\n completed: \"completed\",\n details: \"studyplan_details\",\n suspended: \"suspended\",\n },\n details: {\n details: \"studyplan_details\",\n },\n extrafields: {\n show: \"show@core\"\n },\n prevnext: {\n prev: \"prev@core\",\n previous: \"previous@core\",\n next: \"next@core\",\n select: \"selectanoptions@core\",\n }\n });\n // Create new eventbus for interaction between item components\n const ItemEventBus = new Vue();\n\n Vue.component('s-studyplan-card', {\n props: {\n value: {\n type: Object,\n },\n open: {\n type: Boolean\n },\n ignoresuspend: {\n type: Boolean,\n 'default': false,\n },\n },\n data() {\n return {\n text: strings.studyplancard\n };\n },\n computed: {\n timeless() {\n const plan = this.value;\n if (!plan.pages || plan.pages.length == 0 || plan.pages[0].timeless) {\n return true;\n } else {\n return false;\n }\n },\n timing() {\n return studyplanTiming(this.value);\n },\n dates() {\n const dates = studyplanDates(this.value);\n return {\n start: formatDate(dates.start),\n end: (dates.end) ? formatDate(dates.end) : this.text.noenddate,\n };\n },\n suspended() {\n return (this.value.suspended && !this.ignoresuspend);\n }\n \n },\n methods: {\n onOpenClick(e) {\n this.$emit('open', e);\n }\n },\n template: `\n \n \n \n
\n
\n
\n
\n \n {{value.name}}\n \n
{{text.suspended}}
\n
\n
\n
\n
\n {{ text.idnumber }}: {{ value.idnumber }}\n
\n \n
\n \n
\n \n \n \n `,\n });\n\n Vue.component('s-progress-bar', {\n props: {\n value: {\n type: Number, \n },\n min: {\n type: Number,\n default() {\n return 0;\n }\n },\n max: {\n type: Number,\n default() {\n return 1;\n }\n }\n },\n data() {\n return {\n text: strings.studyplancard\n };\n },\n computed: {\n width_completed() {\n if (this.value) {\n const v = ((this.value - this.min) / (this.max - this.min));\n return v * 100;\n } else {\n return 0;\n }\n },\n width_incomplete() {\n if (this.value) {\n const v = ((this.value - this.min) / (this.max - this.min));\n return (1 - v) * 100;\n } else {\n return 100;\n }\n },\n percentage_complete() {\n if (this.value) {\n const v = ((this.value - this.min) / (this.max - this.min));\n return Math.round(v * 100) + \"%\";\n } else {\n return \"0%\";\n }\n }\n },\n template: `\n
\n
0\"\n :style=\"{width: width_completed+'%'}\"\n class='s-studyplan-card-progress-segment s-studyplan-card-progress-completed'\n >
\n
\n {{ percentage_complete}} {{ text.completed.toLowerCase() }} \n
\n
\n `,\n });\n\n /*\n * S-STUDYLINE-HEADER-HEADING\n * The only reasing this is not a simple empty div, is the fact that the header height\n * needs to match that of the period headers\n */\n Vue.component('s-studyline-header-heading', {\n props: {\n identifier: {\n type: Number, // Page reference.\n default() {\n return 0;\n }\n }\n },\n data() {\n return {\n layerHeights: {}\n };\n },\n created() {\n // Listener for the signal that a new connection was made and needs to be drawn\n // Sent by the incoming item - By convention, outgoing items are responsible for drawing the lines\n ItemEventBus.$on('headerHeightChange', this.onHeaderHeightChange);\n },\n computed: {\n\n },\n methods: {\n onHeaderHeightChange(newheight, identifier) {\n if (this.identifier == identifier) {\n if (this.$refs.main) {\n this.$refs.main.style.height = `${newheight}px`;\n }\n }\n }\n },\n template: `\n
\n `,\n });\n\n Vue.component('s-studyline-header-period', {\n props: {\n value: {\n type: Object, // Dict with layer as index\n },\n identifier: {\n type: Number, // Page reference.\n default() {\n return 0;\n }\n },\n mode: {\n type: String,\n 'default': \"view\",\n }\n },\n mounted() {\n const self = this;\n if (self.value.period == 1) {\n self.resizeListener = new ResizeObserver(() => {\n if (self.$refs.main) {\n const size = self.$refs.main.getBoundingClientRect();\n ItemEventBus.$emit('headerHeightChange', size.height, self.identifier);\n }\n }).observe(self.$refs.main);\n }\n },\n unmounted() {\n if (this.resizeListener) {\n this.resizeListener.disconnect();\n }\n },\n computed: {\n startdate() {\n return formatDate(this.value.startdate);\n },\n enddate() {\n return formatDate(this.value.enddate);\n },\n current() {\n if (this.value && this.value.startdate && this.value.enddate) {\n const now = new Date();\n const pstart = new Date(this.value.startdate);\n const pend = new Date(this.value.enddate);\n return (now >= pstart && now < pend);\n } else {\n return false;\n }\n }\n\n },\n data() {\n return {\n };\n },\n template: `\n

{{ value.shortname }}\n {{ value.fullname }}
\n \n {{ startdate }} - {{ enddate }}\n \n \n

0\" \n class=\"s-studyline-header-period-datespan small\">\n {{ startdate }}\n - 0\">{{ enddate }}\n

\n
\n
\n `,\n });\n\n\n Vue.component('s-studyplan-details', {\n props: {\n value: {\n type: Object, \n },\n variant: {\n type: String,\n default() {\n return \"info\";\n }\n },\n pill: {\n type: Boolean,\n default() {\n return false;\n }\n },\n size: {\n type: String,\n default() {\n return \"\";\n }\n }\n },\n data() {\n return {\n text: strings.details,\n };\n },\n template: `\n \n \n {{ text.details}}\n \n \n \n \n \n \n \n \n \n \n \n \n `,\n });\n\n Vue.component('s-course-extrafields', {\n props: {\n value: {\n type: Array, \n },\n variant: {\n type: String,\n default() {\n return \"info\";\n }\n },\n position: {\n type: String,\n default() {\n return \"below\";\n }\n },\n size: {\n type: String,\n default() {\n return \"\";\n }\n }\n },\n data() {\n return {\n text: strings.extrafields,\n };\n },\n computed: {\n fields() {\n const fields = [];\n for (const ix in this.value) {\n const field = this.value[ix];\n if (field.position == this.position && field.value && field.value.length > 0) {\n fields.push(field);\n }\n }\n return fields;\n },\n },\n methods: {\n displaydate(field) {\n return formatDate(field.value, false);\n },\n },\n template: `\n
0)?position:'empty')\">\n 0\">\n \n \n \n \n
{{ field.title}}\n {{ displaydate(field) }}\n {{ field.value }}\n \n {{text.show}}...\n \n \n \n
\n
\n `,\n });\n\n Vue.component('s-prevnext-selector', {\n props: {\n value: {\n type: Object, \n default() {\n return null;\n }\n },\n options: {\n type: Array,\n },\n grouped: {\n type: Boolean,\n 'default': false,\n },\n titlefield: {\n type: String,\n 'default': \"title\",\n },\n labelfield: {\n type: String,\n 'default': \"label\",\n },\n optionsfield: {\n type: String,\n 'default': \"options\",\n },\n defaultselectable: {\n type: Boolean,\n 'default': false,\n },\n variant: {\n type: String,\n 'default': \"\",\n },\n arrows: {\n type: Boolean,\n 'default': false,\n },\n },\n data() {\n return {\n showarrows: settings(\"showprevnextarrows\"),\n text: strings.prevnext,\n };\n },\n computed: {\n fields() {\n const f = [];\n if (this.defaultselectable) {\n f.push(null);\n }\n if (this.grouped) {\n for (const gix in this.options) {\n const group = this.options[gix];\n for (const ix in group[this.optionsfield]) {\n const v = group[this.optionsfield][ix];\n f.push(v);\n }\n }\n } else {\n for (const ix in this.options) {\n const v = this.options[ix];\n f.push(v);\n }\n }\n return f;\n },\n bubblevalue: {\n get() {\n return (this.value) ? this.value : null;\n },\n set(v) {\n this.$emit('input', (v) ? v : null);\n },\n }\n },\n methods: {\n atFirst() {\n if (this.fields.length > 0) {\n return this.fields[0] == this.value;\n }\n return true; // Since it disables the button, do so if pressing it would make no sense.\n },\n atLast() {\n if (this.fields.length > 0) {\n const l = this.fields.length - 1;\n return this.fields[l] == this.value;\n }\n return true; // Since it disables the button, do so if pressing it would make no sense.\n },\n prev() {\n if (this.fields.length > 0) {\n const index = this.fields.indexOf(this.value);\n if (index > 0) {\n this.$emit(\"input\", this.fields[index - 1]);\n this.$emit(\"change\", this.fields[index - 1]);\n }\n }\n },\n next() {\n if (this.fields.length > 0) {\n const index = this.fields.indexOf(this.value);\n const l = this.fields.length - 1;\n if (index >= 0 && index < l) {\n this.$emit(\"input\", this.fields[index + 1]);\n this.$emit(\"change\", this.fields[index + 1]);\n }\n }\n },\n selectedchanged(value) {\n this.$emit(\"change\", value);\n }\n },\n template: `\n
\n