/*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 LeaderLine from './leaderline'; import {get_strings} from 'core/str'; import {load_strings} from './string-helper'; import {call} from 'core/ajax'; import notification from 'core/notification'; import {svgarcpath} from './svgarc'; //import {fixLineWrappers} from './studyplan-processor'; // Make π available as a constant const π = Math.PI; export default { install(Vue/*,options*/){ let strings = load_strings({ invalid: { error: 'error', }, grading: { ungraded: "ungraded", graded: "graded", allgraded: "allgraded", unsubmitted: "unsubmitted", nogrades: "nogrades", unknown: "unknown", } }); /************************************ * * * Treestudyplan Viewer components * * * ************************************/ /** * Check if element is visible * @param {Object} elem The element to check * @returns {boolean} True if visible */ function isVisible(elem){ return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); } Vue.component('r-progress-circle',{ props: { value: { type: Number, }, max: { type: Number, default: 100, }, min: { type: Number, default: 0, }, stroke: { type: Number, default: 0.2, }, bgopacity: { type: Number, default: 0.2, }, title: { type: String, default: "", }, }, data() { return { selectedstudyplan: null, }; }, computed: { range() { return this.max - this.min; }, fraction(){ if(this.max - this.min == 0){ return 0; // 0 size is always empty :) } else { return (this.value - this.min)/(this.max - this.min); } }, radius() { return 50 - (50*this.stroke); }, arcpath() { let fraction = 0; const r = 50 - (50*this.stroke); if(this.max - this.min != 0){ fraction = (this.value - this.min)/(this.max - this.min); } const Δ = fraction * 2*π; return svgarcpath([50,50],[r,r],[0,Δ], 1.5*π); }, }, methods: { }, template: ` {{title}} `, }); Vue.component('r-report', { props: { value: { type: Array, }, guestmode: { type: Boolean, default: false, }, teachermode: { type: Boolean, default: false, } }, data() { return { selectedstudyplan: null, }; }, computed: { displayedstudyplan(){ if(this.selectedstudyplan){ return this.selectedstudyplan; } else if(this.value && this.value.length > 0){ return this.value[0]; } else { return null; } } }, methods: { }, template: `
{{studyplan.name}}
`, }); Vue.component('r-studyplan', { props: { value: { type: Object, }, guestmode: { type: Boolean, default: false, }, teachermode: { type: Boolean, default: false, } }, data() { return { }; }, updated(){ this.$root.$emit('redrawLines'); }, mounted(){ this.$root.$emit('redrawLines'); }, computed: { }, methods: { }, template: `
`, }); /* * R-STUDYLINE */ Vue.component('r-studyline', { props: ['color','name','code', 'slots','sequence','numlines','guestmode','teachermode'], data() { return { }; }, computed: { }, methods: { }, template: `
{{ code }}
`, }); Vue.component('r-studyline-slot', { props: { type : { type: String, default: 'competency', }, slotindex : { type: Number, default: 0, }, lineid : { type: Number, default: 0, }, value: { type: Array, default(){ return [];}, }, plan: { type: Object, default(){ return null;} }, guestmode: { type: Boolean, default: false, }, teachermode: { type: Boolean, default: false, } }, computed: { sorted(){ let copy = [...this.value]; copy.sort(function(a,b){ return a.layer - b.layer; }); return copy; } }, data() { return { }; }, methods: { }, template: `
`, }); Vue.component('r-item', { props: { value :{ type: Object, default: function(){ return null;}, }, plan: { type: Object, default(){ return null;} }, guestmode: { type: Boolean, default: false, }, teachermode: { type: Boolean, default: false, } }, data() { return { lines: [], }; }, methods: { lineColor(){ if(this.teachermode){ return "#aaa"; } else{ switch(this.value.completion){ default: // "incomplete" return "#777"; case "failed": return "#933"; case "progress": return "#da3"; case "completed": return "#383"; case "good": return "#398"; case "excellent": return "#36f"; } } }, redrawLine(conn){ let lineColor = this.lineColor(); // prepare lineinfo link or delete old line let lineinfo = this.lines[conn.to_id]; if(lineinfo){ if(lineinfo.line){ if(lineinfo.lineElm ){ lineinfo.lineElm.parentNode.removeChild(lineinfo.lineElm); lineinfo.lineElm = undefined; } else { lineinfo.line.remove(); } lineinfo.line = undefined; } } else { lineinfo = {}; this.lines[conn.to_id] = lineinfo; } // draw new line... let start = document.getElementById('studyitem-'+conn.from_id); let end= document.getElementById('studyitem-'+conn.to_id); LeaderLine.positionByWindowResize = false; if(start !== null && end !== null && isVisible(start) && isVisible(end)){ lineinfo.line = new LeaderLine(start,end,{ color: lineColor, startSocket: 'right', endSocket: 'left', startSocketGravity: 75, endSocketGravity: 75, }); let elmWrapper = (this.plan.id >=0)?document.getElementById('studyplan-linewrapper-'+this.plan.id):null; if(elmWrapper !== null){ let elmLine = document.querySelector('body > .leader-line:last-child'); elmWrapper.appendChild(elmLine); lineinfo.lineElm = elmLine; // store line element so it can more easily be removed from the dom } setTimeout(function(){ if(lineinfo.line){ lineinfo.line.position(); } },1); } }, redrawLines(){ for(let i in this.value.connections.out){ let conn = this.value.connections.out[i]; this.redrawLine(conn); } }, onWindowResize(){ this.redrawLines(); } }, computed: { hasConnectionsOut() { return !(["finish",].includes(this.value.type)); }, hasConnectionsIn() { return !(["start",].includes(this.value.type)); }, hasContext() { return ['start','junction','finish'].includes(this.value.type); } }, created(){ }, mounted(){ // Initialize connection lines when mounting this.redrawLines(); setTimeout(()=>{ this.redrawLines(); },50); // Add resize event listener window.addEventListener('resize',this.onWindowResize); }, beforeDestroy(){ for(let i in this.value.connections.out){ let conn = this.value.connections.out[i]; let lineinfo = this.lines[conn.to_id]; if(lineinfo){ if(lineinfo.line){ if(lineinfo.lineElm ){ lineinfo.lineElm.parentNode.removeChild(lineinfo.lineElm); lineinfo.lineElm = undefined; } else { lineinfo.line.remove(); } lineinfo.line = undefined; } } } // Remove resize event listener window.removeEventListener('resize',this.onWindowResize); }, beforeUpdate(){ }, updated(){ if(!this.dummy) { this.redrawLines(); } }, template: `
`, }); Vue.component('r-item-invalid', { props: { 'value' :{ type: Object, default: function(){ return null;}, }, }, data() { return { text: strings.invalid, }; }, methods: { }, template: ` {{ text.error }} `, }); //TAG: Item Course Vue.component('r-item-course', { props: { value :{ type: Object, default(){ return null;}, }, guestmode: { type: Boolean, default(){ return false;} }, teachermode: { type: Boolean, default(){ return false;} }, plan: { type: Object, default(){ return null;} } }, data() { return { text: { completion_incomplete: "completion_incomplete", completion_failed: "completion_failed", completion_pending: "completion_pending", completion_progress: "completion_progress", completion_completed: "completion_completed", completion_good: "completion_good", completion_excellent: "completion_excellent", view_feedback: "view_feedback", coursetiming_past: "coursetiming_past", coursetiming_present: "coursetiming_present", coursetiming_future: "coursetiming_future", required_goal: "required_goal", }, }; }, computed: { }, created(){ const self = this; // Get text strings for condition settings let stringkeys = []; for(const key in this.text){ stringkeys.push({ key: key, component: 'local_treestudyplan'}); } get_strings(stringkeys).then(function(strings){ let i = 0; for(const key in self.text){ self.text[key] = strings[i]; i++; } }); }, methods: { completion_icon(completion) { switch(completion){ default: // case "incomplete" return "circle-o"; case "pending": return "question-circle"; case "failed": return "times-circle"; case "progress": return "exclamation-circle"; case "completed": return "check-circle"; case "good": return "check-circle"; case "excellent": return "check-circle"; } }, }, template: ` {{ value.course.displayname }} `, }); //TAG: Selected activities dispaly Vue.component('r-item-studentgrades',{ props: { value : { type: Object, default: function(){ return {};}, }, guestmode: { type: Boolean, default: false, }, }, data() { return { }; }, computed: { pendingsubmission(){ let result = false; for(const ix in this.value.course.grades){ const g = this.value.course.grades[ix]; if(g.pendingsubmission){ result = true; break; } } return result; }, useRequiredGrades() { if(this.plan && this.plan.aggregation_info && this.plan.aggregation_info.useRequiredGrades !== undefined){ return this.plan.aggregation_info.useRequiredGrades; } else { return false; } }, }, methods: { completion_icon(completion) { switch(completion){ default: // case "incomplete" return "circle-o"; case "pending": return "question-circle"; case "failed": return "times-circle"; case "progress": return "exclamation-circle"; case "completed": return "check-circle"; case "good": return "check-circle"; case "excellent": return "check-circle"; } }, }, template: `
{{g.name}} {{g.grade}} {{ text["view_feedback"]}}
`, }); //TAG: Core completion version of student course info Vue.component('r-item-studentcompletion',{ props: { value : { type: Object, default: function(){ return {};}, }, guestmode: { type: Boolean, default: false, }, course: { type: Object, default: function(){ return {};}, }, }, data() { return { text: { completion_incomplete: "completion_incomplete", completion_failed: "completion_failed", completion_pending: "completion_pending", completion_progress: "completion_progress", completion_completed: "completion_completed", completion_good: "completion_good", completion_excellent: "completion_excellent", view_feedback: "view_feedback", coursetiming_past: "coursetiming_past", coursetiming_present: "coursetiming_present", coursetiming_future: "coursetiming_future", required_goal: "required_goal", }, }; }, created(){ const self = this; // Get text strings for condition settings let stringkeys = []; for(const key in this.text){ stringkeys.push({ key: key, component: 'local_treestudyplan'}); } get_strings(stringkeys).then(function(strings){ let i = 0; for(const key in self.text){ self.text[key] = strings[i]; i++; } }); }, computed: { }, methods: { completion_icon(completion) { switch(completion){ case "progress": return "exclamation-circle"; case "complete": return "check-circle"; case "complete-pass": return "check-circle"; case "complete-fail": return "times-circle"; default: // case "incomplete" return "circle-o"; } }, completion_tag(cgroup){ return cgroup.completion?'completed':'incomplete'; } }, template: `
`, }); //TODO: Implement corecompletion Vue.component('r-item-teachercourse', { props: { value :{ type: Object, default(){ return null;} }, guestmode: { type: Boolean, default(){ return false;} }, teachermode: { type: Boolean, default(){ return false;} }, plan: { type: Object, default(){ return null;} } }, data() { return { text: { select_conditions: "select_conditions", select_grades: "select_grades", coursetiming_past: "coursetiming_past", coursetiming_present: "coursetiming_present", coursetiming_future: "coursetiming_future", grade_include: "grade_include", grade_require: "grade_require", required_goal: "required_goal", }, txt: { grading: strings.grading, } }; }, computed: { course_grading_needed(){ return this.course_grading_state(); }, course_grading_icon(){ return this.determine_grading_icon(this.course_grading_state()); }, filtered_grades(){ return this.value.course.grades.filter(g => g.selected); }, useRequiredGrades() { if(this.plan && this.plan.aggregation_info && this.plan.aggregation_info.useRequiredGrades !== undefined){ return this.plan.aggregation_info.useRequiredGrades; } else { return false; } }, }, created(){ const self = this; // Get text strings for condition settings let stringkeys = []; for(const key in this.text){ stringkeys.push({ key: key, component: 'local_treestudyplan'}); } get_strings(stringkeys).then(function(strings){ let i = 0; for(const key in self.text){ self.text[key] = strings[i]; i++; } }); }, methods: { course_grading_state(){ let ungraded = 0; let unknown = 0; let graded = 0; let allgraded = 0; const grades = this.filtered_grades; if(!Array.isArray(grades) || grades == 0){ return 'nogrades'; } for(const ix in grades){ const grade = grades[ix]; if(grade.grading){ if(Number(grade.grading.ungraded) > 0){ ungraded++; } else if(Number(grade.grading.graded) > 0) { if(Number(grade.grading.graded) == Number(grade.grading.students)){ allgraded++; } else { graded++; } } } else { unknown = true; } } if(ungraded > 0){ return 'ungraded'; } else if(unknown) { return 'unknown'; } else if(graded){ return 'graded'; } else if(allgraded){ return 'allgraded'; } else { return 'unsubmitted'; } }, determine_grading_icon(gradingstate){ switch(gradingstate){ default: // "nogrades": return "circle-o"; case "ungraded": return "exclamation-circle"; case "unknown": return "question-circle-o"; case "graded": return "check"; case "allgraded": return "check"; case "unsubmitted": return "dot-circle-o"; } }, }, template: ` {{ value.course.displayname }} `, }); //TODO: Selecte activities to use in grade overview Vue.component('r-item-teacher-gradepicker', { props: { value : { type: Object, default: function(){ return {};}, }, useRequiredGrades: { type: Boolean, default(){ return null;} } }, data() { return { }; }, computed: { }, methods: { }, template: ` `, }); //TODO: Selected activities dispaly Vue.component('r-item-teachergrades',{ props: { value : { type: Object, default: function(){ return {};}, }, useRequiredGrades: { type: Boolean, default: false, }, }, data() { return { txt: { grading: strings.grading, } }; }, computed: { pendingsubmission(){ let result = false; for(const ix in this.value.grades){ const g = this.value.grades[ix]; if(g.pendingsubmission){ result = true; break; } } return result; }, filtered_grades(){ return this.value.grades.filter(g => g.selected); }, }, methods: { determine_grading_icon(gradingstate){ switch(gradingstate){ default: // "nogrades": return "circle-o"; case "ungraded": return "exclamation-circle"; case "unknown": return "question-circle-o"; case "graded": return "check"; case "allgraded": return "check"; case "unsubmitted": return "dot-circle-o"; } }, grading_icon(grade){ return this.determine_grading_icon(this.is_grading_needed(grade)); }, is_grading_needed(grade){ if(grade.grading){ if(grade.grading.ungraded){ return 'ungraded'; } else if(grade.grading.graded){ if(Number(grade.grading.graded) == Number(grade.grading.students)){ return 'allgraded'; } else { return 'graded'; } } else { return 'unsubmitted'; } } else { return 'unknown'; } }, includeChanged(newValue,g){ call([{ methodname: 'local_treestudyplan_include_grade', args: { 'grade_id': g.id, 'item_id': this.value.id, 'include': newValue, 'required': g.required, } }])[0].fail(notification.exception); }, requiredChanged(newValue,g){ call([{ methodname: 'local_treestudyplan_include_grade', args: { 'grade_id': g.id, 'item_id': this.value.id, 'include': g.selected, 'required': newValue, } }])[0].fail(notification.exception); }, }, template: `
{{g.name}}
`, }); //TODO: Core completion version of student course info Vue.component('r-item-teachercompletion',{ props: { value : { type: Object, default: function(){ return {};}, }, guestmode: { type: Boolean, default: false, }, course: { type: Object, default: function(){ return {};}, }, }, data() { return { text: { completion_incomplete: "completion_incomplete", completion_failed: "completion_failed", completion_pending: "completion_pending", completion_progress: "completion_progress", completion_completed: "completion_completed", completion_good: "completion_good", completion_excellent: "completion_excellent", view_feedback: "view_feedback", coursetiming_past: "coursetiming_past", coursetiming_present: "coursetiming_present", coursetiming_future: "coursetiming_future", required_goal: "required_goal", }, }; }, created(){ const self = this; // Get text strings for condition settings let stringkeys = []; for(const key in this.text){ stringkeys.push({ key: key, component: 'local_treestudyplan'}); } get_strings(stringkeys).then(function(strings){ let i = 0; for(const key in self.text){ self.text[key] = strings[i]; i++; } }); }, computed: { }, methods: { completion_icon(completion) { switch(completion){ case "progress": return "exclamation-circle"; case "complete": return "check-circle"; case "complete-pass": return "check-circle"; case "complete-fail": return "times-circle"; default: // case "incomplete" return "circle-o"; } }, completion_tag(cgroup){ return cgroup.completion?'completed':'incomplete'; } }, template: `
`, }); Vue.component('r-grading-bar',{ props: { value : { type: Object, default: function(){ return {};}, }, width: { type: Number, default: 150, }, height: { type: Number, default: 15, } }, data() { return { text: strings.grading, }; }, computed: { width_unsubmitted() { return this.width * this.fraction_unsubmitted(); }, width_graded() { return this.width * this.fraction_graded(); }, width_ungraded() { return this.width * this.fraction_ungraded(); }, count_unsubmitted(){ return (this.value.students - this.value.graded - this.value.ungraded); } }, methods: { fraction_unsubmitted() { if(this.value.students > 0){ return 1 - ((this.value.graded + this.value.ungraded) / this.value.students); } else { return 1; } }, fraction_graded() { if(this.value.students > 0){ return this.value.graded / this.value.students; } else { return 0; } }, fraction_ungraded() { if(this.value.students > 0){ return this.value.ungraded / this.value.students; } else { return 0; } }, }, template: ` `, }); Vue.component('r-item-junction',{ props: { value : { type: Object, default: function(){ return {};}, }, guestmode: { type: Boolean, default: false, }, teachermode: { type: Boolean, default: false, } }, data() { return { }; }, computed: { completion(){ if(this.value.completion){ return this.value.completion; } else { return "incomplete"; } } }, methods: { }, template: `
`, }); Vue.component('r-item-finish',{ props: { value : { type: Object, default: function(){ return {};}, }, guestmode: { type: Boolean, default: false, }, teachermode: { type: Boolean, default: false, } }, data() { return { }; }, computed: { completion(){ if(this.value.completion){ return this.value.completion; } else { return "incomplete"; } } }, methods: { }, template: `
`, }); Vue.component('r-item-start',{ props: { value : { type: Object, default: function(){ return {};}, }, guestmode: { type: Boolean, default: false, }, teachermode: { type: Boolean, default: false, } }, data() { return { }; }, computed: { completion(){ if(this.value.completion){ return this.value.completion; } else { return "incomplete"; } } }, created(){ }, methods: { }, template: `
`, }); Vue.component('r-item-badge',{ props: { value : { type: Object, default: function(){ return {};}, }, guestmode: { type: Boolean, default: false, }, teachermode: { type: Boolean, default: false, } }, data() { return { }; }, computed: { completion() { return this.value.badge.issued?"completed":"incomplete"; }, }, methods: { }, template: `
`, }); }, };