moodle_local_treestudyplan/amd/src/treestudyplan-components.js

403 lines
15 KiB
JavaScript

/*eslint no-var: "error"*/
/*eslint no-console: "off"*/
/*eslint-disable no-trailing-spaces */
/*eslint-env es6*/
// Put this file in path/to/plugin/amd/src
import {load_strings} from './util/string-helper';
import {format_date, studyplanDates, studyplanTiming} from './util/date-helper';
export default {
install(Vue/*,options*/){
let strings = load_strings({
studyplancard: {
open: "open",
noenddate: "noenddate",
idnumber: "studyplan_idnumber",
description: "studyplan_description",
completed: "completed",
details: "studyplan_details",
},
details: {
details: "studyplan_details",
},
extrafields: {
show: "show@core"
}
});
// Create new eventbus for interaction between item components
const ItemEventBus = new Vue();
Vue.component('s-studyplan-card', {
props: {
value: {
type: Object,
},
open: {
type: Boolean
},
},
data() {
return {
text: strings.studyplancard
};
},
computed: {
timing(){
return studyplanTiming(this.value);
},
dates(){
const dates = studyplanDates(this.value);
return {
start: format_date(dates.start),
end: (dates.end)?format_date(dates.end):this.text.noenddate,
};
},
},
methods: {
onOpenClick(e) {
this.$emit('open',e);
}
},
template: `
<b-card
:class="'s-studyplan-card timing-' + timing"
>
<template #header></template>
<div class='s-studyplan-card-content'>
<div class='s-studyplan-card-icon'><img :src='value.icon'></div>
<div class='s-studyplan-card-info'>
<div class='s-studyplan-card-titlebar'>
<b-card-title>
<a class='title' v-if='open' href='#' @click.prevent='onOpenClick($event)'>{{value.name}}</a>
<template v-else>{{value.name}}</template>
</b-card-title>
<div class='s-studyplan-card-titleslot'><slot name='title'></slot></div>
</div>
<div class='s-studyplan-card-idnumber' v-if='value.idnumber'>
{{ text.idnumber }}: {{ value.idnumber }}
</div>
<s-progress-bar
v-if='value.progress !== undefined && value.progress !== null'
v-model="value.progress"
></s-progress-bar>
</div>
</div>
<slot></slot>
<template #footer>
<span :class="'t-timing-'+timing" v-html="dates.start + ' - '+ dates.end"></span>
<span class="s-studyplan-card-buttons">
<slot name='footer'></slot>
<s-studyplan-details
v-model="value"
v-if="value.description"
><i class='fa fa-info-circle'></i></s-studyplan-details>
<b-button style="float:right;" v-if='open' variant='primary'
@click.prevent='onOpenClick($event)'>{{ text.open }}</b-button>
</span>
</template>
</b-card>
`,
});
Vue.component('s-progress-bar', {
props: {
value: {
type: Number,
},
min: {
type: Number,
default() { return 0;}
},
max: {
type: Number,
default() { return 1;}
}
},
data() {
return {
text: strings.studyplancard
};
},
computed: {
width_completed() {
if(this.value) {
const v = ( (this.value - this.min) / (this.max - this.min));
return v * 100;
} else {
return 0;
}
},
width_incomplete() {
if(this.value) {
const v = ( (this.value - this.min) / (this.max - this.min));
return (1-v) * 100;
} else {
return 100;
}
},
percentage_complete() {
if(this.value) {
const v = ( (this.value - this.min) / (this.max - this.min));
return Math.round(v * 100) + "%";
} else {
return "0%";
}
}
},
template: `
<div class='s-studyplan-card-progress' >
<div class="s-studyplan-card-progressbar"
><span v-if="width_completed > 0"
:style="{width: width_completed+'%'}"
class='s-studyplan-card-progress-segment s-studyplan-card-progress-completed'
></span
><span :style="{width: width_incomplete+'%'}"
class='s-studyplan-card-progress-segment s-studyplan-card-progress-incomplete'
></span
></div>
<div class="s-studyplan-card-progresstext">
{{ percentage_complete}} {{ text.completed.toLowerCase() }}
</div>
</div>
`,
});
/*
* S-STUDYLINE-HEADER-HEADING
* The only reasing this is not a simple empty div, is the fact that the header height
* needs to match that of the period headers
*/
Vue.component('s-studyline-header-heading', {
props: {
identifier: {
type: Number, // Page reference.
default() { return 0;}
}
},
data() {
return {
layerHeights: {}
};
},
created() {
// Listener for the signal that a new connection was made and needs to be drawn
// Sent by the incoming item - By convention, outgoing items are responsible for drawing the lines
ItemEventBus.$on('headerHeightChange', this.onHeaderHeightChange);
},
computed: {
},
methods: {
onHeaderHeightChange(newheight,identifier){
if (this.identifier == identifier) {
if(this.$refs.main){
this.$refs.main.style.height = `${newheight}px`;
}
}
}
},
template: `
<div class="s-studyline-header-heading" ref="main" :data-id="identifier"></div>
`,
});
Vue.component('s-studyline-header-period', {
props: {
value: {
type: Object, // dict with layer as index
},
identifier: {
type: Number, // Page reference.
default() { return 0;}
}
},
mounted() {
const self=this;
if(self.value.period == 1){
self.resizeListener = new ResizeObserver(() => {
if(self.$refs.main){
const size = self.$refs.main.getBoundingClientRect();
ItemEventBus.$emit('headerHeightChange', size.height, self.identifier);
}
}).observe(self.$refs.main);
}
},
unmounted() {
if(this.resizeListener) {
this.resizeListener.disconnect();
}
},
computed: {
startdate(){
return format_date(this.value.startdate);
},
enddate(){
return format_date(this.value.enddate);
},
current(){
if( this.value && this.value.startdate && this.value.enddate){
const now = new Date();
const pstart = new Date(this.value.startdate);
const pend = new Date(this.value.enddate);
return (now >= pstart && now < pend);
}
else {
return false;
}
}
},
data() {
return {
};
},
template: `
<div :class="'s-studyline-header-period ' + (current?'current ':' ')" ref="main" :data-id="identifier"
><p><abbr :id="'s-period-'+value.id" :title="value.fullname">{{ value.shortname }}</abbr>
<b-tooltip
:target="'s-period-'+value.id" triggers="hover"
>{{ value.fullname }}<br>
<span class="s-studyline-header-period-datespan">
<span class="date">{{ startdate }}</span> - <span class="date">{{ enddate }}</span>
</span>
</b-tooltip>
<slot></slot
><p class="s-studyline-header-period-datespan small">
<span class="date">{{ startdate }}</span> - <span class="date">{{ enddate }}</span>
</p>
</div>
`,
});
Vue.component('s-studyplan-details', {
props: {
value: {
type: Object,
},
variant: {
type: String,
default() { return "info"; }
},
pill: {
type: Boolean,
default() { return false; }
},
size: {
type: String,
default() { return "";}
}
},
data() {
return {
text: strings.details,
};
},
template: `
<span>
<b-button
:size="size"
:pill="pill"
:variant="variant"
v-b-modal="'modal-description-'+value.id"
><slot><i class='fa fa-info-circle'></i>
{{ text.details}}</slot>
</b-button>
<b-modal
:title="value.name"
scrollable
centered
ok-only
content-class="s-studyplan-details"
:id="'modal-description-'+value.id"
>
<b-container>
<b-row>
<b-col cols="4"><img :src='value.icon'></b-col>
<b-col cols="8" class="pl-1">
<span v-html="value.description"></span>
</b-col>
</b-row>
</b-container>
</b-modal>
</span>
`,
});
Vue.component('s-course-extrafields', {
props: {
value: {
type: Array,
},
variant: {
type: String,
default() { return "info"; }
},
position: {
type: String,
default() { return "below"; }
},
size: {
type: String,
default() { return "";}
}
},
data() {
return {
text: strings.extrafields,
};
},
computed: {
fields() {
const fields = [];
for (const ix in this.value) {
const field = this.value[ix];
if (field.position == this.position && field.value && field.value.length > 0) {
fields.push(field);
}
}
return fields;
},
},
methods: {
displaydate(field) {
return format_date(field.value,false);
},
},
template: `
<div :class="'s-course-extrafields ' + ((fields.length>0)?position:'empty')">
<table v-if="fields.length > 0">
<tr v-for="field in fields">
<td><span class='title' v-if='field.title'>{{ field.title}}</span></td>
<td>
<span v-if='field.type == "date"' class="value date">{{ displaydate(field) }}</span>
<span v-else-if='field.type == "checkbox"'
:class="'value ' + (field.checked?'true':'false')">{{ field.value }}</span>
<span v-else-if='field.type == "textarea"'>
<a class='text-info' href='#' v-b-modal="field.courseid+'_'+field.fieldname">{{text.show}}...</a>
<b-modal
:id="field.courseid+'_'+field.fieldname"
:title="field.title""
scrollable
centered
ok-only
><span v-html="field.value"></span
></b-modal>
</span>
<span v-else class="value"><span v-html="field.value"></span></span>
</td>
</tr>
</table>
</div>
`,
});
}
};