Code style fixes (mostly white-space)

This commit is contained in:
PMKuipers 2023-09-08 12:47:29 +02:00
parent 5eacc6f68c
commit 9f8a87ac83
68 changed files with 720 additions and 709 deletions

View File

@ -34,11 +34,11 @@ module.exports = function(grunt) {
const sass = require('sass');
// Import grunt configuration for moodle base
process.chdir("../.."); // change dir to moodle base
process.chdir("../.."); // change dir to moodle base
require(path.resolve(`./Gruntfile.js`))(grunt); // Run Gruntfile module from moodle base
grunt.registerTask('scssplugin','Compile scss/*.sccs into styles.css and css/devstyles.css', () => {
const devoutput = 'css/devstyles.css';
const prodoutput = 'styles.css';
@ -93,7 +93,7 @@ module.exports = function(grunt) {
// Remove gherkinlint from the startup list, since it exits with an error because of errors in moodle's own code
grunt.moodleEnv.startupTasks.splice(grunt.moodleEnv.startupTasks.indexOf("gherkinlint"),1);
// Add the 'scssplugin' task as a startup task.
grunt.moodleEnv.startupTasks.push('scssplugin');

View File

@ -1,6 +1,6 @@
# Moodle studyplan plugin
Plugin to organize a curriculum into an easy to read graphical respresentation of courses and student progress therein.
Plugin to organize a curriculum into an easy to read graphical respresentation of courses and student progress therein.
The studyplan plugin extends Moodle with the ability to show students and teachers an overview of their curriculum and results therein.
By showing students an easy to read graphical overview of their progress over multiple courses, students are more in control of their own
@ -14,7 +14,7 @@ Install the plugin by adding the zip file manually to the installed plugins in y
### Dynamic Navigation links
The studyplan plugin will automatically add links to the flat navigation bat used in Moodle 3.11 and below
However, Moodle 4.0-4.2 use a new "primary navigation" bar, and does not yet support customizing the primary navigation bar (it is a planned feature though)
Use the following workaround:
You can add custom primary menu items in **Site administration** -> **Appearance** -> **Theme settings**
That page contains an item called Custom menu items". Add the following into that area to custimize it
@ -37,13 +37,13 @@ The recommended roles below give an example of that.
#### Recommended roles
It is recommended to create the following new roles:
- **Studyplan Manager** (*studyplanmanager*)
Context types: *System*, *Category*
- **Studyplan Manager** (*studyplanmanager*)
Context types: *System*, *Category*
*Capabilities*
- **Manage studyplans** (*local/treestudyplan:editstudyplan*)
- **Studyplan Viewer** (*studyplanviewer*)
Context types: *System*, *Category*
*Capabilities*
- **Studyplan Viewer** (*studyplanviewer*)
Context types: *System*, *Category*
*Capabilities*
- **View study plan of others** (*local/treestudyplan:viewuserreports*)
Then assign the role **studyplanmanager** in a specific category context, or the system context to all users who should be able to create and edit studyplans in that specific context
@ -54,7 +54,7 @@ After installing the plugin you can find the main configuration in **Site admini
## Documentation / User manual
After installing the plugin, the detailed user manual can be found under **Site administration** -> **Courses** -> **Studyplans** -> **Studyplan plugin documentation**
Alternatively, you can unpack this ZIP file and open the **doc/index.html** file.
Alternatively, you can unpack this ZIP file and open the **doc/index.html** file.
(Note that viewing the html files directly will not five the optimal experience, since fontawesome tags and bootstrap styles will not be properly rendered)
## Development

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
{"version":3,"file":"cfg-grades.min.js","sources":["../src/cfg-grades.js"],"sourcesContent":["/*eslint no-var: \"error\" */\n/*eslint no-unused-vars: \"off\" */\n/*eslint linebreak-style: \"off\" */\n/*eslint no-trailing-spaces: \"off\" */\n/*eslint-env es6*/\n// Put this file in path/to/plugin/amd/src\n// You can call it anything you like\n\nimport {get_string,get_strings} from 'core/str';\nimport {call} from 'core/ajax';\nimport Debugger from './debugger';\n\nimport {load_strings} from './string-helper';\n\nlet debug = new Debugger(\"treestudyplan-config-grades\");\n\n/*\nlet strings = load_strings({\n studyplan: {\n studyplan_select_placeholder: 'studyplan_select_placeholder',\n },\n});\n*/\n\n/**\n * Initialize grade cfg page\n */\nexport function init() {\n { const\n intRx = /\\d/,\n integerChange = (event) => {\n if ( (event.key.length > 1) || intRx.test(event.key)\n ) { \n return; \n }\n event.preventDefault();\n };\n \n for (let input of document.querySelectorAll( 'input[type=\"number\"][step=\"1\"][min=\"0\"]' )){\n input.addEventListener(\"keydown\", integerChange);\n } \n \n }\n\n { const\n decimal= /^[0-9]*?\\.[0-9]*?$/,\n intRx = /\\d/,\n floatChange = (event) => {\n if ( (event.key.length > 1) || ( (event.key === \".\") && (!event.currentTarget.value.match(decimal)) )\n || intRx.test(event.key)\n ) { \n return; \n }\n event.preventDefault();\n };\n \n for (let input of document.querySelectorAll( 'input[type=\"number\"][min=\"0\"]:not([step])' )){\n input.addEventListener(\"keydown\", floatChange);\n } \n for (let input of document.querySelectorAll( 'input[type=\"text\"].float' )){\n input.addEventListener(\"keydown\", floatChange);\n } \n \n }\n\n\n}\n\n"],"names":["intRx","integerChange","event","key","length","test","preventDefault","document","querySelectorAll","addEventListener","decimal","floatChange","currentTarget","value","match"],"mappings":"kgDA6BQA,MAAQ,KACRC,cAAgB,SAACC,OACTA,MAAMC,IAAIC,OAAS,GAAMJ,MAAMK,KAAKH,MAAMC,MAIhDD,MAAMI,uDAGQC,SAASC,iBAAkB,+FAA4C,aACjFC,iBAAiB,UAAWR,6EAMlCS,QAAS,qBACTV,OAAQ,KACRW,YAAc,SAACT,OACPA,MAAMC,IAAIC,OAAS,GAAuB,MAAdF,MAAMC,MAAkBD,MAAMU,cAAcC,MAAMC,MAAMJ,UACjFV,OAAMK,KAAKH,MAAMC,MAI1BD,MAAMI,wDAGQC,SAASC,iBAAkB,oGAA8C,cACnFC,iBAAiB,UAAWE,mHAElBJ,SAASC,iBAAkB,mFAA6B,cAClEC,iBAAiB,UAAWE,oEA9C9B,yEAAa"}
{"version":3,"file":"cfg-grades.min.js","sources":["../src/cfg-grades.js"],"sourcesContent":["/*eslint no-var: \"error\" */\n/*eslint no-unused-vars: \"off\" */\n/*eslint linebreak-style: \"off\" */\n/*eslint no-trailing-spaces: \"off\" */\n/*eslint-env es6*/\n// Put this file in path/to/plugin/amd/src\n// You can call it anything you like\n\nimport {get_string,get_strings} from 'core/str';\nimport {call} from 'core/ajax';\nimport Debugger from './debugger';\n\nimport {load_strings} from './string-helper';\n\nlet debug = new Debugger(\"treestudyplan-config-grades\");\n\n/*\nlet strings = load_strings({\n studyplan: {\n studyplan_select_placeholder: 'studyplan_select_placeholder',\n },\n});\n*/\n\n/**\n * Initialize grade cfg page\n */\nexport function init() {\n { const\n intRx = /\\d/,\n integerChange = (event) => {\n if ( (event.key.length > 1) || intRx.test(event.key)\n ) {\n return;\n }\n event.preventDefault();\n };\n\n for (let input of document.querySelectorAll( 'input[type=\"number\"][step=\"1\"][min=\"0\"]' )){\n input.addEventListener(\"keydown\", integerChange);\n }\n\n }\n\n { const\n decimal= /^[0-9]*?\\.[0-9]*?$/,\n intRx = /\\d/,\n floatChange = (event) => {\n if ( (event.key.length > 1) || ( (event.key === \".\") && (!event.currentTarget.value.match(decimal)) )\n || intRx.test(event.key)\n ) {\n return;\n }\n event.preventDefault();\n };\n\n for (let input of document.querySelectorAll( 'input[type=\"number\"][min=\"0\"]:not([step])' )){\n input.addEventListener(\"keydown\", floatChange);\n }\n for (let input of document.querySelectorAll( 'input[type=\"text\"].float' )){\n input.addEventListener(\"keydown\", floatChange);\n }\n\n }\n\n\n}\n\n"],"names":["intRx","integerChange","event","key","length","test","preventDefault","document","querySelectorAll","addEventListener","decimal","floatChange","currentTarget","value","match"],"mappings":"kgDA6BQA,MAAQ,KACRC,cAAgB,SAACC,OACTA,MAAMC,IAAIC,OAAS,GAAMJ,MAAMK,KAAKH,MAAMC,MAIhDD,MAAMI,uDAGQC,SAASC,iBAAkB,+FAA4C,aACjFC,iBAAiB,UAAWR,6EAMlCS,QAAS,qBACTV,OAAQ,KACRW,YAAc,SAACT,OACPA,MAAMC,IAAIC,OAAS,GAAuB,MAAdF,MAAMC,MAAkBD,MAAMU,cAAcC,MAAMC,MAAMJ,UACjFV,OAAMK,KAAKH,MAAMC,MAI1BD,MAAMI,wDAGQC,SAASC,iBAAkB,oGAA8C,cACnFC,iBAAiB,UAAWE,mHAElBJ,SAASC,iBAAkB,mFAA6B,cAClEC,iBAAiB,UAAWE,oEA9C9B,yEAAa"}

View File

@ -1,3 +1,3 @@
define("local_treestudyplan/modedit-modal",["exports","core/fragment","./util/string-helper","core/ajax","core/notification","core/templates"],(function(_exports,_fragment,_stringHelper,_ajax,_notification,_templates){var obj;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_notification=(obj=_notification)&&obj.__esModule?obj:{default:obj};var _default={install:function(Vue){var strings=(0,_stringHelper.load_strings)({editmod:{save$core:"save$core",cancel$core:"cancel$core"}});Vue.component("s-edit-mod",{props:{cmid:{type:Number},coursectxid:{type:Number},title:{type:String,default:""},genericonly:{type:Boolean,default:!1}},data:function(){return{content:"",text:strings.editmod}},computed:{},methods:{openForm:function(){this.$refs.editormodal.show()},onShown:function(){var self=this,params={cmid:this.cmid};console.info("Loading form"),(0,_fragment.loadFragment)("local_treestudyplan","mod_edit_form",this.coursectxid,params).then((function(html,js){(0,_templates.replaceNodeContents)(self.$refs.content,html,js)})).catch(_notification.default.exception)},onSave:function(){var self=this,form=this.$refs.content.getElementsByTagName("form")[0];form.dispatchEvent(new Event("save-form-state"));var formdata=new FormData(form),data=new URLSearchParams(formdata).toString();(0,_ajax.call)([{methodname:"local_treestudyplan_submit_cm_editform",args:{cmid:this.cmid,formdata:data}}])[0].done((function(){self.$emit("saved",formdata)})).fail(_notification.default.exception)}},template:'\n <span class=\'s-edit-mod\'><a href=\'#\' @click.prevent="openForm"><slot><i class="fa fa-cog"></i></slot></a>\n <b-modal \n ref="editormodal"\n scrollable\n centered\n size="xl"\n id="\'modal-cm-\'+cmid"\n @shown="onShown"\n @ok="onSave"\n :title="title"\n :ok-title="text.save$core"\n ><div :class="\'s-edit-mod-form \'+ (genericonly?\'genericonly\':\'\')" ref="content"\n ><div class="d-flex justify-content-center mb-3"><b-spinner variant="primary"></b-spinner></div\n ></div\n ></b-modal>\n </span>\n '})}};return _exports.default=_default,_exports.default}));
define("local_treestudyplan/modedit-modal",["exports","core/fragment","./util/string-helper","core/ajax","core/notification","core/templates"],(function(_exports,_fragment,_stringHelper,_ajax,_notification,_templates){var obj;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_notification=(obj=_notification)&&obj.__esModule?obj:{default:obj};var _default={install:function(Vue){var strings=(0,_stringHelper.load_strings)({editmod:{save$core:"save$core",cancel$core:"cancel$core"}});Vue.component("s-edit-mod",{props:{cmid:{type:Number},coursectxid:{type:Number},title:{type:String,default:""},genericonly:{type:Boolean,default:!1}},data:function(){return{content:"",text:strings.editmod}},computed:{},methods:{openForm:function(){this.$refs.editormodal.show()},onShown:function(){var self=this,params={cmid:this.cmid};console.info("Loading form"),(0,_fragment.loadFragment)("local_treestudyplan","mod_edit_form",this.coursectxid,params).then((function(html,js){(0,_templates.replaceNodeContents)(self.$refs.content,html,js)})).catch(_notification.default.exception)},onSave:function(){var self=this,form=this.$refs.content.getElementsByTagName("form")[0];form.dispatchEvent(new Event("save-form-state"));var formdata=new FormData(form),data=new URLSearchParams(formdata).toString();(0,_ajax.call)([{methodname:"local_treestudyplan_submit_cm_editform",args:{cmid:this.cmid,formdata:data}}])[0].done((function(){self.$emit("saved",formdata)})).fail(_notification.default.exception)}},template:'\n <span class=\'s-edit-mod\'><a href=\'#\' @click.prevent="openForm"><slot><i class="fa fa-cog"></i></slot></a>\n <b-modal\n ref="editormodal"\n scrollable\n centered\n size="xl"\n id="\'modal-cm-\'+cmid"\n @shown="onShown"\n @ok="onSave"\n :title="title"\n :ok-title="text.save$core"\n ><div :class="\'s-edit-mod-form \'+ (genericonly?\'genericonly\':\'\')" ref="content"\n ><div class="d-flex justify-content-center mb-3"><b-spinner variant="primary"></b-spinner></div\n ></div\n ></b-modal>\n </span>\n '})}};return _exports.default=_default,_exports.default}));
//# sourceMappingURL=modedit-modal.min.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
{"version":3,"file":"page-myreport.min.js","sources":["../src/page-myreport.js"],"sourcesContent":["/*eslint no-var: \"error\" */\n/*eslint no-unused-vars: \"off\" */\n/*eslint linebreak-style: \"off\" */\n/*eslint no-trailing-spaces: \"off\" */\n/*eslint no-console: \"off\" */\n/*eslint-env es6*/\n// Put this file in path/to/plugin/amd/src\n// You can call it anything you like\n\nimport {call} from 'core/ajax';\nimport notification from 'core/notification';\n\nimport Vue from './vue/vue';\n\nimport RVComponents from './report-viewer-components';\nVue.use(RVComponents);\nimport TSComponents from './treestudyplan-components';\n\nimport Debugger from './util/debugger';\n\nimport {ProcessStudyplans} from './studyplan-processor';\n\n\nimport PortalVue from './portal-vue/portal-vue.esm';\nVue.use(PortalVue);\nimport BootstrapVue from './bootstrap-vue/bootstrap-vue';\nVue.use(BootstrapVue);\n\nlet debug = new Debugger(\"treestudyplan-report\");\n\n/**\n * Initialize the Page\n * @param {string} type Type of page to show\n * @param {Object} arg Arguments passed\n */\n export function init(type=\"myreport\",arg) {\n let app = new Vue({\n el: '#root',\n data: {\n \"studyplans\": [],\n },\n mounted() {\n let call_method;\n let call_args;\n if(type == \"invited\"){\n call_method = 'local_treestudyplan_get_invited_studyplan';\n call_args = {\"invitekey\": arg};\n }\n else if(type == \"other\"){\n call_method = 'local_treestudyplan_get_user_studyplans';\n call_args = {\"userid\": arg};\n }\n else if(type == \"teaching\"){\n call_method = 'local_treestudyplan_get_teaching_studyplans';\n call_args = {};\n }\n else{\n call_method = 'local_treestudyplan_get_own_studyplan';\n call_args = {};\n }\n call([{\n methodname: call_method,\n args: call_args\n }])[0].done(function(response){\n debug.info(\"Studyplans:\",response);\n const timingval = { future: 0, present: 1, past: 2, };\n response.sort((a,b) => {\n const timinga = TSComponents.studyplanTiming(a);\n const timingb = TSComponents.studyplanTiming(b);\n\n let t = timingval[timinga] - timingval[timingb];\n if(t == 0){\n // sort by start date if timing is equal\n t = new Date(b.startdate).getTime() - new Date(a.startdate).getTime();\n \n if (t == 0) {\n // sort by name if timing is equal\n t = a.name.localeCompare(b.name);\n }\n }\n return t;\n });\n app.studyplans = ProcessStudyplans(response);\n }).fail(notification.exception); \n \n },\n\n methods: {\n \n },\n });\n\n}\n\n"],"names":["type","arg","app","Vue","el","data","mounted","call_method","call_args","methodname","args","done","response","debug","info","timingval","future","present","past","sort","a","b","timinga","TSComponents","studyplanTiming","timingb","t","Date","startdate","getTime","name","localeCompare","studyplans","fail","notification","exception","methods","use","RVComponents","PortalVue","BootstrapVue","Debugger"],"mappings":"ikBAmCsBA,4DAAK,WAAWC,2CAC9BC,IAAM,IAAIC,aAAI,CACdC,GAAI,QACJC,KAAM,YACY,IAElBC,uBACQC,YACAC,UACO,WAARR,MACCO,YAAc,4CACdC,UAAY,WAAcP,MAEd,SAARD,MACJO,YAAc,0CACdC,UAAY,QAAWP,MAEX,YAARD,MACJO,YAAc,8CACdC,UAAY,KAGZD,YAAc,wCACdC,UAAY,mBAEX,CAAC,CACFC,WAAYF,YACZG,KAAMF,aACN,GAAGG,MAAK,SAASC,UACjBC,MAAMC,KAAK,cAAcF,cACnBG,UAAY,CAAEC,OAAQ,EAAGC,QAAS,EAAGC,KAAM,GACjDN,SAASO,MAAK,SAACC,EAAEC,OACPC,QAAUC,iCAAaC,gBAAgBJ,GACvCK,QAAUF,iCAAaC,gBAAgBH,GAEzCK,EAAIX,UAAUO,SAAWP,UAAUU,gBAC/B,GAALC,GAIU,IAFTA,EAAI,IAAIC,KAAKN,EAAEO,WAAWC,UAAY,IAAIF,KAAKP,EAAEQ,WAAWC,aAIxDH,EAAIN,EAAEU,KAAKC,cAAcV,EAAES,OAG5BJ,KAEXxB,IAAI8B,YAAa,yCAAkBpB,aACpCqB,KAAKC,sBAAaC,YAIzBC,QAAS,yYAxEbC,IAAIC,8CASJD,IAAIE,iCAEJF,IAAIG,2BAEJ3B,MAAQ,IAAI4B,kBAAS"}
{"version":3,"file":"page-myreport.min.js","sources":["../src/page-myreport.js"],"sourcesContent":["/*eslint no-var: \"error\" */\n/*eslint no-unused-vars: \"off\" */\n/*eslint linebreak-style: \"off\" */\n/*eslint no-trailing-spaces: \"off\" */\n/*eslint no-console: \"off\" */\n/*eslint-env es6*/\n// Put this file in path/to/plugin/amd/src\n// You can call it anything you like\n\nimport {call} from 'core/ajax';\nimport notification from 'core/notification';\n\nimport Vue from './vue/vue';\n\nimport RVComponents from './report-viewer-components';\nVue.use(RVComponents);\nimport TSComponents from './treestudyplan-components';\n\nimport Debugger from './util/debugger';\n\nimport {ProcessStudyplans} from './studyplan-processor';\n\n\nimport PortalVue from './portal-vue/portal-vue.esm';\nVue.use(PortalVue);\nimport BootstrapVue from './bootstrap-vue/bootstrap-vue';\nVue.use(BootstrapVue);\n\nlet debug = new Debugger(\"treestudyplan-report\");\n\n/**\n * Initialize the Page\n * @param {string} type Type of page to show\n * @param {Object} arg Arguments passed\n */\n export function init(type=\"myreport\",arg) {\n let app = new Vue({\n el: '#root',\n data: {\n \"studyplans\": [],\n },\n mounted() {\n let call_method;\n let call_args;\n if(type == \"invited\"){\n call_method = 'local_treestudyplan_get_invited_studyplan';\n call_args = {\"invitekey\": arg};\n }\n else if(type == \"other\"){\n call_method = 'local_treestudyplan_get_user_studyplans';\n call_args = {\"userid\": arg};\n }\n else if(type == \"teaching\"){\n call_method = 'local_treestudyplan_get_teaching_studyplans';\n call_args = {};\n }\n else{\n call_method = 'local_treestudyplan_get_own_studyplan';\n call_args = {};\n }\n call([{\n methodname: call_method,\n args: call_args\n }])[0].done(function(response){\n debug.info(\"Studyplans:\",response);\n const timingval = { future: 0, present: 1, past: 2, };\n response.sort((a,b) => {\n const timinga = TSComponents.studyplanTiming(a);\n const timingb = TSComponents.studyplanTiming(b);\n\n let t = timingval[timinga] - timingval[timingb];\n if(t == 0){\n // sort by start date if timing is equal\n t = new Date(b.startdate).getTime() - new Date(a.startdate).getTime();\n\n if (t == 0) {\n // sort by name if timing is equal\n t = a.name.localeCompare(b.name);\n }\n }\n return t;\n });\n app.studyplans = ProcessStudyplans(response);\n }).fail(notification.exception);\n\n },\n\n methods: {\n\n },\n });\n\n}\n\n"],"names":["type","arg","app","Vue","el","data","mounted","call_method","call_args","methodname","args","done","response","debug","info","timingval","future","present","past","sort","a","b","timinga","TSComponents","studyplanTiming","timingb","t","Date","startdate","getTime","name","localeCompare","studyplans","fail","notification","exception","methods","use","RVComponents","PortalVue","BootstrapVue","Debugger"],"mappings":"ikBAmCsBA,4DAAK,WAAWC,2CAC9BC,IAAM,IAAIC,aAAI,CACdC,GAAI,QACJC,KAAM,YACY,IAElBC,uBACQC,YACAC,UACO,WAARR,MACCO,YAAc,4CACdC,UAAY,WAAcP,MAEd,SAARD,MACJO,YAAc,0CACdC,UAAY,QAAWP,MAEX,YAARD,MACJO,YAAc,8CACdC,UAAY,KAGZD,YAAc,wCACdC,UAAY,mBAEX,CAAC,CACFC,WAAYF,YACZG,KAAMF,aACN,GAAGG,MAAK,SAASC,UACjBC,MAAMC,KAAK,cAAcF,cACnBG,UAAY,CAAEC,OAAQ,EAAGC,QAAS,EAAGC,KAAM,GACjDN,SAASO,MAAK,SAACC,EAAEC,OACPC,QAAUC,iCAAaC,gBAAgBJ,GACvCK,QAAUF,iCAAaC,gBAAgBH,GAEzCK,EAAIX,UAAUO,SAAWP,UAAUU,gBAC/B,GAALC,GAIU,IAFTA,EAAI,IAAIC,KAAKN,EAAEO,WAAWC,UAAY,IAAIF,KAAKP,EAAEQ,WAAWC,aAIxDH,EAAIN,EAAEU,KAAKC,cAAcV,EAAES,OAG5BJ,KAEXxB,IAAI8B,YAAa,yCAAkBpB,aACpCqB,KAAKC,sBAAaC,YAIzBC,QAAS,yYAxEbC,IAAIC,8CASJD,IAAIE,iCAEJF,IAAIG,2BAEJ3B,MAAQ,IAAI4B,kBAAS"}

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

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

@ -1,3 +1,3 @@
define("local_treestudyplan/treestudyplan-components",["exports","./util/string-helper","./util/date-helper"],(function(_exports,_stringHelper,_dateHelper){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;var _default={studyplanTiming:function(a){var now=(new Date).getTime(),timing="future";return new Date(a.startdate).getTime()<now&&(timing=a.enddate&&now>new Date(a.enddate).getTime()?"past":"present"),timing},install:function(Vue){var strings=(0,_stringHelper.load_strings)({studyplancard:{open:"open",noenddate:"noenddate",idnumber:"studyplan_idnumber"}}),ItemEventBus=new Vue;Vue.component("s-studyplan-card",{props:{value:{type:Object},open:{type:Boolean}},data:function(){return{text:strings.studyplancard}},computed:{timing:function(){var now=(new Date).getTime(),startdate=new Date(this.value.pages[0].startdate).getTime(),enddate=new Date(this.value.pages[0].enddate).getTime(),timing="future";return startdate<now&&(timing=this.value.pages[0].enddate&&now>enddate?"past":"present"),timing},startdate:function(){return(0,_dateHelper.format_date)(this.value.pages[0].startdate)},enddate:function(){return this.value.pages[0].enddate?(0,_dateHelper.format_date)(this.value.pages[0].enddate):this.text.noenddate}},methods:{onOpenClick:function(e){this.$emit("open",e)}},template:"\n <b-card \n :class=\"'s-studyplan-card timing-' + timing\"\n >\n <template #header></template>\n <b-card-title>\n <a v-if='open' href='#' @click.prevent='onOpenClick($event)'>{{value.name}}</a>\n <template v-else>{{value.name}}</template>\n <slot name='title'></slot>\n </b-card-title>\n <div class='s-studyplan-card-idnumber' v-if='value.idnumber'><i>{{ text.idnumber}}:</i> {{ value.idnumber }}</div>\n <div class='s-studyplan-card-description' v-if='value.description'>{{ value.description }}</div>\n <slot></slot>\n <template #footer>\n <span :class=\"'t-timing-'+timing\" v-html=\"startdate + ' - '+ enddate\"></span>\n <span class=\"s-studyplan-card-buttons\">\n <slot name='footer'></slot>\n <b-button style=\"float:right;\" v-if='open' variant='primary' \n @click.prevent='onOpenClick($event)'>{{ text.open }}</b-button>\n </span>\n </template>\n </b-card>\n "}),Vue.component("s-studyline-header-heading",{props:{},data:function(){return{layerHeights:{}}},created:function(){ItemEventBus.$on("headerHeightChange",this.onHeaderHeightChange)},computed:{},methods:{onHeaderHeightChange:function(newheight){this.$refs.main&&(this.$refs.main.style.height="".concat(newheight,"px"))}},template:'\n <div class="s-studyline-header-heading" ref="main"></div>\n '}),Vue.component("s-studyline-header-period",{props:{value:{type:Object}},mounted:function(){var self=this;1==self.value.period&&(self.resizeListener=new ResizeObserver((function(){if(self.$refs.main){var size=self.$refs.main.getBoundingClientRect();ItemEventBus.$emit("headerHeightChange",size.height)}})).observe(self.$refs.main))},unmounted:function(){this.resizeListener&&this.resizeListener.disconnect()},computed:{startdate:function(){return(0,_dateHelper.format_date)(this.value.startdate)},enddate:function(){return(0,_dateHelper.format_date)(this.value.enddate)},current:function(){if(this.value&&this.value.startdate&&this.value.enddate){var now=new Date,pstart=new Date(this.value.startdate),pend=new Date(this.value.enddate);return now>=pstart&&now<pend}return!1}},data:function(){return{}},template:'\n <div :class="\'s-studyline-header-period \' + (current?\'current \':\' \')" ref="main"\n ><p><abbr :id="\'s-period-\'+value.id" :title="value.fullname">{{ value.shortname }}</abbr>\n <b-tooltip\n :target="\'s-period-\'+value.id" triggers="hover"\n >{{ value.fullname }}<br>\n <span class="s-studyline-header-period-datespan">\n <span class="date">{{ startdate }}</span> - <span class="date">{{ enddate }}</span>\n </span>\n </b-tooltip>\n <slot></slot\n ><p class="s-studyline-header-period-datespan small">\n <span class="date">{{ startdate }}</span> - <span class="date">{{ enddate }}</span>\n </p>\n </div>\n '})}};return _exports.default=_default,_exports.default}));
define("local_treestudyplan/treestudyplan-components",["exports","./util/string-helper","./util/date-helper"],(function(_exports,_stringHelper,_dateHelper){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;var _default={studyplanTiming:function(a){var now=(new Date).getTime(),timing="future";return new Date(a.startdate).getTime()<now&&(timing=a.enddate&&now>new Date(a.enddate).getTime()?"past":"present"),timing},install:function(Vue){var strings=(0,_stringHelper.load_strings)({studyplancard:{open:"open",noenddate:"noenddate",idnumber:"studyplan_idnumber"}}),ItemEventBus=new Vue;Vue.component("s-studyplan-card",{props:{value:{type:Object},open:{type:Boolean}},data:function(){return{text:strings.studyplancard}},computed:{timing:function(){var now=(new Date).getTime(),startdate=new Date(this.value.pages[0].startdate).getTime(),enddate=new Date(this.value.pages[0].enddate).getTime(),timing="future";return startdate<now&&(timing=this.value.pages[0].enddate&&now>enddate?"past":"present"),timing},startdate:function(){return(0,_dateHelper.format_date)(this.value.pages[0].startdate)},enddate:function(){return this.value.pages[0].enddate?(0,_dateHelper.format_date)(this.value.pages[0].enddate):this.text.noenddate}},methods:{onOpenClick:function(e){this.$emit("open",e)}},template:"\n <b-card\n :class=\"'s-studyplan-card timing-' + timing\"\n >\n <template #header></template>\n <b-card-title>\n <a v-if='open' href='#' @click.prevent='onOpenClick($event)'>{{value.name}}</a>\n <template v-else>{{value.name}}</template>\n <slot name='title'></slot>\n </b-card-title>\n <div class='s-studyplan-card-idnumber' v-if='value.idnumber'><i>{{ text.idnumber}}:</i> {{ value.idnumber }}</div>\n <div class='s-studyplan-card-description' v-if='value.description'>{{ value.description }}</div>\n <slot></slot>\n <template #footer>\n <span :class=\"'t-timing-'+timing\" v-html=\"startdate + ' - '+ enddate\"></span>\n <span class=\"s-studyplan-card-buttons\">\n <slot name='footer'></slot>\n <b-button style=\"float:right;\" v-if='open' variant='primary'\n @click.prevent='onOpenClick($event)'>{{ text.open }}</b-button>\n </span>\n </template>\n </b-card>\n "}),Vue.component("s-studyline-header-heading",{props:{},data:function(){return{layerHeights:{}}},created:function(){ItemEventBus.$on("headerHeightChange",this.onHeaderHeightChange)},computed:{},methods:{onHeaderHeightChange:function(newheight){this.$refs.main&&(this.$refs.main.style.height="".concat(newheight,"px"))}},template:'\n <div class="s-studyline-header-heading" ref="main"></div>\n '}),Vue.component("s-studyline-header-period",{props:{value:{type:Object}},mounted:function(){var self=this;1==self.value.period&&(self.resizeListener=new ResizeObserver((function(){if(self.$refs.main){var size=self.$refs.main.getBoundingClientRect();ItemEventBus.$emit("headerHeightChange",size.height)}})).observe(self.$refs.main))},unmounted:function(){this.resizeListener&&this.resizeListener.disconnect()},computed:{startdate:function(){return(0,_dateHelper.format_date)(this.value.startdate)},enddate:function(){return(0,_dateHelper.format_date)(this.value.enddate)},current:function(){if(this.value&&this.value.startdate&&this.value.enddate){var now=new Date,pstart=new Date(this.value.startdate),pend=new Date(this.value.enddate);return now>=pstart&&now<pend}return!1}},data:function(){return{}},template:'\n <div :class="\'s-studyline-header-period \' + (current?\'current \':\' \')" ref="main"\n ><p><abbr :id="\'s-period-\'+value.id" :title="value.fullname">{{ value.shortname }}</abbr>\n <b-tooltip\n :target="\'s-period-\'+value.id" triggers="hover"\n >{{ value.fullname }}<br>\n <span class="s-studyline-header-period-datespan">\n <span class="date">{{ startdate }}</span> - <span class="date">{{ enddate }}</span>\n </span>\n </b-tooltip>\n <slot></slot\n ><p class="s-studyline-header-period-datespan small">\n <span class="date">{{ startdate }}</span> - <span class="date">{{ enddate }}</span>\n </p>\n </div>\n '})}};return _exports.default=_default,_exports.default}));
//# sourceMappingURL=treestudyplan-components.min.js.map

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

@ -9,7 +9,7 @@ That's why the plain bootstrap.js file is used
2. Copy node_modules/bootstrap-vue/dist/bootstrap-vue.js to amd/src/bootstrap-vue
Copy node_modules/bootstrap-vue/dist/bootstrap-vue.min.css to css/bootstrap-vue
3. Change require("vue"...) and define("vue"...) in the lines 10 - 16 to
3. Change require("vue"...) and define("vue"...) in the lines 10 - 16 to
require("../vue/vue"...) and define("../vue/vue"...)

View File

@ -1,10 +1,10 @@
/* eslint-disable */
/*
/*
Compiled from ts into js by PMKuipers @ 2023) from source
at https://github.com/alexsasharegan/vue-functional-data-merge
at https://github.com/alexsasharegan/vue-functional-data-merge
Below is copy of licens in original project
*/
/*
/*
The MIT License (MIT)
Copyright (c) 2017 Alex Regan

View File

@ -30,16 +30,16 @@ export function init() {
intRx = /\d/,
integerChange = (event) => {
if ( (event.key.length > 1) || intRx.test(event.key)
) {
return;
) {
return;
}
event.preventDefault();
};
for (let input of document.querySelectorAll( 'input[type="number"][step="1"][min="0"]' )){
input.addEventListener("keydown", integerChange);
}
}
}
{ const
@ -48,19 +48,19 @@ export function init() {
floatChange = (event) => {
if ( (event.key.length > 1) || ( (event.key === ".") && (!event.currentTarget.value.match(decimal)) )
|| intRx.test(event.key)
) {
return;
) {
return;
}
event.preventDefault();
};
for (let input of document.querySelectorAll( 'input[type="number"][min="0"]:not([step])' )){
input.addEventListener("keydown", floatChange);
}
}
for (let input of document.querySelectorAll( 'input[type="text"].float' )){
input.addEventListener("keydown", floatChange);
}
}
}

View File

@ -59,12 +59,12 @@ export default {
loadFragment('local_treestudyplan', 'mod_edit_form', this.coursectxid, params).then((html,js) =>{
replaceNodeContents(self.$refs["content"], html, js);
}).catch(notification.exception);
},
onSave(){
const self = this;
let form = this.$refs["content"].getElementsByTagName("form")[0];
// markFormSubmitted(form); // Moodle 4.00+ only
// We call this, so other modules can update the form with the latest state.
form.dispatchEvent(new Event("save-form-state"));
@ -88,7 +88,7 @@ export default {
},
template: `
<span class='s-edit-mod'><a href='#' @click.prevent="openForm"><slot><i class="fa fa-cog"></i></slot></a>
<b-modal
<b-modal
ref="editormodal"
scrollable
centered

View File

@ -52,7 +52,7 @@ let strings = load_strings({
*/
export function init(contextid,categoryid) {
// Make sure the id's are numeric and integer
if(undefined === contextid || !Number.isInteger(Number(contextid)) || contextid < 1 ){ contextid = 1;}
if(undefined === contextid || !Number.isInteger(Number(contextid)) || contextid < 1 ){ contextid = 1;}
else { contextid = Number(contextid);} // ensure a numeric value instead of string
if(undefined === categoryid || !Number.isInteger(Number(categoryid))){ categoryid = 0;}
else { categoryid = Number(categoryid);} // ensure a numeric value instead of string
@ -92,7 +92,7 @@ export function init(contextid,categoryid) {
},
created() {
this.$root.$on('studyplanRemoved',(studyplan)=>{
if(app.activestudyplan == studyplan){
app.activestudyplan = null;
}
@ -125,14 +125,14 @@ export function init(contextid,categoryid) {
if(t == 0){
// sort by start date if timing is equal
t = new Date(b.startdate).getTime() - new Date(a.startdate).getTime();
if (t == 0) {
// sort by name if timing is equal
t = a.name.localeCompare(b.name);
}
}
return t;
});
});
app.studyplans = response;
// load studyplan from hash if applicable
@ -151,19 +151,19 @@ export function init(contextid,categoryid) {
args: {}
}])[0].done(function(response){
app.badges = response;
}).fail(notification.exception);
}).fail(notification.exception);
call([{
methodname: 'local_treestudyplan_map_categories',
args: {root_id: categoryid}
}])[0].done(function(response){
app.courses = response;
}).fail(notification.exception);
}).fail(notification.exception);
call([{
methodname: 'local_treestudyplan_list_used_categories',
args: { operation: 'edit'}
}])[0].done(function(response){
app.usedcontexts = response;
}).fail(notification.exception);
}).fail(notification.exception);
},
computed: {
@ -191,7 +191,7 @@ export function init(contextid,categoryid) {
closeStudyplan() {
app.activestudyplan = null;
window.location.hash = '';
},
},
movedStudyplan(plan,from,to) {
// reload the page in the new context (needed, since a number of links are not reactive in the page)
const params = new URLSearchParams(location.search);
@ -208,7 +208,7 @@ export function init(contextid,categoryid) {
const params = new URLSearchParams(location.search);
params.set('categoryid', ctx.id);
window.location.search = params.toString();
},
selectStudyplan(studyplan){
// fetch studyplan
@ -242,8 +242,8 @@ export function init(contextid,categoryid) {
} else {
debug.error("Import failed: ",response.msg);
}
}).fail(notification.exception);
}).fail(notification.exception);
}, "application/json");
},
export_plan(plan,format){
@ -258,9 +258,9 @@ export function init(contextid,categoryid) {
format: format
},
}])[0].done(function(response){
download(plan.shortname+".json",response.content,response.format);
}).fail(notification.exception);
}).fail(notification.exception);
},
toggletoolbox(event) {
debug.info(event);

View File

@ -72,7 +72,7 @@ let debug = new Debugger("treestudyplan-report");
if(t == 0){
// sort by start date if timing is equal
t = new Date(b.startdate).getTime() - new Date(a.startdate).getTime();
if (t == 0) {
// sort by name if timing is equal
t = a.name.localeCompare(b.name);
@ -81,12 +81,12 @@ let debug = new Debugger("treestudyplan-report");
return t;
});
app.studyplans = ProcessStudyplans(response);
}).fail(notification.exception);
}).fail(notification.exception);
},
methods: {
},
});

View File

@ -42,7 +42,7 @@ let strings = load_strings({
*/
export function init(contextid,categoryid) {
// Make sure the id's are numeric and integer
if(undefined === contextid || !Number.isInteger(Number(contextid)) || contextid < 1 ){ contextid = 1;}
if(undefined === contextid || !Number.isInteger(Number(contextid)) || contextid < 1 ){ contextid = 1;}
else { contextid = Number(contextid);} // ensure a numeric value instead of string
if(undefined === categoryid || !Number.isInteger(Number(categoryid))){ categoryid = 0;}
else { categoryid = Number(categoryid);} // ensure a numeric value instead of string

View File

@ -1,13 +1,13 @@
/* eslint-disable */
/*!
* portal-vue © Thorsten Lünborg, 2019
*
/*!
* portal-vue © Thorsten Lünborg, 2019
*
* Version: 2.1.7
*
* LICENCE: MIT
*
*
* LICENCE: MIT
*
* https://github.com/linusborg/portal-vue
*
*
*/
/* Modification by PMKuipers to handle relative loading of vue */

View File

@ -118,9 +118,9 @@ export default {
select_grades: "select_grades",
coursetiming_past: "coursetiming_past",
coursetiming_present: "coursetiming_present",
coursetiming_future: "coursetiming_future",
grade_include: "grade_include",
grade_require: "grade_require",
coursetiming_future: "coursetiming_future",
grade_include: "grade_include",
grade_require: "grade_require",
required_goal: "required_goal",
student_from_plan_enrolled: "student_from_plan_enrolled",
students_from_plan_enrolled: "students_from_plan_enrolled",
@ -197,7 +197,7 @@ export default {
return 50 - (50*this.stroke);
},
arcpath() {
let fraction = 0;
let fraction = 0;
const r = 50 - (50*this.stroke);
if(this.max - this.min != 0){
fraction = (this.value - this.min)/(this.max - this.min);
@ -214,17 +214,17 @@ export default {
<svg width="1em" height="1em" viewBox="0 0 100 100"
style="position: absolute;top: 0;left: 0;">
<title>{{title}}</title>
<circle v-if="fraction >= 1.0" cx="50" cy="50" :r="radius"
<circle v-if="fraction >= 1.0" cx="50" cy="50" :r="radius"
:style="'opacity: 1; stroke-width: '+ (stroke*100)+'; stroke: currentcolor; fill: none;'"/>
<g v-else>
<circle cx="50" cy="50" :r="radius"
<circle cx="50" cy="50" :r="radius"
:style="'opacity: ' + bgopacity + ';stroke-width: '+ (stroke*100)+'; stroke: currentcolor; fill: none;'"/>
<path :d="arcpath"
<path :d="arcpath"
:style="'stroke-width: ' + (stroke*100) +'; stroke: currentcolor; fill: none;'"/>
</g>
</svg>
<i v-if='icon' :class="'fa fa-'+icon"
style=" position: absolute; top: 0.42em; left: 0.43em; text-align:center; display: inline-block;
<i v-if='icon' :class="'fa fa-'+icon"
style=" position: absolute; top: 0.42em; left: 0.43em; text-align:center; display: inline-block;
width:1em; font-size: 0.55em; "
></i>
</div>
@ -268,9 +268,9 @@ export default {
},
},
template: `
<div class='t-studyplan-container r-report-tabs'>
<div class='t-studyplan-container r-report-tabs'>
<b-list-group horizontal>
<b-list-group-item
<b-list-group-item
v-for="(studyplan,planindex) in value"
:key="studyplan.id"
:active="displayedstudyplan && studyplan.id == displayedstudyplan.id"
@ -364,7 +364,7 @@ export default {
}
}
}
return show;
}
},
@ -386,7 +386,7 @@ export default {
v-model="page.studylines[lineindex]"
:layers='countLineLayers(line)+1'
:class=" 't-studyline' + ((lineindex%2==0)?' odd ' :' even ' )
+ ((lineindex==0)?' first ':' ')
+ ((lineindex==0)?' first ':' ')
+ ((lineindex==page.studylines.length-1)?' last ':' ')"
></r-studyline-heading
></div>
@ -395,24 +395,24 @@ export default {
<div class="r-studyplan-timeline" :style="columns_stylerule">
<!-- add period information -->
<template v-for="(n,index) in (page.periods+1)">
<s-studyline-header-period
<s-studyline-header-period
v-if="index > 0"
v-model="page.perioddesc[index-1]"
></s-studyline-header-period>
<div class="s-studyline-header-filter"></div>
</template>
<!-- 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="(n,index) in (page.periods+1)"
><r-studyline-slot
><r-studyline-slot
v-if="index > 0 && showslot(line, index, layeridx, 'gradable')"
type='gradable'
v-model="line.slots[index].courses"
:key="'c-'+lineindex+'-'+index+'-'+layernr"
:slotindex="index"
v-model="line.slots[index].courses"
:key="'c-'+lineindex+'-'+index+'-'+layernr"
:slotindex="index"
:line="line"
:plan="value"
:page="page"
@ -424,13 +424,13 @@ export default {
+ ((lineindex==0 && layernr==1)?' first ':' ')
+ ((lineindex==page.studylines.length-1)?' last ':' ')"
></r-studyline-slot
><r-studyline-slot
><r-studyline-slot
v-if="showslot(line, index, layeridx, 'gradable')"
type='filter'
v-model="line.slots[index].filters"
v-model="line.slots[index].filters"
:teachermode='teachermode'
:key="'f-'+lineindex+'-'+index+'-'+layernr"
:slotindex="index"
:key="'f-'+lineindex+'-'+index+'-'+layernr"
:slotindex="index"
:line="line"
:plan="value"
:page="page"
@ -475,12 +475,12 @@ export default {
ItemEventBus.$on('lineHeightChange', this.onLineHeightChange);
},
computed: {
},
methods: {
onLineHeightChange(lineid){
// 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
// 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 || lineid === null)){
const items = document.querySelectorAll(
@ -500,7 +500,7 @@ export default {
}
},
template: `
<div class="r-studyline r-studyline-heading "
<div class="r-studyline r-studyline-heading "
:data-studyline="value.id" ref="mainEl"
><div class="r-studyline-handle" :style="'background-color: ' + value.color"></div>
<div class="r-studyline-title">
@ -530,7 +530,7 @@ export default {
},
layer : {
type: Number,
},
},
plan: {
type: Object,
default(){ return null;},
@ -538,7 +538,7 @@ export default {
page: {
type: Object,
default(){ return null;},
},
},
guestmode: {
type: Boolean,
default: false,
@ -547,7 +547,7 @@ export default {
type: Boolean,
default: false,
},
period: {
period: {
type: Object,
default(){ return null;},
}
@ -598,17 +598,17 @@ export default {
};
},
methods: {
},
template: `
<div :class=" 'r-studyline-slot ' + type + ' ' + (current?'current ':' ')
+ 'r-studyline-slot-' + slotindex + ' '
<div :class=" 'r-studyline-slot ' + type + ' ' + (current?'current ':' ')
+ 'r-studyline-slot-' + slotindex + ' '
+ ((slotindex==0)?'r-studyline-firstcolumn ':' ')"
:data-studyline="line.id" ref="sizeElement"
:style='spanCss'
><div class="r-slot-item" v-if="item"
><r-item
v-model="item"
><r-item
v-model="item"
:plan="plan"
:guestmode='guestmode'
:teachermode='teachermode'></r-item
@ -624,7 +624,7 @@ export default {
value :{
type: Object,
default: function(){ return null;},
},
},
plan: {
type: Object,
default(){ return null;}
@ -725,7 +725,7 @@ export default {
}
},
created(){
},
mounted(){
// Initialize connection lines when mounting
@ -752,7 +752,7 @@ export default {
}
lineinfo.line = undefined;
}
}
}
}
// Remove resize event listener
window.removeEventListener('resize',this.onWindowResize);
@ -780,7 +780,7 @@ export default {
v-model="value" :guestmode="guestmode" :teachermode="teachermode" ></r-item-finish>
<r-item-badge v-if="value.type == 'badge'"
v-model="value" :guestmode="guestmode" :teachermode="teachermode" ></r-item-badge>
<r-item-invalid v-if="value.type == 'invalid' && teachermode"
<r-item-invalid v-if="value.type == 'invalid' && teachermode"
v-model="value" ></r-item-invalid>
</div>
`,
@ -832,7 +832,7 @@ export default {
teachermode: {
type: Boolean,
default(){ return false;}
},
},
plan: {
type: Object,
default(){ return null;}
@ -850,7 +850,7 @@ export default {
enddate(){
if(this.value.course.enddate){
return format_date(this.value.course.enddate);
}
}
else {
return this.text.noenddate;
}
@ -866,7 +866,7 @@ export default {
case "pending":
return "question-circle";
case "failed":
return "times-circle";
return "times-circle";
case "progress":
return "exclamation-circle";
case "completed":
@ -882,7 +882,7 @@ export default {
default: // case "incomplete"
return null;
case "failed":
return "times";
return "times";
case "progress":
return "exclamation";
case "completed":
@ -899,15 +899,15 @@ export default {
<b-card no-body >
<b-row no-gutters>
<b-col md="1">
<span
:title="text['coursetiming_'+value.course.timing]"
v-b-popover.hover.top="startdate+' - '+enddate"
<span
:title="text['coursetiming_'+value.course.timing]"
v-b-popover.hover.top="startdate+' - '+enddate"
:class="'r-timing-indicator timing-'+value.course.timing"></span>
</b-col>
<b-col md="11" class="align-items-center">
<b-card-body >
<template v-if='!value.course.enrolled'>
<i v-b-popover.top
<i v-b-popover.top
class="r-course-result fa fa-exclamation-triangle t-not-enrolled-alert"
:title="text.student_not_tracked"></i>
</template>
@ -920,28 +920,28 @@ export default {
:icon='circle_icon(value.completion)'
:title="text['completion_'+value.completion]"
></r-progress-circle>
<i v-else v-b-popover.top
<i v-else v-b-popover.top
:class="'r-course-result fa fa-'+completion_icon(value.completion)+
' r-completion-'+value.completion"
:title="text['completion_'+value.completion]"></i>
</template>
<template v-else>
<i v-b-popover.top
<i v-b-popover.top
:class="'r-course-result fa fa-'+completion_icon(value.completion)+
' r-completion-'+value.completion"
:title="text['completion_'+value.completion]"></i>
</template>
<a v-b-modal="'r-item-course-details-'+value.id"
<a v-b-modal="'r-item-course-details-'+value.id"
:href="(!guestmode)?('/course/view.php?id='+value.course.id):'#'"
@click.prevent.stop=''
>{{ value.course.displayname }}</i></a>
</b-card-body>
</b-col>
</b-row>
<b-modal
:id="'r-item-course-details-'+value.id"
:title="value.course.displayname + ' - ' + value.course.fullname"
:id="'r-item-course-details-'+value.id"
:title="value.course.displayname + ' - ' + value.course.fullname"
size="lg"
ok-only
centered
@ -957,7 +957,7 @@ export default {
<div class="r-completion-detail-header">
<template v-if='!value.course.enrolled'>
{{text.not_enrolled}}
<i v-b-popover.top
<i v-b-popover.top
class="r-course-result fa fa-exclamation-triangle t-not-enrolled-alert"
:title="text.student_not_tracked"></i>
</template>
@ -971,7 +971,7 @@ export default {
:class="'r-progress-circle-popup r-completion-'+value.completion"
:icon='circle_icon(value.completion)'
></r-progress-circle
><i v-else v-b-popover.top
><i v-else v-b-popover.top
:class="'fa fa-'+completion_icon(value.completion)+
' r-progress-icon-popup r-completion-'+value.completion"
:title="text['completion_'+value.completion]"></i>
@ -988,16 +988,16 @@ export default {
</div>
</div>
</template>
<r-item-studentgrades
v-if='!!value.course.grades && value.course.grades.length > 0'
<r-item-studentgrades
v-if='!!value.course.grades && value.course.grades.length > 0'
v-model='value'
:guestmode='guestmode'></r-item-studentgrades>
<r-item-studentcompletion
v-if='!!value.course.completion'
v-if='!!value.course.completion'
v-model='value.course.completion'
:course='value.course'
:guestmode='guestmode'></r-item-studentcompletion>
</b-modal>
</b-modal>
</b-card></div>
`,
});
@ -1038,7 +1038,7 @@ export default {
else {
return false;
}
},
},
},
methods: {
completion_icon(completion) {
@ -1048,7 +1048,7 @@ export default {
case "pending":
return "question-circle";
case "failed":
return "times-circle";
return "times-circle";
case "progress":
return "exclamation-circle";
case "completed":
@ -1074,14 +1074,14 @@ export default {
<td><span :class="' r-completion-'+g.completion">{{g.grade}}</span></td>
<td><i :class="'fa fa-'+completion_icon(g.completion)+' r-completion-'+g.completion"
:title="text['completion_'+g.completion]"></i>
<i v-if='g.pendingsubmission' :title="text['completion_pending']"
<i v-if='g.pendingsubmission' :title="text['completion_pending']"
class="r-pendingsubmission fa fa-clock-o"></i></td>
<td v-if="g.feedback">
<a v-b-modal="'r-grade-feedback-'+g.id"
href="#"
<a v-b-modal="'r-grade-feedback-'+g.id"
href="#"
>{{ text["view_feedback"]}}</a>
<b-modal
:id="'r-grade-feedback-'+g.id"
:id="'r-grade-feedback-'+g.id"
size="sm"
ok-only
centered
@ -1090,7 +1090,7 @@ export default {
<template #modal-header>
<h2><i class="fa fa-graduation-cap"></i>{{ value.course.fullname }}</h2><br>
<span class="r-activity-icon" :title="g.typename" v-html="g.icon"></span>{{g.name}}
</template>
</template>
<span v-html="g.feedback"></span>
</b-modal>
</td>
@ -1205,13 +1205,13 @@ export default {
<span v-else v-html='addTargetBlank(ci.details.criteria)'></span>
<a href="#" v-b-tooltip.click.hover.right.html="{ customClass: 'r-tooltip ' + ci.status}"
:title="requirementHTML(ci.details.requirement)"
class="text-primary"><i v-if="ci.details.requirement"
class='fa fa-question-circle'
class="text-primary"><i v-if="ci.details.requirement"
class='fa fa-question-circle'
></i></a>
<td
><span :class="' r-completion-'+ci.status">{{ci.grade}}</span>
<span v-if="ci.warning"
><i class="text-primary fa fa-exclamation-triangle"
><i class="text-primary fa fa-exclamation-triangle"
v-b-tooltip.hover.right.click="{ customClass: 'r-tooltip info' }"
:title="ci.warning"
></i
@ -1219,15 +1219,15 @@ export default {
></td>
<td><i :class="'fa fa-'+completion_icon(ci.status)+' r-completion-'+ci.status"
:title="text['completion_'+ci.status]"></i>
<i v-if='ci.pending' :title="text['completion_pending']"
<i v-if='ci.pending' :title="text['completion_pending']"
class="r-pendingsubmission fa fa-clock-o"></i>
</td>
<td v-if="ci.feedback">
<a v-b-modal="'r-grade-feedback-'+ci.id"
href="#"
<a v-b-modal="'r-grade-feedback-'+ci.id"
href="#"
>{{ text["view_feedback"]}}</a>
<b-modal
:id="'r-grade-feedback-'+ci.id"
:id="'r-grade-feedback-'+ci.id"
size="sm"
ok-only
centered
@ -1356,7 +1356,7 @@ export default {
enddate(){
if(this.value.course.enddate){
return format_date(this.value.course.enddate);
}
}
else {
return this.text.noenddate;
}
@ -1385,7 +1385,7 @@ export default {
}
else if(Number(grade.grading.graded) > 0) {
if(Number(grade.grading.graded) == Number(grade.grading.students)){
allgraded++;
allgraded++;
} else {
graded++;
}
@ -1394,7 +1394,7 @@ export default {
unknown = true;
}
}
if(ungraded > 0){
return 'ungraded';
} else if(unknown) {
@ -1429,14 +1429,14 @@ export default {
<b-card no-body>
<div class='d-flex flex-wrap mr-0 ml-0'>
<div>
<span
:title="text['coursetiming_'+value.course.timing]"
v-b-popover.hover.top="startdate+' - '+enddate"
<span
:title="text['coursetiming_'+value.course.timing]"
v-b-popover.hover.top="startdate+' - '+enddate"
:class="'r-timing-indicator timing-'+value.course.timing"></span>
</div>
<div class="flex-fill align-items-center">
<b-card-body>
<a v-b-modal="'r-item-course-details-'+value.id"
<a v-b-modal="'r-item-course-details-'+value.id"
:href="(!guestmode)?('/course/view.php?id='+value.course.id):'#'"
@click.prevent.stop=''
>{{ value.course.displayname }}</i></a>
@ -1447,8 +1447,8 @@ export default {
</div>
<b-modal
v-if="true"
:id="'r-item-course-details-'+value.id"
:title="value.course.displayname + ' - ' + value.course.fullname"
:id="'r-item-course-details-'+value.id"
:title="value.course.displayname + ' - ' + value.course.fullname"
size="lg"
ok-only
centered
@ -1463,7 +1463,7 @@ export default {
:useRequiredGrades="useRequiredGrades"
:plan="plan"
></r-item-teacher-gradepicker>
<a v-if='!!value.course.completion && value.course.amteacher'
<a v-if='!!value.course.completion && value.course.amteacher'
:href="'/course/completion.php?id='+value.course.id" target="_blank"
:title="text.configure_completion"><i class="fa fa-gear"></i></a>
</h1>
@ -1484,18 +1484,18 @@ export default {
</div>
</div>
</template>
<r-item-teachergrades
v-if='!!value.course.grades && value.course.grades.length > 0'
<r-item-teachergrades
v-if='!!value.course.grades && value.course.grades.length > 0'
v-model='value.course'
:useRequiredGrades="useRequiredGrades"
></r-item-teachergrades>
<r-item-teachercompletion
v-if='!!value.course.completion'
v-if='!!value.course.completion'
v-model='value.course.completion'
:course='value.course'
></r-item-teachercompletion>
</b-modal>
</b-card>
</div>
`,
@ -1526,7 +1526,7 @@ export default {
call([{
methodname: 'local_treestudyplan_include_grade',
args: { 'grade_id': g.id,
'item_id': this.value.id,
'item_id': this.value.id,
'include': newValue,
'required': g.required,
}
@ -1536,21 +1536,21 @@ export default {
call([{
methodname: 'local_treestudyplan_include_grade',
args: { 'grade_id': g.id,
'item_id': this.value.id,
'item_id': this.value.id,
'include': g.selected,
'required': newValue,
}
}])[0].fail(notification.exception);
},
},
},
template: `
<a v-if="value.course.canselectgradables" href='#'
v-b-modal="'r-item-course-config-'+value.id"
<a v-if="value.course.canselectgradables" href='#'
v-b-modal="'r-item-course-config-'+value.id"
@click.prevent.stop=''
><i class='fa fa-cog'></i>
<b-modal v-if='value.course.canselectgradables'
:id="'r-item-course-config-'+value.id"
:title="value.course.displayname + ' - ' + value.course.fullname"
:id="'r-item-course-config-'+value.id"
:title="value.course.displayname + ' - ' + value.course.fullname"
ok-only
scrollable
>
@ -1581,13 +1581,13 @@ export default {
<b-form-checkbox v-if="useRequiredGrades" inline :disabled="!g.selected"
@change="requiredChanged($event,g)" v-model="g.required"
></b-form-checkbox>
<span :title="g.typename" v-html="g.icon"></span><a
<span :title="g.typename" v-html="g.icon"></span><a
:href="g.link" target="_blank">{{g.name}}</a>
<s-edit-mod
<s-edit-mod
:title="value.course.fullname"
@saved="(fd) => g.name = fd.get('name')"
v-if="g.cmid > 0"
:cmid="g.cmid"
v-if="g.cmid > 0"
:cmid="g.cmid"
:coursectxid="value.course.ctxid"
genericonly></s-edit-mod>
</li>
@ -1660,7 +1660,7 @@ export default {
if(grade.grading){
if(grade.grading.ungraded){
return 'ungraded';
}
}
else if(grade.grading.completed_pass || grade.grading.completed || grade.grading.completed_fail) {
if(Number(grade.grading.completed) + Number(grade.grading.completed_pass)
+ Number(grade.grading.completed_fail)
@ -1678,7 +1678,7 @@ export default {
return 'unknown';
}
},
},
template: `
<div>
@ -1688,17 +1688,17 @@ export default {
><a
:href="g.gradinglink"
target="_blank" :title="g.name">{{g.name}}</a>
<s-edit-mod
<s-edit-mod
:title="value.fullname"
@saved="(fd) => g.name = fd.get('name')"
v-if="g.cmid > 0"
:cmid="g.cmid"
v-if="g.cmid > 0"
:cmid="g.cmid"
:coursectxid="value.ctxid"
genericonly></s-edit-mod>
<abbr v-if="useRequiredGrades && g.required" :title="text.required_goal"
:class="'s-required ' + is_grading_needed(g)"
><i class='fa fa-asterisk' ></i
></abbr>
></abbr>
</td>
<td v-if='g.grading'
><i :class="'r-course-grading fa fa-'+grading_icon(g)+' r-graded-'+is_grading_needed(g)"
@ -1795,8 +1795,8 @@ export default {
<td><span v-html='ci.details.criteria'></span>
<a href="#" v-b-tooltip.click
:title="ci.details.requirement"
class='text-info'><i v-if="ci.details.requirement"
class='fa fa-question-circle'
class='text-info'><i v-if="ci.details.requirement"
class='fa fa-question-circle'
></i></a>
</td>
<td>
@ -1805,7 +1805,7 @@ export default {
</tr>
</template>
<tr><td colspan='2' class='pt-2'>
<a target="_blank" :href='completionreport'>{{ text.view_completion_report}}
<a target="_blank" :href='completionreport'>{{ text.view_completion_report}}
<i class='fa fa-external-link'></i></a></td></tr>
</table>
`,
@ -2049,7 +2049,7 @@ export default {
return this.arcpath(begin,this.fraction_completed_fail());
},
arcpath_incomplete() {
const begin = this.fraction_ungraded()
const begin = this.fraction_ungraded()
+ this.fraction_completed()
+ this.fraction_completed_pass()
+ this.fraction_completed_fail();
@ -2106,20 +2106,20 @@ export default {
template: `
<svg width="1em" height="1em" viewBox="0 0 100 100">
<title>{{title}}</title>
<circle cx="50" cy="50" :r="radius"
<circle cx="50" cy="50" :r="radius"
:style="'stroke-width: ' + (stroke*100)+'; stroke: #ccc; fill: none;'"/>
<path :d="arcpath_ungraded"
<path :d="arcpath_ungraded"
:style="'stroke-width: ' + (stroke*100) +'; stroke: var(--warning); fill: none;'"/>
<path :d="arcpath_completed"
<path :d="arcpath_completed"
:style="'stroke-width: ' + (stroke*100) +'; stroke: var(--info); fill: none;'"/>
<path :d="arcpath_completed_pass"
<path :d="arcpath_completed_pass"
:style="'stroke-width: ' + (stroke*100) +'; stroke: var(--success); fill: none;'"/>
<path :d="arcpath_completed_fail"
<path :d="arcpath_completed_fail"
:style="'stroke-width: ' + (stroke*100) +'; stroke: var(--danger); fill: none;'"/>
<circle v-if="disabled" cx="50" cy="50" :r="radius/2"
<circle v-if="disabled" cx="50" cy="50" :r="radius/2"
:style="'fill: var(--dark);'"/>
<circle v-else-if="value.ungraded > 0" cx="50" cy="50" :r="radius/2"
<circle v-else-if="value.ungraded > 0" cx="50" cy="50" :r="radius/2"
:style="'fill: var(--warning);'"/>
</g>
</svg>
@ -2158,7 +2158,7 @@ export default {
},
template: `
<div :class="'r-item-junction r-item-filter completion-'+completion">
<div :class="'r-item-junction r-item-filter completion-'+completion">
<i v-if="value.completion=='incomplete'" class="fa fa-circle-o"></i>
<i v-else-if="value.completion=='failed'" class="fa fa-times-circle"></i>
<i v-else-if="value.completion=='progress'" class="fa fa-exclamation-circle"></i>
@ -2198,9 +2198,9 @@ export default {
methods: {
},
template: `
<div :class="'r-item-finish r-item-filter completion-'+completion">
<div :class="'r-item-finish r-item-filter completion-'+completion">
<i class="fa fa-stop-circle"></i>
</div>
</div>
`,
});
@ -2231,9 +2231,9 @@ export default {
return "incomplete";
}
}
},
},
created(){
},
methods: {
},
@ -2306,19 +2306,19 @@ export default {
},
},
template: `
<div :class="'r-item-badge r-item-filter r-completion-'+completion" v-b-tooltip.hover :title="value.badge.name">
<a v-b-modal="'r-item-badge-details-'+value.id"
<div :class="'r-item-badge r-item-filter r-completion-'+completion" v-b-tooltip.hover :title="value.badge.name">
<a v-b-modal="'r-item-badge-details-'+value.id"
><svg class="r-badge-backdrop " width='50px' height='50px' viewBox="0 0 100 100">
<title>{{value.badge.name}}</title>
<template v-if="teachermode">
<circle cx="50" cy="50" r="44"
<circle cx="50" cy="50" r="44"
style="stroke: #ccc; stroke-width: 8; fill: #ddd; fill-opacity: 0.8;"/>
<path :d="arcpath_issued"
<path :d="arcpath_issued"
:style="'stroke-width: 8; stroke: var(--info); fill: none;'"/>
</template>
<circle v-else-if="value.badge.issued" cx="50" cy="50" r="46"
<circle v-else-if="value.badge.issued" cx="50" cy="50" r="46"
style="stroke: currentcolor; stroke-width: 4; fill: currentcolor; fill-opacity: 0.5;"/>
<circle v-else cx="50" cy="50" r="46"
<circle v-else cx="50" cy="50" r="46"
stroke-dasharray="6 9"
style="stroke: #999; stroke-width: 6; fill: #ddd; fill-opacity: 0.8;"/>
<image class="badge-image" clip-path="circle() fill-box"
@ -2327,8 +2327,8 @@ export default {
</svg></a>
<b-modal
:id="'r-item-badge-details-'+value.id"
:title="value.badge.name"
:id="'r-item-badge-details-'+value.id"
:title="value.badge.name"
size="lg"
ok-only
centered
@ -2376,7 +2376,7 @@ export default {
</b-col></b-row>
</b-container>
</b-modal>
</div>
</div>
`,
});

View File

@ -24,11 +24,11 @@ export const Absolute = {
/** One point. 1pt = 1/72nd of 1in */
pt: 96 / 72
};
// units ->(calc context)-> pixels
export const Relative = {
/**
* Equal to 1% of the height of the viewport
* Equal to 1% of the height of the viewport
* @param {number} count
* @param {object} ctx
*/
@ -36,7 +36,7 @@ export const Absolute = {
return ((ctx ? ctx.viewportHeight : window.innerHeight) / 100) * count;
},
/**
* Equal to 1% of the width of the viewport
* Equal to 1% of the width of the viewport
* @param {number} count
* @param {object} ctx
*/
@ -44,7 +44,7 @@ export const Absolute = {
return ((ctx ? ctx.viewportWidth : window.innerWidth) / 100) * count;
},
/**
* 1/100th of the smallest viewport side
* 1/100th of the smallest viewport side
* @param {number} count
* @param {object} ctx
*/
@ -58,7 +58,7 @@ export const Absolute = {
);
},
/**
* 1/100th of the largest viewport side
* 1/100th of the largest viewport side
* @param {number} count
* @param {object} ctx
*/
@ -72,7 +72,7 @@ export const Absolute = {
);
},
/**
* Represents the font-size of <html> element
* Represents the font-size of <html> element
* @param {number} count
* @param {object} ctx
*/
@ -86,7 +86,7 @@ export const Absolute = {
);
},
/**
* percent of width
* percent of width
* @param {number} count
* @param {object} ctx
*/
@ -94,7 +94,7 @@ export const Absolute = {
return ((ctx ? ctx.width : document.body.clientWidth) / 100) * count;
},
/**
* percent of height
* percent of height
* @param {number} count
* @param {object} ctx
*/
@ -102,59 +102,59 @@ export const Absolute = {
return ((ctx ? ctx.height : document.body.clientHeight) / 100) * count;
}
};
export const Units = {
...Relative,
...Absolute
};
export const UnitRegexpStr = `(?:\\s|^)(\\d*(?:\\.\\d+)?)(${Object.keys(
Units
).join("|")})(?:\\s|$|\\n)`;
export const UnitRegexp = new RegExp(UnitRegexpStr);
export const UnitRegexpGM = new RegExp(UnitRegexpStr, "gm");
/**
*
* @param {*} count
* @param {*} fromUnits
* @param {*} toUnits
* @param {*} ctx
* @returns
*
* @param {*} count
* @param {*} fromUnits
* @param {*} toUnits
* @param {*} ctx
* @returns
*/
export function convert(count, fromUnits, toUnits, ctx = calcCtx()) {
const baseUnit = Units[fromUnits];
const basePx =
typeof baseUnit === "function" ? baseUnit(count, ctx) : baseUnit * count;
const dstUnit = Units[toUnits];
const dstBasePx = typeof dstUnit === "function" ? dstUnit(1, ctx) : dstUnit;
return basePx / dstBasePx;
}
/**
*
* @param {*} expr
* @param {*} toUnits
* @param {*} ctx
* @returns
*
* @param {*} expr
* @param {*} toUnits
* @param {*} ctx
* @returns
*/
export function convertAllInStr(expr, toUnits, ctx = calcCtx()) {
return expr.replace(UnitRegexpGM, (substr, count, unit) => {
return convert(parseFloat(count), unit, toUnits, ctx).toString();
});
}
/**
*
* @param {*} el
* @returns
*
* @param {*} el
* @returns
*/
export function calcCtx(el) {
if (el) {
const rect = el.getBoundingClientRect();
return {
width: rect.width,
height: rect.height,
@ -176,13 +176,13 @@ export const Absolute = {
};
}
}
/**
*
* @param {*} expression
* @param {*} el_ctx
* @param {*} ctx
* @returns
*
* @param {*} expression
* @param {*} el_ctx
* @param {*} ctx
* @returns
*/
export function calc(expression, el_ctx, ctx) {
if (el_ctx === undefined) {ctx = calcCtx(); }
@ -193,7 +193,6 @@ export const Absolute = {
ctx = el_ctx;
}
}
return eval(convertAllInStr(expression, "px", ctx));
}

View File

@ -1,7 +1,7 @@
/*eslint no-console: "off"*/
/*eslint no-trailing-spaces: "off"*/
/*
/*
The MIT License (MIT)
Copyright (c) 2023 P.M. Kuipers
@ -100,10 +100,10 @@ export class SimpleLine {
/**
* Create a new line object
*
*
* @param {HTMLElement|string} start The element the line starts from
* @param {HTMLElement|string} end The element the line ends at
* @param {object} config Configuration for the
* @param {object} config Configuration for the
* @returns {SimpleLine} object
*/
constructor(start, end, config){
@ -159,10 +159,10 @@ export class SimpleLine {
});
});
});
this.mutationObserver.observe(this.start.parentElement, { subtree: false, childList: true });
this.mutationObserver.observe(this.end.parentElement, { subtree: false, childList: true });
// Setup the position checker
this.positionCheck(); // Initialize refresh
@ -175,7 +175,7 @@ export class SimpleLine {
/**
* Change the simpleline config
* @param {object} config The object containing the specs
* @param {object} config The object containing the specs
*/
setConfig(config){
// setup defaults
@ -325,7 +325,7 @@ export class SimpleLine {
/**
* (Re)Paint the arrow
*
*
*/
update(){
if(!this.active){ return;} // don't do this if we are no longer active

File diff suppressed because it is too large Load Diff

View File

@ -67,7 +67,7 @@ export default {
enddate(){
if(this.value.pages[0].enddate){
return format_date(this.value.pages[0].enddate);
}
}
else {
return this.text.noenddate;
}
@ -79,7 +79,7 @@ export default {
}
},
template: `
<b-card
<b-card
:class="'s-studyplan-card timing-' + timing"
>
<template #header></template>
@ -95,7 +95,7 @@ export default {
<span :class="'t-timing-'+timing" v-html="startdate + ' - '+ enddate"></span>
<span class="s-studyplan-card-buttons">
<slot name='footer'></slot>
<b-button style="float:right;" v-if='open' variant='primary'
<b-button style="float:right;" v-if='open' variant='primary'
@click.prevent='onOpenClick($event)'>{{ text.open }}</b-button>
</span>
</template>
@ -123,7 +123,7 @@ export default {
ItemEventBus.$on('headerHeightChange', this.onHeaderHeightChange);
},
computed: {
},
methods: {
onHeaderHeightChange(newheight){

View File

@ -2,7 +2,7 @@ Instructions for downloading and integrating vue-easy-dnd
(Unfortunately we cannot use the latest version, since that requires vue3 support, and bootstrap-vue isn't there yet...)
1. download vue-easy-dnd distribusion throuhg npm
1. download vue-easy-dnd distribusion throuhg npm
npm install vue-easy-dnd@1.22.0
2. Copy node_modules/vue-easy-dnd/dist/vue-easy-dnd.esm.js to amd/src/vue-easy-dnd/
@ -17,7 +17,7 @@ Instructions for downloading and integrating vue-easy-dnd
This version if vue-easy-dnd requires some additional support libraries in the same folder
-- vue-class-component.js --
1. if not already installed during vue-easy-dnd install, download vue-class-component distribusion through npm
1. if not already installed during vue-easy-dnd install, download vue-class-component distribusion through npm
npm install vue-class-component@7.2.6
2. copy node_modules/vue-class-component/dist/vue-class-component.esm-browser.js to amd/src/vue-easy-dnd/vue-class-component.js
@ -31,9 +31,9 @@ This version if vue-easy-dnd requires some additional support libraries in the s
5. Replace both export statements at the bottom of the file by the following:
export { Component, createDecorator, mixins };
-- vue-property-decorator --
1. if not already installed during vue-easy-dnd install, download vue-property-decorator distribusion through npm
1. if not already installed during vue-easy-dnd install, download vue-property-decorator distribusion through npm
npm install vue-property-decorator@8.5.1
2. copy node_modules/vue-property-decorator/lib/vue-property-decorator.js to amd/src/vue-easy-dnd/
@ -47,7 +47,7 @@ This version if vue-easy-dnd requires some additional support libraries in the s
/*eslint no-unused-vars: "off" */
-- reflect-metadata.js --
1. if not already installed during vue-easy-dnd install, download reflect-metadata distribusion through npm
1. if not already installed during vue-easy-dnd install, download reflect-metadata distribusion through npm
npm install reflect-metadata@0.1.13
2. copy node_modules/reflect-metadata/Reflect.js to amd/src/vue-easy-dnd/reflect-metadata.js
@ -56,5 +56,5 @@ This version if vue-easy-dnd requires some additional support libraries in the s
/* eslint-disable */
/*eslint no-unused-vars: "off" */
4. add the following after the copyright notice:
4. add the following after the copyright notice:
export {Reflect};

View File

@ -334,7 +334,7 @@ function performEdgeScroll(event, container, clientX, clientY, edgeSize) {
cancelScrollAction();
return false;
}
// NOTE: Much of the information here, with regard to document dimensions,
// viewport dimensions, and window scrolling is derived from JavaScript.info.
// I am consuming it here primarily as NOTE TO SELF.
@ -345,18 +345,18 @@ function performEdgeScroll(event, container, clientX, clientY, edgeSize) {
// recalculated on window-resize events (for the most part). I am keeping it
// all here in the mousemove event handler to remove as many of the moving
// parts as possible and keep the demo as simple as possible.
// Get the viewport-relative coordinates of the mousemove event.
var rect = container.getBoundingClientRect();
var isBody = container === document.body;
var viewportX = clientX - rect.left;
var viewportY = clientY - rect.top;
if (isBody) {
viewportX = clientX;
viewportY = clientY;
}
// Get the viewport dimensions.
var viewportWidth = rect.width;
var viewportHeight = rect.height;
@ -364,7 +364,7 @@ function performEdgeScroll(event, container, clientX, clientY, edgeSize) {
viewportWidth = document.documentElement.clientWidth;
viewportHeight = document.documentElement.clientHeight;
}
// Next, we need to determine if the mouse is within the "edge" of the
// viewport, which may require scrolling the window. To do this, we need to
// calculate the boundaries of the edge in the viewport (these coordinates
@ -373,22 +373,22 @@ function performEdgeScroll(event, container, clientX, clientY, edgeSize) {
var edgeLeft = edgeSize;
var edgeBottom = ( viewportHeight - edgeSize );
var edgeRight = ( viewportWidth - edgeSize );
var isInLeftEdge = ( viewportX < edgeLeft );
var isInRightEdge = ( viewportX > edgeRight );
var isInTopEdge = ( viewportY < edgeTop );
var isInBottomEdge = ( viewportY > edgeBottom );
// If the mouse is not in the viewport edge, there's no need to calculate
// anything else.
if (!(isInLeftEdge || isInRightEdge || isInTopEdge || isInBottomEdge)) {
cancelScrollAction();
return false;
}
// If we made it this far, the user's mouse is located within the edge of the
// viewport. As such, we need to check to see if scrolling needs to be done.
// Get the document dimensions.
var documentWidth = Math.max(
container.scrollWidth,
@ -400,13 +400,13 @@ function performEdgeScroll(event, container, clientX, clientY, edgeSize) {
container.offsetHeight,
container.clientHeight
);
// Calculate the maximum scroll offset in each direction. Since you can only
// scroll the overflow portion of the document, the maximum represents the
// length of the document that is NOT in the viewport.
var maxScrollX = (documentWidth - viewportWidth);
var maxScrollY = (documentHeight - viewportHeight);
// As we examine the mousemove event, we want to adjust the window scroll in
// immediate response to the event; but, we also want to continue adjusting
// the window scroll if the user rests their mouse in the edge boundary. To
@ -415,12 +415,12 @@ function performEdgeScroll(event, container, clientX, clientY, edgeSize) {
// still be scrolled in a particular direction.
(function checkForWindowScroll() {
cancelScrollAction();
if (adjustWindowScroll()) {
timer = setTimeout( checkForWindowScroll, 30 );
}
})();
// Adjust the window scroll based on the user's mouse position. Returns True
// or False depending on whether or not the window scroll was changed.
function adjustWindowScroll() {
@ -431,27 +431,27 @@ function performEdgeScroll(event, container, clientX, clientY, edgeSize) {
currentScrollX = window.pageXOffset;
currentScrollY = window.pageYOffset;
}
// Determine if the window can be scrolled in any particular direction.
var canScrollUp = (currentScrollY > 0);
var canScrollDown = (currentScrollY < maxScrollY);
var canScrollLeft = (currentScrollX > 0);
var canScrollRight = (currentScrollX < maxScrollX);
// Since we can potentially scroll in two directions at the same time,
// let's keep track of the next scroll, starting with the current scroll.
// Each of these values can then be adjusted independently in the logic
// below.
var nextScrollX = currentScrollX;
var nextScrollY = currentScrollY;
// As we examine the mouse position within the edge, we want to make the
// incremental scroll changes more "intense" the closer that the user
// gets the viewport edge. As such, we'll calculate the percentage that
// the user has made it "through the edge" when calculating the delta.
// Then, that use that percentage to back-off from the "max" step value.
var maxStep = 50;
// Should we scroll left?
if (isInLeftEdge && canScrollLeft) {
var intensity = ((edgeLeft - viewportX) / edgeSize);
@ -462,7 +462,7 @@ function performEdgeScroll(event, container, clientX, clientY, edgeSize) {
var intensity = ((viewportX - edgeRight) / edgeSize);
nextScrollX = (nextScrollX + (maxStep * intensity));
}
// Should we scroll up?
if (isInTopEdge && canScrollUp) {
var intensity = ((edgeTop - viewportY) / edgeSize);
@ -473,14 +473,14 @@ function performEdgeScroll(event, container, clientX, clientY, edgeSize) {
var intensity = ((viewportY - edgeBottom) / edgeSize);
nextScrollY = (nextScrollY + (maxStep * intensity));
}
// Sanitize invalid maximums. An invalid scroll offset won't break the
// subsequent .scrollTo() call; however, it will make it harder to
// determine if the .scrollTo() method should have been called in the
// first place.
nextScrollX = Math.max(0, Math.min(maxScrollX, nextScrollX));
nextScrollY = Math.max(0, Math.min(maxScrollY, nextScrollY));
if (
(nextScrollX !== currentScrollX) ||
(nextScrollY !== currentScrollY)
@ -492,7 +492,7 @@ function performEdgeScroll(event, container, clientX, clientY, edgeSize) {
return false;
}
}
return true
}
@ -1036,9 +1036,9 @@ var __vue_staticRenderFns__ = [];
/* functional template */
var __vue_is_functional_template__ = false;
/* style inject SSR */
var Drag$1 = normalizeComponent_1(
{ render: __vue_render__, staticRenderFns: __vue_staticRenderFns__ },
__vue_inject_styles__,
@ -1291,9 +1291,9 @@ var __vue_staticRenderFns__$1 = [];
/* functional template */
var __vue_is_functional_template__$1 = false;
/* style inject SSR */
var Drop$1 = normalizeComponent_1(
{ render: __vue_render__$1, staticRenderFns: __vue_staticRenderFns__$1 },
__vue_inject_styles__$1,
@ -1349,11 +1349,11 @@ var __vue_staticRenderFns__$2 = [];
/* functional template */
var __vue_is_functional_template__$2 = false;
/* style inject */
/* style inject SSR */
/* style inject SSR */
var DropMask$1 = normalizeComponent_1(
{ render: __vue_render__$2, staticRenderFns: __vue_staticRenderFns__$2 },
__vue_inject_styles__$2,
@ -1531,11 +1531,11 @@ var __vue_staticRenderFns__$3 = [];
/* functional template */
var __vue_is_functional_template__$3 = false;
/* style inject */
/* style inject SSR */
/* style inject SSR */
var DragFeedback$1 = normalizeComponent_1(
{ render: __vue_render__$3, staticRenderFns: __vue_staticRenderFns__$3 },
__vue_inject_styles__$3,
@ -2055,9 +2055,9 @@ var __vue_staticRenderFns__$4 = [];
/* functional template */
var __vue_is_functional_template__$4 = false;
/* style inject SSR */
var DropList$1 = normalizeComponent_1(
{ render: __vue_render__$4, staticRenderFns: __vue_staticRenderFns__$4 },
__vue_inject_styles__$4,

View File

@ -633,7 +633,7 @@
// multiple renders, cloning them avoids errors when DOM manipulations rely
// on their elm reference.
function cloneVNode(vnode) {
var cloned = new VNode(vnode.tag, vnode.data,
var cloned = new VNode(vnode.tag, vnode.data,
// #7975
// clone children array to avoid mutating original in case of cloning
// a child.
@ -2066,7 +2066,7 @@
return data;
}
function resolveScopedSlots(fns, res,
function resolveScopedSlots(fns, res,
// the following are added in 2.6
hasDynamicKeys, contentHashKey) {
res = res || { $stable: !hasDynamicKeys };
@ -4118,7 +4118,7 @@
*/
var Watcher = /** @class */ (function () {
function Watcher(vm, expOrFn, cb, options, isRenderWatcher) {
recordEffectScope(this,
recordEffectScope(this,
// if the active effect scope is manually created (not a component scope),
// prioritize it
activeEffectScope && !activeEffectScope._vm
@ -5025,14 +5025,14 @@
var name = getComponentName(Ctor.options) || tag;
var vnode = new VNode(
// @ts-expect-error
"vue-component-".concat(Ctor.cid).concat(name ? "-".concat(name) : ''), data, undefined, undefined, undefined, context,
"vue-component-".concat(Ctor.cid).concat(name ? "-".concat(name) : ''), data, undefined, undefined, undefined, context,
// @ts-expect-error
{ Ctor: Ctor, propsData: propsData, listeners: listeners, tag: tag, children: children }, asyncFactory);
return vnode;
}
function createComponentInstanceForVnode(
// we know it's MountedComponentVNode but flow doesn't
vnode,
vnode,
// activeInstance in lifecycle state
parent) {
var options = {
@ -7119,7 +7119,7 @@
var oldElm = oldVnode.elm;
var parentElm = nodeOps.parentNode(oldElm);
// create new node
createElm(vnode, insertedVnodeQueue,
createElm(vnode, insertedVnodeQueue,
// extremely rare edge case: do not insert if old element is in a
// leaving transition. Only happens when combining transition +
// keep-alive + HOCs. (#4590)
@ -7495,7 +7495,7 @@
if (c === 0x2f) {
// /
var j = i - 1;
var p
var p
// find first non-whitespace prev char
= void 0;
// find first non-whitespace prev char
@ -8019,7 +8019,7 @@
target.addEventListener(name, handler, supportsPassive ? { capture: capture, passive: passive } : capture);
}
function remove(name, handler, capture, _target) {
(_target || target).removeEventListener(name,
(_target || target).removeEventListener(name,
//@ts-expect-error
handler._wrapper || handler, capture);
}
@ -11018,7 +11018,7 @@
if (!el.plain || (el.pre && maybeComponent)) {
data = genData(el, state);
}
var tag
var tag
// check if this is a component in <script setup>
= void 0;
// check if this is a component in <script setup>

View File

@ -633,7 +633,7 @@
// multiple renders, cloning them avoids errors when DOM manipulations rely
// on their elm reference.
function cloneVNode(vnode) {
var cloned = new VNode(vnode.tag, vnode.data,
var cloned = new VNode(vnode.tag, vnode.data,
// #7975
// clone children array to avoid mutating original in case of cloning
// a child.
@ -2066,7 +2066,7 @@
return data;
}
function resolveScopedSlots(fns, res,
function resolveScopedSlots(fns, res,
// the following are added in 2.6
hasDynamicKeys, contentHashKey) {
res = res || { $stable: !hasDynamicKeys };
@ -4118,7 +4118,7 @@
*/
var Watcher = /** @class */ (function () {
function Watcher(vm, expOrFn, cb, options, isRenderWatcher) {
recordEffectScope(this,
recordEffectScope(this,
// if the active effect scope is manually created (not a component scope),
// prioritize it
activeEffectScope && !activeEffectScope._vm
@ -5025,14 +5025,14 @@
var name = getComponentName(Ctor.options) || tag;
var vnode = new VNode(
// @ts-expect-error
"vue-component-".concat(Ctor.cid).concat(name ? "-".concat(name) : ''), data, undefined, undefined, undefined, context,
"vue-component-".concat(Ctor.cid).concat(name ? "-".concat(name) : ''), data, undefined, undefined, undefined, context,
// @ts-expect-error
{ Ctor: Ctor, propsData: propsData, listeners: listeners, tag: tag, children: children }, asyncFactory);
return vnode;
}
function createComponentInstanceForVnode(
// we know it's MountedComponentVNode but flow doesn't
vnode,
vnode,
// activeInstance in lifecycle state
parent) {
var options = {
@ -7119,7 +7119,7 @@
var oldElm = oldVnode.elm;
var parentElm = nodeOps.parentNode(oldElm);
// create new node
createElm(vnode, insertedVnodeQueue,
createElm(vnode, insertedVnodeQueue,
// extremely rare edge case: do not insert if old element is in a
// leaving transition. Only happens when combining transition +
// keep-alive + HOCs. (#4590)
@ -7495,7 +7495,7 @@
if (c === 0x2f) {
// /
var j = i - 1;
var p
var p
// find first non-whitespace prev char
= void 0;
// find first non-whitespace prev char
@ -8019,7 +8019,7 @@
target.addEventListener(name, handler, supportsPassive ? { capture: capture, passive: passive } : capture);
}
function remove(name, handler, capture, _target) {
(_target || target).removeEventListener(name,
(_target || target).removeEventListener(name,
//@ts-expect-error
handler._wrapper || handler, capture);
}
@ -11018,7 +11018,7 @@
if (!el.plain || (el.pre && maybeComponent)) {
data = genData(el, state);
}
var tag
var tag
// check if this is a component in <script setup>
= void 0;
// check if this is a component in <script setup>

View File

@ -17,7 +17,7 @@ fi
# switch vuejs to build mode
$SCRIPTDIR/vuemode.sh $BUILDMODE
# run the grunt script in the scripts working directory
# run the grunt script in the scripts working directory
# so we can be sure all javascript is properly built from the most recent source
cd $SCRIPTDIR
rm -rf "${SCRIPTDIR}/amd/build/" # clear all files in the amd build dir so we don't have leftover files messing things up

View File

@ -79,12 +79,12 @@ class completionscanner {
/**
* Filter a list of students to return only the students enrolled as student
* in this scanner's course
* @param int[] $studentlist Array of student id's
* @param int[] $studentlist Array of student id's
* @return int[]
*/
private function filter_studentlist(array $studentlist) : array {
$course_students = courseinfo::get_course_students($this->courseid);
return array_intersect($studentlist, $course_students);
$coursestudents = courseinfo::get_course_students($this->courseid);
return array_intersect($studentlist, $coursestudents);
}
/**
@ -166,7 +166,9 @@ class completionscanner {
statistics are marred.
*/
// Get completion info.
$students = isset($studentlist) ? $this->filter_studentlist($studentlist) : courseinfo::get_course_students($this->courseid);
$students = isset($studentlist) ?
$this->filter_studentlist($studentlist) :
courseinfo::get_course_students($this->courseid);
$completed = 0;
$ungraded = 0;
$completedpass = 0;
@ -179,7 +181,7 @@ class completionscanner {
// Retrieve data for this object.
$data = $this->completioninfo->get_data($this->cm, false, $userid);
// If it's an activity completion, add all the relevant activities as sub-items.
$completionstatus = $data->completionstate;
$completionstatus = $data->completionstate;
if ($completionstatus == COMPLETION_COMPLETE_PASS) {
$completedpass++;

View File

@ -481,7 +481,7 @@ class corecompletioninfo {
// Retrieve data for this object.
$data = $this->completion->get_data($cm, false, $userid);
// If it's an activity completion, add all the relevant activities as sub-items.
$completionstatus = $data->completionstate;
$completionstatus = $data->completionstate;
$gradecompletion = $this->completion->get_grade_completion($cm, $userid);
/* To comply with the moodle completion report, only count COMPLETED_PASS as completed if
@ -489,30 +489,33 @@ class corecompletioninfo {
and we want to show similar behaviour. This happens when completion data is reset
in a module
*/
if(!$completion->is_complete() && in_array($gradecompletion, [COMPLETION_COMPLETE, COMPLETION_COMPLETE_PASS])) {
if ( !$completion->is_complete()
&&
in_array($gradecompletion, [COMPLETION_COMPLETE, COMPLETION_COMPLETE_PASS])) {
/* If a passing grade was provided, but the activity was not completed,
* most likely the completion data was erased.
*/
/*$completionstatus = COMPLETION_INCOMPLETE; */
if ( !is_null($cm->completiongradeitemnumber) || ($cm->completionpassgrade) ) {
// Show a warning if this activity has grade completions to help make sense of the completion.
$iinfo["warning"] = get_string("warning_incomplete_pass","local_treestudyplan");
$iinfo["warning"] = get_string("warning_incomplete_pass", "local_treestudyplan");
} else {
// Show a warning if this activity has no grade requirment for completion.
$iinfo["warning"] = get_string("warning_incomplete_nograderq","local_treestudyplan");
$iinfo["warning"] = get_string("warning_incomplete_nograderq", "local_treestudyplan");
}
}
$iinfo['status'] = self::completion_handle($data->completionstate);
$iinfo['status'] = self::completion_handle($data->completionstate);
// Re-evaluate the completed value, to make sure COMPLETE_FAIL doesn't creep in as completed.
if (($data->completionstate == COMPLETION_INCOMPLETE) || ($data->completionstate == COMPLETION_COMPLETE_FAIL)) {
if (($data->completionstate == COMPLETION_INCOMPLETE)
||
($data->completionstate == COMPLETION_COMPLETE_FAIL)) {
$iinfo['completed'] = false;
} else {
$iinfo['completed'] = true;
$progress += 1; // Add a point to the progress counter.
}
// Determine the grade (retrieve from grade item, not from completion).
$grade = $this->get_grade($cm, $userid);
$iinfo['grade'] = $grade->grade;
@ -542,8 +545,7 @@ class corecompletioninfo {
if ($completion->is_complete()) {
$progress += 1; // Add a point to the progress counter.
}
}
else {
} else {
if ($completion->is_complete()) {
$progress += 1; // Add a point to the progress counter.
}
@ -665,7 +667,7 @@ class corecompletioninfo {
// First, let's make sure completion is enabled.
if (!$this->completion->is_enabled()) {
debugging("Completion is not enabled for {$this->course->shortname}",DEBUG_NORMAL);
debugging("Completion is not enabled for {$this->course->shortname}", DEBUG_NORMAL);
return (object)[
'count' => 0,
'completed' => 0,

View File

@ -306,7 +306,7 @@ class courseinfo {
$timing = $this->timing();
if (isset($studyitem)) {
if (isset($studyitem)) {
$numenrolled = $this->count_enrolled_students($studyitem->studyline()->studyplan()->find_linked_userids());
} else {
$numenrolled = 0;
@ -393,7 +393,7 @@ class courseinfo {
public function is_enrolled_student($userid) : bool {
global $CFG;
foreach (explode(', ', $CFG->gradebookroles) as $roleid) {
if (user_has_role_assignment($userid, $roleid, $this->coursecontext->id)) {
if (user_has_role_assignment($userid, $roleid, $this->coursecontext->id)) {
return true;
}
}
@ -407,8 +407,8 @@ class courseinfo {
*/
private function count_enrolled_students(array $userids) {
$count = 0;
foreach($userids as $userid){
if($this->is_enrolled_student($userid)){
foreach ($userids as $userid) {
if ($this->is_enrolled_student($userid)) {
$count++;
}
}
@ -447,7 +447,7 @@ class courseinfo {
$info['grades'][] = $gi->user_model($userid);
}
} else if (isset($this->studyitem)) {
$cc = new corecompletioninfo($this->course,$this->studyitem);
$cc = new corecompletioninfo($this->course, $this->studyitem);
$info['completion'] = $cc->user_model($userid);
}

View File

@ -444,8 +444,7 @@ class courseservice extends \external_api {
/**
* Scan criterium for progress statistics
* @param mixed $criteriaid Id of criterium
* @param mixed $studyplanid Id of studyplan relevant to this criteria
* @param mixed $courseid Id of course this cirteria is related to
* @param mixed $studyitemid Id of studyplan relevant to this criteria
* @return array
*/
public static function scan_completion_progress($criteriaid, $studyitemid) {

View File

@ -263,13 +263,13 @@ class bistate_aggregator extends \local_treestudyplan\aggregator {
// - completion::INCOMPLETE - All incoming states are incomplete.
// - completion::PROGRESS - All other states.
$method = strtoupper($studyitem->conditions()); // One of ANY or ALL.
$method = strtoupper($studyitem->conditions()); // One of ANY or ALL.
// First count all states.
$statecount = completion::count_states($completion);
$total = count($completion);
if($method == "ANY"){
if ($method == "ANY") {
if ( $statecount[completion::EXCELLENT] >= 1 ) {
return completion::EXCELLENT;
} else if ( $statecount[completion::GOOD] >= 1 ) {
@ -283,7 +283,7 @@ class bistate_aggregator extends \local_treestudyplan\aggregator {
} else {
return completion::INCOMPLETE;
}
} else { /* ALL (default) */
} else { /* default value of ALL */
if ( $total == $statecount[completion::EXCELLENT]) {
return completion::EXCELLENT;
} else if ( $total == ( $statecount[completion::EXCELLENT]

View File

@ -203,13 +203,13 @@ class core_aggregator extends \local_treestudyplan\aggregator {
// - completion::INCOMPLETE - All incoming states are incomplete.
// - completion::PROGRESS - All other states.
$method = strtoupper($studyitem->conditions()); // One of ANY or ALL.
$method = strtoupper($studyitem->conditions()); // One of ANY or ALL.
// First count all states.
$statecount = completion::count_states($completion);
$total = count($completion);
if($method == "ANY"){
if ($method == "ANY") {
if ( $statecount[completion::EXCELLENT] >= 1 ) {
return completion::EXCELLENT;
} else if ( $statecount[completion::GOOD] >= 1 ) {
@ -223,7 +223,7 @@ class core_aggregator extends \local_treestudyplan\aggregator {
} else {
return completion::INCOMPLETE;
}
} else { /* ALL (default) */
} else { /* default value of ALL */
if ( $total == $statecount[completion::EXCELLENT]) {
return completion::EXCELLENT;
} else if ( $total == ( $statecount[completion::EXCELLENT]

View File

@ -312,7 +312,7 @@ class period {
// Reload record after edit and ensure end dates of previous period are adjusted if needed.
$this->r = $DB->get_record(self::TABLE, ['id' => $this->id], "*", MUST_EXIST);
// Adjust end date of previous period if needed
// Adjust end date of previous period if needed.
if (isset($prev) && !empty($fields['startdate'])) {
$maxdate = $this->startdate()->sub(new DateInterval("P1D")); // Subtract 1 day, since periods include the end day.
$rqdate = $prev->enddate();
@ -321,7 +321,7 @@ class period {
}
}
// Adjust start date of next period if needed
// Adjust start date of next period if needed.
if (isset($next) && !empty($fields['enddate'])) {
$mindate = $this->enddate()->add(new DateInterval("P1D")); // Subtract 1 day, since periods include the end day.
$rqdate = $next->startdate();

View File

@ -1471,7 +1471,7 @@ class studyplanservice extends \external_api {
*/
public static function get_period($id) {
$p = period::find_by_id($id);
// Public data - no rights check needed
// Public data - no rights check needed.
\external_api::validate_context($p->page()->studyplan()->context());
return $p->model();
}

View File

@ -27,7 +27,7 @@ require_once($CFG->libdir.'/weblib.php');
$systemcontext = context_system::instance();
$PAGE->set_url("/local/treestudyplan/doc.php", array());
if($CFG->debugdeveloper){
if ($CFG->debugdeveloper) {
$PAGE->requires->css(new moodle_url($CFG->wwwroot.'/local/treestudyplan/css/devstyles.css'));
}
$PAGE->set_pagelayout('base');

View File

@ -18,12 +18,12 @@
<h3>Course manipulation</h3>
<p><b>Cascade cohort sync</b> can be used to manually enrol cohorts and users linked to the study plan into the courses that are in the studyplan.
If you do not have automatic cascading enabled, you must use this button to do it manually</p>
<p>It will create a cohort-sync enrolment to all courses in the studyplan for each cohort you have linked here.
<p>It will create a cohort-sync enrolment to all courses in the studyplan for each cohort you have linked here.
It can also automatically remove those enrolments when a cohort is unlinked from a studyplan.</br>
Individually linked students can also be cascaded down to the courses (if the feature is enabled). In those cases a manual enrolment is created for the users
in each of the studyplan's courses. Those enrolments will never be automatically removed, since that might deprive a student of study content.
</p>
<p><b>Force scales</b> Only useful in <i>Manual</i> aggregation mode. In those cases you may want to enforce a specific scale to all selected gradables in
<p><b>Force scales</b> Only useful in <i>Manual</i> aggregation mode. In those cases you may want to enforce a specific scale to all selected gradables in
the studyplan. This can save time manually updating all the gradables used in the studyplan to a standerdized grade scale. This tool allows you to do so.</p>
<p></p>
<h3>Backup and restore</h3>
@ -32,12 +32,12 @@
<p>Use <b>restore studylines from backup</b> to import the content (study lines and courses) from a studyplan into the current study plan</p>
<div class="generalbox alert alert-success"><b>PRO TIP:</b><br>
<p>If you are proficient in editing json like data structures, you can manually edit the .json backups and then restore them as new courses.</p>
<p>This is mostly useful if you need to create multiple study plans with only minor changes, or predictable changes
<p>This is mostly useful if you need to create multiple study plans with only minor changes, or predictable changes
(like changing some text in course short names to link to new versions of courses)</p>
</div>
<h3>Deleting a studyplan</h3>
<p>Sometimes you want to delete an entire study plan. This can be done using the button found in the <b>delete</b> tab</p>
<h2>Setting a simple name for the courses</h2>
<p>By default, the short name of the course will be displayed in the study plan. However, often this name is quite long and has some
prefixes or suffixes to distinguish it from other courses that were given in earlier academic years. If you want to display a simpler name,
@ -52,22 +52,22 @@
<a id="gradescale"><h2>Grade & Scale interpretation</h2></a>
<div class="generalbox alert alert-info"><b>NOTE:</b><br>
<p>The information in this category is mostly applicable to <i>manual</i> aggregation methods for study plans.
<p>The information in this category is mostly applicable to <i>manual</i> aggregation methods for study plans.
If <i>Moodle course completion</i> is used, it is especially important to always configure <b>grade to pass</b></p>
<p>Note that future versions may use this feature to automatically repair missing <b>grade to pass</b> in gradable activities.</p>
</div>
<p>Best practice is to set a <b>grade to pass</b> in all gradable activities in the studyplan. This way the entire system will know
if a gradable activity wass passed or failed.</p>
<p>But since teachers are all human, this may occasionally (or regularly) be forgotten. In the absence of a configured <b>grade to pass</b>,
the studyplan system will use the values configured here to determine if a grade was a passable grade.</p>
the studyplan system will use the values configured here to determine if a grade was a passable grade.</p>
<p>Go to <b>Site administration</b> -> <b>Courses</b> -> <b>Studyplans</b> -> <b>Configure grade &amp; scale interpretation</b> to configure it.</p>
<p>Each scale will need to have a <b>completion threshold</b> set. This is the scale value that starts a passing grade.</p>
<p>You can also configure certain <b>maximum grade points</b> to have a default passing value (<b>completion threshold</b>)</p>
<p>While scales are automatically added and cannot be removed, you can remove previously configured <i>maximum grade points</i>.
<p>While scales are automatically added and cannot be removed, you can remove previously configured <i>maximum grade points</i>.
To do so, check the <b>delete</b> mark next to it, and press <b>save</b> </p>
<h2>Accessing studyplans in context</h2>
<p>The studyplan plugin is category context aware - meaning that every studyplan is placed in a context. And users can have rights to view and/or edit
<p>The studyplan plugin is category context aware - meaning that every studyplan is placed in a context. And users can have rights to view and/or edit
studyplans within a specific context. </p>
<p>To edit a studyplan in a specific context, you can use one or two ways:</p>
<ol>
@ -83,14 +83,14 @@
<p> Viewing the studyplans of students is also a right that is context-aware. (Right <i>local/treestudyplan:viewuserreports</i>)</p>
<p> Both methods outlined above also work for viewing student studyplans. The main difference is that the drop-down list on the top-left of the page
only shows the categories that actually have studyplans that are viewable for the current user. (Which means that the user can not only see the study plan
itself, but also the results of specific users). It is recommended to give this right to all teachers in a specific category, unless you have specific reasons
itself, but also the results of specific users). It is recommended to give this right to all teachers in a specific category, unless you have specific reasons
why teachers might not be allowed to see all results of the students.
</p>
<p><img src="img/Contexts-View.png" class="border border-primary rounded-lg ml-3"></p>
<h2>Block MyStudyplan</h2>
<p>Using the block <i>My Studyplan</i> you can show students the studyplans they are enrolled in on the dashboard page. This may be useful for navigation.</p>
<p>This block shows <i>teachers</i> another view - namely all studyplans they are currently teaching courses in (orderd in tabs). For teachers, this view will often take a
<p>This block shows <i>teachers</i> another view - namely all studyplans they are currently teaching courses in (orderd in tabs). For teachers, this view will often take a
few seconds to load, especialy if they teach in multiple study plans, since the result statistics for each course are collected each time</p>
<p>In spite of the loading time, this view can be beneficial to teachers, since it can easily show them the statistics for the courses they are teaching</p>
@ -134,14 +134,14 @@
<td></td>
<td>manager</td>
<td>System and/or category</td>
</tr>
</tr>
</table>
<p>While the studyplan management rights are added by default to the <b>manager</b> role, the right to just view the studyplans for teachers and students is not
<p>While the studyplan management rights are added by default to the <b>manager</b> role, the right to just view the studyplans for teachers and students is not
added to any default role. You should make a separate role for faculty that is allowed to view the study plans (or integrate it in an existing role),
grant the roght <i>local/treestudyplan:viewuserreports</i> to that role, and assign the role to all users that should have this right in a given context.</p>
</div>

View File

@ -16,12 +16,12 @@
<p>Basic configuration options for the studyplan plug-in</p>
<ul>
<li><b>Default aggregation style</b><br>
Select which aggregation style is selected by default upon creation of a new study plan. Setting this according to your preferences, will save you a
Select which aggregation style is selected by default upon creation of a new study plan. Setting this according to your preferences, will save you a
few clicks when creating a lot of new study plans.
</li>
<li><b>Course display name</b><br>
Select which course field is used as a short course name in the studyplan overview. You can choose between <i>shortname</i>, <i>ID</i> and
any custom course field that you already have configured. It is recommended to create a custom course field specifically for the display name in
Select which course field is used as a short course name in the studyplan overview. You can choose between <i>shortname</i>, <i>ID</i> and
any custom course field that you already have configured. It is recommended to create a custom course field specifically for the display name in
the studyplan, and put the appropriate text there.<br>
<i>Shortname</i> is often not useful in the studyplan (especially on larger sites), since it requires prefixes/suffixes to ensure unique names.
</li>
@ -31,17 +31,17 @@
If you only use the <i>Moodle Course completion</i> method, you can safely ignore these parameters.
</li>
</ul>
<h3>Manage study plans</h3>
<p>This option is a direct link to the <b>manage studyplans</b> page. It may be useful for administrators.</p>
<h3>Studyplan cohort sync</h3>
<p>Here you can configure automatic cohort syncing. This syncronizes the cohorts and students linked with a study plan to enrolment
<p>Here you can configure automatic cohort syncing. This syncronizes the cohorts and students linked with a study plan to enrolment
within the courses in the study plan </p>
<p>Cohort syncing is done by creating a <i>cohort sync</i> enrolment method in each course, while students are synced by adding a <i>manual enrolment</i></p>
<ul>
<li><b>Automatic Cohort sync</b><br>
Enable automatic cohort sync here. If enabled, a background process will automatically perform a synchronization each time a cohort is added or removed,
Enable automatic cohort sync here. If enabled, a background process will automatically perform a synchronization each time a cohort is added or removed,
and each time a course is added.
</li>
<li><b>Automatic deletion</b><br>
@ -49,7 +49,7 @@
cohort to be enrolled. Note that individual student enrolments are never automatically deleted.
</li>
<li><b>Remember existing cohort syncs</b><br>
Since there can only be one <i>cohort-sync</i> enrolment for each combination of course and cohort, existing cohort-sync enrolments are marked with the studyplans
Since there can only be one <i>cohort-sync</i> enrolment for each combination of course and cohort, existing cohort-sync enrolments are marked with the studyplans
involved when a studyplan wants them as well. By checking this item, such existing cohort syncs are marked as <i>manual</i>, so they will not be automatically removed
when no study plans require them anymore
</li>
@ -58,7 +58,7 @@
cohort.
</li>
<li><b>Enrol linked users</b><br>
You can choose to include linked users in the synchronization or not. Default is yes, since it would be unexpected behaviour if cohorts were automatically synced,
You can choose to include linked users in the synchronization or not. Default is yes, since it would be unexpected behaviour if cohorts were automatically synced,
but users were not.
</li>
<li><b>Role</b><br>
@ -71,15 +71,15 @@
<h2>Theming guide</h2>
<p>A number of colors in the studyplan plugin are based on the colors you define in your theme for
<b class="text-primary">primary (brand color)</b>,
<b class="text-secondary">secondary</b>,
<b class="text-success">success</b>,
<b class="text-info">info</b>,
<p>A number of colors in the studyplan plugin are based on the colors you define in your theme for
<b class="text-primary">primary (brand color)</b>,
<b class="text-secondary">secondary</b>,
<b class="text-success">success</b>,
<b class="text-info">info</b>,
<b class="text-warning">warning</b> and
<b class="text-danger">danger</b>
</p>
<p>You may be able to define them in your theme's configuration. If not, follow the instructions below for <b>tweaking other colors</b>
<p>You may be able to define them in your theme's configuration. If not, follow the instructions below for <b>tweaking other colors</b>
and use the following code as a starting point. (The values are the default boost theme colors)</p>
<pre style="max-width: 30em;" class="p-3 border border-primary rounded-lg ml-3">
:root {
@ -91,8 +91,8 @@
--danger: #dc3545;
--light: #f8f9fa;
--dark: #343a40;
}</pre>
}</pre>
<h3>Tweaking other colors</h3>
<p>There are some other custom css variables which you can tweak to change the styling. An overview:</p>
<table>
@ -121,7 +121,7 @@
--pending: var(--gray);
--incomplete: var(--gray);
}</pre>
</div>
</body>

View File

@ -20,9 +20,9 @@
<p><img src="img/Newstudyplanscreen.png" class="border border-primary rounded-lg ml-3"></p>
<p>The screen allows you to specify the <b>full name</b>, <b>code</b> or short name and <b>description</b> for this specific plan</p>
<p>You can also choose the category <b>context</b> in which to place this study plan. This is useful to manage access rights in a large moodle installation</p>
<p>The next options give more information about the timing of the plan. The <b>number of slots</b> to place courses in the plan can be specified.
<p>The next options give more information about the timing of the plan. The <b>number of slots</b> to place courses in the plan can be specified.
This is usually used to show the different periods in the academic year. You can expect this to become more explicit in upcoming versions of the plugin.
The <b>start date</b> and <b>end date</b> of the plan help to show students and teachers which plans are current, and which were finished in the past.
The <b>start date</b> and <b>end date</b> of the plan help to show students and teachers which plans are current, and which were finished in the past.
They will default to start on august 1 of this year and continue until august 1 of next year, since that matches most academic years.</p>
<p>Finally we have the <b>aggregation style</b>, which is ised to determine how the courses show grades. There are a number of options available:</p>
<ul>
@ -34,17 +34,17 @@
<p>
With this aggregation method you manually select which grade items are used in the progress report. The design intent is to prepare a number of gradable items,
and describe the learning objectives for each course and use these to indicate completion. While any grade will be properly shown, it makes most sense to use a scale which indicates
if the student has either met the objective or not. The teacher or studyplan editor can also mark certain gradables/objectives as required or compulsory.
if the student has either met the objective or not. The teacher or studyplan editor can also mark certain gradables/objectives as required or compulsory.
</p>
<p>The final outcome for the courses will then be determined based on the percentage of met objectives, provided that all required/compulsory objectives are met</p>
<p><img src="img/Newstudyplanscreen.png" class="border border-primary rounded-lg ml-3"></p>
<p>The added options allow you to set the <b>threshold</b> percentage of completed objectives in each course to determine the outcome as <i>excellent</i>, <i>good</i>
or <i>completed</i>. If an objective is not met it will be marked as either "progress" (meaning that the student's work needs to be improved) or
or <i>completed</i>. If an objective is not met it will be marked as either "progress" (meaning that the student's work needs to be improved) or
"incomplete" (meaning that the student has not started working on this goal yet) </p>
<p>When <i>failed</i> results are enabled, any "progress" will be marked as "failed" when the course end date has passed</p>
<p>Finally, you can opt to have an object ive </p>
<a id="studylines"><h2>Setting up studylines</h2></a>
<p>Before you can add courses to the studyplan, you have to define <i>study lines</i>. You use them to group together courses in logical categories.
<p>Before you can add courses to the studyplan, you have to define <i>study lines</i>. You use them to group together courses in logical categories.
Most likely these are categories or themes within your curriculum.</p>
<p>For performance reasons, study lines can only be edited when in <i>study line edit mode</i>. The courses and flow between them will be hidden,
so the lines can be reshuffled and edited. You can toggle studyline edit mode with the toggle switch <i>edit stydy lines</i></p>
@ -57,14 +57,14 @@
<p>Using the <i class="fa fa-pencil"></i> and <i class="fa fa-trash"></i> symbols you can edit and delete the studyline (only empty studylines can be deleted)</p>
<p><img src="img/Studylines-view.png" class="border border-primary rounded-lg ml-3"></p>
<div class="generalbox alert alert-success"><b>TIP:</b><br>
If you have many studyplans using the same study line make-up you can export the first studyplan after you set up the study lines, and then import it on the next
If you have many studyplans using the same study line make-up you can export the first studyplan after you set up the study lines, and then import it on the next
study plans.<br>(Use <b>Advanced</b> -> <b>Backup studyplan</b> to find the <i>backup</i> and <i>restore</i> features)</div>
<a id="studycourses"><h2>Adding courses</h2></a>
<p>To add courses, you need to exit <i>study line edit mode</i> and open the toolbox using the <button class="btn btn-primary">Open toolbox</button> button.
<p>To add courses, you need to exit <i>study line edit mode</i> and open the toolbox using the <button class="btn btn-primary">Open toolbox</button> button.
The toolbox will now open on the right of the screen. (Using the toggle button on top, you can also switch it to the left of the screen if needed)</p>
<p><img src="img/Toolbox-Courses.png" class="border border-primary rounded-lg ml-3"></p>
<p>The toolbox shows you all the categories you have access to and the courses in them. Simply drag the course you want to include from the toolbox and
<p>The toolbox shows you all the categories you have access to and the courses in them. Simply drag the course you want to include from the toolbox and
drop it in one of the slots in the studyplan that now appear.</p>
<p><img src="img/Toolbox-dragcourse.png" class="border border-primary rounded-lg ml-3"></p>
<p>To re-arrange the courses in the study plan, simply drag and drop them from the places they are now in.</p>
@ -77,17 +77,17 @@
<p><img src="img/Studyplan-draglayers.png" class="border border-primary rounded-lg ml-3"></p>
<a id="periods"><h2>Configuring periods</h2></a>
All periods in the study plan will have a label showing the timing and it's short name directly above their course columns in the
All periods in the study plan will have a label showing the timing and it's short name directly above their course columns in the
timeline. Upon hovering over the short name, it's full name will appear.<br>
Also, the period matching the current date, will be highlighted in the color
indicated by the <pre style="display:inline;">--highlight</pre> css variable, which corresponds to the
value of the <pre style="display:inline;">--info</pre> color by default.
<p></p>
<p><img src="img/Periods-in-plan.png" class="border border-primary rounded-lg ml-3"></p>
<p><img src="img/Period-hover.png" class="border border-primary rounded-lg ml-3"></p>
<p> At the start, the periods start and end times will be naively selected by splitting the study plan's duration among the number of periods.<br>
By clicking on the <i class="text-primary fa fa-pencil"></i> icon, you can open the edit screen and change the period's <i>full name</i>,
By clicking on the <i class="text-primary fa fa-pencil"></i> icon, you can open the edit screen and change the period's <i>full name</i>,
<i>short name</i>, <i>start time</i> and <i>end time</i>
</p>
<p><img src="img/Period-edit.png" class="border border-primary rounded-lg ml-3"></p>
@ -97,26 +97,26 @@
</div>
<a id="studycourses"><h2>Configuring courses</h2></a>
<p>Once a course is added to the studyplan, an icon shows if it needs to be configured (<i class='fa fa-exclamation-circle text-warning'></i>) or
<p>Once a course is added to the studyplan, an icon shows if it needs to be configured (<i class='fa fa-exclamation-circle text-warning'></i>) or
is already configured (<i class='fa fa-check text-success'></i>).</p>
<p>By clicking on the course title (it's shortname, or a configured custom field), you can view the details, and configure it where applicable.
The configuration is dependent on the aggregation method for the study plan:</p>
<h3>Moodle course completion</h3>
<p>When using moodle course completion as a base, all configuration is done in the <i>course's completion settings</i>.
<p>When using moodle course completion as a base, all configuration is done in the <i>course's completion settings</i>.
The course details show a <i class='fa fa-gear primary'></i> gear icon in the title, which takes you directly to the course completion configuration.
Once it is configured, the course details will show you exactly how the course completion is configured</p>
<p><img src="img/Course-config-corecompletion.png" class="border border-primary rounded-lg ml-3"></p>
<h3>Manual: Completed + required goals</h3>
<p> In this mode, the course details show you all the gradable activities in the course. You need to select which of them will be used to determine progress
and will be shown to the students and teachers in the studyplan view. You can also mark gradables ad <i>required</i>. Gradable activities marked as
and will be shown to the students and teachers in the studyplan view. You can also mark gradables ad <i>required</i>. Gradable activities marked as
such will all need to be completed for the course to be marked as completed, regardless of the minimum percentage configured</p>
<p><img src="img/Course-config-bistate.png" class="border border-primary rounded-lg ml-3"></p>
<h3>Course start and end times</h3>
<p> Often, the course will have their start and end times configured differently than the period you drop them in. If this is the case,
a screen will appear asking if you want to update the course's start and end times. The time details of both course and period will
be shown for your comparison. By clicking <button class="btn btn-danger">Yes</button>, the course's start and end times will be
be shown for your comparison. By clicking <button class="btn btn-danger">Yes</button>, the course's start and end times will be
updated to those of the period. Any activity dates set will be adjusted according to the changed start date.</p>
<p><img src="img/Course-timing-mismatch-screen.png" class="border border-primary rounded-lg ml-3"></p>
<div class="generalbox alert alert-info"><b>NOTE:</b><br>
@ -132,7 +132,7 @@
Do note however, that the available spans are limited by the available space to the right of the course in the studyplan</p>
<p><img src="img/Course-timing-indicators.png" class="border border-primary rounded-lg ml-3"></p>
<p> Upon selecting the span you want, the course display will immediately be updated to reflect the changes.<br>
immediately after this, you will see the screen asking if you want to change the course start and end times to reflect the
immediately after this, you will see the screen asking if you want to change the course start and end times to reflect the
new period span.</p>
<a id="studybadges"><h2>Adding badges</h2></a>
@ -142,7 +142,7 @@
<a id="flow"><h2>Drawing the flow</h2></a>
<p>You can draw arrows between the courses and add flow indicators to show students how different courses are related in the studyplan</p>
<div class="generalbox alert alert-success"><b>TIP:</b><br>
While there are many possible relations between courses, it is best to limit the arrows to the most obvious relations. The studyplan can
While there are many possible relations between courses, it is best to limit the arrows to the most obvious relations. The studyplan can
easily become too crowded to give a good overview. Use <i>junctions</i> to combine multiple arrows into one if you do need to show the relation
from many courses to a few.</div>
<h3>Drawing arrows</h3>
@ -152,7 +152,7 @@
</p>
<p><img src="img/Arrows-drag.png" class="border border-primary rounded-lg ml-3"></p>
<h3>Removing arrows</h3>
<p> To remove an arrow, click on the green square where the arrow starts. A red <i class='fa fa-trash text-danger'></i> trash can icon will appear for
<p> To remove an arrow, click on the green square where the arrow starts. A red <i class='fa fa-trash text-danger'></i> trash can icon will appear for
each line that starts there. Hover over the trash can icons to find the line you want to remove. The arrow for each trash can icon will be highlighted in red
once you hover over it with your cursor. Once you find the arrow you want to remove, click the trash icon.</p>
<p> To close the trash icon menu without removing an arrow, click the green square (now red) again.</p>
@ -165,7 +165,7 @@
<li><b>Start/continue</b> These indicators are always green and can be used to indicate the start of a series of courses</li>
</ul>
<p><img src="img/Toolbox-flow.png" class="border border-primary rounded-lg ml-3"></p>
<p> By clicking on the <i class="fa fa-gear"></i> icon in the junction and finish indicators, you can select how the incoming arrows will be combined.
<p> By clicking on the <i class="fa fa-gear"></i> icon in the junction and finish indicators, you can select how the incoming arrows will be combined.
The indicated numer of arrows need to be completed in order to show the indicator as completed</p>
<ul>
<li><b>All entries</b></li>
@ -181,7 +181,7 @@
<p>The recommended way is association by cohort, since that allows to dynamically allocate users to courses and studyplans by allocatong them to cohorts.
Assigning students to a studyplan individually is only useful in some fringe cases.
</p>
<p>To edit associations, click on the <span class="text-primary"><i class="fa fa-users"></i> associations</span> link in the studyplan,
<p>To edit associations, click on the <span class="text-primary"><i class="fa fa-users"></i> associations</span> link in the studyplan,
or click on the <i class="fa fa-users text-primary"></i> icon in the studyplan overview.</p>
<p>You will now get the association window</p>
<p><img src="img/Cohort-link.png" class="border border-primary rounded-lg ml-3"></p>
@ -192,14 +192,14 @@
<p>In order to link <i>individual students</i>, click <i>students</i> and follow the same procedure as lined out above for cohorts.</p>
<div class="generalbox alert alert-warning"><b>IMPORTANT:</b><br>
<p>If you have cascading cohort sync enabled (see configuration), the system will automatically create a cohort-sync enrolment to all courses in the studyplan
<p>If you have cascading cohort sync enabled (see configuration), the system will automatically create a cohort-sync enrolment to all courses in the studyplan
for each cohort you have linked here. It can also automatically remove those enrolments when a cohort is unlinked from a studyplan.
</p>
<p>Individually linked students can also be cascaded down to the courses (if the feature is enabled). In those cases a manual enrolment is created for the users
in each of the studyplan's courses. Those enrolments will never be automatically removed, since that might deprive a student of study content.
</p>
</div>
</div>
</body>

View File

@ -19,7 +19,7 @@
<a id="#postinstall"><h2>Configuration post install</h2></a>
<p>The studyplan plugin will automatically add links to the flat navigation bat used in Moodle 3.11 and below<p>
<p>However, Moodle 4.0-4.2 use a new "primary navigation" bar, and does not yet support customizing the primary navigation bar (it is a planned feature though)</p>
<p><strong>Use the following workaround:</strong></p>
<p>You can add custom primary menu items in <b>Site administration</b> -> <b>Appearance</b>-> <b>Theme settings</b>
That page contains an item called Custom menu items". Add the following into that area to custimize it
@ -34,7 +34,7 @@ Manage Study plans|/local/treestudyplan/edit-plan.php||en
</pre></p>
<p> The studyplan plugin wil automatically hide the primary navigation items that are not applicable for the current user.</p>
<h2>Roles post install</h2>
<p>While the studyplan management rights are added by default to the <b>manager</b> role, the right to just view the studyplans for teachers and students is not
<p>While the studyplan management rights are added by default to the <b>manager</b> role, the right to just view the studyplans for teachers and students is not
added to any default role. You should make a separate role for faculty that is allowed to view the study plans (or integrate it in an existing role),
grant the roght <i>local/treestudyplan:viewuserreports</i> to that role, and assign the role to all users that should have this right in a given context.</p>
<p>See <a href="advanced.htm#rights"><b>Advanced</b> -> <b>Roles and permissions</b></a> for more details</p>
@ -62,7 +62,7 @@ Manage Study plans|/local/treestudyplan/edit-plan.php||en
</div>
</li>
</ul>
<p> Then assign the role <b>studyplanmanager</b> in a specific category context, or the system context to all users who should be able to create and edit studyplans
<p> Then assign the role <b>studyplanmanager</b> in a specific category context, or the system context to all users who should be able to create and edit studyplans
in that specific context</p>
<p> Assign the role <b>studyplanviewer</b> to all users who should be able to view the teacher view of the studyplan and view the specific results of users associated
with that studyplan. </p>

View File

@ -31,7 +31,7 @@
<p>The colors can be tweaked by changing the CSS vars <i>--past</i>, <i>--present</i> and <i>--future</i></p>
<p>These same colors are also used to indicate the timing status of courses in the studyplan view itself</p>
</div>
<h2>Statistics view</h2>
<p>The statistics view shows the studyplan in its entirety, but shows the completion statistics for each course or badge</p>
<p><img src="img/Teacherview-statistics-badge-course.png" class="border border-primary rounded-lg ml-3"></p>
@ -64,7 +64,7 @@
<p>When using a manual aggregation method instead of the course completion method, the view is slightly different.</p>
<p>Here, only the gradable activities for this course are shown.</p>
<p><img src="img/Teacherview-course-manual.png" class="border border-primary rounded-lg ml-3"></p>
<h2>Badge details</h2>
<p>By clicking on the badge, more information about it are shown, like <b>description</b>, <b>award criteria</b> and a link to the <b>badge details</b> </p>
<p> A progress bar is also shown, indicating how many students in the study plan have earned this badge</p>
@ -79,7 +79,7 @@
<p> This opens a drawer to the right of the screen, where the teacher can select any of the students linked to this study plan and view their results</p>
<p>This view is identical to the view that the students themselves see when viewing their study plan.</p>
<p><img src="img/Teacherview-choose-student.png" class="border border-primary rounded-lg ml-3"></p>
<p>By selecting <button type="button" class="list-group-item list-group-item-primary list-group-item-action">Teacher view</button>, the teacher can
<p>By selecting <button type="button" class="list-group-item list-group-item-primary list-group-item-action">Teacher view</button>, the teacher can
return to the teacher view</p>
</div>

View File

@ -35,7 +35,7 @@
<td>Completed Passed</td>
<td class="text-center"><i title="Completed" style="float: none;" class="r-course-result fa fa-check-circle r-completion-completed"></i></td>
<td>Bootstrap color variable <i>--success</i></td>
</tr>
</tr>
<tr>
<td>Good</td>
<td class="text-center"><i title="Good" style="float: none;" class="r-course-result fa fa-check-circle r-completion-good"></i></td>
@ -57,12 +57,12 @@
<td>Failed</td>
<td class="text-center"><i title="In progress" style="float: none;" class="r-course-result fa fa-times-circle r-completion-failed"></i></td>
<td>Bootstrap color variable <i>--danger</i></td>
</tr>
</tr>
<tr>
<td>Not started</td>
<td class="text-center"><i title="Not started" style="float: none;" class="r-course-result fa fa-circle-o r-completion-incomplete"></i></td>
<td>Color variable <i>--incomplete</i></td>
</tr>
</tr>
</table>
<br>
<p> As you can see, the used colors depend on your theme. The configured colors for <i>success</i>, <i>warning</i>, <i>danger</i> and <i>info</i> are all
@ -71,13 +71,13 @@
</p>
<p>The colored tabs in front of each course show if the course is currently given (blue), in the past (purple) or in the future (grey)</p>
<p> By coloring the arrows in the color of the progress, both students and teachers can quickly see a student's progress in a course</p>
<p> Study plans that use the <i>Moodle course completion</i> aggregation method show a small circle that shows the progress percentage of all requirements,
<p> Study plans that use the <i>Moodle course completion</i> aggregation method show a small circle that shows the progress percentage of all requirements,
instead of an icon</p>
<h2>Course detail view</h2>
By clicking on a course, a student can view more details about this course.
<p>By clicking on the <b>course title</b>, or any of the <b>activities</b> the student is directly sent to the course or activity page, in a new tab.</p>
<h3>Moodle course completion view</h3>
<p><img src="img/Student-coursedetails-corecompletion.png" class="border border-primary rounded-lg ml-3"></p>
<p>Per completion requirement, it is shown whether or not the student has <b>completed</b>, <b>passed</b> or <b>failed</b> the requirement.</p>
@ -92,7 +92,7 @@
<p><img src="img/Student-coursedetails-manual.png" class="border border-primary rounded-lg ml-3"></p>
<p>Per selected activity, it is indicated if the student has completed this activity or not</p>
<p>Until the course end date has passed, the end status is marked as <b>progress</b> as long as one or more requirements are met or graded.
<i>After</i> the course end date has passed, ther course is indicated as <b>failed</b> if the completion requirements are not met
<i>After</i> the course end date has passed, ther course is indicated as <b>failed</b> if the completion requirements are not met
<b>and the failed option is enabled</b> in the studplan configuration</b></p>
<p>If a teacher left feedback for an activity, the link <i>View feedback</i> is shownand shows the specific feedback upon clicking it.</p>
<p>(This is due to the specific grading requirements of the first school to request the study plan plugin)</p>
@ -105,9 +105,9 @@
<p>The student first enters the <i>Invite management</i> page where they can add, remove, edit or re-send invitations</p>
<p><img src="img/Share-studyplan-manage.png" class="border border-primary rounded-lg ml-3"></p>
<p>By clicking the <i class="fa fa-eye text-primary"></i> icon, the student can verify how their invitees see the studyplan</p>
<p>the other icons are respecively
<i class="fa fa-envelope text-primary">re-send mail</i>,
<i class="fa fa-pencil text-primary">edit</i> and
<p>the other icons are respecively
<i class="fa fa-envelope text-primary">re-send mail</i>,
<i class="fa fa-pencil text-primary">edit</i> and
<i class="fa fa-trash text-primary">delete</i></p>
<p>When starting a new invite, the student is asked to provide the <i>name</i> and <i>email</i> of the person to send the invitation to</p>

View File

@ -75,7 +75,7 @@ if ($studyplancontext->id > 1) {
// Load javascripts and specific css.
$PAGE->requires->css(new moodle_url($CFG->wwwroot.'/local/treestudyplan/css/bootstrap-vue/bootstrap-vue.css'));
if($CFG->debugdeveloper){
if ($CFG->debugdeveloper) {
$PAGE->requires->css(new moodle_url($CFG->wwwroot.'/local/treestudyplan/css/devstyles.css'));
}
$PAGE->requires->js_call_amd('local_treestudyplan/page-edit-plan', 'init', [$studyplancontext->id, $categoryid]);
@ -141,7 +141,12 @@ print $OUTPUT->header();
</div>
<div class='t-studyplan-container'>
<t-studyplan v-if='activestudyplan' v-model='activestudyplan' @moved="movedStudyplan" @toggletoolbox="toggletoolbox"></t-studyplan>
<t-studyplan
v-if='activestudyplan'
v-model='activestudyplan'
@moved="movedStudyplan"
@toggletoolbox="toggletoolbox"
></t-studyplan>
<div v-else-if='loadingstudyplan' class="spinner-border text-primary" role="status">
<span class="sr-only">Loading...</span>
</div>

View File

@ -65,7 +65,7 @@ if (empty($invite)) {
} else {
// Load javascripts and specific css.
$PAGE->requires->css(new moodle_url($CFG->wwwroot.'/local/treestudyplan/css/bootstrap-vue/bootstrap-vue.css'));
if($CFG->debugdeveloper){
if ($CFG->debugdeveloper) {
$PAGE->requires->css(new moodle_url($CFG->wwwroot.'/local/treestudyplan/css/devstyles.css'));
}
$PAGE->requires->js_call_amd('local_treestudyplan/page-myreport', 'init', ['invited', $invitekey]);

26
lib.php
View File

@ -42,6 +42,10 @@ function local_treestudyplan_unit_get_editor_options(context $context) {
'trusttext' => 0];
}
/**
* Create primary navigation links for studyplan if needed
*/
function local_treestudyplan_autofill_customusermenuitems() {
if (get_config("local_treestudyplan", "primary_nav_autofill")) {
$lang = current_language();
@ -51,12 +55,12 @@ function local_treestudyplan_autofill_customusermenuitems() {
"/local/treestudyplan/edit-plan.php" => ["included" => false, "strkey" => "link_editplan"],
];
// Load the custom menu items from config
// Load the custom menu items from config.
$custommenuitems = get_config("core", "custommenuitems");
// Scan through all the lines to see if it is a link to one of our nav items in the current language.
$lines = explode("\n", $custommenuitems);
//debugging("\n\nLines: ". print_r($lines,true)."\n");
$links = array_keys($navitems);
foreach ($lines as $line) {
$parms = explode('|', $line);
@ -69,13 +73,13 @@ function local_treestudyplan_autofill_customusermenuitems() {
}
}
// List through all the links to see if we need to add one or more
foreach($navitems as $link => $details){
// List through all the links to see if we need to add one or more.
foreach ($navitems as $link => $details) {
if (!$details["included"]) {
$line = implode("|",[
get_string($details["strkey"],"local_treestudyplan"), // Menu text.
$line = implode("|", [
get_string($details["strkey"], "local_treestudyplan"), // Menu text.
$link, // Link.
'', // Tooltip,
'', // Tooltip.
$lang, // Language code.
" #Automatically added by studyplan plugin. See setting 'primary_nav_autofill' to disable this"
]);
@ -83,8 +87,8 @@ function local_treestudyplan_autofill_customusermenuitems() {
}
}
// Store the modified custom menu items.
set_config("custommenuitems",$custommenuitems);
// Store the modified custom menu items.
set_config("custommenuitems", $custommenuitems);
}
}
@ -113,8 +117,8 @@ function local_treestudyplan_extend_navigation(global_navigation $navigation) {
We will add all the hrefs that should be hidden to this variable below.
*/
/*
In addition, the function local_treestudyplan_autofill_customusermenuitems() called below will
/*
In addition, the function local_treestudyplan_autofill_customusermenuitems() called below will
automatically generate the required lines if they are missing...
*/
local_treestudyplan_autofill_customusermenuitems();

View File

@ -38,7 +38,7 @@ $PAGE->set_heading(get_string('report_invited', 'local_treestudyplan', "{$USER->
// Load javascripts and specific css.
$PAGE->requires->css(new moodle_url($CFG->wwwroot.'/local/treestudyplan/css/bootstrap-vue/bootstrap-vue.css'));
if($CFG->debugdeveloper){
if ($CFG->debugdeveloper) {
$PAGE->requires->css(new moodle_url($CFG->wwwroot.'/local/treestudyplan/css/devstyles.css'));
}
$PAGE->requires->js_call_amd('local_treestudyplan/page-myreport', 'init');

View File

@ -46,7 +46,7 @@ if ($teachermode) {
// Load javascripts and specific css.
$PAGE->requires->css(new moodle_url($CFG->wwwroot.'/local/treestudyplan/css/bootstrap-vue/bootstrap-vue.css'));
if($CFG->debugdeveloper){
if ($CFG->debugdeveloper) {
$PAGE->requires->css(new moodle_url($CFG->wwwroot.'/local/treestudyplan/css/devstyles.css'));
}
$PAGE->requires->js_call_amd('local_treestudyplan/page-myreport', 'init', [$teachermode ? 'teaching' : 'myreport']);

View File

@ -4,5 +4,5 @@
}
}

View File

@ -12,5 +12,5 @@
--excellent: var(--blue);
--pending: var(--gray);
--incomplete: var(--gray);
}

View File

@ -1,4 +1,4 @@
.path-local-treestudyplan, .features-treestudyplan {
.path-local-treestudyplan, .features-treestudyplan {
div.tab-pane:target {
margin-top: 0;
}

View File

@ -1,5 +1,5 @@
The amount of css rules required for this plugin is quite high. To accomodate the
moodle plugin practice of plugin specific css selectors, this makes the css rules
The amount of css rules required for this plugin is quite high. To accomodate the
moodle plugin practice of plugin specific css selectors, this makes the css rules
quite hard to maintain.
Unfortunately, moodle does not extend the sass/scss compiler support to plugins
@ -12,7 +12,7 @@ to compile 'scss/styles.scss' into 'css/devstyles.css' (and 'styles.css')
cache every time a style rule is changed during development.
Once I devise a method to use styles.css in production environtments and css/devstyles.css
production environments, the compiler will compile it into both css/devstyles.css
production environments, the compiler will compile it into both css/devstyles.css
and styles.css
The grunt command to compile is 'grunt scssplugin'

View File

@ -16,7 +16,7 @@
.t-studyplan-headings,
.r-studyplan-headings {
display: block;
display: block;
}
.t-studyplan-wrapper,
@ -50,10 +50,10 @@
.r-studyplan-scrollable::-webkit-scrollbar-track {
background: color-mix(in srgb, var(--primary) 20%, white);
}
.t-studyplan-scrollable::-webkit-scrollbar-thumb,
.r-studyplan-scrollable::-webkit-scrollbar-thumb {
background:var(--primary);
background:var(--primary);
}
.t-studyplan-column-heading,
@ -121,7 +121,7 @@
}
.t-studyplan-controlbox .control {
margin-left: 10px;
margin-right: 5px;
}
@ -1059,10 +1059,10 @@
.s-required.complete {
color: var(--info);
}
}
.s-required.complete-pass,
.s-required.good,
.s-required.complete-pass,
.s-required.good,
.s-required.excellent,
.s-required.allgraded {
color: var(--success);
@ -1078,14 +1078,14 @@
}
.r-tooltip.warning .arrow::before {
border-right-color: color-mix(in srgb, var(--warning) 60%, #000);
}
}
.r-tooltip.info .tooltip-inner
{
background-color: color-mix(in srgb, var(--info) 60%, #000);
}
.r-tooltip.info .arrow::before {
border-right-color: color-mix(in srgb, var(--info) 60%, #000);
}
}
.r-tooltip.incomplete .tooltip-inner,
.r-tooltip.complete-fail .tooltip-inner,
@ -1133,10 +1133,10 @@
margin-right: 1em;
}
.s-edit-mod-form [data-fieldtype=submit] {
.s-edit-mod-form [data-fieldtype=submit] {
/* if not working, make selector more specific */
display: none;
}
}
.s-edit-mod-form.genericonly form > fieldset:not(#id_general) {
/* if not working, make selector more specific */
display: none;

View File

@ -42,12 +42,12 @@ if ($hassiteconfig) {
$page = new admin_settingpage('local_treestudyplan_settings',
get_string('settingspage', 'local_treestudyplan', null, true));
// NAVIGATION
// NAVIGATION.
$page->add(new admin_setting_heading('local_treestudyplan/navigation_heading',
get_string('setting_navigation_heading', 'local_treestudyplan'),
get_string('settingdesc_navigation_heading', 'local_treestudyplan')
));
$page->add(new admin_setting_configcheckbox('local_treestudyplan/primary_nav_autofill',
get_string('setting_primary_nav_autofill', 'local_treestudyplan'),
get_string('settingdesc_primary_nav_autofill', 'local_treestudyplan'),

View File

@ -24,7 +24,7 @@
<copyright>2016-2023 - BootstrapVue</copyright>
</copyrights>
</library>
<library>
<location>amd/src/portal-vue/</location>
<name>Portal-Vue.js</name>
@ -36,7 +36,7 @@
<copyright>2021 Thorsten Lünborg</copyright>
</copyrights>
</library>
<library>
<location>amd/src/vue</location>
<name>Vue</name>
@ -48,8 +48,8 @@
<copyright>2014-2022 Evan You</copyright>
</copyrights>
</library>
<library>
<location>amd/src/vue-easy-dnd/vue-easy-dnd.esm.js</location>
<name>Vue-Easy-DnD</name>
@ -61,7 +61,7 @@
<copyright>2019 Régis Lemaigre</copyright>
</copyrights>
</library>
<library>
<location>amd/src/vue-easy-dnd/reflect-metadata.js</location>
<name>reflect-metadata</name>
@ -76,7 +76,7 @@
<copyright>William Buchwalter</copyright>
</copyrights>
</library>
<library>
<location>amd/src/vue-easy-dnd/vue-class-component.js</location>
<name>vue-class-component</name>
@ -88,7 +88,7 @@
<copyright>2015-present Evan You</copyright>
</copyrights>
</library>
<library>
<location>amd/src/vue-easy-dnd/vue-property-decorator.js</location>
<name>vue-property-decorator</name>
@ -100,7 +100,7 @@
<copyright>2015-present Evan You</copyright>
</copyrights>
</library>
<library>
<location>amd/src/simpleline</location>
<name>SimpleLine</name>
@ -110,8 +110,8 @@
<repository>https://github.com/miqraeng/simpleline</repository>
<copyrights>
<copyright>2023 P.M. Kuipers</copyright>
<copyright>Morglod/jchnkl</copyright>
<copyright>Morglod/jchnkl</copyright>
</copyrights>
</library>
</libraries>

View File

@ -75,7 +75,7 @@ if ($studyplancontext->id > 1) {
// Load javascripts and specific css.
$PAGE->requires->css(new moodle_url($CFG->wwwroot.'/local/treestudyplan/css/bootstrap-vue/bootstrap-vue.css'));
if($CFG->debugdeveloper){
if ($CFG->debugdeveloper) {
$PAGE->requires->css(new moodle_url($CFG->wwwroot.'/local/treestudyplan/css/devstyles.css'));
}
$PAGE->requires->js_call_amd('local_treestudyplan/page-view-plan', 'init', [$studyplancontext->id, $categoryid]);

View File

@ -14,12 +14,12 @@ if [[ "$VUEJS_MD5" == "$VUEDEV_MD5" ]]; then
elif [[ "$VUEJS_MD5" == "$VUEPROD_MD5" ]]; then
MODE="prod"
elif [[ "$2" != "-f" && "$1" != "query" ]]; then
echo "ERROR: $VUELINK is not a a copy of either ../vue/vue-dev.js or ../vue/vue-prod.js"
echo "ERROR: $VUELINK is not a a copy of either ../vue/vue-dev.js or ../vue/vue-prod.js"
echo "Maybe you manually changed it? Use -f to force overwrite in this case"
exit 1
fi
# SINCE the production version of vue.js s already minified, we may get away with simply copying it
# SINCE the production version of vue.js s already minified, we may get away with simply copying it
# to the build directory
if [ "$1" == "dev" ]; then
cp $VUEDEV/vue.js $VUEBUILD/vue.min.js