Finished implementing frontend CRU for pages

This commit is contained in:
PMKuipers 2023-11-05 22:33:57 +01:00
parent 702435566d
commit a60c259408
12 changed files with 483 additions and 292 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -128,6 +128,15 @@ export default {
required_goal: "required_goal",
student_from_plan_enrolled: "student_from_plan_enrolled",
students_from_plan_enrolled: "students_from_plan_enrolled",
},
pageinfo: {
edit: 'period_edit',
fullname: 'studyplan_name',
shortname: 'studyplan_shortname',
startdate: 'studyplan_startdate',
enddate: 'studyplan_enddate',
description: 'studyplan_description',
duration: 'studyplan_duration'
}
});
@ -303,29 +312,34 @@ export default {
},
data() {
return {
selectedpageindex: 0,
text: strings.pageinfo,
};
},
computed: {
columns() {
return 1+ (this.page.periods * 2);
selectedpage() {
return this.value.pages[this.selectedpageindex];
},
columns_stylerule() {
},
methods: {
pageduration(page){
return format_date(page.startdate,false) + " - " + format_date(page.enddate,false);
},
columns(page) {
return 1+ (page.periods * 2);
},
columns_stylerule(page) {
// 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<this.page.periods;i++){
for(let i=0; i<page.periods;i++){
s+= " var(--studyplan-course-width) var(--studyplan-filter-width)";
}
return s+";";
},
page() {
//FIXME: Replace this when actual page management is implemented
return this.value.pages[0];
},
},
methods: {
countLineLayers(line){
countLineLayers(line,page){
let maxLayer = -1;
for(let i = 0; i <= this.page.periods; i++){
for(let i = 0; i <= page.periods; i++){
const slot = line.slots[i];
// Determine the amount of used layers in a studyline slit
for(const ix in line.slots[i].courses){
@ -343,11 +357,11 @@ export default {
}
return (maxLayer >= 0)?(maxLayer+1):1;
},
showslot(line,index, layeridx, type){
showslot(page,line,index, layeridx, type){
// check if the slot should be hidden because a previous slot has an item with a span
// so big that it hides this slot
const forGradable = (type == 'gradable')?true:false;
const periods = this.page.periods;
const periods = page.periods;
let show = true;
for(let i = 0; i < periods; i++){
if(line.slots[index-i] && line.slots[index-i].courses){
@ -370,6 +384,10 @@ export default {
}
return show;
},
selectedpageChanged(newTabIndex,prevTabIndex) {
const page = this.value.pages[newTabIndex];
scrollCurrentIntoView(this.value.id);
}
},
mounted() {
@ -381,14 +399,62 @@ export default {
},
template: `
<div class='r-studyplan-content'>
<div>
<b-card no-body>
<b-tabs
v-model='selectedpageindex'
@activate-tab='selectedpageChanged'
content-class="mt-1">
<b-tab
v-for="(page,pageindex) in value.pages"
:key="page.id"
:title-item-class="'s-studyplanpage-tab '+ page.timing"
><template #title>
<span v-b-tooltip.hover :title='page.fullname'>{{page.shortname}}</span>
<a href="#" v-b-modal="'studyplanpage-info-'+page.id" variant='info'
v-if='pageindex == selectedpageindex'
><i class='fa fa-info-circle'></i></a>
</template>
<b-modal
:id="'studyplanpage-info-'+page.id"
scrollable
ok-only
>
<template #modal-title>
{{page.fullname}}
</template>
<b-container>
<b-row>
<b-col cols="4"><b>{{ text.shortname}}</b></b-col>
<b-col cols="8">
{{ page.shortname }}
</b-col>
</b-row>
<b-row>
<b-col cols="4"><b>{{ text.duration}}</b></b-col>
<b-col cols="8">
{{ pageduration(page) }}
</b-col>
</b-row>
<b-row v-if="page.description">
<b-col cols="12"><b>{{ text.description}}</b></b-col>
</b-row>
<b-row v-if="page.description">
<b-col cols="12">
<span v-html="page.description"></span>
</b-col>
</b-row>
</b-container>
</b-modal>
<div v-if="page.studylines.length > 0" class='r-studyplan-content'>
<!-- First paint the headings-->
<div class='r-studyplan-headings'
><s-studyline-header-heading></s-studyline-header-heading>
><s-studyline-header-heading :identifier="Number(page.id)"></s-studyline-header-heading>
<r-studyline-heading v-for="(line,lineindex) in page.studylines"
:key="line.id"
v-model="page.studylines[lineindex]"
:layers='countLineLayers(line)+1'
:layers='countLineLayers(page,line)+1'
:class=" 't-studyline' + ((lineindex%2==0)?' odd ' :' even ' )
+ ((lineindex==0)?' first ':' ')
+ ((lineindex==page.studylines.length-1)?' last ':' ')"
@ -396,12 +462,13 @@ export default {
></div>
<!-- Next, paint all the cells in the scrollable -->
<div class="r-studyplan-scrollable" >
<div class="r-studyplan-timeline" :style="columns_stylerule">
<div class="r-studyplan-timeline" :style="columns_stylerule(page)">
<!-- add period information -->
<template v-for="(n,index) in (page.periods+1)">
<s-studyline-header-period
v-if="index > 0"
v-model="page.perioddesc[index-1]"
:identifier="Number(page.id)"
></s-studyline-header-period>
<div class="s-studyline-header-filter"></div>
</template>
@ -409,10 +476,10 @@ export default {
<!-- Line by line add the items -->
<!-- The grid layout handles putting it in rows and columns -->
<template v-for="(line,lineindex) in page.studylines"
><template v-for="(layernr,layeridx) in countLineLayers(line)"
><template v-for="(layernr,layeridx) in countLineLayers(page,line)"
><template v-for="(n,index) in (page.periods+1)"
><r-studyline-slot
v-if="index > 0 && showslot(line, index, layeridx, 'gradable')"
v-if="index > 0 && showslot(page,line, index, layeridx, 'gradable')"
type='gradable'
v-model="line.slots[index].courses"
:key="'c-'+lineindex+'-'+index+'-'+layernr"
@ -429,7 +496,7 @@ export default {
+ ((lineindex==page.studylines.length-1)?' last ':' ')"
></r-studyline-slot
><r-studyline-slot
v-if="showslot(line, index, layeridx, 'gradable')"
v-if="showslot(page,line, index, layeridx, 'gradable')"
type='filter'
v-model="line.slots[index].filters"
:teachermode='teachermode'
@ -451,6 +518,10 @@ export default {
></div
></div
></div>
</b-tab>
</b-tabs>
</b-card>
</div>
`,
});

View File

@ -136,6 +136,8 @@ export default {
studyplan_edit: {
studyplan_edit: 'studyplan_edit',
studyplan_add: 'studyplan_add',
studyplanpage_add: 'studyplanpage_add',
studyplanpage_edit: 'studyplanpage_edit',
},
period_edit: {
edit: 'period_edit',
@ -241,6 +243,7 @@ export default {
type: Object,
default(){ return null;},
},
},
data() {
return {
@ -628,7 +631,7 @@ export default {
@saved="planSaved"
:variant="variant"
:type="type"
:title="(mode == 'create')?text.studyplan_add:text.studyplan_edit"
:title="(mode == 'create')?text.studyplanpage_add:text.studyplanpage_edit"
><slot><i class='fa fa-gear'></i></slot></mform>
</span>
`
@ -1163,6 +1166,15 @@ export default {
cache: {
linelayers: {},
},
selectedpageindex: 0,
emptyline: {
id: -1,
name: '<No study lines defined>',
shortname: '<No study lines>',
color: '#FF0000',
filterslots: [{}],
courseslots: [{}]
}
};
},
created() {
@ -1180,7 +1192,9 @@ export default {
ItemEventBus.$emit('redrawLines');
},
computed: {
selectedpage() {
return this.value.pages[this.selectedpageindex];
}
},
methods: {
columns(page) {
@ -1360,7 +1374,6 @@ export default {
deleteStudyItem(event){
//const self = this;
let item = event.data;
call([{
methodname: 'local_treestudyplan_delete_studyitem',
args: { 'id': item.id, }
@ -1426,6 +1439,15 @@ export default {
},
pagecreated(page) {
this.value.pages.push(page);
},
selectedpageChanged(newTabIndex,prevTabIndex) {
const page = this.value.pages[newTabIndex];
if (page.studylines.length == 0) {
this.edit.studyline.editmode = true;
} else {
this.edit.studyline.editmode = false;
}
}
}
,
@ -1466,7 +1488,11 @@ export default {
</span>
</div>
</div>
<b-tabs content-class="mt-1">
<b-card no-body>
<b-tabs
v-model='selectedpageindex'
@activate-tab='selectedpageChanged'
content-class="mt-1">
<!-- New Tab Button (Using tabs-end slot) -->
<template #tabs-end>
<t-studyplan-page-edit
@ -1484,12 +1510,14 @@ export default {
<template #title>
{{page.shortname}}
<t-studyplan-page-edit
v-if="pageindex == selectedpageindex"
v-model="value.pages[pageindex]"
:studyplan="value"
type="link"
></t-studyplan-page-edit>
</template>
<div class='t-studyplan-content-edit' v-if="edit.studyline.editmode">
<div class='t-studyplan-content-edit'
v-if="edit.studyline.editmode">
<drop-list
:items="page.studylines"
class="t-slot-droplist"
@ -1521,10 +1549,10 @@ export default {
</drop-list>
</div>
<div class='t-studyplan-content' v-else>
<!-- Now paint the headings column -->
<div class='t-studyplan-headings'>
<s-studyline-header-heading :identifier='Number(page.id)'></s-studyline-header-heading>
<template v-if="page.studylines.length > 0">
<t-studyline-heading v-for="(line,lineindex) in page.studylines"
:key="line.id"
@resize="headingresized(lineindex,$event)"
@ -1534,6 +1562,13 @@ export default {
+ ((lineindex==0)?' first ':' ')
+ ((lineindex==page.studylines.length-1)?' last ':' ')"
></t-studyline-heading>
</template>
<t-studyline-heading v-else
@resize="headingresized(0,$event)"
:layers="1"
:class="'odd first last'"
></t-studyline-heading>
</div>
<!-- Next, paint all the cells in the scrollable -->
<div class="t-studyplan-scrollable" >
@ -1594,15 +1629,14 @@ export default {
+ ((index==page.periods)?' rightmost':'')
+ ((layernr == countLineLayers(line,page))?' lastlyr ':' ')
+ ((layernr == countLineLayers(line,page)+1)?' newlyr ':' ')"
>
</t-studyline-slot
></t-studyline-slot
></template
></template
></template
></div>
</div>
</div>
<div v-if="edit.studyline.editmode" class='t-studyline-add'>
<div v-if="edit.studyline.editmode" class='t-studyline-add ml-2 mt-1'>
<a href="#" v-b-modal="'modal-add-studyline-'+page.id" @click="false;"
><i class='fa fa-plus'></i>{{ text.studyline_add }}</a>
</div>
@ -1674,6 +1708,7 @@ export default {
</b-modal>
</b-tab>
</b-tabs>
</b-card>
</div>
`
});
@ -2118,7 +2153,6 @@ export default {
:data-studyline="line.id" ref="main"
:style='spanCss'
><drag v-if="item"
:key="item.id"
class="t-slot-item"
:data="item"

View File

@ -240,7 +240,7 @@ class studyplanpage_editform extends formbase {
]);
}
/* Return the simple model of the page to make sure we can update stuff.
/* Return the editor structure of the new / edited page
Parse it through the clean_returnvalue function of exernal api (of which studyplanservice is a subclass)
so we return it in a consistent way
*/

View File

@ -142,6 +142,40 @@ class studyplanpage {
}
/**
* Return description with all file references resolved
* @return string
*/
public function description() {
$text = file_rewrite_pluginfile_urls(
// The content of the text stored in the database.
$this->r->description,
// The pluginfile URL which will serve the request.
'pluginfile.php',
// The combination of contextid / component / filearea / itemid
// form the virtual bucket that file are stored in.
\context_system::instance()->id, // System instance is always used for this
'local_treestudyplan',
'studyplanpage',
$this->id
);
return $text;
}
public function timing() {
$now = new \DateTime();
if ($now > $this->startdate) {
if ($this->enddate > 0 && $now > $this->enddate) {
return "past";
} else {
return "present";
}
} else {
return "future";
}
}
/**
* Webservice structure for basic info
* @param int $value Webservice requirement constant
@ -155,6 +189,7 @@ class studyplanpage {
"description" => new \external_value(PARAM_RAW, 'description of studyplan page'),
"startdate" => new \external_value(PARAM_TEXT, 'start date of studyplan'),
"enddate" => new \external_value(PARAM_TEXT, 'end date of studyplan'),
"timing" => new \external_value(PARAM_TEXT, '(past|present|future)'),
"perioddesc" => period::page_structure(),
], 'Studyplan page basic info', $value);
}
@ -169,9 +204,10 @@ class studyplanpage {
'fullname' => $this->r->fullname,
'shortname' => $this->r->shortname,
'periods' => $this->r->periods,
'description' => $this->r->description,
'description' => $this->description(),
'startdate' => $this->r->startdate,
'enddate' => $this->r->enddate,
'timing' => $this->timing(),
"perioddesc" => period::page_model($this),
];
}
@ -189,6 +225,7 @@ class studyplanpage {
"periods" => new \external_value(PARAM_INT, 'number of periods in studyplan page'),
"startdate" => new \external_value(PARAM_TEXT, 'start date of studyplan page'),
"enddate" => new \external_value(PARAM_TEXT, 'end date of studyplan page'),
"timing" => new \external_value(PARAM_TEXT, '(past|present|future)'),
"studylines" => new \external_multiple_structure(studyline::editor_structure()),
"perioddesc" => period::page_structure(),
], 'Studyplan page full structure', $value);
@ -205,10 +242,11 @@ class studyplanpage {
'id' => $this->r->id,
'fullname' => $this->r->fullname,
'shortname' => $this->r->shortname,
'description' => $this->r->description,
'description' => $this->description(),
'periods' => $this->r->periods,
'startdate' => $this->r->startdate,
'enddate' => $this->r->enddate,
'timing' => $this->timing(),
'studylines' => [],
"perioddesc" => period::page_model($this),
];
@ -313,6 +351,7 @@ class studyplanpage {
"periods" => new \external_value(PARAM_INT, 'number of slots in studyplan page'),
"startdate" => new \external_value(PARAM_TEXT, 'start date of studyplan page'),
"enddate" => new \external_value(PARAM_TEXT, 'end date of studyplan page'),
"timing" => new \external_value(PARAM_TEXT, '(past|present|future)'),
"studylines" => new \external_multiple_structure(studyline::user_structure()),
"perioddesc" => period::page_structure(),
], 'Studyplan page with user info', $value);
@ -329,10 +368,11 @@ class studyplanpage {
'id' => $this->r->id,
'fullname' => $this->r->fullname,
'shortname' => $this->r->shortname,
'description' => $this->r->description,
'description' => $this->description(),
'periods' => $this->r->periods,
'startdate' => $this->r->startdate,
'enddate' => $this->r->enddate,
'timing' => $this->timing(),
'studylines' => [],
"perioddesc" => period::page_model($this),
];

View File

@ -114,7 +114,13 @@ $string["refreshteacherlist_name"] = "Refresh teacher's study plan list";
$string["studyplan_add"] = 'Add study plan';
$string["studyplan_edit"] = 'Edit study plan';
$string["studyplan_remove"] = 'Remove study plan';
$string["studyplan_confirm_remove"] = 'Are you sure you want to remove study plan {$a}?';
$string["studyplan_confirm_remove"] = 'Are you sure you want to remove study plan page {$a}?';
$string["studyplanpage"] = 'Study plan page';
$string["studyplanpage_add"] = 'Add study plan page';
$string["studyplanpage_edit"] = 'Edit study plan page';
$string["studyplanpage_remove"] = 'Remove study plan page';
$string["studyplanpage_confirm_remove"] = 'Are you sure you want to remove study plan page {$a}?';
$string["studyplan_duration"] = 'Duration';
$string["studyplan_name"] = 'Full name';
$string["studyplan_name_ph"] = '';
$string["studyplan_icon"] = "Image for this studyplan";

View File

@ -112,6 +112,12 @@ $string["studyplan_add"] = 'Nieuw studieplan';
$string["studyplan_edit"] = 'Studieplan bewerken';
$string["studyplan_remove"] = 'Studieplan verwijderen';
$string["studyplan_confirm_remove"] = 'Weet je zeker dat je studieplan {$a} wilt verwijderen?';
$string["studyplanpage"] = 'Studieplan-tabblad';
$string["studyplanpage_add"] = 'Nieuw studieplan-tabblad';
$string["studyplanpage_edit"] = 'Studieplan-tabblad bewerken';
$string["studyplanpage_remove"] = 'Studieplan-tabblad verwijderen';
$string["studyplanpage_confirm_remove"] = 'Weet je zeker dat je studieplan-tabblad {$a} wilt verwijderen?';
$string["studyplan_duration"] = 'Duur';
$string["studyplan_name"] = 'Volledige Naam';
$string["studyplan_name_ph"] = '';
$string["studyplan_icon"] = "Afbeelding for this studyplan";

35
lib.php
View File

@ -25,6 +25,7 @@
use local_treestudyplan\local\helpers\webservicehelper;
use \local_treestudyplan\studyplan;
use local_treestudyplan\studyplanpage;
/**
* Describe editor options
@ -457,6 +458,40 @@ function local_treestudyplan_pluginfile(
return false;
}
} else if (in_array($filearea,["studyplanpage"])) {
// The args is an array containing [itemid, path].
// Fetch the itemid from the path.
$itemid = array_shift($args);
$page = studyplanpage::find_by_id($itemid);
$plan = $page->studyplan();
$planctx = $plan->context();
// Check if the current user has access to this studyplan
if ( webservicehelper::has_capabilities($studyplan_filecaps,$planctx) || $plan->has_linked_user($USER)) {
// Extract the filename / filepath from the $args array
$filename = array_pop($args); // The last item in the $args array.
if (empty($args)) {
// $args is empty => the path is '/'.
$filepath = '/';
} else {
// $args contains the remaining elements of the filepath.
$filepath = '/' . implode('/', $args) . '/';
}
// Retrieve the file from the Files API.
$fs = get_file_storage();
$file = $fs->get_file(\context_system::instance()->id, 'local_treestudyplan', $filearea, $itemid, $filepath, $filename);
if (!$file) {
// The file does not exist.
return false;
}
// We can now send the file back to the browser - in this case with a cache lifetime of 1 day and no filtering.
send_stored_file($file, 24*60*60, 0, $forcedownload, $options);
} else {
return false;
}
} else if (in_array($filearea,['defaulticon'])) {
// The args is an array containing [itemid, path].
// Fetch the itemid from the path.

View File

@ -1117,5 +1117,4 @@
margin-bottom: auto;
margin-left: 0.5em;
}
}