Reworked timeline grid (stage 1)
This commit is contained in:
parent
f8896ddfdb
commit
340b0eef6b
4 changed files with 147 additions and 126 deletions
|
@ -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
|
||||
|
@ -1131,6 +1133,26 @@ export default {
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
countLineLayers(line){
|
||||
let maxLayer = -1;
|
||||
for(let i = 0; i <= this.value.slots; i++){
|
||||
const slot = line.slots[i];
|
||||
// Determine the amount of used layers in a studyline slit
|
||||
for(const ix in line.slots[i].competencies){
|
||||
const item = line.slots[i].competencies[ix];
|
||||
if(item.layer > 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;
|
||||
},
|
||||
slotsempty(slots) {
|
||||
if(Array.isArray(slots)){
|
||||
let count = 0;
|
||||
|
@ -1353,34 +1375,38 @@ export default {
|
|||
<!-- Line by line add the items -->
|
||||
<!-- The grid layout handles putting it in rows and columns -->
|
||||
<template v-for="(line,lineindex) in value.studylines"
|
||||
><template v-for="layeridx in countLineLayers(line)+1"
|
||||
><template v-for="(n,index) in (value.slots+1)"
|
||||
><t-studyline-slot
|
||||
>
|
||||
<t-studyline-slot
|
||||
v-if="index > 0"
|
||||
type='gradable'
|
||||
v-model="line.slots[index].competencies"
|
||||
:key="'c-'+lineindex+'-'+index"
|
||||
:key="'c-'+lineindex+'-'+index+'-'+layeridx"
|
||||
:slotindex="index"
|
||||
:line="line"
|
||||
:plan="value"
|
||||
:class= "'t-studyline ' + (line.sequence==1?' first':'') +
|
||||
:layer="layeridx-1"
|
||||
:class= "'t-studyline ' + ((line.sequence==1 && layeridx==1)?' first':'') +
|
||||
(line.sequence==value.studylines.length?' last':'')"
|
||||
>
|
||||
</t-studyline-slot
|
||||
></t-studyline-slot
|
||||
><t-studyline-slot
|
||||
type='filter'
|
||||
v-model="line.slots[index].filters"
|
||||
:key="'f-'+lineindex+'-'+index"
|
||||
:key="'f-'+lineindex+'-'+index+'-'+layeridx"
|
||||
:slotindex="index"
|
||||
:line="line"
|
||||
:plan="value"
|
||||
:layer="layeridx-1"
|
||||
:class=" 't-studyline '
|
||||
+ (line.sequence==1?' first':'')
|
||||
+ ((line.sequence==1 && layeridx==1)?' first':'')
|
||||
+ (line.sequence==value.studylines.length?' last':'')
|
||||
+ (index==value.slots?' end':'')"
|
||||
>
|
||||
</t-studyline-slot
|
||||
></template
|
||||
></template
|
||||
></template
|
||||
></div><div :id="'studyplan-linewrapper-'+value.id" class='l-leaderline-linewrapper'></div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1594,6 +1620,11 @@ export default {
|
|||
`,
|
||||
});
|
||||
|
||||
/*
|
||||
* During a redisign it was decided to have the studyline still get the entire array as a value,
|
||||
* even though it only shows one drop slot for the layer it is in. This is to make repainting easier,
|
||||
* since we modify the array for the layer we handle
|
||||
*/
|
||||
Vue.component('t-studyline-slot', {
|
||||
props: {
|
||||
type : {
|
||||
|
@ -1608,8 +1639,11 @@ export default {
|
|||
type: Object,
|
||||
default(){ return null;},
|
||||
},
|
||||
layer : {
|
||||
type: Number,
|
||||
},
|
||||
value: {
|
||||
type: Array,
|
||||
type: Array, // dict with layer as index
|
||||
default(){ return [];},
|
||||
},
|
||||
plan: {
|
||||
|
@ -1640,20 +1674,35 @@ export default {
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
item(){
|
||||
for(const itm of this.value){
|
||||
if(itm.layer == this.layer){
|
||||
return itm;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
listtype() {
|
||||
return this.type;
|
||||
},
|
||||
dragacceptlist(){
|
||||
if(this.type == "gradable"){
|
||||
return ["course","competency","gradable-item"];
|
||||
return ["course", "gradable-item"];
|
||||
} else {
|
||||
return ["filter", "filter-item"];
|
||||
}
|
||||
},
|
||||
courseHoverDummy(){
|
||||
return {course: this.hover.component};
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
resizeListener: null,
|
||||
hover: {
|
||||
component:null,
|
||||
type: null,
|
||||
}
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
|
@ -1666,51 +1715,34 @@ export default {
|
|||
},
|
||||
dragacceptcomponent(){
|
||||
if(this.type == "gradable"){
|
||||
return ["course","competency",];
|
||||
return ["course",];
|
||||
} else {
|
||||
return ["filter",];
|
||||
}
|
||||
},
|
||||
onInsert(event) {
|
||||
onDrop(event) {
|
||||
this.hover.component = null;
|
||||
this.hover.type = null;
|
||||
|
||||
const self = this;
|
||||
if(self.dragacceptitem().includes(event.type)) {
|
||||
let item = event.data;
|
||||
self.value.splice( event.index,0, item);
|
||||
self.afterReorder(self.value).done(function(){
|
||||
// Perform layer update - set this slot and layer here
|
||||
self.relocateStudyItem(item).done(()=>{
|
||||
item.layer = this.layer;
|
||||
item.slot = this.slotindex;
|
||||
self.value.push(item);
|
||||
self.$emit("input",self.value);
|
||||
});
|
||||
}
|
||||
else if(self.dragacceptcomponent().includes(event.type) ){
|
||||
if(event.type == "competency"){
|
||||
call([{
|
||||
methodname: 'local_treestudyplan_add_studyitem',
|
||||
args: {
|
||||
"line_id": self.line.id,
|
||||
"slot" : self.slotindex,
|
||||
"type": 'competency',
|
||||
"details": {
|
||||
"competency_id": event.data.id,
|
||||
'conditions':'',
|
||||
'course_id':null,
|
||||
'badge_id':null,
|
||||
'continuation_id':null,
|
||||
}
|
||||
}
|
||||
}])[0].done((response) => {
|
||||
console.info('Add item response:', response);
|
||||
let item = response;
|
||||
self.value.splice(event.index, 0, item);
|
||||
self.afterReorder(self.value).done(function () {
|
||||
self.$emit("input", self.value);
|
||||
});
|
||||
}).fail(notification.exception);
|
||||
}
|
||||
else if(event.type == "course"){
|
||||
if(event.type == "course"){
|
||||
call([{
|
||||
methodname: 'local_treestudyplan_add_studyitem',
|
||||
args: {
|
||||
"line_id": self.line.id,
|
||||
"slot" : self.slotindex,
|
||||
"layer" : self.layer,
|
||||
"type": 'course',
|
||||
"details": {
|
||||
"competency_id": null,
|
||||
|
@ -1723,8 +1755,8 @@ export default {
|
|||
}])[0].done((response) => {
|
||||
console.info('Add item response:', response);
|
||||
let item = response;
|
||||
self.value.splice(event.index, 0, item);
|
||||
self.afterReorder(self.value).done(function () {
|
||||
self.relocateStudyItem(item).done(()=>{
|
||||
self.value.push(item);
|
||||
self.$emit("input",self.value);
|
||||
});
|
||||
}).fail(notification.exception);
|
||||
|
@ -1743,8 +1775,8 @@ export default {
|
|||
}])[0].done((response) => {
|
||||
console.info('Add item response:', response);
|
||||
let item = response;
|
||||
self.value.splice(event.index, 0, item);
|
||||
self.afterReorder(self.value).done(function () {
|
||||
self.relocateStudyItem(item).done(()=>{
|
||||
self.value.push(item);
|
||||
self.$emit("input",self.value);
|
||||
});
|
||||
}).fail(notification.exception);
|
||||
|
@ -1754,88 +1786,76 @@ export default {
|
|||
onCut(event) {
|
||||
const self=this;
|
||||
let id = event.data.id;
|
||||
|
||||
for(let i = 0; i < self.value.length; i++){
|
||||
if(self.value[i].id == id){
|
||||
self.value.splice(i, 1); i--;
|
||||
break; // just remove one
|
||||
}
|
||||
}
|
||||
this.afterReorder(self.value);
|
||||
// Do something to signal that this item has been removed
|
||||
this.$emit("input",this.value);
|
||||
},
|
||||
onReorder(event) {
|
||||
const self=this;
|
||||
// apply list first
|
||||
event.apply(self.value);
|
||||
this.afterReorder(self.value);
|
||||
},
|
||||
afterReorder() {
|
||||
const self=this;
|
||||
// send the new order to the server
|
||||
let items = [];
|
||||
for(let idx in self.value)
|
||||
{
|
||||
self.value[idx].layer = idx;
|
||||
items.push({'id': self.value[idx].id,'layer': idx, 'slot': this.slotindex, 'line_id': this.line.id});
|
||||
}
|
||||
relocateStudyItem(item){
|
||||
const iteminfo = {'id': item.id, 'layer': this.layer, 'slot': this.slotindex, 'line_id': this.line.id};
|
||||
return call([{
|
||||
methodname: 'local_treestudyplan_reorder_studyitems',
|
||||
args: { 'items': items }
|
||||
args: { 'items': [iteminfo] } // function was designed to relocate multiple items at once, hence the array
|
||||
}])[0].fail(notification.exception);
|
||||
},
|
||||
feedbackDummy(type,data){
|
||||
let item = {};
|
||||
item[type] = data;
|
||||
return item;
|
||||
}
|
||||
onDragEnter(event){
|
||||
this.hover.component = event.data;
|
||||
this.hover.type = event.type;
|
||||
},
|
||||
onDragLeave(){
|
||||
this.hover.component = null;
|
||||
this.hover.type = null;
|
||||
},
|
||||
|
||||
},
|
||||
template: `
|
||||
<div :class="'t-studyline-slot '+type + ' t-studyline-slot-'+slotindex + ((slotindex==0)?'t-studyline-firstcolumn':'')
|
||||
+ ((line.sequence%2)?' odd':' even') "
|
||||
:data-studyline="line.id" ref="sizeElement"
|
||||
>
|
||||
<drop-list
|
||||
:items="value"
|
||||
:class="'t-slot-droplist '+type"
|
||||
:accepts-type="dragacceptlist"
|
||||
@insert="onInsert"
|
||||
@reorder="onReorder"
|
||||
mode="cut"
|
||||
row
|
||||
>
|
||||
<template v-slot:item="{item}">
|
||||
<drag
|
||||
><drag v-if="item"
|
||||
|
||||
:key="item.id"
|
||||
class="t-slot-item"
|
||||
:data="item"
|
||||
:type="type+'-item'"
|
||||
@cut="onCut"><t-item v-model="item" :plan="plan"></t-item></drag>
|
||||
</template>
|
||||
<template v-slot:feedback="{data,type}">
|
||||
<div v-if="type == listtype+'-item'"
|
||||
@cut="onCut"
|
||||
><t-item v-model="item" :plan="plan"></t-item
|
||||
></drag
|
||||
><drop v-else
|
||||
:class="'t-slot-droplist '+type"
|
||||
:accepts-type="dragacceptlist"
|
||||
@drop="onDrop"
|
||||
mode="cut"
|
||||
@dragenter="onDragEnter"
|
||||
@dragleave="onDragLeave"
|
||||
><template v-if="hover.component">
|
||||
<div v-if="hover.type == listtype+'-item'"
|
||||
class="t-slot-item feedback"
|
||||
:key="data.id"><t-item v-model="data" dummy></t-item></div>
|
||||
<div v-else-if="type == 'competency'"
|
||||
:key="hover.component.id"
|
||||
><t-item v-model="hover.component" dummy></t-item
|
||||
></div
|
||||
><div v-else-if="hover.type == 'course'"
|
||||
class="t-slot-item feedback"
|
||||
:key="'competency-'+data.idnumber"><t-item-competency v-model="data"></t-item-competency></div>
|
||||
<div v-else-if="type == 'course'"
|
||||
:key="'course-'+hover.component.id"
|
||||
><t-item-course v-model="courseHoverDummy"></t-item-course></div
|
||||
><div v-else-if="hover.type == 'filter'"
|
||||
class="t-slot-item feedback"
|
||||
:key="'course-;'+data.id"><t-item-course v-model="feedbackDummy('course',data)"></t-item-course></div>
|
||||
<div v-else-if="type == 'filter'"
|
||||
key="tooldrop"
|
||||
><t-item-junction v-if="hover.component.type == 'junction'" ></t-item-junction
|
||||
><t-item-start v-else-if="hover.component.type == 'start'" ></t-item-start
|
||||
><t-item-finish v-else-if="hover.component.type == 'finish'" ></t-item-finish
|
||||
><t-item-badge v-else-if="hover.component.type == 'badge'" ></t-item-badge
|
||||
></div
|
||||
><div v-else
|
||||
class="t-slot-item feedback"
|
||||
key="tooldrop">
|
||||
<t-item-junction v-if="data.type == 'junction'" ></t-item-junction>
|
||||
<t-item-start v-else-if="data.type == 'start'" ></t-item-start>
|
||||
<t-item-finish v-else-if="data.type == 'finish'" ></t-item-finish>
|
||||
<t-item-badge v-else-if="data.type == 'badge'" ></t-item-badge>
|
||||
</div>
|
||||
<div v-else
|
||||
class="t-slot-item feedback"
|
||||
:key="type">--{{ type }}--</div>
|
||||
</template>
|
||||
</drop-list>
|
||||
</div>
|
||||
:key="hover.type">--{{ hover.type }}--</div
|
||||
></template
|
||||
></drop
|
||||
></div>
|
||||
`,
|
||||
});
|
||||
|
||||
|
|
|
@ -189,9 +189,8 @@ class studyitem {
|
|||
public static function add($fields,$import=false)
|
||||
{
|
||||
global $DB;
|
||||
$addable = ['line_id','type','conditions','slot','competency_id','course_id','badge_id','continuation_id'];
|
||||
if($import){ $addable[] = "layer";}
|
||||
$info = [ 'layer' => -1, ];
|
||||
$addable = ['line_id','type','layer','conditions','slot','competency_id','course_id','badge_id','continuation_id'];
|
||||
$info = [ 'layer' => 0, ];
|
||||
foreach($addable as $f){
|
||||
if(array_key_exists($f,$fields)){
|
||||
$info[$f] = $fields[$f];
|
||||
|
|
|
@ -417,6 +417,7 @@ class studyplanservice extends \external_api
|
|||
"continuation_id" => new \external_value(PARAM_INT, 'id of continued item',VALUE_OPTIONAL),
|
||||
]),
|
||||
"slot" => new \external_value(PARAM_INT, 'slot in the study plan',VALUE_DEFAULT),
|
||||
"layer" => new \external_value(PARAM_INT, 'layer in the slot',VALUE_OPTIONAL),
|
||||
] );
|
||||
}
|
||||
|
||||
|
@ -425,7 +426,7 @@ class studyplanservice extends \external_api
|
|||
return studyitem::editor_structure();
|
||||
}
|
||||
|
||||
public static function add_studyitem($line_id,$type,$details,$slot=-1)
|
||||
public static function add_studyitem($line_id,$type,$details,$slot=-1,$layer=0)
|
||||
{
|
||||
webservicehelper::require_capabilities(self::CAP_EDIT,studyline::findById($line_id)->context());
|
||||
|
||||
|
@ -434,6 +435,7 @@ class studyplanservice extends \external_api
|
|||
'type' => $type,
|
||||
//'conditions' => $conditions,
|
||||
'slot' => $slot,
|
||||
'layer' => $layer,
|
||||
'competency_id' => isset($details['competency_id'])?$details['competency_id']:null,
|
||||
'course_id' => isset($details['course_id'])?$details['course_id']:null,
|
||||
'badge_id' => isset($details['badge_id'])?$details['badge_id']:null,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
$plugin->component = 'local_treestudyplan'; // Recommended since 2.0.2 (MDL-26035). Required since 3.0 (MDL-48494)
|
||||
$plugin->version = 2023070400; // YYYYMMDDHH (year, month, day, iteration)
|
||||
$plugin->version = 2023071400; // YYYYMMDDHH (year, month, day, iteration)
|
||||
$plugin->requires = 2021051700; // YYYYMMDDHH (This is the release version for Moodle 3.11)
|
||||
|
||||
$plugin->dependencies = [
|
||||
|
|
Loading…
Reference in a new issue