%PDF- %PDF-
Direktori : /home/vacivi36/ava/blocks/accessreview/amd/src/ |
Current File : /home/vacivi36/ava/blocks/accessreview/amd/src/module.js |
// This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. /** * Manager for the accessreview block. * * @module block_accessreview/module * @author Max Larkin <max@brickfieldlabs.ie> * @copyright 2020 Brickfield Education Labs <max@brickfieldlabs.ie> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ import {call as fetchMany} from 'core/ajax'; import * as Templates from 'core/templates'; import {exception as displayError} from 'core/notification'; /** * The number of colours used to represent the heatmap. (Indexed on 0.) * @type {number} */ const numColours = 2; /** * The toggle state of the heatmap. * @type {boolean} */ let toggleState = true; /** * Renders the HTML template onto a particular HTML element. * @param {HTMLElement} element The element to attach the HTML to. * @param {number} errorCount The number of errors on this module/section. * @param {number} checkCount The number of checks triggered on this module/section. * @param {String} displayFormat * @param {Number} minViews * @param {Number} viewDelta * @returns {Promise} */ const renderTemplate = (element, errorCount, checkCount, displayFormat, minViews, viewDelta) => { // Calculate a weight? const weight = parseInt((errorCount - minViews) / viewDelta * numColours); const context = { resultPassed: !errorCount, classList: '', passRate: { errorCount, checkCount, failureRate: Math.round(errorCount / checkCount * 100), }, }; if (!element) { return Promise.resolve(); } const elementClassList = ['block_accessreview']; if (context.resultPassed) { elementClassList.push('block_accessreview_success'); } else if (weight) { elementClassList.push('block_accessreview_danger'); } else { elementClassList.push('block_accessreview_warning'); } const showIcons = (displayFormat == 'showicons') || (displayFormat == 'showboth'); const showBackground = (displayFormat == 'showbackground') || (displayFormat == 'showboth'); if (showBackground && !showIcons) { // Only the background is displayed. // No need to display the template. // Note: The case where both the background and icons are shown is handled later to avoid jankiness. element.classList.add(...elementClassList, 'alert'); return Promise.resolve(); } if (showIcons && !showBackground) { context.classList = elementClassList.join(' '); } // The icons are displayed either with, or without, the background. return Templates.renderForPromise('block_accessreview/status', context) .then(({html, js}) => { Templates.appendNodeContents(element, html, js); if (showBackground) { element.classList.add(...elementClassList, 'alert'); } return; }) .catch(); }; /** * Applies the template to all sections and modules on the course page. * * @param {Number} courseId * @param {String} displayFormat * @param {Boolean} updatePreference * @returns {Promise} */ const showAccessMap = (courseId, displayFormat, updatePreference = false) => { // Get error data. return Promise.all(fetchReviewData(courseId, updatePreference)) .then(([sectionData, moduleData]) => { // Get total data. const {minViews, viewDelta} = getErrorTotals(sectionData, moduleData); sectionData.forEach(section => { const element = document.querySelector(`#section-${section.section} .summary`); if (!element) { return; } renderTemplate(element, section.numerrors, section.numchecks, displayFormat, minViews, viewDelta); }); moduleData.forEach(module => { const element = document.getElementById(`module-${module.cmid}`); if (!element) { return; } renderTemplate(element, module.numerrors, module.numchecks, displayFormat, minViews, viewDelta); }); // Change the icon display. document.querySelector('.icon-accessmap').classList.remove(...['fa-eye-slash']); document.querySelector('.icon-accessmap').classList.add(...['fa-eye']); return { sectionData, moduleData, }; }) .catch(displayError); }; /** * Hides or removes the templates from the HTML of the current page. * * @param {Boolean} updatePreference */ const hideAccessMap = (updatePreference = false) => { // Removes the added elements. document.querySelectorAll('.block_accessreview_view').forEach(node => node.remove()); const classList = [ 'block_accessreview', 'block_accessreview_success', 'block_accessreview_warning', 'block_accessreview_danger', 'block_accessreview_view', 'alert', ]; // Removes the added classes. document.querySelectorAll('.block_accessreview').forEach(node => node.classList.remove(...classList)); if (updatePreference) { setToggleStatePreference(false); } // Change the icon display. document.querySelector('.icon-accessmap').classList.remove(...['fa-eye']); document.querySelector('.icon-accessmap').classList.add(...['fa-eye-slash']); }; /** * Toggles the heatmap on/off. * * @param {Number} courseId * @param {String} displayFormat */ const toggleAccessMap = (courseId, displayFormat) => { toggleState = !toggleState; if (!toggleState) { hideAccessMap(true); } else { showAccessMap(courseId, displayFormat, true); } }; /** * Parses information on the errors, generating the min, max and totals. * * @param {Object[]} sectionData The error data for course sections. * @param {Object[]} moduleData The error data for course modules. * @returns {Object} An object representing the extra error information. */ const getErrorTotals = (sectionData, moduleData) => { const totals = { totalErrors: 0, totalUsers: 0, minViews: 0, maxViews: 0, viewDelta: 0, }; [].concat(sectionData, moduleData).forEach(item => { totals.totalErrors += item.numerrors; if (item.numerrors < totals.minViews) { totals.minViews = item.numerrors; } if (item.numerrors > totals.maxViews) { totals.maxViews = item.numerrors; } totals.totalUsers += item.numchecks; }); totals.viewDelta = totals.maxViews - totals.minViews + 1; return totals; }; const registerEventListeners = (courseId, displayFormat) => { document.addEventListener('click', e => { if (e.target.closest('#toggle-accessmap')) { e.preventDefault(); toggleAccessMap(courseId, displayFormat); } }); }; /** * Set the user preference for the toggle value. * * @param {Boolean} toggleState * @returns {Promise} */ const getTogglePreferenceParams = toggleState => { return { methodname: 'core_user_update_user_preferences', args: { preferences: [{ type: 'block_accessreviewtogglestate', value: toggleState, }], } }; }; const setToggleStatePreference = toggleState => fetchMany([getTogglePreferenceParams(toggleState)]); /** * Fetch the review data. * * @param {Number} courseid * @param {Boolean} updatePreference * @returns {Promise[]} */ const fetchReviewData = (courseid, updatePreference = false) => { const calls = [ { methodname: 'block_accessreview_get_section_data', args: {courseid} }, { methodname: 'block_accessreview_get_module_data', args: {courseid} }, ]; if (updatePreference) { calls.push(getTogglePreferenceParams(true)); } return fetchMany(calls); }; /** * Setting up the access review module. * @param {number} toggled A number represnting the state of the review toggle. * @param {string} displayFormat A string representing the display format for icons. * @param {number} courseId The course ID. */ export const init = (toggled, displayFormat, courseId) => { // Settings consts. toggleState = toggled == 1; if (toggleState) { showAccessMap(courseId, displayFormat); } registerEventListeners(courseId, displayFormat); };