// 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) { 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); }); }, 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 } } console.info("Config", config); console.info("Colors", colors); console.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, colors.lightPoint); baseGradient.addColorStop(1, 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; 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 ); rflGradient.addColorStop(0, colors.reflection.lightest); rflGradient.addColorStop(1, 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(); // 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, 15, 15, 120, 120);//imPos.x, imPos.y, imPos.w, imPos.h); } console.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) { // 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); }, }; return self; });