diff --git a/amd/src/debugger.js b/amd/src/debugger.js new file mode 100644 index 0000000..07a138f --- /dev/null +++ b/amd/src/debugger.js @@ -0,0 +1,52 @@ +/*eslint no-var: "error"*/ +/*eslint no-console: "off"*/ +/*eslint-env es6*/ +// Put this file in path/to/plugin/amd/src +// You can call it anything you like + +define([], function () { + + return (function (name) { + let handle = name; + let output_enabled = false; + + return { + write: function debugger_write() { + if (output_enabled) { + let args = Array.prototype.slice.call(arguments); + args.unshift(handle + ": "); + console.info.apply(console, args); + } + }, + info: function debugger_info() { + if (output_enabled) { + let args = Array.prototype.slice.call(arguments); + args.unshift(handle + ": "); + console.info.apply(console, args); + } + }, + warn: function debugger_warn() { + if (output_enabled) { + let args = Array.prototype.slice.call(arguments); + args.unshift(handle + ": "); + console.warn.apply(console, args); + } + }, + error: function debugger_error() { + if (output_enabled) { + let args = Array.prototype.slice.call(arguments); + args.unshift(handle + ": "); + console.error.apply(console, args); + } + }, + enable: function debugger_enable() { + output_enabled = true; + }, + + disable: function debugger_disable() { + output_enabled = false; + } + }; + + }); +}); \ No newline at end of file diff --git a/amd/src/handlers.js b/amd/src/handlers.js new file mode 100644 index 0000000..618afa5 --- /dev/null +++ b/amd/src/handlers.js @@ -0,0 +1,22 @@ +/*eslint no-var: "error"*/ +/*eslint no-console: "off"*/ +/*eslint-env es6*/ +// Put this file in path/to/plugin/amd/src +// You can call it anything you like + +define([], function () { + let debug_enabled = false; + + let self = { + fail_report_exception: function fail_report_exception(ex) { + if (ex.error != undefined) { + console.error("Error from webservice: ", ex.error, "\n", ex.debuginfo, "\n---Stack trace---\n", ex.stacktrace, "\n", ex); + } + else { + console.error("Exception from webservice: ", ex.message, "\n", ex.debuginfo, "\n---Stack trace---\n", ex.backtrace, "\n", ex); + } + + }, + }; + return self; +}); \ No newline at end of file diff --git a/amd/src/jquery.animate-shadow.js b/amd/src/jquery.animate-shadow.js new file mode 100644 index 0000000..4c8a6c1 --- /dev/null +++ b/amd/src/jquery.animate-shadow.js @@ -0,0 +1,235 @@ +/**! + * @preserve Shadow animation 1.11 + * http://www.bitstorm.org/jquery/shadow-animation/ + * Copyright 2011, 2013 Edwin Martin + * Contributors: Mark Carver, Xavier Lepretre and Jason Redding + * Released under the MIT and GPL licenses. + */ + +define(['jquery'], function (jQuery) { + + return jQuery(function($, undefined) { + /** + * Check whether the browser supports RGBA color mode. + * + * Author Mehdi Kabab + * @return {boolean} True if the browser support RGBA. False otherwise. + */ + function isRGBACapable() { + var $script = $('script:first'), + color = $script.css('color'), + result = false; + if (/^rgba/.test(color)) { + result = true; + } else { + try { + result = (color !== $script.css('color', 'rgba(0, 0, 0, 0.5)').css('color')); + $script.css('color', color); + } catch (e) { + } + } + $script.removeAttr('style'); + + return result; + } + + $.extend(true, $, { + support: { + 'rgba': isRGBACapable() + } + }); + + /*************************************/ + + // First define which property to use + var styles = $('html').prop('style'); + var boxShadowProperty; + $.each(['boxShadow', 'MozBoxShadow', 'WebkitBoxShadow'], function(i, property) { + var val = styles[property]; + if (typeof val !== 'undefined') { + boxShadowProperty = property; + return false; + } + }); + + // Extend the animate-function + if (boxShadowProperty) { + $['Tween']['propHooks']['boxShadow'] = { + get: function(tween) { + return $(tween.elem).css(boxShadowProperty); + }, + set: function(tween) { + var style = tween.elem.style; + var p_begin = parseShadows($(tween.elem)[0].style[boxShadowProperty] || $(tween.elem).css(boxShadowProperty)); + var p_end = parseShadows(tween.end); + var maxShadowCount = Math.max(p_begin.length, p_end.length); + var i; + for(i = 0; i < maxShadowCount; i++) { + p_end[i] = $.extend({}, p_begin[i], p_end[i]); + if (p_begin[i]) { + if (!('color' in p_begin[i]) || $.isArray(p_begin[i].color) === false) { + p_begin[i].color = p_end[i].color || [0, 0, 0, 0]; + } + } else { + p_begin[i] = parseShadows('0 0 0 0 rgba(0,0,0,0)')[0]; + } + } + tween['run'] = function(progress) { + var rs = calculateShadows(p_begin, p_end, progress); + style[boxShadowProperty] = rs; + }; + } + }; + } + + // Calculate an in-between shadow. + function calculateShadows(beginList, endList, pos) { + var shadows = []; + $.each(beginList, function(i) { + var parts = [], begin = beginList[i], end = endList[i]; + + if (begin.inset) { + parts.push('inset'); + } + if (typeof end.left !== 'undefined') { + parts.push(parseFloat(begin.left + pos * (end.left - begin.left)) + 'px ' + + parseFloat(begin.top + pos * (end.top - begin.top)) + 'px'); + } + if (typeof end.blur !== 'undefined') { + parts.push(parseFloat(begin.blur + pos * (end.blur - begin.blur)) + 'px'); + } + if (typeof end.spread !== 'undefined') { + parts.push(parseFloat(begin.spread + pos * (end.spread - begin.spread)) + 'px'); + } + if (typeof end.color !== 'undefined') { + var color = 'rgb' + ($.support['rgba'] ? 'a' : '') + '(' + + parseInt((begin.color[0] + pos * (end.color[0] - begin.color[0])), 10) + ',' + + parseInt((begin.color[1] + pos * (end.color[1] - begin.color[1])), 10) + ',' + + parseInt((begin.color[2] + pos * (end.color[2] - begin.color[2])), 10); + if ($.support['rgba']) { + color += ',' + parseFloat(begin.color[3] + pos * (end.color[3] - begin.color[3])); + } + color += ')'; + parts.push(color); + } + shadows.push(parts.join(' ')); + }); + return shadows.join(', '); + } + + // Parse the shadow value and extract the values. + function parseShadows(shadow) { + var parsedShadows = []; + var parsePosition = 0; + var parseLength = shadow.length; + + function findInset() { + var m = /^inset\b/.exec(shadow.substring(parsePosition)); + if (m !== null && m.length > 0) { + parsedShadow.inset = true; + parsePosition += m[0].length; + return true; + } + return false; + } + function findOffsets() { + var m = /^(-?[0-9\.]+)(?:px)?\s+(-?[0-9\.]+)(?:px)?(?:\s+(-?[0-9\.]+)(?:px)?)?(?:\s+(-?[0-9\.]+)(?:px)?)?/.exec(shadow.substring(parsePosition)); + if (m !== null && m.length > 0) { + parsedShadow.left = parseInt(m[1], 10); + parsedShadow.top = parseInt(m[2], 10); + parsedShadow.blur = (m[3] ? parseInt(m[3], 10) : 0); + parsedShadow.spread = (m[4] ? parseInt(m[4], 10) : 0); + parsePosition += m[0].length; + return true; + } + return false; + } + function findColor() { + var m = /^#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})/.exec(shadow.substring(parsePosition)); + if (m !== null && m.length > 0) { + parsedShadow.color = [parseInt(m[1], 16), parseInt(m[2], 16), parseInt(m[3], 16), 1]; + parsePosition += m[0].length; + return true; + } + m = /^#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])/.exec(shadow.substring(parsePosition)); + if (m !== null && m.length > 0) { + parsedShadow.color = [parseInt(m[1], 16) * 17, parseInt(m[2], 16) * 17, parseInt(m[3], 16) * 17, 1]; + parsePosition += m[0].length; + return true; + } + m = /^rgb\(\s*([0-9\.]+)\s*,\s*([0-9\.]+)\s*,\s*([0-9\.]+)\s*\)/.exec(shadow.substring(parsePosition)); + if (m !== null && m.length > 0) { + parsedShadow.color = [parseInt(m[1], 10), parseInt(m[2], 10), parseInt(m[3], 10), 1]; + parsePosition += m[0].length; + return true; + } + m = /^rgba\(\s*([0-9\.]+)\s*,\s*([0-9\.]+)\s*,\s*([0-9\.]+)\s*,\s*([0-9\.]+)\s*\)/.exec(shadow.substring(parsePosition)); + if (m !== null && m.length > 0) { + parsedShadow.color = [parseInt(m[1], 10), parseInt(m[2], 10), parseInt(m[3], 10), parseFloat(m[4])]; + parsePosition += m[0].length; + return true; + } + return false; + } + function findWhiteSpace() { + var m = /^\s+/.exec(shadow.substring(parsePosition)); + if (m !== null && m.length > 0) { + parsePosition += m[0].length; + return true; + } + return false; + } + function findComma() { + var m = /^\s*,\s*/.exec(shadow.substring(parsePosition)); + if (m !== null && m.length > 0) { + parsePosition += m[0].length; + return true; + } + return false; + } + function normalizeShadow(shadow) { + if ($.isPlainObject(shadow)) { + var i, sColor, cLength = 0, color = []; + if ($.isArray(shadow.color)) { + sColor = shadow.color; + cLength = sColor.length; + } + for(i = 0; i < 4; i++) { + if (i < cLength) { + color.push(sColor[i]); + } else if (i === 3) { + color.push(1); + } else { + color.push(0); + } + } + } + return $.extend({ + 'left': 0, + 'top': 0, + 'blur': 0, + 'spread': 0 + }, shadow); + } + var parsedShadow = normalizeShadow(); + + while (parsePosition < parseLength) { + if (findInset()) { + findWhiteSpace(); + } else if (findOffsets()) { + findWhiteSpace(); + } else if (findColor()) { + findWhiteSpace(); + } else if (findComma()) { + parsedShadows.push(normalizeShadow(parsedShadow)); + parsedShadow = {}; + } else { + break; + } + } + parsedShadows.push(normalizeShadow(parsedShadow)); + return parsedShadows; + } + }); + +}); \ No newline at end of file diff --git a/amd/src/jscolor.js b/amd/src/jscolor.js index cb9ec1f..73d4f90 100644 --- a/amd/src/jscolor.js +++ b/amd/src/jscolor.js @@ -18,7 +18,7 @@ define([], function () { //if (!window.jscolor) { window.jscolor = (function () { return (function () { - console.info("Initializing JSColor"); + //console.info("Initializing JSColor"); var jsc = { diff --git a/amd/src/leveleditor.js b/amd/src/leveleditor.js index 0a72357..70d963d 100644 --- a/amd/src/leveleditor.js +++ b/amd/src/leveleditor.js @@ -1,7 +1,14 @@ +/*eslint no-var: "error"*/ +/*eslint-env es6*/ // Put this file in path/to/plugin/amd/src // You can call it anything you like -define(['jquery', 'core/str', 'core/ajax', 'block_gradelevel/jscolor'], function ($, str, ajax, jscolor) { +define(['jquery', 'block_gradelevel/jquery.animate-shadow', 'core/str', 'core/ajax', 'block_gradelevel/jscolor', + 'block_gradelevel/handlers', 'block_gradelevel/debugger'], function ($, $shadow, str, ajax, jscolor, handlers, Debugger) { + + let debug = new Debugger("leveleditor"); + debug.enable(); + let skill_id = null; let $skill_table = null; @@ -13,15 +20,12 @@ define(['jquery', 'core/str', 'core/ajax', 'block_gradelevel/jscolor'], function skill_id = Number($skill_table.attr('data-skill')); if (!skill_id && skill_id !== 0 && skill_id !== '0') { - console.error("Cannot find configured skill id"); + debug.error("Cannot find configured skill id"); return; } - // load and fill the level table - self.refresh(); - // make sure all uint type numbers are not able to go below 0 - $(document).on('change', "input[type=number].uint", function (evt) { + $(document).on('input', "input[type=number].uint", function (evt) { let $this = $(this); if ($this.val() < 0) { $this.val(0); @@ -35,6 +39,8 @@ define(['jquery', 'core/str', 'core/ajax', 'block_gradelevel/jscolor'], function // On click handler for save changes button $("button[data-action=savechanges]").on('click', self.submit); + // On click handler for save changes button + $("button[data-action=resetlevels]").on('click', self.resetlevels); }, @@ -53,7 +59,7 @@ define(['jquery', 'core/str', 'core/ajax', 'block_gradelevel/jscolor'], function $skill_table.find("tbody").append($newtr); }, refresh: function refresh() { - console.info("Attempting to refresh"); + debug.info("Attempting to refresh"); // perform refresh call let promises = ajax.call([{ @@ -82,7 +88,7 @@ define(['jquery', 'core/str', 'core/ajax', 'block_gradelevel/jscolor'], function level_data.push(row); }); - //console.info("Attempting to submit", level_data); + //debug.info("Attempting to submit", level_data); // perform ajax call let promises = ajax.call([{ @@ -91,27 +97,54 @@ define(['jquery', 'core/str', 'core/ajax', 'block_gradelevel/jscolor'], function }]); // and link promise returns to callbacks - promises[0].done(self.success_refill_table).fail(self.fail_report_exception); + promises[0].done(self.success_refill_table).fail(handlers.fail_report_exception); + }, + resetlevels: function resetlevels(evt) { + let $table = $("table#level_config"); + + // Send a request to delete all rows + let level_data = []; + $table.find("tbody tr[data-rowid]").each(function () { + let $this = $(this); + let row = { + id: Number($this.attr('data-rowid')), + points: -255, // means: delete row + badgecolor: "", // totally irrelevent when requesting a delete + } + + level_data.push(row); + }); + + //debug.info("Attempting to submit", level_data); + + // perform ajax call + let promises = ajax.call([{ + methodname: 'block_gradelevel_submit_levels', + args: { skill_id: skill_id, levels: level_data }, + }]); + + // and link promise returns to callbacks + promises[0].done(self.success_refill_table).fail(handlers.fail_report_exception); }, success_refill_table: function success_refill_table(response) { - //console.info("Response from webservice: ", response); + //debug.info("Response from webservice: ", response); let $tbody = $skill_table.find('tbody'); $tbody.empty(); for (let idx in response) { let lvl = response[idx]; - //console.info("Level:", lvl); + //debug.info("Level:", lvl); self.add_levelrow(lvl.id, lvl.points, lvl.badgecolor); } + let $inputs = $tbody.find("input[data-name='points']"); + + let orig_border = $inputs.css('border-left-color'); + $inputs + .animate({ boxShadow: '0px 0px 5px 3px #3FDFCF'}, 400) + .delay(200) + .animate({ boxShadow: 'none'}, 800); + }, - fail_report_exception: function fail_report_exception(ex) { - if (ex.error != undefined) { - console.error("Error from webservice: ", ex.error, "\n" , ex.debuginfo, "\n---Stack trace---\n", ex.stacktrace, "\n", ex); - } - else { - console.error("Exception from webservice: ", ex.message, "\n", ex.debuginfo, "\n---Stack trace---\n", ex.backtrace, "\n", ex); - } - - }, + }; return self; diff --git a/amd/src/renderbadge.js b/amd/src/renderbadge.js index 74e511c..d8f3ae4 100644 --- a/amd/src/renderbadge.js +++ b/amd/src/renderbadge.js @@ -1,23 +1,39 @@ +/*eslint no-var: "error"*/ +/*eslint-env es6*/ // Put this file in path/to/plugin/amd/src // You can call it anything you like -define(['jquery', 'core/str', 'core/ajax'], function ($, str, ajax) { +define(['jquery', 'core/str', 'core/ajax','block_gradelevel/debugger' ], function ($, str, ajax, Debugger) { + let debug = Debugger("renderbadge"); + //debug.enable(); + let self = { init: function init() { - + // Put whatever you like here. $ is available // to you as normal. $("figure.levelbadge").each(function () { - let props = self.fetchProperties(this); - let $this = $(this); - let $canvas = $(""); - //console.info("$canvas", $canvas, $canvas[0]); - $('canvas', this).remove();// clear out any existing canvas - $this.append($canvas); // Put the canvas in there - self.render($canvas[0], props); + self.setup_badge(this,true); }); }, + setup_badge: function setup_Badge(figure, allowskip) { + let $figure = $(figure); + let props = $(figure).data('badge-props'); + debug.info("Setting up skill badge on ", figure, props); + if (props && allowskip) { + debug.info(" skill badge was already configured. Skipping process..."); + } + else { + props = self.fetchProperties(figure); + let $canvas = $(""); + $('canvas', figure).remove();// clear out any existing canvas + $figure.append($canvas); // Put the new canvas in there + self.render($canvas[0], props); + // Store properties + $figure.data("badge-props", props); + } + }, render: function render(canvas, props) { let ctx = canvas.getContext("2d"); // Color configuration @@ -61,9 +77,9 @@ define(['jquery', 'core/str', 'core/ajax'], function ($, str, ajax) { scale: 0.7, // scale to this fraction of image size } } - //console.info("Config", config); - //console.info("Colors", colors); - //console.info("Props", props); + debug.info(" Config", config); + debug.info(" Colors", colors); + debug.info(" Props", props); // draw main circle let baseGradient = ctx.createRadialGradient( @@ -72,7 +88,7 @@ define(['jquery', 'core/str', 'core/ajax'], function ($, str, ajax) { (config.size ) * colors.radialGradient.r0, config.size * colors.radialGradient.x1, config.size * colors.radialGradient.y1, - (config.size ) * colors.radialGradient.r1, + (config.size ) * colors.radialGradient.r1 ); baseGradient.addColorStop(0, colors.lightPoint); baseGradient.addColorStop(1, colors.base); @@ -157,9 +173,9 @@ define(['jquery', 'core/str', 'core/ajax'], function ($, str, ajax) { imPos.x = (config.size * config.icon.x) - (imPos.w / 2); imPos.y = (config.size * config.icon.y) - (imPos.h / 2); - ctx.drawImage(this, 15, 15, 120, 120);//imPos.x, imPos.y, imPos.w, imPos.h); + ctx.drawImage(this, imPos.x, imPos.y, imPos.w, imPos.h); } - //console.info("Image: ",props.image); + //debug.info("Image: ",props.image); iconImg.src = props.image; } diff --git a/amd/src/skilleditor.js b/amd/src/skilleditor.js new file mode 100644 index 0000000..ca9128b --- /dev/null +++ b/amd/src/skilleditor.js @@ -0,0 +1,196 @@ +/*eslint no-var: "error"*/ +/*eslint-env es6*/ +// Put this file in path/to/plugin/amd/src +// You can call it anything you like + +define(['jquery', 'core/str', 'core/ajax', 'core/config', 'block_gradelevel/renderbadge', 'block_gradelevel/handlers', + 'block_gradelevel/debugger', 'core/modal_factory','core/modal_events'], function ($, str, ajax, mdlcfg, renderbadge, handlers, Debugger, ModalFactory, ModalEvents) { + + let debug = Debugger("skilleditor"); + debug.enable(); + + let $skillset_div = null; + const uploadFilter = ".png,.svg"; + + let self = { + init: function init() { + // Find skill table and skill id + $skillset_div = $("div#skill_set"); + // Run initialization code on all items. + debug.info("Initializing editors"); + $skillset_div.find("li.skill_info").each(function () { + self.initialize_item(this); + }); + + debug.info("Attaching function to addskill link"); + $skillset_div.find("a[data-action='addskill']").on('click', function (ev) { + let promises = ajax.call([{ + methodname: 'block_gradelevel_add_skill', + args: { name: null, icon: null }, + }]); + + // and link promise returns to callbacks + promises[0].fail(handlers.fail_report_exception).done(function (data) { + let $newskill = $(data.html); + $skillset_div.find('ul').append($newskill); + $newskill.each(function () { + self.initialize_item(this); + }); + $newskill.find('figure.levelbadge').each(function () { + renderbadge.setup_badge(this); + }); + + + }); + }); + + }, + initialize_item: function initialize_item(li) { + let $li = $(li); + let skill_id = $li.data('id'); + + // Attach edit name pencil + $li.find("a[data-action='editname']").on('click', function (evt) { + let $span = $li.find("figcaption span[data-type='label']"); + if ($span.length > 0) { + let name = $span.text(); + let $input = $("").val(name);// value='" + name + "' />"); + + $span.replaceWith($input); + $input.on("keyup", function input_keypress(e) { + let keycode = (e.keyCode ? e.keyCode : e.which); + if (keycode == '13') { + debug.info("Saving name"); + // save the new name + let new_name = $input.val(); + + let promises = ajax.call([{ + methodname: 'block_gradelevel_update_skill', + args: { id: skill_id, name: new_name, icon: null }, + }]); + + // and link promise returns to callbacks + promises[0].fail(handlers.fail_report_exception).done(function (data) { + $input.replaceWith($("" + data.name + "")); + }); + + } + if (keycode == '27') { + debug.info("Cancelled editing"); + $input.replaceWith($("" + name + "")); + } + }).on("focusout", function input_cancel(e) { + debug.info("Cancelled editing"); + $input.replaceWith($("" + name + "")); + }); + $input.focus(); + + } + else { + let $textinput = $li.find("figurecaption input[type=text]"); + + } + }); + + // Attach edit icon pencil + $li.find("a[data-action='editicon']").on('click', function (ev) { + let $uploader = $(""); + + $uploader.on('change', function (ev) { + ev.stopPropagation(); + ev.preventDefault(); + let file = ev.originalEvent.target.files[0]; + let fr = new FileReader(); + $(fr).on('load', function (ev) { + let dataURI = ev.target.result; + + let promises = ajax.call([{ + methodname: 'block_gradelevel_update_skill', + args: { id: skill_id, name: null, icon: dataURI }, + }]); + + // and link promise returns to callbacks + promises[0].fail(handlers.fail_report_exception).done(function (data) { + + let $figure = $li.find("figure.levelbadge"); + + $figure.empty(); + $figure.append($("")); + + $figure.each(function () { + renderbadge.setup_badge(this); + }); + }); + + + $uploader.remove(); // delete the uploader after it's done + }); + fr.readAsDataURL(file); + + }); + $li.append($uploader); + $uploader.trigger('click'); + + }); + + // Attach edit levels link + $li.find("a[data-action='editlevels']").attr('href', mdlcfg.wwwroot + "/blocks/gradelevel/cfg_skilllevels.php?skill_id=" + skill_id); + + // Attach delete function to any existing functional delete link + $li.find("a[data-action='deleteskill']").each(function () { + let $trigger = $(this); + // Attach delete button code + ModalFactory.create({ + type: ModalFactory.types.SAVE_CANCEL, + title: str.get_string('title_confirmdelete', 'block_gradelevel'), + body: str.get_string('dialog_confirmdeleteskill', 'block_gradelevel'), + }, $trigger).done(function (modal) { + // Do what you want with your new modal. + debug.info("Modal done", modal); + modal.setSaveButtonText(str.get_string('delete', 'core')); + modal.getRoot().on(ModalEvents.save, function (e) { + debug.info("Delete Confirmed"); + //e.preventDefault(); if you don't want to close the window + + // call the delete function + let promises = ajax.call([{ + methodname: 'block_gradelevel_delete_skill', + args: { id: skill_id}, + }]); + + // and link promise returns to callbacks + promises[0].fail(handlers.fail_report_exception).done(function (data) { + + if (data.deleted) { + $li.remove(); + } + + }); + }); + }); + }); + + }, + success_refill_table: function success_refill_table(response) { + //console.info("Response from webservice: ", response); + let $tbody = $skill_table.find('tbody'); + $tbody.empty(); + for (let idx in response) { + let lvl = response[idx]; + //console.info("Level:", lvl); + self.add_levelrow(lvl.id, lvl.points, lvl.badgecolor); + } + }, + fail_report_exception: function fail_report_exception(ex) { + if (ex.error != undefined) { + console.error("Error from webservice: ", ex.error, "\n" , ex.debuginfo, "\n---Stack trace---\n", ex.stacktrace, "\n", ex); + } + else { + console.error("Exception from webservice: ", ex.message, "\n", ex.debuginfo, "\n---Stack trace---\n", ex.backtrace, "\n", ex); + } + + }, + + }; + return self; +}); \ No newline at end of file diff --git a/block_gradelevel.php b/block_gradelevel.php index 6cfd92c..6f60cfb 100644 --- a/block_gradelevel.php +++ b/block_gradelevel.php @@ -24,13 +24,6 @@ class block_gradelevel extends block_base { // find or create the levelset for this course $this->levelset = block_gradelevel_levelset::find_by_course($COURSE->id); - if(empty($this->levelset)) - { - // create a new levelset with the name of this course and attach - $this->levelset = block_gradelevel_levelset::create_new($COURSE->shortname); - $this->levelset->attach_course($COURSE->id); - } - } // The PHP tag and the curly bracket for the class definition @@ -43,6 +36,7 @@ class block_gradelevel extends block_base { } public function get_content() { + global $CFG; global $USER; global $COURSE; @@ -52,28 +46,34 @@ class block_gradelevel extends block_base { $this->content = new stdClass; - // below can be a single call to $this->levelset->get_user_leveldata() once the need for debugging (fixed point total) is gone - $pointstotal = $this->levelset->get_levelset_grade($USER->id); - $level_info = $this->levelset->calculate_level($pointstotal); + if(empty($this->levelset)) + { + $this->content->text = "
"; + $this->content->footer = get_string("unattached_course",'block_gradelevel'); + } + else { + // below can be a single call to $this->levelset->get_user_leveldata() once the need for debugging (fixed point total) is gone + $pointstotal = $this->levelset->get_levelset_grade($USER->id); + $level_info = $this->levelset->calculate_level($pointstotal); - $this->content->text = $this->levelset->render_badge($pointstotal); + $this->content->text = $this->levelset->render_badge($pointstotal); - if($level_info->levelup_total > 0) - { - $this->content->footer = "
".get_string('levelup_at','block_gradelevel')." {$level_info->points_in_level}/{$level_info->levelup_total}
"; - } - else - { - $this->content->footer = "
".get_string('levelup_done','block_gradelevel')."
"; - } + if($level_info->levelup_total > 0) + { + $this->content->footer = "
".get_string('levelup_at','block_gradelevel')." {$level_info->points_in_level}/{$level_info->levelup_total}
"; + } + else + { + $this->content->footer = "
".get_string('levelup_done','block_gradelevel')."
"; + } - $coursecontext = context_course::instance($COURSE->id); + $coursecontext = context_course::instance($COURSE->id); - if(has_capability('block/gradelevel:viewresults', $coursecontext)) - { - $this->content->footer .= "\n"; - } - + if(has_capability('block/gradelevel:viewresults', $coursecontext)) + { + $this->content->footer .= "\n"; + } + } return $this->content; } diff --git a/globallevels.php b/cfg_globallevels.php similarity index 100% rename from globallevels.php rename to cfg_globallevels.php diff --git a/cfg_skilllevels.php b/cfg_skilllevels.php new file mode 100644 index 0000000..7404ad7 --- /dev/null +++ b/cfg_skilllevels.php @@ -0,0 +1,48 @@ +libdir.'/adminlib.php'); + + +admin_externalpage_setup("block_gradelevel_config_skills"); + + +$skill_id = required_param('skill_id', PARAM_INT); + +$systemcontext = context_system::instance(); +// Check if user has capability to manage skills +require_capability('block/gradelevel:skillmanager', $systemcontext); + +$skill = block_gradelevel_levelset::find_by_id($skill_id); + +$PAGE->requires->js_call_amd('block_gradelevel/renderbadge', 'init'); +$PAGE->requires->js_call_amd('block_gradelevel/skilleditor', 'init'); +$PAGE->requires->js_call_amd('block_gradelevel/leveleditor', 'init'); + +print $OUTPUT->header(); + +// render skill editor +print $OUTPUT->heading(get_string('cfgpage_editskill','block_gradelevel')." ".$skill->getName()); +print block_gradelevel_skillmgmtservice::render_skill_editor($skill_id); + +// render level editor +print $OUTPUT->heading(get_string('cfgpage_skilllevels','block_gradelevel')); +print block_gradelevel_skillmgmtservice::render_leveltable($skill_id); + +// add back button to return to skill management page +$cfg_skills_url = $CFG->wwwroot."/blocks/gradelevel/cfg_skills.php"; +print ""; +print $OUTPUT->footer(); diff --git a/cfg_skills.php b/cfg_skills.php new file mode 100644 index 0000000..3d9139b --- /dev/null +++ b/cfg_skills.php @@ -0,0 +1,35 @@ +libdir.'/adminlib.php'); + + +admin_externalpage_setup("block_gradelevel_config_skills"); + +$systemcontext = context_system::instance(); +// Check if user has capability to manage skills +require_capability('block/gradelevel:skillmanager', $systemcontext); + + +$PAGE->requires->js_call_amd('block_gradelevel/skilleditor', 'init'); +$PAGE->requires->js_call_amd('block_gradelevel/renderbadge', 'init'); + +print $OUTPUT->header(); +print $OUTPUT->heading(get_string('cfgpage_skills', 'block_gradelevel')); + +// render page for skill level 0 (global) +print block_gradelevel_skillmgmtservice::render_skill_list(0); +print $OUTPUT->footer(); diff --git a/classes/levelset.php b/classes/levelset.php index b1eae4f..6721a8e 100644 --- a/classes/levelset.php +++ b/classes/levelset.php @@ -141,9 +141,11 @@ class block_gradelevel_levelset { return null; // return null if no current levelset linked } - static public function find_by_id($id) + static public function find_by_id(int $id) { - $levelset = $DB->get_record('block_gradelevel_levelset', array('id' => array_values($records)[0]->levelset_id)); + global $DB; + + $levelset = $DB->get_record('block_gradelevel_levelset', array('id' => $id)); if($levelset) { return new static($levelset->id,$levelset); @@ -397,7 +399,6 @@ class block_gradelevel_levelset { // If we have levels defined, use those, otherwise use the global levels if(!empty($this->levels)) { - print ("EXSTING_LEVELS"); if(array_values($this->levels)[0]->points > 0) { // insert level 0 @@ -411,16 +412,13 @@ class block_gradelevel_levelset { if(!empty($lvl->badgecolor)) { $color = $lvl->badgecolor; } - elseif(isset($this->global_levels[$i])) - { - $color = $this->global_levels[$i]->badgecolor; - } else { $color = static::UNDEFINED_BADGE_COLOR; } - $level_info[$lvl->points]; + $level_info[$lvl->points] = $color; + $i++; } } @@ -458,7 +456,7 @@ class block_gradelevel_levelset { $info = $this->calculate_level($points); $image = $this->getIcon(); - if(strncmp($image,"data: ",6) != 0) + if(strncmp($image,"data:",5) != 0) { $image_url = $CFG->wwwroot.$image; } @@ -487,7 +485,7 @@ class block_gradelevel_levelset { $levels = $this->badgelevels(); $maxpoints = array_pop(array_keys($levels)); - return $this->render_badge($maxpoints,$size); + return $this->render_badge($maxpoints, $size); } diff --git a/classes/skillmgmtservice.php b/classes/skillmgmtservice.php index 882b3c6..abcd9ee 100644 --- a/classes/skillmgmtservice.php +++ b/classes/skillmgmtservice.php @@ -8,8 +8,8 @@ require_once($CFG->dirroot.'/grade/querylib.php'); class block_gradelevel_skillmgmtservice extends external_api { - const DEBUG = false; // enable debug logging - const DEMOBADGE_SIZE = 100; // size of demo badge + const DEBUG = true; // enable debug logging + const DEMOBADGE_SIZE = 150; // size of demo badge private static function log($message) { @@ -199,28 +199,6 @@ class block_gradelevel_skillmgmtservice extends external_api $systemcontext = context_system::instance(); self::validate_context($systemcontext); - if(skill_id == 0) - { - // Check if user has capability to manage global levels - require_capability('block/gradelevel:managelevels', $systemcontext); - } - else - { - // Check if user has teacher or manager capability in any of the attached courses - $capability = false; - $courses = self::list_courses($skill_id); - foreach($courses as $course_id) - { - $coursecontext = context_course::instance($course_id); - $capability |= has_capability('block/gradelevel:changelevels', $coursecontext); - } - - if(!$capability) - { - throw new Exception("Access denied - you do not have editing capabilities for one of the attached courses"); - } - } - foreach($levels as $lvl_raw) { @@ -273,13 +251,35 @@ class block_gradelevel_skillmgmtservice extends external_api $levels = $DB->get_records('block_gradelevel_levels', array('levelset_id' => $skill_id)); - // Sort by points - usort( $levels, function( $a, $b) { - return ( $a->points < $b->points ) ? -1 : 1; - } ); + if($skill_id == 0 || count($levels) > 0) + { + // If global level, or skills are defined, return those + + // Sort by points + usort( $levels, function( $a, $b) { + return ( $a->points < $b->points ) ? -1 : 1; + } ); - return $levels; + return $levels; + } + else + { + // Else, return a nameless clone of the default levels). + $levels = $DB->get_records('block_gradelevel_levels', array('levelset_id' => 0)); + + // Sort by points + usort( $levels, function( $a, $b) { + return ( $a->points < $b->points ) ? -1 : 1; + } ); + + foreach($levels as $lvl) + { + $lvl->id = -255; // replace level id with -255, which is code for "new level" when returned + } + + return $levels; + } } @@ -316,35 +316,51 @@ class block_gradelevel_skillmgmtservice extends external_api } - public static function update_skill(int $id, string $name, string $icon ) + public static function update_skill(int $id, $name, $icon ) { global $CFG, $DB; $skill = $DB->get_record('block_gradelevel_levelset', array('id' => $id)); - $skill->name = $name; - $skill->icon = $icon; + if($name != null){ + $skill->name = $name; + } + + if($icon != null){ + $skill->icon = $icon; + } $DB->update_record('block_gradelevel_levelset',$skill); + $skill->html = block_gradelevel_levelset::find_by_id($skill->id)->render_demo_badge(static::DEMOBADGE_SIZE); return $skill; } - public static function add_skill(string $name, string $icon) + public static function add_skill($name, $icon) { global $CFG, $DB; - $skill = stdObj; + $skill = new stdClass; - $skill->name = $name; - $skill->icon = $icon; + if(empty($name)){ + $skill->name = get_string('defaults_name','block_gradelevel'); + } + else { + $skill->name = $name; + } + if(empty($icon)){ + $skill->icon = "/blocks/gradelevel/pix/undefinedskill.svg"; + } + else { + $skill->icon = $icon; + } $id = $DB->insert_record('block_gradelevel_levelset',$skill, true); $skill->id = $id; - $skill->html = block_gradelevel_levelset::find_by_id($skill->id)->render_demo_badge(static::DEMOBADGE_SIZE); + $skill->html = static::single_skill_editor_item(block_gradelevel_levelset::find_by_id($skill->id)); return $skill; } @@ -353,10 +369,14 @@ class block_gradelevel_skillmgmtservice extends external_api { global $CFG, $DB; - $result = stdObj; - $result->id = $id; + $skill = block_gradelevel_levelset::find_by_id($id); + if(count($skill->list_courses()) > 0) + { + throw new Exception("Cannot delete skills that have courses attached"); + } - $result->id = ($DB->delete_record('block_gradelevel_levelset',array('id' => $id)))?true:false; + $result = array('id' => $id); + $result['deleted'] = ($DB->delete_records('block_gradelevel_levelset',array('id' => $id)))?true:false; return $result; } @@ -368,7 +388,7 @@ class block_gradelevel_skillmgmtservice extends external_api $levels = static::list_levels($skill_id); $s = "

".get_string('levelcfg_description','block_gradelevel')."

"; - $s .= ""; + $s .= "
"; $s .= ""; $s .= ""; foreach($levels as $lvl) @@ -380,7 +400,12 @@ class block_gradelevel_skillmgmtservice extends external_api $s .= ""; $s .= ""; $s .= "
".get_string('levelcfg_head_points','block_gradelevel')."".get_string('levelcfg_head_color','block_gradelevel')."
".get_string("levelcfg_addlevel",'block_gradelevel')."
"; - $s .= "

"; + $s .= "

"; + if($skill_id > 0) + { + $s .= " "; + } + $s .= "

"; return $s; @@ -388,19 +413,61 @@ class block_gradelevel_skillmgmtservice extends external_api public static function render_skill_list() { + global $CFG; $skills = block_gradelevel_levelset::list_all(); - $s = "
    "; + // Sort by points + usort( $skills, function( $a, $b) { + return ( $a->getName() < $b->getName()) ? -1 : 1; + } ); + + $s = "
      "; foreach($skills as $skill) { - $skill_id = $skill->getId(); - $s .= "
    • ".$skill->render_demo_badge(DEMOBADGE_SIZE); - $s .= "
      ".$skill->getName()."
      "; - $s .= "
    • "; + $s .= static::single_skill_editor_item($skill); } + $s .= "
    "; + $s .= "".get_string('cfg_addskill','block_gradelevel').""; + $s .= "
    "; + + return $s; + } + + public static function render_skill_editor(int $skill_id) + { + $skill = block_gradelevel_levelset::find_by_id($skill_id); + + $s = "
      "; + $s .= static::single_skill_editor_item($skill,true); $s .= "
    "; + return $s; + } + + private static function single_skill_editor_item($skill,$single=false) + { + global $OUTPUT; + + $skill_id = $skill->getId(); + $s = "
  • "; + if(!$single){ + $s .= ""; + } + $s .= $skill->render_demo_badge(static::DEMOBADGE_SIZE).""; + + if(!$single){ + $s .= ""; + if(count($skill->list_courses()) ){ + $s .= ""; + } + } + + $s .= ""; + $s .= "
    ".$skill->getName().""; + $s .= ""; + $s .= "
  • "; + return $s; } } \ No newline at end of file diff --git a/edit_form.php b/edit_form.php index ab7013e..7d9c31d 100644 --- a/edit_form.php +++ b/edit_form.php @@ -13,35 +13,35 @@ class block_gradelevel_edit_form extends block_edit_form { // Section header title according to language file. $mform->addElement('header', 'config_header', get_string('blocksettings', 'block')); - // A sample string variable with a default value. - $mform->addElement('text', 'levelset_name', get_string('levelset_name', 'block_gradelevel')); - - $icon = $levelset->getIcon(); - if(strncmp($icon,"data: ",6) != 0) + $skills = block_gradelevel_levelset::list_all(); + // sort by name + usort( $skills, function( $a, $b) { + return ( $a->getName() < $b->getName()) ? -1 : 1; + } ); + + $options = array( '-1' => get_string('edit_noneskill', 'block_gradelevel')); + foreach($skills as $skill) { - $icon = $CFG->wwwroot.$icon; + $options[$skill->getId()] = $skill->getName(); } - - $icon_title = get_string('levelset_icon_cur', 'block_gradelevel'); - $mform->addElement('html',"
    {$icon_title}
    "); // A sample string variable with a default value. - $mform->addElement('filepicker', 'levelset_icon', get_string('levelset_icon_new', 'block_gradelevel'), null, - array('maxbytes' => $maxbytes, 'accepted_types' => array('.png', '.svg'))); - - $mform->addElement('header', 'config_header', get_string('levelset_levels', 'block_gradelevel')); - $mform->addElement('html',print block_gradelevel_managelevelservice::render_leveltable(0)); + $mform->addElement('select', 'attached_skill', get_string('edit_pickskill', 'block_gradelevel'), $options); + } public function set_data($defaults) { + global $COURSE; + + $defaults->attached_skill = -1; // retrieve levelset - $levelset = $this->block->levelset; + $skill = block_gradelevel_levelset::find_by_course($COURSE->id); + if(isset($skill)){ + $defaults->attached_skill = $skill->getId(); + } - $defaults->levelset_name = $levelset->getName(); - - $defaults->levelset_icon = $levelset->getIcon(); parent::set_data($defaults); } @@ -61,50 +61,26 @@ class block_gradelevel_edit_form extends block_edit_form { protected function save_data($data) { + global $COURSE; // retrieve levelset - $levelset = $this->block->levelset; - $this->debug("data: ".print_r($data,true)); - - - if(isset($data->levelset_name)){ - $levelset->setName($data->levelset_name); - } - - if(isset($data->levelset_icon)) + if(isset($data->attached_skill)) { - $filename = $this->get_new_filename('levelset_icon'); - $contents = $this->get_file_content('levelset_icon'); - - $imageData = base64_encode($contents); - - $pi = pathinfo($filename); - if($pi['extension'] == 'svg') + $currentskill = block_gradelevel_levelset::find_by_course($COURSE->id); + if(isset($currentskill)) { - $mime = "image/svg+xml"; - } - else if($pi['extension'] == 'png') - { - $mime = "image/png"; - } - else - { - $mime = "binary/octet-stream"; // no idea what it is + $this->debug("deetaching..."); + $currentskill->detach_course($COURSE->id); } - - $dataSrc = 'data: '.$mime.';base64,'.$imageData; - - $levelset->setIcon($dataSrc); + $skill = block_gradelevel_levelset::find_by_id($data->attached_skill); + if(isset($skill)){ + $this->debug("attaching..."); + $skill->attach_course($COURSE->id); + } } - - $this->debug("levelset: ".print_r($levelset,true)); - - $levelset->save_data(); } - - private function debug($msg) { error_log($msg, 3, '/tmp/moodledevlog.log'); diff --git a/lang/en/block_gradelevel.php b/lang/en/block_gradelevel.php index 8fcb711..903ab8a 100644 --- a/lang/en/block_gradelevel.php +++ b/lang/en/block_gradelevel.php @@ -7,16 +7,18 @@ $string['gradelevel:myaddinstance'] = 'Add a new Grade Level block to the My Moo $string['blockstring'] = 'Text in the block'; $string['levelup_at'] = 'Next level: '; $string['levelup_done'] = 'Complete'; +$string['unattached_course'] = 'Please attach this course to a skill in the block settings'; $string['teacher_view_results'] = 'View student results'; -$string['levelset_name'] = "Skill name"; -$string['levelset_icon_cur'] = "Current skill icon (for level badge)"; -$string['levelset_icon_new'] = "New skill icon (for level badge)"; +$string['edit_pickskill'] = "Attach to skill"; +$string['edit_noneskill'] = "None"; $string['cfgpage_generic'] = "Grade Level settings"; $string['cfgpage_globallevels'] = "Default levels"; - +$string['cfgpage_skilllevels'] = "Edit levels"; +$string['cfgpage_skills'] = "Manage skills"; +$string['cfgpage_editskill'] = "Edit skill"; $string['descconfig'] = "Configuration for Grade Level block"; @@ -34,3 +36,16 @@ $string['levelcfg_description'] = "Levels will be sorted based on points. Make s $string['levelcfg_head_points'] = "Points"; $string['levelcfg_head_color'] = "Medal color"; $string['levelcfg_addlevel'] = "Add level"; + +$string['tooltip_editicon'] = "Edit icon"; +$string['tooltip_editname'] = "Edit name"; +$string['tooltip_deleteskill'] = "Delete skill"; +$string['tooltip_deleteskill_disabled'] = "Cannot delete skill, because there are attached courses"; + +$string['defaults_name'] = "New skill"; +$string['cfg_resetlevels'] = "Reset levels to default"; +$string['cfg_addskill'] = "Add skill"; + +$string['title_confirmdelete'] = "Confirm delete"; +$string['dialog_confirmdeleteskill'] = "Are you sure you want to delete this skill?"; +//$string[''] = ""; diff --git a/pix/brokenbadge.svg b/pix/brokenbadge.svg new file mode 100644 index 0000000..9040798 --- /dev/null +++ b/pix/brokenbadge.svg @@ -0,0 +1,2601 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Engels + Nederlands + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Engels + Nederlands + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/settings.php b/settings.php index 8a1164d..17b7ee7 100644 --- a/settings.php +++ b/settings.php @@ -18,16 +18,23 @@ $settings->add(new admin_setting_configtext( )); +$ADMIN->add("block_gradelevel", $settings); // Add the default levels page -$external = new admin_externalpage( - 'block_gradelevel_default_levels', - get_string('cfgpage_globallevels', 'block_gradelevel'), - $CFG->wwwroot . '/blocks/gradelevel/globallevels.php' - ); +$ADMIN->add("block_gradelevel", new admin_externalpage( + 'block_gradelevel_default_levels', + get_string('cfgpage_globallevels', 'block_gradelevel'), + $CFG->wwwroot . '/blocks/gradelevel/cfg_globallevels.php' + ) + ); -$ADMIN->add("block_gradelevel", $settings); -$ADMIN->add("block_gradelevel", $external); +// Add the skill configpage +$ADMIN->add("block_gradelevel", new admin_externalpage( + 'block_gradelevel_config_skills', + get_string('cfgpage_skills', 'block_gradelevel'), + $CFG->wwwroot . '/blocks/gradelevel/cfg_skills.php' + ) + ); $settings = null; \ No newline at end of file diff --git a/styles.css b/styles.css index 164f933..ad207e4 100644 --- a/styles.css +++ b/styles.css @@ -1,4 +1,4 @@ -figure.levelbadge { +figure.levelbadge, figure.dummybadge { text-align: center; } @@ -6,17 +6,19 @@ div.pointinfo { text-align: center; font-family: "Open Sans", Arial, helvetica, sans-serif; } - div.pointinfo.complete { - font-weight: bold; - } - div.pointinfo span.currentpoints { - font-weight: bold; - } +div.pointinfo.complete { + font-weight: bold; +} + +div.pointinfo span.currentpoints { + font-weight: bold; +} + +div.pointinfo span.leveluppoints { + font-weight: bold; +} - div.pointinfo span.leveluppoints { - font-weight: bold; - } div.teachermode { text-align: center; } @@ -29,6 +31,70 @@ input.jscolor[type=text] { width: 5em; } + +div.skill_set ul { + padding-left: 0px; + margin-left: 0px; +} + +div.skill_set li.skill_info { + position: relative; /* needed to have absolute positions be relative to this li */ + list-style: none; + display: inline-block; + width: 220px; + height: 220px; + border-color: #ccc; + border-width: 1px; + border-style: solid; + margin: 5px; + padding: 5px; + padding-top: 10px; +} + + +div.skill_set li.skill_info figcaption { + text-align: center; +} + +div.skill_set li.skill_info input[type=text] { + width: 150px; +} + +div.skill_set li.skill_info a.editicon { + position: absolute; + top: 5px; + right: 10px; +} + +div.skill_set li.skill_info a.deleteskill { + position: absolute; + top: 5px; + left: 7px; +} +div.skill_set li.skill_info a.deleteskill i.icon { + color: red; +} + +div.skill_set li.skill_info a.deleteskill i.icon.disabled { + color: #ccc; +} + +div.skill_set li.skill_info a.editname { + margin-left: 5px; +} + +table.level_config tr[data-rowid='-255'] { +} + +table.level_config tr[data-rowid='-255'] td { + +} + +table.level_config tr[data-rowid='-255'] input[data-name='points'] { + background-color: #F0F0F0; +} + + figure.levelset_icon { background-color: #ccc; background-image: -moz-linear-gradient(45deg, #aaa 25%, transparent 25%), -moz-linear-gradient(-45deg, #aaa 25%, transparent 25%), -moz-linear-gradient(45deg, transparent 75%, #aaa 75%), -moz-linear-gradient(-45deg, transparent 75%, #aaa 75%); diff --git a/version.php b/version.php index c9215c8..0cc95b0 100644 --- a/version.php +++ b/version.php @@ -1,4 +1,4 @@ component = 'block_gradelevel'; // Recommended since 2.0.2 (MDL-26035). Required since 3.0 (MDL-48494) -$plugin->version = 2018092101; // YYYYMMDDHH (year, month, day, 24-hr time) +$plugin->version = 2018092300; // YYYYMMDDHH (year, month, day, 24-hr time) $plugin->requires = 2018050800; // YYYYMMDDHH (This is the release version for Moodle 3.5) \ No newline at end of file