UNPKG

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

Version:
310 lines (290 loc) 14.1 kB
define(['jquery', 'lodash', 'core/encoder/time', 'ui/component', 'handlebars', 'lib/handlebars/helpers', 'taoQtiTest/runner/helpers/getTimerMessage', 'ui/tooltip', 'css!taoQtiTest/runner/plugins/controls/timer/component/css/countdown.css', 'moment'], function ($$1, _, timeEncoder, component, Handlebars, Helpers0, getTimerMessage, tooltip, countdown_css, moment) { 'use strict'; $$1 = $$1 && Object.prototype.hasOwnProperty.call($$1, 'default') ? $$1['default'] : $$1; _ = _ && Object.prototype.hasOwnProperty.call(_, 'default') ? _['default'] : _; timeEncoder = timeEncoder && Object.prototype.hasOwnProperty.call(timeEncoder, 'default') ? timeEncoder['default'] : timeEncoder; component = component && Object.prototype.hasOwnProperty.call(component, 'default') ? component['default'] : component; Handlebars = Handlebars && Object.prototype.hasOwnProperty.call(Handlebars, 'default') ? Handlebars['default'] : Handlebars; Helpers0 = Helpers0 && Object.prototype.hasOwnProperty.call(Helpers0, 'default') ? Helpers0['default'] : Helpers0; getTimerMessage = getTimerMessage && Object.prototype.hasOwnProperty.call(getTimerMessage, 'default') ? getTimerMessage['default'] : getTimerMessage; tooltip = tooltip && Object.prototype.hasOwnProperty.call(tooltip, 'default') ? tooltip['default'] : tooltip; moment = moment && Object.prototype.hasOwnProperty.call(moment, 'default') ? moment['default'] : moment; 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, functionType="function", escapeExpression=this.escapeExpression; buffer += "<div class=\"countdown\" data-control=\""; if (helper = helpers.id) { stack1 = helper.call(depth0, {hash:{},data:data}); } else { helper = (depth0 && depth0.id); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; } buffer += escapeExpression(stack1) + "\" data-type=\""; if (helper = helpers.type) { stack1 = helper.call(depth0, {hash:{},data:data}); } else { helper = (depth0 && depth0.type); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; } buffer += escapeExpression(stack1) + "\" data-scope=\""; if (helper = helpers.scope) { stack1 = helper.call(depth0, {hash:{},data:data}); } else { helper = (depth0 && depth0.scope); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; } buffer += escapeExpression(stack1) + "\" title=\""; if (helper = helpers.label) { stack1 = helper.call(depth0, {hash:{},data:data}); } else { helper = (depth0 && depth0.label); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; } buffer += escapeExpression(stack1) + "\" disabled>\n <span aria-label=\""; if (helper = helpers.label) { stack1 = helper.call(depth0, {hash:{},data:data}); } else { helper = (depth0 && depth0.label); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; } buffer += escapeExpression(stack1) + "\" class=\"label truncate\">"; if (helper = helpers.label) { stack1 = helper.call(depth0, {hash:{},data:data}); } else { helper = (depth0 && depth0.label); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; } buffer += escapeExpression(stack1) + "</span>\n <span class=\"time\" aria-hidden=\"true\"></span>\n <div class=\"time--screenreader visible-hidden\"></div>\n</div>\n"; return buffer; }); function countdownTpl(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 ; */ //Precision is milliseconds var precision = 1000; /** * Default config values, see below. */ var defaults = { showBeforeStart: true, displayWarning: true }; /** * time to display warnings */ var warningTimeout = { info: 2000, success: 2000, warning: 4000, danger: 4000, error: 8000 }; /** * Creates, initialize and render a countdown component. * * @param {jQueryElement|HTMLElement} $container - where to append the countdown * @param {Object} config * @param {String} config.id - the timer unique identifier * @param {String} config.label - the text to display above the timer * @param {String} config.type - the type of countdown (to categorize them) * @param {String} [config.scope] - scope of a timer * @param {Number} [config.unansweredQuestions] - number of unanswered options * @param {Number} [config.remainingTime] - the current value of the countdown, in milliseconds * @param {Boolean} [config.showBeforeStart = true] - do we show the time before starting * @param {Boolean} [config.displayWarning = true] - do we display the warnings or trigger only the event * @param {Object[]} [config.warnings] - define warnings thresholds * @param {Number} [config.warnings.threshold] - when the warning is shown, in milliseconds * @param {String} [config.warnings.message] - the warning message * @param {String} [config.warnings.level = warn] - the feedback level in (success, info, warn, danger and error) * @returns {countdown} the component, initialized and rendered */ function countdownFactory($container, config) { let $time; let $timeScreenreader; /** * @typedef {Object} countdown */ var countdown = component({ /** * Update the countdown * @param {Number} remainingTime - the time remaining (milliseconds) * @param {Number} unansweredQuestions * @returns {countdown} chains * @fires countdown#change - when the value has changed * @fires countdown#warn - when a threshold is reached */ update: function udpate(remainingTime, unansweredQuestions) { var self = this; var encodedTime; var warningId; var warningMessage; if (!this.is('completed')) { if (remainingTime <= 0) { this.remainingTime = 0; } else { this.remainingTime = parseInt(remainingTime, 10); } if (this.is('rendered') && this.is('running')) { encodedTime = timeEncoder.encode(this.remainingTime / precision); if (encodedTime !== this.encodedTime) { this.encodedTime = encodedTime; const time = moment.duration(this.remainingTime / precision, 'seconds'); const hours = time.get('hours'); const minutes = time.get('minutes'); const seconds = time.get('seconds'); $time.text(this.encodedTime); $timeScreenreader.text(getTimerMessage(hours, minutes, seconds, unansweredQuestions, this.config.scope)); } if (this.warnings) { //the warnings have already be sorted warningId = _.findLastKey(this.warnings, function (warning) { return warning && !warning.shown && warning.threshold > 0 && warning.threshold >= self.remainingTime; }); if (warningId) { this.warnings[warningId].shown = true; if (_.isFunction(this.warnings[warningId].message)) { warningMessage = this.warnings[warningId].message(this.remainingTime); } else { warningMessage = this.warnings[warningId].message; } /** * Warn user the timer reach a threshold * @event countdown#warn * @param {String} message * @param {String} level */ this.trigger('warn', warningMessage, this.warnings[warningId].level); } } if (this.warningsForScreenreader) { //the warnings have already be sorted const screenreaderWarningId = _.findLastKey(this.warningsForScreenreader, warning => warning && !warning.shown && warning.threshold > 0 && warning.threshold >= self.remainingTime); if (screenreaderWarningId) { this.warningsForScreenreader[screenreaderWarningId].shown = true; /** * Warn user the timer reach a threshold * @event countdown#warnscreenreader * @param {Function} message * @param {Number} remainingTime * @param {String} scope */ this.trigger('warnscreenreader', this.warningsForScreenreader[screenreaderWarningId].message, self.remainingTime, this.warningsForScreenreader[screenreaderWarningId].scope); } } /** * The current value has changed * @event countdown#change * @param {Number} remainingTime - the updated time * @param {String} displayed - the displayed value */ this.trigger('change', this.remainingTime, encodedTime); } if (this.remainingTime === 0) { this.complete(); } } return this; }, /** * Starts the countdown * @returns {countdown} chains * @fires countdown#start */ start: function start() { if (this.is('rendered') && !this.is('running') && !this.is('completed')) { this.enable(); this.setState('running', true); /** * The count has started * @event countdown#start */ this.trigger('start'); } return this; }, /** * Stops the countdown (can be restarted then) * @returns {countdown} chains * @fires countdown#stop */ stop: function stop() { if (this.is('rendered') && this.is('running')) { this.setState('running', false); /** * The count is stopped * @event countdown#stop */ this.trigger('stop'); } return this; }, /** * Calls to complete the countdown, * it can't be resumed after. * * @returns {countdown} chains * * @fires countdown#complete * @fires countdown#end */ complete: function complete() { if (this.is('rendered') && this.is('running') && !this.is('completed')) { this.stop(); this.setState('completed', true); /** * The countdown has ended, is completed * @event countdown#complete * @event countdown#end (alias) */ this.trigger('complete end'); } return this; } }, defaults).on('init', function () { this.remainingTime = this.config.remainingTime; this.unansweredQuestions = this.config.unansweredQuestions; if (this.config.warnings) { this.warnings = _.sortBy(this.config.warnings, 'threshold'); } if (this.config.warningsForScreenreader) { this.warningsForScreenreader = _.sortBy(this.config.warningsForScreenreader, 'threshold'); } //auto renders this.render($container); }).on('render', function () { $time = $$1('.time', this.getElement()); $timeScreenreader = $$1('.time--screenreader', this.getElement()); if (this.config.showBeforeStart === true) { $time.text(timeEncoder.encode(this.remainingTime / precision)); } }).on('warn', function (message, level) { var countdownTooltip; level = level || 'warning'; if (this.is('rendered') && this.is('running') && _.isString(message) && !_.isEmpty(message)) { $time.removeClass('txt-success txt-info txt-warning txt-danger txt-error').addClass(`txt-${level}`); if (this.config.displayWarning === true) { countdownTooltip = tooltip.create(this.getElement(), message, { trigger: 'manual', theme: level, placement: 'bottom' }); countdownTooltip.show(); setTimeout(function () { countdownTooltip.hide(); countdownTooltip.dispose(); }, warningTimeout[level] || 2000); } } }); countdown.setTemplate(countdownTpl); _.defer(function () { countdown.init(config); }); return countdown; } return countdownFactory; });