UNPKG

@oat-sa/tao-test-runner-qti

Version:
373 lines (348 loc) 15.4 kB
define(['jquery', 'lodash', 'i18n', 'ui/component', 'ui/hider', 'taoQtiTest/runner/plugins/controls/timer/component/countdown', 'handlebars', 'lib/handlebars/helpers', 'css!taoQtiTest/runner/plugins/controls/timer/component/css/timerbox.css'], function ($$1, _, __, component, hider, countdownFactory, Handlebars, Helpers0, timerbox_css) { 'use strict'; $$1 = $$1 && Object.prototype.hasOwnProperty.call($$1, 'default') ? $$1['default'] : $$1; _ = _ && Object.prototype.hasOwnProperty.call(_, 'default') ? _['default'] : _; __ = __ && Object.prototype.hasOwnProperty.call(__, 'default') ? __['default'] : __; component = component && Object.prototype.hasOwnProperty.call(component, 'default') ? component['default'] : component; hider = hider && Object.prototype.hasOwnProperty.call(hider, 'default') ? hider['default'] : hider; countdownFactory = countdownFactory && Object.prototype.hasOwnProperty.call(countdownFactory, 'default') ? countdownFactory['default'] : countdownFactory; Handlebars = Handlebars && Object.prototype.hasOwnProperty.call(Handlebars, 'default') ? Handlebars['default'] : Handlebars; Helpers0 = Helpers0 && Object.prototype.hasOwnProperty.call(Helpers0, 'default') ? Helpers0['default'] : Helpers0; if (!Helpers0.__initialized) { Helpers0(Handlebars); Helpers0.__initialized = true; } var Template = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) { this.compilerInfo = [4,'>= 1.0.0']; helpers = this.merge(helpers, Handlebars.helpers); data = data || {}; var buffer = "", stack1, helper, helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression, self=this, functionType="function"; function program1(depth0,data) { var buffer = "", helper, options; buffer += "\n <a href=\"#\" class=\"timer-toggler hidden\" title=\"" + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, "Hide timers", options) : helperMissing.call(depth0, "__", "Hide timers", options))) + "\" role=\"button\"><span class=\"icon-clock\"></span></a>\n "; return buffer; } buffer += "<div class=\"timer-box\">\n "; stack1 = helpers['if'].call(depth0, ((stack1 = (depth0 && depth0.zenMode)),stack1 == null || stack1 === false ? stack1 : stack1.enabled), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data}); if(stack1 || stack1 === 0) { buffer += stack1; } buffer += "\n <div class=\"timer-wrapper\" aria-hidden=\""; if (helper = helpers.ariaHidden) { stack1 = helper.call(depth0, {hash:{},data:data}); } else { helper = (depth0 && depth0.ariaHidden); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; } buffer += escapeExpression(stack1) + "\">\n\n </div>\n</div>\n"; return buffer; }); function timerboxTpl(data, options, asString) { var html = Template(data, options); return (asString || true) ? html : $(html); } /** * This program 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; under version 2 * of the License (non-upgradable). * * This program 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 this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Copyright (c) 2018 (original work) Open Assessment Technologies SA ; */ /** * Default config values, see below. */ var defaults = { zenMode: { enabled: true, startHidden: false } }; /** * Creates and initialize the timerbox component. * Please not the component IS NOT rendered. * You'll have to render it by yourself. * * @param {Object} config * @param {Boolean} [config.zenMode = true] - zen mode adds a button to hide the countdowns to stay zen * @param {Object[]} [config.timers] - the timers to start with * @param {Boolean} [config.displayWarning = true] - let the countdown display their warning (contextual) * @returns {timerbox} the component, initialized and rendered */ function timerboxFactory(config) { var $zenModeToggler; var $countdownContainer; /** * @typedef {Object} timerbox */ var timerbox = component({ /*** * Update the displayed timers. * Compare the current and the given, it will add,remove and update timers. * * @param {Object[]} timers - the new timers * @returns {Promise<Array>} resolves when all timers are up to date (with the result of all the update operations) * * @fires timerbox#update the update is done */ update: function update(timers) { var self = this; var updating = []; //remove timers var timerIdsToRemove = _.difference(_.keys(this.timers), _.keys(timers)); if (timerIdsToRemove.length) { _.forEach(timerIdsToRemove, function (timerId) { updating.push(self.removeTimer(timerId)); }); } //add/update _.forEach(timers, function (timer, id) { if (typeof self.timers[id] === 'undefined') { updating.push(self.addTimer(id, timer)); } else { updating.push(self.updateTimer(id, timer)); } }); return Promise.all(updating).then(function (results) { //show the toggler only if there's timers if (_.size(self.timers) > 0) { hider.show($zenModeToggler); } else { hider.hide($zenModeToggler); } /** * The timer box update is done * @event timerbox#update * @param {Object[]} timers - ALL update results (includes removals) */ self.trigger('update', results); return results; }); }, /** * Get the current timers * @return {Object[]} the timers */ getTimers: function getTimers() { return this.timers; }, /** * Adds a new timer to the box * @param {String} id - the timer unique identifier * @param {Object} timer - the new timer * @return {Promise<Object|Boolean>} resolves with the timer once added or false * * @fires timerbox#timerchange something changes in a timer * @fires timerbox#timeradd a new timer is in the box * @fires timerbox#timerstart a timer get started * @fires timerbox#timerstop a timer get stopped * @fires timerbox#timerend a timer get completed * @fires timerbox#change spread from the countdown */ addTimer: function addTimer(id, timer) { var self = this; if (this.is('rendered') && typeof this.timers[id] === 'undefined') { return new Promise(function (resolve) { var countdown = countdownFactory($countdownContainer, _.defaults(timer, { displayWarning: self.config.displayWarning })).on('render', function () { //keep track of the new timer //and the countdown component self.timers[id] = _.clone(timer); self.timers[id].countdown = this; /** * The timers have changed (add, update, remove) * @event timerbox#timerchange * @param {String} action - add, update, remove * @param {Object} timer */ self.trigger('timerchange', 'add', self.timers[id]); /** * A new timer is added * @event timerbox#timeradd * @param {Object} timer */ self.trigger('timeradd', self.timers[id]); resolve(self.timers[id]); }).on('start', function () { /** * A timer starts * @event timerbox#timerstart * @param {Object} timer */ self.trigger('timerstart', self.timers[id]); }).on('stop', function () { /** * A timer stops * @event timerbox#timerstop * @param {Object} timer */ self.trigger('timerstop', self.timers[id]); }).on('end', function () { /** * A timer ends * @event timerbox#timerend * @param {Object} timer */ self.trigger('timerend', self.timers[id]); }).on('change', function (value) { if (self.timers[id]) { self.trigger('timertick', this.remainingTime, self.timers[id].scope, self.timers[id].unansweredQuestions); // propogate current timer data //keep the current timer data in sync self.timers[id].remainingTime = value; } }); countdown.spread(self, ['error', 'change', 'warn', 'warnscreenreader']); }); } return Promise.resolve(false); }, /** * Updates an existing timer * @param {String} id - the timer unique identifier * @param {Object} timer - the new timer * @return {Promise<Object|Boolean>} resolves with the timer once updated or false * * @fires timerbox#timerchange something changes in a timer * @fires timerbox#timerupdate an existing timer is updated */ updateTimer: function updateTimer(id, timer) { if (this.is('rendered') && typeof this.timers[id] !== 'undefined') { this.timers[id].remainingTime = timer.remainingTime; this.timers[id].unansweredQuestions = timer.unansweredQuestions; this.timers[id].extraTime = timer.extraTime; if (_.isNumber(timer.remainingWithoutExtraTime)) { this.timers[id].remainingWithoutExtraTime = timer.remainingWithoutExtraTime; } if (this.timers[id].countdown) { this.timers[id].countdown.update(timer.remainingTime, timer.unansweredQuestions); } this.trigger('timerchange', 'update', this.timers[id]); /** * A timer has been updated with external values * @event timerbox#timerupdate * @param {Object} timer */ this.trigger('timerupdate', this.timers[id]); return Promise.resolve(this.timers[id]); } return Promise.resolve(false); }, /** * Remove a timer * @param {String} id - the timer unique identifier * @return {Promise<Object|Boolean>} resolves with the timer once removed or false * * @fires timerbox#timerchange something changes in a timer * @fires timerbox#timerremove a timer is removed */ removeTimer: function removeTimer(id) { var self = this; if (this.is('rendered') && typeof this.timers[id] !== 'undefined') { return new Promise(function (resolve) { /** * Artifact function, remove the timer from the component index */ var deindex = function deindex() { //keep a clone, without the component, for the event var removed = _.omit(self.timers[id], 'countdown'); //remove the timer from the list self.timers = _.omit(self.timers, id); self.trigger('timerchange', 'remove', removed); /** * A timer has been updated with external values * @event timerbox#timerupdate * @param {Object} timer */ self.trigger('timerremove', removed); resolve(removed); }; if (self.timers[id].countdown) { self.timers[id].countdown.on('destroy', deindex).destroy(); } else { deindex(); } }); } return Promise.resolve(); }, /** * Starts all the timers contained in the box * @returns {timerbox} chains */ start: function start() { _.forEach(this.timers, function (timer) { if (timer.countdown) { timer.countdown.start(); } }); return this; }, /** * Stops all the timers contained in the box * @returns {timerbox} chains */ stop: function stop() { _.forEach(this.timers, function (timer) { if (timer.countdown) { timer.countdown.stop(); } }); return this; }, /** * Show/hide the timers aka "zen mode" * @returns {timerbox} chains * @fires timerbox#zenchange */ toggleZenMode: function toggleZenMode() { if (this.is('rendered') && this.config.zenMode.enabled) { if (this.is('zen')) { this.setState('zen', false); $zenModeToggler.attr('title', __('Hide timers')); } else { this.setState('zen', true); $zenModeToggler.attr('title', __('Show timers')); } /** * @event timerbox#zenchange * @param {Boolean} isZen */ this.trigger('zenchange', this.is('zen')); } return this; } }, defaults).on('init', function () { //index the current timers this.timers = {}; }).on('render', function () { var self = this; var $element = this.getElement(); //where we append the countdowns components $countdownContainer = $$1('.timer-wrapper', $element); //set up the zen mode toggler if (this.config.zenMode.enabled) { $zenModeToggler = $$1('.timer-toggler', $element); self.setState('zen', !!self.config.zenMode.startHidden); $zenModeToggler.on('click', function (e) { e.preventDefault(); self.toggleZenMode(); }); } //if timers are provided with the config, we perform the 1st update if (this.config.timers) { this.update(this.config.timers); } }); timerbox.setTemplate(timerboxTpl); _.defer(function () { timerbox.init(config); }); return timerbox; } return timerboxFactory; });