/*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/debugger' ], function ($, str, ajax, Debugger) { let debug = Debugger("renderbadge"); debug.enable(); // function is used to overcome Edge's non-support of CSS4 hex rgba notation (@nov 2018) /* eslint-disable no-bitwise*/ function hexToRgbA(hex){ /*eslint no-bitwise: "off"*/ if(/^#([A-Fa-f0-9]{3})$/.test(hex)){ let h= hex.substring(1).split(''); let c= '0x'+[h[0], h[0], h[1], h[1], h[2], h[2]].join(''); return 'rgb('+[(c>>16)&255, (c>>8)&255, c&255].join(',')+')'; } if(/^#([A-Fa-f0-9]{6})$/.test(hex)){ let h= hex.substring(1).split(''); let c= '0x'+h.join(''); return 'rgb('+[(c>>16)&255, (c>>8)&255, c&255].join(',')+')'; } if(/^#([A-Fa-f0-9]{8})$/.test(hex)){ let h= hex.substring(1).split(''); let c = '0x' + [h[0],h[1],h[2],h[3],h[4],h[5],].join(''); let a= '0x' + [h[6],h[7],].join(''); return 'rgba('+[(c>>16)&255, (c>>8)&255, c&255].join(',')+','+a/255.0+')'; } throw Error('Invalid hex code: ' + hex); } /* eslint-enable no-bitwise */ let self = { init: function init() { debug.info("Setting up badge renderers"); // Put whatever you like here. $ is available // to you as normal. $("figure.levelbadge").each(function () { 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 let colors = { base: props.color, light: self.shadeBlendConvert(0.6, props.color), // 60% lighter dark: self.shadeBlendConvert(0.3, props.color), // 30% lighter lightPoint: self.shadeBlendConvert(0.8, props.color), // 80% lighter reflection: { lightest: "#ffffff51", darkest: "#ffffff20", }, radialGradient: { x0: 0.75, y0: 0.25, r0: 0.05, x1: 0.6, y1: 0.4, r1: 0.4, }, levelText: "white", }; // size and position configuration let config = { size: Math.min(props.height, props.width), borderWidth: 0.05, // factor of image size reflection: { angle: -20, // relative to horizontal offset: 0.125, // relative to radius }, levelText: { x: 0.5, y: 0.9, size: 0.2, font: "Open Sans, Arial, helvetica, sans-serif", }, icon: { x: 0.5, y: 0.47, scale: 0.7, // scale to this fraction of image size } }; debug.info(" Config", config); debug.info(" Colors", colors); debug.info(" Props", props); // draw main circle let baseGradient = ctx.createRadialGradient( config.size * colors.radialGradient.x0, config.size * colors.radialGradient.y0, (config.size ) * colors.radialGradient.r0, config.size * colors.radialGradient.x1, config.size * colors.radialGradient.y1, (config.size ) * colors.radialGradient.r1 ); baseGradient.addColorStop(0, hexToRgbA(colors.lightPoint)); baseGradient.addColorStop(1, hexToRgbA(colors.base)); ctx.beginPath(); ctx.fillStyle = baseGradient; ctx.arc(0.5 * config.size, 0.5 * config.size, config.size / 2, 0, 2 * Math.PI); ctx.fill(); // draw main reflection let rflOffset = Math.asin(config.reflection.offset); let rflAngleRad = (config.reflection.angle / 360.0) * 2 * Math.PI; debug.info("rflAngleRad:",rflAngleRad); let rflGradient = ctx.createLinearGradient( (0.5 - config.reflection.offset * Math.sin(rflAngleRad))/2 * config.size, (0.5 + config.reflection.offset * Math.cos(rflAngleRad))/2 * config.size, Math.sin(rflAngleRad)/2 * config.size, Math.cos(rflAngleRad)/2 * config.size ); debug.info("rflGradient",rflGradient); rflGradient.addColorStop(0, hexToRgbA(colors.reflection.lightest)); rflGradient.addColorStop(1, hexToRgbA(colors.reflection.darkest)); ctx.beginPath(); ctx.fillStyle = rflGradient; ctx.arc(0.5 * config.size, 0.5 * config.size, config.size / 2, 0 + rflOffset + rflAngleRad, Math.PI - rflOffset + rflAngleRad); ctx.fill(); debug.info("Starting with border"); // draw empty border let strokeWidth = config.size * config.borderWidth; ctx.beginPath(); ctx.strokeStyle = colors.light; ctx.lineWidth = strokeWidth; ctx.arc(0.5 * config.size, 0.5 * config.size, config.size / 2 - strokeWidth / 2, 0, 2 * Math.PI); ctx.stroke(); // draw current progress border ctx.beginPath(); ctx.strokeStyle = colors.dark; ctx.lineWidth = strokeWidth; ctx.arc(0.5 * config.size, 0.5 * config.size, config.size / 2 - strokeWidth / 2, 0 - Math.PI / 2, // -90 degrees (top) props.progress * 2 * Math.PI - Math.PI / 2 // fraction of whole circle offset with -90 degrees ); ctx.stroke(); if (props.level) { // write level in lower part ctx.font = "" + config.size * config.levelText.size + "px " + config.levelText.font; ctx.fillStyle = colors.levelText; ctx.textAlign = "center"; ctx.fillText("" + props.level, config.size * config.levelText.x, config.size * config.levelText.y); } /* var imageObj = new Image(); imageObj.onload = function () { ctx.drawImage(this, 15, 15, 120, 120); }; imageObj.src = "https://dev.miqra.nl/blocks/gradelevel/pix/undefinedskill.svg"; */ // paint image in center if (props.image) { let iconImg = new Image(); iconImg.onload = function () { let imPos = { x: 0, // to be determined later y: 0, // to be determined later w: config.size * config.icon.scale, h: config.size * config.icon.scale, }; // preserve aspect ratio if (this.width > this.height) { imPos.h *= this.height / this.width; } else { imPos.w *= this.width / this.height; } // calculate x and y imPos.x = (config.size * config.icon.x) - (imPos.w / 2); imPos.y = (config.size * config.icon.y) - (imPos.h / 2); ctx.drawImage(this, imPos.x, imPos.y, imPos.w, imPos.h); }; //debug.info("Image: ",props.image); iconImg.src = props.image; } // complete }, fetchProperties: function fetchProperties(figure) { let $figure = $(figure); let $image = $figure.find("img"); let image = null; if ($image.length > 0) { image = $image.attr("src"); } return { progress: $figure.attr("data-progress"), width: $figure.attr("data-width"), height: $figure.attr("data-height"), color: "#"+$figure.attr("data-color"), level: $figure.attr("data-level"), image: image, }; }, shadeBlendConvert: function shadeBlendConvert(p, from, to) { /* eslint-disable no-bitwise, max-len, curly, no-var, no-unused-expressions */ // Code for this function was taken from https://github.com/PimpTrizkit/PJs pSBC.js if (typeof (p) != "number" || p < -1 || p > 1 || typeof (from) != "string" || (from[0] != 'r' && from[0] != '#') || (to && typeof (to) != "string")) return null; //ErrorCheck if (!this.sbcRip) this.sbcRip = (d) => { let l = d.length, RGB = {}; if (l > 9) { d = d.split(","); if (d.length < 3 || d.length > 4) return null;//ErrorCheck RGB[0] = i(d[0].split("(")[1]), RGB[1] = i(d[1]), RGB[2] = i(d[2]), RGB[3] = d[3] ? parseFloat(d[3]) : -1; } else { if (l == 8 || l == 6 || l < 4) return null; //ErrorCheck if (l < 6) d = "#" + d[1] + d[1] + d[2] + d[2] + d[3] + d[3] + (l > 4 ? d[4] + "" + d[4] : ""); //3 or 4 digit d = i(d.slice(1), 16), RGB[0] = d >> 16 & 255, RGB[1] = d >> 8 & 255, RGB[2] = d & 255, RGB[3] = -1; if (l == 9 || l == 5) RGB[3] = r((RGB[2] / 255) * 10000) / 10000, RGB[2] = RGB[1], RGB[1] = RGB[0], RGB[0] = d >> 24 & 255; } return RGB; }; var i = parseInt, r = Math.round, h = from.length > 9, h = typeof (to) == "string" ? to.length > 9 ? true : to == "c" ? !h : false : h, b = p < 0, p = b ? p * -1 : p, to = to && to != "c" ? to : b ? "#000000" : "#FFFFFF", f = this.sbcRip(from), t = this.sbcRip(to); if (!f || !t) return null; //ErrorCheck if (h) return "rgb" + (f[3] > -1 || t[3] > -1 ? "a(" : "(") + r((t[0] - f[0]) * p + f[0]) + "," + r((t[1] - f[1]) * p + f[1]) + "," + r((t[2] - f[2]) * p + f[2]) + (f[3] < 0 && t[3] < 0 ? ")" : "," + (f[3] > -1 && t[3] > -1 ? r(((t[3] - f[3]) * p + f[3]) * 10000) / 10000 : t[3] < 0 ? f[3] : t[3]) + ")"); else return "#" + (0x100000000 + r((t[0] - f[0]) * p + f[0]) * 0x1000000 + r((t[1] - f[1]) * p + f[1]) * 0x10000 + r((t[2] - f[2]) * p + f[2]) * 0x100 + (f[3] > -1 && t[3] > -1 ? r(((t[3] - f[3]) * p + f[3]) * 255) : t[3] > -1 ? r(t[3] * 255) : f[3] > -1 ? r(f[3] * 255) : 255)).toString(16).slice(1, f[3] > -1 || t[3] > -1 ? undefined : -2); /* eslint-enable no-bitwise, max-len, curly, no-var, no-unused-expressions */ }, }; return self; });