UNPKG

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

Version:
270 lines (249 loc) 9.16 kB
define(['jquery', 'i18n', 'ui/hider', 'ui/transformer', 'util/shortcut', 'util/namespace', 'taoTests/runner/plugin', 'taoQtiTest/runner/helpers/map'], function ($, __, hider, transformer, shortcut, namespaceHelper, pluginFactory, mapHelper) { 'use strict'; $ = $ && Object.prototype.hasOwnProperty.call($, 'default') ? $['default'] : $; __ = __ && Object.prototype.hasOwnProperty.call(__, 'default') ? __['default'] : __; transformer = transformer && Object.prototype.hasOwnProperty.call(transformer, 'default') ? transformer['default'] : transformer; shortcut = shortcut && Object.prototype.hasOwnProperty.call(shortcut, 'default') ? shortcut['default'] : shortcut; namespaceHelper = namespaceHelper && Object.prototype.hasOwnProperty.call(namespaceHelper, 'default') ? namespaceHelper['default'] : namespaceHelper; pluginFactory = pluginFactory && Object.prototype.hasOwnProperty.call(pluginFactory, 'default') ? pluginFactory['default'] : pluginFactory; mapHelper = mapHelper && Object.prototype.hasOwnProperty.call(mapHelper, 'default') ? mapHelper['default'] : mapHelper; /** * 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) 2016-2022 (original work) Open Assessment Technologies SA; * * @author dieter <dieter@taotesting.com> * @author Alexander Zagovorichev <zagovorichev@1pt.com> */ /** * The standard zoom level, in percentage * @type {Number} */ const standard = 100; /** * Zoom-In/Zoom-Out steps * @type {Number} */ const increment = 10; /** * The zoom boundaries, in percentage * @type {Object} */ const threshold = { lower: 10, upper: 200 }; /** * Sets the zoom level * @param {jQuery} $target * @param {Number} level - Zoom percentage */ const _setZoomLevel = ($target, level) => { const $parent = $target.parent(); const newScale = level / standard; const isOverZoom = $parent.outerWidth(true) < $target.width() * newScale; if (isOverZoom) { transformer.setTransformOrigin($target, '0 0'); $parent.css('margin-left', '0'); } else { transformer.setTransformOrigin($target, '50% 0'); $parent.css('margin-left', ''); } transformer.scale($target, newScale); }; /** * Restores the standard zoom level * @param {jQuery} $target */ const _resetZoom = $target => { transformer.reset($target); }; /** * Forces a browser repaint * Solution from http://stackoverflow.com/questions/3485365/how-can-i-force-webkit-to-redraw-repaint-to-propagate-style-changes?answertab=votes#tab-top * @param {jQuery} $target */ const forceRepaint = $target => { const sel = $target[0]; if (sel) { sel.style.display = 'none'; sel.offsetHeight; // no need to store this anywhere, the reference is enough sel.style.display = ''; } }; /** * Returns the configured plugin */ var zoom = pluginFactory({ name: 'zoom', /** * Initialize the plugin (called during runner's init) */ init() { const testRunner = this.getTestRunner(); const testRunnerOptions = testRunner.getOptions(); const pluginShortcuts = (testRunnerOptions.shortcuts || {})[this.getName()] || {}; const testRunnerContainer = this.getAreaBroker().getContainer().get(0); /** * Checks if the plugin is currently available * @returns {Boolean} */ const isConfigured = () => { //to be activated with the special category x-tao-option-zoom return mapHelper.hasItemCategory(testRunner.getTestMap(), testRunner.getTestContext().itemIdentifier, 'zoom', true); }; /** * Is zoom activated ? if not, then we hide the plugin */ const togglePlugin = () => { if (isConfigured()) { //allow zoom this.show(); } else { this.hide(); } }; const zoomAction = dir => { const inc = increment * dir; if (this.$zoomTarget) { const el = this.$zoomTarget[0]; const before = el.getBoundingClientRect(); let sx = this.$container.scrollLeft(); let sy = this.$container.scrollTop(); this.zoom = Math.max(threshold.lower, Math.min(threshold.upper, this.zoom + inc)); if (this.zoom === standard) { _resetZoom(this.$zoomTarget); } else { _setZoomLevel(this.$zoomTarget, this.zoom); } testRunnerContainer.style.setProperty('--tool-zoom-level', this.zoom / standard); // force a browser repaint to fix a scrollbar issue with WebKit forceRepaint(this.$zoomTarget); const after = el.getBoundingClientRect(); sx = Math.max(0, sx + (after.width - before.width) / 2); sy = Math.max(0, sy + (after.height - before.height) / 2); this.$container.scrollLeft(sx).scrollTop(sy); } }; const zoomIn = () => { if (this.getState('enabled') !== false) { zoomAction(1); } }; const zoomOut = () => { if (this.getState('enabled') !== false) { zoomAction(-1); } }; /** * Reapplys the same zoom level to the target * It can be useful if the element was (visually-)hidden why zoom happened */ const zoomReApply = () => { if (this.zoom !== standard) { _setZoomLevel(this.$zoomTarget, this.zoom); } }; //build element (detached) this.buttonZoomOut = this.getAreaBroker().getToolbox().createEntry({ control: 'zoomOut', title: __('Zoom out'), icon: 'remove' }); this.buttonZoomIn = this.getAreaBroker().getToolbox().createEntry({ control: 'zoomIn', title: __('Zoom in'), icon: 'add' }); //attach behavior this.buttonZoomIn.on('click', e => { e.preventDefault(); testRunner.trigger('tool-zoomin'); }); //attach behavior this.buttonZoomOut.on('click', e => { e.preventDefault(); testRunner.trigger('tool-zoomout'); }); if (testRunnerOptions.allowShortcuts) { if (pluginShortcuts.in) { shortcut.add(namespaceHelper.namespaceAll(pluginShortcuts.in, this.getName(), true), () => { testRunner.trigger('tool-zoomin'); }, { avoidInput: true }); } if (pluginShortcuts.out) { shortcut.add(namespaceHelper.namespaceAll(pluginShortcuts.out, this.getName(), true), () => { testRunner.trigger('tool-zoomout'); }, { avoidInput: true }); } } //start disabled togglePlugin(); this.disable(); //update plugin state based on changes testRunner.on('loaditem', () => { this.zoom = standard; togglePlugin(); this.disable(); }).on('renderitem', () => { this.$container = $('#qti-content'); this.$zoomTarget = $('.qti-item'); this.enable(); }).on('enabletools', () => { this.enable(); }).on('disabletools unloaditem', () => { this.disable(); }).on('tool-zoomin', zoomIn).on('tool-zoomout', zoomOut).on('tool-zoomreapply', zoomReApply); }, /** * Called during the runner's destroy phase */ destroy() { shortcut.remove(`.${this.getName()}`); }, /** * Enable the button */ enable() { this.buttonZoomIn.enable(); this.buttonZoomOut.enable(); }, /** * Disable the button */ disable() { this.buttonZoomIn.disable(); this.buttonZoomOut.disable(); }, /** * Show the button */ show() { this.buttonZoomIn.show(); this.buttonZoomOut.show(); }, /** * Hide the button */ hide() { this.buttonZoomIn.hide(); this.buttonZoomOut.hide(); } }); return zoom; });