From 5d5ac2ce1c7a003e5b38930aa662f976c46cc095 Mon Sep 17 00:00:00 2001 From: PMKuipers Date: Thu, 27 Jul 2023 15:07:14 +0200 Subject: [PATCH] New grid style and rudimentary page support added to report --- amd/src/report-viewer-components.js | 287 +++++++++++++++++-------- amd/src/studyplan-editor-components.js | 5 +- classes/studyplanpage.php | 6 +- css/devstyles.css | 165 ++++++-------- 4 files changed, 264 insertions(+), 199 deletions(-) diff --git a/amd/src/report-viewer-components.js b/amd/src/report-viewer-components.js index 22a9701..8741a39 100644 --- a/amd/src/report-viewer-components.js +++ b/amd/src/report-viewer-components.js @@ -1,5 +1,7 @@ /*eslint no-var: "error"*/ /*eslint no-console: "off"*/ +/*eslint no-unused-vars: warn */ +/*eslint max-len: ["error", { "code": 160 }] */ /*eslint-disable no-trailing-spaces */ /*eslint-env es6*/ // Put this file in path/to/plugin/amd/src @@ -94,6 +96,9 @@ export default { return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); } + // Create new eventbus for interaction between item components + const ItemEventBus = new Vue(); + Vue.component('r-progress-circle',{ props: { value: { @@ -238,108 +243,192 @@ export default { return { }; }, - updated(){ - this.$root.$emit('redrawLines'); - - }, - mounted(){ - this.$root.$emit('redrawLines'); - }, computed: { + columns() { + return 1+ (this.page.periods * 2); + }, + columns_stylerule() { + // Uses css variables, so width for slots and filters can be configured in css + let s = "grid-template-columns: var(--studyplan-filter-width)"; // use css variable here + for(let i=0; i maxLayer){ + maxLayer = item.layer; + } + } + for(const ix in line.slots[i].filters){ + const item = line.slots[i].filters[ix]; + if(item.layer > maxLayer){ + maxLayer = item.layer; + } + } + } + return maxLayer+1; + }, }, template: `
- -
+ +
+ +
+
+ + +
`, }); - /* - * R-STUDYLINE + * R-STUDYLINE-HEADER */ - Vue.component('r-studyline', { - props: ['color','name','code', 'slots','sequence','numlines','guestmode','teachermode'], + Vue.component('r-studyline-heading', { + props: { + value : { + type: Object, // Studyline + default: function(){ return {};}, + }, + layers: { + type: Number, + default: 1, + }, + }, 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('lineHeightChange', this.onLineHeightChange); + }, computed: { - + }, methods: { + onLineHeightChange(lineid,layerid,newheight){ + // All layers for this line have the first slot send an update message on layer height change. + // When one of those updates is received, record the height and recalculate the total height of the + // header + if(this.$refs.mainEl && lineid == this.value.id){ + const items = document.querySelectorAll( + `.r-studyline-slot-0[data-studyline='${this.value.id}']`); + + // determine the height of all the lines and add them up. + let heightSum = 0; + items.forEach((el) => { + // getBoundingClientRect() Gets the actual fractional height instead of rounded to integer pixels + const r = el.getBoundingClientRect(); + const height = r.height; + heightSum += height; + }); + + const heightStyle=`${heightSum}px`; + this.$refs.mainEl.style.height = heightStyle; + } + } }, template: ` -
-
-
- {{ code }} +
+
+ {{ value.shortname }}
-
`, }); Vue.component('r-studyline-slot', { props: { + value: { + type: Array, // item to display + default(){ return [];}, + }, type : { type: String, - default: 'competency', + default: 'gradable', }, slotindex : { type: Number, default: 0, }, - lineid : { - type: Number, - default: 0, + line : { + type: Object, + default(){ return null;}, }, - value: { - type: Array, - default(){ return [];}, - }, + layer : { + type: Number, + }, plan: { type: Object, - default(){ return null;} + default(){ return null;}, }, guestmode: { type: Boolean, @@ -350,15 +439,28 @@ export default { default: false, } }, - computed: { - sorted(){ - let copy = [...this.value]; - copy.sort(function(a,b){ - return a.layer - b.layer; - }); - return copy; + mounted() { + const self=this; + if(self.type == "gradable" && self.slotindex == 1){ + self.resizeListener = new ResizeObserver(() => { + if(self.$refs.sizeElement){ + const height = self.$refs.sizeElement.getBoundingClientRect().height; + ItemEventBus.$emit('lineHeightChange', self.line.id, self.layer, height); + } + }).observe(self.$refs.sizeElement); } }, + computed: { + item(){ + for(const ix in this.value){ + const itm = this.value[ix]; + if(itm.layer == this.layer){ + return itm; + } + } + return null; + }, + }, data() { return { }; @@ -367,13 +469,19 @@ export default { }, template: ` -
- -
+
`, }); @@ -405,22 +513,22 @@ export default { methods: { lineColor(){ if(this.teachermode){ - return "#aaa"; + return "var(--gray)"; } else{ switch(this.value.completion){ default: // "incomplete" - return "#777"; + return "var(--gray)"; case "failed": - return "#933"; + return "var(--danger)"; case "progress": - return "#da3"; + return "var(--warning)"; case "completed": - return "#383"; + return "var(--success)"; case "good": - return "#398"; + return "var(--info)"; case "excellent": - return "#36f"; + return "var(--blue)"; } } }, @@ -458,11 +566,6 @@ export default { 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(){ @@ -499,7 +602,6 @@ export default { // Add resize event listener window.addEventListener('resize',this.onWindowResize); - }, beforeDestroy(){ for(let i in this.value.connections.out){ @@ -519,7 +621,6 @@ export default { } // Remove resize event listener window.removeEventListener('resize',this.onWindowResize); - }, beforeUpdate(){ }, diff --git a/amd/src/studyplan-editor-components.js b/amd/src/studyplan-editor-components.js index 937401b..c3822e5 100644 --- a/amd/src/studyplan-editor-components.js +++ b/amd/src/studyplan-editor-components.js @@ -1524,7 +1524,7 @@ export default { // All layers for this line have the first slot send an update message on layer height change. // When one of those updates is received, record the height and recalculate the total height of the // header - if(lineid == this.value.id){ + if(this.$refs.mainEl && lineid == this.value.id){ const items = document.querySelectorAll( `.t-studyline-slot-0[data-studyline='${this.value.id}']`); @@ -1672,7 +1672,8 @@ export default { }, computed: { item(){ - for(const itm of this.value){ + for(const ix in this.value){ + const itm = this.value[ix]; if(itm.layer == this.layer){ return itm; } diff --git a/classes/studyplanpage.php b/classes/studyplanpage.php index 3a48ff0..7d67bba 100644 --- a/classes/studyplanpage.php +++ b/classes/studyplanpage.php @@ -95,7 +95,7 @@ class studyplanpage { public static function editor_structure($value=VALUE_REQUIRED){ return new \external_single_structure([ "id" => new \external_value(PARAM_INT, 'id of studyplan'), - "name" => new \external_value(PARAM_TEXT, 'name of studyplan page'), + "fullname" => new \external_value(PARAM_TEXT, 'name of studyplan page'), "shortname"=> new \external_value(PARAM_TEXT, 'shortname of studyplan page'), "description"=> new \external_value(PARAM_TEXT, 'description of studyplan page'), "periods" => new \external_value(PARAM_INT, 'number of periods in studyplan page'), @@ -110,7 +110,7 @@ class studyplanpage { $model = [ 'id' => $this->r->id, - 'name' => $this->r->name, + 'fullname' => $this->r->fullname, 'shortname' => $this->r->shortname, 'description' => $this->r->description, 'periods' => $this->r->periods, @@ -198,7 +198,7 @@ class studyplanpage { $model = [ 'id' => $this->r->id, - 'fullname' => $this->r->name, + 'fullname' => $this->r->fullname, 'shortname' => $this->r->shortname, 'description' => $this->r->description, 'periods' => $this->r->periods, diff --git a/css/devstyles.css b/css/devstyles.css index fb91a80..dc84d76 100644 --- a/css/devstyles.css +++ b/css/devstyles.css @@ -10,18 +10,23 @@ } -.t-studyplan-content { +.t-studyplan-content, +.r-studyplan-content { display: flex; } -.t-studyplan-headings { + +.t-studyplan-headings, +.r-studyplan-headings { display: block; } -.t-studyplan-wrapper { +.t-studyplan-wrapper, +.r-studyplan-wrapper { display: block; } -.t-studyplan-timeline { +.t-studyplan-timeline, +.r-studyplan-timeline { display: grid; position: relative; /* make sure this grid is the offset for all arrows that are drawn by SimpleLine */ /* grid-template-columns will be set in the style attribute */ @@ -30,27 +35,32 @@ --studyplan-course-width: auto; /* better leave this at auto for now*/ } -.t-studyplan-scrollable { +.t-studyplan-scrollable, +.r-studyplan-scrollable { overflow-x: scroll; scrollbar-color: var(--primary) color-mix(in srgb, var(--primary) 20%, white); scrollbar-width: thin; } -.t-studyplan-scrollable::-webkit-scrollbar { +.t-studyplan-scrollable::-webkit-scrollbar, +.r-studyplan-scrollable::-webkit-scrollbar { width: 8px; } /* Track */ -.t-studyplan-scrollable::-webkit-scrollbar-track { +.t-studyplan-scrollable::-webkit-scrollbar-track, +.r-studyplan-scrollable::-webkit-scrollbar-track { background: color-mix(in srgb, var(--primary) 20%, white); } /* Handle */ -.t-studyplan-scrollable::-webkit-scrollbar-thumb { +.t-studyplan-scrollable::-webkit-scrollbar-thumb, +.r-studyplan-scrollable::-webkit-scrollbar-thumb { background:var(--primary); } -.t-studyplan-column-heading { +.t-studyplan-column-heading, +.r-studyplan-column-heading { color: inherit; /* placeholder */ } @@ -58,7 +68,8 @@ ul.dropdown-menu.show { background-color: white; } -.t-studyline { +.t-studyline, +.r-studyline { display: grid; grid-auto-flow: column; /*border-bottom-style: solid;*/ @@ -70,12 +81,13 @@ ul.dropdown-menu.show { justify-content: start; } - -.t-studyline.t-studyline-heading { +.t-studyline.t-studyline-heading, +.r-studyline.r-studyline-heading { border-right-style: none; } -.t-studyline.end { +.t-studyline.end, +.r-studyline.end { border-right-style: solid; } @@ -124,7 +136,8 @@ ul.dropdown-menu.show { margin-bottom: 1em; } -.t-studyline-title { +.t-studyline-title, +.r-studyline-title { padding-top: 5px; padding-left: 10px; width: 150px; @@ -138,7 +151,8 @@ ul.dropdown-menu.show { } -.t-studyline-title abbr { +.t-studyline-title abbr, +.r-studyline-title abbr { display: inline-block; vertical-align: middle; font-weight: bold; @@ -229,19 +243,26 @@ ul.t-competency-list li { } -.t-studyline-slot { +.t-studyline-slot, +.r-studyline-slot{ width: 130px; } -.t-studyline-slot.t-studyline-slot-0 { +.r-studyline-slot { + min-height: 32px; +} + + +.t-studyline-slot.t-studyline-slot-0, +.r-studyline-slot.r-studyline-slot-0 { width: 75px; } -.t-studyline-slot.t-studyline-slot-0 .t-slot-drop.filter .t-slot-item { +.t-studyline-slot.t-studyline-slot-0 .t-slot-drop.filter .t-slot-item, +.r-studyline-slot.r-studyline-slot-0 .r-item-base { margin-left: 10px; } - .t-slot-drop { min-height: 32px; height: 100%; @@ -318,7 +339,8 @@ ul.t-competency-list li { max-width: 300px; } -.gradable .t-slot-item { +.gradable .t-slot-item, +.gradable .r-slit-item { width: 100%; } @@ -422,29 +444,29 @@ ul.t-toolbox li { } .t-item-junction i { - color: #eebb00; + color: var(--warning); } .t-item-finish i { - color: #009900; + color: var(--success); } .t-item-start i { - color: #009900; + color: var(--success); } .t-item-badge svg { - color: #ddaa00; + color: var(--warning); } .t-slot-drop.type-allowed { - border-color: green; + border-color: var(--success); border-style: dashed; border-width: 1px; } .t-slot-drop.type-allowed.drop-forbidden { - border-color: red; + border-color: var(--danger); } .t-slot-drop.filter .t-item-base { @@ -528,11 +550,7 @@ a.t-item-course-config { width: inherit; } -.r-studyplan-content { - overflow-y: visible; - width: min-content; - position: relative; -} + .r-studyplan-tab, .t-studyplan-tab { @@ -541,19 +559,11 @@ a.t-item-course-config { } -.r-studyline { - width: min-content; - display: grid; - grid-auto-flow: column; - /*border-bottom-style: solid;*/ - border-color: #cccccc; - border-width: 1px; -} - .t-studyline-drag:nth-child(odd) .t-studyline div, .t-studyline-heading.odd, .r-studyline-heading.odd, -.t-studyline-slot.odd{ +.t-studyline-slot.odd, +.r-studyline-slot.odd { background-color: #f0f0f0; } @@ -561,7 +571,7 @@ a.t-item-course-config { .t-studyline-heading.first, .t-studyline-slot.first, .r-studyline-heading.first, -.r-studyline.first { +.r-studyline-slot.first { border-top-style: solid; } @@ -569,12 +579,13 @@ a.t-item-course-config { .t-studyline-heading.last, .t-studyline-slot.last, .r-studyline-heading.last, -.r-studyline.last { +.r-studyline-slot.last { border-bottom-style: solid; } -.t-studyline-slot.rightmost { +.t-studyline-slot.rightmost, +.r-studyline-slot.rightmost { border-right-style: solid; } @@ -588,55 +599,7 @@ a.t-item-course-config { border-color: rgba(0, 0, 0, 0.125); } -.r-studyline-title { - padding-top: 5px; - padding-left: 10px; - width: 130px; - flex-shrink: 0; - white-space: nowrap; - border-color: rgba(0, 0, 0, 0.125); - border-width: 1px; - border-left-style: solid; - border-right-style: solid; - display: flex; - flex-direction: column; - justify-content: center; -} -.r-studyline-title abbr { - display: inline-block; - vertical-align: middle; - font-weight: bold; - font-style: italic; -} - -.r-studyline-slot { - width: 130px; - min-height: 32px; - min-width: 50px; - display: flex; - flex-shrink: 0; - flex-direction: column; - align-content: center; - justify-content: center; -} - -.r-studyline-slot.r-studyline-slot-0 { - width: 75px; -} - -.r-studyline-slot.r-studyline-slot-0 .r-item-base { - margin-left: 10px; -} - - -.r-studyline-slot.competency { - min-width: 100px; -} - -.r-studyline-slot.filter { - min-width: 50px; -} .r-item-base { margin-top: 5px; @@ -646,7 +609,7 @@ a.t-item-course-config { position: relative; } -.competency .r-item-base { +.gradable .r-item-base { width: 100%; } @@ -674,11 +637,11 @@ a.t-item-course-config { } .r-item-start i { - color: #009900; + color: var(--success); } .r-item-badge i { - color: #ddaa00; + color: var(--warning); } .r-badges li { @@ -789,28 +752,28 @@ tr.r-completion-category-header { .r-item-finish.completion-incomplete, .r-item-junction.completion-incomplete { - color: rgb(127, 127, 127); + color: var(--gray); } .r-item-finish.completion-progress, .r-item-junction.completion-progress { - color: rgb(139, 107, 0); + color: var(--warning); } .r-item-finish.completion-completed, .r-item-junction.completion-completed { - color: rgb(0, 126, 0); + color: var(--success); } .r-item-finish.completion-good, .r-item-junction.completion-good { - color: #398; + color: var(--info); } .r-item-finish.completion-excellent, .r-item-junction.completion-excellent { - color: rgb(0, 103, 255); + color: var(--blue); } .r-item-finish.completion-failed, .r-item-junction.completion-failed { - color: #933; + color: var(--danger); } .r-activity-icon {