Compare commits

...

72 Commits

Author SHA1 Message Date
PMKuipers
8adaf04cd3 Version bump 2024-06-28 17:28:05 +02:00
PMKuipers
4cc04371a0 Fixed minor bug caused by code style cleanup 2024-06-28 17:27:56 +02:00
PMKuipers
675455630d Disabled premium check for now 2024-06-28 17:27:17 +02:00
PMKuipers
9b4ead935e Removed unused component 2024-06-28 17:26:45 +02:00
PMKuipers
e6307a9308 Minor ui fix 2024-06-12 21:19:26 +02:00
PMKuipers
aaa8c79df4 Code cleanup and minor ui fixes 2024-06-12 21:12:00 +02:00
PMKuipers
ba9d58c01d Fixed feedback display 2024-06-05 23:38:38 +02:00
PMKuipers
6b9826934f Cleanup of eslint warnings 2024-06-05 23:27:00 +02:00
PMKuipers
b60e5065f0 Code cleanup of eslint warnings 2024-06-05 22:32:18 +02:00
PMKuipers
46f8575291 Cleanup of eslint warnings 2024-06-05 22:22:19 +02:00
PMKuipers
5c1f21cee0 ESLint Code cleanup 2024-06-03 23:24:16 +02:00
PMKuipers
e999fdb125 Cleanup 2024-06-03 10:51:18 +02:00
PMKuipers
dbb1386b7d Cleanup 2024-06-03 10:48:07 +02:00
PMKuipers
2268aba9d5 Fixed phpdocs 2024-06-03 04:00:46 +02:00
PMKuipers
520034bb5f Tweaks 2024-06-02 23:28:43 +02:00
PMKuipers
64c821bbe9 Completed code guidelines rework 2024-06-02 23:23:32 +02:00
PMKuipers
e72be595aa Code style 2024-06-02 19:23:40 +02:00
PMKuipers
4c669ff08c Code checking - 2024-06-02 18:47:23 +02:00
PMKuipers
ebdb39c5ca Added back button catching 2024-06-02 17:21:30 +02:00
PMKuipers
3d53966b5a Layout tweaks 2024-06-01 14:50:14 +02:00
PMKuipers
8b5cad01f8 Fixed layout of item labels 2024-06-01 14:00:32 +02:00
PMKuipers
742f9ef772 Fixed issue in search for cohorts/users 2024-06-01 08:42:18 +02:00
PMKuipers
3d48f290d6 Tweak to flow control layout 2024-05-31 22:33:10 +02:00
PMKuipers
de8e8199ca Side bar now has proper scrolling 2024-05-31 22:32:43 +02:00
PMKuipers
ee0a9c3d3a Fixed some minor warnings 2024-05-31 20:41:18 +02:00
PMKuipers
4fd1e3a547 Implemented HiViz dropslot big "drop here" feature 2024-05-27 22:11:32 +02:00
PMKuipers
c88f132201 Added hack to limit length of edit screen 2024-05-24 13:16:43 +02:00
PMKuipers
582db40016 Risplay improvements in studyplan overview 2024-05-24 10:42:29 +02:00
PMKuipers
09c6e4b029 Made debug log better able to handle write errors without breaking things for no good reason 2024-05-24 09:30:16 +02:00
PMKuipers
3dd1682f5a style fixes 2024-05-24 09:20:23 +02:00
PMKuipers
ebc3632788 Reworked card view to better handle long course titles 2024-05-23 23:32:59 +02:00
PMKuipers
5dc94a7525 Typo fix 2024-05-22 23:24:06 +02:00
PMKuipers
ff9044ae9a Version bump 2024-05-22 23:19:02 +02:00
PMKuipers
3ba4d4d4c5 Added clauses to exclude deleted users from the queries 2024-05-22 23:18:05 +02:00
PMKuipers
5bd10de529 Cleanup - and added proper check on available coaching studyplans 2024-05-22 22:16:47 +02:00
PMKuipers
39d184c2e9 fixed issue with premium key not properly handling unicode spaces in key wrappers 2024-05-22 21:46:56 +02:00
PMKuipers
50110a3e0f Minor spell checking 2024-05-18 16:55:50 +02:00
PMKuipers
9caf652ff5 Made some code less confusing 2024-05-18 16:48:35 +02:00
PMKuipers
bcb288bac8 Enabled coach progress bar 2024-05-18 16:46:27 +02:00
PMKuipers
9a1b32855f Added IDNumber getter and did some polishing 2024-05-15 22:57:54 +02:00
PMKuipers
c938b994a1 Fixes 2024-05-10 15:22:52 +02:00
PMKuipers
97ce14fe20 Fixed bug with gradebookroles 2024-04-30 10:36:01 +02:00
PMKuipers
be0141eb7b version bump 2024-04-21 23:43:25 +02:00
PMKuipers
12efb061f1 Reworked sidebar 2024-04-21 23:08:03 +02:00
PMKuipers
886a11d99e Added extra warning if no templates are available 2024-04-19 17:01:17 +02:00
PMKuipers
82838c57c5 Added templating function 2024-04-19 16:46:30 +02:00
PMKuipers
2369610903 Fixed issue with recursive scan for categories with a given permission causing system hangup 2024-03-25 23:43:27 +01:00
PMKuipers
88744c2b66 fixed issues with Moodle 3.11 compatibility 2024-03-25 23:42:40 +01:00
PMKuipers
d225d82038 Number of minor changes 2024-03-22 22:01:42 +01:00
PMKuipers
0163471a92 Added course filter 2024-03-11 23:21:59 +01:00
PMKuipers
383c07becd Tweaks to timeless mode 2024-03-10 23:11:54 +01:00
PMKuipers
310f042772 Added option to remove timing from periods 2024-03-10 23:09:33 +01:00
PMKuipers
3002a866bb Added coaching navigation links and enable/disable setting 2024-03-10 21:51:37 +01:00
PMKuipers
747e1decd1 Implemended studyplan suspension 2024-03-10 15:56:35 +01:00
PMKuipers
f2fac43139 Added option to limit course list to studyplan context. Added suspended field in studyplan db table 2024-03-09 23:29:58 +01:00
PMKuipers
7dbcb437c3 Implemented coach view 2024-03-09 22:51:34 +01:00
PMKuipers
6bb13de5cf Removed unneeded minified files 2024-03-09 22:51:14 +01:00
PMKuipers
500555e785 Moved toolbox to studyplan components to better support coaching role 2024-03-09 08:23:42 +01:00
PMKuipers
25a5ff8398 Started work on coaching page 2024-03-09 00:11:42 +01:00
PMKuipers
c480c20098 added coach association to studyplan 2024-03-08 17:05:07 +01:00
PMKuipers
a57ee3d884 Removed debug statements 2024-03-08 16:57:34 +01:00
PMKuipers
35afe06a91 Implemented enrollable lines in cohort and user sync 2024-03-08 11:54:39 +01:00
PMKuipers
d713e24e32 Implemented hiding of non-enrolled lines in student result views 2024-03-07 22:44:29 +01:00
PMKuipers
079e2f77cc Implemented line enrolling management panel 2024-03-06 23:49:50 +01:00
PMKuipers
8938facdef Added proper sorting of time field 2024-03-04 23:05:01 +01:00
PMKuipers
f20a724c52 Prepared sorting of enrolled students list 2024-03-04 23:03:18 +01:00
PMKuipers
d07e6170f3 Updated API functions to yied more useful results 2024-03-04 22:51:51 +01:00
PMKuipers
8382c4a117 Implemented self enrol buttons 2024-03-04 22:39:29 +01:00
PMKuipers
61256207be Minfied files for last commit 2024-03-01 16:06:21 +01:00
PMKuipers
7ea7576ac8 Fix students seeing the result overview links 2024-03-01 16:06:07 +01:00
PMKuipers
1fe4db6328 Added code to edit enrolment config on study lines 2024-02-25 23:45:39 +01:00
PMKuipers
9619fd17de version bump 2024-02-25 16:23:30 +01:00
186 changed files with 10631 additions and 5584 deletions

10
.vscode/settings.json vendored
View File

@ -2,5 +2,13 @@
"intelephense.environment.includePaths": [
"/srv/moodle4/moodle"
],
"intelephense.diagnostics.undefinedProperties": false
"intelephense.diagnostics.undefinedProperties": false,
"spellright.language": [
"en"
],
"spellright.documentTypes": [
"markdown",
"latex",
"plaintext"
]
}

View File

@ -1,3 +1,4 @@
/* eslint no-console: "off" */
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
@ -22,7 +23,6 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Grunt configuration.
*
@ -34,10 +34,10 @@ 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', () => {
grunt.registerTask('scssplugin', 'Compile scss/styles.sccs into styles.css and css/devstyles.css', () => {
const devoutput = 'css/devstyles.css';
const prodoutput = 'styles.css';
@ -54,19 +54,19 @@ module.exports = function(grunt) {
// Some regex processing to match moodle stylelint styles
// change tab indent to 4 instead of 2
let css = result.css.replace(/^ ([^ ])/gm ," $1");
// Change tab indent to 4 instead of 2
let css = result.css.replace(/^ {2}([^ ])/gm, " $1");
// insert a newline after every comma in a css selector
// Insert a newline after every comma in a css selector
// (only combined selectors will get that long)
css = css.replace(/^[^ ].*[\{,]$/gm , (m) => { // Find CSS selector lines
return m.replace(/, /g,",\n"); // replace comma followed by space with comma newline
css = css.replace(/^[^ ].*[{,]$/gm, (m) => { // Find CSS selector lines
return m.replace(/, /g, ",\n"); // Replace comma followed by space with comma newline
});
// replace hex color codes with lowercase (convenience function since I don't really care for the hex codes lowercase only rule)
// Replace hex color codes with lowercase.
const hexCodeToLower = (match, m1, m2) => {
return '#' + m1.toLowerCase() + m2;
}
};
css = css.replace(/#([A-F0-9a-f]{3})([^A-F0-9a-f]?)/gm, hexCodeToLower); // 3 digit color codes
css = css.replace(/#([A-F0-9a-f]{6})([^A-F0-9a-f]?)/gm, hexCodeToLower); // 6 digit color codes
css = css.replace(/#([A-F0-9a-f]{8})([^A-F0-9a-f]?)/gm, hexCodeToLower); // 8 digit color codes (with alpha)

View File

@ -1,13 +1,13 @@
# 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 representation 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
learning process than by just listing results alone
## Installing
Install the plugin by adding the zip file manually to the installed plugins in your moodle version
Install the plugin by adding the zip file manually to the installed plugins in your Moodle version
## Configuration post install

File diff suppressed because one or more lines are too long

View File

@ -1,3 +1,3 @@
define("local_treestudyplan/cfg-grades",["exports","core/str","core/ajax","./debugger","./string-helper"],(function(_exports,_str,_ajax,_debugger,_stringHelper){var obj;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=function(){{const intRx=/\d/,integerChange=event=>{event.key.length>1||intRx.test(event.key)||event.preventDefault()};for(let input of document.querySelectorAll('input[type="number"][step="1"][min="0"]'))input.addEventListener("keydown",integerChange)}{const decimal=/^[0-9]*?\.[0-9]*?$/,intRx=/\d/,floatChange=event=>{event.key.length>1||"."===event.key&&!event.currentTarget.value.match(decimal)||intRx.test(event.key)||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)}};new(_debugger=(obj=_debugger)&&obj.__esModule?obj:{default:obj}).default("treestudyplan-config-grades")}));
define("local_treestudyplan/cfg-grades",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=function(){{const intRx=/\d/,integerChange=event=>{event.key.length>1||intRx.test(event.key)||event.preventDefault()};for(let input of document.querySelectorAll('input[type="number"][step="1"][min="0"]'))input.addEventListener("keydown",integerChange)}{const decimal=/^[0-9]*?\.[0-9]*?$/,intRx=/\d/,floatChange=event=>{event.key.length>1||"."===event.key&&!event.currentTarget.value.match(decimal)||intRx.test(event.key)||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)}}}));
//# sourceMappingURL=cfg-grades.min.js.map

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","input","document","querySelectorAll","addEventListener","decimal","floatChange","currentTarget","value","match"],"mappings":"iQA6BQA,MAAQ,KACRC,cAAiBC,QACTA,MAAMC,IAAIC,OAAS,GAAMJ,MAAMK,KAAKH,MAAMC,MAIhDD,MAAMI,sBAGL,IAAIC,SAASC,SAASC,iBAAkB,2CAC3CF,MAAMG,iBAAiB,UAAWT,sBAMlCU,QAAS,qBACTX,MAAQ,KACRY,YAAeV,QACPA,MAAMC,IAAIC,OAAS,GAAuB,MAAdF,MAAMC,MAAkBD,MAAMW,cAAcC,MAAMC,MAAMJ,UACjFX,MAAMK,KAAKH,MAAMC,MAI1BD,MAAMI,sBAGL,IAAIC,SAASC,SAASC,iBAAkB,6CAC3CF,MAAMG,iBAAiB,UAAWE,iBAE/B,IAAIL,SAASC,SAASC,iBAAkB,4BAC3CF,MAAMG,iBAAiB,UAAWE,eA9C9B,yEAAa"}
{"version":3,"file":"cfg-grades.min.js","sources":["../src/cfg-grades.js"],"sourcesContent":["/* eslint-env es6*/\n// Put this file in path/to/plugin/amd/src\n// You can call it anything you like\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","input","document","querySelectorAll","addEventListener","decimal","floatChange","currentTarget","value","match"],"mappings":"gKASQA,MAAQ,KACRC,cAAiBC,QACVA,MAAMC,IAAIC,OAAS,GAAMJ,MAAMK,KAAKH,MAAMC,MAI/CD,MAAMI,sBAGL,IAAIC,SAASC,SAASC,iBAAiB,2CAC1CF,MAAMG,iBAAiB,UAAWT,sBAMlCU,QAAU,qBACVX,MAAQ,KACRY,YAAeV,QACRA,MAAMC,IAAIC,OAAS,GAAsB,MAAdF,MAAMC,MAAkBD,MAAMW,cAAcC,MAAMC,MAAMJ,UAC/EX,MAAMK,KAAKH,MAAMC,MAI1BD,MAAMI,sBAGL,IAAIC,SAASC,SAASC,iBAAiB,6CAC1CF,MAAMG,iBAAiB,UAAWE,iBAE/B,IAAIL,SAASC,SAASC,iBAAiB,4BAC1CF,MAAMG,iBAAiB,UAAWE"}

View File

@ -1 +1 @@
{"version":3,"file":"downloader.min.js","sources":["../src/downloader.js"],"sourcesContent":["/*eslint no-console: \"off\"*/\n\n/**\n * Save a piece of text to file as if it was downloaded\n * @param {string} filename\n * @param {string} text\n * @param {string} type\n */\nexport function download(filename, text, type) {\n if(undefined == type) { type = \"text/plain\"; }\n var pom = document.createElement('a');\n pom.setAttribute('href', 'data:'+type+';charset=utf-8,' + encodeURIComponent(text));\n pom.setAttribute('download', filename);\n\n if (document.createEvent) {\n var event = document.createEvent('MouseEvents');\n event.initEvent('click', true, true);\n pom.dispatchEvent(event);\n }\n else {\n pom.click();\n }\n}\n\n/**\n * This callback type is called `requestCallback` and is displayed as a global symbol.\n *\n * @callback fileOpenedCallback\n * @param {File} file File name\n * @param {string} content File Contents\n */\n\n/**\n * Open a file from disk and read its contents\n * @param {fileOpenedCallback} onready Callback to run when file is opened\n * @param {Array} accept Array of mime types that the file dialog will accept\n */\nexport function upload(onready,accept) {\n let input = document.createElement('input');\n input.type = 'file';\n if(Array.isArray(accept)){\n if(accept.count > 0){\n input.accept = accept.join(\", \");\n }\n } else if (undefined !== accept){\n input.accept = accept;\n }\n input.onchange = () => {\n let files = Array.from(input.files);\n if(files.length > 0){\n let file = files[0];\n var reader = new FileReader();\n reader.onload = function(e) {\n var contents = e.target.result;\n if(onready instanceof Function){\n onready(file,contents);\n }\n };\n reader.readAsText(file);\n }\n };\n\n if (document.createEvent) {\n var event = document.createEvent('MouseEvents');\n event.initEvent('click', true, true);\n input.dispatchEvent(event);\n }\n else {\n input.click();\n }\n}"],"names":["filename","text","type","undefined","pom","document","createElement","setAttribute","encodeURIComponent","createEvent","event","initEvent","dispatchEvent","click","onready","accept","input","Array","isArray","count","join","onchange","files","from","length","file","reader","FileReader","onload","e","contents","target","result","Function","readAsText"],"mappings":"2JAQyBA,SAAUC,KAAMC,MAClCC,MAAaD,OAAQA,KAAO,kBAC3BE,IAAMC,SAASC,cAAc,QACjCF,IAAIG,aAAa,OAAQ,QAAQL,KAAK,kBAAoBM,mBAAmBP,OAC7EG,IAAIG,aAAa,WAAYP,UAEzBK,SAASI,YAAa,KAClBC,MAAQL,SAASI,YAAY,eACjCC,MAAMC,UAAU,SAAS,GAAM,GAC/BP,IAAIQ,cAAcF,YAGlBN,IAAIS,kCAiBWC,QAAQC,YACvBC,MAAQX,SAASC,cAAc,SACnCU,MAAMd,KAAO,OACVe,MAAMC,QAAQH,QACVA,OAAOI,MAAQ,IACdH,MAAMD,OAASA,OAAOK,KAAK,YAExBjB,IAAcY,SACrBC,MAAMD,OAASA,WAEnBC,MAAMK,SAAW,SACLC,MAAUL,MAAMM,KAAKP,MAAMM,UAC5BA,MAAME,OAAS,EAAE,KACZC,KAAOH,MAAM,OACbI,OAAS,IAAIC,WACjBD,OAAOE,OAAS,SAASC,OACjBC,SAAWD,EAAEE,OAAOC,OACrBlB,mBAAmBmB,UAClBnB,QAAQW,KAAKK,WAGrBJ,OAAOQ,WAAWT,QAI1BpB,SAASI,YAAa,KAClBC,MAAQL,SAASI,YAAY,eACjCC,MAAMC,UAAU,SAAS,GAAM,GAC/BK,MAAMJ,cAAcF,YAGpBM,MAAMH"}
{"version":3,"file":"downloader.min.js","sources":["../src/downloader.js"],"sourcesContent":["/**\n * Save a piece of text to file as if it was downloaded\n * @param {string} filename\n * @param {string} text\n * @param {string} type\n */\nexport function download(filename, text, type) {\n if (undefined == type) {\n type = \"text/plain\";\n }\n var pom = document.createElement('a');\n pom.setAttribute('href', 'data:' + type + ';charset=utf-8,' + encodeURIComponent(text));\n pom.setAttribute('download', filename);\n\n if (document.createEvent) {\n var event = document.createEvent('MouseEvents');\n event.initEvent('click', true, true);\n pom.dispatchEvent(event);\n } else {\n pom.click();\n }\n}\n\n/**\n * This callback type is called `requestCallback` and is displayed as a global symbol.\n *\n * @callback fileOpenedCallback\n * @param {File} file File name\n * @param {string} content File Contents\n */\n\n/**\n * Open a file from disk and read its contents\n * @param {fileOpenedCallback} onready Callback to run when file is opened\n * @param {Array} accept Array of mime types that the file dialog will accept\n */\nexport function upload(onready, accept) {\n let input = document.createElement('input');\n input.type = 'file';\n if (Array.isArray(accept)) {\n if (accept.count > 0) {\n input.accept = accept.join(\", \");\n }\n } else if (undefined !== accept) {\n input.accept = accept;\n }\n input.onchange = () => {\n let files = Array.from(input.files);\n if (files.length > 0) {\n let file = files[0];\n var reader = new FileReader();\n reader.onload = function(e) {\n var contents = e.target.result;\n if (onready instanceof Function) {\n onready(file, contents);\n }\n };\n reader.readAsText(file);\n }\n };\n\n if (document.createEvent) {\n var event = document.createEvent('MouseEvents');\n event.initEvent('click', true, true);\n input.dispatchEvent(event);\n } else {\n input.click();\n }\n}"],"names":["filename","text","type","undefined","pom","document","createElement","setAttribute","encodeURIComponent","createEvent","event","initEvent","dispatchEvent","click","onready","accept","input","Array","isArray","count","join","onchange","files","from","length","file","reader","FileReader","onload","e","contents","target","result","Function","readAsText"],"mappings":"2JAMyBA,SAAUC,KAAMC,MACjCC,MAAaD,OACbA,KAAO,kBAEPE,IAAMC,SAASC,cAAc,QACjCF,IAAIG,aAAa,OAAQ,QAAUL,KAAO,kBAAoBM,mBAAmBP,OACjFG,IAAIG,aAAa,WAAYP,UAEzBK,SAASI,YAAa,KAClBC,MAAQL,SAASI,YAAY,eACjCC,MAAMC,UAAU,SAAS,GAAM,GAC/BP,IAAIQ,cAAcF,YAElBN,IAAIS,kCAiBWC,QAASC,YACxBC,MAAQX,SAASC,cAAc,SACnCU,MAAMd,KAAO,OACTe,MAAMC,QAAQH,QACVA,OAAOI,MAAQ,IACfH,MAAMD,OAASA,OAAOK,KAAK,YAExBjB,IAAcY,SACrBC,MAAMD,OAASA,WAEnBC,MAAMK,SAAW,SACLC,MAAQL,MAAMM,KAAKP,MAAMM,UACzBA,MAAME,OAAS,EAAG,KACdC,KAAOH,MAAM,OACbI,OAAS,IAAIC,WACjBD,OAAOE,OAAS,SAASC,OACjBC,SAAWD,EAAEE,OAAOC,OACpBlB,mBAAmBmB,UACnBnB,QAAQW,KAAMK,WAGtBJ,OAAOQ,WAAWT,QAI1BpB,SAASI,YAAa,KAClBC,MAAQL,SAASI,YAAY,eACjCC,MAAMC,UAAU,SAAS,GAAM,GAC/BK,MAAMJ,cAAcF,YAEpBM,MAAMH"}

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(Vue){let 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:()=>({content:"",text:strings.editmod}),computed:{},methods:{openForm(){this.$refs.editormodal.show()},onShown(){const self=this;let params={cmid:this.cmid};console.info("Loading form"),(0,_fragment.loadFragment)("local_treestudyplan","mod_edit_form",this.coursectxid,params).then(((html,js)=>{(0,_templates.replaceNodeContents)(self.$refs.content,html,js)})).catch(_notification.default.exception)},onSave(){const self=this;let form=this.$refs.content.getElementsByTagName("form")[0];form.dispatchEvent(new Event("save-form-state"));const 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].then((()=>{self.$emit("saved",formdata)})).catch(_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(Vue){let strings=(0,_stringHelper.loadStrings)({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:()=>({content:"",text:strings.editmod}),computed:{},methods:{openForm(){this.$refs.editormodal.show()},onShown(){const self=this;let params={cmid:this.cmid};(0,_fragment.loadFragment)("local_treestudyplan","mod_edit_form",this.coursectxid,params).then(((html,js)=>((0,_templates.replaceNodeContents)(self.$refs.content,html,js),null))).catch(_notification.default.exception)},onSave(){const self=this;let form=this.$refs.content.getElementsByTagName("form")[0];form.dispatchEvent(new Event("save-form-state"));const 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].then((()=>(self.$emit("saved",formdata),null))).catch(_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

3
amd/build/page-coach.min.js vendored Normal file

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/page-invitemanager",["exports","./util/debugger","core/str","core/modal_factory","core/modal_events"],(function(_exports,_debugger,_str,_modal_factory,_modal_events){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=function(){getstr_func([{key:"ok",component:"core"},{key:"confirm",component:"core"}]).then((s=>{const strOk=s[0],strConfirm=s[1];document.querySelectorAll(".path-local-treestudyplan a.m-action-confirm").forEach((el=>{el.addEventListener("click",(e=>{e.preventDefault();const link=e.currentTarget;let href=link.getAttribute("data-actionhref"),text=link.getAttribute("data-confirmtext"),oktext=link.getAttribute("data-confirmbtn");null==oktext&&(oktext=strOk);let title=link.getAttribute("data-confirmtitle");null==title&&(title=strConfirm),_modal_factory.default.create({type:_modal_factory.default.types.SAVE_CANCEL,title:title,body:text}).then((function(modal){return modal.setSaveButtonText(oktext),modal.getRoot().on(_modal_events.default.save,(function(){window.location=href})),modal.modal[0].style["max-width"]="345px",modal.show(),modal}))}))}))}))},_debugger=_interopRequireDefault(_debugger),_modal_factory=_interopRequireDefault(_modal_factory),_modal_events=_interopRequireDefault(_modal_events);const getstr_func=void 0!==_str.getStrings?_str.getStrings:_str.get_strings;new _debugger.default("treestudyplan")}));
define("local_treestudyplan/page-invitemanager",["exports","./util/debugger","core/str","core/modal_factory","core/modal_events"],(function(_exports,_debugger,_str,_modal_factory,_modal_events){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=function(){getstrFunc([{key:"ok",component:"core"},{key:"confirm",component:"core"}]).then((s=>{const strOk=s[0],strConfirm=s[1];document.querySelectorAll(".path-local-treestudyplan a.m-action-confirm").forEach((el=>{el.addEventListener("click",(e=>{e.preventDefault();const link=e.currentTarget;let href=link.getAttribute("data-actionhref"),text=link.getAttribute("data-confirmtext"),oktext=link.getAttribute("data-confirmbtn");null==oktext&&(oktext=strOk);let title=link.getAttribute("data-confirmtitle");null==title&&(title=strConfirm),_modal_factory.default.create({type:_modal_factory.default.types.SAVE_CANCEL,title:title,body:text}).then((modal=>(modal.setSaveButtonText(oktext),modal.getRoot().on(_modal_events.default.save,(()=>{window.location=href})),modal.modal[0].style["max-width"]="345px",modal.show(),modal))).catch((x=>{debug.warn(x)}))}))}))})).catch((x=>{debug.warn(x)}))},_debugger=_interopRequireDefault(_debugger),_modal_factory=_interopRequireDefault(_modal_factory),_modal_events=_interopRequireDefault(_modal_events);const getstrFunc=void 0!==_str.getStrings?_str.getStrings:_str.get_strings;let debug=new _debugger.default("treestudyplan-invitemanager")}));
//# sourceMappingURL=page-invitemanager.min.js.map

View File

@ -1 +1 @@
{"version":3,"file":"page-invitemanager.min.js","sources":["../src/page-invitemanager.js"],"sourcesContent":["/*eslint no-var: \"error\" */\n/*eslint no-unused-vars: \"off\" */\n/*eslint linebreak-style: \"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 Debugger from './util/debugger';\nimport {get_strings} from 'core/str';\nimport {getStrings} from 'core/str';\nimport ModalFactory from 'core/modal_factory';\nimport ModalEvents from 'core/modal_events';\n\n/* Determine the proper getstrings function to use (MDL4.3+ recommends use of getStrings, which is jquery independent) */\nconst getstr_func = (getStrings !== undefined)?getStrings:get_strings;\n\nlet debug = new Debugger(\"treestudyplan\");\n\n /**\n * Init function for page-invitemanager\n * @return undefined\n */\nexport function init() {\n getstr_func([\n { key: 'ok', component: 'core'},\n { key: 'confirm', component: 'core'},\n ]).then((s) => {\n const strOk = s[0];\n const strConfirm = s[1];\n\n const els = document.querySelectorAll('.path-local-treestudyplan a.m-action-confirm');\n els.forEach((el) => {\n el.addEventListener('click', (e) => {\n e.preventDefault();\n const link = e.currentTarget;\n let href = link.getAttribute('data-actionhref');\n let text = link.getAttribute('data-confirmtext');\n let oktext = link.getAttribute('data-confirmbtn');\n if (undefined == oktext) {\n oktext = strOk;\n }\n let title = link.getAttribute('data-confirmtitle');\n if (undefined == title) {\n title = strConfirm;\n }\n\n ModalFactory.create({\n type: ModalFactory.types.SAVE_CANCEL,\n title: title,\n body: text,\n }).then(function (modal) {\n modal.setSaveButtonText(oktext);\n\n let root = modal.getRoot();\n root.on(ModalEvents.save, function () {\n window.location = href;\n });\n modal.modal[0].style[\"max-width\"] = \"345px\";\n modal.show();\n return modal;\n });\n });\n });\n });\n}\n"],"names":["getstr_func","key","component","then","s","strOk","strConfirm","document","querySelectorAll","forEach","el","addEventListener","e","preventDefault","link","currentTarget","href","getAttribute","text","oktext","undefined","title","create","type","ModalFactory","types","SAVE_CANCEL","body","modal","setSaveButtonText","getRoot","on","ModalEvents","save","window","location","style","show","getStrings","get_strings","Debugger"],"mappings":"qWAuBIA,YAAY,CACR,CAAEC,IAAK,KAAMC,UAAW,QACxB,CAAED,IAAK,UAAWC,UAAW,UAC9BC,MAAMC,UACCC,MAAQD,EAAE,GACVE,WAAaF,EAAE,GAETG,SAASC,iBAAiB,gDAClCC,SAASC,KACTA,GAAGC,iBAAiB,SAAUC,IAC1BA,EAAEC,uBACIC,KAAOF,EAAEG,kBACXC,KAAOF,KAAKG,aAAa,mBACzBC,KAAOJ,KAAKG,aAAa,oBACzBE,OAASL,KAAKG,aAAa,mBAC3BG,MAAaD,SACbA,OAASd,WAETgB,MAAQP,KAAKG,aAAa,qBAC1BG,MAAaC,QACbA,MAAQf,mCAGCgB,OAAO,CAChBC,KAAMC,uBAAaC,MAAMC,YACzBL,MAAOA,MACPM,KAAMT,OACPf,MAAK,SAAUyB,cACdA,MAAMC,kBAAkBV,QAEbS,MAAME,UACZC,GAAGC,sBAAYC,MAAM,WACtBC,OAAOC,SAAWnB,QAEtBY,MAAMA,MAAM,GAAGQ,MAAM,aAAe,QACpCR,MAAMS,OACCT,+KA7CrB5B,iBAA8BoB,IAAfkB,gBAA0BA,gBAAWC,iBAE9C,IAAIC,kBAAS"}
{"version":3,"file":"page-invitemanager.min.js","sources":["../src/page-invitemanager.js"],"sourcesContent":["/* eslint no-var: \"error\" */\n/* eslint no-unused-vars: \"off\" */\n/* eslint linebreak-style: \"off\" */\n/* eslint-env es6*/\n/* eslint camelcase: \"off\" */\n// Put this file in path/to/plugin/amd/src\n// You can call it anything you like\n\nimport Debugger from './util/debugger';\nimport {get_strings} from 'core/str';\nimport {getStrings} from 'core/str';\nimport ModalFactory from 'core/modal_factory';\nimport ModalEvents from 'core/modal_events';\n\n/* Determine the proper getstrings function to use (MDL4.3+ recommends use of getStrings, which is jquery independent) */\nconst getstrFunc = (getStrings !== undefined) ? getStrings : get_strings;\n\nlet debug = new Debugger(\"treestudyplan-invitemanager\");\n\n /**\n * Init function for page-invitemanager\n */\nexport function init() {\n getstrFunc([\n {key: 'ok', component: 'core'},\n {key: 'confirm', component: 'core'},\n ]).then((s) => {\n const strOk = s[0];\n const strConfirm = s[1];\n\n const els = document.querySelectorAll('.path-local-treestudyplan a.m-action-confirm');\n els.forEach((el) => {\n el.addEventListener('click', (e) => {\n e.preventDefault();\n const link = e.currentTarget;\n let href = link.getAttribute('data-actionhref');\n let text = link.getAttribute('data-confirmtext');\n let oktext = link.getAttribute('data-confirmbtn');\n if (undefined == oktext) {\n oktext = strOk;\n }\n let title = link.getAttribute('data-confirmtitle');\n if (undefined == title) {\n title = strConfirm;\n }\n\n ModalFactory.create({\n type: ModalFactory.types.SAVE_CANCEL,\n title: title,\n body: text,\n }).then((modal) => {\n modal.setSaveButtonText(oktext);\n\n let root = modal.getRoot();\n root.on(ModalEvents.save, () => {\n window.location = href;\n });\n modal.modal[0].style[\"max-width\"] = \"345px\";\n modal.show();\n return modal;\n }).catch((x) => {\n debug.warn(x);\n });\n });\n });\n return;\n }).catch((x) => {\n debug.warn(x);\n });\n}\n"],"names":["getstrFunc","key","component","then","s","strOk","strConfirm","document","querySelectorAll","forEach","el","addEventListener","e","preventDefault","link","currentTarget","href","getAttribute","text","oktext","undefined","title","create","type","ModalFactory","types","SAVE_CANCEL","body","modal","setSaveButtonText","getRoot","on","ModalEvents","save","window","location","style","show","catch","x","debug","warn","getStrings","get_strings","Debugger"],"mappings":"qWAuBIA,WAAW,CACP,CAACC,IAAK,KAAMC,UAAW,QACvB,CAACD,IAAK,UAAWC,UAAW,UAC7BC,MAAMC,UACCC,MAAQD,EAAE,GACVE,WAAaF,EAAE,GAETG,SAASC,iBAAiB,gDAClCC,SAASC,KACTA,GAAGC,iBAAiB,SAAUC,IAC1BA,EAAEC,uBACIC,KAAOF,EAAEG,kBACXC,KAAOF,KAAKG,aAAa,mBACzBC,KAAOJ,KAAKG,aAAa,oBACzBE,OAASL,KAAKG,aAAa,mBAC3BG,MAAaD,SACbA,OAASd,WAETgB,MAAQP,KAAKG,aAAa,qBAC1BG,MAAaC,QACbA,MAAQf,mCAGCgB,OAAO,CAChBC,KAAMC,uBAAaC,MAAMC,YACzBL,MAAOA,MACPM,KAAMT,OACPf,MAAMyB,QACLA,MAAMC,kBAAkBV,QAEbS,MAAME,UACZC,GAAGC,sBAAYC,MAAM,KACtBC,OAAOC,SAAWnB,IAAlB,IAEJY,MAAMA,MAAM,GAAGQ,MAAM,aAAe,QACpCR,MAAMS,OACCT,SACRU,OAAOC,IACNC,MAAMC,KAAKF,eAKxBD,OAAOC,IACNC,MAAMC,KAAKF,mKApDbvC,gBAA6BoB,IAAfsB,gBAA4BA,gBAAaC,qBAEzDH,MAAQ,IAAII,kBAAS"}

View File

@ -1,3 +1,3 @@
define("local_treestudyplan/page-myreport",["exports","./vue/vue","./report-viewer-components","./util/debugger","./portal-vue/portal-vue.esm","./bootstrap-vue/bootstrap-vue"],(function(_exports,_vue,_reportViewerComponents,_debugger,_portalVue,_bootstrapVue){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=function(){let type=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"own",arg1=arguments.length>1?arguments[1]:void 0;new _vue.default({el:"#root",data:{studyplans:[],type:type,invitekey:"invited"==type?arg1:null,userid:"other"==type?arg1:null},methods:{}})},_vue=_interopRequireDefault(_vue),_reportViewerComponents=_interopRequireDefault(_reportViewerComponents),_debugger=_interopRequireDefault(_debugger),_portalVue=_interopRequireDefault(_portalVue),_bootstrapVue=_interopRequireDefault(_bootstrapVue),_vue.default.use(_reportViewerComponents.default),_vue.default.use(_portalVue.default),_vue.default.use(_bootstrapVue.default);new _debugger.default("treestudyplan-report")}));
define("local_treestudyplan/page-myreport",["exports","./vue/vue","./report-viewer-components","./portal-vue/portal-vue.esm","./bootstrap-vue/bootstrap-vue"],(function(_exports,_vue,_reportViewerComponents,_portalVue,_bootstrapVue){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=function(){let type=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"own",arg1=arguments.length>1?arguments[1]:void 0;new _vue.default({el:"#root",data:{studyplans:[],type:type,invitekey:"invited"==type?arg1:null,userid:"other"==type?arg1:null},methods:{}})},_vue=_interopRequireDefault(_vue),_reportViewerComponents=_interopRequireDefault(_reportViewerComponents),_portalVue=_interopRequireDefault(_portalVue),_bootstrapVue=_interopRequireDefault(_bootstrapVue),_vue.default.use(_reportViewerComponents.default),_vue.default.use(_portalVue.default),_vue.default.use(_bootstrapVue.default)}));
//# sourceMappingURL=page-myreport.min.js.map

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 Vue from './vue/vue';\n\nimport RVComponents from './report-viewer-components';\nVue.use(RVComponents);\n\nimport Debugger from './util/debugger';\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} arg1 Argument1 as passed\n */\n export function init(type=\"own\",arg1) {\n let app = new Vue({\n el: '#root',\n data: {\n \"studyplans\": [],\n \"type\": type,\n \"invitekey\": (type==\"invited\")?arg1:null,\n \"userid\": (type==\"other\")?arg1:null,\n },\n methods: {\n\n },\n });\n\n}\n\n"],"names":["type","arg1","Vue","el","data","methods","use","RVComponents","PortalVue","BootstrapVue","Debugger"],"mappings":"2aA4BsBA,4DAAK,MAAMC,4CACnB,IAAIC,aAAI,CACdC,GAAI,QACJC,KAAM,YACY,QACNJ,eACY,WAANA,KAAiBC,KAAK,YACnB,SAAND,KAAeC,KAAK,MAEnCI,QAAS,2QAzBbC,IAAIC,8CAKJD,IAAIE,iCAEJF,IAAIG,uBAEI,IAAIC,kBAAS"}
{"version":3,"file":"page-myreport.min.js","sources":["../src/page-myreport.js"],"sourcesContent":["/* eslint no-unused-vars: \"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 Vue from './vue/vue';\n\nimport RVComponents from './report-viewer-components';\nVue.use(RVComponents);\n\nimport PortalVue from './portal-vue/portal-vue.esm';\nVue.use(PortalVue);\nimport BootstrapVue from './bootstrap-vue/bootstrap-vue';\nVue.use(BootstrapVue);\n\n/**\n * Initialize the Page\n * @param {string} type Type of page to show\n * @param {Object} arg1 Argument1 as passed\n */\n export function init(type = \"own\", arg1) {\n let app = new Vue({\n el: '#root',\n data: {\n \"studyplans\": [],\n \"type\": type,\n \"invitekey\": (type == \"invited\") ? arg1 : null,\n \"userid\": (type == \"other\") ? arg1 : null,\n },\n methods: {\n\n },\n });\n\n}\n\n"],"names":["type","arg1","Vue","el","data","methods","use","RVComponents","PortalVue","BootstrapVue"],"mappings":"+YAoBsBA,4DAAO,MAAOC,4CACtB,IAAIC,aAAI,CACdC,GAAI,QACJC,KAAM,YACY,QACNJ,eACc,WAARA,KAAqBC,KAAO,YACvB,SAARD,KAAmBC,KAAO,MAEzCI,QAAS,+NArBbC,IAAIC,8CAGJD,IAAIE,iCAEJF,IAAIG"}

View File

@ -1,3 +1,3 @@
define("local_treestudyplan/page-result-overview",["exports","core/ajax","core/notification","./vue/vue","./util/debugger","./util/string-helper","./studyplan-report-components","./report-viewer-components","./modedit-modal","./portal-vue/portal-vue.esm","./bootstrap-vue/bootstrap-vue"],(function(_exports,_ajax,_notification,_vue,_debugger,_stringHelper,_studyplanReportComponents,_reportViewerComponents,_modeditModal,_portalVue,_bootstrapVue){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=function(studyplanid,pageid,firstperiod,lastperiod){if(void 0===pageid||!Number.isInteger(Number(pageid))||void 0===studyplanid||!Number.isInteger(Number(studyplanid)))return void debug.error("Error: studyplan id and page id not provided as integer numbers to script.",studyplanid,pageid,firstperiod,lastperiod);studyplanid=Number(studyplanid),pageid=Number(pageid);new _vue.default({el:"#root",data:{structure:null,studyplan:null,page:null,text:strings.studyplan_report},async mounted(){},created(){this.loadStructure(pageid,firstperiod,lastperiod)},computed:{},methods:{loadStructure(pageid,firstperiod,lastperiod){const self=this;this.structure=null,(0,_ajax.call)([{methodname:"local_treestudyplan_get_report_structure",args:{pageid:pageid,firstperiod:firstperiod,lastperiod:lastperiod}}])[0].then((function(response){self.structure=response,self.studyplan=response.studyplan,self.page=response.page})).catch(_notification.default.exception)},selectedPage(e){debug.info("SelectedPage",e);const pageid=e.target.value;this.loadStructure(pageid)},selectedFirstPeriod(e){debug.info("selectedFirstPeriod",e);let f=e.target.value,l=this.structure.lastperiod;l<f&&(l=f),this.loadStructure(this.page.id,f,l)},selectedLastPeriod(e){debug.info("selectedLastPeriod",e);let f=this.structure.firstperiod,l=e.target.value;l<f&&(l=f),this.loadStructure(this.page.id,f,l)}}})},_notification=_interopRequireDefault(_notification),_vue=_interopRequireDefault(_vue),_debugger=_interopRequireDefault(_debugger),_studyplanReportComponents=_interopRequireDefault(_studyplanReportComponents),_reportViewerComponents=_interopRequireDefault(_reportViewerComponents),_modeditModal=_interopRequireDefault(_modeditModal),_portalVue=_interopRequireDefault(_portalVue),_bootstrapVue=_interopRequireDefault(_bootstrapVue),_vue.default.use(_studyplanReportComponents.default),_vue.default.use(_reportViewerComponents.default),_vue.default.use(_modeditModal.default),_vue.default.use(_portalVue.default),_vue.default.use(_bootstrapVue.default);let debug=new _debugger.default("treestudyplanviewer"),strings=(0,_stringHelper.load_strings)({studyplan_report:{studyplan_select_placeholder:"studyplan_select_placeholder",studyplan:"studyplan",page:"studyplanpage",periods:"periods",period:"period",loading:"loading@core",all:"all@core",from:"from@core",to:"to@core"}})}));
define("local_treestudyplan/page-result-overview",["exports","core/ajax","core/notification","./vue/vue","./util/debugger","./util/string-helper","./studyplan-report-components","./report-viewer-components","./modedit-modal","./portal-vue/portal-vue.esm","./bootstrap-vue/bootstrap-vue"],(function(_exports,_ajax,_notification,_vue,_debugger,_stringHelper,_studyplanReportComponents,_reportViewerComponents,_modeditModal,_portalVue,_bootstrapVue){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=function(studyplanid,pageid,firstperiod,lastperiod){if(void 0===pageid||!Number.isInteger(Number(pageid))||void 0===studyplanid||!Number.isInteger(Number(studyplanid)))return void debug.error("Error: studyplan id and page id not provided as integer numbers to script.",studyplanid,pageid,firstperiod,lastperiod);studyplanid=Number(studyplanid),pageid=Number(pageid);new _vue.default({el:"#root",data:{structure:null,studyplan:null,page:null,text:strings.studyplanReport},created(){this.loadStructure(pageid,firstperiod,lastperiod)},computed:{},methods:{loadStructure(pageid,firstperiod,lastperiod){const self=this;this.structure=null,(0,_ajax.call)([{methodname:"local_treestudyplan_get_report_structure",args:{pageid:pageid,firstperiod:firstperiod,lastperiod:lastperiod}}])[0].then((response=>{self.structure=response,self.studyplan=response.studyplan,self.page=response.page})).catch(_notification.default.exception)},selectedPage(e){debug.info("SelectedPage",e);const pageid=e.target.value;this.loadStructure(pageid)},selectedFirstPeriod(e){debug.info("selectedFirstPeriod",e);let f=e.target.value,l=this.structure.lastperiod;l<f&&(l=f),this.loadStructure(this.page.id,f,l)},selectedLastPeriod(e){debug.info("selectedLastPeriod",e);let f=this.structure.firstperiod,l=e.target.value;l<f&&(l=f),this.loadStructure(this.page.id,f,l)}}})},_notification=_interopRequireDefault(_notification),_vue=_interopRequireDefault(_vue),_debugger=_interopRequireDefault(_debugger),_studyplanReportComponents=_interopRequireDefault(_studyplanReportComponents),_reportViewerComponents=_interopRequireDefault(_reportViewerComponents),_modeditModal=_interopRequireDefault(_modeditModal),_portalVue=_interopRequireDefault(_portalVue),_bootstrapVue=_interopRequireDefault(_bootstrapVue),_vue.default.use(_studyplanReportComponents.default),_vue.default.use(_reportViewerComponents.default),_vue.default.use(_modeditModal.default),_vue.default.use(_portalVue.default),_vue.default.use(_bootstrapVue.default);let debug=new _debugger.default("treestudyplanviewer"),strings=(0,_stringHelper.loadStrings)({studyplanReport:{studyplan:"studyplan",page:"studyplanpage",periods:"periods",period:"period",loading:"loading@core",all:"all@core",from:"from@core",to:"to@core"}})}));
//# sourceMappingURL=page-result-overview.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

View File

@ -1,3 +1,3 @@
define("local_treestudyplan/primary-nav-tools",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.hide_primary=function(hrefs){("string"==typeof hrefs||hrefs instanceof String)&&(hrefs=[hrefs]);if("object"==typeof hrefs&&Array.isArray(hrefs)){let css="";for(const ix in hrefs){const href=hrefs[ix];css+=`\n li > a[href*="${href}"] {\n display: none !important; \n }\n `,css+=`\n #usernavigation a[href*="${href}"] {\n display: none !important;\n }\n `}const element=document.createElement("style");element.setAttribute("type","text/css"),"textContent"in element?element.textContent=css:element.styleSheet.cssText=css,document.head.appendChild(element)}}}));
define("local_treestudyplan/primary-nav-tools",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.hidePrimary=function(hrefs){("string"==typeof hrefs||hrefs instanceof String)&&(hrefs=[hrefs]);if("object"==typeof hrefs&&Array.isArray(hrefs)){let css="";for(const ix in hrefs){const href=hrefs[ix];css+=`\n li > a[href*="${href}"] {\n display: none !important; \n }\n `,css+=`\n #usernavigation a[href*="${href}"] {\n display: none !important;\n }\n `}const element=document.createElement("style");element.setAttribute("type","text/css"),"textContent"in element?element.textContent=css:element.styleSheet.cssText=css,document.head.appendChild(element)}}}));
//# sourceMappingURL=primary-nav-tools.min.js.map

View File

@ -1 +1 @@
{"version":3,"file":"primary-nav-tools.min.js","sources":["../src/primary-nav-tools.js"],"sourcesContent":["/*eslint-env es6*/\n/*eslint no-console: \"off\"*/\n\n/**\n * Hide a primary navigation item by href\n * @param {string|Array} hrefs The link that should be hidden\n */\nexport function hide_primary(hrefs) {\n if(typeof hrefs === 'string' || hrefs instanceof String){\n hrefs = [hrefs];\n }\n\n if(typeof hrefs === 'object' && Array.isArray(hrefs)){\n let css = '' ;\n for(const ix in hrefs){\n const href = hrefs[ix];\n css += `\n li > a[href*=\"${href}\"] {\n display: none !important; \n }\n `;\n css += `\n #usernavigation a[href*=\"${href}\"] {\n display: none !important;\n }\n `;\n }\n\n\n const element = document.createElement('style');\n element.setAttribute('type', 'text/css');\n\n if ('textContent' in element) {\n element.textContent = css;\n } else {\n element.styleSheet.cssText = css;\n }\n\n document.head.appendChild(element);\n\n }\n}\n"],"names":["hrefs","String","Array","isArray","css","ix","href","element","document","createElement","setAttribute","textContent","styleSheet","cssText","head","appendChild"],"mappings":"sKAO6BA,QACL,iBAAVA,OAAsBA,iBAAiBC,UAC7CD,MAAQ,CAACA,WAGO,iBAAVA,OAAsBE,MAAMC,QAAQH,OAAO,KAC7CI,IAAM,OACN,MAAMC,MAAML,MAAM,OACZM,KAAON,MAAMK,IACnBD,KAAQ,mCACYE,4FAIpBF,KAAQ,8CACuBE,iGAO7BC,QAAUC,SAASC,cAAc,SACvCF,QAAQG,aAAa,OAAQ,YAEzB,gBAAiBH,QACjBA,QAAQI,YAAcP,IAEtBG,QAAQK,WAAWC,QAAUT,IAGjCI,SAASM,KAAKC,YAAYR"}
{"version":3,"file":"primary-nav-tools.min.js","sources":["../src/primary-nav-tools.js"],"sourcesContent":["/* eslint-env es6*/\n/* eslint no-console: \"off\"*/\n\n/**\n * Hide a primary navigation item by href\n * @param {string|Array} hrefs The link that should be hidden\n */\nexport function hidePrimary(hrefs) {\n if (typeof hrefs === 'string' || hrefs instanceof String) {\n hrefs = [hrefs];\n }\n\n if (typeof hrefs === 'object' && Array.isArray(hrefs)) {\n let css = '';\n for (const ix in hrefs) {\n const href = hrefs[ix];\n css += `\n li > a[href*=\"${href}\"] {\n display: none !important; \n }\n `;\n css += `\n #usernavigation a[href*=\"${href}\"] {\n display: none !important;\n }\n `;\n }\n\n\n const element = document.createElement('style');\n element.setAttribute('type', 'text/css');\n\n if ('textContent' in element) {\n element.textContent = css;\n } else {\n element.styleSheet.cssText = css;\n }\n\n document.head.appendChild(element);\n\n }\n}\n"],"names":["hrefs","String","Array","isArray","css","ix","href","element","document","createElement","setAttribute","textContent","styleSheet","cssText","head","appendChild"],"mappings":"qKAO4BA,QACH,iBAAVA,OAAsBA,iBAAiBC,UAC9CD,MAAQ,CAACA,WAGQ,iBAAVA,OAAsBE,MAAMC,QAAQH,OAAQ,KAC/CI,IAAM,OACL,MAAMC,MAAML,MAAO,OACdM,KAAON,MAAMK,IACnBD,KAAQ,mCACYE,4FAIpBF,KAAQ,8CACuBE,iGAO7BC,QAAUC,SAASC,cAAc,SACvCF,QAAQG,aAAa,OAAQ,YAEzB,gBAAiBH,QACjBA,QAAQI,YAAcP,IAEtBG,QAAQK,WAAWC,QAAUT,IAGjCI,SAASM,KAAKC,YAAYR"}

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/studyplan-processor",["exports"],(function(_exports){function ProcessStudyplan(studyplan){for(const ip in studyplan.pages){ProcessStudyplanPage(studyplan.pages[ip])}return studyplan}function ProcessStudyplanPage(page){let connections={};for(const il in page.studylines){const line=page.studylines[il];for(const is in line.slots){const slot=line.slots[is];if(void 0!==slot.courses)for(const ic in slot.courses){const itm=slot.courses[ic];for(const idx in itm.connections.in){const conn=itm.connections.in[idx];conn.id in connections?itm.connections[idx]=connections[conn.id]:connections[conn.id]=conn}for(const idx in itm.connections.out){const conn=itm.connections.out[idx];conn.id in connections?itm.connections[idx]=connections[conn.id]:connections[conn.id]=conn}}if(void 0!==slot.filters)for(const ix in slot.filters){const itm=slot.filters[ix];for(const idx in itm.connections.in){const conn=itm.connections.in[idx];conn.id in connections?itm.connections[idx]=connections[conn.id]:connections[conn.id]=conn}for(const idx in itm.connections.out){const conn=itm.connections.out[idx];conn.id in connections?itm.connections[idx]=connections[conn.id]:connections[conn.id]=conn}}}}return page}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.ProcessStudyplan=ProcessStudyplan,_exports.ProcessStudyplanPage=ProcessStudyplanPage,_exports.ProcessStudyplans=function(studyplans){for(const isx in studyplans){ProcessStudyplan(studyplans[isx])}return studyplans},_exports.objCopy=function(target,source,fields){null==fields&&(fields=Object.getOwnPropertyNames(source));for(const ix in fields){const field=fields[ix];target[field]=source[field]}return target},_exports.transportItem=function(target,source,identifier,param){param||(param="value");let item,itemindex;for(const ix in source)if(source[ix][param]==identifier){item=source[ix],itemindex=ix;break}item&&(target.push(item),source.splice(itemindex,1))}}));
define("local_treestudyplan/studyplan-processor",["exports"],(function(_exports){function processStudyplan(studyplan){for(const ip in studyplan.pages){processStudyplanPage(studyplan.pages[ip])}return studyplan}function processStudyplanPage(page){let connections={};for(const il in page.studylines){const line=page.studylines[il];for(const is in line.slots){const slot=line.slots[is];if(void 0!==slot.courses)for(const ic in slot.courses){const itm=slot.courses[ic];for(const idx in itm.connections.in){const conn=itm.connections.in[idx];conn.id in connections?itm.connections[idx]=connections[conn.id]:connections[conn.id]=conn}for(const idx in itm.connections.out){const conn=itm.connections.out[idx];conn.id in connections?itm.connections[idx]=connections[conn.id]:connections[conn.id]=conn}}if(void 0!==slot.filters)for(const ix in slot.filters){const itm=slot.filters[ix];for(const idx in itm.connections.in){const conn=itm.connections.in[idx];conn.id in connections?itm.connections[idx]=connections[conn.id]:connections[conn.id]=conn}for(const idx in itm.connections.out){const conn=itm.connections.out[idx];conn.id in connections?itm.connections[idx]=connections[conn.id]:connections[conn.id]=conn}}}}return page}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.objCopy=function(target,source,fields){null==fields&&(fields=Object.getOwnPropertyNames(source));for(const ix in fields){const field=fields[ix];target[field]=source[field]}return target},_exports.processStudyplan=processStudyplan,_exports.processStudyplanPage=processStudyplanPage,_exports.processStudyplans=function(studyplans){for(const isx in studyplans){processStudyplan(studyplans[isx])}return studyplans},_exports.transportItem=function(target,source,identifier,param){param||(param="value");let item,itemindex;for(const ix in source)if(source[ix][param]==identifier){item=source[ix],itemindex=ix;break}item&&(target.push(item),source.splice(itemindex,1))}}));
//# sourceMappingURL=studyplan-processor.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

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,3 @@
define("local_treestudyplan/util/browserbuttonevents",["exports","./debugger"],(function(_exports,_debugger){var obj;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.addBrowserButtonEvent=function(backwardCB){let forwardCB=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,reloadCB=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;debug.log("Registering navigation events",backwardCB,forwardCB,reloadCB);addEventListener("popstate",(e=>{const positionLastShown=Number(sessionStorage.getItem("positionLastShown"));let position=history.state;null===position&&(position=positionLastShown+1,history.replaceState(position,"")),sessionStorage.setItem("positionLastShown",String(position));const direction=Math.sign(position-positionLastShown);debug.log("Travel direction is "+direction),-1==direction&&backwardCB&&backwardCB(),1==direction&&forwardCB&&forwardCB(),0==direction&&reloadCB&&reloadCB()}))};const debug=new(_debugger=(obj=_debugger)&&obj.__esModule?obj:{default:obj}).default("browserbuttonevents")}));
//# sourceMappingURL=browserbuttonevents.min.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"browserbuttonevents.min.js","sources":["../../src/util/browserbuttonevents.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 capitalized-comments: \"off\" */\n/* eslint-env es6 */\n\nimport Debugger from './debugger';\nconst debug = new Debugger(\"browserbuttonevents\");\n\n/**\n * \n * @param {function} backwardCB \n * @param {function} forwardCB \n * @param {function} reloadCB \n */\nexport function addBrowserButtonEvent(backwardCB, forwardCB = null, reloadCB = null) {\n debug.log(\"Registering navigation events\", backwardCB, forwardCB, reloadCB);\n const reorient = (e) => { // After travelling in the history stack\n const positionLastShown = Number( // If none, then zero\n sessionStorage.getItem('positionLastShown'));\n // debug.log(\"Popstate event\",e,positionLastShown,history);\n let position = history.state; // Absolute position in stack\n if (position === null) { // Meaning a new entry on the stack\n position = positionLastShown + 1; // Top of stack\n \n // (1) Stamp the entry with its own position in the stack\n history.replaceState(position, /* no title */''); \n }\n \n // (2) Keep track of the last position shown\n sessionStorage.setItem('positionLastShown', String(position));\n \n // (3) Discover the direction of travel by comparing the two\n const direction = Math.sign(position - positionLastShown);\n debug.log('Travel direction is ' + direction); \n // One of backward (-1), reload (0) and forward (1)\n if (direction == -1 && backwardCB) {\n backwardCB();\n }\n if (direction == 1 && forwardCB) {\n forwardCB();\n }\n if (direction == 0 && reloadCB) {\n reloadCB();\n }\n };\n // addEventListener( 'pageshow', reorient );\n addEventListener('popstate', reorient); // Travel in same page\n}"],"names":["backwardCB","forwardCB","reloadCB","debug","log","addEventListener","e","positionLastShown","Number","sessionStorage","getItem","position","history","state","replaceState","setItem","String","direction","Math","sign"],"mappings":"qNAgBsCA,gBAAYC,iEAAY,KAAMC,gEAAW,KAC3EC,MAAMC,IAAI,gCAAiCJ,WAAYC,UAAWC,UA+BlEG,iBAAiB,YA9BCC,UACRC,kBAAoBC,OACxBC,eAAeC,QAAQ,0BAErBC,SAAWC,QAAQC,MACN,OAAbF,WACAA,SAAWJ,kBAAoB,EAG/BK,QAAQE,aAAaH,SAAwB,KAIjDF,eAAeM,QAAQ,oBAAqBC,OAAOL,iBAG7CM,UAAYC,KAAKC,KAAKR,SAAWJ,mBACvCJ,MAAMC,IAAI,uBAAyBa,YAEjB,GAAdA,WAAmBjB,YACnBA,aAEa,GAAbiB,WAAkBhB,WAClBA,YAEa,GAAbgB,WAAkBf,UAClBA,qBApCNC,MAAQ,yEAAa"}

File diff suppressed because one or more lines are too long

View File

@ -1,3 +1,3 @@
define("local_treestudyplan/util/date-helper",["exports"],(function(_exports){function format_date(d,short){d instanceof Date||(d=new Date(d));let monthformat="short";return!0===short?monthformat="numeric":!1===short&&(monthformat="long"),d.toLocaleDateString(document.documentElement.lang,{year:"numeric",month:monthformat,day:"numeric"})}function studyplanDates(plan){let earliestStart=null,latestEnd=null,openEnded=!1;for(const ix in plan.pages){const page=plan.pages[ix],s=new Date(page.startdate);if(page.enddate||(openEnded=!0),(!earliestStart||s<earliestStart)&&(earliestStart=s),page.enddate){const e=new Date(page.enddate);(!latestEnd||e>latestEnd)&&(latestEnd=e)}}return{start:earliestStart,end:openEnded?null:latestEnd}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.add_days=function(datestr,days){const date=new Date(datestr);return function(date){const d=new Date(date);let month=""+(d.getMonth()+1),day=""+d.getDate();const year=d.getFullYear();month.length<2&&(month="0"+month);day.length<2&&(day="0"+day);return[year,month,day].join("-")}(new Date(date.getTime()+864e5*days))},_exports.datespaninfo=function(first,last){first instanceof Date||(first=new Date(first));last instanceof Date||(last=new Date(last));first.setHours(0),first.setMinutes(0),first.setSeconds(0),first.setMilliseconds(0),last.setHours(23),last.setMinutes(59),last.setSeconds(59),last.setMilliseconds(999);const dayspan=Math.round((last-first+1)/864e5),years=Math.floor(dayspan/365),ydaysleft=dayspan%365,weeks=Math.floor(ydaysleft/7);return{first:first,last:last,totaldays:dayspan,years:years,weeks:weeks,days:ydaysleft%7,formatted:{first:format_date(first),last:format_date(last)}}},_exports.format_date=format_date,_exports.format_datetime=function(d,short){d instanceof Date||(d=new Date(d));let monthformat="short";!0===short?monthformat="numeric":!1===short&&(monthformat="long");return d.toLocaleDateString(document.documentElement.lang,{year:"numeric",month:monthformat,day:"numeric"})+" "+d.toLocaleTimeString(document.documentElement.lang,{timeStyle:"short"})},_exports.studyplanDates=studyplanDates,_exports.studyplanPageTiming=function(page){const now=(new Date).getTime(),start=new Date(page.startdate),end=page.enddate?new Date(page.enddate):null;return start<now?end&&now>end?"past":"present":"future"},_exports.studyplanTiming=function(plan){const now=(new Date).getTime(),dates=studyplanDates(plan);return dates.start<now?dates.end&&now>dates.end?"past":"present":"future"}}));
define("local_treestudyplan/util/date-helper",["exports"],(function(_exports){function formatDate(d,short){d instanceof Date||("number"==typeof d&&(d*=1e3),d=new Date(d));let monthformat="short";return!0===short?monthformat="numeric":!1===short&&(monthformat="long"),d.toLocaleDateString(document.documentElement.lang,{year:"numeric",month:monthformat,day:"numeric"})}function studyplanDates(plan){let earliestStart=null,latestEnd=null,openEnded=!1;for(const ix in plan.pages){const page=plan.pages[ix],s=new Date(page.startdate);if(page.enddate||(openEnded=!0),(!earliestStart||s<earliestStart)&&(earliestStart=s),page.enddate){const e=new Date(page.enddate);(!latestEnd||e>latestEnd)&&(latestEnd=e)}}return{start:earliestStart,end:openEnded?null:latestEnd}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.addDays=function(datestr,days){const date=new Date(datestr);return function(date){const d=new Date(date);let month=""+(d.getMonth()+1),day=""+d.getDate();const year=d.getFullYear();month.length<2&&(month="0"+month);day.length<2&&(day="0"+day);return[year,month,day].join("-")}(new Date(date.getTime()+864e5*days))},_exports.datespaninfo=function(first,last){first instanceof Date||(first=new Date(first));last instanceof Date||(last=new Date(last));first.setHours(0),first.setMinutes(0),first.setSeconds(0),first.setMilliseconds(0),last.setHours(23),last.setMinutes(59),last.setSeconds(59),last.setMilliseconds(999);const dayspan=Math.round((last-first+1)/864e5),years=Math.floor(dayspan/365),ydaysleft=dayspan%365,weeks=Math.floor(ydaysleft/7);return{first:first,last:last,totaldays:dayspan,years:years,weeks:weeks,days:ydaysleft%7,formatted:{first:formatDate(first),last:formatDate(last)}}},_exports.formatDate=formatDate,_exports.formatDatetime=function(d,short){d instanceof Date||("number"==typeof d&&(d*=1e3),d=new Date(d));let monthformat="short";!0===short?monthformat="numeric":!1===short&&(monthformat="long");return d.toLocaleDateString(document.documentElement.lang,{year:"numeric",month:monthformat,day:"numeric"})+" "+d.toLocaleTimeString(document.documentElement.lang,{timeStyle:"short"})},_exports.studyplanDates=studyplanDates,_exports.studyplanPageTiming=function(page){const now=(new Date).getTime();if(page.timeless)return"present";const start=new Date(page.startdate),end=page.enddate?new Date(page.enddate):null;return start<now?end&&now>end?"past":"present":"future"},_exports.studyplanTiming=function(plan){const now=(new Date).getTime();if(!plan.pages&&0==plan.pages.length||plan.pages[0]&&plan.pages[0].timeless)return"present";const dates=studyplanDates(plan);return dates.start<now?dates.end&&now>dates.end?"past":"present":"future"}}));
//# sourceMappingURL=date-helper.min.js.map

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
{"version":3,"file":"debounce.min.js","sources":["../../src/util/debounce.js"],"sourcesContent":["/*eslint no-var: \"error\"*/\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\n/**\n * Limits consecutive function calls.\n * @param {function} func The function to wrap.\n * @param {int} wait The time limit between function calls.\n * @param {bool} immediate perform the actual function call first rather than after the timout passed.\n * @returns {function} a new function that wraps the debounce.\n */\nfunction debounce(func, wait, immediate) {\n let timeout;\n return function() {\n let context = this, args = arguments;\n let later = function() {\n timeout = null;\n if (!immediate){ func.apply(context, args); }\n };\n let callNow = immediate && !timeout;\n clearTimeout(timeout);\n timeout = setTimeout(later, wait);\n if (callNow){ func.apply(context, args); }\n };\n}\n\nexport {debounce};"],"names":["func","wait","immediate","timeout","context","this","args","arguments","callNow","clearTimeout","setTimeout","apply"],"mappings":"8JAakBA,KAAMC,KAAMC,eACtBC,eACG,eACCC,QAAUC,KAAMC,KAAOC,UAKvBC,QAAUN,YAAcC,QAC5BM,aAAaN,SACbA,QAAUO,YANE,WACRP,QAAU,KACLD,WAAYF,KAAKW,MAAMP,QAASE,QAIbL,MACxBO,SAAUR,KAAKW,MAAMP,QAASE"}
{"version":3,"file":"debounce.min.js","sources":["../../src/util/debounce.js"],"sourcesContent":["/* eslint no-var: \"error\"*/\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\n/**\n * Limits consecutive function calls.\n * @param {function} func The function to wrap.\n * @param {int} wait The time limit between function calls.\n * @param {bool} immediate perform the actual function call first rather than after the timout passed.\n * @returns {function} a new function that wraps the debounce.\n */\nfunction debounce(func, wait, immediate) {\n let timeout;\n return function() {\n let context = this;\n let args = arguments;\n let later = function() {\n timeout = null;\n if (!immediate) {\n func.apply(context, args);\n }\n };\n let callNow = immediate && !timeout;\n clearTimeout(timeout);\n timeout = setTimeout(later, wait);\n if (callNow) {\n func.apply(context, args);\n }\n };\n}\n\nexport {debounce};"],"names":["func","wait","immediate","timeout","context","this","args","arguments","callNow","clearTimeout","setTimeout","apply"],"mappings":"8JAakBA,KAAMC,KAAMC,eACtBC,eACG,eACCC,QAAUC,KACVC,KAAOC,UAOPC,QAAUN,YAAcC,QAC5BM,aAAaN,SACbA,QAAUO,YARE,WACRP,QAAU,KACLD,WACDF,KAAKW,MAAMP,QAASE,QAKAL,MACxBO,SACAR,KAAKW,MAAMP,QAASE"}

View File

@ -1,3 +1,3 @@
define("local_treestudyplan/util/debugger",["exports","core/config"],(function(_exports,_config){var obj;return Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=function(handle){let output_enabled=_config.default.developerdebug;output_enabled?console.warn(`In development environment. Debugger output enabled for ${handle}`):console.warn(`In production environment. Debugger output disabled for ${handle}`);return{write(){if(output_enabled){let args=Array.prototype.slice.call(arguments);args.unshift(handle+": "),console.info.apply(console,args)}},info(){if(output_enabled){let args=Array.prototype.slice.call(arguments);args.unshift(handle+": "),console.info.apply(console,args)}},warn(){if(output_enabled){let args=Array.prototype.slice.call(arguments);args.unshift(handle+": "),console.warn.apply(console,args)}},error(){if(output_enabled){let args=Array.prototype.slice.call(arguments);args.unshift(handle+": "),console.error.apply(console,args)}},time(){if(output_enabled){let args=Array.prototype.slice.call(arguments);args.unshift(handle+": "),console.time.apply(console,args)}},timeEnd(){if(output_enabled){let args=Array.prototype.slice.call(arguments);args.unshift(handle+": "),console.timeEnd.apply(console,args)}},timeLog(){if(output_enabled){let args=Array.prototype.slice.call(arguments);args.unshift(handle+": "),console.timeLog.apply(console,args)}},timeStamp(){if(output_enabled){let args=Array.prototype.slice.call(arguments);args.unshift(handle+": "),console.timeStamp.apply(console,args)}},enable:function(){output_enabled=!0},disable:function(){output_enabled=!1}}},_config=(obj=_config)&&obj.__esModule?obj:{default:obj},_exports.default}));
define("local_treestudyplan/util/debugger",["exports","core/config"],(function(_exports,_config){var obj;return Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=function(handle){let outputEnabled=_config.default.developerdebug;outputEnabled?console.warn(`In development environment. Debugger output enabled for ${handle}`):console.warn(`In production environment. Debugger output disabled for ${handle}`);return{write(){if(outputEnabled){let args=Array.prototype.slice.call(arguments);args.unshift(handle+": "),console.info.apply(console,args)}},log(){if(outputEnabled){let args=Array.prototype.slice.call(arguments);args.unshift(handle+": "),console.log.apply(console,args)}},info(){if(outputEnabled){let args=Array.prototype.slice.call(arguments);args.unshift(handle+": "),console.info.apply(console,args)}},warn(){if(outputEnabled){let args=Array.prototype.slice.call(arguments);args.unshift(handle+": "),console.warn.apply(console,args)}},error(){if(outputEnabled){let args=Array.prototype.slice.call(arguments);args.unshift(handle+": "),console.error.apply(console,args)}},time(){if(outputEnabled){let args=Array.prototype.slice.call(arguments);args.unshift(handle+": "),console.time.apply(console,args)}},timeEnd(){if(outputEnabled){let args=Array.prototype.slice.call(arguments);args.unshift(handle+": "),console.timeEnd.apply(console,args)}},timeLog(){if(outputEnabled){let args=Array.prototype.slice.call(arguments);args.unshift(handle+": "),console.timeLog.apply(console,args)}},timeStamp(){if(outputEnabled){let args=Array.prototype.slice.call(arguments);args.unshift(handle+": "),console.timeStamp.apply(console,args)}},enable(){outputEnabled=!0},disable(){outputEnabled=!1}}},_config=(obj=_config)&&obj.__esModule?obj:{default:obj},_exports.default}));
//# sourceMappingURL=debugger.min.js.map

View File

@ -1 +1 @@
{"version":3,"file":"debugger.min.js","sources":["../../src/util/debugger.js"],"sourcesContent":["/*eslint no-var: \"error\"*/\n/*eslint no-console: \"off\"*/\n/*eslint-env es6*/\n\nimport Config from \"core/config\";\n\n/**\n * Start a new debugger\n * @param {*} handle The string to attach to all messages from this debugger\n * @returns Debugger object\n */\nexport default function (handle) {\n let output_enabled = Config.developerdebug;\n if(output_enabled){\n console.warn(`In development environment. Debugger output enabled for ${handle}`);\n } else {\n console.warn(`In production environment. Debugger output disabled for ${handle}`);\n }\n\n return {\n write() {\n if (output_enabled) {\n let args = Array.prototype.slice.call(arguments);\n args.unshift(handle + \": \");\n console.info.apply(console, args);\n }\n },\n info() {\n if (output_enabled) {\n let args = Array.prototype.slice.call(arguments);\n args.unshift(handle + \": \");\n console.info.apply(console, args);\n }\n },\n warn() {\n if (output_enabled) {\n let args = Array.prototype.slice.call(arguments);\n args.unshift(handle + \": \");\n console.warn.apply(console, args);\n }\n },\n error() {\n if (output_enabled) {\n let args = Array.prototype.slice.call(arguments);\n args.unshift(handle + \": \");\n console.error.apply(console, args);\n }\n },\n time() {\n if (output_enabled) {\n let args = Array.prototype.slice.call(arguments);\n args.unshift(handle + \": \");\n console.time.apply(console, args);\n }\n },\n timeEnd() {\n if (output_enabled) {\n let args = Array.prototype.slice.call(arguments);\n args.unshift(handle + \": \");\n console.timeEnd.apply(console, args);\n }\n },\n timeLog() {\n if (output_enabled) {\n let args = Array.prototype.slice.call(arguments);\n args.unshift(handle + \": \");\n console.timeLog.apply(console, args);\n }\n },\n timeStamp() {\n if (output_enabled) {\n let args = Array.prototype.slice.call(arguments);\n args.unshift(handle + \": \");\n console.timeStamp.apply(console, args);\n }\n },\n enable: function debugger_enable() {\n output_enabled = true;\n },\n\n disable: function debugger_disable() {\n output_enabled = false;\n }\n };\n\n}\n"],"names":["handle","output_enabled","Config","developerdebug","console","warn","write","args","Array","prototype","slice","call","arguments","unshift","info","apply","error","time","timeEnd","timeLog","timeStamp","enable","disable"],"mappings":"kMAWyBA,YACjBC,eAAiBC,gBAAOC,eACzBF,eACCG,QAAQC,KAAM,2DAA0DL,UAExEI,QAAQC,KAAM,2DAA0DL,gBAGrE,CACHM,WACQL,eAAgB,KACZM,KAAOC,MAAMC,UAAUC,MAAMC,KAAKC,WACtCL,KAAKM,QAAQb,OAAS,MACtBI,QAAQU,KAAKC,MAAMX,QAASG,QAGpCO,UACQb,eAAgB,KACZM,KAAOC,MAAMC,UAAUC,MAAMC,KAAKC,WACtCL,KAAKM,QAAQb,OAAS,MACtBI,QAAQU,KAAKC,MAAMX,QAASG,QAGpCF,UACQJ,eAAgB,KACZM,KAAOC,MAAMC,UAAUC,MAAMC,KAAKC,WACtCL,KAAKM,QAAQb,OAAS,MACtBI,QAAQC,KAAKU,MAAMX,QAASG,QAGpCS,WACQf,eAAgB,KACZM,KAAOC,MAAMC,UAAUC,MAAMC,KAAKC,WACtCL,KAAKM,QAAQb,OAAS,MACtBI,QAAQY,MAAMD,MAAMX,QAASG,QAGrCU,UACQhB,eAAgB,KACZM,KAAOC,MAAMC,UAAUC,MAAMC,KAAKC,WACtCL,KAAKM,QAAQb,OAAS,MACtBI,QAAQa,KAAKF,MAAMX,QAASG,QAGpCW,aACQjB,eAAgB,KACZM,KAAOC,MAAMC,UAAUC,MAAMC,KAAKC,WACtCL,KAAKM,QAAQb,OAAS,MACtBI,QAAQc,QAAQH,MAAMX,QAASG,QAGvCY,aACQlB,eAAgB,KACZM,KAAOC,MAAMC,UAAUC,MAAMC,KAAKC,WACtCL,KAAKM,QAAQb,OAAS,MACtBI,QAAQe,QAAQJ,MAAMX,QAASG,QAGvCa,eACQnB,eAAgB,KACZM,KAAOC,MAAMC,UAAUC,MAAMC,KAAKC,WACtCL,KAAKM,QAAQb,OAAS,MACtBI,QAAQgB,UAAUL,MAAMX,QAASG,QAGzCc,OAAQ,WACJpB,gBAAiB,GAGrBqB,QAAS,WACLrB,gBAAiB"}
{"version":3,"file":"debugger.min.js","sources":["../../src/util/debugger.js"],"sourcesContent":["/* eslint no-var: \"error\"*/\n/* eslint no-console: \"off\"*/\n/* eslint-env es6*/\n\nimport Config from \"core/config\";\n\n/**\n * Start a new debugger\n * @param {*} handle The string to attach to all messages from this debugger\n * @returns {object} Debugger object\n */\nexport default function(handle) {\n let outputEnabled = Config.developerdebug;\n if (outputEnabled) {\n console.warn(`In development environment. Debugger output enabled for ${handle}`);\n } else {\n console.warn(`In production environment. Debugger output disabled for ${handle}`);\n }\n\n return {\n write() {\n if (outputEnabled) {\n let args = Array.prototype.slice.call(arguments);\n args.unshift(handle + \": \");\n console.info.apply(console, args);\n }\n },\n log() {\n if (outputEnabled) {\n let args = Array.prototype.slice.call(arguments);\n args.unshift(handle + \": \");\n console.log.apply(console, args);\n }\n },\n info() {\n if (outputEnabled) {\n let args = Array.prototype.slice.call(arguments);\n args.unshift(handle + \": \");\n console.info.apply(console, args);\n }\n },\n warn() {\n if (outputEnabled) {\n let args = Array.prototype.slice.call(arguments);\n args.unshift(handle + \": \");\n console.warn.apply(console, args);\n }\n },\n error() {\n if (outputEnabled) {\n let args = Array.prototype.slice.call(arguments);\n args.unshift(handle + \": \");\n console.error.apply(console, args);\n }\n },\n time() {\n if (outputEnabled) {\n let args = Array.prototype.slice.call(arguments);\n args.unshift(handle + \": \");\n console.time.apply(console, args);\n }\n },\n timeEnd() {\n if (outputEnabled) {\n let args = Array.prototype.slice.call(arguments);\n args.unshift(handle + \": \");\n console.timeEnd.apply(console, args);\n }\n },\n timeLog() {\n if (outputEnabled) {\n let args = Array.prototype.slice.call(arguments);\n args.unshift(handle + \": \");\n console.timeLog.apply(console, args);\n }\n },\n timeStamp() {\n if (outputEnabled) {\n let args = Array.prototype.slice.call(arguments);\n args.unshift(handle + \": \");\n console.timeStamp.apply(console, args);\n }\n },\n enable() {\n outputEnabled = true;\n },\n disable() {\n outputEnabled = false;\n }\n };\n\n}\n"],"names":["handle","outputEnabled","Config","developerdebug","console","warn","write","args","Array","prototype","slice","call","arguments","unshift","info","apply","log","error","time","timeEnd","timeLog","timeStamp","enable","disable"],"mappings":"kMAWwBA,YAChBC,cAAgBC,gBAAOC,eACvBF,cACAG,QAAQC,KAAM,2DAA0DL,UAExEI,QAAQC,KAAM,2DAA0DL,gBAGrE,CACHM,WACQL,cAAe,KACXM,KAAOC,MAAMC,UAAUC,MAAMC,KAAKC,WACtCL,KAAKM,QAAQb,OAAS,MACtBI,QAAQU,KAAKC,MAAMX,QAASG,QAGpCS,SACQf,cAAe,KACXM,KAAOC,MAAMC,UAAUC,MAAMC,KAAKC,WACtCL,KAAKM,QAAQb,OAAS,MACtBI,QAAQY,IAAID,MAAMX,QAASG,QAGnCO,UACQb,cAAe,KACXM,KAAOC,MAAMC,UAAUC,MAAMC,KAAKC,WACtCL,KAAKM,QAAQb,OAAS,MACtBI,QAAQU,KAAKC,MAAMX,QAASG,QAGpCF,UACQJ,cAAe,KACXM,KAAOC,MAAMC,UAAUC,MAAMC,KAAKC,WACtCL,KAAKM,QAAQb,OAAS,MACtBI,QAAQC,KAAKU,MAAMX,QAASG,QAGpCU,WACQhB,cAAe,KACXM,KAAOC,MAAMC,UAAUC,MAAMC,KAAKC,WACtCL,KAAKM,QAAQb,OAAS,MACtBI,QAAQa,MAAMF,MAAMX,QAASG,QAGrCW,UACQjB,cAAe,KACXM,KAAOC,MAAMC,UAAUC,MAAMC,KAAKC,WACtCL,KAAKM,QAAQb,OAAS,MACtBI,QAAQc,KAAKH,MAAMX,QAASG,QAGpCY,aACQlB,cAAe,KACXM,KAAOC,MAAMC,UAAUC,MAAMC,KAAKC,WACtCL,KAAKM,QAAQb,OAAS,MACtBI,QAAQe,QAAQJ,MAAMX,QAASG,QAGvCa,aACQnB,cAAe,KACXM,KAAOC,MAAMC,UAAUC,MAAMC,KAAKC,WACtCL,KAAKM,QAAQb,OAAS,MACtBI,QAAQgB,QAAQL,MAAMX,QAASG,QAGvCc,eACQpB,cAAe,KACXM,KAAOC,MAAMC,UAAUC,MAAMC,KAAKC,WACtCL,KAAKM,QAAQb,OAAS,MACtBI,QAAQiB,UAAUN,MAAMX,QAASG,QAGzCe,SACIrB,eAAgB,GAEpBsB,UACItB,eAAgB"}

View File

@ -1,3 +1,3 @@
define("local_treestudyplan/util/fittext-vue",["exports","./css-calc","./fitty","./textfit"],(function(_exports,_cssCalc,_fitty,_textfit){var obj;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_fitty=(obj=_fitty)&&obj.__esModule?obj:{default:obj};var _default={install(Vue){Vue.component("fittext",{props:{maxsize:{type:String,default:"512px"},minsize:{type:String,default:"10px"},vertical:Boolean,singleline:Boolean,dynamic:Boolean},data:()=>({resizeObserver:null,mutationObserver:null}),computed:{rootStyle(){return this.vertical?"height: 100%;":"width: 100%;"}},methods:{},mounted(){(0,_fitty.default)(this.$refs.text,{minSize:(0,_cssCalc.calc)(this.minsize),maxSize:(0,_cssCalc.calc)(this.maxsize),vertical:this.vertical,multiline:!this.singleline})},unmounted(){this.mutationObserver&&this.mutationObserver.disconnect(),this.resizeObserver&&this.resizeObserver.disconnect()},template:"\n <div class='q-fittext' ref='container' :style=\"rootStyle\">\n <span :style=\"'display:block; white-space:'+ ((singleline)?'nowrap':'normal')+';'\" class='q-fittext-text' ref='text'><slot></slot>\n </span\n ></div>\n "})}};return _exports.default=_default,_exports.default}));
define("local_treestudyplan/util/fittext-vue",["exports","./css-calc","./fitty"],(function(_exports,_cssCalc,_fitty){var obj;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_fitty=(obj=_fitty)&&obj.__esModule?obj:{default:obj};var _default={install(Vue){Vue.component("fittext",{props:{maxsize:{type:String,default:"512px"},minsize:{type:String,default:"10px"},vertical:Boolean,singleline:Boolean,dynamic:Boolean},data:()=>({resizeObserver:null,mutationObserver:null}),computed:{rootStyle(){return this.vertical?"height: 100%;":"width: 100%;"}},methods:{},mounted(){(0,_fitty.default)(this.$refs.text,{minSize:(0,_cssCalc.calc)(this.minsize),maxSize:(0,_cssCalc.calc)(this.maxsize),vertical:this.vertical,multiline:!this.singleline})},unmounted(){this.mutationObserver&&this.mutationObserver.disconnect(),this.resizeObserver&&this.resizeObserver.disconnect()},template:"\n <div class='q-fittext' ref='container' :style=\"rootStyle + ';min-height:0;min-width:0;'\">\n <span :style=\"'display:block; white-space:'+ ((singleline)?'nowrap':'normal')+';' + rootStyle\" class='q-fittext-text' ref='text'><slot></slot>\n </span\n ></div>\n "})}};return _exports.default=_default,_exports.default}));
//# sourceMappingURL=fittext-vue.min.js.map

View File

@ -1 +1 @@
{"version":3,"file":"fittext-vue.min.js","sources":["../../src/util/fittext-vue.js"],"sourcesContent":["/*eslint no-unused-vars: warn */\n/*eslint max-len: [\"error\", { \"code\": 160 }] */\n/*eslint-disable no-trailing-spaces */\n/*eslint-env es6*/\n\nimport {calc} from \"./css-calc\";\nimport fitty from \"./fitty\";\nimport {textFit} from \"./textfit\";\n\nexport default {\n install(Vue/*,options*/){\n Vue.component('fittext',{\n props: {\n maxsize: {\n type: String,\n default: \"512px\",\n },\n minsize: {\n type: String,\n default: \"10px\",\n },\n vertical: Boolean,\n singleline: Boolean,\n dynamic: Boolean,\n },\n data() {\n return {\n resizeObserver: null,\n mutationObserver: null,\n };\n },\n computed: {\n rootStyle() {\n if (this.vertical) {\n return `height: 100%;`;\n } else {\n return `width: 100%;`;\n }\n }\n },\n methods: {\n },\n mounted() {\n const self = this;\n // If the content could change after initial presentation,\n // Use the fitty method. It is slightly worse on multiline horizontal text,\n // but better supports content that can change later on.\n fitty(self.$refs.text,\n {\n minSize: calc(self.minsize),\n maxSize: calc(self.maxsize),\n vertical: self.vertical,\n multiline: !self.singleline,\n });\n },\n unmounted() {\n if(this.mutationObserver) {\n this.mutationObserver.disconnect();\n }\n if(this.resizeObserver) {\n this.resizeObserver.disconnect();\n }\n },\n template: `\n <div class='q-fittext' ref='container' :style=\"rootStyle\">\n <span :style=\"'display:block; white-space:'+ ((singleline)?'nowrap':'normal')+';'\" class='q-fittext-text' ref='text'><slot></slot>\n </span\n ></div>\n `,\n });\n },\n};"],"names":["install","Vue","component","props","maxsize","type","String","default","minsize","vertical","Boolean","singleline","dynamic","data","resizeObserver","mutationObserver","computed","rootStyle","this","methods","mounted","$refs","text","minSize","maxSize","multiline","unmounted","disconnect","template"],"mappings":"qSASe,CACXA,QAAQC,KACJA,IAAIC,UAAU,UAAU,CACxBC,MAAO,CACHC,QAAS,CACLC,KAAMC,OACNC,QAAS,SAEbC,QAAS,CACLH,KAAMC,OACNC,QAAS,QAEbE,SAAUC,QACVC,WAAYD,QACZE,QAASF,SAEbG,KAAI,KACO,CACHC,eAAgB,KAChBC,iBAAkB,OAG1BC,SAAU,CACNC,mBACQC,KAAKT,SACG,gBAEA,iBAIpBU,QAAS,GAETC,6BACiBF,KAIFG,MAAMC,KACb,CACAC,SAAS,iBANAL,KAMUV,SACnBgB,SAAS,iBAPAN,KAOUd,SACnBK,SARSS,KAQMT,SACfgB,WATSP,KASQP,cAGzBe,YACOR,KAAKH,uBACCA,iBAAiBY,aAEvBT,KAAKJ,qBACCA,eAAea,cAG5BC,SAAW"}
{"version":3,"file":"fittext-vue.min.js","sources":["../../src/util/fittext-vue.js"],"sourcesContent":["/* eslint no-unused-vars: warn */\n/* eslint max-len: [\"error\", { \"code\": 160 }] */\n/* eslint-disable no-trailing-spaces */\n/* eslint capitalized-comments: \"off\" */\n/* eslint-env es6*/\n\nimport {calc} from \"./css-calc\";\nimport fitty from \"./fitty\";\n\nexport default {\n install(Vue/* ,options */) {\n Vue.component('fittext', {\n props: {\n maxsize: {\n type: String,\n 'default': \"512px\",\n },\n minsize: {\n type: String,\n 'default': \"10px\",\n },\n vertical: Boolean,\n singleline: Boolean,\n dynamic: Boolean,\n },\n data() {\n return {\n resizeObserver: null,\n mutationObserver: null,\n };\n },\n computed: {\n rootStyle() {\n if (this.vertical) {\n return `height: 100%;`;\n } else {\n return `width: 100%;`;\n }\n }\n },\n methods: {\n },\n mounted() {\n const self = this;\n // If the content could change after initial presentation,\n // Use the fitty method. It is slightly worse on multiline horizontal text,\n // but better supports content that can change later on.\n fitty(self.$refs.text,\n {\n minSize: calc(self.minsize),\n maxSize: calc(self.maxsize),\n vertical: self.vertical,\n multiline: !self.singleline,\n });\n },\n unmounted() {\n if (this.mutationObserver) {\n this.mutationObserver.disconnect();\n }\n if (this.resizeObserver) {\n this.resizeObserver.disconnect();\n }\n },\n template: `\n <div class='q-fittext' ref='container' :style=\"rootStyle + ';min-height:0;min-width:0;'\">\n <span :style=\"'display:block; white-space:'+ ((singleline)?'nowrap':'normal')+';' + rootStyle\" class='q-fittext-text' ref='text'><slot></slot>\n </span\n ></div>\n `,\n });\n },\n};"],"names":["install","Vue","component","props","maxsize","type","String","minsize","vertical","Boolean","singleline","dynamic","data","resizeObserver","mutationObserver","computed","rootStyle","this","methods","mounted","$refs","text","minSize","maxSize","multiline","unmounted","disconnect","template"],"mappings":"gRASe,CACXA,QAAQC,KACJA,IAAIC,UAAU,UAAW,CACzBC,MAAO,CACHC,QAAS,CACLC,KAAMC,eACK,SAEfC,QAAS,CACLF,KAAMC,eACK,QAEfE,SAAUC,QACVC,WAAYD,QACZE,QAASF,SAEbG,KAAI,KACO,CACHC,eAAgB,KAChBC,iBAAkB,OAG1BC,SAAU,CACNC,mBACQC,KAAKT,SACG,gBAEA,iBAIpBU,QAAS,GAETC,6BACiBF,KAIFG,MAAMC,KACb,CACAC,SAAS,iBANAL,KAMUV,SACnBgB,SAAS,iBAPAN,KAOUb,SACnBI,SARSS,KAQMT,SACfgB,WATSP,KASQP,cAGzBe,YACQR,KAAKH,uBACAA,iBAAiBY,aAEtBT,KAAKJ,qBACAA,eAAea,cAG5BC,SAAW"}

File diff suppressed because one or more lines are too long

View File

@ -1,3 +1,3 @@
define("local_treestudyplan/util/formfields",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.text_integer=function(id){let unsigned=arguments.length>1&&void 0!==arguments[1]&&arguments[1],nonzero=arguments.length>2&&void 0!==arguments[2]&&arguments[2];const element=document.getElementById(id);element&&element.addEventListener("input",(()=>{var val=element.value;if(""!=val)return!(isNaN(val)&&(unsigned||"-"!=val)||unsigned&&val<0||nonzero&&0==val)||(unsigned?(element.value=val.replace(/[^0-9]/g,""),nonzero&&0==val&&(element.value="")):element.value=val.replace(/[^0-9-]/g,"").replace(/(.{1})(-)/g,"$1"),!1)}))}}));
define("local_treestudyplan/util/formfields",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.textInteger=function(id){let unsigned=arguments.length>1&&void 0!==arguments[1]&&arguments[1],nonzero=arguments.length>2&&void 0!==arguments[2]&&arguments[2];const element=document.getElementById(id);element&&element.addEventListener("input",(()=>{var val=element.value;return""!=val?!(isNaN(val)&&(unsigned||"-"!=val)||unsigned&&val<0||nonzero&&0==val)||(unsigned?(element.value=val.replace(/[^0-9]/g,""),nonzero&&0==val&&(element.value="")):element.value=val.replace(/[^0-9-]/g,"").replace(/(.{1})(-)/g,"$1"),!1):null}))}}));
//# sourceMappingURL=formfields.min.js.map

View File

@ -1 +1 @@
{"version":3,"file":"formfields.min.js","sources":["../../src/util/formfields.js"],"sourcesContent":["/**\n * convert a text field into an integer only text field\n * @param {string} id The Id of the form field\n * @param {bool} unsigned Allow only unsigned values\n * @param {bool} nonzero Do not allow zero values\n */\nexport function text_integer(id,unsigned=false,nonzero=false){\n const element = document.getElementById(id);\n\n if (element) {\n element.addEventListener(\"input\",() => {\n var val = element.value;\n if (val != '') {\n if ((isNaN(val) && !(!unsigned && val == '-')) || (unsigned && val < 0) || (nonzero && val == 0)) {\n // Set input value empty\n if (unsigned) {\n element.value = val.replace(/[^0-9]/g,'');\n if (nonzero && val == 0) {\n element.value = '';\n }\n } else {\n element.value = val.replace(/[^0-9-]/g,'').replace(/(.{1})(-)/g,'$1');\n }\n return false;\n } else {\n return true;\n }\n }\n });\n }\n}"],"names":["id","unsigned","nonzero","element","document","getElementById","addEventListener","val","value","isNaN","replace"],"mappings":"oKAM6BA,QAAGC,iEAAeC,sEACrCC,QAAUC,SAASC,eAAeL,IAEpCG,SACAA,QAAQG,iBAAiB,SAAQ,SACzBC,IAAMJ,QAAQK,SACP,IAAPD,YACKE,MAAMF,OAAWN,UAAmB,KAAPM,MAAiBN,UAAYM,IAAM,GAAOL,SAAkB,GAAPK,OAE/EN,UACAE,QAAQK,MAAQD,IAAIG,QAAQ,UAAU,IAClCR,SAAkB,GAAPK,MACXJ,QAAQK,MAAQ,KAGpBL,QAAQK,MAAQD,IAAIG,QAAQ,WAAW,IAAIA,QAAQ,aAAa,OAE7D"}
{"version":3,"file":"formfields.min.js","sources":["../../src/util/formfields.js"],"sourcesContent":["/**\n * Convert a text field into an integer only text field\n * @param {string} id The Id of the form field\n * @param {bool} unsigned Allow only unsigned values\n * @param {bool} nonzero Do not allow zero values\n */\nexport function textInteger(id, unsigned = false, nonzero = false) {\n const element = document.getElementById(id);\n\n if (element) {\n element.addEventListener(\"input\", () => {\n var val = element.value;\n if (val != '') {\n if ((isNaN(val) && !(!unsigned && val == '-')) || (unsigned && val < 0) || (nonzero && val == 0)) {\n // Set input value empty\n if (unsigned) {\n element.value = val.replace(/[^0-9]/g, '');\n if (nonzero && val == 0) {\n element.value = '';\n }\n } else {\n element.value = val.replace(/[^0-9-]/g, '').replace(/(.{1})(-)/g, '$1');\n }\n return false;\n } else {\n return true;\n }\n }\n return null;\n });\n }\n}"],"names":["id","unsigned","nonzero","element","document","getElementById","addEventListener","val","value","isNaN","replace"],"mappings":"mKAM4BA,QAAIC,iEAAkBC,sEACxCC,QAAUC,SAASC,eAAeL,IAEpCG,SACAA,QAAQG,iBAAiB,SAAS,SAC1BC,IAAMJ,QAAQK,YACP,IAAPD,MACKE,MAAMF,OAAWN,UAAmB,KAAPM,MAAiBN,UAAYM,IAAM,GAAOL,SAAkB,GAAPK,OAE/EN,UACAE,QAAQK,MAAQD,IAAIG,QAAQ,UAAW,IACnCR,SAAkB,GAAPK,MACXJ,QAAQK,MAAQ,KAGpBL,QAAQK,MAAQD,IAAIG,QAAQ,WAAY,IAAIA,QAAQ,aAAc,OAE/D,GAKR,IAAP"}

View File

@ -1,3 +1,3 @@
define("local_treestudyplan/util/mform-helper",["exports","core/ajax","core/fragment","core/templates","core/notification","./string-helper","./debugger"],(function(_exports,_ajax,_fragment,_templates,_notification,_stringHelper,_debugger){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_notification=_interopRequireDefault(_notification),_debugger=_interopRequireDefault(_debugger);var _default={install(Vue){let debug=new _debugger.default("treestudyplan-mform-helper"),strings=(0,_stringHelper.load_strings)({editmod:{save$core:"save$core",cancel$core:"cancel$core"}});Vue.component("mform",{props:{name:{type:String},params:{type:Object},title:{type:String,default:""},variant:{type:String,default:"primary"},type:{type:String,default:"link"}},data:()=>({content:"",loading:!0,uuid:void 0!==crypto.randomUUID?crypto.randomUUID():"10000000-1000-4000-8000-100000000000".replace(/[018]/g,(c=>(c^crypto.getRandomValues(new Uint8Array(1))[0]&15>>c/4).toString(16))),text:strings,submitok:!1,observer:null,inputs:[]}),computed:{},methods:{openForm(){this.$refs.editormodal.show()},onShown(){const self=this;debug.info(`Loading form "${self.name}" with params`,self.params),self.loading=!1,(0,_ajax.call)([{methodname:"local_treestudyplan_get_mform",args:{formname:self.name,params:JSON.stringify(self.params)}}])[0].then((data=>{const html=data.html;self.loading=!1;const js=(0,_fragment.processCollectedJavascript)(data.javascript);(0,_templates.replaceNodeContents)(self.$refs.content,html,js),self.initListenChanges()})).catch(_notification.default.exception)},onSave(){const self=this;let form=this.$refs.content.getElementsByTagName("form")[0];form.dispatchEvent(new Event("save-form-state"));const formdata=new FormData(form),data=new URLSearchParams(formdata).toString();this.checkSave()&&(0,_ajax.call)([{methodname:"local_treestudyplan_submit_mform",args:{formname:self.name,params:JSON.stringify(self.params),formdata:data}}])[0].then((response=>{const updatedplan=JSON.parse(response.data);self.$emit("saved",updatedplan,formdata)})).catch(_notification.default.exception)},checkSave(){let canSave=!0;return this.inputs.forEach((el=>{el.focus(),el.blur(),el.classList.contains("is-invalid")&&(canSave=!1)}),this),this.submitok=canSave,canSave},initListenChanges(){const content=this.$refs.content;this.inputs=content.querySelectorAll("input.form-control"),setTimeout(this.checkSave,100),this.observer&&this.observer.disconnect(),this.observer=new MutationObserver((mutationList=>{for(const mix in mutationList){const mutation=mutationList[mix];"attributes"===mutation.type&&"class"===mutation.attributeName&&this.checkSave()}})),this.inputs.forEach((el=>{this.observer.observe(el,{attributes:!0})}),this)}},unmount(){this.observer&&this.observer.disconnect()},template:'\n <span class=\'mform-container\'>\n <b-button :variant="variant" v-if=\'type == "button"\' @click.prevent=\'openForm\'\n ><slot><i class=\'fa fa-gear\'></i></slot></b-button>\n <a variant="variant" v-else href=\'#\' @click.prevent=\'openForm\'\n ><slot><i class=\'fa fa-gear\'></i></slot></a>\n <b-modal\n ref="editormodal"\n scrollable\n centered\n size="xl"\n id="\'modal-\'+uuid"\n @shown="onShown"\n @ok="onSave"\n :ok-disabled="!submitok"\n :title="title"\n :ok-title="text.save$core"\n ><div :class="\'s-mform-content\'" ref="content"\n ><div class="d-flex justify-content-center mb-3"\n ><b-spinner variant="primary"></b-spinner\n ></div\n ></div\n ></b-modal>\n </span>\n '})}};return _exports.default=_default,_exports.default}));
define("local_treestudyplan/util/mform-helper",["exports","core/ajax","core/fragment","core/templates","core/notification","./string-helper","./debugger"],(function(_exports,_ajax,_fragment,_templates,_notification,_stringHelper,_debugger){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_notification=_interopRequireDefault(_notification),_debugger=_interopRequireDefault(_debugger);var _default={install(Vue){let debug=new _debugger.default("treestudyplan-mform-helper"),strings=(0,_stringHelper.loadStrings)({editmod:{save$core:"save$core",cancel$core:"cancel$core"}});Vue.component("mform",{props:{name:{type:String},params:{type:Object},title:{type:String,default:""},variant:{type:String,default:"primary"},type:{type:String,default:"link"}},data:()=>({content:"",loading:!0,uuid:void 0!==crypto.randomUUID?crypto.randomUUID():"10000000-1000-4000-8000-100000000000".replace(/[018]/g,(c=>(c^crypto.getRandomValues(new Uint8Array(1))[0]&15>>c/4).toString(16))),text:strings,submitok:!1,observer:null,inputs:[]}),computed:{},methods:{openForm(){this.$refs.editormodal.show()},onShown(){const self=this;debug.info(`Loading form "${self.name}" with params`,self.params),self.loading=!1,(0,_ajax.call)([{methodname:"local_treestudyplan_get_mform",args:{formname:self.name,params:JSON.stringify(self.params)}}])[0].then((data=>{const html=data.html;self.loading=!1;const js=(0,_fragment.processCollectedJavascript)(data.javascript);(0,_templates.replaceNodeContents)(self.$refs.content,html,js),self.initListenChanges()})).catch(_notification.default.exception)},onSave(){const self=this;let form=this.$refs.content.getElementsByTagName("form")[0];form.dispatchEvent(new Event("save-form-state"));const formdata=new FormData(form),data=new URLSearchParams(formdata).toString();this.checkSave()&&(0,_ajax.call)([{methodname:"local_treestudyplan_submit_mform",args:{formname:self.name,params:JSON.stringify(self.params),formdata:data}}])[0].then((response=>{const updatedplan=JSON.parse(response.data);self.$emit("saved",updatedplan,formdata)})).catch(_notification.default.exception)},checkSave(){let canSave=!0;return this.inputs.forEach((el=>{el.focus(),el.blur(),el.classList.contains("is-invalid")&&(canSave=!1)}),this),this.submitok=canSave,canSave},initListenChanges(){const content=this.$refs.content;this.inputs=content.querySelectorAll("input.form-control"),setTimeout(this.checkSave,100),this.observer&&this.observer.disconnect(),this.observer=new MutationObserver((mutationList=>{for(const mix in mutationList){const mutation=mutationList[mix];"attributes"===mutation.type&&"class"===mutation.attributeName&&this.checkSave()}})),this.inputs.forEach((el=>{this.observer.observe(el,{attributes:!0})}),this)}},unmount(){this.observer&&this.observer.disconnect()},template:'\n <span class=\'mform-container\'>\n <b-button :variant="variant" v-if=\'type == "button"\' @click.prevent=\'openForm\'\n ><slot><i class=\'fa fa-gear\'></i></slot></b-button>\n <a variant="variant" v-else href=\'#\' @click.prevent=\'openForm\'\n ><slot><i class=\'fa fa-gear\'></i></slot></a>\n <b-modal\n ref="editormodal"\n scrollable\n centered\n size="xl"\n id="\'modal-\'+uuid"\n @shown="onShown"\n @ok="onSave"\n :ok-disabled="!submitok"\n :title="title"\n :ok-title="text.save$core"\n ><div :class="\'s-mform-content\'" ref="content"\n ><div class="d-flex justify-content-center mb-3"\n ><b-spinner variant="primary"></b-spinner\n ></div\n ></div\n ></b-modal>\n </span>\n '})}};return _exports.default=_default,_exports.default}));
//# sourceMappingURL=mform-helper.min.js.map

File diff suppressed because one or more lines are too long

View File

@ -1,3 +1,3 @@
define("local_treestudyplan/util/premium",["exports","core/ajax"],(function(_exports,_ajax){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.enabled=function(){return!!premiumstatus.enabled};let premiumstatus={enabled:!1,website:"",name:"",expires:""}}));
define("local_treestudyplan/util/premium",["exports","core/ajax","core/notification"],(function(_exports,_ajax,_notification){var obj;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.premiumenabled=function(){const premium=premiumstatus();return!premium||!!premium.enabled},_notification=(obj=_notification)&&obj.__esModule?obj:{default:obj};let premiumstatusCache=null;function premiumstatus(){return premiumstatusCache||(0,_ajax.call)([{methodname:"local_treestudyplan_premiumstatus",args:{}}])[0].then((response=>{premiumstatusCache=response})).catch(_notification.default.exception),premiumstatusCache}premiumstatus()}));
//# sourceMappingURL=premium.min.js.map

View File

@ -1 +1 @@
{"version":3,"file":"premium.min.js","sources":["../../src/util/premium.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\nimport {call} from 'core/ajax';\n\n// Prepare default value.\nlet premiumstatus = {\n enabled: false,\n website: \"\",\n name: \"\",\n expires: \"\",\n};\n\n/**\n * Check if premium status is enabled.\n * @returns {Object} The map with strings loaded in\n */\nexport function enabled (){\n return !!premiumstatus.enabled;\n}\n"],"names":["premiumstatus","enabled","website","name","expires"],"mappings":"wLAqBaA,cAAcC,aAZvBD,cAAgB,CAChBC,SAAS,EACTC,QAAS,GACTC,KAAM,GACNC,QAAS"}
{"version":3,"file":"premium.min.js","sources":["../../src/util/premium.js"],"sourcesContent":["/* eslint no-var: \"error\" */\n/* eslint-env es6*/\n\nimport {call} from 'core/ajax';\nimport notification from 'core/notification';\n\n// Prepare default value.\nlet premiumstatusCache = null;\n\n/**\n * Check if premium status is enabled.\n * @returns {Bool} True/False\n */\nexport function premiumenabled() {\n const premium = premiumstatus();\n if (premium) {\n return (premium.enabled) ? true : false;\n } else {\n return true; // Temporarily return true, that is easier than going full async.\n }\n}\n\n/**\n * Get premium status object\n * @returns {Object} The premium status object\n */\nfunction premiumstatus() {\n if (!premiumstatusCache) {\n // Retrieve premium status if needed.\n call([{\n methodname: 'local_treestudyplan_premiumstatus',\n args: {}\n }])[0].then((response) => {\n premiumstatusCache = response;\n return;\n }).catch(notification.exception);\n }\n return premiumstatusCache;\n}\n\n// Preload premium status.\npremiumstatus();"],"names":["premium","premiumstatus","enabled","premiumstatusCache","methodname","args","then","response","catch","notification","exception"],"mappings":"uOAcUA,QAAUC,uBACZD,WACQA,QAAQE,iFATpBC,mBAAqB,cAmBhBF,uBACAE,mCAEI,CAAC,CACFC,WAAY,oCACZC,KAAM,MACN,GAAGC,MAAMC,WACTJ,mBAAqBI,YAEtBC,MAAMC,sBAAaC,WAEnBP,mBAIXF"}

3
amd/build/util/psidebar-vue.min.js vendored Normal file
View File

@ -0,0 +1,3 @@
define("local_treestudyplan/util/psidebar-vue",["exports","./debugger"],(function(_exports,_debugger){var obj;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;let debug=new(_debugger=(obj=_debugger)&&obj.__esModule?obj:{default:obj}).default("p-sidebar");var _default={install(Vue){Vue.component("p-sidebar",{props:{value:{type:Boolean,default:!0},right:{type:Boolean,default:!1},shadow:{type:Boolean,default:!1},target:{type:String,default:"body"},offsetRef:{type:String,default:""}},data:()=>({wrapper:null,contentwrapper:null,resizeobserver:null}),computed:{},methods:{initWrappers(target){let initializeWrapperContent=!1;if(this.wrapper=document.querySelector("#p-sidebar-wrapper"),this.wrapper||(initializeWrapperContent=!0,this.wrapper=document.createElement("div"),this.wrapper.setAttribute("id","p-sidebar-wrapper")),this.contentwrapper=document.querySelector("#p-sidebar-contentwrapper"),this.contentwrapper||(initializeWrapperContent=!0,this.contentwrapper=document.createElement("div"),this.contentwrapper.setAttribute("id","p-sidebar-contentwrapper"),this.wrapper.appendChild(this.contentwrapper)),initializeWrapperContent){let targetEl=document.querySelector(target);for(console.info(`Targeting '${target}' to `,targetEl),targetEl&&"HTML"!=targetEl.nodeType||(targetEl=document.querySelector("body")),debug.warn("Initializing wrappers with content of target ",targetEl);targetEl.childNodes.length>0;)this.contentwrapper.appendChild(targetEl.childNodes[0]);targetEl.appendChild(this.wrapper)}},rePosition(right){const el=this.$refs.container;right?this.wrapper.insertBefore(el,this.contentwrapper.nextSibling):this.wrapper.insertBefore(el,this.contentwrapper)},setOffset(reference){const ref=reference?document.querySelector(reference):null;console.info(`Setting offset from '${reference}'`,ref);let offsetTop=ref?ref.offsetTop:0;offsetTop+=0!=offsetTop?"px":"";const el=this.$refs.container;el.style.height=`calc( 100vh - ${offsetTop})`,el.style.marginTop=offsetTop}},watch:{right(newVal){this.rePosition(newVal)},offsetRef(reference){this.setOffset(reference)}},mounted(){const el=this.$refs.container;this.initWrappers(this.target),this.setOffset(this.offsetRef),this.rePosition(this.right,this.besides),this.resizeObserver=new ResizeObserver((()=>{let wx=0-el.getBoundingClientRect().width;wx+=0!=wx?"px":"",el.style.setProperty("--p-sidebar-hideoffset",wx)})),this.resizeObserver.observe(el)},unmounted(){this.resizeObserver&&this.resizeObserver.disconnect()},template:"\n <div>\n <div ref='container' \n :class=\"'p-sidebar ' + (right?'p-sidebar-right ':'') + (shadow?'p-sidebar-shadow ':'') + (value?'shown ':'hidden ')\"\n ><slot></slot></div>\n </div>\n "})}};return _exports.default=_default,_exports.default}));
//# sourceMappingURL=psidebar-vue.min.js.map

File diff suppressed because one or more lines are too long

3
amd/build/util/settings.min.js vendored Normal file
View File

@ -0,0 +1,3 @@
define("local_treestudyplan/util/settings",["exports","core/ajax","core/notification"],(function(_exports,_ajax,_notification){var obj;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.settings=function(key){const settings=loadsettings();return key in settings?settings[key]:null},_notification=(obj=_notification)&&obj.__esModule?obj:{default:obj};let settingcache=null;function loadsettings(){return settingcache||(0,_ajax.call)([{methodname:"local_treestudyplan_getsettings",args:{}}])[0].then((response=>{settingcache=response})).catch(_notification.default.exception),settingcache}loadsettings()}));
//# sourceMappingURL=settings.min.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"settings.min.js","sources":["../../src/util/settings.js"],"sourcesContent":["/* eslint no-var: \"error\" */\n/* eslint-env es6*/\n\nimport {call} from 'core/ajax';\nimport notification from 'core/notification';\n\n// Prepare default value.\nlet settingcache = null;\n\n/**\n * Check if premium status is enabled.\n * @param {String} key\n * @returns {*}\n */\nexport function settings(key) {\n const settings = loadsettings();\n if (key in settings) {\n return settings[key];\n } else {\n return null;\n }\n}\n\n/**\n * Get settings from server or cache\n * @returns {Object} The settings object\n */\nfunction loadsettings() {\n if (!settingcache) {\n // Retrieve setting cache if needed.\n call([{\n methodname: 'local_treestudyplan_getsettings',\n args: {}\n }])[0].then((response) => {\n settingcache = response;\n return;\n }).catch(notification.exception);\n }\n return settingcache;\n}\n\n// Preload premium status.\nloadsettings();"],"names":["key","settings","loadsettings","settingcache","methodname","args","then","response","catch","notification","exception"],"mappings":"0NAcyBA,WACfC,SAAWC,sBACbF,OAAOC,SACAA,SAASD,KAET,8EAZXG,aAAe,cAoBVD,sBACAC,6BAEI,CAAC,CACFC,WAAY,kCACZC,KAAM,MACN,GAAGC,MAAMC,WACTJ,aAAeI,YAEhBC,MAAMC,sBAAaC,WAEnBP,aAIXD"}

View File

@ -1,3 +1,3 @@
define("local_treestudyplan/util/string-helper",["exports","core/str"],(function(_exports,_str){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.load_stringkeys=function(string_keys){for(let idx in string_keys){let stringkeys=[];for(const i in string_keys[idx]){let parts=string_keys[idx][i].textkey.split("$"),identifier=parts[0],component=parts.length>1?parts[1]:"local_treestudyplan";stringkeys.push({key:identifier,component:component})}getstr_func(stringkeys).then((function(strings){for(const i in strings){const s=strings[i];string_keys[idx][i].text=s}}))}return string_keys},_exports.load_strings=function(strings){for(let idx in strings){let stringkeys=[];for(const handle in strings[idx]){let parts=strings[idx][handle].split(/[$@]/),identifier=parts[0],component=parts.length>1?parts[1]:"local_treestudyplan";stringkeys.push({key:identifier,component:component})}getstr_func(stringkeys).then((function(str){let i=0;for(const handle in strings[idx])strings[idx][handle]=str[i],i++}))}return strings},_exports.strformat=function(str,values){return str.replace(/\{(\w+)\}/g,((m,m1)=>m1&&values.hasOwnProperty(m1)?values[m1]:m))};const getstr_func=void 0!==_str.getStrings?_str.getStrings:_str.get_strings}));
define("local_treestudyplan/util/string-helper",["exports","core/str","./debugger"],(function(_exports,_str,_debugger){var obj;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.loadStringKeys=function(string_keys,plugin){void 0===plugin&&(plugin="local_treestudyplan");for(let idx in string_keys){let stringkeys=[];for(const i in string_keys[idx]){let parts=string_keys[idx][i].textkey.split("$"),identifier=parts[0],component=parts.length>1?parts[1]:plugin;stringkeys.push({key:identifier,component:component})}getstrFunc(stringkeys).then((function(strings){for(const i in strings){const s=strings[i];string_keys[idx][i].text=s}})).catch((x=>{debug.warn(x)}))}return string_keys},_exports.loadStrings=function(strings,plugin){void 0===plugin&&(plugin="local_treestudyplan");for(let idx in strings){let stringkeys=[];for(const handle in strings[idx]){let parts=strings[idx][handle].split(/[$@]/),identifier=parts[0],component=parts.length>1?parts[1]:plugin;stringkeys.push({key:identifier,component:component})}getstrFunc(stringkeys).then((function(str){let i=0;for(const handle in strings[idx])strings[idx][handle]=str[i],i++})).catch((x=>{debug.warn(x)}))}return strings},_exports.strformat=function(str,values){return str.replace(/\{(\w+)\}/g,((m,m1)=>m1&&values.hasOwnProperty(m1)?values[m1]:m))};let debug=new(_debugger=(obj=_debugger)&&obj.__esModule?obj:{default:obj}).default("string-helper");const getstrFunc=void 0!==_str.getStrings?_str.getStrings:_str.get_strings}));
//# sourceMappingURL=string-helper.min.js.map

View File

@ -1 +1 @@
{"version":3,"file":"string-helper.min.js","sources":["../../src/util/string-helper.js"],"sourcesContent":["import {get_strings} from 'core/str';\nimport {getStrings} from 'core/str';\n\n/* Determine the proper getstrings function to use (MDL4.3+ recommends use of getStrings, which is jquery independent) */\nconst getstr_func = (getStrings !== undefined)?getStrings:get_strings;\n\n/**\n * Load the translation of strings from a strings object\n * @param {Object} strings The map of strings\n * @returns {Object} The map with strings loaded in\n */\nexport function load_strings(strings){\n for(let idx in strings){\n let stringkeys = [];\n for(const handle in strings[idx]){\n const key = strings[idx][handle];\n let parts = key.split(/[$@]/);\n let identifier = parts[0];\n let component = (parts.length > 1)?parts[1]:'local_treestudyplan';\n stringkeys.push({ key: identifier, component: component});\n }\n getstr_func(stringkeys).then(function(str){\n let i = 0;\n for(const handle in strings[idx]){\n strings[idx][handle] = str[i];\n i++;\n }\n });\n }\n\n return strings;\n}\n\n/**\n * Load the translation of strings from a strings object based on keys\n * Used for loading values for a drop down menu or the like\n * @param {Object} string_keys The map of stringkeys\n * @returns {Object} The map with strings loaded in\n */\nexport function load_stringkeys(string_keys){\n for(let idx in string_keys){\n // Get text strings for condition settings\n let stringkeys = [];\n for(const i in string_keys[idx]){\n const key = string_keys[idx][i].textkey;\n let parts = key.split(\"$\");\n let identifier = parts[0];\n let component = (parts.length > 1)?parts[1]:'local_treestudyplan';\n stringkeys.push({ key: identifier, component: component});\n }\n getstr_func(stringkeys).then(function(strings){\n for(const i in strings) {\n const s = strings[i];\n const l = string_keys[idx][i];\n l.text = s;\n }\n });\n }\n return string_keys;\n}\n\n/**\n * String formatting function - replaces {name} in string by value of same key in values parameter\n * @param {string} str String t\n * @param {object} values Object containing keys to replace {key} strings with\n * @returns Formatted string\n */\nexport function strformat(str,values) {\n return str.replace(/\\{(\\w+)\\}/g, (m, m1) => {\n if (m1 && values.hasOwnProperty(m1)) {\n return values[m1];\n } else {\n return m;\n }\n });\n}"],"names":["string_keys","idx","stringkeys","i","parts","textkey","split","identifier","component","length","push","key","getstr_func","then","strings","s","text","handle","str","values","replace","m","m1","hasOwnProperty","undefined","getStrings","get_strings"],"mappings":"0LAuCgCA,iBACxB,IAAIC,OAAOD,YAAY,KAEnBE,WAAa,OACb,MAAMC,KAAMH,YAAYC,KAAK,KAEzBG,MADQJ,YAAYC,KAAKE,GAAGE,QAChBC,MAAM,KAClBC,WAAaH,MAAM,GACnBI,UAAaJ,MAAMK,OAAS,EAAGL,MAAM,GAAG,sBAC5CF,WAAWQ,KAAK,CAAEC,IAAKJ,WAAYC,UAAWA,YAElDI,YAAYV,YAAYW,MAAK,SAASC,aAC9B,MAAMX,KAAKW,QAAS,OACdC,EAAID,QAAQX,GACRH,YAAYC,KAAKE,GACzBa,KAAOD,aAIdf,4CA/CkBc,aACrB,IAAIb,OAAOa,QAAQ,KACfZ,WAAa,OACb,MAAMe,UAAWH,QAAQb,KAAK,KAE1BG,MADQU,QAAQb,KAAKgB,QACTX,MAAM,QAClBC,WAAaH,MAAM,GACnBI,UAAaJ,MAAMK,OAAS,EAAGL,MAAM,GAAG,sBAC5CF,WAAWQ,KAAK,CAAEC,IAAKJ,WAAYC,UAAWA,YAElDI,YAAYV,YAAYW,MAAK,SAASK,SAC9Bf,EAAI,MACJ,MAAMc,UAAUH,QAAQb,KACxBa,QAAQb,KAAKgB,QAAUC,IAAIf,GAC3BA,cAKLW,qCAqCeI,IAAIC,eACnBD,IAAIE,QAAQ,cAAc,CAACC,EAAGC,KAC7BA,IAAMH,OAAOI,eAAeD,IACrBH,OAAOG,IAEPD,WApEbT,iBAA8BY,IAAfC,gBAA0BA,gBAAWC"}
{"version":3,"file":"string-helper.min.js","sources":["../../src/util/string-helper.js"],"sourcesContent":["/* eslint camelcase: \"off\" */\nimport {get_strings} from 'core/str';\nimport {getStrings} from 'core/str';\nimport Debugger from './debugger';\nlet debug = new Debugger(\"string-helper\");\n\n/* Determine the proper getstrings function to use (MDL4.3+ recommends use of getStrings, which is jquery independent) */\nconst getstrFunc = (getStrings !== undefined) ? getStrings : get_strings;\n\n/**\n * Load the translation of strings from a strings object\n * @param {Object} strings The map of strings\n * @param {String} plugin Name of plugin to load for by default (leave empty for 'local_treestudyplan')\n * @returns {Object} The map with strings loaded in\n */\nexport function loadStrings(strings, plugin) {\n if (plugin === undefined) {\n plugin = 'local_treestudyplan';\n }\n for (let idx in strings) {\n let stringkeys = [];\n for (const handle in strings[idx]) {\n const key = strings[idx][handle];\n let parts = key.split(/[$@]/);\n let identifier = parts[0];\n let component = (parts.length > 1) ? parts[1] : plugin;\n stringkeys.push({key: identifier, component: component});\n }\n getstrFunc(stringkeys).then(function(str) {\n let i = 0;\n for (const handle in strings[idx]) {\n strings[idx][handle] = str[i];\n i++;\n }\n return;\n }).catch((x) => {\n debug.warn(x);\n });\n }\n\n return strings;\n}\n\n/**\n * Load the translation of strings from a strings object based on keys\n * Used for loading values for a drop down menu or the like\n * @param {Object} string_keys The map of stringkeys\n * @param {String} plugin Name of plugin to load for by default (leave empty for 'local_treestudyplan')\n * @returns {Object} The map with strings loaded in\n */\nexport function loadStringKeys(string_keys, plugin) {\n if (plugin === undefined) {\n plugin = 'local_treestudyplan';\n }\n for (let idx in string_keys) {\n // Get text strings for condition settings\n let stringkeys = [];\n for (const i in string_keys[idx]) {\n const key = string_keys[idx][i].textkey;\n let parts = key.split(\"$\");\n let identifier = parts[0];\n let component = (parts.length > 1) ? parts[1] : plugin;\n stringkeys.push({key: identifier, component: component});\n }\n getstrFunc(stringkeys).then(function(strings) {\n for (const i in strings) {\n const s = strings[i];\n const l = string_keys[idx][i];\n l.text = s;\n }\n return;\n }).catch((x) => {\n debug.warn(x);\n });\n }\n return string_keys;\n}\n\n/**\n * String formatting function - replaces {name} in string by value of same key in values parameter\n * @param {string} str String t\n * @param {object} values Object containing keys to replace {key} strings with\n * @returns {string} Formatted string\n */\nexport function strformat(str, values) {\n return str.replace(/\\{(\\w+)\\}/g, (m, m1) => {\n if (m1 && values.hasOwnProperty(m1)) {\n return values[m1];\n } else {\n return m;\n }\n });\n}"],"names":["string_keys","plugin","undefined","idx","stringkeys","i","parts","textkey","split","identifier","component","length","push","key","getstrFunc","then","strings","s","text","catch","x","debug","warn","handle","str","values","replace","m","m1","hasOwnProperty","getStrings","get_strings"],"mappings":"wNAkD+BA,YAAaC,aACzBC,IAAXD,SACAA,OAAS,2BAER,IAAIE,OAAOH,YAAa,KAErBI,WAAa,OACZ,MAAMC,KAAKL,YAAYG,KAAM,KAE1BG,MADQN,YAAYG,KAAKE,GAAGE,QAChBC,MAAM,KAClBC,WAAaH,MAAM,GACnBI,UAAaJ,MAAMK,OAAS,EAAKL,MAAM,GAAKL,OAChDG,WAAWQ,KAAK,CAACC,IAAKJ,WAAYC,UAAWA,YAEjDI,WAAWV,YAAYW,MAAK,SAASC,aAC5B,MAAMX,KAAKW,QAAS,OACfC,EAAID,QAAQX,GACRL,YAAYG,KAAKE,GACzBa,KAAOD,MAGdE,OAAOC,IACNC,MAAMC,KAAKF,aAGZpB,2CA5DiBgB,QAASf,aAClBC,IAAXD,SACAA,OAAS,2BAER,IAAIE,OAAOa,QAAS,KACjBZ,WAAa,OACZ,MAAMmB,UAAUP,QAAQb,KAAM,KAE3BG,MADQU,QAAQb,KAAKoB,QACTf,MAAM,QAClBC,WAAaH,MAAM,GACnBI,UAAaJ,MAAMK,OAAS,EAAKL,MAAM,GAAKL,OAChDG,WAAWQ,KAAK,CAACC,IAAKJ,WAAYC,UAAWA,YAEjDI,WAAWV,YAAYW,MAAK,SAASS,SAC7BnB,EAAI,MACH,MAAMkB,UAAUP,QAAQb,KACzBa,QAAQb,KAAKoB,QAAUC,IAAInB,GAC3BA,OAGLc,OAAOC,IACNC,MAAMC,KAAKF,aAIZJ,qCA4CeQ,IAAKC,eACpBD,IAAIE,QAAQ,cAAc,CAACC,EAAGC,KAC7BA,IAAMH,OAAOI,eAAeD,IACrBH,OAAOG,IAEPD,SArFfN,MAAQ,yEAAa,uBAGnBP,gBAA6BZ,IAAf4B,gBAA4BA,gBAAaC"}

View File

@ -1,3 +1,3 @@
define("local_treestudyplan/util/svgarc",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.svgarcpath=_exports.svgarc=void 0;const cos=Math.cos,sin=Math.sin,π=Math.PI,f_matrix_times=(_ref,_ref2)=>{let[[a,b],[c,d]]=_ref,[x,y]=_ref2;return[a*x+b*y,c*x+d*y]},f_vec_add=(_ref3,_ref4)=>{let[a1,a2]=_ref3,[b1,b2]=_ref4;return[a1+b1,a2+b2]},svgarcpath=(_ref5,_ref6,_ref7,φ)=>{let[cx,cy]=_ref5,[rx,ry]=_ref6,[t1,Δ]=_ref7;Δ%=2*π;const rotMatrix=[[cos(x=φ),-sin(x)],[sin(x),cos(x)]];var x;const[sX,sY]=f_vec_add(f_matrix_times(rotMatrix,[rx*cos(t1),ry*sin(t1)]),[cx,cy]),[eX,eY]=f_vec_add(f_matrix_times(rotMatrix,[rx*cos(t1+Δ),ry*sin(t1+Δ)]),[cx,cy]);return"M "+sX+" "+sY+" A "+[rx,ry,φ/(2*π)*360,Δ>π?1:0,Δ>0?1:0,eX,eY].join(" ")};_exports.svgarcpath=svgarcpath;_exports.svgarc=(_ref8,_ref9,_ref10,φ)=>{let[cx,cy]=_ref8,[rx,ry]=_ref9,[t1,Δ]=_ref10;const path_2wk2r=document.createElementNS("http://www.w3.org/2000/svg","path"),d=svgarcpath([cx,cy],[rx,ry],[t1,Δ],φ);return path_2wk2r.setAttribute("d",d),path_2wk2r}}));
define("local_treestudyplan/util/svgarc",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.svgarcpath=_exports.svgarc=void 0;const cos=Math.cos,sin=Math.sin,π=Math.PI,fMatrixTimes=(_ref,_ref2)=>{let[[a,b],[c,d]]=_ref,[x,y]=_ref2;return[a*x+b*y,c*x+d*y]},fVecAdd=(_ref3,_ref4)=>{let[a1,a2]=_ref3,[b1,b2]=_ref4;return[a1+b1,a2+b2]},svgarcpath=(_ref5,_ref6,_ref7,φ)=>{let[cx,cy]=_ref5,[rx,ry]=_ref6,[t1,Δ]=_ref7;Δ%=2*π;const rotMatrix=[[cos(x=φ),-sin(x)],[sin(x),cos(x)]];var x;const[sX,sY]=fVecAdd(fMatrixTimes(rotMatrix,[rx*cos(t1),ry*sin(t1)]),[cx,cy]),[eX,eY]=fVecAdd(fMatrixTimes(rotMatrix,[rx*cos(t1+Δ),ry*sin(t1+Δ)]),[cx,cy]),fA=Δ>π?1:0,fS=Δ>0?1:0;return isNaN(eY)||isNaN(eX)?"":"M "+sX+" "+sY+" A "+[rx,ry,φ/(2*π)*360,fA,fS,eX,eY].join(" ")};_exports.svgarcpath=svgarcpath;_exports.svgarc=(_ref8,_ref9,_ref10,φ)=>{let[cx,cy]=_ref8,[rx,ry]=_ref9,[t1,Δ]=_ref10;const path2wk2r=document.createElementNS("http://www.w3.org/2000/svg","path"),d=svgarcpath([cx,cy],[rx,ry],[t1,Δ],φ);return path2wk2r.setAttribute("d",d),path2wk2r}}));
//# sourceMappingURL=svgarc.min.js.map

View File

@ -1 +1 @@
{"version":3,"file":"svgarc.min.js","sources":["../../src/util/svgarc.js"],"sourcesContent":["/*\nCopyright © 2020 Xah Lee, © 2023 P.M Kuipers\nPermission is hereby granted, free of charge, to any person obtaining a\ncopy of this software and associated documentation files (the “Software”),\nto deal in the Software without restriction, including without limitation\nthe rights to use, copy, modify, merge, publish, distribute, sublicense,\nand/or sell copies of the Software, and to permit persons to whom the\nSoftware is furnished to do so, subject to the following conditions:\nThe above copyright notice and this permission notice shall be included\nin all copies or substantial portions of the Software.\nTHE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\nTHE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\nFROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\nURL: SVG Circle Arc http://xahlee.info/js/svg_circle_arc.html\n*/\n\nconst cos = Math.cos;\nconst sin = Math.sin;\nconst π = Math.PI;\n\nconst f_matrix_times = (( [[a,b], [c,d]], [x,y]) => [ a * x + b * y, c * x + d * y]);\nconst f_rotate_matrix = (x => [[cos(x),-sin(x)], [sin(x), cos(x)]]);\nconst f_vec_add = (([a1, a2], [b1, b2]) => [a1 + b1, a2 + b2]);\n\n// function modified by pmkuipers for text params\n/**\n * Create svg path text for an arc\n * @param {*} center [cx,cy] center of ellipse\n * @param {*} radius [rx,ry] major minor radius\n * @param {*} angle [t1, Δ] start angle, in radian, angle to sweep, in radian. positive.\n * @param {*} φ rotation on the whole, in radian\n * @returns a SVG path element that represent a ellipse. Text describing the arc path in an svg path element\n */\nconst svgarcpath = (([cx,cy],[rx,ry], [t1, Δ], φ ) => {\n Δ = Δ % (2*π);\n const rotMatrix = f_rotate_matrix (φ);\n const [sX, sY] = ( f_vec_add ( f_matrix_times ( rotMatrix, [rx * cos(t1), ry * sin(t1)] ), [cx,cy] ) );\n const [eX, eY] = ( f_vec_add ( f_matrix_times ( rotMatrix, [rx * cos(t1+Δ), ry * sin(t1+Δ)] ), [cx,cy] ) );\n const fA = ( ( Δ > π ) ? 1 : 0 );\n const fS = ( ( Δ > 0 ) ? 1 : 0 );\n return \"M \" + sX + \" \" + sY + \" A \" + [ rx , ry , φ / (2*π) *360, fA, fS, eX, eY ].join(\" \");\n});\n\n/**\n * Create an svg arc element\n * @param {*} center [cx,cy] center of ellipse\n * @param {*} radius [rx,ry] major minor radius\n * @param {*} angle [t1, Δ] start angle, in radian, angle to sweep, in radian. positive.\n * @param {*} φ rotation on the whole, in radian\n * @returns a SVG path element that represent a ellipse.\n */\nconst svgarc = (([cx,cy],[rx,ry], [t1, Δ], φ ) => {\n const path_2wk2r = document.createElementNS(\"http://www.w3.org/2000/svg\", \"path\");\n const d = svgarcpath([cx,cy],[rx,ry], [t1, Δ], φ );\n path_2wk2r.setAttribute(\"d\", d);\n return path_2wk2r;\n});\n\nexport {svgarc, svgarcpath};"],"names":["cos","Math","sin","π","PI","f_matrix_times","a","b","c","d","x","y","f_vec_add","a1","a2","b1","b2","svgarcpath","φ","cx","cy","rx","ry","t1","Δ","rotMatrix","sX","sY","eX","eY","join","path_2wk2r","document","createElementNS","setAttribute"],"mappings":"kLAoBMA,IAAMC,KAAKD,IACXE,IAAMD,KAAKC,IACXC,EAAIF,KAAKG,GAETC,eAAkB,oBAAIC,EAAEC,IAAKC,EAAEC,UAAMC,EAAEC,eAAO,CAAEL,EAAII,EAAIH,EAAII,EAAGH,EAAIE,EAAID,EAAIE,EAAzD,EAElBC,UAAa,oBAAEC,GAAIC,WAAMC,GAAIC,gBAAQ,CAACH,GAAKE,GAAID,GAAKE,GAAvC,EAWbC,WAAc,mBAA2BC,SAAzBC,GAAGC,WAAKC,GAAGC,WAAMC,GAAIC,SACvCA,GAAS,EAAErB,QACLsB,UAdoB,CAAC,CAACzB,IAAPU,EAccQ,IAdChB,IAAIQ,IAAK,CAACR,IAAIQ,GAAIV,IAAIU,KAArCA,YAedgB,GAAIC,IAAQf,UAAYP,eAAiBoB,UAAW,CAACJ,GAAKrB,IAAIuB,IAAKD,GAAKpB,IAAIqB,MAAQ,CAACJ,GAAGC,MACxFQ,GAAIC,IAAQjB,UAAYP,eAAiBoB,UAAW,CAACJ,GAAKrB,IAAIuB,GAAGC,GAAIF,GAAKpB,IAAIqB,GAAGC,KAAO,CAACL,GAAGC,WAG5F,KAAOM,GAAK,IAAMC,GAAK,MAAQ,CAAEN,GAAKC,GAAKJ,GAAK,EAAEf,GAAI,IAF7CqB,EAAIrB,EAAM,EAAI,EACdqB,EAAI,EAAM,EAAI,EAC4CI,GAAIC,IAAKC,KAAK,IAAxF,iDAWY,oBAA2BZ,SAAzBC,GAAGC,WAAKC,GAAGC,WAAMC,GAAIC,gBAC7BO,WAAaC,SAASC,gBAAgB,6BAA8B,QACpExB,EAAIQ,WAAW,CAACE,GAAGC,IAAI,CAACC,GAAGC,IAAK,CAACC,GAAIC,GAAIN,UAC/Ca,WAAWG,aAAa,IAAKzB,GACtBsB,UAAP"}
{"version":3,"file":"svgarc.min.js","sources":["../../src/util/svgarc.js"],"sourcesContent":["/*\nCopyright © 2020 Xah Lee, © 2023 P.M Kuipers\nPermission is hereby granted, free of charge, to any person obtaining a\ncopy of this software and associated documentation files (the “Software”),\nto deal in the Software without restriction, including without limitation\nthe rights to use, copy, modify, merge, publish, distribute, sublicense,\nand/or sell copies of the Software, and to permit persons to whom the\nSoftware is furnished to do so, subject to the following conditions:\nThe above copyright notice and this permission notice shall be included\nin all copies or substantial portions of the Software.\nTHE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\nTHE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\nFROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\nURL: SVG Circle Arc http://xahlee.info/js/svg_circle_arc.html\n*/\n\nconst cos = Math.cos;\nconst sin = Math.sin;\nconst π = Math.PI;\n\nconst fMatrixTimes = (([[a, b], [c, d]], [x, y]) => [a * x + b * y, c * x + d * y]);\nconst fRotateMatrix = (x => [[cos(x), -sin(x)], [sin(x), cos(x)]]);\nconst fVecAdd = (([a1, a2], [b1, b2]) => [a1 + b1, a2 + b2]);\n\n// Function modified by pmkuipers for text params\n/**\n * Create svg path text for an arc\n * @param {*} center [cx,cy] center of ellipse\n * @param {*} radius [rx,ry] major minor radius\n * @param {*} angle [t1, Δ] start angle, in radian, angle to sweep, in radian. positive.\n * @param {*} φ rotation on the whole, in radian\n * @returns {string} a SVG path description that represent a ellipse. Text describing the arc path in an svg path element\n */\nconst svgarcpath = (([cx, cy], [rx, ry], [t1, Δ], φ) => {\n Δ = Δ % (2 * π);\n const rotMatrix = fRotateMatrix(φ);\n const [sX, sY] = (fVecAdd(fMatrixTimes(rotMatrix, [rx * cos(t1), ry * sin(t1)]), [cx, cy]));\n const [eX, eY] = (fVecAdd(fMatrixTimes(rotMatrix, [rx * cos(t1 + Δ), ry * sin(t1 + Δ)]), [cx, cy]));\n const fA = ((Δ > π) ? 1 : 0);\n const fS = ((Δ > 0) ? 1 : 0);\n if (isNaN(eY) || isNaN(eX)) {\n return \"\";\n } else {\n return \"M \" + sX + \" \" + sY + \" A \" + [rx, ry, φ / (2 * π) * 360, fA, fS, eX, eY].join(\" \");\n }\n});\n\n/**\n * Create an svg arc element\n * @param {*} center [cx,cy] center of ellipse\n * @param {*} radius [rx,ry] major minor radius\n * @param {*} angle [t1, Δ] start angle, in radian, angle to sweep, in radian. positive.\n * @param {*} φ rotation on the whole, in radian\n * @returns {string} a SVG path element that represent a ellipse.\n */\nconst svgarc = (([cx, cy], [rx, ry], [t1, Δ], φ) => {\n const path2wk2r = document.createElementNS(\"http://www.w3.org/2000/svg\", \"path\");\n const d = svgarcpath([cx, cy], [rx, ry], [t1, Δ], φ);\n path2wk2r.setAttribute(\"d\", d);\n return path2wk2r;\n});\n\nexport {svgarc, svgarcpath};"],"names":["cos","Math","sin","π","PI","fMatrixTimes","a","b","c","d","x","y","fVecAdd","a1","a2","b1","b2","svgarcpath","φ","cx","cy","rx","ry","t1","Δ","rotMatrix","sX","sY","eX","eY","fA","fS","isNaN","join","path2wk2r","document","createElementNS","setAttribute"],"mappings":"kLAoBMA,IAAMC,KAAKD,IACXE,IAAMD,KAAKC,IACXC,EAAIF,KAAKG,GAETC,aAAgB,oBAAGC,EAAGC,IAAKC,EAAGC,UAAMC,EAAGC,eAAO,CAACL,EAAII,EAAIH,EAAII,EAAGH,EAAIE,EAAID,EAAIE,EAA1D,EAEhBC,QAAW,oBAAEC,GAAIC,WAAMC,GAAIC,gBAAQ,CAACH,GAAKE,GAAID,GAAKE,GAAvC,EAWXC,WAAc,mBAA8BC,SAA5BC,GAAIC,WAAMC,GAAIC,WAAMC,GAAIC,SAC1CA,GAAS,EAAIrB,QACPsB,UAdkB,CAAC,CAACzB,IAAPU,EAcaQ,IAdGhB,IAAIQ,IAAK,CAACR,IAAIQ,GAAIV,IAAIU,KAAtCA,YAeZgB,GAAIC,IAAOf,QAAQP,aAAaoB,UAAW,CAACJ,GAAKrB,IAAIuB,IAAKD,GAAKpB,IAAIqB,MAAO,CAACJ,GAAIC,MAC/EQ,GAAIC,IAAOjB,QAAQP,aAAaoB,UAAW,CAACJ,GAAKrB,IAAIuB,GAAKC,GAAIF,GAAKpB,IAAIqB,GAAKC,KAAM,CAACL,GAAIC,KACxFU,GAAON,EAAIrB,EAAK,EAAI,EACpB4B,GAAOP,EAAI,EAAK,EAAI,SACtBQ,MAAMH,KAAOG,MAAMJ,IACZ,GAEA,KAAOF,GAAK,IAAMC,GAAK,MAAQ,CAACN,GAAIC,GAAIJ,GAAK,EAAIf,GAAK,IAAK2B,GAAIC,GAAIH,GAAIC,IAAII,KAAK,qDAY/E,oBAA8Bf,SAA5BC,GAAIC,WAAMC,GAAIC,WAAMC,GAAIC,gBAChCU,UAAYC,SAASC,gBAAgB,6BAA8B,QACnE3B,EAAIQ,WAAW,CAACE,GAAIC,IAAK,CAACC,GAAIC,IAAK,CAACC,GAAIC,GAAIN,UAClDgB,UAAUG,aAAa,IAAK5B,GACrByB,SAAP"}

View File

@ -1,3 +0,0 @@
define("local_treestudyplan/util/textfit",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.textFit=function(els,options){options||(options={});var settings={};for(var key in defaultSettings)options.hasOwnProperty(key)?settings[key]=options[key]:settings[key]=defaultSettings[key];"function"==typeof els.toArray&&(els=els.toArray());var elType=Object.prototype.toString.call(els);"[object Array]"!==elType&&"[object NodeList]"!==elType&&"[object HTMLCollection]"!==elType&&(els=[els]);for(var i=0;i<els.length;i++)processItem(els[i],settings)};var defaultSettings={alignVert:!1,alignHoriz:!1,multiLine:!1,detectMultiLine:!0,minFontSize:6,maxFontSize:80,reProcess:!0,widthOnly:!1,alignVertWithFlexbox:!1};function processItem(el,settings){if(o=el,!("object"==typeof HTMLElement?o instanceof HTMLElement:o&&"object"==typeof o&&null!==o&&1===o.nodeType&&"string"==typeof o.nodeName)||!settings.reProcess&&el.getAttribute("textFitted"))return!1;var o,innerSpan,originalHeight,originalHTML,originalWidth,low,mid,high;if(settings.reProcess||el.setAttribute("textFitted",1),originalHTML=el.innerHTML,originalWidth=function(el){var style=window.getComputedStyle(el,null);return el.getBoundingClientRect().width-parseInt(style.getPropertyValue("padding-left"),10)-parseInt(style.getPropertyValue("padding-right"),10)}(el),originalHeight=function(el){var style=window.getComputedStyle(el,null);return el.getBoundingClientRect().height-parseInt(style.getPropertyValue("padding-top"),10)-parseInt(style.getPropertyValue("padding-bottom"),10)}(el),!originalWidth||!settings.widthOnly&&!originalHeight)throw settings.widthOnly?new Error("Set a static width on the target element "+el.outerHTML+" before using textFit!"):new Error("Set a static height and width on the target element "+el.outerHTML+" before using textFit!");-1===originalHTML.indexOf("textFitted")?((innerSpan=document.createElement("span")).className="textFitted",innerSpan.style.display="inline-block",innerSpan.innerHTML=originalHTML,el.innerHTML="",el.appendChild(innerSpan)):hasClass(innerSpan=el.querySelector("span.textFitted"),"textFitAlignVert")&&(innerSpan.className=innerSpan.className.replace("textFitAlignVert",""),innerSpan.style.height="",el.className.replace("textFitAlignVertFlex","")),settings.alignHoriz&&(el.style["text-align"]="center",innerSpan.style["text-align"]="center");var multiLine=settings.multiLine;settings.detectMultiLine&&!multiLine&&innerSpan.getBoundingClientRect().height>=2*parseInt(window.getComputedStyle(innerSpan)["font-size"],10)&&(multiLine=!0),multiLine||(el.style["white-space"]="nowrap"),low=settings.minFontSize,high=settings.maxFontSize;for(var size=low;low<=high;){mid=(high+low)/2,innerSpan.style.fontSize=mid+"px";var innerSpanBoundingClientRect=innerSpan.getBoundingClientRect();innerSpanBoundingClientRect.width<=originalWidth&&(settings.widthOnly||innerSpanBoundingClientRect.height<=originalHeight)?(size=mid,low=mid+1):high=mid-1}if(innerSpan.style.fontSize!=size+"px"&&(innerSpan.style.fontSize=size+"px"),settings.alignVert){!function(){if(document.getElementById("textFitStyleSheet"))return;var style=[".textFitAlignVert{","position: absolute;","top: 0; right: 0; bottom: 0; left: 0;","margin: auto;","display: flex;","justify-content: center;","flex-direction: column;","}",".textFitAlignVertFlex{","display: flex;","}",".textFitAlignVertFlex .textFitAlignVert{","position: static;","}"].join(""),css=document.createElement("style");css.type="text/css",css.id="textFitStyleSheet",css.innerHTML=style,document.body.appendChild(css)}();var height=innerSpan.scrollHeight;"static"===window.getComputedStyle(el).position&&(el.style.position="relative"),hasClass(innerSpan,"textFitAlignVert")||(innerSpan.className=innerSpan.className+" textFitAlignVert"),innerSpan.style.height=height+"px",settings.alignVertWithFlexbox&&!hasClass(el,"textFitAlignVertFlex")&&(el.className=el.className+" textFitAlignVertFlex")}}function hasClass(element,cls){return(" "+element.className+" ").indexOf(" "+cls+" ")>-1}}));
//# sourceMappingURL=textfit.min.js.map

File diff suppressed because one or more lines are too long

View File

@ -1,3 +1,4 @@
/* eslint-disable */
/*!
* BootstrapVue 2.23.1
*

View File

@ -1,27 +1,7 @@
/*eslint no-var: "error" */
/*eslint no-unused-vars: "off" */
/*eslint linebreak-style: "off" */
/*eslint no-trailing-spaces: "off" */
/* eslint-env es6*/
// Put this file in path/to/plugin/amd/src
// You can call it anything you like
import {get_string,get_strings} from 'core/str';
import {call} from 'core/ajax';
import Debugger from './debugger';
import {load_strings} from './string-helper';
let debug = new Debugger("treestudyplan-config-grades");
/*
let strings = load_strings({
studyplan: {
studyplan_select_placeholder: 'studyplan_select_placeholder',
},
});
*/
/**
* Initialize grade cfg page
*/

View File

@ -1,5 +1,3 @@
/*eslint no-console: "off"*/
/**
* Save a piece of text to file as if it was downloaded
* @param {string} filename
@ -7,7 +5,9 @@
* @param {string} type
*/
export function download(filename, text, type) {
if(undefined == type) { type = "text/plain"; }
if (undefined == type) {
type = "text/plain";
}
var pom = document.createElement('a');
pom.setAttribute('href', 'data:' + type + ';charset=utf-8,' + encodeURIComponent(text));
pom.setAttribute('download', filename);
@ -16,8 +16,7 @@ export function download(filename, text, type) {
var event = document.createEvent('MouseEvents');
event.initEvent('click', true, true);
pom.dispatchEvent(event);
}
else {
} else {
pom.click();
}
}
@ -64,8 +63,7 @@ export function upload(onready,accept) {
var event = document.createEvent('MouseEvents');
event.initEvent('click', true, true);
input.dispatchEvent(event);
}
else {
} else {
input.click();
}
}

View File

@ -1,21 +1,34 @@
/* eslint no-var: "error" */
/*eslint no-console: "off"*/
/*eslint-disable no-trailing-spaces */
/* eslint capitalized-comments: "off" */
/* eslint-env es6 */
// Put this file in path/to/plugin/amd/src
import {loadFragment} from 'core/fragment';
import {load_strings} from './util/string-helper';
import {loadStrings} from './util/string-helper';
import {call} from 'core/ajax';
import notification from 'core/notification';
import {replaceNodeContents} from 'core/templates';
// import {markFormSubmitted} from 'core_form/changechecker'; // Moodle 4.00+ only
// import {notifyFormSubmittedByJavascript} from 'core_form/events'; // Moodle 4.00+ only
/* Moodle 3.11 safe import for when needed
let markFormSubmitted = () => {};
let notifyFormSubmittedByJavascript = () => {};
import('core_form/changechecker').then((ns) => {
debug.info(ns);
if(ns.markFormSubmitted) {
markFormSubmitted = ns.markFormSubmitted;
}
if(ns.notifyFormSubmittedByJavascript) {
notifyFormSubmittedByJavascript = ns.notifyFormSubmittedByJavascript;
}
}).catch(()=>{});
*/
export default {
install(Vue/* ,options */) {
let strings = load_strings({
let strings = loadStrings({
editmod: {
save$core: "save$core",
cancel$core: "cancel$core",
@ -32,11 +45,11 @@ export default {
},
title: {
type: String,
default: "",
'default': "",
},
genericonly: {
type: Boolean,
default: false,
'default': false,
}
},
data() {
@ -50,20 +63,20 @@ export default {
methods: {
openForm() {
const self = this;
self.$refs["editormodal"].show();
self.$refs.editormodal.show();
},
onShown() {
const self = this;
let params = {cmid: this.cmid};
console.info("Loading form");
loadFragment('local_treestudyplan', 'mod_edit_form', this.coursectxid, params).then((html, js) => {
replaceNodeContents(self.$refs["content"], html, js);
replaceNodeContents(self.$refs.content, html, js);
return null;
}).catch(notification.exception);
},
onSave() {
const self = this;
let form = this.$refs["content"].getElementsByTagName("form")[0];
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.
@ -82,8 +95,8 @@ export default {
args: {cmid: this.cmid, formdata: data}
}])[0].then(() => {
self.$emit("saved", formdata);
return null;
}).catch(notification.exception);
}
},
template: `

253
amd/src/page-coach.js Normal file
View File

@ -0,0 +1,253 @@
/* eslint no-var: "error" */
/* eslint no-unused-vars: "off" */
/* eslint linebreak-style: "off" */
/* eslint no-trailing-spaces: "off" */
/* eslint max-depth: ["error", 6]*/
/* eslint-env es6*/
// Put this file in path/to/plugin/amd/src
// You can call it anything you like
import {call} from 'core/ajax';
import notification from 'core/notification';
import Vue from './vue/vue';
import Debugger from './util/debugger';
import {loadStrings} from './util/string-helper';
import {processStudyplan} from './studyplan-processor';
import {studyplanTiming} from './util/date-helper';
import {addBrowserButtonEvent} from './util/browserbuttonevents';
import EditorComponents from './studyplan-editor-components';
Vue.use(EditorComponents);
import TSComponents from './treestudyplan-components';
Vue.use(TSComponents);
import RVComponents from './report-viewer-components';
Vue.use(RVComponents);
import ModalComponents from './modedit-modal';
Vue.use(ModalComponents);
import PortalVue from './portal-vue/portal-vue.esm';
Vue.use(PortalVue);
import BootstrapVue from './bootstrap-vue/bootstrap-vue';
Vue.use(BootstrapVue);
let debug = new Debugger("treestudyplancoach");
let strings = loadStrings({
coach: {
},
});
/**
* Initialize the Page
*/
export function init() {
let app = new Vue({
el: '#root',
data: {
selected: {
planid: 0,
studentid: 0,
},
displayedstudyplan: null,
activestudyplan: null,
associatedstudents: [],
selectedstudent: null,
studentstudyplan: null,
loadingstudyplan: false,
studyplans: [],
text: strings.coach,
toolbox: {
right: true,
},
usedcontexts: [],
editmode: false,
},
async mounted() {
call([{
methodname: 'local_treestudyplan_list_coaching_studyplans',
args: {}
}])[0].then(function(response) {
const timingval = {present: 0, past: 1, future: 2};
response.sort((a, b) => {
const timinga = studyplanTiming(a);
const timingb = studyplanTiming(b);
const t = timingval[timinga] - timingval[timingb];
if (t == 0) {
// Sort by name if timing is equal
return a.name.localeCompare(b.name);
} else {
return t;
}
});
app.studyplans = response;
// Load studyplan from hash if applicable
const hash = window.location.hash.replace('#', '');
const parts = hash.split("-");
if (!!parts && parts.length > 0 && parts[0] != '') {
for (let idx in app.studyplans) {
if (app.studyplans[idx].id == parts[0]) {
app.selectStudyplan(app.studyplans[idx], parts[1], false);
break;
}
}
}
return;
}).catch(notification.exception);
addBrowserButtonEvent(this.navChanged, this.navChanged);
},
computed: {
},
methods: {
navChanged() {
const hash = window.location.hash.replace('#', '');
const parts = hash.split("-");
debug.log("Navigation changed", hash, parts);
if (!!parts && parts.length > 0) {
const planid = Number(parts[0]);
const studentid = (parts.length > 1) ? Number(parts[1]) : 0;
debug.log("Selected ids", planid, studentid, this.selected.planid, this.selected.studentid);
if (planid == 0) {
if (planid != this.selected.planid) {
this.closeStudyplan(false);
}
} else if (this.selected.planid != planid || (studentid == 0 && this.selected.studentid != 0)) {
debug.info("Requested plan changed - loading studyplan");
for (let idx in app.studyplans) {
const plan = this.studyplans[idx];
if (Number(plan.id) == planid) {
this.selectStudyplan(plan, studentid, false);
break;
}
}
} else if (this.selected.studentid != studentid) {
for (const group of app.associatedstudents) {
for (const student of group.users) {
if (Number(student.id) == studentid) {
app.showStudentView(student, false);
break;
}
}
}
}
}
},
closeStudyplan(updatehash = true) {
app.selected.planid = 0;
app.selected.studentid = 0;
app.activestudyplan = null;
app.associatedstudents = [];
app.studentstudyplan = [];
app.displayedstudyplan = null;
if (updatehash) {
window.location.hash = '';
}
},
selectStudyplan(studyplan, studentid, updatehash = true) {
app.selected.planid = Number(studyplan.id);
app.selected.studentid = studentid ? Number(studentid) : 0;
// Fetch studyplan
const self = this;
self.loadingstudyplan = true;
self.associatedstudents = [];
self.selectedstudent = null;
self.studentstudyplan = null;
call([{
methodname: 'local_treestudyplan_get_studyplan_map',
args: {id: studyplan.id}
}])[0].then((response) => {
self.activestudyplan = processStudyplan(response, true);
call([{
methodname: 'local_treestudyplan_all_associated_grouped',
args: {'studyplan_id': studyplan.id}
}])[0].then(function(response) {
self.associatedstudents = response;
let foundstudent = false;
if (studentid) {
for (const group of self.associatedstudents) {
for (const student of group.users) {
if (student.id == studentid) {
foundstudent = true;
self.showStudentView(student);
break;
}
}
}
}
if (!foundstudent) {
// Select first student available.
for (const group of self.associatedstudents) {
for (const student of group.users) {
foundstudent = true;
self.showStudentView(student);
break;
}
}
}
if (!foundstudent) {
// Update hash with just the studyplan if no student was available for display
app.selected.studentid = 0;
if (updatehash) {
window.location.hash = app.activestudyplan.id;
}
self.displayedstudyplan = self.activestudyplan;
self.loadingstudyplan = false;
}
return;
}).catch(notification.exception);
return;
}).catch(function(error) {
notification.exception(error);
app.loadingstudyplan = false;
});
},
showStudentView(student, updatehash = true) {
app.selected.studentid = student ? Number(student.id) : 0;
if (student) {
app.selectedstudent = student;
app.studentstudyplan = null;
app.loadingstudyplan = true;
call([{
methodname: 'local_treestudyplan_get_user_studyplan',
args: {userid: student.id, studyplanid: app.activestudyplan.id}
}])[0].then((response) => {
app.studentstudyplan = processStudyplan(response, false);
app.displayedstudyplan = app.studentstudyplan;
app.loadingstudyplan = false;
if (updatehash) {
window.location.hash = app.activestudyplan.id + "-" + student.id;
}
return;
}).catch(function(error) {
notification.exception(error);
app.loadingstudyplan = false;
});
} else {
this.showOverview(updatehash);
}
},
showOverview(updatehash = true) {
debug.info("Switch to overview", updatehash);
app.selected.studentid = 0;
app.selectedstudent = null;
app.studentstudyplan = null;
app.displayedstudyplan = app.activestudyplan;
if (updatehash) {
window.location.hash = app.activestudyplan.id;
}
}
},
});
}

View File

@ -9,12 +9,11 @@
import {get_string,get_strings} from 'core/str';
import {call} from 'core/ajax';
import notification from 'core/notification';
import {resetAllFormDirtyStates} from 'core_form/changechecker';
//import {resetAllFormDirtyStates} from 'core_form/changechecker'; // Moodle 4.00+ only
import Vue from './vue/vue';
import EditorComponents from './studyplan-editor-components';
Vue.use(EditorComponents);
@ -24,27 +23,40 @@ Vue.use(TSComponents);
import ModalComponents from './modedit-modal';
Vue.use(ModalComponents);
import Debugger from './util/debugger';
import {addBrowserButtonEvent} from './util/browserbuttonevents';
import {load_strings} from './util/string-helper';
import {ProcessStudyplan} from './studyplan-processor';
import {loadStrings} from './util/string-helper';
import {processStudyplan} from './studyplan-processor';
import {download,upload} from './downloader';
import {studyplanTiming} from './util/date-helper';
import { premiumenabled, premiumstatus } from "./util/premium";
import mFormComponents from "./util/mform-helper";
Vue.use(mFormComponents);
import PortalVue from './portal-vue/portal-vue.esm';
Vue.use(PortalVue);
import BootstrapVue from './bootstrap-vue/bootstrap-vue';
Vue.use(BootstrapVue);
import {Drag, Drop, DropList} from './vue-easy-dnd/vue-easy-dnd.esm';
Vue.component('drag',Drag);
Vue.component('drop',Drop);
Vue.component('drop-list',DropList);
const debug = new Debugger("treestudyplan");
let strings = load_strings({
let resetAllFormDirtyStates = () => {};
import('core_form/changechecker').then((ns) => {
debug.info(ns);
if(ns.resetAllFormDirtyStates) {
resetAllFormDirtyStates = ns.resetAllFormDirtyStates;
}
}).catch(()=>{});
let strings = loadStrings({
studyplan: {
studyplan_select_placeholder: 'studyplan_select_placeholder',
advanced_import_from_file: 'advanced_import_from_file',
advanced_create_from_template: 'advanced_create_from_template',
studyplan_add: "studyplan_add",
},
});
@ -89,22 +101,13 @@ export function init(contextid,categoryid,options) {
aggregation_config: '',
}
},
toolbox: {
shown: false,
right: true,
},
filters: {
systembadges: "",
relatedbadges: "",
},
activestudyplan: null,
activepage: null,
loadingstudyplan: false,
studyplans: [],
frameworks: [],
relatedbadges: [],
systembadges: [],
courses: [],
templatecount: 0,
text: strings.studyplan,
usedcontexts: [],
},
@ -131,8 +134,13 @@ export function init(contextid,categoryid,options) {
},
mounted() {
this.initialize();
addBrowserButtonEvent(this.backPressed);
},
beforeunmount() {
},
computed: {
premiumenabled,
dropdown_title(){
if(this.activestudyplan && this.activestudyplan.name){
return this.activestudyplan.name;
@ -144,16 +152,16 @@ export function init(contextid,categoryid,options) {
contextid(){
return contextid;
},
filterComponentType(){
return {
item: false,
component: true,
span: 1,
type: 'filter',
};
},
},
methods: {
backPressed() {
debug.log("Back button pressed");
if (app.activestudyplan) {
debug.log("Closing studyplan");
this.closeStudyplan();
}
},
initialize() {
call([{
methodname: 'local_treestudyplan_list_studyplans',
@ -182,22 +190,31 @@ export function init(contextid,categoryid,options) {
const hash = location.hash.replace('#','');
if(hash){
const id = hash;
for(const p of app.studyplans) {
if (p.id == id) {
app.selectStudyplan(id);
break;
}
}
}
}).catch(notification.exception);
call([{
methodname: 'local_treestudyplan_map_categories',
args: { }
}])[0].then(function(response){
app.courses = response;
}).catch(notification.exception);
call([{
methodname: 'local_treestudyplan_list_available_categories',
args: { operation: 'edit', refcontext_id: contextid}
}])[0].then(function(response){
app.usedcontexts = response;
}).catch(notification.exception);
this.filter_systembadges();
this.refreshTemplateCount();
},
refreshTemplateCount(){
call([{
methodname: 'local_treestudyplan_count_templates',
args: { }
}])[0].then(function(response){
app.templatecount = response;
}).catch(notification.exception);
},
closeStudyplan() {
app.activestudyplan = null;
@ -244,7 +261,7 @@ export function init(contextid,categoryid,options) {
methodname: 'local_treestudyplan_get_studyplan_map',
args: { id: studyplanid}
}])[0].then(function(response){
app.activestudyplan = ProcessStudyplan(response);
app.activestudyplan = processStudyplan(response);
debug.info('studyplan processed');
app.loadingstudyplan = false;
window.location.hash = app.activestudyplan.id;
@ -253,10 +270,6 @@ export function init(contextid,categoryid,options) {
app.loadingstudyplan = false;
});
},
onPageChange(page) {
this.activepage = page;
this.filter_relatedbadges();
},
import_studyplan(){
const self = this;
upload((filename,content)=>{
@ -293,43 +306,7 @@ export function init(contextid,categoryid,options) {
download(plan.shortname+".json",response.content,response.format);
}).catch(notification.exception);
},
toggletoolbox(event) {
debug.info(event);
this.toolbox.shown = event;
},
filter_systembadges() {
const self = this;
call([{
methodname: 'local_treestudyplan_search_badges',
args: {
search: this.filters.systembadges || ""
}
}])[0].then(function(response){
self.systembadges = response;
}).catch(notification.exception);
},
filter_relatedbadges() {
const self = this;
if (this.activepage) {
call([{
methodname: 'local_treestudyplan_search_related_badges',
args: {
page_id: this.activepage.id,
search: this.filters.relatedbadges || ""
}
}])[0].then(function(response){
self.relatedbadges = response;
}).catch(notification.exception);
}
},
reset_systembadges() {
this.filters.systembadges = "";
this.filter_systembadges();
},
reset_relatedbadges() {
this.filters.relatedbadges = "";
this.filter_relatedbadges();
},
},
});
}

View File

@ -2,6 +2,7 @@
/* eslint no-unused-vars: "off" */
/* eslint linebreak-style: "off" */
/* eslint-env es6*/
/* eslint camelcase: "off" */
// Put this file in path/to/plugin/amd/src
// You can call it anything you like
@ -12,16 +13,15 @@ import ModalFactory from 'core/modal_factory';
import ModalEvents from 'core/modal_events';
/* Determine the proper getstrings function to use (MDL4.3+ recommends use of getStrings, which is jquery independent) */
const getstr_func = (getStrings !== undefined)?getStrings:get_strings;
const getstrFunc = (getStrings !== undefined) ? getStrings : get_strings;
let debug = new Debugger("treestudyplan");
let debug = new Debugger("treestudyplan-invitemanager");
/**
* Init function for page-invitemanager
* @return undefined
*/
export function init() {
getstr_func([
getstrFunc([
{key: 'ok', component: 'core'},
{key: 'confirm', component: 'core'},
]).then((s) => {
@ -48,18 +48,23 @@ export function init() {
type: ModalFactory.types.SAVE_CANCEL,
title: title,
body: text,
}).then(function (modal) {
}).then((modal) => {
modal.setSaveButtonText(oktext);
let root = modal.getRoot();
root.on(ModalEvents.save, function () {
root.on(ModalEvents.save, () => {
window.location = href;
});
modal.modal[0].style["max-width"] = "345px";
modal.show();
return modal;
}).catch((x) => {
debug.warn(x);
});
});
});
return;
}).catch((x) => {
debug.warn(x);
});
}

View File

@ -1,8 +1,4 @@
/*eslint no-var: "error" */
/* eslint no-unused-vars: "off" */
/*eslint linebreak-style: "off" */
/*eslint no-trailing-spaces: "off" */
/*eslint no-console: "off" */
/* eslint-env es6*/
// Put this file in path/to/plugin/amd/src
// You can call it anything you like
@ -12,15 +8,11 @@ import Vue from './vue/vue';
import RVComponents from './report-viewer-components';
Vue.use(RVComponents);
import Debugger from './util/debugger';
import PortalVue from './portal-vue/portal-vue.esm';
Vue.use(PortalVue);
import BootstrapVue from './bootstrap-vue/bootstrap-vue';
Vue.use(BootstrapVue);
let debug = new Debugger("treestudyplan-report");
/**
* Initialize the Page
* @param {string} type Type of page to show

View File

@ -10,7 +10,7 @@ import notification from 'core/notification';
import Vue from './vue/vue';
import Debugger from './util/debugger';
import {load_strings} from './util/string-helper';
import {loadStrings} from './util/string-helper';
import SRComponents from './studyplan-report-components';
Vue.use(SRComponents);
@ -28,9 +28,8 @@ Vue.use(BootstrapVue);
let debug = new Debugger("treestudyplanviewer");
let strings = load_strings({
studyplan_report: {
studyplan_select_placeholder: 'studyplan_select_placeholder',
let strings = loadStrings({
studyplanReport: {
studyplan: 'studyplan',
page: 'studyplanpage',
periods: 'periods',
@ -67,10 +66,7 @@ export function init(studyplanid, pageid, firstperiod, lastperiod) {
structure: null,
studyplan: null,
page: null,
text: strings.studyplan_report,
},
async mounted() {
text: strings.studyplanReport,
},
created() {
// On creation, load the page as specified
@ -89,10 +85,11 @@ export function init(studyplanid, pageid, firstperiod, lastperiod) {
firstperiod: firstperiod,
lastperiod: lastperiod
}
}])[0].then(function(response){
}])[0].then((response) => {
self.structure = response;
self.studyplan = response.studyplan;
self.page = response.page;
return;
}).catch(notification.exception);
},
selectedPage(e) {

View File

@ -1,8 +1,6 @@
/*eslint no-var: "error" */
/* eslint no-unused-vars: "off" */
/*eslint linebreak-style: "off" */
/* eslint no-trailing-spaces: "off" */
/*eslint-env es6*/
/* eslint max-depth: ["error", 5] */
// Put this file in path/to/plugin/amd/src
// You can call it anything you like
@ -12,13 +10,13 @@ import notification from 'core/notification';
import Vue from './vue/vue';
import Debugger from './util/debugger';
import {load_strings} from './util/string-helper';
import {ProcessStudyplan} from './studyplan-processor';
import {loadStrings} from './util/string-helper';
import {processStudyplan} from './studyplan-processor';
import {studyplanTiming} from './util/date-helper';
import {addBrowserButtonEvent} from './util/browserbuttonevents';
import RVComponents from './report-viewer-components';
Vue.use(RVComponents);
import TSComponents from './treestudyplan-components';
import ModalComponents from './modedit-modal';
Vue.use(ModalComponents);
@ -30,29 +28,37 @@ Vue.use(BootstrapVue);
let debug = new Debugger("treestudyplanviewer");
let strings = load_strings({
let strings = loadStrings({
studyplan: {
studyplan_select_placeholder: 'studyplan_select_placeholder',
studyplanSelectPlaceholder: 'studyplan_select_placeholder',
},
});
/**
* Initialize the Page
* @param {int} contextid The context we should attempt to work in (1:1 related to the category)
* @param {int} categoryid The category we shoud attempt to work in (1:1 related to the context)
* @param {number} contextid The context we should attempt to work in (1:1 related to the category)
* @param {number} categoryid The category we shoud attempt to work in (1:1 related to the context)
*/
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;}
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
const in_systemcontext = (contextid <= 1);
if (undefined === contextid || !Number.isInteger(Number(contextid)) || contextid < 1) {
contextid = 1;
} else { // Ensure a numeric value instead of string
contextid = Number(contextid);
}
if (undefined === categoryid || !Number.isInteger(Number(categoryid))) {
categoryid = 0;
} else { // Ensure a numeric value instead of string
categoryid = Number(categoryid);
}
let app = new Vue({
el: '#root',
data: {
selected: {
planid: 0,
studentid: 0,
},
displayedstudyplan: null,
activestudyplan: null,
associatedstudents: [],
@ -69,8 +75,8 @@ export function init(contextid,categoryid) {
async mounted() {
call([{
methodname: 'local_treestudyplan_list_studyplans',
args: {context_id: contextid}
}])[0].then(function(response){
args: {'context_id': contextid}
}])[0].then((response) => {
const timingval = {present: 0, past: 1, future: 2};
response.sort((a, b) => {
const timinga = studyplanTiming(a);
@ -78,19 +84,18 @@ export function init(contextid,categoryid) {
const t = timingval[timinga] - timingval[timingb];
if (t == 0) {
// sort by name if timing is equal
// Sort by name if timing is equal
return a.name.localeCompare(b.name);
}
else {
} else {
return t;
}
});
app.studyplans = response;
// load studyplan from hash if applicable
// Load studyplan from hash if applicable
const hash = window.location.hash.replace('#', '');
const parts = hash.split("-");
if(!!parts && parts.length > 0){
if (!!parts && parts.length > 0 && parts[0] != '') {
for (let idx in app.studyplans) {
if (app.studyplans[idx].id == parts[0]) {
app.selectStudyplan(app.studyplans[idx], parts[1]);
@ -98,11 +103,12 @@ export function init(contextid,categoryid) {
}
}
}
return;
}).catch(notification.exception);
call([{
methodname: 'local_treestudyplan_list_available_categories',
args: { operation: 'view', refcontext_id: contextid}
}])[0].then(function(response){
args: {operation: 'view', 'refcontext_id': contextid}
}])[0].then((response) => {
const contexts = [];
for (const ix in response) {
const cat = response[ix];
@ -111,15 +117,16 @@ export function init(contextid,categoryid) {
}
}
app.usedcontexts = contexts;
return;
}).catch(notification.exception);
addBrowserButtonEvent(this.navChanged, this.navChanged);
},
computed: {
dropdown_title(){
dropdownTitle() {
if (this.activestudyplan && this.activestudyplan.name) {
return this.activestudyplan.name;
}
else{
return this.text.studyplan_select_placeholder;
} else {
return this.text.studyplanSelectPlaceholder;
}
},
contextid() {
@ -127,6 +134,41 @@ export function init(contextid,categoryid) {
}
},
methods: {
navChanged() {
const hash = window.location.hash.replace('#', '');
const parts = hash.split("-");
debug.log("Navigation changed", hash, parts);
if (!!parts && parts.length > 0) {
const planid = Number(parts[0]);
const studentid = (parts.length > 1) ? Number(parts[1]) : 0;
debug.log("Selected ids", planid, studentid, this.selected.planid, this.selected.studentid);
if (planid == 0) {
if (planid != this.selected.planid) {
this.closeStudyplan(false);
}
} else if (this.selected.planid != planid || (studentid == 0 && this.selected.studentid != 0)) {
debug.info("Requested plan changed - loading studyplan");
for (let idx in app.studyplans) {
const plan = this.studyplans[idx];
if (Number(plan.id) == planid) {
this.selectStudyplan(plan, studentid, false);
break;
}
}
} else if (this.selected.studentid != studentid) {
for (const group of app.associatedstudents) {
for (const student of group.users) {
if (Number(student.id) == studentid) {
app.showStudentView(student, false);
break;
}
}
}
}
}
},
switchContext(ctxid) {
const params = new URLSearchParams(location.search);
params.delete('categoryid');
@ -136,77 +178,99 @@ export function init(contextid,categoryid) {
window.location.href = window.location.pathname + "?" + params.toString();
}, 50);
},
closeStudyplan() {
closeStudyplan(updatehash = true) {
app.selected.planid = 0;
app.selected.studentid = 0;
app.activestudyplan = null;
app.associatedstudents = [];
app.studentstudyplan = [];
app.displayedstudyplan = null;
if (updatehash) {
window.location.hash = '';
}
},
selectStudyplan(studyplan,studentid){
// fetch studyplan
selectStudyplan(studyplan, studentid, updatehash = true) {
app.selected.planid = Number(studyplan.id);
app.selected.studentid = studentid ? Number(studentid) : 0;
// Fetch studyplan
app.loadingstudyplan = true;
app.activestudyplan = null;
app.associatedstudents = [];
app.selectedstudent = null;
app.studentstudyplan = null;
call([{
methodname: 'local_treestudyplan_get_studyplan_map',
args: {id: studyplan.id}
}])[0].then(function(response){
app.activestudyplan = ProcessStudyplan(response,true);
app.displayedstudyplan = app.activestudyplan;
app.loadingstudyplan = false;
window.location.hash = app.activestudyplan.id;
}])[0].then((response) => {
app.activestudyplan = processStudyplan(response, true);
call([{
methodname: 'local_treestudyplan_all_associated_grouped',
args: { studyplan_id: studyplan.id}
}])[0].then(function(response){
args: {'studyplan_id': studyplan.id}
}])[0].then((response) => {
app.associatedstudents = response;
let foundstudent = false;
if (studentid) {
for (const group of app.associatedstudents) {
for (const student of group.users) {
if (student.id == studentid) {
app.showStudentView(student);
foundstudent = true;
app.showStudentView(student, updatehash);
break;
}
}
}
}
if (!foundstudent) {
app.selected.studentid = 0;
if (updatehash) {
window.location.hash = app.activestudyplan.id;
}
app.displayedstudyplan = app.activestudyplan;
app.loadingstudyplan = false;
}
return;
}).catch(notification.exception);
return;
}).catch(function(error) {
notification.exception(error);
app.loadingstudyplan = false;
});
},
showStudentView(student){
showStudentView(student, updatehash = true) {
app.selected.studentid = student ? Number(student.id) : 0;
if (student) {
app.selectedstudent = student;
app.studentstudyplan = null;
if (student) {
app.loadingstudyplan = true;
call([{
methodname: 'local_treestudyplan_get_user_studyplan',
args: { userid: student.id, studyplanid: app.activestudyplan.id}
}])[0].then(function(response){
app.studentstudyplan = ProcessStudyplan(response,false);
args: {userid: student.id, studyplanid: app.selected.planid}
}])[0].then((response) => {
app.studentstudyplan = processStudyplan(response, false);
app.displayedstudyplan = app.studentstudyplan;
app.loadingstudyplan = false;
if (updatehash) {
window.location.hash = app.activestudyplan.id + "-" + student.id;
}).catch(function(error){
}
return;
}).catch((error) => {
notification.exception(error);
app.loadingstudyplan = false;
});
} else {
this.showOverview(updatehash);
}
},
showOverview(){
showOverview(updatehash = true) {
app.selected.studentid = 0;
app.selectedstudent = null;
app.studentstudyplan = null;
app.displayedstudyplan = app.activestudyplan;
if (updatehash) {
window.location.hash = app.activestudyplan.id;
}
}
},
});

View File

@ -5,7 +5,7 @@
* Hide a primary navigation item by href
* @param {string|Array} hrefs The link that should be hidden
*/
export function hide_primary(hrefs) {
export function hidePrimary(hrefs) {
if (typeof hrefs === 'string' || hrefs instanceof String) {
hrefs = [hrefs];
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,5 @@
/* eslint no-console: "off"*/
/* eslint max-depth: ["error", 6]*/
/**
* Copy fields from one object to another
* @param {Object} target The target to copy to
@ -28,7 +29,7 @@ export function transportItem(target,source,identifier,param){
if (!param) {
param = 'value';
}
// find item
// Find item
let item;
let itemindex;
for (const ix in source) {
@ -49,14 +50,12 @@ export function transportItem(target,source,identifier,param){
* @param {Array} studyplans The list of studyplans to load
* @returns {Array} List of updated studyplans
*/
export function ProcessStudyplans(studyplans){
export function processStudyplans(studyplans) {
// Unify object references to connections between items, so there are no duplicates
for(const isx in studyplans)
{
for (const isx in studyplans) {
const studyplan = studyplans[isx];
ProcessStudyplan(studyplan);
processStudyplan(studyplan);
}
return studyplans;
}
@ -64,12 +63,12 @@ export function ProcessStudyplans(studyplans){
* Perform initial processing on a downloaded studyplan
* Mainly used to create the proper references between items
* @param {Object} studyplan The studyplan to process
* @returns Processed studyplan
* @returns {object} Processed studyplan
*/
export function ProcessStudyplan(studyplan){
export function processStudyplan(studyplan) {
for (const ip in studyplan.pages) {
const page = studyplan.pages[ip];
ProcessStudyplanPage(page);
processStudyplanPage(page);
}
return studyplan;
}
@ -78,9 +77,9 @@ export function ProcessStudyplan(studyplan){
* Perform initial processing on a downloaded studyplan'page
* Mainly used to create the proper references between items
* @param {Object} page The studyplan page to process
* @returns Processed studyplan
* @returns {object} Processed studyplan
*/
export function ProcessStudyplanPage(page){
export function processStudyplanPage(page) {
let connections = {};
for (const il in page.studylines) {
const line = page.studylines[il];

View File

@ -1,32 +1,22 @@
/* eslint no-var: "error"*/
/*eslint no-console: "off"*/
/* eslint no-unused-vars: warn */
/*eslint max-len: ["error", { "code": 160 }] */
/*eslint-disable no-trailing-spaces */
/* eslint max-depth: ["error", 6] */
/* eslint camelcase: "off" */
/* eslint-env es6*/
// Put this file in path/to/plugin/amd/src
import {load_strings} from './util/string-helper';
import {loadStrings} from './util/string-helper';
import {call} from 'core/ajax';
import notification from 'core/notification';
import Debugger from './util/debugger';
import Config from 'core/config';
import TSComponents from './treestudyplan-components';
import FitTextVue from './util/fittext-vue';
import {format_datetime} from "./util/date-helper";
import {formatDatetime} from "./util/date-helper";
const debug = new Debugger("treestudyplan-viewer");
// Make π available as a constant
const π = Math.PI;
// Gravity value for arrow lines - determines how much a line is pulled in the direction of the start/end before changing direction
const LINE_GRAVITY = 1.3;
/**
* Strip tags from html
* @param {*} html
* @returns
* @returns {string}
*/
function striptags(html) {
const tmp = document.createElement("DIV");
@ -39,6 +29,7 @@ function striptags(html) {
/**
* Retrieve condition headers
* @param {Object} item
* @returns {Array}
*/
function conditionHeaders(item) {
const course = item.course;
@ -46,7 +37,7 @@ function conditionHeaders(item) {
if (course.competency) {
for (const cmp of course.competency.competencies) {
list.push({
name: (cmp.details?`${cmp.title} - ${cmp.details}`:cmp.title),
name: (cmp.details ? (`${cmp.title} - ${cmp.details}`) : cmp.title),
tooltip: cmp.description,
});
}
@ -75,6 +66,7 @@ function conditionHeaders(item) {
/**
* Retrieve conditions
* @param {Object} item
* @returns {Array}
*/
function conditions(item) {
const course = item.course;
@ -105,7 +97,7 @@ export default {
Vue.use(TSComponents);
Vue.use(FitTextVue);
let strings = load_strings({
let strings = loadStrings({
report: {
loading: "loadinghelp@core",
studyplan_past: "studyplan_past",
@ -138,11 +130,11 @@ export default {
}
});
/************************************
/* **********************************
* *
* Treestudyplan Viewer components *
* *
************************************/
* **********************************/
Vue.component('q-studyplanreport', {
props: {
@ -168,14 +160,11 @@ export default {
}
};
},
created() {
},
watch: {
structure: {
immediate: true,
handler(structure) {
this.loadStudents(); // reload the student list
this.loadStudents(); // Reload the student list
// (Re)build expansion info structure
let firstperiod = true;
for (const period of structure.periods) {
@ -227,8 +216,7 @@ export default {
},
computed: {
sortedstudents() {
const self=this;
// Probably could make a deep copy for purity's sake, but this works just as well and is probably more efficient.
// Probably could make a deep copy for purity's sake, but this works just as well.
const students = this.students;
for (const group of this.students) {
group.users.sort((a, b) => {
@ -247,7 +235,6 @@ export default {
}
});
}
return students;
},
resultColCount() {
@ -283,8 +270,8 @@ export default {
self.studentsloading = true;
call([{
methodname: 'local_treestudyplan_all_associated_grouped',
args: { studyplan_id: this.structure.studyplan.id}
}])[0].then(function(response){
args: {'studyplan_id': this.structure.studyplan.id}
}])[0].then((response) => {
self.students = response;
for (const group of self.students) {
self.$set(
@ -306,18 +293,21 @@ export default {
);
call([{
methodname: 'local_treestudyplan_get_report_data',
args: { pageid: self.structure.page.id,
args: {
pageid: self.structure.page.id,
userid: student.id,
firstperiod: self.structure.firstperiod,
lastperiod: self.structure.lastperiod,
}
}])[0].then(function(response){
}])[0].then((response) => {
self.studentresults[student.id].loading = false;
self.studentresults[student.id].results = response;
return;
}).catch(notification.exception);
}
}
self.studentsloading = false;
return;
}).catch(notification.exception);
},
expansionChanged(parm, id, val) {
@ -473,15 +463,7 @@ export default {
this.$emit('togglesort', heading);
}
},
mounted() {
},
updated() {
},
/* https://css-tricks.com/position-sticky-and-table-headers/ */
/* TODO: Rework below to make use of tables. Use <Thead> as main element. Then create multiple <tr> as needed for the headers.
This should create a much better view than using divs overal.
*/
/* TODO: https://css-tricks.com/position-sticky-and-table-headers/ */
template: `
<thead class='q-header'>
<tr> <!-- period heading -->
@ -502,7 +484,8 @@ export default {
<template v-for="p in structure.periods">
<template v-if="expansion.periods[p.period.id].expanded">
<th v-for="l in p.lines"
:class="'q-line-heading ' + ((expansion.lines[p.period.id][l.line.id].expanded)?'expanded':'collapsed')"
:class="'q-line-heading '
+ ((expansion.lines[p.period.id][l.line.id].expanded)?'expanded':'collapsed')"
:colspan="colspanLine(p,l)"
:rowspan='(expansion.lines[p.period.id][l.line.id].expanded)?1:4'
><span class="q-wrap"><fittext vertical maxsize="18pt"
@ -524,22 +507,21 @@ export default {
:class="'q-item-heading ' + ((expansion.items[item.id].expanded)?'expanded':'collapsed')"
:colspan="colspanItem(item)"
:rowspan='(expansion.items[item.id].expanded)?1:3'
><span class="q-wrap"><a href='#'
@click.prevent="toggleItem(item)"
><a class="q-wrap" href='#' @click.prevent="toggleItem(item)"
><div class="q-toggle"
><i v-if="expansion.items[item.id].expanded"
class='q-chevron fa fa-minus'></i
><i v-else
class='q-chevron fa fa-plus'></i
></a
>&nbsp;<a style="display: inline-block;" href='#'
@click.prevent="toggleItem(item)"
><fittext vertical maxsize="18pt" singleline
></div><div class="q-title"
><fittext vertical maxsize="12pt" minsize="9pt"
><span class='q-label'
:title="item.course.displayname"
v-html="item.course.displayname"
></span
></fittext
></a></span
></div
></a
></th>
</template>
</template>
@ -558,7 +540,8 @@ export default {
<th v-for="c in conditions(item)"
rowspan="2"
class='q-condition-heading'
><span class="q-wrap"><fittext vertical maxsize="14pt"><a class='q-label q-condition-label'
><span class="q-wrap"
><fittext vertical maxsize="14pt"><a class='q-label q-condition-label'
:title="c.tooltip" href="#" @click.prevent
v-b-tooltip.focus
v-html="c.name"></a
@ -601,11 +584,11 @@ export default {
},
resultcolumns: {
type: Number,
default: 1
'default': 1
},
studentinfocolumns: {
type: Number,
default: 1
'default': 1
},
expanded: {
type: Boolean,
@ -638,11 +621,11 @@ export default {
props: {
resultcolumns: {
type: Number,
default: 1
'default': 1
},
studentinfocolumns: {
type: Number,
default: 1
'default': 1
},
},
data() {
@ -675,14 +658,14 @@ export default {
},
loading: {
type: Boolean,
default: false
'default': false
},
expansion: {
type: Object,
},
even: {
type: Boolean,
default: false,
'default': false,
}
},
data() {
@ -693,7 +676,7 @@ export default {
computed: {
lastaccess() {
if (this.student.lastaccess) {
return format_datetime(this.student.lastaccess*1000); // Takes date in milliseconds
return formatDatetime(this.student.lastaccess); // Takes date in milliseconds
} else {
return this.text.never;
}
@ -719,9 +702,6 @@ export default {
},
},
/* https://css-tricks.com/position-sticky-and-table-headers/ */
/* TODO: Rework below to make use of tables. Use <Thead> as main element. Then create multiple <tr> as needed for the headers.
This should create a much better view than using divs overal.
*/
template: `
<tr :class="'q-student-results userrow ' + (even?'even':'odd')">
<td class='q-studentinfo q-name'><fittext maxsize="12pt">{{student.firstname}} {{student.lastname}}</fittext></td>
@ -770,7 +750,7 @@ export default {
},
loading: {
type: Boolean,
default: false
'default': false
},
},
data() {
@ -790,7 +770,7 @@ export default {
completion_icon() {
const completion = this.item.completion;
switch (completion) {
default: // case "incomplete"
default: // Case "incomplete"
return "circle-o";
case "pending":
return "question-circle";
@ -819,7 +799,7 @@ export default {
class="fa fa-exclamation-triangle t-not-enrolled-alert"
:title="text.student_not_tracked"></i>
</template>
<template v-else>
<template v-else-if="item.lineenrolled" >
<i v-b-popover.top
:class="'fa fa-'+completion_icon+
' r-completion-'+item.completion"
@ -839,7 +819,7 @@ export default {
},
loading: {
type: Boolean,
default: false
'default': false
},
conditionidx: {
type: Number,
@ -872,7 +852,7 @@ export default {
completion_icon() {
const completion = this.condition_completion();
switch (completion) {
default: // case "incomplete"
default: // Case "incomplete"
return "circle-o";
case "pending":
return "question-circle";
@ -927,16 +907,17 @@ export default {
return this.condition.status;
} else if (course.grades) {
return this.condition.completion;
} else {
return "incomplete";
}
}
},
methods: {
},
// TODO: Show actual grades when relevant at all (don;t forget the grade point completion requirement)
template: `
<span class='q-conditionresult'>
<fittext maxsize="10pt" singleline dynamic>
<fittext v-if="item.lineenrolled" maxsize="10pt" singleline dynamic>
<template v-if="loading">
<div class="spinner-border spinner-border-sm text-info" role="status"></div>
</template>
@ -955,8 +936,5 @@ export default {
</span>
`,
});
},
};

View File

@ -1,16 +1,21 @@
/* eslint no-var: "error"*/
/* eslint no-console: "off"*/
/* eslint camelcase: "off" */
/* eslint-disable no-trailing-spaces */
/* eslint-env es6*/
// Put this file in path/to/plugin/amd/src
import {load_strings} from './util/string-helper';
import {format_date, studyplanDates, studyplanTiming} from './util/date-helper';
import {loadStrings} from './util/string-helper';
import {formatDate, studyplanDates, studyplanTiming} from './util/date-helper';
import FitTextVue from './util/fittext-vue';
export default {
install(Vue /* ,options */) {
let strings = load_strings({
Vue.use(FitTextVue);
let strings = loadStrings({
studyplancard: {
open: "open",
noenddate: "noenddate",
@ -18,6 +23,7 @@ export default {
description: "studyplan_description",
completed: "completed",
details: "studyplan_details",
suspended: "suspended",
},
details: {
details: "studyplan_details",
@ -43,6 +49,10 @@ export default {
open: {
type: Boolean
},
ignoresuspend: {
type: Boolean,
'default': false,
},
},
data() {
return {
@ -50,16 +60,27 @@ export default {
};
},
computed: {
timeless() {
const plan = this.value;
if (!plan.pages || plan.pages.length == 0 || plan.pages[0].timeless) {
return true;
} else {
return false;
}
},
timing() {
return studyplanTiming(this.value);
},
dates() {
const dates = studyplanDates(this.value);
return {
start: format_date(dates.start),
end: (dates.end)?format_date(dates.end):this.text.noenddate,
start: formatDate(dates.start),
end: (dates.end) ? formatDate(dates.end) : this.text.noenddate,
};
},
suspended() {
return (this.value.suspended && !this.ignoresuspend);
}
},
methods: {
@ -69,7 +90,7 @@ export default {
},
template: `
<b-card
:class="'s-studyplan-card timing-' + timing"
:class="'s-studyplan-card timing-' + timing + (suspended?' s-suspended':'')"
>
<template #header></template>
@ -78,8 +99,11 @@ export default {
<div class='s-studyplan-card-info'>
<div class='s-studyplan-card-titlebar'>
<b-card-title>
<a class='title' v-if='open' href='#' @click.prevent='onOpenClick($event)'>{{value.name}}</a>
<a class='title' v-if='open && !suspended'
href='#' @click.prevent='onOpenClick($event)'>{{value.name}}</a>
<template v-else>{{value.name}}</template>
<div v-if="suspended" class='text-danger'
><fittext maxsize="12pt">{{text.suspended}}</fittext></div>
</b-card-title>
<div class='s-studyplan-card-titleslot'><slot name='title'></slot></div>
</div>
@ -87,7 +111,7 @@ export default {
{{ text.idnumber }}: {{ value.idnumber }}
</div>
<s-progress-bar
v-if='value.progress !== undefined && value.progress !== null'
v-if='!suspended && value.progress !== undefined && value.progress !== null'
v-model="value.progress"
></s-progress-bar>
</div>
@ -95,14 +119,14 @@ export default {
</div>
<slot></slot>
<template #footer>
<span :class="'t-timing-'+timing" v-html="dates.start + ' - '+ dates.end"></span>
<span v-if="!timeless" :class="'t-timing-'+timing" v-html="dates.start + ' - '+ dates.end"></span>
<span class="s-studyplan-card-buttons">
<slot name='footer'></slot>
<s-studyplan-details
v-model="value"
v-if="value.description"
><i class='fa fa-info-circle'></i></s-studyplan-details>
<b-button style="float:right;" v-if='open' variant='primary'
<b-button style="float:right;" v-if='open && !suspended' variant='primary'
@click.prevent='onOpenClick($event)'>{{ text.open }}</b-button>
</span>
</template>
@ -117,11 +141,15 @@ export default {
},
min: {
type: Number,
default() { return 0;}
default() {
return 0;
}
},
max: {
type: Number,
default() { return 1;}
default() {
return 1;
}
}
},
data() {
@ -173,8 +201,6 @@ export default {
`,
});
/*
* S-STUDYLINE-HEADER-HEADING
* The only reasing this is not a simple empty div, is the fact that the header height
@ -184,7 +210,9 @@ export default {
props: {
identifier: {
type: Number, // Page reference.
default() { return 0;}
default() {
return 0;
}
}
},
data() {
@ -217,11 +245,17 @@ export default {
Vue.component('s-studyline-header-period', {
props: {
value: {
type: Object, // dict with layer as index
type: Object, // Dict with layer as index
},
identifier: {
type: Number, // Page reference.
default() { return 0;}
default() {
return 0;
}
},
mode: {
type: String,
'default': "view",
}
},
mounted() {
@ -242,10 +276,10 @@ export default {
},
computed: {
startdate() {
return format_date(this.value.startdate);
return formatDate(this.value.startdate);
},
enddate() {
return format_date(this.value.enddate);
return formatDate(this.value.enddate);
},
current() {
if (this.value && this.value.startdate && this.value.enddate) {
@ -253,8 +287,7 @@ export default {
const pstart = new Date(this.value.startdate);
const pend = new Date(this.value.enddate);
return (now >= pstart && now < pend);
}
else {
} else {
return false;
}
}
@ -275,10 +308,12 @@ export default {
</span>
</b-tooltip>
<slot></slot
><p class="s-studyline-header-period-datespan small">
<span class="date">{{ startdate }}</span> - <span class="date">{{ enddate }}</span>
><p v-if="value.startdate > 0"
class="s-studyline-header-period-datespan small">
<span class="date">{{ startdate }}</span>
- <span class="date" v-if="this.value.enddate > 0">{{ enddate }}</span><span class="date" v-else>&infin;</span>
</p>
<div v-if="current" class='s-studyline-period-highlight'></div>
<div v-if="current && mode == 'view'" class='s-studyline-period-highlight'></div>
</div>
`,
});
@ -291,15 +326,21 @@ export default {
},
variant: {
type: String,
default() { return "info"; }
default() {
return "info";
}
},
pill: {
type: Boolean,
default() { return false; }
default() {
return false;
}
},
size: {
type: String,
default() { return "";}
default() {
return "";
}
}
},
data() {
@ -345,15 +386,21 @@ export default {
},
variant: {
type: String,
default() { return "info"; }
default() {
return "info";
}
},
position: {
type: String,
default() { return "below"; }
default() {
return "below";
}
},
size: {
type: String,
default() { return "";}
default() {
return "";
}
}
},
data() {
@ -375,7 +422,7 @@ export default {
},
methods: {
displaydate(field) {
return format_date(field.value,false);
return formatDate(field.value, false);
},
},
template: `
@ -388,7 +435,10 @@ export default {
<span v-else-if='field.type == "checkbox"'
:class="'value ' + (field.checked?'true':'false')">{{ field.value }}</span>
<span v-else-if='field.type == "textarea"'>
<a class='text-info' href='#' v-b-modal="field.courseid+'_'+field.fieldname">{{text.show}}...</a>
<a class='text-info'
@click.prevent.stop=""
href='#'
v-b-modal="field.courseid+'_'+field.fieldname">{{text.show}}...</a>
<b-modal
:id="field.courseid+'_'+field.fieldname"
:title="field.title""
@ -410,38 +460,40 @@ export default {
props: {
value: {
type: Object,
default() { return null;}
default() {
return null;
}
},
options: {
type: Array,
},
grouped: {
type: Boolean,
default: false,
'default': false,
},
titlefield: {
type: String,
default: "title",
'default': "title",
},
labelfield: {
type: String,
default: "label",
'default': "label",
},
optionsfield: {
type: String,
default: "options",
'default': "options",
},
defaultselectable: {
type: Boolean,
default: false,
'default': false,
},
variant: {
type: String,
default: "",
'default': "",
},
arrows: {
type: Boolean,
default: false,
'default': false,
},
},
data() {
@ -472,8 +524,12 @@ export default {
return f;
},
bubblevalue: {
get() { return (this.value)?this.value:null;},
set(v) { this.$emit('input',(v)?v:null);},
get() {
return (this.value) ? this.value : null;
},
set(v) {
this.$emit('input', (v) ? v : null);
},
}
},
methods: {

View File

@ -0,0 +1,50 @@
/* eslint no-var: "error" */
/* eslint no-unused-vars: "off" */
/* eslint linebreak-style: "off" */
/* eslint no-trailing-spaces: "off" */
/* eslint capitalized-comments: "off" */
/* eslint-env es6 */
import Debugger from './debugger';
const debug = new Debugger("browserbuttonevents");
/**
*
* @param {function} backwardCB
* @param {function} forwardCB
* @param {function} reloadCB
*/
export function addBrowserButtonEvent(backwardCB, forwardCB = null, reloadCB = null) {
debug.log("Registering navigation events", backwardCB, forwardCB, reloadCB);
const reorient = (e) => { // After travelling in the history stack
const positionLastShown = Number( // If none, then zero
sessionStorage.getItem('positionLastShown'));
// debug.log("Popstate event",e,positionLastShown,history);
let position = history.state; // Absolute position in stack
if (position === null) { // Meaning a new entry on the stack
position = positionLastShown + 1; // Top of stack
// (1) Stamp the entry with its own position in the stack
history.replaceState(position, /* no title */'');
}
// (2) Keep track of the last position shown
sessionStorage.setItem('positionLastShown', String(position));
// (3) Discover the direction of travel by comparing the two
const direction = Math.sign(position - positionLastShown);
debug.log('Travel direction is ' + direction);
// One of backward (-1), reload (0) and forward (1)
if (direction == -1 && backwardCB) {
backwardCB();
}
if (direction == 1 && forwardCB) {
forwardCB();
}
if (direction == 0 && reloadCB) {
reloadCB();
}
};
// addEventListener( 'pageshow', reorient );
addEventListener('popstate', reorient); // Travel in same page
}

View File

@ -1,4 +1,5 @@
/*eslint no-trailing-spaces: "off"*/
/* eslint camelcase: "off" */
/* eslint capitalized-comments: "off" */
/* eslint no-eval: "off" */
/* **********************************
@ -18,7 +19,7 @@ export const Absolute = {
/** One quarter of a millimeter. 1Q = 1/40th of 1cm */
Q: 96 / 101.6,
/** One inch. 1in = 2.54cm = 96px */
in: 96,
'in': 96,
/** One pica. 1pc = 12pt = 1/6th of 1in */
pc: 96 / 6,
/** One point. 1pt = 1/72nd of 1in */
@ -31,6 +32,7 @@ export const Absolute = {
* Equal to 1% of the height of the viewport
* @param {number} count
* @param {object} ctx
* @returns {number}
*/
vh: (count = 1, ctx) => {
return ((ctx ? ctx.viewportHeight : window.innerHeight) / 100) * count;
@ -39,6 +41,7 @@ export const Absolute = {
* Equal to 1% of the width of the viewport
* @param {number} count
* @param {object} ctx
* @returns {number}
*/
vw: (count = 1, ctx) => {
return ((ctx ? ctx.viewportWidth : window.innerWidth) / 100) * count;
@ -47,6 +50,7 @@ export const Absolute = {
* 1/100th of the smallest viewport side
* @param {number} count
* @param {object} ctx
* @returns {number}
*/
vmin: (count = 1, ctx) => {
return (
@ -61,6 +65,7 @@ export const Absolute = {
* 1/100th of the largest viewport side
* @param {number} count
* @param {object} ctx
* @returns {number}
*/
vmax: (count = 1, ctx) => {
return (
@ -75,6 +80,7 @@ export const Absolute = {
* Represents the font-size of <html> element
* @param {number} count
* @param {object} ctx
* @returns {number}
*/
rem: (count = 1, ctx) => {
return (
@ -89,6 +95,7 @@ export const Absolute = {
* percent of width
* @param {number} count
* @param {object} ctx
* @returns {number}
*/
"%w": (count = 1, ctx) => {
return ((ctx ? ctx.width : document.body.clientWidth) / 100) * count;
@ -97,6 +104,7 @@ export const Absolute = {
* percent of height
* @param {number} count
* @param {object} ctx
* @returns {number}
*/
"%h": (count = 1, ctx) => {
return ((ctx ? ctx.height : document.body.clientHeight) / 100) * count;
@ -120,7 +128,7 @@ export const Absolute = {
* @param {*} fromUnits
* @param {*} toUnits
* @param {*} ctx
* @returns
* @returns {number}
*/
export function convert(count, fromUnits, toUnits, ctx = calcCtx()) {
const baseUnit = Units[fromUnits];
@ -138,7 +146,7 @@ export const Absolute = {
* @param {*} expr
* @param {*} toUnits
* @param {*} ctx
* @returns
* @returns {String}
*/
export function convertAllInStr(expr, toUnits, ctx = calcCtx()) {
return expr.replace(UnitRegexpGM, (substr, count, unit) => {
@ -149,7 +157,7 @@ export const Absolute = {
/**
*
* @param {*} el
* @returns
* @returns {object}
*/
export function calcCtx(el) {
if (el) {
@ -182,13 +190,16 @@ export const Absolute = {
* @param {*} expression
* @param {*} el_ctx
* @param {*} ctx
* @returns
* @returns {String}
*/
export function calc(expression, el_ctx, ctx) {
if (el_ctx === undefined) {ctx = calcCtx(); }
else {
if (el_ctx === undefined) {
ctx = calcCtx();
} else {
if (el_ctx instanceof HTMLElement) {
if (!ctx) {ctx = calcCtx(el_ctx); }
if (!ctx) {
ctx = calcCtx(el_ctx);
}
} else {
ctx = el_ctx;
}

View File

@ -1,11 +1,15 @@
/* eslint capitalized-comments: "off" */
/**
* Format a date according to localized custom
* @param {Date|string} d The date to convert
* @param {boolean} short Short format (default false)
* @returns {string}
*/
export function format_date(d,short){
export function formatDate(d, short) {
if (!(d instanceof Date)) {
if (typeof d == 'number') {
d *= 1000; // Convert from seconds to milliseconds.
}
d = new Date(d);
}
@ -26,8 +30,11 @@ export function format_date(d,short){
* @param {boolean} short Short format (default false)
* @returns {string}
*/
export function format_datetime(d,short){
export function formatDatetime(d, short) {
if (!(d instanceof Date)) {
if (typeof d == 'number') {
d *= 1000; // Convert from seconds to milliseconds.
}
d = new Date(d);
}
@ -52,8 +59,12 @@ export function format_datetime(d,short){
* @returns {Object} Object containing formatted start and end dates and span information
*/
export function datespaninfo(first, last) {
if(!(first instanceof Date)){ first = new Date(first);}
if(!(last instanceof Date)){ last = new Date(last);}
if (!(first instanceof Date)) {
first = new Date(first);
}
if (!(last instanceof Date)) {
last = new Date(last);
}
// Make sure the end date is at the very end of the day and the start date at the very beginning
first.setHours(0);
@ -80,8 +91,8 @@ export function datespaninfo(first,last){
weeks: weeks,
days: wdaysleft,
formatted: {
first: format_date(first),
last: format_date(last),
first: formatDate(first),
last: formatDate(last),
}
};
@ -90,7 +101,7 @@ export function datespaninfo(first,last){
/**
* Format a Date object to YYYY-MM-DD format
* @param {Date} date Date object
* @returns Date string in YYYY-MM-DD format
* @returns {String} Date string in YYYY-MM-DD format
*/
function dateYYYYMMDD(date) {
const d = new Date(date);
@ -111,9 +122,9 @@ function dateYYYYMMDD(date) {
* String formatting function - replaces {name} in string by value of same key in values parameter
* @param {string} datestr String containing date format in YYYY-MM-DD
* @param {int} days Object containing keys to replace {key} strings with. Make negative to subtract
* @returns Date string in YYYY-MM-DD format
* @returns {String} Date string in YYYY-MM-DD format
*/
export function add_days(datestr,days) {
export function addDays(datestr, days) {
const date = new Date(datestr);
const newdate = new Date(date.getTime() + (days * 86400000)); // Add n days in ms.
return dateYYYYMMDD(newdate);
@ -122,12 +133,13 @@ export function add_days(datestr,days) {
/**
* Determine start and end dates of a studyplan
* @param {*} plan
* @returns
* @returns {object}
*/
export function studyplanDates(plan) {
let earliestStart = null;
let latestEnd = null;
let openEnded = false;
for (const ix in plan.pages) {
const page = plan.pages[ix];
const s = new Date(page.startdate);
@ -159,6 +171,12 @@ export function studyplanDates(plan) {
*/
export function studyplanTiming(plan) {
const now = new Date().getTime();
// If timeless or no timing info is available, all plans are present
if (!plan.pages && plan.pages.length == 0 || (plan.pages[0] && plan.pages[0].timeless)) {
return 'present';
}
const dates = studyplanDates(plan);
if (dates.start < now) {
@ -179,6 +197,10 @@ export function studyplanTiming(plan) {
*/
export function studyplanPageTiming(page) {
const now = new Date().getTime();
if (page.timeless) {
return "present";
}
const start = new Date(page.startdate);
const end = (page.enddate) ? (new Date(page.enddate)) : null;

View File

@ -14,15 +14,20 @@
function debounce(func, wait, immediate) {
let timeout;
return function() {
let context = this, args = arguments;
let context = this;
let args = arguments;
let later = function() {
timeout = null;
if (!immediate){ func.apply(context, args); }
if (!immediate) {
func.apply(context, args);
}
};
let callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow){ func.apply(context, args); }
if (callNow) {
func.apply(context, args);
}
};
}

View File

@ -7,11 +7,11 @@ import Config from "core/config";
/**
* Start a new debugger
* @param {*} handle The string to attach to all messages from this debugger
* @returns Debugger object
* @returns {object} Debugger object
*/
export default function(handle) {
let output_enabled = Config.developerdebug;
if(output_enabled){
let outputEnabled = Config.developerdebug;
if (outputEnabled) {
console.warn(`In development environment. Debugger output enabled for ${handle}`);
} else {
console.warn(`In production environment. Debugger output disabled for ${handle}`);
@ -19,67 +19,73 @@ export default function (handle) {
return {
write() {
if (output_enabled) {
if (outputEnabled) {
let args = Array.prototype.slice.call(arguments);
args.unshift(handle + ": ");
console.info.apply(console, args);
}
},
log() {
if (outputEnabled) {
let args = Array.prototype.slice.call(arguments);
args.unshift(handle + ": ");
console.log.apply(console, args);
}
},
info() {
if (output_enabled) {
if (outputEnabled) {
let args = Array.prototype.slice.call(arguments);
args.unshift(handle + ": ");
console.info.apply(console, args);
}
},
warn() {
if (output_enabled) {
if (outputEnabled) {
let args = Array.prototype.slice.call(arguments);
args.unshift(handle + ": ");
console.warn.apply(console, args);
}
},
error() {
if (output_enabled) {
if (outputEnabled) {
let args = Array.prototype.slice.call(arguments);
args.unshift(handle + ": ");
console.error.apply(console, args);
}
},
time() {
if (output_enabled) {
if (outputEnabled) {
let args = Array.prototype.slice.call(arguments);
args.unshift(handle + ": ");
console.time.apply(console, args);
}
},
timeEnd() {
if (output_enabled) {
if (outputEnabled) {
let args = Array.prototype.slice.call(arguments);
args.unshift(handle + ": ");
console.timeEnd.apply(console, args);
}
},
timeLog() {
if (output_enabled) {
if (outputEnabled) {
let args = Array.prototype.slice.call(arguments);
args.unshift(handle + ": ");
console.timeLog.apply(console, args);
}
},
timeStamp() {
if (output_enabled) {
if (outputEnabled) {
let args = Array.prototype.slice.call(arguments);
args.unshift(handle + ": ");
console.timeStamp.apply(console, args);
}
},
enable: function debugger_enable() {
output_enabled = true;
enable() {
outputEnabled = true;
},
disable: function debugger_disable() {
output_enabled = false;
disable() {
outputEnabled = false;
}
};

View File

@ -1,11 +1,11 @@
/* eslint no-unused-vars: warn */
/* eslint max-len: ["error", { "code": 160 }] */
/* eslint-disable no-trailing-spaces */
/* eslint capitalized-comments: "off" */
/* eslint-env es6*/
import {calc} from "./css-calc";
import fitty from "./fitty";
import {textFit} from "./textfit";
export default {
install(Vue/* ,options */) {
@ -13,11 +13,11 @@ export default {
props: {
maxsize: {
type: String,
default: "512px",
'default': "512px",
},
minsize: {
type: String,
default: "10px",
'default': "10px",
},
vertical: Boolean,
singleline: Boolean,
@ -62,8 +62,8 @@ export default {
}
},
template: `
<div class='q-fittext' ref='container' :style="rootStyle">
<span :style="'display:block; white-space:'+ ((singleline)?'nowrap':'normal')+';'" class='q-fittext-text' ref='text'><slot></slot>
<div class='q-fittext' ref='container' :style="rootStyle + ';min-height:0;min-width:0;'">
<span :style="'display:block; white-space:'+ ((singleline)?'nowrap':'normal')+';' + rootStyle" class='q-fittext-text' ref='text'><slot></slot>
</span
></div>
`,

View File

@ -1,4 +1,4 @@
/*eslint no-console: "off"*/
/* eslint-disable */
/*
Copyright (c) 2017-2021 Rik Schennink - All rights reserved.

View File

@ -1,10 +1,10 @@
/**
* convert a text field into an integer only text field
* Convert a text field into an integer only text field
* @param {string} id The Id of the form field
* @param {bool} unsigned Allow only unsigned values
* @param {bool} nonzero Do not allow zero values
*/
export function text_integer(id,unsigned=false,nonzero=false){
export function textInteger(id, unsigned = false, nonzero = false) {
const element = document.getElementById(id);
if (element) {
@ -26,6 +26,7 @@ export function text_integer(id,unsigned=false,nonzero=false){
return true;
}
}
return null;
});
}
}

View File

@ -2,21 +2,37 @@
/* eslint no-console: "off" */
/* eslint no-bitwise: "off" */
/* eslint-disable no-trailing-spaces */
/* eslint camelcase: "off" */
/* eslint capitalized-comments: "off" */
/* eslint-env es6 */
// Put this file in path/to/plugin/amd/src
import {call} from 'core/ajax';
import {processCollectedJavascript} from 'core/fragment';
import {replaceNodeContents} from 'core/templates';
import notification from 'core/notification';
import {load_strings} from './string-helper';
import {loadStrings} from './string-helper';
import Debugger from './debugger';
// import {markFormSubmitted} from 'core_form/changechecker'; // Moodle 4.00+ only
// import {notifyFormSubmittedByJavascript} from 'core_form/events'; // Moodle 4.00+ only
/* Moodle 3.11 safe import for when needed
let markFormSubmitted = () => {};
let notifyFormSubmittedByJavascript = () => {};
import('core_form/changechecker').then((ns) => {
debug.info(ns);
if(ns.markFormSubmitted) {
markFormSubmitted = ns.markFormSubmitted;
}
if(ns.notifyFormSubmittedByJavascript) {
notifyFormSubmittedByJavascript = ns.notifyFormSubmittedByJavascript;
}
}).catch(()=>{});
*/
/**
* Create a random UUID in both secure and insecure contexts
* @returns UUID
* @returns {String} UUID
*/
function create_uuid() {
if (crypto.randomUUID !== undefined) {
@ -31,7 +47,7 @@ function create_uuid() {
export default {
install(Vue /* ,options */) {
let debug = new Debugger("treestudyplan-mform-helper");
let strings = load_strings({
let strings = loadStrings({
editmod: {
save$core: "save$core",
cancel$core: "cancel$core",
@ -48,15 +64,15 @@ export default {
},
title: {
type: String,
default: "",
'default': "",
},
variant: {
type: String,
default: "primary",
'default': "primary",
},
type: {
type: String,
default: "link",
'default': "link",
}
},
data() {
@ -75,7 +91,7 @@ export default {
methods: {
openForm() {
const self = this;
self.$refs["editormodal"].show();
self.$refs.editormodal.show();
},
onShown() {
const self = this;
@ -89,14 +105,15 @@ export default {
self.loading = false;
// Process the collected javascript;
const js = processCollectedJavascript(data.javascript);
replaceNodeContents(self.$refs["content"], html, js);
replaceNodeContents(self.$refs.content, html, js);
self.initListenChanges();
return;
}).catch(notification.exception);
},
onSave() {
const self = this;
let form = this.$refs["content"].getElementsByTagName("form")[0];
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.
@ -114,6 +131,7 @@ export default {
}])[0].then((response) => {
const updatedplan = JSON.parse(response.data);
self.$emit("saved", updatedplan, formdata);
return;
}).catch(notification.exception);
}
/* No error if we cannot save, since it would just be to handle the edge case
@ -133,7 +151,7 @@ export default {
return canSave;
},
initListenChanges() {
const content = this.$refs["content"];
const content = this.$refs.content;
this.inputs = content.querySelectorAll("input.form-control");
// Check if save needs to be blocked immediately. (delay call by a few ms)

View File

@ -1,23 +1,42 @@
/* eslint no-var: "error" */
/*eslint no-unused-vars: "off" */
/*eslint linebreak-style: "off" */
/*eslint no-trailing-spaces: "off" */
/* eslint-env es6*/
import {call} from 'core/ajax';
import notification from 'core/notification';
// Prepare default value.
let premiumstatus = {
enabled: false,
website: "",
name: "",
expires: "",
};
let premiumstatusCache = null;
/**
* Check if premium status is enabled.
* @returns {Object} The map with strings loaded in
* @returns {Bool} True/False
*/
export function enabled (){
return !!premiumstatus.enabled;
export function premiumenabled() {
const premium = premiumstatus();
if (premium) {
return (premium.enabled) ? true : false;
} else {
return true; // Temporarily return true, that is easier than going full async.
}
}
/**
* Get premium status object
* @returns {Object} The premium status object
*/
function premiumstatus() {
if (!premiumstatusCache) {
// Retrieve premium status if needed.
call([{
methodname: 'local_treestudyplan_premiumstatus',
args: {}
}])[0].then((response) => {
premiumstatusCache = response;
return;
}).catch(notification.exception);
}
return premiumstatusCache;
}
// Preload premium status.
premiumstatus();

View File

@ -0,0 +1,138 @@
/* eslint no-unused-vars: warn */
/* eslint max-len: ["error", { "code": 160 }] */
/* eslint-disable no-trailing-spaces */
/* eslint-disable no-console */
/* eslint-env es6*/
import Debugger from './debugger';
let debug = new Debugger("p-sidebar");
export default {
install(Vue /* ,options */) {
Vue.component('p-sidebar', {
props: {
value: {
type: Boolean,
'default': true,
},
right: {
type: Boolean,
'default': false,
},
shadow: {
type: Boolean,
'default': false,
},
target: {
type: String,
'default': 'body',
},
offsetRef: {
type: String,
'default': '',
}
},
data() {
return {
wrapper: null,
contentwrapper: null,
resizeobserver: null,
};
},
computed: {
},
methods: {
initWrappers(target) {
let initializeWrapperContent = false;
// First check if the sidebar wrapper already exists.
// Creating the wrappers over and over again is a recipe for disaster.
this.wrapper = document.querySelector("#p-sidebar-wrapper");
if (!this.wrapper) {
initializeWrapperContent = true;
// Otherwise, create it.
this.wrapper = document.createElement("div");
this.wrapper.setAttribute("id", "p-sidebar-wrapper");
}
// First check if the contentwrapper already exists
this.contentwrapper = document.querySelector("#p-sidebar-contentwrapper");
if (!this.contentwrapper) {
initializeWrapperContent = true;
// Otherwise, create it.
this.contentwrapper = document.createElement("div");
this.contentwrapper.setAttribute("id", "p-sidebar-contentwrapper");
this.wrapper.appendChild(this.contentwrapper);
}
if (initializeWrapperContent) {
// Find containing target (otherwise use body)
let targetEl = document.querySelector(target);
console.info(`Targeting '${target}' to `, targetEl);
if (!targetEl || targetEl.nodeType == "HTML") {
targetEl = document.querySelector("body");
}
debug.warn(`Initializing wrappers with content of target `, targetEl);
// Move all target content parts to content wrapper....
while (targetEl.childNodes.length > 0) {
this.contentwrapper.appendChild(targetEl.childNodes[0]);
}
// Add sidebar wrapper to target Element
targetEl.appendChild(this.wrapper);
}
},
rePosition(right) {
// Place the container elsewhere in the DOM.
const el = this.$refs.container;
if (right) {
this.wrapper.insertBefore(el, this.contentwrapper.nextSibling);
} else {
this.wrapper.insertBefore(el, this.contentwrapper);
}
},
setOffset(reference) {
const ref = reference ? document.querySelector(reference) : null;
console.info(`Setting offset from '${reference}'`, ref);
let offsetTop = (ref ? ref.offsetTop : 0);
offsetTop += (offsetTop != 0) ? "px" : "";
const el = this.$refs.container;
el.style.height = `calc( 100vh - ${offsetTop})`;
el.style.marginTop = offsetTop;
}
},
watch: {
right(newVal) {
this.rePosition(newVal);
},
offsetRef(reference) {
this.setOffset(reference);
}
},
mounted() {
const self = this;
const el = self.$refs.container;
this.initWrappers(this.target);
this.setOffset(this.offsetRef);
this.rePosition(this.right, this.besides);
this.resizeObserver = new ResizeObserver(() => {
let wx = 0 - el.getBoundingClientRect().width;
wx += (wx != 0) ? "px" : "";
el.style.setProperty("--p-sidebar-hideoffset", wx);
});
this.resizeObserver.observe(el);
},
unmounted() {
if (this.resizeObserver) {
this.resizeObserver.disconnect();
}
},
template: `
<div>
<div ref='container'
:class="'p-sidebar ' + (right?'p-sidebar-right ':'') + (shadow?'p-sidebar-shadow ':'') + (value?'shown ':'hidden ')"
><slot></slot></div>
</div>
`,
});
},
};

43
amd/src/util/settings.js Normal file
View File

@ -0,0 +1,43 @@
/* eslint no-var: "error" */
/* eslint-env es6*/
import {call} from 'core/ajax';
import notification from 'core/notification';
// Prepare default value.
let settingcache = null;
/**
* Check if premium status is enabled.
* @param {String} key
* @returns {*}
*/
export function settings(key) {
const settings = loadsettings();
if (key in settings) {
return settings[key];
} else {
return null;
}
}
/**
* Get settings from server or cache
* @returns {Object} The settings object
*/
function loadsettings() {
if (!settingcache) {
// Retrieve setting cache if needed.
call([{
methodname: 'local_treestudyplan_getsettings',
args: {}
}])[0].then((response) => {
settingcache = response;
return;
}).catch(notification.exception);
}
return settingcache;
}
// Preload premium status.
loadsettings();

View File

@ -1,30 +1,40 @@
/* eslint camelcase: "off" */
import {get_strings} from 'core/str';
import {getStrings} from 'core/str';
import Debugger from './debugger';
let debug = new Debugger("string-helper");
/* Determine the proper getstrings function to use (MDL4.3+ recommends use of getStrings, which is jquery independent) */
const getstr_func = (getStrings !== undefined)?getStrings:get_strings;
const getstrFunc = (getStrings !== undefined) ? getStrings : get_strings;
/**
* Load the translation of strings from a strings object
* @param {Object} strings The map of strings
* @param {String} plugin Name of plugin to load for by default (leave empty for 'local_treestudyplan')
* @returns {Object} The map with strings loaded in
*/
export function load_strings(strings){
export function loadStrings(strings, plugin) {
if (plugin === undefined) {
plugin = 'local_treestudyplan';
}
for (let idx in strings) {
let stringkeys = [];
for (const handle in strings[idx]) {
const key = strings[idx][handle];
let parts = key.split(/[$@]/);
let identifier = parts[0];
let component = (parts.length > 1)?parts[1]:'local_treestudyplan';
let component = (parts.length > 1) ? parts[1] : plugin;
stringkeys.push({key: identifier, component: component});
}
getstr_func(stringkeys).then(function(str){
getstrFunc(stringkeys).then(function(str) {
let i = 0;
for (const handle in strings[idx]) {
strings[idx][handle] = str[i];
i++;
}
return;
}).catch((x) => {
debug.warn(x);
});
}
@ -35,9 +45,13 @@ export function load_strings(strings){
* Load the translation of strings from a strings object based on keys
* Used for loading values for a drop down menu or the like
* @param {Object} string_keys The map of stringkeys
* @param {String} plugin Name of plugin to load for by default (leave empty for 'local_treestudyplan')
* @returns {Object} The map with strings loaded in
*/
export function load_stringkeys(string_keys){
export function loadStringKeys(string_keys, plugin) {
if (plugin === undefined) {
plugin = 'local_treestudyplan';
}
for (let idx in string_keys) {
// Get text strings for condition settings
let stringkeys = [];
@ -45,15 +59,18 @@ export function load_stringkeys(string_keys){
const key = string_keys[idx][i].textkey;
let parts = key.split("$");
let identifier = parts[0];
let component = (parts.length > 1)?parts[1]:'local_treestudyplan';
let component = (parts.length > 1) ? parts[1] : plugin;
stringkeys.push({key: identifier, component: component});
}
getstr_func(stringkeys).then(function(strings){
getstrFunc(stringkeys).then(function(strings) {
for (const i in strings) {
const s = strings[i];
const l = string_keys[idx][i];
l.text = s;
}
return;
}).catch((x) => {
debug.warn(x);
});
}
return string_keys;
@ -63,7 +80,7 @@ export function load_stringkeys(string_keys){
* String formatting function - replaces {name} in string by value of same key in values parameter
* @param {string} str String t
* @param {object} values Object containing keys to replace {key} strings with
* @returns Formatted string
* @returns {string} Formatted string
*/
export function strformat(str, values) {
return str.replace(/\{(\w+)\}/g, (m, m1) => {

View File

@ -22,27 +22,31 @@ const cos = Math.cos;
const sin = Math.sin;
const π = Math.PI;
const f_matrix_times = (( [[a,b], [c,d]], [x,y]) => [ a * x + b * y, c * x + d * y]);
const f_rotate_matrix = (x => [[cos(x),-sin(x)], [sin(x), cos(x)]]);
const f_vec_add = (([a1, a2], [b1, b2]) => [a1 + b1, a2 + b2]);
const fMatrixTimes = (([[a, b], [c, d]], [x, y]) => [a * x + b * y, c * x + d * y]);
const fRotateMatrix = (x => [[cos(x), -sin(x)], [sin(x), cos(x)]]);
const fVecAdd = (([a1, a2], [b1, b2]) => [a1 + b1, a2 + b2]);
// function modified by pmkuipers for text params
// Function modified by pmkuipers for text params
/**
* Create svg path text for an arc
* @param {*} center [cx,cy] center of ellipse
* @param {*} radius [rx,ry] major minor radius
* @param {*} angle [t1, Δ] start angle, in radian, angle to sweep, in radian. positive.
* @param {*} φ rotation on the whole, in radian
* @returns a SVG path element that represent a ellipse. Text describing the arc path in an svg path element
* @returns {string} a SVG path description that represent a ellipse. Text describing the arc path in an svg path element
*/
const svgarcpath = (([cx, cy], [rx, ry], [t1, Δ], φ) => {
Δ = Δ % (2 * π);
const rotMatrix = f_rotate_matrix (φ);
const [sX, sY] = ( f_vec_add ( f_matrix_times ( rotMatrix, [rx * cos(t1), ry * sin(t1)] ), [cx,cy] ) );
const [eX, eY] = ( f_vec_add ( f_matrix_times ( rotMatrix, [rx * cos(t1+Δ), ry * sin(t1+Δ)] ), [cx,cy] ) );
const rotMatrix = fRotateMatrix(φ);
const [sX, sY] = (fVecAdd(fMatrixTimes(rotMatrix, [rx * cos(t1), ry * sin(t1)]), [cx, cy]));
const [eX, eY] = (fVecAdd(fMatrixTimes(rotMatrix, [rx * cos(t1 + Δ), ry * sin(t1 + Δ)]), [cx, cy]));
const fA = ((Δ > π) ? 1 : 0);
const fS = ((Δ > 0) ? 1 : 0);
if (isNaN(eY) || isNaN(eX)) {
return "";
} else {
return "M " + sX + " " + sY + " A " + [rx, ry, φ / (2 * π) * 360, fA, fS, eX, eY].join(" ");
}
});
/**
@ -51,13 +55,13 @@ const svgarcpath = (([cx,cy],[rx,ry], [t1, Δ], φ ) => {
* @param {*} radius [rx,ry] major minor radius
* @param {*} angle [t1, Δ] start angle, in radian, angle to sweep, in radian. positive.
* @param {*} φ rotation on the whole, in radian
* @returns a SVG path element that represent a ellipse.
* @returns {string} a SVG path element that represent a ellipse.
*/
const svgarc = (([cx, cy], [rx, ry], [t1, Δ], φ) => {
const path_2wk2r = document.createElementNS("http://www.w3.org/2000/svg", "path");
const path2wk2r = document.createElementNS("http://www.w3.org/2000/svg", "path");
const d = svgarcpath([cx, cy], [rx, ry], [t1, Δ], φ);
path_2wk2r.setAttribute("d", d);
return path_2wk2r;
path2wk2r.setAttribute("d", d);
return path2wk2r;
});
export {svgarc, svgarcpath};

View File

@ -1,251 +0,0 @@
/**
* textFit v2.3.1
* Previously known as jQuery.textFit
* 11/2014 by STRML (strml.github.com)
* MIT License
*
* To use: textFit(document.getElementById('target-div'), options);
*
* Will make the *text* content inside a container scale to fit the container
* The container is required to have a set width and height
* Uses binary search to fit text with minimal layout calls.
* Version 2.0 does not use jQuery.
*/
var defaultSettings = {
alignVert: false, // if true, textFit will align vertically using css tables
alignHoriz: false, // if true, textFit will set text-align: center
multiLine: false, // if true, textFit will not set white-space: no-wrap
detectMultiLine: true, // disable to turn off automatic multi-line sensing
minFontSize: 6,
maxFontSize: 80,
reProcess: true, // if true, textFit will re-process already-fit nodes. Set to 'false' for better performance
widthOnly: false, // if true, textFit will fit text to element width, regardless of text height
alignVertWithFlexbox: false, // if true, textFit will use flexbox for vertical alignment
};
/**
* Create new textFit element(S)
* @param {*} els element or element list
* @param {*} options Options (See default settings in textfit.js)
*/
export function textFit(els, options) {
if (!options) { options = {}; }
// Extend options.
var settings = {};
for(var key in defaultSettings){
if(options.hasOwnProperty(key)){
settings[key] = options[key];
} else {
settings[key] = defaultSettings[key];
}
}
// Convert jQuery objects into arrays
if (typeof els.toArray === "function") {
els = els.toArray();
}
// Support passing a single el
var elType = Object.prototype.toString.call(els);
if (elType !== '[object Array]' && elType !== '[object NodeList]' &&
elType !== '[object HTMLCollection]'){
els = [els];
}
// Process each el we've passed.
for(var i = 0; i < els.length; i++){
processItem(els[i], settings);
}
}
/**
* The meat. Given an el, make the text inside it fit its parent.
* @param {DOMElement} el Child el.
* @param {Object} settings Options for fit.
*/
function processItem(el, settings){
if (!isElement(el) || (!settings.reProcess && el.getAttribute('textFitted'))) {
return false;
}
// Set textFitted attribute so we know this was processed.
if(!settings.reProcess){
el.setAttribute('textFitted', 1);
}
var innerSpan, originalHeight, originalHTML, originalWidth;
var low, mid, high;
// Get element data.
originalHTML = el.innerHTML;
originalWidth = innerWidth(el);
originalHeight = innerHeight(el);
// Don't process if we can't find box dimensions
if (!originalWidth || (!settings.widthOnly && !originalHeight)) {
if(!settings.widthOnly) {
throw new Error('Set a static height and width on the target element ' + el.outerHTML +
' before using textFit!');
} else {
throw new Error('Set a static width on the target element ' + el.outerHTML +
' before using textFit!');
}
}
// Add textFitted span inside this container.
if (originalHTML.indexOf('textFitted') === -1) {
innerSpan = document.createElement('span');
innerSpan.className = 'textFitted';
// Inline block ensure it takes on the size of its contents, even if they are enclosed
// in other tags like <p>
innerSpan.style['display'] = 'inline-block';
innerSpan.innerHTML = originalHTML;
el.innerHTML = '';
el.appendChild(innerSpan);
} else {
// Reprocessing.
innerSpan = el.querySelector('span.textFitted');
// Remove vertical align if we're reprocessing.
if (hasClass(innerSpan, 'textFitAlignVert')){
innerSpan.className = innerSpan.className.replace('textFitAlignVert', '');
innerSpan.style['height'] = '';
el.className.replace('textFitAlignVertFlex', '');
}
}
// Prepare & set alignment
if (settings.alignHoriz) {
el.style['text-align'] = 'center';
innerSpan.style['text-align'] = 'center';
}
// Check if this string is multiple lines
// Not guaranteed to always work if you use wonky line-heights
var multiLine = settings.multiLine;
if (settings.detectMultiLine && !multiLine &&
innerSpan.getBoundingClientRect().height >= parseInt(window.getComputedStyle(innerSpan)['font-size'], 10) * 2){
multiLine = true;
}
// If we're not treating this as a multiline string, don't let it wrap.
if (!multiLine) {
el.style['white-space'] = 'nowrap';
}
low = settings.minFontSize;
high = settings.maxFontSize;
// Binary search for highest best fit
var size = low;
while (low <= high) {
mid = (high + low) / 2;
innerSpan.style.fontSize = mid + 'px';
var innerSpanBoundingClientRect = innerSpan.getBoundingClientRect();
if (
innerSpanBoundingClientRect.width <= originalWidth
&& (settings.widthOnly || innerSpanBoundingClientRect.height <= originalHeight)
) {
size = mid;
low = mid + 1;
} else {
high = mid - 1;
}
// await injection point
}
// found, updating font if differs:
if( innerSpan.style.fontSize != size + 'px' ) { innerSpan.style.fontSize = size + 'px'; }
// Our height is finalized. If we are aligning vertically, set that up.
if (settings.alignVert) {
addStyleSheet();
var height = innerSpan.scrollHeight;
if (window.getComputedStyle(el)['position'] === "static"){
el.style['position'] = 'relative';
}
if (!hasClass(innerSpan, "textFitAlignVert")){
innerSpan.className = innerSpan.className + " textFitAlignVert";
}
innerSpan.style['height'] = height + "px";
if (settings.alignVertWithFlexbox && !hasClass(el, "textFitAlignVertFlex")) {
el.className = el.className + " textFitAlignVertFlex";
}
}
}
/**
* Calculate height without padding.
* @param {*} el
* @returns
*/
function innerHeight(el){
var style = window.getComputedStyle(el, null);
return el.getBoundingClientRect().height -
parseInt(style.getPropertyValue('padding-top'), 10) -
parseInt(style.getPropertyValue('padding-bottom'), 10);
}
/**
* Calculate width without padding.
* @param {*} el
* @returns
*/
function innerWidth(el){
var style = window.getComputedStyle(el, null);
return el.getBoundingClientRect().width -
parseInt(style.getPropertyValue('padding-left'), 10) -
parseInt(style.getPropertyValue('padding-right'), 10);
}
/**
* Returns true if it is a DOM element
* @param {*} o
* @returns
*/
function isElement(o){
return (
typeof HTMLElement === "object" ? o instanceof HTMLElement : //DOM2
o && typeof o === "object" && o !== null && o.nodeType === 1 && typeof o.nodeName==="string"
);
}
/**
* Check if element has a specific class
* @param {*} element
* @param {*} cls
* @returns
*/
function hasClass(element, cls) {
return (' ' + element.className + ' ').indexOf(' ' + cls + ' ') > -1;
}
// Better than a stylesheet dependency
/**
* Add stylesheet to the page for this
* @returns
*/
function addStyleSheet() {
if (document.getElementById("textFitStyleSheet")) { return; }
var style = [
".textFitAlignVert{",
"position: absolute;",
"top: 0; right: 0; bottom: 0; left: 0;",
"margin: auto;",
"display: flex;",
"justify-content: center;",
"flex-direction: column;",
"}",
".textFitAlignVertFlex{",
"display: flex;",
"}",
".textFitAlignVertFlex .textFitAlignVert{",
"position: static;",
"}",].join("");
var css = document.createElement("style");
css.type = "text/css";
css.id = "textFitStyleSheet";
css.innerHTML = style;
document.body.appendChild(css);
}

View File

@ -33,8 +33,6 @@ $excludepaths = [
"build/*",
"build.*",
"vuemode.sh",
"amd/src",
"amd/src/*",
".git",
".git/*",
".gitignore",

View File

@ -50,6 +50,9 @@ foreach ($mappings as $cfg) {
}
print $OUTPUT->header();
print "<pre>";
print $CFG->gradebookroles;
print "</pre>";
if ($_POST["action"] == "update") {
// First loop through the scales to see which need to be updated.
@ -73,7 +76,7 @@ if ($_POST["action"] == "update") {
}
} else {
$scalecfg = (object)[ "scale_id" => $scale->id, ];
$scalecfg = (object)[ "scale_id" => $scale->id];
$requireinsert = false;
foreach (["min_progress", "min_completed"] as $handle) {
$key = "s_{$scale->id}_{$handle}";
@ -151,8 +154,8 @@ foreach ($scales as $scale) {
$scalecfg = $scalecfgs[$scale->id];
}
$attrsc = ['value' => '', 'disabled' => 'disabled', ];
$attrsp = ['value' => '', 'disabled' => 'disabled', ];
$attrsc = ['value' => '', 'disabled' => 'disabled'];
$attrsp = ['value' => '', 'disabled' => 'disabled'];
if (!isset($scalecfg) || $scalecfg->min_completed == "") {
$attrsc["selected"] = "selected";
@ -195,7 +198,7 @@ foreach ($scales as $scale) {
$data[] = $row;
}
print html_writer::start_tag("form", ["method" => "post", ]);
print html_writer::start_tag("form", ["method" => "post"]);
print html_writer::tag("input", null, ['name' => "action", 'value' => 'update', 'type' => 'hidden']);
$table = new html_table();
@ -224,7 +227,7 @@ foreach ($gradecfgs as $g) {
'autocomplete' => 'off']);
$row[] = html_writer::tag("input",
null,
['name' => "g_{$g->grade_points}_delete", 'type' => 'checkbox', ]);
['name' => "g_{$g->grade_points}_delete", 'type' => 'checkbox']);
$data[] = $row;
}

View File

@ -23,7 +23,6 @@
namespace local_treestudyplan;
use moodle_exception;
use \ValueError;
defined('MOODLE_INTERNAL') || die();
@ -78,7 +77,7 @@ abstract class aggregator {
* Create a new aggregatior object based on the specified method
* @param mixed $method Aggregation method
* @param mixed $configstr Configuration string for aggregator
* @throws ValueError If method is not found
* @throws moodle_exception If method is not found
*/
public static function create($method, $configstr): self {
if (self::supported($method)) {
@ -98,7 +97,7 @@ abstract class aggregator {
public static function create_or_default($method, $configstr): self {
try {
return self::create($method, $configstr);
} catch (\ValueError $x) {
} catch (\moodle_exception $x) {
return self::create(self::FALLBACK, "");
}
}
@ -202,7 +201,6 @@ abstract class aggregator {
return false;
}
/**
* Return the current configuration string.
* @return string Configuration string
@ -211,7 +209,6 @@ abstract class aggregator {
return "";
}
/**
* Webservice structure for basic aggregator info
* @param int $value Webservice requirement constant

View File

@ -24,6 +24,7 @@ namespace local_treestudyplan;
defined('MOODLE_INTERNAL') || die();
use local_treestudyplan\local\helpers\webservicehelper;
use local_treestudyplan\task\autocohortsync;
require_once($CFG->libdir.'/externallib.php');
@ -41,7 +42,11 @@ class associationservice extends \external_api {
* @var string
*/
const CAP_VIEW = "local/treestudyplan:viewuserreports";
/**
* Capability required to be linked as coach to a studyplan
* @var string
*/
const CAP_COACH = "local/treestudyplan:coach";
/**
* Webservice structure to use in describing a user
*/
@ -53,7 +58,8 @@ class associationservice extends \external_api {
"lastname" => new \external_value(PARAM_TEXT, 'last name'),
"idnumber" => new \external_value(PARAM_TEXT, 'id number'),
"email" => new \external_value(PARAM_TEXT, 'email address'),
"lastaccess" => new \external_value(PARAM_INT, 'id of last access this user had to any course in the studyplan', VALUE_OPTIONAL),
"lastaccess" => new \external_value(PARAM_INT,
'id of last access this user had to any course in the studyplan', VALUE_OPTIONAL),
]);
}
@ -119,13 +125,18 @@ class associationservice extends \external_api {
"shortpath" => array_map(function($c) {
return \context::instance_by_id($c)->get_context_name(false, true);
}, $ctxpath),
]
],
];
return $result;
}
/**
* Get last access time of user to any of the courses in the studyplan
* @param int $userid ID of user
* @param int $studyplanid ID of studyplan
*/
public static function user_lastaccess($userid, $studyplanid=null) {
global $DB;
if (!empty($studyplanid)) {
@ -135,27 +146,22 @@ class associationservice extends \external_api {
INNER JOIN {local_treestudyplan_page} p ON l.page_id = p.id
WHERE a.userid = :userid AND p.studyplan_id = :studyplanid";
$lastaccess = $DB->get_field_sql($lasql, ["userid" => $userid, "studyplanid" => $studyplanid]);
debug::write("Got lastaccess '{$lastaccess}' for user {$userid} in plan {$studyplanid}");
} else {
$lasql = "SELECT MAX(a.timeaccess) FROM {user_lastaccess} a
WHERE a.userid = :userid";
$lastaccess = $DB->get_field_sql($lasql, ["userid" => $userid]);
debug::write("Got lastaccess '{$lastaccess}' for user {$userid} in any course");
}
return $lastaccess;
}
/**
* Parameter description for webservice function list_cohort
*/
public static function list_cohort_parameters(): \external_function_parameters {
return new \external_function_parameters( [
'like' => new \external_value(PARAM_TEXT, 'search text', VALUE_OPTIONAL),
'exclude_id' => new \external_value(PARAM_INT, 'exclude members of this studyplan', VALUE_OPTIONAL),
'context_ic' => new \external_value(PARAM_INT, 'context for this request', VALUE_OPTIONAL),
'like' => new \external_value(PARAM_TEXT, 'search text'),
'studyplan_id' => new \external_value(PARAM_INT, 'id of studyplan to list for'),
] );
}
@ -169,33 +175,25 @@ class associationservice extends \external_api {
/**
* Search cohorts for matching string
* @param string $like String to match cohorts with
* @param null $excludeid Do not include these cohorts
* @param int $contextid Context to search (default system)
* @param int $studyplanid Do not include these cohorts
* @return array
*/
public static function list_cohort($like = '', $excludeid = null, $contextid = 1) {
public static function list_cohort($like, $studyplanid) {
global $CFG, $DB;
// Only allow this if the user has the right to edit in this context.
$context = webservicehelper::find_context($contextid);
$studyplan = studyplan::find_by_id($studyplanid);
$context = $studyplan->context();
webservicehelper::require_capabilities(self::CAP_EDIT, $context);
$pattern = "%{$like}%";
$params = ["pattern_nm" => $pattern, "pattern_id" => $pattern, ];
$params = ["pattern_nm" => $pattern, "pattern_id" => $pattern ];
$sql = "SELECT DISTINCT c.* from {cohort} c LEFT JOIN {local_treestudyplan_cohort} j ON c.id = j.cohort_id
WHERE c.visible = 1 AND(name LIKE :pattern_nm OR idnumber LIKE :pattern_id)";
if (isset($excludeid) && is_numeric($excludeid)) {
$sql .= " AND (j.studyplan_id IS NULL OR j.studyplan_id != :exclude_id)";
$params['exclude_id'] = $excludeid;
}
if ($contextid > 1) {
// System context returns all cohorts, including system cohorts.
// Otherwise, .
$sql .= " AND contextid = :context_id";
$params['context_id'] = $contextid;
}
WHERE c.visible = 1 AND(name LIKE :pattern_nm OR idnumber LIKE :pattern_id)
AND (j.studyplan_id IS NULL OR j.studyplan_id != :exclude_id)";
$params['exclude_id'] = $studyplanid;
$cohorts = [];
$rs = $DB->get_recordset_sql($sql, $params);
@ -212,8 +210,7 @@ class associationservice extends \external_api {
public static function find_user_parameters(): \external_function_parameters {
return new \external_function_parameters( [
'like' => new \external_value(PARAM_TEXT, 'search text'),
'exclude_id' => new \external_value(PARAM_INT, 'exclude members of this studyplan', VALUE_OPTIONAL),
'context_id' => new \external_value(PARAM_INT, 'context for this request', VALUE_OPTIONAL),
'studyplan_id' => new \external_value(PARAM_INT, 'id of studyplan to list for'),
] );
}
@ -227,15 +224,15 @@ class associationservice extends \external_api {
/**
* Search users for match
* @param string $like String to match user firstname/lastname with
* @param null $excludeid Do not include these users
* @param int $contextid Context to search (default system)
* @param int $studyplanid Id of studyplan to search for
* @return array
*/
public static function find_user($like, $excludeid = null, $contextid = 1) {
public static function find_user($like, $studyplanid) {
global $CFG, $DB;
// Only allow this if the user has the right to edit in this context.
$context = webservicehelper::find_context($contextid);
$studyplan = studyplan::find_by_id($studyplanid);
$context = $studyplan->context();
webservicehelper::require_capabilities(self::CAP_EDIT, $context);
$pattern = "%{$like}%";
@ -244,11 +241,9 @@ class associationservice extends \external_api {
"pattern_un" => $pattern,
];
$sql = "SELECT DISTINCT u.* from {user} u LEFT JOIN {local_treestudyplan_user} j ON u.id = j.user_id
WHERE u.deleted != 1 AND (firstname LIKE :pattern_fn OR lastname LIKE :pattern_ln OR username LIKE :pattern_un)";
if (isset($excludeid) && is_numeric($excludeid)) {
$sql .= " AND (j.studyplan_id IS NULL OR j.studyplan_id != :exclude_id)";
$params['exclude_id'] = $excludeid;
}
WHERE u.deleted != 1 AND (firstname LIKE :pattern_fn OR lastname LIKE :pattern_ln OR username LIKE :pattern_un)
AND (j.studyplan_id IS NULL OR j.studyplan_id != :exclude_id)";
$params['exclude_id'] = $studyplanid;
$users = [];
$rs = $DB->get_recordset_sql($sql, $params);
@ -470,10 +465,14 @@ class associationservice extends \external_api {
public static function associated_users($studyplanid) {
global $CFG, $DB;
$studyplan = studyplan::find_by_id($studyplanid);
if ($studyplan->is_coach()) {
\external_api::validate_context($studyplan->context());
} else {
webservicehelper::require_capabilities(self::CAP_VIEW, $studyplan->context());
}
$sql = "SELECT DISTINCT u.* FROM {user} u INNER JOIN {local_treestudyplan_user} j ON j.user_id = u.id
WHERE j.studyplan_id = :studyplan_id
WHERE j.studyplan_id = :studyplan_id AND u.deleted != 1
ORDER BY u.lastname, u.firstname";
$rs = $DB->get_recordset_sql($sql, ['studyplan_id' => $studyplanid]);
@ -566,8 +565,9 @@ class associationservice extends \external_api {
LEFT JOIN {cohort_members} cm ON u.id = cm.userid
LEFT JOIN {local_treestudyplan_cohort} tc ON cm.cohortid = tc.cohort_id
LEFT JOIN {local_treestudyplan_user} tu ON u.id = tu.user_id
WHERE tc.studyplan_id = :studyplanid
OR tu.studyplan_id = :studyplanidtoo
WHERE ( tc.studyplan_id = :studyplanid
OR tu.studyplan_id = :studyplanidtoo )
AND u.deleted != 1
ORDER BY u.lastname, u.firstname";
$rs = $DB->get_recordset_sql($sql, ["studyplanid" => $studyplan->id(), "studyplanidtoo" => $studyplan->id()]);
@ -585,7 +585,7 @@ class associationservice extends \external_api {
*/
public static function all_associated_grouped_parameters(): \external_function_parameters {
return new \external_function_parameters( [
"studyplan_id" => new \external_value(PARAM_INT, 'id of studyplan', VALUE_OPTIONAL),
"studyplan_id" => new \external_value(PARAM_INT, 'id of studyplan'),
] );
}
@ -609,14 +609,18 @@ class associationservice extends \external_api {
global $CFG, $DB;
$studyplan = studyplan::find_by_id($studyplanid);
if ($studyplan->is_coach()) {
\external_api::validate_context($studyplan->context());
} else {
webservicehelper::require_capabilities(self::CAP_VIEW, $studyplan->context());
}
$userlist = [
[
'id' => 0,
'label' => get_string("individuals", 'local_treestudyplan'),
'users' => self::associated_users($studyplanid),
]
],
];
$sql = "SELECT DISTINCT c.* FROM {cohort} c INNER JOIN {local_treestudyplan_cohort} j ON j.cohort_id = c.id
@ -627,7 +631,7 @@ class associationservice extends \external_api {
$sql = "SELECT DISTINCT u.id, u.username, u.firstname, u.lastname, u.idnumber, u.email
FROM {user} u
LEFT JOIN {cohort_members} cm ON u.id = cm.userid
WHERE cm.cohortid = :cohortid
WHERE cm.cohortid = :cohortid AND u.deleted != 1
ORDER BY u.lastname, u.firstname";
$rs = $DB->get_recordset_sql($sql, ["cohortid" => $c->id]);
@ -649,7 +653,6 @@ class associationservice extends \external_api {
return $userlist;
}
/**
* Sort a list of user models by firstname->lastname
* @param array $list Reference to list of user models
@ -698,17 +701,199 @@ class associationservice extends \external_api {
$studyplan = studyplan::find_by_id($studyplanid);
webservicehelper::require_capabilities(self::CAP_EDIT, $studyplan->context());
$enroller = new cascadecohortsync($studyplan);
$enroller->sync();
if (get_config("local_treestudyplan", "csync_users")) {
$userenroller = new cascadeusersync($studyplan);
$userenroller->sync();
}
$studyplan->clear_csync_changed(); // Clear the csync required flag.
autocohortsync::syncplan($studyplan);
return success::success()->model();
}
/**
* Parameter description for webservice function connect_user
*/
public static function connect_coach_parameters(): \external_function_parameters {
return new \external_function_parameters( [
"studyplan_id" => new \external_value(PARAM_INT, 'id of studyplan', VALUE_OPTIONAL),
"user_id" => new \external_value(PARAM_INT, 'id of user to link', VALUE_OPTIONAL),
] );
}
/**
* Return value description for webservice function connect_user
*/
public static function connect_coach_returns(): \external_description {
return new \external_single_structure([
"success" => new \external_value(PARAM_BOOL, 'operation completed succesfully'),
"msg" => new \external_value(PARAM_TEXT, 'message'),
]);
}
/**
* Connect a user to a studyplan
* @param mixed $studyplanid Id of studyplan
* @param mixed $userid Id of user
* @return array Success/fail model
*/
public static function connect_coach($studyplanid, $userid) {
global $CFG, $DB;
$studyplan = studyplan::find_by_id($studyplanid);
webservicehelper::require_capabilities(self::CAP_EDIT, $studyplan->context());
$user = $DB->get_record("user", ["id" => $userid]);
if (has_capability(self::CAP_COACH, $studyplan->context(), $user)) {
if (!$DB->record_exists('local_treestudyplan_coach', ['studyplan_id' => $studyplanid, 'user_id' => $userid])) {
$id = $DB->insert_record('local_treestudyplan_coach', [
'studyplan_id' => $studyplanid,
'user_id' => $userid,
]);
return ['success' => true, 'msg' => 'User connected as coach'];
} else {
return ['success' => true, 'msg' => 'User already connected as coach'];
}
} else {
return ['success' => false, 'msg' => 'User does not have coach capability in this studyplan\'s context'];
}
}
/**
* Parameter description for webservice function disconnect_user
*/
public static function disconnect_coach_parameters(): \external_function_parameters {
return new \external_function_parameters( [
"studyplan_id" => new \external_value(PARAM_INT, 'id of studyplan', VALUE_OPTIONAL),
"user_id" => new \external_value(PARAM_INT, 'id of user to link', VALUE_OPTIONAL),
] );
}
/**
* Return value description for webservice function disconnect_user
*/
public static function disconnect_coach_returns(): \external_description {
return new \external_single_structure([
"success" => new \external_value(PARAM_BOOL, 'operation completed succesfully'),
"msg" => new \external_value(PARAM_TEXT, 'message'),
]);
}
/**
* Disconnect a user from a studyplan
* @param mixed $studyplanid Id of studyplan
* @param mixed $userid Id of user
* @return array Success/fail model
*/
public static function disconnect_coach($studyplanid, $userid) {
global $CFG, $DB;
$studyplan = studyplan::find_by_id($studyplanid);
webservicehelper::require_capabilities(self::CAP_EDIT, $studyplan->context());
if ($DB->record_exists('local_treestudyplan_coach', ['studyplan_id' => $studyplanid, 'user_id' => $userid])) {
$DB->delete_records('local_treestudyplan_coach', [
'studyplan_id' => $studyplanid,
'user_id' => $userid,
]);
return ['success' => true, 'msg' => 'User Disconnected as coach'];
} else {
return ['success' => true, 'msg' => 'Connection does not exist'];
}
}
/**
* Parameter description for webservice function find_user
*/
public static function find_coach_parameters(): \external_function_parameters {
return new \external_function_parameters( [
'like' => new \external_value(PARAM_TEXT, 'search text'),
'studyplan_id' => new \external_value(PARAM_INT, 'studyplan id to associate for'),
] );
}
/**
* Return value description for webservice function find_user
*/
public static function find_coach_returns(): \external_description {
return new \external_multiple_structure(self::user_structure());
}
/**
* Search users for match
* @param string $like String to match user firstname/lastname with
* @param int $studyplanid Id of studyplan to search for
* @return array
*/
public static function find_coach($like, $studyplanid) {
global $CFG, $DB;
// Only allow this if the user has the right to edit in this context.
$studyplan = studyplan::find_by_id($studyplanid);
$context = $studyplan->context();
webservicehelper::require_capabilities(self::CAP_EDIT, $context);
$pattern = "%{$like}%";
$params = ["pattern_fn" => $pattern,
"pattern_ln" => $pattern,
"pattern_un" => $pattern,
];
$sql = "SELECT DISTINCT u.* FROM {user} u LEFT JOIN {local_treestudyplan_coach} j ON u.id = j.user_id
WHERE u.deleted != 1 AND (firstname LIKE :pattern_fn OR lastname LIKE :pattern_ln OR username LIKE :pattern_un)";
if (isset($excludeid) && is_numeric($excludeid)) {
$sql .= " AND (j.studyplan_id IS NULL OR j.studyplan_id != :exclude_id)";
$params['exclude_id'] = $studyplan;
}
$users = [];
$rs = $DB->get_recordset_sql($sql, $params);
foreach ($rs as $r) {
if (has_capability(self::CAP_COACH, $context, $r)) {
$users[] = static::make_user_model($r);
}
}
$rs->close();
self::sortusermodels($users);
return $users;
}
/**
* Parameter description for webservice function associated_users
*/
public static function associated_coaches_parameters(): \external_function_parameters {
return new \external_function_parameters( [
"studyplan_id" => new \external_value(PARAM_INT, 'id of studyplan', VALUE_OPTIONAL),
] );
}
/**
* Return value description for webservice function associated_users
*/
public static function associated_coaches_returns(): \external_description {
return new \external_multiple_structure(self::user_structure());
}
/**
* List all users associated to a studyplan
* @param mixed $studyplanid Id of studyplan
* @return array
*/
public static function associated_coaches($studyplanid) {
global $CFG, $DB;
$studyplan = studyplan::find_by_id($studyplanid);
webservicehelper::require_capabilities(self::CAP_VIEW, $studyplan->context());
$sql = "SELECT DISTINCT u.* FROM {user} u INNER JOIN {local_treestudyplan_coach} j ON j.user_id = u.id
WHERE j.studyplan_id = :studyplan_id AND u.deleted != 1
ORDER BY u.lastname, u.firstname";
$rs = $DB->get_recordset_sql($sql, ['studyplan_id' => $studyplanid]);
$users = [];
foreach ($rs as $u) {
$user = self::make_user_model($u);
$users[] = $user;
}
$rs->close();
self::sortusermodels($users);
return $users;
}
}

View File

@ -26,7 +26,7 @@ defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir.'/externallib.php');
use award_criteria;
use \core_badges\badge;
use core_badges\badge;
use moodle_url;
/**
@ -97,10 +97,9 @@ class badgeinfo {
*/
public static function exists($id) {
global $DB;
return is_numeric($id) && $DB->record_exists('badge', array('id' => $id));
return is_numeric($id) && $DB->record_exists('badge', ['id' => $id]);
}
/**
* Webservice structure for editor info
* @param int $value Webservice requirement constant
@ -120,7 +119,6 @@ class badgeinfo {
/**
* Webservice model for editor info
* @param int[] $studentlist List of user id's to use for checking issueing progress within a study plan
* @return array Webservice data model
*/
public function simple_model() {
@ -168,10 +166,10 @@ class badgeinfo {
/**
* Webservice model for editor info
* @param int[] $studentlist List of user id's to use for checking issueing progress within a study plan
* @param int[]|null $studentlist List of user id's to use for checking issueing progress within a study plan
* @return array Webservice data model
*/
public function editor_model(array $studentlist = null) {
public function editor_model(?array $studentlist = null) {
if ($this->badge->type == BADGE_TYPE_SITE) {
$context = \context_system::instance();
} else {
@ -258,9 +256,8 @@ class badgeinfo {
"active" => $this->badge->is_active(),
];
if ($issued) {
$issueinfo = $DB->get_record('badge_issued', array('badgeid' => $this->badge->id, 'userid' => $userid));
$issueinfo = $DB->get_record('badge_issued', ['badgeid' => $this->badge->id, 'userid' => $userid]);
$badge['dateissued'] = date("Y-m-d", $issueinfo->dateissued);
if ($issueinfo->expiredate) {
$badge['dateexpire'] = date("Y-m-d", $issueinfo->dateexpire);
@ -272,7 +269,10 @@ class badgeinfo {
return $badge;
}
/**
* Define structure for badge completion parts
* @param object $value VALUE_OPTIONAL or VALUE_REQUIRED
*/
protected static function badge_completion_structure($value) {
return new \external_single_structure([
"types" => new \external_multiple_structure(new \external_single_structure([
@ -300,6 +300,10 @@ class badgeinfo {
], 'badge completion information', $value);
}
/**
* Generate completion model of this badge for a given user
* @param int $userid id of user to check for
*/
protected function badge_completion_data($userid): array {
$count = 0;
@ -333,7 +337,7 @@ class badgeinfo {
if ($badgeagg == BADGE_CRITERIA_AGGREGATION_ANY) {
/* If ANY completion overall, count only the criteria type with the highest completion percentage -.
Overwrite data if current type is more complete */
$typefraction = ($typecount > 0)?($typeprogress / $typecount):0;
$typefraction = ($typecount > 0) ? ($typeprogress / $typecount) : ($typeprogress / $typecount);
if ($typefraction > $fraction || ($fraction == 0 && $typecount > $count)) {
$fraction = $typefraction;
$count = $typecount;
@ -343,12 +347,13 @@ class badgeinfo {
/* If ALL completion overall, just add it up to the total */
$count += $typecount;
$progress += $typeprogress;
$typefraction = floatval($typeprogress) / floatval($typecount);
}
$aggrgation_handle = ($typeagg == BADGE_CRITERIA_AGGREGATION_ALL)?"all":"any";
$aggrgationhandle = ($typeagg == BADGE_CRITERIA_AGGREGATION_ALL) ? "all" : "any";
$typeinfo = [
'title' => ucfirst(get_string("criteria_descr_$type","badges", get_string($aggrgation_handle,"core"))),
'aggregation' => $aggrgation_handle,
'title' => ucfirst(get_string("criteria_descr_$type", "badges", get_string($aggrgationhandle, "core"))),
'aggregation' => $aggrgationhandle,
'criteria' => $typecrit,
'count' => $typecount,
'progress' => $typeprogress,
@ -358,11 +363,11 @@ class badgeinfo {
}
}
$aggrgation_handle = ($badgeagg == BADGE_CRITERIA_AGGREGATION_ALL)?"all":"any";
$aggrgationhandle = ($badgeagg == BADGE_CRITERIA_AGGREGATION_ALL) ? "all" : "any";
return [
"types" => $types,
"title" => ucfirst(get_string("criteria_descr_0","badges", mb_strtolower(get_string($aggrgation_handle,"core")))),
"aggregation" => $aggrgation_handle,
"title" => ucfirst(get_string("criteria_descr_0", "badges", mb_strtolower(get_string($aggrgationhandle, "core")))),
"aggregation" => $aggrgationhandle,
"count" => $count,
"progress" => $progress,
"fraction" => $fraction,
@ -387,11 +392,10 @@ class badgeinfo {
}
/**
* Gets the module instance from the database and returns it.
* If no module instance exists this function returns false.
*
* @param int $cmid Course module id
* @return stdClass|bool
*/
private static function get_mod_instance($cmid) {
@ -400,7 +404,7 @@ class badgeinfo {
FROM {course_modules} cm,
{modules} md
WHERE cm.id = ? AND
md.id = cm.module", array($cmid));
md.id = cm.module", [$cmid]);
if ($rec) {
return get_coursemodule_from_id($rec->name, $cmid);
@ -412,12 +416,12 @@ class badgeinfo {
/**
* Gets role name.
* If no such role exists this function returns null.
*
* @param int $rid Role id
* @return string|null
*/
private static function get_role_name($rid) {
global $DB, $PAGE;
$rec = $DB->get_record('role', array('id' => $rid));
$rec = $DB->get_record('role', ['id' => $rid]);
if ($rec) {
return role_get_name($rec, \context_system::instance(), ROLENAME_BOTH);
@ -427,10 +431,9 @@ class badgeinfo {
}
/**
* [Description for get_award_subcriteria]
*
* @param award_criteria $crit
*
* Get subcriteria info for awarding a badge for a user
* @param award_criteria $crit The criteria to build info for
* @param int|null $userid Optional userid to check subcriteria completion for
* @return array
*
*/
@ -448,7 +451,8 @@ class badgeinfo {
$title = \html_writer::tag('b', '"' . get_string('modulename', $mod->modname) . ' - ' . $mod->name . '"');;
$description = \html_writer::tag('b', '"' . get_string('modulename', $mod->modname) . ' - ' . $mod->name . '"');
if (isset($p['bydate'])) {
$description .= get_string('criteria_descr_bydate', 'badges', userdate($p['bydate'], get_string('strftimedate', 'core_langconfig')));
$description .= get_string('criteria_descr_bydate', 'badges',
userdate($p['bydate'], get_string('strftimedate', 'core_langconfig')));
}
}
@ -459,13 +463,14 @@ class badgeinfo {
'completion' => [
'title' => get_string('completeactivity', 'core').
get_string('modulename', $mod->modname) . ' - ' . $mod->name,
]
]
],
],
];
if (isset($p["bydate"])) {
$subcrit["requirements"]["bydate"] = [
'title' => get_string('criteria_descr_bydate', 'badges', userdate($p['bydate'], get_string('strftimedate', 'core_langconfig'))),
'title' => get_string('criteria_descr_bydate', 'badges',
userdate($p['bydate'], get_string('strftimedate', 'core_langconfig'))),
];
}
@ -487,14 +492,14 @@ class badgeinfo {
$subcrit["requirements"]["completion"]["completed"] = $modcompleted;
$check_date = true;
$checkdate = true;
if (isset($p["bydate"])) {
$date = $data->timemodified;
$check_date = ($date <= $p['bydate']);
$subcrit["requirements"]["bydate"]["completed"] = $check_date;
$checkdate = ($date <= $p['bydate']);
$subcrit["requirements"]["bydate"]["completed"] = $checkdate;
}
$subcrit["completed"] = $modcompleted && $check_date;
$subcrit["completed"] = $modcompleted && $checkdate;
}
$list[] = $subcrit;
}
@ -513,20 +518,19 @@ class badgeinfo {
$subcrit = [
"title" => $title,
"description" => $description,
"requirements" => []
"requirements" => [],
];
if (isset($userid)) {
$crit = $DB->get_record('badge_manual_award', array('issuerrole' => $p['role'], 'recipientid' => $userid, 'badgeid' => $crit->badgeid));
$crit = $DB->get_record('badge_manual_award',
['issuerrole' => $p['role'], 'recipientid' => $userid, 'badgeid' => $crit->badgeid]);
$subcrit["completed"] = $crit !== false;
}
$list[] = $subcrit;
}
} else if ($crit->criteriatype == BADGE_CRITERIA_TYPE_SOCIAL) {
/* Unused in moodle - probably deprecated */
} else if ($crit->criteriatype == BADGE_CRITERIA_TYPE_COURSE || $crit->criteriatype == BADGE_CRITERIA_TYPE_COURSESET) {
if ($crit->criteriatype == BADGE_CRITERIA_TYPE_COURSE) {
$params = [reset($crit->params)]; // Only use the first parameter
$params = [reset($crit->params)]; // Only use the first parameter.
} else {
$params = $crit->params;
}
@ -538,7 +542,8 @@ class badgeinfo {
} else {
$description = \html_writer::tag('b', '"' . $course->fullname . '"');
if (isset($p['bydate'])) {
$description .= get_string('criteria_descr_bydate', 'badges', userdate($p['bydate'], get_string('strftimedate', 'core_langconfig')));
$description .= get_string( 'criteria_descr_bydate', 'badges',
userdate($p['bydate'], get_string('strftimedate', 'core_langconfig')));
}
if (isset($p['grade'])) {
$description .= get_string('criteria_descr_grade', 'badges', $p['grade']);
@ -552,19 +557,19 @@ class badgeinfo {
"requirements" => [
'completion' => [
'title' => get_string('coursecompleted', 'completion'),
]
]
],
],
];
if (isset($p["grade"])) {
$subcrit["requirements"]["grade"] = [
'title' => get_string('criteria_descr_grade', 'badges', $p["grade"]),
];
}
if (isset($p["bydate"])) {
$subcrit["requirements"]["bydate"] = [
'title' => get_string('criteria_descr_bydate', 'badges', userdate($p['bydate'], get_string('strftimedate', 'core_langconfig'))),
'title' => get_string('criteria_descr_bydate', 'badges',
userdate($p['bydate'], get_string('strftimedate', 'core_langconfig'))),
];
}
@ -573,19 +578,20 @@ class badgeinfo {
$coursecompleted = $coursecompletion->is_complete();
$subcrit["requirements"]["completion"]["completed"] = (bool) $coursecompleted;
$check_grade = true;
$checkgrade = true;
if (isset($p["grade"])) {
$grade = \grade_get_course_grade($userid, $course->id);
$check_grade = ($grade->grade >= $p['grade']);
$subcrit["requirements"]["grade"]["completed"] = (bool) $check_grade;
$checkgrade = ($grade->grade >= $p['grade']);
$subcrit["requirements"]["grade"]["completed"] = (bool) $checkgrade;
}
$check_date = true;
$checkdate = true;
if (isset($p["bydate"])) {
$check_date = ((bool) $coursecompletion->timecompleted) && ($coursecompletion->timecompleted <= $p["bydate"]);
$subcrit["requirements"]["bydate"]["completed"] = (bool) $check_date;
$checkdate = ((bool) $coursecompletion->timecompleted) &&
($coursecompletion->timecompleted <= $p["bydate"]);
$subcrit["requirements"]["bydate"]["completed"] = (bool) $checkdate;
}
$subcrit["completed"] = $coursecompleted && $check_grade && $check_date;
$subcrit["completed"] = $coursecompleted && $checkgrade && $checkdate;
}
$list[] = $subcrit;
}
@ -612,7 +618,7 @@ class badgeinfo {
$subcrit = [
"title" => $title,
"description" => $description,
"requirements" => []
"requirements" => [],
];
if (isset($userid)) {
@ -643,7 +649,7 @@ class badgeinfo {
}
} else if ($crit->criteriatype == BADGE_CRITERIA_TYPE_BADGE) {
foreach ($crit->params as $p) {
$badgename = $DB->get_field('badge', 'name', array('id' => $p['badge']));
$badgename = $DB->get_field('badge', 'name', ['id' => $p['badge']]);
if (!$badgename) {
$title = get_string('error:nosuchbadge', 'badges');
$description = get_string('error:nosuchbadge', 'badges');
@ -656,14 +662,14 @@ class badgeinfo {
"title" => $title,
"description" => $description,
"link" => (new \moodle_url($CFG->wwwroot."/badges/overview.php", ["id" => $p["badge"]]))->out(),
"requirements" => []
"requirements" => [],
];
if (isset($userid)) {
$badge = $DB->get_record('badge', array('id' => $p['badge']));
$badge = $DB->get_record('badge', ['id' => $p['badge']]);
// See if the user has earned this badge.
if ($badge) {
$awarded = $DB->get_record('badge_issued', array('badgeid' => $p['badge'], 'userid' => $userid));
$awarded = $DB->get_record('badge_issued', ['badgeid' => $p['badge'], 'userid' => $userid]);
$awarded = isset($awarded);
} else {
$awarded = false;
@ -674,7 +680,7 @@ class badgeinfo {
}
} else if ($crit->criteriatype == BADGE_CRITERIA_TYPE_COHORT) {
foreach ($crit->params as $p) {
$cohortname = $DB->get_field('cohort', 'name', array('id' => $p['cohort']));
$cohortname = $DB->get_field('cohort', 'name', ['id' => $p['cohort']]);
if (!$cohortname) {
$title = get_string('error:nosuchcohort', 'badges');
$description = get_string('error:nosuchcohort', 'badges');
@ -686,11 +692,11 @@ class badgeinfo {
$subcrit = [
"title" => $title,
"description" => $description,
"requirements" => []
"requirements" => [],
];
if (isset($userid)) {
$cohort = $DB->get_record('cohort', array('id' => $p['cohort']));
$cohort = $DB->get_record('cohort', ['id' => $p['cohort']]);
$ismember = (bool) \cohort_is_member($cohort->id, $userid);
$subcrit["completed"] = $ismember;
}
@ -734,7 +740,7 @@ class badgeinfo {
$subcrit = [
"title" => $title,
"description" => $description,
"requirements" => []
"requirements" => [],
];
if (isset($userid)) {
@ -742,7 +748,7 @@ class badgeinfo {
to use criteria api class instead of direct calls....
*/
$proficiency = false;
$badge = $DB->get_record('badge', array('id' => $crit->badgeid));
$badge = $DB->get_record('badge', ['id' => $crit->badgeid]);
if ($badge->type == BADGE_TYPE_SITE) {
$uc = \core_competency\api::get_user_competency($userid, $p['competency']);
$proficiency = $uc->get('proficiency');
@ -777,17 +783,6 @@ class badgeinfo {
throw new \moodle_exception("\$course argument must be course id or course object", "local_treestudyplan");
}
/*
$crit->citeriatype = BADGE_CRITERIA_TYPE_COURSE
$badge->params[0] = ["course"=> $courseid]
$crit->citeriatype = BADGE_CRITERIA_TYPE_COURSESET
$badge->params[] = [["course"=> $courseid]...]
$crit->citeriatype = BADGE_CRITERIA_TYPE_ACTIVITY
$badge->params[] = [["module"=> $cmid]...]
$crit->citeriatype = BADGE_CRITERIA_TYPE_COMPETENCY
$badge->params[] = [["competency"=> $competencyid]...]
*/
$search = trim($search);
$conditions = "";
$basesqlparams = ['courseid' => $courseid];
@ -803,7 +798,8 @@ class badgeinfo {
}
[$ctypesql, $ctypeinparams] = $DB->get_in_or_equal([BADGE_CRITERIA_TYPE_COURSESET,BADGE_CRITERIA_TYPE_COURSE],SQL_PARAMS_NAMED);
[$ctypesql, $ctypeinparams] = $DB->get_in_or_equal([BADGE_CRITERIA_TYPE_COURSESET, BADGE_CRITERIA_TYPE_COURSE],
SQL_PARAMS_NAMED);
$sqlparams = array_merge($basesqlparams, $ctypeinparams);
$sql = "SELECT DISTINCT b.id from {badge} b
@ -811,7 +807,6 @@ class badgeinfo {
INNER JOIN {badge_criteria_param} p on p.critid = crit.id
WHERE p.value = :courseid AND crit.criteriatype $ctypesql $conditions";
$courserelids = $DB->get_fieldset_sql($sql, $sqlparams);
[$ctypesql, $ctypeinparams] = $DB->get_in_or_equal([BADGE_CRITERIA_TYPE_COMPETENCY], SQL_PARAMS_NAMED);
@ -822,10 +817,8 @@ class badgeinfo {
INNER JOIN {competency_coursecomp} cc on cc.competencyid = p.value
WHERE cc.courseid = :courseid AND crit.criteriatype $ctypesql $conditions";
$competencyrelids = $DB->get_fieldset_sql($sql, $sqlparams);
$badgeids = [];
foreach ([$courserelids, $competencyrelids] as $list) {
foreach ($list as $id) {
@ -853,7 +846,6 @@ class badgeinfo {
throw new \moodle_exception("\$course argument must be course id or course object", "local_treestudyplan");
}
$search = trim($search);
$conditions = "";
$basesqlparams = ['courseid' => $courseid];
@ -868,7 +860,7 @@ class badgeinfo {
$basesqlparams = array_merge($basesqlparams, $inparams);
}
// Get all course badges for this course
// Get all course badges for this course.
$badgesids = [];
$sql = "SELECT DISTINCT b.id from {badge} b
WHERE b.courseid = :courseid AND $conditions";
@ -879,29 +871,26 @@ class badgeinfo {
}
/**
* [Description for find_page_related_badges]
*
* @param studyplanpage $page
* @param mixed $search=""
* @param mixed $active=false
* @param mixed $includecoursebadges=true
*
* Find badges related to a page
* @param studyplanpage $page Page to search for/in
* @param mixed $search Search string to use
* @param mixed $active Include only active badges
* @param mixed $includecoursebadges Include course badges
* @return array of badgeinfo
*
*/
public static function find_page_related_badges(studyplanpage $page, $search="", $active=false, $includecoursebadges=true) {
$badgeids = [];
foreach (studyline::find_page_children($page) as $line) {
foreach (studyitem::find_studyline_children($line) as $item) {
if ($item->type() == studyitem::COURSE && $item->courseid()) {
$courseid = $item->courseid();
$relatedbadges = badgeinfo::find_badges_by_course_relation($courseid,$search,$active);
$relatedbadges = self::find_badges_by_course_relation($courseid, $search, $active);
foreach ($relatedbadges as $id) {
$badgeids[] = $id;
}
if ($includecoursebadges) {
$coursebadges = badgeinfo::find_course_badges($courseid,$search,$active);
$coursebadges = self::find_course_badges($courseid, $search, $active);
foreach ($coursebadges as $id) {
$badgeids[] = $id;
}
@ -944,7 +933,7 @@ class badgeinfo {
$basesqlparams = array_merge($basesqlparams, $inparams);
}
// Get all course badges for this course
// Get all course badges for this course.
$sql = "SELECT DISTINCT b.id from {badge} b
WHERE $conditions";
@ -962,5 +951,4 @@ class badgeinfo {
return $badges;
}
}

View File

@ -25,7 +25,7 @@ defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir.'/externallib.php');
use \local_treestudyplan\studyplan;
use local_treestudyplan\studyplan;
/**
* Task class to synchronize enrolled cohorts in courses with cohorts associated with studyplans these courses are in
@ -39,6 +39,14 @@ class cascadecohortsync {
private $studyplan;
/** @var int */
private $studyplanid;
/** @var object */
private $enrol;
/** @var object */
private $manualenrol;
/** @var int */
private $roleid;
/** @var array */
private $cohortids;
/**
* Create a synchronization task for a studyplan
@ -47,6 +55,12 @@ class cascadecohortsync {
public function __construct(studyplan $studyplan) {
$this->studyplan = $studyplan;
$this->studyplanid = $studyplan->id();
$this->enrol = \enrol_get_plugin(self::METHOD);
$this->manualenrol = \enrol_get_plugin("manual");
// Get the roleid to use for synchronizations.
$this->roleid = get_config("local_treestudyplan", "csync_roleid");
// And find the cohorts that are linked to this studyplan.
$this->cohortids = $this->studyplan->get_linked_cohort_ids();
}
/**
@ -78,8 +92,8 @@ class cascadecohortsync {
require_once($CFG->dirroot.'/group/lib.php');
// Check to see if the group name already exists in this course.
if ($DB->record_exists('groups', array('name' => $groupname, 'courseid' => $courseid))) {
$group = $DB->get_record('groups', array('name' => $groupname, 'courseid' => $courseid));
if ($DB->record_exists('groups', ['name' => $groupname, 'courseid' => $courseid])) {
$group = $DB->get_record('groups', ['name' => $groupname, 'courseid' => $courseid]);
return $group->id;
}
// The named group doesn't exist, so create a new one in the course.
@ -91,6 +105,58 @@ class cascadecohortsync {
return $groupid;
}
/**
* Find all cohort sync instances for a specific course
* @param int $courseid Id of course to search for
*/
private function list_cohortsyncs($courseid) {
global $DB;
// Find all cohort syncs for this course.
$searchparams = [
'courseid' => $courseid,
'enrol' => self::METHOD,
'roleid' => get_config("local_treestudyplan", "csync_roleid"),
];
$list = [];
$records = $DB->get_records("enrol", $searchparams);
foreach ($records as $instance) {
if (!empty($instance->customtext4)) {
// Only add to the list if customtext4 is not empty.
$list[] = $instance;
}
}
return $list;
}
/**
* Remove this studyplan reference from cohort sync
* @param object $enrolinstance Enrol instance to unlink or update
*/
private function unlink_cohortsync($enrolinstance) {
// So it may or may not need to be removed.
$plans = json_decode($enrolinstance->customtext4);
if ($plans !== null && is_array($plans)) {
// If a valid array is not returned, better leave it be, we don't want to mess with it.
// Otherwise, check if we should remove it.
if (in_array($this->studyplanid, $plans)) {
// If this plan was referenced before.
// First remove the link.
$fplans = self::array_remove_value($plans, $this->studyplanid);
if (count($fplans) == 0) {
// Delete the sync if there are no studyplan references left.
$this->enrol->delete_instance($enrolinstance);
} else {
// Otherwise just update the references so this studyplan is no longer linked.
$this->enrol->update_instance($enrolinstance, (object)["customtext4" => json_encode($fplans)]);
}
}
}
}
/**
* Enroll all cohorts associated to the studyplan in the courses linked to this studyplan
*/
@ -106,17 +172,74 @@ class cascadecohortsync {
removed outside of this script, it was determined to be the simplest and cleanest solution.
*/
$enrol = \enrol_get_plugin(self::METHOD);
// Find the courses that need to be synced to the associated cohorts.
$courseids = $this->studyplan->get_linked_course_ids();
// And find the cohorts that are linked to this studyplan.
$cohortids = $this->studyplan->get_linked_cohort_ids();
// Find the study lines associated to this studyplan.
$lines = $this->studyplan->get_all_studylines();
foreach ($lines as $line) {
$this->syncline($line);
}
}
/**
* Enroll all cohorts associated to the studyplan in the courses linked to the specified study line
* @param studyline $line Studyline to sync enrolment for
*/
public function syncline(studyline $line) {
global $DB;
// Find the courses that need to be synced to the associated cohorts.
$courseids = $line->get_linked_course_ids();
if ($line->enrollable()) {
// Since the studyline is enrollable, we need to cascade by student.
foreach ($courseids as $courseid) {
/* 1: Associate the users by individual association */
$course = \get_course($courseid);
$userids = $line->get_enrolled_userids();
if (count($userids) > 0) {
// Get the manual enrol instance for this course.
$instanceparams = ['courseid' => $courseid, 'enrol' => 'manual'];
if (!($instance = $DB->get_record('enrol', $instanceparams))) {
if ($instanceid = $this->manualenrol->add_default_instance($course)) {
$instance = $DB->get_record('enrol', ['id' => $instanceid]);
} else {
// Instance not added for some reason, so report an error somewhere.
// (or not).
$instance = null;
}
}
if ($instance !== null) {
foreach ($userids as $uid) {
// Try a manual registration - it will just be updated if it is already there....
$this->manualenrol->enrol_user($instance, $uid, $this->roleid);
}
}
}
/* 2: Remove the cohort sync for this studyplan for this line if it exists,
since this course should not have cohort sync form this studyplan
Then - if no one uses the link anymore, deactivate it...
This does not remove the sync from courses that are unlinked from a studplan.
But maybe we do not want that anyway, since it is generally a good idea to keep student
access to courses available.
*/
if (get_config("local_treestudyplan", "csync_autoremove")) {
// Only try the autoremove if the option is enabled.
foreach ($this->list_cohortsyncs($courseid) as $instance) {
$this->unlink_cohortsync($instance);
}
}
}
} else {
// Studyline is not enrollable, we can enrol by cohort...
foreach ($courseids as $courseid) {
$course = \get_course($courseid);
// First create any nonexistent links.
foreach ($cohortids as $cohortid) {
foreach ($this->cohortids as $cohortid) {
$cohort = $DB->get_record('cohort', ['id' => $cohortid]);
$instanceparams = [
@ -141,7 +264,6 @@ class cascadecohortsync {
// It already exists.
// Check if this studyplan is already referenced in customtext4 in json format.
// TODO: Check this code - Maybe add option to not remember manually added stuff .
$plans = json_decode($instance->customtext4);
if ($plans == false || !is_array(($plans))) {
// If the data was not an array (null or garbled), count it as manually added.
@ -156,7 +278,7 @@ class cascadecohortsync {
// If not, add it to the reference.
$plans[] = (int)($this->studyplanid);
$enrol->update_instance($instance, (object)["customtext4" => json_encode($plans)]);
$this->enrol->update_instance($instance, (object)["customtext4" => json_encode($plans)]);
}
} else {
@ -171,12 +293,13 @@ class cascadecohortsync {
$instancenewparams['customint2'] = self::uploadenrolmentmethods_get_group($courseid, $groupname);
}
if ($instanceid = $enrol->add_instance($course, $instancenewparams)) {
if ($instanceid = $this->enrol->add_instance($course, $instancenewparams)) {
// Also record the (as of yet only) studyplans id requiring this association.
// In the customtext4 field in json format.
$instance = $DB->get_record('enrol', array('id' => $instanceid));
$enrol->update_instance($instance, (object)["customtext4" => json_encode([(int)($this->studyplanid)])]);
$instance = $DB->get_record('enrol', ['id' => $instanceid]);
$this->enrol->update_instance($instance, (object)[
"customtext4" => json_encode([(int)($this->studyplanid)])]);
// Successfully added a valid new instance, so now instantiate it.
// First synchronise the enrolment.
@ -201,45 +324,17 @@ class cascadecohortsync {
if (get_config("local_treestudyplan", "csync_autoremove")) {
// Only try the autoremove if the option is enabled.
// Find all cohort syncs for this course.
$searchparams = [
'courseid' => $courseid,
'enrol' => self::METHOD,
'roleid' => get_config("local_treestudyplan", "csync_roleid"),
];
$records = $DB->get_records("enrol", $searchparams);
foreach ($records as $instance) {
if (!empty($instance->customtext4)) {
foreach ($this->list_cohortsyncs($courseid) as $instance) {
// Only check the records that have studyplan information in the customtext4 field.
// First check if the cohort is not one of the cohort id's we have associated.
if (!in_array($instance->customint1, $cohortids)) {
if (!in_array($instance->customint1, $this->cohortids)) {
$this->unlink_cohortsync($instance);
}
}
// So it may or may not need to be removed.
$plans = json_decode($instance->customtext4);
if ($plans !== null && is_array($plans)) {
// If a valid array is not returned, better leave it be, we don't want to mess with it.
// Otherwise, check if we should remove it.
if (in_array($this->studyplanid, $plans)) {
}
}
}
}
// If this plan was referenced before.
// First remove the link.
$fplans = self::array_remove_value($plans, $this->studyplanid);
if (count($fplans) == 0) {
// Delete the sync if there are no studyplan references left.
$enrol->delete_instance($instance);
} else {
// Otherwise just update the references so this studyplan is no longer linked.
$enrol->update_instance($instance, (object)["customtext4" => json_encode($fplans)]);
}
}
}
}
}
}
}
}
}
}

View File

@ -25,7 +25,7 @@ defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir.'/externallib.php');
use \local_treestudyplan\studyplan;
use local_treestudyplan\studyplan;
/**
* Task class to synchronize enrolled users in courses with users associated with studyplans these courses are in
@ -38,6 +38,12 @@ class cascadeusersync {
private const METHOD = "manual";
/** @var studyplan */
private $studyplan;
/** @var object */
private $enrol;
/** @var int */
private $roleid;
/** @var array */
private $userids;
/**
* Create a synchronization task for a studyplan
@ -45,9 +51,13 @@ class cascadeusersync {
*/
public function __construct(studyplan $studyplan) {
$this->studyplan = $studyplan;
$this->enrol = \enrol_get_plugin(self::METHOD);
// Get the roleid to use for synchronizations.
$this->roleid = get_config("local_treestudyplan", "csync_roleid");
// And find the users that are linked to this studyplan.
$this->userids = $this->studyplan->get_linked_user_ids();
}
/**
* Enroll all users associated to the studyplan in the courses linked to this studyplan
*/
@ -63,23 +73,53 @@ class cascadeusersync {
outside of this script, it was determined to be the simplest and cleanest solution.
*/
$enrol = \enrol_get_plugin(self::METHOD);
// Find the courses that need to be synced to the associated cohorts.
$courseids = $this->studyplan->get_linked_course_ids();
// Find the study lines associated to this studyplan.
$lines = $this->studyplan->get_all_studylines();
// And find the users that are linked to this studyplan.
$userids = $this->studyplan->get_linked_user_ids();
// Get the roleid to use for synchronizations.
$roleid = get_config("local_treestudyplan", "csync_roleid");
foreach ($lines as $line) {
$this->syncline($line);
}
}
/**
* Enroll all cohorts associated to the studyplan in the courses linked to the specified study line
* @param studyline $line Studyline to sync enrolment for
*/
public function syncline(studyline $line) {
// Find the courses that need to be synced to the associated cohorts.
$courseids = $line->get_linked_course_ids();
if ($line->enrollable()) {
$lineuids = $line->get_enrolled_userids();
// Next, for each course that is linked:.
foreach ($courseids as $courseid) {
$this->perform_enrol($courseid, $lineuids);
// We do not do any autoremoval for user syncs, to avoid students losing access to the course data.
}
} else {
// Next, for each course that is linked:.
foreach ($courseids as $courseid) {
$this->perform_enrol($courseid, $this->userids);
// We do not do any autoremoval for user syncs, to avoid students losing access to the course data.
}
}
}
/**
* Enrol a list of users into a specific course
* @param int $courseid Id of the course
* @param array $userids List of userids to enrol
*/
private function perform_enrol($courseid, $userids) {
global $DB;
$course = \get_course($courseid);
if (count($userids) > 0) {
// Get the manual enrol instance for this course.
$instanceparams = ['courseid' => $courseid, 'enrol' => 'manual'];
if (!($instance = $DB->get_record('enrol', $instanceparams))) {
if ($instanceid = $enrol->add_default_instance($course)) {
$instance = $DB->get_record('enrol', array('id' => $instanceid));
if ($instanceid = $this->enrol->add_default_instance($course)) {
$instance = $DB->get_record('enrol', ['id' => $instanceid]);
} else {
// Instance not added for some reason, so report an error somewhere.
// (or not).
@ -89,13 +129,9 @@ class cascadeusersync {
if ($instance !== null) {
foreach ($userids as $uid) {
// Try a manual registration - it will just be updated if it is already there....
$enrol->enrol_user($instance, $uid, $roleid);
$this->enrol->enrol_user($instance, $uid, $this->roleid);
}
}
}
// We do not do any autoremoval for user syncs, to avoid students losing access to the course data.
}
}
}

View File

@ -25,8 +25,8 @@ defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir.'/externallib.php');
use \grade_item;
use \completion_criteria;
use grade_item;
use completion_criteria;
/**
* Scan course completion criteria for pending grading actions
@ -159,7 +159,7 @@ class completionscanner {
* Webservice model for basic info
* @param int[]|null $studentlist Array of student userids to use for checking. Leave empty to use all course students
*/
public function model(array $studentlist = null) : array {
public function model(?array $studentlist = null): array {
/*
If an array of students is provided (usually the case if called from a courecompletioninfo object),
make sure to filter it out, so only the students enrolled in the course are included.. Otherwise

View File

@ -21,7 +21,7 @@
*/
namespace local_treestudyplan;
use \context;
use context;
/**
* Class to abstract information about (category) context for web service export
@ -61,7 +61,6 @@ class contextinfo {
*/
public function model() {
return [
"name" => $this->context->get_context_name(false, false),
"shortname" => $this->context->get_context_name(false, true),
@ -72,7 +71,7 @@ class contextinfo {
/**
* Return context path names
* @param $short Use short names of contexts in path
* @param bool $short Use short names of contexts in path
* @return array of context path names
*/
public function path($short=false) {
@ -89,14 +88,13 @@ class contextinfo {
/**
* Return full context path name
* @param $short Use short names of contexts in path
* @param bool $short Use short names of contexts in path
* @return string Concatenated string of paths
*/
public function pathstr($short=false) {
return implode(" / ", $this->path($short));
}
/**
* Make new Contextinfo for context id
* @param int $contextid Context id

Some files were not shown because too many files have changed in this diff Show More